source: trunk/server/www/vendors/simpletest/http.php @ 6

Last change on this file since 6 was 6, checked in by sander, 12 years ago

Added SimpleTest? test framework

File size: 18.8 KB
Line 
1<?php
2/**
3 *  base include file for SimpleTest
4 *  @package    SimpleTest
5 *  @subpackage WebTester
6 *  @version    $Id: http.php 1722 2008-04-07 19:30:56Z lastcraft $
7 */
8
9/**#@+
10 *  include other SimpleTest class files
11 */
12require_once(dirname(__FILE__) . '/socket.php');
13require_once(dirname(__FILE__) . '/cookies.php');
14require_once(dirname(__FILE__) . '/url.php');
15/**#@-*/
16
17/**
18 *    Creates HTTP headers for the end point of
19 *    a HTTP request.
20 *    @package SimpleTest
21 *    @subpackage WebTester
22 */
23class SimpleRoute {
24    var $_url;
25   
26    /**
27     *    Sets the target URL.
28     *    @param SimpleUrl $url   URL as object.
29     *    @access public
30     */
31    function SimpleRoute($url) {
32        $this->_url = $url;
33    }
34   
35    /**
36     *    Resource name.
37     *    @return SimpleUrl        Current url.
38     *    @access protected
39     */
40    function getUrl() {
41        return $this->_url;
42    }
43   
44    /**
45     *    Creates the first line which is the actual request.
46     *    @param string $method   HTTP request method, usually GET.
47     *    @return string          Request line content.
48     *    @access protected
49     */
50    function _getRequestLine($method) {
51        return $method . ' ' . $this->_url->getPath() .
52                $this->_url->getEncodedRequest() . ' HTTP/1.0';
53    }
54   
55    /**
56     *    Creates the host part of the request.
57     *    @return string          Host line content.
58     *    @access protected
59     */
60    function _getHostLine() {
61        $line = 'Host: ' . $this->_url->getHost();
62        if ($this->_url->getPort()) {
63            $line .= ':' . $this->_url->getPort();
64        }
65        return $line;
66    }
67   
68    /**
69     *    Opens a socket to the route.
70     *    @param string $method      HTTP request method, usually GET.
71     *    @param integer $timeout    Connection timeout.
72     *    @return SimpleSocket       New socket.
73     *    @access public
74     */
75    function &createConnection($method, $timeout) {
76        $default_port = ('https' == $this->_url->getScheme()) ? 443 : 80;
77        $socket = &$this->_createSocket(
78                $this->_url->getScheme() ? $this->_url->getScheme() : 'http',
79                $this->_url->getHost(),
80                $this->_url->getPort() ? $this->_url->getPort() : $default_port,
81                $timeout);
82        if (! $socket->isError()) {
83            $socket->write($this->_getRequestLine($method) . "\r\n");
84            $socket->write($this->_getHostLine() . "\r\n");
85            $socket->write("Connection: close\r\n");
86        }
87        return $socket;
88    }
89   
90    /**
91     *    Factory for socket.
92     *    @param string $scheme                   Protocol to use.
93     *    @param string $host                     Hostname to connect to.
94     *    @param integer $port                    Remote port.
95     *    @param integer $timeout                 Connection timeout.
96     *    @return SimpleSocket/SimpleSecureSocket New socket.
97     *    @access protected
98     */
99    function &_createSocket($scheme, $host, $port, $timeout) {
100        if (in_array($scheme, array('https'))) {
101            $socket = &new SimpleSecureSocket($host, $port, $timeout);
102        } else {
103            $socket = &new SimpleSocket($host, $port, $timeout);
104        }
105        return $socket;
106    }
107}
108
109/**
110 *    Creates HTTP headers for the end point of
111 *    a HTTP request via a proxy server.
112 *    @package SimpleTest
113 *    @subpackage WebTester
114 */
115class SimpleProxyRoute extends SimpleRoute {
116    var $_proxy;
117    var $_username;
118    var $_password;
119   
120    /**
121     *    Stashes the proxy address.
122     *    @param SimpleUrl $url     URL as object.
123     *    @param string $proxy      Proxy URL.
124     *    @param string $username   Username for autentication.
125     *    @param string $password   Password for autentication.
126     *    @access public
127     */
128    function SimpleProxyRoute($url, $proxy, $username = false, $password = false) {
129        $this->SimpleRoute($url);
130        $this->_proxy = $proxy;
131        $this->_username = $username;
132        $this->_password = $password;
133    }
134   
135    /**
136     *    Creates the first line which is the actual request.
137     *    @param string $method   HTTP request method, usually GET.
138     *    @param SimpleUrl $url   URL as object.
139     *    @return string          Request line content.
140     *    @access protected
141     */
142    function _getRequestLine($method) {
143        $url = $this->getUrl();
144        $scheme = $url->getScheme() ? $url->getScheme() : 'http';
145        $port = $url->getPort() ? ':' . $url->getPort() : '';
146        return $method . ' ' . $scheme . '://' . $url->getHost() . $port .
147                $url->getPath() . $url->getEncodedRequest() . ' HTTP/1.0';
148    }
149   
150    /**
151     *    Creates the host part of the request.
152     *    @param SimpleUrl $url   URL as object.
153     *    @return string          Host line content.
154     *    @access protected
155     */
156    function _getHostLine() {
157        $host = 'Host: ' . $this->_proxy->getHost();
158        $port = $this->_proxy->getPort() ? $this->_proxy->getPort() : 8080;
159        return "$host:$port";
160    }
161   
162    /**
163     *    Opens a socket to the route.
164     *    @param string $method       HTTP request method, usually GET.
165     *    @param integer $timeout     Connection timeout.
166     *    @return SimpleSocket        New socket.
167     *    @access public
168     */
169    function &createConnection($method, $timeout) {
170        $socket = &$this->_createSocket(
171                $this->_proxy->getScheme() ? $this->_proxy->getScheme() : 'http',
172                $this->_proxy->getHost(),
173                $this->_proxy->getPort() ? $this->_proxy->getPort() : 8080,
174                $timeout);
175        if ($socket->isError()) {
176            return $socket;
177        }
178        $socket->write($this->_getRequestLine($method) . "\r\n");
179        $socket->write($this->_getHostLine() . "\r\n");
180        if ($this->_username && $this->_password) {
181            $socket->write('Proxy-Authorization: Basic ' .
182                    base64_encode($this->_username . ':' . $this->_password) .
183                    "\r\n");
184        }
185        $socket->write("Connection: close\r\n");
186        return $socket;
187    }
188}
189
190/**
191 *    HTTP request for a web page. Factory for
192 *    HttpResponse object.
193 *    @package SimpleTest
194 *    @subpackage WebTester
195 */
196class SimpleHttpRequest {
197    var $_route;
198    var $_encoding;
199    var $_headers;
200    var $_cookies;
201   
202    /**
203     *    Builds the socket request from the different pieces.
204     *    These include proxy information, URL, cookies, headers,
205     *    request method and choice of encoding.
206     *    @param SimpleRoute $route              Request route.
207     *    @param SimpleFormEncoding $encoding    Content to send with
208     *                                           request.
209     *    @access public
210     */
211    function SimpleHttpRequest(&$route, $encoding) {
212        $this->_route = &$route;
213        $this->_encoding = $encoding;
214        $this->_headers = array();
215        $this->_cookies = array();
216    }
217   
218    /**
219     *    Dispatches the content to the route's socket.
220     *    @param integer $timeout      Connection timeout.
221     *    @return SimpleHttpResponse   A response which may only have
222     *                                 an error, but hopefully has a
223     *                                 complete web page.
224     *    @access public
225     */
226    function &fetch($timeout) {
227        $socket = &$this->_route->createConnection($this->_encoding->getMethod(), $timeout);
228        if (! $socket->isError()) {
229            $this->_dispatchRequest($socket, $this->_encoding);
230        }
231        $response = &$this->_createResponse($socket);
232        return $response;
233    }
234   
235    /**
236     *    Sends the headers.
237     *    @param SimpleSocket $socket           Open socket.
238     *    @param string $method                 HTTP request method,
239     *                                          usually GET.
240     *    @param SimpleFormEncoding $encoding   Content to send with request.
241     *    @access private
242     */
243    function _dispatchRequest(&$socket, $encoding) {
244        foreach ($this->_headers as $header_line) {
245            $socket->write($header_line . "\r\n");
246        }
247        if (count($this->_cookies) > 0) {
248            $socket->write("Cookie: " . implode(";", $this->_cookies) . "\r\n");
249        }
250        $encoding->writeHeadersTo($socket);
251        $socket->write("\r\n");
252        $encoding->writeTo($socket);
253    }
254   
255    /**
256     *    Adds a header line to the request.
257     *    @param string $header_line    Text of full header line.
258     *    @access public
259     */
260    function addHeaderLine($header_line) {
261        $this->_headers[] = $header_line;
262    }
263   
264    /**
265     *    Reads all the relevant cookies from the
266     *    cookie jar.
267     *    @param SimpleCookieJar $jar     Jar to read
268     *    @param SimpleUrl $url           Url to use for scope.
269     *    @access public
270     */
271    function readCookiesFromJar($jar, $url) {
272        $this->_cookies = $jar->selectAsPairs($url);
273    }
274   
275    /**
276     *    Wraps the socket in a response parser.
277     *    @param SimpleSocket $socket   Responding socket.
278     *    @return SimpleHttpResponse    Parsed response object.
279     *    @access protected
280     */
281    function &_createResponse(&$socket) {
282        $response = &new SimpleHttpResponse(
283                $socket,
284                $this->_route->getUrl(),
285                $this->_encoding);
286        return $response;
287    }
288}
289
290/**
291 *    Collection of header lines in the response.
292 *    @package SimpleTest
293 *    @subpackage WebTester
294 */
295class SimpleHttpHeaders {
296    var $_raw_headers;
297    var $_response_code;
298    var $_http_version;
299    var $_mime_type;
300    var $_location;
301    var $_cookies;
302    var $_authentication;
303    var $_realm;
304   
305    /**
306     *    Parses the incoming header block.
307     *    @param string $headers     Header block.
308     *    @access public
309     */
310    function SimpleHttpHeaders($headers) {
311        $this->_raw_headers = $headers;
312        $this->_response_code = false;
313        $this->_http_version = false;
314        $this->_mime_type = '';
315        $this->_location = false;
316        $this->_cookies = array();
317        $this->_authentication = false;
318        $this->_realm = false;
319        foreach (split("\r\n", $headers) as $header_line) {
320            $this->_parseHeaderLine($header_line);
321        }
322    }
323   
324    /**
325     *    Accessor for parsed HTTP protocol version.
326     *    @return integer           HTTP error code.
327     *    @access public
328     */
329    function getHttpVersion() {
330        return $this->_http_version;
331    }
332   
333    /**
334     *    Accessor for raw header block.
335     *    @return string        All headers as raw string.
336     *    @access public
337     */
338    function getRaw() {
339        return $this->_raw_headers;
340    }
341   
342    /**
343     *    Accessor for parsed HTTP error code.
344     *    @return integer           HTTP error code.
345     *    @access public
346     */
347    function getResponseCode() {
348        return (integer)$this->_response_code;
349    }
350   
351    /**
352     *    Returns the redirected URL or false if
353     *    no redirection.
354     *    @return string      URL or false for none.
355     *    @access public
356     */
357    function getLocation() {
358        return $this->_location;
359    }
360   
361    /**
362     *    Test to see if the response is a valid redirect.
363     *    @return boolean       True if valid redirect.
364     *    @access public
365     */
366    function isRedirect() {
367        return in_array($this->_response_code, array(301, 302, 303, 307)) &&
368                (boolean)$this->getLocation();
369    }
370   
371    /**
372     *    Test to see if the response is an authentication
373     *    challenge.
374     *    @return boolean       True if challenge.
375     *    @access public
376     */
377    function isChallenge() {
378        return ($this->_response_code == 401) &&
379                (boolean)$this->_authentication &&
380                (boolean)$this->_realm;
381    }
382   
383    /**
384     *    Accessor for MIME type header information.
385     *    @return string           MIME type.
386     *    @access public
387     */
388    function getMimeType() {
389        return $this->_mime_type;
390    }
391   
392    /**
393     *    Accessor for authentication type.
394     *    @return string        Type.
395     *    @access public
396     */
397    function getAuthentication() {
398        return $this->_authentication;
399    }
400   
401    /**
402     *    Accessor for security realm.
403     *    @return string        Realm.
404     *    @access public
405     */
406    function getRealm() {
407        return $this->_realm;
408    }
409   
410    /**
411     *    Writes new cookies to the cookie jar.
412     *    @param SimpleCookieJar $jar   Jar to write to.
413     *    @param SimpleUrl $url         Host and path to write under.
414     *    @access public
415     */
416    function writeCookiesToJar(&$jar, $url) {
417        foreach ($this->_cookies as $cookie) {
418            $jar->setCookie(
419                    $cookie->getName(),
420                    $cookie->getValue(),
421                    $url->getHost(),
422                    $cookie->getPath(),
423                    $cookie->getExpiry());
424        }
425    }
426
427    /**
428     *    Called on each header line to accumulate the held
429     *    data within the class.
430     *    @param string $header_line        One line of header.
431     *    @access protected
432     */
433    function _parseHeaderLine($header_line) {
434        if (preg_match('/HTTP\/(\d+\.\d+)\s+(\d+)/i', $header_line, $matches)) {
435            $this->_http_version = $matches[1];
436            $this->_response_code = $matches[2];
437        }
438        if (preg_match('/Content-type:\s*(.*)/i', $header_line, $matches)) {
439            $this->_mime_type = trim($matches[1]);
440        }
441        if (preg_match('/Location:\s*(.*)/i', $header_line, $matches)) {
442            $this->_location = trim($matches[1]);
443        }
444        if (preg_match('/Set-cookie:(.*)/i', $header_line, $matches)) {
445            $this->_cookies[] = $this->_parseCookie($matches[1]);
446        }
447        if (preg_match('/WWW-Authenticate:\s+(\S+)\s+realm=\"(.*?)\"/i', $header_line, $matches)) {
448            $this->_authentication = $matches[1];
449            $this->_realm = trim($matches[2]);
450        }
451    }
452   
453    /**
454     *    Parse the Set-cookie content.
455     *    @param string $cookie_line    Text after "Set-cookie:"
456     *    @return SimpleCookie          New cookie object.
457     *    @access private
458     */
459    function _parseCookie($cookie_line) {
460        $parts = split(";", $cookie_line);
461        $cookie = array();
462        preg_match('/\s*(.*?)\s*=(.*)/', array_shift($parts), $cookie);
463        foreach ($parts as $part) {
464            if (preg_match('/\s*(.*?)\s*=(.*)/', $part, $matches)) {
465                $cookie[$matches[1]] = trim($matches[2]);
466            }
467        }
468        return new SimpleCookie(
469                $cookie[1],
470                trim($cookie[2]),
471                isset($cookie["path"]) ? $cookie["path"] : "",
472                isset($cookie["expires"]) ? $cookie["expires"] : false);
473    }
474}
475
476/**
477 *    Basic HTTP response.
478 *    @package SimpleTest
479 *    @subpackage WebTester
480 */
481class SimpleHttpResponse extends SimpleStickyError {
482    var $_url;
483    var $_encoding;
484    var $_sent;
485    var $_content;
486    var $_headers;
487   
488    /**
489     *    Constructor. Reads and parses the incoming
490     *    content and headers.
491     *    @param SimpleSocket $socket   Network connection to fetch
492     *                                  response text from.
493     *    @param SimpleUrl $url         Resource name.
494     *    @param mixed $encoding        Record of content sent.
495     *    @access public
496     */
497    function SimpleHttpResponse(&$socket, $url, $encoding) {
498        $this->SimpleStickyError();
499        $this->_url = $url;
500        $this->_encoding = $encoding;
501        $this->_sent = $socket->getSent();
502        $this->_content = false;
503        $raw = $this->_readAll($socket);
504        if ($socket->isError()) {
505            $this->_setError('Error reading socket [' . $socket->getError() . ']');
506            return;
507        }
508        $this->_parse($raw);
509    }
510   
511    /**
512     *    Splits up the headers and the rest of the content.
513     *    @param string $raw    Content to parse.
514     *    @access private
515     */
516    function _parse($raw) {
517        if (! $raw) {
518            $this->_setError('Nothing fetched');
519            $this->_headers = &new SimpleHttpHeaders('');
520        } elseif (! strstr($raw, "\r\n\r\n")) {
521            $this->_setError('Could not split headers from content');
522            $this->_headers = &new SimpleHttpHeaders($raw);
523        } else {
524            list($headers, $this->_content) = split("\r\n\r\n", $raw, 2);
525            $this->_headers = &new SimpleHttpHeaders($headers);
526        }
527    }
528   
529    /**
530     *    Original request method.
531     *    @return string        GET, POST or HEAD.
532     *    @access public
533     */
534    function getMethod() {
535        return $this->_encoding->getMethod();
536    }
537   
538    /**
539     *    Resource name.
540     *    @return SimpleUrl        Current url.
541     *    @access public
542     */
543    function getUrl() {
544        return $this->_url;
545    }
546   
547    /**
548     *    Original request data.
549     *    @return mixed              Sent content.
550     *    @access public
551     */
552    function getRequestData() {
553        return $this->_encoding;
554    }
555   
556    /**
557     *    Raw request that was sent down the wire.
558     *    @return string        Bytes actually sent.
559     *    @access public
560     */
561    function getSent() {
562        return $this->_sent;
563    }
564   
565    /**
566     *    Accessor for the content after the last
567     *    header line.
568     *    @return string           All content.
569     *    @access public
570     */
571    function getContent() {
572        return $this->_content;
573    }
574   
575    /**
576     *    Accessor for header block. The response is the
577     *    combination of this and the content.
578     *    @return SimpleHeaders        Wrapped header block.
579     *    @access public
580     */
581    function getHeaders() {
582        return $this->_headers;
583    }
584   
585    /**
586     *    Accessor for any new cookies.
587     *    @return array       List of new cookies.
588     *    @access public
589     */
590    function getNewCookies() {
591        return $this->_headers->getNewCookies();
592    }
593   
594    /**
595     *    Reads the whole of the socket output into a
596     *    single string.
597     *    @param SimpleSocket $socket  Unread socket.
598     *    @return string               Raw output if successful
599     *                                 else false.
600     *    @access private
601     */
602    function _readAll(&$socket) {
603        $all = '';
604        while (! $this->_isLastPacket($next = $socket->read())) {
605            $all .= $next;
606        }
607        return $all;
608    }
609   
610    /**
611     *    Test to see if the packet from the socket is the
612     *    last one.
613     *    @param string $packet    Chunk to interpret.
614     *    @return boolean          True if empty or EOF.
615     *    @access private
616     */
617    function _isLastPacket($packet) {
618        if (is_string($packet)) {
619            return $packet === '';
620        }
621        return ! $packet;
622    }
623}
624?>
Note: See TracBrowser for help on using the repository browser.