/*
   +----------------------------------------------------------------------+
   | PHP version 4.0                                                      |
   +----------------------------------------------------------------------+
   | Copyright (c) 1997, 1998, 1999, 2000 The PHP Group                   |
   +----------------------------------------------------------------------+
   | This source file is subject to version 2.02 of the PHP license,      |
   | that is bundled with this package in the file LICENSE, and is        |
   | available at through the world-wide-web at                           |
   | http://www.php.net/license/2_02.txt.                                 |
   | If you did not receive a copy of the PHP license and are unable to   |
   | obtain it through the world-wide-web, please send a note to          |
   | license@php.net so we can mail you a copy immediately.               |
   +----------------------------------------------------------------------+
   | Authors: Rasmus Lerdorf <rasmus@php.net>                             |
   |          Stig Sæther Bakken <ssb@fast.no>                            |
   |          Zeev Suraski <zeev@zend.com>                                |
   +----------------------------------------------------------------------+
 */

/* $Id: string.c,v 1.168.2.1 2000/12/14 16:00:31 sas Exp $ */

/* Synced with php 3.0 revision 1.193 1999-06-16 [ssb] */

#include <stdio.h>
#include "php.h"
#include "reg.h"
#include "php_string.h"
#include "php_variables.h"
#ifdef HAVE_LOCALE_H
# include <locale.h>
#endif
#include "scanf.h"
#include "zend_API.h"
#include "zend_execute.h"
#include "php_globals.h"
#include "basic_functions.h"
#include "php_smart_str.h"

#define STR_PAD_LEFT			0
#define STR_PAD_RIGHT			1
#define STR_PAD_BOTH			2
#define PHP_PATHINFO_DIRNAME 	0
#define PHP_PATHINFO_BASENAME 	1
#define PHP_PATHINFO_EXTENSION 	2

void register_string_constants(INIT_FUNC_ARGS)
{
	REGISTER_LONG_CONSTANT("STR_PAD_LEFT", STR_PAD_LEFT, CONST_CS | CONST_PERSISTENT);
	REGISTER_LONG_CONSTANT("STR_PAD_RIGHT", STR_PAD_RIGHT, CONST_CS | CONST_PERSISTENT);
	REGISTER_LONG_CONSTANT("STR_PAD_BOTH", STR_PAD_BOTH, CONST_CS | CONST_PERSISTENT);
	REGISTER_LONG_CONSTANT("PATHINFO_DIRNAME", PHP_PATHINFO_DIRNAME, CONST_CS | CONST_PERSISTENT);
	REGISTER_LONG_CONSTANT("PATHINFO_BASENAME", PHP_PATHINFO_BASENAME, CONST_CS | CONST_PERSISTENT);
	REGISTER_LONG_CONSTANT("PATHINFO_EXTENSION", PHP_PATHINFO_EXTENSION, CONST_CS | CONST_PERSISTENT);
}

int php_tag_find(char *tag, int len, char *set);

/* this is read-only, so it's ok */
static char hexconvtab[] = "0123456789abcdef";

static char *php_bin2hex(const unsigned char *old, const size_t oldlen, size_t *newlen)
{
	unsigned char *result = NULL;
	size_t i, j;

	result = (char *) emalloc(oldlen * 2 * sizeof(char));
	if(!result) {
		return result;
	}
	
	for(i = j = 0; i < oldlen; i++) {
		result[j++] = hexconvtab[old[i] >> 4];
		result[j++] = hexconvtab[old[i] & 15];
	}

	if(newlen) *newlen = oldlen * 2 * sizeof(char);

	return result;
}

/* {{{ proto string bin2hex(string data)
   Converts the binary representation of data to hex */
PHP_FUNCTION(bin2hex)
{
	zval **data;
	char *result;
	size_t newlen;

	if(ZEND_NUM_ARGS() != 1 || zend_get_parameters_ex(1, &data) == FAILURE) {
		WRONG_PARAM_COUNT;
	}

	convert_to_string_ex(data);

	result = php_bin2hex((*data)->value.str.val, (*data)->value.str.len, &newlen);
	
	if(!result) {
		RETURN_FALSE;
	}

	RETURN_STRINGL(result, newlen, 0);
}
/* }}} */

/* {{{ proto int strspn(string str, string mask)
   Find length of initial segment consisting entirely of characters found in mask */
