Skip to content

Commit 600efd9

Browse files
committed
Add headerParams function
1 parent 8837941 commit 600efd9

File tree

3 files changed

+74
-1
lines changed

3 files changed

+74
-1
lines changed

CHANGELOG.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33

44
This package strictly follows [Semantic Versioning](https://semver.org).
55

6-
## v1.6.0 (2022-11-21)
6+
## v1.6.0 (2022-11-24)
77

88
### Features
99

10+
* Added `headerParams`, `headerQuote` and `headerUnquote` functions.
1011
* Added `removeHook` method to `AsyncHooks` class.
1112

1213
## v1.5.0 (2022-11-20)

src/util.ts

+44
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ const XML_UNESCAPE: Record<string, string> = {
3232
'#39': "'"
3333
};
3434

35+
const NAME_RE = new RegExp('[;\\s]*([^=;, ]+)\\s*', 'ys');
36+
const QUOTED_RE = new RegExp('=\\s*("(?:\\\\\\\\|\\\\"|[^"])*")', 'ys');
37+
const UNQUOTED_RE = new RegExp('=\\s*([^;, ]*)', 'ys');
38+
3539
/**
3640
* Safe string that should not be escaped.
3741
*/
@@ -161,6 +165,46 @@ export function escapeRegExp(string: string) {
161165
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
162166
}
163167

168+
/**
169+
* Extract HTTP header field parameters until the first comma.
170+
*/
171+
export function headerParams(value: string): {params: Record<string, string>; remainder: string} {
172+
const params: Record<string, string> = {};
173+
174+
const sticky = {offset: 0, value};
175+
while (value.length > sticky.offset) {
176+
const nameMatch = stickyMatch(sticky, NAME_RE);
177+
if (nameMatch === null) break;
178+
const name = nameMatch[1];
179+
180+
const quotedMatch = stickyMatch(sticky, QUOTED_RE);
181+
if (quotedMatch !== null) {
182+
params[name] ??= headerUnquote(quotedMatch[1]);
183+
continue;
184+
}
185+
186+
const unquotedMatch = stickyMatch(sticky, UNQUOTED_RE);
187+
if (unquotedMatch !== null) params[name] ??= unquotedMatch[1];
188+
}
189+
190+
return {params, remainder: value.slice(sticky.offset)};
191+
}
192+
193+
/**
194+
* Quote HTTP header field parameter.
195+
*/
196+
export function headerQuote(value: string): string {
197+
return '"' + value.replaceAll('\\', '\\\\').replaceAll('"', '\\"') + '"';
198+
}
199+
200+
/**
201+
* Unquote HTTP header field parameter.
202+
*/
203+
export function headerUnquote(value: string): string {
204+
if (value.startsWith('"') !== true || value.endsWith('"') !== true) return value;
205+
return value.slice(1, -1).replaceAll('\\\\', '\\').replaceAll('\\"', '"');
206+
}
207+
164208
/**
165209
* JSON pointers.
166210
*/

test/util.js

+28
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import {
66
cssUnescape,
77
decodeURIComponentSafe,
88
escapeRegExp,
9+
headerParams,
10+
headerQuote,
11+
headerUnquote,
912
jsonPointer,
1013
SafeString,
1114
stickyMatch,
@@ -56,6 +59,31 @@ t.test('Util', async t => {
5659
t.end();
5760
});
5861

62+
t.test('headerQuote', t => {
63+
t.equal(headerQuote('foo; 23 "bar'), '"foo; 23 \\"bar"');
64+
t.equal(headerQuote('"foo; 23 "bar"'), '"\\"foo; 23 \\"bar\\""');
65+
t.end();
66+
});
67+
68+
t.test('headerUnquote', t => {
69+
t.equal(headerUnquote('foo"bar"'), 'foo"bar"');
70+
t.equal(headerUnquote('"foo 23 \\"bar"'), 'foo 23 "bar');
71+
t.equal(headerUnquote('"\\"foo 23 \\"bar\\""'), '"foo 23 "bar"');
72+
t.end();
73+
});
74+
75+
t.test('headerParams', t => {
76+
t.same(headerParams(''), {params: {}, remainder: ''});
77+
t.same(headerParams('foo=b=a=r'), {params: {foo: 'b=a=r'}, remainder: ''});
78+
t.same(headerParams('a=b; c, d=e'), {params: {a: 'b'}, remainder: ', d=e'});
79+
t.same(headerParams('a=b; a=c'), {params: {a: 'b'}, remainder: ''});
80+
t.same(headerParams('a=b; a="c"'), {params: {a: 'b'}, remainder: ''});
81+
t.same(headerParams('a=b; b="c=d"'), {params: {a: 'b', b: 'c=d'}, remainder: ''});
82+
t.same(headerParams('a=b, c=d'), {params: {a: 'b'}, remainder: ', c=d'});
83+
t.same(headerParams(`rel="x"; t*=UTF-8'de'a%20b`), {params: {rel: 'x', 't*': "UTF-8'de'a%20b"}, remainder: ''});
84+
t.end();
85+
});
86+
5987
t.test('decodeURIComponentSafe', t => {
6088
const decode = decodeURIComponentSafe;
6189
t.same(decode('%E0%A4%A'), null);

0 commit comments

Comments
 (0)