diff options
author | 2023-01-06 18:01:54 +0100 | |
---|---|---|
committer | 2023-01-06 12:01:54 -0500 | |
commit | 2f6745019ac25785032ac3659c2433b6e224f383 (patch) | |
tree | 4c104600c0450ad6fa1bdb7180bd97a2f7b60971 /packages/integrations/netlify/src | |
parent | 23937fbbc9fc5647d155dbe418c7c2afd4814c06 (diff) | |
download | astro-2f6745019ac25785032ac3659c2433b6e224f383.tar.gz astro-2f6745019ac25785032ac3659c2433b6e224f383.tar.zst astro-2f6745019ac25785032ac3659c2433b6e224f383.zip |
Drop Node 14 in CI for Node 16 and add Node 18 to the matrix (#5768)
* ci(node): Move CI to Node 16 and add Node 18 to the matrix
* fix(netlify): Fix set-cookie not working on Node 18
* fix(netlify): Handle if `set-cookie` is already somehow an array (apparently it can?)
* test(node): Fix `toPromise` to match Astro's
* fix(tests): Use the actual underlying ArrayBuffer instance to create the buffer in toPromise
* chore: changeset
Diffstat (limited to 'packages/integrations/netlify/src')
-rw-r--r-- | packages/integrations/netlify/src/netlify-functions.ts | 117 |
1 files changed, 105 insertions, 12 deletions
diff --git a/packages/integrations/netlify/src/netlify-functions.ts b/packages/integrations/netlify/src/netlify-functions.ts index fe6ce7a15..57b042af9 100644 --- a/packages/integrations/netlify/src/netlify-functions.ts +++ b/packages/integrations/netlify/src/netlify-functions.ts @@ -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 - 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': Array.isArray(cookies) ? cookies : splitCookiesString(cookies), + }; + } } } @@ -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; +} |