diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index c18b5f2..0000000 --- a/.editorconfig +++ /dev/null @@ -1,18 +0,0 @@ -# EditorConfig helps developers define and maintain consistent coding styles between different editors and IDEs -# http://editorconfig.org - -root = true - -[*] -indent_style = space -indent_size = 4 -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true - -[*{.yml}] -indent_size = 2 - -[*.md] -trim_trailing_whitespace = false -max_line_length = off diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0b20bf1..a4b08b1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,10 +14,10 @@ jobs: name: Release runs-on: ubuntu-latest permissions: - contents: write # to be able to publish a GitHub release - issues: write # to be able to comment on released issues + contents: write # to be able to publish a GitHub release + issues: write # to be able to comment on released issues pull-requests: write # to be able to comment on released pull requests - id-token: write # to enable use of OIDC for npm provenance + id-token: write # to enable use of OIDC for npm provenance steps: - name: Checkout repository uses: actions/checkout@v4 diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml deleted file mode 100644 index 2ab4aa5..0000000 --- a/.github/workflows/stale.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: "Close stale issues and PRs" -on: - schedule: - - cron: "0 0 * * *" - -jobs: - stale: - runs-on: ubuntu-latest - steps: - - uses: actions/stale@v3 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - days-before-stale: 60 - days-before-close: 7 - stale-issue-message: > - This issue has been automatically marked as stale because it has not had - recent activity in the past 60 days. It will be closed within 7 days if no further activity occurs. If the issue persists please comment here to bump your issue. - Thank You - React Dropzone Maintaners - stale-pr-message: > - This PR has been automatically marked as stale because it has not had - recent activity in the past 60 days. It will be closed within 7 days if no further activity occurs. If the issue persists please comment here to bump your issue. - Thank You - React Dropzone Maintaners - stale-issue-label: stale - stale-pr-label: stale diff --git a/README.md b/README.md index 00d8207..5758af5 100644 --- a/README.md +++ b/README.md @@ -11,14 +11,13 @@ # Table of Contents -* [Installation](#installation) -* [Usage](#usage) -* [Browser Support](#browser-support) -* [Contribute](#contribute) -* [Credits](#credits) -* [Support](#support) -* [License](#license) - +- [Installation](#installation) +- [Usage](#usage) +- [Browser Support](#browser-support) +- [Contribute](#contribute) +- [Credits](#credits) +- [Support](#support) +- [License](#license) ## Installation @@ -33,7 +32,7 @@ npm add file-selector If you are using a bundler such as [Vite](https://vite.dev/) or [Webpack](https://webpack.js.org/) you can import the package directly: ```js -import {fromEvent} from 'file-selector'; +import { fromEvent } from "file-selector"; ``` ### Browser @@ -46,7 +45,7 @@ If you want to use a CDN, you can use [Skypack](https://www.skypack.dev/), or an ```html ``` @@ -56,7 +55,7 @@ Self hosting is also possible, make sure to copy or link the contents of the NPM ```html ``` @@ -66,17 +65,17 @@ To avoid repeating the import path and get an experience similar to a bundler yo ```html ``` @@ -85,64 +84,69 @@ To avoid repeating the import path and get an experience similar to a bundler yo Convert a [DragEvent](https://developer.mozilla.org/en-US/docs/Web/API/DragEvent) to File objects: ```js -import {fromEvent} from 'file-selector'; +import { fromEvent } from "file-selector"; -document.addEventListener('drop', async (event) => { - const files = await fromEvent(event); - console.log(files); +document.addEventListener("drop", async (event) => { + const files = await fromEvent(event); + console.log(files); }); ``` Convert a [change event](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/change_event) for an input type file to File objects: ```js -import {fromEvent} from 'file-selector'; +import { fromEvent } from "file-selector"; -const input = document.getElementById('myInput'); -input.addEventListener('change', async (event) => { - const files = await fromEvent(event); - console.log(files); +const input = document.getElementById("myInput"); +input.addEventListener("change", async (event) => { + const files = await fromEvent(event); + console.log(files); }); ``` Convert [FileSystemFileHandle](https://developer.mozilla.org/en-US/docs/Web/API/FileSystemFileHandle) items to File objects: ```js -import {fromEvent} from 'file-selector'; +import { fromEvent } from "file-selector"; -const handles = await window.showOpenFilePicker({multiple: true}); +const handles = await window.showOpenFilePicker({ multiple: true }); const files = await fromEvent(handles); console.log(files); ``` -> [!NOTE] +> [!NOTE] > The above is experimental and subject to change. ## Browser Support + Most browser support basic File selection with drag 'n' drop or file input: -* [File API](https://developer.mozilla.org/en-US/docs/Web/API/File#Browser_compatibility) -* [Drag Event](https://developer.mozilla.org/en-US/docs/Web/API/DragEvent#Browser_compatibility) -* [DataTransfer](https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer#Browser_compatibility) -* [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#Browser_compatibility) + +- [File API](https://developer.mozilla.org/en-US/docs/Web/API/File#Browser_compatibility) +- [Drag Event](https://developer.mozilla.org/en-US/docs/Web/API/DragEvent#Browser_compatibility) +- [DataTransfer](https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer#Browser_compatibility) +- [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#Browser_compatibility) For folder drop we use the [FileSystem API](https://developer.mozilla.org/en-US/docs/Web/API/FileSystem) which has very limited support: -* [DataTransferItem.getAsFile()](https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem/getAsFile#Browser_compatibility) -* [DataTransferItem.webkitGetAsEntry()](https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem/webkitGetAsEntry#Browser_compatibility) -* [FileSystemEntry](https://developer.mozilla.org/en-US/docs/Web/API/FileSystemEntry#Browser_compatibility) -* [FileSystemFileEntry.file()](https://developer.mozilla.org/en-US/docs/Web/API/FileSystemFileEntry/file#Browser_compatibility) -* [FileSystemDirectoryEntry.createReader()](https://developer.mozilla.org/en-US/docs/Web/API/FileSystemDirectoryEntry/createReader#Browser_compatibility) -* [FileSystemDirectoryReader.readEntries()](https://developer.mozilla.org/en-US/docs/Web/API/FileSystemDirectoryReader/readEntries#Browser_compatibility) +- [DataTransferItem.getAsFile()](https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem/getAsFile#Browser_compatibility) +- [DataTransferItem.webkitGetAsEntry()](https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem/webkitGetAsEntry#Browser_compatibility) +- [FileSystemEntry](https://developer.mozilla.org/en-US/docs/Web/API/FileSystemEntry#Browser_compatibility) +- [FileSystemFileEntry.file()](https://developer.mozilla.org/en-US/docs/Web/API/FileSystemFileEntry/file#Browser_compatibility) +- [FileSystemDirectoryEntry.createReader()](https://developer.mozilla.org/en-US/docs/Web/API/FileSystemDirectoryEntry/createReader#Browser_compatibility) +- [FileSystemDirectoryReader.readEntries()](https://developer.mozilla.org/en-US/docs/Web/API/FileSystemDirectoryReader/readEntries#Browser_compatibility) ## Contribute + Checkout the organization [CONTRIBUTING.md](https://github.com/react-dropzone/.github/blob/main/CONTRIBUTING.md). ## Credits -* [html5-file-selector](https://github.com/quarklemotion/html5-file-selector) + +- [html5-file-selector](https://github.com/quarklemotion/html5-file-selector) ## Support ### Backers + Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/react-dropzone#backer)] @@ -158,6 +162,7 @@ Support us with a monthly donation and help us continue our activities. [[Become ### Sponsors + Become a sponsor and get your logo on our README on Github with a link to your site. [[Become a sponsor](https://opencollective.com/react-dropzone#sponsor)] @@ -173,4 +178,5 @@ Become a sponsor and get your logo on our README on Github with a link to your s ## License + MIT diff --git a/eslint.config.js b/eslint.config.js index 833eea3..4cc681a 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,14 +1,16 @@ -import eslint from '@eslint/js'; -import tseslint from 'typescript-eslint'; +import eslint from "@eslint/js"; +import tseslint from "typescript-eslint"; +import eslintConfigPrettier from "eslint-config-prettier"; export default tseslint.config( - eslint.configs.recommended, - ...tseslint.configs.recommended, - { - rules: { - // TODO: Fix these rule and remove their overrides. - '@typescript-eslint/no-empty-object-type': 'off', - '@typescript-eslint/no-explicit-any': 'off', - } - } + eslint.configs.recommended, + ...tseslint.configs.recommended, + { + rules: { + // TODO: Fix these rule and remove their overrides. + "@typescript-eslint/no-empty-object-type": "off", + "@typescript-eslint/no-explicit-any": "off", + }, + }, + eslintConfigPrettier, ); diff --git a/jest.config.js b/jest.config.js index d0a057f..c0a3f93 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,15 +1,15 @@ -import { createDefaultEsmPreset } from 'ts-jest' +import { createDefaultEsmPreset } from "ts-jest"; -const defaultPreset = createDefaultEsmPreset() +const defaultPreset = createDefaultEsmPreset(); /** @type {import('ts-jest').JestConfigWithTsJest} */ const jestConfig = { - ...defaultPreset, - testEnvironment: 'jsdom', - // Convert `.js` imports to one without an extension to support 'NodeNext' module resolution. - moduleNameMapper: { - '^(\\.{1,2}/.*)\\.js$': '$1' - } -} + ...defaultPreset, + testEnvironment: "jsdom", + // Convert `.js` imports to one without an extension to support 'NodeNext' module resolution. + moduleNameMapper: { + "^(\\.{1,2}/.*)\\.js$": "$1", + }, +}; -export default jestConfig +export default jestConfig; diff --git a/package-lock.json b/package-lock.json index a1db184..6c435aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,8 +14,10 @@ "@types/jest": "^29.5.13", "@types/node": "^22.7.5", "eslint": "^9.14.0", + "eslint-config-prettier": "^9.1.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", + "prettier": "^3.3.3", "ts-jest": "^29.2.5", "typescript": "^5.6.3", "typescript-eslint": "^8.12.2" @@ -2727,6 +2729,19 @@ } } }, + "node_modules/eslint-config-prettier": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, "node_modules/eslint-scope": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", @@ -4909,6 +4924,22 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", diff --git a/package.json b/package.json index 625db0b..fa046f6 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,8 @@ }, "scripts": { "build": "rm -rf dist && tsc -p ./tsconfig.build.json", - "lint": "eslint .", + "lint": "prettier . --check && eslint .", + "lint:fix": "prettier --write . && eslint . --fix", "test": "jest" }, "devDependencies": { @@ -45,8 +46,10 @@ "@types/jest": "^29.5.13", "@types/node": "^22.7.5", "eslint": "^9.14.0", + "eslint-config-prettier": "^9.1.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", + "prettier": "^3.3.3", "ts-jest": "^29.2.5", "typescript": "^5.6.3", "typescript-eslint": "^8.12.2" diff --git a/src/file-selector.spec.ts b/src/file-selector.spec.ts index 2df57f6..82c67d9 100644 --- a/src/file-selector.spec.ts +++ b/src/file-selector.spec.ts @@ -1,511 +1,581 @@ -import {FileWithPath} from './file.js'; -import {fromEvent} from './file-selector.js'; +import { FileWithPath } from "./file.js"; +import { fromEvent } from "./file-selector.js"; -it('returns a Promise', async () => { - const evt = new Event('test'); - expect(fromEvent(evt)).toBeInstanceOf(Promise); +it("returns a Promise", async () => { + const evt = new Event("test"); + expect(fromEvent(evt)).toBeInstanceOf(Promise); }); -it('should return an empty array if the passed arg is not what we expect', async () => { - const files = await fromEvent({}); - expect(files).toHaveLength(0); +it("should return an empty array if the passed arg is not what we expect", async () => { + const files = await fromEvent({}); + expect(files).toHaveLength(0); }); -it('should return an empty array if drag event', async () => { - const files = await fromEvent({}); - expect(files).toHaveLength(0); +it("should return an empty array if drag event", async () => { + const files = await fromEvent({}); + expect(files).toHaveLength(0); }); -it('should return the evt {target} {files} if the passed event is an input evt', async () => { - const name = 'test.json'; - const mockFile = createFile(name, {ping: true}, { - type: 'application/json' - }); - const evt = inputEvtFromFiles(mockFile); - - const files = await fromEvent(evt); - expect(files).toHaveLength(1); - expect(files.every(file => file instanceof File)).toBe(true); - - const [file] = files as FileWithPath[]; - - expect(file.name).toBe(mockFile.name); - expect(file.type).toBe(mockFile.type); - expect(file.size).toBe(mockFile.size); - expect(file.lastModified).toBe(mockFile.lastModified); - expect(file.path).toBe(`./${name}`); +it("should return the evt {target} {files} if the passed event is an input evt", async () => { + const name = "test.json"; + const mockFile = createFile( + name, + { ping: true }, + { + type: "application/json", + }, + ); + const evt = inputEvtFromFiles(mockFile); + + const files = await fromEvent(evt); + expect(files).toHaveLength(1); + expect(files.every((file) => file instanceof File)).toBe(true); + + const [file] = files as FileWithPath[]; + + expect(file.name).toBe(mockFile.name); + expect(file.type).toBe(mockFile.type); + expect(file.size).toBe(mockFile.size); + expect(file.lastModified).toBe(mockFile.lastModified); + expect(file.path).toBe(`./${name}`); }); -it('should return an empty array if the evt {target} has no {files} prop', async () => { - const evt = inputEvtFromFiles(); - const files = await fromEvent(evt); - expect(files).toHaveLength(0); +it("should return an empty array if the evt {target} has no {files} prop", async () => { + const evt = inputEvtFromFiles(); + const files = await fromEvent(evt); + expect(files).toHaveLength(0); }); -it('should return files if the arg is a list of FileSystemFileHandle', async () => { - const name = 'test.json'; - const [mockFile, mockHandle] = createFileSystemFileHandle(name, {ping: true}, { - type: 'application/json' - }); - - const files = await fromEvent([mockHandle]); - expect(files).toHaveLength(1); - expect(files.every(file => file instanceof File)).toBe(true); - - const [file] = files as FileWithPath[]; - - expect(file.name).toBe(mockFile.name); - expect(file.type).toBe(mockFile.type); - expect(file.size).toBe(mockFile.size); - expect(file.lastModified).toBe(mockFile.lastModified); - expect(file.path).toBe(`./${name}`); +it("should return files if the arg is a list of FileSystemFileHandle", async () => { + const name = "test.json"; + const [mockFile, mockHandle] = createFileSystemFileHandle( + name, + { ping: true }, + { + type: "application/json", + }, + ); + + const files = await fromEvent([mockHandle]); + expect(files).toHaveLength(1); + expect(files.every((file) => file instanceof File)).toBe(true); + + const [file] = files as FileWithPath[]; + + expect(file.name).toBe(mockFile.name); + expect(file.type).toBe(mockFile.type); + expect(file.size).toBe(mockFile.size); + expect(file.lastModified).toBe(mockFile.lastModified); + expect(file.path).toBe(`./${name}`); }); -it('should return an empty array if the passed event is not a DragEvent', async () => { - const evt = new Event('test'); - const files = await fromEvent(evt); - expect(files).toHaveLength(0); +it("should return an empty array if the passed event is not a DragEvent", async () => { + const evt = new Event("test"); + const files = await fromEvent(evt); + expect(files).toHaveLength(0); }); -it('should return {files} from DataTransfer if {items} is not defined (e.g. IE11)', async () => { - const name = 'test.json'; - const mockFile = createFile(name, {ping: true}, { - type: 'application/json' - }); - const evt = dragEvt([mockFile]); - - const files = await fromEvent(evt); - expect(files).toHaveLength(1); - expect(files.every(file => file instanceof File)).toBe(true); - - const [file] = files as FileWithPath[]; - - expect(file.name).toBe(mockFile.name); - expect(file.type).toBe(mockFile.type); - expect(file.size).toBe(mockFile.size); - expect(file.lastModified).toBe(mockFile.lastModified); - expect(file.path).toBe(`./${name}`); +it("should return {files} from DataTransfer if {items} is not defined (e.g. IE11)", async () => { + const name = "test.json"; + const mockFile = createFile( + name, + { ping: true }, + { + type: "application/json", + }, + ); + const evt = dragEvt([mockFile]); + + const files = await fromEvent(evt); + expect(files).toHaveLength(1); + expect(files.every((file) => file instanceof File)).toBe(true); + + const [file] = files as FileWithPath[]; + + expect(file.name).toBe(mockFile.name); + expect(file.type).toBe(mockFile.type); + expect(file.size).toBe(mockFile.size); + expect(file.lastModified).toBe(mockFile.lastModified); + expect(file.path).toBe(`./${name}`); }); -it('should return files from DataTransfer {items} if the passed event is a DragEvent', async () => { - const name = 'test.json'; - const mockFile = createFile(name, {ping: true}, { - type: 'application/json' - }); - const item = dataTransferItemFromFile(mockFile); - const evt = dragEvtFromFilesAndItems([], [item]); - - const files = await fromEvent(evt); - expect(files).toHaveLength(1); - expect(files.every(file => file instanceof File)).toBe(true); - - const [file] = files as FileWithPath[]; - - expect(file.name).toBe(mockFile.name); - expect(file.type).toBe(mockFile.type); - expect(file.size).toBe(mockFile.size); - expect(file.lastModified).toBe(mockFile.lastModified); - expect(file.path).toBe(`./${name}`); +it("should return files from DataTransfer {items} if the passed event is a DragEvent", async () => { + const name = "test.json"; + const mockFile = createFile( + name, + { ping: true }, + { + type: "application/json", + }, + ); + const item = dataTransferItemFromFile(mockFile); + const evt = dragEvtFromFilesAndItems([], [item]); + + const files = await fromEvent(evt); + expect(files).toHaveLength(1); + expect(files.every((file) => file instanceof File)).toBe(true); + + const [file] = files as FileWithPath[]; + + expect(file.name).toBe(mockFile.name); + expect(file.type).toBe(mockFile.type); + expect(file.size).toBe(mockFile.size); + expect(file.lastModified).toBe(mockFile.lastModified); + expect(file.path).toBe(`./${name}`); }); -it('should use the {fullPath} for {path} if {webkitGetAsEntry} is supported and the items are FileSystemFileEntry', async () => { - const name = 'test.json'; - const fullPath = '/testfolder/test.json' - const mockFile = createFile(name, {ping: true}, { - type: 'application/json' - }); - - const file = fileSystemFileEntryFromFile(mockFile); - file.fullPath = fullPath - const item = dataTransferItemFromEntry(file, mockFile); - const evt = dragEvtFromFilesAndItems([], [item]); - - const files = await fromEvent(evt); - expect(files).toHaveLength(1); - expect(files.every(file => file instanceof File)).toBe(true); - - const [f] = files as FileWithPath[]; - - expect(f.name).toBe(mockFile.name); - expect(f.type).toBe(mockFile.type); - expect(f.size).toBe(mockFile.size); - expect(f.lastModified).toBe(mockFile.lastModified); - expect(f.path).toBe(fullPath); +it("should use the {fullPath} for {path} if {webkitGetAsEntry} is supported and the items are FileSystemFileEntry", async () => { + const name = "test.json"; + const fullPath = "/testfolder/test.json"; + const mockFile = createFile( + name, + { ping: true }, + { + type: "application/json", + }, + ); + + const file = fileSystemFileEntryFromFile(mockFile); + file.fullPath = fullPath; + const item = dataTransferItemFromEntry(file, mockFile); + const evt = dragEvtFromFilesAndItems([], [item]); + + const files = await fromEvent(evt); + expect(files).toHaveLength(1); + expect(files.every((file) => file instanceof File)).toBe(true); + + const [f] = files as FileWithPath[]; + + expect(f.name).toBe(mockFile.name); + expect(f.type).toBe(mockFile.type); + expect(f.size).toBe(mockFile.size); + expect(f.lastModified).toBe(mockFile.lastModified); + expect(f.path).toBe(fullPath); }); it('skips DataTransfer {items} that are of kind "string"', async () => { - const name = 'test.json'; - const mockFile = createFile(name, {ping: true}, { - type: 'application/json' - }); - const f = dataTransferItemFromFile(mockFile); - const str = dataTransferItemFromStr('test'); - const evt = dragEvtFromItems([str, f]); - - const files = await fromEvent(evt); - expect(files).toHaveLength(1); - - const [file] = files as FileWithPath[]; - - expect(file.name).toBe(mockFile.name); - expect(file.type).toBe(mockFile.type); - expect(file.size).toBe(mockFile.size); - expect(file.lastModified).toBe(mockFile.lastModified); - expect(file.path).toBe(`./${name}`); + const name = "test.json"; + const mockFile = createFile( + name, + { ping: true }, + { + type: "application/json", + }, + ); + const f = dataTransferItemFromFile(mockFile); + const str = dataTransferItemFromStr("test"); + const evt = dragEvtFromItems([str, f]); + + const files = await fromEvent(evt); + expect(files).toHaveLength(1); + + const [file] = files as FileWithPath[]; + + expect(file.name).toBe(mockFile.name); + expect(file.type).toBe(mockFile.type); + expect(file.size).toBe(mockFile.size); + expect(file.lastModified).toBe(mockFile.lastModified); + expect(file.path).toBe(`./${name}`); }); -it('can read a tree of directories recursively and return a flat list of FileWithPath objects', async () => { - const mockFiles = sortFiles([ - createFile('ping.json', {ping: true}), - createFile('pong.json', {pong: true}), - createFile('foo.json', {foo: true}), - createFile('bar.json', {bar: true}), - createFile('john.json', {john: true}), - createFile('jane.json', {jane: true}) - ]); - const [f1, f2, f3, f4, f5, f6] = mockFiles; - const [f7, f8] = [ - createFile('.DS_Store', {macOs: true}), - createFile('Thumbs.db', {windows: true}) - ]; - const evt = dragEvtFromItems([ - dataTransferItemFromEntry(fileSystemFileEntryFromFile(f1), f1), - dataTransferItemFromEntry(fileSystemFileEntryFromFile(f2), f2), - dataTransferItemFromEntry(fileSystemDirEntryFromFile([ - fileSystemFileEntryFromFile(f3), - fileSystemDirEntryFromFile([ - fileSystemFileEntryFromFile(f4) - ]), - fileSystemFileEntryFromFile(f5) - ], 2)), - dataTransferItemFromEntry(fileSystemFileEntryFromFile(f6), f6), - dataTransferItemFromEntry(fileSystemFileEntryFromFile(f7), f7), - dataTransferItemFromEntry(fileSystemFileEntryFromFile(f8), f8) - ]); - - const items = await fromEvent(evt); - const files = sortFiles(items as FileWithPath[]); - expect(files).toHaveLength(6); - expect(files.every(file => file instanceof File)).toBe(true); - expect(files.every(file => typeof file.path === 'string')).toBe(true); - expect(files).toEqual(mockFiles); +it("can read a tree of directories recursively and return a flat list of FileWithPath objects", async () => { + const mockFiles = sortFiles([ + createFile("ping.json", { ping: true }), + createFile("pong.json", { pong: true }), + createFile("foo.json", { foo: true }), + createFile("bar.json", { bar: true }), + createFile("john.json", { john: true }), + createFile("jane.json", { jane: true }), + ]); + const [f1, f2, f3, f4, f5, f6] = mockFiles; + const [f7, f8] = [ + createFile(".DS_Store", { macOs: true }), + createFile("Thumbs.db", { windows: true }), + ]; + const evt = dragEvtFromItems([ + dataTransferItemFromEntry(fileSystemFileEntryFromFile(f1), f1), + dataTransferItemFromEntry(fileSystemFileEntryFromFile(f2), f2), + dataTransferItemFromEntry( + fileSystemDirEntryFromFile( + [ + fileSystemFileEntryFromFile(f3), + fileSystemDirEntryFromFile([fileSystemFileEntryFromFile(f4)]), + fileSystemFileEntryFromFile(f5), + ], + 2, + ), + ), + dataTransferItemFromEntry(fileSystemFileEntryFromFile(f6), f6), + dataTransferItemFromEntry(fileSystemFileEntryFromFile(f7), f7), + dataTransferItemFromEntry(fileSystemFileEntryFromFile(f8), f8), + ]); + + const items = await fromEvent(evt); + const files = sortFiles(items as FileWithPath[]); + expect(files).toHaveLength(6); + expect(files.every((file) => file instanceof File)).toBe(true); + expect(files.every((file) => typeof file.path === "string")).toBe(true); + expect(files).toEqual(mockFiles); }); it('returns the DataTransfer {items} if the DragEvent {type} is not "drop"', async () => { - const name = 'test.json'; - const mockFile = createFile(name, {ping: true}, { - type: 'application/json' - }); - const item = dataTransferItemFromFile(mockFile); - const evt = dragEvtFromItems(item, 'dragenter'); - - const items = await fromEvent(evt); - expect(items).toHaveLength(1); - - const [itm] = items as DataTransferItem[]; + const name = "test.json"; + const mockFile = createFile( + name, + { ping: true }, + { + type: "application/json", + }, + ); + const item = dataTransferItemFromFile(mockFile); + const evt = dragEvtFromItems(item, "dragenter"); + + const items = await fromEvent(evt); + expect(items).toHaveLength(1); + + const [itm] = items as DataTransferItem[]; + + expect(itm.kind).toBe(item.kind); + expect(itm.kind).toBe("file"); +}); - expect(itm.kind).toBe(item.kind); - expect(itm.kind).toBe('file'); +it('filters DataTransfer {items} if the DragEvent {type} is not "drop" and DataTransferItem {kind} is "string"', async () => { + const name = "test.json"; + const mockFile = createFile( + name, + { ping: true }, + { + type: "application/json", + }, + ); + const file = dataTransferItemFromFile(mockFile); + const str = dataTransferItemFromStr("test"); + const evt = dragEvtFromItems([file, str], "dragenter"); + + const items = await fromEvent(evt); + expect(items).toHaveLength(1); + + const [item] = items as DataTransferItem[]; + + expect(item.kind).toBe(file.kind); + expect(item.kind).toBe("file"); }); -it( - 'filters DataTransfer {items} if the DragEvent {type} is not "drop" and DataTransferItem {kind} is "string"', - async () => { - const name = 'test.json'; - const mockFile = createFile(name, {ping: true}, { - type: 'application/json' - }); - const file = dataTransferItemFromFile(mockFile); - const str = dataTransferItemFromStr('test'); - const evt = dragEvtFromItems([file, str], 'dragenter'); - - const items = await fromEvent(evt); - expect(items).toHaveLength(1); - - const [item] = items as DataTransferItem[]; - - expect(item.kind).toBe(file.kind); - expect(item.kind).toBe('file'); - } -); - -it('filters thumbnail cache files', async () => { - const mockFile = createFile('Thumbs.db', {ping: true}, { - type: 'text/plain' - }); - const evt = dragEvt([mockFile]); - const items = await fromEvent(evt); - expect(items).toHaveLength(0); +it("filters thumbnail cache files", async () => { + const mockFile = createFile( + "Thumbs.db", + { ping: true }, + { + type: "text/plain", + }, + ); + const evt = dragEvt([mockFile]); + const items = await fromEvent(evt); + expect(items).toHaveLength(0); }); -it('should throw if reading dir entries fails', done => { - const mockFiles = sortFiles([ - createFile('ping.json', {ping: true}), - createFile('pong.json', {pong: true}) - ]); - const [f1, f2] = mockFiles; - const evt = dragEvtFromItems([ - dataTransferItemFromEntry(fileSystemDirEntryFromFile([ - fileSystemFileEntryFromFile(f1), - fileSystemFileEntryFromFile(f2) - ], 1, 1)) - ]); - - fromEvent(evt) - .then(() => done.fail('Getting the files should have failed')) - .catch(() => done()); +it("should throw if reading dir entries fails", (done) => { + const mockFiles = sortFiles([ + createFile("ping.json", { ping: true }), + createFile("pong.json", { pong: true }), + ]); + const [f1, f2] = mockFiles; + const evt = dragEvtFromItems([ + dataTransferItemFromEntry( + fileSystemDirEntryFromFile( + [fileSystemFileEntryFromFile(f1), fileSystemFileEntryFromFile(f2)], + 1, + 1, + ), + ), + ]); + + fromEvent(evt) + .then(() => done.fail("Getting the files should have failed")) + .catch(() => done()); }); -it('should throw if reading file entry fails', done => { - const mockFiles = sortFiles([ - createFile('ping.json', {ping: true}), - createFile('pong.json', {pong: true}) - ]); - const [f1, f2] = mockFiles; - const evt = dragEvtFromItems([ - dataTransferItemFromEntry(fileSystemDirEntryFromFile([ - fileSystemFileEntryFromFile(f1), - fileSystemFileEntryFromFile(f2, 'Oops :(') - ], 1, 1)) - ]); - - fromEvent(evt) - .then(() => done.fail('Getting the files should have failed')) - .catch(() => done()); +it("should throw if reading file entry fails", (done) => { + const mockFiles = sortFiles([ + createFile("ping.json", { ping: true }), + createFile("pong.json", { pong: true }), + ]); + const [f1, f2] = mockFiles; + const evt = dragEvtFromItems([ + dataTransferItemFromEntry( + fileSystemDirEntryFromFile( + [ + fileSystemFileEntryFromFile(f1), + fileSystemFileEntryFromFile(f2, "Oops :("), + ], + 1, + 1, + ), + ), + ]); + + fromEvent(evt) + .then(() => done.fail("Getting the files should have failed")) + .catch(() => done()); }); -it('should throw if DataTransferItem is not a File', done => { - const item = dataTransferItem(null, 'file'); - const evt = dragEvtFromFilesAndItems([], [item]); +it("should throw if DataTransferItem is not a File", (done) => { + const item = dataTransferItem(null, "file"); + const evt = dragEvtFromFilesAndItems([], [item]); - fromEvent(evt) - .then(() => done.fail('Getting the files should have failed')) - .catch(() => done()); + fromEvent(evt) + .then(() => done.fail("Getting the files should have failed")) + .catch(() => done()); }); -it('should use getAsFileSystemHandle when available', async () => { - const name = 'test.json'; - const [f, h] = createFileSystemFileHandle(name, {ping: true}, { - type: 'application/json' - }); - const evt = dragEvtFromItems([ - dataTransferItemWithFsHandle(f, h) - ]); - const files = await fromEvent(evt); - expect(files).toHaveLength(1); - expect(files.every(file => file instanceof File)).toBe(true); - - const [file] = files as FileWithPath[]; - - expect(file.name).toBe(f.name); - expect(file.type).toBe(f.type); - expect(file.size).toBe(f.size); - expect(file.lastModified).toBe(f.lastModified); - expect(file.path).toBe(`./${name}`); +it("should use getAsFileSystemHandle when available", async () => { + const name = "test.json"; + const [f, h] = createFileSystemFileHandle( + name, + { ping: true }, + { + type: "application/json", + }, + ); + const evt = dragEvtFromItems([dataTransferItemWithFsHandle(f, h)]); + const files = await fromEvent(evt); + expect(files).toHaveLength(1); + expect(files.every((file) => file instanceof File)).toBe(true); + + const [file] = files as FileWithPath[]; + + expect(file.name).toBe(f.name); + expect(file.type).toBe(f.type); + expect(file.size).toBe(f.size); + expect(file.lastModified).toBe(f.lastModified); + expect(file.path).toBe(`./${name}`); }); -function dragEvtFromItems(items: DataTransferItem | DataTransferItem[], type: string = 'drop'): DragEvent { - return { - type, - dataTransfer: { - items: Array.isArray(items) ? items : [items] - } - } as any; +function dragEvtFromItems( + items: DataTransferItem | DataTransferItem[], + type: string = "drop", +): DragEvent { + return { + type, + dataTransfer: { + items: Array.isArray(items) ? items : [items], + }, + } as any; } -function dragEvt(files?: File[], items?: DataTransferItem[], type: string = 'drop'): DragEvent { - return { - type, - dataTransfer: {items, files} - } as any; +function dragEvt( + files?: File[], + items?: DataTransferItem[], + type: string = "drop", +): DragEvent { + return { + type, + dataTransfer: { items, files }, + } as any; } -function dragEvtFromFilesAndItems(files: File[], items: DataTransferItem[], type: string = 'drop'): DragEvent { - return { - type, - dataTransfer: {files, items} - } as any; +function dragEvtFromFilesAndItems( + files: File[], + items: DataTransferItem[], + type: string = "drop", +): DragEvent { + return { + type, + dataTransfer: { files, items }, + } as any; } function dataTransferItemFromFile(file: File): DataTransferItem { - return { - kind: 'file', - type: file.type, - getAsFile() { - return file; - }, - getAsString() {} - } as any; + return { + kind: "file", + type: file.type, + getAsFile() { + return file; + }, + getAsString() {}, + } as any; } -function dataTransferItem(file?: any, kind?: string, type: string = ''): DataTransferItem { - return { - kind, - type, - getAsFile() { - return file; - } - } as any; +function dataTransferItem( + file?: any, + kind?: string, + type: string = "", +): DataTransferItem { + return { + kind, + type, + getAsFile() { + return file; + }, + } as any; } function dataTransferItemFromStr(str: string): DataTransferItem { - return { - kind: 'string', - type: 'text/plain', - getAsFile() { - return null; - }, - getAsString(cb: (data: string) => void) { - return cb(str); - } - } as any; + return { + kind: "string", + type: "text/plain", + getAsFile() { + return null; + }, + getAsString(cb: (data: string) => void) { + return cb(str); + }, + } as any; } -function dataTransferItemFromEntry(entry: FileEntry | DirEntry, file?: File): DataTransferItem { - return { - kind: 'file', - getAsFile() { - return file; - }, - webkitGetAsEntry: () => { - return entry; - } - } as any; +function dataTransferItemFromEntry( + entry: FileEntry | DirEntry, + file?: File, +): DataTransferItem { + return { + kind: "file", + getAsFile() { + return file; + }, + webkitGetAsEntry: () => { + return entry; + }, + } as any; } -function dataTransferItemWithFsHandle(file?: File, h?: FileSystemFileHandle): DataTransferItem { - return { - kind: 'file', - getAsFile() { - return file; - }, - getAsFileSystemHandle() { - return Promise.resolve(h); - } - } as any; +function dataTransferItemWithFsHandle( + file?: File, + h?: FileSystemFileHandle, +): DataTransferItem { + return { + kind: "file", + getAsFile() { + return file; + }, + getAsFileSystemHandle() { + return Promise.resolve(h); + }, + } as any; } function fileSystemFileEntryFromFile(file: File, err?: any): FileEntry { - return { - isDirectory: false, - isFile: true, - file(cb, errCb) { - if (err) { - errCb(err); - } else { - cb(file); - } - } - }; + return { + isDirectory: false, + isFile: true, + file(cb, errCb) { + if (err) { + errCb(err); + } else { + cb(file); + } + }, + }; } function fileSystemDirEntryFromFile( - files: FileOrDirEntry[], - batchSize: number = 1, - throwAfter: number = 0 + files: FileOrDirEntry[], + batchSize: number = 1, + throwAfter: number = 0, ): DirEntry { - const copy = files.slice(0); - const batches: FileOrDirEntry[][] = []; - - let current = 0; - while (copy.length) { - const length = copy.length; - current += batchSize; - const batch = copy.splice(0, current > length ? length : current); - batches.push(batch); - } - - return { - isDirectory: true, - isFile: false, - createReader: () => { - let cbCount = 0; - - return { - readEntries(cb, errCb) { - const batch = batches[cbCount]; - cbCount++; - - if (throwAfter !== 0 && cbCount === throwAfter) { - errCb('Failed to read files'); - } - - if (batch) { - cb(batch); - } else { - cb([]); - } - } - }; - } - }; + const copy = files.slice(0); + const batches: FileOrDirEntry[][] = []; + + let current = 0; + while (copy.length) { + const length = copy.length; + current += batchSize; + const batch = copy.splice(0, current > length ? length : current); + batches.push(batch); + } + + return { + isDirectory: true, + isFile: false, + createReader: () => { + let cbCount = 0; + + return { + readEntries(cb, errCb) { + const batch = batches[cbCount]; + cbCount++; + + if (throwAfter !== 0 && cbCount === throwAfter) { + errCb("Failed to read files"); + } + + if (batch) { + cb(batch); + } else { + cb([]); + } + }, + }; + }, + }; } function inputEvtFromFiles(...files: File[]): Event { - const input = document.createElement('input'); - if (files.length) { - Object.defineProperty(input, 'files', { - value: files - }); - } - return new Proxy(new CustomEvent('input'), { - get(t, p) { - if (p === 'target') { - return input; - } - return (t as any)[p]; - } + const input = document.createElement("input"); + if (files.length) { + Object.defineProperty(input, "files", { + value: files, }); + } + return new Proxy(new CustomEvent("input"), { + get(t, p) { + if (p === "target") { + return input; + } + return (t as any)[p]; + }, + }); } function createFile(name: string, data: T, options?: FilePropertyBag) { - const json = JSON.stringify(data); - const file = new File([json], name, options); - return file; + const json = JSON.stringify(data); + const file = new File([json], name, options); + return file; } -function createFileSystemFileHandle(name: string, data: T, options?: FilePropertyBag): [File, FileSystemFileHandle] { - const json = JSON.stringify(data); - const file = new File([json], name, options); - return [file, { - getFile() { - return Promise.resolve(file); - } - }]; +function createFileSystemFileHandle( + name: string, + data: T, + options?: FilePropertyBag, +): [File, FileSystemFileHandle] { + const json = JSON.stringify(data); + const file = new File([json], name, options); + return [ + file, + { + getFile() { + return Promise.resolve(file); + }, + }, + ]; } function sortFiles(files: T[]) { - return files.slice(0) - .sort((a, b) => a.name.localeCompare(b.name)); + return files.slice(0).sort((a, b) => a.name.localeCompare(b.name)); } - interface FileSystemFileHandle { - getFile(): Promise; + getFile(): Promise; } -type FileOrDirEntry = FileEntry | DirEntry +type FileOrDirEntry = FileEntry | DirEntry; interface FileEntry extends Entry { - file( - cb: (file: File) => void, - errCb: (err: any) => void - ): void; + file(cb: (file: File) => void, errCb: (err: any) => void): void; } interface DirEntry extends Entry { - createReader(): DirReader; + createReader(): DirReader; } interface Entry { - isDirectory: boolean; - isFile: boolean; - fullPath?: string; + isDirectory: boolean; + isFile: boolean; + fullPath?: string; } interface DirReader { - readEntries( - cb: (entries: FileOrDirEntry[]) => void, - errCb: (err: any) => void - ): void; + readEntries( + cb: (entries: FileOrDirEntry[]) => void, + errCb: (err: any) => void, + ): void; } diff --git a/src/file-selector.ts b/src/file-selector.ts index b51f513..ba0228f 100644 --- a/src/file-selector.ts +++ b/src/file-selector.ts @@ -1,13 +1,11 @@ -import {FileWithPath, toFileWithPath} from './file.js'; - +import { FileWithPath, toFileWithPath } from "./file.js"; const FILES_TO_IGNORE = [ - // Thumbnail cache files for macOS and Windows - '.DS_Store', // macOs - 'Thumbs.db' // Windows + // Thumbnail cache files for macOS and Windows + ".DS_Store", // macOs + "Thumbs.db", // Windows ]; - /** * Convert a DragEvent's DataTrasfer object to a list of File objects * NOTE: If some of the items are folders, @@ -18,61 +16,69 @@ const FILES_TO_IGNORE = [ * * @param evt */ -export async function fromEvent(evt: Event | any): Promise<(FileWithPath | DataTransferItem)[]> { - if (isObject(evt) && isDataTransfer(evt.dataTransfer)) { - return getDataTransferFiles(evt.dataTransfer, evt.type); - } else if (isChangeEvt(evt)) { - return getInputFiles(evt); - } else if (Array.isArray(evt) && evt.every(item => 'getFile' in item && typeof item.getFile === 'function')) { - return getFsHandleFiles(evt) - } - return []; +export async function fromEvent( + evt: Event | any, +): Promise<(FileWithPath | DataTransferItem)[]> { + if (isObject(evt) && isDataTransfer(evt.dataTransfer)) { + return getDataTransferFiles(evt.dataTransfer, evt.type); + } else if (isChangeEvt(evt)) { + return getInputFiles(evt); + } else if ( + Array.isArray(evt) && + evt.every((item) => "getFile" in item && typeof item.getFile === "function") + ) { + return getFsHandleFiles(evt); + } + return []; } function isDataTransfer(value: any): value is DataTransfer { - return isObject(value); + return isObject(value); } function isChangeEvt(value: any): value is Event { - return isObject(value) && isObject(value.target); + return isObject(value) && isObject(value.target); } function isObject(v: any): v is T { - return typeof v === 'object' && v !== null + return typeof v === "object" && v !== null; } function getInputFiles(evt: Event) { - return fromList((evt.target as HTMLInputElement).files).map(file => toFileWithPath(file)); + return fromList((evt.target as HTMLInputElement).files).map( + (file) => toFileWithPath(file), + ); } // Ee expect each handle to be https://developer.mozilla.org/en-US/docs/Web/API/FileSystemFileHandle async function getFsHandleFiles(handles: any[]) { - const files = await Promise.all(handles.map(h => h.getFile())); - return files.map(file => toFileWithPath(file)); + const files = await Promise.all(handles.map((h) => h.getFile())); + return files.map((file) => toFileWithPath(file)); } - async function getDataTransferFiles(dt: DataTransfer, type: string) { - // IE11 does not support dataTransfer.items - // See https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/items#Browser_compatibility - if (dt.items) { - const items = fromList(dt.items) - .filter(item => item.kind === 'file'); - // According to https://html.spec.whatwg.org/multipage/dnd.html#dndevents, - // only 'dragstart' and 'drop' has access to the data (source node) - if (type !== 'drop') { - return items; - } - const files = await Promise.all(items.map(toFilePromises)); - return noIgnoredFiles(flatten(files)); + // IE11 does not support dataTransfer.items + // See https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/items#Browser_compatibility + if (dt.items) { + const items = fromList(dt.items).filter( + (item) => item.kind === "file", + ); + // According to https://html.spec.whatwg.org/multipage/dnd.html#dndevents, + // only 'dragstart' and 'drop' has access to the data (source node) + if (type !== "drop") { + return items; } + const files = await Promise.all(items.map(toFilePromises)); + return noIgnoredFiles(flatten(files)); + } - return noIgnoredFiles(fromList(dt.files) - .map(file => toFileWithPath(file))); + return noIgnoredFiles( + fromList(dt.files).map((file) => toFileWithPath(file)), + ); } function noIgnoredFiles(files: FileWithPath[]) { - return files.filter(file => FILES_TO_IGNORE.indexOf(file.name) === -1); + return files.filter((file) => FILES_TO_IGNORE.indexOf(file.name) === -1); } // IE11 does not support Array.from() @@ -80,116 +86,126 @@ function noIgnoredFiles(files: FileWithPath[]) { // https://developer.mozilla.org/en-US/docs/Web/API/FileList // https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItemList function fromList(items: DataTransferItemList | FileList | null): T[] { - if (items === null) { - return []; - } + if (items === null) { + return []; + } - const files = []; + const files = []; - for (let i = 0; i < items.length; i++) { - const file = items[i]; - files.push(file); - } + for (let i = 0; i < items.length; i++) { + const file = items[i]; + files.push(file); + } - return files as any; + return files as any; } // https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem function toFilePromises(item: DataTransferItem) { - if (typeof item.webkitGetAsEntry !== 'function') { - return fromDataTransferItem(item); - } + if (typeof item.webkitGetAsEntry !== "function") { + return fromDataTransferItem(item); + } - const entry = item.webkitGetAsEntry(); + const entry = item.webkitGetAsEntry(); - // Safari supports dropping an image node from a different window and can be retrieved using - // the DataTransferItem.getAsFile() API - // NOTE: FileSystemEntry.file() throws if trying to get the file - if (entry && entry.isDirectory) { - return fromDirEntry(entry) as any; - } + // Safari supports dropping an image node from a different window and can be retrieved using + // the DataTransferItem.getAsFile() API + // NOTE: FileSystemEntry.file() throws if trying to get the file + if (entry && entry.isDirectory) { + return fromDirEntry(entry) as any; + } - return fromDataTransferItem(item, entry); + return fromDataTransferItem(item, entry); } function flatten(items: any[]): T[] { - return items.reduce((acc, files) => [ - ...acc, - ...(Array.isArray(files) ? flatten(files) : [files]) - ], []); + return items.reduce( + (acc, files) => [ + ...acc, + ...(Array.isArray(files) ? flatten(files) : [files]), + ], + [], + ); } -function fromDataTransferItem(item: DataTransferItem, entry?: FileSystemEntry | null) { - if (typeof (item as any).getAsFileSystemHandle === 'function') { - return (item as any).getAsFileSystemHandle() - .then(async (h: any) => { - const file = await h.getFile(); - file.handle = h; - return toFileWithPath(file); - }); - } - const file = item.getAsFile(); - if (!file) { - return Promise.reject(`${item} is not a File`); - } - const fwp = toFileWithPath(file, entry?.fullPath ?? undefined); - return Promise.resolve(fwp); +function fromDataTransferItem( + item: DataTransferItem, + entry?: FileSystemEntry | null, +) { + if (typeof (item as any).getAsFileSystemHandle === "function") { + return (item as any).getAsFileSystemHandle().then(async (h: any) => { + const file = await h.getFile(); + file.handle = h; + return toFileWithPath(file); + }); + } + const file = item.getAsFile(); + if (!file) { + return Promise.reject(`${item} is not a File`); + } + const fwp = toFileWithPath(file, entry?.fullPath ?? undefined); + return Promise.resolve(fwp); } // https://developer.mozilla.org/en-US/docs/Web/API/FileSystemEntry async function fromEntry(entry: any) { - return entry.isDirectory ? fromDirEntry(entry) : fromFileEntry(entry); + return entry.isDirectory ? fromDirEntry(entry) : fromFileEntry(entry); } // https://developer.mozilla.org/en-US/docs/Web/API/FileSystemDirectoryEntry function fromDirEntry(entry: any) { - const reader = entry.createReader(); - - return new Promise((resolve, reject) => { - const entries: Promise[] = []; - - function readEntries() { - // https://developer.mozilla.org/en-US/docs/Web/API/FileSystemDirectoryEntry/createReader - // https://developer.mozilla.org/en-US/docs/Web/API/FileSystemDirectoryReader/readEntries - reader.readEntries(async (batch: any[]) => { - if (!batch.length) { - // Done reading directory - try { - const files = await Promise.all(entries); - resolve(files); - } catch (err) { - reject(err); - } - } else { - const items = Promise.all(batch.map(fromEntry)); - entries.push(items); - - // Continue reading - readEntries(); - } - }, (err: any) => { - reject(err); - }); - } - - readEntries(); - }); + const reader = entry.createReader(); + + return new Promise((resolve, reject) => { + const entries: Promise[] = []; + + function readEntries() { + // https://developer.mozilla.org/en-US/docs/Web/API/FileSystemDirectoryEntry/createReader + // https://developer.mozilla.org/en-US/docs/Web/API/FileSystemDirectoryReader/readEntries + reader.readEntries( + async (batch: any[]) => { + if (!batch.length) { + // Done reading directory + try { + const files = await Promise.all(entries); + resolve(files); + } catch (err) { + reject(err); + } + } else { + const items = Promise.all(batch.map(fromEntry)); + entries.push(items); + + // Continue reading + readEntries(); + } + }, + (err: any) => { + reject(err); + }, + ); + } + + readEntries(); + }); } // https://developer.mozilla.org/en-US/docs/Web/API/FileSystemFileEntry async function fromFileEntry(entry: any) { - return new Promise((resolve, reject) => { - entry.file((file: FileWithPath) => { - const fwp = toFileWithPath(file, entry.fullPath); - resolve(fwp); - }, (err: any) => { - reject(err); - }); - }); + return new Promise((resolve, reject) => { + entry.file( + (file: FileWithPath) => { + const fwp = toFileWithPath(file, entry.fullPath); + resolve(fwp); + }, + (err: any) => { + reject(err); + }, + ); + }); } // Infinite type recursion // https://github.com/Microsoft/TypeScript/issues/3496#issuecomment-128553540 interface FileArray extends Array {} -type FileValue = FileWithPath - | FileArray[]; +type FileValue = FileWithPath | FileArray[]; diff --git a/src/file.spec.ts b/src/file.spec.ts index 51be2dd..a4081c7 100644 --- a/src/file.spec.ts +++ b/src/file.spec.ts @@ -1,196 +1,195 @@ -import {COMMON_MIME_TYPES, toFileWithPath} from './file.js'; - -describe('toFile()', () => { - it('should be an instance of a File', () => { - const file = new File([], 'test.json'); - const fileWithPath = toFileWithPath(file); - expect(fileWithPath).toBeInstanceOf(File); - }); - - it('has all the File options', () => { - const type = 'application/json'; - const opts: FilePropertyBag = {type}; - const file = new File([], 'test.json', opts); - const fileWithPath = toFileWithPath(file); - expect(fileWithPath.type).toBe(type); - }); - - it('does not overwrite {path} if it exists', () => { - const fullPath = '/Users/test/Desktop/test/test.json'; - const path = '/test/test.json'; - const file = new File([], 'test.json'); - // @ts-expect-error: This is set only in the case of an electron app - file.path = fullPath; - const fileWithPath = toFileWithPath(file, path); - expect(fileWithPath.path).toBe(fullPath); - }); - - it('sets the {path} if provided', () => { - const path = '/test/test.json'; - const file = new File([], 'test.json'); - const fileWithPath = toFileWithPath(file, path); - expect(fileWithPath.path).toBe(path); - }); - - test('{path} is enumerable', () => { - const path = '/test/test.json'; - const file = new File([], 'test.json'); - const fileWithPath = toFileWithPath(file, path); - - expect(Object.keys(fileWithPath)).toContain('path'); - - const keys: string[] = []; - for (const key in fileWithPath) { - keys.push(key); - } - - expect(keys).toContain('path'); - }); - - it('uses the File {name} as {path} if not provided', () => { - const name = 'test.json'; - const file = new File([], name); - const fileWithPath = toFileWithPath(file); - expect(fileWithPath.path).toBe(`./${name}`); - }); - - it('uses the File {webkitRelativePath} as {path} if it exists', () => { - const name = 'test.json'; - const path = 'test/test.json'; - const file = new File([], name); - Object.defineProperty(file, 'webkitRelativePath', { - value: path - }); - const fileWithPath = toFileWithPath(file); - expect(fileWithPath.path).toBe(path); - }); - - it('sets the {relativePath} if provided without overwriting {path}', () => { - const fullPath = '/Users/test/Desktop/test/test.json'; - const path = '/test/test.json'; - const file = new File([], 'test.json'); - - // @ts-expect-error: This is set only in the case of an electron app - file.path = fullPath; - const fileWithPath = toFileWithPath(file, path); - expect(fileWithPath.path).toBe(fullPath); - expect(fileWithPath.relativePath).toBe(path); - }); - - test('{relativePath} is enumerable', () => { - const path = '/test/test.json'; - const file = new File([], 'test.json'); - const fileWithPath = toFileWithPath(file, path); - - expect(Object.keys(fileWithPath)).toContain('relativePath'); - - const keys: string[] = []; - for (const key in fileWithPath) { - keys.push(key); - } - - expect(keys).toContain('relativePath'); - }); - - it('uses the File {webkitRelativePath} as {relativePath} if it exists', () => { - const name = 'test.json'; - const path = 'test/test.json'; - const file = new File([], name); - Object.defineProperty(file, 'webkitRelativePath', { - value: path - }); - const fileWithPath = toFileWithPath(file); - expect(fileWithPath.relativePath).toBe(path); - }); - - it('sets the {type} from extension', () => { - const types = Array.from(COMMON_MIME_TYPES.values()); - const files = Array.from(COMMON_MIME_TYPES.keys()) - .map(ext => new File([], `test.${ext}`)) - .map(f => toFileWithPath(f)); - - for (const file of files) { - expect(types.includes(file.type)).toBe(true); - } - }); - - test('{type} is enumerable', () => { - const file = new File([], 'test.gif'); - const fileWithPath = toFileWithPath(file); +import { COMMON_MIME_TYPES, toFileWithPath } from "./file.js"; + +describe("toFile()", () => { + it("should be an instance of a File", () => { + const file = new File([], "test.json"); + const fileWithPath = toFileWithPath(file); + expect(fileWithPath).toBeInstanceOf(File); + }); + + it("has all the File options", () => { + const type = "application/json"; + const opts: FilePropertyBag = { type }; + const file = new File([], "test.json", opts); + const fileWithPath = toFileWithPath(file); + expect(fileWithPath.type).toBe(type); + }); + + it("does not overwrite {path} if it exists", () => { + const fullPath = "/Users/test/Desktop/test/test.json"; + const path = "/test/test.json"; + const file = new File([], "test.json"); + // @ts-expect-error: This is set only in the case of an electron app + file.path = fullPath; + const fileWithPath = toFileWithPath(file, path); + expect(fileWithPath.path).toBe(fullPath); + }); + + it("sets the {path} if provided", () => { + const path = "/test/test.json"; + const file = new File([], "test.json"); + const fileWithPath = toFileWithPath(file, path); + expect(fileWithPath.path).toBe(path); + }); + + test("{path} is enumerable", () => { + const path = "/test/test.json"; + const file = new File([], "test.json"); + const fileWithPath = toFileWithPath(file, path); + + expect(Object.keys(fileWithPath)).toContain("path"); + + const keys: string[] = []; + for (const key in fileWithPath) { + keys.push(key); + } - expect(Object.keys(fileWithPath)).toContain('type'); + expect(keys).toContain("path"); + }); + + it("uses the File {name} as {path} if not provided", () => { + const name = "test.json"; + const file = new File([], name); + const fileWithPath = toFileWithPath(file); + expect(fileWithPath.path).toBe(`./${name}`); + }); + + it("uses the File {webkitRelativePath} as {path} if it exists", () => { + const name = "test.json"; + const path = "test/test.json"; + const file = new File([], name); + Object.defineProperty(file, "webkitRelativePath", { + value: path, + }); + const fileWithPath = toFileWithPath(file); + expect(fileWithPath.path).toBe(path); + }); + + it("sets the {relativePath} if provided without overwriting {path}", () => { + const fullPath = "/Users/test/Desktop/test/test.json"; + const path = "/test/test.json"; + const file = new File([], "test.json"); + + // @ts-expect-error: This is set only in the case of an electron app + file.path = fullPath; + const fileWithPath = toFileWithPath(file, path); + expect(fileWithPath.path).toBe(fullPath); + expect(fileWithPath.relativePath).toBe(path); + }); + + test("{relativePath} is enumerable", () => { + const path = "/test/test.json"; + const file = new File([], "test.json"); + const fileWithPath = toFileWithPath(file, path); + + expect(Object.keys(fileWithPath)).toContain("relativePath"); + + const keys: string[] = []; + for (const key in fileWithPath) { + keys.push(key); + } - const keys: string[] = []; - for (const key in fileWithPath) { - keys.push(key); - } + expect(keys).toContain("relativePath"); + }); - expect(keys).toContain('type'); + it("uses the File {webkitRelativePath} as {relativePath} if it exists", () => { + const name = "test.json"; + const path = "test/test.json"; + const file = new File([], name); + Object.defineProperty(file, "webkitRelativePath", { + value: path, }); + const fileWithPath = toFileWithPath(file); + expect(fileWithPath.relativePath).toBe(path); + }); - it('sets the {type} from extension regardless of case', () => { - const types = Array.from(COMMON_MIME_TYPES.values()); - const files = Array.from(COMMON_MIME_TYPES.keys()) - .map(key => key.toUpperCase()) - .map(ext => new File([], `test.${ext}`)) - .map(f => toFileWithPath(f)); + it("sets the {type} from extension", () => { + const types = Array.from(COMMON_MIME_TYPES.values()); + const files = Array.from(COMMON_MIME_TYPES.keys()) + .map((ext) => new File([], `test.${ext}`)) + .map((f) => toFileWithPath(f)); - for (const file of files) { - expect(types.includes(file.type)).toBe(true); - } - }); + for (const file of files) { + expect(types.includes(file.type)).toBe(true); + } + }); - it('should behave like a File', done => { - const data = {ping: true}; - const json = JSON.stringify(data); - const file = new File([json], 'test.json'); - const fileWithPath = toFileWithPath(file); - - const reader = new FileReader(); - reader.onload = evt => { - try { - const d = JSON.parse(evt.target?.result as string); - expect(d).toEqual(data); - done(); - } catch (e) { - done.fail(e as Error); - } - }; - - reader.readAsText(fileWithPath); - }); + test("{type} is enumerable", () => { + const file = new File([], "test.gif"); + const fileWithPath = toFileWithPath(file); - it('sets the {handle} if provided', () => { - const path = '/test/test.json'; - const file = new File([], 'test.json'); - const fileWithHandle = toFileWithPath(file, path, fsHandleFromFile(file)); - expect(fileWithHandle.handle).toBeDefined(); - expect(fileWithHandle.handle?.name).toEqual(file.name); - }); + expect(Object.keys(fileWithPath)).toContain("type"); - test('{handle} is enumerable', () => { - const path = '/test/test.json'; - const file = new File([], 'test.json'); - const fileWithHandle = toFileWithPath(file, path, fsHandleFromFile(file)); + const keys: string[] = []; + for (const key in fileWithPath) { + keys.push(key); + } - expect(Object.keys(fileWithHandle)).toContain('handle'); + expect(keys).toContain("type"); + }); - const keys: string[] = []; - for (const key in fileWithHandle) { - keys.push(key); - } + it("sets the {type} from extension regardless of case", () => { + const types = Array.from(COMMON_MIME_TYPES.values()); + const files = Array.from(COMMON_MIME_TYPES.keys()) + .map((key) => key.toUpperCase()) + .map((ext) => new File([], `test.${ext}`)) + .map((f) => toFileWithPath(f)); - expect(keys).toContain('handle'); - }); + for (const file of files) { + expect(types.includes(file.type)).toBe(true); + } + }); + + it("should behave like a File", (done) => { + const data = { ping: true }; + const json = JSON.stringify(data); + const file = new File([json], "test.json"); + const fileWithPath = toFileWithPath(file); + + const reader = new FileReader(); + reader.onload = (evt) => { + try { + const d = JSON.parse(evt.target?.result as string); + expect(d).toEqual(data); + done(); + } catch (e) { + done.fail(e as Error); + } + }; + + reader.readAsText(fileWithPath); + }); + + it("sets the {handle} if provided", () => { + const path = "/test/test.json"; + const file = new File([], "test.json"); + const fileWithHandle = toFileWithPath(file, path, fsHandleFromFile(file)); + expect(fileWithHandle.handle).toBeDefined(); + expect(fileWithHandle.handle?.name).toEqual(file.name); + }); + + test("{handle} is enumerable", () => { + const path = "/test/test.json"; + const file = new File([], "test.json"); + const fileWithHandle = toFileWithPath(file, path, fsHandleFromFile(file)); + + expect(Object.keys(fileWithHandle)).toContain("handle"); + + const keys: string[] = []; + for (const key in fileWithHandle) { + keys.push(key); + } + expect(keys).toContain("handle"); + }); }); function fsHandleFromFile(f: File): FileSystemHandle { - return { - kind: 'file', - name: f.name, - isSameEntry() { - return Promise.resolve(false) - } - } + return { + kind: "file", + name: f.name, + isSameEntry() { + return Promise.resolve(false); + }, + }; } diff --git a/src/file.ts b/src/file.ts index c33a201..7b4d8d1 100644 --- a/src/file.ts +++ b/src/file.ts @@ -1,1267 +1,1292 @@ export const COMMON_MIME_TYPES = new Map([ - // https://github.com/guzzle/psr7/blob/2d9260799e713f1c475d3c5fdc3d6561ff7441b2/src/MimeType.php - ['1km', 'application/vnd.1000minds.decision-model+xml'], - ['3dml', 'text/vnd.in3d.3dml'], - ['3ds', 'image/x-3ds'], - ['3g2', 'video/3gpp2'], - ['3gp', 'video/3gp'], - ['3gpp', 'video/3gpp'], - ['3mf', 'model/3mf'], - ['7z', 'application/x-7z-compressed'], - ['7zip', 'application/x-7z-compressed'], - ['123', 'application/vnd.lotus-1-2-3'], - ['aab', 'application/x-authorware-bin'], - ['aac', 'audio/x-acc'], - ['aam', 'application/x-authorware-map'], - ['aas', 'application/x-authorware-seg'], - ['abw', 'application/x-abiword'], - ['ac', 'application/vnd.nokia.n-gage.ac+xml'], - ['ac3', 'audio/ac3'], - ['acc', 'application/vnd.americandynamics.acc'], - ['ace', 'application/x-ace-compressed'], - ['acu', 'application/vnd.acucobol'], - ['acutc', 'application/vnd.acucorp'], - ['adp', 'audio/adpcm'], - ['aep', 'application/vnd.audiograph'], - ['afm', 'application/x-font-type1'], - ['afp', 'application/vnd.ibm.modcap'], - ['ahead', 'application/vnd.ahead.space'], - ['ai', 'application/pdf'], - ['aif', 'audio/x-aiff'], - ['aifc', 'audio/x-aiff'], - ['aiff', 'audio/x-aiff'], - ['air', 'application/vnd.adobe.air-application-installer-package+zip'], - ['ait', 'application/vnd.dvb.ait'], - ['ami', 'application/vnd.amiga.ami'], - ['amr', 'audio/amr'], - ['apk', 'application/vnd.android.package-archive'], - ['apng', 'image/apng'], - ['appcache', 'text/cache-manifest'], - ['application', 'application/x-ms-application'], - ['apr', 'application/vnd.lotus-approach'], - ['arc', 'application/x-freearc'], - ['arj', 'application/x-arj'], - ['asc', 'application/pgp-signature'], - ['asf', 'video/x-ms-asf'], - ['asm', 'text/x-asm'], - ['aso', 'application/vnd.accpac.simply.aso'], - ['asx', 'video/x-ms-asf'], - ['atc', 'application/vnd.acucorp'], - ['atom', 'application/atom+xml'], - ['atomcat', 'application/atomcat+xml'], - ['atomdeleted', 'application/atomdeleted+xml'], - ['atomsvc', 'application/atomsvc+xml'], - ['atx', 'application/vnd.antix.game-component'], - ['au', 'audio/x-au'], - ['avi', 'video/x-msvideo'], - ['avif', 'image/avif'], - ['aw', 'application/applixware'], - ['azf', 'application/vnd.airzip.filesecure.azf'], - ['azs', 'application/vnd.airzip.filesecure.azs'], - ['azv', 'image/vnd.airzip.accelerator.azv'], - ['azw', 'application/vnd.amazon.ebook'], - ['b16', 'image/vnd.pco.b16'], - ['bat', 'application/x-msdownload'], - ['bcpio', 'application/x-bcpio'], - ['bdf', 'application/x-font-bdf'], - ['bdm', 'application/vnd.syncml.dm+wbxml'], - ['bdoc', 'application/x-bdoc'], - ['bed', 'application/vnd.realvnc.bed'], - ['bh2', 'application/vnd.fujitsu.oasysprs'], - ['bin', 'application/octet-stream'], - ['blb', 'application/x-blorb'], - ['blorb', 'application/x-blorb'], - ['bmi', 'application/vnd.bmi'], - ['bmml', 'application/vnd.balsamiq.bmml+xml'], - ['bmp', 'image/bmp'], - ['book', 'application/vnd.framemaker'], - ['box', 'application/vnd.previewsystems.box'], - ['boz', 'application/x-bzip2'], - ['bpk', 'application/octet-stream'], - ['bpmn', 'application/octet-stream'], - ['bsp', 'model/vnd.valve.source.compiled-map'], - ['btif', 'image/prs.btif'], - ['buffer', 'application/octet-stream'], - ['bz', 'application/x-bzip'], - ['bz2', 'application/x-bzip2'], - ['c', 'text/x-c'], - ['c4d', 'application/vnd.clonk.c4group'], - ['c4f', 'application/vnd.clonk.c4group'], - ['c4g', 'application/vnd.clonk.c4group'], - ['c4p', 'application/vnd.clonk.c4group'], - ['c4u', 'application/vnd.clonk.c4group'], - ['c11amc', 'application/vnd.cluetrust.cartomobile-config'], - ['c11amz', 'application/vnd.cluetrust.cartomobile-config-pkg'], - ['cab', 'application/vnd.ms-cab-compressed'], - ['caf', 'audio/x-caf'], - ['cap', 'application/vnd.tcpdump.pcap'], - ['car', 'application/vnd.curl.car'], - ['cat', 'application/vnd.ms-pki.seccat'], - ['cb7', 'application/x-cbr'], - ['cba', 'application/x-cbr'], - ['cbr', 'application/x-cbr'], - ['cbt', 'application/x-cbr'], - ['cbz', 'application/x-cbr'], - ['cc', 'text/x-c'], - ['cco', 'application/x-cocoa'], - ['cct', 'application/x-director'], - ['ccxml', 'application/ccxml+xml'], - ['cdbcmsg', 'application/vnd.contact.cmsg'], - ['cda', 'application/x-cdf'], - ['cdf', 'application/x-netcdf'], - ['cdfx', 'application/cdfx+xml'], - ['cdkey', 'application/vnd.mediastation.cdkey'], - ['cdmia', 'application/cdmi-capability'], - ['cdmic', 'application/cdmi-container'], - ['cdmid', 'application/cdmi-domain'], - ['cdmio', 'application/cdmi-object'], - ['cdmiq', 'application/cdmi-queue'], - ['cdr', 'application/cdr'], - ['cdx', 'chemical/x-cdx'], - ['cdxml', 'application/vnd.chemdraw+xml'], - ['cdy', 'application/vnd.cinderella'], - ['cer', 'application/pkix-cert'], - ['cfs', 'application/x-cfs-compressed'], - ['cgm', 'image/cgm'], - ['chat', 'application/x-chat'], - ['chm', 'application/vnd.ms-htmlhelp'], - ['chrt', 'application/vnd.kde.kchart'], - ['cif', 'chemical/x-cif'], - ['cii', 'application/vnd.anser-web-certificate-issue-initiation'], - ['cil', 'application/vnd.ms-artgalry'], - ['cjs', 'application/node'], - ['cla', 'application/vnd.claymore'], - ['class', 'application/octet-stream'], - ['clkk', 'application/vnd.crick.clicker.keyboard'], - ['clkp', 'application/vnd.crick.clicker.palette'], - ['clkt', 'application/vnd.crick.clicker.template'], - ['clkw', 'application/vnd.crick.clicker.wordbank'], - ['clkx', 'application/vnd.crick.clicker'], - ['clp', 'application/x-msclip'], - ['cmc', 'application/vnd.cosmocaller'], - ['cmdf', 'chemical/x-cmdf'], - ['cml', 'chemical/x-cml'], - ['cmp', 'application/vnd.yellowriver-custom-menu'], - ['cmx', 'image/x-cmx'], - ['cod', 'application/vnd.rim.cod'], - ['coffee', 'text/coffeescript'], - ['com', 'application/x-msdownload'], - ['conf', 'text/plain'], - ['cpio', 'application/x-cpio'], - ['cpp', 'text/x-c'], - ['cpt', 'application/mac-compactpro'], - ['crd', 'application/x-mscardfile'], - ['crl', 'application/pkix-crl'], - ['crt', 'application/x-x509-ca-cert'], - ['crx', 'application/x-chrome-extension'], - ['cryptonote', 'application/vnd.rig.cryptonote'], - ['csh', 'application/x-csh'], - ['csl', 'application/vnd.citationstyles.style+xml'], - ['csml', 'chemical/x-csml'], - ['csp', 'application/vnd.commonspace'], - ['csr', 'application/octet-stream'], - ['css', 'text/css'], - ['cst', 'application/x-director'], - ['csv', 'text/csv'], - ['cu', 'application/cu-seeme'], - ['curl', 'text/vnd.curl'], - ['cww', 'application/prs.cww'], - ['cxt', 'application/x-director'], - ['cxx', 'text/x-c'], - ['dae', 'model/vnd.collada+xml'], - ['daf', 'application/vnd.mobius.daf'], - ['dart', 'application/vnd.dart'], - ['dataless', 'application/vnd.fdsn.seed'], - ['davmount', 'application/davmount+xml'], - ['dbf', 'application/vnd.dbf'], - ['dbk', 'application/docbook+xml'], - ['dcr', 'application/x-director'], - ['dcurl', 'text/vnd.curl.dcurl'], - ['dd2', 'application/vnd.oma.dd2+xml'], - ['ddd', 'application/vnd.fujixerox.ddd'], - ['ddf', 'application/vnd.syncml.dmddf+xml'], - ['dds', 'image/vnd.ms-dds'], - ['deb', 'application/x-debian-package'], - ['def', 'text/plain'], - ['deploy', 'application/octet-stream'], - ['der', 'application/x-x509-ca-cert'], - ['dfac', 'application/vnd.dreamfactory'], - ['dgc', 'application/x-dgc-compressed'], - ['dic', 'text/x-c'], - ['dir', 'application/x-director'], - ['dis', 'application/vnd.mobius.dis'], - ['disposition-notification', 'message/disposition-notification'], - ['dist', 'application/octet-stream'], - ['distz', 'application/octet-stream'], - ['djv', 'image/vnd.djvu'], - ['djvu', 'image/vnd.djvu'], - ['dll', 'application/octet-stream'], - ['dmg', 'application/x-apple-diskimage'], - ['dmn', 'application/octet-stream'], - ['dmp', 'application/vnd.tcpdump.pcap'], - ['dms', 'application/octet-stream'], - ['dna', 'application/vnd.dna'], - ['doc', 'application/msword'], - ['docm', 'application/vnd.ms-word.template.macroEnabled.12'], - ['docx', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'], - ['dot', 'application/msword'], - ['dotm', 'application/vnd.ms-word.template.macroEnabled.12'], - ['dotx', 'application/vnd.openxmlformats-officedocument.wordprocessingml.template'], - ['dp', 'application/vnd.osgi.dp'], - ['dpg', 'application/vnd.dpgraph'], - ['dra', 'audio/vnd.dra'], - ['drle', 'image/dicom-rle'], - ['dsc', 'text/prs.lines.tag'], - ['dssc', 'application/dssc+der'], - ['dtb', 'application/x-dtbook+xml'], - ['dtd', 'application/xml-dtd'], - ['dts', 'audio/vnd.dts'], - ['dtshd', 'audio/vnd.dts.hd'], - ['dump', 'application/octet-stream'], - ['dvb', 'video/vnd.dvb.file'], - ['dvi', 'application/x-dvi'], - ['dwd', 'application/atsc-dwd+xml'], - ['dwf', 'model/vnd.dwf'], - ['dwg', 'image/vnd.dwg'], - ['dxf', 'image/vnd.dxf'], - ['dxp', 'application/vnd.spotfire.dxp'], - ['dxr', 'application/x-director'], - ['ear', 'application/java-archive'], - ['ecelp4800', 'audio/vnd.nuera.ecelp4800'], - ['ecelp7470', 'audio/vnd.nuera.ecelp7470'], - ['ecelp9600', 'audio/vnd.nuera.ecelp9600'], - ['ecma', 'application/ecmascript'], - ['edm', 'application/vnd.novadigm.edm'], - ['edx', 'application/vnd.novadigm.edx'], - ['efif', 'application/vnd.picsel'], - ['ei6', 'application/vnd.pg.osasli'], - ['elc', 'application/octet-stream'], - ['emf', 'image/emf'], - ['eml', 'message/rfc822'], - ['emma', 'application/emma+xml'], - ['emotionml', 'application/emotionml+xml'], - ['emz', 'application/x-msmetafile'], - ['eol', 'audio/vnd.digital-winds'], - ['eot', 'application/vnd.ms-fontobject'], - ['eps', 'application/postscript'], - ['epub', 'application/epub+zip'], - ['es', 'application/ecmascript'], - ['es3', 'application/vnd.eszigno3+xml'], - ['esa', 'application/vnd.osgi.subsystem'], - ['esf', 'application/vnd.epson.esf'], - ['et3', 'application/vnd.eszigno3+xml'], - ['etx', 'text/x-setext'], - ['eva', 'application/x-eva'], - ['evy', 'application/x-envoy'], - ['exe', 'application/octet-stream'], - ['exi', 'application/exi'], - ['exp', 'application/express'], - ['exr', 'image/aces'], - ['ext', 'application/vnd.novadigm.ext'], - ['ez', 'application/andrew-inset'], - ['ez2', 'application/vnd.ezpix-album'], - ['ez3', 'application/vnd.ezpix-package'], - ['f', 'text/x-fortran'], - ['f4v', 'video/mp4'], - ['f77', 'text/x-fortran'], - ['f90', 'text/x-fortran'], - ['fbs', 'image/vnd.fastbidsheet'], - ['fcdt', 'application/vnd.adobe.formscentral.fcdt'], - ['fcs', 'application/vnd.isac.fcs'], - ['fdf', 'application/vnd.fdf'], - ['fdt', 'application/fdt+xml'], - ['fe_launch', 'application/vnd.denovo.fcselayout-link'], - ['fg5', 'application/vnd.fujitsu.oasysgp'], - ['fgd', 'application/x-director'], - ['fh', 'image/x-freehand'], - ['fh4', 'image/x-freehand'], - ['fh5', 'image/x-freehand'], - ['fh7', 'image/x-freehand'], - ['fhc', 'image/x-freehand'], - ['fig', 'application/x-xfig'], - ['fits', 'image/fits'], - ['flac', 'audio/x-flac'], - ['fli', 'video/x-fli'], - ['flo', 'application/vnd.micrografx.flo'], - ['flv', 'video/x-flv'], - ['flw', 'application/vnd.kde.kivio'], - ['flx', 'text/vnd.fmi.flexstor'], - ['fly', 'text/vnd.fly'], - ['fm', 'application/vnd.framemaker'], - ['fnc', 'application/vnd.frogans.fnc'], - ['fo', 'application/vnd.software602.filler.form+xml'], - ['for', 'text/x-fortran'], - ['fpx', 'image/vnd.fpx'], - ['frame', 'application/vnd.framemaker'], - ['fsc', 'application/vnd.fsc.weblaunch'], - ['fst', 'image/vnd.fst'], - ['ftc', 'application/vnd.fluxtime.clip'], - ['fti', 'application/vnd.anser-web-funds-transfer-initiation'], - ['fvt', 'video/vnd.fvt'], - ['fxp', 'application/vnd.adobe.fxp'], - ['fxpl', 'application/vnd.adobe.fxp'], - ['fzs', 'application/vnd.fuzzysheet'], - ['g2w', 'application/vnd.geoplan'], - ['g3', 'image/g3fax'], - ['g3w', 'application/vnd.geospace'], - ['gac', 'application/vnd.groove-account'], - ['gam', 'application/x-tads'], - ['gbr', 'application/rpki-ghostbusters'], - ['gca', 'application/x-gca-compressed'], - ['gdl', 'model/vnd.gdl'], - ['gdoc', 'application/vnd.google-apps.document'], - ['geo', 'application/vnd.dynageo'], - ['geojson', 'application/geo+json'], - ['gex', 'application/vnd.geometry-explorer'], - ['ggb', 'application/vnd.geogebra.file'], - ['ggt', 'application/vnd.geogebra.tool'], - ['ghf', 'application/vnd.groove-help'], - ['gif', 'image/gif'], - ['gim', 'application/vnd.groove-identity-message'], - ['glb', 'model/gltf-binary'], - ['gltf', 'model/gltf+json'], - ['gml', 'application/gml+xml'], - ['gmx', 'application/vnd.gmx'], - ['gnumeric', 'application/x-gnumeric'], - ['gpg', 'application/gpg-keys'], - ['gph', 'application/vnd.flographit'], - ['gpx', 'application/gpx+xml'], - ['gqf', 'application/vnd.grafeq'], - ['gqs', 'application/vnd.grafeq'], - ['gram', 'application/srgs'], - ['gramps', 'application/x-gramps-xml'], - ['gre', 'application/vnd.geometry-explorer'], - ['grv', 'application/vnd.groove-injector'], - ['grxml', 'application/srgs+xml'], - ['gsf', 'application/x-font-ghostscript'], - ['gsheet', 'application/vnd.google-apps.spreadsheet'], - ['gslides', 'application/vnd.google-apps.presentation'], - ['gtar', 'application/x-gtar'], - ['gtm', 'application/vnd.groove-tool-message'], - ['gtw', 'model/vnd.gtw'], - ['gv', 'text/vnd.graphviz'], - ['gxf', 'application/gxf'], - ['gxt', 'application/vnd.geonext'], - ['gz', 'application/gzip'], - ['gzip', 'application/gzip'], - ['h', 'text/x-c'], - ['h261', 'video/h261'], - ['h263', 'video/h263'], - ['h264', 'video/h264'], - ['hal', 'application/vnd.hal+xml'], - ['hbci', 'application/vnd.hbci'], - ['hbs', 'text/x-handlebars-template'], - ['hdd', 'application/x-virtualbox-hdd'], - ['hdf', 'application/x-hdf'], - ['heic', 'image/heic'], - ['heics', 'image/heic-sequence'], - ['heif', 'image/heif'], - ['heifs', 'image/heif-sequence'], - ['hej2', 'image/hej2k'], - ['held', 'application/atsc-held+xml'], - ['hh', 'text/x-c'], - ['hjson', 'application/hjson'], - ['hlp', 'application/winhlp'], - ['hpgl', 'application/vnd.hp-hpgl'], - ['hpid', 'application/vnd.hp-hpid'], - ['hps', 'application/vnd.hp-hps'], - ['hqx', 'application/mac-binhex40'], - ['hsj2', 'image/hsj2'], - ['htc', 'text/x-component'], - ['htke', 'application/vnd.kenameaapp'], - ['htm', 'text/html'], - ['html', 'text/html'], - ['hvd', 'application/vnd.yamaha.hv-dic'], - ['hvp', 'application/vnd.yamaha.hv-voice'], - ['hvs', 'application/vnd.yamaha.hv-script'], - ['i2g', 'application/vnd.intergeo'], - ['icc', 'application/vnd.iccprofile'], - ['ice', 'x-conference/x-cooltalk'], - ['icm', 'application/vnd.iccprofile'], - ['ico', 'image/x-icon'], - ['ics', 'text/calendar'], - ['ief', 'image/ief'], - ['ifb', 'text/calendar'], - ['ifm', 'application/vnd.shana.informed.formdata'], - ['iges', 'model/iges'], - ['igl', 'application/vnd.igloader'], - ['igm', 'application/vnd.insors.igm'], - ['igs', 'model/iges'], - ['igx', 'application/vnd.micrografx.igx'], - ['iif', 'application/vnd.shana.informed.interchange'], - ['img', 'application/octet-stream'], - ['imp', 'application/vnd.accpac.simply.imp'], - ['ims', 'application/vnd.ms-ims'], - ['in', 'text/plain'], - ['ini', 'text/plain'], - ['ink', 'application/inkml+xml'], - ['inkml', 'application/inkml+xml'], - ['install', 'application/x-install-instructions'], - ['iota', 'application/vnd.astraea-software.iota'], - ['ipfix', 'application/ipfix'], - ['ipk', 'application/vnd.shana.informed.package'], - ['irm', 'application/vnd.ibm.rights-management'], - ['irp', 'application/vnd.irepository.package+xml'], - ['iso', 'application/x-iso9660-image'], - ['itp', 'application/vnd.shana.informed.formtemplate'], - ['its', 'application/its+xml'], - ['ivp', 'application/vnd.immervision-ivp'], - ['ivu', 'application/vnd.immervision-ivu'], - ['jad', 'text/vnd.sun.j2me.app-descriptor'], - ['jade', 'text/jade'], - ['jam', 'application/vnd.jam'], - ['jar', 'application/java-archive'], - ['jardiff', 'application/x-java-archive-diff'], - ['java', 'text/x-java-source'], - ['jhc', 'image/jphc'], - ['jisp', 'application/vnd.jisp'], - ['jls', 'image/jls'], - ['jlt', 'application/vnd.hp-jlyt'], - ['jng', 'image/x-jng'], - ['jnlp', 'application/x-java-jnlp-file'], - ['joda', 'application/vnd.joost.joda-archive'], - ['jp2', 'image/jp2'], - ['jpe', 'image/jpeg'], - ['jpeg', 'image/jpeg'], - ['jpf', 'image/jpx'], - ['jpg', 'image/jpeg'], - ['jpg2', 'image/jp2'], - ['jpgm', 'video/jpm'], - ['jpgv', 'video/jpeg'], - ['jph', 'image/jph'], - ['jpm', 'video/jpm'], - ['jpx', 'image/jpx'], - ['js', 'application/javascript'], - ['json', 'application/json'], - ['json5', 'application/json5'], - ['jsonld', 'application/ld+json'], - // https://jsonlines.org/ - ['jsonl', 'application/jsonl'], - ['jsonml', 'application/jsonml+json'], - ['jsx', 'text/jsx'], - ['jxr', 'image/jxr'], - ['jxra', 'image/jxra'], - ['jxrs', 'image/jxrs'], - ['jxs', 'image/jxs'], - ['jxsc', 'image/jxsc'], - ['jxsi', 'image/jxsi'], - ['jxss', 'image/jxss'], - ['kar', 'audio/midi'], - ['karbon', 'application/vnd.kde.karbon'], - ['kdb', 'application/octet-stream'], - ['kdbx', 'application/x-keepass2'], - ['key', 'application/x-iwork-keynote-sffkey'], - ['kfo', 'application/vnd.kde.kformula'], - ['kia', 'application/vnd.kidspiration'], - ['kml', 'application/vnd.google-earth.kml+xml'], - ['kmz', 'application/vnd.google-earth.kmz'], - ['kne', 'application/vnd.kinar'], - ['knp', 'application/vnd.kinar'], - ['kon', 'application/vnd.kde.kontour'], - ['kpr', 'application/vnd.kde.kpresenter'], - ['kpt', 'application/vnd.kde.kpresenter'], - ['kpxx', 'application/vnd.ds-keypoint'], - ['ksp', 'application/vnd.kde.kspread'], - ['ktr', 'application/vnd.kahootz'], - ['ktx', 'image/ktx'], - ['ktx2', 'image/ktx2'], - ['ktz', 'application/vnd.kahootz'], - ['kwd', 'application/vnd.kde.kword'], - ['kwt', 'application/vnd.kde.kword'], - ['lasxml', 'application/vnd.las.las+xml'], - ['latex', 'application/x-latex'], - ['lbd', 'application/vnd.llamagraphics.life-balance.desktop'], - ['lbe', 'application/vnd.llamagraphics.life-balance.exchange+xml'], - ['les', 'application/vnd.hhe.lesson-player'], - ['less', 'text/less'], - ['lgr', 'application/lgr+xml'], - ['lha', 'application/octet-stream'], - ['link66', 'application/vnd.route66.link66+xml'], - ['list', 'text/plain'], - ['list3820', 'application/vnd.ibm.modcap'], - ['listafp', 'application/vnd.ibm.modcap'], - ['litcoffee', 'text/coffeescript'], - ['lnk', 'application/x-ms-shortcut'], - ['log', 'text/plain'], - ['lostxml', 'application/lost+xml'], - ['lrf', 'application/octet-stream'], - ['lrm', 'application/vnd.ms-lrm'], - ['ltf', 'application/vnd.frogans.ltf'], - ['lua', 'text/x-lua'], - ['luac', 'application/x-lua-bytecode'], - ['lvp', 'audio/vnd.lucent.voice'], - ['lwp', 'application/vnd.lotus-wordpro'], - ['lzh', 'application/octet-stream'], - ['m1v', 'video/mpeg'], - ['m2a', 'audio/mpeg'], - ['m2v', 'video/mpeg'], - ['m3a', 'audio/mpeg'], - ['m3u', 'text/plain'], - ['m3u8', 'application/vnd.apple.mpegurl'], - ['m4a', 'audio/x-m4a'], - ['m4p', 'application/mp4'], - ['m4s', 'video/iso.segment'], - ['m4u', 'application/vnd.mpegurl'], - ['m4v', 'video/x-m4v'], - ['m13', 'application/x-msmediaview'], - ['m14', 'application/x-msmediaview'], - ['m21', 'application/mp21'], - ['ma', 'application/mathematica'], - ['mads', 'application/mads+xml'], - ['maei', 'application/mmt-aei+xml'], - ['mag', 'application/vnd.ecowin.chart'], - ['maker', 'application/vnd.framemaker'], - ['man', 'text/troff'], - ['manifest', 'text/cache-manifest'], - ['map', 'application/json'], - ['mar', 'application/octet-stream'], - ['markdown', 'text/markdown'], - ['mathml', 'application/mathml+xml'], - ['mb', 'application/mathematica'], - ['mbk', 'application/vnd.mobius.mbk'], - ['mbox', 'application/mbox'], - ['mc1', 'application/vnd.medcalcdata'], - ['mcd', 'application/vnd.mcd'], - ['mcurl', 'text/vnd.curl.mcurl'], - ['md', 'text/markdown'], - ['mdb', 'application/x-msaccess'], - ['mdi', 'image/vnd.ms-modi'], - ['mdx', 'text/mdx'], - ['me', 'text/troff'], - ['mesh', 'model/mesh'], - ['meta4', 'application/metalink4+xml'], - ['metalink', 'application/metalink+xml'], - ['mets', 'application/mets+xml'], - ['mfm', 'application/vnd.mfmp'], - ['mft', 'application/rpki-manifest'], - ['mgp', 'application/vnd.osgeo.mapguide.package'], - ['mgz', 'application/vnd.proteus.magazine'], - ['mid', 'audio/midi'], - ['midi', 'audio/midi'], - ['mie', 'application/x-mie'], - ['mif', 'application/vnd.mif'], - ['mime', 'message/rfc822'], - ['mj2', 'video/mj2'], - ['mjp2', 'video/mj2'], - ['mjs', 'application/javascript'], - ['mk3d', 'video/x-matroska'], - ['mka', 'audio/x-matroska'], - ['mkd', 'text/x-markdown'], - ['mks', 'video/x-matroska'], - ['mkv', 'video/x-matroska'], - ['mlp', 'application/vnd.dolby.mlp'], - ['mmd', 'application/vnd.chipnuts.karaoke-mmd'], - ['mmf', 'application/vnd.smaf'], - ['mml', 'text/mathml'], - ['mmr', 'image/vnd.fujixerox.edmics-mmr'], - ['mng', 'video/x-mng'], - ['mny', 'application/x-msmoney'], - ['mobi', 'application/x-mobipocket-ebook'], - ['mods', 'application/mods+xml'], - ['mov', 'video/quicktime'], - ['movie', 'video/x-sgi-movie'], - ['mp2', 'audio/mpeg'], - ['mp2a', 'audio/mpeg'], - ['mp3', 'audio/mpeg'], - ['mp4', 'video/mp4'], - ['mp4a', 'audio/mp4'], - ['mp4s', 'application/mp4'], - ['mp4v', 'video/mp4'], - ['mp21', 'application/mp21'], - ['mpc', 'application/vnd.mophun.certificate'], - ['mpd', 'application/dash+xml'], - ['mpe', 'video/mpeg'], - ['mpeg', 'video/mpeg'], - ['mpg', 'video/mpeg'], - ['mpg4', 'video/mp4'], - ['mpga', 'audio/mpeg'], - ['mpkg', 'application/vnd.apple.installer+xml'], - ['mpm', 'application/vnd.blueice.multipass'], - ['mpn', 'application/vnd.mophun.application'], - ['mpp', 'application/vnd.ms-project'], - ['mpt', 'application/vnd.ms-project'], - ['mpy', 'application/vnd.ibm.minipay'], - ['mqy', 'application/vnd.mobius.mqy'], - ['mrc', 'application/marc'], - ['mrcx', 'application/marcxml+xml'], - ['ms', 'text/troff'], - ['mscml', 'application/mediaservercontrol+xml'], - ['mseed', 'application/vnd.fdsn.mseed'], - ['mseq', 'application/vnd.mseq'], - ['msf', 'application/vnd.epson.msf'], - ['msg', 'application/vnd.ms-outlook'], - ['msh', 'model/mesh'], - ['msi', 'application/x-msdownload'], - ['msl', 'application/vnd.mobius.msl'], - ['msm', 'application/octet-stream'], - ['msp', 'application/octet-stream'], - ['msty', 'application/vnd.muvee.style'], - ['mtl', 'model/mtl'], - ['mts', 'model/vnd.mts'], - ['mus', 'application/vnd.musician'], - ['musd', 'application/mmt-usd+xml'], - ['musicxml', 'application/vnd.recordare.musicxml+xml'], - ['mvb', 'application/x-msmediaview'], - ['mvt', 'application/vnd.mapbox-vector-tile'], - ['mwf', 'application/vnd.mfer'], - ['mxf', 'application/mxf'], - ['mxl', 'application/vnd.recordare.musicxml'], - ['mxmf', 'audio/mobile-xmf'], - ['mxml', 'application/xv+xml'], - ['mxs', 'application/vnd.triscape.mxs'], - ['mxu', 'video/vnd.mpegurl'], - ['n-gage', 'application/vnd.nokia.n-gage.symbian.install'], - ['n3', 'text/n3'], - ['nb', 'application/mathematica'], - ['nbp', 'application/vnd.wolfram.player'], - ['nc', 'application/x-netcdf'], - ['ncx', 'application/x-dtbncx+xml'], - ['nfo', 'text/x-nfo'], - ['ngdat', 'application/vnd.nokia.n-gage.data'], - ['nitf', 'application/vnd.nitf'], - ['nlu', 'application/vnd.neurolanguage.nlu'], - ['nml', 'application/vnd.enliven'], - ['nnd', 'application/vnd.noblenet-directory'], - ['nns', 'application/vnd.noblenet-sealer'], - ['nnw', 'application/vnd.noblenet-web'], - ['npx', 'image/vnd.net-fpx'], - ['nq', 'application/n-quads'], - ['nsc', 'application/x-conference'], - ['nsf', 'application/vnd.lotus-notes'], - ['nt', 'application/n-triples'], - ['ntf', 'application/vnd.nitf'], - ['numbers', 'application/x-iwork-numbers-sffnumbers'], - ['nzb', 'application/x-nzb'], - ['oa2', 'application/vnd.fujitsu.oasys2'], - ['oa3', 'application/vnd.fujitsu.oasys3'], - ['oas', 'application/vnd.fujitsu.oasys'], - ['obd', 'application/x-msbinder'], - ['obgx', 'application/vnd.openblox.game+xml'], - ['obj', 'model/obj'], - ['oda', 'application/oda'], - ['odb', 'application/vnd.oasis.opendocument.database'], - ['odc', 'application/vnd.oasis.opendocument.chart'], - ['odf', 'application/vnd.oasis.opendocument.formula'], - ['odft', 'application/vnd.oasis.opendocument.formula-template'], - ['odg', 'application/vnd.oasis.opendocument.graphics'], - ['odi', 'application/vnd.oasis.opendocument.image'], - ['odm', 'application/vnd.oasis.opendocument.text-master'], - ['odp', 'application/vnd.oasis.opendocument.presentation'], - ['ods', 'application/vnd.oasis.opendocument.spreadsheet'], - ['odt', 'application/vnd.oasis.opendocument.text'], - ['oga', 'audio/ogg'], - ['ogex', 'model/vnd.opengex'], - ['ogg', 'audio/ogg'], - ['ogv', 'video/ogg'], - ['ogx', 'application/ogg'], - ['omdoc', 'application/omdoc+xml'], - ['onepkg', 'application/onenote'], - ['onetmp', 'application/onenote'], - ['onetoc', 'application/onenote'], - ['onetoc2', 'application/onenote'], - ['opf', 'application/oebps-package+xml'], - ['opml', 'text/x-opml'], - ['oprc', 'application/vnd.palm'], - ['opus', 'audio/ogg'], - ['org', 'text/x-org'], - ['osf', 'application/vnd.yamaha.openscoreformat'], - ['osfpvg', 'application/vnd.yamaha.openscoreformat.osfpvg+xml'], - ['osm', 'application/vnd.openstreetmap.data+xml'], - ['otc', 'application/vnd.oasis.opendocument.chart-template'], - ['otf', 'font/otf'], - ['otg', 'application/vnd.oasis.opendocument.graphics-template'], - ['oth', 'application/vnd.oasis.opendocument.text-web'], - ['oti', 'application/vnd.oasis.opendocument.image-template'], - ['otp', 'application/vnd.oasis.opendocument.presentation-template'], - ['ots', 'application/vnd.oasis.opendocument.spreadsheet-template'], - ['ott', 'application/vnd.oasis.opendocument.text-template'], - ['ova', 'application/x-virtualbox-ova'], - ['ovf', 'application/x-virtualbox-ovf'], - ['owl', 'application/rdf+xml'], - ['oxps', 'application/oxps'], - ['oxt', 'application/vnd.openofficeorg.extension'], - ['p', 'text/x-pascal'], - ['p7a', 'application/x-pkcs7-signature'], - ['p7b', 'application/x-pkcs7-certificates'], - ['p7c', 'application/pkcs7-mime'], - ['p7m', 'application/pkcs7-mime'], - ['p7r', 'application/x-pkcs7-certreqresp'], - ['p7s', 'application/pkcs7-signature'], - ['p8', 'application/pkcs8'], - ['p10', 'application/x-pkcs10'], - ['p12', 'application/x-pkcs12'], - ['pac', 'application/x-ns-proxy-autoconfig'], - ['pages', 'application/x-iwork-pages-sffpages'], - ['pas', 'text/x-pascal'], - ['paw', 'application/vnd.pawaafile'], - ['pbd', 'application/vnd.powerbuilder6'], - ['pbm', 'image/x-portable-bitmap'], - ['pcap', 'application/vnd.tcpdump.pcap'], - ['pcf', 'application/x-font-pcf'], - ['pcl', 'application/vnd.hp-pcl'], - ['pclxl', 'application/vnd.hp-pclxl'], - ['pct', 'image/x-pict'], - ['pcurl', 'application/vnd.curl.pcurl'], - ['pcx', 'image/x-pcx'], - ['pdb', 'application/x-pilot'], - ['pde', 'text/x-processing'], - ['pdf', 'application/pdf'], - ['pem', 'application/x-x509-user-cert'], - ['pfa', 'application/x-font-type1'], - ['pfb', 'application/x-font-type1'], - ['pfm', 'application/x-font-type1'], - ['pfr', 'application/font-tdpfr'], - ['pfx', 'application/x-pkcs12'], - ['pgm', 'image/x-portable-graymap'], - ['pgn', 'application/x-chess-pgn'], - ['pgp', 'application/pgp'], - ['php', 'application/x-httpd-php'], - ['php3', 'application/x-httpd-php'], - ['php4', 'application/x-httpd-php'], - ['phps', 'application/x-httpd-php-source'], - ['phtml', 'application/x-httpd-php'], - ['pic', 'image/x-pict'], - ['pkg', 'application/octet-stream'], - ['pki', 'application/pkixcmp'], - ['pkipath', 'application/pkix-pkipath'], - ['pkpass', 'application/vnd.apple.pkpass'], - ['pl', 'application/x-perl'], - ['plb', 'application/vnd.3gpp.pic-bw-large'], - ['plc', 'application/vnd.mobius.plc'], - ['plf', 'application/vnd.pocketlearn'], - ['pls', 'application/pls+xml'], - ['pm', 'application/x-perl'], - ['pml', 'application/vnd.ctc-posml'], - ['png', 'image/png'], - ['pnm', 'image/x-portable-anymap'], - ['portpkg', 'application/vnd.macports.portpkg'], - ['pot', 'application/vnd.ms-powerpoint'], - ['potm', 'application/vnd.ms-powerpoint.presentation.macroEnabled.12'], - ['potx', 'application/vnd.openxmlformats-officedocument.presentationml.template'], - ['ppa', 'application/vnd.ms-powerpoint'], - ['ppam', 'application/vnd.ms-powerpoint.addin.macroEnabled.12'], - ['ppd', 'application/vnd.cups-ppd'], - ['ppm', 'image/x-portable-pixmap'], - ['pps', 'application/vnd.ms-powerpoint'], - ['ppsm', 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12'], - ['ppsx', 'application/vnd.openxmlformats-officedocument.presentationml.slideshow'], - ['ppt', 'application/powerpoint'], - ['pptm', 'application/vnd.ms-powerpoint.presentation.macroEnabled.12'], - ['pptx', 'application/vnd.openxmlformats-officedocument.presentationml.presentation'], - ['pqa', 'application/vnd.palm'], - ['prc', 'application/x-pilot'], - ['pre', 'application/vnd.lotus-freelance'], - ['prf', 'application/pics-rules'], - ['provx', 'application/provenance+xml'], - ['ps', 'application/postscript'], - ['psb', 'application/vnd.3gpp.pic-bw-small'], - ['psd', 'application/x-photoshop'], - ['psf', 'application/x-font-linux-psf'], - ['pskcxml', 'application/pskc+xml'], - ['pti', 'image/prs.pti'], - ['ptid', 'application/vnd.pvi.ptid1'], - ['pub', 'application/x-mspublisher'], - ['pvb', 'application/vnd.3gpp.pic-bw-var'], - ['pwn', 'application/vnd.3m.post-it-notes'], - ['pya', 'audio/vnd.ms-playready.media.pya'], - ['pyv', 'video/vnd.ms-playready.media.pyv'], - ['qam', 'application/vnd.epson.quickanime'], - ['qbo', 'application/vnd.intu.qbo'], - ['qfx', 'application/vnd.intu.qfx'], - ['qps', 'application/vnd.publishare-delta-tree'], - ['qt', 'video/quicktime'], - ['qwd', 'application/vnd.quark.quarkxpress'], - ['qwt', 'application/vnd.quark.quarkxpress'], - ['qxb', 'application/vnd.quark.quarkxpress'], - ['qxd', 'application/vnd.quark.quarkxpress'], - ['qxl', 'application/vnd.quark.quarkxpress'], - ['qxt', 'application/vnd.quark.quarkxpress'], - ['ra', 'audio/x-realaudio'], - ['ram', 'audio/x-pn-realaudio'], - ['raml', 'application/raml+yaml'], - ['rapd', 'application/route-apd+xml'], - ['rar', 'application/x-rar'], - ['ras', 'image/x-cmu-raster'], - ['rcprofile', 'application/vnd.ipunplugged.rcprofile'], - ['rdf', 'application/rdf+xml'], - ['rdz', 'application/vnd.data-vision.rdz'], - ['relo', 'application/p2p-overlay+xml'], - ['rep', 'application/vnd.businessobjects'], - ['res', 'application/x-dtbresource+xml'], - ['rgb', 'image/x-rgb'], - ['rif', 'application/reginfo+xml'], - ['rip', 'audio/vnd.rip'], - ['ris', 'application/x-research-info-systems'], - ['rl', 'application/resource-lists+xml'], - ['rlc', 'image/vnd.fujixerox.edmics-rlc'], - ['rld', 'application/resource-lists-diff+xml'], - ['rm', 'audio/x-pn-realaudio'], - ['rmi', 'audio/midi'], - ['rmp', 'audio/x-pn-realaudio-plugin'], - ['rms', 'application/vnd.jcp.javame.midlet-rms'], - ['rmvb', 'application/vnd.rn-realmedia-vbr'], - ['rnc', 'application/relax-ng-compact-syntax'], - ['rng', 'application/xml'], - ['roa', 'application/rpki-roa'], - ['roff', 'text/troff'], - ['rp9', 'application/vnd.cloanto.rp9'], - ['rpm', 'audio/x-pn-realaudio-plugin'], - ['rpss', 'application/vnd.nokia.radio-presets'], - ['rpst', 'application/vnd.nokia.radio-preset'], - ['rq', 'application/sparql-query'], - ['rs', 'application/rls-services+xml'], - ['rsa', 'application/x-pkcs7'], - ['rsat', 'application/atsc-rsat+xml'], - ['rsd', 'application/rsd+xml'], - ['rsheet', 'application/urc-ressheet+xml'], - ['rss', 'application/rss+xml'], - ['rtf', 'text/rtf'], - ['rtx', 'text/richtext'], - ['run', 'application/x-makeself'], - ['rusd', 'application/route-usd+xml'], - ['rv', 'video/vnd.rn-realvideo'], - ['s', 'text/x-asm'], - ['s3m', 'audio/s3m'], - ['saf', 'application/vnd.yamaha.smaf-audio'], - ['sass', 'text/x-sass'], - ['sbml', 'application/sbml+xml'], - ['sc', 'application/vnd.ibm.secure-container'], - ['scd', 'application/x-msschedule'], - ['scm', 'application/vnd.lotus-screencam'], - ['scq', 'application/scvp-cv-request'], - ['scs', 'application/scvp-cv-response'], - ['scss', 'text/x-scss'], - ['scurl', 'text/vnd.curl.scurl'], - ['sda', 'application/vnd.stardivision.draw'], - ['sdc', 'application/vnd.stardivision.calc'], - ['sdd', 'application/vnd.stardivision.impress'], - ['sdkd', 'application/vnd.solent.sdkm+xml'], - ['sdkm', 'application/vnd.solent.sdkm+xml'], - ['sdp', 'application/sdp'], - ['sdw', 'application/vnd.stardivision.writer'], - ['sea', 'application/octet-stream'], - ['see', 'application/vnd.seemail'], - ['seed', 'application/vnd.fdsn.seed'], - ['sema', 'application/vnd.sema'], - ['semd', 'application/vnd.semd'], - ['semf', 'application/vnd.semf'], - ['senmlx', 'application/senml+xml'], - ['sensmlx', 'application/sensml+xml'], - ['ser', 'application/java-serialized-object'], - ['setpay', 'application/set-payment-initiation'], - ['setreg', 'application/set-registration-initiation'], - ['sfd-hdstx', 'application/vnd.hydrostatix.sof-data'], - ['sfs', 'application/vnd.spotfire.sfs'], - ['sfv', 'text/x-sfv'], - ['sgi', 'image/sgi'], - ['sgl', 'application/vnd.stardivision.writer-global'], - ['sgm', 'text/sgml'], - ['sgml', 'text/sgml'], - ['sh', 'application/x-sh'], - ['shar', 'application/x-shar'], - ['shex', 'text/shex'], - ['shf', 'application/shf+xml'], - ['shtml', 'text/html'], - ['sid', 'image/x-mrsid-image'], - ['sieve', 'application/sieve'], - ['sig', 'application/pgp-signature'], - ['sil', 'audio/silk'], - ['silo', 'model/mesh'], - ['sis', 'application/vnd.symbian.install'], - ['sisx', 'application/vnd.symbian.install'], - ['sit', 'application/x-stuffit'], - ['sitx', 'application/x-stuffitx'], - ['siv', 'application/sieve'], - ['skd', 'application/vnd.koan'], - ['skm', 'application/vnd.koan'], - ['skp', 'application/vnd.koan'], - ['skt', 'application/vnd.koan'], - ['sldm', 'application/vnd.ms-powerpoint.slide.macroenabled.12'], - ['sldx', 'application/vnd.openxmlformats-officedocument.presentationml.slide'], - ['slim', 'text/slim'], - ['slm', 'text/slim'], - ['sls', 'application/route-s-tsid+xml'], - ['slt', 'application/vnd.epson.salt'], - ['sm', 'application/vnd.stepmania.stepchart'], - ['smf', 'application/vnd.stardivision.math'], - ['smi', 'application/smil'], - ['smil', 'application/smil'], - ['smv', 'video/x-smv'], - ['smzip', 'application/vnd.stepmania.package'], - ['snd', 'audio/basic'], - ['snf', 'application/x-font-snf'], - ['so', 'application/octet-stream'], - ['spc', 'application/x-pkcs7-certificates'], - ['spdx', 'text/spdx'], - ['spf', 'application/vnd.yamaha.smaf-phrase'], - ['spl', 'application/x-futuresplash'], - ['spot', 'text/vnd.in3d.spot'], - ['spp', 'application/scvp-vp-response'], - ['spq', 'application/scvp-vp-request'], - ['spx', 'audio/ogg'], - ['sql', 'application/x-sql'], - ['src', 'application/x-wais-source'], - ['srt', 'application/x-subrip'], - ['sru', 'application/sru+xml'], - ['srx', 'application/sparql-results+xml'], - ['ssdl', 'application/ssdl+xml'], - ['sse', 'application/vnd.kodak-descriptor'], - ['ssf', 'application/vnd.epson.ssf'], - ['ssml', 'application/ssml+xml'], - ['sst', 'application/octet-stream'], - ['st', 'application/vnd.sailingtracker.track'], - ['stc', 'application/vnd.sun.xml.calc.template'], - ['std', 'application/vnd.sun.xml.draw.template'], - ['stf', 'application/vnd.wt.stf'], - ['sti', 'application/vnd.sun.xml.impress.template'], - ['stk', 'application/hyperstudio'], - ['stl', 'model/stl'], - ['stpx', 'model/step+xml'], - ['stpxz', 'model/step-xml+zip'], - ['stpz', 'model/step+zip'], - ['str', 'application/vnd.pg.format'], - ['stw', 'application/vnd.sun.xml.writer.template'], - ['styl', 'text/stylus'], - ['stylus', 'text/stylus'], - ['sub', 'text/vnd.dvb.subtitle'], - ['sus', 'application/vnd.sus-calendar'], - ['susp', 'application/vnd.sus-calendar'], - ['sv4cpio', 'application/x-sv4cpio'], - ['sv4crc', 'application/x-sv4crc'], - ['svc', 'application/vnd.dvb.service'], - ['svd', 'application/vnd.svd'], - ['svg', 'image/svg+xml'], - ['svgz', 'image/svg+xml'], - ['swa', 'application/x-director'], - ['swf', 'application/x-shockwave-flash'], - ['swi', 'application/vnd.aristanetworks.swi'], - ['swidtag', 'application/swid+xml'], - ['sxc', 'application/vnd.sun.xml.calc'], - ['sxd', 'application/vnd.sun.xml.draw'], - ['sxg', 'application/vnd.sun.xml.writer.global'], - ['sxi', 'application/vnd.sun.xml.impress'], - ['sxm', 'application/vnd.sun.xml.math'], - ['sxw', 'application/vnd.sun.xml.writer'], - ['t', 'text/troff'], - ['t3', 'application/x-t3vm-image'], - ['t38', 'image/t38'], - ['taglet', 'application/vnd.mynfc'], - ['tao', 'application/vnd.tao.intent-module-archive'], - ['tap', 'image/vnd.tencent.tap'], - ['tar', 'application/x-tar'], - ['tcap', 'application/vnd.3gpp2.tcap'], - ['tcl', 'application/x-tcl'], - ['td', 'application/urc-targetdesc+xml'], - ['teacher', 'application/vnd.smart.teacher'], - ['tei', 'application/tei+xml'], - ['teicorpus', 'application/tei+xml'], - ['tex', 'application/x-tex'], - ['texi', 'application/x-texinfo'], - ['texinfo', 'application/x-texinfo'], - ['text', 'text/plain'], - ['tfi', 'application/thraud+xml'], - ['tfm', 'application/x-tex-tfm'], - ['tfx', 'image/tiff-fx'], - ['tga', 'image/x-tga'], - ['tgz', 'application/x-tar'], - ['thmx', 'application/vnd.ms-officetheme'], - ['tif', 'image/tiff'], - ['tiff', 'image/tiff'], - ['tk', 'application/x-tcl'], - ['tmo', 'application/vnd.tmobile-livetv'], - ['toml', 'application/toml'], - ['torrent', 'application/x-bittorrent'], - ['tpl', 'application/vnd.groove-tool-template'], - ['tpt', 'application/vnd.trid.tpt'], - ['tr', 'text/troff'], - ['tra', 'application/vnd.trueapp'], - ['trig', 'application/trig'], - ['trm', 'application/x-msterminal'], - ['ts', 'video/mp2t'], - ['tsd', 'application/timestamped-data'], - ['tsv', 'text/tab-separated-values'], - ['ttc', 'font/collection'], - ['ttf', 'font/ttf'], - ['ttl', 'text/turtle'], - ['ttml', 'application/ttml+xml'], - ['twd', 'application/vnd.simtech-mindmapper'], - ['twds', 'application/vnd.simtech-mindmapper'], - ['txd', 'application/vnd.genomatix.tuxedo'], - ['txf', 'application/vnd.mobius.txf'], - ['txt', 'text/plain'], - ['u8dsn', 'message/global-delivery-status'], - ['u8hdr', 'message/global-headers'], - ['u8mdn', 'message/global-disposition-notification'], - ['u8msg', 'message/global'], - ['u32', 'application/x-authorware-bin'], - ['ubj', 'application/ubjson'], - ['udeb', 'application/x-debian-package'], - ['ufd', 'application/vnd.ufdl'], - ['ufdl', 'application/vnd.ufdl'], - ['ulx', 'application/x-glulx'], - ['umj', 'application/vnd.umajin'], - ['unityweb', 'application/vnd.unity'], - ['uoml', 'application/vnd.uoml+xml'], - ['uri', 'text/uri-list'], - ['uris', 'text/uri-list'], - ['urls', 'text/uri-list'], - ['usdz', 'model/vnd.usdz+zip'], - ['ustar', 'application/x-ustar'], - ['utz', 'application/vnd.uiq.theme'], - ['uu', 'text/x-uuencode'], - ['uva', 'audio/vnd.dece.audio'], - ['uvd', 'application/vnd.dece.data'], - ['uvf', 'application/vnd.dece.data'], - ['uvg', 'image/vnd.dece.graphic'], - ['uvh', 'video/vnd.dece.hd'], - ['uvi', 'image/vnd.dece.graphic'], - ['uvm', 'video/vnd.dece.mobile'], - ['uvp', 'video/vnd.dece.pd'], - ['uvs', 'video/vnd.dece.sd'], - ['uvt', 'application/vnd.dece.ttml+xml'], - ['uvu', 'video/vnd.uvvu.mp4'], - ['uvv', 'video/vnd.dece.video'], - ['uvva', 'audio/vnd.dece.audio'], - ['uvvd', 'application/vnd.dece.data'], - ['uvvf', 'application/vnd.dece.data'], - ['uvvg', 'image/vnd.dece.graphic'], - ['uvvh', 'video/vnd.dece.hd'], - ['uvvi', 'image/vnd.dece.graphic'], - ['uvvm', 'video/vnd.dece.mobile'], - ['uvvp', 'video/vnd.dece.pd'], - ['uvvs', 'video/vnd.dece.sd'], - ['uvvt', 'application/vnd.dece.ttml+xml'], - ['uvvu', 'video/vnd.uvvu.mp4'], - ['uvvv', 'video/vnd.dece.video'], - ['uvvx', 'application/vnd.dece.unspecified'], - ['uvvz', 'application/vnd.dece.zip'], - ['uvx', 'application/vnd.dece.unspecified'], - ['uvz', 'application/vnd.dece.zip'], - ['vbox', 'application/x-virtualbox-vbox'], - ['vbox-extpack', 'application/x-virtualbox-vbox-extpack'], - ['vcard', 'text/vcard'], - ['vcd', 'application/x-cdlink'], - ['vcf', 'text/x-vcard'], - ['vcg', 'application/vnd.groove-vcard'], - ['vcs', 'text/x-vcalendar'], - ['vcx', 'application/vnd.vcx'], - ['vdi', 'application/x-virtualbox-vdi'], - ['vds', 'model/vnd.sap.vds'], - ['vhd', 'application/x-virtualbox-vhd'], - ['vis', 'application/vnd.visionary'], - ['viv', 'video/vnd.vivo'], - ['vlc', 'application/videolan'], - ['vmdk', 'application/x-virtualbox-vmdk'], - ['vob', 'video/x-ms-vob'], - ['vor', 'application/vnd.stardivision.writer'], - ['vox', 'application/x-authorware-bin'], - ['vrml', 'model/vrml'], - ['vsd', 'application/vnd.visio'], - ['vsf', 'application/vnd.vsf'], - ['vss', 'application/vnd.visio'], - ['vst', 'application/vnd.visio'], - ['vsw', 'application/vnd.visio'], - ['vtf', 'image/vnd.valve.source.texture'], - ['vtt', 'text/vtt'], - ['vtu', 'model/vnd.vtu'], - ['vxml', 'application/voicexml+xml'], - ['w3d', 'application/x-director'], - ['wad', 'application/x-doom'], - ['wadl', 'application/vnd.sun.wadl+xml'], - ['war', 'application/java-archive'], - ['wasm', 'application/wasm'], - ['wav', 'audio/x-wav'], - ['wax', 'audio/x-ms-wax'], - ['wbmp', 'image/vnd.wap.wbmp'], - ['wbs', 'application/vnd.criticaltools.wbs+xml'], - ['wbxml', 'application/wbxml'], - ['wcm', 'application/vnd.ms-works'], - ['wdb', 'application/vnd.ms-works'], - ['wdp', 'image/vnd.ms-photo'], - ['weba', 'audio/webm'], - ['webapp', 'application/x-web-app-manifest+json'], - ['webm', 'video/webm'], - ['webmanifest', 'application/manifest+json'], - ['webp', 'image/webp'], - ['wg', 'application/vnd.pmi.widget'], - ['wgt', 'application/widget'], - ['wks', 'application/vnd.ms-works'], - ['wm', 'video/x-ms-wm'], - ['wma', 'audio/x-ms-wma'], - ['wmd', 'application/x-ms-wmd'], - ['wmf', 'image/wmf'], - ['wml', 'text/vnd.wap.wml'], - ['wmlc', 'application/wmlc'], - ['wmls', 'text/vnd.wap.wmlscript'], - ['wmlsc', 'application/vnd.wap.wmlscriptc'], - ['wmv', 'video/x-ms-wmv'], - ['wmx', 'video/x-ms-wmx'], - ['wmz', 'application/x-msmetafile'], - ['woff', 'font/woff'], - ['woff2', 'font/woff2'], - ['word', 'application/msword'], - ['wpd', 'application/vnd.wordperfect'], - ['wpl', 'application/vnd.ms-wpl'], - ['wps', 'application/vnd.ms-works'], - ['wqd', 'application/vnd.wqd'], - ['wri', 'application/x-mswrite'], - ['wrl', 'model/vrml'], - ['wsc', 'message/vnd.wfa.wsc'], - ['wsdl', 'application/wsdl+xml'], - ['wspolicy', 'application/wspolicy+xml'], - ['wtb', 'application/vnd.webturbo'], - ['wvx', 'video/x-ms-wvx'], - ['x3d', 'model/x3d+xml'], - ['x3db', 'model/x3d+fastinfoset'], - ['x3dbz', 'model/x3d+binary'], - ['x3dv', 'model/x3d-vrml'], - ['x3dvz', 'model/x3d+vrml'], - ['x3dz', 'model/x3d+xml'], - ['x32', 'application/x-authorware-bin'], - ['x_b', 'model/vnd.parasolid.transmit.binary'], - ['x_t', 'model/vnd.parasolid.transmit.text'], - ['xaml', 'application/xaml+xml'], - ['xap', 'application/x-silverlight-app'], - ['xar', 'application/vnd.xara'], - ['xav', 'application/xcap-att+xml'], - ['xbap', 'application/x-ms-xbap'], - ['xbd', 'application/vnd.fujixerox.docuworks.binder'], - ['xbm', 'image/x-xbitmap'], - ['xca', 'application/xcap-caps+xml'], - ['xcs', 'application/calendar+xml'], - ['xdf', 'application/xcap-diff+xml'], - ['xdm', 'application/vnd.syncml.dm+xml'], - ['xdp', 'application/vnd.adobe.xdp+xml'], - ['xdssc', 'application/dssc+xml'], - ['xdw', 'application/vnd.fujixerox.docuworks'], - ['xel', 'application/xcap-el+xml'], - ['xenc', 'application/xenc+xml'], - ['xer', 'application/patch-ops-error+xml'], - ['xfdf', 'application/vnd.adobe.xfdf'], - ['xfdl', 'application/vnd.xfdl'], - ['xht', 'application/xhtml+xml'], - ['xhtml', 'application/xhtml+xml'], - ['xhvml', 'application/xv+xml'], - ['xif', 'image/vnd.xiff'], - ['xl', 'application/excel'], - ['xla', 'application/vnd.ms-excel'], - ['xlam', 'application/vnd.ms-excel.addin.macroEnabled.12'], - ['xlc', 'application/vnd.ms-excel'], - ['xlf', 'application/xliff+xml'], - ['xlm', 'application/vnd.ms-excel'], - ['xls', 'application/vnd.ms-excel'], - ['xlsb', 'application/vnd.ms-excel.sheet.binary.macroEnabled.12'], - ['xlsm', 'application/vnd.ms-excel.sheet.macroEnabled.12'], - ['xlsx', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'], - ['xlt', 'application/vnd.ms-excel'], - ['xltm', 'application/vnd.ms-excel.template.macroEnabled.12'], - ['xltx', 'application/vnd.openxmlformats-officedocument.spreadsheetml.template'], - ['xlw', 'application/vnd.ms-excel'], - ['xm', 'audio/xm'], - ['xml', 'application/xml'], - ['xns', 'application/xcap-ns+xml'], - ['xo', 'application/vnd.olpc-sugar'], - ['xop', 'application/xop+xml'], - ['xpi', 'application/x-xpinstall'], - ['xpl', 'application/xproc+xml'], - ['xpm', 'image/x-xpixmap'], - ['xpr', 'application/vnd.is-xpr'], - ['xps', 'application/vnd.ms-xpsdocument'], - ['xpw', 'application/vnd.intercon.formnet'], - ['xpx', 'application/vnd.intercon.formnet'], - ['xsd', 'application/xml'], - ['xsl', 'application/xml'], - ['xslt', 'application/xslt+xml'], - ['xsm', 'application/vnd.syncml+xml'], - ['xspf', 'application/xspf+xml'], - ['xul', 'application/vnd.mozilla.xul+xml'], - ['xvm', 'application/xv+xml'], - ['xvml', 'application/xv+xml'], - ['xwd', 'image/x-xwindowdump'], - ['xyz', 'chemical/x-xyz'], - ['xz', 'application/x-xz'], - ['yaml', 'text/yaml'], - ['yang', 'application/yang'], - ['yin', 'application/yin+xml'], - ['yml', 'text/yaml'], - ['ymp', 'text/x-suse-ymp'], - ['z', 'application/x-compress'], - ['z1', 'application/x-zmachine'], - ['z2', 'application/x-zmachine'], - ['z3', 'application/x-zmachine'], - ['z4', 'application/x-zmachine'], - ['z5', 'application/x-zmachine'], - ['z6', 'application/x-zmachine'], - ['z7', 'application/x-zmachine'], - ['z8', 'application/x-zmachine'], - ['zaz', 'application/vnd.zzazz.deck+xml'], - ['zip', 'application/zip'], - ['zir', 'application/vnd.zul'], - ['zirz', 'application/vnd.zul'], - ['zmm', 'application/vnd.handheld-entertainment+xml'], - ['zsh', 'text/x-scriptzsh'] + // https://github.com/guzzle/psr7/blob/2d9260799e713f1c475d3c5fdc3d6561ff7441b2/src/MimeType.php + ["1km", "application/vnd.1000minds.decision-model+xml"], + ["3dml", "text/vnd.in3d.3dml"], + ["3ds", "image/x-3ds"], + ["3g2", "video/3gpp2"], + ["3gp", "video/3gp"], + ["3gpp", "video/3gpp"], + ["3mf", "model/3mf"], + ["7z", "application/x-7z-compressed"], + ["7zip", "application/x-7z-compressed"], + ["123", "application/vnd.lotus-1-2-3"], + ["aab", "application/x-authorware-bin"], + ["aac", "audio/x-acc"], + ["aam", "application/x-authorware-map"], + ["aas", "application/x-authorware-seg"], + ["abw", "application/x-abiword"], + ["ac", "application/vnd.nokia.n-gage.ac+xml"], + ["ac3", "audio/ac3"], + ["acc", "application/vnd.americandynamics.acc"], + ["ace", "application/x-ace-compressed"], + ["acu", "application/vnd.acucobol"], + ["acutc", "application/vnd.acucorp"], + ["adp", "audio/adpcm"], + ["aep", "application/vnd.audiograph"], + ["afm", "application/x-font-type1"], + ["afp", "application/vnd.ibm.modcap"], + ["ahead", "application/vnd.ahead.space"], + ["ai", "application/pdf"], + ["aif", "audio/x-aiff"], + ["aifc", "audio/x-aiff"], + ["aiff", "audio/x-aiff"], + ["air", "application/vnd.adobe.air-application-installer-package+zip"], + ["ait", "application/vnd.dvb.ait"], + ["ami", "application/vnd.amiga.ami"], + ["amr", "audio/amr"], + ["apk", "application/vnd.android.package-archive"], + ["apng", "image/apng"], + ["appcache", "text/cache-manifest"], + ["application", "application/x-ms-application"], + ["apr", "application/vnd.lotus-approach"], + ["arc", "application/x-freearc"], + ["arj", "application/x-arj"], + ["asc", "application/pgp-signature"], + ["asf", "video/x-ms-asf"], + ["asm", "text/x-asm"], + ["aso", "application/vnd.accpac.simply.aso"], + ["asx", "video/x-ms-asf"], + ["atc", "application/vnd.acucorp"], + ["atom", "application/atom+xml"], + ["atomcat", "application/atomcat+xml"], + ["atomdeleted", "application/atomdeleted+xml"], + ["atomsvc", "application/atomsvc+xml"], + ["atx", "application/vnd.antix.game-component"], + ["au", "audio/x-au"], + ["avi", "video/x-msvideo"], + ["avif", "image/avif"], + ["aw", "application/applixware"], + ["azf", "application/vnd.airzip.filesecure.azf"], + ["azs", "application/vnd.airzip.filesecure.azs"], + ["azv", "image/vnd.airzip.accelerator.azv"], + ["azw", "application/vnd.amazon.ebook"], + ["b16", "image/vnd.pco.b16"], + ["bat", "application/x-msdownload"], + ["bcpio", "application/x-bcpio"], + ["bdf", "application/x-font-bdf"], + ["bdm", "application/vnd.syncml.dm+wbxml"], + ["bdoc", "application/x-bdoc"], + ["bed", "application/vnd.realvnc.bed"], + ["bh2", "application/vnd.fujitsu.oasysprs"], + ["bin", "application/octet-stream"], + ["blb", "application/x-blorb"], + ["blorb", "application/x-blorb"], + ["bmi", "application/vnd.bmi"], + ["bmml", "application/vnd.balsamiq.bmml+xml"], + ["bmp", "image/bmp"], + ["book", "application/vnd.framemaker"], + ["box", "application/vnd.previewsystems.box"], + ["boz", "application/x-bzip2"], + ["bpk", "application/octet-stream"], + ["bpmn", "application/octet-stream"], + ["bsp", "model/vnd.valve.source.compiled-map"], + ["btif", "image/prs.btif"], + ["buffer", "application/octet-stream"], + ["bz", "application/x-bzip"], + ["bz2", "application/x-bzip2"], + ["c", "text/x-c"], + ["c4d", "application/vnd.clonk.c4group"], + ["c4f", "application/vnd.clonk.c4group"], + ["c4g", "application/vnd.clonk.c4group"], + ["c4p", "application/vnd.clonk.c4group"], + ["c4u", "application/vnd.clonk.c4group"], + ["c11amc", "application/vnd.cluetrust.cartomobile-config"], + ["c11amz", "application/vnd.cluetrust.cartomobile-config-pkg"], + ["cab", "application/vnd.ms-cab-compressed"], + ["caf", "audio/x-caf"], + ["cap", "application/vnd.tcpdump.pcap"], + ["car", "application/vnd.curl.car"], + ["cat", "application/vnd.ms-pki.seccat"], + ["cb7", "application/x-cbr"], + ["cba", "application/x-cbr"], + ["cbr", "application/x-cbr"], + ["cbt", "application/x-cbr"], + ["cbz", "application/x-cbr"], + ["cc", "text/x-c"], + ["cco", "application/x-cocoa"], + ["cct", "application/x-director"], + ["ccxml", "application/ccxml+xml"], + ["cdbcmsg", "application/vnd.contact.cmsg"], + ["cda", "application/x-cdf"], + ["cdf", "application/x-netcdf"], + ["cdfx", "application/cdfx+xml"], + ["cdkey", "application/vnd.mediastation.cdkey"], + ["cdmia", "application/cdmi-capability"], + ["cdmic", "application/cdmi-container"], + ["cdmid", "application/cdmi-domain"], + ["cdmio", "application/cdmi-object"], + ["cdmiq", "application/cdmi-queue"], + ["cdr", "application/cdr"], + ["cdx", "chemical/x-cdx"], + ["cdxml", "application/vnd.chemdraw+xml"], + ["cdy", "application/vnd.cinderella"], + ["cer", "application/pkix-cert"], + ["cfs", "application/x-cfs-compressed"], + ["cgm", "image/cgm"], + ["chat", "application/x-chat"], + ["chm", "application/vnd.ms-htmlhelp"], + ["chrt", "application/vnd.kde.kchart"], + ["cif", "chemical/x-cif"], + ["cii", "application/vnd.anser-web-certificate-issue-initiation"], + ["cil", "application/vnd.ms-artgalry"], + ["cjs", "application/node"], + ["cla", "application/vnd.claymore"], + ["class", "application/octet-stream"], + ["clkk", "application/vnd.crick.clicker.keyboard"], + ["clkp", "application/vnd.crick.clicker.palette"], + ["clkt", "application/vnd.crick.clicker.template"], + ["clkw", "application/vnd.crick.clicker.wordbank"], + ["clkx", "application/vnd.crick.clicker"], + ["clp", "application/x-msclip"], + ["cmc", "application/vnd.cosmocaller"], + ["cmdf", "chemical/x-cmdf"], + ["cml", "chemical/x-cml"], + ["cmp", "application/vnd.yellowriver-custom-menu"], + ["cmx", "image/x-cmx"], + ["cod", "application/vnd.rim.cod"], + ["coffee", "text/coffeescript"], + ["com", "application/x-msdownload"], + ["conf", "text/plain"], + ["cpio", "application/x-cpio"], + ["cpp", "text/x-c"], + ["cpt", "application/mac-compactpro"], + ["crd", "application/x-mscardfile"], + ["crl", "application/pkix-crl"], + ["crt", "application/x-x509-ca-cert"], + ["crx", "application/x-chrome-extension"], + ["cryptonote", "application/vnd.rig.cryptonote"], + ["csh", "application/x-csh"], + ["csl", "application/vnd.citationstyles.style+xml"], + ["csml", "chemical/x-csml"], + ["csp", "application/vnd.commonspace"], + ["csr", "application/octet-stream"], + ["css", "text/css"], + ["cst", "application/x-director"], + ["csv", "text/csv"], + ["cu", "application/cu-seeme"], + ["curl", "text/vnd.curl"], + ["cww", "application/prs.cww"], + ["cxt", "application/x-director"], + ["cxx", "text/x-c"], + ["dae", "model/vnd.collada+xml"], + ["daf", "application/vnd.mobius.daf"], + ["dart", "application/vnd.dart"], + ["dataless", "application/vnd.fdsn.seed"], + ["davmount", "application/davmount+xml"], + ["dbf", "application/vnd.dbf"], + ["dbk", "application/docbook+xml"], + ["dcr", "application/x-director"], + ["dcurl", "text/vnd.curl.dcurl"], + ["dd2", "application/vnd.oma.dd2+xml"], + ["ddd", "application/vnd.fujixerox.ddd"], + ["ddf", "application/vnd.syncml.dmddf+xml"], + ["dds", "image/vnd.ms-dds"], + ["deb", "application/x-debian-package"], + ["def", "text/plain"], + ["deploy", "application/octet-stream"], + ["der", "application/x-x509-ca-cert"], + ["dfac", "application/vnd.dreamfactory"], + ["dgc", "application/x-dgc-compressed"], + ["dic", "text/x-c"], + ["dir", "application/x-director"], + ["dis", "application/vnd.mobius.dis"], + ["disposition-notification", "message/disposition-notification"], + ["dist", "application/octet-stream"], + ["distz", "application/octet-stream"], + ["djv", "image/vnd.djvu"], + ["djvu", "image/vnd.djvu"], + ["dll", "application/octet-stream"], + ["dmg", "application/x-apple-diskimage"], + ["dmn", "application/octet-stream"], + ["dmp", "application/vnd.tcpdump.pcap"], + ["dms", "application/octet-stream"], + ["dna", "application/vnd.dna"], + ["doc", "application/msword"], + ["docm", "application/vnd.ms-word.template.macroEnabled.12"], + [ + "docx", + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + ], + ["dot", "application/msword"], + ["dotm", "application/vnd.ms-word.template.macroEnabled.12"], + [ + "dotx", + "application/vnd.openxmlformats-officedocument.wordprocessingml.template", + ], + ["dp", "application/vnd.osgi.dp"], + ["dpg", "application/vnd.dpgraph"], + ["dra", "audio/vnd.dra"], + ["drle", "image/dicom-rle"], + ["dsc", "text/prs.lines.tag"], + ["dssc", "application/dssc+der"], + ["dtb", "application/x-dtbook+xml"], + ["dtd", "application/xml-dtd"], + ["dts", "audio/vnd.dts"], + ["dtshd", "audio/vnd.dts.hd"], + ["dump", "application/octet-stream"], + ["dvb", "video/vnd.dvb.file"], + ["dvi", "application/x-dvi"], + ["dwd", "application/atsc-dwd+xml"], + ["dwf", "model/vnd.dwf"], + ["dwg", "image/vnd.dwg"], + ["dxf", "image/vnd.dxf"], + ["dxp", "application/vnd.spotfire.dxp"], + ["dxr", "application/x-director"], + ["ear", "application/java-archive"], + ["ecelp4800", "audio/vnd.nuera.ecelp4800"], + ["ecelp7470", "audio/vnd.nuera.ecelp7470"], + ["ecelp9600", "audio/vnd.nuera.ecelp9600"], + ["ecma", "application/ecmascript"], + ["edm", "application/vnd.novadigm.edm"], + ["edx", "application/vnd.novadigm.edx"], + ["efif", "application/vnd.picsel"], + ["ei6", "application/vnd.pg.osasli"], + ["elc", "application/octet-stream"], + ["emf", "image/emf"], + ["eml", "message/rfc822"], + ["emma", "application/emma+xml"], + ["emotionml", "application/emotionml+xml"], + ["emz", "application/x-msmetafile"], + ["eol", "audio/vnd.digital-winds"], + ["eot", "application/vnd.ms-fontobject"], + ["eps", "application/postscript"], + ["epub", "application/epub+zip"], + ["es", "application/ecmascript"], + ["es3", "application/vnd.eszigno3+xml"], + ["esa", "application/vnd.osgi.subsystem"], + ["esf", "application/vnd.epson.esf"], + ["et3", "application/vnd.eszigno3+xml"], + ["etx", "text/x-setext"], + ["eva", "application/x-eva"], + ["evy", "application/x-envoy"], + ["exe", "application/octet-stream"], + ["exi", "application/exi"], + ["exp", "application/express"], + ["exr", "image/aces"], + ["ext", "application/vnd.novadigm.ext"], + ["ez", "application/andrew-inset"], + ["ez2", "application/vnd.ezpix-album"], + ["ez3", "application/vnd.ezpix-package"], + ["f", "text/x-fortran"], + ["f4v", "video/mp4"], + ["f77", "text/x-fortran"], + ["f90", "text/x-fortran"], + ["fbs", "image/vnd.fastbidsheet"], + ["fcdt", "application/vnd.adobe.formscentral.fcdt"], + ["fcs", "application/vnd.isac.fcs"], + ["fdf", "application/vnd.fdf"], + ["fdt", "application/fdt+xml"], + ["fe_launch", "application/vnd.denovo.fcselayout-link"], + ["fg5", "application/vnd.fujitsu.oasysgp"], + ["fgd", "application/x-director"], + ["fh", "image/x-freehand"], + ["fh4", "image/x-freehand"], + ["fh5", "image/x-freehand"], + ["fh7", "image/x-freehand"], + ["fhc", "image/x-freehand"], + ["fig", "application/x-xfig"], + ["fits", "image/fits"], + ["flac", "audio/x-flac"], + ["fli", "video/x-fli"], + ["flo", "application/vnd.micrografx.flo"], + ["flv", "video/x-flv"], + ["flw", "application/vnd.kde.kivio"], + ["flx", "text/vnd.fmi.flexstor"], + ["fly", "text/vnd.fly"], + ["fm", "application/vnd.framemaker"], + ["fnc", "application/vnd.frogans.fnc"], + ["fo", "application/vnd.software602.filler.form+xml"], + ["for", "text/x-fortran"], + ["fpx", "image/vnd.fpx"], + ["frame", "application/vnd.framemaker"], + ["fsc", "application/vnd.fsc.weblaunch"], + ["fst", "image/vnd.fst"], + ["ftc", "application/vnd.fluxtime.clip"], + ["fti", "application/vnd.anser-web-funds-transfer-initiation"], + ["fvt", "video/vnd.fvt"], + ["fxp", "application/vnd.adobe.fxp"], + ["fxpl", "application/vnd.adobe.fxp"], + ["fzs", "application/vnd.fuzzysheet"], + ["g2w", "application/vnd.geoplan"], + ["g3", "image/g3fax"], + ["g3w", "application/vnd.geospace"], + ["gac", "application/vnd.groove-account"], + ["gam", "application/x-tads"], + ["gbr", "application/rpki-ghostbusters"], + ["gca", "application/x-gca-compressed"], + ["gdl", "model/vnd.gdl"], + ["gdoc", "application/vnd.google-apps.document"], + ["geo", "application/vnd.dynageo"], + ["geojson", "application/geo+json"], + ["gex", "application/vnd.geometry-explorer"], + ["ggb", "application/vnd.geogebra.file"], + ["ggt", "application/vnd.geogebra.tool"], + ["ghf", "application/vnd.groove-help"], + ["gif", "image/gif"], + ["gim", "application/vnd.groove-identity-message"], + ["glb", "model/gltf-binary"], + ["gltf", "model/gltf+json"], + ["gml", "application/gml+xml"], + ["gmx", "application/vnd.gmx"], + ["gnumeric", "application/x-gnumeric"], + ["gpg", "application/gpg-keys"], + ["gph", "application/vnd.flographit"], + ["gpx", "application/gpx+xml"], + ["gqf", "application/vnd.grafeq"], + ["gqs", "application/vnd.grafeq"], + ["gram", "application/srgs"], + ["gramps", "application/x-gramps-xml"], + ["gre", "application/vnd.geometry-explorer"], + ["grv", "application/vnd.groove-injector"], + ["grxml", "application/srgs+xml"], + ["gsf", "application/x-font-ghostscript"], + ["gsheet", "application/vnd.google-apps.spreadsheet"], + ["gslides", "application/vnd.google-apps.presentation"], + ["gtar", "application/x-gtar"], + ["gtm", "application/vnd.groove-tool-message"], + ["gtw", "model/vnd.gtw"], + ["gv", "text/vnd.graphviz"], + ["gxf", "application/gxf"], + ["gxt", "application/vnd.geonext"], + ["gz", "application/gzip"], + ["gzip", "application/gzip"], + ["h", "text/x-c"], + ["h261", "video/h261"], + ["h263", "video/h263"], + ["h264", "video/h264"], + ["hal", "application/vnd.hal+xml"], + ["hbci", "application/vnd.hbci"], + ["hbs", "text/x-handlebars-template"], + ["hdd", "application/x-virtualbox-hdd"], + ["hdf", "application/x-hdf"], + ["heic", "image/heic"], + ["heics", "image/heic-sequence"], + ["heif", "image/heif"], + ["heifs", "image/heif-sequence"], + ["hej2", "image/hej2k"], + ["held", "application/atsc-held+xml"], + ["hh", "text/x-c"], + ["hjson", "application/hjson"], + ["hlp", "application/winhlp"], + ["hpgl", "application/vnd.hp-hpgl"], + ["hpid", "application/vnd.hp-hpid"], + ["hps", "application/vnd.hp-hps"], + ["hqx", "application/mac-binhex40"], + ["hsj2", "image/hsj2"], + ["htc", "text/x-component"], + ["htke", "application/vnd.kenameaapp"], + ["htm", "text/html"], + ["html", "text/html"], + ["hvd", "application/vnd.yamaha.hv-dic"], + ["hvp", "application/vnd.yamaha.hv-voice"], + ["hvs", "application/vnd.yamaha.hv-script"], + ["i2g", "application/vnd.intergeo"], + ["icc", "application/vnd.iccprofile"], + ["ice", "x-conference/x-cooltalk"], + ["icm", "application/vnd.iccprofile"], + ["ico", "image/x-icon"], + ["ics", "text/calendar"], + ["ief", "image/ief"], + ["ifb", "text/calendar"], + ["ifm", "application/vnd.shana.informed.formdata"], + ["iges", "model/iges"], + ["igl", "application/vnd.igloader"], + ["igm", "application/vnd.insors.igm"], + ["igs", "model/iges"], + ["igx", "application/vnd.micrografx.igx"], + ["iif", "application/vnd.shana.informed.interchange"], + ["img", "application/octet-stream"], + ["imp", "application/vnd.accpac.simply.imp"], + ["ims", "application/vnd.ms-ims"], + ["in", "text/plain"], + ["ini", "text/plain"], + ["ink", "application/inkml+xml"], + ["inkml", "application/inkml+xml"], + ["install", "application/x-install-instructions"], + ["iota", "application/vnd.astraea-software.iota"], + ["ipfix", "application/ipfix"], + ["ipk", "application/vnd.shana.informed.package"], + ["irm", "application/vnd.ibm.rights-management"], + ["irp", "application/vnd.irepository.package+xml"], + ["iso", "application/x-iso9660-image"], + ["itp", "application/vnd.shana.informed.formtemplate"], + ["its", "application/its+xml"], + ["ivp", "application/vnd.immervision-ivp"], + ["ivu", "application/vnd.immervision-ivu"], + ["jad", "text/vnd.sun.j2me.app-descriptor"], + ["jade", "text/jade"], + ["jam", "application/vnd.jam"], + ["jar", "application/java-archive"], + ["jardiff", "application/x-java-archive-diff"], + ["java", "text/x-java-source"], + ["jhc", "image/jphc"], + ["jisp", "application/vnd.jisp"], + ["jls", "image/jls"], + ["jlt", "application/vnd.hp-jlyt"], + ["jng", "image/x-jng"], + ["jnlp", "application/x-java-jnlp-file"], + ["joda", "application/vnd.joost.joda-archive"], + ["jp2", "image/jp2"], + ["jpe", "image/jpeg"], + ["jpeg", "image/jpeg"], + ["jpf", "image/jpx"], + ["jpg", "image/jpeg"], + ["jpg2", "image/jp2"], + ["jpgm", "video/jpm"], + ["jpgv", "video/jpeg"], + ["jph", "image/jph"], + ["jpm", "video/jpm"], + ["jpx", "image/jpx"], + ["js", "application/javascript"], + ["json", "application/json"], + ["json5", "application/json5"], + ["jsonld", "application/ld+json"], + // https://jsonlines.org/ + ["jsonl", "application/jsonl"], + ["jsonml", "application/jsonml+json"], + ["jsx", "text/jsx"], + ["jxr", "image/jxr"], + ["jxra", "image/jxra"], + ["jxrs", "image/jxrs"], + ["jxs", "image/jxs"], + ["jxsc", "image/jxsc"], + ["jxsi", "image/jxsi"], + ["jxss", "image/jxss"], + ["kar", "audio/midi"], + ["karbon", "application/vnd.kde.karbon"], + ["kdb", "application/octet-stream"], + ["kdbx", "application/x-keepass2"], + ["key", "application/x-iwork-keynote-sffkey"], + ["kfo", "application/vnd.kde.kformula"], + ["kia", "application/vnd.kidspiration"], + ["kml", "application/vnd.google-earth.kml+xml"], + ["kmz", "application/vnd.google-earth.kmz"], + ["kne", "application/vnd.kinar"], + ["knp", "application/vnd.kinar"], + ["kon", "application/vnd.kde.kontour"], + ["kpr", "application/vnd.kde.kpresenter"], + ["kpt", "application/vnd.kde.kpresenter"], + ["kpxx", "application/vnd.ds-keypoint"], + ["ksp", "application/vnd.kde.kspread"], + ["ktr", "application/vnd.kahootz"], + ["ktx", "image/ktx"], + ["ktx2", "image/ktx2"], + ["ktz", "application/vnd.kahootz"], + ["kwd", "application/vnd.kde.kword"], + ["kwt", "application/vnd.kde.kword"], + ["lasxml", "application/vnd.las.las+xml"], + ["latex", "application/x-latex"], + ["lbd", "application/vnd.llamagraphics.life-balance.desktop"], + ["lbe", "application/vnd.llamagraphics.life-balance.exchange+xml"], + ["les", "application/vnd.hhe.lesson-player"], + ["less", "text/less"], + ["lgr", "application/lgr+xml"], + ["lha", "application/octet-stream"], + ["link66", "application/vnd.route66.link66+xml"], + ["list", "text/plain"], + ["list3820", "application/vnd.ibm.modcap"], + ["listafp", "application/vnd.ibm.modcap"], + ["litcoffee", "text/coffeescript"], + ["lnk", "application/x-ms-shortcut"], + ["log", "text/plain"], + ["lostxml", "application/lost+xml"], + ["lrf", "application/octet-stream"], + ["lrm", "application/vnd.ms-lrm"], + ["ltf", "application/vnd.frogans.ltf"], + ["lua", "text/x-lua"], + ["luac", "application/x-lua-bytecode"], + ["lvp", "audio/vnd.lucent.voice"], + ["lwp", "application/vnd.lotus-wordpro"], + ["lzh", "application/octet-stream"], + ["m1v", "video/mpeg"], + ["m2a", "audio/mpeg"], + ["m2v", "video/mpeg"], + ["m3a", "audio/mpeg"], + ["m3u", "text/plain"], + ["m3u8", "application/vnd.apple.mpegurl"], + ["m4a", "audio/x-m4a"], + ["m4p", "application/mp4"], + ["m4s", "video/iso.segment"], + ["m4u", "application/vnd.mpegurl"], + ["m4v", "video/x-m4v"], + ["m13", "application/x-msmediaview"], + ["m14", "application/x-msmediaview"], + ["m21", "application/mp21"], + ["ma", "application/mathematica"], + ["mads", "application/mads+xml"], + ["maei", "application/mmt-aei+xml"], + ["mag", "application/vnd.ecowin.chart"], + ["maker", "application/vnd.framemaker"], + ["man", "text/troff"], + ["manifest", "text/cache-manifest"], + ["map", "application/json"], + ["mar", "application/octet-stream"], + ["markdown", "text/markdown"], + ["mathml", "application/mathml+xml"], + ["mb", "application/mathematica"], + ["mbk", "application/vnd.mobius.mbk"], + ["mbox", "application/mbox"], + ["mc1", "application/vnd.medcalcdata"], + ["mcd", "application/vnd.mcd"], + ["mcurl", "text/vnd.curl.mcurl"], + ["md", "text/markdown"], + ["mdb", "application/x-msaccess"], + ["mdi", "image/vnd.ms-modi"], + ["mdx", "text/mdx"], + ["me", "text/troff"], + ["mesh", "model/mesh"], + ["meta4", "application/metalink4+xml"], + ["metalink", "application/metalink+xml"], + ["mets", "application/mets+xml"], + ["mfm", "application/vnd.mfmp"], + ["mft", "application/rpki-manifest"], + ["mgp", "application/vnd.osgeo.mapguide.package"], + ["mgz", "application/vnd.proteus.magazine"], + ["mid", "audio/midi"], + ["midi", "audio/midi"], + ["mie", "application/x-mie"], + ["mif", "application/vnd.mif"], + ["mime", "message/rfc822"], + ["mj2", "video/mj2"], + ["mjp2", "video/mj2"], + ["mjs", "application/javascript"], + ["mk3d", "video/x-matroska"], + ["mka", "audio/x-matroska"], + ["mkd", "text/x-markdown"], + ["mks", "video/x-matroska"], + ["mkv", "video/x-matroska"], + ["mlp", "application/vnd.dolby.mlp"], + ["mmd", "application/vnd.chipnuts.karaoke-mmd"], + ["mmf", "application/vnd.smaf"], + ["mml", "text/mathml"], + ["mmr", "image/vnd.fujixerox.edmics-mmr"], + ["mng", "video/x-mng"], + ["mny", "application/x-msmoney"], + ["mobi", "application/x-mobipocket-ebook"], + ["mods", "application/mods+xml"], + ["mov", "video/quicktime"], + ["movie", "video/x-sgi-movie"], + ["mp2", "audio/mpeg"], + ["mp2a", "audio/mpeg"], + ["mp3", "audio/mpeg"], + ["mp4", "video/mp4"], + ["mp4a", "audio/mp4"], + ["mp4s", "application/mp4"], + ["mp4v", "video/mp4"], + ["mp21", "application/mp21"], + ["mpc", "application/vnd.mophun.certificate"], + ["mpd", "application/dash+xml"], + ["mpe", "video/mpeg"], + ["mpeg", "video/mpeg"], + ["mpg", "video/mpeg"], + ["mpg4", "video/mp4"], + ["mpga", "audio/mpeg"], + ["mpkg", "application/vnd.apple.installer+xml"], + ["mpm", "application/vnd.blueice.multipass"], + ["mpn", "application/vnd.mophun.application"], + ["mpp", "application/vnd.ms-project"], + ["mpt", "application/vnd.ms-project"], + ["mpy", "application/vnd.ibm.minipay"], + ["mqy", "application/vnd.mobius.mqy"], + ["mrc", "application/marc"], + ["mrcx", "application/marcxml+xml"], + ["ms", "text/troff"], + ["mscml", "application/mediaservercontrol+xml"], + ["mseed", "application/vnd.fdsn.mseed"], + ["mseq", "application/vnd.mseq"], + ["msf", "application/vnd.epson.msf"], + ["msg", "application/vnd.ms-outlook"], + ["msh", "model/mesh"], + ["msi", "application/x-msdownload"], + ["msl", "application/vnd.mobius.msl"], + ["msm", "application/octet-stream"], + ["msp", "application/octet-stream"], + ["msty", "application/vnd.muvee.style"], + ["mtl", "model/mtl"], + ["mts", "model/vnd.mts"], + ["mus", "application/vnd.musician"], + ["musd", "application/mmt-usd+xml"], + ["musicxml", "application/vnd.recordare.musicxml+xml"], + ["mvb", "application/x-msmediaview"], + ["mvt", "application/vnd.mapbox-vector-tile"], + ["mwf", "application/vnd.mfer"], + ["mxf", "application/mxf"], + ["mxl", "application/vnd.recordare.musicxml"], + ["mxmf", "audio/mobile-xmf"], + ["mxml", "application/xv+xml"], + ["mxs", "application/vnd.triscape.mxs"], + ["mxu", "video/vnd.mpegurl"], + ["n-gage", "application/vnd.nokia.n-gage.symbian.install"], + ["n3", "text/n3"], + ["nb", "application/mathematica"], + ["nbp", "application/vnd.wolfram.player"], + ["nc", "application/x-netcdf"], + ["ncx", "application/x-dtbncx+xml"], + ["nfo", "text/x-nfo"], + ["ngdat", "application/vnd.nokia.n-gage.data"], + ["nitf", "application/vnd.nitf"], + ["nlu", "application/vnd.neurolanguage.nlu"], + ["nml", "application/vnd.enliven"], + ["nnd", "application/vnd.noblenet-directory"], + ["nns", "application/vnd.noblenet-sealer"], + ["nnw", "application/vnd.noblenet-web"], + ["npx", "image/vnd.net-fpx"], + ["nq", "application/n-quads"], + ["nsc", "application/x-conference"], + ["nsf", "application/vnd.lotus-notes"], + ["nt", "application/n-triples"], + ["ntf", "application/vnd.nitf"], + ["numbers", "application/x-iwork-numbers-sffnumbers"], + ["nzb", "application/x-nzb"], + ["oa2", "application/vnd.fujitsu.oasys2"], + ["oa3", "application/vnd.fujitsu.oasys3"], + ["oas", "application/vnd.fujitsu.oasys"], + ["obd", "application/x-msbinder"], + ["obgx", "application/vnd.openblox.game+xml"], + ["obj", "model/obj"], + ["oda", "application/oda"], + ["odb", "application/vnd.oasis.opendocument.database"], + ["odc", "application/vnd.oasis.opendocument.chart"], + ["odf", "application/vnd.oasis.opendocument.formula"], + ["odft", "application/vnd.oasis.opendocument.formula-template"], + ["odg", "application/vnd.oasis.opendocument.graphics"], + ["odi", "application/vnd.oasis.opendocument.image"], + ["odm", "application/vnd.oasis.opendocument.text-master"], + ["odp", "application/vnd.oasis.opendocument.presentation"], + ["ods", "application/vnd.oasis.opendocument.spreadsheet"], + ["odt", "application/vnd.oasis.opendocument.text"], + ["oga", "audio/ogg"], + ["ogex", "model/vnd.opengex"], + ["ogg", "audio/ogg"], + ["ogv", "video/ogg"], + ["ogx", "application/ogg"], + ["omdoc", "application/omdoc+xml"], + ["onepkg", "application/onenote"], + ["onetmp", "application/onenote"], + ["onetoc", "application/onenote"], + ["onetoc2", "application/onenote"], + ["opf", "application/oebps-package+xml"], + ["opml", "text/x-opml"], + ["oprc", "application/vnd.palm"], + ["opus", "audio/ogg"], + ["org", "text/x-org"], + ["osf", "application/vnd.yamaha.openscoreformat"], + ["osfpvg", "application/vnd.yamaha.openscoreformat.osfpvg+xml"], + ["osm", "application/vnd.openstreetmap.data+xml"], + ["otc", "application/vnd.oasis.opendocument.chart-template"], + ["otf", "font/otf"], + ["otg", "application/vnd.oasis.opendocument.graphics-template"], + ["oth", "application/vnd.oasis.opendocument.text-web"], + ["oti", "application/vnd.oasis.opendocument.image-template"], + ["otp", "application/vnd.oasis.opendocument.presentation-template"], + ["ots", "application/vnd.oasis.opendocument.spreadsheet-template"], + ["ott", "application/vnd.oasis.opendocument.text-template"], + ["ova", "application/x-virtualbox-ova"], + ["ovf", "application/x-virtualbox-ovf"], + ["owl", "application/rdf+xml"], + ["oxps", "application/oxps"], + ["oxt", "application/vnd.openofficeorg.extension"], + ["p", "text/x-pascal"], + ["p7a", "application/x-pkcs7-signature"], + ["p7b", "application/x-pkcs7-certificates"], + ["p7c", "application/pkcs7-mime"], + ["p7m", "application/pkcs7-mime"], + ["p7r", "application/x-pkcs7-certreqresp"], + ["p7s", "application/pkcs7-signature"], + ["p8", "application/pkcs8"], + ["p10", "application/x-pkcs10"], + ["p12", "application/x-pkcs12"], + ["pac", "application/x-ns-proxy-autoconfig"], + ["pages", "application/x-iwork-pages-sffpages"], + ["pas", "text/x-pascal"], + ["paw", "application/vnd.pawaafile"], + ["pbd", "application/vnd.powerbuilder6"], + ["pbm", "image/x-portable-bitmap"], + ["pcap", "application/vnd.tcpdump.pcap"], + ["pcf", "application/x-font-pcf"], + ["pcl", "application/vnd.hp-pcl"], + ["pclxl", "application/vnd.hp-pclxl"], + ["pct", "image/x-pict"], + ["pcurl", "application/vnd.curl.pcurl"], + ["pcx", "image/x-pcx"], + ["pdb", "application/x-pilot"], + ["pde", "text/x-processing"], + ["pdf", "application/pdf"], + ["pem", "application/x-x509-user-cert"], + ["pfa", "application/x-font-type1"], + ["pfb", "application/x-font-type1"], + ["pfm", "application/x-font-type1"], + ["pfr", "application/font-tdpfr"], + ["pfx", "application/x-pkcs12"], + ["pgm", "image/x-portable-graymap"], + ["pgn", "application/x-chess-pgn"], + ["pgp", "application/pgp"], + ["php", "application/x-httpd-php"], + ["php3", "application/x-httpd-php"], + ["php4", "application/x-httpd-php"], + ["phps", "application/x-httpd-php-source"], + ["phtml", "application/x-httpd-php"], + ["pic", "image/x-pict"], + ["pkg", "application/octet-stream"], + ["pki", "application/pkixcmp"], + ["pkipath", "application/pkix-pkipath"], + ["pkpass", "application/vnd.apple.pkpass"], + ["pl", "application/x-perl"], + ["plb", "application/vnd.3gpp.pic-bw-large"], + ["plc", "application/vnd.mobius.plc"], + ["plf", "application/vnd.pocketlearn"], + ["pls", "application/pls+xml"], + ["pm", "application/x-perl"], + ["pml", "application/vnd.ctc-posml"], + ["png", "image/png"], + ["pnm", "image/x-portable-anymap"], + ["portpkg", "application/vnd.macports.portpkg"], + ["pot", "application/vnd.ms-powerpoint"], + ["potm", "application/vnd.ms-powerpoint.presentation.macroEnabled.12"], + [ + "potx", + "application/vnd.openxmlformats-officedocument.presentationml.template", + ], + ["ppa", "application/vnd.ms-powerpoint"], + ["ppam", "application/vnd.ms-powerpoint.addin.macroEnabled.12"], + ["ppd", "application/vnd.cups-ppd"], + ["ppm", "image/x-portable-pixmap"], + ["pps", "application/vnd.ms-powerpoint"], + ["ppsm", "application/vnd.ms-powerpoint.slideshow.macroEnabled.12"], + [ + "ppsx", + "application/vnd.openxmlformats-officedocument.presentationml.slideshow", + ], + ["ppt", "application/powerpoint"], + ["pptm", "application/vnd.ms-powerpoint.presentation.macroEnabled.12"], + [ + "pptx", + "application/vnd.openxmlformats-officedocument.presentationml.presentation", + ], + ["pqa", "application/vnd.palm"], + ["prc", "application/x-pilot"], + ["pre", "application/vnd.lotus-freelance"], + ["prf", "application/pics-rules"], + ["provx", "application/provenance+xml"], + ["ps", "application/postscript"], + ["psb", "application/vnd.3gpp.pic-bw-small"], + ["psd", "application/x-photoshop"], + ["psf", "application/x-font-linux-psf"], + ["pskcxml", "application/pskc+xml"], + ["pti", "image/prs.pti"], + ["ptid", "application/vnd.pvi.ptid1"], + ["pub", "application/x-mspublisher"], + ["pvb", "application/vnd.3gpp.pic-bw-var"], + ["pwn", "application/vnd.3m.post-it-notes"], + ["pya", "audio/vnd.ms-playready.media.pya"], + ["pyv", "video/vnd.ms-playready.media.pyv"], + ["qam", "application/vnd.epson.quickanime"], + ["qbo", "application/vnd.intu.qbo"], + ["qfx", "application/vnd.intu.qfx"], + ["qps", "application/vnd.publishare-delta-tree"], + ["qt", "video/quicktime"], + ["qwd", "application/vnd.quark.quarkxpress"], + ["qwt", "application/vnd.quark.quarkxpress"], + ["qxb", "application/vnd.quark.quarkxpress"], + ["qxd", "application/vnd.quark.quarkxpress"], + ["qxl", "application/vnd.quark.quarkxpress"], + ["qxt", "application/vnd.quark.quarkxpress"], + ["ra", "audio/x-realaudio"], + ["ram", "audio/x-pn-realaudio"], + ["raml", "application/raml+yaml"], + ["rapd", "application/route-apd+xml"], + ["rar", "application/x-rar"], + ["ras", "image/x-cmu-raster"], + ["rcprofile", "application/vnd.ipunplugged.rcprofile"], + ["rdf", "application/rdf+xml"], + ["rdz", "application/vnd.data-vision.rdz"], + ["relo", "application/p2p-overlay+xml"], + ["rep", "application/vnd.businessobjects"], + ["res", "application/x-dtbresource+xml"], + ["rgb", "image/x-rgb"], + ["rif", "application/reginfo+xml"], + ["rip", "audio/vnd.rip"], + ["ris", "application/x-research-info-systems"], + ["rl", "application/resource-lists+xml"], + ["rlc", "image/vnd.fujixerox.edmics-rlc"], + ["rld", "application/resource-lists-diff+xml"], + ["rm", "audio/x-pn-realaudio"], + ["rmi", "audio/midi"], + ["rmp", "audio/x-pn-realaudio-plugin"], + ["rms", "application/vnd.jcp.javame.midlet-rms"], + ["rmvb", "application/vnd.rn-realmedia-vbr"], + ["rnc", "application/relax-ng-compact-syntax"], + ["rng", "application/xml"], + ["roa", "application/rpki-roa"], + ["roff", "text/troff"], + ["rp9", "application/vnd.cloanto.rp9"], + ["rpm", "audio/x-pn-realaudio-plugin"], + ["rpss", "application/vnd.nokia.radio-presets"], + ["rpst", "application/vnd.nokia.radio-preset"], + ["rq", "application/sparql-query"], + ["rs", "application/rls-services+xml"], + ["rsa", "application/x-pkcs7"], + ["rsat", "application/atsc-rsat+xml"], + ["rsd", "application/rsd+xml"], + ["rsheet", "application/urc-ressheet+xml"], + ["rss", "application/rss+xml"], + ["rtf", "text/rtf"], + ["rtx", "text/richtext"], + ["run", "application/x-makeself"], + ["rusd", "application/route-usd+xml"], + ["rv", "video/vnd.rn-realvideo"], + ["s", "text/x-asm"], + ["s3m", "audio/s3m"], + ["saf", "application/vnd.yamaha.smaf-audio"], + ["sass", "text/x-sass"], + ["sbml", "application/sbml+xml"], + ["sc", "application/vnd.ibm.secure-container"], + ["scd", "application/x-msschedule"], + ["scm", "application/vnd.lotus-screencam"], + ["scq", "application/scvp-cv-request"], + ["scs", "application/scvp-cv-response"], + ["scss", "text/x-scss"], + ["scurl", "text/vnd.curl.scurl"], + ["sda", "application/vnd.stardivision.draw"], + ["sdc", "application/vnd.stardivision.calc"], + ["sdd", "application/vnd.stardivision.impress"], + ["sdkd", "application/vnd.solent.sdkm+xml"], + ["sdkm", "application/vnd.solent.sdkm+xml"], + ["sdp", "application/sdp"], + ["sdw", "application/vnd.stardivision.writer"], + ["sea", "application/octet-stream"], + ["see", "application/vnd.seemail"], + ["seed", "application/vnd.fdsn.seed"], + ["sema", "application/vnd.sema"], + ["semd", "application/vnd.semd"], + ["semf", "application/vnd.semf"], + ["senmlx", "application/senml+xml"], + ["sensmlx", "application/sensml+xml"], + ["ser", "application/java-serialized-object"], + ["setpay", "application/set-payment-initiation"], + ["setreg", "application/set-registration-initiation"], + ["sfd-hdstx", "application/vnd.hydrostatix.sof-data"], + ["sfs", "application/vnd.spotfire.sfs"], + ["sfv", "text/x-sfv"], + ["sgi", "image/sgi"], + ["sgl", "application/vnd.stardivision.writer-global"], + ["sgm", "text/sgml"], + ["sgml", "text/sgml"], + ["sh", "application/x-sh"], + ["shar", "application/x-shar"], + ["shex", "text/shex"], + ["shf", "application/shf+xml"], + ["shtml", "text/html"], + ["sid", "image/x-mrsid-image"], + ["sieve", "application/sieve"], + ["sig", "application/pgp-signature"], + ["sil", "audio/silk"], + ["silo", "model/mesh"], + ["sis", "application/vnd.symbian.install"], + ["sisx", "application/vnd.symbian.install"], + ["sit", "application/x-stuffit"], + ["sitx", "application/x-stuffitx"], + ["siv", "application/sieve"], + ["skd", "application/vnd.koan"], + ["skm", "application/vnd.koan"], + ["skp", "application/vnd.koan"], + ["skt", "application/vnd.koan"], + ["sldm", "application/vnd.ms-powerpoint.slide.macroenabled.12"], + [ + "sldx", + "application/vnd.openxmlformats-officedocument.presentationml.slide", + ], + ["slim", "text/slim"], + ["slm", "text/slim"], + ["sls", "application/route-s-tsid+xml"], + ["slt", "application/vnd.epson.salt"], + ["sm", "application/vnd.stepmania.stepchart"], + ["smf", "application/vnd.stardivision.math"], + ["smi", "application/smil"], + ["smil", "application/smil"], + ["smv", "video/x-smv"], + ["smzip", "application/vnd.stepmania.package"], + ["snd", "audio/basic"], + ["snf", "application/x-font-snf"], + ["so", "application/octet-stream"], + ["spc", "application/x-pkcs7-certificates"], + ["spdx", "text/spdx"], + ["spf", "application/vnd.yamaha.smaf-phrase"], + ["spl", "application/x-futuresplash"], + ["spot", "text/vnd.in3d.spot"], + ["spp", "application/scvp-vp-response"], + ["spq", "application/scvp-vp-request"], + ["spx", "audio/ogg"], + ["sql", "application/x-sql"], + ["src", "application/x-wais-source"], + ["srt", "application/x-subrip"], + ["sru", "application/sru+xml"], + ["srx", "application/sparql-results+xml"], + ["ssdl", "application/ssdl+xml"], + ["sse", "application/vnd.kodak-descriptor"], + ["ssf", "application/vnd.epson.ssf"], + ["ssml", "application/ssml+xml"], + ["sst", "application/octet-stream"], + ["st", "application/vnd.sailingtracker.track"], + ["stc", "application/vnd.sun.xml.calc.template"], + ["std", "application/vnd.sun.xml.draw.template"], + ["stf", "application/vnd.wt.stf"], + ["sti", "application/vnd.sun.xml.impress.template"], + ["stk", "application/hyperstudio"], + ["stl", "model/stl"], + ["stpx", "model/step+xml"], + ["stpxz", "model/step-xml+zip"], + ["stpz", "model/step+zip"], + ["str", "application/vnd.pg.format"], + ["stw", "application/vnd.sun.xml.writer.template"], + ["styl", "text/stylus"], + ["stylus", "text/stylus"], + ["sub", "text/vnd.dvb.subtitle"], + ["sus", "application/vnd.sus-calendar"], + ["susp", "application/vnd.sus-calendar"], + ["sv4cpio", "application/x-sv4cpio"], + ["sv4crc", "application/x-sv4crc"], + ["svc", "application/vnd.dvb.service"], + ["svd", "application/vnd.svd"], + ["svg", "image/svg+xml"], + ["svgz", "image/svg+xml"], + ["swa", "application/x-director"], + ["swf", "application/x-shockwave-flash"], + ["swi", "application/vnd.aristanetworks.swi"], + ["swidtag", "application/swid+xml"], + ["sxc", "application/vnd.sun.xml.calc"], + ["sxd", "application/vnd.sun.xml.draw"], + ["sxg", "application/vnd.sun.xml.writer.global"], + ["sxi", "application/vnd.sun.xml.impress"], + ["sxm", "application/vnd.sun.xml.math"], + ["sxw", "application/vnd.sun.xml.writer"], + ["t", "text/troff"], + ["t3", "application/x-t3vm-image"], + ["t38", "image/t38"], + ["taglet", "application/vnd.mynfc"], + ["tao", "application/vnd.tao.intent-module-archive"], + ["tap", "image/vnd.tencent.tap"], + ["tar", "application/x-tar"], + ["tcap", "application/vnd.3gpp2.tcap"], + ["tcl", "application/x-tcl"], + ["td", "application/urc-targetdesc+xml"], + ["teacher", "application/vnd.smart.teacher"], + ["tei", "application/tei+xml"], + ["teicorpus", "application/tei+xml"], + ["tex", "application/x-tex"], + ["texi", "application/x-texinfo"], + ["texinfo", "application/x-texinfo"], + ["text", "text/plain"], + ["tfi", "application/thraud+xml"], + ["tfm", "application/x-tex-tfm"], + ["tfx", "image/tiff-fx"], + ["tga", "image/x-tga"], + ["tgz", "application/x-tar"], + ["thmx", "application/vnd.ms-officetheme"], + ["tif", "image/tiff"], + ["tiff", "image/tiff"], + ["tk", "application/x-tcl"], + ["tmo", "application/vnd.tmobile-livetv"], + ["toml", "application/toml"], + ["torrent", "application/x-bittorrent"], + ["tpl", "application/vnd.groove-tool-template"], + ["tpt", "application/vnd.trid.tpt"], + ["tr", "text/troff"], + ["tra", "application/vnd.trueapp"], + ["trig", "application/trig"], + ["trm", "application/x-msterminal"], + ["ts", "video/mp2t"], + ["tsd", "application/timestamped-data"], + ["tsv", "text/tab-separated-values"], + ["ttc", "font/collection"], + ["ttf", "font/ttf"], + ["ttl", "text/turtle"], + ["ttml", "application/ttml+xml"], + ["twd", "application/vnd.simtech-mindmapper"], + ["twds", "application/vnd.simtech-mindmapper"], + ["txd", "application/vnd.genomatix.tuxedo"], + ["txf", "application/vnd.mobius.txf"], + ["txt", "text/plain"], + ["u8dsn", "message/global-delivery-status"], + ["u8hdr", "message/global-headers"], + ["u8mdn", "message/global-disposition-notification"], + ["u8msg", "message/global"], + ["u32", "application/x-authorware-bin"], + ["ubj", "application/ubjson"], + ["udeb", "application/x-debian-package"], + ["ufd", "application/vnd.ufdl"], + ["ufdl", "application/vnd.ufdl"], + ["ulx", "application/x-glulx"], + ["umj", "application/vnd.umajin"], + ["unityweb", "application/vnd.unity"], + ["uoml", "application/vnd.uoml+xml"], + ["uri", "text/uri-list"], + ["uris", "text/uri-list"], + ["urls", "text/uri-list"], + ["usdz", "model/vnd.usdz+zip"], + ["ustar", "application/x-ustar"], + ["utz", "application/vnd.uiq.theme"], + ["uu", "text/x-uuencode"], + ["uva", "audio/vnd.dece.audio"], + ["uvd", "application/vnd.dece.data"], + ["uvf", "application/vnd.dece.data"], + ["uvg", "image/vnd.dece.graphic"], + ["uvh", "video/vnd.dece.hd"], + ["uvi", "image/vnd.dece.graphic"], + ["uvm", "video/vnd.dece.mobile"], + ["uvp", "video/vnd.dece.pd"], + ["uvs", "video/vnd.dece.sd"], + ["uvt", "application/vnd.dece.ttml+xml"], + ["uvu", "video/vnd.uvvu.mp4"], + ["uvv", "video/vnd.dece.video"], + ["uvva", "audio/vnd.dece.audio"], + ["uvvd", "application/vnd.dece.data"], + ["uvvf", "application/vnd.dece.data"], + ["uvvg", "image/vnd.dece.graphic"], + ["uvvh", "video/vnd.dece.hd"], + ["uvvi", "image/vnd.dece.graphic"], + ["uvvm", "video/vnd.dece.mobile"], + ["uvvp", "video/vnd.dece.pd"], + ["uvvs", "video/vnd.dece.sd"], + ["uvvt", "application/vnd.dece.ttml+xml"], + ["uvvu", "video/vnd.uvvu.mp4"], + ["uvvv", "video/vnd.dece.video"], + ["uvvx", "application/vnd.dece.unspecified"], + ["uvvz", "application/vnd.dece.zip"], + ["uvx", "application/vnd.dece.unspecified"], + ["uvz", "application/vnd.dece.zip"], + ["vbox", "application/x-virtualbox-vbox"], + ["vbox-extpack", "application/x-virtualbox-vbox-extpack"], + ["vcard", "text/vcard"], + ["vcd", "application/x-cdlink"], + ["vcf", "text/x-vcard"], + ["vcg", "application/vnd.groove-vcard"], + ["vcs", "text/x-vcalendar"], + ["vcx", "application/vnd.vcx"], + ["vdi", "application/x-virtualbox-vdi"], + ["vds", "model/vnd.sap.vds"], + ["vhd", "application/x-virtualbox-vhd"], + ["vis", "application/vnd.visionary"], + ["viv", "video/vnd.vivo"], + ["vlc", "application/videolan"], + ["vmdk", "application/x-virtualbox-vmdk"], + ["vob", "video/x-ms-vob"], + ["vor", "application/vnd.stardivision.writer"], + ["vox", "application/x-authorware-bin"], + ["vrml", "model/vrml"], + ["vsd", "application/vnd.visio"], + ["vsf", "application/vnd.vsf"], + ["vss", "application/vnd.visio"], + ["vst", "application/vnd.visio"], + ["vsw", "application/vnd.visio"], + ["vtf", "image/vnd.valve.source.texture"], + ["vtt", "text/vtt"], + ["vtu", "model/vnd.vtu"], + ["vxml", "application/voicexml+xml"], + ["w3d", "application/x-director"], + ["wad", "application/x-doom"], + ["wadl", "application/vnd.sun.wadl+xml"], + ["war", "application/java-archive"], + ["wasm", "application/wasm"], + ["wav", "audio/x-wav"], + ["wax", "audio/x-ms-wax"], + ["wbmp", "image/vnd.wap.wbmp"], + ["wbs", "application/vnd.criticaltools.wbs+xml"], + ["wbxml", "application/wbxml"], + ["wcm", "application/vnd.ms-works"], + ["wdb", "application/vnd.ms-works"], + ["wdp", "image/vnd.ms-photo"], + ["weba", "audio/webm"], + ["webapp", "application/x-web-app-manifest+json"], + ["webm", "video/webm"], + ["webmanifest", "application/manifest+json"], + ["webp", "image/webp"], + ["wg", "application/vnd.pmi.widget"], + ["wgt", "application/widget"], + ["wks", "application/vnd.ms-works"], + ["wm", "video/x-ms-wm"], + ["wma", "audio/x-ms-wma"], + ["wmd", "application/x-ms-wmd"], + ["wmf", "image/wmf"], + ["wml", "text/vnd.wap.wml"], + ["wmlc", "application/wmlc"], + ["wmls", "text/vnd.wap.wmlscript"], + ["wmlsc", "application/vnd.wap.wmlscriptc"], + ["wmv", "video/x-ms-wmv"], + ["wmx", "video/x-ms-wmx"], + ["wmz", "application/x-msmetafile"], + ["woff", "font/woff"], + ["woff2", "font/woff2"], + ["word", "application/msword"], + ["wpd", "application/vnd.wordperfect"], + ["wpl", "application/vnd.ms-wpl"], + ["wps", "application/vnd.ms-works"], + ["wqd", "application/vnd.wqd"], + ["wri", "application/x-mswrite"], + ["wrl", "model/vrml"], + ["wsc", "message/vnd.wfa.wsc"], + ["wsdl", "application/wsdl+xml"], + ["wspolicy", "application/wspolicy+xml"], + ["wtb", "application/vnd.webturbo"], + ["wvx", "video/x-ms-wvx"], + ["x3d", "model/x3d+xml"], + ["x3db", "model/x3d+fastinfoset"], + ["x3dbz", "model/x3d+binary"], + ["x3dv", "model/x3d-vrml"], + ["x3dvz", "model/x3d+vrml"], + ["x3dz", "model/x3d+xml"], + ["x32", "application/x-authorware-bin"], + ["x_b", "model/vnd.parasolid.transmit.binary"], + ["x_t", "model/vnd.parasolid.transmit.text"], + ["xaml", "application/xaml+xml"], + ["xap", "application/x-silverlight-app"], + ["xar", "application/vnd.xara"], + ["xav", "application/xcap-att+xml"], + ["xbap", "application/x-ms-xbap"], + ["xbd", "application/vnd.fujixerox.docuworks.binder"], + ["xbm", "image/x-xbitmap"], + ["xca", "application/xcap-caps+xml"], + ["xcs", "application/calendar+xml"], + ["xdf", "application/xcap-diff+xml"], + ["xdm", "application/vnd.syncml.dm+xml"], + ["xdp", "application/vnd.adobe.xdp+xml"], + ["xdssc", "application/dssc+xml"], + ["xdw", "application/vnd.fujixerox.docuworks"], + ["xel", "application/xcap-el+xml"], + ["xenc", "application/xenc+xml"], + ["xer", "application/patch-ops-error+xml"], + ["xfdf", "application/vnd.adobe.xfdf"], + ["xfdl", "application/vnd.xfdl"], + ["xht", "application/xhtml+xml"], + ["xhtml", "application/xhtml+xml"], + ["xhvml", "application/xv+xml"], + ["xif", "image/vnd.xiff"], + ["xl", "application/excel"], + ["xla", "application/vnd.ms-excel"], + ["xlam", "application/vnd.ms-excel.addin.macroEnabled.12"], + ["xlc", "application/vnd.ms-excel"], + ["xlf", "application/xliff+xml"], + ["xlm", "application/vnd.ms-excel"], + ["xls", "application/vnd.ms-excel"], + ["xlsb", "application/vnd.ms-excel.sheet.binary.macroEnabled.12"], + ["xlsm", "application/vnd.ms-excel.sheet.macroEnabled.12"], + ["xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"], + ["xlt", "application/vnd.ms-excel"], + ["xltm", "application/vnd.ms-excel.template.macroEnabled.12"], + [ + "xltx", + "application/vnd.openxmlformats-officedocument.spreadsheetml.template", + ], + ["xlw", "application/vnd.ms-excel"], + ["xm", "audio/xm"], + ["xml", "application/xml"], + ["xns", "application/xcap-ns+xml"], + ["xo", "application/vnd.olpc-sugar"], + ["xop", "application/xop+xml"], + ["xpi", "application/x-xpinstall"], + ["xpl", "application/xproc+xml"], + ["xpm", "image/x-xpixmap"], + ["xpr", "application/vnd.is-xpr"], + ["xps", "application/vnd.ms-xpsdocument"], + ["xpw", "application/vnd.intercon.formnet"], + ["xpx", "application/vnd.intercon.formnet"], + ["xsd", "application/xml"], + ["xsl", "application/xml"], + ["xslt", "application/xslt+xml"], + ["xsm", "application/vnd.syncml+xml"], + ["xspf", "application/xspf+xml"], + ["xul", "application/vnd.mozilla.xul+xml"], + ["xvm", "application/xv+xml"], + ["xvml", "application/xv+xml"], + ["xwd", "image/x-xwindowdump"], + ["xyz", "chemical/x-xyz"], + ["xz", "application/x-xz"], + ["yaml", "text/yaml"], + ["yang", "application/yang"], + ["yin", "application/yin+xml"], + ["yml", "text/yaml"], + ["ymp", "text/x-suse-ymp"], + ["z", "application/x-compress"], + ["z1", "application/x-zmachine"], + ["z2", "application/x-zmachine"], + ["z3", "application/x-zmachine"], + ["z4", "application/x-zmachine"], + ["z5", "application/x-zmachine"], + ["z6", "application/x-zmachine"], + ["z7", "application/x-zmachine"], + ["z8", "application/x-zmachine"], + ["zaz", "application/vnd.zzazz.deck+xml"], + ["zip", "application/zip"], + ["zir", "application/vnd.zul"], + ["zirz", "application/vnd.zul"], + ["zmm", "application/vnd.handheld-entertainment+xml"], + ["zsh", "text/x-scriptzsh"], ]); - -export function toFileWithPath(file: FileWithPath, path?: string, h?: FileSystemHandle): FileWithPath { - const f = withMimeType(file); - const {webkitRelativePath} = file; - const p = typeof path === 'string' - ? path - // If is set, +export function toFileWithPath( + file: FileWithPath, + path?: string, + h?: FileSystemHandle, +): FileWithPath { + const f = withMimeType(file); + const { webkitRelativePath } = file; + const p = + typeof path === "string" + ? path + : // If is set, // the File will have a {webkitRelativePath} property // https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/webkitdirectory - : typeof webkitRelativePath === 'string' && webkitRelativePath.length > 0 - ? webkitRelativePath - : `./${file.name}`; - if (typeof f.path !== 'string') { // on electron, path is already set to the absolute path - setObjProp(f, 'path', p); - } - if (h !== undefined) { - Object.defineProperty(f, 'handle', { - value: h, - writable: false, - configurable: false, - enumerable: true - }); - } - // Always populate a relative path so that even electron apps have access to a relativePath value - setObjProp(f, 'relativePath', p); - return f; + typeof webkitRelativePath === "string" && webkitRelativePath.length > 0 + ? webkitRelativePath + : `./${file.name}`; + if (typeof f.path !== "string") { + // on electron, path is already set to the absolute path + setObjProp(f, "path", p); + } + if (h !== undefined) { + Object.defineProperty(f, "handle", { + value: h, + writable: false, + configurable: false, + enumerable: true, + }); + } + // Always populate a relative path so that even electron apps have access to a relativePath value + setObjProp(f, "relativePath", p); + return f; } export interface FileWithPath extends File { - readonly path?: string; - readonly handle?: FileSystemFileHandle; - readonly relativePath?: string; + readonly path?: string; + readonly handle?: FileSystemFileHandle; + readonly relativePath?: string; } function withMimeType(file: FileWithPath) { - const {name} = file; - const hasExtension = name && name.lastIndexOf('.') !== -1; + const { name } = file; + const hasExtension = name && name.lastIndexOf(".") !== -1; - if (hasExtension && !file.type) { - const ext = name.split('.') - .pop()!.toLowerCase(); - const type = COMMON_MIME_TYPES.get(ext); - if (type) { - Object.defineProperty(file, 'type', { - value: type, - writable: false, - configurable: false, - enumerable: true - }); - } + if (hasExtension && !file.type) { + const ext = name.split(".").pop()!.toLowerCase(); + const type = COMMON_MIME_TYPES.get(ext); + if (type) { + Object.defineProperty(file, "type", { + value: type, + writable: false, + configurable: false, + enumerable: true, + }); } + } - return file; + return file; } function setObjProp(f: FileWithPath, key: string, value: string) { - Object.defineProperty(f, key, { - value, - writable: false, - configurable: false, - enumerable: true - }) + Object.defineProperty(f, key, { + value, + writable: false, + configurable: false, + enumerable: true, + }); } diff --git a/src/index.ts b/src/index.ts index f4966b4..065773a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,2 @@ -export {fromEvent} from './file-selector.js'; -export {FileWithPath} from './file.js'; +export { fromEvent } from "./file-selector.js"; +export { FileWithPath } from "./file.js"; diff --git a/tsconfig.build.json b/tsconfig.build.json index e746ff3..3744585 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -1,10 +1,8 @@ { - "extends": "./tsconfig.json", - "files": [ - "./src/index.ts" - ], - "compilerOptions": { - "outDir": "./dist", - "declaration": true - } + "extends": "./tsconfig.json", + "files": ["./src/index.ts"], + "compilerOptions": { + "outDir": "./dist", + "declaration": true + } } diff --git a/tsconfig.json b/tsconfig.json index 3df33be..9ca4674 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,9 +1,9 @@ { - "compilerOptions": { - "target": "ESNext", - "module": "NodeNext", - "strict": true, - "noImplicitReturns": true, - "noUnusedLocals": true - } + "compilerOptions": { + "target": "ESNext", + "module": "NodeNext", + "strict": true, + "noImplicitReturns": true, + "noUnusedLocals": true + } }