@midwayjs/upload 3.0.0-beta.10
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/CHANGELOG.md +11 -0
- package/LICENSE +21 -0
- package/dist/config/config.default.d.ts +3 -0
- package/dist/config/config.default.js +44 -0
- package/dist/configuration.d.ts +7 -0
- package/dist/configuration.js +50 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +18 -0
- package/dist/interface.d.ts +20 -0
- package/dist/interface.js +10 -0
- package/dist/middleware.d.ts +12 -0
- package/dist/middleware.js +164 -0
- package/dist/upload.d.ts +15 -0
- package/dist/upload.js +189 -0
- package/package.json +38 -0
- package/readme.md +82 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Change Log
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
|
+
|
|
6
|
+
# [3.0.0-beta.10](https://github.com/midwayjs/midway/compare/v3.0.0-beta.9...v3.0.0-beta.10) (2021-12-20)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* 3.x upload ([#1422](https://github.com/midwayjs/midway/issues/1422)) ([cbd8e33](https://github.com/midwayjs/midway/commit/cbd8e334a918222f526552859401f0cf222737b6))
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2013 - Now midwayjs
|
|
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.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.upload = void 0;
|
|
4
|
+
const interface_1 = require("../interface");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
const os_1 = require("os");
|
|
7
|
+
exports.upload = {
|
|
8
|
+
mode: interface_1.UploadMode.File,
|
|
9
|
+
fileSize: '10mb',
|
|
10
|
+
whitelist: [
|
|
11
|
+
// images
|
|
12
|
+
'.jpg',
|
|
13
|
+
'.jpeg',
|
|
14
|
+
'.png',
|
|
15
|
+
'.gif',
|
|
16
|
+
'.bmp',
|
|
17
|
+
'.wbmp',
|
|
18
|
+
'.webp',
|
|
19
|
+
'.tif',
|
|
20
|
+
'.psd',
|
|
21
|
+
// text
|
|
22
|
+
'.svg',
|
|
23
|
+
'.js',
|
|
24
|
+
'.jsx',
|
|
25
|
+
'.json',
|
|
26
|
+
'.css',
|
|
27
|
+
'.less',
|
|
28
|
+
'.html',
|
|
29
|
+
'.htm',
|
|
30
|
+
'.xml',
|
|
31
|
+
'.pdf',
|
|
32
|
+
// tar
|
|
33
|
+
'.zip',
|
|
34
|
+
'.gz',
|
|
35
|
+
'.tgz',
|
|
36
|
+
'.gzip',
|
|
37
|
+
// video
|
|
38
|
+
'.mp3',
|
|
39
|
+
'.mp4',
|
|
40
|
+
'.avi',
|
|
41
|
+
],
|
|
42
|
+
tmpdir: (0, path_1.join)((0, os_1.tmpdir)(), 'midway-upload-files'),
|
|
43
|
+
};
|
|
44
|
+
//# sourceMappingURL=config.default.js.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.UploadConfiguration = void 0;
|
|
13
|
+
const decorator_1 = require("@midwayjs/decorator");
|
|
14
|
+
const DefaultConfig = require("./config/config.default");
|
|
15
|
+
const core_1 = require("@midwayjs/core");
|
|
16
|
+
const middleware_1 = require("./middleware");
|
|
17
|
+
const fs_extra_1 = require("fs-extra");
|
|
18
|
+
let UploadConfiguration = class UploadConfiguration {
|
|
19
|
+
async onReady() {
|
|
20
|
+
const { tmpdir } = this.uploadConfig;
|
|
21
|
+
if (tmpdir) {
|
|
22
|
+
await (0, fs_extra_1.ensureDir)(tmpdir);
|
|
23
|
+
}
|
|
24
|
+
this.applicationManager
|
|
25
|
+
.getApplications(['koa', 'faas', 'express', 'egg'])
|
|
26
|
+
.forEach(app => {
|
|
27
|
+
app.useMiddleware(middleware_1.UploadMiddleware);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
__decorate([
|
|
32
|
+
(0, decorator_1.Inject)(),
|
|
33
|
+
__metadata("design:type", core_1.MidwayApplicationManager)
|
|
34
|
+
], UploadConfiguration.prototype, "applicationManager", void 0);
|
|
35
|
+
__decorate([
|
|
36
|
+
(0, decorator_1.Config)('upload'),
|
|
37
|
+
__metadata("design:type", Object)
|
|
38
|
+
], UploadConfiguration.prototype, "uploadConfig", void 0);
|
|
39
|
+
UploadConfiguration = __decorate([
|
|
40
|
+
(0, decorator_1.Configuration)({
|
|
41
|
+
namespace: 'upload',
|
|
42
|
+
importConfigs: [
|
|
43
|
+
{
|
|
44
|
+
default: DefaultConfig,
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
})
|
|
48
|
+
], UploadConfiguration);
|
|
49
|
+
exports.UploadConfiguration = UploadConfiguration;
|
|
50
|
+
//# sourceMappingURL=configuration.js.map
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
5
|
+
}) : (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
o[k2] = m[k];
|
|
8
|
+
}));
|
|
9
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
10
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
11
|
+
};
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.Configuration = void 0;
|
|
14
|
+
var configuration_1 = require("./configuration");
|
|
15
|
+
Object.defineProperty(exports, "Configuration", { enumerable: true, get: function () { return configuration_1.UploadConfiguration; } });
|
|
16
|
+
__exportStar(require("./interface"), exports);
|
|
17
|
+
__exportStar(require("./middleware"), exports);
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { Readable } from "stream";
|
|
3
|
+
export declare enum UploadMode {
|
|
4
|
+
Stream = "stream",
|
|
5
|
+
File = "file",
|
|
6
|
+
Buffer = "buffer"
|
|
7
|
+
}
|
|
8
|
+
export interface UploadOptions {
|
|
9
|
+
mode?: UploadMode;
|
|
10
|
+
fileSize?: string;
|
|
11
|
+
whitelist?: string[];
|
|
12
|
+
tmpdir?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface UploadFileInfo {
|
|
15
|
+
filename: string;
|
|
16
|
+
fieldname: string;
|
|
17
|
+
mimeType: string;
|
|
18
|
+
data: Buffer | Readable | string;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=interface.d.ts.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.UploadMode = void 0;
|
|
4
|
+
var UploadMode;
|
|
5
|
+
(function (UploadMode) {
|
|
6
|
+
UploadMode["Stream"] = "stream";
|
|
7
|
+
UploadMode["File"] = "file";
|
|
8
|
+
UploadMode["Buffer"] = "buffer";
|
|
9
|
+
})(UploadMode = exports.UploadMode || (exports.UploadMode = {}));
|
|
10
|
+
//# sourceMappingURL=interface.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { IMiddleware, IMidwayLogger } from '@midwayjs/core';
|
|
2
|
+
import { UploadOptions } from '.';
|
|
3
|
+
export declare class UploadMiddleware implements IMiddleware<any, any> {
|
|
4
|
+
upload: UploadOptions;
|
|
5
|
+
logger: IMidwayLogger;
|
|
6
|
+
resolve(app: any): (req: any, res: any, next: any) => Promise<any>;
|
|
7
|
+
execUpload(ctx: any, req: any, res: any, next: any, isExpress: any): Promise<any>;
|
|
8
|
+
getUploadBoundary(request: any): false | string;
|
|
9
|
+
isReadableStream(req: any, isExpress: any): boolean;
|
|
10
|
+
checkExt(filename: any): boolean;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=middleware.d.ts.map
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.UploadMiddleware = void 0;
|
|
13
|
+
const decorator_1 = require("@midwayjs/decorator");
|
|
14
|
+
const path_1 = require("path");
|
|
15
|
+
const fs_1 = require("fs");
|
|
16
|
+
const stream_1 = require("stream");
|
|
17
|
+
const _1 = require(".");
|
|
18
|
+
const upload_1 = require("./upload");
|
|
19
|
+
const getRawBody = require("raw-body");
|
|
20
|
+
let UploadMiddleware = class UploadMiddleware {
|
|
21
|
+
resolve(app) {
|
|
22
|
+
if (app.getFrameworkType() === decorator_1.MidwayFrameworkType.WEB_EXPRESS) {
|
|
23
|
+
return async (req, res, next) => {
|
|
24
|
+
return this.execUpload(req, req, res, next, true);
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
return async (ctx, next) => {
|
|
29
|
+
var _a;
|
|
30
|
+
const req = ((_a = ctx.request) === null || _a === void 0 ? void 0 : _a.req) || ctx.request;
|
|
31
|
+
return this.execUpload(ctx, req, ctx, next, false);
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async execUpload(ctx, req, res, next, isExpress) {
|
|
36
|
+
const { mode, tmpdir, fileSize } = this.upload;
|
|
37
|
+
const boundary = this.getUploadBoundary(req);
|
|
38
|
+
if (!boundary) {
|
|
39
|
+
return next();
|
|
40
|
+
}
|
|
41
|
+
ctx.fields = {};
|
|
42
|
+
let body;
|
|
43
|
+
if (this.isReadableStream(req, isExpress)) {
|
|
44
|
+
if (mode === _1.UploadMode.Stream) {
|
|
45
|
+
const { fields, fileInfo } = await (0, upload_1.parseFromReadableStream)(req, boundary);
|
|
46
|
+
if (!this.checkExt(fileInfo.filename)) {
|
|
47
|
+
res.status = 400;
|
|
48
|
+
const errorMessage = 'Invalid filename: ' + fileInfo.filename;
|
|
49
|
+
const err = new Error(errorMessage);
|
|
50
|
+
this.logger.error(err);
|
|
51
|
+
if (isExpress) {
|
|
52
|
+
return res.sendStatus(400);
|
|
53
|
+
}
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
ctx.fields = fields;
|
|
58
|
+
ctx.files = [fileInfo];
|
|
59
|
+
return next();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
body = await getRawBody(req, {
|
|
63
|
+
limit: fileSize,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
body = req.body;
|
|
68
|
+
}
|
|
69
|
+
const data = await (0, upload_1.parseMultipart)(body, boundary);
|
|
70
|
+
if (!data) {
|
|
71
|
+
return next();
|
|
72
|
+
}
|
|
73
|
+
ctx.fields = data.fields;
|
|
74
|
+
const requireId = `upload_${Date.now()}.${Math.random()}`;
|
|
75
|
+
const files = data.files;
|
|
76
|
+
const notCheckFile = files.find(fileInfo => {
|
|
77
|
+
if (!this.checkExt(fileInfo.filename)) {
|
|
78
|
+
return fileInfo;
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
if (notCheckFile) {
|
|
82
|
+
res.status = 400;
|
|
83
|
+
const errorMessage = 'Invalid filename: ' + notCheckFile.filename;
|
|
84
|
+
const err = new Error(errorMessage);
|
|
85
|
+
this.logger.error(err);
|
|
86
|
+
if (isExpress) {
|
|
87
|
+
return res.sendStatus(400);
|
|
88
|
+
}
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
ctx.files =
|
|
92
|
+
mode === 'buffer'
|
|
93
|
+
? files
|
|
94
|
+
: files.map((file, index) => {
|
|
95
|
+
const { data, filename } = file;
|
|
96
|
+
if (mode === _1.UploadMode.File) {
|
|
97
|
+
const ext = (0, path_1.extname)(filename);
|
|
98
|
+
const tmpFileName = (0, path_1.resolve)(tmpdir, `${requireId}.${index}${ext}`);
|
|
99
|
+
(0, fs_1.writeFileSync)(tmpFileName, data, 'binary');
|
|
100
|
+
file.data = tmpFileName;
|
|
101
|
+
}
|
|
102
|
+
else if (mode === _1.UploadMode.Stream) {
|
|
103
|
+
file.data = new stream_1.Readable({
|
|
104
|
+
read() {
|
|
105
|
+
this.push(data);
|
|
106
|
+
this.push(null);
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
return file;
|
|
111
|
+
});
|
|
112
|
+
return next();
|
|
113
|
+
}
|
|
114
|
+
getUploadBoundary(request) {
|
|
115
|
+
var _a;
|
|
116
|
+
const method = (request.method || request.httpMethod || '').toUpperCase();
|
|
117
|
+
if (!((_a = request.headers) === null || _a === void 0 ? void 0 : _a['content-type']) || method !== 'POST') {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
const contentType = request.headers['content-type'];
|
|
121
|
+
if (!contentType.startsWith('multipart/form-data;')) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
const boundaryMatch = /boundary=(.*)(;|\s|$)/.exec(contentType);
|
|
125
|
+
if (!(boundaryMatch === null || boundaryMatch === void 0 ? void 0 : boundaryMatch[1])) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
return boundaryMatch[1];
|
|
129
|
+
}
|
|
130
|
+
isReadableStream(req, isExpress) {
|
|
131
|
+
// ref: https://github.com/rvagg/isstream/blob/master/isstream.js#L10
|
|
132
|
+
if (req instanceof stream_1.Stream &&
|
|
133
|
+
typeof req._read === 'function' &&
|
|
134
|
+
typeof req._readableState === 'object' &&
|
|
135
|
+
(!req.body || isExpress)) {
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
if (req.pipe && req.on && !req.body) {
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
checkExt(filename) {
|
|
144
|
+
const ext = (0, path_1.extname)(filename).toLowerCase();
|
|
145
|
+
const { whitelist } = this.upload;
|
|
146
|
+
if (!Array.isArray(whitelist)) {
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
return whitelist.includes(ext);
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
__decorate([
|
|
153
|
+
(0, decorator_1.Config)('upload'),
|
|
154
|
+
__metadata("design:type", Object)
|
|
155
|
+
], UploadMiddleware.prototype, "upload", void 0);
|
|
156
|
+
__decorate([
|
|
157
|
+
(0, decorator_1.Logger)(),
|
|
158
|
+
__metadata("design:type", Object)
|
|
159
|
+
], UploadMiddleware.prototype, "logger", void 0);
|
|
160
|
+
UploadMiddleware = __decorate([
|
|
161
|
+
(0, decorator_1.Middleware)()
|
|
162
|
+
], UploadMiddleware);
|
|
163
|
+
exports.UploadMiddleware = UploadMiddleware;
|
|
164
|
+
//# sourceMappingURL=middleware.js.map
|
package/dist/upload.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { Readable } from 'stream';
|
|
3
|
+
import { UploadFileInfo } from './interface';
|
|
4
|
+
export declare const parseMultipart: (body: any, boundary: string) => Promise<{
|
|
5
|
+
files: any[];
|
|
6
|
+
fields: {};
|
|
7
|
+
}>;
|
|
8
|
+
export declare const parseFromReadableStream: (readStream: Readable, boundary: any) => Promise<{
|
|
9
|
+
fields: any;
|
|
10
|
+
fileInfo: UploadFileInfo;
|
|
11
|
+
}>;
|
|
12
|
+
export declare const bufferIndexOf: (buffer: Buffer, search: Buffer, offset?: number) => number;
|
|
13
|
+
export declare const bufferSplit: (buffer: Buffer, separator: Buffer, limit?: number) => Buffer[];
|
|
14
|
+
export declare const parseHead: (headBuf: Buffer) => {};
|
|
15
|
+
//# sourceMappingURL=upload.d.ts.map
|
package/dist/upload.js
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseHead = exports.bufferSplit = exports.bufferIndexOf = exports.parseFromReadableStream = exports.parseMultipart = void 0;
|
|
4
|
+
const stream_1 = require("stream");
|
|
5
|
+
const headSeparator = Buffer.from('\r\n\r\n');
|
|
6
|
+
const parseMultipart = async (body, boundary) => {
|
|
7
|
+
if (typeof body === 'string') {
|
|
8
|
+
body = Buffer.from(body);
|
|
9
|
+
}
|
|
10
|
+
const bufferSeparator = Buffer.from('\r\n--' + boundary);
|
|
11
|
+
const fields = {};
|
|
12
|
+
const files = [];
|
|
13
|
+
(0, exports.bufferSplit)(body, bufferSeparator).forEach(buf => {
|
|
14
|
+
const [headerBuf, data] = (0, exports.bufferSplit)(buf, headSeparator, 2);
|
|
15
|
+
const head = (0, exports.parseHead)(headerBuf);
|
|
16
|
+
if (!head['content-disposition']) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (!head['content-disposition'].filename) {
|
|
20
|
+
if (head['content-disposition'].name) {
|
|
21
|
+
fields[head['content-disposition'].name] = data.toString();
|
|
22
|
+
}
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
files.push({
|
|
26
|
+
filename: head['content-disposition'].filename,
|
|
27
|
+
data,
|
|
28
|
+
fieldname: head['content-disposition'].name,
|
|
29
|
+
mimeType: head['content-type'],
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
return {
|
|
33
|
+
files,
|
|
34
|
+
fields,
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
exports.parseMultipart = parseMultipart;
|
|
38
|
+
const pre = Buffer.from('\r\n');
|
|
39
|
+
const parseFromReadableStream = (readStream, boundary) => {
|
|
40
|
+
const bufferSeparator = Buffer.from(`\r\n--${boundary}`);
|
|
41
|
+
const fields = {};
|
|
42
|
+
const fileInfo = {
|
|
43
|
+
filename: '',
|
|
44
|
+
data: null,
|
|
45
|
+
fieldname: '',
|
|
46
|
+
mimeType: '',
|
|
47
|
+
};
|
|
48
|
+
const emptyBuf = Buffer.alloc(0);
|
|
49
|
+
// 上一次遗留的 chunk
|
|
50
|
+
let lastChunk = emptyBuf;
|
|
51
|
+
// 前一个chunk的后缀
|
|
52
|
+
let preChunk = emptyBuf;
|
|
53
|
+
let isTransformFileData = false;
|
|
54
|
+
let isTransformFileDataEnd = false;
|
|
55
|
+
// let isEnd = false;
|
|
56
|
+
let isFirst = true;
|
|
57
|
+
let allChuns = Buffer.alloc(0);
|
|
58
|
+
return new Promise(resolve => {
|
|
59
|
+
fileInfo.data = new stream_1.Transform({
|
|
60
|
+
highWaterMark: 1000,
|
|
61
|
+
transform(chunk, encoding, callback) {
|
|
62
|
+
if (isFirst) {
|
|
63
|
+
chunk = Buffer.concat([pre, chunk]);
|
|
64
|
+
isFirst = false;
|
|
65
|
+
}
|
|
66
|
+
// 已经结束了
|
|
67
|
+
if (isTransformFileDataEnd) {
|
|
68
|
+
return callback(null, null);
|
|
69
|
+
}
|
|
70
|
+
// 正在传输中的话
|
|
71
|
+
if (isTransformFileData) {
|
|
72
|
+
if (lastChunk.length) {
|
|
73
|
+
chunk = Buffer.concat([lastChunk, chunk]);
|
|
74
|
+
lastChunk = emptyBuf;
|
|
75
|
+
}
|
|
76
|
+
const newPreChunk = Buffer.concat([preChunk, chunk]);
|
|
77
|
+
const newBlockIndex = (0, exports.bufferIndexOf)(newPreChunk, bufferSeparator);
|
|
78
|
+
// 存在新的块则代表已经结束了
|
|
79
|
+
if (newBlockIndex !== -1) {
|
|
80
|
+
// 上一个块的最后一部分数据,需要追加写入
|
|
81
|
+
const lastDataBlock = newPreChunk.slice(preChunk.length, newBlockIndex);
|
|
82
|
+
isTransformFileDataEnd = true;
|
|
83
|
+
callback(null, lastDataBlock);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
// 块尚未结束,则继续写入
|
|
87
|
+
callback(null, chunk);
|
|
88
|
+
preChunk = newPreChunk.slice(-bufferSeparator.length);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
// 未在传输过程中
|
|
92
|
+
allChuns = Buffer.concat([allChuns, chunk]);
|
|
93
|
+
const splitAllChuns = (0, exports.bufferSplit)(allChuns, bufferSeparator);
|
|
94
|
+
for (let chunkIndex = 0; chunkIndex < splitAllChuns.length; chunkIndex++) {
|
|
95
|
+
const [headerBuf, data] = (0, exports.bufferSplit)(splitAllChuns[chunkIndex], headSeparator);
|
|
96
|
+
const head = (0, exports.parseHead)(headerBuf);
|
|
97
|
+
if (!head['content-disposition']) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (!head['content-disposition'].filename) {
|
|
101
|
+
if (head['content-disposition'].name) {
|
|
102
|
+
fields[head['content-disposition'].name] = data.toString();
|
|
103
|
+
}
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
// 这里就是找到了 file 的段,如果没有数据,则需要继续等待
|
|
107
|
+
if (!data.length) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
fileInfo.filename = head['content-disposition'].filename;
|
|
111
|
+
fileInfo.fieldname = head['content-disposition'].name;
|
|
112
|
+
fileInfo.mimeType = head['content-type'];
|
|
113
|
+
isTransformFileData = true;
|
|
114
|
+
lastChunk = data;
|
|
115
|
+
allChuns = emptyBuf;
|
|
116
|
+
this.pause();
|
|
117
|
+
resolve({ fileInfo, fields });
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
callback(null, emptyBuf);
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
readStream.pipe(fileInfo.data);
|
|
124
|
+
const empty = new stream_1.Writable();
|
|
125
|
+
empty._write = function (chunk, encoding, cb) {
|
|
126
|
+
cb();
|
|
127
|
+
};
|
|
128
|
+
fileInfo.data.pipe(empty);
|
|
129
|
+
});
|
|
130
|
+
};
|
|
131
|
+
exports.parseFromReadableStream = parseFromReadableStream;
|
|
132
|
+
// search buffer index
|
|
133
|
+
const bufferIndexOf = (buffer, search, offset) => {
|
|
134
|
+
return buffer.indexOf(search, offset);
|
|
135
|
+
};
|
|
136
|
+
exports.bufferIndexOf = bufferIndexOf;
|
|
137
|
+
// split buffer to buffer list
|
|
138
|
+
const bufferSplit = (buffer, separator, limit) => {
|
|
139
|
+
let index = 0;
|
|
140
|
+
const result = [];
|
|
141
|
+
let find = (0, exports.bufferIndexOf)(buffer, separator, index);
|
|
142
|
+
while (find !== -1) {
|
|
143
|
+
result.push(buffer.slice(index, find));
|
|
144
|
+
index = find + separator.length;
|
|
145
|
+
if (limit && result.length + 1 === limit) {
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
find = (0, exports.bufferIndexOf)(buffer, separator, index);
|
|
149
|
+
}
|
|
150
|
+
result.push(buffer.slice(index));
|
|
151
|
+
return result;
|
|
152
|
+
};
|
|
153
|
+
exports.bufferSplit = bufferSplit;
|
|
154
|
+
const headReg = /^([^:]+):[ \t]?(.+)?$/;
|
|
155
|
+
const parseHead = (headBuf) => {
|
|
156
|
+
const head = {};
|
|
157
|
+
const headStrList = headBuf.toString().split('\r\n');
|
|
158
|
+
for (const headStr of headStrList) {
|
|
159
|
+
const matched = headReg.exec(headStr);
|
|
160
|
+
if (!matched) {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
const name = matched[1].toLowerCase();
|
|
164
|
+
const value = matched[2]
|
|
165
|
+
? matched[2].replace(/&#(\d+);/g, (origin, code) => {
|
|
166
|
+
try {
|
|
167
|
+
return String.fromCharCode(parseInt(code));
|
|
168
|
+
}
|
|
169
|
+
catch (_a) {
|
|
170
|
+
return origin;
|
|
171
|
+
}
|
|
172
|
+
})
|
|
173
|
+
: '';
|
|
174
|
+
if (name === 'content-disposition') {
|
|
175
|
+
const headCol = {};
|
|
176
|
+
value.split(/;\s+/).forEach((kv) => {
|
|
177
|
+
const [k, v] = kv.split('=');
|
|
178
|
+
headCol[k] = v ? v.replace(/^"/, '').replace(/"$/, '') : v !== null && v !== void 0 ? v : true;
|
|
179
|
+
});
|
|
180
|
+
head[name] = headCol;
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
head[name] = value;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return head;
|
|
187
|
+
};
|
|
188
|
+
exports.parseHead = parseHead;
|
|
189
|
+
//# sourceMappingURL=upload.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@midwayjs/upload",
|
|
3
|
+
"version": "3.0.0-beta.10",
|
|
4
|
+
"description": "Midway Component for upload",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"typings": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"test": "node --require=ts-node/register ../../node_modules/.bin/jest",
|
|
10
|
+
"cov": "node --require=ts-node/register ../../node_modules/.bin/jest --coverage --forceExit",
|
|
11
|
+
"ci": "npm run test"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [],
|
|
14
|
+
"author": "",
|
|
15
|
+
"files": [
|
|
16
|
+
"dist/**/*.js",
|
|
17
|
+
"dist/**/*.d.ts"
|
|
18
|
+
],
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=12"
|
|
21
|
+
},
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"fs-extra": "^8.0.1",
|
|
25
|
+
"raw-body": "^2.4.1"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@midwayjs/core": "^3.0.0-beta.10",
|
|
29
|
+
"@midwayjs/decorator": "^3.0.0-beta.10",
|
|
30
|
+
"@midwayjs/express": "^3.0.0-beta.10",
|
|
31
|
+
"@midwayjs/faas": "^3.0.0-beta.10",
|
|
32
|
+
"@midwayjs/koa": "^3.0.0-beta.10",
|
|
33
|
+
"@midwayjs/mock": "^3.0.0-beta.10",
|
|
34
|
+
"@midwayjs/serverless-app": "^3.0.0-beta.10",
|
|
35
|
+
"@midwayjs/web": "^3.0.0-beta.10"
|
|
36
|
+
},
|
|
37
|
+
"gitHead": "153870f2e2dd6b17673ec7591e49224a6bd51b36"
|
|
38
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
## Upload 上传组件
|
|
2
|
+
|
|
3
|
+
适用于 `@midwayjs/faas` 、`@midwayjs/web` 、`@midwayjs/koa` 和 `@midwayjs/express` 多种框架的通用上传组件,支持 `file` (服务器临时文件)、`stream` (流)多种模式。
|
|
4
|
+
|
|
5
|
+
### Usage
|
|
6
|
+
|
|
7
|
+
1. 安装依赖
|
|
8
|
+
```shell
|
|
9
|
+
tnpm i @midwayjs/upload --save
|
|
10
|
+
```
|
|
11
|
+
2. 在 configuration 中引入组件,
|
|
12
|
+
```ts
|
|
13
|
+
import * as upload from '../../../../src';
|
|
14
|
+
@Configuration({
|
|
15
|
+
imports: [
|
|
16
|
+
// ...other components
|
|
17
|
+
upload
|
|
18
|
+
],
|
|
19
|
+
importConfigs: [
|
|
20
|
+
{
|
|
21
|
+
default: {
|
|
22
|
+
upload: { // 上传组件的配置
|
|
23
|
+
mode: upload.UploadMode.File, // 默认为 file 模式
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
})
|
|
29
|
+
export class AutoConfiguration {}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
3. 在代码中获取上传的文件
|
|
33
|
+
```ts
|
|
34
|
+
@Controller('/')
|
|
35
|
+
export class HomeController {
|
|
36
|
+
|
|
37
|
+
@Inject()
|
|
38
|
+
ctx;
|
|
39
|
+
|
|
40
|
+
@Post('/upload')
|
|
41
|
+
async upload() {
|
|
42
|
+
const { files, fields } = this.ctx;
|
|
43
|
+
/*
|
|
44
|
+
files = [
|
|
45
|
+
{
|
|
46
|
+
filename: 'test.pdf', // 文件原名
|
|
47
|
+
data: '/var/tmp/xxx.pdf', // mode 为 file 时为服务器临时文件地址
|
|
48
|
+
fieldname: 'test1', // 表单 field 名
|
|
49
|
+
mimeType: 'application/pdf', // mime
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
filename: 'test.pdf', // 文件原名
|
|
53
|
+
data: ReadStream, // mode 为 stream 时为服务器临时文件地址
|
|
54
|
+
fieldname: 'test2', // 表单 field 名
|
|
55
|
+
mimeType: 'application/pdf', // mime
|
|
56
|
+
},
|
|
57
|
+
// ...file 下支持同时上传多个文件
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
*/
|
|
61
|
+
return {
|
|
62
|
+
files,
|
|
63
|
+
fields
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
### 配置
|
|
71
|
+
```ts
|
|
72
|
+
export const upload = {
|
|
73
|
+
// mode: UploadMode, 默认为file,即上传到服务器临时目录,可以配置为 stream
|
|
74
|
+
mode: 'file',
|
|
75
|
+
// fileSize: string, 最大上传文件大小,默认为 10mb
|
|
76
|
+
fileSize: '10mb',
|
|
77
|
+
// whitelist: string[],文件扩展名白名单,默认为 null
|
|
78
|
+
whitelist: null,
|
|
79
|
+
// tmpdir: string,上传的文件临时存储路径
|
|
80
|
+
tmpdir: join(tmpdir(), 'midway-upload-files'),
|
|
81
|
+
}
|
|
82
|
+
```
|