Coverage Report

Created: 2026-05-07 06:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/unit/src/nxt_http_parse.c
Line
Count
Source
1
2
/*
3
 * Copyright (C) NGINX, Inc.
4
 * Copyright (C) Valentin V. Bartenev
5
 */
6
7
#include <nxt_main.h>
8
9
10
static nxt_int_t nxt_http_parse_unusual_target(nxt_http_request_parse_t *rp,
11
    u_char **pos, const u_char *end);
12
static nxt_int_t nxt_http_parse_request_line(nxt_http_request_parse_t *rp,
13
    u_char **pos, const u_char *end);
14
static nxt_int_t nxt_http_parse_field_name(nxt_http_request_parse_t *rp,
15
    u_char **pos, const u_char *end);
16
static nxt_int_t nxt_http_parse_field_value(nxt_http_request_parse_t *rp,
17
    u_char **pos, const u_char *end);
18
static u_char *nxt_http_lookup_field_end(u_char *p, const u_char *end);
19
static nxt_int_t nxt_http_parse_field_end(nxt_http_request_parse_t *rp,
20
    u_char **pos, const u_char *end);
21
22
static nxt_int_t nxt_http_field_hash_test(nxt_lvlhsh_query_t *lhq, void *data);
23
24
static nxt_int_t nxt_http_field_hash_collision(nxt_lvlhsh_query_t *lhq,
25
    void *data);
26
27
28
#define NXT_HTTP_MAX_FIELD_NAME         0xFF
29
#define NXT_HTTP_MAX_FIELD_VALUE        NXT_INT32_T_MAX
30
31
0
#define NXT_HTTP_FIELD_LVLHSH_SHIFT     5
32
33
34
typedef enum {
35
    NXT_HTTP_TARGET_SPACE = 1,   /* \s  */
36
    NXT_HTTP_TARGET_HASH,        /*  #  */
37
    NXT_HTTP_TARGET_AGAIN,
38
    NXT_HTTP_TARGET_BAD,         /* \0\r\n */
39
40
    /* traps below are used for extended check only */
41
42
    NXT_HTTP_TARGET_SLASH = 5,   /*  /  */
43
    NXT_HTTP_TARGET_DOT,         /*  .  */
44
    NXT_HTTP_TARGET_ARGS_MARK,   /*  ?  */
45
    NXT_HTTP_TARGET_QUOTE_MARK,  /*  %  */
46
} nxt_http_target_traps_e;
47
48
49
static const uint8_t  nxt_http_target_chars[256] nxt_aligned(64) = {
50
    /* \0                               \n        \r       */
51
        4, 0, 0, 0,  0, 0, 0, 0,   0, 0, 4, 0,  0, 4, 0, 0,
52
        0, 0, 0, 0,  0, 0, 0, 0,   0, 0, 0, 0,  0, 0, 0, 0,
53
54
    /* \s  !  "  #   $  %  &  '    (  )  *  +   ,  -  .  / */
55
        1, 0, 0, 2,  0, 8, 0, 0,   0, 0, 0, 0,  0, 0, 6, 5,
56
57
    /*  0  1  2  3   4  5  6  7    8  9  :  ;   <  =  >  ? */
58
        0, 0, 0, 0,  0, 0, 0, 0,   0, 0, 0, 0,  0, 0, 0, 7,
59
};
60
61
62
nxt_inline nxt_http_target_traps_e
63
nxt_http_parse_target(u_char **pos, const u_char *end)
64
0
{
65
0
    u_char      *p;
66
0
    nxt_uint_t  trap;
67
68
0
    p = *pos;
69
70
0
    while (nxt_fast_path(end - p >= 10)) {
71
72
0
#define nxt_target_test_char(ch)                                              \
73
0
                                                                              \
74
0
        trap = nxt_http_target_chars[ch];                                     \
75
0
                                                                              \
76
0
        if (nxt_slow_path(trap != 0)) {                                       \
77
0
            *pos = &(ch);                                                     \
78
0
            return trap;                                                      \
79
0
        }
80
81
/* enddef */
82
83
0
        nxt_target_test_char(p[0]);
84
0
        nxt_target_test_char(p[1]);
85
0
        nxt_target_test_char(p[2]);
86
0
        nxt_target_test_char(p[3]);
87
88
0
        nxt_target_test_char(p[4]);
89
0
        nxt_target_test_char(p[5]);
90
0
        nxt_target_test_char(p[6]);
91
0
        nxt_target_test_char(p[7]);
92
93
0
        nxt_target_test_char(p[8]);
94
0
        nxt_target_test_char(p[9]);
95
96
0
        p += 10;
97
0
    }
98
99
0
    while (p != end) {
100
0
        nxt_target_test_char(*p); p++;
101
0
    }
102
103
0
    return NXT_HTTP_TARGET_AGAIN;
104
0
}
105
106
107
nxt_int_t
108
nxt_http_parse_request_init(nxt_http_request_parse_t *rp, nxt_mp_t *mp)
109
0
{
110
0
    rp->mem_pool = mp;
111
112
0
    rp->fields = nxt_list_create(mp, 8, sizeof(nxt_http_field_t));
113
0
    if (nxt_slow_path(rp->fields == NULL)) {
114
0
        return NXT_ERROR;
115
0
    }
116
117
0
    rp->field_hash = NXT_HTTP_FIELD_HASH_INIT;
118
119
0
    return NXT_OK;
120
0
}
121
122
123
nxt_int_t
124
nxt_http_parse_request(nxt_http_request_parse_t *rp, nxt_buf_mem_t *b)
125
0
{
126
0
    nxt_int_t  rc;
127
128
0
    if (rp->handler == NULL) {
129
0
        rp->handler = &nxt_http_parse_request_line;
130
0
    }
131
132
0
    do {
133
0
        rc = rp->handler(rp, &b->pos, b->free);
134
0
    } while (rc == NXT_OK);
135
136
0
    return rc;
137
0
}
138
139
140
nxt_int_t
141
nxt_http_parse_fields(nxt_http_request_parse_t *rp, nxt_buf_mem_t *b)
142
0
{
143
0
    nxt_int_t  rc;
144
145
0
    if (rp->handler == NULL) {
146
0
        rp->handler = &nxt_http_parse_field_name;
147
0
    }
148
149
0
    do {
150
0
        rc = rp->handler(rp, &b->pos, b->free);
151
0
    } while (rc == NXT_OK);
152
153
0
    return rc;
154
0
}
155
156
157
static nxt_int_t
158
nxt_http_parse_request_line(nxt_http_request_parse_t *rp, u_char **pos,
159
    const u_char *end)
