Coverage Report

Created: 2026-10-28 06:20

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
0
#define PREG_OFFSET_CAPTURE     (1<<8)
26
0
#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
141
#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 bool pcre2_init_ok = false;
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
53
{
107
53
  php_pcre_error_code preg_code = PHP_PCRE_NO_ERROR;
108
109
53
  switch (pcre_code) {
110
2
    case PCRE2_ERROR_MATCHLIMIT:
111
2
      preg_code = PHP_PCRE_BACKTRACK_LIMIT_ERROR;
112
2
      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
51
    default:
129
51
      if (pcre_code <= PCRE2_ERROR_UTF8_ERR1 && pcre_code >= PCRE2_ERROR_UTF8_ERR21) {
130
8
        preg_code = PHP_PCRE_BAD_UTF8_ERROR;
131
43
      } else  {
132
43
        preg_code = PHP_PCRE_INTERNAL_ERROR;
133
43
      }
134
51
      break;
135
53
  }
136
137
53
  PCRE_G(error_code) = preg_code;
138
53
}
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
206
{
182
206
  return pemalloc(size, 1);
183
206
}
184
185
static void php_pcre_free(void *block, void *data)
186
1
{
187
1
  pefree(block, 1);
188
1
}
189
190
static void *php_pcre_emalloc(PCRE2_SIZE size, void *data)
191
306k
{
192
306k
  return femalloc(size, /* fallible */ true);
193
306k
}
194
195
static void php_pcre_efree(void *block, void *data)
196
306k
{
197
306k
  efree(block);
198
306k
}
199
200
330
#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 = false;
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 = false;
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 = false;
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 = false;
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 = false;
242
0
      return;
243
0
    }
244
16
  }
245
246
16
  pcre2_init_ok = true;
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 = false;
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
16
{/*{{{*/
357
16
  int len = pcre2_config(what, NULL);
358
16
  char *ret = (char *) malloc(len + 1);
359
360
16
  len = pcre2_config(what, ret);
361
16
  if (!len) {
362
0
    free(ret);
363
0
    return NULL;
364
0
  }
365
366
16
  return ret;
367
16
}/*}}}*/
368
369
/* {{{ PHP_MINFO_FUNCTION(pcre) */
370
static PHP_MINFO_FUNCTION(pcre)
371
0
{
372
#ifdef HAVE_PCRE_JIT_SUPPORT
373
  uint32_t flag = 0;
374
  char *jit_target = _pcre2_config_str(PCRE2_CONFIG_JITTARGET);
375
#endif
376
0
  char *version = _pcre2_config_str(PCRE2_CONFIG_VERSION);
377
0
  char *unicode = _pcre2_config_str(PCRE2_CONFIG_UNICODE_VERSION);
378
379
0
  php_info_print_table_start();
380
0
  php_info_print_table_row(2, "PCRE (Perl Compatible Regular Expressions) Support", "enabled" );
381
0
  php_info_print_table_row(2, "PCRE Library Version", version);
382
0
  free(version);
383
0
  php_info_print_table_row(2, "PCRE Unicode Version", unicode);
384
0
  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
0
  php_info_print_table_row(2, "PCRE JIT Support", "not compiled in" );
398
0
#endif
399
400
#ifdef HAVE_PCRE_VALGRIND_SUPPORT
401
  php_info_print_table_row(2, "PCRE Valgrind Support", "enabled" );
402
#endif
403
404
0
  php_info_print_table_end();
405
406
0
  DISPLAY_INI_ENTRIES();
407
0
}
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
306k
{
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
306k
  PCRE_G(error_code) = PHP_PCRE_NO_ERROR;
463
306k
  PCRE_G(gctx_zmm) = pcre2_general_context_create(php_pcre_emalloc, php_pcre_efree, NULL);
464
306k
  if (!PCRE_G(gctx_zmm)) {
465
0
    return FAILURE;
466
0
  }
467
468
306k
  return SUCCESS;
469
306k
}
470
/* }}} */
471
472
static PHP_RSHUTDOWN_FUNCTION(pcre)
473
306k
{
474
306k
  pcre_cache_entry *pce;
475
14.4M
  ZEND_HASH_MAP_FOREACH_PTR(&PCRE_G(pcre_cache), pce) {
476
14.4M
    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
14.4M
  } ZEND_HASH_FOREACH_END();
481
482
306k
  pcre2_general_context_free(PCRE_G(gctx_zmm));
483
306k
  PCRE_G(gctx_zmm) = NULL;
484
485
306k
  zval_ptr_dtor(&PCRE_G(unmatched_null_pair));
486
306k
  zval_ptr_dtor(&PCRE_G(unmatched_empty_pair));
487
306k
  ZVAL_UNDEF(&PCRE_G(unmatched_null_pair));
488
306k
  ZVAL_UNDEF(&PCRE_G(unmatched_empty_pair));
489
306k
  return SUCCESS;
490
306k
}
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, const 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(const pcre_cache_entry *pce, const char *start)
557
49
{
558
49
  size_t unit_len;
559
560
49
  if (pce->compile_options & PCRE2_UTF) {
561
2
    const char *end = start;
562
563
    /* skip continuation bytes */
564
2
    while ((*++end & 0xC0) == 0x80);
565
2
    unit_len = end - start;
566
47
  } else {
567
47
    unit_len = 1;
568
47
  }
569
49
  return unit_len;
570
49
}
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
357
{
576
357
  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
357
  uint32_t       coptions = 0;
581
357
#endif
582
357
  uint32_t       eoptions = 0;
583
357
  PCRE2_UCHAR           error[128];
584
357
  PCRE2_SIZE           erroffset;
585
357
  int                  errnumber;
586
357
  char         delimiter;
587
357
  char         start_delimiter;
588
357
  char         end_delimiter;
589
357
  char        *p, *pp;
590
357
  char        *pattern;
591
357
  size_t         pattern_len;
592
357
  uint32_t       poptions = 0;
593
357
  const uint8_t       *tables = NULL;
594
357
  zval                *zv;
595
357
  pcre_cache_entry   new_entry;
596
357
  int          rc;
597
357
  zend_string     *key;
598
357
  pcre_cache_entry  *ret;
599
600
357
  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
357
  } else {
605
357
    key = regex;
606
357
  }
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
357
  zv = zend_hash_find(&PCRE_G(pcre_cache), key);
611
357
  if (zv) {
612
173
    if (key != regex) {
613
0
      zend_string_release_ex(key, 0);
614
0
    }
615
173
    return (pcre_cache_entry*)Z_PTR_P(zv);
616
173
  }
617
618
184
  p = ZSTR_VAL(regex);
619
184
  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
184
  while (isspace((unsigned char)*p)) p++;
624
184
  if (p >= end_p) {
625
0
    if (key != regex) {
626
0
      zend_string_release_ex(key, 0);
627
0
    }
628
0
    php_error_docref(NULL, E_WARNING, "Empty regular expression");
629
0
    pcre_handle_exec_error(PCRE2_ERROR_INTERNAL);
630
0
    return NULL;
631
0
  }
632
633
  /* Get the delimiter and display a warning if it is alphanumeric
634
     or a backslash. */
635
184
  delimiter = *p++;
636
184
  if (isalnum((unsigned char)delimiter) || delimiter == '\\' || delimiter == '\0') {
637
0
    if (key != regex) {
638
0
      zend_string_release_ex(key, 0);
639
0
    }
640
0
    php_error_docref(NULL, E_WARNING, "Delimiter must not be alphanumeric, backslash, or NUL byte");
641
0
    pcre_handle_exec_error(PCRE2_ERROR_INTERNAL);
642
0
    return NULL;
643
0
  }
644
645
184
  start_delimiter = delimiter;
646
184
  if ((pp = strchr("([{< )]}> )]}>", delimiter)))
647
16
    delimiter = pp[5];
648
184
  end_delimiter = delimiter;
649
650
184
  pp = p;
651
652
184
  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
11.0k
    while (pp < end_p) {
657
11.0k
      if (*pp == '\\' && pp + 1 < end_p) pp++;
658
10.3k
      else if (*pp == delimiter)
659
168
        break;
660
10.9k
      pp++;
661
10.9k
    }
662
183
  } 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
1
    int brackets = 1;   /* brackets nesting level */
669
78
    while (pp < end_p) {
670
77
      if (*pp == '\\' && pp + 1 < end_p) pp++;
671
66
      else if (*pp == end_delimiter && --brackets <= 0)
672
0
        break;
673
66
      else if (*pp == start_delimiter)
674
0
        brackets++;
675
77
      pp++;
676
77
    }
677
1
  }
678
679
184
  if (pp >= end_p) {
680
16
    if (key != regex) {
681
0
      zend_string_release_ex(key, 0);
682
0
    }
683
16
    if (start_delimiter == end_delimiter) {
684
15
      php_error_docref(NULL,E_WARNING, "No ending delimiter '%c' found", delimiter);
685
15
    } else {
686
1
      php_error_docref(NULL,E_WARNING, "No ending matching delimiter '%c' found", delimiter);
687
1
    }
688
16
    pcre_handle_exec_error(PCRE2_ERROR_INTERNAL);
689
16
    return NULL;
690
16
  }
691
692
  /* Make a copy of the actual pattern. */
693
168
  pattern_len = pp - p;
694
168
  pattern = estrndup(p, pattern_len);
695
696
  /* Move on to the options */
697
168
  pp++;
698
699
  /* Parse through the options, setting appropriate flags.  Display
700
     a warning if we encounter an unknown modifier. */
701
483
  while (pp < end_p) {
702
316
    switch (*pp++) {
703
      /* Perl compatible options */
704
91
      case 'i': coptions |= PCRE2_CASELESS;   break;
705
6
      case 'm': coptions |= PCRE2_MULTILINE;   break;
706
0
      case 'n': coptions |= PCRE2_NO_AUTO_CAPTURE; break;
707
40
      case 's': coptions |= PCRE2_DOTALL;   break;
708
2
      case 'x': coptions |= PCRE2_EXTENDED;   break;
709
710
      /* PCRE specific options */
711
0
      case 'A': coptions |= PCRE2_ANCHORED;   break;
712
0
      case 'D': coptions |= PCRE2_DOLLAR_ENDONLY;break;
713
0
#ifdef PCRE2_EXTRA_CASELESS_RESTRICT
714
6
      case 'r': eoptions |= PCRE2_EXTRA_CASELESS_RESTRICT; break;
715
0
#endif
716
6
      case 'S': /* Pass. */         break;
717
8
      case 'X': /* Pass. */         break;
718
20
      case 'U': coptions |= PCRE2_UNGREEDY;   break;
719
99
      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
99
#ifdef PCRE2_UCP
724
99
            coptions |= PCRE2_UCP;
725
99
#endif
726
            /* The \C escape sequence is unsafe in PCRE2_UTF mode */
727
99
            coptions |= PCRE2_NEVER_BACKSLASH_C;
728
99
        break;
729
15
      case 'J': coptions |= PCRE2_DUPNAMES;   break;
730
731
0
      case ' ':
732
21
      case '\n':
733
22
      case '\r':
734
22
        break;
735
736
0
      case 'e': /* legacy eval */
737
1
      default:
738
1
        if (pp[-1]) {
739
1
          php_error_docref(NULL, E_WARNING, "Unknown modifier '%c'", pp[-1]);
740
1
        } else {
741
0
          php_error_docref(NULL, E_WARNING, "NUL byte is not a valid modifier");
742
0
        }
743
1
        pcre_handle_exec_error(PCRE2_ERROR_INTERNAL);
744
1
        efree(pattern);
745
1
        if (key != regex) {
746
0
          zend_string_release_ex(key, 0);
747
0
        }
748
1
        return NULL;
749
316
    }
750
316
  }
751
752
167
  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
167
  pcre2_set_character_tables(cctx, tables);
773
774
167
  pcre2_set_compile_extra_options(cctx, eoptions);
775
776
  /* Compile pattern and display a warning if compilation failed. */
777
167
  re = pcre2_compile((PCRE2_SPTR)pattern, pattern_len, coptions, &errnumber, &erroffset, cctx);
778
779
167
  if (re == NULL) {
780
26
    if (key != regex) {
781
0
      zend_string_release_ex(key, 0);
782
0
    }
783
26
    const char *err_msg = (const char*) error;
784
26
    if (errnumber == PCRE2_ERROR_BACKSLASH_C_CALLER_DISABLED) {
785
2
      err_msg = "using \\C is incompatible with the 'u' modifier";
786
24
    } else {
787
24
      pcre2_get_error_message(errnumber, error, sizeof(error));
788
24
    }
789
26
    php_error_docref(NULL,E_WARNING, "Compilation failed: %s at offset %zu", err_msg, erroffset);
790
26
    pcre_handle_exec_error(PCRE2_ERROR_INTERNAL);
791
26
    efree(pattern);
792
26
    return NULL;
793
26
  }
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
141
  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
141
  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
141
  new_entry.re = re;
831
141
  new_entry.preg_options = poptions;
832
141
  new_entry.compile_options = coptions;
833
141
  new_entry.refcount = 0;
834
141
  new_entry.subpats_table = NULL;
835
836
141
  if ((rc = pcre2_pattern_info(re, PCRE2_INFO_CAPTURECOUNT, &new_entry.capture_count)) < 0 ||
837
141
      (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
141
  if (!(GC_FLAGS(key) & IS_STR_PERMANENT)) {
856
141
    zend_string *str = zend_string_init(ZSTR_VAL(key), ZSTR_LEN(key), 1);
857
141
    GC_MAKE_PERSISTENT_LOCAL(str);
858
859
141
    ret = zend_hash_add_new_mem(&PCRE_G(pcre_cache), str, &new_entry, sizeof(pcre_cache_entry));
860
141
    zend_string_release(str);
861
141
  } else {
862
0
    ret = zend_hash_add_new_mem(&PCRE_G(pcre_cache), key, &new_entry, sizeof(pcre_cache_entry));
863
0
  }
864
865
141
  if (key != regex) {
866
0
    zend_string_release_ex(key, 0);
867
0
  }
868
869
141
  return ret;
870
141
}
871
/* }}} */
872
873
/* {{{ pcre_get_compiled_regex_cache */
874
PHPAPI pcre_cache_entry* pcre_get_compiled_regex_cache(zend_string *regex)
875
357
{
876
357
  return pcre_get_compiled_regex_cache_ex(regex, true);
877
357
}
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
0
    zval *val, const char *subject, PCRE2_SIZE start_offset, PCRE2_SIZE end_offset) {
942
0
  ZVAL_STRINGL_FAST(val, subject + start_offset, end_offset - start_offset);
943
0
}
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
0
    bool unmatched_as_null) {
948
0
  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
0
  } else {
955
0
    populate_match_value_str(val, subject, start_offset, end_offset);
956
0
  }
