Skip to content

Commit 5b17006

Browse files
committed
Make cookie store web-only
It is needed on the web where custom Response objects may not be assigned Set-Cookie headers. Otherwise, cookies from PHP requests would not be respected. But a cookie store isn't needed for Playground CLI, and applying the same cookies to all requests to a CLI-based server interferes with clients that want to use other WP auth schemes.
1 parent ac36cbe commit 5b17006

File tree

3 files changed

+59
-11
lines changed

3 files changed

+59
-11
lines changed

packages/php-wasm/universal/src/lib/php-request-handler.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import {
1515
PHPProcessManager,
1616
SpawnedPHP,
1717
} from './php-process-manager';
18-
import { HttpCookieStore } from './http-cookie-store';
1918
import mimeTypes from './mime-types.json';
2019

2120
export type RewriteRule = {
@@ -159,7 +158,6 @@ export class PHPRequestHandler {
159158
#HOST: string;
160159
#PATHNAME: string;
161160
#ABSOLUTE_URL: string;
162-
#cookieStore: HttpCookieStore;
163161
rewriteRules: RewriteRule[];
164162
processManager: PHPProcessManager;
165163
getFileNotFoundAction: FileNotFoundGetActionCallback;
@@ -198,7 +196,6 @@ export class PHPRequestHandler {
198196
maxPhpInstances: config.maxPhpInstances,
199197
});
200198
}
201-
this.#cookieStore = new HttpCookieStore();
202199
this.#DOCROOT = documentRoot;
203200

204201
const url = new URL(absoluteUrl);
@@ -490,7 +487,6 @@ export class PHPRequestHandler {
490487
const headers: Record<string, string> = {
491488
host: this.#HOST,
492489
...normalizeHeaders(request.headers || {}),
493-
cookie: this.#cookieStore.getCookieRequestHeader(),
494490
};
495491

496492
let body = request.body;
@@ -520,9 +516,6 @@ export class PHPRequestHandler {
520516
scriptPath,
521517
headers,
522518
});
523-
this.#cookieStore.rememberCookiesFromResponseHeaders(
524-
response.headers
525-
);
526519
return response;
527520
} catch (error) {
528521
const executionError = error as PHPExecutionFailureError;

packages/php-wasm/web-service-worker/src/utils.ts

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,12 @@ declare const self: ServiceWorkerGlobalScope;
33

44
import { awaitReply, getNextRequestId } from './messaging';
55
import { getURLScope, isURLScoped, setURLScope } from '@php-wasm/scopes';
6+
import { HttpCookieStore } from '@php-wasm/universal';
67

7-
export async function convertFetchEventToPHPRequest(event: FetchEvent) {
8+
export async function convertFetchEventToPHPRequest(
9+
event: FetchEvent,
10+
cookieStore: HttpCookieStore
11+
) {
812
let url = new URL(event.request.url);
913

1014
if (!isURLScoped(url)) {
@@ -22,8 +26,28 @@ export async function convertFetchEventToPHPRequest(event: FetchEvent) {
2226
? new Uint8Array(await event.request.clone().arrayBuffer())
2327
: undefined;
2428
const requestHeaders: Record<string, string> = {};
29+
30+
let providedCookies = '';
31+
// @TODO: Why don't our types properly include Headers#entries()?
2532
for (const pair of (event.request.headers as any).entries()) {
26-
requestHeaders[pair[0]] = pair[1];
33+
const [name, value] = pair as [string, string];
34+
if (name.toLowerCase() === 'cookie') {
35+
providedCookies = value;
36+
} else {
37+
requestHeaders[name] = value;
38+
}
39+
}
40+
41+
const credentialsAllowed =
42+
event.request.credentials === 'include' ||
43+
event.request.credentials === 'same-origin';
44+
if (credentialsAllowed) {
45+
const storedCookies = cookieStore.getCookieRequestHeader();
46+
const effectiveCookies = [storedCookies];
47+
if (providedCookies) {
48+
effectiveCookies.push(providedCookies);
49+
}
50+
requestHeaders['Cookie'] = effectiveCookies.join('; ');
2751
}
2852

2953
let phpResponse;
@@ -63,6 +87,23 @@ export async function convertFetchEventToPHPRequest(event: FetchEvent) {
6387
throw e;
6488
}
6589

90+
/**
91+
* We should not consider Set-Cookie headers unless credentials are allowed.
92+
* From https://fetch.spec.whatwg.org/#concept-request-credentials-mode :
93+
* > "omit"
94+
* > Excludes credentials from this request, and causes any credentials
95+
* > sent back in the response to be ignored.
96+
*/
97+
if (credentialsAllowed) {
98+
// @TODO: Make way to relay non-HttpOnly cookies from PHP to the client.
99+
// Those would be available to client JS in a classic WP setup.
100+
cookieStore.rememberCookiesFromResponseHeaders(phpResponse.headers);
101+
}
102+
// Explicitly remove the Set-Cookie header because:
103+
// - The browser is forbidden from relaying it via custom Response objects.
104+
// - We can be sure it is not relayed by removing it ourselves.
105+
delete phpResponse.headers['set-cookie'];
106+
66107
/**
67108
* Safari has a bug that prevents Service Workers from redirecting relative URLs.
68109
* When attempting to redirect to a relative URL, the network request will return an error.

packages/playground/remote/service-worker.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,8 @@
102102
declare const self: ServiceWorkerGlobalScope;
103103

104104
import { getURLScope, isURLScoped, removeURLScope } from '@php-wasm/scopes';
105-
import { applyRewriteRules } from '@php-wasm/universal';
105+
// @TODO: Consider moving HttpCookieStore definition to web-specific package.
106+
import { applyRewriteRules, HttpCookieStore } from '@php-wasm/universal';
106107
import {
107108
awaitReply,
108109
convertFetchEventToPHPRequest,
@@ -315,6 +316,11 @@ self.addEventListener('fetch', (event) => {
315316
return event.respondWith(cacheFirstFetch(event.request));
316317
});
317318

319+
// @TODO: Persist these cookie stores for the life of the browser session
320+
// so Playground can tolerate a service worker being terminated and restarted
321+
// while the app is in use.
322+
const scopedCookieStores = new Map<string, HttpCookieStore>();
323+
318324
/**
319325
* A request to a PHP Worker Thread or to a regular static asset,
320326
* but initiated by a scoped referer (e.g. fetch() from a block editor iframe).
@@ -326,7 +332,15 @@ async function handleScopedRequest(event: FetchEvent, scope: string) {
326332
return emptyHtml();
327333
}
328334

329-
const workerResponse = await convertFetchEventToPHPRequest(event);
335+
if (!scopedCookieStores.has(scope)) {
336+
scopedCookieStores.set(scope, new HttpCookieStore());
337+
}
338+
const cookieStore = scopedCookieStores.get(scope) as HttpCookieStore;
339+
340+
const workerResponse = await convertFetchEventToPHPRequest(
341+
event,
342+
cookieStore
343+
);
330344

331345
if (
332346
workerResponse.status === 404 &&

0 commit comments

Comments
 (0)