+ /**
+ * 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 (
+ // RFC 7231 IMF-fixdate
+ $ts->getTimestamp( TS_RFC2822 ) === $value ||
+ // RFC 850
+ $ts->format( 'l, d-M-y H:i:s' ) . ' GMT' === $value ||
+ // asctime (with and without space-padded day)
+ $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;
+ }
+