/** * checkLastModified tells the client to use the client-cached page if * possible. If successful, the OutputPage is disabled so that * any future call to OutputPage->output() have no effect. * * Side effect: sets mLastModified for Last-Modified header * * @param string $timestamp * * @return bool True if cache-ok headers was sent. */ public function checkLastModified($timestamp) { if (!$timestamp || $timestamp == '19700101000000') { wfDebug(__METHOD__ . ": CACHE DISABLED, NO TIMESTAMP\n"); return false; } $config = $this->getConfig(); if (!$config->get('CachePages')) { wfDebug(__METHOD__ . ": CACHE DISABLED\n"); return false; } $timestamp = wfTimestamp(TS_MW, $timestamp); $modifiedTimes = array('page' => $timestamp, 'user' => $this->getUser()->getTouched(), 'epoch' => $config->get('CacheEpoch')); if ($config->get('UseSquid')) { // bug 44570: the core page itself may not change, but resources might $modifiedTimes['sepoch'] = wfTimestamp(TS_MW, time() - $config->get('SquidMaxage')); } Hooks::run('OutputPageCheckLastModified', array(&$modifiedTimes)); $maxModified = max($modifiedTimes); $this->mLastModified = wfTimestamp(TS_RFC2822, $maxModified); $clientHeader = $this->getRequest()->getHeader('If-Modified-Since'); if ($clientHeader === false) { wfDebug(__METHOD__ . ": client did not send If-Modified-Since header\n", 'log'); return false; } # IE sends sizes after the date like this: # Wed, 20 Aug 2003 06:51:19 GMT; length=5202 # this breaks strtotime(). $clientHeader = preg_replace('/;.*$/', '', $clientHeader); MediaWiki\suppressWarnings(); // E_STRICT system time bitching $clientHeaderTime = strtotime($clientHeader); MediaWiki\restoreWarnings(); if (!$clientHeaderTime) { wfDebug(__METHOD__ . ": unable to parse the client's If-Modified-Since header: {$clientHeader}\n"); return false; } $clientHeaderTime = wfTimestamp(TS_MW, $clientHeaderTime); # Make debug info $info = ''; foreach ($modifiedTimes as $name => $value) { if ($info !== '') { $info .= ', '; } $info .= "{$name}=" . wfTimestamp(TS_ISO_8601, $value); } wfDebug(__METHOD__ . ": client sent If-Modified-Since: " . wfTimestamp(TS_ISO_8601, $clientHeaderTime) . "\n", 'log'); wfDebug(__METHOD__ . ": effective Last-Modified: " . wfTimestamp(TS_ISO_8601, $maxModified) . "\n", 'log'); if ($clientHeaderTime < $maxModified) { wfDebug(__METHOD__ . ": STALE, {$info}\n", 'log'); return false; } # Not modified # Give a 304 Not Modified response code and disable body output wfDebug(__METHOD__ . ": NOT MODIFIED, {$info}\n", 'log'); ini_set('zlib.output_compression', 0); $this->getRequest()->response()->statusHeader(304); $this->sendCacheControl(); $this->disable(); // Don't output a compressed blob when using ob_gzhandler; // it's technically against HTTP spec and seems to confuse // Firefox when the response gets split over two packets. wfClearOutputBuffers(); return true; }
/** * Check selected RFC 7232 precondition headers * * RFC 7232 envisions a particular model where you send your request to "a * resource", and for write requests that you can read "the resource" by * changing the method to GET. When the API receives a GET request, it * works out even though "the resource" from RFC 7232's perspective might * be many resources from MediaWiki's perspective. But it totally fails for * a POST, since what HTTP sees as "the resource" is probably just * "/api.php" with all the interesting bits in the body. * * Therefore, we only support RFC 7232 precondition headers for GET (and * HEAD). That means we don't need to bother with If-Match and * If-Unmodified-Since since they only apply to modification requests. * * And since we don't support Range, If-Range is ignored too. * * @since 1.26 * @param ApiBase $module Api module being used * @return bool True on success, false should exit immediately */ protected function checkConditionalRequestHeaders($module) { if ($this->mInternalMode) { // No headers to check in internal mode return true; } if ($this->getRequest()->getMethod() !== 'GET' && $this->getRequest()->getMethod() !== 'HEAD') { // Don't check POSTs return true; } $return304 = false; $ifNoneMatch = array_diff($this->getRequest()->getHeader('If-None-Match', WebRequest::GETHEADER_LIST) ?: array(), array('')); if ($ifNoneMatch) { if ($ifNoneMatch === array('*')) { // API responses always "exist" $etag = '*'; } else { $etag = $module->getConditionalRequestData('etag'); } } if ($ifNoneMatch && $etag !== null) { $test = substr($etag, 0, 2) === 'W/' ? substr($etag, 2) : $etag; $match = array_map(function ($s) { return substr($s, 0, 2) === 'W/' ? substr($s, 2) : $s; }, $ifNoneMatch); $return304 = in_array($test, $match, true); } else { $value = trim($this->getRequest()->getHeader('If-Modified-Since')); // Some old browsers sends sizes after the date, like this: // Wed, 20 Aug 2003 06:51:19 GMT; length=5202 // Ignore that. $i = strpos($value, ';'); if ($i !== false) { $value = trim(substr($value, 0, $i)); } if ($value !== '') { try { $ts = new MWTimestamp($value); if ($ts->getTimestamp(TS_RFC2822) === $value || $ts->format('l, d-M-y H:i:s') . ' GMT' === $value || $ts->format('D M j H:i:s Y') === $value || $ts->format('D M j H:i:s Y') === $value) { $lastMod = $module->getConditionalRequestData('last-modified'); if ($lastMod !== null) { // Mix in some MediaWiki modification times $modifiedTimes = array('page' => $lastMod, 'user' => $this->getUser()->getTouched(), 'epoch' => $this->getConfig()->get('CacheEpoch')); if ($this->getConfig()->get('UseSquid')) { // T46570: the core page itself may not change, but resources might $modifiedTimes['sepoch'] = wfTimestamp(TS_MW, time() - $this->getConfig()->get('SquidMaxage')); } Hooks::run('OutputPageCheckLastModified', array(&$modifiedTimes)); $lastMod = max($modifiedTimes); $return304 = wfTimestamp(TS_MW, $lastMod) <= $ts->getTimestamp(TS_MW); } } } catch (TimestampException $e) { // Invalid timestamp, ignore it } } } if ($return304) { $this->getRequest()->response()->statusHeader(304); // Avoid outputting the compressed representation of a zero-length body MediaWiki\suppressWarnings(); ini_set('zlib.output_compression', 0); MediaWiki\restoreWarnings(); wfClearOutputBuffers(); return false; } return true; }
/** * checkLastModified tells the client to use the client-cached page if * possible. If sucessful, the OutputPage is disabled so that * any future call to OutputPage->output() have no effect. * * @return bool True iff cache-ok headers was sent. */ function checkLastModified($timestamp) { global $wgCachePages, $wgCacheEpoch, $wgUser, $wgRequest; $fname = 'OutputPage::checkLastModified'; if (!$timestamp || $timestamp == '19700101000000') { wfDebug("{$fname}: CACHE DISABLED, NO TIMESTAMP\n"); return; } if (!$wgCachePages) { wfDebug("{$fname}: CACHE DISABLED\n", false); return; } if ($wgUser->getOption('nocache')) { wfDebug("{$fname}: USER DISABLED CACHE\n", false); return; } $timestamp = wfTimestamp(TS_MW, $timestamp); $lastmod = wfTimestamp(TS_RFC2822, max($timestamp, $wgUser->mTouched, $wgCacheEpoch)); if (!empty($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { # IE sends sizes after the date like this: # Wed, 20 Aug 2003 06:51:19 GMT; length=5202 # this breaks strtotime(). $modsince = preg_replace('/;.*$/', '', $_SERVER["HTTP_IF_MODIFIED_SINCE"]); wfSuppressWarnings(); // E_STRICT system time bitching $modsinceTime = strtotime($modsince); wfRestoreWarnings(); $ismodsince = wfTimestamp(TS_MW, $modsinceTime ? $modsinceTime : 1); wfDebug("{$fname}: -- client send If-Modified-Since: " . $modsince . "\n", false); wfDebug("{$fname}: -- we might send Last-Modified : {$lastmod}\n", false); if ($ismodsince >= $timestamp && $wgUser->validateCache($ismodsince) && $ismodsince >= $wgCacheEpoch) { # Make sure you're in a place you can leave when you call us! $wgRequest->response()->header("HTTP/1.0 304 Not Modified"); $this->mLastModified = $lastmod; $this->sendCacheControl(); wfDebug("{$fname}: CACHED client: {$ismodsince} ; user: {$wgUser->mTouched} ; page: {$timestamp} ; site {$wgCacheEpoch}\n", false); $this->disable(); // Don't output a compressed blob when using ob_gzhandler; // it's technically against HTTP spec and seems to confuse // Firefox when the response gets split over two packets. wfClearOutputBuffers(); return true; } else { wfDebug("{$fname}: READY client: {$ismodsince} ; user: {$wgUser->mTouched} ; page: {$timestamp} ; site {$wgCacheEpoch}\n", false); $this->mLastModified = $lastmod; } } else { wfDebug("{$fname}: client did not send If-Modified-Since header\n", false); $this->mLastModified = $lastmod; } }
/** * checkLastModified tells the client to use the client-cached page if * possible. If sucessful, the OutputPage is disabled so that * any future call to OutputPage->output() have no effect. * * Side effect: sets mLastModified for Last-Modified header * * @return bool True iff cache-ok headers was sent. */ function checkLastModified($timestamp) { global $wgCachePages, $wgCacheEpoch, $wgUser, $wgRequest; if (!$timestamp || $timestamp == '19700101000000') { wfDebug(__METHOD__ . ": CACHE DISABLED, NO TIMESTAMP\n"); return false; } if (!$wgCachePages) { wfDebug(__METHOD__ . ": CACHE DISABLED\n", false); return false; } if ($wgUser->getOption('nocache')) { wfDebug(__METHOD__ . ": USER DISABLED CACHE\n", false); return false; } $timestamp = wfTimestamp(TS_MW, $timestamp); $modifiedTimes = array('page' => $timestamp, 'user' => $wgUser->getTouched(), 'epoch' => $wgCacheEpoch); wfRunHooks('OutputPageCheckLastModified', array(&$modifiedTimes)); $maxModified = max($modifiedTimes); $this->mLastModified = wfTimestamp(TS_RFC2822, $maxModified); if (empty($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { wfDebug(__METHOD__ . ": client did not send If-Modified-Since header\n", false); return false; } # Make debug info $info = ''; foreach ($modifiedTimes as $name => $value) { if ($info !== '') { $info .= ', '; } $info .= "{$name}=" . wfTimestamp(TS_ISO_8601, $value); } # IE sends sizes after the date like this: # Wed, 20 Aug 2003 06:51:19 GMT; length=5202 # this breaks strtotime(). $clientHeader = preg_replace('/;.*$/', '', $_SERVER["HTTP_IF_MODIFIED_SINCE"]); wfSuppressWarnings(); // E_STRICT system time bitching $clientHeaderTime = strtotime($clientHeader); wfRestoreWarnings(); if (!$clientHeaderTime) { wfDebug(__METHOD__ . ": unable to parse the client's If-Modified-Since header: {$clientHeader}\n"); return false; } $clientHeaderTime = wfTimestamp(TS_MW, $clientHeaderTime); wfDebug(__METHOD__ . ": client sent If-Modified-Since: " . wfTimestamp(TS_ISO_8601, $clientHeaderTime) . "\n", false); wfDebug(__METHOD__ . ": effective Last-Modified: " . wfTimestamp(TS_ISO_8601, $maxModified) . "\n", false); if ($clientHeaderTime < $maxModified) { wfDebug(__METHOD__ . ": STALE, {$info}\n", false); return false; } # Not modified # Give a 304 response code and disable body output wfDebug(__METHOD__ . ": NOT MODIFIED, {$info}\n", false); $wgRequest->response()->header("HTTP/1.1 304 Not Modified"); $this->sendCacheControl(); $this->disable(); // Don't output a compressed blob when using ob_gzhandler; // it's technically against HTTP spec and seems to confuse // Firefox when the response gets split over two packets. wfClearOutputBuffers(); return true; }
/** * Sets the response of this request to 304 not modified * Adds required headers and disables body output * * @author macbre / BAC-1521 * @see https://gerrit.wikimedia.org/r/#/c/60440/7/includes/OutputPage.php */ private function setCacheOK() { ini_set('zlib.output_compression', 0); $this->getRequest()->response()->header("HTTP/1.1 304 Not Modified"); $this->sendCacheControl(); $this->disable(); // Don't output a compressed blob when using ob_gzhandler; // it's technically against HTTP spec and seems to confuse // Firefox when the response gets split over two packets. wfClearOutputBuffers(); return true; }