Coverage Report

Created: 2025-10-13 07:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/h2o/lib/handler/file.c
Line
Count
Source
1
/*
2
 * Copyright (c) 2014-2016 DeNA Co., Ltd., Kazuho Oku, Domingo Alvarez Duarte,
3
 *                         Tatsuhiko Kubo, Nick Desaulniers, Marc Hoersken,
4
 *                         Justin Zhu, Tatsuhiro Tsujikawa
5
 *
6
 * Permission is hereby granted, free of charge, to any person obtaining a copy
7
 * of this software and associated documentation files (the "Software"), to
8
 * deal in the Software without restriction, including without limitation the
9
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10
 * sell copies of the Software, and to permit persons to whom the Software is
11
 * furnished to do so, subject to the following conditions:
12
 *
13
 * The above copyright notice and this permission notice shall be included in
14
 * all copies or substantial portions of the Software.
15
 *
16
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22
 * IN THE SOFTWARE.
23
 */
24
#include <dirent.h>
25
#include <errno.h>
26
#include <fcntl.h>
27
#include <limits.h>
28
#include <stdio.h>
29
#include <stdlib.h>
30
#if defined(__linux__)
31
#include <sys/sendfile.h>
32
#endif
33
#include <sys/stat.h>
34
#include <sys/types.h>
35
#include <time.h>
36
#include <unistd.h>
37
#include "h2o.h"
38
#include "h2o/pipe_sender.h"
39
#if H2O_USE_IO_URING
40
#include "h2o/io_uring.h"
41
#endif
42
43
0
#define MAX_BUF_SIZE 65000
44
0
#define BOUNDARY_SIZE 20
45
0
#define FIXED_PART_SIZE (sizeof("\r\n--") - 1 + BOUNDARY_SIZE + sizeof("\r\nContent-Range: bytes=-/\r\nContent-Type: \r\n\r\n") - 1)
46
47
struct st_h2o_sendfile_generator_t {
48
    h2o_generator_t super;
49
    struct {
50
        h2o_filecache_ref_t *ref;
51
        off_t off;
52
    } file;
53
    size_t bytesleft;
54
    h2o_iovec_t content_encoding;
55
    unsigned send_vary : 1;
56
    unsigned send_etag : 1;
57
    unsigned gunzip : 1;
58
    struct {
59
        char *multirange_buf; /* multi-range mode uses push */
60
        size_t filesize;
61
        size_t range_count;
62
        size_t *range_infos;  /* size_t shows in pair. first is start offset, then length */
63
        h2o_iovec_t boundary; /* boundary used for multipart/byteranges */
64
        h2o_iovec_t mimetype; /* original mimetype for multipart */
65
        size_t current_range; /* range that processing now */
66
    } ranged;
67
    struct {
68
        char last_modified[H2O_TIMESTR_RFC1123_LEN + 1];
69
        char etag[H2O_FILECACHE_ETAG_MAXLEN + 1];
70
    } header_bufs;
71
#if H2O_USE_IO_URING
72
    /**
73
     * back pointer to the request which is necessary for splicing async; becomes NULL when the generator is stopped
74
     */
75
    h2o_req_t *src_req;
76
    h2o_pipe_sender_t pipe_sender;
77
#endif
78
};
79
80
struct st_h2o_file_handler_t {
81
    h2o_handler_t super;
82
    h2o_iovec_t conf_path; /* has "/" appended at last */
83
    h2o_iovec_t real_path; /* has "/" appended at last */
84
    h2o_mimemap_t *mimemap;
85
    int flags;
86
    size_t max_index_file_len;
87
    h2o_iovec_t index_files[1];
88
};
89
90
struct st_h2o_specific_file_handler_t {
91
    h2o_handler_t super;
92
    h2o_iovec_t real_path;
93
    h2o_mimemap_type_t *mime_type;
94
    int flags;
95
};
96
97
struct st_gzip_decompress_t {
98
    h2o_ostream_t super;
99
    h2o_compress_context_t *decompressor;
100
};
101
102
static const char *default_index_files[] = {"index.html", "index.htm", "index.txt", NULL};
103
104
const char **h2o_file_default_index_files = default_index_files;
105
106
#include "file/templates.c.h"
107
108
static int tm_is_lessthan(struct tm *x, struct tm *y)
109
0
{
110
0
#define CMP(f)                                                                                                                     \
111
0
    if (x->f < y->f)                                                                                                               \
112
0
        return 1;                                                                                                                  \
113
0
    else if (x->f > y->f)                                                                                                          \
114
0
        return 0;
115
0
    CMP(tm_year);
116
0
    CMP(tm_mon);
117
0
    CMP(tm_mday);
118
0
    CMP(tm_hour);
119
0
    CMP(tm_min);
120
0
    CMP(tm_sec);
121
0
    return 0;
122
0
#undef CMP
123
0
}
124
125
static void close_file(struct st_h2o_sendfile_generator_t *self)
126
0
{
127
0
    if (self->file.ref != NULL) {
128
0
        h2o_filecache_close_file(self->file.ref);
129
0
        self->file.ref = NULL;
130
0
    }
131
#if H2O_USE_IO_URING
132
    h2o_pipe_sender_dispose(&self->pipe_sender, self->src_req != NULL ? self->src_req->conn->ctx : NULL);
133
#endif
134
0
}
135
136
static void on_generator_dispose(void *_self)
137
0
{
138
0
    struct st_h2o_sendfile_generator_t *self = _self;
139
0
    close_file(self);
140
0
}
141
142
#if H2O_USE_IO_URING
143
144
static void do_stop_async_splice(h2o_generator_t *_self, h2o_req_t *req)
145
{
146
    struct st_h2o_sendfile_generator_t *self = (void *)_self;
147
    self->src_req = NULL;
148
}
149
150
static void do_proceed_on_splice_complete(h2o_io_uring_cmd_t *cmd)
151
{
152
    struct st_h2o_sendfile_generator_t *self = cmd->cb.data;
153
154
    if (self->src_req == NULL) {
155
        h2o_mem_release_shared(self);
156
        return;
157
    }
158
159
    h2o_mem_release_shared(self);
160
161
    if (cmd->result <= 0) {
162
        assert(cmd->result != -EINTR); /* could this ever happen? */
163
        h2o_send(self->src_req, NULL, 0, H2O_SEND_STATE_ERROR);
164
        return;
165
    }
166
167
    self->file.off += cmd->result;
168
    self->bytesleft -= cmd->result;
169
170
    h2o_send_state_t send_state = self->bytesleft != 0 ? H2O_SEND_STATE_IN_PROGRESS : H2O_SEND_STATE_FINAL;
171
172
    h2o_pipe_sender_send(self->src_req, &self->pipe_sender, cmd->result, send_state);
173
}
174
175
#endif
176
177
static int do_pread(h2o_sendvec_t *src, void *dst, size_t len)
178
0
{
179
0
    struct st_h2o_sendfile_generator_t *self = (void *)src->cb_arg[0];
180
0
    uint64_t *file_chunk_at = &src->cb_arg[1];
181
0
    size_t bytes_read = 0;
182
0
    ssize_t rret;
183
184
    /* read */
185
0
    while (bytes_read < len) {
186
0
        while ((rret = pread(self->file.ref->fd, dst + bytes_read, len - bytes_read, *file_chunk_at)) == -1 && errno == EINTR)
187
0
            ;
188
0
        if (rret <= 0)
189
0
            return 0;
190
0
        bytes_read += rret;
191
0
        *file_chunk_at += rret;
192
0
        src->len -= rret;
193
0
    }
194
195
0
    return 1;
196
0
}
197
198
#if defined(__linux__)
199
static size_t do_sendfile(int sockfd, int filefd, off_t off, size_t len)
200
0
{
201
0
    off_t iooff = off;
202
0
    ssize_t ret;
203
0
    while ((ret = sendfile(sockfd, filefd, &iooff, len)) == -1 && errno == EINTR)
204
0
        ;
205
0
    if (ret <= 0)
206
0
        return ret == -1 && errno == EAGAIN ? 0 : SIZE_MAX;
207
0
    return ret;
208
0
}
209
#elif defined(__APPLE__)
210
static size_t do_sendfile(int sockfd, int filefd, off_t off, size_t len)
211
{
212
    off_t iolen = len;
213
    int ret;
214
    while ((ret = sendfile(filefd, sockfd, off, &iolen, NULL, 0)) != 0 && errno == EINTR)
215
        ;
216
    if (ret != 0 && errno != EAGAIN)
217
        return SIZE_MAX;
218
    return iolen;
219
}
220
#elif defined(__FreeBSD__)
221
static size_t do_sendfile(int sockfd, int filefd, off_t off, size_t len)
222
{
223
    off_t outlen;
224
    int ret;
225
    while ((ret = sendfile(filefd, sockfd, off, len, NULL, &outlen, 0)) != 0 && errno == EINTR)
226
        ;
227
    if (ret != 0 && errno != EAGAIN)
228
        return SIZE_MAX;
229
    return outlen;
230
}
231
#else
232
#define sendvec_sendfile NULL
233
#endif
234
#if !defined(sendvec_sendfile)
235
static size_t sendvec_sendfile(h2o_sendvec_t *src, int sockfd, size_t len)
236
0
{
237
0
    struct st_h2o_sendfile_generator_t *self = (void *)src->cb_arg[0];
238
0
    ssize_t bytes_sent = do_sendfile(sockfd, self->file.ref->fd, (off_t)src->cb_arg[1], len);
239
0
    if (bytes_sent > 0) {
240
0
        src->cb_arg[1] += bytes_sent;
241
0
        src->len -= bytes_sent;
242
0
    }
243
0
    return bytes_sent;
244
0
}
245
#endif
246
247
static void do_proceed(h2o_generator_t *_self, h2o_req_t *req)
248
0
{
249
0
    struct st_h2o_sendfile_generator_t *self = (void *)_self;
250
0
    size_t bytes_to_send = self->bytesleft < H2O_PULL_SENDVEC_MAX_SIZE ? self->bytesleft : H2O_PULL_SENDVEC_MAX_SIZE;
251
252
    /* if io_uring is to be used, addref so that the self would not be released, then call `h2o_io_uring_splice_file` */
253
#if H2O_USE_IO_URING
254
    if (h2o_pipe_sender_in_use(&self->pipe_sender)) {
255
        h2o_mem_addref_shared(self);
256
        h2o_io_uring_splice(self->src_req->conn->ctx->loop, self->file.ref->fd, self->file.off, self->pipe_sender.fds[1], -1,
257
                            bytes_to_send, 0, do_proceed_on_splice_complete, self);
258
        return;
259
    }
260
#endif
261
262
0
    static const h2o_sendvec_callbacks_t sendvec_callbacks = {.read_ = do_pread, .send_ = sendvec_sendfile};
263
0
    h2o_sendvec_t vec = {
264
0
        .callbacks = &sendvec_callbacks, .len = bytes_to_send, .cb_arg[0] = (uint64_t)self, .cb_arg[1] = self->file.off};
265
266
0
    self->file.off += vec.len;
267
0
    self->bytesleft -= vec.len;
268
269
0
    h2o_sendvec(req, &vec, 1, self->bytesleft != 0 ? H2O_SEND_STATE_IN_PROGRESS : H2O_SEND_STATE_FINAL);
270
0
}
271
272
static void do_multirange_proceed(h2o_generator_t *_self, h2o_req_t *req)
273
0
{
274
0
    struct st_h2o_sendfile_generator_t *self = (void *)_self;
275
0
    size_t rlen, used_buf = 0;
276
0
    ssize_t rret, vecarrsize;
277
0
    h2o_iovec_t vec[2];
278
0
    h2o_send_state_t send_state;
279
280
0
    if (self->bytesleft == 0) {
281
0
        size_t *range_cur = self->ranged.range_infos + 2 * self->ranged.current_range;
282
0
        size_t range_end = *range_cur + *(range_cur + 1) - 1;
283
0
        if (H2O_LIKELY(self->ranged.current_range != 0))
284
0
            used_buf =
285
0
                sprintf(self->ranged.multirange_buf, "\r\n--%s\r\nContent-Type: %s\r\nContent-Range: bytes %zd-%zd/%zd\r\n\r\n",
286
0
                        self->ranged.boundary.base, self->ranged.mimetype.base, *range_cur, range_end, self->ranged.filesize);
287
0
        else
288
0
            used_buf =
289
0
                sprintf(self->ranged.multirange_buf, "--%s\r\nContent-Type: %s\r\nContent-Range: bytes %zd-%zd/%zd\r\n\r\n",
290
0
                        self->ranged.boundary.base, self->ranged.mimetype.base, *range_cur, range_end, self->ranged.filesize);
291
0
        self->ranged.current_range++;
292
0
        self->file.off = *range_cur;
293
0
        self->bytesleft = *++range_cur;
294
0
    }
295
0
    rlen = self->bytesleft;
296
0
    if (rlen + used_buf > MAX_BUF_SIZE)
297
0
        rlen = MAX_BUF_SIZE - used_buf;
298
0
    while ((rret = pread(self->file.ref->fd, self->ranged.multirange_buf + used_buf, rlen, self->file.off)) == -1 && errno == EINTR)
299
0
        ;
300
0
    if (rret == -1)
301
0
        goto Error;
302
0
    self->file.off += rret;
303
0
    self->bytesleft -= rret;
304
305
0
    vec[0].base = self->ranged.multirange_buf;
306
0
    vec[0].len = rret + used_buf;
307
0
    if (self->ranged.current_range == self->ranged.range_count && self->bytesleft == 0) {
308
0
        vec[1].base = h2o_mem_alloc_pool(&req->pool, char, sizeof("\r\n--") - 1 + BOUNDARY_SIZE + sizeof("--\r\n"));
309
0
        vec[1].len = sprintf(vec[1].base, "\r\n--%s--\r\n", self->ranged.boundary.base);
310
0
        vecarrsize = 2;
311
0
        send_state = H2O_SEND_STATE_FINAL;
312
0
    } else {
313
0
        vecarrsize = 1;
314
0
        send_state = H2O_SEND_STATE_IN_PROGRESS;
315
0
    }
316
0
    h2o_send(req, vec, vecarrsize, send_state);
317
0
    return;
318
319
0
Error:
320
0
    h2o_send(req, NULL, 0, H2O_SEND_STATE_ERROR);
321
0
    return;
322
0
}
323
324
static struct st_h2o_sendfile_generator_t *create_generator(h2o_req_t *req, const char *path, size_t path_len, int *is_dir,
325
                                                            int flags)
