summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEuAndreh <eu@euandre.org>2024-03-16 06:03:40 -0300
committerEuAndreh <eu@euandre.org>2024-03-16 06:03:40 -0300
commitc3387c891a4376a558f3c09c804f7aa45422ad41 (patch)
tree303eb83bf1064e51a0023a93331550d952d678b6
parentsrc/hero.mjs: Add "upgrade" and "socket" keys to `req` (diff)
downloadpapod-c3387c891a4376a558f3c09c804f7aa45422ad41.tar.gz
papod-c3387c891a4376a558f3c09c804f7aa45422ad41.tar.xz
src/hero.mjs: Add validateUpgrade(), computeHash() and their helper functions
-rw-r--r--src/hero.mjs99
-rw-r--r--tests/js/hero.mjs171
2 files changed, 268 insertions, 2 deletions
diff --git a/src/hero.mjs b/src/hero.mjs
index 859d86f..6df57ea 100644
--- a/src/hero.mjs
+++ b/src/hero.mjs
@@ -38,6 +38,105 @@ export const makeLogger = (writerFn = console.error) => ({
export const log = makeLogger();
+export const isValidMethod = method =>
+ method === "GET";
+
+export const isValidUpgrade = val =>
+ val.toLowerCase() === "websocket";
+
+export const isValidKey = key =>
+ /^[0-9a-zA-Z+/]{22}==$/.test(key);
+
+export const isValidVersion = n =>
+ n === 13;
+
+export const validateUpgrade = (method, headers) => {
+ const upgrade = headers["upgrade"];
+ const key = headers["sec-websocket-key"];
+ const versionStr = headers["sec-websocket-version"];
+ const version = parseInt(versionStr);
+
+ if (!isValidMethod(method)) {
+ return {
+ isValid: false,
+ response: {
+ status: 405,
+ },
+ };
+ }
+
+ if (!upgrade) {
+ return {
+ isValid: false,
+ response: {
+ status: 400,
+ body: 'Missing "Upgrade" header\n',
+ },
+ };
+ }
+
+ if (!isValidUpgrade(upgrade)) {
+ return {
+ isValid: false,
+ response: {
+ status: 400,
+ body: 'Invalid "Upgrade" value\n',
+ },
+ };
+ }
+
+ if (!key) {
+ return {
+ isValid: false,
+ response: {
+ status: 400,
+ body: 'Missing "Sec-WebSocket-Key" header\n',
+ },
+ };
+ }
+
+ if (!isValidKey(key)) {
+ return {
+ isValid: false,
+ response: {
+ status: 400,
+ body: 'Invalid "Sec-WebSocket-Key" value\n',
+ },
+ };
+ }
+
+ if (!version) {
+ return {
+ isValid: false,
+ response: {
+ status: 400,
+ body: 'Missing "Sec-WebSocket-Version" header\n',
+ },
+ };
+ }
+
+ if (!isValidVersion(version)) {
+ return {
+ isValid: false,
+ response: {
+ status: 400,
+ body: 'Invalid "Sec-WebSocket-Version" value\n',
+ },
+ };
+ }
+
+ return {
+ isValid: true,
+ };
+};
+
+const GUID_MAGIC_NUMBER = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+export const computeHash = key =>
+ crypto
+ .createHash("SHA1")
+ .update(key + GUID_MAGIC_NUMBER)
+ .digest("base64");
+
export const interceptorsFn = ({
uuidFn = crypto.randomUUID,
logger = log,
diff --git a/tests/js/hero.mjs b/tests/js/hero.mjs
index b183e9a..bbb03ec 100644
--- a/tests/js/hero.mjs
+++ b/tests/js/hero.mjs
@@ -1,8 +1,7 @@
import assert from "node:assert/strict";
-import child_process from "node:child_process";
import fs from "node:fs";
import http from "node:http";
-import procees from "node:process";
+import process from "node:process";
import * as runner from "../runner.mjs";
import * as u from "../../src/utils.mjs";
@@ -12,6 +11,12 @@ import {
configLogger,
logit,
makeLogger,
+ isValidMethod,
+ isValidUpgrade,
+ isValidKey,
+ isValidVersion,
+ validateUpgrade,
+ computeHash,
interceptorsFn,
interceptors,
defaultInterceptors,
@@ -192,6 +197,162 @@ const test_makeLogger = async t => {
});
};
+const test_isValidMethod = async t => {
+ t.start("isValidMethod()");
+
+ await t.test("we only accept a single value", () => {
+ assert.ok(isValidMethod("GET"));
+ assert.ok(!isValidMethod("get"));
+ assert.ok(!isValidMethod("PUT"));
+ })
+};
+
+const test_isValidUpgrade = async t => {
+ t.start("isValidUpgrade()");
+
+ await t.test("we ignoee the case", () => {
+ assert.ok(isValidUpgrade("Websocket"));
+ assert.ok(isValidUpgrade("WebSocket"));
+ assert.ok(isValidUpgrade("websocket"));
+ assert.ok(!isValidUpgrade("web socket"));
+ });
+};
+
+const test_isValidKey = async t => {
+ t.start("isValidKey()");
+
+ await t.test("RFC example value", () => {
+ const key = "dGhlIHNhbXBsZSBub25jZQ==";
+ assert.ok(isValidKey(key));
+ });
+
+ await t.test("wrong example values", () => {
+ const key1 = "_GhlIHNhbXBsZSBub25jZQ==";
+ const key2 = "dGhlIHNhbXBsZSBub25jZQ=";
+ assert.ok(!isValidKey(key1));
+ assert.ok(!isValidKey(key2));
+ });
+};
+
+const test_isValidVersion = async t => {
+ t.start("isValidVersion()");
+
+ await t.test("we only accept a single value", () => {
+ assert.ok(isValidVersion(13));
+ assert.ok(!isValidVersion(9));
+ assert.ok(!isValidVersion(10));
+ assert.ok(!isValidVersion(11));
+ assert.ok(!isValidVersion(12));
+ });
+};
+
+const test_validateUpgrade = async t => {
+ t.start("validateUpgrade()");
+
+ await t.test("invalid method", () => {
+ assert.deepEqual(validateUpgrade("POST", {}), {
+ isValid: false,
+ response: {
+ status: 405,
+ },
+ });
+ });
+
+ await t.test("missing upgrade", () => {
+ assert.deepEqual(validateUpgrade("GET", {}), {
+ isValid: false,
+ response: {
+ status: 400,
+ body: 'Missing "Upgrade" header\n',
+ },
+ });
+ });
+
+ await t.test("invalid upgrade", () => {
+ assert.deepEqual(validateUpgrade("GET", {
+ "upgrade": "web socket",
+ }), {
+ isValid: false,
+ response: {
+ status: 400,
+ body: 'Invalid "Upgrade" value\n',
+ },
+ });
+ });
+
+ await t.test("missing sec-websocket-key", () => {
+ assert.deepEqual(validateUpgrade("GET", {
+ "upgrade": "websocket",
+ }), {
+ isValid: false,
+ response: {
+ status: 400,
+ body: 'Missing "Sec-WebSocket-Key" header\n',
+ },
+ });
+ });
+
+ await t.test("invalid sec-websocket-key", () => {
+ assert.deepEqual(validateUpgrade("GET", {
+ "upgrade": "websocket",
+ "sec-websocket-key": "bad value",
+ }), {
+ isValid: false,
+ response: {
+ status: 400,
+ body: 'Invalid "Sec-WebSocket-Key" value\n',
+ },
+ });
+ });
+
+ await t.test("missing sec-websocket-version", () => {
+ assert.deepEqual(validateUpgrade("GET", {
+ "upgrade": "websocket",
+ "sec-websocket-key": "aaaaabbbbbcccccdddddee==",
+ }), {
+ isValid: false,
+ response: {
+ status: 400,
+ body: 'Missing "Sec-WebSocket-Version" header\n',
+ },
+ });
+ });
+
+ await t.test("invalid sec-websocket-version", () => {
+ assert.deepEqual(validateUpgrade("GET", {
+ "upgrade": "websocket",
+ "sec-websocket-key": "aaaaabbbbbcccccdddddee==",
+ "sec-websocket-version": "12",
+ }), {
+ isValid: false,
+ response: {
+ status: 400,
+ body: 'Invalid "Sec-WebSocket-Version" value\n',
+ },
+ });
+ });
+
+ await t.test("valid upgrade", () => {
+ assert.deepEqual(validateUpgrade("GET", {
+ "upgrade": "websocket",
+ "sec-websocket-key": "aaaaabbbbbcccccdddddee==",
+ "sec-websocket-version": "13",
+ }), {
+ isValid: true,
+ });
+ });
+};
+
+const test_computeHash = async t => {
+ t.start("computeHash()");
+
+ await t.test("RFC example value", () => {
+ const key = "dGhlIHNhbXBsZSBub25jZQ==";
+ const hash = "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=";
+ assert.equal(computeHash(key), hash);
+ });
+};
+
const test_interceptorsFn = async t => {
const next = x => x;
@@ -2025,6 +2186,12 @@ await runner.runTests([
test_configLogger,
test_logit,
test_makeLogger,
+ test_isValidMethod,
+ test_isValidUpgrade,
+ test_isValidKey,
+ test_isValidVersion,
+ test_validateUpgrade,
+ test_computeHash,
test_interceptorsFn,
test_chainInterceptors,
test_wrapHandler,