Coverage Report

Created: 2025-08-28 06:50

/src/unit/src/nxt_http_static.c
Line
Count
Source (jump to first uncovered line)
1
2
/*
3
 * Copyright (C) NGINX, Inc.
4
 */
5
6
#include <nxt_router.h>
7
#include <nxt_http.h>
8
#include <nxt_http_compression.h>
9
10
11
typedef struct {
12
    nxt_tstr_t                  *tstr;
13
#if (NXT_HAVE_OPENAT2)
14
    u_char                      *fname;
15
#endif
16
    uint8_t                     is_const;  /* 1 bit */
17
} nxt_http_static_share_t;
18
19
20
typedef struct {
21
    nxt_uint_t                  nshares;
22
    nxt_http_static_share_t     *shares;
23
    nxt_str_t                   index;
24
#if (NXT_HAVE_OPENAT2)
25
    nxt_tstr_t                  *chroot;
26
    nxt_uint_t                  resolve;
27
#endif
28
    nxt_http_route_rule_t       *types;
29
} nxt_http_static_conf_t;
30
31
32
typedef struct {
33
    nxt_http_action_t           *action;
34
    nxt_str_t                   share;
35
#if (NXT_HAVE_OPENAT2)
36
    nxt_str_t                   chroot;
37
#endif
38
    uint32_t                    share_idx;
39
    uint8_t                     need_body;  /* 1 bit */
40
} nxt_http_static_ctx_t;
41
42
43
0
#define NXT_HTTP_STATIC_BUF_COUNT  2
44
0
#define NXT_HTTP_STATIC_BUF_SIZE   (128 * 1024)
45
46
47
static nxt_http_action_t *nxt_http_static(nxt_task_t *task,
48
    nxt_http_request_t *r, nxt_http_action_t *action);
49
static void nxt_http_static_iterate(nxt_task_t *task, nxt_http_request_t *r,
50
    nxt_http_static_ctx_t *ctx);
51
static void nxt_http_static_send(nxt_task_t *task, nxt_http_request_t *r,
52
    nxt_http_static_ctx_t *ctx);
53
static void nxt_http_static_next(nxt_task_t *task, nxt_http_request_t *r,
54
    nxt_http_static_ctx_t *ctx, nxt_http_status_t status);
55
#if (NXT_HAVE_OPENAT2)
56
static u_char *nxt_http_static_chroot_match(u_char *chr, u_char *shr);
57
#endif
58
static void nxt_http_static_extract_extension(nxt_str_t *path,
59
    nxt_str_t *exten);
60
static void nxt_http_static_body_handler(nxt_task_t *task, void *obj,
61
    void *data);
62
static void nxt_http_static_buf_completion(nxt_task_t *task, void *obj,
63
    void *data);
64
65
static nxt_int_t nxt_http_static_mtypes_hash_test(nxt_lvlhsh_query_t *lhq,
66
    void *data);
67
static void *nxt_http_static_mtypes_hash_alloc(void *data, size_t size);
68
static void nxt_http_static_mtypes_hash_free(void *data, void *p);
69
70
71
static const nxt_http_request_state_t  nxt_http_static_send_state;
72
73
74
nxt_int_t
75
nxt_http_static_init(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
76
    nxt_http_action_t *action, nxt_http_action_conf_t *acf)