160
0
{
161
0
    u_char                   *p, ch, *after_slash, *args;
162
0
    nxt_int_t                rc;
163
0
    nxt_bool_t               rest;
164
0
    nxt_http_ver_t           ver;
165
0
    nxt_http_target_traps_e  trap;
166
167
0
    static const nxt_http_ver_t  http11 = { "HTTP/1.1" };
168
0
    static const nxt_http_ver_t  http10 = { "HTTP/1.0" };
169
170
0
    p = *pos;
171
172
0
    rp->method.start = p;
173
174
0
    for ( ;; ) {
175
176
0
        while (nxt_fast_path(end - p >= 8)) {
177
178
0
#define nxt_method_test_char(ch)                                              \
179
0
                                                                              \
180
0
            if (nxt_slow_path((ch) < 'A' || (ch) > 'Z')) {                    \
181
0
                p = &(ch);                                                    \
182
0
                goto method_unusual_char;                                     \
183
0
            }
184
185
/* enddef */
186
187
0
            nxt_method_test_char(p[0]);
188
0
            nxt_method_test_char(p[1]);
189
0
            nxt_method_test_char(p[2]);
190
0
            nxt_method_test_char(p[3]);
191
192
0
            nxt_method_test_char(p[4]);
193
0
            nxt_method_test_char(p[5]);
194
0
            nxt_method_test_char(p[6]);
195
0
            nxt_method_test_char(p[7]);
196
197
0
            p += 8;
198
0
        }
199
200
0
        while (p != end) {
201
0
            nxt_method_test_char(*p); p++;
202
0
        }
203
204
0
        rp->method.length = p - rp->method.start;
205
206
0
        return NXT_AGAIN;
207
208
0
    method_unusual_char:
209
210
0
        ch = *p;
211
212
0
        if (nxt_fast_path(ch == ' ')) {
213
0
            rp->method.length = p - rp->method.start;
214
0
            break;
215
0
        }
216
217
0
        if (ch == '_' || ch == '-') {
218
0
            p++;
219
0
            continue;
220
0
        }
221
222
0
        if (rp->method.start == p && (ch == '\r' || ch == '\n')) {
223
0
            rp->method.start++;
224
0
            p++;
225
0
            continue;
226
0
        }
227
228
0
        rp->method.length = p - rp->method.start;
229
230
0
        return NXT_HTTP_PARSE_INVALID;
231
0
    }
232
233
0
    p++;
234
235
0
    if (nxt_slow_path(p == end)) {
236
0
        return NXT_AGAIN;
237
0
    }
238
239
    /* target */
240
241
0
    ch = *p;
242
243
0
    if (nxt_slow_path(ch != '/')) {
244
0
        rc = nxt_http_parse_unusual_target(rp, &p, end);
245
246
0
        if (nxt_slow_path(rc != NXT_OK)) {
247
0
            return rc;
248
0
        }
249
0
    }
250
251
0
    rp->target_start = p;
252
253
0
    after_slash = p + 1;
254
0
    args = NULL;
255
0
    rest = 0;
256
257
0
continue_target:
258
259
0
    for ( ;; ) {
260
0
        p++;
261
262
0
        trap = nxt_http_parse_target(&p, end);
263
264
0
        switch (trap) {
265
0
        case NXT_HTTP_TARGET_SLASH:
266
0
            if (nxt_slow_path(after_slash == p)) {
267
0
                rp->complex_target = 1;
268
0
                goto rest_of_target;
269
0
            }
270
271
0
            after_slash = p + 1;
272
0
            continue;
273
274
0
        case NXT_HTTP_TARGET_DOT:
275
0
            if (nxt_slow_path(after_slash == p)) {
276
0
                rp->complex_target = 1;
277
0
                goto rest_of_target;
278
0
            }
279
280
0
            continue;
281
282
0
        case NXT_HTTP_TARGET_ARGS_MARK:
283
0
            args = p + 1;
284
0
            goto rest_of_target;
285
286
0
        case NXT_HTTP_TARGET_SPACE:
287
0
            rp->target_end = p;
288
0
            goto space_after_target;
289
290
0
        case NXT_HTTP_TARGET_QUOTE_MARK:
291
0
            rp->quoted_target = 1;
292
0
            goto rest_of_target;
293
294
0
        case NXT_HTTP_TARGET_HASH:
295
0
            rp->complex_target = 1;
296
0
            goto rest_of_target;
297
298
0
        case NXT_HTTP_TARGET_AGAIN:
299
0
            rp->target_end = p;
300
0
            return NXT_AGAIN;
301
302
0
        case NXT_HTTP_TARGET_BAD:
303
0
            rp->target_end = p;
304
0
            return NXT_HTTP_PARSE_INVALID;
305
0
        }
306
307
0
        nxt_unreachable();
308
0
    }
309
310
0
rest_of_target:
311
312
0
    rest = 1;
313
314
0
    for ( ;; ) {
315
0
        p++;
316
317
0
        trap = nxt_http_parse_target(&p, end);
318
319
0
        switch (trap) {
320
0
        case NXT_HTTP_TARGET_SPACE:
321
0
            rp->target_end = p;
322
0
            goto space_after_target;
323
324
0
        case NXT_HTTP_TARGET_HASH:
325
0
            rp->complex_target = 1;
326
0
            continue;
327
328
0
        case NXT_HTTP_TARGET_AGAIN:
329
0
            rp->target_end = p;
330
0
            return NXT_AGAIN;
331
332
0
        case NXT_HTTP_TARGET_BAD:
333
0
            rp->target_end = p;
334
0
            return NXT_HTTP_PARSE_INVALID;
335
336
0
        default:
337
0
            continue;
338
0
        }
339
340
0
        nxt_unreachable();
341
0
    }
342
343
0
space_after_target:
344
345
0
    if (nxt_slow_path(end - p < 10)) {
346
347
0
        do {
348
0
            p++;
349
350
0
            if (p == end) {
351
0
                return NXT_AGAIN;
352
0
            }
353
354
0
        } while (*p == ' ');
355
356
0
        if (memcmp(p, "HTTP/", nxt_min(end - p, 5)) == 0) {
357
358
0
            switch (end - p) {
359
0
            case 8:
360
0
                if (p[7] < '0' || p[7] > '9') {
361
0
                    break;
362
0
                }
363
                /* Fall through. */
364
0
            case 7:
365
0
                if (p[6] != '.') {
366
0
                    break;
367
0
                }
368
                /* Fall through. */
369
0
            case 6:
370
0
                if (p[5] < '0' || p[5] > '9') {
371
0
                    break;
372
0
                }
373
                /* Fall through. */
374
0
            default:
375
0
                return NXT_AGAIN;
376
0
            }
377
0
        }
378
379
        //rp->space_in_target = 1;
380
381
0
        if (rest) {
382
0
            goto rest_of_target;
383
0
        }
384
385
0
        goto continue_target;
386
0
    }
387
388
    /* " HTTP/1.1\r\n" or " HTTP/1.1\n" */
389
390
0
    if (nxt_slow_path(p[9] != '\r' && p[9] != '\n')) {
391
392
0
        if (p[1] == ' ') {
393
            /* surplus space after tartet */
394
0
            p++;
395
0
            goto space_after_target;
396
0
        }
397
398
        //rp->space_in_target = 1;
399
400
0
        if (rest) {
401
0
            goto rest_of_target;
402
0
        }
403
404
0
        goto continue_target;
405
0
    }
406
407
0
    nxt_memcpy(ver.str, &p[1], 8);
408
409
0
    if (nxt_fast_path(ver.ui64 == http11.ui64
410
0
                      || ver.ui64 == http10.ui64
411
0
                      || (memcmp(ver.str, "HTTP/1.", 7) == 0
412
0
                          && ver.s.minor >= '0' && ver.s.minor <= '9')))
413
0
    {
414
0
        rp->version.ui64 = ver.ui64;
415
416
0
        p += 9;
417
0
        if (nxt_fast_path(*p == '\r')) {
418
419
0
            if (nxt_slow_path(p + 1 == end)) {
420
0
                return NXT_AGAIN;
421
0
            }
422
423
0
            if (nxt_slow_path(p[1] != '\n')) {
424
0
                return NXT_HTTP_PARSE_INVALID;
425
0
            }
426
427
0
            *pos = p + 2;
428
429
0
        } else {
430
0
            *pos = p + 1;
431
0
        }
432
433
0
        rp->request_line_end = p;
434
435
0
        if (rp->complex_target || rp->quoted_target) {
436
0
            rc = nxt_http_parse_complex_target(rp);
437
438
0
            if (nxt_slow_path(rc != NXT_OK)) {
439
0
                return rc;
440
0
            }
441
442
0
            return nxt_http_parse_field_name(rp, pos, end);
443
0
        }
444
445
0
        rp->path.start = rp->target_start;
446
447
0
        if (args != NULL) {
448
0
            rp->path.length = args - rp->target_start - 1;
449
450
0
            rp->args.length = rp->target_end - args;
451
0
            rp->args.start = args;
452
453
0
        } else {
454
0
            rp->path.length = rp->target_end - rp->target_start;
455
0
        }
456
457
0
        return nxt_http_parse_field_name(rp, pos, end);
458
0
    }
459
460
0
    if (memcmp(ver.s.prefix, "HTTP/", 5) == 0
461
0
        && ver.s.major >= '0' && ver.s.major <= '9'
462
0
        && ver.s.point == '.'
463
0
        && ver.s.minor >= '0' && ver.s.minor <= '9')
464
0
    {
465
0
        rp->version.ui64 = ver.ui64;
466
0
        return NXT_HTTP_PARSE_UNSUPPORTED_VERSION;
467
0
    }
468
469
0
    return NXT_HTTP_PARSE_INVALID;
470
0
}
471
472
473
static nxt_int_t
474
nxt_http_parse_unusual_target(nxt_http_request_parse_t *rp, u_char **pos,
475
    const u_char *end)