957
0
}
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 zend_result populate_subpat_array(
1024
    HashTable *subpats_ht, const char *subject, PCRE2_SIZE *offsets, zend_string **subpat_names,
1025
0
    uint32_t num_subpats, int count, const PCRE2_SPTR mark, zend_long flags) {
1026
0
  zend_long offset_capture = flags & PREG_OFFSET_CAPTURE;
1027
0
  zend_long unmatched_as_null = flags & PREG_UNMATCHED_AS_NULL;
1028
0
  zval val;
1029
0
  int i;
1030
0
  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
        if (ZEND_HAS_OOM()) {
1037
0
          return FAILURE;
1038
0
        }
1039
0
      }
1040
0
      if (unmatched_as_null) {
1041
0
        for (i = count; i < num_subpats; i++) {
1042
0
          add_offset_pair(subpats_ht, NULL, PCRE2_UNSET, PCRE2_UNSET, subpat_names[i], 1);
1043
0
          if (ZEND_HAS_OOM()) {
1044
0
            return FAILURE;
1045
0
          }
1046
0
        }
1047
0
      }
1048
0
    } else {
1049
0
      for (i = 0; i < count; i++) {
1050
0
        populate_match_value(
1051
0
          &val, subject, offsets[2*i], offsets[2*i+1], unmatched_as_null);
1052
0
        if (subpat_names[i]) {
1053
0
          add_named(subpats_ht, subpat_names[i], &val, offsets[2*i] == PCRE2_UNSET);
1054
0
        }
1055
0
        zend_hash_next_index_insert_new(subpats_ht, &val);
1056
0
        if (ZEND_HAS_OOM()) {
1057
0
          return FAILURE;
1058
0
        }
1059
0
      }
1060
0
      if (unmatched_as_null) {
1061
0
        for (i = count; i < num_subpats; i++) {
1062
0
          ZVAL_NULL(&val);
1063
0
          if (subpat_names[i]) {
1064
0
            zend_hash_add(subpats_ht, subpat_names[i], &val);
1065
0
          }
1066
0
          zend_hash_next_index_insert_new(subpats_ht, &val);
1067
0
          if (ZEND_HAS_OOM()) {
1068
0
            return FAILURE;
1069
0
          }
1070
0
        }
1071
0
      }
1072
0
    }
1073
0
  } else {
1074
0
    if (offset_capture) {
1075
0
      for (i = 0; i < count; i++) {
1076
0
        add_offset_pair(
1077
0
          subpats_ht, subject, offsets[2*i], offsets[2*i+1], NULL, unmatched_as_null);
1078
0
        if (ZEND_HAS_OOM()) {
1079
0
          return FAILURE;
1080
0
        }
1081
0
      }
1082
0
      if (unmatched_as_null) {
1083
0
        for (i = count; i < num_subpats; i++) {
1084
0
          add_offset_pair(subpats_ht, NULL, PCRE2_UNSET, PCRE2_UNSET, NULL, 1);
1085
0
          if (ZEND_HAS_OOM()) {
1086
0
            return FAILURE;
1087
0
          }
1088
0
        }
1089
0
      }
1090
0
    } else {
1091
0
      for (i = 0; i < count; i++) {
1092
0
        populate_match_value(
1093
0
          &val, subject, offsets[2*i], offsets[2*i+1], unmatched_as_null);
1094
0
        zend_hash_next_index_insert_new(subpats_ht, &val);
1095
0
        if (ZEND_HAS_OOM()) {
1096
0
          return FAILURE;
1097
0
        }
1098
0
      }
1099
0
      if (unmatched_as_null) {
1100
0
        ZVAL_NULL(&val);
1101
0
        for (i = count; i < num_subpats; i++) {
1102
0
          zend_hash_next_index_insert_new(subpats_ht, &val);
1103
0
          if (ZEND_HAS_OOM()) {
1104
0
            return FAILURE;
1105
0
          }
1106
0
        }
1107
0
      }
1108
0
    }
1109
0
  }
1110
  /* Add MARK, if available */
1111
0
  if (mark) {
1112
0
    ZVAL_STRING(&val, (char *)mark);
1113
0
    zend_hash_str_update(subpats_ht, ZEND_STRL("MARK"), &val);
1114
0
  }
1115
0
  return ZEND_HAS_OOM() ? FAILURE : SUCCESS;
1116
0
}
1117
1118
static void php_do_pcre_match(INTERNAL_FUNCTION_PARAMETERS, bool global) /* {{{ */
1119
356
{
1120
  /* parameters */
1121
356
  zend_string    *regex;      /* Regular expression */
1122
356
  zend_string    *subject;      /* String to match against */
1123
356
  pcre_cache_entry *pce;        /* Compiled regular expression */
1124
356
  zval       *subpats = NULL; /* Array for subpatterns */
1125
356
  zend_long     flags = 0;    /* Match control flags */
1126
356
  zend_long     start_offset = 0; /* Where the new search starts */
1127
1128
1.06k
  ZEND_PARSE_PARAMETERS_START(2, 5)
1129
1.40k
    Z_PARAM_STR(regex)
1130
1.75k
    Z_PARAM_STR(subject)
1131
350
    Z_PARAM_OPTIONAL
1132
702
    Z_PARAM_ZVAL(subpats)
1133
702
    Z_PARAM_LONG(flags)
1134
0
    Z_PARAM_LONG(start_offset)
1135
356
  ZEND_PARSE_PARAMETERS_END();
1136
1137
  /* Compile regex or get it from cache. */
1138
350
  if ((pce = pcre_get_compiled_regex_cache(regex)) == NULL) {
1139
39
    RETURN_FALSE;
1140
39
  }
1141
1142
311
  if (start_offset == ZEND_LONG_MIN) {
1143
0
    zend_argument_value_error(5, "must be greater than " ZEND_LONG_FMT, ZEND_LONG_MIN);
1144
0
    RETURN_THROWS();
1145
0
  }
1146
1147
311
  pce->refcount++;
1148
311
  php_pcre_match_impl(pce, subject, return_value, subpats,
1149
311
    global, flags, start_offset);
1150
311
  pce->refcount--;
1151
311
}
1152
/* }}} */
1153
1154
static zend_always_inline bool is_known_valid_utf8(
1155
89
    const zend_string *subject_str, PCRE2_SIZE start_offset) {
1156
89
  if (!ZSTR_IS_VALID_UTF8(subject_str)) {
1157
    /* We don't know whether the string is valid UTF-8 or not. */
1158
89
    return false;
1159
89
  }
1160
1161
0
  if (start_offset == ZSTR_LEN(subject_str)) {
1162
    /* Degenerate case: Offset points to end of string. */
1163
0
    return true;
1164
0
  }
1165
1166
  /* Check that the offset does not point to an UTF-8 continuation byte. */
1167
0
  return (ZSTR_VAL(subject_str)[start_offset] & 0xc0) != 0x80;
1168
0
}
1169
1170
/* {{{ php_pcre_match_impl() */
1171
PHPAPI void php_pcre_match_impl(pcre_cache_entry *pce, zend_string *subject_str, zval *return_value,
1172
  zval *subpats, bool global, zend_long flags, zend_off_t start_offset)
