TYPO3  7.6
Request2.php
Go to the documentation of this file.
1 <?php
24 require_once 'Net/URL2.php';
25 
29 require_once 'HTTP/Request2/Exception.php';
30 
42 class HTTP_Request2 implements SplSubject
43 {
49  const METHOD_OPTIONS = 'OPTIONS';
50  const METHOD_GET = 'GET';
51  const METHOD_HEAD = 'HEAD';
52  const METHOD_POST = 'POST';
53  const METHOD_PUT = 'PUT';
54  const METHOD_DELETE = 'DELETE';
55  const METHOD_TRACE = 'TRACE';
56  const METHOD_CONNECT = 'CONNECT';
64  const AUTH_BASIC = 'basic';
65  const AUTH_DIGEST = 'digest';
72  const REGEXP_INVALID_TOKEN = '![\x00-\x1f\x7f-\xff()<>@,;:\\\\"/\[\]?={}\s]!';
73 
79  const REGEXP_INVALID_COOKIE = '/[\s,;]/';
80 
86  private static $_fileinfoDb;
87 
92  protected $observers = array();
93 
98  protected $url;
99 
104  protected $method = self::METHOD_GET;
105 
111  protected $auth;
112 
117  protected $headers = array();
118 
124  protected $config = array(
125  'adapter' => 'HTTP_Request2_Adapter_Socket',
126  'connect_timeout' => 10,
127  'timeout' => 0,
128  'use_brackets' => true,
129  'protocol_version' => '1.1',
130  'buffer_size' => 16384,
131  'store_body' => true,
132  'local_ip' => null,
133 
134  'proxy_host' => '',
135  'proxy_port' => '',
136  'proxy_user' => '',
137  'proxy_password' => '',
138  'proxy_auth_scheme' => self::AUTH_BASIC,
139  'proxy_type' => 'http',
140 
141  'ssl_verify_peer' => true,
142  'ssl_verify_host' => true,
143  'ssl_cafile' => null,
144  'ssl_capath' => null,
145  'ssl_local_cert' => null,
146  'ssl_passphrase' => null,
147 
148  'digest_compat_ie' => false,
149 
150  'follow_redirects' => false,
151  'max_redirects' => 5,
152  'strict_redirects' => false
153  );
154 
160  protected $lastEvent = array(
161  'name' => 'start',
162  'data' => null
163  );
164 
170  protected $body = '';
171 
176  protected $postParams = array();
177 
182  protected $uploads = array();
183 
188  protected $adapter;
189 
194  protected $cookieJar = null;
195 
205  public function __construct(
206  $url = null, $method = self::METHOD_GET, array $config = array()
207  ) {
208  $this->setConfig($config);
209  if (!empty($url)) {
210  $this->setUrl($url);
211  }
212  if (!empty($method)) {
213  $this->setMethod($method);
214  }
215  $this->setHeader(
216  'user-agent', 'HTTP_Request2/@package_version@ ' .
217  '(http://pear.php.net/package/http_request2) PHP/' . phpversion()
218  );
219  }
220 
233  public function setUrl($url)
234  {
235  if (is_string($url)) {
236  $url = new Net_URL2(
237  $url, array(Net_URL2::OPTION_USE_BRACKETS => $this->config['use_brackets'])
238  );
239  }
240  if (!$url instanceof Net_URL2) {
242  'Parameter is not a valid HTTP URL',
244  );
245  }
246  // URL contains username / password?
247  if ($url->getUserinfo()) {
248  $username = $url->getUser();
249  $password = $url->getPassword();
250  $this->setAuth(rawurldecode($username), $password? rawurldecode($password): '');
251  $url->setUserinfo('');
252  }
253  if ('' == $url->getPath()) {
254  $url->setPath('/');
255  }
256  $this->url = $url;
257 
258  return $this;
259  }
260 
266  public function getUrl()
267  {
268  return $this->url;
269  }
270 
279  public function setMethod($method)
280  {
281  // Method name should be a token: http://tools.ietf.org/html/rfc2616#section-5.1.1
282  if (preg_match(self::REGEXP_INVALID_TOKEN, $method)) {
284  "Invalid request method '{$method}'",
286  );
287  }
288  $this->method = $method;
289 
290  return $this;
291  }
292 
298  public function getMethod()
299  {
300  return $this->method;
301  }
302 
357  public function setConfig($nameOrConfig, $value = null)
358  {
359  if (is_array($nameOrConfig)) {
360  foreach ($nameOrConfig as $name => $value) {
361  $this->setConfig($name, $value);
362  }
363 
364  } elseif ('proxy' == $nameOrConfig) {
365  $url = new Net_URL2($value);
366  $this->setConfig(array(
367  'proxy_type' => $url->getScheme(),
368  'proxy_host' => $url->getHost(),
369  'proxy_port' => $url->getPort(),
370  'proxy_user' => rawurldecode($url->getUser()),
371  'proxy_password' => rawurldecode($url->getPassword())
372  ));
373 
374  } else {
375  if (!array_key_exists($nameOrConfig, $this->config)) {
377  "Unknown configuration parameter '{$nameOrConfig}'",
379  );
380  }
381  $this->config[$nameOrConfig] = $value;
382  }
383 
384  return $this;
385  }
386 
396  public function getConfig($name = null)
397  {
398  if (null === $name) {
399  return $this->config;
400  } elseif (!array_key_exists($name, $this->config)) {
402  "Unknown configuration parameter '{$name}'",
404  );
405  }
406  return $this->config[$name];
407  }
408 
418  public function setAuth($user, $password = '', $scheme = self::AUTH_BASIC)
419  {
420  if (empty($user)) {
421  $this->auth = null;
422  } else {
423  $this->auth = array(
424  'user' => (string)$user,
425  'password' => (string)$password,
426  'scheme' => $scheme
427  );
428  }
429 
430  return $this;
431  }
432 
441  public function getAuth()
442  {
443  return $this->auth;
444  }
445 
474  public function setHeader($name, $value = null, $replace = true)
475  {
476  if (is_array($name)) {
477  foreach ($name as $k => $v) {
478  if (is_string($k)) {
479  $this->setHeader($k, $v, $replace);
480  } else {
481  $this->setHeader($v, null, $replace);
482  }
483  }
484  } else {
485  if (null === $value && strpos($name, ':')) {
486  list($name, $value) = array_map('trim', explode(':', $name, 2));
487  }
488  // Header name should be a token: http://tools.ietf.org/html/rfc2616#section-4.2
489  if (preg_match(self::REGEXP_INVALID_TOKEN, $name)) {
491  "Invalid header name '{$name}'",
493  );
494  }
495  // Header names are case insensitive anyway
496  $name = strtolower($name);
497  if (null === $value) {
498  unset($this->headers[$name]);
499 
500  } else {
501  if (is_array($value)) {
502  $value = implode(', ', array_map('trim', $value));
503  } elseif (is_string($value)) {
504  $value = trim($value);
505  }
506  if (!isset($this->headers[$name]) || $replace) {
507  $this->headers[$name] = $value;
508  } else {
509  $this->headers[$name] .= ', ' . $value;
510  }
511  }
512  }
513 
514  return $this;
515  }
516 
525  public function getHeaders()
526  {
527  return $this->headers;
528  }
529 
548  public function addCookie($name, $value)
549  {
550  if (!empty($this->cookieJar)) {
551  $this->cookieJar->store(
552  array('name' => $name, 'value' => $value), $this->url
553  );
554 
555  } else {
556  $cookie = $name . '=' . $value;
557  if (preg_match(self::REGEXP_INVALID_COOKIE, $cookie)) {
559  "Invalid cookie: '{$cookie}'",
561  );
562  }
563  $cookies = empty($this->headers['cookie'])? '': $this->headers['cookie'] . '; ';
564  $this->setHeader('cookie', $cookies . $cookie);
565  }
566 
567  return $this;
568  }
569 
585  public function setBody($body, $isFilename = false)
586  {
587  if (!$isFilename && !is_resource($body)) {
588  if (!$body instanceof HTTP_Request2_MultipartBody) {
589  $this->body = (string)$body;
590  } else {
591  $this->body = $body;
592  }
593  } else {
594  $fileData = $this->fopenWrapper($body, empty($this->headers['content-type']));
595  $this->body = $fileData['fp'];
596  if (empty($this->headers['content-type'])) {
597  $this->setHeader('content-type', $fileData['type']);
598  }
599  }
600  $this->postParams = $this->uploads = array();
601 
602  return $this;
603  }
604 
610  public function getBody()
611  {
612  if (self::METHOD_POST == $this->method
613  && (!empty($this->postParams) || !empty($this->uploads))
614  ) {
615  if (0 === strpos($this->headers['content-type'], 'application/x-www-form-urlencoded')) {
616  $body = http_build_query($this->postParams, '', '&');
617  if (!$this->getConfig('use_brackets')) {
618  $body = preg_replace('/%5B\d+%5D=/', '=', $body);
619  }
620  // support RFC 3986 by not encoding '~' symbol (request #15368)
621  return str_replace('%7E', '~', $body);
622 
623  } elseif (0 === strpos($this->headers['content-type'], 'multipart/form-data')) {
624  require_once 'HTTP/Request2/MultipartBody.php';
625  return new HTTP_Request2_MultipartBody(
626  $this->postParams, $this->uploads, $this->getConfig('use_brackets')
627  );
628  }
629  }
630  return $this->body;
631  }
632 
654  public function addUpload(
655  $fieldName, $filename, $sendFilename = null, $contentType = null
656  ) {
657  if (!is_array($filename)) {
658  $fileData = $this->fopenWrapper($filename, empty($contentType));
659  $this->uploads[$fieldName] = array(
660  'fp' => $fileData['fp'],
661  'filename' => !empty($sendFilename)? $sendFilename
662  :(is_string($filename)? basename($filename): 'anonymous.blob') ,
663  'size' => $fileData['size'],
664  'type' => empty($contentType)? $fileData['type']: $contentType
665  );
666  } else {
667  $fps = $names = $sizes = $types = array();
668  foreach ($filename as $f) {
669  if (!is_array($f)) {
670  $f = array($f);
671  }
672  $fileData = $this->fopenWrapper($f[0], empty($f[2]));
673  $fps[] = $fileData['fp'];
674  $names[] = !empty($f[1])? $f[1]
675  :(is_string($f[0])? basename($f[0]): 'anonymous.blob');
676  $sizes[] = $fileData['size'];
677  $types[] = empty($f[2])? $fileData['type']: $f[2];
678  }
679  $this->uploads[$fieldName] = array(
680  'fp' => $fps, 'filename' => $names, 'size' => $sizes, 'type' => $types
681  );
682  }
683  if (empty($this->headers['content-type'])
684  || 'application/x-www-form-urlencoded' == $this->headers['content-type']
685  ) {
686  $this->setHeader('content-type', 'multipart/form-data');
687  }
688 
689  return $this;
690  }
691 
700  public function addPostParameter($name, $value = null)
701  {
702  if (!is_array($name)) {
703  $this->postParams[$name] = $value;
704  } else {
705  foreach ($name as $k => $v) {
706  $this->addPostParameter($k, $v);
707  }
708  }
709  if (empty($this->headers['content-type'])) {
710  $this->setHeader('content-type', 'application/x-www-form-urlencoded');
711  }
712 
713  return $this;
714  }
715 
721  public function attach(SplObserver $observer)
722  {
723  foreach ($this->observers as $attached) {
724  if ($attached === $observer) {
725  return;
726  }
727  }
728  $this->observers[] = $observer;
729  }
730 
736  public function detach(SplObserver $observer)
737  {
738  foreach ($this->observers as $key => $attached) {
739  if ($attached === $observer) {
740  unset($this->observers[$key]);
741  return;
742  }
743  }
744  }
745 
749  public function notify()
750  {
751  foreach ($this->observers as $observer) {
752  $observer->update($this);
753  }
754  }
755 
765  public function setLastEvent($name, $data = null)
766  {
767  $this->lastEvent = array(
768  'name' => $name,
769  'data' => $data
770  );
771  $this->notify();
772  }
773 
803  public function getLastEvent()
804  {
805  return $this->lastEvent;
806  }
807 
825  public function setAdapter($adapter)
826  {
827  if (is_string($adapter)) {
828  if (!class_exists($adapter, false)) {
829  if (false === strpos($adapter, '_')) {
830  $adapter = 'HTTP_Request2_Adapter_' . ucfirst($adapter);
831  }
832  if (!class_exists($adapter, false)
833  && preg_match('/^HTTP_Request2_Adapter_([a-zA-Z0-9]+)$/', $adapter)
834  ) {
835  include_once str_replace('_', DIRECTORY_SEPARATOR, $adapter) . '.php';
836  }
837  if (!class_exists($adapter, false)) {
839  "Class {$adapter} not found",
841  );
842  }
843  }
844  $adapter = new $adapter;
845  }
846  if (!$adapter instanceof HTTP_Request2_Adapter) {
848  'Parameter is not a HTTP request adapter',
850  );
851  }
852  $this->adapter = $adapter;
853 
854  return $this;
855  }
856 
870  public function setCookieJar($jar = true)
871  {
872  if (!class_exists('HTTP_Request2_CookieJar', false)) {
873  require_once 'HTTP/Request2/CookieJar.php';
874  }
875 
876  if ($jar instanceof HTTP_Request2_CookieJar) {
877  $this->cookieJar = $jar;
878  } elseif (true === $jar) {
879  $this->cookieJar = new HTTP_Request2_CookieJar();
880  } elseif (!$jar) {
881  $this->cookieJar = null;
882  } else {
884  'Invalid parameter passed to setCookieJar()',
886  );
887  }
888 
889  return $this;
890  }
891 
897  public function getCookieJar()
898  {
899  return $this->cookieJar;
900  }
901 
908  public function send()
909  {
910  // Sanity check for URL
911  if (!$this->url instanceof Net_URL2
912  || !$this->url->isAbsolute()
913  || !in_array(strtolower($this->url->getScheme()), array('https', 'http'))
914  ) {
916  'HTTP_Request2 needs an absolute HTTP(S) request URL, '
917  . ($this->url instanceof Net_URL2
918  ? "'" . $this->url->__toString() . "'" : 'none')
919  . ' given',
921  );
922  }
923  if (empty($this->adapter)) {
924  $this->setAdapter($this->getConfig('adapter'));
925  }
926  // magic_quotes_runtime may break file uploads and chunked response
927  // processing; see bug #4543. Don't use ini_get() here; see bug #16440.
928  if ($magicQuotes = get_magic_quotes_runtime()) {
929  set_magic_quotes_runtime(false);
930  }
931  // force using single byte encoding if mbstring extension overloads
932  // strlen() and substr(); see bug #1781, bug #10605
933  if (extension_loaded('mbstring') && (2 & ini_get('mbstring.func_overload'))) {
934  $oldEncoding = mb_internal_encoding();
935  mb_internal_encoding('8bit');
936  }
937 
938  try {
939  $response = $this->adapter->sendRequest($this);
940  } catch (Exception $e) {
941  }
942  // cleanup in either case (poor man's "finally" clause)
943  if ($magicQuotes) {
944  set_magic_quotes_runtime(true);
945  }
946  if (!empty($oldEncoding)) {
947  mb_internal_encoding($oldEncoding);
948  }
949  // rethrow the exception
950  if (!empty($e)) {
951  throw $e;
952  }
953  return $response;
954  }
955 
967  protected function fopenWrapper($file, $detectType = false)
968  {
969  if (!is_string($file) && !is_resource($file)) {
971  "Filename or file pointer resource expected",
973  );
974  }
975  $fileData = array(
976  'fp' => is_string($file)? null: $file,
977  'type' => 'application/octet-stream',
978  'size' => 0
979  );
980  if (is_string($file)) {
981  if (!($fileData['fp'] = @fopen($file, 'rb'))) {
982  $error = error_get_last();
984  $error['message'], HTTP_Request2_Exception::READ_ERROR
985  );
986  }
987  if ($detectType) {
988  $fileData['type'] = self::detectMimeType($file);
989  }
990  }
991  if (!($stat = fstat($fileData['fp']))) {
993  "fstat() call failed", HTTP_Request2_Exception::READ_ERROR
994  );
995  }
996  $fileData['size'] = $stat['size'];
997 
998  return $fileData;
999  }
1000 
1012  protected static function detectMimeType($filename)
1013  {
1014  // finfo extension from PECL available
1015  if (function_exists('finfo_open')) {
1016  if (!isset(self::$_fileinfoDb)) {
1017  self::$_fileinfoDb = @finfo_open(FILEINFO_MIME);
1018  }
1019  if (self::$_fileinfoDb) {
1020  $info = finfo_file(self::$_fileinfoDb, $filename);
1021  }
1022  }
1023  // (deprecated) mime_content_type function available
1024  if (empty($info) && function_exists('mime_content_type')) {
1025  return mime_content_type($filename);
1026  }
1027  return empty($info)? 'application/octet-stream': $info;
1028  }
1029 }
1030 ?>