77
0
{
78
0
    uint32_t                i;
79
0
    nxt_mp_t                *mp;
80
0
    nxt_str_t               str, *ret;
81
0
    nxt_tstr_t              *tstr;
82
0
    nxt_conf_value_t        *cv;
83
0
    nxt_router_conf_t       *rtcf;
84
0
    nxt_http_static_conf_t  *conf;
85
86
0
    rtcf = tmcf->router_conf;
87
0
    mp = rtcf->mem_pool;
88
89
0
    conf = nxt_mp_zget(mp, sizeof(nxt_http_static_conf_t));
90
0
    if (nxt_slow_path(conf == NULL)) {
91
0
        return NXT_ERROR;
92
0
    }
93
94
0
    action->handler = nxt_http_static;
95
0
    action->u.conf = conf;
96
97
0
    conf->nshares = nxt_conf_array_elements_count_or_1(acf->share);
98
0
    conf->shares = nxt_mp_zget(mp, sizeof(nxt_http_static_share_t)
99
0
                                   * conf->nshares);
100
0
    if (nxt_slow_path(conf->shares == NULL)) {
101
0
        return NXT_ERROR;
102
0
    }
103
104
0
    for (i = 0; i < conf->nshares; i++) {
105
0
        cv = nxt_conf_get_array_element_or_itself(acf->share, i);
106
0
        nxt_conf_get_string(cv, &str);
107
108
0
        tstr = nxt_tstr_compile(rtcf->tstr_state, &str, NXT_TSTR_STRZ);
109
0
        if (nxt_slow_path(tstr == NULL)) {
110
0
            return NXT_ERROR;
111
0
        }
112
113
0
        conf->shares[i].tstr = tstr;
114
0
        conf->shares[i].is_const = nxt_tstr_is_const(tstr);
115
0
    }
116
117
0
    if (acf->index == NULL) {
118
0
        nxt_str_set(&conf->index, "index.html");
119
120
0
    } else {
121
0
        ret = nxt_conf_get_string_dup(acf->index, mp, &conf->index);
122
0
        if (nxt_slow_path(ret == NULL)) {
123
0
            return NXT_ERROR;
124
0
        }
125
0
    }
126
127
#if (NXT_HAVE_OPENAT2)
128
    if (acf->chroot.length > 0) {
129
        nxt_str_t   chr, shr;
130
        nxt_bool_t  is_const;
131
132
        conf->chroot = nxt_tstr_compile(rtcf->tstr_state, &acf->chroot,
133
                                        NXT_TSTR_STRZ);
134
        if (nxt_slow_path(conf->chroot == NULL)) {
135
            return NXT_ERROR;
136
        }
137
138
        is_const = nxt_tstr_is_const(conf->chroot);
139
140
        for (i = 0; i < conf->nshares; i++) {
141
            conf->shares[i].is_const &= is_const;
142
143
            if (conf->shares[i].is_const) {
144
                nxt_tstr_str(conf->chroot, &chr);
145
                nxt_tstr_str(conf->shares[i].tstr, &shr);
146
147
                conf->shares[i].fname = nxt_http_static_chroot_match(chr.start,
148
                                                                     shr.start);
149
            }
150
        }
151
    }
152
153
    if (acf->follow_symlinks != NULL
154
        && !nxt_conf_get_boolean(acf->follow_symlinks))
155
    {
156
        conf->resolve |= RESOLVE_NO_SYMLINKS;
157
    }
158
159
    if (acf->traverse_mounts != NULL
160
        && !nxt_conf_get_boolean(acf->traverse_mounts))
161
    {
162
        conf->resolve |= RESOLVE_NO_XDEV;
163
    }
164
#endif
165
166
0
    if (acf->types != NULL) {
167
0
        conf->types = nxt_http_route_types_rule_create(task, mp, acf->types);
168
0
        if (nxt_slow_path(conf->types == NULL)) {
169
0
            return NXT_ERROR;
170
0
        }
171
0
    }
172
173
0
    if (acf->fallback != NULL) {
174
0
        action->fallback = nxt_mp_alloc(mp, sizeof(nxt_http_action_t));
175
0
        if (nxt_slow_path(action->fallback == NULL)) {
176
0
            return NXT_ERROR;
177
0
        }
178
179
0
        return nxt_http_action_init(task, tmcf, acf->fallback,
180
0
                                    action->fallback);
181
0
    }
182
183
0
    return NXT_OK;
184
0
}
185
186
187
static nxt_http_action_t *
188
nxt_http_static(nxt_task_t *task, nxt_http_request_t *r,
189
    nxt_http_action_t *action)
190
0
{
191
0
    nxt_bool_t             need_body;
192
0
    nxt_http_static_ctx_t  *ctx;
193
194
0
    if (nxt_slow_path(!nxt_str_eq(r->method, "GET", 3))) {
195
196
0
        if (!nxt_str_eq(r->method, "HEAD", 4)) {
197
0
            if (action->fallback != NULL) {
198
0
                if (nxt_slow_path(r->log_route)) {
199
0
                    nxt_log(task, NXT_LOG_NOTICE, "\"fallback\" taken");
200
0
                }
201
0
                return action->fallback;
202
0
            }
203
204
0
            nxt_http_request_error(task, r, NXT_HTTP_METHOD_NOT_ALLOWED);
205
0
            return NULL;
206
0
        }
207
208
0
        need_body = 0;
209
210
0
    } else {
211
0
        need_body = 1;
212
0
    }
213
214
0
    ctx = nxt_mp_zget(r->mem_pool, sizeof(nxt_http_static_ctx_t));
215
0
    if (nxt_slow_path(ctx == NULL)) {
216
0
        nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR);
217
0
        return NULL;
218
0
    }
219
220
0
    ctx->action = action;
221
0
    ctx->need_body = need_body;
222
223
0
    nxt_http_static_iterate(task, r, ctx);
224
225
0
    return NULL;
226
0
}
227
228
229
static void
230
nxt_http_static_iterate(nxt_task_t *task, nxt_http_request_t *r,
231
    nxt_http_static_ctx_t *ctx)
