流传的移除XSS攻击的php函数

The goal of this function is to be a generic function that can be used to parse almost any input and render it XSS safe. For more information on actual XSS attacks, check out http://ha.ckers.org/xss.html. Another excellent site is the XSS Database which details each attack and how it works.

<?php
/**
 * Usage: Run *every* variable passed in through it.
 * The goal of this function is to be a generic function that can be used to
 * parse almost any input and render it XSS safe. For more information on
 * actual XSS attacks, check out http://ha.ckers.org/xss.html. Another
 * excellent site is the XSS Database which details each attack and how it
 * works.
 *
 * Used with permission by the author.
 * URL: http://quickwired.com/smallprojects/php_xss_filter_function.php
 *
 * License:
 * This code is public domain, you are free to do whatever you want with it,
 * including adding it to your own project which can be under any license.
 *
 * $Id: RemoveXSS.php 2663 2007-11-05 09:22:23Z ingmars $
 *
 * @author	Travis Puderbaugh <kallahar@quickwired.com>
 * @package RemoveXSS
 */
class RemoveXSS {

	/**
	 * Wrapper for the RemoveXSS function.
	 * Removes potential XSS code from an input string.
	 *
	 * Using an external class by Travis Puderbaugh <kallahar@quickwired.com>
	 *
	 * @param	string		Input string
	 * @return	string		Input string with potential XSS code removed
	 */
	function RemoveXSS($val)	{
		// remove all non-printable characters. CR(0a) and LF(0b) and TAB(9) are allowed
		// this prevents some character re-spacing such as <java\0script>
		// note that you have to handle splits with \n, \r, and \t later since they *are* allowed in some inputs
		$val = preg_replace('/([\x00-\x08][\x0b-\x0c][\x0e-\x20])/', '', $val);

		// straight replacements, the user should never need these since they're normal characters
		// this prevents like <IMG SRC=&#X40&#X61&#X76&#X61&#X73&#X63&#X72&#X69&#X70&#X74&#X3A&#X61&#X6C&#X65&#X72&#X74&#X28&#X27&#X58&#X53&#X53&#X27&#X29>
		$search = 'abcdefghijklmnopqrstuvwxyz';
		$search.= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
		$search.= '1234567890!@#$%^&*()';
		$search.= '~`";:?+/={}[]-_|\'\\';

		for ($i = 0; $i < strlen($search); $i++) {
			// ;? matches the ;, which is optional
			// 0{0,7} matches any padded zeros, which are optional and go up to 8 chars

			// &#x0040 @ search for the hex values
			$val = preg_replace('/(&#[x|X]0{0,8}'.dechex(ord($search[$i])).';?)/i', $search[$i], $val); // with a ;
			// &#00064 @ 0{0,7} matches '0' zero to seven times
			$val = preg_replace('/(&#0{0,8}'.ord($search[$i]).';?)/', $search[$i], $val); // with a ;
		}

		// now the only remaining whitespace attacks are \t, \n, and \r
		$ra1 = array('javascript', 'vbscript', 'expression', 'applet', 'meta', 'xml', 'blink', 'link', 'style', 'script', 'embed', 'object', 'iframe', 'frame', 'frameset', 'ilayer', 'layer', 'bgsound', 'title', 'base');
		$ra2 = array('onabort', 'onactivate', 'onafterprint', 'onafterupdate', 'onbeforeactivate', 'onbeforecopy', 'onbeforecut', 'onbeforedeactivate', 'onbeforeeditfocus', 'onbeforepaste', 'onbeforeprint', 'onbeforeunload', 'onbeforeupdate', 'onblur', 'onbounce', 'oncellchange', 'onchange', 'onclick', 'oncontextmenu', 'oncontrolselect', 'oncopy', 'oncut', 'ondataavailable', 'ondatasetchanged', 'ondatasetcomplete', 'ondblclick', 'ondeactivate', 'ondrag', 'ondragend', 'ondragenter', 'ondragleave', 'ondragover', 'ondragstart', 'ondrop', 'onerror', 'onerrorupdate', 'onfilterchange', 'onfinish', 'onfocus', 'onfocusin', 'onfocusout', 'onhelp', 'onkeydown', 'onkeypress', 'onkeyup', 'onlayoutcomplete', 'onload', 'onlosecapture', 'onmousedown', 'onmouseenter', 'onmouseleave', 'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup', 'onmousewheel', 'onmove', 'onmoveend', 'onmovestart', 'onpaste', 'onpropertychange', 'onreadystatechange', 'onreset', 'onresize', 'onresizeend', 'onresizestart', 'onrowenter', 'onrowexit', 'onrowsdelete', 'onrowsinserted', 'onscroll', 'onselect', 'onselectionchange', 'onselectstart', 'onstart', 'onstop', 'onsubmit', 'onunload');
		$ra = array_merge($ra1, $ra2);

		$found = true; // keep replacing as long as the previous round replaced something
		while ($found == true) {
			$val_before = $val;
			for ($i = 0; $i < sizeof($ra); $i++) {
				$pattern = '/';
				for ($j = 0; $j < strlen($ra[$i]); $j++) {
					if ($j > 0) {
						$pattern .= '(';
						$pattern .= '(&#[x|X]0{0,8}([9][a][b]);?)?';
						$pattern .= '|(&#0{0,8}([9][10][13]);?)?';
						$pattern .= ')?';
					}
					$pattern .= $ra[$i][$j];
				}
				$pattern .= '/i';
				$replacement = substr($ra[$i], 0, 2).'<x>'.substr($ra[$i], 2); // add in <> to nerf the tag
				$val = preg_replace($pattern, $replacement, $val); // filter out the hex tags
				if ($val_before == $val) {
					// no replacements were made, so exit the loop
					$found = false;
				}
			}
		}

		return $val;
	}
}

?>

 

Discuz系统中 防止XSS漏洞攻击,过滤HTML危险标签属性的PHP函数

