}
public function initialize( $done, $filename, $sessionKey, $path, $fileSize, $sessionData ) {
- global $wgTmpDirectory;
- $this->status = new Status;
+ $this->status = Status::newGood();
- $this->initFromSessionKey( $sessionKey, $sessionData );
+ $this->initializePathInfo( $filename, $path, 0, true );
+ if ( $sessionKey !== null ) {
+ $this->initFromSessionKey( $sessionKey, $sessionData, $fileSize );
- if ( !$this->sessionKey && !$done ) {
+ if ( $done ) {
+ $this->chunkMode = self::DONE;
+ } else {
+ $this->mTempPath = $path;
+ $this->chunkMode = self::CHUNK;
+ }
+ } else {
// session key not set, init the chunk upload system:
$this->chunkMode = self::INIT;
- $this->initializePathInfo( $filename, $path, 0, true);
- } else if ( $this->sessionKey && !$done ) {
- $this->chunkMode = self::CHUNK;
- } else if ( $this->sessionKey && $done ) {
- $this->chunkMode = self::DONE;
}
- if ( $this->chunkMode == self::CHUNK || $this->chunkMode == self::DONE ) {
- $this->mTempPath = $path;
- $this->mFileSize += $fileSize;
+
+ if ( $this->status->isOk()
+ && ( $this->mDesiredDestName === null || $this->mFileSize === null ) ) {
+ $this->status = Status::newFatal( 'chunk-init-error' );
}
}
* @returns string the session key for this chunked upload
*/
protected function setupChunkSession( $comment, $pageText, $watch ) {
- $this->sessionKey = $this->getSessionKey();
- $_SESSION['wsUploadData'][$this->sessionKey] = array(
- 'comment' => $comment,
- 'pageText' => $pageText,
- 'watch' => $watch,
- 'mFilteredName' => $this->mFilteredName,
- 'repoPath' => null,
- 'mDesiredDestName' => $this->mDesiredDestName,
- 'version' => self::SESSION_VERSION,
- );
+ if ( !isset( $this->sessionKey ) ) {
+ $this->sessionKey = $this->getSessionKey();
+ }
+ foreach ( array( 'mFilteredName', 'repoPath', 'mFileSize', 'mDesiredDestName' )
+ as $key ) {
+ if ( isset( $this->$key ) ) {
+ $_SESSION['wsUploadData'][$this->sessionKey][$key] = $this->$key;
+ }
+ }
+ if ( isset( $comment ) ) {
+ $_SESSION['wsUploadData'][$this->sessionKey]['commment'] = $comment;
+ }
+ if ( isset( $pageText ) ) {
+ $_SESSION['wsUploadData'][$this->sessionKey]['pageText'] = $pageText;
+ }
+ if ( isset( $watch ) ) {
+ $_SESSION['wsUploadData'][$this->sessionKey]['watch'] = $watch;
+ }
+ $_SESSION['wsUploadData'][$this->sessionKey]['version'] = self::SESSION_VERSION;
+
return $this->sessionKey;
}
* Initialize a continuation of a chunked upload from a session key
* @param $sessionKey string
* @param $request WebRequest
+ * @param $fileSize int Size of this chunk
*
* @returns void
*/
- protected function initFromSessionKey( $sessionKey, $sessionData ) {
+ protected function initFromSessionKey( $sessionKey, $sessionData, $fileSize ) {
// testing against null because we don't want to cause obscure
// bugs when $sessionKey is full of "0"
- if ( $sessionKey === null ) {
- return;
- }
$this->sessionKey = $sessionKey;
if ( isset( $sessionData[$this->sessionKey]['version'] )
&& $sessionData[$this->sessionKey]['version'] == self::SESSION_VERSION )
{
- $this->comment = $sessionData[$this->sessionKey]['comment'];
- $this->pageText = $sessionData[$this->sessionKey]['pageText'];
- $this->watch = $sessionData[$this->sessionKey]['watch'];
- $this->mFilteredName = $sessionData[$this->sessionKey]['mFilteredName'];
- $this->repoPath = $sessionData[$this->sessionKey]['repoPath'];
- $this->mDesiredDestName = $sessionData[$this->sessionKey]['mDesiredDestName'];
+ foreach ( array( 'comment', 'pageText', 'watch', 'mFilteredName', 'repoPath', 'mFileSize', 'mDesiredDestName' )
+ as $key ) {
+ if ( isset( $sessionData[$this->sessionKey][$key] ) ) {
+ $this->$key = $sessionData[$this->sessionKey][$key];
+ }
+ }
+
+ $this->mFileSize += $fileSize;
} else {
$this->status = Status::newFatal( 'invalid-session-key' );
}
// b) should only happen over POST
// c) we need the token to validate chunks are coming from a non-xss request
return Status::newGood(
- array('uploadUrl' => wfExpandUrl( wfScript( 'api' ) ) . "?" .
- wfArrayToCGI(array('action' => 'upload',
- 'token' => $wgUser->editToken(),
- 'format' => 'json',
- 'filename' => $pageText,
- 'enablechunks' => 'true',
- 'chunksession' => $this->setupChunkSession( $comment, $pageText, $watch ) ) ) ) );
+ array( 'uploadUrl' => wfExpandUrl( wfScript( 'api' ) ) . "?" .
+ wfArrayToCGI( array(
+ 'action' => 'upload',
+ 'token' => $wgUser->editToken(),
+ 'format' => 'json',
+ 'filename' => $this->mDesiredDestName,
+ 'enablechunks' => 'true',
+ 'chunksession' =>
+ $this->setupChunkSession( $comment, $pageText, $watch ) ) ) ) );
} else if ( $this->chunkMode == self::CHUNK ) {
+ $this->setupChunkSession();
$this->appendChunk();
if ( !$this->status->isOK() ) {
return $this->status;
array( 'result' => 1, 'filesize' => $this->mFileSize )
);
} else if ( $this->chunkMode == self::DONE ) {
- if ( !$comment )
- $comment = $this->comment;
+ $this->finalizeFile();
+ // We ignore the passed-in parameters because these were set on the first contact.
+ $status = parent::performUpload( $this->comment, $this->pageText, $this->watch, $user );
- if ( !$pageText )
- $pageText = $this->pageText;
-
- if ( !$watch )
- $watch = $this->watch;
-
- $status = parent::performUpload( $comment, $pageText, $watch, $user );
if ( !$status->isGood() ) {
return $status;
}
// firefogg expects a specific result
// http://www.firefogg.org/dev/chunk_post.html
return Status::newGood(
- array('result' => 1, 'done' => 1, 'resultUrl' => wfExpandUrl( $file->getDescriptionUrl() ) )
+ array( 'result' => 1, 'done' => 1, 'resultUrl' => wfExpandUrl( $file->getDescriptionUrl() ) )
);
}
}
if ( $this->getRealPath( $this->repoPath ) ) {
$this->status = $this->appendToUploadFile( $this->repoPath, $this->mTempPath );
+
+ if ( $this->mFileSize > $wgMaxUploadSize )
+ $this->status = Status::newFatal( 'largefileserver' );
+
} else {
$this->status = Status::newFatal( 'filenotfound', $this->repoPath );
}
- if ( $this->mFileSize > $wgMaxUploadSize )
- $this->status = Status::newFatal( 'largefileserver' );
+ }
+
+ /**
+ * Append the final chunk and ready file for parent::performUpload()
+ * @return void
+ */
+ protected function finalizeFile() {
+ $this->appendChunk();
+ $this->mTempPath = $this->getRealPath( $this->repoPath );
}
public function verifyUpload() {
if ( $this->chunkMode != self::DONE ) {
- return array('status' => UploadBase::OK);
+ return array( 'status' => UploadBase::OK );
}
return parent::verifyUpload();
}
<?php
-require_once("ApiSetup.php");
+require_once( "ApiSetup.php" );
class UploadFromChunksTest extends ApiSetup {
function setUp() {
global $wgEnableUploads;
- $wgEnableUploads=true;
- ini_set('file_loads', true);
+ $wgEnableUploads = true;
parent::setup();
+ ini_set( 'file_loads', 1 );
+ ini_set( 'log_errors', 1 );
+ ini_set( 'error_reporting', 1 );
+ ini_set( 'display_errors', 1 );
}
- function makeChunk() {
+ function makeChunk( $content ) {
$file = tempnam( wfTempDir(), "" );
- $fh = fopen($file, "w");
- if($fh == false) {
- $this->markTestIncomplete("Couldn't open $file!\n");
+ $fh = fopen( $file, "w" );
+ if ( $fh == false ) {
+ $this->markTestIncomplete( "Couldn't open $file!\n" );
return;
}
- fwrite($fh, "123");
- fclose($fh);
+ fwrite( $fh, $content );
+ fclose( $fh );
$_FILES['chunk']['tmp_name'] = $file;
$_FILES['chunk']['size'] = 3;
}
function cleanChunk() {
- if(file_exists($_FILES['chunk']['tmp_name']))
- unlink($_FILES['chunk']['tmp_name']);
+ if ( file_exists( $_FILES['chunk']['tmp_name'] ) )
+ unlink( $_FILES['chunk']['tmp_name'] );
}
- function doApiRequest($params) {
- $session = isset( $_SESSION ) ? $_SESSION : array();
- $req = new FauxRequest($params, true, $session);
- $module = new ApiMain($req, true);
+ 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 $module->getResultData();
+ return array( $module->getResultData(), $req, $_SESSION );
}
function testGetTitle() {
$filename = tempnam( wfTempDir(), "" );
$c = new UploadFromChunks();
- $c->initialize(false, "temp.txt", null, $filename, 0, null);
- $this->assertEquals(null, $c->getTitle());
+ $c->initialize( false, "temp.txt", null, $filename, 0, null );
+ $this->assertEquals( null, $c->getTitle() );
$c = new UploadFromChunks();
- $c->initialize(false, "temp.png", null, $filename, 0, null);
- $this->assertEquals(Title::makeTitleSafe(NS_FILE, "Temp.png"), $c->getTitle());
+ $c->initialize( false, "temp.png", null, $filename, 0, null );
+ $this->assertEquals( Title::makeTitleSafe( NS_FILE, "Temp.png" ), $c->getTitle() );
}
function testLogin() {
- $data = $this->doApiRequest(array('action' => 'login',
- "lgname" => self::$userName,
- "lgpassword" => self::$passWord ) );
- $this->assertArrayHasKey("login", $data);
- $this->assertArrayHasKey("result", $data['login']);
- $this->assertEquals("Success", $data['login']['result']);
+ $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( "Success", $data[0]['login']['result'] );
+ $this->assertArrayHasKey( 'lgtoken', $data[0]['login'] );
return $data;
}
-
/**
* @depends testLogin
*/
- function testGetEditToken($data) {
+ function testSetupChunkSession( $data ) {
global $wgUser;
- $wgUser = User::newFromName(self::$userName);
+ $wgUser = User::newFromName( self::$userName );
$wgUser->load();
+ $data[2]['wsEditToken'] = $data[2]['wsToken'];
+ $token = md5( $data[2]['wsToken'] ) . EDIT_TOKEN_SUFFIX;
+ $exception = false;
+
+ $data = $this->doApiRequest( array(
+ 'filename' => 'tmp.txt',
+ 'action' => 'upload',
+ 'enablechunks' => true,
+ 'token' => $token ), $data );
+ $this->assertArrayHasKey( 'uploadUrl', $data[0] );
+ $this->assertRegexp( '/action=upload/', $data[0]['uploadUrl'] );
+ $this->assertRegexp( '/enablechunks=true/', $data[0]['uploadUrl'] );
+ $this->assertRegexp( '/format=json/', $data[0]['uploadUrl'] );
+ $this->assertRegexp( '/chunksession=/', $data[0]['uploadUrl'] );
+ $this->assertRegexp( '/token=/', $data[0]['uploadUrl'] );
- $data = $this->doApiRequest(array('action' => 'query',
- 'prop' => 'info',
- 'intoken' => 'edit'));
+ return $data;
}
- function testSetupChunkSession() {
- }
+ /**
+ * @depends testSetupChunkSession
+ */
+ function testAppendChunkTypeBanned( $data ) {
+ global $wgUser;
+ $wgUser = User::newFromName( self::$userName );
+ $url = $data[0]['uploadUrl'];
+ $params = wfCgiToArray( substr( $url, strpos( $url, "?" ) ) );
- /**
- * @expectedException UsageException
- */
- function testPerformUploadInitError() {
- global $wgUser;
- $wgUser = User::newFromId(1);
+ $size = 0;
+ for ( $i = 0; $i < 4; $i++ ) {
+ $this->makeChunk( "123" );
+ $size += $_FILES['chunk']['size'];
- $req = new FauxRequest(
- array('action' => 'upload',
- 'enablechunks' => '1',
- 'filename' => 'test.png',
- ));
- $module = new ApiMain($req, true);
- $module->execute();
+ $data = $this->doApiRequest( $params, $data );
+ $this->assertArrayHasKey( "result", $data[0] );
+ $this->assertTrue( (bool)$data[0]["result"] );
+
+ $this->assertArrayHasKey( "filesize", $data[0] );
+ $this->assertEquals( $size, $data[0]['filesize'] );
+
+ $this->cleanChunk();
+ }
+
+ $data['param'] = $params;
+ return $data;
}
/**
* @depends testLogin
*/
- function testPerformUploadInitSuccess($login) {
+ function testInvalidSessionKey( $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',
+ 'enablechunks' => true,
+ 'token' => $token,
+ 'chunksession' => 'bogus' ), $data );
+ } catch ( UsageException $e ) {
+ $exception = true;
+ $this->assertEquals( "Not a valid session key", $e->getMessage() );
+ }
- $wgUser = User::newFromName(self::$userName);
- $token = $wgUser->editToken();
+ $this->assertTrue( $exception, "Got exception" );
+ }
- $data = $this->doApiRequest(
- array('action' => 'upload',
- 'enablechunks' => '1',
- 'filename' => 'test.png',
- 'token' => $token,
- ));
+ function testPerformUploadInitError() {
+ global $wgUser;
+ $wgUser = User::newFromId( 1 );
- $this->assertArrayHasKey("uploadUrl", $data);
+ $req = new FauxRequest(
+ array(
+ 'action' => 'upload',
+ 'enablechunks' => 'false',
+ 'sessionkey' => '1',
+ 'filename' => 'test.png',
+ ) );
+ $module = new ApiMain( $req, true );
+ $gotException = false;
+ try {
+ $module->execute();
+ } catch ( UsageException $e ) {
+ $this->assertEquals( "The token parameter must be set", $e->getMessage() );
+ $gotException = true;
+ }
- return array('data' => $data, 'session' => $_SESSION, 'token' => $token);
+ $this->assertTrue( $gotException );
}
/**
- * @depends testPerformUploadInitSuccess
+ * @depends testAppendChunkTypeBanned
*/
- function testAppendChunk($combo) {
+ function testUploadChunkDoneTypeBanned( $data ) {
global $wgUser;
- $data = $combo['data'];
- $_SESSION = $combo['session'];
- $wgUser = User::newFromName(self::$userName);
+ $wgUser = User::newFromName( self::$userName );
$token = $wgUser->editToken();
+ $params = $data['param'];
+ $params['done'] = 1;
- $url = $data['uploadUrl'];
- $params = wfCgiToArray(substr($url, strpos($url, "?")));
+ $this->makeChunk( "123" );
+
+ $gotException = false;
+ try {
+ $data = $this->doApiRequest( $params, $data );
+ } catch ( UsageException $e ) {
+ $this->assertEquals( "This type of file is banned",
+ $e->getMessage() );
+ $gotException = true;
+ }
+ $this->cleanChunk();
+ $this->assertTrue( $gotException );
+ }
+
+ /**
+ * @depends testLogin
+ */
+ function testUploadChunkDoneDuplicate( $data ) {
+ global $wgUser, $wgVerifyMimeType;
+
+ $wgVerifyMimeType = false;
+ $wgUser = User::newFromName( self::$userName );
+ $data[2]['wsEditToken'] = $data[2]['wsToken'];
+ $token = md5( $data[2]['wsToken'] ) . EDIT_TOKEN_SUFFIX;
+ $data = $this->doApiRequest( array(
+ 'filename' => 'tmp.png',
+ 'action' => 'upload',
+ 'enablechunks' => true,
+ 'token' => $token ), $data );
+
+ $url = $data[0]['uploadUrl'];
+ $params = wfCgiToArray( substr( $url, strpos( $url, "?" ) ) );
+ $size = 0;
+ for ( $i = 0; $i < 4; $i++ ) {
+ $this->makeChunk( "123" );
+ $size += $_FILES['chunk']['size'];
+
+ $data = $this->doApiRequest( $params, $data );
+ $this->assertArrayHasKey( "result", $data[0] );
+ $this->assertTrue( (bool)$data[0]["result"] );
+
+ $this->assertArrayHasKey( "filesize", $data[0] );
+ $this->assertEquals( $size, $data[0]['filesize'] );
- for($i=0;$i<10;$i++) {
- $this->makeChunk();
- $data = $this->doApiRequest($params);
$this->cleanChunk();
}
- return array('data' => $data, 'session' => $_SESSION, 'token' => $token, 'params' => $params);
+ $params['done'] = true;
+
+ $this->makeChunk( "123" );
+ $data = $this->doApiRequest( $params, $data );
+ $this->cleanChunk();
+
+ $this->assertArrayHasKey( 'upload', $data[0] );
+ $this->assertArrayHasKey( 'result', $data[0]['upload'] );
+ $this->assertEquals( 'Warning', $data[0]['upload']['result'] );
+
+ $this->assertArrayHasKey( 'warnings', $data[0]['upload'] );
+ $this->assertArrayHasKey( 'exists',
+ $data[0]['upload']['warnings'] );
+ $this->assertEquals( 'Tmp.png',
+ $data[0]['upload']['warnings']['exists'] );
+
}
/**
- * @depends testAppendChunk
+ * @depends testLogin
*/
- function testUploadChunkDone($combo) {
- global $wgUser;
- $data = $combo['data'];
- $params = $combo['params'];
- $_SESSION = $combo['session'];
- $wgUser = User::newFromName(self::$userName);
- $token = $wgUser->editToken();
+ function testUploadChunkDoneGood( $data ) {
+ global $wgUser, $wgVerifyMimeType;
+ $wgVerifyMimeType = false;
- $params['done'] = 1;
+ $id = Title::newFromText( "Twar.png", NS_FILE )->getArticleID();
+
+ $oldFile = Article::newFromID( $id );
+ if ( $oldFile ) {
+ $oldFile->doDeleteArticle();
+ $oldFile->doPurge();
+ }
+ $oldFile = wfFindFile( "Twar.png" );
+ if ( $oldFile ) {
+ $oldFile->delete();
+ }
+
+ $wgUser = User::newFromName( self::$userName );
+ $data[2]['wsEditToken'] = $data[2]['wsToken'];
+ $token = md5( $data[2]['wsToken'] ) . EDIT_TOKEN_SUFFIX;
+ $data = $this->doApiRequest( array(
+ 'filename' => 'twar.png',
+ 'action' => 'upload',
+ 'enablechunks' => true,
+ 'token' => $token ), $data );
+
+ $url = $data[0]['uploadUrl'];
+ $params = wfCgiToArray( substr( $url, strpos( $url, "?" ) ) );
+ $size = 0;
+ for ( $i = 0; $i < 5; $i++ ) {
+ $this->makeChunk( "123" );
+ $size += $_FILES['chunk']['size'];
+
+ $data = $this->doApiRequest( $params, $data );
+ $this->assertArrayHasKey( "result", $data[0] );
+ $this->assertTrue( (bool)$data[0]["result"] );
+
+ $this->assertArrayHasKey( "filesize", $data[0] );
+ $this->assertEquals( $size, $data[0]['filesize'] );
+
+ $this->cleanChunk();
+ }
+
+ $params['done'] = true;
+
+ $this->makeChunk( "456" );
+ $data = $this->doApiRequest( $params, $data );
- $this->makeChunk();
- $data = $this->doApiRequest($params);
$this->cleanChunk();
+
+ if ( isset( $data[0]['upload'] ) ) {
+ $this->markTestSkipped( "Please run 'php maintenance/deleteArchivedFiles.php --delete --force' and 'php maintenance/deleteArchivedRevisions.php --delete'" );
+ }
+
+ $this->assertArrayHasKey( 'result', $data[0] );
+ $this->assertEquals( 1, $data[0]['result'] );
+
+ $this->assertArrayHasKey( 'done', $data[0] );
+ $this->assertEquals( 1, $data[0]['done'] );
+
+ $this->assertArrayHasKey( 'resultUrl', $data[0] );
+ $this->assertRegExp( '/File:Twar.png/', $data[0]['resultUrl'] );
}
}