hQuery.php
hQuery.php
1 <?php
2 namespace duzun;
3 // ------------------------------------------------------------------------
4 class_exists('duzun\\hQuery\\HTML_Parser', false) or require_once __DIR__ . DIRECTORY_SEPARATOR . 'hQuery' . DIRECTORY_SEPARATOR . 'HTML_Parser.php';
5 
6 // ------------------------------------------------------------------------
21 class hQuery extends hQuery\HTML_Parser {
22 
23  // ------------------------------------------------------------------------
24  // Response headers when using self::fromURL()
25  public $headers;
26 
27  public static $cache_path;
28  public static $cache_expires = 3600;
29 
30  // ------------------------------------------------------------------------
31  public static $_mockup_class; // Used internally for teting
32  // ------------------------------------------------------------------------
41  public static function fromHTML($html, $url=NULL) {
42  $index_time = microtime(true);
43  if ( isset(self::$_mockup_class) ) {
44  $doc = new self::$_mockup_class($html, false);
45  }
46  else {
47  $doc = new self($html, false);
48  }
49  if($url) {
50  $doc->location($url);
51  }
52  $doc->index();
53  $index_time = microtime(true) - $index_time;
54  $doc->index_time = $index_time * 1000;
55  return $doc;
56  }
57 
67  public static function fromFile($filename, $use_include_path=false, $context=NULL) {
68  $read_time = microtime(true);
69  $html = file_get_contents($filename, $use_include_path, $context);
70  $read_time = microtime(true) - $read_time;
71  if($html === false) return $html;
72  $doc = self::fromHTML($html, $filename);
73  $doc->source_type = 'file';
74  $doc->read_time = $read_time * 1000;
75  return $doc;
76  }
77 
88  public static function fromURL($url, $headers=NULL, $body=NULL, $options=NULL) {
89  $opt = array(
90  'timeout' => 7,
91  'redirects' => 7,
92  'close' => false,
93  'decode' => 'gzip',
94  'expires' => self::$cache_expires,
95  );
96  $hd = array('Accept-Charset' => 'UTF-8,*');
97 
98  if($options) $opt = $options + $opt;
99  if($headers) $hd = $headers + $hd;
100 
101  $expires = $opt['expires'];
102  unset($opt['expires']);
103 
104  if(0 < $expires and $dir = self::$cache_path) {
105  ksort($opt);
106  $t = realpath($dir) and $dir = $t or mkdir($dir, 0766, true);
107  $dir .= DIRECTORY_SEPARATOR;
108  $cch_id = hash('sha1', $url, true);
109  $t = hash('md5', self::jsonize($opt), true);
110  $cch_id = bin2hex(substr($cch_id, 0, -strlen($t)) . (substr($cch_id, -strlen($t)) ^ $t));
111  $cch_fn = $dir . $cch_id;
112  $ext = strtolower(strrchr($url, '.'));
113  if(strlen($ext) < 7 && preg_match('/^\\.[a-z0-9]+$/', $ext)) {
114  $cch_fn .= $ext;
115  }
116  $cch_fn .= '.gz';
117  $read_time = microtime(true);
118  $ret = self::get_cache($cch_fn, $expires, false);
119  $read_time = microtime(true) - $read_time;
120  if($ret) {
121  $source_type = 'cache';
122  $html = $ret[0];
123  $hdrs = $ret[1]['hdr'];
124  $code = $ret[1]['code'];
125  $url = $ret[1]['url'];
126  $cch_meta = $ret[1];
127  self::$last_http_result = (object)array(
128  'body' => $html,
129  'code' => $code,
130  'url' => $url,
131  'headers' => $hdrs,
132  'cached' => true,
133  );
134  }
135  }
136  else {
137  $ret = NULL;
138  }
139 
140  if(empty($ret)) {
141  $source_type = 'url';
142  $read_time = microtime(true);
143  // var_export(compact('url', 'opt', 'hd'));
144  $ret = self::http_wr($url, $hd, $body, $opt);
145  $read_time = microtime(true) - $read_time;
146  $html = $ret->body;
147  $code = $ret->code;
148  $hdrs = $ret->headers;
149 
150  // Catch the redirects
151  if($ret->url) $url = $ret->url;
152 
153  if(!empty($cch_fn)) {
154  $save = self::set_cache($cch_fn, $html, array('hdr' => $hdrs, 'code' => $code, 'url' => $url));
155  }
156  }
157  if($code != 200) {
158  return false;
159  }
160 
161  $doc = self::fromHTML($html, $url);
162  if($doc) {
163  $doc->headers = $hdrs;
164  $doc->source_type = $source_type;
165  isset($read_time) and $doc->read_time = $read_time * 1000;
166  if(!empty($cch_meta)) $doc->cch_meta = $cch_meta;
167  }
168 
169  return $doc;
170  }
171 
172  // ------------------------------------------------------------------------
182  public function find($sel, $_attr=NULL, $ctx=NULL) {
183  $attr = array();
184  $c = func_num_args();
185  for($i=1;$i<$c;$i++) {
186  $a = func_get_arg($i);
187  if(is_object($a)) {
188  if($a instanceof hQuery\Node) $ctx = $a;
189  else throw new \Exception('Wrong context in ' . __METHOD__);
190  }
191  elseif(is_array($a)) $attr = array_merge($attr, $a);
192  elseif(is_string($a)) $attr = array_merge($attr, self::html_parseAttrStr($a));
193  }
194  if(isset($ctx)) $ctx = $this->_get_ctx($ctx);
195 
196  $sel = self::html_selector2struc($sel);
197 
198  $ra = NULL;
199  // , //
200  foreach($sel as $a) {
201  $rb = NULL;
202  $cx = $ctx;
203  // //
204  foreach($a as $b) {
205  $rc = NULL;
206  if($rb) {
207  $cx = $this->_get_ctx($rb);
208  if(!$cx) ; // ??? error
209  }
210  // > //
211  foreach($b as $c) {
212  $at = $attr;
213  if(isset($c['i'])) $at['id'] = $c['i'];
214  // x of x > y > ...
215  if(!$rc) {
216  $rc = $this->_find($c['n'], $c['c'], $at, $cx);
217  }
218  // y of x > y > ...
219  else {
220  $ch = $this->_children($rc);
221  $rc = $this->_filter($ch, $c['n'], $c['c'], $at);
222  }
223  unset($ch);
224  if(!$rc) break;
225  if(isset($c['p'])) {
226  foreach($c['p'] as $p) {
227  if(is_int($p)) {
228  if($p < 0) $p += count($rc);
229  if(count($rc) >= 1 || $p) {
230  $rc = $p < 0 ? NULL : array_slice($rc, $p, 1, true);
231  }
232  }
233  elseif(is_array($p)) {
234  $ch = reset($p);
235  switch(key($p)) {
236  case '<': $rc = array_slice($rc, 0, $ch, true); break;
237  case '>': $rc = array_slice($rc, $ch, count($rc), true); break;
238  case '-': $rc = $this->_prev($rc, $ch); break;
239  case '+': $rc = $this->_next($rc, $ch); break;
240  case '|': do $rc = $this->_parent($rc); while($ch-- > 0); break;
241  case '*': do $rc = $this->_children($rc); while($ch-- > 0); break;
242  }
243  }
244  if(!$rc) break 2;
245  }
246  }
247  }
248  $rb = $rc;
249  if(!$rb) break;
250  }
251  if($rc) if(!$ra) $ra = $rc; else { foreach($rc as $rb => $rc) $ra[$rb] = $rc; }
252  }
253  if($ra) {
254  ksort($ra);
255  return new hQuery\Element($this, $ra);
256  }
257  return NULL;
258  }
259 
269  public function find_html($sel, $attr=NULL, $ctx=NULL) {
270  $r = $this->find($sel, $attr=NULL, $ctx=NULL);
271  $ret = self::$_ar_;
272  if($r) foreach($r as $k => $v) $ret[$k] = $v->html();
273  return $ret;
274  }
275 
285  public function find_text($sel, $attr=NULL, $ctx=NULL) {
286  $r = $this->find($sel, $attr=NULL, $ctx=NULL);
287  $ret = self::$_ar_;
288  if($r) foreach($r as $k => $v) $ret[$k] = $v->text();
289  return $ret;
290  }
291 
295  public function index() { return $this->_index_all(); }
296 
297  // - Helpers ------------------------------------------------
298 
299  // ---------------------------------------------------------------
308  public static function jsonize($data, &$type = NULL, $ops = 0) {
309  if(defined('JSON_UNESCAPED_UNICODE')) {
310  $ops |= JSON_UNESCAPED_UNICODE;
311  }
312  $str = $ops ? json_encode($data, $ops) : json_encode($data);
313  if( $str === false /*&& json_last_error() != JSON_ERROR_NONE*/ ) {
314  $str = serialize($data);
315  $type = 'ser';
316  }
317  else {
318  $type = 'json';
319  }
320  return $str;
321  }
322 
332  public static function unjsonize($str, &$type=NULL) {
333  if(!isset($type)) {
334  $type = self::serjstype($str);
335  }
336  static $_json_support;
337  if ( !isset($_json_support) ) {
338  $_json_support = 0;
339  // PHP 5 >= 5.3.0
340  if ( function_exists('json_last_error') ) {
341  ++$_json_support;
342  // PHP 5 >= 5.5.0
343  if ( function_exists('json_last_error_msg') ) {
344  ++$_json_support;
345  }
346  }
347  }
348  switch($type) {
349  case 'ser': {
350  $data = @unserialize($str);
351  if ( $data === false ) {
352  if ( strpos($str, "\n") !== false ) {
353  if ( $retry = strpos($str, "\r") === false ) {
354  $str = str_replace("\n", "\r\n", $str);
355  }
356  elseif ( $retry = strpos($str, "\r\n") !== false ) {
357  $str = str_replace("\r\n", "\n", $str);
358  }
359  $retry and $data = unserialize($str);
360  }
361  }
362  } break;
363 
364  case 'json': {
365  $data = json_decode($str, true);
366  // Check for errors only if $data is NULL
367  if ( is_null($data) ) {
368  // If can't decode JSON, try to remove trailing commans in arrays and objects:
369  if( $_json_support == 0 ? $str !== 'null' : json_last_error() != JSON_ERROR_NONE ) {
370  $t = preg_replace('/,\s*([\]\}])/m', '$1', $str) and
371  $data = json_decode($t, true);
372  }
373  if( is_null($data) ) {
374  // PHP 5 >= 5.3.0
375  if ( $_json_support ) {
376  if ( json_last_error() != JSON_ERROR_NONE ) {
377  // PHP 5 >= 5.5.0
378  if ( $_json_support > 1 ) {
379  error_log('json_decode: ' . json_last_error_msg());
380  }
381  elseif( $_json_support > 0 ) {
382  error_log("json_decode error with code #".json_last_error());
383  }
384  }
385  }
386  // PHP 5 < 5.3.0
387  else {
388  // Only 'null' should result in NULL
389  if ( $str !== 'null' ) {
390  error_log("json_decode error");
391  }
392  }
393  }
394  }
395  } break;
396 
397  default: { // at least try!
398  $data = json_decode($str, true);
399  if( is_null($data) && ($_json_support == 0 ? $str !== 'null' : json_last_error() != JSON_ERROR_NONE) ) {
400  $data = unserialize($str);
401  }
402  }
403  }
404  return $data;
405  }
406 
414  protected static function serjstype($str) {
415  $c = substr($str, 0, 1);
416  if($str === 'N;' || strpos('sibadO', $c) !== false && substr($str, 1, 1) === ':') {
417  $type = 'ser';
418  }
419  else {
420  $l = substr($str, -1);
421  if($c == '{' && $l == '}' || $c == '[' && $l == ']') {
422  $type = 'json';
423  }
424  else {
425  $type = false; // Unknown
426  }
427  }
428  return $type;
429  }
430 
435  public static function gz_supported() {
436  function_exists('zlib_decode') and $_gzdecode = 'zlib_decode' or
437  function_exists('gzdecode') and $_gzdecode = 'gzdecode' or
438  $_gzdecode = false;
439  return $_gzdecode;
440  }
441 
445  public static function gzdecode($str) {
446  static $_gzdecode;
447  if ( !isset($_gzdecode) ) {
448  $_gzdecode = self::gz_supported();
449  }
450 
451  return $_gzdecode ? $_gzdecode($str) : self::_gzdecode($str);
452  }
453 
458  protected static function _gzdecode($gzdata, $maxlen=NULL) {
459  #-- decode header
460  $len = strlen($gzdata);
461  if ($len < 20) {
462  return;
463  }
464  $head = substr($gzdata, 0, 10);
465  $head = unpack("n1id/C1cm/C1flg/V1mtime/C1xfl/C1os", $head);
466  list($ID, $CM, $FLG, $MTIME, $XFL, $OS) = array_values($head);
467  $FTEXT = 1<<0;
468  $FHCRC = 1<<1;
469  $FEXTRA = 1<<2;
470  $FNAME = 1<<3;
471  $FCOMMENT = 1<<4;
472  $head = unpack("V1crc/V1isize", substr($gzdata, $len-8, 8));
473  list($CRC32, $ISIZE) = array_values($head);
474 
475  #-- check gzip stream identifier
476  if ($ID != 0x1f8b) {
477  trigger_error("gzdecode: not in gzip format", E_USER_WARNING);
478  return;
479  }
480  #-- check for deflate algorithm
481  if ($CM != 8) {
482  trigger_error("gzdecode: cannot decode anything but deflated streams", E_USER_WARNING);
483  return;
484  }
485  #-- start of data, skip bonus fields
486  $s = 10;
487  if ($FLG & $FEXTRA) {
488  $s += $XFL;
489  }
490  if ($FLG & $FNAME) {
491  $s = strpos($gzdata, "\000", $s) + 1;
492  }
493  if ($FLG & $FCOMMENT) {
494  $s = strpos($gzdata, "\000", $s) + 1;
495  }
496  if ($FLG & $FHCRC) {
497  $s += 2; // cannot check
498  }
499 
500  #-- get data, uncompress
501  $gzdata = substr($gzdata, $s, $len-$s);
502  if ($maxlen) {
503  $gzdata = gzinflate($gzdata, $maxlen);
504  return($gzdata); // no checks(?!)
505  }
506  else {
507  $gzdata = gzinflate($gzdata);
508  }
509 
510  #-- check+fin
511  $chk = crc32($gzdata);
512  if ($CRC32 != $chk) {
513  trigger_error("gzdecode: checksum failed (real$chk != comp$CRC32)", E_USER_WARNING);
514  }
515  elseif ($ISIZE != strlen($gzdata)) {
516  trigger_error("gzdecode: stream size mismatch", E_USER_WARNING);
517  }
518  else {
519  return($gzdata);
520  }
521  }
522 
532  protected static function get_cache($fn, $expire=false, $meta_only=false) {
533  $meta = $cnt = NULL;
534  if( $fm = @filemtime($fn) and (!$expire || $fm + $expire > time()) ) {
535  $cnt = self::flock_get_contents($fn);
536  }
537  $t = strlen($cnt);
538  if(!empty($cnt)) {
539  if($gz = !strncmp($cnt, "\x1F\x8B", 2)) {
540  $cnt = self::gzdecode($cnt);
541  }
542  if($cnt[0] == '#') {
543  $n = (int)substr($cnt, 1, 0x10);
544  $l = strlen($n) + 2;
545  if($n) {
546  $meta = substr($cnt, $l, $n);
547  if($meta !== '') $meta = self::unjsonize($meta);
548  }
549  if($meta_only) $cnt = '';
550  else {
551  $l += $n;
552  if($cnt[$l] == "\n") {
553  $cnt = substr($cnt, ++$l);
554  if($cnt !== '') $cnt = self::unjsonize($cnt);
555  }
556  else {
557  $cnt = substr($cnt, $l);
558  }
559  }
560  }
561  else {
562  if($meta_only) $cnt = '';
563  }
564  }
565  return $cnt || $meta ? array($cnt, $meta) : false;
566  }
567 
578  protected static function set_cache($fn, $cnt, $meta=NULL, $gzip=true) {
579  if($cnt === false) return !file_exists($fn) || unlink($fn);
580  $n = 0;
581  if(isset($meta)) {
582  $meta = self::jsonize($meta);
583  $n += strlen($meta);
584  }
585  $meta = '#'.$n . "\n" . $meta;
586  if(!is_string($cnt) || $cnt[0] == "\n") { $cnt = "\n" . self::jsonize($cnt); ++$n; }
587  if($n) $cnt = $meta . $cnt;
588  unset($meta);
589  @mkdir(dirname($fn), 0777, true);
590  if($gzip) {
591  $gl = is_int($gzip) ? $gzip : 1024;
592  // Cache as gzip only if built-in gzdecode() defined (more CPU for less IO)
593  strlen($cnt) > $gl && self::gz_supported() and
594  $cnt = gzencode($cnt);
595  }
596  return self::flock_put_contents($fn, $cnt);
597  }
598 
611  static function do_flock($fp, $lock, $timeout_ms=384) {
612  $l = flock($fp, $lock);
613  if( !$l && ($lock & LOCK_UN) != LOCK_UN ) {
614  $st = microtime(true);
615  $m = min( 1e3, $timeout_ms*1e3);
616  $n = min(64e3, $timeout_ms*1e3);
617  if($m == $n) $m = ($n >> 1) + 1;
618  $timeout_ms = (float)$timeout_ms / 1000;
619  // If lock not obtained sleep for 0 - 64 milliseconds, to avoid collision and CPU load
620  do {
621  usleep($t = rand($m, $n));
622  $l = flock($fp, $lock);
623  } while ( !$l && (microtime(true)-$st) < $timeout_ms );
624  }
625  return $l;
626  }
627 
628  static function flock_put_contents($fn, $cnt, $block=false) {
629  // return file_put_contents($fn, $cnt, $block & FILE_APPEND);
630  $ret = false;
631  if( $f = fopen($fn, 'c+') ) {
632  $app = $block & FILE_APPEND and $block ^= $app;
633  if( $block ? self::do_flock($f, LOCK_EX) : flock($f, LOCK_EX | LOCK_NB) ) {
634  if(is_array($cnt) || is_object($cnt)) $cnt = self::jsonize($cnt);
635  if($app) fseek($f, 0, SEEK_END);
636  if(false !== ($ret = fwrite($f, $cnt))) {
637  fflush($f);
638  ftruncate($f, ftell($f));
639  }
640  flock($f, LOCK_UN);
641  }
642  fclose($f);
643  }
644  return $ret;
645  }
646 
647  static function flock_get_contents($fn, $block=false) {
648  // return file_get_contents($fn);
649  $ret = false;
650  if( $f = fopen($fn, 'r') ) {
651  if( flock($f, LOCK_SH | ($block ? 0 : LOCK_NB)) ) {
652  $s = 1 << 14 ;
653  do $ret .= $r = fread($f, $s); while($r !== false && !feof($f));
654  if($ret == NULL && $r === false) $ret = $r;
655  // filesize result is cached
656  flock($f, LOCK_UN);
657  }
658  fclose($f);
659  }
660  return $ret;
661  }
662 
663  // ------------------------------------------------------------------------
664  public static function parse_cookie($str) {
665  $ret = array();
666  if ( is_array($str) ) {
667  foreach($str as $k => $v) {
668  $ret[$k] = self::parse_cookie($v);
669  }
670  return $ret;
671  }
672 
673  $str = explode(';', $str);
674  $t = explode('=', array_shift($str), 2);
675  $ret['key'] = $t[0];
676  $ret['value'] = $t[1];
677  foreach ($str as $t) {
678  $t = explode('=', trim($t), 2);
679  if ( count($t) == 2 ) {
680  $ret[strtolower($t[0])] = $t[1];
681  }
682  else {
683  $ret[strtolower($t[0])] = true;
684  }
685  }
686 
687  if ( !empty($ret['expires']) && is_string($ret['expires']) ) {
688  $t = strtotime($ret['expires']);
689  if ( $t !== false and $t !== -1 ) {
690  $ret['expires'] = $t;
691  }
692  }
693 
694  return $ret;
695  }
696 
697  // ------------------------------------------------------------------------
720  public static function http_wr($host, $head = NULL, $body = NULL, $options = NULL) {
721  self::$last_http_result =
722  $ret = new \stdClass;
723  empty($options) and $options = array();
724 
725  // If $host is a URL
726  if($p = strpos($host, '://') and $p < 7) {
727  $ret->url = $host;
728  $p = parse_url($host);
729  if(!$p) {
730  throw new \Exception('Wrong host specified'); // error
731  }
732  $host = $p['host'];
733  $path = @$p['path'];
734  if(isset($p['query'])) {
735  $path .= '?' . $p['query'];
736  }
737  if(isset($p['port'])) {
738  $port = $p['port'];
739  }
740  unset($p['path'], $p['query']);
741  $options += $p;
742  }
743  // If $host is not an URL, but might contain path and port
744  else {
745  $p = explode('/', $host, 2); list($host, $path) = $p;
746  $p = explode(':', $host, 2); list($host, $port) = $p;
747  }
748 
749  if(strncmp($path, '/', 1)) {
750  $path = '/' . $path;
751  }
752  // isset($path) or $path = '/';
753 
754  if(!isset($port)) {
755  if(isset($options['port'])) {
756  $port = $options['port'];
757  }
758  else {
759  switch($options['scheme']) {
760  case 'tls' :
761  case 'ssl' :
762  case 'https': $port = 443; break;
763  case 'ftp' : $port = 21; break;
764  case 'sftp' : $port = 22; break;
765  case 'http' :
766  default : $port = 80;
767  }
768  }
769  }
770 
771  $ret->host =
772  $conhost = $host;
773  $_h = array(
774  'host' => isset($options['host']) ? $options['host'] : $host,
775  'accept' => 'text/html,application/xhtml+xml,application/xml;q =0.9,*/*;q=0.8',
776  );
777  if(!empty($options['scheme'])) {
778  switch($p['scheme']) {
779  case 'http':
780  case 'ftp':
781  break;
782  case 'https':
783  $conhost = 'tls://' . $host;
784  break;
785  default:
786  $conhost = $options['scheme'] . '://' . $host;
787  }
788  }
789 
790  static $boundary = "\r\n\r\n";
791  $blen = strlen($boundary);
792  if($body) {
793  if(is_array($body) || is_object($body)) {
794  $body = http_build_query($body);
795  $_h['content-type'] = 'application/x-www-form-urlencoded';
796  }
797  $body = (string)$body;
798  $_h['content-length'] = strlen($body);
799  $body .= $boundary;
800  empty($options['method']) and $options['method'] = 'POST';
801  }
802  else {
803  $body = NULL;
804  }
805 
806  !empty($options['method']) and $meth = strtoupper($options['method']) or $meth = 'GET';
807 
808  if($head) {
809  if(!is_array($head)) {
810  $head = explode("\r\n", $head);
811  }
812  foreach($head as $i => $v) {
813  if(is_int($i)) {
814  $v = explode(':', $v, 2);
815  if(count($v) != 2) continue; // Invalid header
816  list($i, $v) = $v;
817  }
818  $i = strtolower(strtr($i, ' _', '--'));
819  $_h[$i] = trim($v);
820  }
821  }
822 
823  if(@$options['decode'] == 'gzip') {
824  // if(self::gz_supported()) {
825  $_h['accept-encoding'] = 'gzip';
826  // }
827  // else {
828  // $options['decode'] = NULL;
829  // }
830  }
831 
832  if(!isset($options['close']) || @$options['close']) {
833  $_h['connection'] = 'close';
834  }
835  else {
836  $_h['connection'] = 'keep-alive';
837  }
838 
839  $prot = empty($options['protocol']) ? 'HTTP/1.1' : $options['protocol'];
840 
841  $head = array("$meth $path $prot");
842  foreach($_h as $i => $v) {
843  $i = explode('-', $i);
844  foreach($i as &$j) $j = ucfirst($j);
845  $i = implode('-', $i);
846  $head[] = $i . ': ' . $v;
847  }
848  $rqst = implode("\r\n", $head) . $boundary . $body;
849  $head = NULL; // free mem
850 
851  $timeout = isset($options['timeout']) ? $options['timeout'] : @ini_get("default_socket_timeout");
852 
853  $ret->options = $options;
854 
855  // ------------------- Connection and data transfer -------------------
856  $errno = 0;
857  $errstr =
858  $rsps = '';
859  $h = $_rh = NULL;
860  $fs = @fsockopen($conhost, $port, $errno, $errstr, $timeout);
861  if(!$fs) {
862  throw new \Exception('unable to create socket "'.$conhost.':'.$port.'" '.$errstr, $errno);
863  }
864  if(!fwrite($fs, $rqst)) {
865  throw new \Exception("unable to write");
866  }
867  else {
868  $l = $blen - 1;
869  // read headers
870  while($open = !feof($fs) && ($p = @fgets($fs, 1024))) {
871  if($p == "\r\n") break;
872  $rsps .= $p;
873  }
874 
875  if($rsps) {
876  $h = explode("\r\n", rtrim($rsps));
877  list($rprot, $rcode, $rmsg) = explode(' ', array_shift($h), 3);
878  foreach($h as $v) {
879  $v = explode(':', $v, 2);
880  $k = strtoupper(strtr($v[0], '- ', '__'));
881  $v = isset($v[1]) ? trim($v[1]) : NULL;
882 
883  // Gather headers
884  if ( isset($_rh[$k]) ) {
885  if ( isset($v) ) {
886  if ( is_array($_rh[$k]) ) {
887  $_rh[$k][] = $v;
888  }
889  else {
890  $_rh[$k] = array($_rh[$k], $v);
891  }
892  }
893  }
894  else {
895  $_rh[$k] = $v;
896  }
897  }
898  $rsps = NULL;
899  $_preserve_method = true;
900  switch($rcode) {
901  case 301:
902  case 302:
903  case 303:
904  $_preserve_method = false;
905  case 307:
906  case 308:
907  // repeat request using the same method and post data
908  if( @$options['redirects'] > 0 && $loc = @$_rh['LOCATION'] ) {
909  if ( !empty($options['host']) ) {
910  $host = $options['host'];
911  }
912  is_array($loc) and $loc = end($loc);
913  $loc = self::abs_url($loc, compact('host', 'port', 'path') + array('scheme' => empty($options['scheme'])?'':$options['scheme']));
914  unset($_h['host'], $options['host'], $options['port'], $options['scheme']);
915  if ( isset($options['redirect_method']) ) {
916  $redirect_method = $options['redirect_method'];
917  if ( is_string($redirect_method) ) {
918  $options['method'] = $redirect_method = strtoupper($redirect_method);
919  $_preserve_method = true;
920  if ( $redirect_method != 'POST' && $redirect_method != 'PUT' && $redirect_method != 'DELETE' ) {
921  $body = NULL;
922  }
923  }
924  else {
925  $_preserve_method = (bool)$redirect_method;
926  }
927  }
928  if ( !$_preserve_method ) {
929  $body = NULL;
930  unset($options['method']);
931  }
932  --$options['redirects'];
933  // ??? could save cookies for redirect
934  if ( !empty($_rh['SET_COOKIE']) && !empty($options['use_cookies']) ) {
935  $t = self::parse_cookie((array)$_rh['SET_COOKIE']);
936  if ( $t ) {
937  $now = time();
938  // @TODO: Filter out cookies by $c['domain'] and $c['path'] (compare to $loc)
939  foreach($t as $c) {
940  if ( empty($c['expires']) || $c['expires'] >= $now ) {
941  $_h['cookie'] = (empty($_h['cookie']) ? '' : $_h['cookie'] . '; ') .
942  $c['key'] . '=' . $c['value'];
943  }
944  }
945  }
946  }
947  return self::http_wr($loc, $_h, $body, $options);
948  }
949  break;
950  }
951 
952  // Detect body length
953  if(@!$open || $rcode < 200 || $rcode == 204 || $rcode == 304 || $meth == 'HEAD') {
954  $te = 1;
955  }
956  elseif(isset($_rh['TRANSFER_ENCODING']) && strtolower($_rh['TRANSFER_ENCODING']) === 'chunked') {
957  $te = 3;
958  }
959  elseif(isset($_rh['CONTENT_LENGTH'])) {
960  $bl = (int)$_rh['CONTENT_LENGTH'];
961  $te = 2;
962  }
963  else {
964  $te = 0; // useless, just to avoid Notice: Undefined variable: te...
965  }
966 
967  switch($te) {
968  case 1:
969  break;
970  case 2:
971  while($bl > 0 and $open &= !feof($fs) && ($p = @fread($fs, $bl))) {
972  $rsps .= $p;
973  $bl -= strlen($p);
974  }
975  break;
976  case 3:
977  while($open &= !feof($fs) && ($p = @fgets($fs, 1024))) {
978  $_re = explode(';', rtrim($p));
979  $cs = reset($_re);
980  $bl = hexdec($cs);
981  if(!$bl) break; // empty chunk
982  while($bl > 0 and $open &= !feof($fs) && ($p = @fread($fs, $bl))) {
983  $rsps .= $p;
984  $bl -= strlen($p);
985  }
986  @fgets($fs, 3); // \r\n
987  }
988  if($open &= !feof($fs) && ($p = @fgets($fs, 1024))) {
989  if($p = rtrim($p)) {
990  // ??? Trailer Header
991  $v = explode(':', $p, 2);
992  $k = strtoupper(strtr($v[0], '- ', '__'));
993  $v = isset($v[1]) ? trim($v[1]) : NULL;
994 
995  // Gather headers
996  if ( isset($_rh[$k]) ) {
997  if ( isset($v) ) {
998  if ( is_array($_rh[$k]) ) {
999  $_rh[$k][] = $v;
1000  }
1001  else {
1002  $_rh[$k] = array($_rh[$k], $v);
1003  }
1004  }
1005  }
1006  else {
1007  $_rh[$k] = $v;
1008  }
1009 
1010  @fgets($fs, 3); // \r\n
1011  }
1012  }
1013  break;
1014  default:
1015  while($open &= !feof($fs) && ($p = @fread($fs, 1024))) { // ???
1016  $rsps .= $p;
1017  }
1018  break;
1019  }
1020 
1021  if ( $rsps != '' &&
1022  isset($options['decode']) && $options['decode'] == 'gzip' &&
1023  isset($_rh['CONTENT_ENCODING']) && $_rh['CONTENT_ENCODING'] == 'gzip'
1024  ) {
1025  $r = self::gzdecode($rsps);
1026  if($r !== false) {
1027  unset($_rh['CONTENT_ENCODING']);
1028  $rsps = $r;
1029  }
1030  else {
1031  throw new \Exception("Can't gzdecode(response), try ['decode' => false] option");
1032  }
1033  }
1034  $ret->code = $rcode;
1035  $ret->msg = $rmsg;
1036  $ret->headers = isset($_rh) ? $_rh : NULL;
1037  $ret->body = $rsps;
1038  $ret->method = $meth;
1039  // $ret->host = $host;
1040  $ret->port = $port;
1041  $ret->path = $path;
1042  $ret->request = $rqst;
1043 
1044  return $ret;
1045 
1046  // Old return:
1047  // contents headers status-code status-message
1048  // return array( $rsps, @$_rh, $rcode, $rmsg, $host, $port, $path, $rqst );
1049  }
1050  }
1051  fclose($fs);
1052 
1053  return false; // no response
1054  }
1055 
1056  // ------------------------------------------------------------------------
1057 
1058 }
static fromFile($filename, $use_include_path=false, $context=NULL)
Definition: hQuery.php:67
static get_cache($fn, $expire=false, $meta_only=false)
Definition: hQuery.php:532
static http_wr($host, $head=NULL, $body=NULL, $options=NULL)
Definition: hQuery.php:720
find_text($sel, $attr=NULL, $ctx=NULL)
Definition: hQuery.php:285
static gzdecode($str)
Definition: hQuery.php:445
static set_cache($fn, $cnt, $meta=NULL, $gzip=true)
Definition: hQuery.php:578
static fromHTML($html, $url=NULL)
Definition: hQuery.php:41
find($sel, $_attr=NULL, $ctx=NULL)
Definition: hQuery.php:182
static unjsonize($str, &$type=NULL)
Definition: hQuery.php:332
static fromURL($url, $headers=NULL, $body=NULL, $options=NULL)
Definition: hQuery.php:88
static do_flock($fp, $lock, $timeout_ms=384)
Definition: hQuery.php:611
static _gzdecode($gzdata, $maxlen=NULL)
Definition: hQuery.php:458
static gz_supported()
Definition: hQuery.php:435
static jsonize($data, &$type=NULL, $ops=0)
Definition: hQuery.php:308
find_html($sel, $attr=NULL, $ctx=NULL)
Definition: hQuery.php:269
static serjstype($str)
Definition: hQuery.php:414