Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/honest-loops-cheat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Fixes a bug that caused cookies to ignore custom decode function if has() had been called before
28 changes: 16 additions & 12 deletions packages/astro/src/core/cookies/cookies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ const DELETED_EXPIRATION = new Date(0);
const DELETED_VALUE = 'deleted';
const responseSentSymbol = Symbol.for('astro.responseSent');

const identity = (value: string) => value;

class AstroCookie implements AstroCookieInterface {
constructor(public value: string) {}
json() {
Expand Down Expand Up @@ -116,12 +118,14 @@ class AstroCookies implements AstroCookiesInterface {
return undefined;
}
}
// decodeURIComponent is the default decode function for cookies
const decode = options?.decode ?? decodeURIComponent

const values = this.#ensureParsed(options);
const values = this.#ensureParsed();
if (key in values) {
const value = values[key];
if (value) {
return new AstroCookie(value);
return new AstroCookie(decode(value));
}
}
}
Expand All @@ -130,15 +134,16 @@ class AstroCookies implements AstroCookiesInterface {
* Astro.cookies.has(key) returns a boolean indicating whether this cookie is either
* part of the initial request or set via Astro.cookies.set(key)
* @param key The cookie to check for.
* @param _options This parameter is no longer used.
* @returns
*/
has(key: string, options: AstroCookieGetOptions | undefined = undefined): boolean {
has(key: string, _options?: AstroCookieGetOptions): boolean {
if (this.#outgoing?.has(key)) {
let [, , isSetValue] = this.#outgoing.get(key)!;
return isSetValue;
}
const values = this.#ensureParsed(options);
return !!values[key];
const values = this.#ensureParsed();
return values[key] !== undefined;
Comment on lines -141 to +146
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously it would return false for has() when the cookie had a falsy value, even if it existed

}

/**
Expand Down Expand Up @@ -227,11 +232,9 @@ class AstroCookies implements AstroCookiesInterface {
return cookies.headers();
}

#ensureParsed(
options: AstroCookieGetOptions | undefined = undefined,
): Record<string, string | undefined> {
#ensureParsed(): Record<string, string | undefined> {
if (!this.#requestValues) {
this.#parse(options);
this.#parse();
}
if (!this.#requestValues) {
this.#requestValues = {};
Expand All @@ -246,13 +249,14 @@ class AstroCookies implements AstroCookiesInterface {
return this.#outgoing;
}

#parse(options: AstroCookieGetOptions | undefined = undefined) {
#parse() {
const raw = this.#request.headers.get('cookie');
if (!raw) {
return;
}

this.#requestValues = parse(raw, options);
// Pass identity function for decoding so it doesn't use the default.
// We'll do the actual decoding when we read the value.
this.#requestValues = parse(raw, { decode: identity });
}
}

Expand Down
21 changes: 16 additions & 5 deletions packages/astro/test/units/cookies/get.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ import { apply as applyPolyfill } from '../../../dist/core/polyfill.js';

applyPolyfill();

export const encode = (data) => {
const dataSerialized = typeof data === 'string' ? data : JSON.stringify(data);
return Buffer.from(dataSerialized).toString('base64');
};

export const decode = (str) => {
return Buffer.from(str, 'base64').toString();
};

describe('astro/src/core/cookies', () => {
describe('Astro.cookies.get', () => {
it('gets the cookie value', () => {
Expand All @@ -18,7 +27,7 @@ describe('astro/src/core/cookies', () => {
});

it('gets the cookie value with default decode', () => {
const url = 'http://localhost';
const url = 'http://localhost/?hello=world&foo=bar#hash';
const req = new Request('http://example.com/', {
headers: {
cookie: `url=${encodeURIComponent(url)}`,
Expand All @@ -30,15 +39,17 @@ describe('astro/src/core/cookies', () => {
});

it('gets the cookie value with custom decode', () => {
const url = 'http://localhost';
const url = 'http://localhost/?hello=world&foo=bar#hash';
const req = new Request('http://example.com/', {
headers: {
cookie: `url=${encodeURIComponent(url)}`,
cookie: `url=${encode(url)}`,
},
});
const cookies = new AstroCookies(req);
// set decode to the identity function to prevent decodeURIComponent on the value
assert.equal(cookies.get('url', { decode: (o) => o }).value, encodeURIComponent(url));

assert.ok(cookies.has('url'));
assert.equal(cookies.get('url', { decode } ).value, url);
assert.equal(cookies.get('url').value, encode(url));
});

it("Returns undefined is the value doesn't exist", () => {
Expand Down