1173
311
{
1174
311
  zval       result_set;    /* Holds a set of subpatterns after
1175
                       a global match */
1176
311
  HashTable    **match_sets = NULL; /* An array of sets of matches for each
1177
                       subpattern after a global match */
1178
311
  uint32_t     options;     /* Execution options */
1179
311
  int        count;       /* Count of matched subpatterns */
1180
311
  uint32_t     num_subpats;   /* Number of captured subpatterns */
1181
311
  int        matched;     /* Has anything matched */
1182
311
  zend_string    **subpat_names;    /* Array for named subpatterns */
1183
311
  uint32_t     subpats_order;   /* Order of subpattern matches */
1184
311
  uint32_t     offset_capture;  /* Capture match offsets: yes/no */
1185
311
  zend_long    unmatched_as_null; /* Null non-matches: yes/no */
1186
311
  PCRE2_SPTR       mark = NULL;   /* Target for MARK name */
1187
311
  HashTable   *marks = NULL;   /* Array of marks for PREG_PATTERN_ORDER */
1188
311
  pcre2_match_data *match_data;
1189
311
  PCRE2_SIZE     start_offset2, orig_start_offset;
1190
311
  bool old_mdata_used;
1191
1192
311
  char *subject = ZSTR_VAL(subject_str);
1193
311
  size_t subject_len = ZSTR_LEN(subject_str);
1194
1195
  /* Overwrite the passed-in value for subpatterns with an empty array. */
1196
311
  if (subpats != NULL) {
1197
1
    subpats = zend_try_array_init(subpats);
1198
1
    if (!subpats) {
1199
0
      RETURN_THROWS();
1200
0
    }
1201
1
  }
1202
1203
311
  subpats_order = global ? PREG_PATTERN_ORDER : 0;
1204
1205
311
  if (flags) {
1206
0
    offset_capture = flags & PREG_OFFSET_CAPTURE;
1207
0
    unmatched_as_null = flags & PREG_UNMATCHED_AS_NULL;
1208
1209
    /*
1210
     * subpats_order is pre-set to pattern mode so we change it only if
1211
     * necessary.
1212
     */
1213
0
    if (flags & 0xff) {
1214
0
      subpats_order = flags & 0xff;
1215
0
      if ((global && (subpats_order < PREG_PATTERN_ORDER || subpats_order > PREG_SET_ORDER)) ||
1216
0
        (!global && subpats_order != 0)) {
1217
0
        zend_argument_value_error(4, "must be a PREG_* constant");
1218
0
        RETURN_THROWS();
1219
0
      }
1220
0
    }
1221
311
  } else {
1222
311
    offset_capture = 0;
1223
311
    unmatched_as_null = 0;
1224
311
  }
1225
1226
  /* Negative offset counts from the end of the string. */
1227
311
  if (start_offset < 0) {
1228
0
    if ((PCRE2_SIZE)-start_offset <= subject_len) {
1229
0
      start_offset2 = subject_len + start_offset;
1230
0
    } else {
1231
0
      start_offset2 = 0;
1232
0
    }
1233
311
  } else {
1234
311
    start_offset2 = (PCRE2_SIZE)start_offset;
1235
311
  }
1236
1237
311
  if (start_offset2 > subject_len) {
1238
0
    pcre_handle_exec_error(PCRE2_ERROR_BADOFFSET);
1239
0
    RETURN_FALSE;
1240
0
  }
1241
1242
  /* Calculate the size of the offsets array, and allocate memory for it. */
1243
311
  num_subpats = pce->capture_count + 1;
1244
1245
  /*
1246
   * Build a mapping from subpattern numbers to their names. We will
1247
   * allocate the table only if there are any named subpatterns.
1248
   */
1249
311
  subpat_names = NULL;
1250
311
  if (subpats && pce->name_count > 0) {
1251
0
    subpat_names = ensure_subpats_table(pce->name_count, pce);
1252
0
    if (UNEXPECTED(!subpat_names)) {
1253
0
      RETURN_FALSE;
1254
0
    }
1255
0
  }
1256
1257
311
  matched = 0;
1258
311
  PCRE_G(error_code) = PHP_PCRE_NO_ERROR;
1259
1260
311
  old_mdata_used = mdata_used;
1261
311
  if (!old_mdata_used && num_subpats <= PHP_PCRE_PREALLOC_MDATA_SIZE) {
1262
311
    mdata_used = true;
1263
311
    match_data = mdata;
1264
311
  } else {
1265
0
    match_data = pcre2_match_data_create_from_pattern(pce->re, PCRE_G(gctx_zmm));
1266
0
    if (!match_data) {
1267
0
      PCRE_G(error_code) = PHP_PCRE_INTERNAL_ERROR;
1268
0
      RETURN_FALSE;
1269
0
    }
1270
0
  }
1271
1272
  /* Allocate match sets array and initialize the values. */
1273
311
  if (global && subpats && subpats_order == PREG_PATTERN_ORDER) {
1274
0
    match_sets = safe_emalloc(num_subpats, sizeof(HashTable *), 0);
1275
0
    for (uint32_t i = 0; i < num_subpats; i++) {
1276
0
      match_sets[i] = zend_new_array(0);
1277
0
    }
1278
0
  }
1279
1280
  /* Array of subpattern offsets */
1281
311
  PCRE2_SIZE *const offsets = pcre2_get_ovector_pointer(match_data);
1282
1283
311
  orig_start_offset = start_offset2;
1284
311
  options =
1285
311
    (pce->compile_options & PCRE2_UTF) && !is_known_valid_utf8(subject_str, orig_start_offset)
1286
311
      ? 0 : PCRE2_NO_UTF_CHECK;
1287
1288
  /* Execute the regular expression. */
1289
#ifdef HAVE_PCRE_JIT_SUPPORT
1290
  if ((pce->preg_options & PREG_JIT) && options) {
1291
    count = pcre2_jit_match(pce->re, (PCRE2_SPTR)subject, subject_len, start_offset2,
1292
        PCRE2_NO_UTF_CHECK, match_data, mctx);
1293
  } else
1294
#endif
1295
311
  count = pcre2_match(pce->re, (PCRE2_SPTR)subject, subject_len, start_offset2,
1296
311
      options, match_data, mctx);
1297
1298
311
  while (1) {
1299
311
    if (ZEND_HAS_OOM()) {
1300
0
      goto oom;
1301
0
    }
1302
1303
    /* If something has matched */
1304
311
    if (count >= 0) {
1305
      /* Check for too many substrings condition. */
1306
12
      if (UNEXPECTED(count == 0)) {
1307
0
        php_error_docref(NULL, E_NOTICE, "Matched, but too many substrings");
1308
0
        count = num_subpats;
1309
0
      }
1310
1311
12
matched:
1312
12
      matched++;
1313
1314
      /* If subpatterns array has been passed, fill it in with values. */
1315
12
      if (subpats != NULL) {
1316
        /* Try to get the list of substrings and display a warning if failed. */
1317
0
        if (UNEXPECTED(offsets[1] < offsets[0])) {
1318
0
          if (match_sets) {
1319
0
            for (uint32_t i = 0; i < num_subpats; i++) {
1320
0
              zend_array_destroy(match_sets[i]);
1321
0
            }
1322
0
            efree(match_sets);
1323
0
          }
1324
0
          if (marks) {
1325
0
            zend_array_destroy(marks);
1326
0
          }
1327
0
          if (match_data != mdata) {
1328
0
            pcre2_match_data_free(match_data);
1329
0
          }
1330
0
          php_error_docref(NULL, E_WARNING, "Get subpatterns list failed");
1331
0
          RETURN_FALSE;
1332
0
        }
1333
1334
0
        if (global) { /* global pattern matching */
1335
0
          if (subpats_order == PREG_PATTERN_ORDER) {
1336
            /* For each subpattern, insert it into the appropriate array. */
1337
0
            if (offset_capture) {
1338
0
              for (int i = 0; i < count; i++) {
1339
0
                add_offset_pair(
1340
0
                  match_sets[i], subject, offsets[2*i], offsets[2*i+1],
1341
0
                  NULL, unmatched_as_null);
1342
0
                if (ZEND_HAS_OOM()) {
1343
0
                  goto oom;
1344
0
                }
1345
0
              }
1346
0
            } else {
1347
0
              for (int i = 0; i < count; i++) {
1348
0
                zval val;
1349
0
                populate_match_value(
1350
0
                  &val, subject, offsets[2*i], offsets[2*i+1], unmatched_as_null);
1351
0
                zend_hash_next_index_insert_new(match_sets[i], &val);
1352
0
                if (ZEND_HAS_OOM()) {
1353
0
                  goto oom;
1354
0
                }
1355
0
              }
1356
0
            }
1357
0
            mark = pcre2_get_mark(match_data);
1358
            /* Add MARK, if available */
1359
0
            if (mark) {
1360
0
              if (!marks) {
1361
0
                marks = zend_new_array(0);
1362
0
              }
1363
0
              zval tmp;
1364
0
              ZVAL_STRING(&tmp, (char *) mark);
1365
0
              zend_hash_index_add_new(marks, matched - 1, &tmp);
1366
0
            }
1367
            /*
1368
             * If the number of captured subpatterns on this run is
1369
             * less than the total possible number, pad the result
1370
             * arrays with NULLs or empty strings.
1371
             */
1372
0
            if (count < num_subpats) {
1373
0
              for (int i = count; i < num_subpats; i++) {
1374
0
                if (offset_capture) {
1375
0
                  add_offset_pair(
1376
0
                    match_sets[i], NULL, PCRE2_UNSET, PCRE2_UNSET,
1377
0
                    NULL, unmatched_as_null);
1378
0
                } else if (unmatched_as_null) {
1379
0
                  zval tmp;
1380
0
                  ZVAL_NULL(&tmp);
1381
0
                  zend_hash_next_index_insert_new(match_sets[i], &tmp);
1382
0
                } else {
1383
0
                  zval tmp;
1384
0
                  ZVAL_EMPTY_STRING(&tmp);
1385
0
                  zend_hash_next_index_insert_new(match_sets[i], &tmp);
1386
0
                }
1387
0
                if (ZEND_HAS_OOM()) {
1388
0
                  goto oom;
1389
0
                }
1390
0
              }
1391
0
            }
1392
0
          } else {
1393
            /* Allocate and populate the result set array */
1394
0
            mark = pcre2_get_mark(match_data);
1395
0
            array_init_size(&result_set, count + (mark ? 1 : 0));
1396
0
            zend_result result = populate_subpat_array(
1397
0
              Z_ARRVAL(result_set), subject, offsets, subpat_names,
1398
0
              num_subpats, count, mark, flags);
1399
0
            if (UNEXPECTED(result == FAILURE)) {
1400
0
              zval_ptr_dtor(&result_set);
1401
0
              goto oom;
1402
0
            }
1403
            /* And add it to the output array */
1404
0
            zend_hash_next_index_insert_new(Z_ARRVAL_P(subpats), &result_set);
1405
0
          }
1406
0
        } else {     /* single pattern matching */
1407
          /* For each subpattern, insert it into the subpatterns array. */
1408
0
          mark = pcre2_get_mark(match_data);
1409
0
          zend_result result = populate_subpat_array(
1410
0
            Z_ARRVAL_P(subpats), subject, offsets, subpat_names, num_subpats, count, mark, flags);
1411
0
          if (UNEXPECTED(result == FAILURE)) {
1412
0
            goto oom;
1413
0
          }
1414
0
          break;
1415
0
        }
1416
0
      }
1417
1418
      /* Advance to the next piece. */
1419
12
      start_offset2 = offsets[1];
1420
1421
      /* If we have matched an empty string, mimic what Perl's /g options does.
1422
         This turns out to be rather cunning. First we set PCRE2_NOTEMPTY_ATSTART and try
1423
         the match again at the same point. If this fails (picked up above) we
1424
         advance to the next character. */
1425
12
      if (start_offset2 == offsets[0]) {
1426
3
        count = pcre2_match(pce->re, (PCRE2_SPTR)subject, subject_len, start_offset2,
1427
3
          PCRE2_NO_UTF_CHECK | PCRE2_NOTEMPTY_ATSTART | PCRE2_ANCHORED, match_data, mctx);
1428
3
        if (count >= 0) {
1429
0
          if (global) {
1430
0
            goto matched;
1431
0
          } else {
1432
0
            break;
1433
0
          }
1434
3
        } else if (count == PCRE2_ERROR_NOMATCH) {
1435
          /* If we previously set PCRE2_NOTEMPTY_ATSTART after a null match,
1436
             this is not necessarily the end. We need to advance
1437
             the start offset, and continue. Fudge the offset values
1438
             to achieve this, unless we're already at the end of the string. */
1439
3
          if (start_offset2 < subject_len) {
1440
3
            size_t unit_len = calculate_unit_length(pce, subject + start_offset2);
1441
1442
3
            start_offset2 += unit_len;
1443
3
          } else {
1444
0
            break;
1445
0
          }
1446
3
        } else {
1447
0
          goto error;
1448
0
        }
1449
3
      }
1450
299
    } else if (count == PCRE2_ERROR_NOMATCH) {
1451
289
      break;
1452
289
    } else {
1453
10
error:
1454
10
      pcre_handle_exec_error(count);
1455
10
      break;
1456
10
    }
1457
1458
12
    if (!global) {
1459
12
      break;
1460
12
    }
1461
1462
    /* Execute the regular expression. */
1463
#ifdef HAVE_PCRE_JIT_SUPPORT
1464
    if ((pce->preg_options & PREG_JIT)) {
1465
      if (start_offset2 > subject_len) {
1466
        pcre_handle_exec_error(PCRE2_ERROR_BADOFFSET);
1467
        break;
1468
      }
1469
      count = pcre2_jit_match(pce->re, (PCRE2_SPTR)subject, subject_len, start_offset2,
1470
          PCRE2_NO_UTF_CHECK, match_data, mctx);
1471
    } else
1472
#endif
1473
0
    count = pcre2_match(pce->re, (PCRE2_SPTR)subject, subject_len, start_offset2,
1474
0
        PCRE2_NO_UTF_CHECK, match_data, mctx);
1475
0
  }
1476
311
  if (match_data != mdata) {
1477
0
    pcre2_match_data_free(match_data);
1478
0
  }
1479
311
  mdata_used = old_mdata_used;
1480
1481
  /* Add the match sets to the output array and clean up */
1482
311
  if (match_sets) {
1483
0
    if (subpat_names) {
1484
0
      for (uint32_t i = 0; i < num_subpats; i++) {
1485
0
        zval wrapper;
1486
0
        ZVAL_ARR(&wrapper, match_sets[i]);
1487
0
        if (subpat_names[i]) {
1488
0
          zend_hash_update(Z_ARRVAL_P(subpats), subpat_names[i], &wrapper);
1489
0
          GC_ADDREF(match_sets[i]);
1490
0
        }
1491
0
        zend_hash_next_index_insert_new(Z_ARRVAL_P(subpats), &wrapper);
1492
0
      }
1493
0
    } else {
1494
0
      for (uint32_t i = 0; i < num_subpats; i++) {
1495
0
        zval wrapper;
1496
0
        ZVAL_ARR(&wrapper, match_sets[i]);
1497
0
        zend_hash_next_index_insert_new(Z_ARRVAL_P(subpats), &wrapper);
1498
0
      }
1499
0
    }
1500
0
    efree(match_sets);
1501
1502
0
    if (marks) {
1503
0
      zval tmp;
1504
0
      ZVAL_ARR(&tmp, marks);
1505
0
      zend_hash_str_update(Z_ARRVAL_P(subpats), "MARK", sizeof("MARK") - 1, &tmp);
1506
0
    }
1507
0
  }
1508
1509
311
  if (PCRE_G(error_code) == PHP_PCRE_NO_ERROR) {
1510
    /* If there was no error and we're in /u mode, remember that the string is valid UTF-8. */
1511
301
    if ((pce->compile_options & PCRE2_UTF)
1512
79
        && !ZSTR_IS_INTERNED(subject_str) && orig_start_offset == 0) {
1513
79
      GC_ADD_FLAGS(subject_str, IS_STR_VALID_UTF8);
1514
79
    }
1515
1516
301
    RETVAL_LONG(matched);
1517
301
  } else {
1518
10
    RETVAL_FALSE;
1519
10
  }
1520
311
  return;
1521
1522
0
oom:
1523
0
  if (match_data != mdata) {
1524
0
    pcre2_match_data_free(match_data);
1525
0
  }
1526
0
  if (match_sets) {
1527
0
    for (uint32_t i = 0; i < num_subpats; i++) {
1528
0
      zend_array_destroy(match_sets[i]);
1529
0
    }
1530
0
    efree(match_sets);
1531
0
  }
1532
0
  if (marks) {
1533
0
    zend_array_destroy(marks);
1534
0
  }
1535
0
  if (subpats) {
1536
0
    zend_hash_clean(Z_ARRVAL_P(subpats));
1537
0
  }
1538
0
  RETURN_OOM();
1539
0
}
1540
/* }}} */
1541
1542
/* {{{ Perform a Perl-style regular expression match */
1543
PHP_FUNCTION(preg_match)
1544
356
{
1545
356
  php_do_pcre_match(INTERNAL_FUNCTION_PARAM_PASSTHRU, false);
1546
356
}
1547
/* }}} */
1548
1549
ZEND_FRAMELESS_FUNCTION(preg_match, 2)
1550
0
{
1551
0
  zval regex_tmp, subject_tmp;
1552
0
  zend_string *regex, *subject;
1553
1554
0
  Z_FLF_PARAM_STR(1, regex, regex_tmp);
1555
0
  Z_FLF_PARAM_STR(2, subject, subject_tmp);
1556
1557
  /* Compile regex or get it from cache. */
1558
0
  pcre_cache_entry *pce;
1559
0
  if ((pce = pcre_get_compiled_regex_cache(regex)) == NULL) {
1560
0
    RETVAL_FALSE;
1561
0
    goto flf_clean;
1562
0
  }
1563
1564
0
  pce->refcount++;
1565
0
  php_pcre_match_impl(pce, subject, return_value, /* subpats */ NULL,
1566
0
    /* global */ false, /* flags */ 0, /* start_offset */ 0);
1567
0
  pce->refcount--;
1568
1569
0
flf_clean:
1570
0
  Z_FLF_PARAM_FREE_STR(1, regex_tmp);
1571
0
  Z_FLF_PARAM_FREE_STR(2, subject_tmp);
1572
0
}
1573
1574
/* {{{ Perform a Perl-style global regular expression match */
1575
PHP_FUNCTION(preg_match_all)
1576
0
{
1577
0
  php_do_pcre_match(INTERNAL_FUNCTION_PARAM_PASSTHRU, true);
1578
0
}
1579
/* }}} */
1580
1581
static bool preg_get_backref(char **str, int *backref)
1582
90
{
1583
90
  bool in_brace = false;
1584
90
  char *walk = *str;
1585
1586
90
  if (walk[1] == 0) {
1587
0
    return false;
1588
0
  }
1589
1590
90
  if (*walk == '$' && walk[1] == '{') {
1591
0
    in_brace = true;
1592
0
    walk++;
1593
0
  }
1594
90
  walk++;
1595
1596
90
  if (*walk >= '0' && *walk <= '9') {
1597
0
    *backref = *walk - '0';
1598
0
    walk++;
1599
90
  } else {
1600
90
    return false;
1601
90
  }
1602
1603
0
  if (*walk && *walk >= '0' && *walk <= '9') {
1604
0
    *backref = *backref * 10 + *walk - '0';
1605
0
    walk++;
1606
0
  }
1607
1608
0
  if (in_brace) {
1609
0
    if (*walk != '}') {
1610
0
      return false;
1611
0
    }
1612
0
    walk++;
1613
0
  }
1614
1615
0
  *str = walk;
1616
0
  return true;
1617
0
}
1618
1619
/* Return NULL if an exception has occurred */
1620
static zend_string *preg_do_repl_func(zend_fcall_info_cache *fcc, const zend_string *subject, PCRE2_SIZE *offsets, zend_string **subpat_names, uint32_t num_subpats, int count, const PCRE2_SPTR mark, zend_long flags)
1621
0
{
1622
0
  zend_string *result_str = NULL;
1623
0
  zval     retval;      /* Function return value */
1624
0
  zval       arg;       /* Argument to pass to function */
1625
1626
0
  array_init_size(&arg, count + (mark ? 1 : 0));
1627
0
  zend_result result = populate_subpat_array(Z_ARRVAL(arg), ZSTR_VAL(subject), offsets, subpat_names, num_subpats, count, mark, flags);
1628
0
  if (UNEXPECTED(result == FAILURE)) {
1629
0
    zval_ptr_dtor(&arg);
1630
0
    return NULL;
1631
0
  }
1632
1633
0
  zend_call_known_fcc_ex(fcc, &retval, 1, &arg, NULL, zend_fci_consumed_arg(0));
1634
0
  zval_ptr_dtor(&arg);
1635
0
  if (EXPECTED(Z_TYPE(retval) == IS_STRING)) {
1636
0
    return Z_STR(retval);
1637
0
  }
1638
  /* No Exception has occurred */
1639
0
  else if (EXPECTED(Z_TYPE(retval) != IS_UNDEF)) {
1640
0
    result_str = zval_try_get_string_func(&retval);
1641
0
  }
1642
0
  zval_ptr_dtor(&retval);
1643
1644
0
  return result_str;
1645
0
}
1646
1647
/* {{{ php_pcre_replace */
1648
PHPAPI zend_string *php_pcre_replace(zend_string *regex,
1649
                zend_string *subject_str,
1650
                const char *subject, size_t subject_len,
1651
                zend_string *replace_str,
1652
                size_t limit, size_t *replace_count)