232
0
{
233
0
    nxt_int_t                ret;
234
0
    nxt_router_conf_t        *rtcf;
235
0
    nxt_http_static_conf_t   *conf;
236
0
    nxt_http_static_share_t  *share;
237
238
0
    conf = ctx->action->u.conf;
239
240
0
    share = &conf->shares[ctx->share_idx];
241
242
#if (NXT_DEBUG)
243
    nxt_str_t  shr;
244
    nxt_str_t  idx;
245
246
    nxt_tstr_str(share->tstr, &shr);
247
    idx = conf->index;
248
249
#if (NXT_HAVE_OPENAT2)
250
    nxt_str_t  chr;
251
252
    if (conf->chroot != NULL) {
253
        nxt_tstr_str(conf->chroot, &chr);
254
255
    } else {
256
        nxt_str_set(&chr, "");
257
    }
258
259
    nxt_debug(task, "http static: \"%V\", index: \"%V\" (chroot: \"%V\")",
260
              &shr, &idx, &chr);
261
#else
262
    nxt_debug(task, "http static: \"%V\", index: \"%V\"", &shr, &idx);
263
#endif
264
#endif /* NXT_DEBUG */
265
266
0
    if (share->is_const) {
267
0
        nxt_tstr_str(share->tstr, &ctx->share);
268
269
#if (NXT_HAVE_OPENAT2)
270
        if (conf->chroot != NULL && ctx->share_idx == 0) {
271
            nxt_tstr_str(conf->chroot, &ctx->chroot);
272
        }
273
#endif
274
275
0
    } else {
276
0
        rtcf = r->conf->socket_conf->router_conf;
277
278
0
        ret = nxt_tstr_query_init(&r->tstr_query, rtcf->tstr_state,
279
0
                                  &r->tstr_cache, r, r->mem_pool);
280
0
        if (nxt_slow_path(ret != NXT_OK)) {
281
0
            goto fail;
282
0
        }
283
284
0
        ret = nxt_tstr_query(task, r->tstr_query, share->tstr, &ctx->share);
285
0
        if (nxt_slow_path(ret != NXT_OK)) {
286
0
            goto fail;
287
0
        }
288
289
#if (NXT_HAVE_OPENAT2)
290
        if (conf->chroot != NULL && ctx->share_idx == 0) {
291
            ret = nxt_tstr_query(task, r->tstr_query, conf->chroot,
292
                                 &ctx->chroot);
293
            if (nxt_slow_path(ret != NXT_OK)) {
294
                goto fail;
295
            }
296
        }
297
#endif
298
0
    }
299
300
0
    nxt_http_static_send(task, r, ctx);
301
302
0
    return;
303
304
0
fail:
305
306
0
    nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR);
307
0
}
308
309
310
static void
311
nxt_http_static_send(nxt_task_t *task, nxt_http_request_t *r,
312
    nxt_http_static_ctx_t *ctx)
