github.com/geraldss/go/src@v0.0.0-20210511222824-ac7d0ebfc235/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 ".css": "text/css; charset=utf-8", 62 ".gif": "image/gif", 63 ".htm": "text/html; charset=utf-8", 64 ".html": "text/html; charset=utf-8", 65 ".jpeg": "image/jpeg", 66 ".jpg": "image/jpeg", 67 ".js": "text/javascript; charset=utf-8", 68 ".json": "application/json", 69 ".mjs": "text/javascript; charset=utf-8", 70 ".pdf": "application/pdf", 71 ".png": "image/png", 72 ".svg": "image/svg+xml", 73 ".wasm": "application/wasm", 74 ".webp": "image/webp", 75 ".xml": "text/xml; charset=utf-8", 76 } 77 78 var once sync.Once // guards initMime 79 80 var testInitMime, osInitMime func() 81 82 func initMime() { 83 if fn := testInitMime; fn != nil { 84 fn() 85 } else { 86 setMimeTypes(builtinTypesLower, builtinTypesLower) 87 osInitMime() 88 } 89 } 90 91 // TypeByExtension returns the MIME type associated with the file extension ext. 92 // The extension ext should begin with a leading dot, as in ".html". 93 // When ext has no associated type, TypeByExtension returns "". 94 // 95 // Extensions are looked up first case-sensitively, then case-insensitively. 96 // 97 // The built-in table is small but on unix it is augmented by the local 98 // system's mime.types file(s) if available under one or more of these 99 // names: 100 // 101 // /etc/mime.types 102 // /etc/apache2/mime.types 103 // /etc/apache/mime.types 104 // 105 // On Windows, MIME types are extracted from the registry. 106 // 107 // Text types have the charset parameter set to "utf-8" by default. 108 func TypeByExtension(ext string) string { 109 once.Do(initMime) 110 111 // Case-sensitive lookup. 112 if v, ok := mimeTypes.Load(ext); ok { 113 return v.(string) 114 } 115 116 // Case-insensitive lookup. 117 // Optimistically assume a short ASCII extension and be 118 // allocation-free in that case. 119 var buf [10]byte 120 lower := buf[:0] 121 const utf8RuneSelf = 0x80 // from utf8 package, but not importing it. 122 for i := 0; i < len(ext); i++ { 123 c := ext[i] 124 if c >= utf8RuneSelf { 125 // Slow path. 126 si, _ := mimeTypesLower.Load(strings.ToLower(ext)) 127 s, _ := si.(string) 128 return s 129 } 130 if 'A' <= c && c <= 'Z' { 131 lower = append(lower, c+('a'-'A')) 132 } else { 133 lower = append(lower, c) 134 } 135 } 136 si, _ := mimeTypesLower.Load(string(lower)) 137 s, _ := si.(string) 138 return s 139 } 140 141 // ExtensionsByType returns the extensions known to be associated with the MIME 142 // type typ. The returned extensions will each begin with a leading dot, as in 143 // ".html". When typ has no associated extensions, ExtensionsByType returns an 144 // nil slice. 145 func ExtensionsByType(typ string) ([]string, error) { 146 justType, _, err := ParseMediaType(typ) 147 if err != nil { 148 return nil, err 149 } 150 151 once.Do(initMime) 152 s, ok := extensions.Load(justType) 153 if !ok { 154 return nil, nil 155 } 156 ret := append([]string(nil), s.([]string)...) 157 sort.Strings(ret) 158 return ret, nil 159 } 160 161 // AddExtensionType sets the MIME type associated with 162 // the extension ext to typ. The extension should begin with 163 // a leading dot, as in ".html". 164 func AddExtensionType(ext, typ string) error { 165 if !strings.HasPrefix(ext, ".") { 166 return fmt.Errorf("mime: extension %q missing leading dot", ext) 167 } 168 once.Do(initMime) 169 return setExtensionType(ext, typ) 170 } 171 172 func setExtensionType(extension, mimeType string) error { 173 justType, param, err := ParseMediaType(mimeType) 174 if err != nil { 175 return err 176 } 177 if strings.HasPrefix(mimeType, "text/") && param["charset"] == "" { 178 param["charset"] = "utf-8" 179 mimeType = FormatMediaType(mimeType, param) 180 } 181 extLower := strings.ToLower(extension) 182 183 mimeTypes.Store(extension, mimeType) 184 mimeTypesLower.Store(extLower, mimeType) 185 186 extensionsMu.Lock() 187 defer extensionsMu.Unlock() 188 var exts []string 189 if ei, ok := extensions.Load(justType); ok { 190 exts = ei.([]string) 191 } 192 for _, v := range exts { 193 if v == extLower { 194 return nil 195 } 196 } 197 extensions.Store(justType, append(exts, extLower)) 198 return nil 199 }