1<?php 2/** 3 * Base include file for SimpleTest 4 * @package SimpleTest 5 * @subpackage WebTester 6 * @version $Id$ 7 */ 8 9/**#@+ 10 * include other SimpleTest class files 11 */ 12require_once(dirname(__FILE__) . '/cookies.php'); 13require_once(dirname(__FILE__) . '/http.php'); 14require_once(dirname(__FILE__) . '/encoding.php'); 15require_once(dirname(__FILE__) . '/authentication.php'); 16/**#@-*/ 17 18if (! defined('DEFAULT_MAX_REDIRECTS')) { 19 define('DEFAULT_MAX_REDIRECTS', 3); 20} 21if (! defined('DEFAULT_CONNECTION_TIMEOUT')) { 22 define('DEFAULT_CONNECTION_TIMEOUT', 15); 23} 24 25/** 26 * Fetches web pages whilst keeping track of 27 * cookies and authentication. 28 * @package SimpleTest 29 * @subpackage WebTester 30 */ 31class SimpleUserAgent { 32 private $cookie_jar; 33 private $cookies_enabled = true; 34 private $authenticator; 35 private $max_redirects = DEFAULT_MAX_REDIRECTS; 36 private $proxy = false; 37 private $proxy_username = false; 38 private $proxy_password = false; 39 private $connection_timeout = DEFAULT_CONNECTION_TIMEOUT; 40 private $additional_headers = array(); 41 42 /** 43 * Starts with no cookies, realms or proxies. 44 * @access public 45 */ 46 function __construct() { 47 $this->cookie_jar = new SimpleCookieJar(); 48 $this->authenticator = new SimpleAuthenticator(); 49 } 50 51 /** 52 * Removes expired and temporary cookies as if 53 * the browser was closed and re-opened. Authorisation 54 * has to be obtained again as well. 55 * @param string/integer $date Time when session restarted. 56 * If omitted then all persistent 57 * cookies are kept. 58 * @access public 59 */ 60 function restart($date = false) { 61 $this->cookie_jar->restartSession($date); 62 $this->authenticator->restartSession(); 63 } 64 65 /** 66 * Adds a header to every fetch. 67 * @param string $header Header line to add to every 68 * request until cleared. 69 * @access public 70 */ 71 function addHeader($header) { 72 $this->additional_headers[] = $header; 73 } 74 75 /** 76 * Ages the cookies by the specified time. 77 * @param integer $interval Amount in seconds. 78 * @access public 79 */ 80 function ageCookies($interval) { 81 $this->cookie_jar->agePrematurely($interval); 82 } 83 84 /** 85 * Sets an additional cookie. If a cookie has 86 * the same name and path it is replaced. 87 * @param string $name Cookie key. 88 * @param string $value Value of cookie. 89 * @param string $host Host upon which the cookie is valid. 90 * @param string $path Cookie path if not host wide. 91 * @param string $expiry Expiry date. 92 * @access public 93 */ 94 function setCookie($name, $value, $host = false, $path = '/', $expiry = false) { 95 $this->cookie_jar->setCookie($name, $value, $host, $path, $expiry); 96 } 97 98 /** 99 * Reads the most specific cookie value from the 100 * browser cookies. 101 * @param string $host Host to search. 102 * @param string $path Applicable path. 103 * @param string $name Name of cookie to read. 104 * @return string False if not present, else the 105 * value as a string. 106 * @access public 107 */ 108 function getCookieValue($host, $path, $name) { 109 return $this->cookie_jar->getCookieValue($host, $path, $name); 110 } 111 112 /** 113 * Reads the current cookies within the base URL. 114 * @param string $name Key of cookie to find. 115 * @param SimpleUrl $base Base URL to search from. 116 * @return string/boolean Null if there is no base URL, false 117 * if the cookie is not set. 118 * @access public 119 */ 120 function getBaseCookieValue($name, $base) { 121 if (! $base) { 122 return null; 123 } 124 return $this->getCookieValue($base->getHost(), $base->getPath(), $name); 125 } 126 127 /** 128 * Switches off cookie sending and recieving. 129 * @access public 130 */ 131 function ignoreCookies() { 132 $this->cookies_enabled = false; 133 } 134 135 /** 136 * Switches back on the cookie sending and recieving. 137 * @access public 138 */ 139 function useCookies() { 140 $this->cookies_enabled = true; 141 } 142 143 /** 144 * Sets the socket timeout for opening a connection. 145 * @param integer $timeout Maximum time in seconds. 146 * @access public 147 */ 148 function setConnectionTimeout($timeout) { 149 $this->connection_timeout = $timeout; 150 } 151 152 /** 153 * Sets the maximum number of redirects before 154 * a page will be loaded anyway. 155 * @param integer $max Most hops allowed. 156 * @access public 157 */ 158 function setMaximumRedirects($max) { 159 $this->max_redirects = $max; 160 } 161 162 /** 163 * Sets proxy to use on all requests for when 164 * testing from behind a firewall. Set URL 165 * to false to disable. 166 * @param string $proxy Proxy URL. 167 * @param string $username Proxy username for authentication. 168 * @param string $password Proxy password for authentication. 169 * @access public 170 */ 171 function useProxy($proxy, $username, $password) { 172 if (! $proxy) { 173 $this->proxy = false; 174 return; 175 } 176 if ((strncmp($proxy, 'http://', 7) != 0) && (strncmp($proxy, 'https://', 8) != 0)) { 177 $proxy = 'http://'. $proxy; 178 } 179 $this->proxy = new SimpleUrl($proxy); 180 $this->proxy_username = $username; 181 $this->proxy_password = $password; 182 } 183 184 /** 185 * Test to see if the redirect limit is passed. 186 * @param integer $redirects Count so far. 187 * @return boolean True if over. 188 * @access private 189 */ 190 protected function isTooManyRedirects($redirects) { 191 return ($redirects > $this->max_redirects); 192 } 193 194 /** 195 * Sets the identity for the current realm. 196 * @param string $host Host to which realm applies. 197 * @param string $realm Full name of realm. 198 * @param string $username Username for realm. 199 * @param string $password Password for realm. 200 * @access public 201 */ 202 function setIdentity($host, $realm, $username, $password) { 203 $this->authenticator->setIdentityForRealm($host, $realm, $username, $password); 204 } 205 206 /** 207 * Fetches a URL as a response object. Will keep trying if redirected. 208 * It will also collect authentication realm information. 209 * @param string/SimpleUrl $url Target to fetch. 210 * @param SimpleEncoding $encoding Additional parameters for request. 211 * @return SimpleHttpResponse Hopefully the target page. 212 * @access public 213 */ 214 function fetchResponse($url, $encoding) { 215 if ($encoding->getMethod() != 'POST') { 216 $url->addRequestParameters($encoding); 217 $encoding->clear(); 218 } 219 $response = $this->fetchWhileRedirected($url, $encoding); 220 if ($headers = $response->getHeaders()) { 221 if ($headers->isChallenge()) { 222 $this->authenticator->addRealm( 223 $url, 224 $headers->getAuthentication(), 225 $headers->getRealm()); 226 } 227 } 228 return $response; 229 } 230 231 /** 232 * Fetches the page until no longer redirected or 233 * until the redirect limit runs out. 234 * @param SimpleUrl $url Target to fetch. 235 * @param SimpelFormEncoding $encoding Additional parameters for request. 236 * @return SimpleHttpResponse Hopefully the target page. 237 * @access private 238 */ 239 protected function fetchWhileRedirected($url, $encoding) { 240 $redirects = 0; 241 do { 242 $response = $this->fetch($url, $encoding); 243 if ($response->isError()) { 244 return $response; 245 } 246 $headers = $response->getHeaders(); 247 if ($this->cookies_enabled) { 248 $headers->writeCookiesToJar($this->cookie_jar, $url); 249 } 250 if (! $headers->isRedirect()) { 251 break; 252 } 253 $location = new SimpleUrl($headers->getLocation()); 254 $url = $location->makeAbsolute($url); 255 $encoding = new SimpleGetEncoding(); 256 } while (! $this->isTooManyRedirects(++$redirects)); 257 return $response; 258 } 259 260 /** 261 * Actually make the web request. 262 * @param SimpleUrl $url Target to fetch. 263 * @param SimpleFormEncoding $encoding Additional parameters for request. 264 * @return SimpleHttpResponse Headers and hopefully content. 265 * @access protected 266 */ 267 protected function fetch($url, $encoding) { 268 $request = $this->createRequest($url, $encoding); 269 return $request->fetch($this->connection_timeout); 270 } 271 272 /** 273 * Creates a full page request. 274 * @param SimpleUrl $url Target to fetch as url object. 275 * @param SimpleFormEncoding $encoding POST/GET parameters. 276 * @return SimpleHttpRequest New request. 277 * @access private 278 */ 279 protected function createRequest($url, $encoding) { 280 $request = $this->createHttpRequest($url, $encoding); 281 $this->addAdditionalHeaders($request); 282 if ($this->cookies_enabled) { 283 $request->readCookiesFromJar($this->cookie_jar, $url); 284 } 285 $this->authenticator->addHeaders($request, $url); 286 return $request; 287 } 288 289 /** 290 * Builds the appropriate HTTP request object. 291 * @param SimpleUrl $url Target to fetch as url object. 292 * @param SimpleFormEncoding $parameters POST/GET parameters. 293 * @return SimpleHttpRequest New request object. 294 * @access protected 295 */ 296 protected function createHttpRequest($url, $encoding) { 297 return new SimpleHttpRequest($this->createRoute($url), $encoding); 298 } 299 300 /** 301 * Sets up either a direct route or via a proxy. 302 * @param SimpleUrl $url Target to fetch as url object. 303 * @return SimpleRoute Route to take to fetch URL. 304 * @access protected 305 */ 306 protected function createRoute($url) { 307 if ($this->proxy) { 308 return new SimpleProxyRoute( 309 $url, 310 $this->proxy, 311 $this->proxy_username, 312 $this->proxy_password); 313 } 314 return new SimpleRoute($url); 315 } 316 317 /** 318 * Adds additional manual headers. 319 * @param SimpleHttpRequest $request Outgoing request. 320 * @access private 321 */ 322 protected function addAdditionalHeaders(&$request) { 323 foreach ($this->additional_headers as $header) { 324 $request->addHeaderLine($header); 325 } 326 } 327} 328?>