Skip to content

Commit d53f818

Browse files
committed
Remote: Use CORS proxy for all PHP requests
Adjusts setupFetchNetworkTransport() to funnel all PHP-sourced network requests through the CORS proxy. This is intentionally not adding that logic to the `fetch` event handler in the service worker, as that would also affect all the cross-origin `fetch()` requests initiated by JavaScript shipped with WordPress plugins. ## Changes in this PR * Adds a `corsProxyUrl` option to `setupFetchNetworkTransport()` * Adds the constants required by the CORS proxy to run in the local dev setup * Configures the CORS proxy to provide the Access-Control-Allow-Origin headers in the local setup where it's running on a different host. ## Concerns * Is it too permissive? * I built this to turn Playground into an RSS reader with @akirk's Friends plugins. It can fetch the list of feeds as expected, but then it usess SimplePie to parse the feeds and that requires curl which we don't provide in the web version of Playground. #1093 is a blocker for that. Until that's resolved, I'm not compelled to prioritize this. ## Testing changes TBD
1 parent 85f8686 commit d53f818

File tree

11 files changed

+79
-37
lines changed

11 files changed

+79
-37
lines changed

packages/playground/blueprints/src/lib/compile.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,11 @@ export interface CompileBlueprintOptions {
6565
/**
6666
* Proxy URL to use for cross-origin requests.
6767
*
68-
* For example, if corsProxy is set to "https://cors.wordpress.net/proxy.php",
68+
* For example, if corsProxyUrl is set to "https://cors.wordpress.net/proxy.php",
6969
* then the CORS requests to https://github.com/WordPress/gutenberg.git would actually
7070
* be made to https://cors.wordpress.net/proxy.php?https://github.com/WordPress/gutenberg.git.
7171
*/
72-
corsProxy?: string;
72+
corsProxyUrl?: string;
7373
}
7474

7575
/**
@@ -86,7 +86,7 @@ export function compileBlueprint(
8686
progress = new ProgressTracker(),
8787
semaphore = new Semaphore({ concurrency: 3 }),
8888
onStepCompleted = () => {},
89-
corsProxy,
89+
corsProxyUrl,
9090
}: CompileBlueprintOptions = {}
9191
): CompiledBlueprint {
9292
// Deep clone the blueprint to avoid mutating the input
@@ -260,7 +260,7 @@ export function compileBlueprint(
260260
semaphore,
261261
rootProgressTracker: progress,
262262
totalProgressWeight,
263-
corsProxy,
263+
corsProxyUrl,
264264
})
265265
);
266266

@@ -422,9 +422,9 @@ interface CompileStepArgsOptions {
422422
/**
423423
* Proxy URL to use for cross-origin requests.
424424
*
425-
* @see CompileBlueprintOptions.corsProxy
425+
* @see CompileBlueprintOptions.corsProxyUrl
426426
*/
427-
corsProxy?: string;
427+
corsProxyUrl?: string;
428428
}
429429

