From 673beb9c37de9d024551eb8325c215381098561a Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Wed, 22 Jan 2025 23:57:08 +0800 Subject: [PATCH] feat: support custom pathToRegexpModule --- app/middleware/multipart.js | 9 +- package.json | 5 +- test/dynamic-option.test.js | 2 +- test/enable-pathToRegexpModule.test.js | 135 ++++++++++++++++++ test/file-mode.test.js | 10 +- .../app/controller/save.js | 9 ++ .../app/controller/upload.js | 8 ++ .../app/router.js | 8 ++ .../app/views/home.html | 8 ++ .../config/config.default.js | 6 + .../config/config.unittest.js | 8 ++ .../package.json | 3 + .../apps/fileModeMatch-glob/app/router.js | 1 + test/stream-mode-with-filematch-glob.test.js | 43 +++++- 14 files changed, 242 insertions(+), 13 deletions(-) create mode 100644 test/enable-pathToRegexpModule.test.js create mode 100644 test/fixtures/apps/fileModeMatch-glob-with-pathToRegexpModule/app/controller/save.js create mode 100644 test/fixtures/apps/fileModeMatch-glob-with-pathToRegexpModule/app/controller/upload.js create mode 100644 test/fixtures/apps/fileModeMatch-glob-with-pathToRegexpModule/app/router.js create mode 100644 test/fixtures/apps/fileModeMatch-glob-with-pathToRegexpModule/app/views/home.html create mode 100644 test/fixtures/apps/fileModeMatch-glob-with-pathToRegexpModule/config/config.default.js create mode 100644 test/fixtures/apps/fileModeMatch-glob-with-pathToRegexpModule/config/config.unittest.js create mode 100644 test/fixtures/apps/fileModeMatch-glob-with-pathToRegexpModule/package.json diff --git a/app/middleware/multipart.js b/app/middleware/multipart.js index 34eb884..ece8bf2 100644 --- a/app/middleware/multipart.js +++ b/app/middleware/multipart.js @@ -1,10 +1,11 @@ -'use strict'; - const pathMatching = require('egg-path-matching'); -module.exports = options => { +module.exports = (options, app) => { // normalize - const matchFn = options.fileModeMatch && pathMatching({ match: options.fileModeMatch }); + const matchFn = options.fileModeMatch && pathMatching({ + match: options.fileModeMatch, + pathToRegexpModule: app.options.pathToRegexpModule, + }); return async function multipart(ctx, next) { if (!ctx.is('multipart')) return next(); diff --git a/package.json b/package.json index 00b14e6..d95cb58 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "dependencies": { "co-busboy": "^2.0.0", "dayjs": "^1.11.5", - "egg-path-matching": "^1.0.1", + "egg-path-matching": "^1.2.0", "humanize-bytes": "^1.0.1" }, "devDependencies": { @@ -62,6 +62,7 @@ "is-type-of": "^1.2.1", "typescript": "5", "urllib": "3", - "stream-wormhole": "^2.0.1" + "stream-wormhole": "^2.0.1", + "path-to-regexp-v8": "npm:path-to-regexp@8" } } diff --git a/test/dynamic-option.test.js b/test/dynamic-option.test.js index 02fc714..82fce07 100644 --- a/test/dynamic-option.test.js +++ b/test/dynamic-option.test.js @@ -41,6 +41,6 @@ describe('test/dynamic-option.test.js', () => { }); assert(res.status === 413); - assert(res.data.toString().includes('Request_fileSize_limitError: Reach fileSize limit')); + assert.match(res.data.toString(), /Error: Reach fileSize limit/); }); }); diff --git a/test/enable-pathToRegexpModule.test.js b/test/enable-pathToRegexpModule.test.js new file mode 100644 index 0000000..afa864d --- /dev/null +++ b/test/enable-pathToRegexpModule.test.js @@ -0,0 +1,135 @@ +const assert = require('assert'); +const formstream = require('formstream'); +const urllib = require('urllib'); +const path = require('path'); +const mock = require('egg-mock'); +const fs = require('fs').promises; + +describe('test/enable-pathToRegexpModule.test.js', () => { + let app; + let server; + let host; + before(() => { + app = mock.app({ + baseDir: 'apps/fileModeMatch-glob-with-pathToRegexpModule', + pathToRegexpModule: require.resolve('path-to-regexp-v8'), + }); + return app.ready(); + }); + before(() => { + server = app.listen(); + host = 'http://127.0.0.1:' + server.address().port; + }); + after(() => { + return fs.rm(app.config.multipart.tmpdir, { force: true, recursive: true }); + }); + after(() => app.close()); + after(() => server.close()); + beforeEach(() => app.mockCsrf()); + afterEach(mock.restore); + + it('should upload match file mode work on /upload_file', async () => { + const form = formstream(); + form.field('foo', 'fengmk2').field('love', 'egg'); + form.file('file1', __filename, 'foooooooo.js'); + form.file('file2', __filename); + // will ignore empty file + form.buffer('file3', Buffer.from(''), '', 'application/octet-stream'); + form.file('bigfile', path.join(__dirname, 'fixtures', 'bigfile.js')); + // other form fields + form.field('work', 'with Node.js'); + + const headers = form.headers(); + const res = await urllib.request(host + '/upload_file', { + method: 'POST', + headers, + stream: form, + }); + + assert(res.status === 200); + const data = JSON.parse(res.data); + assert.deepStrictEqual(data.body, { foo: 'fengmk2', love: 'egg', work: 'with Node.js' }); + assert(data.files.length === 3); + assert(data.files[0].field === 'file1'); + assert(data.files[0].filename === 'foooooooo.js'); + assert(data.files[0].encoding === '7bit'); + assert(data.files[0].mime === 'application/javascript'); + assert(data.files[0].filepath.startsWith(app.config.multipart.tmpdir)); + + assert(data.files[1].field === 'file2'); + assert(data.files[1].filename === 'enable-pathToRegexpModule.test.js'); + assert(data.files[1].encoding === '7bit'); + assert(data.files[1].mime === 'application/javascript'); + assert(data.files[1].filepath.startsWith(app.config.multipart.tmpdir)); + + assert(data.files[2].field === 'bigfile'); + assert(data.files[2].filename === 'bigfile.js'); + assert(data.files[2].encoding === '7bit'); + assert(data.files[2].mime === 'application/javascript'); + assert(data.files[2].filepath.startsWith(app.config.multipart.tmpdir)); + }); + + it('should upload match file mode work on /upload_file/*', async () => { + const form = formstream(); + form.field('foo', 'fengmk2').field('love', 'egg'); + form.file('file1', __filename, 'foooooooo.js'); + form.file('file2', __filename); + // will ignore empty file + form.buffer('file3', Buffer.from(''), '', 'application/octet-stream'); + form.file('bigfile', path.join(__dirname, 'fixtures', 'bigfile.js')); + // other form fields + form.field('work', 'with Node.js'); + + const headers = form.headers(); + const res = await urllib.request(host + '/upload_file/foo', { + method: 'POST', + headers, + stream: form, + }); + + assert.equal(res.status, 200); + const data = JSON.parse(res.data); + assert.deepStrictEqual(data.body, { foo: 'fengmk2', love: 'egg', work: 'with Node.js' }); + assert(data.files.length === 3); + assert(data.files[0].field === 'file1'); + assert(data.files[0].filename === 'foooooooo.js'); + assert(data.files[0].encoding === '7bit'); + assert(data.files[0].mime === 'application/javascript'); + assert(data.files[0].filepath.startsWith(app.config.multipart.tmpdir)); + + assert(data.files[1].field === 'file2'); + assert(data.files[1].filename === 'enable-pathToRegexpModule.test.js'); + assert(data.files[1].encoding === '7bit'); + assert(data.files[1].mime === 'application/javascript'); + assert(data.files[1].filepath.startsWith(app.config.multipart.tmpdir)); + + assert(data.files[2].field === 'bigfile'); + assert(data.files[2].filename === 'bigfile.js'); + assert(data.files[2].encoding === '7bit'); + assert(data.files[2].mime === 'application/javascript'); + assert(data.files[2].filepath.startsWith(app.config.multipart.tmpdir)); + }); + + it('should upload not match file mode', async () => { + const form = formstream(); + form.field('foo', 'fengmk2').field('love', 'egg'); + form.file('file1', __filename, 'foooooooo.js'); + form.file('file2', __filename); + // will ignore empty file + form.buffer('file3', Buffer.from(''), '', 'application/octet-stream'); + form.file('bigfile', path.join(__dirname, 'fixtures', 'bigfile.js')); + // other form fields + form.field('work', 'with Node.js'); + + const headers = form.headers(); + const res = await urllib.request(host + '/upload', { + method: 'POST', + headers, + stream: form, + }); + + assert(res.status === 200); + const data = JSON.parse(res.data); + assert.deepStrictEqual(data, { body: {} }); + }); +}); diff --git a/test/file-mode.test.js b/test/file-mode.test.js index 1ec163a..d9373a4 100644 --- a/test/file-mode.test.js +++ b/test/file-mode.test.js @@ -187,7 +187,7 @@ describe('test/file-mode.test.js', () => { }); assert(res.status === 413); - assert(res.data.toString().includes('Request_fields_limitError: Reach fields limit')); + assert.match(res.data.toString(), /Error: Reach fields limit/); }); it('should throw error when request files limit', async () => { @@ -205,7 +205,7 @@ describe('test/file-mode.test.js', () => { }); assert(res.status === 413); - assert(res.data.toString().includes('Request_files_limitError: Reach files limit')); + assert.match(res.data.toString(), /Error: Reach files limit/); }); it('should throw error when request field size limit', async () => { @@ -220,7 +220,7 @@ describe('test/file-mode.test.js', () => { }); assert(res.status === 413); - assert(res.data.toString().includes('Request_fieldSize_limitError: Reach fieldSize limit')); + assert.match(res.data.toString(), /Error: Reach fieldSize limit/); }); // fieldNameSize is TODO on busboy @@ -237,7 +237,7 @@ describe('test/file-mode.test.js', () => { }); assert(res.status === 413); - assert(res.data.toString().includes('Request_fieldSize_limitError: Reach fieldSize limit')); + assert.match(res.data.toString(), /Error: Reach fieldSize limit/); }); it('should throw error when request file size limit', async () => { @@ -256,7 +256,7 @@ describe('test/file-mode.test.js', () => { }); assert(res.status === 413); - assert(res.data.toString().includes('Request_fileSize_limitError: Reach fileSize limit')); + assert.match(res.data.toString(), /Error: Reach fileSize limit/); }); it('should throw error when file name invalid', async () => { diff --git a/test/fixtures/apps/fileModeMatch-glob-with-pathToRegexpModule/app/controller/save.js b/test/fixtures/apps/fileModeMatch-glob-with-pathToRegexpModule/app/controller/save.js new file mode 100644 index 0000000..def1972 --- /dev/null +++ b/test/fixtures/apps/fileModeMatch-glob-with-pathToRegexpModule/app/controller/save.js @@ -0,0 +1,9 @@ +'use strict'; + +module.exports = async ctx => { + await ctx.saveRequestFiles(); + ctx.body = { + body: ctx.request.body, + files: ctx.request.files, + }; +}; diff --git a/test/fixtures/apps/fileModeMatch-glob-with-pathToRegexpModule/app/controller/upload.js b/test/fixtures/apps/fileModeMatch-glob-with-pathToRegexpModule/app/controller/upload.js new file mode 100644 index 0000000..2d86f94 --- /dev/null +++ b/test/fixtures/apps/fileModeMatch-glob-with-pathToRegexpModule/app/controller/upload.js @@ -0,0 +1,8 @@ +'use strict'; + +module.exports = async ctx => { + ctx.body = { + body: ctx.request.body, + files: ctx.request.files, + }; +}; diff --git a/test/fixtures/apps/fileModeMatch-glob-with-pathToRegexpModule/app/router.js b/test/fixtures/apps/fileModeMatch-glob-with-pathToRegexpModule/app/router.js new file mode 100644 index 0000000..f6edc00 --- /dev/null +++ b/test/fixtures/apps/fileModeMatch-glob-with-pathToRegexpModule/app/router.js @@ -0,0 +1,8 @@ +'use strict'; + +module.exports = app => { + app.post('/upload', app.controller.upload); + app.post('/upload_file', app.controller.upload); + app.post('/upload_file/foo', app.controller.upload); + app.post('/save', app.controller.save); +}; diff --git a/test/fixtures/apps/fileModeMatch-glob-with-pathToRegexpModule/app/views/home.html b/test/fixtures/apps/fileModeMatch-glob-with-pathToRegexpModule/app/views/home.html new file mode 100644 index 0000000..ce0ffb8 --- /dev/null +++ b/test/fixtures/apps/fileModeMatch-glob-with-pathToRegexpModule/app/views/home.html @@ -0,0 +1,8 @@ +
+ title: + file1: + file2: + file3: + other: + +
diff --git a/test/fixtures/apps/fileModeMatch-glob-with-pathToRegexpModule/config/config.default.js b/test/fixtures/apps/fileModeMatch-glob-with-pathToRegexpModule/config/config.default.js new file mode 100644 index 0000000..3ce68cb --- /dev/null +++ b/test/fixtures/apps/fileModeMatch-glob-with-pathToRegexpModule/config/config.default.js @@ -0,0 +1,6 @@ +exports.multipart = { + mode: 'stream', + fileModeMatch: '/upload_file{/:paths}' +}; + +exports.keys = 'multipart'; diff --git a/test/fixtures/apps/fileModeMatch-glob-with-pathToRegexpModule/config/config.unittest.js b/test/fixtures/apps/fileModeMatch-glob-with-pathToRegexpModule/config/config.unittest.js new file mode 100644 index 0000000..868ad8d --- /dev/null +++ b/test/fixtures/apps/fileModeMatch-glob-with-pathToRegexpModule/config/config.unittest.js @@ -0,0 +1,8 @@ +'use strict'; + +exports.logger = { + consoleLevel: 'NONE', + coreLogger: { + // consoleLevel: 'DEBUG', + }, +}; diff --git a/test/fixtures/apps/fileModeMatch-glob-with-pathToRegexpModule/package.json b/test/fixtures/apps/fileModeMatch-glob-with-pathToRegexpModule/package.json new file mode 100644 index 0000000..c79de33 --- /dev/null +++ b/test/fixtures/apps/fileModeMatch-glob-with-pathToRegexpModule/package.json @@ -0,0 +1,3 @@ +{ + "name": "fileModeMatch-glob-with-pathToRegexpModule" +} diff --git a/test/fixtures/apps/fileModeMatch-glob/app/router.js b/test/fixtures/apps/fileModeMatch-glob/app/router.js index 96b44aa..f6edc00 100644 --- a/test/fixtures/apps/fileModeMatch-glob/app/router.js +++ b/test/fixtures/apps/fileModeMatch-glob/app/router.js @@ -3,5 +3,6 @@ module.exports = app => { app.post('/upload', app.controller.upload); app.post('/upload_file', app.controller.upload); + app.post('/upload_file/foo', app.controller.upload); app.post('/save', app.controller.save); }; diff --git a/test/stream-mode-with-filematch-glob.test.js b/test/stream-mode-with-filematch-glob.test.js index b82380f..4bda660 100644 --- a/test/stream-mode-with-filematch-glob.test.js +++ b/test/stream-mode-with-filematch-glob.test.js @@ -29,7 +29,7 @@ describe('test/stream-mode-with-filematch-glob.test.js', () => { beforeEach(() => app.mockCsrf()); afterEach(mock.restore); - it('should upload match file mode', async () => { + it('should upload match file mode work on /upload_file', async () => { const form = formstream(); form.field('foo', 'fengmk2').field('love', 'egg'); form.file('file1', __filename, 'foooooooo.js'); @@ -70,6 +70,47 @@ describe('test/stream-mode-with-filematch-glob.test.js', () => { assert(data.files[2].filepath.startsWith(app.config.multipart.tmpdir)); }); + it('should upload match file mode work on /upload_file/*', async () => { + const form = formstream(); + form.field('foo', 'fengmk2').field('love', 'egg'); + form.file('file1', __filename, 'foooooooo.js'); + form.file('file2', __filename); + // will ignore empty file + form.buffer('file3', Buffer.from(''), '', 'application/octet-stream'); + form.file('bigfile', path.join(__dirname, 'fixtures', 'bigfile.js')); + // other form fields + form.field('work', 'with Node.js'); + + const headers = form.headers(); + const res = await urllib.request(host + '/upload_file/foo', { + method: 'POST', + headers, + stream: form, + }); + + assert.equal(res.status, 200); + const data = JSON.parse(res.data); + assert.deepStrictEqual(data.body, { foo: 'fengmk2', love: 'egg', work: 'with Node.js' }); + assert(data.files.length === 3); + assert(data.files[0].field === 'file1'); + assert(data.files[0].filename === 'foooooooo.js'); + assert(data.files[0].encoding === '7bit'); + assert(data.files[0].mime === 'application/javascript'); + assert(data.files[0].filepath.startsWith(app.config.multipart.tmpdir)); + + assert(data.files[1].field === 'file2'); + assert(data.files[1].filename === 'stream-mode-with-filematch-glob.test.js'); + assert(data.files[1].encoding === '7bit'); + assert(data.files[1].mime === 'application/javascript'); + assert(data.files[1].filepath.startsWith(app.config.multipart.tmpdir)); + + assert(data.files[2].field === 'bigfile'); + assert(data.files[2].filename === 'bigfile.js'); + assert(data.files[2].encoding === '7bit'); + assert(data.files[2].mime === 'application/javascript'); + assert(data.files[2].filepath.startsWith(app.config.multipart.tmpdir)); + }); + it('should upload not match file mode', async () => { const form = formstream(); form.field('foo', 'fengmk2').field('love', 'egg');