Source file
src/mime/type.go
1
2
3
4
5
6 package mime
7
8 import (
9 "fmt"
10 "sort"
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 func clearSyncMap(m *sync.Map) {
26 m.Range(func(k, _ any) bool {
27 m.Delete(k)
28 return true
29 })
30 }
31
32
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
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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111 func TypeByExtension(ext string) string {
112 once.Do(initMime)
113
114
115 if v, ok := mimeTypes.Load(ext); ok {
116 return v.(string)
117 }
118
119
120
121
122 var buf [10]byte
123 lower := buf[:0]
124 const utf8RuneSelf = 0x80
125 for i := 0; i < len(ext); i++ {
126 c := ext[i]
127 if c >= utf8RuneSelf {
128
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
145
146
147
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
165
166
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 }
203
View as plain text