Coverage Report

Created: 2026-10-14 07:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/njs/external/njs_regex.c
Line
Count
Source
1
2
/*
3
 * Copyright (C) Igor Sysoev
4
 * Copyright (C) Dmitry Volyntsev
5
 * Copyright (C) NGINX, Inc.
6
 */
7
8
9
#include <njs_main.h>
10
11
#ifdef NJS_HAVE_PCRE2
12
13
#define PCRE2_CODE_UNIT_WIDTH 8
14
#include <pcre2.h>
15
16
17
static const u_char* njs_regex_pcre2_error(int errcode, u_char buffer[128]);
18
19
#else
20
21
#include <pcre.h>
22
23
24
static void *njs_pcre_malloc(size_t size);
25
static void njs_pcre_free(void *p);
26
27
28
static njs_regex_generic_ctx_t  *regex_context;
29
30
#endif
31
32
33
njs_regex_generic_ctx_t *
34
njs_regex_generic_ctx_create(njs_pcre_malloc_t private_malloc,
35
    njs_pcre_free_t private_free, void *memory_data)
36
1.08k
{
37
1.08k
#ifdef NJS_HAVE_PCRE2
38
39
1.08k
    return pcre2_general_context_create(private_malloc, private_free,
40
1.08k
                                        memory_data);
41
#else
42
43
    njs_regex_generic_ctx_t  *ctx;
44
45
    ctx = private_malloc(sizeof(njs_regex_generic_ctx_t), memory_data);
46
47
    if (njs_fast_path(ctx != NULL)) {
48
        ctx->private_malloc = private_malloc;
49
        ctx->private_free = private_free;
50
        ctx->memory_data = memory_data;
51
    }
52
53
    return ctx;
54
55
#endif
56
1.08k
}
57
58
59
njs_regex_compile_ctx_t *
60
njs_regex_compile_ctx_create(njs_regex_generic_ctx_t *ctx)
61
1.08k
{
62
1.08k
#ifdef NJS_HAVE_PCRE2
63
1.08k
    pcre2_compile_context  *cc;
64
65
1.08k
    cc = pcre2_compile_context_create(ctx);
66
67
1.08k
#ifdef PCRE2_EXTRA_ALLOW_SURROGATE_ESCAPES
68
1.08k
    if (njs_fast_path(cc != NULL)) {
69
        /* Workaround for surrogate pairs in regular expressions
70
         *
71
         * This option is needed because njs, unlike the standard ECMAScript,
72
         * stores and processes strings in UTF-8 encoding.
73
         * PCRE2 does not support surrogate pairs by default when it
74
         * is compiled for UTF-8 only strings. But many polyfills
75
         * and transpilers use such surrogate pairs expressions.
76
         */
77
1.08k
        pcre2_set_compile_extra_options(cc,
78
1.08k
                                        PCRE2_EXTRA_ALLOW_SURROGATE_ESCAPES);
79
1.08k
    }
80
1.08k
#endif
81
82
1.08k
    return cc;
83
84
#else
85
86
    return ctx;
87
88
#endif
89
1.08k
}
90
91
92
93
njs_int_t
94
njs_regex_escape(njs_mp_t *mp, njs_str_t *text)
95
47.3k
{
96
47.3k
#ifdef NJS_HAVE_PCRE2
97
47.3k
    size_t  anychars, nomatches;
98
47.3k
    u_char  *p, *dst, *start, *end;
99
100
    /*
101
     * 1) [^] is a valid regexp expression in JavaScript, but PCRE2
102
     * rejects it as invalid, replacing it with equivalent PCRE2 [\s\S]
103
     * expression.
104
     * 2) [] is a valid regexp expression in JavaScript, but PCRE2
105
     * rejects it as invalid, replacing it with equivalent PCRE2 (?!)
106
     * expression which matches nothing.
107
     */
108
109
47.3k
    start = text->start;
110
47.3k
    end = text->start + text->length;
111
112
47.3k
    anychars = 0;
113
47.3k
    nomatches = 0;
114
115
32.1M
    for (p = start; p < end; p++) {
116
32.0M
        switch (*p) {
117
18.5k
        case '\\':
118
18.5k
            p += 1;
119
120
18.5k
            break;
121
122
11.1k
        case '[':
123
11.1k
            if (p + 1 < end && p[1] == ']') {
124
5.21k
                p += 1;
125
5.21k
                nomatches += 1;
126
127
5.96k
            } else if (p + 2 < end && p[1] == '^' && p[2] == ']') {
128
8
                p += 2;
129
8
                anychars += 1;
130
131
5.95k
            } else {
132
604k
                while (p < end && *p != ']') {
133
598k
                    p += 1;
134
598k
                }
135
5.95k
            }
136
137
11.1k
            break;
138
32.0M
        }
139
32.0M
    }
140
141
47.3k
    if (!anychars && !nomatches) {
142
42.1k
        return NJS_OK;
143
42.1k
    }
144
145
5.22k
    text->length = text->length
146
5.22k
                   + anychars * (njs_length("\\s\\S") - njs_length("^"))
147
5.22k
                   + nomatches * (njs_length("?!"));
148
149
5.22k
    text->start = njs_mp_alloc(mp, text->length);
150
5.22k
    if (njs_slow_path(text->start == NULL)) {
151
0
        return NJS_ERROR;
152
0
    }
153
154
5.22k
    dst = text->start;
155
156
721k
    for (p = start; p < end; p++) {
157
158
715k
        switch (*p) {
159
3.68k
        case '\\':
160
3.68k
            *dst++ = *p;
161
3.68k
            if (p + 1 < end) {
162
3.68k
                p += 1;
163
3.68k
                *dst++ = *p;
164
3.68k
            }
165
166
3.68k
            continue;
167
168
10.5k
        case '[':
169
10.5k
            if (p + 1 < end && p[1] == ']') {
170
5.21k
                p += 1;
171
5.21k
                dst = njs_cpymem(dst, "(?!)", 4);
172
5.21k
                continue;
173
174
5.32k
            } else if (p + 2 < end && p[1] == '^' && p[2] == ']') {
175
8
                p += 2;
176
8
                dst = njs_cpymem(dst, "[\\s\\S]", 6);
177
8
                continue;
178
179
5.31k
            } else {
180
5.31k
                *dst++ = *p++; /* Copy '['. */
181
182
59.1k
                while (p < end && *p != ']') {
183
53.8k
                    *dst++ = *p++;
184
53.8k
                }
185
186
5.31k
                if (p < end) {
187
5.31k
                    *dst++ = *p; /* Copy ']'. */
188
5.31k
                }
189
190
5.31k
                continue;
191
5.31k
            }
192
715k
        }
193
194
701k
        *dst++ = *p;
195
701k
    }
196
197
5.22k
    njs_assert(dst == text->start + text->length);
198
199
5.22k
    return NJS_OK;
200
201
#else
202
203
    /*
204
     * 1) PCRE with PCRE_JAVASCRIPT_COMPAT flag rejects regexps with
205
     * lone closing square brackets as invalid.  Whereas according
206
     * to ES6: 11.8.5 it is a valid regexp expression.
207
     *
208
     * 2) escaping zero byte characters as "\u0000".
209
     *
210
     * Escaping it here as a workaround.
211
     */
212
213
    size_t      brackets, zeros;
214
    u_char      *p, *dst, *start, *end;
215
    njs_bool_t  in;
216
217
    start = text->start;
218
    end = text->start + text->length;
219
220
    in = 0;
221
    zeros = 0;
222
    brackets = 0;
223
224
    for (p = start; p < end; p++) {
225
226
        switch (*p) {
227
        case '[':
228
            in = 1;
229
            break;
230
231
        case ']':
232
            if (!in) {
233
                brackets++;
234
            }
235
236
            in = 0;
237
            break;
238
239
        case '\\':
240
            p++;
241
242
            if (p == end || *p != '\0') {
243
                break;
244
            }
245
246
            /* Fall through. */
247
248
        case '\0':
249
            zeros++;
250
            break;
251
        }
252
    }
253
254
    if (!brackets && !zeros) {
255
        return NJS_OK;
256
    }
257
258
    text->length = text->length + brackets + zeros * njs_length("\\u0000");
259
260
    text->start = njs_mp_alloc(mp, text->length);
261
    if (njs_slow_path(text->start == NULL)) {
262
        return NJS_ERROR;
263
    }
264
265
    in = 0;
266
    dst = text->start;
267
268
    for (p = start; p < end; p++) {
269
270
        switch (*p) {
271
        case '[':
272
            in = 1;
273
            break;
274
275
        case ']':
276
            if (!in) {
277
                *dst++ = '\\';
278
            }
279
280
            in = 0;
281
            break;
282
283
        case '\\':
284
            *dst++ = *p++;
285
286
            if (p == end) {
287
                goto done;
288
            }
289
290
            if (*p != '\0') {
291
                break;
292
            }
293
294
            /* Fall through. */
295
296
        case '\0':
297
            dst = njs_cpymem(dst, "\\u0000", 6);
298
            continue;
299
        }
300
301
        *dst++ = *p;
302
    }
303
304
done:
305
306
    text->length = dst - text->start;
307
308
    return NJS_OK;
309
310
#endif
311
5.22k
}
312
313
314
njs_int_t
315
njs_regex_compile(njs_regex_t *regex, u_char *source, size_t len,
316
    njs_regex_flags_t flags, njs_regex_compile_ctx_t *cctx, njs_trace_t *trace)