476
0
{
477
0
    u_char  *p, ch;
478
479
0
    p = *pos;
480
481
0
    ch = *p;
482
483
0
    if (ch == ' ') {
484
        /* skip surplus spaces before target */
485
486
0
        do {
487
0
            p++;
488
489
0
            if (nxt_slow_path(p == end)) {
490
0
                return NXT_AGAIN;
491
0
            }
492
493
0
            ch = *p;
494
495
0
        } while (ch == ' ');
496
497
0
        if (ch == '/') {
498
0
            *pos = p;
499
0
            return NXT_OK;
500
0
        }
501
0
    }
502
503
    /* absolute path or '*' */
504
505
    /* TODO */
506
507
0
    return NXT_HTTP_PARSE_INVALID;
508
0
}
509
510
511
static nxt_int_t
512
nxt_http_parse_field_name(nxt_http_request_parse_t *rp, u_char **pos,
513
    const u_char *end)
514
0
{
515
0
    u_char    *p, c;
516
0
    size_t    len;
517
0
    uint32_t  hash;
518
519
0
    static const u_char  normal[256]  NXT_NONSTRING nxt_aligned(64) =
520
0
        "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
521
    /*   \s ! " # $ % & ' ( ) * + ,        . /                 : ; < = > ?   */
522
0
        "\0\1\0\1\1\1\1\1\0\0\1\1\0" "-" "\1\0" "0123456789" "\0\0\0\0\0\0"
523
524
    /*    @                                 [ \ ] ^ _                        */
525
0
        "\0" "abcdefghijklmnopqrstuvwxyz" "\0\0\0\1\1"
526
    /*    `                                 { | } ~                          */
527
0
        "\1" "abcdefghijklmnopqrstuvwxyz" "\0\1\0\1\0"
528
529
0
        "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
530
0
        "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
531
0
        "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
532
0
        "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
533
534
0
    p = *pos + rp->field_name.length;
535
0
    hash = rp->field_hash;
536
537
0
    while (nxt_fast_path(end - p >= 8)) {
538
539
0
#define nxt_field_name_test_char(ch)                                          \
540
0
                                                                              \
541
0
        c = normal[ch];                                                       \
542
0
                                                                              \
543
0
        if (nxt_slow_path(c <= '\1')) {                                       \
544
0
            if (c == '\0') {                                                  \
545
0
                p = &(ch);                                                    \
546
0
                goto name_end;                                                \
547
0
            }                                                                 \
548
0
                                                                              \
549
0
            rp->skip_field = rp->discard_unsafe_fields;                       \
550
0
            c = ch;                                                           \
551
0
        }                                                                     \
552
0
                                                                              \
553
0
        hash = nxt_http_field_hash_char(hash, c);
554
555
/* enddef */
556
557
0
        nxt_field_name_test_char(p[0]);
558
0
        nxt_field_name_test_char(p[1]);
559
0
        nxt_field_name_test_char(p[2]);
560
0
        nxt_field_name_test_char(p[3]);
561
562
0
        nxt_field_name_test_char(p[4]);
563
0
        nxt_field_name_test_char(p[5]);
564
0
        nxt_field_name_test_char(p[6]);
565
0
        nxt_field_name_test_char(p[7]);
566
567
0
        p += 8;
568
0
    }
569
570
0
    while (nxt_fast_path(p != end)) {
571
0
        nxt_field_name_test_char(*p); p++;
572
0
    }
573
574
0
    len = p - *pos;
575
576
0
    if (nxt_slow_path(len > NXT_HTTP_MAX_FIELD_NAME)) {
577
0
        return NXT_HTTP_PARSE_TOO_LARGE_FIELD;
578
0
    }
579
580
0
    rp->field_hash = hash;
581
0
    rp->field_name.length = len;
582
583
0
    rp->handler = &nxt_http_parse_field_name;
584
585
0
    return NXT_AGAIN;
586
587
0
name_end:
588
589
0
    if (nxt_fast_path(*p == ':')) {
590
0
        if (nxt_slow_path(p == *pos)) {
591
0
            return NXT_HTTP_PARSE_INVALID;
592
0
        }
593
594
0
        len = p - *pos;
595
596
0
        if (nxt_slow_path(len > NXT_HTTP_MAX_FIELD_NAME)) {
597
0
            return NXT_HTTP_PARSE_TOO_LARGE_FIELD;
598
0
        }
599
600
0
        rp->field_hash = hash;
601
602
0
        rp->field_name.length = len;
603
0
        rp->field_name.start = *pos;
604
605
0
        *pos = p + 1;
606
607
0
        return nxt_http_parse_field_value(rp, pos, end);
608
0
    }
609
610
0
    if (nxt_slow_path(p != *pos)) {
611
0
        return NXT_HTTP_PARSE_INVALID;
612
0
    }
613
614
0
    return nxt_http_parse_field_end(rp, pos, end);
615
0
}
616
617
618
static nxt_int_t
619
nxt_http_parse_field_value(nxt_http_request_parse_t *rp, u_char **pos,
620
    const u_char *end)