313
0
{
314
0
    size_t                  length, encode;
315
0
    u_char                  *p, *fname;
316
0
    struct tm               tm;
317
0
    nxt_buf_t               *fb;
318
0
    nxt_int_t               ret;
319
0
    nxt_str_t               *shr, *index, exten, *mtype;
320
0
    nxt_uint_t              level;
321
0
    nxt_file_t              *f, file;
322
0
    nxt_file_info_t         fi;
323
0
    nxt_http_field_t        *field;
324
0
    nxt_http_status_t       status;
325
0
    nxt_router_conf_t       *rtcf;
326
0
    nxt_http_action_t       *action;
327
0
    nxt_work_handler_t      body_handler;
328
0
    nxt_http_static_conf_t  *conf;
329
330
0
    action = ctx->action;
331
0
    conf = action->u.conf;
332
0
    rtcf = r->conf->socket_conf->router_conf;
333
334
0
    f = NULL;
335
0
    mtype = NULL;
336
337
0
    shr = &ctx->share;
338
0
    index = &conf->index;
339
340
0
    if (shr->start[shr->length - 1] == '/') {
341
0
        nxt_http_static_extract_extension(index, &exten);
342
343
0
        length = shr->length + index->length;
344
345
0
        fname = nxt_mp_nget(r->mem_pool, length + 1);
346
0
        if (nxt_slow_path(fname == NULL)) {
347
0
            goto fail;
348
0
        }
349
350
0
        p = fname;
351
0
        p = nxt_cpymem(p, shr->start, shr->length);
352
0
        p = nxt_cpymem(p, index->start, index->length);
353
0
        *p = '\0';
354
355
0
    } else {
356
0
        if (conf->types == NULL) {
357
0
            nxt_str_null(&exten);
358
359
0
        } else {
360
0
            nxt_http_static_extract_extension(shr, &exten);
361
0
            mtype = nxt_http_static_mtype_get(&rtcf->mtypes_hash, &exten);
362
363
0
            ret = nxt_http_route_test_rule(r, conf->types, mtype->start,
364
0
                                           mtype->length);
365
0
            if (nxt_slow_path(ret == NXT_ERROR)) {
366
0
                goto fail;
367
0
            }
368
369
0
            if (ret == 0) {
370
0
                nxt_http_static_next(task, r, ctx, NXT_HTTP_FORBIDDEN);
371
0
                return;
372
0
            }
373
0
        }
374
375
0
        fname = ctx->share.start;
376
0
    }
377
378
0
    nxt_memzero(&file, sizeof(nxt_file_t));
379
380
0
    file.name = fname;
381
382
#if (NXT_HAVE_OPENAT2)
383
    if (conf->resolve != 0 || ctx->chroot.length > 0) {
384
        nxt_str_t                *chr;
385
        nxt_uint_t               resolve;
386
        nxt_http_static_share_t  *share;
387
388
        share = &conf->shares[ctx->share_idx];
389
390
        resolve = conf->resolve;
391
        chr = &ctx->chroot;
392
393
        if (chr->length > 0) {
394
            resolve |= RESOLVE_IN_ROOT;
395
396
            fname = share->is_const
397
                    ? share->fname
398
                    : nxt_http_static_chroot_match(chr->start, file.name);
399
400
            if (fname != NULL) {
401
                file.name = chr->start;
402
                ret = nxt_file_open(task, &file, NXT_FILE_SEARCH, NXT_FILE_OPEN,
403
                                    0);
404
405
            } else {
406
                file.error = NXT_EACCES;
407
                ret = NXT_ERROR;
408
            }
409
410
        } else if (fname[0] == '/') {
411
            file.name = (u_char *) "/";
412
            ret = nxt_file_open(task, &file, NXT_FILE_SEARCH, NXT_FILE_OPEN, 0);
413
414
        } else {
415
            file.name = (u_char *) ".";
416
            file.fd = AT_FDCWD;
417
            ret = NXT_OK;
418
        }
419
420
        if (nxt_fast_path(ret == NXT_OK)) {
421
            nxt_file_t  af;
422
423
            af = file;
424
            nxt_memzero(&file, sizeof(nxt_file_t));
425
            file.name = fname;
426
427
            ret = nxt_file_openat2(task, &file, NXT_FILE_RDONLY,
428
                                   NXT_FILE_OPEN, 0, af.fd, resolve);
429
430
            if (af.fd != AT_FDCWD) {
431
                nxt_file_close(task, &af);
432
            }
433
        }
434
435
    } else {
436
        ret = nxt_file_open(task, &file, NXT_FILE_RDONLY, NXT_FILE_OPEN, 0);
437
    }
438
439
#else
440
0
    ret = nxt_file_open(task, &file, NXT_FILE_RDONLY, NXT_FILE_OPEN, 0);
441
0
#endif
442
443
0
    if (nxt_slow_path(ret != NXT_OK)) {
444
445
0
        switch (file.error) {
446
447
        /*
448
         * For Unix domain sockets "errno" is set to:
449
         *  - ENXIO on Linux;
450
         *  - EOPNOTSUPP on *BSD, MacOSX, and Solaris.
451
         */
452
453
0
        case NXT_ENOENT:
454
0
        case NXT_ENOTDIR:
455
0
        case NXT_ENAMETOOLONG:
456
0
#if (NXT_LINUX)
457
0
        case NXT_ENXIO:
458
#else
459
        case NXT_EOPNOTSUPP:
460
#endif
461
0
            level = NXT_LOG_ERR;
462
0
            status = NXT_HTTP_NOT_FOUND;
463
0
            break;
464
465
0
        case NXT_EACCES:
466
#if (NXT_HAVE_OPENAT2)
467
        case NXT_ELOOP:
468
        case NXT_EXDEV:
469
#endif
470
0
            level = NXT_LOG_ERR;
471
0
            status = NXT_HTTP_FORBIDDEN;
472
0
            break;
473
474
0
        default:
475
0
            level = NXT_LOG_ALERT;
476
0
            status = NXT_HTTP_INTERNAL_SERVER_ERROR;
477
0
            break;
478
0
        }
479
480
0
        if (status != NXT_HTTP_NOT_FOUND) {
481
#if (NXT_HAVE_OPENAT2)
482
            nxt_str_t  *chr = &ctx->chroot;
483
484
            if (chr->length > 0) {
485
                nxt_log(task, level, "opening \"%s\" at \"%V\" failed %E",
486
                        fname, chr, file.error);
487
488
            } else {
489
                nxt_log(task, level, "opening \"%s\" failed %E",
490
                        fname, file.error);
491
            }
492
493
#else
494
0
            nxt_log(task, level, "opening \"%s\" failed %E", fname, file.error);
495
0
#endif
496
0
        }
497
498
0
        if (level == NXT_LOG_ERR) {
499
0
            nxt_http_static_next(task, r, ctx, status);
500
0
            return;
501
0
        }
502
503
0
        goto fail;
504
0
    }
505
506
0
    f = nxt_mp_get(r->mem_pool, sizeof(nxt_file_t));
507
0
    if (nxt_slow_path(f == NULL)) {
508
0
        nxt_file_close(task, &file);
509
0
        goto fail;
510
0
    }
511
512
0
    *f = file;
513
514
0
    ret = nxt_file_info(f, &fi);
515
0
    if (nxt_slow_path(ret != NXT_OK)) {
516
0
        goto fail;
517
0
    }
518
519
0
    if (nxt_fast_path(nxt_is_file(&fi))) {
520
0
        r->status = NXT_HTTP_OK;
521
0
        r->resp.content_length_n = nxt_file_size(&fi);
522
523
0
        field = nxt_list_zero_add(r->resp.fields);
524
0
        if (nxt_slow_path(field == NULL)) {
525
0
            goto fail;
526
0
        }
527
528
0
        nxt_http_field_name_set(field, "Last-Modified");
529
530
0
        p = nxt_mp_nget(r->mem_pool, NXT_HTTP_DATE_LEN);
531
0
        if (nxt_slow_path(p == NULL)) {
532
0
            goto fail;
533
0
        }
534
535
0
        nxt_localtime(nxt_file_mtime(&fi), &tm);
536
537
0
        field->value = p;
538
0
        field->value_length = nxt_http_date(p, &tm) - p;
539
540
0
        field = nxt_list_zero_add(r->resp.fields);
541
0
        if (nxt_slow_path(field == NULL)) {
542
0
            goto fail;
543
0
        }
544
545
0
        nxt_http_field_name_set(field, "ETag");
546
547
0
        length = NXT_TIME_T_HEXLEN + NXT_OFF_T_HEXLEN + 3;
548
549
0
        p = nxt_mp_nget(r->mem_pool, length);
550
0
        if (nxt_slow_path(p == NULL)) {
551
0
            goto fail;
552
0
        }
553
554
0
        field->value = p;
555
0
        field->value_length = nxt_sprintf(p, p + length, "\"%xT-%xO\"",
556
0
                                          nxt_file_mtime(&fi),
557
0
                                          nxt_file_size(&fi))
558
0
                              - p;
559
560
0
        if (exten.start == NULL) {
561
0
            nxt_http_static_extract_extension(shr, &exten);
562
0
        }
563
564
0
        if (mtype == NULL) {
565
0
            mtype = nxt_http_static_mtype_get(&rtcf->mtypes_hash, &exten);
566
0
        }
567
568
0
        if (mtype->length != 0) {
569
0
            field = nxt_list_zero_add(r->resp.fields);
570
0
            if (nxt_slow_path(field == NULL)) {
571
0
                goto fail;
572
0
            }
573
574
0
            nxt_http_field_name_set(field, "Content-Type");
575
576
0
            field->value = mtype->start;
577
0
            field->value_length = mtype->length;
578
0
        }
579
580
0
        r->resp.mime_type = mtype;
581
582
0
        if (ctx->need_body && nxt_file_size(&fi) > 0) {
583
0
            ret = nxt_http_comp_check_compression(task, r);
584
0
            if (ret == NXT_HTTP_NOT_ACCEPTABLE) {
585
0
                nxt_http_request_error(task, r, NXT_HTTP_NOT_ACCEPTABLE);
586
0
                return;
587
0
            } else if (ret != NXT_OK) {
588
0
                goto fail;
589
0
            }
590
591
0
            if (nxt_http_comp_wants_compression()) {
592
0
                size_t     out_total;
593
0
                nxt_int_t  ret;
594
595
0
                ret = nxt_http_comp_compress_static_response(
596
0
                                                    task, r, &f, &fi,
597
0
                                                    NXT_HTTP_STATIC_BUF_SIZE,
598
0
                                                    &out_total);
599
0
                if (ret == NXT_ERROR) {
600
0
                    goto fail;
601
0
                }
602
603
0
                ret = nxt_file_info(f, &fi);
604
0
                if (nxt_slow_path(ret != NXT_OK)) {
605
0
                    goto fail;
606
0
                }
607
608
0
                r->resp.content_length_n = out_total;
609
0
            }
610
611
0
            fb = nxt_mp_zget(r->mem_pool, NXT_BUF_FILE_SIZE);
612
0
            if (nxt_slow_path(fb == NULL)) {
613
0
                goto fail;
614
0
            }
615
616
0
            fb->file = f;
617
0
            fb->file_end = nxt_file_size(&fi);
618
619
0
            r->out = fb;
620
621
0
            body_handler = &nxt_http_static_body_handler;
622
623
0
        } else {
624
0
            nxt_file_close(task, f);
625
0
            body_handler = NULL;
626
0
        }
627
628
0
    } else {
629
        /* Not a file. */
630
0
        nxt_file_close(task, f);
631
632
0
        if (nxt_slow_path(!nxt_is_dir(&fi)
633
0
                          || shr->start[shr->length - 1] == '/'))
634
0
        {
635
0
            nxt_log(task, NXT_LOG_ERR, "\"%FN\" is not a regular file",
636
0
                    f->name);
637
638
0
            nxt_http_static_next(task, r, ctx, NXT_HTTP_NOT_FOUND);
639
0
            return;
640
0
        }
641
642
0
        f = NULL;
643
644
0
        r->status = NXT_HTTP_MOVED_PERMANENTLY;
645
0
        r->resp.content_length_n = 0;
646
647
0
        field = nxt_list_zero_add(r->resp.fields);
648
0
        if (nxt_slow_path(field == NULL)) {
649
0
            goto fail;
650
0
        }
651
652
0
        nxt_http_field_name_set(field, "Location");
653
654
0
        encode = nxt_encode_uri(NULL, r->path->start, r->path->length);
655
0
        length = r->path->length + encode * 2 + 1;
656
657
0
        if (r->args->length > 0) {
658
0
            length += 1 + r->args->length;
659
0
        }
660
661
0
        p = nxt_mp_nget(r->mem_pool, length);
662
0
        if (nxt_slow_path(p == NULL)) {
663
0
            goto fail;
664
0
        }
665
666
0
        field->value = p;
667
0
        field->value_length = length;
668
669
0
        if (encode > 0) {
670
0
            p = (u_char *) nxt_encode_uri(p, r->path->start, r->path->length);
671
672
0
        } else {
673
0
            p = nxt_cpymem(p, r->path->start, r->path->length);
674
0
        }
675
676
0
        *p++ = '/';
677
678
0
        if (r->args->length > 0) {
679
0
            *p++ = '?';
680
0
            nxt_memcpy(p, r->args->start, r->args->length);
681
0
        }
682
683
0
        body_handler = NULL;
684
0
    }
685
686
0
    nxt_http_request_header_send(task, r, body_handler, NULL);
687
688
0
    r->state = &nxt_http_static_send_state;
689
0
    return;
690
691
0
fail:
692
693
0
    if (f != NULL) {
694
0
        nxt_file_close(task, f);
695
0
    }
696
697
0
    nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR);
