Coverage Report

Created: 2026-09-14 06:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/php-src/ext/pcre/php_pcre.c
Line
Count
Source
1
/*
2
   +----------------------------------------------------------------------+
3
   | Copyright © The PHP Group and Contributors.                          |
4
   +----------------------------------------------------------------------+
5
   | This source file is subject to the Modified BSD License that is      |
6
   | bundled with this package in the file LICENSE, and is available      |
7
   | through the World Wide Web at <https://www.php.net/license/>.        |
8
   |                                                                      |
9
   | SPDX-License-Identifier: BSD-3-Clause                                |
10
   +----------------------------------------------------------------------+
11
   | Author: Andrei Zmievski <andrei@php.net>                             |
12
   +----------------------------------------------------------------------+
13
 */
14
15
#include "php.h"
16
#include "php_ini.h"
17
#include "php_pcre.h"
18
#include "ext/standard/info.h"
19
#include "ext/standard/basic_functions.h"
20
#include "zend_smart_str.h"
21
#include "SAPI.h"
22
23
0
#define PREG_PATTERN_ORDER      1
24
0
#define PREG_SET_ORDER        2
25
626
#define PREG_OFFSET_CAPTURE     (1<<8)
26
626
#define PREG_UNMATCHED_AS_NULL    (1<<9)
27
28
0
#define PREG_SPLIT_NO_EMPTY     (1<<0)
29
0
#define PREG_SPLIT_DELIM_CAPTURE  (1<<1)
30
0
#define PREG_SPLIT_OFFSET_CAPTURE (1<<2)
31
32
0
#define PREG_GREP_INVERT      (1<<0)
33
34
#define PREG_JIT                    (1<<3)
35
36
2.22k
#define PCRE_CACHE_SIZE 4096
37
38
#ifdef HAVE_PCRE_JIT_SUPPORT
39
#define PHP_PCRE_JIT_SUPPORT 1
40
#else
41
#define PHP_PCRE_JIT_SUPPORT 0
42
#endif
43
44
char *php_pcre_version;
45
46
#include "php_pcre_arginfo.h"
47
48
struct _pcre_cache_entry {
49
  pcre2_code *re;
50
  /* Pointer is not NULL (during request) when there are named captures.
51
   * Length is equal to capture_count + 1 to account for capture group 0.
52
   * This table cache is only valid during request.
53
   * Trying to store this over multiple requests causes issues when the keys are exposed in user arrays
54
   * (see GH-17122 and GH-17132). */
55
  zend_string **subpats_table;
56
  uint32_t preg_options;
57
  uint32_t name_count;
58
  uint32_t capture_count;
59
  uint32_t compile_options;
60
  uint32_t refcount;
61
};
62
63
PHPAPI ZEND_DECLARE_MODULE_GLOBALS(pcre)
64
65
#ifdef HAVE_PCRE_JIT_SUPPORT
66
#define PCRE_JIT_STACK_MIN_SIZE (32 * 1024)
67
#define PCRE_JIT_STACK_MAX_SIZE (192 * 1024)
68
ZEND_TLS pcre2_jit_stack *jit_stack = NULL;
69
#endif
70
/* General context using (infallible) system allocator. */
71
ZEND_TLS pcre2_general_context *gctx = NULL;
72
/* These two are global per thread for now. Though it is possible to use these
73
  per pattern. Either one can copy it and use in pce, or one does no global
74
  contexts at all, but creates for every pce. */
75
ZEND_TLS pcre2_compile_context *cctx = NULL;
76
ZEND_TLS pcre2_match_context   *mctx = NULL;
77
ZEND_TLS pcre2_match_data      *mdata = NULL;
78
ZEND_TLS bool              mdata_used = 0;
79
ZEND_TLS uint8_t pcre2_init_ok = 0;
80
#if defined(ZTS) && defined(HAVE_PCRE_JIT_SUPPORT)
81
static MUTEX_T pcre_mt = NULL;
82
#define php_pcre_mutex_alloc() \
83
  if (tsrm_is_main_thread() && !pcre_mt) pcre_mt = tsrm_mutex_alloc();
84
#define php_pcre_mutex_free() \
85
  if (tsrm_is_main_thread() && pcre_mt) { tsrm_mutex_free(pcre_mt); pcre_mt = NULL; }
86
#define php_pcre_mutex_lock() tsrm_mutex_lock(pcre_mt);
87
#define php_pcre_mutex_unlock() tsrm_mutex_unlock(pcre_mt);
88
#else
89
#define php_pcre_mutex_alloc()
90
#define php_pcre_mutex_free()
91
#define php_pcre_mutex_lock()
92
#define php_pcre_mutex_unlock()
93
#endif
94
95
ZEND_TLS HashTable char_tables;
96
97
static void free_subpats_table(zend_string **subpat_names, uint32_t num_subpats);
98
99
static void php_pcre_free_char_table(zval *data)
100
0
{/*{{{*/
101
0
  void *ptr = Z_PTR_P(data);
102
0
  pefree(ptr, 1);
103
0
}/*}}}*/
104
105
static void pcre_handle_exec_error(int pcre_code) /* {{{ */
106
2.39k
{
107
2.39k
  int preg_code = 0;
108
109
2.39k
  switch (pcre_code) {
110
48
    case PCRE2_ERROR_MATCHLIMIT:
111
48
      preg_code = PHP_PCRE_BACKTRACK_LIMIT_ERROR;
112
48
      break;
113
114
0
    case PCRE2_ERROR_RECURSIONLIMIT:
115
0
      preg_code = PHP_PCRE_RECURSION_LIMIT_ERROR;
116
0
      break;
117
118
0
    case PCRE2_ERROR_BADUTFOFFSET:
119
0
      preg_code = PHP_PCRE_BAD_UTF8_OFFSET_ERROR;
120
0
      break;
121
122
#ifdef HAVE_PCRE_JIT_SUPPORT
123
    case PCRE2_ERROR_JIT_STACKLIMIT:
124
      preg_code = PHP_PCRE_JIT_STACKLIMIT_ERROR;
125
      break;
126
#endif
127
128
2.34k
    default:
129
2.34k
      if (pcre_code <= PCRE2_ERROR_UTF8_ERR1 && pcre_code >= PCRE2_ERROR_UTF8_ERR21) {
130
217
        preg_code = PHP_PCRE_BAD_UTF8_ERROR;
131
2.12k
      } else  {
132
2.12k
        preg_code = PHP_PCRE_INTERNAL_ERROR;
133
2.12k
      }
134
2.34k
      break;
135
2.39k
  }
136
137
2.39k
  PCRE_G(error_code) = preg_code;
138
2.39k
}
139
/* }}} */
140
141
static const char *php_pcre_get_error_msg(php_pcre_error_code error_code) /* {{{ */
142
0
{
143
0
  switch (error_code) {
144
0
    case PHP_PCRE_NO_ERROR:
145
0
      return "No error";
146
0
    case PHP_PCRE_INTERNAL_ERROR:
147
0
      return "Internal error";
148
0
    case PHP_PCRE_BAD_UTF8_ERROR:
149
0
      return "Malformed UTF-8 characters, possibly incorrectly encoded";
150
0
    case PHP_PCRE_BAD_UTF8_OFFSET_ERROR:
151
0
      return "The offset did not correspond to the beginning of a valid UTF-8 code point";
152
0
    case PHP_PCRE_BACKTRACK_LIMIT_ERROR:
153
0
      return "Backtrack limit exhausted";
154
0
    case PHP_PCRE_RECURSION_LIMIT_ERROR:
155
0
      return "Recursion limit exhausted";
156
157
#ifdef HAVE_PCRE_JIT_SUPPORT
158
    case PHP_PCRE_JIT_STACKLIMIT_ERROR:
159
      return "JIT stack limit exhausted";
160
#endif
161
162
0
    default:
163
0
      return "Unknown error";
164
0
  }
165
0
}
166
/* }}} */
167
168
static void php_free_pcre_cache(zval *data) /* {{{ */
169
0
{
170
0
  pcre_cache_entry *pce = (pcre_cache_entry *) Z_PTR_P(data);
171
0
  if (!pce) return;
172
0
  if (pce->subpats_table) {
173
0
    free_subpats_table(pce->subpats_table, pce->capture_count + 1);
174
0
  }
175
0
  pcre2_code_free(pce->re);
176
0
  free(pce);
177
0
}
178
/* }}} */
179
180
static void *php_pcre_malloc(PCRE2_SIZE size, void *data)
181
2.58k
{
182
2.58k
  return pemalloc(size, 1);
183
2.58k
}
184
185
static void php_pcre_free(void *block, void *data)
186
297
{
187
297
  pefree(block, 1);
188
297
}
189
190
static void *php_pcre_emalloc(PCRE2_SIZE size, void *data)
191
297k
{
192
297k
  return emalloc(size);
193
297k
}
194
195
static void php_pcre_efree(void *block, void *data)
196
298k
{
197
298k
  efree(block);
198
298k
}
199
200
4.53k
#define PHP_PCRE_PREALLOC_MDATA_SIZE 32
201
202
static void php_pcre_init_pcre2(uint8_t jit)
203
16
{/*{{{*/
204
16
  if (!gctx) {
205
16
    gctx = pcre2_general_context_create(php_pcre_malloc, php_pcre_free, NULL);
206
16
    if (!gctx) {
207
0
      pcre2_init_ok = 0;
208
0
      return;
209
0
    }
210
16
  }
211
212
16
  if (!cctx) {
213
16
    cctx = pcre2_compile_context_create(gctx);
214
16
    if (!cctx) {
215
0
      pcre2_init_ok = 0;
216
0
      return;
217
0
    }
218
16
  }
219
220
16
  if (!mctx) {
221
16
    mctx = pcre2_match_context_create(gctx);
222
16
    if (!mctx) {
223
0
      pcre2_init_ok = 0;
224
0
      return;
225
0
    }
226
16
  }
227
228
#ifdef HAVE_PCRE_JIT_SUPPORT
229
  if (jit && !jit_stack) {
230
    jit_stack = pcre2_jit_stack_create(PCRE_JIT_STACK_MIN_SIZE, PCRE_JIT_STACK_MAX_SIZE, gctx);
231
    if (!jit_stack) {
232
      pcre2_init_ok = 0;
233
      return;
234
    }
235
  }
236
#endif
237
238
16
  if (!mdata) {
239
16
    mdata = pcre2_match_data_create(PHP_PCRE_PREALLOC_MDATA_SIZE, gctx);
240
16
    if (!mdata) {
241
0
      pcre2_init_ok = 0;
242
0
      return;
243
0
    }
244
16
  }
245
246
16
  pcre2_init_ok = 1;
247
16
}/*}}}*/
248
249
static void php_pcre_shutdown_pcre2(void)
250
0
{/*{{{*/
251
0
  if (gctx) {
252
0
    pcre2_general_context_free(gctx);
253
0
    gctx = NULL;
254
0
  }
255
256
0
  if (cctx) {
257
0
    pcre2_compile_context_free(cctx);
258
0
    cctx = NULL;
259
0
  }
260
261
0
  if (mctx) {
262
0
    pcre2_match_context_free(mctx);
263
0
    mctx = NULL;
264
0
  }
265
266
#ifdef HAVE_PCRE_JIT_SUPPORT
267
  /* Stack may only be destroyed when no cached patterns
268
    possibly associated with it do exist. */
269
  if (jit_stack) {
270
    pcre2_jit_stack_free(jit_stack);
271
    jit_stack = NULL;
272
  }
273
#endif
274
275
0
  if (mdata) {
276
0
    pcre2_match_data_free(mdata);
277
0
    mdata = NULL;
278
0
  }
279
280
0
  pcre2_init_ok = 0;
281
0
}/*}}}*/
282
283
static PHP_GINIT_FUNCTION(pcre) /* {{{ */
284
16
{
285
16
  php_pcre_mutex_alloc();
286
287
16
  zend_hash_init(&pcre_globals->pcre_cache, 0, NULL, php_free_pcre_cache, 1);
288
289
16
  pcre_globals->backtrack_limit = 0;
290
16
  pcre_globals->recursion_limit = 0;
291
16
  pcre_globals->error_code      = PHP_PCRE_NO_ERROR;
292
16
  ZVAL_UNDEF(&pcre_globals->unmatched_null_pair);
293
16
  ZVAL_UNDEF(&pcre_globals->unmatched_empty_pair);
294
#ifdef HAVE_PCRE_JIT_SUPPORT
295
  pcre_globals->jit = 1;
296
#endif
297
298
16
  php_pcre_init_pcre2(1);
299
16
  zend_hash_init(&char_tables, 1, NULL, php_pcre_free_char_table, 1);
300
16
}
301
/* }}} */
302
303
static PHP_GSHUTDOWN_FUNCTION(pcre) /* {{{ */
304
0
{
305
0
  zend_hash_destroy(&pcre_globals->pcre_cache);
306
307
0
  php_pcre_shutdown_pcre2();
308
0
  zend_hash_destroy(&char_tables);
309
0
  php_pcre_mutex_free();
310
0
}
311
/* }}} */
312
313
static PHP_INI_MH(OnUpdateBacktrackLimit)
314
16
{/*{{{*/
315
16
  OnUpdateLong(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage);
316
16
  if (mctx) {
317
16
    pcre2_set_match_limit(mctx, (uint32_t)PCRE_G(backtrack_limit));
318
16
  }
319
320
16
  return SUCCESS;
321
16
}/*}}}*/
322
323
static PHP_INI_MH(OnUpdateRecursionLimit)
324
16
{/*{{{*/
325
16
  OnUpdateLong(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage);
326
16
  if (mctx) {
327
16
    pcre2_set_depth_limit(mctx, (uint32_t)PCRE_G(recursion_limit));
328
16
  }
329
330
16
  return SUCCESS;
331
16
}/*}}}*/
332
333
#ifdef HAVE_PCRE_JIT_SUPPORT
334
static PHP_INI_MH(OnUpdateJit)
335
{/*{{{*/
336
  OnUpdateBool(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage);
337
  if (PCRE_G(jit) && jit_stack) {
338
    pcre2_jit_stack_assign(mctx, NULL, jit_stack);
339
  } else {
340
    pcre2_jit_stack_assign(mctx, NULL, NULL);
341
  }
342
343
  return SUCCESS;
344
}/*}}}*/
345
#endif
346
347
PHP_INI_BEGIN()
348
  STD_PHP_INI_ENTRY("pcre.backtrack_limit", "1000000", PHP_INI_ALL, OnUpdateBacktrackLimit, backtrack_limit, zend_pcre_globals, pcre_globals)
349
  STD_PHP_INI_ENTRY("pcre.recursion_limit", "100000",  PHP_INI_ALL, OnUpdateRecursionLimit, recursion_limit, zend_pcre_globals, pcre_globals)
350
#ifdef HAVE_PCRE_JIT_SUPPORT
351
  STD_PHP_INI_BOOLEAN("pcre.jit",           "1",       PHP_INI_ALL, OnUpdateJit,            jit,             zend_pcre_globals, pcre_globals)
