Subversion Repositories cheapmusic

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
25 - 1
<?php
2
/*
3
 * Copyright 2012 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
 * Implement the caching directives specified in rfc2616. This
19
 * implementation is guided by the guidance offered in rfc2616-sec13.
20
 * @author Chirag Shah <chirags@google.com>
21
 */
22
class Google_CacheParser {
23
  public static $CACHEABLE_HTTP_METHODS = array('GET', 'HEAD');
24
  public static $CACHEABLE_STATUS_CODES = array('200', '203', '300', '301');
25
 
26
  private function __construct() {}
27
 
28
  /**
29
   * Check if an HTTP request can be cached by a private local cache.
30
   *
31
   * @static
32
   * @param Google_HttpRequest $resp
33
   * @return bool True if the request is cacheable.
34
   * False if the request is uncacheable.
35
   */
36
  public static function isRequestCacheable (Google_HttpRequest $resp) {
37
    $method = $resp->getRequestMethod();
38
    if (! in_array($method, self::$CACHEABLE_HTTP_METHODS)) {
39
      return false;
40
    }
41
 
42
    // Don't cache authorized requests/responses.
43
    // [rfc2616-14.8] When a shared cache receives a request containing an
44
    // Authorization field, it MUST NOT return the corresponding response
45
    // as a reply to any other request...
46
    if ($resp->getRequestHeader("authorization")) {
47
      return false;
48
    }
49
 
50
    return true;
51
  }
52
 
53
  /**
54
   * Check if an HTTP response can be cached by a private local cache.
55
   *
56
   * @static
57
   * @param Google_HttpRequest $resp
58
   * @return bool True if the response is cacheable.
59
   * False if the response is un-cacheable.
60
   */
61
  public static function isResponseCacheable (Google_HttpRequest $resp) {
62
    // First, check if the HTTP request was cacheable before inspecting the
63
    // HTTP response.
64
    if (false == self::isRequestCacheable($resp)) {
65
      return false;
66
    }
67
 
68
    $code = $resp->getResponseHttpCode();
69
    if (! in_array($code, self::$CACHEABLE_STATUS_CODES)) {
70
      return false;
71
    }
72
 
73
    // The resource is uncacheable if the resource is already expired and
74
    // the resource doesn't have an ETag for revalidation.
75
    $etag = $resp->getResponseHeader("etag");
76
    if (self::isExpired($resp) && $etag == false) {
77
      return false;
78
    }
79
 
80
    // [rfc2616-14.9.2]  If [no-store is] sent in a response, a cache MUST NOT
81
    // store any part of either this response or the request that elicited it.
82
    $cacheControl = $resp->getParsedCacheControl();
83
    if (isset($cacheControl['no-store'])) {
84
      return false;
85
    }
86
 
87
    // Pragma: no-cache is an http request directive, but is occasionally
88
    // used as a response header incorrectly.
89
    $pragma = $resp->getResponseHeader('pragma');
90
    if ($pragma == 'no-cache' || strpos($pragma, 'no-cache') !== false) {
91
      return false;
92
    }
93
 
94
    // [rfc2616-14.44] Vary: * is extremely difficult to cache. "It implies that
95
    // a cache cannot determine from the request headers of a subsequent request
96
    // whether this response is the appropriate representation."
97
    // Given this, we deem responses with the Vary header as uncacheable.
98
    $vary = $resp->getResponseHeader('vary');
99
    if ($vary) {
100
      return false;
101
    }
102
 
103
    return true;
104
  }
105
 
106
  /**
107
   * @static
108
   * @param Google_HttpRequest $resp
109
   * @return bool True if the HTTP response is considered to be expired.
110
   * False if it is considered to be fresh.
111
   */
112
  public static function isExpired(Google_HttpRequest $resp) {
113
    // HTTP/1.1 clients and caches MUST treat other invalid date formats,
114
    // especially including the value “0”, as in the past.
115
    $parsedExpires = false;
116
    $responseHeaders = $resp->getResponseHeaders();
117
    if (isset($responseHeaders['expires'])) {
118
      $rawExpires = $responseHeaders['expires'];
119
      // Check for a malformed expires header first.
120
      if (empty($rawExpires) || (is_numeric($rawExpires) && $rawExpires <= 0)) {
121
        return true;
122
      }
123
 
124
      // See if we can parse the expires header.
125
      $parsedExpires = strtotime($rawExpires);
126
      if (false == $parsedExpires || $parsedExpires <= 0) {
127
        return true;
128
      }
129
    }
130
 
131
    // Calculate the freshness of an http response.
132
    $freshnessLifetime = false;
133
    $cacheControl = $resp->getParsedCacheControl();
134
    if (isset($cacheControl['max-age'])) {
135
      $freshnessLifetime = $cacheControl['max-age'];
136
    }
137
 
138
    $rawDate = $resp->getResponseHeader('date');
139
    $parsedDate = strtotime($rawDate);
140
 
141
    if (empty($rawDate) || false == $parsedDate) {
142
      $parsedDate = time();
143
    }
144
    if (false == $freshnessLifetime && isset($responseHeaders['expires'])) {
145
      $freshnessLifetime = $parsedExpires - $parsedDate;
146
    }
147
 
148
    if (false == $freshnessLifetime) {
149
      return true;
150
    }
151
 
152
    // Calculate the age of an http response.
153
    $age = max(0, time() - $parsedDate);
154
    if (isset($responseHeaders['age'])) {
155
      $age = max($age, strtotime($responseHeaders['age']));
156
    }
157
 
158
    return $freshnessLifetime <= $age;
159
  }
160
 
161
  /**
162
   * Determine if a cache entry should be revalidated with by the origin.
163
   *
164
   * @param Google_HttpRequest $response
165
   * @return bool True if the entry is expired, else return false.
166
   */
167
  public static function mustRevalidate(Google_HttpRequest $response) {
168
    // [13.3] When a cache has a stale entry that it would like to use as a
169
    // response to a client's request, it first has to check with the origin
170
    // server to see if its cached entry is still usable.
171
    return self::isExpired($response);
172
  }
173
}