621
0
{
622
0
    u_char  *p, *start, ch;
623
0
    size_t  len;
624
625
0
    p = *pos;
626
627
0
    for ( ;; ) {
628
0
        if (nxt_slow_path(p == end)) {
629
0
            *pos = p;
630
0
            rp->handler = &nxt_http_parse_field_value;
631
0
            return NXT_AGAIN;
632
0
        }
633
634
0
        ch = *p;
635
636
0
        if (ch != ' ' && ch != '\t') {
637
0
            break;
638
0
        }
639
640
0
        p++;
641
0
    }
642
643
0
    start = p;
644
645
0
    p += rp->field_value.length;
646
647
0
    for ( ;; ) {
648
0
        p = nxt_http_lookup_field_end(p, end);
649
650
0
        if (nxt_slow_path(p == end)) {
651
0
            *pos = start;
652
653
0
            len = p - start;
654
655
0
            if (nxt_slow_path(len > NXT_HTTP_MAX_FIELD_VALUE)) {
656
0
                return NXT_HTTP_PARSE_TOO_LARGE_FIELD;
657
0
            }
658
659
0
            rp->field_value.length = len;
660
0
            rp->handler = &nxt_http_parse_field_value;
661
0
            return NXT_AGAIN;
662
0
        }
663
664
0
        ch = *p;
665
666
0
        if (nxt_fast_path(ch == '\r' || ch == '\n')) {
667
0
            break;
668
0
        }
669
670
0
        if (ch != '\t') {
671
0
            return NXT_HTTP_PARSE_INVALID;
672
0
        }
673
674
0
        p++;
675
0
    }
676
677
0
    *pos = p;
678
679
0
    if (nxt_fast_path(p != start)) {
680
681
0
        while (p[-1] == ' ' || p[-1] == '\t') {
682
0
            p--;
683
0
        }
684
0
    }
685
686
0
    len = p - start;
687
688
0
    if (nxt_slow_path(len > NXT_HTTP_MAX_FIELD_VALUE)) {
689
0
        return NXT_HTTP_PARSE_TOO_LARGE_FIELD;
690
0
    }
691
692
0
    rp->field_value.length = len;
693
0
    rp->field_value.start = start;
694
695
0
    return nxt_http_parse_field_end(rp, pos, end);
696
0
}
697
698
699
static u_char *
700
nxt_http_lookup_field_end(u_char *p, const u_char *end)
701
0
{
702
0
    while (nxt_fast_path(end - p >= 16)) {
703
704
0
#define nxt_field_end_test_char(ch)                                           \
705
0
                                                                              \
706
0
        if (nxt_slow_path((ch) < 0x20)) {                                     \
707
0
            return &(ch);                                                     \
708
0
        }
709
710
/* enddef */
711
712
0
        nxt_field_end_test_char(p[0]);
713
0
        nxt_field_end_test_char(p[1]);
714
0
        nxt_field_end_test_char(p[2]);
715
0
        nxt_field_end_test_char(p[3]);
716
717
0
        nxt_field_end_test_char(p[4]);
718
0
        nxt_field_end_test_char(p[5]);
719
0
        nxt_field_end_test_char(p[6]);
720
0
        nxt_field_end_test_char(p[7]);
721
722
0
        nxt_field_end_test_char(p[8]);
723
0
        nxt_field_end_test_char(p[9]);
724
0
        nxt_field_end_test_char(p[10]);
725
0
        nxt_field_end_test_char(p[11]);
726
727
0
        nxt_field_end_test_char(p[12]);
728
0
        nxt_field_end_test_char(p[13]);
729
0
        nxt_field_end_test_char(p[14]);
730
0
        nxt_field_end_test_char(p[15]);
731
732
0
        p += 16;
733
0
    }
734
735
0
    while (nxt_fast_path(end - p >= 4)) {
736
737
0
        nxt_field_end_test_char(p[0]);
738
0
        nxt_field_end_test_char(p[1]);
739
0
        nxt_field_end_test_char(p[2]);
740
0
        nxt_field_end_test_char(p[3]);
741
742
0
        p += 4;
743
0
    }
744
745
0
    switch (end - p) {
746
0
    case 3:
747
0
        nxt_field_end_test_char(*p); p++;
748
        /* Fall through. */
749
0
    case 2:
750
0
        nxt_field_end_test_char(*p); p++;
751
        /* Fall through. */
752
0
    case 1:
753
0
        nxt_field_end_test_char(*p); p++;
754
        /* Fall through. */
755
0
    case 0:
756
0
        break;
757
0
    default:
758
0
        nxt_unreachable();
759
0
    }
760
761
0
    return p;
762
0
}
763
764
765
static nxt_int_t
766
nxt_http_parse_field_end(nxt_http_request_parse_t *rp, u_char **pos,
767
    const u_char *end)
