PHP_FUNCTION(addcslashes)、addslashes、stripcslashes、stripslashes

两对转义特殊字符的函数

addslashes经常用于防注入,注:mysql_real_escape_string要更安全。

看其中常用的一对吧,addslashes:

while (source < end) {
		switch (*source) {
			case '\0':
				*target++ = '\\';
				*target++ = '0';
				break;
			case '\'':
			case '\"':
			case '\\':
				*target++ = '\\';
				/* break is missing *intentionally* */
			default:
				*target++ = *source;
				break;
		}

		source++;
	}

这里有个特别的地方,当遇到\0时,显式的拼接了*target++ = ‘0’;而\’\”\\都是*target++ = *source;按理讲,这两种写法其实是一样的,那为什么\0要单独写个case且单独break呢?我猜\0本身就是字符结束的标记,拼接*target++ = ‘\\’;后,就无法再访问*target++了,所以不能使用*target++ = *source;只能显式拼接一个0

类似的stripslashes里也单独处理了\0

PHP_FUNCTION(similar_text)

proto int similar_text(string str1, string str2 [, float percent])
Calculates the similarity between two strings

计算两个字符串的相似度,完全一样,则返回100。

手册里说是依据这个算法:Programming Classics: Implementing the World’s Best Algorithms by Oliver (ISBN 0-131-00413-1)

没什么兴趣去研究这个算法,直接看源码吧:

sim = php_similar_char(t1, t1_len, t2, t2_len);

	if (ac > 2) {
		Z_DVAL_PP(percent) = sim * 200.0 / (t1_len + t2_len);
	}

直接调用了php_similar_char函数,返回结果显然是介于0~二分之一(t1_len+t2_len)之间,只有这样计算出来的percent才会介于0-100之间。

php_similar_char:

static int php_similar_char(const char *txt1, int len1, const char *txt2, int len2)
{
	int sum;
	int pos1 = 0, pos2 = 0, 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;
}

有递归,且又调用了php_similar_str函数:

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;
			}
		}
	}
}

看到了三层for,简单描述一下这三层for要干什么事情,对输入的两个字符串每个字符,都进行交叉比较,找出第一对不相等的字符,得到这对字符在第三层for遍历的步长,在这所有交叉比较中,找出步长最长的,赋给max,注意max一级pos1、pos2是引用,在php_similar_char里可以直接使用。最典型的是,如果两个字符串只有最后一对字符不相等,显然max=len-1,那么根据这个公式:sim * 200.0 / (t1_len + t2_len);会得到:

percent = (len-1)*200/(2*len)

len=1,percent=0;

len=2,percent=50;

len=3,percent=66.66;

…..

php_similar_char里还有递归,当不是第一对或者两字符串都没遍历完的时候,还要递归遍历下去,直到有一个遍历结束。

递归+三层遍历,这个函数要小心使用!!!!!

PHP_FUNCTION(strtr)

proto string strtr(string str, string from[, string to])
Translates characters in str using given translation tables

用给定的对照表,转换str中的字符。用了translates这个单词,我理解是替换,还是字符替换啊。。。

其实第二三参数也可以合并成一个关联数组。

看一下源代码:

if (ac == 2) {
		php_strtr_array(return_value, str, str_len, HASH_OF(*from));
	} else {
		convert_to_string_ex(from);

		ZVAL_STRINGL(return_value, str, str_len, 1);

		php_strtr(Z_STRVAL_P(return_value),
				  Z_STRLEN_P(return_value),
				  Z_STRVAL_PP(from),
				  to,
				  MIN(Z_STRLEN_PP(from),
				  to_len));
	}

两参数时,会调用php_strtr_array处理,三参数调用php_strtr处理,先看一下两参数的情况:

从php_strtr_array参数表里可以看出from是个hashtable类型,php_strtr_array里又分别调用了以下函数进行处理:

php_strtr_array_prepare_repls、php_strtr_array_prepare、php_strtr_array_do_repl

核心代码在php_strtr_array_do_repl里,说实话,这种命名很长有很类似的函数名,跳来跳去,很容易看晕头,最后两级遍历是少不了的,一级是遍历原串str本身,再遍历from,匹配到,执行拼接,将对应的to拼接到新串上,源码略。

回头再看三参数的情况,因为三参数执行的是char级的逐个替换,是逐个字符替换,所以代码就简单的多:

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;
}

源码写的很精巧,先定义一个char型数组xlat,初始化成key=value:for (i = 0; i < 256; xlat[i] = i, i++);

然后将from作为key,to作为value,重置xlat数组,这里要非常注意了,如果from里有重复的字符,因为for是从0开始循环,显然后面的对应关系会替换掉前面的对应关系。还有一点需要注意,最后一个参数trlen,这个长度取的是to串的长度,这里循环的长度用的就是trlen,所以from如果比to长,多余的字符是无效的,反过来呢,如果to比from长,没发现这里有什么特别处理,str_from[i],如果i越界了,会怎么样?

最后遍历一遍原串str,用xlat挨个替换,不在from里的那些xlat数组,其key是与value相等的,所以最后经过str[i] = xlat[(unsigned char) str[i]];处理的,只是在from里有的字符才会被替换。

