Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade mime to v2 #240

Merged
merged 10 commits into from
Apr 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
- Drop testing/support for nodes 4 and 5
- Fix parsing of CORS options
- Upgrade mime module to v2, use charset module for charset detection
- Remove ability to set mime types with a .types file
- Add ability to override mime type and charset lookup with globally-set
functions
- Removes default charset of utf8 - if you need this, try using a custom
charset lookup function

2019/02/10 Version 3.3.1
- Publish via linux to hopefully fix #238
Expand Down
1 change: 1 addition & 0 deletions CONTRIBUTORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,4 @@ Listed in no particular order:
* Victor Didenko @victordidenko
* Zong Jhe Wu @s25g5d4
* Jade Michael Thornton @thornjad <jade@jmthorton.net>
* @BigBlueHat
53 changes: 49 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,11 +236,18 @@ Defaults to **application/octet-stream**.
### `--mime-types {filename}`

Add new or override one or more mime-types. This affects the HTTP Content-Type
header. Can either be a path to a
[`.types`](http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types)
file or an object hash of type(s).
header. May be either an object hash of type(s), *or* a function
`(file, defaultValue) => 'some/mimetype'`. Naturally, only the object hash
works on the command line.

ecstatic({ mimeTypes: { 'mime-type': ['file_extension', 'file_extension'] } })
ecstatic({ mimeTypes: { 'some/mimetype': ['file_extension', 'file_extension'] } })

It's important to note that any changes to mime handling are **global**, since
the `mime` module appears to be poorly behaved outside of a global singleton
context. For clarity you may prefer to call `require('ecstatic').mime.define`
or `require('ecstatic').setCustomGetType` directly and skip using this option,
particularly in cases where you're using multiple instances of ecstatic's
middleware. **You've been warned!**

### `opts.handleError`

Expand Down Expand Up @@ -280,6 +287,44 @@ This works more or less as you'd expect.

This returns another middleware which will attempt to show a directory view. Turning on auto-indexing is roughly equivalent to adding this middleware after an ecstatic middleware with autoindexing disabled.

### ecstatic.mime.define(mappings);

This defines new mappings for the mime singleton, as specified in the main
docs for the ecstatic middleware. Calling this directly should make global
mutation more clear than setting the options when instantiating the middleware,
and is recommended if you're using more than one middlware instance.

### ecstatic.mime.setCustomGetType(fn);

This sets a global custom function for getting the mime type for a filename.
If this function returns a falsey value, getType will fall back to the mime
module's handling. Calling this directly should make global mutation more clear
than setting the options when instantiating the middleware, and is recommended
if you're using more than one middleware instance.

### ecstatic.mime.getType(filename, defaultValue);

This will return the mimetype for a filename, first using any function supplied
with `ecstatic.mime.setCustomGetType`, then trying `require('mime').getType`,
then falling back to defaultValue. Generally you don't want to use this
directly.

### ecstatic.mime.setCustomLookupCharset(fn);

This sets a global custom function for getting the charset for a mime type.
If this function returns a falsey value, lookupCharset will fall back on the
charset module's handling. Calling this directly should make global mutation
more clear than setting the options when instantiating the middleware, and is
recommended if you're using more than one middleware instance.

### ecstatic.mime.lookupCharset(mimeType);

This will look up the charset for the supplied mime type, first using any
function supplied with `ecstatic.mime.setCustomLookupCharset`, then trying
`require('charset')(mimeType)`. Generally you
don't want to use this directly.


# Tests:

