Coverage Report

Created: 2023-03-26 06:28

/src/httpd/server/util_pcre.c
Line
Count
Source (jump to first uncovered line)
1
/* Licensed to the Apache Software Foundation (ASF) under one or more
2
 * contributor license agreements.  See the NOTICE file distributed with
3
 * this work for additional information regarding copyright ownership.
4
 * The ASF licenses this file to You under the Apache License, Version 2.0
5
 * (the "License"); you may not use this file except in compliance with
6
 * the License.  You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
17
/* This code is based on pcreposix.c from the PCRE Library distribution,
18
 * as originally written by Philip Hazel <ph10@cam.ac.uk>, and forked by
19
 * the Apache HTTP Server project to provide POSIX-style regex function
20
 * wrappers around underlying PCRE library functions for httpd.
21
 * 
22
 * The original source file pcreposix.c is copyright and licensed as follows;
23
24
           Copyright (c) 1997-2004 University of Cambridge
25
26
-----------------------------------------------------------------------------
27
Redistribution and use in source and binary forms, with or without
28
modification, are permitted provided that the following conditions are met:
29
30
    * Redistributions of source code must retain the above copyright notice,
31
      this list of conditions and the following disclaimer.
32
33
    * Redistributions in binary form must reproduce the above copyright
34
      notice, this list of conditions and the following disclaimer in the
35
      documentation and/or other materials provided with the distribution.
36
37
    * Neither the name of the University of Cambridge nor the names of its
38
      contributors may be used to endorse or promote products derived from
39
      this software without specific prior written permission.
40
41
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
42
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
43
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
44
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
45
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
46
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
47
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
48
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
49
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
50
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
51
POSSIBILITY OF SUCH DAMAGE.
52
-----------------------------------------------------------------------------
53
*/
54
55
#include "httpd.h"
56
#include "apr_strings.h"
57
#include "apr_tables.h"
58
59
#ifdef HAVE_PCRE2
60
#define PCRE2_CODE_UNIT_WIDTH 8
61
#include "pcre2.h"
62
0
#define PCREn(x) PCRE2_ ## x
63
#else
64
#include "pcre.h"
65
#define PCREn(x) PCRE_ ## x
66
#endif
67
68
/* PCRE_DUPNAMES is only present since version 6.7 of PCRE */
69
#if !defined(PCRE_DUPNAMES) && !defined(HAVE_PCRE2)
70
#error PCRE Version 6.7 or later required!
71
#else
72
73
#define APR_WANT_STRFUNC
74
#include "apr_want.h"
75
76
#ifndef POSIX_MALLOC_THRESHOLD
77
#define POSIX_MALLOC_THRESHOLD (10)
78
#endif
79
80
#ifdef HAVE_PCRE2
81
/* Reserve 128 bytes for the PCRE2 structs, that is (a bit above):
82
 *   sizeof(pcre2_general_context) + offsetof(pcre2_match_data, ovector)
83
 */
84
#define AP_PCRE_STACKBUF_SIZE \
85
    APR_ALIGN_DEFAULT(128 + POSIX_MALLOC_THRESHOLD * sizeof(PCRE2_SIZE) * 2)
86
#else
87
#define AP_PCRE_STACKBUF_SIZE \
88
    APR_ALIGN_DEFAULT(POSIX_MALLOC_THRESHOLD * sizeof(int) * 3)
89
#endif
90
91
/* Table of error strings corresponding to POSIX error codes; must be
92
 * kept in synch with include/ap_regex.h's AP_REG_E* definitions.
93
 */