317
94.6k
{
318
94.6k
#ifdef NJS_HAVE_PCRE2
319
320
94.6k
    int         ret;
321
94.6k
    u_char      *error;
322
94.6k
    size_t      erroff;
323
94.6k
    njs_uint_t  options;
324
94.6k
    u_char      errstr[128];
325
326
94.6k
    options = PCRE2_ALT_BSUX | PCRE2_MATCH_UNSET_BACKREF;
327
328
94.6k
    if ((flags & NJS_REGEX_IGNORE_CASE)) {
329
12.1k
         options |= PCRE2_CASELESS;
330
12.1k
    }
331
332
94.6k
    if ((flags & NJS_REGEX_MULTILINE)) {
333
0
         options |= PCRE2_MULTILINE;
334
0
    }
335
336
94.6k
    if ((flags & NJS_REGEX_STICKY)) {
337
8.15k
         options |= PCRE2_ANCHORED;
338
8.15k
    }
339
340
94.6k
    if ((flags & NJS_REGEX_UTF8)) {
341
47.3k
         options |= PCRE2_UTF;
342
47.3k
    }
343
344
94.6k
    regex->code = pcre2_compile(source, len, options, &ret, &erroff, cctx);
345
346
94.6k
    if (njs_slow_path(regex->code == NULL)) {
347
30.5k
        error = &source[erroff];
348
349
30.5k
        njs_alert(trace, NJS_LEVEL_ERROR,
350
30.5k
                  "pcre_compile2(\"%s\") failed: %s at \"%s\"",
351
30.5k
                  source, njs_regex_pcre2_error(ret, errstr), error);
352
353
30.5k
        return NJS_DECLINED;
354
30.5k
    }
355
356
64.1k
    ret = pcre2_pattern_info(regex->code, PCRE2_INFO_CAPTURECOUNT,
357
64.1k
                             &regex->ncaptures);
358
359
64.1k
    if (njs_slow_path(ret < 0)) {
360
0
        njs_alert(trace, NJS_LEVEL_ERROR,
361
0
               "pcre2_pattern_info(\"%s\", PCRE2_INFO_CAPTURECOUNT) failed: %s",
362
0
               source, njs_regex_pcre2_error(ret, errstr));
363
364
0
        return NJS_ERROR;
365
0
    }
366
367
64.1k
    ret = pcre2_pattern_info(regex->code, PCRE2_INFO_BACKREFMAX,
368
64.1k
                             &regex->backrefmax);
369
370
64.1k
    if (njs_slow_path(ret < 0)) {
371
0
        njs_alert(trace, NJS_LEVEL_ERROR,
372
0
                 "pcre2_pattern_info(\"%s\", PCRE2_INFO_BACKREFMAX) failed: %s",
373
0
                 source, njs_regex_pcre2_error(ret, errstr));
374
375
0
        return NJS_ERROR;
376
0
    }
377
378
    /* Reserve additional elements for the first "$0" capture. */
379
64.1k
    regex->ncaptures++;
380
381
64.1k
    if (regex->ncaptures > 1) {
382
10.7k
        ret = pcre2_pattern_info(regex->code, PCRE2_INFO_NAMECOUNT,
383
10.7k
                                 &regex->nentries);
384
385
10.7k
        if (njs_slow_path(ret < 0)) {
386
0
            njs_alert(trace, NJS_LEVEL_ERROR,
387
0
                  "pcre2_pattern_info(\"%s\", PCRE2_INFO_NAMECOUNT) failed: %s",
388
0
                   source, njs_regex_pcre2_error(ret, errstr));
389
390
0
            return NJS_ERROR;
391
0
        }
392
393
10.7k
        if (regex->nentries != 0) {
394
6.02k
            ret = pcre2_pattern_info(regex->code, PCRE2_INFO_NAMEENTRYSIZE,
395
6.02k
                                     &regex->entry_size);
396
397
6.02k
            if (njs_slow_path(ret < 0)) {
398
0
                njs_alert(trace, NJS_LEVEL_ERROR,
399
0
                          "pcre2_pattern_info(\"%s\", PCRE2_INFO_NAMEENTRYSIZE)"
400
0
                          " failed: %s", source,
401
0
                          njs_regex_pcre2_error(ret, errstr));
402
403
0
                return NJS_ERROR;
404
0
            }
405
406
6.02k
            ret = pcre2_pattern_info(regex->code, PCRE2_INFO_NAMETABLE,
407
6.02k
                                     &regex->entries);
408
409
6.02k
            if (njs_slow_path(ret < 0)) {
410
0
                njs_alert(trace, NJS_LEVEL_ERROR,
411
0
                          "pcre2_pattern_info(\"%s\", PCRE2_INFO_NAMETABLE) "
412
0
                          "failed: %s", source,
413
0
                          njs_regex_pcre2_error(ret, errstr));
414
415
0
                return NJS_ERROR;
416
0
            }
417
6.02k
        }
418
10.7k
    }
419
420
64.1k
    return NJS_OK;
421
422
#else
423
424
    int                      ret, err, erroff;
425
    char                     *pattern, *error;
426
    void                     *(*saved_malloc)(size_t size);
427
    void                     (*saved_free)(void *p);
428
    njs_uint_t               options;
429
    const char               *errstr;
430
    njs_regex_generic_ctx_t  *ctx;
431
432
    ctx = cctx;
433
434
    ret = NJS_ERROR;
435
436
    saved_malloc = pcre_malloc;
437
    pcre_malloc = njs_pcre_malloc;
438
    saved_free = pcre_free;
439
    pcre_free = njs_pcre_free;
440
    regex_context = ctx;
441
442
#ifdef PCRE_JAVASCRIPT_COMPAT
443
    /* JavaScript compatibility has been introduced in PCRE-7.7. */
444
    options = PCRE_JAVASCRIPT_COMPAT;
445
#else
446
    options = 0;
447
#endif
448
449
    if ((flags & NJS_REGEX_IGNORE_CASE)) {
450
         options |= PCRE_CASELESS;
451
    }
452
453
    if ((flags & NJS_REGEX_MULTILINE)) {
454
         options |= PCRE_MULTILINE;
455
    }
456
457
    if ((flags & NJS_REGEX_STICKY)) {
458
         options |= PCRE_ANCHORED;
459
    }
460
461
    if ((flags & NJS_REGEX_UTF8)) {
462
         options |= PCRE_UTF8;
463
    }
464
465
    pattern = (char *) source;
466
467
    regex->code = pcre_compile(pattern, options, &errstr, &erroff, NULL);
468
469
    if (njs_slow_path(regex->code == NULL)) {
470
        error = pattern + erroff;
471
472
        if (*error != '\0') {
473
            njs_alert(trace, NJS_LEVEL_ERROR,
474
                      "pcre_compile(\"%s\") failed: %s at \"%s\"",
475
                      pattern, errstr, error);
476
477
        } else {
478
            njs_alert(trace, NJS_LEVEL_ERROR,
479
                      "pcre_compile(\"%s\") failed: %s", pattern, errstr);
480
        }
481
482
        ret = NJS_DECLINED;
483
484
        goto done;
485
    }
486
487
    regex->extra = pcre_study(regex->code, 0, &errstr);
488
489
    if (njs_slow_path(errstr != NULL)) {
490
        njs_alert(trace, NJS_LEVEL_WARN,
491
                  "pcre_study(\"%s\") failed: %s", pattern, errstr);
492
    }
493
494
    err = pcre_fullinfo(regex->code, NULL, PCRE_INFO_CAPTURECOUNT,
495
                        &regex->ncaptures);
496
497
    if (njs_slow_path(err < 0)) {
498
        njs_alert(trace, NJS_LEVEL_ERROR,
499
                  "pcre_fullinfo(\"%s\", PCRE_INFO_CAPTURECOUNT) failed: %d",
500
                  pattern, err);
501
502
        goto done;
503
    }
504
505
    err = pcre_fullinfo(regex->code, NULL, PCRE_INFO_BACKREFMAX,
506
                        &regex->backrefmax);
507
508
    if (njs_slow_path(err < 0)) {
509
        njs_alert(trace, NJS_LEVEL_ERROR,
510
                  "pcre_fullinfo(\"%s\", PCRE_INFO_BACKREFMAX) failed: %d",
511
                  pattern, err);
512
513
        goto done;
514
    }
515
516
    /* Reserve additional elements for the first "$0" capture. */
517
    regex->ncaptures++;
518
519
    if (regex->ncaptures > 1) {
520
        err = pcre_fullinfo(regex->code, NULL, PCRE_INFO_NAMECOUNT,
521
                            &regex->nentries);
522
523
        if (njs_slow_path(err < 0)) {
524
            njs_alert(trace, NJS_LEVEL_ERROR,
525
                      "pcre_fullinfo(\"%s\", PCRE_INFO_NAMECOUNT) failed: %d",
526
                      pattern, err);
527
528
            goto done;
529
        }
530
531
        if (regex->nentries != 0) {
532
            err = pcre_fullinfo(regex->code, NULL, PCRE_INFO_NAMEENTRYSIZE,
533
                                &regex->entry_size);
534
535
            if (njs_slow_path(err < 0)) {
536
                njs_alert(trace, NJS_LEVEL_ERROR, "pcre_fullinfo(\"%s\", "
537
                          "PCRE_INFO_NAMEENTRYSIZE) failed: %d", pattern, err);
538
539
                goto done;
540
            }
541
542
            err = pcre_fullinfo(regex->code, NULL, PCRE_INFO_NAMETABLE,
543
                                &regex->entries);
544
545
            if (njs_slow_path(err < 0)) {
546
                njs_alert(trace, NJS_LEVEL_ERROR, "pcre_fullinfo(\"%s\", "
547
                          "PCRE_INFO_NAMETABLE) failed: %d", pattern, err);
548
549
                goto done;
550
            }
551
        }
552
    }
553
554
    ret = NJS_OK;
555
556
done:
557
558
    pcre_malloc = saved_malloc;
559
    pcre_free = saved_free;
560
    regex_context = NULL;
561
562
    return ret;
563
564
#endif
565
64.1k
}
566
567
568
njs_bool_t
569
njs_regex_is_valid(njs_regex_t *regex)
570
96.0k
{
571
96.0k
    return (regex->code != NULL);
572
96.0k
}
573
574
575
njs_int_t
576
njs_regex_named_capture(njs_regex_t *regex, njs_str_t *name, int n,
577
    uint32_t *capture)
