@sveltejs/adapter-netlify 1.0.0-next.4 → 1.0.0-next.43
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/README.md +62 -7
- package/files/cjs/handler.js +99 -0
- package/files/cjs/multipart-parser-52bc5518.js +451 -0
- package/files/cjs/shims-24e5b259.js +6530 -0
- package/files/cjs/shims.js +11 -0
- package/files/esm/handler.js +95 -0
- package/files/esm/multipart-parser-a360c9ae.js +449 -0
- package/files/esm/shims-c8fba98f.js +6520 -0
- package/files/esm/shims.js +8 -0
- package/index.d.ts +4 -0
- package/index.js +204 -48
- package/package.json +44 -19
- package/CHANGELOG.md +0 -62
- package/files/index.cjs +0 -6
- package/files/render.js +0 -44
package/README.md
CHANGED
|
@@ -1,18 +1,73 @@
|
|
|
1
1
|
# adapter-netlify
|
|
2
2
|
|
|
3
|
-
Adapter for Svelte apps that creates a Netlify app, using
|
|
3
|
+
Adapter for Svelte apps that creates a Netlify app, using serverless functions for dynamically generating pages.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Installation
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
> ⚠️ For the time being, the latest version of adapter-netlify is at the @next tag. If you get the error `config.kit.adapter should be an object with an "adapt" method.`, this is a sign that you are using the wrong version (eg `1.0.0-next.0` instead of `1.0.0-next.9`).
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
```bash
|
|
10
|
+
npm i -D @sveltejs/adapter-netlify@next
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
You can then configure it inside of `svelte.config.js`:
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
import adapter from '@sveltejs/adapter-netlify';
|
|
17
|
+
|
|
18
|
+
export default {
|
|
19
|
+
kit: {
|
|
20
|
+
adapter: adapter({
|
|
21
|
+
// if true, will split your app into multiple functions
|
|
22
|
+
// instead of creating a single one for the entire app
|
|
23
|
+
split: false
|
|
24
|
+
}),
|
|
25
|
+
target: '#svelte'
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Then, make sure you have a [netlify.toml](https://docs.netlify.com/configure-builds/file-based-configuration) file in the project root. This will determine where to write static assets based on the `build.publish` settings, as per this sample configuration:
|
|
10
31
|
|
|
11
32
|
```toml
|
|
12
33
|
[build]
|
|
13
34
|
command = "npm run build"
|
|
14
|
-
publish = "build
|
|
15
|
-
functions = "functions/"
|
|
35
|
+
publish = "build"
|
|
16
36
|
```
|
|
17
37
|
|
|
18
|
-
|
|
38
|
+
If the `netlify.toml` file or the `build.publish` value is missing, a default value of `"build"` will be used. Note that if you have set the publish directory in the Netlify UI to something else then you will need to set it in `netlify.toml` too, or use the default value of `"build"`.
|
|
39
|
+
|
|
40
|
+
## Netlify alternatives to SvelteKit functionality
|
|
41
|
+
|
|
42
|
+
You may build your app using functionality provided directly by SvelteKit without relying on any Netlify functionality. Using the SvelteKit versions of these features will allow them to be used in dev mode, tested with integration tests, and to work with other adapters should you ever decide to switch away from Netlify. However, in some scenarios you may find it beneficial to use the Netlify versions of these features. One example would be if you're migrating an app that's already hosted on Netlify to SvelteKit.
|
|
43
|
+
|
|
44
|
+
### Using Netlify Redirect Rules
|
|
45
|
+
|
|
46
|
+
During compilation, redirect rules are automatically appended to your `_redirects` file. (If it doesn't exist yet, it will be created.) That means:
|
|
47
|
+
|
|
48
|
+
- `[[redirects]]` in `netlify.toml` will never match as `_redirects` has a [higher priority](https://docs.netlify.com/routing/redirects/#rule-processing-order). So always put your rules in the [`_redirects` file](https://docs.netlify.com/routing/redirects/#syntax-for-the-redirects-file).
|
|
49
|
+
- `_redirects` shouldn't have any custom "catch all" rules such as `/* /foobar/:splat`. Otherwise the automatically appended rule will never be applied as Netlify is only processing [the first matching rule](https://docs.netlify.com/routing/redirects/#rule-processing-order).
|
|
50
|
+
|
|
51
|
+
### Using Netlify Forms
|
|
52
|
+
|
|
53
|
+
1. Create your Netlify HTML form as described [here](https://docs.netlify.com/forms/setup/#html-forms), e.g. as `/routes/contact.svelte`. (Don't forget to add the hidden `form-name` input element!)
|
|
54
|
+
2. Netlify's build bot parses your HTML files at deploy time, which means your form must be [prerendered](https://kit.svelte.dev/docs#page-options-prerender) as HTML. You can either add `export const prerender = true` to your `contact.svelte` to prerender just that page or set the `kit.prerender.force: true` option to prerender all pages.
|
|
55
|
+
3. If your Netlify form has a [custom success message](https://docs.netlify.com/forms/setup/#success-messages) like `<form netlify ... action="/success">` then ensure the corresponding `/routes/success.svelte` exists and is prerendered.
|
|
56
|
+
|
|
57
|
+
### Using Netlify Functions
|
|
58
|
+
|
|
59
|
+
[Netlify Functions](https://docs.netlify.com/functions/overview/) can be used alongside your SvelteKit routes. If you would like to add them to your site, you should create a directory for them and add the configuration to your `netlify.toml` file. For example:
|
|
60
|
+
|
|
61
|
+
```toml
|
|
62
|
+
[build]
|
|
63
|
+
command = "npm run build"
|
|
64
|
+
publish = "build"
|
|
65
|
+
|
|
66
|
+
[functions]
|
|
67
|
+
directory = "functions"
|
|
68
|
+
node_bundler = "esbuild"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Changelog
|
|
72
|
+
|
|
73
|
+
[The Changelog for this package is available on GitHub](https://github.com/sveltejs/kit/blob/master/packages/adapter-netlify/CHANGELOG.md).
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
require('./shims-24e5b259.js');
|
|
6
|
+
var APP = require('APP');
|
|
7
|
+
require('node:http');
|
|
8
|
+
require('node:https');
|
|
9
|
+
require('node:zlib');
|
|
10
|
+
require('node:stream');
|
|
11
|
+
require('node:util');
|
|
12
|
+
require('node:url');
|
|
13
|
+
require('net');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {import('@sveltejs/kit').SSRManifest} manifest
|
|
17
|
+
* @returns {import('@netlify/functions').Handler}
|
|
18
|
+
*/
|
|
19
|
+
function init(manifest) {
|
|
20
|
+
const app = new APP.App(manifest);
|
|
21
|
+
|
|
22
|
+
return async (event) => {
|
|
23
|
+
const rendered = await app.render(to_request(event));
|
|
24
|
+
|
|
25
|
+
const partial_response = {
|
|
26
|
+
statusCode: rendered.status,
|
|
27
|
+
...split_headers(rendered.headers)
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// TODO this is probably wrong now?
|
|
31
|
+
if (rendered.body instanceof Uint8Array) {
|
|
32
|
+
// Function responses should be strings (or undefined), and responses with binary
|
|
33
|
+
// content should be base64 encoded and set isBase64Encoded to true.
|
|
34
|
+
// https://github.com/netlify/functions/blob/main/src/function/response.ts
|
|
35
|
+
return {
|
|
36
|
+
...partial_response,
|
|
37
|
+
isBase64Encoded: true,
|
|
38
|
+
body: Buffer.from(rendered.body).toString('base64')
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
...partial_response,
|
|
44
|
+
body: await rendered.text()
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @param {import('@netlify/functions').HandlerEvent} event
|
|
51
|
+
* @returns {Request}
|
|
52
|
+
*/
|
|
53
|
+
function to_request(event) {
|
|
54
|
+
const { httpMethod, headers, rawUrl, body, isBase64Encoded } = event;
|
|
55
|
+
|
|
56
|
+
/** @type {RequestInit} */
|
|
57
|
+
const init = {
|
|
58
|
+
method: httpMethod,
|
|
59
|
+
headers: new Headers(headers)
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
if (httpMethod !== 'GET' && httpMethod !== 'HEAD') {
|
|
63
|
+
const encoding = isBase64Encoded ? 'base64' : 'utf-8';
|
|
64
|
+
init.body = typeof body === 'string' ? Buffer.from(body, encoding) : body;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return new Request(rawUrl, init);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Splits headers into two categories: single value and multi value
|
|
72
|
+
* @param {Headers} headers
|
|
73
|
+
* @returns {{
|
|
74
|
+
* headers: Record<string, string>,
|
|
75
|
+
* multiValueHeaders: Record<string, string[]>
|
|
76
|
+
* }}
|
|
77
|
+
*/
|
|
78
|
+
function split_headers(headers) {
|
|
79
|
+
/** @type {Record<string, string>} */
|
|
80
|
+
const h = {};
|
|
81
|
+
|
|
82
|
+
/** @type {Record<string, string[]>} */
|
|
83
|
+
const m = {};
|
|
84
|
+
|
|
85
|
+
headers.forEach((value, key) => {
|
|
86
|
+
if (key === 'set-cookie') {
|
|
87
|
+
m[key] = value.split(', ');
|
|
88
|
+
} else {
|
|
89
|
+
h[key] = value;
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
headers: h,
|
|
95
|
+
multiValueHeaders: m
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
exports.init = init;
|
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
require('node:fs');
|
|
4
|
+
require('node:path');
|
|
5
|
+
var node_worker_threads = require('node:worker_threads');
|
|
6
|
+
var shims = require('./shims-24e5b259.js');
|
|
7
|
+
require('node:http');
|
|
8
|
+
require('node:https');
|
|
9
|
+
require('node:zlib');
|
|
10
|
+
require('node:stream');
|
|
11
|
+
require('node:util');
|
|
12
|
+
require('node:url');
|
|
13
|
+
require('net');
|
|
14
|
+
|
|
15
|
+
globalThis.DOMException || (() => {
|
|
16
|
+
const port = new node_worker_threads.MessageChannel().port1;
|
|
17
|
+
const ab = new ArrayBuffer(0);
|
|
18
|
+
try { port.postMessage(ab, [ab, ab]); } catch (err) { return err.constructor }
|
|
19
|
+
})();
|
|
20
|
+
|
|
21
|
+
let s = 0;
|
|
22
|
+
const S = {
|
|
23
|
+
START_BOUNDARY: s++,
|
|
24
|
+
HEADER_FIELD_START: s++,
|
|
25
|
+
HEADER_FIELD: s++,
|
|
26
|
+
HEADER_VALUE_START: s++,
|
|
27
|
+
HEADER_VALUE: s++,
|
|
28
|
+
HEADER_VALUE_ALMOST_DONE: s++,
|
|
29
|
+
HEADERS_ALMOST_DONE: s++,
|
|
30
|
+
PART_DATA_START: s++,
|
|
31
|
+
PART_DATA: s++,
|
|
32
|
+
END: s++
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
let f = 1;
|
|
36
|
+
const F = {
|
|
37
|
+
PART_BOUNDARY: f,
|
|
38
|
+
LAST_BOUNDARY: f *= 2
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const LF = 10;
|
|
42
|
+
const CR = 13;
|
|
43
|
+
const SPACE = 32;
|
|
44
|
+
const HYPHEN = 45;
|
|
45
|
+
const COLON = 58;
|
|
46
|
+
const A = 97;
|
|
47
|
+
const Z = 122;
|
|
48
|
+
|
|
49
|
+
const lower = c => c | 0x20;
|
|
50
|
+
|
|
51
|
+
const noop = () => {};
|
|
52
|
+
|
|
53
|
+
class MultipartParser {
|
|
54
|
+
/**
|
|
55
|
+
* @param {string} boundary
|
|
56
|
+
*/
|
|
57
|
+
constructor(boundary) {
|
|
58
|
+
this.index = 0;
|
|
59
|
+
this.flags = 0;
|
|
60
|
+
|
|
61
|
+
this.onHeaderEnd = noop;
|
|
62
|
+
this.onHeaderField = noop;
|
|
63
|
+
this.onHeadersEnd = noop;
|
|
64
|
+
this.onHeaderValue = noop;
|
|
65
|
+
this.onPartBegin = noop;
|
|
66
|
+
this.onPartData = noop;
|
|
67
|
+
this.onPartEnd = noop;
|
|
68
|
+
|
|
69
|
+
this.boundaryChars = {};
|
|
70
|
+
|
|
71
|
+
boundary = '\r\n--' + boundary;
|
|
72
|
+
const ui8a = new Uint8Array(boundary.length);
|
|
73
|
+
for (let i = 0; i < boundary.length; i++) {
|
|
74
|
+
ui8a[i] = boundary.charCodeAt(i);
|
|
75
|
+
this.boundaryChars[ui8a[i]] = true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
this.boundary = ui8a;
|
|
79
|
+
this.lookbehind = new Uint8Array(this.boundary.length + 8);
|
|
80
|
+
this.state = S.START_BOUNDARY;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @param {Uint8Array} data
|
|
85
|
+
*/
|
|
86
|
+
write(data) {
|
|
87
|
+
let i = 0;
|
|
88
|
+
const length_ = data.length;
|
|
89
|
+
let previousIndex = this.index;
|
|
90
|
+
let {lookbehind, boundary, boundaryChars, index, state, flags} = this;
|
|
91
|
+
const boundaryLength = this.boundary.length;
|
|
92
|
+
const boundaryEnd = boundaryLength - 1;
|
|
93
|
+
const bufferLength = data.length;
|
|
94
|
+
let c;
|
|
95
|
+
let cl;
|
|
96
|
+
|
|
97
|
+
const mark = name => {
|
|
98
|
+
this[name + 'Mark'] = i;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const clear = name => {
|
|
102
|
+
delete this[name + 'Mark'];
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const callback = (callbackSymbol, start, end, ui8a) => {
|
|
106
|
+
if (start === undefined || start !== end) {
|
|
107
|
+
this[callbackSymbol](ui8a && ui8a.subarray(start, end));
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const dataCallback = (name, clear) => {
|
|
112
|
+
const markSymbol = name + 'Mark';
|
|
113
|
+
if (!(markSymbol in this)) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (clear) {
|
|
118
|
+
callback(name, this[markSymbol], i, data);
|
|
119
|
+
delete this[markSymbol];
|
|
120
|
+
} else {
|
|
121
|
+
callback(name, this[markSymbol], data.length, data);
|
|
122
|
+
this[markSymbol] = 0;
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
for (i = 0; i < length_; i++) {
|
|
127
|
+
c = data[i];
|
|
128
|
+
|
|
129
|
+
switch (state) {
|
|
130
|
+
case S.START_BOUNDARY:
|
|
131
|
+
if (index === boundary.length - 2) {
|
|
132
|
+
if (c === HYPHEN) {
|
|
133
|
+
flags |= F.LAST_BOUNDARY;
|
|
134
|
+
} else if (c !== CR) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
index++;
|
|
139
|
+
break;
|
|
140
|
+
} else if (index - 1 === boundary.length - 2) {
|
|
141
|
+
if (flags & F.LAST_BOUNDARY && c === HYPHEN) {
|
|
142
|
+
state = S.END;
|
|
143
|
+
flags = 0;
|
|
144
|
+
} else if (!(flags & F.LAST_BOUNDARY) && c === LF) {
|
|
145
|
+
index = 0;
|
|
146
|
+
callback('onPartBegin');
|
|
147
|
+
state = S.HEADER_FIELD_START;
|
|
148
|
+
} else {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (c !== boundary[index + 2]) {
|
|
156
|
+
index = -2;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (c === boundary[index + 2]) {
|
|
160
|
+
index++;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
break;
|
|
164
|
+
case S.HEADER_FIELD_START:
|
|
165
|
+
state = S.HEADER_FIELD;
|
|
166
|
+
mark('onHeaderField');
|
|
167
|
+
index = 0;
|
|
168
|
+
// falls through
|
|
169
|
+
case S.HEADER_FIELD:
|
|
170
|
+
if (c === CR) {
|
|
171
|
+
clear('onHeaderField');
|
|
172
|
+
state = S.HEADERS_ALMOST_DONE;
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
index++;
|
|
177
|
+
if (c === HYPHEN) {
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (c === COLON) {
|
|
182
|
+
if (index === 1) {
|
|
183
|
+
// empty header field
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
dataCallback('onHeaderField', true);
|
|
188
|
+
state = S.HEADER_VALUE_START;
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
cl = lower(c);
|
|
193
|
+
if (cl < A || cl > Z) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
break;
|
|
198
|
+
case S.HEADER_VALUE_START:
|
|
199
|
+
if (c === SPACE) {
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
mark('onHeaderValue');
|
|
204
|
+
state = S.HEADER_VALUE;
|
|
205
|
+
// falls through
|
|
206
|
+
case S.HEADER_VALUE:
|
|
207
|
+
if (c === CR) {
|
|
208
|
+
dataCallback('onHeaderValue', true);
|
|
209
|
+
callback('onHeaderEnd');
|
|
210
|
+
state = S.HEADER_VALUE_ALMOST_DONE;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
break;
|
|
214
|
+
case S.HEADER_VALUE_ALMOST_DONE:
|
|
215
|
+
if (c !== LF) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
state = S.HEADER_FIELD_START;
|
|
220
|
+
break;
|
|
221
|
+
case S.HEADERS_ALMOST_DONE:
|
|
222
|
+
if (c !== LF) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
callback('onHeadersEnd');
|
|
227
|
+
state = S.PART_DATA_START;
|
|
228
|
+
break;
|
|
229
|
+
case S.PART_DATA_START:
|
|
230
|
+
state = S.PART_DATA;
|
|
231
|
+
mark('onPartData');
|
|
232
|
+
// falls through
|
|
233
|
+
case S.PART_DATA:
|
|
234
|
+
previousIndex = index;
|
|
235
|
+
|
|
236
|
+
if (index === 0) {
|
|
237
|
+
// boyer-moore derrived algorithm to safely skip non-boundary data
|
|
238
|
+
i += boundaryEnd;
|
|
239
|
+
while (i < bufferLength && !(data[i] in boundaryChars)) {
|
|
240
|
+
i += boundaryLength;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
i -= boundaryEnd;
|
|
244
|
+
c = data[i];
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (index < boundary.length) {
|
|
248
|
+
if (boundary[index] === c) {
|
|
249
|
+
if (index === 0) {
|
|
250
|
+
dataCallback('onPartData', true);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
index++;
|
|
254
|
+
} else {
|
|
255
|
+
index = 0;
|
|
256
|
+
}
|
|
257
|
+
} else if (index === boundary.length) {
|
|
258
|
+
index++;
|
|
259
|
+
if (c === CR) {
|
|
260
|
+
// CR = part boundary
|
|
261
|
+
flags |= F.PART_BOUNDARY;
|
|
262
|
+
} else if (c === HYPHEN) {
|
|
263
|
+
// HYPHEN = end boundary
|
|
264
|
+
flags |= F.LAST_BOUNDARY;
|
|
265
|
+
} else {
|
|
266
|
+
index = 0;
|
|
267
|
+
}
|
|
268
|
+
} else if (index - 1 === boundary.length) {
|
|
269
|
+
if (flags & F.PART_BOUNDARY) {
|
|
270
|
+
index = 0;
|
|
271
|
+
if (c === LF) {
|
|
272
|
+
// unset the PART_BOUNDARY flag
|
|
273
|
+
flags &= ~F.PART_BOUNDARY;
|
|
274
|
+
callback('onPartEnd');
|
|
275
|
+
callback('onPartBegin');
|
|
276
|
+
state = S.HEADER_FIELD_START;
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
} else if (flags & F.LAST_BOUNDARY) {
|
|
280
|
+
if (c === HYPHEN) {
|
|
281
|
+
callback('onPartEnd');
|
|
282
|
+
state = S.END;
|
|
283
|
+
flags = 0;
|
|
284
|
+
} else {
|
|
285
|
+
index = 0;
|
|
286
|
+
}
|
|
287
|
+
} else {
|
|
288
|
+
index = 0;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (index > 0) {
|
|
293
|
+
// when matching a possible boundary, keep a lookbehind reference
|
|
294
|
+
// in case it turns out to be a false lead
|
|
295
|
+
lookbehind[index - 1] = c;
|
|
296
|
+
} else if (previousIndex > 0) {
|
|
297
|
+
// if our boundary turned out to be rubbish, the captured lookbehind
|
|
298
|
+
// belongs to partData
|
|
299
|
+
const _lookbehind = new Uint8Array(lookbehind.buffer, lookbehind.byteOffset, lookbehind.byteLength);
|
|
300
|
+
callback('onPartData', 0, previousIndex, _lookbehind);
|
|
301
|
+
previousIndex = 0;
|
|
302
|
+
mark('onPartData');
|
|
303
|
+
|
|
304
|
+
// reconsider the current character even so it interrupted the sequence
|
|
305
|
+
// it could be the beginning of a new sequence
|
|
306
|
+
i--;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
break;
|
|
310
|
+
case S.END:
|
|
311
|
+
break;
|
|
312
|
+
default:
|
|
313
|
+
throw new Error(`Unexpected state entered: ${state}`);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
dataCallback('onHeaderField');
|
|
318
|
+
dataCallback('onHeaderValue');
|
|
319
|
+
dataCallback('onPartData');
|
|
320
|
+
|
|
321
|
+
// Update properties for the next call
|
|
322
|
+
this.index = index;
|
|
323
|
+
this.state = state;
|
|
324
|
+
this.flags = flags;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
end() {
|
|
328
|
+
if ((this.state === S.HEADER_FIELD_START && this.index === 0) ||
|
|
329
|
+
(this.state === S.PART_DATA && this.index === this.boundary.length)) {
|
|
330
|
+
this.onPartEnd();
|
|
331
|
+
} else if (this.state !== S.END) {
|
|
332
|
+
throw new Error('MultipartParser.end(): stream ended unexpectedly');
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function _fileName(headerValue) {
|
|
338
|
+
// matches either a quoted-string or a token (RFC 2616 section 19.5.1)
|
|
339
|
+
const m = headerValue.match(/\bfilename=("(.*?)"|([^()<>@,;:\\"/[\]?={}\s\t]+))($|;\s)/i);
|
|
340
|
+
if (!m) {
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const match = m[2] || m[3] || '';
|
|
345
|
+
let filename = match.slice(match.lastIndexOf('\\') + 1);
|
|
346
|
+
filename = filename.replace(/%22/g, '"');
|
|
347
|
+
filename = filename.replace(/&#(\d{4});/g, (m, code) => {
|
|
348
|
+
return String.fromCharCode(code);
|
|
349
|
+
});
|
|
350
|
+
return filename;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
async function toFormData(Body, ct) {
|
|
354
|
+
if (!/multipart/i.test(ct)) {
|
|
355
|
+
throw new TypeError('Failed to fetch');
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const m = ct.match(/boundary=(?:"([^"]+)"|([^;]+))/i);
|
|
359
|
+
|
|
360
|
+
if (!m) {
|
|
361
|
+
throw new TypeError('no or bad content-type header, no multipart boundary');
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const parser = new MultipartParser(m[1] || m[2]);
|
|
365
|
+
|
|
366
|
+
let headerField;
|
|
367
|
+
let headerValue;
|
|
368
|
+
let entryValue;
|
|
369
|
+
let entryName;
|
|
370
|
+
let contentType;
|
|
371
|
+
let filename;
|
|
372
|
+
const entryChunks = [];
|
|
373
|
+
const formData = new shims.FormData();
|
|
374
|
+
|
|
375
|
+
const onPartData = ui8a => {
|
|
376
|
+
entryValue += decoder.decode(ui8a, {stream: true});
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
const appendToFile = ui8a => {
|
|
380
|
+
entryChunks.push(ui8a);
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
const appendFileToFormData = () => {
|
|
384
|
+
const file = new shims.File(entryChunks, filename, {type: contentType});
|
|
385
|
+
formData.append(entryName, file);
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
const appendEntryToFormData = () => {
|
|
389
|
+
formData.append(entryName, entryValue);
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
const decoder = new TextDecoder('utf-8');
|
|
393
|
+
decoder.decode();
|
|
394
|
+
|
|
395
|
+
parser.onPartBegin = function () {
|
|
396
|
+
parser.onPartData = onPartData;
|
|
397
|
+
parser.onPartEnd = appendEntryToFormData;
|
|
398
|
+
|
|
399
|
+
headerField = '';
|
|
400
|
+
headerValue = '';
|
|
401
|
+
entryValue = '';
|
|
402
|
+
entryName = '';
|
|
403
|
+
contentType = '';
|
|
404
|
+
filename = null;
|
|
405
|
+
entryChunks.length = 0;
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
parser.onHeaderField = function (ui8a) {
|
|
409
|
+
headerField += decoder.decode(ui8a, {stream: true});
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
parser.onHeaderValue = function (ui8a) {
|
|
413
|
+
headerValue += decoder.decode(ui8a, {stream: true});
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
parser.onHeaderEnd = function () {
|
|
417
|
+
headerValue += decoder.decode();
|
|
418
|
+
headerField = headerField.toLowerCase();
|
|
419
|
+
|
|
420
|
+
if (headerField === 'content-disposition') {
|
|
421
|
+
// matches either a quoted-string or a token (RFC 2616 section 19.5.1)
|
|
422
|
+
const m = headerValue.match(/\bname=("([^"]*)"|([^()<>@,;:\\"/[\]?={}\s\t]+))/i);
|
|
423
|
+
|
|
424
|
+
if (m) {
|
|
425
|
+
entryName = m[2] || m[3] || '';
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
filename = _fileName(headerValue);
|
|
429
|
+
|
|
430
|
+
if (filename) {
|
|
431
|
+
parser.onPartData = appendToFile;
|
|
432
|
+
parser.onPartEnd = appendFileToFormData;
|
|
433
|
+
}
|
|
434
|
+
} else if (headerField === 'content-type') {
|
|
435
|
+
contentType = headerValue;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
headerValue = '';
|
|
439
|
+
headerField = '';
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
for await (const chunk of Body) {
|
|
443
|
+
parser.write(chunk);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
parser.end();
|
|
447
|
+
|
|
448
|
+
return formData;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
exports.toFormData = toFormData;
|