326
15.8k
{
327
15.8k
    struct st_h2o_sendfile_generator_t *self;
328
15.8k
    h2o_filecache_ref_t *fileref;
329
15.8k
    h2o_iovec_t content_encoding = (h2o_iovec_t){NULL};
330
15.8k
    unsigned gunzip = 0;
331
332
15.8k
    *is_dir = 0;
333
334
15.8k
    if ((flags & H2O_FILE_FLAG_SEND_COMPRESSED) != 0 && req->version >= 0x101) {
335
0
        int compressible_types = h2o_get_compressible_types(&req->headers);
336
0
        if (compressible_types != 0) {
337
0
            char *variant_path = h2o_mem_alloc_pool(&req->pool, *variant_path, path_len + sizeof(".gz"));
338
0
            memcpy(variant_path, path, path_len);
339
0
#define TRY_VARIANT(mask, enc, ext)                                                                                                \
340
0
    if ((compressible_types & mask) != 0) {                                                                                        \
341
0
        strcpy(variant_path + path_len, ext);                                                                                      \
342
0
        if ((fileref = h2o_filecache_open_file(req->conn->ctx->filecache, variant_path, O_RDONLY | O_CLOEXEC)) != NULL) {          \
343
0
            content_encoding = h2o_iovec_init(enc, sizeof(enc) - 1);                                                               \
344
0
            goto Opened;                                                                                                           \
345
0
        }                                                                                                                          \
346
0
    }
347
0
            TRY_VARIANT(H2O_COMPRESSIBLE_BROTLI, "br", ".br");
348
0
            TRY_VARIANT(H2O_COMPRESSIBLE_ZSTD, "zstd", ".zst");
349
0
            TRY_VARIANT(H2O_COMPRESSIBLE_GZIP, "gzip", ".gz");
350
351
            // Deprecated
352
0
            TRY_VARIANT(H2O_COMPRESSIBLE_ZSTD, "zstd", ".zstd");
353
0
#undef TRY_VARIANT
354
0
        }
355
0
    }
356
15.8k
    if ((fileref = h2o_filecache_open_file(req->conn->ctx->filecache, path, O_RDONLY | O_CLOEXEC)) != NULL) {
357
0
        goto Opened;
358
0
    }
359
15.8k
    if ((flags & H2O_FILE_FLAG_GUNZIP) != 0 && req->version >= 0x101) {
360
0
        char *variant_path = h2o_mem_alloc_pool(&req->pool, *variant_path, path_len + sizeof(".gz"));
361
0
        memcpy(variant_path, path, path_len);
362
0
        strcpy(variant_path + path_len, ".gz");
363
0
        if ((fileref = h2o_filecache_open_file(req->conn->ctx->filecache, variant_path, O_RDONLY | O_CLOEXEC)) != NULL) {
364
0
            gunzip = 1;
365
0
            goto Opened;
366
0
        }
367
0
    }
368
15.8k
    return NULL;
369
370
0
Opened:
371
0
    if (S_ISDIR(fileref->st.st_mode)) {
372
0
        h2o_filecache_close_file(fileref);
373
0
        *is_dir = 1;
374
0
        return NULL;
375
0
    }
376
377
0
    self = h2o_mem_alloc_shared(&req->pool, sizeof(*self), on_generator_dispose);
378
0
    self->super.proceed = do_proceed;
379
0
    self->super.stop = NULL;
380
0
    self->file.ref = fileref;
381
0
    self->file.off = 0;
382
0
    self->bytesleft = self->file.ref->st.st_size;
383
0
    self->ranged.range_count = 0;
384
0
    self->ranged.range_infos = NULL;
385
0
    self->content_encoding = content_encoding;
386
0
    self->send_vary = (flags & H2O_FILE_FLAG_SEND_COMPRESSED) != 0;
387
0
    self->send_etag = (flags & H2O_FILE_FLAG_NO_ETAG) == 0;
388
0
    self->gunzip = gunzip;
389
#if H2O_USE_IO_URING
390
    self->src_req = req;
391
    h2o_pipe_sender_init(&self->pipe_sender);
392
    int try_async_splice = (flags & H2O_FILE_FLAG_IO_URING) != 0 && self->bytesleft != 0;
393
    if (try_async_splice && h2o_pipe_sender_start(req->conn->ctx, &self->pipe_sender)) {
394
        self->super.stop = do_stop_async_splice;
395
    } else if (try_async_splice) {
396
        h2o_req_log_error(req, "lib/handler/file.c", "failed to allocate a pipe for async I/O; falling back to blocking I/O");
397
    }
398
#endif
399
400
0
    return self;
401
0
}
402
403
static void add_headers_unconditional(struct st_h2o_sendfile_generator_t *self, h2o_req_t *req)
404
0
{
405
    /* RFC 7232 4.1: The server generating a 304 response MUST generate any of the following header fields that would have been sent
406
     * in a 200 (OK) response to the same request: Cache-Control, Content-Location, Date, ETag, Expires, and Vary (snip) a sender
407
     * SHOULD NOT generate representation metadata other than the above listed fields unless said metadata exists for the purpose of
408
     * guiding cache updates. */
409
0
    if (self->send_etag) {
410
0
        size_t etag_len = h2o_filecache_get_etag(self->file.ref, self->header_bufs.etag);
411
0
        h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_ETAG, NULL, self->header_bufs.etag, etag_len);
412
0
    }
