* removes the HTTP classes from this project and uses p3k-http library instead * reorganizes the XRay classes into a psr-4 compatible folder * moves controller autoload into -dev in preparation for turning this into a library (#17)pull/38/head
| @ -1,56 +0,0 @@ | |||
| <?php | |||
| namespace p3k; | |||
| class HTTP { | |||
| public $timeout = 4; | |||
| public $max_redirects = 8; | |||
| public function get($url, $headers=[]) { | |||
| $class = $this->_class($url); | |||
| $http = new $class($url); | |||
| $http->timeout = $this->timeout; | |||
| $http->max_redirects = $this->max_redirects; | |||
| return $http->get($url, $headers); | |||
| } | |||
| public function post($url, $body, $headers=[]) { | |||
| $class = $this->_class($url); | |||
| $http = new $class($url); | |||
| $http->timeout = $this->timeout; | |||
| $http->max_redirects = $this->max_redirects; | |||
| return $http->post($url, $body, $headers); | |||
| } | |||
| public function head($url) { | |||
| $class = $this->_class($url); | |||
| $http = new $class($url); | |||
| $http->timeout = $this->timeout; | |||
| $http->max_redirects = $this->max_redirects; | |||
| return $http->head($url); | |||
| } | |||
| private function _class($url) { | |||
| if(!should_follow_redirects($url)) { | |||
| return 'p3k\HTTPStream'; | |||
| } else { | |||
| return 'p3k\HTTPCurl'; | |||
| } | |||
| } | |||
| public static function link_rels($header_array) { | |||
| $headers = ''; | |||
| foreach($header_array as $k=>$header) { | |||
| if(is_string($header)) { | |||
| $headers .= $k . ': ' . $header . "\r\n"; | |||
| } else { | |||
| foreach($header as $h) { | |||
| $headers .= $k . ': ' . $h . "\r\n"; | |||
| } | |||
| } | |||
| } | |||
| $rels = \IndieWeb\http_rels($headers); | |||
| return $rels; | |||
| } | |||
| } | |||
| @ -1,127 +0,0 @@ | |||
| <?php | |||
| namespace p3k; | |||
| class HTTPCurl { | |||
| public $timeout = 4; | |||
| public $max_redirects = 8; | |||
| public function get($url, $headers=[]) { | |||
| $ch = curl_init($url); | |||
| $this->_set_curlopts($ch, $url); | |||
| if($headers) | |||
| curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); | |||
| $response = curl_exec($ch); | |||
| $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); | |||
| return array( | |||
| 'code' => curl_getinfo($ch, CURLINFO_HTTP_CODE), | |||
| 'headers' => self::parse_headers(trim(substr($response, 0, $header_size))), | |||
| 'body' => substr($response, $header_size), | |||
| 'error' => self::error_string_from_code(curl_errno($ch)), | |||
| 'error_description' => curl_error($ch), | |||
| 'error_code' => curl_errno($ch), | |||
| 'url' => curl_getinfo($ch, CURLINFO_EFFECTIVE_URL), | |||
| ); | |||
| } | |||
| public function post($url, $body, $headers=[]) { | |||
| $ch = curl_init($url); | |||
| $this->_set_curlopts($ch, $url); | |||
| curl_setopt($ch, CURLOPT_POST, true); | |||
| curl_setopt($ch, CURLOPT_POSTFIELDS, $body); | |||
| if($headers) | |||
| curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); | |||
| $response = curl_exec($ch); | |||
| $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); | |||
| return array( | |||
| 'code' => curl_getinfo($ch, CURLINFO_HTTP_CODE), | |||
| 'headers' => self::parse_headers(trim(substr($response, 0, $header_size))), | |||
| 'body' => substr($response, $header_size), | |||
| 'error' => self::error_string_from_code(curl_errno($ch)), | |||
| 'error_description' => curl_error($ch), | |||
| 'error_code' => curl_errno($ch), | |||
| 'url' => curl_getinfo($ch, CURLINFO_EFFECTIVE_URL), | |||
| ); | |||
| } | |||
| public function head($url) { | |||
| $ch = curl_init($url); | |||
| $this->_set_curlopts($ch, $url); | |||
| curl_setopt($ch, CURLOPT_NOBODY, true); | |||
| $response = curl_exec($ch); | |||
| return array( | |||
| 'code' => curl_getinfo($ch, CURLINFO_HTTP_CODE), | |||
| 'headers' => self::parse_headers(trim($response)), | |||
| 'error' => self::error_string_from_code(curl_errno($ch)), | |||
| 'error_description' => curl_error($ch), | |||
| 'error_code' => curl_errno($ch), | |||
| 'url' => curl_getinfo($ch, CURLINFO_EFFECTIVE_URL), | |||
| ); | |||
| } | |||
| private function _set_curlopts($ch, $url) { | |||
| curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); | |||
| curl_setopt($ch, CURLOPT_HEADER, true); | |||
| // Special-case appspot.com URLs to not follow redirects. | |||
| // https://cloud.google.com/appengine/docs/php/urlfetch/ | |||
| if(should_follow_redirects($url)) { | |||
| curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); | |||
| curl_setopt($ch, CURLOPT_MAXREDIRS, $this->max_redirects); | |||
| } else { | |||
| curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false); | |||
| } | |||
| curl_setopt($ch, CURLOPT_TIMEOUT_MS, round($this->timeout * 1000)); | |||
| curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, 2000); | |||
| } | |||
| public static function error_string_from_code($code) { | |||
| switch($code) { | |||
| case 0: | |||
| return ''; | |||
| case CURLE_COULDNT_RESOLVE_HOST: | |||
| return 'dns_error'; | |||
| case CURLE_COULDNT_CONNECT: | |||
| return 'connect_error'; | |||
| case CURLE_OPERATION_TIMEDOUT: | |||
| return 'timeout'; | |||
| case CURLE_SSL_CONNECT_ERROR: | |||
| return 'ssl_error'; | |||
| case CURLE_SSL_CERTPROBLEM: | |||
| return 'ssl_cert_error'; | |||
| case CURLE_SSL_CIPHER: | |||
| return 'ssl_unsupported_cipher'; | |||
| case CURLE_SSL_CACERT: | |||
| return 'ssl_cert_error'; | |||
| case CURLE_TOO_MANY_REDIRECTS: | |||
| return 'too_many_redirects'; | |||
| default: | |||
| return 'unknown'; | |||
| } | |||
| } | |||
| public static function parse_headers($headers) { | |||
| $retVal = array(); | |||
| $fields = explode("\r\n", preg_replace('/\x0D\x0A[\x09\x20]+/', ' ', $headers)); | |||
| foreach($fields as $field) { | |||
| if(preg_match('/([^:]+): (.+)/m', $field, $match)) { | |||
| $match[1] = preg_replace_callback('/(?<=^|[\x09\x20\x2D])./', function($m) { | |||
| return strtoupper($m[0]); | |||
| }, strtolower(trim($match[1]))); | |||
| // If there's already a value set for the header name being returned, turn it into an array and add the new value | |||
| $match[1] = preg_replace_callback('/(?<=^|[\x09\x20\x2D])./', function($m) { | |||
| return strtoupper($m[0]); | |||
| }, strtolower(trim($match[1]))); | |||
| if(isset($retVal[$match[1]])) { | |||
| if(!is_array($retVal[$match[1]])) | |||
| $retVal[$match[1]] = array($retVal[$match[1]]); | |||
| $retVal[$match[1]][] = $match[2]; | |||
| } else { | |||
| $retVal[$match[1]] = trim($match[2]); | |||
| } | |||
| } | |||
| } | |||
| return $retVal; | |||
| } | |||
| } | |||
| @ -1,138 +0,0 @@ | |||
| <?php | |||
| namespace p3k; | |||
| class HTTPStream { | |||
| public $timeout = 4; | |||
| public $max_redirects = 8; | |||
| public static function exception_error_handler($severity, $message, $file, $line) { | |||
| if (!(error_reporting() & $severity)) { | |||
| // This error code is not included in error_reporting | |||
| return; | |||
| } | |||
| throw new \ErrorException($message, 0, $severity, $file, $line); | |||
| } | |||
| public function get($url, $headers=[]) { | |||
| set_error_handler("p3k\HTTPStream::exception_error_handler"); | |||
| $context = $this->_stream_context('GET', $url, false, $headers); | |||
| return $this->_fetch($url, $context); | |||
| } | |||
| public function post($url, $body, $headers=[]) { | |||
| set_error_handler("p3k\HTTPStream::exception_error_handler"); | |||
| $context = $this->_stream_context('POST', $url, $body, $headers); | |||
| return $this->_fetch($url, $context); | |||
| } | |||
| public function head($url) { | |||
| set_error_handler("p3k\HTTPStream::exception_error_handler"); | |||
| $context = $this->_stream_context('HEAD', $url); | |||
| return $this->_fetch($url, $context); | |||
| } | |||
| private function _fetch($url, $context) { | |||
| $error = false; | |||
| try { | |||
| $body = file_get_contents($url, false, $context); | |||
| // This sets $http_response_header | |||
| // see http://php.net/manual/en/reserved.variables.httpresponseheader.php | |||
| } catch(\Exception $e) { | |||
| $body = false; | |||
| $http_response_header = []; | |||
| $description = str_replace('file_get_contents(): ', '', $e->getMessage()); | |||
| $code = 'unknown'; | |||
| if(preg_match('/getaddrinfo failed/', $description)) { | |||
| $code = 'dns_error'; | |||
| $description = str_replace('php_network_getaddresses: ', '', $description); | |||
| } | |||
| if(preg_match('/timed out|request failed/', $description)) { | |||
| $code = 'timeout'; | |||
| } | |||
| if(preg_match('/certificate/', $description)) { | |||
| $code = 'ssl_error'; | |||
| } | |||
| $error = [ | |||
| 'description' => $description, | |||
| 'code' => $code | |||
| ]; | |||
| } | |||
| return array( | |||
| 'code' => self::parse_response_code($http_response_header), | |||
| 'headers' => self::parse_headers($http_response_header), | |||
| 'body' => $body, | |||
| 'error' => $error ? $error['code'] : false, | |||
| 'error_description' => $error ? $error['description'] : false, | |||
| 'url' => $url, | |||
| ); | |||
| } | |||
| private function _stream_context($method, $url, $body=false, $headers=[]) { | |||
| $options = [ | |||
| 'method' => $method, | |||
| 'timeout' => $this->timeout, | |||
| 'ignore_errors' => true, | |||
| ]; | |||
| if($body) { | |||
| $options['content'] = $body; | |||
| } | |||
| if($headers) { | |||
| $options['header'] = implode("\r\n", $headers); | |||
| } | |||
| // Special-case appspot.com URLs to not follow redirects. | |||
| // https://cloud.google.com/appengine/docs/php/urlfetch/ | |||
| if(should_follow_redirects($url)) { | |||
| $options['follow_location'] = 1; | |||
| $options['max_redirects'] = $this->max_redirects; | |||
| } else { | |||
| $options['follow_location'] = 0; | |||
| } | |||
| return stream_context_create(['http' => $options]); | |||
| } | |||
| public static function parse_response_code($headers) { | |||
| // When a response is a redirect, we want to find the last occurrence of the HTTP code | |||
| $code = false; | |||
| foreach($headers as $field) { | |||
| if(preg_match('/HTTP\/\d\.\d (\d+)/', $field, $match)) { | |||
| $code = $match[1]; | |||
| } | |||
| } | |||
| return $code; | |||
| } | |||
| public static function parse_headers($headers) { | |||
| $retVal = array(); | |||
| foreach($headers as $field) { | |||
| if(preg_match('/([^:]+): (.+)/m', $field, $match)) { | |||
| $match[1] = preg_replace_callback('/(?<=^|[\x09\x20\x2D])./', function($m) { | |||
| return strtoupper($m[0]); | |||
| }, strtolower(trim($match[1]))); | |||
| // If there's already a value set for the header name being returned, turn it into an array and add the new value | |||
| $match[1] = preg_replace_callback('/(?<=^|[\x09\x20\x2D])./', function($m) { | |||
| return strtoupper($m[0]); | |||
| }, strtolower(trim($match[1]))); | |||
| if(isset($retVal[$match[1]])) { | |||
| if(!is_array($retVal[$match[1]])) | |||
| $retVal[$match[1]] = array($retVal[$match[1]]); | |||
| $retVal[$match[1]][] = $match[2]; | |||
| } else { | |||
| $retVal[$match[1]] = trim($match[2]); | |||
| } | |||
| } | |||
| } | |||
| return $retVal; | |||
| } | |||
| } | |||
| @ -1,92 +0,0 @@ | |||
| <?php | |||
| namespace p3k; | |||
| class HTTPTest extends HTTPCurl { | |||
| private $_testDataPath; | |||
| private $_redirects_remaining; | |||
| public function __construct($testDataPath) { | |||
| $this->_testDataPath = $testDataPath; | |||
| } | |||
| public function get($url, $headers=[]) { | |||
| $this->_redirects_remaining = $this->max_redirects; | |||
| $parts = parse_url($url); | |||
| unset($parts['fragment']); | |||
| $url = \build_url($parts); | |||
| return $this->_read_file($url); | |||
| } | |||
| public function post($url, $body, $headers=[]) { | |||
| return $this->_read_file($url); | |||
| } | |||
| public function head($url) { | |||
| $response = $this->_read_file($url); | |||
| return array( | |||
| 'code' => $response['code'], | |||
| 'headers' => $response['headers'], | |||
| 'error' => '', | |||
| 'error_description' => '', | |||
| 'url' => $response['url'] | |||
| ); | |||
| } | |||
| private function _read_file($url) { | |||
| $parts = parse_url($url); | |||
| if($parts['path']) { | |||
| $parts['path'] = '/'.str_replace('/','_',substr($parts['path'],1)); | |||
| $url = \build_url($parts); | |||
| } | |||
| $filename = $this->_testDataPath.preg_replace('/https?:\/\//', '', $url); | |||
| if(!file_exists($filename)) { | |||
| $filename = $this->_testDataPath.'404.response.txt'; | |||
| } | |||
| $response = file_get_contents($filename); | |||
| $split = explode("\r\n\r\n", $response); | |||
| if(count($split) < 2) { | |||
| throw new \Exception("Invalid file contents in test data, check that newlines are CRLF: $url"); | |||
| } | |||
| $headers = array_shift($split); | |||
| $body = implode("\r\n", $split); | |||
| if(preg_match('/HTTP\/1\.1 (\d+)/', $headers, $match)) { | |||
| $code = $match[1]; | |||
| } | |||
| $headers = preg_replace('/HTTP\/1\.1 \d+ .+/', '', $headers); | |||
| $parsedHeaders = self::parse_headers($headers); | |||
| if(array_key_exists('Location', $parsedHeaders)) { | |||
| $effectiveUrl = \mf2\resolveUrl($url, $parsedHeaders['Location']); | |||
| if($this->_redirects_remaining > 0) { | |||
| $this->_redirects_remaining--; | |||
| return $this->_read_file($effectiveUrl); | |||
| } else { | |||
| return [ | |||
| 'code' => 0, | |||
| 'headers' => $parsedHeaders, | |||
| 'body' => $body, | |||
| 'error' => 'too_many_redirects', | |||
| 'error_description' => '', | |||
| 'url' => $effectiveUrl | |||
| ]; | |||
| } | |||
| } else { | |||
| $effectiveUrl = $url; | |||
| } | |||
| return array( | |||
| 'code' => $code, | |||
| 'headers' => $parsedHeaders, | |||
| 'body' => $body, | |||
| 'error' => (isset($parsedHeaders['X-Test-Error']) ? $parsedHeaders['X-Test-Error'] : ''), | |||
| 'error_description' => '', | |||
| 'url' => $effectiveUrl | |||
| ); | |||
| } | |||
| } | |||