//屏蔽html
function checkhtml($html) {
	$html = stripslashes($html);
	if(!checkperm('allowhtml')) {

		preg_match_all("/<([^<]+)>/is", $html, $ms);

		$searchs[] = '<';
		$replaces[] = '<';
		$searchs[] = '>';
		$replaces[] = '>';

		if($ms[1]) {
			$allowtags = 'img|a|font|div|table|tbody|caption|tr|td|th|br
						|p|b|strong|i|u|em|span|ol|ul|li|blockquote
						|object|param|embed';//允许的标签
			$ms[1] = array_unique($ms[1]);
			foreach ($ms[1] as $value) {
				$searchs[] = "<".$value.">";
				$value = shtmlspecialchars($value);
				$value = str_replace(array('\','/*'), array('.','/.'), $value);
				$skipkeys = array(
						'onabort','onactivate','onafterprint','onafterupdate',
						'onbeforeactivate','onbeforecopy','onbeforecut',
						'onbeforedeactivate','onbeforeeditfocus','onbeforepaste',
						'onbeforeprint','onbeforeunload','onbeforeupdate',
						'onblur','onbounce','oncellchange','onchange',
						'onclick','oncontextmenu','oncontrolselect',
						'oncopy','oncut','ondataavailable',
						'ondatasetchanged','ondatasetcomplete','ondblclick',
						'ondeactivate','ondrag','ondragend',
						'ondragenter','ondragleave','ondragover',
						'ondragstart','ondrop','onerror','onerrorupdate',
						'onfilterchange','onfinish','onfocus','onfocusin',
						'onfocusout','onhelp','onkeydown','onkeypress',
						'onkeyup','onlayoutcomplete','onload',
						'onlosecapture','onmousedown','onmouseenter',
						'onmouseleave','onmousemove','onmouseout',
						'onmouseover','onmouseup','onmousewheel',
						'onmove','onmoveend','onmovestart','onpaste',
						'onpropertychange','onreadystatechange','onreset',
						'onresize','onresizeend','onresizestart',
						'onrowenter','onrowexit','onrowsdelete',
						'onrowsinserted','onscroll','onselect',
						'onselectionchange','onselectstart','onstart',
						'onstop','onsubmit','onunload','javascript',
						'script','eval','behaviour','expression',
						'style','class'
					);
				$skipstr = implode('|', $skipkeys);
				$value = preg_replace(array("/($skipstr)/i"), '.', $value);
				if(!preg_match("/^[/|s]?($allowtags)(s+|$)/is", $value)) {
					$value = '';
				}
				$replaces[] = empty($value)?'':"<".str_replace('"', '"', $value).">";
			}
		}
		$html = str_replace($searchs, $replaces, $html);
	}
	$html = addslashes($html);

	return $html;
}

 


php后门木马常用的函数大致上可分为四种类型:

1. 执行系统命令: system, passthru, shell_exec, exec, popen, proc_open
2. 代码执行与加密: eval, assert, call_user_func,base64_decode, gzinflate, gzuncompress, gzdecode, str_rot13
3. 文件包含与生成: require, require_once, include, include_once, file_get_contents, file_put_contents, fputs, fwrite
4. .htaccess: SetHandler, auto_prepend_file, auto_append_file

 


想找一个 关键词是“hellow word” 在哪些文件中有,我们用grep命令
grep –color -i -r -n “hellow word”  /data/www/

这样就能搜索出来 文件中包含关键词的文件

–color是关键词标红

-i是不区分大小写
-r是包含子目录的搜索
-d skip忽略子目录

可以用以上命令查找网站项目里的带有挂马的文件

 


.两个查后门的实用linux命令:
find /data/web/website/ -iname *.php -mtime -35 找出/data/web/website/目录下 35分钟前新建的php
find /data/web/website/ -name “*.php” | xargs grep “eval($_POST[” 找出/data/web/website/ 里面源码包含eval($_POST[的php文件

 

例如
注入漏洞eval(base64_decode
grep –color -i -r -n “eval”  /data/www/   找出来对比以前正常的代码,看是否正常。然后用stat查看这个木马文件的修改时间,最后去寻找WEB日志,找出木马从哪里进来的

 

五:

实用查找PHP木马命令:

查找PHP木马

# find ./ -name "*.php" |xargs egrep "phpspy|c99sh|milw0rm|eval\(gunerpress|eval\(base64_decoolcode|spider_bc"> /tmp/php.txt
# grep -r --include=*.php  '[^a-z]eval($_POST' . > /tmp/eval.txt
# grep -r --include=*.php  'file_put_contents(.*$_POST\[.*\]);' . > /tmp/file_put_contents.txt
# find ./ -name "*.php" -type f -print0 | xargs -0 egrep "(phpspy|c99sh|milw0rm|eval\(gzuncompress\(base64_decoolcode|eval\(base64_decoolcode|spider_bc|gzinflate)" | awk -F: '{print $1}' | sort | uniq

 

查找最近一天被修改的PHP文件

#   find -mtime -1 -type f -name \*.php

修改网站的权限

# find -type f -name \*.php -exec chmod 444 {} \;
# find ./ -type d -exec chmod 555{} \;

假设最后更新是10天前我们可以查找10天内生成的可以php文件:

find /var/www/ -name “*.php” -mtime -10

也可以通过关键字的形式查找 常见的木马常用代码函数 eval,shell_exec,passthru,popen,system

#find /var/www/ -name “*.php” |xargs grep “eval” |more
#find /var/www/ -name “*.php” |xargs grep “shell_exec” |more
#find /var/www/ -name “*.php” |xargs grep “passthru” |more

还有查看access.log 当然前提是你网站的所有php文件不是很多的情况下

一句话查找PHP木马

# find ./ -name “*.php” |xargs egrep “phpspy|c99sh|milw0rm|eval(gunerpress|eval(base64_decode|spider_bc”> /tmp/php.txt
# grep -r –include=*.php ’[^a-z]eval($_POST’ . > /tmp/eval.txt
# grep -r –include=*.php ’file_put_contents(.*$_POST[.*]);’ . > /tmp/file_put_contents.txt
# find ./ -name “*.php” -type f -print0 | xargs -0 egrep “(phpspy|c99sh|milw0rm|eval(gzuncompress(base64_decode|eval(base64_decode|spider_bc|gzinflate)” | awk -F: ‘{print $1}’ | sort | uniq

查找最近一天被修改的PHP文件
# find -mtime -1 -type f -name *.php

 

以下其实是多余的操作了其实,但是还是有值得看的地方

检查代码。

肯定不是一个文件一个文件的检查,Linxu有强悍的命令

grep ‘eval’ * -R 全盘搜索当前目录所有文件(包含子目录)中带有eval的文件,这条可以快速查找到被挂马的文件。

关于eval,请自行google一句话php代码。

2,查看日志。

不到这个时候不知道日志的可贵啊。

还是以grep命令为主。

思路:负责的站点是Linux,只开了2个端口,一个22和80,外部的执行命令是由从80端口进来,Selinux报httpd访问/boot文件,确认被挂马。而所有的命令执行必须POST提交给执行的文件。所以,查找日志中所有的POST记录。

cat access_log_20120823.log | grep ‘POST’ | grep -v ‘反向查找’ | less,通过grep -v排除正常post,egrep也支持正则,但是太复杂了,看懂不知道怎么运用。

(这里不建议用cat,用tail可以追加一个文件来看)

这可以防患于未然,防止不知道哪天又被人黑进来了。每天看一眼日志。

3,对于网页目录,只给apache用户rx权限,不要给w权限,目录设置要加上rx,不要给w,个别文件除外。所以,配合2使用,Linux下可以快速过滤刷选出来不规则的POST请求。

综合1,2其实就可以快速查找被黑的页面,被修改的文件替换干净的代码。

 

文章来源: http://blog.csdn.net/miltonzhong/article/details/9717179

汉化:http://doslin.com/learn-regular-expressions-in-about-55-minutes/

原文:http://qntm.org/files/re/re.html

 

翻译水平有限,如有谬误,欢迎评论斧正或者Pull Request

正则表达式(“regexes”)即增强查找/字符串替换操作。当在文本编辑器中编辑文字时,正则表达式经常用于:

  • 检查文本是否包含一个给定的模式
  • 查找任何匹配的模式
  • 从文本中拉取信息(比如截断)
  • 修改文本

和文本编辑器一样,绝大多数高级编程语言支持正则表达式。在本文中,“文本”仅仅是一个字符串变量,但是有效的操作却是一致的。某些编程语言(Perl,JavaScript)甚至为正则表达式提供专用的语法。

但是正则表达式是什么?

一个正则表达式仅仅为一个字符串。它没有长度限制,但是通常该字符串很短。下面看几个例子:

  • I had a \S+ day today
  • [A-Za-z0-9\-_]{3,16}
  • \d\d\d\d-\d\d-\d\d
  • v(\d+)(\.\d+)*
  • TotalMessages="(.*?)"
  • <[^<>]>

这个字符串实际上是一个极小的计算程序,并且正则表达式是一门语法小而简洁,领域特定的编程语言。牢记以下几点,它们不该在学习过程中让你感到惊讶:

  • 每个正则表达式都能分解成一串指令。“找到这个,再找到那个,然后找到其中一个…”
  • 一个正则表达式拥有输入(文本)和输出(模式匹配,和有些时候的自定义文本)。
  • 存在语法错误——不是每个字符串都是合法的正则表达式!
  • 语法有些怪异,也可以说是恐怖。
  • 一个正则表达式有时候可以被编译以便更快运行。

正则实现一直有着显著的改变。对于本文,我所关注的是那些几乎每个正则表达式都实现了的核心语法。

练习

获取一个支持正则的文本编辑器。我推荐Notepad++

下载一篇很长的散文故事比如Gutenberg出版社出版的H. G. Wells的《时光机器》然后打开它。

下载一部字典,比如这个,解压然后打开。

一切准备就绪,稍后开始练习。

提示: 正则表达式与文件通配符语法完全不兼容,比如*.xml

正则表达式基础语法

字面值(Literals)

正则表达式由只代表自身的字面值和代表特定含义的元字符组成。

这里也有一些例子。我会对元字符进行高亮。

  • I had a \S+ day today
  • [A-Za-z0-9-_]{3,16}
  • \d\d\d\d-\d\d-\d\d
  • v(\d+)(.\d+)*
  • TotalMessages="(.*?)"
  • <[^<>]*>

大部分字符,包括字母数字字符,会以字面值的形式出现。这意味着它们查找的是自身。比如,正则表达式cat代表“先找到c,接着找到a,最后找到t”。

目前为止感觉良好。这的确很像

  • 一个普通的查找对话框
  • Java中的String.indexOf()函数
  • PHP中的strpos()函数
  • 等等

提示:除非特别说明,正则表达式是大小写敏感的。然而,绝大多数实现都会提供一个标记来开启不区分大小写的功能。

句点(dot)

我们第一个元字符是句号(译者注:句点,英文句号),.。一个.表示匹配任何单个字符。下面这个正则表达式c.t代表“先找到c,接着找到任何单个字符,再找到t”。

在一段文本中,这个表达式将会找到catcotczt,甚至字面值为c.t的字符串(c,句点,t),但是不包括ct或者coot

任何元字符如果用一个反斜杆\进行转义就会变成字面值。所以上述的正则表达式c\.t就代表“先找到c,接着找到句号,再找到t”。

反斜杠是一个元字符,这意味着它也可以使用反斜杠转义。所以正则表达式c\\t代表“先找到c,接着找到反斜杆,再找到t”。

注意! 在一些实现中,. 会匹配任意字符除了 换行符。这意味着“换行符”在不同的实现中也会变化。 要查看你的文档。在这篇文章中, 我会确保. 会匹配任意字符。

在其它情况下, 通常会有一个标记来调整这种行为,那就是`DOTALL`或类似的标记

练习

使用你目前所学,在字典中使用正则表达式,匹配一个有两个z的单词,其中这两个z离得越远越好。

你最终的正则表达式应该是z.......z会匹配到四个单词: razzamatazzrazzamatazzeszwischenzug以及zwischenzugs

练习

《时光机器》这本书中,使用正则表达式来查找以介词收尾的句子。

你的正则表达式应该类似这样up\.

字符类(Character classes)

字符类是字符在方括号中的集合。表示“找到其中任意的字符”。

  • 正则表达式c[aeiou]t表示“找到c后跟一个元音字母,再找到t”。在一段文本中,将会匹配到catcetcitcotcut
  • 正则表达式[0123456789]表示找到一个数字
  • 正则表达式[a]a意义相同:“找到a

一些转义的例子:

  • \[a\]表示“找到一个左方括号紧跟着一个a,再跟着一个右方括号”。
  • [\[\]ab]表示“匹配一个左方括号或者右方括号或者a或者 b”。
  • [\\\[\]]表示“匹配一个反斜杆或者一个左方括号或者一个右方括号”。(呕!)

在字符类中顺序和重复字符并不重要。[dabaaabcc][abcd]一样。

重要的提示

在字符类内部的“规则”和在字符类内部的规则有所不同。一些字符在字符类内部扮演着元字符的角色,但在字符类外部则充当字面值。还有一些字符做着相反的事。一些字符在两种情形都为元字符,但在各自情形里代表不同的含义。

特别地, .表示“匹配任意字符”,但是[.]表示“匹配句点”。不能并为一谈。

练习

结合目前所学,在字典中,使用正则表达式查找有连续的元音和连续的辅音的单词。

[aeiou][aeiou][aeiou][aeiou][aeiou][aeiou]匹配到六元音单词euouae and euouaes,而可怕的[bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz]找到了有十个辅音的华丽的sulphhydryls。我们将会很快看到如何简化这些恐怖的表达式。

字符类区间(ranges)

你可以在字符类中使用连字符来表示一个字母或数字的区间

  • [b-f][bcdef]都表示“找到一个bcd或 ef”。
  • [A-Z][ABCDEFGHIJKLMNOPQRSTUVWXYZ]都表示“匹配大写字母”。
  • [1-9][123456789]都表示“匹配一个非零数字”。

连字符在字符类外部使用时并没有特别都含义。正则表达式a-z表示“找到一个a接着跟着一个连字符,然后匹配一个z”。

区间和单独都字符可能会共存于同一个字符类:

  • [0-9.,]表示“匹配一个数字或者一个句点或者一个逗号”。
  • [0-9a-fA-F]表示“匹配一个十六进制数”。
  • [a-zA-Z0-9\-]表示“匹配一个字母数字字符或连字符”。

虽然你可以尝试在区间内以非字母数字字符结束(比如abc[!-/]def),但这在其它实现中的语法不一定对。即使语法正确,但在这个区间内很难看出包含了哪个字符。请谨慎使用(我的意思是不要这么干)。

同样的,区间端点的范围应该一致。即使像[A-z]这种表达式在你选择的实现中合法,但它做的可能会与你想法用出入。(补充:可以有Za的区间范围)。

注意。 区间是字符的区间,不是数字的区间。正则表达式[1-31]表示“找到一个1或一个 2或一个3”,不是“找到一个从131的整数”。

练习

使用目前学习,编写一个查找以YYYY-MM-DD为格式的日期的正则表达式。

目前我们能写出来的是[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]。同样地,我们将能够很快简化这个式子。

字符类的否定(negation)

你可以通过在最开始的位置使用插入符号(译者注:^)来否定一个字符类。

  • [^a]表示“匹配除了a的任意字符”。
  • [^a-zA-Z0-9]表示“找到一个非字母数字字符”。
  • [\^abc]表示“找到一个插入符或者a或者b或者c”。
  • [^\^]表示“找到除了插入符外的任意字符”。(呕!)

练习

在字典中,使用正则表达式去找到这个规则的反例“i位于e 前面并且不出现在c的后面”。

字符类补充

正则表达式\d含义与[0-9]一致:“匹配一个数字”。(为了匹配一个反斜杆后跟一个d,可以使用\\d。)

\w的含义与[0-9A-Za-z_]一致:“匹配一个单词字符(译者注:字母或数字或下划线或汉字)”。

\s表示“匹配任意空白字符(空格,tab,回车或者换行)”。

此外,

  • \D[^0-9]:“匹配任意非数字的字符”。
  • \W[^0-9A-Za-z_]:“匹配任意非单词字符(译者注:匹配任意不是字母,数字,下划线,汉字的字符)”。
  • \S表示“匹配任意不是空白符的字符”。

这些字符类都很常见,你必须学会。

你可能也注意到了,句点.本质上是一个包含任意字符的字符类

许多实现提供了很多额外的字符类或标记,它们通过扩展现有的字符类来覆盖ASCII之外范围的字符。提示:Unicode包含更多的“数字字符”而不仅仅是09,这一点同样对于“单词”和“空格”也适用。注意你的文档所写。

练习

简化正则表达式[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]

\d\d\d\d-\d\d-\d\d.

乘法器(Multipliers)

你可以在一个字面值或者字符类后跟着一个大括号来使用乘法器

  • 正则表达式a{1}a,表示“匹配一个a”。
  • a{3}表示“找到一个a后再跟一个a,最后找到一个a”。
  • a{0}表示“匹配空字符”。就其本身而言,这似乎没有用处。如果你在任何一段文本中使用该表达式,你会在你刚开始搜索的端点处立即得到一个匹配。即使你的文本为空字符串结果也为真。
  • a\{2\}代表“找到一个a,跟着一个左大括号,接着跟匹配一个2,然后跟着一个右大括号”。
  • 在字符类中大括号没有特别的含义。[{}]代表“匹配一个左大括号或者一个右大括号”。

注意。 乘法器没有记忆。该正则表达式[abc]{2}表示“匹配a或者b或者c,接着匹配a或者b或者c。这跟“匹配aaabacbabbbccacbcc”相同。这跟“匹配aabbcc”含义不同

练习

简化以下正则表达式:

  • z.......z
  • \d\d\d\d-\d\d-\d\d
  • [aeiou][aeiou][aeiou][aeiou][aeiou][aeiou]
  • [bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz]

  • z.{7}z
  • \d{4}-\d{2}-\d{2}
  • [aeiou]{6}
  • [bcdfghjklmnpqrstvwxyz]{10}

乘法器区间

乘法器可能会有区间

  • x{4,4}x{4}一样。
  • colou{0,1}r表示“匹配colourcolor
  • a{3,5}表示“匹配aaaaaaaaaaaa”。

值得注意的是优先选择更长的匹配,因为乘法器是贪婪的。如果你输入的文本是I had an aaaaawful day,该正则表达式就会在aaaaawful中匹配到aaaaa。不会在第三个a后就停止匹配。

乘法器是贪婪的,但它不会忽略一个更好的匹配。如果你的输入文本为I had an aaawful daaaaay,之后这个正则表达式会在第一次的匹配中于aaawful找到aaa。只有在你说“给我找到另一个匹配”的时候,它才会继续搜索然后在daaaaay中找到aaaaa

乘法器区间可能是开区间:

  • a{1,}表示“在一列中找到一个或多个a”。然而你的乘法器将会是贪婪的。在找到第一个a后,它将会尽可能匹配到更多的a
  • .{0,}表示“匹配任何情形”。不管你的输入文本是什么——甚至为空——这个正则表达式都会匹配整个字符串然后返回给你。

练习

编写一个能匹配双引号字符串的正则表达式。同时该字符串可以拥有任意数量的字符。

用你已经学到的之时,修改上面的正则表达式,来找到了双引号字符串,但它们之间没有多余的双引号。

".{0,}",然后是"[^"]{0,}"

乘法器补充

?代表的含义与{0,1}相同。比如说,colour?r表示“匹配colourcolor”。

*等于{0,}。比如说,.*表示“匹配一切”,跟上面提到的一样。

+等于{1,}。比如说,\w+表示“匹配一个单词”。这里的“单词”是1个或多个“单词字符”的序列,就像_varAccountName1

这些乘法器都很常见,你必须掌握。还有:

  • \?\*\+表示“匹配一个问号,接着找到一个星号,然后跟着一个加号”。
  • [?*+]表示“找到一个问号或者一个星号或者一个加号”。

练习

简化下面的正则表达式:

  • ".{0,}""[^"]{0,}"
  • x?x?x?
  • y*y*
  • z+z+z+z+

  • ".*""[^"]*"
  • x{0,3}
  • y*
  • z{4,}

练习

编写一个表达式来查找非单词字符分隔的两个单词。如果改为三个单词或者六个单词又该怎么写?

\w+\W+\w+\w+\W+\w+\W+\w+\w+\W+\w+\W+\w+\W+\w+\W+\w+\W+\w+。当然,我们之后会学习如何简化它们。

惰性(Non-greed)

正则表达式".*"表示“找到一个双引号,接着找到尽可能多的字符,最后再找到一个双引号”。注意一下被.*匹配的内部字符,很可能包含多个双引号。这通常不是非常有用。

乘法器可通过追加问号来实现惰性。这里对优先顺序进行了反转:

  • \d{4,5}?表示“匹配\d\d\d\d\d\d\d\d\d”。其实跟\d{4}行为一致。
  • colou??r就是colou{0,1}?r,表示“找到colorcolour”。和colou?r行为一致。
  • ".*?"表示“匹配一个双引号,跟着一个尽可能少的字符,再跟着一个双引号”。这个不像上面两个例子,实际上很有用。

分支(Alternation)

你可以使用管道符号来实现匹配多种选择:

  • cat|dog表示“匹配catdog”。
  • red|blue|red||blue以及|red|blue都是同样的意思,“匹配redblue或空字符串”。
  • a|b|c[abc]一样。
  • cat|dog|\|表示“匹配catdog管道符号”。
  • [cat|dog]表示“找到acddgot或一个管道符号”。

练习

尽你所能简化下述正则表达式:

  • s|t|u|v|w
  • aa|ab|ba|bb
  • [abc]|[^abc]
  • [^ab]|[^bc]
  • [ab][ab][ab]?[ab]?

  • [s-w]
  • [ab]{2}
  • .
  • [^b]
  • [ab]{2,4}

练习

编写一个正则表达式匹配1到31(含)之间的整数。 记住,[1-31]不是正确答案。

有几种方法都可以做到这一点。我认为其中[1-9]|[12][0-9]|3[01]可读性最好。

组合(Grouping)

你可以使用圆括号来组合表达式:

  • 在一周中找到一天,使用(Mon|Tues|Wednes|Thurs|Fri|Satur|Sun)day
  • (\w*)ility等同于\w*ility。都表示“找到以ility结尾的单词”。为什么第一种形式更有用,后面会看到…
  • \(\)表示“匹配一个左圆括号后,再匹配一个右圆括号”。
  • [()]表示“匹配一个左圆括号或一个右圆括号”。

练习

《时光机器》这本书中,使用正则表达式来查找包裹在括号中的句子。接着,修改你的答案来查找没有被括号包裹的句子。

\(.*\),然后是\([^()]*\)

组合可能会包含空字符串:

  • (red|blue|)表示“匹配redblue空字符串”。
  • abc()def等同于abcdef

可能你会在组合中使用乘法器:

  • (red|blue)?等同于(red|blue|)
  • \w+(\s+\w+)*代表“找到一个或多个单词,它们以空格隔开”。

练习

简化\w+\W+\w+\W+\w+\w+\W+\w+\W+\w+\W+\w+\W+\w+\W+\w+

\w+(\W+\w+){2}\w+(\W+\w+){5}

单词边界(Word boundaries)

单词边界是一个单词字符和非单词字符之间的位置。记住,一个单词字符是\w,它是[0-9A-Za-z_],一个非单词字符是\W,也就是[^0-9A-Za-z_]

文本的开头和结尾总是当作单词边界。

输入的文本it's a cat有八个单词边界。如果我们在cat后追加一个空格,这里就会有九个单词边界。

  • 正则表达式\b表示“匹配一个单词边界”。
  • \b\w\w\w\b表示“匹配一个三个字母的单词”。
  • a\ba表示“找到a,跟着一个单词边界,接着找到b”。不管输入文本是什么,这个正则表达式永远都不会成功找到一个匹配。

单词边界不是字符。它们宽度为零.下面的正则表达式表示相同的含义:

  • (\bcat)\b
  • (\bcat\b)
  • \b(cat)\b
  • \b(cat\b)

练习

查找字典中最长的单词。

在一些试验和错误之后,这个正则表达式就是\b.{45,}\b,在字典中找到唯一一个结果:pneumonoultramicroscopicsilicovolcanoconiosis

行边界(Line boundaries)

每一块文本会分解成一个或多个行,用换行符分隔,像这样:

  • 换行
  • 换行
  • 换行

注意文本不是以换行符结束,而是以行结束。然而,任何行,包括最后一行,可以包含零个字符。

起始行位置是在一个换行符和下一行的第一个字符之间。与单词边界一样,在文本的开头也算作一个起始的行。

结束行位置是在行的最后一个字符和换行符之间。与单词边界一样,文本结束也算作行结束。

所以我们都细分为:

  • 起始行,行,结束行
  • 换行
  • 开始行,行,结束行
  • 换行
  • 换行
  • 开始行,行,结束行

在此基础上,有:

  • 正则表达式^表示“匹配开始行”。
  • 正则表达式$表示“匹配结束行”。
  • ^$表示“匹配空行”。
  • ^.*$将会匹配整个文本,因为换行符是一个字符,所以.会匹配它。为了匹配单行,要使用惰性乘法器,^.*?$
  • \^\$表示“匹配尖符号后跟着一个美元符号”。
  • [$]表示“匹配一个美元符”。然而[^]是非法单正则表达式。要记住的是尖符号在方括号中时有不同的特殊含义。把尖符号放在字符类中,这么用[\^]

像单词边界一样,行边界也不是字符。它们宽度为零。下面的正则表达式表示相同的含义:

  • (^cat)$
  • (^cat$)
  • ^(cat)$
  • ^(cat$)

练习

适用正则表达式查找《时光机器》中最长的一行。

Gutenberg出版社的这个版本一行最多有73个字符,使用该^.{73,}$表达式。许多行都是这个长度。

文本边界(Text boundaries)

很多实现提供一个标记,通过改变它来改变^$的含义。从“行开始”和“行结束”变成“文本开始”和“文本结束”。

其它的一些实现提供单独的元字符\A\z来达到这个目的。

捕获和替换

这里就是正则表达式开始变得异常强大的地方。

捕获组

你已经知道,括号是用来表示组。它们也可以用来捕获子串。如果正则表达式是一个很小的电脑程序,这个捕获组就是它的输出(的一部分)。

正则表达式(\w*)ility表示“找到一个以ility结束的单词”。捕获组1就是匹配了部分内容的\w*。举个例子,如果我们的文本包含单词accessibility,捕获组1就是accessib。如果我们的文本自身只包含ility,捕获组1就是空字符串。

你可以拥有多个捕获组,它们甚至可以嵌套使用。捕获组从左到右进行编号。只要计算左圆括号。

假设我们到正则表达式是(\w+) had a ((\w+) \w+)。如果我们的输入文本是I had a nice day,那么

  • 捕获组1是I
  • 捕获组2是nice day
  • 捕获组3是nice

在一些实现中,你可能可以访问捕获组0,即完整匹配:I had a nice day

是的,这确实意味着圆括号有些重复。一些实现就提供了一个独立语法来声明“非捕获组”,但是这个语法不符合标准,所以这里我们不涉及。

从一个成功返回的匹配中捕获组数量总是等于原来正则表达式中捕获组的数量。记住这一点,因为它可以帮助你理解一些令人困惑的情形。

正则表达式((cat)|dog)表示“匹配catdog”。这里总是存在两组捕获组。如果我们的输入文本是dog,那么捕获组1是dog,捕获组2是空字符串,因为另一个选择未被使用。

正则表达式a(\w)*表示“匹配一个以a开头的单词”。这里总是只有一个捕获组(译者注:除去捕获组0)

  • 如果输入文本是a,捕获组1是空字符串。
  • 如果输入文本是ad,捕获组1是d
  • 如果输入文本是avocado,捕获组1是v。然而,捕获组0会是整个单词,avocado

替换

一旦你用了正则表达式来查找字符串,你可以指定另一个字符串来替换它。第二个字符串时替换表达式。首先,就像:

  • 传统的替换对话框
  • Java的String.replace()函数
  • PHP的String.replace()函数
  • 等等

练习

使用r替换《时间机器》中所有的元音字母。确保使用正确的大小写!

分别使用正则表达式[aeiou][AEIOU],替换表达式rR

然而,你可以在你的替换表达式中引用捕获组。这是你可以在替换表达式唯一能的特殊的事,它是令人难以置信的强大,因为它意味着你不必完全销毁你刚刚发现的东西。

比方说,你尝试去用ISO 8691格式的日期(YYYY-MM-DD)去替换美式日期(MM/DD/YY)。

  • 通过正则表达式(\d\d)/(\d\d)/(\d\d)开始。注意这里有三个捕获组:月,日和两个数字表示的年。
  • 通过使用一个反斜杆和一个捕获组号来引用一个捕获组。所以,你的替换表达式为20\3-\1-\2
  • 如果我们的输入文本是03/04/05(表示 3月4号,2005年),那么
    • 捕获组1是03
    • 捕获组2是04
    • 捕获组3是05
    • 替换字符串为2005-03-04

你可以在替换表达式中多次引用捕获组。

  • 使用正则表达式([aeiou])和替换表达式\1\1来让元音翻倍。

在替换表达式中的反斜杆必须进行转义。举个例子,你有一些在计算机程序的字面值中使用的文本。那就意味着你需要在普通文本中的每个双引号或者反斜杆前放置一个反斜杆。

  • 正则表达式([\\"])中,捕获组1是双引号或者反斜杆。
  • 替换表达式\\\1中,一个字面值反斜杆后跟着一个匹配的双引号或者反斜杆。

后向引用(Back-references)

你可以在同样的表达式中引用同一个捕获组。这称为后向引用

举个例子,再次调用前面的表达式[abc]{2}表示“匹配aaabac or babbbccacbcc”。但是表达式([abc])\1表示“匹配aabbcc”。

练习

在字典中,找到出现两次相同字符串的最长的单词(比如papa, coco)。

\b(.{6,})\1\b匹配到chiquichiqui。如果我们不关心完整的单词,我们可以舍去单词边界断言,使用(.{7,})\1会找到countercountermeasurecountercountermeasures

结合正则表达式编程

一些具体的注意事项:

过度反斜线综合征(Excessive backslash syndrome)

在一些编程语言中,如Java,对于含有正则表达式的字符串没有提供特别的支持。字符串有自己的转义规则,这些规则与正则表达式的转义规则叠加,通常会导致反斜杆过多(overload)。比如(还是Java):

  • 为了匹配一个数字,正则表达式\d在源代码中变成String re = "\\d;"
  • 为了匹配一个双引号字符串,"[^"]*"变成String re = "\"[^\"]*\"";
  • 为了匹配一个反斜杆或者一个左方括号或者一个又方括号,正则表达式[\\\[\]]变成String re = "[\\\\\\[\\]]";
  • String re = "\\s";String re = "[ \t\r\n]";是一样的。注意不同的转义“优先级”。

在其它编程语言里,通过一个特殊标记来标识正则表达式,通常是正斜杆/。这里有一些JavaScript例子:

  • 为了匹配一个数字,\d变成var regExp = /\d/;
  • 匹配一个反斜杆或者一个左方括号或者一个右方括号,var regExp = /[\\\[\]]/;
  • var regExp = /\s/;var regExp = /[ \t\r\n]/;一样。
  • 当然,这意味着必须对正斜杠而不是双引号进行转义。匹配URL的前面部分:var regExp = /https?:\/\//;

基于这一点,我希望你明白为什么我对你反复提及反斜杆。

偏移量(Offsets)

在文本编辑器中,会在你光标所在处开始搜索。这个编辑器会向前开始搜索文字,然后停在第一个匹配的地方。下一次搜索会在第一次完成搜索的地方的右侧开始。

当编程的时候,文本的偏移量是必须的。这个偏移量会在代码中有明确的支持,或保存在包含文本的对象中(如Perl),或包含正则表达式的对象中(如JavaScirpt)。(在Java里,这是一个由正则表达式和复合对象的字符串。)在任何情况下,默认值为0,表示文本的开始。搜索后,偏移量会自动更新,或者作为输出的一部分返回。

无论什么情况,通常很容易去使用循环来解决这个问题。

注意。正则表达式匹配空字符串是完全可能的。 你可以立马实现的一个简单的例子是a{0}在这种情况下,新的偏移量等于旧偏移量,从而导致死循环。

一些实现可能保护你避免发生这些情况,但要查下对应的文档。

动态正则表达式

动态地构造一个正则表达式字符串时一定要小心。如果你使用的字符串不是固定的,那么它可能包含意想不到的元字符。这会导致语法错误。更糟糕的是,它可能产生一个语法正确,但行为不可预期的正则表达式。

有bug的Java代码:

  1. String sep = System.getProperty("file.separator");
  2. String[] directories = filePath.split(sep);

这个bug就是:String.split()认为sep是一个正则表达式。但是在Windows下,sep是由犯斜杆组成的字符串"\\".这不是一个语法正确的正则表达式。结果是:一个异常PatternSyntaxException

任何一个优秀的编程语言都提供了一种机制,用以转义在一个字符串中出现的所有元字符。在Java中,你可以这么做:

  1. String sep = System.getProperty("file.separator");
  2. String[] directories = filePath.split(Pattern.quote(sep));

循环内的正则表达式

把正则表达式字符串编译进一个正在运行的“程序”中是一个代价昂贵的操作。如果你能避免在循环内这么做的话能提高程序性能。

各类建议

输入验证

正则表达式能用于用户输入验证。但过于严格的验证会让用户感到难受。下面举几个例子:

支付卡号

我在网页上输入我的卡号如1234 5678 8765 4321。会被这个站点拒绝。因为它使用\d{16}来进行验证。

该正则表达式允许出现空格和连字符。

其实,为什么不直接去掉所有非数字字符,然后再进行验证?要做到这一点,使用正则表达式\D和空字符串来替换表达式。

练习

编写一个正则表达式,可以验证我的卡号而不用让我删去非数字字符。

\D*(\d\D*){16}是能多种实现中的一个方式。

名字

不要使用正则表达式来验证用户的名字。其实,不需要验证名字,你无能无力。

Falsehoods programmers believe about names提到了:

  • 名字不能包含空格。
  • 名字不能包含标点符号。
  • 名字只能使用ASCII字符。
  • 名字会被限制在任何特定的字符集。
  • 名字总是有像M字符那么长。
  • 人总是有且只有一个用的名字。
  • 人总是有且仅有一个中间名。
  • 人总是有且只有一个姓。

邮件地址

不要使用正则表达式来验证邮件地址。

首先,这很难保证正确无误。电子邮件地址确实符合一个正则表达式,但是这个表达式长又复杂地让人联想到世界末日。任何缩略都会可能产生遗漏(false negatives)。(你知道吗?电子邮件地址可以包含注释!)

其次,即使所提供的电子邮件地址符合正则表达式,但也并不能证明它的存在。验证电子邮件地址的唯一方法是发送电子邮件给它。

标记

在正式的应用中,不要使用正则表达式来解析HTML或XML。解析HTML/XML是

  1. 不可能使用简单的正则
  2. 一般来说很难
  3. 一个已解决了的问题。

不妨找一个已有的解析库来为你搞定这些工作。

这就是55分钟内容

总结:

  • 字面值:a b c d 1 2 3 4等等。
  • 字符类:. [abc] [a-z] \d \w \s
    • .表示“任何字符”
    • \d表示“一个数字”
    • \w表示“一个单词字符”,[0-9A-Za-z_]
    • \s表示“一个空格,tab,回车或一个换行符”
    • 否定字符类:[^abc] \D \W \S
  • 乘法器:{4} {3,16} {1,} ? * +
    • ?表示“没有或一个”
    • *表示“没有或多个”
    • +表示“一个或多个”
    • 乘法器是贪婪的除非你在之后使用?
  • 分支和组合:(Septem|Octo|Novem|Decem)ber
  • 词、行和文本边界:\b ^ $ \A \z
  • 反向捕获组:\1 \2 \3等等。(在替换表达式和匹配表达式中同时生效)
  • 元字符列表:. \ [ ] { } ? * + | ( ) ^ $
  • 字符类中使用到元字符列表:[ ] \ - ^
  • 你总是可以使用反斜杆对元字符进行转义:\

感谢阅读

正则表达式无处不在,令人难以置信的有用。那些在编辑文本和写电脑程序方面将花费大量时间的人们应该学会如何使用它们。 到目前为止,我们只接触了冰山一角。

练习

继续阅读你选择的正则表达式实现的对应文档。我保证在我们这里所讨论的部分之外还有更多的特性并未涉及。

 

php5.2.x php5.3.x php5.4.x php5.5.x php5.6.x 对比详解

截至目前(2014.2), PHP 的最新稳定版本是 PHP5.5, 但有差不多一半的用户仍在使用已经不在维护 [注] 的 PHP5.2, 其余的一半用户在使用 PHP5.3 [注].
因为 PHP 那“集百家之长”的蛋疼语法,加上社区氛围不好,很多人对新版本,新特征并无兴趣。
本文将会介绍自 PHP5.2 起,直至 PHP5.6 中增加的新特征。

  • PHP5.2 以前:autoload, PDO 和 MySQLi, 类型约束
  • PHP5.2:JSON 支持
  • PHP5.3:弃用的功能,匿名函数,新增魔术方法,命名空间,后期静态绑定,Heredoc 和 Nowdoc, const, 三元运算符,Phar
  • PHP5.4:Short Open Tag, 数组简写形式,Traits, 内置 Web 服务器,细节修改
  • PHP5.5:yield, list() 用于 foreach, 细节修改
  • PHP5.6: 常量增强,可变函数参数,命名空间增强

注:已于2011年1月停止支持: http://www.php.net/eol.php
注:http://w3techs.com/technologies/details/pl-php/5/all

PHP5.2以前

(2006前)
顺便介绍一下 PHP5.2 已经出现但值得介绍的特征。

autoload

大家可能都知道 __autoload() 函数,如果定义了该函数,那么当在代码中使用一个未定义的类的时候,该函数就会被调用,你可以在该函数中加载相应的类实现文件,如:

function __autoload($classname)
{
    require_once("{$classname}.php")
}

但该函数已经不被建议使用,原因是一个项目中仅能有一个这样的 __autoload() 函数,因为 PHP 不允许函数重名。但当你使用一些类库的时候,难免会出现多个 autoload 函数的需要,于是 spl_autoload_register() 取而代之:

spl_autoload_register(function($classname)
{
    require_once("{$classname}.php")
});

spl_autoload_register() 会将一个函数注册到 autoload 函数列表中,当出现未定义的类的时候,SPL [注] 会按照注册的倒序逐个调用被注册的 autoload 函数,这意味着你可以使用 spl_autoload_register() 注册多个 autoload 函数.

注:SPL: Standard PHP Library, 标准 PHP 库, 被设计用来解决一些经典问题(如数据结构).

PDO 和 MySQLi

即 PHP Data Object, PHP 数据对象,这是 PHP 的新式数据库访问接口。

按照传统的风格,访问 MySQL 数据库应该是这样子:

// 连接到服务器,选择数据库
$conn = mysql_connect("localhost", "user", "password");
mysql_select_db("database");

// 执行 SQL 查询
$type = $_POST['type'];
$sql = "SELECT * FROM `table` WHERE `type` = {$type}";
$result = mysql_query($sql);

// 打印结果
while($row = mysql_fetch_array($result, MYSQL_ASSOC))
{
    foreach($row as $k => $v)
        print "{$k}: {$v}\n";
}

// 释放结果集,关闭连接
mysql_free_result($result);
mysql_close($conn);

为了能够让代码实现数据库无关,即一段代码同时适用于多种数据库(例如以上代码仅仅适用于MySQL),PHP 官方设计了 PDO.
除此之外,PDO 还提供了更多功能,比如:

  • 面向对象风格的接口
  • SQL预编译(prepare), 占位符语法
  • 更高的执行效率,作为官方推荐,有特别的性能优化
  • 支持大部分SQL数据库,更换数据库无需改动代码

上面的代码用 PDO 实现将会是这样:

// 连接到数据库
$conn = new PDO("mysql:host=localhost;dbname=database", "user", "password");

// 预编译SQL, 绑定参数
$query = $conn->prepare("SELECT * FROM `table` WHERE `type` = :type");
$query->bindParam("type", $_POST['type']);

// 执行查询并打印结果
foreach($query->execute() as $row)
{
    foreach($row as $k => $v)
        print "{$k}: {$v}\n";
}

PDO 是官方推荐的,更为通用的数据库访问方式,如果你没有特殊需求,那么你最好学习和使用 PDO.
但如果你需要使用 MySQL 所特有的高级功能,那么你可能需要尝试一下 MySQLi, 因为 PDO 为了能够同时在多种数据库上使用,不会包含那些 MySQL 独有的功能。

MySQLi 是 MySQL 的增强接口,同时提供面向过程和面向对象接口,也是目前推荐的 MySQL 驱动,旧的C风格 MySQL 接口将会在今后被默认关闭。
MySQLi 的用法和以上两段代码相比,没有太多新概念,在此不再给出示例,可以参见 PHP 官网文档 [注]。

注:http://www.php.net/manual/en/mysqli.quickstart.php

类型约束

通过类型约束可以限制参数的类型,不过这一机制并不完善,目前仅适用于类和 callable(可执行类型) 以及 array(数组), 不适用于 string 和 int.

// 限制第一个参数为 MyClass, 第二个参数为可执行类型,第三个参数为数组
function MyFunction(MyClass $a, callable $b, array $c)
{
    // ...
}

PHP5.2

(2006-2011)

JSON 支持

包括 json_encode(), json_decode() 等函数,JSON 算是在 Web 领域非常常用的数据交换格式,可以被 JS 直接支持,JSON 实际上是 JS 语法的一部分。
JSON 系列函数,可以将 PHP 中的数组结构与 JSON 字符串进行转换:

$array = ["key" => "value", "array" => [1, 2, 3, 4]];
$json = json_encode($array);
echo "{$json}\n";

$object = json_decode($json);
print_r($object);

输出:

{"key":"value","array":[1,2,3,4]}
stdClass Object
(
    [key] => value
    [array] => Array
        (
            [0] => 1
            [1] => 2
            [2] => 3
            [3] => 4
        )
)

值得注意的是 json_decode() 默认会返回一个对象而非数组,如果需要返回数组需要将第二个参数设置为 true.

PHP5.3

(2009-2012)

PHP5.3 算是一个非常大的更新,新增了大量新特征,同时也做了一些不向下兼容的修改。

弃用的功能

以下几个功能被弃用,若在配置文件中启用,则 PHP 会在运行时发出警告。

Register Globals

这是 php.ini 中的一个选项(register_globals), 开启后会将所有表单变量($_GET和$_POST)注册为全局变量.
看下面的例子:

if(isAuth())
    $authorized = true;
if($authorized)
    include("page.php");

这段代码在通过验证时,将 $authorized 设置为 true. 然后根据 $authorized 的值来决定是否显示页面.

但由于并没有事先把 $authorized 初始化为 false, 当 register_globals 打开时,可能访问 /auth.php?authorized=1 来定义该变量值,绕过身份验证。

该特征属于历史遗留问题,在 PHP4.2 中被默认关闭,在 PHP5.4 中被移除。

Magic Quotes

对应 php.ini 中的选项 magic_quotes_gpc, 这个特征同样属于历史遗留问题,已经在 PHP5.4 中移除。

该特征会将所有用户输入进行转义,这看上去不错,在第一章我们提到过要对用户输入进行转义。
但是 PHP 并不知道哪些输入会进入 SQL , 哪些输入会进入 Shell, 哪些输入会被显示为 HTML, 所以很多时候这种转义会引起混乱。

Safe Mode

很多虚拟主机提供商使用 Safe Mode 来隔离多个用户,但 Safe Mode 存在诸多问题,例如某些扩展并不按照 Safe Mode 来进行权限控制。
PHP官方推荐使用操作系统的机制来进行权限隔离,让Web服务器以不同的用户权限来运行PHP解释器,请参见第一章中的最小权限原则.

匿名函数

也叫闭包(Closures), 经常被用来临时性地创建一个无名函数,用于回调函数等用途。

$func = function($arg)
{
    print $arg;
};

$func("Hello World");

以上代码定义了一个匿名函数,并赋值给了 $func.
可以看到定义匿名函数依旧使用 function 关键字,只不过省略了函数名,直接是参数列表。

然后我们又调用了 $func 所储存的匿名函数。

匿名函数还可以用 use 关键字来捕捉外部变量:

function arrayPlus($array, $num)
{
    array_walk($array, function(&$v) use($num){
        $v += $num;
    });
}

上面的代码定义了一个 arrayPlus() 函数(这不是匿名函数), 它会将一个数组($array)中的每一项,加上一个指定的数字($num).

在 arrayPlus() 的实现中,我们使用了 array_walk() 函数,它会为一个数组的每一项执行一个回调函数,即我们定义的匿名函数。
在匿名函数的参数列表后,我们用 use 关键字将匿名函数外的 $num 捕捉到了函数内,以便知道到底应该加上多少。

魔术方法:__invoke(), __callStatic()

PHP 的面向对象体系中,提供了若干“魔术方法”,用于实现类似其他语言中的“重载”,如在访问不存在的属性、方法时触发某个魔术方法。

随着匿名函数的加入,PHP 引入了一个新的魔术方法 __invoke().
该魔术方法会在将一个对象作为函数调用时被调用:

class A
{
    public function __invoke($str)
    {
        print "A::__invoke(): {$str}";
    }
}

$a = new A;
$a("Hello World");

输出毫无疑问是:

A::__invoke(): Hello World

__callStatic() 则会在调用一个不存在的静态方法时被调用。

命名空间

PHP的命名空间有着前无古人后无来者的无比蛋疼的语法:

<?php
// 命名空间的分隔符是反斜杠,该声明语句必须在文件第一行。
// 命名空间中可以包含任意代码,但只有 **类, 函数, 常量** 受命名空间影响。
namespace XXOO\Test;

// 该类的完整限定名是 \XXOO\Test\A , 其中第一个反斜杠表示全局命名空间。
class A{}

// 你还可以在已经文件中定义第二个命名空间,接下来的代码将都位于 \Other\Test2 .
namespace Other\Test2;

// 实例化来自其他命名空间的对象:
$a = new \XXOO\Test\A;
class B{}

// 你还可以用花括号定义第三个命名空间
namespace Other {
    // 实例化来自子命名空间的对象:
    $b = new Test2\B;

    // 导入来自其他命名空间的名称,并重命名,
    // 注意只能导入类,不能用于函数和常量。
    use \XXOO\Test\A as ClassA
}

更多有关命名空间的语法介绍请参见官网 [注].

命名空间时常和 autoload 一同使用,用于自动加载类实现文件:

spl_autoload_register(
    function ($class) {
        spl_autoload(str_replace("\\", "/", $class));
    }
);

当你实例化一个类 \XXOO\Test\A 的时候,这个类的完整限定名会被传递给 autoload 函数,autoload 函数将类名中的命名空间分隔符(反斜杠)替换为斜杠,并包含对应文件。
这样可以实现类定义文件分级储存,按需自动加载。

注:http://www.php.net/manual/zh/language.namespaces.php

后期静态绑定

PHP 的 OPP 机制,具有继承和类似虚函数的功能,例如如下的代码:

class A
{
    public function callFuncXXOO()
    {
        print $this->funcXXOO();
    }

    public function funcXXOO()
    {
        return "A::funcXXOO()";
    }
}

class B extends A
{
    public function funcXXOO()
    {
        return "B::funcXXOO";
    }
}

$b = new B;
$b->callFuncXXOO();

输出是:

B::funcXXOO

可以看到,当在 A 中使用 $this->funcXXOO() 时,体现了“虚函数”的机制,实际调用的是 B::funcXXOO().
然而如果将所有函数都改为静态函数:

class A
{
    static public function callFuncXXOO()
    {
        print self::funcXXOO();
    }

    static public function funcXXOO()
    {
        return "A::funcXXOO()";
    }
}

class B extends A
{
    static public function funcXXOO()
    {
        return "B::funcXXOO";
    }
}

$b = new B;
$b->callFuncXXOO();

情况就没这么乐观了,输出是:

A::funcXXOO()

这是因为 self 的语义本来就是“当前类”,所以 PHP5.3 给 static 关键字赋予了一个新功能:后期静态绑定:

class A
{
    static public function callFuncXXOO()
    {
        print static::funcXXOO();
    }

    // ...
}

// ...

这样就会像预期一样输出了:

B::funcXXOO

Heredoc 和 Nowdoc

PHP5.3 对 Heredoc 以及 Nowdoc 进行了一些改进,它们都用于在 PHP 代码中嵌入大段字符串。

Heredoc 的行为类似于一个双引号字符串:

$name = "MyName";
echo <<< TEXT
My name is "{$name}".
TEXT;

Heredoc 以三个左尖括号开始,后面跟一个标识符(TEXT), 直到一个同样的顶格的标识符(不能缩进)结束。
就像双引号字符串一样,其中可以嵌入变量。

Heredoc 还可以用于函数参数,以及类成员初始化:

var_dump(<<<EOD
Hello World
EOD
);

class A
{
    const xx = <<< EOD
Hello World
EOD;

    public $oo = <<< EOD
Hello World
EOD;
}

Nowdoc 的行为像一个单引号字符串,不能在其中嵌入变量,和 Heredoc 唯一的区别就是,三个左尖括号后的标识符要以单引号括起来:

$name = "MyName";
echo <<< 'TEXT'
My name is "{$name}".
TEXT;

输出:

My name is "{$name}".

用 const 定义常量

PHP5.3 起同时支持在全局命名空间和类中使用 const 定义常量。

旧式风格:

define("XOOO", "Value");

新式风格:

const XXOO = "Value";

const 形式仅适用于常量,不适用于运行时才能求值的表达式:

// 正确
const XXOO = 1234;
// 错误
const XXOO = 2 * 617;

三元运算符简写形式

旧式风格:

echo $a ? $a : "No Value";

可简写成:

echo $a ?: "No Value";

即如果省略三元运算符的第二个部分,会默认用第一个部分代替。

Phar

Phar即PHP Archive, 起初只是Pear中的一个库而已,后来在PHP5.3被重新编写成C扩展并内置到 PHP 中。
Phar用来将多个 .php 脚本打包(也可以打包其他文件)成一个 .phar 的压缩文件(通常是ZIP格式)。
目的在于模仿 Java 的 .jar, 不对,目的是为了让发布PHP应用程序更加方便。同时还提供了数字签名验证等功能。

.phar 文件可以像 .php 文件一样,被PHP引擎解释执行,同时你还可以写出这样的代码来包含(require) .phar 中的代码:

require("xxoo.phar");
require("phar://xxoo.phar/xo/ox.php");

更多信息请参见官网 [注].

注:http://www.php.net/manual/zh/phar.using.intro.php

PHP5.4

(2012-2013)

Short Open Tag

Short Open Tag 自 PHP5.4 起总是可用。
在这里集中讲一下有关 PHP 起止标签的问题。即:

<?php
// Code...
?>

通常就是上面的形式,除此之外还有一种简写形式:

<? /* Code... */ ?>

还可以把

<?php echo $xxoo;?>

简写成:

<?= $xxoo;?>

这种简写形式被称为 Short Open Tag, 在 PHP5.3 起被默认开启,在 PHP5.4 起总是可用。
使用这种简写形式在 HTML 中嵌入 PHP 变量将会非常方便。

对于纯 PHP 文件(如类实现文件), PHP 官方建议顶格写起始标记,同时 省略 结束标记。
这样可以确保整个 PHP 文件都是 PHP 代码,没有任何输出,否则当你包含该文件后,设置 Header 和 Cookie 时会遇到一些麻烦 [注].

注:Header 和 Cookie 必须在输出任何内容之前被发送。

数组简写形式

这是非常方便的一项特征!

// 原来的数组写法
$arr = array("key" => "value", "key2" => "value2");
// 简写形式
$arr = ["key" => "value", "key2" => "value2"];

Traits

所谓Traits就是“构件”,是用来替代继承的一种机制。PHP中无法进行多重继承,但一个类可以包含多个Traits.

// Traits不能被单独实例化,只能被类所包含
trait SayWorld
{
    public function sayHello()
    {
        echo 'World!';
    }
}

class MyHelloWorld
{
    // 将SayWorld中的成员包含进来
    use SayWorld;
}

$xxoo = new MyHelloWorld();
// sayHello() 函数是来自 SayWorld 构件的
$xxoo->sayHello();

Traits还有很多神奇的功能,比如包含多个Traits, 解决冲突,修改访问权限,为函数设置别名等等。
Traits中也同样可以包含Traits. 篇幅有限不能逐个举例,详情参见官网 [注].

注:http://www.php.net/manual/zh/language.oop5.traits.php

内置 Web 服务器

PHP从5.4开始内置一个轻量级的Web服务器,不支持并发,定位是用于开发和调试环境。

在开发环境使用它的确非常方便。

php -S localhost:8000

这样就在当前目录建立起了一个Web服务器,你可以通过 http://localhost:8000/ 来访问。
其中localhost是监听的ip,8000是监听的端口,可以自行修改。

很多应用中,都会进行URL重写,所以PHP提供了一个设置路由脚本的功能:

php -S localhost:8000 index.php

这样一来,所有的请求都会由index.php来处理。

你还可以使用 XDebug 来进行断点调试。

细节修改

PHP5.4 新增了动态访问静态方法的方式:

$func = "funcXXOO";
A::{$func}();

新增在实例化时访问类成员的特征:

(new MyClass)->xxoo();

新增支持对函数返回数组的成员访问解析(这种写法在之前版本是会报错的):

print func()[0];

PHP5.5

(2013起)

yield

yield关键字用于当函数需要返回一个迭代器的时候, 逐个返回值。

function number10()
{
    for($i = 1; $i <= 10; $i += 1)
        yield $i;
}

该函数的返回值是一个数组:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

list() 用于 foreach

可以用 list() 在 foreach 中解析嵌套的数组:

$array = [
    [1, 2, 3],
    [4, 5, 6],
];

foreach ($array as list($a, $b, $c))
    echo "{$a} {$b} {$c}\n";

结果:

1 2 3
4 5 6

细节修改

不推荐使用 mysql 函数,推荐使用 PDO 或 MySQLi, 参见前文。
不再支持Windows XP.

可用 MyClass::class 取到一个类的完整限定名(包括命名空间)。

empty() 支持表达式作为参数。

try-catch 结构新增 finally 块。

PHP5.6

更好的常量

定义常量时允许使用之前定义的常量进行计算:

const A = 2;
const B = A + 1;

class C
{
    const STR = "hello";
    const STR2 = self::STR + ", world";
}

允许常量作为函数参数默认值:

function func($arg = C::STR2)

更好的可变函数参数

用于代替 func_get_args()

function add(...$args)
{
    $result = 0;
    foreach($args as $arg)
        $result += $arg;
    return $result;
}

同时可以在调用函数时,把数组展开为函数参数:

$arr = [2, 3];
add(1, ...$arr);
// 结果为 6

命名空间

命名空间支持常量和函数:

namespace Name\Space {
    const FOO = 42;
    function f() { echo __FUNCTION__."\n"; }
}

namespace {
    use const Name\Space\FOO;
    use function Name\Space\f;

    echo FOO."\n";
    f();
}

任意环境下调试php,debug php

在不管php.ini配置的情况下开启php调试,php debug.

在你需要调试的php文件首行中加入.

ini_set('display_errors',1); //错误信息
ini_set('display_startup_errors',1); //php启动错误信息
error_reporting(-1); //打印出所有的 错误信息
ini_set('error_log', dirname(__FILE__) . '/error_log.txt'); //将出错信息输出到一个文本文件

开发环境调试php,debug php php.ini 配置

;显示错误信息
display_errors = On
;显示php开始错误信息
display_startup_errors = On
;日志记录错误信息
log_errors = On
log_errors = On
error_log = "/usr/local/php/var/log/php-fpm.log"
error_reporting=E_ALL&amp;~E_NOTICE

php-fom conf配置

[global]
error_log = /usr/local/php/var/log/php-fpm.log
log_level = notice

[www]
catch_workers_output = yes

参考文档:

errorfunc.configuration.php

该脚本实现了MikroTik RouterOS 分时段封禁 封闭 禁止特定网站访问,提供一个思路,如法炮制即可。

 

# jun/11/2013 17:27:05 by RouterOS 5.20
#  www.iamle.com wwek
#  qq 121901634
# time kill web

/ip firewall filter
add action=drop chain=forward comment="kill web" disabled=no packet-mark=\
    "Kill Web mark packet"

/ip firewall mangle
add action=mark-connection chain=forward comment="web taobao.com" content=\
    taobao.com disabled=no new-connection-mark="Kill Web Conn" passthrough=\
    yes
add action=mark-connection chain=forward comment="web a.tbcdn.cn" content=\
    tbcdn.cn disabled=no new-connection-mark="Kill Web Conn" passthrough=yes
add action=mark-connection chain=forward comment="web jd.com" content=jd.com \
    disabled=no new-connection-mark="Kill Web Conn" passthrough=yes
add action=mark-packet chain=forward comment="Kill Web mark packet" \
    connection-mark="Kill Web Conn" disabled=no new-packet-mark=\
    "Kill Web mark packet" passthrough=no

/system scheduler
add disabled=no interval=3m30s name="kill web" on-event=":global nowtime  [:pi\
    ck [/system clock get time] 0 2]\r\
    \n:if (\$nowtime >= 08 && \$nowtime <= 12) do={\r\
    \n:log info [/ip firewall filter enable [/ip firewall filter find comment=\
    \"kill web\"]]\r\
    \n:log info (\"\A1\BC\BD\FB\D6\B9\CD\F8\D5\BE\D6\D0 8:00 -- 12:00\A1\BD\A3\
    \AC\CF\D6\D4\DA\CA\B1\BC\E4\A3\BA\".\$nowtime.\"\B5\E3\")}\r\
    \n:if (\$nowtime >= 12 && \$nowtime < 13) do={\r\
    \n:log info [/ip firewall filter disable [/ip firewall filter find comment\
    =\"kill web\"]]\r\
    \n:log info (\"\A1\BC\CD\F8\D5\BE\B7\C5\D0\D0\D6\D0 12:00 -- 13:00\A1\BD\
    \A3\AC\CF\D6\D4\DA\CA\B1\BC\E4\A3\BA\".\$nowtime.\"\B5\E3\")}\r\
    \n:if (\$nowtime >= 13 && \$nowtime < 17) do={\r\
    \n:log info [/ip firewall filter enable [/ip firewall filter find comment=\
    \"kill web\"]]\r\
    \n:log info (\"\A1\BC\BD\FB\D6\B9\CD\F8\D5\BE\D6\D0 13:00 -- 17:00\A1\BD\
    \A3\AC\CF\D6\D4\DA\CA\B1\BC\E4\A3\BA\".\$nowtime.\"\B5\E3\")}\r\
    \n:if (\$nowtime >=17  || \$nowtime < 08) do={\r\
    \n:log info [/ip firewall filter disable [/ip firewall filter find comment\
    =\"kill web\"]]\r\
    \n:log info (\"\A1\BC\CD\F8\D5\BE\B7\C5\D0\D0\D6\D0 17:00 -- 08:00\A1\BD\
    \A3\AC\CF\D6\D4\DA\CA\B1\BC\E4\A3\BA\".\$nowtime.\"\B5\E3\")}\r\
    \n\r\
    \n:log info (\"\A1\BC\CD\F8\D5\BE\B7\C3\CE\CA\BF\D8\D6\C6\BD\C5\B1\BE\D6\
    \B4\D0\D0\CD\EA\B1\CF\A1\BD\A3\AC\CF\D6\D4\DA\CA\B1\BC\E4\A3\BA\".\$nowtim\
    e.\"\B5\E3\")\r\
    \n" policy=\
    ftp,reboot,read,write,policy,test,winbox,password,sniff,sensitive,api \
    start-date=jun/11/2013 start-time=08:46:55