summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/tests/unit/http_test.ts29
-rw-r--r--ext/http/01_http.js92
2 files changed, 109 insertions, 12 deletions
diff --git a/cli/tests/unit/http_test.ts b/cli/tests/unit/http_test.ts
index 73ff35e09..570399841 100644
--- a/cli/tests/unit/http_test.ts
+++ b/cli/tests/unit/http_test.ts
@@ -14,6 +14,11 @@ import {
} from "./test_util.ts";
import { join } from "../../../test_util/std/path/mod.ts";
+const {
+ buildCaseInsensitiveCommaValueFinder,
+ // @ts-expect-error TypeScript (as of 3.7) does not support indexing namespaces by symbol
+} = Deno[Deno.internal];
+
async function writeRequestAndReadResponse(conn: Deno.Conn): Promise<string> {
const encoder = new TextEncoder();
const decoder = new TextDecoder();
@@ -2612,6 +2617,30 @@ Deno.test({
},
});
+Deno.test("case insensitive comma value finder", async (t) => {
+ const cases = /** @type {[string, boolean][]} */ ([
+ ["websocket", true],
+ ["wEbSOcKET", true],
+ [",wEbSOcKET", true],
+ [",wEbSOcKET,", true],
+ [", wEbSOcKET ,", true],
+ ["test, wEbSOcKET ,", true],
+ ["test ,\twEbSOcKET\t\t ,", true],
+ ["test , wEbSOcKET", true],
+ ["test, asdf,web,wEbSOcKET", true],
+ ["test, asdf,web,wEbSOcKETs", false],
+ ["test, asdf,awebsocket,wEbSOcKETs", false],
+ ]);
+
+ const findValue = buildCaseInsensitiveCommaValueFinder("websocket");
+ for (const [input, expected] of cases) {
+ await t.step(input.toString(), () => {
+ const actual = findValue(input);
+ assertEquals(actual, expected);
+ });
+ }
+});
+
async function httpServerWithErrorBody(
listener: Deno.Listener,
compression: boolean,
diff --git a/ext/http/01_http.js b/ext/http/01_http.js
index 1da371e8d..f9e15e7d5 100644
--- a/ext/http/01_http.js
+++ b/ext/http/01_http.js
@@ -1,5 +1,6 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
const core = globalThis.Deno.core;
+const internals = globalThis.__bootstrap.internals;
const primordials = globalThis.__bootstrap.primordials;
const { BadResourcePrototype, InterruptedPrototype, ops } = core;
import * as webidl from "internal:deno_webidl/00_webidl.js";
@@ -40,18 +41,18 @@ import {
} from "internal:deno_web/06_streams.js";
const {
ArrayPrototypeIncludes,
+ ArrayPrototypeMap,
ArrayPrototypePush,
- ArrayPrototypeSome,
Error,
ObjectPrototypeIsPrototypeOf,
SafeSetIterator,
Set,
SetPrototypeAdd,
SetPrototypeDelete,
+ StringPrototypeCharCodeAt,
StringPrototypeIncludes,
StringPrototypeToLowerCase,
StringPrototypeSplit,
- StringPrototypeTrim,
Symbol,
SymbolAsyncIterator,
TypeError,
@@ -389,15 +390,13 @@ function createRespondWith(
}
const _ws = Symbol("[[associated_ws]]");
+const websocketCvf = buildCaseInsensitiveCommaValueFinder("websocket");
+const upgradeCvf = buildCaseInsensitiveCommaValueFinder("upgrade");
function upgradeWebSocket(request, options = {}) {
const upgrade = request.headers.get("upgrade");
const upgradeHasWebSocketOption = upgrade !== null &&
- ArrayPrototypeSome(
- StringPrototypeSplit(upgrade, ","),
- (option) =>
- StringPrototypeToLowerCase(StringPrototypeTrim(option)) === "websocket",
- );
+ websocketCvf(upgrade);
if (!upgradeHasWebSocketOption) {
throw new TypeError(
"Invalid Header: 'upgrade' header must contain 'websocket'",
@@ -406,11 +405,7 @@ function upgradeWebSocket(request, options = {}) {
const connection = request.headers.get("connection");
const connectionHasUpgradeOption = connection !== null &&
- ArrayPrototypeSome(
- StringPrototypeSplit(connection, ","),
- (option) =>
- StringPrototypeToLowerCase(StringPrototypeTrim(option)) === "upgrade",
- );
+ upgradeCvf(connection);
if (!connectionHasUpgradeOption) {
throw new TypeError(
"Invalid Header: 'connection' header must contain 'Upgrade'",
@@ -471,4 +466,77 @@ function upgradeHttp(req) {
return req[_deferred].promise;
}
+const spaceCharCode = StringPrototypeCharCodeAt(" ", 0);
+const tabCharCode = StringPrototypeCharCodeAt("\t", 0);
+const commaCharCode = StringPrototypeCharCodeAt(",", 0);
+
+/** Builds a case function that can be used to find a case insensitive
+ * value in some text that's separated by commas.
+ *
+ * This is done because it doesn't require any allocations.
+ * @param checkText {string} - The text to find. (ex. "websocket")
+ */
+function buildCaseInsensitiveCommaValueFinder(checkText) {
+ const charCodes = ArrayPrototypeMap(
+ StringPrototypeSplit(
+ StringPrototypeToLowerCase(checkText),
+ "",
+ ),
+ (c) => [c.charCodeAt(0), c.toUpperCase().charCodeAt(0)],
+ );
+ /** @type {number} */
+ let i;
+ /** @type {number} */
+ let char;
+
+ /** @param value {string} */
+ return function (value) {
+ for (i = 0; i < value.length; i++) {
+ char = value.charCodeAt(i);
+ skipWhitespace(value);
+
+ if (hasWord(value)) {
+ skipWhitespace(value);
+ if (i === value.length || char === commaCharCode) {
+ return true;
+ }
+ } else {
+ skipUntilComma(value);
+ }
+ }
+
+ return false;
+ };
+
+ /** @param value {string} */
+ function hasWord(value) {
+ for (const [cLower, cUpper] of charCodes) {
+ if (cLower === char || cUpper === char) {
+ char = StringPrototypeCharCodeAt(value, ++i);
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /** @param value {string} */
+ function skipWhitespace(value) {
+ while (char === spaceCharCode || char === tabCharCode) {
+ char = StringPrototypeCharCodeAt(value, ++i);
+ }
+ }
+
+ /** @param value {string} */
+ function skipUntilComma(value) {
+ while (char !== commaCharCode && i < value.length) {
+ char = StringPrototypeCharCodeAt(value, ++i);
+ }
+ }
+}
+
+// Expose this function for unit tests
+internals.buildCaseInsensitiveCommaValueFinder =
+ buildCaseInsensitiveCommaValueFinder;
+
export { _ws, HttpConn, upgradeHttp, upgradeWebSocket };