352
#endif
353
PHP_INI_END()
354
355
static char *_pcre2_config_str(uint32_t what)
356
32
{/*{{{*/
357
32
  int len = pcre2_config(what, NULL);
358
32
  char *ret = (char *) malloc(len + 1);
359
360
32
  len = pcre2_config(what, ret);
361
32
  if (!len) {
362
0
    free(ret);
363
0
    return NULL;
364
0
  }
365
366
32
  return ret;
367
32
}/*}}}*/
368
369
/* {{{ PHP_MINFO_FUNCTION(pcre) */
370
static PHP_MINFO_FUNCTION(pcre)
371
8
{
372
#ifdef HAVE_PCRE_JIT_SUPPORT
373
  uint32_t flag = 0;
374
  char *jit_target = _pcre2_config_str(PCRE2_CONFIG_JITTARGET);
375
#endif
376
8
  char *version = _pcre2_config_str(PCRE2_CONFIG_VERSION);
377
8
  char *unicode = _pcre2_config_str(PCRE2_CONFIG_UNICODE_VERSION);
378
379
8
  php_info_print_table_start();
380
8
  php_info_print_table_row(2, "PCRE (Perl Compatible Regular Expressions) Support", "enabled" );
381
8
  php_info_print_table_row(2, "PCRE Library Version", version);
382
8
  free(version);
383
8
  php_info_print_table_row(2, "PCRE Unicode Version", unicode);
384
8
  free(unicode);
385
386
#ifdef HAVE_PCRE_JIT_SUPPORT
387
  if (!pcre2_config(PCRE2_CONFIG_JIT, &flag)) {
388
    php_info_print_table_row(2, "PCRE JIT Support", flag ? "enabled" : "disabled");
389
  } else {
390
    php_info_print_table_row(2, "PCRE JIT Support", "unknown" );
391
  }
392
  if (jit_target) {
393
    php_info_print_table_row(2, "PCRE JIT Target", jit_target);
394
  }
395
  free(jit_target);
396
#else
397
8
  php_info_print_table_row(2, "PCRE JIT Support", "not compiled in" );
398
8
#endif
399
400
#ifdef HAVE_PCRE_VALGRIND_SUPPORT
401
  php_info_print_table_row(2, "PCRE Valgrind Support", "enabled" );
402
#endif
403
404
8
  php_info_print_table_end();
405
406
8
  DISPLAY_INI_ENTRIES();
407
8
}
408
/* }}} */
409
410
/* {{{ PHP_MINIT_FUNCTION(pcre) */
411
static PHP_MINIT_FUNCTION(pcre)
412
16
{
413
#ifdef HAVE_PCRE_JIT_SUPPORT
414
  if (UNEXPECTED(!pcre2_init_ok)) {
415
    /* Retry. */
416
    php_pcre_init_pcre2(PCRE_G(jit));
417
    if (!pcre2_init_ok) {
418
      return FAILURE;
419
    }
420
  }
421
#endif
422
423
16
  REGISTER_INI_ENTRIES();
424
425
16
  php_pcre_version = _pcre2_config_str(PCRE2_CONFIG_VERSION);
426
427
16
  register_php_pcre_symbols(module_number);
428
429
16
  return SUCCESS;
430
16
}
431
/* }}} */
432
433
/* {{{ PHP_MSHUTDOWN_FUNCTION(pcre) */
434
static PHP_MSHUTDOWN_FUNCTION(pcre)
435
0
{
436
0
  UNREGISTER_INI_ENTRIES();
437
438
0
  free(php_pcre_version);
439
440
0
  return SUCCESS;
441
0
}
442
/* }}} */
443
444
/* {{{ PHP_RINIT_FUNCTION(pcre) */
445
static PHP_RINIT_FUNCTION(pcre)
446
295k
{
447
#ifdef HAVE_PCRE_JIT_SUPPORT
448
  if (UNEXPECTED(!pcre2_init_ok)) {
449
    /* Retry. */
450
    php_pcre_mutex_lock();
451
    php_pcre_init_pcre2(PCRE_G(jit));
452
    if (!pcre2_init_ok) {
453
      php_pcre_mutex_unlock();
454
      return FAILURE;
455
    }
456
    php_pcre_mutex_unlock();
457
  }
458
459
  mdata_used = 0;
460
#endif
461
462
295k
  PCRE_G(error_code) = PHP_PCRE_NO_ERROR;
463
295k
  PCRE_G(gctx_zmm) = pcre2_general_context_create(php_pcre_emalloc, php_pcre_efree, NULL);
464
295k
  if (!PCRE_G(gctx_zmm)) {
465
0
    return FAILURE;
466
0
  }
467
468
295k
  return SUCCESS;
469
295k
}
470
/* }}} */
471
472
static PHP_RSHUTDOWN_FUNCTION(pcre)
473
295k
{
474
295k
  pcre_cache_entry *pce;
475
142M
  ZEND_HASH_MAP_FOREACH_PTR(&PCRE_G(pcre_cache), pce) {
476
142M
    if (pce->subpats_table) {
477
0
      free_subpats_table(pce->subpats_table, pce->capture_count + 1);
478
0
      pce->subpats_table = NULL;
479
0
    }
480
142M
  } ZEND_HASH_FOREACH_END();
481
482
295k
  pcre2_general_context_free(PCRE_G(gctx_zmm));
483
295k
  PCRE_G(gctx_zmm) = NULL;
484
485
295k
  zval_ptr_dtor(&PCRE_G(unmatched_null_pair));
486
295k
  zval_ptr_dtor(&PCRE_G(unmatched_empty_pair));
487
295k
  ZVAL_UNDEF(&PCRE_G(unmatched_null_pair));
488
295k
  ZVAL_UNDEF(&PCRE_G(unmatched_empty_pair));
489
295k
  return SUCCESS;
490
295k
}
491
492
/* {{{ static pcre_clean_cache */
493
static int pcre_clean_cache(zval *data, void *arg)
494
0
{
495
0
  pcre_cache_entry *pce = (pcre_cache_entry *) Z_PTR_P(data);
496
0
  int *num_clean = (int *)arg;
497
498
0
  if (!pce->refcount) {
499
0
    if (--(*num_clean) == 0) {
500
0
      return ZEND_HASH_APPLY_REMOVE|ZEND_HASH_APPLY_STOP;
501
0
    }
502
0
    return ZEND_HASH_APPLY_REMOVE;
503
0
  } else {
504
0
    return ZEND_HASH_APPLY_KEEP;
505
0
  }
506
0
}
507
/* }}} */
508
509
0
static void free_subpats_table(zend_string **subpat_names, uint32_t num_subpats) {
510
0
  uint32_t i;
511
0
  for (i = 0; i < num_subpats; i++) {
512
0
    if (subpat_names[i]) {
513
0
      zend_string_release_ex(subpat_names[i], false);
514
0
    }
515
0
  }
516
0
  efree(subpat_names);
517
0
}
518
519
/* {{{ static make_subpats_table */
520
static zend_string **make_subpats_table(uint32_t name_cnt, pcre_cache_entry *pce)
521
0
{
522
0
  uint32_t num_subpats = pce->capture_count + 1;
523
0
  uint32_t name_size, ni = 0;
524
0
  char *name_table;
525
0
  zend_string **subpat_names;
526
0
  int rc1, rc2;
527
528
0
  rc1 = pcre2_pattern_info(pce->re, PCRE2_INFO_NAMETABLE, &name_table);
529
0
  rc2 = pcre2_pattern_info(pce->re, PCRE2_INFO_NAMEENTRYSIZE, &name_size);
530
0
  if (rc1 < 0 || rc2 < 0) {
531
0
    php_error_docref(NULL, E_WARNING, "Internal pcre2_pattern_info() error %d", rc1 < 0 ? rc1 : rc2);
532
0
    return NULL;
533
0
  }
534
535
0
  subpat_names = ecalloc(num_subpats, sizeof(zend_string *));
536
0
  while (ni++ < name_cnt) {
537
0
    unsigned short name_idx = 0x100 * (unsigned char)name_table[0] + (unsigned char)name_table[1];
538
0
    const char *name = name_table + 2;
539
0
    subpat_names[name_idx] = zend_string_init(name, strlen(name), false);
540
0
    name_table += name_size;
541
0
  }
542
0
  return subpat_names;
543
0
}
544
/* }}} */
545
546
static zend_string **ensure_subpats_table(uint32_t name_cnt, pcre_cache_entry *pce)
547
0
{
548
0
  if (!pce->subpats_table) {
549
0
    pce->subpats_table = make_subpats_table(name_cnt, pce);
550
0
  }
551
0
  return pce->subpats_table;
552
0
}
553
554
/* {{{ static calculate_unit_length */
555
/* Calculates the byte length of the next character. Assumes valid UTF-8 for PCRE2_UTF. */
556
static zend_always_inline size_t calculate_unit_length(pcre_cache_entry *pce, const char *start)
557
640
{
558
640
  size_t unit_len;
559
560
640
  if (pce->compile_options & PCRE2_UTF) {
561
35
    const char *end = start;
562
563
    /* skip continuation bytes */
564
35
    while ((*++end & 0xC0) == 0x80);
565
35
    unit_len = end - start;
566
605
  } else {
567
605
    unit_len = 1;
568
605
  }
569
640
  return unit_len;
570
640
}
571
/* }}} */
572
573
/* {{{ pcre_get_compiled_regex_cache */
574
PHPAPI pcre_cache_entry* pcre_get_compiled_regex_cache_ex(zend_string *regex, bool locale_aware)
575
7.63k
{
576
7.63k
  pcre2_code      *re = NULL;
577
#if 10 == PCRE2_MAJOR && 37 == PCRE2_MINOR && !defined(HAVE_BUNDLED_PCRE)
578
  uint32_t       coptions = PCRE2_NO_START_OPTIMIZE;
579
#else
580
7.63k
  uint32_t       coptions = 0;
581
7.63k
#endif
582
7.63k
  uint32_t       eoptions = 0;
583
7.63k
  PCRE2_UCHAR           error[128];
584
7.63k
  PCRE2_SIZE           erroffset;
585
7.63k
  int                  errnumber;
586
7.63k
  char         delimiter;
587
7.63k
  char         start_delimiter;
588
7.63k
  char         end_delimiter;
589
7.63k
  char        *p, *pp;
590
7.63k
  char        *pattern;
591
7.63k
  size_t         pattern_len;
592
7.63k
  uint32_t       poptions = 0;
593
7.63k
  const uint8_t       *tables = NULL;
594
7.63k
  zval                *zv;
595
7.63k
  pcre_cache_entry   new_entry;
596
7.63k
  int          rc;
597
7.63k
  zend_string     *key;
598
7.63k
  pcre_cache_entry  *ret;
599
600
7.63k
  if (locale_aware && BG(ctype_string)) {
601
0
    key = zend_string_concat2(
602
0
      ZSTR_VAL(BG(ctype_string)), ZSTR_LEN(BG(ctype_string)),
603
0
      ZSTR_VAL(regex), ZSTR_LEN(regex));
604
7.63k
  } else {
605
7.63k
    key = regex;
606
7.63k
  }
607
608
  /* Try to lookup the cached regex entry, and if successful, just pass
609
     back the compiled pattern, otherwise go on and compile it. */
610
7.63k
  zv = zend_hash_find(&PCRE_G(pcre_cache), key);
611
7.63k
  if (zv) {
612
3.31k
    if (key != regex) {
613
0
      zend_string_release_ex(key, 0);
614
0
    }
615
3.31k
    return (pcre_cache_entry*)Z_PTR_P(zv);
616
3.31k
  }
617
618
4.31k
  p = ZSTR_VAL(regex);
619
4.31k
  const char* end_p = ZSTR_VAL(regex) + ZSTR_LEN(regex);
620
621
  /* Parse through the leading whitespace, and display a warning if we
622
     get to the end without encountering a delimiter. */
623
4.31k
  while (isspace((unsigned char)*p)) p++;
624
4.31k
  if (p >= end_p) {
625
6
    if (key != regex) {
626
0
      zend_string_release_ex(key, 0);
627
0
    }
628
6
    php_error_docref(NULL, E_WARNING, "Empty regular expression");
629
6
    pcre_handle_exec_error(PCRE2_ERROR_INTERNAL);
630
6
    return NULL;
631
6
  }
632
633
  /* Get the delimiter and display a warning if it is alphanumeric
634
     or a backslash. */
635
4.31k
  delimiter = *p++;
636
4.31k
  if (isalnum((unsigned char)delimiter) || delimiter == '\\' || delimiter == '\0') {
637
35
    if (key != regex) {
638
0
      zend_string_release_ex(key, 0);
639
0
    }
640
35
    php_error_docref(NULL, E_WARNING, "Delimiter must not be alphanumeric, backslash, or NUL byte");
641
35
    pcre_handle_exec_error(PCRE2_ERROR_INTERNAL);
642
35
    return NULL;
643
35
  }
644
645
4.27k
  start_delimiter = delimiter;
646
4.27k
  if ((pp = strchr("([{< )]}> )]}>", delimiter)))
647
77
    delimiter = pp[5];
648
4.27k
  end_delimiter = delimiter;
649
650
4.27k
  pp = p;
651
652
4.27k
  if (start_delimiter == end_delimiter) {
653
    /* We need to iterate through the pattern, searching for the ending delimiter,
654
       but skipping the backslashed delimiters.  If the ending delimiter is not
655
       found, display a warning. */
656
1.45M
    while (pp < end_p) {
657
1.45M
      if (*pp == '\\' && pp + 1 < end_p) pp++;
658
1.39M
      else if (*pp == delimiter)
659
4.14k
        break;
660
1.45M
      pp++;
661
1.45M
    }
662
4.21k
  } else {
663
    /* We iterate through the pattern, searching for the matching ending
664
     * delimiter. For each matching starting delimiter, we increment nesting
665
     * level, and decrement it for each matching ending delimiter. If we
666
     * reach the end of the pattern without matching, display a warning.
667
     */
668
63
    int brackets = 1;   /* brackets nesting level */
669
20.1k
    while (pp < end_p) {
670
20.1k
      if (*pp == '\\' && pp + 1 < end_p) pp++;
671
19.5k
      else if (*pp == end_delimiter && --brackets <= 0)
672
8
        break;
673
19.4k
      else if (*pp == start_delimiter)
674
924
        brackets++;
675
20.1k
      pp++;
676
20.1k
    }
677
63
  }
678
679
4.27k
  if (pp >= end_p) {
680
124
    if (key != regex) {
681
0
      zend_string_release_ex(key, 0);
682
0
    }
683
124
    if (start_delimiter == end_delimiter) {
684
69
      php_error_docref(NULL,E_WARNING, "No ending delimiter '%c' found", delimiter);
685
69
    } else {
686
55
      php_error_docref(NULL,E_WARNING, "No ending matching delimiter '%c' found", delimiter);
687
55
    }
688
124
    pcre_handle_exec_error(PCRE2_ERROR_INTERNAL);
689
124
    return NULL;
690
124
  }
691
692
  /* Make a copy of the actual pattern. */
693
4.15k
  pattern_len = pp - p;
694
4.15k
  pattern = estrndup(p, pattern_len);
695
696
  /* Move on to the options */
697
4.15k
  pp++;
698
699
  /* Parse through the options, setting appropriate flags.  Display
700
     a warning if we encounter an unknown modifier. */
701
9.53k
  while (pp < end_p) {
702
5.75k
    switch (*pp++) {
703
      /* Perl compatible options */
704
1.65k
      case 'i': coptions |= PCRE2_CASELESS;   break;
705
394
      case 'm': coptions |= PCRE2_MULTILINE;   break;
706
45
      case 'n': coptions |= PCRE2_NO_AUTO_CAPTURE; break;
707
305
      case 's': coptions |= PCRE2_DOTALL;   break;
708
74
      case 'x': coptions |= PCRE2_EXTENDED;   break;
709
710
      /* PCRE specific options */
711
388
      case 'A': coptions |= PCRE2_ANCHORED;   break;
712
3
      case 'D': coptions |= PCRE2_DOLLAR_ENDONLY;break;
713
0
#ifdef PCRE2_EXTRA_CASELESS_RESTRICT
714
30
      case 'r': eoptions |= PCRE2_EXTRA_CASELESS_RESTRICT; break;
715
0
#endif
716
5
      case 'S': /* Pass. */         break;
717
50
      case 'X': /* Pass. */         break;
718
506
      case 'U': coptions |= PCRE2_UNGREEDY;   break;
719
1.36k
      case 'u': coptions |= PCRE2_UTF;
720
  /* In  PCRE,  by  default, \d, \D, \s, \S, \w, and \W recognize only ASCII
721
     characters, even in UTF-8 mode. However, this can be changed by setting
722
     the PCRE2_UCP option. */
723
1.36k
#ifdef PCRE2_UCP
724
1.36k
            coptions |= PCRE2_UCP;
725
1.36k
#endif
726
            /* The \C escape sequence is unsafe in PCRE2_UTF mode */
727
1.36k
            coptions |= PCRE2_NEVER_BACKSLASH_C;
728
1.36k
        break;
729
117
      case 'J': coptions |= PCRE2_DUPNAMES;   break;
730
731
57
      case ' ':
732
369
      case '\n':
733
450
      case '\r':
734
450
        break;
735
736
2
      case 'e': /* legacy eval */
737
372
      default:
738
372
        if (pp[-1]) {
739
363
          php_error_docref(NULL, E_WARNING, "Unknown modifier '%c'", pp[-1]);
740
363
        } else {
741
9
          php_error_docref(NULL, E_WARNING, "NUL byte is not a valid modifier");
742
9
        }
743
372
        pcre_handle_exec_error(PCRE2_ERROR_INTERNAL);
744
372
        efree(pattern);
745
372
        if (key != regex) {
746
0
          zend_string_release_ex(key, 0);
747
0
        }
748
372
        return NULL;
749
5.75k
    }
750
5.75k
  }
751
752
3.77k
  if (key != regex) {
753
0
    zv = zend_hash_str_lookup(&char_tables, ZSTR_VAL(BG(ctype_string)), ZSTR_LEN(BG(ctype_string)));
754
0
    if (Z_ISNULL_P(zv)) {
755
0
      tables = pcre2_maketables(gctx);
756
0
      if (UNEXPECTED(!tables)) {
757
        /* Remove the placeholder entry created by zend_hash_str_lookup(),
758
         * set ptr to NULL first so the destructor (pefree) is safe. */
759
0
        ZVAL_PTR(zv, NULL);
760
0
        zend_hash_str_del(&char_tables, ZSTR_VAL(BG(ctype_string)), ZSTR_LEN(BG(ctype_string)));
761
0
        php_error_docref(NULL,E_WARNING, "Failed to generate locale character tables");
762
0
        pcre_handle_exec_error(PCRE2_ERROR_NOMEMORY);
763
0
        zend_string_release_ex(key, 0);
764
0
        efree(pattern);
765
0
        return NULL;
766
0
      }
767
0
      ZVAL_PTR(zv, (void *)tables);
768
0
    } else {
769
0
      tables = Z_PTR_P(zv);
770
0
    }
771
0
  }
772
3.77k
  pcre2_set_character_tables(cctx, tables);
773
774
3.77k
  pcre2_set_compile_extra_options(cctx, eoptions);
775
776
  /* Compile pattern and display a warning if compilation failed. */
777
3.77k
  re = pcre2_compile((PCRE2_SPTR)pattern, pattern_len, coptions, &errnumber, &erroffset, cctx);
778
779
3.77k
  if (re == NULL) {
780
1.55k
    if (key != regex) {
781
0
      zend_string_release_ex(key, 0);
782
0
    }
783
1.55k
    const char *err_msg = (const char*) error;
784
1.55k
    if (errnumber == PCRE2_ERROR_BACKSLASH_C_CALLER_DISABLED) {
785
55
      err_msg = "using \\C is incompatible with the 'u' modifier";
786
1.50k
    } else {
787
1.50k
      pcre2_get_error_message(errnumber, error, sizeof(error));
788
1.50k
    }
789
1.55k
    php_error_docref(NULL,E_WARNING, "Compilation failed: %s at offset %zu", err_msg, erroffset);
790
1.55k
    pcre_handle_exec_error(PCRE2_ERROR_INTERNAL);
791
1.55k
    efree(pattern);
792
1.55k
    return NULL;
793
1.55k
  }
794
795
#ifdef HAVE_PCRE_JIT_SUPPORT
796
  if (PCRE_G(jit)) {
797
    /* Enable PCRE JIT compiler */
798
    rc = pcre2_jit_compile(re, PCRE2_JIT_COMPLETE);
799
    if (EXPECTED(rc >= 0)) {
800
      size_t jit_size = 0;
801
      if (!pcre2_pattern_info(re, PCRE2_INFO_JITSIZE, &jit_size) && jit_size > 0) {
802
        poptions |= PREG_JIT;
803
      }
804
    } else if (rc == PCRE2_ERROR_NOMEMORY) {
805
      php_error_docref(NULL, E_WARNING,
806
        "Allocation of JIT memory failed, PCRE JIT will be disabled. "
807
        "This is likely caused by security restrictions. "
808
        "Either grant PHP permission to allocate executable memory, or set pcre.jit=0");
809
      PCRE_G(jit) = 0;
810
    } else {
811
      pcre2_get_error_message(rc, error, sizeof(error));
812
      php_error_docref(NULL, E_WARNING, "JIT compilation failed: %s", error);
813
      pcre_handle_exec_error(PCRE2_ERROR_INTERNAL);
814
    }
815
  }
816
#endif
817
2.22k
  efree(pattern);
818
819
  /*
820
   * If we reached cache limit, clean out the items from the head of the list;
821
   * these are supposedly the oldest ones (but not necessarily the least used
822
   * ones).
823
   */
824
2.22k
  if (zend_hash_num_elements(&PCRE_G(pcre_cache)) == PCRE_CACHE_SIZE) {
825
0
    int num_clean = PCRE_CACHE_SIZE / 8;
826
0
    zend_hash_apply_with_argument(&PCRE_G(pcre_cache), pcre_clean_cache, &num_clean);
827
0
  }
828
829
  /* Store the compiled pattern and extra info in the cache. */
830
2.22k
  new_entry.re = re;
831
2.22k
  new_entry.preg_options = poptions;
832
2.22k
  new_entry.compile_options = coptions;
833
2.22k
  new_entry.refcount = 0;
834
2.22k
  new_entry.subpats_table = NULL;
835
836
2.22k
  if ((rc = pcre2_pattern_info(re, PCRE2_INFO_CAPTURECOUNT, &new_entry.capture_count)) < 0 ||
837
2.22k
      (rc = pcre2_pattern_info(re, PCRE2_INFO_NAMECOUNT, &new_entry.name_count)) < 0) {
838
0
    if (key != regex) {
839
0
      zend_string_release_ex(key, 0);
840
0
    }
841
0
    pcre2_code_free(new_entry.re);
842
0
    php_error_docref(NULL, E_WARNING, "Internal pcre_pattern_info() error %d", rc);
843
0
    pcre_handle_exec_error(PCRE2_ERROR_INTERNAL);
844
0
    return NULL;
845
0
  }
846
847
  /*
848
   * Interned strings are not duplicated when stored in HashTable,
849
   * but all the interned strings created during HTTP request are removed
850
   * at end of request. However PCRE_G(pcre_cache) must be consistent
851
   * on the next request as well. So we disable usage of interned strings
852
   * as hash keys especually for this table.
853
   * See bug #63180
854
   */
855
2.22k
  if (!(GC_FLAGS(key) & IS_STR_PERMANENT)) {
856
1.29k
    zend_string *str = zend_string_init(ZSTR_VAL(key), ZSTR_LEN(key), 1);
857
1.29k
    GC_MAKE_PERSISTENT_LOCAL(str);
858
859
1.29k
    ret = zend_hash_add_new_mem(&PCRE_G(pcre_cache), str, &new_entry, sizeof(pcre_cache_entry));
860
1.29k
    zend_string_release(str);
861
1.29k
  } else {
862
928
    ret = zend_hash_add_new_mem(&PCRE_G(pcre_cache), key, &new_entry, sizeof(pcre_cache_entry));
863
928
  }
864
865
2.22k
  if (key != regex) {
866
0
    zend_string_release_ex(key, 0);
867
0
  }
868
869
2.22k
  return ret;
870
2.22k
}
871
/* }}} */
872
873
/* {{{ pcre_get_compiled_regex_cache */
874
PHPAPI pcre_cache_entry* pcre_get_compiled_regex_cache(zend_string *regex)
875
7.63k
{
876
7.63k
  return pcre_get_compiled_regex_cache_ex(regex, true);
877
7.63k
}
878
/* }}} */
879
880
/* {{{ pcre_get_compiled_regex */
881
PHPAPI pcre2_code *pcre_get_compiled_regex(zend_string *regex, uint32_t *capture_count)
882
0
{
883
0
  pcre_cache_entry * pce = pcre_get_compiled_regex_cache(regex);
884
885
0
  if (capture_count) {
886
0
    *capture_count = pce ? pce->capture_count : 0;
887
0
  }
888
889
0
  return pce ? pce->re : NULL;
890
0
}
891
/* }}} */
892
893
/* XXX For the cases where it's only about match yes/no and no capture
894
    required, perhaps just a minimum sized data would suffice. */
