Fix RevDel_RevisionItem::getAuthorNameField to work for ips
[lhc/web/wiklou.git] / thumb.php
index b0d9f10..707f1e2 100644 (file)
--- a/thumb.php
+++ b/thumb.php
@@ -36,6 +36,10 @@ if ( defined( 'THUMB_HANDLER' ) ) {
 }
 
 wfLogProfilingData();
+// Commit and close up!
+$factory = wfGetLBFactory();
+$factory->commitMasterChanges();
+$factory->shutdown();
 
 //--------------------------------------------------------------------------
 
@@ -88,7 +92,7 @@ function wfThumbHandle404() {
 /**
  * Stream a thumbnail specified by parameters
  *
- * @param $params Array List of thumbnailing parameters. In addition to parameters
+ * @param array $params List of thumbnailing parameters. In addition to parameters
  *  passed to the MediaHandler, this may also includes the keys:
  *   f (for filename), archived (if archived file), temp (if temp file),
  *   w (alias for width), p (alias for page), r (ignored; historical),
@@ -159,6 +163,12 @@ function wfStreamThumb( array $params ) {
                return;
        }
 
+       // Check if the file is hidden
+       if ( $img->isDeleted( File::DELETED_FILE ) ) {
+               wfThumbError( 404, "The source file '$fileName' does not exist." );
+               return;
+       }
+
        // Check permissions if there are read restrictions
        $varyHeader = array();
        if ( !in_array( 'read', User::getGroupPermissions( array( '*' ) ), true ) ) {
@@ -243,8 +253,10 @@ function wfStreamThumb( array $params ) {
                }
        }
 
+       $rel404 = isset( $params['rel404'] ) ? $params['rel404'] : null;
        unset( $params['r'] ); // ignore 'r' because we unconditionally pass File::RENDER
        unset( $params['f'] ); // We're done with 'f' parameter.
+       unset( $params['rel404'] ); // moved to $rel404
 
        // Get the normalized thumbnail name from the parameters...
        try {
@@ -263,10 +275,10 @@ function wfStreamThumb( array $params ) {
        // for the thumb params and the parent directory for the source file name.
        // Check that the zone relative path matches up so squid caches won't pick
        // up thumbs that would not be purged on source file deletion (bug 34231).
-       if ( isset( $params['rel404'] ) ) { // thumbnail was handled via 404
-               if ( rawurldecode( $params['rel404'] ) === $img->getThumbRel( $thumbName ) ) {
+       if ( $rel404 !== null ) { // thumbnail was handled via 404
+               if ( rawurldecode( $rel404 ) === $img->getThumbRel( $thumbName ) ) {
                        // Request for the canonical thumbnail name
-               } elseif ( rawurldecode( $params['rel404'] ) === $img->getThumbRel( $thumbName2 ) ) {
+               } elseif ( rawurldecode( $rel404 ) === $img->getThumbRel( $thumbName2 ) ) {
                        // Request for the "long" thumbnail name; redirect to canonical name
                        $response = RequestContext::getMain()->getRequest()->response();
                        $response->header( "HTTP/1.1 301 " . HttpStatus::getMessage( 301 ) );
@@ -284,7 +296,7 @@ function wfStreamThumb( array $params ) {
                } else {
                        wfThumbError( 404, "The given path of the specified thumbnail is incorrect;
                                expected '" . $img->getThumbRel( $thumbName ) . "' but got '" .
-                               rawurldecode( $params['rel404'] ) . "'." );
+                               rawurldecode( $rel404 ) . "'." );
                        return;
                }
        }
@@ -306,27 +318,21 @@ function wfStreamThumb( array $params ) {
        }
 
        $user = RequestContext::getMain()->getUser();
-       if ( $user->pingLimiter( 'renderfile' ) ) {
+       if ( !wfThumbIsStandard( $img, $params ) && $user->pingLimiter( 'renderfile-nonstandard' ) ) {
                wfThumbError( 500, wfMessage( 'actionthrottledtext' ) );
                return;
-       } elseif ( wfThumbIsAttemptThrottled( $img, $thumbName, 5 ) ) {
-               wfThumbError( 500, wfMessage( 'thumbnail_image-failure-limit', 5 ) );
+       } elseif ( $user->pingLimiter( 'renderfile' ) ) {
+               wfThumbError( 500, wfMessage( 'actionthrottledtext' ) );
                return;
        }
 
-       // Thumbnail isn't already there, so create the new thumbnail...
-       try {
-               $thumb = $img->transform( $params, File::RENDER_NOW );
-       } catch ( Exception $ex ) {
-               // Tried to select a page on a non-paged file?
-               $thumb = false;
-       }
+       // Actually generate a new thumbnail
+       list( $thumb, $errorMsg ) = wfGenerateThumbnail( $img, $params, $thumbName );
 
        // Check for thumbnail generation errors...
-       $errorMsg = false;
        $msg = wfMessage( 'thumbnail_error' );
        if ( !$thumb ) {
-               $errorMsg = $msg->rawParams( 'File::transform() returned false' )->escaped();
+               $errorMsg = $errorMsg ?: $msg->rawParams( 'File::transform() returned false' )->escaped();
        } elseif ( $thumb->isError() ) {
                $errorMsg = $thumb->getHtmlMsg();
        } elseif ( !$thumb->hasFile() ) {
@@ -337,7 +343,6 @@ function wfStreamThumb( array $params ) {
        }
 
        if ( $errorMsg !== false ) {
-               wfThumbIncrAttemptFailures( $img, $thumbName );
                wfThumbError( 500, $errorMsg );
        } else {
                // Stream the file if there were no errors
@@ -346,42 +351,102 @@ function wfStreamThumb( array $params ) {
 }
 
 /**
- * @param File $img
+ * Actually try to generate a new thumbnail
+ *
+ * @param File $file
+ * @param array $params
  * @param string $thumbName
- * @param int $limit
- * @return int|bool
+ * @return array (MediaTransformOutput|bool, string|bool error message HTML)
  */
-function wfThumbIsAttemptThrottled( File $img, $thumbName, $limit ) {
-       global $wgMemc;
+function wfGenerateThumbnail( File $file, array $params, $thumbName ) {
+       global $wgMemc, $wgAttemptFailureEpoch;
 
-       return ( $wgMemc->get( wfThumbAttemptKey( $img, $thumbName ) ) >= $limit );
-}
+       $key = wfMemcKey( 'attempt-failures', $wgAttemptFailureEpoch,
+               $file->getRepo()->getName(), md5( $file->getName() ), md5( $thumbName ) );
 
-/**
- * @param File $img
- * @param string $thumbName
- */
-function wfThumbIncrAttemptFailures( File $img, $thumbName ) {
-       global $wgMemc;
+       // Check if this file keeps failing to render
+       if ( $wgMemc->get( $key ) >= 4 ) {
+               return array( false, wfMessage( 'thumbnail_image-failure-limit', 4 ) );
+       }
 
-       $key = wfThumbAttemptKey( $img, $thumbName );
-       if ( !$wgMemc->incr( $key, 1 ) ) {
-               if ( !$wgMemc->add( $key, 1, 3600 ) ) {
-                       $wgMemc->incr( $key, 1 );
+       $done = false;
+       // Record failures on PHP fatals in addition to caching exceptions
+       register_shutdown_function( function() use ( &$done, $key ) {
+               if ( !$done ) { // transform() gave a fatal
+                       global $wgMemc;
+                       $wgMemc->incrWithInit( $key, 3600 );
                }
+       } );
+
+       $thumb = false;
+       $errorHtml = false;
+
+       // Thumbnail isn't already there, so create the new thumbnail...
+       try {
+               $work = new PoolCounterWorkViaCallback( 'FileRender', sha1( $file->getName() ),
+                       array(
+                               'doWork' => function() use ( $file, $params ) {
+                                       return $file->transform( $params, File::RENDER_NOW );
+                               },
+                               'getCachedWork' => function() use ( $file, $params ) {
+                                       return $file->transform( $params );
+                               },
+                               'fallback' => function() {
+                                       return wfMessage( 'generic-pool-error' )->parse();
+                               },
+                               'error' => function ( $status ) {
+                                       return $status->getHTML();
+                               }
+                       )
+               );
+               $result = $work->execute();
+               if ( $result instanceof MediaTransformOutput ) {
+                       $thumb = $result;
+               } elseif ( is_string( $result ) ) { // error
+                       $errorHtml = $result;
+               }
+       } catch ( Exception $e ) {
+               // Tried to select a page on a non-paged file?
+       }
+
+       $done = true; // no PHP fatal occured
+
+       if ( !$thumb || $thumb->isError() ) {
+               $wgMemc->incrWithInit( $key, 3600 );
        }
+
+       return array( $thumb, $errorHtml );
 }
 
 /**
+ * Returns true if this thumbnail is one that MediaWiki generates
+ * links to on file description pages and possibly parser output.
+ *
+ * $params is considered non-standard if they involve a non-standard
+ * width or any parameter aside from width and page number. The number
+ * of possible files with standard parameters is far less than that of all
+ * possible combinations; rate-limiting for them can thus be more generious.
+ *
  * @param File $img
- * @param string $thumbName
- * @return string
+ * @param array $params
+ * @return bool
  */
-function wfThumbAttemptKey( File $img, $thumbName ) {
-       global $wgAttemptFailureEpoch;
-
-       return wfMemcKey( 'attempt-failures', $wgAttemptFailureEpoch,
-               $img->getRepo()->getName(), md5( $img->getName() ), md5( $thumbName ) );
+function wfThumbIsStandard( File $img, array $params ) {
+       global $wgThumbLimits, $wgImageLimits;
+       // @TODO: use polymorphism with media handler here
+       if ( array_diff( array_keys( $params ), array( 'width', 'page' ) ) ) {
+               return false; // extra parameters present
+       }
+       if ( isset( $params['width'] ) ) {
+               $widths = $wgThumbLimits;
+               foreach ( $wgImageLimits as $pair ) {
+                       $widths[] = $pair[0];
+               }
+               if ( !in_array( $params['width'], $widths ) ) {
+                       return false;
+               }
+       }
+       return true;
 }
 
 /**
@@ -400,8 +465,8 @@ function wfThumbAttemptKey( File $img, $thumbName ) {
  *
  * Transform specific parameters are set later via wfExtractThumbParams().
  *
- * @param $thumbRel String Thumbnail path relative to the thumb zone
- * @return Array|null associative params array or null
+ * @param string $thumbRel Thumbnail path relative to the thumb zone
+ * @return array|null Associative params array or null
  */
 function wfExtractThumbRequestInfo( $thumbRel ) {
        $repo = RepoGroup::singleton()->getLocalRepo();
@@ -437,9 +502,9 @@ function wfExtractThumbRequestInfo( $thumbRel ) {
  * Convert a thumbnail name (122px-foo.png) to parameters, using
  * file handler.
  *
- * @param File $file File object for file in question.
- * @param $param Array Array of parameters so far.
- * @return Array parameters array with more parameters.
+ * @param File $file File object for file in question
+ * @param array $param Array of parameters so far
+ * @return array Parameters array with more parameters
  */
 function wfExtractThumbParams( $file, $params ) {
        if ( !isset( $params['thumbName'] ) ) {
@@ -495,8 +560,8 @@ function wfExtractThumbParams( $file, $params ) {
 /**
  * Output a thumbnail generation error message
  *
- * @param $status integer
- * @param $msg string
+ * @param int $status
+ * @param string $msg
  * @return void
  */
 function wfThumbError( $status, $msg ) {