94
95
static const char *const pstring[] = {
96
    "",                         /* Dummy for value 0 */
97
    "internal error",           /* AP_REG_ASSERT */
98
    "failed to get memory",     /* AP_REG_ESPACE */
99
    "bad argument",             /* AP_REG_INVARG */
100
    "match failed"              /* AP_REG_NOMATCH */
101
};
102
103
AP_DECLARE(const char *) ap_pcre_version_string(int which)
104
0
{
105
0
#ifdef HAVE_PCRE2
106
0
    static char buf[80];
107
0
#endif
108
0
    switch (which) {
109
0
    case AP_REG_PCRE_COMPILED:
110
0
        return APR_STRINGIFY(PCREn(MAJOR)) "." APR_STRINGIFY(PCREn(MINOR)) " " APR_STRINGIFY(PCREn(DATE));
111
0
    case AP_REG_PCRE_LOADED:
112
0
#ifdef HAVE_PCRE2
113
0
        pcre2_config(PCRE2_CONFIG_VERSION, buf);
114
0
        return buf;
115
#else
116
        return pcre_version();
117
#endif
118
0
    default:
119
0
        return "Unknown";
120
0
    }
121
0
}
122
123
AP_DECLARE(apr_size_t) ap_regerror(int errcode, const ap_regex_t *preg,
124
                                   char *errbuf, apr_size_t errbuf_size)
125
0
{
126
0
    const char *message, *addmessage;
127
0
    apr_size_t length, addlength;
128
129
0
    message = (errcode >= (int)(sizeof(pstring) / sizeof(char *))) ?
130
0
              "unknown error code" : pstring[errcode];
131
0
    length = strlen(message) + 1;
132
133
0
    addmessage = " at offset ";
134
0
    addlength = (preg != NULL && (int)preg->re_erroffset != -1) ?
135
0
                strlen(addmessage) + 6 : 0;
136
137
0
    if (errbuf_size > 0) {
138
0
        if (addlength > 0 && errbuf_size >= length + addlength)
139
0
            apr_snprintf(errbuf, errbuf_size, "%s%s%-6d", message, addmessage,
140
0
                         (int)preg->re_erroffset);
141
0
        else
142
0
            apr_cpystrn(errbuf, message, errbuf_size);
143
0
    }
144
145
0
    return length + addlength;
146
0
}
147
148
149
150
151
/*************************************************
152
 *           Free store held by a regex          *
153
 *************************************************/
154
155
AP_DECLARE(void) ap_regfree(ap_regex_t *preg)
156
0
{
157
0
#ifdef HAVE_PCRE2
158
0
    pcre2_code_free(preg->re_pcre);
159
#else
160
    (pcre_free)(preg->re_pcre);
161
#endif
162
0
}
163
164
165
166
167
/*************************************************
168
 *            Compile a regular expression       *
169
 *************************************************/
