0337d704 |
1 | <?php |
2 | /** |
3 | * Base include file for SimpleTest |
4 | * @package SimpleTest |
5 | * @subpackage WebTester |
6 | * @version $Id: user_agent.php,v 1.38 2004/06/30 20:40:07 lastcraft Exp $ |
7 | */ |
8 | |
9 | /**#@+ |
10 | * include other SimpleTest class files |
11 | */ |
12 | require_once(dirname(__FILE__) . '/http.php'); |
13 | require_once(dirname(__FILE__) . '/authentication.php'); |
14 | /**#@-*/ |
15 | |
16 | define('DEFAULT_MAX_REDIRECTS', 3); |
17 | define('DEFAULT_CONNECTION_TIMEOUT', 15); |
18 | |
19 | /** |
20 | * Repository for cookies. This stuff is a |
21 | * tiny bit browser dependent. |
22 | * @package SimpleTest |
23 | * @subpackage WebTester |
24 | */ |
25 | class SimpleCookieJar { |
26 | var $_cookies; |
27 | |
28 | /** |
29 | * Constructor. Jar starts empty. |
30 | * @access public |
31 | */ |
32 | function SimpleCookieJar() { |
33 | $this->_cookies = array(); |
34 | } |
35 | |
36 | /** |
37 | * Removes expired and temporary cookies as if |
38 | * the browser was closed and re-opened. |
39 | * @param string/integer $now Time to test expiry against. |
40 | * @access public |
41 | */ |
42 | function restartSession($date = false) { |
43 | $surviving_cookies = array(); |
44 | for ($i = 0; $i < count($this->_cookies); $i++) { |
45 | if (! $this->_cookies[$i]->getValue()) { |
46 | continue; |
47 | } |
48 | if (! $this->_cookies[$i]->getExpiry()) { |
49 | continue; |
50 | } |
51 | if ($date && $this->_cookies[$i]->isExpired($date)) { |
52 | continue; |
53 | } |
54 | $surviving_cookies[] = $this->_cookies[$i]; |
55 | } |
56 | $this->_cookies = $surviving_cookies; |
57 | } |
58 | |
59 | /** |
60 | * Ages all cookies in the cookie jar. |
61 | * @param integer $interval The old session is moved |
62 | * into the past by this number |
63 | * of seconds. Cookies now over |
64 | * age will be removed. |
65 | * @access public |
66 | */ |
67 | function agePrematurely($interval) { |
68 | for ($i = 0; $i < count($this->_cookies); $i++) { |
69 | $this->_cookies[$i]->agePrematurely($interval); |
70 | } |
71 | } |
72 | |
73 | /** |
74 | * Adds a cookie to the jar. This will overwrite |
75 | * cookies with matching host, paths and keys. |
76 | * @param SimpleCookie $cookie New cookie. |
77 | * @access public |
78 | */ |
79 | function setCookie($cookie) { |
80 | for ($i = 0; $i < count($this->_cookies); $i++) { |
81 | $is_match = $this->_isMatch( |
82 | $cookie, |
83 | $this->_cookies[$i]->getHost(), |
84 | $this->_cookies[$i]->getPath(), |
85 | $this->_cookies[$i]->getName()); |
86 | if ($is_match) { |
87 | $this->_cookies[$i] = $cookie; |
88 | return; |
89 | } |
90 | } |
91 | $this->_cookies[] = $cookie; |
92 | } |
93 | |
94 | /** |
95 | * Fetches a hash of all valid cookies filtered |
96 | * by host, path and keyed by name |
97 | * Any cookies with missing categories will not |
98 | * be filtered out by that category. Expired |
99 | * cookies must be cleared by restarting the session. |
100 | * @param string $host Host name requirement. |
101 | * @param string $path Path encompassing cookies. |
102 | * @return hash Valid cookie objects keyed |
103 | * on the cookie name. |
104 | * @access public |
105 | */ |
106 | function getValidCookies($host = false, $path = "/") { |
107 | $valid_cookies = array(); |
108 | foreach ($this->_cookies as $cookie) { |
109 | if ($this->_isMatch($cookie, $host, $path, $cookie->getName())) { |
110 | $valid_cookies[] = $cookie; |
111 | } |
112 | } |
113 | return $valid_cookies; |
114 | } |
115 | |
116 | /** |
117 | * Tests cookie for matching against search |
118 | * criteria. |
119 | * @param SimpleTest $cookie Cookie to test. |
120 | * @param string $host Host must match. |
121 | * @param string $path Cookie path must be shorter than |
122 | * this path. |
123 | * @param string $name Name must match. |
124 | * @return boolean True if matched. |
125 | * @access private |
126 | */ |
127 | function _isMatch($cookie, $host, $path, $name) { |
128 | if ($cookie->getName() != $name) { |
129 | return false; |
130 | } |
131 | if ($host && $cookie->getHost() && !$cookie->isValidHost($host)) { |
132 | return false; |
133 | } |
134 | if (! $cookie->isValidPath($path)) { |
135 | return false; |
136 | } |
137 | return true; |
138 | } |
139 | |
140 | /** |
141 | * Adds the current cookies to a request. |
142 | * @param SimpleHttpRequest $request Request to modify. |
143 | * @param SimpleUrl $url Cookie selector. |
144 | * @access private |
145 | */ |
146 | function addHeaders(&$request, $url) { |
147 | $cookies = $this->getValidCookies($url->getHost(), $url->getPath()); |
148 | foreach ($cookies as $cookie) { |
149 | $request->setCookie($cookie); |
150 | } |
151 | } |
152 | } |
153 | |
154 | /** |
155 | * Fetches web pages whilst keeping track of |
156 | * cookies and authentication. |
157 | * @package SimpleTest |
158 | * @subpackage WebTester |
159 | */ |
160 | class SimpleUserAgent { |
161 | var $_cookie_jar; |
162 | var $_authenticator; |
163 | var $_max_redirects; |
164 | var $_proxy; |
165 | var $_proxy_username; |
166 | var $_proxy_password; |
167 | var $_connection_timeout; |
168 | var $_additional_headers; |
169 | |
170 | /** |
171 | * Starts with no cookies, realms or proxies. |
172 | * @access public |
173 | */ |
174 | function SimpleUserAgent() { |
175 | $this->_cookie_jar = &new SimpleCookieJar(); |
176 | $this->_authenticator = &new SimpleAuthenticator(); |
177 | $this->setMaximumRedirects(DEFAULT_MAX_REDIRECTS); |
178 | $this->_proxy = false; |
179 | $this->_proxy_username = false; |
180 | $this->_proxy_password = false; |
181 | $this->setConnectionTimeout(DEFAULT_CONNECTION_TIMEOUT); |
182 | $this->_additional_headers = array(); |
183 | } |
184 | |
185 | /** |
186 | * Removes expired and temporary cookies as if |
187 | * the browser was closed and re-opened. |
188 | * @param string/integer $date Time when session restarted. |
189 | * If omitted then all persistent |
190 | * cookies are kept. |
191 | * @access public |
192 | */ |
193 | function restartSession($date = false) { |
194 | $this->_cookie_jar->restartSession($date); |
195 | } |
196 | |
197 | /** |
198 | * Adds a header to every fetch. |
199 | * @param string $header Header line to add to every |
200 | * request until cleared. |
201 | * @access public |
202 | */ |
203 | function addHeader($header) { |
204 | $this->_additional_headers[] = $header; |
205 | } |
206 | |
207 | /** |
208 | * Ages the cookies by the specified time. |
209 | * @param integer $interval Amount in seconds. |
210 | * @access public |
211 | */ |
212 | function ageCookies($interval) { |
213 | $this->_cookie_jar->agePrematurely($interval); |
214 | } |
215 | |
216 | /** |
217 | * Sets an additional cookie. If a cookie has |
218 | * the same name and path it is replaced. |
219 | * @param string $name Cookie key. |
220 | * @param string $value Value of cookie. |
221 | * @param string $host Host upon which the cookie is valid. |
222 | * @param string $path Cookie path if not host wide. |
223 | * @param string $expiry Expiry date. |
224 | * @access public |
225 | */ |
226 | function setCookie($name, $value, $host = false, $path = '/', $expiry = false) { |
227 | $cookie = new SimpleCookie($name, $value, $path, $expiry); |
228 | if ($host) { |
229 | $cookie->setHost($host); |
230 | } |
231 | $this->_cookie_jar->setCookie($cookie); |
232 | } |
233 | |
234 | /** |
235 | * Reads the most specific cookie value from the |
236 | * browser cookies. |
237 | * @param string $host Host to search. |
238 | * @param string $path Applicable path. |
239 | * @param string $name Name of cookie to read. |
240 | * @return string False if not present, else the |
241 | * value as a string. |
242 | * @access public |
243 | */ |
244 | function getCookieValue($host, $path, $name) { |
245 | $longest_path = ''; |
246 | foreach ($this->_cookie_jar->getValidCookies($host, $path) as $cookie) { |
247 | if ($name == $cookie->getName()) { |
248 | if (strlen($cookie->getPath()) > strlen($longest_path)) { |
249 | $value = $cookie->getValue(); |
250 | $longest_path = $cookie->getPath(); |
251 | } |
252 | } |
253 | } |
254 | return (isset($value) ? $value : false); |
255 | } |
256 | |
257 | /** |
258 | * Reads the current cookies within the base URL. |
259 | * @param string $name Key of cookie to find. |
260 | * @param SimpleUrl $base Base URL to search from. |
261 | * @return string Null if there is no base URL, false |
262 | * if the cookie is not set. |
263 | * @access public |
264 | */ |
265 | function getBaseCookieValue($name, $base) { |
266 | if (! $base) { |
267 | return null; |
268 | } |
269 | return $this->getCookieValue($base->getHost(), $base->getPath(), $name); |
270 | } |
271 | |
272 | /** |
273 | * Sets the socket timeout for opening a connection. |
274 | * @param integer $timeout Maximum time in seconds. |
275 | * @access public |
276 | */ |
277 | function setConnectionTimeout($timeout) { |
278 | $this->_connection_timeout = $timeout; |
279 | } |
280 | |
281 | /** |
282 | * Sets the maximum number of redirects before |
283 | * a page will be loaded anyway. |
284 | * @param integer $max Most hops allowed. |
285 | * @access public |
286 | */ |
287 | function setMaximumRedirects($max) { |
288 | $this->_max_redirects = $max; |
289 | } |
290 | |
291 | /** |
292 | * Sets proxy to use on all requests for when |
293 | * testing from behind a firewall. Set URL |
294 | * to false to disable. |
295 | * @param string $proxy Proxy URL. |
296 | * @param string $username Proxy username for authentication. |
297 | * @param string $password Proxy password for authentication. |
298 | * @access public |
299 | */ |
300 | function useProxy($proxy, $username, $password) { |
301 | if (! $proxy) { |
302 | $this->_proxy = false; |
303 | return; |
304 | } |
305 | if (strncmp($proxy, 'http://', 7) != 0) { |
306 | $proxy = 'http://'. $proxy; |
307 | } |
308 | $this->_proxy = &new SimpleUrl($proxy); |
309 | $this->_proxy_username = $username; |
310 | $this->_proxy_password = $password; |
311 | } |
312 | |
313 | /** |
314 | * Test to see if the redirect limit is passed. |
315 | * @param integer $redirects Count so far. |
316 | * @return boolean True if over. |
317 | * @access private |
318 | */ |
319 | function _isTooManyRedirects($redirects) { |
320 | return ($redirects > $this->_max_redirects); |
321 | } |
322 | |
323 | /** |
324 | * Sets the identity for the current realm. |
325 | * @param string $host Host to which realm applies. |
326 | * @param string $realm Full name of realm. |
327 | * @param string $username Username for realm. |
328 | * @param string $password Password for realm. |
329 | * @access public |
330 | */ |
331 | function setIdentity($host, $realm, $username, $password) { |
332 | $this->_authenticator->setIdentityForRealm($host, $realm, $username, $password); |
333 | } |
334 | |
335 | /** |
336 | * Fetches a URL as a response object. Will keep trying if redirected. |
337 | * It will also collect authentication realm information. |
338 | * @param string $method GET, POST, etc. |
339 | * @param string/SimpleUrl $url Target to fetch. |
340 | * @param hash $parameters Additional parameters for request. |
341 | * @return SimpleHttpResponse Hopefully the target page. |
342 | * @access public |
343 | */ |
344 | function &fetchResponse($method, $url, $parameters = false) { |
345 | if ($method != 'POST') { |
346 | $url->addRequestParameters($parameters); |
347 | $parameters = false; |
348 | } |
349 | $response = &$this->_fetchWhileRedirected($method, $url, $parameters); |
350 | if ($headers = $response->getHeaders()) { |
351 | if ($headers->isChallenge()) { |
352 | $this->_authenticator->addRealm( |
353 | $url, |
354 | $headers->getAuthentication(), |
355 | $headers->getRealm()); |
356 | } |
357 | } |
358 | return $response; |
359 | } |
360 | |
361 | /** |
362 | * Fetches the page until no longer redirected or |
363 | * until the redirect limit runs out. |
364 | * @param string $method GET, POST, etc. |
365 | * @param SimpleUrl $url Target to fetch. |
366 | * @param hash $parameters Additional parameters for request. |
367 | * @return SimpleHttpResponse Hopefully the target page. |
368 | * @access private |
369 | */ |
370 | function &_fetchWhileRedirected($method, $url, $parameters) { |
371 | $redirects = 0; |
372 | do { |
373 | $response = &$this->_fetch($method, $url, $parameters); |
374 | if ($response->isError()) { |
375 | return $response; |
376 | } |
377 | $headers = $response->getHeaders(); |
378 | $location = new SimpleUrl($headers->getLocation()); |
379 | $url = $location->makeAbsolute($url); |
380 | $this->_addCookiesToJar($url, $headers->getNewCookies()); |
381 | if (! $headers->isRedirect()) { |
382 | break; |
383 | } |
384 | $method = 'GET'; |
385 | $parameters = false; |
386 | } while (! $this->_isTooManyRedirects(++$redirects)); |
387 | return $response; |
388 | } |
389 | |
390 | /** |
391 | * Actually make the web request. |
392 | * @param string $method GET, POST, etc. |
393 | * @param SimpleUrl $url Target to fetch. |
394 | * @param hash $parameters Additional parameters for request. |
395 | * @return SimpleHttpResponse Headers and hopefully content. |
396 | * @access protected |
397 | */ |
398 | function &_fetch($method, $url, $parameters) { |
399 | if (! $parameters) { |
400 | $parameters = array(); |
401 | } |
402 | $request = &$this->_createRequest($method, $url, $parameters); |
403 | return $request->fetch($this->_connection_timeout); |
404 | } |
405 | |
406 | /** |
407 | * Creates a full page request. |
408 | * @param string $method Fetching method. |
409 | * @param SimpleUrl $url Target to fetch as url object. |
410 | * @param hash $parameters POST/GET parameters. |
411 | * @return SimpleHttpRequest New request. |
412 | * @access private |
413 | */ |
414 | function &_createRequest($method, $url, $parameters) { |
415 | $request = &$this->_createHttpRequest($method, $url, $parameters); |
416 | $this->_addAdditionalHeaders($request); |
417 | $this->_cookie_jar->addHeaders($request, $url); |
418 | $this->_authenticator->addHeaders($request, $url); |
419 | return $request; |
420 | } |
421 | |
422 | /** |
423 | * Builds the appropriate HTTP request object. |
424 | * @param string $method Fetching method. |
425 | * @param SimpleUrl $url Target to fetch as url object. |
426 | * @param hash $parameters POST/GET parameters. |
427 | * @return SimpleHttpRequest New request object. |
428 | * @access protected |
429 | */ |
430 | function &_createHttpRequest($method, $url, $parameters) { |
431 | if ($method == 'POST') { |
432 | $request = &new SimpleHttpPostRequest( |
433 | $this->_createRoute($url), |
434 | $parameters); |
435 | return $request; |
436 | } |
437 | if ($parameters) { |
438 | $url->addRequestParameters($parameters); |
439 | } |
440 | return new SimpleHttpRequest($this->_createRoute($url), $method); |
441 | } |
442 | |
443 | /** |
444 | * Sets up either a direct route or via a proxy. |
445 | * @param SimpleUrl $url Target to fetch as url object. |
446 | * @return SimpleRoute Route to take to fetch URL. |
447 | * @access protected |
448 | */ |
449 | function &_createRoute($url) { |
450 | if ($this->_proxy) { |
451 | return new SimpleProxyRoute( |
452 | $url, |
453 | $this->_proxy, |
454 | $this->_proxy_username, |
455 | $this->_proxy_password); |
456 | } |
457 | return new SimpleRoute($url); |
458 | } |
459 | |
460 | /** |
461 | * Adds additional manual headers. |
462 | * @param SimpleHttpRequest $request Outgoing request. |
463 | * @access private |
464 | */ |
465 | function _addAdditionalHeaders(&$request) { |
466 | foreach ($this->_additional_headers as $header) { |
467 | $request->addHeaderLine($header); |
468 | } |
469 | } |
470 | |
471 | /** |
472 | * Extracts new cookies into the cookie jar. |
473 | * @param SimpleUrl $url Target to fetch as url object. |
474 | * @param array $cookies New cookies. |
475 | * @access private |
476 | */ |
477 | function _addCookiesToJar($url, $cookies) { |
478 | foreach ($cookies as $cookie) { |
479 | if ($url->getHost()) { |
480 | $cookie->setHost($url->getHost()); |
481 | } |
482 | $this->_cookie_jar->setCookie($cookie); |
483 | } |
484 | } |
485 | } |
486 | ?> |