/src/h2o/lib/handler/mimemap.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2014 DeNA Co., Ltd. |
3 | | * |
4 | | * Permission is hereby granted, free of charge, to any person obtaining a copy |
5 | | * of this software and associated documentation files (the "Software"), to |
6 | | * deal in the Software without restriction, including without limitation the |
7 | | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or |
8 | | * sell copies of the Software, and to permit persons to whom the Software is |
9 | | * furnished to do so, subject to the following conditions: |
10 | | * |
11 | | * The above copyright notice and this permission notice shall be included in |
12 | | * all copies or substantial portions of the Software. |
13 | | * |
14 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
15 | | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
16 | | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
17 | | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
18 | | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
19 | | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
20 | | * IN THE SOFTWARE. |
21 | | */ |
22 | | #include <assert.h> |
23 | | #include <stdlib.h> |
24 | | #include <string.h> |
25 | | #include "khash.h" |
26 | | #include "h2o.h" |
27 | | |
28 | | KHASH_MAP_INIT_STR(extmap, h2o_mimemap_type_t *) |
29 | | |
30 | | static inline khint_t hash_mimemap_type(h2o_mimemap_type_t *mimetype) |
31 | 33.7k | { |
32 | 33.7k | khint_t h = 0; |
33 | 33.7k | size_t i; |
34 | 666k | for (i = 0; i != mimetype->data.mimetype.len; ++i) |
35 | 632k | h = (h << 5) - h + (khint_t)mimetype->data.mimetype.base[i]; |
36 | 33.7k | return h; |
37 | 33.7k | } |
38 | | |
39 | | static inline int mimemap_type_equals(h2o_mimemap_type_t *x, h2o_mimemap_type_t *y) |
40 | 17.5k | { |
41 | 17.5k | return h2o_memis(x->data.mimetype.base, x->data.mimetype.len, y->data.mimetype.base, y->data.mimetype.len); |
42 | 17.5k | } |
43 | | |
44 | | KHASH_INIT(typeset, h2o_mimemap_type_t *, char, 0, hash_mimemap_type, mimemap_type_equals) |
45 | | |
46 | | h2o_mime_attributes_t h2o_mime_attributes_as_is; |
47 | | |
48 | | struct st_h2o_mimemap_t { |
49 | | khash_t(extmap) * extmap; |
50 | | khash_t(typeset) * typeset; /* refs point to the entries in extmap */ |
51 | | h2o_mimemap_type_t *default_type; |
52 | | size_t num_dynamic; |
53 | | }; |
54 | | |
55 | | static h2o_iovec_t dupref(const char *s) |
56 | 272 | { |
57 | 272 | h2o_iovec_t ret; |
58 | 272 | ret.len = strlen(s); |
59 | 272 | ret.base = h2o_mem_alloc_shared(NULL, ret.len + 1, NULL); |
60 | 272 | memcpy(ret.base, s, ret.len + 1); |
61 | 272 | return ret; |
62 | 272 | } |
63 | | |
64 | | static void on_dispose(void *_mimemap) |
65 | 0 | { |
66 | 0 | h2o_mimemap_t *mimemap = _mimemap; |
67 | 0 | const char *ext; |
68 | 0 | h2o_mimemap_type_t *type; |
69 | |
|
70 | 0 | kh_destroy(typeset, mimemap->typeset); |
71 | 0 | kh_foreach(mimemap->extmap, ext, type, { |
72 | 0 | h2o_mem_release_shared((char *)ext); |
73 | 0 | h2o_mem_release_shared(type); |
74 | 0 | }); |
75 | 0 | kh_destroy(extmap, mimemap->extmap); |
76 | 0 | h2o_mem_release_shared(mimemap->default_type); |
77 | 0 | } |
78 | | |
79 | | static void on_unlink(h2o_mimemap_t *mimemap, h2o_mimemap_type_t *type) |
80 | 0 | { |
81 | 0 | switch (type->type) { |
82 | 0 | case H2O_MIMEMAP_TYPE_MIMETYPE: |
83 | 0 | break; |
84 | 0 | case H2O_MIMEMAP_TYPE_DYNAMIC: |
85 | 0 | --mimemap->num_dynamic; |
86 | 0 | break; |
87 | 0 | } |
88 | 0 | } |
89 | | |
90 | | static void on_link(h2o_mimemap_t *mimemap, h2o_mimemap_type_t *type) |
91 | 274 | { |
92 | 274 | switch (type->type) { |
93 | 274 | case H2O_MIMEMAP_TYPE_MIMETYPE: |
94 | 274 | break; |
95 | 0 | case H2O_MIMEMAP_TYPE_DYNAMIC: |
96 | 0 | ++mimemap->num_dynamic; |
97 | 0 | break; |
98 | 274 | } |
99 | 274 | } |
100 | | |
101 | | static void rebuild_typeset(h2o_mimemap_t *mimemap) |
102 | 274 | { |
103 | 274 | kh_clear(typeset, mimemap->typeset); |
104 | | |
105 | 274 | const char *ext; |
106 | 274 | h2o_mimemap_type_t *mime; |
107 | 274 | kh_foreach(mimemap->extmap, ext, mime, { |
108 | 274 | if (mime->type == H2O_MIMEMAP_TYPE_MIMETYPE) { |
109 | 274 | khiter_t iter = kh_get(typeset, mimemap->typeset, mime); |
110 | 274 | if (iter == kh_end(mimemap->typeset)) { |
111 | 274 | int r; |
112 | 274 | kh_put(typeset, mimemap->typeset, mime, &r); |
113 | 274 | } |
114 | 274 | } |
115 | 274 | }); |
116 | 274 | } |
117 | | |
118 | | static h2o_mimemap_type_t *create_extension_type(const char *mime, h2o_mime_attributes_t *attr) |
119 | 206 | { |
120 | 206 | h2o_mimemap_type_t *type = h2o_mem_alloc_shared(NULL, sizeof(*type) + strlen(mime) + 1, NULL); |
121 | 206 | size_t i; |
122 | | |
123 | 206 | memset(type, 0, sizeof(*type)); |
124 | | |
125 | 206 | type->type = H2O_MIMEMAP_TYPE_MIMETYPE; |
126 | | |
127 | | /* normalize-copy type->data.mimetype */ |
128 | 206 | type->data.mimetype.base = (char *)type + sizeof(*type); |
129 | 4.01k | for (i = 0; mime[i] != '\0' && mime[i] != ';'; ++i) |
130 | 3.80k | type->data.mimetype.base[i] = h2o_tolower(mime[i]); |
131 | 206 | for (; mime[i] != '\0'; ++i) |
132 | 0 | type->data.mimetype.base[i] = mime[i]; |
133 | 206 | type->data.mimetype.base[i] = '\0'; |
134 | 206 | type->data.mimetype.len = i; |
135 | | |
136 | 206 | if (attr != NULL) { |
137 | 0 | type->data.attr = *attr; |
138 | 206 | } else { |
139 | 206 | h2o_mimemap_get_default_attributes(mime, &type->data.attr); |
140 | 206 | } |
141 | | |
142 | 206 | return type; |
143 | 206 | } |
144 | | |
145 | | static void dispose_dynamic_type(h2o_mimemap_type_t *type) |
146 | 0 | { |
147 | 0 | h2o_config_dispose_pathconf(&type->data.dynamic.pathconf); |
148 | 0 | } |
149 | | |
150 | | static h2o_mimemap_type_t *create_dynamic_type(h2o_globalconf_t *globalconf, h2o_mimemap_t *mimemap) |
151 | 0 | { |
152 | 0 | h2o_mimemap_type_t *type = h2o_mem_alloc_shared(NULL, sizeof(*type), (void (*)(void *))dispose_dynamic_type); |
153 | |
|
154 | 0 | type->type = H2O_MIMEMAP_TYPE_DYNAMIC; |
155 | 0 | memset(&type->data.dynamic, 0, sizeof(type->data.dynamic)); |
156 | 0 | h2o_config_init_pathconf(&type->data.dynamic.pathconf, globalconf, NULL, mimemap); |
157 | |
|
158 | 0 | return type; |
159 | 0 | } |
160 | | |
161 | | h2o_mimemap_t *h2o_mimemap_create() |
162 | 2 | { |
163 | 2 | h2o_mimemap_t *mimemap = h2o_mem_alloc_shared(NULL, sizeof(*mimemap), on_dispose); |
164 | | |
165 | 2 | mimemap->extmap = kh_init(extmap); |
166 | 2 | mimemap->typeset = kh_init(typeset); |
167 | 2 | mimemap->default_type = create_extension_type("application/octet-stream", NULL); |
168 | 2 | mimemap->num_dynamic = 0; |
169 | 2 | on_link(mimemap, mimemap->default_type); |
170 | | |
171 | 2 | { /* setup the tiny default */ |
172 | 2 | static const char *default_types[] = { |
173 | 272 | #define MIMEMAP(ext, mime) ext, mime, |
174 | 2 | #include "mimemap/defaults.c.h" |
175 | 2 | #undef MIMEMAP |
176 | 2 | NULL}; |
177 | 2 | const char **p; |
178 | 274 | for (p = default_types; *p != NULL; p += 2) |
179 | 272 | h2o_mimemap_define_mimetype(mimemap, p[0], p[1], NULL); |
180 | 2 | } |
181 | 2 | rebuild_typeset(mimemap); |
182 | | |
183 | 2 | return mimemap; |
184 | 2 | } |
185 | | |
186 | | h2o_mimemap_t *h2o_mimemap_clone(h2o_mimemap_t *src) |
187 | 0 | { |
188 | 0 | h2o_mimemap_t *dst = h2o_mem_alloc_shared(NULL, sizeof(*dst), on_dispose); |
189 | 0 | const char *ext; |
190 | 0 | h2o_mimemap_type_t *type; |
191 | |
|
192 | 0 | dst->extmap = kh_init(extmap); |
193 | 0 | dst->typeset = kh_init(typeset); |
194 | 0 | kh_foreach(src->extmap, ext, type, { |
195 | 0 | int r; |
196 | 0 | khiter_t iter = kh_put(extmap, dst->extmap, ext, &r); |
197 | 0 | kh_val(dst->extmap, iter) = type; |
198 | 0 | h2o_mem_addref_shared((char *)ext); |
199 | 0 | h2o_mem_addref_shared(type); |
200 | 0 | on_link(dst, type); |
201 | 0 | }); |
202 | 0 | dst->default_type = src->default_type; |
203 | 0 | h2o_mem_addref_shared(dst->default_type); |
204 | 0 | on_link(dst, dst->default_type); |
205 | 0 | rebuild_typeset(dst); |
206 | |
|
207 | 0 | return dst; |
208 | 0 | } |
209 | | |
210 | | #define FOREACH_TYPE(mimemap, block) \ |
211 | 1 | do { \ |
212 | 1 | const char *ext; \ |
213 | 1 | h2o_mimemap_type_t *type; \ |
214 | 1 | type = mimemap->default_type; \ |
215 | 1 | {block}; \ |
216 | 1 | kh_foreach(mimemap->extmap, ext, type, {block}); \ |
217 | 1 | } while (0) |
218 | | |
219 | | void h2o_mimemap_on_context_init(h2o_mimemap_t *mimemap, h2o_context_t *ctx) |
220 | 1 | { |
221 | 1 | FOREACH_TYPE(mimemap, { |
222 | 1 | switch (type->type) { |
223 | 1 | case H2O_MIMEMAP_TYPE_DYNAMIC: |
224 | 1 | h2o_context_init_pathconf_context(ctx, &type->data.dynamic.pathconf); |
225 | 1 | break; |
226 | 1 | case H2O_MIMEMAP_TYPE_MIMETYPE: |
227 | 1 | break; |
228 | 1 | } |
229 | 1 | }); |
230 | 1 | } |
231 | | |
232 | | void h2o_mimemap_on_context_dispose(h2o_mimemap_t *mimemap, h2o_context_t *ctx) |
233 | 0 | { |
234 | 0 | FOREACH_TYPE(mimemap, { |
235 | 0 | switch (type->type) { |
236 | 0 | case H2O_MIMEMAP_TYPE_DYNAMIC: |
237 | 0 | h2o_context_dispose_pathconf_context(ctx, &type->data.dynamic.pathconf); |
238 | 0 | break; |
239 | 0 | case H2O_MIMEMAP_TYPE_MIMETYPE: |
240 | 0 | break; |
241 | 0 | } |
242 | 0 | }); |
243 | 0 | } |
244 | | |
245 | | #undef FOREACH_TYPE |
246 | | |
247 | | int h2o_mimemap_has_dynamic_type(h2o_mimemap_t *mimemap) |
248 | 521 | { |
249 | 521 | return mimemap->num_dynamic != 0; |
250 | 521 | } |
251 | | |
252 | | void set_default_type(h2o_mimemap_t *mimemap, h2o_mimemap_type_t *type) |
253 | 0 | { |
254 | | /* unlink the old one */ |
255 | 0 | on_unlink(mimemap, mimemap->default_type); |
256 | 0 | h2o_mem_release_shared(mimemap->default_type); |
257 | | |
258 | | /* update */ |
259 | 0 | h2o_mem_addref_shared(type); |
260 | 0 | mimemap->default_type = type; |
261 | 0 | on_link(mimemap, type); |
262 | 0 | rebuild_typeset(mimemap); |
263 | 0 | } |
264 | | |
265 | | void h2o_mimemap_set_default_type(h2o_mimemap_t *mimemap, const char *mime, h2o_mime_attributes_t *attr) |
266 | 0 | { |
267 | 0 | h2o_mimemap_type_t *new_type; |
268 | | |
269 | | /* obtain or create new type */ |
270 | 0 | if ((new_type = h2o_mimemap_get_type_by_mimetype(mimemap, h2o_iovec_init(mime, strlen(mime)), 1)) != NULL && |
271 | 0 | (attr == NULL || memcmp(&new_type->data.attr, attr, sizeof(*attr)) == 0)) { |
272 | 0 | h2o_mem_addref_shared(new_type); |
273 | 0 | } else { |
274 | 0 | new_type = create_extension_type(mime, attr); |
275 | 0 | } |
276 | |
|
277 | 0 | set_default_type(mimemap, new_type); |
278 | 0 | h2o_mem_release_shared(new_type); |
279 | 0 | } |
280 | | |
281 | | static void set_type(h2o_mimemap_t *mimemap, const char *ext, h2o_mimemap_type_t *type) |
282 | 272 | { |
283 | | /* obtain key, and remove the old value */ |
284 | 272 | khiter_t iter = kh_get(extmap, mimemap->extmap, ext); |
285 | 272 | if (iter != kh_end(mimemap->extmap)) { |
286 | 0 | h2o_mimemap_type_t *oldtype = kh_val(mimemap->extmap, iter); |
287 | 0 | on_unlink(mimemap, oldtype); |
288 | 0 | h2o_mem_release_shared(oldtype); |
289 | 272 | } else { |
290 | 272 | int ret; |
291 | 272 | iter = kh_put(extmap, mimemap->extmap, dupref(ext).base, &ret); |
292 | 272 | assert(iter != kh_end(mimemap->extmap)); |
293 | 272 | } |
294 | | |
295 | | /* update */ |
296 | 272 | h2o_mem_addref_shared(type); |
297 | 272 | kh_val(mimemap->extmap, iter) = type; |
298 | 272 | on_link(mimemap, type); |
299 | 272 | rebuild_typeset(mimemap); |
300 | 272 | } |
301 | | |
302 | | void h2o_mimemap_define_mimetype(h2o_mimemap_t *mimemap, const char *ext, const char *mime, h2o_mime_attributes_t *attr) |
303 | 272 | { |
304 | 272 | h2o_mimemap_type_t *new_type; |
305 | | |
306 | 272 | if ((new_type = h2o_mimemap_get_type_by_mimetype(mimemap, h2o_iovec_init(mime, strlen(mime)), 1)) != NULL && |
307 | 272 | (attr == NULL || memcmp(&new_type->data.attr, attr, sizeof(*attr)) == 0)) { |
308 | 68 | h2o_mem_addref_shared(new_type); |
309 | 204 | } else { |
310 | 204 | new_type = create_extension_type(mime, attr); |
311 | 204 | } |
312 | 272 | set_type(mimemap, ext, new_type); |
313 | 272 | h2o_mem_release_shared(new_type); |
314 | 272 | } |
315 | | |
316 | | h2o_mimemap_type_t *h2o_mimemap_define_dynamic(h2o_mimemap_t *mimemap, const char **exts, h2o_globalconf_t *globalconf) |
317 | 0 | { |
318 | | /* FIXME: fix memory leak introduced by this a cyclic link (mimemap -> new_type -> mimemap) |
319 | | * note also that we may want to update the reference from the dynamic type to the mimemap as we clone the mimemap, |
320 | | * but doing so naively would cause unnecessary copies of fastcgi.spawns... */ |
321 | 0 | h2o_mimemap_type_t *new_type = create_dynamic_type(globalconf, mimemap); |
322 | 0 | size_t i; |
323 | |
|
324 | 0 | for (i = 0; exts[i] != NULL; ++i) { |
325 | 0 | if (exts[i][0] == '\0') { |
326 | | /* empty string means default */ |
327 | 0 | set_default_type(mimemap, new_type); |
328 | 0 | } else { |
329 | 0 | set_type(mimemap, exts[i], new_type); |
330 | 0 | } |
331 | 0 | } |
332 | 0 | h2o_mem_release_shared(new_type); |
333 | 0 | return new_type; |
334 | 0 | } |
335 | | |
336 | | void h2o_mimemap_remove_type(h2o_mimemap_t *mimemap, const char *ext) |
337 | 0 | { |
338 | 0 | khiter_t iter = kh_get(extmap, mimemap->extmap, ext); |
339 | 0 | if (iter != kh_end(mimemap->extmap)) { |
340 | 0 | const char *key = kh_key(mimemap->extmap, iter); |
341 | 0 | h2o_mimemap_type_t *type = kh_val(mimemap->extmap, iter); |
342 | 0 | on_unlink(mimemap, type); |
343 | 0 | h2o_mem_release_shared(type); |
344 | 0 | kh_del(extmap, mimemap->extmap, iter); |
345 | 0 | h2o_mem_release_shared((char *)key); |
346 | 0 | rebuild_typeset(mimemap); |
347 | 0 | } |
348 | 0 | } |
349 | | |
350 | | void h2o_mimemap_clear_types(h2o_mimemap_t *mimemap) |
351 | 0 | { |
352 | 0 | khiter_t iter; |
353 | |
|
354 | 0 | for (iter = kh_begin(mimemap->extmap); iter != kh_end(mimemap->extmap); ++iter) { |
355 | 0 | if (!kh_exist(mimemap->extmap, iter)) |
356 | 0 | continue; |
357 | 0 | const char *key = kh_key(mimemap->extmap, iter); |
358 | 0 | h2o_mimemap_type_t *type = kh_val(mimemap->extmap, iter); |
359 | 0 | on_unlink(mimemap, type); |
360 | 0 | h2o_mem_release_shared(type); |
361 | 0 | kh_del(extmap, mimemap->extmap, iter); |
362 | 0 | h2o_mem_release_shared((char *)key); |
363 | 0 | } |
364 | 0 | rebuild_typeset(mimemap); |
365 | 0 | } |
366 | | |
367 | | h2o_mimemap_type_t *h2o_mimemap_get_default_type(h2o_mimemap_t *mimemap) |
368 | 0 | { |
369 | 0 | return mimemap->default_type; |
370 | 0 | } |
371 | | |
372 | | h2o_mimemap_type_t *h2o_mimemap_get_type_by_extension(h2o_mimemap_t *mimemap, h2o_iovec_t ext) |
373 | 0 | { |
374 | 0 | char lcbuf[256]; |
375 | |
|
376 | 0 | if (0 < ext.len && ext.len < sizeof(lcbuf)) { |
377 | 0 | memcpy(lcbuf, ext.base, ext.len); |
378 | 0 | h2o_strtolower(lcbuf, ext.len); |
379 | 0 | lcbuf[ext.len] = '\0'; |
380 | 0 | khiter_t iter = kh_get(extmap, mimemap->extmap, lcbuf); |
381 | 0 | if (iter != kh_end(mimemap->extmap)) |
382 | 0 | return kh_val(mimemap->extmap, iter); |
383 | 0 | } |
384 | 0 | return mimemap->default_type; |
385 | 0 | } |
386 | | |
387 | | h2o_mimemap_type_t *h2o_mimemap_get_type_by_mimetype(h2o_mimemap_t *mimemap, h2o_iovec_t mime, int exact_match_only) |
388 | 272 | { |
389 | 272 | h2o_mimemap_type_t key = {H2O_MIMEMAP_TYPE_MIMETYPE}; |
390 | 272 | khiter_t iter; |
391 | 272 | size_t type_end_at; |
392 | | |
393 | | /* exact match */ |
394 | 272 | key.data.mimetype = mime; |
395 | 272 | if ((iter = kh_get(typeset, mimemap->typeset, &key)) != kh_end(mimemap->typeset)) |
396 | 68 | return kh_key(mimemap->typeset, iter); |
397 | | |
398 | 204 | if (!exact_match_only) { |
399 | | /* determine the end of the type */ |
400 | 0 | for (type_end_at = 0; type_end_at != mime.len; ++type_end_at) |
401 | 0 | if (mime.base[type_end_at] == ';' || mime.base[type_end_at] == ' ') |
402 | 0 | goto HasAttributes; |
403 | 0 | } |
404 | 204 | return NULL; |
405 | | |
406 | 0 | HasAttributes: |
407 | | /* perform search without attributes */ |
408 | 0 | key.data.mimetype.len = type_end_at; |
409 | 0 | if ((iter = kh_get(typeset, mimemap->typeset, &key)) != kh_end(mimemap->typeset)) |
410 | 0 | return kh_key(mimemap->typeset, iter); |
411 | | |
412 | 0 | return NULL; |
413 | 0 | } |
414 | | |
415 | | void h2o_mimemap_get_default_attributes(const char *mime, h2o_mime_attributes_t *attr) |
416 | 206 | { |
417 | 206 | size_t mime_len; |
418 | | |
419 | 4.01k | for (mime_len = 0; !(mime[mime_len] == '\0' || mime[mime_len] == ';'); ++mime_len) |
420 | 3.80k | ; |
421 | | |
422 | 206 | *attr = (h2o_mime_attributes_t){0}; |
423 | | |
424 | 2.65k | #define MIME_IS(x) h2o_memis(mime, mime_len, H2O_STRLIT(x)) |
425 | 402 | #define MIME_STARTS_WITH(x) (mime_len >= sizeof(x) - 1 && memcmp(mime, x, sizeof(x) - 1) == 0) |
426 | 556 | #define MIME_ENDS_WITH(x) (mime_len >= sizeof(x) - 1 && memcmp(mime + mime_len - (sizeof(x) - 1), x, sizeof(x) - 1) == 0) |
427 | | |
428 | 206 | if (MIME_IS("text/css") || MIME_IS("application/ecmascript") || MIME_IS("application/javascript") || |
429 | 206 | MIME_IS("text/ecmascript") || MIME_IS("text/javascript")) { |
430 | 4 | attr->is_compressible = 1; |
431 | 4 | attr->priority = H2O_MIME_ATTRIBUTE_PRIORITY_HIGHEST; |
432 | 202 | } else if (MIME_IS("application/json") || MIME_IS("application/xml") || MIME_STARTS_WITH("text/") || MIME_ENDS_WITH("+json") || |
433 | 202 | MIME_ENDS_WITH("+xml")) { |
434 | 44 | attr->is_compressible = 1; |
435 | 44 | } |
436 | | |
437 | 206 | #undef MIME_IS |
438 | 206 | #undef MIME_STARTS_WITH |
439 | 206 | #undef MIME_ENDS_WITH |
440 | 206 | } |