Skip to content

Commit

Permalink
fix(node-fetch): respect set-cookies given in HeadersInit (#2079)
Browse files Browse the repository at this point in the history
  • Loading branch information
ardatan authored Feb 20, 2025
1 parent 3d043c5 commit 090b4b0
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 15 deletions.
5 changes: 5 additions & 0 deletions .changeset/curvy-insects-cheer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@whatwg-node/node-fetch': patch
---

Fix the bug when `set-cookies` given is ignored in `HeadersInit`
50 changes: 35 additions & 15 deletions packages/node-fetch/src/Headers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ export class PonyfillHeaders implements Headers {
private _map: Map<string, string> | 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
Expand All @@ -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 {
Expand Down Expand Up @@ -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;
}
Expand All @@ -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;
}
Expand All @@ -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;
}
Expand All @@ -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
}
Expand All @@ -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) {
Expand Down Expand Up @@ -179,7 +192,7 @@ export class PonyfillHeaders implements Headers {
}

*_keys(): IterableIterator<string> {
if (this._setCookies.length) {
if (this._setCookies?.length) {
yield 'set-cookie';
}
if (!this._map) {
Expand All @@ -204,7 +217,9 @@ export class PonyfillHeaders implements Headers {
}

*_values(): IterableIterator<string> {
yield* this._setCookies;
if (this._setCookies?.length) {
yield* this._setCookies;
}
if (!this._map) {
if (this.headersInit) {
if (Array.isArray(this.headersInit)) {
Expand All @@ -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)) {
Expand All @@ -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]> {
Expand All @@ -261,7 +281,7 @@ export class PonyfillHeaders implements Headers {
const record: Record<string, string[] | string> = {};
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;
}
Expand Down
16 changes: 16 additions & 0 deletions packages/node-fetch/tests/Headers.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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']);
});
});
});

0 comments on commit 090b4b0

Please sign in to comment.