157
7
XHP
XHP
(类似
XHTML
命名方式)是
Hack
的一个特性,它允许编程人员借助于嵌入式
XML
类似的语法,把一个
HTML
树描述为
PHP/Hack
对象。在
Web
应用中,它消除了
整个种类的错误,以及安全漏洞的来源。它使得
UI
代码更整洁、更易于维护,也更加灵活。
在传统的
PHP
中,你在网页中输出可能通过下面两种方式之一,或者使用混合
HTML
PHP
模板:
<tt>
Hello
<strong>
<?= $user_name ?>
</strong>
!
</tt>
或者通过字符串连接或者字符串插入来输出:
echo
"<tt>Hello <strong>$user_name</strong>!</tt>";
有了
XHP
,上面的例子可能看起来是这样的:
echo
<tt>Hello <strong>{$user_name}</strong></tt>;
这是一个非常普通的
echo
语句,并且没有任何引号。这种类似
HTML
的语法是整个
XHP
语法体系中的一部分。
XHP
对于一个现代的、面向对象的
Web
应用
UI
库是一个非常好的基石。在本章的学习中,
我们将会看到你为什么应该使用它,怎么使用它,如何在它的基础上进行构建,还有如
何使用它对一个传统的代码库进行改造。
7.1
为什么使用
XHP
XHP
可以帮助提高你
UI
代码的安全性及正确性,它有着大量方法用于防止你犯常见的
158
7
错误。它还可以通过对你的
HTML
标记提供一个面向对象的接口,帮助你更稳健地组织
你的代码。
7.1.1
运行时验证
你能够发现以下这段代码中的问题吗?
echo
'<div class="section-header">';
echo
'<a href="#intro">Intro to <span class="metal">Death Metal</sapn></a>';
echo
'</div>';
其中一个结束标记拼写错了
</sapn>
。在实际的代码中,直到你在一个浏览器中的结
果页面查看,才可能发现这样的一个
bug
。甚至于你可能根本没有注意到它。这一切取
决于
bug
本身。
XHP
消除了这一类错误。上例如果在
XHP
中(包含着错误的拼写),将会是这样的:
echo
<div class="section-header">
<a href="#intro">Intro to <span class="metal">Death Metal</sapn></a>
</div>;
当你试图运行
include
或者
require
个文件,你将会得到一个致命错误:
Fatal error: XHP: mismatched tag: 'sapn' not the same as 'span' in
/home/oyamauchi/test.php on line 4
XHP
还提供了更加精妙的验证形式。
HTML
拥有规则用于管理标记之间的允许关系,类
似于下面的问题:哪一个标记内部允许出现其他标记,什么样的标记允许内部是文本而
非其他标记。
XHP
可以检查这些约束,如果哪里有违反,则会产生一个错误信息。
例如,下面的代码就不是合法的
HTML
代码,因为在
<select>
记内不允许出现
<option>
<optgroup>
外的代码:
<select><strong>
bold text!
</strong></select>
如果你试图在
XHP
中运行这些代码,你将会得到一个致命错误,它会对错误的位置及
类型进行具体描述:
Fatal error: Element `select` was rendered with invalid children.
/home/oyamauchi/test.php:2
Verified 0 children before failing.
Children expected:
(:option|:optgroup)*
XHP
159
Children received:
:strong
XHP
HTML5
草案中推行的很多规则都做了验证,但并非是全部。当你使用自定义类
来扩展
XHP
的时候,你可以为它们添加验证规则。我们将在
7.3.2
节进行具体学习。
7.1.2
默认安全
这里有一些代码应用于网站表单提交。使用者在表单输入项目内输入她的名字,然后这
个页面将展示一个个性化的欢迎信息。这里有什么问题呢?
$user_name = $_REQUEST['name'];
echo
'<html>';
echo
'<head><title>Welcome</title></head>';
echo
'<body>Welcome, ' . $user_name . '</body>';
echo
'</html>';
这里有个安全漏洞。如果用户提交了一个包含
HTML
标记的字符串,这个标记将被浏览
器解释为文档对象模型(
DOM
)的一部分。例如,如果用户在
name
查询参数里面提交
<blink>blinkytext</blink>
将会在结果页面上有个不停闪烁的文本,这显然不是
网站作者想看到的。这个问题还称为跨站脚本攻击(
cross-site scripting vulnerability
XSS
1
如果没有
XHP
,这种
XSS
攻击需要添加一个对函数
htmlspecialchars()
调用来进行
修复:
$user_name = htmlspecialchars($_REQUEST['name']);
// ...
这仍然很麻烦:你必须记住为每一个可能包含用户输入项目的字符串进行转义(包含从
数据库查询得到的结果)。你还必须确保它们只转义了一次,否则,你可能会遇到双重
转义的
bug
,这并不是什么安全漏洞,但仍然是不推荐的。
这个例子很容易修复,但还是特别令人震惊。在真实的代码中,
XSS
攻击更加精妙。大
多数代码库都会有大量的函数或者方法输出一个完整的
Web
页面的片段,并且它们在很
多不同的层次被调用,来组成最终的页面。确保所有必要的转义只做一次,对于所有的
层次来说,都是非常困难和精妙的任务。
这里有些
XHP
中类似的代码:
$user_name = $_REQUEST['name'];
1
这里并不是
CSS
,因为
CSS
Cascading Style Sheets
160
7
echo
<html>
<head><title>Welcome</title></head>
<body>Welcome, {$user_name}</body>
</html>;
这里并没有任何对
htmlspecialchars()
数的调用,或者其他转义程序,但是这里并
没有
XSS
漏洞。
XHP
输出一个字符串之前,会把里面的保留字符转义为
HTML
实体。
例如,会
<
替换为
&lt;
这个问题的根源在于:
PHP
Hack
对于原始字符串和
HTML
字符串并没有区别对待。
最好认为它们是两个完全不同的数据类型,然后使用特殊的逻辑在它们之间进行转化。
一个原始字符串意味着原样输出。而一个
HTML
字符串是一个序列化的
DOM
树,意味
着用做
HTML
渲染引擎的输入项。
XSS
漏洞源自于把原始字符串误当作
HTML
字符串进行对待。用户在表单项目中输入的
这个字符串是个原始字符串,所以它必须在被一个
HTML
渲染引擎当作输入项之前,转
化为一个
HTML
字符串(保留
HTML
字符必须被转义)。如果任务失败了,原则上来
说就是一个类型错误。
XHP
通过缓解你对处理
HTML
字符串的需求以彻底解决这个问题。
HTML
想象成一种序列化的格式,而不是一种标记语言,将使问题变得更加清晰。对
比另外一种常用的序列化格式
JSON
。当你编写一段将要输出
JSON
的代码时,你并不
会去手工组装
JSON
字符串。而是使用
PHP/Hack
对象或者数组来建立对应的结构,并
在最后一步使用
json_encode()
数把它序列化为
JSON
。你作为程序开发人员,绝对
不会直接处理包含
JSON
编码数据的字符串。
类似地,
XHP
将会给你一个方式来使用
PHP
Hack
对象构建一个结构体,然后把它序
列化为
HTML
,而不是直接处理一个序列化的
HTML
字符串,除非把输出为流。
为什么
XSS
是危险的?
XSS
漏洞的全面探索已经超出了本书的范围,但是这里有个简要的预览。以
XSS
身份出现的最紧的危险就是:它允许攻击者在用户信任的网站上下文中,执行
恶意的
JavaScript
代码。
一个浏览器中运行的
JavaScript
代码,通常可以获得其他窗口和同一个浏览器其他
tab
中的信息,但是前提是它们显示的都是同一个网站。在这种情况下,如果你在
一个
tab
中开着你的银行网站,然后另外一个
tab
打开一个恶意网站,恶意网站的
JavaScript
代码并不能访问你的银行信息。这种限制称之为同源策略。

Get Hack 与HHVM 权威指南 now with O’Reilly online learning.

O’Reilly members experience live online training, plus books, videos, and digital content from 200+ publishers.