768
0
{
769
0
    u_char            *p;
770
0
    nxt_http_field_t  *field;
771
772
0
    p = *pos;
773
774
0
    if (nxt_fast_path(*p == '\r')) {
775
0
        p++;
776
777
0
        if (nxt_slow_path(p == end)) {
778
0
            rp->handler = &nxt_http_parse_field_end;
779
0
            return NXT_AGAIN;
780
0
        }
781
0
    }
782
783
0
    if (nxt_fast_path(*p == '\n')) {
784
0
        *pos = p + 1;
785
786
0
        if (rp->field_name.length != 0) {
787
0
            if (rp->skip_field) {
788
0
                rp->skip_field = 0;
789
790
0
            } else {
791
0
                field = nxt_list_add(rp->fields);
792
793
0
                if (nxt_slow_path(field == NULL)) {
794
0
                    return NXT_ERROR;
795
0
                }
796
797
0
                field->hash = nxt_http_field_hash_end(rp->field_hash);
798
0
                field->skip = 0;
799
0
                field->hopbyhop = 0;
800
801
0
                field->name_length = rp->field_name.length;
802
0
                field->value_length = rp->field_value.length;
803
0
                field->name = rp->field_name.start;
804
0
                field->value = rp->field_value.start;
805
0
            }
806
807
0
            rp->field_hash = NXT_HTTP_FIELD_HASH_INIT;
808
809
0
            rp->field_name.length = 0;
810
0
            rp->field_value.length = 0;
811
812
0
            rp->handler = &nxt_http_parse_field_name;
813
0
            return NXT_OK;
814
0
        }
815
816
0
        return NXT_DONE;
817
0
    }
818
819
0
    return NXT_HTTP_PARSE_INVALID;
820
0
}
821
822
823
#define nxt_http_is_normal(c)                                                 \
824
0
    (nxt_fast_path((nxt_http_normal[c / 8] & (1 << (c & 7))) != 0))
