'FakeTitle' => 'includes/FakeTitle.php',
'FakeMemCachedClient' => 'includes/ObjectCache.php',
'FauxRequest' => 'includes/WebRequest.php',
+ 'FauxResponse' => 'includes/WebResponse.php',
'FeedItem' => 'includes/Feed.php',
'FeedUtils' => 'includes/FeedUtils.php',
'FileDeleteForm' => 'includes/FileDeleteForm.php',
class FauxRequest extends WebRequest {
private $wasPosted = false;
private $session = array();
+ private $response;
/**
* @param $data Array of *non*-urlencoded key => value pairs, the
}
public function getSessionData( $key ) {
- if( !isset( $this->session[$key] ) )
- return null;
- return $this->session[$key];
+ if( isset( $this->session[$key] ) )
+ return $this->session[$key];
}
public function setSessionData( $key, $data ) {
return false;
}
+ public function response() {
+ /* Lazy initialization of response object for this request */
+ if ( !is_object( $this->response ) ) {
+ $this->response = new FauxResponse;
+ }
+ return $this->response;
+ }
}
*/
class WebResponse {
- /**
+ /**
* Output a HTTP header, wrapper for PHP's
* header()
* @param $string String: header to output
}
}
}
+
+
+class FauxResponse extends WebResponse {
+ private $headers;
+ private $cookies;
+
+ public function header($string, $replace=true) {
+ list($key, $val) = explode(":", $string, 2);
+
+ if($replace || !isset($this->headers[$key])) {
+ $this->headers[$key] = $val;
+ }
+ }
+
+ public function getheader($key) {
+ return $this->headers[$key];
+ }
+
+ public function setcookie( $name, $value, $expire = 0 ) {
+ $this->cookies[$name] = $value;
+ }
+
+ public function getcookie( $name ) {
+ if ( isset($this->cookies[$name]) ) {
+ return $this->cookies[$name];
+ }
+ }
+}
\ No newline at end of file
public function execute() {
global $wgUser, $wgAllowCopyUploads;
+ // Check whether upload is enabled
+ if ( !UploadBase::isEnabled() )
+ $this->dieUsageMsg( array( 'uploaddisabled' ) );
+
$this->getMain()->isWriteMode();
$this->mParams = $this->extractRequestParams();
$request = $this->getMain()->getRequest();
// Add the uploaded file to the params array
$this->mParams['file'] = $request->getFileName( 'file' );
- // Check whether upload is enabled
- if ( !UploadBase::isEnabled() )
- $this->dieUsageMsg( array( 'uploaddisabled' ) );
-
// One and only one of the following parameters is needed
$this->requireOnlyOneParameter( $this->mParams,
'sessionkey', 'file', 'url', 'enablechunks' );
- if ( $this->mParams['sessionkey'] ) {
+ // Initialize $this->mUpload
+ if ( $this->mParams['enablechunks'] ) {
+ $this->mUpload = new UploadFromChunks();
+
+ $this->mUpload->initialize(
+ $request->getVal( 'done', null ),
+ $request->getVal( 'filename', null ),
+ $request->getVal( 'chunksession', null ),
+ $request->getFileTempName( 'chunk' ),
+ $request->getFileSize( 'chunk' ),
+ $request->getSessionData( 'wsUploadData' )
+ );
+
+ if ( !$this->mUpload->status->isOK() ) {
+ return $this->dieUsageMsg( $this->mUpload->status->getErrorsArray() );
+ }
+ } elseif ( $this->mParams['sessionkey'] ) {
/**
* Upload stashed in a previous request
*/
$this->mUpload = new UploadFromStash();
$this->mUpload->initialize( $this->mParams['filename'],
$_SESSION['wsUploadData'][$this->mParams['sessionkey']] );
- } else {
+ } elseif ( isset( $this->mParams['filename'] ) ) {
/**
* Upload from url, etc
* Parameter filename is required
*/
- if ( !isset( $this->mParams['filename'] ) )
- $this->dieUsageMsg( array( 'missingparam', 'filename' ) );
- // Initialize $this->mUpload
- if ( $this->mParams['enablechunks'] ) {
- $this->mUpload = new UploadFromChunks();
- $this->mUpload->initialize(
- $request->getVal( 'done', null ),
- $request->getVal( 'filename', null ),
- $request->getVal( 'chunksessionkey', null ),
- $request->getFileTempName( 'chunk' ),
- $request->getFileSize( 'chunk' ),
- $request->getSessionData( 'wsUploadData' )
- );
-
- if ( !$this->mUpload->status->isOK() ) {
- return $this->dieUsageMsg( $this->mUpload->status->getErrorsArray() );
- }
- } elseif ( isset( $this->mParams['file'] ) ) {
+ if ( isset( $this->mParams['file'] ) ) {
$this->mUpload = new UploadFromFile();
$this->mUpload->initialize(
$this->mParams['filename'],
return $this->dieUsage( $status->getWikiText(), 'fetchfileerror' );
}
}
- }
+ } else $this->dieUsageMsg( array( 'missingparam', 'filename' ) );
if ( !isset( $this->mUpload ) )
$this->dieUsage( 'No upload module set', 'nomodule' );
$this->getResult()->setIndexedTagName( $result['details'], 'error' );
$this->dieUsage( 'An internal error occurred', 'internal-error', 0, $error );
+ } elseif( isset($status->value->uploadUrl) ) {
+ return $status->value;
}
$file = $this->mUpload->getLocalFile();
'ignorewarnings' => false,
'file' => null,
'enablechunks' => false,
- 'chunksessionkey' => null,
+ 'chunksession' => null,
'chunk' => null,
'done' => false,
'url' => null,
'ignorewarnings' => 'Ignore any warnings',
'file' => 'File contents',
'enablechunks' => 'Set to use chunk mode; see http://firefogg.org/dev/chunk_post.html for protocol',
- 'chunksessionkey' => 'The session key, established on the first contact during the chunked upload',
+ 'chunksession' => 'The session key, established on the first contact during the chunked upload',
'chunk' => 'The data in this chunk of a chunked upload',
'done' => 'Set to 1 on the last chunk of a chunked upload',
'url' => 'Url to fetch the file from',
return $status;
}
+ function append( $srcPath, $toAppendPath ) {
+ $status = $this->newGood();
+
+ // Resolve the virtual URL
+ if ( self::isVirtualUrl( $srcPath ) ) {
+ $srcPath = $this->resolveVirtualUrl( $srcPath );
+ }
+ // Make sure the files are there
+ if ( !is_file( $srcPath ) )
+ $status->fatal( 'append-src-filenotfound', $srcPath );
+
+ if ( !is_file( $toAppendPath ) )
+ $status->fatal( 'append-toappend-filenotfound', $toAppendPath );
+
+ // Do the append
+ if( file_put_contents( $srcPath, file_get_contents( $toAppendPath ), FILE_APPEND ) ) {
+ $status->value = $srcPath;
+ } else {
+ $status->fatal( 'fileappenderror', $toAppendPath, $srcPath);
+ }
+
+ // Remove the source file
+ unlink( $toAppendPath );
+
+ return $status;
+ }
+
/**
* Checks existence of specified array of files.
*
}
return strtr( $param, $this->simpleCleanPairs );
}
-
+
/**
* Chmod a file, supressing the warnings.
* @param String $path The path to change
// Optional settings
$this->initialCapital = MWNamespace::isCapitalized( NS_FILE );
foreach ( array( 'descBaseUrl', 'scriptDirUrl', 'articleUrl', 'fetchDescription',
- 'thumbScriptUrl', 'initialCapital', 'pathDisclosureProtection',
+ 'thumbScriptUrl', 'initialCapital', 'pathDisclosureProtection',
'descriptionCacheExpiry', 'hashLevels', 'url', 'thumbUrl' ) as $var )
{
if ( isset( $info[$var] ) ) {
*
* ignoreRedirect: If true, do not follow file redirects
*
- * private: If true, return restricted (deleted) files if the current
+ * private: If true, return restricted (deleted) files if the current
* user is allowed to view them. Otherwise, such files will not
* be found.
*/
}
}
}
-
+
# Now try redirects
if ( !empty( $options['ignoreRedirect'] ) ) {
return false;
}
- $redir = $this->checkRedirect( $title );
+ $redir = $this->checkRedirect( $title );
if( $redir && $redir->getNamespace() == NS_FILE) {
$img = $this->newFile( $redir );
if( !$img ) {
}
return false;
}
-
+
/*
- * Find many files at once.
+ * Find many files at once.
* @param array $items, an array of titles, or an array of findFile() options with
* the "title" option giving the title. Example:
*
}
return $result;
}
-
+
/**
* Create a new File object from the local repository
* @param mixed $sha1 SHA-1 key
return call_user_func( $this->fileFactoryKey, $sha1, $this );
}
}
-
+
/**
* Find an instance of the file with this key, created at the specified time
* Returns false if the file does not exist. Repositories not supporting
* version control should return false if the time is specified.
*
* @param string $sha1 string
- * @param array $options Option array, same as findFile().
+ * @param array $options Option array, same as findFile().
*/
function findFileFromKey( $sha1, $options = array() ) {
if ( !is_array( $options ) ) {
function getThumbScriptUrl() {
return $this->thumbScriptUrl;
}
-
+
/**
* Get the URL corresponding to one of the four basic zones
* @param String $zone One of: public, deleted, temp, thumb
return $path;
}
}
-
+
/**
* Get a relative path including trailing slash, e.g. f/fa/
* If the repo is not hashed, returns an empty string
*/
abstract function storeTemp( $originalName, $srcPath );
+ abstract function append( $srcPath, $toAppendPath );
+
/**
* Remove a temporary file or mark it for garbage collection
* @param string $virtualUrl The virtual URL returned by storeTemp
/**
* Invalidates image redirect cache related to that image
* Doesn't do anything for repositories that don't support image redirects.
- *
+ *
* STUB
* @param Title $title Title of image
- */
+ */
function invalidateImageRedirect( $title ) {}
-
+
/**
- * Get an array or iterator of file objects for files that have a given
+ * Get an array or iterator of file objects for files that have a given
* SHA-1 content hash.
*
* STUB
function findBySha1( $hash ) {
return array();
}
-
+
/**
- * Get the human-readable name of the repo.
+ * Get the human-readable name of the repo.
* @return string
*/
public function getDisplayName() {
if ( !wfEmptyMsg( 'shared-repo-name-' . $this->name, $repoName ) ) {
return $repoName;
}
- return wfMsg( 'shared-repo' );
+ return wfMsg( 'shared-repo' );
}
-
+
/**
* Get a key on the primary cache for this repository.
- * Returns false if the repository's cache is not accessible at this site.
+ * Returns false if the repository's cache is not accessible at this site.
* The parameters are the parts of the key, as for wfMemcKey().
*
* STUB
}
/**
- * Get a key for this repo in the local cache domain. These cache keys are
+ * Get a key for this repo in the local cache domain. These cache keys are
* not shared with remote instances of the repo.
* The parameters are the parts of the key, as for wfMemcKey().
*/
var $apiThumbCacheExpiry = 86400;
protected $mQueryCache = array();
protected $mFileExists = array();
-
+
function __construct( $info ) {
parent::__construct( $info );
$this->mApiBase = $info['apibase']; // http://commons.wikimedia.org/w/api.php
$this->thumbUrl = $this->url . '/thumb';
}
}
-
+
/**
* Per docs in FileRepo, this needs to return false if we don't support versioned
* files. Well, we don't.
function storeTemp( $originalName, $srcPath ) {
return false;
}
+ function append( $srcPath, $toAppendPath ){
+ return false;
+ }
function publishBatch( $triplets, $flags = 0 ) {
return false;
}
function deleteBatch( $sourceDestPairs ) {
return false;
}
-
+
function fileExistsBatch( $files, $flags = 0 ) {
$results = array();
function getFileProps( $virtualUrl ) {
return false;
}
-
+
protected function queryImage( $query ) {
$data = $this->fetchImageQuery( $query );
-
+
if( isset( $data['query']['pages'] ) ) {
foreach( $data['query']['pages'] as $pageid => $info ) {
if( isset( $info['imageinfo'][0] ) ) {
}
return false;
}
-
+
protected function fetchImageQuery( $query ) {
global $wgMemc;
-
+
$url = $this->mApiBase .
'?' .
wfArrayToCgi(
array(
'format' => 'json',
'action' => 'query' ) ) );
-
+
if( !isset( $this->mQueryCache[$url] ) ) {
$key = $this->getLocalCacheKey( 'ForeignAPIRepo', 'Metadata', md5( $url ) );
$data = $wgMemc->get( $key );
}
return FormatJson::decode( $this->mQueryCache[$url], true );
}
-
+
function getImageInfo( $title, $time = false ) {
return $this->queryImage( array(
'titles' => 'Image:' . $title->getText(),
'iiprop' => 'timestamp|user|comment|url|size|sha1|metadata|mime',
'prop' => 'imageinfo' ) );
}
-
+
function findBySha1( $hash ) {
$results = $this->fetchImageQuery( array(
'aisha1base36' => $hash,
}
return $ret;
}
-
+
function getThumbUrl( $name, $width=-1, $height=-1 ) {
$info = $this->queryImage( array(
'titles' => 'Image:' . $name,
return false;
}
}
-
+
function getThumbUrlFromCache( $name, $width, $height ) {
global $wgMemc, $wgUploadPath, $wgServer, $wgUploadDirectory;
-
+
if ( !$this->canCacheThumbs() ) {
return $this->getThumbUrl( $name, $width, $height );
}
-
+
$key = $this->getLocalCacheKey( 'ForeignAPIRepo', 'ThumbUrl', $name );
if ( $thumbUrl = $wgMemc->get($key) ) {
wfDebug("Got thumb from local cache. $thumbUrl \n");
}
else {
$foreignUrl = $this->getThumbUrl( $name, $width, $height );
-
+
// We need the same filename as the remote one :)
$fileName = rawurldecode( pathinfo( $foreignUrl, PATHINFO_BASENAME ) );
$path = 'thumb/' . $this->getHashPath( $name ) . $name . "/";
return $localUrl;
}
}
-
+
/**
* @see FileRepo::getZoneUrl()
*/
function storeTemp( $originalName, $srcPath ) {
return false;
}
+ function append( $srcPath, $toAppendPath ){
+ return false;
+ }
function publishBatch( $triplets, $flags = 0 ) {
return false;
}
protected $chunkMode; // INIT, CHUNK, DONE
protected $sessionKey;
protected $comment;
- protected $fileSize = 0;
protected $repoPath;
protected $pageText;
protected $watch;
throw new MWException( 'not implemented' );
}
- public function initialize( $done, $filename, $sessionKey, $path,
- $fileSize, $sessionData )
- {
+ public function initialize( $done, $filename, $sessionKey, $path, $fileSize, $sessionData ) {
+ global $wgTmpDirectory;
$this->status = new Status;
$this->initFromSessionKey( $sessionKey, $sessionData );
if ( !$this->sessionKey && !$done ) {
// session key not set, init the chunk upload system:
$this->chunkMode = self::INIT;
- $this->mDesiredDestName = $filename;
+ $this->initializePathInfo( $filename, $path, 0, true);
} else if ( $this->sessionKey && !$done ) {
$this->chunkMode = self::CHUNK;
} else if ( $this->sessionKey && $done ) {
}
if ( $this->chunkMode == self::CHUNK || $this->chunkMode == self::DONE ) {
$this->mTempPath = $path;
- $this->fileSize += $fileSize;
+ $this->mFileSize += $fileSize;
}
}
// a) the user must have requested the token to get here and
// b) should only happen over POST
// c) we need the token to validate chunks are coming from a non-xss request
- $token = urlencode( $wgUser->editToken() );
- echo FormatJson::encode( array(
- 'uploadUrl' => wfExpandUrl( wfScript( 'api' ) ) . "?action=upload&" .
- "token={$token}&format=json&enablechunks=true&chunksessionkey=" .
- $this->setupChunkSession( $comment, $pageText, $watch ) ) );
- $wgOut->disable();
+ 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 ) ) ) ) );
} else if ( $this->chunkMode == self::CHUNK ) {
- $status = $this->appendChunk();
- if ( !$status->isOK() ) {
- return $status;
+ $this->appendChunk();
+ if ( !$this->status->isOK() ) {
+ return $this->status;
}
// return success:
// firefogg expects a specific result
// http://www.firefogg.org/dev/chunk_post.html
- echo FormatJson::encode(
- array( 'result' => 1, 'filesize' => $this->fileSize )
+ return Status::newGood(
+ array( 'result' => 1, 'filesize' => $this->mFileSize )
);
- $wgOut->disable();
} else if ( $this->chunkMode == self::DONE ) {
if ( !$comment )
$comment = $this->comment;
// firefogg expects a specific result
// http://www.firefogg.org/dev/chunk_post.html
- echo FormatJson::encode( array(
- 'result' => 1,
- 'done' => 1,
- 'resultUrl' => $file->getDescriptionUrl() )
+ return Status::newGood(
+ array('result' => 1, 'done' => 1, 'resultUrl' => $file->getDescriptionUrl() )
);
- $wgOut->disable();
}
return Status::newGood();
if ( !$this->repoPath ) {
$this->status = $this->saveTempUploadedFile( $this->mDesiredDestName, $this->mTempPath );
- if ( $status->isOK() ) {
- $this->repoPath = $status->value;
+ if ( $this->status->isOK() ) {
+ $this->repoPath = $this->status->value;
$_SESSION['wsUploadData'][$this->sessionKey]['repoPath'] = $this->repoPath;
}
- return $status;
+ return;
}
if ( $this->getRealPath( $this->repoPath ) ) {
$this->status = $this->appendToUploadFile( $this->repoPath, $this->mTempPath );
} else {
$this->status = Status::newFatal( 'filenotfound', $this->repoPath );
}
- if ( $this->fileSize > $wgMaxUploadSize )
+ if ( $this->mFileSize > $wgMaxUploadSize )
$this->status = Status::newFatal( 'largefileserver' );
}
$wgEnableUploads=true;
ini_set('file_loads', true);
+ parent::setup();
+
+ }
+
+ function makeChunk() {
+ $file = tempnam( wfTempDir(), "" );
+ $fh = fopen($file, "w");
+ if($fh == false) {
+ $this->markTestIncomplete("Couldn't open $file!\n");
+ return;
+ }
+ fwrite($fh, "123");
+ fclose($fh);
+
+ $_FILES['chunk']['tmp_name'] = $file;
+ $_FILES['chunk']['size'] = 3;
+ $_FILES['chunk']['error'] = null;
+ $_FILES['chunk']['name'] = "test.txt";
+ }
+
+ function cleanChunk() {
+ 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);
+ $module->execute();
+
+ return $module->getResultData();
}
function testGetTitle() {
$this->assertEquals(Title::makeTitleSafe(NS_FILE, "Temp.png"), $c->getTitle());
}
- function testGetEditToken() {
+ 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']);
+
+ return $data;
}
- function testInitFromSessionKey() {
- }
+ /**
+ * @depends testLogin
+ */
+ function testGetEditToken($data) {
+ global $wgUser;
+ $wgUser = User::newFromName(self::$userName);
+ $wgUser->load();
- function testInitialize() {
+ $data = $this->doApiRequest(array('action' => 'query',
+ 'prop' => 'info',
+ 'intoken' => 'edit'));
}
function testSetupChunkSession() {
}
- function makeChunk() {
- $file = tempnam( wfTempDir(), "" );
- $fh = fopen($file, "w");
- if($fh == false) {
- $this->markTestIncomplete("Couldn't open $file!\n");
- return;
- }
- fwrite($fh, "123");
- fclose($fh);
-
- $_FILES['chunk']['tmp_name'] = $file;
- $_FILES['chunk']['size'] = 3;
- $_FILES['chunk']['error'] = null;
- $_FILES['chunk']['name'] = "test.txt";
- }
-
- function cleanChunk() {
- unlink($_FILES['chunk']['tmp_name']);
- }
-
/**
* @expectedException UsageException
*/
function testPerformUploadInitError() {
global $wgUser;
-
$wgUser = User::newFromId(1);
- $token = $wgUser->editToken();
- $this->makeChunk();
$req = new FauxRequest(
array('action' => 'upload',
'enablechunks' => '1',
'filename' => 'test.png',
- 'token' => $token,
));
$module = new ApiMain($req, true);
$module->execute();
}
- function testPerformUploadInitSuccess() {
+ /**
+ * @depends testLogin
+ */
+ function testPerformUploadInitSuccess($login) {
global $wgUser;
- $wgUser = User::newFromId(1);
+ $wgUser = User::newFromName(self::$userName);
$token = $wgUser->editToken();
- $this->makeChunk();
- $req = new FauxRequest(
+ $data = $this->doApiRequest(
array('action' => 'upload',
'enablechunks' => '1',
'filename' => 'test.png',
'token' => $token,
));
- $module = new ApiMain($req, true);
- $module->execute();
- }
- function testAppendToUploadFile() {
- }
+ $this->assertArrayHasKey("upload", $data);
+ $this->assertArrayHasKey("uploadUrl", $data['upload']);
- function testAppendChunk() {
+ return array('data' => $data, 'session' => $_SESSION, 'token' => $token);
}
- function testPeformUploadChunk() {
- }
+ /**
+ * @depends testPerformUploadInitSuccess
+ */
+ function testAppendChunk($combo) {
+ global $wgUser;
+ $data = $combo['data'];
+ $_SESSION = $combo['session'];
+ $wgUser = User::newFromName(self::$userName);
+ $token = $wgUser->editToken();
+
+ $url = $data['upload']['uploadUrl'];
+ $params = wfCgiToArray(substr($url, strpos($url, "?")));
+
+ for($i=0;$i<10;$i++) {
+ $this->makeChunk();
+ $data = $this->doApiRequest($params);
+ $this->cleanChunk();
+ }
- function testPeformUploadDone() {
+ return array('data' => $data, 'session' => $_SESSION, 'token' => $token, 'params' => $params);
}
+ /**
+ * @depends testAppendChunk
+ */
+ function testUploadChunkDone($combo) {
+ global $wgUser;
+ $data = $combo['data'];
+ $params = $combo['params'];
+ $_SESSION = $combo['session'];
+ $wgUser = User::newFromName(self::$userName);
+ $token = $wgUser->editToken();
+ $params['done'] = 1;
+ $this->makeChunk();
+ $data = $this->doApiRequest($params);
+ $this->cleanChunk();
+ }
}