PageRenderTime 46ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/wp-includes/Requests/Transport/fsockopen.php

https://gitlab.com/campus-academy/krowkaramel
PHP | 451 lines | 275 code | 70 blank | 106 comment | 74 complexity | 04e4e4b1068bd88353e8675c0a4742d4 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. * Second to microsecond conversion
  17. *
  18. * @var integer
  19. */
  20. const SECOND_IN_MICROSECONDS = 1000000;
  21. /**
  22. * Raw HTTP data
  23. *
  24. * @var string
  25. */
  26. public $headers = '';
  27. /**
  28. * Stream metadata
  29. *
  30. * @var array Associative array of properties, see {@see https://secure.php.net/stream_get_meta_data}
  31. */
  32. public $info;
  33. /**
  34. * What's the maximum number of bytes we should keep?
  35. *
  36. * @var int|bool Byte count, or false if no limit.
  37. */
  38. protected $max_bytes = false;
  39. protected $connect_error = '';
  40. /**
  41. * Perform a request
  42. *
  43. * @throws Requests_Exception On failure to connect to socket (`fsockopenerror`)
  44. * @throws Requests_Exception On socket timeout (`timeout`)
  45. *
  46. * @param string $url URL to request
  47. * @param array $headers Associative array of request headers
  48. * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD
  49. * @param array $options Request options, see {@see Requests::response()} for documentation
  50. * @return string Raw HTTP result
  51. */
  52. public function request($url, $headers = array(), $data = array(), $options = array()) {
  53. $options['hooks']->dispatch('fsockopen.before_request');
  54. $url_parts = parse_url($url);
  55. if (empty($url_parts)) {
  56. throw new Requests_Exception('Invalid URL.', 'invalidurl', $url);
  57. }
  58. $host = $url_parts['host'];
  59. $context = stream_context_create();
  60. $verifyname = false;
  61. $case_insensitive_headers = new Requests_Utility_CaseInsensitiveDictionary($headers);
  62. // HTTPS support
  63. if (isset($url_parts['scheme']) && strtolower($url_parts['scheme']) === 'https') {
  64. $remote_socket = 'ssl://' . $host;
  65. if (!isset($url_parts['port'])) {
  66. $url_parts['port'] = 443;
  67. }
  68. $context_options = array(
  69. 'verify_peer' => true,
  70. 'capture_peer_cert' => true,
  71. );
  72. $verifyname = true;
  73. // SNI, if enabled (OpenSSL >=0.9.8j)
  74. // phpcs:ignore PHPCompatibility.Constants.NewConstants.openssl_tlsext_server_nameFound
  75. if (defined('OPENSSL_TLSEXT_SERVER_NAME') && OPENSSL_TLSEXT_SERVER_NAME) {
  76. $context_options['SNI_enabled'] = true;
  77. if (isset($options['verifyname']) && $options['verifyname'] === false) {
  78. $context_options['SNI_enabled'] = false;
  79. }
  80. }
  81. if (isset($options['verify'])) {
  82. if ($options['verify'] === false) {
  83. $context_options['verify_peer'] = false;
  84. $context_options['verify_peer_name'] = false;
  85. $verifyname = false;
  86. }
  87. elseif (is_string($options['verify'])) {
  88. $context_options['cafile'] = $options['verify'];
  89. }
  90. }
  91. if (isset($options['verifyname']) && $options['verifyname'] === false) {
  92. $context_options['verify_peer_name'] = false;
  93. $verifyname = false;
  94. }
  95. stream_context_set_option($context, array('ssl' => $context_options));
  96. }
  97. else {
  98. $remote_socket = 'tcp://' . $host;
  99. }
  100. $this->max_bytes = $options['max_bytes'];
  101. if (!isset($url_parts['port'])) {
  102. $url_parts['port'] = 80;
  103. }
  104. $remote_socket .= ':' . $url_parts['port'];
  105. // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_set_error_handler
  106. set_error_handler(array($this, 'connect_error_handler'), E_WARNING | E_NOTICE);
  107. $options['hooks']->dispatch('fsockopen.remote_socket', array(&$remote_socket));
  108. $socket = stream_socket_client($remote_socket, $errno, $errstr, ceil($options['connect_timeout']), STREAM_CLIENT_CONNECT, $context);
  109. restore_error_handler();
  110. if ($verifyname && !$this->verify_certificate_from_context($host, $context)) {
  111. throw new Requests_Exception('SSL certificate did not match the requested domain name', 'ssl.no_match');
  112. }
  113. if (!$socket) {
  114. if ($errno === 0) {
  115. // Connection issue
  116. throw new Requests_Exception(rtrim($this->connect_error), 'fsockopen.connect_error');
  117. }
  118. throw new Requests_Exception($errstr, 'fsockopenerror', null, $errno);
  119. }
  120. $data_format = $options['data_format'];
  121. if ($data_format === 'query') {
  122. $path = self::format_get($url_parts, $data);
  123. $data = '';
  124. }
  125. else {
  126. $path = self::format_get($url_parts, array());
  127. }
  128. $options['hooks']->dispatch('fsockopen.remote_host_path', array(&$path, $url));
  129. $request_body = '';
  130. $out = sprintf("%s %s HTTP/%.1F\r\n", $options['type'], $path, $options['protocol_version']);
  131. if ($options['type'] !== Requests::TRACE) {
  132. if (is_array($data)) {
  133. $request_body = http_build_query($data, '', '&');
  134. }
  135. else {
  136. $request_body = $data;
  137. }
  138. // Always include Content-length on POST requests to prevent
  139. // 411 errors from some servers when the body is empty.
  140. if (!empty($data) || $options['type'] === Requests::POST) {
  141. if (!isset($case_insensitive_headers['Content-Length'])) {
  142. $headers['Content-Length'] = strlen($request_body);
  143. }
  144. if (!isset($case_insensitive_headers['Content-Type'])) {
  145. $headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
  146. }
  147. }
  148. }
  149. if (!isset($case_insensitive_headers['Host'])) {
  150. $out .= sprintf('Host: %s', $url_parts['host']);
  151. if ((strtolower($url_parts['scheme']) === 'http' && $url_parts['port'] !== 80) || (strtolower($url_parts['scheme']) === 'https' && $url_parts['port'] !== 443)) {
  152. $out .= ':' . $url_parts['port'];
  153. }
  154. $out .= "\r\n";
  155. }
  156. if (!isset($case_insensitive_headers['User-Agent'])) {
  157. $out .= sprintf("User-Agent: %s\r\n", $options['useragent']);
  158. }
  159. $accept_encoding = $this->accept_encoding();
  160. if (!isset($case_insensitive_headers['Accept-Encoding']) && !empty($accept_encoding)) {
  161. $out .= sprintf("Accept-Encoding: %s\r\n", $accept_encoding);
  162. }
  163. $headers = Requests::flatten($headers);
  164. if (!empty($headers)) {
  165. $out .= implode("\r\n", $headers) . "\r\n";
  166. }
  167. $options['hooks']->dispatch('fsockopen.after_headers', array(&$out));
  168. if (substr($out, -2) !== "\r\n") {
  169. $out .= "\r\n";
  170. }
  171. if (!isset($case_insensitive_headers['Connection'])) {
  172. $out .= "Connection: Close\r\n";
  173. }
  174. $out .= "\r\n" . $request_body;
  175. $options['hooks']->dispatch('fsockopen.before_send', array(&$out));
  176. fwrite($socket, $out);
  177. $options['hooks']->dispatch('fsockopen.after_send', array($out));
  178. if (!$options['blocking']) {
  179. fclose($socket);
  180. $fake_headers = '';
  181. $options['hooks']->dispatch('fsockopen.after_request', array(&$fake_headers));
  182. return '';
  183. }
  184. $timeout_sec = (int) floor($options['timeout']);
  185. if ($timeout_sec === $options['timeout']) {
  186. $timeout_msec = 0;
  187. }
  188. else {
  189. $timeout_msec = self::SECOND_IN_MICROSECONDS * $options['timeout'] % self::SECOND_IN_MICROSECONDS;
  190. }
  191. stream_set_timeout($socket, $timeout_sec, $timeout_msec);
  192. $response = '';
  193. $body = '';
  194. $headers = '';
  195. $this->info = stream_get_meta_data($socket);
  196. $size = 0;
  197. $doingbody = false;
  198. $download = false;
  199. if ($options['filename']) {
  200. $download = fopen($options['filename'], 'wb');
  201. }
  202. while (!feof($socket)) {
  203. $this->info = stream_get_meta_data($socket);
  204. if ($this->info['timed_out']) {
  205. throw new Requests_Exception('fsocket timed out', 'timeout');
  206. }
  207. $block = fread($socket, Requests::BUFFER_SIZE);
  208. if (!$doingbody) {
  209. $response .= $block;
  210. if (strpos($response, "\r\n\r\n")) {
  211. list($headers, $block) = explode("\r\n\r\n", $response, 2);
  212. $doingbody = true;
  213. }
  214. }
  215. // Are we in body mode now?
  216. if ($doingbody) {
  217. $options['hooks']->dispatch('request.progress', array($block, $size, $this->max_bytes));
  218. $data_length = strlen($block);
  219. if ($this->max_bytes) {
  220. // Have we already hit a limit?
  221. if ($size === $this->max_bytes) {
  222. continue;
  223. }
  224. if (($size + $data_length) > $this->max_bytes) {
  225. // Limit the length
  226. $limited_length = ($this->max_bytes - $size);
  227. $block = substr($block, 0, $limited_length);
  228. }
  229. }
  230. $size += strlen($block);
  231. if ($download) {
  232. fwrite($download, $block);
  233. }
  234. else {
  235. $body .= $block;
  236. }
  237. }
  238. }
  239. $this->headers = $headers;
  240. if ($download) {
  241. fclose($download);
  242. }
  243. else {
  244. $this->headers .= "\r\n\r\n" . $body;
  245. }
  246. fclose($socket);
  247. $options['hooks']->dispatch('fsockopen.after_request', array(&$this->headers, &$this->info));
  248. return $this->headers;
  249. }
  250. /**
  251. * Send multiple requests simultaneously
  252. *
  253. * @param array $requests Request data (array of 'url', 'headers', 'data', 'options') as per {@see Requests_Transport::request}
  254. * @param array $options Global options, see {@see Requests::response()} for documentation
  255. * @return array Array of Requests_Response objects (may contain Requests_Exception or string responses as well)
  256. */
  257. public function request_multiple($requests, $options) {
  258. $responses = array();
  259. $class = get_class($this);
  260. foreach ($requests as $id => $request) {
  261. try {
  262. $handler = new $class();
  263. $responses[$id] = $handler->request($request['url'], $request['headers'], $request['data'], $request['options']);
  264. $request['options']['hooks']->dispatch('transport.internal.parse_response', array(&$responses[$id], $request));
  265. }
  266. catch (Requests_Exception $e) {
  267. $responses[$id] = $e;
  268. }
  269. if (!is_string($responses[$id])) {
  270. $request['options']['hooks']->dispatch('multiple.request.complete', array(&$responses[$id], $id));
  271. }
  272. }
  273. return $responses;
  274. }
  275. /**
  276. * Retrieve the encodings we can accept
  277. *
  278. * @return string Accept-Encoding header value
  279. */
  280. protected static function accept_encoding() {
  281. $type = array();
  282. if (function_exists('gzinflate')) {
  283. $type[] = 'deflate;q=1.0';
  284. }
  285. if (function_exists('gzuncompress')) {
  286. $type[] = 'compress;q=0.5';
  287. }
  288. $type[] = 'gzip;q=0.5';
  289. return implode(', ', $type);
  290. }
  291. /**
  292. * Format a URL given GET data
  293. *
  294. * @param array $url_parts
  295. * @param array|object $data Data to build query using, see {@see https://secure.php.net/http_build_query}
  296. * @return string URL with data
  297. */
  298. protected static function format_get($url_parts, $data) {
  299. if (!empty($data)) {
  300. if (empty($url_parts['query'])) {
  301. $url_parts['query'] = '';
  302. }
  303. $url_parts['query'] .= '&' . http_build_query($data, '', '&');
  304. $url_parts['query'] = trim($url_parts['query'], '&');
  305. }
  306. if (isset($url_parts['path'])) {
  307. if (isset($url_parts['query'])) {
  308. $get = $url_parts['path'] . '?' . $url_parts['query'];
  309. }
  310. else {
  311. $get = $url_parts['path'];
  312. }
  313. }
  314. else {
  315. $get = '/';
  316. }
  317. return $get;
  318. }
  319. /**
  320. * Error handler for stream_socket_client()
  321. *
  322. * @param int $errno Error number (e.g. E_WARNING)
  323. * @param string $errstr Error message
  324. */
  325. public function connect_error_handler($errno, $errstr) {
  326. // Double-check we can handle it
  327. if (($errno & E_WARNING) === 0 && ($errno & E_NOTICE) === 0) {
  328. // Return false to indicate the default error handler should engage
  329. return false;
  330. }
  331. $this->connect_error .= $errstr . "\n";
  332. return true;
  333. }
  334. /**
  335. * Verify the certificate against common name and subject alternative names
  336. *
  337. * Unfortunately, PHP doesn't check the certificate against the alternative
  338. * names, leading things like 'https://www.github.com/' to be invalid.
  339. * Instead
  340. *
  341. * @see https://tools.ietf.org/html/rfc2818#section-3.1 RFC2818, Section 3.1
  342. *
  343. * @throws Requests_Exception On failure to connect via TLS (`fsockopen.ssl.connect_error`)
  344. * @throws Requests_Exception On not obtaining a match for the host (`fsockopen.ssl.no_match`)
  345. * @param string $host Host name to verify against
  346. * @param resource $context Stream context
  347. * @return bool
  348. */
  349. public function verify_certificate_from_context($host, $context) {
  350. $meta = stream_context_get_options($context);
  351. // If we don't have SSL options, then we couldn't make the connection at
  352. // all
  353. if (empty($meta) || empty($meta['ssl']) || empty($meta['ssl']['peer_certificate'])) {
  354. throw new Requests_Exception(rtrim($this->connect_error), 'ssl.connect_error');
  355. }
  356. $cert = openssl_x509_parse($meta['ssl']['peer_certificate']);
  357. return Requests_SSL::verify_certificate($host, $cert);
  358. }
  359. /**
  360. * Whether this transport is valid
  361. *
  362. * @codeCoverageIgnore
  363. * @return boolean True if the transport is valid, false otherwise.
  364. */
  365. public static function test($capabilities = array()) {
  366. if (!function_exists('fsockopen')) {
  367. return false;
  368. }
  369. // If needed, check that streams support SSL
  370. if (isset($capabilities['ssl']) && $capabilities['ssl']) {
  371. if (!extension_loaded('openssl') || !function_exists('openssl_x509_parse')) {
  372. return false;
  373. }
  374. // Currently broken, thanks to https://github.com/facebook/hhvm/issues/2156
  375. if (defined('HHVM_VERSION')) {
  376. return false;
  377. }
  378. }
  379. return true;
  380. }
  381. }