895
PHPAPI pcre2_match_data *php_pcre_create_match_data(uint32_t capture_count, pcre2_code *re)
896
0
{/*{{{*/
897
898
0
  assert(NULL != re);
899
900
0
  if (EXPECTED(!mdata_used)) {
901
0
    int rc = 0;
902
903
0
    if (!capture_count) {
904
      /* As we deal with a non cached pattern, no other way to gather this info. */
905
0
      rc = pcre2_pattern_info(re, PCRE2_INFO_CAPTURECOUNT, &capture_count);
906
0
    }
907
908
0
    if (rc >= 0 && capture_count + 1 <= PHP_PCRE_PREALLOC_MDATA_SIZE) {
909
0
      mdata_used = 1;
910
0
      return mdata;
911
0
    }
912
0
  }
913
914
0
  return pcre2_match_data_create_from_pattern(re, gctx);
915
0
}/*}}}*/
916
917
PHPAPI void php_pcre_free_match_data(pcre2_match_data *match_data)
918
0
{/*{{{*/
919
0
  if (UNEXPECTED(match_data != mdata)) {
920
0
    pcre2_match_data_free(match_data);
921
0
  } else {
922
0
    mdata_used = 0;
923
0
  }
924
0
}/*}}}*/
925
926
0
static void init_unmatched_null_pair(zval *pair) {
927
0
  zval val1, val2;
928
0
  ZVAL_NULL(&val1);
929
0
  ZVAL_LONG(&val2, -1);
930
0
  ZVAL_ARR(pair, zend_new_pair(&val1, &val2));
931
0
}
932
933
0
static void init_unmatched_empty_pair(zval *pair) {
934
0
  zval val1, val2;
935
0
  ZVAL_EMPTY_STRING(&val1);
936
0
  ZVAL_LONG(&val2, -1);
937
0
  ZVAL_ARR(pair, zend_new_pair(&val1, &val2));
938
0
}
939
940
static zend_always_inline void populate_match_value_str(
941
1.24k
    zval *val, const char *subject, PCRE2_SIZE start_offset, PCRE2_SIZE end_offset) {
942
1.24k
  ZVAL_STRINGL_FAST(val, subject + start_offset, end_offset - start_offset);
943
1.24k
}
944
945
static zend_always_inline void populate_match_value(
946
    zval *val, const char *subject, PCRE2_SIZE start_offset, PCRE2_SIZE end_offset,
947
1.24k
    bool unmatched_as_null) {
948
1.24k
  if (PCRE2_UNSET == start_offset) {
949
0
    if (unmatched_as_null) {
950
0
      ZVAL_NULL(val);
951
0
    } else {
952
0
      ZVAL_EMPTY_STRING(val);
953
0
    }
954
1.24k
  } else {
955
1.24k
    populate_match_value_str(val, subject, start_offset, end_offset);
956
1.24k
  }
957
1.24k
}
958
959
static inline void add_named(
960
0
    HashTable *const subpats, zend_string *name, zval *val, bool unmatched) {
961
0
  ZEND_ASSERT(!(GC_FLAGS(name) & IS_STR_PERSISTENT));
962
963
  /* If the DUPNAMES option is used, multiple subpatterns might have the same name.
964
   * In this case we want to preserve the one that actually has a value. */
965
0
  if (!unmatched) {
966
0
    zend_hash_update(subpats, name, val);
967
0
  } else {
968
0
    if (!zend_hash_add(subpats, name, val)) {
969
0
      return;
970
0
    }
971
0
  }
972
0
  Z_TRY_ADDREF_P(val);
973
0
}
974
975
/* {{{ add_offset_pair */
976
static inline void add_offset_pair(
977
    HashTable *const result, const char *subject, PCRE2_SIZE start_offset, PCRE2_SIZE end_offset,
978
    zend_string *name, zend_long unmatched_as_null)
979
0
{
980
0
  zval match_pair;
981
982
  /* Add (match, offset) to the return value */
983
0
  if (PCRE2_UNSET == start_offset) {
984
0
    if (unmatched_as_null) {
985
0
      do {
986
0
        if (Z_ISUNDEF(PCRE_G(unmatched_null_pair))) {
987
0
          if (UNEXPECTED(EG(flags) & EG_FLAGS_IN_SHUTDOWN)) {
988
0
            init_unmatched_null_pair(&match_pair);
989
0
            break;
990
0
          } else {
991
0
            init_unmatched_null_pair(&PCRE_G(unmatched_null_pair));
992
0
          }
993
0
        }
994
0
        ZVAL_COPY(&match_pair, &PCRE_G(unmatched_null_pair));
995
0
      } while (0);
996
0
    } else {
997
0
      do {
998
0
        if (Z_ISUNDEF(PCRE_G(unmatched_empty_pair))) {
999
0
          if (UNEXPECTED(EG(flags) & EG_FLAGS_IN_SHUTDOWN)) {
1000
0
            init_unmatched_empty_pair(&match_pair);
1001
0
            break;
1002
0
          } else {
1003
0
            init_unmatched_empty_pair(&PCRE_G(unmatched_empty_pair));
1004
0
          }
1005
0
        }
1006
0
        ZVAL_COPY(&match_pair, &PCRE_G(unmatched_empty_pair));
1007
0
      } while (0);
1008
0
    }
1009
0
  } else {
1010
0
    zval val1, val2;
1011
0
    populate_match_value_str(&val1, subject, start_offset, end_offset);
1012
0
    ZVAL_LONG(&val2, start_offset);
1013
0
    ZVAL_ARR(&match_pair, zend_new_pair(&val1, &val2));
1014
0
  }
1015
1016
0
  if (name) {
1017
0
    add_named(result, name, &match_pair, start_offset == PCRE2_UNSET);
1018
0
  }
1019
0
  zend_hash_next_index_insert_new(result, &match_pair);
1020
0
}
1021
/* }}} */
1022
1023
static void populate_subpat_array(
1024
    HashTable *subpats_ht, const char *subject, PCRE2_SIZE *offsets, zend_string **subpat_names,
1025
626
    uint32_t num_subpats, int count, const PCRE2_SPTR mark, zend_long flags) {
1026
626
  zend_long offset_capture = flags & PREG_OFFSET_CAPTURE;
1027
626
  zend_long unmatched_as_null = flags & PREG_UNMATCHED_AS_NULL;
1028
626
  zval val;
1029
626
  int i;
1030
626
  if (subpat_names) {
1031
0
    if (offset_capture) {
1032
0
      for (i = 0; i < count; i++) {
1033
0
        add_offset_pair(
1034
0
          subpats_ht, subject, offsets[2*i], offsets[2*i+1],
1035
0
          subpat_names[i], unmatched_as_null);
1036
0
      }
1037
0
      if (unmatched_as_null) {
1038
0
        for (i = count; i < num_subpats; i++) {
1039
0
          add_offset_pair(subpats_ht, NULL, PCRE2_UNSET, PCRE2_UNSET, subpat_names[i], 1);
1040
0
        }
1041
0
      }
1042
0
    } else {
1043
0
      for (i = 0; i < count; i++) {
1044
0
        populate_match_value(
1045
0
          &val, subject, offsets[2*i], offsets[2*i+1], unmatched_as_null);
1046
0
        if (subpat_names[i]) {
1047
0
          add_named(subpats_ht, subpat_names[i], &val, offsets[2*i] == PCRE2_UNSET);
1048
0
        }
1049
0
        zend_hash_next_index_insert_new(subpats_ht, &val);
1050
0
      }
1051
0
      if (unmatched_as_null) {
1052
0
        for (i = count; i < num_subpats; i++) {
1053
0
          ZVAL_NULL(&val);
1054
0
          if (subpat_names[i]) {
1055
0
            zend_hash_add(subpats_ht, subpat_names[i], &val);
1056
0
          }
1057
0
          zend_hash_next_index_insert_new(subpats_ht, &val);
1058
0
        }
1059
0
      }
1060
0
    }
1061
626
  } else {
1062
626
    if (offset_capture) {
1063
0
      for (i = 0; i < count; i++) {
1064
0
        add_offset_pair(
1065
0
          subpats_ht, subject, offsets[2*i], offsets[2*i+1], NULL, unmatched_as_null);
1066
0
      }
1067
0
      if (unmatched_as_null) {
1068
0
        for (i = count; i < num_subpats; i++) {
1069
0
          add_offset_pair(subpats_ht, NULL, PCRE2_UNSET, PCRE2_UNSET, NULL, 1);
1070
0
        }
1071
0
      }
1072
626
    } else {
1073
1.86k
      for (i = 0; i < count; i++) {
1074
1.24k
        populate_match_value(
1075
1.24k
          &val, subject, offsets[2*i], offsets[2*i+1], unmatched_as_null);
1076
1.24k
        zend_hash_next_index_insert_new(subpats_ht, &val);
1077
1.24k
      }
1078
626
      if (unmatched_as_null) {
1079
0
        ZVAL_NULL(&val);
1080
0
        for (i = count; i < num_subpats; i++) {
1081
0
          zend_hash_next_index_insert_new(subpats_ht, &val);
1082
0
        }
1083
0
      }
1084
626
    }
1085
626
  }
1086
  /* Add MARK, if available */
1087
626
  if (mark) {
1088
0
    ZVAL_STRING(&val, (char *)mark);
1089
0
    zend_hash_str_update(subpats_ht, ZEND_STRL("MARK"), &val);
1090
0
  }
1091
626
}
1092
1093
static void php_do_pcre_match(INTERNAL_FUNCTION_PARAMETERS, bool global) /* {{{ */
1094
6.69k
{
1095
  /* parameters */
1096
6.69k
  zend_string    *regex;      /* Regular expression */
1097
6.69k
  zend_string    *subject;      /* String to match against */
1098
6.69k
  pcre_cache_entry *pce;        /* Compiled regular expression */
1099
6.69k
  zval       *subpats = NULL; /* Array for subpatterns */
1100
6.69k
  zend_long     flags = 0;    /* Match control flags */
1101
6.69k
  zend_long     start_offset = 0; /* Where the new search starts */
1102
1103
20.0k
  ZEND_PARSE_PARAMETERS_START(2, 5)
1104
26.7k
    Z_PARAM_STR(regex)
1105
33.4k
    Z_PARAM_STR(subject)
1106
6.68k
    Z_PARAM_OPTIONAL
1107
13.3k
    Z_PARAM_ZVAL(subpats)
1108
13.3k
    Z_PARAM_LONG(flags)
1109
0
    Z_PARAM_LONG(start_offset)
1110
6.69k
  ZEND_PARSE_PARAMETERS_END();
1111
1112
  /* Compile regex or get it from cache. */
1113
6.68k
  if ((pce = pcre_get_compiled_regex_cache(regex)) == NULL) {
1114
1.73k
    RETURN_FALSE;
1115
1.73k
  }
1116
1117
4.95k
  if (start_offset == ZEND_LONG_MIN) {
1118
0
    zend_argument_value_error(5, "must be greater than " ZEND_LONG_FMT, ZEND_LONG_MIN);
1119
0
    RETURN_THROWS();
1120
0
  }
1121
1122
4.95k
  pce->refcount++;
1123
4.95k
  php_pcre_match_impl(pce, subject, return_value, subpats,
1124
4.95k
    global, flags, start_offset);
1125
4.95k
  pce->refcount--;
1126
4.95k
}
1127
/* }}} */
1128
1129
static zend_always_inline bool is_known_valid_utf8(
1130
1.74k
    zend_string *subject_str, PCRE2_SIZE start_offset) {
1131
1.74k
  if (!ZSTR_IS_VALID_UTF8(subject_str)) {
1132
    /* We don't know whether the string is valid UTF-8 or not. */
1133
1.74k
    return false;
1134
1.74k
  }
1135
1136
1
  if (start_offset == ZSTR_LEN(subject_str)) {
1137
    /* Degenerate case: Offset points to end of string. */
1138
1
    return true;
1139
1
  }
1140
1141
  /* Check that the offset does not point to an UTF-8 continuation byte. */
1142
0
  return (ZSTR_VAL(subject_str)[start_offset] & 0xc0) != 0x80;
1143
1
}
1144
1145
/* {{{ php_pcre_match_impl() */
1146
PHPAPI void php_pcre_match_impl(pcre_cache_entry *pce, zend_string *subject_str, zval *return_value,
1147
  zval *subpats, bool global, zend_long flags, zend_off_t start_offset)
