blob: 64e26ffb7cc900f9d68d4badb8c170e996051c3e [file] [log] [blame]
Michael Hoisie0cba5fc2010-02-09 20:47:45 -08001// Copyright 2010 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
Nigel Tao6a186d32011-04-20 09:57:05 +10005// Package mime implements parts of the MIME spec.
Michael Hoisie0cba5fc2010-02-09 20:47:45 -08006package mime
7
8import (
Pascal S. de Kloebd3627c2011-08-26 16:55:25 -04009 "fmt"
Michael Hoisie0cba5fc2010-02-09 20:47:45 -080010 "strings"
Yuusei Kuwanac21e2f32010-07-29 14:12:04 -070011 "sync"
Michael Hoisie0cba5fc2010-02-09 20:47:45 -080012)
13
Jeff R. Allenaf12dc52014-08-28 08:22:54 -070014var (
Bryan C. Millse8d7e5d2017-02-16 17:59:53 -050015 mimeTypes sync.Map // map[string]string; ".Z" => "application/x-compress"
16 mimeTypesLower sync.Map // map[string]string; ".z" => "application/x-compress"
Brad Fitzpatrickb86f3932015-03-29 21:21:15 +020017
18 // extensions maps from MIME type to list of lowercase file
19 // extensions: "image/jpeg" => [".jpg", ".jpeg"]
Bryan C. Millse8d7e5d2017-02-16 17:59:53 -050020 extensionsMu sync.Mutex // Guards stores (but not loads) on extensions.
21 extensions sync.Map // map[string][]string; slice values are append-only.
Brad Fitzpatrickb155e792014-08-28 11:07:46 -070022)
23
Bryan C. Millse8d7e5d2017-02-16 17:59:53 -050024func clearSyncMap(m *sync.Map) {
25 m.Range(func(k, _ interface{}) bool {
26 m.Delete(k)
27 return true
28 })
29}
30
Brad Fitzpatrickb86f3932015-03-29 21:21:15 +020031// setMimeTypes is used by initMime's non-test path, and by tests.
Brad Fitzpatrickb86f3932015-03-29 21:21:15 +020032func setMimeTypes(lowerExt, mixExt map[string]string) {
Bryan C. Millse8d7e5d2017-02-16 17:59:53 -050033 clearSyncMap(&mimeTypes)
34 clearSyncMap(&mimeTypesLower)
35 clearSyncMap(&extensions)
36
37 for k, v := range lowerExt {
38 mimeTypesLower.Store(k, v)
Brad Fitzpatrickb86f3932015-03-29 21:21:15 +020039 }
Bryan C. Millse8d7e5d2017-02-16 17:59:53 -050040 for k, v := range mixExt {
41 mimeTypes.Store(k, v)
42 }
43
44 extensionsMu.Lock()
45 defer extensionsMu.Unlock()
46 for k, v := range lowerExt {
47 justType, _, err := ParseMediaType(v)
48 if err != nil {
49 panic(err)
50 }
51 var exts []string
52 if ei, ok := extensions.Load(k); ok {
53 exts = ei.([]string)
54 }
55 extensions.Store(justType, append(exts, k))
56 }
Brad Fitzpatrickb86f3932015-03-29 21:21:15 +020057}
58
59var builtinTypesLower = map[string]string{
60 ".css": "text/css; charset=utf-8",
61 ".gif": "image/gif",
62 ".htm": "text/html; charset=utf-8",
63 ".html": "text/html; charset=utf-8",
64 ".jpg": "image/jpeg",
Brad Fitzpatrickfeeff232018-06-20 18:25:54 +000065 ".js": "application/javascript",
Yasuhiro Matsumoto534ddf72018-06-15 18:46:45 +090066 ".wasm": "application/wasm",
Brad Fitzpatrickb86f3932015-03-29 21:21:15 +020067 ".pdf": "application/pdf",
68 ".png": "image/png",
69 ".svg": "image/svg+xml",
70 ".xml": "text/xml; charset=utf-8",
71}
72
Brad Fitzpatrickb155e792014-08-28 11:07:46 -070073var once sync.Once // guards initMime
Rob Pikec78be462010-08-06 06:14:41 +100074
Brad Fitzpatrickb86f3932015-03-29 21:21:15 +020075var testInitMime, osInitMime func()
76
77func initMime() {
78 if fn := testInitMime; fn != nil {
79 fn()
80 } else {
Bryan C. Millse8d7e5d2017-02-16 17:59:53 -050081 setMimeTypes(builtinTypesLower, builtinTypesLower)
Brad Fitzpatrickb86f3932015-03-29 21:21:15 +020082 osInitMime()
83 }
84}
85
Michael Hoisie0cba5fc2010-02-09 20:47:45 -080086// TypeByExtension returns the MIME type associated with the file extension ext.
87// The extension ext should begin with a leading dot, as in ".html".
88// When ext has no associated type, TypeByExtension returns "".
Brad Fitzpatrick9b64fef2010-07-14 17:26:14 -070089//
Jeff R. Allenaf12dc52014-08-28 08:22:54 -070090// Extensions are looked up first case-sensitively, then case-insensitively.
91//
Alex Brainmanac17fd42011-11-18 10:07:36 +110092// The built-in table is small but on unix it is augmented by the local
Brad Fitzpatrick9b64fef2010-07-14 17:26:14 -070093// system's mime.types file(s) if available under one or more of these
94// names:
95//
96// /etc/mime.types
97// /etc/apache2/mime.types
98// /etc/apache/mime.types
Pascal S. de Kloebd3627c2011-08-26 16:55:25 -040099//
Brad Fitzpatrickb155e792014-08-28 11:07:46 -0700100// On Windows, MIME types are extracted from the registry.
Alex Brainmanac17fd42011-11-18 10:07:36 +1100101//
Pascal S. de Kloebd3627c2011-08-26 16:55:25 -0400102// Text types have the charset parameter set to "utf-8" by default.
Michael Hoisie0cba5fc2010-02-09 20:47:45 -0800103func TypeByExtension(ext string) string {
104 once.Do(initMime)
Brad Fitzpatrickb155e792014-08-28 11:07:46 -0700105
106 // Case-sensitive lookup.
Bryan C. Millse8d7e5d2017-02-16 17:59:53 -0500107 if v, ok := mimeTypes.Load(ext); ok {
108 return v.(string)
Jeff R. Allenaf12dc52014-08-28 08:22:54 -0700109 }
Brad Fitzpatrickb155e792014-08-28 11:07:46 -0700110
111 // Case-insensitive lookup.
112 // Optimistically assume a short ASCII extension and be
113 // allocation-free in that case.
114 var buf [10]byte
115 lower := buf[:0]
116 const utf8RuneSelf = 0x80 // from utf8 package, but not importing it.
117 for i := 0; i < len(ext); i++ {
118 c := ext[i]
119 if c >= utf8RuneSelf {
120 // Slow path.
Bryan C. Millse8d7e5d2017-02-16 17:59:53 -0500121 si, _ := mimeTypesLower.Load(strings.ToLower(ext))
122 s, _ := si.(string)
123 return s
Brad Fitzpatrickb155e792014-08-28 11:07:46 -0700124 }
125 if 'A' <= c && c <= 'Z' {
126 lower = append(lower, c+('a'-'A'))
127 } else {
128 lower = append(lower, c)
129 }
130 }
Bryan C. Millse8d7e5d2017-02-16 17:59:53 -0500131 si, _ := mimeTypesLower.Load(string(lower))
132 s, _ := si.(string)
133 return s
Yuusei Kuwanac21e2f32010-07-29 14:12:04 -0700134}
135
Nick Cooperec56bad2015-03-12 11:23:44 +1100136// ExtensionsByType returns the extensions known to be associated with the MIME
137// type typ. The returned extensions will each begin with a leading dot, as in
138// ".html". When typ has no associated extensions, ExtensionsByType returns an
139// nil slice.
140func ExtensionsByType(typ string) ([]string, error) {
141 justType, _, err := ParseMediaType(typ)
142 if err != nil {
143 return nil, err
144 }
145
146 once.Do(initMime)
Bryan C. Millse8d7e5d2017-02-16 17:59:53 -0500147 s, ok := extensions.Load(justType)
Nick Cooperec56bad2015-03-12 11:23:44 +1100148 if !ok {
149 return nil, nil
150 }
Bryan C. Millse8d7e5d2017-02-16 17:59:53 -0500151 return append([]string{}, s.([]string)...), nil
Nick Cooperec56bad2015-03-12 11:23:44 +1100152}
153
Yuusei Kuwanac21e2f32010-07-29 14:12:04 -0700154// AddExtensionType sets the MIME type associated with
Jeff R. Allenaf12dc52014-08-28 08:22:54 -0700155// the extension ext to typ. The extension should begin with
Yuusei Kuwanac21e2f32010-07-29 14:12:04 -0700156// a leading dot, as in ".html".
Russ Coxc2049d22011-11-01 22:04:37 -0400157func AddExtensionType(ext, typ string) error {
Jeff R. Allenaf12dc52014-08-28 08:22:54 -0700158 if !strings.HasPrefix(ext, ".") {
Brad Fitzpatrickb86f3932015-03-29 21:21:15 +0200159 return fmt.Errorf("mime: extension %q missing leading dot", ext)
Yuusei Kuwanac21e2f32010-07-29 14:12:04 -0700160 }
Pascal S. de Kloebd3627c2011-08-26 16:55:25 -0400161 once.Do(initMime)
162 return setExtensionType(ext, typ)
163}
164
Russ Coxc2049d22011-11-01 22:04:37 -0400165func setExtensionType(extension, mimeType string) error {
Nick Cooperec56bad2015-03-12 11:23:44 +1100166 justType, param, err := ParseMediaType(mimeType)
Pascal S. de Kloebd3627c2011-08-26 16:55:25 -0400167 if err != nil {
168 return err
169 }
Brad Fitzpatricka00de452012-01-17 11:57:42 -0800170 if strings.HasPrefix(mimeType, "text/") && param["charset"] == "" {
171 param["charset"] = "utf-8"
172 mimeType = FormatMediaType(mimeType, param)
Pascal S. de Kloebd3627c2011-08-26 16:55:25 -0400173 }
Jeff R. Allenaf12dc52014-08-28 08:22:54 -0700174 extLower := strings.ToLower(extension)
175
Bryan C. Millse8d7e5d2017-02-16 17:59:53 -0500176 mimeTypes.Store(extension, mimeType)
177 mimeTypesLower.Store(extLower, mimeType)
178
179 extensionsMu.Lock()
180 defer extensionsMu.Unlock()
181 var exts []string
182 if ei, ok := extensions.Load(justType); ok {
183 exts = ei.([]string)
184 }
185 for _, v := range exts {
Nick Cooperec56bad2015-03-12 11:23:44 +1100186 if v == extLower {
187 return nil
188 }
189 }
Bryan C. Millse8d7e5d2017-02-16 17:59:53 -0500190 extensions.Store(justType, append(exts, extLower))
Yuusei Kuwanac21e2f32010-07-29 14:12:04 -0700191 return nil
Michael Hoisie0cba5fc2010-02-09 20:47:45 -0800192}