1653
7
{
1654
7
  pcre_cache_entry  *pce;         /* Compiled regular expression */
1655
7
  zend_string     *result;      /* Function result */
1656
1657
  /* Abort on pending exception, e.g. thrown from __toString(). */
1658
7
  if (UNEXPECTED(EG(exception))) {
1659
0
    return NULL;
1660
0
  }
1661
1662
  /* Compile regex or get it from cache. */
1663
7
  if ((pce = pcre_get_compiled_regex_cache(regex)) == NULL) {
1664
4
    return NULL;
1665
4
  }
1666
3
  pce->refcount++;
1667
3
  result = php_pcre_replace_impl(pce, subject_str, subject, subject_len, replace_str,
1668
3
    limit, replace_count);
1669
3
  pce->refcount--;
1670
1671
3
  return result;
1672
7
}
1673
/* }}} */
1674
1675
/* {{{ php_pcre_replace_impl() */
1676
PHPAPI zend_string *php_pcre_replace_impl(const 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)
1677
3
{
1678
3
  uint32_t     options;     /* Execution options */
1679
3
  int        count;       /* Count of matched subpatterns */
1680
3
  uint32_t     num_subpats;   /* Number of captured subpatterns */
1681
3
  size_t       new_len;     /* Length of needed storage */
1682
3
  size_t       alloc_len;     /* Actual allocated length */
1683
3
  size_t       match_len;     /* Length of the current match */
1684
3
  int        backref;     /* Backreference number */
1685
3
  PCRE2_SIZE     start_offset;    /* Where the new search starts */
1686
3
  size_t       last_end_offset; /* Where the last search ended */
1687
3
  char      *walkbuf,     /* Location of current replacement in the result */
1688
3
          *walk,        /* Used to walk the replacement string */
1689
3
           walk_last;     /* Last walked character */
1690
3
  const char    *match,       /* The current match */
1691
3
          *piece,       /* The current piece of subject */
1692
3
          *replace_end;   /* End of replacement string */
1693
3
  size_t      result_len;     /* Length of result */
1694
3
  zend_string   *result;      /* Result of replacement */
1695
3
  pcre2_match_data *match_data;
1696
3
  bool old_mdata_used;
1697
1698
  /* Calculate the size of the offsets array, and allocate memory for it. */
1699
3
  num_subpats = pce->capture_count + 1;
1700
3
  alloc_len = 0;
1701
3
  result = NULL;
1702
1703
  /* Initialize */
1704
3
  match = NULL;
1705
3
  start_offset = 0;
1706
3
  last_end_offset = 0;
1707
3
  result_len = 0;
1708
3
  PCRE_G(error_code) = PHP_PCRE_NO_ERROR;
1709
1710
3
  old_mdata_used = mdata_used;
1711
3
  if (!old_mdata_used && num_subpats <= PHP_PCRE_PREALLOC_MDATA_SIZE) {
1712
3
    mdata_used = true;
1713
3
    match_data = mdata;
1714
3
  } else {
1715
0
    match_data = pcre2_match_data_create_from_pattern(pce->re, PCRE_G(gctx_zmm));
1716
0
    if (!match_data) {
1717
0
      PCRE_G(error_code) = PHP_PCRE_INTERNAL_ERROR;
1718
0
      return NULL;
1719
0
    }
1720
0
  }
1721
1722
3
  options = (pce->compile_options & PCRE2_UTF) ? 0 : PCRE2_NO_UTF_CHECK;
1723
1724
  /* Array of subpattern offsets */
1725
3
  PCRE2_SIZE *const offsets = pcre2_get_ovector_pointer(match_data);
1726
1727
  /* Execute the regular expression. */
1728
#ifdef HAVE_PCRE_JIT_SUPPORT
1729
  if ((pce->preg_options & PREG_JIT) && options) {
1730
    count = pcre2_jit_match(pce->re, (PCRE2_SPTR)subject, subject_len, start_offset,
1731
        PCRE2_NO_UTF_CHECK, match_data, mctx);
1732
  } else
1733
#endif
1734
3
  count = pcre2_match(pce->re, (PCRE2_SPTR)subject, subject_len, start_offset,
1735
3
      options, match_data, mctx);
1736
1737
49
  while (1) {
1738
49
    piece = subject + last_end_offset;
1739
1740
49
    if (count >= 0 && limit > 0) {
1741
48
      bool simple_string;
1742
1743
      /* Check for too many substrings condition. */
1744
48
      if (UNEXPECTED(count == 0)) {
1745
0
        php_error_docref(NULL,E_NOTICE, "Matched, but too many substrings");
1746
0
        count = num_subpats;
1747
0
      }
1748
1749
48
matched:
1750
48
      if (UNEXPECTED(offsets[1] < offsets[0])) {
1751
0
        PCRE_G(error_code) = PHP_PCRE_INTERNAL_ERROR;
1752
0
        if (result) {
1753
0
          zend_string_release_ex(result, 0);
1754
0
          result = NULL;
1755
0
        }
1756
0
        break;
1757
0
      }
1758
1759
48
      if (replace_count) {
1760
48
        ++*replace_count;
1761
48
      }
1762
1763
      /* Set the match location in subject */
1764
48
      match = subject + offsets[0];
1765
1766
48
      new_len = result_len + offsets[0] - last_end_offset; /* part before the match */
1767
1768
48
      walk = ZSTR_VAL(replace_str);
1769
48
      replace_end = walk + ZSTR_LEN(replace_str);
1770
48
      walk_last = 0;
1771
48
      simple_string = true;
1772
813
      while (walk < replace_end) {
1773
765
        if ('\\' == *walk || '$' == *walk) {
1774
45
          simple_string = false;
1775
45
          if (walk_last == '\\') {
1776
0
            walk++;
1777
0
            walk_last = 0;
1778
0
            continue;
1779
0
          }
1780
45
          if (preg_get_backref(&walk, &backref)) {
1781
0
            if (backref < count)
1782
0
              new_len += offsets[(backref<<1)+1] - offsets[backref<<1];
1783
0
            continue;
1784
0
          }
1785
45
        }
1786
765
        new_len++;
1787
765
        walk++;
1788
765
        walk_last = walk[-1];
1789
765
      }
1790
1791
48
      if (new_len >= alloc_len) {
1792
9
        alloc_len = zend_safe_address_guarded(2, new_len, ZSTR_MAX_OVERHEAD) - ZSTR_MAX_OVERHEAD;
1793
9
        if (result == NULL) {
1794
2
          result = zend_string_falloc(alloc_len, /* persistent */ false, /* fallible */ true);
1795
2
          if (!result) {
1796
0
            goto error;
1797
0
          }
1798
7
        } else {
1799
7
          zend_string *extended_result = zend_string_fextend(result, alloc_len, /* persistent */ false, /* fallible */ true);
1800
7
          if (!extended_result) {
1801
0
            zend_string_release(result);
1802
0
            result = NULL;
1803
0
            goto error;
1804
0
          }
1805
7
          result = extended_result;
1806
7
        }
1807
9
      }
1808
1809
48
      if (match-piece > 0) {
1810
        /* copy the part of the string before the match */
1811
46
        memcpy(&ZSTR_VAL(result)[result_len], piece, match-piece);
1812
46
        result_len += (match-piece);
1813
46
      }
1814
1815
48
      if (simple_string) {
1816
        /* copy replacement */
1817
3
        memcpy(&ZSTR_VAL(result)[result_len], ZSTR_VAL(replace_str), ZSTR_LEN(replace_str)+1);
1818
3
        result_len += ZSTR_LEN(replace_str);
1819
45
      } else {
1820
        /* copy replacement and backrefs */
1821
45
        walkbuf = ZSTR_VAL(result) + result_len;
1822
1823
45
        walk = ZSTR_VAL(replace_str);
1824
45
        walk_last = 0;
1825
810
        while (walk < replace_end) {
1826
765
          if ('\\' == *walk || '$' == *walk) {
1827
45
            if (walk_last == '\\') {
1828
0
              *(walkbuf-1) = *walk++;
1829
0
              walk_last = 0;
1830
0
              continue;
1831
0
            }
1832
45
            if (preg_get_backref(&walk, &backref)) {
1833
0
              if (backref < count) {
1834
0
                if (offsets[backref<<1] < SIZE_MAX) {
1835
0
                  match_len = offsets[(backref<<1)+1] - offsets[backref<<1];
1836
0
                  walkbuf = zend_mempcpy(walkbuf, subject + offsets[backref << 1], match_len);
1837
0
                }
1838
0
              }
1839
0
              continue;
1840
0
            }
1841
45
          }
1842
765
          *walkbuf++ = *walk++;
1843
765
          walk_last = walk[-1];
1844
765
        }
1845
45
        *walkbuf = '\0';
1846
        /* increment the result length by how much we've added to the string */
1847
45
        result_len += (walkbuf - (ZSTR_VAL(result) + result_len));
1848
45
      }
1849
1850
48
      limit--;
1851
1852
      /* Advance to the next piece. */
1853
48
      start_offset = last_end_offset = offsets[1];
1854
1855
      /* If we have matched an empty string, mimic what Perl's /g options does.
1856
         This turns out to be rather cunning. First we set PCRE2_NOTEMPTY_ATSTART and try
1857
         the match again at the same point. If this fails (picked up above) we
1858
         advance to the next character. */
1859
48
      if (start_offset == offsets[0]) {
1860
48
        count = pcre2_match(pce->re, (PCRE2_SPTR)subject, subject_len, start_offset,
1861
48
          PCRE2_NO_UTF_CHECK | PCRE2_NOTEMPTY_ATSTART | PCRE2_ANCHORED, match_data, mctx);
1862
1863
48
        piece = subject + start_offset;
1864
48
        if (count >= 0 && limit > 0) {
1865
0
          goto matched;
1866
48
        } else if (count == PCRE2_ERROR_NOMATCH || limit == 0) {
1867
          /* If we previously set PCRE2_NOTEMPTY_ATSTART after a null match,
1868
             this is not necessarily the end. We need to advance
1869
             the start offset, and continue. Fudge the offset values
1870
             to achieve this, unless we're already at the end of the string. */
1871
48
          if (start_offset < subject_len) {
1872
46
            size_t unit_len = calculate_unit_length(pce, piece);
1873
46
            start_offset += unit_len;
1874
46
          } else {
1875
2
            goto not_matched;
1876
2
          }
1877
48
        } else {
1878
0
          goto error;
1879
0
        }
1880
48
      }
1881
1882
48
    } else if (count == PCRE2_ERROR_NOMATCH || limit == 0) {
1883
3
not_matched:
1884
3
      if (!result && subject_str) {
1885
1
        result = zend_string_copy(subject_str);
1886
1
        break;
1887
1
      }
1888
      /* now we know exactly how long it is */
1889
2
      alloc_len = result_len + subject_len - last_end_offset;
1890
2
      if (NULL != result) {
1891
2
        result = zend_string_realloc(result, alloc_len, 0);
1892
2
      } else {
1893
0
        result = zend_string_alloc(alloc_len, 0);
1894
0
      }
1895
      /* stick that last bit of string on our output */
1896
2
      memcpy(ZSTR_VAL(result) + result_len, piece, subject_len - last_end_offset);
1897
2
      result_len += subject_len - last_end_offset;
1898
2
      ZSTR_VAL(result)[result_len] = '\0';
1899
2
      ZSTR_LEN(result) = result_len;
1900
2
      break;
1901
3
    } else {
1902
0
error:
1903
0
      pcre_handle_exec_error(count);
1904
0
      if (result) {
1905
0
        zend_string_release_ex(result, 0);
1906
0
        result = NULL;
1907
0
      }
1908
0
      break;
1909
0
    }
1910
1911
#ifdef HAVE_PCRE_JIT_SUPPORT
1912
    if (pce->preg_options & PREG_JIT) {
1913
      count = pcre2_jit_match(pce->re, (PCRE2_SPTR)subject, subject_len, start_offset,
1914
          PCRE2_NO_UTF_CHECK, match_data, mctx);
1915
    } else
1916
#endif
1917
46
    count = pcre2_match(pce->re, (PCRE2_SPTR)subject, subject_len, start_offset,
1918
46
          PCRE2_NO_UTF_CHECK, match_data, mctx);
1919
46
  }
1920
3
  if (match_data != mdata) {
1921
0
    pcre2_match_data_free(match_data);
1922
0
  }
1923
3
  mdata_used = old_mdata_used;
1924
1925
3
  return result;
1926
3
}
1927
/* }}} */
1928
1929
static zend_string *php_pcre_replace_func_impl(pcre_cache_entry *pce, zend_string *subject_str,
1930
  zend_fcall_info_cache *fcc,
1931
  size_t limit, size_t *replace_count, zend_long flags
1932
0
) {
1933
0
  uint32_t     options;     /* Execution options */
1934
0
  int        count;       /* Count of matched subpatterns */
1935
0
  zend_string   **subpat_names;   /* Array for named subpatterns */
1936
0
  uint32_t     num_subpats;   /* Number of captured subpatterns */
1937
0
  size_t       alloc_len;     /* Actual allocated length */
1938
0
  PCRE2_SIZE     start_offset;    /* Where the new search starts */
1939
0
  size_t       last_end_offset; /* Where the last search ended */
1940
0
  const char    *match,       /* The current match */
1941
0
          *piece;       /* The current piece of subject */
1942
0
  size_t      result_len;     /* Length of result */
1943
0
  zend_string   *result;      /* Result of replacement */
1944
0
  pcre2_match_data *match_data;
1945
0
  bool old_mdata_used;
1946
1947
  /* Calculate the size of the offsets array, and allocate memory for it. */
1948
0
  num_subpats = pce->capture_count + 1;
1949
0
  if (pce->name_count > 0) {
1950
0
    subpat_names = ensure_subpats_table(pce->name_count, pce);
1951
0
    if (UNEXPECTED(!subpat_names)) {
1952
0
      return NULL;
1953
0
    }
1954
0
  } else {
1955
0
    subpat_names = NULL;
1956
0
  }
1957
1958
0
  alloc_len = 0;
1959
0
  result = NULL;
1960
1961
  /* Initialize */
1962
0
  match = NULL;
1963
0
  start_offset = 0;
1964
0
  last_end_offset = 0;
1965
0
  result_len = 0;
1966
0
  PCRE_G(error_code) = PHP_PCRE_NO_ERROR;
1967
1968
0
  old_mdata_used = mdata_used;
1969
0
  if (!old_mdata_used && num_subpats <= PHP_PCRE_PREALLOC_MDATA_SIZE) {
1970
0
    mdata_used = 1;
1971
0
    match_data = mdata;
1972
0
  } else {
1973
0
    match_data = pcre2_match_data_create_from_pattern(pce->re, PCRE_G(gctx_zmm));
1974
0
    if (!match_data) {
1975
0
      PCRE_G(error_code) = PHP_PCRE_INTERNAL_ERROR;
1976
0
      mdata_used = old_mdata_used;
1977
0
      return NULL;
1978
0
    }
1979
0
  }
1980
1981
0
  options = (pce->compile_options & PCRE2_UTF) ? 0 : PCRE2_NO_UTF_CHECK;
1982
1983
  /* Array of subpattern offsets */
1984
0
  PCRE2_SIZE *const offsets = pcre2_get_ovector_pointer(match_data);
1985
1986
  /* Execute the regular expression. */
1987
#ifdef HAVE_PCRE_JIT_SUPPORT
1988
  if ((pce->preg_options & PREG_JIT) && options) {
1989
    count = pcre2_jit_match(pce->re, (PCRE2_SPTR)ZSTR_VAL(subject_str), ZSTR_LEN(subject_str), start_offset,
1990
        PCRE2_NO_UTF_CHECK, match_data, mctx);
1991
  } else
1992
#endif
1993
0
  count = pcre2_match(pce->re, (PCRE2_SPTR)ZSTR_VAL(subject_str), ZSTR_LEN(subject_str), start_offset,
1994
0
      options, match_data, mctx);
1995
1996
0
  while (1) {
1997
0
    piece = ZSTR_VAL(subject_str) + last_end_offset;
1998
1999
0
    if (count >= 0 && limit) {
2000
      /* Check for too many substrings condition. */
2001
0
      if (UNEXPECTED(count == 0)) {
2002
0
        php_error_docref(NULL,E_NOTICE, "Matched, but too many substrings");
2003
0
        count = num_subpats;
2004
0
      }
2005
2006
0
matched:
2007
0
      if (UNEXPECTED(offsets[1] < offsets[0])) {
2008
0
        PCRE_G(error_code) = PHP_PCRE_INTERNAL_ERROR;
2009
0
        if (result) {
2010
0
          zend_string_release_ex(result, 0);
2011
0
          result = NULL;
2012
0
        }
2013
0
        break;
2014
0
      }
2015
2016
0
      if (replace_count) {
2017
0
        ++*replace_count;
2018
0
      }
2019
2020
      /* Set the match location in subject */
2021
0
      match = ZSTR_VAL(subject_str) + offsets[0];
2022
2023
      /* Length of needed storage */
2024
0
      size_t new_len = result_len + offsets[0] - last_end_offset; /* part before the match */
2025
2026
      /* Use custom function to get replacement string and its length. */
2027
0
      zend_string *eval_result = preg_do_repl_func(
2028
0
        fcc, subject_str, offsets, subpat_names, num_subpats, count,
2029
0
        pcre2_get_mark(match_data), flags);
2030
2031
0
      if (UNEXPECTED(!eval_result)) {
2032
0
        goto error;
2033
0
      }
2034
0
      new_len = zend_safe_address_guarded(1, ZSTR_LEN(eval_result) + ZSTR_MAX_OVERHEAD, new_len) -ZSTR_MAX_OVERHEAD;
2035
0
      if (new_len >= alloc_len) {
2036
0
        alloc_len = zend_safe_address_guarded(2, new_len, ZSTR_MAX_OVERHEAD) - ZSTR_MAX_OVERHEAD;
2037
0
        if (result == NULL) {
2038
0
          result = zend_string_falloc(alloc_len, /* persistent */ false, /* fallible */ true);
2039
0
          if (!result) {
2040
0
            zend_string_release_ex(eval_result, 0);
2041
0
            goto error;
2042
0
          }
2043
0
        } else {
2044
0
          zend_string *extended_result = zend_string_fextend(result, alloc_len, /* persistent */ false, /* fallible */ true);
2045
0
          if (!extended_result) {
2046
0
            zend_string_release_ex(eval_result, 0);
2047
0
            zend_string_release(result);
2048
0
            result = NULL;
2049
0
            goto error;
2050
0
          }
2051
0
          result = extended_result;
2052
0
        }
2053
0
      }
2054
2055
0
      if (match-piece > 0) {
2056
        /* copy the part of the string before the match */
2057
0
        memcpy(ZSTR_VAL(result) + result_len, piece, match-piece);
2058
0
        result_len += (match-piece);
2059
0
      }
2060
2061
      /* If using custom function, copy result to the buffer and clean up. */
2062
0
      memcpy(ZSTR_VAL(result) + result_len, ZSTR_VAL(eval_result), ZSTR_LEN(eval_result));
2063
0
      result_len += ZSTR_LEN(eval_result);
2064
0
      zend_string_release_ex(eval_result, 0);
2065
2066
0
      limit--;
2067
2068
      /* Advance to the next piece. */
2069
0
      start_offset = last_end_offset = offsets[1];
2070
2071
      /* If we have matched an empty string, mimic what Perl's /g options does.
2072
         This turns out to be rather cunning. First we set PCRE2_NOTEMPTY_ATSTART and try
2073
         the match again at the same point. If this fails (picked up above) we
2074
         advance to the next character. */
2075
0
      if (start_offset == offsets[0]) {
2076
0
        count = pcre2_match(pce->re, (PCRE2_SPTR)ZSTR_VAL(subject_str), ZSTR_LEN(subject_str), start_offset,
2077
0
          PCRE2_NO_UTF_CHECK | PCRE2_NOTEMPTY_ATSTART | PCRE2_ANCHORED, match_data, mctx);
2078
2079
0
        piece = ZSTR_VAL(subject_str) + start_offset;
2080
0
        if (count >= 0 && limit) {
2081
0
          goto matched;
2082
0
        } else if (count == PCRE2_ERROR_NOMATCH || limit == 0) {
2083
          /* If we previously set PCRE2_NOTEMPTY_ATSTART after a null match,
2084
             this is not necessarily the end. We need to advance
2085
             the start offset, and continue. Fudge the offset values
2086
             to achieve this, unless we're already at the end of the string. */
2087
0
          if (start_offset < ZSTR_LEN(subject_str)) {
2088
0
            size_t unit_len = calculate_unit_length(pce, piece);
2089
0
            start_offset += unit_len;
2090
0
          } else {
2091
0
            goto not_matched;
2092
0
          }
2093
0
        } else {
2094
0
          goto error;
2095
0
        }
2096
0
      }
2097
2098
0
    } else if (count == PCRE2_ERROR_NOMATCH || limit == 0) {
2099
0
not_matched:
2100
0
      if (result == NULL) {
2101
0
        result = zend_string_copy(subject_str);
2102
0
        break;
2103
0
      }
2104
      /* now we know exactly how long it is */
2105
0
      size_t segment_len = ZSTR_LEN(subject_str) - last_end_offset;
2106
0
      alloc_len = result_len + segment_len;
2107
0
      result = zend_string_realloc(result, alloc_len, 0);
2108
      /* stick that last bit of string on our output */
2109
0
      memcpy(ZSTR_VAL(result) + result_len, piece, segment_len);
2110
0
      result_len += segment_len;
2111
0
      ZSTR_VAL(result)[result_len] = '\0';
2112
0
      ZSTR_LEN(result) = result_len;
2113
0
      break;
2114
0
    } else {
2115
0
error:
2116
0
      pcre_handle_exec_error(count);
2117
0
      if (result) {
2118
0
        zend_string_release_ex(result, 0);
2119
0
        result = NULL;
2120
0
      }
2121
0
      break;
2122
0
    }
2123
#ifdef HAVE_PCRE_JIT_SUPPORT
2124
    if ((pce->preg_options & PREG_JIT)) {
2125
      count = pcre2_jit_match(pce->re, (PCRE2_SPTR)ZSTR_VAL(subject_str), ZSTR_LEN(subject_str), start_offset,
2126
          PCRE2_NO_UTF_CHECK, match_data, mctx);
2127
    } else
2128
#endif
2129
0
    count = pcre2_match(pce->re, (PCRE2_SPTR)ZSTR_VAL(subject_str), ZSTR_LEN(subject_str), start_offset,
2130
0
        PCRE2_NO_UTF_CHECK, match_data, mctx);
2131
0
  }
2132
0
  if (match_data != mdata) {
2133
0
    pcre2_match_data_free(match_data);
2134
0
  }
2135
0
  mdata_used = old_mdata_used;
2136
2137
0
  return result;
2138
0
}
2139
2140
static zend_always_inline zend_string *php_pcre_replace_func(zend_string *regex,
2141
                zend_string *subject_str,
2142
                zend_fcall_info_cache *fcc,
2143
                size_t limit, size_t *replace_count, zend_long flags)