825
826
827
static const uint8_t  nxt_http_normal[32]  nxt_aligned(32) = {
828
829
                             /*        \0   \r  \n                         */
830
    0xFE, 0xDB, 0xFF, 0xFF,  /* 1111 1110  1101 1011  1111 1111  1111 1111 */
831
832
                             /* '&%$ #"!   /.-, |*)(  7654 3210  ?>=< ;:98 */
833
    0xD6, 0x37, 0xFF, 0x7F,  /* 1101 0110  0011 0111  1111 1111  0111 1111 */
834
835
                             /* GFED CBA@  ONML KJIH  WVUT SRQP  _^]\ [ZYX */
836
    0xFF, 0xFF, 0xFF, 0xFF,  /* 1111 1111  1111 1111  1111 1111  1111 1111 */
837
838
                             /* gfed cba`  onml kjih  wvut srqp   ~}| {zyx */
839
    0xFF, 0xFF, 0xFF, 0xFF,  /* 1111 1111  1111 1111  1111 1111  1111 1111 */
840
841
    0xFF, 0xFF, 0xFF, 0xFF,  /* 1111 1111  1111 1111  1111 1111  1111 1111 */
842
    0xFF, 0xFF, 0xFF, 0xFF,  /* 1111 1111  1111 1111  1111 1111  1111 1111 */
843
    0xFF, 0xFF, 0xFF, 0xFF,  /* 1111 1111  1111 1111  1111 1111  1111 1111 */
844
    0xFF, 0xFF, 0xFF, 0xFF,  /* 1111 1111  1111 1111  1111 1111  1111 1111 */
845
};
846
847
848
nxt_int_t
849
nxt_http_parse_complex_target(nxt_http_request_parse_t *rp)
850
0
{
851
0
    u_char  *p, *u, c, ch, high, *args;
852
853
0
    enum {
854
0
        sw_normal = 0,
855
0
        sw_slash,
856
0
        sw_dot,
857
0
        sw_dot_dot,
858
0
        sw_quoted,
859
0
        sw_quoted_second,
860
0
    } state, saved_state;
861
862
0
    nxt_prefetch(nxt_http_normal);
863
864
0
    state = sw_normal;
865
0
    saved_state = sw_normal;
866
0
    p = rp->target_start;
867
868
0
    u = nxt_mp_alloc(rp->mem_pool, rp->target_end - p + 1);
869
0
    if (nxt_slow_path(u == NULL)) {
870
0
        return NXT_ERROR;
871
0
    }
872
873
0
    rp->path.length = 0;
874
0
    rp->path.start = u;
875
876
0
    high = '\0';
877
0
    args = NULL;
878
879
0
    while (p < rp->target_end) {
880
881
0
        ch = *p++;
882
883
0
    again:
884
885
0
        switch (state) {
886
887
0
        case sw_normal:
888
889
0
            if (nxt_http_is_normal(ch)) {
890
0
                *u++ = ch;
891
0
                continue;
892
0
            }
893
894
0
            switch (ch) {
895
0
            case '/':
896
0
                state = sw_slash;
897
0
                *u++ = ch;
898
0
                continue;
899
0
            case '%':
900
0
                saved_state = state;
901
0
                state = sw_quoted;
902
0
                continue;
903
0
            case '?':
904
0
                args = p;
905
0
                goto args;
906
0
            case '#':
907
0
                goto done;
908
0
            default:
909
0
                *u++ = ch;
910
0
                continue;
911
0
            }
912
913
0
            break;
914
915
0
        case sw_slash:
916
917
0
            if (nxt_http_is_normal(ch)) {
918
0
                state = sw_normal;
919
0
                *u++ = ch;
920
0
                continue;
921
0
            }
922
923
0
            switch (ch) {
924
0
            case '/':
925
0
                continue;
926
0
            case '.':
927
0
                state = sw_dot;
928
0
                *u++ = ch;
929
0
                continue;
930
0
            case '%':
931
0
                saved_state = state;
932
0
                state = sw_quoted;
933
0
                continue;
934
0
            case '?':
935
0
                args = p;
936
0
                goto args;
937
0
            case '#':
938
0
                goto done;
939
0
            default:
940
0
                state = sw_normal;
941
0
                *u++ = ch;
942
0
                continue;
943
0
            }
944
945
0
            break;
946
947
0
        case sw_dot:
948
949
0
            if (nxt_http_is_normal(ch)) {
950
0
                state = sw_normal;
951
0
                *u++ = ch;
952
0
                continue;
953
0
            }
954
955
0
            switch (ch) {
956
0
            case '/':
957
0
                state = sw_slash;
958
0
                u--;
959
0
                continue;
960
0
            case '.':
961
0
                state = sw_dot_dot;
962
0
                *u++ = ch;
963
0
                continue;
964
0
            case '%':
965
0
                saved_state = state;
966
0
                state = sw_quoted;
967
0
                continue;
968
0
            case '?':
969
0
                u--;
970
0
                args = p;
971
0
                goto args;
972
0
            case '#':
973
0
                u--;
974
0
                goto done;
975
0
            default:
976
0
                state = sw_normal;
977
0
                *u++ = ch;
978
0
                continue;
979
0
            }
980
981
0
            break;
982
983
0
        case sw_dot_dot:
984
985
0
            if (nxt_http_is_normal(ch)) {
986
0
                state = sw_normal;
987
0
                *u++ = ch;
988
0
                continue;
989
0
            }
990
991
0
            switch (ch) {
992
993
0
            case '/':
994
0
            case '?':
995
0
            case '#':
996
0
                u -= 5;
997
998
0
                for ( ;; ) {
999
0
                    if (u < rp->path.start) {
1000
0
                        return NXT_HTTP_PARSE_INVALID;
1001
0
                    }
1002
1003
0
                    if (*u == '/') {
1004
0
                        u++;
1005
0
                        break;
1006
0
                    }
1007
1008
0
                    u--;
1009
0
                }
1010
1011
0
                if (ch == '?') {
1012
0
                    args = p;
1013
0
                    goto args;
1014
0
                }
1015
1016
0
                if (ch == '#') {
1017
0
                    goto done;
1018
0
                }
1019
1020
0
                state = sw_slash;
1021
0
                break;
1022
1023
0
            case '%':
1024
0
                saved_state = state;
1025
0
                state = sw_quoted;
1026
0
                continue;
1027
1028
0
            default:
1029
0
                state = sw_normal;
1030
0
                *u++ = ch;
1031
0
                continue;
1032
0
            }
1033
1034
0
            break;
1035
1036
0
        case sw_quoted:
1037
0
            rp->quoted_target = 1;
1038
1039
0
            if (ch >= '0' && ch <= '9') {
1040
0
                high = (u_char) (ch - '0');
1041
0
                state = sw_quoted_second;
1042
0
                continue;
1043
0
            }
1044
1045
0
            c = (u_char) (ch | 0x20);
1046
0
            if (c >= 'a' && c <= 'f') {
1047
0
                high = (u_char) (c - 'a' + 10);
1048
0
                state = sw_quoted_second;
1049
0
                continue;
1050
0
            }
1051
1052
0
            return NXT_HTTP_PARSE_INVALID;
1053
1054
0
        case sw_quoted_second:
1055
0
            if (ch >= '0' && ch <= '9') {
1056
0
                ch = (u_char) ((high << 4) + ch - '0');
1057
1058
0
                if (ch == '%') {
1059
0
                    state = sw_normal;
1060
0
                    *u++ = '%';
1061
1062
0
                    if (rp->encoded_slashes) {
1063
0
                        *u++ = '2';
1064
0
                        *u++ = '5';
1065
0
                    }
1066
1067
0
                    continue;
1068
0
                }
1069
1070
0
                if (ch == '#') {
1071
0
                    state = sw_normal;
1072
0
                    *u++ = '#';
1073
0
                    continue;
1074
0
                }
1075
1076
0
                if (ch == '\0') {
1077
0
                    return NXT_HTTP_PARSE_INVALID;
1078
0
                }
1079
1080
0
                state = saved_state;
1081
0
                goto again;
1082
0
            }
1083
1084
0
            c = (u_char) (ch | 0x20);
1085
0
            if (c >= 'a' && c <= 'f') {
1086
0
                ch = (u_char) ((high << 4) + c - 'a' + 10);
1087
1088
0
                if (ch == '?') {
1089
0
                    state = sw_normal;
1090
0
                    *u++ = ch;
1091
0
                    continue;
1092
0
                }
1093
1094
0
                if (ch == '/' && rp->encoded_slashes) {
1095
0
                    state = sw_normal;
1096
0
                    *u++ = '%';
1097
0
                    *u++ = '2';
1098
0
                    *u++ = p[-1];  /* 'f' or 'F' */
1099
0
                    continue;
1100
0
                }
1101
1102
0
                state = saved_state;
1103
0
                goto again;
1104
0
            }
1105
1106
0
            return NXT_HTTP_PARSE_INVALID;
1107
0
        }
1108
0
    }
1109
1110
0
    if (state >= sw_dot) {
1111
0
        if (state >= sw_quoted) {
1112
0
            return NXT_HTTP_PARSE_INVALID;
1113
0
        }
1114
1115
        /* "/." and "/.." must be normalized similar to "/./" and "/../". */
1116
0
        ch = '/';
1117
0
        goto again;
1118
0
    }
1119
1120
0
args:
1121
1122
0
    for (/* void */; p < rp->target_end; p++) {
1123
0
        if (*p == '#') {
1124
0
            break;
1125
0
        }
1126
0
    }
1127
1128
0
    if (args != NULL) {
1129
0
        rp->args.length = p - args;
1130
0
        rp->args.start = args;
1131
0
    }
1132
1133
0
done:
1134
1135
0
    rp->path.length = u - rp->path.start;
1136
1137
0
    return NXT_OK;
1138
0
}
1139
1140
1141
const nxt_lvlhsh_proto_t  nxt_http_fields_hash_proto  nxt_aligned(64) = {
1142
    NXT_LVLHSH_BUCKET_SIZE(64),
1143
    { NXT_HTTP_FIELD_LVLHSH_SHIFT, 0, 0, 0, 0, 0, 0, 0 },
1144
    nxt_http_field_hash_test,
1145
    nxt_lvlhsh_alloc,
1146
    nxt_lvlhsh_free,
1147
};
1148
1149
1150
static nxt_int_t
1151
nxt_http_field_hash_test(nxt_lvlhsh_query_t *lhq, void *data)
1152
0
{
1153
0
    nxt_http_field_proc_t  *field;
1154
1155
0
    field = data;
1156
1157
0
    if (nxt_strcasestr_eq(&lhq->key, &field->name)) {
1158
0
        return NXT_OK;
1159
0
    }
1160
1161
0
    return NXT_DECLINED;
1162
0
}
1163
1164
1165
static nxt_int_t
1166
nxt_http_field_hash_collision(nxt_lvlhsh_query_t *lhq, void *data)
1167
0
{
1168
0
    return NXT_OK;
1169
0
}
1170
1171
1172
nxt_int_t
1173
nxt_http_fields_hash(nxt_lvlhsh_t *hash,
1174
    nxt_http_field_proc_t items[], nxt_uint_t count)