1148
4.95k
{
1149
4.95k
  zval       result_set;    /* Holds a set of subpatterns after
1150
                       a global match */
1151
4.95k
  HashTable    **match_sets = NULL; /* An array of sets of matches for each
1152
                       subpattern after a global match */
1153
4.95k
  uint32_t     options;     /* Execution options */
1154
4.95k
  int        count;       /* Count of matched subpatterns */
1155
4.95k
  uint32_t     num_subpats;   /* Number of captured subpatterns */
1156
4.95k
  int        matched;     /* Has anything matched */
1157
4.95k
  zend_string    **subpat_names;    /* Array for named subpatterns */
1158
4.95k
  size_t       i;
1159
4.95k
  uint32_t     subpats_order;   /* Order of subpattern matches */
1160
4.95k
  uint32_t     offset_capture;  /* Capture match offsets: yes/no */
1161
4.95k
  zend_long    unmatched_as_null; /* Null non-matches: yes/no */
1162
4.95k
  PCRE2_SPTR       mark = NULL;   /* Target for MARK name */
1163
4.95k
  HashTable   *marks = NULL;   /* Array of marks for PREG_PATTERN_ORDER */
1164
4.95k
  pcre2_match_data *match_data;
1165
4.95k
  PCRE2_SIZE     start_offset2, orig_start_offset;
1166
4.95k
  bool old_mdata_used;
1167
1168
4.95k
  char *subject = ZSTR_VAL(subject_str);
1169
4.95k
  size_t subject_len = ZSTR_LEN(subject_str);
1170
1171
  /* Overwrite the passed-in value for subpatterns with an empty array. */
1172
4.95k
  if (subpats != NULL) {
1173
2
    subpats = zend_try_array_init(subpats);
1174
2
    if (!subpats) {
1175
0
      RETURN_THROWS();
1176
0
    }
1177
2
  }
1178
1179
4.95k
  subpats_order = global ? PREG_PATTERN_ORDER : 0;
1180
1181
4.95k
  if (flags) {
1182
0
    offset_capture = flags & PREG_OFFSET_CAPTURE;
1183
0
    unmatched_as_null = flags & PREG_UNMATCHED_AS_NULL;
1184
1185
    /*
1186
     * subpats_order is pre-set to pattern mode so we change it only if
1187
     * necessary.
1188
     */
1189
0
    if (flags & 0xff) {
1190
0
      subpats_order = flags & 0xff;
1191
0
      if ((global && (subpats_order < PREG_PATTERN_ORDER || subpats_order > PREG_SET_ORDER)) ||
1192
0
        (!global && subpats_order != 0)) {
1193
0
        zend_argument_value_error(4, "must be a PREG_* constant");
1194
0
        RETURN_THROWS();
1195
0
      }
1196
0
    }
1197
4.95k
  } else {
1198
4.95k
    offset_capture = 0;
1199
4.95k
    unmatched_as_null = 0;
1200
4.95k
  }
1201
1202
  /* Negative offset counts from the end of the string. */
1203
4.95k
  if (start_offset < 0) {
1204
0
    if ((PCRE2_SIZE)-start_offset <= subject_len) {
1205
0
      start_offset2 = subject_len + start_offset;
1206
0
    } else {
1207
0
      start_offset2 = 0;
1208
0
    }
1209
4.95k
  } else {
1210
4.95k
    start_offset2 = (PCRE2_SIZE)start_offset;
1211
4.95k
  }
1212
1213
4.95k
  if (start_offset2 > subject_len) {
1214
0
    pcre_handle_exec_error(PCRE2_ERROR_BADOFFSET);
1215
0
    RETURN_FALSE;
1216
0
  }
1217
1218
  /* Calculate the size of the offsets array, and allocate memory for it. */
1219
4.95k
  num_subpats = pce->capture_count + 1;
1220
1221
  /*
1222
   * Build a mapping from subpattern numbers to their names. We will
1223
   * allocate the table only if there are any named subpatterns.
1224
   */
1225
4.95k
  subpat_names = NULL;
1226
4.95k
  if (subpats && pce->name_count > 0) {
1227
0
    subpat_names = ensure_subpats_table(pce->name_count, pce);
1228
0
    if (UNEXPECTED(!subpat_names)) {
1229
0
      RETURN_FALSE;
1230
0
    }
1231
0
  }
1232
1233
4.95k
  matched = 0;
1234
4.95k
  PCRE_G(error_code) = PHP_PCRE_NO_ERROR;
1235
1236
4.95k
  old_mdata_used = mdata_used;
1237
4.95k
  if (!old_mdata_used && num_subpats <= PHP_PCRE_PREALLOC_MDATA_SIZE) {
1238
4.19k
    mdata_used = true;
1239
4.19k
    match_data = mdata;
1240
4.19k
  } else {
1241
757
    match_data = pcre2_match_data_create_from_pattern(pce->re, PCRE_G(gctx_zmm));
1242
757
    if (!match_data) {
1243
0
      PCRE_G(error_code) = PHP_PCRE_INTERNAL_ERROR;
1244
0
      RETURN_FALSE;
1245
0
    }
1246
757
  }
1247
1248
  /* Allocate match sets array and initialize the values. */
1249
4.95k
  if (global && subpats && subpats_order == PREG_PATTERN_ORDER) {
1250
0
    match_sets = safe_emalloc(num_subpats, sizeof(HashTable *), 0);
1251
0
    for (i=0; i<num_subpats; i++) {
1252
0
      match_sets[i] = zend_new_array(0);
1253
0
    }
1254
0
  }
1255
1256
  /* Array of subpattern offsets */
1257
4.95k
  PCRE2_SIZE *const offsets = pcre2_get_ovector_pointer(match_data);
1258
1259
4.95k
  orig_start_offset = start_offset2;
1260
4.95k
  options =
1261
4.95k
    (pce->compile_options & PCRE2_UTF) && !is_known_valid_utf8(subject_str, orig_start_offset)
1262
4.95k
      ? 0 : PCRE2_NO_UTF_CHECK;
1263
1264
  /* Execute the regular expression. */
1265
#ifdef HAVE_PCRE_JIT_SUPPORT
1266
  if ((pce->preg_options & PREG_JIT) && options) {
1267
    count = pcre2_jit_match(pce->re, (PCRE2_SPTR)subject, subject_len, start_offset2,
1268
        PCRE2_NO_UTF_CHECK, match_data, mctx);
1269
  } else
1270
#endif
1271
4.95k
  count = pcre2_match(pce->re, (PCRE2_SPTR)subject, subject_len, start_offset2,
1272
4.95k
      options, match_data, mctx);
1273
1274
4.95k
  while (1) {
1275
    /* If something has matched */
1276
4.95k
    if (count >= 0) {
1277
      /* Check for too many substrings condition. */
1278
326
      if (UNEXPECTED(count == 0)) {
1279
0
        php_error_docref(NULL, E_NOTICE, "Matched, but too many substrings");
1280
0
        count = num_subpats;
1281
0
      }
1282
1283
326
matched:
1284
326
      matched++;
1285
1286
      /* If subpatterns array has been passed, fill it in with values. */
1287
326
      if (subpats != NULL) {
1288
        /* Try to get the list of substrings and display a warning if failed. */
1289
0
        if (UNEXPECTED(offsets[1] < offsets[0])) {
1290
0
          if (match_sets) {
1291
0
            for (i = 0; i < num_subpats; i++) {
1292
0
              zend_array_destroy(match_sets[i]);
1293
0
            }
1294
0
            efree(match_sets);
1295
0
          }
1296
0
          if (marks) {
1297
0
            zend_array_destroy(marks);
1298
0
          }
1299
0
          if (match_data != mdata) {
1300
0
            pcre2_match_data_free(match_data);
1301
0
          }
1302
0
          php_error_docref(NULL, E_WARNING, "Get subpatterns list failed");
1303
0
          RETURN_FALSE;
1304
0
        }
1305
1306
0
        if (global) { /* global pattern matching */
1307
0
          if (subpats_order == PREG_PATTERN_ORDER) {
1308
            /* For each subpattern, insert it into the appropriate array. */
1309
0
            if (offset_capture) {
1310
0
              for (i = 0; i < count; i++) {
1311
0
                add_offset_pair(
1312
0
                  match_sets[i], subject, offsets[2*i], offsets[2*i+1],
1313
0
                  NULL, unmatched_as_null);
1314
0
              }
1315
0
            } else {
1316
0
              for (i = 0; i < count; i++) {
1317
0
                zval val;
1318
0
                populate_match_value(
1319
0
                  &val, subject, offsets[2*i], offsets[2*i+1], unmatched_as_null);
1320
0
                zend_hash_next_index_insert_new(match_sets[i], &val);
1321
0
              }
1322
0
            }
1323
0
            mark = pcre2_get_mark(match_data);
1324
            /* Add MARK, if available */
1325
0
            if (mark) {
1326
0
              if (!marks) {
1327
0
                marks = zend_new_array(0);
1328
0
              }
1329
0
              zval tmp;
1330
0
              ZVAL_STRING(&tmp, (char *) mark);
1331
0
              zend_hash_index_add_new(marks, matched - 1, &tmp);
1332
0
            }
1333
            /*
1334
             * If the number of captured subpatterns on this run is
1335
             * less than the total possible number, pad the result
1336
             * arrays with NULLs or empty strings.
1337
             */
1338
0
            if (count < num_subpats) {
1339
0
              for (int i = count; i < num_subpats; i++) {
1340
0
                if (offset_capture) {
1341
0
                  add_offset_pair(
1342
0
                    match_sets[i], NULL, PCRE2_UNSET, PCRE2_UNSET,
1343
0
                    NULL, unmatched_as_null);
1344
0
                } else if (unmatched_as_null) {
1345
0
                  zval tmp;
1346
0
                  ZVAL_NULL(&tmp);
1347
0
                  zend_hash_next_index_insert_new(match_sets[i], &tmp);
1348
0
                } else {
1349
0
                  zval tmp;
1350
0
                  ZVAL_EMPTY_STRING(&tmp);
1351
0
                  zend_hash_next_index_insert_new(match_sets[i], &tmp);
1352
0
                }
1353
0
              }
1354
0
            }
1355
0
          } else {
1356
            /* Allocate and populate the result set array */
1357
0
            mark = pcre2_get_mark(match_data);
1358
0
            array_init_size(&result_set, count + (mark ? 1 : 0));
1359
0
            populate_subpat_array(
1360
0
              Z_ARRVAL(result_set), subject, offsets, subpat_names,
1361
0
              num_subpats, count, mark, flags);
1362
            /* And add it to the output array */
1363
0
            zend_hash_next_index_insert_new(Z_ARRVAL_P(subpats), &result_set);
1364
0
          }
1365
0
        } else {     /* single pattern matching */
1366
          /* For each subpattern, insert it into the subpatterns array. */
1367
0
          mark = pcre2_get_mark(match_data);
1368
0
          populate_subpat_array(
1369
0
            Z_ARRVAL_P(subpats), subject, offsets, subpat_names, num_subpats, count, mark, flags);
1370
0
          break;
1371
0
        }
1372
0
      }
1373
1374
      /* Advance to the next piece. */
1375
326
      start_offset2 = offsets[1];
1376
1377
      /* If we have matched an empty string, mimic what Perl's /g options does.
1378
         This turns out to be rather cunning. First we set PCRE2_NOTEMPTY_ATSTART and try
1379
         the match again at the same point. If this fails (picked up above) we
1380
         advance to the next character. */
1381
326
      if (start_offset2 == offsets[0]) {
1382
125
        count = pcre2_match(pce->re, (PCRE2_SPTR)subject, subject_len, start_offset2,
1383
125
          PCRE2_NO_UTF_CHECK | PCRE2_NOTEMPTY_ATSTART | PCRE2_ANCHORED, match_data, mctx);
1384
125
        if (count >= 0) {
1385
7
          if (global) {
1386
0
            goto matched;
1387
7
          } else {
1388
7
            break;
1389
7
          }
1390
118
        } else if (count == PCRE2_ERROR_NOMATCH) {
1391
          /* If we previously set PCRE2_NOTEMPTY_ATSTART after a null match,
1392
             this is not necessarily the end. We need to advance
1393
             the start offset, and continue. Fudge the offset values
1394
             to achieve this, unless we're already at the end of the string. */
1395
118
          if (start_offset2 < subject_len) {
1396
101
            size_t unit_len = calculate_unit_length(pce, subject + start_offset2);
1397
1398
101
            start_offset2 += unit_len;
1399
101
          } else {
1400
17
            break;
1401
17
          }
1402
118
        } else {
1403
0
          goto error;
1404
0
        }
1405
125
      }
1406
4.62k
    } else if (count == PCRE2_ERROR_NOMATCH) {
1407
4.35k
      break;
1408
4.35k
    } else {
1409
266
error:
1410
266
      pcre_handle_exec_error(count);
1411
266
      break;
1412
266
    }
1413
1414
302
    if (!global) {
1415
302
      break;
1416
302
    }
1417
1418
    /* Execute the regular expression. */
1419
#ifdef HAVE_PCRE_JIT_SUPPORT
1420
    if ((pce->preg_options & PREG_JIT)) {
1421
      if (start_offset2 > subject_len) {
1422
        pcre_handle_exec_error(PCRE2_ERROR_BADOFFSET);
1423
        break;
1424
      }
1425
      count = pcre2_jit_match(pce->re, (PCRE2_SPTR)subject, subject_len, start_offset2,
1426
          PCRE2_NO_UTF_CHECK, match_data, mctx);
1427
    } else
1428
#endif
1429
0
    count = pcre2_match(pce->re, (PCRE2_SPTR)subject, subject_len, start_offset2,
1430
0
        PCRE2_NO_UTF_CHECK, match_data, mctx);
1431
0
  }
1432
4.95k
  if (match_data != mdata) {
1433
757
    pcre2_match_data_free(match_data);
1434
757
  }
1435
4.95k
  mdata_used = old_mdata_used;
1436
1437
  /* Add the match sets to the output array and clean up */
1438
4.95k
  if (match_sets) {
1439
0
    if (subpat_names) {
1440
0
      for (i = 0; i < num_subpats; i++) {
1441
0
        zval wrapper;
1442
0
        ZVAL_ARR(&wrapper, match_sets[i]);
1443
0
        if (subpat_names[i]) {
1444
0
          zend_hash_update(Z_ARRVAL_P(subpats), subpat_names[i], &wrapper);
1445
0
          GC_ADDREF(match_sets[i]);
1446
0
        }
1447
0
        zend_hash_next_index_insert_new(Z_ARRVAL_P(subpats), &wrapper);
1448
0
      }
1449
0
    } else {
1450
0
      for (i = 0; i < num_subpats; i++) {
1451
0
        zval wrapper;
1452
0
        ZVAL_ARR(&wrapper, match_sets[i]);
1453
0
        zend_hash_next_index_insert_new(Z_ARRVAL_P(subpats), &wrapper);
1454
0
      }
1455
0
    }
1456
0
    efree(match_sets);
1457
1458
0
    if (marks) {
1459
0
      zval tmp;
1460
0
      ZVAL_ARR(&tmp, marks);
1461
0
      zend_hash_str_update(Z_ARRVAL_P(subpats), "MARK", sizeof("MARK") - 1, &tmp);
1462
0
    }
1463
0
  }
1464
1465
4.95k
  if (PCRE_G(error_code) == PHP_PCRE_NO_ERROR) {
1466
    /* If there was no error and we're in /u mode, remember that the string is valid UTF-8. */
1467
4.68k
    if ((pce->compile_options & PCRE2_UTF)
1468
1.48k
        && !ZSTR_IS_INTERNED(subject_str) && orig_start_offset == 0) {
1469
735
      GC_ADD_FLAGS(subject_str, IS_STR_VALID_UTF8);
1470
735
    }
1471
1472
4.68k
    RETVAL_LONG(matched);
1473
4.68k
  } else {
1474
266
    RETVAL_FALSE;
1475
266
  }
1476
4.95k
}
1477
/* }}} */
1478
1479
/* {{{ Perform a Perl-style regular expression match */
1480
PHP_FUNCTION(preg_match)
1481
6.69k
{
1482
6.69k
  php_do_pcre_match(INTERNAL_FUNCTION_PARAM_PASSTHRU, false);
1483
6.69k
}
1484
/* }}} */
1485
1486
ZEND_FRAMELESS_FUNCTION(preg_match, 2)
1487
0
{
1488
0
  zval regex_tmp, subject_tmp;
1489
0
  zend_string *regex, *subject;
1490
1491
0
  Z_FLF_PARAM_STR(1, regex, regex_tmp);
1492
0
  Z_FLF_PARAM_STR(2, subject, subject_tmp);
1493
1494
  /* Compile regex or get it from cache. */
1495
0
  pcre_cache_entry *pce;
1496
0
  if ((pce = pcre_get_compiled_regex_cache(regex)) == NULL) {
1497
0
    RETVAL_FALSE;
1498
0
    goto flf_clean;
1499
0
  }
1500
1501
0
  pce->refcount++;
1502
0
  php_pcre_match_impl(pce, subject, return_value, /* subpats */ NULL,
1503
0
    /* global */ false, /* flags */ 0, /* start_offset */ 0);
1504
0
  pce->refcount--;
1505
1506
0
flf_clean:
1507
0
  Z_FLF_PARAM_FREE_STR(1, regex_tmp);
1508
0
  Z_FLF_PARAM_FREE_STR(2, subject_tmp);
1509
0
}
1510
1511
/* {{{ Perform a Perl-style global regular expression match */
1512
PHP_FUNCTION(preg_match_all)
1513
0
{
1514
0
  php_do_pcre_match(INTERNAL_FUNCTION_PARAM_PASSTHRU, true);
1515
0
}
1516
/* }}} */
1517
1518
/* {{{ preg_get_backref */
1519
static int preg_get_backref(char **str, int *backref)
1520
144
{
1521
144
  char in_brace = 0;
1522
144
  char *walk = *str;
1523
1524
144
  if (walk[1] == 0)
1525
0
    return 0;
1526
1527
144
  if (*walk == '$' && walk[1] == '{') {
1528
0
    in_brace = 1;
1529
0
    walk++;
1530
0
  }
1531
144
  walk++;
1532
1533
144
  if (*walk >= '0' && *walk <= '9') {
1534
0
    *backref = *walk - '0';
1535
0
    walk++;
1536
0
  } else
1537
144
    return 0;
1538
1539
0
  if (*walk && *walk >= '0' && *walk <= '9') {
1540
0
    *backref = *backref * 10 + *walk - '0';
1541
0
    walk++;
1542
0
  }
1543
1544
0
  if (in_brace) {
1545
0
    if (*walk != '}')
1546
0
      return 0;
1547
0
    else
1548
0
      walk++;
1549
0
  }
1550
1551
0
  *str = walk;
1552
0
  return 1;
1553
0
}
1554
/* }}} */
1555
1556
/* Return NULL if an exception has occurred */
1557
static zend_string *preg_do_repl_func(zend_fcall_info *fci, zend_fcall_info_cache *fcc, const char *subject, PCRE2_SIZE *offsets, zend_string **subpat_names, uint32_t num_subpats, int count, const PCRE2_SPTR mark, zend_long flags)
1558
626
{
1559
626
  zend_string *result_str = NULL;
1560
626
  zval     retval;      /* Function return value */
1561
626
  zval       arg;       /* Argument to pass to function */
1562
1563
626
  array_init_size(&arg, count + (mark ? 1 : 0));
1564
626
  populate_subpat_array(Z_ARRVAL(arg), subject, offsets, subpat_names, num_subpats, count, mark, flags);
1565
1566
626
  fci->retval = &retval;
1567
626
  fci->param_count = 1;
1568
626
  fci->params = &arg;
1569
626
  fci->consumed_args = zend_fci_consumed_arg(0);
1570
626
  zend_call_function(fci, fcc);
1571
626
  zval_ptr_dtor(&arg);
1572
626
  if (EXPECTED(Z_TYPE(retval) == IS_STRING)) {
1573
67
    return Z_STR(retval);
1574
67
  }
1575
  /* No Exception has occurred */
1576
559
  else if (EXPECTED(Z_TYPE(retval) != IS_UNDEF)) {
1577
525
    result_str = zval_try_get_string_func(&retval);
1578
525
  }
1579
559
  zval_ptr_dtor(&retval);
1580
1581
559
  return result_str;
1582
626
}
1583
1584
/* {{{ php_pcre_replace */
1585
PHPAPI zend_string *php_pcre_replace(zend_string *regex,
1586
                zend_string *subject_str,
1587
                const char *subject, size_t subject_len,
1588
                zend_string *replace_str,
1589
                size_t limit, size_t *replace_count)