170
171
static int default_cflags = AP_REG_DEFAULT;
172
173
AP_DECLARE(int) ap_regcomp_get_default_cflags(void)
174
0
{
175
0
    return default_cflags;
176
0
}
177
178
AP_DECLARE(void) ap_regcomp_set_default_cflags(int cflags)
179
0
{
180
0
    default_cflags = cflags;
181
0
}
182
183
AP_DECLARE(int) ap_regcomp_default_cflag_by_name(const char *name)
184
0
{
185
0
    int cflag = 0;
186
187
0
    if (ap_cstr_casecmp(name, "ICASE") == 0) {
188
0
        cflag = AP_REG_ICASE;
189
0
    }
190
0
    else if (ap_cstr_casecmp(name, "DOTALL") == 0) {
191
0
        cflag = AP_REG_DOTALL;
192
0
    }
193
0
    else if (ap_cstr_casecmp(name, "DOLLAR_ENDONLY") == 0) {
194
0
        cflag = AP_REG_DOLLAR_ENDONLY;
195
0
    }
196
0
    else if (ap_cstr_casecmp(name, "EXTENDED") == 0) {
197
0
        cflag = AP_REG_EXTENDED;
198
0
    }
199
200
0
    return cflag;
201
0
}
202
203
/*
204
 * Arguments:
205
 *  preg        points to a structure for recording the compiled expression
206
 *  pattern     the pattern to compile
207
 *  cflags      compilation flags
208
 *
209
 * Returns:      0 on success
210
 *               various non-zero codes on failure
211
*/
212
AP_DECLARE(int) ap_regcomp(ap_regex_t * preg, const char *pattern, int cflags)
213
0
{
214
0
#ifdef HAVE_PCRE2
215
0
    uint32_t capcount;
216
0
    PCRE2_SIZE erroffset;
217
#else
218
    const char *errorptr;
219
    int erroffset;
220
#endif
221
0
    int errcode = 0;
222
0
    int options = PCREn(DUPNAMES);
223
224
0
    if ((cflags & AP_REG_NO_DEFAULT) == 0)
225
0
        cflags |= default_cflags;
226
227
0
    if ((cflags & AP_REG_ICASE) != 0)
228
0
        options |= PCREn(CASELESS);
229
0
    if ((cflags & AP_REG_NEWLINE) != 0)
230
0
        options |= PCREn(MULTILINE);
231
0
    if ((cflags & AP_REG_DOTALL) != 0)
232
0
        options |= PCREn(DOTALL);
233
0
    if ((cflags & AP_REG_DOLLAR_ENDONLY) != 0)
234
0
        options |= PCREn(DOLLAR_ENDONLY);
235
236
0
#ifdef HAVE_PCRE2
237
0
    preg->re_pcre = pcre2_compile((const unsigned char *)pattern,
238
0
                                  PCRE2_ZERO_TERMINATED, options, &errcode,
239
0
                                  &erroffset, NULL);
240
#else
241
    preg->re_pcre = pcre_compile2(pattern, options, &errcode,
242
                                  &errorptr, &erroffset, NULL);
243
#endif
244
245
0
    preg->re_erroffset = erroffset;
246
0
    if (preg->re_pcre == NULL) {
247
        /* Internal ERR21 is "failed to get memory" according to pcreapi(3) */
248
0
        if (errcode == 21)
249
0
            return AP_REG_ESPACE;
250
0
        return AP_REG_INVARG;
251
0
    }
252
253
0
#ifdef HAVE_PCRE2
254
0
    pcre2_pattern_info((const pcre2_code *)preg->re_pcre,
255
0
                       PCRE2_INFO_CAPTURECOUNT, &capcount);
256
0
    preg->re_nsub = capcount;
257
#else
258
    pcre_fullinfo((const pcre *)preg->re_pcre, NULL,
259
                  PCRE_INFO_CAPTURECOUNT, &(preg->re_nsub));
260
#endif
261
0
    return 0;
262
0
}
263
264
265
266
267
/*************************************************
268
 *              Match a regular expression       *
269
 *************************************************/
270
271
/* Unfortunately, PCRE1 requires 3 ints of working space for each captured
272
 * substring, so we have to get and release working store instead of just using
273
 * the POSIX structures as was done in earlier releases when PCRE needed only 2
274
 * ints. However, if the number of possible capturing brackets is small, use a
275
 * block of store on the stack, to reduce the use of malloc/free. The threshold
276
 * is in POSIX_MALLOC_THRESHOLD macro that can be changed at configure time.
277
 * PCRE2 takes an opaque match context and lets us provide the callbacks to
278
 * manage the memory needed during the match, so we can still use a small stack
279
 * space that will suffice for the match context struct and a single frame of
280
 * POSIX_MALLOC_THRESHOLD captures, above that either use a thread local
281
 * subpool cache (#if AP_HAS_THREAD_LOCAL) or fall back to malloc()/free().
282
 */