2144
0
{
2145
0
  pcre_cache_entry  *pce;         /* Compiled regular expression */
2146
0
  zend_string     *result;      /* Function result */
2147
2148
  /* Compile regex or get it from cache. */
2149
0
  if ((pce = pcre_get_compiled_regex_cache(regex)) == NULL) {
2150
0
    return NULL;
2151
0
  }
2152
0
  pce->refcount++;
2153
0
  result = php_pcre_replace_func_impl(pce, subject_str, fcc, limit, replace_count, flags);
2154
0
  pce->refcount--;
2155
2156
0
  return result;
2157
0
}
2158
2159
/* {{{ php_pcre_replace_array */
2160
static zend_string *php_pcre_replace_array(const HashTable *regex,
2161
  zend_string *replace_str, const HashTable *replace_ht,
2162
  zend_string *subject_str, size_t limit, size_t *replace_count)
2163
0
{
2164
0
  zval    *regex_entry;
2165
0
  zend_string *result;
2166
2167
0
  zend_string_addref(subject_str);
2168
2169
0
  if (replace_ht) {
2170
0
    uint32_t replace_idx = 0;
2171
2172
    /* For each entry in the regex array, get the entry */
2173
0
    ZEND_HASH_FOREACH_VAL(regex, regex_entry) {
2174
      /* Make sure we're dealing with strings. */
2175
0
      zend_string *tmp_regex_str;
2176
0
      zend_string *regex_str = zval_get_tmp_string(regex_entry, &tmp_regex_str);
2177
0
      zend_string *replace_entry_str, *tmp_replace_entry_str;
2178
2179
      /* Get current entry */
2180
0
      while (1) {
2181
0
        if (replace_idx == replace_ht->nNumUsed) {
2182
0
          replace_entry_str = ZSTR_EMPTY_ALLOC();
2183
0
          tmp_replace_entry_str = NULL;
2184
0
          break;
2185
0
        }
2186
0
        const zval *zv = ZEND_HASH_ELEMENT(replace_ht, replace_idx);
2187
0
        replace_idx++;
2188
0
        if (Z_TYPE_P(zv) != IS_UNDEF) {
2189
0
          replace_entry_str = zval_get_tmp_string(zv, &tmp_replace_entry_str);
2190
0
          break;
2191
0
        }
2192
0
      }
2193
2194
      /* Do the actual replacement and put the result back into subject_str
2195
         for further replacements. */
2196
0
      result = php_pcre_replace(regex_str, subject_str, ZSTR_VAL(subject_str),
2197
0
        ZSTR_LEN(subject_str), replace_entry_str, limit, replace_count);
2198
0
      zend_tmp_string_release(tmp_replace_entry_str);
2199
0
      zend_tmp_string_release(tmp_regex_str);
2200
0
      zend_string_release_ex(subject_str, 0);
2201
0
      subject_str = result;
2202
0
      if (UNEXPECTED(result == NULL)) {
2203
0
        break;
2204
0
      }
2205
0
    } ZEND_HASH_FOREACH_END();
2206
2207
0
  } else {
2208
0
    ZEND_ASSERT(replace_str != NULL);
2209
2210
    /* For each entry in the regex array, get the entry */
2211
0
    ZEND_HASH_FOREACH_VAL(regex, regex_entry) {
2212
      /* Make sure we're dealing with strings. */
2213
0
      zend_string *tmp_regex_str;
2214
0
      zend_string *regex_str = zval_get_tmp_string(regex_entry, &tmp_regex_str);
2215
2216
      /* Do the actual replacement and put the result back into subject_str
2217
         for further replacements. */
2218
0
      result = php_pcre_replace(regex_str, subject_str, ZSTR_VAL(subject_str),
2219
0
        ZSTR_LEN(subject_str), replace_str, limit, replace_count);
2220
0
      zend_tmp_string_release(tmp_regex_str);
2221
0
      zend_string_release_ex(subject_str, 0);
2222
0
      subject_str = result;
2223
2224
0
      if (UNEXPECTED(result == NULL)) {
2225
0
        break;
2226
0
      }
2227
0
    } ZEND_HASH_FOREACH_END();
2228
0
  }
2229
2230
0
  return subject_str;
2231
0
}
2232
/* }}} */
2233
2234
/* {{{ php_replace_in_subject */
2235
static zend_always_inline zend_string *php_replace_in_subject(
2236
  zend_string *regex_str, const HashTable *regex_ht,
2237
  zend_string *replace_str, const HashTable *replace_ht,
2238
  zend_string *subject, size_t limit, size_t *replace_count)
2239
7
{
2240
7
  zend_string *result;
2241
2242
7
  if (regex_str) {
2243
7
    ZEND_ASSERT(replace_str != NULL);
2244
7
    result = php_pcre_replace(regex_str, subject, ZSTR_VAL(subject), ZSTR_LEN(subject),
2245
7
      replace_str, limit, replace_count);
2246
7
  } else {
2247
0
    ZEND_ASSERT(regex_ht != NULL);
2248
0
    result = php_pcre_replace_array(regex_ht, replace_str, replace_ht, subject,
2249
0
      limit, replace_count);
2250
0
  }
2251
7
  return result;
2252
7
}
2253
/* }}} */
2254
2255
static zend_string *php_replace_in_subject_func(zend_string *regex_str, const HashTable *regex_ht,
2256
  zend_fcall_info_cache *fcc,
2257
  zend_string *subject, size_t limit, size_t *replace_count, zend_long flags)
2258
0
{
2259
0
  zend_string *result;
2260
2261
0
  if (regex_str) {
2262
0
    result = php_pcre_replace_func(regex_str, subject, fcc, limit, replace_count, flags);
2263
0
    return result;
2264
0
  } else {
2265
    /* If regex is an array */
2266
2267
0
    ZEND_ASSERT(regex_ht != NULL);
2268
2269
0
    zend_string_addref(subject);
2270
2271
    /* For each entry in the regex array, get the entry */
2272
0
    ZEND_HASH_FOREACH_VAL(regex_ht, zval *regex_entry) {
2273
      /* Make sure we're dealing with strings. */
2274
0
      zend_string *tmp_regex_entry_str;
2275
0
      zend_string *regex_entry_str = zval_try_get_tmp_string(regex_entry, &tmp_regex_entry_str);
2276
0
      if (UNEXPECTED(regex_entry_str == NULL)) {
2277
0
        break;
2278
0
      }
2279
2280
      /* Do the actual replacement and put the result back into subject
2281
         for further replacements. */
2282
0
      result = php_pcre_replace_func(
2283
0
        regex_entry_str, subject, fcc, limit, replace_count, flags);
2284
0
      zend_tmp_string_release(tmp_regex_entry_str);
2285
0
      zend_string_release(subject);
2286
0
      subject = result;
2287
0
      if (UNEXPECTED(result == NULL)) {
2288
0
        break;
2289
0
      }
2290
0
    } ZEND_HASH_FOREACH_END();
2291
2292
0
    return subject;
2293
0
  }
2294
0
}
2295
2296
static size_t php_preg_replace_func_impl(zval *return_value,
2297
  zend_string *regex_str, const HashTable *regex_ht,
2298
  zend_fcall_info_cache *fcc,
2299
  zend_string *subject_str, const HashTable *subject_ht, zend_long limit_val, zend_long flags)
2300
0
{
2301
0
  zend_string *result;
2302
0
  size_t replace_count = 0;
2303
2304
0
  if (subject_str) {
2305
0
    result = php_replace_in_subject_func(
2306
0
      regex_str, regex_ht, fcc, subject_str, limit_val, &replace_count, flags);
2307
0
    if (result != NULL) {
2308
0
      RETVAL_STR(result);
2309
0
    } else {
2310
0
      RETVAL_NULL();
2311
0
    }
2312
0
  } else {
2313
    /* if subject is an array */
2314
0
    ZEND_ASSERT(subject_ht != NULL);
2315
2316
0
    array_init_size(return_value, zend_hash_num_elements(subject_ht));
2317
0
    HashTable *return_value_ht = Z_ARRVAL_P(return_value);
2318
2319
    /* For each subject entry, convert it to string, then perform replacement
2320
       and add the result to the return_value array. */
2321
0
    ZEND_HASH_FOREACH_KEY_VAL(subject_ht, zend_ulong num_key, zend_string *string_key, zval *subject_entry) {
2322
0
      zend_string *tmp_subject_entry_str;
2323
0
      zend_string *subject_entry_str = zval_try_get_tmp_string(subject_entry, &tmp_subject_entry_str);
2324
0
      if (UNEXPECTED(subject_entry_str == NULL)) {
2325
0
        break;
2326
0
      }
2327
2328
0
      result = php_replace_in_subject_func(
2329
0
        regex_str, regex_ht, fcc, subject_entry_str, limit_val, &replace_count, flags);
2330
0
      if (result != NULL) {
2331
        /* Add to return array */
2332
0
        zval zv;
2333
0
        ZVAL_STR(&zv, result);
2334
0
        if (string_key) {
2335
0
          zend_hash_add_new(return_value_ht, string_key, &zv);
2336
0
        } else {
2337
0
          zend_hash_index_add_new(return_value_ht, num_key, &zv);
2338
0
        }
2339
0
      }
2340
0
      zend_tmp_string_release(tmp_subject_entry_str);
2341
0
    } ZEND_HASH_FOREACH_END();
2342
0
  }
2343
2344
0
  return replace_count;
2345
0
}
2346
2347
static void _preg_replace_common(
2348
  zval *return_value,
2349
  HashTable *regex_ht, zend_string *regex_str,
2350
  const HashTable *replace_ht, zend_string *replace_str,
2351
  const HashTable *subject_ht, zend_string *subject_str,
2352
  zend_long limit,
2353
  zval *zcount,
2354
  bool is_filter
2355
7
) {
2356
7
  size_t replace_count = 0;
2357
7
  zend_string *result;
2358
7
  size_t old_replace_count;
2359
2360
  /* If replace is an array then the regex argument needs to also be an array */
2361
7
  if (replace_ht && !regex_ht) {
2362
0
    zend_argument_type_error(1, "must be of type array when argument #2 ($replacement) is an array, string given");
2363
0
    RETURN_THROWS();
2364
0
  }
2365
2366
7
  if (subject_str) {
2367
7
    old_replace_count = replace_count;
2368
7
    result = php_replace_in_subject(regex_str, regex_ht, replace_str, replace_ht,
2369
7
      subject_str, limit, &replace_count);
2370
7
    if (result != NULL) {
2371
3
      if (!is_filter || replace_count > old_replace_count) {
2372
3
        RETVAL_STR(result);
2373
3
      } else {
2374
0
        zend_string_release_ex(result, 0);
2375
0
        RETVAL_NULL();
2376
0
      }
2377
4
    } else {
2378
4
      RETVAL_NULL();
2379
4
    }
2380
7
  } else {
2381
    /* if subject is an array */
2382
0
    ZEND_ASSERT(subject_ht != NULL);
2383
2384
0
    array_init_size(return_value, zend_hash_num_elements(subject_ht));
2385
0
    HashTable *return_value_ht = Z_ARRVAL_P(return_value);
2386
2387
    /* For each subject entry, convert it to string, then perform replacement
2388
       and add the result to the return_value array. */
2389
0
    ZEND_HASH_FOREACH_KEY_VAL(subject_ht, zend_ulong num_key, zend_string *string_key, zval *subject_entry) {
2390
0
      old_replace_count = replace_count;
2391
0
      zend_string *tmp_subject_entry_str;
2392
0
      zend_string *subject_entry_str = zval_get_tmp_string(subject_entry, &tmp_subject_entry_str);
2393
0
      result = php_replace_in_subject(regex_str, regex_ht, replace_str, replace_ht,
2394
0
        subject_entry_str, limit, &replace_count);
2395
2396
0
      if (result != NULL) {
2397
0
        if (!is_filter || replace_count > old_replace_count) {
2398
          /* Add to return array */
2399
0
          zval zv;
2400
0
          ZVAL_STR(&zv, result);
2401
0
          if (string_key) {
2402
0
            zend_hash_add_new(return_value_ht, string_key, &zv);
2403
0
          } else {
2404
0
            zend_hash_index_add_new(return_value_ht, num_key, &zv);
2405
0
          }
2406
0
        } else {
2407
0
          zend_string_release_ex(result, 0);
2408
0
        }
2409
0
      }
2410
0
      zend_tmp_string_release(tmp_subject_entry_str);
2411
0
    } ZEND_HASH_FOREACH_END();
2412
0
  }
2413
2414
7
  if (zcount) {
2415
0
    ZEND_TRY_ASSIGN_REF_LONG(zcount, replace_count);
2416
0
  }
2417
7
}
2418
2419
/* {{{ preg_replace_common */
2420
static void preg_replace_common(INTERNAL_FUNCTION_PARAMETERS, bool is_filter)
2421
7
{
2422
7
  zend_string *regex_str, *replace_str, *subject_str;
2423
7
  HashTable *regex_ht, *replace_ht, *subject_ht;
2424
7
  zend_long limit = -1;
2425
7
  zval *zcount = NULL;
2426
2427
  /* Get function parameters and do error-checking. */
2428
21
  ZEND_PARSE_PARAMETERS_START(3, 5)
2429
35
    Z_PARAM_ARRAY_HT_OR_STR(regex_ht, regex_str)
2430
35
    Z_PARAM_ARRAY_HT_OR_STR(replace_ht, replace_str)
2431
35
    Z_PARAM_ARRAY_HT_OR_STR(subject_ht, subject_str)
2432
35
    Z_PARAM_OPTIONAL
2433
35
    Z_PARAM_LONG(limit)
2434
0
    Z_PARAM_ZVAL(zcount)
2435
7
  ZEND_PARSE_PARAMETERS_END();
2436
2437
7
  _preg_replace_common(
2438
7
    return_value,
2439
7
    regex_ht, regex_str,
2440
7
    replace_ht, replace_str,
2441
7
    subject_ht, subject_str,
2442
7
    limit, zcount, is_filter);
2443
7
}
2444
/* }}} */
2445
2446
/* {{{ Perform Perl-style regular expression replacement. */
2447
PHP_FUNCTION(preg_replace)
2448
7
{
2449
7
  preg_replace_common(INTERNAL_FUNCTION_PARAM_PASSTHRU, false);
2450
7
}
2451
/* }}} */
2452
2453
ZEND_FRAMELESS_FUNCTION(preg_replace, 3)
2454
0
{
2455
0
  zend_string *regex_str, *replace_str, *subject_str;
2456
0
  HashTable *regex_ht, *replace_ht, *subject_ht;
2457
0
  zval regex_tmp, replace_tmp, subject_tmp;
2458
2459
0
  Z_FLF_PARAM_ARRAY_HT_OR_STR(1, regex_ht, regex_str, regex_tmp);
2460
0
  Z_FLF_PARAM_ARRAY_HT_OR_STR(2, replace_ht, replace_str, replace_tmp);
2461
0
  Z_FLF_PARAM_ARRAY_HT_OR_STR(3, subject_ht, subject_str, subject_tmp);
2462
2463
0
  _preg_replace_common(
2464
0
    return_value,
2465
0
    regex_ht, regex_str,
2466
0
    replace_ht, replace_str,
2467
0
    subject_ht, subject_str,
2468
0
    /* limit */ -1, /* zcount */ NULL, /* is_filter */ false);
2469
2470
0
flf_clean:;
2471
0
  Z_FLF_PARAM_FREE_STR(1, regex_tmp);
2472
0
  Z_FLF_PARAM_FREE_STR(2, replace_tmp);
2473
0
  Z_FLF_PARAM_FREE_STR(3, subject_tmp);
2474
0
}
2475
2476
/* {{{ Perform Perl-style regular expression replacement using replacement callback. */
2477
PHP_FUNCTION(preg_replace_callback)
2478
0
{
2479
0
  zval *zcount = NULL;
2480
0
  zend_string *regex_str;
2481
0
  HashTable *regex_ht;
2482
0
  zend_string *subject_str;
2483
0
  HashTable *subject_ht;
2484
0
  zend_long limit = -1, flags = 0;
2485
0
  size_t replace_count;
2486
0
  zend_fcall_info fci = empty_fcall_info;
2487
0
  zend_fcall_info_cache fcc = empty_fcall_info_cache;
2488
2489
  /* Get function parameters and do error-checking. */
2490
0
  ZEND_PARSE_PARAMETERS_START(3, 6)
2491
0
    Z_PARAM_ARRAY_HT_OR_STR(regex_ht, regex_str)
2492
0
    Z_PARAM_FUNC_NO_TRAMPOLINE_FREE(fci, fcc)
2493
0
    Z_PARAM_ARRAY_HT_OR_STR(subject_ht, subject_str)
2494
0
    Z_PARAM_OPTIONAL
2495
0
    Z_PARAM_LONG(limit)
2496
0
    Z_PARAM_ZVAL(zcount)
2497
0
    Z_PARAM_LONG(flags)
2498
0
  ZEND_PARSE_PARAMETERS_END_EX(goto free_trampoline);
2499
2500
0
  replace_count = php_preg_replace_func_impl(return_value, regex_str, regex_ht,
2501
0
    &fcc,
2502
0
    subject_str, subject_ht, limit, flags);
2503
0
  if (zcount) {
2504
0
    ZEND_TRY_ASSIGN_REF_LONG(zcount, replace_count);
2505
0
  }
2506
2507
0
free_trampoline:
2508
0
  zend_release_fcall_info_cache(&fcc);
2509
0
}
2510
/* }}} */
2511
2512
/* {{{ Perform Perl-style regular expression replacement using replacement callback. */
2513
PHP_FUNCTION(preg_replace_callback_array)
2514
0
{
2515
0
  zval *zcount = NULL;
2516
0
  HashTable *pattern, *subject_ht;
2517
0
  zend_string *subject_str;
2518
0
  zend_long limit = -1, flags = 0;
2519
0
  size_t replace_count = 0;
2520
2521
  /* Get function parameters and do error-checking. */
2522
0
  ZEND_PARSE_PARAMETERS_START(2, 5)
2523
0
    Z_PARAM_ARRAY_HT(pattern)
2524
0
    Z_PARAM_ARRAY_HT_OR_STR(subject_ht, subject_str)
2525
0
    Z_PARAM_OPTIONAL
2526
0
    Z_PARAM_LONG(limit)
2527
0
    Z_PARAM_ZVAL(zcount)
2528
0
    Z_PARAM_LONG(flags)
2529
0
  ZEND_PARSE_PARAMETERS_END();
2530
2531
0
  if (subject_ht) {
2532
0
    GC_TRY_ADDREF(subject_ht);
2533
0
  } else {
2534
0
    GC_TRY_ADDREF(subject_str);
2535
0
  }
2536
2537
0
  ZEND_HASH_FOREACH_STR_KEY_VAL(pattern, zend_string *str_idx_regex, zval *replace) {
2538
0
    if (!str_idx_regex) {
2539
0
      zend_argument_type_error(1, "must contain only string patterns as keys");
2540
0
      goto error;
2541
0
    }
2542
2543
0
    zend_fcall_info_cache fcc = empty_fcall_info_cache;
2544
2545
0
    if (!zend_is_callable(replace, &fcc, NULL)) {
2546
0
      zend_argument_type_error(1, "must contain only valid callbacks");
2547
0
      goto error;
2548
0
    }
2549
2550
0
    zval retval;
2551
0
    zend_fcc_addref(&fcc);
2552
0
    replace_count += php_preg_replace_func_impl(&retval, str_idx_regex, /* regex_ht */ NULL, &fcc,
2553
0
      subject_str, subject_ht, limit, flags);
2554
0
    zend_fcc_dtor(&fcc);
2555
2556
0
    switch (Z_TYPE(retval)) {
2557
0
      case IS_ARRAY:
2558
0
        ZEND_ASSERT(subject_ht);
2559
0
        zend_array_release(subject_ht);
2560
0
        subject_ht = Z_ARR(retval);
2561
0
        break;
2562
0
      case IS_STRING:
2563
0
        ZEND_ASSERT(subject_str);
2564
0
        zend_string_release(subject_str);
2565
0
        subject_str = Z_STR(retval);
2566
0
        break;
2567
0
      case IS_NULL:
2568
0
        RETVAL_NULL();
2569
0
        goto error;
2570
0
      default: ZEND_UNREACHABLE();
2571
0
    }
2572
2573
0
    if (EG(exception)) {
2574
0
      goto error;
2575
0
    }
2576
0
  } ZEND_HASH_FOREACH_END();
2577
2578
0
  if (zcount) {
2579
0
    ZEND_TRY_ASSIGN_REF_LONG(zcount, replace_count);
2580
0
  }
2581
2582
0
  if (subject_ht) {
2583
0
    RETVAL_ARR(subject_ht);
2584
    // Unset the type_flags of immutable arrays to prevent the VM from performing refcounting
2585
0
    if (GC_FLAGS(subject_ht) & IS_ARRAY_IMMUTABLE) {
2586
0
      Z_TYPE_FLAGS_P(return_value) = 0;
2587
0
    }
2588
0
    return;
2589
0
  } else {
2590
0
    RETURN_STR(subject_str);
2591
0
  }
2592
2593
0
error:
2594
0
  if (subject_ht) {
2595
0
    zend_array_release(subject_ht);
2596
0
  } else {
2597
0
    zend_string_release(subject_str);
2598
0
  }
2599
0
}
2600
/* }}} */
2601
2602
/* {{{ Perform Perl-style regular expression replacement and only return matches. */
2603
PHP_FUNCTION(preg_filter)
2604
0
{
2605
0
  preg_replace_common(INTERNAL_FUNCTION_PARAM_PASSTHRU, true);
2606
0
}
2607
/* }}} */
2608
2609
/* {{{ Split string into an array using a perl-style regular expression as a delimiter */
2610
PHP_FUNCTION(preg_split)
2611
0
{
2612
0
  zend_string     *regex;     /* Regular expression */
2613
0
  zend_string     *subject;   /* String to match against */
2614
0
  zend_long      limit_val = -1;/* Integer value of limit */
2615
0
  zend_long      flags = 0;   /* Match control flags */
2616
0
  pcre_cache_entry  *pce;     /* Compiled regular expression */
2617
2618
  /* Get function parameters and do error checking */
2619
0
  ZEND_PARSE_PARAMETERS_START(2, 4)
2620
0
    Z_PARAM_STR(regex)
2621
0
    Z_PARAM_STR(subject)
2622
0
    Z_PARAM_OPTIONAL
2623
0
    Z_PARAM_LONG(limit_val)
2624
0
    Z_PARAM_LONG(flags)
2625
0
  ZEND_PARSE_PARAMETERS_END();
2626
2627
  /* Compile regex or get it from cache. */
2628
0
  if ((pce = pcre_get_compiled_regex_cache(regex)) == NULL) {
2629
0
    RETURN_FALSE;
2630
0
  }
2631
2632
0
  pce->refcount++;
2633
0
  php_pcre_split_impl(pce, subject, return_value, limit_val, flags);
2634
0
  pce->refcount--;
2635
0
}
2636
/* }}} */
2637
2638
/* {{{ php_pcre_split */
2639
PHPAPI void php_pcre_split_impl(const pcre_cache_entry *pce, zend_string *subject_str, zval *return_value,
2640
  zend_long limit_val, zend_long flags)
