1/**
2 * Standard MIME type lookup.
3 *
4 * @stability unstable
5 * @since 4.0.0
6 */
7import * as Option from "../Option.ts"
8import standardTypes from "./internal/mimeTypes.ts"
9
10interface Tables {
11 readonly extensionToType: Map<string, string>
12 readonly typeToExtension: Map<string, string>
13 readonly typeToExtensions: Map<string, Set<string>>
14}
15
16// Built on first lookup rather than at module evaluation, so bundles that import this
17// module without calling a lookup can drop the tables and the MIME data they index.
18let tables: Tables | undefined
19
20const getTables = (): Tables => {
21 if (tables !== undefined) {
22 return tables
23 }
24 const extensionToType = new Map<string, string>()
25 const typeToExtension = new Map<string, string>()
26 const typeToExtensions = new Map<string, Set<string>>()
27
28 for (const [type, extensions] of Object.entries(standardTypes)) {
29 const allExtensions = new Set<string>()
30 typeToExtensions.set(type, allExtensions)
31 for (let index = 0; index < extensions.length; index++) {
32 let extension: string = extensions[index]
33 const starred = extension.startsWith("*")
34 if (starred) {
35 extension = extension.slice(1)
36 }
37 allExtensions.add(extension)
38 if (index === 0) {
39 typeToExtension.set(type, extension)
40 }
41 if (!starred) {
42 extensionToType.set(extension, type)
43 }
44 }
45 }
46 tables = { extensionToType, typeToExtension, typeToExtensions }
47 return tables
48}
49
50/**
51 * Returns the standard MIME type associated with a file name or extension.
52 *
53 * @stability unstable
54 * @category utilities
55 * @since 4.0.0
56 */
57export const getType = (path: string): Option.Option<string> => {
58 if (typeof path !== "string") {
59 return Option.none()
60 }
61 const last = path.replace(/^.*[/\\]/s, "").toLowerCase()
62 const extension = last.replace(/^.*\./s, "").toLowerCase()
63 const hasPath = last.length < path.length
64 const hasDot = extension.length < last.length - 1
65 if (!hasDot && hasPath) {
66 return Option.none()
67 }
68 return Option.fromUndefinedOr(getTables().extensionToType.get(extension))
69}
70
71/**
72 * Returns the default file extension associated with a standard MIME type.
73 *
74 * @stability unstable
75 * @category utilities
76 * @since 4.0.0
77 */
78export const getExtension = (type: string): Option.Option<string> => {
79 if (typeof type !== "string") {
80 return Option.none()
81 }
82 return Option.fromUndefinedOr(getTables().typeToExtension.get(type.split(";")[0].trim().toLowerCase()))
83}
84
85/**
86 * Returns every file extension associated with a standard MIME type.
87 *
88 * @stability unstable
89 * @category utilities
90 * @since 4.0.0
91 */
92export const getAllExtensions = (type: string): Option.Option<ReadonlySet<string>> => {
93 if (typeof type !== "string") {
94 return Option.none()
95 }
96 return Option.fromUndefinedOr(getTables().typeToExtensions.get(type.split(";")[0].trim().toLowerCase()))
97}