home *** CD-ROM | disk | FTP | other *** search
/ HTML Examples / WP.iso / wordpress2 / wp-includes / Requests / Transport / fsockopen.php < prev   
Encoding:
PHP Script  |  2016-10-04  |  12.1 KB  |  445 lines

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