1175
0
{
1176
0
    u_char              ch;
1177
0
    uint32_t            key;
1178
0
    nxt_str_t           *name;
1179
0
    nxt_int_t           ret;
1180
0
    nxt_uint_t          i, j;
1181
0
    nxt_lvlhsh_query_t  lhq;
1182
1183
0
    lhq.replace = 0;
1184
0
    lhq.proto = &nxt_http_fields_hash_proto;
1185
0
    lhq.pool = NULL;
1186
1187
0
    for (i = 0; i < count; i++) {
1188
0
        key = NXT_HTTP_FIELD_HASH_INIT;
1189
0
        name = &items[i].name;
1190
1191
0
        for (j = 0; j < name->length; j++) {
1192
0
            ch = nxt_lowcase(name->start[j]);
1193
0
            key = nxt_http_field_hash_char(key, ch);
1194
0
        }
1195
1196
0
        lhq.key_hash = nxt_http_field_hash_end(key) & 0xFFFF;
1197
0
        lhq.key = *name;
1198
0
        lhq.value = &items[i];
1199
1200
0
        ret = nxt_lvlhsh_insert(hash, &lhq);
1201
1202
0
        if (nxt_slow_path(ret != NXT_OK)) {
1203
0
            return NXT_ERROR;
1204
0
        }
1205
0
    }
1206
1207
0
    return NXT_OK;
1208
0
}
1209
1210
1211
nxt_uint_t
1212
nxt_http_fields_hash_collisions(nxt_lvlhsh_t *hash,
1213
    nxt_http_field_proc_t items[], nxt_uint_t count, nxt_bool_t level)
