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

Drop Node 14 in CI for Node 16 and add Node 18 to the matrix #5768

Merged
merged 6 commits into from
Jan 6, 2023
Merged
Changes from 1 commit
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
117 changes: 105 additions & 12 deletions packages/integrations/netlify/src/netlify-functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,19 +104,29 @@ export const createExports = (manifest: SSRManifest, args: Args) => {
// The fetch API does not have a way to get multiples of a single header, but instead concatenates
// them. There are non-standard ways to do it, and node-fetch gives us headers.raw()
// See https://github.com/whatwg/fetch/issues/973 for discussion
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not enjoy going down this rabbit hole, hurt me right in the computer

if (response.headers.has('set-cookie') && 'raw' in response.headers) {
// Node fetch allows you to get the raw headers, which includes multiples of the same type.
// This is needed because Set-Cookie *must* be called for each cookie, and can't be
// concatenated together.
type HeadersWithRaw = Headers & {
raw: () => Record<string, string[]>;
};

const rawPacked = (response.headers as HeadersWithRaw).raw();
if ('set-cookie' in rawPacked) {
fnResponse.multiValueHeaders = {
'set-cookie': rawPacked['set-cookie'],
if (response.headers.has('set-cookie')) {
if ('raw' in response.headers) {
// Node fetch allows you to get the raw headers, which includes multiples of the same type.
// This is needed because Set-Cookie *must* be called for each cookie, and can't be
// concatenated together.
type HeadersWithRaw = Headers & {
raw: () => Record<string, string[]>;
};

const rawPacked = (response.headers as HeadersWithRaw).raw();
if ('set-cookie' in rawPacked) {
fnResponse.multiValueHeaders = {
'set-cookie': rawPacked['set-cookie'],
};
}
} else {
const cookies = response.headers.get('set-cookie');

if (cookies) {
fnResponse.multiValueHeaders = {
'set-cookie': splitCookiesString(cookies),
};
}
}
}

Expand All @@ -135,3 +145,86 @@ export const createExports = (manifest: SSRManifest, args: Args) => {

return { handler };
};

/*
From: https://github.com/nfriedly/set-cookie-parser/blob/5cae030d8ef0f80eec58459e3583d43a07b984cb/lib/set-cookie.js#L144
Set-Cookie header field-values are sometimes comma joined in one string. This splits them without choking on commas
that are within a single set-cookie field-value, such as in the Expires portion.
This is uncommon, but explicitly allowed - see https://tools.ietf.org/html/rfc2616#section-4.2
Node.js does this for every header *except* set-cookie - see https://github.com/nodejs/node/blob/d5e363b77ebaf1caf67cd7528224b651c86815c1/lib/_http_incoming.js#L128
React Native's fetch does this for *every* header, including set-cookie.
Based on: https://github.com/google/j2objc/commit/16820fdbc8f76ca0c33472810ce0cb03d20efe25
Credits to: https://github.com/tomball for original and https://github.com/chrusart for JavaScript implementation
*/
function splitCookiesString(cookiesString: string): string[] {
if (Array.isArray(cookiesString)) {
return cookiesString;
}
if (typeof cookiesString !== 'string') {
return [];
}

let 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;
} 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;
}