Skip to content

Commit 032f439

Browse files
authored
feat: support custom filename when file is Buffer or Readable (#508)
> Uploading files with Buffer defaults the filename to the key or path, which can conflict with middleware checks. * 🤖 files as an object defaults filename to the key. ~~* 🚨 File paths or streams also override the filename.~~ * ♻️ only work for buffer scence. ---------- > 当直接通过 Buffer 来上传文件时,无法自定义文件名,这在某些中间件会校验 filename,导致无法上传。 * 🤖 files 参数为 object 时,默认将 key 作为文件名传输 ~~* 🚨 传入文件路径或 Readable 流时也同样覆盖~~ * ♻️ 仅处理 buffer 场景 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Summary by CodeRabbit - **New Features** - Enhanced file upload functionality to support optional custom file names, allowing users to specify filenames when uploading files. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent bc21ec3 commit 032f439

File tree

2 files changed

+37
-6
lines changed

2 files changed

+37
-6
lines changed

src/HttpClient.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,7 @@ export class HttpClient extends EventEmitter {
440440
requestOptions.method = 'POST';
441441
}
442442
const formData = new FormData();
443-
const uploadFiles: [string, string | Readable | Buffer][] = [];
443+
const uploadFiles: [string, string | Readable | Buffer, string?][] = [];
444444
if (Array.isArray(args.files)) {
445445
for (const [ index, file ] of args.files.entries()) {
446446
const field = index === 0 ? 'file' : `file${index}`;
@@ -452,7 +452,8 @@ export class HttpClient extends EventEmitter {
452452
uploadFiles.push([ 'file', args.files ]);
453453
} else if (typeof args.files === 'object') {
454454
for (const field in args.files) {
455-
uploadFiles.push([ field, args.files[field] ]);
455+
// set custom fileName
456+
uploadFiles.push([ field, args.files[field], field ]);
456457
}
457458
}
458459
// set normal fields first
@@ -461,7 +462,7 @@ export class HttpClient extends EventEmitter {
461462
formData.append(field, args.data[field]);
462463
}
463464
}
464-
for (const [ index, [ field, file ]] of uploadFiles.entries()) {
465+
for (const [ index, [ field, file, customFileName ]] of uploadFiles.entries()) {
465466
if (typeof file === 'string') {
466467
// FIXME: support non-ascii filename
467468
// const fileName = encodeURIComponent(basename(file));
@@ -470,9 +471,9 @@ export class HttpClient extends EventEmitter {
470471
const fileReadable = createReadStream(file);
471472
formData.append(field, new BlobFromStream(fileReadable, mime.lookup(fileName) || ''), fileName);
472473
} else if (Buffer.isBuffer(file)) {
473-
formData.append(field, new Blob([ file ]), `bufferfile${index}`);
474+
formData.append(field, new Blob([ file ]), customFileName || `bufferfile${index}`);
474475
} else if (file instanceof Readable || isReadable(file as any)) {
475-
const fileName = getFileName(file) || `streamfile${index}`;
476+
const fileName = getFileName(file) || customFileName || `streamfile${index}`;
476477
formData.append(field, new BlobFromStream(file, mime.lookup(fileName) || ''), fileName);
477478
isStreamingRequest = true;
478479
}

test/options.files.test.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ describe('options.files.test.ts', () => {
120120
// console.log(response.data);
121121
assert.equal(response.data.method, 'POST');
122122
assert.match(response.data.headers['content-type'], /^multipart\/form-data;/);
123-
assert.equal(response.data.files.hello.filename, 'bufferfile0');
123+
assert.equal(response.data.files.hello.filename, 'hello');
124124
assert.equal(response.data.files.hello.mimeType, 'application/octet-stream');
125125
assert.equal(response.data.files.hello.encoding, '7bit');
126126
assert.equal(response.data.files.hello.size, 11);
@@ -268,4 +268,34 @@ describe('options.files.test.ts', () => {
268268
assert.equal(response.data.form.hello, 'hello world,😄😓');
269269
assert.equal(response.data.form.foo, 'bar');
270270
});
271+
272+
it('should support custom fileName when use files:object', async () => {
273+
const rawData = JSON.stringify({ a: 1 });
274+
const response = await urllib.request(`${_url}multipart`, {
275+
files: {
276+
'buffer.js': Buffer.from(rawData),
277+
'readable.js': Readable.from([ rawData ]),
278+
},
279+
data: {
280+
hello: 'hello world,😄😓',
281+
foo: 'bar',
282+
},
283+
dataType: 'json',
284+
});
285+
assert.equal(response.status, 200);
286+
// console.log(response.data);
287+
assert.equal(response.data.method, 'POST');
288+
assert.match(response.data.headers['content-type'], /^multipart\/form-data;/);
289+
290+
assert.equal(response.data.files['readable.js'].filename, 'readable.js');
291+
// set mimeType by filename
292+
assert.equal(response.data.files['readable.js'].mimeType, 'application/javascript');
293+
294+
assert.equal(response.data.files['buffer.js'].filename, 'buffer.js');
295+
assert.equal(response.data.files['buffer.js'].mimeType, 'application/octet-stream');
296+
assert.equal(response.data.files['buffer.js'].size, rawData.length);
297+
298+
assert.equal(response.data.form.hello, 'hello world,😄😓');
299+
assert.equal(response.data.form.foo, 'bar');
300+
});
271301
});

0 commit comments

Comments
 (0)