Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
 * Abstract IO base class
20
 */
21
 
22
if (!class_exists('Google_Client')) {
23
  require_once dirname(__FILE__) . '/../autoload.php';
24
}
25
 
26
abstract class Google_IO_Abstract
27
{
28
  const UNKNOWN_CODE = 0;
29
  const FORM_URLENCODED = 'application/x-www-form-urlencoded';
30
  private static $CONNECTION_ESTABLISHED_HEADERS = array(
31
    "HTTP/1.0 200 Connection established\r\n\r\n",
32
    "HTTP/1.1 200 Connection established\r\n\r\n",
33
  );
34
  private static $ENTITY_HTTP_METHODS = array("POST" => null, "PUT" => null);
35
  private static $HOP_BY_HOP = array(
36
    'connection' => true,
37
    'keep-alive' => true,
38
    'proxy-authenticate' => true,
39
    'proxy-authorization' => true,
40
    'te' => true,
41
    'trailers' => true,
42
    'transfer-encoding' => true,
43
    'upgrade' => true
44
  );
45
 
46
 
47
  /** @var Google_Client */
48
  protected $client;
49
 
50
  public function __construct(Google_Client $client)
51
  {
52
    $this->client = $client;
53
    $timeout = $client->getClassConfig('Google_IO_Abstract', 'request_timeout_seconds');
54
    if ($timeout > 0) {
55
      $this->setTimeout($timeout);
56
    }
57
  }
58
 
59
  /**
60
   * Executes a Google_Http_Request
61
   * @param Google_Http_Request $request the http request to be executed
62
   * @return array containing response headers, body, and http code
63
   * @throws Google_IO_Exception on curl or IO error
64
   */
65
  abstract public function executeRequest(Google_Http_Request $request);
66
 
67
  /**
68
   * Set options that update the transport implementation's behavior.
69
   * @param $options
70
   */
71
  abstract public function setOptions($options);
72
 
73
  /**
74
   * Set the maximum request time in seconds.
75
   * @param $timeout in seconds
76
   */
77
  abstract public function setTimeout($timeout);
78
 
79
  /**
80
   * Get the maximum request time in seconds.
81
   * @return timeout in seconds
82
   */
83
  abstract public function getTimeout();
84
 
85
  /**
86
   * Test for the presence of a cURL header processing bug
87
   *
88
   * The cURL bug was present in versions prior to 7.30.0 and caused the header
89
   * length to be miscalculated when a "Connection established" header added by
90
   * some proxies was present.
91
   *
92
   * @return boolean
93
   */
94
  abstract protected function needsQuirk();
95
 
96
  /**
97
   * @visible for testing.
98
   * Cache the response to an HTTP request if it is cacheable.
99
   * @param Google_Http_Request $request
100
   * @return bool Returns true if the insertion was successful.
101
   * Otherwise, return false.
102
   */
103
  public function setCachedRequest(Google_Http_Request $request)
104
  {
105
    // Determine if the request is cacheable.
106
    if (Google_Http_CacheParser::isResponseCacheable($request)) {
107
      $this->client->getCache()->set($request->getCacheKey(), $request);
108
      return true;
109
    }
110
 
111
    return false;
112
  }
113
 
114
  /**
115
   * Execute an HTTP Request
116
   *
117
   * @param Google_Http_Request $request the http request to be executed
118
   * @return Google_Http_Request http request with the response http code,
119
   * response headers and response body filled in
120
   * @throws Google_IO_Exception on curl or IO error
121
   */
122
  public function makeRequest(Google_Http_Request $request)
123
  {
124
    // First, check to see if we have a valid cached version.
125
    $cached = $this->getCachedRequest($request);
126
    if ($cached !== false && $cached instanceof Google_Http_Request) {
127
      if (!$this->checkMustRevalidateCachedRequest($cached, $request)) {
128
        return $cached;
129
      }
130
    }
131
 
132
    if (array_key_exists($request->getRequestMethod(), self::$ENTITY_HTTP_METHODS)) {
133
      $request = $this->processEntityRequest($request);
134
    }
135
 
136
    list($responseData, $responseHeaders, $respHttpCode) = $this->executeRequest($request);
137
 
138
    if ($respHttpCode == 304 && $cached) {
139
      // If the server responded NOT_MODIFIED, return the cached request.
140
      $this->updateCachedRequest($cached, $responseHeaders);
141
      return $cached;
142
    }
143
 
144
    if (!isset($responseHeaders['Date']) && !isset($responseHeaders['date'])) {
145
      $responseHeaders['date'] = date("r");
146
    }
147
 
148
    $request->setResponseHttpCode($respHttpCode);
149
    $request->setResponseHeaders($responseHeaders);
150
    $request->setResponseBody($responseData);
151
    // Store the request in cache (the function checks to see if the request
152
    // can actually be cached)
153
    $this->setCachedRequest($request);
154
    return $request;
155
  }
156
 
157
  /**
158
   * @visible for testing.
159
   * @param Google_Http_Request $request
160
   * @return Google_Http_Request|bool Returns the cached object or
161
   * false if the operation was unsuccessful.
162
   */
163
  public function getCachedRequest(Google_Http_Request $request)
164
  {
165
    if (false === Google_Http_CacheParser::isRequestCacheable($request)) {
166
      return false;
167
    }
168
 
169
    return $this->client->getCache()->get($request->getCacheKey());
170
  }
171
 
172
  /**
173
   * @visible for testing
174
   * Process an http request that contains an enclosed entity.
175
   * @param Google_Http_Request $request
176
   * @return Google_Http_Request Processed request with the enclosed entity.
177
   */
178
  public function processEntityRequest(Google_Http_Request $request)
179
  {
180
    $postBody = $request->getPostBody();
181
    $contentType = $request->getRequestHeader("content-type");
182
 
183
    // Set the default content-type as application/x-www-form-urlencoded.
184
    if (false == $contentType) {
185
      $contentType = self::FORM_URLENCODED;
186
      $request->setRequestHeaders(array('content-type' => $contentType));
187
    }
188
 
189
    // Force the payload to match the content-type asserted in the header.
190
    if ($contentType == self::FORM_URLENCODED && is_array($postBody)) {
191
      $postBody = http_build_query($postBody, '', '&');
192
      $request->setPostBody($postBody);
193
    }
194
 
195
    // Make sure the content-length header is set.
196
    if (!$postBody || is_string($postBody)) {
197
      $postsLength = strlen($postBody);
198
      $request->setRequestHeaders(array('content-length' => $postsLength));
199
    }
200
 
201
    return $request;
202
  }
203
 
204
  /**
205
   * Check if an already cached request must be revalidated, and if so update
206
   * the request with the correct ETag headers.
207
   * @param Google_Http_Request $cached A previously cached response.
208
   * @param Google_Http_Request $request The outbound request.
209
   * return bool If the cached object needs to be revalidated, false if it is
210
   * still current and can be re-used.
211
   */
212
  protected function checkMustRevalidateCachedRequest($cached, $request)
213
  {
214
    if (Google_Http_CacheParser::mustRevalidate($cached)) {
215
      $addHeaders = array();
216
      if ($cached->getResponseHeader('etag')) {
217
        // [13.3.4] If an entity tag has been provided by the origin server,
218
        // we must use that entity tag in any cache-conditional request.
219
        $addHeaders['If-None-Match'] = $cached->getResponseHeader('etag');
220
      } elseif ($cached->getResponseHeader('date')) {
221
        $addHeaders['If-Modified-Since'] = $cached->getResponseHeader('date');
222
      }
223
 
224
      $request->setRequestHeaders($addHeaders);
225
      return true;
226
    } else {
227
      return false;
228
    }
229
  }
230
 
231
  /**
232
   * Update a cached request, using the headers from the last response.
233
   * @param Google_Http_Request $cached A previously cached response.
234
   * @param mixed Associative array of response headers from the last request.
235
   */
236
  protected function updateCachedRequest($cached, $responseHeaders)
237
  {
238
    $hopByHop = self::$HOP_BY_HOP;
239
    if (!empty($responseHeaders['connection'])) {
240
      $connectionHeaders = array_map(
241
          'strtolower',
242
          array_filter(
243
              array_map('trim', explode(',', $responseHeaders['connection']))
244
          )
245
      );
246
      $hopByHop += array_fill_keys($connectionHeaders, true);
247
    }
248
 
249
    $endToEnd = array_diff_key($responseHeaders, $hopByHop);
250
    $cached->setResponseHeaders($endToEnd);
251
  }
252
 
253
  /**
254
   * Used by the IO lib and also the batch processing.
255
   *
256
   * @param $respData
257
   * @param $headerSize
258
   * @return array
259
   */
260
  public function parseHttpResponse($respData, $headerSize)
261
  {
262
    // check proxy header
263
    foreach (self::$CONNECTION_ESTABLISHED_HEADERS as $established_header) {
264
      if (stripos($respData, $established_header) !== false) {
265
        // existed, remove it
266
        $respData = str_ireplace($established_header, '', $respData);
267
        // Subtract the proxy header size unless the cURL bug prior to 7.30.0
268
        // is present which prevented the proxy header size from being taken into
269
        // account.
270
        if (!$this->needsQuirk()) {
271
          $headerSize -= strlen($established_header);
272
        }
273
        break;
274
      }
275
    }
276
 
277
    if ($headerSize) {
278
      $responseBody = substr($respData, $headerSize);
279
      $responseHeaders = substr($respData, 0, $headerSize);
280
    } else {
281
      $responseSegments = explode("\r\n\r\n", $respData, 2);
282
      $responseHeaders = $responseSegments[0];
283
      $responseBody = isset($responseSegments[1]) ? $responseSegments[1] :
284
                                                    null;
285
    }
286
 
287
    $responseHeaders = $this->getHttpResponseHeaders($responseHeaders);
288
    return array($responseHeaders, $responseBody);
289
  }
290
 
291
  /**
292
   * Parse out headers from raw headers
293
   * @param rawHeaders array or string
294
   * @return array
295
   */
296
  public function getHttpResponseHeaders($rawHeaders)
297
  {
298
    if (is_array($rawHeaders)) {
299
      return $this->parseArrayHeaders($rawHeaders);
300
    } else {
301
      return $this->parseStringHeaders($rawHeaders);
302
    }
303
  }
304
 
305
  private function parseStringHeaders($rawHeaders)
306
  {
307
    $headers = array();
308
    $responseHeaderLines = explode("\r\n", $rawHeaders);
309
    foreach ($responseHeaderLines as $headerLine) {
310
      if ($headerLine && strpos($headerLine, ':') !== false) {
311
        list($header, $value) = explode(': ', $headerLine, 2);
312
        $header = strtolower($header);
313
        if (isset($headers[$header])) {
314
          $headers[$header] .= "\n" . $value;
315
        } else {
316
          $headers[$header] = $value;
317
        }
318
      }
319
    }
320
    return $headers;
321
  }
322
 
323
  private function parseArrayHeaders($rawHeaders)
324
  {
325
    $header_count = count($rawHeaders);
326
    $headers = array();
327
 
328
    for ($i = 0; $i < $header_count; $i++) {
329
      $header = $rawHeaders[$i];
330
      // Times will have colons in - so we just want the first match.
331
      $header_parts = explode(': ', $header, 2);
332
      if (count($header_parts) == 2) {
333
        $headers[strtolower($header_parts[0])] = $header_parts[1];
334
      }
335
    }
336
 
337
    return $headers;
338
  }
339
}