漫谈引号及转义-与正则表达式、SQL结合

2010年1月31日 由 Hackfan | 有 1,074 人次阅读 留言 »

高级语言已能灵活、优雅地处理字符串操作了,在PHP中,对变量赋以字符串十分简单:

$str = "Hello, World!";
$str = 'I am very happy.';

单引号与双引号的区别,是许多PHPer晶晶乐道的话题。除了功能区别外,运行速度也是大家讨论的焦点。先说两者在功能上的区别。

单引号是标注字符串最简单的方法,单引号的转义规则极其简单:只需对’和\进行转义:

$str = 'My document\'s path is D:\\documents\\';
//$str的内容为:My document's path is D:\documents\

与单引号不同,双引号不仅支持更多的转义符号,还支持引用变量内容:

$path = "D:\\documents\\";
$str = "My document\'s path is $path";
//$str的内容为:My document's path is D:\documents\

至于两者的速度,从逻辑上推理,单引号的功能少、规则简单,应该比双引号的速度快。风雪之隅也曾撰文《PHP的单引号和双引号》,从Opcodes的层面,得出了“单引号比双引号快”这个结论。但我之前针对纯字符串(无转义、无变量引用)做过测试,得出的结论竟然是“双引号比单引号快”。大家也可以写一下代码测试一下单双引号在不同情况下的速度。

很多初学者在接触纯PHP编程时,在转义的处理上,极少出错。但当程序中有正则表达式、SQL语句出现时,需要将相关的表达式、语句用字符串传递给相关函数,则会出现一些问题。下面用2个例子来说明。

例1、正则表达式

某网站的Ajax接口返回一段包含HTML代码的JSON字符串,如下:

"<table>\n\t<tr>\n\t\t<td width=\"100\">Hello, World!<\/td>\n\t<\/tr>\n<\/table>\n"

现在我们通过PHP的file_get_contents()函数,将以上字符串装进了变量$c,现在要提取其中td标签内的内容,请用正则表达式来完成。

首先考虑正则表达式如何写:

/<td width="100">(.*?)<\/td>/sm

其中的“\/”是对正则表达式中的“\”进行转义。现在要将如上的正则表达式,装进PHP的变量内。有2个方法装:

1、间接法

间接法可免去将正则表达式翻译成PHP字符串的麻烦,将正则表达式装入文本文件、数据库、memcache等容器,在PHP程序中,读取该值,传入变量,再将此变量传递给preg_match函数,即可。

2、直接法

直接在程序中包含正则表达式,有2种办法:
a、单双引号法
单双引号法,就是将正则表达式,根据单双引号的转义规则,装入单双引号,然后赋值给变量。
为了避免双引号复杂的转义规则与正则的转义规则相混淆,个人建议所有的正则表达式,都使用单引号。上述正则表达式可通过如下方式赋值:

$regex = '/<td width="100">(.*?)<\/td>/sm';

b、Heredoc或Nowdoc法
PHP支持Heredoc和Nowdoc的定界符赋值。以上正则表达式可如下赋值:

$regex = <<<EOT
/<td width="100">(.*?)<\/td>/sm
EOT;

Heredoc的好处在于,在界定符内,可输入任何你想要的字符,不用担心转义。当然,Heredoc会自动解析变量,而Nowdoc则不会。要想使用Nowdoc,首先确定PHP的版本大于等于5.3.0,然后通过以下代码赋值:

$regex = <<<'EOT'
/<td width="100">(.*?)<\/td>/sm
EOT;

c、总结
无论是通过单双引号法,还是通过Heredoc、Nowdoc法,都是跟PHP在打交道,我们的目的无非是让PHP得到我们最终想传递给相关函数的正则表达式。所以,请确保你的正则表达式是正确的。如果你的正则表达式远比上述的复杂,甚至涉及到了变量,则需要对相关变量,做escape quote的操作。否则,将会引发错误甚至是程序漏洞。

举例说明:
我们的网站规定,用户的Username中,比如包含其Firstname。如我的Firstname是Gerry,那么我在该网站注册时,Username可以是Gerry_Hu、Gerry123,但不能是Hackfan。

相关程序:

$firstname = $user->firstname;
$regex = '/'.$firstname.'/gi';
if(preg_match($regex, $username, $match))
{
    //OK
}

以上代码,存在“正则注入”的漏洞。SQL注入漏洞大家都很熟悉,正则表达式,如果在编程时未注意,也会产生注入漏洞,危害系统安全。
如果我将firstname构造为(.*),那么任何username都会通过审查。所以,我们要如下处理:

$regex = '/'.preg_quote($firstname, '/').'/gi';

preg_quote将需要匹配的字符串值,进行了转义。preg系的preg_quote函数就好比是mysql系的mysql_escape_string函数。

例2、SQL

讲完了复杂的正则表达式与单双引号、转义的关系,SQL相比就显得简单多了。

由于在ANSI-SQL标准中,字符串使用单引号,所以我们的SQL语句,也尽量符合规范标准。以下是从MySQL数据库中,取出某用户资料的SQL:

SELECT * FROM `user` WHERE `username` LIKE 'hackfan';

其中’hackfan’是字符串。将上述SQL语句放入PHP程序:

$sql = "SELECT * FROM `user` WHERE `username` LIKE 'hackfan';";

由于SQL语句中,很少会涉及到转义的引用,且单引号在SQL中出现的频率很高,因此,个人建议凡是涉及SQL语句的,在PHP中,全部用双引号进行赋值。

纠结SQL与PHP的单双引号应用的朋友不多,只是还有不少朋友对与SQL注入不够重视,屡屡留下注入漏洞。

总结

本文介绍了PHP的字符串赋值方法,以及单双引号的转义规则。并举例说明PHP字符串与正则表达式、SQL结合时,所可能出现的问题,以及解决方法。

1、尽量使用单引号进行字符串操作——避免双引号复杂的转义规则。

2、尽量在正则表达式应用时,使用单引号——避免双引号的转义规则与正则表达式的转义规则相混淆。

3、尽量在SQL应用时,使用双引号——SQL中经常出现单引号,但不常出现转义。

如果你认为我的博客还不错,请订阅我的博客。

标签:, , , , , ,

相关日志

留下评论