1590
608
{
1591
608
  pcre_cache_entry  *pce;         /* Compiled regular expression */
1592
608
  zend_string     *result;      /* Function result */
1593
1594
  /* Abort on pending exception, e.g. thrown from __toString(). */
1595
608
  if (UNEXPECTED(EG(exception))) {
1596
0
    return NULL;
1597
0
  }
1598
1599
  /* Compile regex or get it from cache. */
1600
608
  if ((pce = pcre_get_compiled_regex_cache(regex)) == NULL) {
1601
324
    return NULL;
1602
324
  }
1603
284
  pce->refcount++;
1604
284
  result = php_pcre_replace_impl(pce, subject_str, subject, subject_len, replace_str,
1605
284
    limit, replace_count);
1606
284
  pce->refcount--;
1607
1608
284
  return result;
1609
608
}
1610
/* }}} */
1611
1612
/* {{{ php_pcre_replace_impl() */
1613
PHPAPI zend_string *php_pcre_replace_impl(pcre_cache_entry *pce, zend_string *subject_str, const char *subject, size_t subject_len, zend_string *replace_str, size_t limit, size_t *replace_count)
1614
284
{
1615
284
  uint32_t     options;     /* Execution options */
1616
284
  int        count;       /* Count of matched subpatterns */
1617
284
  uint32_t     num_subpats;   /* Number of captured subpatterns */
1618
284
  size_t       new_len;     /* Length of needed storage */
1619
284
  size_t       alloc_len;     /* Actual allocated length */
1620
284
  size_t       match_len;     /* Length of the current match */
1621
284
  int        backref;     /* Backreference number */
1622
284
  PCRE2_SIZE     start_offset;    /* Where the new search starts */
1623
284
  size_t       last_end_offset; /* Where the last search ended */
1624
284
  char      *walkbuf,     /* Location of current replacement in the result */
1625
284
          *walk,        /* Used to walk the replacement string */
1626
284
           walk_last;     /* Last walked character */
1627
284
  const char    *match,       /* The current match */
1628
284
          *piece,       /* The current piece of subject */
1629
284
          *replace_end;   /* End of replacement string */
1630
284
  size_t      result_len;     /* Length of result */
1631
284
  zend_string   *result;      /* Result of replacement */
1632
284
  pcre2_match_data *match_data;
1633
284
  bool old_mdata_used;
1634
1635
  /* Calculate the size of the offsets array, and allocate memory for it. */
1636
284
  num_subpats = pce->capture_count + 1;
1637
284
  alloc_len = 0;
1638
284
  result = NULL;
1639
1640
  /* Initialize */
1641
284
  match = NULL;
1642
284
  start_offset = 0;
1643
284
  last_end_offset = 0;
1644
284
  result_len = 0;
1645
284
  PCRE_G(error_code) = PHP_PCRE_NO_ERROR;
1646
1647
284
  old_mdata_used = mdata_used;
1648
284
  if (!old_mdata_used && num_subpats <= PHP_PCRE_PREALLOC_MDATA_SIZE) {
1649
41
    mdata_used = true;
1650
41
    match_data = mdata;
1651
243
  } else {
1652
243
    match_data = pcre2_match_data_create_from_pattern(pce->re, PCRE_G(gctx_zmm));
1653
243
    if (!match_data) {
1654
0
      PCRE_G(error_code) = PHP_PCRE_INTERNAL_ERROR;
1655
0
      return NULL;
1656
0
    }
1657
243
  }
1658
1659
284
  options = (pce->compile_options & PCRE2_UTF) ? 0 : PCRE2_NO_UTF_CHECK;
1660
1661
  /* Array of subpattern offsets */
1662
284
  PCRE2_SIZE *const offsets = pcre2_get_ovector_pointer(match_data);
1663
1664
  /* Execute the regular expression. */
1665
#ifdef HAVE_PCRE_JIT_SUPPORT
1666
  if ((pce->preg_options & PREG_JIT) && options) {
1667
    count = pcre2_jit_match(pce->re, (PCRE2_SPTR)subject, subject_len, start_offset,
1668
        PCRE2_NO_UTF_CHECK, match_data, mctx);
1669
  } else
1670
#endif
1671
284
  count = pcre2_match(pce->re, (PCRE2_SPTR)subject, subject_len, start_offset,
1672
284
      options, match_data, mctx);
1673
1674
793
  while (1) {
1675
793
    piece = subject + last_end_offset;
1676
1677
793
    if (count >= 0 && limit > 0) {
1678
523
      bool simple_string;
1679
1680
      /* Check for too many substrings condition. */
1681
523
      if (UNEXPECTED(count == 0)) {
1682
0
        php_error_docref(NULL,E_NOTICE, "Matched, but too many substrings");
1683
0
        count = num_subpats;
1684
0
      }
1685
1686
539
matched:
1687
539
      if (UNEXPECTED(offsets[1] < offsets[0])) {
1688
0
        PCRE_G(error_code) = PHP_PCRE_INTERNAL_ERROR;
1689
0
        if (result) {
1690
0
          zend_string_release_ex(result, 0);
1691
0
          result = NULL;
1692
0
        }
1693
0
        break;
1694
0
      }
1695
1696
539
      if (replace_count) {
1697
539
        ++*replace_count;
1698
539
      }
1699
1700
      /* Set the match location in subject */
1701
539
      match = subject + offsets[0];
1702
1703
539
      new_len = result_len + offsets[0] - last_end_offset; /* part before the match */
1704
1705
539
      walk = ZSTR_VAL(replace_str);
1706
539
      replace_end = walk + ZSTR_LEN(replace_str);
1707
539
      walk_last = 0;
1708
539
      simple_string = true;
1709
2.23k
      while (walk < replace_end) {
1710
1.69k
        if ('\\' == *walk || '$' == *walk) {
1711
72
          simple_string = false;
1712
72
          if (walk_last == '\\') {
1713
0
            walk++;
1714
0
            walk_last = 0;
1715
0
            continue;
1716
0
          }
1717
72
          if (preg_get_backref(&walk, &backref)) {
1718
0
            if (backref < count)
1719
0
              new_len += offsets[(backref<<1)+1] - offsets[backref<<1];
1720
0
            continue;
1721
0
          }
1722
72
        }
1723
1.69k
        new_len++;
1724
1.69k
        walk++;
1725
1.69k
        walk_last = walk[-1];
1726
1.69k
      }
1727
1728
539
      if (new_len >= alloc_len) {
1729
209
        alloc_len = zend_safe_address_guarded(2, new_len, ZSTR_MAX_OVERHEAD) - ZSTR_MAX_OVERHEAD;
1730
209
        if (result == NULL) {
1731
150
          result = zend_string_alloc(alloc_len, 0);
1732
150
        } else {
1733
59
          result = zend_string_extend(result, alloc_len, 0);
1734
59
        }
1735
209
      }
1736
1737
539
      if (match-piece > 0) {
1738
        /* copy the part of the string before the match */
1739
493
        memcpy(&ZSTR_VAL(result)[result_len], piece, match-piece);
1740
493
        result_len += (match-piece);
1741
493
      }
1742
1743
539
      if (simple_string) {
1744
        /* copy replacement */
1745
467
        memcpy(&ZSTR_VAL(result)[result_len], ZSTR_VAL(replace_str), ZSTR_LEN(replace_str)+1);
1746
467
        result_len += ZSTR_LEN(replace_str);
1747
467
      } else {
1748
        /* copy replacement and backrefs */
1749
72
        walkbuf = ZSTR_VAL(result) + result_len;
1750
1751
72
        walk = ZSTR_VAL(replace_str);
1752
72
        walk_last = 0;
1753
1.30k
        while (walk < replace_end) {
1754
1.23k
          if ('\\' == *walk || '$' == *walk) {
1755
72
            if (walk_last == '\\') {
1756
0
              *(walkbuf-1) = *walk++;
1757
0
              walk_last = 0;
1758
0
              continue;
1759
0
            }
1760
72
            if (preg_get_backref(&walk, &backref)) {
1761
0
              if (backref < count) {
1762
0
                if (offsets[backref<<1] < SIZE_MAX) {
1763
0
                  match_len = offsets[(backref<<1)+1] - offsets[backref<<1];
1764
0
                  walkbuf = zend_mempcpy(walkbuf, subject + offsets[backref << 1], match_len);
1765
0
                }
1766
0
              }
1767
0
              continue;
1768
0
            }
1769
72
          }
1770
1.23k
          *walkbuf++ = *walk++;
1771
1.23k
          walk_last = walk[-1];
1772
1.23k
        }
1773
72
        *walkbuf = '\0';
1774
        /* increment the result length by how much we've added to the string */
1775
72
        result_len += (walkbuf - (ZSTR_VAL(result) + result_len));
1776
72
      }
1777
1778
539
      limit--;
1779
1780
      /* Advance to the next piece. */
1781
539
      start_offset = last_end_offset = offsets[1];
1782
1783
      /* If we have matched an empty string, mimic what Perl's /g options does.
1784
         This turns out to be rather cunning. First we set PCRE2_NOTEMPTY_ATSTART and try
1785
         the match again at the same point. If this fails (picked up above) we
1786
         advance to the next character. */
1787
539
      if (start_offset == offsets[0]) {
1788
380
        count = pcre2_match(pce->re, (PCRE2_SPTR)subject, subject_len, start_offset,
1789
380
          PCRE2_NO_UTF_CHECK | PCRE2_NOTEMPTY_ATSTART | PCRE2_ANCHORED, match_data, mctx);
1790
1791
380
        piece = subject + start_offset;
1792
380
        if (count >= 0 && limit > 0) {
1793
16
          goto matched;
1794
364
        } else if (count == PCRE2_ERROR_NOMATCH || limit == 0) {
1795
          /* If we previously set PCRE2_NOTEMPTY_ATSTART after a null match,
1796
             this is not necessarily the end. We need to advance
1797
             the start offset, and continue. Fudge the offset values
1798
             to achieve this, unless we're already at the end of the string. */
1799
364
          if (start_offset < subject_len) {
1800
350
            size_t unit_len = calculate_unit_length(pce, piece);
1801
350
            start_offset += unit_len;
1802
350
          } else {
1803
14
            goto not_matched;
1804
14
          }
1805
364
        } else {
1806
0
          goto error;
1807
0
        }
1808
380
      }
1809
1810
539
    } else if (count == PCRE2_ERROR_NOMATCH || limit == 0) {
1811
284
not_matched:
1812
284
      if (!result && subject_str) {
1813
134
        result = zend_string_copy(subject_str);
1814
134
        break;
1815
134
      }
1816
      /* now we know exactly how long it is */
1817
150
      alloc_len = result_len + subject_len - last_end_offset;
1818
150
      if (NULL != result) {
1819
150
        result = zend_string_realloc(result, alloc_len, 0);
1820
150
      } else {
1821
0
        result = zend_string_alloc(alloc_len, 0);
1822
0
      }
1823
      /* stick that last bit of string on our output */
1824
150
      memcpy(ZSTR_VAL(result) + result_len, piece, subject_len - last_end_offset);
1825
150
      result_len += subject_len - last_end_offset;
1826
150
      ZSTR_VAL(result)[result_len] = '\0';
1827
150
      ZSTR_LEN(result) = result_len;
1828
150
      break;
1829
284
    } else {
1830
0
error:
1831
0
      pcre_handle_exec_error(count);
1832
0
      if (result) {
1833
0
        zend_string_release_ex(result, 0);
1834
0
        result = NULL;
1835
0
      }
1836
0
      break;
1837
0
    }
1838
1839
#ifdef HAVE_PCRE_JIT_SUPPORT
1840
    if (pce->preg_options & PREG_JIT) {
1841
      count = pcre2_jit_match(pce->re, (PCRE2_SPTR)subject, subject_len, start_offset,
1842
          PCRE2_NO_UTF_CHECK, match_data, mctx);
1843
    } else
1844
#endif
1845
509
    count = pcre2_match(pce->re, (PCRE2_SPTR)subject, subject_len, start_offset,
1846
509
          PCRE2_NO_UTF_CHECK, match_data, mctx);
1847
509
  }
1848
284
  if (match_data != mdata) {
1849
243
    pcre2_match_data_free(match_data);
1850
243
  }
1851
284
  mdata_used = old_mdata_used;
1852
1853
284
  return result;
1854
284
}
1855
/* }}} */
1856
1857
static zend_string *php_pcre_replace_func_impl(pcre_cache_entry *pce, zend_string *subject_str,
1858
  zend_fcall_info *fci, zend_fcall_info_cache *fcc,
1859
  size_t limit, size_t *replace_count, zend_long flags
1860
304
) {
1861
304
  uint32_t     options;     /* Execution options */
1862
304
  int        count;       /* Count of matched subpatterns */
1863
304
  zend_string   **subpat_names;   /* Array for named subpatterns */
1864
304
  uint32_t     num_subpats;   /* Number of captured subpatterns */
1865
304
  size_t       alloc_len;     /* Actual allocated length */
1866
304
  PCRE2_SIZE     start_offset;    /* Where the new search starts */
1867
304
  size_t       last_end_offset; /* Where the last search ended */
1868
304
  const char    *match,       /* The current match */
1869
304
          *piece;       /* The current piece of subject */
1870
304
  size_t      result_len;     /* Length of result */
1871
304
  zend_string   *result;      /* Result of replacement */
1872
304
  pcre2_match_data *match_data;
1873
304
  bool old_mdata_used;
1874
1875
  /* Calculate the size of the offsets array, and allocate memory for it. */
1876
304
  num_subpats = pce->capture_count + 1;
1877
304
  if (pce->name_count > 0) {
1878
0
    subpat_names = ensure_subpats_table(pce->name_count, pce);
1879
0
    if (UNEXPECTED(!subpat_names)) {
1880
0
      return NULL;
1881
0
    }
1882
304
  } else {
1883
304
    subpat_names = NULL;
1884
304
  }
1885
1886
304
  alloc_len = 0;
1887
304
  result = NULL;
1888
1889
  /* Initialize */
1890
304
  match = NULL;
1891
304
  start_offset = 0;
1892
304
  last_end_offset = 0;
1893
304
  result_len = 0;
1894
304
  PCRE_G(error_code) = PHP_PCRE_NO_ERROR;
1895
1896
304
  old_mdata_used = mdata_used;
1897
304
  if (!old_mdata_used && num_subpats <= PHP_PCRE_PREALLOC_MDATA_SIZE) {
1898
248
    mdata_used = 1;
1899
248
    match_data = mdata;
1900
248
  } else {
1901
56
    match_data = pcre2_match_data_create_from_pattern(pce->re, PCRE_G(gctx_zmm));
1902
56
    if (!match_data) {
1903
0
      PCRE_G(error_code) = PHP_PCRE_INTERNAL_ERROR;
1904
0
      mdata_used = old_mdata_used;
1905
0
      return NULL;
1906
0
    }
1907
56
  }
1908
1909
304
  options = (pce->compile_options & PCRE2_UTF) ? 0 : PCRE2_NO_UTF_CHECK;
1910
1911
  /* Array of subpattern offsets */
1912
304
  PCRE2_SIZE *const offsets = pcre2_get_ovector_pointer(match_data);
1913
1914
  /* Execute the regular expression. */
1915
#ifdef HAVE_PCRE_JIT_SUPPORT
1916
  if ((pce->preg_options & PREG_JIT) && options) {
1917
    count = pcre2_jit_match(pce->re, (PCRE2_SPTR)ZSTR_VAL(subject_str), ZSTR_LEN(subject_str), start_offset,
1918
        PCRE2_NO_UTF_CHECK, match_data, mctx);
1919
  } else
1920
#endif
1921
304
  count = pcre2_match(pce->re, (PCRE2_SPTR)ZSTR_VAL(subject_str), ZSTR_LEN(subject_str), start_offset,
1922
304
      options, match_data, mctx);
1923
1924
890
  while (1) {
1925
887
    piece = ZSTR_VAL(subject_str) + last_end_offset;
1926
1927
887
    if (count >= 0 && limit) {
1928
      /* Check for too many substrings condition. */
1929
626
      if (UNEXPECTED(count == 0)) {
1930
0
        php_error_docref(NULL,E_NOTICE, "Matched, but too many substrings");
1931
0
        count = num_subpats;
1932
0
      }
1933
1934
626
matched:
1935
626
      if (UNEXPECTED(offsets[1] < offsets[0])) {
1936
0
        PCRE_G(error_code) = PHP_PCRE_INTERNAL_ERROR;
1937
0
        if (result) {
1938
0
          zend_string_release_ex(result, 0);
1939
0
          result = NULL;
1940
0
        }
1941
0
        break;
1942
0
      }
1943
1944
626
      if (replace_count) {
1945
626
        ++*replace_count;
1946
626
      }
1947
1948
      /* Set the match location in subject */
1949
626
      match = ZSTR_VAL(subject_str) + offsets[0];
1950
1951
      /* Length of needed storage */
1952
626
      size_t new_len = result_len + offsets[0] - last_end_offset; /* part before the match */
1953
1954
      /* Use custom function to get replacement string and its length. */
1955
626
      zend_string *eval_result = preg_do_repl_func(
1956
626
        fci, fcc, ZSTR_VAL(subject_str), offsets, subpat_names, num_subpats, count,
1957
626
        pcre2_get_mark(match_data), flags);
1958
1959
626
      if (UNEXPECTED(eval_result == NULL)) {
1960
31
        goto error;
1961
31
      }
1962
595
      new_len = zend_safe_address_guarded(1, ZSTR_LEN(eval_result) + ZSTR_MAX_OVERHEAD, new_len) -ZSTR_MAX_OVERHEAD;
1963
595
      if (new_len >= alloc_len) {
1964
358
        alloc_len = zend_safe_address_guarded(2, new_len, ZSTR_MAX_OVERHEAD) - ZSTR_MAX_OVERHEAD;
1965
358
        if (result == NULL) {
1966
204
          result = zend_string_alloc(alloc_len, 0);
1967
204
        } else {
1968
154
          result = zend_string_extend(result, alloc_len, 0);
1969
154
        }
1970
358
      }
1971
1972
595
      if (match-piece > 0) {
1973
        /* copy the part of the string before the match */
1974
580
        memcpy(ZSTR_VAL(result) + result_len, piece, match-piece);
1975
580
        result_len += (match-piece);
1976
580
      }
1977
1978
      /* If using custom function, copy result to the buffer and clean up. */
1979
595
      memcpy(ZSTR_VAL(result) + result_len, ZSTR_VAL(eval_result), ZSTR_LEN(eval_result));
1980
595
      result_len += ZSTR_LEN(eval_result);
1981
595
      zend_string_release_ex(eval_result, 0);
1982
1983
595
      limit--;
1984
1985
      /* Advance to the next piece. */
1986
595
      start_offset = last_end_offset = offsets[1];
1987
1988
      /* If we have matched an empty string, mimic what Perl's /g options does.
1989
         This turns out to be rather cunning. First we set PCRE2_NOTEMPTY_ATSTART and try
1990
         the match again at the same point. If this fails (picked up above) we
1991
         advance to the next character. */
1992
595
      if (start_offset == offsets[0]) {
1993
198
        count = pcre2_match(pce->re, (PCRE2_SPTR)ZSTR_VAL(subject_str), ZSTR_LEN(subject_str), start_offset,
1994
198
          PCRE2_NO_UTF_CHECK | PCRE2_NOTEMPTY_ATSTART | PCRE2_ANCHORED, match_data, mctx);
1995
1996
198
        piece = ZSTR_VAL(subject_str) + start_offset;
1997
198
        if (count >= 0 && limit) {
1998
0
          goto matched;
1999
198
        } else if (count == PCRE2_ERROR_NOMATCH || limit == 0) {
2000
          /* If we previously set PCRE2_NOTEMPTY_ATSTART after a null match,
2001
             this is not necessarily the end. We need to advance
2002
             the start offset, and continue. Fudge the offset values
2003
             to achieve this, unless we're already at the end of the string. */
2004
198
          if (start_offset < ZSTR_LEN(subject_str)) {
2005
189
            size_t unit_len = calculate_unit_length(pce, piece);
2006
189
            start_offset += unit_len;
2007
189
          } else {
2008
9
            goto not_matched;
2009
9
          }
2010
198
        } else {
2011
0
          goto error;
2012
0
        }
2013
198
      }
2014
2015
595
    } else if (count == PCRE2_ERROR_NOMATCH || limit == 0) {
2016
270
not_matched:
2017
270
      if (result == NULL) {
2018
69
        result = zend_string_copy(subject_str);
2019
69
        break;
2020
69
      }
2021
      /* now we know exactly how long it is */
2022
201
      size_t segment_len = ZSTR_LEN(subject_str) - last_end_offset;
2023
201
      alloc_len = result_len + segment_len;
2024
201
      result = zend_string_realloc(result, alloc_len, 0);
2025
      /* stick that last bit of string on our output */
2026
201
      memcpy(ZSTR_VAL(result) + result_len, piece, segment_len);
2027
201
      result_len += segment_len;
2028
201
      ZSTR_VAL(result)[result_len] = '\0';
2029
201
      ZSTR_LEN(result) = result_len;
2030
201
      break;
2031
270
    } else {
2032
31
error:
2033
31
      pcre_handle_exec_error(count);
2034
31
      if (result) {
2035
0
        zend_string_release_ex(result, 0);
2036
0
        result = NULL;
2037
0
      }
2038
31
      break;
2039
0
    }
2040
#ifdef HAVE_PCRE_JIT_SUPPORT
2041
    if ((pce->preg_options & PREG_JIT)) {
2042
      count = pcre2_jit_match(pce->re, (PCRE2_SPTR)ZSTR_VAL(subject_str), ZSTR_LEN(subject_str), start_offset,
2043
          PCRE2_NO_UTF_CHECK, match_data, mctx);
2044
    } else
2045
#endif
2046
586
    count = pcre2_match(pce->re, (PCRE2_SPTR)ZSTR_VAL(subject_str), ZSTR_LEN(subject_str), start_offset,
2047
586
        PCRE2_NO_UTF_CHECK, match_data, mctx);
2048
586
  }
2049
304
  if (match_data != mdata) {
2050
54
    pcre2_match_data_free(match_data);
2051
54
  }
2052
304
  mdata_used = old_mdata_used;
2053
2054
304
  return result;
2055
304
}
2056
2057
static zend_always_inline zend_string *php_pcre_replace_func(zend_string *regex,
2058
                zend_string *subject_str,
2059
                zend_fcall_info *fci, zend_fcall_info_cache *fcc,
2060
                size_t limit, size_t *replace_count, zend_long flags)