413
0
    if (self->send_vary)
414
0
        h2o_set_header_token(&req->pool, &req->res.headers, H2O_TOKEN_VARY, H2O_STRLIT("accept-encoding"));
415
0
}
416
417
static void send_decompressed(h2o_ostream_t *_self, h2o_req_t *req, h2o_sendvec_t *inbufs, size_t inbufcnt, h2o_send_state_t state)
418
0
{
419
0
    if (inbufcnt == 0 && h2o_send_state_is_in_progress(state)) {
420
0
        h2o_ostream_send_next(_self, req, inbufs, inbufcnt, state);
421
0
        return;
422
0
    }
423
424
0
    struct st_gzip_decompress_t *self = (void *)_self;
425
0
    h2o_sendvec_t *outbufs;
426
0
    size_t outbufcnt;
427
428
0
    state = h2o_compress_transform(self->decompressor, req, inbufs, inbufcnt, state, &outbufs, &outbufcnt);
429
0
    h2o_ostream_send_next(&self->super, req, outbufs, outbufcnt, state);
430
0
}
431
432
static void do_send_file(struct st_h2o_sendfile_generator_t *self, h2o_req_t *req, int status, const char *reason,
433
                         h2o_iovec_t mime_type, h2o_mime_attributes_t *mime_attr, int is_get)
