Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 311 and 402] [Versions 311 and 403]

   1  <?php
   2  /*
   3   * Copyright 2013 Google Inc.
   4   *
   5   * Licensed under the Apache License, Version 2.0 (the "License");
   6   * you may not use this file except in compliance with the License.
   7   * You may obtain a copy of the License at
   8   *
   9   *     http://www.apache.org/licenses/LICENSE-2.0
  10   *
  11   * Unless required by applicable law or agreed to in writing, software
  12   * distributed under the License is distributed on an "AS IS" BASIS,
  13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14   * See the License for the specific language governing permissions and
  15   * limitations under the License.
  16   */
  17  
  18  /**
  19   * Http Streams based implementation of Google_IO.
  20   *
  21   * @author Stuart Langley <slangley@google.com>
  22   */
  23  
  24  if (!class_exists('Google_Client')) {
  25    require_once dirname(__FILE__) . '/../autoload.php';
  26  }
  27  
  28  class Google_IO_Stream extends Google_IO_Abstract
  29  {
  30    const TIMEOUT = "timeout";
  31    const ZLIB = "compress.zlib://";
  32    private $options = array();
  33    private $trappedErrorNumber;
  34    private $trappedErrorString;
  35  
  36    private static $DEFAULT_HTTP_CONTEXT = array(
  37      "follow_location" => 0,
  38      "ignore_errors" => 1,
  39    );
  40  
  41    private static $DEFAULT_SSL_CONTEXT = array(
  42      "verify_peer" => true,
  43    );
  44  
  45    public function __construct(Google_Client $client)
  46    {
  47      if (!ini_get('allow_url_fopen')) {
  48        $error = 'The stream IO handler requires the allow_url_fopen runtime ' .
  49                 'configuration to be enabled';
  50        $client->getLogger()->critical($error);
  51        throw new Google_IO_Exception($error);
  52      }
  53  
  54      parent::__construct($client);
  55    }
  56  
  57    /**
  58     * Execute an HTTP Request
  59     *
  60     * @param Google_Http_Request $request the http request to be executed
  61     * @return array containing response headers, body, and http code
  62     * @throws Google_IO_Exception on curl or IO error
  63     */
  64    public function executeRequest(Google_Http_Request $request)
  65    {
  66      $default_options = stream_context_get_options(stream_context_get_default());
  67  
  68      $requestHttpContext = array_key_exists('http', $default_options) ?
  69          $default_options['http'] : array();
  70  
  71      if ($request->getPostBody()) {
  72        $requestHttpContext["content"] = $request->getPostBody();
  73      }
  74  
  75      $requestHeaders = $request->getRequestHeaders();
  76      if ($requestHeaders && is_array($requestHeaders)) {
  77        $headers = "";
  78        foreach ($requestHeaders as $k => $v) {
  79          $headers .= "$k: $v\r\n";
  80        }
  81        $requestHttpContext["header"] = $headers;
  82      }
  83  
  84      $requestHttpContext["method"] = $request->getRequestMethod();
  85      $requestHttpContext["user_agent"] = $request->getUserAgent();
  86  
  87      $requestSslContext = array_key_exists('ssl', $default_options) ?
  88          $default_options['ssl'] : array();
  89  
  90      if (!$this->client->isAppEngine() && !array_key_exists("cafile", $requestSslContext)) {
  91        $requestSslContext["cafile"] = dirname(__FILE__) . '/cacerts.pem';
  92      }
  93  
  94      $options = array(
  95          "http" => array_merge(
  96              self::$DEFAULT_HTTP_CONTEXT,
  97              $requestHttpContext
  98          ),
  99          "ssl" => array_merge(
 100              self::$DEFAULT_SSL_CONTEXT,
 101              $requestSslContext
 102          )
 103      );
 104  
 105      $context = stream_context_create($options);
 106  
 107      $url = $request->getUrl();
 108  
 109      if ($request->canGzip()) {
 110        $url = self::ZLIB . $url;
 111      }
 112  
 113      $this->client->getLogger()->debug(
 114          'Stream request',
 115          array(
 116              'url' => $url,
 117              'method' => $request->getRequestMethod(),
 118              'headers' => $requestHeaders,
 119              'body' => $request->getPostBody()
 120          )
 121      );
 122  
 123      // We are trapping any thrown errors in this method only and
 124      // throwing an exception.
 125      $this->trappedErrorNumber = null;
 126      $this->trappedErrorString = null;
 127  
 128      // START - error trap.
 129      set_error_handler(array($this, 'trapError'));
 130      $fh = fopen($url, 'r', false, $context);
 131      restore_error_handler();
 132      // END - error trap.
 133  
 134      if ($this->trappedErrorNumber) {
 135        $error = sprintf(
 136            "HTTP Error: Unable to connect: '%s'",
 137            $this->trappedErrorString
 138        );
 139  
 140        $this->client->getLogger()->error('Stream ' . $error);
 141        throw new Google_IO_Exception($error, $this->trappedErrorNumber);
 142      }
 143  
 144      $response_data = false;
 145      $respHttpCode = self::UNKNOWN_CODE;
 146      if ($fh) {
 147        if (isset($this->options[self::TIMEOUT])) {
 148          stream_set_timeout($fh, $this->options[self::TIMEOUT]);
 149        }
 150  
 151        $response_data = stream_get_contents($fh);
 152        fclose($fh);
 153  
 154        $respHttpCode = $this->getHttpResponseCode($http_response_header);
 155      }
 156  
 157      if (false === $response_data) {
 158        $error = sprintf(
 159            "HTTP Error: Unable to connect: '%s'",
 160            $respHttpCode
 161        );
 162  
 163        $this->client->getLogger()->error('Stream ' . $error);
 164        throw new Google_IO_Exception($error, $respHttpCode);
 165      }
 166  
 167      $responseHeaders = $this->getHttpResponseHeaders($http_response_header);
 168  
 169      $this->client->getLogger()->debug(
 170          'Stream response',
 171          array(
 172              'code' => $respHttpCode,
 173              'headers' => $responseHeaders,
 174              'body' => $response_data,
 175          )
 176      );
 177  
 178      return array($response_data, $responseHeaders, $respHttpCode);
 179    }
 180  
 181    /**
 182     * Set options that update the transport implementation's behavior.
 183     * @param $options
 184     */
 185    public function setOptions($options)
 186    {
 187      $this->options = $options + $this->options;
 188    }
 189  
 190    /**
 191     * Method to handle errors, used for error handling around
 192     * stream connection methods.
 193     */
 194    public function trapError($errno, $errstr)
 195    {
 196      $this->trappedErrorNumber = $errno;
 197      $this->trappedErrorString = $errstr;
 198    }
 199  
 200    /**
 201     * Set the maximum request time in seconds.
 202     * @param $timeout in seconds
 203     */
 204    public function setTimeout($timeout)
 205    {
 206      $this->options[self::TIMEOUT] = $timeout;
 207    }
 208  
 209    /**
 210     * Get the maximum request time in seconds.
 211     * @return timeout in seconds
 212     */
 213    public function getTimeout()
 214    {
 215      return $this->options[self::TIMEOUT];
 216    }
 217  
 218    /**
 219     * Test for the presence of a cURL header processing bug
 220     *
 221     * {@inheritDoc}
 222     *
 223     * @return boolean
 224     */
 225    protected function needsQuirk()
 226    {
 227      return false;
 228    }
 229  
 230    protected function getHttpResponseCode($response_headers)
 231    {
 232      $header_count = count($response_headers);
 233  
 234      for ($i = 0; $i < $header_count; $i++) {
 235        $header = $response_headers[$i];
 236        if (strncasecmp("HTTP", $header, strlen("HTTP")) == 0) {
 237          $response = explode(' ', $header);
 238          return $response[1];
 239        }
 240      }
 241      return self::UNKNOWN_CODE;
 242    }
 243  }