first reimport from platal
[platal.git] / htdocs / TESTS / simpletest / http.php
1 <?php
2 /**
3 * base include file for SimpleTest
4 * @package SimpleTest
5 * @subpackage WebTester
6 * @version $Id: http.php,v 1.94 2004/06/30 22:13:07 lastcraft Exp $
7 */
8
9 /**#@+
10 * include other SimpleTest class files
11 */
12 require_once(dirname(__FILE__) . '/socket.php');
13 require_once(dirname(__FILE__) . '/url.php');
14 /**#@-*/
15
16 /**
17 * Cookie data holder. Cookie rules are full of pretty
18 * arbitary stuff. I have used...
19 * http://wp.netscape.com/newsref/std/cookie_spec.html
20 * http://www.cookiecentral.com/faq/
21 * @package SimpleTest
22 * @subpackage WebTester
23 */
24 class SimpleCookie {
25 var $_host;
26 var $_name;
27 var $_value;
28 var $_path;
29 var $_expiry;
30 var $_is_secure;
31
32 /**
33 * Constructor. Sets the stored values.
34 * @param string $name Cookie key.
35 * @param string $value Value of cookie.
36 * @param string $path Cookie path if not host wide.
37 * @param string $expiry Expiry date as string.
38 * @param boolean $is_secure Currently ignored.
39 */
40 function SimpleCookie($name, $value = false, $path = false, $expiry = false, $is_secure = false) {
41 $this->_host = false;
42 $this->_name = $name;
43 $this->_value = $value;
44 $this->_path = ($path ? $this->_fixPath($path) : "/");
45 $this->_expiry = false;
46 if (is_string($expiry)) {
47 $this->_expiry = strtotime($expiry);
48 } elseif (is_integer($expiry)) {
49 $this->_expiry = $expiry;
50 }
51 $this->_is_secure = $is_secure;
52 }
53
54 /**
55 * Sets the host. The cookie rules determine
56 * that the first two parts are taken for
57 * certain TLDs and three for others. If the
58 * new host does not match these rules then the
59 * call will fail.
60 * @param string $host New hostname.
61 * @return boolean True if hostname is valid.
62 * @access public
63 */
64 function setHost($host) {
65 if ($host = $this->_truncateHost($host)) {
66 $this->_host = $host;
67 return true;
68 }
69 return false;
70 }
71
72 /**
73 * Accessor for the truncated host to which this
74 * cookie applies.
75 * @return string Truncated hostname.
76 * @access public
77 */
78 function getHost() {
79 return $this->_host;
80 }
81
82 /**
83 * Test for a cookie being valid for a host name.
84 * @param string $host Host to test against.
85 * @return boolean True if the cookie would be valid
86 * here.
87 */
88 function isValidHost($host) {
89 return ($this->_truncateHost($host) === $this->getHost());
90 }
91
92 /**
93 * Extracts just the domain part that determines a
94 * cookie's host validity.
95 * @param string $host Host name to truncate.
96 * @return string Domain or false on a bad host.
97 * @access private
98 */
99 function _truncateHost($host) {
100 if (preg_match('/[a-z\-]+\.(com|edu|net|org|gov|mil|int)$/i', $host, $matches)) {
101 return $matches[0];
102 } elseif (preg_match('/[a-z\-]+\.[a-z\-]+\.[a-z\-]+$/i', $host, $matches)) {
103 return $matches[0];
104 }
105 return false;
106 }
107
108 /**
109 * Accessor for name.
110 * @return string Cookie key.
111 * @access public
112 */
113 function getName() {
114 return $this->_name;
115 }
116
117 /**
118 * Accessor for value. A deleted cookie will
119 * have an empty string for this.
120 * @return string Cookie value.
121 * @access public
122 */
123 function getValue() {
124 return $this->_value;
125 }
126
127 /**
128 * Accessor for path.
129 * @return string Valid cookie path.
130 * @access public
131 */
132 function getPath() {
133 return $this->_path;
134 }
135
136 /**
137 * Tests a path to see if the cookie applies
138 * there. The test path must be longer or
139 * equal to the cookie path.
140 * @param string $path Path to test against.
141 * @return boolean True if cookie valid here.
142 * @access public
143 */
144 function isValidPath($path) {
145 return (strncmp(
146 $this->_fixPath($path),
147 $this->getPath(),
148 strlen($this->getPath())) == 0);
149 }
150
151 /**
152 * Accessor for expiry.
153 * @return string Expiry string.
154 * @access public
155 */
156 function getExpiry() {
157 if (! $this->_expiry) {
158 return false;
159 }
160 return gmdate("D, d M Y H:i:s", $this->_expiry) . " GMT";
161 }
162
163 /**
164 * Test to see if cookie is expired against
165 * the cookie format time or timestamp.
166 * Will give true for a session cookie.
167 * @param integer/string $now Time to test against. Result
168 * will be false if this time
169 * is later than the cookie expiry.
170 * Can be either a timestamp integer
171 * or a cookie format date.
172 * @access public
173 */
174 function isExpired($now) {
175 if (! $this->_expiry) {
176 return true;
177 }
178 if (is_string($now)) {
179 $now = strtotime($now);
180 }
181 return ($this->_expiry < $now);
182 }
183
184 /**
185 * Ages the cookie by the specified number of
186 * seconds.
187 * @param integer $interval In seconds.
188 * @public
189 */
190 function agePrematurely($interval) {
191 if ($this->_expiry) {
192 $this->_expiry -= $interval;
193 }
194 }
195
196 /**
197 * Accessor for the secure flag.
198 * @return boolean True if cookie needs SSL.
199 * @access public
200 */
201 function isSecure() {
202 return $this->_is_secure;
203 }
204
205 /**
206 * Adds a trailing and leading slash to the path
207 * if missing.
208 * @param string $path Path to fix.
209 * @access private
210 */
211 function _fixPath($path) {
212 if (substr($path, 0, 1) != '/') {
213 $path = '/' . $path;
214 }
215 if (substr($path, -1, 1) != '/') {
216 $path .= '/';
217 }
218 return $path;
219 }
220 }
221
222 /**
223 * Creates HTTP headers for the end point of
224 * a HTTP request.
225 * @package SimpleTest
226 * @subpackage WebTester
227 */
228 class SimpleRoute {
229 var $_url;
230
231 /**
232 * Sets the target URL.
233 * @param SimpleUrl $url URL as object.
234 * @access public
235 */
236 function SimpleRoute($url) {
237 $this->_url = $url;
238 }
239
240 /**
241 * Resource name.
242 * @return SimpleUrl Current url.
243 * @access protected
244 */
245 function getUrl() {
246 return $this->_url;
247 }
248
249 /**
250 * Creates the first line which is the actual request.
251 * @param string $method HTTP request method, usually GET.
252 * @return string Request line content.
253 * @access protected
254 */
255 function _getRequestLine($method) {
256 return $method . ' ' . $this->_url->getPath() .
257 $this->_url->getEncodedRequest() . ' HTTP/1.0';
258 }
259
260 /**
261 * Creates the host part of the request.
262 * @return string Host line content.
263 * @access protected
264 */
265 function _getHostLine() {
266 $line = 'Host: ' . $this->_url->getHost();
267 if ($this->_url->getPort()) {
268 $line .= ':' . $this->_url->getPort();
269 }
270 return $line;
271 }
272
273 /**
274 * Opens a socket to the route.
275 * @param string $method HTTP request method, usually GET.
276 * @param integer $timeout Connection timeout.
277 * @return SimpleSocket New socket.
278 * @access public
279 */
280 function &createConnection($method, $timeout) {
281 $default_port = ('https' == $this->_url->getScheme()) ? 443 : 80;
282 $socket = &$this->_createSocket(
283 $this->_url->getScheme() ? $this->_url->getScheme() : 'http',
284 $this->_url->getHost(),
285 $this->_url->getPort() ? $this->_url->getPort() : $default_port,
286 $timeout);
287 if (! $socket->isError()) {
288 $socket->write($this->_getRequestLine($method) . "\r\n");
289 $socket->write($this->_getHostLine() . "\r\n");
290 $socket->write("Connection: close\r\n");
291 }
292 return $socket;
293 }
294
295 /**
296 * Factory for socket.
297 * @param string $scheme Protocol to use.
298 * @param string $host Hostname to connect to.
299 * @param integer $port Remote port.
300 * @param integer $timeout Connection timeout.
301 * @return SimpleSocket/SimpleSecureSocket New socket.
302 * @access protected
303 */
304 function &_createSocket($scheme, $host, $port, $timeout) {
305 if (in_array($scheme, array('https'))) {
306 return new SimpleSecureSocket($host, $port, $timeout);
307 }
308 return new SimpleSocket($host, $port, $timeout);
309 }
310 }
311
312 /**
313 * Creates HTTP headers for the end point of
314 * a HTTP request via a proxy server.
315 * @package SimpleTest
316 * @subpackage WebTester
317 */
318 class SimpleProxyRoute extends SimpleRoute {
319 var $_proxy;
320 var $_username;
321 var $_password;
322
323 /**
324 * Stashes the proxy address.
325 * @param SimpleUrl $url URL as object.
326 * @param string $proxy Proxy URL.
327 * @param string $username Username for autentication.
328 * @param string $password Password for autentication.
329 * @access public
330 */
331 function SimpleProxyRoute($url, $proxy, $username = false, $password = false) {
332 $this->SimpleRoute($url);
333 $this->_proxy = $proxy;
334 $this->_username = $username;
335 $this->_password = $password;
336 }
337
338 /**
339 * Creates the first line which is the actual request.
340 * @param string $method HTTP request method, usually GET.
341 * @param SimpleUrl $url URL as object.
342 * @return string Request line content.
343 * @access protected
344 */
345 function _getRequestLine($method) {
346 $url = $this->getUrl();
347 $scheme = $url->getScheme() ? $url->getScheme() : 'http';
348 $port = $url->getPort() ? ':' . $url->getPort() : '';
349 return $method . ' ' . $scheme . '://' . $url->getHost() . $port .
350 $url->getPath() . $url->getEncodedRequest() . ' HTTP/1.0';
351 }
352
353 /**
354 * Creates the host part of the request.
355 * @param SimpleUrl $url URL as object.
356 * @return string Host line content.
357 * @access protected
358 */
359 function _getHostLine() {
360 $host = 'Host: ' . $this->_proxy->getHost();
361 $port = $this->_proxy->getPort() ? $this->_proxy->getPort() : 8080;
362 return "$host:$port";
363 }
364
365 /**
366 * Opens a socket to the route.
367 * @param string $method HTTP request method, usually GET.
368 * @param integer $timeout Connection timeout.
369 * @return SimpleSocket New socket.
370 * @access public
371 */
372 function &createConnection($method, $timeout) {
373 $socket = &$this->_createSocket(
374 $this->_proxy->getScheme() ? $this->_proxy->getScheme() : 'http',
375 $this->_proxy->getHost(),
376 $this->_proxy->getPort() ? $this->_proxy->getPort() : 8080,
377 $timeout);
378 if (! $socket->isError()) {
379 $socket->write($this->_getRequestLine($method) . "\r\n");
380 $socket->write($this->_getHostLine() . "\r\n");
381 if ($this->_username && $this->_password) {
382 $socket->write('Proxy-Authorization: Basic ' .
383 base64_encode($this->_username . ':' . $this->_password) .
384 "\r\n");
385 }
386 $socket->write("Connection: close\r\n");
387 }
388 return $socket;
389 }
390 }
391
392 /**
393 * HTTP request for a web page. Factory for
394 * HttpResponse object.
395 * @package SimpleTest
396 * @subpackage WebTester
397 */
398 class SimpleHttpRequest {
399 var $_route;
400 var $_method;
401 var $_content;
402 var $_headers;
403 var $_cookies;
404
405 /**
406 * Saves the URL ready for fetching.
407 * @param SimpleRoute $route Request route.
408 * @param string $method HTTP request method,
409 * usually GET.
410 * @param string $content Content to send with request.
411 * @access public
412 */
413 function SimpleHttpRequest(&$route, $method, $content = '') {
414 $this->_route = &$route;
415 $this->_method = $method;
416 $this->_content = $content;
417 $this->_headers = array();
418 $this->_cookies = array();
419 }
420
421 /**
422 * Fetches the content and parses the headers.
423 * @param integer $timeout Connection timeout.
424 * @return SimpleHttpResponse A response which may only have
425 * an error.
426 * @access public
427 */
428 function &fetch($timeout) {
429 $socket = &$this->_route->createConnection($this->_method, $timeout);
430 if ($socket->isError()) {
431 return $this->_createResponse($socket);
432 }
433 $this->_dispatchRequest($socket, $this->_method, $this->_content);
434 return $this->_createResponse($socket);
435 }
436
437 /**
438 * Sends the headers.
439 * @param SimpleSocket $socket Open socket.
440 * @param string $method HTTP request method,
441 * usually GET.
442 * @param string $content Content to send with request.
443 * @access protected
444 */
445 function _dispatchRequest(&$socket, $method, $content) {
446 if ($content) {
447 $socket->write("Content-Length: " . strlen($content) . "\r\n");
448 $socket->write("Content-Type: application/x-www-form-urlencoded\r\n");
449 }
450 foreach ($this->_headers as $header_line) {
451 $socket->write($header_line . "\r\n");
452 }
453 if (count($this->_cookies) > 0) {
454 $socket->write("Cookie: " . $this->_marshallCookies($this->_cookies) . "\r\n");
455 }
456 $socket->write("\r\n");
457 if ($content) {
458 $socket->write($content);
459 }
460 }
461
462 /**
463 * Adds a header line to the request.
464 * @param string $header_line Text of header line.
465 * @access public
466 */
467 function addHeaderLine($header_line) {
468 $this->_headers[] = $header_line;
469 }
470
471 /**
472 * Adds a cookie to the request.
473 * @param SimpleCookie $cookie Additional cookie.
474 * @access public
475 */
476 function setCookie($cookie) {
477 $this->_cookies[] = $cookie;
478 }
479
480 /**
481 * Serialises the cookie hash ready for
482 * transmission.
483 * @param hash $cookies Parsed cookies.
484 * @return array Cookies in header form.
485 * @access private
486 */
487 function _marshallCookies($cookies) {
488 $cookie_pairs = array();
489 foreach ($cookies as $cookie) {
490 $cookie_pairs[] = $cookie->getName() . "=" . $cookie->getValue();
491 }
492 return implode(";", $cookie_pairs);
493 }
494
495 /**
496 * Wraps the socket in a response parser.
497 * @param SimpleSocket $socket Responding socket.
498 * @return SimpleHttpResponse Parsed response object.
499 * @access protected
500 */
501 function &_createResponse(&$socket) {
502 return new SimpleHttpResponse(
503 $socket,
504 $this->_method,
505 $this->_route->getUrl(),
506 $this->_content);
507 }
508 }
509
510 /**
511 * Request with data to send. Usually PUT or POST.
512 * @package SimpleTest
513 * @subpackage WebTester
514 */
515 class SimpleHttpPostRequest extends SimpleHttpRequest {
516
517 /**
518 * Cretaes an HTML form request.
519 * @param SimpleRoute $route Request target.
520 * @param array $parameters Content to send.
521 * @access public
522 */
523 function SimpleHttpPostRequest($route, $parameters) {
524 $this->SimpleHttpRequest($route, 'POST', $parameters);
525 }
526
527 /**
528 * Sends the headers.
529 * @param SimpleSocket $socket Open socket.
530 * @param string $method HTTP request method,
531 * usually GET.
532 * @param string $content Content to send with request.
533 * @access protected
534 */
535 function _dispatchRequest(&$socket, $method, $content) {
536 parent::_dispatchRequest(
537 $socket,
538 $method,
539 SimpleUrl::encodeRequest($content));
540 }
541 }
542
543 /**
544 * Collection of header lines in the response.
545 * @package SimpleTest
546 * @subpackage WebTester
547 */
548 class SimpleHttpHeaders {
549 var $_raw_headers;
550 var $_response_code;
551 var $_http_version;
552 var $_mime_type;
553 var $_location;
554 var $_cookies;
555 var $_authentication;
556 var $_realm;
557
558 /**
559 * Parses the incoming header block.
560 * @param string $headers Header block.
561 * @access public
562 */
563 function SimpleHttpHeaders($headers) {
564 $this->_raw_headers = $headers;
565 $this->_response_code = false;
566 $this->_http_version = false;
567 $this->_mime_type = '';
568 $this->_location = false;
569 $this->_cookies = array();
570 $this->_authentication = false;
571 $this->_realm = false;
572 foreach (split("\r\n", $headers) as $header_line) {
573 $this->_parseHeaderLine($header_line);
574 }
575 }
576
577 /**
578 * Accessor for parsed HTTP protocol version.
579 * @return integer HTTP error code.
580 * @access public
581 */
582 function getHttpVersion() {
583 return $this->_http_version;
584 }
585
586 /**
587 * Accessor for raw header block.
588 * @return string All headers as raw string.
589 * @access public
590 */
591 function getRaw() {
592 return $this->_raw_headers;
593 }
594
595 /**
596 * Accessor for parsed HTTP error code.
597 * @return integer HTTP error code.
598 * @access public
599 */
600 function getResponseCode() {
601 return (integer)$this->_response_code;
602 }
603
604 /**
605 * Returns the redirected URL or false if
606 * no redirection.
607 * @return string URL or false for none.
608 * @access public
609 */
610 function getLocation() {
611 return $this->_location;
612 }
613
614 /**
615 * Test to see if the response is a valid redirect.
616 * @return boolean True if valid redirect.
617 * @access public
618 */
619 function isRedirect() {
620 return in_array($this->_response_code, array(301, 302, 303, 307)) &&
621 (boolean)$this->getLocation();
622 }
623
624 /**
625 * Test to see if the response is an authentication
626 * challenge.
627 * @return boolean True if challenge.
628 * @access public
629 */
630 function isChallenge() {
631 return ($this->_response_code == 401) &&
632 (boolean)$this->_authentication &&
633 (boolean)$this->_realm;
634 }
635
636 /**
637 * Accessor for MIME type header information.
638 * @return string MIME type.
639 * @access public
640 */
641 function getMimeType() {
642 return $this->_mime_type;
643 }
644
645 /**
646 * Accessor for authentication type.
647 * @return string Type.
648 * @access public
649 */
650 function getAuthentication() {
651 return $this->_authentication;
652 }
653
654 /**
655 * Accessor for security realm.
656 * @return string Realm.
657 * @access public
658 */
659 function getRealm() {
660 return $this->_realm;
661 }
662
663 /**
664 * Accessor for any new cookies.
665 * @return array List of new cookies.
666 * @access public
667 */
668 function getNewCookies() {
669 return $this->_cookies;
670 }
671
672 /**
673 * Called on each header line to accumulate the held
674 * data within the class.
675 * @param string $header_line One line of header.
676 * @access protected
677 */
678 function _parseHeaderLine($header_line) {
679 if (preg_match('/HTTP\/(\d+\.\d+)\s+(.*?)\s/i', $header_line, $matches)) {
680 $this->_http_version = $matches[1];
681 $this->_response_code = $matches[2];
682 }
683 if (preg_match('/Content-type:\s*(.*)/i', $header_line, $matches)) {
684 $this->_mime_type = trim($matches[1]);
685 }
686 if (preg_match('/Location:\s*(.*)/i', $header_line, $matches)) {
687 $this->_location = trim($matches[1]);
688 }
689 if (preg_match('/Set-cookie:(.*)/i', $header_line, $matches)) {
690 $this->_cookies[] = $this->_parseCookie($matches[1]);
691 }
692 if (preg_match('/WWW-Authenticate:\s+(\S+)\s+realm=\"(.*?)\"/i', $header_line, $matches)) {
693 $this->_authentication = $matches[1];
694 $this->_realm = trim($matches[2]);
695 }
696 }
697
698 /**
699 * Parse the Set-cookie content.
700 * @param string $cookie_line Text after "Set-cookie:"
701 * @return SimpleCookie New cookie object.
702 * @access private
703 */
704 function _parseCookie($cookie_line) {
705 $parts = split(";", $cookie_line);
706 $cookie = array();
707 preg_match('/\s*(.*?)\s*=(.*)/', array_shift($parts), $cookie);
708 foreach ($parts as $part) {
709 if (preg_match('/\s*(.*?)\s*=(.*)/', $part, $matches)) {
710 $cookie[$matches[1]] = trim($matches[2]);
711 }
712 }
713 return new SimpleCookie(
714 $cookie[1],
715 trim($cookie[2]),
716 isset($cookie["path"]) ? $cookie["path"] : "",
717 isset($cookie["expires"]) ? $cookie["expires"] : false);
718 }
719 }
720
721 /**
722 * Basic HTTP response.
723 * @package SimpleTest
724 * @subpackage WebTester
725 */
726 class SimpleHttpResponse extends StickyError {
727 var $_method;
728 var $_url;
729 var $_request_data;
730 var $_sent;
731 var $_content;
732 var $_headers;
733
734 /**
735 * Constructor. Reads and parses the incoming
736 * content and headers.
737 * @param SimpleSocket $socket Network connection to fetch
738 * response text from.
739 * @param string $method HTTP request method.
740 * @param SimpleUrl $url Resource name.
741 * @param mixed $request_data Record of content sent.
742 * @access public
743 */
744 function SimpleHttpResponse(&$socket, $method, $url, $request_data = '') {
745 $this->StickyError();
746 $this->_method = $method;
747 $this->_url = $url;
748 $this->_request_data = $request_data;
749 $this->_sent = $socket->getSent();
750 $this->_content = false;
751 $raw = $this->_readAll($socket);
752 if ($socket->isError()) {
753 $this->_setError('Error reading socket [' . $socket->getError() . ']');
754 return;
755 }
756 $this->_parse($raw);
757 }
758
759 /**
760 * Splits up the headers and the rest of the content.
761 * @param string $raw Content to parse.
762 * @access private
763 */
764 function _parse($raw) {
765 if (! $raw) {
766 $this->_setError('Nothing fetched');
767 $this->_headers = &new SimpleHttpHeaders('');
768 } elseif (! strstr($raw, "\r\n\r\n")) {
769 $this->_setError('Could not parse headers');
770 $this->_headers = &new SimpleHttpHeaders($raw);
771 } else {
772 list($headers, $this->_content) = split("\r\n\r\n", $raw, 2);
773 $this->_headers = &new SimpleHttpHeaders($headers);
774 }
775 }
776
777 /**
778 * Original request method.
779 * @return string GET, POST or HEAD.
780 * @access public
781 */
782 function getMethod() {
783 return $this->_method;
784 }
785
786 /**
787 * Resource name.
788 * @return SimpleUrl Current url.
789 * @access public
790 */
791 function getUrl() {
792 return $this->_url;
793 }
794
795 /**
796 * Original request data.
797 * @return mixed Sent content.
798 * @access public
799 */
800 function getRequestData() {
801 return $this->_request_data;
802 }
803
804 /**
805 * Raw request that was sent down the wire.
806 * @return string Bytes actually sent.
807 * @access public
808 */
809 function getSent() {
810 return $this->_sent;
811 }
812
813 /**
814 * Accessor for the content after the last
815 * header line.
816 * @return string All content.
817 * @access public
818 */
819 function getContent() {
820 return $this->_content;
821 }
822
823 /**
824 * Accessor for header block. The response is the
825 * combination of this and the content.
826 * @return SimpleHeaders Wrapped header block.
827 * @access public
828 */
829 function getHeaders() {
830 return $this->_headers;
831 }
832
833 /**
834 * Accessor for any new cookies.
835 * @return array List of new cookies.
836 * @access public
837 */
838 function getNewCookies() {
839 return $this->_headers->getNewCookies();
840 }
841
842 /**
843 * Reads the whole of the socket output into a
844 * single string.
845 * @param SimpleSocket $socket Unread socket.
846 * @return string Raw output if successful
847 * else false.
848 * @access private
849 */
850 function _readAll(&$socket) {
851 $all = '';
852 while (! $this->_isLastPacket($next = $socket->read())) {
853 $all .= $next;
854 }
855 return $all;
856 }
857
858 /**
859 * Test to see if the packet from the socket is the
860 * last one.
861 * @param string $packet Chunk to interpret.
862 * @return boolean True if empty or EOF.
863 * @access private
864 */
865 function _isLastPacket($packet) {
866 if (is_string($packet)) {
867 return $packet === '';
868 }
869 return ! $packet;
870 }
871 }
872 ?>