434
0
{
435
    /* setup response */
436
0
    req->res.status = status;
437
0
    req->res.reason = reason;
438
0
    req->res.content_length = self->gunzip ? SIZE_MAX : self->bytesleft;
439
0
    req->res.mime_attr = mime_attr;
440
441
0
    if (self->ranged.range_count > 1) {
442
0
        mime_type.base = h2o_mem_alloc_pool(&req->pool, char, 52);
443
0
        mime_type.len = sprintf(mime_type.base, "multipart/byteranges; boundary=%s", self->ranged.boundary.base);
444
0
    }
445
0
    h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_TYPE, NULL, mime_type.base, mime_type.len);
446
0
    h2o_filecache_get_last_modified(self->file.ref, self->header_bufs.last_modified);
447
0
    h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_LAST_MODIFIED, NULL, self->header_bufs.last_modified,
448
0
                   H2O_TIMESTR_RFC1123_LEN);
449
0
    add_headers_unconditional(self, req);
450
0
    if (self->content_encoding.base != NULL)
451
0
        h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_ENCODING, NULL, self->content_encoding.base,
452
0
                       self->content_encoding.len);
453
0
    if (self->ranged.range_count == 0)
454
0
        h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_ACCEPT_RANGES, NULL, H2O_STRLIT("bytes"));
455
0
    else if (self->ranged.range_count == 1) {
456
0
        h2o_iovec_t content_range;
457
0
        content_range.base = h2o_mem_alloc_pool(&req->pool, char, 128);
458
0
        content_range.len = sprintf(content_range.base, "bytes %zd-%zd/%zd", self->ranged.range_infos[0],
459
0
                                    self->ranged.range_infos[0] + self->ranged.range_infos[1] - 1, self->ranged.filesize);
460
0
        h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_RANGE, NULL, content_range.base, content_range.len);
461
0
    }
462
463
    /* special path for cases where we do not need to send any data */
464
0
    if (!is_get || self->bytesleft == 0) {
465
0
        static h2o_generator_t generator = {NULL, NULL};
466
0
        h2o_start_response(req, &generator);
467
0
        h2o_send(req, NULL, 0, H2O_SEND_STATE_FINAL);
468
0
        return;
469
0
    }
470
471
    /* send data */
472
0
    h2o_start_response(req, &self->super);
473
474
    /* dynamically setup gzip decompress ostream */
475
0
    if (self->gunzip) {
476
0
        struct st_gzip_decompress_t *decoder =
477
0
            (void *)h2o_add_ostream(req, H2O_ALIGNOF(*decoder), sizeof(*decoder), &req->_ostr_top);
478
0
        decoder->decompressor = h2o_compress_gunzip_open(&req->pool);
479
0
        decoder->super.do_send = send_decompressed;
480
        /* FIXME disable pull mode */
481
0
    }
482
483
0
    if (self->ranged.range_count == 1)
484
0
        self->file.off = self->ranged.range_infos[0];
485
486
0
    if (self->ranged.range_count < 2)
487
0
        do_proceed(&self->super, req);
488
0
    else {
489
0
        self->ranged.multirange_buf = h2o_mem_alloc_pool(&req->pool, char, MAX_BUF_SIZE);
490
0
        self->bytesleft = 0;
491
0
        self->super.proceed = do_multirange_proceed;
492
0
        do_multirange_proceed(&self->super, req);
493
0
    }
494
0
}
495
496
int h2o_file_send(h2o_req_t *req, int status, const char *reason, const char *path, h2o_iovec_t mime_type, int flags)
497
0
{
498
0
    struct st_h2o_sendfile_generator_t *self;
499
0
    int is_dir;
500
501
0
    if ((self = create_generator(req, path, strlen(path), &is_dir, flags)) == NULL)
502
0
        return -1;
503
    /* note: is_dir is not handled */
504
0
    do_send_file(self, req, status, reason, mime_type, NULL, 1);
505
0
    return 0;
506
0
}
507
508
static int send_dir_listing(h2o_req_t *req, const char *path, size_t path_len, int is_get)
509
0
{
510
0
    static h2o_generator_t generator = {NULL, NULL};
511
0
    DIR *dp;
512
0
    h2o_buffer_t *body;
513
0
    h2o_iovec_t bodyvec;
514
515
    /* build html */
516
0
    if ((dp = opendir(path)) == NULL)
517
0
        return -1;
518
0
    body = build_dir_listing_html(&req->pool, req->path_normalized, dp);
519
0
    closedir(dp);
520
521
0
    if (body == NULL) {
522
0
        h2o_send_error_503(req, "Service Unavailable", "please try again later", 0);
523
0
        return 0;
524
0
    }
525
526
0
    bodyvec = h2o_iovec_init(body->bytes, body->size);
527
0
    h2o_buffer_link_to_pool(body, &req->pool);
528
529
    /* send response */
530
0
    req->res.status = 200;
531
0
    req->res.reason = "OK";
532
0
    h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_TYPE, NULL, H2O_STRLIT("text/html; charset=utf-8"));
533
534
    /* send headers */
535
0
    if (!is_get) {
536
0
        h2o_send_inline(req, NULL, 0);
537
0
        return 0;
538
0
    }
539
540
    /* send data */
541
0
    h2o_start_response(req, &generator);
542
0
    h2o_send(req, &bodyvec, 1, H2O_SEND_STATE_FINAL);
543
0
    return 0;
