rsnext/test/unit/split-cookies-string.test.ts
George Karagkiaouris 450552ddba
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](https://github.com/whatwg/fetch/issues/973#issuecomment-559678813)

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
2021-10-28 17:46:58 +00:00

143 lines
3.6 KiB
TypeScript

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)
})
})
})