578
422k
{
579
422k
    u_char  *entry;
580
581
422k
    if (name == NULL) {
582
42.6k
        return regex->nentries;
583
42.6k
    }
584
585
379k
    if (n >= regex->nentries) {
586
0
        return NJS_ERROR;
587
0
    }
588
589
379k
    entry = regex->entries + regex->entry_size * n;
590
591
379k
    name->start = entry + 2;
592
379k
    name->length = njs_strlen(name->start);
593
594
379k
    *capture = (entry[0] << 8) + entry[1];
595
596
379k
    return NJS_OK;
597
379k
}
598
599
600
njs_regex_match_data_t *
601
njs_regex_match_data(njs_regex_t *regex, njs_regex_generic_ctx_t *ctx)
602
23.5k
{
603
23.5k
#ifdef NJS_HAVE_PCRE2
604
605
23.5k
    if (regex != NULL) {
606
22.4k
        return pcre2_match_data_create_from_pattern(regex->code, ctx);
607
22.4k
    }
608
609
1.08k
    return pcre2_match_data_create(0, ctx);
610
611
#else
612
613
    size_t                  size;
614
    njs_uint_t              ncaptures;
615
    njs_regex_match_data_t  *match_data;
616
617
    if (regex != NULL) {
618
        ncaptures = regex->ncaptures - 1;
619
620
    } else {
621
        ncaptures = 0;
622
    }
623
624
    /* Each capture is stored in 3 "int" vector elements. */
625
    ncaptures *= 3;
626
    size = sizeof(njs_regex_match_data_t) + ncaptures * sizeof(int);
627
628
    match_data = ctx->private_malloc(size, ctx->memory_data);
629
630
    if (njs_fast_path(match_data != NULL)) {
631
        match_data->ncaptures = ncaptures + 3;
632
    }
633
634
    return match_data;
635
636
#endif
637
23.5k
}
638
639
640
void
641
njs_regex_match_data_free(njs_regex_match_data_t *match_data,
642
    njs_regex_generic_ctx_t *ctx)
