xref: /php-src/ext/standard/string.c (revision 75e3f3e0)
1 /*
2    +----------------------------------------------------------------------+
3    | Copyright (c) The PHP Group                                          |
4    +----------------------------------------------------------------------+
5    | This source file is subject to version 3.01 of the PHP license,      |
6    | that is bundled with this package in the file LICENSE, and is        |
7    | available through the world-wide-web at the following url:           |
8    | https://www.php.net/license/3_01.txt                                 |
9    | If you did not receive a copy of the PHP license and are unable to   |
10    | obtain it through the world-wide-web, please send a note to          |
11    | license@php.net so we can mail you a copy immediately.               |
12    +----------------------------------------------------------------------+
13    | Authors: Rasmus Lerdorf <rasmus@php.net>                             |
14    |          Stig Sæther Bakken <ssb@php.net>                          |
15    |          Zeev Suraski <zeev@php.net>                                 |
16    +----------------------------------------------------------------------+
17  */
18 
19 #include <stdio.h>
20 #include "php.h"
21 #include "php_string.h"
22 #include "php_variables.h"
23 #include <locale.h>
24 #ifdef HAVE_LANGINFO_H
25 # include <langinfo.h>
26 #endif
27 
28 #ifdef HAVE_LIBINTL
29 # include <libintl.h> /* For LC_MESSAGES */
30 #endif
31 
32 #include "scanf.h"
33 #include "zend_API.h"
34 #include "zend_execute.h"
35 #include "php_globals.h"
36 #include "basic_functions.h"
37 #include "zend_smart_str.h"
38 #include <Zend/zend_exceptions.h>
39 #ifdef ZTS
40 #include "TSRM.h"
41 #endif
42 
43 /* For str_getcsv() support */
44 #include "ext/standard/file.h"
45 /* For php_next_utf8_char() */
46 #include "ext/standard/html.h"
47 #include "ext/random/php_random.h"
48 
49 #ifdef __SSE2__
50 #include <emmintrin.h>
51 #include "Zend/zend_bitset.h"
52 #endif
53 
54 /* this is read-only, so it's ok */
55 ZEND_SET_ALIGNED(16, static const char hexconvtab[]) = "0123456789abcdef";
56 
57 /* localeconv mutex */
58 #ifdef ZTS
59 static MUTEX_T locale_mutex = NULL;
60 #endif
61 
62 /* {{{ php_bin2hex */
php_bin2hex(const unsigned char * old,const size_t oldlen)63 static zend_string *php_bin2hex(const unsigned char *old, const size_t oldlen)
64 {
65 	zend_string *result;
66 	size_t i, j;
67 
68 	result = zend_string_safe_alloc(oldlen, 2 * sizeof(char), 0, 0);
69 
70 	for (i = j = 0; i < oldlen; i++) {
71 		ZSTR_VAL(result)[j++] = hexconvtab[old[i] >> 4];
72 		ZSTR_VAL(result)[j++] = hexconvtab[old[i] & 15];
73 	}
74 	ZSTR_VAL(result)[j] = '\0';
75 
76 	return result;
77 }
78 /* }}} */
79 
80 /* {{{ php_hex2bin */
php_hex2bin(const unsigned char * old,const size_t oldlen)81 static zend_string *php_hex2bin(const unsigned char *old, const size_t oldlen)
82 {
83 	size_t target_length = oldlen >> 1;
84 	zend_string *str = zend_string_alloc(target_length, 0);
85 	unsigned char *ret = (unsigned char *)ZSTR_VAL(str);
86 	size_t i, j;
87 
88 	for (i = j = 0; i < target_length; i++) {
89 		unsigned char c = old[j++];
90 		unsigned char l = c & ~0x20;
91 		int is_letter = ((unsigned int) ((l - 'A') ^ (l - 'F' - 1))) >> (8 * sizeof(unsigned int) - 1);
92 		unsigned char d;
93 
94 		/* basically (c >= '0' && c <= '9') || (l >= 'A' && l <= 'F') */
95 		if (EXPECTED((((c ^ '0') - 10) >> (8 * sizeof(unsigned int) - 1)) | is_letter)) {
96 			d = (l - 0x10 - 0x27 * is_letter) << 4;
97 		} else {
98 			zend_string_efree(str);
99 			return NULL;
100 		}
101 		c = old[j++];
102 		l = c & ~0x20;
103 		is_letter = ((unsigned int) ((l - 'A') ^ (l - 'F' - 1))) >> (8 * sizeof(unsigned int) - 1);
104 		if (EXPECTED((((c ^ '0') - 10) >> (8 * sizeof(unsigned int) - 1)) | is_letter)) {
105 			d |= l - 0x10 - 0x27 * is_letter;
106 		} else {
107 			zend_string_efree(str);
108 			return NULL;
109 		}
110 		ret[i] = d;
111 	}
112 	ret[i] = '\0';
113 
114 	return str;
115 }
116 /* }}} */
117 
118 /* {{{ localeconv_r
119  * glibc's localeconv is not reentrant, so lets make it so ... sorta */
localeconv_r(struct lconv * out)120 PHPAPI struct lconv *localeconv_r(struct lconv *out)
121 {
122 
123 #ifdef ZTS
124 	tsrm_mutex_lock( locale_mutex );
125 #endif
126 
127 /*  cur->locinfo is struct __crt_locale_info which implementation is
128 	hidden in vc14. TODO revisit this and check if a workaround available
129 	and needed. */
130 #if defined(PHP_WIN32) && _MSC_VER < 1900 && defined(ZTS)
131 	{
132 		/* Even with the enabled per thread locale, localeconv
133 			won't check any locale change in the master thread. */
134 		_locale_t cur = _get_current_locale();
135 		*out = *cur->locinfo->lconv;
136 		_free_locale(cur);
137 	}
138 #else
139 	/* localeconv doesn't return an error condition */
140 	*out = *localeconv();
141 #endif
142 
143 #ifdef ZTS
144 	tsrm_mutex_unlock( locale_mutex );
145 #endif
146 
147 	return out;
148 }
149 /* }}} */
150 
151 #ifdef ZTS
152 /* {{{ PHP_MINIT_FUNCTION */
PHP_MINIT_FUNCTION(localeconv)153 PHP_MINIT_FUNCTION(localeconv)
154 {
155 	locale_mutex = tsrm_mutex_alloc();
156 	return SUCCESS;
157 }
158 /* }}} */
159 
160 /* {{{ PHP_MSHUTDOWN_FUNCTION */
PHP_MSHUTDOWN_FUNCTION(localeconv)161 PHP_MSHUTDOWN_FUNCTION(localeconv)
162 {
163 	tsrm_mutex_free( locale_mutex );
164 	locale_mutex = NULL;
165 	return SUCCESS;
166 }
167 /* }}} */
168 #endif
169 
170 /* {{{ Converts the binary representation of data to hex */
PHP_FUNCTION(bin2hex)171 PHP_FUNCTION(bin2hex)
172 {
173 	zend_string *result;
174 	zend_string *data;
175 
176 	ZEND_PARSE_PARAMETERS_START(1, 1)
177 		Z_PARAM_STR(data)
178 	ZEND_PARSE_PARAMETERS_END();
179 
180 	result = php_bin2hex((unsigned char *)ZSTR_VAL(data), ZSTR_LEN(data));
181 
182 	RETURN_STR(result);
183 }
184 /* }}} */
185 
186 /* {{{ Converts the hex representation of data to binary */
PHP_FUNCTION(hex2bin)187 PHP_FUNCTION(hex2bin)
188 {
189 	zend_string *result, *data;
190 
191 	ZEND_PARSE_PARAMETERS_START(1, 1)
192 		Z_PARAM_STR(data)
193 	ZEND_PARSE_PARAMETERS_END();
194 
195 	if (ZSTR_LEN(data) % 2 != 0) {
196 		php_error_docref(NULL, E_WARNING, "Hexadecimal input string must have an even length");
197 		RETURN_FALSE;
198 	}
199 
200 	result = php_hex2bin((unsigned char *)ZSTR_VAL(data), ZSTR_LEN(data));
201 
202 	if (!result) {
203 		php_error_docref(NULL, E_WARNING, "Input string must be hexadecimal string");
204 		RETURN_FALSE;
205 	}
206 
207 	RETVAL_STR(result);
208 }
209 /* }}} */
210 
php_spn_common_handler(INTERNAL_FUNCTION_PARAMETERS,int behavior)211 static void php_spn_common_handler(INTERNAL_FUNCTION_PARAMETERS, int behavior) /* {{{ */
212 {
213 	zend_string *s11, *s22;
214 	zend_long start = 0, len = 0;
215 	bool len_is_null = 1;
216 
217 	ZEND_PARSE_PARAMETERS_START(2, 4)
218 		Z_PARAM_STR(s11)
219 		Z_PARAM_STR(s22)
220 		Z_PARAM_OPTIONAL
221 		Z_PARAM_LONG(start)
222 		Z_PARAM_LONG_OR_NULL(len, len_is_null)
223 	ZEND_PARSE_PARAMETERS_END();
224 
225 	size_t remain_len = ZSTR_LEN(s11);
226 	if (start < 0) {
227 		start += remain_len;
228 		if (start < 0) {
229 			start = 0;
230 		}
231 	} else if ((size_t) start > remain_len) {
232 		start = remain_len;
233 	}
234 
235 	remain_len -= start;
236 	if (!len_is_null) {
237 		if (len < 0) {
238 			len += remain_len;
239 			if (len < 0) {
240 				len = 0;
241 			}
242 		} else if ((size_t) len > remain_len) {
243 			len = remain_len;
244 		}
245 	} else {
246 		len = remain_len;
247 	}
248 
249 	if (len == 0) {
250 		RETURN_LONG(0);
251 	}
252 
253 	if (behavior == PHP_STR_STRSPN) {
254 		RETURN_LONG(php_strspn(ZSTR_VAL(s11) + start /*str1_start*/,
255 						ZSTR_VAL(s22) /*str2_start*/,
256 						ZSTR_VAL(s11) + start + len /*str1_end*/,
257 						ZSTR_VAL(s22) + ZSTR_LEN(s22) /*str2_end*/));
258 	} else {
259 		ZEND_ASSERT(behavior == PHP_STR_STRCSPN);
260 		RETURN_LONG(php_strcspn(ZSTR_VAL(s11) + start /*str1_start*/,
261 						ZSTR_VAL(s22) /*str2_start*/,
262 						ZSTR_VAL(s11) + start + len /*str1_end*/,
263 						ZSTR_VAL(s22) + ZSTR_LEN(s22) /*str2_end*/));
264 	}
265 }
266 /* }}} */
267 
268 /* {{{ Finds length of initial segment consisting entirely of characters found in mask. If start or/and length is provided works like strspn(substr($s,$start,$len),$good_chars) */
PHP_FUNCTION(strspn)269 PHP_FUNCTION(strspn)
270 {
271 	php_spn_common_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_STR_STRSPN);
272 }
273 /* }}} */
274 
275 /* {{{ Finds length of initial segment consisting entirely of characters not found in mask. If start or/and length is provide works like strcspn(substr($s,$start,$len),$bad_chars) */
PHP_FUNCTION(strcspn)276 PHP_FUNCTION(strcspn)
277 {
278 	php_spn_common_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_STR_STRCSPN);
279 }
280 /* }}} */
281 
282 #ifdef HAVE_NL_LANGINFO
283 /* {{{ Query language and locale information */
PHP_FUNCTION(nl_langinfo)284 PHP_FUNCTION(nl_langinfo)
285 {
286 	zend_long item;
287 	char *value;
288 
289 	ZEND_PARSE_PARAMETERS_START(1, 1)
290 		Z_PARAM_LONG(item)
291 	ZEND_PARSE_PARAMETERS_END();
292 
293 	switch(item) { /* {{{ */
294 #ifdef ABDAY_1
295 		case ABDAY_1:
296 		case ABDAY_2:
297 		case ABDAY_3:
298 		case ABDAY_4:
299 		case ABDAY_5:
300 		case ABDAY_6:
301 		case ABDAY_7:
302 #endif
303 #ifdef DAY_1
304 		case DAY_1:
305 		case DAY_2:
306 		case DAY_3:
307 		case DAY_4:
308 		case DAY_5:
309 		case DAY_6:
310 		case DAY_7:
311 #endif
312 #ifdef ABMON_1
313 		case ABMON_1:
314 		case ABMON_2:
315 		case ABMON_3:
316 		case ABMON_4:
317 		case ABMON_5:
318 		case ABMON_6:
319 		case ABMON_7:
320 		case ABMON_8:
321 		case ABMON_9:
322 		case ABMON_10:
323 		case ABMON_11:
324 		case ABMON_12:
325 #endif
326 #ifdef MON_1
327 		case MON_1:
328 		case MON_2:
329 		case MON_3:
330 		case MON_4:
331 		case MON_5:
332 		case MON_6:
333 		case MON_7:
334 		case MON_8:
335 		case MON_9:
336 		case MON_10:
337 		case MON_11:
338 		case MON_12:
339 #endif
340 #ifdef AM_STR
341 		case AM_STR:
342 #endif
343 #ifdef PM_STR
344 		case PM_STR:
345 #endif
346 #ifdef D_T_FMT
347 		case D_T_FMT:
348 #endif
349 #ifdef D_FMT
350 		case D_FMT:
351 #endif
352 #ifdef T_FMT
353 		case T_FMT:
354 #endif
355 #ifdef T_FMT_AMPM
356 		case T_FMT_AMPM:
357 #endif
358 #ifdef ERA
359 		case ERA:
360 #endif
361 #ifdef ERA_YEAR
362 		case ERA_YEAR:
363 #endif
364 #ifdef ERA_D_T_FMT
365 		case ERA_D_T_FMT:
366 #endif
367 #ifdef ERA_D_FMT
368 		case ERA_D_FMT:
369 #endif
370 #ifdef ERA_T_FMT
371 		case ERA_T_FMT:
372 #endif
373 #ifdef ALT_DIGITS
374 		case ALT_DIGITS:
375 #endif
376 #ifdef INT_CURR_SYMBOL
377 		case INT_CURR_SYMBOL:
378 #endif
379 #ifdef CURRENCY_SYMBOL
380 		case CURRENCY_SYMBOL:
381 #endif
382 #ifdef CRNCYSTR
383 		case CRNCYSTR:
384 #endif
385 #ifdef MON_DECIMAL_POINT
386 		case MON_DECIMAL_POINT:
387 #endif
388 #ifdef MON_THOUSANDS_SEP
389 		case MON_THOUSANDS_SEP:
390 #endif
391 #ifdef MON_GROUPING
392 		case MON_GROUPING:
393 #endif
394 #ifdef POSITIVE_SIGN
395 		case POSITIVE_SIGN:
396 #endif
397 #ifdef NEGATIVE_SIGN
398 		case NEGATIVE_SIGN:
399 #endif
400 #ifdef INT_FRAC_DIGITS
401 		case INT_FRAC_DIGITS:
402 #endif
403 #ifdef FRAC_DIGITS
404 		case FRAC_DIGITS:
405 #endif
406 #ifdef P_CS_PRECEDES
407 		case P_CS_PRECEDES:
408 #endif
409 #ifdef P_SEP_BY_SPACE
410 		case P_SEP_BY_SPACE:
411 #endif
412 #ifdef N_CS_PRECEDES
413 		case N_CS_PRECEDES:
414 #endif
415 #ifdef N_SEP_BY_SPACE
416 		case N_SEP_BY_SPACE:
417 #endif
418 #ifdef P_SIGN_POSN
419 		case P_SIGN_POSN:
420 #endif
421 #ifdef N_SIGN_POSN
422 		case N_SIGN_POSN:
423 #endif
424 #ifdef DECIMAL_POINT
425 		case DECIMAL_POINT:
426 #elif defined(RADIXCHAR)
427 		case RADIXCHAR:
428 #endif
429 #ifdef THOUSANDS_SEP
430 		case THOUSANDS_SEP:
431 #elif defined(THOUSEP)
432 		case THOUSEP:
433 #endif
434 #ifdef GROUPING
435 		case GROUPING:
436 #endif
437 #ifdef YESEXPR
438 		case YESEXPR:
439 #endif
440 #ifdef NOEXPR
441 		case NOEXPR:
442 #endif
443 #ifdef YESSTR
444 		case YESSTR:
445 #endif
446 #ifdef NOSTR
447 		case NOSTR:
448 #endif
449 #ifdef CODESET
450 		case CODESET:
451 #endif
452 			break;
453 		default:
454 			php_error_docref(NULL, E_WARNING, "Item '" ZEND_LONG_FMT "' is not valid", item);
455 			RETURN_FALSE;
456 	}
457 	/* }}} */
458 
459 	value = nl_langinfo(item);
460 	if (value == NULL) {
461 		RETURN_FALSE;
462 	} else {
463 		RETURN_STRING(value);
464 	}
465 }
466 #endif
467 /* }}} */
468 
469 /* {{{ Compares two strings using the current locale */
PHP_FUNCTION(strcoll)470 PHP_FUNCTION(strcoll)
471 {
472 	zend_string *s1, *s2;
473 
474 	ZEND_PARSE_PARAMETERS_START(2, 2)
475 		Z_PARAM_STR(s1)
476 		Z_PARAM_STR(s2)
477 	ZEND_PARSE_PARAMETERS_END();
478 
479 	RETURN_LONG(strcoll((const char *) ZSTR_VAL(s1),
480 	                    (const char *) ZSTR_VAL(s2)));
481 }
482 /* }}} */
483 
484 /* {{{ php_charmask
485  * Fills a 256-byte bytemask with input. You can specify a range like 'a..z',
486  * it needs to be incrementing.
487  * Returns: FAILURE/SUCCESS whether the input was correct (i.e. no range errors)
488  */
php_charmask(const unsigned char * input,size_t len,char * mask)489 static inline zend_result php_charmask(const unsigned char *input, size_t len, char *mask)
490 {
491 	const unsigned char *end;
492 	unsigned char c;
493 	zend_result result = SUCCESS;
494 
495 	memset(mask, 0, 256);
496 	for (end = input+len; input < end; input++) {
497 		c=*input;
498 		if ((input+3 < end) && input[1] == '.' && input[2] == '.'
499 				&& input[3] >= c) {
500 			memset(mask+c, 1, input[3] - c + 1);
501 			input+=3;
502 		} else if ((input+1 < end) && input[0] == '.' && input[1] == '.') {
503 			/* Error, try to be as helpful as possible:
504 			   (a range ending/starting with '.' won't be captured here) */
505 			if (end-len >= input) { /* there was no 'left' char */
506 				php_error_docref(NULL, E_WARNING, "Invalid '..'-range, no character to the left of '..'");
507 				result = FAILURE;
508 				continue;
509 			}
510 			if (input+2 >= end) { /* there is no 'right' char */
511 				php_error_docref(NULL, E_WARNING, "Invalid '..'-range, no character to the right of '..'");
512 				result = FAILURE;
513 				continue;
514 			}
515 			if (input[-1] > input[2]) { /* wrong order */
516 				php_error_docref(NULL, E_WARNING, "Invalid '..'-range, '..'-range needs to be incrementing");
517 				result = FAILURE;
518 				continue;
519 			}
520 			/* FIXME: better error (a..b..c is the only left possibility?) */
521 			php_error_docref(NULL, E_WARNING, "Invalid '..'-range");
522 			result = FAILURE;
523 			continue;
524 		} else {
525 			mask[c]=1;
526 		}
527 	}
528 	return result;
529 }
530 /* }}} */
531 
532 /* {{{ php_trim_int()
533  * mode 1 : trim left
534  * mode 2 : trim right
535  * mode 3 : trim left and right
536  * what indicates which chars are to be trimmed. NULL->default (' \t\n\r\v\0')
537  */
php_trim_int(zend_string * str,const char * what,size_t what_len,int mode)538 static zend_always_inline zend_string *php_trim_int(zend_string *str, const char *what, size_t what_len, int mode)
539 {
540 	const char *start = ZSTR_VAL(str);
541 	const char *end = start + ZSTR_LEN(str);
542 	char mask[256];
543 
544 	if (what) {
545 		if (what_len == 1) {
546 			char p = *what;
547 			if (mode & 1) {
548 				while (start != end) {
549 					if (*start == p) {
550 						start++;
551 					} else {
552 						break;
553 					}
554 				}
555 			}
556 			if (mode & 2) {
557 				while (start != end) {
558 					if (*(end-1) == p) {
559 						end--;
560 					} else {
561 						break;
562 					}
563 				}
564 			}
565 		} else {
566 			php_charmask((const unsigned char *) what, what_len, mask);
567 
568 			if (mode & 1) {
569 				while (start != end) {
570 					if (mask[(unsigned char)*start]) {
571 						start++;
572 					} else {
573 						break;
574 					}
575 				}
576 			}
577 			if (mode & 2) {
578 				while (start != end) {
579 					if (mask[(unsigned char)*(end-1)]) {
580 						end--;
581 					} else {
582 						break;
583 					}
584 				}
585 			}
586 		}
587 	} else {
588 		if (mode & 1) {
589 			while (start != end) {
590 				unsigned char c = (unsigned char)*start;
591 
592 				if (c <= ' ' &&
593 				    (c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == '\v' || c == '\0')) {
594 					start++;
595 				} else {
596 					break;
597 				}
598 			}
599 		}
600 		if (mode & 2) {
601 			while (start != end) {
602 				unsigned char c = (unsigned char)*(end-1);
603 
604 				if (c <= ' ' &&
605 				    (c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == '\v' || c == '\0')) {
606 					end--;
607 				} else {
608 					break;
609 				}
610 			}
611 		}
612 	}
613 
614 	if (ZSTR_LEN(str) == end - start) {
615 		return zend_string_copy(str);
616 	} else if (end - start == 0) {
617 		return ZSTR_EMPTY_ALLOC();
618 	} else {
619 		return zend_string_init(start, end - start, 0);
620 	}
621 }
622 /* }}} */
623 
624 /* {{{ php_trim_int()
625  * mode 1 : trim left
626  * mode 2 : trim right
627  * mode 3 : trim left and right
628  * what indicates which chars are to be trimmed. NULL->default (' \t\n\r\v\0')
629  */
php_trim(zend_string * str,const char * what,size_t what_len,int mode)630 PHPAPI zend_string *php_trim(zend_string *str, const char *what, size_t what_len, int mode)
631 {
632 	return php_trim_int(str, what, what_len, mode);
633 }
634 /* }}} */
635 
636 /* {{{ php_do_trim
637  * Base for trim(), rtrim() and ltrim() functions.
638  */
php_do_trim(INTERNAL_FUNCTION_PARAMETERS,int mode)639 static zend_always_inline void php_do_trim(INTERNAL_FUNCTION_PARAMETERS, int mode)
640 {
641 	zend_string *str;
642 	zend_string *what = NULL;
643 
644 	ZEND_PARSE_PARAMETERS_START(1, 2)
645 		Z_PARAM_STR(str)
646 		Z_PARAM_OPTIONAL
647 		Z_PARAM_STR(what)
648 	ZEND_PARSE_PARAMETERS_END();
649 
650 	ZVAL_STR(return_value, php_trim_int(str, (what ? ZSTR_VAL(what) : NULL), (what ? ZSTR_LEN(what) : 0), mode));
651 }
652 /* }}} */
653 
654 /* {{{ Strips whitespace from the beginning and end of a string */
PHP_FUNCTION(trim)655 PHP_FUNCTION(trim)
656 {
657 	php_do_trim(INTERNAL_FUNCTION_PARAM_PASSTHRU, 3);
658 }
659 /* }}} */
660 
661 ZEND_FRAMELESS_FUNCTION(trim, 1)
662 {
663 	zval str_tmp;
664 	zend_string *str;
665 
666 	Z_FLF_PARAM_STR(1, str, str_tmp);
667 
668 	ZVAL_STR(return_value, php_trim_int(str, /* what */ NULL, /* what_len */ 0, /* mode */ 3));
669 
670 flf_clean:
671 	Z_FLF_PARAM_FREE_STR(1, str_tmp);
672 }
673 
674 ZEND_FRAMELESS_FUNCTION(trim, 2)
675 {
676 	zval str_tmp, what_tmp;
677 	zend_string *str, *what;
678 
679 	Z_FLF_PARAM_STR(1, str, str_tmp);
680 	Z_FLF_PARAM_STR(2, what, what_tmp);
681 
682 	ZVAL_STR(return_value, php_trim_int(str, ZSTR_VAL(what), ZSTR_LEN(what), /* mode */ 3));
683 
684 flf_clean:
685 	Z_FLF_PARAM_FREE_STR(1, str_tmp);
686 	Z_FLF_PARAM_FREE_STR(2, what_tmp);
687 }
688 
689 /* {{{ Removes trailing whitespace */
PHP_FUNCTION(rtrim)690 PHP_FUNCTION(rtrim)
691 {
692 	php_do_trim(INTERNAL_FUNCTION_PARAM_PASSTHRU, 2);
693 }
694 /* }}} */
695 
696 /* {{{ Strips whitespace from the beginning of a string */
PHP_FUNCTION(ltrim)697 PHP_FUNCTION(ltrim)
698 {
699 	php_do_trim(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
700 }
701 /* }}} */
702 
703 /* {{{ Wraps buffer to selected number of characters using string break char */
PHP_FUNCTION(wordwrap)704 PHP_FUNCTION(wordwrap)
705 {
706 	zend_string *text;
707 	char *breakchar = "\n";
708 	size_t newtextlen, chk, breakchar_len = 1;
709 	size_t alloced;
710 	zend_long current = 0, laststart = 0, lastspace = 0;
711 	zend_long linelength = 75;
712 	bool docut = 0;
713 	zend_string *newtext;
714 
715 	ZEND_PARSE_PARAMETERS_START(1, 4)
716 		Z_PARAM_STR(text)
717 		Z_PARAM_OPTIONAL
718 		Z_PARAM_LONG(linelength)
719 		Z_PARAM_STRING(breakchar, breakchar_len)
720 		Z_PARAM_BOOL(docut)
721 	ZEND_PARSE_PARAMETERS_END();
722 
723 	if (ZSTR_LEN(text) == 0) {
724 		RETURN_EMPTY_STRING();
725 	}
726 
727 	if (breakchar_len == 0) {
728 		zend_argument_value_error(3, "cannot be empty");
729 		RETURN_THROWS();
730 	}
731 
732 	if (linelength == 0 && docut) {
733 		zend_argument_value_error(4, "cannot be true when argument #2 ($width) is 0");
734 		RETURN_THROWS();
735 	}
736 
737 	/* Special case for a single-character break as it needs no
738 	   additional storage space */
739 	if (breakchar_len == 1 && !docut) {
740 		newtext = zend_string_init(ZSTR_VAL(text), ZSTR_LEN(text), 0);
741 
742 		laststart = lastspace = 0;
743 		for (current = 0; current < (zend_long)ZSTR_LEN(text); current++) {
744 			if (ZSTR_VAL(text)[current] == breakchar[0]) {
745 				laststart = lastspace = current + 1;
746 			} else if (ZSTR_VAL(text)[current] == ' ') {
747 				if (current - laststart >= linelength) {
748 					ZSTR_VAL(newtext)[current] = breakchar[0];
749 					laststart = current + 1;
750 				}
751 				lastspace = current;
752 			} else if (current - laststart >= linelength && laststart != lastspace) {
753 				ZSTR_VAL(newtext)[lastspace] = breakchar[0];
754 				laststart = lastspace + 1;
755 			}
756 		}
757 
758 		RETURN_NEW_STR(newtext);
759 	} else {
760 		/* Multiple character line break or forced cut */
761 		if (linelength > 0) {
762 			chk = (size_t)(ZSTR_LEN(text)/linelength + 1);
763 			newtext = zend_string_safe_alloc(chk, breakchar_len, ZSTR_LEN(text), 0);
764 			alloced = ZSTR_LEN(text) + chk * breakchar_len + 1;
765 		} else {
766 			chk = ZSTR_LEN(text);
767 			alloced = ZSTR_LEN(text) * (breakchar_len + 1) + 1;
768 			newtext = zend_string_safe_alloc(ZSTR_LEN(text), breakchar_len + 1, 0, 0);
769 		}
770 
771 		/* now keep track of the actual new text length */
772 		newtextlen = 0;
773 
774 		laststart = lastspace = 0;
775 		for (current = 0; current < (zend_long)ZSTR_LEN(text); current++) {
776 			if (chk == 0) {
777 				alloced += (size_t) (((ZSTR_LEN(text) - current + 1)/linelength + 1) * breakchar_len) + 1;
778 				newtext = zend_string_extend(newtext, alloced, 0);
779 				chk = (size_t) ((ZSTR_LEN(text) - current)/linelength) + 1;
780 			}
781 			/* when we hit an existing break, copy to new buffer, and
782 			 * fix up laststart and lastspace */
783 			if (ZSTR_VAL(text)[current] == breakchar[0]
784 				&& current + breakchar_len < ZSTR_LEN(text)
785 				&& !strncmp(ZSTR_VAL(text) + current, breakchar, breakchar_len)) {
786 				memcpy(ZSTR_VAL(newtext) + newtextlen, ZSTR_VAL(text) + laststart, current - laststart + breakchar_len);
787 				newtextlen += current - laststart + breakchar_len;
788 				current += breakchar_len - 1;
789 				laststart = lastspace = current + 1;
790 				chk--;
791 			}
792 			/* if it is a space, check if it is at the line boundary,
793 			 * copy and insert a break, or just keep track of it */
794 			else if (ZSTR_VAL(text)[current] == ' ') {
795 				if (current - laststart >= linelength) {
796 					memcpy(ZSTR_VAL(newtext) + newtextlen, ZSTR_VAL(text) + laststart, current - laststart);
797 					newtextlen += current - laststart;
798 					memcpy(ZSTR_VAL(newtext) + newtextlen, breakchar, breakchar_len);
799 					newtextlen += breakchar_len;
800 					laststart = current + 1;
801 					chk--;
802 				}
803 				lastspace = current;
804 			}
805 			/* if we are cutting, and we've accumulated enough
806 			 * characters, and we haven't see a space for this line,
807 			 * copy and insert a break. */
808 			else if (current - laststart >= linelength
809 					&& docut && laststart >= lastspace) {
810 				memcpy(ZSTR_VAL(newtext) + newtextlen, ZSTR_VAL(text) + laststart, current - laststart);
811 				newtextlen += current - laststart;
812 				memcpy(ZSTR_VAL(newtext) + newtextlen, breakchar, breakchar_len);
813 				newtextlen += breakchar_len;
814 				laststart = lastspace = current;
815 				chk--;
816 			}
817 			/* if the current word puts us over the linelength, copy
818 			 * back up until the last space, insert a break, and move
819 			 * up the laststart */
820 			else if (current - laststart >= linelength
821 					&& laststart < lastspace) {
822 				memcpy(ZSTR_VAL(newtext) + newtextlen, ZSTR_VAL(text) + laststart, lastspace - laststart);
823 				newtextlen += lastspace - laststart;
824 				memcpy(ZSTR_VAL(newtext) + newtextlen, breakchar, breakchar_len);
825 				newtextlen += breakchar_len;
826 				laststart = lastspace = lastspace + 1;
827 				chk--;
828 			}
829 		}
830 
831 		/* copy over any stragglers */
832 		if (laststart != current) {
833 			memcpy(ZSTR_VAL(newtext) + newtextlen, ZSTR_VAL(text) + laststart, current - laststart);
834 			newtextlen += current - laststart;
835 		}
836 
837 		ZSTR_VAL(newtext)[newtextlen] = '\0';
838 		/* free unused memory */
839 		newtext = zend_string_truncate(newtext, newtextlen, 0);
840 
841 		RETURN_NEW_STR(newtext);
842 	}
843 }
844 /* }}} */
845 
846 /* {{{ php_explode */
php_explode(const zend_string * delim,zend_string * str,zval * return_value,zend_long limit)847 PHPAPI void php_explode(const zend_string *delim, zend_string *str, zval *return_value, zend_long limit)
848 {
849 	const char *p1 = ZSTR_VAL(str);
850 	const char *endp = ZSTR_VAL(str) + ZSTR_LEN(str);
851 	const char *p2 = php_memnstr(ZSTR_VAL(str), ZSTR_VAL(delim), ZSTR_LEN(delim), endp);
852 	zval  tmp;
853 
854 	if (p2 == NULL) {
855 		ZVAL_STR_COPY(&tmp, str);
856 		zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &tmp);
857 	} else {
858 		zend_hash_real_init_packed(Z_ARRVAL_P(return_value));
859 		ZEND_HASH_FILL_PACKED(Z_ARRVAL_P(return_value)) {
860 			do {
861 				ZEND_HASH_FILL_GROW();
862 				ZEND_HASH_FILL_SET_STR(zend_string_init_fast(p1, p2 - p1));
863 				ZEND_HASH_FILL_NEXT();
864 				p1 = p2 + ZSTR_LEN(delim);
865 				p2 = php_memnstr(p1, ZSTR_VAL(delim), ZSTR_LEN(delim), endp);
866 			} while (p2 != NULL && --limit > 1);
867 
868 			if (p1 <= endp) {
869 				ZEND_HASH_FILL_GROW();
870 				ZEND_HASH_FILL_SET_STR(zend_string_init_fast(p1, endp - p1));
871 				ZEND_HASH_FILL_NEXT();
872 			}
873 		} ZEND_HASH_FILL_END();
874 	}
875 }
876 /* }}} */
877 
878 /* {{{ php_explode_negative_limit */
php_explode_negative_limit(const zend_string * delim,zend_string * str,zval * return_value,zend_long limit)879 PHPAPI void php_explode_negative_limit(const zend_string *delim, zend_string *str, zval *return_value, zend_long limit)
880 {
881 #define EXPLODE_ALLOC_STEP 64
882 	const char *p1 = ZSTR_VAL(str);
883 	const char *endp = ZSTR_VAL(str) + ZSTR_LEN(str);
884 	const char *p2 = php_memnstr(ZSTR_VAL(str), ZSTR_VAL(delim), ZSTR_LEN(delim), endp);
885 	zval  tmp;
886 
887 	if (p2 == NULL) {
888 		/*
889 		do nothing since limit <= -1, thus if only one chunk - 1 + (limit) <= 0
890 		by doing nothing we return empty array
891 		*/
892 	} else {
893 		size_t allocated = EXPLODE_ALLOC_STEP, found = 0;
894 		zend_long i, to_return;
895 		const char **positions = emalloc(allocated * sizeof(char *));
896 
897 		positions[found++] = p1;
898 		do {
899 			if (found >= allocated) {
900 				allocated = found + EXPLODE_ALLOC_STEP;/* make sure we have enough memory */
901 				positions = erealloc(ZEND_VOIDP(positions), allocated*sizeof(char *));
902 			}
903 			positions[found++] = p1 = p2 + ZSTR_LEN(delim);
904 			p2 = php_memnstr(p1, ZSTR_VAL(delim), ZSTR_LEN(delim), endp);
905 		} while (p2 != NULL);
906 
907 		to_return = limit + found;
908 		/* limit is at least -1 therefore no need of bounds checking : i will be always less than found */
909 		for (i = 0; i < to_return; i++) { /* this checks also for to_return > 0 */
910 			ZVAL_STRINGL(&tmp, positions[i], (positions[i+1] - ZSTR_LEN(delim)) - positions[i]);
911 			zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &tmp);
912 		}
913 		efree((void *)positions);
914 	}
915 #undef EXPLODE_ALLOC_STEP
916 }
917 /* }}} */
918 
919 /* {{{ Splits a string on string separator and return array of components. If limit is positive only limit number of components is returned. If limit is negative all components except the last abs(limit) are returned. */
PHP_FUNCTION(explode)920 PHP_FUNCTION(explode)
921 {
922 	zend_string *str, *delim;
923 	zend_long limit = ZEND_LONG_MAX; /* No limit */
924 	zval tmp;
925 
926 	ZEND_PARSE_PARAMETERS_START(2, 3)
927 		Z_PARAM_STR(delim)
928 		Z_PARAM_STR(str)
929 		Z_PARAM_OPTIONAL
930 		Z_PARAM_LONG(limit)
931 	ZEND_PARSE_PARAMETERS_END();
932 
933 	if (ZSTR_LEN(delim) == 0) {
934 		zend_argument_value_error(1, "cannot be empty");
935 		RETURN_THROWS();
936 	}
937 
938 	array_init(return_value);
939 
940 	if (ZSTR_LEN(str) == 0) {
941 		if (limit >= 0) {
942 			ZVAL_EMPTY_STRING(&tmp);
943 			zend_hash_index_add_new(Z_ARRVAL_P(return_value), 0, &tmp);
944 		}
945 		return;
946 	}
947 
948 	if (limit > 1) {
949 		php_explode(delim, str, return_value, limit);
950 	} else if (limit < 0) {
951 		php_explode_negative_limit(delim, str, return_value, limit);
952 	} else {
953 		ZVAL_STR_COPY(&tmp, str);
954 		zend_hash_index_add_new(Z_ARRVAL_P(return_value), 0, &tmp);
955 	}
956 }
957 /* }}} */
958 
959 /* {{{ php_implode */
php_implode(const zend_string * glue,HashTable * pieces,zval * return_value)960 PHPAPI void php_implode(const zend_string *glue, HashTable *pieces, zval *return_value)
961 {
962 	zval         *tmp;
963 	uint32_t      numelems;
964 	zend_string  *str;
965 	char         *cptr;
966 	size_t        len = 0;
967 	struct {
968 		zend_string *str;
969 		zend_long    lval;
970 	} *strings, *ptr;
971 	ALLOCA_FLAG(use_heap)
972 
973 	numelems = zend_hash_num_elements(pieces);
974 
975 	if (numelems == 0) {
976 		RETURN_EMPTY_STRING();
977 	} else if (numelems == 1) {
978 		/* loop to search the first not undefined element... */
979 		ZEND_HASH_FOREACH_VAL(pieces, tmp) {
980 			RETURN_STR(zval_get_string(tmp));
981 		} ZEND_HASH_FOREACH_END();
982 	}
983 
984 	ptr = strings = do_alloca((sizeof(*strings)) * numelems, use_heap);
985 
986 	uint32_t flags = ZSTR_GET_COPYABLE_CONCAT_PROPERTIES(glue);
987 
988 	ZEND_HASH_FOREACH_VAL(pieces, tmp) {
989 		if (EXPECTED(Z_TYPE_P(tmp) == IS_STRING)) {
990 			ptr->str = Z_STR_P(tmp);
991 			len += ZSTR_LEN(ptr->str);
992 			ptr->lval = 0;
993 			flags &= ZSTR_GET_COPYABLE_CONCAT_PROPERTIES(ptr->str);
994 			ptr++;
995 		} else if (UNEXPECTED(Z_TYPE_P(tmp) == IS_LONG)) {
996 			zend_long val = Z_LVAL_P(tmp);
997 
998 			ptr->str = NULL;
999 			ptr->lval = val;
1000 			ptr++;
1001 			if (val <= 0) {
1002 				len++;
1003 			}
1004 			while (val) {
1005 				val /= 10;
1006 				len++;
1007 			}
1008 		} else {
1009 			ptr->str = zval_get_string_func(tmp);
1010 			len += ZSTR_LEN(ptr->str);
1011 			ptr->lval = 1;
1012 			flags &= ZSTR_GET_COPYABLE_CONCAT_PROPERTIES(ptr->str);
1013 			ptr++;
1014 		}
1015 	} ZEND_HASH_FOREACH_END();
1016 
1017 	/* numelems cannot be 0, we checked above */
1018 	str = zend_string_safe_alloc(numelems - 1, ZSTR_LEN(glue), len, 0);
1019 	GC_ADD_FLAGS(str, flags);
1020 	cptr = ZSTR_VAL(str) + ZSTR_LEN(str);
1021 	*cptr = 0;
1022 
1023 	while (1) {
1024 		ptr--;
1025 		if (EXPECTED(ptr->str)) {
1026 			cptr -= ZSTR_LEN(ptr->str);
1027 			memcpy(cptr, ZSTR_VAL(ptr->str), ZSTR_LEN(ptr->str));
1028 			if (ptr->lval) {
1029 				zend_string_release_ex(ptr->str, 0);
1030 			}
1031 		} else {
1032 			char *oldPtr = cptr;
1033 			char oldVal = *cptr;
1034 			cptr = zend_print_long_to_buf(cptr, ptr->lval);
1035 			*oldPtr = oldVal;
1036 		}
1037 
1038 		if (ptr == strings) {
1039 			break;
1040 		}
1041 
1042 		cptr -= ZSTR_LEN(glue);
1043 		memcpy(cptr, ZSTR_VAL(glue), ZSTR_LEN(glue));
1044 	}
1045 
1046 	free_alloca(strings, use_heap);
1047 	RETURN_NEW_STR(str);
1048 }
1049 /* }}} */
1050 
1051 /* {{{ Joins array elements placing glue string between items and return one string */
PHP_FUNCTION(implode)1052 PHP_FUNCTION(implode)
1053 {
1054 	zend_string *arg1_str = NULL;
1055 	HashTable *arg1_array = NULL;
1056 	zend_array *pieces = NULL;
1057 
1058 	ZEND_PARSE_PARAMETERS_START(1, 2)
1059 		Z_PARAM_ARRAY_HT_OR_STR(arg1_array, arg1_str)
1060 		Z_PARAM_OPTIONAL
1061 		Z_PARAM_ARRAY_HT_OR_NULL(pieces)
1062 	ZEND_PARSE_PARAMETERS_END();
1063 
1064 	if (pieces == NULL) {
1065 		if (arg1_array == NULL) {
1066 			zend_type_error(
1067 				"%s(): If argument #1 ($separator) is of type string, "
1068 				"argument #2 ($array) must be of type array, null given",
1069 				get_active_function_name()
1070 			);
1071 			RETURN_THROWS();
1072 		}
1073 
1074 		arg1_str = ZSTR_EMPTY_ALLOC();
1075 		pieces = arg1_array;
1076 	} else {
1077 		if (arg1_str == NULL) {
1078 			zend_argument_type_error(1, "must be of type string, array given");
1079 			RETURN_THROWS();
1080 		}
1081 	}
1082 
1083 	php_implode(arg1_str, pieces, return_value);
1084 }
1085 /* }}} */
1086 
1087 ZEND_FRAMELESS_FUNCTION(implode, 1)
1088 {
1089 	zval *pieces;
1090 
1091 	/* Manual parsing for more accurate error message. */
1092 	if (!zend_parse_arg_array(arg1, &pieces, /* null_check */ false, /* or_object */ false)) { \
1093 		zend_type_error(
1094 			"%s(): If argument #1 ($separator) is of type string, "
1095 			"argument #2 ($array) must be of type array, null given",
1096 			get_active_function_name()
1097 		);
1098 		goto flf_clean; \
1099 	}
1100 
1101 	zend_string *str = ZSTR_EMPTY_ALLOC();
1102 
1103 	php_implode(str, Z_ARR_P(pieces), return_value);
1104 
1105 flf_clean:;
1106 }
1107 
1108 ZEND_FRAMELESS_FUNCTION(implode, 2)
1109 {
1110 	zval str_tmp;
1111 	zend_string *str;
1112 	zval *pieces;
1113 
1114 	Z_FLF_PARAM_STR(1, str, str_tmp);
1115 	Z_FLF_PARAM_ARRAY_OR_NULL(2, pieces);
1116 
1117 	if (!pieces) {
1118 		zend_type_error(
1119 			"%s(): If argument #1 ($separator) is of type string, "
1120 			"argument #2 ($array) must be of type array, null given",
1121 			get_active_function_name()
1122 		);
1123 		goto flf_clean;
1124 	}
1125 
1126 	php_implode(str, Z_ARR_P(pieces), return_value);
1127 
1128 flf_clean:;
1129 	Z_FLF_PARAM_FREE_STR(1, str_tmp);
1130 }
1131 
1132 #define STRTOK_TABLE(p) BG(strtok_table)[(unsigned char) *p]
1133 
1134 /* {{{ Tokenize a string */
PHP_FUNCTION(strtok)1135 PHP_FUNCTION(strtok)
1136 {
1137 	zend_string *str, *tok = NULL;
1138 	char *token;
1139 	char *token_end;
1140 	char *p;
1141 	char *pe;
1142 	size_t skipped = 0;
1143 
1144 	ZEND_PARSE_PARAMETERS_START(1, 2)
1145 		Z_PARAM_STR(str)
1146 		Z_PARAM_OPTIONAL
1147 		Z_PARAM_STR_OR_NULL(tok)
1148 	ZEND_PARSE_PARAMETERS_END();
1149 
1150 	if (!tok) {
1151 		tok = str;
1152 	} else {
1153 		if (BG(strtok_string)) {
1154 			zend_string_release(BG(strtok_string));
1155 		}
1156 		BG(strtok_string) = zend_string_copy(str);
1157 		BG(strtok_last) = ZSTR_VAL(str);
1158 		BG(strtok_len) = ZSTR_LEN(str);
1159 	}
1160 
1161 	if (!BG(strtok_string)) {
1162 		/* String to tokenize not set. */
1163 		php_error_docref(NULL, E_WARNING, "Both arguments must be provided when starting tokenization");
1164 		RETURN_FALSE;
1165 	}
1166 
1167 	p = BG(strtok_last); /* Where we start to search */
1168 	pe = ZSTR_VAL(BG(strtok_string)) + BG(strtok_len);
1169 	if (p >= pe) {
1170 		/* Reached the end of the string. */
1171 		RETURN_FALSE;
1172 	}
1173 
1174 	token = ZSTR_VAL(tok);
1175 	token_end = token + ZSTR_LEN(tok);
1176 
1177 	while (token < token_end) {
1178 		STRTOK_TABLE(token++) = 1;
1179 	}
1180 
1181 	/* Skip leading delimiters */
1182 	while (STRTOK_TABLE(p)) {
1183 		if (++p >= pe) {
1184 			/* no other chars left */
1185 			goto return_false;
1186 		}
1187 		skipped++;
1188 	}
1189 
1190 	/* We know at this place that *p is no delimiter, so skip it */
1191 	while (++p < pe) {
1192 		if (STRTOK_TABLE(p)) {
1193 			goto return_token;
1194 		}
1195 	}
1196 
1197 	if (p - BG(strtok_last)) {
1198 return_token:
1199 		RETVAL_STRINGL(BG(strtok_last) + skipped, (p - BG(strtok_last)) - skipped);
1200 		BG(strtok_last) = p + 1;
1201 	} else {
1202 return_false:
1203 		RETVAL_FALSE;
1204 		zend_string_release(BG(strtok_string));
1205 		BG(strtok_string) = NULL;
1206 	}
1207 
1208 	/* Restore table -- usually faster then memset'ing the table on every invocation */
1209 	token = ZSTR_VAL(tok);
1210 	while (token < token_end) {
1211 		STRTOK_TABLE(token++) = 0;
1212 	}
1213 }
1214 /* }}} */
1215 
1216 /* {{{ php_strtoupper */
php_strtoupper(char * s,size_t len)1217 PHPAPI char *php_strtoupper(char *s, size_t len)
1218 {
1219 	zend_str_toupper(s, len);
1220 	return s;
1221 }
1222 /* }}} */
1223 
1224 /* {{{ php_string_toupper */
php_string_toupper(zend_string * s)1225 PHPAPI zend_string *php_string_toupper(zend_string *s)
1226 {
1227 	return zend_string_toupper(s);
1228 }
1229 /* }}} */
1230 
1231 /* {{{ Makes a string uppercase */
PHP_FUNCTION(strtoupper)1232 PHP_FUNCTION(strtoupper)
1233 {
1234 	zend_string *arg;
1235 
1236 	ZEND_PARSE_PARAMETERS_START(1, 1)
1237 		Z_PARAM_STR(arg)
1238 	ZEND_PARSE_PARAMETERS_END();
1239 
1240 	RETURN_STR(zend_string_toupper(arg));
1241 }
1242 /* }}} */
1243 
1244 /* {{{ php_strtolower */
php_strtolower(char * s,size_t len)1245 PHPAPI char *php_strtolower(char *s, size_t len)
1246 {
1247 	zend_str_tolower(s, len);
1248 	return s;
1249 }
1250 /* }}} */
1251 
1252 /* {{{ php_string_tolower */
php_string_tolower(zend_string * s)1253 PHPAPI zend_string *php_string_tolower(zend_string *s)
1254 {
1255 	return zend_string_tolower(s);
1256 }
1257 /* }}} */
1258 
1259 /* {{{ Makes a string lowercase */
PHP_FUNCTION(strtolower)1260 PHP_FUNCTION(strtolower)
1261 {
1262 	zend_string *str;
1263 
1264 	ZEND_PARSE_PARAMETERS_START(1, 1)
1265 		Z_PARAM_STR(str)
1266 	ZEND_PARSE_PARAMETERS_END();
1267 
1268 	RETURN_STR(zend_string_tolower(str));
1269 }
1270 /* }}} */
1271 
PHP_FUNCTION(str_increment)1272 PHP_FUNCTION(str_increment)
1273 {
1274 	zend_string *str;
1275 
1276 	ZEND_PARSE_PARAMETERS_START(1, 1)
1277 		Z_PARAM_STR(str)
1278 	ZEND_PARSE_PARAMETERS_END();
1279 
1280 	if (ZSTR_LEN(str) == 0) {
1281 		zend_argument_value_error(1, "cannot be empty");
1282 		RETURN_THROWS();
1283 	}
1284 	if (!zend_string_only_has_ascii_alphanumeric(str)) {
1285 		zend_argument_value_error(1, "must be composed only of alphanumeric ASCII characters");
1286 		RETURN_THROWS();
1287 	}
1288 
1289 	zend_string *incremented = zend_string_init(ZSTR_VAL(str), ZSTR_LEN(str), /* persistent */ false);
1290 	size_t position = ZSTR_LEN(str)-1;
1291 	bool carry = false;
1292 
1293 	do {
1294 		char c = ZSTR_VAL(incremented)[position];
1295 		/* We know c is in ['a', 'z'], ['A', 'Z'], or ['0', '9'] range from zend_string_only_has_ascii_alphanumeric() */
1296 		if (EXPECTED( c != 'z' && c != 'Z' && c != '9' )) {
1297 			carry = false;
1298 			ZSTR_VAL(incremented)[position]++;
1299 		} else { /* if 'z', 'Z', or '9' */
1300 			carry = true;
1301 			if (c == '9') {
1302 				ZSTR_VAL(incremented)[position] = '0';
1303 			} else {
1304 				ZSTR_VAL(incremented)[position] -= 25;
1305 			}
1306 		}
1307 	} while (carry && position-- > 0);
1308 
1309 	if (UNEXPECTED(carry)) {
1310 		zend_string *tmp = zend_string_alloc(ZSTR_LEN(incremented)+1, 0);
1311 		memcpy(ZSTR_VAL(tmp) + 1, ZSTR_VAL(incremented), ZSTR_LEN(incremented));
1312 		ZSTR_VAL(tmp)[ZSTR_LEN(incremented)+1] = '\0';
1313 		switch (ZSTR_VAL(incremented)[0]) {
1314 			case '0':
1315 				ZSTR_VAL(tmp)[0] = '1';
1316 				break;
1317 			default:
1318 				ZSTR_VAL(tmp)[0] = ZSTR_VAL(incremented)[0];
1319 				break;
1320 		}
1321 		zend_string_release_ex(incremented, /* persistent */ false);
1322 		RETURN_STR(tmp);
1323 	}
1324 	RETURN_STR(incremented);
1325 }
1326 
1327 
PHP_FUNCTION(str_decrement)1328 PHP_FUNCTION(str_decrement)
1329 {
1330 	zend_string *str;
1331 
1332 	ZEND_PARSE_PARAMETERS_START(1, 1)
1333 		Z_PARAM_STR(str)
1334 	ZEND_PARSE_PARAMETERS_END();
1335 
1336 	if (ZSTR_LEN(str) == 0) {
1337 		zend_argument_value_error(1, "cannot be empty");
1338 		RETURN_THROWS();
1339 	}
1340 	if (!zend_string_only_has_ascii_alphanumeric(str)) {
1341 		zend_argument_value_error(1, "must be composed only of alphanumeric ASCII characters");
1342 		RETURN_THROWS();
1343 	}
1344 	if (ZSTR_LEN(str) >= 1 && ZSTR_VAL(str)[0] == '0') {
1345 		zend_argument_value_error(1, "\"%s\" is out of decrement range", ZSTR_VAL(str));
1346 		RETURN_THROWS();
1347 	}
1348 
1349 	zend_string *decremented = zend_string_init(ZSTR_VAL(str), ZSTR_LEN(str), /* persistent */ false);
1350 	size_t position = ZSTR_LEN(str)-1;
1351 	bool carry = false;
1352 
1353 	do {
1354 		char c = ZSTR_VAL(decremented)[position];
1355 		/* We know c is in ['a', 'z'], ['A', 'Z'], or ['0', '9'] range from zend_string_only_has_ascii_alphanumeric() */
1356 		if (EXPECTED( c != 'a' && c != 'A' && c != '0' )) {
1357 			carry = false;
1358 			ZSTR_VAL(decremented)[position]--;
1359 		} else { /* if 'a', 'A', or '0' */
1360 			carry = true;
1361 			if (c == '0') {
1362 				ZSTR_VAL(decremented)[position] = '9';
1363 			} else {
1364 				ZSTR_VAL(decremented)[position] += 25;
1365 			}
1366 		}
1367 	} while (carry && position-- > 0);
1368 
1369 	if (UNEXPECTED(carry || (ZSTR_VAL(decremented)[0] == '0' && ZSTR_LEN(decremented) > 1))) {
1370 		if (ZSTR_LEN(decremented) == 1) {
1371 			zend_string_release_ex(decremented, /* persistent */ false);
1372 			zend_argument_value_error(1, "\"%s\" is out of decrement range", ZSTR_VAL(str));
1373 			RETURN_THROWS();
1374 		}
1375 		zend_string *tmp = zend_string_alloc(ZSTR_LEN(decremented) - 1, 0);
1376 		memcpy(ZSTR_VAL(tmp), ZSTR_VAL(decremented) + 1, ZSTR_LEN(decremented) - 1);
1377 		ZSTR_VAL(tmp)[ZSTR_LEN(decremented) - 1] = '\0';
1378 		zend_string_release_ex(decremented, /* persistent */ false);
1379 		RETURN_STR(tmp);
1380 	}
1381 	RETURN_STR(decremented);
1382 }
1383 
1384 #if defined(PHP_WIN32)
_is_basename_start(const char * start,const char * pos)1385 static bool _is_basename_start(const char *start, const char *pos)
1386 {
1387 	if (pos - start >= 1
1388 	    && *(pos-1) != '/'
1389 	    && *(pos-1) != '\\') {
1390 		if (pos - start == 1) {
1391 			return 1;
1392 		} else if (*(pos-2) == '/' || *(pos-2) == '\\') {
1393 			return 1;
1394 		} else if (*(pos-2) == ':'
1395 			&& _is_basename_start(start, pos - 2)) {
1396 			return 1;
1397 		}
1398 	}
1399 	return 0;
1400 }
1401 #endif
1402 
1403 /* {{{ php_basename */
php_basename(const char * s,size_t len,const char * suffix,size_t suffix_len)1404 PHPAPI zend_string *php_basename(const char *s, size_t len, const char *suffix, size_t suffix_len)
1405 {
1406 	const char *basename_start;
1407 	const char *basename_end;
1408 
1409 	if (CG(ascii_compatible_locale)) {
1410 		basename_end = s + len - 1;
1411 
1412 		/* Strip trailing slashes */
1413 		while (basename_end >= s
1414 #ifdef PHP_WIN32
1415 			&& (*basename_end == '/'
1416 				|| *basename_end == '\\'
1417 				|| (*basename_end == ':'
1418 					&& _is_basename_start(s, basename_end)))) {
1419 #else
1420 			&& *basename_end == '/') {
1421 #endif
1422 			basename_end--;
1423 		}
1424 		if (basename_end < s) {
1425 			return ZSTR_EMPTY_ALLOC();
1426 		}
1427 
1428 		/* Extract filename */
1429 		basename_start = basename_end;
1430 		basename_end++;
1431 		while (basename_start > s
1432 #ifdef PHP_WIN32
1433 			&& *(basename_start-1) != '/'
1434 			&& *(basename_start-1) != '\\') {
1435 
1436 			if (*(basename_start-1) == ':' &&
1437 				_is_basename_start(s, basename_start - 1)) {
1438 				break;
1439 			}
1440 #else
1441 			&& *(basename_start-1) != '/') {
1442 #endif
1443 			basename_start--;
1444 		}
1445 	} else {
1446 		/* State 0 is directly after a directory separator (or at the start of the string).
1447 		 * State 1 is everything else. */
1448 		int state = 0;
1449 
1450 		basename_start = s;
1451 		basename_end = s;
1452 		while (len > 0) {
1453 			int inc_len = (*s == '\0' ? 1 : php_mblen(s, len));
1454 
1455 			switch (inc_len) {
1456 				case 0:
1457 					goto quit_loop;
1458 				case 1:
1459 #ifdef PHP_WIN32
1460 					if (*s == '/' || *s == '\\') {
1461 #else
1462 					if (*s == '/') {
1463 #endif
1464 						if (state == 1) {
1465 							state = 0;
1466 							basename_end = s;
1467 						}
1468 #ifdef PHP_WIN32
1469 					/* Catch relative paths in c:file.txt style. They're not to confuse
1470 					   with the NTFS streams. This part ensures also, that no drive
1471 					   letter traversing happens. */
1472 					} else if ((*s == ':' && (s - basename_start == 1))) {
1473 						if (state == 0) {
1474 							basename_start = s;
1475 							state = 1;
1476 						} else {
1477 							basename_end = s;
1478 							state = 0;
1479 						}
1480 #endif
1481 					} else {
1482 						if (state == 0) {
1483 							basename_start = s;
1484 							state = 1;
1485 						}
1486 					}
1487 					break;
1488 				default:
1489 					if (inc_len < 0) {
1490 						/* If character is invalid, treat it like other non-significant characters. */
1491 						inc_len = 1;
1492 						php_mb_reset();
1493 					}
1494 					if (state == 0) {
1495 						basename_start = s;
1496 						state = 1;
1497 					}
1498 					break;
1499 			}
1500 			s += inc_len;
1501 			len -= inc_len;
1502 		}
1503 
1504 quit_loop:
1505 		if (state == 1) {
1506 			basename_end = s;
1507 		}
1508 	}
1509 
1510 	if (suffix != NULL && suffix_len < (size_t)(basename_end - basename_start) &&
1511 			memcmp(basename_end - suffix_len, suffix, suffix_len) == 0) {
1512 		basename_end -= suffix_len;
1513 	}
1514 
1515 	return zend_string_init(basename_start, basename_end - basename_start, 0);
1516 }
1517 /* }}} */
1518 
1519 /* {{{ Returns the filename component of the path */
1520 PHP_FUNCTION(basename)
1521 {
1522 	char *string, *suffix = NULL;
1523 	size_t   string_len, suffix_len = 0;
1524 
1525 	ZEND_PARSE_PARAMETERS_START(1, 2)
1526 		Z_PARAM_STRING(string, string_len)
1527 		Z_PARAM_OPTIONAL
1528 		Z_PARAM_STRING(suffix, suffix_len)
1529 	ZEND_PARSE_PARAMETERS_END();
1530 
1531 	RETURN_STR(php_basename(string, string_len, suffix, suffix_len));
1532 }
1533 /* }}} */
1534 
1535 /* {{{ php_dirname
1536    Returns directory name component of path */
1537 PHPAPI size_t php_dirname(char *path, size_t len)
1538 {
1539 	return zend_dirname(path, len);
1540 }
1541 /* }}} */
1542 
1543 static inline void _zend_dirname(zval *return_value, zend_string *str, zend_long levels)
1544 {
1545 	zend_string *ret;
1546 
1547 	ret = zend_string_init(ZSTR_VAL(str), ZSTR_LEN(str), 0);
1548 
1549 	if (levels == 1) {
1550 		/* Default case */
1551 #ifdef PHP_WIN32
1552 		ZSTR_LEN(ret) = php_win32_ioutil_dirname(ZSTR_VAL(ret), ZSTR_LEN(str));
1553 #else
1554 		ZSTR_LEN(ret) = zend_dirname(ZSTR_VAL(ret), ZSTR_LEN(str));
1555 #endif
1556 	} else if (levels < 1) {
1557 		zend_argument_value_error(2, "must be greater than or equal to 1");
1558 		zend_string_efree(ret);
1559 		RETURN_THROWS();
1560 	} else {
1561 		/* Some levels up */
1562 		size_t str_len;
1563 		do {
1564 #ifdef PHP_WIN32
1565 			ZSTR_LEN(ret) = php_win32_ioutil_dirname(ZSTR_VAL(ret), str_len = ZSTR_LEN(ret));
1566 #else
1567 			ZSTR_LEN(ret) = zend_dirname(ZSTR_VAL(ret), str_len = ZSTR_LEN(ret));
1568 #endif
1569 		} while (ZSTR_LEN(ret) < str_len && --levels);
1570 	}
1571 
1572 	RETURN_NEW_STR(ret);
1573 }
1574 
1575 /* {{{ Returns the directory name component of the path */
1576 PHP_FUNCTION(dirname)
1577 {
1578 	zend_string *str;
1579 	zend_long levels = 1;
1580 
1581 	ZEND_PARSE_PARAMETERS_START(1, 2)
1582 		Z_PARAM_STR(str)
1583 		Z_PARAM_OPTIONAL
1584 		Z_PARAM_LONG(levels)
1585 	ZEND_PARSE_PARAMETERS_END();
1586 
1587 	_zend_dirname(return_value, str, levels);
1588 }
1589 /* }}} */
1590 
1591 ZEND_FRAMELESS_FUNCTION(dirname, 1)
1592 {
1593 	zval str_tmp;
1594 	zend_string *str;
1595 
1596 	Z_FLF_PARAM_STR(1, str, str_tmp);
1597 
1598 	_zend_dirname(return_value, str, 1);
1599 
1600 flf_clean:
1601 	Z_FLF_PARAM_FREE_STR(1, str_tmp);
1602 }
1603 
1604 ZEND_FRAMELESS_FUNCTION(dirname, 2)
1605 {
1606 	zval str_tmp;
1607 	zend_string *str;
1608 	zend_long levels;
1609 
1610 	Z_FLF_PARAM_STR(1, str, str_tmp);
1611 	Z_FLF_PARAM_LONG(2, levels);
1612 
1613 	_zend_dirname(return_value, str, levels);
1614 
1615 flf_clean:
1616 	Z_FLF_PARAM_FREE_STR(1, str_tmp);
1617 }
1618 
1619 /* {{{ Returns information about a certain string */
1620 PHP_FUNCTION(pathinfo)
1621 {
1622 	zval tmp;
1623 	char *path, *dirname;
1624 	size_t path_len;
1625 	bool have_basename;
1626 	zend_long opt = PHP_PATHINFO_ALL;
1627 	zend_string *ret = NULL;
1628 
1629 	ZEND_PARSE_PARAMETERS_START(1, 2)
1630 		Z_PARAM_STRING(path, path_len)
1631 		Z_PARAM_OPTIONAL
1632 		Z_PARAM_LONG(opt)
1633 	ZEND_PARSE_PARAMETERS_END();
1634 
1635 	have_basename = ((opt & PHP_PATHINFO_BASENAME) == PHP_PATHINFO_BASENAME);
1636 
1637 	array_init(&tmp);
1638 
1639 	if ((opt & PHP_PATHINFO_DIRNAME) == PHP_PATHINFO_DIRNAME) {
1640 		dirname = estrndup(path, path_len);
1641 		php_dirname(dirname, path_len);
1642 		if (*dirname) {
1643 			add_assoc_string(&tmp, "dirname", dirname);
1644 		}
1645 		efree(dirname);
1646 	}
1647 
1648 	if (have_basename) {
1649 		ret = php_basename(path, path_len, NULL, 0);
1650 		add_assoc_str(&tmp, "basename", zend_string_copy(ret));
1651 	}
1652 
1653 	if ((opt & PHP_PATHINFO_EXTENSION) == PHP_PATHINFO_EXTENSION) {
1654 		const char *p;
1655 		ptrdiff_t idx;
1656 
1657 		if (!have_basename) {
1658 			ret = php_basename(path, path_len, NULL, 0);
1659 		}
1660 
1661 		p = zend_memrchr(ZSTR_VAL(ret), '.', ZSTR_LEN(ret));
1662 
1663 		if (p) {
1664 			idx = p - ZSTR_VAL(ret);
1665 			add_assoc_stringl(&tmp, "extension", ZSTR_VAL(ret) + idx + 1, ZSTR_LEN(ret) - idx - 1);
1666 		}
1667 	}
1668 
1669 	if ((opt & PHP_PATHINFO_FILENAME) == PHP_PATHINFO_FILENAME) {
1670 		const char *p;
1671 		ptrdiff_t idx;
1672 
1673 		/* Have we already looked up the basename? */
1674 		if (!have_basename && !ret) {
1675 			ret = php_basename(path, path_len, NULL, 0);
1676 		}
1677 
1678 		p = zend_memrchr(ZSTR_VAL(ret), '.', ZSTR_LEN(ret));
1679 
1680 		idx = p ? (p - ZSTR_VAL(ret)) : (ptrdiff_t)ZSTR_LEN(ret);
1681 		add_assoc_stringl(&tmp, "filename", ZSTR_VAL(ret), idx);
1682 	}
1683 
1684 	if (ret) {
1685 		zend_string_release_ex(ret, 0);
1686 	}
1687 
1688 	if (opt == PHP_PATHINFO_ALL) {
1689 		RETURN_COPY_VALUE(&tmp);
1690 	} else {
1691 		zval *element;
1692 		if ((element = zend_hash_get_current_data(Z_ARRVAL(tmp))) != NULL) {
1693 			RETVAL_COPY_DEREF(element);
1694 		} else {
1695 			RETVAL_EMPTY_STRING();
1696 		}
1697 		zval_ptr_dtor(&tmp);
1698 	}
1699 }
1700 /* }}} */
1701 
1702 /* {{{ php_stristr
1703    case insensitive strstr */
1704 PHPAPI char *php_stristr(char *s, char *t, size_t s_len, size_t t_len)
1705 {
1706 	return (char*)php_memnistr(s, t, t_len, s + s_len);
1707 }
1708 /* }}} */
1709 
1710 static size_t php_strspn_strcspn_common(const char *haystack, const char *characters, const char *haystack_end, const char *characters_end, bool must_match)
1711 {
1712 	/* Fast path for short strings.
1713 	 * The table lookup cannot be faster in this case because we not only have to compare, but also build the table.
1714 	 * We only compare in this case.
1715 	 * Empirically tested that the table lookup approach is only beneficial if characters is longer than 1 character. */
1716 	if (characters_end - characters == 1) {
1717 		const char *ptr = haystack;
1718 		while (ptr < haystack_end && (*ptr == *characters) == must_match) {
1719 			ptr++;
1720 		}
1721 		return ptr - haystack;
1722 	}
1723 
1724 	/* Every character in characters will set a boolean in this lookup table.
1725 	 * We'll use the lookup table as a fast lookup for the characters in characters while looping over haystack. */
1726 	bool table[256];
1727 	/* Use multiple small memsets to inline the memset with intrinsics, trick learned from glibc. */
1728 	memset(table, 0, 64);
1729 	memset(table + 64, 0, 64);
1730 	memset(table + 128, 0, 64);
1731 	memset(table + 192, 0, 64);
1732 
1733 	while (characters < characters_end) {
1734 		table[(unsigned char) *characters] = true;
1735 		characters++;
1736 	}
1737 
1738 	const char *ptr = haystack;
1739 	while (ptr < haystack_end && table[(unsigned char) *ptr] == must_match) {
1740 		ptr++;
1741 	}
1742 
1743 	return ptr - haystack;
1744 }
1745 
1746 /* {{{ php_strspn */
1747 PHPAPI size_t php_strspn(const char *haystack, const char *characters, const char *haystack_end, const char *characters_end)
1748 {
1749 	return php_strspn_strcspn_common(haystack, characters, haystack_end, characters_end, true);
1750 }
1751 /* }}} */
1752 
1753 /* {{{ php_strcspn */
1754 PHPAPI size_t php_strcspn(const char *haystack, const char *characters, const char *haystack_end, const char *characters_end)
1755 {
1756 	return php_strspn_strcspn_common(haystack, characters, haystack_end, characters_end, false);
1757 }
1758 /* }}} */
1759 
1760 /* {{{ Finds first occurrence of a string within another, case insensitive */
1761 PHP_FUNCTION(stristr)
1762 {
1763 	zend_string *haystack, *needle;
1764 	const char *found = NULL;
1765 	size_t  found_offset;
1766 	bool part = 0;
1767 
1768 	ZEND_PARSE_PARAMETERS_START(2, 3)
1769 		Z_PARAM_STR(haystack)
1770 		Z_PARAM_STR(needle)
1771 		Z_PARAM_OPTIONAL
1772 		Z_PARAM_BOOL(part)
1773 	ZEND_PARSE_PARAMETERS_END();
1774 
1775 	found = php_stristr(ZSTR_VAL(haystack), ZSTR_VAL(needle), ZSTR_LEN(haystack), ZSTR_LEN(needle));
1776 
1777 	if (UNEXPECTED(!found)) {
1778 		RETURN_FALSE;
1779 	}
1780 	found_offset = found - ZSTR_VAL(haystack);
1781 	if (part) {
1782 		RETURN_STRINGL(ZSTR_VAL(haystack), found_offset);
1783 	}
1784 	RETURN_STRINGL(found, ZSTR_LEN(haystack) - found_offset);
1785 }
1786 /* }}} */
1787 
1788 static inline void _zend_strstr(zval *return_value, zend_string *haystack, zend_string *needle, bool part)
1789 {
1790 	const char *found = NULL;
1791 	zend_long found_offset;
1792 
1793 	found = php_memnstr(ZSTR_VAL(haystack), ZSTR_VAL(needle), ZSTR_LEN(needle), ZSTR_VAL(haystack) + ZSTR_LEN(haystack));
1794 
1795 	if (UNEXPECTED(!found)) {
1796 		RETURN_FALSE;
1797 	}
1798 	found_offset = found - ZSTR_VAL(haystack);
1799 	if (part) {
1800 		RETURN_STRINGL(ZSTR_VAL(haystack), found_offset);
1801 	}
1802 	RETURN_STRINGL(found, ZSTR_LEN(haystack) - found_offset);
1803 }
1804 
1805 /* {{{ Finds first occurrence of a string within another */
1806 PHP_FUNCTION(strstr)
1807 {
1808 	zend_string *haystack, *needle;
1809 	bool part = 0;
1810 
1811 	ZEND_PARSE_PARAMETERS_START(2, 3)
1812 		Z_PARAM_STR(haystack)
1813 		Z_PARAM_STR(needle)
1814 		Z_PARAM_OPTIONAL
1815 		Z_PARAM_BOOL(part)
1816 	ZEND_PARSE_PARAMETERS_END();
1817 
1818 	_zend_strstr(return_value, haystack, needle, part);
1819 }
1820 /* }}} */
1821 
1822 ZEND_FRAMELESS_FUNCTION(strstr, 2)
1823 {
1824 	zval haystack_tmp, needle_tmp;
1825 	zend_string *haystack, *needle;
1826 
1827 	Z_FLF_PARAM_STR(1, haystack, haystack_tmp);
1828 	Z_FLF_PARAM_STR(2, needle, needle_tmp);
1829 
1830 	_zend_strstr(return_value, haystack, needle, /* part */ false);
1831 
1832 flf_clean:
1833 	Z_FLF_PARAM_FREE_STR(1, haystack_tmp);
1834 	Z_FLF_PARAM_FREE_STR(2, needle_tmp);
1835 }
1836 
1837 ZEND_FRAMELESS_FUNCTION(strstr, 3)
1838 {
1839 	zval haystack_tmp, needle_tmp;
1840 	zend_string *haystack, *needle;
1841 	bool part;
1842 
1843 	Z_FLF_PARAM_STR(1, haystack, haystack_tmp);
1844 	Z_FLF_PARAM_STR(2, needle, needle_tmp);
1845 	Z_FLF_PARAM_BOOL(3, part);
1846 
1847 	_zend_strstr(return_value, haystack, needle, part);
1848 
1849 flf_clean:
1850 	Z_FLF_PARAM_FREE_STR(1, haystack_tmp);
1851 	Z_FLF_PARAM_FREE_STR(2, needle_tmp);
1852 }
1853 
1854 /* {{{ Checks if a string contains another */
1855 PHP_FUNCTION(str_contains)
1856 {
1857 	zend_string *haystack, *needle;
1858 
1859 	ZEND_PARSE_PARAMETERS_START(2, 2)
1860 		Z_PARAM_STR(haystack)
1861 		Z_PARAM_STR(needle)
1862 	ZEND_PARSE_PARAMETERS_END();
1863 
1864 	RETURN_BOOL(php_memnstr(ZSTR_VAL(haystack), ZSTR_VAL(needle), ZSTR_LEN(needle), ZSTR_VAL(haystack) + ZSTR_LEN(haystack)));
1865 }
1866 /* }}} */
1867 
1868 ZEND_FRAMELESS_FUNCTION(str_contains, 2)
1869 {
1870 	zval haystack_tmp, needle_tmp;
1871 	zend_string *haystack, *needle;
1872 
1873 	Z_FLF_PARAM_STR(1, haystack, haystack_tmp);
1874 	Z_FLF_PARAM_STR(2, needle, needle_tmp);
1875 
1876 	RETVAL_BOOL(php_memnstr(ZSTR_VAL(haystack), ZSTR_VAL(needle), ZSTR_LEN(needle), ZSTR_VAL(haystack) + ZSTR_LEN(haystack)));
1877 
1878 flf_clean:
1879 	Z_FLF_PARAM_FREE_STR(1, haystack_tmp);
1880 	Z_FLF_PARAM_FREE_STR(2, needle_tmp);
1881 }
1882 
1883 /* {{{ Checks if haystack starts with needle */
1884 PHP_FUNCTION(str_starts_with)
1885 {
1886 	zend_string *haystack, *needle;
1887 
1888 	ZEND_PARSE_PARAMETERS_START(2, 2)
1889 		Z_PARAM_STR(haystack)
1890 		Z_PARAM_STR(needle)
1891 	ZEND_PARSE_PARAMETERS_END();
1892 
1893 	RETURN_BOOL(zend_string_starts_with(haystack, needle));
1894 }
1895 /* }}} */
1896 
1897 ZEND_FRAMELESS_FUNCTION(str_starts_with, 2)
1898 {
1899 	zval haystack_tmp, needle_tmp;
1900 	zend_string *haystack, *needle;
1901 
1902 	Z_FLF_PARAM_STR(1, haystack, haystack_tmp);
1903 	Z_FLF_PARAM_STR(2, needle, needle_tmp);
1904 
1905 	RETVAL_BOOL(zend_string_starts_with(haystack, needle));
1906 
1907 flf_clean:
1908 	Z_FLF_PARAM_FREE_STR(1, haystack_tmp);
1909 	Z_FLF_PARAM_FREE_STR(2, needle_tmp);
1910 }
1911 
1912 /* {{{ Checks if haystack ends with needle */
1913 PHP_FUNCTION(str_ends_with)
1914 {
1915 	zend_string *haystack, *needle;
1916 
1917 	ZEND_PARSE_PARAMETERS_START(2, 2)
1918 		Z_PARAM_STR(haystack)
1919 		Z_PARAM_STR(needle)
1920 	ZEND_PARSE_PARAMETERS_END();
1921 
1922 	if (ZSTR_LEN(needle) > ZSTR_LEN(haystack)) {
1923 		RETURN_FALSE;
1924 	}
1925 
1926 	RETURN_BOOL(memcmp(
1927 		ZSTR_VAL(haystack) + ZSTR_LEN(haystack) - ZSTR_LEN(needle),
1928 		ZSTR_VAL(needle), ZSTR_LEN(needle)) == 0);
1929 }
1930 /* }}} */
1931 
1932 static inline void _zend_strpos(zval *return_value, zend_string *haystack, zend_string *needle, zend_long offset)
1933 {
1934 	const char *found = NULL;
1935 
1936 	if (offset < 0) {
1937 		offset += (zend_long)ZSTR_LEN(haystack);
1938 	}
1939 	if (offset < 0 || (size_t)offset > ZSTR_LEN(haystack)) {
1940 		zend_argument_value_error(3, "must be contained in argument #1 ($haystack)");
1941 		RETURN_THROWS();
1942 	}
1943 
1944 	found = (char*)php_memnstr(ZSTR_VAL(haystack) + offset,
1945 						ZSTR_VAL(needle), ZSTR_LEN(needle),
1946 						ZSTR_VAL(haystack) + ZSTR_LEN(haystack));
1947 
1948 	if (UNEXPECTED(!found)) {
1949 		RETURN_FALSE;
1950 	}
1951 	RETURN_LONG(found - ZSTR_VAL(haystack));
1952 }
1953 
1954 /* {{{ Finds position of first occurrence of a string within another */
1955 PHP_FUNCTION(strpos)
1956 {
1957 	zend_string *haystack, *needle;
1958 	zend_long offset = 0;
1959 
1960 	ZEND_PARSE_PARAMETERS_START(2, 3)
1961 		Z_PARAM_STR(haystack)
1962 		Z_PARAM_STR(needle)
1963 		Z_PARAM_OPTIONAL
1964 		Z_PARAM_LONG(offset)
1965 	ZEND_PARSE_PARAMETERS_END();
1966 
1967 	_zend_strpos(return_value, haystack, needle, offset);
1968 }
1969 /* }}} */
1970 
1971 ZEND_FRAMELESS_FUNCTION(strpos, 2)
1972 {
1973 	zval haystack_tmp, needle_tmp;
1974 	zend_string *haystack, *needle;
1975 
1976 	Z_FLF_PARAM_STR(1, haystack, haystack_tmp);
1977 	Z_FLF_PARAM_STR(2, needle, needle_tmp);
1978 
1979 	_zend_strpos(return_value, haystack, needle, 0);
1980 
1981 flf_clean:
1982 	Z_FLF_PARAM_FREE_STR(1, haystack_tmp);
1983 	Z_FLF_PARAM_FREE_STR(2, needle_tmp);
1984 }
1985 
1986 ZEND_FRAMELESS_FUNCTION(strpos, 3)
1987 {
1988 	zval haystack_tmp, needle_tmp;
1989 	zend_string *haystack, *needle;
1990 	zend_long offset;
1991 
1992 	Z_FLF_PARAM_STR(1, haystack, haystack_tmp);
1993 	Z_FLF_PARAM_STR(2, needle, needle_tmp);
1994 	Z_FLF_PARAM_LONG(3, offset);
1995 
1996 	_zend_strpos(return_value, haystack, needle, offset);
1997 
1998 flf_clean:
1999 	Z_FLF_PARAM_FREE_STR(1, haystack_tmp);
2000 	Z_FLF_PARAM_FREE_STR(2, needle_tmp);
2001 }
2002 
2003 /* {{{ Finds position of first occurrence of a string within another, case insensitive */
2004 PHP_FUNCTION(stripos)
2005 {
2006 	const char *found = NULL;
2007 	zend_string *haystack, *needle;
2008 	zend_long offset = 0;
2009 
2010 	ZEND_PARSE_PARAMETERS_START(2, 3)
2011 		Z_PARAM_STR(haystack)
2012 		Z_PARAM_STR(needle)
2013 		Z_PARAM_OPTIONAL
2014 		Z_PARAM_LONG(offset)
2015 	ZEND_PARSE_PARAMETERS_END();
2016 
2017 	if (offset < 0) {
2018 		offset += (zend_long)ZSTR_LEN(haystack);
2019 	}
2020 	if (offset < 0 || (size_t)offset > ZSTR_LEN(haystack)) {
2021 		zend_argument_value_error(3, "must be contained in argument #1 ($haystack)");
2022 		RETURN_THROWS();
2023 	}
2024 
2025 	found = (char*)php_memnistr(ZSTR_VAL(haystack) + offset,
2026 			ZSTR_VAL(needle), ZSTR_LEN(needle), ZSTR_VAL(haystack) + ZSTR_LEN(haystack));
2027 
2028 	if (UNEXPECTED(!found)) {
2029 		RETURN_FALSE;
2030 	}
2031 	RETURN_LONG(found - ZSTR_VAL(haystack));
2032 }
2033 /* }}} */
2034 
2035 /* {{{ Finds position of last occurrence of a string within another string */
2036 PHP_FUNCTION(strrpos)
2037 {
2038 	zend_string *needle;
2039 	zend_string *haystack;
2040 	zend_long offset = 0;
2041 	const char *p, *e, *found;
2042 
2043 	ZEND_PARSE_PARAMETERS_START(2, 3)
2044 		Z_PARAM_STR(haystack)
2045 		Z_PARAM_STR(needle)
2046 		Z_PARAM_OPTIONAL
2047 		Z_PARAM_LONG(offset)
2048 	ZEND_PARSE_PARAMETERS_END();
2049 
2050 	if (offset >= 0) {
2051 		if ((size_t)offset > ZSTR_LEN(haystack)) {
2052 			zend_argument_value_error(3, "must be contained in argument #1 ($haystack)");
2053 			RETURN_THROWS();
2054 		}
2055 		p = ZSTR_VAL(haystack) + (size_t)offset;
2056 		e = ZSTR_VAL(haystack) + ZSTR_LEN(haystack);
2057 	} else {
2058 		if (offset < -ZEND_LONG_MAX || (size_t)(-offset) > ZSTR_LEN(haystack)) {
2059 			zend_argument_value_error(3, "must be contained in argument #1 ($haystack)");
2060 			RETURN_THROWS();
2061 		}
2062 
2063 		p = ZSTR_VAL(haystack);
2064 		if ((size_t)-offset < ZSTR_LEN(needle)) {
2065 			e = ZSTR_VAL(haystack) + ZSTR_LEN(haystack);
2066 		} else {
2067 			e = ZSTR_VAL(haystack) + ZSTR_LEN(haystack) + offset + ZSTR_LEN(needle);
2068 		}
2069 	}
2070 
2071 	found = zend_memnrstr(p, ZSTR_VAL(needle), ZSTR_LEN(needle), e);
2072 
2073 	if (UNEXPECTED(!found)) {
2074 		RETURN_FALSE;
2075 	}
2076 	RETURN_LONG(found - ZSTR_VAL(haystack));
2077 }
2078 /* }}} */
2079 
2080 /* {{{ Finds position of last occurrence of a string within another string */
2081 PHP_FUNCTION(strripos)
2082 {
2083 	zend_string *needle;
2084 	zend_string *haystack;
2085 	zend_long offset = 0;
2086 	const char *p, *e, *found;
2087 	zend_string *needle_dup, *haystack_dup;
2088 
2089 	ZEND_PARSE_PARAMETERS_START(2, 3)
2090 		Z_PARAM_STR(haystack)
2091 		Z_PARAM_STR(needle)
2092 		Z_PARAM_OPTIONAL
2093 		Z_PARAM_LONG(offset)
2094 	ZEND_PARSE_PARAMETERS_END();
2095 
2096 	if (ZSTR_LEN(needle) == 1) {
2097 		/* Single character search can shortcut memcmps
2098 		   Can also avoid tolower emallocs */
2099 		char lowered;
2100 		if (offset >= 0) {
2101 			if ((size_t)offset > ZSTR_LEN(haystack)) {
2102 				zend_argument_value_error(3, "must be contained in argument #1 ($haystack)");
2103 				RETURN_THROWS();
2104 			}
2105 			p = ZSTR_VAL(haystack) + (size_t)offset;
2106 			e = ZSTR_VAL(haystack) + ZSTR_LEN(haystack) - 1;
2107 		} else {
2108 			p = ZSTR_VAL(haystack);
2109 			if (offset < -ZEND_LONG_MAX || (size_t)(-offset) > ZSTR_LEN(haystack)) {
2110 				zend_argument_value_error(3, "must be contained in argument #1 ($haystack)");
2111 				RETURN_THROWS();
2112 			}
2113 			e = ZSTR_VAL(haystack) + (ZSTR_LEN(haystack) + (size_t)offset);
2114 		}
2115 		lowered = zend_tolower_ascii(*ZSTR_VAL(needle));
2116 		while (e >= p) {
2117 			if (zend_tolower_ascii(*e) == lowered) {
2118 				RETURN_LONG(e - p + (offset > 0 ? offset : 0));
2119 			}
2120 			e--;
2121 		}
2122 		RETURN_FALSE;
2123 	}
2124 
2125 	haystack_dup = zend_string_tolower(haystack);
2126 	if (offset >= 0) {
2127 		if ((size_t)offset > ZSTR_LEN(haystack)) {
2128 			zend_string_release_ex(haystack_dup, 0);
2129 			zend_argument_value_error(3, "must be contained in argument #1 ($haystack)");
2130 			RETURN_THROWS();
2131 		}
2132 		p = ZSTR_VAL(haystack_dup) + offset;
2133 		e = ZSTR_VAL(haystack_dup) + ZSTR_LEN(haystack);
2134 	} else {
2135 		if (offset < -ZEND_LONG_MAX || (size_t)(-offset) > ZSTR_LEN(haystack)) {
2136 			zend_string_release_ex(haystack_dup, 0);
2137 			zend_argument_value_error(3, "must be contained in argument #1 ($haystack)");
2138 			RETURN_THROWS();
2139 		}
2140 
2141 		p = ZSTR_VAL(haystack_dup);
2142 		if ((size_t)-offset < ZSTR_LEN(needle)) {
2143 			e = ZSTR_VAL(haystack_dup) + ZSTR_LEN(haystack);
2144 		} else {
2145 			e = ZSTR_VAL(haystack_dup) + ZSTR_LEN(haystack) + offset + ZSTR_LEN(needle);
2146 		}
2147 	}
2148 
2149 	needle_dup = zend_string_tolower(needle);
2150 	if ((found = (char *)zend_memnrstr(p, ZSTR_VAL(needle_dup), ZSTR_LEN(needle_dup), e))) {
2151 		RETVAL_LONG(found - ZSTR_VAL(haystack_dup));
2152 		zend_string_release_ex(needle_dup, 0);
2153 		zend_string_release_ex(haystack_dup, 0);
2154 	} else {
2155 		zend_string_release_ex(needle_dup, 0);
2156 		zend_string_release_ex(haystack_dup, 0);
2157 		RETURN_FALSE;
2158 	}
2159 }
2160 /* }}} */
2161 
2162 /* {{{ Finds the last occurrence of a character in a string within another */
2163 PHP_FUNCTION(strrchr)
2164 {
2165 	zend_string *haystack, *needle;
2166 	const char *found = NULL;
2167 	zend_long found_offset;
2168 	bool part = 0;
2169 
2170 	ZEND_PARSE_PARAMETERS_START(2, 3)
2171 		Z_PARAM_STR(haystack)
2172 		Z_PARAM_STR(needle)
2173 		Z_PARAM_OPTIONAL
2174 		Z_PARAM_BOOL(part)
2175 	ZEND_PARSE_PARAMETERS_END();
2176 
2177 	found = zend_memrchr(ZSTR_VAL(haystack), *ZSTR_VAL(needle), ZSTR_LEN(haystack));
2178 	if (UNEXPECTED(!found)) {
2179 		RETURN_FALSE;
2180 	}
2181 	found_offset = found - ZSTR_VAL(haystack);
2182 	if (part) {
2183 		RETURN_STRINGL(ZSTR_VAL(haystack), found_offset);
2184 	}
2185 	RETURN_STRINGL(found, ZSTR_LEN(haystack) - found_offset);
2186 }
2187 /* }}} */
2188 
2189 /* {{{ php_chunk_split */
2190 static zend_string *php_chunk_split(const char *src, size_t srclen, const char *end, size_t endlen, size_t chunklen)
2191 {
2192 	char *q;
2193 	const char *p;
2194 	size_t chunks;
2195 	size_t restlen;
2196 	zend_string *dest;
2197 
2198 	chunks = srclen / chunklen;
2199 	restlen = srclen - chunks * chunklen; /* srclen % chunklen */
2200 	if (restlen) {
2201 		/* We want chunks to be rounded up rather than rounded down.
2202 		 * Increment can't overflow because chunks <= SIZE_MAX/2 at this point. */
2203 		chunks++;
2204 	}
2205 
2206 	dest = zend_string_safe_alloc(chunks, endlen, srclen, 0);
2207 
2208 	for (p = src, q = ZSTR_VAL(dest); p < (src + srclen - chunklen + 1); ) {
2209 		q = zend_mempcpy(q, p, chunklen);
2210 		q = zend_mempcpy(q, end, endlen);
2211 		p += chunklen;
2212 	}
2213 
2214 	if (restlen) {
2215 		q = zend_mempcpy(q, p, restlen);
2216 		q = zend_mempcpy(q, end, endlen);
2217 	}
2218 
2219 	*q = '\0';
2220 	ZEND_ASSERT(q - ZSTR_VAL(dest) == ZSTR_LEN(dest));
2221 
2222 	return dest;
2223 }
2224 /* }}} */
2225 
2226 /* {{{ Returns split line */
2227 PHP_FUNCTION(chunk_split)
2228 {
2229 	zend_string *str;
2230 	char *end    = "\r\n";
2231 	size_t endlen   = 2;
2232 	zend_long chunklen = 76;
2233 	zend_string *result;
2234 
2235 	ZEND_PARSE_PARAMETERS_START(1, 3)
2236 		Z_PARAM_STR(str)
2237 		Z_PARAM_OPTIONAL
2238 		Z_PARAM_LONG(chunklen)
2239 		Z_PARAM_STRING(end, endlen)
2240 	ZEND_PARSE_PARAMETERS_END();
2241 
2242 	if (chunklen <= 0) {
2243 		zend_argument_value_error(2, "must be greater than 0");
2244 		RETURN_THROWS();
2245 	}
2246 
2247 	if ((size_t)chunklen > ZSTR_LEN(str)) {
2248 		/* to maintain BC, we must return original string + ending */
2249 		result = zend_string_safe_alloc(ZSTR_LEN(str), 1, endlen, 0);
2250 		memcpy(ZSTR_VAL(result), ZSTR_VAL(str), ZSTR_LEN(str));
2251 		memcpy(ZSTR_VAL(result) + ZSTR_LEN(str), end, endlen);
2252 		ZSTR_VAL(result)[ZSTR_LEN(result)] = '\0';
2253 		RETURN_NEW_STR(result);
2254 	}
2255 
2256 	if (!ZSTR_LEN(str)) {
2257 		RETURN_EMPTY_STRING();
2258 	}
2259 
2260 	result = php_chunk_split(ZSTR_VAL(str), ZSTR_LEN(str), end, endlen, (size_t)chunklen);
2261 
2262 	RETURN_STR(result);
2263 }
2264 /* }}} */
2265 
2266 static inline void _zend_substr(zval *return_value, zend_string *str, zend_long f, bool len_is_null, zend_long l)
2267 {
2268 	if (f < 0) {
2269 		/* if "from" position is negative, count start position from the end
2270 		 * of the string
2271 		 */
2272 		if (-(size_t)f > ZSTR_LEN(str)) {
2273 			f = 0;
2274 		} else {
2275 			f = (zend_long)ZSTR_LEN(str) + f;
2276 		}
2277 	} else if ((size_t)f > ZSTR_LEN(str)) {
2278 		RETURN_EMPTY_STRING();
2279 	}
2280 
2281 	if (!len_is_null) {
2282 		if (l < 0) {
2283 			/* if "length" position is negative, set it to the length
2284 			 * needed to stop that many chars from the end of the string
2285 			 */
2286 			if (-(size_t)l > ZSTR_LEN(str) - (size_t)f) {
2287 				l = 0;
2288 			} else {
2289 				l = (zend_long)ZSTR_LEN(str) - f + l;
2290 			}
2291 		} else if ((size_t)l > ZSTR_LEN(str) - (size_t)f) {
2292 			l = (zend_long)ZSTR_LEN(str) - f;
2293 		}
2294 	} else {
2295 		l = (zend_long)ZSTR_LEN(str) - f;
2296 	}
2297 
2298 	if (l == ZSTR_LEN(str)) {
2299 		RETURN_STR_COPY(str);
2300 	} else {
2301 		RETURN_STRINGL_FAST(ZSTR_VAL(str) + f, l);
2302 	}
2303 }
2304 
2305 /* {{{ Returns part of a string */
2306 PHP_FUNCTION(substr)
2307 {
2308 	zend_string *str;
2309 	zend_long l = 0, f;
2310 	bool len_is_null = 1;
2311 
2312 	ZEND_PARSE_PARAMETERS_START(2, 3)
2313 		Z_PARAM_STR(str)
2314 		Z_PARAM_LONG(f)
2315 		Z_PARAM_OPTIONAL
2316 		Z_PARAM_LONG_OR_NULL(l, len_is_null)
2317 	ZEND_PARSE_PARAMETERS_END();
2318 
2319 	_zend_substr(return_value, str, f, len_is_null, l);
2320 }
2321 /* }}} */
2322 
2323 ZEND_FRAMELESS_FUNCTION(substr, 2)
2324 {
2325 	zval str_tmp;
2326 	zend_string *str;
2327 	zend_long f;
2328 
2329 	Z_FLF_PARAM_STR(1, str, str_tmp);
2330 	Z_FLF_PARAM_LONG(2, f);
2331 
2332 	_zend_substr(return_value, str, f, /* len_is_null */ true, 0);
2333 
2334 flf_clean:
2335 	Z_FLF_PARAM_FREE_STR(1, str_tmp);
2336 }
2337 
2338 ZEND_FRAMELESS_FUNCTION(substr, 3)
2339 {
2340 	zval str_tmp;
2341 	zend_string *str;
2342 	zend_long f, l;
2343 	bool len_is_null;
2344 
2345 	Z_FLF_PARAM_STR(1, str, str_tmp);
2346 	Z_FLF_PARAM_LONG(2, f);
2347 	Z_FLF_PARAM_LONG_OR_NULL(3, len_is_null, l);
2348 
2349 	_zend_substr(return_value, str, f, len_is_null, l);
2350 
2351 flf_clean:
2352 	Z_FLF_PARAM_FREE_STR(1, str_tmp);
2353 }
2354 
2355 /* {{{ Replaces part of a string with another string */
2356 PHP_FUNCTION(substr_replace)
2357 {
2358 	zend_string *str, *repl_str;
2359 	HashTable *str_ht, *repl_ht;
2360 	HashTable *from_ht;
2361 	zend_long from_long;
2362 	HashTable *len_ht = NULL;
2363 	zend_long len_long;
2364 	bool len_is_null = 1;
2365 	zend_long l = 0;
2366 	zend_long f;
2367 	zend_string *result;
2368 	HashPosition from_idx, repl_idx, len_idx;
2369 	zval *tmp_str = NULL, *tmp_repl, *tmp_from = NULL, *tmp_len= NULL;
2370 
2371 	ZEND_PARSE_PARAMETERS_START(3, 4)
2372 		Z_PARAM_ARRAY_HT_OR_STR(str_ht, str)
2373 		Z_PARAM_ARRAY_HT_OR_STR(repl_ht, repl_str)
2374 		Z_PARAM_ARRAY_HT_OR_LONG(from_ht, from_long)
2375 		Z_PARAM_OPTIONAL
2376 		Z_PARAM_ARRAY_HT_OR_LONG_OR_NULL(len_ht, len_long, len_is_null)
2377 	ZEND_PARSE_PARAMETERS_END();
2378 
2379 	if (len_is_null) {
2380 		if (str) {
2381 			l = ZSTR_LEN(str);
2382 		}
2383 	} else if (!len_ht) {
2384 		l = len_long;
2385 	}
2386 
2387 	if (str) {
2388 		if (from_ht) {
2389 			zend_argument_type_error(3, "cannot be an array when working on a single string");
2390 			RETURN_THROWS();
2391 		}
2392 		if (len_ht) {
2393 			zend_argument_type_error(4, "cannot be an array when working on a single string");
2394 			RETURN_THROWS();
2395 		}
2396 
2397 		f = from_long;
2398 
2399 		/* if "from" position is negative, count start position from the end
2400 		 * of the string
2401 		 */
2402 		if (f < 0) {
2403 			f = (zend_long)ZSTR_LEN(str) + f;
2404 			if (f < 0) {
2405 				f = 0;
2406 			}
2407 		} else if ((size_t)f > ZSTR_LEN(str)) {
2408 			f = ZSTR_LEN(str);
2409 		}
2410 		/* if "length" position is negative, set it to the length
2411 		 * needed to stop that many chars from the end of the string
2412 		 */
2413 		if (l < 0) {
2414 			l = ((zend_long)ZSTR_LEN(str) - f) + l;
2415 			if (l < 0) {
2416 				l = 0;
2417 			}
2418 		}
2419 
2420 		if ((size_t)l > ZSTR_LEN(str)) {
2421 			l = ZSTR_LEN(str);
2422 		}
2423 
2424 		if ((f + l) > (zend_long)ZSTR_LEN(str)) {
2425 			l = ZSTR_LEN(str) - f;
2426 		}
2427 
2428 		zend_string *tmp_repl_str = NULL;
2429 		if (repl_ht) {
2430 			repl_idx = 0;
2431 			if (HT_IS_PACKED(repl_ht)) {
2432 				while (repl_idx < repl_ht->nNumUsed) {
2433 					tmp_repl = &repl_ht->arPacked[repl_idx];
2434 					if (Z_TYPE_P(tmp_repl) != IS_UNDEF) {
2435 						break;
2436 					}
2437 					repl_idx++;
2438 				}
2439 			} else {
2440 				while (repl_idx < repl_ht->nNumUsed) {
2441 					tmp_repl = &repl_ht->arData[repl_idx].val;
2442 					if (Z_TYPE_P(tmp_repl) != IS_UNDEF) {
2443 						break;
2444 					}
2445 					repl_idx++;
2446 				}
2447 			}
2448 			if (repl_idx < repl_ht->nNumUsed) {
2449 				repl_str = zval_get_tmp_string(tmp_repl, &tmp_repl_str);
2450 			} else {
2451 				repl_str = STR_EMPTY_ALLOC();
2452 			}
2453 		}
2454 
2455 		result = zend_string_safe_alloc(1, ZSTR_LEN(str) - l + ZSTR_LEN(repl_str), 0, 0);
2456 
2457 		memcpy(ZSTR_VAL(result), ZSTR_VAL(str), f);
2458 		if (ZSTR_LEN(repl_str)) {
2459 			memcpy((ZSTR_VAL(result) + f), ZSTR_VAL(repl_str), ZSTR_LEN(repl_str));
2460 		}
2461 		memcpy((ZSTR_VAL(result) + f + ZSTR_LEN(repl_str)), ZSTR_VAL(str) + f + l, ZSTR_LEN(str) - f - l);
2462 		ZSTR_VAL(result)[ZSTR_LEN(result)] = '\0';
2463 		zend_tmp_string_release(tmp_repl_str);
2464 		RETURN_NEW_STR(result);
2465 	} else { /* str is array of strings */
2466 		zend_string *str_index = NULL;
2467 		size_t result_len;
2468 		zend_ulong num_index;
2469 
2470 		/* TODO
2471 		if (!len_is_null && from_ht) {
2472 			if (zend_hash_num_elements(from_ht) != zend_hash_num_elements(len_ht)) {
2473 				php_error_docref(NULL, E_WARNING, "'start' and 'length' should have the same number of elements");
2474 				RETURN_STR_COPY(str);
2475 			}
2476 		}
2477 		*/
2478 
2479 		array_init(return_value);
2480 
2481 		from_idx = len_idx = repl_idx = 0;
2482 
2483 		ZEND_HASH_FOREACH_KEY_VAL(str_ht, num_index, str_index, tmp_str) {
2484 			zend_string *tmp_orig_str;
2485 			zend_string *orig_str = zval_get_tmp_string(tmp_str, &tmp_orig_str);
2486 
2487 			if (from_ht) {
2488 				if (HT_IS_PACKED(from_ht)) {
2489 					while (from_idx < from_ht->nNumUsed) {
2490 						tmp_from = &from_ht->arPacked[from_idx];
2491 						if (Z_TYPE_P(tmp_from) != IS_UNDEF) {
2492 							break;
2493 						}
2494 						from_idx++;
2495 					}
2496 				} else {
2497 					while (from_idx < from_ht->nNumUsed) {
2498 						tmp_from = &from_ht->arData[from_idx].val;
2499 						if (Z_TYPE_P(tmp_from) != IS_UNDEF) {
2500 							break;
2501 						}
2502 						from_idx++;
2503 					}
2504 				}
2505 				if (from_idx < from_ht->nNumUsed) {
2506 					f = zval_get_long(tmp_from);
2507 
2508 					if (f < 0) {
2509 						f = (zend_long)ZSTR_LEN(orig_str) + f;
2510 						if (f < 0) {
2511 							f = 0;
2512 						}
2513 					} else if (f > (zend_long)ZSTR_LEN(orig_str)) {
2514 						f = ZSTR_LEN(orig_str);
2515 					}
2516 					from_idx++;
2517 				} else {
2518 					f = 0;
2519 				}
2520 			} else {
2521 				f = from_long;
2522 				if (f < 0) {
2523 					f = (zend_long)ZSTR_LEN(orig_str) + f;
2524 					if (f < 0) {
2525 						f = 0;
2526 					}
2527 				} else if (f > (zend_long)ZSTR_LEN(orig_str)) {
2528 					f = ZSTR_LEN(orig_str);
2529 				}
2530 			}
2531 
2532 			if (len_ht) {
2533 				if (HT_IS_PACKED(len_ht)) {
2534 					while (len_idx < len_ht->nNumUsed) {
2535 						tmp_len = &len_ht->arPacked[len_idx];
2536 						if (Z_TYPE_P(tmp_len) != IS_UNDEF) {
2537 							break;
2538 						}
2539 						len_idx++;
2540 					}
2541 				} else {
2542 					while (len_idx < len_ht->nNumUsed) {
2543 						tmp_len = &len_ht->arData[len_idx].val;
2544 						if (Z_TYPE_P(tmp_len) != IS_UNDEF) {
2545 							break;
2546 						}
2547 						len_idx++;
2548 					}
2549 				}
2550 				if (len_idx < len_ht->nNumUsed) {
2551 					l = zval_get_long(tmp_len);
2552 					len_idx++;
2553 				} else {
2554 					l = ZSTR_LEN(orig_str);
2555 				}
2556 			} else if (!len_is_null) {
2557 				l = len_long;
2558 			} else {
2559 				l = ZSTR_LEN(orig_str);
2560 			}
2561 
2562 			if (l < 0) {
2563 				l = (ZSTR_LEN(orig_str) - f) + l;
2564 				if (l < 0) {
2565 					l = 0;
2566 				}
2567 			}
2568 
2569 			ZEND_ASSERT(0 <= f && f <= ZEND_LONG_MAX);
2570 			ZEND_ASSERT(0 <= l && l <= ZEND_LONG_MAX);
2571 			if (((size_t) f + l) > ZSTR_LEN(orig_str)) {
2572 				l = ZSTR_LEN(orig_str) - f;
2573 			}
2574 
2575 			result_len = ZSTR_LEN(orig_str) - l;
2576 
2577 			if (repl_ht) {
2578 				if (HT_IS_PACKED(repl_ht)) {
2579 					while (repl_idx < repl_ht->nNumUsed) {
2580 						tmp_repl = &repl_ht->arPacked[repl_idx];
2581 						if (Z_TYPE_P(tmp_repl) != IS_UNDEF) {
2582 							break;
2583 						}
2584 						repl_idx++;
2585 					}
2586 				} else {
2587 					while (repl_idx < repl_ht->nNumUsed) {
2588 						tmp_repl = &repl_ht->arData[repl_idx].val;
2589 						if (Z_TYPE_P(tmp_repl) != IS_UNDEF) {
2590 							break;
2591 						}
2592 						repl_idx++;
2593 					}
2594 				}
2595 				if (repl_idx < repl_ht->nNumUsed) {
2596 					zend_string *tmp_repl_str;
2597 					zend_string *repl_str = zval_get_tmp_string(tmp_repl, &tmp_repl_str);
2598 
2599 					result_len += ZSTR_LEN(repl_str);
2600 					repl_idx++;
2601 					result = zend_string_safe_alloc(1, result_len, 0, 0);
2602 
2603 					memcpy(ZSTR_VAL(result), ZSTR_VAL(orig_str), f);
2604 					memcpy((ZSTR_VAL(result) + f), ZSTR_VAL(repl_str), ZSTR_LEN(repl_str));
2605 					memcpy((ZSTR_VAL(result) + f + ZSTR_LEN(repl_str)), ZSTR_VAL(orig_str) + f + l, ZSTR_LEN(orig_str) - f - l);
2606 					zend_tmp_string_release(tmp_repl_str);
2607 				} else {
2608 					result = zend_string_safe_alloc(1, result_len, 0, 0);
2609 
2610 					memcpy(ZSTR_VAL(result), ZSTR_VAL(orig_str), f);
2611 					memcpy((ZSTR_VAL(result) + f), ZSTR_VAL(orig_str) + f + l, ZSTR_LEN(orig_str) - f - l);
2612 				}
2613 			} else {
2614 				result_len += ZSTR_LEN(repl_str);
2615 
2616 				result = zend_string_safe_alloc(1, result_len, 0, 0);
2617 
2618 				memcpy(ZSTR_VAL(result), ZSTR_VAL(orig_str), f);
2619 				memcpy((ZSTR_VAL(result) + f), ZSTR_VAL(repl_str), ZSTR_LEN(repl_str));
2620 				memcpy((ZSTR_VAL(result) + f + ZSTR_LEN(repl_str)), ZSTR_VAL(orig_str) + f + l, ZSTR_LEN(orig_str) - f - l);
2621 			}
2622 
2623 			ZSTR_VAL(result)[ZSTR_LEN(result)] = '\0';
2624 
2625 			if (str_index) {
2626 				zval tmp;
2627 
2628 				ZVAL_NEW_STR(&tmp, result);
2629 				zend_symtable_update(Z_ARRVAL_P(return_value), str_index, &tmp);
2630 			} else {
2631 				add_index_str(return_value, num_index, result);
2632 			}
2633 
2634 			zend_tmp_string_release(tmp_orig_str);
2635 		} ZEND_HASH_FOREACH_END();
2636 	} /* if */
2637 }
2638 /* }}} */
2639 
2640 /* {{{ Quotes meta characters */
2641 PHP_FUNCTION(quotemeta)
2642 {
2643 	zend_string *old;
2644 	const char *old_end, *p;
2645 	char *q;
2646 	char c;
2647 	zend_string *str;
2648 
2649 	ZEND_PARSE_PARAMETERS_START(1, 1)
2650 		Z_PARAM_STR(old)
2651 	ZEND_PARSE_PARAMETERS_END();
2652 
2653 	old_end = ZSTR_VAL(old) + ZSTR_LEN(old);
2654 
2655 	if (ZSTR_LEN(old) == 0) {
2656 		RETURN_EMPTY_STRING();
2657 	}
2658 
2659 	str = zend_string_safe_alloc(2, ZSTR_LEN(old), 0, 0);
2660 
2661 	for (p = ZSTR_VAL(old), q = ZSTR_VAL(str); p != old_end; p++) {
2662 		c = *p;
2663 		switch (c) {
2664 			case '.':
2665 			case '\\':
2666 			case '+':
2667 			case '*':
2668 			case '?':
2669 			case '[':
2670 			case '^':
2671 			case ']':
2672 			case '$':
2673 			case '(':
2674 			case ')':
2675 				*q++ = '\\';
2676 				ZEND_FALLTHROUGH;
2677 			default:
2678 				*q++ = c;
2679 		}
2680 	}
2681 
2682 	*q = '\0';
2683 
2684 	RETURN_NEW_STR(zend_string_truncate(str, q - ZSTR_VAL(str), 0));
2685 }
2686 /* }}} */
2687 
2688 /* {{{ Returns ASCII value of character
2689    Warning: This function is special-cased by zend_compile.c and so is bypassed for constant string argument */
2690 PHP_FUNCTION(ord)
2691 {
2692 	zend_string *str;
2693 
2694 	ZEND_PARSE_PARAMETERS_START(1, 1)
2695 		Z_PARAM_STR(str)
2696 	ZEND_PARSE_PARAMETERS_END();
2697 
2698 	RETURN_LONG((unsigned char) ZSTR_VAL(str)[0]);
2699 }
2700 /* }}} */
2701 
2702 /* {{{ Converts ASCII code to a character
2703    Warning: This function is special-cased by zend_compile.c and so is bypassed for constant integer argument */
2704 PHP_FUNCTION(chr)
2705 {
2706 	zend_long c;
2707 
2708 	ZEND_PARSE_PARAMETERS_START(1, 1)
2709 		Z_PARAM_LONG(c)
2710 	ZEND_PARSE_PARAMETERS_END();
2711 
2712 	c &= 0xff;
2713 	RETURN_CHAR(c);
2714 }
2715 /* }}} */
2716 
2717 /* {{{ php_ucfirst
2718    Uppercase the first character of the word in a native string */
2719 static zend_string* php_ucfirst(zend_string *str)
2720 {
2721 	const unsigned char ch = ZSTR_VAL(str)[0];
2722 	unsigned char r = zend_toupper_ascii(ch);
2723 	if (r == ch) {
2724 		return zend_string_copy(str);
2725 	} else {
2726 		zend_string *s = zend_string_init(ZSTR_VAL(str), ZSTR_LEN(str), 0);
2727 		ZSTR_VAL(s)[0] = r;
2728 		return s;
2729 	}
2730 }
2731 /* }}} */
2732 
2733 /* {{{ Makes a string's first character uppercase */
2734 PHP_FUNCTION(ucfirst)
2735 {
2736 	zend_string *str;
2737 
2738 	ZEND_PARSE_PARAMETERS_START(1, 1)
2739 		Z_PARAM_STR(str)
2740 	ZEND_PARSE_PARAMETERS_END();
2741 
2742 	if (!ZSTR_LEN(str)) {
2743 		RETURN_EMPTY_STRING();
2744 	}
2745 
2746 	RETURN_STR(php_ucfirst(str));
2747 }
2748 /* }}} */
2749 
2750 /* {{{
2751    Lowercase the first character of the word in a native string */
2752 static zend_string* php_lcfirst(zend_string *str)
2753 {
2754 	unsigned char r = zend_tolower_ascii(ZSTR_VAL(str)[0]);
2755 	if (r == ZSTR_VAL(str)[0]) {
2756 		return zend_string_copy(str);
2757 	} else {
2758 		zend_string *s = zend_string_init(ZSTR_VAL(str), ZSTR_LEN(str), 0);
2759 		ZSTR_VAL(s)[0] = r;
2760 		return s;
2761 	}
2762 }
2763 /* }}} */
2764 
2765 /* {{{ Make a string's first character lowercase */
2766 PHP_FUNCTION(lcfirst)
2767 {
2768 	zend_string  *str;
2769 
2770 	ZEND_PARSE_PARAMETERS_START(1, 1)
2771 		Z_PARAM_STR(str)
2772 	ZEND_PARSE_PARAMETERS_END();
2773 
2774 	if (!ZSTR_LEN(str)) {
2775 		RETURN_EMPTY_STRING();
2776 	}
2777 
2778 	RETURN_STR(php_lcfirst(str));
2779 }
2780 /* }}} */
2781 
2782 /* {{{ Uppercase the first character of every word in a string */
2783 PHP_FUNCTION(ucwords)
2784 {
2785 	zend_string *str;
2786 	char *delims = " \t\r\n\f\v";
2787 	char *r;
2788 	const char *r_end;
2789 	size_t delims_len = 6;
2790 	char mask[256];
2791 
2792 	ZEND_PARSE_PARAMETERS_START(1, 2)
2793 		Z_PARAM_STR(str)
2794 		Z_PARAM_OPTIONAL
2795 		Z_PARAM_STRING(delims, delims_len)
2796 	ZEND_PARSE_PARAMETERS_END();
2797 
2798 	if (!ZSTR_LEN(str)) {
2799 		RETURN_EMPTY_STRING();
2800 	}
2801 
2802 	php_charmask((const unsigned char *) delims, delims_len, mask);
2803 
2804 	ZVAL_STRINGL(return_value, ZSTR_VAL(str), ZSTR_LEN(str));
2805 	r = Z_STRVAL_P(return_value);
2806 
2807 	*r = zend_toupper_ascii((unsigned char) *r);
2808 	for (r_end = r + Z_STRLEN_P(return_value) - 1; r < r_end; ) {
2809 		if (mask[(unsigned char)*r++]) {
2810 			*r = zend_toupper_ascii((unsigned char) *r);
2811 		}
2812 	}
2813 }
2814 /* }}} */
2815 
2816 /* {{{ php_strtr */
2817 PHPAPI char *php_strtr(char *str, size_t len, const char *str_from, const char *str_to, size_t trlen)
2818 {
2819 	size_t i;
2820 
2821 	if (UNEXPECTED(trlen < 1)) {
2822 		return str;
2823 	} else if (trlen == 1) {
2824 		char ch_from = *str_from;
2825 		char ch_to = *str_to;
2826 
2827 		for (i = 0; i < len; i++) {
2828 			if (str[i] == ch_from) {
2829 				str[i] = ch_to;
2830 			}
2831 		}
2832 	} else {
2833 		unsigned char xlat[256];
2834 
2835 		memset(xlat, 0, sizeof(xlat));
2836 
2837 		for (i = 0; i < trlen; i++) {
2838 			xlat[(size_t)(unsigned char) str_from[i]] = str_to[i] - str_from[i];
2839 		}
2840 
2841 		for (i = 0; i < len; i++) {
2842 			str[i] += xlat[(size_t)(unsigned char) str[i]];
2843 		}
2844 	}
2845 
2846 	return str;
2847 }
2848 /* }}} */
2849 
2850 /* {{{ php_strtr_ex */
2851 static zend_string *php_strtr_ex(zend_string *str, const char *str_from, const char *str_to, size_t trlen)
2852 {
2853 	zend_string *new_str = NULL;
2854 	size_t i;
2855 
2856 	if (UNEXPECTED(trlen < 1)) {
2857 		return zend_string_copy(str);
2858 	} else if (trlen == 1) {
2859 		char ch_from = *str_from;
2860 		char ch_to = *str_to;
2861 		char *output;
2862 		char *input = ZSTR_VAL(str);
2863 		size_t len = ZSTR_LEN(str);
2864 
2865 #ifdef __SSE2__
2866 		if (ZSTR_LEN(str) >= sizeof(__m128i)) {
2867 			__m128i search = _mm_set1_epi8(ch_from);
2868 			__m128i delta = _mm_set1_epi8(ch_to - ch_from);
2869 
2870 			do {
2871 				__m128i src = _mm_loadu_si128((__m128i*)(input));
2872 				__m128i mask = _mm_cmpeq_epi8(src, search);
2873 				if (_mm_movemask_epi8(mask)) {
2874 					new_str = zend_string_alloc(ZSTR_LEN(str), 0);
2875 					memcpy(ZSTR_VAL(new_str), ZSTR_VAL(str), input - ZSTR_VAL(str));
2876 					output = ZSTR_VAL(new_str) + (input - ZSTR_VAL(str));
2877 					_mm_storeu_si128((__m128i *)(output),
2878 						_mm_add_epi8(src,
2879 							_mm_and_si128(mask, delta)));
2880 					input += sizeof(__m128i);
2881 					output += sizeof(__m128i);
2882 					len -= sizeof(__m128i);
2883 					for (; len >= sizeof(__m128i); input += sizeof(__m128i), output += sizeof(__m128i), len -= sizeof(__m128i)) {
2884 						src = _mm_loadu_si128((__m128i*)(input));
2885 						mask = _mm_cmpeq_epi8(src, search);
2886 						_mm_storeu_si128((__m128i *)(output),
2887 							_mm_add_epi8(src,
2888 								_mm_and_si128(mask, delta)));
2889 					}
2890 					for (; len > 0; input++, output++, len--) {
2891 						*output = (*input == ch_from) ? ch_to : *input;
2892 					}
2893 					*output = 0;
2894 					return new_str;
2895 				}
2896 				input += sizeof(__m128i);
2897 				len -= sizeof(__m128i);
2898 			} while (len >= sizeof(__m128i));
2899 		}
2900 #endif
2901 		for (; len > 0; input++, len--) {
2902 			if (*input == ch_from) {
2903 				new_str = zend_string_alloc(ZSTR_LEN(str), 0);
2904 				memcpy(ZSTR_VAL(new_str), ZSTR_VAL(str), input - ZSTR_VAL(str));
2905 				output = ZSTR_VAL(new_str) + (input - ZSTR_VAL(str));
2906 				*output = ch_to;
2907 				input++;
2908 				output++;
2909 				len--;
2910 				for (; len > 0; input++, output++, len--) {
2911 					*output = (*input == ch_from) ? ch_to : *input;
2912 				}
2913 				*output = 0;
2914 				return new_str;
2915 			}
2916 		}
2917 	} else {
2918 		unsigned char xlat[256];
2919 
2920 		memset(xlat, 0, sizeof(xlat));;
2921 
2922 		for (i = 0; i < trlen; i++) {
2923 			xlat[(size_t)(unsigned char) str_from[i]] = str_to[i] - str_from[i];
2924 		}
2925 
2926 		for (i = 0; i < ZSTR_LEN(str); i++) {
2927 			if (xlat[(size_t)(unsigned char) ZSTR_VAL(str)[i]]) {
2928 				new_str = zend_string_alloc(ZSTR_LEN(str), 0);
2929 				memcpy(ZSTR_VAL(new_str), ZSTR_VAL(str), i);
2930 				do {
2931 					ZSTR_VAL(new_str)[i] = ZSTR_VAL(str)[i] + xlat[(size_t)(unsigned char) ZSTR_VAL(str)[i]];
2932 					i++;
2933 				} while (i < ZSTR_LEN(str));
2934 				ZSTR_VAL(new_str)[i] = 0;
2935 				return new_str;
2936 			}
2937 		}
2938 	}
2939 
2940 	return zend_string_copy(str);
2941 }
2942 /* }}} */
2943 
2944 static void php_strtr_array_ex(zval *return_value, zend_string *input, HashTable *pats)
2945 {
2946 	const char *str = ZSTR_VAL(input);
2947 	size_t slen = ZSTR_LEN(input);
2948 	zend_ulong num_key;
2949 	zend_string *str_key;
2950 	size_t len, pos, old_pos;
2951 	bool has_num_keys = false;
2952 	size_t minlen = 128*1024;
2953 	size_t maxlen = 0;
2954 	HashTable str_hash;
2955 	zval *entry;
2956 	const char *key;
2957 	smart_str result = {0};
2958 	zend_ulong bitset[256/sizeof(zend_ulong)];
2959 	zend_ulong *num_bitset;
2960 
2961 	/* we will collect all possible key lengths */
2962 	num_bitset = ecalloc((slen + sizeof(zend_ulong)) / sizeof(zend_ulong), sizeof(zend_ulong));
2963 	memset(bitset, 0, sizeof(bitset));
2964 
2965 	/* check if original array has numeric keys */
2966 	ZEND_HASH_FOREACH_STR_KEY(pats, str_key) {
2967 		if (UNEXPECTED(!str_key)) {
2968 			has_num_keys = true;
2969 		} else {
2970 			len = ZSTR_LEN(str_key);
2971 			if (UNEXPECTED(len == 0)) {
2972 				php_error_docref(NULL, E_WARNING, "Ignoring replacement of empty string");
2973 				continue;
2974 			} else if (UNEXPECTED(len > slen)) {
2975 				/* skip long patterns */
2976 				continue;
2977 			}
2978 			if (len > maxlen) {
2979 				maxlen = len;
2980 			}
2981 			if (len < minlen) {
2982 				minlen = len;
2983 			}
2984 			/* remember possible key length */
2985 			num_bitset[len / sizeof(zend_ulong)] |= Z_UL(1) << (len % sizeof(zend_ulong));
2986 			bitset[((unsigned char)ZSTR_VAL(str_key)[0]) / sizeof(zend_ulong)] |= Z_UL(1) << (((unsigned char)ZSTR_VAL(str_key)[0]) % sizeof(zend_ulong));
2987 		}
2988 	} ZEND_HASH_FOREACH_END();
2989 
2990 	if (UNEXPECTED(has_num_keys)) {
2991 		zend_string *key_used;
2992 		/* we have to rebuild HashTable with numeric keys */
2993 		zend_hash_init(&str_hash, zend_hash_num_elements(pats), NULL, NULL, 0);
2994 		ZEND_HASH_FOREACH_KEY_VAL(pats, num_key, str_key, entry) {
2995 			if (UNEXPECTED(!str_key)) {
2996 				key_used = zend_long_to_str(num_key);
2997 				len = ZSTR_LEN(key_used);
2998 				if (UNEXPECTED(len > slen)) {
2999 					/* skip long patterns */
3000 					zend_string_release(key_used);
3001 					continue;
3002 				}
3003 				if (len > maxlen) {
3004 					maxlen = len;
3005 				}
3006 				if (len < minlen) {
3007 					minlen = len;
3008 				}
3009 				/* remember possible key length */
3010 				num_bitset[len / sizeof(zend_ulong)] |= Z_UL(1) << (len % sizeof(zend_ulong));
3011 				bitset[((unsigned char)ZSTR_VAL(key_used)[0]) / sizeof(zend_ulong)] |= Z_UL(1) << (((unsigned char)ZSTR_VAL(key_used)[0]) % sizeof(zend_ulong));
3012 			} else {
3013 				key_used = str_key;
3014 				len = ZSTR_LEN(key_used);
3015 				if (UNEXPECTED(len > slen)) {
3016 					/* skip long patterns */
3017 					continue;
3018 				}
3019 			}
3020 			zend_hash_add(&str_hash, key_used, entry);
3021 			if (UNEXPECTED(!str_key)) {
3022 				zend_string_release_ex(key_used, 0);
3023 			}
3024 		} ZEND_HASH_FOREACH_END();
3025 		pats = &str_hash;
3026 	}
3027 
3028 	if (UNEXPECTED(minlen > maxlen)) {
3029 		/* return the original string */
3030 		if (pats == &str_hash) {
3031 			zend_hash_destroy(&str_hash);
3032 		}
3033 		efree(num_bitset);
3034 		RETURN_STR_COPY(input);
3035 	}
3036 
3037 	old_pos = pos = 0;
3038 	while (pos <= slen - minlen) {
3039 		key = str + pos;
3040 		if (bitset[((unsigned char)key[0]) / sizeof(zend_ulong)] & (Z_UL(1) << (((unsigned char)key[0]) % sizeof(zend_ulong)))) {
3041 			len = maxlen;
3042 			if (len > slen - pos) {
3043 				len = slen - pos;
3044 			}
3045 			while (len >= minlen) {
3046 				if ((num_bitset[len / sizeof(zend_ulong)] & (Z_UL(1) << (len % sizeof(zend_ulong))))) {
3047 					entry = zend_hash_str_find(pats, key, len);
3048 					if (entry != NULL) {
3049 						zend_string *tmp;
3050 						zend_string *s = zval_get_tmp_string(entry, &tmp);
3051 						smart_str_appendl(&result, str + old_pos, pos - old_pos);
3052 						smart_str_append(&result, s);
3053 						old_pos = pos + len;
3054 						pos = old_pos - 1;
3055 						zend_tmp_string_release(tmp);
3056 						break;
3057 					}
3058 				}
3059 				len--;
3060 			}
3061 		}
3062 		pos++;
3063 	}
3064 
3065 	if (result.s) {
3066 		smart_str_appendl(&result, str + old_pos, slen - old_pos);
3067 		RETVAL_STR(smart_str_extract(&result));
3068 	} else {
3069 		smart_str_free(&result);
3070 		RETVAL_STR_COPY(input);
3071 	}
3072 
3073 	if (pats == &str_hash) {
3074 		zend_hash_destroy(&str_hash);
3075 	}
3076 	efree(num_bitset);
3077 }
3078 
3079 /* {{{ count_chars */
3080 static zend_always_inline zend_long count_chars(const char *p, zend_long length, char ch)
3081 {
3082 	zend_long count = 0;
3083 	const char *endp;
3084 
3085 #ifdef __SSE2__
3086 	if (length >= sizeof(__m128i)) {
3087 		__m128i search = _mm_set1_epi8(ch);
3088 
3089 		do {
3090 			__m128i src = _mm_loadu_si128((__m128i*)(p));
3091 			uint32_t mask = _mm_movemask_epi8(_mm_cmpeq_epi8(src, search));
3092 			// TODO: It would be great to use POPCNT, but it's available only with SSE4.1
3093 #if 1
3094 			while (mask != 0) {
3095 				count++;
3096 				mask = mask & (mask - 1);
3097 			}
3098 #else
3099 			if (mask) {
3100 				mask = mask - ((mask >> 1) & 0x5555);
3101 				mask = (mask & 0x3333) + ((mask >> 2) & 0x3333);
3102 				mask = (mask + (mask >> 4)) & 0x0F0F;
3103 				mask = (mask + (mask >> 8)) & 0x00ff;
3104 				count += mask;
3105 			}
3106 #endif
3107 			p += sizeof(__m128i);
3108 			length -= sizeof(__m128i);
3109 		} while (length >= sizeof(__m128i));
3110 	}
3111 	endp = p + length;
3112 	while (p != endp) {
3113 		count += (*p == ch);
3114 		p++;
3115 	}
3116 #else
3117 	endp = p + length;
3118 	while ((p = memchr(p, ch, endp-p))) {
3119 		count++;
3120 		p++;
3121 	}
3122 #endif
3123 	return count;
3124 }
3125 /* }}} */
3126 
3127 /* {{{ php_char_to_str_ex */
3128 static zend_string* php_char_to_str_ex(zend_string *str, char from, char *to, size_t to_len, bool case_sensitivity, zend_long *replace_count)
3129 {
3130 	zend_string *result;
3131 	size_t char_count;
3132 	int lc_from = 0;
3133 	const char *source, *source_end;
3134 	char *target;
3135 
3136 	if (case_sensitivity) {
3137 		char_count = count_chars(ZSTR_VAL(str), ZSTR_LEN(str), from);
3138 	} else {
3139 		char_count = 0;
3140 		lc_from = zend_tolower_ascii(from);
3141 		source_end = ZSTR_VAL(str) + ZSTR_LEN(str);
3142 		for (source = ZSTR_VAL(str); source < source_end; source++) {
3143 			if (zend_tolower_ascii(*source) == lc_from) {
3144 				char_count++;
3145 			}
3146 		}
3147 	}
3148 
3149 	if (char_count == 0) {
3150 		return zend_string_copy(str);
3151 	}
3152 
3153 	if (replace_count) {
3154 		*replace_count += char_count;
3155 	}
3156 
3157 	if (to_len > 0) {
3158 		result = zend_string_safe_alloc(char_count, to_len - 1, ZSTR_LEN(str), 0);
3159 	} else {
3160 		result = zend_string_alloc(ZSTR_LEN(str) - char_count, 0);
3161 	}
3162 	target = ZSTR_VAL(result);
3163 
3164 	if (case_sensitivity) {
3165 		char *p = ZSTR_VAL(str), *e = p + ZSTR_LEN(str), *s = ZSTR_VAL(str);
3166 
3167 		while ((p = memchr(p, from, (e - p)))) {
3168 			target = zend_mempcpy(target, s, (p - s));
3169 			target = zend_mempcpy(target, to, to_len);
3170 			p++;
3171 			s = p;
3172 			if (--char_count == 0) break;
3173 		}
3174 		if (s < e) {
3175 			target = zend_mempcpy(target, s, e - s);
3176 		}
3177 	} else {
3178 		source_end = ZSTR_VAL(str) + ZSTR_LEN(str);
3179 		for (source = ZSTR_VAL(str); source < source_end; source++) {
3180 			if (zend_tolower_ascii(*source) == lc_from) {
3181 				target = zend_mempcpy(target, to, to_len);
3182 			} else {
3183 				*target = *source;
3184 				target++;
3185 			}
3186 		}
3187 	}
3188 	*target = 0;
3189 	return result;
3190 }
3191 /* }}} */
3192 
3193 /* {{{ php_str_to_str_ex */
3194 static zend_string *php_str_to_str_ex(zend_string *haystack,
3195 	const char *needle, size_t needle_len, const char *str, size_t str_len, zend_long *replace_count)
3196 {
3197 
3198 	if (needle_len < ZSTR_LEN(haystack)) {
3199 		zend_string *new_str;
3200 		const char *end;
3201 		const char *p, *r;
3202 		char *e;
3203 
3204 		if (needle_len == str_len) {
3205 			new_str = NULL;
3206 			end = ZSTR_VAL(haystack) + ZSTR_LEN(haystack);
3207 			for (p = ZSTR_VAL(haystack); (r = (char*)php_memnstr(p, needle, needle_len, end)); p = r + needle_len) {
3208 				if (!new_str) {
3209 					new_str = zend_string_init(ZSTR_VAL(haystack), ZSTR_LEN(haystack), 0);
3210 				}
3211 				memcpy(ZSTR_VAL(new_str) + (r - ZSTR_VAL(haystack)), str, str_len);
3212 				(*replace_count)++;
3213 			}
3214 			if (!new_str) {
3215 				goto nothing_todo;
3216 			}
3217 			return new_str;
3218 		} else {
3219 			size_t count = 0;
3220 			const char *o = ZSTR_VAL(haystack);
3221 			const char *n = needle;
3222 			const char *endp = o + ZSTR_LEN(haystack);
3223 
3224 			while ((o = (char*)php_memnstr(o, n, needle_len, endp))) {
3225 				o += needle_len;
3226 				count++;
3227 			}
3228 			if (count == 0) {
3229 				/* Needle doesn't occur, shortcircuit the actual replacement. */
3230 				goto nothing_todo;
3231 			}
3232 			if (str_len > needle_len) {
3233 				new_str = zend_string_safe_alloc(count, str_len - needle_len, ZSTR_LEN(haystack), 0);
3234 			} else {
3235 				new_str = zend_string_alloc(count * (str_len - needle_len) + ZSTR_LEN(haystack), 0);
3236 			}
3237 
3238 			e = ZSTR_VAL(new_str);
3239 			end = ZSTR_VAL(haystack) + ZSTR_LEN(haystack);
3240 			for (p = ZSTR_VAL(haystack); (r = (char*)php_memnstr(p, needle, needle_len, end)); p = r + needle_len) {
3241 				e = zend_mempcpy(e, p, r - p);
3242 				e = zend_mempcpy(e, str, str_len);
3243 				(*replace_count)++;
3244 			}
3245 
3246 			if (p < end) {
3247 				e = zend_mempcpy(e, p, end - p);
3248 			}
3249 
3250 			*e = '\0';
3251 			return new_str;
3252 		}
3253 	} else if (needle_len > ZSTR_LEN(haystack) || memcmp(ZSTR_VAL(haystack), needle, ZSTR_LEN(haystack))) {
3254 nothing_todo:
3255 		return zend_string_copy(haystack);
3256 	} else {
3257 		(*replace_count)++;
3258 		return zend_string_init_fast(str, str_len);
3259 	}
3260 }
3261 /* }}} */
3262 
3263 /* {{{ php_str_to_str_i_ex */
3264 static zend_string *php_str_to_str_i_ex(zend_string *haystack, const char *lc_haystack,
3265 	zend_string *needle, const char *str, size_t str_len, zend_long *replace_count)
3266 {
3267 	zend_string *new_str = NULL;
3268 	zend_string *lc_needle;
3269 
3270 	if (ZSTR_LEN(needle) < ZSTR_LEN(haystack)) {
3271 		const char *end;
3272 		const char *p, *r;
3273 		char *e;
3274 
3275 		if (ZSTR_LEN(needle) == str_len) {
3276 			lc_needle = zend_string_tolower(needle);
3277 			end = lc_haystack + ZSTR_LEN(haystack);
3278 			for (p = lc_haystack; (r = (char*)php_memnstr(p, ZSTR_VAL(lc_needle), ZSTR_LEN(lc_needle), end)); p = r + ZSTR_LEN(lc_needle)) {
3279 				if (!new_str) {
3280 					new_str = zend_string_init(ZSTR_VAL(haystack), ZSTR_LEN(haystack), 0);
3281 				}
3282 				memcpy(ZSTR_VAL(new_str) + (r - lc_haystack), str, str_len);
3283 				(*replace_count)++;
3284 			}
3285 			zend_string_release_ex(lc_needle, 0);
3286 
3287 			if (!new_str) {
3288 				goto nothing_todo;
3289 			}
3290 			return new_str;
3291 		} else {
3292 			size_t count = 0;
3293 			const char *o = lc_haystack;
3294 			const char *n;
3295 			const char *endp = o + ZSTR_LEN(haystack);
3296 
3297 			lc_needle = zend_string_tolower(needle);
3298 			n = ZSTR_VAL(lc_needle);
3299 
3300 			while ((o = (char*)php_memnstr(o, n, ZSTR_LEN(lc_needle), endp))) {
3301 				o += ZSTR_LEN(lc_needle);
3302 				count++;
3303 			}
3304 			if (count == 0) {
3305 				/* Needle doesn't occur, shortcircuit the actual replacement. */
3306 				zend_string_release_ex(lc_needle, 0);
3307 				goto nothing_todo;
3308 			}
3309 
3310 			if (str_len > ZSTR_LEN(lc_needle)) {
3311 				new_str = zend_string_safe_alloc(count, str_len - ZSTR_LEN(lc_needle), ZSTR_LEN(haystack), 0);
3312 			} else {
3313 				new_str = zend_string_alloc(count * (str_len - ZSTR_LEN(lc_needle)) + ZSTR_LEN(haystack), 0);
3314 			}
3315 
3316 			e = ZSTR_VAL(new_str);
3317 			end = lc_haystack + ZSTR_LEN(haystack);
3318 
3319 			for (p = lc_haystack; (r = (char*)php_memnstr(p, ZSTR_VAL(lc_needle), ZSTR_LEN(lc_needle), end)); p = r + ZSTR_LEN(lc_needle)) {
3320 				e = zend_mempcpy(e, ZSTR_VAL(haystack) + (p - lc_haystack), r - p);
3321 				e = zend_mempcpy(e, str, str_len);
3322 				(*replace_count)++;
3323 			}
3324 
3325 			if (p < end) {
3326 				e = zend_mempcpy(e, ZSTR_VAL(haystack) + (p - lc_haystack), end - p);
3327 			}
3328 			*e = '\0';
3329 
3330 			zend_string_release_ex(lc_needle, 0);
3331 
3332 			return new_str;
3333 		}
3334 	} else if (ZSTR_LEN(needle) > ZSTR_LEN(haystack)) {
3335 nothing_todo:
3336 		return zend_string_copy(haystack);
3337 	} else {
3338 		lc_needle = zend_string_tolower(needle);
3339 
3340 		if (memcmp(lc_haystack, ZSTR_VAL(lc_needle), ZSTR_LEN(lc_needle))) {
3341 			zend_string_release_ex(lc_needle, 0);
3342 			goto nothing_todo;
3343 		}
3344 		zend_string_release_ex(lc_needle, 0);
3345 
3346 		new_str = zend_string_init(str, str_len, 0);
3347 
3348 		(*replace_count)++;
3349 		return new_str;
3350 	}
3351 }
3352 /* }}} */
3353 
3354 /* {{{ php_str_to_str */
3355 PHPAPI zend_string *php_str_to_str(const char *haystack, size_t length, const char *needle, size_t needle_len, const char *str, size_t str_len)
3356 {
3357 	zend_string *new_str;
3358 
3359 	if (needle_len < length) {
3360 		const char *end;
3361 		const char *s, *p;
3362 		char *e, *r;
3363 
3364 		if (needle_len == str_len) {
3365 			new_str = zend_string_init(haystack, length, 0);
3366 			end = ZSTR_VAL(new_str) + length;
3367 			for (p = ZSTR_VAL(new_str); (r = (char*)php_memnstr(p, needle, needle_len, end)); p = r + needle_len) {
3368 				memcpy(r, str, str_len);
3369 			}
3370 			return new_str;
3371 		} else {
3372 			if (str_len < needle_len) {
3373 				new_str = zend_string_alloc(length, 0);
3374 			} else {
3375 				size_t count = 0;
3376 				const char *o = haystack;
3377 				const char *n = needle;
3378 				const char *endp = o + length;
3379 
3380 				while ((o = (char*)php_memnstr(o, n, needle_len, endp))) {
3381 					o += needle_len;
3382 					count++;
3383 				}
3384 				if (