Merge "Introduce new hook UploadVerifyUpload to allow preventing file uploads"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Thu, 7 Jul 2016 02:27:44 +0000 (02:27 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Thu, 7 Jul 2016 02:27:44 +0000 (02:27 +0000)
1  2 
docs/hooks.txt
includes/api/ApiUpload.php
includes/upload/UploadBase.php

diff --combined docs/hooks.txt
@@@ -371,8 -371,7 +371,8 @@@ $user: Current use
  &$message: API usage message to die with, as a message key or array
    as accepted by ApiBase::dieUsageMsg.
  
 -'APIEditBeforeSave': Before saving a page with api.php?action=edit, after
 +'APIEditBeforeSave': DEPRECATED! Use EditFilterMergedContent instead.
 +Before saving a page with api.php?action=edit, after
  processing request parameters. Return false to let the request fail, returning
  an error message or an <edit result="Failure"> tag if $resultArr was filled.
  Unlike for example 'EditFilterMergedContent' this also being run on undo.
@@@ -444,15 -443,6 +444,15 @@@ an exception is thrown during API actio
  $apiMain: Calling ApiMain instance.
  $e: Exception object.
  
 +'ApiMakeParserOptions': Called from ApiParse and ApiExpandTemplates to allow
 +extensions to adjust the ParserOptions before parsing.
 +$options: ParserOptions object
 +$title: Title to be parsed
 +$params: Parameter array for the API module
 +$module: API module (which is also a ContextSource)
 +&$reset: Set to a ScopedCallback used to reset any hooks after the parse is done.
 +&$suppressCache: Set true if cache should be suppressed.
 +
  'ApiOpenSearchSuggest': Called when constructing the OpenSearch results. Hooks
  can alter or append to the array.
  &$results: array with integer keys to associative arrays. Keys in associative
@@@ -683,10 -673,6 +683,10 @@@ $oldPageID: the page ID of the revisio
  revisions of an article.
  $title: Title object of the article
  $ids: Ids to set the visibility for
 +$visibilityChangeMap: Map of revision id to oldBits and newBits.  This array can be
 +  examined to determine exactly what visibility bits have changed for each
 +  revision.  This array is of the form
 +  [id => ['oldBits' => $oldBits, 'newBits' => $newBits], ... ]
  
  'ArticleRollbackComplete': After an article rollback is completed.
  $wikiPage: the WikiPage that was edited
@@@ -933,7 -919,6 +933,7 @@@ $wikiPage: WikiPage that was adde
  'CategoryAfterPageRemoved': After a page is removed from a category.
  $category: Category that page was removed from
  $wikiPage: WikiPage that was removed
 +$id: the page ID (original ID in case of page deletions)
  
  'CategoryPageView': Before viewing a categorypage in CategoryPage::view.
  &$catpage: CategoryPage instance
@@@ -2881,12 -2866,6 +2881,12 @@@ $id: User id number, only provided for 
  $user: User object representing user contributions are being fetched for
  $sp: SpecialPage instance, providing context
  
 +'SpecialContributions::formatRow::flags': Called before rendering a
 +Special:Contributions row.
 +$context: IContextSource object
 +$row: Revision information from the database
 +&$flags: List of flags on this row
 +
  'SpecialContributions::getForm::filters': Called with a list of filters to render
  on Special:Contributions.
  $sp: SpecialContributions object, for context
@@@ -3289,8 -3268,8 +3289,8 @@@ added to the descripto
  &$radio: Boolean, if source type should be shown as radio button
  $selectedSourceType: The selected source type
  
 -'UploadVerification': Additional chances to reject an uploaded file. Consider
 -using UploadVerifyFile instead.
 +'UploadVerification': DEPRECATED! Use UploadVerifyFile instead.
 +Additional chances to reject an uploaded file.
  $saveName: (string) destination file name
  $tempName: (string) filesystem path to the temporary file for checks
  &$error: (string) output: message key for message to show if upload canceled by
@@@ -3306,6 -3285,19 +3306,19 @@@ $mime: (string) The uploaded file's MIM
    representing the problem with the file, where the first element is the message
    key and the remaining elements are used as parameters to the message.
  
+ 'UploadVerifyUpload': Upload verification, based on both file properties like
+ MIME type (same as UploadVerifyFile) and the information entered by the user
+ (upload comment, file page contents etc.).
+ $upload: (object) An instance of UploadBase, with all info about the upload
+ $user: (object) An instance of User, the user uploading this file
+ $props: (array) File properties, as returned by FSFile::getPropsFromPath()
+ $comment: (string) Upload log comment (also used as edit summary)
+ $pageText: (string) File description page text (only used for new uploads)
+ &$error: output: If the file upload should be prevented, set this to the reason
+   in the form of array( messagename, param1, param2, ... ) or a MessageSpecifier
+   instance (you might want to use ApiMessage to provide machine-readable details
+   for the API).
  'UserIsBot': when determining whether a user is a bot account
  $user: the user
  &$isBot: whether this is user a bot or not (boolean)
@@@ -347,12 -347,13 +347,13 @@@ class ApiUpload extends ApiBase 
         * Throw an error that the user can recover from by providing a better
         * value for $parameter
         *
 -       * @param array $error Error array suitable for passing to dieUsageMsg()
 +       * @param array|string|MessageSpecifier $error Error suitable for passing to dieUsageMsg()
         * @param string $parameter Parameter that needs revising
         * @param array $data Optional extra data to pass to the user
+        * @param string $code Error code to use if the error is unknown
         * @throws UsageException
         */
-       private function dieRecoverableError( $error, $parameter, $data = [] ) {
+       private function dieRecoverableError( $error, $parameter, $data = [], $code = 'unknownerror' ) {
                try {
                        $data['filekey'] = $this->performStash();
                        $data['sessionkey'] = $data['filekey'];
                if ( isset( $parsed['data'] ) ) {
                        $data = array_merge( $data, $parsed['data'] );
                }
+               if ( $parsed['code'] === 'unknownerror' ) {
+                       $parsed['code'] = $code;
+               }
  
                $this->dieUsage( $parsed['info'], $parsed['code'], 0, $data );
        }
                                $this->mParams['text'], $watch, $this->getUser(), $this->mParams['tags'] );
  
                        if ( !$status->isGood() ) {
-                               $error = $status->getErrorsArray();
-                               ApiResult::setIndexedTagName( $error, 'error' );
-                               $this->dieUsage( 'An internal error occurred', 'internal-error', 0, $error );
+                               // Is there really no better way to do this?
+                               $errors = $status->getErrorsByType( 'error' );
+                               $msg = array_merge( [ $errors[0]['message'] ], $errors[0]['params'] );
+                               $data = $status->getErrorsArray();
+                               ApiResult::setIndexedTagName( $data, 'error' );
+                               // For backwards-compatibility, we use the 'internal-error' fallback key and merge $data
+                               // into the root of the response (rather than something sane like [ 'details' => $data ]).
+                               $this->dieRecoverableError( $msg, null, $data, 'internal-error' );
                        }
                        $result['result'] = 'Success';
                }
@@@ -353,7 -353,7 +353,7 @@@ abstract class UploadBase 
  
                $error = '';
                if ( !Hooks::run( 'UploadVerification',
 -                      [ $this->mDestName, $this->mTempPath, &$error ] )
 +                      [ $this->mDestName, $this->mTempPath, &$error ], '1.28' )
                ) {
                        return [ 'status' => self::HOOK_ABORTED, 'error' => $error ];
                }
         */
        public function performUpload( $comment, $pageText, $watch, $user, $tags = [] ) {
                $this->getLocalFile()->load( File::READ_LATEST );
+               $props = $this->mFileProps;
+               $error = null;
+               Hooks::run( 'UploadVerifyUpload', [ $this, $user, $props, $comment, $pageText, &$error ] );
+               if ( $error ) {
+                       if ( !is_array( $error ) ) {
+                               $error = [ $error ];
+                       }
+                       return call_user_func_array( 'Status::newFatal', $error );
+               }
  
                $status = $this->getLocalFile()->upload(
                        $this->mTempPath,
                        $comment,
                        $pageText,
                        File::DELETE_SOURCE,
-                       $this->mFileProps,
+                       $props,
                        false,
                        $user,
                        $tags
                                return [ 'uploaded-event-handler-on-svg', $attrib, $value ];
                        }
  
 -                      # href with non-local target (don't allow http://, javascript:, etc)
 +                      # Do not allow relative links, or unsafe url schemas.
 +                      # For <a> tags, only data:, http: and https: and same-document
 +                      # fragment links are allowed. For all other tags, only data:
 +                      # and fragment are allowed.
                        if ( $stripped == 'href'
                                && strpos( $value, 'data:' ) !== 0
                                && strpos( $value, '#' ) !== 0