Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Networking] Use CORS proxy for all PHP requests #1901

Closed
wants to merge 1 commit into from
Closed
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
16 changes: 8 additions & 8 deletions packages/playground/blueprints/src/lib/compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,11 @@ export interface CompileBlueprintOptions {
/**
* Proxy URL to use for cross-origin requests.
*
* For example, if corsProxy is set to "https://cors.wordpress.net/proxy.php",
* For example, if corsProxyUrl is set to "https://cors.wordpress.net/proxy.php",
* then the CORS requests to https://github.com/WordPress/gutenberg.git would actually
* be made to https://cors.wordpress.net/proxy.php?https://github.com/WordPress/gutenberg.git.
*/
corsProxy?: string;
corsProxyUrl?: string;
}

/**
Expand All @@ -86,7 +86,7 @@ export function compileBlueprint(
progress = new ProgressTracker(),
semaphore = new Semaphore({ concurrency: 3 }),
onStepCompleted = () => {},
corsProxy,
corsProxyUrl,
}: CompileBlueprintOptions = {}
): CompiledBlueprint {
// Deep clone the blueprint to avoid mutating the input
Expand Down Expand Up @@ -260,7 +260,7 @@ export function compileBlueprint(
semaphore,
rootProgressTracker: progress,
totalProgressWeight,
corsProxy,
corsProxyUrl,
})
);

Expand Down Expand Up @@ -422,9 +422,9 @@ interface CompileStepArgsOptions {
/**
* Proxy URL to use for cross-origin requests.
*
* @see CompileBlueprintOptions.corsProxy
* @see CompileBlueprintOptions.corsProxyUrl
*/
corsProxy?: string;
corsProxyUrl?: string;
}

