xref: /php-src/ext/standard/string.c (revision 93e0f6b4)
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 /* {{{ Removes trailing whitespace */
PHP_FUNCTION(rtrim)662 PHP_FUNCTION(rtrim)
663 {
664 	php_do_trim(INTERNAL_FUNCTION_PARAM_PASSTHRU, 2);
665 }
666 /* }}} */
667 
668 /* {{{ Strips whitespace from the beginning of a string */
PHP_FUNCTION(ltrim)669 PHP_FUNCTION(ltrim)
670 {
671 	php_do_trim(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
672 }
673 /* }}} */
674 
675 /* {{{ Wraps buffer to selected number of characters using string break char */
PHP_FUNCTION(wordwrap)676 PHP_FUNCTION(wordwrap)
677 {
678 	zend_string *text;
679 	char *breakchar = "\n";
680 	size_t newtextlen, chk, breakchar_len = 1;
681 	size_t alloced;
682 	zend_long current = 0, laststart = 0, lastspace = 0;
683 	zend_long linelength = 75;
684 	bool docut = 0;
685 	zend_string *newtext;
686 
687 	ZEND_PARSE_PARAMETERS_START(1, 4)
688 		Z_PARAM_STR(text)
689 		Z_PARAM_OPTIONAL
690 		Z_PARAM_LONG(linelength)
691 		Z_PARAM_STRING(breakchar, breakchar_len)
692 		Z_PARAM_BOOL(docut)
693 	ZEND_PARSE_PARAMETERS_END();
694 
695 	if (ZSTR_LEN(text) == 0) {
696 		RETURN_EMPTY_STRING();
697 	}
698 
699 	if (breakchar_len == 0) {
700 		zend_argument_value_error(3, "cannot be empty");
701 		RETURN_THROWS();
702 	}
703 
704 	if (linelength == 0 && docut) {
705 		zend_argument_value_error(4, "cannot be true when argument #2 ($width) is 0");
706 		RETURN_THROWS();
707 	}
708 
709 	/* Special case for a single-character break as it needs no
710 	   additional storage space */
711 	if (breakchar_len == 1 && !docut) {
712 		newtext = zend_string_init(ZSTR_VAL(text), ZSTR_LEN(text), 0);
713 
714 		laststart = lastspace = 0;
715 		for (current = 0; current < (zend_long)ZSTR_LEN(text); current++) {
716 			if (ZSTR_VAL(text)[current] == breakchar[0]) {
717 				laststart = lastspace = current + 1;
718 			} else if (ZSTR_VAL(text)[current] == ' ') {
719 				if (current - laststart >= linelength) {
720 					ZSTR_VAL(newtext)[current] = breakchar[0];
721 					laststart = current + 1;
722 				}
723 				lastspace = current;
724 			} else if (current - laststart >= linelength && laststart != lastspace) {
725 				ZSTR_VAL(newtext)[lastspace] = breakchar[0];
726 				laststart = lastspace + 1;
727 			}
728 		}
729 
730 		RETURN_NEW_STR(newtext);
731 	} else {
732 		/* Multiple character line break or forced cut */
733 		if (linelength > 0) {
734 			chk = (size_t)(ZSTR_LEN(text)/linelength + 1);
735 			newtext = zend_string_safe_alloc(chk, breakchar_len, ZSTR_LEN(text), 0);
736 			alloced = ZSTR_LEN(text) + chk * breakchar_len + 1;
737 		} else {
738 			chk = ZSTR_LEN(text);
739 			alloced = ZSTR_LEN(text) * (breakchar_len + 1) + 1;
740 			newtext = zend_string_safe_alloc(ZSTR_LEN(text), breakchar_len + 1, 0, 0);
741 		}
742 
743 		/* now keep track of the actual new text length */
744 		newtextlen = 0;
745 
746 		laststart = lastspace = 0;
747 		for (current = 0; current < (zend_long)ZSTR_LEN(text); current++) {
748 			if (chk == 0) {
749 				alloced += (size_t) (((ZSTR_LEN(text) - current + 1)/linelength + 1) * breakchar_len) + 1;
750 				newtext = zend_string_extend(newtext, alloced, 0);
751 				chk = (size_t) ((ZSTR_LEN(text) - current)/linelength) + 1;
752 			}
753 			/* when we hit an existing break, copy to new buffer, and
754 			 * fix up laststart and lastspace */
755 			if (ZSTR_VAL(text)[current] == breakchar[0]
756 				&& current + breakchar_len < ZSTR_LEN(text)
757 				&& !strncmp(ZSTR_VAL(text) + current, breakchar, breakchar_len)) {
758 				memcpy(ZSTR_VAL(newtext) + newtextlen, ZSTR_VAL(text) + laststart, current - laststart + breakchar_len);
759 				newtextlen += current - laststart + breakchar_len;
760 				current += breakchar_len - 1;
761 				laststart = lastspace = current + 1;
762 				chk--;
763 			}
764 			/* if it is a space, check if it is at the line boundary,
765 			 * copy and insert a break, or just keep track of it */
766 			else if (ZSTR_VAL(text)[current] == ' ') {
767 				if (current - laststart >= linelength) {
768 					memcpy(ZSTR_VAL(newtext) + newtextlen, ZSTR_VAL(text) + laststart, current - laststart);
769 					newtextlen += current - laststart;
770 					memcpy(ZSTR_VAL(newtext) + newtextlen, breakchar, breakchar_len);
771 					newtextlen += breakchar_len;
772 					laststart = current + 1;
773 					chk--;
774 				}
775 				lastspace = current;
776 			}
777 			/* if we are cutting, and we've accumulated enough
778 			 * characters, and we haven't see a space for this line,
779 			 * copy and insert a break. */
780 			else if (current - laststart >= linelength
781 					&& docut && laststart >= lastspace) {
782 				memcpy(ZSTR_VAL(newtext) + newtextlen, ZSTR_VAL(text) + laststart, current - laststart);
783 				newtextlen += current - laststart;
784 				memcpy(ZSTR_VAL(newtext) + newtextlen, breakchar, breakchar_len);
785 				newtextlen += breakchar_len;
786 				laststart = lastspace = current;
787 				chk--;
788 			}
789 			/* if the current word puts us over the linelength, copy
790 			 * back up until the last space, insert a break, and move
791 			 * up the laststart */
792 			else if (current - laststart >= linelength
793 					&& laststart < lastspace) {
794 				memcpy(ZSTR_VAL(newtext) + newtextlen, ZSTR_VAL(text) + laststart, lastspace - laststart);
795 				newtextlen += lastspace - laststart;
796 				memcpy(ZSTR_VAL(newtext) + newtextlen, breakchar, breakchar_len);
797 				newtextlen += breakchar_len;
798 				laststart = lastspace = lastspace + 1;
799 				chk--;
800 			}
801 		}
802 
803 		/* copy over any stragglers */
804 		if (laststart != current) {
805 			memcpy(ZSTR_VAL(newtext) + newtextlen, ZSTR_VAL(text) + laststart, current - laststart);
806 			newtextlen += current - laststart;
807 		}
808 
809 		ZSTR_VAL(newtext)[newtextlen] = '\0';
810 		/* free unused memory */
811 		newtext = zend_string_truncate(newtext, newtextlen, 0);
812 
813 		RETURN_NEW_STR(newtext);
814 	}
815 }
816 /* }}} */
817 
818 /* {{{ php_explode */
php_explode(const zend_string * delim,zend_string * str,zval * return_value,zend_long limit)819 PHPAPI void php_explode(const zend_string *delim, zend_string *str, zval *return_value, zend_long limit)
820 {
821 	const char *p1 = ZSTR_VAL(str);
822 	const char *endp = ZSTR_VAL(str) + ZSTR_LEN(str);
823 	const char *p2 = php_memnstr(ZSTR_VAL(str), ZSTR_VAL(delim), ZSTR_LEN(delim), endp);
824 	zval  tmp;
825 
826 	if (p2 == NULL) {
827 		ZVAL_STR_COPY(&tmp, str);
828 		zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &tmp);
829 	} else {
830 		zend_hash_real_init_packed(Z_ARRVAL_P(return_value));
831 		ZEND_HASH_FILL_PACKED(Z_ARRVAL_P(return_value)) {
832 			do {
833 				ZEND_HASH_FILL_GROW();
834 				ZEND_HASH_FILL_SET_STR(zend_string_init_fast(p1, p2 - p1));
835 				ZEND_HASH_FILL_NEXT();
836 				p1 = p2 + ZSTR_LEN(delim);
837 				p2 = php_memnstr(p1, ZSTR_VAL(delim), ZSTR_LEN(delim), endp);
838 			} while (p2 != NULL && --limit > 1);
839 
840 			if (p1 <= endp) {
841 				ZEND_HASH_FILL_GROW();
842 				ZEND_HASH_FILL_SET_STR(zend_string_init_fast(p1, endp - p1));
843 				ZEND_HASH_FILL_NEXT();
844 			}
845 		} ZEND_HASH_FILL_END();
846 	}
847 }
848 /* }}} */
849 
850 /* {{{ php_explode_negative_limit */
php_explode_negative_limit(const zend_string * delim,zend_string * str,zval * return_value,zend_long limit)851 PHPAPI void php_explode_negative_limit(const zend_string *delim, zend_string *str, zval *return_value, zend_long limit)
852 {
853 #define EXPLODE_ALLOC_STEP 64
854 	const char *p1 = ZSTR_VAL(str);
855 	const char *endp = ZSTR_VAL(str) + ZSTR_LEN(str);
856 	const char *p2 = php_memnstr(ZSTR_VAL(str), ZSTR_VAL(delim), ZSTR_LEN(delim), endp);
857 	zval  tmp;
858 
859 	if (p2 == NULL) {
860 		/*
861 		do nothing since limit <= -1, thus if only one chunk - 1 + (limit) <= 0
862 		by doing nothing we return empty array
863 		*/
864 	} else {
865 		size_t allocated = EXPLODE_ALLOC_STEP, found = 0;
866 		zend_long i, to_return;
867 		const char **positions = emalloc(allocated * sizeof(char *));
868 
869 		positions[found++] = p1;
870 		do {
871 			if (found >= allocated) {
872 				allocated = found + EXPLODE_ALLOC_STEP;/* make sure we have enough memory */
873 				positions = erealloc(ZEND_VOIDP(positions), allocated*sizeof(char *));
874 			}
875 			positions[found++] = p1 = p2 + ZSTR_LEN(delim);
876 			p2 = php_memnstr(p1, ZSTR_VAL(delim), ZSTR_LEN(delim), endp);
877 		} while (p2 != NULL);
878 
879 		to_return = limit + found;
880 		/* limit is at least -1 therefore no need of bounds checking : i will be always less than found */
881 		for (i = 0; i < to_return; i++) { /* this checks also for to_return > 0 */
882 			ZVAL_STRINGL(&tmp, positions[i], (positions[i+1] - ZSTR_LEN(delim)) - positions[i]);
883 			zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &tmp);
884 		}
885 		efree((void *)positions);
886 	}
887 #undef EXPLODE_ALLOC_STEP
888 }
889 /* }}} */
890 
891 /* {{{ 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)892 PHP_FUNCTION(explode)
893 {
894 	zend_string *str, *delim;
895 	zend_long limit = ZEND_LONG_MAX; /* No limit */
896 	zval tmp;
897 
898 	ZEND_PARSE_PARAMETERS_START(2, 3)
899 		Z_PARAM_STR(delim)
900 		Z_PARAM_STR(str)
901 		Z_PARAM_OPTIONAL
902 		Z_PARAM_LONG(limit)
903 	ZEND_PARSE_PARAMETERS_END();
904 
905 	if (ZSTR_LEN(delim) == 0) {
906 		zend_argument_value_error(1, "cannot be empty");
907 		RETURN_THROWS();
908 	}
909 
910 	array_init(return_value);
911 
912 	if (ZSTR_LEN(str) == 0) {
913 		if (limit >= 0) {
914 			ZVAL_EMPTY_STRING(&tmp);
915 			zend_hash_index_add_new(Z_ARRVAL_P(return_value), 0, &tmp);
916 		}
917 		return;
918 	}
919 
920 	if (limit > 1) {
921 		php_explode(delim, str, return_value, limit);
922 	} else if (limit < 0) {
923 		php_explode_negative_limit(delim, str, return_value, limit);
924 	} else {
925 		ZVAL_STR_COPY(&tmp, str);
926 		zend_hash_index_add_new(Z_ARRVAL_P(return_value), 0, &tmp);
927 	}
928 }
929 /* }}} */
930 
931 /* {{{ php_implode */
php_implode(const zend_string * glue,HashTable * pieces,zval * return_value)932 PHPAPI void php_implode(const zend_string *glue, HashTable *pieces, zval *return_value)
933 {
934 	zval         *tmp;
935 	uint32_t      numelems;
936 	zend_string  *str;
937 	char         *cptr;
938 	size_t        len = 0;
939 	struct {
940 		zend_string *str;
941 		zend_long    lval;
942 	} *strings, *ptr;
943 	ALLOCA_FLAG(use_heap)
944 
945 	numelems = zend_hash_num_elements(pieces);
946 
947 	if (numelems == 0) {
948 		RETURN_EMPTY_STRING();
949 	} else if (numelems == 1) {
950 		/* loop to search the first not undefined element... */
951 		ZEND_HASH_FOREACH_VAL(pieces, tmp) {
952 			RETURN_STR(zval_get_string(tmp));
953 		} ZEND_HASH_FOREACH_END();
954 	}
955 
956 	ptr = strings = do_alloca((sizeof(*strings)) * numelems, use_heap);
957 
958 	uint32_t flags = ZSTR_GET_COPYABLE_CONCAT_PROPERTIES(glue);
959 
960 	ZEND_HASH_FOREACH_VAL(pieces, tmp) {
961 		if (EXPECTED(Z_TYPE_P(tmp) == IS_STRING)) {
962 			ptr->str = Z_STR_P(tmp);
963 			len += ZSTR_LEN(ptr->str);
964 			ptr->lval = 0;
965 			flags &= ZSTR_GET_COPYABLE_CONCAT_PROPERTIES(ptr->str);
966 			ptr++;
967 		} else if (UNEXPECTED(Z_TYPE_P(tmp) == IS_LONG)) {
968 			zend_long val = Z_LVAL_P(tmp);
969 
970 			ptr->str = NULL;
971 			ptr->lval = val;
972 			ptr++;
973 			if (val <= 0) {
974 				len++;
975 			}
976 			while (val) {
977 				val /= 10;
978 				len++;
979 			}
980 		} else {
981 			ptr->str = zval_get_string_func(tmp);
982 			len += ZSTR_LEN(ptr->str);
983 			ptr->lval = 1;
984 			flags &= ZSTR_GET_COPYABLE_CONCAT_PROPERTIES(ptr->str);
985 			ptr++;
986 		}
987 	} ZEND_HASH_FOREACH_END();
988 
989 	/* numelems cannot be 0, we checked above */
990 	str = zend_string_safe_alloc(numelems - 1, ZSTR_LEN(glue), len, 0);
991 	GC_ADD_FLAGS(str, flags);
992 	cptr = ZSTR_VAL(str) + ZSTR_LEN(str);
993 	*cptr = 0;
994 
995 	while (1) {
996 		ptr--;
997 		if (EXPECTED(ptr->str)) {
998 			cptr -= ZSTR_LEN(ptr->str);
999 			memcpy(cptr, ZSTR_VAL(ptr->str), ZSTR_LEN(ptr->str));
1000 			if (ptr->lval) {
1001 				zend_string_release_ex(ptr->str, 0);
1002 			}
1003 		} else {
1004 			char *oldPtr = cptr;
1005 			char oldVal = *cptr;
1006 			cptr = zend_print_long_to_buf(cptr, ptr->lval);
1007 			*oldPtr = oldVal;
1008 		}
1009 
1010 		if (ptr == strings) {
1011 			break;
1012 		}
1013 
1014 		cptr -= ZSTR_LEN(glue);
1015 		memcpy(cptr, ZSTR_VAL(glue), ZSTR_LEN(glue));
1016 	}
1017 
1018 	free_alloca(strings, use_heap);
1019 	RETURN_NEW_STR(str);
1020 }
1021 /* }}} */
1022 
1023 /* {{{ Joins array elements placing glue string between items and return one string */
PHP_FUNCTION(implode)1024 PHP_FUNCTION(implode)
1025 {
1026 	zend_string *arg1_str = NULL;
1027 	HashTable *arg1_array = NULL;
1028 	zend_array *pieces = NULL;
1029 
1030 	ZEND_PARSE_PARAMETERS_START(1, 2)
1031 		Z_PARAM_ARRAY_HT_OR_STR(arg1_array, arg1_str)
1032 		Z_PARAM_OPTIONAL
1033 		Z_PARAM_ARRAY_HT_OR_NULL(pieces)
1034 	ZEND_PARSE_PARAMETERS_END();
1035 
1036 	if (pieces == NULL) {
1037 		if (arg1_array == NULL) {
1038 			zend_type_error("%s(): Argument #1 ($pieces) must be of type array, string given", get_active_function_name());
1039 			RETURN_THROWS();
1040 		}
1041 
1042 		arg1_str = ZSTR_EMPTY_ALLOC();
1043 		pieces = arg1_array;
1044 	} else {
1045 		if (arg1_str == NULL) {
1046 			zend_argument_type_error(1, "must be of type string, array given");
1047 			RETURN_THROWS();
1048 		}
1049 	}
1050 
1051 	php_implode(arg1_str, pieces, return_value);
1052 }
1053 /* }}} */
1054 
1055 #define STRTOK_TABLE(p) BG(strtok_table)[(unsigned char) *p]
1056 
1057 /* {{{ Tokenize a string */
PHP_FUNCTION(strtok)1058 PHP_FUNCTION(strtok)
1059 {
1060 	zend_string *str, *tok = NULL;
1061 	char *token;
1062 	char *token_end;
1063 	char *p;
1064 	char *pe;
1065 	size_t skipped = 0;
1066 
1067 	ZEND_PARSE_PARAMETERS_START(1, 2)
1068 		Z_PARAM_STR(str)
1069 		Z_PARAM_OPTIONAL
1070 		Z_PARAM_STR_OR_NULL(tok)
1071 	ZEND_PARSE_PARAMETERS_END();
1072 
1073 	if (!tok) {
1074 		tok = str;
1075 	} else {
1076 		if (BG(strtok_string)) {
1077 			zend_string_release(BG(strtok_string));
1078 		}
1079 		BG(strtok_string) = zend_string_copy(str);
1080 		BG(strtok_last) = ZSTR_VAL(str);
1081 		BG(strtok_len) = ZSTR_LEN(str);
1082 	}
1083 
1084 	if (!BG(strtok_string)) {
1085 		/* String to tokenize not set. */
1086 		php_error_docref(NULL, E_WARNING, "Both arguments must be provided when starting tokenization");
1087 		RETURN_FALSE;
1088 	}
1089 
1090 	p = BG(strtok_last); /* Where we start to search */
1091 	pe = ZSTR_VAL(BG(strtok_string)) + BG(strtok_len);
1092 	if (p >= pe) {
1093 		/* Reached the end of the string. */
1094 		RETURN_FALSE;
1095 	}
1096 
1097 	token = ZSTR_VAL(tok);
1098 	token_end = token + ZSTR_LEN(tok);
1099 
1100 	while (token < token_end) {
1101 		STRTOK_TABLE(token++) = 1;
1102 	}
1103 
1104 	/* Skip leading delimiters */
1105 	while (STRTOK_TABLE(p)) {
1106 		if (++p >= pe) {
1107 			/* no other chars left */
1108 			goto return_false;
1109 		}
1110 		skipped++;
1111 	}
1112 
1113 	/* We know at this place that *p is no delimiter, so skip it */
1114 	while (++p < pe) {
1115 		if (STRTOK_TABLE(p)) {
1116 			goto return_token;
1117 		}
1118 	}
1119 
1120 	if (p - BG(strtok_last)) {
1121 return_token:
1122 		RETVAL_STRINGL(BG(strtok_last) + skipped, (p - BG(strtok_last)) - skipped);
1123 		BG(strtok_last) = p + 1;
1124 	} else {
1125 return_false:
1126 		RETVAL_FALSE;
1127 		zend_string_release(BG(strtok_string));
1128 		BG(strtok_string) = NULL;
1129 	}
1130 
1131 	/* Restore table -- usually faster then memset'ing the table on every invocation */
1132 	token = ZSTR_VAL(tok);
1133 	while (token < token_end) {
1134 		STRTOK_TABLE(token++) = 0;
1135 	}
1136 }
1137 /* }}} */
1138 
1139 /* {{{ php_strtoupper */
php_strtoupper(char * s,size_t len)1140 PHPAPI char *php_strtoupper(char *s, size_t len)
1141 {
1142 	zend_str_toupper(s, len);
1143 	return s;
1144 }
1145 /* }}} */
1146 
1147 /* {{{ php_string_toupper */
php_string_toupper(zend_string * s)1148 PHPAPI zend_string *php_string_toupper(zend_string *s)
1149 {
1150 	return zend_string_toupper(s);
1151 }
1152 /* }}} */
1153 
1154 /* {{{ Makes a string uppercase */
PHP_FUNCTION(strtoupper)1155 PHP_FUNCTION(strtoupper)
1156 {
1157 	zend_string *arg;
1158 
1159 	ZEND_PARSE_PARAMETERS_START(1, 1)
1160 		Z_PARAM_STR(arg)
1161 	ZEND_PARSE_PARAMETERS_END();
1162 
1163 	RETURN_STR(zend_string_toupper(arg));
1164 }
1165 /* }}} */
1166 
1167 /* {{{ php_strtolower */
php_strtolower(char * s,size_t len)1168 PHPAPI char *php_strtolower(char *s, size_t len)
1169 {
1170 	zend_str_tolower(s, len);
1171 	return s;
1172 }
1173 /* }}} */
1174 
1175 /* {{{ php_string_tolower */
php_string_tolower(zend_string * s)1176 PHPAPI zend_string *php_string_tolower(zend_string *s)
1177 {
1178 	return zend_string_tolower(s);
1179 }
1180 /* }}} */
1181 
1182 /* {{{ Makes a string lowercase */
PHP_FUNCTION(strtolower)1183 PHP_FUNCTION(strtolower)
1184 {
1185 	zend_string *str;
1186 
1187 	ZEND_PARSE_PARAMETERS_START(1, 1)
1188 		Z_PARAM_STR(str)
1189 	ZEND_PARSE_PARAMETERS_END();
1190 
1191 	RETURN_STR(zend_string_tolower(str));
1192 }
1193 /* }}} */
1194 
1195 #if defined(PHP_WIN32)
_is_basename_start(const char * start,const char * pos)1196 static bool _is_basename_start(const char *start, const char *pos)
1197 {
1198 	if (pos - start >= 1
1199 	    && *(pos-1) != '/'
1200 	    && *(pos-1) != '\\') {
1201 		if (pos - start == 1) {
1202 			return 1;
1203 		} else if (*(pos-2) == '/' || *(pos-2) == '\\') {
1204 			return 1;
1205 		} else if (*(pos-2) == ':'
1206 			&& _is_basename_start(start, pos - 2)) {
1207 			return 1;
1208 		}
1209 	}
1210 	return 0;
1211 }
1212 #endif
1213 
1214 /* {{{ php_basename */
php_basename(const char * s,size_t len,const char * suffix,size_t suffix_len)1215 PHPAPI zend_string *php_basename(const char *s, size_t len, const char *suffix, size_t suffix_len)
1216 {
1217 	const char *basename_start;
1218 	const char *basename_end;
1219 
1220 	if (CG(ascii_compatible_locale)) {
1221 		basename_end = s + len - 1;
1222 
1223 		/* Strip trailing slashes */
1224 		while (basename_end >= s
1225 #ifdef PHP_WIN32
1226 			&& (*basename_end == '/'
1227 				|| *basename_end == '\\'
1228 				|| (*basename_end == ':'
1229 					&& _is_basename_start(s, basename_end)))) {
1230 #else
1231 			&& *basename_end == '/') {
1232 #endif
1233 			basename_end--;
1234 		}
1235 		if (basename_end < s) {
1236 			return ZSTR_EMPTY_ALLOC();
1237 		}
1238 
1239 		/* Extract filename */
1240 		basename_start = basename_end;
1241 		basename_end++;
1242 		while (basename_start > s
1243 #ifdef PHP_WIN32
1244 			&& *(basename_start-1) != '/'
1245 			&& *(basename_start-1) != '\\') {
1246 
1247 			if (*(basename_start-1) == ':' &&
1248 				_is_basename_start(s, basename_start - 1)) {
1249 				break;
1250 			}
1251 #else
1252 			&& *(basename_start-1) != '/') {
1253 #endif
1254 			basename_start--;
1255 		}
1256 	} else {
1257 		/* State 0 is directly after a directory separator (or at the start of the string).
1258 		 * State 1 is everything else. */
1259 		int state = 0;
1260 
1261 		basename_start = s;
1262 		basename_end = s;
1263 		while (len > 0) {
1264 			int inc_len = (*s == '\0' ? 1 : php_mblen(s, len));
1265 
1266 			switch (inc_len) {
1267 				case 0:
1268 					goto quit_loop;
1269 				case 1:
1270 #ifdef PHP_WIN32
1271 					if (*s == '/' || *s == '\\') {
1272 #else
1273 					if (*s == '/') {
1274 #endif
1275 						if (state == 1) {
1276 							state = 0;
1277 							basename_end = s;
1278 						}
1279 #ifdef PHP_WIN32
1280 					/* Catch relative paths in c:file.txt style. They're not to confuse
1281 					   with the NTFS streams. This part ensures also, that no drive
1282 					   letter traversing happens. */
1283 					} else if ((*s == ':' && (s - basename_start == 1))) {
1284 						if (state == 0) {
1285 							basename_start = s;
1286 							state = 1;
1287 						} else {
1288 							basename_end = s;
1289 							state = 0;
1290 						}
1291 #endif
1292 					} else {
1293 						if (state == 0) {
1294 							basename_start = s;
1295 							state = 1;
1296 						}
1297 					}
1298 					break;
1299 				default:
1300 					if (inc_len < 0) {
1301 						/* If character is invalid, treat it like other non-significant characters. */
1302 						inc_len = 1;
1303 						php_mb_reset();
1304 					}
1305 					if (state == 0) {
1306 						basename_start = s;
1307 						state = 1;
1308 					}
1309 					break;
1310 			}
1311 			s += inc_len;
1312 			len -= inc_len;
1313 		}
1314 
1315 quit_loop:
1316 		if (state == 1) {
1317 			basename_end = s;
1318 		}
1319 	}
1320 
1321 	if (suffix != NULL && suffix_len < (size_t)(basename_end - basename_start) &&
1322 			memcmp(basename_end - suffix_len, suffix, suffix_len) == 0) {
1323 		basename_end -= suffix_len;
1324 	}
1325 
1326 	return zend_string_init(basename_start, basename_end - basename_start, 0);
1327 }
1328 /* }}} */
1329 
1330 /* {{{ Returns the filename component of the path */
1331 PHP_FUNCTION(basename)
1332 {
1333 	char *string, *suffix = NULL;
1334 	size_t   string_len, suffix_len = 0;
1335 
1336 	ZEND_PARSE_PARAMETERS_START(1, 2)
1337 		Z_PARAM_STRING(string, string_len)
1338 		Z_PARAM_OPTIONAL
1339 		Z_PARAM_STRING(suffix, suffix_len)
1340 	ZEND_PARSE_PARAMETERS_END();
1341 
1342 	RETURN_STR(php_basename(string, string_len, suffix, suffix_len));
1343 }
1344 /* }}} */
1345 
1346 /* {{{ php_dirname
1347    Returns directory name component of path */
1348 PHPAPI size_t php_dirname(char *path, size_t len)
1349 {
1350 	return zend_dirname(path, len);
1351 }
1352 /* }}} */
1353 
1354 /* {{{ Returns the directory name component of the path */
1355 PHP_FUNCTION(dirname)
1356 {
1357 	char *str;
1358 	size_t str_len;
1359 	zend_string *ret;
1360 	zend_long levels = 1;
1361 
1362 	ZEND_PARSE_PARAMETERS_START(1, 2)
1363 		Z_PARAM_STRING(str, str_len)
1364 		Z_PARAM_OPTIONAL
1365 		Z_PARAM_LONG(levels)
1366 	ZEND_PARSE_PARAMETERS_END();
1367 
1368 	ret = zend_string_init(str, str_len, 0);
1369 
1370 	if (levels == 1) {
1371 		/* Default case */
1372 #ifdef PHP_WIN32
1373 		ZSTR_LEN(ret) = php_win32_ioutil_dirname(ZSTR_VAL(ret), str_len);
1374 #else
1375 		ZSTR_LEN(ret) = zend_dirname(ZSTR_VAL(ret), str_len);
1376 #endif
1377 	} else if (levels < 1) {
1378 		zend_argument_value_error(2, "must be greater than or equal to 1");
1379 		zend_string_efree(ret);
1380 		RETURN_THROWS();
1381 	} else {
1382 		/* Some levels up */
1383 		do {
1384 #ifdef PHP_WIN32
1385 			ZSTR_LEN(ret) = php_win32_ioutil_dirname(ZSTR_VAL(ret), str_len = ZSTR_LEN(ret));
1386 #else
1387 			ZSTR_LEN(ret) = zend_dirname(ZSTR_VAL(ret), str_len = ZSTR_LEN(ret));
1388 #endif
1389 		} while (ZSTR_LEN(ret) < str_len && --levels);
1390 	}
1391 
1392 	RETURN_NEW_STR(ret);
1393 }
1394 /* }}} */
1395 
1396 /* {{{ Returns information about a certain string */
1397 PHP_FUNCTION(pathinfo)
1398 {
1399 	zval tmp;
1400 	char *path, *dirname;
1401 	size_t path_len;
1402 	bool have_basename;
1403 	zend_long opt = PHP_PATHINFO_ALL;
1404 	zend_string *ret = NULL;
1405 
1406 	ZEND_PARSE_PARAMETERS_START(1, 2)
1407 		Z_PARAM_STRING(path, path_len)
1408 		Z_PARAM_OPTIONAL
1409 		Z_PARAM_LONG(opt)
1410 	ZEND_PARSE_PARAMETERS_END();
1411 
1412 	have_basename = ((opt & PHP_PATHINFO_BASENAME) == PHP_PATHINFO_BASENAME);
1413 
1414 	array_init(&tmp);
1415 
1416 	if ((opt & PHP_PATHINFO_DIRNAME) == PHP_PATHINFO_DIRNAME) {
1417 		dirname = estrndup(path, path_len);
1418 		php_dirname(dirname, path_len);
1419 		if (*dirname) {
1420 			add_assoc_string(&tmp, "dirname", dirname);
1421 		}
1422 		efree(dirname);
1423 	}
1424 
1425 	if (have_basename) {
1426 		ret = php_basename(path, path_len, NULL, 0);
1427 		add_assoc_str(&tmp, "basename", zend_string_copy(ret));
1428 	}
1429 
1430 	if ((opt & PHP_PATHINFO_EXTENSION) == PHP_PATHINFO_EXTENSION) {
1431 		const char *p;
1432 		ptrdiff_t idx;
1433 
1434 		if (!have_basename) {
1435 			ret = php_basename(path, path_len, NULL, 0);
1436 		}
1437 
1438 		p = zend_memrchr(ZSTR_VAL(ret), '.', ZSTR_LEN(ret));
1439 
1440 		if (p) {
1441 			idx = p - ZSTR_VAL(ret);
1442 			add_assoc_stringl(&tmp, "extension", ZSTR_VAL(ret) + idx + 1, ZSTR_LEN(ret) - idx - 1);
1443 		}
1444 	}
1445 
1446 	if ((opt & PHP_PATHINFO_FILENAME) == PHP_PATHINFO_FILENAME) {
1447 		const char *p;
1448 		ptrdiff_t idx;
1449 
1450 		/* Have we already looked up the basename? */
1451 		if (!have_basename && !ret) {
1452 			ret = php_basename(path, path_len, NULL, 0);
1453 		}
1454 
1455 		p = zend_memrchr(ZSTR_VAL(ret), '.', ZSTR_LEN(ret));
1456 
1457 		idx = p ? (p - ZSTR_VAL(ret)) : (ptrdiff_t)ZSTR_LEN(ret);
1458 		add_assoc_stringl(&tmp, "filename", ZSTR_VAL(ret), idx);
1459 	}
1460 
1461 	if (ret) {
1462 		zend_string_release_ex(ret, 0);
1463 	}
1464 
1465 	if (opt == PHP_PATHINFO_ALL) {
1466 		RETURN_COPY_VALUE(&tmp);
1467 	} else {
1468 		zval *element;
1469 		if ((element = zend_hash_get_current_data(Z_ARRVAL(tmp))) != NULL) {
1470 			RETVAL_COPY_DEREF(element);
1471 		} else {
1472 			RETVAL_EMPTY_STRING();
1473 		}
1474 		zval_ptr_dtor(&tmp);
1475 	}
1476 }
1477 /* }}} */
1478 
1479 /* {{{ php_stristr
1480    case insensitive strstr */
1481 PHPAPI char *php_stristr(char *s, char *t, size_t s_len, size_t t_len)
1482 {
1483 	return (char*)php_memnistr(s, t, t_len, s + s_len);
1484 }
1485 /* }}} */
1486 
1487 /* {{{ php_strspn */
1488 PHPAPI size_t php_strspn(const char *s1, const char *s2, const char *s1_end, const char *s2_end)
1489 {
1490 	const char *p = s1, *spanp;
1491 	char c = *p;
1492 
1493 cont:
1494 	for (spanp = s2; p != s1_end && spanp != s2_end;) {
1495 		if (*spanp++ == c) {
1496 			c = *(++p);
1497 			goto cont;
1498 		}
1499 	}
1500 	return (p - s1);
1501 }
1502 /* }}} */
1503 
1504 /* {{{ php_strcspn */
1505 PHPAPI size_t php_strcspn(const char *s1, const char *s2, const char *s1_end, const char *s2_end)
1506 {
1507 	const char *p, *spanp;
1508 	char c = *s1;
1509 
1510 	for (p = s1;;) {
1511 		spanp = s2;
1512 		do {
1513 			if (*spanp == c || p == s1_end) {
1514 				return p - s1;
1515 			}
1516 		} while (spanp++ < (s2_end - 1));
1517 		c = *++p;
1518 	}
1519 	/* NOTREACHED */
1520 }
1521 /* }}} */
1522 
1523 /* {{{ Finds first occurrence of a string within another, case insensitive */
1524 PHP_FUNCTION(stristr)
1525 {
1526 	zend_string *haystack, *needle;
1527 	const char *found = NULL;
1528 	size_t  found_offset;
1529 	bool part = 0;
1530 
1531 	ZEND_PARSE_PARAMETERS_START(2, 3)
1532 		Z_PARAM_STR(haystack)
1533 		Z_PARAM_STR(needle)
1534 		Z_PARAM_OPTIONAL
1535 		Z_PARAM_BOOL(part)
1536 	ZEND_PARSE_PARAMETERS_END();
1537 
1538 	found = php_stristr(ZSTR_VAL(haystack), ZSTR_VAL(needle), ZSTR_LEN(haystack), ZSTR_LEN(needle));
1539 
1540 	if (UNEXPECTED(!found)) {
1541 		RETURN_FALSE;
1542 	}
1543 	found_offset = found - ZSTR_VAL(haystack);
1544 	if (part) {
1545 		RETURN_STRINGL(ZSTR_VAL(haystack), found_offset);
1546 	}
1547 	RETURN_STRINGL(ZSTR_VAL(haystack) + found_offset, ZSTR_LEN(haystack) - found_offset);
1548 }
1549 /* }}} */
1550 
1551 /* {{{ Finds first occurrence of a string within another */
1552 PHP_FUNCTION(strstr)
1553 {
1554 	zend_string *haystack, *needle;
1555 	const char *found = NULL;
1556 	zend_long found_offset;
1557 	bool part = 0;
1558 
1559 	ZEND_PARSE_PARAMETERS_START(2, 3)
1560 		Z_PARAM_STR(haystack)
1561 		Z_PARAM_STR(needle)
1562 		Z_PARAM_OPTIONAL
1563 		Z_PARAM_BOOL(part)
1564 	ZEND_PARSE_PARAMETERS_END();
1565 
1566 	found = php_memnstr(ZSTR_VAL(haystack), ZSTR_VAL(needle), ZSTR_LEN(needle), ZSTR_VAL(haystack) + ZSTR_LEN(haystack));
1567 
1568 	if (UNEXPECTED(!found)) {
1569 		RETURN_FALSE;
1570 	}
1571 	found_offset = found - ZSTR_VAL(haystack);
1572 	if (part) {
1573 		RETURN_STRINGL(ZSTR_VAL(haystack), found_offset);
1574 	}
1575 	RETURN_STRINGL(ZSTR_VAL(haystack) + found_offset, ZSTR_LEN(haystack) - found_offset);
1576 }
1577 /* }}} */
1578 
1579 /* {{{ Checks if a string contains another */
1580 PHP_FUNCTION(str_contains)
1581 {
1582 	zend_string *haystack, *needle;
1583 
1584 	ZEND_PARSE_PARAMETERS_START(2, 2)
1585 		Z_PARAM_STR(haystack)
1586 		Z_PARAM_STR(needle)
1587 	ZEND_PARSE_PARAMETERS_END();
1588 
1589 	RETURN_BOOL(php_memnstr(ZSTR_VAL(haystack), ZSTR_VAL(needle), ZSTR_LEN(needle), ZSTR_VAL(haystack) + ZSTR_LEN(haystack)));
1590 }
1591 /* }}} */
1592 
1593 /* {{{ Checks if haystack starts with needle */
1594 PHP_FUNCTION(str_starts_with)
1595 {
1596 	zend_string *haystack, *needle;
1597 
1598 	ZEND_PARSE_PARAMETERS_START(2, 2)
1599 		Z_PARAM_STR(haystack)
1600 		Z_PARAM_STR(needle)
1601 	ZEND_PARSE_PARAMETERS_END();
1602 
1603 	RETURN_BOOL(zend_string_starts_with(haystack, needle));
1604 }
1605 /* }}} */
1606 
1607 /* {{{ Checks if haystack ends with needle */
1608 PHP_FUNCTION(str_ends_with)
1609 {
1610 	zend_string *haystack, *needle;
1611 
1612 	ZEND_PARSE_PARAMETERS_START(2, 2)
1613 		Z_PARAM_STR(haystack)
1614 		Z_PARAM_STR(needle)
1615 	ZEND_PARSE_PARAMETERS_END();
1616 
1617 	if (ZSTR_LEN(needle) > ZSTR_LEN(haystack)) {
1618 		RETURN_FALSE;
1619 	}
1620 
1621 	RETURN_BOOL(memcmp(
1622 		ZSTR_VAL(haystack) + ZSTR_LEN(haystack) - ZSTR_LEN(needle),
1623 		ZSTR_VAL(needle), ZSTR_LEN(needle)) == 0);
1624 }
1625 /* }}} */
1626 
1627 /* {{{ Finds position of first occurrence of a string within another */
1628 PHP_FUNCTION(strpos)
1629 {
1630 	zend_string *haystack, *needle;
1631 	const char *found = NULL;
1632 	zend_long offset = 0;
1633 
1634 	ZEND_PARSE_PARAMETERS_START(2, 3)
1635 		Z_PARAM_STR(haystack)
1636 		Z_PARAM_STR(needle)
1637 		Z_PARAM_OPTIONAL
1638 		Z_PARAM_LONG(offset)
1639 	ZEND_PARSE_PARAMETERS_END();
1640 
1641 	if (offset < 0) {
1642 		offset += (zend_long)ZSTR_LEN(haystack);
1643 	}
1644 	if (offset < 0 || (size_t)offset > ZSTR_LEN(haystack)) {
1645 		zend_argument_value_error(3, "must be contained in argument #1 ($haystack)");
1646 		RETURN_THROWS();
1647 	}
1648 
1649 	found = (char*)php_memnstr(ZSTR_VAL(haystack) + offset,
1650 						ZSTR_VAL(needle), ZSTR_LEN(needle),
1651 						ZSTR_VAL(haystack) + ZSTR_LEN(haystack));
1652 
1653 	if (UNEXPECTED(!found)) {
1654 		RETURN_FALSE;
1655 	}
1656 	RETURN_LONG(found - ZSTR_VAL(haystack));
1657 }
1658 /* }}} */
1659 
1660 /* {{{ Finds position of first occurrence of a string within another, case insensitive */
1661 PHP_FUNCTION(stripos)
1662 {
1663 	const char *found = NULL;
1664 	zend_string *haystack, *needle;
1665 	zend_long offset = 0;
1666 
1667 	ZEND_PARSE_PARAMETERS_START(2, 3)
1668 		Z_PARAM_STR(haystack)
1669 		Z_PARAM_STR(needle)
1670 		Z_PARAM_OPTIONAL
1671 		Z_PARAM_LONG(offset)
1672 	ZEND_PARSE_PARAMETERS_END();
1673 
1674 	if (offset < 0) {
1675 		offset += (zend_long)ZSTR_LEN(haystack);
1676 	}
1677 	if (offset < 0 || (size_t)offset > ZSTR_LEN(haystack)) {
1678 		zend_argument_value_error(3, "must be contained in argument #1 ($haystack)");
1679 		RETURN_THROWS();
1680 	}
1681 
1682 	found = (char*)php_memnistr(ZSTR_VAL(haystack) + offset,
1683 			ZSTR_VAL(needle), ZSTR_LEN(needle), ZSTR_VAL(haystack) + ZSTR_LEN(haystack));
1684 
1685 	if (UNEXPECTED(!found)) {
1686 		RETURN_FALSE;
1687 	}
1688 	RETURN_LONG(found - ZSTR_VAL(haystack));
1689 }
1690 /* }}} */
1691 
1692 /* {{{ Finds position of last occurrence of a string within another string */
1693 PHP_FUNCTION(strrpos)
1694 {
1695 	zend_string *needle;
1696 	zend_string *haystack;
1697 	zend_long offset = 0;
1698 	const char *p, *e, *found;
1699 
1700 	ZEND_PARSE_PARAMETERS_START(2, 3)
1701 		Z_PARAM_STR(haystack)
1702 		Z_PARAM_STR(needle)
1703 		Z_PARAM_OPTIONAL
1704 		Z_PARAM_LONG(offset)
1705 	ZEND_PARSE_PARAMETERS_END();
1706 
1707 	if (offset >= 0) {
1708 		if ((size_t)offset > ZSTR_LEN(haystack)) {
1709 			zend_argument_value_error(3, "must be contained in argument #1 ($haystack)");
1710 			RETURN_THROWS();
1711 		}
1712 		p = ZSTR_VAL(haystack) + (size_t)offset;
1713 		e = ZSTR_VAL(haystack) + ZSTR_LEN(haystack);
1714 	} else {
1715 		if (offset < -ZEND_LONG_MAX || (size_t)(-offset) > ZSTR_LEN(haystack)) {
1716 			zend_argument_value_error(3, "must be contained in argument #1 ($haystack)");
1717 			RETURN_THROWS();
1718 		}
1719 
1720 		p = ZSTR_VAL(haystack);
1721 		if ((size_t)-offset < ZSTR_LEN(needle)) {
1722 			e = ZSTR_VAL(haystack) + ZSTR_LEN(haystack);
1723 		} else {
1724 			e = ZSTR_VAL(haystack) + ZSTR_LEN(haystack) + offset + ZSTR_LEN(needle);
1725 		}
1726 	}
1727 
1728 	found = zend_memnrstr(p, ZSTR_VAL(needle), ZSTR_LEN(needle), e);
1729 
1730 	if (UNEXPECTED(!found)) {
1731 		RETURN_FALSE;
1732 	}
1733 	RETURN_LONG(found - ZSTR_VAL(haystack));
1734 }
1735 /* }}} */
1736 
1737 /* {{{ Finds position of last occurrence of a string within another string */
1738 PHP_FUNCTION(strripos)
1739 {
1740 	zend_string *needle;
1741 	zend_string *haystack;
1742 	zend_long offset = 0;
1743 	const char *p, *e, *found;
1744 	zend_string *needle_dup, *haystack_dup;
1745 
1746 	ZEND_PARSE_PARAMETERS_START(2, 3)
1747 		Z_PARAM_STR(haystack)
1748 		Z_PARAM_STR(needle)
1749 		Z_PARAM_OPTIONAL
1750 		Z_PARAM_LONG(offset)
1751 	ZEND_PARSE_PARAMETERS_END();
1752 
1753 	if (ZSTR_LEN(needle) == 1) {
1754 		/* Single character search can shortcut memcmps
1755 		   Can also avoid tolower emallocs */
1756 		char lowered;
1757 		if (offset >= 0) {
1758 			if ((size_t)offset > ZSTR_LEN(haystack)) {
1759 				zend_argument_value_error(3, "must be contained in argument #1 ($haystack)");
1760 				RETURN_THROWS();
1761 			}
1762 			p = ZSTR_VAL(haystack) + (size_t)offset;
1763 			e = ZSTR_VAL(haystack) + ZSTR_LEN(haystack) - 1;
1764 		} else {
1765 			p = ZSTR_VAL(haystack);
1766 			if (offset < -ZEND_LONG_MAX || (size_t)(-offset) > ZSTR_LEN(haystack)) {
1767 				zend_argument_value_error(3, "must be contained in argument #1 ($haystack)");
1768 				RETURN_THROWS();
1769 			}
1770 			e = ZSTR_VAL(haystack) + (ZSTR_LEN(haystack) + (size_t)offset);
1771 		}
1772 		lowered = zend_tolower_ascii(*ZSTR_VAL(needle));
1773 		while (e >= p) {
1774 			if (zend_tolower_ascii(*e) == lowered) {
1775 				RETURN_LONG(e - p + (offset > 0 ? offset : 0));
1776 			}
1777 			e--;
1778 		}
1779 		RETURN_FALSE;
1780 	}
1781 
1782 	haystack_dup = zend_string_tolower(haystack);
1783 	if (offset >= 0) {
1784 		if ((size_t)offset > ZSTR_LEN(haystack)) {
1785 			zend_string_release_ex(haystack_dup, 0);
1786 			zend_argument_value_error(3, "must be contained in argument #1 ($haystack)");
1787 			RETURN_THROWS();
1788 		}
1789 		p = ZSTR_VAL(haystack_dup) + offset;
1790 		e = ZSTR_VAL(haystack_dup) + ZSTR_LEN(haystack);
1791 	} else {
1792 		if (offset < -ZEND_LONG_MAX || (size_t)(-offset) > ZSTR_LEN(haystack)) {
1793 			zend_string_release_ex(haystack_dup, 0);
1794 			zend_argument_value_error(3, "must be contained in argument #1 ($haystack)");
1795 			RETURN_THROWS();
1796 		}
1797 
1798 		p = ZSTR_VAL(haystack_dup);
1799 		if ((size_t)-offset < ZSTR_LEN(needle)) {
1800 			e = ZSTR_VAL(haystack_dup) + ZSTR_LEN(haystack);
1801 		} else {
1802 			e = ZSTR_VAL(haystack_dup) + ZSTR_LEN(haystack) + offset + ZSTR_LEN(needle);
1803 		}
1804 	}
1805 
1806 	needle_dup = zend_string_tolower(needle);
1807 	if ((found = (char *)zend_memnrstr(p, ZSTR_VAL(needle_dup), ZSTR_LEN(needle_dup), e))) {
1808 		RETVAL_LONG(found - ZSTR_VAL(haystack_dup));
1809 		zend_string_release_ex(needle_dup, 0);
1810 		zend_string_release_ex(haystack_dup, 0);
1811 	} else {
1812 		zend_string_release_ex(needle_dup, 0);
1813 		zend_string_release_ex(haystack_dup, 0);
1814 		RETURN_FALSE;
1815 	}
1816 }
1817 /* }}} */
1818 
1819 /* {{{ Finds the last occurrence of a character in a string within another */
1820 PHP_FUNCTION(strrchr)
1821 {
1822 	zend_string *haystack, *needle;
1823 	const char *found = NULL;
1824 	zend_long found_offset;
1825 
1826 	ZEND_PARSE_PARAMETERS_START(2, 2)
1827 		Z_PARAM_STR(haystack)
1828 		Z_PARAM_STR(needle)
1829 	ZEND_PARSE_PARAMETERS_END();
1830 
1831 	found = zend_memrchr(ZSTR_VAL(haystack), *ZSTR_VAL(needle), ZSTR_LEN(haystack));
1832 	if (UNEXPECTED(!found)) {
1833 		RETURN_FALSE;
1834 	}
1835 	found_offset = found - ZSTR_VAL(haystack);
1836 	RETURN_STRINGL(found, ZSTR_LEN(haystack) - found_offset);
1837 }
1838 /* }}} */
1839 
1840 /* {{{ php_chunk_split */
1841 static zend_string *php_chunk_split(const char *src, size_t srclen, const char *end, size_t endlen, size_t chunklen)
1842 {
1843 	char *q;
1844 	const char *p;
1845 	size_t chunks;
1846 	size_t restlen;
1847 	zend_string *dest;
1848 
1849 	chunks = srclen / chunklen;
1850 	restlen = srclen - chunks * chunklen; /* srclen % chunklen */
1851 	if (restlen) {
1852 		/* We want chunks to be rounded up rather than rounded down.
1853 		 * Increment can't overflow because chunks <= SIZE_MAX/2 at this point. */
1854 		chunks++;
1855 	}
1856 
1857 	dest = zend_string_safe_alloc(chunks, endlen, srclen, 0);
1858 
1859 	for (p = src, q = ZSTR_VAL(dest); p < (src + srclen - chunklen + 1); ) {
1860 		memcpy(q, p, chunklen);
1861 		q += chunklen;
1862 		memcpy(q, end, endlen);
1863 		q += endlen;
1864 		p += chunklen;
1865 	}
1866 
1867 	if (restlen) {
1868 		memcpy(q, p, restlen);
1869 		q += restlen;
1870 		memcpy(q, end, endlen);
1871 		q += endlen;
1872 	}
1873 
1874 	*q = '\0';
1875 	ZEND_ASSERT(q - ZSTR_VAL(dest) == ZSTR_LEN(dest));
1876 
1877 	return dest;
1878 }
1879 /* }}} */
1880 
1881 /* {{{ Returns split line */
1882 PHP_FUNCTION(chunk_split)
1883 {
1884 	zend_string *str;
1885 	char *end    = "\r\n";
1886 	size_t endlen   = 2;
1887 	zend_long chunklen = 76;
1888 	zend_string *result;
1889 
1890 	ZEND_PARSE_PARAMETERS_START(1, 3)
1891 		Z_PARAM_STR(str)
1892 		Z_PARAM_OPTIONAL
1893 		Z_PARAM_LONG(chunklen)
1894 		Z_PARAM_STRING(end, endlen)
1895 	ZEND_PARSE_PARAMETERS_END();
1896 
1897 	if (chunklen <= 0) {
1898 		zend_argument_value_error(2, "must be greater than 0");
1899 		RETURN_THROWS();
1900 	}
1901 
1902 	if ((size_t)chunklen > ZSTR_LEN(str)) {
1903 		/* to maintain BC, we must return original string + ending */
1904 		result = zend_string_safe_alloc(ZSTR_LEN(str), 1, endlen, 0);
1905 		memcpy(ZSTR_VAL(result), ZSTR_VAL(str), ZSTR_LEN(str));
1906 		memcpy(ZSTR_VAL(result) + ZSTR_LEN(str), end, endlen);
1907 		ZSTR_VAL(result)[ZSTR_LEN(result)] = '\0';
1908 		RETURN_NEW_STR(result);
1909 	}
1910 
1911 	if (!ZSTR_LEN(str)) {
1912 		RETURN_EMPTY_STRING();
1913 	}
1914 
1915 	result = php_chunk_split(ZSTR_VAL(str), ZSTR_LEN(str), end, endlen, (size_t)chunklen);
1916 
1917 	RETURN_STR(result);
1918 }
1919 /* }}} */
1920 
1921 /* {{{ Returns part of a string */
1922 PHP_FUNCTION(substr)
1923 {
1924 	zend_string *str;
1925 	zend_long l = 0, f;
1926 	bool len_is_null = 1;
1927 
1928 	ZEND_PARSE_PARAMETERS_START(2, 3)
1929 		Z_PARAM_STR(str)
1930 		Z_PARAM_LONG(f)
1931 		Z_PARAM_OPTIONAL
1932 		Z_PARAM_LONG_OR_NULL(l, len_is_null)
1933 	ZEND_PARSE_PARAMETERS_END();
1934 
1935 	if (f < 0) {
1936 		/* if "from" position is negative, count start position from the end
1937 		 * of the string
1938 		 */
1939 		if (-(size_t)f > ZSTR_LEN(str)) {
1940 			f = 0;
1941 		} else {
1942 			f = (zend_long)ZSTR_LEN(str) + f;
1943 		}
1944 	} else if ((size_t)f > ZSTR_LEN(str)) {
1945 		RETURN_EMPTY_STRING();
1946 	}
1947 
1948 	if (!len_is_null) {
1949 		if (l < 0) {
1950 			/* if "length" position is negative, set it to the length
1951 			 * needed to stop that many chars from the end of the string
1952 			 */
1953 			if (-(size_t)l > ZSTR_LEN(str) - (size_t)f) {
1954 				l = 0;
1955 			} else {
1956 				l = (zend_long)ZSTR_LEN(str) - f + l;
1957 			}
1958 		} else if ((size_t)l > ZSTR_LEN(str) - (size_t)f) {
1959 			l = (zend_long)ZSTR_LEN(str) - f;
1960 		}
1961 	} else {
1962 		l = (zend_long)ZSTR_LEN(str) - f;
1963 	}
1964 
1965 	if (l == ZSTR_LEN(str)) {
1966 		RETURN_STR_COPY(str);
1967 	} else {
1968 		RETURN_STRINGL_FAST(ZSTR_VAL(str) + f, l);
1969 	}
1970 }
1971 /* }}} */
1972 
1973 /* {{{ Replaces part of a string with another string */
1974 PHP_FUNCTION(substr_replace)
1975 {
1976 	zend_string *str, *repl_str;
1977 	HashTable *str_ht, *repl_ht;
1978 	HashTable *from_ht;
1979 	zend_long from_long;
1980 	HashTable *len_ht = NULL;
1981 	zend_long len_long;
1982 	bool len_is_null = 1;
1983 	zend_long l = 0;
1984 	zend_long f;
1985 	zend_string *result;
1986 	HashPosition from_idx, repl_idx, len_idx;
1987 	zval *tmp_str = NULL, *tmp_repl, *tmp_from = NULL, *tmp_len= NULL;
1988 
1989 	ZEND_PARSE_PARAMETERS_START(3, 4)
1990 		Z_PARAM_ARRAY_HT_OR_STR(str_ht, str)
1991 		Z_PARAM_ARRAY_HT_OR_STR(repl_ht, repl_str)
1992 		Z_PARAM_ARRAY_HT_OR_LONG(from_ht, from_long)
1993 		Z_PARAM_OPTIONAL
1994 		Z_PARAM_ARRAY_HT_OR_LONG_OR_NULL(len_ht, len_long, len_is_null)
1995 	ZEND_PARSE_PARAMETERS_END();
1996 
1997 	if (len_is_null) {
1998 		if (str) {
1999 			l = ZSTR_LEN(str);
2000 		}
2001 	} else if (!len_ht) {
2002 		l = len_long;
2003 	}
2004 
2005 	if (str) {
2006 		if (from_ht) {
2007 			zend_argument_type_error(3, "cannot be an array when working on a single string");
2008 			RETURN_THROWS();
2009 		}
2010 		if (len_ht) {
2011 			zend_argument_type_error(4, "cannot be an array when working on a single string");
2012 			RETURN_THROWS();
2013 		}
2014 
2015 		f = from_long;
2016 
2017 		/* if "from" position is negative, count start position from the end
2018 		 * of the string
2019 		 */
2020 		if (f < 0) {
2021 			f = (zend_long)ZSTR_LEN(str) + f;
2022 			if (f < 0) {
2023 				f = 0;
2024 			}
2025 		} else if ((size_t)f > ZSTR_LEN(str)) {
2026 			f = ZSTR_LEN(str);
2027 		}
2028 		/* if "length" position is negative, set it to the length
2029 		 * needed to stop that many chars from the end of the string
2030 		 */
2031 		if (l < 0) {
2032 			l = ((zend_long)ZSTR_LEN(str) - f) + l;
2033 			if (l < 0) {
2034 				l = 0;
2035 			}
2036 		}
2037 
2038 		if ((size_t)l > ZSTR_LEN(str) || (l < 0 && (size_t)(-l) > ZSTR_LEN(str))) {
2039 			l = ZSTR_LEN(str);
2040 		}
2041 
2042 		if ((f + l) > (zend_long)ZSTR_LEN(str)) {
2043 			l = ZSTR_LEN(str) - f;
2044 		}
2045 
2046 		zend_string *tmp_repl_str = NULL;
2047 		if (repl_ht) {
2048 			repl_idx = 0;
2049 			if (HT_IS_PACKED(repl_ht)) {
2050 				while (repl_idx < repl_ht->nNumUsed) {
2051 					tmp_repl = &repl_ht->arPacked[repl_idx];
2052 					if (Z_TYPE_P(tmp_repl) != IS_UNDEF) {
2053 						break;
2054 					}
2055 					repl_idx++;
2056 				}
2057 			} else {
2058 				while (repl_idx < repl_ht->nNumUsed) {
2059 					tmp_repl = &repl_ht->arData[repl_idx].val;
2060 					if (Z_TYPE_P(tmp_repl) != IS_UNDEF) {
2061 						break;
2062 					}
2063 					repl_idx++;
2064 				}
2065 			}
2066 			if (repl_idx < repl_ht->nNumUsed) {
2067 				repl_str = zval_get_tmp_string(tmp_repl, &tmp_repl_str);
2068 			} else {
2069 				repl_str = STR_EMPTY_ALLOC();
2070 			}
2071 		}
2072 
2073 		result = zend_string_safe_alloc(1, ZSTR_LEN(str) - l + ZSTR_LEN(repl_str), 0, 0);
2074 
2075 		memcpy(ZSTR_VAL(result), ZSTR_VAL(str), f);
2076 		if (ZSTR_LEN(repl_str)) {
2077 			memcpy((ZSTR_VAL(result) + f), ZSTR_VAL(repl_str), ZSTR_LEN(repl_str));
2078 		}
2079 		memcpy((ZSTR_VAL(result) + f + ZSTR_LEN(repl_str)), ZSTR_VAL(str) + f + l, ZSTR_LEN(str) - f - l);
2080 		ZSTR_VAL(result)[ZSTR_LEN(result)] = '\0';
2081 		zend_tmp_string_release(tmp_repl_str);
2082 		RETURN_NEW_STR(result);
2083 	} else { /* str is array of strings */
2084 		zend_string *str_index = NULL;
2085 		size_t result_len;
2086 		zend_ulong num_index;
2087 
2088 		/* TODO
2089 		if (!len_is_null && from_ht) {
2090 			if (zend_hash_num_elements(from_ht) != zend_hash_num_elements(len_ht)) {
2091 				php_error_docref(NULL, E_WARNING, "'start' and 'length' should have the same number of elements");
2092 				RETURN_STR_COPY(str);
2093 			}
2094 		}
2095 		*/
2096 
2097 		array_init(return_value);
2098 
2099 		from_idx = len_idx = repl_idx = 0;
2100 
2101 		ZEND_HASH_FOREACH_KEY_VAL(str_ht, num_index, str_index, tmp_str) {
2102 			zend_string *tmp_orig_str;
2103 			zend_string *orig_str = zval_get_tmp_string(tmp_str, &tmp_orig_str);
2104 
2105 			if (from_ht) {
2106 				if (HT_IS_PACKED(from_ht)) {
2107 					while (from_idx < from_ht->nNumUsed) {
2108 						tmp_from = &from_ht->arPacked[from_idx];
2109 						if (Z_TYPE_P(tmp_from) != IS_UNDEF) {
2110 							break;
2111 						}
2112 						from_idx++;
2113 					}
2114 				} else {
2115 					while (from_idx < from_ht->nNumUsed) {
2116 						tmp_from = &from_ht->arData[from_idx].val;
2117 						if (Z_TYPE_P(tmp_from) != IS_UNDEF) {
2118 							break;
2119 						}
2120 						from_idx++;
2121 					}
2122 				}
2123 				if (from_idx < from_ht->nNumUsed) {
2124 					f = zval_get_long(tmp_from);
2125 
2126 					if (f < 0) {
2127 						f = (zend_long)ZSTR_LEN(orig_str) + f;
2128 						if (f < 0) {
2129 							f = 0;
2130 						}
2131 					} else if (f > (zend_long)ZSTR_LEN(orig_str)) {
2132 						f = ZSTR_LEN(orig_str);
2133 					}
2134 					from_idx++;
2135 				} else {
2136 					f = 0;
2137 				}
2138 			} else {
2139 				f = from_long;
2140 				if (f < 0) {
2141 					f = (zend_long)ZSTR_LEN(orig_str) + f;
2142 					if (f < 0) {
2143 						f = 0;
2144 					}
2145 				} else if (f > (zend_long)ZSTR_LEN(orig_str)) {
2146 					f = ZSTR_LEN(orig_str);
2147 				}
2148 			}
2149 
2150 			if (len_ht) {
2151 				if (HT_IS_PACKED(len_ht)) {
2152 					while (len_idx < len_ht->nNumUsed) {
2153 						tmp_len = &len_ht->arPacked[len_idx];
2154 						if (Z_TYPE_P(tmp_len) != IS_UNDEF) {
2155 							break;
2156 						}
2157 						len_idx++;
2158 					}
2159 				} else {
2160 					while (len_idx < len_ht->nNumUsed) {
2161 						tmp_len = &len_ht->arData[len_idx].val;
2162 						if (Z_TYPE_P(tmp_len) != IS_UNDEF) {
2163 							break;
2164 						}
2165 						len_idx++;
2166 					}
2167 				}
2168 				if (len_idx < len_ht->nNumUsed) {
2169 					l = zval_get_long(tmp_len);
2170 					len_idx++;
2171 				} else {
2172 					l = ZSTR_LEN(orig_str);
2173 				}
2174 			} else if (!len_is_null) {
2175 				l = len_long;
2176 			} else {
2177 				l = ZSTR_LEN(orig_str);
2178 			}
2179 
2180 			if (l < 0) {
2181 				l = (ZSTR_LEN(orig_str) - f) + l;
2182 				if (l < 0) {
2183 					l = 0;
2184 				}
2185 			}
2186 
2187 			ZEND_ASSERT(0 <= f && f <= ZEND_LONG_MAX);
2188 			ZEND_ASSERT(0 <= l && l <= ZEND_LONG_MAX);
2189 			if (((size_t) f + l) > ZSTR_LEN(orig_str)) {
2190 				l = ZSTR_LEN(orig_str) - f;
2191 			}
2192 
2193 			result_len = ZSTR_LEN(orig_str) - l;
2194 
2195 			if (repl_ht) {
2196 				if (HT_IS_PACKED(repl_ht)) {
2197 					while (repl_idx < repl_ht->nNumUsed) {
2198 						tmp_repl = &repl_ht->arPacked[repl_idx];
2199 						if (Z_TYPE_P(tmp_repl) != IS_UNDEF) {
2200 							break;
2201 						}
2202 						repl_idx++;
2203 					}
2204 				} else {
2205 					while (repl_idx < repl_ht->nNumUsed) {
2206 						tmp_repl = &repl_ht->arData[repl_idx].val;
2207 						if (Z_TYPE_P(tmp_repl) != IS_UNDEF) {
2208 							break;
2209 						}
2210 						repl_idx++;
2211 					}
2212 				}
2213 				if (repl_idx < repl_ht->nNumUsed) {
2214 					zend_string *tmp_repl_str;
2215 					zend_string *repl_str = zval_get_tmp_string(tmp_repl, &tmp_repl_str);
2216 
2217 					result_len += ZSTR_LEN(repl_str);
2218 					repl_idx++;
2219 					result = zend_string_safe_alloc(1, result_len, 0, 0);
2220 
2221 					memcpy(ZSTR_VAL(result), ZSTR_VAL(orig_str), f);
2222 					memcpy((ZSTR_VAL(result) + f), ZSTR_VAL(repl_str), ZSTR_LEN(repl_str));
2223 					memcpy((ZSTR_VAL(result) + f + ZSTR_LEN(repl_str)), ZSTR_VAL(orig_str) + f + l, ZSTR_LEN(orig_str) - f - l);
2224 					zend_tmp_string_release(tmp_repl_str);
2225 				} else {
2226 					result = zend_string_safe_alloc(1, result_len, 0, 0);
2227 
2228 					memcpy(ZSTR_VAL(result), ZSTR_VAL(orig_str), f);
2229 					memcpy((ZSTR_VAL(result) + f), ZSTR_VAL(orig_str) + f + l, ZSTR_LEN(orig_str) - f - l);
2230 				}
2231 			} else {
2232 				result_len += ZSTR_LEN(repl_str);
2233 
2234 				result = zend_string_safe_alloc(1, result_len, 0, 0);
2235 
2236 				memcpy(ZSTR_VAL(result), ZSTR_VAL(orig_str), f);
2237 				memcpy((ZSTR_VAL(result) + f), ZSTR_VAL(repl_str), ZSTR_LEN(repl_str));
2238 				memcpy((ZSTR_VAL(result) + f + ZSTR_LEN(repl_str)), ZSTR_VAL(orig_str) + f + l, ZSTR_LEN(orig_str) - f - l);
2239 			}
2240 
2241 			ZSTR_VAL(result)[ZSTR_LEN(result)] = '\0';
2242 
2243 			if (str_index) {
2244 				zval tmp;
2245 
2246 				ZVAL_NEW_STR(&tmp, result);
2247 				zend_symtable_update(Z_ARRVAL_P(return_value), str_index, &tmp);
2248 			} else {
2249 				add_index_str(return_value, num_index, result);
2250 			}
2251 
2252 			zend_tmp_string_release(tmp_orig_str);
2253 		} ZEND_HASH_FOREACH_END();
2254 	} /* if */
2255 }
2256 /* }}} */
2257 
2258 /* {{{ Quotes meta characters */
2259 PHP_FUNCTION(quotemeta)
2260 {
2261 	zend_string *old;
2262 	const char *old_end, *p;
2263 	char *q;
2264 	char c;
2265 	zend_string *str;
2266 
2267 	ZEND_PARSE_PARAMETERS_START(1, 1)
2268 		Z_PARAM_STR(old)
2269 	ZEND_PARSE_PARAMETERS_END();
2270 
2271 	old_end = ZSTR_VAL(old) + ZSTR_LEN(old);
2272 
2273 	if (ZSTR_LEN(old) == 0) {
2274 		RETURN_EMPTY_STRING();
2275 	}
2276 
2277 	str = zend_string_safe_alloc(2, ZSTR_LEN(old), 0, 0);
2278 
2279 	for (p = ZSTR_VAL(old), q = ZSTR_VAL(str); p != old_end; p++) {
2280 		c = *p;
2281 		switch (c) {
2282 			case '.':
2283 			case '\\':
2284 			case '+':
2285 			case '*':
2286 			case '?':
2287 			case '[':
2288 			case '^':
2289 			case ']':
2290 			case '$':
2291 			case '(':
2292 			case ')':
2293 				*q++ = '\\';
2294 				ZEND_FALLTHROUGH;
2295 			default:
2296 				*q++ = c;
2297 		}
2298 	}
2299 
2300 	*q = '\0';
2301 
2302 	RETURN_NEW_STR(zend_string_truncate(str, q - ZSTR_VAL(str), 0));
2303 }
2304 /* }}} */
2305 
2306 /* {{{ Returns ASCII value of character
2307    Warning: This function is special-cased by zend_compile.c and so is bypassed for constant string argument */
2308 PHP_FUNCTION(ord)
2309 {
2310 	zend_string *str;
2311 
2312 	ZEND_PARSE_PARAMETERS_START(1, 1)
2313 		Z_PARAM_STR(str)
2314 	ZEND_PARSE_PARAMETERS_END();
2315 
2316 	RETURN_LONG((unsigned char) ZSTR_VAL(str)[0]);
2317 }
2318 /* }}} */
2319 
2320 /* {{{ Converts ASCII code to a character
2321    Warning: This function is special-cased by zend_compile.c and so is bypassed for constant integer argument */
2322 PHP_FUNCTION(chr)
2323 {
2324 	zend_long c;
2325 
2326 	ZEND_PARSE_PARAMETERS_START(1, 1)
2327 		Z_PARAM_LONG(c)
2328 	ZEND_PARSE_PARAMETERS_END();
2329 
2330 	c &= 0xff;
2331 	RETURN_CHAR(c);
2332 }
2333 /* }}} */
2334 
2335 /* {{{ php_ucfirst
2336    Uppercase the first character of the word in a native string */
2337 static zend_string* php_ucfirst(zend_string *str)
2338 {
2339 	const unsigned char ch = ZSTR_VAL(str)[0];
2340 	unsigned char r = zend_toupper_ascii(ch);
2341 	if (r == ch) {
2342 		return zend_string_copy(str);
2343 	} else {
2344 		zend_string *s = zend_string_init(ZSTR_VAL(str), ZSTR_LEN(str), 0);
2345 		ZSTR_VAL(s)[0] = r;
2346 		return s;
2347 	}
2348 }
2349 /* }}} */
2350 
2351 /* {{{ Makes a string's first character uppercase */
2352 PHP_FUNCTION(ucfirst)
2353 {
2354 	zend_string *str;
2355 
2356 	ZEND_PARSE_PARAMETERS_START(1, 1)
2357 		Z_PARAM_STR(str)
2358 	ZEND_PARSE_PARAMETERS_END();
2359 
2360 	if (!ZSTR_LEN(str)) {
2361 		RETURN_EMPTY_STRING();
2362 	}
2363 
2364 	RETURN_STR(php_ucfirst(str));
2365 }
2366 /* }}} */
2367 
2368 /* {{{
2369    Lowercase the first character of the word in a native string */
2370 static zend_string* php_lcfirst(zend_string *str)
2371 {
2372 	unsigned char r = zend_tolower_ascii(ZSTR_VAL(str)[0]);
2373 	if (r == ZSTR_VAL(str)[0]) {
2374 		return zend_string_copy(str);
2375 	} else {
2376 		zend_string *s = zend_string_init(ZSTR_VAL(str), ZSTR_LEN(str), 0);
2377 		ZSTR_VAL(s)[0] = r;
2378 		return s;
2379 	}
2380 }
2381 /* }}} */
2382 
2383 /* {{{ Make a string's first character lowercase */
2384 PHP_FUNCTION(lcfirst)
2385 {
2386 	zend_string  *str;
2387 
2388 	ZEND_PARSE_PARAMETERS_START(1, 1)
2389 		Z_PARAM_STR(str)
2390 	ZEND_PARSE_PARAMETERS_END();
2391 
2392 	if (!ZSTR_LEN(str)) {
2393 		RETURN_EMPTY_STRING();
2394 	}
2395 
2396 	RETURN_STR(php_lcfirst(str));
2397 }
2398 /* }}} */
2399 
2400 /* {{{ Uppercase the first character of every word in a string */
2401 PHP_FUNCTION(ucwords)
2402 {
2403 	zend_string *str;
2404 	char *delims = " \t\r\n\f\v";
2405 	char *r;
2406 	const char *r_end;
2407 	size_t delims_len = 6;
2408 	char mask[256];
2409 
2410 	ZEND_PARSE_PARAMETERS_START(1, 2)
2411 		Z_PARAM_STR(str)
2412 		Z_PARAM_OPTIONAL
2413 		Z_PARAM_STRING(delims, delims_len)
2414 	ZEND_PARSE_PARAMETERS_END();
2415 
2416 	if (!ZSTR_LEN(str)) {
2417 		RETURN_EMPTY_STRING();
2418 	}
2419 
2420 	php_charmask((const unsigned char *) delims, delims_len, mask);
2421 
2422 	ZVAL_STRINGL(return_value, ZSTR_VAL(str), ZSTR_LEN(str));
2423 	r = Z_STRVAL_P(return_value);
2424 
2425 	*r = zend_toupper_ascii((unsigned char) *r);
2426 	for (r_end = r + Z_STRLEN_P(return_value) - 1; r < r_end; ) {
2427 		if (mask[(unsigned char)*r++]) {
2428 			*r = zend_toupper_ascii((unsigned char) *r);
2429 		}
2430 	}
2431 }
2432 /* }}} */
2433 
2434 /* {{{ php_strtr */
2435 PHPAPI char *php_strtr(char *str, size_t len, const char *str_from, const char *str_to, size_t trlen)
2436 {
2437 	size_t i;
2438 
2439 	if (UNEXPECTED(trlen < 1)) {
2440 		return str;
2441 	} else if (trlen == 1) {
2442 		char ch_from = *str_from;
2443 		char ch_to = *str_to;
2444 
2445 		for (i = 0; i < len; i++) {
2446 			if (str[i] == ch_from) {
2447 				str[i] = ch_to;
2448 			}
2449 		}
2450 	} else {
2451 		unsigned char xlat[256];
2452 
2453 		memset(xlat, 0, sizeof(xlat));
2454 
2455 		for (i = 0; i < trlen; i++) {
2456 			xlat[(size_t)(unsigned char) str_from[i]] = str_to[i] - str_from[i];
2457 		}
2458 
2459 		for (i = 0; i < len; i++) {
2460 			str[i] += xlat[(size_t)(unsigned char) str[i]];
2461 		}
2462 	}
2463 
2464 	return str;
2465 }
2466 /* }}} */
2467 
2468 /* {{{ php_strtr_ex */
2469 static zend_string *php_strtr_ex(zend_string *str, const char *str_from, const char *str_to, size_t trlen)
2470 {
2471 	zend_string *new_str = NULL;
2472 	size_t i;
2473 
2474 	if (UNEXPECTED(trlen < 1)) {
2475 		return zend_string_copy(str);
2476 	} else if (trlen == 1) {
2477 		char ch_from = *str_from;
2478 		char ch_to = *str_to;
2479 		char *output;
2480 		char *input = ZSTR_VAL(str);
2481 		size_t len = ZSTR_LEN(str);
2482 
2483 #ifdef __SSE2__
2484 		if (ZSTR_LEN(str) >= sizeof(__m128i)) {
2485 			__m128i search = _mm_set1_epi8(ch_from);
2486 			__m128i delta = _mm_set1_epi8(ch_to - ch_from);
2487 
2488 			do {
2489 				__m128i src = _mm_loadu_si128((__m128i*)(input));
2490 				__m128i mask = _mm_cmpeq_epi8(src, search);
2491 				if (_mm_movemask_epi8(mask)) {
2492 					new_str = zend_string_alloc(ZSTR_LEN(str), 0);
2493 					memcpy(ZSTR_VAL(new_str), ZSTR_VAL(str), input - ZSTR_VAL(str));
2494 					output = ZSTR_VAL(new_str) + (input - ZSTR_VAL(str));
2495 					_mm_storeu_si128((__m128i *)(output),
2496 						_mm_add_epi8(src,
2497 							_mm_and_si128(mask, delta)));
2498 					input += sizeof(__m128i);
2499 					output += sizeof(__m128i);
2500 					len -= sizeof(__m128i);
2501 					for (; len >= sizeof(__m128i); input += sizeof(__m128i), output += sizeof(__m128i), len -= sizeof(__m128i)) {
2502 						src = _mm_loadu_si128((__m128i*)(input));
2503 						mask = _mm_cmpeq_epi8(src, search);
2504 						_mm_storeu_si128((__m128i *)(output),
2505 							_mm_add_epi8(src,
2506 								_mm_and_si128(mask, delta)));
2507 					}
2508 					for (; len > 0; input++, output++, len--) {
2509 						*output = (*input == ch_from) ? ch_to : *input;
2510 					}
2511 					*output = 0;
2512 					return new_str;
2513 				}
2514 				input += sizeof(__m128i);
2515 				len -= sizeof(__m128i);
2516 			} while (len >= sizeof(__m128i));
2517 		}
2518 #endif
2519 		for (; len > 0; input++, len--) {
2520 			if (*input == ch_from) {
2521 				new_str = zend_string_alloc(ZSTR_LEN(str), 0);
2522 				memcpy(ZSTR_VAL(new_str), ZSTR_VAL(str), input - ZSTR_VAL(str));
2523 				output = ZSTR_VAL(new_str) + (input - ZSTR_VAL(str));
2524 				*output = ch_to;
2525 				input++;
2526 				output++;
2527 				len--;
2528 				for (; len > 0; input++, output++, len--) {
2529 					*output = (*input == ch_from) ? ch_to : *input;
2530 				}
2531 				*output = 0;
2532 				return new_str;
2533 			}
2534 		}
2535 	} else {
2536 		unsigned char xlat[256];
2537 
2538 		memset(xlat, 0, sizeof(xlat));;
2539 
2540 		for (i = 0; i < trlen; i++) {
2541 			xlat[(size_t)(unsigned char) str_from[i]] = str_to[i] - str_from[i];
2542 		}
2543 
2544 		for (i = 0; i < ZSTR_LEN(str); i++) {
2545 			if (xlat[(size_t)(unsigned char) ZSTR_VAL(str)[i]]) {
2546 				new_str = zend_string_alloc(ZSTR_LEN(str), 0);
2547 				memcpy(ZSTR_VAL(new_str), ZSTR_VAL(str), i);
2548 				do {
2549 					ZSTR_VAL(new_str)[i] = ZSTR_VAL(str)[i] + xlat[(size_t)(unsigned char) ZSTR_VAL(str)[i]];
2550 					i++;
2551 				} while (i < ZSTR_LEN(str));
2552 				ZSTR_VAL(new_str)[i] = 0;
2553 				return new_str;
2554 			}
2555 		}
2556 	}
2557 
2558 	return zend_string_copy(str);
2559 }
2560 /* }}} */
2561 
2562 /* {{{ php_strtr_array */
2563 static void php_strtr_array(zval *return_value, zend_string *input, HashTable *pats)
2564 {
2565 	const char *str = ZSTR_VAL(input);
2566 	size_t slen = ZSTR_LEN(input);
2567 	zend_ulong num_key;
2568 	zend_string *str_key;
2569 	size_t len, pos, old_pos;
2570 	bool has_num_keys = false;
2571 	size_t minlen = 128*1024;
2572 	size_t maxlen = 0;
2573 	HashTable str_hash;
2574 	zval *entry;
2575 	const char *key;
2576 	smart_str result = {0};
2577 	zend_ulong bitset[256/sizeof(zend_ulong)];
2578 	zend_ulong *num_bitset;
2579 
2580 	/* we will collect all possible key lengths */
2581 	num_bitset = ecalloc((slen + sizeof(zend_ulong)) / sizeof(zend_ulong), sizeof(zend_ulong));
2582 	memset(bitset, 0, sizeof(bitset));
2583 
2584 	/* check if original array has numeric keys */
2585 	ZEND_HASH_FOREACH_STR_KEY(pats, str_key) {
2586 		if (UNEXPECTED(!str_key)) {
2587 			has_num_keys = true;
2588 		} else {
2589 			len = ZSTR_LEN(str_key);
2590 			if (UNEXPECTED(len == 0)) {
2591 				php_error_docref(NULL, E_WARNING, "Ignoring replacement of empty string");
2592 				continue;
2593 			} else if (UNEXPECTED(len > slen)) {
2594 				/* skip long patterns */
2595 				continue;
2596 			}
2597 			if (len > maxlen) {
2598 				maxlen = len;
2599 			}
2600 			if (len < minlen) {
2601 				minlen = len;
2602 			}
2603 			/* remember possible key length */
2604 			num_bitset[len / sizeof(zend_ulong)] |= Z_UL(1) << (len % sizeof(zend_ulong));
2605 			bitset[((unsigned char)ZSTR_VAL(str_key)[0]) / sizeof(zend_ulong)] |= Z_UL(1) << (((unsigned char)ZSTR_VAL(str_key)[0]) % sizeof(zend_ulong));
2606 		}
2607 	} ZEND_HASH_FOREACH_END();
2608 
2609 	if (UNEXPECTED(has_num_keys)) {
2610 		zend_string *key_used;
2611 		/* we have to rebuild HashTable with numeric keys */
2612 		zend_hash_init(&str_hash, zend_hash_num_elements(pats), NULL, NULL, 0);
2613 		ZEND_HASH_FOREACH_KEY_VAL(pats, num_key, str_key, entry) {
2614 			if (UNEXPECTED(!str_key)) {
2615 				key_used = zend_long_to_str(num_key);
2616 				len = ZSTR_LEN(key_used);
2617 				if (UNEXPECTED(len > slen)) {
2618 					/* skip long patterns */
2619 					zend_string_release(key_used);
2620 					continue;
2621 				}
2622 				if (len > maxlen) {
2623 					maxlen = len;
2624 				}
2625 				if (len < minlen) {
2626 					minlen = len;
2627 				}
2628 				/* remember possible key length */
2629 				num_bitset[len / sizeof(zend_ulong)] |= Z_UL(1) << (len % sizeof(zend_ulong));
2630 				bitset[((unsigned char)ZSTR_VAL(key_used)[0]) / sizeof(zend_ulong)] |= Z_UL(1) << (((unsigned char)ZSTR_VAL(key_used)[0]) % sizeof(zend_ulong));
2631 			} else {
2632 				key_used = str_key;
2633 				len = ZSTR_LEN(key_used);
2634 				if (UNEXPECTED(len > slen)) {
2635 					/* skip long patterns */
2636 					continue;
2637 				}
2638 			}
2639 			zend_hash_add(&str_hash, key_used, entry);
2640 			if (UNEXPECTED(!str_key)) {
2641 				zend_string_release_ex(key_used, 0);
2642 			}
2643 		} ZEND_HASH_FOREACH_END();
2644 		pats = &str_hash;
2645 	}
2646 
2647 	if (UNEXPECTED(minlen > maxlen)) {
2648 		/* return the original string */
2649 		if (pats == &str_hash) {
2650 			zend_hash_destroy(&str_hash);
2651 		}
2652 		efree(num_bitset);
2653 		RETURN_STR_COPY(input);
2654 	}
2655 
2656 	old_pos = pos = 0;
2657 	while (pos <= slen - minlen) {
2658 		key = str + pos;
2659 		if (bitset[((unsigned char)key[0]) / sizeof(zend_ulong)] & (Z_UL(1) << (((unsigned char)key[0]) % sizeof(zend_ulong)))) {
2660 			len = maxlen;
2661 			if (len > slen - pos) {
2662 				len = slen - pos;
2663 			}
2664 			while (len >= minlen) {
2665 				if ((num_bitset[len / sizeof(zend_ulong)] & (Z_UL(1) << (len % sizeof(zend_ulong))))) {
2666 					entry = zend_hash_str_find(pats, key, len);
2667 					if (entry != NULL) {
2668 						zend_string *tmp;
2669 						zend_string *s = zval_get_tmp_string(entry, &tmp);
2670 						smart_str_appendl(&result, str + old_pos, pos - old_pos);
2671 						smart_str_append(&result, s);
2672 						old_pos = pos + len;
2673 						pos = old_pos - 1;
2674 						zend_tmp_string_release(tmp);
2675 						break;
2676 					}
2677 				}
2678 				len--;
2679 			}
2680 		}
2681 		pos++;
2682 	}
2683 
2684 	if (result.s) {
2685 		smart_str_appendl(&result, str + old_pos, slen - old_pos);
2686 		RETVAL_STR(smart_str_extract(&result));
2687 	} else {
2688 		smart_str_free(&result);
2689 		RETVAL_STR_COPY(input);
2690 	}
2691 
2692 	if (pats == &str_hash) {
2693 		zend_hash_destroy(&str_hash);
2694 	}
2695 	efree(num_bitset);
2696 }
2697 /* }}} */
2698 
2699 /* {{{ count_chars */
2700 static zend_always_inline zend_long count_chars(const char *p, zend_long length, char ch)
2701 {
2702 	zend_long count = 0;
2703 	const char *endp;
2704 
2705 #ifdef __SSE2__
2706 	if (length >= sizeof(__m128i)) {
2707 		__m128i search = _mm_set1_epi8(ch);
2708 
2709 		do {
2710 			__m128i src = _mm_loadu_si128((__m128i*)(p));
2711 			uint32_t mask = _mm_movemask_epi8(_mm_cmpeq_epi8(src, search));
2712 			// TODO: It would be great to use POPCNT, but it's available only with SSE4.1
2713 #if 1
2714 			while (mask != 0) {
2715 				count++;
2716 				mask = mask & (mask - 1);
2717 			}
2718 #else
2719 			if (mask) {
2720 				mask = mask - ((mask >> 1) & 0x5555);
2721 				mask = (mask & 0x3333) + ((mask >> 2) & 0x3333);
2722 				mask = (mask + (mask >> 4)) & 0x0F0F;
2723 				mask = (mask + (mask >> 8)) & 0x00ff;
2724 				count += mask;
2725 			}
2726 #endif
2727 			p += sizeof(__m128i);
2728 			length -= sizeof(__m128i);
2729 		} while (length >= sizeof(__m128i));
2730 	}
2731 	endp = p + length;
2732 	while (p != endp) {
2733 		count += (*p == ch);
2734 		p++;
2735 	}
2736 #else
2737 	endp = p + length;
2738 	while ((p = memchr(p, ch, endp-p))) {
2739 		count++;
2740 		p++;
2741 	}
2742 #endif
2743 	return count;
2744 }
2745 /* }}} */
2746 
2747 /* {{{ php_char_to_str_ex */
2748 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)
2749 {
2750 	zend_string *result;
2751 	size_t char_count;
2752 	int lc_from = 0;
2753 	const char *source, *source_end;
2754 	char *target;
2755 
2756 	if (case_sensitivity) {
2757 		char_count = count_chars(ZSTR_VAL(str), ZSTR_LEN(str), from);
2758 	} else {
2759 		char_count = 0;
2760 		lc_from = zend_tolower_ascii(from);
2761 		source_end = ZSTR_VAL(str) + ZSTR_LEN(str);
2762 		for (source = ZSTR_VAL(str); source < source_end; source++) {
2763 			if (zend_tolower_ascii(*source) == lc_from) {
2764 				char_count++;
2765 			}
2766 		}
2767 	}
2768 
2769 	if (char_count == 0) {
2770 		return zend_string_copy(str);
2771 	}
2772 
2773 	if (replace_count) {
2774 		*replace_count += char_count;
2775 	}
2776 
2777 	if (to_len > 0) {
2778 		result = zend_string_safe_alloc(char_count, to_len - 1, ZSTR_LEN(str), 0);
2779 	} else {
2780 		result = zend_string_alloc(ZSTR_LEN(str) - char_count, 0);
2781 	}
2782 	target = ZSTR_VAL(result);
2783 
2784 	if (case_sensitivity) {
2785 		char *p = ZSTR_VAL(str), *e = p + ZSTR_LEN(str), *s = ZSTR_VAL(str);
2786 
2787 		while ((p = memchr(p, from, (e - p)))) {
2788 			memcpy(target, s, (p - s));
2789 			target += p - s;
2790 			memcpy(target, to, to_len);
2791 			target += to_len;
2792 			p++;
2793 			s = p;
2794 			if (--char_count == 0) break;
2795 		}
2796 		if (s < e) {
2797 			memcpy(target, s, (e - s));
2798 			target += e - s;
2799 		}
2800 	} else {
2801 		source_end = ZSTR_VAL(str) + ZSTR_LEN(str);
2802 		for (source = ZSTR_VAL(str); source < source_end; source++) {
2803 			if (zend_tolower_ascii(*source) == lc_from) {
2804 				memcpy(target, to, to_len);
2805 				target += to_len;
2806 			} else {
2807 				*target = *source;
2808 				target++;
2809 			}
2810 		}
2811 	}
2812 	*target = 0;
2813 	return result;
2814 }
2815 /* }}} */
2816 
2817 /* {{{ php_str_to_str_ex */
2818 static zend_string *php_str_to_str_ex(zend_string *haystack,
2819 	const char *needle, size_t needle_len, const char *str, size_t str_len, zend_long *replace_count)
2820 {
2821 
2822 	if (needle_len < ZSTR_LEN(haystack)) {
2823 		zend_string *new_str;
2824 		const char *end;
2825 		const char *p, *r;
2826 		char *e;
2827 
2828 		if (needle_len == str_len) {
2829 			new_str = NULL;
2830 			end = ZSTR_VAL(haystack) + ZSTR_LEN(haystack);
2831 			for (p = ZSTR_VAL(haystack); (r = (char*)php_memnstr(p, needle, needle_len, end)); p = r + needle_len) {
2832 				if (!new_str) {
2833 					new_str = zend_string_init(ZSTR_VAL(haystack), ZSTR_LEN(haystack), 0);
2834 				}
2835 				memcpy(ZSTR_VAL(new_str) + (r - ZSTR_VAL(haystack)), str, str_len);
2836 				(*replace_count)++;
2837 			}
2838 			if (!new_str) {
2839 				goto nothing_todo;
2840 			}
2841 			return new_str;
2842 		} else {
2843 			size_t count = 0;
2844 			const char *o = ZSTR_VAL(haystack);
2845 			const char *n = needle;
2846 			const char *endp = o + ZSTR_LEN(haystack);
2847 
2848 			while ((o = (char*)php_memnstr(o, n, needle_len, endp))) {
2849 				o += needle_len;
2850 				count++;
2851 			}
2852 			if (count == 0) {
2853 				/* Needle doesn't occur, shortcircuit the actual replacement. */
2854 				goto nothing_todo;
2855 			}
2856 			if (str_len > needle_len) {
2857 				new_str = zend_string_safe_alloc(count, str_len - needle_len, ZSTR_LEN(haystack), 0);
2858 			} else {
2859 				new_str = zend_string_alloc(count * (str_len - needle_len) + ZSTR_LEN(haystack), 0);
2860 			}
2861 
2862 			e = ZSTR_VAL(new_str);
2863 			end = ZSTR_VAL(haystack) + ZSTR_LEN(haystack);
2864 			for (p = ZSTR_VAL(haystack); (r = (char*)php_memnstr(p, needle, needle_len, end)); p = r + needle_len) {
2865 				memcpy(e, p, r - p);
2866 				e += r - p;
2867 				memcpy(e, str, str_len);
2868 				e += str_len;
2869 				(*replace_count)++;
2870 			}
2871 
2872 			if (p < end) {
2873 				memcpy(e, p, end - p);
2874 				e += end - p;
2875 			}
2876 
2877 			*e = '\0';
2878 			return new_str;
2879 		}
2880 	} else if (needle_len > ZSTR_LEN(haystack) || memcmp(ZSTR_VAL(haystack), needle, ZSTR_LEN(haystack))) {
2881 nothing_todo:
2882 		return zend_string_copy(haystack);
2883 	} else {
2884 		(*replace_count)++;
2885 		return zend_string_init_fast(str, str_len);
2886 	}
2887 }
2888 /* }}} */
2889 
2890 /* {{{ php_str_to_str_i_ex */
2891 static zend_string *php_str_to_str_i_ex(zend_string *haystack, const char *lc_haystack,
2892 	zend_string *needle, const char *str, size_t str_len, zend_long *replace_count)
2893 {
2894 	zend_string *new_str = NULL;
2895 	zend_string *lc_needle;
2896 
2897 	if (ZSTR_LEN(needle) < ZSTR_LEN(haystack)) {
2898 		const char *end;
2899 		const char *p, *r;
2900 		char *e;
2901 
2902 		if (ZSTR_LEN(needle) == str_len) {
2903 			lc_needle = zend_string_tolower(needle);
2904 			end = lc_haystack + ZSTR_LEN(haystack);
2905 			for (p = lc_haystack; (r = (char*)php_memnstr(p, ZSTR_VAL(lc_needle), ZSTR_LEN(lc_needle), end)); p = r + ZSTR_LEN(lc_needle)) {
2906 				if (!new_str) {
2907 					new_str = zend_string_init(ZSTR_VAL(haystack), ZSTR_LEN(haystack), 0);
2908 				}
2909 				memcpy(ZSTR_VAL(new_str) + (r - lc_haystack), str, str_len);
2910 				(*replace_count)++;
2911 			}
2912 			zend_string_release_ex(lc_needle, 0);
2913 
2914 			if (!new_str) {
2915 				goto nothing_todo;
2916 			}
2917 			return new_str;
2918 		} else {
2919 			size_t count = 0;
2920 			const char *o = lc_haystack;
2921 			const char *n;
2922 			const char *endp = o + ZSTR_LEN(haystack);
2923 
2924 			lc_needle = zend_string_tolower(needle);
2925 			n = ZSTR_VAL(lc_needle);
2926 
2927 			while ((o = (char*)php_memnstr(o, n, ZSTR_LEN(lc_needle), endp))) {
2928 				o += ZSTR_LEN(lc_needle);
2929 				count++;
2930 			}
2931 			if (count == 0) {
2932 				/* Needle doesn't occur, shortcircuit the actual replacement. */
2933 				zend_string_release_ex(lc_needle, 0);
2934 				goto nothing_todo;
2935 			}
2936 
2937 			if (str_len > ZSTR_LEN(lc_needle)) {
2938 				new_str = zend_string_safe_alloc(count, str_len - ZSTR_LEN(lc_needle), ZSTR_LEN(haystack), 0);
2939 			} else {
2940 				new_str = zend_string_alloc(count * (str_len - ZSTR_LEN(lc_needle)) + ZSTR_LEN(haystack), 0);
2941 			}
2942 
2943 			e = ZSTR_VAL(new_str);
2944 			end = lc_haystack + ZSTR_LEN(haystack);
2945 
2946 			for (p = lc_haystack; (r = (char*)php_memnstr(p, ZSTR_VAL(lc_needle), ZSTR_LEN(lc_needle), end)); p = r + ZSTR_LEN(lc_needle)) {
2947 				memcpy(e, ZSTR_VAL(haystack) + (p - lc_haystack), r - p);
2948 				e += r - p;
2949 				memcpy(e, str, str_len);
2950 				e += str_len;
2951 				(*replace_count)++;
2952 			}
2953 
2954 			if (p < end) {
2955 				memcpy(e, ZSTR_VAL(haystack) + (p - lc_haystack), end - p);
2956 				e += end - p;
2957 			}
2958 			*e = '\0';
2959 
2960 			zend_string_release_ex(lc_needle, 0);
2961 
2962 			return new_str;
2963 		}
2964 	} else if (ZSTR_LEN(needle) > ZSTR_LEN(haystack)) {
2965 nothing_todo:
2966 		return zend_string_copy(haystack);
2967 	} else {
2968 		lc_needle = zend_string_tolower(needle);
2969 
2970 		if (memcmp(lc_haystack, ZSTR_VAL(lc_needle), ZSTR_LEN(lc_needle))) {
2971 			zend_string_release_ex(lc_needle, 0);
2972 			goto nothing_todo;
2973 		}
2974 		zend_string_release_ex(lc_needle, 0);
2975 
2976 		new_str = zend_string_init(str, str_len, 0);
2977 
2978 		(*replace_count)++;
2979 		return new_str;
2980 	}
2981 }
2982 /* }}} */
2983 
2984 /* {{{ php_str_to_str */
2985 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)
2986 {
2987 	zend_string *new_str;
2988 
2989 	if (needle_len < length) {
2990 		const char *end;
2991 		const char *s, *p;
2992 		char *e, *r;
2993 
2994 		if (needle_len == str_len) {
2995 			new_str = zend_string_init(haystack, length, 0);
2996 			end = ZSTR_VAL(new_str) + length;
2997 			for (p = ZSTR_VAL(new_str); (r = (char*)php_memnstr(p, needle, needle_len, end)); p = r + needle_len) {
2998 				memcpy(r, str, str_len);
2999 			}
3000 			return new_str;
3001 		} else {
3002 			if (str_len < needle_len) {
3003 				new_str = zend_string_alloc(length, 0);
3004 			} else {
3005 				size_t count = 0;
3006 				const char *o = haystack;
3007 				const char *n = needle;
3008 				const char *endp = o + length;
3009 
3010 				while ((o = (char*)php_memnstr(o, n, needle_len, endp))) {
3011 					o += needle_len;
3012 					count++;
3013 				}
3014 				if (count == 0) {
3015 					/* Needle doesn't occur, shortcircuit the actual replacement. */
3016 					new_str = zend_string_init(haystack, length, 0);
3017 					return new_str;
3018 				} else {
3019 					if (str_len > needle_len) {
3020 						new_str = zend_string_safe_alloc(count, str_len - needle_len, length, 0);
3021 					} else {
3022 						new_str = zend_string_alloc(count * (str_len - needle_len) + length, 0);
3023 					}
3024 				}
3025 			}
3026 
3027 			s = e = ZSTR_VAL(new_str);
3028 			end = haystack + length;
3029 			for (p = haystack; (r = (char*)php_memnstr(p, needle, needle_len, end)); p = r + needle_len) {
3030 				memcpy(e, p, r - p);
3031 				e += r - p;
3032 				memcpy(e, str, str_len);
3033 				e += str_len;
3034 			}
3035 
3036 			if (p < end) {
3037 				memcpy(e, p, end - p);
3038 				e += end - p;
3039 			}
3040 
3041 			*e = '\0';
3042 			new_str = zend_string_truncate(new_str, e - s, 0);
3043 			return new_str;
3044 		}
3045 	} else if (needle_len > length || memcmp(haystack, needle, length)) {
3046 		new_str = zend_string_init(haystack, length, 0);
3047 		return new_str;
3048 	} else {
3049 		new_str = zend_string_init(str, str_len, 0);
3050 
3051 		return new_str;
3052 	}
3053 }
3054 /* }}} */
3055 
3056 /* {{{ Translates characters in str using given translation tables */
3057 PHP_FUNCTION(strtr)
3058 {
3059 	zend_string *str, *from_str = NULL;
3060 	HashTable *from_ht = NULL;
3061 	char *to = NULL;
3062 	size_t to_len = 0;
3063 
3064 	ZEND_PARSE_PARAMETERS_START(2, 3)
3065 		Z_PARAM_STR(str)
3066 		Z_PARAM_ARRAY_HT_OR_STR(from_ht, from_str)
3067 		Z_PARAM_OPTIONAL
3068 		Z_PARAM_STRING_OR_NULL(to, to_len)
3069 	ZEND_PARSE_PARAMETERS_END();
3070 
3071 	if (!to && from_ht == NULL) {
3072 		zend_argument_type_error(2, "must be of type array, string given");
3073 		RETURN_THROWS();
3074 	} else if (to && from_str == NULL) {
3075 		zend_argument_type_error(2, "must be of type string, array given");
3076 		RETURN_THROWS();
3077 	}
3078 
3079 	/* shortcut for empty string */
3080 	if (ZSTR_LEN(str) == 0) {
3081 		RETURN_EMPTY_STRING();
3082 	}
3083 
3084 	if (!to) {
3085 		if (zend_hash_num_elements(from_ht) < 1) {
3086 			RETURN_STR_COPY(str);
3087 		} else if (zend_hash_num_elements(from_ht) == 1) {
3088 			zend_long num_key;
3089 			zend_string *str_key, *tmp_str, *replace, *tmp_replace;
3090 			zval *entry;
3091 
3092 			ZEND_HASH_FOREACH_KEY_VAL(from_ht, num_key, str_key, entry) {
3093 				tmp_str = NULL;
3094 				if (UNEXPECTED(!str_key)) {
3095 					str_key = tmp_str = zend_long_to_str(num_key);
3096 				}
3097 				replace = zval_get_tmp_string(entry, &tmp_replace);
3098 				if (ZSTR_LEN(str_key) < 1) {
3099 					php_error_docref(NULL, E_WARNING, "Ignoring replacement of empty string");
3100 					RETVAL_STR_COPY(str);
3101 				} else if (ZSTR_LEN(str_key) == 1) {
3102 					RETVAL_STR(php_char_to_str_ex(str,
3103 								ZSTR_VAL(str_key)[0],
3104 								ZSTR_VAL(replace),
3105 								ZSTR_LEN(replace),
3106 								/* case_sensitive */ true,
3107 								NULL));
3108 				} else {
3109 					zend_long dummy;
3110 					RETVAL_STR(php_str_to_str_ex(str,
3111 								ZSTR_VAL(str_key), ZSTR_LEN(str_key),
3112 								ZSTR_VAL(replace), ZSTR_LEN(replace), &dummy));
3113 				}
3114 				zend_tmp_string_release(tmp_str);
3115 				zend_tmp_string_release(tmp_replace);
3116 				return;
3117 			} ZEND_HASH_FOREACH_END();
3118 		} else {
3119 			php_strtr_array(return_value, str, from_ht);
3120 		}
3121 	} else {
3122 		RETURN_STR(php_strtr_ex(str,
3123 				  ZSTR_VAL(from_str),
3124 				  to,
3125 				  MIN(ZSTR_LEN(from_str), to_len)));
3126 	}
3127 }
3128 /* }}} */
3129 
3130 /* {{{ Reverse a string */
3131 #ifdef ZEND_INTRIN_SSSE3_NATIVE
3132 #include <tmmintrin.h>
3133 #elif defined(__aarch64__) || defined(_M_ARM64)
3134 #include <arm_neon.h>
3135 #endif
3136 PHP_FUNCTION(strrev)
3137 {
3138 	zend_string *str;
3139 	const char *s, *e;
3140 	char *p;
3141 	zend_string *n;
3142 
3143 	ZEND_PARSE_PARAMETERS_START(1, 1)
3144 		Z_PARAM_STR(str)
3145 	ZEND_PARSE_PARAMETERS_END();
3146 
3147 	n = zend_string_alloc(ZSTR_LEN(str), 0);
3148 	p = ZSTR_VAL(n);
3149 
3150 	s = ZSTR_VAL(str);
3151 	e = s + ZSTR_LEN(str);
3152 	--e;
3153 #ifdef ZEND_INTRIN_SSSE3_NATIVE
3154 	if (e - s > 15) {
3155 		const __m128i map = _mm_set_epi8(
3156 				0, 1, 2, 3,
3157 				4, 5, 6, 7,
3158 				8, 9, 10, 11,
3159 				12, 13, 14, 15);
3160 		do {
3161 			const __m128i str = _mm_loadu_si128((__m128i *)(e - 15));
3162 			_mm_storeu_si128((__m128i *)p, _mm_shuffle_epi8(str, map));
3163 			p += 16;
3164 			e -= 16;
3165 		} while (e - s > 15);
3166 	}
3167 #elif defined(__aarch64__)
3168 	if (e - s > 15) {
3169 		do {
3170 			const uint8x16_t str = vld1q_u8((uint8_t *)(e - 15));
3171 			/* Synthesize rev128 with a rev64 + ext. */
3172 			const uint8x16_t rev = vrev64q_u8(str);
3173 			const uint8x16_t ext = (uint8x16_t)
3174 				vextq_u64((uint64x2_t)rev, (uint64x2_t)rev, 1);
3175 			vst1q_u8((uint8_t *)p, ext);
3176 			p += 16;
3177 			e -= 16;
3178 		} while (e - s > 15);
3179 	}
3180 #elif defined(_M_ARM64)
3181 	if (e - s > 15) {
3182 		do {
3183 			const __n128 str = vld1q_u8((uint8_t *)(e - 15));
3184 			/* Synthesize rev128 with a rev64 + ext. */
3185 			/* strange force cast limit on windows: you cannot convert anything */
3186 			const __n128 rev = vrev64q_u8(str);
3187 			const __n128 ext = vextq_u64(rev, rev, 1);
3188 			vst1q_u8((uint8_t *)p, ext);
3189 			p += 16;
3190 			e -= 16;
3191 		} while (e - s > 15);
3192 	}
3193 #endif
3194 	while (e >= s) {
3195 		*p++ = *e--;
3196 	}
3197 
3198 	*p = '\0';
3199 
3200 	RETVAL_NEW_STR(n);
3201 }
3202 /* }}} */
3203 
3204 /* {{{ php_similar_str */
3205 static void php_similar_str(const char *txt1, size_t len1, const char *txt2, size_t len2, size_t *pos1, size_t *pos2, size_t *max, size_t *count)
3206 {
3207 	const char *p, *q;
3208 	const char *end1 = (char *) txt1 + len1;
3209 	const char *end2 = (char *) txt2 + len2;
3210 	size_t l;
3211 
3212 	*max = 0;
3213 	*count = 0;
3214 	for (p = (char *) txt1; p < end1; p++) {
3215 		for (q = (char *) txt2; q < end2; q++) {
3216 			for (l = 0; (p + l < end1) && (q + l < end2) && (p[l] == q[l]); l++);
3217 			if (l > *max) {
3218 				*max = l;
3219 				*count += 1;
3220 				*pos1 = p - txt1;
3221 				*pos2 = q - txt2;
3222 			}
3223 		}
3224 	}
3225 }
3226 /* }}} */
3227 
3228 /* {{{ php_similar_char */
3229 static size_t php_similar_char(const char *txt1, size_t len1, const char *txt2, size_t len2)
3230 {
3231 	size_t sum;
3232 	size_t pos1 = 0, pos2 = 0, max, count;
3233 
3234 	php_similar_str(txt1, len1, txt2, len2, &pos1, &pos2, &max, &count);
3235 	if ((sum = max)) {
3236 		if (pos1 && pos2 && count > 1) {
3237 			sum += php_similar_char(txt1, pos1,
3238 									txt2, pos2);
3239 		}
3240 		if ((pos1 + max < len1) && (pos2 + max < len2)) {
3241 			sum += php_similar_char(txt1 + pos1 + max, len1 - pos1 - max,
3242 									txt2 + pos2 + max, len2 - pos2 - max);
3243 		}
3244 	}
3245 
3246 	return sum;
3247 }
3248 /* }}} */
3249 
3250 /* {{{ Calculates the similarity between two strings */
3251 PHP_FUNCTION(similar_text)
3252 {
3253 	zend_string *t1, *t2;
3254 	zval *percent = NULL;
3255 	bool compute_percentage = ZEND_NUM_ARGS() >= 3;
3256 	size_t sim;
3257 
3258 	ZEND_PARSE_PARAMETERS_START(2, 3)
3259 		Z_PARAM_STR(t1)
3260 		Z_PARAM_STR(t2)
3261 		Z_PARAM_OPTIONAL
3262 		Z_PARAM_ZVAL(percent)
3263 	ZEND_PARSE_PARAMETERS_END();
3264 
3265 	if (ZSTR_LEN(t1) + ZSTR_LEN(t2) == 0) {
3266 		if (compute_percentage) {
3267 			ZEND_TRY_ASSIGN_REF_DOUBLE(percent, 0);
3268 		}
3269 
3270 		RETURN_LONG(0);
3271 	}
3272 
3273 	sim = php_similar_char(ZSTR_VAL(t1), ZSTR_LEN(t1), ZSTR_VAL(t2), ZSTR_LEN(t2));
3274 
3275 	if (compute_percentage) {
3276 		ZEND_TRY_ASSIGN_REF_DOUBLE(percent, sim * 200.0 / (ZSTR_LEN(t1) + ZSTR_LEN(t2)));
3277 	}
3278 
3279 	RETURN_LONG(sim);
3280 }
3281 /* }}} */
3282 
3283 /* {{{ Escapes all chars mentioned in charlist with backslash. It creates octal representations if asked to backslash characters with 8th bit set or with ASCII<32 (except '\n', '\r', '\t' etc...) */
3284 PHP_FUNCTION(addcslashes)
3285 {
3286 	zend_string *str, *what;
3287 
3288 	ZEND_PARSE_PARAMETERS_START(2, 2)
3289 		Z_PARAM_STR(str)
3290 		Z_PARAM_STR(what)
3291 	ZEND_PARSE_PARAMETERS_END();
3292 
3293 	if (ZSTR_LEN(str) == 0) {
3294 		RETURN_EMPTY_STRING();
3295 	}
3296 
3297 	if (ZSTR_LEN(what) == 0) {
3298 		RETURN_STR_COPY(str);
3299 	}
3300 
3301 	RETURN_STR(php_addcslashes_str(ZSTR_VAL(str), ZSTR_LEN(str), ZSTR_VAL(what), ZSTR_LEN(what)));
3302 }
3303 /* }}} */
3304 
3305 /* {{{ Escapes single quote, double quotes and backslash characters in a string with backslashes */
3306 PHP_FUNCTION(addslashes)
3307 {
3308 	zend_string *str;
3309 
3310 	ZEND_PARSE_PARAMETERS_START(1, 1)
3311 		Z_PARAM_STR(str)
3312 	ZEND_PARSE_PARAMETERS_END();
3313 
3314 	if (ZSTR_LEN(str) == 0) {
3315 		RETURN_EMPTY_STRING();
3316 	}
3317 
3318 	RETURN_STR(php_addslashes(str));
3319 }
3320 /* }}} */
3321 
3322 /* {{{ Strips backslashes from a string. Uses C-style conventions */
3323 PHP_FUNCTION(stripcslashes)
3324 {
3325 	zend_string *str;
3326 
3327 	ZEND_PARSE_PARAMETERS_START(1, 1)
3328 		Z_PARAM_STR(str)
3329 	ZEND_PARSE_PARAMETERS_END();
3330 
3331 	ZVAL_STRINGL(return_value, ZSTR_VAL(str), ZSTR_LEN(str));
3332 	php_stripcslashes(Z_STR_P(return_value));
3333 }
3334 /* }}} */
3335 
3336 /* {{{ Strips backslashes from a string */
3337 PHP_FUNCTION(stripslashes)
3338 {
3339 	zend_string *str;
3340 
3341 	ZEND_PARSE_PARAMETERS_START(1, 1)
3342 		Z_PARAM_STR(str)
3343 	ZEND_PARSE_PARAMETERS_END();
3344 
3345 	ZVAL_STRINGL(return_value, ZSTR_VAL(str), ZSTR_LEN(str));
3346 	php_stripslashes(Z_STR_P(return_value));
3347 }
3348 /* }}} */
3349 
3350 /* {{{ php_stripcslashes */
3351 PHPAPI void php_stripcslashes(zend_string *str)
3352 {
3353 	const char *source, *end;
3354 	char *target;
3355 	size_t  nlen = ZSTR_LEN(str), i;
3356 	char numtmp[4];
3357 
3358 	for (source = (char*)ZSTR_VAL(str), end = source + ZSTR_LEN(str), target = ZSTR_VAL(str); source < end; source++) {
3359 		if (*source == '\\' && source + 1 < end) {
3360 			source++;
3361 			switch (*source) {
3362 				case 'n':  *target++='\n'; nlen--; break;
3363 				case 'r':  *target++='\r'; nlen--; break;
3364 				case 'a':  *target++='\a'; nlen--; break;
3365 				case 't':  *target++='\t'; nlen--; break;
3366 				case 'v':  *target++='\v'; nlen--; break;
3367 				case 'b':  *target++='\b'; nlen--; break;
3368 				case 'f':  *target++='\f'; nlen--; break;
3369 				case '\\': *target++='\\'; nlen--; break;
3370 				case 'x':
3371 					if (source+1 < end && isxdigit((int)(*(source+1)))) {
3372 						numtmp[0] = *++source;
3373 						if (source+1 < end && isxdigit((int)(*(source+1)))) {
3374