http.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628
  1. <?php
  2. /**
  3. * base include file for SimpleTest
  4. * @package SimpleTest
  5. * @subpackage WebTester
  6. * @version $Id: http.php 2011 2011-04-29 08:22:48Z pp11 $
  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. private $url;
  23. /**
  24. * Sets the target URL.
  25. * @param SimpleUrl $url URL as object.
  26. * @access public
  27. */
  28. function __construct($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. protected 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. protected 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. protected function createSocket($scheme, $host, $port, $timeout) {
  92. if (in_array($scheme, array('file'))) {
  93. return new SimpleFileSocket($this->url);
  94. } elseif (in_array($scheme, array('https'))) {
  95. return new SimpleSecureSocket($host, $port, $timeout);
  96. } else {
  97. return new SimpleSocket($host, $port, $timeout);
  98. }
  99. }
  100. }
  101. /**
  102. * Creates HTTP headers for the end point of
  103. * a HTTP request via a proxy server.
  104. * @package SimpleTest
  105. * @subpackage WebTester
  106. */
  107. class SimpleProxyRoute extends SimpleRoute {
  108. private $proxy;
  109. private $username;
  110. private $password;
  111. /**
  112. * Stashes the proxy address.
  113. * @param SimpleUrl $url URL as object.
  114. * @param string $proxy Proxy URL.
  115. * @param string $username Username for autentication.
  116. * @param string $password Password for autentication.
  117. * @access public
  118. */
  119. function __construct($url, $proxy, $username = false, $password = false) {
  120. parent::__construct($url);
  121. $this->proxy = $proxy;
  122. $this->username = $username;
  123. $this->password = $password;
  124. }
  125. /**
  126. * Creates the first line which is the actual request.
  127. * @param string $method HTTP request method, usually GET.
  128. * @param SimpleUrl $url URL as object.
  129. * @return string Request line content.
  130. * @access protected
  131. */
  132. function getRequestLine($method) {
  133. $url = $this->getUrl();
  134. $scheme = $url->getScheme() ? $url->getScheme() : 'http';
  135. $port = $url->getPort() ? ':' . $url->getPort() : '';
  136. return $method . ' ' . $scheme . '://' . $url->getHost() . $port .
  137. $url->getPath() . $url->getEncodedRequest() . ' HTTP/1.0';
  138. }
  139. /**
  140. * Creates the host part of the request.
  141. * @param SimpleUrl $url URL as object.
  142. * @return string Host line content.
  143. * @access protected
  144. */
  145. function getHostLine() {
  146. $host = 'Host: ' . $this->proxy->getHost();
  147. $port = $this->proxy->getPort() ? $this->proxy->getPort() : 8080;
  148. return "$host:$port";
  149. }
  150. /**
  151. * Opens a socket to the route.
  152. * @param string $method HTTP request method, usually GET.
  153. * @param integer $timeout Connection timeout.
  154. * @return SimpleSocket New socket.
  155. * @access public
  156. */
  157. function createConnection($method, $timeout) {
  158. $socket = $this->createSocket(
  159. $this->proxy->getScheme() ? $this->proxy->getScheme() : 'http',
  160. $this->proxy->getHost(),
  161. $this->proxy->getPort() ? $this->proxy->getPort() : 8080,
  162. $timeout);
  163. if ($socket->isError()) {
  164. return $socket;
  165. }
  166. $socket->write($this->getRequestLine($method) . "\r\n");
  167. $socket->write($this->getHostLine() . "\r\n");
  168. if ($this->username && $this->password) {
  169. $socket->write('Proxy-Authorization: Basic ' .
  170. base64_encode($this->username . ':' . $this->password) .
  171. "\r\n");
  172. }
  173. $socket->write("Connection: close\r\n");
  174. return $socket;
  175. }
  176. }
  177. /**
  178. * HTTP request for a web page. Factory for
  179. * HttpResponse object.
  180. * @package SimpleTest
  181. * @subpackage WebTester
  182. */
  183. class SimpleHttpRequest {
  184. private $route;
  185. private $encoding;
  186. private $headers;
  187. private $cookies;
  188. /**
  189. * Builds the socket request from the different pieces.
  190. * These include proxy information, URL, cookies, headers,
  191. * request method and choice of encoding.
  192. * @param SimpleRoute $route Request route.
  193. * @param SimpleFormEncoding $encoding Content to send with
  194. * request.
  195. * @access public
  196. */
  197. function __construct($route, $encoding) {
  198. $this->route = $route;
  199. $this->encoding = $encoding;
  200. $this->headers = array();
  201. $this->cookies = array();
  202. }
  203. /**
  204. * Dispatches the content to the route's socket.
  205. * @param integer $timeout Connection timeout.
  206. * @return SimpleHttpResponse A response which may only have
  207. * an error, but hopefully has a
  208. * complete web page.
  209. * @access public
  210. */
  211. function fetch($timeout) {
  212. $socket = $this->route->createConnection($this->encoding->getMethod(), $timeout);
  213. if (! $socket->isError()) {
  214. $this->dispatchRequest($socket, $this->encoding);
  215. }
  216. return $this->createResponse($socket);
  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. protected 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. protected function createResponse($socket) {
  262. $response = new SimpleHttpResponse(
  263. $socket,
  264. $this->route->getUrl(),
  265. $this->encoding);
  266. $socket->close();
  267. return $response;
  268. }
  269. }
  270. /**
  271. * Collection of header lines in the response.
  272. * @package SimpleTest
  273. * @subpackage WebTester
  274. */
  275. class SimpleHttpHeaders {
  276. private $raw_headers;
  277. private $response_code;
  278. private $http_version;
  279. private $mime_type;
  280. private $location;
  281. private $cookies;
  282. private $authentication;
  283. private $realm;
  284. /**
  285. * Parses the incoming header block.
  286. * @param string $headers Header block.
  287. * @access public
  288. */
  289. function __construct($headers) {
  290. $this->raw_headers = $headers;
  291. $this->response_code = false;
  292. $this->http_version = false;
  293. $this->mime_type = '';
  294. $this->location = false;
  295. $this->cookies = array();
  296. $this->authentication = false;
  297. $this->realm = false;
  298. foreach (explode("\r\n", $headers) as $header_line) {
  299. $this->parseHeaderLine($header_line);
  300. }
  301. }
  302. /**
  303. * Accessor for parsed HTTP protocol version.
  304. * @return integer HTTP error code.
  305. * @access public
  306. */
  307. function getHttpVersion() {
  308. return $this->http_version;
  309. }
  310. /**
  311. * Accessor for raw header block.
  312. * @return string All headers as raw string.
  313. * @access public
  314. */
  315. function getRaw() {
  316. return $this->raw_headers;
  317. }
  318. /**
  319. * Accessor for parsed HTTP error code.
  320. * @return integer HTTP error code.
  321. * @access public
  322. */
  323. function getResponseCode() {
  324. return (integer)$this->response_code;
  325. }
  326. /**
  327. * Returns the redirected URL or false if
  328. * no redirection.
  329. * @return string URL or false for none.
  330. * @access public
  331. */
  332. function getLocation() {
  333. return $this->location;
  334. }
  335. /**
  336. * Test to see if the response is a valid redirect.
  337. * @return boolean True if valid redirect.
  338. * @access public
  339. */
  340. function isRedirect() {
  341. return in_array($this->response_code, array(301, 302, 303, 307)) &&
  342. (boolean)$this->getLocation();
  343. }
  344. /**
  345. * Test to see if the response is an authentication
  346. * challenge.
  347. * @return boolean True if challenge.
  348. * @access public
  349. */
  350. function isChallenge() {
  351. return ($this->response_code == 401) &&
  352. (boolean)$this->authentication &&
  353. (boolean)$this->realm;
  354. }
  355. /**
  356. * Accessor for MIME type header information.
  357. * @return string MIME type.
  358. * @access public
  359. */
  360. function getMimeType() {
  361. return $this->mime_type;
  362. }
  363. /**
  364. * Accessor for authentication type.
  365. * @return string Type.
  366. * @access public
  367. */
  368. function getAuthentication() {
  369. return $this->authentication;
  370. }
  371. /**
  372. * Accessor for security realm.
  373. * @return string Realm.
  374. * @access public
  375. */
  376. function getRealm() {
  377. return $this->realm;
  378. }
  379. /**
  380. * Writes new cookies to the cookie jar.
  381. * @param SimpleCookieJar $jar Jar to write to.
  382. * @param SimpleUrl $url Host and path to write under.
  383. * @access public
  384. */
  385. function writeCookiesToJar($jar, $url) {
  386. foreach ($this->cookies as $cookie) {
  387. $jar->setCookie(
  388. $cookie->getName(),
  389. $cookie->getValue(),
  390. $url->getHost(),
  391. $cookie->getPath(),
  392. $cookie->getExpiry());
  393. }
  394. }
  395. /**
  396. * Called on each header line to accumulate the held
  397. * data within the class.
  398. * @param string $header_line One line of header.
  399. * @access protected
  400. */
  401. protected function parseHeaderLine($header_line) {
  402. if (preg_match('/HTTP\/(\d+\.\d+)\s+(\d+)/i', $header_line, $matches)) {
  403. $this->http_version = $matches[1];
  404. $this->response_code = $matches[2];
  405. }
  406. if (preg_match('/Content-type:\s*(.*)/i', $header_line, $matches)) {
  407. $this->mime_type = trim($matches[1]);
  408. }
  409. if (preg_match('/Location:\s*(.*)/i', $header_line, $matches)) {
  410. $this->location = trim($matches[1]);
  411. }
  412. if (preg_match('/Set-cookie:(.*)/i', $header_line, $matches)) {
  413. $this->cookies[] = $this->parseCookie($matches[1]);
  414. }
  415. if (preg_match('/WWW-Authenticate:\s+(\S+)\s+realm=\"(.*?)\"/i', $header_line, $matches)) {
  416. $this->authentication = $matches[1];
  417. $this->realm = trim($matches[2]);
  418. }
  419. }
  420. /**
  421. * Parse the Set-cookie content.
  422. * @param string $cookie_line Text after "Set-cookie:"
  423. * @return SimpleCookie New cookie object.
  424. * @access private
  425. */
  426. protected function parseCookie($cookie_line) {
  427. $parts = explode(";", $cookie_line);
  428. $cookie = array();
  429. preg_match('/\s*(.*?)\s*=(.*)/', array_shift($parts), $cookie);
  430. foreach ($parts as $part) {
  431. if (preg_match('/\s*(.*?)\s*=(.*)/', $part, $matches)) {
  432. $cookie[$matches[1]] = trim($matches[2]);
  433. }
  434. }
  435. return new SimpleCookie(
  436. $cookie[1],
  437. trim($cookie[2]),
  438. isset($cookie["path"]) ? $cookie["path"] : "",
  439. isset($cookie["expires"]) ? $cookie["expires"] : false);
  440. }
  441. }
  442. /**
  443. * Basic HTTP response.
  444. * @package SimpleTest
  445. * @subpackage WebTester
  446. */
  447. class SimpleHttpResponse extends SimpleStickyError {
  448. private $url;
  449. private $encoding;
  450. private $sent;
  451. private $content;
  452. private $headers;
  453. /**
  454. * Constructor. Reads and parses the incoming
  455. * content and headers.
  456. * @param SimpleSocket $socket Network connection to fetch
  457. * response text from.
  458. * @param SimpleUrl $url Resource name.
  459. * @param mixed $encoding Record of content sent.
  460. * @access public
  461. */
  462. function __construct($socket, $url, $encoding) {
  463. parent::__construct();
  464. $this->url = $url;
  465. $this->encoding = $encoding;
  466. $this->sent = $socket->getSent();
  467. $this->content = false;
  468. $raw = $this->readAll($socket);
  469. if ($socket->isError()) {
  470. $this->setError('Error reading socket [' . $socket->getError() . ']');
  471. return;
  472. }
  473. $this->parse($raw);
  474. }
  475. /**
  476. * Splits up the headers and the rest of the content.
  477. * @param string $raw Content to parse.
  478. * @access private
  479. */
  480. protected function parse($raw) {
  481. if (! $raw) {
  482. $this->setError('Nothing fetched');
  483. $this->headers = new SimpleHttpHeaders('');
  484. } elseif ('file' == $this->url->getScheme()) {
  485. $this->headers = new SimpleHttpHeaders('');
  486. $this->content = $raw;
  487. } elseif (! strstr($raw, "\r\n\r\n")) {
  488. $this->setError('Could not split headers from content');
  489. $this->headers = new SimpleHttpHeaders($raw);
  490. } else {
  491. list($headers, $this->content) = explode("\r\n\r\n", $raw, 2);
  492. $this->headers = new SimpleHttpHeaders($headers);
  493. }
  494. }
  495. /**
  496. * Original request method.
  497. * @return string GET, POST or HEAD.
  498. * @access public
  499. */
  500. function getMethod() {
  501. return $this->encoding->getMethod();
  502. }
  503. /**
  504. * Resource name.
  505. * @return SimpleUrl Current url.
  506. * @access public
  507. */
  508. function getUrl() {
  509. return $this->url;
  510. }
  511. /**
  512. * Original request data.
  513. * @return mixed Sent content.
  514. * @access public
  515. */
  516. function getRequestData() {
  517. return $this->encoding;
  518. }
  519. /**
  520. * Raw request that was sent down the wire.
  521. * @return string Bytes actually sent.
  522. * @access public
  523. */
  524. function getSent() {
  525. return $this->sent;
  526. }
  527. /**
  528. * Accessor for the content after the last
  529. * header line.
  530. * @return string All content.
  531. * @access public
  532. */
  533. function getContent() {
  534. return $this->content;
  535. }
  536. /**
  537. * Accessor for header block. The response is the
  538. * combination of this and the content.
  539. * @return SimpleHeaders Wrapped header block.
  540. * @access public
  541. */
  542. function getHeaders() {
  543. return $this->headers;
  544. }
  545. /**
  546. * Accessor for any new cookies.
  547. * @return array List of new cookies.
  548. * @access public
  549. */
  550. function getNewCookies() {
  551. return $this->headers->getNewCookies();
  552. }
  553. /**
  554. * Reads the whole of the socket output into a
  555. * single string.
  556. * @param SimpleSocket $socket Unread socket.
  557. * @return string Raw output if successful
  558. * else false.
  559. * @access private
  560. */
  561. protected function readAll($socket) {
  562. $all = '';
  563. while (! $this->isLastPacket($next = $socket->read())) {
  564. $all .= $next;
  565. }
  566. return $all;
  567. }
  568. /**
  569. * Test to see if the packet from the socket is the
  570. * last one.
  571. * @param string $packet Chunk to interpret.
  572. * @return boolean True if empty or EOF.
  573. * @access private
  574. */
  575. protected function isLastPacket($packet) {
  576. if (is_string($packet)) {
  577. return $packet === '';
  578. }
  579. return ! $packet;
  580. }
  581. }
  582. ?>