14e3e87e4SAndreas Gohr<?php 24e3e87e4SAndreas Gohr 34e3e87e4SAndreas Gohrnamespace dokuwiki\plugin\upgrade\HTTP; 44e3e87e4SAndreas Gohr 5eb7336c4SAndreas Gohrif(!defined('HTTP_NL')) define('HTTP_NL',"\r\n"); 64e3e87e4SAndreas Gohr 74e3e87e4SAndreas Gohr 84e3e87e4SAndreas Gohr/** 94e3e87e4SAndreas Gohr * This class implements a basic HTTP client 104e3e87e4SAndreas Gohr * 114e3e87e4SAndreas Gohr * It supports POST and GET, Proxy usage, basic authentication, 124e3e87e4SAndreas Gohr * handles cookies and referers. It is based upon the httpclient 134e3e87e4SAndreas Gohr * function from the VideoDB project. 144e3e87e4SAndreas Gohr * 154e3e87e4SAndreas Gohr * @link http://www.splitbrain.org/go/videodb 164e3e87e4SAndreas Gohr * @author Andreas Goetz <cpuidle@gmx.de> 174e3e87e4SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 184e3e87e4SAndreas Gohr * @author Tobias Sarnowski <sarnowski@new-thoughts.org> 194e3e87e4SAndreas Gohr */ 204e3e87e4SAndreas Gohrclass HTTPClient { 214e3e87e4SAndreas Gohr //set these if you like 224e3e87e4SAndreas Gohr public $agent; // User agent 234e3e87e4SAndreas Gohr public $http; // HTTP version defaults to 1.0 244e3e87e4SAndreas Gohr public $timeout; // read timeout (seconds) 254e3e87e4SAndreas Gohr public $cookies; 264e3e87e4SAndreas Gohr public $referer; 274e3e87e4SAndreas Gohr public $max_redirect; 284e3e87e4SAndreas Gohr public $max_bodysize; 294e3e87e4SAndreas Gohr public $max_bodysize_abort = true; // if set, abort if the response body is bigger than max_bodysize 304e3e87e4SAndreas Gohr public $header_regexp; // if set this RE must match against the headers, else abort 314e3e87e4SAndreas Gohr public $headers; 324e3e87e4SAndreas Gohr public $debug; 334e3e87e4SAndreas Gohr public $start = 0.0; // for timings 344e3e87e4SAndreas Gohr public $keep_alive = true; // keep alive rocks 354e3e87e4SAndreas Gohr 364e3e87e4SAndreas Gohr // don't set these, read on error 374e3e87e4SAndreas Gohr public $error; 384e3e87e4SAndreas Gohr public $redirect_count; 394e3e87e4SAndreas Gohr 404e3e87e4SAndreas Gohr // read these after a successful request 414e3e87e4SAndreas Gohr public $status; 424e3e87e4SAndreas Gohr public $resp_body; 434e3e87e4SAndreas Gohr public $resp_headers; 444e3e87e4SAndreas Gohr 454e3e87e4SAndreas Gohr // set these to do basic authentication 464e3e87e4SAndreas Gohr public $user; 474e3e87e4SAndreas Gohr public $pass; 484e3e87e4SAndreas Gohr 494e3e87e4SAndreas Gohr // set these if you need to use a proxy 504e3e87e4SAndreas Gohr public $proxy_host; 514e3e87e4SAndreas Gohr public $proxy_port; 524e3e87e4SAndreas Gohr public $proxy_user; 534e3e87e4SAndreas Gohr public $proxy_pass; 544e3e87e4SAndreas Gohr public $proxy_ssl; //boolean set to true if your proxy needs SSL 554e3e87e4SAndreas Gohr public $proxy_except; // regexp of URLs to exclude from proxy 564e3e87e4SAndreas Gohr 574e3e87e4SAndreas Gohr // list of kept alive connections 584e3e87e4SAndreas Gohr protected static $connections = array(); 594e3e87e4SAndreas Gohr 604e3e87e4SAndreas Gohr // what we use as boundary on multipart/form-data posts 614e3e87e4SAndreas Gohr protected $boundary = '---DokuWikiHTTPClient--4523452351'; 624e3e87e4SAndreas Gohr 634e3e87e4SAndreas Gohr /** 644e3e87e4SAndreas Gohr * Constructor. 654e3e87e4SAndreas Gohr * 664e3e87e4SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 674e3e87e4SAndreas Gohr */ 684e3e87e4SAndreas Gohr public function __construct(){ 694e3e87e4SAndreas Gohr $this->agent = 'Mozilla/4.0 (compatible; DokuWiki HTTP Client; '.PHP_OS.')'; 704e3e87e4SAndreas Gohr $this->timeout = 15; 714e3e87e4SAndreas Gohr $this->cookies = array(); 724e3e87e4SAndreas Gohr $this->referer = ''; 734e3e87e4SAndreas Gohr $this->max_redirect = 3; 744e3e87e4SAndreas Gohr $this->redirect_count = 0; 754e3e87e4SAndreas Gohr $this->status = 0; 764e3e87e4SAndreas Gohr $this->headers = array(); 774e3e87e4SAndreas Gohr $this->http = '1.0'; 784e3e87e4SAndreas Gohr $this->debug = false; 794e3e87e4SAndreas Gohr $this->max_bodysize = 0; 804e3e87e4SAndreas Gohr $this->header_regexp= ''; 814e3e87e4SAndreas Gohr if(extension_loaded('zlib')) $this->headers['Accept-encoding'] = 'gzip'; 824e3e87e4SAndreas Gohr $this->headers['Accept'] = 'text/xml,application/xml,application/xhtml+xml,'. 834e3e87e4SAndreas Gohr 'text/html,text/plain,image/png,image/jpeg,image/gif,*/*'; 844e3e87e4SAndreas Gohr $this->headers['Accept-Language'] = 'en-us'; 854e3e87e4SAndreas Gohr } 864e3e87e4SAndreas Gohr 874e3e87e4SAndreas Gohr 884e3e87e4SAndreas Gohr /** 894e3e87e4SAndreas Gohr * Simple function to do a GET request 904e3e87e4SAndreas Gohr * 914e3e87e4SAndreas Gohr * Returns the wanted page or false on an error; 924e3e87e4SAndreas Gohr * 934e3e87e4SAndreas Gohr * @param string $url The URL to fetch 944e3e87e4SAndreas Gohr * @param bool $sloppy304 Return body on 304 not modified 954e3e87e4SAndreas Gohr * @return false|string response body, false on error 964e3e87e4SAndreas Gohr * 974e3e87e4SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 984e3e87e4SAndreas Gohr */ 994e3e87e4SAndreas Gohr public function get($url,$sloppy304=false){ 1004e3e87e4SAndreas Gohr if(!$this->sendRequest($url)) return false; 1014e3e87e4SAndreas Gohr if($this->status == 304 && $sloppy304) return $this->resp_body; 1024e3e87e4SAndreas Gohr if($this->status < 200 || $this->status > 206) return false; 1034e3e87e4SAndreas Gohr return $this->resp_body; 1044e3e87e4SAndreas Gohr } 1054e3e87e4SAndreas Gohr 1064e3e87e4SAndreas Gohr /** 1074e3e87e4SAndreas Gohr * Simple function to do a GET request with given parameters 1084e3e87e4SAndreas Gohr * 1094e3e87e4SAndreas Gohr * Returns the wanted page or false on an error. 1104e3e87e4SAndreas Gohr * 1114e3e87e4SAndreas Gohr * This is a convenience wrapper around get(). The given parameters 1124e3e87e4SAndreas Gohr * will be correctly encoded and added to the given base URL. 1134e3e87e4SAndreas Gohr * 1144e3e87e4SAndreas Gohr * @param string $url The URL to fetch 1154e3e87e4SAndreas Gohr * @param array $data Associative array of parameters 1164e3e87e4SAndreas Gohr * @param bool $sloppy304 Return body on 304 not modified 1174e3e87e4SAndreas Gohr * @return false|string response body, false on error 1184e3e87e4SAndreas Gohr * 1194e3e87e4SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 1204e3e87e4SAndreas Gohr */ 1214e3e87e4SAndreas Gohr public function dget($url,$data,$sloppy304=false){ 1224e3e87e4SAndreas Gohr if(strpos($url,'?')){ 1234e3e87e4SAndreas Gohr $url .= '&'; 1244e3e87e4SAndreas Gohr }else{ 1254e3e87e4SAndreas Gohr $url .= '?'; 1264e3e87e4SAndreas Gohr } 1274e3e87e4SAndreas Gohr $url .= $this->postEncode($data); 1284e3e87e4SAndreas Gohr return $this->get($url,$sloppy304); 1294e3e87e4SAndreas Gohr } 1304e3e87e4SAndreas Gohr 1314e3e87e4SAndreas Gohr /** 1324e3e87e4SAndreas Gohr * Simple function to do a POST request 1334e3e87e4SAndreas Gohr * 1344e3e87e4SAndreas Gohr * Returns the resulting page or false on an error; 1354e3e87e4SAndreas Gohr * 1364e3e87e4SAndreas Gohr * @param string $url The URL to fetch 1374e3e87e4SAndreas Gohr * @param array $data Associative array of parameters 1384e3e87e4SAndreas Gohr * @return false|string response body, false on error 1394e3e87e4SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 1404e3e87e4SAndreas Gohr */ 1414e3e87e4SAndreas Gohr public function post($url,$data){ 1424e3e87e4SAndreas Gohr if(!$this->sendRequest($url,$data,'POST')) return false; 1434e3e87e4SAndreas Gohr if($this->status < 200 || $this->status > 206) return false; 1444e3e87e4SAndreas Gohr return $this->resp_body; 1454e3e87e4SAndreas Gohr } 1464e3e87e4SAndreas Gohr 1474e3e87e4SAndreas Gohr /** 1484e3e87e4SAndreas Gohr * Send an HTTP request 1494e3e87e4SAndreas Gohr * 1504e3e87e4SAndreas Gohr * This method handles the whole HTTP communication. It respects set proxy settings, 1514e3e87e4SAndreas Gohr * builds the request headers, follows redirects and parses the response. 1524e3e87e4SAndreas Gohr * 1534e3e87e4SAndreas Gohr * Post data should be passed as associative array. When passed as string it will be 1544e3e87e4SAndreas Gohr * sent as is. You will need to setup your own Content-Type header then. 1554e3e87e4SAndreas Gohr * 1564e3e87e4SAndreas Gohr * @param string $url - the complete URL 1574e3e87e4SAndreas Gohr * @param mixed $data - the post data either as array or raw data 1584e3e87e4SAndreas Gohr * @param string $method - HTTP Method usually GET or POST. 1594e3e87e4SAndreas Gohr * @return bool - true on success 1604e3e87e4SAndreas Gohr * 1614e3e87e4SAndreas Gohr * @author Andreas Goetz <cpuidle@gmx.de> 1624e3e87e4SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 1634e3e87e4SAndreas Gohr */ 1644e3e87e4SAndreas Gohr public function sendRequest($url,$data='',$method='GET'){ 1654e3e87e4SAndreas Gohr $this->start = $this->time(); 1664e3e87e4SAndreas Gohr $this->error = ''; 1674e3e87e4SAndreas Gohr $this->status = 0; 1684e3e87e4SAndreas Gohr $this->resp_body = ''; 1694e3e87e4SAndreas Gohr $this->resp_headers = array(); 1704e3e87e4SAndreas Gohr 1714e3e87e4SAndreas Gohr // save unencoded data for recursive call 1724e3e87e4SAndreas Gohr $unencodedData = $data; 1734e3e87e4SAndreas Gohr 1744e3e87e4SAndreas Gohr // don't accept gzip if truncated bodies might occur 1754e3e87e4SAndreas Gohr if($this->max_bodysize && 1764e3e87e4SAndreas Gohr !$this->max_bodysize_abort && 1774e3e87e4SAndreas Gohr isset($this->headers['Accept-encoding']) && 1784e3e87e4SAndreas Gohr $this->headers['Accept-encoding'] == 'gzip'){ 1794e3e87e4SAndreas Gohr unset($this->headers['Accept-encoding']); 1804e3e87e4SAndreas Gohr } 1814e3e87e4SAndreas Gohr 1824e3e87e4SAndreas Gohr // parse URL into bits 1834e3e87e4SAndreas Gohr $uri = parse_url($url); 1844e3e87e4SAndreas Gohr $server = $uri['host']; 1854e3e87e4SAndreas Gohr $path = !empty($uri['path']) ? $uri['path'] : '/'; 1864e3e87e4SAndreas Gohr $uriPort = !empty($uri['port']) ? $uri['port'] : null; 1874e3e87e4SAndreas Gohr if(!empty($uri['query'])) $path .= '?'.$uri['query']; 1884e3e87e4SAndreas Gohr if(isset($uri['user'])) $this->user = $uri['user']; 1894e3e87e4SAndreas Gohr if(isset($uri['pass'])) $this->pass = $uri['pass']; 1904e3e87e4SAndreas Gohr 1914e3e87e4SAndreas Gohr // proxy setup 1924e3e87e4SAndreas Gohr if($this->useProxyForUrl($url)){ 1934e3e87e4SAndreas Gohr $request_url = $url; 1944e3e87e4SAndreas Gohr $server = $this->proxy_host; 1954e3e87e4SAndreas Gohr $port = $this->proxy_port; 1964e3e87e4SAndreas Gohr if (empty($port)) $port = 8080; 1974e3e87e4SAndreas Gohr $use_tls = $this->proxy_ssl; 1984e3e87e4SAndreas Gohr }else{ 1994e3e87e4SAndreas Gohr $request_url = $path; 2004e3e87e4SAndreas Gohr $port = $uriPort ?: ($uri['scheme'] == 'https' ? 443 : 80); 2014e3e87e4SAndreas Gohr $use_tls = ($uri['scheme'] == 'https'); 2024e3e87e4SAndreas Gohr } 2034e3e87e4SAndreas Gohr 2044e3e87e4SAndreas Gohr // add SSL stream prefix if needed - needs SSL support in PHP 2054e3e87e4SAndreas Gohr if($use_tls) { 2064e3e87e4SAndreas Gohr if(!in_array('ssl', stream_get_transports())) { 2074e3e87e4SAndreas Gohr $this->status = -200; 2084e3e87e4SAndreas Gohr $this->error = 'This PHP version does not support SSL - cannot connect to server'; 2094e3e87e4SAndreas Gohr } 2104e3e87e4SAndreas Gohr $server = 'ssl://'.$server; 2114e3e87e4SAndreas Gohr } 2124e3e87e4SAndreas Gohr 2134e3e87e4SAndreas Gohr // prepare headers 2144e3e87e4SAndreas Gohr $headers = $this->headers; 2154e3e87e4SAndreas Gohr $headers['Host'] = $uri['host'] 2164e3e87e4SAndreas Gohr . ($uriPort ? ':' . $uriPort : ''); 2174e3e87e4SAndreas Gohr $headers['User-Agent'] = $this->agent; 2184e3e87e4SAndreas Gohr $headers['Referer'] = $this->referer; 2194e3e87e4SAndreas Gohr 2204e3e87e4SAndreas Gohr if($method == 'POST'){ 2214e3e87e4SAndreas Gohr if(is_array($data)){ 2224e3e87e4SAndreas Gohr if (empty($headers['Content-Type'])) { 2234e3e87e4SAndreas Gohr $headers['Content-Type'] = null; 2244e3e87e4SAndreas Gohr } 2254e3e87e4SAndreas Gohr switch ($headers['Content-Type']) { 2264e3e87e4SAndreas Gohr case 'multipart/form-data': 2274e3e87e4SAndreas Gohr $headers['Content-Type'] = 'multipart/form-data; boundary=' . $this->boundary; 2284e3e87e4SAndreas Gohr $data = $this->postMultipartEncode($data); 2294e3e87e4SAndreas Gohr break; 2304e3e87e4SAndreas Gohr default: 2314e3e87e4SAndreas Gohr $headers['Content-Type'] = 'application/x-www-form-urlencoded'; 2324e3e87e4SAndreas Gohr $data = $this->postEncode($data); 2334e3e87e4SAndreas Gohr } 2344e3e87e4SAndreas Gohr } 2354e3e87e4SAndreas Gohr }elseif($method == 'GET'){ 2364e3e87e4SAndreas Gohr $data = ''; //no data allowed on GET requests 2374e3e87e4SAndreas Gohr } 2384e3e87e4SAndreas Gohr 2394e3e87e4SAndreas Gohr $contentlength = strlen($data); 2404e3e87e4SAndreas Gohr if($contentlength) { 2414e3e87e4SAndreas Gohr $headers['Content-Length'] = $contentlength; 2424e3e87e4SAndreas Gohr } 2434e3e87e4SAndreas Gohr 2444e3e87e4SAndreas Gohr if($this->user) { 2454e3e87e4SAndreas Gohr $headers['Authorization'] = 'Basic '.base64_encode($this->user.':'.$this->pass); 2464e3e87e4SAndreas Gohr } 2474e3e87e4SAndreas Gohr if($this->proxy_user) { 2484e3e87e4SAndreas Gohr $headers['Proxy-Authorization'] = 'Basic '.base64_encode($this->proxy_user.':'.$this->proxy_pass); 2494e3e87e4SAndreas Gohr } 2504e3e87e4SAndreas Gohr 2514e3e87e4SAndreas Gohr // already connected? 2524e3e87e4SAndreas Gohr $connectionId = $this->uniqueConnectionId($server,$port); 2534e3e87e4SAndreas Gohr $this->debug('connection pool', self::$connections); 2544e3e87e4SAndreas Gohr $socket = null; 2554e3e87e4SAndreas Gohr if (isset(self::$connections[$connectionId])) { 2564e3e87e4SAndreas Gohr $this->debug('reusing connection', $connectionId); 2574e3e87e4SAndreas Gohr $socket = self::$connections[$connectionId]; 2584e3e87e4SAndreas Gohr } 2594e3e87e4SAndreas Gohr if (is_null($socket) || feof($socket)) { 2604e3e87e4SAndreas Gohr $this->debug('opening connection', $connectionId); 2614e3e87e4SAndreas Gohr // open socket 2624e3e87e4SAndreas Gohr $socket = @fsockopen($server,$port,$errno, $errstr, $this->timeout); 2634e3e87e4SAndreas Gohr if (!$socket){ 2644e3e87e4SAndreas Gohr $this->status = -100; 2654e3e87e4SAndreas Gohr $this->error = "Could not connect to $server:$port\n$errstr ($errno)"; 2664e3e87e4SAndreas Gohr return false; 2674e3e87e4SAndreas Gohr } 2684e3e87e4SAndreas Gohr 2694e3e87e4SAndreas Gohr // try establish a CONNECT tunnel for SSL 2704e3e87e4SAndreas Gohr try { 2714e3e87e4SAndreas Gohr if($this->ssltunnel($socket, $request_url)){ 2724e3e87e4SAndreas Gohr // no keep alive for tunnels 2734e3e87e4SAndreas Gohr $this->keep_alive = false; 2744e3e87e4SAndreas Gohr // tunnel is authed already 2754e3e87e4SAndreas Gohr if(isset($headers['Proxy-Authentication'])) unset($headers['Proxy-Authentication']); 2764e3e87e4SAndreas Gohr } 2774e3e87e4SAndreas Gohr } catch (HTTPClientException $e) { 2784e3e87e4SAndreas Gohr $this->status = $e->getCode(); 2794e3e87e4SAndreas Gohr $this->error = $e->getMessage(); 2804e3e87e4SAndreas Gohr fclose($socket); 2814e3e87e4SAndreas Gohr return false; 2824e3e87e4SAndreas Gohr } 2834e3e87e4SAndreas Gohr 2844e3e87e4SAndreas Gohr // keep alive? 2854e3e87e4SAndreas Gohr if ($this->keep_alive) { 2864e3e87e4SAndreas Gohr self::$connections[$connectionId] = $socket; 2874e3e87e4SAndreas Gohr } else { 2884e3e87e4SAndreas Gohr unset(self::$connections[$connectionId]); 2894e3e87e4SAndreas Gohr } 2904e3e87e4SAndreas Gohr } 2914e3e87e4SAndreas Gohr 2924e3e87e4SAndreas Gohr if ($this->keep_alive && !$this->useProxyForUrl($request_url)) { 2934e3e87e4SAndreas Gohr // RFC 2068, section 19.7.1: A client MUST NOT send the Keep-Alive 2944e3e87e4SAndreas Gohr // connection token to a proxy server. We still do keep the connection the 2954e3e87e4SAndreas Gohr // proxy alive (well except for CONNECT tunnels) 2964e3e87e4SAndreas Gohr $headers['Connection'] = 'Keep-Alive'; 2974e3e87e4SAndreas Gohr } else { 2984e3e87e4SAndreas Gohr $headers['Connection'] = 'Close'; 2994e3e87e4SAndreas Gohr } 3004e3e87e4SAndreas Gohr 3014e3e87e4SAndreas Gohr try { 3024e3e87e4SAndreas Gohr //set non-blocking 3034e3e87e4SAndreas Gohr stream_set_blocking($socket, 0); 3044e3e87e4SAndreas Gohr 3054e3e87e4SAndreas Gohr // build request 3064e3e87e4SAndreas Gohr $request = "$method $request_url HTTP/".$this->http.HTTP_NL; 3074e3e87e4SAndreas Gohr $request .= $this->buildHeaders($headers); 3084e3e87e4SAndreas Gohr $request .= $this->getCookies(); 3094e3e87e4SAndreas Gohr $request .= HTTP_NL; 3104e3e87e4SAndreas Gohr $request .= $data; 3114e3e87e4SAndreas Gohr 3124e3e87e4SAndreas Gohr $this->debug('request',$request); 3134e3e87e4SAndreas Gohr $this->sendData($socket, $request, 'request'); 3144e3e87e4SAndreas Gohr 3154e3e87e4SAndreas Gohr // read headers from socket 3164e3e87e4SAndreas Gohr $r_headers = ''; 3174e3e87e4SAndreas Gohr do{ 3184e3e87e4SAndreas Gohr $r_line = $this->readLine($socket, 'headers'); 3194e3e87e4SAndreas Gohr $r_headers .= $r_line; 3204e3e87e4SAndreas Gohr }while($r_line != "\r\n" && $r_line != "\n"); 3214e3e87e4SAndreas Gohr 3224e3e87e4SAndreas Gohr $this->debug('response headers',$r_headers); 3234e3e87e4SAndreas Gohr 3244e3e87e4SAndreas Gohr // check if expected body size exceeds allowance 3254e3e87e4SAndreas Gohr if($this->max_bodysize && preg_match('/\r?\nContent-Length:\s*(\d+)\r?\n/i',$r_headers,$match)){ 3264e3e87e4SAndreas Gohr if($match[1] > $this->max_bodysize){ 3274e3e87e4SAndreas Gohr if ($this->max_bodysize_abort) 3284e3e87e4SAndreas Gohr throw new HTTPClientException('Reported content length exceeds allowed response size'); 3294e3e87e4SAndreas Gohr else 3304e3e87e4SAndreas Gohr $this->error = 'Reported content length exceeds allowed response size'; 3314e3e87e4SAndreas Gohr } 3324e3e87e4SAndreas Gohr } 3334e3e87e4SAndreas Gohr 3344e3e87e4SAndreas Gohr // get Status 3354e3e87e4SAndreas Gohr if (!preg_match('/^HTTP\/(\d\.\d)\s*(\d+).*?\n/s', $r_headers, $m)) 3364e3e87e4SAndreas Gohr throw new HTTPClientException('Server returned bad answer '.$r_headers); 3374e3e87e4SAndreas Gohr 3384e3e87e4SAndreas Gohr $this->status = $m[2]; 3394e3e87e4SAndreas Gohr 3404e3e87e4SAndreas Gohr // handle headers and cookies 3414e3e87e4SAndreas Gohr $this->resp_headers = $this->parseHeaders($r_headers); 3424e3e87e4SAndreas Gohr if(isset($this->resp_headers['set-cookie'])){ 3434e3e87e4SAndreas Gohr foreach ((array) $this->resp_headers['set-cookie'] as $cookie){ 344*fc9fd1daSAndreas Gohr list($cookie) = array_pad(explode(';', $cookie, 2), 2, ''); 345*fc9fd1daSAndreas Gohr list($key, $val) = array_pad(explode('=', $cookie, 2), 2, ''); 3464e3e87e4SAndreas Gohr $key = trim($key); 3474e3e87e4SAndreas Gohr if($val == 'deleted'){ 3484e3e87e4SAndreas Gohr if(isset($this->cookies[$key])){ 3494e3e87e4SAndreas Gohr unset($this->cookies[$key]); 3504e3e87e4SAndreas Gohr } 3514e3e87e4SAndreas Gohr }elseif($key){ 3524e3e87e4SAndreas Gohr $this->cookies[$key] = $val; 3534e3e87e4SAndreas Gohr } 3544e3e87e4SAndreas Gohr } 3554e3e87e4SAndreas Gohr } 3564e3e87e4SAndreas Gohr 3574e3e87e4SAndreas Gohr $this->debug('Object headers',$this->resp_headers); 3584e3e87e4SAndreas Gohr 3594e3e87e4SAndreas Gohr // check server status code to follow redirect 3604e3e87e4SAndreas Gohr if(in_array($this->status, [301, 302, 303, 307, 308])){ 3614e3e87e4SAndreas Gohr if (empty($this->resp_headers['location'])){ 3624e3e87e4SAndreas Gohr throw new HTTPClientException('Redirect but no Location Header found'); 3634e3e87e4SAndreas Gohr }elseif($this->redirect_count == $this->max_redirect){ 3644e3e87e4SAndreas Gohr throw new HTTPClientException('Maximum number of redirects exceeded'); 3654e3e87e4SAndreas Gohr }else{ 3664e3e87e4SAndreas Gohr // close the connection because we don't handle content retrieval here 3674e3e87e4SAndreas Gohr // that's the easiest way to clean up the connection 3684e3e87e4SAndreas Gohr fclose($socket); 3694e3e87e4SAndreas Gohr unset(self::$connections[$connectionId]); 3704e3e87e4SAndreas Gohr 3714e3e87e4SAndreas Gohr $this->redirect_count++; 3724e3e87e4SAndreas Gohr $this->referer = $url; 3734e3e87e4SAndreas Gohr // handle non-RFC-compliant relative redirects 3744e3e87e4SAndreas Gohr if (!preg_match('/^http/i', $this->resp_headers['location'])){ 3754e3e87e4SAndreas Gohr if($this->resp_headers['location'][0] != '/'){ 3764e3e87e4SAndreas Gohr $this->resp_headers['location'] = $uri['scheme'].'://'.$uri['host'].':'.$uriPort. 3774e3e87e4SAndreas Gohr dirname($path).'/'.$this->resp_headers['location']; 3784e3e87e4SAndreas Gohr }else{ 3794e3e87e4SAndreas Gohr $this->resp_headers['location'] = $uri['scheme'].'://'.$uri['host'].':'.$uriPort. 3804e3e87e4SAndreas Gohr $this->resp_headers['location']; 3814e3e87e4SAndreas Gohr } 3824e3e87e4SAndreas Gohr } 3834e3e87e4SAndreas Gohr if($this->status == 307 || $this->status == 308) { 3844e3e87e4SAndreas Gohr // perform redirected request, same method as before (required by RFC) 3854e3e87e4SAndreas Gohr return $this->sendRequest($this->resp_headers['location'],$unencodedData,$method); 3864e3e87e4SAndreas Gohr }else{ 3874e3e87e4SAndreas Gohr // perform redirected request, always via GET (required by RFC) 3884e3e87e4SAndreas Gohr return $this->sendRequest($this->resp_headers['location'],array(),'GET'); 3894e3e87e4SAndreas Gohr } 3904e3e87e4SAndreas Gohr } 3914e3e87e4SAndreas Gohr } 3924e3e87e4SAndreas Gohr 3934e3e87e4SAndreas Gohr // check if headers are as expected 3944e3e87e4SAndreas Gohr if($this->header_regexp && !preg_match($this->header_regexp,$r_headers)) 3954e3e87e4SAndreas Gohr throw new HTTPClientException('The received headers did not match the given regexp'); 3964e3e87e4SAndreas Gohr 3974e3e87e4SAndreas Gohr //read body (with chunked encoding if needed) 3984e3e87e4SAndreas Gohr $r_body = ''; 3994e3e87e4SAndreas Gohr if( 4004e3e87e4SAndreas Gohr ( 4014e3e87e4SAndreas Gohr isset($this->resp_headers['transfer-encoding']) && 4024e3e87e4SAndreas Gohr $this->resp_headers['transfer-encoding'] == 'chunked' 4034e3e87e4SAndreas Gohr ) || ( 4044e3e87e4SAndreas Gohr isset($this->resp_headers['transfer-coding']) && 4054e3e87e4SAndreas Gohr $this->resp_headers['transfer-coding'] == 'chunked' 4064e3e87e4SAndreas Gohr ) 4074e3e87e4SAndreas Gohr ) { 4084e3e87e4SAndreas Gohr $abort = false; 4094e3e87e4SAndreas Gohr do { 4104e3e87e4SAndreas Gohr $chunk_size = ''; 4114e3e87e4SAndreas Gohr while (preg_match('/^[a-zA-Z0-9]?$/',$byte=$this->readData($socket,1,'chunk'))){ 4124e3e87e4SAndreas Gohr // read chunksize until \r 4134e3e87e4SAndreas Gohr $chunk_size .= $byte; 4144e3e87e4SAndreas Gohr if (strlen($chunk_size) > 128) // set an abritrary limit on the size of chunks 4154e3e87e4SAndreas Gohr throw new HTTPClientException('Allowed response size exceeded'); 4164e3e87e4SAndreas Gohr } 4174e3e87e4SAndreas Gohr $this->readLine($socket, 'chunk'); // readtrailing \n 4184e3e87e4SAndreas Gohr $chunk_size = hexdec($chunk_size); 4194e3e87e4SAndreas Gohr 4204e3e87e4SAndreas Gohr if($this->max_bodysize && $chunk_size+strlen($r_body) > $this->max_bodysize){ 4214e3e87e4SAndreas Gohr if ($this->max_bodysize_abort) 4224e3e87e4SAndreas Gohr throw new HTTPClientException('Allowed response size exceeded'); 4234e3e87e4SAndreas Gohr $this->error = 'Allowed response size exceeded'; 4244e3e87e4SAndreas Gohr $chunk_size = $this->max_bodysize - strlen($r_body); 4254e3e87e4SAndreas Gohr $abort = true; 4264e3e87e4SAndreas Gohr } 4274e3e87e4SAndreas Gohr 4284e3e87e4SAndreas Gohr if ($chunk_size > 0) { 4294e3e87e4SAndreas Gohr $r_body .= $this->readData($socket, $chunk_size, 'chunk'); 4304e3e87e4SAndreas Gohr $this->readData($socket, 2, 'chunk'); // read trailing \r\n 4314e3e87e4SAndreas Gohr } 4324e3e87e4SAndreas Gohr } while ($chunk_size && !$abort); 4334e3e87e4SAndreas Gohr }elseif(isset($this->resp_headers['content-length']) && !isset($this->resp_headers['transfer-encoding'])){ 4344e3e87e4SAndreas Gohr /* RFC 2616 4354e3e87e4SAndreas Gohr * If a message is received with both a Transfer-Encoding header field and a Content-Length 4364e3e87e4SAndreas Gohr * header field, the latter MUST be ignored. 4374e3e87e4SAndreas Gohr */ 4384e3e87e4SAndreas Gohr 4394e3e87e4SAndreas Gohr // read up to the content-length or max_bodysize 4404e3e87e4SAndreas Gohr // for keep alive we need to read the whole message to clean up the socket for the next read 4414e3e87e4SAndreas Gohr if( 4424e3e87e4SAndreas Gohr !$this->keep_alive && 4434e3e87e4SAndreas Gohr $this->max_bodysize && 4444e3e87e4SAndreas Gohr $this->max_bodysize < $this->resp_headers['content-length'] 4454e3e87e4SAndreas Gohr ) { 4464e3e87e4SAndreas Gohr $length = $this->max_bodysize + 1; 4474e3e87e4SAndreas Gohr }else{ 4484e3e87e4SAndreas Gohr $length = $this->resp_headers['content-length']; 4494e3e87e4SAndreas Gohr } 4504e3e87e4SAndreas Gohr 4514e3e87e4SAndreas Gohr $r_body = $this->readData($socket, $length, 'response (content-length limited)', true); 4524e3e87e4SAndreas Gohr }elseif( !isset($this->resp_headers['transfer-encoding']) && $this->max_bodysize && !$this->keep_alive){ 4534e3e87e4SAndreas Gohr $r_body = $this->readData($socket, $this->max_bodysize+1, 'response (content-length limited)', true); 4544e3e87e4SAndreas Gohr } elseif ((int)$this->status === 204) { 4554e3e87e4SAndreas Gohr // request has no content 4564e3e87e4SAndreas Gohr } else{ 4574e3e87e4SAndreas Gohr // read entire socket 4584e3e87e4SAndreas Gohr while (!feof($socket)) { 4594e3e87e4SAndreas Gohr $r_body .= $this->readData($socket, 4096, 'response (unlimited)', true); 4604e3e87e4SAndreas Gohr } 4614e3e87e4SAndreas Gohr } 4624e3e87e4SAndreas Gohr 4634e3e87e4SAndreas Gohr // recheck body size, we might have read max_bodysize+1 or even the whole body, so we abort late here 4644e3e87e4SAndreas Gohr if($this->max_bodysize){ 4654e3e87e4SAndreas Gohr if(strlen($r_body) > $this->max_bodysize){ 4664e3e87e4SAndreas Gohr if ($this->max_bodysize_abort) { 4674e3e87e4SAndreas Gohr throw new HTTPClientException('Allowed response size exceeded'); 4684e3e87e4SAndreas Gohr } else { 4694e3e87e4SAndreas Gohr $this->error = 'Allowed response size exceeded'; 4704e3e87e4SAndreas Gohr } 4714e3e87e4SAndreas Gohr } 4724e3e87e4SAndreas Gohr } 4734e3e87e4SAndreas Gohr 4744e3e87e4SAndreas Gohr } catch (HTTPClientException $err) { 4754e3e87e4SAndreas Gohr $this->error = $err->getMessage(); 4764e3e87e4SAndreas Gohr if ($err->getCode()) 4774e3e87e4SAndreas Gohr $this->status = $err->getCode(); 4784e3e87e4SAndreas Gohr unset(self::$connections[$connectionId]); 4794e3e87e4SAndreas Gohr fclose($socket); 4804e3e87e4SAndreas Gohr return false; 4814e3e87e4SAndreas Gohr } 4824e3e87e4SAndreas Gohr 4834e3e87e4SAndreas Gohr if (!$this->keep_alive || 4844e3e87e4SAndreas Gohr (isset($this->resp_headers['connection']) && $this->resp_headers['connection'] == 'Close')) { 4854e3e87e4SAndreas Gohr // close socket 4864e3e87e4SAndreas Gohr fclose($socket); 4874e3e87e4SAndreas Gohr unset(self::$connections[$connectionId]); 4884e3e87e4SAndreas Gohr } 4894e3e87e4SAndreas Gohr 4904e3e87e4SAndreas Gohr // decode gzip if needed 4914e3e87e4SAndreas Gohr if(isset($this->resp_headers['content-encoding']) && 4924e3e87e4SAndreas Gohr $this->resp_headers['content-encoding'] == 'gzip' && 4934e3e87e4SAndreas Gohr strlen($r_body) > 10 && substr($r_body,0,3)=="\x1f\x8b\x08"){ 4944e3e87e4SAndreas Gohr $this->resp_body = @gzinflate(substr($r_body, 10)); 4954e3e87e4SAndreas Gohr if($this->resp_body === false){ 4964e3e87e4SAndreas Gohr $this->error = 'Failed to decompress gzip encoded content'; 4974e3e87e4SAndreas Gohr $this->resp_body = $r_body; 4984e3e87e4SAndreas Gohr } 4994e3e87e4SAndreas Gohr }else{ 5004e3e87e4SAndreas Gohr $this->resp_body = $r_body; 5014e3e87e4SAndreas Gohr } 5024e3e87e4SAndreas Gohr 5034e3e87e4SAndreas Gohr $this->debug('response body',$this->resp_body); 5044e3e87e4SAndreas Gohr $this->redirect_count = 0; 5054e3e87e4SAndreas Gohr return true; 5064e3e87e4SAndreas Gohr } 5074e3e87e4SAndreas Gohr 5084e3e87e4SAndreas Gohr /** 5094e3e87e4SAndreas Gohr * Tries to establish a CONNECT tunnel via Proxy 5104e3e87e4SAndreas Gohr * 5114e3e87e4SAndreas Gohr * Protocol, Servername and Port will be stripped from the request URL when a successful CONNECT happened 5124e3e87e4SAndreas Gohr * 5134e3e87e4SAndreas Gohr * @param resource &$socket 5144e3e87e4SAndreas Gohr * @param string &$requesturl 5154e3e87e4SAndreas Gohr * @throws HTTPClientException when a tunnel is needed but could not be established 5164e3e87e4SAndreas Gohr * @return bool true if a tunnel was established 5174e3e87e4SAndreas Gohr */ 5184e3e87e4SAndreas Gohr protected function ssltunnel(&$socket, &$requesturl){ 5194e3e87e4SAndreas Gohr if(!$this->useProxyForUrl($requesturl)) return false; 5204e3e87e4SAndreas Gohr $requestinfo = parse_url($requesturl); 5214e3e87e4SAndreas Gohr if($requestinfo['scheme'] != 'https') return false; 5224e3e87e4SAndreas Gohr if(empty($requestinfo['port'])) $requestinfo['port'] = 443; 5234e3e87e4SAndreas Gohr 5244e3e87e4SAndreas Gohr // build request 5254e3e87e4SAndreas Gohr $request = "CONNECT {$requestinfo['host']}:{$requestinfo['port']} HTTP/1.0".HTTP_NL; 5264e3e87e4SAndreas Gohr $request .= "Host: {$requestinfo['host']}".HTTP_NL; 5274e3e87e4SAndreas Gohr if($this->proxy_user) { 5284e3e87e4SAndreas Gohr $request .= 'Proxy-Authorization: Basic '.base64_encode($this->proxy_user.':'.$this->proxy_pass).HTTP_NL; 5294e3e87e4SAndreas Gohr } 5304e3e87e4SAndreas Gohr $request .= HTTP_NL; 5314e3e87e4SAndreas Gohr 5324e3e87e4SAndreas Gohr $this->debug('SSL Tunnel CONNECT',$request); 5334e3e87e4SAndreas Gohr $this->sendData($socket, $request, 'SSL Tunnel CONNECT'); 5344e3e87e4SAndreas Gohr 5354e3e87e4SAndreas Gohr // read headers from socket 5364e3e87e4SAndreas Gohr $r_headers = ''; 5374e3e87e4SAndreas Gohr do{ 5384e3e87e4SAndreas Gohr $r_line = $this->readLine($socket, 'headers'); 5394e3e87e4SAndreas Gohr $r_headers .= $r_line; 5404e3e87e4SAndreas Gohr }while($r_line != "\r\n" && $r_line != "\n"); 5414e3e87e4SAndreas Gohr 5424e3e87e4SAndreas Gohr $this->debug('SSL Tunnel Response',$r_headers); 5434e3e87e4SAndreas Gohr if(preg_match('/^HTTP\/1\.[01] 200/i',$r_headers)){ 5444e3e87e4SAndreas Gohr // set correct peer name for verification (enabled since PHP 5.6) 5454e3e87e4SAndreas Gohr stream_context_set_option($socket, 'ssl', 'peer_name', $requestinfo['host']); 5464e3e87e4SAndreas Gohr 5474e3e87e4SAndreas Gohr // SSLv3 is broken, use only TLS connections. 5484e3e87e4SAndreas Gohr // @link https://bugs.php.net/69195 5494e3e87e4SAndreas Gohr if (PHP_VERSION_ID >= 50600 && PHP_VERSION_ID <= 50606) { 5504e3e87e4SAndreas Gohr $cryptoMethod = STREAM_CRYPTO_METHOD_TLS_CLIENT; 5514e3e87e4SAndreas Gohr } else { 5524e3e87e4SAndreas Gohr // actually means neither SSLv2 nor SSLv3 5534e3e87e4SAndreas Gohr $cryptoMethod = STREAM_CRYPTO_METHOD_SSLv23_CLIENT; 5544e3e87e4SAndreas Gohr } 5554e3e87e4SAndreas Gohr 5564e3e87e4SAndreas Gohr if (@stream_socket_enable_crypto($socket, true, $cryptoMethod)) { 5574e3e87e4SAndreas Gohr $requesturl = $requestinfo['path']. 5584e3e87e4SAndreas Gohr (!empty($requestinfo['query'])?'?'.$requestinfo['query']:''); 5594e3e87e4SAndreas Gohr return true; 5604e3e87e4SAndreas Gohr } 5614e3e87e4SAndreas Gohr 5624e3e87e4SAndreas Gohr throw new HTTPClientException( 5634e3e87e4SAndreas Gohr 'Failed to set up crypto for secure connection to '.$requestinfo['host'], -151 5644e3e87e4SAndreas Gohr ); 5654e3e87e4SAndreas Gohr } 5664e3e87e4SAndreas Gohr 5674e3e87e4SAndreas Gohr throw new HTTPClientException('Failed to establish secure proxy connection', -150); 5684e3e87e4SAndreas Gohr } 5694e3e87e4SAndreas Gohr 5704e3e87e4SAndreas Gohr /** 5714e3e87e4SAndreas Gohr * Safely write data to a socket 5724e3e87e4SAndreas Gohr * 5734e3e87e4SAndreas Gohr * @param resource $socket An open socket handle 5744e3e87e4SAndreas Gohr * @param string $data The data to write 5754e3e87e4SAndreas Gohr * @param string $message Description of what is being read 5764e3e87e4SAndreas Gohr * @throws HTTPClientException 5774e3e87e4SAndreas Gohr * 5784e3e87e4SAndreas Gohr * @author Tom N Harris <tnharris@whoopdedo.org> 5794e3e87e4SAndreas Gohr */ 5804e3e87e4SAndreas Gohr protected function sendData($socket, $data, $message) { 5814e3e87e4SAndreas Gohr // send request 5824e3e87e4SAndreas Gohr $towrite = strlen($data); 5834e3e87e4SAndreas Gohr $written = 0; 5844e3e87e4SAndreas Gohr while($written < $towrite){ 5854e3e87e4SAndreas Gohr // check timeout 5864e3e87e4SAndreas Gohr $time_used = $this->time() - $this->start; 5874e3e87e4SAndreas Gohr if($time_used > $this->timeout) 5884e3e87e4SAndreas Gohr throw new HTTPClientException(sprintf('Timeout while sending %s (%.3fs)',$message, $time_used), -100); 5894e3e87e4SAndreas Gohr if(feof($socket)) 5904e3e87e4SAndreas Gohr throw new HTTPClientException("Socket disconnected while writing $message"); 5914e3e87e4SAndreas Gohr 5924e3e87e4SAndreas Gohr // select parameters 5934e3e87e4SAndreas Gohr $sel_r = null; 5944e3e87e4SAndreas Gohr $sel_w = array($socket); 5954e3e87e4SAndreas Gohr $sel_e = null; 5964e3e87e4SAndreas Gohr // wait for stream ready or timeout (1sec) 5974e3e87e4SAndreas Gohr if(@stream_select($sel_r,$sel_w,$sel_e,1) === false){ 5984e3e87e4SAndreas Gohr usleep(1000); 5994e3e87e4SAndreas Gohr continue; 6004e3e87e4SAndreas Gohr } 6014e3e87e4SAndreas Gohr 6024e3e87e4SAndreas Gohr // write to stream 6034e3e87e4SAndreas Gohr $nbytes = fwrite($socket, substr($data,$written,4096)); 6044e3e87e4SAndreas Gohr if($nbytes === false) 6054e3e87e4SAndreas Gohr throw new HTTPClientException("Failed writing to socket while sending $message", -100); 6064e3e87e4SAndreas Gohr $written += $nbytes; 6074e3e87e4SAndreas Gohr } 6084e3e87e4SAndreas Gohr } 6094e3e87e4SAndreas Gohr 6104e3e87e4SAndreas Gohr /** 6114e3e87e4SAndreas Gohr * Safely read data from a socket 6124e3e87e4SAndreas Gohr * 6134e3e87e4SAndreas Gohr * Reads up to a given number of bytes or throws an exception if the 6144e3e87e4SAndreas Gohr * response times out or ends prematurely. 6154e3e87e4SAndreas Gohr * 6164e3e87e4SAndreas Gohr * @param resource $socket An open socket handle in non-blocking mode 6174e3e87e4SAndreas Gohr * @param int $nbytes Number of bytes to read 6184e3e87e4SAndreas Gohr * @param string $message Description of what is being read 6194e3e87e4SAndreas Gohr * @param bool $ignore_eof End-of-file is not an error if this is set 6204e3e87e4SAndreas Gohr * @throws HTTPClientException 6214e3e87e4SAndreas Gohr * @return string 6224e3e87e4SAndreas Gohr * 6234e3e87e4SAndreas Gohr * @author Tom N Harris <tnharris@whoopdedo.org> 6244e3e87e4SAndreas Gohr */ 6254e3e87e4SAndreas Gohr protected function readData($socket, $nbytes, $message, $ignore_eof = false) { 6264e3e87e4SAndreas Gohr $r_data = ''; 6274e3e87e4SAndreas Gohr // Does not return immediately so timeout and eof can be checked 6284e3e87e4SAndreas Gohr if ($nbytes < 0) $nbytes = 0; 6294e3e87e4SAndreas Gohr $to_read = $nbytes; 6304e3e87e4SAndreas Gohr do { 6314e3e87e4SAndreas Gohr $time_used = $this->time() - $this->start; 6324e3e87e4SAndreas Gohr if ($time_used > $this->timeout) 6334e3e87e4SAndreas Gohr throw new HTTPClientException( 6344e3e87e4SAndreas Gohr sprintf('Timeout while reading %s after %d bytes (%.3fs)', $message, 6354e3e87e4SAndreas Gohr strlen($r_data), $time_used), -100); 6364e3e87e4SAndreas Gohr if(feof($socket)) { 6374e3e87e4SAndreas Gohr if(!$ignore_eof) 6384e3e87e4SAndreas Gohr throw new HTTPClientException("Premature End of File (socket) while reading $message"); 6394e3e87e4SAndreas Gohr break; 6404e3e87e4SAndreas Gohr } 6414e3e87e4SAndreas Gohr 6424e3e87e4SAndreas Gohr if ($to_read > 0) { 6434e3e87e4SAndreas Gohr // select parameters 6444e3e87e4SAndreas Gohr $sel_r = array($socket); 6454e3e87e4SAndreas Gohr $sel_w = null; 6464e3e87e4SAndreas Gohr $sel_e = null; 6474e3e87e4SAndreas Gohr // wait for stream ready or timeout (1sec) 6484e3e87e4SAndreas Gohr if(@stream_select($sel_r,$sel_w,$sel_e,1) === false){ 6494e3e87e4SAndreas Gohr usleep(1000); 6504e3e87e4SAndreas Gohr continue; 6514e3e87e4SAndreas Gohr } 6524e3e87e4SAndreas Gohr 6534e3e87e4SAndreas Gohr $bytes = fread($socket, $to_read); 6544e3e87e4SAndreas Gohr if($bytes === false) 6554e3e87e4SAndreas Gohr throw new HTTPClientException("Failed reading from socket while reading $message", -100); 6564e3e87e4SAndreas Gohr $r_data .= $bytes; 6574e3e87e4SAndreas Gohr $to_read -= strlen($bytes); 6584e3e87e4SAndreas Gohr } 6594e3e87e4SAndreas Gohr } while ($to_read > 0 && strlen($r_data) < $nbytes); 6604e3e87e4SAndreas Gohr return $r_data; 6614e3e87e4SAndreas Gohr } 6624e3e87e4SAndreas Gohr 6634e3e87e4SAndreas Gohr /** 6644e3e87e4SAndreas Gohr * Safely read a \n-terminated line from a socket 6654e3e87e4SAndreas Gohr * 6664e3e87e4SAndreas Gohr * Always returns a complete line, including the terminating \n. 6674e3e87e4SAndreas Gohr * 6684e3e87e4SAndreas Gohr * @param resource $socket An open socket handle in non-blocking mode 6694e3e87e4SAndreas Gohr * @param string $message Description of what is being read 6704e3e87e4SAndreas Gohr * @throws HTTPClientException 6714e3e87e4SAndreas Gohr * @return string 6724e3e87e4SAndreas Gohr * 6734e3e87e4SAndreas Gohr * @author Tom N Harris <tnharris@whoopdedo.org> 6744e3e87e4SAndreas Gohr */ 6754e3e87e4SAndreas Gohr protected function readLine($socket, $message) { 6764e3e87e4SAndreas Gohr $r_data = ''; 6774e3e87e4SAndreas Gohr do { 6784e3e87e4SAndreas Gohr $time_used = $this->time() - $this->start; 6794e3e87e4SAndreas Gohr if ($time_used > $this->timeout) 6804e3e87e4SAndreas Gohr throw new HTTPClientException( 6814e3e87e4SAndreas Gohr sprintf('Timeout while reading %s (%.3fs) >%s<', $message, $time_used, $r_data), 6824e3e87e4SAndreas Gohr -100); 6834e3e87e4SAndreas Gohr if(feof($socket)) 6844e3e87e4SAndreas Gohr throw new HTTPClientException("Premature End of File (socket) while reading $message"); 6854e3e87e4SAndreas Gohr 6864e3e87e4SAndreas Gohr // select parameters 6874e3e87e4SAndreas Gohr $sel_r = array($socket); 6884e3e87e4SAndreas Gohr $sel_w = null; 6894e3e87e4SAndreas Gohr $sel_e = null; 6904e3e87e4SAndreas Gohr // wait for stream ready or timeout (1sec) 6914e3e87e4SAndreas Gohr if(@stream_select($sel_r,$sel_w,$sel_e,1) === false){ 6924e3e87e4SAndreas Gohr usleep(1000); 6934e3e87e4SAndreas Gohr continue; 6944e3e87e4SAndreas Gohr } 6954e3e87e4SAndreas Gohr 6964e3e87e4SAndreas Gohr $r_data = fgets($socket, 1024); 6974e3e87e4SAndreas Gohr } while (!preg_match('/\n$/',$r_data)); 6984e3e87e4SAndreas Gohr return $r_data; 6994e3e87e4SAndreas Gohr } 7004e3e87e4SAndreas Gohr 7014e3e87e4SAndreas Gohr /** 7024e3e87e4SAndreas Gohr * print debug info 7034e3e87e4SAndreas Gohr * 7044e3e87e4SAndreas Gohr * Uses _debug_text or _debug_html depending on the SAPI name 7054e3e87e4SAndreas Gohr * 7064e3e87e4SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 7074e3e87e4SAndreas Gohr * 7084e3e87e4SAndreas Gohr * @param string $info 7094e3e87e4SAndreas Gohr * @param mixed $var 7104e3e87e4SAndreas Gohr */ 7114e3e87e4SAndreas Gohr protected function debug($info,$var=null){ 7124e3e87e4SAndreas Gohr if(!$this->debug) return; 7134e3e87e4SAndreas Gohr if(php_sapi_name() == 'cli'){ 7144e3e87e4SAndreas Gohr $this->debugText($info, $var); 7154e3e87e4SAndreas Gohr }else{ 7164e3e87e4SAndreas Gohr $this->debugHtml($info, $var); 7174e3e87e4SAndreas Gohr } 7184e3e87e4SAndreas Gohr } 7194e3e87e4SAndreas Gohr 7204e3e87e4SAndreas Gohr /** 7214e3e87e4SAndreas Gohr * print debug info as HTML 7224e3e87e4SAndreas Gohr * 7234e3e87e4SAndreas Gohr * @param string $info 7244e3e87e4SAndreas Gohr * @param mixed $var 7254e3e87e4SAndreas Gohr */ 7264e3e87e4SAndreas Gohr protected function debugHtml($info, $var=null){ 7274e3e87e4SAndreas Gohr print '<b>'.$info.'</b> '.($this->time() - $this->start).'s<br />'; 7284e3e87e4SAndreas Gohr if(!is_null($var)){ 7294e3e87e4SAndreas Gohr ob_start(); 7304e3e87e4SAndreas Gohr print_r($var); 7314e3e87e4SAndreas Gohr $content = htmlspecialchars(ob_get_contents()); 7324e3e87e4SAndreas Gohr ob_end_clean(); 7334e3e87e4SAndreas Gohr print '<pre>'.$content.'</pre>'; 7344e3e87e4SAndreas Gohr } 7354e3e87e4SAndreas Gohr } 7364e3e87e4SAndreas Gohr 7374e3e87e4SAndreas Gohr /** 7384e3e87e4SAndreas Gohr * prints debug info as plain text 7394e3e87e4SAndreas Gohr * 7404e3e87e4SAndreas Gohr * @param string $info 7414e3e87e4SAndreas Gohr * @param mixed $var 7424e3e87e4SAndreas Gohr */ 7434e3e87e4SAndreas Gohr protected function debugText($info, $var=null){ 7444e3e87e4SAndreas Gohr print '*'.$info.'* '.($this->time() - $this->start)."s\n"; 7454e3e87e4SAndreas Gohr if(!is_null($var)) print_r($var); 7464e3e87e4SAndreas Gohr print "\n-----------------------------------------------\n"; 7474e3e87e4SAndreas Gohr } 7484e3e87e4SAndreas Gohr 7494e3e87e4SAndreas Gohr /** 7504e3e87e4SAndreas Gohr * Return current timestamp in microsecond resolution 7514e3e87e4SAndreas Gohr * 7524e3e87e4SAndreas Gohr * @return float 7534e3e87e4SAndreas Gohr */ 7544e3e87e4SAndreas Gohr protected static function time(){ 7554e3e87e4SAndreas Gohr list($usec, $sec) = explode(" ", microtime()); 7564e3e87e4SAndreas Gohr return ((float)$usec + (float)$sec); 7574e3e87e4SAndreas Gohr } 7584e3e87e4SAndreas Gohr 7594e3e87e4SAndreas Gohr /** 7604e3e87e4SAndreas Gohr * convert given header string to Header array 7614e3e87e4SAndreas Gohr * 7624e3e87e4SAndreas Gohr * All Keys are lowercased. 7634e3e87e4SAndreas Gohr * 7644e3e87e4SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 7654e3e87e4SAndreas Gohr * 7664e3e87e4SAndreas Gohr * @param string $string 7674e3e87e4SAndreas Gohr * @return array 7684e3e87e4SAndreas Gohr */ 7694e3e87e4SAndreas Gohr protected function parseHeaders($string){ 7704e3e87e4SAndreas Gohr $headers = array(); 7714e3e87e4SAndreas Gohr $lines = explode("\n",$string); 7724e3e87e4SAndreas Gohr array_shift($lines); //skip first line (status) 7734e3e87e4SAndreas Gohr foreach($lines as $line){ 774*fc9fd1daSAndreas Gohr list($key, $val) = array_pad(explode(':', $line, 2), 2, ''); 7754e3e87e4SAndreas Gohr $key = trim($key); 7764e3e87e4SAndreas Gohr $val = trim($val); 7774e3e87e4SAndreas Gohr $key = strtolower($key); 7784e3e87e4SAndreas Gohr if(!$key) continue; 7794e3e87e4SAndreas Gohr if(isset($headers[$key])){ 7804e3e87e4SAndreas Gohr if(is_array($headers[$key])){ 7814e3e87e4SAndreas Gohr $headers[$key][] = $val; 7824e3e87e4SAndreas Gohr }else{ 7834e3e87e4SAndreas Gohr $headers[$key] = array($headers[$key],$val); 7844e3e87e4SAndreas Gohr } 7854e3e87e4SAndreas Gohr }else{ 7864e3e87e4SAndreas Gohr $headers[$key] = $val; 7874e3e87e4SAndreas Gohr } 7884e3e87e4SAndreas Gohr } 7894e3e87e4SAndreas Gohr return $headers; 7904e3e87e4SAndreas Gohr } 7914e3e87e4SAndreas Gohr 7924e3e87e4SAndreas Gohr /** 7934e3e87e4SAndreas Gohr * convert given header array to header string 7944e3e87e4SAndreas Gohr * 7954e3e87e4SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 7964e3e87e4SAndreas Gohr * 7974e3e87e4SAndreas Gohr * @param array $headers 7984e3e87e4SAndreas Gohr * @return string 7994e3e87e4SAndreas Gohr */ 8004e3e87e4SAndreas Gohr protected function buildHeaders($headers){ 8014e3e87e4SAndreas Gohr $string = ''; 8024e3e87e4SAndreas Gohr foreach($headers as $key => $value){ 8034e3e87e4SAndreas Gohr if($value === '') continue; 8044e3e87e4SAndreas Gohr $string .= $key.': '.$value.HTTP_NL; 8054e3e87e4SAndreas Gohr } 8064e3e87e4SAndreas Gohr return $string; 8074e3e87e4SAndreas Gohr } 8084e3e87e4SAndreas Gohr 8094e3e87e4SAndreas Gohr /** 8104e3e87e4SAndreas Gohr * get cookies as http header string 8114e3e87e4SAndreas Gohr * 8124e3e87e4SAndreas Gohr * @author Andreas Goetz <cpuidle@gmx.de> 8134e3e87e4SAndreas Gohr * 8144e3e87e4SAndreas Gohr * @return string 8154e3e87e4SAndreas Gohr */ 8164e3e87e4SAndreas Gohr protected function getCookies(){ 8174e3e87e4SAndreas Gohr $headers = ''; 8184e3e87e4SAndreas Gohr foreach ($this->cookies as $key => $val){ 8194e3e87e4SAndreas Gohr $headers .= "$key=$val; "; 8204e3e87e4SAndreas Gohr } 8214e3e87e4SAndreas Gohr $headers = substr($headers, 0, -2); 8224e3e87e4SAndreas Gohr if ($headers) $headers = "Cookie: $headers".HTTP_NL; 8234e3e87e4SAndreas Gohr return $headers; 8244e3e87e4SAndreas Gohr } 8254e3e87e4SAndreas Gohr 8264e3e87e4SAndreas Gohr /** 8274e3e87e4SAndreas Gohr * Encode data for posting 8284e3e87e4SAndreas Gohr * 8294e3e87e4SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 8304e3e87e4SAndreas Gohr * 8314e3e87e4SAndreas Gohr * @param array $data 8324e3e87e4SAndreas Gohr * @return string 8334e3e87e4SAndreas Gohr */ 8344e3e87e4SAndreas Gohr protected function postEncode($data){ 8354e3e87e4SAndreas Gohr return http_build_query($data,'','&'); 8364e3e87e4SAndreas Gohr } 8374e3e87e4SAndreas Gohr 8384e3e87e4SAndreas Gohr /** 8394e3e87e4SAndreas Gohr * Encode data for posting using multipart encoding 8404e3e87e4SAndreas Gohr * 8414e3e87e4SAndreas Gohr * @fixme use of urlencode might be wrong here 8424e3e87e4SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 8434e3e87e4SAndreas Gohr * 8444e3e87e4SAndreas Gohr * @param array $data 8454e3e87e4SAndreas Gohr * @return string 8464e3e87e4SAndreas Gohr */ 8474e3e87e4SAndreas Gohr protected function postMultipartEncode($data){ 8484e3e87e4SAndreas Gohr $boundary = '--'.$this->boundary; 8494e3e87e4SAndreas Gohr $out = ''; 8504e3e87e4SAndreas Gohr foreach($data as $key => $val){ 8514e3e87e4SAndreas Gohr $out .= $boundary.HTTP_NL; 8524e3e87e4SAndreas Gohr if(!is_array($val)){ 8534e3e87e4SAndreas Gohr $out .= 'Content-Disposition: form-data; name="'.urlencode($key).'"'.HTTP_NL; 8544e3e87e4SAndreas Gohr $out .= HTTP_NL; // end of headers 8554e3e87e4SAndreas Gohr $out .= $val; 8564e3e87e4SAndreas Gohr $out .= HTTP_NL; 8574e3e87e4SAndreas Gohr }else{ 8584e3e87e4SAndreas Gohr $out .= 'Content-Disposition: form-data; name="'.urlencode($key).'"'; 8594e3e87e4SAndreas Gohr if($val['filename']) $out .= '; filename="'.urlencode($val['filename']).'"'; 8604e3e87e4SAndreas Gohr $out .= HTTP_NL; 8614e3e87e4SAndreas Gohr if($val['mimetype']) $out .= 'Content-Type: '.$val['mimetype'].HTTP_NL; 8624e3e87e4SAndreas Gohr $out .= HTTP_NL; // end of headers 8634e3e87e4SAndreas Gohr $out .= $val['body']; 8644e3e87e4SAndreas Gohr $out .= HTTP_NL; 8654e3e87e4SAndreas Gohr } 8664e3e87e4SAndreas Gohr } 8674e3e87e4SAndreas Gohr $out .= "$boundary--".HTTP_NL; 8684e3e87e4SAndreas Gohr return $out; 8694e3e87e4SAndreas Gohr } 8704e3e87e4SAndreas Gohr 8714e3e87e4SAndreas Gohr /** 8724e3e87e4SAndreas Gohr * Generates a unique identifier for a connection. 8734e3e87e4SAndreas Gohr * 8744e3e87e4SAndreas Gohr * @param string $server 8754e3e87e4SAndreas Gohr * @param string $port 8764e3e87e4SAndreas Gohr * @return string unique identifier 8774e3e87e4SAndreas Gohr */ 8784e3e87e4SAndreas Gohr protected function uniqueConnectionId($server, $port) { 8794e3e87e4SAndreas Gohr return "$server:$port"; 8804e3e87e4SAndreas Gohr } 8814e3e87e4SAndreas Gohr 8824e3e87e4SAndreas Gohr /** 8834e3e87e4SAndreas Gohr * Should the Proxy be used for the given URL? 8844e3e87e4SAndreas Gohr * 8854e3e87e4SAndreas Gohr * Checks the exceptions 8864e3e87e4SAndreas Gohr * 8874e3e87e4SAndreas Gohr * @param string $url 8884e3e87e4SAndreas Gohr * @return bool 8894e3e87e4SAndreas Gohr */ 8904e3e87e4SAndreas Gohr protected function useProxyForUrl($url) { 8914e3e87e4SAndreas Gohr return $this->proxy_host && (!$this->proxy_except || !preg_match('/' . $this->proxy_except . '/i', $url)); 8924e3e87e4SAndreas Gohr } 8934e3e87e4SAndreas Gohr} 894