zero-http 0.2.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.
Files changed (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +311 -0
  3. package/documentation/controllers/cleanup.js +25 -0
  4. package/documentation/controllers/echo.js +14 -0
  5. package/documentation/controllers/headers.js +1 -0
  6. package/documentation/controllers/proxy.js +112 -0
  7. package/documentation/controllers/root.js +1 -0
  8. package/documentation/controllers/uploads.js +289 -0
  9. package/documentation/full-server.js +129 -0
  10. package/documentation/public/data/api.json +167 -0
  11. package/documentation/public/data/examples.json +62 -0
  12. package/documentation/public/data/options.json +13 -0
  13. package/documentation/public/index.html +414 -0
  14. package/documentation/public/prism-overrides.css +40 -0
  15. package/documentation/public/scripts/app.js +44 -0
  16. package/documentation/public/scripts/data-sections.js +300 -0
  17. package/documentation/public/scripts/helpers.js +166 -0
  18. package/documentation/public/scripts/playground.js +71 -0
  19. package/documentation/public/scripts/proxy.js +98 -0
  20. package/documentation/public/scripts/ui.js +210 -0
  21. package/documentation/public/scripts/uploads.js +459 -0
  22. package/documentation/public/styles.css +310 -0
  23. package/documentation/public/vendor/icons/fetch.svg +23 -0
  24. package/documentation/public/vendor/icons/plug.svg +27 -0
  25. package/documentation/public/vendor/icons/static.svg +35 -0
  26. package/documentation/public/vendor/icons/stream.svg +22 -0
  27. package/documentation/public/vendor/icons/zero.svg +21 -0
  28. package/documentation/public/vendor/prism-copy-to-clipboard.min.js +27 -0
  29. package/documentation/public/vendor/prism-javascript.min.js +1 -0
  30. package/documentation/public/vendor/prism-json.min.js +1 -0
  31. package/documentation/public/vendor/prism-okaidia.css +1 -0
  32. package/documentation/public/vendor/prism-toolbar.css +27 -0
  33. package/documentation/public/vendor/prism-toolbar.min.js +41 -0
  34. package/documentation/public/vendor/prism.min.js +1 -0
  35. package/index.js +43 -0
  36. package/lib/app.js +159 -0
  37. package/lib/body/index.js +14 -0
  38. package/lib/body/json.js +54 -0
  39. package/lib/body/multipart.js +310 -0
  40. package/lib/body/raw.js +40 -0
  41. package/lib/body/rawBuffer.js +74 -0
  42. package/lib/body/sendError.js +17 -0
  43. package/lib/body/text.js +43 -0
  44. package/lib/body/typeMatch.js +22 -0
  45. package/lib/body/urlencoded.js +166 -0
  46. package/lib/cors.js +72 -0
  47. package/lib/fetch.js +218 -0
  48. package/lib/logger.js +68 -0
  49. package/lib/rateLimit.js +64 -0
  50. package/lib/request.js +76 -0
  51. package/lib/response.js +165 -0
  52. package/lib/router.js +87 -0
  53. package/lib/static.js +196 -0
  54. package/package.json +44 -0
package/index.js ADDED
@@ -0,0 +1,43 @@
1
+ /**
2
+ * @module zero-http
3
+ * @description Public entry point for the zero-http package.
4
+ * Re-exports every middleware, the app factory, and the fetch helper.
5
+ */
6
+ const App = require('./lib/app');
7
+ const cors = require('./lib/cors');
8
+ const fetch = require('./lib/fetch');
9
+ const body = require('./lib/body');
10
+ const serveStatic = require('./lib/static');
11
+ const rateLimit = require('./lib/rateLimit');
12
+ const logger = require('./lib/logger');
13
+
14
+ module.exports = {
15
+ /**
16
+ * Create a new application instance.
17
+ * @returns {import('./lib/app')} Fresh App with an empty middleware stack.
18
+ */
19
+ createApp: () => new App(),
20
+ /** @see module:cors */
21
+ cors,
22
+ /** @see module:fetch */
23
+ fetch,
24
+ // body parsers
25
+ /** @see module:body/json */
26
+ json: body.json,
27
+ /** @see module:body/urlencoded */
28
+ urlencoded: body.urlencoded,
29
+ /** @see module:body/text */
30
+ text: body.text,
31
+ /** @see module:body/raw */
32
+ raw: body.raw,
33
+ /** @see module:body/multipart */
34
+ multipart: body.multipart,
35
+ // serving
36
+ /** @see module:static */
37
+ static: serveStatic,
38
+ // middleware
39
+ /** @see module:rateLimit */
40
+ rateLimit,
41
+ /** @see module:logger */
42
+ logger,
43
+ };
package/lib/app.js ADDED
@@ -0,0 +1,159 @@
1
+ /**
2
+ * @module app
3
+ * @description Express-like HTTP application with middleware pipeline and
4
+ * method-based routing. Created via `createApp()` in the public API.
5
+ */
6
+ const http = require('http');
7
+ const Router = require('./router');
8
+ const Request = require('./request');
9
+ const Response = require('./response');
10
+
11
+ class App
12
+ {
13
+ /**
14
+ * Create a new App instance.
15
+ * Initialises an empty middleware stack, a {@link Router}, and binds
16
+ * `this.handler` for direct use with `http.createServer()`.
17
+ *
18
+ * @constructor
19
+ */
20
+ constructor()
21
+ {
22
+ /** @type {Router} */
23
+ this.router = new Router();
24
+ /** @type {Function[]} */
25
+ this.middlewares = [];
26
+ /** @type {Function|null} */
27
+ this._errorHandler = null;
28
+
29
+ // Bind for use as `http.createServer(app.handler)`
30
+ this.handler = (req, res) => this.handle(req, res);
31
+ }
32
+
33
+ /**
34
+ * Register middleware.
35
+ * - `use(fn)` — global middleware applied to every request.
36
+ * - `use('/prefix', fn)` — path-scoped middleware (strips the prefix
37
+ * before calling `fn` so downstream sees relative paths).
38
+ *
39
+ * @param {string|Function} pathOrFn - A path prefix string, or middleware function.
40
+ * @param {Function} [fn] - Middleware function when first arg is a path.
41
+ */
42
+ use(pathOrFn, fn)
43
+ {
44
+ if (typeof pathOrFn === 'function')
45
+ {
46
+ this.middlewares.push(pathOrFn);
47
+ }
48
+ else if (typeof pathOrFn === 'string' && typeof fn === 'function')
49
+ {
50
+ const prefix = pathOrFn.endsWith('/') ? pathOrFn.slice(0, -1) : pathOrFn;
51
+ this.middlewares.push((req, res, next) =>
52
+ {
53
+ const urlPath = req.url.split('?')[0];
54
+ if (urlPath === prefix || urlPath.startsWith(prefix + '/'))
55
+ {
56
+ // strip prefix from url so downstream sees relative paths
57
+ const origUrl = req.url;
58
+ req.url = req.url.slice(prefix.length) || '/';
59
+ fn(req, res, () => { req.url = origUrl; next(); });
60
+ }
61
+ else
62
+ {
63
+ next();
64
+ }
65
+ });
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Register a global error handler.
71
+ * The handler receives `(err, req, res, next)` and is invoked whenever
72
+ * a middleware or route handler throws or passes an error to `next(err)`.
73
+ *
74
+ * @param {Function} fn - Error-handling function `(err, req, res, next) => void`.
75
+ */
76
+ onError(fn)
77
+ {
78
+ this._errorHandler = fn;
79
+ }
80
+
81
+ /**
82
+ * Core request handler. Wraps the raw Node `req`/`res` in
83
+ * {@link Request}/{@link Response} wrappers, runs the middleware
84
+ * pipeline, then falls through to the router.
85
+ *
86
+ * @param {import('http').IncomingMessage} req - Raw Node request.
87
+ * @param {import('http').ServerResponse} res - Raw Node response.
88
+ */
89
+ handle(req, res)
90
+ {
91
+ const request = new Request(req);
92
+ const response = new Response(res);
93
+
94
+ let idx = 0;
95
+ const run = (err) =>
96
+ {
97
+ if (err)
98
+ {
99
+ if (this._errorHandler) return this._errorHandler(err, request, response, run);
100
+ response.status(500).json({ error: err.message || 'Internal Server Error' });
101
+ return;
102
+ }
103
+ if (idx < this.middlewares.length)
104
+ {
105
+ const mw = this.middlewares[idx++];
106
+ try
107
+ {
108
+ const result = mw(request, response, run);
109
+ // Handle promise-returning middleware
110
+ if (result && typeof result.catch === 'function')
111
+ {
112
+ result.catch(run);
113
+ }
114
+ }
115
+ catch (e)
116
+ {
117
+ run(e);
118
+ }
119
+ return;
120
+ }
121
+ this.router.handle(request, response);
122
+ };
123
+
124
+ run();
125
+ }
126
+
127
+ /**
128
+ * Start listening for HTTP connections.
129
+ *
130
+ * @param {number} [port=3000] - Port number to bind.
131
+ * @param {Function} [cb] - Callback invoked once the server is listening.
132
+ * @returns {import('http').Server} The underlying Node HTTP server.
133
+ */
134
+ listen(port = 3000, cb)
135
+ {
136
+ const server = http.createServer(this.handler);
137
+ return server.listen(port, cb);
138
+ }
139
+
140
+ /**
141
+ * Register one or more handler functions for a specific HTTP method and path.
142
+ *
143
+ * @param {string} method - HTTP method (GET, POST, etc.) or 'ALL'.
144
+ * @param {string} path - Route pattern (e.g. '/users/:id').
145
+ * @param {...Function} fns - Handler functions `(req, res, next) => void`.
146
+ */
147
+ route(method, path, ...fns) { this.router.add(method, path, fns); }
148
+
149
+ /** @see App#route — shortcut for GET requests. */ get(path, ...fns) { this.route('GET', path, ...fns); }
150
+ /** @see App#route — shortcut for POST requests. */ post(path, ...fns) { this.route('POST', path, ...fns); }
151
+ /** @see App#route — shortcut for PUT requests. */ put(path, ...fns) { this.route('PUT', path, ...fns); }
152
+ /** @see App#route — shortcut for DELETE requests. */ delete(path, ...fns) { this.route('DELETE', path, ...fns); }
153
+ /** @see App#route — shortcut for PATCH requests. */ patch(path, ...fns) { this.route('PATCH', path, ...fns); }
154
+ /** @see App#route — shortcut for OPTIONS requests.*/ options(path, ...fns) { this.route('OPTIONS', path, ...fns); }
155
+ /** @see App#route — shortcut for HEAD requests. */ head(path, ...fns) { this.route('HEAD', path, ...fns); }
156
+ /** @see App#route — matches every HTTP method. */ all(path, ...fns) { this.route('ALL', path, ...fns); }
157
+ }
158
+
159
+ module.exports = App;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @module body
3
+ * @description Barrel export for all body-parsing utilities and middleware.
4
+ */
5
+ const rawBuffer = require('./rawBuffer');
6
+ const isTypeMatch = require('./typeMatch');
7
+ const sendError = require('./sendError');
8
+ const json = require('./json');
9
+ const urlencoded = require('./urlencoded');
10
+ const text = require('./text');
11
+ const raw = require('./raw');
12
+ const multipart = require('./multipart');
13
+
14
+ module.exports = { rawBuffer, isTypeMatch, sendError, json, urlencoded, text, raw, multipart };
@@ -0,0 +1,54 @@
1
+ /**
2
+ * @module body/json
3
+ * @description JSON body-parsing middleware.
4
+ * Reads the request body, parses it as JSON, and sets `req.body`.
5
+ */
6
+ const rawBuffer = require('./rawBuffer');
7
+ const isTypeMatch = require('./typeMatch');
8
+ const sendError = require('./sendError');
9
+
10
+ /**
11
+ * Create a JSON body-parsing middleware.
12
+ *
13
+ * @param {object} [options]
14
+ * @param {string|number} [options.limit] - Max body size (e.g. `'10kb'`).
15
+ * @param {Function} [options.reviver] - `JSON.parse` reviver function.
16
+ * @param {boolean} [options.strict=true] - When true, reject non-object/array roots.
17
+ * @param {string|Function} [options.type='application/json'] - Content-Type to match.
18
+ * @returns {Function} Async middleware `(req, res, next) => void`.
19
+ */
20
+ function json(options = {})
21
+ {
22
+ const opts = options || {};
23
+ const limit = opts.limit || null;
24
+ const reviver = opts.reviver;
25
+ const strict = (opts.hasOwnProperty('strict')) ? !!opts.strict : true;
26
+ const typeOpt = opts.type || 'application/json';
27
+
28
+ return async (req, res, next) =>
29
+ {
30
+ const ct = (req.headers['content-type'] || '');
31
+ if (!isTypeMatch(ct, typeOpt)) return next();
32
+ try
33
+ {
34
+ const buf = await rawBuffer(req, { limit });
35
+ const txt = buf.toString('utf8');
36
+ if (!txt) { req.body = null; return next(); }
37
+ let parsed;
38
+ try { parsed = JSON.parse(txt, reviver); } catch (e) { req.body = null; return next(); }
39
+ if (strict && (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed) === false && Object.keys(parsed).length === 0 && !Array.isArray(parsed)))
40
+ {
41
+ // If strict, prefer objects/arrays; allow arrays but reject primitives
42
+ if (typeof parsed !== 'object') { req.body = null; return next(); }
43
+ }
44
+ req.body = parsed;
45
+ } catch (err)
46
+ {
47
+ if (err && err.status === 413) return sendError(res, 413, 'payload too large');
48
+ req.body = null;
49
+ }
50
+ next();
51
+ };
52
+ }
53
+
54
+ module.exports = json;
@@ -0,0 +1,310 @@
1
+ /**
2
+ * @module body/multipart
3
+ * @description Streaming multipart/form-data parser.
4
+ * Writes uploaded files to a temp directory and collects
5
+ * form fields. Sets `req.body = { fields, files }`.
6
+ */
7
+ const fs = require('fs');
8
+ const os = require('os');
9
+ const path = require('path');
10
+ const sendError = require('./sendError');
11
+
12
+ /**
13
+ * Generate a unique filename with an optional prefix.
14
+ *
15
+ * @param {string} [prefix='miniex'] - Filename prefix.
16
+ * @returns {string}
17
+ */
18
+ function uniqueName(prefix = 'miniex')
19
+ {
20
+ return `${prefix}-${Date.now()}-${Math.floor(Math.random() * 1e9)}`;
21
+ }
22
+
23
+ /**
24
+ * Recursively create a directory if it doesn't exist.
25
+ *
26
+ * @param {string} dir - Directory path.
27
+ */
28
+ function ensureDir(dir)
29
+ {
30
+ try { fs.mkdirSync(dir, { recursive: true }); } catch (e) { }
31
+ }
32
+
33
+ /**
34
+ * Parse raw MIME header text (CRLF-separated) into a plain object.
35
+ *
36
+ * @param {string} headerText - Raw header block.
37
+ * @returns {Object<string, string>} Lower-cased header key/value map.
38
+ */
39
+ function parseHeaders(headerText)
40
+ {
41
+ const lines = headerText.split('\r\n');
42
+ const obj = {};
43
+ for (const l of lines)
44
+ {
45
+ const idx = l.indexOf(':');
46
+ if (idx === -1) continue;
47
+ const k = l.slice(0, idx).trim().toLowerCase();
48
+ const v = l.slice(idx + 1).trim();
49
+ obj[k] = v;
50
+ }
51
+ return obj;
52
+ }
53
+
54
+ /**
55
+ * Extract `name` and `filename` fields from a `Content-Disposition` header.
56
+ *
57
+ * @param {string} cd - Content-Disposition value.
58
+ * @returns {Object<string, string>} Parsed disposition parameters.
59
+ */
60
+ function parseContentDisposition(cd)
61
+ {
62
+ const m = /form-data;(.*)/i.exec(cd);
63
+ if (!m) return {};
64
+ const parts = m[1].split(';').map(s => s.trim());
65
+ const out = {};
66
+ for (const p of parts)
67
+ {
68
+ const mm = /([^=]+)="?([^"]+)"?/.exec(p);
69
+ if (mm) out[mm[1]] = mm[2];
70
+ }
71
+ return out;
72
+ }
73
+
74
+ /**
75
+ * Create a streaming multipart/form-data parsing middleware.
76
+ *
77
+ * @param {object} [opts]
78
+ * @param {string} [opts.dir] - Upload directory (default: OS temp dir).
79
+ * @param {number} [opts.maxFileSize] - Maximum file size in bytes.
80
+ * @returns {Function} Async middleware `(req, res, next) => void`.
81
+ */
82
+ function multipart(opts = {})
83
+ {
84
+ return async (req, res, next) =>
85
+ {
86
+ const ct = req.headers['content-type'] || '';
87
+ const m = /boundary=(?:"([^"]+)"|([^;\s]+))/i.exec(ct);
88
+ if (!m) return next();
89
+ const boundary = (m[1] || m[2] || '').replace(/^"|"$/g, '');
90
+ const dashBoundary = `--${boundary}`;
91
+ const dashBoundaryBuf = Buffer.from('\r\n' + dashBoundary);
92
+ const startBoundaryBuf = Buffer.from(dashBoundary);
93
+
94
+ let tmpDir;
95
+ if (opts.dir)
96
+ {
97
+ tmpDir = path.isAbsolute(opts.dir) ? opts.dir : path.join(process.cwd(), opts.dir);
98
+ } else
99
+ {
100
+ tmpDir = path.join(os.tmpdir(), 'zero-http-uploads');
101
+ }
102
+ const maxFileSize = opts.maxFileSize || null; // bytes
103
+ ensureDir(tmpDir);
104
+
105
+ const fields = {};
106
+ const files = {};
107
+
108
+ let buffer = Buffer.alloc(0);
109
+ let state = 'start'; // start, headers, body
110
+ let current = null; // { headers, name, filename, contentType, writeStream, collectedSize }
111
+
112
+ const pendingWrites = [];
113
+
114
+ function abortFileTooLarge()
115
+ {
116
+ try { current.writeStream.end(); } catch (e) { }
117
+ try { fs.unlinkSync(current.filePath); } catch (e) { }
118
+ if (!req._multipartErrorHandled)
119
+ {
120
+ req._multipartErrorHandled = true;
121
+ sendError(res, 413, 'file too large');
122
+ req.raw.pause && req.raw.pause();
123
+ }
124
+ }
125
+
126
+ function closeCurrent()
127
+ {
128
+ if (!current) return;
129
+ if (current.writeStream)
130
+ {
131
+ // end the stream and record file after it's flushed to disk
132
+ // capture values so we don't rely on `current` later
133
+ const info = { name: current.name, filename: current.filename, filePath: current.filePath, contentType: current.contentType, size: current.collectedSize };
134
+ const p = new Promise((resolve) =>
135
+ {
136
+ current.writeStream.on('finish', () =>
137
+ {
138
+ files[info.name] = { originalFilename: info.filename, storedName: path.basename(info.filePath), path: info.filePath, contentType: info.contentType, size: info.size };
139
+ resolve();
140
+ });
141
+ current.writeStream.on('error', () =>
142
+ {
143
+ resolve();
144
+ });
145
+ });
146
+ pendingWrites.push(p);
147
+ current.writeStream.end();
148
+ } else
149
+ {
150
+ fields[current.name] = current.value || '';
151
+ }
152
+ current = null;
153
+ }
154
+
155
+ req.raw.on('data', (chunk) =>
156
+ {
157
+ buffer = Buffer.concat([buffer, chunk]);
158
+
159
+ while (true)
160
+ {
161
+ if (state === 'start')
162
+ {
163
+ // look for starting boundary
164
+ const idx = buffer.indexOf(startBoundaryBuf);
165
+ if (idx === -1)
166
+ {
167
+ // boundary not yet found
168
+ if (buffer.length > startBoundaryBuf.length) buffer = buffer.slice(buffer.length - startBoundaryBuf.length);
169
+ break;
170
+ }
171
+ // consume up to after boundary and CRLF
172
+ const after = idx + startBoundaryBuf.length;
173
+ if (buffer.length < after + 2) break; // wait for CRLF
174
+ buffer = buffer.slice(after);
175
+ if (buffer.slice(0, 2).toString() === '\r\n') buffer = buffer.slice(2);
176
+ state = 'headers';
177
+ } else if (state === 'headers')
178
+ {
179
+ const idx = buffer.indexOf('\r\n\r\n');
180
+ if (idx === -1)
181
+ {
182
+ // wait for more
183
+ if (buffer.length > 1024 * 1024)
184
+ {
185
+ // keep buffer bounded
186
+ buffer = buffer.slice(buffer.length - 1024 * 16);
187
+ }
188
+ break;
189
+ }
190
+ const headerText = buffer.slice(0, idx).toString('utf8');
191
+ buffer = buffer.slice(idx + 4);
192
+ const hdrs = parseHeaders(headerText);
193
+ const disp = hdrs['content-disposition'] || '';
194
+ const cd = parseContentDisposition(disp);
195
+ const name = cd.name;
196
+ const filename = cd.filename;
197
+ const contentType = hdrs['content-type'] || null;
198
+ current = { headers: hdrs, name, filename, contentType, collectedSize: 0 };
199
+ if (filename)
200
+ {
201
+ // create temp file; preserve the original extension when possible
202
+ const ext = path.extname(filename) || '';
203
+ const safeExt = ext.replace(/[^a-z0-9.]/gi, '');
204
+ let fname = uniqueName('upload');
205
+ if (safeExt) fname = fname + (safeExt.startsWith('.') ? safeExt : ('.' + safeExt));
206
+ const filePath = path.join(tmpDir, fname);
207
+ current.filePath = filePath;
208
+ current.writeStream = fs.createWriteStream(filePath);
209
+ } else
210
+ {
211
+ current.value = '';
212
+ }
213
+ state = 'body';
214
+ } else if (state === 'body')
215
+ {
216
+ // look for boundary preceded by CRLF
217
+ const idx = buffer.indexOf(dashBoundaryBuf);
218
+ if (idx === -1)
219
+ {
220
+ // keep tail in buffer to match partial boundary
221
+ const keep = Math.max(dashBoundaryBuf.length, 1024);
222
+ const writeLen = buffer.length - keep;
223
+ if (writeLen > 0)
224
+ {
225
+ const toWrite = buffer.slice(0, writeLen);
226
+ if (current.writeStream)
227
+ {
228
+ current.writeStream.write(toWrite);
229
+ current.collectedSize += toWrite.length;
230
+ if (maxFileSize && current.collectedSize > maxFileSize)
231
+ {
232
+ abortFileTooLarge();
233
+ return;
234
+ }
235
+ } else
236
+ {
237
+ current.value += toWrite.toString('utf8');
238
+ }
239
+ buffer = buffer.slice(writeLen);
240
+ }
241
+ break;
242
+ }
243
+ // boundary found at idx; data before idx is body chunk (without the leading CRLF)
244
+ const bodyChunk = buffer.slice(0, idx);
245
+ // if bodyChunk starts with CRLF, strip it
246
+ const toWrite = (bodyChunk.slice(0, 2).toString() === '\r\n') ? bodyChunk.slice(2) : bodyChunk;
247
+ if (toWrite.length)
248
+ {
249
+ if (current.writeStream)
250
+ {
251
+ current.writeStream.write(toWrite);
252
+ current.collectedSize += toWrite.length;
253
+ if (maxFileSize && current.collectedSize > maxFileSize)
254
+ {
255
+ abortFileTooLarge();
256
+ return;
257
+ }
258
+ } else
259
+ {
260
+ current.value += toWrite.toString('utf8');
261
+ }
262
+ }
263
+ // consume boundary marker
264
+ buffer = buffer.slice(idx + dashBoundaryBuf.length);
265
+ // check for final boundary '--'
266
+ if (buffer.slice(0, 2).toString() === '--')
267
+ {
268
+ // final
269
+ closeCurrent();
270
+ // wait for any pending file flushes then continue
271
+ req.raw.pause && req.raw.pause();
272
+ Promise.all(pendingWrites).then(() =>
273
+ {
274
+ req.body = { fields, files };
275
+ req._multipart = true;
276
+ return next();
277
+ }).catch(() =>
278
+ {
279
+ req.body = { fields, files };
280
+ req._multipart = true;
281
+ return next();
282
+ });
283
+ return;
284
+ }
285
+ // trim leading CRLF if present
286
+ if (buffer.slice(0, 2).toString() === '\r\n') buffer = buffer.slice(2);
287
+ // close current and continue to next headers
288
+ closeCurrent();
289
+ state = 'headers';
290
+ }
291
+ }
292
+ });
293
+
294
+ req.raw.on('end', () =>
295
+ {
296
+ // finish any current
297
+ if (current) closeCurrent();
298
+ req.body = { fields, files };
299
+ req._multipart = true;
300
+ next();
301
+ });
302
+
303
+ req.raw.on('error', (err) =>
304
+ {
305
+ next();
306
+ });
307
+ };
308
+ }
309
+
310
+ module.exports = multipart;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * @module body/raw
3
+ * @description Raw-buffer body-parsing middleware.
4
+ * Stores the full request body as a Buffer on `req.body`.
5
+ */
6
+ const rawBuffer = require('./rawBuffer');
7
+ const isTypeMatch = require('./typeMatch');
8
+ const sendError = require('./sendError');
9
+
10
+ /**
11
+ * Create a raw-buffer body-parsing middleware.
12
+ *
13
+ * @param {object} [options]
14
+ * @param {string|number} [options.limit] - Max body size.
15
+ * @param {string|Function} [options.type='application/octet-stream'] - Content-Type to match.
16
+ * @returns {Function} Async middleware `(req, res, next) => void`.
17
+ */
18
+ function raw(options = {})
19
+ {
20
+ const opts = options || {};
21
+ const limit = opts.limit || null;
22
+ const typeOpt = opts.type || 'application/octet-stream';
23
+
24
+ return async (req, res, next) =>
25
+ {
26
+ const ct = (req.headers['content-type'] || '');
27
+ if (!isTypeMatch(ct, typeOpt)) return next();
28
+ try
29
+ {
30
+ req.body = await rawBuffer(req, { limit });
31
+ } catch (err)
32
+ {
33
+ if (err && err.status === 413) return sendError(res, 413, 'payload too large');
34
+ req.body = Buffer.alloc(0);
35
+ }
36
+ next();
37
+ };
38
+ }
39
+
40
+ module.exports = raw;