2641
0
{
2642
0
  uint32_t     options;     /* Execution options */
2643
0
  int        count;       /* Count of matched subpatterns */
2644
0
  PCRE2_SIZE     start_offset;    /* Where the new search starts */
2645
0
  PCRE2_SIZE     last_match_offset; /* Location of last match */
2646
0
  uint32_t     no_empty;      /* If NO_EMPTY flag is set */
2647
0
  uint32_t     delim_capture;   /* If delimiters should be captured */
2648
0
  uint32_t     offset_capture;  /* If offsets should be captured */
2649
0
  uint32_t     num_subpats;   /* Number of captured subpatterns */
2650
0
  zval       tmp;
2651
0
  pcre2_match_data *match_data;
2652
0
  bool old_mdata_used;
2653
0
  char *subject = ZSTR_VAL(subject_str);
2654
2655
0
  no_empty = flags & PREG_SPLIT_NO_EMPTY;
2656
0
  delim_capture = flags & PREG_SPLIT_DELIM_CAPTURE;
2657
0
  offset_capture = flags & PREG_SPLIT_OFFSET_CAPTURE;
2658
2659
  /* Initialize return value */
2660
0
  array_init(return_value);
2661
0
  HashTable *return_value_ht = Z_ARRVAL_P(return_value);
2662
2663
  /* Calculate the size of the offsets array, and allocate memory for it. */
2664
0
  num_subpats = pce->capture_count + 1;
2665
2666
  /* Start at the beginning of the string */
2667
0
  start_offset = 0;
2668
0
  last_match_offset = 0;
2669
0
  PCRE_G(error_code) = PHP_PCRE_NO_ERROR;
2670
2671
0
  if (limit_val == -1) {
2672
    /* pass */
2673
0
  } else if (limit_val == 0) {
2674
0
    limit_val = -1;
2675
0
  } else if (limit_val <= 1) {
2676
0
    goto last;
2677
0
  }
2678
2679
0
  old_mdata_used = mdata_used;
2680
0
  if (!old_mdata_used && num_subpats <= PHP_PCRE_PREALLOC_MDATA_SIZE) {
2681
0
    mdata_used = true;
2682
0
    match_data = mdata;
2683
0
  } else {
2684
0
    match_data = pcre2_match_data_create_from_pattern(pce->re, PCRE_G(gctx_zmm));
2685
0
    if (!match_data) {
2686
0
      PCRE_G(error_code) = PHP_PCRE_INTERNAL_ERROR;
2687
0
      zval_ptr_dtor(return_value);
2688
0
      RETURN_FALSE;
2689
0
    }
2690
0
  }
2691
2692
0
  options = (pce->compile_options & PCRE2_UTF) ? 0 : PCRE2_NO_UTF_CHECK;
2693
2694
  /* Array of subpattern offsets */
2695
0
  PCRE2_SIZE *const offsets = pcre2_get_ovector_pointer(match_data);
2696
2697
#ifdef HAVE_PCRE_JIT_SUPPORT
2698
  if ((pce->preg_options & PREG_JIT) && options) {
2699
    count = pcre2_jit_match(pce->re, (PCRE2_SPTR)subject, ZSTR_LEN(subject_str), start_offset,
2700
        PCRE2_NO_UTF_CHECK, match_data, mctx);
2701
  } else
2702
#endif
2703
0
  count = pcre2_match(pce->re, (PCRE2_SPTR)subject, ZSTR_LEN(subject_str), start_offset,
2704
0
      options, match_data, mctx);
2705
2706
0
  while (1) {
2707
0
    if (ZEND_HAS_OOM()) {
2708
0
      PCRE_G(error_code) = PHP_PCRE_INTERNAL_ERROR;
2709
0
      break;
2710
0
    }
2711
2712
    /* If something matched */
2713
0
    if (count >= 0) {
2714
      /* Check for too many substrings condition. */
2715
0
      if (UNEXPECTED(count == 0)) {
2716
0
        php_error_docref(NULL,E_NOTICE, "Matched, but too many substrings");
2717
0
        count = num_subpats;
2718
0
      }
2719
2720
0
matched:
2721
0
      if (UNEXPECTED(offsets[1] < offsets[0])) {
2722
0
        PCRE_G(error_code) = PHP_PCRE_INTERNAL_ERROR;
2723
0
        break;
2724
0
      }
2725
2726
0
      if (!no_empty || offsets[0] != last_match_offset) {
2727
0
        if (offset_capture) {
2728
          /* Add (match, offset) pair to the return value */
2729
0
          add_offset_pair(
2730
0
            return_value_ht, subject, last_match_offset, offsets[0],
2731
0
            NULL, 0);
2732
0
        } else {
2733
          /* Add the piece to the return value */
2734
0
          populate_match_value_str(&tmp, subject, last_match_offset, offsets[0]);
2735
0
          zend_hash_next_index_insert_new(return_value_ht, &tmp);
2736
0
        }
2737
2738
        /* One less left to do */
2739
0
        if (limit_val != -1)
2740
0
          limit_val--;
2741
0
      }
2742
2743
0
      if (delim_capture) {
2744
0
        size_t i;
2745
0
        for (i = 1; i < count; i++) {
2746
          /* If we have matched a delimiter */
2747
0
          if (!no_empty || offsets[2*i] != offsets[2*i+1]) {
2748
0
            if (offset_capture) {
2749
0
              add_offset_pair(
2750
0
                return_value_ht, subject, offsets[2*i], offsets[2*i+1], NULL, 0);
2751
0
            } else {
2752
0
              populate_match_value_str(&tmp, subject, offsets[2*i], offsets[2*i+1]);
2753
0
              zend_hash_next_index_insert_new(return_value_ht, &tmp);
2754
0
            }
2755
0
          }
2756
0
        }
2757
0
      }
2758
2759
      /* Advance to the position right after the last full match */
2760
0
      start_offset = last_match_offset = offsets[1];
2761
2762
      /* If we have matched an empty string, mimic what Perl's /g options does.
2763
         This turns out to be rather cunning. First we set PCRE2_NOTEMPTY_ATSTART and try
2764
         the match again at the same point. If this fails (picked up above) we
2765
         advance to the next character. */
2766
0
      if (start_offset == offsets[0]) {
2767
        /* Get next piece if no limit or limit not yet reached and something matched*/
2768
0
        if (limit_val != -1 && limit_val <= 1) {
2769
0
          break;
2770
0
        }
2771
0
        count = pcre2_match(pce->re, (PCRE2_SPTR)subject, ZSTR_LEN(subject_str), start_offset,
2772
0
          PCRE2_NO_UTF_CHECK | PCRE2_NOTEMPTY_ATSTART | PCRE2_ANCHORED, match_data, mctx);
2773
0
        if (count >= 0) {
2774
0
          goto matched;
2775
0
        } else if (count == PCRE2_ERROR_NOMATCH) {
2776
          /* If we previously set PCRE2_NOTEMPTY_ATSTART after a null match,
2777
             this is not necessarily the end. We need to advance
2778
             the start offset, and continue. Fudge the offset values
2779
             to achieve this, unless we're already at the end of the string. */
2780
0
          if (start_offset < ZSTR_LEN(subject_str)) {
2781
0
            start_offset += calculate_unit_length(pce, subject + start_offset);
2782
0
          } else {
2783
0
            break;
2784
0
          }
2785
0
        } else {
2786
0
          goto error;
2787
0
        }
2788
0
      }
2789
2790
0
    } else if (count == PCRE2_ERROR_NOMATCH) {
2791
0
      break;
2792
0
    } else {
2793
0
error:
2794
0
      pcre_handle_exec_error(count);
2795
0
      break;
2796
0
    }
2797
2798
    /* Get next piece if no limit or limit not yet reached and something matched*/
2799
0
    if (limit_val != -1 && limit_val <= 1) {
2800
0
      break;
2801
0
    }
2802
2803
#ifdef HAVE_PCRE_JIT_SUPPORT
2804
    if (pce->preg_options & PREG_JIT) {
2805
      count = pcre2_jit_match(pce->re, (PCRE2_SPTR)subject, ZSTR_LEN(subject_str), start_offset,
2806
          PCRE2_NO_UTF_CHECK, match_data, mctx);
2807
    } else
2808
#endif
2809
0
    count = pcre2_match(pce->re, (PCRE2_SPTR)subject, ZSTR_LEN(subject_str), start_offset,
2810
0
        PCRE2_NO_UTF_CHECK, match_data, mctx);
2811
0
  }
2812
0
  if (match_data != mdata) {
2813
0
    pcre2_match_data_free(match_data);
2814
0
  }
2815
0
  mdata_used = old_mdata_used;
2816
2817
0
  if (PCRE_G(error_code) != PHP_PCRE_NO_ERROR) {
2818
0
    zval_ptr_dtor(return_value);
2819
0
    RETURN_FALSE;
2820
0
  }
2821
2822
0
last:
2823
0
  start_offset = last_match_offset; /* the offset might have been incremented, but without further successful matches */
2824
2825
0
  if (!no_empty || start_offset < ZSTR_LEN(subject_str)) {
2826
0
    if (offset_capture) {
2827
      /* Add the last (match, offset) pair to the return value */
2828
0
      add_offset_pair(return_value_ht, subject, start_offset, ZSTR_LEN(subject_str), NULL, 0);
2829
0
    } else {
2830
      /* Add the last piece to the return value */
2831
0
      if (start_offset == 0) {
2832
0
        ZVAL_STR_COPY(&tmp, subject_str);
2833
0
      } else {
2834
0
        populate_match_value_str(&tmp, subject, start_offset, ZSTR_LEN(subject_str));
2835
0
      }
2836
0
      zend_hash_next_index_insert_new(return_value_ht, &tmp);
2837
0
    }
2838
0
  }
2839
0
}
2840
/* }}} */
2841
2842
/* {{{ Quote regular expression characters plus an optional character */
2843
PHP_FUNCTION(preg_quote)
2844
1
{
2845
1
  zend_string *str;           /* Input string argument */
2846
1
  zend_string *delim = NULL;   /* Additional delimiter argument */
2847
1
  char    *in_str;      /* Input string */
2848
1
  char    *in_str_end;      /* End of the input string */
2849
1
  zend_string *out_str;     /* Output string with quoted characters */
2850
1
  size_t       extra_len;         /* Number of additional characters */
2851
1
  char    *p,         /* Iterator for input string */
2852
1
        *q,         /* Iterator for output string */
2853
1
         delim_char = '\0', /* Delimiter character to be quoted */
2854
1
         c;         /* Current character */
2855
2856
  /* Get the arguments and check for errors */
2857
3
  ZEND_PARSE_PARAMETERS_START(1, 2)
2858
4
    Z_PARAM_STR(str)
2859
1
    Z_PARAM_OPTIONAL
2860
2
    Z_PARAM_STR_OR_NULL(delim)
2861
1
  ZEND_PARSE_PARAMETERS_END();
2862
2863
  /* Nothing to do if we got an empty string */
2864
1
  if (ZSTR_LEN(str) == 0) {
2865
0
    RETURN_EMPTY_STRING();
2866
0
  }
2867
2868
1
  in_str = ZSTR_VAL(str);
2869
1
  in_str_end = in_str + ZSTR_LEN(str);
2870
2871
1
  if (delim) {
2872
0
    delim_char = ZSTR_VAL(delim)[0];
2873
0
  }
2874
2875
  /* Go through the string and quote necessary characters */
2876
1
  extra_len = 0;
2877
1
  p = in_str;
2878
142
  do {
2879
142
    c = *p;
2880
142
    switch(c) {
2881
0
      case '.':
2882
1
      case '\\':
2883
7
      case '+':
2884
7
      case '*':
2885
10
      case '?':
2886
11
      case '[':
2887
11
      case '^':
2888
12
      case ']':
2889
12
      case '$':
2890
17
      case '(':
2891
17
      case ')':
2892
19
      case '{':
2893
21
      case '}':
2894
21
      case '=':
2895
21
      case '!':
2896
21
      case '>':
2897
21
      case '<':
2898
21
      case '|':
2899
21
      case ':':
2900
22
      case '-':
2901
22
      case '#':
2902
22
        extra_len++;
2903
22
        break;
2904
2905
3
      case '\0':
2906
3
        extra_len+=3;
2907
3
        break;
2908
2909
117
      default:
2910
117
        if (c == delim_char) {
2911
0
          extra_len++;
2912
0
        }
2913
117
        break;
2914
142
    }
2915
142
    p++;
2916
142
  } while (p != in_str_end);
2917
2918
1
  if (extra_len == 0) {
2919
0
    RETURN_STR_COPY(str);
2920
0
  }
2921
2922
  /* Allocate enough memory so that even if each character
2923
     is quoted, we won't run out of room */
2924
1
  out_str = zend_string_safe_alloc(1, ZSTR_LEN(str), extra_len, 0);
2925
1
  q = ZSTR_VAL(out_str);
2926
1
  p = in_str;
2927
2928
142
  do {
2929
142
    c = *p;
2930
142
    switch(c) {
2931
0
      case '.':
2932
1
      case '\\':
2933
7
      case '+':
2934
7
      case '*':
2935
10
      case '?':
2936
11
      case '[':
2937
11
      case '^':
2938
12
      case ']':
2939
12
      case '$':
2940
17
      case '(':
2941
17
      case ')':
2942
19
      case '{':
2943
21
      case '}':
2944
21
      case '=':
2945
21
      case '!':
2946
21
      case '>':
2947
21
      case '<':
2948
21
      case '|':
2949
21
      case ':':
2950
22
      case '-':
2951
22
      case '#':
2952
22
        *q++ = '\\';
2953
22
        *q++ = c;
2954
22
        break;
2955
2956
3
      case '\0':
2957
3
        *q++ = '\\';
2958
3
        *q++ = '0';
2959
3
        *q++ = '0';
2960
3
        *q++ = '0';
2961
3
        break;
2962
2963
117
      default:
2964
117
        if (c == delim_char) {
2965
0
          *q++ = '\\';
2966
0
        }
2967
117
        *q++ = c;
2968
117
        break;
2969
142
    }
2970
142
    p++;
2971
142
  } while (p != in_str_end);
2972
1
  *q = '\0';
2973
2974
1
  RETURN_NEW_STR(out_str);
2975
1
}
2976
/* }}} */
2977
2978
/* {{{ Searches array and returns entries which match regex */
2979
PHP_FUNCTION(preg_grep)
2980
0
{
2981
0
  zend_string     *regex;     /* Regular expression */
2982
0
  zval        *input;     /* Input array */
2983
0
  zend_long      flags = 0;   /* Match control flags */
2984
0
  pcre_cache_entry  *pce;     /* Compiled regular expression */
2985
2986
  /* Get arguments and do error checking */
2987
0
  ZEND_PARSE_PARAMETERS_START(2, 3)
2988
0
    Z_PARAM_STR(regex)
2989
0
    Z_PARAM_ARRAY(input)
2990
0
    Z_PARAM_OPTIONAL
2991
0
    Z_PARAM_LONG(flags)
2992
0
  ZEND_PARSE_PARAMETERS_END();
2993
2994
  /* Compile regex or get it from cache. */
2995
0
  if ((pce = pcre_get_compiled_regex_cache(regex)) == NULL) {
2996
0
    RETURN_FALSE;
2997
0
  }
2998
2999
0
  pce->refcount++;
3000
0
  php_pcre_grep_impl(pce, input, return_value, flags);
3001
0
  pce->refcount--;
3002
0
}
3003
/* }}} */
3004
3005
PHPAPI void php_pcre_grep_impl(const pcre_cache_entry *pce, zval *input, zval *return_value, zend_long flags) /* {{{ */
3006
0
{
3007
0
  uint32_t     num_subpats;   /* Number of captured subpatterns */
3008
0
  int        count;       /* Count of matched subpatterns */
3009
0
  uint32_t     options;     /* Execution options */
3010
0
  bool     invert;      /* Whether to return non-matching
3011
                       entries */
3012
0
  bool old_mdata_used;
3013
0
  pcre2_match_data *match_data;
3014
0
  invert = flags & PREG_GREP_INVERT ? 1 : 0;
3015
3016
  /* Calculate the size of the offsets array, and allocate memory for it. */
3017
0
  num_subpats = pce->capture_count + 1;
3018
3019
  /* Initialize return array */
3020
0
  array_init(return_value);
3021
0
  HashTable *return_value_ht = Z_ARRVAL_P(return_value);
3022
3023
0
  PCRE_G(error_code) = PHP_PCRE_NO_ERROR;
3024
3025
0
  old_mdata_used = mdata_used;
3026
0
  if (!old_mdata_used && num_subpats <= PHP_PCRE_PREALLOC_MDATA_SIZE) {
3027
0
    mdata_used = true;
3028
0
    match_data = mdata;
3029
0
  } else {
3030
0
    match_data = pcre2_match_data_create_from_pattern(pce->re, PCRE_G(gctx_zmm));
3031
0
    if (!match_data) {
3032
0
      PCRE_G(error_code) = PHP_PCRE_INTERNAL_ERROR;
3033
0
      return;
3034
0
    }
3035
0
  }
3036
3037
0
  options = (pce->compile_options & PCRE2_UTF) ? 0 : PCRE2_NO_UTF_CHECK;
3038
3039
  /* Go through the input array */
3040
0
  ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(input), zend_ulong num_key, zend_string *string_key, zval *entry) {
3041
0
    zend_string *tmp_subject_str;
3042
0
    zend_string *subject_str = zval_get_tmp_string(entry, &tmp_subject_str);
3043
3044
    /* Perform the match */
3045
#ifdef HAVE_PCRE_JIT_SUPPORT
3046
    if ((pce->preg_options & PREG_JIT) && options) {
3047
      count = pcre2_jit_match(pce->re, (PCRE2_SPTR)ZSTR_VAL(subject_str), ZSTR_LEN(subject_str), 0,
3048
          PCRE2_NO_UTF_CHECK, match_data, mctx);
3049
    } else
3050
#endif
3051
0
    count = pcre2_match(pce->re, (PCRE2_SPTR)ZSTR_VAL(subject_str), ZSTR_LEN(subject_str), 0,
3052
0
        options, match_data, mctx);
3053
3054
    /* If the entry fits our requirements */
3055
0
    if (count >= 0) {
3056
      /* Check for too many substrings condition. */
3057
0
      if (UNEXPECTED(count == 0)) {
3058
0
        php_error_docref(NULL, E_NOTICE, "Matched, but too many substrings");
3059
0
      }
3060
0
      if (!invert) {
3061
0
        Z_TRY_ADDREF_P(entry);
3062
3063
        /* Add to return array */
3064
0
        if (string_key) {
3065
0
          zend_hash_update(return_value_ht, string_key, entry);
3066
0
        } else {
3067
0
          zend_hash_index_update(return_value_ht, num_key, entry);
3068
0
        }
3069
0
      }
3070
0
    } else if (count == PCRE2_ERROR_NOMATCH) {
3071
0
      if (invert) {
3072
0
        Z_TRY_ADDREF_P(entry);
3073
3074
        /* Add to return array */
3075
0
        if (string_key) {
3076
0
          zend_hash_update(return_value_ht, string_key, entry);
3077
0
        } else {
3078
0
          zend_hash_index_update(return_value_ht, num_key, entry);
3079
0
        }
3080
0
      }
3081
0
    } else {
3082
0
      pcre_handle_exec_error(count);
3083
0
      zend_tmp_string_release(tmp_subject_str);
3084
0
      break;
3085
0
    }
3086
3087
0
    zend_tmp_string_release(tmp_subject_str);
3088
0
  } ZEND_HASH_FOREACH_END();