2061
343
{
2062
343
  pcre_cache_entry  *pce;         /* Compiled regular expression */
2063
343
  zend_string     *result;      /* Function result */
2064
2065
  /* Compile regex or get it from cache. */
2066
343
  if ((pce = pcre_get_compiled_regex_cache(regex)) == NULL) {
2067
39
    return NULL;
2068
39
  }
2069
304
  pce->refcount++;
2070
304
  result = php_pcre_replace_func_impl(pce, subject_str, fci, fcc, limit, replace_count, flags);
2071
304
  pce->refcount--;
2072
2073
304
  return result;
2074
343
}
2075
2076
/* {{{ php_pcre_replace_array */
2077
static zend_string *php_pcre_replace_array(HashTable *regex,
2078
  zend_string *replace_str, HashTable *replace_ht,
2079
  zend_string *subject_str, size_t limit, size_t *replace_count)
2080
0
{
2081
0
  zval    *regex_entry;
2082
0
  zend_string *result;
2083
2084
0
  zend_string_addref(subject_str);
2085
2086
0
  if (replace_ht) {
2087
0
    uint32_t replace_idx = 0;
2088
2089
    /* For each entry in the regex array, get the entry */
2090
0
    ZEND_HASH_FOREACH_VAL(regex, regex_entry) {
2091
      /* Make sure we're dealing with strings. */
2092
0
      zend_string *tmp_regex_str;
2093
0
      zend_string *regex_str = zval_get_tmp_string(regex_entry, &tmp_regex_str);
2094
0
      zend_string *replace_entry_str, *tmp_replace_entry_str;
2095
0
      zval *zv;
2096
2097
      /* Get current entry */
2098
0
      while (1) {
2099
0
        if (replace_idx == replace_ht->nNumUsed) {
2100
0
          replace_entry_str = ZSTR_EMPTY_ALLOC();
2101
0
          tmp_replace_entry_str = NULL;
2102
0
          break;
2103
0
        }
2104
0
        zv = ZEND_HASH_ELEMENT(replace_ht, replace_idx);
2105
0
        replace_idx++;
2106
0
        if (Z_TYPE_P(zv) != IS_UNDEF) {
2107
0
          replace_entry_str = zval_get_tmp_string(zv, &tmp_replace_entry_str);
2108
0
          break;
2109
0
        }
2110
0
      }
2111
2112
      /* Do the actual replacement and put the result back into subject_str
2113
         for further replacements. */
2114
0
      result = php_pcre_replace(regex_str, subject_str, ZSTR_VAL(subject_str),
2115
0
        ZSTR_LEN(subject_str), replace_entry_str, limit, replace_count);
2116
0
      zend_tmp_string_release(tmp_replace_entry_str);
2117
0
      zend_tmp_string_release(tmp_regex_str);
2118
0
      zend_string_release_ex(subject_str, 0);
2119
0
      subject_str = result;
2120
0
      if (UNEXPECTED(result == NULL)) {
2121
0
        break;
2122
0
      }
2123
0
    } ZEND_HASH_FOREACH_END();
2124
2125
0
  } else {
2126
0
    ZEND_ASSERT(replace_str != NULL);
2127
2128
    /* For each entry in the regex array, get the entry */
2129
0
    ZEND_HASH_FOREACH_VAL(regex, regex_entry) {
2130
      /* Make sure we're dealing with strings. */
2131
0
      zend_string *tmp_regex_str;
2132
0
      zend_string *regex_str = zval_get_tmp_string(regex_entry, &tmp_regex_str);
2133
2134
      /* Do the actual replacement and put the result back into subject_str
2135
         for further replacements. */
2136
0
      result = php_pcre_replace(regex_str, subject_str, ZSTR_VAL(subject_str),
2137
0
        ZSTR_LEN(subject_str), replace_str, limit, replace_count);
2138
0
      zend_tmp_string_release(tmp_regex_str);
2139
0
      zend_string_release_ex(subject_str, 0);
2140
0
      subject_str = result;
2141
2142
0
      if (UNEXPECTED(result == NULL)) {
2143
0
        break;
2144
0
      }
2145
0
    } ZEND_HASH_FOREACH_END();
2146
0
  }
2147
2148
0
  return subject_str;
2149
0
}
2150
/* }}} */
2151
2152
/* {{{ php_replace_in_subject */
2153
static zend_always_inline zend_string *php_replace_in_subject(
2154
  zend_string *regex_str, HashTable *regex_ht,
2155
  zend_string *replace_str, HashTable *replace_ht,
2156
  zend_string *subject, size_t limit, size_t *replace_count)
2157
608
{
2158
608
  zend_string *result;
2159
2160
608
  if (regex_str) {
2161
608
    ZEND_ASSERT(replace_str != NULL);
2162
608
    result = php_pcre_replace(regex_str, subject, ZSTR_VAL(subject), ZSTR_LEN(subject),
2163
608
      replace_str, limit, replace_count);
2164
608
  } else {
2165
0
    ZEND_ASSERT(regex_ht != NULL);
2166
0
    result = php_pcre_replace_array(regex_ht, replace_str, replace_ht, subject,
2167
0
      limit, replace_count);
2168
0
  }
2169
608
  return result;
2170
608
}
2171
/* }}} */
2172
2173
static zend_string *php_replace_in_subject_func(zend_string *regex_str, const HashTable *regex_ht,
2174
  zend_fcall_info *fci, zend_fcall_info_cache *fcc,
2175
  zend_string *subject, size_t limit, size_t *replace_count, zend_long flags)
2176
343
{
2177
343
  zend_string *result;
2178
2179
343
  if (regex_str) {
2180
343
    result = php_pcre_replace_func(regex_str, subject, fci, fcc, limit, replace_count, flags);
2181
343
    return result;
2182
343
  } else {
2183
    /* If regex is an array */
2184
0
    zval    *regex_entry;
2185
2186
0
    ZEND_ASSERT(regex_ht != NULL);
2187
2188
0
    zend_string_addref(subject);
2189
2190
    /* For each entry in the regex array, get the entry */
2191
0
    ZEND_HASH_FOREACH_VAL(regex_ht, regex_entry) {
2192
      /* Make sure we're dealing with strings. */
2193
0
      zend_string *tmp_regex_entry_str;
2194
0
      zend_string *regex_entry_str = zval_try_get_tmp_string(regex_entry, &tmp_regex_entry_str);
2195
0
      if (UNEXPECTED(regex_entry_str == NULL)) {
2196
0
        break;
2197
0
      }
2198
2199
      /* Do the actual replacement and put the result back into subject
2200
         for further replacements. */
2201
0
      result = php_pcre_replace_func(
2202
0
        regex_entry_str, subject, fci, fcc, limit, replace_count, flags);
2203
0
      zend_tmp_string_release(tmp_regex_entry_str);
2204
0
      zend_string_release(subject);
2205
0
      subject = result;
2206
0
      if (UNEXPECTED(result == NULL)) {
2207
0
        break;
2208
0
      }
2209
0
    } ZEND_HASH_FOREACH_END();
2210
2211
0
    return subject;
2212
0
  }
2213
343
}
2214
2215
static size_t php_preg_replace_func_impl(zval *return_value,
2216
  zend_string *regex_str, const HashTable *regex_ht,
2217
  zend_fcall_info *fci, zend_fcall_info_cache *fcc,
2218
  zend_string *subject_str, const HashTable *subject_ht, zend_long limit_val, zend_long flags)
