Skip to content

Commit

Permalink
feat(stringify): add escape for quoted-pair
Browse files Browse the repository at this point in the history
  • Loading branch information
TomokiMiyauci committed Apr 28, 2023
1 parent 4497340 commit 5f1bba4
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 6 deletions.
6 changes: 4 additions & 2 deletions auth_param.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
{
"name": "dirty auth param",
"header": "a=a,b=b, c=c , d=d , e=e ",
"expected": { "a": "a", "b": "b", "c": "c", "d": "d", "e": "e" }
"expected": { "a": "a", "b": "b", "c": "c", "d": "d", "e": "e" },
"canonical": "a=a, b=b, c=c, d=d, e=e"
},
{
"name": "all char",
Expand All @@ -34,7 +35,8 @@
{
"name": "quoted-pair",
"header": "a=\"\\a\", b=\"\\\\\"",
"expected": { "a": "\"a\"", "b": "\"\\\"" }
"expected": { "a": "\"a\"", "b": "\"\\\"" },
"canonical": "a=\"a\", b=\"\\\\\""
},
{
"name": "duplicate key",
Expand Down
1 change: 1 addition & 0 deletions deno.lock
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"version": "2",
"remote": {
"https://deno.land/std@0.184.0/collections/map_values.ts": "431b78fd770c72cc978ca7bbfa08cdc0805e69c7d2b69ad19859e093467bd46d",
"https://deno.land/std@0.184.0/fmt/colors.ts": "d67e3cd9f472535241a8e410d33423980bec45047e343577554d3356e1f0ef4e",
"https://deno.land/std@0.184.0/testing/_diff.ts": "1a3c044aedf77647d6cac86b798c6417603361b66b54c53331b312caeb447aea",
"https://deno.land/std@0.184.0/testing/_format.ts": "a69126e8a469009adf4cf2a50af889aca364c349797e63174884a52ff75cf4c7",
Expand Down
1 change: 1 addition & 0 deletions deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export { trim } from "https://deno.land/x/prelude_js@1.2.0/trim.ts";
export { head } from "https://deno.land/x/prelude_js@1.2.0/head.ts";
export { isString } from "https://deno.land/x/isx@1.3.1/is_string.ts";
export { isNullable } from "https://deno.land/x/isx@1.3.1/is_nullable.ts";
export { mapValues } from "https://deno.land/std@0.184.0/collections/map_values.ts";
31 changes: 29 additions & 2 deletions stringify.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2023-latest the httpland authors. All rights reserved. MIT license.
// This module is browser compatible.

import { isNullable, isString, toLowerCase } from "./deps.ts";
import { isNullable, isString, mapValues, toLowerCase } from "./deps.ts";
import { duplicate } from "./utils.ts";
import { Msg } from "./constants.ts";
import type { Authorization, AuthParams } from "./types.ts";
Expand Down Expand Up @@ -75,7 +75,7 @@ export function assertToken68(
}

const reQuotedString =
/^"(?:\t| |!|[ \x23-\x5B\x5D-\x7E]|[\x80-\xFF]|\\(?:\t| |[\x21-\x7E])[\x80-\xFF])*"$/;
/^"(?:\t| |!|[ \x23-\x5B\x5D-\x7E]|[\x80-\xFF]|\\(?:\t| |[\x21-\x7E]|[\x80-\xFF]))*"$/;

export function isQuotedString(input: string): boolean {
return reQuotedString.test(input);
Expand All @@ -98,6 +98,8 @@ function assertAuthParam(input: AuthParams): asserts input {
}

export function stringifyAuthParams(input: AuthParams): string {
input = mapValues(input, normalizeParameterValue);

assertAuthParam(input);

return Object
Expand All @@ -106,6 +108,31 @@ export function stringifyAuthParams(input: AuthParams): string {
.join(", ");
}

export function normalizeParameterValue(input: string): string {
return isQuoted(input) ? `"${escapeOctet(trimChar(input))}"` : input;
}

/** Escape DQuote and Backslash.
* Skip escaped.
* @see https://www.rfc-editor.org/rfc/rfc9110.html#section-5.6.4-5
*/
export function escapeOctet(input: string): string {
// TODO(miyauci): dirty
return input
.replaceAll(`"`, `\\"`)
.replaceAll("\\", "\\\\")
.replaceAll("\\\\\\\\", "\\\\")
.replaceAll(`\\\\"`, '\\"');
}

export function isQuoted(input: string): input is `"${string}"` {
return /^".*"$/.test(input);
}

export function trimChar(input: string): string {
return input.slice(1, -1);
}

function joinEntry(
entry: readonly [string, string],
): string {
Expand Down
120 changes: 118 additions & 2 deletions stringify_test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,35 @@
import { AuthorizationLike, stringifyAuthorization } from "./stringify.ts";
import {
AuthorizationLike,
escapeOctet,
isQuoted,
stringifyAuthorization,
stringifyAuthParams,
trimChar,
} from "./stringify.ts";
import {
assertEquals,
assertThrows,
Authorization,
describe,
it,
} from "./_dev_deps.ts";

import authorization from "./authorization.json" assert { type: "json" };
import authParam from "./auth_param.json" assert { type: "json" };

describe("stringifyAuthParams", () => {
authParam.forEach((v) => {
it(v.name, () => {
if (!v.must_fail && v.expected) {
const input = v.canonical ? v.canonical : v.header;

assertEquals(
stringifyAuthParams(v.expected as Record<string, string>),
input,
);
}
});
});
});

describe("stringifyAuthorization", () => {
it("should return string if the input is valid", () => {
Expand Down Expand Up @@ -61,3 +83,97 @@ describe("stringifyAuthorization", () => {
});
});
});

describe("escapeOctet", () => {
it("should escape double quote and backslash", () => {
const table: [string, string][] = [
["", ""],
["a", "a"],
[`\\"`, `\\"`],
[`""`, `\\"\\"`],
[`"\\"`, `\\"\\"`],
[`\\"\\"`, `\\"\\"`],
[`"""`, `\\"\\"\\"`],
[`""""`, `\\"\\"\\"\\"`],
[`"`, `\\"`],
[`"a"`, `\\"a\\"`],
[`a\\`, `a\\\\`],
[`a"`, `a\\"`],
[`a\\"`, `a\\"`],
[`\\\\`, `\\\\`],
[`\\\\\\`, `\\\\\\\\`],
[`\\\\\\\\`, `\\\\\\\\`],
[`\\`, `\\\\`],
['"\\"', `\\"\\"`],
['\\\\\\"', `\\\\\\"`],
];

table.forEach(([input, expected]) => {
assertEquals(escapeOctet(input), expected);
});
});
});

describe("escapeOctet", () => {
it("should escape double quote and backslash", () => {
const table: [string, string][] = [
["", ""],
["a", "a"],
[`\\"`, `\\"`],
[`""`, `\\"\\"`],
[`"\\"`, `\\"\\"`],
[`\\"\\"`, `\\"\\"`],
[`"""`, `\\"\\"\\"`],
[`""""`, `\\"\\"\\"\\"`],
[`"`, `\\"`],
[`"a"`, `\\"a\\"`],
[`a\\`, `a\\\\`],
[`a"`, `a\\"`],
[`a\\"`, `a\\"`],
[`\\\\`, `\\\\`],
[`\\\\\\`, `\\\\\\\\`],
[`\\\\\\\\`, `\\\\\\\\`],
[`\\`, `\\\\`],
['"\\"', `\\"\\"`],
['\\\\\\"', `\\\\\\"`],
];

table.forEach(([input, expected]) => {
assertEquals(escapeOctet(input), expected);
});
});
});

describe("isQuoted", () => {
it("should pass", () => {
const table: [string, boolean][] = [
["a", false],
["ab", false],
[`"`, false],
[` ""`, false],
[`""`, true],
[`"a"`, true],
[`""""`, true],
[`"""`, true],
];

table.forEach(([input, expected]) => {
assertEquals(isQuoted(input), expected);
});
});
});

describe("trimChar", () => {
it("should pass", () => {
const table: [string, string][] = [
["a", ""],
["ab", ""],
["abc", "b"],
["abcd", "bc"],
];

table.forEach(([input, expected]) => {
assertEquals(trimChar(input), expected);
});
});
});

0 comments on commit 5f1bba4

Please sign in to comment.