/**
Expand All @@ -441,7 +441,7 @@ function compileStep<S extends StepDefinition>(
semaphore,
rootProgressTracker,
totalProgressWeight,
corsProxy,
corsProxyUrl,
}: CompileStepArgsOptions
): { run: CompiledStep; step: S; resources: Array<Resource<any>> } {
const stepProgress = rootProgressTracker.stage(
Expand All @@ -454,7 +454,7 @@ function compileStep<S extends StepDefinition>(
if (isResourceReference(value)) {
value = Resource.create(value, {
semaphore,
corsProxy,
corsProxyUrl,
});
}
args[key] = value;
Expand Down
12 changes: 6 additions & 6 deletions packages/playground/blueprints/src/lib/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,12 +134,12 @@ export abstract class Resource<T extends File | Directory> {
{
semaphore,
progress,
corsProxy,
corsProxyUrl,
}: {
/** Optional semaphore to limit concurrent downloads */
semaphore?: Semaphore;
progress?: ProgressTracker;
corsProxy?: string;
corsProxyUrl?: string;
}
): Resource<File | Directory> {
let resource: Resource<File | Directory>;
Expand All @@ -161,7 +161,7 @@ export abstract class Resource<T extends File | Directory> {
break;
case 'git:directory':
resource = new GitDirectoryResource(ref, progress, {
corsProxy,
corsProxyUrl,
});
break;
case 'literal:directory':
Expand Down Expand Up @@ -452,14 +452,14 @@ export class GitDirectoryResource extends Resource<Directory> {
constructor(
private reference: GitDirectoryReference,
public override _progress?: ProgressTracker,
private options?: { corsProxy?: string }
private options?: { corsProxyUrl?: string }
) {
super();
}

async resolve() {
const repoUrl = this.options?.corsProxy
? `${this.options.corsProxy}?${this.reference.url}`
const repoUrl = this.options?.corsProxyUrl
? `${this.options.corsProxyUrl}?${this.reference.url}`
: this.reference.url;
const ref = ['', 'HEAD'].includes(this.reference.ref)
? 'HEAD'
Expand Down
9 changes: 5 additions & 4 deletions packages/playground/client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,15 +78,15 @@ export interface StartPlaygroundOptions {
/**
* Proxy URL to use for cross-origin requests.
*
* For example, if corsProxy is set to "https://cors.wordpress.net/proxy.php",
* For example, if corsProxyUrl is set to "https://cors.wordpress.net/proxy.php",
* then the CORS requests to https://github.com/WordPress/wordpress-playground.git would actually
* be made to https://cors.wordpress.net/proxy.php?https://github.com/WordPress/wordpress-playground.git.
*
* The Blueprints library will arbitrarily choose which requests to proxy. If you need
* to proxy every single request, do not use this option. Instead, you should preprocess
* your Blueprint to replace all cross-origin URLs with the proxy URL.
*/
corsProxy?: string;
corsProxyUrl?: string;
}

/**
Expand All @@ -108,7 +108,7 @@ export async function startPlaygroundWeb({
onBeforeBlueprint,
mounts,
scope,
corsProxy,
corsProxyUrl,
shouldInstallWordPress,
}: StartPlaygroundOptions): Promise<PlaygroundClient> {
assertValidRemote(remoteUrl);
Expand All @@ -127,7 +127,7 @@ export async function startPlaygroundWeb({
const compiled = compileBlueprint(blueprint, {
progress: progressTracker.stage(0.5),
onStepCompleted: onBlueprintStepCompleted,
corsProxy,
corsProxyUrl,
});

await new Promise((resolve) => {
Expand All @@ -153,6 +153,7 @@ export async function startPlaygroundWeb({
phpVersion: compiled.versions.php,
wpVersion: compiled.versions.wp,
withNetworking: compiled.features.networking,
corsProxyUrl,
});
await playground.isReady();
downloadPHPandWP.finish();
Expand Down
4 changes: 4 additions & 0 deletions packages/playground/php-cors-proxy/cors-proxy-dev-setup.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?php

define('PLAYGROUND_CORS_PROXY_DISABLE_RATE_LIMIT', true);
Copy link
Member

Choose a reason for hiding this comment

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

Thank you for accounting for the dev setup, @adamziel. This is something I missed when adding rate-limiting.

define('PLAYGROUND_CORS_PROXY_SUPPLY_ACCESS_CONTROL_ALLOW_ORIGIN', true);
6 changes: 6 additions & 0 deletions packages/playground/php-cors-proxy/cors-proxy.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,12 @@
// Set the response code from the target server
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
http_response_code($httpCode);
if (
defined('PLAYGROUND_CORS_PROXY_SUPPLY_ACCESS_CONTROL_ALLOW_ORIGIN') &&
PLAYGROUND_CORS_PROXY_SUPPLY_ACCESS_CONTROL_ALLOW_ORIGIN
) {
header('Access-Control-Allow-Origin: *');
}
$httpcode_sent = true;
}
$len = strlen($header);
Expand Down
4 changes: 3 additions & 1 deletion packages/playground/php-cors-proxy/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
"start": {
"executor": "nx:run-commands",
"options": {
"commands": ["php -S 127.0.0.1:5263"],
"commands": [
"php -dauto_prepend_file=cors-proxy-dev-setup.php -S 127.0.0.1:5263"
],
"cwd": "packages/playground/php-cors-proxy"
}
},
Expand Down
4 changes: 3 additions & 1 deletion packages/playground/remote/src/lib/boot-playground-remote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,9 @@ export async function bootPlaygroundRemote() {
);

if (options.withNetworking) {
await setupFetchNetworkTransport(phpWorkerApi);
await setupFetchNetworkTransport(phpWorkerApi, {
corsProxyUrl: options.corsProxyUrl,
});
}

setAPIReady();
Expand Down
5 changes: 4 additions & 1 deletion packages/playground/remote/src/lib/playground-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import type {
WorkerBootOptions,
} from './worker-thread';

export interface WebClientBootOptions extends WorkerBootOptions {
corsProxyUrl?: string;
}
export interface WebClientMixin extends ProgressReceiver {
/**
* Sets the progress bar options.
Expand Down Expand Up @@ -68,7 +71,7 @@ export interface WebClientMixin extends ProgressReceiver {

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

boot(options: WorkerBootOptions): Promise<void>;
boot(options: WebClientBootOptions): Promise<void>;
}

/**
Expand Down
50 changes: 37 additions & 13 deletions packages/playground/remote/src/lib/setup-fetch-network-transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ export interface RequestMessage {
*
* @param playground the Playground instance to set up with network support.
*/
export async function setupFetchNetworkTransport(playground: UniversalPHP) {
export async function setupFetchNetworkTransport(
playground: UniversalPHP,
{ corsProxyUrl }: { corsProxyUrl?: string }
) {
await defineWpConfigConsts(playground, {
consts: {
USE_FETCH_FOR_REQUESTS: true,
Expand Down Expand Up @@ -58,34 +61,55 @@ export async function setupFetchNetworkTransport(playground: UniversalPHP) {
data.headers['x-request-issuer'] = 'php';
}

return handleRequest(data);
return handleRequest(data, {
corsProxyUrl,
});
});
}

export async function handleRequest(data: RequestData, fetchFn = fetch) {
export async function handleRequest(
data: RequestData,
options: { corsProxyUrl?: string; fetchFn?: typeof fetch }
) {
const { corsProxyUrl, fetchFn = fetch } = options;
const hostname = new URL(data.url).hostname;
const fetchUrl = ['w.org', 's.w.org'].includes(hostname)
const isWdotOrg = ['w.org', 's.w.org'].includes(hostname);
const fetchUrl = isWdotOrg
? `/plugin-proxy.php?url=${encodeURIComponent(data.url)}`
: data.url;

const fetchMethod = data.method || 'GET';
const fetchHeaders = data.headers || {};
if (fetchMethod == 'POST') {
fetchHeaders['Content-Type'] = 'application/x-www-form-urlencoded';
}

let response;
try {
const fetchMethod = data.method || 'GET';
const fetchHeaders = data.headers || {};
if (fetchMethod == 'POST') {
fetchHeaders['Content-Type'] = 'application/x-www-form-urlencoded';
}

response = await fetchFn(fetchUrl, {
method: fetchMethod,
headers: fetchHeaders,
body: data.data,
credentials: 'omit',
});
} catch (e) {
return new TextEncoder().encode(
`HTTP/1.1 400 Invalid Request\r\ncontent-type: text/plain\r\n\r\nPlayground could not serve the request.`
);
if (isWdotOrg || !corsProxyUrl) {
return new TextEncoder().encode(
`HTTP/1.1 400 Invalid Request\r\ncontent-type: text/plain\r\n\r\nPlayground could not serve the request.`
);
}
try {
response = await fetchFn(`${corsProxyUrl}?${fetchUrl}`, {
method: fetchMethod,
headers: fetchHeaders,
body: data.data,
credentials: 'omit',
});
} catch (e) {
return new TextEncoder().encode(
`HTTP/1.1 400 Invalid Request\r\ncontent-type: text/plain\r\n\r\nPlayground could not serve the request.`
);
}
}
const responseHeaders: string[] = [];
response.headers.forEach((value, key) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ describe('handleRequest', () => {
url: 'https://playground.wordpress.net/',
headers: { 'Content-type': 'text/html' },
},
fetchMock as any
{ fetchFn: fetchMock as any }
);
expect(new TextDecoder().decode(response)).toBe(
`HTTP/1.1 200 OK\r\ncontent-type: text/html\r\n\r\nHello, world!`
Expand All @@ -43,7 +43,7 @@ describe('handleRequest', () => {
url: 'https://playground.wordpress.net/',
headers: { 'Content-type': 'text/html' },
},
fetchMock as any
{ fetchFn: fetchMock as any }
);
expect(new TextDecoder().decode(response)).toBe(
`HTTP/1.1 400 Invalid Request\r\ncontent-type: text/plain\r\n\r\nPlayground could not serve the request.`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ export function bootSiteClient(
]
: [],
shouldInstallWordPress: !isWordPressInstalled,
corsProxy: corsProxyUrl,
corsProxyUrl,
});

// @TODO: Remove backcompat code after 2024-12-01.
Expand Down
Loading