Skip to content

Commit 5c4a9a6

Browse files
authoredDec 8, 2022
fix(core): handle Request -> Response regressions (#5991)
* fix(next): don't override `Content-Type` by `unstable_getServerSession` * fix(core): handle `,` while setting `set-cookie`
1 parent eddd8fd commit 5c4a9a6

File tree

2 files changed

+95
-10
lines changed

2 files changed

+95
-10
lines changed
 

Diff for: ‎packages/next-auth/src/next/index.ts

+7-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { AuthHandler } from "../core"
2-
import { getURL, getBody } from "../utils/node"
2+
import { getURL, getBody, setHeaders } from "../utils/node"
33

44
import type {
55
GetServerSidePropsContext,
@@ -37,10 +37,7 @@ async function NextAuthHandler(
3737
const { status, headers } = response
3838
res.status(status)
3939

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

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

159156
const { status = 200, headers } = response
160157

161-
for (const [key, val] of headers.entries()) {
162-
const value = key === "set-cookie" ? val.split(",") : val
163-
res.setHeader(key, value)
164-
}
158+
setHeaders(headers, res)
159+
160+
// This would otherwise break rendering
161+
// with `getServerSideProps` that needs to always return HTML
162+
res.removeHeader?.("Content-Type")
165163

166164
const data = await response.json()
167165

Diff for: ‎packages/next-auth/src/utils/node.ts

+88-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { IncomingMessage } from "http"
1+
import type { IncomingMessage, ServerResponse } from "http"
22
import type { GetServerSidePropsContext, NextApiRequest } from "next"
33

44
export function setCookie(res, value: string) {
@@ -54,6 +54,93 @@ export function getURL(
5454
}
5555
}
5656

57+
/**
58+
* Set-Cookie header field-values are sometimes comma joined in one string. This splits them without choking on commas
59+
* that are within a single set-cookie field-value, such as in the Expires portion.
60+
* This is uncommon, but explicitly allowed - see https://tools.ietf.org/html/rfc2616#section-4.2
61+
* Node.js does this for every header *except* set-cookie - see https://github.com/nodejs/node/blob/d5e363b77ebaf1caf67cd7528224b651c86815c1/lib/_http_incoming.js#L128
62+
* Based on: https://github.com/google/j2objc/commit/16820fdbc8f76ca0c33472810ce0cb03d20efe25
63+
* Credits to: https://github.com/tomball for original and https://github.com/chrusart for JavaScript implementation
64+
* @source https://github.com/nfriedly/set-cookie-parser/blob/3eab8b7d5d12c8ed87832532861c1a35520cf5b3/lib/set-cookie.js#L144
65+
*/
66+
function getSetCookies(cookiesString: string) {
67+
if (typeof cookiesString !== "string") {
68+
return []
69+
}
70+
71+
const cookiesStrings: string[] = []
72+
let pos = 0
73+
let start
74+
let ch
75+
let lastComma: number
76+
let nextStart
77+
let cookiesSeparatorFound
78+
79+
function skipWhitespace() {
80+
while (pos < cookiesString.length && /\s/.test(cookiesString.charAt(pos))) {
81+
pos += 1
82+
}
83+
return pos < cookiesString.length
84+
}
85+
86+
function notSpecialChar() {
87+
ch = cookiesString.charAt(pos)
88+
89+
return ch !== "=" && ch !== ";" && ch !== ","
90+
}
91+
92+
while (pos < cookiesString.length) {
93+
start = pos
94+
cookiesSeparatorFound = false
95+
96+
while (skipWhitespace()) {
97+
ch = cookiesString.charAt(pos)
98+
if (ch === ",") {
99+
// ',' is a cookie separator if we have later first '=', not ';' or ','
100+
lastComma = pos
101+
pos += 1
102+
103+
skipWhitespace()
104+
nextStart = pos
105+
106+
while (pos < cookiesString.length && notSpecialChar()) {
107+
pos += 1
108+
}
109+
110+
// currently special character
111+
if (pos < cookiesString.length && cookiesString.charAt(pos) === "=") {
112+
// we found cookies separator
113+
cookiesSeparatorFound = true
114+
// pos is inside the next cookie, so back up and return it.
115+
pos = nextStart
116+
cookiesStrings.push(cookiesString.substring(start, lastComma))
117+
start = pos
118+
} else {
119+
// in param ',' or param separator ';',
120+
// we continue from that comma
121+
pos = lastComma + 1
122+
}
123+
} else {
124+
pos += 1
125+
}
126+
}
127+
128+
if (!cookiesSeparatorFound || pos >= cookiesString.length) {
129+
cookiesStrings.push(cookiesString.substring(start, cookiesString.length))
130+
}
131+
}
132+
133+
return cookiesStrings
134+
}
135+
136+
export function setHeaders(headers: Headers, res: ServerResponse) {
137+
for (const [key, val] of headers.entries()) {
138+
// See: https://github.com/whatwg/fetch/issues/973
139+
const value = key === "set-cookie" ? getSetCookies(val) : val
140+
res.setHeader(key, value)
141+
}
142+
}
143+
57144
declare global {
58145
// eslint-disable-next-line @typescript-eslint/no-namespace
59146
namespace NodeJS {

0 commit comments

Comments
 (0)
Please sign in to comment.