diff options
Diffstat (limited to 'test_tools/simpletest/http.php')
-rw-r--r-- | test_tools/simpletest/http.php | 853 |
1 files changed, 853 insertions, 0 deletions
diff --git a/test_tools/simpletest/http.php b/test_tools/simpletest/http.php new file mode 100644 index 00000000..fa27de9c --- /dev/null +++ b/test_tools/simpletest/http.php @@ -0,0 +1,853 @@ +<?php + /** + * base include file for SimpleTest + * @package SimpleTest + * @subpackage WebTester + * @version $Id: http.php,v 1.98 2005/02/02 23:25:23 lastcraft Exp $ + */ + + /**#@+ + * include other SimpleTest class files + */ + require_once(dirname(__FILE__) . '/socket.php'); + require_once(dirname(__FILE__) . '/url.php'); + /**#@-*/ + + /** + * Cookie data holder. Cookie rules are full of pretty + * arbitary stuff. I have used... + * http://wp.netscape.com/newsref/std/cookie_spec.html + * http://www.cookiecentral.com/faq/ + * @package SimpleTest + * @subpackage WebTester + */ + class SimpleCookie { + protected $_host; + protected $_name; + protected $_value; + protected $_path; + protected $_expiry; + protected $_is_secure; + + /** + * Constructor. Sets the stored values. + * @param string $name Cookie key. + * @param string $value Value of cookie. + * @param string $path Cookie path if not host wide. + * @param string $expiry Expiry date as string. + * @param boolean $is_secure Currently ignored. + */ + function SimpleCookie($name, $value = false, $path = false, $expiry = false, $is_secure = false) { + $this->_host = false; + $this->_name = $name; + $this->_value = $value; + $this->_path = ($path ? $this->_fixPath($path) : "/"); + $this->_expiry = false; + if (is_string($expiry)) { + $this->_expiry = strtotime($expiry); + } elseif (is_integer($expiry)) { + $this->_expiry = $expiry; + } + $this->_is_secure = $is_secure; + } + + /** + * Sets the host. The cookie rules determine + * that the first two parts are taken for + * certain TLDs and three for others. If the + * new host does not match these rules then the + * call will fail. + * @param string $host New hostname. + * @return boolean True if hostname is valid. + * @access public + */ + function setHost($host) { + if ($host = $this->_truncateHost($host)) { + $this->_host = $host; + return true; + } + return false; + } + + /** + * Accessor for the truncated host to which this + * cookie applies. + * @return string Truncated hostname. + * @access public + */ + function getHost() { + return $this->_host; + } + + /** + * Test for a cookie being valid for a host name. + * @param string $host Host to test against. + * @return boolean True if the cookie would be valid + * here. + */ + function isValidHost($host) { + return ($this->_truncateHost($host) === $this->getHost()); + } + + /** + * Extracts just the domain part that determines a + * cookie's host validity. + * @param string $host Host name to truncate. + * @return string Domain or false on a bad host. + * @access private + */ + function _truncateHost($host) { + $tlds = SimpleUrl::getAllTopLevelDomains(); + if (preg_match('/[a-z\-]+\.(' . $tlds . ')$/i', $host, $matches)) { + return $matches[0]; + } elseif (preg_match('/[a-z\-]+\.[a-z\-]+\.[a-z\-]+$/i', $host, $matches)) { + return $matches[0]; + } + return false; + } + + /** + * Accessor for name. + * @return string Cookie key. + * @access public + */ + function getName() { + return $this->_name; + } + + /** + * Accessor for value. A deleted cookie will + * have an empty string for this. + * @return string Cookie value. + * @access public + */ + function getValue() { + return $this->_value; + } + + /** + * Accessor for path. + * @return string Valid cookie path. + * @access public + */ + function getPath() { + return $this->_path; + } + + /** + * Tests a path to see if the cookie applies + * there. The test path must be longer or + * equal to the cookie path. + * @param string $path Path to test against. + * @return boolean True if cookie valid here. + * @access public + */ + function isValidPath($path) { + return (strncmp( + $this->_fixPath($path), + $this->getPath(), + strlen($this->getPath())) == 0); + } + + /** + * Accessor for expiry. + * @return string Expiry string. + * @access public + */ + function getExpiry() { + if (! $this->_expiry) { + return false; + } + return gmdate("D, d M Y H:i:s", $this->_expiry) . " GMT"; + } + + /** + * Test to see if cookie is expired against + * the cookie format time or timestamp. + * Will give true for a session cookie. + * @param integer/string $now Time to test against. Result + * will be false if this time + * is later than the cookie expiry. + * Can be either a timestamp integer + * or a cookie format date. + * @access public + */ + function isExpired($now) { + if (! $this->_expiry) { + return true; + } + if (is_string($now)) { + $now = strtotime($now); + } + return ($this->_expiry < $now); + } + + /** + * Ages the cookie by the specified number of + * seconds. + * @param integer $interval In seconds. + * @public + */ + function agePrematurely($interval) { + if ($this->_expiry) { + $this->_expiry -= $interval; + } + } + + /** + * Accessor for the secure flag. + * @return boolean True if cookie needs SSL. + * @access public + */ + function isSecure() { + return $this->_is_secure; + } + + /** + * Adds a trailing and leading slash to the path + * if missing. + * @param string $path Path to fix. + * @access private + */ + function _fixPath($path) { + if (substr($path, 0, 1) != '/') { + $path = '/' . $path; + } + if (substr($path, -1, 1) != '/') { + $path .= '/'; + } + return $path; + } + } + + /** + * Creates HTTP headers for the end point of + * a HTTP request. + * @package SimpleTest + * @subpackage WebTester + */ + class SimpleRoute { + protected $_url; + + /** + * Sets the target URL. + * @param SimpleUrl $url URL as object. + * @access public + */ + function SimpleRoute($url) { + $this->_url = $url; + } + + /** + * Resource name. + * @return SimpleUrl Current url. + * @access protected + */ + function getUrl() { + return $this->_url; + } + + /** + * Creates the first line which is the actual request. + * @param string $method HTTP request method, usually GET. + * @return string Request line content. + * @access protected + */ + function _getRequestLine($method) { + return $method . ' ' . $this->_url->getPath() . + $this->_url->getEncodedRequest() . ' HTTP/1.0'; + } + + /** + * Creates the host part of the request. + * @return string Host line content. + * @access protected + */ + function _getHostLine() { + $line = 'Host: ' . $this->_url->getHost(); + if ($this->_url->getPort()) { + $line .= ':' . $this->_url->getPort(); + } + return $line; + } + + /** + * Opens a socket to the route. + * @param string $method HTTP request method, usually GET. + * @param integer $timeout Connection timeout. + * @return SimpleSocket New socket. + * @access public + */ + function createConnection($method, $timeout) { + $default_port = ('https' == $this->_url->getScheme()) ? 443 : 80; + $socket = $this->_createSocket( + $this->_url->getScheme() ? $this->_url->getScheme() : 'http', + $this->_url->getHost(), + $this->_url->getPort() ? $this->_url->getPort() : $default_port, + $timeout); + if (! $socket->isError()) { + $socket->write($this->_getRequestLine($method) . "\r\n"); + $socket->write($this->_getHostLine() . "\r\n"); + $socket->write("Connection: close\r\n"); + } + return $socket; + } + + /** + * Factory for socket. + * @param string $scheme Protocol to use. + * @param string $host Hostname to connect to. + * @param integer $port Remote port. + * @param integer $timeout Connection timeout. + * @return SimpleSocket/SimpleSecureSocket New socket. + * @access protected + */ + function _createSocket($scheme, $host, $port, $timeout) { + if (in_array($scheme, array('https'))) { + return new SimpleSecureSocket($host, $port, $timeout); + } + return new SimpleSocket($host, $port, $timeout); + } + } + + /** + * Creates HTTP headers for the end point of + * a HTTP request via a proxy server. + * @package SimpleTest + * @subpackage WebTester + */ + class SimpleProxyRoute extends SimpleRoute { + protected $_proxy; + protected $_username; + protected $_password; + + /** + * Stashes the proxy address. + * @param SimpleUrl $url URL as object. + * @param string $proxy Proxy URL. + * @param string $username Username for autentication. + * @param string $password Password for autentication. + * @access public + */ + function SimpleProxyRoute($url, $proxy, $username = false, $password = false) { + $this->SimpleRoute($url); + $this->_proxy = $proxy; + $this->_username = $username; + $this->_password = $password; + } + + /** + * Creates the first line which is the actual request. + * @param string $method HTTP request method, usually GET. + * @param SimpleUrl $url URL as object. + * @return string Request line content. + * @access protected + */ + function _getRequestLine($method) { + $url = $this->getUrl(); + $scheme = $url->getScheme() ? $url->getScheme() : 'http'; + $port = $url->getPort() ? ':' . $url->getPort() : ''; + return $method . ' ' . $scheme . '://' . $url->getHost() . $port . + $url->getPath() . $url->getEncodedRequest() . ' HTTP/1.0'; + } + + /** + * Creates the host part of the request. + * @param SimpleUrl $url URL as object. + * @return string Host line content. + * @access protected + */ + function _getHostLine() { + $host = 'Host: ' . $this->_proxy->getHost(); + $port = $this->_proxy->getPort() ? $this->_proxy->getPort() : 8080; + return "$host:$port"; + } + + /** + * Opens a socket to the route. + * @param string $method HTTP request method, usually GET. + * @param integer $timeout Connection timeout. + * @return SimpleSocket New socket. + * @access public + */ + function createConnection($method, $timeout) { + $socket = $this->_createSocket( + $this->_proxy->getScheme() ? $this->_proxy->getScheme() : 'http', + $this->_proxy->getHost(), + $this->_proxy->getPort() ? $this->_proxy->getPort() : 8080, + $timeout); + if (! $socket->isError()) { + $socket->write($this->_getRequestLine($method) . "\r\n"); + $socket->write($this->_getHostLine() . "\r\n"); + if ($this->_username && $this->_password) { + $socket->write('Proxy-Authorization: Basic ' . + base64_encode($this->_username . ':' . $this->_password) . + "\r\n"); + } + $socket->write("Connection: close\r\n"); + } + return $socket; + } + } + + /** + * HTTP request for a web page. Factory for + * HttpResponse object. + * @package SimpleTest + * @subpackage WebTester + */ + class SimpleHttpRequest { + protected $_route; + protected $_method; + protected $_encoding; + protected $_headers; + protected $_cookies; + + /** + * Saves the URL ready for fetching. + * @param SimpleRoute $route Request route. + * @param string $method HTTP request method, + * usually GET. + * @param SimpleFormEncoding $encoding Content to send with + * request or false. + * @access public + */ + function SimpleHttpRequest($route, $method, $encoding = false) { + $this->_route = $route; + $this->_method = $method; + $this->_encoding = $encoding; + $this->_headers = array(); + $this->_cookies = array(); + } + + /** + * Fetches the content and parses the headers. + * @param integer $timeout Connection timeout. + * @return SimpleHttpResponse A response which may only have + * an error. + * @access public + */ + function fetch($timeout) { + $socket = $this->_route->createConnection($this->_method, $timeout); + if ($socket->isError()) { + return $this->_createResponse($socket); + } + $this->_dispatchRequest($socket, $this->_method, $this->_encoding); + return $this->_createResponse($socket); + } + + /** + * Sends the headers. + * @param SimpleSocket $socket Open socket. + * @param string $method HTTP request method, + * usually GET. + * @param SimpleFormEncoding $encoding Content to send with request. + * @access private + */ + function _dispatchRequest($socket, $method, $encoding) { + if ($encoding || ($method == 'POST')) { + $socket->write("Content-Length: " . $this->_getContentLength($encoding) . "\r\n"); + $socket->write("Content-Type: application/x-www-form-urlencoded\r\n"); + } + foreach ($this->_headers as $header_line) { + $socket->write($header_line . "\r\n"); + } + if (count($this->_cookies) > 0) { + $socket->write("Cookie: " . $this->_marshallCookies($this->_cookies) . "\r\n"); + } + $socket->write("\r\n"); + if ($encoding) { + $socket->write($encoding->asString()); + } + } + + /** + * Calculates the length of the encoded content. + * @param SimpleFormEncoding $encoding Content to send with + * request or false. + */ + function _getContentLength($encoding) { + if (! $encoding) { + return 0; + } + return (integer)strlen($encoding->asString()); + } + + /** + * Adds a header line to the request. + * @param string $header_line Text of header line. + * @access public + */ + function addHeaderLine($header_line) { + $this->_headers[] = $header_line; + } + + /** + * Adds a cookie to the request. + * @param SimpleCookie $cookie Additional cookie. + * @access public + */ + function setCookie($cookie) { + $this->_cookies[] = $cookie; + } + + /** + * Serialises the cookie hash ready for + * transmission. + * @param hash $cookies Parsed cookies. + * @return array Cookies in header form. + * @access private + */ + function _marshallCookies($cookies) { + $cookie_pairs = array(); + foreach ($cookies as $cookie) { + $cookie_pairs[] = $cookie->getName() . "=" . $cookie->getValue(); + } + return implode(";", $cookie_pairs); + } + + /** + * Wraps the socket in a response parser. + * @param SimpleSocket $socket Responding socket. + * @return SimpleHttpResponse Parsed response object. + * @access protected + */ + function _createResponse($socket) { + return new SimpleHttpResponse( + $socket, + $this->_method, + $this->_route->getUrl(), + $this->_encoding); + } + } + + /** + * Collection of header lines in the response. + * @package SimpleTest + * @subpackage WebTester + */ + class SimpleHttpHeaders { + protected $_raw_headers; + protected $_response_code; + protected $_http_version; + protected $_mime_type; + protected $_location; + protected $_cookies; + protected $_authentication; + protected $_realm; + + /** + * Parses the incoming header block. + * @param string $headers Header block. + * @access public + */ + function SimpleHttpHeaders($headers) { + $this->_raw_headers = $headers; + $this->_response_code = false; + $this->_http_version = false; + $this->_mime_type = ''; + $this->_location = false; + $this->_cookies = array(); + $this->_authentication = false; + $this->_realm = false; + foreach (split("\r\n", $headers) as $header_line) { + $this->_parseHeaderLine($header_line); + } + } + + /** + * Accessor for parsed HTTP protocol version. + * @return integer HTTP error code. + * @access public + */ + function getHttpVersion() { + return $this->_http_version; + } + + /** + * Accessor for raw header block. + * @return string All headers as raw string. + * @access public + */ + function getRaw() { + return $this->_raw_headers; + } + + /** + * Accessor for parsed HTTP error code. + * @return integer HTTP error code. + * @access public + */ + function getResponseCode() { + return (integer)$this->_response_code; + } + + /** + * Returns the redirected URL or false if + * no redirection. + * @return string URL or false for none. + * @access public + */ + function getLocation() { + return $this->_location; + } + + /** + * Test to see if the response is a valid redirect. + * @return boolean True if valid redirect. + * @access public + */ + function isRedirect() { + return in_array($this->_response_code, array(301, 302, 303, 307)) && + (boolean)$this->getLocation(); + } + + /** + * Test to see if the response is an authentication + * challenge. + * @return boolean True if challenge. + * @access public + */ + function isChallenge() { + return ($this->_response_code == 401) && + (boolean)$this->_authentication && + (boolean)$this->_realm; + } + + /** + * Accessor for MIME type header information. + * @return string MIME type. + * @access public + */ + function getMimeType() { + return $this->_mime_type; + } + + /** + * Accessor for authentication type. + * @return string Type. + * @access public + */ + function getAuthentication() { + return $this->_authentication; + } + + /** + * Accessor for security realm. + * @return string Realm. + * @access public + */ + function getRealm() { + return $this->_realm; + } + + /** + * Accessor for any new cookies. + * @return array List of new cookies. + * @access public + */ + function getNewCookies() { + return $this->_cookies; + } + + /** + * Called on each header line to accumulate the held + * data within the class. + * @param string $header_line One line of header. + * @access protected + */ + function _parseHeaderLine($header_line) { + if (preg_match('/HTTP\/(\d+\.\d+)\s+(.*?)\s/i', $header_line, $matches)) { + $this->_http_version = $matches[1]; + $this->_response_code = $matches[2]; + } + if (preg_match('/Content-type:\s*(.*)/i', $header_line, $matches)) { + $this->_mime_type = trim($matches[1]); + } + if (preg_match('/Location:\s*(.*)/i', $header_line, $matches)) { + $this->_location = trim($matches[1]); + } + if (preg_match('/Set-cookie:(.*)/i', $header_line, $matches)) { + $this->_cookies[] = $this->_parseCookie($matches[1]); + } + if (preg_match('/WWW-Authenticate:\s+(\S+)\s+realm=\"(.*?)\"/i', $header_line, $matches)) { + $this->_authentication = $matches[1]; + $this->_realm = trim($matches[2]); + } + } + + /** + * Parse the Set-cookie content. + * @param string $cookie_line Text after "Set-cookie:" + * @return SimpleCookie New cookie object. + * @access private + */ + function _parseCookie($cookie_line) { + $parts = split(";", $cookie_line); + $cookie = array(); + preg_match('/\s*(.*?)\s*=(.*)/', array_shift($parts), $cookie); + foreach ($parts as $part) { + if (preg_match('/\s*(.*?)\s*=(.*)/', $part, $matches)) { + $cookie[$matches[1]] = trim($matches[2]); + } + } + return new SimpleCookie( + $cookie[1], + trim($cookie[2]), + isset($cookie["path"]) ? $cookie["path"] : "", + isset($cookie["expires"]) ? $cookie["expires"] : false); + } + } + + /** + * Basic HTTP response. + * @package SimpleTest + * @subpackage WebTester + */ + class SimpleHttpResponse extends SimpleStickyError { + protected $_method; + protected $_url; + protected $_request_data; + protected $_sent; + protected $_content; + protected $_headers; + + /** + * Constructor. Reads and parses the incoming + * content and headers. + * @param SimpleSocket $socket Network connection to fetch + * response text from. + * @param string $method HTTP request method. + * @param SimpleUrl $url Resource name. + * @param mixed $request_data Record of content sent. + * @access public + */ + function SimpleHttpResponse($socket, $method, $url, $request_data = '') { + $this->SimpleStickyError(); + $this->_method = $method; + $this->_url = $url; + $this->_request_data = $request_data; + $this->_sent = $socket->getSent(); + $this->_content = false; + $raw = $this->_readAll($socket); + if ($socket->isError()) { + $this->_setError('Error reading socket [' . $socket->getError() . ']'); + return; + } + $this->_parse($raw); + } + + /** + * Splits up the headers and the rest of the content. + * @param string $raw Content to parse. + * @access private + */ + function _parse($raw) { + if (! $raw) { + $this->_setError('Nothing fetched'); + $this->_headers = new SimpleHttpHeaders(''); + } elseif (! strstr($raw, "\r\n\r\n")) { + $this->_setError('Could not parse headers'); + $this->_headers = new SimpleHttpHeaders($raw); + } else { + list($headers, $this->_content) = split("\r\n\r\n", $raw, 2); + $this->_headers = new SimpleHttpHeaders($headers); + } + } + + /** + * Original request method. + * @return string GET, POST or HEAD. + * @access public + */ + function getMethod() { + return $this->_method; + } + + /** + * Resource name. + * @return SimpleUrl Current url. + * @access public + */ + function getUrl() { + return $this->_url; + } + + /** + * Original request data. + * @return mixed Sent content. + * @access public + */ + function getRequestData() { + return $this->_request_data; + } + + /** + * Raw request that was sent down the wire. + * @return string Bytes actually sent. + * @access public + */ + function getSent() { + return $this->_sent; + } + + /** + * Accessor for the content after the last + * header line. + * @return string All content. + * @access public + */ + function getContent() { + return $this->_content; + } + + /** + * Accessor for header block. The response is the + * combination of this and the content. + * @return SimpleHeaders Wrapped header block. + * @access public + */ + function getHeaders() { + return $this->_headers; + } + + /** + * Accessor for any new cookies. + * @return array List of new cookies. + * @access public + */ + function getNewCookies() { + return $this->_headers->getNewCookies(); + } + + /** + * Reads the whole of the socket output into a + * single string. + * @param SimpleSocket $socket Unread socket. + * @return string Raw output if successful + * else false. + * @access private + */ + function _readAll($socket) { + $all = ''; + while (! $this->_isLastPacket($next = $socket->read())) { + $all .= $next; + } + return $all; + } + + /** + * Test to see if the packet from the socket is the + * last one. + * @param string $packet Chunk to interpret. + * @return boolean True if empty or EOF. + * @access private + */ + function _isLastPacket($packet) { + if (is_string($packet)) { + return $packet === ''; + } + return ! $packet; + } + } +?>
\ No newline at end of file |