From 9c7a7e57f01e32fd9eed781f39854b90cc27ea3b Mon Sep 17 00:00:00 2001 From: "Mark A. Hershberger" Date: Sat, 17 Apr 2010 02:43:13 +0000 Subject: [PATCH] * New UploadFromUrlJob class to handle Upload-by-Copy * Define variable for ApiUserrights.php that wasn't defined before. * Add convertVerifyErrorToStatus and getVerificationErrorCode to UploadBase to translate error consts since UploadFromUrl will need a message to display to end-users. * refactor mime-checking out of UploadBase::verifyFile into UploadBase::verifyMimeType * Make UploadBase::verifyFile always return arrays for errors * Use HttpFunctions instead of custom curl handler for async downloading * TODO: Need a way to feed errors back to the requestor * TODO: Need to add watchlist param handling and warnings checks. --- includes/AutoLoader.php | 1 + includes/DefaultSettings.php | 1 + includes/UploadFromUrlJob.php | 30 ++++ includes/api/ApiBase.php | 1 + includes/api/ApiUpload.php | 14 +- includes/api/ApiUserrights.php | 1 + includes/upload/UploadBase.php | 82 +++++++--- includes/upload/UploadFromUrl.php | 146 +++++++++++------ languages/messages/MessagesEn.php | 12 ++ maintenance/language/messages.inc | 1 + maintenance/tests/MediaWikiParserTest.php | 1 + maintenance/tests/UploadFromUrlTest.php | 191 ++++++++++++++++++++++ 12 files changed, 400 insertions(+), 81 deletions(-) create mode 100644 includes/UploadFromUrlJob.php create mode 100644 maintenance/tests/UploadFromUrlTest.php diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index a63a450b24..d547e58aa2 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -237,6 +237,7 @@ $wgAutoloadLocalClasses = array( 'UploadFromStash' => 'includes/upload/UploadFromStash.php', 'UploadFromFile' => 'includes/upload/UploadFromFile.php', 'UploadFromUrl' => 'includes/upload/UploadFromUrl.php', + 'UploadFromUrlJob' => 'includes/UploadFromUrlJob.php', 'User' => 'includes/User.php', 'UserArray' => 'includes/UserArray.php', 'UserArrayFromResult' => 'includes/UserArray.php', diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index f93d33090c..6ffe45c2c2 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -1935,6 +1935,7 @@ $wgJobClasses = array( 'sendMail' => 'EmaillingJob', 'enotifNotify' => 'EnotifNotifyJob', 'fixDoubleRedirect' => 'DoubleRedirectJob', + 'uploadFromUrl' => 'UploadFromUrlJob', ); /** diff --git a/includes/UploadFromUrlJob.php b/includes/UploadFromUrlJob.php new file mode 100644 index 0000000000..7e1d64a68d --- /dev/null +++ b/includes/UploadFromUrlJob.php @@ -0,0 +1,30 @@ +params['userID'] ) { + $wgUser = User::newFromId( $this->params['userID'] ); + } else { + $wgUser = new User; + } + $wgUser->mEffectiveGroups[] = 'sysop'; + $wgUser->mRights = null; + + $upload = new UploadFromUrl(); + $upload->initializeFromJob( $this ); + + return $upload->doUpload(); + } +} diff --git a/includes/api/ApiBase.php b/includes/api/ApiBase.php index 1d31f6f14f..f195abd998 100644 --- a/includes/api/ApiBase.php +++ b/includes/api/ApiBase.php @@ -989,6 +989,7 @@ abstract class ApiBase { 'invalid-session-key' => array( 'code' => 'invalid-session-key', 'info' => 'Not a valid session key' ), 'nouploadmodule' => array( 'code' => 'nouploadmodule', 'info' => 'No upload module set' ), 'uploaddisabled' => array( 'code' => 'uploaddisabled', 'info' => 'Uploads are not enabled. Make sure $wgEnableUploads is set to true in LocalSettings.php and the PHP ini setting file_uploads is true' ), + 'copyuploaddisabled' => array( 'code' => 'copyuploaddisabled', 'info' => 'Uploads by URL is not enabled. Make sure $wgAllowCopyUploads is set to true in LocalSettings.php.' ), ); /** diff --git a/includes/api/ApiUpload.php b/includes/api/ApiUpload.php index f2c5269782..d98965f1f0 100644 --- a/includes/api/ApiUpload.php +++ b/includes/api/ApiUpload.php @@ -83,7 +83,7 @@ class ApiUpload extends ApiBase { } elseif ( isset( $this->mParams['url'] ) ) { // make sure upload by URL is enabled: if ( !$wgAllowCopyUploads ) { - $this->dieUsageMsg( array( 'uploaddisabled' ) ); + $this->dieUsageMsg( array( 'copyuploaddisabled' ) ); } // make sure the current user can upload @@ -91,14 +91,12 @@ class ApiUpload extends ApiBase { $this->dieUsageMsg( array( 'badaccess-groups' ) ); } - $this->mUpload = new UploadFromUrl(); - $this->mUpload->initialize( $this->mParams['filename'], - $this->mParams['url'] ); + $this->mUpload = new UploadFromUrl; + $this->mUpload->initialize( $this->mParams['filename'], $this->mParams['url'], + $this->mParams['comment'] ); - $status = $this->mUpload->fetchFile(); - if ( !$status->isOK() ) { - $this->dieUsage( $status->getWikiText(), 'fetchfileerror' ); - } + $this->getResult()->addValue( null, $this->getModuleName(), Status::newGood() ); + return; } } else { $this->dieUsageMsg( array( 'missingparam', 'filename' ) ); diff --git a/includes/api/ApiUserrights.php b/includes/api/ApiUserrights.php index 6eacfd06cc..dfb5f19b53 100644 --- a/includes/api/ApiUserrights.php +++ b/includes/api/ApiUserrights.php @@ -43,6 +43,7 @@ class ApiUserrights extends ApiBase { $user = $this->getUser(); + $form = new UserrightsPage; $r['user'] = $user->getName(); list( $r['added'], $r['removed'] ) = $form->doSaveUserGroups( diff --git a/includes/upload/UploadBase.php b/includes/upload/UploadBase.php index 73031f0579..ea40288835 100644 --- a/includes/upload/UploadBase.php +++ b/includes/upload/UploadBase.php @@ -29,6 +29,7 @@ abstract class UploadBase { const FILETYPE_MISSING = 8; const FILETYPE_BADTYPE = 9; const VERIFICATION_ERROR = 10; + # HOOK_ABORTED is the new name of UPLOAD_VERIFICATION_ERROR const UPLOAD_VERIFICATION_ERROR = 11; const HOOK_ABORTED = 11; @@ -41,6 +42,24 @@ abstract class UploadBase { return self::SESSION_KEYNAME; } + public function getVerificationErrorCode( $error ) { + $code_to_status = array(self::EMPTY_FILE => 'empty-file', + self::FILE_TOO_LARGE => 'file-too-large', + self::FILETYPE_MISSING => 'filetype-missing', + self::FILETYPE_BADTYPE => 'filetype-banned', + self::MIN_LENGTH_PARTNAME => 'filename-tooshort', + self::ILLEGAL_FILENAME => 'illegal-filename', + self::OVERWRITE_EXISTING_FILE => 'overwrite', + self::VERIFICATION_ERROR => 'verification-error', + self::HOOK_ABORTED => 'hookaborted', + ); + if( isset( $code_to_status[$error] ) ) { + return $code_to_status[$error]; + } + + return 'unknown-error'; + } + /** * Returns true if uploads are enabled. * Can be override by subclasses. @@ -201,7 +220,7 @@ abstract class UploadBase { if( $this->isEmptyFile() ) { return array( 'status' => self::EMPTY_FILE ); } - + /** * Honor $wgMaxUploadSize */ @@ -217,9 +236,6 @@ abstract class UploadBase { */ $verification = $this->verifyFile(); if( $verification !== true ) { - if( !is_array( $verification ) ) { - $verification = array( $verification ); - } return array( 'status' => self::VERIFICATION_ERROR, 'details' => $verification @@ -278,19 +294,12 @@ abstract class UploadBase { } /** - * Verifies that it's ok to include the uploaded file - * - * @return mixed true of the file is verified, a string or array otherwise. + * Verify the mime type + * @param $magic MagicMime object + * @param $mime string representing the mime + * @return mixed true if the file is verified, an array otherwise */ - protected function verifyFile() { - $this->mFileProps = File::getPropsFromPath( $this->mTempPath, $this->mFinalExtension ); - $this->checkMacBinary(); - - # magically determine mime type - $magic = MimeMagic::singleton(); - $mime = $magic->guessMimeType( $this->mTempPath, false ); - - # check mime type, if desired + protected function verifyMimeType( $magic, $mime ) { global $wgVerifyMimeType; if ( $wgVerifyMimeType ) { wfDebug ( "\n\nmime: <$mime> extension: <{$this->mFinalExtension}>\n\n"); @@ -316,13 +325,35 @@ abstract class UploadBase { } } + return true; + } + + /** + * Verifies that it's ok to include the uploaded file + * + * @return mixed true of the file is verified, array otherwise. + */ + protected function verifyFile() { + $this->mFileProps = File::getPropsFromPath( $this->mTempPath, $this->mFinalExtension ); + $this->checkMacBinary(); + + # magically determine mime type + $magic = MimeMagic::singleton(); + $mime = $magic->guessMimeType( $this->mTempPath, false ); + + # check mime type, if desired + $status = $this->verifyMimeType( $magic, $mime ); + if ( $status !== true ) { + return $status; + } + # check for htmlish code and javascript if( self::detectScript( $this->mTempPath, $mime, $this->mFinalExtension ) ) { - return 'uploadscripted'; + return array( 'uploadscripted' ); } if( $this->mFinalExtension == 'svg' || $mime == 'image/svg+xml' ) { if( self::detectScriptInSvg( $this->mTempPath ) ) { - return 'uploadscripted'; + return array( 'uploadscripted' ); } } @@ -354,7 +385,6 @@ abstract class UploadBase { return true; } $permErrors = $nt->getUserPermissionsErrors( 'edit', $user ); - $permErrorsUpload = $nt->getUserPermissionsErrors( 'upload', $user ); $permErrorsCreate = ( $nt->exists() ? array() : $nt->getUserPermissionsErrors( 'create', $user ) ); if( $permErrors || $permErrorsUpload || $permErrorsCreate ) { $permErrors = array_merge( $permErrors, wfArrayDiff2( $permErrorsUpload, $permErrors ) ); @@ -440,7 +470,8 @@ abstract class UploadBase { * @return mixed Status indicating the whether the upload succeeded. */ public function performUpload( $comment, $pageText, $watch, $user ) { - wfDebug( "\n\n\performUpload: sum:" . $comment . ' c: ' . $pageText . ' w:' . $watch ); + wfDebug( "\n\n\performUpload: sum: " . $comment . ' c: ' . $pageText . + ' w: ' . $watch ); $status = $this->getLocalFile()->upload( $this->mTempPath, $comment, $pageText, File::DELETE_SOURCE, $this->mFileProps, false, $user ); @@ -575,7 +606,8 @@ abstract class UploadBase { } /** - * Generate a random session key from stash in cases where we want to start an upload without much information + * Generate a random session key from stash in cases where we want + * to start an upload without much information */ protected function getSessionKey() { $key = mt_rand( 0, 0x7fffffff ); @@ -850,7 +882,8 @@ abstract class UploadBase { if ( !$wgAntivirusSetup[$wgAntivirus] ) { wfDebug( __METHOD__ . ": unknown virus scanner: $wgAntivirus\n" ); - $wgOut->wrapWikiMsg( "
\n$1
", array( 'virus-badscanner', $wgAntivirus ) ); + $wgOut->wrapWikiMsg( "
\n$1
", + array( 'virus-badscanner', $wgAntivirus ) ); return wfMsg( 'virus-unknownscanner' ) . " $wgAntivirus"; } @@ -1133,4 +1166,9 @@ abstract class UploadBase { return ApiQueryImageInfo::getInfo( $file, array_flip( $imParam ), $result ); } + public function convertVerifyErrorToStatus( $error ) { + $args = func_get_args(); + array_shift($args); + return Status::newFatal( $this->getVerificationErrorCode( $error ), $args ); + } } diff --git a/includes/upload/UploadFromUrl.php b/includes/upload/UploadFromUrl.php index af97d9fc80..a904cf2fa5 100644 --- a/includes/upload/UploadFromUrl.php +++ b/includes/upload/UploadFromUrl.php @@ -10,6 +10,7 @@ */ class UploadFromUrl extends UploadBase { protected $mTempDownloadPath; + protected $comment, $watchList; /** * Checks if the user is allowed to use the upload-by-URL feature. If the @@ -32,13 +33,53 @@ class UploadFromUrl extends UploadBase { /** * Entry point for API upload */ - public function initialize( $name, $url, $na, $nb = false ) { - global $wgTmpDirectory; + public function initialize( $name, $url, $comment, $watchlist ) { + global $wgUser; - $localFile = tempnam( $wgTmpDirectory, 'WEBUPLOAD' ); - $this->initializePathInfo( $name, $localFile, 0, true ); + if( !Http::isValidURI( $url ) ) { + return Status::newFatal( 'http-invalid-url' ); + } + $params = array( + "userName" => $wgUser->getName(), + "userID" => $wgUser->getID(), + "url" => trim( $url ), + "timestamp" => wfTimestampNow(), + "comment" => $comment, + "watchlist" => $watchlist); + + $title = Title::newFromText( $name ); + /* // Check whether the user has the appropriate permissions to upload anyway */ + /* $permission = $this->isAllowed( $wgUser ); */ + + /* if ( $permission !== true ) { */ + /* if ( !$wgUser->isLoggedIn() ) { */ + /* return Status::newFatal( 'uploadnologintext' ); */ + /* } else { */ + /* return Status::newFatal( 'badaccess-groups' ); */ + /* } */ + /* } */ + + /* $permErrors = $this->verifyPermissions( $wgUser ); */ + /* if ( $permErrors !== true ) { */ + /* return Status::newFatal( 'badaccess-groups' ); */ + /* } */ - $this->mUrl = trim( $url ); + + $job = new UploadFromUrlJob( $title, $params ); + $job->insert(); + } + + /** + * Initialize a queued download + * @param $job Job + */ + public function initializeFromJob( $job ) { + $this->mUrl = $job->params['url']; + $this->mTempPath = tempnam( $wgTmpDirectory, 'COPYUPLOAD' ); + $this->mDesiredDestName = $job->title; + $this->comment = $job->params['comment']; + $this->watchList = $job->params['watchlist']; + $this->getTitle(); } /** @@ -66,60 +107,63 @@ class UploadFromUrl extends UploadBase { return self::isValidUrl( $request->getVal( 'wpUploadFileURL' ) ); } - public static function isValidUrl( $url ) { - // Only allow HTTP or FTP for now - return (bool)preg_match( '!^(http://|ftp://)!', $url ); + private function saveTempFile( $req ) { + $filename = tempnam( wfTempDir(), 'URL' ); + if ( $filename === false ) { + return Status::newFatal( 'tmp-create-error' ); + } + if ( file_put_contents( $filename, $req->getContent() ) === false ) { + return Status::newFatal( 'tmp-write-error' ); + } + + $this->mTempPath = $filename; + $this->mFileSize = filesize( $filename ); + + return Status::newGood(); } - /** - * Do the real fetching stuff - */ - function fetchFile() { - if( !self::isValidUrl( $this->mUrl ) ) { - return Status::newFatal( 'upload-proto-error' ); + public function doUpload() { + global $wgUser; + + $req = HttpRequest::factory($this->mUrl); + $status = $req->execute(); + + if( !$status->isOk() ) { + return $status; } - # Open temporary file - $this->mCurlDestHandle = @fopen( $this->mTempPath, "wb" ); - if( $this->mCurlDestHandle === false ) { - # Could not open temporary file to write in - return Status::newFatal( 'upload-file-error' ); + $status = $this->saveTempFile( $req ); + $this->mRemoveTempFile = true; + + if( !$status->isOk() ) { + return $status; } - - $options = array( - 'method' => 'GET', - 'timeout' => 10, - ); - $req = HttpRequest::factory( $this->mUrl, $options ); - $req->setCallback( array( $this, 'uploadCurlCallback' ) ); - $status = $req->execute(); - fclose( $this->mCurlDestHandle ); - unset( $this->mCurlDestHandle ); - - global $wgMaxUploadSize; - if ( $this->mFileSize > $wgMaxUploadSize ) { - # Just return an ok, so that the regular verifications can handle - # the file-too-large error - return Status::newGood(); + + $v = $this->verifyUpload(); + if( $v['status'] !== UploadBase::OK ) { + return $this->convertVerifyErrorToStatus( $v['status'], $v['details'] ); } - return $status; - } + // This has to come from API + /* $warnings = $this->checkForWarnings(); */ + /* if( isset($warnings) ) return $warnings; */ - /** - * Callback function for CURL-based web transfer - * Write data to file unless we've passed the length limit; - * if so, abort immediately. - * @access private - */ - function uploadCurlCallback( $ch, $data ) { - global $wgMaxUploadSize; - $length = strlen( $data ); - $this->mFileSize += $length; - if( $this->mFileSize > $wgMaxUploadSize ) { - return 0; + // Use comment as initial page text by default + if ( is_null( $this->mParams['text'] ) ) { + $this->mParams['text'] = $this->mParams['comment']; + } + + $file = $this->getLocalFile(); + // This comes from ApiBase + /* $watch = $this->getWatchlistValue( $this->mParams['watchlist'], $file->getTitle() ); */ + + if ( !$status->isGood() ) { + return $status; } - fwrite( $this->mCurlDestHandle, $data ); - return $length; + + $status = $this->getLocalFile()->upload( $this->mTempPath, $this->comment, + $this->pageText, File::DELETE_SOURCE, $this->mFileProps, false, $wgUser ); + + return $status; } } diff --git a/languages/messages/MessagesEn.php b/languages/messages/MessagesEn.php index 7c2a1db15e..9b6eb43b94 100644 --- a/languages/messages/MessagesEn.php +++ b/languages/messages/MessagesEn.php @@ -2055,6 +2055,17 @@ Preferred {{PLURAL:\$3|file type is|file types are}} \$2.", 'filetype-banned-type' => "'''\".\$1\"''' is not a permitted file type. Permitted {{PLURAL:\$3|file type is|file types are}} \$2.", 'filetype-missing' => 'The file has no extension (like ".jpg").', +'empty-file' => 'The file you submitted was empty', +'file-too-large' => 'The file you submitted was too large', +'filename-tooshort' => 'The filename is too short', +'filetype-banned' => 'This type of file is banned', +'verification-error' => 'This file did not pass file verification', +'hookaborted' => 'The modification you tried to make was aborted by an extension hook', +'illegal-filename' => 'The filename is not allowed', +'overwrite' => 'Overwriting an existing file is not allowed', +'unknown-error' => 'An unknown error occured', +'tmp-create-error' => 'Couldn\'t create temporary file', +'tmp-write-error' => 'Error writing temporary file', 'large-file' => 'It is recommended that files are no larger than $1; this file is $2.', 'largefileserver' => 'This file is bigger than the server is configured to allow.', @@ -2094,6 +2105,7 @@ You should check that file's deletion history before proceeding to re-upload it. 'uploadedimage' => 'uploaded "[[$1]]"', 'overwroteimage' => 'uploaded a new version of "[[$1]]"', 'uploaddisabled' => 'Uploads disabled', +'copyuploaddisabled' => 'Upload by URL disabled', 'uploaddisabledtext' => 'File uploads are disabled.', 'php-uploaddisabledtext' => 'File uploads are disabled in PHP. Please check the file_uploads setting.', diff --git a/maintenance/language/messages.inc b/maintenance/language/messages.inc index de097b212b..4fe9433868 100644 --- a/maintenance/language/messages.inc +++ b/maintenance/language/messages.inc @@ -1233,6 +1233,7 @@ $wgMessageStructure = array( 'uploadedimage', 'overwroteimage', 'uploaddisabled', + 'copyuploaddisabled', 'uploaddisabledtext', 'php-uploaddisabledtext', 'uploadscripted', diff --git a/maintenance/tests/MediaWikiParserTest.php b/maintenance/tests/MediaWikiParserTest.php index 247d840181..f00779f043 100644 --- a/maintenance/tests/MediaWikiParserTest.php +++ b/maintenance/tests/MediaWikiParserTest.php @@ -27,6 +27,7 @@ class MediaWikiParserTestSuite extends PHPUnit_Framework_TestSuite { $tables[] = 'filearchive'; $tables[] = 'logging'; $tables[] = 'updatelog'; + $tables[] = 'iwlinks'; return true; } diff --git a/maintenance/tests/UploadFromUrlTest.php b/maintenance/tests/UploadFromUrlTest.php new file mode 100644 index 0000000000..77fd099004 --- /dev/null +++ b/maintenance/tests/UploadFromUrlTest.php @@ -0,0 +1,191 @@ + 'LocalRepo', + 'name' => 'local', + 'directory' => 'test-repo', + 'url' => 'http://example.com/images', + 'hashLevels' => 2, + 'transformVia404' => false, + ); + + ini_set( 'log_errors', 1 ); + ini_set( 'error_reporting', 1 ); + ini_set( 'display_errors', 1 ); + } + + function doApiRequest( $params, $data = null ) { + $session = isset( $data[2] ) ? $data[2] : array(); + $_SESSION = $session; + + $req = new FauxRequest( $params, true, $session ); + $module = new ApiMain( $req, true ); + $module->execute(); + + return array( $module->getResultData(), $req, $_SESSION ); + } + + function testClearQueue() { + while ( $job = Job::pop() ) {} + $this->assertFalse($job); + } + + function testLogin() { + $data = $this->doApiRequest( array( + 'action' => 'login', + 'lgname' => self::$userName, + 'lgpassword' => self::$passWord ) ); + $this->assertArrayHasKey( "login", $data[0] ); + $this->assertArrayHasKey( "result", $data[0]['login'] ); + $this->assertEquals( "NeedToken", $data[0]['login']['result'] ); + $token = $data[0]['login']['token']; + + $data = $this->doApiRequest( array( + 'action' => 'login', + "lgtoken" => $token, + "lgname" => self::$userName, + "lgpassword" => self::$passWord ) ); + + $this->assertArrayHasKey( "login", $data[0] ); + $this->assertArrayHasKey( "result", $data[0]['login'] ); + $this->assertEquals( "Success", $data[0]['login']['result'] ); + $this->assertArrayHasKey( 'lgtoken', $data[0]['login'] ); + + return $data; + } + + /** + * @depends testLogin + */ + function testSetupUrlDownload( $data ) { + global $wgUser; + $wgUser = User::newFromName( self::$userName ); + $wgUser->load(); + $data[2]['wsEditToken'] = $data[2]['wsToken']; + $token = md5( $data[2]['wsToken'] ) . EDIT_TOKEN_SUFFIX; + $exception = false; + + try { + $this->doApiRequest( array( + 'action' => 'upload', + ), $data ); + } catch ( UsageException $e ) { + $exception = true; + $this->assertEquals( "The token parameter must be set", $e->getMessage() ); + } + $this->assertTrue( $exception, "Got exception" ); + + $exception = false; + try { + $this->doApiRequest( array( + 'action' => 'upload', + 'token' => $token, + ), $data ); + } catch ( UsageException $e ) { + $exception = true; + $this->assertEquals( "One of the parameters sessionkey, file, url is required", + $e->getMessage() ); + } + $this->assertTrue( $exception, "Got exception" ); + + $exception = false; + try { + $this->doApiRequest( array( + 'action' => 'upload', + 'url' => 'http://www.example.com/test.png', + 'token' => $token, + ), $data ); + } catch ( UsageException $e ) { + $exception = true; + $this->assertEquals( "The filename parameter must be set", $e->getMessage() ); + } + $this->assertTrue( $exception, "Got exception" ); + + $wgUser->removeGroup('sysop'); + $exception = false; + try { + $this->doApiRequest( array( + 'action' => 'upload', + 'url' => 'http://www.example.com/test.png', + 'filename' => 'Test.png', + 'token' => $token, + ), $data ); + } catch ( UsageException $e ) { + $exception = true; + $this->assertEquals( "Permission denied", $e->getMessage() ); + } + $this->assertTrue( $exception, "Got exception" ); + + $wgUser->addGroup('*'); + $wgUser->addGroup('sysop'); + $exception = false; + $data = $this->doApiRequest( array( + 'action' => 'upload', + 'url' => 'http://bits.wikimedia.org/skins-1.5/common/images/poweredby_mediawiki_88x31.png', + 'filename' => 'Test.png', + 'token' => $token, + ), $data ); + + $this->assertThat( $data[0]['upload'], $this->isInstanceOf( 'Status' ), + "Got Status Object" ); + $this->assertTrue( $data[0]['upload']->isOk(), 'Job added'); + + $job = Job::pop(); + $this->assertThat( $job, $this->isInstanceOf( 'UploadFromUrlJob' ), + "Got Job Object" ); + + $job = Job::pop_type( 'upload' ); + $this->assertFalse( $job ); + } + + /** + * @depends testLogin + */ + function testDoDownload( $data ) { + global $wgUser; + $data[2]['wsEditToken'] = $data[2]['wsToken']; + $token = md5( $data[2]['wsToken'] ) . EDIT_TOKEN_SUFFIX; + + $wgUser->addGroup('users'); + $data = $this->doApiRequest( array( + 'action' => 'upload', + 'filename' => 'Test.png', + 'url' => 'http://bits.wikimedia.org/skins-1.5/common/images/poweredby_mediawiki_88x31.png', + 'token' => $token, + ), $data ); + + $job = Job::pop(); + $this->assertEquals( 'UploadFromUrlJob', get_class($job) ); + + $status = $job->run(); + $this->assertTrue( $status->isOk() ); + + return $data; + } + + /** + * @depends testDoDownload + */ + function testVerifyDownload( $data ) { + $t = Title::newFromText("Test.png", NS_FILE); + + $this->assertTrue($t->exists()); + } +} -- 2.20.1