00001 <?php
00013 class HttpCaching {
00014
00019 private $cacheControlDirectives = array();
00023 private $ages = array('max-age' => -1, 's-maxage' => -1);
00027 private $lastModified;
00028 private $eTag;
00029
00044 function sendStatusAndHeaders($die) {
00045 $isFresh = $_SERVER['REQUEST_METHOD'] == "GET" ? $this->isFresh() : false;
00046
00047 if ($isFresh == true) {
00048 header('HTTP/1.1 304 Not Modified');
00049 }
00050 $this->sendHeaders();
00051
00052 if ($isFresh == true && $die == true) {
00053 exit();
00054 }
00055 return $isFresh;
00056 }
00057
00069 function sendHeaders() {
00070
00071 if ($this->ages['max-age'] >= 0) {
00072 header('Expires: ' . self::formatDate(time() + $this->ages['max-age']), 1);
00073 }
00074
00075 if (!is_array($this->cacheControlDirectives)) {
00076 $this->cacheControlDirectives = array();
00077 }
00078 foreach($this->ages as $dir => $value) {
00079 if ($value >= 0) {
00080 array_push($this->cacheControlDirectives, "$dir=$value");
00081 }
00082 }
00083 if (count($this->cacheControlDirectives) > 0) {
00084 header('Cache-Control: ' .
00085 implode(', ', $this->cacheControlDirectives), 1);
00086 }
00087
00088 if ($this->eTag) {
00089 header('ETag: ' . $this->eTag);
00090 }
00091 if ($this->lastModified) {
00092 $lm = $this->lastModified;
00093 } else if (!$this->eTag) {
00094 $lm = time();
00095 }
00096 if ($lm) {
00097 header('Last-Modified: ' . self::formatDate($lm));
00098 }
00099 }
00100
00112 function isFresh() {
00113 if (!isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) &&
00114 !isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
00115
00116 return false;
00117 }
00118
00119 if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
00120 if (!$this->lastModified) {
00121 return false;
00122 }
00123
00124 $ifModifiedSince = explode(';', $_SERVER['HTTP_IF_MODIFIED_SINCE']);
00125
00126 $ifModifiedSince = strtotime($ifModifiedSince[0]);
00127
00128 if ($this->lastModified > $ifModifiedSince) {
00129 return false;
00130 }
00131 }
00132
00133 if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
00134 if ($_SERVER['HTTP_IF_NONE_MATCH'] == '*') {
00135 return true;
00136 }
00137 if (!$this->eTag) {
00138 return false;
00139 }
00140 $etags = preg_split('/,\s*/', $_SERVER['HTTP_IF_NONE_MATCH']);
00141 foreach($etags as $e) {
00142 if ($this->etagMatch($e)) {
00143 return true;
00144 }
00145 }
00146 return false;
00147 }
00148
00149 return true;
00150 }
00151
00157 function etagMatch($etag) {
00158 if (!$this->eTag) {
00159 return false;
00160 }
00161 if ((self::isEtagWeak($this->eTag) || self::isEtagWeak($etag))
00162 &&
00163 ($_SERVER['REQUEST_METHOD'] != "GET" || isset($_SERVER['HTTP_RANGE'])))
00164 {
00165
00166 return false;
00167 }
00168 if (self::etagValidator($this->eTag) == self::etagValidator($etag)) {
00169 return true;
00170 } else {
00171 return false;
00172 }
00173 }
00174
00180 static function isEtagWeak($etag) {
00181 return (substr_compare($etag, 'W/', 0, 2) == 0);
00182 }
00183
00190 static function etagValidator($etag) {
00191 if (self::isEtagWeak($etag)) {
00192 return substr($etag, 2);
00193 } else {
00194 return $etag;
00195 }
00196 }
00197
00205 function getDuration($type) {
00206 if ($type != "max-age" && $type != "s-maxage") {
00207 throw new Exception("Invalid type");
00208 }
00209 return $this->ages[$type];
00210 }
00211
00220 function setDuration($type, $time) {
00221 if ($type != "max-age" && $type != "s-maxage") {
00222 throw new Exception("Invalid type");
00223 }
00224 if (is_numeric($time)) {
00225 $time = intval($time);
00226 if ($time < 0) {
00227 $time = -1;
00228 }
00229 } else {
00230 if ($time == 'now') {
00231 $time = 0;
00232 } else {
00233 $time = strtotime($time) - time();
00234 }
00235 if ($time < 0) {
00236 throw new Exception("Bad interval specified to strtotime()");
00237 }
00238 }
00239 return $this->ages[$type] = $time;
00240 }
00241
00249 function freshFor($time) {
00250 return $this->setDuration('max-age', $time);
00251 }
00252
00258 function setCacheControlDirective($type, $set) {
00259 if ($set == true) {
00260 if (!in_array($type, $this->cacheControlDirectives)) {
00261 array_push($this->cacheControlDirectives, $type);
00262 }
00263 } else {
00264 $this->cacheControlDirectives = array_diff($this->cacheControlDirectives,
00265 array($type));
00266 }
00267 }
00268
00273 function ccPublic($set) {
00274 $this->setCacheControlDirective('public', $set);
00275 }
00276
00281 function ccNoCache($set) {
00282 $this->setCacheControlDirective('no-cache', $set);
00283 }
00284
00289 function ccNoStore($set) {
00290 $this->setCacheControlDirective('no-store', $set);
00291 }
00292
00297 function ccMustRevalidate($set) {
00298 $this->setCacheControlDirective('must-revalidate', $set);
00299 }
00300
00305 function ccProxyRevalidate($set) {
00306 $this->setCacheControlDirective('proxy-revalidate', $set);
00307 }
00308
00315 function etag($value) {
00316 $this->eTag = '"' . $value . '"';
00317 }
00318
00325 function weakEtag($value) {
00326 $this->etag($value);
00327 $this->eTag = "W/" . $this->eTag;
00328 }
00329
00334 function lastModified($value) {
00335 $this->lastModified = $value;
00336 }
00337
00343 function setLastModifiedFromFile($file) {
00344 $finfo = stat($file);
00345 if (!$finfo) {
00346 return;
00347 }
00348 $this->lastModified($finfo['mtime']);
00349 }
00350
00357 static function sendHeadersSpecifyingFreshness($time) {
00358 $hc = new HttpCaching();
00359 $hc->freshFor($time);
00360 $hc->ccMustRevalidate(true);
00361 $hc->sendHeaders();
00362 }
00363
00369 static function formatDate($time) {
00370 return gmdate("D, d M Y H:i:s", $time) . ' GMT';
00371 }
00372 }
00373
00403 ?>