Skip to content

Commit

Permalink
feat: [capricorn86#1315] Implement the getSetCookie method of Headers
Browse files Browse the repository at this point in the history
  • Loading branch information
ushiboy committed Mar 16, 2024
1 parent 8b2eb74 commit d774b93
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 0 deletions.
64 changes: 64 additions & 0 deletions packages/happy-dom/src/cookie/urilities/CookieStringUtility.ts
Expand Up @@ -113,4 +113,68 @@ export default class CookieStringUtility {

return cookieString.join('; ');
}

/**
* Returns a list of Cookie String parsed from Cookies String.
*
* Based on:
* https://github.com/nfriedly/set-cookie-parser/blob/master/lib/set-cookie.js (MIT)
*
* @param cookiesString Cookies string.
* @returns Cookies strings.
*/
public static splitCookiesString(cookiesString: string): string[] {
let pos = 0;

const skipWhiteSpace = (): boolean => {
while (pos < cookiesString.length && /\s/.test(cookiesString.charAt(pos))) {
pos += 1;
}
return pos < cookiesString.length;
};

const notSpecialChar = (): boolean => {
const ch = cookiesString.charAt(pos);
return ch !== '=' && ch !== ';' && ch !== ',';
};

const cookiesStrings: string[] = [];

while (pos < cookiesString.length) {
let start = pos;
let cookiesSeparatorFound = false;

while (skipWhiteSpace()) {
const ch = cookiesString.charAt(pos);
if (ch === ',') {
const lastComma = pos;
pos += 1;

skipWhiteSpace();
const nextStart = pos;

while (pos < cookiesString.length && notSpecialChar()) {
pos += 1;
}

if (pos < cookiesString.length && cookiesString.charAt(pos) === '=') {
cookiesSeparatorFound = true;
pos = nextStart;
cookiesStrings.push(cookiesString.substring(start, lastComma));
start = pos;
} else {
pos = lastComma + 1;
}
} else {
pos += 1;
}
}

if (!cookiesSeparatorFound || pos >= cookiesString.length) {
cookiesStrings.push(cookiesString.substring(start, cookiesString.length));
}
}

return cookiesStrings;
}
}
10 changes: 10 additions & 0 deletions packages/happy-dom/src/fetch/Headers.ts
Expand Up @@ -3,6 +3,7 @@ import * as PropertySymbol from '../PropertySymbol.js';
import DOMExceptionNameEnum from '../exception/DOMExceptionNameEnum.js';
import IHeaders from './types/IHeaders.js';
import IHeadersInit from './types/IHeadersInit.js';
import CookieStringUtility from '../cookie/urilities/CookieStringUtility.js';

/**
* Fetch headers.
Expand Down Expand Up @@ -89,6 +90,15 @@ export default class Headers implements IHeaders {
};
}

/**
* Returns an array containing the values of all Set-Cookie headers associated with a response.
*
* @returns An array of strings representing the values of all the different Set-Cookie headers.
*/
public getSetCookie(): string[] {
return CookieStringUtility.splitCookiesString(this.get('Set-Cookie') || '');
}

/**
* Returns whether an Headers object contains a certain key.
*
Expand Down
7 changes: 7 additions & 0 deletions packages/happy-dom/src/fetch/types/IHeaders.ts
Expand Up @@ -35,6 +35,13 @@ export default interface IHeaders extends Iterable<[string, string]> {
*/
set(name: string, value: string): void;

/**
* Returns an array containing the values of all Set-Cookie headers associated with a response.
*
* @returns An array of strings representing the values of all the different Set-Cookie headers.
*/
getSetCookie(): string[];

/**
* Returns whether an Headers object contains a certain key.
*
Expand Down
@@ -0,0 +1,48 @@
import CookieStringUtility from '../../../src/cookie/urilities/CookieStringUtility.js';
import { describe, it, expect } from 'vitest';

describe('CookieStringUtility', () => {
describe('splitCookiesString()', () => {
it('Should parse empty string.', () => {
expect(CookieStringUtility.splitCookiesString('')).toEqual([]);
});

it('Should parse single cookie without params.', () => {
expect(CookieStringUtility.splitCookiesString('key=value')).toEqual(['key=value']);
});

it('Should parse single cookie with params.', () => {
expect(
CookieStringUtility.splitCookiesString(
'key=value; HttpOnly; Path=/; Expires=Fri, 01 Jan 2100 00:00:00 GMT'
)
).toEqual(['key=value; HttpOnly; Path=/; Expires=Fri, 01 Jan 2100 00:00:00 GMT']);
});

it('Should parse multiple cookies without params.', () => {
expect(CookieStringUtility.splitCookiesString('key1=value1, key2=value2')).toEqual([
'key1=value1',
'key2=value2'
]);
});

it('Should parse multiple cookies with params.', () => {
expect(
CookieStringUtility.splitCookiesString(
'key1=value1; HttpOnly; Path=/; Expires=Fri, 01 Jan 2100 00:00:00 GMT, key2=value2; Domain=example.com; Max-Age=1'
)
).toEqual([
'key1=value1; HttpOnly; Path=/; Expires=Fri, 01 Jan 2100 00:00:00 GMT',
'key2=value2; Domain=example.com; Max-Age=1'
]);
});

it('Should parse multiple cookies including those with and without params.', () => {
expect(
CookieStringUtility.splitCookiesString(
'key1=value1, key2=value2; Expires=Fri, 01 Jan 2100 00:00:00 GMT; Path=/'
)
).toEqual(['key1=value1', 'key2=value2; Expires=Fri, 01 Jan 2100 00:00:00 GMT; Path=/']);
});
});
});
21 changes: 21 additions & 0 deletions packages/happy-dom/test/fetch/Headers.test.ts
Expand Up @@ -99,6 +99,27 @@ describe('Headers', () => {
});
});

describe('getSetCookie()', () => {
it('Returns an empty list if there is no Set-Cookie header.', () => {
const headers = new Headers();

expect(headers.getSetCookie()).toEqual([]);
});

it('Returns an array of strings representing the values of all the different Set-Cookie headers.', () => {
const headers = new Headers();

headers.append('Content-Type', 'application/json');
headers.append('Set-Cookie', 'a=1');
headers.append('Set-Cookie', 'b=2; Expires=Fri, 01 Jan 2100 00:00:00 GMT');

expect(headers.getSetCookie()).toEqual([
'a=1',
'b=2; Expires=Fri, 01 Jan 2100 00:00:00 GMT'
]);
});
});

describe('has()', () => {
it('Returns true if an header exists.', () => {
const headers = new Headers();
Expand Down

0 comments on commit d774b93

Please sign in to comment.