544
0
}
545
546
static size_t *process_range(h2o_mem_pool_t *pool, h2o_iovec_t *range_value, size_t file_size, size_t *ret)
547
0
{
548
0
#define CHECK_EOF()                                                                                                                \
549
0
    if (buf == buf_end)                                                                                                            \
550
0
        return NULL;
551
552
0
#define CHECK_OVERFLOW(range)                                                                                                      \
553
0
    if (range == SIZE_MAX)                                                                                                         \
554
0
        return NULL;
555
556
0
    size_t range_start = SIZE_MAX, range_count = 0;
557
0
    char *buf = range_value->base, *buf_end = buf + range_value->len;
558
0
    int needs_comma = 0;
559
0
    H2O_VECTOR(size_t) ranges = {NULL};
560
561
0
    if (range_value->len < 6 || memcmp(buf, "bytes=", 6) != 0)
562
0
        return NULL;
563
564
0
    buf += 6;
565
0
    CHECK_EOF();
566
567
    /* most range requests contain only one range */
568
0
    do {
569
0
        while (1) {
570
0
            if (*buf != ',') {
571
0
                if (needs_comma)
572
0
                    return NULL;
573
0
                break;
574
0
            }
575
0
            needs_comma = 0;
576
0
            buf++;
577
0
            while (H2O_UNLIKELY(*buf == ' ') || H2O_UNLIKELY(*buf == '\t')) {
578
0
                buf++;
579
0
                CHECK_EOF();
580
0
            }
581
0
        }
582
0
        if (H2O_UNLIKELY(buf == buf_end))
583
0
            break;
584
0
        if (H2O_LIKELY((range_start = h2o_strtosizefwd(&buf, buf_end - buf)) != SIZE_MAX)) {
585
0
            CHECK_EOF();
586
0
            if (*buf++ != '-')
587
0
                return NULL;
588
0
            range_count = h2o_strtosizefwd(&buf, buf_end - buf);
589
0
            if (H2O_UNLIKELY(range_start >= file_size)) {
590
0
                range_start = SIZE_MAX;
591
0
            } else if (H2O_LIKELY(range_count != SIZE_MAX)) {
592
0
                if (H2O_UNLIKELY(range_count > file_size - 1))
593
0
                    range_count = file_size - 1;
594
0
                if (H2O_LIKELY(range_start <= range_count))
595
0
                    range_count -= range_start - 1;
596
0
                else
597
0
                    range_start = SIZE_MAX;
598
0
            } else {
599
0
                range_count = file_size - range_start;
600
0
            }
601
0
        } else if (H2O_LIKELY(*buf++ == '-')) {
602
0
            CHECK_EOF();
603
0
            range_count = h2o_strtosizefwd(&buf, buf_end - buf);
604
0
            if (H2O_UNLIKELY(range_count == SIZE_MAX))
605
0
                return NULL;
606
0
            if (H2O_LIKELY(range_count != 0)) {
607
0
                if (H2O_UNLIKELY(range_count > file_size))
608
0
                    range_count = file_size;
609
0
                range_start = file_size - range_count;
610
0
            } else {
611
0
                range_start = SIZE_MAX;
612
0
            }
613
0
        } else {
614
0
            return NULL;
615
0
        }
616
617
0
        if (H2O_LIKELY(range_start != SIZE_MAX)) {
618
0
            h2o_vector_reserve(pool, &ranges, ranges.size + 2);
619
0
            ranges.entries[ranges.size++] = range_start;
620
0
            ranges.entries[ranges.size++] = range_count;
621
0
        }
622
0
        if (buf != buf_end)
623
0
            while (H2O_UNLIKELY(*buf == ' ') || H2O_UNLIKELY(*buf == '\t')) {
624
0
                buf++;
625
0
                CHECK_EOF();
626
0
            }
627
0
        needs_comma = 1;
628
0
    } while (H2O_UNLIKELY(buf < buf_end));
629
0
    *ret = ranges.size / 2;
630
0
    return ranges.entries;
631
0
#undef CHECK_EOF
632
0
#undef CHECK_OVERFLOW
633
0
}
634
635
static void gen_rand_string(h2o_iovec_t *s)
636
0
{
637
0
    int i;
638
0
    static const char alphanum[] = "0123456789"
639
0
                                   "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
640
0
                                   "abcdefghijklmnopqrstuvwxyz";
641
642
0
    for (i = 0; i < s->len; ++i) {
643
0
        s->base[i] = alphanum[h2o_rand() % (sizeof(alphanum) - 1)];
644
0
    }
645
646
0
    s->base[s->len] = 0;
647
0
}
648
649
static int delegate_dynamic_request(h2o_req_t *req, h2o_iovec_t script_name, h2o_iovec_t path_info, const char *local_path,
650
                                    size_t local_path_len, h2o_mimemap_type_t *mime_type)
651
0
{
652
0
    h2o_filereq_t *filereq;
653
654
0
    assert(mime_type->data.dynamic.pathconf.handlers.size == 1);
655
0
    assert(mime_type->data.dynamic.pathconf._filters.size == 0);
656
0
    assert(mime_type->data.dynamic.pathconf._loggers.size == 0);
657
658
    /* setup CGI attributes (e.g., PATH_INFO) */
659
0
    filereq = h2o_mem_alloc_pool(&req->pool, *filereq, 1);
660
0
    filereq->script_name = script_name;
661
0
    filereq->path_info = path_info;
662
0
    filereq->local_path = h2o_strdup(&req->pool, local_path, local_path_len);
663
0
    req->filereq = filereq;
664
665
    /* apply environment */
666
0
    if (mime_type->data.dynamic.pathconf.env != NULL)
667
0
        h2o_req_apply_env(req, mime_type->data.dynamic.pathconf.env);
668
669
    /* call the dynamic handler while retaining current hostconf or pathconf; in other words, filters and loggers of current
670
     * path level is applied, rather than of the extension level */
671
0
    h2o_handler_t *handler = mime_type->data.dynamic.pathconf.handlers.entries[0];
672
0
    return handler->on_req(handler, req);
673
0
}
674
675
static int try_dynamic_request(h2o_file_handler_t *self, h2o_req_t *req, char *rpath, size_t rpath_len)
676
0
{
677
    /* we have full local path in {rpath,rpath_len}, and need to split it into name and path_info */
678
0
    struct stat st;
679
0
    size_t slash_at = self->real_path.len;
680
681
0
    while (1) {
682
        /* find the next slash (or return -1 if failed) */
683
0
        for (++slash_at;; ++slash_at) {
684
0
            if (slash_at >= rpath_len)
685
0
                return -1;
686
0
            if (rpath[slash_at] == '/')
687
0
                break;
688
0
        }
689
        /* change the slash to '\0', and check if the file exists */
690
0
        rpath[slash_at] = '\0';
691
0
        if (stat(rpath, &st) != 0)
692
0
            return -1;
693
0
        if (!S_ISDIR(st.st_mode))
694
0
            break;
695
        /* restore slash, and continue the search */
696
0
        rpath[slash_at] = '/';
697
0
    }
698
699
    /* file found! */
700
0
    h2o_mimemap_type_t *mime_type = h2o_mimemap_get_type_by_extension(self->mimemap, h2o_get_filext(rpath, slash_at));
701
0
    switch (mime_type->type) {
702
0
    case H2O_MIMEMAP_TYPE_MIMETYPE:
703
0
        return -1;
704
0
    case H2O_MIMEMAP_TYPE_DYNAMIC: {
705
0
        h2o_iovec_t script_name = h2o_iovec_init(req->path_normalized.base, self->conf_path.len + slash_at - self->real_path.len);
706
0
        h2o_iovec_t path_info =
707
0
            h2o_iovec_init(req->path_normalized.base + script_name.len, req->path_normalized.len - script_name.len);
708
0
        return delegate_dynamic_request(req, script_name, path_info, rpath, slash_at, mime_type);
709
0
    }
710
0
    }
711
0
    h2o_fatal("unknown h2o_miemmap_type_t::type (%d)\n", (int)mime_type->type);
712
0
}
713
714
static void send_method_not_allowed(h2o_req_t *req)
715
0
{
716
0
    h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_ALLOW, NULL, H2O_STRLIT("GET, HEAD"));
717
0
    h2o_send_error_405(req, "Method Not Allowed", "method not allowed", H2O_SEND_ERROR_KEEP_HEADERS);
