PageRenderTime 72ms CodeModel.GetById 9ms RepoModel.GetById 0ms app.codeStats 0ms

/src/lib/net/http_client.php

http://textmotion.googlecode.com/
PHP | 467 lines | 287 code | 76 blank | 104 comment | 87 complexity | 0dfd4670e912ede474bc6beade10f88f MD5 | raw file
Possible License(s): MIT, CC0-1.0
  1. <?php
  2. /**
  3. * HTTP client
  4. * A simple HTTP client
  5. * ---
  6. * Written by Jose Carlos Nieto <xiam@menteslibres.org>
  7. * Copyright (c) 2007 Jose Carlos Nieto <xiam@menteslibres.org>
  8. *
  9. * Licensed under The MIT License
  10. * Redistributions of files must retain the above copyright notice.
  11. *
  12. * @author Jose Carlos Nieto <xiam@menteslibres.org>
  13. * @copyright Copyright (c) 2007-2008, Jose Carlos Nieto <xiam@menteslibres.org>
  14. * @link http://opensource.astrata.com.mx Astrata Open Source Projects
  15. * @version $Revision: $
  16. * @modifiedby $LastChangedBy: $
  17. * @lastmodified $Date: $
  18. * @license http://www.opensource.org/licenses/mit-license.php The MIT License
  19. *
  20. */
  21. require_once TM_LIB_DIR.'socket.php';
  22. class http_client extends socket {
  23. /**
  24. * Host to connect
  25. * @public string
  26. */
  27. public $http_host;
  28. /**
  29. * Port to connect
  30. * @public integer
  31. */
  32. public $http_port = 80;
  33. /**
  34. * User to log in
  35. * @public string
  36. */
  37. public $http_user;
  38. /**
  39. * Password to log in
  40. * @public string
  41. */
  42. public $http_pass;
  43. /**
  44. * User agent
  45. * @public string
  46. */
  47. public $user_agent = null;
  48. /**
  49. * Client headers
  50. * @public array
  51. */
  52. public $request_headers;
  53. /**
  54. * Response headers
  55. * @public array
  56. */
  57. public $response_headers;
  58. /**
  59. * Response document
  60. * @public array
  61. */
  62. public $response_document;
  63. /**
  64. * Constructor.
  65. */
  66. public function __construct($host = null, $port = 80, $user = null, $pass = null) {
  67. parent::__construct();
  68. if ($host) {
  69. if (preg_match('/([^:]+):([^:]+)@(.+)$/', $host, $match)) {
  70. $host = $match[3].':80';
  71. $user = $match[1];
  72. $pass = $match[2];
  73. }
  74. if (preg_match('/([^:]+):(\d+)/', $host, $match)) {
  75. $host = $match[1];
  76. $port = $match[2];
  77. }
  78. $this->set_data($host, $port, $user, $pass);
  79. }
  80. }
  81. public function set_data($host, $port = 80, $user = null, $pass = null) {
  82. $this->http_host = $host;
  83. $this->http_port = $port;
  84. $this->http_user = $user;
  85. $this->http_pass = $pass;
  86. $this->set_header('host', $this->http_host.':'.$this->http_port);
  87. if ($this->http_user) {
  88. $this->set_header('authorization', 'Basic '.base64_encode($this->http_user.':'.$this->http_pass));
  89. }
  90. }
  91. /**
  92. * Opens a connection to the web server
  93. * @returns boolean connection status
  94. */
  95. public function open() {
  96. $this->connect($this->http_host, $this->http_port);
  97. return $this->status();
  98. }
  99. /**
  100. * Evaluates the served file
  101. * @parse string &$buff reference to the contents of the downloaded file
  102. */
  103. public function parse_response(&$buff) {
  104. preg_match("/^(.*?)\r\n\r\n(.*?)$/s", $buff, $match);
  105. if (isset($match[2])) {
  106. $this->response_document = $match[2];
  107. $headers = explode("\n", $match[1]);
  108. $status = false;
  109. foreach ($headers as $h) {
  110. if ($status == false) {
  111. $status_code = preg_match('/^[^\s]+\s(\d+)\s.*$/', $h, $match);
  112. $status = $h;
  113. // expecting HTTP/1.1 200 OK
  114. if (!strpos($status, '200')) {
  115. return false;
  116. }
  117. } else {
  118. preg_match("/^([^:]*?):\s*(.*?)$/i", $h, $m);
  119. if (isset($m[2]))
  120. $this->response_headers[strtolower($m[1])] = trim($m[2]);
  121. }
  122. }
  123. // inflating gzip'ed pages
  124. if ((isset($this->response_headers['content-encoding']) && ($this->response_headers['content-encoding'] == "gzip")) || (isset($this->response_headers['vary']) && strtolower($this->response_headers['vary']) == 'accept-encoding')) {
  125. // Read http://www.php.net/manual/en/public function.gzinflate.php
  126. $this->uncompress($this->response_document);
  127. }
  128. }
  129. }
  130. /**
  131. * Sets a client header.
  132. * @param string $name Name of the HTTP header.
  133. * @param string $value Value of the header.
  134. */
  135. public function set_header($name, $value) {
  136. $this->request_headers[strtolower($name)] = $value;
  137. }
  138. /*
  139. * Sends client headers.
  140. *
  141. */
  142. public function send_headers() {
  143. debug('*** Sending headers...', 3);
  144. $this->set_header('user-agent', $this->user_agent ? $this->user_agent : 'Mozilla/5.0 Textmotion/'.TM_VERSION.'');
  145. $this->set_header('host', $this->http_host);
  146. $this->set_header('accept', '*/*');
  147. if (function_exists('gzinflate')) {
  148. $this->set_header('accept-encoding', 'gzip,deflate');
  149. }
  150. $this->set_header('keep-alive', '300');
  151. $this->set_header('connection', 'keep-alive');
  152. foreach ($this->request_headers as $name => $value) {
  153. $name = preg_replace_callback('/(^|-)(.)/', create_function('$a', 'return $a[1].strtoupper($a[2]);'), $name);
  154. $this->write("{$name}: $value\r\n");
  155. }
  156. $this->write("\r\n");
  157. }
  158. /**
  159. * Prepares variables for being sent.
  160. * @param array $vars Variables to send
  161. * @returns string Variables formatted to be sent
  162. */
  163. public function join_variables($vars) {
  164. $buff = array();
  165. foreach ($vars as $f => $v)
  166. $buff[] = "$f=".urlencode($v);
  167. return implode("&", $buff);
  168. }
  169. /**
  170. * Performs a HTTP Post
  171. * @param string $url URL where to post
  172. * @param array $vars Variables to send
  173. * @param boolean $only_headers Set to true if you want only the document headers to be returned.
  174. * @returns string Document contents or headers
  175. */
  176. public function post($url, $vars, $only_headers = false) {
  177. if (is_array($vars)) {
  178. $data = $this->join_variables($vars);
  179. } else {
  180. $data = $vars;
  181. }
  182. $url = preg_replace("/[\r\n].*/", '', $url);
  183. $this->write("POST $url HTTP/1.1\r\n");
  184. $this->set_header('content-length', strlen($data));
  185. $this->set_header('content-type', 'application/x-www-form-urlencoded');
  186. $this->send_headers();
  187. $this->write($data);
  188. if ($only_headers) {
  189. return $this->get_headers();
  190. }
  191. $this->read_response();
  192. return $this->response_document;
  193. }
  194. /**
  195. * Reads only HTTP headers
  196. * @returns string HTTP headers
  197. */
  198. private function get_headers() {
  199. $buff = '';
  200. stream_set_timeout($this->_socket, $this->read_timeout);
  201. do {
  202. $line = $this->read_line($this->_socket);
  203. $buff .= $line;
  204. if ($line == "\r\n")
  205. return $buff;
  206. } while (!feof($this->_socket));
  207. }
  208. // taken from http://us.php.net/manual/en/public function.gzinflate.php#77336
  209. private function uncompress(&$data) {
  210. if (substr($data, 0, 3) == "\x1f\x8b\x08") {
  211. $i = 10;
  212. $flag = ord(substr($data, 3, 1));
  213. if ($flag > 0) {
  214. if ($flag & 4) {
  215. list($xlen) = unpack('v', substr($data, $i, 2));
  216. }
  217. if ($flag & 8) {
  218. $i = strpos($data, "\0", $i) + 1;
  219. }
  220. if ($flag & 16) {
  221. $i = strpos($data, "\0", $i) + 1;
  222. }
  223. if ($flag & 2) {
  224. $i = $i + 2;
  225. }
  226. }
  227. $data = gzinflate(substr($data, $i, -8));
  228. }
  229. }
  230. private function read_response() {
  231. if ($this->download()) {
  232. if ((isset($this->response_headers['content-encoding']) && ($this->response_headers['content-encoding'] == "gzip")) || (isset($this->response_headers['vary']) && strtolower($this->response_headers['vary']) == 'accept-encoding')) {
  233. $this->uncompress($this->response_document);
  234. }
  235. }
  236. }
  237. private function download() {
  238. unset($headers);
  239. unset($document);
  240. $data = '';
  241. $buff = '';
  242. $size = 0;
  243. $tmp = '';
  244. $response = 0;
  245. while ($this->status()) {
  246. $buff = $tmp.$this->read();
  247. $len = strlen($buff);
  248. $tmp = '';
  249. for ($i = 0; $i < $len; $i++) {
  250. $chr = $buff{$i};
  251. $tmp .= $chr;
  252. if (isset($document)) {
  253. if (isset($headers['transfer-encoding'])) {
  254. switch($headers['transfer-encoding'] == 'chunked') {
  255. case 'chunked':
  256. if ($size) {
  257. $tmp = '';
  258. for (; $size > 0 && $i < $len; $i++, $size--) {
  259. $tmp .= $buff{$i};
  260. }
  261. $document .= $tmp;
  262. $tmp = '';
  263. } else {
  264. if ($chr == "\n" && strlen(trim($tmp))) {
  265. $size = hexdec(trim($tmp));
  266. if ($size == 0) {
  267. $this->response_document = $document;
  268. return true;
  269. }
  270. $tmp = '';
  271. }
  272. }
  273. break;
  274. default:
  275. debug('Unsupported transfer encoding '.$headers['transfer-encoding'], 3);
  276. return false;
  277. break;
  278. }
  279. } else if (isset($headers['content-length'])) {
  280. if (!$size) {
  281. $size = $headers['content-length'];
  282. }
  283. $tmp = '';
  284. for (; $size > 0 && $i < $len; $i++, $size--) {
  285. $tmp .= $buff{$i};
  286. }
  287. $document .= $tmp;
  288. $tmp = '';
  289. if ($size == 0) {
  290. $this->response_document = $document;
  291. return true;
  292. }
  293. } else {
  294. $document = substr($buff, $i);
  295. while ($this->status()) {
  296. $document .= $this->read();
  297. }
  298. $this->response_document = $document;
  299. return true;
  300. }
  301. } else {
  302. if ($chr == "\n") {
  303. if (!isset($headers)) {
  304. // first response.
  305. debug('>>> '.$tmp, 3);
  306. preg_match('/[^\s]+\s(\d+)\s.*/', $tmp, $code);
  307. $this->response_code = $code[1];
  308. if (preg_match('/^(200|302|301)$/', $code[1])) {
  309. $headers = array();
  310. } else {
  311. debug('Unknown response', 3);
  312. return false;
  313. }
  314. } else {
  315. if (trim($tmp)) {
  316. // storing headers
  317. preg_match('/^([^:]+):\s?(.+)$/', $tmp, $match);
  318. $headers[strtolower($match[1])] = trim($match[2]);
  319. } else {
  320. $document = '';
  321. $this->response_headers = $headers;
  322. if (preg_match('/^(302|301)$/', $this->response_code) && !empty($headers['location'])) {
  323. return false;
  324. } else if (empty($headers['content-length'])) {
  325. if (isset($headers['transfer-encoding']) && $headers['transfer-encoding'] != 'chunked') {
  326. debug('No content-length header.', 3);
  327. return false;
  328. }
  329. }
  330. }
  331. }
  332. $tmp = '';
  333. }
  334. }
  335. }
  336. }
  337. }
  338. /**
  339. * Standard HTTP get
  340. * @param string $url URL to get
  341. * @param boolean $only_headers Set true if you want only the document headers
  342. * @returns string Document content or headers
  343. */
  344. public public function get($url, $only_headers = false, $count = 0) {
  345. if (!$this->status()) {
  346. $this->open();
  347. }
  348. debug('HTTP GET '.$url, 3);
  349. if ($count < 3) {
  350. $url = preg_replace("/[\r\n].*/", '', $url);
  351. $this->write("GET $url HTTP/1.1\r\n");
  352. $this->send_headers();
  353. if ($only_headers) {
  354. return $this->get_headers();
  355. }
  356. $this->read_response();
  357. if (preg_match('/^(301|302)$/', $this->response_code)) {
  358. $redir = $this->response_headers['location'];
  359. if ($redir{0} != '/' && !preg_match('/^http:\/\/([^\/]+)/i', $redir, $match)) {
  360. $redir = dirname($url).'/'.$redir;
  361. }
  362. if (preg_match('/[a-z]+:\/\/([^\/]+)/i', $redir, $match)) {
  363. if (isset($match[1])) {
  364. $host = explode(':', $match[1]);
  365. $this->http_host = $host[0];
  366. if (isset($host[1])) {
  367. $this->http_port = $host[1];
  368. }
  369. }
  370. }
  371. debug(sprintf('Redirected to %s...', $redir), 3);
  372. return $this->get($redir, $only_headers, $count + 1);
  373. }
  374. //debug($this->response_headers);
  375. return $this->response_document;
  376. }
  377. }
  378. /**
  379. * Runs a public functional test
  380. */
  381. public function run_test() {
  382. header('content-type: text/plain');
  383. $http = new http_client('localhost', '80');
  384. if ($http->open()) {
  385. echo $http->get('/test/foo.txt');
  386. $http->close();
  387. }
  388. }
  389. }
  390. // http_client::run_test();
  391. ?>