Ecstatic has a fairly extensive test suite. You can run it with:
Expand Down
22 changes: 12 additions & 10 deletions lib/ecstatic.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
const path = require('path');
const fs = require('fs');
const url = require('url');
const mime = require('mime');
const mime = require('./ecstatic/mime');
const urlJoin = require('url-join');
const showDir = require('./ecstatic/show-dir');
const version = require('../package.json').version;
Expand Down Expand Up @@ -54,7 +54,7 @@ function shouldCompressBrotli(req) {

function hasGzipId12(gzipped, cb) {
const stream = fs.createReadStream(gzipped, { start: 0, end: 1 });
let buffer = Buffer('');
let buffer = Buffer.from('');
let hasBeenCalled = false;

stream.on('data', (chunk) => {
Expand Down Expand Up @@ -110,19 +110,19 @@ module.exports = function createMiddleware(_dir, _options) {
defaultExt = defaultExt.replace(/^\./, '');
}

// Support hashes and .types files in mimeTypes @since 0.8
if (opts.mimeTypes) {
try {
// You can pass a JSON blob here---useful for CLI use
opts.mimeTypes = JSON.parse(opts.mimeTypes);
} catch (e) {
// swallow parse errors, treat this as a string mimetype input
}
if (typeof opts.mimeTypes === 'string') {
mime.load(opts.mimeTypes);
} else if (typeof opts.mimeTypes === 'object') {
if (typeof opts.mimeTypes === 'object') {
mime.define(opts.mimeTypes);
}
if (typeof opts.mimeTypes === 'function') {
mime.setCustomGetType(opts.mimeTypes);
}
}

function shouldReturn304(req, serverLastModified, serverEtag) {
Expand Down Expand Up @@ -244,15 +244,16 @@ module.exports = function createMiddleware(_dir, _options) {
// Do a MIME lookup, fall back to octet-stream and handle gzip
// and brotli special case.
const defaultType = opts.contentType || 'application/octet-stream';
let contentType = mime.lookup(file, defaultType);

let contentType = mime.getType(file, defaultType);
let charSet;
const range = (req.headers && req.headers.range);
const lastModified = (new Date(stat.mtime)).toUTCString();
const etag = generateEtag(stat, weakEtags);
let cacheControl = cache;
let stream = null;
if (contentType) {
charSet = mime.charsets.lookup(contentType, 'utf-8');
charSet = mime.lookupCharset(contentType);
if (charSet) {
contentType += `; charset=${charSet}`;
}
Expand All @@ -261,11 +262,11 @@ module.exports = function createMiddleware(_dir, _options) {
if (file === gzippedFile) { // is .gz picked up
res.setHeader('Content-Encoding', 'gzip');
// strip gz ending and lookup mime type
contentType = mime.lookup(path.basename(file, '.gz'), defaultType);
contentType = mime.getType(path.basename(file, '.gz'), defaultType);
} else if (file === brotliFile) { // is .br picked up
res.setHeader('Content-Encoding', 'br');
// strip br ending and lookup mime type
contentType = mime.lookup(path.basename(file, '.br'), defaultType);
contentType = mime.getType(path.basename(file, '.br'), defaultType);
}

if (typeof cacheControl === 'function') {
Expand Down Expand Up @@ -465,6 +466,7 @@ module.exports = function createMiddleware(_dir, _options) {
ecstatic = module.exports;
ecstatic.version = version;
ecstatic.showDir = showDir;
ecstatic.mime = mime;


if (!module.parent) {
Expand Down
26 changes: 26 additions & 0 deletions lib/ecstatic/mime.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use strict';

const mime = require('mime');
const charset = require('charset');


exports.define = mappings => mime.define(mappings, true);

let customGetType = (file, defaultValue) => null; // eslint-disable-line no-unused-vars

exports.setCustomGetType = (fn) => { customGetType = fn; };

exports.getType = (file, defaultValue) => (
customGetType(file, defaultValue) ||
mime.getType(file) ||
defaultValue
);

let customLookupCharset = mimeType => null; // eslint-disable-line no-unused-vars

exports.setCustomLookupCharset = (fn) => { customLookupCharset = fn; };

exports.lookupCharset = mimeType => (
customLookupCharset(mimeType) ||
charset(mimeType)
);
13 changes: 9 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@
"middleware"
],
"dependencies": {
"charset": "^1.0.1",
"he": "^1.1.1",
"mime": "^1.6.0",
"mime": "^2.4.1",
"minimist": "^1.1.0",
"url-join": "^2.0.5"
},
Expand Down
26 changes: 4 additions & 22 deletions test/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,30 +82,13 @@ test('setting port via cli - custom port', (t) => {
});
});

test('setting mimeTypes via cli - .types file', (t) => {
t.plan(2);

const port = getRandomPort();
const root = path.resolve(__dirname, 'public/');
const pathMimetypeFile = path.resolve(__dirname, 'fixtures/custom_mime_type.types');
const options = [root, '--port', port, '--mimetypes', pathMimetypeFile];
const ecstatic = startEcstatic(options);

tearDown(ecstatic, t);

ecstatic.stdout.on('data', () => {
t.pass('ecstatic should be started');
checkServerIsRunning(`${defaultUrl}:${port}/custom_mime_type.opml`, t);
});
});

test('setting mimeTypes via cli - directly', (t) => {
t.plan(4);

t.plan(3);
const port = getRandomPort();
const root = path.resolve(__dirname, 'public/');
const mimeType = ['--mimeTypes', '{ "application/x-my-type": ["opml"] }'];
const options = [root, '--port', port, '--mimetypes'].concat(mimeType);
const options = [root, '--port', port].concat(mimeType);

const ecstatic = startEcstatic(options);

// TODO: remove error handler
Expand All @@ -114,8 +97,7 @@ test('setting mimeTypes via cli - directly', (t) => {
ecstatic.stdout.on('data', () => {
t.pass('ecstatic should be started');
checkServerIsRunning(`${defaultUrl}:${port}/custom_mime_type.opml`, t, (err, res) => {
t.error(err);
t.equal(res.headers['content-type'], 'application/x-my-type; charset=utf-8');
t.equal(res.headers['content-type'], 'application/x-my-type');
});
});
});
2 changes: 1 addition & 1 deletion test/content-type.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ test('default default contentType', (t) => {
request.get(`http://localhost:${port}/f_f`, (err, res) => {
t.ifError(err);
t.equal(res.statusCode, 200);
t.equal(res.headers['content-type'], 'text/plain; charset=UTF-8');
t.equal(res.headers['content-type'], 'text/plain');
server.close(() => { t.end(); });
});
});
Expand Down
31 changes: 0 additions & 31 deletions test/custom-content-type-file-secret.js

This file was deleted.

47 changes: 0 additions & 47 deletions test/custom-content-type-file.js

This file was deleted.

2 changes: 1 addition & 1 deletion test/custom-content-type.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ test('custom contentType', (t) => {
request.get(`http://localhost:${port}/custom_mime_type.opml`, (err, res) => {
t.ifError(err);
t.equal(res.statusCode, 200, 'custom_mime_type.opml should be found');
t.equal(res.headers['content-type'], 'application/jon; charset=utf-8');
t.equal(res.headers['content-type'], 'application/jon');
server.close(() => { t.end(); });
});
});
Expand Down
3 changes: 0 additions & 3 deletions test/fixtures/custom_mime_type.types

This file was deleted.

Loading