php测试代码:

$str = strtr("abcdefg","abca","1234");
print_r($str);

 

PHP_FUNCTION(ucfirst)、lcfirst、ucwords

几个处理首字母大小写的函数

ucfirst:

toupper((unsigned char) *r);

lcfirst:

tolower((unsigned char) *r);

ucwords:

*r = toupper((unsigned char) *r);
	for (r_end = r + Z_STRLEN_P(return_value) - 1; r < r_end; ) {
		if (isspace((int) *(unsigned char *)r++)) {
			*r = toupper((unsigned char) *r);
		}
	}

如果当前字符(*r++)是空格,就把下一个字符转大写。特殊情况,如果连续空格,将后续空格转大写,那还是空格。

PHP_FUNCTION(quotemeta)

proto string quotemeta(string str)
Quotes meta characters

什么叫meta字符呢?解释起来费劲,直接看源码里,看哪些是mata字符吧:

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;

我的理解是正则匹配里有特殊含义的那些字符。

源码也就是上面这点,这句注释有意思break is missing _intentionally_,故意漏掉了break。这样的效果是:如果正好匹配到meta字符的话,在meta字符前加个反杠,然后拼接上meta字符,非meta字符,当然是直接到default里了。

 

PHP_FUNCTION(substr_replace)

proto mixed substr_replace(mixed str, mixed repl, mixed start [, mixed length])
Replaces part of a string with another string

这个函数是异常复杂,所有参数类型都是mixed,既可以是string也可以是array

先看正常的string处理,和前面substr函数类似,start和length都是可以为负的,其处理也类似,所以这部分代码略:

memcpy(result, Z_STRVAL_PP(str), f);
			if (repl_len) {
				memcpy((result + f), (Z_TYPE_PP(repl) == IS_ARRAY ? Z_STRVAL_PP(tmp_repl) : Z_STRVAL_PP(repl)), repl_len);
			}
			memcpy((result + f + repl_len), Z_STRVAL_PP(str) + f + l, Z_STRLEN_PP(str) - f - l);
			result[result_len] = '\0';

就是一个简单的部分字串替换处理。

如果是array,处理就复杂多了,源码相当长,看下来发现虽然每个参数都是mixed类型,真正处理的时候,实际上就是把第一个参数当数组,意思是将第一个数组参数里的每个元素,都执行一次substr_replace(string,….)处理。

因为要对数组每个元素都要处理,所以遍历数组是免不了的,其他参数最后都会转换成普通参数。

源码太长略,给一个php范例,增强理解。

$arr = substr_replace(array('a','b','c'),'d',0,0);
print_r($arr);
$arr = substr_replace(array('a','b','c'),array('d','e'),0,0);
print_r($arr);
$arr = substr_replace(array('a','b','c'),array('d','e'),array(0,1),0);
print_r($arr);

 

 

 

PHP_FUNCTION(substr)

proto string substr(string str, int start [, int length])
Returns part of a string

核心代码其实就一句:

RETURN_STRINGL(str + f, l, 1);

f和l是计算过的偏移和长度。

参数start和length都是可以为负的,所以要先处理负数的情况。

当start为负:

if (f < 0) {
		f = str_len + f;
		if (f < 0) {
			f = 0;
		}
	}

相当于是从str右数abs(f)长度开始截,如果f小到加上字串长度还负,就默认为0了,也就是从左起点开始。

当length为负:

if (l < 0) {
		l = (str_len - f) + l;
		if (l < 0) {
			l = 0;
		}
	}

str_len-f+l,注意这里的f已经是处理过的偏移量,不会是负数,也就是去掉字符串右边的abs(l)个字符。

PHP_FUNCTION(chunk_split)

proto string chunk_split(string str [, int chunklen [, string ending]])
Returns split line

功能似乎和wordwrap有点类似,chunklen默认长度是76,ending默认是\r\n。

这里讲一下为什么chunklen默认是76,base64编码,按照RFC 822规定,每76个字符要加一个回车换行,这么一讲就知道这个函数最初的作用了。php的base64_encode并不会每76字符就加回车换行,使用这个函数就可以转换为符合RFC 822规定的base64编码。

看一下源码:

if (chunklen <= 0) {
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Chunk length should be greater than zero");
		RETURN_FALSE;
	}

如果chunklen小于等于0,是毫不含糊,直接false的

if (chunklen > str_len) {
		/* to maintain BC, we must return original string + ending */
		result_len = endlen + str_len;
		result = emalloc(result_len + 1);
		memcpy(result, str, str_len);
		memcpy(result + str_len, end, endlen);
		result[result_len] = '\0';
		RETURN_STRINGL(result, result_len, 0);
	}

如果chunklen比字串本身还要长,直接拼接上ending,返回。

主体处理,使用的是php_chunk_split函数,里面很节省的计算了要给拼接字串分配的空间大小,看源码仔细体会吧,这里把拼接部分源码拿出来看看:

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

将原串赋给p,先拼接chunklen长度的p到q里,移动q指针到末尾,再拼接end到q,再移动指针到q结尾,p指针移动chunklen长度,依次循环,直到不够chunklen长度,最后再把剩余的尾巴拼接上

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

注意,最后一定会带有一个end