Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.

Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 401 and 402]

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