Skip to content

Commit

Permalink
fix(core): handle Request -> Response regressions (#5991)
Browse files Browse the repository at this point in the history
* fix(next): don't override `Content-Type` by `unstable_getServerSession`

* fix(core): handle `,` while setting `set-cookie`
  • Loading branch information
balazsorban44 authored Dec 8, 2022
1 parent eddd8fd commit 5c4a9a6
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 10 deletions.
16 changes: 7 additions & 9 deletions packages/next-auth/src/next/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AuthHandler } from "../core"
import { getURL, getBody } from "../utils/node"
import { getURL, getBody, setHeaders } from "../utils/node"

import type {
GetServerSidePropsContext,
Expand Down Expand Up @@ -37,10 +37,7 @@ async function NextAuthHandler(
const { status, headers } = response
res.status(status)

for (const [key, val] of headers.entries()) {
const value = key === "set-cookie" ? val.split(",") : val
res.setHeader(key, value)
}
setHeaders(headers, res)

// If the request expects a return URL, send it as JSON
// instead of doing an actual redirect.
Expand Down Expand Up @@ -158,10 +155,11 @@ export async function unstable_getServerSession<

const { status = 200, headers } = response

for (const [key, val] of headers.entries()) {
const value = key === "set-cookie" ? val.split(",") : val
res.setHeader(key, value)
}
setHeaders(headers, res)

// This would otherwise break rendering
// with `getServerSideProps` that needs to always return HTML
res.removeHeader?.("Content-Type")

const data = await response.json()

Expand Down
89 changes: 88 additions & 1 deletion packages/next-auth/src/utils/node.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { IncomingMessage } from "http"
import type { IncomingMessage, ServerResponse } from "http"
import type { GetServerSidePropsContext, NextApiRequest } from "next"

export function setCookie(res, value: string) {
Expand Down Expand Up @@ -54,6 +54,93 @@ export function getURL(
}
}

/**
* 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
* 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
* @source https://github.com/nfriedly/set-cookie-parser/blob/3eab8b7d5d12c8ed87832532861c1a35520cf5b3/lib/set-cookie.js#L144
*/
function getSetCookies(cookiesString: string) {
if (typeof cookiesString !== "string") {
return []
}

const cookiesStrings: string[] = []
let pos = 0
let start
let ch
let lastComma: number
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
}

export function setHeaders(headers: Headers, res: ServerResponse) {
for (const [key, val] of headers.entries()) {
// See: https://github.com/whatwg/fetch/issues/973
const value = key === "set-cookie" ? getSetCookies(val) : val
res.setHeader(key, value)
}
}

declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace NodeJS {
Expand Down

0 comments on commit 5c4a9a6

Please sign in to comment.