PHP_FUNCTION(strspn)
{
	zval **s1,**s2;
	
	if (ZEND_NUM_ARGS()!=2 || zend_get_parameters_ex(2, &s1, &s2) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
	convert_to_string_ex(s1);
	convert_to_string_ex(s2);
	RETURN_LONG(php_strspn((*s1)->value.str.val, (*s2)->value.str.val,
                           (*s1)->value.str.val + (*s1)->value.str.len,
						   (*s2)->value.str.val + (*s2)->value.str.len));
}
/* }}} */

/* {{{ proto int strcspn(string str, string mask)
   Find length of initial segment consisting entirely of characters not found in mask */
PHP_FUNCTION(strcspn)
{
	zval **s1,**s2;
	
	if (ZEND_NUM_ARGS()!=2 || zend_get_parameters_ex(2, &s1, &s2) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
	convert_to_string_ex(s1);
	convert_to_string_ex(s2);
	RETURN_LONG(php_strcspn((*s1)->value.str.val, (*s2)->value.str.val,
				            (*s1)->value.str.val + (*s1)->value.str.len,
							(*s2)->value.str.val + (*s2)->value.str.len));
}
/* }}} */

PHPAPI void php_trim(zval *str, zval * return_value, int mode)
/* mode 1 : trim left
   mode 2 : trim right
   mode 3 : trim left and right
*/
{
	register int i;
	int len = str->value.str.len;
	int trimmed = 0;
	char *c = str->value.str.val;

	if (mode & 1) {
		for (i = 0; i < len; i++) {
			if (c[i] == ' ' || c[i] == '\n' || c[i] == '\r' ||
				c[i] == '\t' || c[i] == '\v' || c[i] == '\0') {
				trimmed++;
			} else {
				break;
			}
		}
		len -= trimmed;
		c += trimmed;
	}
	if (mode & 2) {
		for (i = len - 1; i >= 0; i--) {
			if (c[i] == ' ' || c[i] == '\n' || c[i] == '\r' ||
				c[i] == '\t' || c[i] == '\v' || c[i] == '\0') {
				len--;
			} else {
				break;
			}
		}
	}
	RETVAL_STRINGL(c, len, 1);
}

/* {{{ proto string rtrim(string str)
   An alias for chop */
/* }}} */

/* {{{ proto string chop(string str)
   Remove trailing whitespace */
PHP_FUNCTION(chop)
{
	zval **str;
	
	if (ZEND_NUM_ARGS() != 1 || zend_get_parameters_ex(1, &str) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
	convert_to_string_ex(str);

	if ((*str)->type == IS_STRING) {
		php_trim(*str, return_value, 2);
		return;
	}
	RETURN_FALSE;
}
/* }}} */

/* {{{ proto string trim(string str)
   Strip whitespace from the beginning and end of a string */
PHP_FUNCTION(trim)
{
	zval **str;
	
	if (ZEND_NUM_ARGS() != 1 || zend_get_parameters_ex(1, &str) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
	convert_to_string_ex(str);

	if ((*str)->type == IS_STRING) {
		php_trim(*str, return_value, 3);
		return;
	}
	RETURN_FALSE;
}
/* }}} */

/* {{{ proto string ltrim(string str)
   Strip whitespace from the beginning of a string */
PHP_FUNCTION(ltrim)
{
	zval **str;
	
	if (ZEND_NUM_ARGS() != 1 || zend_get_parameters_ex(1, &str) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
	convert_to_string_ex(str);
	if ((*str)->type == IS_STRING) {
		php_trim(*str, return_value, 1);
		return;
	}
	RETURN_FALSE;
}
/* }}} */


/* {{{ proto string wordwrap(string str [, int width [, string break [, int cut]]])
   Wrap buffer to selected number of characters using string break char */
PHP_FUNCTION(wordwrap)
{
	pval **ptext, **plinelength, **pbreakchar, **cut;
	long i=0, l=0, pgr=0, linelength=0, last=0, breakcharlen, docut=0;
	char *text, *breakchar, *newtext;

	if (ZEND_NUM_ARGS() < 1 || ZEND_NUM_ARGS() > 4 || zend_get_parameters_ex(ZEND_NUM_ARGS(), &ptext, &plinelength, &pbreakchar, &cut) == FAILURE) {
		WRONG_PARAM_COUNT;
	}

	convert_to_string_ex(ptext);
	text = (*ptext)->value.str.val;

	if (ZEND_NUM_ARGS() > 1) {
		convert_to_long_ex(plinelength);
		linelength = (*plinelength)->value.lval;
	}
	else {
		linelength = 75;
	}

	if (ZEND_NUM_ARGS() > 2) {
		convert_to_string_ex(pbreakchar);
		breakchar = (*pbreakchar)->value.str.val;
		breakcharlen = (*pbreakchar)->value.str.len;
	}
	else {
		breakchar = "\n";
		breakcharlen = 1;
	}

	if (ZEND_NUM_ARGS() > 3) {
		convert_to_long_ex(cut);
		docut = (*cut)->value.lval;
	}

	/* Special case for a single-character break as it needs no
	   additional storage space */

	if (breakcharlen == 1 && docut == 0) {
		newtext = estrdup (text);
		while (newtext[i] != '\0') {
			/* prescan line to see if it is greater than linelength */
			l = 0;
			while (newtext[i+l] != breakchar[0]) {
				if (newtext[i+l] == '\0') {
					l --;
					break;
				}
				l++;
			}
			if (l >= linelength) {
				pgr = l;
				l = linelength;
				/* needs breaking; work backwards to find previous word */
				while (l >= 0) {
					if (newtext[i+l] == ' ') {
						newtext[i+l] = breakchar[0];
						break;
					}
					l --;
				}
				if (l == -1) {
					/* couldn't break is backwards, try looking forwards */
					l = linelength;
					while (l <= pgr) {
						if(newtext[i+l] == ' ') {
							newtext[i+l] = breakchar[0];
							break;
						}
						l ++;
					}
				}
			}
			i += l+1;
		}
		RETVAL_STRINGL(newtext, strlen(newtext), 1);
		efree(newtext);
	}
	else {
		/* Multiple character line break */
		newtext = emalloc((*ptext)->value.str.len * (breakcharlen+1));
		newtext[0] = '\0';

		i = 0;
		while (text[i] != '\0') {
			/* prescan line to see if it is greater than linelength */
			l = 0;
			while (text[i+l] != '\0') {
				if (text[i+l] == breakchar[0]) {
					if (breakcharlen == 1 || strncmp(text+i+l, breakchar, breakcharlen)==0)
						break;
				}
				l ++;
			}
			if (l >= linelength) {
				pgr = l;
				l = linelength;

				/* needs breaking; work backwards to find previous word */
				while (l >= 0) {
					if (text[i+l] == ' ') {
						strncat(newtext, text+last, i+l-last);
						strcat(newtext, breakchar);
						last = i + l + 1;
						break;
					}
					l --;
				}
				if (l == -1) {
					/* couldn't break it backwards, try looking forwards */
					l = linelength;
					while (l <= pgr) {
						if (docut == 0)
						{
							if (text[i+l] == ' ') {
								strncat(newtext, text+last, i+l-last);
								strcat(newtext, breakchar);
								last = i + l + 1;
								break;
							}
						}
						if (docut == 1)
						{
							if (text[i+l] == ' ' || l > i-last) {
								strncat(newtext, text+last, i+l-last);
								strcat(newtext, breakchar);
								last = i + l;
								break;
							}
						}
						l ++;
					}
				}
				i += l+1;
			}
			else {
				i += (l ? l : 1);
			}
		}

		if (i+l > last) {
			strcat(newtext, text+last);
		}

		RETVAL_STRINGL(newtext, strlen(newtext), 1);
		efree(newtext);
	}
}
/* }}} */


PHPAPI void php_explode(zval *delim, zval *str, zval *return_value, int limit) 
{
	char *p1, *p2, *endp;

	endp = str->value.str.val + str->value.str.len;

	p1 = str->value.str.val;
	p2 = php_memnstr(str->value.str.val, delim->value.str.val, delim->value.str.len, endp);

	if (p2 == NULL) {
		add_next_index_stringl(return_value, p1, str->value.str.len, 1);
	} else {
		do {
			add_next_index_stringl(return_value, p1, p2-p1, 1);
			p1 = p2 + delim->value.str.len;
		} while ((p2 = php_memnstr(p1, delim->value.str.val, delim->value.str.len, endp)) != NULL &&
				 (limit == -1 || --limit > 1));

		if (p1 <= endp)
			add_next_index_stringl(return_value, p1, endp-p1, 1);
	}
}

/* {{{ proto array explode(string separator, string str [, int limit])
   Split a string on string separator and return array of components */
PHP_FUNCTION(explode)
{
	zval **str, **delim, **zlimit = NULL;
	int limit;

	switch (ZEND_NUM_ARGS()) {
	case 2:
		if (zend_get_parameters_ex(2, &delim, &str) == FAILURE)
			WRONG_PARAM_COUNT;
		limit=-1;
		break;
	case 3:
		if (zend_get_parameters_ex(3, &delim, &str, &zlimit) == FAILURE)
		WRONG_PARAM_COUNT;
		convert_to_long_ex(zlimit);
		limit = (*zlimit)->value.lval;
		break;
	default:
		WRONG_PARAM_COUNT;                                         
	}

	convert_to_string_ex(str);
	convert_to_string_ex(delim);

	if (! (*delim)->value.str.len) {
		php_error(E_WARNING,"Empty delimiter");
		RETURN_FALSE;
	}

	if (array_init(return_value) == FAILURE) {
		RETURN_FALSE;
	}

	if(limit==0 || limit==1) {
		add_index_stringl(return_value, 0, (*str)->value.str.val, (*str)->value.str.len, 1);
	} else {
		php_explode(*delim, *str, return_value, limit);
	}
}
/* }}} */


/* {{{ proto string join(array src, string glue)
   An alias for implode */
/* }}} */
PHPAPI void php_implode(zval *delim, zval *arr, zval *return_value) 
{
	zval **tmp;
	int len = 0, count = 0, target = 0;
	HashPosition pos;

	/* convert everything to strings, and calculate length */
	zend_hash_internal_pointer_reset_ex(arr->value.ht, &pos);
	while (zend_hash_get_current_data_ex(arr->value.ht, (void **) &tmp, &pos) == SUCCESS) {
		convert_to_string_ex(tmp);
		len += (*tmp)->value.str.len;
		if (count>0) {
			len += delim->value.str.len;
		}
		count++;
		zend_hash_move_forward_ex(arr->value.ht, &pos);
	}

	/* do it */
	return_value->value.str.val = (char *) emalloc(len + 1);
	return_value->value.str.val[0] = '\0';
	return_value->value.str.val[len] = '\0';
	zend_hash_internal_pointer_reset_ex(arr->value.ht, &pos);
	while (zend_hash_get_current_data_ex(arr->value.ht, (void **) &tmp, &pos) == SUCCESS) {
		count--;
		memcpy(return_value->value.str.val + target, (*tmp)->value.str.val,
               (*tmp)->value.str.len);
		target += (*tmp)->value.str.len; 
		if (count > 0) {
			memcpy(return_value->value.str.val + target, delim->value.str.val,
                   delim->value.str.len);
			target += delim->value.str.len;
		}
		zend_hash_move_forward_ex(arr->value.ht, &pos);
	}
	return_value->type = IS_STRING;
	return_value->value.str.len = len;
}


/* {{{ proto string implode(array src, string glue)
   Join array elements placing glue string between items and return one string */
PHP_FUNCTION(implode)
{
	zval **arg1, **arg2, *delim, *arr;
	
	if (ZEND_NUM_ARGS() != 2 || zend_get_parameters_ex(2, &arg1, &arg2) == FAILURE) {
		WRONG_PARAM_COUNT;
	}

	if ((*arg1)->type == IS_ARRAY) {
		SEPARATE_ZVAL(arg1);
		arr = *arg1;
		convert_to_string_ex(arg2);
		delim = *arg2;
	} else if ((*arg2)->type == IS_ARRAY) {
		SEPARATE_ZVAL(arg2)
		arr = *arg2;
		convert_to_string_ex(arg1);
		delim = *arg1;
	} else {
		php_error(E_WARNING, "Bad arguments to %s()",
				   get_active_function_name());
		return;
	}
	php_implode(delim, arr, return_value);
}
/* }}} */


/* {{{ proto string strtok([string str,] string token)
   Tokenize a string */
PHP_FUNCTION(strtok)
{
	zval **str, **tok;
	char *token = NULL, *tokp=NULL;
	char *first = NULL;
	int argc;
	BLS_FETCH();
	
	argc = ZEND_NUM_ARGS();

	if ((argc == 1 && zend_get_parameters_ex(1, &tok) == FAILURE) ||
		(argc == 2 && zend_get_parameters_ex(2, &str, &tok) == FAILURE) ||
		argc < 1 || argc > 2) {
		WRONG_PARAM_COUNT;
	}
	convert_to_string_ex(tok);
	tokp = token = (*tok)->value.str.val;

	if (argc == 2) {
		convert_to_string_ex(str);

		STR_FREE(BG(strtok_string));
		BG(strtok_string) = estrndup((*str)->value.str.val,(*str)->value.str.len);
		BG(strtok_pos1) = BG(strtok_string);
		BG(strtok_pos2) = NULL;
	}
	if (BG(strtok_pos1) && *BG(strtok_pos1)) {
		for ( /* NOP */ ; token && *token; token++) {
			BG(strtok_pos2) = strchr(BG(strtok_pos1), (int) *token);
			if (!first || (BG(strtok_pos2) && BG(strtok_pos2) < first)) {
				first = BG(strtok_pos2);
			}
		}						/* NB: token is unusable now */

		BG(strtok_pos2) = first;
		if (BG(strtok_pos2)) {
			*BG(strtok_pos2) = '\0';
		}
		RETVAL_STRING(BG(strtok_pos1),1);
#if 0
		/* skip 'token' white space for next call to strtok */
		while (BG(strtok_pos2) && 
			strchr(tokp, *(BG(strtok_pos2)+1))) {
			BG(strtok_pos2)++;
		}
#endif
		if (BG(strtok_pos2))
			BG(strtok_pos1) = BG(strtok_pos2) + 1;
		else
			BG(strtok_pos1) = NULL;
	} else {
		RETVAL_FALSE;
	}
}
/* }}} */

PHPAPI char *php_strtoupper(char *s, size_t len)
{
	char *c;
	int ch;
	size_t i;

	c = s;
	for (i=0; i<len; i++) {
		ch = toupper((unsigned char)*c);
		*c++ = ch;
	}
	return (s);
}

/* {{{ proto string strtoupper(string str)
   Make a string uppercase */
PHP_FUNCTION(strtoupper)
{
	zval **arg;
	
	if (ZEND_NUM_ARGS() != 1 || zend_get_parameters_ex(1, &arg)) {
		WRONG_PARAM_COUNT;
	}
	convert_to_string_ex(arg);

	*return_value = **arg;
	zval_copy_ctor(return_value);
	php_strtoupper(return_value->value.str.val, return_value->value.str.len);
}
/* }}} */


PHPAPI char *php_strtolower(char *s, size_t len)
{
	register int ch;
	char *c;
	size_t i;

	c = s;
	for (i=0; i<len; i++) {
		ch = tolower((unsigned char)*c);
		*c++ = ch;
	}
	return (s);
}

/* {{{ proto string strtolower(string str)
   Make a string lowercase */
PHP_FUNCTION(strtolower)
{
	zval **str;
	char *ret;
	
	if (ZEND_NUM_ARGS() != 1 || zend_get_parameters_ex(1, &str)) {
		WRONG_PARAM_COUNT;
	}
	convert_to_string_ex(str);

	*return_value = **str;
	zval_copy_ctor(return_value);
	ret = php_strtolower(return_value->value.str.val, return_value->value.str.len);
}
/* }}} */

PHPAPI char *php_basename(char *s, size_t len)
{
	char *ret=NULL, *c, *p=NULL, buf='\0';
	c = s + len - 1;	

	/* strip trailing slashes */
	while (*c == '/'
#ifdef PHP_WIN32
		   || *c == '\\'
#endif
		)
		c--;
	if(c < s+len-1) {
		buf = *(c + 1);  /* Save overwritten char */
		*(c + 1) = '\0'; /* overwrite char */
		p = c + 1;       /* Save pointer to overwritten char */
	}

	if ((c = strrchr(s, '/'))
#ifdef PHP_WIN32
		|| (c = strrchr(s, '\\'))
#endif
		) {
		ret = estrdup(c + 1);
	} else {
		ret = estrdup(s);
	}
	if(buf) *p = buf;
	return (ret);
}

/* {{{ proto string basename(string path)
   Return the filename component of the path */
PHP_FUNCTION(basename)
{
	zval **str;
	char *ret;

	if (ZEND_NUM_ARGS() != 1 || zend_get_parameters_ex(1, &str)) {
		WRONG_PARAM_COUNT;
	}
	convert_to_string_ex(str);
	ret = php_basename(Z_STRVAL_PP(str), Z_STRLEN_PP(str));
	RETVAL_STRING(ret, 0)
}
/* }}} */

/* This function doesn't work with absolute paths in Win32 such as C:\foo
 *  (and it didn't before either). This needs to be fixed
 */
PHPAPI void php_dirname(char *path, int len)
{
	register char *end = path + len - 1;

	if (len <= 0) {
		/* Illegal use of this function */
		return;
	}

	/* Strip trailing slashes */
	while (end >= path && IS_SLASH(*end)) {
		end--;
	}
	if (end < path) {
		/* The path only contained slashes */
		path[0] = DEFAULT_SLASH;
		path[1] = '\0';
		return;
	}

	/* Strip filename */
	while (end >= path && !IS_SLASH(*end)) {
		end--;
	}
	if (end < path) {
		/* No slash found, therefore return '.' */
		path[0] = '.';
		path[1] = '\0';
		return;
	}

	/* Strip slashes which came before the file name */
	while (end >= path && IS_SLASH(*end)) {
		end--;
	}
	if (end < path) {
		path[0] = DEFAULT_SLASH;
		path[1] = '\0';
		return;
	}
	*(end+1) = '\0';
}

/* {{{ proto string dirname(string path)
   Return the directory name component of the path */
PHP_FUNCTION(dirname)
{
	zval **str;
	char *ret;
	
	if (ZEND_NUM_ARGS() != 1 || zend_get_parameters_ex(1, &str) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
	convert_to_string_ex(str);
	ret = estrndup(Z_STRVAL_PP(str), Z_STRLEN_PP(str));
	php_dirname(ret, Z_STRLEN_PP(str));
	RETVAL_STRING(ret, 0);
}
/* }}} */

/* {{{ proto array pathinfo(string path)
   Return information about a certain string */
PHP_FUNCTION(pathinfo)
{
	zval **path, **uopt, *tmp;
	char *ret;
	int argc = ZEND_NUM_ARGS(), opt, len;
	
	if (argc < 1 || argc > 2 ||
	    zend_get_parameters_ex(argc, &path, &uopt) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
	convert_to_string_ex(path);
	len = Z_STRLEN_PP(path);
	
	if (argc > 1) {
		convert_to_long_ex(uopt);
		opt = Z_LVAL_PP(uopt);
		if (opt < PHP_PATHINFO_DIRNAME || opt > PHP_PATHINFO_EXTENSION) {
			php_error(E_WARNING, "Invalid option in call to %s()",
					  get_active_function_name());
			return;
		}
	}
	
	MAKE_STD_ZVAL(tmp);
	array_init(tmp);
	
	if (argc < 2 || opt == PHP_PATHINFO_DIRNAME) {
		ret = estrndup(Z_STRVAL_PP(path), len);
		php_dirname(ret, len);
		if (*ret)
			add_assoc_string(tmp, "dirname", ret, 1);
		efree(ret);
	}
	
	if (argc < 2 || opt == PHP_PATHINFO_BASENAME) {
		ret = php_basename(Z_STRVAL_PP(path), len);
		add_assoc_string(tmp, "basename", ret, 0);
	}			
	
	if (argc < 2 || opt == PHP_PATHINFO_EXTENSION) {
		char *p;
		int idx;

		p = strrchr(Z_STRVAL_PP(path), '.');
		if (*p) {
			idx = p - Z_STRVAL_PP(path);
			add_assoc_stringl(tmp, "extension", Z_STRVAL_PP(path) + idx + 1, len - idx - 1, 1);
		}
	}
	
	if (argc == 2) {
		zval **element;
		zend_hash_get_current_data(Z_ARRVAL_P(tmp), (void **)&element);
		*return_value = **element;
	} else {
		*return_value = *tmp;
	}

	zval_copy_ctor(return_value);
	zval_dtor(tmp);
	efree(tmp);
}
/* }}} */

/* case insensitve strstr */
PHPAPI char *php_stristr(unsigned char *s, unsigned char *t,
                         size_t s_len, size_t t_len)
{
	php_strtolower(s, s_len);
	php_strtolower(t, t_len);
	return php_memnstr(s, t, t_len, s + s_len);
}

PHPAPI size_t php_strspn(char *s1, char *s2, char *s1_end, char *s2_end)
{
	register const char *p = s1, *spanp;
	register char c = *p;

cont:
	for (spanp = s2; p != s1_end && spanp != s2_end;)
		if (*spanp++ == c) {
			c = *(++p);
			goto cont;
		}
	return (p - s1);
}

PHPAPI size_t php_strcspn(char *s1, char *s2, char *s1_end, char *s2_end)
{
	register const char *p, *spanp;
	register char c = *s1;

	for (p = s1;;) {
		spanp = s2;
		do {
			if (*spanp == c || p == s1_end)
				return (p - s1);
		} while (spanp++ < s2_end);
		c = *(++p);
	}
	/* NOTREACHED */
}

/* {{{ proto string stristr(string haystack, string needle)
   Find first occurrence of a string within another, case insensitive */
PHP_FUNCTION(stristr)
{
	zval **haystack, **needle;
	char *found = NULL;
	int  found_offset;
	char *haystack_orig;
	char needle_char[2];
	
	if (ZEND_NUM_ARGS() != 2 || zend_get_parameters_ex(2, &haystack, &needle) ==
		FAILURE) {
		WRONG_PARAM_COUNT;
	}

	SEPARATE_ZVAL(haystack);
	SEPARATE_ZVAL(needle);
	convert_to_string_ex(haystack);
	haystack_orig = estrndup((*haystack)->value.str.val,
							 (*haystack)->value.str.len);

	if ((*needle)->type == IS_STRING) {
		if ((*needle)->value.str.len==0) {
			php_error(E_WARNING,"Empty delimiter");
			efree(haystack_orig);
			RETURN_FALSE;
		}

		found = php_stristr((*haystack)->value.str.val, (*needle)->value.str.val,
							(*haystack)->value.str.len, (*needle)->value.str.len);
	} else {
		convert_to_long_ex(needle);
		needle_char[0] = tolower((char) (*needle)->value.lval);
		needle_char[1] = '\0';

		found = php_stristr((*haystack)->value.str.val, needle_char,
				            (*haystack)->value.str.len, 1);
	}

	if (found) {
		found_offset = found - (*haystack)->value.str.val;
		RETVAL_STRINGL(haystack_orig + found_offset,
					   (*haystack)->value.str.len - found_offset, 1);
	} else {
		RETVAL_FALSE;
	}
	efree(haystack_orig);
}
/* }}} */

/* {{{ proto string strstr(string haystack, string needle)
   Find first occurrence of a string within another */
PHP_FUNCTION(strstr)
{
	zval **haystack, **needle;
	char *haystack_end;
	char *found = NULL;
	char needle_char[2];
	
	if (ZEND_NUM_ARGS() != 2 || zend_get_parameters_ex(2, &haystack, &needle) ==
		FAILURE) {
		WRONG_PARAM_COUNT;
	}
	convert_to_string_ex(haystack);
	haystack_end = (*haystack)->value.str.val + (*haystack)->value.str.len;

	if ((*needle)->type == IS_STRING) {
		if ((*needle)->value.str.len==0) {
			php_error(E_WARNING,"Empty delimiter");
			RETURN_FALSE;
		}
		found = php_memnstr((*haystack)->value.str.val, (*needle)->value.str.val,
                            (*needle)->value.str.len, haystack_end);
	} else {
		convert_to_long_ex(needle);
		needle_char[0] = (char) (*needle)->value.lval;
		needle_char[1] = '\0';
		found = php_memnstr((*haystack)->value.str.val, needle_char, 1, haystack_end);
	}


	if (found) {
		RETVAL_STRING(found, 1);
	} else {
		RETVAL_FALSE;
	}
}
/* }}} */

/* {{{ proto string strchr(string haystack, string needle)
   An alias for strstr */
/* }}} */

/* {{{ proto int strpos(string haystack, string needle [, int offset])
   Find position of first occurrence of a string within another */
PHP_FUNCTION(strpos)
{
	zval **haystack, **needle, **OFFSET;
	int offset = 0;
	char *found = NULL;
	char *endp;
	char *startp;
	
	switch(ZEND_NUM_ARGS()) {
	case 2:
		if (zend_get_parameters_ex(2, &haystack, &needle) == FAILURE) {
			WRONG_PARAM_COUNT;
		}
		break;
	case 3:
		if (zend_get_parameters_ex(3, &haystack, &needle, &OFFSET) == FAILURE) {
			WRONG_PARAM_COUNT;
		}
		convert_to_long_ex(OFFSET);
		offset = (*OFFSET)->value.lval;
		if (offset < 0) {
			php_error(E_WARNING,"offset not contained in string");
			RETURN_FALSE;
		}	
		break;
	default:
		WRONG_PARAM_COUNT;
	}

	convert_to_string_ex(haystack);

	if (offset > (*haystack)->value.str.len) {
		php_error(E_WARNING,"offset not contained in string");
		RETURN_FALSE;
	}

	startp = (*haystack)->value.str.val;
	startp+= offset;

	endp = (*haystack)->value.str.val;
	endp+= (*haystack)->value.str.len;

	if ((*needle)->type == IS_STRING) {
		if ((*needle)->value.str.len==0) {
			php_error(E_WARNING,"Empty delimiter");
			RETURN_FALSE;
		}
		found = php_memnstr(startp, (*needle)->value.str.val, (*needle)->value.str.len, endp);
	} else {
		char buf;

		convert_to_long_ex(needle);
		buf = (char) (*needle)->value.lval;

		found = php_memnstr(startp, &buf, 1, endp);
	}

	if (found) {
		RETVAL_LONG(found - (*haystack)->value.str.val);
	} else {
		RETVAL_FALSE;
	}
}
/* }}} */

/* {{{ proto int strrpos(string haystack, string needle)
   Find position of last occurrence of a character in a string within another */
PHP_FUNCTION(strrpos)
{
	zval **haystack, **needle;
	char *found = NULL;
	
	if (ZEND_NUM_ARGS() != 2 || zend_get_parameters_ex(2, &haystack, &needle) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
	convert_to_string_ex(haystack);

	if ((*needle)->type == IS_STRING) {
		found = strrchr((*haystack)->value.str.val, *(*needle)->value.str.val);
	} else {
		convert_to_long_ex(needle);
		found = strrchr((*haystack)->value.str.val, (char) (*needle)->value.lval);
	}

	if (found) {
		RETVAL_LONG((*haystack)->value.str.len - strlen(found));
	} else {
		RETVAL_FALSE;
	}
}
/* }}} */

/* {{{ proto string strrchr(string haystack, string needle)
   Find the last occurrence of a character in a string within another */
PHP_FUNCTION(strrchr)
{
	zval **haystack, **needle;
	char *found = NULL;
	
	if (ZEND_NUM_ARGS() != 2 || zend_get_parameters_ex(2, &haystack, &needle) ==
		FAILURE) {
		WRONG_PARAM_COUNT;
	}
	convert_to_string_ex(haystack);

	if ((*needle)->type == IS_STRING) {
		found = strrchr((*haystack)->value.str.val, *(*needle)->value.str.val);
	} else {

		convert_to_long_ex(needle);
		found = strrchr((*haystack)->value.str.val, (*needle)->value.lval);
	}


	if (found) {
		RETVAL_STRING(found,1);
	} else {
		RETVAL_FALSE;
	}
}
/* }}} */

static char *php_chunk_split(char *src, int srclen, char *end, int endlen,
							 int chunklen, int *destlen)
{
	char *dest;
	char *p, *q;
	int chunks; /* complete chunks! */
	int restlen;

	chunks = srclen / chunklen;
	restlen = srclen - chunks * chunklen; /* srclen % chunklen */

	dest = emalloc((srclen + (chunks + 1) * endlen + 1) * sizeof(char));

	for(p = src, q = dest; p < (src + srclen - chunklen + 1); ) {
		memcpy(q, p, chunklen);
		q += chunklen;
		memcpy(q, end, endlen);
		q += endlen;
		p += chunklen;
	}

	if(restlen) {
		memcpy(q, p, restlen);
		q += restlen;
		memcpy(q, end, endlen);
		q += endlen;
	}

	*q = '\0';
	if (destlen) {
		*destlen = q - dest;
	}

	return(dest);
}

/* {{{ proto string chunk_split(string str [, int chunklen [, string ending]])
   Return split line */
PHP_FUNCTION(chunk_split) 
{
	zval **p_str, **p_chunklen, **p_ending;
	int argc;
	char *result;
	char *end    = "\r\n";
	int endlen   = 2;
	int chunklen = 76;
	int result_len;

	argc = ZEND_NUM_ARGS();

	if (argc < 1 || argc > 3 ||
		zend_get_parameters_ex(argc, &p_str, &p_chunklen, &p_ending) == FAILURE) {
		WRONG_PARAM_COUNT;
	}

	switch(argc) {
		case 3:
			convert_to_string_ex(p_ending);
			end    = (*p_ending)->value.str.val;
			endlen = (*p_ending)->value.str.len;
		case 2:
			convert_to_long_ex(p_chunklen);
			chunklen = (*p_chunklen)->value.lval;
		case 1:
			convert_to_string_ex(p_str);
	}
			
	if(chunklen <= 0) {
		php_error(E_WARNING, "Chunk length should be greater than zero");
		RETURN_FALSE;
	}

	if((*p_str)->value.str.len == 0) {
		RETURN_EMPTY_STRING();
	}

	result = php_chunk_split((*p_str)->value.str.val, (*p_str)->value.str.len,
						     end, endlen, chunklen, &result_len);
	
	if(result) {
		RETVAL_STRINGL(result, result_len, 0);
	} else {
		RETURN_FALSE;
	}
}
/* }}} */

/* {{{ proto string substr(string str, int start [, int length])
   Return part of a string */
PHP_FUNCTION(substr)
{
	zval **str, **from, **len;
	int argc, l;
	int f;
	
	argc = ZEND_NUM_ARGS();

	if ((argc == 2 && zend_get_parameters_ex(2, &str, &from) == FAILURE) ||
		(argc == 3 && zend_get_parameters_ex(3, &str, &from, &len) == FAILURE) ||
		argc < 2 || argc > 3) {
		WRONG_PARAM_COUNT;
	}
	convert_to_string_ex(str);
	convert_to_long_ex(from);
	f = (*from)->value.lval;

	if (argc == 2) {
		l = (*str)->value.str.len;
	} else {
		convert_to_long_ex(len);
		l = (*len)->value.lval;
	}

	/* if "from" position is negative, count start position from the end
	 * of the string
	 */
	if (f < 0) {
		f = (*str)->value.str.len + f;
		if (f < 0) {
			f = 0;
		}
	}

	/* if "length" position is negative, set it to the length
	 * needed to stop that many chars from the end of the string
	 */
	if (l < 0) {
		l = ((*str)->value.str.len - f) + l;
		if (l < 0) {
			l = 0;
		}
	}

	if (f >= (int)(*str)->value.str.len) {
		RETURN_FALSE;
	}

	if((f+l) > (int)(*str)->value.str.len) {
		l = (int)(*str)->value.str.len - f;
	}

	RETVAL_STRINGL((*str)->value.str.val + f, l, 1);
}
/* }}} */


/* {{{ proto string substr_replace(string str, string repl, int start [, int length])
   Replace part of a string with another string */
PHP_FUNCTION(substr_replace)
{
    zval**      str;
    zval**      from;
    zval**      len;
    zval**      repl;
    char*       result;
	int         result_len;
    int         argc;
    int         l;
    int         f;
	
	argc = ZEND_NUM_ARGS();

	if ((argc == 3 && zend_get_parameters_ex(3, &str, &repl, &from) == FAILURE) ||
		(argc == 4 && zend_get_parameters_ex(4, &str, &repl, &from, &len) == FAILURE) ||
		argc < 3 || argc > 4) {
		WRONG_PARAM_COUNT;
	}
	
	convert_to_string_ex(str);
	convert_to_string_ex(repl);
	convert_to_long_ex(from);
	f = (*from)->value.lval;

	if (argc == 3) {
		l = (*str)->value.str.len;
	} else {
		convert_to_long_ex(len);
		l = (*len)->value.lval;
	}

	/* if "from" position is negative, count start position from the end
	 * of the string
	 */
	if (f < 0) {
		f = (*str)->value.str.len + f;
		if (f < 0) {
			f = 0;
		}
	} else if (f > (int)(*str)->value.str.len)
		f = (int)(*str)->value.str.len;


	/* if "length" position is negative, set it to the length
	 * needed to stop that many chars from the end of the string
	 */
	if (l < 0) {
		l = ((*str)->value.str.len - f) + l;
		if (l < 0) {
			l = 0;
		}
	}

	if((f+l) > (int)(*str)->value.str.len) {
		l = (int)(*str)->value.str.len - f;
	}

	result_len = (*str)->value.str.len - l + (*repl)->value.str.len;
	result = (char *)ecalloc(result_len + 1, sizeof(char *));

	memcpy(result, (*str)->value.str.val, f);
	memcpy(&result[f], (*repl)->value.str.val, (*repl)->value.str.len);
	memcpy(&result[f + (*repl)->value.str.len], (*str)->value.str.val + f + l,
          (*str)->value.str.len - f - l);

	RETVAL_STRINGL(result, result_len, 0);
}
/* }}} */


/* {{{ proto string quotemeta(string str)
   Quote meta characters */
PHP_FUNCTION(quotemeta)
{
	zval **arg;
	char *str, *old;
	char *old_end;
	char *p, *q;
	char c;
	
	if (ZEND_NUM_ARGS() != 1 || zend_get_parameters_ex(1, &arg) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
	convert_to_string_ex(arg);

	old = (*arg)->value.str.val;
	old_end = (*arg)->value.str.val + (*arg)->value.str.len;

	if (old == old_end) {
		RETURN_FALSE;
	}
	
	str = emalloc(2 * (*arg)->value.str.len + 1);
	
	for(p = old, q = str; p != old_end; p++) {
		c = *p;
		switch(c) {
			case '.':
			case '\\':
			case '+':
			case '*':
			case '?':
			case '[':
			case '^':
			case ']':
			case '$':
			case '(':
			case ')':
				*q++ = '\\';
				/* break is missing _intentionally_ */
			default:
				*q++ = c;
		}
	}
	*q = 0;
	RETVAL_STRINGL(erealloc(str, q - str + 1), q - str, 0);
}
/* }}} */

/* {{{ proto int ord(string character)
   Return ASCII value of character */
PHP_FUNCTION(ord)
{
	zval **str;
	
	if (ZEND_NUM_ARGS() != 1 || zend_get_parameters_ex(1, &str) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
	convert_to_string_ex(str);
	RETVAL_LONG((unsigned char)(*str)->value.str.val[0]);
}
/* }}} */

/* {{{ proto string chr(int ascii)
   Convert ASCII code to a character */
PHP_FUNCTION(chr)
{
	zval **num;
	char temp[2];
	
	if (ZEND_NUM_ARGS() != 1 || zend_get_parameters_ex(1, &num) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
	convert_to_long_ex(num);
	temp[0] = (char) (*num)->value.lval;
	temp[1] = '\0';
	RETVAL_STRINGL(temp, 1,1);
}
/* }}} */

/* {{{ proto string ucfirst(string str)
   Make a string's first character uppercase */
PHP_FUNCTION(ucfirst)
{
	zval **arg;
	
	if (ZEND_NUM_ARGS() != 1 || zend_get_parameters_ex(1, &arg) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
	convert_to_string_ex(arg);

	if (!*(*arg)->value.str.val) {
		RETURN_FALSE;
	}

	*return_value=**arg;
	zval_copy_ctor(return_value);
	*return_value->value.str.val = toupper((unsigned char)*return_value->value.str.val);
}
/* }}} */

/* {{{ proto string ucwords(string str)
   Uppercase the first character of every word in a string */
PHP_FUNCTION(ucwords)
{
	zval **str;
	register char *r, *r_end;
	
	if (ZEND_NUM_ARGS() != 1 || zend_get_parameters_ex(1, &str) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
	convert_to_string_ex(str);

	if (!Z_STRLEN_PP(str)) {
		RETURN_FALSE;
	}
	*return_value=**str;
	zval_copy_ctor(return_value);

	r=return_value->value.str.val;
	*r=toupper((unsigned char)*r);
	for(r_end = r + return_value->value.str.len - 1 ; r < r_end ; ) {
		if(isspace((int)*r++)) {
			*r=toupper((unsigned char)*r);
		}
	}
}
/* }}} */

PHPAPI char *php_strtr(char *str, int len, char *str_from,
					   char *str_to, int trlen)
{
	int i;
	unsigned char xlat[256];

	if ((trlen < 1) || (len < 1)) {
		return str;
	}

	for (i = 0; i < 256; xlat[i] = i, i++);

	for (i = 0; i < trlen; i++) {
		xlat[(unsigned char) str_from[i]] = str_to[i];
	}

	for (i = 0; i < len; i++) {
		str[i] = xlat[(unsigned char) str[i]];
	}

	return str;
}

static void php_strtr_array(zval *return_value,char *str,int slen,HashTable *hash)
{
	zval *entry;
	char *string_key;
	zval **trans;
	zval ctmp;
	ulong num_key;
	int minlen = 128*1024;
	int maxlen = 0, pos, len, found;
	char *key;
	HashPosition hpos;
	smart_str result = {0};
	
	zend_hash_internal_pointer_reset_ex(hash, &hpos);
	while (zend_hash_get_current_data_ex(hash, (void **)&entry, &hpos) == SUCCESS) {
		switch (zend_hash_get_current_key_ex(hash, &string_key, NULL, &num_key, &hpos)) {
		case HASH_KEY_IS_STRING:
			len = strlen(string_key);
			if (len > maxlen) maxlen = len;
			if (len < minlen) minlen = len;
			efree(string_key);
			break; 
			
		case HASH_KEY_IS_LONG:
			ctmp.type = IS_LONG;
			ctmp.value.lval = num_key;
			
			convert_to_string(&ctmp);
			len = ctmp.value.str.len;
			zval_dtor(&ctmp);

			if (len > maxlen) maxlen = len;
			if (len < minlen) minlen = len;
			break;
		}
		zend_hash_move_forward_ex(hash, &hpos);
	}

	key = emalloc(maxlen+1);
	pos = 0;

	while (pos < slen) {
		if ((pos + maxlen) > slen) {
			maxlen = slen - pos;
		}
			
		found = 0;
		memcpy(key,str+pos,maxlen);

		for (len = maxlen; len >= minlen; len--) {
			key[ len ]=0;
			
			if (zend_hash_find(hash,key,len+1,(void**)&trans) == SUCCESS) {
				char *tval;
				int tlen;
				zval tmp;

				if ((*trans)->type != IS_STRING) {
					tmp = **trans;
					zval_copy_ctor(&tmp);
					convert_to_string(&tmp);
					tval = tmp.value.str.val;
					tlen = tmp.value.str.len;
				} else {
					tval = (*trans)->value.str.val;
					tlen = (*trans)->value.str.len;
				}

				smart_str_appendl(&result, tval, tlen);
				pos += len;
				found = 1;

				if ((*trans)->type != IS_STRING) {
					zval_dtor(&tmp);
				}
				break;
			} 
		}

		if (! found) {
			smart_str_appendc(&result, str[pos++]);
		}
	}

	efree(key);
	smart_str_0(&result);
	RETVAL_STRINGL(result.c,result.len,0);
}

/* {{{ proto string strtr(string str, string from, string to)
   Translate characters in str using given translation tables */
PHP_FUNCTION(strtr)
{								/* strtr(STRING,FROM,TO) */
	zval **str, **from, **to;
	int ac = ZEND_NUM_ARGS();

	if (ac < 2 || ac > 3 || zend_get_parameters_ex(ac, &str, &from, &to) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
	
	if (ac == 2 && (*from)->type != IS_ARRAY) {
		php_error(E_WARNING,"arg2 must be passed an array");
		RETURN_FALSE;
	}

	convert_to_string_ex(str);

	/* shortcut for empty string */
	if(Z_STRLEN_PP(str) == 0) {
		RETURN_EMPTY_STRING();
	}

	if (ac == 2) {
		php_strtr_array(return_value,(*str)->value.str.val,(*str)->value.str.len,HASH_OF(*from));
	} else {
		convert_to_string_ex(from);
		convert_to_string_ex(to);

		*return_value=**str;
		zval_copy_ctor(return_value);
		
		php_strtr(return_value->value.str.val,
				  return_value->value.str.len,
				  (*from)->value.str.val,
				  (*to)->value.str.val,
				  MIN((*from)->value.str.len,(*to)->value.str.len));
	}
}
/* }}} */


/* {{{ proto string strrev(string str)
   Reverse a string */
PHP_FUNCTION(strrev)
{
	zval **str;
	int i,len;
	char c;
	
	if (ZEND_NUM_ARGS()!=1 || zend_get_parameters_ex(1, &str)==FAILURE) {
		WRONG_PARAM_COUNT;
	}
	
	convert_to_string_ex(str);
	
	*return_value = **str;
	zval_copy_ctor(return_value);

	len = return_value->value.str.len;
	
	for (i=0; i<len-1-i; i++) {
		c=return_value->value.str.val[i];
		return_value->value.str.val[i] = return_value->value.str.val[len-1-i];
		return_value->value.str.val[len-1-i]=c;
	}
}
/* }}} */

static void php_similar_str(const char *txt1, int len1, const char *txt2, 
							int len2, int *pos1, int *pos2, int *max)
{
	char *p, *q;
	char *end1 = (char *) txt1 + len1;
	char *end2 = (char *) txt2 + len2;
	int l;
	
	*max = 0;
	for (p = (char *) txt1; p < end1; p++) {
		for (q = (char *) txt2; q < end2; q++) {
			for (l = 0; (p + l < end1) && (q + l < end2) && (p[l] == q[l]); 
				 l++);
			if (l > *max) {
				*max = l;
				*pos1 = p - txt1;
				*pos2 = q - txt2;
			}
		}
	}
}

static int php_similar_char(const char *txt1, int len1, 
							const char *txt2, int len2)
{
	int sum;
	int pos1, pos2, max;

	php_similar_str(txt1, len1, txt2, len2, &pos1, &pos2, &max);
	if ((sum = max)) {
		if (pos1 && pos2)
			sum += php_similar_char(txt1, pos1, txt2, pos2);
		if ((pos1 + max < len1) && (pos2 + max < len2))
			sum += php_similar_char(txt1 + pos1 + max, len1 - pos1 - max, 
									txt2 + pos2 + max, len2 - pos2 - max);
	}
	return sum;
}

/* {{{ proto int similar_text(string str1, string str2 [, double percent])
   Calculates the similarity between two strings */
PHP_FUNCTION(similar_text)
{
	zval **t1, **t2, **percent;
	int ac = ZEND_NUM_ARGS();
	int sim;
	
	if (ac < 2 || ac > 3 ||
		zend_get_parameters_ex(ac, &t1, &t2, &percent) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
	
	convert_to_string_ex(t1);
	convert_to_string_ex(t2);
	if (ac > 2) {
		convert_to_double_ex(percent);
	}
	
	if (((*t1)->value.str.len + (*t2)->value.str.len) == 0) {
		if(ac > 2) {
			(*percent)->value.dval = 0;
		}
		RETURN_LONG(0);
	}
	
	sim = php_similar_char((*t1)->value.str.val, (*t1)->value.str.len, 
							 (*t2)->value.str.val, (*t2)->value.str.len);
	
	if (ac > 2) {
		(*percent)->value.dval = sim * 200.0 / ((*t1)->value.str.len + (*t2)->value.str.len);
	}
	
	RETURN_LONG(sim);
}
/* }}} */
	

/* be careful, this edits the string in-place */
PHPAPI void php_stripslashes(char *str, int *len)
{
	char *s, *t;
	int l;
	char escape_char='\\';
	PLS_FETCH();

	if (PG(magic_quotes_sybase)) {
		escape_char='\'';
	}

	if (len != NULL) {
		l = *len;
	} else {
		l = strlen(str);
	}
	s = str;
	t = str;
	while (l > 0) {
		if (*t == escape_char) {
			t++;				/* skip the slash */
			if (len != NULL)
				(*len)--;
			l--;
			if (l > 0) {
				if(*t=='0') {
					*s++='\0';
					t++;
				} else {
					*s++ = *t++;	/* preserve the next character */
				}
				l--;
			}
		} else {
			if (s != t)
				*s++ = *t++;
			else {
				s++;
				t++;
			}
			l--;
		}
	}
	if (s != t) {
		*s = '\0';
	}
}

/* {{{ proto string addcslashes(string str, string charlist)
   Escape 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...) */
PHP_FUNCTION(addcslashes)
{
	zval **str, **what;

	if (ZEND_NUM_ARGS() != 2 || zend_get_parameters_ex(2, &str, &what) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
	convert_to_string_ex(str);
	convert_to_string_ex(what);

	if(Z_STRLEN_PP(str) == 0) {
		RETURN_EMPTY_STRING();
	}

	if(Z_STRLEN_PP(what) == 0) {
		RETURN_STRINGL(Z_STRVAL_PP(str),Z_STRLEN_PP(str),1);
	}

	return_value->value.str.val = php_addcslashes((*str)->value.str.val,(*str)->value.str.len,&return_value->value.str.len,0,(*what)->value.str.val,(*what)->value.str.len);
	return_value->type = IS_STRING;
}
/* }}} */

/* {{{ proto string addslashes(string str)
   Escape single quote, double quotes and backslash characters in a string with backslashes */
PHP_FUNCTION(addslashes)
{
	zval **str;

	if (ZEND_NUM_ARGS() != 1 || zend_get_parameters_ex(1, &str) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
	convert_to_string_ex(str);

	if(Z_STRLEN_PP(str) == 0) {
		RETURN_EMPTY_STRING();
	}

	return_value->value.str.val = php_addslashes((*str)->value.str.val,(*str)->value.str.len,&return_value->value.str.len,0);
	return_value->type = IS_STRING;
}
/* }}} */

/* {{{ proto string stripcslashes(string str)
   Strip backslashes from a string. Uses C-style conventions */
PHP_FUNCTION(stripcslashes)
{
	zval **str;
	
	if (ZEND_NUM_ARGS() != 1 || zend_get_parameters_ex(1, &str) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
	convert_to_string_ex(str);

	*return_value = **str;
	zval_copy_ctor(return_value);
	php_stripcslashes(return_value->value.str.val,&return_value->value.str.len);
}
/* }}} */

/* {{{ proto string stripslashes(string str)
   Strip backslashes from a string */
PHP_FUNCTION(stripslashes)
{
	zval **str;
	
	if (ZEND_NUM_ARGS() != 1 || zend_get_parameters_ex(1, &str) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
	convert_to_string_ex(str);

	*return_value = **str;
	zval_copy_ctor(return_value);
	php_stripslashes(return_value->value.str.val,&return_value->value.str.len);
}
/* }}} */

#ifndef HAVE_STRERROR
char *php_strerror(int errnum) 
{
	extern int sys_nerr;
	extern char *sys_errlist[];
	BLS_FETCH();

	if ((unsigned int)errnum < sys_nerr) return(sys_errlist[errnum]);
	(void)sprintf(BG(str_ebuf), "Unknown error: %d", errnum);
	return(BG(str_ebuf));
}
#endif

PHPAPI void php_stripcslashes(char *str, int *len)
{
	char *source,*target,*end;
	int  nlen = *len, i;
	char numtmp[4];

	for (source=str,end=str+nlen,target=str; source<end; source++) {
		if (*source == '\\' && source+1<end) {
			source++;
			switch (*source) {
				case 'n': *target++='\n'; nlen--; break;
				case 'r': *target++='\r'; nlen--; break;
				case 'a': *target++='\a'; nlen--; break;
				case 't': *target++='\t'; nlen--; break;
				case 'v': *target++='\v'; nlen--; break;
				case 'b': *target++='\b'; nlen--; break;
				case 'f': *target++='\f'; nlen--; break;
				case '\\': *target++='\\'; nlen--; break;
				case 'x': if (source+1<end && isxdigit((int)(*(source+1)))) {
						numtmp[0] = *++source;
						if (source+1<end && isxdigit((int)(*(source+1)))) {
							numtmp[1] = *++source;
							numtmp[2] = '\0';
							nlen-=3;
						} else {
							numtmp[1] = '\0';
							nlen-=2;
						}
						*target++=(char)strtol(numtmp, NULL, 16);
						break;
					}
					/* break is left intentionally */
				default: i=0; 
					while (source<end && *source>='0' && *source<='7' && i<3) {
						numtmp[i++] = *source++;
					}
					if (i) {
						numtmp[i]='\0';
						*target++=(char)strtol(numtmp, NULL, 8);
						nlen-=i;
						source--;
					} else {
						*target++=*source;
						nlen--;
					}
			}
		} else {
			*target++=*source;
		}
	}

	if(nlen != 0) {
		*target='\0';
	}

	*len = nlen;
}
			

PHPAPI char *php_addcslashes(char *str, int length, int *new_length, int should_free, char *what, int wlength)
{
	char flags[256];
	char *new_str = emalloc((length?length:(length=strlen(str)))*4+1); 
	char *source,*target;
	char *end;
	char c;
	int  newlen;

	if (!wlength) {
		wlength = strlen(what);
	}

	if (!length) {
		length = strlen(str);
	}

	memset(flags, '\0', sizeof(flags));
	for (source=what,end=source+wlength; (c=*source) || source<end; source++) {
		if (source+3<end && *(source+1) == '.' && *(source+2) == '.' && (unsigned char)*(source+3)>=(unsigned char)c) {
			memset(flags+c, 1, (unsigned char)*(source+3)-(unsigned char)c+1);
			source+=3;
		} else
			flags[(unsigned char)c]=1;
	}

	for (source=str,end=source+length,target=new_str; (c=*source) || source<end; source++) {
		if (flags[(unsigned char)c]) {
			if ((unsigned char)c<32 || (unsigned char)c>126) {
				*target++ = '\\';
				switch (c) {
					case '\n': *target++ = 'n'; break;
					case '\t': *target++ = 't'; break;
					case '\r': *target++ = 'r'; break;
					case '\a': *target++ = 'a'; break;
					case '\v': *target++ = 'v'; break;
					case '\b': *target++ = 'b'; break;
					case '\f': *target++ = 'f'; break;
					default: target += sprintf(target, "%03o", (unsigned char)c);
				}
				continue;
			} 
			*target++ = '\\';
		}
		*target++ = c;
	}
	*target = 0;
	newlen = target-new_str;
	if (target-new_str<length*4) {
		new_str = erealloc(new_str, newlen+1);
	}
	if (new_length) {
		*new_length = newlen;
	}
	if (should_free) {
		STR_FREE(str);
	}
	return new_str;
}

PHPAPI char *php_addslashes(char *str, int length, int *new_length, int should_free)
{
	/* maximum string length, worst case situation */
	char *new_str;
	char *source,*target;
	char *end;
	char c;
	PLS_FETCH();
 	
	if (!str) {
		*new_length = 0;
		return str;
	}
	new_str = (char *) emalloc((length?length:(length=strlen(str)))*2+1);
	for (source=str,end=source+length,target=new_str; source<end; source++) {
		c = *source;
		switch(c) {
			case '\0':
				*target++ = '\\';
				*target++ = '0';
				break;
			case '\'':
				if (PG(magic_quotes_sybase)) {
					*target++ = '\'';
					*target++ = '\'';
					break;
				}
				/* break is missing *intentionally* */
			case '\"':
			case '\\':
				if (!PG(magic_quotes_sybase)) {
					*target++ = '\\';
				}
				/* break is missing *intentionally* */
			default:
				*target++ = c;
				break;
		}
	}
	*target = 0;
	if (new_length) {
		*new_length = target - new_str;
	}
	if (should_free) {
		STR_FREE(str);
	}
	return new_str;
}


#define _HEB_BLOCK_TYPE_ENG 1
#define _HEB_BLOCK_TYPE_HEB 2
#define isheb(c) (((((unsigned char) c)>=224) && (((unsigned char) c)<=250)) ? 1 : 0)
#define _isblank(c) (((((unsigned char) c)==' ' || ((unsigned char) c)=='\t')) ? 1 : 0)
#define _isnewline(c) (((((unsigned char) c)=='\n' || ((unsigned char) c)=='\r')) ? 1 : 0)

PHPAPI void php_char_to_str(char *str,uint len,char from,char *to,int to_len,zval *result)
{
	int char_count=0;
	char *source,*target,*tmp,*source_end=str+len, *tmp_end=NULL;
	
	for (source=str; source<source_end; source++) {
		if (*source==from) {
			char_count++;
		}
	}

	result->type = IS_STRING;
		
	if (char_count==0) {
		result->value.str.val = estrndup(str,len);
		result->value.str.len = len;
		return;
	}
	
	result->value.str.len = len+char_count*(to_len-1);
	result->value.str.val = target = (char *) emalloc(result->value.str.len+1);
	
	for (source=str; source<source_end; source++) {
		if (*source==from) {
			for (tmp=to,tmp_end=tmp+to_len; tmp<tmp_end; tmp++) {
				*target = *tmp;
				target++;
			}
		} else {
			*target = *source;
			target++;
		}
	}
	*target = 0;
}

PHPAPI char *php_str_to_str(char *haystack, int length, 
	char *needle, int needle_len, char *str, int str_len, int *_new_length)
{
	char *p;
	char *r;
	char *end = haystack + length;
	smart_str result = {0};

	for (p = haystack;
			(r = php_memnstr(p, needle, needle_len, end));
			p = r + needle_len) {
		smart_str_appendl(&result, p, r - p);
		smart_str_appendl(&result, str, str_len);
	}

	if (p < end) 
		smart_str_appendl(&result, p, end - p);

	smart_str_0(&result);

	if (_new_length) *_new_length = result.len;

	return result.c;
}


/* {{{ proto string str_replace(string needle, string str, string haystack)
   Replace all occurrences of needle in haystack with str */
PHP_FUNCTION(str_replace)
{
	zval **haystack, **needle, **str;
	char *result;
	int len = 0;

	if(ZEND_NUM_ARGS() != 3 ||
			zend_get_parameters_ex(3, &needle, &str, &haystack) == FAILURE) {
		WRONG_PARAM_COUNT;
	}

	convert_to_string_ex(haystack);
	convert_to_string_ex(needle);
	convert_to_string_ex(str);

	if((*haystack)->value.str.len == 0) {
		RETURN_STRING(empty_string,1);
	}

	if((*needle)->value.str.len == 1) {
		php_char_to_str((*haystack)->value.str.val,
						(*haystack)->value.str.len,
						(*needle)->value.str.val[0],
						(*str)->value.str.val,
						(*str)->value.str.len,
						return_value);
		return;
	}

	if((*needle)->value.str.len == 0) {
		php_error(E_WARNING, "The length of the needle must not be 0");
		RETURN_FALSE;
	}

	result = php_str_to_str((*haystack)->value.str.val, (*haystack)->value.str.len,
						 (*needle)->value.str.val, (*needle)->value.str.len,
						 (*str)->value.str.val, (*str)->value.str.len, &len);
	RETURN_STRINGL(result, len, 0);
}
/* }}} */

/* Converts Logical Hebrew text (Hebrew Windows style) to Visual text
 * Cheers/complaints/flames - Zeev Suraski <zeev@php.net>
 */
static void php_hebrev(INTERNAL_FUNCTION_PARAMETERS,int convert_newlines)
{
	zval **str,**max_chars_per_line;
	char *heb_str,*tmp,*target,*opposite_target,*broken_str;
	int block_start, block_end, block_type, block_length, i;
	int block_ended;
	long max_chars=0;
	int begin,end,char_count,orig_begin;

	
	switch(ZEND_NUM_ARGS()) {
		case 1:
			if (zend_get_parameters_ex(1, &str)==FAILURE) {
				RETURN_FALSE;
			}
			break;
		case 2:
			if (zend_get_parameters_ex(2, &str, &max_chars_per_line)==FAILURE) {
				RETURN_FALSE;
			}
			convert_to_long_ex(max_chars_per_line);
			max_chars = (*max_chars_per_line)->value.lval;
			break;
		default:
			WRONG_PARAM_COUNT;
			break;
	}
	
	convert_to_string_ex(str);
	
	if ((*str)->value.str.len==0) {
		RETURN_FALSE;
	}

	tmp = (*str)->value.str.val;
	block_start=block_end=0;
	block_ended=0;

	heb_str = (char *) emalloc((*str)->value.str.len+1);
	target = heb_str+(*str)->value.str.len;
	opposite_target = heb_str;
	*target = 0;
	target--;

	block_length=0;

	if (isheb(*tmp)) {
		block_type = _HEB_BLOCK_TYPE_HEB;
	} else {
		block_type = _HEB_BLOCK_TYPE_ENG;
	}
	
	do {
		if (block_type==_HEB_BLOCK_TYPE_HEB) {
			while((isheb((int)*(tmp+1)) || _isblank((int)*(tmp+1)) || ispunct((int)*(tmp+1)) || (int)*(tmp+1)=='\n' ) && block_end<(*str)->value.str.len-1) {
				tmp++;
				block_end++;
				block_length++;
			}
			for (i=block_start; i<=block_end; i++) {
				*target = (*str)->value.str.val[i];
				switch (*target) {
					case '(':
						*target = ')';
						break;
					case ')':
						*target = '(';
						break;
					default:
						break;
				}
				target--;
			}
			block_type = _HEB_BLOCK_TYPE_ENG;
		} else {
			while(!isheb(*(tmp+1)) && (int)*(tmp+1)!='\n' && block_end<(*str)->value.str.len-1) {
				tmp++;
				block_end++;
				block_length++;
			}
			while ((_isblank((int)*tmp) || ispunct((int)*tmp)) && *tmp!='/' && *tmp!='-' && block_end>block_start) {
				tmp--;
				block_end--;
			}
			for (i=block_end; i>=block_start; i--) {
				*target = (*str)->value.str.val[i];
				target--;
			}
			block_type = _HEB_BLOCK_TYPE_HEB;
		}
		block_start=block_end+1;
	} while(block_end<(*str)->value.str.len-1);


	broken_str = (char *) emalloc((*str)->value.str.len+1);
	begin=end=(*str)->value.str.len-1;
	target = broken_str;
		
	while (1) {
		char_count=0;
		while ((!max_chars || char_count<max_chars) && begin>0) {
			char_count++;
			begin--;
			if (begin<=0 || _isnewline(heb_str[begin])) {
				while(begin>0 && _isnewline(heb_str[begin-1])) {
					begin--;
					char_count++;
				}
				break;
			}
		}
		if (char_count==max_chars) { /* try to avoid breaking words */
			int new_char_count=char_count, new_begin=begin;
			
			while (new_char_count>0) {
				if (_isblank(heb_str[new_begin]) || _isnewline(heb_str[new_begin])) {
					break;
				}
				new_begin++;
				new_char_count--;
			}
			if (new_char_count>0) {
				char_count=new_char_count;
				begin=new_begin;
			}
		}
		orig_begin=begin;
		
		if (_isblank(heb_str[begin])) {
			heb_str[begin]='\n';
		}
		while (begin<=end && _isnewline(heb_str[begin])) { /* skip leading newlines */
			begin++;
		}
		for (i=begin; i<=end; i++) { /* copy content */
			*target = heb_str[i];
			target++;
		}
		for (i=orig_begin; i<=end && _isnewline(heb_str[i]); i++) {
			*target = heb_str[i];
			target++;
		}
		begin=orig_begin;

		if (begin<=0) {
			*target = 0;
			break;
		}
		begin--;
		end=begin;
	}
	efree(heb_str);

	if (convert_newlines) {
		php_char_to_str(broken_str,(*str)->value.str.len,'\n',"<br>\n",5,return_value);
		efree(broken_str);
	} else {
		return_value->value.str.val = broken_str;
		return_value->value.str.len = (*str)->value.str.len;
		return_value->type = IS_STRING;
	}
}


/* {{{ proto string hebrev(string str [, int max_chars_per_line])
   Convert logical Hebrew text to visual text */
PHP_FUNCTION(hebrev)
{
	php_hebrev(INTERNAL_FUNCTION_PARAM_PASSTHRU,0);
}
/* }}} */

/* {{{ proto string hebrevc(string str [, int max_chars_per_line])
   Convert logical Hebrew text to visual text with newline conversion */
PHP_FUNCTION(hebrevc)
{
	php_hebrev(INTERNAL_FUNCTION_PARAM_PASSTHRU,1);
}
/* }}} */

/* {{{ proto string nl2br(string str)
   Converts newlines to HTML line breaks */
PHP_FUNCTION(nl2br)
{
	zval **str;
	
	if (ZEND_NUM_ARGS()!=1 || zend_get_parameters_ex(1, &str)==FAILURE) {
		WRONG_PARAM_COUNT;
	}
	
	convert_to_string_ex(str);
	
	php_char_to_str((*str)->value.str.val,(*str)->value.str.len,'\n',"<br>\n",5,return_value);
}
/* }}} */

/* {{{ proto string strip_tags(string str [, string allowable_tags])
   Strips HTML and PHP tags from a string */
PHP_FUNCTION(strip_tags)
{
	char *buf;
	zval **str, **allow=NULL;
	char *allowed_tags=NULL;
	int allowed_tags_len=0;

	switch(ZEND_NUM_ARGS()) {
		case 1:
			if(zend_get_parameters_ex(1, &str)==FAILURE) {
				RETURN_FALSE;
			}
			break;
		case 2:
			if(zend_get_parameters_ex(2, &str, &allow)==FAILURE) {
				RETURN_FALSE;
			}
			convert_to_string_ex(allow);
			allowed_tags = (*allow)->value.str.val;
			allowed_tags_len = (*allow)->value.str.len;
			break;
		default:
			WRONG_PARAM_COUNT;
			break;
	}
	convert_to_string_ex(str);
	buf = estrndup((*str)->value.str.val,(*str)->value.str.len);
	php_strip_tags(buf, (*str)->value.str.len, 0, allowed_tags, allowed_tags_len);
	RETURN_STRING(buf, 0);
}
/* }}} */

/* {{{ proto string setlocale(string category, string locale)
   Set locale information */
PHP_FUNCTION(setlocale)
{
	zval **pcategory, **plocale;
	zval *category, *locale;
	int cat;
	char *loc, *retval;
	BLS_FETCH();

	if (ZEND_NUM_ARGS()!=2 || zend_get_parameters_ex(2, &pcategory, &plocale)==FAILURE)
		WRONG_PARAM_COUNT;
#ifdef HAVE_SETLOCALE
	convert_to_string_ex(pcategory);
	convert_to_string_ex(plocale);
	category = *pcategory;
	locale = *plocale;
	if (!strcasecmp ("LC_ALL", category->value.str.val))
		cat = LC_ALL;
	else if (!strcasecmp ("LC_COLLATE", category->value.str.val))
		cat = LC_COLLATE;
	else if (!strcasecmp ("LC_CTYPE", category->value.str.val))
		cat = LC_CTYPE;
#ifdef LC_MESSAGES
	else if (!strcasecmp ("LC_MESSAGES", category->value.str.val))
		cat = LC_MESSAGES;
#endif
	else if (!strcasecmp ("LC_MONETARY", category->value.str.val))
		cat = LC_MONETARY;
	else if (!strcasecmp ("LC_NUMERIC", category->value.str.val))
		cat = LC_NUMERIC;
	else if (!strcasecmp ("LC_TIME", category->value.str.val))
		cat = LC_TIME;
	else {
		php_error(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->value.str.val);
		RETURN_FALSE;
	}
	if (!strcmp ("0", locale->value.str.val))
		loc = NULL;
	else
		loc = locale->value.str.val;
	retval = setlocale (cat, loc);
	if (retval) {
		/* Remember if locale was changed */
		if (loc) {
			STR_FREE(BG(locale_string));
			BG(locale_string) = estrdup(retval);
		}

		RETVAL_STRING(retval,1);
		return;
	}
#endif
	RETURN_FALSE;
}
/* }}} */

/* {{{ proto void parse_str(string encoded_string, [array result])
   Parses GET/POST/COOKIE data and sets global variables */
PHP_FUNCTION(parse_str)
{
	zval **arg;
	zval **arrayArg;
	zval *sarg;
	char *res = NULL;
	int argCount;
	int old_rg;
	
	PLS_FETCH();
	SLS_FETCH();

	argCount = ARG_COUNT(ht);
	if(argCount < 1 || argCount > 2 || zend_get_parameters_ex(argCount, &arg, &arrayArg) == FAILURE) {
		WRONG_PARAM_COUNT;
	}

	convert_to_string_ex(arg);
	sarg = *arg;
	if (sarg->value.str.val && *sarg->value.str.val) {
		res = estrndup(sarg->value.str.val, sarg->value.str.len);
	}

	old_rg = PG(register_globals);
	if(argCount == 1) {
		PG(register_globals) = 1;
		php_treat_data(PARSE_STRING, res, NULL ELS_CC PLS_CC SLS_CC);
	} else 	{
		PG(register_globals) = 0;
		/* Clear out the array that was passed in. */
		zval_dtor(*arrayArg);
		array_init(*arrayArg);
		
		php_treat_data(PARSE_STRING, res, *arrayArg ELS_CC PLS_CC SLS_CC);
	}
	PG(register_globals) = old_rg;
}
/* }}} */

#define PHP_TAG_BUF_SIZE 1023

/* Check if tag is in a set of tags 
 *
 * states:
 * 
 * 0 start tag
 * 1 first non-whitespace char seen
 */
int php_tag_find(char *tag, int len, char *set) {
	char c, *n, *t;
	int state=0, done=0;
	char *norm = emalloc(len+1);

	n = norm;
	t = tag;
	c = tolower(*t);
	/* 
	   normalize the tag removing leading and trailing whitespace
	   and turn any <a whatever...> into just <a> and any </tag>
	   into <tag>
	*/
	if (!len) {
		return 0;
	}
	while(!done) {
		switch(c) {
		case '<':
			*(n++) = c;
			break;
		case '>':
			done =1;
			break;
		default:
			if(!isspace((int)c)) {
				if(state==0) {
					state=1;
					if(c!='/') *(n++) = c;
				} else {
					*(n++) = c;
				}
			} else {
				if(state==1) done=1;
			}
			break;
		}
		c = tolower(*(++t));
	}  
	*(n++) = '>';
	*n = '\0'; 
	if(strstr(set,norm)) {
		done=1;
	} else {
		done=0;
	}
	efree(norm);
	return done;
}

/* A simple little state-machine to strip out html and php tags 
	
	State 0 is the output state, State 1 means we are inside a
	normal html tag and state 2 means we are inside a php tag.

	The state variable is passed in to allow a function like fgetss
	to maintain state across calls to the function.

	lc holds the last significant character read and br is a bracket
	counter.

	When an allow string is passed in we keep track of the string
	in state 1 and when the tag is closed check it against the
	allow string to see if we should allow it.
*/
PHPAPI void php_strip_tags(char *rbuf, int len, int state, char *allow, int allow_len)
{
	char *tbuf, *buf, *p, *tp, *rp, c, lc;
	int br, i=0;

	buf = estrndup(rbuf,len);
	c = *buf;
	lc = '\0';
	p = buf;
	rp = rbuf;
	br = 0;
	if(allow) {
		php_strtolower(allow, allow_len);
		tbuf = emalloc(PHP_TAG_BUF_SIZE+1);
		tp = tbuf;
	} else {
		tbuf = tp = NULL;
	}

	while(i<len) {
		switch (c) {
			case '<':
				if (state == 0) {
					lc = '<';
					state = 1;
					if(allow) {
						*(tp++) = '<';
					}
				}
				break;

			case '(':
				if (state == 2) {
					if (lc != '\"') {
						lc = '(';
						br++;
					}
				} else if (state == 0) {
					*(rp++) = c;
				}
				break;	

			case ')':
				if (state == 2) {
					if (lc != '\"') {
						lc = ')';
						br--;
					}
				} else if (state == 0) {
					*(rp++) = c;
				}
				break;	

			case '>':
				if (state == 1) {
					lc = '>';
					state = 0;
					if(allow) {
						*(tp++) = '>';
						*tp='\0';
						if(php_tag_find(tbuf, tp-tbuf, allow)) {
							memcpy(rp,tbuf,tp-tbuf);
							rp += tp-tbuf;
						}
						tp = tbuf;
					}
				} else if (state == 2) {
					if (!br && lc != '\"' && *(p-1)=='?') {
						state = 0;
						tp = tbuf;
					}
				}
				break;

			case '\"':
				if (state == 2) {
					if (lc == '\"') {
						lc = '\0';
					} else if (lc != '\\') {
						lc = '\"';
					}
				} else if (state == 0) {
					*(rp++) = c;
				} else if (allow && state == 1) {
					*(tp++) = c;
				}
				break;

			case '?':
				if (state==1 && *(p-1)=='<') {
					br=0;
					state=2;
					break;
				}
				/* fall-through */

			default:
				if (state == 0) {
					*(rp++) = c;
				} else if(allow && state == 1) {
					*(tp++) = c;
					if( (tp-tbuf)>=PHP_TAG_BUF_SIZE ) { /* no buffer overflows */
						tp = tbuf;
					}
				}
				break;
		}
		c = *(++p);
		i++;
	}	
	*rp = '\0';
	efree(buf);
	if(allow) efree(tbuf);
}

/* {{{ proto string str_repeat(string input, int mult)
   Returns the input string repeat mult times */
PHP_FUNCTION(str_repeat)
{
	zval		**input_str;		/* Input string */
	zval		**mult;				/* Multiplier */
	char		 *result;			/* Resulting string */
	int			  result_len;		/* Length of the resulting string */
	int			  i;
	
	if (ZEND_NUM_ARGS() != 2 || zend_get_parameters_ex(2, &input_str, &mult) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
	
	/* Make sure we're dealing with proper types */
	convert_to_string_ex(input_str);
	convert_to_long_ex(mult);
	
	if ((*mult)->value.lval < 0) {
		php_error(E_WARNING, "Second argument to %s() has to be greater than or equal to 0",
				  get_active_function_name());
		return;
	}

	/* Don't waste our time if it's empty */
	if ((*input_str)->value.str.len == 0)
		RETURN_STRINGL(empty_string, 0, 1);
	
	/* ... or if the multiplier is zero */
	if ((*mult)->value.lval == 0)
		RETURN_STRINGL(empty_string, 0, 1);
	
	/* Initialize the result string */	
	result_len = (*input_str)->value.str.len * (*mult)->value.lval;
	result = (char *)emalloc(result_len + 1);
	
	/* Copy the input string into the result as many times as necessary */
	for (i=0; i<(*mult)->value.lval; i++) {
		memcpy(result + (*input_str)->value.str.len * i,
			   (*input_str)->value.str.val,
			   (*input_str)->value.str.len);
	}
	result[result_len] = '\0';
	
	RETURN_STRINGL(result, result_len, 0);
}
/* }}} */

/* {{{ proto mixed count_chars(string input [, int mode])
   Returns info about what characters are used in input */
PHP_FUNCTION(count_chars)
{
	zval **input, **mode;
	int chars[256];
	int ac=ZEND_NUM_ARGS();
	int mymode=0;
	unsigned char *buf;
	int len, inx;
	char retstr[256];
	int retlen=0;

	if (ac < 1 || ac > 2 || zend_get_parameters_ex(ac, &input, &mode) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
	
	convert_to_string_ex(input);

	if (ac == 2) {
		convert_to_long_ex(mode);
		mymode = (*mode)->value.lval;
		
		if (mymode < 0 || mymode > 4) {
			php_error(E_WARNING, "unknown mode");
			RETURN_FALSE;
		}
	}
	
	len = (*input)->value.str.len;
	buf = (unsigned char *) (*input)->value.str.val;
	memset((void*) chars,0,sizeof(chars));

	while (len > 0) {
		chars[*buf]++;
		buf++;
		len--;
	}

	if (mymode < 3) {
		array_init(return_value);
	}

	for (inx=0; inx < 255; inx++) {
		switch (mymode) {
 		case 0:
			add_index_long(return_value,inx,chars[inx]);
			break;
 		case 1:
			if (chars[inx] != 0) {
				add_index_long(return_value,inx,chars[inx]);
			}
			break;
  		case 2:
			if (chars[inx] == 0) {
				add_index_long(return_value,inx,chars[inx]);
			}
			break;
  		case 3:
			if (chars[inx] != 0) {
				retstr[retlen++] = inx;
			}
			break;
  		case 4:
			if (chars[inx] == 0) {
				retstr[retlen++] = inx;
			}
			break;
		}
	}
	
	if (mymode >= 3 && mymode <= 4) {
		RETURN_STRINGL(retstr,retlen,1);
	}
}
/* }}} */

static void php_strnatcmp(INTERNAL_FUNCTION_PARAMETERS, int fold_case)
{
	zval **s1, **s2;

	if (ZEND_NUM_ARGS()!=2 || zend_get_parameters_ex(2, &s1, &s2) == FAILURE) {
		WRONG_PARAM_COUNT;
	}

	convert_to_string_ex(s1);
	convert_to_string_ex(s2);

	RETURN_LONG(strnatcmp_ex((*s1)->value.str.val, (*s1)->value.str.len,
							 (*s2)->value.str.val, (*s2)->value.str.len,
							 fold_case));
}


/* {{{ proto int strnatcmp(string s1, string s2)
   Returns the result of string comparison using 'natural' algorithm */
PHP_FUNCTION(strnatcmp)
{
	php_strnatcmp(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
}
/* }}} */


/* {{{ proto int strnatcasecmp(string s1, string s2)
   Returns the result of case-insensitive string comparison using 'natural' algorithm */
PHP_FUNCTION(strnatcasecmp)
{
	php_strnatcmp(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
}
/* }}} */


/* {{{ proto int substr_count(string haystack, string needle)
   Returns the number of times a substring occurs in the string */
PHP_FUNCTION(substr_count)
{
	zval **haystack, **needle;	
	int i, length, count = 0;
	char *p, *endp, cmp;

	if (ZEND_NUM_ARGS() != 2 || zend_get_parameters_ex(2, &haystack, &needle) == FAILURE) {
		WRONG_PARAM_COUNT;
	}

	convert_to_string_ex(haystack);
	convert_to_string_ex(needle);

	if ((*needle)->value.str.len == 0) {
		php_error(E_WARNING, "Empty substring");
		RETURN_FALSE;
	} else if ((*needle)->value.str.len == 1) {
		/* Special optimized case to avoid calls to php_memnstr(). */
		for (i = 0, p = (*haystack)->value.str.val, 
		     length = (*haystack)->value.str.len, cmp = (*needle)->value.str.val[0]; 
		     i < length; i++) {
			if (p[i] == cmp) {
				count++;
			}
		}
	} else {
 		p = (*haystack)->value.str.val;
		endp = p + (*haystack)->value.str.len;
		while (p <= endp) {
			if( (p = php_memnstr(p, (*needle)->value.str.val, (*needle)->value.str.len, endp)) != NULL ) {
				p += (*needle)->value.str.len;
				count++;
			} else {
				break;
			}
		}
	}

	RETURN_LONG(count);
}
/* }}} */	


/* {{{ proto string str_pad(string input, int pad_length [, string pad_string [, int pad_type]])
   Returns input string padded on the left or right to specified length with pad_string */
PHP_FUNCTION(str_pad)
{
	/* Input arguments */
	zval **input,				/* Input string */
		 **pad_length,			/* Length to pad to */
		 **pad_string,			/* Padding string */
		 **pad_type;			/* Padding type (left/right/both) */
	
	/* Helper variables */
	int	   num_pad_chars;		/* Number of padding characters (total - input size) */
	char  *result = NULL;		/* Resulting string */
	int	   result_len = 0;		/* Length of the resulting string */
	char  *pad_str_val = " ";	/* Pointer to padding string */
	int    pad_str_len = 1;		/* Length of the padding string */
	int	   pad_type_val = STR_PAD_RIGHT; /* The padding type value */
	int	   i, left_pad=0, right_pad=0;


	if (ZEND_NUM_ARGS() < 2 || ZEND_NUM_ARGS() > 4 ||
		zend_get_parameters_ex(ZEND_NUM_ARGS(), &input, &pad_length, &pad_string, &pad_type) == FAILURE) {
		WRONG_PARAM_COUNT;
	}

	/* Perform initial conversion to expected data types. */
	convert_to_string_ex(input);
	convert_to_long_ex(pad_length);

	num_pad_chars = Z_LVAL_PP(pad_length) - Z_STRLEN_PP(input);

	/* If resulting string turns out to be shorter than input string,
	   we simply copy the input and return. */
	if (num_pad_chars < 0) {
		*return_value = **input;
		zval_copy_ctor(return_value);
		return;
	}

	/* Setup the padding string values if specified. */
	if (ZEND_NUM_ARGS() > 2) {
		convert_to_string_ex(pad_string);
		if (Z_STRLEN_PP(pad_string) == 0) {
			php_error(E_WARNING, "Padding string cannot be empty in %s()",
					  get_active_function_name());
			return;
		}
		pad_str_val = Z_STRVAL_PP(pad_string);
		pad_str_len = Z_STRLEN_PP(pad_string);

		if (ZEND_NUM_ARGS() > 3) {
			convert_to_long_ex(pad_type);
			pad_type_val = Z_LVAL_PP(pad_type);
			if (pad_type_val < STR_PAD_LEFT || pad_type_val > STR_PAD_BOTH) {
				php_error(E_WARNING, "Padding type has to be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH in %s()", get_active_function_name());
				return;
			}
		}
	}

	result = (char *)emalloc(Z_STRLEN_PP(input) + num_pad_chars + 1);

	/* We need to figure out the left/right padding lengths. */
	switch (pad_type_val) {
		case STR_PAD_RIGHT:
			left_pad = 0;
			right_pad = num_pad_chars;
			break;

		case STR_PAD_LEFT:
			left_pad = num_pad_chars;
			right_pad = 0;
			break;

		case STR_PAD_BOTH:
			left_pad = num_pad_chars / 2;
			right_pad = num_pad_chars - left_pad;
			break;
	}

	/* First we pad on the left. */
	for (i = 0; i < left_pad; i++)
		result[result_len++] = pad_str_val[i % pad_str_len];

	/* Then we copy the input string. */
	memcpy(result + result_len, Z_STRVAL_PP(input), Z_STRLEN_PP(input));
	result_len += Z_STRLEN_PP(input);

	/* Finally, we pad on the right. */
	for (i = 0; i < right_pad; i++)
		result[result_len++] = pad_str_val[i % pad_str_len];

	result[result_len] = '\0';

	RETURN_STRINGL(result, result_len, 0);
}
/* }}} */

   
/* {{{ proto mixed sscanf(string str, string format [, string ...])
   Implements an ANSI C compatible sscanf */
PHP_FUNCTION(sscanf)
{
    zval **format;
    zval **literal;
    int  result;
	zval ***args;
	int	argCount;	


    argCount = ZEND_NUM_ARGS();
	if (argCount < 2) {
		WRONG_PARAM_COUNT;
	}
	args = (zval ***)emalloc(argCount * sizeof(zval **));
	if (!args || (zend_get_parameters_array_ex(argCount,args) == FAILURE)) {
		efree( args );
		WRONG_PARAM_COUNT;
	}
	
	literal = args[0];
	format  = args[1];

	convert_to_string_ex( format );
	convert_to_string_ex( literal );
	
	result = php_sscanf_internal( (*literal)->value.str.val,
								  (*format)->value.str.val,
								  argCount,args,
								  2,&return_value);
	efree(args);

	if (SCAN_ERROR_WRONG_PARAM_COUNT == result) {
		WRONG_PARAM_COUNT;
	}

}
/* }}} */


/*
 * Local variables:
 * tab-width: 4
 * c-basic-offset: 4
 * End:
 */
