http_class.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. <?php
  2. /* @(#) $Header: /sources/phpprintipp/phpprintipp/php_classes/http_class.php,v 1.7 2010/08/22 15:45:17 harding Exp $ */
  3. /* vim: set expandtab tabstop=2 shiftwidth=2 foldmethod=marker: */
  4. /* ====================================================================
  5. * GNU Lesser General Public License
  6. * Version 2.1, February 1999
  7. *
  8. * Class http_class - Basic http client with "Basic" and Digest/MD5
  9. * authorization mechanism.
  10. * handle ipv4/v6 addresses, Unix sockets, http and https
  11. * have file streaming capability, to cope with php "memory_limit"
  12. *
  13. * Copyright (C) 2006,2007,2008 Thomas HARDING
  14. *
  15. * This library is free software; you can redistribute it and/or
  16. * modify it under the terms of the GNU Lesser General Public
  17. * License as published by the Free Software Foundation; either
  18. * version 2.1 of the License, or (at your option) any later version.
  19. *
  20. * This library is distributed in the hope that it will be useful,
  21. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  22. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  23. * Lesser General Public License for more details.
  24. *
  25. * You should have received a copy of the GNU Lesser General Public
  26. * License along with this library; if not, write to the Free Software
  27. * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  28. * $Id: http_class.php,v 1.7 2010/08/22 15:45:17 harding Exp $
  29. */
  30. /**
  31. * This class is intended to implement a subset of Hyper Text Transfer Protocol
  32. * (HTTP/1.1) on client side (currently: POST operation), with file streaming
  33. * capability.
  34. *
  35. * It can perform Basic and Digest authentication.
  36. *
  37. * References needed to debug / add functionnalities:
  38. * - RFC 2616
  39. * - RFC 2617
  40. *
  41. *
  42. * Class and Function List:
  43. * Function list:
  44. * - __construct()
  45. * - getErrorFormatted()
  46. * - getErrno()
  47. * - __construct()
  48. * - GetRequestArguments()
  49. * - Open()
  50. * - SendRequest()
  51. * - ReadReplyHeaders()
  52. * - ReadReplyBody()
  53. * - Close()
  54. * - _StreamRequest()
  55. * - _ReadReply()
  56. * - _ReadStream()
  57. * - _BuildDigest()
  58. * Classes list:
  59. * - httpException extends Exception
  60. * - http_class
  61. */
  62. /***********************
  63. *
  64. * httpException class
  65. *
  66. ************************/
  67. class httpException extends Exception
  68. {
  69. protected $errno;
  70. public function __construct($msg, $errno = null)
  71. {
  72. parent::__construct($msg);
  73. $this->errno = $errno;
  74. }
  75. public function getErrorFormatted()
  76. {
  77. return sprintf("[http_class]: %s -- "._(" file %s, line %s"),
  78. $this->getMessage(), $this->getFile(), $this->getLine());
  79. }
  80. public function getErrno()
  81. {
  82. return $this->errno;
  83. }
  84. }
  85. function error2string($value)
  86. {
  87. $level_names = array(
  88. E_ERROR => 'E_ERROR',
  89. E_WARNING => 'E_WARNING',
  90. E_PARSE => 'E_PARSE',
  91. E_NOTICE => 'E_NOTICE',
  92. E_CORE_ERROR => 'E_CORE_ERROR',
  93. E_CORE_WARNING => 'E_CORE_WARNING',
  94. E_COMPILE_ERROR => 'E_COMPILE_ERROR',
  95. E_COMPILE_WARNING => 'E_COMPILE_WARNING',
  96. E_USER_ERROR => 'E_USER_ERROR',
  97. E_USER_WARNING => 'E_USER_WARNING',
  98. E_USER_NOTICE => 'E_USER_NOTICE'
  99. );
  100. if (defined('E_STRICT')) {
  101. $level_names[E_STRICT]='E_STRICT';
  102. }
  103. $levels=array();
  104. if (($value&E_ALL)==E_ALL) {
  105. $levels[]='E_ALL';
  106. $value&=~E_ALL;
  107. }
  108. foreach ($level_names as $level=>$name) {
  109. if (($value&$level)==$level) {
  110. $levels[]=$name;
  111. }
  112. }
  113. return implode(' | ', $levels);
  114. }
  115. /***********************
  116. *
  117. * class http_class
  118. *
  119. ************************/
  120. class http_class
  121. {
  122. // variables declaration
  123. public $debug;
  124. public $html_debug;
  125. public $timeout = 30; // time waiting for connection, seconds
  126. public $data_timeout = 30; // time waiting for data, milliseconds
  127. public $data_chunk_timeout = 1; // time waiting between data chunks, millisecond
  128. public $force_multipart_form_post;
  129. public $username;
  130. public $password;
  131. public $request_headers = array ();
  132. public $request_body = "Not a useful information";
  133. public $status;
  134. public $window_size = 1024; // chunk size of data
  135. public $with_exceptions = 0; // compatibility mode for old scripts
  136. public $port;
  137. public $host;
  138. private $default_port = 631;
  139. private $headers;
  140. private $reply_headers = array ();
  141. private $reply_body = array ();
  142. private $connection;
  143. private $arguments;
  144. private $bodystream = array ();
  145. private $last_limit;
  146. private $connected;
  147. private $nc = 1;
  148. private $user_agent = "PRINTIPP/0.81+CVS";
  149. private $readed_bytes = 0;
  150. public function __construct()
  151. {
  152. true;
  153. }
  154. /*********************
  155. *
  156. * Public functions
  157. *
  158. **********************/
  159. public function GetRequestArguments($url, &$arguments)
  160. {
  161. $this->arguments = array ();
  162. $this->arguments["URL"] = $arguments["URL"] = $url;
  163. $this->arguments["RequestMethod"] = $arguments["RequestMethod"] = "POST";
  164. $this->headers["Content-Length"] = 0;
  165. $this->headers["Content-Type"] = "application/octet-stream";
  166. $this->headers["Host"] = $this->host;
  167. $this->headers["User-Agent"] = $this->user_agent;
  168. //$this->headers["Expect"] = "100-continue";
  169. }
  170. public function Open($arguments)
  171. {
  172. $this->connected = false;
  173. $url = $arguments["URL"];
  174. $port = $this->default_port;
  175. // $url = split (':', $url, 2);
  176. $url = preg_split('#:#', $url, 2);
  177. $transport_type = $url[0];
  178. $unix = false;
  179. switch ($transport_type) {
  180. case 'http':
  181. $transport_type = 'tcp://';
  182. break;
  183. case 'https':
  184. $transport_type = 'tls://';
  185. break;
  186. case 'unix':
  187. $transport_type = 'unix://';
  188. $port = 0;
  189. $unix = true;
  190. break;
  191. default:
  192. $transport_type = 'tcp://';
  193. break;
  194. }
  195. $url = $url[1];
  196. if (!$unix) {
  197. // $url = split ("/", preg_replace ("#^/{1,}#", '', $url), 2);
  198. $url = preg_split("#/#", preg_replace("#^/{1,}#", '', $url), 2);
  199. $url = $url[0];
  200. $port = $this->port;
  201. $error = sprintf(_("Cannot resolve url: %s"), $url);
  202. $ip = gethostbyname($url);
  203. $ip = @gethostbyaddr($ip);
  204. if (!$ip) {
  205. return $this->_HttpError($error, E_USER_WARNING);
  206. }
  207. if (strstr($url, ":")) { // we got an ipv6 address
  208. if (!strstr($url, "[")) { // it is not escaped
  209. $url = sprintf("[%s]", $url);
  210. }
  211. }
  212. }
  213. $this->connection = @fsockopen($transport_type.$url, $port, $errno, $errstr, $this->timeout);
  214. $error =
  215. sprintf(_('Unable to connect to "%s%s port %s": %s'), $transport_type,
  216. $url, $port, $errstr);
  217. if (!$this->connection) {
  218. return $this->_HttpError($error, E_USER_WARNING);
  219. }
  220. $this->connected = true;
  221. return array (true, "success");
  222. }
  223. public function SendRequest($arguments)
  224. {
  225. $error =
  226. sprintf(_('Streaming request failed to %s'), $arguments['RequestURI']);
  227. $result = self::_StreamRequest($arguments);
  228. if (!$result[0]) {
  229. return $this->_HttpError($error." ".$result[1], E_USER_WARNING);
  230. }
  231. self::_ReadReply();
  232. if (!preg_match('#http/1.1 401 unauthorized#', $this->status)) {
  233. return array (true, "success");
  234. }
  235. $headers = array_keys($this->reply_headers);
  236. $error = _("need authentication but no mechanism provided");
  237. if (!in_array("www-authenticate", $headers)) {
  238. return $this->_HttpError($error, E_USER_WARNING);
  239. }
  240. // $authtype = split (' ', $this->reply_headers["www-authenticate"]);
  241. $authtype = preg_split('# #', $this->reply_headers["www-authenticate"]);
  242. $authtype = strtolower($authtype[0]);
  243. switch ($authtype) {
  244. case 'basic':
  245. $pass = base64_encode($this->user.":".$this->password);
  246. $arguments["Headers"]["Authorization"] = "Basic ".$pass;
  247. break;
  248. case 'digest':
  249. $arguments["Headers"]["Authorization"] = self::_BuildDigest();
  250. break;
  251. default:
  252. $error =
  253. sprintf(_("need '%s' authentication mechanism, but have not"),
  254. $authtype[0]);
  255. return $this->_HttpError($error, E_USER_WARNING);
  256. break;
  257. }
  258. self::Close();
  259. self::Open($arguments);
  260. $error = sprintf(_('Streaming request failed to %s after a try to authenticate'), $arguments['RequestURI']);
  261. $result = self::_StreamRequest($arguments);
  262. if (!$result[0]) {
  263. return $this->_HttpError($error.": ".$result[1], E_USER_WARNING);
  264. }
  265. self::_ReadReply();
  266. return array (true, "success");
  267. }
  268. public function ReadReplyHeaders(&$headers)
  269. {
  270. $headers = $this->reply_headers;
  271. }
  272. public function ReadReplyBody(&$body, $chunk_size)
  273. {
  274. $body = substr($this->reply_body, $this->last_limit, $chunk_size);
  275. $this->last_limit += $chunk_size;
  276. }
  277. public function Close()
  278. {
  279. if (!$this->connected) {
  280. return;
  281. }
  282. fclose($this->connection);
  283. }
  284. /*********************
  285. *
  286. * Private functions
  287. *
  288. *********************/
  289. private function _HttpError($msg, $level, $errno = null)
  290. {
  291. $trace = '';
  292. $backtrace = debug_backtrace();
  293. foreach ($backtrace as $trace) {
  294. $trace .= sprintf("in [file: '%s'][function: '%s'][line: %s];\n", $trace['file'], $trace['function'], $trace['line']);
  295. }
  296. $msg = sprintf( '%s\n%s: [errno: %s]: %s',
  297. $trace, error2string($level), $errno, $msg);
  298. if ($this->with_exceptions) {
  299. throw new httpException($msg, $errno);
  300. } else {
  301. trigger_error($msg, $level);
  302. return array (false, $msg);
  303. }
  304. }
  305. private function _streamString($string)
  306. {
  307. $success = fwrite($this->connection, $string);
  308. if (!$success) {
  309. return false;
  310. }
  311. return true;
  312. }
  313. private function _StreamRequest($arguments)
  314. {
  315. $this->status = false;
  316. $this->reply_headers = array ();
  317. $this->reply_body = "";
  318. if (!$this->connected) {
  319. return $this->_HttpError(_("not connected"), E_USER_WARNING);
  320. }
  321. $this->arguments = $arguments;
  322. $content_length = 0;
  323. foreach ($this->arguments["BodyStream"] as $argument) {
  324. // list ($type, $value) = each ($argument);
  325. $type = key($argument);
  326. $value = current($argument);
  327. reset($argument);
  328. if ($type == "Data") {
  329. $length = strlen($value);
  330. } elseif ($type == "File") {
  331. if (is_readable($value)) {
  332. $length = filesize($value);
  333. } else {
  334. $length = 0;
  335. return $this->_HttpError(sprintf(_("%s: file is not readable"), $value), E_USER_WARNING);
  336. }
  337. } else {
  338. $length = 0;
  339. return $this->_HttpError(sprintf(_("%s: not a valid argument for content"), $type), E_USER_WARNING);
  340. }
  341. $content_length += $length;
  342. }
  343. $this->request_body = sprintf(_("%s Bytes"), $content_length);
  344. $this->headers["Content-Length"] = $content_length;
  345. $this->arguments["Headers"] = array_merge($this->headers, $this->arguments["Headers"]);
  346. if ($this->arguments["RequestMethod"] != "POST") {
  347. return $this->_HttpError(sprintf(_("%s: method not implemented"), $arguments["RequestMethod"]), E_USER_WARNING);
  348. }
  349. $string = sprintf("POST %s HTTP/1.1\r\n", $this->arguments["RequestURI"]);
  350. $this->request_headers[$string] = '';
  351. if (!$this->_streamString($string)) {
  352. return $this->_HttpError(_("Error while puts POST operation"), E_USER_WARNING);
  353. }
  354. foreach ($this->arguments["Headers"] as $header => $value) {
  355. $string = sprintf("%s: %s\r\n", $header, $value);
  356. $this->request_headers[$header] = $value;
  357. if (!$this->_streamString($string)) {
  358. return $this->_HttpError(_("Error while puts HTTP headers"), E_USER_WARNING);
  359. }
  360. }
  361. $string = "\r\n";
  362. if (!$this->_streamString($string)) {
  363. return $this->_HttpError(_("Error while ends HTTP headers"), E_USER_WARNING);
  364. }
  365. foreach ($this->arguments["BodyStream"] as $argument) {
  366. // list ($type, $value) = each ($argument);
  367. $type = key($argument);
  368. $value = current($argument);
  369. reset($argument);
  370. if ($type == "Data") {
  371. $streamed_length = 0;
  372. while ($streamed_length < strlen($value)) {
  373. $string = substr($value, $streamed_length, $this->window_size);
  374. if (!$this->_streamString($string)) {
  375. return $this->_HttpError(_("error while sending body data"), E_USER_WARNING);
  376. }
  377. $streamed_length += $this->window_size;
  378. }
  379. } elseif ($type == "File") {
  380. if (is_readable($value)) {
  381. $file = fopen($value, 'rb');
  382. while (!feof($file)) {
  383. if (gettype($block = @fread($file, $this->window_size)) != "string") {
  384. return $this->_HttpError(_("cannot read file to upload"), E_USER_WARNING);
  385. }
  386. if (!$this->_streamString($block)) {
  387. return $this->_HttpError(_("error while sending body data"), E_USER_WARNING);
  388. }
  389. }
  390. }
  391. }
  392. }
  393. return array (true, "success");
  394. }
  395. private function _ReadReply()
  396. {
  397. if (!$this->connected) {
  398. return array (false, _("not connected"));
  399. }
  400. $this->reply_headers = array ();
  401. $this->reply_body = "";
  402. $headers = array ();
  403. $body = "";
  404. while (!feof($this->connection)) {
  405. $line = fgets($this->connection, 1024);
  406. if (strlen(trim($line)) == 0) {
  407. break;
  408. } // \r\n => end of headers
  409. if (preg_match('#^[[:space:]]#', $line)) {
  410. $headers[-1] .= sprintf(' %s', trim($line));
  411. continue;
  412. }
  413. $headers[] = trim($line);
  414. }
  415. $this->status = isset($headers[0]) ? strtolower($headers[0]) : false;
  416. foreach ($headers as $header) {
  417. $header = preg_split("#: #", $header);
  418. $header[0] = strtolower($header[0]);
  419. if ($header[0] !== "www-authenticate") {
  420. $header[1] = isset($header[1]) ? strtolower($header[1]) : "";
  421. }
  422. if (!isset($this->reply_headers[$header[0]])) {
  423. $this->reply_headers[$header[0]] = $header[1];
  424. }
  425. }
  426. self::_ReadStream();
  427. return true;
  428. }
  429. private function _ReadStream()
  430. {
  431. if (! array_key_exists("content-length", $this->reply_headers)) {
  432. stream_set_blocking($this->connection, 0);
  433. $this->reply_body = stream_get_contents($this->connection);
  434. return true;
  435. }
  436. stream_set_blocking($this->connection, 1);
  437. $content_length = $this->reply_headers["content-length"];
  438. $this->reply_body = stream_get_contents($this->connection, $content_length);
  439. return true;
  440. }
  441. private function _BuildDigest()
  442. {
  443. $auth = $this->reply_headers["www-authenticate"];
  444. // list ($head, $auth) = split (" ", $auth, 2);
  445. list ($head, $auth) = preg_split("# #", $auth, 2);
  446. // $auth = split (", ", $auth);
  447. $auth = preg_split("#, #", $auth);
  448. foreach ($auth as $sheme) {
  449. // list ($sheme, $value) = split ('=', $sheme);
  450. list ($sheme, $value) = preg_split('#=#', $sheme);
  451. $fields[$sheme] = trim(trim($value), '"');
  452. }
  453. $nc = sprintf('%x', $this->nc);
  454. $prepend = "";
  455. while ((strlen($nc) + strlen($prepend)) < 8)
  456. $prependi .= "0";
  457. $nc = $prepend.$nc;
  458. $cnonce = "printipp";
  459. $username = $this->user;
  460. $password = $this->password;
  461. $A1 = $username.":".$fields["realm"].":".$password;
  462. if (array_key_exists("algorithm", $fields)) {
  463. $algorithm = strtolower($fields["algorithm"]);
  464. switch ($algorithm) {
  465. case "md5":
  466. break;
  467. case "md5-sess":
  468. $A1 =
  469. $username.":".$fields["realm"].":".$password.":".
  470. $fields['nonce'].":".$cnonce;
  471. break;
  472. default:
  473. return $this->_HttpError(
  474. sprintf(_("digest Authorization: algorithm '%s' not implemented"),
  475. $algorithm),
  476. E_USER_WARNING);
  477. return false;
  478. break;
  479. }
  480. }
  481. $A2 = "POST:".$this->arguments["RequestURI"];
  482. if (array_key_exists("qop", $fields)) {
  483. $qop = strtolower($fields["qop"]);
  484. // $qop = split (" ", $qop);
  485. $qop = preg_split("# #", $qop);
  486. if (in_array("auth", $qop)) {
  487. $qop = "auth";
  488. } else {
  489. self::_HttpError(
  490. sprintf(_("digest Authorization: algorithm '%s' not implemented"),
  491. $qop),
  492. E_USER_WARNING);
  493. return false;
  494. }
  495. }
  496. $response = md5(md5($A1).":".$fields["nonce"].":".md5($A2));
  497. if (isset($qop) && ($qop == "auth")) {
  498. $response =
  499. md5(md5($A1).":".$fields["nonce"].":".$nc.":".$cnonce.":".$qop.
  500. ":".$A2);
  501. }
  502. $auth_scheme =
  503. sprintf('Digest username="%s", realm="%s", nonce="%s", uri="%s", response="%s"',
  504. $username, $fields["realm"], $fields['nonce'],
  505. $this->arguments["RequestURI"], $response);
  506. if (isset($algorithm)) {
  507. $auth_scheme .= sprintf(', algorithm="%s"', $algorithm);
  508. }
  509. if (isset($qop)) {
  510. $auth_scheme .= sprintf(', cnonce="%s"', $cnonce);
  511. }
  512. if (array_key_exists("opaque", $fields)) {
  513. $auth_scheme .= sprintf(', opaque="%s"', $fields['opaque']);
  514. }
  515. if (isset($qop)) {
  516. $auth_scheme .= sprintf(', qop="%s"', $qop);
  517. }
  518. $auth_scheme .= sprintf(', nc=%s', $nc);
  519. $this->nc++;
  520. return $auth_scheme;
  521. }
  522. }