http.php 19 KB

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