643
22.4k
{
644
22.4k
#ifdef NJS_HAVE_PCRE2
645
646
22.4k
    pcre2_match_data_free(match_data);
647
648
#else
649
650
    ctx->private_free(match_data, ctx->memory_data);
651
652
#endif
653
22.4k
}
654
655
656
njs_int_t
657
njs_regex_match(njs_regex_t *regex, const u_char *subject, size_t off,
658
    size_t len, njs_regex_match_data_t *match_data, njs_trace_t *trace)
659
22.4k
{
660
22.4k
#ifdef NJS_HAVE_PCRE2
661
662
22.4k
    int     ret;
663
22.4k
    u_char  errstr[128];
664
665
22.4k
    ret = pcre2_match(regex->code, subject, len, off, 0, match_data, NULL);
666
667
22.4k
    if (ret < 0) {
668
19.3k
        if (ret == PCRE2_ERROR_NOMATCH) {
669
19.3k
            return NJS_DECLINED;
670
19.3k
        }
671
672
2
        njs_alert(trace, NJS_LEVEL_ERROR, "pcre2_match() failed: %s",
673
2
                  njs_regex_pcre2_error(ret, errstr));
674
2
        return NJS_ERROR;
675
19.3k
    }
676
677
3.16k
    return ret;
678
679
#else
680
681
    int  ret;
682
683
    ret = pcre_exec(regex->code, regex->extra, (const char *) subject, len,
684
                    off, 0, match_data->captures, match_data->ncaptures);
685
686
    if (ret <= PCRE_ERROR_NOMATCH) {
687
        if (ret == PCRE_ERROR_NOMATCH) {
688
            return NJS_DECLINED;
689
        }
690
691
        njs_alert(trace, NJS_LEVEL_ERROR, "pcre_exec() failed: %d", ret);
692
        return NJS_ERROR;
693
    }
694
695
    return ret;
696
697
#endif
698
22.4k
}
699
700
701
size_t
702
njs_regex_capture(njs_regex_match_data_t *match_data, njs_uint_t n)
703
14.3k
{
704
14.3k
#ifdef NJS_HAVE_PCRE2
705
706
14.3k
    size_t  c;
707
708
14.3k
    c = pcre2_get_ovector_pointer(match_data)[n];
709
710
14.3k
    if (c == PCRE2_UNSET) {
711
1.69k
        return NJS_REGEX_UNSET;
712
1.69k
    }
713
714
12.6k
    return c;
715
716
#else
717
718
    return match_data->captures[n];
719
720
#endif
721
14.3k
}
722
723
#ifdef NJS_HAVE_PCRE2
724
725
static const u_char *
726
njs_regex_pcre2_error(int errcode, u_char buffer[128])
727
30.5k
{
728
30.5k
    pcre2_get_error_message(errcode, buffer, 128);
729
730
30.5k
    return buffer;
731
30.5k
}
732
733
#else
734
735
static void *
736
njs_pcre_malloc(size_t size)
737
{
738
    return regex_context->private_malloc(size, regex_context->memory_data);
739
}
740
741
742
static void
743
njs_pcre_free(void *p)
744
{
745
    regex_context->private_free(p, regex_context->memory_data);
746
}
747
748
#endif
749