698
0
}
699
700
701
static void
702
nxt_http_static_next(nxt_task_t *task, nxt_http_request_t *r,
703
    nxt_http_static_ctx_t *ctx, nxt_http_status_t status)
704
0
{
705
0
    nxt_http_action_t       *action;
706
0
    nxt_http_static_conf_t  *conf;
707
708
0
    action = ctx->action;
709
0
    conf = action->u.conf;
710
711
0
    ctx->share_idx++;
712
713
0
    if (ctx->share_idx < conf->nshares) {
714
0
        nxt_http_static_iterate(task, r, ctx);
715
0
        return;
716
0
    }
717
718
0
    if (action->fallback != NULL) {
719
0
        if (nxt_slow_path(r->log_route)) {
720
0
            nxt_log(task, NXT_LOG_NOTICE, "\"fallback\" taken");
721
0
        }
722
723
0
        r->action = action->fallback;
724
0
        nxt_http_request_action(task, r, action->fallback);
725
0
        return;
726
0
    }
727
728
0
    nxt_http_request_error(task, r, status);
729
0
}
730
731
732
#if (NXT_HAVE_OPENAT2)
733
734
static u_char *
735
nxt_http_static_chroot_match(u_char *chr, u_char *shr)
736
{
737
    if (*chr != *shr) {
738
        return NULL;
739
    }
740
741
    chr++;
742
    shr++;
743
744
    for ( ;; ) {
745
        if (*shr == '\0') {
746
            return NULL;
747
        }
748
749
        if (*chr == *shr) {
750
            chr++;
751
            shr++;
752
            continue;
753
        }
754
755
        if (*chr == '\0') {
756
            break;
757
        }
758
759
        if (*chr == '/') {
760
            if (chr[-1] == '/') {
761
                chr++;
762
                continue;
763
            }
764
765
        } else if (*shr == '/') {
766
            if (shr[-1] == '/') {
767
                shr++;
768
                continue;
769
            }
770
        }
771
772
        return NULL;
773
    }
774
775
    if (shr[-1] != '/' && *shr != '/') {
776
        return NULL;
777
    }
778
779
    while (*shr == '/') {
780
        shr++;
781
    }
782
783
    return (*shr != '\0') ? shr : NULL;
784
}
785
786
#endif
787
788
789
static void
790
nxt_http_static_extract_extension(nxt_str_t *path, nxt_str_t *exten)
791
0
{
792
0
    u_char  ch, *p, *end;
793
794
0
    end = path->start + path->length;
795
0
    p = end;
796
797
0
    while (p > path->start) {
798
0
        p--;
799
0
        ch = *p;
800
801
0
        switch (ch) {
802
0
        case '/':
803
0
            p++;
804
            /* Fall through. */
805
0
        case '.':
806
0
            goto extension;
807
0
        }
808
0
    }
809
810
0
extension:
811
812
0
    exten->length = end - p;
813
0
    exten->start = p;
814
0
}
815
816
817
static void
818
nxt_http_static_body_handler(nxt_task_t *task, void *obj, void *data)
819
0
{
820
0
    size_t              alloc;
821
0
    nxt_buf_t           *fb, *b, **next, *out;
822
0
    nxt_off_t           rest;
823
0
    nxt_int_t           n;
824
0
    nxt_work_queue_t    *wq;
825
0
    nxt_http_request_t  *r;
826
827
0
    r = obj;
828
0
    fb = r->out;
829
830
0
    rest = fb->file_end - fb->file_pos;
831
0
    out = NULL;
832
0
    next = &out;
833
0
    n = 0;
834
835
0
    do {
836
0
        alloc = nxt_min(rest, NXT_HTTP_STATIC_BUF_SIZE);
837
838
0
        b = nxt_buf_mem_alloc(r->mem_pool, alloc, 0);
839
0
        if (nxt_slow_path(b == NULL)) {
840
0
            goto fail;
841
0
        }
842
843
0
        b->completion_handler = nxt_http_static_buf_completion;
844
0
        b->parent = r;
845
846
0
        nxt_mp_retain(r->mem_pool);
847
848
0
        *next = b;
849
0
        next = &b->next;
850
851
0
        rest -= alloc;
852
853
0
    } while (rest > 0 && ++n < NXT_HTTP_STATIC_BUF_COUNT);
854
855
0
    wq = &task->thread->engine->fast_work_queue;
856
857
0
    nxt_sendbuf_drain(task, wq, out);
858
0
    return;
859
860
0
fail:
861
862
0
    while (out != NULL) {
863
0
        b = out;
864
0
        out = b->next;
865
866
0
        nxt_mp_free(r->mem_pool, b);
867
0
        nxt_mp_release(r->mem_pool);
868
0
    }
869
0
}
870
871
872
static const nxt_http_request_state_t  nxt_http_static_send_state
873
    nxt_aligned(64) =
