Source file
src/mime/type.go
1
2
3
4
5
6 package mime
7
8 import (
9 "fmt"
10 "slices"
11 "strings"
12 "sync"
13 )
14
15 var (
16 mimeTypes sync.Map
17 mimeTypesLower sync.Map
18
19
20
21 extensionsMu sync.Mutex
22 extensions sync.Map
23 )
24
25
26 func setMimeTypes(lowerExt, mixExt map[string]string) {
27 mimeTypes.Clear()
28 mimeTypesLower.Clear()
29 extensions.Clear()
30
31 for k, v := range lowerExt {
32 mimeTypesLower.Store(k, v)
33 }
34 for k, v := range mixExt {
35 mimeTypes.Store(k, v)
36 }
37
38 extensionsMu.Lock()
39 defer extensionsMu.Unlock()
40 for k, v := range lowerExt {
41 justType, _, err := ParseMediaType(v)
42 if err != nil {
43 panic(err)
44 }
45 var exts []string
46 if ei, ok := extensions.Load(justType); ok {
47 exts = ei.([]string)
48 }
49 extensions.Store(justType, append(exts, k))
50 }
51 }
52
53
54
55
56
57
58
59
60
61
62
63
64 var builtinTypesLower = map[string]string{
65 ".ai": "application/postscript",
66 ".apk": "application/vnd.android.package-archive",
67 ".apng": "image/apng",
68 ".avif": "image/avif",
69 ".bin": "application/octet-stream",
70 ".bmp": "image/bmp",
71 ".com": "application/octet-stream",
72 ".css": "text/css; charset=utf-8",
73 ".csv": "text/csv; charset=utf-8",
74 ".doc": "application/msword",
75 ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
76 ".ehtml": "text/html; charset=utf-8",
77 ".eml": "message/rfc822",
78 ".eps": "application/postscript",
79 ".exe": "application/octet-stream",
80 ".flac": "audio/flac",
81 ".gif": "image/gif",
82 ".gz": "application/gzip",
83 ".htm": "text/html; charset=utf-8",
84 ".html": "text/html; charset=utf-8",
85 ".ico": "image/vnd.microsoft.icon",
86 ".ics": "text/calendar; charset=utf-8",
87 ".jfif": "image/jpeg",
88 ".jpeg": "image/jpeg",
89 ".jpg": "image/jpeg",
90 ".js": "text/javascript; charset=utf-8",
91 ".json": "application/json",
92 ".m4a": "audio/mp4",
93 ".mjs": "text/javascript; charset=utf-8",
94 ".mp3": "audio/mpeg",
95 ".mp4": "video/mp4",
96 ".oga": "audio/ogg",
97 ".ogg": "audio/ogg",
98 ".ogv": "video/ogg",
99 ".opus": "audio/ogg",
100 ".pdf": "application/pdf",
101 ".pjp": "image/jpeg",
102 ".pjpeg": "image/jpeg",
103 ".png": "image/png",
104 ".ppt": "application/vnd.ms-powerpoint",
105 ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
106 ".ps": "application/postscript",
107 ".rdf": "application/rdf+xml",
108 ".rtf": "application/rtf",
109 ".shtml": "text/html; charset=utf-8",
110 ".svg": "image/svg+xml",
111 ".text": "text/plain; charset=utf-8",
112 ".tif": "image/tiff",
113 ".tiff": "image/tiff",
114 ".txt": "text/plain; charset=utf-8",
115 ".vtt": "text/vtt; charset=utf-8",
116 ".wasm": "application/wasm",
117 ".wav": "audio/wav",
118 ".weba": "audio/webm",
119 ".webm": "video/webm",
120 ".webp": "image/webp",
121 ".xbl": "text/xml; charset=utf-8",
122 ".xbm": "image/x-xbitmap",
123 ".xht": "application/xhtml+xml",
124 ".xhtml": "application/xhtml+xml",
125 ".xls": "application/vnd.ms-excel",
126 ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
127 ".xml": "text/xml; charset=utf-8",
128 ".xsl": "text/xml; charset=utf-8",
129 ".zip": "application/zip",
130 }
131
132 var once sync.Once
133
134 var testInitMime, osInitMime func()
135
136 func initMime() {
137 if fn := testInitMime; fn != nil {
138 fn()
139 } else {
140 setMimeTypes(builtinTypesLower, builtinTypesLower)
141 osInitMime()
142 }
143 }
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165 func TypeByExtension(ext string) string {
166 once.Do(initMime)
167
168
169 if v, ok := mimeTypes.Load(ext); ok {
170 return v.(string)
171 }
172
173
174
175
176 var buf [10]byte
177 lower := buf[:0]
178 const utf8RuneSelf = 0x80
179 for i := 0; i < len(ext); i++ {
180 c := ext[i]
181 if c >= utf8RuneSelf {
182
183 si, _ := mimeTypesLower.Load(strings.ToLower(ext))
184 s, _ := si.(string)
185 return s
186 }
187 if 'A' <= c && c <= 'Z' {
188 lower = append(lower, c+('a'-'A'))
189 } else {
190 lower = append(lower, c)
191 }
192 }
193 si, _ := mimeTypesLower.Load(string(lower))
194 s, _ := si.(string)
195 return s
196 }
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215 func ExtensionsByType(typ string) ([]string, error) {
216 justType, _, err := ParseMediaType(typ)
217 if err != nil {
218 return nil, err
219 }
220
221 once.Do(initMime)
222 s, ok := extensions.Load(justType)
223 if !ok {
224 return nil, nil
225 }
226 ret := append([]string(nil), s.([]string)...)
227 slices.Sort(ret)
228 return ret, nil
229 }
230
231
232
233
234 func AddExtensionType(ext, typ string) error {
235 if !strings.HasPrefix(ext, ".") {
236 return fmt.Errorf("mime: extension %q missing leading dot", ext)
237 }
238 once.Do(initMime)
239 return setExtensionType(ext, typ)
240 }
241
242 func setExtensionType(extension, mimeType string) error {
243 justType, param, err := ParseMediaType(mimeType)
244 if err != nil {
245 return err
246 }
247 if strings.HasPrefix(mimeType, "text/") && param["charset"] == "" {
248 param["charset"] = "utf-8"
249 mimeType = FormatMediaType(mimeType, param)
250 }
251 extLower := strings.ToLower(extension)
252
253 mimeTypes.Store(extension, mimeType)
254 mimeTypesLower.Store(extLower, mimeType)
255
256 extensionsMu.Lock()
257 defer extensionsMu.Unlock()
258 var exts []string
259 if ei, ok := extensions.Load(justType); ok {
260 exts = ei.([]string)
261 }
262 for _, v := range exts {
263 if v == extLower {
264 return nil
265 }
266 }
267 extensions.Store(justType, append(exts, extLower))
268 return nil
269 }
270
View as plain text