283
284
#if AP_HAS_THREAD_LOCAL && !defined(APREG_NO_THREAD_LOCAL)
285
#define APREG_USE_THREAD_LOCAL 1
286
#else
287
#define APREG_USE_THREAD_LOCAL 0
288
#endif
289
290
#ifdef HAVE_PCRE2
291
typedef PCRE2_SIZE* match_vector_pt;
292
#else
293
typedef int*        match_vector_pt;
294
#endif
295
296
#if APREG_USE_THREAD_LOCAL
297
static AP_THREAD_LOCAL apr_pool_t *thread_pool;
298
#endif
299
300
struct match_data_state {
301
    /* keep first, struct aligned */
302
    char buf[AP_PCRE_STACKBUF_SIZE];
303
    apr_size_t buf_used;
304
305
#if APREG_USE_THREAD_LOCAL
306
    apr_thread_t *thd;
307
    apr_pool_t *pool;
308
#endif
309
310
#ifdef HAVE_PCRE2
311
    pcre2_general_context *pcre2_ctx;
312
    pcre2_match_data* match_data;
313
#else
314
    int *match_data;
315
#endif
316
};
317
318
static void * private_malloc(size_t size, void *ctx)
319
0
{
320
0
    struct match_data_state *state = ctx;
321
322
0
    if (size <= sizeof(state->buf) - state->buf_used) {
323
0
        void *p = state->buf + state->buf_used;
324
0
        state->buf_used += APR_ALIGN_DEFAULT(size);
325
0
        return p;
326
0
    }
327
328
0
#if APREG_USE_THREAD_LOCAL
329
0
    if (state->thd) {
330
0
        apr_pool_t *pool = state->pool;
331
0
        if (pool == NULL) {
332
0
            pool = thread_pool;
333
0
            if (pool == NULL) {
334
0
                apr_pool_create(&pool, apr_thread_pool_get(state->thd));
335
0
                thread_pool = pool;
336
0
            }
337
0
            state->pool = pool;
338
0
        }
339
0
        return apr_palloc(pool, size);
340
0
    }
341
0
#endif
342
343
0
    return malloc(size);
344
0
}
345
346
static void private_free(void *block, void *ctx)
347
0
{
348
0
    struct match_data_state *state = ctx;
349
0
    char *p = block;
350
351
0
    if (p >= state->buf && p < state->buf + sizeof(state->buf)) {
352
        /* This block allocated from stack buffer. Do nothing. */
353
0
        return;
354
0
    }
355
356
0
#if APREG_USE_THREAD_LOCAL
357
0
    if (state->thd) {
358
        /* Freed in cleanup_state() eventually. */
359
0
        return;
360
0
    }
361
0
#endif
362
363
0
    free(block);
364
0
} 
365
366
static APR_INLINE
367
int setup_state(struct match_data_state *state, apr_uint32_t ncaps)
368
0
{
369
0
    state->buf_used = 0;
370
371
0
#if APREG_USE_THREAD_LOCAL
372
0
    state->thd = ap_thread_current();
373
0
    state->pool = NULL;
374
0
#endif
375
376
0
#ifdef HAVE_PCRE2
377
0
    state->pcre2_ctx = pcre2_general_context_create(private_malloc,
378
0
                                                    private_free, state);
379
0
    if (!state->pcre2_ctx) { 
380
0
        return 0;
381
0
    }
382
383
0
    state->match_data = pcre2_match_data_create(ncaps, state->pcre2_ctx);
384
0
    if (!state->match_data) {
385
0
        pcre2_general_context_free(state->pcre2_ctx);
386
0
        return 0;
387
0
    }
388
#else
389
    if (ncaps) {
390
        state->match_data = private_malloc(ncaps * sizeof(int) * 3, state);
391
        if (!state->match_data) {
392
            return 0;
393
        }
394
    }
395
    else {
396
        /* Fine with PCRE1 */
397
        state->match_data = NULL;
398
    }
399
#endif
400
401
0
    return 1;
402
0
}
403
404
static APR_INLINE
405
void cleanup_state(struct match_data_state *state)
406
0
{
407
0
#ifdef HAVE_PCRE2
408
0
    pcre2_match_data_free(state->match_data);
409
0
    pcre2_general_context_free(state->pcre2_ctx);
410
#else
411
    if (state->match_data) {
412
        private_free(state->match_data, state);
413
    }
414
#endif
415
416
0
#if APREG_USE_THREAD_LOCAL
417
0
    if (state->pool) {
418
        /* Let the thread's pool allocator recycle or free according
419
         * to its max_free setting.
420
         */
421
0
        apr_pool_clear(state->pool);
422
0
    }
423
0
#endif
424
0
}
425
426
AP_DECLARE(int) ap_regexec(const ap_regex_t *preg, const char *string,
427
                           apr_size_t nmatch, ap_regmatch_t *pmatch,
428
                           int eflags)
