Add media_types collection (#97)

This commit is contained in:
Kitson Kelly 2019-01-11 16:16:47 +11:00 committed by Ryan Dahl
parent 92bbca8166
commit 0e00fe9cd3
10 changed files with 8003 additions and 106 deletions

View File

@ -14,6 +14,10 @@
Command line logging
- **[media_types](./media_types/)**
A library for resolving media types (MIME types) and extensions.
- **[mkdirp](./mkdirp/)**
Make directory branches.

93
media_types/README.md Normal file
View File

@ -0,0 +1,93 @@
# media_types
A module that assists in resolving media types and extensions. It consumes the
[mime-db](https://github.com/jshttp/mime-db) and provides API access to the
information.
## Usage
### `lookup(path)`
Lookup the content type associated with a file. The path can be just the
extension or the full path name. If the content type cannot be determined the
function returns `undefined`:
```ts
import { lookup } from "https://deno.land/x/std/media_types/mod.ts";
lookup("json"); // "application/json"
lookup(".md"); // "text/markdown"
lookup("folder/file.js"); // "application/javascript"
lookup("folder/.htaccess"); // undefined
```
### `contentType(type)`
Return a full `Content-Type` header value for a given content type or
extension. When an extension is used, `lookup()` is used to resolve the
content type first. A default charset is added if not present. The
function will return `undefined` if the content type cannot be resolved:
```ts
import { contentType } from "https://deno.land/x/std/media_types/mod.ts";
import * as path from "https://deno.land/x/std/path/mod.ts";
contentType("markdown"); // "text/markdown; charset=utf-8"
contentType("file.json"); // "application/json; charset=utf-8"
contentType("text/html"); // "text/html; charset=utf-8"
contentType("text/html; charset=iso-8859-1"); // "text/html; charset=iso-8859-1"
contentType(path.extname("/path/to/file.json")); // "application/json; charset=utf-8"
```
### `extension(type)`
Return a default extension for a given content type. If there is not an
appropriate extension, `undefined` is returned:
```ts
import { extension } from "https://deno.land/x/std/media_types/mod.ts";
extension("application/octet-stream"); // "bin"
```
### `charset(type)`
Lookup the implied default charset for a given content type. If the content
type cannot be resolved, `undefined` is returned:
```ts
import { charset } from "https://deno.land/x/std/media_types/mod.ts";
charset("text/markdown"); // "UTF-8"
```
### `extensions`
A `Map` of extensions by content type, in priority order:
```ts
import { extensions } from "https://deno.land/x/std/media_types/mod.ts";
extensions.get("application/javascript"); // [ "js", "mjs" ]
```
### `types`
A `Map` of content types by extension:
```ts
import { types } from "https://deno.land/x/std/media_types/mod.ts";
types.get("ts"); // "application/javascript"
```
---
Adapted from [mime-type](https://github.com/jshttp/mime-types).
MIT License.
```
```

7688
media_types/db_1.37.0.json Normal file

File diff suppressed because it is too large Load Diff

15
media_types/deps.ts Normal file
View File

@ -0,0 +1,15 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
export { extname } from "../fs/path.ts";
interface DB {
[mediaType: string]: {
source?: string;
compressible?: boolean;
charset?: string;
extensions?: string[];
};
}
import * as _db from "./db_1.37.0.json";
export const db: DB = _db;

149
media_types/mod.ts Normal file
View File

@ -0,0 +1,149 @@
/*!
* Ported from: https://github.com/jshttp/mime-types and licensed as:
*
* (The MIT License)
*
* Copyright (c) 2014 Jonathan Ong <me@jongleberry.com>
* Copyright (c) 2015 Douglas Christopher Wilson <doug@somethingdoug.com>
* Copyright (c) 2019 the Deno authors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* 'Software'), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { db, extname } from "./deps.ts";
const EXTRACT_TYPE_REGEXP = /^\s*([^;\s]*)(?:;|\s|$)/;
const TEXT_TYPE_REGEXP = /^text\//i;
/** A map of extensions for a given media type */
export const extensions = new Map<string, string[]>();
/** A map of the media type for a given extension */
export const types = new Map<string, string>();
/** Internal function to populate the maps based on the Mime DB */
function populateMaps(
extensions: Map<string, string[]>,
types: Map<string, string>
) {
const preference = ["nginx", "apache", undefined, "iana"];
for (const type of Object.keys(db)) {
const mime = db[type];
const exts = mime.extensions;
if (!exts || !exts.length) {
continue;
}
extensions.set(type, exts);
for (const ext of exts) {
if (types.has(ext)) {
const current = types.get(ext)!;
const from = preference.indexOf(db[current].source);
const to = preference.indexOf(mime.source);
if (
current !== "application/octet-stream" &&
(from > to ||
(from === to && current.substr(0, 12) === "application/"))
) {
continue;
}
}
types.set(ext, type);
}
}
}
// Populate the maps upon module load
populateMaps(extensions, types);
/** Given a media type return any default charset string. Returns `undefined`
* if not resolvable.
*/
export function charset(type: string): string | undefined {
const m = EXTRACT_TYPE_REGEXP.exec(type);
if (!m) {
return;
}
const [match] = m;
const mime = db[match.toLowerCase()];
if (mime && mime.charset) {
return mime.charset;
}
if (TEXT_TYPE_REGEXP.test(match)) {
return "UTF-8";
}
}
/** Given an extension or media type, return the full `Content-Type` header
* string. Returns `undefined` if not resolvable.
*/
export function contentType(str: string): string | undefined {
let mime = str.includes("/") ? str : lookup(str);
if (!mime) {
return;
}
if (!mime.includes("charset")) {
const cs = charset(mime);
if (cs) {
mime += `; charset=${cs.toLowerCase()}`;
}
}
return mime;
}
/** Given a media type, return the most appropriate extension or return
* `undefined` if there is none.
*/
export function extension(type: string): string | undefined {
const match = EXTRACT_TYPE_REGEXP.exec(type);
if (!match) {
return;
}
const exts = extensions.get(match[1].toLowerCase());
if (!exts || !exts.length) {
return;
}
return exts[0];
}
/** Given an extension, lookup the appropriate media type for that extension.
* Likely you should be using `contentType()` though instead.
*/
export function lookup(path: string): string | undefined {
const extension = extname("x." + path)
.toLowerCase()
.substr(1);
return types.get(extension);
}

50
media_types/test.ts Normal file
View File

@ -0,0 +1,50 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
import { assertEqual, test } from "../testing/mod.ts";
import {
lookup,
contentType,
extension,
charset,
extensions,
types
} from "./mod.ts";
test(function testLookup() {
assertEqual(lookup("json"), "application/json");
assertEqual(lookup(".md"), "text/markdown");
assertEqual(lookup("folder/file.js"), "application/javascript");
assertEqual(lookup("folder/.htaccess"), undefined);
});
test(function testContentType() {
assertEqual(contentType("markdown"), "text/markdown; charset=utf-8");
assertEqual(contentType("file.json"), "application/json; charset=utf-8");
assertEqual(contentType("text/html"), "text/html; charset=utf-8");
assertEqual(
contentType("text/html; charset=iso-8859-1"),
"text/html; charset=iso-8859-1"
);
assertEqual(contentType(".htaccess"), undefined);
});
test(function testExtension() {
assertEqual(extension("application/octet-stream"), "bin");
assertEqual(extension("application/javascript"), "js");
assertEqual(extension("text/html"), "html");
});
test(function testCharset() {
assertEqual(charset("text/markdown"), "UTF-8");
assertEqual(charset("text/css"), "UTF-8");
});
test(function testExtensions() {
assertEqual(extensions.get("application/javascript"), ["js", "mjs"]);
assertEqual(extensions.get("foo"), undefined);
});
test(function testTypes() {
assertEqual(types.get("js"), "application/javascript");
assertEqual(types.get("foo"), undefined);
});

View File

@ -1,85 +0,0 @@
{
"": "application/octet-stream",
".7z": "application/x-7z-compressed",
".aac": "audio/aac",
".abw": "application/x-abiword",
".arc": "application/octet-stream",
".avi": "video/x-msvideo",
".azw": "application/vnd.amazon.ebook",
".bin": "application/octet-stream",
".bmp": "image/bmp",
".bz": "application/x-bzip",
".bz2": "application/x-bzip2",
".csh": "application/x-csh",
".css": "text/css",
".csv": "text/csv",
".doc": "application/msword",
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
".eot": "application/vnd.ms-fontobject",
".epub": "application/epub+zip",
".es": "application/ecmascript",
".gif": "image/gif",
".gz": "application/gzip",
".htm": "text/html",
".html": "text/html",
".ico": "image/x-icon",
".ics": "text/calendar",
".jar": "application/java-archive",
".jpeg": "image/jpeg",
".jpg": "image/jpeg",
".js": "application/javascript",
".json": "application/json",
".md": "text/markdown",
".mid": "audio/x-midi",
".midi": "audio/x-midi",
".mp3": "audio/mpeg",
".mp4": "video/mpeg",
".mpeg": "video/mpeg",
".mpkg": "application/vnd.apple.installer+xml",
".less": "text/less",
".odp": "application/vnd.oasis.opendocument.presentation",
".ods": "application/vnd.oasis.opendocument.spreadsheet",
".odt": "application/vnd.oasis.opendocument.text",
".oga": "audio/ogg",
".ogv": "video/ogg",
".ogx": "application/ogg",
".otf": "font/otf",
".png": "image/png",
".pdf": "application/pdf",
".ppm": "image/x-portable-pixmap",
".pgm": "image/x-portable-graymap",
".pmm": "image/x-portable-bitmap",
".pnm": "image/x-portable-anymap",
".ppt": "application/vnd.ms-powerpoint",
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
".rar": "application/x-rar-compressed",
".rtf": "application/rtf",
".sh": "application/x-sh",
".sass": "text/x-sass",
".scss": "text/x-scss",
".svg": "image/svg+xml",
".swf": "application/x-shockwave-flash",
".tar": "application/x-tar",
".tar.gz": "application/tar+gzip",
".tif": "image/tiff",
".tiff": "image/tiff",
".toml": "application/toml",
".ts": "application/typescript",
".ttf": "font/ttf",
".txt": "text/plain",
".vsd": "application/vnd.visio",
".wav": "audio/wav",
".weba": "audio/webm",
".webm": "video/webm",
".webp": "image/webp",
".woff": "font/woff",
".woff2": "font/woff2",
".xhtml": "application/xhtml+xml",
".xls": "application/vnd.ms-excel",
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
".xml": "application/xml",
".xul": "application/vnd.mozilla.xul+xml",
".yml": "text/yaml",
".yaml": "text/yaml",
".zip": "application/zip"
}

View File

@ -13,7 +13,7 @@ import {
} from "./http.ts";
import { cwd, DenoError, ErrorKind, args, stat, readDir, open } from "deno";
import { extname } from "../fs/path.ts";
import * as extensionsMap from "./extension_map.json";
import { contentType } from "../media_types/mod.ts";
const dirViewerTemplate = `
<!DOCTYPE html>
@ -162,30 +162,12 @@ async function serveDir(req: ServerRequest, dirPath: string, dirName: string) {
return res;
}
function guessContentType(filename: string): string {
let extension = extname(filename);
let contentType = extensionsMap[extension];
if (contentType) {
return contentType;
}
extension = extension.toLowerCase();
contentType = extensionsMap[extension];
if (contentType) {
return contentType;
}
return extensionsMap[""];
}
async function serveFile(req: ServerRequest, filename: string) {
const file = await open(filename);
const fileInfo = await stat(filename);
const headers = new Headers();
headers.set("content-length", fileInfo.len.toString());
headers.set("content-type", guessContentType(filename));
headers.set("content-type", contentType(extname(filename)) || "text/plain");
const res = {
status: 200,

View File

@ -21,7 +21,7 @@ export function runTests(serverReadyPromise: Promise<any>) {
const res = await fetch("http://localhost:4500/azure-pipelines.yml");
assert(res.headers.has("access-control-allow-origin"));
assert(res.headers.has("access-control-allow-headers"));
assertEqual(res.headers.get("content-type"), "text/yaml");
assertEqual(res.headers.get("content-type"), "text/yaml; charset=utf-8");
const downloadedFile = await res.text();
const localFile = new TextDecoder().decode(
await readFile("./azure-pipelines.yml")

View File

@ -6,6 +6,7 @@ import "datetime/test.ts";
import "examples/test.ts";
import "flags/test.ts";
import "logging/test.ts";
import "media_types/test.ts";
import "mkdirp/test.ts";
import "net/bufio_test.ts";
import "net/http_test.ts";