@php-wasm/universal 0.1.40 → 0.1.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.cjs +40 -0
- package/index.js +1070 -0
- package/lib/base-php.d.ts +70 -0
- package/lib/error-event-polyfill.d.ts +30 -0
- package/{src/lib/index.ts → lib/index.d.ts} +4 -36
- package/lib/is-local-php.d.ts +2 -0
- package/lib/is-remote-php.d.ts +2 -0
- package/{src/lib/load-php-runtime.ts → lib/load-php-runtime.d.ts} +23 -101
- package/lib/php-browser.d.ts +52 -0
- package/lib/php-request-handler.d.ts +43 -0
- package/lib/php-response.d.ts +54 -0
- package/lib/rethrow-file-system-error.d.ts +13 -0
- package/lib/supported-php-versions.d.ts +4 -0
- package/lib/universal-php.d.ts +309 -0
- package/{src/lib/urls.ts → lib/urls.d.ts} +4 -19
- package/lib/wasm-error-reporting.d.ts +26 -0
- package/package.json +4 -3
- package/.eslintrc.json +0 -18
- package/LICENSE +0 -339
- package/README.md +0 -0
- package/project.json +0 -50
- package/src/lib/base-php.ts +0 -555
- package/src/lib/error-event-polyfill.ts +0 -50
- package/src/lib/is-local-php.ts +0 -8
- package/src/lib/is-remote-php.ts +0 -8
- package/src/lib/php-browser.ts +0 -137
- package/src/lib/php-request-handler.ts +0 -381
- package/src/lib/php-response.ts +0 -104
- package/src/lib/rethrow-file-system-error.ts +0 -125
- package/src/lib/supported-php-versions.ts +0 -14
- package/src/lib/universal-php.ts +0 -354
- package/src/lib/wasm-error-reporting.ts +0 -172
- package/tsconfig.json +0 -22
- package/tsconfig.lib.json +0 -14
- package/tsconfig.spec.json +0 -20
- package/vite.config.ts +0 -47
- /package/{src/index.ts → index.d.ts} +0 -0
package/src/lib/php-browser.ts
DELETED
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
import type { PHPRequestHandler } from './php-request-handler';
|
|
2
|
-
import type { PHPResponse } from './php-response';
|
|
3
|
-
import { PHPRequest, RequestHandler } from './universal-php';
|
|
4
|
-
|
|
5
|
-
export interface PHPBrowserConfiguration {
|
|
6
|
-
/**
|
|
7
|
-
* Should handle redirects internally?
|
|
8
|
-
*/
|
|
9
|
-
handleRedirects?: boolean;
|
|
10
|
-
/**
|
|
11
|
-
* The maximum number of redirects to follow internally. Once
|
|
12
|
-
* exceeded, request() will return the redirecting response.
|
|
13
|
-
*/
|
|
14
|
-
maxRedirects?: number;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* A fake web browser that handles PHPRequestHandler's cookies and redirects
|
|
19
|
-
* internally without exposing them to the consumer.
|
|
20
|
-
*
|
|
21
|
-
* @public
|
|
22
|
-
*/
|
|
23
|
-
export class PHPBrowser implements RequestHandler {
|
|
24
|
-
#cookies: Record<string, string>;
|
|
25
|
-
#config;
|
|
26
|
-
|
|
27
|
-
requestHandler: PHPRequestHandler;
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* @param server - The PHP server to browse.
|
|
31
|
-
* @param config - The browser configuration.
|
|
32
|
-
*/
|
|
33
|
-
constructor(
|
|
34
|
-
requestHandler: PHPRequestHandler,
|
|
35
|
-
config: PHPBrowserConfiguration = {}
|
|
36
|
-
) {
|
|
37
|
-
this.requestHandler = requestHandler;
|
|
38
|
-
this.#cookies = {};
|
|
39
|
-
this.#config = {
|
|
40
|
-
handleRedirects: false,
|
|
41
|
-
maxRedirects: 4,
|
|
42
|
-
...config,
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Sends the request to the server.
|
|
48
|
-
*
|
|
49
|
-
* When cookies are present in the response, this method stores
|
|
50
|
-
* them and sends them with any subsequent requests.
|
|
51
|
-
*
|
|
52
|
-
* When a redirection is present in the response, this method
|
|
53
|
-
* follows it by discarding a response and sending a subsequent
|
|
54
|
-
* request.
|
|
55
|
-
*
|
|
56
|
-
* @param request - The request.
|
|
57
|
-
* @param redirects - Internal. The number of redirects handled so far.
|
|
58
|
-
* @returns PHPRequestHandler response.
|
|
59
|
-
*/
|
|
60
|
-
async request(request: PHPRequest, redirects = 0): Promise<PHPResponse> {
|
|
61
|
-
const response = await this.requestHandler.request({
|
|
62
|
-
...request,
|
|
63
|
-
headers: {
|
|
64
|
-
...request.headers,
|
|
65
|
-
cookie: this.#serializeCookies(),
|
|
66
|
-
},
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
if (response.headers['set-cookie']) {
|
|
70
|
-
this.#setCookies(response.headers['set-cookie']);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (
|
|
74
|
-
this.#config.handleRedirects &&
|
|
75
|
-
response.headers['location'] &&
|
|
76
|
-
redirects < this.#config.maxRedirects
|
|
77
|
-
) {
|
|
78
|
-
const redirectUrl = new URL(
|
|
79
|
-
response.headers['location'][0],
|
|
80
|
-
this.requestHandler.absoluteUrl
|
|
81
|
-
);
|
|
82
|
-
return this.request(
|
|
83
|
-
{
|
|
84
|
-
url: redirectUrl.toString(),
|
|
85
|
-
method: 'GET',
|
|
86
|
-
headers: {},
|
|
87
|
-
},
|
|
88
|
-
redirects + 1
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return response;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/** @inheritDoc */
|
|
96
|
-
pathToInternalUrl(path: string) {
|
|
97
|
-
return this.requestHandler.pathToInternalUrl(path);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/** @inheritDoc */
|
|
101
|
-
internalUrlToPath(internalUrl: string) {
|
|
102
|
-
return this.requestHandler.internalUrlToPath(internalUrl);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/** @inheritDoc */
|
|
106
|
-
get absoluteUrl() {
|
|
107
|
-
return this.requestHandler.absoluteUrl;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/** @inheritDoc */
|
|
111
|
-
get documentRoot() {
|
|
112
|
-
return this.requestHandler.documentRoot;
|
|
113
|
-
}
|
|
114
|
-
#setCookies(cookies: string[]) {
|
|
115
|
-
for (const cookie of cookies) {
|
|
116
|
-
try {
|
|
117
|
-
if (!cookie.includes('=')) {
|
|
118
|
-
continue;
|
|
119
|
-
}
|
|
120
|
-
const equalsIndex = cookie.indexOf('=');
|
|
121
|
-
const name = cookie.substring(0, equalsIndex);
|
|
122
|
-
const value = cookie.substring(equalsIndex + 1).split(';')[0];
|
|
123
|
-
this.#cookies[name] = value;
|
|
124
|
-
} catch (e) {
|
|
125
|
-
console.error(e);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
#serializeCookies() {
|
|
131
|
-
const cookiesArray: string[] = [];
|
|
132
|
-
for (const name in this.#cookies) {
|
|
133
|
-
cookiesArray.push(`${name}=${this.#cookies[name]}`);
|
|
134
|
-
}
|
|
135
|
-
return cookiesArray.join('; ');
|
|
136
|
-
}
|
|
137
|
-
}
|
|
@@ -1,381 +0,0 @@
|
|
|
1
|
-
import { Semaphore } from '@php-wasm/util';
|
|
2
|
-
import {
|
|
3
|
-
ensurePathPrefix,
|
|
4
|
-
toRelativeUrl,
|
|
5
|
-
removePathPrefix,
|
|
6
|
-
DEFAULT_BASE_URL,
|
|
7
|
-
} from './urls';
|
|
8
|
-
import { BasePHP, normalizeHeaders } from './base-php';
|
|
9
|
-
import { PHPResponse } from './php-response';
|
|
10
|
-
import {
|
|
11
|
-
FileInfo,
|
|
12
|
-
PHPRequest,
|
|
13
|
-
PHPRunOptions,
|
|
14
|
-
RequestHandler,
|
|
15
|
-
} from './universal-php';
|
|
16
|
-
|
|
17
|
-
export interface PHPRequestHandlerConfiguration {
|
|
18
|
-
/**
|
|
19
|
-
* The directory in the PHP filesystem where the server will look
|
|
20
|
-
* for the files to serve. Default: `/var/www`.
|
|
21
|
-
*/
|
|
22
|
-
documentRoot?: string;
|
|
23
|
-
/**
|
|
24
|
-
* Request Handler URL. Used to populate $_SERVER details like HTTP_HOST.
|
|
25
|
-
*/
|
|
26
|
-
absoluteUrl?: string;
|
|
27
|
-
/**
|
|
28
|
-
* Callback used by the PHPRequestHandler to decide whether
|
|
29
|
-
* the requested path refers to a PHP file or a static file.
|
|
30
|
-
*/
|
|
31
|
-
isStaticFilePath?: (path: string) => boolean;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/** @inheritDoc */
|
|
35
|
-
export class PHPRequestHandler implements RequestHandler {
|
|
36
|
-
#DOCROOT: string;
|
|
37
|
-
#PROTOCOL: string;
|
|
38
|
-
#HOSTNAME: string;
|
|
39
|
-
#PORT: number;
|
|
40
|
-
#HOST: string;
|
|
41
|
-
#PATHNAME: string;
|
|
42
|
-
#ABSOLUTE_URL: string;
|
|
43
|
-
#semaphore: Semaphore;
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* The PHP instance
|
|
47
|
-
*/
|
|
48
|
-
php: BasePHP;
|
|
49
|
-
#isStaticFilePath: (path: string) => boolean;
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* @param php - The PHP instance.
|
|
53
|
-
* @param config - Request Handler configuration.
|
|
54
|
-
*/
|
|
55
|
-
constructor(php: BasePHP, config: PHPRequestHandlerConfiguration = {}) {
|
|
56
|
-
this.#semaphore = new Semaphore({ concurrency: 1 });
|
|
57
|
-
const {
|
|
58
|
-
documentRoot = '/www/',
|
|
59
|
-
absoluteUrl = typeof location === 'object' ? location?.href : '',
|
|
60
|
-
isStaticFilePath = () => false,
|
|
61
|
-
} = config;
|
|
62
|
-
this.php = php;
|
|
63
|
-
this.#DOCROOT = documentRoot;
|
|
64
|
-
this.#isStaticFilePath = isStaticFilePath;
|
|
65
|
-
|
|
66
|
-
const url = new URL(absoluteUrl);
|
|
67
|
-
this.#HOSTNAME = url.hostname;
|
|
68
|
-
this.#PORT = url.port
|
|
69
|
-
? Number(url.port)
|
|
70
|
-
: url.protocol === 'https:'
|
|
71
|
-
? 443
|
|
72
|
-
: 80;
|
|
73
|
-
this.#PROTOCOL = (url.protocol || '').replace(':', '');
|
|
74
|
-
const isNonStandardPort = this.#PORT !== 443 && this.#PORT !== 80;
|
|
75
|
-
this.#HOST = [
|
|
76
|
-
this.#HOSTNAME,
|
|
77
|
-
isNonStandardPort ? `:${this.#PORT}` : '',
|
|
78
|
-
].join('');
|
|
79
|
-
this.#PATHNAME = url.pathname.replace(/\/+$/, '');
|
|
80
|
-
this.#ABSOLUTE_URL = [
|
|
81
|
-
`${this.#PROTOCOL}://`,
|
|
82
|
-
this.#HOST,
|
|
83
|
-
this.#PATHNAME,
|
|
84
|
-
].join('');
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/** @inheritDoc */
|
|
88
|
-
pathToInternalUrl(path: string): string {
|
|
89
|
-
return `${this.absoluteUrl}${path}`;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/** @inheritDoc */
|
|
93
|
-
internalUrlToPath(internalUrl: string): string {
|
|
94
|
-
const url = new URL(internalUrl);
|
|
95
|
-
if (url.pathname.startsWith(this.#PATHNAME)) {
|
|
96
|
-
url.pathname = url.pathname.slice(this.#PATHNAME.length);
|
|
97
|
-
}
|
|
98
|
-
return toRelativeUrl(url);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
get isRequestRunning() {
|
|
102
|
-
return this.#semaphore.running > 0;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/** @inheritDoc */
|
|
106
|
-
get absoluteUrl() {
|
|
107
|
-
return this.#ABSOLUTE_URL;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/** @inheritDoc */
|
|
111
|
-
get documentRoot() {
|
|
112
|
-
return this.#DOCROOT;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/** @inheritDoc */
|
|
116
|
-
async request(request: PHPRequest): Promise<PHPResponse> {
|
|
117
|
-
const isAbsolute =
|
|
118
|
-
request.url.startsWith('http://') ||
|
|
119
|
-
request.url.startsWith('https://');
|
|
120
|
-
const requestedUrl = new URL(
|
|
121
|
-
request.url,
|
|
122
|
-
isAbsolute ? undefined : DEFAULT_BASE_URL
|
|
123
|
-
);
|
|
124
|
-
|
|
125
|
-
const normalizedRelativeUrl = removePathPrefix(
|
|
126
|
-
requestedUrl.pathname,
|
|
127
|
-
this.#PATHNAME
|
|
128
|
-
);
|
|
129
|
-
if (this.#isStaticFilePath(normalizedRelativeUrl)) {
|
|
130
|
-
return this.#serveStaticFile(normalizedRelativeUrl);
|
|
131
|
-
}
|
|
132
|
-
return await this.#dispatchToPHP(request, requestedUrl);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Serves a static file from the PHP filesystem.
|
|
137
|
-
*
|
|
138
|
-
* @param path - The requested static file path.
|
|
139
|
-
* @returns The response.
|
|
140
|
-
*/
|
|
141
|
-
#serveStaticFile(path: string): PHPResponse {
|
|
142
|
-
const fsPath = `${this.#DOCROOT}${path}`;
|
|
143
|
-
|
|
144
|
-
if (!this.php.fileExists(fsPath)) {
|
|
145
|
-
return new PHPResponse(
|
|
146
|
-
404,
|
|
147
|
-
{},
|
|
148
|
-
new TextEncoder().encode('404 File not found')
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
const arrayBuffer = this.php.readFileAsBuffer(fsPath);
|
|
152
|
-
return new PHPResponse(
|
|
153
|
-
200,
|
|
154
|
-
{
|
|
155
|
-
'content-length': [`${arrayBuffer.byteLength}`],
|
|
156
|
-
// @TODO: Infer the content-type from the arrayBuffer instead of the file path.
|
|
157
|
-
// The code below won't return the correct mime-type if the extension
|
|
158
|
-
// was tampered with.
|
|
159
|
-
'content-type': [inferMimeType(fsPath)],
|
|
160
|
-
'accept-ranges': ['bytes'],
|
|
161
|
-
'cache-control': ['public, max-age=0'],
|
|
162
|
-
},
|
|
163
|
-
arrayBuffer
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Runs the requested PHP file with all the request and $_SERVER
|
|
169
|
-
* superglobals populated.
|
|
170
|
-
*
|
|
171
|
-
* @param request - The request.
|
|
172
|
-
* @returns The response.
|
|
173
|
-
*/
|
|
174
|
-
async #dispatchToPHP(
|
|
175
|
-
request: PHPRequest,
|
|
176
|
-
requestedUrl: URL
|
|
177
|
-
): Promise<PHPResponse> {
|
|
178
|
-
/*
|
|
179
|
-
* Prevent multiple requests from running at the same time.
|
|
180
|
-
* For example, if a request is made to a PHP file that
|
|
181
|
-
* requests another PHP file, the second request may
|
|
182
|
-
* be dispatched before the first one is finished.
|
|
183
|
-
*/
|
|
184
|
-
const release = await this.#semaphore.acquire();
|
|
185
|
-
try {
|
|
186
|
-
this.php.addServerGlobalEntry('DOCUMENT_ROOT', this.#DOCROOT);
|
|
187
|
-
this.php.addServerGlobalEntry(
|
|
188
|
-
'HTTPS',
|
|
189
|
-
this.#ABSOLUTE_URL.startsWith('https://') ? 'on' : ''
|
|
190
|
-
);
|
|
191
|
-
|
|
192
|
-
let preferredMethod: PHPRunOptions['method'] = 'GET';
|
|
193
|
-
|
|
194
|
-
const headers: Record<string, string> = {
|
|
195
|
-
host: this.#HOST,
|
|
196
|
-
...normalizeHeaders(request.headers || {}),
|
|
197
|
-
};
|
|
198
|
-
const fileInfos: FileInfo[] = [];
|
|
199
|
-
if (request.files && Object.keys(request.files).length) {
|
|
200
|
-
preferredMethod = 'POST';
|
|
201
|
-
for (const key in request.files) {
|
|
202
|
-
const file: File = request.files[key];
|
|
203
|
-
fileInfos.push({
|
|
204
|
-
key,
|
|
205
|
-
name: file.name,
|
|
206
|
-
type: file.type,
|
|
207
|
-
data: new Uint8Array(await file.arrayBuffer()),
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* When the files are present, we can't use the multipart/form-data
|
|
213
|
-
* Content-type header. Instead, we rewrite the request body
|
|
214
|
-
* to application/x-www-form-urlencoded.
|
|
215
|
-
* See the phpwasm_init_uploaded_files_hash() docstring for more details.
|
|
216
|
-
*/
|
|
217
|
-
if (
|
|
218
|
-
headers['content-type']?.startsWith('multipart/form-data')
|
|
219
|
-
) {
|
|
220
|
-
request.formData = parseMultipartFormDataString(
|
|
221
|
-
request.body || ''
|
|
222
|
-
);
|
|
223
|
-
headers['content-type'] =
|
|
224
|
-
'application/x-www-form-urlencoded';
|
|
225
|
-
delete request.body;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
let body;
|
|
230
|
-
if (request.formData !== undefined) {
|
|
231
|
-
preferredMethod = 'POST';
|
|
232
|
-
headers['content-type'] =
|
|
233
|
-
headers['content-type'] ||
|
|
234
|
-
'application/x-www-form-urlencoded';
|
|
235
|
-
body = new URLSearchParams(
|
|
236
|
-
request.formData as Record<string, string>
|
|
237
|
-
).toString();
|
|
238
|
-
} else {
|
|
239
|
-
body = request.body;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
return await this.php.run({
|
|
243
|
-
relativeUri: ensurePathPrefix(
|
|
244
|
-
toRelativeUrl(requestedUrl),
|
|
245
|
-
this.#PATHNAME
|
|
246
|
-
),
|
|
247
|
-
protocol: this.#PROTOCOL,
|
|
248
|
-
method: request.method || preferredMethod,
|
|
249
|
-
body,
|
|
250
|
-
fileInfos,
|
|
251
|
-
scriptPath: this.#resolvePHPFilePath(requestedUrl.pathname),
|
|
252
|
-
headers,
|
|
253
|
-
});
|
|
254
|
-
} finally {
|
|
255
|
-
release();
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Resolve the requested path to the filesystem path of the requested PHP file.
|
|
261
|
-
*
|
|
262
|
-
* Fall back to index.php as if there was a url rewriting rule in place.
|
|
263
|
-
*
|
|
264
|
-
* @param requestedPath - The requested pathname.
|
|
265
|
-
* @returns The resolved filesystem path.
|
|
266
|
-
*/
|
|
267
|
-
#resolvePHPFilePath(requestedPath: string): string {
|
|
268
|
-
let filePath = removePathPrefix(requestedPath, this.#PATHNAME);
|
|
269
|
-
|
|
270
|
-
// If the path mentions a .php extension, that's our file's path.
|
|
271
|
-
if (filePath.includes('.php')) {
|
|
272
|
-
filePath = filePath.split('.php')[0] + '.php';
|
|
273
|
-
} else {
|
|
274
|
-
// Otherwise, let's assume the file is $request_path/index.php
|
|
275
|
-
if (!filePath.endsWith('/')) {
|
|
276
|
-
filePath += '/';
|
|
277
|
-
}
|
|
278
|
-
if (!filePath.endsWith('index.php')) {
|
|
279
|
-
filePath += 'index.php';
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
const resolvedFsPath = `${this.#DOCROOT}${filePath}`;
|
|
284
|
-
if (this.php.fileExists(resolvedFsPath)) {
|
|
285
|
-
return resolvedFsPath;
|
|
286
|
-
}
|
|
287
|
-
return `${this.#DOCROOT}/index.php`;
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
/**
|
|
292
|
-
* Parses a multipart/form-data string into a key-value object.
|
|
293
|
-
*
|
|
294
|
-
* @param multipartString
|
|
295
|
-
* @returns
|
|
296
|
-
*/
|
|
297
|
-
function parseMultipartFormDataString(multipartString: string) {
|
|
298
|
-
const parsedData: Record<string, string> = {};
|
|
299
|
-
|
|
300
|
-
// Extract the boundary from the string
|
|
301
|
-
const boundaryMatch = multipartString.match(/--(.*)\r\n/);
|
|
302
|
-
if (!boundaryMatch) {
|
|
303
|
-
return parsedData;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
const boundary = boundaryMatch[1];
|
|
307
|
-
|
|
308
|
-
// Split the string into parts
|
|
309
|
-
const parts = multipartString.split(`--${boundary}`);
|
|
310
|
-
|
|
311
|
-
// Remove the first and the last part, which are just boundary markers
|
|
312
|
-
parts.shift();
|
|
313
|
-
parts.pop();
|
|
314
|
-
|
|
315
|
-
// Process each part
|
|
316
|
-
parts.forEach((part: string) => {
|
|
317
|
-
const headerBodySplit = part.indexOf('\r\n\r\n');
|
|
318
|
-
const headers = part.substring(0, headerBodySplit).trim();
|
|
319
|
-
const body = part.substring(headerBodySplit + 4).trim();
|
|
320
|
-
|
|
321
|
-
const nameMatch = headers.match(/name="([^"]+)"/);
|
|
322
|
-
if (nameMatch) {
|
|
323
|
-
const name = nameMatch[1];
|
|
324
|
-
parsedData[name] = body;
|
|
325
|
-
}
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
return parsedData;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
/**
|
|
332
|
-
* Naively infer a file mime type from its path.
|
|
333
|
-
*
|
|
334
|
-
* @todo Infer the mime type based on the file contents.
|
|
335
|
-
* A naive function like this one can be inaccurate
|
|
336
|
-
* and potentially have negative security consequences.
|
|
337
|
-
*
|
|
338
|
-
* @param path - The file path
|
|
339
|
-
* @returns The inferred mime type.
|
|
340
|
-
*/
|
|
341
|
-
function inferMimeType(path: string): string {
|
|
342
|
-
const extension = path.split('.').pop();
|
|
343
|
-
switch (extension) {
|
|
344
|
-
case 'css':
|
|
345
|
-
return 'text/css';
|
|
346
|
-
case 'js':
|
|
347
|
-
return 'application/javascript';
|
|
348
|
-
case 'png':
|
|
349
|
-
return 'image/png';
|
|
350
|
-
case 'jpg':
|
|
351
|
-
case 'jpeg':
|
|
352
|
-
return 'image/jpeg';
|
|
353
|
-
case 'gif':
|
|
354
|
-
return 'image/gif';
|
|
355
|
-
case 'svg':
|
|
356
|
-
return 'image/svg+xml';
|
|
357
|
-
case 'woff':
|
|
358
|
-
return 'font/woff';
|
|
359
|
-
case 'woff2':
|
|
360
|
-
return 'font/woff2';
|
|
361
|
-
case 'ttf':
|
|
362
|
-
return 'font/ttf';
|
|
363
|
-
case 'otf':
|
|
364
|
-
return 'font/otf';
|
|
365
|
-
case 'eot':
|
|
366
|
-
return 'font/eot';
|
|
367
|
-
case 'ico':
|
|
368
|
-
return 'image/x-icon';
|
|
369
|
-
case 'html':
|
|
370
|
-
return 'text/html';
|
|
371
|
-
case 'json':
|
|
372
|
-
return 'application/json';
|
|
373
|
-
case 'xml':
|
|
374
|
-
return 'application/xml';
|
|
375
|
-
case 'txt':
|
|
376
|
-
case 'md':
|
|
377
|
-
return 'text/plain';
|
|
378
|
-
default:
|
|
379
|
-
return 'application-octet-stream';
|
|
380
|
-
}
|
|
381
|
-
}
|
package/src/lib/php-response.ts
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* This type is used in Comlink.transferHandlers.set('PHPResponse', { ... })
|
|
3
|
-
* so be sure to update that if you change this type.
|
|
4
|
-
*/
|
|
5
|
-
export interface PHPResponseData {
|
|
6
|
-
/**
|
|
7
|
-
* Response headers.
|
|
8
|
-
*/
|
|
9
|
-
readonly headers: Record<string, string[]>;
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Response body. Contains the output from `echo`,
|
|
13
|
-
* `print`, inline HTML etc.
|
|
14
|
-
*/
|
|
15
|
-
readonly bytes: ArrayBuffer;
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Stderr contents, if any.
|
|
19
|
-
*/
|
|
20
|
-
readonly errors: string;
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* The exit code of the script. `0` is a success, while
|
|
24
|
-
* `1` and `2` indicate an error.
|
|
25
|
-
*/
|
|
26
|
-
readonly exitCode: number;
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Response HTTP status code, e.g. 200.
|
|
30
|
-
*/
|
|
31
|
-
readonly httpStatusCode: number;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* PHP response. Body is an `ArrayBuffer` because it can
|
|
36
|
-
* contain binary data.
|
|
37
|
-
*
|
|
38
|
-
* This type is used in Comlink.transferHandlers.set('PHPResponse', \{ ... \})
|
|
39
|
-
* so be sure to update that if you change this type.
|
|
40
|
-
*/
|
|
41
|
-
export class PHPResponse implements PHPResponseData {
|
|
42
|
-
/** @inheritDoc */
|
|
43
|
-
readonly headers: Record<string, string[]>;
|
|
44
|
-
|
|
45
|
-
/** @inheritDoc */
|
|
46
|
-
readonly bytes: ArrayBuffer;
|
|
47
|
-
|
|
48
|
-
/** @inheritDoc */
|
|
49
|
-
readonly errors: string;
|
|
50
|
-
|
|
51
|
-
/** @inheritDoc */
|
|
52
|
-
readonly exitCode: number;
|
|
53
|
-
|
|
54
|
-
/** @inheritDoc */
|
|
55
|
-
readonly httpStatusCode: number;
|
|
56
|
-
|
|
57
|
-
constructor(
|
|
58
|
-
httpStatusCode: number,
|
|
59
|
-
headers: Record<string, string[]>,
|
|
60
|
-
body: ArrayBuffer,
|
|
61
|
-
errors = '',
|
|
62
|
-
exitCode = 0
|
|
63
|
-
) {
|
|
64
|
-
this.httpStatusCode = httpStatusCode;
|
|
65
|
-
this.headers = headers;
|
|
66
|
-
this.bytes = body;
|
|
67
|
-
this.exitCode = exitCode;
|
|
68
|
-
this.errors = errors;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
static fromRawData(data: PHPResponseData): PHPResponse {
|
|
72
|
-
return new PHPResponse(
|
|
73
|
-
data.httpStatusCode,
|
|
74
|
-
data.headers,
|
|
75
|
-
data.bytes,
|
|
76
|
-
data.errors,
|
|
77
|
-
data.exitCode
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
toRawData(): PHPResponseData {
|
|
82
|
-
return {
|
|
83
|
-
headers: this.headers,
|
|
84
|
-
bytes: this.bytes,
|
|
85
|
-
errors: this.errors,
|
|
86
|
-
exitCode: this.exitCode,
|
|
87
|
-
httpStatusCode: this.httpStatusCode,
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Response body as JSON.
|
|
93
|
-
*/
|
|
94
|
-
get json() {
|
|
95
|
-
return JSON.parse(this.text);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Response body as text.
|
|
100
|
-
*/
|
|
101
|
-
get text() {
|
|
102
|
-
return new TextDecoder().decode(this.bytes);
|
|
103
|
-
}
|
|
104
|
-
}
|