Skip to content

Commit 23f0d1e

Browse files
authored
Merge pull request #163 from OffBlocks/encoded-uri
fix: use undecoded @path, @query and @query-param according to the spec
2 parents 1d05689 + c086d9f commit 23f0d1e

File tree

2 files changed

+98
-4
lines changed

2 files changed

+98
-4
lines changed

src/httpbis/index.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,9 @@ export function deriveComponent(component: string, params: Map<string, string |
9393
throw new Error('Cannot derive @scheme on response');
9494
}
9595
const { pathname } = typeof context.url === 'string' ? new URL(context.url) : context.url;
96-
return [decodeURI(pathname)];
96+
// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures#section-2.2.6
97+
// empty path means use `/`
98+
return [pathname || '/'];
9799
}
98100
case '@query': {
99101
if (!isRequest(context)) {
@@ -102,7 +104,7 @@ export function deriveComponent(component: string, params: Map<string, string |
102104
const { search } = typeof context.url === 'string' ? new URL(context.url) : context.url;
103105
// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures#section-2.2.7
104106
// absent query params means use `?`
105-
return [decodeURI(search) || '?'];
107+
return [search || '?'];
106108
}
107109
case '@query-param': {
108110
if (!isRequest(context)) {
@@ -112,11 +114,11 @@ export function deriveComponent(component: string, params: Map<string, string |
112114
if (!params.has('name')) {
113115
throw new Error('@query-param must have a named parameter');
114116
}
115-
const name = (params.get('name') as BareItem).toString();
117+
const name = decodeURIComponent((params.get('name') as BareItem).toString());
116118
if (!searchParams.has(name)) {
117119
throw new Error(`Expected query parameter "${name}" not found`);
118120
}
119-
return searchParams.getAll(name);
121+
return searchParams.getAll(name).map((value) => encodeURIComponent(value));
120122
}
121123
case '@status': {
122124
if (isRequest(context)) {

test/httpbis/httpbis.spec.ts

+92
Original file line numberDiff line numberDiff line change
@@ -168,12 +168,36 @@ describe('httpbis', () => {
168168
expect(httpbis.deriveComponent('@path', new Map(), req)).to.deep.equal([
169169
'/path',
170170
]);
171+
expect(httpbis.deriveComponent('@path', new Map(), {
172+
...req,
173+
url: 'https://www.example.com/path%7D?param=value',
174+
})).to.deep.equal([
175+
'/path%7D',
176+
]);
177+
expect(httpbis.deriveComponent('@path', new Map(), {
178+
...req,
179+
url: 'https://www.example.com',
180+
})).to.deep.equal([
181+
'/',
182+
]);
171183
expect(httpbis.deriveComponent('@path', new Map(), {
172184
...req,
173185
url: new URL(req.url as string),
174186
})).to.deep.equal([
175187
'/path',
176188
]);
189+
expect(httpbis.deriveComponent('@path', new Map(), {
190+
...req,
191+
url: new URL('https://www.example.com/path%7D?param=value'),
192+
})).to.deep.equal([
193+
'/path%7D',
194+
]);
195+
expect(httpbis.deriveComponent('@path', new Map(), {
196+
...req,
197+
url: new URL('https://www.example.com'),
198+
})).to.deep.equal([
199+
'/',
200+
]);
177201
});
178202
it('derives @query', () => {
179203
const req: Request = {
@@ -198,6 +222,18 @@ describe('httpbis', () => {
198222
})).to.deep.equal([
199223
'?',
200224
]);
225+
expect(httpbis.deriveComponent('@query', new Map(), {
226+
...req,
227+
url: 'https://www.example.com//path?param=value&foo=bar&baz=bat%2Dman',
228+
})).to.deep.equal([
229+
'?param=value&foo=bar&baz=bat%2Dman',
230+
]);
231+
expect(httpbis.deriveComponent('@query', new Map(), {
232+
...req,
233+
url: 'https://www.example.com/path',
234+
})).to.deep.equal([
235+
'?',
236+
]);
201237
// with URL objects
202238
expect(httpbis.deriveComponent('@query', new Map(), {
203239
...req,
@@ -211,6 +247,12 @@ describe('httpbis', () => {
211247
})).to.deep.equal([
212248
'?queryString',
213249
]);
250+
expect(httpbis.deriveComponent('@query', new Map(), {
251+
...req,
252+
url: new URL('https://www.example.com//path?param=value&foo=bar&baz=bat%2Dman'),
253+
})).to.deep.equal([
254+
'?param=value&foo=bar&baz=bat%2Dman',
255+
]);
214256
expect(httpbis.deriveComponent('@query', new Map(), {
215257
...req,
216258
url: new URL('https://www.example.com/path'),
@@ -242,6 +284,31 @@ describe('httpbis', () => {
242284
'value',
243285
'value2',
244286
]);
287+
expect(httpbis.deriveComponent('@query-param', new Map([['name', 'param']]), {
288+
...req,
289+
url: 'https://example.com/path?param=value%7D&param=value2%7D',
290+
})).to.deep.equal([
291+
'value%7D',
292+
'value2%7D',
293+
]);
294+
expect(httpbis.deriveComponent('@query-param', new Map([['name', 'var']]), {
295+
...req,
296+
url: 'https://example.com/parameters?var=this%20is%20a%20big%0Amultiline%20value&bar=with+plus+whitespace&fa%C3%A7ade%22%3A%20=something',
297+
})).to.deep.equal([
298+
'this%20is%20a%20big%0Amultiline%20value',
299+
]);
300+
expect(httpbis.deriveComponent('@query-param', new Map([['name', 'bar']]), {
301+
...req,
302+
url: 'https://example.com/parameters?var=this%20is%20a%20big%0Amultiline%20value&bar=with+plus+whitespace&fa%C3%A7ade%22%3A%20=something',
303+
})).to.deep.equal([
304+
'with%20plus%20whitespace',
305+
]);
306+
expect(httpbis.deriveComponent('@query-param', new Map([['name', 'fa%C3%A7ade%22%3A%20']]), {
307+
...req,
308+
url: 'https://example.com/parameters?var=this%20is%20a%20big%0Amultiline%20value&bar=with+plus+whitespace&fa%C3%A7ade%22%3A%20=something',
309+
})).to.deep.equal([
310+
'something',
311+
]);
245312
// with URL objects
246313
expect(httpbis.deriveComponent('@query-param', new Map([['name', 'baz']]), {
247314
...req,
@@ -268,6 +335,31 @@ describe('httpbis', () => {
268335
'value',
269336
'value2',
270337
]);
338+
expect(httpbis.deriveComponent('@query-param', new Map([['name', 'param']]), {
339+
...req,
340+
url: new URL('https://example.com/path?param=value%7D&param=value2%7D'),
341+
})).to.deep.equal([
342+
'value%7D',
343+
'value2%7D',
344+
]);
345+
expect(httpbis.deriveComponent('@query-param', new Map([['name', 'var']]), {
346+
...req,
347+
url: new URL('https://example.com/parameters?var=this%20is%20a%20big%0Amultiline%20value&bar=with+plus+whitespace&fa%C3%A7ade%22%3A%20=something'),
348+
})).to.deep.equal([
349+
'this%20is%20a%20big%0Amultiline%20value',
350+
]);
351+
expect(httpbis.deriveComponent('@query-param', new Map([['name', 'bar']]), {
352+
...req,
353+
url: new URL('https://example.com/parameters?var=this%20is%20a%20big%0Amultiline%20value&bar=with+plus+whitespace&fa%C3%A7ade%22%3A%20=something'),
354+
})).to.deep.equal([
355+
'with%20plus%20whitespace',
356+
]);
357+
expect(httpbis.deriveComponent('@query-param', new Map([['name', 'fa%C3%A7ade%22%3A%20']]), {
358+
...req,
359+
url: new URL('https://example.com/parameters?var=this%20is%20a%20big%0Amultiline%20value&bar=with+plus+whitespace&fa%C3%A7ade%22%3A%20=something'),
360+
})).to.deep.equal([
361+
'something',
362+
]);
271363
});
272364
it('derives @status', () => {
273365
const req: Request = {

0 commit comments

Comments
 (0)