|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00063 class HTTP_ConditionalGet { 00064 00073 public $cacheIsValid = null; 00074 00112 public function __construct($spec) 00113 { 00114 $scope = (isset($spec['isPublic']) && $spec['isPublic']) 00115 ? 'public' 00116 : 'private'; 00117 $maxAge = 0; 00118 // backwards compatibility (can be removed later) 00119 if (isset($spec['setExpires']) 00120 && is_numeric($spec['setExpires']) 00121 && ! isset($spec['maxAge'])) { 00122 $spec['maxAge'] = $spec['setExpires'] - $_SERVER['REQUEST_TIME']; 00123 } 00124 if (isset($spec['maxAge'])) { 00125 $maxAge = $spec['maxAge']; 00126 $this->_headers['Expires'] = self::gmtDate( 00127 $_SERVER['REQUEST_TIME'] + $spec['maxAge'] 00128 ); 00129 } 00130 $etagAppend = ''; 00131 if (isset($spec['encoding'])) { 00132 $this->_stripEtag = true; 00133 $this->_headers['Vary'] = 'Accept-Encoding'; 00134 if ('' !== $spec['encoding']) { 00135 if (0 === strpos($spec['encoding'], 'x-')) { 00136 $spec['encoding'] = substr($spec['encoding'], 2); 00137 } 00138 $etagAppend = ';' . substr($spec['encoding'], 0, 2); 00139 } 00140 } 00141 if (isset($spec['lastModifiedTime'])) { 00142 $this->_setLastModified($spec['lastModifiedTime']); 00143 if (isset($spec['eTag'])) { // Use it 00144 $this->_setEtag($spec['eTag'], $scope); 00145 } else { // base both headers on time 00146 $this->_setEtag($spec['lastModifiedTime'] . $etagAppend, $scope); 00147 } 00148 } elseif (isset($spec['eTag'])) { // Use it 00149 $this->_setEtag($spec['eTag'], $scope); 00150 } elseif (isset($spec['contentHash'])) { // Use the hash as the ETag 00151 $this->_setEtag($spec['contentHash'] . $etagAppend, $scope); 00152 } 00153 $this->_headers['Cache-Control'] = "max-age={$maxAge}, {$scope}"; 00154 // invalidate cache if disabled, otherwise check 00155 $this->cacheIsValid = (isset($spec['invalidate']) && $spec['invalidate']) 00156 ? false 00157 : $this->_isCacheValid(); 00158 } 00159 00176 public function getHeaders() 00177 { 00178 return $this->_headers; 00179 } 00180 00192 public function setContentLength($bytes) 00193 { 00194 return $this->_headers['Content-Length'] = $bytes; 00195 } 00196 00208 public function sendHeaders() 00209 { 00210 $headers = $this->_headers; 00211 if (array_key_exists('_responseCode', $headers)) { 00212 header($headers['_responseCode']); 00213 unset($headers['_responseCode']); 00214 } 00215 foreach ($headers as $name => $val) { 00216 header($name . ': ' . $val); 00217 } 00218 } 00219 00236 public static function check($lastModifiedTime = null, $isPublic = false, $options = array()) 00237 { 00238 if (null !== $lastModifiedTime) { 00239 $options['lastModifiedTime'] = (int)$lastModifiedTime; 00240 } 00241 $options['isPublic'] = (bool)$isPublic; 00242 $cg = new HTTP_ConditionalGet($options); 00243 $cg->sendHeaders(); 00244 if ($cg->cacheIsValid) { 00245 exit(); 00246 } 00247 } 00248 00249 00261 public static function gmtDate($time) 00262 { 00263 return gmdate('D, d M Y H:i:s \G\M\T', $time); 00264 } 00265 00266 protected $_headers = array(); 00267 protected $_lmTime = null; 00268 protected $_etag = null; 00269 protected $_stripEtag = false; 00270 00271 protected function _setEtag($hash, $scope) 00272 { 00273 $this->_etag = '"' . substr($scope, 0, 3) . $hash . '"'; 00274 $this->_headers['ETag'] = $this->_etag; 00275 } 00276 00277 protected function _setLastModified($time) 00278 { 00279 $this->_lmTime = (int)$time; 00280 $this->_headers['Last-Modified'] = self::gmtDate($time); 00281 } 00282 00286 protected function _isCacheValid() 00287 { 00288 if (null === $this->_etag) { 00289 // lmTime is copied to ETag, so this condition implies that the 00290 // server sent neither ETag nor Last-Modified, so the client can't 00291 // possibly has a valid cache. 00292 return false; 00293 } 00294 $isValid = ($this->resourceMatchedEtag() || $this->resourceNotModified()); 00295 if ($isValid) { 00296 $this->_headers['_responseCode'] = 'HTTP/1.0 304 Not Modified'; 00297 } 00298 return $isValid; 00299 } 00300 00301 protected function resourceMatchedEtag() 00302 { 00303 if (!isset($_SERVER['HTTP_IF_NONE_MATCH'])) { 00304 return false; 00305 } 00306 $clientEtagList = get_magic_quotes_gpc() 00307 ? stripslashes($_SERVER['HTTP_IF_NONE_MATCH']) 00308 : $_SERVER['HTTP_IF_NONE_MATCH']; 00309 $clientEtags = explode(',', $clientEtagList); 00310 00311 $compareTo = $this->normalizeEtag($this->_etag); 00312 foreach ($clientEtags as $clientEtag) { 00313 if ($this->normalizeEtag($clientEtag) === $compareTo) { 00314 // respond with the client's matched ETag, even if it's not what 00315 // we would've sent by default 00316 $this->_headers['ETag'] = trim($clientEtag); 00317 return true; 00318 } 00319 } 00320 return false; 00321 } 00322 00323 protected function normalizeEtag($etag) { 00324 $etag = trim($etag); 00325 return $this->_stripEtag 00326 ? preg_replace('/;\\w\\w"$/', '"', $etag) 00327 : $etag; 00328 } 00329 00330 protected function resourceNotModified() 00331 { 00332 if (!isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { 00333 return false; 00334 } 00335 $ifModifiedSince = $_SERVER['HTTP_IF_MODIFIED_SINCE']; 00336 if (false !== ($semicolon = strrpos($ifModifiedSince, ';'))) { 00337 // IE has tacked on extra data to this header, strip it 00338 $ifModifiedSince = substr($ifModifiedSince, 0, $semicolon); 00339 } 00340 if ($ifModifiedSince == self::gmtDate($this->_lmTime)) { 00341 // Apache 2.2's behavior. If there was no ETag match, send the 00342 // non-encoded version of the ETag value. 00343 $this->_headers['ETag'] = $this->normalizeEtag($this->_etag); 00344 return true; 00345 } 00346 return false; 00347 } 00348 }