2219
343
{
2220
343
  zend_string *result;
2221
343
  size_t replace_count = 0;
2222
2223
343
  if (subject_str) {
2224
343
    result = php_replace_in_subject_func(
2225
343
      regex_str, regex_ht, fci, fcc, subject_str, limit_val, &replace_count, flags);
2226
343
    if (result != NULL) {
2227
270
      RETVAL_STR(result);
2228
270
    } else {
2229
73
      RETVAL_NULL();
2230
73
    }
2231
343
  } else {
2232
    /* if subject is an array */
2233
0
    zval    *subject_entry, zv;
2234
0
    zend_string *string_key;
2235
0
    zend_ulong   num_key;
2236
2237
0
    ZEND_ASSERT(subject_ht != NULL);
2238
2239
0
    array_init_size(return_value, zend_hash_num_elements(subject_ht));
2240
0
    HashTable *return_value_ht = Z_ARRVAL_P(return_value);
2241
2242
    /* For each subject entry, convert it to string, then perform replacement
2243
       and add the result to the return_value array. */
2244
0
    ZEND_HASH_FOREACH_KEY_VAL(subject_ht, num_key, string_key, subject_entry) {
2245
0
      zend_string *tmp_subject_entry_str;
2246
0
      zend_string *subject_entry_str = zval_try_get_tmp_string(subject_entry, &tmp_subject_entry_str);
2247
0
      if (UNEXPECTED(subject_entry_str == NULL)) {
2248
0
        break;
2249
0
      }
2250
2251
0
      result = php_replace_in_subject_func(
2252
0
        regex_str, regex_ht, fci, fcc, subject_entry_str, limit_val, &replace_count, flags);
2253
0
      if (result != NULL) {
2254
        /* Add to return array */
2255
0
        ZVAL_STR(&zv, result);
2256
0
        if (string_key) {
2257
0
          zend_hash_add_new(return_value_ht, string_key, &zv);
2258
0
        } else {
2259
0
          zend_hash_index_add_new(return_value_ht, num_key, &zv);
2260
0
        }
2261
0
      }
2262
0
      zend_tmp_string_release(tmp_subject_entry_str);
2263
0
    } ZEND_HASH_FOREACH_END();
2264
0
  }
2265
2266
343
  return replace_count;
2267
343
}
2268
2269
static void _preg_replace_common(
2270
  zval *return_value,
2271
  HashTable *regex_ht, zend_string *regex_str,
2272
  HashTable *replace_ht, zend_string *replace_str,
2273
  HashTable *subject_ht, zend_string *subject_str,
2274
  zend_long limit,
2275
  zval *zcount,
2276
  bool is_filter
2277
604
) {
2278
604
  size_t replace_count = 0;
2279
604
  zend_string *result;
2280
604
  size_t old_replace_count;
2281
2282
  /* If replace is an array then the regex argument needs to also be an array */
2283
604
  if (replace_ht && !regex_ht) {
2284
0
    zend_argument_type_error(1, "must be of type array when argument #2 ($replacement) is an array, string given");
2285
0
    RETURN_THROWS();
2286
0
  }
2287
2288
604
  if (subject_str) {
2289
601
    old_replace_count = replace_count;
2290
601
    result = php_replace_in_subject(regex_str, regex_ht, replace_str, replace_ht,
2291
601
      subject_str, limit, &replace_count);
2292
601
    if (result != NULL) {
2293
284
      if (!is_filter || replace_count > old_replace_count) {
2294
284
        RETVAL_STR(result);
2295
284
      } else {
2296
0
        zend_string_release_ex(result, 0);
2297
0
        RETVAL_NULL();
2298
0
      }
2299
317
    } else {
2300
317
      RETVAL_NULL();
2301
317
    }
2302
601
  } else {
2303
    /* if subject is an array */
2304
3
    zval    *subject_entry, zv;
2305
3
    zend_string *string_key;
2306
3
    zend_ulong   num_key;
2307
2308
3
    ZEND_ASSERT(subject_ht != NULL);
2309
2310
3
    array_init_size(return_value, zend_hash_num_elements(subject_ht));
2311
3
    HashTable *return_value_ht = Z_ARRVAL_P(return_value);
2312
2313
    /* For each subject entry, convert it to string, then perform replacement
2314
       and add the result to the return_value array. */
2315
17
    ZEND_HASH_FOREACH_KEY_VAL(subject_ht, num_key, string_key, subject_entry) {
2316
17
      old_replace_count = replace_count;
2317
17
      zend_string *tmp_subject_entry_str;
2318
17
      zend_string *subject_entry_str = zval_get_tmp_string(subject_entry, &tmp_subject_entry_str);
2319
17
      result = php_replace_in_subject(regex_str, regex_ht, replace_str, replace_ht,
2320
17
        subject_entry_str, limit, &replace_count);
2321
2322
17
      if (result != NULL) {
2323
0
        if (!is_filter || replace_count > old_replace_count) {
2324
          /* Add to return array */
2325
0
          ZVAL_STR(&zv, result);
2326
0
          if (string_key) {
2327
0
            zend_hash_add_new(return_value_ht, string_key, &zv);
2328
0
          } else {
2329
0
            zend_hash_index_add_new(return_value_ht, num_key, &zv);
2330
0
          }
2331
0
        } else {
2332
0
          zend_string_release_ex(result, 0);
2333
0
        }
2334
0
      }
2335
17
      zend_tmp_string_release(tmp_subject_entry_str);
2336
17
    } ZEND_HASH_FOREACH_END();
2337
3
  }
2338
2339
604
  if (zcount) {
2340
0
    ZEND_TRY_ASSIGN_REF_LONG(zcount, replace_count);
2341
0
  }
2342
604
}
2343
2344
/* {{{ preg_replace_common */
2345
static void preg_replace_common(INTERNAL_FUNCTION_PARAMETERS, bool is_filter)
2346
610
{
2347
610
  zend_string *regex_str, *replace_str, *subject_str;
2348
610
  HashTable *regex_ht, *replace_ht, *subject_ht;
2349
610
  zend_long limit = -1;
2350
610
  zval *zcount = NULL;
2351
2352
  /* Get function parameters and do error-checking. */
2353
1.82k
  ZEND_PARSE_PARAMETERS_START(3, 5)
2354
3.04k
    Z_PARAM_ARRAY_HT_OR_STR(regex_ht, regex_str)
2355
3.04k
    Z_PARAM_ARRAY_HT_OR_STR(replace_ht, replace_str)
2356
3.04k
    Z_PARAM_ARRAY_HT_OR_STR(subject_ht, subject_str)
2357
3.04k
    Z_PARAM_OPTIONAL
2358
3.04k
    Z_PARAM_LONG(limit)
2359
1.60k
    Z_PARAM_ZVAL(zcount)
2360
1.60k
  ZEND_PARSE_PARAMETERS_END();
2361
2362
604
  _preg_replace_common(
2363
604
    return_value,
2364
604
    regex_ht, regex_str,
2365
604
    replace_ht, replace_str,
2366
604
    subject_ht, subject_str,
2367
604
    limit, zcount, is_filter);
2368
604
}
2369
/* }}} */
2370
2371
/* {{{ Perform Perl-style regular expression replacement. */
2372
PHP_FUNCTION(preg_replace)
2373
610
{
2374
610
  preg_replace_common(INTERNAL_FUNCTION_PARAM_PASSTHRU, false);
2375
610
}
2376
/* }}} */
2377
2378
ZEND_FRAMELESS_FUNCTION(preg_replace, 3)
2379
0
{
2380
0
  zend_string *regex_str, *replace_str, *subject_str;
2381
0
  HashTable *regex_ht, *replace_ht, *subject_ht;
2382
0
  zval regex_tmp, replace_tmp, subject_tmp;
2383
2384
0
  Z_FLF_PARAM_ARRAY_HT_OR_STR(1, regex_ht, regex_str, regex_tmp);
2385
0
  Z_FLF_PARAM_ARRAY_HT_OR_STR(2, replace_ht, replace_str, replace_tmp);
2386
0
  Z_FLF_PARAM_ARRAY_HT_OR_STR(3, subject_ht, subject_str, subject_tmp);
2387
2388
0
  _preg_replace_common(
2389
0
    return_value,
2390
0
    regex_ht, regex_str,
2391
0
    replace_ht, replace_str,
2392
0
    subject_ht, subject_str,
2393
0
    /* limit */ -1, /* zcount */ NULL, /* is_filter */ false);
2394
2395
0
flf_clean:;
2396
0
  Z_FLF_PARAM_FREE_STR(1, regex_tmp);
2397
0
  Z_FLF_PARAM_FREE_STR(2, replace_tmp);
2398
0
  Z_FLF_PARAM_FREE_STR(3, subject_tmp);
2399
0
}
2400
2401
/* {{{ Perform Perl-style regular expression replacement using replacement callback. */
2402
PHP_FUNCTION(preg_replace_callback)
2403
345
{
2404
345
  zval *zcount = NULL;
2405
345
  zend_string *regex_str;
2406
345
  HashTable *regex_ht;
2407
345
  zend_string *subject_str;
2408
345
  HashTable *subject_ht;
2409
345
  zend_long limit = -1, flags = 0;
2410
345
  size_t replace_count;
2411
345
  zend_fcall_info fci = empty_fcall_info;
2412
345
  zend_fcall_info_cache fcc = empty_fcall_info_cache;
2413
2414
  /* Get function parameters and do error-checking. */
2415
1.03k
  ZEND_PARSE_PARAMETERS_START(3, 6)
2416
1.72k
    Z_PARAM_ARRAY_HT_OR_STR(regex_ht, regex_str)
2417
1.72k
    Z_PARAM_FUNC(fci, fcc)
2418
2.05k
    Z_PARAM_ARRAY_HT_OR_STR(subject_ht, subject_str)
2419
2.05k
    Z_PARAM_OPTIONAL
2420
2.05k
    Z_PARAM_LONG(limit)
2421
0
    Z_PARAM_ZVAL(zcount)
2422
0
    Z_PARAM_LONG(flags)
2423
345
  ZEND_PARSE_PARAMETERS_END();
2424
2425
343
  replace_count = php_preg_replace_func_impl(return_value, regex_str, regex_ht,
2426
343
    &fci, &fcc,
2427
343
    subject_str, subject_ht, limit, flags);
2428
343
  if (zcount) {
2429
0
    ZEND_TRY_ASSIGN_REF_LONG(zcount, replace_count);
2430
0
  }
2431
343
}
2432
/* }}} */
2433
2434
/* {{{ Perform Perl-style regular expression replacement using replacement callback. */
2435
PHP_FUNCTION(preg_replace_callback_array)
2436
0
{
2437
0
  zval *replace, *zcount = NULL;
2438
0
  HashTable *pattern, *subject_ht;
2439
0
  zend_string *subject_str, *str_idx_regex;
2440
0
  zend_long limit = -1, flags = 0;
2441
0
  size_t replace_count = 0;
2442
2443
  /* Get function parameters and do error-checking. */
2444
0
  ZEND_PARSE_PARAMETERS_START(2, 5)
2445
0
    Z_PARAM_ARRAY_HT(pattern)
2446
0
    Z_PARAM_ARRAY_HT_OR_STR(subject_ht, subject_str)
2447
0
    Z_PARAM_OPTIONAL
2448
0
    Z_PARAM_LONG(limit)
2449
0
    Z_PARAM_ZVAL(zcount)
2450
0
    Z_PARAM_LONG(flags)
2451
0
  ZEND_PARSE_PARAMETERS_END();
2452
2453
0
  if (subject_ht) {
2454
0
    GC_TRY_ADDREF(subject_ht);
2455
0
  } else {
2456
0
    GC_TRY_ADDREF(subject_str);
2457
0
  }
2458
2459
0
  ZEND_HASH_FOREACH_STR_KEY_VAL(pattern, str_idx_regex, replace) {
2460
0
    if (!str_idx_regex) {
2461
0
      zend_argument_type_error(1, "must contain only string patterns as keys");
2462
0
      goto error;
2463
0
    }
2464
2465
0
    zend_fcall_info_cache fcc = empty_fcall_info_cache;
2466
0
    zend_fcall_info fci = empty_fcall_info;
2467
0
    fci.size = sizeof(zend_fcall_info);
2468
    /* Copy potential trampoline */
2469
0
    ZVAL_COPY_VALUE(&fci.function_name, replace);
2470
2471
0
    if (!zend_is_callable_ex(replace, NULL, 0, NULL, &fcc, NULL)) {
2472
0
      zend_argument_type_error(1, "must contain only valid callbacks");
2473
0
      goto error;
2474
0
    }
2475
2476
0
    zval retval;
2477
0
    replace_count += php_preg_replace_func_impl(&retval, str_idx_regex, /* regex_ht */ NULL, &fci, &fcc,
2478
0
      subject_str, subject_ht, limit, flags);
2479
0
    zend_release_fcall_info_cache(&fcc);
2480
2481
0
    switch (Z_TYPE(retval)) {
2482
0
      case IS_ARRAY:
2483
0
        ZEND_ASSERT(subject_ht);
2484
0
        zend_array_release(subject_ht);
2485
0
        subject_ht = Z_ARR(retval);
2486
0
        break;
2487
0
      case IS_STRING:
2488
0
        ZEND_ASSERT(subject_str);
2489
0
        zend_string_release(subject_str);
2490
0
        subject_str = Z_STR(retval);
2491
0
        break;
2492
0
      case IS_NULL:
2493
0
        RETVAL_NULL();
2494
0
        goto error;
2495
0
      default: ZEND_UNREACHABLE();
2496
0
    }
2497
2498
0
    if (EG(exception)) {
2499
0
      goto error;
2500
0
    }
2501
0
  } ZEND_HASH_FOREACH_END();
2502
2503
0
  if (zcount) {
2504
0
    ZEND_TRY_ASSIGN_REF_LONG(zcount, replace_count);
2505
0
  }
2506
2507
0
  if (subject_ht) {
2508
0
    RETVAL_ARR(subject_ht);
2509
    // Unset the type_flags of immutable arrays to prevent the VM from performing refcounting
2510
0
    if (GC_FLAGS(subject_ht) & IS_ARRAY_IMMUTABLE) {
2511
0
      Z_TYPE_FLAGS_P(return_value) = 0;
2512
0
    }
2513
0
    return;
2514
0
  } else {
2515
0
    RETURN_STR(subject_str);
2516
0
  }
2517
2518
0
error:
2519
0
  if (subject_ht) {
2520
0
    zend_array_release(subject_ht);
2521
0
  } else {
2522
0
    zend_string_release(subject_str);
2523
0
  }
2524
0
}
2525
/* }}} */
2526
2527
/* {{{ Perform Perl-style regular expression replacement and only return matches. */
2528
PHP_FUNCTION(preg_filter)
2529
0
{
2530
0
  preg_replace_common(INTERNAL_FUNCTION_PARAM_PASSTHRU, true);
2531
0
}
2532
/* }}} */
2533
2534
/* {{{ Split string into an array using a perl-style regular expression as a delimiter */
2535
PHP_FUNCTION(preg_split)
2536
0
{
2537
0
  zend_string     *regex;     /* Regular expression */
2538
0
  zend_string     *subject;   /* String to match against */
2539
0
  zend_long      limit_val = -1;/* Integer value of limit */
2540
0
  zend_long      flags = 0;   /* Match control flags */
2541
0
  pcre_cache_entry  *pce;     /* Compiled regular expression */
2542
2543
  /* Get function parameters and do error checking */
2544
0
  ZEND_PARSE_PARAMETERS_START(2, 4)
2545
0
    Z_PARAM_STR(regex)
2546
0
    Z_PARAM_STR(subject)
2547
0
    Z_PARAM_OPTIONAL
2548
0
    Z_PARAM_LONG(limit_val)
2549
0
    Z_PARAM_LONG(flags)
2550
0
  ZEND_PARSE_PARAMETERS_END();
2551
2552
  /* Compile regex or get it from cache. */
2553
0
  if ((pce = pcre_get_compiled_regex_cache(regex)) == NULL) {
2554
0
    RETURN_FALSE;
2555
0
  }
2556
2557
0
  pce->refcount++;
2558
0
  php_pcre_split_impl(pce, subject, return_value, limit_val, flags);
2559
0
  pce->refcount--;
2560
0
}
2561
/* }}} */
2562
2563
/* {{{ php_pcre_split */
2564
PHPAPI void php_pcre_split_impl(pcre_cache_entry *pce, zend_string *subject_str, zval *return_value,
2565
  zend_long limit_val, zend_long flags)
