Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

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

   1  <?php
   2  /*
   3   * Copyright 2010 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  if (!class_exists('Google_Client')) {
  19    require_once dirname(__FILE__) . '/../autoload.php';
  20  }
  21  
  22  /**
  23   * HTTP Request to be executed by IO classes. Upon execution, the
  24   * responseHttpCode, responseHeaders and responseBody will be filled in.
  25   *
  26   * @author Chris Chabot <chabotc@google.com>
  27   * @author Chirag Shah <chirags@google.com>
  28   *
  29   */
  30  class Google_Http_Request
  31  {
  32    const GZIP_UA = " (gzip)";
  33  
  34    private $batchHeaders = array(
  35      'Content-Type' => 'application/http',
  36      'Content-Transfer-Encoding' => 'binary',
  37      'MIME-Version' => '1.0',
  38    );
  39  
  40    protected $queryParams;
  41    protected $requestMethod;
  42    protected $requestHeaders;
  43    protected $baseComponent = null;
  44    protected $path;
  45    protected $postBody;
  46    protected $userAgent;
  47    protected $canGzip = null;
  48  
  49    protected $responseHttpCode;
  50    protected $responseHeaders;
  51    protected $responseBody;
  52    
  53    protected $expectedClass;
  54    protected $expectedRaw = false;
  55  
  56    public $accessKey;
  57  
  58    public function __construct(
  59        $url,
  60        $method = 'GET',
  61        $headers = array(),
  62        $postBody = null
  63    ) {
  64      $this->setUrl($url);
  65      $this->setRequestMethod($method);
  66      $this->setRequestHeaders($headers);
  67      $this->setPostBody($postBody);
  68    }
  69  
  70    /**
  71     * Misc function that returns the base url component of the $url
  72     * used by the OAuth signing class to calculate the base string
  73     * @return string The base url component of the $url.
  74     */
  75    public function getBaseComponent()
  76    {
  77      return $this->baseComponent;
  78    }
  79    
  80    /**
  81     * Set the base URL that path and query parameters will be added to.
  82     * @param $baseComponent string
  83     */
  84    public function setBaseComponent($baseComponent)
  85    {
  86      $this->baseComponent = rtrim($baseComponent, '/');
  87    }
  88    
  89    /**
  90     * Enable support for gzipped responses with this request.
  91     */
  92    public function enableGzip()
  93    {
  94      $this->setRequestHeaders(array("Accept-Encoding" => "gzip"));
  95      $this->canGzip = true;
  96      $this->setUserAgent($this->userAgent);
  97    }
  98    
  99    /**
 100     * Disable support for gzip responses with this request.
 101     */
 102    public function disableGzip()
 103    {
 104      if (
 105          isset($this->requestHeaders['accept-encoding']) &&
 106          $this->requestHeaders['accept-encoding'] == "gzip"
 107      ) {
 108        unset($this->requestHeaders['accept-encoding']);
 109      }
 110      $this->canGzip = false;
 111      $this->userAgent = str_replace(self::GZIP_UA, "", $this->userAgent);
 112    }
 113    
 114    /**
 115     * Can this request accept a gzip response?
 116     * @return bool
 117     */
 118    public function canGzip()
 119    {
 120      return $this->canGzip;
 121    }
 122  
 123    /**
 124     * Misc function that returns an array of the query parameters of the current
 125     * url used by the OAuth signing class to calculate the signature
 126     * @return array Query parameters in the query string.
 127     */
 128    public function getQueryParams()
 129    {
 130      return $this->queryParams;
 131    }
 132  
 133    /**
 134     * Set a new query parameter.
 135     * @param $key - string to set, does not need to be URL encoded
 136     * @param $value - string to set, does not need to be URL encoded
 137     */
 138    public function setQueryParam($key, $value)
 139    {
 140      $this->queryParams[$key] = $value;
 141    }
 142  
 143    /**
 144     * @return string HTTP Response Code.
 145     */
 146    public function getResponseHttpCode()
 147    {
 148      return (int) $this->responseHttpCode;
 149    }
 150  
 151    /**
 152     * @param int $responseHttpCode HTTP Response Code.
 153     */
 154    public function setResponseHttpCode($responseHttpCode)
 155    {
 156      $this->responseHttpCode = $responseHttpCode;
 157    }
 158  
 159    /**
 160     * @return $responseHeaders (array) HTTP Response Headers.
 161     */
 162    public function getResponseHeaders()
 163    {
 164      return $this->responseHeaders;
 165    }
 166  
 167    /**
 168     * @return string HTTP Response Body
 169     */
 170    public function getResponseBody()
 171    {
 172      return $this->responseBody;
 173    }
 174    
 175    /**
 176     * Set the class the response to this request should expect.
 177     *
 178     * @param $class string the class name
 179     */
 180    public function setExpectedClass($class)
 181    {
 182      $this->expectedClass = $class;
 183    }
 184    
 185    /**
 186     * Retrieve the expected class the response should expect.
 187     * @return string class name
 188     */
 189    public function getExpectedClass()
 190    {
 191      return $this->expectedClass;
 192    }
 193  
 194    /**
 195     * Enable expected raw response
 196     */
 197    public function enableExpectedRaw()
 198    {
 199      $this->expectedRaw = true;
 200    }
 201  
 202    /**
 203     * Disable expected raw response
 204     */
 205    public function disableExpectedRaw()
 206    {
 207      $this->expectedRaw = false;
 208    }
 209  
 210    /**
 211     * Expected raw response or not.
 212     * @return boolean expected raw response
 213     */
 214    public function getExpectedRaw()
 215    {
 216      return $this->expectedRaw;
 217    }
 218  
 219    /**
 220     * @param array $headers The HTTP response headers
 221     * to be normalized.
 222     */
 223    public function setResponseHeaders($headers)
 224    {
 225      $headers = Google_Utils::normalize($headers);
 226      if ($this->responseHeaders) {
 227        $headers = array_merge($this->responseHeaders, $headers);
 228      }
 229  
 230      $this->responseHeaders = $headers;
 231    }
 232  
 233    /**
 234     * @param string $key
 235     * @return array|boolean Returns the requested HTTP header or
 236     * false if unavailable.
 237     */
 238    public function getResponseHeader($key)
 239    {
 240      return isset($this->responseHeaders[$key])
 241          ? $this->responseHeaders[$key]
 242          : false;
 243    }
 244  
 245    /**
 246     * @param string $responseBody The HTTP response body.
 247     */
 248    public function setResponseBody($responseBody)
 249    {
 250      $this->responseBody = $responseBody;
 251    }
 252  
 253    /**
 254     * @return string $url The request URL.
 255     */
 256    public function getUrl()
 257    {
 258      return $this->baseComponent . $this->path .
 259          (count($this->queryParams) ?
 260              "?" . $this->buildQuery($this->queryParams) :
 261              '');
 262    }
 263  
 264    /**
 265     * @return string $method HTTP Request Method.
 266     */
 267    public function getRequestMethod()
 268    {
 269      return $this->requestMethod;
 270    }
 271  
 272    /**
 273     * @return array $headers HTTP Request Headers.
 274     */
 275    public function getRequestHeaders()
 276    {
 277      return $this->requestHeaders;
 278    }
 279  
 280    /**
 281     * @param string $key
 282     * @return array|boolean Returns the requested HTTP header or
 283     * false if unavailable.
 284     */
 285    public function getRequestHeader($key)
 286    {
 287      return isset($this->requestHeaders[$key])
 288          ? $this->requestHeaders[$key]
 289          : false;
 290    }
 291  
 292    /**
 293     * @return string $postBody HTTP Request Body.
 294     */
 295    public function getPostBody()
 296    {
 297      return $this->postBody;
 298    }
 299  
 300    /**
 301     * @param string $url the url to set
 302     */
 303    public function setUrl($url)
 304    {
 305      if (substr($url, 0, 4) != 'http') {
 306        // Force the path become relative.
 307        if (substr($url, 0, 1) !== '/') {
 308          $url = '/' . $url;
 309        }
 310      }
 311      $parts = parse_url($url);
 312      if (isset($parts['host'])) {
 313        $this->baseComponent = sprintf(
 314            "%s%s%s",
 315            isset($parts['scheme']) ? $parts['scheme'] . "://" : '',
 316            isset($parts['host']) ? $parts['host'] : '',
 317            isset($parts['port']) ? ":" . $parts['port'] : ''
 318        );
 319      }
 320      $this->path = isset($parts['path']) ? $parts['path'] : '';
 321      $this->queryParams = array();
 322      if (isset($parts['query'])) {
 323        $this->queryParams = $this->parseQuery($parts['query']);
 324      }
 325    }
 326  
 327    /**
 328     * @param string $method Set he HTTP Method and normalize
 329     * it to upper-case, as required by HTTP.
 330     *
 331     */
 332    public function setRequestMethod($method)
 333    {
 334      $this->requestMethod = strtoupper($method);
 335    }
 336  
 337    /**
 338     * @param array $headers The HTTP request headers
 339     * to be set and normalized.
 340     */
 341    public function setRequestHeaders($headers)
 342    {
 343      $headers = Google_Utils::normalize($headers);
 344      if ($this->requestHeaders) {
 345        $headers = array_merge($this->requestHeaders, $headers);
 346      }
 347      $this->requestHeaders = $headers;
 348    }
 349  
 350    /**
 351     * @param string $postBody the postBody to set
 352     */
 353    public function setPostBody($postBody)
 354    {
 355      $this->postBody = $postBody;
 356    }
 357  
 358    /**
 359     * Set the User-Agent Header.
 360     * @param string $userAgent The User-Agent.
 361     */
 362    public function setUserAgent($userAgent)
 363    {
 364      $this->userAgent = $userAgent;
 365      if ($this->canGzip) {
 366        $this->userAgent = $userAgent . self::GZIP_UA;
 367      }
 368    }
 369  
 370    /**
 371     * @return string The User-Agent.
 372     */
 373    public function getUserAgent()
 374    {
 375      return $this->userAgent;
 376    }
 377  
 378    /**
 379     * Returns a cache key depending on if this was an OAuth signed request
 380     * in which case it will use the non-signed url and access key to make this
 381     * cache key unique per authenticated user, else use the plain request url
 382     * @return string The md5 hash of the request cache key.
 383     */
 384    public function getCacheKey()
 385    {
 386      $key = $this->getUrl();
 387  
 388      if (isset($this->accessKey)) {
 389        $key .= $this->accessKey;
 390      }
 391  
 392      if (isset($this->requestHeaders['authorization'])) {
 393        $key .= $this->requestHeaders['authorization'];
 394      }
 395  
 396      return md5($key);
 397    }
 398  
 399    public function getParsedCacheControl()
 400    {
 401      $parsed = array();
 402      $rawCacheControl = $this->getResponseHeader('cache-control');
 403      if ($rawCacheControl) {
 404        $rawCacheControl = str_replace(', ', '&', $rawCacheControl);
 405        parse_str($rawCacheControl, $parsed);
 406      }
 407  
 408      return $parsed;
 409    }
 410  
 411    /**
 412     * @param string $id
 413     * @return string A string representation of the HTTP Request.
 414     */
 415    public function toBatchString($id)
 416    {
 417      $str = '';
 418      $path = parse_url($this->getUrl(), PHP_URL_PATH) . "?" .
 419          http_build_query($this->queryParams);
 420      $str .= $this->getRequestMethod() . ' ' . $path . " HTTP/1.1\n";
 421  
 422      foreach ($this->getRequestHeaders() as $key => $val) {
 423        $str .= $key . ': ' . $val . "\n";
 424      }
 425  
 426      if ($this->getPostBody()) {
 427        $str .= "\n";
 428        $str .= $this->getPostBody();
 429      }
 430      
 431      $headers = '';
 432      foreach ($this->batchHeaders as $key => $val) {
 433        $headers .= $key . ': ' . $val . "\n";
 434      }
 435  
 436      $headers .= "Content-ID: $id\n";
 437      $str = $headers . "\n" . $str;
 438  
 439      return $str;
 440    }
 441    
 442    /**
 443     * Our own version of parse_str that allows for multiple variables
 444     * with the same name.
 445     * @param $string - the query string to parse
 446     */
 447    private function parseQuery($string)
 448    {
 449      $return = array();
 450      $parts = explode("&", $string);
 451      foreach ($parts as $part) {
 452        list($key, $value) = explode('=', $part, 2);
 453        $value = urldecode($value);
 454        if (isset($return[$key])) {
 455          if (!is_array($return[$key])) {
 456            $return[$key] = array($return[$key]);
 457          }
 458          $return[$key][] = $value;
 459        } else {
 460          $return[$key] = $value;
 461        }
 462      }
 463      return $return;
 464    }
 465    
 466    /**
 467     * A version of build query that allows for multiple
 468     * duplicate keys.
 469     * @param $parts array of key value pairs
 470     */
 471    private function buildQuery($parts)
 472    {
 473      $return = array();
 474      foreach ($parts as $key => $value) {
 475        if (is_array($value)) {
 476          foreach ($value as $v) {
 477            $return[] = urlencode($key) . "=" . urlencode($v);
 478          }
 479        } else {
 480          $return[] = urlencode($key) . "=" . urlencode($value);
 481        }
 482      }
 483      return implode('&', $return);
 484    }
 485    
 486    /**
 487     * If we're POSTing and have no body to send, we can send the query
 488     * parameters in there, which avoids length issues with longer query
 489     * params.
 490     */
 491    public function maybeMoveParametersToBody()
 492    {
 493      if ($this->getRequestMethod() == "POST" && empty($this->postBody)) {
 494        $this->setRequestHeaders(
 495            array(
 496              "content-type" =>
 497                  "application/x-www-form-urlencoded; charset=UTF-8"
 498            )
 499        );
 500        $this->setPostBody($this->buildQuery($this->queryParams));
 501        $this->queryParams = array();
 502      }
 503    }
 504  }