Skip to content

Commit 4d84e35

Browse files
authored
fix(upload): detect mime type from file extension (#911)
1 parent 79b7a84 commit 4d84e35

File tree

7 files changed

+247
-10
lines changed

7 files changed

+247
-10
lines changed

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@
4444
"extract-zip": "^1.6.6",
4545
"https-proxy-agent": "^3.0.0",
4646
"jpeg-js": "^0.3.6",
47-
"mime": "^2.0.3",
4847
"pngjs": "^3.4.0",
4948
"progress": "^2.0.3",
5049
"proxy-from-env": "^1.0.0",
@@ -56,7 +55,6 @@
5655
"@types/debug": "0.0.31",
5756
"@types/extract-zip": "^1.6.2",
5857
"@types/jpeg-js": "^0.3.7",
59-
"@types/mime": "^2.0.0",
6058
"@types/node": "^8.10.34",
6159
"@types/pngjs": "^3.4.0",
6260
"@types/proxy-from-env": "^1.0.0",
@@ -69,6 +67,7 @@
6967
"cross-env": "^5.0.5",
7068
"eslint": "^6.6.0",
7169
"esprima": "^4.0.0",
70+
"formidable": "^1.2.1",
7271
"minimist": "^1.2.0",
7372
"ncp": "^2.0.0",
7473
"node-stream-zip": "^1.8.2",

src/dom.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
432432
if (typeof item === 'string') {
433433
const file: types.FilePayload = {
434434
name: platform.basename(item),
435-
type: 'application/octet-stream',
435+
type: platform.getMimeType(item),
436436
data: await platform.readFileAsync(item, 'base64')
437437
};
438438
return file;
@@ -620,7 +620,7 @@ export function waitForSelectorTask(selector: string, visibility: types.Visibili
620620
export const setFileInputFunction = async (element: HTMLInputElement, payloads: types.FilePayload[]) => {
621621
const files = await Promise.all(payloads.map(async (file: types.FilePayload) => {
622622
const result = await fetch(`data:${file.type};base64,${file.data}`);
623-
return new File([await result.blob()], file.name);
623+
return new File([await result.blob()], file.name, {type: file.type});
624624
}));
625625
const dt = new DataTransfer();
626626
for (const file of files)

src/platform.ts

Lines changed: 203 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import * as nodeFS from 'fs';
2121
import * as nodePath from 'path';
2222
import * as nodeDebug from 'debug';
2323
import * as nodeBuffer from 'buffer';
24-
import * as mime from 'mime';
2524
import * as jpeg from 'jpeg-js';
2625
import * as png from 'pngjs';
2726
import * as http from 'http';
@@ -213,9 +212,9 @@ export async function closeFdAsync(fd: number): Promise<void> {
213212
return await promisify(nodeFS.close)(fd);
214213
}
215214

216-
export function getMimeType(file: string): string | null {
217-
assertFileAccess();
218-
return mime.getType(file);
215+
export function getMimeType(file: string): string {
216+
const extension = file.substring(file.lastIndexOf('.') + 1);
217+
return extensionToMime[extension] || 'application/octet-stream';
219218
}
220219

221220
export function urlMatches(urlString: string, match: types.URLMatch | undefined): boolean {
@@ -358,3 +357,203 @@ export class WebSocketTransport implements ConnectionTransport {
358357
this._ws.close();
359358
}
360359
}
360+
361+
const extensionToMime: { [key: string]: string } = {
362+
'ai': 'application/postscript',
363+
'apng': 'image/apng',
364+
'appcache': 'text/cache-manifest',
365+
'au': 'audio/basic',
366+
'bmp': 'image/bmp',
367+
'cer': 'application/pkix-cert',
368+
'cgm': 'image/cgm',
369+
'coffee': 'text/coffeescript',
370+
'conf': 'text/plain',
371+
'crl': 'application/pkix-crl',
372+
'css': 'text/css',
373+
'csv': 'text/csv',
374+
'def': 'text/plain',
375+
'doc': 'application/msword',
376+
'dot': 'application/msword',
377+
'drle': 'image/dicom-rle',
378+
'dtd': 'application/xml-dtd',
379+
'ear': 'application/java-archive',
380+
'emf': 'image/emf',
381+
'eps': 'application/postscript',
382+
'exr': 'image/aces',
383+
'fits': 'image/fits',
384+
'g3': 'image/g3fax',
385+
'gbr': 'application/rpki-ghostbusters',
386+
'gif': 'image/gif',
387+
'glb': 'model/gltf-binary',
388+
'gltf': 'model/gltf+json',
389+
'gz': 'application/gzip',
390+
'h261': 'video/h261',
391+
'h263': 'video/h263',
392+
'h264': 'video/h264',
393+
'heic': 'image/heic',
394+
'heics': 'image/heic-sequence',
395+
'heif': 'image/heif',
396+
'heifs': 'image/heif-sequence',
397+
'htm': 'text/html',
398+
'html': 'text/html',
399+
'ics': 'text/calendar',
400+
'ief': 'image/ief',
401+
'ifb': 'text/calendar',
402+
'iges': 'model/iges',
403+
'igs': 'model/iges',
404+
'in': 'text/plain',
405+
'ini': 'text/plain',
406+
'jade': 'text/jade',
407+
'jar': 'application/java-archive',
408+
'jls': 'image/jls',
409+
'jp2': 'image/jp2',
410+
'jpe': 'image/jpeg',
411+
'jpeg': 'image/jpeg',
412+
'jpf': 'image/jpx',
413+
'jpg': 'image/jpeg',
414+
'jpg2': 'image/jp2',
415+
'jpgm': 'video/jpm',
416+
'jpgv': 'video/jpeg',
417+
'jpm': 'image/jpm',
418+
'jpx': 'image/jpx',
419+
'js': 'application/javascript',
420+
'json': 'application/json',
421+
'json5': 'application/json5',
422+
'jsx': 'text/jsx',
423+
'jxr': 'image/jxr',
424+
'kar': 'audio/midi',
425+
'ktx': 'image/ktx',
426+
'less': 'text/less',
427+
'list': 'text/plain',
428+
'litcoffee': 'text/coffeescript',
429+
'log': 'text/plain',
430+
'm1v': 'video/mpeg',
431+
'm21': 'application/mp21',
432+
'm2a': 'audio/mpeg',
433+
'm2v': 'video/mpeg',
434+
'm3a': 'audio/mpeg',
435+
'm4a': 'audio/mp4',
436+
'm4p': 'application/mp4',
437+
'man': 'text/troff',
438+
'manifest': 'text/cache-manifest',
439+
'markdown': 'text/markdown',
440+
'mathml': 'application/mathml+xml',
441+
'md': 'text/markdown',
442+
'mdx': 'text/mdx',
443+
'me': 'text/troff',
444+
'mesh': 'model/mesh',
445+
'mft': 'application/rpki-manifest',
446+
'mid': 'audio/midi',
447+
'midi': 'audio/midi',
448+
'mj2': 'video/mj2',
449+
'mjp2': 'video/mj2',
450+
'mjs': 'application/javascript',
451+
'mml': 'text/mathml',
452+
'mov': 'video/quicktime',
453+
'mp2': 'audio/mpeg',
454+
'mp21': 'application/mp21',
455+
'mp2a': 'audio/mpeg',
456+
'mp3': 'audio/mpeg',
457+
'mp4': 'video/mp4',
458+
'mp4a': 'audio/mp4',
459+
'mp4s': 'application/mp4',
460+
'mp4v': 'video/mp4',
461+
'mpe': 'video/mpeg',
462+
'mpeg': 'video/mpeg',
463+
'mpg': 'video/mpeg',
464+
'mpg4': 'video/mp4',
465+
'mpga': 'audio/mpeg',
466+
'mrc': 'application/marc',
467+
'ms': 'text/troff',
468+
'msh': 'model/mesh',
469+
'n3': 'text/n3',
470+
'oga': 'audio/ogg',
471+
'ogg': 'audio/ogg',
472+
'ogv': 'video/ogg',
473+
'ogx': 'application/ogg',
474+
'otf': 'font/otf',
475+
'p10': 'application/pkcs10',
476+
'p7c': 'application/pkcs7-mime',
477+
'p7m': 'application/pkcs7-mime',
478+
'p7s': 'application/pkcs7-signature',
479+
'p8': 'application/pkcs8',
480+
'pdf': 'application/pdf',
481+
'pki': 'application/pkixcmp',
482+
'pkipath': 'application/pkix-pkipath',
483+
'png': 'image/png',
484+
'ps': 'application/postscript',
485+
'pskcxml': 'application/pskc+xml',
486+
'qt': 'video/quicktime',
487+
'rmi': 'audio/midi',
488+
'rng': 'application/xml',
489+
'roa': 'application/rpki-roa',
490+
'roff': 'text/troff',
491+
'rsd': 'application/rsd+xml',
492+
'rss': 'application/rss+xml',
493+
'rtf': 'application/rtf',
494+
'rtx': 'text/richtext',
495+
's3m': 'audio/s3m',
496+
'sgi': 'image/sgi',
497+
'sgm': 'text/sgml',
498+
'sgml': 'text/sgml',
499+
'shex': 'text/shex',
500+
'shtml': 'text/html',
501+
'sil': 'audio/silk',
502+
'silo': 'model/mesh',
503+
'slim': 'text/slim',
504+
'slm': 'text/slim',
505+
'snd': 'audio/basic',
506+
'spx': 'audio/ogg',
507+
'stl': 'model/stl',
508+
'styl': 'text/stylus',
509+
'stylus': 'text/stylus',
510+
'svg': 'image/svg+xml',
511+
'svgz': 'image/svg+xml',
512+
't': 'text/troff',
513+
't38': 'image/t38',
514+
'text': 'text/plain',
515+
'tfx': 'image/tiff-fx',
516+
'tif': 'image/tiff',
517+
'tiff': 'image/tiff',
518+
'tr': 'text/troff',
519+
'ts': 'video/mp2t',
520+
'tsv': 'text/tab-separated-values',
521+
'ttc': 'font/collection',
522+
'ttf': 'font/ttf',
523+
'ttl': 'text/turtle',
524+
'txt': 'text/plain',
525+
'uri': 'text/uri-list',
526+
'uris': 'text/uri-list',
527+
'urls': 'text/uri-list',
528+
'vcard': 'text/vcard',
529+
'vrml': 'model/vrml',
530+
'vtt': 'text/vtt',
531+
'war': 'application/java-archive',
532+
'wasm': 'application/wasm',
533+
'wav': 'audio/wav',
534+
'weba': 'audio/webm',
535+
'webm': 'video/webm',
536+
'webmanifest': 'application/manifest+json',
537+
'webp': 'image/webp',
538+
'wmf': 'image/wmf',
539+
'woff': 'font/woff',
540+
'woff2': 'font/woff2',
541+
'wrl': 'model/vrml',
542+
'x3d': 'model/x3d+xml',
543+
'x3db': 'model/x3d+fastinfoset',
544+
'x3dbz': 'model/x3d+binary',
545+
'x3dv': 'model/x3d-vrml',
546+
'x3dvz': 'model/x3d+vrml',
547+
'x3dz': 'model/x3d+xml',
548+
'xaml': 'application/xaml+xml',
549+
'xht': 'application/xhtml+xml',
550+
'xhtml': 'application/xhtml+xml',
551+
'xm': 'audio/xm',
552+
'xml': 'text/xml',
553+
'xsd': 'application/xml',
554+
'xsl': 'application/xml',
555+
'xslt': 'application/xslt+xml',
556+
'yaml': 'text/yaml',
557+
'yml': 'text/yaml',
558+
'zip': 'application/zip'
559+
};

src/web.webpack.config.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ module.exports = {
4646
'path': 'dummy',
4747
'debug': 'dummy',
4848
'buffer': 'dummy',
49-
'mime': 'dummy',
5049
'jpeg-js': 'dummy',
5150
'pngjs': 'dummy',
5251
'http': 'dummy',

test/assets/input/fileupload.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
<title>File upload test</title>
55
</head>
66
<body>
7+
<form action="/input/fileupload.html">
78
<input type="file">
9+
<input type="submit">
10+
</form>
811
</body>
912
</html>

test/input.spec.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
*/
1717

1818
const path = require('path');
19+
const fs = require('fs');
20+
const formidable = require('formidable');
1921

2022
const FILE_TO_UPLOAD = path.join(__dirname, '/assets/file-to-upload.txt');
2123

@@ -114,6 +116,41 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROMI
114116
expect(await page.$eval('input', input => input.files.length)).toBe(1);
115117
expect(await page.$eval('input', input => input.files[0].name)).toBe('file-to-upload.txt');
116118
});
119+
it('should detect mime type', async({page, server}) => {
120+
let callback;
121+
const result = new Promise(f => callback = f);
122+
server.setRoute('/upload', async (req, res) => {
123+
const form = new formidable.IncomingForm();
124+
form.parse(req, function(err, fields, { file1, file2 }) {
125+
expect(file1.name).toBe('file-to-upload.txt');
126+
expect(file1.type).toBe('text/plain');
127+
expect(
128+
fs.readFileSync(file1.path).toString()
129+
).toBe(
130+
fs.readFileSync(path.join(__dirname, '/assets/file-to-upload.txt')).toString()
131+
);
132+
expect(file2.name).toBe('file-to-upload.png');
133+
expect(file2.type).toBe('image/png');
134+
expect(
135+
fs.readFileSync(file2.path).toString()
136+
).toBe(
137+
fs.readFileSync(path.join(__dirname, '/assets/file-to-upload.png')).toString()
138+
);
139+
callback();
140+
});
141+
});
142+
await page.goto(server.EMPTY_PAGE);
143+
await page.setContent(`
144+
<form action="/upload" method="post" enctype="multipart/form-data" >
145+
<input type="file" name="file1">
146+
<input type="file" name="file2">
147+
<input type="submit" value="Submit">
148+
</form>`)
149+
await (await page.$('input[name=file1]')).setInputFiles(path.join(__dirname, '/assets/file-to-upload.txt'));
150+
await (await page.$('input[name=file2]')).setInputFiles(path.join(__dirname, '/assets/file-to-upload.png'));
151+
page.click('input[type=submit]');
152+
await result;
153+
});
117154
it('should be able to read selected file', async({page, server}) => {
118155
await page.setContent(`<input type=file>`);
119156
page.waitForEvent('filechooser').then(({element}) => element.setInputFiles(FILE_TO_UPLOAD));

test/test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ require('events').defaultMaxListeners *= parallel;
2929

3030
let timeout = process.env.CI ? 30 * 1000 : 10 * 1000;
3131
if (!isNaN(process.env.TIMEOUT))
32-
timeout = parseInt(process.env.TIMEOUT, 10);
32+
timeout = parseInt(process.env.TIMEOUT * 1000, 10);
3333
const testRunner = new TestRunner({
3434
timeout,
3535
parallel,

0 commit comments

Comments
 (0)