/** * 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; }