From 090b4b0d2aefbf36707fa236395bc6ea99227b9c Mon Sep 17 00:00:00 2001 From: Arda TANRIKULU Date: Thu, 20 Feb 2025 14:43:16 +0300 Subject: [PATCH] fix(node-fetch): respect `set-cookies` given in `HeadersInit` (#2079) --- .changeset/curvy-insects-cheer.md | 5 +++ packages/node-fetch/src/Headers.ts | 50 ++++++++++++++++------- packages/node-fetch/tests/Headers.spec.ts | 16 ++++++++ 3 files changed, 56 insertions(+), 15 deletions(-) create mode 100644 .changeset/curvy-insects-cheer.md diff --git a/.changeset/curvy-insects-cheer.md b/.changeset/curvy-insects-cheer.md new file mode 100644 index 00000000000..b57acc91b70 --- /dev/null +++ b/.changeset/curvy-insects-cheer.md @@ -0,0 +1,5 @@ +--- +'@whatwg-node/node-fetch': patch +--- + +Fix the bug when `set-cookies` given is ignored in `HeadersInit` diff --git a/packages/node-fetch/src/Headers.ts b/packages/node-fetch/src/Headers.ts index 96cdaf3a0b1..3869e52a080 100644 --- a/packages/node-fetch/src/Headers.ts +++ b/packages/node-fetch/src/Headers.ts @@ -11,14 +11,14 @@ export class PonyfillHeaders implements Headers { private _map: Map | undefined; private objectNormalizedKeysOfHeadersInit: string[] = []; private objectOriginalKeysOfHeadersInit: string[] = []; - private _setCookies: string[] = []; + private _setCookies?: string[]; constructor(private headersInit?: PonyfillHeadersInit) {} // perf: we don't need to build `this.map` for Requests, as we can access the headers directly private _get(key: string) { const normalized = key.toLowerCase(); - if (normalized === 'set-cookie') { + if (normalized === 'set-cookie' && this._setCookies?.length) { return this._setCookies.join(', '); } // If the map is built, reuse it @@ -32,7 +32,16 @@ export class PonyfillHeaders implements Headers { } if (Array.isArray(this.headersInit)) { - return this.headersInit.find(header => header[0].toLowerCase() === normalized)?.[1] || null; + const found = this.headersInit.filter( + ([headerKey]) => headerKey.toLowerCase() === normalized, + ); + if (found.length === 0) { + return null; + } + if (found.length === 1) { + return found[0][1]; + } + return found.map(([, value]) => value).join(', '); } else if (isHeadersLike(this.headersInit)) { return this.headersInit.get(normalized); } else { @@ -61,21 +70,23 @@ export class PonyfillHeaders implements Headers { // I could do a getter here, but I'm too lazy to type `getter`. private getMap() { if (!this._map) { + this._setCookies = []; if (this.headersInit != null) { if (Array.isArray(this.headersInit)) { this._map = new Map(); - this.headersInit.forEach(([key, value]) => { + for (const [key, value] of this.headersInit) { const normalizedKey = key.toLowerCase(); if (normalizedKey === 'set-cookie') { this._setCookies.push(value); - return; + continue; } - this._map!.set(normalizedKey, value); - }); + this._map.set(normalizedKey, value); + } } else if (isHeadersLike(this.headersInit)) { this._map = new Map(); this.headersInit.forEach((value, key) => { if (key === 'set-cookie') { + this._setCookies ||= []; this._setCookies.push(value); return; } @@ -88,6 +99,7 @@ export class PonyfillHeaders implements Headers { if (initValue != null) { const normalizedKey = initKey.toLowerCase(); if (normalizedKey === 'set-cookie') { + this._setCookies ||= []; this._setCookies.push(initValue); continue; } @@ -106,6 +118,7 @@ export class PonyfillHeaders implements Headers { append(name: string, value: string): void { const key = name.toLowerCase(); if (key === 'set-cookie') { + this._setCookies ||= []; this._setCookies.push(value); return; } @@ -121,12 +134,12 @@ export class PonyfillHeaders implements Headers { return null; } - return value; + return value.toString(); } has(name: string): boolean { if (name === 'set-cookie') { - return this._setCookies.length > 0; + return !!this._setCookies?.length; } return !!this._get(name); // we might need to check if header exists and not just check if it's not nullable } @@ -150,7 +163,7 @@ export class PonyfillHeaders implements Headers { } forEach(callback: (value: string, key: string, parent: Headers) => void): void { - this._setCookies.forEach(setCookie => { + this._setCookies?.forEach(setCookie => { callback(setCookie, 'set-cookie', this); }); if (!this._map) { @@ -179,7 +192,7 @@ export class PonyfillHeaders implements Headers { } *_keys(): IterableIterator { - if (this._setCookies.length) { + if (this._setCookies?.length) { yield 'set-cookie'; } if (!this._map) { @@ -204,7 +217,9 @@ export class PonyfillHeaders implements Headers { } *_values(): IterableIterator { - yield* this._setCookies; + if (this._setCookies?.length) { + yield* this._setCookies; + } if (!this._map) { if (this.headersInit) { if (Array.isArray(this.headersInit)) { @@ -227,7 +242,9 @@ export class PonyfillHeaders implements Headers { } *_entries(): IterableIterator<[string, string]> { - yield* this._setCookies.map(cookie => ['set-cookie', cookie] as [string, string]); + if (this._setCookies?.length) { + yield* this._setCookies.map(cookie => ['set-cookie', cookie] as [string, string]); + } if (!this._map) { if (this.headersInit) { if (Array.isArray(this.headersInit)) { @@ -250,7 +267,10 @@ export class PonyfillHeaders implements Headers { } getSetCookie() { - return this._setCookies; + if (!this._setCookies) { + this.getMap(); + } + return this._setCookies!; } [Symbol.iterator](): HeadersIterator<[string, string]> { @@ -261,7 +281,7 @@ export class PonyfillHeaders implements Headers { const record: Record = {}; this.forEach((value, key) => { if (key === 'set-cookie') { - record['set-cookie'] = this._setCookies; + record['set-cookie'] = this._setCookies || []; } else { record[key] = value?.includes(',') ? value.split(',').map(el => el.trim()) : value; } diff --git a/packages/node-fetch/tests/Headers.spec.ts b/packages/node-fetch/tests/Headers.spec.ts index 2d706d49b78..45db0cfb180 100644 --- a/packages/node-fetch/tests/Headers.spec.ts +++ b/packages/node-fetch/tests/Headers.spec.ts @@ -61,4 +61,20 @@ describe('Headers', () => { headers.set('X-Header', null!); expect(inspect(headers)).toBe("Headers { 'x-header': null }"); }); + describe('Set-Cookie', () => { + it('handles values in the given map for get method', () => { + const headers = new PonyfillHeaders([ + ['set-cookie', 'a=b'], + ['set-cookie', 'c=d'], + ]); + expect(headers.get('Set-Cookie')).toBe('a=b, c=d'); + }); + it('handles values in the given map for getSetCookie method', () => { + const headers = new PonyfillHeaders([ + ['set-cookie', 'a=b'], + ['set-cookie', 'c=d'], + ]); + expect(headers.getSetCookie()).toEqual(['a=b', 'c=d']); + }); + }); });