718
0
}
719
720
static int serve_with_generator(struct st_h2o_sendfile_generator_t *generator, h2o_req_t *req, h2o_iovec_t resolved_path,
721
                                const char *rpath, size_t rpath_len, h2o_mimemap_type_t *mime_type)
722
0
{
723
0
    enum { METHOD_IS_GET, METHOD_IS_HEAD, METHOD_IS_OTHER } method_type;
724
0
    size_t if_modified_since_header_index, if_none_match_header_index;
725
0
    size_t range_header_index, if_range_header_index;
726
727
    /* determine the method */
728
0
    if (h2o_memis(req->method.base, req->method.len, H2O_STRLIT("GET"))) {
729
0
        method_type = METHOD_IS_GET;
730
0
    } else if (h2o_memis(req->method.base, req->method.len, H2O_STRLIT("HEAD"))) {
731
0
        method_type = METHOD_IS_HEAD;
732
0
    } else {
733
0
        method_type = METHOD_IS_OTHER;
734
0
    }
735
736
    /* obtain mime type */
737
0
    if (mime_type->type == H2O_MIMEMAP_TYPE_DYNAMIC) {
738
0
        assert(generator->file.ref != NULL);
739
0
        close_file(generator);
740
0
        return delegate_dynamic_request(req, resolved_path, h2o_iovec_init(NULL, 0), rpath, rpath_len, mime_type);
741
0
    }
742
0
    assert(mime_type->type == H2O_MIMEMAP_TYPE_MIMETYPE);
743
744
    /* if-non-match and if-modified-since */
745
0
    if ((if_none_match_header_index = h2o_find_header(&req->headers, H2O_TOKEN_IF_NONE_MATCH, -1)) != -1) {
746
0
        h2o_iovec_t *if_none_match = &req->headers.entries[if_none_match_header_index].value;
747
0
        char etag[H2O_FILECACHE_ETAG_MAXLEN + 1];
748
0
        size_t etag_len = h2o_filecache_get_etag(generator->file.ref, etag);
749
0
        if (h2o_filecache_compare_etag_strong(if_none_match->base, if_none_match->len, etag, etag_len))
750
0
            goto NotModified;
751
0
    } else if ((if_modified_since_header_index = h2o_find_header(&req->headers, H2O_TOKEN_IF_MODIFIED_SINCE, -1)) != -1) {
752
0
        h2o_iovec_t *ims_vec = &req->headers.entries[if_modified_since_header_index].value;
753
0
        struct tm ims_tm, *last_modified_tm;
754
0
        if (h2o_time_parse_rfc1123(ims_vec->base, ims_vec->len, &ims_tm) == 0) {
755
0
            last_modified_tm = h2o_filecache_get_last_modified(generator->file.ref, NULL);
756
0
            if (!tm_is_lessthan(&ims_tm, last_modified_tm))
757
0
                goto NotModified;
758
0
        }
759
0
    }
760
761
    /* only allow GET or HEAD for static files */
762
0
    if (method_type == METHOD_IS_OTHER) {
763
0
        close_file(generator);
764
0
        send_method_not_allowed(req);
765
0
        return 0;
766
0
    }
767
768
    /* range request */
769
0
    if ((range_header_index = h2o_find_header(&req->headers, H2O_TOKEN_RANGE, -1)) != -1) {
770
        /* if range */
771
0
        if ((if_range_header_index = h2o_find_header(&req->headers, H2O_TOKEN_IF_RANGE, -1)) != -1) {
772
0
            h2o_iovec_t *if_range = &req->headers.entries[if_range_header_index].value;
773
            /* first try parse if-range as http-date */
774
0
            struct tm ir_tm, *last_modified_tm;
775
0
            if (h2o_time_parse_rfc1123(if_range->base, if_range->len, &ir_tm) == 0) {
776
0
                last_modified_tm = h2o_filecache_get_last_modified(generator->file.ref, NULL);
777
0
                if (tm_is_lessthan(&ir_tm, last_modified_tm))
778
0
                    goto EntireFile;
779
0
            } else { /* treat it as an e-tag */
780
0
                char etag[H2O_FILECACHE_ETAG_MAXLEN + 1];
781
0
                size_t etag_len = h2o_filecache_get_etag(generator->file.ref, etag);
782
0
                if (!h2o_filecache_compare_etag_strong(if_range->base, if_range->len, etag, etag_len))
783
0
                    goto EntireFile;
784
0
            }
785
0
        }
786
0
        h2o_iovec_t *range = &req->headers.entries[range_header_index].value;
787
0
        size_t *range_infos, range_count;
788
0
        range_infos = process_range(&req->pool, range, generator->bytesleft, &range_count);
789
0
        if (range_infos == NULL) {
790
0
            h2o_iovec_t content_range;
791
0
            content_range.base = h2o_mem_alloc_pool(&req->pool, char, 32);
792
0
            content_range.len = sprintf(content_range.base, "bytes */%zu", generator->bytesleft);
793
0
            h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_RANGE, NULL, content_range.base, content_range.len);
794
0
            h2o_send_error_416(req, "Request Range Not Satisfiable", "requested range not satisfiable",
795
0
                               H2O_SEND_ERROR_KEEP_HEADERS);
796
0
            goto Close;
797
0
        }
798
0
        generator->ranged.range_count = range_count;
799
0
        generator->ranged.range_infos = range_infos;
800
0
        generator->ranged.current_range = 0;
801
0
        generator->ranged.filesize = generator->bytesleft;
802
803
        /* set content-length according to range */
804
0
        if (range_count == 1)
805
0
            generator->bytesleft = range_infos[1];
806
0
        else {
807
0
            generator->ranged.mimetype = h2o_strdup(&req->pool, mime_type->data.mimetype.base, mime_type->data.mimetype.len);
808
0
            size_t final_content_len = 0, size_tmp = 0, size_fixed_each_part, i;
809
0
            generator->ranged.boundary.base = h2o_mem_alloc_pool(&req->pool, char, BOUNDARY_SIZE + 1);
810
0
            generator->ranged.boundary.len = BOUNDARY_SIZE;
811
0
            gen_rand_string(&generator->ranged.boundary);
812
0
            i = generator->bytesleft;
813
0
            while (i) {
814
0
                i /= 10;
815
0
                size_tmp++;
816
0
            }
817
0
            size_fixed_each_part = FIXED_PART_SIZE + mime_type->data.mimetype.len + size_tmp;
818
0
            for (i = 0; i < range_count; i++) {
819
0
                size_tmp = *range_infos++;
820
0
                if (size_tmp == 0)
821
0
                    final_content_len++;
822
0
                while (size_tmp) {
823
0
                    size_tmp /= 10;
824
0
                    final_content_len++;
825
0
                }
826
827
0
                size_tmp = *(range_infos - 1);
828
0
                final_content_len += *range_infos;
829
830
0
                size_tmp += *range_infos++ - 1;
831
0
                if (size_tmp == 0)
832
0
                    final_content_len++;
833
0
                while (size_tmp) {
834
0
                    size_tmp /= 10;
835
0
                    final_content_len++;
836
0
                }
837
0
            }
838
0
            final_content_len += sizeof("\r\n--") - 1 + BOUNDARY_SIZE + sizeof("--\r\n") - 1 + size_fixed_each_part * range_count -
839
0
                                 (sizeof("\r\n") - 1);
840
0
            generator->bytesleft = final_content_len;
841
0
        }
842
0
        do_send_file(generator, req, 206, "Partial Content", mime_type->data.mimetype, &h2o_mime_attributes_as_is,
843
0
                     method_type == METHOD_IS_GET);
844
0
        return 0;
845
0
    }
