Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix for handling chunked cookies in edge runtime #1236

Merged
merged 2 commits into from
Jun 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 78 additions & 2 deletions src/utils/middleware-cookies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ import { NextRequest, NextResponse } from 'next/server';
export default class MiddlewareCookies extends Cookies {
protected getSetCookieHeader(res: NextResponse): string[] {
const value = res.headers.get('set-cookie');
return value?.split(', ') || [];
return splitCookiesString(value as string);
}

protected setSetCookieHeader(res: NextResponse, cookies: string[]): void {
res.headers.set('set-cookie', cookies.join(', '));
res.headers.delete('set-cookie');
for (const cookie of cookies) {
res.headers.append('set-cookie', cookie);
}
}

getAll(req: NextRequest): Record<string, string> {
Expand All @@ -24,3 +27,76 @@ export default class MiddlewareCookies extends Cookies {
}, {});
}
}

/* eslint-disable max-len */
/**
* Handle cookies with commas, eg `foo=; Expires=Thu, 01 Jan 1970 00:00:00 GMT`
* @source https://github.com/vercel/edge-runtime/blob/90160abc42e6139c41494c5d2e98f09e9a5fa514/packages/cookies/src/response-cookies.ts#L128
*/
function splitCookiesString(cookiesString: string) {
if (!cookiesString) return [];
const cookiesStrings = [];
let pos = 0;
let start;
let ch;
let lastComma;
let nextStart;
let cookiesSeparatorFound;

function skipWhitespace() {
while (pos < cookiesString.length && /\s/.test(cookiesString.charAt(pos))) {
pos += 1;
}
return pos < cookiesString.length;
}

function notSpecialChar() {
ch = cookiesString.charAt(pos);

return ch !== '=' && ch !== ';' && ch !== ',';
}

while (pos < cookiesString.length) {
start = pos;
cookiesSeparatorFound = false;

while (skipWhitespace()) {
ch = cookiesString.charAt(pos);
if (ch === ',') {
// ',' is a cookie separator if we have later first '=', not ';' or ','
lastComma = pos;
pos += 1;

skipWhitespace();
nextStart = pos;

while (pos < cookiesString.length && notSpecialChar()) {
pos += 1;
}

// currently special character
if (pos < cookiesString.length && cookiesString.charAt(pos) === '=') {
// we found cookies separator
cookiesSeparatorFound = true;
// pos is inside the next cookie, so back up and return it.
pos = nextStart;
cookiesStrings.push(cookiesString.substring(start, lastComma));
start = pos;
/* c8 ignore next 5 */
} else {
// in param ',' or param separator ';',
// we continue from that comma
pos = lastComma + 1;
}
} else {
pos += 1;
}
}

if (!cookiesSeparatorFound || pos >= cookiesString.length) {
cookiesStrings.push(cookiesString.substring(start, cookiesString.length));
}
}

return cookiesStrings;
}
15 changes: 15 additions & 0 deletions tests/utils/middleware-cookies.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/
import MiddlewareCookies from '../../src/utils/middleware-cookies';
import { NextRequest, NextResponse } from 'next/server';
import { serialize } from 'cookie';

const setup = (reqInit?: RequestInit): [NextRequest, NextResponse] => {
return [new NextRequest(new URL('http://example.com'), reqInit), NextResponse.next()];
Expand Down Expand Up @@ -54,6 +55,20 @@ describe('cookie', () => {
expect(res.headers.get('set-cookie')).toEqual(['foo=bar', 'baz=qux'].join(', '));
});

it('should not overwrite existing set cookie with expiry', async () => {
const [, res] = setup();
res.headers.set('set-cookie', serialize('foo', '', { expires: new Date(0) }));
const setter = new MiddlewareCookies();
setter.set('baz', 'qux');
setter.commit(res);
expect(res.headers.get('set-cookie')).toEqual(
['foo=; Expires=Thu, 01 Jan 1970 00:00:00 GMT', 'baz=qux'].join(', ')
);
if ('getAll' in res.headers) {
expect((res.headers.getAll as (header: string) => string[])('set-cookie')).toHaveLength(2);
}
});

it('should override existing cookies that equal name', async () => {
const [, res] = setup();
res.headers.set('set-cookie', ['foo=bar', 'baz=qux'].join(', '));
Expand Down