Whoops - parameters need to match category check and display of course.
[moodle-linuxchix.git] / lib / simpletestlib / http.php
blob120d74c276cb3f18c1070ee85d2e68255fe1b1f7
1 <?php
2 /**
3 * base include file for SimpleTest
4 * @package SimpleTest
5 * @subpackage WebTester
6 * @version $Id$
7 */
9 /**#@+
10 * include other SimpleTest class files
12 require_once(dirname(__FILE__) . '/socket.php');
13 require_once(dirname(__FILE__) . '/cookies.php');
14 require_once(dirname(__FILE__) . '/url.php');
15 /**#@-*/
17 /**
18 * Creates HTTP headers for the end point of
19 * a HTTP request.
20 * @package SimpleTest
21 * @subpackage WebTester
23 class SimpleRoute {
24 var $_url;
26 /**
27 * Sets the target URL.
28 * @param SimpleUrl $url URL as object.
29 * @access public
31 function SimpleRoute($url) {
32 $this->_url = $url;
35 /**
36 * Resource name.
37 * @return SimpleUrl Current url.
38 * @access protected
40 function getUrl() {
41 return $this->_url;
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
50 function _getRequestLine($method) {
51 return $method . ' ' . $this->_url->getPath() .
52 $this->_url->getEncodedRequest() . ' HTTP/1.0';
55 /**
56 * Creates the host part of the request.
57 * @return string Host line content.
58 * @access protected
60 function _getHostLine() {
61 $line = 'Host: ' . $this->_url->getHost();
62 if ($this->_url->getPort()) {
63 $line .= ':' . $this->_url->getPort();
65 return $line;
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
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");
87 return $socket;
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
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);
105 return $socket;
110 * Creates HTTP headers for the end point of
111 * a HTTP request via a proxy server.
112 * @package SimpleTest
113 * @subpackage WebTester
115 class SimpleProxyRoute extends SimpleRoute {
116 var $_proxy;
117 var $_username;
118 var $_password;
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
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;
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
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';
151 * Creates the host part of the request.
152 * @param SimpleUrl $url URL as object.
153 * @return string Host line content.
154 * @access protected
156 function _getHostLine() {
157 $host = 'Host: ' . $this->_proxy->getHost();
158 $port = $this->_proxy->getPort() ? $this->_proxy->getPort() : 8080;
159 return "$host:$port";
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
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;
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");
185 $socket->write("Connection: close\r\n");
186 return $socket;
191 * HTTP request for a web page. Factory for
192 * HttpResponse object.
193 * @package SimpleTest
194 * @subpackage WebTester
196 class SimpleHttpRequest {
197 var $_route;
198 var $_encoding;
199 var $_headers;
200 var $_cookies;
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
211 function SimpleHttpRequest(&$route, $encoding) {
212 $this->_route = &$route;
213 $this->_encoding = $encoding;
214 $this->_headers = array();
215 $this->_cookies = array();
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
226 function &fetch($timeout) {
227 $socket = &$this->_route->createConnection($this->_encoding->getMethod(), $timeout);
228 if (! $socket->isError()) {
229 $this->_dispatchRequest($socket, $this->_encoding);
231 $response = &$this->_createResponse($socket);
232 return $response;
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
243 function _dispatchRequest(&$socket, $encoding) {
244 foreach ($this->_headers as $header_line) {
245 $socket->write($header_line . "\r\n");
247 if (count($this->_cookies) > 0) {
248 $socket->write("Cookie: " . implode(";", $this->_cookies) . "\r\n");
250 $encoding->writeHeadersTo($socket);
251 $socket->write("\r\n");
252 $encoding->writeTo($socket);
256 * Adds a header line to the request.
257 * @param string $header_line Text of full header line.
258 * @access public
260 function addHeaderLine($header_line) {
261 $this->_headers[] = $header_line;
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
271 function readCookiesFromJar($jar, $url) {
272 $this->_cookies = $jar->selectAsPairs($url);
276 * Wraps the socket in a response parser.
277 * @param SimpleSocket $socket Responding socket.
278 * @return SimpleHttpResponse Parsed response object.
279 * @access protected
281 function &_createResponse(&$socket) {
282 $response = &new SimpleHttpResponse(
283 $socket,
284 $this->_route->getUrl(),
285 $this->_encoding);
286 return $response;
291 * Collection of header lines in the response.
292 * @package SimpleTest
293 * @subpackage WebTester
295 class 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;
306 * Parses the incoming header block.
307 * @param string $headers Header block.
308 * @access public
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);
325 * Accessor for parsed HTTP protocol version.
326 * @return integer HTTP error code.
327 * @access public
329 function getHttpVersion() {
330 return $this->_http_version;
334 * Accessor for raw header block.
335 * @return string All headers as raw string.
336 * @access public
338 function getRaw() {
339 return $this->_raw_headers;
343 * Accessor for parsed HTTP error code.
344 * @return integer HTTP error code.
345 * @access public
347 function getResponseCode() {
348 return (integer)$this->_response_code;
352 * Returns the redirected URL or false if
353 * no redirection.
354 * @return string URL or false for none.
355 * @access public
357 function getLocation() {
358 return $this->_location;
362 * Test to see if the response is a valid redirect.
363 * @return boolean True if valid redirect.
364 * @access public
366 function isRedirect() {
367 return in_array($this->_response_code, array(301, 302, 303, 307)) &&
368 (boolean)$this->getLocation();
372 * Test to see if the response is an authentication
373 * challenge.
374 * @return boolean True if challenge.
375 * @access public
377 function isChallenge() {
378 return ($this->_response_code == 401) &&
379 (boolean)$this->_authentication &&
380 (boolean)$this->_realm;
384 * Accessor for MIME type header information.
385 * @return string MIME type.
386 * @access public
388 function getMimeType() {
389 return $this->_mime_type;
393 * Accessor for authentication type.
394 * @return string Type.
395 * @access public
397 function getAuthentication() {
398 return $this->_authentication;
402 * Accessor for security realm.
403 * @return string Realm.
404 * @access public
406 function getRealm() {
407 return $this->_realm;
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
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());
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
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];
438 if (preg_match('/Content-type:\s*(.*)/i', $header_line, $matches)) {
439 $this->_mime_type = trim($matches[1]);
441 if (preg_match('/Location:\s*(.*)/i', $header_line, $matches)) {
442 $this->_location = trim($matches[1]);
444 if (preg_match('/Set-cookie:(.*)/i', $header_line, $matches)) {
445 $this->_cookies[] = $this->_parseCookie($matches[1]);
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]);
454 * Parse the Set-cookie content.
455 * @param string $cookie_line Text after "Set-cookie:"
456 * @return SimpleCookie New cookie object.
457 * @access private
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]);
468 return new SimpleCookie(
469 $cookie[1],
470 trim($cookie[2]),
471 isset($cookie["path"]) ? $cookie["path"] : "",
472 isset($cookie["expires"]) ? $cookie["expires"] : false);
477 * Basic HTTP response.
478 * @package SimpleTest
479 * @subpackage WebTester
481 class SimpleHttpResponse extends SimpleStickyError {
482 var $_url;
483 var $_encoding;
484 var $_sent;
485 var $_content;
486 var $_headers;
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
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;
508 $this->_parse($raw);
512 * Splits up the headers and the rest of the content.
513 * @param string $raw Content to parse.
514 * @access private
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);
530 * Original request method.
531 * @return string GET, POST or HEAD.
532 * @access public
534 function getMethod() {
535 return $this->_encoding->getMethod();
539 * Resource name.
540 * @return SimpleUrl Current url.
541 * @access public
543 function getUrl() {
544 return $this->_url;
548 * Original request data.
549 * @return mixed Sent content.
550 * @access public
552 function getRequestData() {
553 return $this->_encoding;
557 * Raw request that was sent down the wire.
558 * @return string Bytes actually sent.
559 * @access public
561 function getSent() {
562 return $this->_sent;
566 * Accessor for the content after the last
567 * header line.
568 * @return string All content.
569 * @access public
571 function getContent() {
572 return $this->_content;
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
581 function getHeaders() {
582 return $this->_headers;
586 * Accessor for any new cookies.
587 * @return array List of new cookies.
588 * @access public
590 function getNewCookies() {
591 return $this->_headers->getNewCookies();
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
602 function _readAll(&$socket) {
603 $all = '';
604 while (! $this->_isLastPacket($next = $socket->read())) {
605 $all .= $next;
607 return $all;
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
617 function _isLastPacket($packet) {
618 if (is_string($packet)) {
619 return $packet === '';
621 return ! $packet;