1214
0
{
1215
0
    u_char              ch;
1216
0
    uint32_t            key, mask;
1217
0
    nxt_str_t           *name;
1218
0
    nxt_uint_t          colls, i, j;
1219
0
    nxt_lvlhsh_proto_t  proto;
1220
0
    nxt_lvlhsh_query_t  lhq;
1221
1222
0
    proto = nxt_http_fields_hash_proto;
1223
0
    proto.test = nxt_http_field_hash_collision;
1224
1225
0
    lhq.replace = 0;
1226
0
    lhq.proto = &proto;
1227
1228
0
    mask = level ? (1 << NXT_HTTP_FIELD_LVLHSH_SHIFT) - 1 : 0xFFFF;
1229
1230
0
    colls = 0;
1231
1232
0
    for (i = 0; i < count; i++) {
1233
0
        key = NXT_HTTP_FIELD_HASH_INIT;
1234
0
        name = &items[i].name;
1235
1236
0
        for (j = 0; j < name->length; j++) {
1237
0
            ch = nxt_lowcase(name->start[j]);
1238
0
            key = nxt_http_field_hash_char(key, ch);
1239
0
        }
1240
1241
0
        lhq.key_hash = nxt_http_field_hash_end(key) & mask;
1242
0
        lhq.value = &items[i];
1243
1244
0
        if (nxt_lvlhsh_insert(hash, &lhq) == NXT_DECLINED) {
1245
0
            colls++;
1246
0
        }
1247
0
    }
1248
1249
0
    return colls;
1250
0
}
1251
1252
1253
nxt_int_t
1254
nxt_http_fields_process(nxt_list_t *fields, nxt_lvlhsh_t *hash, void *ctx)
1255
0
{
1256
0
    nxt_int_t         ret;
1257
0
    nxt_http_field_t  *field;
1258
1259
0
    nxt_list_each(field, fields) {
1260
1261
0
        ret = nxt_http_field_process(field, hash, ctx);
1262
0
        if (nxt_slow_path(ret != NXT_OK)) {
1263
0
            return ret;
1264
0
        }
1265
1266
0
    } nxt_list_loop;
1267
1268
0
    return NXT_OK;
1269
0
}