3089
0
  if (match_data != mdata) {
3090
0
    pcre2_match_data_free(match_data);
3091
0
  }
3092
3093
0
  mdata_used = old_mdata_used;
3094
3095
0
  if (PCRE_G(error_code) != PHP_PCRE_NO_ERROR) {
3096
0
    zend_array_destroy(Z_ARR_P(return_value));
3097
0
    RETURN_FALSE;
3098
0
  }
3099
0
}
3100
/* }}} */
3101
3102
/* {{{ Returns the error code of the last regexp execution. */
3103
PHP_FUNCTION(preg_last_error)
3104
0
{
3105
0
  ZEND_PARSE_PARAMETERS_NONE();
3106
3107
0
  RETURN_LONG(PCRE_G(error_code));
3108
0
}
3109
/* }}} */
3110
3111
/* {{{ Returns the error message of the last regexp execution. */
3112
PHP_FUNCTION(preg_last_error_msg)
3113
0
{
3114
0
  ZEND_PARSE_PARAMETERS_NONE();
3115
3116
0
  RETURN_STRING(php_pcre_get_error_msg(PCRE_G(error_code)));
3117
0
}
3118
/* }}} */
3119
3120
/* {{{ module definition structures */
3121
3122
zend_module_entry pcre_module_entry = {
3123
  STANDARD_MODULE_HEADER,
3124
  "pcre",
3125
  ext_functions,
3126
  PHP_MINIT(pcre),
3127
  PHP_MSHUTDOWN(pcre),
3128
  PHP_RINIT(pcre),
3129
  PHP_RSHUTDOWN(pcre),
3130
  PHP_MINFO(pcre),
3131
  PHP_PCRE_VERSION,
3132
  PHP_MODULE_GLOBALS(pcre),
3133
  PHP_GINIT(pcre),
3134
  PHP_GSHUTDOWN(pcre),
3135
  NULL,
3136
  STANDARD_MODULE_PROPERTIES_EX
3137
};
3138
3139
#ifdef COMPILE_DL_PCRE
3140
ZEND_GET_MODULE(pcre)
3141
#endif
3142
3143
/* }}} */
3144
3145
PHPAPI pcre2_match_context *php_pcre_mctx(void)
3146
1
{/*{{{*/
3147
1
  return mctx;
3148
1
}/*}}}*/
3149
3150
PHPAPI pcre2_general_context *php_pcre_gctx(void)
3151
0
{/*{{{*/
3152
0
  return gctx;
3153
0
}/*}}}*/
3154
3155
PHPAPI pcre2_compile_context *php_pcre_cctx(void)
3156
0
{/*{{{*/
3157
0
  return cctx;
3158
0
}/*}}}*/
3159
3160
PHPAPI void php_pcre_pce_incref(pcre_cache_entry *pce)
3161
0
{/*{{{*/
3162
0
  assert(NULL != pce);
3163
0
  pce->refcount++;
3164
0
}/*}}}*/
3165
3166
PHPAPI void php_pcre_pce_decref(pcre_cache_entry *pce)
3167
0
{/*{{{*/
3168
0
  assert(NULL != pce);
3169
0
  assert(0 != pce->refcount);
3170
0
  pce->refcount--;
3171
0
}/*}}}*/
3172
3173
PHPAPI pcre2_code *php_pcre_pce_re(pcre_cache_entry *pce)
3174
0
{/*{{{*/
3175
0
  assert(NULL != pce);
3176
0
  return pce->re;
3177
0
}/*}}}*/