Michael Hoisie | 0cba5fc | 2010-02-09 20:47:45 -0800 | [diff] [blame] | 1 | // 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 Tao | 6a186d3 | 2011-04-20 09:57:05 +1000 | [diff] [blame] | 5 | // Package mime implements parts of the MIME spec. |
Michael Hoisie | 0cba5fc | 2010-02-09 20:47:45 -0800 | [diff] [blame] | 6 | package mime |
| 7 | |
| 8 | import ( |
Pascal S. de Kloe | bd3627c | 2011-08-26 16:55:25 -0400 | [diff] [blame] | 9 | "fmt" |
Michael Hoisie | 0cba5fc | 2010-02-09 20:47:45 -0800 | [diff] [blame] | 10 | "strings" |
Yuusei Kuwana | c21e2f3 | 2010-07-29 14:12:04 -0700 | [diff] [blame] | 11 | "sync" |
Michael Hoisie | 0cba5fc | 2010-02-09 20:47:45 -0800 | [diff] [blame] | 12 | ) |
| 13 | |
Jeff R. Allen | af12dc5 | 2014-08-28 08:22:54 -0700 | [diff] [blame] | 14 | var ( |
Bryan C. Mills | e8d7e5d | 2017-02-16 17:59:53 -0500 | [diff] [blame] | 15 | mimeTypes sync.Map // map[string]string; ".Z" => "application/x-compress" |
| 16 | mimeTypesLower sync.Map // map[string]string; ".z" => "application/x-compress" |
Brad Fitzpatrick | b86f393 | 2015-03-29 21:21:15 +0200 | [diff] [blame] | 17 | |
| 18 | // extensions maps from MIME type to list of lowercase file |
| 19 | // extensions: "image/jpeg" => [".jpg", ".jpeg"] |
Bryan C. Mills | e8d7e5d | 2017-02-16 17:59:53 -0500 | [diff] [blame] | 20 | extensionsMu sync.Mutex // Guards stores (but not loads) on extensions. |
| 21 | extensions sync.Map // map[string][]string; slice values are append-only. |
Brad Fitzpatrick | b155e79 | 2014-08-28 11:07:46 -0700 | [diff] [blame] | 22 | ) |
| 23 | |
Bryan C. Mills | e8d7e5d | 2017-02-16 17:59:53 -0500 | [diff] [blame] | 24 | func clearSyncMap(m *sync.Map) { |
| 25 | m.Range(func(k, _ interface{}) bool { |
| 26 | m.Delete(k) |
| 27 | return true |
| 28 | }) |
| 29 | } |
| 30 | |
Brad Fitzpatrick | b86f393 | 2015-03-29 21:21:15 +0200 | [diff] [blame] | 31 | // setMimeTypes is used by initMime's non-test path, and by tests. |
Brad Fitzpatrick | b86f393 | 2015-03-29 21:21:15 +0200 | [diff] [blame] | 32 | func setMimeTypes(lowerExt, mixExt map[string]string) { |
Bryan C. Mills | e8d7e5d | 2017-02-16 17:59:53 -0500 | [diff] [blame] | 33 | clearSyncMap(&mimeTypes) |
| 34 | clearSyncMap(&mimeTypesLower) |
| 35 | clearSyncMap(&extensions) |
| 36 | |
| 37 | for k, v := range lowerExt { |
| 38 | mimeTypesLower.Store(k, v) |
Brad Fitzpatrick | b86f393 | 2015-03-29 21:21:15 +0200 | [diff] [blame] | 39 | } |
Bryan C. Mills | e8d7e5d | 2017-02-16 17:59:53 -0500 | [diff] [blame] | 40 | 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 Fitzpatrick | b86f393 | 2015-03-29 21:21:15 +0200 | [diff] [blame] | 57 | } |
| 58 | |
| 59 | var 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 Fitzpatrick | feeff23 | 2018-06-20 18:25:54 +0000 | [diff] [blame] | 65 | ".js": "application/javascript", |
Yasuhiro Matsumoto | 534ddf7 | 2018-06-15 18:46:45 +0900 | [diff] [blame] | 66 | ".wasm": "application/wasm", |
Brad Fitzpatrick | b86f393 | 2015-03-29 21:21:15 +0200 | [diff] [blame] | 67 | ".pdf": "application/pdf", |
| 68 | ".png": "image/png", |
| 69 | ".svg": "image/svg+xml", |
| 70 | ".xml": "text/xml; charset=utf-8", |
| 71 | } |
| 72 | |
Brad Fitzpatrick | b155e79 | 2014-08-28 11:07:46 -0700 | [diff] [blame] | 73 | var once sync.Once // guards initMime |
Rob Pike | c78be46 | 2010-08-06 06:14:41 +1000 | [diff] [blame] | 74 | |
Brad Fitzpatrick | b86f393 | 2015-03-29 21:21:15 +0200 | [diff] [blame] | 75 | var testInitMime, osInitMime func() |
| 76 | |
| 77 | func initMime() { |
| 78 | if fn := testInitMime; fn != nil { |
| 79 | fn() |
| 80 | } else { |
Bryan C. Mills | e8d7e5d | 2017-02-16 17:59:53 -0500 | [diff] [blame] | 81 | setMimeTypes(builtinTypesLower, builtinTypesLower) |
Brad Fitzpatrick | b86f393 | 2015-03-29 21:21:15 +0200 | [diff] [blame] | 82 | osInitMime() |
| 83 | } |
| 84 | } |
| 85 | |
Michael Hoisie | 0cba5fc | 2010-02-09 20:47:45 -0800 | [diff] [blame] | 86 | // 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 Fitzpatrick | 9b64fef | 2010-07-14 17:26:14 -0700 | [diff] [blame] | 89 | // |
Jeff R. Allen | af12dc5 | 2014-08-28 08:22:54 -0700 | [diff] [blame] | 90 | // Extensions are looked up first case-sensitively, then case-insensitively. |
| 91 | // |
Alex Brainman | ac17fd4 | 2011-11-18 10:07:36 +1100 | [diff] [blame] | 92 | // The built-in table is small but on unix it is augmented by the local |
Brad Fitzpatrick | 9b64fef | 2010-07-14 17:26:14 -0700 | [diff] [blame] | 93 | // 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 Kloe | bd3627c | 2011-08-26 16:55:25 -0400 | [diff] [blame] | 99 | // |
Brad Fitzpatrick | b155e79 | 2014-08-28 11:07:46 -0700 | [diff] [blame] | 100 | // On Windows, MIME types are extracted from the registry. |
Alex Brainman | ac17fd4 | 2011-11-18 10:07:36 +1100 | [diff] [blame] | 101 | // |
Pascal S. de Kloe | bd3627c | 2011-08-26 16:55:25 -0400 | [diff] [blame] | 102 | // Text types have the charset parameter set to "utf-8" by default. |
Michael Hoisie | 0cba5fc | 2010-02-09 20:47:45 -0800 | [diff] [blame] | 103 | func TypeByExtension(ext string) string { |
| 104 | once.Do(initMime) |
Brad Fitzpatrick | b155e79 | 2014-08-28 11:07:46 -0700 | [diff] [blame] | 105 | |
| 106 | // Case-sensitive lookup. |
Bryan C. Mills | e8d7e5d | 2017-02-16 17:59:53 -0500 | [diff] [blame] | 107 | if v, ok := mimeTypes.Load(ext); ok { |
| 108 | return v.(string) |
Jeff R. Allen | af12dc5 | 2014-08-28 08:22:54 -0700 | [diff] [blame] | 109 | } |
Brad Fitzpatrick | b155e79 | 2014-08-28 11:07:46 -0700 | [diff] [blame] | 110 | |
| 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. Mills | e8d7e5d | 2017-02-16 17:59:53 -0500 | [diff] [blame] | 121 | si, _ := mimeTypesLower.Load(strings.ToLower(ext)) |
| 122 | s, _ := si.(string) |
| 123 | return s |
Brad Fitzpatrick | b155e79 | 2014-08-28 11:07:46 -0700 | [diff] [blame] | 124 | } |
| 125 | if 'A' <= c && c <= 'Z' { |
| 126 | lower = append(lower, c+('a'-'A')) |
| 127 | } else { |
| 128 | lower = append(lower, c) |
| 129 | } |
| 130 | } |
Bryan C. Mills | e8d7e5d | 2017-02-16 17:59:53 -0500 | [diff] [blame] | 131 | si, _ := mimeTypesLower.Load(string(lower)) |
| 132 | s, _ := si.(string) |
| 133 | return s |
Yuusei Kuwana | c21e2f3 | 2010-07-29 14:12:04 -0700 | [diff] [blame] | 134 | } |
| 135 | |
Nick Cooper | ec56bad | 2015-03-12 11:23:44 +1100 | [diff] [blame] | 136 | // 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. |
| 140 | func 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. Mills | e8d7e5d | 2017-02-16 17:59:53 -0500 | [diff] [blame] | 147 | s, ok := extensions.Load(justType) |
Nick Cooper | ec56bad | 2015-03-12 11:23:44 +1100 | [diff] [blame] | 148 | if !ok { |
| 149 | return nil, nil |
| 150 | } |
Bryan C. Mills | e8d7e5d | 2017-02-16 17:59:53 -0500 | [diff] [blame] | 151 | return append([]string{}, s.([]string)...), nil |
Nick Cooper | ec56bad | 2015-03-12 11:23:44 +1100 | [diff] [blame] | 152 | } |
| 153 | |
Yuusei Kuwana | c21e2f3 | 2010-07-29 14:12:04 -0700 | [diff] [blame] | 154 | // AddExtensionType sets the MIME type associated with |
Jeff R. Allen | af12dc5 | 2014-08-28 08:22:54 -0700 | [diff] [blame] | 155 | // the extension ext to typ. The extension should begin with |
Yuusei Kuwana | c21e2f3 | 2010-07-29 14:12:04 -0700 | [diff] [blame] | 156 | // a leading dot, as in ".html". |
Russ Cox | c2049d2 | 2011-11-01 22:04:37 -0400 | [diff] [blame] | 157 | func AddExtensionType(ext, typ string) error { |
Jeff R. Allen | af12dc5 | 2014-08-28 08:22:54 -0700 | [diff] [blame] | 158 | if !strings.HasPrefix(ext, ".") { |
Brad Fitzpatrick | b86f393 | 2015-03-29 21:21:15 +0200 | [diff] [blame] | 159 | return fmt.Errorf("mime: extension %q missing leading dot", ext) |
Yuusei Kuwana | c21e2f3 | 2010-07-29 14:12:04 -0700 | [diff] [blame] | 160 | } |
Pascal S. de Kloe | bd3627c | 2011-08-26 16:55:25 -0400 | [diff] [blame] | 161 | once.Do(initMime) |
| 162 | return setExtensionType(ext, typ) |
| 163 | } |
| 164 | |
Russ Cox | c2049d2 | 2011-11-01 22:04:37 -0400 | [diff] [blame] | 165 | func setExtensionType(extension, mimeType string) error { |
Nick Cooper | ec56bad | 2015-03-12 11:23:44 +1100 | [diff] [blame] | 166 | justType, param, err := ParseMediaType(mimeType) |
Pascal S. de Kloe | bd3627c | 2011-08-26 16:55:25 -0400 | [diff] [blame] | 167 | if err != nil { |
| 168 | return err |
| 169 | } |
Brad Fitzpatrick | a00de45 | 2012-01-17 11:57:42 -0800 | [diff] [blame] | 170 | if strings.HasPrefix(mimeType, "text/") && param["charset"] == "" { |
| 171 | param["charset"] = "utf-8" |
| 172 | mimeType = FormatMediaType(mimeType, param) |
Pascal S. de Kloe | bd3627c | 2011-08-26 16:55:25 -0400 | [diff] [blame] | 173 | } |
Jeff R. Allen | af12dc5 | 2014-08-28 08:22:54 -0700 | [diff] [blame] | 174 | extLower := strings.ToLower(extension) |
| 175 | |
Bryan C. Mills | e8d7e5d | 2017-02-16 17:59:53 -0500 | [diff] [blame] | 176 | 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 Cooper | ec56bad | 2015-03-12 11:23:44 +1100 | [diff] [blame] | 186 | if v == extLower { |
| 187 | return nil |
| 188 | } |
| 189 | } |
Bryan C. Mills | e8d7e5d | 2017-02-16 17:59:53 -0500 | [diff] [blame] | 190 | extensions.Store(justType, append(exts, extLower)) |
Yuusei Kuwana | c21e2f3 | 2010-07-29 14:12:04 -0700 | [diff] [blame] | 191 | return nil |
Michael Hoisie | 0cba5fc | 2010-02-09 20:47:45 -0800 | [diff] [blame] | 192 | } |