429
0
{
430
0
    return ap_regexec_len(preg, string, strlen(string), nmatch, pmatch,
431
0
                          eflags);
432
0
}
433
434
AP_DECLARE(int) ap_regexec_len(const ap_regex_t *preg, const char *buff,
435
                               apr_size_t len, apr_size_t nmatch,
436
                               ap_regmatch_t *pmatch, int eflags)
437
0
{
438
0
    int rc;
439
0
    int options = 0;
440
0
    struct match_data_state state;
441
0
    match_vector_pt ovector = NULL;
442
0
    apr_uint32_t ncaps = (apr_uint32_t)preg->re_nsub + 1;
443
444
#ifndef HAVE_PCRE2
445
    /* This is fine if pcre_exec() gets a vector size smaller than the
446
     * number of capturing groups (it will treat the remaining ones as
447
     * non-capturing), but if the vector is too small to keep track of
448
     * the potential backrefs within the pattern, it will temporarily
449
     * malloc()ate the necessary space anyway. So let's provide a vector
450
     * of at least PCRE_INFO_BACKREFMAX entries (likely zero, otherwise
451
     * the vector is most likely cached already anyway).
452
     * Note that if no captures are to be used by the caller, passing an
453
     * nmatch of zero (thus forcing all groups to be non-capturing) may
454
     * allow for some optimizations and/or less recursion (stack usage)
455
     * with PCRE1, unless backrefs..
456
     */
457
    if (ncaps > nmatch) {
458
        int backrefmax = 0;
459
        pcre_fullinfo((const pcre *)preg->re_pcre, NULL,
460
                      PCRE_INFO_BACKREFMAX, &backrefmax);
461
        if (backrefmax > 0 && (apr_uint32_t)backrefmax >= nmatch) {
462
            ncaps = (apr_uint32_t)backrefmax + 1;
463
        }
464
        else {
465
            ncaps = nmatch;
466
        }
467
    }
468
#endif
469
470
0
    if (!setup_state(&state, ncaps)) {
471
0
        return AP_REG_ESPACE;
472
0
    }
473
474
0
    if ((eflags & AP_REG_NOTBOL) != 0)
475
0
        options |= PCREn(NOTBOL);
476
0
    if ((eflags & AP_REG_NOTEOL) != 0)
477
0
        options |= PCREn(NOTEOL);
478
0
    if ((eflags & AP_REG_NOTEMPTY) != 0)
479
0
        options |= PCREn(NOTEMPTY);
480
0
    if ((eflags & AP_REG_ANCHORED) != 0)
481
0
        options |= PCREn(ANCHORED);
482
483
0
#ifdef HAVE_PCRE2
484
0
    rc = pcre2_match((const pcre2_code *)preg->re_pcre,
485
0
                     (const unsigned char *)buff, len, 0, options,
486
0
                     state.match_data, NULL);
487
0
    ovector = pcre2_get_ovector_pointer(state.match_data);
488
#else
489
    ovector = state.match_data;
490
    rc = pcre_exec((const pcre *)preg->re_pcre, NULL, buff, (int)len,
491
                   0, options, ovector, ncaps * 3);
492
#endif
493
494
0
    if (rc >= 0) {
495
0
        apr_size_t n = rc, i;
496
0
        if (n == 0 || n > nmatch)
497
0
            rc = n = nmatch; /* All capture slots were filled in */
498
0
        for (i = 0; i < n; i++) {
499
0
            pmatch[i].rm_so = ovector[i * 2];
500
0
            pmatch[i].rm_eo = ovector[i * 2 + 1];
501
0
        }
502
0
        for (; i < nmatch; i++)
503
0
            pmatch[i].rm_so = pmatch[i].rm_eo = -1;
504
0
        cleanup_state(&state);
505
0
        return 0;
506
0
    }
507
0
    else {
508
0
        cleanup_state(&state);
509
0
#ifdef HAVE_PCRE2
510
0
        if (rc <= PCRE2_ERROR_UTF8_ERR1 && rc >= PCRE2_ERROR_UTF8_ERR21)
511
0
            return AP_REG_INVARG;
512
0
#endif
513
0
        switch (rc) {
514
0
        case PCREn(ERROR_NOMATCH):
515
0
            return AP_REG_NOMATCH;
516
0
        case PCREn(ERROR_NULL):
517
0
            return AP_REG_INVARG;
518
0
        case PCREn(ERROR_BADOPTION):
519
0
            return AP_REG_INVARG;
520
0
        case PCREn(ERROR_BADMAGIC):
521
0
            return AP_REG_INVARG;
522
0
        case PCREn(ERROR_NOMEMORY):
523
0
            return AP_REG_ESPACE;
524
0
#if defined(HAVE_PCRE2) || defined(PCRE_ERROR_MATCHLIMIT)
525
0
        case PCREn(ERROR_MATCHLIMIT):
526
0
            return AP_REG_ESPACE;
527
0
#endif
528
#if defined(PCRE_ERROR_UNKNOWN_NODE)
529
        case PCRE_ERROR_UNKNOWN_NODE:
530
            return AP_REG_ASSERT;
531
#endif
532
#if defined(PCRE_ERROR_BADUTF8)
533
        case PCREn(ERROR_BADUTF8):
534
            return AP_REG_INVARG;
535
#endif
536
#if defined(PCRE_ERROR_BADUTF8_OFFSET)
537
        case PCREn(ERROR_BADUTF8_OFFSET):
538
            return AP_REG_INVARG;
539
#endif
540
0
        default:
541
0
            return AP_REG_ASSERT;
542
0
        }
543
0
    }
544
0
}
545
546
AP_DECLARE(int) ap_regname(const ap_regex_t *preg,
547
                           apr_array_header_t *names, const char *prefix,
548
                           int upper)
