Skip to content

Commit 32af9b5

Browse files
alan-agius4thePunderWoman
authored andcommitted
fix(http): strip RFC 6265 DQUOTE characters and handle URIError in parseCookieValue
Previously, `parseCookieValue` did not strip enclosing double quotes (`DQUOTE`) from quoted cookie values as specified in RFC 6265 Section 4.1.1. In addition, malformed percent-encoding in cookie values caused an unhandled `URIError` when calling `decodeURIComponent`. (cherry picked from commit 280d09b)
1 parent 688a0a7 commit 32af9b5

3 files changed

Lines changed: 56 additions & 2 deletions

File tree

packages/common/http/test/xsrf_spec.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ describe('HttpXsrfInterceptor', () => {
135135
expect(req.request.headers.get('X-XSRF-TOKEN')).toEqual('blah');
136136
req.flush({});
137137
});
138+
138139
it('does not set the header for a null token', () => {
139140
TestBed.resetTestingModule();
140141
TestBed.configureTestingModule({
@@ -160,10 +161,12 @@ describe('HttpXsrfInterceptor', () => {
160161
expect(req.request.headers.has('X-XSRF-TOKEN')).toEqual(false);
161162
req.flush({});
162163
});
164+
163165
afterEach(() => {
164166
backend.verify();
165167
});
166168
});
169+
167170
describe('HttpXsrfCookieExtractor', () => {
168171
let document: {[key: string]: string};
169172
let extractor: HttpXsrfCookieExtractor;
@@ -181,20 +184,38 @@ describe('HttpXsrfCookieExtractor', () => {
181184
});
182185
extractor = TestBed.inject(HttpXsrfCookieExtractor);
183186
});
187+
184188
it('parses the cookie from document.cookie', () => {
185189
expect(extractor.getToken()).toEqual('test');
186190
});
191+
187192
it('does not re-parse if document.cookie has not changed', () => {
188193
expect(extractor.getToken()).toEqual('test');
189194
expect(extractor.getToken()).toEqual('test');
190195
expect(getParseCount(extractor)).toEqual(1);
191196
});
197+
192198
it('re-parses if document.cookie changes', () => {
193199
expect(extractor.getToken()).toEqual('test');
194200
document['cookie'] = 'XSRF-TOKEN=blah';
195201
expect(extractor.getToken()).toEqual('blah');
196202
expect(getParseCount(extractor)).toEqual(2);
197203
});
204+
205+
it('extracts token without quotes when value is enclosed in DQUOTE characters', () => {
206+
document['cookie'] = 'XSRF-TOKEN="quoted-token-value"';
207+
expect(extractor.getToken()).toEqual('quoted-token-value');
208+
});
209+
210+
it('extracts token without quotes when value is enclosed in URL-encoded DQUOTE characters (%22)', () => {
211+
document['cookie'] = 'XSRF-TOKEN=%22quoted-token-value%22';
212+
expect(extractor.getToken()).toEqual('quoted-token-value');
213+
});
214+
215+
it('extracts token without crashing when value has malformed percent-encoding', () => {
216+
document['cookie'] = 'XSRF-TOKEN=%ZZ';
217+
expect(extractor.getToken()).toEqual('%ZZ');
218+
});
198219
});
199220

200221
function getParseCount(extractor: HttpXsrfCookieExtractor): number {

packages/common/src/cookie.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,29 @@
88

99
export function parseCookieValue(cookieStr: string, name: string): string | null {
1010
name = encodeURIComponent(name);
11+
1112
for (const cookie of cookieStr.split(';')) {
1213
const eqIndex = cookie.indexOf('=');
1314
const [cookieName, cookieValue]: string[] =
1415
eqIndex == -1 ? [cookie, ''] : [cookie.slice(0, eqIndex), cookie.slice(eqIndex + 1)];
15-
if (cookieName.trim() === name) {
16-
return decodeURIComponent(cookieValue);
16+
17+
if (cookieName.trim() !== name) {
18+
continue;
19+
}
20+
21+
let value = cookieValue;
22+
try {
23+
value = decodeURIComponent(cookieValue);
24+
} catch {
25+
// Fall back to raw cookie value if decoding fails (e.g. malformed percent-encoding).
1726
}
27+
28+
if (value.length > 1 && value[0] === '"' && value[value.length - 1] === '"') {
29+
value = value.slice(1, -1);
30+
}
31+
32+
return value;
1833
}
34+
1935
return null;
2036
}

packages/common/test/cookie_spec.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,30 @@ describe('cookies', () => {
2121
const cookie = 'other-cookie=false; xsrf-token=token-value; is_awesome=true; ffo=true;';
2222
expect(parseCookieValue(cookie, 'xsrf-token')).toBe('token-value');
2323
});
24+
2425
it('handles encoded keys', () => {
2526
expect(parseCookieValue('whitespace%20token=token-value', 'whitespace token')).toBe(
2627
'token-value',
2728
);
2829
});
30+
2931
it('handles encoded values', () => {
3032
expect(parseCookieValue('token=whitespace%20', 'token')).toBe('whitespace ');
3133
expect(parseCookieValue('token=whitespace%0A', 'token')).toBe('whitespace\n');
3234
});
35+
36+
it('strips DQUOTE characters per RFC 6265 Section 4.1.1', () => {
37+
expect(parseCookieValue('token="abc123"', 'token')).toBe('abc123');
38+
expect(parseCookieValue('token=%22abc123%22', 'token')).toBe('abc123');
39+
expect(parseCookieValue('token="abc=def"', 'token')).toBe('abc=def');
40+
expect(parseCookieValue('token="abc def"', 'token')).toBe('abc def');
41+
expect(parseCookieValue('token=""', 'token')).toBe('');
42+
expect(parseCookieValue('token="abc"', 'token')).toBe('abc');
43+
expect(parseCookieValue('token="', 'token')).toBe('"');
44+
});
45+
46+
it('handles malformed percent-encoding without throwing URIError', () => {
47+
expect(parseCookieValue('token=%ZZ', 'token')).toBe('%ZZ');
48+
expect(parseCookieValue('token="abc%ZZ"', 'token')).toBe('abc%ZZ');
49+
});
3350
});

0 commit comments

Comments
 (0)