Coverage Report

Created: 2025-07-12 06:32

/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
}