2566
0
{
2567
0
  uint32_t     options;     /* Execution options */
2568
0
  int        count;       /* Count of matched subpatterns */
2569
0
  PCRE2_SIZE     start_offset;    /* Where the new search starts */
2570
0
  PCRE2_SIZE     last_match_offset; /* Location of last match */
2571
0
  uint32_t     no_empty;      /* If NO_EMPTY flag is set */
2572
0
  uint32_t     delim_capture;   /* If delimiters should be captured */
2573
0
  uint32_t     offset_capture;  /* If offsets should be captured */
2574
0
  uint32_t     num_subpats;   /* Number of captured subpatterns */
2575
0
  zval       tmp;
2576
0
  pcre2_match_data *match_data;
2577
0
  bool old_mdata_used;
2578
0
  char *subject = ZSTR_VAL(subject_str);
2579
2580
0
  no_empty = flags & PREG_SPLIT_NO_EMPTY;
2581
0
  delim_capture = flags & PREG_SPLIT_DELIM_CAPTURE;
2582
0
  offset_capture = flags & PREG_SPLIT_OFFSET_CAPTURE;
2583
2584
  /* Initialize return value */
2585
0
  array_init(return_value);
2586
0
  HashTable *return_value_ht = Z_ARRVAL_P(return_value);
2587
2588
  /* Calculate the size of the offsets array, and allocate memory for it. */
2589
0
  num_subpats = pce->capture_count + 1;
2590
2591
  /* Start at the beginning of the string */
2592
0
  start_offset = 0;
2593
0
  last_match_offset = 0;
2594
0
  PCRE_G(error_code) = PHP_PCRE_NO_ERROR;
2595
2596
0
  if (limit_val == -1) {
2597
    /* pass */
2598
0
  } else if (limit_val == 0) {
2599
0
    limit_val = -1;
2600
0
  } else if (limit_val <= 1) {
2601
0
    goto last;
2602
0
  }
2603
2604
0
  old_mdata_used = mdata_used;
2605
0
  if (!old_mdata_used && num_subpats <= PHP_PCRE_PREALLOC_MDATA_SIZE) {
2606
0
    mdata_used = true;
2607
0
    match_data = mdata;
2608
0
  } else {
2609
0
    match_data = pcre2_match_data_create_from_pattern(pce->re, PCRE_G(gctx_zmm));
2610
0
    if (!match_data) {
2611
0
      PCRE_G(error_code) = PHP_PCRE_INTERNAL_ERROR;
2612
0
      zval_ptr_dtor(return_value);
2613
0
      RETURN_FALSE;
2614
0
    }
2615
0
  }
2616
2617
0
  options = (pce->compile_options & PCRE2_UTF) ? 0 : PCRE2_NO_UTF_CHECK;
2618
2619
  /* Array of subpattern offsets */
2620
0
  PCRE2_SIZE *const offsets = pcre2_get_ovector_pointer(match_data);
2621
2622
#ifdef HAVE_PCRE_JIT_SUPPORT
2623
  if ((pce->preg_options & PREG_JIT) && options) {
2624
    count = pcre2_jit_match(pce->re, (PCRE2_SPTR)subject, ZSTR_LEN(subject_str), start_offset,
2625
        PCRE2_NO_UTF_CHECK, match_data, mctx);
2626
  } else
2627
#endif
2628
0
  count = pcre2_match(pce->re, (PCRE2_SPTR)subject, ZSTR_LEN(subject_str), start_offset,
2629
0
      options, match_data, mctx);
2630
2631
0
  while (1) {
2632
    /* If something matched */
2633
0
    if (count >= 0) {
2634
      /* Check for too many substrings condition. */
2635
0
      if (UNEXPECTED(count == 0)) {
2636
0
        php_error_docref(NULL,E_NOTICE, "Matched, but too many substrings");
2637
0
        count = num_subpats;
2638
0
      }
2639
2640
0
matched:
2641
0
      if (UNEXPECTED(offsets[1] < offsets[0])) {
2642
0
        PCRE_G(error_code) = PHP_PCRE_INTERNAL_ERROR;
2643
0
        break;
2644
0
      }
2645
2646
0
      if (!no_empty || offsets[0] != last_match_offset) {
2647
0
        if (offset_capture) {
2648
          /* Add (match, offset) pair to the return value */
2649
0
          add_offset_pair(
2650
0
            return_value_ht, subject, last_match_offset, offsets[0],
2651
0
            NULL, 0);
2652
0
        } else {
2653
          /* Add the piece to the return value */
2654
0
          populate_match_value_str(&tmp, subject, last_match_offset, offsets[0]);
2655
0
          zend_hash_next_index_insert_new(return_value_ht, &tmp);
2656
0
        }
2657
2658
        /* One less left to do */
2659
0
        if (limit_val != -1)
2660
0
          limit_val--;
2661
0
      }
2662
2663
0
      if (delim_capture) {
2664
0
        size_t i;
2665
0
        for (i = 1; i < count; i++) {
2666
          /* If we have matched a delimiter */
2667
0
          if (!no_empty || offsets[2*i] != offsets[2*i+1]) {
2668
0
            if (offset_capture) {
2669
0
              add_offset_pair(
2670
0
                return_value_ht, subject, offsets[2*i], offsets[2*i+1], NULL, 0);
2671
0
            } else {
2672
0
              populate_match_value_str(&tmp, subject, offsets[2*i], offsets[2*i+1]);
2673
0
              zend_hash_next_index_insert_new(return_value_ht, &tmp);
2674
0
            }
2675
0
          }
2676
0
        }
2677
0
      }
2678
2679
      /* Advance to the position right after the last full match */
2680
0
      start_offset = last_match_offset = offsets[1];
2681
2682
      /* If we have matched an empty string, mimic what Perl's /g options does.
2683
         This turns out to be rather cunning. First we set PCRE2_NOTEMPTY_ATSTART and try
2684
         the match again at the same point. If this fails (picked up above) we
2685
         advance to the next character. */
2686
0
      if (start_offset == offsets[0]) {
2687
        /* Get next piece if no limit or limit not yet reached and something matched*/
2688
0
        if (limit_val != -1 && limit_val <= 1) {
2689
0
          break;
2690
0
        }
2691
0
        count = pcre2_match(pce->re, (PCRE2_SPTR)subject, ZSTR_LEN(subject_str), start_offset,
2692
0
          PCRE2_NO_UTF_CHECK | PCRE2_NOTEMPTY_ATSTART | PCRE2_ANCHORED, match_data, mctx);
2693
0
        if (count >= 0) {
2694
0
          goto matched;
2695
0
        } else if (count == PCRE2_ERROR_NOMATCH) {
2696
          /* If we previously set PCRE2_NOTEMPTY_ATSTART after a null match,
2697
             this is not necessarily the end. We need to advance
2698
             the start offset, and continue. Fudge the offset values
2699
             to achieve this, unless we're already at the end of the string. */
2700
0
          if (start_offset < ZSTR_LEN(subject_str)) {
2701
0
            start_offset += calculate_unit_length(pce, subject + start_offset);
2702
0
          } else {
2703
0
            break;
2704
0
          }
2705
0
        } else {
2706
0
          goto error;
2707
0
        }
2708
0
      }
2709
2710
0
    } else if (count == PCRE2_ERROR_NOMATCH) {
2711
0
      break;
2712
0
    } else {
2713
0
error:
2714
0
      pcre_handle_exec_error(count);
2715
0
      break;
2716
0
    }
2717
2718
    /* Get next piece if no limit or limit not yet reached and something matched*/
2719
0
    if (limit_val != -1 && limit_val <= 1) {
2720
0
      break;
2721
0
    }
2722
2723
#ifdef HAVE_PCRE_JIT_SUPPORT
2724
    if (pce->preg_options & PREG_JIT) {
2725
      count = pcre2_jit_match(pce->re, (PCRE2_SPTR)subject, ZSTR_LEN(subject_str), start_offset,
2726
          PCRE2_NO_UTF_CHECK, match_data, mctx);
2727
    } else
2728
#endif
2729
0
    count = pcre2_match(pce->re, (PCRE2_SPTR)subject, ZSTR_LEN(subject_str), start_offset,
2730
0
        PCRE2_NO_UTF_CHECK, match_data, mctx);
2731
0
  }
2732
0
  if (match_data != mdata) {
2733
0
    pcre2_match_data_free(match_data);
2734
0
  }
2735
0
  mdata_used = old_mdata_used;
2736
2737
0
  if (PCRE_G(error_code) != PHP_PCRE_NO_ERROR) {
2738
0
    zval_ptr_dtor(return_value);
2739
0
    RETURN_FALSE;
2740
0
  }
2741
2742
0
last:
2743
0
  start_offset = last_match_offset; /* the offset might have been incremented, but without further successful matches */
2744
2745
0
  if (!no_empty || start_offset < ZSTR_LEN(subject_str)) {
2746
0
    if (offset_capture) {
2747
      /* Add the last (match, offset) pair to the return value */
2748
0
      add_offset_pair(return_value_ht, subject, start_offset, ZSTR_LEN(subject_str), NULL, 0);
2749
0
    } else {
2750
      /* Add the last piece to the return value */
2751
0
      if (start_offset == 0) {
2752
0
        ZVAL_STR_COPY(&tmp, subject_str);
2753
0
      } else {
2754
0
        populate_match_value_str(&tmp, subject, start_offset, ZSTR_LEN(subject_str));
2755
0
      }
2756
0
      zend_hash_next_index_insert_new(return_value_ht, &tmp);
2757
0
    }
2758
0
  }
2759
0
}
2760
/* }}} */
2761
2762
/* {{{ Quote regular expression characters plus an optional character */
2763
PHP_FUNCTION(preg_quote)
2764
56
{
2765
56
  zend_string *str;           /* Input string argument */
2766
56
  zend_string *delim = NULL;   /* Additional delimiter argument */
2767
56
  char    *in_str;      /* Input string */
2768
56
  char    *in_str_end;      /* End of the input string */
2769
56
  zend_string *out_str;     /* Output string with quoted characters */
2770
56
  size_t       extra_len;         /* Number of additional characters */
2771
56
  char    *p,         /* Iterator for input string */
2772
56
        *q,         /* Iterator for output string */
2773
56
         delim_char = '\0', /* Delimiter character to be quoted */
2774
56
         c;         /* Current character */
2775
2776
  /* Get the arguments and check for errors */
2777
168
  ZEND_PARSE_PARAMETERS_START(1, 2)
2778
224
    Z_PARAM_STR(str)
2779
56
    Z_PARAM_OPTIONAL
2780
132
    Z_PARAM_STR_OR_NULL(delim)
2781
56
  ZEND_PARSE_PARAMETERS_END();
2782
2783
  /* Nothing to do if we got an empty string */
2784
56
  if (ZSTR_LEN(str) == 0) {
2785
0
    RETURN_EMPTY_STRING();
2786
0
  }
2787
2788
56
  in_str = ZSTR_VAL(str);
2789
56
  in_str_end = in_str + ZSTR_LEN(str);
2790
2791
56
  if (delim) {
2792
10
    delim_char = ZSTR_VAL(delim)[0];
2793
10
  }
2794
2795
  /* Go through the string and quote necessary characters */
2796
56
  extra_len = 0;
2797
56
  p = in_str;
2798
75.1k
  do {
2799
75.1k
    c = *p;
2800
75.1k
    switch(c) {
2801
752
      case '.':
2802
1.22k
      case '\\':
2803
1.82k
      case '+':
2804
1.90k
      case '*':
2805
2.11k
      case '?':
2806
2.26k
      case '[':
2807
2.32k
      case '^':
2808
2.46k
      case ']':
2809
2.46k
      case '$':
2810
2.74k
      case '(':
2811
3.47k
      case ')':
2812
3.60k
      case '{':
2813
4.21k
      case '}':
2814
4.81k
      case '=':
2815
4.84k
      case '!':
2816
5.18k
      case '>':
2817
5.22k
      case '<':
2818
5.31k
      case '|':
2819
5.82k
      case ':':
2820
6.09k
      case '-':
2821
6.70k
      case '#':
2822
6.70k
        extra_len++;
2823
6.70k
        break;
2824
2825
4.07k
      case '\0':
2826
4.07k
        extra_len+=3;
2827
4.07k
        break;
2828
2829
64.3k
      default:
2830
64.3k
        if (c == delim_char) {
2831
143
          extra_len++;
2832
143
        }
2833
64.3k
        break;
2834
75.1k
    }
2835
75.1k
    p++;
2836
75.1k
  } while (p != in_str_end);
2837
2838
56
  if (extra_len == 0) {
2839
1
    RETURN_STR_COPY(str);
2840
1
  }
2841
2842
  /* Allocate enough memory so that even if each character
2843
     is quoted, we won't run out of room */
2844
55
  out_str = zend_string_safe_alloc(1, ZSTR_LEN(str), extra_len, 0);
2845
55
  q = ZSTR_VAL(out_str);
2846
55
  p = in_str;
2847
2848
75.1k
  do {
2849
75.1k
    c = *p;
2850
75.1k
    switch(c) {
2851
752
      case '.':
2852
1.22k
      case '\\':
2853
1.82k
      case '+':
2854
1.90k
      case '*':
2855
2.11k
      case '?':
2856
2.26k
      case '[':
2857
2.32k
      case '^':
2858
2.46k
      case ']':
2859
2.46k
      case '$':
2860
2.74k
      case '(':
2861
3.47k
      case ')':
2862
3.60k
      case '{':
2863
4.21k
      case '}':
2864
4.81k
      case '=':
2865
4.84k
      case '!':
2866
5.18k
      case '>':
2867
5.22k
      case '<':
2868
5.31k
      case '|':
2869
5.82k
      case ':':
2870
6.09k
      case '-':
2871
6.70k
      case '#':
2872
6.70k
        *q++ = '\\';
2873
6.70k
        *q++ = c;
2874
6.70k
        break;
2875
2876
4.07k
      case '\0':
2877
4.07k
        *q++ = '\\';
2878
4.07k
        *q++ = '0';
2879
4.07k
        *q++ = '0';
2880
4.07k
        *q++ = '0';
2881
4.07k
        break;
2882
2883
64.3k
      default:
2884
64.3k
        if (c == delim_char) {
2885
143
          *q++ = '\\';
2886
143
        }
2887
64.3k
        *q++ = c;
2888
64.3k
        break;
2889
75.1k
    }
2890
75.1k
    p++;
2891
75.1k
  } while (p != in_str_end);
2892
55
  *q = '\0';
2893
2894
55
  RETURN_NEW_STR(out_str);
2895
55
}
2896
/* }}} */
2897
2898
/* {{{ Searches array and returns entries which match regex */
2899
PHP_FUNCTION(preg_grep)
2900
0
{
2901
0
  zend_string     *regex;     /* Regular expression */
2902
0
  zval        *input;     /* Input array */
2903
0
  zend_long      flags = 0;   /* Match control flags */
2904
0
  pcre_cache_entry  *pce;     /* Compiled regular expression */
2905
2906
  /* Get arguments and do error checking */
2907
0
  ZEND_PARSE_PARAMETERS_START(2, 3)
2908
0
    Z_PARAM_STR(regex)
2909
0
    Z_PARAM_ARRAY(input)
2910
0
    Z_PARAM_OPTIONAL
2911
0
    Z_PARAM_LONG(flags)
2912
0
  ZEND_PARSE_PARAMETERS_END();
2913
2914
  /* Compile regex or get it from cache. */
2915
0
  if ((pce = pcre_get_compiled_regex_cache(regex)) == NULL) {
2916
0
    RETURN_FALSE;
2917
0
  }
2918
2919
0
  pce->refcount++;
2920
0
  php_pcre_grep_impl(pce, input, return_value, flags);
2921
0
  pce->refcount--;
2922
0
}
2923
/* }}} */
2924
2925
PHPAPI void  php_pcre_grep_impl(pcre_cache_entry *pce, zval *input, zval *return_value, zend_long flags) /* {{{ */
2926
0
{
2927
0
  zval            *entry;             /* An entry in the input array */
2928
0
  uint32_t     num_subpats;   /* Number of captured subpatterns */
2929
0
  int        count;       /* Count of matched subpatterns */
2930
0
  uint32_t     options;     /* Execution options */
2931
0
  zend_string   *string_key;
2932
0
  zend_ulong     num_key;
2933
0
  bool     invert;      /* Whether to return non-matching
2934
                       entries */
2935
0
  bool old_mdata_used;
2936
0
  pcre2_match_data *match_data;
2937
0
  invert = flags & PREG_GREP_INVERT ? 1 : 0;
2938
2939
  /* Calculate the size of the offsets array, and allocate memory for it. */
2940
0
  num_subpats = pce->capture_count + 1;
2941
2942
  /* Initialize return array */
2943
0
  array_init(return_value);
2944
0
  HashTable *return_value_ht = Z_ARRVAL_P(return_value);
2945
2946
0
  PCRE_G(error_code) = PHP_PCRE_NO_ERROR;
2947
2948
0
  old_mdata_used = mdata_used;
2949
0
  if (!old_mdata_used && num_subpats <= PHP_PCRE_PREALLOC_MDATA_SIZE) {
2950
0
    mdata_used = true;
2951
0
    match_data = mdata;
2952
0
  } else {
2953
0
    match_data = pcre2_match_data_create_from_pattern(pce->re, PCRE_G(gctx_zmm));
2954
0
    if (!match_data) {
2955
0
      PCRE_G(error_code) = PHP_PCRE_INTERNAL_ERROR;
2956
0
      return;
2957
0
    }
2958
0
  }
2959
2960
0
  options = (pce->compile_options & PCRE2_UTF) ? 0 : PCRE2_NO_UTF_CHECK;
2961
2962
  /* Go through the input array */
2963
0
  ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(input), num_key, string_key, entry) {
2964
0
    zend_string *tmp_subject_str;
2965
0
    zend_string *subject_str = zval_get_tmp_string(entry, &tmp_subject_str);
2966
2967
    /* Perform the match */
2968
#ifdef HAVE_PCRE_JIT_SUPPORT
2969
    if ((pce->preg_options & PREG_JIT) && options) {
2970
      count = pcre2_jit_match(pce->re, (PCRE2_SPTR)ZSTR_VAL(subject_str), ZSTR_LEN(subject_str), 0,
2971
          PCRE2_NO_UTF_CHECK, match_data, mctx);
2972
    } else
2973
#endif
2974
0
    count = pcre2_match(pce->re, (PCRE2_SPTR)ZSTR_VAL(subject_str), ZSTR_LEN(subject_str), 0,
2975
0
        options, match_data, mctx);
2976
2977
    /* If the entry fits our requirements */
2978
0
    if (count >= 0) {
2979
      /* Check for too many substrings condition. */
2980
0
      if (UNEXPECTED(count == 0)) {
2981
0
        php_error_docref(NULL, E_NOTICE, "Matched, but too many substrings");
2982
0
      }
2983
0
      if (!invert) {
2984
0
        Z_TRY_ADDREF_P(entry);
2985
2986
        /* Add to return array */
2987
0
        if (string_key) {
2988
0
          zend_hash_update(return_value_ht, string_key, entry);
2989
0
        } else {
2990
0
          zend_hash_index_update(return_value_ht, num_key, entry);
2991
0
        }
2992
0
      }
2993
0
    } else if (count == PCRE2_ERROR_NOMATCH) {
2994
0
      if (invert) {
2995
0
        Z_TRY_ADDREF_P(entry);
2996
2997
        /* Add to return array */
2998
0
        if (string_key) {
2999
0
          zend_hash_update(return_value_ht, string_key, entry);
3000
0
        } else {
3001
0
          zend_hash_index_update(return_value_ht, num_key, entry);
3002
0
        }
3003
0
      }
3004
0
    } else {
3005
0
      pcre_handle_exec_error(count);
3006
0
      zend_tmp_string_release(tmp_subject_str);
3007
0
      break;
3008
0
    }
3009
3010
0
    zend_tmp_string_release(tmp_subject_str);
3011
0
  } ZEND_HASH_FOREACH_END();
3012
0
  if (match_data != mdata) {
3013
0
    pcre2_match_data_free(match_data);
3014
0
  }
3015
3016
0
  mdata_used = old_mdata_used;
3017
3018
0
  if (PCRE_G(error_code) != PHP_PCRE_NO_ERROR) {
3019
0
    zend_array_destroy(Z_ARR_P(return_value));
3020
0
    RETURN_FALSE;
3021
0
  }
3022
0
}
3023
/* }}} */
3024
3025
/* {{{ Returns the error code of the last regexp execution. */
3026
PHP_FUNCTION(preg_last_error)
3027
0
{
3028
0
  ZEND_PARSE_PARAMETERS_NONE();
3029
3030
0
  RETURN_LONG(PCRE_G(error_code));
3031
0
}
3032
/* }}} */
3033
3034
/* {{{ Returns the error message of the last regexp execution. */
3035
PHP_FUNCTION(preg_last_error_msg)
3036
0
{
3037
0
  ZEND_PARSE_PARAMETERS_NONE();
3038
3039
0
  RETURN_STRING(php_pcre_get_error_msg(PCRE_G(error_code)));
3040
0
}
3041
/* }}} */
3042
3043
/* {{{ module definition structures */
3044
3045
zend_module_entry pcre_module_entry = {
3046
  STANDARD_MODULE_HEADER,
3047
  "pcre",
3048
  ext_functions,
3049
  PHP_MINIT(pcre),
3050
  PHP_MSHUTDOWN(pcre),
3051
  PHP_RINIT(pcre),
3052
  PHP_RSHUTDOWN(pcre),
3053
  PHP_MINFO(pcre),
3054
  PHP_PCRE_VERSION,
3055
  PHP_MODULE_GLOBALS(pcre),
3056
  PHP_GINIT(pcre),
3057
  PHP_GSHUTDOWN(pcre),
3058
  NULL,
3059
  STANDARD_MODULE_PROPERTIES_EX
3060
};
3061
3062
#ifdef COMPILE_DL_PCRE
3063
ZEND_GET_MODULE(pcre)
3064
#endif
3065
3066
/* }}} */
3067
3068
PHPAPI pcre2_match_context *php_pcre_mctx(void)
3069
10
{/*{{{*/
3070
10
  return mctx;
3071
10
}/*}}}*/
3072
3073
PHPAPI pcre2_general_context *php_pcre_gctx(void)
3074
0
{/*{{{*/
3075
0
  return gctx;
3076
0
}/*}}}*/
3077
3078
PHPAPI pcre2_compile_context *php_pcre_cctx(void)
3079
0
{/*{{{*/
3080
0
  return cctx;
3081
0
}/*}}}*/
3082
3083
PHPAPI void php_pcre_pce_incref(pcre_cache_entry *pce)
3084
0
{/*{{{*/
3085
0
  assert(NULL != pce);
3086
0
  pce->refcount++;
3087
0
}/*}}}*/
3088
3089
PHPAPI void php_pcre_pce_decref(pcre_cache_entry *pce)
3090
0
{/*{{{*/
3091
0
  assert(NULL != pce);
3092
0
  assert(0 != pce->refcount);
3093
0
  pce->refcount--;
3094
0
}/*}}}*/
3095
3096
PHPAPI pcre2_code *php_pcre_pce_re(pcre_cache_entry *pce)
3097
0
{/*{{{*/
3098
0
  assert(NULL != pce);
3099
0
  return pce->re;
3100
0
}/*}}}*/