430430
/**
@@ -441,7 +441,7 @@ function compileStep<S extends StepDefinition>(
441441
semaphore,
442442
rootProgressTracker,
443443
totalProgressWeight,
444-
corsProxy,
444+
corsProxyUrl,
445445
}: CompileStepArgsOptions
446446
): { run: CompiledStep; step: S; resources: Array<Resource<any>> } {
447447
const stepProgress = rootProgressTracker.stage(
@@ -454,7 +454,7 @@ function compileStep<S extends StepDefinition>(
454454
if (isResourceReference(value)) {
455455
value = Resource.create(value, {
456456
semaphore,
457-
corsProxy,
457+
corsProxyUrl,
458458
});
459459
}
460460
args[key] = value;

packages/playground/blueprints/src/lib/resources.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -134,12 +134,12 @@ export abstract class Resource<T extends File | Directory> {
134134
{
135135
semaphore,
136136
progress,
137-
corsProxy,
137+
corsProxyUrl,
138138
}: {
139139
/** Optional semaphore to limit concurrent downloads */
140140
semaphore?: Semaphore;
141141
progress?: ProgressTracker;
142-
corsProxy?: string;
142+
corsProxyUrl?: string;
143143
}
144144
): Resource<File | Directory> {
145145
let resource: Resource<File | Directory>;
@@ -161,7 +161,7 @@ export abstract class Resource<T extends File | Directory> {
161161
break;
162162
case 'git:directory':
163163
resource = new GitDirectoryResource(ref, progress, {
164-
corsProxy,
164+
corsProxyUrl,
165165
});
166166
break;
167167
case 'literal:directory':
@@ -452,14 +452,14 @@ export class GitDirectoryResource extends Resource<Directory> {
452452
constructor(
453453
private reference: GitDirectoryReference,
454454
public override _progress?: ProgressTracker,
455-
private options?: { corsProxy?: string }
455+
private options?: { corsProxyUrl?: string }
456456
) {
457457
super();
458458
}
459459

460460
async resolve() {
461-
const repoUrl = this.options?.corsProxy
462-
? `${this.options.corsProxy}?${this.reference.url}`
461+
const repoUrl = this.options?.corsProxyUrl
462+
? `${this.options.corsProxyUrl}?${this.reference.url}`
463463
: this.reference.url;
464464
const ref = ['', 'HEAD'].includes(this.reference.ref)
465465
? 'HEAD'

packages/playground/client/src/index.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,15 +78,15 @@ export interface StartPlaygroundOptions {
7878
/**
7979
* Proxy URL to use for cross-origin requests.
8080
*
81-
* For example, if corsProxy is set to "https://cors.wordpress.net/proxy.php",
81+
* For example, if corsProxyUrl is set to "https://cors.wordpress.net/proxy.php",
8282
* then the CORS requests to https://github.com/WordPress/wordpress-playground.git would actually
8383
* be made to https://cors.wordpress.net/proxy.php?https://github.com/WordPress/wordpress-playground.git.
8484
*
8585
* The Blueprints library will arbitrarily choose which requests to proxy. If you need
8686
* to proxy every single request, do not use this option. Instead, you should preprocess
8787
* your Blueprint to replace all cross-origin URLs with the proxy URL.
8888
*/
89-
corsProxy?: string;
89+
corsProxyUrl?: string;
9090
}
9191

9292
/**
@@ -108,7 +108,7 @@ export async function startPlaygroundWeb({
108108
onBeforeBlueprint,
109109
mounts,
110110
scope,
111-
corsProxy,
111+
corsProxyUrl,
112112
shouldInstallWordPress,
113113
}: StartPlaygroundOptions): Promise<PlaygroundClient> {
114114
assertValidRemote(remoteUrl);
@@ -127,7 +127,7 @@ export async function startPlaygroundWeb({
127127
const compiled = compileBlueprint(blueprint, {
128128
progress: progressTracker.stage(0.5),
129129
onStepCompleted: onBlueprintStepCompleted,
130-
corsProxy,
130+
corsProxyUrl,
131131
});
132132

133133
await new Promise((resolve) => {
@@ -153,6 +153,7 @@ export async function startPlaygroundWeb({
153153
phpVersion: compiled.versions.php,
154154
wpVersion: compiled.versions.wp,
155155
withNetworking: compiled.features.networking,
156+
corsProxyUrl,
156157
});
157158
await playground.isReady();
158159
downloadPHPandWP.finish();
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?php
2+
3+
define('PLAYGROUND_CORS_PROXY_DISABLE_RATE_LIMIT', true);
4+
define('PLAYGROUND_CORS_PROXY_SUPPLY_ACCESS_CONTROL_ALLOW_ORIGIN', true);

packages/playground/php-cors-proxy/cors-proxy.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,12 @@
103103
// Set the response code from the target server
104104
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
105105
http_response_code($httpCode);
106+
if (
107+
defined('PLAYGROUND_CORS_PROXY_SUPPLY_ACCESS_CONTROL_ALLOW_ORIGIN') &&
108+
PLAYGROUND_CORS_PROXY_SUPPLY_ACCESS_CONTROL_ALLOW_ORIGIN
109+
) {
110+
header('Access-Control-Allow-Origin: *');
111+
}
106112
$httpcode_sent = true;
107113
}
108114
$len = strlen($header);

packages/playground/php-cors-proxy/project.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
"start": {
88
"executor": "nx:run-commands",
99
"options": {
10-
"commands": ["php -S 127.0.0.1:5263"],
10+
"commands": [
11+
"php -dauto_prepend_file=cors-proxy-dev-setup.php -S 127.0.0.1:5263"
12+
],
1113
"cwd": "packages/playground/php-cors-proxy"
1214
}
1315
},

packages/playground/remote/src/lib/boot-playground-remote.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,9 @@ export async function bootPlaygroundRemote() {
292292
);
293293

294294
if (options.withNetworking) {
295-
await setupFetchNetworkTransport(phpWorkerApi);
295+
await setupFetchNetworkTransport(phpWorkerApi, {
296+
corsProxyUrl: options.corsProxyUrl,
297+
});
296298
}
297299

298300
setAPIReady();

packages/playground/remote/src/lib/playground-client.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import type {
1111
WorkerBootOptions,
1212
} from './worker-thread';
1313

14+
export interface WebClientBootOptions extends WorkerBootOptions {
15+
corsProxyUrl?: string;
16+
}
1417
export interface WebClientMixin extends ProgressReceiver {
1518
/**
1619
* Sets the progress bar options.
@@ -68,7 +71,7 @@ export interface WebClientMixin extends ProgressReceiver {
6871

6972
unmountOpfs(mountpoint: string): Promise<void>;
7073

71-
boot(options: WorkerBootOptions): Promise<void>;
74+
boot(options: WebClientBootOptions): Promise<void>;
7275
}
7376

7477
/**

packages/playground/remote/src/lib/setup-fetch-network-transport.ts

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ export interface RequestMessage {
1919
*
2020
* @param playground the Playground instance to set up with network support.
2121
*/
22-
export async function setupFetchNetworkTransport(playground: UniversalPHP) {
22+
export async function setupFetchNetworkTransport(
23+
playground: UniversalPHP,
24+
{ corsProxyUrl }: { corsProxyUrl?: string }
25+
) {
2326
await defineWpConfigConsts(playground, {
2427
consts: {
2528
USE_FETCH_FOR_REQUESTS: true,
@@ -58,34 +61,55 @@ export async function setupFetchNetworkTransport(playground: UniversalPHP) {
5861
data.headers['x-request-issuer'] = 'php';
5962
}
6063

61-
return handleRequest(data);
64+
return handleRequest(data, {
65+
corsProxyUrl,
66+
});
6267
});
6368
}
6469

65-
export async function handleRequest(data: RequestData, fetchFn = fetch) {
70+
export async function handleRequest(
71+
data: RequestData,
72+
options: { corsProxyUrl?: string; fetchFn?: typeof fetch }
73+
) {
74+
const { corsProxyUrl, fetchFn = fetch } = options;
6675
const hostname = new URL(data.url).hostname;
67-
const fetchUrl = ['w.org', 's.w.org'].includes(hostname)
76+
const isWdotOrg = ['w.org', 's.w.org'].includes(hostname);
77+
const fetchUrl = isWdotOrg
6878
? `/plugin-proxy.php?url=${encodeURIComponent(data.url)}`
6979
: data.url;
7080

81+
const fetchMethod = data.method || 'GET';
82+
const fetchHeaders = data.headers || {};
83+
if (fetchMethod == 'POST') {
84+
fetchHeaders['Content-Type'] = 'application/x-www-form-urlencoded';
85+
}
86+
7187
let response;
7288
try {
73-
const fetchMethod = data.method || 'GET';
74-
const fetchHeaders = data.headers || {};
75-
if (fetchMethod == 'POST') {
76-
fetchHeaders['Content-Type'] = 'application/x-www-form-urlencoded';
77-
}
78-
7989
response = await fetchFn(fetchUrl, {
8090
method: fetchMethod,
8191
headers: fetchHeaders,
8292
body: data.data,
8393
credentials: 'omit',
8494
});
8595
} catch (e) {
86-
return new TextEncoder().encode(
87-
`HTTP/1.1 400 Invalid Request\r\ncontent-type: text/plain\r\n\r\nPlayground could not serve the request.`
88-
);
96+
if (isWdotOrg || !corsProxyUrl) {
97+
return new TextEncoder().encode(
98+
`HTTP/1.1 400 Invalid Request\r\ncontent-type: text/plain\r\n\r\nPlayground could not serve the request.`
99+
);
100+
}
101+
try {
102+
response = await fetchFn(`${corsProxyUrl}?${fetchUrl}`, {
103+
method: fetchMethod,
104+
headers: fetchHeaders,
105+
body: data.data,
106+
credentials: 'omit',
107+
});
108+
} catch (e) {
109+
return new TextEncoder().encode(
110+
`HTTP/1.1 400 Invalid Request\r\ncontent-type: text/plain\r\n\r\nPlayground could not serve the request.`
111+
);
112+
}
89113
}
90114
const responseHeaders: string[] = [];
91115
response.headers.forEach((value, key) => {

packages/playground/remote/src/test/setup-fetch-network-transport.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ describe('handleRequest', () => {
1919
url: 'https://playground.wordpress.net/',
2020
headers: { 'Content-type': 'text/html' },
2121
},
22-
fetchMock as any
22+
{ fetchFn: fetchMock as any }
2323
);
2424
expect(new TextDecoder().decode(response)).toBe(
2525
`HTTP/1.1 200 OK\r\ncontent-type: text/html\r\n\r\nHello, world!`
@@ -43,7 +43,7 @@ describe('handleRequest', () => {
4343
url: 'https://playground.wordpress.net/',
4444
headers: { 'Content-type': 'text/html' },
4545
},
46-
fetchMock as any
46+
{ fetchFn: fetchMock as any }
4747
);
4848
expect(new TextDecoder().decode(response)).toBe(
4949
`HTTP/1.1 400 Invalid Request\r\ncontent-type: text/plain\r\n\r\nPlayground could not serve the request.`

0 commit comments

Comments
 (0)