PageRenderTime 37ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/modules/requests/Requests/Transport/fsockopen.php

https://gitlab.com/x33n/ampache
PHP | 381 lines | 239 code | 54 blank | 88 comment | 55 complexity | ce8d5e2e4e0842b8574cb274c5eabc5d MD5 | raw file
  1. <?php
  2. /**
  3. * fsockopen HTTP transport
  4. *
  5. * @package Requests
  6. * @subpackage Transport
  7. */
  8. /**
  9. * fsockopen HTTP transport
  10. *
  11. * @package Requests
  12. * @subpackage Transport
  13. */
  14. class Requests_Transport_fsockopen implements Requests_Transport {
  15. /**
  16. * Raw HTTP data
  17. *
  18. * @var string
  19. */
  20. public $headers = '';
  21. /**
  22. * Stream metadata
  23. *
  24. * @var array Associative array of properties, see {@see http://php.net/stream_get_meta_data}
  25. */
  26. public $info;
  27. protected $connect_error = '';
  28. /**
  29. * Perform a request
  30. *
  31. * @throws Requests_Exception On failure to connect to socket (`fsockopenerror`)
  32. * @throws Requests_Exception On socket timeout (`timeout`)
  33. *
  34. * @param string $url URL to request
  35. * @param array $headers Associative array of request headers
  36. * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD
  37. * @param array $options Request options, see {@see Requests::response()} for documentation
  38. * @return string Raw HTTP result
  39. */
  40. public function request($url, $headers = array(), $data = array(), $options = array()) {
  41. $options['hooks']->dispatch('fsockopen.before_request');
  42. $url_parts = parse_url($url);
  43. $host = $url_parts['host'];
  44. $context = stream_context_create();
  45. $verifyname = false;
  46. // HTTPS support
  47. if (isset($url_parts['scheme']) && strtolower($url_parts['scheme']) === 'https') {
  48. $remote_socket = 'ssl://' . $host;
  49. $url_parts['port'] = 443;
  50. $context_options = array(
  51. 'verify_peer' => true,
  52. // 'CN_match' => $host,
  53. 'capture_peer_cert' => true
  54. );
  55. $verifyname = true;
  56. // SNI, if enabled (OpenSSL >=0.9.8j)
  57. if (defined('OPENSSL_TLSEXT_SERVER_NAME') && OPENSSL_TLSEXT_SERVER_NAME) {
  58. $context_options['SNI_enabled'] = true;
  59. if (isset($options['verifyname']) && $options['verifyname'] === false) {
  60. $context_options['SNI_enabled'] = false;
  61. }
  62. }
  63. if (isset($options['verify'])) {
  64. if ($options['verify'] === false) {
  65. $context_options['verify_peer'] = false;
  66. } elseif (is_string($options['verify'])) {
  67. $context_options['cafile'] = $options['verify'];
  68. }
  69. }
  70. if (isset($options['verifyname']) && $options['verifyname'] === false) {
  71. $verifyname = false;
  72. }
  73. stream_context_set_option($context, array('ssl' => $context_options));
  74. }
  75. else {
  76. $remote_socket = 'tcp://' . $host;
  77. }
  78. $proxy = isset( $options['proxy'] );
  79. $proxy_auth = $proxy && isset( $options['proxy_username'] ) && isset( $options['proxy_password'] );
  80. if (!isset($url_parts['port'])) {
  81. $url_parts['port'] = 80;
  82. }
  83. $remote_socket .= ':' . $url_parts['port'];
  84. set_error_handler(array($this, 'connect_error_handler'), E_WARNING | E_NOTICE);
  85. $options['hooks']->dispatch('fsockopen.remote_socket', array(&$remote_socket));
  86. $fp = stream_socket_client($remote_socket, $errno, $errstr, $options['timeout'], STREAM_CLIENT_CONNECT, $context);
  87. restore_error_handler();
  88. if ($verifyname) {
  89. if (!$this->verify_certificate_from_context($host, $context)) {
  90. throw new Requests_Exception('SSL certificate did not match the requested domain name', 'ssl.no_match');
  91. }
  92. }
  93. if (!$fp) {
  94. if ($errno === 0) {
  95. // Connection issue
  96. throw new Requests_Exception(rtrim($this->connect_error), 'fsockopen.connect_error');
  97. }
  98. else {
  99. throw new Requests_Exception($errstr, 'fsockopenerror');
  100. return;
  101. }
  102. }
  103. $request_body = '';
  104. $out = '';
  105. switch ($options['type']) {
  106. case Requests::POST:
  107. case Requests::PUT:
  108. case Requests::PATCH:
  109. if (isset($url_parts['path'])) {
  110. $path = $url_parts['path'];
  111. if (isset($url_parts['query'])) {
  112. $path .= '?' . $url_parts['query'];
  113. }
  114. }
  115. else {
  116. $path = '/';
  117. }
  118. $options['hooks']->dispatch( 'fsockopen.remote_host_path', array( &$path, $url ) );
  119. $out = $options['type'] . " $path HTTP/1.0\r\n";
  120. if (is_array($data)) {
  121. $request_body = http_build_query($data, null, '&');
  122. }
  123. else {
  124. $request_body = $data;
  125. }
  126. if (empty($headers['Content-Length'])) {
  127. $headers['Content-Length'] = strlen($request_body);
  128. }
  129. if (empty($headers['Content-Type'])) {
  130. $headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
  131. }
  132. break;
  133. case Requests::HEAD:
  134. case Requests::GET:
  135. case Requests::DELETE:
  136. $path = self::format_get($url_parts, $data);
  137. $options['hooks']->dispatch('fsockopen.remote_host_path', array(&$path, $url));
  138. $out = $options['type'] . " $path HTTP/1.0\r\n";
  139. break;
  140. }
  141. $out .= "Host: {$url_parts['host']}";
  142. if ($url_parts['port'] !== 80) {
  143. $out .= ":{$url_parts['port']}";
  144. }
  145. $out .= "\r\n";
  146. $out .= "User-Agent: {$options['useragent']}\r\n";
  147. $accept_encoding = $this->accept_encoding();
  148. if (!empty($accept_encoding)) {
  149. $out .= "Accept-Encoding: $accept_encoding\r\n";
  150. }
  151. $headers = Requests::flatten($headers);
  152. if (!empty($headers)) {
  153. $out .= implode($headers, "\r\n") . "\r\n";
  154. }
  155. $options['hooks']->dispatch('fsockopen.after_headers', array(&$out));
  156. if (substr($out, -2) !== "\r\n") {
  157. $out .= "\r\n";
  158. }
  159. $out .= "Connection: Close\r\n\r\n" . $request_body;
  160. $options['hooks']->dispatch('fsockopen.before_send', array(&$out));
  161. fwrite($fp, $out);
  162. $options['hooks']->dispatch('fsockopen.after_send', array(&$fake_headers));
  163. if (!$options['blocking']) {
  164. fclose($fp);
  165. $fake_headers = '';
  166. $options['hooks']->dispatch('fsockopen.after_request', array(&$fake_headers));
  167. return '';
  168. }
  169. stream_set_timeout($fp, $options['timeout']);
  170. $this->info = stream_get_meta_data($fp);
  171. $this->headers = '';
  172. $this->info = stream_get_meta_data($fp);
  173. if (!$options['filename']) {
  174. while (!feof($fp)) {
  175. $this->info = stream_get_meta_data($fp);
  176. if ($this->info['timed_out']) {
  177. throw new Requests_Exception('fsocket timed out', 'timeout');
  178. }
  179. $this->headers .= fread($fp, 1160);
  180. }
  181. }
  182. else {
  183. $download = fopen($options['filename'], 'wb');
  184. $doingbody = false;
  185. $response = '';
  186. while (!feof($fp)) {
  187. $this->info = stream_get_meta_data($fp);
  188. if ($this->info['timed_out']) {
  189. throw new Requests_Exception('fsocket timed out', 'timeout');
  190. }
  191. $block = fread($fp, 1160);
  192. if ($doingbody) {
  193. fwrite($download, $block);
  194. }
  195. else {
  196. $response .= $block;
  197. if (strpos($response, "\r\n\r\n")) {
  198. list($this->headers, $block) = explode("\r\n\r\n", $response, 2);
  199. $doingbody = true;
  200. fwrite($download, $block);
  201. }
  202. }
  203. }
  204. fclose($download);
  205. }
  206. fclose($fp);
  207. $options['hooks']->dispatch('fsockopen.after_request', array(&$this->headers));
  208. return $this->headers;
  209. }
  210. /**
  211. * Send multiple requests simultaneously
  212. *
  213. * @param array $requests Request data (array of 'url', 'headers', 'data', 'options') as per {@see Requests_Transport::request}
  214. * @param array $options Global options, see {@see Requests::response()} for documentation
  215. * @return array Array of Requests_Response objects (may contain Requests_Exception or string responses as well)
  216. */
  217. public function request_multiple($requests, $options) {
  218. $responses = array();
  219. $class = get_class($this);
  220. foreach ($requests as $id => $request) {
  221. try {
  222. $handler = new $class();
  223. $responses[$id] = $handler->request($request['url'], $request['headers'], $request['data'], $request['options']);
  224. $request['options']['hooks']->dispatch('transport.internal.parse_response', array(&$responses[$id], $request));
  225. }
  226. catch (Requests_Exception $e) {
  227. $responses[$id] = $e;
  228. }
  229. if (!is_string($responses[$id])) {
  230. $request['options']['hooks']->dispatch('multiple.request.complete', array(&$responses[$id], $id));
  231. }
  232. }
  233. return $responses;
  234. }
  235. /**
  236. * Retrieve the encodings we can accept
  237. *
  238. * @return string Accept-Encoding header value
  239. */
  240. protected static function accept_encoding() {
  241. $type = array();
  242. if (function_exists('gzinflate')) {
  243. $type[] = 'deflate;q=1.0';
  244. }
  245. if (function_exists('gzuncompress')) {
  246. $type[] = 'compress;q=0.5';
  247. }
  248. $type[] = 'gzip;q=0.5';
  249. return implode(', ', $type);
  250. }
  251. /**
  252. * Format a URL given GET data
  253. *
  254. * @param array $url_parts
  255. * @param array|object $data Data to build query using, see {@see http://php.net/http_build_query}
  256. * @return string URL with data
  257. */
  258. protected static function format_get($url_parts, $data) {
  259. if (!empty($data)) {
  260. if (empty($url_parts['query']))
  261. $url_parts['query'] = '';
  262. $url_parts['query'] .= '&' . http_build_query($data, null, '&');
  263. $url_parts['query'] = trim($url_parts['query'], '&');
  264. }
  265. if (isset($url_parts['path'])) {
  266. if (isset($url_parts['query'])) {
  267. $get = $url_parts['path'] . '?' . $url_parts['query'];
  268. }
  269. else {
  270. $get = $url_parts['path'];
  271. }
  272. }
  273. else {
  274. $get = '/';
  275. }
  276. return $get;
  277. }
  278. /**
  279. * Error handler for stream_socket_client()
  280. *
  281. * @param int $errno Error number (e.g. E_WARNING)
  282. * @param string $errstr Error message
  283. */
  284. public function connect_error_handler($errno, $errstr) {
  285. // Double-check we can handle it
  286. if (($errno & E_WARNING) === 0 && ($errno & E_NOTICE) === 0) {
  287. // Return false to indicate the default error handler should engage
  288. return false;
  289. }
  290. $this->connect_error .= $errstr . "\n";
  291. return true;
  292. }
  293. /**
  294. * Verify the certificate against common name and subject alternative names
  295. *
  296. * Unfortunately, PHP doesn't check the certificate against the alternative
  297. * names, leading things like 'https://www.github.com/' to be invalid.
  298. * Instead
  299. *
  300. * @see http://tools.ietf.org/html/rfc2818#section-3.1 RFC2818, Section 3.1
  301. *
  302. * @throws Requests_Exception On failure to connect via TLS (`fsockopen.ssl.connect_error`)
  303. * @throws Requests_Exception On not obtaining a match for the host (`fsockopen.ssl.no_match`)
  304. * @param string $host Host name to verify against
  305. * @param resource $context Stream context
  306. * @return bool
  307. */
  308. public function verify_certificate_from_context($host, $context) {
  309. $meta = stream_context_get_options($context);
  310. // If we don't have SSL options, then we couldn't make the connection at
  311. // all
  312. if (empty($meta) || empty($meta['ssl']) || empty($meta['ssl']['peer_certificate'])) {
  313. throw new Requests_Exception(rtrim($this->connect_error), 'ssl.connect_error');
  314. }
  315. $cert = openssl_x509_parse($meta['ssl']['peer_certificate']);
  316. return Requests_SSL::verify_certificate($host, $cert);
  317. }
  318. /**
  319. * Whether this transport is valid
  320. *
  321. * @codeCoverageIgnore
  322. * @return boolean True if the transport is valid, false otherwise.
  323. */
  324. public static function test() {
  325. return function_exists('fsockopen');
  326. }
  327. }