-
-
Save etng/a40cfeee723673bf10a88a490b99e3be to your computer and use it in GitHub Desktop.
dns2https.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/* | |
tcp dns client for google dns over https (https://dns.google.com) | |
ubuntu上使用: | |
在/etc/rc.local里加/usr/bin/php /home/<your_name>/dns2https.php | |
执行: | |
sysv-rc-conf unbound off | |
sysv-rc-conf dnscrypt-proxy off | |
sysv-rc-conf dnsmasq off | |
在/etc/resolv.conf里加: | |
options use-vc | |
nameserver 127.0.0.1 | |
在/etc/resolvconf/resolv.conf.d/head里加: | |
options use-vc | |
在/etc/network/interfaces正在使用的iface下加: | |
dns-nameservers 127.0.0.1 | |
*/ | |
//thanks to: | |
//https://github.com/mikepultz/netdns2/ | |
//https://github.com/yswery/PHP-DNS-SERVER/ | |
/* | |
开发时录制模仿系统的dns请求: | |
//mkfifo fifo | |
//nc -lk localhost 53 <fifo | tee -a a.txt | nc 8.8.4.4 53 | tee -a b.txt >fifo | |
$data=file_get_contents('a.txt'); | |
var_export(header_parse(substr($data, 2))); | |
$q=question_parse(substr($data, 14)); | |
var_export($q); | |
var_export(answer_parse(substr($data, 14+$q['length']), 1, substr($data, 0, 14+$q['length']))); | |
exit(); | |
*/ | |
/**********************************************主循环************************************************/ | |
//$ipsock=stream_socket_server('tcp://127.0.0.1:53', $errno2, $errstr2); | |
$ipsock=stream_socket_server('tcp://0.0.0.0:53', $errno2, $errstr2); | |
stream_set_blocking($ipsock, 0); //means fread() won't wait for data | |
//echo 'default_socket_timeout:'.ini_get("default_socket_timeout")."\n"; | |
$queue=curl_multi_init(); | |
$is_tcp=1; //udp还不支持 | |
$timeout=10; //每个并发域名查询的超时时间 | |
$force_ttl=3600; //设0则采用真实ttl | |
$google_domains=array('mail.google.com'=>'mail.google.com\/mail\/', 'www.google.com'=>'/url?'); | |
$cache=array(//'twitter.com'=>array('time'=>time(), 'ip'=>'1.2.3.4', 'ttl'=>3600*24), | |
//'t.co'=>array('time'=>time(), 'ip'=>'1.2.3.4', 'ttl'=>3600*24), | |
'*.baidu.com'=>array('time'=>time(), 'ip'=>'255.255.255.255', 'ttl'=>3600*24) | |
); | |
$ipsock_cons=array(); | |
$ipsock_con_datas=array(); | |
$running_domain_tasks=array(); | |
$statis=array(); | |
$google_ips=explode('|', '64.233.160.131|64.233.160.129|64.233.161.131|64.233.161.129|64.233.162.81|64.233.162.82|64.233.162.83|64.233.162.84|64.233.162.129|64.233.163.131|64.233.163.129|64.233.164.129|64.233.164.131'); | |
while (true) | |
{ | |
//echo "sleep 0.005\n"; | |
usleep(1000);//0.001s, to reduce cpu consumption | |
//处理监听连接 | |
//echo "1".time()."\n"; | |
$ipsock_con=@stream_socket_accept($ipsock, 0); //加参数0避免等待 | |
if ($ipsock_con!==false) | |
{ | |
//print_r($ipsock_con);echo " connected\n"; | |
$ipsock_cons[$ipsock_con]=$ipsock_con; //加入连接池 | |
$ipsock_con_datas[$ipsock_con]['last_time']=time(); //记录首次活动时间 | |
} | |
//结束超时的client connection | |
foreach ($ipsock_cons as $con) //根据末次活动时间判断连接池里的连接是否客户端意外断开(收不到feof信号) | |
{ | |
if (time()-$ipsock_con_datas[$con]['last_time']>60) | |
{ | |
echo "connection timeout(".$con.")!\n"; | |
end_ipsock_con($con); | |
} | |
} | |
//处理监听收信, 给向第三方发信创建任务 | |
//echo "2\n"; | |
$cons_read=$ipsock_cons; | |
$cons_write=$cons_except=array(); | |
if ($cons_read) | |
{ | |
stream_select($cons_read, $cons_write, $cons_except, 0); //过滤出所有可读任务resource ids, 加参数0避免等待 | |
} | |
if ($cons_read) | |
{ | |
//echo 'need to receive:'.strtr(print_r($cons_read, 1), array("\n"=>''))."\n"; | |
foreach ($cons_read as $con) | |
{ | |
if (feof($con)) //客户端断开连接服务端会收到feof信号, 即使客户端进程意外中断也会收到, 连接都是系统管理 | |
{ | |
echo "client side close(".$con.")!\n"; | |
end_ipsock_con($con); | |
continue; | |
} | |
if ($data=fread($con, 65536)) //由于是dns请求一个请求不会太长, 未做循环读取 | |
{ | |
//echo print_r($con, 1).' received: '.strlen($data).' '.$data." \n"; | |
//file_put_contents('a.txt', $data."\n======================\n", FILE_APPEND); | |
$ipsock_con_datas[$con]['last_time']=time(); //记录末次活动时间 | |
$reqs=get_domain_and_packet_id($data); | |
foreach ($reqs as $req) | |
{ | |
if ($req['packet_id']==='') //包头都不完整时跳过, 不断开连接, 有新请求过来就接着处理 | |
{ | |
continue; | |
} | |
$ipsock_con_datas[$con]['domain_packs'][@$req['domain'].':'.@$req['packet_id']]=array('domain'=>@$req['domain'], 'packet_id'=>@$req['packet_id']); | |
$this_con_packs=array($con.':'.$req['packet_id']=>array('con'=>$con, 'packet_id'=>$req['packet_id'])); | |
if (!preg_match('/[A-Za-z0-9_\-\.]+/', $req['domain']) //不支持含有非英语数字及三符号的域名查询 | |
|| strpos($req['domain'], '.')===false //不支持本地域名查询 | |
|| strpos($req['domain'], 'in-addr')!==false //不支持逆向域名查询inverse address | |
|| $req['type']!='A' //不支持非type==A的查询 | |
) | |
{ | |
echo "answer: not support (".$req['domain'].':'.$req['type'].")\n"; | |
//直接返回, 不创建domain task | |
send_answer($this_con_packs, $req['domain'], array('Answer'=>array(array('type'=>@$req['type'], 'data'=>'', 'TTL'=>'')))); | |
continue; | |
} | |
//已有缓存的域名且未过期 | |
if (@$cache[$req['domain']] && time()-$cache[$req['domain']]['time']<$cache[$req['domain']]['ttl']) | |
{ | |
//echo 'matched ';var_export($cache); | |
//直接返回, 不创建domain task | |
send_answer($this_con_packs, $req['domain'], array('Answer'=>array(array('type'=>1, 'data'=>$cache[$req['domain']]['ip'], 'TTL'=>$cache[$req['domain']]['ttl']-time()+$cache[$req['domain']]['time'])))); | |
continue; | |
} | |
//支持泛域名默认配置 | |
$tmp='*.'.implode('.', array_slice(explode('.', $req['domain']), -2)); | |
if (@$cache[$tmp]) | |
{ | |
//echo 'matched ';var_export($cache); | |
//直接返回, 不创建domain task | |
send_answer($this_con_packs, $req['domain'], array('Answer'=>array(array('type'=>1, 'data'=>$cache[$tmp]['ip'], 'TTL'=>$cache[$tmp]['ttl']-time()+$cache[$tmp]['time'])))); | |
continue; | |
} | |
unset($cache[$req['domain']]); //过期的缓存要清除 | |
create_query_tasks($con, $req['packet_id'], $req['domain']); | |
} | |
} | |
} | |
} | |
//执行第三方发信任务, 处理第三方收信, 处理监听发信 | |
//echo "3\n"; | |
while (($code=curl_multi_exec($queue, $active))==CURLM_CALL_MULTI_PERFORM); //总之有了这个while循环后, 有新发送任务时接收可以共用发送的5次执行, 没有新发送任务时, 要么有新接受任务共执行2次, 要么没有新接收任务共执行1次 | |
while ($done=curl_multi_info_read($queue)) //把所有已完成的任务都处理掉, curl_multi_info_read执行一次读取一条 | |
{ | |
$url_ori=curl_getinfo($done['handle'], CURLINFO_EFFECTIVE_URL); | |
$success_google_ip=parse_url($url_ori, 1); | |
parse_str(parse_url($url_ori, 6), $args_get); | |
if ($content=curl_multi_getcontent($done['handle'])) | |
{ | |
parse_body_headers($content, $body, $rsp_headers); | |
if (in_array($args_get['name'], array_keys($google_domains))) | |
{ | |
//echo $success_google_ip.' '.json_encode($rsp_headers).' '.$body."=========================\n"; | |
if (strpos(json_encode($rsp_headers).' '.$body, $google_domains[$args_get['name']])!==false) | |
{ | |
$body=array('Answer'=>array(array('type'=>1, 'data'=>$success_google_ip, 'TTL'=>60*5))); //google ip容易被封, 写死5分钟过期 | |
} | |
else | |
{ | |
$body=''; | |
} | |
} | |
else | |
{ | |
$body=@json_decode($body, 1); | |
} | |
//var_export(/*var_export($rsp_headers)."\n\n".*/$body); | |
if ($body) | |
{ | |
echo "===========\nfastest: ".$url_ori."\n"; | |
$statis[$success_google_ip]=@$statis[$success_google_ip]+1; | |
file_put_contents('statis.txt', var_export($statis, 1)); | |
//过滤掉非A记录答案 | |
$body['Answer']=is_array($body['Answer'])?$body['Answer']:array(); | |
foreach ($body['Answer'] as $k=>$answer) | |
{ | |
if ($answer['type']===1) //多个answer里第一个type为ip地址的就返回 | |
{ | |
$body['Answer'][$k]['TTL']=$force_ttl?$force_ttl:intval(@$answer['TTL']); | |
} | |
else | |
{ | |
unset($body['Answer'][$k]); | |
} | |
} | |
//发送答案给所有请求该域名的客户端 | |
send_answer($running_domain_tasks[$args_get['name']]['con_packs'], $args_get['name'], $body); | |
//创建的domain task也要清理 | |
end_domain_task($args_get['name']); | |
} | |
} | |
} | |
//结束超时的domain task | |
foreach ($running_domain_tasks as $domain=>$task) | |
{ | |
if (time()-$task['time']>$timeout+2) | |
{ | |
echo "domain task timeout(".$domain.")!\n"; | |
send_answer($task['con_packs'], $domain, array('Answer'=>array(array('type'=>1, 'data'=>'', 'TTL'=>'')))); | |
//创建的domain task也要清理 | |
end_domain_task($domain); | |
} | |
} | |
} | |
/*********************************************后面都是用到的函数*********************************************/ | |
function end_ipsock_con($con) | |
{ | |
global $ipsock_cons, $ipsock_con_datas, $running_domain_tasks; | |
foreach ($ipsock_con_datas[$con]['domain_packs'] as $domain_pack) | |
{ | |
unset($running_domain_tasks[$domain_pack['domain']]['con_packs'][$con.':'.$domain_pack['packet_id']]); //domain task回来后就少一个客户端连接需要回信了 | |
} | |
@fclose($con); //客户端断掉的连接(有feof信号)或者客户端意外中断(没有feof信号)服务端都要再断一次 | |
unset($ipsock_cons[$con]); //从轮询队列里移除 | |
unset($ipsock_con_datas[$con]); //从连接关联数据里移除 | |
//echo print_r($con, 1)." closed, left: ".print_r($ipsock_cons, 1)."\n"; | |
} | |
function end_domain_task($domain) | |
{ | |
global $queue, $running_domain_tasks; | |
echo date('i:s').'('.(time()-$running_domain_tasks[$domain]['time']).'s) end domain task: '.$domain."\n"; | |
//取消所有兄弟handle任务并关闭所有兄弟handles, 清理该域名的running domain task | |
foreach ($running_domain_tasks[$domain]['handles'] as $handle) | |
{ | |
curl_multi_remove_handle($queue, $handle); | |
curl_close($handle); | |
} | |
unset($running_domain_tasks[$domain]); | |
} | |
function send_answer($con_packs, $domain, $rtn) | |
{ | |
global $is_tcp, $cache; | |
//var_export($rtn);echo "\n"; | |
$answers=@$rtn['Answer']; | |
$answers=$answers?$answers:array(); | |
$ip=''; | |
$ttl=''; | |
foreach ($answers as $answer) | |
{ | |
if ($answer['type']===1) //多个answer里第一个type为ip地址的就返回 | |
{ | |
$ip=@$answer['data']; | |
$ttl=@$answer['TTL']; | |
break; | |
} | |
} | |
echo date('i:s').' answer: '.$ip."\n"; | |
//记到缓存 | |
if ($ip && !@$cache[$domain]) | |
{ | |
$cache[$domain]=array('time'=>time(), 'ip'=>$ip, 'ttl'=>$ttl); | |
//echo 'cached '; print_r($cache); | |
} | |
//如果还有需要回信的client connections就回信 | |
foreach ($con_packs as $con_pack) | |
{ | |
//发给客户端 | |
$res=header_data($con_pack['packet_id'], 1, $ip?1:0).question_data($domain).($ip?answer_data_a($ip, $ttl):''); | |
myfwrite($con_pack['con'], ($is_tcp?pack('n', strlen($res)):'').$res); | |
//file_put_contents('b.txt', ($is_tcp?pack('n', strlen($res)):'').$res."\n======================\n", FILE_APPEND); | |
} | |
} | |
function create_query_tasks($con, $packet_id, $domain) | |
{ | |
global $google_ips, $timeout, $queue, $running_domain_tasks, $google_domains; | |
if (!@$running_domain_tasks[$domain]) //没有正在运行的该domain task才去创建domain task | |
{ | |
echo 'new domain task: '.$domain.'('.$con.")\n"; | |
$running_domain_tasks[$domain]=array('con_packs'=>array(), 'handles'=>array(), 'time'=>time()); | |
$selected_google_ips=array_rand(array_flip($google_ips), 10); | |
foreach ($selected_google_ips as $google_ip) | |
{ | |
if (in_array($domain, array_keys($google_domains))) | |
{ | |
$url='https://'.$google_ip.'/?name='.$domain; | |
$host=$domain.':443'; | |
} | |
else | |
{ | |
$url='https://'.$google_ip.'/resolve?name='.$domain.'&type=A&dnssec=true#name='.$domain; //也用hash传变量方便请求返回的时候统一解析 | |
$host='dns.google.com:443'; | |
} | |
$ch=curl_to_host('GET', | |
$url, | |
array('Host'=>$host), | |
array(), | |
$resp_head, | |
$timeout | |
); | |
curl_multi_add_handle($queue, $ch); | |
$running_domain_tasks[$domain]['handles'][]=$ch; //记下一个domain task的10个handles, 后面有一个handle返回就要close掉所有兄弟handle | |
} | |
} | |
else | |
{ | |
echo 'duplicated domain task: '.$domain.'('.$con.':'.$packet_id.")\n"; | |
} | |
$running_domain_tasks[$domain]['con_packs'][$con.':'.$packet_id]=array('con'=>$con, 'packet_id'=>$packet_id); //往该domain task服务的客户端连接池里加一个客户端连接, task返回时向多个客户端(比如浏览器的多个线程)返回结果 | |
} | |
function get_domain_and_packet_id($data) | |
{ | |
global $is_tcp; | |
$RR_TYPES=array('A'=>1, | |
'NS'=>2, | |
'CNAME'=>5, | |
'MX'=>15, | |
'TXT'=>16, | |
'AAAA'=>28 | |
); | |
$types=array_flip($RR_TYPES); | |
$rtn=array(); | |
while ($data) | |
{ | |
$tmp=array('domain'=>'', 'packet_id'=>'', 'type'=>''); | |
if (strlen($data)<12) //忽略客户端不完整包或非查询包 | |
{ | |
$rtn[]=$tmp; | |
$data=''; | |
continue; | |
} | |
$data=$is_tcp?substr($data, 2):$data; | |
$data_header=header_parse($data); | |
//var_export($data_header);echo "\n"; | |
$tmp['packet_id']=$data_header['id']; | |
$data_question=substr($data, 12); | |
$data=strstr($data_question, "\0"); | |
$tmp['type']=@$types[hexdec(bin2hex(substr($data, 1, 2)))]; | |
$data_question=@substr(expand(strstr($data_question, "\0", 1)), 1); | |
echo "===============\n".date('i:s')." query: ".$data_question.':'.$tmp['type']."\n"; | |
$tmp['domain']=strtolower($data_question); | |
$rtn[]=$tmp; | |
$data=@substr($data, 5); | |
} | |
return $rtn; | |
} | |
function myfwrite($fd,$buf) { | |
$i=0; | |
while ($buf != "" and is_resource($fd)) { | |
$i=fwrite ($fd,$buf,strlen($buf)); | |
if ($i==false) { | |
if (!feof($fd)) continue; | |
break; | |
} | |
$buf=substr($buf,$i); | |
} | |
return $i; | |
} | |
//header | |
function header_data($packet_id=1, $qr=0, $c_ans=0) | |
{$packet_id=$packet_id; | |
$qr=$qr; //Query or Response | |
$oc=0; //Op Code | |
$aa=0; //Authoritative Answer | |
$tc=0; //TrunCation | |
$rd=1; //Recursion Desired | |
$ra=1; //Recursion Available | |
$z=0; //reserved | |
$rc=0; //Response Code | |
$c_quest=1; //items count in question | |
$c_ans=$c_ans; //items count in answer | |
$c_auth=0; //items count in authority | |
$c_add=0; //items count in additional | |
$data_put=pack('n6', | |
$packet_id, | |
($qr&0x1)<<15|($oc&0xf)<<11|($aa&0x1)<<10|($tc&0x1)<<9|($rd&0x1)<<8|($ra&0x1)<<7|($z&0x7)<<4|($rc &0xf), | |
$c_quest, | |
$c_ans, | |
$c_auth, | |
$c_add | |
); | |
return $data_put; | |
} | |
//question | |
function question_data($dn, $type='A') | |
{$RR_TYPES=array('A'=>1, | |
'NS'=>2, | |
'CNAME'=>5, | |
'MX'=>15, | |
'TXT'=>16, | |
'AAAA'=>28 | |
); | |
$RR_CLASS_IN=1; //IN means internet | |
$data_put=preg_replace(array("/\.([^.]+)/e"), | |
array('chr(strlen("\1"))."\1"'), | |
'.'.$dn | |
) | |
."\0" | |
.pack('n2', | |
$RR_TYPES[$type], | |
$RR_CLASS_IN | |
); | |
return $data_put; | |
} | |
//answer 'A' | |
function answer_data_a($ip, $ttl) | |
{$RR_TYPES=array('A'=>1, | |
'NS'=>2, | |
'CNAME'=>5, | |
'MX'=>15, | |
'TXT'=>16, | |
'AAAA'=>28 | |
); | |
$RR_CLASS_IN=1; //IN means internet | |
return "\xC0"."\x0C".pack('nnNn', $RR_TYPES['A'], $RR_CLASS_IN, $ttl, '4').@inet_pton($ip); //"C00C"表示是针对第一域名的答案 | |
} | |
//parse header | |
function header_parse($data) | |
{$data_get=array(); | |
$offset=0; | |
$data_get['id']=ord($data[$offset]) <<8 | ord($data[++$offset]); | |
++$offset; | |
$data_get['qr']=(ord($data[$offset]) >>7) & 0x1; | |
$data_get['oc']=(ord($data[$offset]) >>3) & 0xf; | |
$data_get['aa']=(ord($data[$offset]) >>2) & 0x1; | |
$data_get['tc']=(ord($data[$offset]) >>1) & 0x1; | |
$data_get['rd']=ord($data[$offset]) & 0x1; | |
++$offset; | |
$data_get['ra']=(ord($data[$offset]) >>7) & 0x1; | |
$data_get['z']=0; | |
$data_get['rc']=ord($data[$offset]) & 0xf; | |
$data_get['c_quest']=ord($data[++$offset]) <<8 | ord($data[++$offset]); | |
$data_get['c_ans']=ord($data[++$offset]) <<8 | ord($data[++$offset]); | |
$data_get['c_auth']=ord($data[++$offset]) <<8 | ord($data[++$offset]); | |
$data_get['c_add']= ord($data[++$offset]) <<8 | ord($data[++$offset]); | |
return $data_get; | |
} | |
//parse question | |
function question_parse($pkt) | |
{$name_length=strpos($pkt, "\0"); | |
$qname = substr(expand(substr($pkt, 0, $name_length)), 1); | |
$tmp = unpack('nqtype/nqclass', substr($pkt, $name_length+1, 4)); | |
$tmp['qname'] = $qname; | |
$tmp['length']=$name_length+5; | |
return $tmp; | |
} | |
//parse answer | |
function answer_parse($data, $cnt, $data_before) | |
{$rtn=array(); | |
$offset=-1; | |
for ($i=0;$i<$cnt;++$i) | |
{if (strlen($data)>=16) | |
{$offset+=2; //前两个字节'CXXX', 表示改答案对应整个response的'XXX'偏移的那个域名, 比如第一域名是"C00C" | |
$type=ord($data[++$offset]) <<8 | ord($data[++$offset]); | |
$class=ord($data[++$offset]) <<8 | ord($data[++$offset]); | |
} | |
$ttl=ord($data[++$offset]) <<24 | ord($data[++$offset]) <<16 | ord($data[++$offset]) <<8 | ord($data[++$offset]); | |
$length=ord($data[++$offset]) << 8 | ord($data[++$offset]); | |
if ($type==1) //'A' | |
{if ($length>4) | |
{$offset+=$length; | |
continue; | |
} | |
$rtn[]=array('ttl'=>$ttl, | |
'ip'=>ord($data[++$offset]).'.'.ord($data[++$offset]).'.'.ord($data[++$offset]).'.'.ord($data[++$offset]) | |
); | |
} | |
else if ($type==2) //'NS' | |
{$ndsname=expand(substr($data, $offset+1, $length)); | |
$ndsname=preg_replace('/>>>(.)<<</e', | |
'expand(substr($data_before, ord("\1")))', | |
$ndsname | |
); | |
$ndsname=trim($ndsname, '.'); | |
$rtn[]=array('ttl'=>$ttl, | |
'nsdname'=>$ndsname | |
); | |
$offset+=$length; | |
} | |
} | |
return $rtn; | |
} | |
function expand($result) | |
{return preg_replace('/(.)(.*)/se', //加/s因为为长度为10时没匹配到 | |
'($cnt=@ord("\1")) | |
?($cnt===192?">>>":".").substr(($str=@strtr(\'\2\', array(\'\"\'=>\'"\', \'\\\\0\'=>"\x00"))), 0, $cnt).($cnt===192?"<<<":"").expand(substr($str, $cnt)) | |
:"" | |
', | |
$result | |
); | |
} | |
function &curl_to_host($method, $url, $headers, $data, &$resp_headers, $total_timeout=20) | |
{$ch=curl_init($url); | |
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5); | |
curl_setopt($ch, CURLOPT_TIMEOUT, $total_timeout); | |
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); | |
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false); | |
curl_setopt($ch, CURLOPT_HEADER, 1); | |
curl_setopt($ch, CURLOPT_NOSIGNAL, true); //skip slow dns resolve | |
if (stripos($url, 'https')===0) | |
{curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); | |
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); | |
} | |
if ($method=='POST') | |
{curl_setopt($ch, CURLOPT_POST, true); | |
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data)); | |
} | |
foreach ($headers as $k=>$v) | |
{$headers[$k]=str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $k)))).': '.$v; | |
} | |
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); | |
return $ch; | |
} | |
function parse_body_headers($rtn, &$body, &$rsp_headers) | |
{$rtn=explode("\r\n\r\nHTTP/", $rtn, 2); //to deal with "HTTP/1.1 100 Continue\r\n\r\nHTTP/1.1 200 OK...\r\n\r\n..." header | |
$rtn=(count($rtn)>1 ? 'HTTP/' : '').array_pop($rtn); | |
@list($str_resp_headers, $body)=explode("\r\n\r\n", $rtn, 2); | |
$str_resp_headers=explode("\r\n", $str_resp_headers); | |
array_shift($str_resp_headers); //get rid of "HTTP/1.1 200 OK" | |
$rsp_headers=array(); | |
foreach ($str_resp_headers as $k=>$v) | |
{$v=explode(': ', $v, 2); | |
$rsp_headers[$v[0]]=$v[1]; | |
} | |
} | |
?> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment