1/*
2   +----------------------------------------------------------------------+
3   | PHP Version 5                                                        |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 1997-2014 The PHP Group                                |
6   +----------------------------------------------------------------------+
7   | This source file is subject to version 3.01 of the PHP license,      |
8   | that is bundled with this package in the file LICENSE, and is        |
9   | available through the world-wide-web at the following url:           |
10   | http://www.php.net/license/3_01.txt                                  |
11   | If you did not receive a copy of the PHP license and are unable to   |
12   | obtain it through the world-wide-web, please send a note to          |
13   | license@php.net so we can mail you a copy immediately.               |
14   +----------------------------------------------------------------------+
15   | Authors: Rasmus Lerdorf <rasmus@php.net>                             |
16   |          Stig S�ther Bakken <ssb@php.net>                            |
17   |          Zeev Suraski <zeev@zend.com>                                |
18   +----------------------------------------------------------------------+
19 */
20
21/* $Id$ */
22
23/* Synced with php 3.0 revision 1.193 1999-06-16 [ssb] */
24
25#include <stdio.h>
26#include "php.h"
27#include "php_rand.h"
28#include "php_string.h"
29#include "php_variables.h"
30#ifdef HAVE_LOCALE_H
31# include <locale.h>
32#endif
33#ifdef HAVE_LANGINFO_H
34# include <langinfo.h>
35#endif
36#ifdef HAVE_MONETARY_H
37# include <monetary.h>
38#endif
39/*
40 * This define is here because some versions of libintl redefine setlocale
41 * to point to libintl_setlocale.  That's a ridiculous thing to do as far
42 * as I am concerned, but with this define and the subsequent undef we
43 * limit the damage to just the actual setlocale() call in this file
44 * without turning zif_setlocale into zif_libintl_setlocale.  -Rasmus
45 */
46#define php_my_setlocale setlocale
47#ifdef HAVE_LIBINTL
48# include <libintl.h> /* For LC_MESSAGES */
49 #ifdef setlocale
50 # undef setlocale
51 #endif
52#endif
53
54#include "scanf.h"
55#include "zend_API.h"
56#include "zend_execute.h"
57#include "php_globals.h"
58#include "basic_functions.h"
59#include "php_smart_str.h"
60#include <Zend/zend_exceptions.h>
61#ifdef ZTS
62#include "TSRM.h"
63#endif
64
65/* For str_getcsv() support */
66#include "ext/standard/file.h"
67
68#define STR_PAD_LEFT            0
69#define STR_PAD_RIGHT           1
70#define STR_PAD_BOTH            2
71#define PHP_PATHINFO_DIRNAME    1
72#define PHP_PATHINFO_BASENAME   2
73#define PHP_PATHINFO_EXTENSION  4
74#define PHP_PATHINFO_FILENAME   8
75#define PHP_PATHINFO_ALL    (PHP_PATHINFO_DIRNAME | PHP_PATHINFO_BASENAME | PHP_PATHINFO_EXTENSION | PHP_PATHINFO_FILENAME)
76
77#define STR_STRSPN              0
78#define STR_STRCSPN             1
79
80/* {{{ register_string_constants
81 */
82void register_string_constants(INIT_FUNC_ARGS)
83{
84    REGISTER_LONG_CONSTANT("STR_PAD_LEFT", STR_PAD_LEFT, CONST_CS | CONST_PERSISTENT);
85    REGISTER_LONG_CONSTANT("STR_PAD_RIGHT", STR_PAD_RIGHT, CONST_CS | CONST_PERSISTENT);
86    REGISTER_LONG_CONSTANT("STR_PAD_BOTH", STR_PAD_BOTH, CONST_CS | CONST_PERSISTENT);
87    REGISTER_LONG_CONSTANT("PATHINFO_DIRNAME", PHP_PATHINFO_DIRNAME, CONST_CS | CONST_PERSISTENT);
88    REGISTER_LONG_CONSTANT("PATHINFO_BASENAME", PHP_PATHINFO_BASENAME, CONST_CS | CONST_PERSISTENT);
89    REGISTER_LONG_CONSTANT("PATHINFO_EXTENSION", PHP_PATHINFO_EXTENSION, CONST_CS | CONST_PERSISTENT);
90    REGISTER_LONG_CONSTANT("PATHINFO_FILENAME", PHP_PATHINFO_FILENAME, CONST_CS | CONST_PERSISTENT);
91
92#ifdef HAVE_LOCALECONV
93    /* If last members of struct lconv equal CHAR_MAX, no grouping is done */
94
95/* This is bad, but since we are going to be hardcoding in the POSIX stuff anyway... */
96# ifndef HAVE_LIMITS_H
97# define CHAR_MAX 127
98# endif
99
100    REGISTER_LONG_CONSTANT("CHAR_MAX", CHAR_MAX, CONST_CS | CONST_PERSISTENT);
101#endif
102
103#ifdef HAVE_LOCALE_H
104    REGISTER_LONG_CONSTANT("LC_CTYPE", LC_CTYPE, CONST_CS | CONST_PERSISTENT);
105    REGISTER_LONG_CONSTANT("LC_NUMERIC", LC_NUMERIC, CONST_CS | CONST_PERSISTENT);
106    REGISTER_LONG_CONSTANT("LC_TIME", LC_TIME, CONST_CS | CONST_PERSISTENT);
107    REGISTER_LONG_CONSTANT("LC_COLLATE", LC_COLLATE, CONST_CS | CONST_PERSISTENT);
108    REGISTER_LONG_CONSTANT("LC_MONETARY", LC_MONETARY, CONST_CS | CONST_PERSISTENT);
109    REGISTER_LONG_CONSTANT("LC_ALL", LC_ALL, CONST_CS | CONST_PERSISTENT);
110# ifdef LC_MESSAGES
111    REGISTER_LONG_CONSTANT("LC_MESSAGES", LC_MESSAGES, CONST_CS | CONST_PERSISTENT);
112# endif
113#endif
114
115}
116/* }}} */
117
118int php_tag_find(char *tag, size_t len, char *set);
119
120/* this is read-only, so it's ok */
121static char hexconvtab[] = "0123456789abcdef";
122
123/* localeconv mutex */
124#ifdef ZTS
125static MUTEX_T locale_mutex = NULL;
126#endif
127
128/* {{{ php_bin2hex
129 */
130static zend_string *php_bin2hex(const unsigned char *old, const size_t oldlen)
131{
132    zend_string *result;
133    size_t i, j;
134
135    result = zend_string_safe_alloc(oldlen, 2 * sizeof(char), 0, 0);
136
137    for (i = j = 0; i < oldlen; i++) {
138        result->val[j++] = hexconvtab[old[i] >> 4];
139        result->val[j++] = hexconvtab[old[i] & 15];
140    }
141    result->val[j] = '\0';
142
143    return result;
144}
145/* }}} */
146
147/* {{{ php_hex2bin
148 */
149static zend_string *php_hex2bin(const unsigned char *old, const size_t oldlen)
150{
151    size_t target_length = oldlen >> 1;
152    zend_string *str = zend_string_alloc(target_length, 0);
153    unsigned char *ret = (unsigned char *)str->val;
154    size_t i, j;
155
156    for (i = j = 0; i < target_length; i++) {
157        unsigned char c = old[j++];
158        unsigned char d;
159
160        if (c >= '0' && c <= '9') {
161            d = (c - '0') << 4;
162        } else if (c >= 'a' && c <= 'f') {
163            d = (c - 'a' + 10) << 4;
164        } else if (c >= 'A' && c <= 'F') {
165            d = (c - 'A' + 10) << 4;
166        } else {
167            zend_string_free(str);
168            return NULL;
169        }
170        c = old[j++];
171        if (c >= '0' && c <= '9') {
172            d |= c - '0';
173        } else if (c >= 'a' && c <= 'f') {
174            d |= c - 'a' + 10;
175        } else if (c >= 'A' && c <= 'F') {
176            d |= c - 'A' + 10;
177        } else {
178            zend_string_free(str);
179            return NULL;
180        }
181        ret[i] = d;
182    }
183    ret[i] = '\0';
184
185    return str;
186}
187/* }}} */
188
189#ifdef HAVE_LOCALECONV
190/* {{{ localeconv_r
191 * glibc's localeconv is not reentrant, so lets make it so ... sorta */
192PHPAPI struct lconv *localeconv_r(struct lconv *out)
193{
194    struct lconv *res;
195
196# ifdef ZTS
197    tsrm_mutex_lock( locale_mutex );
198# endif
199
200    /* localeconv doesn't return an error condition */
201    res = localeconv();
202
203    *out = *res;
204
205# ifdef ZTS
206    tsrm_mutex_unlock( locale_mutex );
207# endif
208
209    return out;
210}
211/* }}} */
212
213# ifdef ZTS
214/* {{{ PHP_MINIT_FUNCTION
215 */
216PHP_MINIT_FUNCTION(localeconv)
217{
218    locale_mutex = tsrm_mutex_alloc();
219    return SUCCESS;
220}
221/* }}} */
222
223/* {{{ PHP_MSHUTDOWN_FUNCTION
224 */
225PHP_MSHUTDOWN_FUNCTION(localeconv)
226{
227    tsrm_mutex_free( locale_mutex );
228    locale_mutex = NULL;
229    return SUCCESS;
230}
231/* }}} */
232# endif
233#endif
234
235/* {{{ proto string bin2hex(string data)
236   Converts the binary representation of data to hex */
237PHP_FUNCTION(bin2hex)
238{
239    zend_string *result;
240    zend_string *data;
241
242    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "S", &data) == FAILURE) {
243        return;
244    }
245
246    result = php_bin2hex((unsigned char *)data->val, data->len);
247
248    if (!result) {
249        RETURN_FALSE;
250    }
251
252    RETURN_STR(result);
253}
254/* }}} */
255
256/* {{{ proto string hex2bin(string data)
257   Converts the hex representation of data to binary */
258PHP_FUNCTION(hex2bin)
259{
260    zend_string *result, *data;
261
262    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "S", &data) == FAILURE) {
263        return;
264    }
265
266    if (data->len % 2 != 0) {
267        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Hexadecimal input string must have an even length");
268        RETURN_FALSE;
269    }
270
271    result = php_hex2bin((unsigned char *)data->val, data->len);
272
273    if (!result) {
274        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Input string must be hexadecimal string");
275        RETURN_FALSE;
276    }
277
278    RETVAL_STR(result);
279}
280/* }}} */
281
282static void php_spn_common_handler(INTERNAL_FUNCTION_PARAMETERS, int behavior) /* {{{ */
283{
284    zend_string *s11, *s22;
285    zend_long start = 0, len = 0;
286
287    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "SS|ll", &s11,
288                &s22, &start, &len) == FAILURE) {
289        return;
290    }
291
292    if (ZEND_NUM_ARGS() < 4) {
293        len = s11->len;
294    }
295
296    /* look at substr() function for more information */
297
298    if (start < 0) {
299        start += (zend_long)s11->len;
300        if (start < 0) {
301            start = 0;
302        }
303    } else if ((size_t)start > s11->len) {
304        RETURN_FALSE;
305    }
306
307    if (len < 0) {
308        len += (s11->len - start);
309        if (len < 0) {
310            len = 0;
311        }
312    }
313
314    if (len > (zend_long)s11->len - start) {
315        len = s11->len - start;
316    }
317
318    if(len == 0) {
319        RETURN_LONG(0);
320    }
321
322    if (behavior == STR_STRSPN) {
323        RETURN_LONG(php_strspn(s11->val + start /*str1_start*/,
324                        s22->val /*str2_start*/,
325                        s11->val + start + len /*str1_end*/,
326                        s22->val + s22->len /*str2_end*/));
327    } else if (behavior == STR_STRCSPN) {
328        RETURN_LONG(php_strcspn(s11->val + start /*str1_start*/,
329                        s22->val /*str2_start*/,
330                        s11->val + start + len /*str1_end*/,
331                        s22->val + s22->len /*str2_end*/));
332    }
333
334}
335/* }}} */
336
337/* {{{ proto int strspn(string str, string mask [, start [, len]])
338   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) */
339PHP_FUNCTION(strspn)
340{
341    php_spn_common_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, STR_STRSPN);
342}
343/* }}} */
344
345/* {{{ proto int strcspn(string str, string mask [, start [, len]])
346   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) */
347PHP_FUNCTION(strcspn)
348{
349    php_spn_common_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, STR_STRCSPN);
350}
351/* }}} */
352
353/* {{{ PHP_MINIT_FUNCTION(nl_langinfo) */
354#if HAVE_NL_LANGINFO
355PHP_MINIT_FUNCTION(nl_langinfo)
356{
357#define REGISTER_NL_LANGINFO_CONSTANT(x)    REGISTER_LONG_CONSTANT(#x, x, CONST_CS | CONST_PERSISTENT)
358#ifdef ABDAY_1
359    REGISTER_NL_LANGINFO_CONSTANT(ABDAY_1);
360    REGISTER_NL_LANGINFO_CONSTANT(ABDAY_2);
361    REGISTER_NL_LANGINFO_CONSTANT(ABDAY_3);
362    REGISTER_NL_LANGINFO_CONSTANT(ABDAY_4);
363    REGISTER_NL_LANGINFO_CONSTANT(ABDAY_5);
364    REGISTER_NL_LANGINFO_CONSTANT(ABDAY_6);
365    REGISTER_NL_LANGINFO_CONSTANT(ABDAY_7);
366#endif
367#ifdef DAY_1
368    REGISTER_NL_LANGINFO_CONSTANT(DAY_1);
369    REGISTER_NL_LANGINFO_CONSTANT(DAY_2);
370    REGISTER_NL_LANGINFO_CONSTANT(DAY_3);
371    REGISTER_NL_LANGINFO_CONSTANT(DAY_4);
372    REGISTER_NL_LANGINFO_CONSTANT(DAY_5);
373    REGISTER_NL_LANGINFO_CONSTANT(DAY_6);
374    REGISTER_NL_LANGINFO_CONSTANT(DAY_7);
375#endif
376#ifdef ABMON_1
377    REGISTER_NL_LANGINFO_CONSTANT(ABMON_1);
378    REGISTER_NL_LANGINFO_CONSTANT(ABMON_2);
379    REGISTER_NL_LANGINFO_CONSTANT(ABMON_3);
380    REGISTER_NL_LANGINFO_CONSTANT(ABMON_4);
381    REGISTER_NL_LANGINFO_CONSTANT(ABMON_5);
382    REGISTER_NL_LANGINFO_CONSTANT(ABMON_6);
383    REGISTER_NL_LANGINFO_CONSTANT(ABMON_7);
384    REGISTER_NL_LANGINFO_CONSTANT(ABMON_8);
385    REGISTER_NL_LANGINFO_CONSTANT(ABMON_9);
386    REGISTER_NL_LANGINFO_CONSTANT(ABMON_10);
387    REGISTER_NL_LANGINFO_CONSTANT(ABMON_11);
388    REGISTER_NL_LANGINFO_CONSTANT(ABMON_12);
389#endif
390#ifdef MON_1
391    REGISTER_NL_LANGINFO_CONSTANT(MON_1);
392    REGISTER_NL_LANGINFO_CONSTANT(MON_2);
393    REGISTER_NL_LANGINFO_CONSTANT(MON_3);
394    REGISTER_NL_LANGINFO_CONSTANT(MON_4);
395    REGISTER_NL_LANGINFO_CONSTANT(MON_5);
396    REGISTER_NL_LANGINFO_CONSTANT(MON_6);
397    REGISTER_NL_LANGINFO_CONSTANT(MON_7);
398    REGISTER_NL_LANGINFO_CONSTANT(MON_8);
399    REGISTER_NL_LANGINFO_CONSTANT(MON_9);
400    REGISTER_NL_LANGINFO_CONSTANT(MON_10);
401    REGISTER_NL_LANGINFO_CONSTANT(MON_11);
402    REGISTER_NL_LANGINFO_CONSTANT(MON_12);
403#endif
404#ifdef AM_STR
405    REGISTER_NL_LANGINFO_CONSTANT(AM_STR);
406#endif
407#ifdef PM_STR
408    REGISTER_NL_LANGINFO_CONSTANT(PM_STR);
409#endif
410#ifdef D_T_FMT
411    REGISTER_NL_LANGINFO_CONSTANT(D_T_FMT);
412#endif
413#ifdef D_FMT
414    REGISTER_NL_LANGINFO_CONSTANT(D_FMT);
415#endif
416#ifdef T_FMT
417    REGISTER_NL_LANGINFO_CONSTANT(T_FMT);
418#endif
419#ifdef T_FMT_AMPM
420    REGISTER_NL_LANGINFO_CONSTANT(T_FMT_AMPM);
421#endif
422#ifdef ERA
423    REGISTER_NL_LANGINFO_CONSTANT(ERA);
424#endif
425#ifdef ERA_YEAR
426    REGISTER_NL_LANGINFO_CONSTANT(ERA_YEAR);
427#endif
428#ifdef ERA_D_T_FMT
429    REGISTER_NL_LANGINFO_CONSTANT(ERA_D_T_FMT);
430#endif
431#ifdef ERA_D_FMT
432    REGISTER_NL_LANGINFO_CONSTANT(ERA_D_FMT);
433#endif
434#ifdef ERA_T_FMT
435    REGISTER_NL_LANGINFO_CONSTANT(ERA_T_FMT);
436#endif
437#ifdef ALT_DIGITS
438    REGISTER_NL_LANGINFO_CONSTANT(ALT_DIGITS);
439#endif
440#ifdef INT_CURR_SYMBOL
441    REGISTER_NL_LANGINFO_CONSTANT(INT_CURR_SYMBOL);
442#endif
443#ifdef CURRENCY_SYMBOL
444    REGISTER_NL_LANGINFO_CONSTANT(CURRENCY_SYMBOL);
445#endif
446#ifdef CRNCYSTR
447    REGISTER_NL_LANGINFO_CONSTANT(CRNCYSTR);
448#endif
449#ifdef MON_DECIMAL_POINT
450    REGISTER_NL_LANGINFO_CONSTANT(MON_DECIMAL_POINT);
451#endif
452#ifdef MON_THOUSANDS_SEP
453    REGISTER_NL_LANGINFO_CONSTANT(MON_THOUSANDS_SEP);
454#endif
455#ifdef MON_GROUPING
456    REGISTER_NL_LANGINFO_CONSTANT(MON_GROUPING);
457#endif
458#ifdef POSITIVE_SIGN
459    REGISTER_NL_LANGINFO_CONSTANT(POSITIVE_SIGN);
460#endif
461#ifdef NEGATIVE_SIGN
462    REGISTER_NL_LANGINFO_CONSTANT(NEGATIVE_SIGN);
463#endif
464#ifdef INT_FRAC_DIGITS
465    REGISTER_NL_LANGINFO_CONSTANT(INT_FRAC_DIGITS);
466#endif
467#ifdef FRAC_DIGITS
468    REGISTER_NL_LANGINFO_CONSTANT(FRAC_DIGITS);
469#endif
470#ifdef P_CS_PRECEDES
471    REGISTER_NL_LANGINFO_CONSTANT(P_CS_PRECEDES);
472#endif
473#ifdef P_SEP_BY_SPACE
474    REGISTER_NL_LANGINFO_CONSTANT(P_SEP_BY_SPACE);
475#endif
476#ifdef N_CS_PRECEDES
477    REGISTER_NL_LANGINFO_CONSTANT(N_CS_PRECEDES);
478#endif
479#ifdef N_SEP_BY_SPACE
480    REGISTER_NL_LANGINFO_CONSTANT(N_SEP_BY_SPACE);
481#endif
482#ifdef P_SIGN_POSN
483    REGISTER_NL_LANGINFO_CONSTANT(P_SIGN_POSN);
484#endif
485#ifdef N_SIGN_POSN
486    REGISTER_NL_LANGINFO_CONSTANT(N_SIGN_POSN);
487#endif
488#ifdef DECIMAL_POINT
489    REGISTER_NL_LANGINFO_CONSTANT(DECIMAL_POINT);
490#endif
491#ifdef RADIXCHAR
492    REGISTER_NL_LANGINFO_CONSTANT(RADIXCHAR);
493#endif
494#ifdef THOUSANDS_SEP
495    REGISTER_NL_LANGINFO_CONSTANT(THOUSANDS_SEP);
496#endif
497#ifdef THOUSEP
498    REGISTER_NL_LANGINFO_CONSTANT(THOUSEP);
499#endif
500#ifdef GROUPING
501    REGISTER_NL_LANGINFO_CONSTANT(GROUPING);
502#endif
503#ifdef YESEXPR
504    REGISTER_NL_LANGINFO_CONSTANT(YESEXPR);
505#endif
506#ifdef NOEXPR
507    REGISTER_NL_LANGINFO_CONSTANT(NOEXPR);
508#endif
509#ifdef YESSTR
510    REGISTER_NL_LANGINFO_CONSTANT(YESSTR);
511#endif
512#ifdef NOSTR
513    REGISTER_NL_LANGINFO_CONSTANT(NOSTR);
514#endif
515#ifdef CODESET
516    REGISTER_NL_LANGINFO_CONSTANT(CODESET);
517#endif
518#undef REGISTER_NL_LANGINFO_CONSTANT
519    return SUCCESS;
520}
521/* }}} */
522
523/* {{{ proto string nl_langinfo(int item)
524   Query language and locale information */
525PHP_FUNCTION(nl_langinfo)
526{
527    zend_long item;
528    char *value;
529
530    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &item) == FAILURE) {
531        return;
532    }
533
534    switch(item) { /* {{{ */
535#ifdef ABDAY_1
536        case ABDAY_1:
537        case ABDAY_2:
538        case ABDAY_3:
539        case ABDAY_4:
540        case ABDAY_5:
541        case ABDAY_6:
542        case ABDAY_7:
543#endif
544#ifdef DAY_1
545        case DAY_1:
546        case DAY_2:
547        case DAY_3:
548        case DAY_4:
549        case DAY_5:
550        case DAY_6:
551        case DAY_7:
552#endif
553#ifdef ABMON_1
554        case ABMON_1:
555        case ABMON_2:
556        case ABMON_3:
557        case ABMON_4:
558        case ABMON_5:
559        case ABMON_6:
560        case ABMON_7:
561        case ABMON_8:
562        case ABMON_9:
563        case ABMON_10:
564        case ABMON_11:
565        case ABMON_12:
566#endif
567#ifdef MON_1
568        case MON_1:
569        case MON_2:
570        case MON_3:
571        case MON_4:
572        case MON_5:
573        case MON_6:
574        case MON_7:
575        case MON_8:
576        case MON_9:
577        case MON_10:
578        case MON_11:
579        case MON_12:
580#endif
581#ifdef AM_STR
582        case AM_STR:
583#endif
584#ifdef PM_STR
585        case PM_STR:
586#endif
587#ifdef D_T_FMT
588        case D_T_FMT:
589#endif
590#ifdef D_FMT
591        case D_FMT:
592#endif
593#ifdef T_FMT
594        case T_FMT:
595#endif
596#ifdef T_FMT_AMPM
597        case T_FMT_AMPM:
598#endif
599#ifdef ERA
600        case ERA:
601#endif
602#ifdef ERA_YEAR
603        case ERA_YEAR:
604#endif
605#ifdef ERA_D_T_FMT
606        case ERA_D_T_FMT:
607#endif
608#ifdef ERA_D_FMT
609        case ERA_D_FMT:
610#endif
611#ifdef ERA_T_FMT
612        case ERA_T_FMT:
613#endif
614#ifdef ALT_DIGITS
615        case ALT_DIGITS:
616#endif
617#ifdef INT_CURR_SYMBOL
618        case INT_CURR_SYMBOL:
619#endif
620#ifdef CURRENCY_SYMBOL
621        case CURRENCY_SYMBOL:
622#endif
623#ifdef CRNCYSTR
624        case CRNCYSTR:
625#endif
626#ifdef MON_DECIMAL_POINT
627        case MON_DECIMAL_POINT:
628#endif
629#ifdef MON_THOUSANDS_SEP
630        case MON_THOUSANDS_SEP:
631#endif
632#ifdef MON_GROUPING
633        case MON_GROUPING:
634#endif
635#ifdef POSITIVE_SIGN
636        case POSITIVE_SIGN:
637#endif
638#ifdef NEGATIVE_SIGN
639        case NEGATIVE_SIGN:
640#endif
641#ifdef INT_FRAC_DIGITS
642        case INT_FRAC_DIGITS:
643#endif
644#ifdef FRAC_DIGITS
645        case FRAC_DIGITS:
646#endif
647#ifdef P_CS_PRECEDES
648        case P_CS_PRECEDES:
649#endif
650#ifdef P_SEP_BY_SPACE
651        case P_SEP_BY_SPACE:
652#endif
653#ifdef N_CS_PRECEDES
654        case N_CS_PRECEDES:
655#endif
656#ifdef N_SEP_BY_SPACE
657        case N_SEP_BY_SPACE:
658#endif
659#ifdef P_SIGN_POSN
660        case P_SIGN_POSN:
661#endif
662#ifdef N_SIGN_POSN
663        case N_SIGN_POSN:
664#endif
665#ifdef DECIMAL_POINT
666        case DECIMAL_POINT:
667#elif defined(RADIXCHAR)
668        case RADIXCHAR:
669#endif
670#ifdef THOUSANDS_SEP
671        case THOUSANDS_SEP:
672#elif defined(THOUSEP)
673        case THOUSEP:
674#endif
675#ifdef GROUPING
676        case GROUPING:
677#endif
678#ifdef YESEXPR
679        case YESEXPR:
680#endif
681#ifdef NOEXPR
682        case NOEXPR:
683#endif
684#ifdef YESSTR
685        case YESSTR:
686#endif
687#ifdef NOSTR
688        case NOSTR:
689#endif
690#ifdef CODESET
691        case CODESET:
692#endif
693            break;
694        default:
695            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Item '" ZEND_LONG_FMT "' is not valid", item);
696            RETURN_FALSE;
697    }
698    /* }}} */
699
700    value = nl_langinfo(item);
701    if (value == NULL) {
702        RETURN_FALSE;
703    } else {
704        RETURN_STRING(value);
705    }
706}
707#endif
708/* }}} */
709
710#ifdef HAVE_STRCOLL
711/* {{{ proto int strcoll(string str1, string str2)
712   Compares two strings using the current locale */
713PHP_FUNCTION(strcoll)
714{
715    zend_string *s1, *s2;
716
717    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "SS", &s1, &s2) == FAILURE) {
718        return;
719    }
720
721    RETURN_LONG(strcoll((const char *) s1->val,
722                        (const char *) s2->val));
723}
724/* }}} */
725#endif
726
727/* {{{ php_charmask
728 * Fills a 256-byte bytemask with input. You can specify a range like 'a..z',
729 * it needs to be incrementing.
730 * Returns: FAILURE/SUCCESS whether the input was correct (i.e. no range errors)
731 */
732static inline int php_charmask(unsigned char *input, size_t len, char *mask TSRMLS_DC)
733{
734    unsigned char *end;
735    unsigned char c;
736    int result = SUCCESS;
737
738    memset(mask, 0, 256);
739    for (end = input+len; input < end; input++) {
740        c=*input;
741        if ((input+3 < end) && input[1] == '.' && input[2] == '.'
742                && input[3] >= c) {
743            memset(mask+c, 1, input[3] - c + 1);
744            input+=3;
745        } else if ((input+1 < end) && input[0] == '.' && input[1] == '.') {
746            /* Error, try to be as helpful as possible:
747               (a range ending/starting with '.' won't be captured here) */
748            if (end-len >= input) { /* there was no 'left' char */
749                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid '..'-range, no character to the left of '..'");
750                result = FAILURE;
751                continue;
752            }
753            if (input+2 >= end) { /* there is no 'right' char */
754                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid '..'-range, no character to the right of '..'");
755                result = FAILURE;
756                continue;
757            }
758            if (input[-1] > input[2]) { /* wrong order */
759                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid '..'-range, '..'-range needs to be incrementing");
760                result = FAILURE;
761                continue;
762            }
763            /* FIXME: better error (a..b..c is the only left possibility?) */
764            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid '..'-range");
765            result = FAILURE;
766            continue;
767        } else {
768            mask[c]=1;
769        }
770    }
771    return result;
772}
773/* }}} */
774
775/* {{{ php_trim()
776 * mode 1 : trim left
777 * mode 2 : trim right
778 * mode 3 : trim left and right
779 * what indicates which chars are to be trimmed. NULL->default (' \t\n\r\v\0')
780 */
781PHPAPI char *php_trim(char *c, size_t len, char *what, size_t what_len, zval *return_value, int mode TSRMLS_DC)
782{
783    register size_t i;
784    size_t trimmed = 0;
785    char mask[256];
786
787    if (what) {
788        php_charmask((unsigned char*)what, what_len, mask TSRMLS_CC);
789    } else {
790        php_charmask((unsigned char*)" \n\r\t\v\0", 6, mask TSRMLS_CC);
791    }
792
793    if (mode & 1) {
794        for (i = 0; i < len; i++) {
795            if (mask[(unsigned char)c[i]]) {
796                trimmed++;
797            } else {
798                break;
799            }
800        }
801        len -= trimmed;
802        c += trimmed;
803    }
804    if (mode & 2) {
805        if (len > 0) {
806            i = len - 1;
807            do {
808                if (mask[(unsigned char)c[i]]) {
809                    len--;
810                } else {
811                    break;
812                }
813            } while (i-- != 0);
814        }
815    }
816
817    if (return_value) {
818        RETVAL_STRINGL(c, len);
819    } else {
820        return estrndup(c, len);
821    }
822    return "";
823}
824/* }}} */
825
826/* {{{ php_do_trim
827 * Base for trim(), rtrim() and ltrim() functions.
828 */
829static void php_do_trim(INTERNAL_FUNCTION_PARAMETERS, int mode)
830{
831    zend_string *str;
832    zend_string *what = NULL;
833
834#ifndef FAST_ZPP
835    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "S|S", &str, &what) == FAILURE) {
836        return;
837    }
838#else
839    ZEND_PARSE_PARAMETERS_START(1, 2)
840        Z_PARAM_STR(str)
841        Z_PARAM_OPTIONAL
842        Z_PARAM_STR(what)
843    ZEND_PARSE_PARAMETERS_END();
844#endif
845
846    php_trim(str->val, str->len, (what ? what->val : NULL), (what ? what->len : 0), return_value, mode TSRMLS_CC);
847}
848/* }}} */
849
850/* {{{ proto string trim(string str [, string character_mask])
851   Strips whitespace from the beginning and end of a string */
852PHP_FUNCTION(trim)
853{
854    php_do_trim(INTERNAL_FUNCTION_PARAM_PASSTHRU, 3);
855}
856/* }}} */
857
858/* {{{ proto string rtrim(string str [, string character_mask])
859   Removes trailing whitespace */
860PHP_FUNCTION(rtrim)
861{
862    php_do_trim(INTERNAL_FUNCTION_PARAM_PASSTHRU, 2);
863}
864/* }}} */
865
866/* {{{ proto string ltrim(string str [, string character_mask])
867   Strips whitespace from the beginning of a string */
868PHP_FUNCTION(ltrim)
869{
870    php_do_trim(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
871}
872/* }}} */
873
874/* {{{ proto string wordwrap(string str [, int width [, string break [, boolean cut]]])
875   Wraps buffer to selected number of characters using string break char */
876PHP_FUNCTION(wordwrap)
877{
878    zend_string *text;
879    char *breakchar = "\n";
880    size_t newtextlen, chk, breakchar_len = 1;
881    size_t alloced;
882    zend_long current = 0, laststart = 0, lastspace = 0;
883    zend_long linelength = 75;
884    zend_bool docut = 0;
885    zend_string *newtext;
886
887    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "S|lsb", &text, &linelength, &breakchar, &breakchar_len, &docut) == FAILURE) {
888        return;
889    }
890
891    if (text->len == 0) {
892        RETURN_EMPTY_STRING();
893    }
894
895    if (breakchar_len == 0) {
896        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Break string cannot be empty");
897        RETURN_FALSE;
898    }
899
900    if (linelength == 0 && docut) {
901        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Can't force cut when width is zero");
902        RETURN_FALSE;
903    }
904
905    /* Special case for a single-character break as it needs no
906       additional storage space */
907    if (breakchar_len == 1 && !docut) {
908        newtext = zend_string_init(text->val, text->len, 0);
909
910        laststart = lastspace = 0;
911        for (current = 0; current < text->len; current++) {
912            if (text->val[current] == breakchar[0]) {
913                laststart = lastspace = current + 1;
914            } else if (text->val[current] == ' ') {
915                if (current - laststart >= linelength) {
916                    newtext->val[current] = breakchar[0];
917                    laststart = current + 1;
918                }
919                lastspace = current;
920            } else if (current - laststart >= linelength && laststart != lastspace) {
921                newtext->val[lastspace] = breakchar[0];
922                laststart = lastspace + 1;
923            }
924        }
925
926        RETURN_NEW_STR(newtext);
927    } else {
928        /* Multiple character line break or forced cut */
929        if (linelength > 0) {
930            chk = (size_t)(text->len/linelength + 1);
931            newtext = zend_string_alloc(chk * breakchar_len + text->len, 0);
932            alloced = text->len + chk * breakchar_len + 1;
933        } else {
934            chk = text->len;
935            alloced = text->len * (breakchar_len + 1) + 1;
936            newtext = zend_string_alloc(text->len * (breakchar_len + 1), 0);
937        }
938
939        /* now keep track of the actual new text length */
940        newtextlen = 0;
941
942        laststart = lastspace = 0;
943        for (current = 0; current < text->len; current++) {
944            if (chk <= 0) {
945                alloced += (size_t) (((text->len - current + 1)/linelength + 1) * breakchar_len) + 1;
946                newtext = zend_string_realloc(newtext, alloced, 0);
947                chk = (size_t) ((text->len - current)/linelength) + 1;
948            }
949            /* when we hit an existing break, copy to new buffer, and
950             * fix up laststart and lastspace */
951            if (text->val[current] == breakchar[0]
952                && current + breakchar_len < text->len
953                && !strncmp(text->val+current, breakchar, breakchar_len)) {
954                memcpy(newtext->val + newtextlen, text->val + laststart, current - laststart + breakchar_len);
955                newtextlen += current - laststart + breakchar_len;
956                current += breakchar_len - 1;
957                laststart = lastspace = current + 1;
958                chk--;
959            }
960            /* if it is a space, check if it is at the line boundary,
961             * copy and insert a break, or just keep track of it */
962            else if (text->val[current] == ' ') {
963                if (current - laststart >= linelength) {
964                    memcpy(newtext->val + newtextlen, text->val + laststart, current - laststart);
965                    newtextlen += current - laststart;
966                    memcpy(newtext->val + newtextlen, breakchar, breakchar_len);
967                    newtextlen += breakchar_len;
968                    laststart = current + 1;
969                    chk--;
970                }
971                lastspace = current;
972            }
973            /* if we are cutting, and we've accumulated enough
974             * characters, and we haven't see a space for this line,
975             * copy and insert a break. */
976            else if (current - laststart >= linelength
977                    && docut && laststart >= lastspace) {
978                memcpy(newtext->val + newtextlen, text->val + laststart, current - laststart);
979                newtextlen += current - laststart;
980                memcpy(newtext->val + newtextlen, breakchar, breakchar_len);
981                newtextlen += breakchar_len;
982                laststart = lastspace = current;
983                chk--;
984            }
985            /* if the current word puts us over the linelength, copy
986             * back up until the last space, insert a break, and move
987             * up the laststart */
988            else if (current - laststart >= linelength
989                    && laststart < lastspace) {
990                memcpy(newtext->val + newtextlen, text->val + laststart, lastspace - laststart);
991                newtextlen += lastspace - laststart;
992                memcpy(newtext->val + newtextlen, breakchar, breakchar_len);
993                newtextlen += breakchar_len;
994                laststart = lastspace = lastspace + 1;
995                chk--;
996            }
997        }
998
999        /* copy over any stragglers */
1000        if (laststart != current) {
1001            memcpy(newtext->val + newtextlen, text->val + laststart, current - laststart);
1002            newtextlen += current - laststart;
1003        }
1004
1005        newtext->val[newtextlen] = '\0';
1006        /* free unused memory */
1007        newtext = zend_string_realloc(newtext, newtextlen, 0);
1008
1009        RETURN_NEW_STR(newtext);
1010    }
1011}
1012/* }}} */
1013
1014/* {{{ php_explode
1015 */
1016PHPAPI void php_explode(zval *delim, zval *str, zval *return_value, zend_long limit)
1017{
1018    char *p1, *p2, *endp;
1019
1020    endp = Z_STRVAL_P(str) + Z_STRLEN_P(str);
1021
1022    p1 = Z_STRVAL_P(str);
1023    p2 = (char*)php_memnstr(Z_STRVAL_P(str), Z_STRVAL_P(delim), Z_STRLEN_P(delim), endp);
1024
1025    if (p2 == NULL) {
1026        add_next_index_stringl(return_value, p1, Z_STRLEN_P(str));
1027    } else {
1028        do {
1029            add_next_index_stringl(return_value, p1, p2 - p1);
1030            p1 = p2 + Z_STRLEN_P(delim);
1031        } while ((p2 = (char*)php_memnstr(p1, Z_STRVAL_P(delim), Z_STRLEN_P(delim), endp)) != NULL &&
1032                 --limit > 1);
1033
1034        if (p1 <= endp)
1035            add_next_index_stringl(return_value, p1, endp-p1);
1036    }
1037}
1038/* }}} */
1039
1040/* {{{ php_explode_negative_limit
1041 */
1042PHPAPI void php_explode_negative_limit(zval *delim, zval *str, zval *return_value, zend_long limit)
1043{
1044#define EXPLODE_ALLOC_STEP 64
1045    char *p1, *p2, *endp;
1046
1047    endp = Z_STRVAL_P(str) + Z_STRLEN_P(str);
1048
1049    p1 = Z_STRVAL_P(str);
1050    p2 = (char*)php_memnstr(Z_STRVAL_P(str), Z_STRVAL_P(delim), Z_STRLEN_P(delim), endp);
1051
1052    if (p2 == NULL) {
1053        /*
1054        do nothing since limit <= -1, thus if only one chunk - 1 + (limit) <= 0
1055        by doing nothing we return empty array
1056        */
1057    } else {
1058        size_t allocated = EXPLODE_ALLOC_STEP, found = 0;
1059        zend_long i, to_return;
1060        char **positions = emalloc(allocated * sizeof(char *));
1061
1062        positions[found++] = p1;
1063        do {
1064            if (found >= allocated) {
1065                allocated = found + EXPLODE_ALLOC_STEP;/* make sure we have enough memory */
1066                positions = erealloc(positions, allocated*sizeof(char *));
1067            }
1068            positions[found++] = p1 = p2 + Z_STRLEN_P(delim);
1069        } while ((p2 = (char*)php_memnstr(p1, Z_STRVAL_P(delim), Z_STRLEN_P(delim), endp)) != NULL);
1070
1071        to_return = limit + found;
1072        /* limit is at least -1 therefore no need of bounds checking : i will be always less than found */
1073        for (i = 0;i < to_return;i++) { /* this checks also for to_return > 0 */
1074            add_next_index_stringl(return_value, positions[i],
1075                    (positions[i+1] - Z_STRLEN_P(delim)) - positions[i]);
1076        }
1077        efree(positions);
1078    }
1079#undef EXPLODE_ALLOC_STEP
1080}
1081/* }}} */
1082
1083/* {{{ proto array explode(string separator, string str [, int limit])
1084   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. */
1085PHP_FUNCTION(explode)
1086{
1087    zend_string *str, *delim;
1088    zend_long limit = ZEND_LONG_MAX; /* No limit */
1089    zval zdelim, zstr;
1090
1091#ifndef FAST_ZPP
1092    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "SS|l", &delim, &str, &limit) == FAILURE) {
1093        return;
1094    }
1095#else
1096    ZEND_PARSE_PARAMETERS_START(2, 3)
1097        Z_PARAM_STR(delim)
1098        Z_PARAM_STR(str)
1099        Z_PARAM_OPTIONAL
1100        Z_PARAM_LONG(limit)
1101    ZEND_PARSE_PARAMETERS_END();
1102#endif
1103
1104    if (delim->len == 0) {
1105        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Empty delimiter");
1106        RETURN_FALSE;
1107    }
1108
1109    array_init(return_value);
1110
1111    if (str->len == 0) {
1112        if (limit >= 0) {
1113            add_next_index_stringl(return_value, "", sizeof("") - 1);
1114        }
1115        return;
1116    }
1117
1118    ZVAL_STR(&zstr, str);
1119    ZVAL_STR(&zdelim, delim);
1120    if (limit > 1) {
1121        php_explode(&zdelim, &zstr, return_value, limit);
1122    } else if (limit < 0) {
1123        php_explode_negative_limit(&zdelim, &zstr, return_value, limit);
1124    } else {
1125        add_index_stringl(return_value, 0, str->val, str->len);
1126    }
1127}
1128/* }}} */
1129
1130/* {{{ proto string join(array src, string glue)
1131   An alias for implode */
1132/* }}} */
1133
1134/* {{{ php_implode
1135 */
1136PHPAPI void php_implode(zval *delim, zval *arr, zval *return_value TSRMLS_DC)
1137{
1138    zval          *tmp;
1139    smart_str      implstr = {0};
1140    int            numelems, i = 0;
1141    zend_string *str;
1142
1143    numelems = zend_hash_num_elements(Z_ARRVAL_P(arr));
1144
1145    if (numelems == 0) {
1146        RETURN_EMPTY_STRING();
1147    }
1148
1149    ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(arr), tmp) {
1150again:
1151        switch (Z_TYPE_P(tmp)) {
1152            case IS_STRING:
1153                smart_str_appendl(&implstr, Z_STRVAL_P(tmp), Z_STRLEN_P(tmp));
1154                break;
1155
1156            case IS_LONG:
1157                smart_str_append_long(&implstr, Z_LVAL_P(tmp));
1158                break;
1159
1160            case IS_TRUE:
1161                smart_str_appendl(&implstr, "1", sizeof("1")-1);
1162                break;
1163
1164            case IS_NULL:
1165            case IS_FALSE:
1166                break;
1167
1168            case IS_DOUBLE: {
1169                char *stmp;
1170                size_t str_len = spprintf(&stmp, 0, "%.*G", (int) EG(precision), Z_DVAL_P(tmp));
1171                smart_str_appendl(&implstr, stmp, str_len);
1172                efree(stmp);
1173            }
1174                break;
1175
1176            case IS_REFERENCE:
1177                tmp = Z_REFVAL_P(tmp);
1178                goto again;
1179
1180            default:
1181                str = zval_get_string(tmp);
1182                smart_str_appendl(&implstr, str->val, str->len);
1183                zend_string_release(str);
1184                break;
1185
1186        }
1187
1188        if (++i != numelems) {
1189            smart_str_appendl(&implstr, Z_STRVAL_P(delim), Z_STRLEN_P(delim));
1190        }
1191    } ZEND_HASH_FOREACH_END();
1192
1193    smart_str_0(&implstr);
1194
1195    if (implstr.s) {
1196        RETURN_STR(implstr.s);
1197    } else {
1198        smart_str_free(&implstr);
1199        RETURN_EMPTY_STRING();
1200    }
1201}
1202/* }}} */
1203
1204/* {{{ proto string implode([string glue,] array pieces)
1205   Joins array elements placing glue string between items and return one string */
1206PHP_FUNCTION(implode)
1207{
1208    zval *arg1 = NULL, *arg2 = NULL, *delim, *arr, tmp;
1209
1210#ifndef FAST_ZPP
1211    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|z", &arg1, &arg2) == FAILURE) {
1212        return;
1213    }
1214#else
1215    ZEND_PARSE_PARAMETERS_START(1, 2)
1216        Z_PARAM_ZVAL(arg1)
1217        Z_PARAM_OPTIONAL
1218        Z_PARAM_ZVAL(arg2)
1219    ZEND_PARSE_PARAMETERS_END();
1220#endif
1221
1222    if (arg2 == NULL) {
1223        if (Z_TYPE_P(arg1) != IS_ARRAY) {
1224            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Argument must be an array");
1225            return;
1226        }
1227
1228        ZVAL_EMPTY_STRING(&tmp);
1229        delim = &tmp;
1230
1231        SEPARATE_ZVAL(arg1);
1232        arr = arg1;
1233    } else {
1234        if (Z_TYPE_P(arg1) == IS_ARRAY) {
1235            arr = arg1;
1236            convert_to_string_ex(arg2);
1237            delim = arg2;
1238        } else if (Z_TYPE_P(arg2) == IS_ARRAY) {
1239            arr = arg2;
1240            convert_to_string_ex(arg1);
1241            delim = arg1;
1242        } else {
1243            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid arguments passed");
1244            return;
1245        }
1246    }
1247
1248    php_implode(delim, arr, return_value TSRMLS_CC);
1249}
1250/* }}} */
1251
1252#define STRTOK_TABLE(p) BG(strtok_table)[(unsigned char) *p]
1253
1254/* {{{ proto string strtok([string str,] string token)
1255   Tokenize a string */
1256PHP_FUNCTION(strtok)
1257{
1258    zend_string *str, *tok = NULL;
1259    char *token;
1260    char *token_end;
1261    char *p;
1262    char *pe;
1263    size_t skipped = 0;
1264
1265    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "S|S", &str, &tok) == FAILURE) {
1266        return;
1267    }
1268
1269    if (ZEND_NUM_ARGS() == 1) {
1270        tok = str;
1271    } else {
1272        zval_ptr_dtor(&BG(strtok_zval));
1273        ZVAL_STRINGL(&BG(strtok_zval), str->val, str->len);
1274        BG(strtok_last) = BG(strtok_string) = Z_STRVAL(BG(strtok_zval));
1275        BG(strtok_len) = str->len;
1276    }
1277
1278    p = BG(strtok_last); /* Where we start to search */
1279    pe = BG(strtok_string) + BG(strtok_len);
1280
1281    if (!p || p >= pe) {
1282        RETURN_FALSE;
1283    }
1284
1285    token = tok->val;
1286    token_end = token + tok->len;
1287
1288    while (token < token_end) {
1289        STRTOK_TABLE(token++) = 1;
1290    }
1291
1292    /* Skip leading delimiters */
1293    while (STRTOK_TABLE(p)) {
1294        if (++p >= pe) {
1295            /* no other chars left */
1296            BG(strtok_last) = NULL;
1297            RETVAL_FALSE;
1298            goto restore;
1299        }
1300        skipped++;
1301    }
1302
1303    /* We know at this place that *p is no delimiter, so skip it */
1304    while (++p < pe) {
1305        if (STRTOK_TABLE(p)) {
1306            goto return_token;
1307        }
1308    }
1309
1310    if (p - BG(strtok_last)) {
1311return_token:
1312        RETVAL_STRINGL(BG(strtok_last) + skipped, (p - BG(strtok_last)) - skipped);
1313        BG(strtok_last) = p + 1;
1314    } else {
1315        RETVAL_FALSE;
1316        BG(strtok_last) = NULL;
1317    }
1318
1319    /* Restore table -- usually faster then memset'ing the table on every invocation */
1320restore:
1321    token = tok->val;
1322
1323    while (token < token_end) {
1324        STRTOK_TABLE(token++) = 0;
1325    }
1326}
1327/* }}} */
1328
1329/* {{{ php_strtoupper
1330 */
1331PHPAPI char *php_strtoupper(char *s, size_t len)
1332{
1333    unsigned char *c, *e;
1334
1335    c = (unsigned char *)s;
1336    e = (unsigned char *)c+len;
1337
1338    while (c < e) {
1339        *c = toupper(*c);
1340        c++;
1341    }
1342    return s;
1343}
1344/* }}} */
1345
1346/* {{{ proto string strtoupper(string str)
1347   Makes a string uppercase */
1348PHP_FUNCTION(strtoupper)
1349{
1350    zend_string *arg;
1351    zend_string *result;
1352
1353    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "S", &arg) == FAILURE) {
1354        return;
1355    }
1356
1357    result = zend_string_init(arg->val, arg->len, 0);
1358    php_strtoupper(result->val, result->len);
1359    RETURN_NEW_STR(result);
1360}
1361/* }}} */
1362
1363/* {{{ php_strtolower
1364 */
1365PHPAPI char *php_strtolower(char *s, size_t len)
1366{
1367    unsigned char *c, *e;
1368
1369    c = (unsigned char *)s;
1370    e = c+len;
1371
1372    while (c < e) {
1373        *c = tolower(*c);
1374        c++;
1375    }
1376    return s;
1377}
1378/* }}} */
1379
1380/* {{{ proto string strtolower(string str)
1381   Makes a string lowercase */
1382PHP_FUNCTION(strtolower)
1383{
1384    zend_string *str;
1385    zend_string *result;
1386
1387#ifndef FAST_ZPP
1388    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "S", &str) == FAILURE) {
1389        return;
1390    }
1391#else
1392    ZEND_PARSE_PARAMETERS_START(1, 1)
1393        Z_PARAM_STR(str)
1394    ZEND_PARSE_PARAMETERS_END();
1395#endif
1396
1397    result = zend_string_init(str->val, str->len, 0);
1398    php_strtolower(result->val, result->len);
1399    RETURN_NEW_STR(result);
1400}
1401/* }}} */
1402
1403/* {{{ php_basename
1404 */
1405PHPAPI zend_string *php_basename(const char *s, size_t len, char *suffix, size_t sufflen TSRMLS_DC)
1406{
1407    char *c, *comp, *cend;
1408    size_t inc_len, cnt;
1409    int state;
1410    zend_string *ret;
1411
1412    c = comp = cend = (char*)s;
1413    cnt = len;
1414    state = 0;
1415    while (cnt > 0) {
1416        inc_len = (*c == '\0' ? 1 : php_mblen(c, cnt));
1417
1418        switch (inc_len) {
1419            case -2:
1420            case -1:
1421                inc_len = 1;
1422                php_mb_reset();
1423                break;
1424            case 0:
1425                goto quit_loop;
1426            case 1:
1427#if defined(PHP_WIN32) || defined(NETWARE)
1428                if (*c == '/' || *c == '\\') {
1429#else
1430                if (*c == '/') {
1431#endif
1432                    if (state == 1) {
1433                        state = 0;
1434                        cend = c;
1435                    }
1436#if defined(PHP_WIN32) || defined(NETWARE)
1437                /* Catch relative paths in c:file.txt style. They're not to confuse
1438                   with the NTFS streams. This part ensures also, that no drive
1439                   letter traversing happens. */
1440                } else if ((*c == ':' && (c - comp == 1))) {
1441                    if (state == 0) {
1442                        comp = c;
1443                        state = 1;
1444                    } else {
1445                        cend = c;
1446                        state = 0;
1447                    }
1448#endif
1449                } else {
1450                    if (state == 0) {
1451                        comp = c;
1452                        state = 1;
1453                    }
1454                }
1455                break;
1456            default:
1457                if (state == 0) {
1458                    comp = c;
1459                    state = 1;
1460                }
1461                break;
1462        }
1463        c += inc_len;
1464        cnt -= inc_len;
1465    }
1466
1467quit_loop:
1468    if (state == 1) {
1469        cend = c;
1470    }
1471    if (suffix != NULL && sufflen < (size_t)(cend - comp) &&
1472            memcmp(cend - sufflen, suffix, sufflen) == 0) {
1473        cend -= sufflen;
1474    }
1475
1476    len = cend - comp;
1477
1478    ret = zend_string_init(comp, len, 0);
1479    return ret;
1480}
1481/* }}} */
1482
1483/* {{{ proto string basename(string path [, string suffix])
1484   Returns the filename component of the path */
1485PHP_FUNCTION(basename)
1486{
1487    char *string, *suffix = NULL;
1488    size_t   string_len, suffix_len = 0;
1489
1490    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|s", &string, &string_len, &suffix, &suffix_len) == FAILURE) {
1491        return;
1492    }
1493
1494    RETURN_STR(php_basename(string, string_len, suffix, suffix_len TSRMLS_CC));
1495}
1496/* }}} */
1497
1498/* {{{ php_dirname
1499   Returns directory name component of path */
1500PHPAPI size_t php_dirname(char *path, size_t len)
1501{
1502    return zend_dirname(path, len);
1503}
1504/* }}} */
1505
1506/* {{{ proto string dirname(string path)
1507   Returns the directory name component of the path */
1508PHP_FUNCTION(dirname)
1509{
1510    char *str;
1511    zend_string *ret;
1512    size_t str_len;
1513
1514    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &str, &str_len) == FAILURE) {
1515        return;
1516    }
1517
1518    ret = zend_string_init(str, str_len, 0);
1519    ret->len = zend_dirname(ret->val, str_len);
1520
1521    RETURN_NEW_STR(ret);
1522}
1523/* }}} */
1524
1525/* {{{ proto array pathinfo(string path[, int options])
1526   Returns information about a certain string */
1527PHP_FUNCTION(pathinfo)
1528{
1529    zval tmp;
1530    char *path, *dirname;
1531    size_t path_len;
1532    int have_basename;
1533    zend_long opt = PHP_PATHINFO_ALL;
1534    zend_string *ret = NULL;
1535
1536    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &path, &path_len, &opt) == FAILURE) {
1537        return;
1538    }
1539
1540    have_basename = ((opt & PHP_PATHINFO_BASENAME) == PHP_PATHINFO_BASENAME);
1541
1542    array_init(&tmp);
1543
1544    if ((opt & PHP_PATHINFO_DIRNAME) == PHP_PATHINFO_DIRNAME) {
1545        dirname = estrndup(path, path_len);
1546        php_dirname(dirname, path_len);
1547        if (*dirname) {
1548            add_assoc_string(&tmp, "dirname", dirname);
1549        }
1550        efree(dirname);
1551    }
1552
1553    if (have_basename) {
1554        ret = php_basename(path, path_len, NULL, 0 TSRMLS_CC);
1555        add_assoc_str(&tmp, "basename", zend_string_copy(ret));
1556    }
1557
1558    if ((opt & PHP_PATHINFO_EXTENSION) == PHP_PATHINFO_EXTENSION) {
1559        const char *p;
1560        ptrdiff_t idx;
1561
1562        if (!have_basename) {
1563            ret = php_basename(path, path_len, NULL, 0 TSRMLS_CC);
1564        }
1565
1566        p = zend_memrchr(ret->val, '.', ret->len);
1567
1568        if (p) {
1569            idx = p - ret->val;
1570            add_assoc_stringl(&tmp, "extension", ret->val + idx + 1, ret->len - idx - 1);
1571        }
1572    }
1573
1574    if ((opt & PHP_PATHINFO_FILENAME) == PHP_PATHINFO_FILENAME) {
1575        const char *p;
1576        ptrdiff_t idx;
1577
1578        /* Have we already looked up the basename? */
1579        if (!have_basename && !ret) {
1580            ret = php_basename(path, path_len, NULL, 0 TSRMLS_CC);
1581        }
1582
1583        p = zend_memrchr(ret->val, '.', ret->len);
1584
1585        idx = p ? (p - ret->val) : ret->len;
1586        add_assoc_stringl(&tmp, "filename", ret->val, idx);
1587    }
1588
1589    if (ret) {
1590        zend_string_release(ret);
1591    }
1592
1593    if (opt == PHP_PATHINFO_ALL) {
1594        RETURN_ZVAL(&tmp, 0, 1);
1595    } else {
1596        zval *element;
1597        if ((element = zend_hash_get_current_data(Z_ARRVAL(tmp))) != NULL) {
1598            RETVAL_ZVAL(element, 1, 0);
1599        } else {
1600            ZVAL_EMPTY_STRING(return_value);
1601        }
1602    }
1603
1604    zval_ptr_dtor(&tmp);
1605}
1606/* }}} */
1607
1608/* {{{ php_stristr
1609   case insensitve strstr */
1610PHPAPI char *php_stristr(char *s, char *t, size_t s_len, size_t t_len)
1611{
1612    php_strtolower(s, s_len);
1613    php_strtolower(t, t_len);
1614    return (char*)php_memnstr(s, t, t_len, s + s_len);
1615}
1616/* }}} */
1617
1618/* {{{ php_strspn
1619 */
1620PHPAPI size_t php_strspn(char *s1, char *s2, char *s1_end, char *s2_end)
1621{
1622    register const char *p = s1, *spanp;
1623    register char c = *p;
1624
1625cont:
1626    for (spanp = s2; p != s1_end && spanp != s2_end;) {
1627        if (*spanp++ == c) {
1628            c = *(++p);
1629            goto cont;
1630        }
1631    }
1632    return (p - s1);
1633}
1634/* }}} */
1635
1636/* {{{ php_strcspn
1637 */
1638PHPAPI size_t php_strcspn(char *s1, char *s2, char *s1_end, char *s2_end)
1639{
1640    register const char *p, *spanp;
1641    register char c = *s1;
1642
1643    for (p = s1;;) {
1644        spanp = s2;
1645        do {
1646            if (*spanp == c || p == s1_end) {
1647                return p - s1;
1648            }
1649        } while (spanp++ < (s2_end - 1));
1650        c = *++p;
1651    }
1652    /* NOTREACHED */
1653}
1654/* }}} */
1655
1656/* {{{ php_needle_char
1657 */
1658static int php_needle_char(zval *needle, char *target TSRMLS_DC)
1659{
1660    switch (Z_TYPE_P(needle)) {
1661        case IS_LONG:
1662            *target = (char)Z_LVAL_P(needle);
1663            return SUCCESS;
1664        case IS_NULL:
1665        case IS_FALSE:
1666            *target = '\0';
1667            return SUCCESS;
1668        case IS_TRUE:
1669            *target = '\1';
1670            return SUCCESS;
1671        case IS_DOUBLE:
1672            *target = (char)(int)Z_DVAL_P(needle);
1673            return SUCCESS;
1674        case IS_OBJECT:
1675            {
1676                zval holder = *needle;
1677                zval_copy_ctor(&(holder));
1678                convert_to_long(&(holder));
1679                if(Z_TYPE(holder) != IS_LONG) {
1680                    return FAILURE;
1681                }
1682                *target = (char)Z_LVAL(holder);
1683                return SUCCESS;
1684            }
1685        default: {
1686            php_error_docref(NULL TSRMLS_CC, E_WARNING, "needle is not a string or an integer");
1687            return FAILURE;
1688         }
1689    }
1690}
1691/* }}} */
1692
1693/* {{{ proto string stristr(string haystack, string needle[, bool part])
1694   Finds first occurrence of a string within another, case insensitive */
1695PHP_FUNCTION(stristr)
1696{
1697    zval *needle;
1698    zend_string *haystack;
1699    char *found = NULL;
1700    size_t  found_offset;
1701    char *haystack_dup;
1702    char needle_char[2];
1703    zend_bool part = 0;
1704
1705    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Sz|b", &haystack, &needle, &part) == FAILURE) {
1706        return;
1707    }
1708
1709    haystack_dup = estrndup(haystack->val, haystack->len);
1710
1711    if (Z_TYPE_P(needle) == IS_STRING) {
1712        char *orig_needle;
1713        if (!Z_STRLEN_P(needle)) {
1714            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Empty needle");
1715            efree(haystack_dup);
1716            RETURN_FALSE;
1717        }
1718        orig_needle = estrndup(Z_STRVAL_P(needle), Z_STRLEN_P(needle));
1719        found = php_stristr(haystack_dup, orig_needle,  haystack->len, Z_STRLEN_P(needle));
1720        efree(orig_needle);
1721    } else {
1722        if (php_needle_char(needle, needle_char TSRMLS_CC) != SUCCESS) {
1723            efree(haystack_dup);
1724            RETURN_FALSE;
1725        }
1726        needle_char[1] = 0;
1727
1728        found = php_stristr(haystack_dup, needle_char,  haystack->len, 1);
1729    }
1730
1731    if (found) {
1732        found_offset = found - haystack_dup;
1733        if (part) {
1734            RETVAL_STRINGL(haystack->val, found_offset);
1735        } else {
1736            RETVAL_STRINGL(haystack->val + found_offset, haystack->len - found_offset);
1737        }
1738    } else {
1739        RETVAL_FALSE;
1740    }
1741
1742    efree(haystack_dup);
1743}
1744/* }}} */
1745
1746/* {{{ proto string strstr(string haystack, string needle[, bool part])
1747   Finds first occurrence of a string within another */
1748PHP_FUNCTION(strstr)
1749{
1750    zval *needle;
1751    zend_string *haystack;
1752    char *found = NULL;
1753    char needle_char[2];
1754    zend_long found_offset;
1755    zend_bool part = 0;
1756
1757    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Sz|b", &haystack, &needle, &part) == FAILURE) {
1758        return;
1759    }
1760
1761    if (Z_TYPE_P(needle) == IS_STRING) {
1762        if (!Z_STRLEN_P(needle)) {
1763            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Empty needle");
1764            RETURN_FALSE;
1765        }
1766
1767        found = (char*)php_memnstr(haystack->val, Z_STRVAL_P(needle), Z_STRLEN_P(needle), haystack->val + haystack->len);
1768    } else {
1769        if (php_needle_char(needle, needle_char TSRMLS_CC) != SUCCESS) {
1770            RETURN_FALSE;
1771        }
1772        needle_char[1] = 0;
1773
1774        found = (char*)php_memnstr(haystack->val, needle_char,  1, haystack->val + haystack->len);
1775    }
1776
1777    if (found) {
1778        found_offset = found - haystack->val;
1779        if (part) {
1780            RETURN_STRINGL(haystack->val, found_offset);
1781        } else {
1782            RETURN_STRINGL(found, haystack->len - found_offset);
1783        }
1784    }
1785    RETURN_FALSE;
1786}
1787/* }}} */
1788
1789/* {{{ proto string strchr(string haystack, string needle)
1790   An alias for strstr */
1791/* }}} */
1792
1793/* {{{ proto int strpos(string haystack, string needle [, int offset])
1794   Finds position of first occurrence of a string within another */
1795PHP_FUNCTION(strpos)
1796{
1797    zval *needle;
1798    zend_string *haystack;
1799    char *found = NULL;
1800    char  needle_char[2];
1801    zend_long  offset = 0;
1802
1803#ifndef FAST_ZPP
1804    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Sz|l", &haystack, &needle, &offset) == FAILURE) {
1805        return;
1806    }
1807#else
1808    ZEND_PARSE_PARAMETERS_START(2, 3)
1809        Z_PARAM_STR(haystack)
1810        Z_PARAM_ZVAL(needle)
1811        Z_PARAM_OPTIONAL
1812        Z_PARAM_LONG(offset)
1813    ZEND_PARSE_PARAMETERS_END();
1814#endif
1815
1816    if (offset < 0 || (size_t)offset > haystack->len) {
1817        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Offset not contained in string");
1818        RETURN_FALSE;
1819    }
1820
1821    if (Z_TYPE_P(needle) == IS_STRING) {
1822        if (!Z_STRLEN_P(needle)) {
1823            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Empty needle");
1824            RETURN_FALSE;
1825        }
1826
1827        found = (char*)php_memnstr(haystack->val + offset,
1828                            Z_STRVAL_P(needle),
1829                            Z_STRLEN_P(needle),
1830                            haystack->val + haystack->len);
1831    } else {
1832        if (php_needle_char(needle, needle_char TSRMLS_CC) != SUCCESS) {
1833            RETURN_FALSE;
1834        }
1835        needle_char[1] = 0;
1836
1837        found = (char*)php_memnstr(haystack->val + offset,
1838                            needle_char,
1839                            1,
1840                            haystack->val + haystack->len);
1841    }
1842
1843    if (found) {
1844        RETURN_LONG(found - haystack->val);
1845    } else {
1846        RETURN_FALSE;
1847    }
1848}
1849/* }}} */
1850
1851/* {{{ proto int stripos(string haystack, string needle [, int offset])
1852   Finds position of first occurrence of a string within another, case insensitive */
1853PHP_FUNCTION(stripos)
1854{
1855    char *found = NULL;
1856    zend_string *haystack;
1857    zend_long offset = 0;
1858    char *needle_dup = NULL, *haystack_dup;
1859    char needle_char[2];
1860    zval *needle;
1861
1862    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Sz|l", &haystack, &needle, &offset) == FAILURE) {
1863        return;
1864    }
1865
1866    if (offset < 0 || (size_t)offset > haystack->len) {
1867        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Offset not contained in string");
1868        RETURN_FALSE;
1869    }
1870
1871    if (haystack->len == 0) {
1872        RETURN_FALSE;
1873    }
1874
1875    haystack_dup = estrndup(haystack->val, haystack->len);
1876    php_strtolower(haystack_dup, haystack->len);
1877
1878    if (Z_TYPE_P(needle) == IS_STRING) {
1879        if (Z_STRLEN_P(needle) == 0 || Z_STRLEN_P(needle) > haystack->len) {
1880            efree(haystack_dup);
1881            RETURN_FALSE;
1882        }
1883
1884        needle_dup = estrndup(Z_STRVAL_P(needle), Z_STRLEN_P(needle));
1885        php_strtolower(needle_dup, Z_STRLEN_P(needle));
1886        found = (char*)php_memnstr(haystack_dup + offset, needle_dup, Z_STRLEN_P(needle), haystack_dup + haystack->len);
1887    } else {
1888        if (php_needle_char(needle, needle_char TSRMLS_CC) != SUCCESS) {
1889            efree(haystack_dup);
1890            RETURN_FALSE;
1891        }
1892        needle_char[0] = tolower(needle_char[0]);
1893        needle_char[1] = '\0';
1894        found = (char*)php_memnstr(haystack_dup + offset,
1895                            needle_char,
1896                            sizeof(needle_char) - 1,
1897                            haystack_dup + haystack->len);
1898    }
1899
1900    efree(haystack_dup);
1901    if (needle_dup) {
1902        efree(needle_dup);
1903    }
1904
1905    if (found) {
1906        RETURN_LONG(found - haystack_dup);
1907    } else {
1908        RETURN_FALSE;
1909    }
1910}
1911/* }}} */
1912
1913/* {{{ proto int strrpos(string haystack, string needle [, int offset])
1914   Finds position of last occurrence of a string within another string */
1915PHP_FUNCTION(strrpos)
1916{
1917    zval *zneedle;
1918    char *needle;
1919    zend_string *haystack;
1920    size_t needle_len;
1921    zend_long offset = 0;
1922    char *p, *e, ord_needle[2];
1923
1924#ifndef FAST_ZPP
1925    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Sz|l", &haystack, &zneedle, &offset) == FAILURE) {
1926        RETURN_FALSE;
1927    }
1928#else
1929    ZEND_PARSE_PARAMETERS_START(2, 3)
1930        Z_PARAM_STR(haystack)
1931        Z_PARAM_ZVAL(zneedle)
1932        Z_PARAM_OPTIONAL
1933        Z_PARAM_LONG(offset)
1934    ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
1935#endif
1936
1937    if (Z_TYPE_P(zneedle) == IS_STRING) {
1938        needle = Z_STRVAL_P(zneedle);
1939        needle_len = Z_STRLEN_P(zneedle);
1940    } else {
1941        if (php_needle_char(zneedle, ord_needle TSRMLS_CC) != SUCCESS) {
1942            RETURN_FALSE;
1943        }
1944        ord_needle[1] = '\0';
1945        needle = ord_needle;
1946        needle_len = 1;
1947    }
1948
1949    if ((haystack->len == 0) || (needle_len == 0)) {
1950        RETURN_FALSE;
1951    }
1952
1953    if (offset >= 0) {
1954        if ((size_t)offset > haystack->len) {
1955            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Offset is greater than the length of haystack string");
1956            RETURN_FALSE;
1957        }
1958        p = haystack->val + (size_t)offset;
1959        e = haystack->val + haystack->len - needle_len;
1960    } else {
1961        if (offset < -INT_MAX || (size_t)(-offset) > haystack->len) {
1962            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Offset is greater than the length of haystack string");
1963            RETURN_FALSE;
1964        }
1965
1966        p = haystack->val;
1967        if (needle_len > (size_t)(-offset)) {
1968            e = haystack->val + haystack->len - needle_len;
1969        } else {
1970            e = haystack->val + haystack->len + offset;
1971        }
1972    }
1973
1974    if (needle_len == 1) {
1975        /* Single character search can shortcut memcmps */
1976        while (e >= p) {
1977            if (*e == *needle) {
1978                RETURN_LONG(e - p + (offset > 0 ? offset : 0));
1979            }
1980            e--;
1981        }
1982        RETURN_FALSE;
1983    }
1984
1985    while (e >= p) {
1986        if (memcmp(e, needle, needle_len) == 0) {
1987            RETURN_LONG(e - p + (offset > 0 ? offset : 0));
1988        }
1989        e--;
1990    }
1991
1992    RETURN_FALSE;
1993}
1994/* }}} */
1995
1996/* {{{ proto int strripos(string haystack, string needle [, int offset])
1997   Finds position of last occurrence of a string within another string */
1998PHP_FUNCTION(strripos)
1999{
2000    zval *zneedle;
2001    char *needle;
2002    zend_string *haystack;
2003    size_t needle_len;
2004    zend_long offset = 0;
2005    char *p, *e, ord_needle[2];
2006    char *needle_dup, *haystack_dup;
2007
2008    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Sz|l", &haystack, &zneedle, &offset) == FAILURE) {
2009        RETURN_FALSE;
2010    }
2011
2012    if (Z_TYPE_P(zneedle) == IS_STRING) {
2013        needle = Z_STRVAL_P(zneedle);
2014        needle_len = Z_STRLEN_P(zneedle);
2015    } else {
2016        if (php_needle_char(zneedle, ord_needle TSRMLS_CC) != SUCCESS) {
2017            RETURN_FALSE;
2018        }
2019        ord_needle[1] = '\0';
2020        needle = ord_needle;
2021        needle_len = 1;
2022    }
2023
2024    if ((haystack->len == 0) || (needle_len == 0)) {
2025        RETURN_FALSE;
2026    }
2027
2028    if (needle_len == 1) {
2029        /* Single character search can shortcut memcmps
2030           Can also avoid tolower emallocs */
2031        if (offset >= 0) {
2032            if ((size_t)offset > haystack->len) {
2033                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Offset is greater than the length of haystack string");
2034                RETURN_FALSE;
2035            }
2036            p = haystack->val + offset;
2037            e = haystack->val + haystack->len - 1;
2038        } else {
2039            p = haystack->val;
2040            if (offset < -INT_MAX || (size_t)(-offset) > haystack->len) {
2041                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Offset is greater than the length of haystack string");
2042                RETURN_FALSE;
2043            }
2044            e = haystack->val + haystack->len + offset;
2045        }
2046        /* Borrow that ord_needle buffer to avoid repeatedly tolower()ing needle */
2047        *ord_needle = tolower(*needle);
2048        while (e >= p) {
2049            if (tolower(*e) == *ord_needle) {
2050                RETURN_LONG(e - p + (offset > 0 ? offset : 0));
2051            }
2052            e--;
2053        }
2054        RETURN_FALSE;
2055    }
2056
2057    needle_dup = estrndup(needle, needle_len);
2058    php_strtolower(needle_dup, needle_len);
2059    haystack_dup = estrndup(haystack->val, haystack->len);
2060    php_strtolower(haystack_dup, haystack->len);
2061
2062    if (offset >= 0) {
2063        if ((size_t)offset > haystack->len) {
2064            efree(needle_dup);
2065            efree(haystack_dup);
2066            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Offset is greater than the length of haystack string");
2067            RETURN_FALSE;
2068        }
2069        p = haystack_dup + offset;
2070        e = haystack_dup + haystack->len - needle_len;
2071    } else {
2072        if (offset < -INT_MAX || (size_t)(-offset) > haystack->len) {
2073            efree(needle_dup);
2074            efree(haystack_dup);
2075            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Offset is greater than the length of haystack string");
2076            RETURN_FALSE;
2077        }
2078        p = haystack_dup;
2079        if (needle_len > (size_t)(-offset)) {
2080            e = haystack_dup + haystack->len - needle_len;
2081        } else {
2082            e = haystack_dup + haystack->len + offset;
2083        }
2084    }
2085
2086    while (e >= p) {
2087        if (memcmp(e, needle_dup, needle_len) == 0) {
2088            efree(haystack_dup);
2089            efree(needle_dup);
2090            RETURN_LONG(e - p + (offset > 0 ? offset : 0));
2091        }
2092        e--;
2093    }
2094
2095    efree(haystack_dup);
2096    efree(needle_dup);
2097    RETURN_FALSE;
2098}
2099/* }}} */
2100
2101/* {{{ proto string strrchr(string haystack, string needle)
2102   Finds the last occurrence of a character in a string within another */
2103PHP_FUNCTION(strrchr)
2104{
2105    zval *needle;
2106    zend_string *haystack;
2107    const char *found = NULL;
2108    zend_long found_offset;
2109
2110    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Sz", &haystack, &needle) == FAILURE) {
2111        return;
2112    }
2113
2114    if (Z_TYPE_P(needle) == IS_STRING) {
2115        found = zend_memrchr(haystack->val, *Z_STRVAL_P(needle), haystack->len);
2116    } else {
2117        char needle_chr;
2118        if (php_needle_char(needle, &needle_chr TSRMLS_CC) != SUCCESS) {
2119            RETURN_FALSE;
2120        }
2121
2122        found = zend_memrchr(haystack->val,  needle_chr, haystack->len);
2123    }
2124
2125    if (found) {
2126        found_offset = found - haystack->val;
2127        RETURN_STRINGL(found, haystack->len - found_offset);
2128    } else {
2129        RETURN_FALSE;
2130    }
2131}
2132/* }}} */
2133
2134/* {{{ php_chunk_split
2135 */
2136static zend_string *php_chunk_split(char *src, size_t srclen, char *end, size_t endlen, size_t chunklen)
2137{
2138    char *p, *q;
2139    size_t chunks; /* complete chunks! */
2140    size_t restlen;
2141    size_t out_len;
2142    zend_string *dest;
2143
2144    chunks = srclen / chunklen;
2145    restlen = srclen - chunks * chunklen; /* srclen % chunklen */
2146
2147    if (chunks > INT_MAX - 1) {
2148        return NULL;
2149    }
2150    out_len = chunks + 1;
2151    if (endlen !=0 && out_len > INT_MAX/endlen) {
2152        return NULL;
2153    }
2154    out_len *= endlen;
2155    if (out_len > INT_MAX - srclen - 1) {
2156        return NULL;
2157    }
2158    out_len += srclen + 1;
2159
2160    dest = zend_string_alloc(out_len * sizeof(char), 0);
2161
2162    for (p = src, q = dest->val; p < (src + srclen - chunklen + 1); ) {
2163        memcpy(q, p, chunklen);
2164        q += chunklen;
2165        memcpy(q, end, endlen);
2166        q += endlen;
2167        p += chunklen;
2168    }
2169
2170    if (restlen) {
2171        memcpy(q, p, restlen);
2172        q += restlen;
2173        memcpy(q, end, endlen);
2174        q += endlen;
2175    }
2176
2177    *q = '\0';
2178    dest->len = q - dest->val;
2179
2180    return dest;
2181}
2182/* }}} */
2183
2184/* {{{ proto string chunk_split(string str [, int chunklen [, string ending]])
2185   Returns split line */
2186PHP_FUNCTION(chunk_split)
2187{
2188    zend_string *str;
2189    char *end    = "\r\n";
2190    size_t endlen   = 2;
2191    zend_long chunklen = 76;
2192    zend_string *result;
2193
2194    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "S|ls", &str, &chunklen, &end, &endlen) == FAILURE) {
2195        return;
2196    }
2197
2198    if (chunklen <= 0) {
2199        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Chunk length should be greater than zero");
2200        RETURN_FALSE;
2201    }
2202
2203    if ((size_t)chunklen > str->len) {
2204        /* to maintain BC, we must return original string + ending */
2205        result = zend_string_alloc(endlen + str->len, 0);
2206        memcpy(result->val, str->val, str->len);
2207        memcpy(result->val + str->len, end, endlen);
2208        result->val[result->len] = '\0';
2209        RETURN_NEW_STR(result);
2210    }
2211
2212    if (!str->len) {
2213        RETURN_EMPTY_STRING();
2214    }
2215
2216    result = php_chunk_split(str->val, str->len, end, endlen, (size_t)chunklen);
2217
2218    if (result) {
2219        RETURN_STR(result);
2220    } else {
2221        RETURN_FALSE;
2222    }
2223}
2224/* }}} */
2225
2226/* {{{ proto string substr(string str, int start [, int length])
2227   Returns part of a string */
2228PHP_FUNCTION(substr)
2229{
2230    zend_string *str;
2231    zend_long l = 0, f;
2232    int argc = ZEND_NUM_ARGS();
2233
2234#ifndef FAST_ZPP
2235    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Sl|l", &str, &f, &l) == FAILURE) {
2236        return;
2237    }
2238#else
2239    ZEND_PARSE_PARAMETERS_START(2, 3)
2240        Z_PARAM_STR(str)
2241        Z_PARAM_LONG(f)
2242        Z_PARAM_OPTIONAL
2243        Z_PARAM_LONG(l)
2244    ZEND_PARSE_PARAMETERS_END();
2245#endif
2246
2247    if (argc > 2) {
2248        if ((l < 0 && (size_t)(-l) > str->len)) {
2249            RETURN_FALSE;
2250        } else if (l > (zend_long)str->len) {
2251            l = str->len;
2252        }
2253    } else {
2254        l = str->len;
2255    }
2256
2257    if (f > (zend_long)str->len) {
2258        RETURN_FALSE;
2259    } else if (f < 0 && -f > str->len) {
2260        f = 0;
2261    }
2262
2263    if (l < 0 && (l + (zend_long)str->len - f) < 0) {
2264        RETURN_FALSE;
2265    }
2266
2267    /* if "from" position is negative, count start position from the end
2268     * of the string
2269     */
2270    if (f < 0) {
2271        f = (zend_long)str->len + f;
2272        if (f < 0) {
2273            f = 0;
2274        }
2275    }
2276
2277    /* if "length" position is negative, set it to the length
2278     * needed to stop that many chars from the end of the string
2279     */
2280    if (l < 0) {
2281        l = ((zend_long)str->len - f) + l;
2282        if (l < 0) {
2283            l = 0;
2284        }
2285    }
2286
2287    if (f >= (zend_long)str->len) {
2288        RETURN_FALSE;
2289    }
2290
2291    if ((f + l) > (zend_long)str->len) {
2292        l = str->len - f;
2293    }
2294
2295    RETURN_STRINGL(str->val + f, l);
2296}
2297/* }}} */
2298
2299/* {{{ proto mixed substr_replace(mixed str, mixed repl, mixed start [, mixed length])
2300   Replaces part of a string with another string */
2301PHP_FUNCTION(substr_replace)
2302{
2303    zval *str;
2304    zval *from;
2305    zval *len = NULL;
2306    zval *repl;
2307    zend_long l = 0; /* l and f should be size_t, however this needs much closer below logic investigation.*/
2308    zend_long f;
2309    int argc = ZEND_NUM_ARGS();
2310    zend_string *result;
2311
2312    HashPosition pos_from, pos_repl, pos_len;
2313    zval *tmp_str = NULL, *tmp_from = NULL, *tmp_repl = NULL, *tmp_len= NULL;
2314
2315    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zzz|z/", &str, &repl, &from, &len) == FAILURE) {
2316        return;
2317    }
2318
2319    if (Z_TYPE_P(str) != IS_ARRAY) {
2320        convert_to_string_ex(str);
2321    }
2322    if (Z_TYPE_P(repl) != IS_ARRAY) {
2323        convert_to_string_ex(repl);
2324    }
2325    if (Z_TYPE_P(from) != IS_ARRAY) {
2326        convert_to_long_ex(from);
2327    }
2328
2329    if (argc > 3) {
2330        if (Z_TYPE_P(len) != IS_ARRAY) {
2331            l = zval_get_long(len);
2332        }
2333    } else {
2334        if (Z_TYPE_P(str) != IS_ARRAY) {
2335            l = Z_STRLEN_P(str);
2336        }
2337    }
2338
2339    if (Z_TYPE_P(str) == IS_STRING) {
2340        if (
2341            (argc == 3 && Z_TYPE_P(from) == IS_ARRAY) ||
2342            (argc == 4 && Z_TYPE_P(from) != Z_TYPE_P(len))
2343        ) {
2344            php_error_docref(NULL TSRMLS_CC, E_WARNING, "'from' and 'len' should be of same type - numerical or array ");
2345            RETURN_STR(zend_string_copy(Z_STR_P(str)));
2346        }
2347        if (argc == 4 && Z_TYPE_P(from) == IS_ARRAY) {
2348            if (zend_hash_num_elements(Z_ARRVAL_P(from)) != zend_hash_num_elements(Z_ARRVAL_P(len))) {
2349                php_error_docref(NULL TSRMLS_CC, E_WARNING, "'from' and 'len' should have the same number of elements");
2350                RETURN_STR(zend_string_copy(Z_STR_P(str)));
2351            }
2352        }
2353    }
2354
2355    if (Z_TYPE_P(str) != IS_ARRAY) {
2356        if (Z_TYPE_P(from) != IS_ARRAY) {
2357            size_t repl_len = 0;
2358
2359            f = Z_LVAL_P(from);
2360
2361            /* if "from" position is negative, count start position from the end
2362             * of the string
2363             */
2364            if (f < 0) {
2365                f = Z_STRLEN_P(str) + f;
2366                if (f < 0) {
2367                    f = 0;
2368                }
2369            } else if (f > Z_STRLEN_P(str)) {
2370                f = Z_STRLEN_P(str);
2371            }
2372            /* if "length" position is negative, set it to the length
2373             * needed to stop that many chars from the end of the string
2374             */
2375            if (l < 0) {
2376                l = (Z_STRLEN_P(str) - f) + l;
2377                if (l < 0) {
2378                    l = 0;
2379                }
2380            }
2381
2382            if (f > Z_STRLEN_P(str) || (f < 0 && -f > Z_STRLEN_P(str))) {
2383                RETURN_FALSE;
2384            } else if (l > Z_STRLEN_P(str) || (l < 0 && -l > Z_STRLEN_P(str))) {
2385                l = Z_STRLEN_P(str);
2386            }
2387
2388            if ((f + l) > Z_STRLEN_P(str)) {
2389                l = Z_STRLEN_P(str) - f;
2390            }
2391            if (Z_TYPE_P(repl) == IS_ARRAY) {
2392                zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(repl), &pos_repl);
2393                if (NULL != (tmp_repl = zend_hash_get_current_data_ex(Z_ARRVAL_P(repl), &pos_repl))) {
2394                    convert_to_string_ex(tmp_repl);
2395                    repl_len = Z_STRLEN_P(tmp_repl);
2396                }
2397            } else {
2398                repl_len = Z_STRLEN_P(repl);
2399            }
2400
2401            result = zend_string_alloc(Z_STRLEN_P(str) - l + repl_len, 0);
2402
2403            memcpy(result->val, Z_STRVAL_P(str), f);
2404            if (repl_len) {
2405                memcpy((result->val + f), (Z_TYPE_P(repl) == IS_ARRAY ? Z_STRVAL_P(tmp_repl) : Z_STRVAL_P(repl)), repl_len);
2406            }
2407            memcpy((result->val + f + repl_len), Z_STRVAL_P(str) + f + l, Z_STRLEN_P(str) - f - l);
2408            result->val[result->len] = '\0';
2409            RETURN_NEW_STR(result);
2410        } else {
2411            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Functionality of 'from' and 'len' as arrays is not implemented");
2412            RETURN_STR(zend_string_copy(Z_STR_P(str)));
2413        }
2414    } else { /* str is array of strings */
2415        zend_string *str_index = NULL;
2416        size_t result_len;
2417        zend_ulong num_index;
2418
2419        array_init(return_value);
2420
2421        if (Z_TYPE_P(from) == IS_ARRAY) {
2422            zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(from), &pos_from);
2423        }
2424
2425        if (argc > 3 && Z_TYPE_P(len) == IS_ARRAY) {
2426            zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(len), &pos_len);
2427        }
2428
2429        if (Z_TYPE_P(repl) == IS_ARRAY) {
2430            zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(repl), &pos_repl);
2431        }
2432
2433        ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(str), num_index, str_index, tmp_str) {
2434            zval *orig_str;
2435            zval dummy;
2436
2437            if (Z_ISREF_P(tmp_str)) {
2438                /* see bug #55871 */
2439                ZVAL_DUP(&dummy, Z_REFVAL_P(tmp_str));
2440                convert_to_string(&dummy);
2441                orig_str = &dummy;
2442            } else if (Z_TYPE_P(tmp_str) != IS_STRING) {
2443                ZVAL_DUP(&dummy, tmp_str);
2444                convert_to_string(&dummy);
2445                orig_str = &dummy;
2446            } else {
2447                orig_str = tmp_str;
2448            }
2449
2450            /*???
2451            refcount = Z_REFCOUNT_P(orig_str);
2452            */
2453
2454            if (Z_TYPE_P(from) == IS_ARRAY) {
2455                if (NULL != (tmp_from = zend_hash_get_current_data_ex(Z_ARRVAL_P(from), &pos_from))) {
2456                    f = zval_get_long(tmp_from);
2457
2458                    if (f < 0) {
2459                        f = Z_STRLEN_P(orig_str) + f;
2460                        if (f < 0) {
2461                            f = 0;
2462                        }
2463                    } else if (f > Z_STRLEN_P(orig_str)) {
2464                        f = Z_STRLEN_P(orig_str);
2465                    }
2466                    zend_hash_move_forward_ex(Z_ARRVAL_P(from), &pos_from);
2467                } else {
2468                    f = 0;
2469                }
2470            } else {
2471                f = Z_LVAL_P(from);
2472                if (f < 0) {
2473                    f = Z_STRLEN_P(orig_str) + f;
2474                    if (f < 0) {
2475                        f = 0;
2476                    }
2477                } else if (f > Z_STRLEN_P(orig_str)) {
2478                    f = Z_STRLEN_P(orig_str);
2479                }
2480            }
2481
2482            if (argc > 3 && Z_TYPE_P(len) == IS_ARRAY) {
2483                if (NULL != (tmp_len = zend_hash_get_current_data_ex(Z_ARRVAL_P(len), &pos_len))) {
2484                    l = zval_get_long(tmp_len);
2485                    zend_hash_move_forward_ex(Z_ARRVAL_P(len), &pos_len);
2486                } else {
2487                    l = Z_STRLEN_P(orig_str);
2488                }
2489            } else if (argc > 3) {
2490                l = Z_LVAL_P(len);
2491            } else {
2492                l = Z_STRLEN_P(orig_str);
2493            }
2494
2495            if (l < 0) {
2496                l = (Z_STRLEN_P(orig_str) - f) + l;
2497                if (l < 0) {
2498                    l = 0;
2499                }
2500            }
2501
2502            if ((f + l) > Z_STRLEN_P(orig_str)) {
2503                l = Z_STRLEN_P(orig_str) - f;
2504            }
2505
2506            result_len = Z_STRLEN_P(orig_str) - l;
2507
2508            if (Z_TYPE_P(repl) == IS_ARRAY) {
2509                if (NULL != (tmp_repl = zend_hash_get_current_data_ex(Z_ARRVAL_P(repl), &pos_repl))) {
2510                    zval *repl_str;
2511                    zval zrepl;
2512
2513                    ZVAL_DEREF(tmp_repl);
2514                    if (Z_TYPE_P(tmp_repl) != IS_STRING) {
2515                        ZVAL_DUP(&zrepl, tmp_repl);
2516                        convert_to_string(&zrepl);
2517                        repl_str = &zrepl;
2518                    } else {
2519                        repl_str = tmp_repl;
2520                    }
2521                    /*???
2522                    if (Z_REFCOUNT_P(orig_str) != refcount) {
2523                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Argument was modified while replacing");
2524                        if (Z_TYPE_P(tmp_repl) != IS_STRING) {
2525                            zval_dtor(repl_str);
2526                        }
2527                        break;
2528                    }
2529                    */
2530
2531                    result_len += Z_STRLEN_P(repl_str);
2532                    zend_hash_move_forward_ex(Z_ARRVAL_P(repl), &pos_repl);
2533                    result = zend_string_alloc(result_len, 0);
2534
2535                    memcpy(result->val, Z_STRVAL_P(orig_str), f);
2536                    memcpy((result->val + f), Z_STRVAL_P(repl_str), Z_STRLEN_P(repl_str));
2537                    memcpy((result->val + f + Z_STRLEN_P(repl_str)), Z_STRVAL_P(orig_str) + f + l, Z_STRLEN_P(orig_str) - f - l);
2538                    if(Z_TYPE_P(tmp_repl) != IS_STRING) {
2539                        zval_dtor(repl_str);
2540                    }
2541                } else {
2542                    result = zend_string_alloc(result_len, 0);
2543
2544                    memcpy(result->val, Z_STRVAL_P(orig_str), f);
2545                    memcpy((result->val + f), Z_STRVAL_P(orig_str) + f + l, Z_STRLEN_P(orig_str) - f - l);
2546                }
2547            } else {
2548                result_len += Z_STRLEN_P(repl);
2549
2550                result = zend_string_alloc(result_len, 0);
2551
2552                memcpy(result->val, Z_STRVAL_P(orig_str), f);
2553                memcpy((result->val + f), Z_STRVAL_P(repl), Z_STRLEN_P(repl));
2554                memcpy((result->val + f + Z_STRLEN_P(repl)), Z_STRVAL_P(orig_str) + f + l, Z_STRLEN_P(orig_str) - f - l);
2555            }
2556
2557            result->val[result->len] = '\0';
2558
2559            if (str_index) {
2560                zval tmp;
2561
2562                ZVAL_STR(&tmp, result);
2563                zend_symtable_update(Z_ARRVAL_P(return_value), str_index, &tmp);
2564            } else {
2565                add_index_str(return_value, num_index, result);
2566            }
2567
2568            if(Z_TYPE_P(tmp_str) != IS_STRING) {
2569                zval_dtor(orig_str);
2570            } else {
2571//???           Z_SET_ISREF_TO_P(orig_str, was_ref);
2572            }
2573        } ZEND_HASH_FOREACH_END();
2574    } /* if */
2575}
2576/* }}} */
2577
2578/* {{{ proto string quotemeta(string str)
2579   Quotes meta characters */
2580PHP_FUNCTION(quotemeta)
2581{
2582    zend_string *old;
2583    char *old_end;
2584    char *p, *q;
2585    char c;
2586    zend_string *str;
2587
2588    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "S", &old) == FAILURE) {
2589        return;
2590    }
2591
2592    old_end = old->val + old->len;
2593
2594    if (old->val == old_end) {
2595        RETURN_FALSE;
2596    }
2597
2598    str = zend_string_alloc(2 * old->len, 0);
2599
2600    for (p = old->val, q = str->val; p != old_end; p++) {
2601        c = *p;
2602        switch (c) {
2603            case '.':
2604            case '\\':
2605            case '+':
2606            case '*':
2607            case '?':
2608            case '[':
2609            case '^':
2610            case ']':
2611            case '$':
2612            case '(':
2613            case ')':
2614                *q++ = '\\';
2615                /* break is missing _intentionally_ */
2616            default:
2617                *q++ = c;
2618        }
2619    }
2620
2621    *q = '\0';
2622
2623    RETURN_NEW_STR(zend_string_realloc(str, q - str->val, 0));
2624}
2625/* }}} */
2626
2627/* {{{ proto int ord(string character)
2628   Returns ASCII value of character */
2629PHP_FUNCTION(ord)
2630{
2631    char   *str;
2632    size_t str_len;
2633
2634#ifndef FAST_ZPP
2635    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &str, &str_len) == FAILURE) {
2636        return;
2637    }
2638#else
2639    ZEND_PARSE_PARAMETERS_START(1, 1)
2640        Z_PARAM_STRING(str, str_len)
2641    ZEND_PARSE_PARAMETERS_END();
2642#endif
2643
2644    RETURN_LONG((unsigned char) str[0]);
2645}
2646/* }}} */
2647
2648/* {{{ proto string chr(int ascii)
2649   Converts ASCII code to a character */
2650PHP_FUNCTION(chr)
2651{
2652    zend_long c;
2653    char temp[2];
2654
2655    if (ZEND_NUM_ARGS() != 1) {
2656        WRONG_PARAM_COUNT;
2657    }
2658
2659    if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS() TSRMLS_CC, "l", &c) == FAILURE) {
2660        c = 0;
2661    }
2662
2663    temp[0] = (char)c;
2664    temp[1] = '\0';
2665
2666    RETURN_STRINGL(temp, 1);
2667}
2668/* }}} */
2669
2670/* {{{ php_ucfirst
2671   Uppercase the first character of the word in a native string */
2672static void php_ucfirst(char *str)
2673{
2674    register char *r;
2675    r = str;
2676    *r = toupper((unsigned char) *r);
2677}
2678/* }}} */
2679
2680/* {{{ proto string ucfirst(string str)
2681   Makes a string's first character uppercase */
2682PHP_FUNCTION(ucfirst)
2683{
2684    zend_string *str;
2685
2686#ifndef FAST_ZPP
2687    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "S", &str) == FAILURE) {
2688        return;
2689    }
2690#else
2691    ZEND_PARSE_PARAMETERS_START(1, 1)
2692        Z_PARAM_STR(str)
2693    ZEND_PARSE_PARAMETERS_END();
2694#endif
2695
2696    if (!str->len) {
2697        RETURN_EMPTY_STRING();
2698    }
2699
2700    ZVAL_STRINGL(return_value, str->val, str->len);
2701    php_ucfirst(Z_STRVAL_P(return_value));
2702}
2703/* }}} */
2704
2705/* {{{
2706   Lowercase the first character of the word in a native string */
2707static void php_lcfirst(char *str)
2708{
2709    register char *r;
2710    r = str;
2711    *r = tolower((unsigned char) *r);
2712}
2713/* }}} */
2714
2715/* {{{ proto string lcfirst(string str)
2716   Make a string's first character lowercase */
2717PHP_FUNCTION(lcfirst)
2718{
2719    zend_string  *str;
2720
2721    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "S", &str) == FAILURE) {
2722        return;
2723    }
2724
2725    if (!str->len) {
2726        RETURN_EMPTY_STRING();
2727    }
2728
2729    ZVAL_STRINGL(return_value, str->val, str->len);
2730    php_lcfirst(Z_STRVAL_P(return_value));
2731}
2732/* }}} */
2733
2734/* {{{ proto string ucwords(string str)
2735   Uppercase the first character of every word in a string */
2736PHP_FUNCTION(ucwords)
2737{
2738    zend_string *str;
2739    char *delims = " \t\r\n\f\v";
2740    register char *r, *r_end;
2741    size_t delims_len = 6;
2742    char mask[256];
2743
2744#ifndef FAST_ZPP
2745    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "S|s", &str, &delims, &delims_len) == FAILURE) {
2746        return;
2747    }
2748#else
2749    ZEND_PARSE_PARAMETERS_START(1, 2)
2750        Z_PARAM_STR(str)
2751        Z_PARAM_OPTIONAL
2752        Z_PARAM_STRING(delims, delims_len)
2753    ZEND_PARSE_PARAMETERS_END();
2754#endif
2755
2756    if (!str->len) {
2757        RETURN_EMPTY_STRING();
2758    }
2759
2760    php_charmask((unsigned char *)delims, delims_len, mask TSRMLS_CC);
2761
2762    ZVAL_STRINGL(return_value, str->val, str->len);
2763    r = Z_STRVAL_P(return_value);
2764
2765    *r = toupper((unsigned char) *r);
2766    for (r_end = r + Z_STRLEN_P(return_value) - 1; r < r_end; ) {
2767        if (mask[(unsigned char)*r++]) {
2768            *r = toupper((unsigned char) *r);
2769        }
2770    }
2771}
2772/* }}} */
2773
2774/* {{{ php_strtr
2775 */
2776PHPAPI char *php_strtr(char *str, size_t len, char *str_from, char *str_to, size_t trlen)
2777{
2778    size_t i;
2779    unsigned char xlat[256];
2780
2781    if ((trlen < 1) || (len < 1)) {
2782        return str;
2783    }
2784
2785    for (i = 0; i < 256; xlat[i] = i, i++);
2786
2787    for (i = 0; i < trlen; i++) {
2788        xlat[(unsigned char) str_from[i]] = str_to[i];
2789    }
2790
2791    for (i = 0; i < len; i++) {
2792        str[i] = xlat[(unsigned char) str[i]];
2793    }
2794
2795    return str;
2796}
2797/* }}} */
2798
2799static int php_strtr_key_compare(const void *a, const void *b TSRMLS_DC) /* {{{ */
2800{
2801    Bucket *f = (Bucket *) a;
2802    Bucket *s = (Bucket *) b;
2803
2804    return f->h > s->h ? -1 : 1;
2805}
2806/* }}} */
2807
2808/* {{{ php_strtr_array */
2809static void php_strtr_array(zval *return_value, char *str, size_t slen, HashTable *pats TSRMLS_DC)
2810{
2811    zend_ulong num_key;
2812    zend_string *str_key;
2813    size_t len, pos, found;
2814    int num_keys = 0;
2815    size_t minlen = 128*1024;
2816    size_t maxlen = 0;
2817    HashTable str_hash, num_hash;
2818    zval *entry, tmp, dummy;
2819    char *key;
2820    smart_str result = {0};
2821
2822    /* we will collect all possible key lenghts */
2823    ZVAL_NULL(&dummy);
2824    zend_hash_init(&num_hash, 8, NULL, NULL, 0);
2825
2826    /* check if original array has numeric keys */
2827    ZEND_HASH_FOREACH_KEY(pats, num_key, str_key) {
2828        if (UNEXPECTED(!str_key)) {
2829            num_keys = 1;
2830        } else {
2831            len = str_key->len;
2832            if (UNEXPECTED(len < 1)) {
2833                RETURN_FALSE;
2834            } else if (UNEXPECTED(len > slen)) {
2835                /* skip long patterns */
2836                continue;
2837            }
2838            if (len > maxlen) {
2839                maxlen = len;
2840            }
2841            if (len < minlen) {
2842                minlen = len;
2843            }
2844            /* remember possible key lenght */
2845            zend_hash_index_add(&num_hash, len, &dummy);
2846        }
2847    } ZEND_HASH_FOREACH_END();
2848
2849    if (num_keys) {
2850        /* we have to rebuild HashTable with numeric keys */
2851        zend_hash_init(&str_hash, zend_hash_num_elements(pats), NULL, NULL, 0);
2852        ZEND_HASH_FOREACH_KEY_VAL(pats, num_key, str_key, entry) {
2853            if (UNEXPECTED(!str_key)) {
2854                ZVAL_LONG(&tmp, num_key);
2855                convert_to_string(&tmp);
2856                str_key = Z_STR(tmp);
2857                len = str_key->len;
2858                if (UNEXPECTED(len > slen)) {
2859                    /* skip long patterns */
2860                    zval_dtor(&tmp);
2861                    continue;
2862                }
2863                if (len > maxlen) {
2864                    maxlen = len;
2865                }
2866                if (len < minlen) {
2867                    minlen = len;
2868                }
2869                /* remember possible key lenght */
2870                zend_hash_index_add(&num_hash, len, &dummy);
2871            } else {
2872                len = str_key->len;
2873                if (UNEXPECTED(len > slen)) {
2874                    /* skip long patterns */
2875                    continue;
2876                }
2877            }
2878            zend_hash_add(&str_hash, str_key, entry);
2879            if (str_key == Z_STR(tmp)) {
2880                zval_dtor(&tmp);
2881            }
2882        } ZEND_HASH_FOREACH_END();
2883        pats = &str_hash;
2884    }
2885
2886    if (UNEXPECTED(minlen > maxlen)) {
2887        /* return the original string */
2888        if (pats == &str_hash) {
2889            zend_hash_destroy(&str_hash);
2890        }
2891        zend_hash_destroy(&num_hash);
2892        RETURN_STRINGL(str, slen);
2893    }
2894    /* select smart or simple algorithm */
2895    // TODO: tune the condition ???
2896    len = zend_hash_num_elements(&num_hash);
2897    if ((maxlen - (minlen - 1) - len > 0) &&
2898        /* smart algorithm, sort key lengths first */
2899        zend_hash_sort(&num_hash, zend_qsort, php_strtr_key_compare, 0 TSRMLS_CC) == SUCCESS) {
2900
2901        pos = 0;
2902        while (pos <= slen - minlen) {
2903            found = 0;
2904            key = str + pos;
2905            ZEND_HASH_FOREACH_NUM_KEY(&num_hash, len) {
2906                if (len > slen - pos) continue;
2907                entry = zend_hash_str_find(pats, key, len);
2908                if (entry != NULL) {
2909                    zend_string *str = zval_get_string(entry);
2910                    smart_str_appendl(&result, str->val, str->len);
2911                    pos += len;
2912                    found = 1;
2913                    zend_string_release(str);
2914                    break;
2915                }
2916            } ZEND_HASH_FOREACH_END();
2917            if (!found) {
2918                smart_str_appendc(&result, str[pos++]);
2919            }
2920        }
2921        smart_str_appendl(&result, str + pos, slen - pos);
2922    } else {
2923        /* use simple algorithm */
2924        pos = 0;
2925        while (pos <= slen - minlen) {
2926            if (maxlen > slen - pos) {
2927                maxlen = slen - pos;
2928            }
2929            found = 0;
2930            key = str + pos;
2931            for (len = maxlen; len >= minlen; len--) {
2932                entry = zend_hash_str_find(pats, key, len);
2933                if (entry != NULL) {
2934                    zend_string *str = zval_get_string(entry);
2935                    smart_str_appendl(&result, str->val, str->len);
2936                    pos += len;
2937                    found = 1;
2938                    zend_string_release(str);
2939                    break;
2940                }
2941            }
2942            if (!found) {
2943                smart_str_appendc(&result, str[pos++]);
2944            }
2945        }
2946        smart_str_appendl(&result, str + pos, slen - pos);
2947    }
2948
2949    if (pats == &str_hash) {
2950        zend_hash_destroy(&str_hash);
2951    }
2952    zend_hash_destroy(&num_hash);
2953    smart_str_0(&result);
2954    RETURN_STR(result.s);
2955}
2956/* }}} */
2957
2958/* {{{ proto string strtr(string str, string from[, string to])
2959   Translates characters in str using given translation tables */
2960PHP_FUNCTION(strtr)
2961{
2962    zval *from;
2963    char *str, *to = NULL;
2964    size_t str_len, to_len = 0;
2965    int ac = ZEND_NUM_ARGS();
2966
2967#ifndef FAST_ZPP
2968    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz|s", &str, &str_len, &from, &to, &to_len) == FAILURE) {
2969        return;
2970    }
2971#else
2972    ZEND_PARSE_PARAMETERS_START(2, 3)
2973        Z_PARAM_STRING(str, str_len)
2974        Z_PARAM_ZVAL(from)
2975        Z_PARAM_OPTIONAL
2976        Z_PARAM_STRING(to, to_len)
2977    ZEND_PARSE_PARAMETERS_END();
2978#endif
2979
2980    if (ac == 2 && Z_TYPE_P(from) != IS_ARRAY) {
2981        php_error_docref(NULL TSRMLS_CC, E_WARNING, "The second argument is not an array");
2982        RETURN_FALSE;
2983    }
2984
2985    /* shortcut for empty string */
2986    if (str_len == 0) {
2987        RETURN_EMPTY_STRING();
2988    }
2989
2990    if (ac == 2) {
2991        php_strtr_array(return_value, str, str_len, HASH_OF(from) TSRMLS_CC);
2992    } else {
2993        convert_to_string_ex(from);
2994
2995        ZVAL_STRINGL(return_value, str, str_len);
2996
2997        php_strtr(Z_STRVAL_P(return_value),
2998                  Z_STRLEN_P(return_value),
2999                  Z_STRVAL_P(from),
3000                  to,
3001                  MIN(Z_STRLEN_P(from),
3002                  to_len));
3003    }
3004}
3005/* }}} */
3006
3007/* {{{ proto string strrev(string str)
3008   Reverse a string */
3009PHP_FUNCTION(strrev)
3010{
3011    zend_string *str;
3012    char *e, *p;
3013    zend_string *n;
3014
3015    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "S", &str) == FAILURE) {
3016        return;
3017    }
3018
3019    n = zend_string_alloc(str->len, 0);
3020    p = n->val;
3021
3022    e = str->val + str->len;
3023
3024    while (--e>=str->val) {
3025        *p++ = *e;
3026    }
3027
3028    *p = '\0';
3029
3030    RETVAL_NEW_STR(n);
3031}
3032/* }}} */
3033
3034/* {{{ php_similar_str
3035 */
3036static 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)
3037{
3038    char *p, *q;
3039    char *end1 = (char *) txt1 + len1;
3040    char *end2 = (char *) txt2 + len2;
3041    size_t l;
3042
3043    *max = 0;
3044    for (p = (char *) txt1; p < end1; p++) {
3045        for (q = (char *) txt2; q < end2; q++) {
3046            for (l = 0; (p + l < end1) && (q + l < end2) && (p[l] == q[l]); l++);
3047            if (l > *max) {
3048                *max = l;
3049                *pos1 = p - txt1;
3050                *pos2 = q - txt2;
3051            }
3052        }
3053    }
3054}
3055/* }}} */
3056
3057/* {{{ php_similar_char
3058 */
3059static size_t php_similar_char(const char *txt1, size_t len1, const char *txt2, size_t len2)
3060{
3061    size_t sum;
3062    size_t pos1 = 0, pos2 = 0, max;
3063
3064    php_similar_str(txt1, len1, txt2, len2, &pos1, &pos2, &max);
3065    if ((sum = max)) {
3066        if (pos1 && pos2) {
3067            sum += php_similar_char(txt1, pos1,
3068                                    txt2, pos2);
3069        }
3070        if ((pos1 + max < len1) && (pos2 + max < len2)) {
3071            sum += php_similar_char(txt1 + pos1 + max, len1 - pos1 - max,
3072                                    txt2 + pos2 + max, len2 - pos2 - max);
3073        }
3074    }
3075
3076    return sum;
3077}
3078/* }}} */
3079
3080/* {{{ proto int similar_text(string str1, string str2 [, float percent])
3081   Calculates the similarity between two strings */
3082PHP_FUNCTION(similar_text)
3083{
3084    zend_string *t1, *t2;
3085    zval *percent = NULL;
3086    int ac = ZEND_NUM_ARGS();
3087    size_t sim;
3088
3089    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "SS|z/", &t1, &t2, &percent) == FAILURE) {
3090        return;
3091    }
3092
3093    if (ac > 2) {
3094        convert_to_double_ex(percent);
3095    }
3096
3097    if (t1->len + t2->len == 0) {
3098        if (ac > 2) {
3099            Z_DVAL_P(percent) = 0;
3100        }
3101
3102        RETURN_LONG(0);
3103    }
3104
3105    sim = php_similar_char(t1->val, t1->len, t2->val, t2->len);
3106
3107    if (ac > 2) {
3108        Z_DVAL_P(percent) = sim * 200.0 / (t1->len + t2->len);
3109    }
3110
3111    RETURN_LONG(sim);
3112}
3113/* }}} */
3114
3115/* {{{ php_stripslashes
3116 *
3117 * be careful, this edits the string in-place */
3118PHPAPI void php_stripslashes(char *str, size_t *len TSRMLS_DC)
3119{
3120    char *s, *t;
3121    size_t l;
3122
3123    if (len != NULL) {
3124        l = *len;
3125    } else {
3126        l = strlen(str);
3127    }
3128    s = str;
3129    t = str;
3130
3131    while (l > 0) {
3132        if (*t == '\\') {
3133            t++;                /* skip the slash */
3134            if (len != NULL) {
3135                (*len)--;
3136            }
3137            l--;
3138            if (l > 0) {
3139                if (*t == '0') {
3140                    *s++='\0';
3141                    t++;
3142                } else {
3143                    *s++ = *t++;    /* preserve the next character */
3144                }
3145                l--;
3146            }
3147        } else {
3148            *s++ = *t++;
3149            l--;
3150        }
3151    }
3152    if (s != t) {
3153        *s = '\0';
3154    }
3155}
3156/* }}} */
3157
3158/* {{{ proto string addcslashes(string str, string charlist)
3159   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...) */
3160PHP_FUNCTION(addcslashes)
3161{
3162    zend_string *str, *what;
3163
3164    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "SS", &str, &what) == FAILURE) {
3165        return;
3166    }
3167
3168    if (str->len == 0) {
3169        RETURN_EMPTY_STRING();
3170    }
3171
3172    if (what->len == 0) {
3173        RETURN_STRINGL(str->val, str->len);
3174    }
3175
3176    RETURN_STR(php_addcslashes(str->val, str->len, 0, what->val, what->len TSRMLS_CC));
3177}
3178/* }}} */
3179
3180/* {{{ proto string addslashes(string str)
3181   Escapes single quote, double quotes and backslash characters in a string with backslashes */
3182PHP_FUNCTION(addslashes)
3183{
3184    zend_string *str;
3185
3186#ifndef FAST_ZPP
3187    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "S", &str) == FAILURE) {
3188        return;
3189    }
3190#else
3191    ZEND_PARSE_PARAMETERS_START(1, 1)
3192        Z_PARAM_STR(str)
3193    ZEND_PARSE_PARAMETERS_END();
3194#endif
3195
3196    if (str->len == 0) {
3197        RETURN_EMPTY_STRING();
3198    }
3199
3200    RETURN_STR(php_addslashes(str->val, str->len, 0 TSRMLS_CC));
3201}
3202/* }}} */
3203
3204/* {{{ proto string stripcslashes(string str)
3205   Strips backslashes from a string. Uses C-style conventions */
3206PHP_FUNCTION(stripcslashes)
3207{
3208    zend_string *str;
3209
3210    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "S", &str) == FAILURE) {
3211        return;
3212    }
3213
3214    ZVAL_STRINGL(return_value, str->val, str->len);
3215    php_stripcslashes(Z_STRVAL_P(return_value), &Z_STRLEN_P(return_value));
3216}
3217/* }}} */
3218
3219/* {{{ proto string stripslashes(string str)
3220   Strips backslashes from a string */
3221PHP_FUNCTION(stripslashes)
3222{
3223    zend_string *str;
3224
3225    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "S", &str) == FAILURE) {
3226        return;
3227    }
3228
3229    ZVAL_STRINGL(return_value, str->val, str->len);
3230    php_stripslashes(Z_STRVAL_P(return_value), &Z_STRLEN_P(return_value) TSRMLS_CC);
3231}
3232/* }}} */
3233
3234#ifndef HAVE_STRERROR
3235/* {{{ php_strerror
3236 */
3237char *php_strerror(int errnum)
3238{
3239    extern int sys_nerr;
3240    extern char *sys_errlist[];
3241    TSRMLS_FETCH();
3242
3243    if ((unsigned int) errnum < sys_nerr) {
3244        return(sys_errlist[errnum]);
3245    }
3246
3247    (void) snprintf(BG(str_ebuf), sizeof(php_basic_globals.str_ebuf), "Unknown error: %d", errnum);
3248    return(BG(str_ebuf));
3249}
3250/* }}} */
3251#endif
3252
3253/* {{{ php_stripcslashes
3254 */
3255PHPAPI void php_stripcslashes(char *str, size_t *len)
3256{
3257    char *source, *target, *end;
3258    size_t  nlen = *len, i;
3259    char numtmp[4];
3260
3261    for (source=str, end=str+nlen, target=str; source < end; source++) {
3262        if (*source == '\\' && source+1 < end) {
3263            source++;
3264            switch (*source) {
3265                case 'n':  *target++='\n'; nlen--; break;
3266                case 'r':  *target++='\r'; nlen--; break;
3267                case 'a':  *target++='\a'; nlen--; break;
3268                case 't':  *target++='\t'; nlen--; break;
3269                case 'v':  *target++='\v'; nlen--; break;
3270                case 'b':  *target++='\b'; nlen--; break;
3271                case 'f':  *target++='\f'; nlen--; break;
3272                case '\\': *target++='\\'; nlen--; break;
3273                case 'x':
3274                    if (source+1 < end && isxdigit((int)(*(source+1)))) {
3275                        numtmp[0] = *++source;
3276                        if (source+1 < end && isxdigit((int)(*(source+1)))) {
3277                            numtmp[1] = *++source;
3278                            numtmp[2] = '\0';
3279                            nlen-=3;
3280                        } else {
3281                            numtmp[1] = '\0';
3282                            nlen-=2;
3283                        }
3284                        *target++=(char)strtol(numtmp, NULL, 16);
3285                        break;
3286                    }
3287                    /* break is left intentionally */
3288                default:
3289                    i=0;
3290                    while (source < end && *source >= '0' && *source <= '7' && i<3) {
3291                        numtmp[i++] = *source++;
3292                    }
3293                    if (i) {
3294                        numtmp[i]='\0';
3295                        *target++=(char)strtol(numtmp, NULL, 8);
3296                        nlen-=i;
3297                        source--;
3298                    } else {
3299                        *target++=*source;
3300                        nlen--;
3301                    }
3302            }
3303        } else {
3304            *target++=*source;
3305        }
3306    }
3307
3308    if (nlen != 0) {
3309        *target='\0';
3310    }
3311
3312    *len = nlen;
3313}
3314/* }}} */
3315
3316/* {{{ php_addcslashes
3317 */
3318PHPAPI zend_string *php_addcslashes(const char *str, size_t length, int should_free, char *what, size_t wlength TSRMLS_DC)
3319{
3320    char flags[256];
3321    char *source, *target;
3322    char *end;
3323    char c;
3324    size_t  newlen;
3325    zend_string *new_str = zend_string_alloc(4 * (length? length : (length = strlen(str))), 0);
3326
3327    if (!wlength) {
3328        wlength = strlen(what);
3329    }
3330
3331    php_charmask((unsigned char *)what, wlength, flags TSRMLS_CC);
3332
3333    for (source = (char*)str, end = source + length, target = new_str->val; source < end; source++) {
3334        c = *source;
3335        if (flags[(unsigned char)c]) {
3336            if ((unsigned char) c < 32 || (unsigned char) c > 126) {
3337                *target++ = '\\';
3338                switch (c) {
3339                    case '\n': *target++ = 'n'; break;
3340                    case '\t': *target++ = 't'; break;
3341                    case '\r': *target++ = 'r'; break;
3342                    case '\a': *target++ = 'a'; break;
3343                    case '\v': *target++ = 'v'; break;
3344                    case '\b': *target++ = 'b'; break;
3345                    case '\f': *target++ = 'f'; break;
3346                    default: target += sprintf(target, "%03o", (unsigned char) c);
3347                }
3348                continue;
3349            }
3350            *target++ = '\\';
3351        }
3352        *target++ = c;
3353    }
3354    *target = 0;
3355    newlen = target - new_str->val;
3356    if (newlen < length * 4) {
3357        new_str = zend_string_realloc(new_str, newlen, 0);
3358    }
3359    if (should_free) {
3360        efree((char*)str);
3361    }
3362    return new_str;
3363}
3364/* }}} */
3365
3366/* {{{ php_addslashes
3367 */
3368PHPAPI zend_string *php_addslashes(char *str, size_t length, int should_free TSRMLS_DC)
3369{
3370    /* maximum string length, worst case situation */
3371    char *source, *target;
3372    char *end;
3373    zend_string *new_str;
3374
3375    if (!str) {
3376        return STR_EMPTY_ALLOC();
3377    }
3378
3379    new_str = zend_string_alloc(2 * (length ? length : (length = strlen(str))), 0);
3380    source = str;
3381    end = source + length;
3382    target = new_str->val;
3383
3384    while (source < end) {
3385        switch (*source) {
3386            case '\0':
3387                *target++ = '\\';
3388                *target++ = '0';
3389                break;
3390            case '\'':
3391            case '\"':
3392            case '\\':
3393                *target++ = '\\';
3394                /* break is missing *intentionally* */
3395            default:
3396                *target++ = *source;
3397                break;
3398        }
3399
3400        source++;
3401    }
3402
3403    *target = 0;
3404    if (should_free) {
3405        efree(str);
3406    }
3407    new_str = zend_string_realloc(new_str, target - new_str->val, 0);
3408
3409    return new_str;
3410}
3411/* }}} */
3412
3413#define _HEB_BLOCK_TYPE_ENG 1
3414#define _HEB_BLOCK_TYPE_HEB 2
3415#define isheb(c)      (((((unsigned char) c) >= 224) && (((unsigned char) c) <= 250)) ? 1 : 0)
3416#define _isblank(c)   (((((unsigned char) c) == ' '  || ((unsigned char) c) == '\t')) ? 1 : 0)
3417#define _isnewline(c) (((((unsigned char) c) == '\n' || ((unsigned char) c) == '\r')) ? 1 : 0)
3418
3419/* {{{ php_char_to_str_ex
3420 */
3421PHPAPI size_t php_char_to_str_ex(char *str, size_t len, char from, char *to, size_t to_len, zval *result, int case_sensitivity, size_t *replace_count)
3422{
3423    size_t char_count = 0;
3424    size_t replaced = 0;
3425    char *source, *target, *tmp, *source_end=str+len, *tmp_end = NULL;
3426
3427    if (case_sensitivity) {
3428        char *p = str, *e = p + len;
3429        while ((p = memchr(p, from, (e - p)))) {
3430            char_count++;
3431            p++;
3432        }
3433    } else {
3434        for (source = str; source < source_end; source++) {
3435            if (tolower(*source) == tolower(from)) {
3436                char_count++;
3437            }
3438        }
3439    }
3440
3441    if (char_count == 0 && case_sensitivity) {
3442        ZVAL_STRINGL(result, str, len);
3443        return 0;
3444    }
3445
3446    if (to_len > 0) {
3447        ZVAL_NEW_STR(result, zend_string_safe_alloc(char_count, to_len - 1, len, 0));
3448    } else {
3449        ZVAL_NEW_STR(result, zend_string_alloc(len - char_count, 0));
3450    }
3451    target = Z_STRVAL_P(result);
3452
3453    if (case_sensitivity) {
3454        char *p = str, *e = p + len, *s = str;
3455        while ((p = memchr(p, from, (e - p)))) {
3456            memcpy(target, s, (p - s));
3457            target += p - s;
3458            memcpy(target, to, to_len);
3459            target += to_len;
3460            p++;
3461            s = p;
3462            if (replace_count) {
3463                *replace_count += 1;
3464            }
3465        }
3466        if (s < e) {
3467            memcpy(target, s, (e - s));
3468            target += e - s;
3469        }
3470    } else {
3471        for (source = str; source < source_end; source++) {
3472            if (tolower(*source) == tolower(from)) {
3473                replaced = 1;
3474                if (replace_count) {
3475                    *replace_count += 1;
3476                }
3477                for (tmp = to, tmp_end = tmp+to_len; tmp < tmp_end; tmp++) {
3478                    *target = *tmp;
3479                    target++;
3480                }
3481            } else {
3482                *target = *source;
3483                target++;
3484            }
3485        }
3486    }
3487    *target = 0;
3488    return replaced;
3489}
3490/* }}} */
3491
3492/* {{{ php_char_to_str
3493 */
3494PHPAPI size_t php_char_to_str(char *str, size_t len, char from, char *to, size_t to_len, zval *result)
3495{
3496    return php_char_to_str_ex(str, len, from, to, to_len, result, 1, NULL);
3497}
3498/* }}} */
3499
3500/* {{{ php_str_to_str_ex
3501 */
3502PHPAPI zend_string *php_str_to_str_ex(char *haystack, size_t length,
3503    char *needle, size_t needle_len, char *str, size_t str_len, int case_sensitivity, size_t *replace_count)
3504{
3505    zend_string *new_str;
3506
3507    if (needle_len < length) {
3508        char *end, *haystack_dup = NULL, *needle_dup = NULL;
3509        char *e, *s, *p, *r;
3510
3511        if (needle_len == str_len) {
3512            new_str = zend_string_init(haystack, length, 0);
3513
3514            if (case_sensitivity) {
3515                end = new_str->val + length;
3516                for (p = new_str->val; (r = (char*)php_memnstr(p, needle, needle_len, end)); p = r + needle_len) {
3517                    memcpy(r, str, str_len);
3518                    if (replace_count) {
3519                        (*replace_count)++;
3520                    }
3521                }
3522            } else {
3523                haystack_dup = estrndup(haystack, length);
3524                needle_dup = estrndup(needle, needle_len);
3525                php_strtolower(haystack_dup, length);
3526                php_strtolower(needle_dup, needle_len);
3527                end = haystack_dup + length;
3528                for (p = haystack_dup; (r = (char*)php_memnstr(p, needle_dup, needle_len, end)); p = r + needle_len) {
3529                    memcpy(new_str->val + (r - haystack_dup), str, str_len);
3530                    if (replace_count) {
3531                        (*replace_count)++;
3532                    }
3533                }
3534                efree(haystack_dup);
3535                efree(needle_dup);
3536            }
3537            return new_str;
3538        } else {
3539            if (!case_sensitivity) {
3540                haystack_dup = estrndup(haystack, length);
3541                needle_dup = estrndup(needle, needle_len);
3542                php_strtolower(haystack_dup, length);
3543                php_strtolower(needle_dup, needle_len);
3544            }
3545
3546            if (str_len < needle_len) {
3547                new_str = zend_string_alloc(length, 0);
3548            } else {
3549                size_t count = 0;
3550                char *o, *n, *endp;
3551
3552                if (case_sensitivity) {
3553                    o = haystack;
3554                    n = needle;
3555                } else {
3556                    o = haystack_dup;
3557                    n = needle_dup;
3558                }
3559                endp = o + length;
3560
3561                while ((o = (char*)php_memnstr(o, n, needle_len, endp))) {
3562                    o += needle_len;
3563                    count++;
3564                }
3565                if (count == 0) {
3566                    /* Needle doesn't occur, shortcircuit the actual replacement. */
3567                    if (haystack_dup) {
3568                        efree(haystack_dup);
3569                    }
3570                    if (needle_dup) {
3571                        efree(needle_dup);
3572                    }
3573                    new_str = zend_string_init(haystack, length, 0);
3574                    return new_str;
3575                } else {
3576                    new_str = zend_string_alloc(count * (str_len - needle_len) + length, 0);
3577                }
3578            }
3579
3580            e = s = new_str->val;
3581
3582            if (case_sensitivity) {
3583                end = haystack + length;
3584                for (p = haystack; (r = (char*)php_memnstr(p, needle, needle_len, end)); p = r + needle_len) {
3585                    memcpy(e, p, r - p);
3586                    e += r - p;
3587                    memcpy(e, str, str_len);
3588                    e += str_len;
3589                    if (replace_count) {
3590                        (*replace_count)++;
3591                    }
3592                }
3593
3594                if (p < end) {
3595                    memcpy(e, p, end - p);
3596                    e += end - p;
3597                }
3598            } else {
3599                end = haystack_dup + length;
3600
3601                for (p = haystack_dup; (r = (char*)php_memnstr(p, needle_dup, needle_len, end)); p = r + needle_len) {
3602                    memcpy(e, haystack + (p - haystack_dup), r - p);
3603                    e += r - p;
3604                    memcpy(e, str, str_len);
3605                    e += str_len;
3606                    if (replace_count) {
3607                        (*replace_count)++;
3608                    }
3609                }
3610
3611                if (p < end) {
3612                    memcpy(e, haystack + (p - haystack_dup), end - p);
3613                    e += end - p;
3614                }
3615            }
3616
3617            if (haystack_dup) {
3618                efree(haystack_dup);
3619            }
3620            if (needle_dup) {
3621                efree(needle_dup);
3622            }
3623
3624            *e = '\0';
3625
3626            new_str = zend_string_realloc(new_str, e - s, 0);
3627            return new_str;
3628        }
3629    } else if (needle_len > length) {
3630nothing_todo:
3631        new_str = zend_string_init(haystack, length, 0);
3632        return new_str;
3633    } else {
3634        if (case_sensitivity && memcmp(haystack, needle, length)) {
3635            goto nothing_todo;
3636        } else if (!case_sensitivity) {
3637            char *l_haystack, *l_needle;
3638
3639            l_haystack = estrndup(haystack, length);
3640            l_needle = estrndup(needle, length);
3641
3642            php_strtolower(l_haystack, length);
3643            php_strtolower(l_needle, length);
3644
3645            if (memcmp(l_haystack, l_needle, length)) {
3646                efree(l_haystack);
3647                efree(l_needle);
3648                goto nothing_todo;
3649            }
3650            efree(l_haystack);
3651            efree(l_needle);
3652        }
3653
3654        new_str = zend_string_init(str, str_len, 0);
3655
3656        if (replace_count) {
3657            (*replace_count)++;
3658        }
3659        return new_str;
3660    }
3661
3662}
3663/* }}} */
3664
3665/* {{{ php_str_to_str
3666 */
3667PHPAPI zend_string *php_str_to_str(char *haystack, size_t length, char *needle, size_t needle_len, char *str, size_t str_len)
3668{
3669    return php_str_to_str_ex(haystack, length, needle, needle_len, str, str_len, 1, NULL);
3670}
3671/* }}} */
3672
3673/* {{{ php_str_replace_in_subject
3674 */
3675static void php_str_replace_in_subject(zval *search, zval *replace, zval *subject, zval *result, int case_sensitivity, size_t *replace_count TSRMLS_DC)
3676{
3677    zval        *search_entry,
3678                *replace_entry = NULL,
3679                 temp_result,
3680                 tmp_subject;
3681    char        *replace_value = NULL;
3682    size_t           replace_len = 0;
3683    HashPosition pos;
3684
3685    /* Make sure we're dealing with strings. */
3686    if (Z_ISREF_P(subject)) {
3687        subject = Z_REFVAL_P(subject);
3688    }
3689    ZVAL_UNDEF(&tmp_subject);
3690    if (Z_TYPE_P(subject) != IS_STRING) {
3691        ZVAL_DUP(&tmp_subject, subject);
3692        convert_to_string_ex(&tmp_subject);
3693        subject = &tmp_subject;
3694    }
3695    if (Z_STRLEN_P(subject) == 0) {
3696        zval_ptr_dtor(&tmp_subject);
3697        ZVAL_EMPTY_STRING(result);
3698        return;
3699    }
3700//???   Z_TYPE_P(result) = IS_STRING;
3701
3702    /* If search is an array */
3703    if (Z_TYPE_P(search) == IS_ARRAY) {
3704        /* Duplicate subject string for repeated replacement */
3705        ZVAL_DUP(result, subject);
3706
3707        if (Z_TYPE_P(replace) == IS_ARRAY) {
3708            zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(replace), &pos);
3709        } else {
3710            /* Set replacement value to the passed one */
3711            replace_value = Z_STRVAL_P(replace);
3712            replace_len = Z_STRLEN_P(replace);
3713        }
3714
3715        /* For each entry in the search array, get the entry */
3716        ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(search), search_entry) {
3717            /* Make sure we're dealing with strings. */
3718            SEPARATE_ZVAL(search_entry);
3719            convert_to_string(search_entry);
3720            if (Z_STRLEN_P(search_entry) == 0) {
3721                if (Z_TYPE_P(replace) == IS_ARRAY) {
3722                    zend_hash_move_forward_ex(Z_ARRVAL_P(replace), &pos);
3723                }
3724                continue;
3725            }
3726
3727            /* If replace is an array. */
3728            if (Z_TYPE_P(replace) == IS_ARRAY) {
3729                /* Get current entry */
3730                if ((replace_entry = zend_hash_get_current_data_ex(Z_ARRVAL_P(replace), &pos)) != NULL) {
3731                    /* Make sure we're dealing with strings. */
3732                    convert_to_string_ex(replace_entry);
3733
3734                    /* Set replacement value to the one we got from array */
3735                    replace_value = Z_STRVAL_P(replace_entry);
3736                    replace_len = Z_STRLEN_P(replace_entry);
3737
3738                    zend_hash_move_forward_ex(Z_ARRVAL_P(replace), &pos);
3739                } else {
3740                    /* We've run out of replacement strings, so use an empty one. */
3741                    replace_value = "";
3742                    replace_len = 0;
3743                }
3744            }
3745
3746            if (Z_STRLEN_P(search_entry) == 1) {
3747                php_char_to_str_ex(Z_STRVAL_P(result),
3748                                Z_STRLEN_P(result),
3749                                Z_STRVAL_P(search_entry)[0],
3750                                replace_value,
3751                                replace_len,
3752                                &temp_result,
3753                                case_sensitivity,
3754                                replace_count);
3755            } else if (Z_STRLEN_P(search_entry) > 1) {
3756                ZVAL_STR(&temp_result, php_str_to_str_ex(Z_STRVAL_P(result), Z_STRLEN_P(result),
3757                            Z_STRVAL_P(search_entry), Z_STRLEN_P(search_entry),
3758                            replace_value, replace_len, case_sensitivity, replace_count));
3759            }
3760
3761            zend_string_free(Z_STR_P(result));
3762            Z_STR_P(result) = Z_STR(temp_result);
3763            Z_TYPE_INFO_P(result) = Z_TYPE_INFO(temp_result);
3764
3765            if (Z_STRLEN_P(result) == 0) {
3766                zval_ptr_dtor(&tmp_subject);
3767                return;
3768            }
3769        } ZEND_HASH_FOREACH_END();
3770    } else {
3771        if (Z_STRLEN_P(search) == 1) {
3772            php_char_to_str_ex(Z_STRVAL_P(subject),
3773                            Z_STRLEN_P(subject),
3774                            Z_STRVAL_P(search)[0],
3775                            Z_STRVAL_P(replace),
3776                            Z_STRLEN_P(replace),
3777                            result,
3778                            case_sensitivity,
3779                            replace_count);
3780        } else if (Z_STRLEN_P(search) > 1) {
3781            ZVAL_STR(result, php_str_to_str_ex(Z_STRVAL_P(subject), Z_STRLEN_P(subject),
3782                        Z_STRVAL_P(search), Z_STRLEN_P(search),
3783                        Z_STRVAL_P(replace), Z_STRLEN_P(replace), case_sensitivity, replace_count));
3784        } else {
3785            ZVAL_DUP(result, subject);
3786        }
3787    }
3788    zval_ptr_dtor(&tmp_subject);
3789}
3790/* }}} */
3791
3792/* {{{ php_str_replace_common
3793 */
3794static void php_str_replace_common(INTERNAL_FUNCTION_PARAMETERS, int case_sensitivity)
3795{
3796    zval *subject, *search, *replace, *subject_entry, *zcount = NULL;
3797    zval result;
3798    zend_string *string_key;
3799    zend_ulong num_key;
3800    size_t count = 0;
3801    int argc = ZEND_NUM_ARGS();
3802
3803#ifndef FAST_ZPP
3804    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zzz|z/", &search, &replace, &subject, &zcount) == FAILURE) {
3805        return;
3806    }
3807#else
3808    ZEND_PARSE_PARAMETERS_START(3, 4)
3809        Z_PARAM_ZVAL(search)
3810        Z_PARAM_ZVAL(replace)
3811        Z_PARAM_ZVAL(subject)
3812        Z_PARAM_OPTIONAL
3813        Z_PARAM_ZVAL_EX(zcount, 0, 1)
3814    ZEND_PARSE_PARAMETERS_END();
3815#endif
3816
3817    /* Make sure we're dealing with strings and do the replacement. */
3818    if (Z_TYPE_P(search) != IS_ARRAY) {
3819        SEPARATE_ZVAL(search);
3820        convert_to_string_ex(search);
3821        if (Z_TYPE_P(replace) != IS_STRING) {
3822            convert_to_string_ex(replace);
3823            SEPARATE_ZVAL(replace);
3824        }
3825    } else if (Z_TYPE_P(replace) != IS_ARRAY) {
3826        SEPARATE_ZVAL(replace);
3827        convert_to_string_ex(replace);
3828    }
3829
3830    /* if subject is an array */
3831    if (Z_TYPE_P(subject) == IS_ARRAY) {
3832        array_init(return_value);
3833
3834        /* For each subject entry, convert it to string, then perform replacement
3835           and add the result to the return_value array. */
3836        ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(subject), num_key, string_key, subject_entry) {
3837            if (Z_TYPE_P(subject_entry) != IS_ARRAY && Z_TYPE_P(subject_entry) != IS_OBJECT) {
3838                php_str_replace_in_subject(search, replace, subject_entry, &result, case_sensitivity, (argc > 3) ? &count : NULL TSRMLS_CC);
3839            } else {
3840                ZVAL_COPY(&result, subject_entry);
3841            }
3842            /* Add to return array */
3843            if (string_key) {
3844                zend_hash_update(Z_ARRVAL_P(return_value), string_key, &result);
3845            } else {
3846                add_index_zval(return_value, num_key, &result);
3847            }
3848        } ZEND_HASH_FOREACH_END();
3849    } else {    /* if subject is not an array */
3850        php_str_replace_in_subject(search, replace, subject, return_value, case_sensitivity, (argc > 3) ? &count : NULL TSRMLS_CC);
3851    }
3852    if (argc > 3) {
3853        zval_dtor(zcount);
3854        ZVAL_LONG(zcount, count);
3855    }
3856}
3857/* }}} */
3858
3859/* {{{ proto mixed str_replace(mixed search, mixed replace, mixed subject [, int &replace_count])
3860   Replaces all occurrences of search in haystack with replace */
3861PHP_FUNCTION(str_replace)
3862{
3863    php_str_replace_common(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
3864}
3865/* }}} */
3866
3867/* {{{ proto mixed str_ireplace(mixed search, mixed replace, mixed subject [, int &replace_count])
3868   Replaces all occurrences of search in haystack with replace / case-insensitive */
3869PHP_FUNCTION(str_ireplace)
3870{
3871    php_str_replace_common(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
3872}
3873/* }}} */
3874
3875/* {{{ php_hebrev
3876 *
3877 * Converts Logical Hebrew text (Hebrew Windows style) to Visual text
3878 * Cheers/complaints/flames - Zeev Suraski <zeev@php.net>
3879 */
3880static void php_hebrev(INTERNAL_FUNCTION_PARAMETERS, int convert_newlines)
3881{
3882    char *str;
3883    char *heb_str, *tmp, *target;
3884    size_t block_start, block_end, block_type, block_length, i;
3885    zend_long max_chars=0;
3886    size_t begin, end, char_count, orig_begin;
3887    size_t str_len;
3888    zend_string *broken_str;
3889
3890    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &str, &str_len, &max_chars) == FAILURE) {
3891        return;
3892    }
3893
3894    if (str_len == 0) {
3895        RETURN_FALSE;
3896    }
3897
3898    tmp = str;
3899    block_start=block_end=0;
3900
3901    heb_str = (char *) emalloc(str_len+1);
3902    target = heb_str+str_len;
3903    *target = 0;
3904    target--;
3905
3906    block_length=0;
3907
3908    if (isheb(*tmp)) {
3909        block_type = _HEB_BLOCK_TYPE_HEB;
3910    } else {
3911        block_type = _HEB_BLOCK_TYPE_ENG;
3912    }
3913
3914    do {
3915        if (block_type == _HEB_BLOCK_TYPE_HEB) {
3916            while ((isheb((int)*(tmp+1)) || _isblank((int)*(tmp+1)) || ispunct((int)*(tmp+1)) || (int)*(tmp+1)=='\n' ) && block_end<str_len-1) {
3917                tmp++;
3918                block_end++;
3919                block_length++;
3920            }
3921            for (i = block_start+1; i<= block_end+1; i++) {
3922                *target = str[i-1];
3923                switch (*target) {
3924                    case '(':
3925                        *target = ')';
3926                        break;
3927                    case ')':
3928                        *target = '(';
3929                        break;
3930                    case '[':
3931                        *target = ']';
3932                        break;
3933                    case ']':
3934                        *target = '[';
3935                        break;
3936                    case '{':
3937                        *target = '}';
3938                        break;
3939                    case '}':
3940                        *target = '{';
3941                        break;
3942                    case '<':
3943                        *target = '>';
3944                        break;
3945                    case '>':
3946                        *target = '<';
3947                        break;
3948                    case '\\':
3949                        *target = '/';
3950                        break;
3951                    case '/':
3952                        *target = '\\';
3953                        break;
3954                    default:
3955                        break;
3956                }
3957                target--;
3958            }
3959            block_type = _HEB_BLOCK_TYPE_ENG;
3960        } else {
3961            while (!isheb(*(tmp+1)) && (int)*(tmp+1)!='\n' && block_end < str_len-1) {
3962                tmp++;
3963                block_end++;
3964                block_length++;
3965            }
3966            while ((_isblank((int)*tmp) || ispunct((int)*tmp)) && *tmp!='/' && *tmp!='-' && block_end > block_start) {
3967                tmp--;
3968                block_end--;
3969            }
3970            for (i = block_end+1; i >= block_start+1; i--) {
3971                *target = str[i-1];
3972                target--;
3973            }
3974            block_type = _HEB_BLOCK_TYPE_HEB;
3975        }
3976        block_start=block_end+1;
3977    } while (block_end < str_len-1);
3978
3979
3980    broken_str = zend_string_alloc(str_len, 0);
3981    begin = end = str_len-1;
3982    target = broken_str->val;
3983
3984    while (1) {
3985        char_count=0;
3986        while ((!max_chars || max_chars > 0 && char_count < max_chars) && begin > 0) {
3987            char_count++;
3988            begin--;
3989            if (begin <= 0 || _isnewline(heb_str[begin])) {
3990                while (begin > 0 && _isnewline(heb_str[begin-1])) {
3991                    begin--;
3992                    char_count++;
3993                }
3994                break;
3995            }
3996        }
3997        if (max_chars >= 0 && char_count == max_chars) { /* try to avoid breaking words */
3998            size_t new_char_count=char_count, new_begin=begin;
3999
4000            while (new_char_count > 0) {
4001                if (_isblank(heb_str[new_begin]) || _isnewline(heb_str[new_begin])) {
4002                    break;
4003                }
4004                new_begin++;
4005                new_char_count--;
4006            }
4007            if (new_char_count > 0) {
4008                begin=new_begin;
4009            }
4010        }
4011        orig_begin=begin;
4012
4013        if (_isblank(heb_str[begin])) {
4014            heb_str[begin]='\n';
4015        }
4016        while (begin <= end && _isnewline(heb_str[begin])) { /* skip leading newlines */
4017            begin++;
4018        }
4019        for (i = begin; i <= end; i++) { /* copy content */
4020            *target = heb_str[i];
4021            target++;
4022        }
4023        for (i = orig_begin; i <= end && _isnewline(heb_str[i]); i++) {
4024            *target = heb_str[i];
4025            target++;
4026        }
4027        begin=orig_begin;
4028
4029        if (begin <= 0) {
4030            *target = 0;
4031            break;
4032        }
4033        begin--;
4034        end=begin;
4035    }
4036    efree(heb_str);
4037
4038    if (convert_newlines) {
4039        php_char_to_str(broken_str->val, broken_str->len,'\n', "<br />\n", 7, return_value);
4040        zend_string_free(broken_str);
4041    } else {
4042        RETURN_NEW_STR(broken_str);
4043    }
4044}
4045/* }}} */
4046
4047/* {{{ proto string hebrev(string str [, int max_chars_per_line])
4048   Converts logical Hebrew text to visual text */
4049PHP_FUNCTION(hebrev)
4050{
4051    php_hebrev(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
4052}
4053/* }}} */
4054
4055/* {{{ proto string hebrevc(string str [, int max_chars_per_line])
4056   Converts logical Hebrew text to visual text with newline conversion */
4057PHP_FUNCTION(hebrevc)
4058{
4059    php_hebrev(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
4060}
4061/* }}} */
4062
4063/* {{{ proto string nl2br(string str [, bool is_xhtml])
4064   Converts newlines to HTML line breaks */
4065PHP_FUNCTION(nl2br)
4066{
4067    /* in brief this inserts <br /> or <br> before matched regexp \n\r?|\r\n? */
4068    char    *tmp;
4069    zend_string *str;
4070    char    *end, *target;
4071    size_t  repl_cnt = 0;
4072    zend_bool   is_xhtml = 1;
4073    zend_string *result;
4074
4075    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "S|b", &str, &is_xhtml) == FAILURE) {
4076        return;
4077    }
4078
4079    tmp = str->val;
4080    end = str->val + str->len;
4081
4082    /* it is really faster to scan twice and allocate mem once instead of scanning once
4083       and constantly reallocing */
4084    while (tmp < end) {
4085        if (*tmp == '\r') {
4086            if (*(tmp+1) == '\n') {
4087                tmp++;
4088            }
4089            repl_cnt++;
4090        } else if (*tmp == '\n') {
4091            if (*(tmp+1) == '\r') {
4092                tmp++;
4093            }
4094            repl_cnt++;
4095        }
4096
4097        tmp++;
4098    }
4099
4100    if (repl_cnt == 0) {
4101        RETURN_STRINGL(str->val, str->len);
4102    }
4103
4104    {
4105        size_t repl_len = is_xhtml ? (sizeof("<br />") - 1) : (sizeof("<br>") - 1);
4106
4107        result = zend_string_alloc(repl_cnt * repl_len + str->len, 0);
4108        target = result->val;
4109    }
4110
4111    tmp = str->val;
4112    while (tmp < end) {
4113        switch (*tmp) {
4114            case '\r':
4115            case '\n':
4116                *target++ = '<';
4117                *target++ = 'b';
4118                *target++ = 'r';
4119
4120                if (is_xhtml) {
4121                    *target++ = ' ';
4122                    *target++ = '/';
4123                }
4124
4125                *target++ = '>';
4126
4127                if ((*tmp == '\r' && *(tmp+1) == '\n') || (*tmp == '\n' && *(tmp+1) == '\r')) {
4128                    *target++ = *tmp++;
4129                }
4130                /* lack of a break; is intentional */
4131            default:
4132                *target++ = *tmp;
4133        }
4134
4135        tmp++;
4136    }
4137
4138    *target = '\0';
4139
4140    RETURN_NEW_STR(result);
4141}
4142/* }}} */
4143
4144/* {{{ proto string strip_tags(string str [, string allowable_tags])
4145   Strips HTML and PHP tags from a string */
4146PHP_FUNCTION(strip_tags)
4147{
4148    zend_string *buf;
4149    zend_string *str;
4150    zval *allow=NULL;
4151    char *allowed_tags=NULL;
4152    size_t allowed_tags_len=0;
4153
4154    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "S|z", &str, &allow) == FAILURE) {
4155        return;
4156    }
4157
4158    /* To maintain a certain BC, we allow anything for the second parameter and return original string */
4159    if (allow != NULL) {
4160        convert_to_string_ex(allow);
4161// TODO: reimplement to avoid reallocation ???
4162        if (IS_INTERNED(Z_STR_P(allow))) {
4163            allowed_tags = estrndup(Z_STRVAL_P(allow), Z_STRLEN_P(allow));
4164            allowed_tags_len = Z_STRLEN_P(allow);
4165        } else {
4166            allowed_tags = Z_STRVAL_P(allow);
4167            allowed_tags_len = Z_STRLEN_P(allow);
4168        }
4169    }
4170
4171    buf = zend_string_init(str->val, str->len, 0);
4172    buf->len = php_strip_tags_ex(buf->val, str->len, NULL, allowed_tags, allowed_tags_len, 0);
4173
4174// TODO: reimplement to avoid reallocation ???
4175    if (allow && IS_INTERNED(Z_STR_P(allow))) {
4176        efree(allowed_tags);
4177    }
4178    RETURN_STR(buf);
4179}
4180/* }}} */
4181
4182/* {{{ proto string setlocale(mixed category, string locale [, string ...])
4183   Set locale information */
4184PHP_FUNCTION(setlocale)
4185{
4186    zval *args = NULL;
4187    zval *pcategory, *plocale;
4188    int num_args, cat, i = 0;
4189    char *loc, *retval;
4190    HashPosition pos;
4191
4192    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z+", &pcategory, &args, &num_args) == FAILURE) {
4193        return;
4194    }
4195
4196#ifdef HAVE_SETLOCALE
4197    if (Z_TYPE_P(pcategory) == IS_LONG) {
4198        cat = Z_LVAL_P(pcategory);
4199    } else {
4200        /* FIXME: The following behaviour should be removed. */
4201        char *category;
4202        zval tmp;
4203
4204        php_error_docref(NULL TSRMLS_CC, E_DEPRECATED, "Passing locale category name as string is deprecated. Use the LC_* -constants instead");
4205
4206        ZVAL_DUP(&tmp, pcategory);
4207        convert_to_string_ex(&tmp);
4208        category = Z_STRVAL(tmp);
4209
4210        if (!strcasecmp("LC_ALL", category)) {
4211            cat = LC_ALL;
4212        } else if (!strcasecmp("LC_COLLATE", category)) {
4213            cat = LC_COLLATE;
4214        } else if (!strcasecmp("LC_CTYPE", category)) {
4215            cat = LC_CTYPE;
4216#ifdef LC_MESSAGES
4217        } else if (!strcasecmp("LC_MESSAGES", category)) {
4218            cat = LC_MESSAGES;
4219#endif
4220        } else if (!strcasecmp("LC_MONETARY", category)) {
4221            cat = LC_MONETARY;
4222        } else if (!strcasecmp("LC_NUMERIC", category)) {
4223            cat = LC_NUMERIC;
4224        } else if (!strcasecmp("LC_TIME", category)) {
4225            cat = LC_TIME;
4226        } else {
4227            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid locale category name %s, must be one of LC_ALL, LC_COLLATE, LC_CTYPE, LC_MONETARY, LC_NUMERIC, or LC_TIME", category);
4228
4229            zval_dtor(&tmp);
4230            RETURN_FALSE;
4231        }
4232        zval_dtor(&tmp);
4233    }
4234
4235    if (Z_TYPE(args[0]) == IS_ARRAY) {
4236        zend_hash_internal_pointer_reset_ex(Z_ARRVAL(args[0]), &pos);
4237    }
4238
4239    while (1) {
4240        zval tmp;
4241        if (Z_TYPE(args[0]) == IS_ARRAY) {
4242            if (!zend_hash_num_elements(Z_ARRVAL(args[0]))) {
4243                break;
4244            }
4245            if ((plocale = zend_hash_get_current_data_ex(Z_ARRVAL(args[0]), &pos)) == NULL) {
4246                break;
4247            }
4248        } else {
4249            plocale = &args[i];
4250        }
4251
4252        ZVAL_DUP(&tmp, plocale);
4253        convert_to_string(&tmp);
4254
4255        if (!strcmp ("0", Z_STRVAL(tmp))) {
4256            loc = NULL;
4257        } else {
4258            loc = Z_STRVAL(tmp);
4259            if (Z_STRLEN(tmp) >= 255) {
4260                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Specified locale name is too long");
4261                zval_dtor(&tmp);
4262                break;
4263            }
4264        }
4265
4266        retval = php_my_setlocale(cat, loc);
4267        zend_update_current_locale();
4268        if (retval) {
4269            /* Remember if locale was changed */
4270            if (loc) {
4271//???           zend_string_free(BG(locale_string));
4272                if (BG(locale_string)) {
4273                    efree(BG(locale_string));
4274                }
4275                BG(locale_string) = estrdup(retval);
4276            }
4277
4278            zval_dtor(&tmp);
4279            RETURN_STRING(retval);
4280        }
4281        zval_dtor(&tmp);
4282
4283        if (Z_TYPE(args[0]) == IS_ARRAY) {
4284            if (zend_hash_move_forward_ex(Z_ARRVAL(args[0]), &pos) == FAILURE) break;
4285        } else {
4286            if (++i >= num_args) break;
4287        }
4288    }
4289
4290#endif
4291    RETURN_FALSE;
4292}
4293/* }}} */
4294
4295/* {{{ proto void parse_str(string encoded_string [, array result])
4296   Parses GET/POST/COOKIE data and sets global variables */
4297PHP_FUNCTION(parse_str)
4298{
4299    char *arg;
4300    zval *arrayArg = NULL;
4301    char *res = NULL;
4302    size_t arglen;
4303
4304    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|z/", &arg, &arglen, &arrayArg) == FAILURE) {
4305        return;
4306    }
4307
4308    res = estrndup(arg, arglen);
4309
4310    if (arrayArg == NULL) {
4311        zval tmp;
4312        zend_array *symbol_table = zend_rebuild_symbol_table(TSRMLS_C);
4313
4314        ZVAL_ARR(&tmp, symbol_table);
4315        sapi_module.treat_data(PARSE_STRING, res, &tmp TSRMLS_CC);
4316    } else  {
4317        zval ret;
4318
4319        /* Clear out the array that was passed in. */
4320        zval_dtor(arrayArg);
4321        array_init(&ret);
4322        sapi_module.treat_data(PARSE_STRING, res, &ret TSRMLS_CC);
4323        ZVAL_COPY_VALUE(arrayArg, &ret);
4324    }
4325}
4326/* }}} */
4327
4328#define PHP_TAG_BUF_SIZE 1023
4329
4330/* {{{ php_tag_find
4331 *
4332 * Check if tag is in a set of tags
4333 *
4334 * states:
4335 *
4336 * 0 start tag
4337 * 1 first non-whitespace char seen
4338 */
4339int php_tag_find(char *tag, size_t len, char *set) {
4340    char c, *n, *t;
4341    int state=0, done=0;
4342    char *norm;
4343
4344    if (len <= 0) {
4345        return 0;
4346    }
4347
4348    norm = emalloc(len+1);
4349
4350    n = norm;
4351    t = tag;
4352    c = tolower(*t);
4353    /*
4354       normalize the tag removing leading and trailing whitespace
4355       and turn any <a whatever...> into just <a> and any </tag>
4356       into <tag>
4357    */
4358    while (!done) {
4359        switch (c) {
4360            case '<':
4361                *(n++) = c;
4362                break;
4363            case '>':
4364                done =1;
4365                break;
4366            default:
4367                if (!isspace((int)c)) {
4368                    if (state == 0) {
4369                        state=1;
4370                    }
4371                    if (c != '/') {
4372                        *(n++) = c;
4373                    }
4374                } else {
4375                    if (state == 1)
4376                        done=1;
4377                }
4378                break;
4379        }
4380        c = tolower(*(++t));
4381    }
4382    *(n++) = '>';
4383    *n = '\0';
4384    if (strstr(set, norm)) {
4385        done=1;
4386    } else {
4387        done=0;
4388    }
4389    efree(norm);
4390    return done;
4391}
4392/* }}} */
4393
4394PHPAPI size_t php_strip_tags(char *rbuf, size_t len, int *stateptr, char *allow, size_t allow_len) /* {{{ */
4395{
4396    return php_strip_tags_ex(rbuf, len, stateptr, allow, allow_len, 0);
4397}
4398/* }}} */
4399
4400/* {{{ php_strip_tags
4401
4402    A simple little state-machine to strip out html and php tags
4403
4404    State 0 is the output state, State 1 means we are inside a
4405    normal html tag and state 2 means we are inside a php tag.
4406
4407    The state variable is passed in to allow a function like fgetss
4408    to maintain state across calls to the function.
4409
4410    lc holds the last significant character read and br is a bracket
4411    counter.
4412
4413    When an allow string is passed in we keep track of the string
4414    in state 1 and when the tag is closed check it against the
4415    allow string to see if we should allow it.
4416
4417    swm: Added ability to strip <?xml tags without assuming it PHP
4418    code.
4419*/
4420PHPAPI size_t php_strip_tags_ex(char *rbuf, size_t len, int *stateptr, char *allow, size_t allow_len, zend_bool allow_tag_spaces)
4421{
4422    char *tbuf, *buf, *p, *tp, *rp, c, lc;
4423    int br, depth=0, in_q = 0;
4424    int state = 0;
4425    size_t pos, i = 0;
4426    char *allow_free = NULL;
4427
4428    if (stateptr)
4429        state = *stateptr;
4430
4431    buf = estrndup(rbuf, len);
4432    c = *buf;
4433    lc = '\0';
4434    p = buf;
4435    rp = rbuf;
4436    br = 0;
4437    if (allow) {
4438//???       if (IS_INTERNED(allow)) {
4439//???           allow_free = allow = zend_str_tolower_dup(allow, allow_len);
4440//???       } else {
4441            allow_free = NULL;
4442            php_strtolower(allow, allow_len);
4443//???       }
4444        tbuf = emalloc(PHP_TAG_BUF_SIZE + 1);
4445        tp = tbuf;
4446    } else {
4447        tbuf = tp = NULL;
4448    }
4449
4450    while (i < len) {
4451        switch (c) {
4452            case '\0':
4453                break;
4454            case '<':
4455                if (in_q) {
4456                    break;
4457                }
4458                if (isspace(*(p + 1)) && !allow_tag_spaces) {
4459                    goto reg_char;
4460                }
4461                if (state == 0) {
4462                    lc = '<';
4463                    state = 1;
4464                    if (allow) {
4465                        if (tp - tbuf >= PHP_TAG_BUF_SIZE) {
4466                            pos = tp - tbuf;
4467                            tbuf = erealloc(tbuf, (tp - tbuf) + PHP_TAG_BUF_SIZE + 1);
4468                            tp = tbuf + pos;
4469                        }
4470                        *(tp++) = '<';
4471                    }
4472                } else if (state == 1) {
4473                    depth++;
4474                }
4475                break;
4476
4477            case '(':
4478                if (state == 2) {
4479                    if (lc != '"' && lc != '\'') {
4480                        lc = '(';
4481                        br++;
4482                    }
4483                } else if (allow && state == 1) {
4484                    if (tp - tbuf >= PHP_TAG_BUF_SIZE) {
4485                        pos = tp - tbuf;
4486                        tbuf = erealloc(tbuf, (tp - tbuf) + PHP_TAG_BUF_SIZE + 1);
4487                        tp = tbuf + pos;
4488                    }
4489                    *(tp++) = c;
4490                } else if (state == 0) {
4491                    *(rp++) = c;
4492                }
4493                break;
4494
4495            case ')':
4496                if (state == 2) {
4497                    if (lc != '"' && lc != '\'') {
4498                        lc = ')';
4499                        br--;
4500                    }
4501                } else if (allow && state == 1) {
4502                    if (tp - tbuf >= PHP_TAG_BUF_SIZE) {
4503                        pos = tp - tbuf;
4504                        tbuf = erealloc(tbuf, (tp - tbuf) + PHP_TAG_BUF_SIZE + 1);
4505                        tp = tbuf + pos;
4506                    }
4507                    *(tp++) = c;
4508                } else if (state == 0) {
4509                    *(rp++) = c;
4510                }
4511                break;
4512
4513            case '>':
4514                if (depth) {
4515                    depth--;
4516                    break;
4517                }
4518
4519                if (in_q) {
4520                    break;
4521                }
4522
4523                switch (state) {
4524                    case 1: /* HTML/XML */
4525                        lc = '>';
4526                        in_q = state = 0;
4527                        if (allow) {
4528                            if (tp - tbuf >= PHP_TAG_BUF_SIZE) {
4529                                pos = tp - tbuf;
4530                                tbuf = erealloc(tbuf, (tp - tbuf) + PHP_TAG_BUF_SIZE + 1);
4531                                tp = tbuf + pos;
4532                            }
4533                            *(tp++) = '>';
4534                            *tp='\0';
4535                            if (php_tag_find(tbuf, tp-tbuf, allow)) {
4536                                memcpy(rp, tbuf, tp-tbuf);
4537                                rp += tp-tbuf;
4538                            }
4539                            tp = tbuf;
4540                        }
4541                        break;
4542
4543                    case 2: /* PHP */
4544                        if (!br && lc != '\"' && *(p-1) == '?') {
4545                            in_q = state = 0;
4546                            tp = tbuf;
4547                        }
4548                        break;
4549
4550                    case 3:
4551                        in_q = state = 0;
4552                        tp = tbuf;
4553                        break;
4554
4555                    case 4: /* JavaScript/CSS/etc... */
4556                        if (p >= buf + 2 && *(p-1) == '-' && *(p-2) == '-') {
4557                            in_q = state = 0;
4558                            tp = tbuf;
4559                        }
4560                        break;
4561
4562                    default:
4563                        *(rp++) = c;
4564                        break;
4565                }
4566                break;
4567
4568            case '"':
4569            case '\'':
4570                if (state == 4) {
4571                    /* Inside <!-- comment --> */
4572                    break;
4573                } else if (state == 2 && *(p-1) != '\\') {
4574                    if (lc == c) {
4575                        lc = '\0';
4576                    } else if (lc != '\\') {
4577                        lc = c;
4578                    }
4579                } else if (state == 0) {
4580                    *(rp++) = c;
4581                } else if (allow && state == 1) {
4582                    if (tp - tbuf >= PHP_TAG_BUF_SIZE) {
4583                        pos = tp - tbuf;
4584                        tbuf = erealloc(tbuf, (tp - tbuf) + PHP_TAG_BUF_SIZE + 1);
4585                        tp = tbuf + pos;
4586                    }
4587                    *(tp++) = c;
4588                }
4589                if (state && p != buf && (state == 1 || *(p-1) != '\\') && (!in_q || *p == in_q)) {
4590                    if (in_q) {
4591                        in_q = 0;
4592                    } else {
4593                        in_q = *p;
4594                    }
4595                }
4596                break;
4597
4598            case '!':
4599                /* JavaScript & Other HTML scripting languages */
4600                if (state == 1 && *(p-1) == '<') {
4601                    state = 3;
4602                    lc = c;
4603                } else {
4604                    if (state == 0) {
4605                        *(rp++) = c;
4606                    } else if (allow && state == 1) {
4607                        if (tp - tbuf >= PHP_TAG_BUF_SIZE) {
4608                            pos = tp - tbuf;
4609                            tbuf = erealloc(tbuf, (tp - tbuf) + PHP_TAG_BUF_SIZE + 1);
4610                            tp = tbuf + pos;
4611                        }
4612                        *(tp++) = c;
4613                    }
4614                }
4615                break;
4616
4617            case '-':
4618                if (state == 3 && p >= buf + 2 && *(p-1) == '-' && *(p-2) == '!') {
4619                    state = 4;
4620                } else {
4621                    goto reg_char;
4622                }
4623                break;
4624
4625            case '?':
4626
4627                if (state == 1 && *(p-1) == '<') {
4628                    br=0;
4629                    state=2;
4630                    break;
4631                }
4632
4633            case 'E':
4634            case 'e':
4635                /* !DOCTYPE exception */
4636                if (state==3 && p > buf+6
4637                             && tolower(*(p-1)) == 'p'
4638                             && tolower(*(p-2)) == 'y'
4639                             && tolower(*(p-3)) == 't'
4640                             && tolower(*(p-4)) == 'c'
4641                             && tolower(*(p-5)) == 'o'
4642                             && tolower(*(p-6)) == 'd') {
4643                    state = 1;
4644                    break;
4645                }
4646                /* fall-through */
4647
4648            case 'l':
4649            case 'L':
4650
4651                /* swm: If we encounter '<?xml' then we shouldn't be in
4652                 * state == 2 (PHP). Switch back to HTML.
4653                 */
4654
4655                if (state == 2 && p > buf+2 && strncasecmp(p-2, "xm", 2) == 0) {
4656                    state = 1;
4657                    break;
4658                }
4659
4660                /* fall-through */
4661            default:
4662reg_char:
4663                if (state == 0) {
4664                    *(rp++) = c;
4665                } else if (allow && state == 1) {
4666                    if (tp - tbuf >= PHP_TAG_BUF_SIZE) {
4667                        pos = tp - tbuf;
4668                        tbuf = erealloc(tbuf, (tp - tbuf) + PHP_TAG_BUF_SIZE + 1);
4669                        tp = tbuf + pos;
4670                    }
4671                    *(tp++) = c;
4672                }
4673                break;
4674        }
4675        c = *(++p);
4676        i++;
4677    }
4678    if (rp < rbuf + len) {
4679        *rp = '\0';
4680    }
4681    efree(buf);
4682    if (allow) {
4683        efree(tbuf);
4684        if (allow_free) {
4685            efree(allow_free);
4686        }
4687    }
4688    if (stateptr)
4689        *stateptr = state;
4690
4691    return (size_t)(rp - rbuf);
4692}
4693/* }}} */
4694
4695/* {{{ proto array str_getcsv(string input[, string delimiter[, string enclosure[, string escape]]])
4696Parse a CSV string into an array */
4697PHP_FUNCTION(str_getcsv)
4698{
4699    zend_string *str;
4700    char delim = ',', enc = '"', esc = '\\';
4701    char *delim_str = NULL, *enc_str = NULL, *esc_str = NULL;
4702    size_t delim_len = 0, enc_len = 0, esc_len = 0;
4703
4704    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "S|sss", &str, &delim_str, &delim_len,
4705        &enc_str, &enc_len, &esc_str, &esc_len) == FAILURE) {
4706        return;
4707    }
4708
4709    delim = delim_len ? delim_str[0] : delim;
4710    enc = enc_len ? enc_str[0] : enc;
4711    esc = esc_len ? esc_str[0] : esc;
4712
4713    php_fgetcsv(NULL, delim, enc, esc, str->len, str->val, return_value TSRMLS_CC);
4714}
4715/* }}} */
4716
4717/* {{{ proto string str_repeat(string input, int mult)
4718   Returns the input string repeat mult times */
4719PHP_FUNCTION(str_repeat)
4720{
4721    zend_string     *input_str;     /* Input string */
4722    zend_long       mult;           /* Multiplier */
4723    zend_string *result;        /* Resulting string */
4724    size_t      result_len;     /* Length of the resulting string */
4725
4726    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Sl", &input_str, &mult) == FAILURE) {
4727        return;
4728    }
4729
4730    if (mult < 0) {
4731        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Second argument has to be greater than or equal to 0");
4732        return;
4733    }
4734
4735    /* Don't waste our time if it's empty */
4736    /* ... or if the multiplier is zero */
4737    if (input_str->len == 0 || mult == 0)
4738        RETURN_EMPTY_STRING();
4739
4740    /* Initialize the result string */
4741    result = zend_string_safe_alloc(input_str->len, mult, 0, 0);
4742    result_len = input_str->len * mult;
4743
4744    /* Heavy optimization for situations where input string is 1 byte long */
4745    if (input_str->len == 1) {
4746        memset(result->val, *(input_str->val), mult);
4747    } else {
4748        char *s, *e, *ee;
4749        ptrdiff_t l=0;
4750        memcpy(result->val, input_str->val, input_str->len);
4751        s = result->val;
4752        e = result->val + input_str->len;
4753        ee = result->val + result_len;
4754
4755        while (e<ee) {
4756            l = (e-s) < (ee-e) ? (e-s) : (ee-e);
4757            memmove(e, s, l);
4758            e += l;
4759        }
4760    }
4761
4762    result->val[result_len] = '\0';
4763
4764    RETURN_NEW_STR(result);
4765}
4766/* }}} */
4767
4768/* {{{ proto mixed count_chars(string input [, int mode])
4769   Returns info about what characters are used in input */
4770PHP_FUNCTION(count_chars)
4771{
4772    zend_string *input;
4773    int chars[256];
4774    zend_long mymode=0;
4775    unsigned char *buf;
4776    int inx;
4777    char retstr[256];
4778    size_t retlen=0;
4779    size_t tmp = 0;
4780
4781    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "S|l", &input, &mymode) == FAILURE) {
4782        return;
4783    }
4784
4785    if (mymode < 0 || mymode > 4) {
4786        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unknown mode");
4787        RETURN_FALSE;
4788    }
4789
4790    buf = (unsigned char *) input->val;
4791    memset((void*) chars, 0, sizeof(chars));
4792
4793    while (tmp < input->len) {
4794        chars[*buf]++;
4795        buf++;
4796        tmp++;
4797    }
4798
4799    if (mymode < 3) {
4800        array_init(return_value);
4801    }
4802
4803    for (inx = 0; inx < 256; inx++) {
4804        switch (mymode) {
4805            case 0:
4806                add_index_long(return_value, inx, chars[inx]);
4807                break;
4808            case 1:
4809                if (chars[inx] != 0) {
4810                    add_index_long(return_value, inx, chars[inx]);
4811                }
4812                break;
4813            case 2:
4814                if (chars[inx] == 0) {
4815                    add_index_long(return_value, inx, chars[inx]);
4816                }
4817                break;
4818            case 3:
4819                if (chars[inx] != 0) {
4820                    retstr[retlen++] = inx;
4821                }
4822                break;
4823            case 4:
4824                if (chars[inx] == 0) {
4825                    retstr[retlen++] = inx;
4826                }
4827                break;
4828        }
4829    }
4830
4831    if (mymode >= 3 && mymode <= 4) {
4832        RETURN_STRINGL(retstr, retlen);
4833    }
4834}
4835/* }}} */
4836
4837/* {{{ php_strnatcmp
4838 */
4839static void php_strnatcmp(INTERNAL_FUNCTION_PARAMETERS, int fold_case)
4840{
4841    zend_string *s1, *s2;
4842
4843    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "SS", &s1, &s2) == FAILURE) {
4844        return;
4845    }
4846
4847    RETURN_LONG(strnatcmp_ex(s1->val, s1->len,
4848                             s2->val, s2->len,
4849                             fold_case));
4850}
4851/* }}} */
4852
4853PHPAPI int string_natural_compare_function_ex(zval *result, zval *op1, zval *op2, zend_bool case_insensitive TSRMLS_DC) /* {{{ */
4854{
4855    zend_string *str1 = zval_get_string(op1);
4856    zend_string *str2 = zval_get_string(op2);
4857
4858    ZVAL_LONG(result, strnatcmp_ex(str1->val, str1->len, str2->val, str2->len, case_insensitive));
4859
4860    zend_string_release(str1);
4861    zend_string_release(str2);
4862    return SUCCESS;
4863}
4864/* }}} */
4865
4866PHPAPI int string_natural_case_compare_function(zval *result, zval *op1, zval *op2 TSRMLS_DC) /* {{{ */
4867{
4868    return string_natural_compare_function_ex(result, op1, op2, 1 TSRMLS_CC);
4869}
4870/* }}} */
4871
4872PHPAPI int string_natural_compare_function(zval *result, zval *op1, zval *op2 TSRMLS_DC) /* {{{ */
4873{
4874    return string_natural_compare_function_ex(result, op1, op2, 0 TSRMLS_CC);
4875}
4876/* }}} */
4877
4878/* {{{ proto int strnatcmp(string s1, string s2)
4879   Returns the result of string comparison using 'natural' algorithm */
4880PHP_FUNCTION(strnatcmp)
4881{
4882    php_strnatcmp(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
4883}
4884/* }}} */
4885
4886/* {{{ proto array localeconv(void)
4887   Returns numeric formatting information based on the current locale */
4888PHP_FUNCTION(localeconv)
4889{
4890    zval grouping, mon_grouping;
4891    int len, i;
4892
4893    /* We don't need no stinkin' parameters... */
4894    if (zend_parse_parameters_none() == FAILURE) {
4895        return;
4896    }
4897
4898    array_init(return_value);
4899    array_init(&grouping);
4900    array_init(&mon_grouping);
4901
4902#ifdef HAVE_LOCALECONV
4903    {
4904        struct lconv currlocdata;
4905
4906        localeconv_r( &currlocdata );
4907
4908        /* Grab the grouping data out of the array */
4909        len = strlen(currlocdata.grouping);
4910
4911        for (i = 0; i < len; i++) {
4912            add_index_long(&grouping, i, currlocdata.grouping[i]);
4913        }
4914
4915        /* Grab the monetary grouping data out of the array */
4916        len = strlen(currlocdata.mon_grouping);
4917
4918        for (i = 0; i < len; i++) {
4919            add_index_long(&mon_grouping, i, currlocdata.mon_grouping[i]);
4920        }
4921
4922        add_assoc_string(return_value, "decimal_point",     currlocdata.decimal_point);
4923        add_assoc_string(return_value, "thousands_sep",     currlocdata.thousands_sep);
4924        add_assoc_string(return_value, "int_curr_symbol",   currlocdata.int_curr_symbol);
4925        add_assoc_string(return_value, "currency_symbol",   currlocdata.currency_symbol);
4926        add_assoc_string(return_value, "mon_decimal_point", currlocdata.mon_decimal_point);
4927        add_assoc_string(return_value, "mon_thousands_sep", currlocdata.mon_thousands_sep);
4928        add_assoc_string(return_value, "positive_sign",     currlocdata.positive_sign);
4929        add_assoc_string(return_value, "negative_sign",     currlocdata.negative_sign);
4930        add_assoc_long(  return_value, "int_frac_digits",   currlocdata.int_frac_digits);
4931        add_assoc_long(  return_value, "frac_digits",       currlocdata.frac_digits);
4932        add_assoc_long(  return_value, "p_cs_precedes",     currlocdata.p_cs_precedes);
4933        add_assoc_long(  return_value, "p_sep_by_space",    currlocdata.p_sep_by_space);
4934        add_assoc_long(  return_value, "n_cs_precedes",     currlocdata.n_cs_precedes);
4935        add_assoc_long(  return_value, "n_sep_by_space",    currlocdata.n_sep_by_space);
4936        add_assoc_long(  return_value, "p_sign_posn",       currlocdata.p_sign_posn);
4937        add_assoc_long(  return_value, "n_sign_posn",       currlocdata.n_sign_posn);
4938    }
4939#else
4940    /* Ok, it doesn't look like we have locale info floating around, so I guess it
4941       wouldn't hurt to just go ahead and return the POSIX locale information?  */
4942
4943    add_index_long(&grouping, 0, -1);
4944    add_index_long(&mon_grouping, 0, -1);
4945
4946    add_assoc_string(return_value, "decimal_point",     "\x2E");
4947    add_assoc_string(return_value, "thousands_sep",     "");
4948    add_assoc_string(return_value, "int_curr_symbol",   "");
4949    add_assoc_string(return_value, "currency_symbol",   "");
4950    add_assoc_string(return_value, "mon_decimal_point", "\x2E");
4951    add_assoc_string(return_value, "mon_thousands_sep", "");
4952    add_assoc_string(return_value, "positive_sign",     "");
4953    add_assoc_string(return_value, "negative_sign",     "");
4954    add_assoc_long(  return_value, "int_frac_digits",   CHAR_MAX);
4955    add_assoc_long(  return_value, "frac_digits",       CHAR_MAX);
4956    add_assoc_long(  return_value, "p_cs_precedes",     CHAR_MAX);
4957    add_assoc_long(  return_value, "p_sep_by_space",    CHAR_MAX);
4958    add_assoc_long(  return_value, "n_cs_precedes",     CHAR_MAX);
4959    add_assoc_long(  return_value, "n_sep_by_space",    CHAR_MAX);
4960    add_assoc_long(  return_value, "p_sign_posn",       CHAR_MAX);
4961    add_assoc_long(  return_value, "n_sign_posn",       CHAR_MAX);
4962#endif
4963
4964    zend_hash_str_update(Z_ARRVAL_P(return_value), "grouping", sizeof("grouping")-1, &grouping);
4965    zend_hash_str_update(Z_ARRVAL_P(return_value), "mon_grouping", sizeof("mon_grouping")-1, &mon_grouping);
4966}
4967/* }}} */
4968
4969/* {{{ proto int strnatcasecmp(string s1, string s2)
4970   Returns the result of case-insensitive string comparison using 'natural' algorithm */
4971PHP_FUNCTION(strnatcasecmp)
4972{
4973    php_strnatcmp(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
4974}
4975/* }}} */
4976
4977/* {{{ proto int substr_count(string haystack, string needle [, int offset [, int length]])
4978   Returns the number of times a substring occurs in the string */
4979PHP_FUNCTION(substr_count)
4980{
4981    char *haystack, *needle;
4982    zend_long offset = 0, length = 0;
4983    int ac = ZEND_NUM_ARGS();
4984    int count = 0;
4985    size_t haystack_len, needle_len;
4986    char *p, *endp, cmp;
4987
4988    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss|ll", &haystack, &haystack_len, &needle, &needle_len, &offset, &length) == FAILURE) {
4989        return;
4990    }
4991
4992    if (needle_len == 0) {
4993        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Empty substring");
4994        RETURN_FALSE;
4995    }
4996
4997    p = haystack;
4998    endp = p + haystack_len;
4999
5000    if (offset < 0) {
5001        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Offset should be greater than or equal to 0");
5002        RETURN_FALSE;
5003    }
5004
5005    if ((size_t)offset > haystack_len) {
5006        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Offset value " ZEND_LONG_FMT " exceeds string length", offset);
5007        RETURN_FALSE;
5008    }
5009    p += offset;
5010
5011    if (ac == 4) {
5012
5013        if (length <= 0) {
5014            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Length should be greater than 0");
5015            RETURN_FALSE;
5016        }
5017        if (length > (haystack_len - offset)) {
5018            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Length value " ZEND_LONG_FMT " exceeds string length", length);
5019            RETURN_FALSE;
5020        }
5021        endp = p + length;
5022    }
5023
5024    if (needle_len == 1) {
5025        cmp = needle[0];
5026
5027        while ((p = memchr(p, cmp, endp - p))) {
5028            count++;
5029            p++;
5030        }
5031    } else {
5032        while ((p = (char*)php_memnstr(p, needle, needle_len, endp))) {
5033            p += needle_len;
5034            count++;
5035        }
5036    }
5037
5038    RETURN_LONG(count);
5039}
5040/* }}} */
5041
5042/* {{{ proto string str_pad(string input, int pad_length [, string pad_string [, int pad_type]])
5043   Returns input string padded on the left or right to specified length with pad_string */
5044PHP_FUNCTION(str_pad)
5045{
5046    /* Input arguments */
5047    zend_string *input;             /* Input string */
5048    zend_long pad_length;           /* Length to pad to */
5049
5050    /* Helper variables */
5051    size_t num_pad_chars;       /* Number of padding characters (total - input size) */
5052    char *pad_str = " "; /* Pointer to padding string */
5053    size_t pad_str_len = 1;
5054    zend_long   pad_type_val = STR_PAD_RIGHT; /* The padding type value */
5055    size_t     i, left_pad=0, right_pad=0;
5056    zend_string *result = NULL; /* Resulting string */
5057
5058    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Sl|sl", &input, &pad_length, &pad_str, &pad_str_len, &pad_type_val) == FAILURE) {
5059        return;
5060    }
5061
5062    /* If resulting string turns out to be shorter than input string,
5063       we simply copy the input and return. */
5064    if (pad_length < 0  || (size_t)pad_length <= input->len) {
5065        RETURN_STRINGL(input->val, input->len);
5066    }
5067
5068    if (pad_str_len == 0) {
5069        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Padding string cannot be empty");
5070        return;
5071    }
5072
5073    if (pad_type_val < STR_PAD_LEFT || pad_type_val > STR_PAD_BOTH) {
5074        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Padding type has to be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH");
5075        return;
5076    }
5077
5078    num_pad_chars = pad_length - input->len;
5079    if (num_pad_chars >= INT_MAX) {
5080        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Padding length is too long");
5081        return;
5082    }
5083
5084    result = zend_string_alloc(input->len + num_pad_chars, 0);
5085    result->len = 0;
5086
5087    /* We need to figure out the left/right padding lengths. */
5088    switch (pad_type_val) {
5089        case STR_PAD_RIGHT:
5090            left_pad = 0;
5091            right_pad = num_pad_chars;
5092            break;
5093
5094        case STR_PAD_LEFT:
5095            left_pad = num_pad_chars;
5096            right_pad = 0;
5097            break;
5098
5099        case STR_PAD_BOTH:
5100            left_pad = num_pad_chars / 2;
5101            right_pad = num_pad_chars - left_pad;
5102            break;
5103    }
5104
5105    /* First we pad on the left. */
5106    for (i = 0; i < left_pad; i++)
5107        result->val[result->len++] = pad_str[i % pad_str_len];
5108
5109    /* Then we copy the input string. */
5110    memcpy(result->val + result->len, input->val, input->len);
5111    result->len += input->len;
5112
5113    /* Finally, we pad on the right. */
5114    for (i = 0; i < right_pad; i++)
5115        result->val[result->len++] = pad_str[i % pad_str_len];
5116
5117    result->val[result->len] = '\0';
5118
5119    RETURN_NEW_STR(result);
5120}
5121/* }}} */
5122
5123/* {{{ proto mixed sscanf(string str, string format [, string ...])
5124   Implements an ANSI C compatible sscanf */
5125PHP_FUNCTION(sscanf)
5126{
5127    zval *args = NULL;
5128    char *str, *format;
5129    size_t str_len, format_len;
5130    int result, num_args = 0;
5131
5132    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss*", &str, &str_len, &format, &format_len,
5133        &args, &num_args) == FAILURE) {
5134        return;
5135    }
5136
5137    result = php_sscanf_internal(str, format, num_args, args, 0, return_value TSRMLS_CC);
5138
5139    if (SCAN_ERROR_WRONG_PARAM_COUNT == result) {
5140        WRONG_PARAM_COUNT;
5141    }
5142}
5143/* }}} */
5144
5145static char rot13_from[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
5146static char rot13_to[] = "nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM";
5147
5148/* {{{ proto string str_rot13(string str)
5149   Perform the rot13 transform on a string */
5150PHP_FUNCTION(str_rot13)
5151{
5152    zend_string *arg;
5153
5154    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "S", &arg) == FAILURE) {
5155        return;
5156    }
5157
5158    RETVAL_STRINGL(arg->val, arg->len);
5159
5160    php_strtr(Z_STRVAL_P(return_value), Z_STRLEN_P(return_value), rot13_from, rot13_to, 52);
5161}
5162/* }}} */
5163
5164static void php_string_shuffle(char *str, zend_long len TSRMLS_DC) /* {{{ */
5165{
5166    zend_long n_elems, rnd_idx, n_left;
5167    char temp;
5168    /* The implementation is stolen from array_data_shuffle       */
5169    /* Thus the characteristics of the randomization are the same */
5170    n_elems = len;
5171
5172    if (n_elems <= 1) {
5173        return;
5174    }
5175
5176    n_left = n_elems;
5177
5178    while (--n_left) {
5179        rnd_idx = php_rand(TSRMLS_C);
5180        RAND_RANGE(rnd_idx, 0, n_left, PHP_RAND_MAX);
5181        if (rnd_idx != n_left) {
5182            temp = str[n_left];
5183            str[n_left] = str[rnd_idx];
5184            str[rnd_idx] = temp;
5185        }
5186    }
5187}
5188/* }}} */
5189
5190/* {{{ proto void str_shuffle(string str)
5191   Shuffles string. One permutation of all possible is created */
5192PHP_FUNCTION(str_shuffle)
5193{
5194    zend_string *arg;
5195
5196    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "S", &arg) == FAILURE) {
5197        return;
5198    }
5199
5200    RETVAL_STRINGL(arg->val, arg->len);
5201    if (Z_STRLEN_P(return_value) > 1) {
5202        php_string_shuffle(Z_STRVAL_P(return_value), (zend_long) Z_STRLEN_P(return_value) TSRMLS_CC);
5203    }
5204}
5205/* }}} */
5206
5207/* {{{ proto mixed str_word_count(string str, [int format [, string charlist]])
5208    Counts the number of words inside a string. If format of 1 is specified,
5209    then the function will return an array containing all the words
5210    found inside the string. If format of 2 is specified, then the function
5211    will return an associated array where the position of the word is the key
5212    and the word itself is the value.
5213
5214    For the purpose of this function, 'word' is defined as a locale dependent
5215    string containing alphabetic characters, which also may contain, but not start
5216    with "'" and "-" characters.
5217*/
5218PHP_FUNCTION(str_word_count)
5219{
5220    zend_string *str;
5221    char *char_list = NULL, *p, *e, *s, ch[256];
5222    size_t char_list_len = 0, word_count = 0;
5223    zend_long type = 0;
5224
5225    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "S|ls", &str, &type, &char_list, &char_list_len) == FAILURE) {
5226        return;
5227    }
5228
5229    switch(type) {
5230        case 1:
5231        case 2:
5232            array_init(return_value);
5233            if (!str->len) {
5234                return;
5235            }
5236            break;
5237        case 0:
5238            if (!str->len) {
5239                RETURN_LONG(0);
5240            }
5241            /* nothing to be done */
5242            break;
5243        default:
5244            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid format value " ZEND_LONG_FMT, type);
5245            RETURN_FALSE;
5246    }
5247
5248    if (char_list) {
5249        php_charmask((unsigned char *)char_list, char_list_len, ch TSRMLS_CC);
5250    }
5251
5252    p = str->val;
5253    e = str->val + str->len;
5254
5255    /* first character cannot be ' or -, unless explicitly allowed by the user */
5256    if ((*p == '\'' && (!char_list || !ch['\''])) || (*p == '-' && (!char_list || !ch['-']))) {
5257        p++;
5258    }
5259    /* last character cannot be -, unless explicitly allowed by the user */
5260    if (*(e - 1) == '-' && (!char_list || !ch['-'])) {
5261        e--;
5262    }
5263
5264    while (p < e) {
5265        s = p;
5266        while (p < e && (isalpha((unsigned char)*p) || (char_list && ch[(unsigned char)*p]) || *p == '\'' || *p == '-')) {
5267            p++;
5268        }
5269        if (p > s) {
5270            switch (type)
5271            {
5272                case 1:
5273                    add_next_index_stringl(return_value, s, p - s);
5274                    break;
5275                case 2:
5276                    add_index_stringl(return_value, (s - str->val), s, p - s);
5277                    break;
5278                default:
5279                    word_count++;
5280                    break;
5281            }
5282        }
5283        p++;
5284    }
5285
5286    if (!type) {
5287        RETURN_LONG(word_count);
5288    }
5289}
5290
5291/* }}} */
5292
5293#if HAVE_STRFMON
5294/* {{{ proto string money_format(string format , float value)
5295   Convert monetary value(s) to string */
5296PHP_FUNCTION(money_format)
5297{
5298    size_t format_len = 0;
5299    char *format, *p, *e;
5300    double value;
5301    zend_bool check = 0;
5302    zend_string *str;
5303
5304    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sd", &format, &format_len, &value) == FAILURE) {
5305        return;
5306    }
5307
5308    p = format;
5309    e = p + format_len;
5310    while ((p = memchr(p, '%', (e - p)))) {
5311        if (*(p + 1) == '%') {
5312            p += 2;
5313        } else if (!check) {
5314            check = 1;
5315            p++;
5316        } else {
5317            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Only a single %%i or %%n token can be used");
5318            RETURN_FALSE;
5319        }
5320    }
5321
5322    str = zend_string_alloc(format_len + 1024, 0);
5323    if ((str->len = strfmon(str->val, str->len, format, value)) < 0) {
5324        zend_string_free(str);
5325        RETURN_FALSE;
5326    }
5327    str->val[str->len] = '\0';
5328
5329    RETURN_NEW_STR(zend_string_realloc(str, str->len, 0));
5330}
5331/* }}} */
5332#endif
5333
5334/* {{{ proto array str_split(string str [, int split_length])
5335   Convert a string to an array. If split_length is specified, break the string down into chunks each split_length characters long. */
5336PHP_FUNCTION(str_split)
5337{
5338    zend_string *str;
5339    zend_long split_length = 1;
5340    char *p;
5341    size_t n_reg_segments;
5342
5343    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "S|l", &str, &split_length) == FAILURE) {
5344        return;
5345    }
5346
5347    if (split_length <= 0) {
5348        php_error_docref(NULL TSRMLS_CC, E_WARNING, "The length of each segment must be greater than zero");
5349        RETURN_FALSE;
5350    }
5351
5352
5353    if (0 == str->len || (size_t)split_length >= str->len) {
5354        array_init_size(return_value, 1);
5355        add_next_index_stringl(return_value, str->val, str->len);
5356        return;
5357    }
5358
5359    array_init_size(return_value, ((str->len - 1) / split_length) + 1);
5360
5361    n_reg_segments = str->len / split_length;
5362    p = str->val;
5363
5364    while (n_reg_segments-- > 0) {
5365        add_next_index_stringl(return_value, p, split_length);
5366        p += split_length;
5367    }
5368
5369    if (p != (str->val + str->len)) {
5370        add_next_index_stringl(return_value, p, (str->val + str->len - p));
5371    }
5372}
5373/* }}} */
5374
5375/* {{{ proto array strpbrk(string haystack, string char_list)
5376   Search a string for any of a set of characters */
5377PHP_FUNCTION(strpbrk)
5378{
5379    zend_string *haystack, *char_list;
5380    char *haystack_ptr, *cl_ptr;
5381
5382    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "SS", &haystack, &char_list) == FAILURE) {
5383        RETURN_FALSE;
5384    }
5385
5386    if (!char_list->len) {
5387        php_error_docref(NULL TSRMLS_CC, E_WARNING, "The character list cannot be empty");
5388        RETURN_FALSE;
5389    }
5390
5391    for (haystack_ptr = haystack->val; haystack_ptr < (haystack->val + haystack->len); ++haystack_ptr) {
5392        for (cl_ptr = char_list->val; cl_ptr < (char_list->val + char_list->len); ++cl_ptr) {
5393            if (*cl_ptr == *haystack_ptr) {
5394                RETURN_STRINGL(haystack_ptr, (haystack->val + haystack->len - haystack_ptr));
5395            }
5396        }
5397    }
5398
5399    RETURN_FALSE;
5400}
5401/* }}} */
5402
5403/* {{{ proto int substr_compare(string main_str, string str, int offset [, int length [, bool case_sensitivity]])
5404   Binary safe optionally case insensitive comparison of 2 strings from an offset, up to length characters */
5405PHP_FUNCTION(substr_compare)
5406{
5407    zend_string *s1, *s2;
5408    zend_long offset, len=0;
5409    zend_bool cs=0;
5410    size_t cmp_len;
5411
5412    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "SSl|lb", &s1, &s2, &offset, &len, &cs) == FAILURE) {
5413        RETURN_FALSE;
5414    }
5415
5416    if (ZEND_NUM_ARGS() >= 4 && len <= 0) {
5417        if (len == 0) {
5418            RETURN_LONG(0L);
5419        } else {
5420            php_error_docref(NULL TSRMLS_CC, E_WARNING, "The length must be greater than or equal to zero");
5421            RETURN_FALSE;
5422        }
5423    }
5424
5425    if (offset < 0) {
5426        offset = s1->len + offset;
5427        offset = (offset < 0) ? 0 : offset;
5428    }
5429
5430    if ((size_t)offset >= s1->len) {
5431        php_error_docref(NULL TSRMLS_CC, E_WARNING, "The start position cannot exceed initial string length");
5432        RETURN_FALSE;
5433    }
5434
5435    cmp_len = (size_t) (len ? len : MAX(s2->len, (s1->len - offset)));
5436
5437    if (!cs) {
5438        RETURN_LONG(zend_binary_strncmp(s1->val + offset, (s1->len - offset), s2->val, s2->len, cmp_len));
5439    } else {
5440        RETURN_LONG(zend_binary_strncasecmp_l(s1->val + offset, (s1->len - offset), s2->val, s2->len, cmp_len));
5441    }
5442}
5443/* }}} */
5444
5445/*
5446 * Local variables:
5447 * tab-width: 4
5448 * c-basic-offset: 4
5449 * End:
5450 * vim600: noet sw=4 ts=4 fdm=marker
5451 * vim<600: noet sw=4 ts=4
5452 */
5453