846
847
0
EntireFile:
848
    /* return file */
849
0
    do_send_file(generator, req, 200, "OK", mime_type->data.mimetype, &mime_type->data.attr, method_type == METHOD_IS_GET);
850
0
    return 0;
851
852
0
NotModified:
853
0
    req->res.status = 304;
854
0
    req->res.reason = "Not Modified";
855
0
    add_headers_unconditional(generator, req);
856
0
    h2o_send_inline(req, NULL, 0);
857
0
Close:
858
0
    close_file(generator);
859
0
    return 0;
860
0
}
861
862
static int on_req(h2o_handler_t *_self, h2o_req_t *req)
863
10.7k
{
864
10.7k
    h2o_file_handler_t *self = (void *)_self;
865
10.7k
    char *rpath;
866
10.7k
    size_t rpath_len, req_path_prefix;
867
10.7k
    struct st_h2o_sendfile_generator_t *generator = NULL;
868
10.7k
    int is_dir;
869
870
    /* path including a NULL character never matches a file system (because NULL cannot be used on the file system) */
871
10.7k
    if (req->path_normalized_has_null_char)
872
49
        return -1;
873
874
10.6k
    if (req->path_normalized.len < self->conf_path.len) {
875
0
        h2o_iovec_t dest = h2o_uri_escape(&req->pool, self->conf_path.base, self->conf_path.len, "/");
876
0
        if (req->query_at != SIZE_MAX)
877
0
            dest = h2o_concat(&req->pool, dest, h2o_iovec_init(req->path.base + req->query_at, req->path.len - req->query_at));
878
0
        h2o_send_redirect(req, 301, "Moved Permanently", dest.base, dest.len);
879
0
        return 0;
880
0
    }
881
882
    /* build path (still unterminated at the end of the block) */
883
10.6k
    req_path_prefix = self->conf_path.len;
884
10.6k
    rpath = alloca(self->real_path.len + (req->path_normalized.len - req_path_prefix) + self->max_index_file_len + 1);
885
10.6k
    rpath_len = 0;
886
10.6k
    memcpy(rpath + rpath_len, self->real_path.base, self->real_path.len);
887
10.6k
    rpath_len += self->real_path.len;
888
10.6k
    memcpy(rpath + rpath_len, req->path_normalized.base + req_path_prefix, req->path_normalized.len - req_path_prefix);
889
10.6k
    rpath_len += req->path_normalized.len - req_path_prefix;
890
891
10.6k
    h2o_resp_add_date_header(req);
892
893
10.6k
    h2o_iovec_t resolved_path = req->path_normalized;
894
895
    /* build generator (as well as terminating the rpath and its length upon success) */
896
10.6k
    if (rpath[rpath_len - 1] == '/') {
897
2.58k
        h2o_iovec_t *index_file;
898
10.3k
        for (index_file = self->index_files; index_file->base != NULL; ++index_file) {
899
7.73k
            memcpy(rpath + rpath_len, index_file->base, index_file->len);
900
7.73k
            rpath[rpath_len + index_file->len] = '\0';
901
7.73k
            if ((generator = create_generator(req, rpath, rpath_len + index_file->len, &is_dir, self->flags)) != NULL) {
902
0
                rpath_len += index_file->len;
903
0
                resolved_path = h2o_concat(&req->pool, req->path_normalized, *index_file);
904
0
                goto Opened;
905
0
            }
906
7.73k
            if (is_dir) {
907
                /* note: apache redirects "path/" to "path/index.txt/" if index.txt is a dir */
908
0
                h2o_iovec_t dest = h2o_concat(&req->pool, req->path_normalized, *index_file, h2o_iovec_init(H2O_STRLIT("/")));
909
0
                dest = h2o_uri_escape(&req->pool, dest.base, dest.len, "/");
910
0
                if (req->query_at != SIZE_MAX)
911
0
                    dest =
912
0
                        h2o_concat(&req->pool, dest, h2o_iovec_init(req->path.base + req->query_at, req->path.len - req->query_at));
913
0
                h2o_send_redirect(req, 301, "Moved Permantently", dest.base, dest.len);
914
0
                return 0;
915
0
            }
916
7.73k
            if (errno != ENOENT)
917
9
                break;
918
7.73k
        }
919
2.58k
        if (index_file->base == NULL && (self->flags & H2O_FILE_FLAG_DIR_LISTING) != 0) {
920
0
            rpath[rpath_len] = '\0';
921
0
            int is_get = 0;
922
0
            if (h2o_memis(req->method.base, req->method.len, H2O_STRLIT("GET"))) {
923
0
                is_get = 1;
924
0
            } else if (h2o_memis(req->method.base, req->method.len, H2O_STRLIT("HEAD"))) {
925
                /* ok */
926
0
            } else {
927
0
                send_method_not_allowed(req);
928
0
                return 0;
929
0
            }
930
0
            if (send_dir_listing(req, rpath, rpath_len, is_get) == 0)
931
0
                return 0;
932
0
        }
933
8.10k
    } else {
934
8.10k
        rpath[rpath_len] = '\0';
935
8.10k
        if ((generator = create_generator(req, rpath, rpath_len, &is_dir, self->flags)) != NULL)
936
0
            goto Opened;
937
8.10k
        if (is_dir) {
938
0
            h2o_iovec_t dest = h2o_concat(&req->pool, req->path_normalized, h2o_iovec_init(H2O_STRLIT("/")));
939
0
            dest = h2o_uri_escape(&req->pool, dest.base, dest.len, "/");
940
0
            if (req->query_at != SIZE_MAX)
941
0
                dest = h2o_concat(&req->pool, dest, h2o_iovec_init(req->path.base + req->query_at, req->path.len - req->query_at));
942
0
            h2o_send_redirect(req, 301, "Moved Permanently", dest.base, dest.len);
943
0
            return 0;
944
0
        }
945
8.10k
    }
946
    /* failed to open */
947
948
10.6k
    if (errno == ENFILE || errno == EMFILE) {
949
0
        h2o_send_error_503(req, "Service Unavailable", "please try again later", 0);
950
10.6k
    } else {
951
10.6k
        if (h2o_mimemap_has_dynamic_type(self->mimemap) && try_dynamic_request(self, req, rpath, rpath_len) == 0)
952
0
            return 0;
953
10.6k
        if (errno == ENOENT || errno == ENOTDIR) {
954
10.6k
            return -1;
955
10.6k
        } else {
956
38
            h2o_send_error_403(req, "Access Forbidden", "access forbidden", 0);
957
38
        }
958
10.6k
    }
959
38
    return 0;
960
961
0
Opened:
962
0
    return serve_with_generator(generator, req, resolved_path, rpath, rpath_len,
963
0
                                h2o_mimemap_get_type_by_extension(self->mimemap, h2o_get_filext(rpath, rpath_len)));
964
10.6k
}
965
966
static void on_context_init(h2o_handler_t *_self, h2o_context_t *ctx)
967
3
{
968
3
    h2o_file_handler_t *self = (void *)_self;
969
970
3
    h2o_mimemap_on_context_init(self->mimemap, ctx);
971
3
}
972
973
static void on_context_dispose(h2o_handler_t *_self, h2o_context_t *ctx)
974
0
{
975
0
    h2o_file_handler_t *self = (void *)_self;
976
977
0
    h2o_mimemap_on_context_dispose(self->mimemap, ctx);
978
0
}
979
980
static void on_handler_dispose(h2o_handler_t *_self)
981
0
{
982
0
    h2o_file_handler_t *self = (void *)_self;
983
0
    size_t i;
984
985
0
    free(self->conf_path.base);
986
0
    free(self->real_path.base);
987
0
    h2o_mem_release_shared(self->mimemap);
988
0
    for (i = 0; self->index_files[i].base != NULL; ++i)
989
0
        free(self->index_files[i].base);
990
0
}
991
992
h2o_file_handler_t *h2o_file_register(h2o_pathconf_t *pathconf, const char *real_path, const char **index_files,
993
                                      h2o_mimemap_t *mimemap, int flags)