874
{
875
    .error_handler = nxt_http_request_error_handler,
876
};
877
878
879
static void
880
nxt_http_static_buf_completion(nxt_task_t *task, void *obj, void *data)
881
0
{
882
0
    ssize_t             n, size;
883
0
    nxt_buf_t           *b, *fb, *next;
884
0
    nxt_off_t           rest;
885
0
    nxt_http_request_t  *r;
886
887
0
    b = obj;
888
0
    r = data;
889
890
0
complete_buf:
891
892
0
    fb = r->out;
893
894
0
    if (nxt_slow_path(fb == NULL || r->error)) {
895
0
        goto clean;
896
0
    }
897
898
0
    rest = fb->file_end - fb->file_pos;
899
0
    size = nxt_buf_mem_size(&b->mem);
900
901
0
    size = nxt_min(rest, (nxt_off_t) size);
902
903
0
    n = nxt_file_read(fb->file, b->mem.start, size, fb->file_pos);
904
905
0
    if (nxt_slow_path(n == NXT_ERROR)) {
906
0
        nxt_http_request_error_handler(task, r, r->proto.any);
907
0
        goto clean;
908
0
    }
909
910
0
    next = b->next;
911
912
0
    if (n == rest) {
913
0
        nxt_file_close(task, fb->file);
914
0
        r->out = NULL;
915
916
0
        b->next = nxt_http_buf_last(r);
917
918
0
    } else {
919
0
        fb->file_pos += n;
920
0
        b->next = NULL;
921
0
    }
922
923
0
    b->mem.pos = b->mem.start;
924
0
    b->mem.free = b->mem.pos + n;
925
926
0
    nxt_http_request_send(task, r, b);
927
928
0
    if (next != NULL) {
929
0
        b = next;
930
0
        goto complete_buf;
931
0
    }
932
933
0
    return;
934
935
0
clean:
936
937
0
    do {
938
0
        next = b->next;
939
940
0
        nxt_mp_free(r->mem_pool, b);
941
0
        nxt_mp_release(r->mem_pool);
942
943
0
        b = next;
944
0
    } while (b != NULL);
945
946
0
    if (fb != NULL) {
947
0
        nxt_file_close(task, fb->file);
948
0
        r->out = NULL;
949
0
    }
950
0
}
951
952
953
nxt_int_t
954
nxt_http_static_mtypes_init(nxt_mp_t *mp, nxt_lvlhsh_t *hash)
955
0
{
956
0
    nxt_str_t   *type, exten;
957
0
    nxt_int_t   ret;
958
0
    nxt_uint_t  i;
959
960
0
    static const struct {
961
0
        nxt_str_t   type;
962
0
        const char  *exten;
963
0
    } default_types[] = {
964
965
0
        { nxt_string("text/html"),      ".html"  },
966
0
        { nxt_string("text/html"),      ".htm"   },
967
0
        { nxt_string("text/css"),       ".css"   },
968
969
0
        { nxt_string("image/svg+xml"),  ".svg"   },
970
0
        { nxt_string("image/webp"),     ".webp"  },
971
0
        { nxt_string("image/png"),      ".png"   },
972
0
        { nxt_string("image/apng"),     ".apng"  },
973
0
        { nxt_string("image/jpeg"),     ".jpeg"  },
974
0
        { nxt_string("image/jpeg"),     ".jpg"   },
975
0
        { nxt_string("image/gif"),      ".gif"   },
976
0
        { nxt_string("image/x-icon"),   ".ico"   },
977
978
0
        { nxt_string("image/avif"),           ".avif"  },
979
0
        { nxt_string("image/avif-sequence"),  ".avifs" },
980
981
0
        { nxt_string("font/woff"),      ".woff"  },
982
0
        { nxt_string("font/woff2"),     ".woff2" },
983
0
        { nxt_string("font/otf"),       ".otf"   },
984
0
        { nxt_string("font/ttf"),       ".ttf"   },
985
986
0
        { nxt_string("text/plain"),     ".txt"   },
987
0
        { nxt_string("text/markdown"),  ".md"    },
988
0
        { nxt_string("text/x-rst"),     ".rst"   },
989
990
0
        { nxt_string("application/javascript"),  ".js"   },
991
0
        { nxt_string("application/javascript"),  ".mjs"  },
992
0
        { nxt_string("application/json"),        ".json" },
993
0
        { nxt_string("application/xml"),         ".xml"  },
994
0
        { nxt_string("application/rss+xml"),     ".rss"  },
995
0
        { nxt_string("application/atom+xml"),    ".atom" },
996
0
        { nxt_string("application/pdf"),         ".pdf"  },
997
998
0
        { nxt_string("application/zip"),         ".zip"  },
999
1000
0
        { nxt_string("audio/mpeg"),       ".mp3"  },
1001
0
        { nxt_string("audio/ogg"),        ".ogg"  },
1002
0
        { nxt_string("audio/midi"),       ".midi" },
1003
0
        { nxt_string("audio/midi"),       ".mid"  },
1004
0
        { nxt_string("audio/flac"),       ".flac" },
1005
0
        { nxt_string("audio/aac"),        ".aac"  },
1006
0
        { nxt_string("audio/wav"),        ".wav"  },
1007
1008
0
        { nxt_string("video/mpeg"),       ".mpeg" },
1009
0
        { nxt_string("video/mpeg"),       ".mpg"  },
1010
0
        { nxt_string("video/mp4"),        ".mp4"  },
1011
0
        { nxt_string("video/webm"),       ".webm" },
1012
0
        { nxt_string("video/x-msvideo"),  ".avi"  },
1013
1014
0
        { nxt_string("application/octet-stream"),  ".exe" },
1015
0
        { nxt_string("application/octet-stream"),  ".bin" },
1016
0
        { nxt_string("application/octet-stream"),  ".dll" },
1017
0
        { nxt_string("application/octet-stream"),  ".iso" },
1018
0
        { nxt_string("application/octet-stream"),  ".img" },
1019
0
        { nxt_string("application/octet-stream"),  ".msi" },
1020
1021
0
        { nxt_string("application/octet-stream"),  ".deb" },
1022
0
        { nxt_string("application/octet-stream"),  ".rpm" },
1023
1024
0
        { nxt_string("application/x-httpd-php"),   ".php" },
1025
0
    };
1026
1027
0
    for (i = 0; i < nxt_nitems(default_types); i++) {
1028
0
        type = (nxt_str_t *) &default_types[i].type;
1029
1030
0
        exten.start = (u_char *) default_types[i].exten;
1031
0
        exten.length = nxt_strlen(exten.start);
1032
1033
0
        ret = nxt_http_static_mtypes_hash_add(mp, hash, &exten, type);
1034
0
        if (nxt_slow_path(ret != NXT_OK)) {
1035
0
            return NXT_ERROR;
1036
0
        }
1037
0
    }
1038
1039
0
    return NXT_OK;
1040
0
}
1041
1042
1043
static const nxt_lvlhsh_proto_t  nxt_http_static_mtypes_hash_proto
1044
    nxt_aligned(64) =
