用PHP实现一个高效安全的ftp服务器(二),php实现ftp服务器
接前文。
1.实现用户类CUser。
用户的存储采用文本形式,将用户数组进行json编码。
<span>用户文件格式:
</span>* <span>array</span><span>(
</span>* 'user1' => <span>array</span><span>(
</span>* 'pass'=>'',
* 'group'=>'',
* 'home'=>'/home/ftp/', <span>//</span><span>ftp主目录</span>
* 'active'=><span>true</span>,
* 'expired=>'2015-12-12'<span>,
* </span>'description'=>''<span>,
* </span>'email' => ''<span>,
* </span>'folder'<span>=>array(
* //可以列出主目录下的文件和目录,但不能创建和删除,也不能进入主目录下的目录
* //前1-5位是文件权限,6-9是文件夹权限,10是否继承(inherit)
* array(</span>'path'=>'/home/ftp/','access'=>'RWANDLCNDI'<span>),
* //可以列出/home/ftp/a/下的文件和目录,可以创建和删除,可以进入/home/ftp/a/下的子目录,可以创建和删除。
* array(</span>'path'=>'/home/ftp/a/','access'=>'RWAND-----'<span>),
* ),
* </span>'ip'<span>=>array(
* </span>'allow'<span>=>array(ip1,ip2,...),//支持*通配符: 192.168.0.*
* </span>'deny'<span>=>array(ip1,ip2,...)
* )
* )
* )
*
* 组文件格式:
* array(
* </span>'group1'<span>=>array(
* </span>'home'=>'/home/ftp/dept1/'<span>,
* </span>'folder'<span>=>array(
*
* ),
* </span>'ip'<span>=>array(
* </span>'allow'<span>=>array(ip1,ip2,...),
* </span>'deny'<span>=>array(ip1,ip2,...)
* )
* )
* )</span>
文件夹和文件的权限说明:
* 文件权限
* R读 : 允许用户读取(即下载)文件。该权限不允许用户列出目录内容,执行该操作需要列表权限。
* W写: 允许用户写入(即上传)文件。该权限不允许用户修改现有的文件,执行该操作需要追加权限。
* A追加: 允许用户向现有文件中追加数据。该权限通常用于使用户能够对部分上传的文件进行续传。
* N重命名: 允许用户重命名现有的文件。
* D删除: 允许用户删除文件。
*
* 目录权限
* L列表: 允许用户列出目录中包含的文件。
* C创建: 允许用户在目录中新建子目录。
* N重命名: 允许用户在目录中重命名现有子目录。
* D删除: 允许用户在目录中删除现有子目录。注意: 如果目录包含文件,用户要删除目录还需要具有删除文件权限。
*
* 子目录权限
* I继承: 允许所有子目录继承其父目录具有的相同权限。继承权限适用于大多数情况,但是如果访问必须受限于子文件夹,例如实施强制访问控制(Mandatory Access Control)时,则取消继承并为文件夹逐一授予权限。
*
实现代码如下:
![](http://www.bkjia.com/uploads/allimg/151230/164F62010-0.gif)
class User{
const I = 1;
// inherit
const FD = 2;
// folder delete
const FN = 4;
// folder rename
const FC = 8;
// folder create
const FL = 16;
// folder list
const D = 32;
// file delete
const N = 64;
// file rename
const A = 128;
// file append
const W = 256;
// file write (upload)
const R = 512;
// file read (download)
private $hash_salt = ''
;
private $user_file;
private $group_file;
private $users =
array();
private $groups =
array();
private $file_hash = ''
;
public function __construct(){
$this->user_file = BASE_PATH.'/conf/users'
;
$this->group_file = BASE_PATH.'/conf/groups'
;
$this->
reload();
}
/**
* 返回权限表达式
* @param int $access
* @return string
*/
public static function AC(
$access){
$str = ''
;
$char =
array('R','W','A','N','D','L','C','N','D','I'
);
for(
$i = 0;
$i $i++
){
if(
$access &
pow(2,9-
$i))
$str.=
$char[
$i];
else $str.= '-'
;
}
return $str;
}
/**
* 加载用户数据
*/
public function reload(){
$user_file_hash =
md5_file(
$this->
user_file);
$group_file_hash =
md5_file(
$this->
group_file);
if(
$this->file_hash !=
md5(
$user_file_hash.
$group_file_hash)){
if((
$user =
file_get_contents(
$this->user_file)) !==
false){
$this->users = json_decode(
$user,
true);
if(
$this->
users){
//folder排序
foreach (
$this->users
as $user=>
$profile){
if(
isset(
$profile['folder'
])){
$this->users[
$user]['folder'] =
$this->sortFolder(
$profile['folder'
]);
}
}
}
}
if((
$group =
file_get_contents(
$this->group_file)) !==
false){
$this->groups = json_decode(
$group,
true);
if(
$this->
groups){
//folder排序
foreach (
$this->groups
as $group=>
$profile){
if(
isset(
$profile['folder'
])){
$this->groups[
$group]['folder'] =
$this->sortFolder(
$profile['folder'
]);
}
}
}
}
$this->file_hash =
md5(
$user_file_hash.
$group_file_hash);
}
}
/**
* 对folder进行排序
* @return array
*/
private function sortFolder(
$folder){
uasort(
$folder,
function(
$a,
$b){
return strnatcmp(
$a['path'],
$b['path'
]);
});
$result =
array();
foreach (
$folder as $v){
$result[] =
$v;
}
return $result;
}
/**
* 保存用户数据
*/
public function save(){
file_put_contents(
$this->user_file, json_encode(
$this->users),
LOCK_EX);
file_put_contents(
$this->group_file, json_encode(
$this->groups),
LOCK_EX);
}
/**
* 添加用户
* @param string $user
* @param string $pass
* @param string $home
* @param string $expired
* @param boolean $active
* @param string $group
* @param string $description
* @param string $email
* @return boolean
*/
public function addUser(
$user,
$pass,
$home,
$expired,
$active=
true,
$group='',
$description='',
$email = ''
){
$user =
strtolower(
$user);
if(
isset(
$this->users[
$user]) ||
empty(
$user)){
return false;
}
$this->users[
$user] =
array(
'pass' =>
md5(
$user.
$this->hash_salt.
$pass),
'home' =>
$home,
'expired' =>
$expired,
'active' =>
$active,
'group' =>
$group,
'description' =>
$description,
'email' =>
$email,
);
return true;
}
/**
* 设置用户资料
* @param string $user
* @param array $profile
* @return boolean
*/
public function setUserProfile(
$user,
$profile){
$user =
strtolower(
$user);
if(
is_array(
$profile) &&
isset(
$this->users[
$user])){
if(
isset(
$profile['pass'
])){
$profile['pass'] =
md5(
$user.
$this->hash_salt.
$profile['pass'
]);
}
if(
isset(
$profile['active'
])){
if(!
is_bool(
$profile['active'
])){
$profile['active'] =
$profile['active'] == 'true' ?
true :
false;
}
}
$this->users[
$user] =
array_merge(
$this->users[
$user],
$profile);
return true;
}
return false;
}
/**
* 获取用户资料
* @param string $user
* @return multitype:|boolean
*/
public function getUserProfile(
$user){
$user =
strtolower(
$user);
if(
isset(
$this->users[
$user])){
return $this->users[
$user];
}
return false;
}
/**
* 删除用户
* @param string $user
* @return boolean
*/
public function delUser(
$user){
$user =
strtolower(
$user);
if(
isset(
$this->users[
$user])){
unset(
$this->users[
$user]);
return true;
}
return false;
}
/**
* 获取用户列表
* @return array
*/
public function getUserList(){
$list =
array();
if(
$this->
users){
foreach (
$this->users
as $user=>
$profile){
$list[] =
$user;
}
}
sort(
$list);
return $list;
}
/**
* 添加组
* @param string $group
* @param string $home
* @return boolean
*/
public function addGroup(
$group,
$home){
$group =
strtolower(
$group);
if(
isset(
$this->groups[
$group])){
return false;
}
$this->groups[
$group] =
array(
'home' =>
$home
);
return true;
}
/**
* 设置组资料
* @param string $group
* @param array $profile
* @return boolean
*/
public function setGroupProfile(
$group,
$profile){
$group =
strtolower(
$group);
if(
is_array(
$profile) &&
isset(
$this->groups[
$group])){
$this->groups[
$group] =
array_merge(
$this->groups[
$group],
$profile);
return true;
}
return false;
}
/**
* 获取组资料
* @param string $group
* @return multitype:|boolean
*/
public function getGroupProfile(
$group){
$group =
strtolower(
$group);
if(
isset(
$this->groups[
$group])){
return $this->groups[
$group];
}
return false;
}
/**
* 删除组
* @param string $group
* @return boolean
*/
public function delGroup(
$group){
$group =
strtolower(
$group);
if(
isset(
$this->groups[
$group])){
unset(
$this->groups[
$group]);
foreach (
$this->users
as $user =>
$profile){
if(
$profile['group'] ==
$group)
$this->users[
$user]['group'] = ''
;
}
return true;
}
return false;
}
/**
* 获取组列表
* @return array
*/
public function getGroupList(){
$list =
array();
if(
$this->
groups){
foreach (
$this->groups
as $group=>
$profile){
$list[] =
$group;
}
}
sort(
$list);
return $list;
}
/**
* 获取组用户列表
* @param string $group
* @return array
*/
public function getUserListOfGroup(
$group){
$list =
array();
if(
isset(
$this->groups[
$group]) &&
$this->
users){
foreach (
$this->users
as $user=>
$profile){
if(
isset(
$profile['group']) &&
$profile['group'] ==
$group){
$list[] =
$user;
}
}
}
sort(
$list);
return $list;
}
/**
* 用户验证
* @param string $user
* @param string $pass
* @param string $ip
* @return boolean
*/
public function checkUser(
$user,
$pass,
$ip = ''
){
$this->
reload();
$user =
strtolower(
$user);
if(
isset(
$this->users[
$user])){
if(
$this->users[
$user]['active'] &&
time() strtotime(
$this->users[
$user]['expired'
])
&&
$this->users[
$user]['pass'] ==
md5(
$user.
$this->hash_salt.
$pass)){
if(
empty(
$ip)){
return true;
}else{
//ip验证
return $this->checkIP(
$user,
$ip);
}
}else{
return false;
}
}
return false;
}
/**
* basic auth
* @param string $base64
*/
public function checkUserBasicAuth(
$base64){
$base64 =
trim(
str_replace('Basic ', '',
$base64));
$str =
base64_decode(
$base64);
if(
$str !==
false){
list(
$user,
$pass) =
explode(':',
$str,2
);
$this->
reload();
$user =
strtolower(
$user);
if(
isset(
$this->users[
$user])){
$group =
$this->users[
$user]['group'
];
if(
$group == 'admin' &&
$this->users[
$user]['active'] &&
time() strtotime(
$this->users[
$user]['expired'
])
&&
$this->users[
$user]['pass'] ==
md5(
$user.
$this->hash_salt.
$pass)){
return true;
}else{
return false;
}
}
}
return false;
}
/**
* 用户登录ip验证
* @param string $user
* @param string $ip
*
* 用户的ip权限继承组的IP权限。
* 匹配规则:
* 1.进行组允许列表匹配;
* 2.如同通过,进行组拒绝列表匹配;
* 3.进行用户允许匹配
* 4.如果通过,进行用户拒绝匹配
*
*/
public function checkIP(
$user,
$ip){
$pass =
false;
//先进行组验证
$group =
$this->users[
$user]['group'
];
//组允许匹配
if(
isset(
$this->groups[
$group]['ip']['allow'
])){
foreach (
$this->groups[
$group]['ip']['allow']
as $addr){
$pattern = '/'.
str_replace('*','\d+',
str_replace('.', '\.',
$addr)).'/'
;
if(
preg_match(
$pattern,
$ip) && !
empty(
$addr)){
$pass =
true;
break;
}
}
}
//如果允许通过,进行拒绝匹配
if(
$pass){
if(
isset(
$this->groups[
$group]['ip']['deny'
])){
foreach (
$this->groups[
$group]['ip']['deny']
as $addr){
$pattern = '/'.
str_replace('*','\d+',
str_replace('.', '\.',
$addr)).'/'
;
if(
preg_match(
$pattern,
$ip) && !
empty(
$addr)){
$pass =
false;
break;
}
}
}
}
if(
isset(
$this->users[
$user]['ip']['allow'
])){
foreach (
$this->users[
$user]['ip']['allow']
as $addr){
$pattern = '/'.
str_replace('*','\d+',
str_replace('.', '\.',
$addr)).'/'
;
if(
preg_match(
$pattern,
$ip) && !
empty(
$addr)){
$pass =
true;
break;
}
}
}
if(
$pass){
if(
isset(
$this->users[
$user]['ip']['deny'
])){
foreach (
$this->users[
$user]['ip']['deny']
as $addr){
$pattern = '/'.
str_replace('*','\d+',
str_replace('.', '\.',
$addr)).'/'
;
if(
preg_match(
$pattern,
$ip) && !
empty(
$addr)){
$pass =
false;
break;
}
}
}
}
echo date('Y-m-d H:i:s')." [debug]\tIP ACCESS:".' '.(
$pass?'true':'false')."\n"
;
return $pass;
}
/**
* 获取用户主目录
* @param string $user
* @return string
*/
public function getHomeDir(
$user){
$user =
strtolower(
$user);
$group =
$this->users[
$user]['group'
];
$dir = ''
;
if(
$group){
if(
isset(
$this->groups[
$group]['home']))
$dir =
$this->groups[
$group]['home'
];
}
$dir = !
empty(
$this->users[
$user]['home'])?
$this->users[
$user]['home']:
$dir;
return $dir;
}
//文件权限判断
public function isReadable(
$user,
$path){
$result =
$this->getPathAccess(
$user,
$path);
if(
$result['isExactMatch'
]){
return $result['access'][0] == 'R'
;
}else{
return $result['access'][0] == 'R' &&
$result['access'][9] == 'I'
;
}
}
public function isWritable(
$user,
$path){
$result =
$this->getPathAccess(
$user,
$path);
if(
$result['isExactMatch'
]){
return $result['access'][1] == 'W'
;
}else{
return $result['access'][1] == 'W' &&
$result['access'][9] == 'I'
;
}
}
public function isAppendable(
$user,
$path){
$result =
$this->getPathAccess(
$user,
$path);
if(
$result['isExactMatch'
]){
return $result['access'][2] == 'A'
;
}else{
return $result['access'][2] == 'A' &&
$result['access'][9] == 'I'
;
}
}
public function isRenamable(
$user,
$path){
$result =
$this->getPathAccess(
$user,
$path);
if(
$result['isExactMatch'
]){
return $result['access'][3] == 'N'
;
}else{
return $result['access'][3] == 'N' &&
$result['access'][9] == 'I'
;
}
}
public function isDeletable(
$user,
$path){
$result =
$this->getPathAccess(
$user,
$path);
if(
$result['isExactMatch'
]){
return $result['access'][4] == 'D'
;
}else{
return $result['access'][4] == 'D' &&
$result['access'][9] == 'I'
;
}
}
//目录权限判断
public function isFolderListable(
$user,
$path){
$result =
$this->getPathAccess(
$user,
$path);
if(
$result['isExactMatch'
]){
return $result['access'][5] == 'L'
;
}else{
return $result['access'][5] == 'L' &&
$result['access'][9] == 'I'
;
}
}
public function isFolderCreatable(
$user,
$path){
$result =
$this->getPathAccess(
$user,
$path);
if(
$result['isExactMatch'
]){
return $result['access'][6] == 'C'
;
}else{
return $result['access'][6] == 'C' &&
$result['access'][9] == 'I'
;
}
}
public function isFolderRenamable(
$user,
$path){
$result =
$this->getPathAccess(
$user,
$path);
if(
$result['isExactMatch'
]){
return $result['access'][7] == 'N'
;
}else{
return $result['access'][7] == 'N' &&
$result['access'][9] == 'I'
;
}
}
public function isFolderDeletable(
$user,
$path){
$result =
$this->getPathAccess(
$user,
$path);
if(
$result['isExactMatch'
]){
return $result['access'][8] == 'D'
;
}else{
return $result['access'][8] == 'D' &&
$result['access'][9] == 'I'
;
}
}
/**
* 获取目录权限
* @param string $user
* @param string $path
* @return array
* 进行最长路径匹配
*
* 返回:
* array(
* 'access'=>目前权限
* ,'isExactMatch'=>是否精确匹配
*
* );
*
* 如果精确匹配,则忽略inherit.
* 否则应判断是否继承父目录的权限,
* 权限位表:
* +---+---+---+---+---+---+---+---+---+---+
* | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
* +---+---+---+---+---+---+---+---+---+---+
* | R | W | A | N | D | L | C | N | D | I |
* +---+---+---+---+---+---+---+---+---+---+
* | FILE | FOLDER |
* +-------------------+-------------------+
*/
public function getPathAccess(
$user,
$path){
$this->
reload();
$user =
strtolower(
$user);
$group =
$this->users[
$user]['group'
];
//去除文件名称
$path =
str_replace(
substr(
strrchr(
$path, '/'),1),'',
$path);
$access = self::AC(0
);
$isExactMatch =
false;
if(
$group){
if(
isset(
$this->groups[
$group]['folder'
])){
foreach (
$this->groups[
$group]['folder']
as $f){
//中文处理
$t_path =
iconv('UTF-8','GB18030',
$f['path'
]);
if(
strpos(
$path,
$t_path) === 0
){
$access =
$f['access'
];
$isExactMatch = (
$path ==
$t_path?
true:
false);
}
}
}
}
if(
isset(
$this->users[
$user]['folder'
])){
foreach (
$this->users[
$user]['folder']
as $f){
//中文处理
$t_path =
iconv('UTF-8','GB18030',
$f['path'
]);
if(
strpos(
$path,
$t_path) === 0
){
$access =
$f['access'
];
$isExactMatch = (
$path ==
$t_path?
true:
false);
}
}
}
echo date('Y-m-d H:i:s')." [debug]\tACCESS:
$access ".' '.(
$isExactMatch?'1':'0')."
$path\n"
;
return array('access'=>
$access,'isExactMatch'=>
$isExactMatch);
}
/**
* 添加在线用户
* @param ShareMemory $shm
* @param swoole_server $serv
* @param unknown $user
* @param unknown $fd
* @param unknown $ip
* @return Ambigous >
*/
public function addOnline(ShareMemory
$shm ,
$serv,
$user,
$fd,
$ip){
$shm_data =
$shm->
read();
if(
$shm_data !==
false){
$shm_data['online'][
$user.'-'.
$fd] =
array('ip'=>
$ip,'time'=>
time());
$shm_data['last_login'][] =
array('user' =>
$user,'ip'=>
$ip,'time'=>
time());
//清除旧数据
if(
count(
$shm_data['last_login'])>30)
array_shift(
$shm_data['last_login'
]);
$list =
array();
foreach (
$shm_data['online']
as $k =>
$v){
$arr =
explode('-',
$k);
if(
$serv->connection_info(
$arr[1]) !==
false){
$list[
$k] =
$v;
}
}
$shm_data['online'] =
$list;
$shm->write(
$shm_data);
}
return $shm_data;
}
/**
* 添加登陆失败记录
* @param ShareMemory $shm
* @param unknown $user
* @param unknown $ip
* @return Ambigous
*/
public function addAttempt(ShareMemory
$shm ,
$user,
$ip){
$shm_data =
$shm->
read();
if(
$shm_data !==
false){
if(
isset(
$shm_data['login_attempt'][
$ip.'||'.
$user]['count'
])){
$shm_data['login_attempt'][
$ip.'||'.
$user]['count'] += 1
;
}else{
$shm_data['login_attempt'][
$ip.'||'.
$user]['count'] = 1
;
}
$shm_data['login_attempt'][
$ip.'||'.
$user]['time'] =
time();
//清除旧数据
if(
count(
$shm_data['login_attempt'])>30)
array_shift(
$shm_data['login_attempt'
]);
$shm->write(
$shm_data);
}
return $shm_data;
}
/**
* 密码错误上限
* @param unknown $shm
* @param unknown $user
* @param unknown $ip
* @return boolean
*/
public function isAttemptLimit(ShareMemory
$shm,
$user,
$ip){
$shm_data =
$shm->
read();
if(
$shm_data !==
false){
if(
isset(
$shm_data['login_attempt'][
$ip.'||'.
$user]['count'
])){
if(
$shm_data['login_attempt'][
$ip.'||'.
$user]['count'] > 10 &&
time() -
$shm_data['login_attempt'][
$ip.'||'.
$user]['time'] ){
return true;
}
}
}
return false;
}
/**
* 生成随机密钥
* @param int $len
* @return Ambigous
*/
public static function genPassword(
$len){
$str =
null;
$strPol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz@!#$%*+-"
;
$max =
strlen(
$strPol)-1
;
for(
$i=0;
$i$len;
$i++
){
$str.=
$strPol[
rand(0,
$max)];
//rand($min,$max)生成介于min和max两个数之间的一个随机整数
}
return $str;
}
}
View Code
2.共享内存操作类
这个相对简单,使用php的shmop扩展即可。
![](http://www.bkjia.com/uploads/allimg/151230/164F62010-0.gif)
class ShareMemory{
private $mode = 0644
;
private $shm_key;
private $shm_size;
/**
* 构造函数
*/
public function __construct(){
$key = 'F'
;
$size = 1024*1024
;
$this->shm_key = ftok(
__FILE__,
$key);
$this->shm_size =
$size + 1
;
}
/**
* 读取内存数组
* @return array|boolean
*/
public function read(){
if((
$shm_id = shmop_open(
$this->shm_key,'c',
$this->mode,
$this->shm_size)) !==
false){
$str = shmop_read(
$shm_id,1,
$this->shm_size-1
);
shmop_close($shm_id);
if((
$i =
strpos(
$str,"\0")) !==
false)
$str =
substr(
$str,0,
$i);
if(
$str){
return json_decode(
$str,
true);
}else{
return array();
}
}
return false;
}
/**
* 写入数组到内存
* @param array $arr
* @return int|boolean
*/
public function write(
$arr){
if(!
is_array(
$arr))
return false;
$str = json_encode(
$arr)."\0"
;
if(
strlen(
$str) >
$this->shm_size)
return false;
if((
$shm_id = shmop_open(
$this->shm_key,'c',
$this->mode,
$this->shm_size)) !==
false){
$count = shmop_write(
$shm_id,
$str,1
);
shmop_close($shm_id);
return $count;
}
return false;
}
/**
* 删除内存块,下次使用时将重新开辟内存块
* @return boolean
*/
public function delete(){
if((
$shm_id = shmop_open(
$this->shm_key,'c',
$this->mode,
$this->shm_size)) !==
false){
$result = shmop_delete(
$shm_id);
shmop_close($shm_id);
return $result;
}
return false;
}
}
View Code
3.内置的web服务器类
这个主要是嵌入在ftp的http服务器类,功能不是很完善,进行ftp的管理还是可行的。不过需要注意的是,这个实现与apache等其他http服务器运行的方式可能有所不同。代码是驻留内存的。
![](http://www.bkjia.com/uploads/allimg/151230/164F62010-0.gif)
class CWebServer{
protected $buffer_header =
array();
protected $buffer_maxlen = 65535;
//最大POST尺寸
const DATE_FORMAT_HTTP = 'D, d-M-Y H:i:s T'
;
const HTTP_EOF = "\r\n\r\n"
;
const HTTP_HEAD_MAXLEN = 8192;
//http头最大长度不得超过2k
const HTTP_POST_MAXLEN = 1048576;
//1m
const ST_FINISH = 1;
//完成,进入处理流程
const ST_WAIT = 2;
//等待数据
const ST_ERROR = 3;
//错误,丢弃此包
private $requsts =
array();
private $config =
array();
public function log(
$msg,
$level = 'debug'
){
echo date('Y-m-d H:i:s').' ['.
$level."]\t" .
$msg."\n"
;
}
public function __construct(
$config =
array()){
$this->config =
array(
'wwwroot' => __DIR__.'/wwwroot/',
'index' => 'index.php',
'path_deny' =>
array('/protected/'),
);
}
public function onReceive(
$serv,
$fd,
$data){
$ret =
$this->checkData(
$fd,
$data);
switch (
$ret){
case self::ST_ERROR:
$serv->close(
$fd);
$this->cleanBuffer(
$fd);
$this->
log('Recevie error.'
);
break;
case self::ST_WAIT:
$this->
log('Recevie wait.'
);
return;
default:
break;
}
//开始完整的请求
$request =
$this->requsts[
$fd];
$info =
$serv->connection_info(
$fd);
$request =
$this->parseRequest(
$request);
$request['remote_ip'] =
$info['remote_ip'
];
$response =
$this->onRequest(
$request);
$output =
$this->parseResponse(
$request,
$response);
$serv->send(
$fd,
$output);
if(
isset(
$request['head']['Connection']) &&
strtolower(
$request['head']['Connection']) == 'close'
){
$serv->close(
$fd);
}
unset(
$this->requsts[
$fd]);
$_REQUEST =
$_SESSION =
$_COOKIE =
$_FILES =
$_POST =
$_SERVER =
$_GET =
array();
}
/**
* 处理请求
* @param array $request
* @return array $response
*
* $request=array(
* 'time'=>
* 'head'=>array(
* 'method'=>
* 'path'=>
* 'protocol'=>
* 'uri'=>
* //other http header
* '..'=>value
* )
* 'body'=>
* 'get'=>(if appropriate)
* 'post'=>(if appropriate)
* 'cookie'=>(if appropriate)
*
*
* )
*/
public function onRequest(
$request){
if(
$request['head']['path'][
strlen(
$request['head']['path']) - 1] == '/'
){
$request['head']['path'] .=
$this->config['index'
];
}
$response =
$this->process(
$request);
return $response;
}
/**
* 清除数据
* @param unknown $fd
*/
public function cleanBuffer(
$fd){
unset(
$this->requsts[
$fd]);
unset(
$this->buffer_header[
$fd]);
}
/**
* 检查数据
* @param unknown $fd
* @param unknown $data
* @return string
*/
public function checkData(
$fd,
$data){
if(
isset(
$this->buffer_header[
$fd])){
$data =
$this->buffer_header[
$fd].
$data;
}
$request =
$this->checkHeader(
$fd,
$data);
//请求头错误
if(
$request ===
false){
$this->buffer_header[
$fd] =
$data;
if(
strlen(
$data) > self::
HTTP_HEAD_MAXLEN){
return self::
ST_ERROR;
}else{
return self::
ST_WAIT;
}
}
//post请求检查
if(
$request['head']['method'] == 'POST'
){
return $this->checkPost(
$request);
}else{
return self::
ST_FINISH;
}
}
/**
* 检查请求头
* @param unknown $fd
* @param unknown $data
* @return boolean|array
*/
public function checkHeader(
$fd,
$data){
//新的请求
if(!
isset(
$this->requsts[
$fd])){
//http头结束符
$ret =
strpos(
$data,self::
HTTP_EOF);
if(
$ret ===
false){
return false;
}else{
$this->buffer_header[
$fd] = ''
;
$request =
array();
list(
$header,
$request['body']) =
explode(self::HTTP_EOF,
$data,2
);
$request['head'] =
$this->parseHeader(
$header);
$this->requsts[
$fd] =
$request;
if(
$request['head'] ==
false){
return false;
}
}
}else{
//post 数据合并
$request =
$this->requsts[
$fd];
$request['body'] .=
$data;
}
return $request;
}
/**
* 解析请求头
* @param string $header
* @return array
* array(
* 'method'=>,
* 'uri'=>
* 'protocol'=>
* 'name'=>value,...
*
*
*
* }
*/
public function parseHeader(
$header){
$request =
array();
$headlines =
explode("\r\n",
$header);
list(
$request['method'],
$request['uri'],
$request['protocol']) =
explode(' ',
$headlines[0],3
);
foreach (
$headlines as $k=>
$line){
$line =
trim(
$line);
if(
$k && !
empty(
$line) &&
strpos(
$line,':') !==
false){
list(
$name,
$value) =
explode(':',
$line,2
);
$request[
trim(
$name)] =
trim(
$value);
}
}
return $request;
}
/**
* 检查post数据是否完整
* @param unknown $request
* @return string
*/
public function checkPost(
$request){
if(
isset(
$request['head']['Content-Length'
])){
if(
intval(
$request['head']['Content-Length']) > self::
HTTP_POST_MAXLEN){
return self::
ST_ERROR;
}
if(
intval(
$request['head']['Content-Length']) >
strlen(
$request['body'
])){
return self::
ST_WAIT;
}else{
return self::
ST_FINISH;
}
}
return self::
ST_ERROR;
}
/**
* 解析请求
* @param unknown $request
* @return Ambigous
*/
public function parseRequest(
$request){
$request['time'] =
time();
$url_info =
parse_url(
$request['head']['uri'
]);
$request['head']['path'] =
$url_info['path'
];
if(
isset(
$url_info['fragment']))
$request['head']['fragment'] =
$url_info['fragment'
];
if(
isset(
$url_info['query'
])){
parse_str(
$url_info['query'],
$request['get'
]);
}
//parse post body
if(
$request['head']['method'] == 'POST'
){
//目前只处理表单提交
if (
isset(
$request['head']['Content-Type']) &&
substr(
$request['head']['Content-Type'], 0, 33) == 'application/x-www-form-urlencoded'
||
isset(
$request['head']['X-Request-With']) &&
$request['head']['X-Request-With'] == 'XMLHttpRequest'
){
parse_str(
$request['body'],
$request['post'
]);
}
}
//parse cookies
if(!
empty(
$request['head']['Cookie'
])){
$params =
array();
$blocks =
explode(";",
$request['head']['Cookie'
]);
foreach (
$blocks as $b){
$_r =
explode("=",
$b, 2
);
if(
count(
$_r)==2
){
list (
$key,
$value) =
$_r;
$params[
trim(
$key)] =
trim(
$value, "\r\n \t\""
);
}else{
$params[
$_r[0]] = ''
;
}
}
$request['cookie'] =
$params;
}
return $request;
}
public function parseResponse(
$request,
$response){
if(!
isset(
$response['head']['Date'
])){
$response['head']['Date'] =
gmdate("D, d M Y H:i:s T"
);
}
if(!
isset(
$response['head']['Content-Type'
])){
$response['head']['Content-Type'] = 'text/html;charset=utf-8'
;
}
if(!
isset(
$response['head']['Content-Length'
])){
$response['head']['Content-Length'] =
strlen(
$response['body'
]);
}
if(!
isset(
$response['head']['Connection'
])){
if(
isset(
$request['head']['Connection']) &&
strtolower(
$request['head']['Connection']) == 'keep-alive'
){
$response['head']['Connection'] = 'keep-alive'
;
}else{
$response['head']['Connection'] = 'close'
;
}
}
$response['head']['Server'] = CFtpServer::
$software.'/'.CFtpServer::
VERSION;
$out = ''
;
if(
isset(
$response['head']['Status'
])){
$out .= 'HTTP/1.1 '.
$response['head']['Status']."\r\n"
;
unset(
$response['head']['Status'
]);
}else{
$out .= "HTTP/1.1 200 OK\r\n"
;
}
//headers
foreach(
$response['head']
as $k=>
$v){
$out .=
$k.': '.
$v."\r\n"
;
}
//cookies
if(
$_COOKIE){
$arr =
array();
foreach (
$_COOKIE as $k =>
$v){
$arr[] =
$k.'='.
$v;
}
$out .= 'Set-Cookie: '.
implode(';',
$arr)."\r\n"
;
}
//End
$out .= "\r\n"
;
$out .=
$response['body'
];
return $out