├── tests
├── example
│ ├── assets
│ │ ├── file with space.html
│ │ ├── exists.png
│ │ ├── index.js
│ │ └── index.css
│ ├── nested
│ │ ├── assets
│ │ │ ├── index.js
│ │ │ ├── exists.png
│ │ │ └── index.css
│ │ ├── component.js
│ │ ├── index.html
│ │ └── index.js
│ ├── index.html
│ └── index.js
├── new
│ └── app.js
└── index.js
├── .npmignore
├── .gitignore
├── utils
├── ca.conf
├── ssl.conf
├── openBrowser.js
├── common.js
├── directoryListing.js
├── openChrome.applescript
└── mimeTypes.js
├── package.json
├── LICENSE
├── certify.sh
├── cli.js
├── servor.js
└── README.md
/tests/example/assets/file with space.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | test
2 |
3 | *.key
4 | *.pem
5 | *.crt
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.key
2 | *.pem
3 | *.crt
4 |
5 | .DS_Store
6 | node_modules
--------------------------------------------------------------------------------
/tests/example/assets/exists.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lukejacksonn/servor/HEAD/tests/example/assets/exists.png
--------------------------------------------------------------------------------
/tests/example/assets/index.js:
--------------------------------------------------------------------------------
1 | var $text = document.querySelector('h2');
2 | $text.innerHTML = window.location;
3 |
--------------------------------------------------------------------------------
/tests/example/nested/assets/index.js:
--------------------------------------------------------------------------------
1 | var $text = document.querySelector('h2');
2 | $text.innerHTML = window.location
3 |
--------------------------------------------------------------------------------
/tests/example/nested/assets/exists.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lukejacksonn/servor/HEAD/tests/example/nested/assets/exists.png
--------------------------------------------------------------------------------
/tests/example/nested/component.js:
--------------------------------------------------------------------------------
1 | import { react, html, css } from 'https://unpkg.com/rplus';
2 |
3 | export default 'component';
4 |
--------------------------------------------------------------------------------
/utils/ca.conf:
--------------------------------------------------------------------------------
1 | [req]
2 | prompt = no
3 | distinguished_name = options
4 | [options]
5 | C = US
6 | ST = State
7 | L = Locality
8 | O = Company
9 | CN = servor
--------------------------------------------------------------------------------
/utils/ssl.conf:
--------------------------------------------------------------------------------
1 | authorityKeyIdentifier=keyid,issuer
2 | basicConstraints=CA:FALSE
3 | keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
4 | subjectAltName = @alt_names
5 | [alt_names]
6 | DNS.1 = localhost
7 | DNS.2 = 127.0.0.1
8 | DNS.3 = ::1
--------------------------------------------------------------------------------
/tests/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SERVOR_TEST_INDEX
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | servør
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/tests/example/nested/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SERVOR_TEST_NESTED_INDEX
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | servør
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "servor",
3 | "version": "4.0.2",
4 | "description": "A dependency free dev server for single page app development",
5 | "repository": "lukejacksonn/servor",
6 | "main": "./servor.js",
7 | "bin": {
8 | "servor": "./cli.js"
9 | },
10 | "keywords": [
11 | "server",
12 | "https",
13 | "livereload",
14 | "spa"
15 | ],
16 | "scripts": {
17 | "start": "sudo node cli tests/example --reload --browse --secure --static --module",
18 | "cleanup": "rm -f servor.key servor.crt",
19 | "test": "npm run cleanup && cd tests && node index.js"
20 | },
21 | "author": "Luke Jackson ",
22 | "license": "MIT",
23 | "devDependencies": {
24 | "puppeteer": "^3.0.4"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/utils/openBrowser.js:
--------------------------------------------------------------------------------
1 | const childProcess = require('child_process');
2 |
3 | module.exports = (url) => {
4 | let cmd;
5 | const args = [];
6 |
7 | if (process.platform === 'darwin') {
8 | try {
9 | childProcess.execSync(
10 | `osascript openChrome.applescript "${encodeURI(url)}"`,
11 | {
12 | cwd: __dirname,
13 | stdio: 'ignore',
14 | }
15 | );
16 | return true;
17 | } catch (err) {}
18 | cmd = 'open';
19 | } else if (process.platform === 'win32') {
20 | cmd = 'cmd.exe';
21 | args.push('/c', 'start', '""', '/b');
22 | url = url.replace(/&/g, '^&');
23 | } else {
24 | cmd = 'xdg-open';
25 | }
26 |
27 | args.push(url);
28 | childProcess.spawn(cmd, args);
29 | };
30 |
--------------------------------------------------------------------------------
/tests/new/app.js:
--------------------------------------------------------------------------------
1 | // Simple react app by @lukejacksonn
2 | // ----------------
3 |
4 | import { react, html, css } from 'https://unpkg.com/rplus';
5 |
6 | const random = () => `${Math.random() * 80}%`;
7 | const style = css`
8 | position: absolute;
9 | font-size: 2rem;
10 | width: 5rem;
11 | height: 5rem;
12 | border-radius: 50%;
13 | border: 5px solid black;
14 | font-weight: bold;
15 | `;
16 |
17 | const app = () => {
18 | const [score, setScore] = react.useState(0);
19 | return html`
20 |
27 | `;
28 | };
29 |
30 | react.render(html`<${app} />`, document.body);
31 |
--------------------------------------------------------------------------------
/tests/example/assets/index.css:
--------------------------------------------------------------------------------
1 | * {
2 | display: block;
3 | flex: none;
4 | margin: 0;
5 | padding: 0;
6 | border: 0;
7 | color: #f2f2f2;
8 | box-sizing: border-box;
9 | -webkit-tap-highlight-color: transparent;
10 | }
11 |
12 | main {
13 | font-family: 'Roboto', sans-serif;
14 | font-size: 16px;
15 | background: #212121;
16 | height: 100vh;
17 | display: flex;
18 | align-items: center;
19 | justify-content: center;
20 | flex-direction: column;
21 | }
22 |
23 | head,
24 | script,
25 | style {
26 | display: none;
27 | }
28 |
29 | img {
30 | width: 30vmin;
31 | opacity: 0.38;
32 | }
33 |
34 | h1 {
35 | font-size: 20vmin;
36 | }
37 |
38 | h2 {
39 | color: #6f6f6f;
40 | font-size: 4vmin;
41 | font-weight: normal;
42 | }
43 |
44 | code {
45 | display: inline;
46 | }
47 |
--------------------------------------------------------------------------------
/tests/example/nested/assets/index.css:
--------------------------------------------------------------------------------
1 | * {
2 | display: block;
3 | flex: none;
4 | margin: 0;
5 | padding: 0;
6 | border: 0;
7 | color: #f2f2f2;
8 | box-sizing: border-box;
9 | -webkit-tap-highlight-color: transparent;
10 | }
11 |
12 | body {
13 | font-family: 'Roboto', sans-serif;
14 | font-size: 16px;
15 | background: #212121;
16 | height: 100vh;
17 | display: flex;
18 | align-items: center;
19 | justify-content: center;
20 | flex-direction: column;
21 | }
22 |
23 | head,
24 | script,
25 | style {
26 | display: none;
27 | }
28 |
29 | img {
30 | width: 30vmin;
31 | opacity: 0.38;
32 | }
33 |
34 | h1 {
35 | font-size: 20vmin;
36 | }
37 |
38 | h2 {
39 | color: #9f9f9f;
40 | font-size: 4vmin;
41 | font-weight: normal;
42 | }
43 |
44 | code {
45 | display: inline;
46 | }
47 |
--------------------------------------------------------------------------------
/utils/common.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const os = require('os');
3 | const net = require('net');
4 |
5 | const fileWatch =
6 | process.platform !== 'linux'
7 | ? (x, cb) => fs.watch(x, { recursive: true }, cb)
8 | : (x, cb) => {
9 | if (fs.statSync(x).isDirectory()) {
10 | fs.watch(x, cb);
11 | fs.readdirSync(x).forEach((xx) => fileWatch(`${x}/${xx}`, cb));
12 | }
13 | };
14 |
15 | module.exports.fileWatch = fileWatch;
16 |
17 | const usePort = (port = 0) =>
18 | new Promise((ok, x) => {
19 | const s = net.createServer();
20 | s.on('error', x);
21 | s.listen(port, () => (a = s.address()) && s.close(() => ok(a.port)));
22 | });
23 |
24 | module.exports.usePort = usePort;
25 |
26 | const networkIps = Object.values(os.networkInterfaces())
27 | .reduce((every, i) => [...every, ...i], [])
28 | .filter((i) => i.family === 'IPv4' && i.internal === false)
29 | .map((i) => i.address);
30 |
31 | module.exports.networkIps = networkIps;
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Luke Jackson
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/tests/example/index.js:
--------------------------------------------------------------------------------
1 | /* SERVOR_TEST_MODULE_INDEX */
2 |
3 | import { react, html, css } from 'https://unpkg.com/rplus';
4 |
5 | document.title = 'SERVOR_TEST_MODULE_INDEX';
6 |
7 | const style = css`
8 | font-family: 'Roboto', sans-serif;
9 | font-size: 16px;
10 | background: #212121;
11 | color: #f2f2f2;
12 | height: 100vh;
13 | display: flex;
14 | flex-direction: column;
15 | align-items: center;
16 | justify-content: center;
17 |
18 | :global(*) {
19 | display: block;
20 | flex: none;
21 | margin: 0;
22 | box-sizing: border-box;
23 | -webkit-tap-highlight-color: transparent;
24 | }
25 |
26 | :global(head),
27 | :global(script),
28 | :global(style) {
29 | display: none;
30 | }
31 |
32 | img {
33 | width: 30vmin;
34 | opacity: 0.38;
35 | }
36 |
37 | h1 {
38 | font-size: 20vmin;
39 | }
40 |
41 | h2 {
42 | color: #6f6f6f;
43 | font-size: 4vmin;
44 | font-weight: normal;
45 | }
46 | `;
47 |
48 | react.render(
49 | html`
50 |
51 |
52 | servør
53 | ${location.href}
54 |
55 | `,
56 | document.body
57 | );
58 |
--------------------------------------------------------------------------------
/tests/example/nested/index.js:
--------------------------------------------------------------------------------
1 | /* SERVOR_TEST_NESTED_MODULE_INDEX */
2 |
3 | import { react, html, css } from 'https://unpkg.com/rplus';
4 | import component from './component.js';
5 |
6 | document.title = 'SERVOR_TEST_NESTED_MODULE_INDEX';
7 |
8 | console.log(component);
9 |
10 | const style = css`
11 | font-family: 'Roboto', sans-serif;
12 | font-size: 16px;
13 | background: #333;
14 | color: #f2f2f2;
15 | height: 100vh;
16 | display: flex;
17 | flex-direction: column;
18 | align-items: center;
19 | justify-content: center;
20 |
21 | :global(*) {
22 | display: block;
23 | flex: none;
24 | margin: 0;
25 | box-sizing: border-box;
26 | -webkit-tap-highlight-color: transparent;
27 | }
28 |
29 | :global(head),
30 | :global(script),
31 | :global(style) {
32 | display: none;
33 | }
34 |
35 | img {
36 | width: 30vmin;
37 | opacity: 0.38;
38 | }
39 |
40 | h1 {
41 | font-size: 20vmin;
42 | }
43 |
44 | h2 {
45 | color: #6f6f6f;
46 | font-size: 4vmin;
47 | font-weight: normal;
48 | }
49 | `;
50 |
51 | react.render(
52 | html`
53 |
54 |
55 | servør
56 | ${location.href}
57 |
58 | `,
59 | document.body
60 | );
61 |
--------------------------------------------------------------------------------
/certify.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | name="servor"
4 |
5 | function command_exists () {
6 | type "$1" &> /dev/null ;
7 | }
8 |
9 | # Make sure openssl exists
10 | if ! command_exists openssl ; then
11 | echo "OpenSSL isn't installed. You need that to generate SSL certificates."
12 | exit
13 | fi
14 |
15 | ## Make sure the tmp/ directory exists
16 | if [ ! -d "tmp" ]; then
17 | mkdir tmp/
18 | fi
19 |
20 | # Generate Certificate Authority
21 | openssl genrsa -out "tmp/${name}CA.key" 2048 &>/dev/null
22 | openssl req -x509 -config utils/ca.conf -new -nodes -key "tmp/${name}CA.key" -sha256 -days 1825 -out "${name}CA.pem" &>/dev/null
23 |
24 | # This is the part that demands root privileges
25 | if [ "$EUID" -eq 0 ] ; then
26 | if command_exists security ; then
27 | # Delete trusted certs by their common name via https://unix.stackexchange.com/a/227014
28 | security find-certificate -c "${name}" -a -Z | sudo awk '/SHA-1/{system("security delete-certificate -Z "$NF)}'
29 | # Trust the Root Certificate cert
30 | security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain "${name}CA.pem"
31 | fi
32 | fi
33 |
34 | # Generate CA-signed Certificate
35 | openssl genrsa -out "${name}.key" 2048 &>/dev/null
36 | openssl req -new -config utils/ca.conf -key "${name}.key" -out "tmp/${name}.csr" &>/dev/null
37 |
38 | # Generate SSL Certificate
39 | openssl x509 -req -in "tmp/${name}.csr" -CA "${name}CA.pem" -CAkey "tmp/${name}CA.key" -CAcreateserial -out "${name}.crt" -days 1825 -sha256 -extfile utils/ssl.conf &>/dev/null
40 |
41 | # Cleanup files
42 | rm servorCA.pem servorCA.srl
43 | rm -rf tmp
44 |
--------------------------------------------------------------------------------
/utils/directoryListing.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 |
4 | module.exports = (uri) => {
5 | const dir = (x) => fs.statSync(path.join(uri, x)).isDirectory();
6 | const size = (x) => fs.statSync(path.join(uri, x)).size;
7 |
8 | const link = (x) =>
9 | dir(x)
10 | ? `
11 |
12 |
🗂
13 |
${x}
14 |
${size(x)}B
15 |
16 | `
17 | : `
18 |
19 |
📄
20 |
${x}
21 |
${size(x)}B
22 |
23 | `;
24 |
25 | return `
26 |
27 |
28 |
63 |
64 |
65 |
66 | ${fs.readdirSync(uri).map(link).join('')}
67 |
68 |
69 |
70 | `;
71 | };
72 |
--------------------------------------------------------------------------------
/utils/openChrome.applescript:
--------------------------------------------------------------------------------
1 | (*
2 | Copyright (c) 2015-present, Facebook, Inc.
3 | This source code is licensed under the MIT license found in the
4 | LICENSE file in the root directory of this source tree.
5 | *)
6 |
7 | property targetTab: null
8 | property targetTabIndex: -1
9 | property targetWindow: null
10 |
11 | on run argv
12 | set theURL to item 1 of argv
13 |
14 | tell application "Chrome"
15 |
16 | if (count every window) = 0 then
17 | make new window
18 | end if
19 |
20 | -- 1: Looking for tab running debugger
21 | -- then, Reload debugging tab if found
22 | -- then return
23 | set found to my lookupTabWithUrl(theURL)
24 | if found then
25 | set targetWindow's active tab index to targetTabIndex
26 | tell targetTab to reload
27 | tell targetWindow to activate
28 | set index of targetWindow to 1
29 | return
30 | end if
31 |
32 | -- 2: Looking for Empty tab
33 | -- In case debugging tab was not found
34 | -- We try to find an empty tab instead
35 | set found to my lookupTabWithUrl("chrome://newtab/")
36 | if found then
37 | set targetWindow's active tab index to targetTabIndex
38 | set URL of targetTab to theURL
39 | tell targetWindow to activate
40 | return
41 | end if
42 |
43 | -- 3: Create new tab
44 | -- both debugging and empty tab were not found
45 | -- make a new tab with url
46 | tell window 1
47 | activate
48 | make new tab with properties {URL:theURL}
49 | end tell
50 | end tell
51 | end run
52 |
53 | -- Function:
54 | -- Lookup tab with given url
55 | -- if found, store tab, index, and window in properties
56 | -- (properties were declared on top of file)
57 | on lookupTabWithUrl(lookupUrl)
58 | tell application "Chrome"
59 | -- Find a tab with the given url
60 | set found to false
61 | set theTabIndex to -1
62 | repeat with theWindow in every window
63 | set theTabIndex to 0
64 | repeat with theTab in every tab of theWindow
65 | set theTabIndex to theTabIndex + 1
66 | if (theTab's URL as string) contains lookupUrl then
67 | -- assign tab, tab index, and window to properties
68 | set targetTab to theTab
69 | set targetTabIndex to theTabIndex
70 | set targetWindow to theWindow
71 | set found to true
72 | exit repeat
73 | end if
74 | end repeat
75 |
76 | if found then
77 | exit repeat
78 | end if
79 | end repeat
80 | end tell
81 | return found
82 | end lookupTabWithUrl
--------------------------------------------------------------------------------
/cli.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | const fs = require('fs');
3 | const servor = require('./servor.js');
4 | const openBrowser = require('./utils/openBrowser.js');
5 |
6 | const readCredentials = () => ({
7 | cert: fs.readFileSync(__dirname + '/servor.crt'),
8 | key: fs.readFileSync(__dirname + '/servor.key'),
9 | });
10 |
11 | const certify = () =>
12 | require('child_process').execSync(__dirname + '/certify.sh', {
13 | cwd: __dirname,
14 | });
15 |
16 | const open =
17 | process.platform == 'darwin'
18 | ? 'open'
19 | : process.platform == 'win32'
20 | ? 'start'
21 | : 'xdg-open';
22 |
23 | (async () => {
24 | const args = process.argv.slice(2).filter((x) => !~x.indexOf('--'));
25 | const admin = process.getuid && process.getuid() === 0;
26 | let credentials;
27 |
28 | if (args[0] && args[0].startsWith('gh:')) {
29 | const repo = args[0].replace('gh:', '');
30 | const dest = repo.split('/')[1];
31 | if (!fs.existsSync(dest)) {
32 | try {
33 | require('child_process').execSync(
34 | `git clone https://github.com/${repo}`,
35 | { stdio: 'ignore' }
36 | );
37 | } catch (e) {
38 | console.log(
39 | `\n ⚠️ Could not clone from https://github.com/${repo}\n`
40 | );
41 | process.exit();
42 | }
43 | }
44 | args[0] = dest;
45 | }
46 |
47 | if (~process.argv.indexOf('--editor')) {
48 | try {
49 | require('child_process').execSync(`code ${args[0] || '.'}`);
50 | } catch (e) {
51 | console.log(`\n ⚠️ Could not open code editor for ${args[0] || '.'}`);
52 | }
53 | }
54 |
55 | // Generate ssl certificates
56 |
57 | if (~process.argv.indexOf('--secure')) {
58 | admin && certify();
59 | admin && process.platform === 'darwin' && process.setuid(501);
60 | try {
61 | credentials = readCredentials();
62 | } catch (e) {
63 | certify();
64 | try {
65 | credentials = readCredentials();
66 | } catch (e) {
67 | console.log(
68 | '\n ⚠️ There was a problem generating ssl credentials. Try removing `--secure`\n'
69 | );
70 | process.exit();
71 | }
72 | }
73 | }
74 |
75 | // Parse arguments from the command line
76 |
77 | const { root, protocol, port, ips, url } = await servor({
78 | root: args[0],
79 | fallback: args[1],
80 | port: args[2],
81 | reload: !!~process.argv.indexOf('--reload'),
82 | module: !!~process.argv.indexOf('--module'),
83 | static: !!~process.argv.indexOf('--static'),
84 | credentials,
85 | });
86 |
87 | // Output server details to the console
88 |
89 | !~process.argv.indexOf('--silent') &&
90 | console.log(`
91 | 🗂 Serving:\t${root}\n
92 | 🏡 Local:\t${url}
93 | ${ips.map((ip) => `📡 Network:\t${protocol}://${ip}:${port}`).join('\n ')}
94 | `);
95 |
96 | // Browser the server index
97 |
98 | !!~process.argv.indexOf('--browse') && openBrowser(url);
99 | })();
100 |
--------------------------------------------------------------------------------
/tests/index.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const puppeteer = require('puppeteer');
3 | const cp = require('child_process');
4 |
5 | const matches = (obj, source) =>
6 | Object.keys(source).every(
7 | (key) =>
8 | obj.hasOwnProperty(key) &&
9 | JSON.stringify(obj[key]) === JSON.stringify(source[key])
10 | );
11 |
12 | const modifyFile = (x) =>
13 | fs.readFile(x, 'utf-8', (_, data) => {
14 | fs.writeFileSync(x, data, 'utf-8');
15 | });
16 |
17 | const test = (cmd) => (url) => async (expect) => {
18 | // Make sure nothing is running on port 8080
19 | cp.execSync(
20 | "lsof -n -i4TCP:8080 | grep LISTEN | awk '{ print $2 }' | xargs kill"
21 | );
22 |
23 | // Run the command and wait for the server to start
24 | const [c, ...a] = ('node ../cli example ' + cmd).trim().split(' ');
25 | const servor = cp.spawn(c, a);
26 | const { origin } = await new Promise((resolve) =>
27 | servor.stdout.once('data', (out) => {
28 | resolve(new URL(out.toString().match(/Local:\t(.*)\n/)[1]));
29 | })
30 | );
31 |
32 | const browser = await puppeteer.launch({
33 | ignoreHTTPSErrors: true,
34 | headless: true,
35 | slowMo: 0,
36 | });
37 |
38 | // Load new page and go to url
39 | const page = await browser.newPage();
40 | await page.setCacheEnabled(false);
41 |
42 | const res = await page.goto(`${origin}${url}`);
43 |
44 | // Collect data from response and page
45 | const status = res.status();
46 | const headers = res.headers();
47 | const content = await page.content();
48 |
49 | // Change a file to trigger reload
50 | let reload = false;
51 | if (cmd.includes('--reload')) {
52 | modifyFile('example/index.html');
53 | reload = await page.waitForNavigation({ timeout: 1000 }).catch(() => false);
54 | modifyFile('example/assets/index.js');
55 | reload = await page.waitForNavigation({ timeout: 1000 }).catch(() => false);
56 | }
57 |
58 | const result = {
59 | status,
60 | reload: !!reload,
61 | gzip: headers['content-encoding'] === 'gzip',
62 | cors: headers['access-control-allow-origin'] === '*',
63 | includes: [
64 | 'SERVOR_TEST_INDEX',
65 | 'SERVOR_TEST_NESTED_INDEX',
66 | 'SERVOR_TEST_MODULE_INDEX',
67 | 'SERVOR_TEST_NESTED_MODULE_INDEX',
68 | ].filter((x) => content.includes(x)),
69 | };
70 |
71 | const passed = matches(result, expect);
72 | console.log(
73 | passed
74 | ? { ['PASSED']: { cmd, url, out: JSON.stringify(result) } }
75 | : { ['FAILED']: { cmd, url, result, expect } }
76 | );
77 |
78 | servor.kill();
79 | await browser.close();
80 | };
81 |
82 | (async () => {
83 | const base = { status: 200, gzip: true, cors: true, reload: false };
84 |
85 | await test('')('/')({
86 | ...base,
87 | includes: ['SERVOR_TEST_INDEX'],
88 | });
89 |
90 | await test('')('/index.html')({
91 | ...base,
92 | includes: ['SERVOR_TEST_INDEX'],
93 | });
94 |
95 | await test('')('/assets/file with space.html')({
96 | ...base,
97 | });
98 |
99 | await test('')('/assets/exists.png')({
100 | ...base,
101 | gzip: false,
102 | });
103 |
104 | await test('')('/assets/no-exists.png')({
105 | ...base,
106 | status: 404,
107 | gzip: false,
108 | });
109 |
110 | await test('')('/nested')({
111 | ...base,
112 | status: 301,
113 | includes: ['SERVOR_TEST_INDEX'],
114 | });
115 |
116 | await test('')('/nested/assets/exists.png')({
117 | ...base,
118 | gzip: false,
119 | });
120 |
121 | await test('')('/nested/assets/no-xists.png')({
122 | ...base,
123 | status: 404,
124 | gzip: false,
125 | });
126 |
127 | await test('--reload')('/')({
128 | ...base,
129 | reload: true,
130 | includes: ['SERVOR_TEST_INDEX'],
131 | });
132 |
133 | await test('--static')('/')({
134 | ...base,
135 | includes: ['SERVOR_TEST_INDEX'],
136 | });
137 |
138 | await test('--static')('/nested')({
139 | ...base,
140 | includes: ['SERVOR_TEST_NESTED_INDEX'],
141 | });
142 |
143 | await test('--static')('/broken-nested')({
144 | ...base,
145 | status: 404,
146 | gzip: false,
147 | });
148 |
149 | await test('--module')('/')({
150 | ...base,
151 | includes: ['SERVOR_TEST_MODULE_INDEX'],
152 | });
153 |
154 | await test('--secure')('/')({
155 | ...base,
156 | includes: ['SERVOR_TEST_INDEX'],
157 | });
158 |
159 | await test('--secure --reload --static --module')('/')({
160 | ...base,
161 | reload: true,
162 | includes: ['SERVOR_TEST_MODULE_INDEX'],
163 | });
164 |
165 | await test('--secure --reload --static --module')('/nested')({
166 | ...base,
167 | reload: true,
168 | includes: ['SERVOR_TEST_NESTED_MODULE_INDEX'],
169 | });
170 | })();
171 |
--------------------------------------------------------------------------------
/servor.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const url = require('url');
3 | const path = require('path');
4 | const http = require('http');
5 | const http2 = require('http2');
6 | const https = require('https');
7 | const zlib = require('zlib');
8 |
9 | const mimeTypes = require('./utils/mimeTypes.js');
10 | const directoryListing = require('./utils/directoryListing.js');
11 |
12 | const { fileWatch, usePort, networkIps } = require('./utils/common.js');
13 |
14 | module.exports = async ({
15 | root = '.',
16 | module = false,
17 | fallback = module ? 'index.js' : 'index.html',
18 | reload = true,
19 | static = false,
20 | inject = '',
21 | credentials,
22 | port,
23 | } = {}) => {
24 | // Try start on specified port then fail or find a free port
25 |
26 | try {
27 | port = await usePort(port || process.env.PORT || 8080);
28 | } catch (e) {
29 | if (port || process.env.PORT) {
30 | console.log('[ERR] The port you have specified is already in use!');
31 | process.exit();
32 | }
33 | port = await usePort();
34 | }
35 |
36 | // Configure globals
37 |
38 | root = root.startsWith('/') ? root : path.join(process.cwd(), root);
39 |
40 | if (!fs.existsSync(root)) {
41 | console.log(`[ERR] Root directory ${root} does not exist!`);
42 | process.exit();
43 | }
44 |
45 | if (!fs.statSync(root).isDirectory()) {
46 | console.log(`[ERR] Root directory "${root}" is not directory!`);
47 | process.exit();
48 | }
49 |
50 | const reloadClients = [];
51 | const protocol = credentials ? 'https' : 'http';
52 | const server = credentials
53 | ? reload
54 | ? (cb) => https.createServer(credentials, cb)
55 | : (cb) => http2.createSecureServer(credentials, cb)
56 | : (cb) => http.createServer(cb);
57 |
58 | const livereload = reload
59 | ? `
60 |
67 | `
68 | : '';
69 |
70 | // Server utility functions
71 |
72 | const isRouteRequest = (pathname) => !~pathname.split('/').pop().indexOf('.');
73 | const utf8 = (file) => Buffer.from(file, 'binary').toString('utf8');
74 |
75 | const baseDoc = (pathname = '', base = path.join('/', pathname, '/')) =>
76 | ``;
77 |
78 | const sendError = (res, status) => {
79 | res.writeHead(status);
80 | res.write(`${status}`);
81 | res.end();
82 | };
83 |
84 | const sendFile = (res, status, file, ext, encoding = 'binary') => {
85 | if (['js', 'css', 'html', 'json', 'xml', 'svg'].includes(ext)) {
86 | res.setHeader('content-encoding', 'gzip');
87 | file = zlib.gzipSync(utf8(file));
88 | encoding = 'utf8';
89 | }
90 | res.writeHead(status, { 'content-type': mimeTypes(ext) });
91 | res.write(file, encoding);
92 | res.end();
93 | };
94 |
95 | const sendMessage = (res, channel, data) => {
96 | res.write(`event: ${channel}\nid: 0\ndata: ${data}\n`);
97 | res.write('\n\n');
98 | };
99 |
100 | // Respond to reload requests with keep alive
101 |
102 | const serveReload = (res) => {
103 | res.writeHead(200, {
104 | connection: 'keep-alive',
105 | 'content-type': 'text/event-stream',
106 | 'cache-control': 'no-cache',
107 | });
108 | sendMessage(res, 'connected', 'ready');
109 | setInterval(sendMessage, 60000, res, 'ping', 'waiting');
110 | reloadClients.push(res);
111 | };
112 |
113 | // Respond to requests with a file extension
114 |
115 | const serveStaticFile = (res, pathname) => {
116 | const uri = path.join(root, pathname);
117 | let ext = uri.replace(/^.*[\.\/\\]/, '').toLowerCase();
118 | if (!fs.existsSync(uri)) return sendError(res, 404);
119 | fs.readFile(uri, 'binary', (err, file) =>
120 | err ? sendError(res, 500) : sendFile(res, 200, file, ext)
121 | );
122 | };
123 |
124 | // Respond to requests without a file extension
125 |
126 | const serveRoute = (res, pathname) => {
127 | const index = static
128 | ? path.join(root, pathname, fallback)
129 | : path.join(root, fallback);
130 | if (!fs.existsSync(index) || (pathname.endsWith('/') && pathname !== '/'))
131 | return serveDirectoryListing(res, pathname);
132 | fs.readFile(index, 'binary', (err, file) => {
133 | if (err) return sendError(res, 500);
134 | const status = pathname === '/' || static ? 200 : 301;
135 | if (module) file = ``;
136 | if (static) file = baseDoc(pathname) + file;
137 | file = file + inject + livereload;
138 | sendFile(res, status, file, 'html');
139 | });
140 | };
141 |
142 | // Respond to requests with a trailing slash
143 |
144 | const serveDirectoryListing = (res, pathname) => {
145 | const uri = path.join(root, pathname);
146 | if (!fs.existsSync(uri)) return sendError(res, 404);
147 | res.writeHead(200, { 'Content-Type': 'text/html' });
148 | res.write(baseDoc(pathname) + directoryListing(uri) + livereload);
149 | res.end();
150 | };
151 |
152 | // Start the server and route requests
153 |
154 | server((req, res) => {
155 | const decodePathname = decodeURI(url.parse(req.url).pathname);
156 | const pathname = path.normalize(decodePathname).replace(/^(\.\.(\/|\\|$))+/, '');
157 | res.setHeader('access-control-allow-origin', '*');
158 | if (reload && pathname === '/livereload') return serveReload(res);
159 | if (!isRouteRequest(pathname)) return serveStaticFile(res, pathname);
160 | return serveRoute(res, pathname);
161 | }).listen(parseInt(port, 10));
162 |
163 | // Notify livereload reloadClients on file change
164 |
165 | reload &&
166 | fileWatch(root, () => {
167 | while (reloadClients.length > 0)
168 | sendMessage(reloadClients.pop(), 'message', 'reload');
169 | });
170 |
171 | // Close socket connections on sigint
172 |
173 | process.on('SIGINT', () => {
174 | while (reloadClients.length > 0) reloadClients.pop().end();
175 | process.exit();
176 | });
177 |
178 | const x = { url: `${protocol}://localhost:${port}` };
179 | return { ...x, root, protocol, port, ips: networkIps };
180 | };
181 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Servør
2 |
3 | > A dependency free dev server for modern web application development
4 |
5 | A very compact but capable static file server with https, live reloading, gzip and other useful features to support modern web app development on localhost and over a local network. The motivation here was to write a package from the ground up with no dependencies; using only native, node and browser APIs to do some specific tasks with minimal code.
6 |
7 | Servør can be invoked via the command line or programmatically using the node API.
8 |
9 | **Quickstart Example**
10 |
11 | The following command instructs servør to; clone [perflink](https://github.com/lukejacksonn/perflink), start a server at the project root, open the url in a browser, open the source code in an editor and reload the browser when files change.
12 |
13 | ```s
14 | npx servor gh:lukejacksonn/perflink --browse --editor --reload
15 | ```
16 |
17 | Most features are disabled by default but you can customize behaviour by passing positional arguments and flags to enable features.
18 |
19 |
20 |
21 |
22 |
23 | ## Features
24 |
25 | - 🗂 Serves static content like scripts, styles, images from a given directory
26 | - ♻️ Reloads the browser when project files get added, removed or modified
27 | - 🗜 Uses gzip on common filetypes like html, css, js and json
28 | - 🔐 Supports https and http2 with trusted self signed certificates
29 | - 🖥 Redirects all path requests to a single file for frontend routing
30 | - 📦 Accepts both HTML and JavaScript files as the root file for a directory
31 | - 🔎 Discovers freely available ports to start if the default is in use
32 | - 📄 Renders directory listing for urls ending with a trailing slash
33 | - 🗃 Opens browser tab and code editor to streamline quick start
34 |
35 | ## CLI Usage
36 |
37 | Run as a terminal command without adding it as a dependency using `npx`:
38 |
39 | ```s
40 | npx servor
41 | ```
42 |
43 | > You can pass a GitHub repo as `` using the syntax `gh:/`
44 |
45 | - `` path to serve static files from (defaults to current directory `.`)
46 | - `` the file served for all non-file requests (defaults to `index.html`)
47 | - `` what port you want to serve the files from (defaults to `8080`)
48 |
49 | Optional flags passed as non-positional arguments:
50 |
51 | - `--browse` causes the browser to open when the server starts
52 | - `--reload` causes the browser to reload when files change
53 | - `--secure` starts the server with https using generated credentials
54 | - `--silent` prevents the server node process from logging to stdout
55 | - `--module` causes the server to wrap the root in script type module tags
56 | - `--static` causes the server to route nested index files if they exist
57 | - `--editor` opens a code editor (currently only vscode) at the project root
58 |
59 | Example usage with npm scripts in a `package.json` file after running `npm i servor -D`:
60 |
61 | ```json
62 | {
63 | "devDependencies": {
64 | "servor": "4.0.0"
65 | },
66 | "scripts": {
67 | "start": "servor www index.html 8080 --reload --browse"
68 | }
69 | }
70 | ```
71 |
72 | ### Generating Credentials
73 |
74 | > NOTE: This process depends on the `openssl` command existing (tested on macOS and linux only)
75 |
76 | The files `servor.crt` and `servor.key` need to exist for the server to start using https. If the files do not exist when the `--secure` flag is passed, then [`certify.sh`](/certify.sh) is invoked which:
77 |
78 | - Creates a local certificate authority used to generate self signed SSL certificates
79 | - Runs the appropriate `openssl` commands to produce:
80 | - a root certificate (pem) so the system will trust the self signed certificate
81 | - a public certificate (crt) that the server sends to clients
82 | - a private key for the certificate (key) to encrypt and decrypt traffic
83 |
84 | #### Adding credentials to the trusted store
85 |
86 | > NOTE: This process depends on the `sudo` and `security` commands existing (tested on macOS only)
87 |
88 | For the browser to trust self signed certificates the root certificate must be added to the system trusted store. This can be done automatically by running `sudo servor --secure` which:
89 |
90 | - Adds the root certificate to the system Keychain Access
91 | - Prevents the "⚠️ Your connection is not private" screen
92 | - Makes the 🔒 icon appear in the browsers address bar
93 |
94 | The approach was adopted from [@kingkool68/generate-ssl-certs-for-local-development](https://github.com/kingkool68/generate-ssl-certs-for-local-development)
95 |
96 | ## API Usage
97 |
98 | Use servor programmatically with node by requiring it as a module in your script:
99 |
100 | ```js
101 | const servor = require('servor');
102 | const instance = await servor({
103 | root: '.',
104 | fallback: 'index.html',
105 | module: false,
106 | static: false,
107 | reload: false,
108 | inject: ''
109 | credentials: null,
110 | port: 8080,
111 | });
112 | ```
113 |
114 | The `servor` function accepts a config object with optional props assigned the above default values if none are provided. Calling the `servor` function starts up a new server and returns an object describing its configuration.
115 |
116 | ```js
117 | const { url, root, protocol, port, ips } = await servor(config);
118 | ```
119 |
120 | ### Inject
121 |
122 | The `inject` property accepts a string that gets appended to the servers root document (which is `index.html` by default). This could be used to inject config or extend the development servers behavior and capabilities to suit specific environments.
123 |
124 | ```js
125 | const config = require('package.json');
126 | servor({ inject: `` });
127 | ```
128 |
129 | ### Credentials
130 |
131 | The `credentials` property accepts an object containing the entries `cert` and `key` which must both be valid for the server to start successfully. If valid credentials are provided then the server will start serving over https.
132 |
133 | It is possible to generate the appropriate credentials using the `--secure` CLI flag.
134 |
135 | ## Notes
136 |
137 | Thanks to all the contributors to this projects so far. If you find a bug please create an issue or if you have an idea for a new feature then fork the project and create a pull request. Let me know how you are using servør [on twitter](https://twitter.com/lukejacksonn).
138 |
--------------------------------------------------------------------------------
/utils/mimeTypes.js:
--------------------------------------------------------------------------------
1 | const types = {
2 | 'application/andrew-inset': ['ez'],
3 | 'application/applixware': ['aw'],
4 | 'application/atom+xml': ['atom'],
5 | 'application/atomcat+xml': ['atomcat'],
6 | 'application/atomsvc+xml': ['atomsvc'],
7 | 'application/bdoc': ['bdoc'],
8 | 'application/ccxml+xml': ['ccxml'],
9 | 'application/cdmi-capability': ['cdmia'],
10 | 'application/cdmi-container': ['cdmic'],
11 | 'application/cdmi-domain': ['cdmid'],
12 | 'application/cdmi-object': ['cdmio'],
13 | 'application/cdmi-queue': ['cdmiq'],
14 | 'application/cu-seeme': ['cu'],
15 | 'application/dash+xml': ['mpd'],
16 | 'application/davmount+xml': ['davmount'],
17 | 'application/docbook+xml': ['dbk'],
18 | 'application/dssc+der': ['dssc'],
19 | 'application/dssc+xml': ['xdssc'],
20 | 'application/ecmascript': ['ecma'],
21 | 'application/emma+xml': ['emma'],
22 | 'application/epub+zip': ['epub'],
23 | 'application/exi': ['exi'],
24 | 'application/font-tdpfr': ['pfr'],
25 | 'application/font-woff': [],
26 | 'application/font-woff2': [],
27 | 'application/geo+json': ['geojson'],
28 | 'application/gml+xml': ['gml'],
29 | 'application/gpx+xml': ['gpx'],
30 | 'application/gxf': ['gxf'],
31 | 'application/gzip': ['gz'],
32 | 'application/hyperstudio': ['stk'],
33 | 'application/inkml+xml': ['ink', 'inkml'],
34 | 'application/ipfix': ['ipfix'],
35 | 'application/java-archive': ['jar', 'war', 'ear'],
36 | 'application/java-serialized-object': ['ser'],
37 | 'application/java-vm': ['class'],
38 | 'application/javascript': ['js', 'mjs'],
39 | 'application/json': ['json', 'map'],
40 | 'application/json5': ['json5'],
41 | 'application/jsonml+json': ['jsonml'],
42 | 'application/ld+json': ['jsonld'],
43 | 'application/lost+xml': ['lostxml'],
44 | 'application/mac-binhex40': ['hqx'],
45 | 'application/mac-compactpro': ['cpt'],
46 | 'application/mads+xml': ['mads'],
47 | 'application/manifest+json': ['webmanifest'],
48 | 'application/marc': ['mrc'],
49 | 'application/marcxml+xml': ['mrcx'],
50 | 'application/mathematica': ['ma', 'nb', 'mb'],
51 | 'application/mathml+xml': ['mathml'],
52 | 'application/mbox': ['mbox'],
53 | 'application/mediaservercontrol+xml': ['mscml'],
54 | 'application/metalink+xml': ['metalink'],
55 | 'application/metalink4+xml': ['meta4'],
56 | 'application/mets+xml': ['mets'],
57 | 'application/mods+xml': ['mods'],
58 | 'application/mp21': ['m21', 'mp21'],
59 | 'application/mp4': ['mp4s', 'm4p'],
60 | 'application/msword': ['doc', 'dot'],
61 | 'application/mxf': ['mxf'],
62 | 'application/octet-stream': [
63 | 'bin',
64 | 'dms',
65 | 'lrf',
66 | 'mar',
67 | 'so',
68 | 'dist',
69 | 'distz',
70 | 'pkg',
71 | 'bpk',
72 | 'dump',
73 | 'elc',
74 | 'deploy',
75 | 'exe',
76 | 'dll',
77 | 'deb',
78 | 'dmg',
79 | 'iso',
80 | 'img',
81 | 'msi',
82 | 'msp',
83 | 'msm',
84 | 'buffer',
85 | ],
86 | 'application/oda': ['oda'],
87 | 'application/oebps-package+xml': ['opf'],
88 | 'application/ogg': ['ogx'],
89 | 'application/omdoc+xml': ['omdoc'],
90 | 'application/onenote': ['onetoc', 'onetoc2', 'onetmp', 'onepkg'],
91 | 'application/oxps': ['oxps'],
92 | 'application/patch-ops-error+xml': ['xer'],
93 | 'application/pdf': ['pdf'],
94 | 'application/pgp-encrypted': ['pgp'],
95 | 'application/pgp-signature': ['asc', 'sig'],
96 | 'application/pics-rules': ['prf'],
97 | 'application/pkcs10': ['p10'],
98 | 'application/pkcs7-mime': ['p7m', 'p7c'],
99 | 'application/pkcs7-signature': ['p7s'],
100 | 'application/pkcs8': ['p8'],
101 | 'application/pkix-attr-cert': ['ac'],
102 | 'application/pkix-cert': ['cer'],
103 | 'application/pkix-crl': ['crl'],
104 | 'application/pkix-pkipath': ['pkipath'],
105 | 'application/pkixcmp': ['pki'],
106 | 'application/pls+xml': ['pls'],
107 | 'application/postscript': ['ai', 'eps', 'ps'],
108 | 'application/prs.cww': ['cww'],
109 | 'application/pskc+xml': ['pskcxml'],
110 | 'application/raml+yaml': ['raml'],
111 | 'application/rdf+xml': ['rdf'],
112 | 'application/reginfo+xml': ['rif'],
113 | 'application/relax-ng-compact-syntax': ['rnc'],
114 | 'application/resource-lists+xml': ['rl'],
115 | 'application/resource-lists-diff+xml': ['rld'],
116 | 'application/rls-services+xml': ['rs'],
117 | 'application/rpki-ghostbusters': ['gbr'],
118 | 'application/rpki-manifest': ['mft'],
119 | 'application/rpki-roa': ['roa'],
120 | 'application/rsd+xml': ['rsd'],
121 | 'application/rss+xml': ['rss'],
122 | 'application/rtf': ['rtf'],
123 | 'application/sbml+xml': ['sbml'],
124 | 'application/scvp-cv-request': ['scq'],
125 | 'application/scvp-cv-response': ['scs'],
126 | 'application/scvp-vp-request': ['spq'],
127 | 'application/scvp-vp-response': ['spp'],
128 | 'application/sdp': ['sdp'],
129 | 'application/set-payment-initiation': ['setpay'],
130 | 'application/set-registration-initiation': ['setreg'],
131 | 'application/shf+xml': ['shf'],
132 | 'application/smil+xml': ['smi', 'smil'],
133 | 'application/sparql-query': ['rq'],
134 | 'application/sparql-results+xml': ['srx'],
135 | 'application/srgs': ['gram'],
136 | 'application/srgs+xml': ['grxml'],
137 | 'application/sru+xml': ['sru'],
138 | 'application/ssdl+xml': ['ssdl'],
139 | 'application/ssml+xml': ['ssml'],
140 | 'application/tei+xml': ['tei', 'teicorpus'],
141 | 'application/thraud+xml': ['tfi'],
142 | 'application/timestamped-data': ['tsd'],
143 | 'application/vnd.3gpp.pic-bw-large': ['plb'],
144 | 'application/vnd.3gpp.pic-bw-small': ['psb'],
145 | 'application/vnd.3gpp.pic-bw-var': ['pvb'],
146 | 'application/vnd.3gpp2.tcap': ['tcap'],
147 | 'application/vnd.3m.post-it-notes': ['pwn'],
148 | 'application/vnd.accpac.simply.aso': ['aso'],
149 | 'application/vnd.accpac.simply.imp': ['imp'],
150 | 'application/vnd.acucobol': ['acu'],
151 | 'application/vnd.acucorp': ['atc', 'acutc'],
152 | 'application/vnd.adobe.air-application-installer-package+zip': ['air'],
153 | 'application/vnd.adobe.formscentral.fcdt': ['fcdt'],
154 | 'application/vnd.adobe.fxp': ['fxp', 'fxpl'],
155 | 'application/vnd.adobe.xdp+xml': ['xdp'],
156 | 'application/vnd.adobe.xfdf': ['xfdf'],
157 | 'application/vnd.ahead.space': ['ahead'],
158 | 'application/vnd.airzip.filesecure.azf': ['azf'],
159 | 'application/vnd.airzip.filesecure.azs': ['azs'],
160 | 'application/vnd.amazon.ebook': ['azw'],
161 | 'application/vnd.americandynamics.acc': ['acc'],
162 | 'application/vnd.amiga.ami': ['ami'],
163 | 'application/vnd.android.package-archive': ['apk'],
164 | 'application/vnd.anser-web-certificate-issue-initiation': ['cii'],
165 | 'application/vnd.anser-web-funds-transfer-initiation': ['fti'],
166 | 'application/vnd.antix.game-component': ['atx'],
167 | 'application/vnd.apple.installer+xml': ['mpkg'],
168 | 'application/vnd.apple.mpegurl': ['m3u8'],
169 | 'application/vnd.apple.pkpass': ['pkpass'],
170 | 'application/vnd.aristanetworks.swi': ['swi'],
171 | 'application/vnd.astraea-software.iota': ['iota'],
172 | 'application/vnd.audiograph': ['aep'],
173 | 'application/vnd.blueice.multipass': ['mpm'],
174 | 'application/vnd.bmi': ['bmi'],
175 | 'application/vnd.businessobjects': ['rep'],
176 | 'application/vnd.chemdraw+xml': ['cdxml'],
177 | 'application/vnd.chipnuts.karaoke-mmd': ['mmd'],
178 | 'application/vnd.cinderella': ['cdy'],
179 | 'application/vnd.claymore': ['cla'],
180 | 'application/vnd.cloanto.rp9': ['rp9'],
181 | 'application/vnd.clonk.c4group': ['c4g', 'c4d', 'c4f', 'c4p', 'c4u'],
182 | 'application/vnd.cluetrust.cartomobile-config': ['c11amc'],
183 | 'application/vnd.cluetrust.cartomobile-config-pkg': ['c11amz'],
184 | 'application/vnd.commonspace': ['csp'],
185 | 'application/vnd.contact.cmsg': ['cdbcmsg'],
186 | 'application/vnd.cosmocaller': ['cmc'],
187 | 'application/vnd.crick.clicker': ['clkx'],
188 | 'application/vnd.crick.clicker.keyboard': ['clkk'],
189 | 'application/vnd.crick.clicker.palette': ['clkp'],
190 | 'application/vnd.crick.clicker.template': ['clkt'],
191 | 'application/vnd.crick.clicker.wordbank': ['clkw'],
192 | 'application/vnd.criticaltools.wbs+xml': ['wbs'],
193 | 'application/vnd.ctc-posml': ['pml'],
194 | 'application/vnd.cups-ppd': ['ppd'],
195 | 'application/vnd.curl.car': ['car'],
196 | 'application/vnd.curl.pcurl': ['pcurl'],
197 | 'application/vnd.dart': ['dart'],
198 | 'application/vnd.data-vision.rdz': ['rdz'],
199 | 'application/vnd.dece.data': ['uvf', 'uvvf', 'uvd', 'uvvd'],
200 | 'application/vnd.dece.ttml+xml': ['uvt', 'uvvt'],
201 | 'application/vnd.dece.unspecified': ['uvx', 'uvvx'],
202 | 'application/vnd.dece.zip': ['uvz', 'uvvz'],
203 | 'application/vnd.denovo.fcselayout-link': ['fe_launch'],
204 | 'application/vnd.dna': ['dna'],
205 | 'application/vnd.dolby.mlp': ['mlp'],
206 | 'application/vnd.dpgraph': ['dpg'],
207 | 'application/vnd.dreamfactory': ['dfac'],
208 | 'application/vnd.ds-keypoint': ['kpxx'],
209 | 'application/vnd.dvb.ait': ['ait'],
210 | 'application/vnd.dvb.service': ['svc'],
211 | 'application/vnd.dynageo': ['geo'],
212 | 'application/vnd.ecowin.chart': ['mag'],
213 | 'application/vnd.enliven': ['nml'],
214 | 'application/vnd.epson.esf': ['esf'],
215 | 'application/vnd.epson.msf': ['msf'],
216 | 'application/vnd.epson.quickanime': ['qam'],
217 | 'application/vnd.epson.salt': ['slt'],
218 | 'application/vnd.epson.ssf': ['ssf'],
219 | 'application/vnd.eszigno3+xml': ['es3', 'et3'],
220 | 'application/vnd.ezpix-album': ['ez2'],
221 | 'application/vnd.ezpix-package': ['ez3'],
222 | 'application/vnd.fdf': ['fdf'],
223 | 'application/vnd.fdsn.mseed': ['mseed'],
224 | 'application/vnd.fdsn.seed': ['seed', 'dataless'],
225 | 'application/vnd.flographit': ['gph'],
226 | 'application/vnd.fluxtime.clip': ['ftc'],
227 | 'application/vnd.framemaker': ['fm', 'frame', 'maker', 'book'],
228 | 'application/vnd.frogans.fnc': ['fnc'],
229 | 'application/vnd.frogans.ltf': ['ltf'],
230 | 'application/vnd.fsc.weblaunch': ['fsc'],
231 | 'application/vnd.fujitsu.oasys': ['oas'],
232 | 'application/vnd.fujitsu.oasys2': ['oa2'],
233 | 'application/vnd.fujitsu.oasys3': ['oa3'],
234 | 'application/vnd.fujitsu.oasysgp': ['fg5'],
235 | 'application/vnd.fujitsu.oasysprs': ['bh2'],
236 | 'application/vnd.fujixerox.ddd': ['ddd'],
237 | 'application/vnd.fujixerox.docuworks': ['xdw'],
238 | 'application/vnd.fujixerox.docuworks.binder': ['xbd'],
239 | 'application/vnd.fuzzysheet': ['fzs'],
240 | 'application/vnd.genomatix.tuxedo': ['txd'],
241 | 'application/vnd.geogebra.file': ['ggb'],
242 | 'application/vnd.geogebra.tool': ['ggt'],
243 | 'application/vnd.geometry-explorer': ['gex', 'gre'],
244 | 'application/vnd.geonext': ['gxt'],
245 | 'application/vnd.geoplan': ['g2w'],
246 | 'application/vnd.geospace': ['g3w'],
247 | 'application/vnd.gmx': ['gmx'],
248 | 'application/vnd.google-apps.document': ['gdoc'],
249 | 'application/vnd.google-apps.presentation': ['gslides'],
250 | 'application/vnd.google-apps.spreadsheet': ['gsheet'],
251 | 'application/vnd.google-earth.kml+xml': ['kml'],
252 | 'application/vnd.google-earth.kmz': ['kmz'],
253 | 'application/vnd.grafeq': ['gqf', 'gqs'],
254 | 'application/vnd.groove-account': ['gac'],
255 | 'application/vnd.groove-help': ['ghf'],
256 | 'application/vnd.groove-identity-message': ['gim'],
257 | 'application/vnd.groove-injector': ['grv'],
258 | 'application/vnd.groove-tool-message': ['gtm'],
259 | 'application/vnd.groove-tool-template': ['tpl'],
260 | 'application/vnd.groove-vcard': ['vcg'],
261 | 'application/vnd.hal+xml': ['hal'],
262 | 'application/vnd.handheld-entertainment+xml': ['zmm'],
263 | 'application/vnd.hbci': ['hbci'],
264 | 'application/vnd.hhe.lesson-player': ['les'],
265 | 'application/vnd.hp-hpgl': ['hpgl'],
266 | 'application/vnd.hp-hpid': ['hpid'],
267 | 'application/vnd.hp-hps': ['hps'],
268 | 'application/vnd.hp-jlyt': ['jlt'],
269 | 'application/vnd.hp-pcl': ['pcl'],
270 | 'application/vnd.hp-pclxl': ['pclxl'],
271 | 'application/vnd.hydrostatix.sof-data': ['sfd-hdstx'],
272 | 'application/vnd.ibm.minipay': ['mpy'],
273 | 'application/vnd.ibm.modcap': ['afp', 'listafp', 'list3820'],
274 | 'application/vnd.ibm.rights-management': ['irm'],
275 | 'application/vnd.ibm.secure-container': ['sc'],
276 | 'application/vnd.iccprofile': ['icc', 'icm'],
277 | 'application/vnd.igloader': ['igl'],
278 | 'application/vnd.immervision-ivp': ['ivp'],
279 | 'application/vnd.immervision-ivu': ['ivu'],
280 | 'application/vnd.insors.igm': ['igm'],
281 | 'application/vnd.intercon.formnet': ['xpw', 'xpx'],
282 | 'application/vnd.intergeo': ['i2g'],
283 | 'application/vnd.intu.qbo': ['qbo'],
284 | 'application/vnd.intu.qfx': ['qfx'],
285 | 'application/vnd.ipunplugged.rcprofile': ['rcprofile'],
286 | 'application/vnd.irepository.package+xml': ['irp'],
287 | 'application/vnd.is-xpr': ['xpr'],
288 | 'application/vnd.isac.fcs': ['fcs'],
289 | 'application/vnd.jam': ['jam'],
290 | 'application/vnd.jcp.javame.midlet-rms': ['rms'],
291 | 'application/vnd.jisp': ['jisp'],
292 | 'application/vnd.joost.joda-archive': ['joda'],
293 | 'application/vnd.kahootz': ['ktz', 'ktr'],
294 | 'application/vnd.kde.karbon': ['karbon'],
295 | 'application/vnd.kde.kchart': ['chrt'],
296 | 'application/vnd.kde.kformula': ['kfo'],
297 | 'application/vnd.kde.kivio': ['flw'],
298 | 'application/vnd.kde.kontour': ['kon'],
299 | 'application/vnd.kde.kpresenter': ['kpr', 'kpt'],
300 | 'application/vnd.kde.kspread': ['ksp'],
301 | 'application/vnd.kde.kword': ['kwd', 'kwt'],
302 | 'application/vnd.kenameaapp': ['htke'],
303 | 'application/vnd.kidspiration': ['kia'],
304 | 'application/vnd.kinar': ['kne', 'knp'],
305 | 'application/vnd.koan': ['skp', 'skd', 'skt', 'skm'],
306 | 'application/vnd.kodak-descriptor': ['sse'],
307 | 'application/vnd.las.las+xml': ['lasxml'],
308 | 'application/vnd.llamagraphics.life-balance.desktop': ['lbd'],
309 | 'application/vnd.llamagraphics.life-balance.exchange+xml': ['lbe'],
310 | 'application/vnd.lotus-1-2-3': ['123'],
311 | 'application/vnd.lotus-approach': ['apr'],
312 | 'application/vnd.lotus-freelance': ['pre'],
313 | 'application/vnd.lotus-notes': ['nsf'],
314 | 'application/vnd.lotus-organizer': ['org'],
315 | 'application/vnd.lotus-screencam': ['scm'],
316 | 'application/vnd.lotus-wordpro': ['lwp'],
317 | 'application/vnd.macports.portpkg': ['portpkg'],
318 | 'application/vnd.mcd': ['mcd'],
319 | 'application/vnd.medcalcdata': ['mc1'],
320 | 'application/vnd.mediastation.cdkey': ['cdkey'],
321 | 'application/vnd.mfer': ['mwf'],
322 | 'application/vnd.mfmp': ['mfm'],
323 | 'application/vnd.micrografx.flo': ['flo'],
324 | 'application/vnd.micrografx.igx': ['igx'],
325 | 'application/vnd.mif': ['mif'],
326 | 'application/vnd.mobius.daf': ['daf'],
327 | 'application/vnd.mobius.dis': ['dis'],
328 | 'application/vnd.mobius.mbk': ['mbk'],
329 | 'application/vnd.mobius.mqy': ['mqy'],
330 | 'application/vnd.mobius.msl': ['msl'],
331 | 'application/vnd.mobius.plc': ['plc'],
332 | 'application/vnd.mobius.txf': ['txf'],
333 | 'application/vnd.mophun.application': ['mpn'],
334 | 'application/vnd.mophun.certificate': ['mpc'],
335 | 'application/vnd.mozilla.xul+xml': ['xul'],
336 | 'application/vnd.ms-artgalry': ['cil'],
337 | 'application/vnd.ms-cab-compressed': ['cab'],
338 | 'application/vnd.ms-excel': ['xls', 'xlm', 'xla', 'xlc', 'xlt', 'xlw'],
339 | 'application/vnd.ms-excel.addin.macroenabled.12': ['xlam'],
340 | 'application/vnd.ms-excel.sheet.binary.macroenabled.12': ['xlsb'],
341 | 'application/vnd.ms-excel.sheet.macroenabled.12': ['xlsm'],
342 | 'application/vnd.ms-excel.template.macroenabled.12': ['xltm'],
343 | 'application/vnd.ms-fontobject': ['eot'],
344 | 'application/vnd.ms-htmlhelp': ['chm'],
345 | 'application/vnd.ms-ims': ['ims'],
346 | 'application/vnd.ms-lrm': ['lrm'],
347 | 'application/vnd.ms-officetheme': ['thmx'],
348 | 'application/vnd.ms-outlook': ['msg'],
349 | 'application/vnd.ms-pki.seccat': ['cat'],
350 | 'application/vnd.ms-pki.stl': ['stl'],
351 | 'application/vnd.ms-powerpoint': ['ppt', 'pps', 'pot'],
352 | 'application/vnd.ms-powerpoint.addin.macroenabled.12': ['ppam'],
353 | 'application/vnd.ms-powerpoint.presentation.macroenabled.12': ['pptm'],
354 | 'application/vnd.ms-powerpoint.slide.macroenabled.12': ['sldm'],
355 | 'application/vnd.ms-powerpoint.slideshow.macroenabled.12': ['ppsm'],
356 | 'application/vnd.ms-powerpoint.template.macroenabled.12': ['potm'],
357 | 'application/vnd.ms-project': ['mpp', 'mpt'],
358 | 'application/vnd.ms-word.document.macroenabled.12': ['docm'],
359 | 'application/vnd.ms-word.template.macroenabled.12': ['dotm'],
360 | 'application/vnd.ms-works': ['wps', 'wks', 'wcm', 'wdb'],
361 | 'application/vnd.ms-wpl': ['wpl'],
362 | 'application/vnd.ms-xpsdocument': ['xps'],
363 | 'application/vnd.mseq': ['mseq'],
364 | 'application/vnd.musician': ['mus'],
365 | 'application/vnd.muvee.style': ['msty'],
366 | 'application/vnd.mynfc': ['taglet'],
367 | 'application/vnd.neurolanguage.nlu': ['nlu'],
368 | 'application/vnd.nitf': ['ntf', 'nitf'],
369 | 'application/vnd.noblenet-directory': ['nnd'],
370 | 'application/vnd.noblenet-sealer': ['nns'],
371 | 'application/vnd.noblenet-web': ['nnw'],
372 | 'application/vnd.nokia.n-gage.data': ['ngdat'],
373 | 'application/vnd.nokia.n-gage.symbian.install': ['n-gage'],
374 | 'application/vnd.nokia.radio-preset': ['rpst'],
375 | 'application/vnd.nokia.radio-presets': ['rpss'],
376 | 'application/vnd.novadigm.edm': ['edm'],
377 | 'application/vnd.novadigm.edx': ['edx'],
378 | 'application/vnd.novadigm.ext': ['ext'],
379 | 'application/vnd.oasis.opendocument.chart': ['odc'],
380 | 'application/vnd.oasis.opendocument.chart-template': ['otc'],
381 | 'application/vnd.oasis.opendocument.database': ['odb'],
382 | 'application/vnd.oasis.opendocument.formula': ['odf'],
383 | 'application/vnd.oasis.opendocument.formula-template': ['odft'],
384 | 'application/vnd.oasis.opendocument.graphics': ['odg'],
385 | 'application/vnd.oasis.opendocument.graphics-template': ['otg'],
386 | 'application/vnd.oasis.opendocument.image': ['odi'],
387 | 'application/vnd.oasis.opendocument.image-template': ['oti'],
388 | 'application/vnd.oasis.opendocument.presentation': ['odp'],
389 | 'application/vnd.oasis.opendocument.presentation-template': ['otp'],
390 | 'application/vnd.oasis.opendocument.spreadsheet': ['ods'],
391 | 'application/vnd.oasis.opendocument.spreadsheet-template': ['ots'],
392 | 'application/vnd.oasis.opendocument.text': ['odt'],
393 | 'application/vnd.oasis.opendocument.text-master': ['odm'],
394 | 'application/vnd.oasis.opendocument.text-template': ['ott'],
395 | 'application/vnd.oasis.opendocument.text-web': ['oth'],
396 | 'application/vnd.olpc-sugar': ['xo'],
397 | 'application/vnd.oma.dd2+xml': ['dd2'],
398 | 'application/vnd.openofficeorg.extension': ['oxt'],
399 | 'application/vnd.openxmlformats-officedocument.presentationml.presentation': [
400 | 'pptx',
401 | ],
402 | 'application/vnd.openxmlformats-officedocument.presentationml.slide': [
403 | 'sldx',
404 | ],
405 | 'application/vnd.openxmlformats-officedocument.presentationml.slideshow': [
406 | 'ppsx',
407 | ],
408 | 'application/vnd.openxmlformats-officedocument.presentationml.template': [
409 | 'potx',
410 | ],
411 | 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': ['xlsx'],
412 | 'application/vnd.openxmlformats-officedocument.spreadsheetml.template': [
413 | 'xltx',
414 | ],
415 | 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': [
416 | 'docx',
417 | ],
418 | 'application/vnd.openxmlformats-officedocument.wordprocessingml.template': [
419 | 'dotx',
420 | ],
421 | 'application/vnd.osgeo.mapguide.package': ['mgp'],
422 | 'application/vnd.osgi.dp': ['dp'],
423 | 'application/vnd.osgi.subsystem': ['esa'],
424 | 'application/vnd.palm': ['pdb', 'pqa', 'oprc'],
425 | 'application/vnd.pawaafile': ['paw'],
426 | 'application/vnd.pg.format': ['str'],
427 | 'application/vnd.pg.osasli': ['ei6'],
428 | 'application/vnd.picsel': ['efif'],
429 | 'application/vnd.pmi.widget': ['wg'],
430 | 'application/vnd.pocketlearn': ['plf'],
431 | 'application/vnd.powerbuilder6': ['pbd'],
432 | 'application/vnd.previewsystems.box': ['box'],
433 | 'application/vnd.proteus.magazine': ['mgz'],
434 | 'application/vnd.publishare-delta-tree': ['qps'],
435 | 'application/vnd.pvi.ptid1': ['ptid'],
436 | 'application/vnd.quark.quarkxpress': [
437 | 'qxd',
438 | 'qxt',
439 | 'qwd',
440 | 'qwt',
441 | 'qxl',
442 | 'qxb',
443 | ],
444 | 'application/vnd.realvnc.bed': ['bed'],
445 | 'application/vnd.recordare.musicxml': ['mxl'],
446 | 'application/vnd.recordare.musicxml+xml': ['musicxml'],
447 | 'application/vnd.rig.cryptonote': ['cryptonote'],
448 | 'application/vnd.rim.cod': ['cod'],
449 | 'application/vnd.rn-realmedia': ['rm'],
450 | 'application/vnd.rn-realmedia-vbr': ['rmvb'],
451 | 'application/vnd.route66.link66+xml': ['link66'],
452 | 'application/vnd.sailingtracker.track': ['st'],
453 | 'application/vnd.seemail': ['see'],
454 | 'application/vnd.sema': ['sema'],
455 | 'application/vnd.semd': ['semd'],
456 | 'application/vnd.semf': ['semf'],
457 | 'application/vnd.shana.informed.formdata': ['ifm'],
458 | 'application/vnd.shana.informed.formtemplate': ['itp'],
459 | 'application/vnd.shana.informed.interchange': ['iif'],
460 | 'application/vnd.shana.informed.package': ['ipk'],
461 | 'application/vnd.simtech-mindmapper': ['twd', 'twds'],
462 | 'application/vnd.smaf': ['mmf'],
463 | 'application/vnd.smart.teacher': ['teacher'],
464 | 'application/vnd.solent.sdkm+xml': ['sdkm', 'sdkd'],
465 | 'application/vnd.spotfire.dxp': ['dxp'],
466 | 'application/vnd.spotfire.sfs': ['sfs'],
467 | 'application/vnd.stardivision.calc': ['sdc'],
468 | 'application/vnd.stardivision.draw': ['sda'],
469 | 'application/vnd.stardivision.impress': ['sdd'],
470 | 'application/vnd.stardivision.math': ['smf'],
471 | 'application/vnd.stardivision.writer': ['sdw', 'vor'],
472 | 'application/vnd.stardivision.writer-global': ['sgl'],
473 | 'application/vnd.stepmania.package': ['smzip'],
474 | 'application/vnd.stepmania.stepchart': ['sm'],
475 | 'application/vnd.sun.wadl+xml': ['wadl'],
476 | 'application/vnd.sun.xml.calc': ['sxc'],
477 | 'application/vnd.sun.xml.calc.template': ['stc'],
478 | 'application/vnd.sun.xml.draw': ['sxd'],
479 | 'application/vnd.sun.xml.draw.template': ['std'],
480 | 'application/vnd.sun.xml.impress': ['sxi'],
481 | 'application/vnd.sun.xml.impress.template': ['sti'],
482 | 'application/vnd.sun.xml.math': ['sxm'],
483 | 'application/vnd.sun.xml.writer': ['sxw'],
484 | 'application/vnd.sun.xml.writer.global': ['sxg'],
485 | 'application/vnd.sun.xml.writer.template': ['stw'],
486 | 'application/vnd.sus-calendar': ['sus', 'susp'],
487 | 'application/vnd.svd': ['svd'],
488 | 'application/vnd.symbian.install': ['sis', 'sisx'],
489 | 'application/vnd.syncml+xml': ['xsm'],
490 | 'application/vnd.syncml.dm+wbxml': ['bdm'],
491 | 'application/vnd.syncml.dm+xml': ['xdm'],
492 | 'application/vnd.tao.intent-module-archive': ['tao'],
493 | 'application/vnd.tcpdump.pcap': ['pcap', 'cap', 'dmp'],
494 | 'application/vnd.tmobile-livetv': ['tmo'],
495 | 'application/vnd.trid.tpt': ['tpt'],
496 | 'application/vnd.triscape.mxs': ['mxs'],
497 | 'application/vnd.trueapp': ['tra'],
498 | 'application/vnd.ufdl': ['ufd', 'ufdl'],
499 | 'application/vnd.uiq.theme': ['utz'],
500 | 'application/vnd.umajin': ['umj'],
501 | 'application/vnd.unity': ['unityweb'],
502 | 'application/vnd.uoml+xml': ['uoml'],
503 | 'application/vnd.vcx': ['vcx'],
504 | 'application/vnd.visio': ['vsd', 'vst', 'vss', 'vsw'],
505 | 'application/vnd.visionary': ['vis'],
506 | 'application/vnd.vsf': ['vsf'],
507 | 'application/vnd.wap.wbxml': ['wbxml'],
508 | 'application/vnd.wap.wmlc': ['wmlc'],
509 | 'application/vnd.wap.wmlscriptc': ['wmlsc'],
510 | 'application/vnd.webturbo': ['wtb'],
511 | 'application/vnd.wolfram.player': ['nbp'],
512 | 'application/vnd.wordperfect': ['wpd'],
513 | 'application/vnd.wqd': ['wqd'],
514 | 'application/vnd.wt.stf': ['stf'],
515 | 'application/vnd.xara': ['xar'],
516 | 'application/vnd.xfdl': ['xfdl'],
517 | 'application/vnd.yamaha.hv-dic': ['hvd'],
518 | 'application/vnd.yamaha.hv-script': ['hvs'],
519 | 'application/vnd.yamaha.hv-voice': ['hvp'],
520 | 'application/vnd.yamaha.openscoreformat': ['osf'],
521 | 'application/vnd.yamaha.openscoreformat.osfpvg+xml': ['osfpvg'],
522 | 'application/vnd.yamaha.smaf-audio': ['saf'],
523 | 'application/vnd.yamaha.smaf-phrase': ['spf'],
524 | 'application/vnd.yellowriver-custom-menu': ['cmp'],
525 | 'application/vnd.zul': ['zir', 'zirz'],
526 | 'application/vnd.zzazz.deck+xml': ['zaz'],
527 | 'application/voicexml+xml': ['vxml'],
528 | 'application/wasm': ['wasm'],
529 | 'application/widget': ['wgt'],
530 | 'application/winhlp': ['hlp'],
531 | 'application/wsdl+xml': ['wsdl'],
532 | 'application/wspolicy+xml': ['wspolicy'],
533 | 'application/x-7z-compressed': ['7z'],
534 | 'application/x-abiword': ['abw'],
535 | 'application/x-ace-compressed': ['ace'],
536 | 'application/x-apple-diskimage': [],
537 | 'application/x-arj': ['arj'],
538 | 'application/x-authorware-bin': ['aab', 'x32', 'u32', 'vox'],
539 | 'application/x-authorware-map': ['aam'],
540 | 'application/x-authorware-seg': ['aas'],
541 | 'application/x-bcpio': ['bcpio'],
542 | 'application/x-bdoc': [],
543 | 'application/x-bittorrent': ['torrent'],
544 | 'application/x-blorb': ['blb', 'blorb'],
545 | 'application/x-bzip': ['bz'],
546 | 'application/x-bzip2': ['bz2', 'boz'],
547 | 'application/x-cbr': ['cbr', 'cba', 'cbt', 'cbz', 'cb7'],
548 | 'application/x-cdlink': ['vcd'],
549 | 'application/x-cfs-compressed': ['cfs'],
550 | 'application/x-chat': ['chat'],
551 | 'application/x-chess-pgn': ['pgn'],
552 | 'application/x-chrome-extension': ['crx'],
553 | 'application/x-cocoa': ['cco'],
554 | 'application/x-conference': ['nsc'],
555 | 'application/x-cpio': ['cpio'],
556 | 'application/x-csh': ['csh'],
557 | 'application/x-debian-package': ['udeb'],
558 | 'application/x-dgc-compressed': ['dgc'],
559 | 'application/x-director': [
560 | 'dir',
561 | 'dcr',
562 | 'dxr',
563 | 'cst',
564 | 'cct',
565 | 'cxt',
566 | 'w3d',
567 | 'fgd',
568 | 'swa',
569 | ],
570 | 'application/x-doom': ['wad'],
571 | 'application/x-dtbncx+xml': ['ncx'],
572 | 'application/x-dtbook+xml': ['dtb'],
573 | 'application/x-dtbresource+xml': ['res'],
574 | 'application/x-dvi': ['dvi'],
575 | 'application/x-envoy': ['evy'],
576 | 'application/x-eva': ['eva'],
577 | 'application/x-font-bdf': ['bdf'],
578 | 'application/x-font-ghostscript': ['gsf'],
579 | 'application/x-font-linux-psf': ['psf'],
580 | 'application/x-font-pcf': ['pcf'],
581 | 'application/x-font-snf': ['snf'],
582 | 'application/x-font-type1': ['pfa', 'pfb', 'pfm', 'afm'],
583 | 'application/x-freearc': ['arc'],
584 | 'application/x-futuresplash': ['spl'],
585 | 'application/x-gca-compressed': ['gca'],
586 | 'application/x-glulx': ['ulx'],
587 | 'application/x-gnumeric': ['gnumeric'],
588 | 'application/x-gramps-xml': ['gramps'],
589 | 'application/x-gtar': ['gtar'],
590 | 'application/x-hdf': ['hdf'],
591 | 'application/x-httpd-php': ['php'],
592 | 'application/x-install-instructions': ['install'],
593 | 'application/x-iso9660-image': [],
594 | 'application/x-java-archive-diff': ['jardiff'],
595 | 'application/x-java-jnlp-file': ['jnlp'],
596 | 'application/x-latex': ['latex'],
597 | 'application/x-lua-bytecode': ['luac'],
598 | 'application/x-lzh-compressed': ['lzh', 'lha'],
599 | 'application/x-makeself': ['run'],
600 | 'application/x-mie': ['mie'],
601 | 'application/x-mobipocket-ebook': ['prc', 'mobi'],
602 | 'application/x-ms-application': ['application'],
603 | 'application/x-ms-shortcut': ['lnk'],
604 | 'application/x-ms-wmd': ['wmd'],
605 | 'application/x-ms-wmz': ['wmz'],
606 | 'application/x-ms-xbap': ['xbap'],
607 | 'application/x-msaccess': ['mdb'],
608 | 'application/x-msbinder': ['obd'],
609 | 'application/x-mscardfile': ['crd'],
610 | 'application/x-msclip': ['clp'],
611 | 'application/x-msdos-program': [],
612 | 'application/x-msdownload': ['com', 'bat'],
613 | 'application/x-msmediaview': ['mvb', 'm13', 'm14'],
614 | 'application/x-msmetafile': ['wmf', 'emf', 'emz'],
615 | 'application/x-msmoney': ['mny'],
616 | 'application/x-mspublisher': ['pub'],
617 | 'application/x-msschedule': ['scd'],
618 | 'application/x-msterminal': ['trm'],
619 | 'application/x-mswrite': ['wri'],
620 | 'application/x-netcdf': ['nc', 'cdf'],
621 | 'application/x-ns-proxy-autoconfig': ['pac'],
622 | 'application/x-nzb': ['nzb'],
623 | 'application/x-perl': ['pl', 'pm'],
624 | 'application/x-pilot': [],
625 | 'application/x-pkcs12': ['p12', 'pfx'],
626 | 'application/x-pkcs7-certificates': ['p7b', 'spc'],
627 | 'application/x-pkcs7-certreqresp': ['p7r'],
628 | 'application/x-rar-compressed': ['rar'],
629 | 'application/x-redhat-package-manager': ['rpm'],
630 | 'application/x-research-info-systems': ['ris'],
631 | 'application/x-sea': ['sea'],
632 | 'application/x-sh': ['sh'],
633 | 'application/x-shar': ['shar'],
634 | 'application/x-shockwave-flash': ['swf'],
635 | 'application/x-silverlight-app': ['xap'],
636 | 'application/x-sql': ['sql'],
637 | 'application/x-stuffit': ['sit'],
638 | 'application/x-stuffitx': ['sitx'],
639 | 'application/x-subrip': ['srt'],
640 | 'application/x-sv4cpio': ['sv4cpio'],
641 | 'application/x-sv4crc': ['sv4crc'],
642 | 'application/x-t3vm-image': ['t3'],
643 | 'application/x-tads': ['gam'],
644 | 'application/x-tar': ['tar'],
645 | 'application/x-tcl': ['tcl', 'tk'],
646 | 'application/x-tex': ['tex'],
647 | 'application/x-tex-tfm': ['tfm'],
648 | 'application/x-texinfo': ['texinfo', 'texi'],
649 | 'application/x-tgif': ['obj'],
650 | 'application/x-ustar': ['ustar'],
651 | 'application/x-virtualbox-hdd': ['hdd'],
652 | 'application/x-virtualbox-ova': ['ova'],
653 | 'application/x-virtualbox-ovf': ['ovf'],
654 | 'application/x-virtualbox-vbox': ['vbox'],
655 | 'application/x-virtualbox-vbox-extpack': ['vbox-extpack'],
656 | 'application/x-virtualbox-vdi': ['vdi'],
657 | 'application/x-virtualbox-vhd': ['vhd'],
658 | 'application/x-virtualbox-vmdk': ['vmdk'],
659 | 'application/x-wais-source': ['src'],
660 | 'application/x-web-app-manifest+json': ['webapp'],
661 | 'application/x-x509-ca-cert': ['der', 'crt', 'pem'],
662 | 'application/x-xfig': ['fig'],
663 | 'application/x-xliff+xml': ['xlf'],
664 | 'application/x-xpinstall': ['xpi'],
665 | 'application/x-xz': ['xz'],
666 | 'application/x-zmachine': ['z1', 'z2', 'z3', 'z4', 'z5', 'z6', 'z7', 'z8'],
667 | 'application/xaml+xml': ['xaml'],
668 | 'application/xcap-diff+xml': ['xdf'],
669 | 'application/xenc+xml': ['xenc'],
670 | 'application/xhtml+xml': ['xhtml', 'xht'],
671 | 'application/xml': ['xml', 'xsl', 'xsd', 'rng'],
672 | 'application/xml-dtd': ['dtd'],
673 | 'application/xop+xml': ['xop'],
674 | 'application/xproc+xml': ['xpl'],
675 | 'application/xslt+xml': ['xslt'],
676 | 'application/xspf+xml': ['xspf'],
677 | 'application/xv+xml': ['mxml', 'xhvml', 'xvml', 'xvm'],
678 | 'application/yang': ['yang'],
679 | 'application/yin+xml': ['yin'],
680 | 'application/zip': ['zip'],
681 | 'audio/3gpp': [],
682 | 'audio/adpcm': ['adp'],
683 | 'audio/basic': ['au', 'snd'],
684 | 'audio/midi': ['mid', 'midi', 'kar', 'rmi'],
685 | 'audio/mp3': [],
686 | 'audio/mp4': ['m4a', 'mp4a'],
687 | 'audio/mpeg': ['mpga', 'mp2', 'mp2a', 'mp3', 'm2a', 'm3a'],
688 | 'audio/ogg': ['oga', 'ogg', 'spx'],
689 | 'audio/s3m': ['s3m'],
690 | 'audio/silk': ['sil'],
691 | 'audio/vnd.dece.audio': ['uva', 'uvva'],
692 | 'audio/vnd.digital-winds': ['eol'],
693 | 'audio/vnd.dra': ['dra'],
694 | 'audio/vnd.dts': ['dts'],
695 | 'audio/vnd.dts.hd': ['dtshd'],
696 | 'audio/vnd.lucent.voice': ['lvp'],
697 | 'audio/vnd.ms-playready.media.pya': ['pya'],
698 | 'audio/vnd.nuera.ecelp4800': ['ecelp4800'],
699 | 'audio/vnd.nuera.ecelp7470': ['ecelp7470'],
700 | 'audio/vnd.nuera.ecelp9600': ['ecelp9600'],
701 | 'audio/vnd.rip': ['rip'],
702 | 'audio/wav': ['wav'],
703 | 'audio/wave': [],
704 | 'audio/webm': ['weba'],
705 | 'audio/x-aac': ['aac'],
706 | 'audio/x-aiff': ['aif', 'aiff', 'aifc'],
707 | 'audio/x-caf': ['caf'],
708 | 'audio/x-flac': ['flac'],
709 | 'audio/x-m4a': [],
710 | 'audio/x-matroska': ['mka'],
711 | 'audio/x-mpegurl': ['m3u'],
712 | 'audio/x-ms-wax': ['wax'],
713 | 'audio/x-ms-wma': ['wma'],
714 | 'audio/x-pn-realaudio': ['ram', 'ra'],
715 | 'audio/x-pn-realaudio-plugin': ['rmp'],
716 | 'audio/x-realaudio': [],
717 | 'audio/x-wav': [],
718 | 'audio/xm': ['xm'],
719 | 'chemical/x-cdx': ['cdx'],
720 | 'chemical/x-cif': ['cif'],
721 | 'chemical/x-cmdf': ['cmdf'],
722 | 'chemical/x-cml': ['cml'],
723 | 'chemical/x-csml': ['csml'],
724 | 'chemical/x-xyz': ['xyz'],
725 | 'font/collection': ['ttc'],
726 | 'font/otf': ['otf'],
727 | 'font/ttf': ['ttf'],
728 | 'font/woff': ['woff'],
729 | 'font/woff2': ['woff2'],
730 | 'image/apng': ['apng'],
731 | 'image/bmp': ['bmp'],
732 | 'image/cgm': ['cgm'],
733 | 'image/g3fax': ['g3'],
734 | 'image/gif': ['gif'],
735 | 'image/ief': ['ief'],
736 | 'image/jp2': ['jp2', 'jpg2'],
737 | 'image/jpeg': ['jpeg', 'jpg', 'jpe'],
738 | 'image/jpm': ['jpm'],
739 | 'image/jpx': ['jpx', 'jpf'],
740 | 'image/ktx': ['ktx'],
741 | 'image/png': ['png'],
742 | 'image/prs.btif': ['btif'],
743 | 'image/sgi': ['sgi'],
744 | 'image/svg+xml': ['svg', 'svgz'],
745 | 'image/tiff': ['tiff', 'tif'],
746 | 'image/vnd.adobe.photoshop': ['psd'],
747 | 'image/vnd.dece.graphic': ['uvi', 'uvvi', 'uvg', 'uvvg'],
748 | 'image/vnd.djvu': ['djvu', 'djv'],
749 | 'image/vnd.dvb.subtitle': [],
750 | 'image/vnd.dwg': ['dwg'],
751 | 'image/vnd.dxf': ['dxf'],
752 | 'image/vnd.fastbidsheet': ['fbs'],
753 | 'image/vnd.fpx': ['fpx'],
754 | 'image/vnd.fst': ['fst'],
755 | 'image/vnd.fujixerox.edmics-mmr': ['mmr'],
756 | 'image/vnd.fujixerox.edmics-rlc': ['rlc'],
757 | 'image/vnd.ms-modi': ['mdi'],
758 | 'image/vnd.ms-photo': ['wdp'],
759 | 'image/vnd.net-fpx': ['npx'],
760 | 'image/vnd.wap.wbmp': ['wbmp'],
761 | 'image/vnd.xiff': ['xif'],
762 | 'image/webp': ['webp'],
763 | 'image/x-3ds': ['3ds'],
764 | 'image/x-cmu-raster': ['ras'],
765 | 'image/x-cmx': ['cmx'],
766 | 'image/x-freehand': ['fh', 'fhc', 'fh4', 'fh5', 'fh7'],
767 | 'image/x-icon': ['ico'],
768 | 'image/x-jng': ['jng'],
769 | 'image/x-mrsid-image': ['sid'],
770 | 'image/x-ms-bmp': [],
771 | 'image/x-pcx': ['pcx'],
772 | 'image/x-pict': ['pic', 'pct'],
773 | 'image/x-portable-anymap': ['pnm'],
774 | 'image/x-portable-bitmap': ['pbm'],
775 | 'image/x-portable-graymap': ['pgm'],
776 | 'image/x-portable-pixmap': ['ppm'],
777 | 'image/x-rgb': ['rgb'],
778 | 'image/x-tga': ['tga'],
779 | 'image/x-xbitmap': ['xbm'],
780 | 'image/x-xpixmap': ['xpm'],
781 | 'image/x-xwindowdump': ['xwd'],
782 | 'message/rfc822': ['eml', 'mime'],
783 | 'model/gltf+json': ['gltf'],
784 | 'model/gltf-binary': ['glb'],
785 | 'model/iges': ['igs', 'iges'],
786 | 'model/mesh': ['msh', 'mesh', 'silo'],
787 | 'model/vnd.collada+xml': ['dae'],
788 | 'model/vnd.dwf': ['dwf'],
789 | 'model/vnd.gdl': ['gdl'],
790 | 'model/vnd.gtw': ['gtw'],
791 | 'model/vnd.mts': ['mts'],
792 | 'model/vnd.vtu': ['vtu'],
793 | 'model/vrml': ['wrl', 'vrml'],
794 | 'model/x3d+binary': ['x3db', 'x3dbz'],
795 | 'model/x3d+vrml': ['x3dv', 'x3dvz'],
796 | 'model/x3d+xml': ['x3d', 'x3dz'],
797 | 'text/cache-manifest': ['appcache', 'manifest'],
798 | 'text/calendar': ['ics', 'ifb'],
799 | 'text/coffeescript': ['coffee', 'litcoffee'],
800 | 'text/css': ['css'],
801 | 'text/csv': ['csv'],
802 | 'text/hjson': ['hjson'],
803 | 'text/html': ['html', 'htm', 'shtml'],
804 | 'text/jade': ['jade'],
805 | 'text/jsx': ['jsx'],
806 | 'text/less': ['less'],
807 | 'text/markdown': ['markdown', 'md'],
808 | 'text/mathml': ['mml'],
809 | 'text/n3': ['n3'],
810 | 'text/plain': ['txt', 'text', 'conf', 'def', 'list', 'log', 'in', 'ini'],
811 | 'text/prs.lines.tag': ['dsc'],
812 | 'text/richtext': ['rtx'],
813 | 'text/rtf': [],
814 | 'text/sgml': ['sgml', 'sgm'],
815 | 'text/slim': ['slim', 'slm'],
816 | 'text/stylus': ['stylus', 'styl'],
817 | 'text/tab-separated-values': ['tsv'],
818 | 'text/troff': ['t', 'tr', 'roff', 'man', 'me', 'ms'],
819 | 'text/turtle': ['ttl'],
820 | 'text/uri-list': ['uri', 'uris', 'urls'],
821 | 'text/vcard': ['vcard'],
822 | 'text/vnd.curl': ['curl'],
823 | 'text/vnd.curl.dcurl': ['dcurl'],
824 | 'text/vnd.curl.mcurl': ['mcurl'],
825 | 'text/vnd.curl.scurl': ['scurl'],
826 | 'text/vnd.dvb.subtitle': ['sub'],
827 | 'text/vnd.fly': ['fly'],
828 | 'text/vnd.fmi.flexstor': ['flx'],
829 | 'text/vnd.graphviz': ['gv'],
830 | 'text/vnd.in3d.3dml': ['3dml'],
831 | 'text/vnd.in3d.spot': ['spot'],
832 | 'text/vnd.sun.j2me.app-descriptor': ['jad'],
833 | 'text/vnd.wap.wml': ['wml'],
834 | 'text/vnd.wap.wmlscript': ['wmls'],
835 | 'text/vtt': ['vtt'],
836 | 'text/x-asm': ['s', 'asm'],
837 | 'text/x-c': ['c', 'cc', 'cxx', 'cpp', 'h', 'hh', 'dic'],
838 | 'text/x-component': ['htc'],
839 | 'text/x-fortran': ['f', 'for', 'f77', 'f90'],
840 | 'text/x-handlebars-template': ['hbs'],
841 | 'text/x-java-source': ['java'],
842 | 'text/x-lua': ['lua'],
843 | 'text/x-markdown': ['mkd'],
844 | 'text/x-nfo': ['nfo'],
845 | 'text/x-opml': ['opml'],
846 | 'text/x-org': [],
847 | 'text/x-pascal': ['p', 'pas'],
848 | 'text/x-processing': ['pde'],
849 | 'text/x-sass': ['sass'],
850 | 'text/x-scss': ['scss'],
851 | 'text/x-setext': ['etx'],
852 | 'text/x-sfv': ['sfv'],
853 | 'text/x-suse-ymp': ['ymp'],
854 | 'text/x-uuencode': ['uu'],
855 | 'text/x-vcalendar': ['vcs'],
856 | 'text/x-vcard': ['vcf'],
857 | 'text/xml': [],
858 | 'text/yaml': ['yaml', 'yml'],
859 | 'video/3gpp': ['3gp', '3gpp'],
860 | 'video/3gpp2': ['3g2'],
861 | 'video/h261': ['h261'],
862 | 'video/h263': ['h263'],
863 | 'video/h264': ['h264'],
864 | 'video/jpeg': ['jpgv'],
865 | 'video/jpm': ['jpgm'],
866 | 'video/mj2': ['mj2', 'mjp2'],
867 | 'video/mp2t': ['ts'],
868 | 'video/mp4': ['mp4', 'mp4v', 'mpg4'],
869 | 'video/mpeg': ['mpeg', 'mpg', 'mpe', 'm1v', 'm2v'],
870 | 'video/ogg': ['ogv'],
871 | 'video/quicktime': ['qt', 'mov'],
872 | 'video/vnd.dece.hd': ['uvh', 'uvvh'],
873 | 'video/vnd.dece.mobile': ['uvm', 'uvvm'],
874 | 'video/vnd.dece.pd': ['uvp', 'uvvp'],
875 | 'video/vnd.dece.sd': ['uvs', 'uvvs'],
876 | 'video/vnd.dece.video': ['uvv', 'uvvv'],
877 | 'video/vnd.dvb.file': ['dvb'],
878 | 'video/vnd.fvt': ['fvt'],
879 | 'video/vnd.mpegurl': ['mxu', 'm4u'],
880 | 'video/vnd.ms-playready.media.pyv': ['pyv'],
881 | 'video/vnd.uvvu.mp4': ['uvu', 'uvvu'],
882 | 'video/vnd.vivo': ['viv'],
883 | 'video/webm': ['webm'],
884 | 'video/x-f4v': ['f4v'],
885 | 'video/x-fli': ['fli'],
886 | 'video/x-flv': ['flv'],
887 | 'video/x-m4v': ['m4v'],
888 | 'video/x-matroska': ['mkv', 'mk3d', 'mks'],
889 | 'video/x-mng': ['mng'],
890 | 'video/x-ms-asf': ['asf', 'asx'],
891 | 'video/x-ms-vob': ['vob'],
892 | 'video/x-ms-wm': ['wm'],
893 | 'video/x-ms-wmv': ['wmv'],
894 | 'video/x-ms-wmx': ['wmx'],
895 | 'video/x-ms-wvx': ['wvx'],
896 | 'video/x-msvideo': ['avi'],
897 | 'video/x-sgi-movie': ['movie'],
898 | 'video/x-smv': ['smv'],
899 | 'x-conference/x-cooltalk': ['ice'],
900 | };
901 |
902 | const mimes = Object.entries(types).reduce(
903 | (all, [type, exts]) =>
904 | Object.assign(all, ...exts.map((ext) => ({ [ext]: type }))),
905 | {}
906 | );
907 |
908 | module.exports = (ext) => mimes[ext] || 'application/octet-stream';
909 |
--------------------------------------------------------------------------------