994
3
{
995
3
    h2o_file_handler_t *self;
996
3
    size_t i;
997
998
3
    if (index_files == NULL)
999
3
        index_files = default_index_files;
1000
1001
    /* allocate memory */
1002
12
    for (i = 0; index_files[i] != NULL; ++i)
1003
9
        ;
1004
3
    self =
1005
3
        (void *)h2o_create_handler(pathconf, offsetof(h2o_file_handler_t, index_files[0]) + sizeof(self->index_files[0]) * (i + 1));
1006
1007
    /* setup callbacks */
1008
3
    self->super.on_context_init = on_context_init;
1009
3
    self->super.on_context_dispose = on_context_dispose;
1010
3
    self->super.dispose = on_handler_dispose;
1011
3
    self->super.on_req = on_req;
1012
1013
    /* setup attributes */
1014
3
    self->conf_path = h2o_strdup_slashed(NULL, pathconf->path.base, pathconf->path.len);
1015
3
    self->real_path = h2o_strdup_slashed(NULL, real_path, SIZE_MAX);
1016
3
    if (mimemap != NULL) {
1017
0
        h2o_mem_addref_shared(mimemap);
1018
0
        self->mimemap = mimemap;
1019
3
    } else {
1020
3
        self->mimemap = h2o_mimemap_create();
1021
3
    }
1022
3
    self->flags = flags;
1023
12
    for (i = 0; index_files[i] != NULL; ++i) {
1024
9
        self->index_files[i] = h2o_strdup(NULL, index_files[i], SIZE_MAX);
1025
9
        if (self->max_index_file_len < self->index_files[i].len)
1026
3
            self->max_index_file_len = self->index_files[i].len;
1027
9
    }
1028
1029
3
    return self;
1030
3
}
1031
1032
h2o_mimemap_t *h2o_file_get_mimemap(h2o_file_handler_t *handler)
1033
0
{
1034
0
    return handler->mimemap;
1035
0
}
1036
1037
static void specific_handler_on_context_init(h2o_handler_t *_self, h2o_context_t *ctx)
1038
0
{
1039
0
    struct st_h2o_specific_file_handler_t *self = (void *)_self;
1040
1041
0
    if (self->mime_type->type == H2O_MIMEMAP_TYPE_DYNAMIC)
1042
0
        h2o_context_init_pathconf_context(ctx, &self->mime_type->data.dynamic.pathconf);
1043
0
}
1044
1045
static void specific_handler_on_context_dispose(h2o_handler_t *_self, h2o_context_t *ctx)
1046
0
{
1047
0
    struct st_h2o_specific_file_handler_t *self = (void *)_self;
1048
1049
0
    if (self->mime_type->type == H2O_MIMEMAP_TYPE_DYNAMIC)
1050
0
        h2o_context_dispose_pathconf_context(ctx, &self->mime_type->data.dynamic.pathconf);
1051
0
}
1052
1053
static void specific_handler_on_dispose(h2o_handler_t *_self)
1054
0
{
1055
0
    struct st_h2o_specific_file_handler_t *self = (void *)_self;
1056
1057
0
    free(self->real_path.base);
1058
0
    h2o_mem_release_shared(self->mime_type);
1059
0
}
1060
1061
static int specific_handler_on_req(h2o_handler_t *_self, h2o_req_t *req)
1062
0
{
1063
0
    struct st_h2o_specific_file_handler_t *self = (void *)_self;
1064
0
    struct st_h2o_sendfile_generator_t *generator;
1065
0
    int is_dir;
1066
1067
    /* open file (or send error or return -1) */
1068
0
    if ((generator = create_generator(req, self->real_path.base, self->real_path.len, &is_dir, self->flags)) == NULL) {
1069
0
        if (is_dir) {
1070
0
            h2o_send_error_403(req, "Access Forbidden", "access forbidden", 0);
1071
0
        } else if (errno == ENOENT) {
1072
0
            return -1;
1073
0
        } else if (errno == ENFILE || errno == EMFILE) {
1074
0
            h2o_send_error_503(req, "Service Unavailable", "please try again later", 0);
1075
0
        } else {
1076
0
            h2o_send_error_403(req, "Access Forbidden", "access forbidden", 0);
1077
0
        }
1078
0
        return 0;
1079
0
    }
1080
1081
0
    return serve_with_generator(generator, req, req->path_normalized, self->real_path.base, self->real_path.len, self->mime_type);
1082
0
}
1083
1084
h2o_handler_t *h2o_file_register_file(h2o_pathconf_t *pathconf, const char *real_path, h2o_mimemap_type_t *mime_type, int flags)
1085
0
{
1086
0
    struct st_h2o_specific_file_handler_t *self = (void *)h2o_create_handler(pathconf, sizeof(*self));
1087
1088
0
    self->super.on_context_init = specific_handler_on_context_init;
1089
0
    self->super.on_context_dispose = specific_handler_on_context_dispose;
1090
0
    self->super.dispose = specific_handler_on_dispose;
1091
0
    self->super.on_req = specific_handler_on_req;
1092
1093
0
    self->real_path = h2o_strdup(NULL, real_path, SIZE_MAX);
1094
0
    h2o_mem_addref_shared(mime_type);
1095
0
    self->mime_type = mime_type;
1096
0
    self->flags = flags;
1097
1098
0
    return &self->super;
1099
0
}