1045
{
1046
    NXT_LVLHSH_DEFAULT,
1047
    nxt_http_static_mtypes_hash_test,
1048
    nxt_http_static_mtypes_hash_alloc,
1049
    nxt_http_static_mtypes_hash_free,
1050
};
1051
1052
1053
typedef struct {
1054
    nxt_str_t  exten;
1055
    nxt_str_t  *type;
1056
} nxt_http_static_mtype_t;
1057
1058
1059
nxt_int_t
1060
nxt_http_static_mtypes_hash_add(nxt_mp_t *mp, nxt_lvlhsh_t *hash,
1061
    const nxt_str_t *exten, nxt_str_t *type)
1062
0
{
1063
0
    nxt_lvlhsh_query_t       lhq;
1064
0
    nxt_http_static_mtype_t  *mtype;
1065
1066
0
    mtype = nxt_mp_get(mp, sizeof(nxt_http_static_mtype_t));
1067
0
    if (nxt_slow_path(mtype == NULL)) {
1068
0
        return NXT_ERROR;
1069
0
    }
1070
1071
0
    mtype->exten = *exten;
1072
0
    mtype->type = type;
1073
1074
0
    lhq.key = *exten;
1075
0
    lhq.key_hash = nxt_djb_hash_lowcase(lhq.key.start, lhq.key.length);
1076
0
    lhq.replace = 1;
1077
0
    lhq.value = mtype;
1078
0
    lhq.proto = &nxt_http_static_mtypes_hash_proto;
1079
0
    lhq.pool = mp;
1080
1081
0
    return nxt_lvlhsh_insert(hash, &lhq);
1082
0
}
1083
1084
1085
nxt_str_t *
1086
nxt_http_static_mtype_get(nxt_lvlhsh_t *hash, const nxt_str_t *exten)
1087
0
{
1088
0
    nxt_lvlhsh_query_t       lhq;
1089
0
    nxt_http_static_mtype_t  *mtype;
1090
1091
0
    static nxt_str_t  empty = nxt_string("");
1092
1093
0
    lhq.key = *exten;
1094
0
    lhq.key_hash = nxt_djb_hash_lowcase(lhq.key.start, lhq.key.length);
1095
0
    lhq.proto = &nxt_http_static_mtypes_hash_proto;
1096
1097
0
    if (nxt_lvlhsh_find(hash, &lhq) == NXT_OK) {
1098
0
        mtype = lhq.value;
1099
0
        return mtype->type;
1100
0
    }
1101
1102
0
    return &empty;
1103
0
}
1104
1105
1106
static nxt_int_t
1107
nxt_http_static_mtypes_hash_test(nxt_lvlhsh_query_t *lhq, void *data)
1108
0
{
1109
0
    nxt_http_static_mtype_t  *mtype;
1110
1111
0
    mtype = data;
1112
1113
0
    return nxt_strcasestr_eq(&lhq->key, &mtype->exten) ? NXT_OK : NXT_DECLINED;
1114
0
}
1115
1116
1117
static void *
1118
nxt_http_static_mtypes_hash_alloc(void *data, size_t size)
1119
0
{
1120
0
    return nxt_mp_align(data, size, size);
1121
0
}
1122
1123
1124
static void
1125
nxt_http_static_mtypes_hash_free(void *data, void *p)
1126
0
{
1127
0
    nxt_mp_free(data, p);
1128
0
}