github.com/code-reading/golang@v0.0.0-20220303082512-ba5bc0e589a3/go/src/mime/type.go (about) 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 5 // Package mime implements parts of the MIME spec. 6 package mime 7 8 import ( 9 "fmt" 10 "sort" 11 "strings" 12 "sync" 13 ) 14 15 var ( 16 mimeTypes sync.Map // map[string]string; ".Z" => "application/x-compress" 17 mimeTypesLower sync.Map // map[string]string; ".z" => "application/x-compress" 18 19 // extensions maps from MIME type to list of lowercase file 20 // extensions: "image/jpeg" => [".jpg", ".jpeg"] 21 extensionsMu sync.Mutex // Guards stores (but not loads) on extensions. 22 extensions sync.Map // map[string][]string; slice values are append-only. 23 ) 24 25 func clearSyncMap(m *sync.Map) { 26 m.Range(func(k, _ interface{}) bool { 27 m.Delete(k) 28 return true 29 }) 30 } 31 32 // setMimeTypes is used by initMime's non-test path, and by tests. 33 func setMimeTypes(lowerExt, mixExt map[string]string) { 34 clearSyncMap(&mimeTypes) 35 clearSyncMap(&mimeTypesLower) 36 clearSyncMap(&extensions) 37 38 for k, v := range lowerExt { 39 mimeTypesLower.Store(k, v) 40 } 41 for k, v := range mixExt { 42 mimeTypes.Store(k, v) 43 } 44 45 extensionsMu.Lock() 46 defer extensionsMu.Unlock() 47 for k, v := range lowerExt { 48 justType, _, err := ParseMediaType(v) 49 if err != nil { 50 panic(err) 51 } 52 var exts []string 53 if ei, ok := extensions.Load(justType); ok { 54 exts = ei.([]string) 55 } 56 extensions.Store(justType, append(exts, k)) 57 } 58 } 59 60 var builtinTypesLower = map[string]string{ 61 ".avif": "image/avif", 62 ".css": "text/css; charset=utf-8", 63 ".gif": "image/gif", 64 ".htm": "text/html; charset=utf-8", 65 ".html": "text/html; charset=utf-8", 66 ".jpeg": "image/jpeg", 67 ".jpg": "image/jpeg", 68 ".js": "text/javascript; charset=utf-8", 69 ".json": "application/json", 70 ".mjs": "text/javascript; charset=utf-8", 71 ".pdf": "application/pdf", 72 ".png": "image/png", 73 ".svg": "image/svg+xml", 74 ".wasm": "application/wasm", 75 ".webp": "image/webp", 76 ".xml": "text/xml; charset=utf-8", 77 } 78 79 var once sync.Once // guards initMime 80 81 var testInitMime, osInitMime func() 82 83 func initMime() { 84 if fn := testInitMime; fn != nil { 85 fn() 86 } else { 87 setMimeTypes(builtinTypesLower, builtinTypesLower) 88 osInitMime() 89 } 90 } 91 92 // TypeByExtension returns the MIME type associated with the file extension ext. 93 // The extension ext should begin with a leading dot, as in ".html". 94 // When ext has no associated type, TypeByExtension returns "". 95 // 96 // Extensions are looked up first case-sensitively, then case-insensitively. 97 // 98 // The built-in table is small but on unix it is augmented by the local 99 // system's MIME-info database or mime.types file(s) if available under one or 100 // more of these names: 101 // 102 // /usr/local/share/mime/globs2 103 // /usr/share/mime/globs2 104 // /etc/mime.types 105 // /etc/apache2/mime.types 106 // /etc/apache/mime.types 107 // 108 // On Windows, MIME types are extracted from the registry. 109 // 110 // Text types have the charset parameter set to "utf-8" by default. 111 func TypeByExtension(ext string) string { 112 once.Do(initMime) 113 114 // Case-sensitive lookup. 115 if v, ok := mimeTypes.Load(ext); ok { 116 return v.(string) 117 } 118 119 // Case-insensitive lookup. 120 // Optimistically assume a short ASCII extension and be 121 // allocation-free in that case. 122 var buf [10]byte 123 lower := buf[:0] 124 const utf8RuneSelf = 0x80 // from utf8 package, but not importing it. 125 for i := 0; i < len(ext); i++ { 126 c := ext[i] 127 if c >= utf8RuneSelf { 128 // Slow path. 129 si, _ := mimeTypesLower.Load(strings.ToLower(ext)) 130 s, _ := si.(string) 131 return s 132 } 133 if 'A' <= c && c <= 'Z' { 134 lower = append(lower, c+('a'-'A')) 135 } else { 136 lower = append(lower, c) 137 } 138 } 139 si, _ := mimeTypesLower.Load(string(lower)) 140 s, _ := si.(string) 141 return s 142 } 143 144 // ExtensionsByType returns the extensions known to be associated with the MIME 145 // type typ. The returned extensions will each begin with a leading dot, as in 146 // ".html". When typ has no associated extensions, ExtensionsByType returns an 147 // nil slice. 148 func ExtensionsByType(typ string) ([]string, error) { 149 justType, _, err := ParseMediaType(typ) 150 if err != nil { 151 return nil, err 152 } 153 154 once.Do(initMime) 155 s, ok := extensions.Load(justType) 156 if !ok { 157 return nil, nil 158 } 159 ret := append([]string(nil), s.([]string)...) 160 sort.Strings(ret) 161 return ret, nil 162 } 163 164 // AddExtensionType sets the MIME type associated with 165 // the extension ext to typ. The extension should begin with 166 // a leading dot, as in ".html". 167 func AddExtensionType(ext, typ string) error { 168 if !strings.HasPrefix(ext, ".") { 169 return fmt.Errorf("mime: extension %q missing leading dot", ext) 170 } 171 once.Do(initMime) 172 return setExtensionType(ext, typ) 173 } 174 175 func setExtensionType(extension, mimeType string) error { 176 justType, param, err := ParseMediaType(mimeType) 177 if err != nil { 178 return err 179 } 180 if strings.HasPrefix(mimeType, "text/") && param["charset"] == "" { 181 param["charset"] = "utf-8" 182 mimeType = FormatMediaType(mimeType, param) 183 } 184 extLower := strings.ToLower(extension) 185 186 mimeTypes.Store(extension, mimeType) 187 mimeTypesLower.Store(extLower, mimeType) 188 189 extensionsMu.Lock() 190 defer extensionsMu.Unlock() 191 var exts []string 192 if ei, ok := extensions.Load(justType); ok { 193 exts = ei.([]string) 194 } 195 for _, v := range exts { 196 if v == extLower { 197 return nil 198 } 199 } 200 extensions.Store(justType, append(exts, extLower)) 201 return nil 202 }