549
0
{
550
0
    char *nametable;
551
552
0
#ifdef HAVE_PCRE2
553
0
    uint32_t namecount;
554
0
    uint32_t nameentrysize;
555
0
    uint32_t i;
556
0
    pcre2_pattern_info((const pcre2_code *)preg->re_pcre,
557
0
                       PCRE2_INFO_NAMECOUNT, &namecount);
558
0
    pcre2_pattern_info((const pcre2_code *)preg->re_pcre,
559
0
                       PCRE2_INFO_NAMEENTRYSIZE, &nameentrysize);
560
0
    pcre2_pattern_info((const pcre2_code *)preg->re_pcre,
561
0
                       PCRE2_INFO_NAMETABLE, &nametable);
562
#else
563
    int namecount;
564
    int nameentrysize;
565
    int i;
566
    pcre_fullinfo((const pcre *)preg->re_pcre, NULL,
567
                  PCRE_INFO_NAMECOUNT, &namecount);
568
    pcre_fullinfo((const pcre *)preg->re_pcre, NULL,
569
                  PCRE_INFO_NAMEENTRYSIZE, &nameentrysize);
570
    pcre_fullinfo((const pcre *)preg->re_pcre, NULL,
571
                  PCRE_INFO_NAMETABLE, &nametable);
572
#endif
573
574
0
    for (i = 0; i < namecount; i++) {
575
0
        const char *offset = nametable + i * nameentrysize;
576
0
        int capture = ((offset[0] << 8) + offset[1]);
577
0
        while (names->nelts <= capture) {
578
0
            apr_array_push(names);
579
0
        }
580
0
        if (upper || prefix) {
581
0
            char *name = ((char **) names->elts)[capture] =
582
0
                    prefix ? apr_pstrcat(names->pool, prefix, offset + 2,
583
0
                            NULL) :
584
0
                            apr_pstrdup(names->pool, offset + 2);
585
0
            if (upper) {
586
0
                ap_str_toupper(name);
587
0
            }
588
0
        }
589
0
        else {
590
0
            ((const char **)names->elts)[capture] = offset + 2;
591
0
        }
592
0
    }
593
594
0
    return namecount;
595
0
}
596
597
#endif /* PCRE_DUPNAMES defined */
598
599
/* End of pcreposix.c */