@remix-run/multipart-parser 0.11.0
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/LICENSE +21 -0
- package/README.md +235 -0
- package/dist/lib/buffer-search.d.ts +9 -0
- package/dist/lib/buffer-search.d.ts.map +1 -0
- package/dist/lib/multipart-request.d.ts +25 -0
- package/dist/lib/multipart-request.d.ts.map +1 -0
- package/dist/lib/multipart.d.ts +145 -0
- package/dist/lib/multipart.d.ts.map +1 -0
- package/dist/lib/multipart.node.d.ts +41 -0
- package/dist/lib/multipart.node.d.ts.map +1 -0
- package/dist/lib/read-stream.d.ts +2 -0
- package/dist/lib/read-stream.d.ts.map +1 -0
- package/dist/multipart-parser.cjs +2021 -0
- package/dist/multipart-parser.cjs.map +7 -0
- package/dist/multipart-parser.d.ts +4 -0
- package/dist/multipart-parser.d.ts.map +1 -0
- package/dist/multipart-parser.js +1985 -0
- package/dist/multipart-parser.js.map +7 -0
- package/dist/multipart-parser.node.cjs +2027 -0
- package/dist/multipart-parser.node.cjs.map +7 -0
- package/dist/multipart-parser.node.d.ts +5 -0
- package/dist/multipart-parser.node.d.ts.map +1 -0
- package/dist/multipart-parser.node.js +1991 -0
- package/dist/multipart-parser.node.js.map +7 -0
- package/package.json +66 -0
- package/src/lib/buffer-search.ts +67 -0
- package/src/lib/multipart-request.ts +55 -0
- package/src/lib/multipart.node.ts +82 -0
- package/src/lib/multipart.ts +420 -0
- package/src/lib/read-stream.ts +15 -0
- package/src/multipart-parser.node.ts +14 -0
- package/src/multipart-parser.ts +16 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Michael Jackson
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# multipart-parser
|
|
2
|
+
|
|
3
|
+
`multipart-parser` is a fast, streaming multipart parser that works in **any JavaScript environment**, from serverless functions to traditional servers. Whether you're handling file uploads, parsing email attachments, or working with multipart API responses, `multipart-parser` has you covered.
|
|
4
|
+
|
|
5
|
+
## 🚀 Why multipart-parser?
|
|
6
|
+
|
|
7
|
+
- **Universal JavaScript** - One library that works everywhere: Node.js, Bun, Deno, Cloudflare Workers, and browsers
|
|
8
|
+
- **Blazing Fast** - Consistently outperforms popular alternatives like busboy in benchmarks
|
|
9
|
+
- **Zero Dependencies** - Lightweight and secure with no external dependencies
|
|
10
|
+
- **Memory Efficient** - Streaming architecture that `yield`s files as they are found in the stream
|
|
11
|
+
- **Type Safe** - Written in TypeScript with comprehensive type definitions
|
|
12
|
+
- **Standards Based** - Built on the web standard [Streams API](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API) for maximum compatibility
|
|
13
|
+
- **Production Ready** - Battle-tested error handling with specific error types for common scenarios
|
|
14
|
+
|
|
15
|
+
## 📦 Features
|
|
16
|
+
|
|
17
|
+
- Parse file uploads (`multipart/form-data`) with automatic field and file detection
|
|
18
|
+
- Support for all `multipart/*` content types (mixed, alternative, related, etc.)
|
|
19
|
+
- Convenient `MultipartPart` API with `arrayBuffer`, `bytes`, `text`, `size`, and metadata access
|
|
20
|
+
- Built-in file size limiting to prevent abuse
|
|
21
|
+
- First-class Node.js support with native `http.IncomingMessage` compatibility
|
|
22
|
+
- [Examples for every major runtime](https://github.com/remix-run/remix/tree/v3/packages/multipart-parser/examples)
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
Install from [npm](https://www.npmjs.com/):
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
npm install @remix-run/multipart-parser
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Or install from [JSR](https://jsr.io/):
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
deno add @remix-run/multipart-parser
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
|
|
40
|
+
The most common use case for `multipart-parser` is handling file uploads when you're building a web server. For this case, the `parseMultipartRequest` function is your friend. It automatically validates the request is `multipart/form-data`, extracts the multipart boundary from the `Content-Type` header, parses all fields and files in the `request.body` stream, and gives each one to you as a `MultipartPart` object with a rich API for accessing its metadata and content.
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { MultipartParseError, parseMultipartRequest } from '@remix-run/multipart-parser';
|
|
44
|
+
|
|
45
|
+
async function handleRequest(request: Request): void {
|
|
46
|
+
try {
|
|
47
|
+
for await (let part of parseMultipartRequest(request)) {
|
|
48
|
+
if (part.isFile) {
|
|
49
|
+
// Access file data in multiple formats
|
|
50
|
+
let buffer = part.arrayBuffer; // ArrayBuffer
|
|
51
|
+
console.log(`File received: ${part.filename} (${buffer.byteLength} bytes)`);
|
|
52
|
+
console.log(`Content type: ${part.mediaType}`);
|
|
53
|
+
console.log(`Field name: ${part.name}`);
|
|
54
|
+
|
|
55
|
+
// Save to disk, upload to cloud storage, etc.
|
|
56
|
+
await saveFile(part.filename, part.bytes);
|
|
57
|
+
} else {
|
|
58
|
+
let text = part.text; // string
|
|
59
|
+
console.log(`Field received: ${part.name} = ${JSON.stringify(text)}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
} catch (error) {
|
|
63
|
+
if (error instanceof MultipartParseError) {
|
|
64
|
+
console.error('Failed to parse multipart request:', error.message);
|
|
65
|
+
} else {
|
|
66
|
+
console.error('An unexpected error occurred:', error);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Limiting File Upload Size
|
|
73
|
+
|
|
74
|
+
A common use case when handling file uploads is limiting the size of uploaded files to prevent malicious users from sending very large files that may overload your server's memory and/or storage capacity. You can set a file upload size limit using the `maxFileSize` option, and return a 413 "Payload Too Large" response when you receive a request that exceeds the limit.
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
import {
|
|
78
|
+
MultipartParseError,
|
|
79
|
+
MaxFileSizeExceededError,
|
|
80
|
+
parseMultipartRequest,
|
|
81
|
+
} from '@remix-run/multipart-parser/node';
|
|
82
|
+
|
|
83
|
+
const oneMb = Math.pow(2, 20);
|
|
84
|
+
const maxFileSize = 10 * oneMb;
|
|
85
|
+
|
|
86
|
+
async function handleRequest(request: Request): Promise<Response> {
|
|
87
|
+
try {
|
|
88
|
+
for await (let part of parseMultipartRequest(request, { maxFileSize })) {
|
|
89
|
+
// ...
|
|
90
|
+
}
|
|
91
|
+
} catch (error) {
|
|
92
|
+
if (error instanceof MaxFileSizeExceededError) {
|
|
93
|
+
return new Response('File size limit exceeded', { status: 413 });
|
|
94
|
+
} else if (error instanceof MultipartParseError) {
|
|
95
|
+
return new Response('Failed to parse multipart request', { status: 400 });
|
|
96
|
+
} else {
|
|
97
|
+
console.error(error);
|
|
98
|
+
return new Response('Internal Server Error', { status: 500 });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Node.js Bindings
|
|
105
|
+
|
|
106
|
+
The main module (`import from "@remix-run/multipart-parser"`) assumes you're working with [the fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) (`Request`, `ReadableStream`, etc). Support for these interfaces was added to Node.js by the [undici](https://github.com/nodejs/undici) project in [version 16.5.0](https://nodejs.org/en/blog/release/v16.5.0).
|
|
107
|
+
|
|
108
|
+
If however you're building a server for Node.js that relies on node-specific APIs like `http.IncomingMessage`, `stream.Readable`, and `buffer.Buffer` (ala Express or `http.createServer`), `multipart-parser` ships with an additional module that works directly with these APIs.
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
import * as http from 'node:http';
|
|
112
|
+
import { MultipartParseError, parseMultipartRequest } from '@remix-run/multipart-parser/node';
|
|
113
|
+
|
|
114
|
+
let server = http.createServer(async (req, res) => {
|
|
115
|
+
try {
|
|
116
|
+
for await (let part of parseMultipartRequest(req)) {
|
|
117
|
+
// ...
|
|
118
|
+
}
|
|
119
|
+
} catch (error) {
|
|
120
|
+
if (error instanceof MultipartParseError) {
|
|
121
|
+
console.error('Failed to parse multipart request:', error.message);
|
|
122
|
+
} else {
|
|
123
|
+
console.error('An unexpected error occurred:', error);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
server.listen(8080);
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Low-level API
|
|
132
|
+
|
|
133
|
+
If you're working directly with multipart boundaries and buffers/streams of multipart data that are not necessarily part of a request, `multipart-parser` provides a low-level `parseMultipart()` API that you can use directly:
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
import { parseMultipart } from '@remix-run/multipart-parser';
|
|
137
|
+
|
|
138
|
+
let message = new Uint8Array(/* ... */);
|
|
139
|
+
let boundary = '----WebKitFormBoundary56eac3x';
|
|
140
|
+
|
|
141
|
+
for (let part of parseMultipart(message, { boundary })) {
|
|
142
|
+
// ...
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
In addition, the `parseMultipartStream` function provides an `async` generator interface for multipart data in a `ReadableStream`:
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
import { parseMultipartStream } from '@remix-run/multipart-parser';
|
|
150
|
+
|
|
151
|
+
let message = new ReadableStream(/* ... */);
|
|
152
|
+
let boundary = '----WebKitFormBoundary56eac3x';
|
|
153
|
+
|
|
154
|
+
for await (let part of parseMultipartStream(message, { boundary })) {
|
|
155
|
+
// ...
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Examples
|
|
160
|
+
|
|
161
|
+
The [`examples` directory](https://github.com/remix-run/remix/tree/v3/packages/multipart-parser/examples) contains a few working examples of how you can use this library:
|
|
162
|
+
|
|
163
|
+
- [`examples/bun`](https://github.com/remix-run/remix/tree/v3/packages/multipart-parser/examples/bun) - using multipart-parser in Bun
|
|
164
|
+
- [`examples/cf-workers`](https://github.com/remix-run/remix/tree/v3/packages/multipart-parser/examples/cf-workers) - using multipart-parser in a Cloudflare Worker and storing file uploads in R2
|
|
165
|
+
- [`examples/deno`](https://github.com/remix-run/remix/tree/v3/packages/multipart-parser/examples/deno) - using multipart-parser in Deno
|
|
166
|
+
- [`examples/node`](https://github.com/remix-run/remix/tree/v3/packages/multipart-parser/examples/node) - using multipart-parser in Node.js
|
|
167
|
+
|
|
168
|
+
## Benchmark
|
|
169
|
+
|
|
170
|
+
`multipart-parser` is designed to be as efficient as possible, operating on streams of data and rarely buffering in common usage. This design yields exceptional performance when handling multipart payloads of any size. In benchmarks, `multipart-parser` is as fast or faster than `busboy`.
|
|
171
|
+
|
|
172
|
+
The results of running the benchmarks on my laptop:
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
> @remix-run/multipart-parser@0.10.1 bench:node /Users/michael/Projects/remix-the-web/packages/multipart-parser
|
|
176
|
+
> node --disable-warning=ExperimentalWarning ./bench/runner.ts
|
|
177
|
+
|
|
178
|
+
Platform: Darwin (24.5.0)
|
|
179
|
+
CPU: Apple M1 Pro
|
|
180
|
+
Date: 6/13/2025, 12:27:09 PM
|
|
181
|
+
Node.js v24.0.2
|
|
182
|
+
┌──────────────────┬──────────────────┬──────────────────┬──────────────────┬───────────────────┐
|
|
183
|
+
│ (index) │ 1 small file │ 1 large file │ 100 small files │ 5 large files │
|
|
184
|
+
├──────────────────┼──────────────────┼──────────────────┼──────────────────┼───────────────────┤
|
|
185
|
+
│ multipart-parser │ '0.01 ms ± 0.03' │ '1.08 ms ± 0.08' │ '0.04 ms ± 0.01' │ '10.50 ms ± 0.38' │
|
|
186
|
+
│ multipasta │ '0.02 ms ± 0.06' │ '1.07 ms ± 0.02' │ '0.15 ms ± 0.02' │ '10.46 ms ± 0.11' │
|
|
187
|
+
│ busboy │ '0.06 ms ± 0.17' │ '3.07 ms ± 0.24' │ '0.24 ms ± 0.05' │ '29.85 ms ± 0.18' │
|
|
188
|
+
│ @fastify/busboy │ '0.05 ms ± 0.13' │ '1.23 ms ± 0.09' │ '0.45 ms ± 0.22' │ '11.81 ms ± 0.11' │
|
|
189
|
+
└──────────────────┴──────────────────┴──────────────────┴──────────────────┴───────────────────┘
|
|
190
|
+
|
|
191
|
+
> @remix-run/multipart-parser@0.10.1 bench:bun /Users/michael/Projects/remix-the-web/packages/multipart-parser
|
|
192
|
+
> bun run ./bench/runner.ts
|
|
193
|
+
|
|
194
|
+
Platform: Darwin (24.5.0)
|
|
195
|
+
CPU: Apple M1 Pro
|
|
196
|
+
Date: 6/13/2025, 12:27:31 PM
|
|
197
|
+
Bun 1.2.13
|
|
198
|
+
┌──────────────────┬────────────────┬────────────────┬─────────────────┬─────────────────┐
|
|
199
|
+
│ │ 1 small file │ 1 large file │ 100 small files │ 5 large files │
|
|
200
|
+
├──────────────────┼────────────────┼────────────────┼─────────────────┼─────────────────┤
|
|
201
|
+
│ multipart-parser │ 0.01 ms ± 0.04 │ 0.86 ms ± 0.09 │ 0.04 ms ± 0.01 │ 8.32 ms ± 0.26 │
|
|
202
|
+
│ multipasta │ 0.02 ms ± 0.07 │ 0.87 ms ± 0.03 │ 0.25 ms ± 0.21 │ 8.27 ms ± 0.09 │
|
|
203
|
+
│ busboy │ 0.05 ms ± 0.17 │ 3.54 ms ± 0.10 │ 0.30 ms ± 0.03 │ 34.79 ms ± 0.38 │
|
|
204
|
+
│ @fastify/busboy │ 0.06 ms ± 0.18 │ 4.04 ms ± 0.08 │ 0.48 ms ± 0.06 │ 39.91 ms ± 0.37 │
|
|
205
|
+
└──────────────────┴────────────────┴────────────────┴─────────────────┴─────────────────┘
|
|
206
|
+
|
|
207
|
+
> @remix-run/multipart-parser@0.10.1 bench:deno /Users/michael/Projects/remix-the-web/packages/multipart-parser
|
|
208
|
+
> deno run --allow-sys ./bench/runner.ts
|
|
209
|
+
|
|
210
|
+
Platform: Darwin (24.5.0)
|
|
211
|
+
CPU: Apple M1 Pro
|
|
212
|
+
Date: 6/13/2025, 12:28:12 PM
|
|
213
|
+
Deno 2.3.6
|
|
214
|
+
┌──────────────────┬──────────────────┬────────────────────┬──────────────────┬─────────────────────┐
|
|
215
|
+
│ (idx) │ 1 small file │ 1 large file │ 100 small files │ 5 large files │
|
|
216
|
+
├──────────────────┼──────────────────┼────────────────────┼──────────────────┼─────────────────────┤
|
|
217
|
+
│ multipart-parser │ "0.01 ms ± 0.03" │ "1.03 ms ± 0.04" │ "0.05 ms ± 0.01" │ "10.05 ms ± 0.20" │
|
|
218
|
+
│ multipasta │ "0.02 ms ± 0.07" │ "1.04 ms ± 0.03" │ "0.16 ms ± 0.02" │ "10.10 ms ± 0.08" │
|
|
219
|
+
│ busboy │ "0.05 ms ± 0.19" │ "3.06 ms ± 0.15" │ "0.32 ms ± 0.05" │ "29.92 ms ± 0.24" │
|
|
220
|
+
│ @fastify/busboy │ "0.06 ms ± 0.14" │ "14.72 ms ± 11.42" │ "0.81 ms ± 0.20" │ "127.63 ms ± 35.77" │
|
|
221
|
+
└──────────────────┴──────────────────┴────────────────────┴──────────────────┴─────────────────────┘
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## Related Packages
|
|
225
|
+
|
|
226
|
+
- [`form-data-parser`](https://github.com/remix-run/remix/tree/v3/packages/form-data-parser) - Uses `multipart-parser` internally to parse multipart requests and generate `FileUpload`s for storage
|
|
227
|
+
- [`headers`](https://github.com/remix-run/remix/tree/v3/packages/headers) - Used internally to parse HTTP headers and get metadata (filename, content type) for each `MultipartPart`
|
|
228
|
+
|
|
229
|
+
## Credits
|
|
230
|
+
|
|
231
|
+
Thanks to Jacob Ebey who gave me several code reviews on this project prior to publishing.
|
|
232
|
+
|
|
233
|
+
## License
|
|
234
|
+
|
|
235
|
+
See [LICENSE](https://github.com/remix-run/remix/blob/v3/LICENSE)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface SearchFunction {
|
|
2
|
+
(haystack: Uint8Array, start?: number): number;
|
|
3
|
+
}
|
|
4
|
+
export declare function createSearch(pattern: string): SearchFunction;
|
|
5
|
+
export interface PartialTailSearchFunction {
|
|
6
|
+
(haystack: Uint8Array): number;
|
|
7
|
+
}
|
|
8
|
+
export declare function createPartialTailSearch(pattern: string): PartialTailSearchFunction;
|
|
9
|
+
//# sourceMappingURL=buffer-search.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buffer-search.d.ts","sourceRoot":"","sources":["../../src/lib/buffer-search.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,CAAC,QAAQ,EAAE,UAAU,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAChD;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CA+B5D;AAED,MAAM,WAAW,yBAAyB;IACxC,CAAC,QAAQ,EAAE,UAAU,GAAG,MAAM,CAAC;CAChC;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,yBAAyB,CAyBlF"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { MultipartParserOptions, MultipartPart } from './multipart.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Extracts the boundary string from a `multipart/*` content type.
|
|
4
|
+
*
|
|
5
|
+
* @param contentType The `Content-Type` header value from the request
|
|
6
|
+
* @return The boundary string if found, or null if not present
|
|
7
|
+
*/
|
|
8
|
+
export declare function getMultipartBoundary(contentType: string): string | null;
|
|
9
|
+
/**
|
|
10
|
+
* Returns true if the given request contains multipart data.
|
|
11
|
+
*
|
|
12
|
+
* @param request The `Request` object to check
|
|
13
|
+
* @return `true` if the request is a multipart request, `false` otherwise
|
|
14
|
+
*/
|
|
15
|
+
export declare function isMultipartRequest(request: Request): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Parse a multipart [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) and yield each part as
|
|
18
|
+
* a `MultipartPart` object. Useful in HTTP server contexts for handling incoming `multipart/*` requests.
|
|
19
|
+
*
|
|
20
|
+
* @param request The `Request` object containing multipart data
|
|
21
|
+
* @param options Optional parser options, such as `maxHeaderSize` and `maxFileSize`
|
|
22
|
+
* @return An async generator yielding `MultipartPart` objects
|
|
23
|
+
*/
|
|
24
|
+
export declare function parseMultipartRequest(request: Request, options?: MultipartParserOptions): AsyncGenerator<MultipartPart, void, unknown>;
|
|
25
|
+
//# sourceMappingURL=multipart-request.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multipart-request.d.ts","sourceRoot":"","sources":["../../src/lib/multipart-request.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAG5E;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGvE;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAG5D;AAED;;;;;;;GAOG;AACH,wBAAuB,qBAAqB,CAC1C,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE,sBAAsB,GAC/B,cAAc,CAAC,aAAa,EAAE,IAAI,EAAE,OAAO,CAAC,CAkB9C"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import Headers from '@remix-run/headers';
|
|
2
|
+
/**
|
|
3
|
+
* The base class for errors thrown by the multipart parser.
|
|
4
|
+
*/
|
|
5
|
+
export declare class MultipartParseError extends Error {
|
|
6
|
+
constructor(message: string);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* An error thrown when the maximum allowed size of a header is exceeded.
|
|
10
|
+
*/
|
|
11
|
+
export declare class MaxHeaderSizeExceededError extends MultipartParseError {
|
|
12
|
+
constructor(maxHeaderSize: number);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* An error thrown when the maximum allowed size of a file is exceeded.
|
|
16
|
+
*/
|
|
17
|
+
export declare class MaxFileSizeExceededError extends MultipartParseError {
|
|
18
|
+
constructor(maxFileSize: number);
|
|
19
|
+
}
|
|
20
|
+
export interface ParseMultipartOptions {
|
|
21
|
+
/**
|
|
22
|
+
* The boundary string used to separate parts in the multipart message,
|
|
23
|
+
* e.g. the `boundary` parameter in the `Content-Type` header.
|
|
24
|
+
*/
|
|
25
|
+
boundary: string;
|
|
26
|
+
/**
|
|
27
|
+
* The maximum allowed size of a header in bytes. If an individual part's header
|
|
28
|
+
* exceeds this size, a `MaxHeaderSizeExceededError` will be thrown.
|
|
29
|
+
*
|
|
30
|
+
* Default: 8 KiB
|
|
31
|
+
*/
|
|
32
|
+
maxHeaderSize?: number;
|
|
33
|
+
/**
|
|
34
|
+
* The maximum allowed size of a file in bytes. If an individual part's content
|
|
35
|
+
* exceeds this size, a `MaxFileSizeExceededError` will be thrown.
|
|
36
|
+
*
|
|
37
|
+
* Default: 2 MiB
|
|
38
|
+
*/
|
|
39
|
+
maxFileSize?: number;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Parse a `multipart/*` message from a buffer/iterable and yield each part as a `MultipartPart` object.
|
|
43
|
+
*
|
|
44
|
+
* Note: This is a low-level API that requires manual handling of the content and boundary. If you're
|
|
45
|
+
* building a web server, consider using `parseMultipartRequest(request)` instead.
|
|
46
|
+
*
|
|
47
|
+
* @param message The multipart message as a `Uint8Array` or an iterable of `Uint8Array` chunks
|
|
48
|
+
* @param options Options for the parser
|
|
49
|
+
* @return A generator that yields `MultipartPart` objects
|
|
50
|
+
*/
|
|
51
|
+
export declare function parseMultipart(message: Uint8Array | Iterable<Uint8Array>, options: ParseMultipartOptions): Generator<MultipartPart, void, unknown>;
|
|
52
|
+
/**
|
|
53
|
+
* Parse a `multipart/*` message stream and yield each part as a `MultipartPart` object.
|
|
54
|
+
*
|
|
55
|
+
* Note: This is a low-level API that requires manual handling of the content and boundary. If you're
|
|
56
|
+
* building a web server, consider using `parseMultipartRequest(request)` instead.
|
|
57
|
+
*
|
|
58
|
+
* @param stream A stream containing multipart data as a `ReadableStream<Uint8Array>`
|
|
59
|
+
* @param options Options for the parser
|
|
60
|
+
* @return An async generator that yields `MultipartPart` objects
|
|
61
|
+
*/
|
|
62
|
+
export declare function parseMultipartStream(stream: ReadableStream<Uint8Array>, options: ParseMultipartOptions): AsyncGenerator<MultipartPart, void, unknown>;
|
|
63
|
+
export type MultipartParserOptions = Omit<ParseMultipartOptions, 'boundary'>;
|
|
64
|
+
/**
|
|
65
|
+
* A streaming parser for `multipart/*` HTTP messages.
|
|
66
|
+
*/
|
|
67
|
+
export declare class MultipartParser {
|
|
68
|
+
#private;
|
|
69
|
+
readonly boundary: string;
|
|
70
|
+
readonly maxHeaderSize: number;
|
|
71
|
+
readonly maxFileSize: number;
|
|
72
|
+
constructor(boundary: string, options?: MultipartParserOptions);
|
|
73
|
+
/**
|
|
74
|
+
* Write a chunk of data to the parser.
|
|
75
|
+
*
|
|
76
|
+
* @param chunk A chunk of data to write to the parser
|
|
77
|
+
* @return A generator yielding `MultipartPart` objects as they are parsed
|
|
78
|
+
*/
|
|
79
|
+
write(chunk: Uint8Array): Generator<MultipartPart, void, unknown>;
|
|
80
|
+
/**
|
|
81
|
+
* Should be called after all data has been written to the parser.
|
|
82
|
+
*
|
|
83
|
+
* Note: This will throw if the multipart message is incomplete or
|
|
84
|
+
* wasn't properly terminated.
|
|
85
|
+
*
|
|
86
|
+
* @return void
|
|
87
|
+
*/
|
|
88
|
+
finish(): void;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* A part of a `multipart/*` HTTP message.
|
|
92
|
+
*/
|
|
93
|
+
export declare class MultipartPart {
|
|
94
|
+
#private;
|
|
95
|
+
/**
|
|
96
|
+
* The raw content of this part as an array of `Uint8Array` chunks.
|
|
97
|
+
*/
|
|
98
|
+
readonly content: Uint8Array[];
|
|
99
|
+
constructor(header: Uint8Array, content: Uint8Array[]);
|
|
100
|
+
/**
|
|
101
|
+
* The content of this part as an `ArrayBuffer`.
|
|
102
|
+
*/
|
|
103
|
+
get arrayBuffer(): ArrayBuffer;
|
|
104
|
+
/**
|
|
105
|
+
* The content of this part as a single `Uint8Array`. In `multipart/form-data` messages, this is useful
|
|
106
|
+
* for reading the value of files that were uploaded using `<input type="file">` fields.
|
|
107
|
+
*/
|
|
108
|
+
get bytes(): Uint8Array;
|
|
109
|
+
/**
|
|
110
|
+
* The headers associated with this part.
|
|
111
|
+
*/
|
|
112
|
+
get headers(): Headers;
|
|
113
|
+
/**
|
|
114
|
+
* True if this part originated from a file upload.
|
|
115
|
+
*/
|
|
116
|
+
get isFile(): boolean;
|
|
117
|
+
/**
|
|
118
|
+
* True if this part originated from a text input field in a form submission.
|
|
119
|
+
*/
|
|
120
|
+
get isText(): boolean;
|
|
121
|
+
/**
|
|
122
|
+
* The filename of the part, if it is a file upload.
|
|
123
|
+
*/
|
|
124
|
+
get filename(): string | undefined;
|
|
125
|
+
/**
|
|
126
|
+
* The media type of the part.
|
|
127
|
+
*/
|
|
128
|
+
get mediaType(): string | undefined;
|
|
129
|
+
/**
|
|
130
|
+
* The name of the part, usually the `name` of the field in the `<form>` that submitted the request.
|
|
131
|
+
*/
|
|
132
|
+
get name(): string | undefined;
|
|
133
|
+
/**
|
|
134
|
+
* The size of the content in bytes.
|
|
135
|
+
*/
|
|
136
|
+
get size(): number;
|
|
137
|
+
/**
|
|
138
|
+
* The content of this part as a string. In `multipart/form-data` messages, this is useful for
|
|
139
|
+
* reading the value of parts that originated from `<input type="text">` fields.
|
|
140
|
+
*
|
|
141
|
+
* Note: Do not use this for binary data, use `part.bytes` or `part.arrayBuffer` instead.
|
|
142
|
+
*/
|
|
143
|
+
get text(): string;
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=multipart.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multipart.d.ts","sourceRoot":"","sources":["../../src/lib/multipart.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,oBAAoB,CAAC;AAMzC;;GAEG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI5B;AAED;;GAEG;AACH,qBAAa,0BAA2B,SAAQ,mBAAmB;gBACrD,aAAa,EAAE,MAAM;CAIlC;AAED;;GAEG;AACH,qBAAa,wBAAyB,SAAQ,mBAAmB;gBACnD,WAAW,EAAE,MAAM;CAIhC;AAED,MAAM,WAAW,qBAAqB;IACpC;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;GASG;AACH,wBAAiB,cAAc,CAC7B,OAAO,EAAE,UAAU,GAAG,QAAQ,CAAC,UAAU,CAAC,EAC1C,OAAO,EAAE,qBAAqB,GAC7B,SAAS,CAAC,aAAa,EAAE,IAAI,EAAE,OAAO,CAAC,CAmBzC;AAED;;;;;;;;;GASG;AACH,wBAAuB,oBAAoB,CACzC,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,EAClC,OAAO,EAAE,qBAAqB,GAC7B,cAAc,CAAC,aAAa,EAAE,IAAI,EAAE,OAAO,CAAC,CAe9C;AAED,MAAM,MAAM,sBAAsB,GAAG,IAAI,CAAC,qBAAqB,EAAE,UAAU,CAAC,CAAC;AAa7E;;GAEG;AACH,qBAAa,eAAe;;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;gBAajB,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,sBAAsB;IAY9D;;;;;OAKG;IACF,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,SAAS,CAAC,aAAa,EAAE,IAAI,EAAE,OAAO,CAAC;IA0HlE;;;;;;;OAOG;IACH,MAAM,IAAI,IAAI;CAKf;AAID;;GAEG;AACH,qBAAa,aAAa;;IACxB;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,UAAU,EAAE,CAAC;gBAKnB,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,EAAE;IAKrD;;OAEG;IACH,IAAI,WAAW,IAAI,WAAW,CAE7B;IAED;;;OAGG;IACH,IAAI,KAAK,IAAI,UAAU,CAUtB;IAED;;OAEG;IACH,IAAI,OAAO,IAAI,OAAO,CAMrB;IAED;;OAEG;IACH,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED;;OAEG;IACH,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED;;OAEG;IACH,IAAI,QAAQ,IAAI,MAAM,GAAG,SAAS,CAEjC;IAED;;OAEG;IACH,IAAI,SAAS,IAAI,MAAM,GAAG,SAAS,CAElC;IAED;;OAEG;IACH,IAAI,IAAI,IAAI,MAAM,GAAG,SAAS,CAE7B;IAED;;OAEG;IACH,IAAI,IAAI,IAAI,MAAM,CAQjB;IAED;;;;;OAKG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type * as http from 'node:http';
|
|
2
|
+
import { Readable } from 'node:stream';
|
|
3
|
+
import type { ParseMultipartOptions, MultipartParserOptions, MultipartPart } from './multipart.ts';
|
|
4
|
+
/**
|
|
5
|
+
* Parse a `multipart/*` Node.js `Buffer` and yield each part as a `MultipartPart` object.
|
|
6
|
+
*
|
|
7
|
+
* Note: This is a low-level API that requires manual handling of the content and boundary. If you're
|
|
8
|
+
* building a web server, consider using `parseMultipartRequest(request)` instead.
|
|
9
|
+
*
|
|
10
|
+
* @param message The multipart message as a `Buffer` or an iterable of `Buffer` chunks
|
|
11
|
+
* @param options Options for the parser
|
|
12
|
+
* @return A generator yielding `MultipartPart` objects
|
|
13
|
+
*/
|
|
14
|
+
export declare function parseMultipart(message: Buffer | Iterable<Buffer>, options: ParseMultipartOptions): Generator<MultipartPart, void, unknown>;
|
|
15
|
+
/**
|
|
16
|
+
* Parse a `multipart/*` Node.js `Readable` stream and yield each part as a `MultipartPart` object.
|
|
17
|
+
*
|
|
18
|
+
* Note: This is a low-level API that requires manual handling of the stream and boundary. If you're
|
|
19
|
+
* building a web server, consider using `parseMultipartRequest(request)` instead.
|
|
20
|
+
*
|
|
21
|
+
* @param stream A Node.js `Readable` stream containing multipart data
|
|
22
|
+
* @param options Options for the parser
|
|
23
|
+
* @return An async generator yielding `MultipartPart` objects
|
|
24
|
+
*/
|
|
25
|
+
export declare function parseMultipartStream(stream: Readable, options: ParseMultipartOptions): AsyncGenerator<MultipartPart, void, unknown>;
|
|
26
|
+
/**
|
|
27
|
+
* Returns true if the given request is a multipart request.
|
|
28
|
+
*
|
|
29
|
+
* @param req The Node.js `http.IncomingMessage` object to check
|
|
30
|
+
* @return `true` if the request is a multipart request, `false` otherwise
|
|
31
|
+
*/
|
|
32
|
+
export declare function isMultipartRequest(req: http.IncomingMessage): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Parse a multipart Node.js request and yield each part as a `MultipartPart` object.
|
|
35
|
+
*
|
|
36
|
+
* @param req The Node.js `http.IncomingMessage` object containing multipart data
|
|
37
|
+
* @param options Options for the parser
|
|
38
|
+
* @return An async generator yielding `MultipartPart` objects
|
|
39
|
+
*/
|
|
40
|
+
export declare function parseMultipartRequest(req: http.IncomingMessage, options?: MultipartParserOptions): AsyncGenerator<MultipartPart, void, unknown>;
|
|
41
|
+
//# sourceMappingURL=multipart.node.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multipart.node.d.ts","sourceRoot":"","sources":["../../src/lib/multipart.node.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,IAAI,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC,OAAO,KAAK,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAQnG;;;;;;;;;GASG;AACH,wBAAiB,cAAc,CAC7B,OAAO,EAAE,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,EAClC,OAAO,EAAE,qBAAqB,GAC7B,SAAS,CAAC,aAAa,EAAE,IAAI,EAAE,OAAO,CAAC,CAEzC;AAED;;;;;;;;;GASG;AACH,wBAAuB,oBAAoB,CACzC,MAAM,EAAE,QAAQ,EAChB,OAAO,EAAE,qBAAqB,GAC7B,cAAc,CAAC,aAAa,EAAE,IAAI,EAAE,OAAO,CAAC,CAE9C;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,GAAG,OAAO,CAGrE;AAED;;;;;;GAMG;AACH,wBAAuB,qBAAqB,CAC1C,GAAG,EAAE,IAAI,CAAC,eAAe,EACzB,OAAO,CAAC,EAAE,sBAAsB,GAC/B,cAAc,CAAC,aAAa,EAAE,IAAI,EAAE,OAAO,CAAC,CAe9C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read-stream.d.ts","sourceRoot":"","sources":["../../src/lib/read-stream.ts"],"names":[],"mappings":"AAEA,wBAAuB,UAAU,CAAC,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,aAAa,CAAC,UAAU,CAAC,CAY/F"}
|