@nestjs/platform-express 12.1.3 → 12.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.
- package/adapters/express-adapter.d.ts +4 -0
- package/adapters/express-adapter.js +37 -8
- package/adapters/utils/get-media-type-version.util.d.ts +4 -0
- package/adapters/utils/get-media-type-version.util.js +37 -3
- package/multer/interceptors/any-files.interceptor.js +5 -3
- package/multer/interceptors/file-fields.interceptor.js +5 -3
- package/multer/interceptors/file.interceptor.js +5 -3
- package/multer/interceptors/files.interceptor.js +5 -3
- package/multer/interceptors/no-files.interceptor.js +2 -2
- package/multer/interfaces/multer-options.interface.d.ts +13 -4
- package/multer/multer/multer.constants.d.ts +11 -0
- package/multer/multer/multer.constants.js +11 -0
- package/multer/multer/multer.utils.d.ts +15 -0
- package/multer/multer/multer.utils.js +62 -1
- package/package.json +6 -5
|
@@ -68,6 +68,10 @@ export declare class ExpressAdapter extends AbstractHttpAdapter<http.Server | ht
|
|
|
68
68
|
setLocal(key: string, value: any): this;
|
|
69
69
|
getType(): string;
|
|
70
70
|
isRouteOrderSensitive(): boolean;
|
|
71
|
+
getRouteMatchingOptions(): {
|
|
72
|
+
caseSensitive: boolean;
|
|
73
|
+
ignoreTrailingSlash: boolean;
|
|
74
|
+
};
|
|
71
75
|
applyVersionFilter(handler: Function, version: VersionValue, versioningOptions: VersioningOptions): VersionedRoute;
|
|
72
76
|
mapException(error: unknown): unknown;
|
|
73
77
|
private normalizePrefix;
|
|
@@ -95,6 +95,12 @@ export class ExpressAdapter extends AbstractHttpAdapter {
|
|
|
95
95
|
return response.send();
|
|
96
96
|
}
|
|
97
97
|
if (body instanceof StreamableFile) {
|
|
98
|
+
// A stream that failed before the reply (e.g. while an interceptor
|
|
99
|
+
// awaited) has already emitted its 'error' event.
|
|
100
|
+
const { errored } = body.getStream();
|
|
101
|
+
if (errored) {
|
|
102
|
+
return body.errorHandler(errored, response);
|
|
103
|
+
}
|
|
98
104
|
this.applyStreamHeaders(response, body);
|
|
99
105
|
const stream = body.getStream();
|
|
100
106
|
stream.once('error', err => {
|
|
@@ -107,6 +113,14 @@ export class ExpressAdapter extends AbstractHttpAdapter {
|
|
|
107
113
|
stream.destroy();
|
|
108
114
|
}
|
|
109
115
|
});
|
|
116
|
+
// Node discards the body of a HEAD response without ever applying
|
|
117
|
+
// backpressure, so piping would read the whole source. The response
|
|
118
|
+
// ends once the source is readable instead, which still leaves an
|
|
119
|
+
// error opening it (e.g. a missing file) to the error handler.
|
|
120
|
+
if (response.req?.method === 'HEAD') {
|
|
121
|
+
stream.once('readable', () => response.end());
|
|
122
|
+
return response.on('error', (err) => body.errorLogger(err));
|
|
123
|
+
}
|
|
110
124
|
return stream
|
|
111
125
|
.pipe(response)
|
|
112
126
|
.on('error', (err) => body.errorLogger(err));
|
|
@@ -114,7 +128,12 @@ export class ExpressAdapter extends AbstractHttpAdapter {
|
|
|
114
128
|
const responseContentType = response.getHeader('Content-Type');
|
|
115
129
|
if (typeof responseContentType === 'string' &&
|
|
116
130
|
!isJsonContentType(responseContentType) &&
|
|
117
|
-
body?.statusCode >= HttpStatus.BAD_REQUEST
|
|
131
|
+
(body?.statusCode >= HttpStatus.BAD_REQUEST ||
|
|
132
|
+
// A custom error body (e.g. "new HttpException({ ... }, 404)") need
|
|
133
|
+
// not have a "statusCode" of its own
|
|
134
|
+
(isObject(body) &&
|
|
135
|
+
!isNil(statusCode) &&
|
|
136
|
+
statusCode >= HttpStatus.BAD_REQUEST))) {
|
|
118
137
|
this.logger.warn("Content-Type doesn't match Reply body, you might need a custom ExceptionFilter for non-JSON responses");
|
|
119
138
|
response.setHeader('Content-Type', 'application/json');
|
|
120
139
|
}
|
|
@@ -267,13 +286,11 @@ export class ExpressAdapter extends AbstractHttpAdapter {
|
|
|
267
286
|
if (isExactPath && router === this.instance.use) {
|
|
268
287
|
router = this.instance.all;
|
|
269
288
|
}
|
|
270
|
-
// Express routes are not strict, so "/api" also matches "/api/"
|
|
271
|
-
//
|
|
272
|
-
// "
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
: callback;
|
|
276
|
-
return router.call(this.instance, convertedPath, handler);
|
|
289
|
+
// Express routes are not strict, so "/api" also matches "/api/", which
|
|
290
|
+
// the wildcard path registered next to it may match too ("/api/{*path}"
|
|
291
|
+
// does, "/api/*path" does not). The core runs the middleware for the
|
|
292
|
+
// first of its paths that matches a request only.
|
|
293
|
+
return router.call(this.instance, convertedPath, callback);
|
|
277
294
|
}
|
|
278
295
|
catch (e) {
|
|
279
296
|
if (e instanceof TypeError) {
|
|
@@ -333,6 +350,18 @@ export class ExpressAdapter extends AbstractHttpAdapter {
|
|
|
333
350
|
isRouteOrderSensitive() {
|
|
334
351
|
return true;
|
|
335
352
|
}
|
|
353
|
+
// The return type is spelled out (rather than "RouteMatchingOptions") so
|
|
354
|
+
// that the declarations still compile against an older "@nestjs/common"
|
|
355
|
+
getRouteMatchingOptions() {
|
|
356
|
+
// Routes are registered on the application router, which takes the
|
|
357
|
+
// "case sensitive routing" and "strict routing" settings in effect when
|
|
358
|
+
// it is created and ignores later changes
|
|
359
|
+
const router = this.getInstance().router;
|
|
360
|
+
return {
|
|
361
|
+
caseSensitive: !!router?.caseSensitive,
|
|
362
|
+
ignoreTrailingSlash: !router?.strict,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
336
365
|
applyVersionFilter(handler, version, versioningOptions) {
|
|
337
366
|
const callNextHandler = (req, res, next) => {
|
|
338
367
|
if (!next) {
|
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
* media range of a list (`text/html, application/json;v=2`); the first match
|
|
7
7
|
* wins. Returns `undefined` when no parameter starts with the key.
|
|
8
8
|
*
|
|
9
|
+
* Quoted parameter values are unescaped, and separators inside them are ignored.
|
|
10
|
+
* An unterminated quoted string runs to the end of the header value, so a
|
|
11
|
+
* version that follows its opening quote is not read.
|
|
12
|
+
*
|
|
9
13
|
* Duplicated in each platform package so that a platform package never
|
|
10
14
|
* depends on a newer `@nestjs/core` than the one installed.
|
|
11
15
|
*/
|
|
@@ -1,3 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Splits a header value at separators outside quoted strings in linear time.
|
|
3
|
+
* Backslash-escaped characters inside quoted strings are treated as literals.
|
|
4
|
+
*/
|
|
5
|
+
const splitOutsideQuotes = (value, separator) => {
|
|
6
|
+
const parts = [];
|
|
7
|
+
let start = 0;
|
|
8
|
+
let isQuoted = false;
|
|
9
|
+
const len = value.length;
|
|
10
|
+
for (let index = 0; index < len; index++) {
|
|
11
|
+
const character = value[index];
|
|
12
|
+
if (isQuoted && character === '\\') {
|
|
13
|
+
// A quoted-pair consumes the next character, including an escaped quote.
|
|
14
|
+
index++;
|
|
15
|
+
}
|
|
16
|
+
else if (character === '"') {
|
|
17
|
+
isQuoted = !isQuoted;
|
|
18
|
+
}
|
|
19
|
+
else if (character === separator && !isQuoted) {
|
|
20
|
+
parts.push(value.slice(start, index));
|
|
21
|
+
start = index + 1;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
parts.push(value.slice(start));
|
|
25
|
+
return parts;
|
|
26
|
+
};
|
|
1
27
|
/**
|
|
2
28
|
* Returns the version carried by an `Accept` header for media type versioning,
|
|
3
29
|
* e.g. `2` for `application/json;v=2` with the `v=` key.
|
|
@@ -6,19 +32,27 @@
|
|
|
6
32
|
* media range of a list (`text/html, application/json;v=2`); the first match
|
|
7
33
|
* wins. Returns `undefined` when no parameter starts with the key.
|
|
8
34
|
*
|
|
35
|
+
* Quoted parameter values are unescaped, and separators inside them are ignored.
|
|
36
|
+
* An unterminated quoted string runs to the end of the header value, so a
|
|
37
|
+
* version that follows its opening quote is not read.
|
|
38
|
+
*
|
|
9
39
|
* Duplicated in each platform package so that a platform package never
|
|
10
40
|
* depends on a newer `@nestjs/core` than the one installed.
|
|
11
41
|
*/
|
|
12
42
|
export function getMediaTypeVersion(acceptHeader, key) {
|
|
13
43
|
const mediaRanges = []
|
|
14
44
|
.concat(acceptHeader ?? [])
|
|
15
|
-
.flatMap(value => value
|
|
45
|
+
.flatMap(value => splitOutsideQuotes(value, ','));
|
|
16
46
|
for (const mediaRange of mediaRanges) {
|
|
17
|
-
const [, ...parameters] = mediaRange
|
|
47
|
+
const [, ...parameters] = splitOutsideQuotes(mediaRange, ';');
|
|
18
48
|
for (const parameter of parameters) {
|
|
19
49
|
const trimmedParameter = parameter.trim();
|
|
20
50
|
if (trimmedParameter.startsWith(key)) {
|
|
21
|
-
|
|
51
|
+
const version = trimmedParameter.slice(key.length);
|
|
52
|
+
if (version.startsWith('"') && version.endsWith('"')) {
|
|
53
|
+
return version.slice(1, -1).replace(/\\([\s\S])/g, '$1');
|
|
54
|
+
}
|
|
55
|
+
return version;
|
|
22
56
|
}
|
|
23
57
|
}
|
|
24
58
|
}
|
|
@@ -2,7 +2,7 @@ import { __decorate, __metadata, __param } from "tslib";
|
|
|
2
2
|
import { Inject, mixin, Optional, } from '@nestjs/common';
|
|
3
3
|
import multer from 'multer';
|
|
4
4
|
import { MULTER_MODULE_OPTIONS } from '../files.constants.js';
|
|
5
|
-
import { mergeMulterOptions, transformException, } from '../multer/multer.utils.js';
|
|
5
|
+
import { applyDefaultLimits, mergeMulterOptions, removeFilesOnPipeRejection, transformException, } from '../multer/multer.utils.js';
|
|
6
6
|
/**
|
|
7
7
|
* @param localOptions
|
|
8
8
|
*
|
|
@@ -12,7 +12,7 @@ export function AnyFilesInterceptor(localOptions) {
|
|
|
12
12
|
let MixinInterceptor = class MixinInterceptor {
|
|
13
13
|
multer;
|
|
14
14
|
constructor(options = {}) {
|
|
15
|
-
this.multer = multer(mergeMulterOptions(options, localOptions));
|
|
15
|
+
this.multer = multer(applyDefaultLimits(mergeMulterOptions(options, localOptions)));
|
|
16
16
|
}
|
|
17
17
|
async intercept(context, next) {
|
|
18
18
|
const ctx = context.switchToHttp();
|
|
@@ -23,7 +23,9 @@ export function AnyFilesInterceptor(localOptions) {
|
|
|
23
23
|
}
|
|
24
24
|
resolve();
|
|
25
25
|
}));
|
|
26
|
-
return next
|
|
26
|
+
return next
|
|
27
|
+
.handle()
|
|
28
|
+
.pipe(removeFilesOnPipeRejection(this.multer.storage, ctx.getRequest(), ctx.getResponse()));
|
|
27
29
|
}
|
|
28
30
|
};
|
|
29
31
|
MixinInterceptor = __decorate([
|
|
@@ -2,7 +2,7 @@ import { __decorate, __metadata, __param } from "tslib";
|
|
|
2
2
|
import { Inject, mixin, Optional, } from '@nestjs/common';
|
|
3
3
|
import multer from 'multer';
|
|
4
4
|
import { MULTER_MODULE_OPTIONS } from '../files.constants.js';
|
|
5
|
-
import { mergeMulterOptions, transformException, } from '../multer/multer.utils.js';
|
|
5
|
+
import { applyDefaultLimits, mergeMulterOptions, removeFilesOnPipeRejection, transformException, } from '../multer/multer.utils.js';
|
|
6
6
|
/**
|
|
7
7
|
* @param uploadFields
|
|
8
8
|
* @param localOptions
|
|
@@ -12,7 +12,7 @@ export function FileFieldsInterceptor(uploadFields, localOptions) {
|
|
|
12
12
|
let MixinInterceptor = class MixinInterceptor {
|
|
13
13
|
multer;
|
|
14
14
|
constructor(options = {}) {
|
|
15
|
-
this.multer = multer(mergeMulterOptions(options, localOptions));
|
|
15
|
+
this.multer = multer(applyDefaultLimits(mergeMulterOptions(options, localOptions)));
|
|
16
16
|
}
|
|
17
17
|
async intercept(context, next) {
|
|
18
18
|
const ctx = context.switchToHttp();
|
|
@@ -23,7 +23,9 @@ export function FileFieldsInterceptor(uploadFields, localOptions) {
|
|
|
23
23
|
}
|
|
24
24
|
resolve();
|
|
25
25
|
}));
|
|
26
|
-
return next
|
|
26
|
+
return next
|
|
27
|
+
.handle()
|
|
28
|
+
.pipe(removeFilesOnPipeRejection(this.multer.storage, ctx.getRequest(), ctx.getResponse()));
|
|
27
29
|
}
|
|
28
30
|
};
|
|
29
31
|
MixinInterceptor = __decorate([
|
|
@@ -2,7 +2,7 @@ import { __decorate, __metadata, __param } from "tslib";
|
|
|
2
2
|
import { Inject, mixin, Optional, } from '@nestjs/common';
|
|
3
3
|
import multer from 'multer';
|
|
4
4
|
import { MULTER_MODULE_OPTIONS } from '../files.constants.js';
|
|
5
|
-
import { mergeMulterOptions, transformException, } from '../multer/multer.utils.js';
|
|
5
|
+
import { applyDefaultLimits, mergeMulterOptions, removeFilesOnPipeRejection, transformException, } from '../multer/multer.utils.js';
|
|
6
6
|
/**
|
|
7
7
|
* @param fieldName
|
|
8
8
|
* @param localOptions
|
|
@@ -13,7 +13,7 @@ export function FileInterceptor(fieldName, localOptions) {
|
|
|
13
13
|
let MixinInterceptor = class MixinInterceptor {
|
|
14
14
|
multer;
|
|
15
15
|
constructor(options = {}) {
|
|
16
|
-
this.multer = multer(mergeMulterOptions(options, localOptions));
|
|
16
|
+
this.multer = multer(applyDefaultLimits(mergeMulterOptions(options, localOptions)));
|
|
17
17
|
}
|
|
18
18
|
async intercept(context, next) {
|
|
19
19
|
const ctx = context.switchToHttp();
|
|
@@ -24,7 +24,9 @@ export function FileInterceptor(fieldName, localOptions) {
|
|
|
24
24
|
}
|
|
25
25
|
resolve();
|
|
26
26
|
}));
|
|
27
|
-
return next
|
|
27
|
+
return next
|
|
28
|
+
.handle()
|
|
29
|
+
.pipe(removeFilesOnPipeRejection(this.multer.storage, ctx.getRequest(), ctx.getResponse()));
|
|
28
30
|
}
|
|
29
31
|
};
|
|
30
32
|
MixinInterceptor = __decorate([
|
|
@@ -2,7 +2,7 @@ import { __decorate, __metadata, __param } from "tslib";
|
|
|
2
2
|
import { Inject, mixin, Optional, } from '@nestjs/common';
|
|
3
3
|
import multer from 'multer';
|
|
4
4
|
import { MULTER_MODULE_OPTIONS } from '../files.constants.js';
|
|
5
|
-
import { mergeMulterOptions, transformException, } from '../multer/multer.utils.js';
|
|
5
|
+
import { applyDefaultLimits, mergeMulterOptions, removeFilesOnPipeRejection, transformException, } from '../multer/multer.utils.js';
|
|
6
6
|
/**
|
|
7
7
|
*
|
|
8
8
|
* @param fieldName
|
|
@@ -15,7 +15,7 @@ export function FilesInterceptor(fieldName, maxCount, localOptions) {
|
|
|
15
15
|
let MixinInterceptor = class MixinInterceptor {
|
|
16
16
|
multer;
|
|
17
17
|
constructor(options = {}) {
|
|
18
|
-
this.multer = multer(mergeMulterOptions(options, localOptions));
|
|
18
|
+
this.multer = multer(applyDefaultLimits(mergeMulterOptions(options, localOptions)));
|
|
19
19
|
}
|
|
20
20
|
async intercept(context, next) {
|
|
21
21
|
const ctx = context.switchToHttp();
|
|
@@ -26,7 +26,9 @@ export function FilesInterceptor(fieldName, maxCount, localOptions) {
|
|
|
26
26
|
}
|
|
27
27
|
resolve();
|
|
28
28
|
}));
|
|
29
|
-
return next
|
|
29
|
+
return next
|
|
30
|
+
.handle()
|
|
31
|
+
.pipe(removeFilesOnPipeRejection(this.multer.storage, ctx.getRequest(), ctx.getResponse()));
|
|
30
32
|
}
|
|
31
33
|
};
|
|
32
34
|
MixinInterceptor = __decorate([
|
|
@@ -2,7 +2,7 @@ import { __decorate, __metadata, __param } from "tslib";
|
|
|
2
2
|
import { Inject, mixin, Optional, } from '@nestjs/common';
|
|
3
3
|
import multer from 'multer';
|
|
4
4
|
import { MULTER_MODULE_OPTIONS } from '../files.constants.js';
|
|
5
|
-
import { mergeMulterOptions, transformException, } from '../multer/multer.utils.js';
|
|
5
|
+
import { applyDefaultLimits, mergeMulterOptions, transformException, } from '../multer/multer.utils.js';
|
|
6
6
|
/**
|
|
7
7
|
*
|
|
8
8
|
* @param localOptions
|
|
@@ -12,7 +12,7 @@ export function NoFilesInterceptor(localOptions) {
|
|
|
12
12
|
let MixinInterceptor = class MixinInterceptor {
|
|
13
13
|
multer;
|
|
14
14
|
constructor(options = {}) {
|
|
15
|
-
this.multer = multer(mergeMulterOptions(options, localOptions));
|
|
15
|
+
this.multer = multer(applyDefaultLimits(mergeMulterOptions(options, localOptions)));
|
|
16
16
|
}
|
|
17
17
|
async intercept(context, next) {
|
|
18
18
|
const ctx = context.switchToHttp();
|
|
@@ -16,9 +16,17 @@ export interface MulterLimits {
|
|
|
16
16
|
parts?: number;
|
|
17
17
|
/** For multipart forms, the max number of header key=> value pairs to parse Default: 2000(same as node's http). */
|
|
18
18
|
headerPairs?: number;
|
|
19
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* Max nesting depth of field names; `a[b][c]` has 2 levels (Default: 32,
|
|
21
|
+
* where multer's own default is Infinity). Set it to `Infinity` to lift
|
|
22
|
+
* the limit.
|
|
23
|
+
*/
|
|
20
24
|
fieldNestingDepth?: number;
|
|
21
|
-
/**
|
|
25
|
+
/**
|
|
26
|
+
* Max numeric array index accepted in field names; `a[3]` uses index 3
|
|
27
|
+
* (Default: 1000, where multer's own default is Infinity). Set it to
|
|
28
|
+
* `Infinity` to lift the limit.
|
|
29
|
+
*/
|
|
22
30
|
fieldArrayIndexLimit?: number;
|
|
23
31
|
}
|
|
24
32
|
/**
|
|
@@ -31,8 +39,9 @@ export interface MulterOptions {
|
|
|
31
39
|
/** The storage engine to use for uploaded files. */
|
|
32
40
|
storage?: any;
|
|
33
41
|
/**
|
|
34
|
-
* An object specifying the size limits of the following optional properties. This object is passed to
|
|
35
|
-
*
|
|
42
|
+
* An object specifying the size limits of the following optional properties. This object is passed to multer
|
|
43
|
+
* and on to busboy, with `fieldNestingDepth` and `fieldArrayIndexLimit` set to their defaults when absent.
|
|
44
|
+
* The details of the busboy properties can be found on https://github.com/mscdex/busboy#busboy-methods
|
|
36
45
|
*
|
|
37
46
|
* Since multer 2.4.0, this can also be a function of the request that returns such an object.
|
|
38
47
|
*/
|
|
@@ -11,6 +11,17 @@ export declare const multerExceptions: {
|
|
|
11
11
|
LIMIT_FIELD_ARRAY_INDEX: string;
|
|
12
12
|
INVALID_FIELD_NAME: string;
|
|
13
13
|
};
|
|
14
|
+
/**
|
|
15
|
+
* Defaults of the field name limits that multer leaves at `Infinity`, which
|
|
16
|
+
* let a single small request build an arbitrarily deep `req.body`
|
|
17
|
+
* (`fieldNestingDepth`) or arrays of arbitrary length (`fieldArrayIndexLimit`).
|
|
18
|
+
* `32` is the nesting depth `body-parser` allows in the urlencoded bodies the
|
|
19
|
+
* ExpressAdapter parses; `1000` is the number of parameters it parses.
|
|
20
|
+
*/
|
|
21
|
+
export declare const DEFAULT_FIELD_NAME_LIMITS: {
|
|
22
|
+
readonly fieldNestingDepth: 32;
|
|
23
|
+
readonly fieldArrayIndexLimit: 1000;
|
|
24
|
+
};
|
|
14
25
|
export declare const busboyExceptions: {
|
|
15
26
|
MULTIPART_BOUNDARY_NOT_FOUND: string;
|
|
16
27
|
MULTIPART_MALFORMED_PART_HEADER: string;
|
|
@@ -15,6 +15,17 @@ export const multerExceptions = {
|
|
|
15
15
|
// engine after the file stream was destroyed, not by the request, so it
|
|
16
16
|
// stays a 500.
|
|
17
17
|
};
|
|
18
|
+
/**
|
|
19
|
+
* Defaults of the field name limits that multer leaves at `Infinity`, which
|
|
20
|
+
* let a single small request build an arbitrarily deep `req.body`
|
|
21
|
+
* (`fieldNestingDepth`) or arrays of arbitrary length (`fieldArrayIndexLimit`).
|
|
22
|
+
* `32` is the nesting depth `body-parser` allows in the urlencoded bodies the
|
|
23
|
+
* ExpressAdapter parses; `1000` is the number of parameters it parses.
|
|
24
|
+
*/
|
|
25
|
+
export const DEFAULT_FIELD_NAME_LIMITS = {
|
|
26
|
+
fieldNestingDepth: 32,
|
|
27
|
+
fieldArrayIndexLimit: 1000,
|
|
28
|
+
};
|
|
18
29
|
export const busboyExceptions = {
|
|
19
30
|
// from https://github.com/mscdex/busboy/blob/master/lib/types/multipart.js
|
|
20
31
|
MULTIPART_BOUNDARY_NOT_FOUND: 'Multipart: Boundary not found',
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { HttpException } from '@nestjs/common';
|
|
2
|
+
import { type MonoTypeOperatorFunction } from 'rxjs';
|
|
2
3
|
import { MulterModuleOptions } from '../interfaces/index.js';
|
|
3
4
|
import { MulterOptions } from '../interfaces/multer-options.interface.js';
|
|
4
5
|
/**
|
|
@@ -12,6 +13,20 @@ import { MulterOptions } from '../interfaces/multer-options.interface.js';
|
|
|
12
13
|
* `limits`) the local value wins outright, matching the pre-merge behaviour.
|
|
13
14
|
*/
|
|
14
15
|
export declare function mergeMulterOptions(options?: MulterModuleOptions, localOptions?: MulterOptions): MulterModuleOptions;
|
|
16
|
+
/**
|
|
17
|
+
* Sets the field name limits multer leaves at `Infinity` to
|
|
18
|
+
* `DEFAULT_FIELD_NAME_LIMITS` wherever they are not set. multer only checks
|
|
19
|
+
* a limit that is present in `limits`, so the defaults are added to the
|
|
20
|
+
* object, or to the object a function-valued `limits` returns.
|
|
21
|
+
*/
|
|
22
|
+
export declare function applyDefaultLimits<T extends MulterOptions>(options: T): T;
|
|
23
|
+
/**
|
|
24
|
+
* Removes the files multer stored for `req` when the route's pipes reject
|
|
25
|
+
* the request (e.g. `ParseFilePipe`): the route handler never sees them, and
|
|
26
|
+
* nothing else would remove them. They are removed once the response has
|
|
27
|
+
* been sent, as an exception filter may still use (or remove) them.
|
|
28
|
+
*/
|
|
29
|
+
export declare function removeFilesOnPipeRejection<T>(storage: any, req: any, res: any): MonoTypeOperatorFunction<T>;
|
|
15
30
|
export declare function transformException(error: (Error & {
|
|
16
31
|
field?: string;
|
|
17
32
|
code?: string;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { BadRequestException, HttpException, PayloadTooLargeException, } from '@nestjs/common';
|
|
2
2
|
import { isPlainObject } from '@nestjs/common/internal';
|
|
3
|
-
import {
|
|
3
|
+
import { isRejectedByPipes } from '@nestjs/core/internal';
|
|
4
|
+
import { catchError, throwError } from 'rxjs';
|
|
5
|
+
import { finished } from 'stream';
|
|
6
|
+
import { DEFAULT_FIELD_NAME_LIMITS, multerExceptions, busboyExceptions, } from './multer.constants.js';
|
|
4
7
|
/**
|
|
5
8
|
* Merges module-level (global) multer options with interceptor-level (local)
|
|
6
9
|
* options. `limits` is merged key-by-key rather than replaced outright, so
|
|
@@ -16,8 +19,66 @@ export function mergeMulterOptions(options = {}, localOptions = {}) {
|
|
|
16
19
|
if (isPlainObject(options.limits) && isPlainObject(localOptions.limits)) {
|
|
17
20
|
merged.limits = { ...options.limits, ...localOptions.limits };
|
|
18
21
|
}
|
|
22
|
+
// `dest` and `storage` both say where files go, and multer prefers
|
|
23
|
+
// `storage`, so a global storage engine would shadow a local `dest`.
|
|
24
|
+
if (localOptions.dest && !localOptions.storage) {
|
|
25
|
+
delete merged.storage;
|
|
26
|
+
}
|
|
19
27
|
return merged;
|
|
20
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Sets the field name limits multer leaves at `Infinity` to
|
|
31
|
+
* `DEFAULT_FIELD_NAME_LIMITS` wherever they are not set. multer only checks
|
|
32
|
+
* a limit that is present in `limits`, so the defaults are added to the
|
|
33
|
+
* object, or to the object a function-valued `limits` returns.
|
|
34
|
+
*/
|
|
35
|
+
export function applyDefaultLimits(options) {
|
|
36
|
+
const { limits } = options;
|
|
37
|
+
return {
|
|
38
|
+
...options,
|
|
39
|
+
limits: typeof limits === 'function'
|
|
40
|
+
? (req) => withDefaultLimits(limits(req))
|
|
41
|
+
: withDefaultLimits(limits),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function withDefaultLimits(limits) {
|
|
45
|
+
return {
|
|
46
|
+
...limits,
|
|
47
|
+
fieldNestingDepth: limits?.fieldNestingDepth ?? DEFAULT_FIELD_NAME_LIMITS.fieldNestingDepth,
|
|
48
|
+
fieldArrayIndexLimit: limits?.fieldArrayIndexLimit ??
|
|
49
|
+
DEFAULT_FIELD_NAME_LIMITS.fieldArrayIndexLimit,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Removes the files multer stored for `req` when the route's pipes reject
|
|
54
|
+
* the request (e.g. `ParseFilePipe`): the route handler never sees them, and
|
|
55
|
+
* nothing else would remove them. They are removed once the response has
|
|
56
|
+
* been sent, as an exception filter may still use (or remove) them.
|
|
57
|
+
*/
|
|
58
|
+
export function removeFilesOnPipeRejection(storage, req, res) {
|
|
59
|
+
return catchError(err => {
|
|
60
|
+
if (isRejectedByPipes(req)) {
|
|
61
|
+
finished(res, () => {
|
|
62
|
+
for (const file of storedFiles(req)) {
|
|
63
|
+
try {
|
|
64
|
+
storage._removeFile(req, file, () => undefined);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// The request has failed already.
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return throwError(() => err);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function storedFiles(req) {
|
|
76
|
+
const { file, files } = req;
|
|
77
|
+
if (file) {
|
|
78
|
+
return [file];
|
|
79
|
+
}
|
|
80
|
+
return Array.isArray(files) ? files : Object.values(files ?? {}).flat();
|
|
81
|
+
}
|
|
21
82
|
// Multer may add in a 'field' property to the error
|
|
22
83
|
// https://github.com/expressjs/multer/blob/aa42bea6ac7d0cb8fcb279b15a7278cda805dc63/lib/multer-error.js#L19
|
|
23
84
|
export function transformException(error) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nestjs/platform-express",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.2.0",
|
|
4
4
|
"description": "Nest - modern, fast, powerful node.js web framework (@platform-express)",
|
|
5
5
|
"author": "Kamil Mysliwiec",
|
|
6
6
|
"license": "MIT",
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"main": "./index.js",
|
|
9
9
|
"exports": {
|
|
10
10
|
".": "./index.js",
|
|
11
|
+
"./package.json": "./package.json",
|
|
11
12
|
"./*.js": "./*.js",
|
|
12
13
|
"./*": "./*.js"
|
|
13
14
|
},
|
|
@@ -26,18 +27,18 @@
|
|
|
26
27
|
},
|
|
27
28
|
"dependencies": {
|
|
28
29
|
"cors": "2.8.6",
|
|
29
|
-
"express": "5.
|
|
30
|
+
"express": "5.3.0",
|
|
30
31
|
"multer": "2.4.0",
|
|
31
32
|
"path-to-regexp": "8.4.2",
|
|
32
33
|
"tslib": "2.8.1"
|
|
33
34
|
},
|
|
34
35
|
"devDependencies": {
|
|
35
|
-
"@nestjs/common": "12.
|
|
36
|
-
"@nestjs/core": "12.
|
|
36
|
+
"@nestjs/common": "12.2.0",
|
|
37
|
+
"@nestjs/core": "12.2.0"
|
|
37
38
|
},
|
|
38
39
|
"peerDependencies": {
|
|
39
40
|
"@nestjs/common": "^12.0.0",
|
|
40
41
|
"@nestjs/core": "^12.0.0"
|
|
41
42
|
},
|
|
42
|
-
"gitHead": "
|
|
43
|
+
"gitHead": "af37111368de7fd2dfab0bdec2f530c7845510c2"
|
|
43
44
|
}
|