Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Split Set-Cookie header correctly (#30560)
## Bug - [x] Related issues linked using `fixes #number` - [x] Integration tests added - [ ] Errors have helpful link attached, see `contributing.md` Fixes #30430 There's some more discussion in the issue, but in summary: - web `Headers` implementation combines all header values with `', '` - For `Set-Cookie` headers, you're supposed to set them as separate values, not combine them - web `Headers` forbids the use of `Cookie`, `Set-Cookie` and some more headers, so they don't have custom implementation for those, and still joins them with `,` - We currently just split them using `split(',')`, but this breaks when the header contains a date (expires, max-age) that also includes a `,` I used this method to split the Set-Cookie header properly: https://www.npmjs.com/package/set-cookie-parser#splitcookiestringcombinedsetcookieheader as suggested [here](whatwg/fetch#973 (comment)) I didn't add it as a dependency, since we only needed that one method and I wasn't sure what the process is for adding dependencies, so I just added the method in the middleware utils
- Loading branch information
1 parent
5b4ad4a
commit 450552d
Showing
7 changed files
with
235 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
import { splitCookiesString } from 'next/dist/server/web/utils' | ||
import cookie, { CookieSerializeOptions } from 'next/dist/compiled/cookie' | ||
|
||
function generateCookies( | ||
...cookieOptions: (CookieSerializeOptions & { name: string; value: string })[] | ||
) { | ||
const cookies = cookieOptions.map((opts) => | ||
cookie.serialize(opts.name, opts.value, opts) | ||
) | ||
return { | ||
joined: cookies.join(', '), | ||
expected: cookies, | ||
} | ||
} | ||
|
||
describe('splitCookiesString', () => { | ||
describe('with a single cookie', () => { | ||
it('should parse a plain value', () => { | ||
const { joined, expected } = generateCookies({ | ||
name: 'foo', | ||
value: 'bar', | ||
}) | ||
const result = splitCookiesString(joined) | ||
expect(result).toEqual(expected) | ||
}) | ||
|
||
it('should parse expires', () => { | ||
const { joined, expected } = generateCookies({ | ||
name: 'foo', | ||
value: 'bar', | ||
expires: new Date(), | ||
}) | ||
const result = splitCookiesString(joined) | ||
expect(result).toEqual(expected) | ||
}) | ||
|
||
it('should parse max-age', () => { | ||
const { joined, expected } = generateCookies({ | ||
name: 'foo', | ||
value: 'bar', | ||
maxAge: 10, | ||
}) | ||
const result = splitCookiesString(joined) | ||
expect(result).toEqual(expected) | ||
}) | ||
|
||
it('should parse with all the options', () => { | ||
const { joined, expected } = generateCookies({ | ||
name: 'foo', | ||
value: 'bar', | ||
expires: new Date(Date.now() + 10 * 1000), | ||
maxAge: 10, | ||
domain: 'https://foo.bar', | ||
httpOnly: true, | ||
path: '/path', | ||
sameSite: 'lax', | ||
secure: true, | ||
}) | ||
const result = splitCookiesString(joined) | ||
expect(result).toEqual(expected) | ||
}) | ||
}) | ||
|
||
describe('with a mutliple cookies', () => { | ||
it('should parse a plain value', () => { | ||
const { joined, expected } = generateCookies( | ||
{ | ||
name: 'foo', | ||
value: 'bar', | ||
}, | ||
{ | ||
name: 'x', | ||
value: 'y', | ||
} | ||
) | ||
const result = splitCookiesString(joined) | ||
expect(result).toEqual(expected) | ||
}) | ||
|
||
it('should parse expires', () => { | ||
const { joined, expected } = generateCookies( | ||
{ | ||
name: 'foo', | ||
value: 'bar', | ||
expires: new Date(), | ||
}, | ||
{ | ||
name: 'x', | ||
value: 'y', | ||
expires: new Date(), | ||
} | ||
) | ||
const result = splitCookiesString(joined) | ||
expect(result).toEqual(expected) | ||
}) | ||
|
||
it('should parse max-age', () => { | ||
const { joined, expected } = generateCookies( | ||
{ | ||
name: 'foo', | ||
value: 'bar', | ||
maxAge: 10, | ||
}, | ||
{ | ||
name: 'x', | ||
value: 'y', | ||
maxAge: 10, | ||
} | ||
) | ||
const result = splitCookiesString(joined) | ||
expect(result).toEqual(expected) | ||
}) | ||
|
||
it('should parse with all the options', () => { | ||
const { joined, expected } = generateCookies( | ||
{ | ||
name: 'foo', | ||
value: 'bar', | ||
expires: new Date(Date.now() + 10 * 1000), | ||
maxAge: 10, | ||
domain: 'https://foo.bar', | ||
httpOnly: true, | ||
path: '/path', | ||
sameSite: 'lax', | ||
secure: true, | ||
}, | ||
{ | ||
name: 'x', | ||
value: 'y', | ||
expires: new Date(Date.now() + 20 * 1000), | ||
maxAge: 20, | ||
domain: 'https://x.y', | ||
httpOnly: true, | ||
path: '/path', | ||
sameSite: 'strict', | ||
secure: true, | ||
} | ||
) | ||
const result = splitCookiesString(joined) | ||
expect(result).toEqual(expected) | ||
}) | ||
}) | ||
}) |
450552d
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stats from current release
Default Build (Increase detected⚠️ )
General Overall decrease ✓
Page Load Tests Overall increase ✓
Client Bundles (main, webpack, commons) Overall increase⚠️
Legacy Client Bundles (polyfills)
Client Pages
Client Build Manifests
Rendered Page Sizes Overall decrease ✓
Diffs
Diff for main-HASH.js
Diff for index.html
Diff for link.html
Diff for withRouter.html
Default Build with SWC (Increase detected⚠️ )
General Overall decrease ✓
Page Load Tests Overall decrease⚠️
Client Bundles (main, webpack, commons) Overall increase⚠️
Legacy Client Bundles (polyfills)
Client Pages Overall increase⚠️
Client Build Manifests Overall increase⚠️
Rendered Page Sizes
Diffs
Diff for main-HASH.js
Diff for index.html
Diff for link.html
Diff for withRouter.html