Skip to content

Commit 8aa443c

Browse files
Merge pull request #2 from httpland/beta
Beta
2 parents f19fcdd + f46eb2b commit 8aa443c

File tree

13 files changed

+313
-211
lines changed

13 files changed

+313
-211
lines changed

CHANGELOG.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,25 @@
1+
# [1.1.0-beta.3](https://github.com/httpland/authorization-parser/compare/1.1.0-beta.2...1.1.0-beta.3) (2023-05-02)
2+
3+
4+
### Performance Improvements
5+
6+
* **parse:** optimize regex pattern ([b86dcd6](https://github.com/httpland/authorization-parser/commit/b86dcd6c5a5a8c88fbd0808b0775e5565870bdd0))
7+
8+
# [1.1.0-beta.2](https://github.com/httpland/authorization-parser/compare/1.1.0-beta.1...1.1.0-beta.2) (2023-05-02)
9+
10+
11+
### Performance Improvements
12+
13+
* **parse:** remove complex regex pattern ([dbed45b](https://github.com/httpland/authorization-parser/commit/dbed45bc16066302a38393292fd5906641a94223))
14+
* **parse:** simplify regex pattern ([9af9114](https://github.com/httpland/authorization-parser/commit/9af9114094ff5afe406a0bcda4437fa646b49ef2))
15+
16+
# [1.1.0-beta.1](https://github.com/httpland/authorization-parser/compare/1.0.0...1.1.0-beta.1) (2023-04-29)
17+
18+
19+
### Features
20+
21+
* use external validation for token ([1928b53](https://github.com/httpland/authorization-parser/commit/1928b535d796de70988bd792be4d423d8e4ec0e5))
22+
123
# 1.0.0 (2023-04-28)
224

325

_abnf.ts

Lines changed: 3 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,13 @@
1-
import {
2-
atomic,
3-
either,
4-
maybe,
5-
namedCapture,
6-
sequence,
7-
suffix,
8-
} from "npm:compose-regexp";
1+
import { either, namedCapture, sequence, suffix } from "npm:compose-regexp";
92
import { optimize } from "https://esm.sh/regexp-tree";
103

114
const tchar = /[!#$%&'*+.^_`|~\dA-Za-z-]/;
12-
const token = atomic(suffix("+", tchar));
13-
const authScheme = namedCapture("authScheme", token);
5+
const token = suffix("+", tchar);
146
const SP = / /;
15-
const ALPHA = /[A-Za-z]/;
16-
const DIGIT = /\d/;
17-
const token68 = sequence(
18-
atomic(suffix("+", either(ALPHA, DIGIT, /[-._~+/]/))),
19-
atomic(suffix("*", "=")),
20-
);
21-
22-
const challenge = sequence(
23-
authScheme,
24-
maybe(
25-
atomic(suffix("+", SP)),
26-
either(
27-
namedCapture("token68", token68),
28-
namedCapture("authParam", atomic(/.*/)),
29-
),
30-
),
31-
);
327

338
const OWS = /[ \t]*/;
349
const BWS = OWS;
3510

36-
const element = sequence(
37-
atomic(
38-
maybe(atomic(/.*?/), atomic(suffix("*", OWS, ",", OWS, maybe(/.*?/)))),
39-
),
40-
);
41-
4211
const DQUOTE = /"/;
4312
const obsText = /[\x80-\xFF]/;
4413
const HTAB = /\t/;
@@ -48,7 +17,7 @@ const quotedPair = sequence("\\", either(HTAB, SP, VCHAR, obsText));
4817

4918
const quotedString = sequence(
5019
DQUOTE,
51-
atomic(suffix("*", either(qdtext, quotedPair))),
20+
suffix("*", either(qdtext, quotedPair)),
5221
DQUOTE,
5322
);
5423

@@ -64,13 +33,5 @@ const authParam = sequence(
6433
);
6534

6635
if (import.meta.main) {
67-
console.log("challenge:", optimize(challenge).toRegExp());
68-
console.log("element: ", element);
6936
console.log("authParam: ", optimize(authParam).toRegExp());
70-
console.log("token: ", optimize(token).toRegExp());
71-
console.log("token68: ", optimize(token68).toRegExp());
72-
console.log(
73-
"quoted-string: ",
74-
optimize(quotedString).toRegExp(),
75-
);
7637
}

_dev_deps.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export {
22
assert,
33
assertEquals,
4+
assertFalse,
45
assertIsError,
56
assertThrows,
67
} from "https://deno.land/[email protected]/testing/asserts.ts";

_tools/meta.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,6 @@ export const makeOptions = (version: string): BuildOptions => ({
4444
version: "1.2.0",
4545
subPath: "to_lower_case.js",
4646
},
47-
"https://deno.land/x/[email protected]/trim.ts": {
48-
name: "@miyauci/prelude",
49-
version: "1.2.0",
50-
subPath: "trim.js",
51-
},
5247
"https://deno.land/x/[email protected]/head.ts": {
5348
name: "@miyauci/prelude",
5449
version: "1.2.0",
@@ -64,5 +59,24 @@ export const makeOptions = (version: string): BuildOptions => ({
6459
version: "1.3.1",
6560
subPath: "is_nullable.js",
6661
},
62+
"https://deno.land/x/[email protected]/quoted_string.ts": {
63+
name: "@httpland/http-utils",
64+
version: "1.2.0",
65+
subPath: "quoted_string.js",
66+
},
67+
"https://deno.land/x/[email protected]/token.ts": {
68+
name: "@httpland/http-utils",
69+
version: "1.2.0",
70+
subPath: "token.js",
71+
},
72+
"https://deno.land/x/[email protected]/list.ts": {
73+
name: "@httpland/http-utils",
74+
version: "1.2.0",
75+
subPath: "list.js",
76+
},
77+
"https://esm.sh/[email protected]?pin=v118": {
78+
name: "escape-string-regexp",
79+
version: "5.0.0",
80+
},
6781
},
6882
});

_tools/publish_npm.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@ if (import.meta.main) {
1616
const tag = isPrerelease?.[0] ?? "latest";
1717

1818
const pkg = makeOptions(version);
19-
const result = await Deno.run({
20-
cmd: ["npm", "publish", pkg.outDir, "--tag", String(tag)],
21-
stdout: "piped",
22-
})
23-
.output();
19+
const command = new Deno.Command("npm", {
20+
args: ["publish", pkg.outDir, "--tag", String(tag)],
21+
});
22+
const result = await command.output();
2423

25-
console.log(new TextDecoder().decode(result));
24+
if (!result.success) {
25+
console.error(new TextDecoder().decode(result.stderr));
26+
}
2627
}

authorization.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,11 @@
9797
"header": "<invalid>",
9898
"must_fail": true
9999
},
100+
{
101+
"name": "invalid params",
102+
"header": "scheme==",
103+
"must_fail": true
104+
},
100105
{
101106
"name": "token68 and auth param",
102107
"header": "Basic YWxhZGRpbjpvcGVuc2VzYW1l a=a",

deno.json

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,13 @@
44
"build:npm": "deno run -A _tools/build_npm.ts"
55
},
66
"fmt": {
7-
"files": {
8-
"exclude": ["CHANGELOG.md", "CODE_OF_CONDUCT.md"]
9-
}
7+
"exclude": ["CHANGELOG.md", "CODE_OF_CONDUCT.md"]
108
},
119
"lint": {
12-
"files": {
13-
"exclude": ["CHANGELOG.md", "CODE_OF_CONDUCT.md"]
14-
}
10+
"exclude": ["CHANGELOG.md", "CODE_OF_CONDUCT.md"]
1511
},
1612
"test": {
17-
"files": {
18-
"exclude": ["CHANGELOG.md", "CODE_OF_CONDUCT.md"]
19-
}
13+
"exclude": ["CHANGELOG.md", "CODE_OF_CONDUCT.md"]
2014
},
2115
"compilerOptions": {
2216
"noImplicitReturns": true,

deno.lock

Lines changed: 15 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

deps.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,17 @@
22
// This module is browser compatible.
33

44
export { toLowerCase } from "https://deno.land/x/[email protected]/to_lower_case.ts";
5-
export { trim } from "https://deno.land/x/[email protected]/trim.ts";
65
export { head } from "https://deno.land/x/[email protected]/head.ts";
76
export { isString } from "https://deno.land/x/[email protected]/is_string.ts";
87
export { isNullable } from "https://deno.land/x/[email protected]/is_nullable.ts";
98
export { mapValues } from "https://deno.land/[email protected]/collections/map_values.ts";
9+
export {
10+
isQuotedString,
11+
type QuotedString,
12+
} from "https://deno.land/x/[email protected]/quoted_string.ts";
13+
export {
14+
isToken,
15+
type Token,
16+
} from "https://deno.land/x/[email protected]/token.ts";
17+
export { parseListFields } from "https://deno.land/x/[email protected]/list.ts";
18+
export { default as escapeStringRegExp } from "https://esm.sh/[email protected]?pin=v118";

parse.ts

Lines changed: 52 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
// Copyright 2023-latest the httpland authors. All rights reserved. MIT license.
22
// This module is browser compatible.
33

4-
import { duplicate, parseList } from "./utils.ts";
5-
import { head, isString, toLowerCase } from "./deps.ts";
4+
import { divideWhile, duplicate, isToken68, trimStartBy } from "./utils.ts";
5+
import {
6+
head,
7+
isString,
8+
isToken,
9+
parseListFields,
10+
type QuotedString,
11+
type Token,
12+
toLowerCase,
13+
} from "./deps.ts";
614
import { Msg } from "./constants.ts";
715
import type { Authorization, AuthParams } from "./types.ts";
816

9-
/** Generate from _abnf.ts. */
10-
const reAuthorization =
11-
/^(?<authScheme>(?=([\w!#$%&'*+.^`|~-]+))\2)(?:(?=( +))\3(?:(?<token68>(?=((?:[A-Za-z]|\d|[+./_~-])+))\5(?=(=*))\6)|(?<authParam>(?=(.*))\8)))?$/;
12-
1317
/** Parse string into {@link Authorization}.
1418
*
1519
* @example
@@ -39,28 +43,28 @@ const reAuthorization =
3943
* @throws {Error} If the auth param key is duplicated.
4044
*/
4145
export function parseAuthorization(input: string): Authorization {
42-
const result = reAuthorization.exec(input);
46+
const result = divideWhile(input, isToken);
4347

44-
if (!result || !result.groups) throw SyntaxError(Msg.InvalidSyntax);
48+
if (!result) throw new SyntaxError(Msg.InvalidSyntax);
4549

46-
const groups = result.groups as ParsedGroups;
47-
const { authScheme } = groups;
48-
const params = isString(groups.authParam)
49-
? parseAuthParams(groups.authParam)
50-
: groups.token68;
50+
const [authScheme, rest] = result;
5151

52-
return { authScheme, params: params ?? null };
53-
}
52+
// challenge = auth-scheme
53+
if (!rest) return { authScheme, params: null };
54+
if (!rest.startsWith(" ")) throw new SyntaxError(Msg.InvalidSyntax);
5455

55-
type ParsedGroups = {
56-
readonly authScheme: string;
57-
readonly token68: string | undefined;
58-
readonly authParam: string | undefined;
59-
};
56+
const maybeToken68OrAuthParam = trimStartBy(rest, " ");
6057

61-
/** Generate from _abnf.ts. */
62-
const reAuthParam =
63-
/^(?<key>(?=([\w!#$%&'*+.^`|~-]+))\2)[\t ]*=[\t ]*(?:(?<token>(?=([\w!#$%&'*+.^`|~-]+))\4)|(?<quotedString>"(?=((?:\t| |!|[ \x23-\x5B\x5D-\x7E]|[\x80-\xFF]|\\(?:\t| |[\x21-\x7E]|[\x80-\xFF]))*))\6"))$/;
58+
// challenge = auth-scheme [ 1*SP ( token68 ) ]
59+
if (isToken68(maybeToken68OrAuthParam)) {
60+
return { authScheme, params: maybeToken68OrAuthParam };
61+
}
62+
63+
// challenge = auth-scheme [ 1*SP ( #auth-param ) ]
64+
const params = parseAuthParams(maybeToken68OrAuthParam);
65+
66+
return { authScheme, params };
67+
}
6468

6569
type AuthParamGroups =
6670
& { key: string }
@@ -69,26 +73,13 @@ type AuthParamGroups =
6973
quotedString: string;
7074
});
7175

72-
/** Parse string into {@link AuthParam}.
76+
/** Parse string into {@link AuthParams}.
7377
* @throws {SyntaxError} It the input is invalid [auth-param](https://www.rfc-editor.org/rfc/rfc9110.html#section-11.2-5).
7478
* @throws {Error} If the auth param key is duplicated.
7579
*/
7680
export function parseAuthParams(input: string): AuthParams {
77-
const list = parseList(input);
78-
79-
const entries = list.map((el) => {
80-
const result = reAuthParam.exec(el);
81-
82-
if (!result || !result.groups) throw SyntaxError(Msg.InvalidSyntax);
83-
84-
const groups = result.groups as AuthParamGroups;
85-
const value = isString(groups.token)
86-
? groups.token
87-
: groups.quotedString.replace(/\\(.)/g, "$1");
88-
89-
return [groups.key, value] as const;
90-
});
91-
81+
const list = parseListFields(input);
82+
const entries = list.map(parseAuthParam);
9283
const duplicates = duplicate(
9384
entries
9485
.map<string>(head)
@@ -99,3 +90,25 @@ export function parseAuthParams(input: string): AuthParams {
9990

10091
return Object.fromEntries(entries);
10192
}
93+
94+
type AuthParam = [key: string, value: Token | QuotedString];
95+
96+
/** Generate from _abnf.ts. */
97+
const reAuthParam =
98+
/^(?<key>[\w!#$%&'*+.^`|~-]+?)[\t ]*?=[\t ]*?(?:(?<token>[\w!#$%&'*+.^`|~-]+?)|(?<quotedString>"(?:[\t !\x23-\x5B\x5D-\x7E\x80-\xFF]|\\[\t \x21-\x7E\x80-\xFF])*?"))$/;
99+
100+
/** Parse string into {@link AuthParam}.
101+
* @throws {SyntaxError} It the input is invalid [auth-param](https://www.rfc-editor.org/rfc/rfc9110.html#section-11.2-5).
102+
*/
103+
function parseAuthParam(input: string): AuthParam {
104+
const result = reAuthParam.exec(input);
105+
106+
if (!result || !result.groups) throw new SyntaxError(Msg.InvalidSyntax);
107+
108+
const groups = result.groups as AuthParamGroups;
109+
const value = isString(groups.token)
110+
? groups.token as Token
111+
: groups.quotedString.replace(/\\(.)/g, "$1") as QuotedString;
112+
113+
return [groups.key as Token, value];
114+
}

0 commit comments

Comments
 (0)