wiki.techinc.nl/includes/upload/UploadFromChunks.php

245 lines
8.2 KiB
PHP

<?php
/**
* first destination checks are made (if ignorewarnings is not checked) errors / warning is returned.
*
* we return the uploadUrl
* we then accept chunk uploads from the client.
* return chunk id on each POSTED chunk
* once the client posts done=1 concatenated the files together.
* more info at: http://firefogg.org/dev/chunk_post.html
*/
class UploadFromChunks extends UploadBase {
var $chunk_mode; // init, chunk, done
var $mSessionKey = false;
var $status = array();
const INIT = 1;
const CHUNK = 2;
const DONE = 3;
function initializeFromParams( $param, &$request ) {
$this->initFromSessionKey( $param['chunksessionkey'], $request );
// set the chunk mode:
if( !$this->mSessionKey && !$param['done'] ){
// session key not set init the chunk upload system:
$this->chunk_mode = UploadFromChunks::INIT;
$this->mDesiredDestName = $param['filename'];
} else if( $this->mSessionKey && !$param['done'] ){
// this is a chunk piece
$this->chunk_mode = UploadFromChunks::CHUNK;
} else if( $this->mSessionKey && $param['done'] ){
// this is the last chunk
$this->chunk_mode = UploadFromChunks::DONE;
}
if( $this->chunk_mode == UploadFromChunks::CHUNK ||
$this->chunk_mode == UploadFromChunks::DONE ){
// set chunk related vars:
$this->mTempPath = $request->getFileTempName( 'chunk' );
$this->mFileSize = $request->getFileSize( 'chunk' );
}
return $this->status;
}
static function isValidRequest( $request ) {
$sessionData = $request->getSessionData( 'wsUploadData' );
if( !self::isValidSessionKey(
$request->getInt( 'wpSessionKey' ),
$sessionData ) )
return false;
// check for the file:
return (bool)$request->getFileTempName( 'file' );
}
/* check warnings depending on chunk_mode */
function checkWarnings(){
$warning = array();
return $warning;
}
function isEmptyFile(){
// does not apply to chunk init
if( $this->chunk_mode == UploadFromChunks::INIT ){
return false;
} else {
return parent::isEmptyFile();
}
}
/**
* Verify whether the upload is sane.
* Returns self::OK or else an array with error information
*/
function verifyUpload() {
// no checks on chunk upload mode:
if( $this->chunk_mode == UploadFromChunks::INIT )
return self::OK;
// verify on init and last chunk request
if( $this->chunk_mode == UploadFromChunks::CHUNK ||
$this->chunk_mode == UploadFromChunks::DONE )
return parent::verifyUpload();
}
// only run verifyFile on completed uploaded chunks
function verifyFile( $tmpFile ){
if( $this->chunk_mode == UploadFromChunks::DONE ){
// first append last chunk (so we can do a real verifyFile check... (check file type etc)
$status = $this->doChunkAppend();
if( $status->isOK() ){
$this->mTempPath = $this->getRealPath( $this->mTempAppendPath );
// verify the completed merged chunks as if it was the file that got uploaded:
return parent::verifyFile( $this->mTempPath );
} else {
// conflict of status returns (have to return the error ary) ... why we don't consistantly use a status object is beyond me..
return $status->getErrorsArray();
}
} else {
return true;
}
}
function getRealPath( $srcPath ){
$repo = RepoGroup::singleton()->getLocalRepo();
if ( $repo->isVirtualUrl( $srcPath ) ) {
return $repo->resolveVirtualUrl( $srcPath );
}
}
// pretty ugly inter-mixing of mParam and local vars
function setupChunkSession( $summary, $comment, $watch ) {
$this->mSessionKey = $this->getSessionKey();
$_SESSION['wsUploadData'][$this->mSessionKey] = array(
'mComment' => $comment,
'mSummary' => $summary,
'mWatch' => $watch,
'mIgnorewarnings' => true, //ignore warning on chunk uploads (for now)
'mFilteredName' => $this->mFilteredName,
'mTempAppendPath' => null, // the repo append path (not temporary local node mTempPath)
'mDesiredDestName' => $this->mDesiredDestName,
'version' => self::SESSION_VERSION,
);
return $this->mSessionKey;
}
function initFromSessionKey( $sessionKey, $request ){
if( !$sessionKey || empty( $sessionKey ) ){
return false;
}
$this->mSessionKey = $sessionKey;
// load the sessionData array:
$sessionData = $request->getSessionData( 'wsUploadData' );
if( isset( $sessionData[$this->mSessionKey]['version'] ) &&
$sessionData[$this->mSessionKey]['version'] == self::SESSION_VERSION ) {
// update the local object from the session
$this->mComment = $sessionData[$this->mSessionKey]['mComment'];
$this->mSummary = $sessionData[$this->mSessionKey]['mSummary'];
$this->mWatch = $sessionData[$this->mSessionKey]['mWatch'];
$this->mIgnorewarnings = $sessionData[$this->mSessionKey]['mIgnorewarnings'];
$this->mFilteredName = $sessionData[$this->mSessionKey]['mFilteredName'];
$this->mTempAppendPath = $sessionData[$this->mSessionKey]['mTempAppendPath'];
$this->mDesiredDestName = $sessionData[$this->mSessionKey]['mDesiredDestName'];
} else {
$this->status = array( 'error' => 'missing session data' );
return false;
}
}
// Lets us return an api result (as flow for chunk uploads is kind of different than others.
function performUpload( $summary = '', $comment = '', $watch = '', $user ){
global $wgServer, $wgScriptPath, $wgUser;
if( $this->chunk_mode == UploadFromChunks::INIT ){
// firefogg expects a specific result per:
// http://www.firefogg.org/dev/chunk_post.html
// it's okay to return the token here because
// 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() );
ob_clean();
echo ApiFormatJson::getJsonEncode( array(
'uploadUrl' => "{$wgServer}{$wgScriptPath}/api.php?action=upload&".
"token={$token}&format=json&enablechunks=true&chunksessionkey=".
$this->setupChunkSession( $summary, $comment, $watch ) ) );
exit( 0 );
} else if( $this->chunk_mode == UploadFromChunks::CHUNK ){
$status = $this->doChunkAppend();
if( $status->isOK() ){
// return success:
// firefogg expects a specific result per:
// http://www.firefogg.org/dev/chunk_post.html
ob_clean();
echo ApiFormatJson::getJsonEncode( array(
'result' => 1,
'filesize' => filesize( $this->getRealPath( $this->mTempAppendPath ) )
)
);
exit( 0 );
/*return array(
'result' => 1
);*/
} else {
return $status;
}
} else if( $this->chunk_mode == UploadFromChunks::DONE ){
// update the values from the local (session init) if not paseed again)
if( $summary == '' )
$summary = $this->mSummary;
if( $comment == '' )
$comment = $this->mComment;
if( $watch == '' )
$watch = $this->mWatch;
$status = parent::performUpload( $summary, $comment, $watch, $user );
if( !$status->isGood() ) {
return $status;
}
$file = $this->getLocalFile();
// firefogg expects a specific result per:
// http://www.firefogg.org/dev/chunk_post.html
ob_clean();
echo ApiFormatJson::getJsonEncode( array(
'result' => 1,
'done' => 1,
'resultUrl' => $file->getDescriptionUrl()
)
);
exit( 0 );
}
}
// append the given chunk to the temporary uploaded file. (if no temporary uploaded file exists created it.
function doChunkAppend(){
global $wgMaxUploadSize;
// if we don't have a mTempAppendPath to generate a file from the chunk packaged var:
if( !$this->mTempAppendPath ){
// get temp name:
// make a chunk store path. (append tmp file to chunk)
$status = $this->saveTempUploadedFile( $this->mDestName, $this->mTempPath );
if( $status->isOK() ) {
$this->mTempAppendPath = $status->value;
$_SESSION['wsUploadData'][$this->mSessionKey]['mTempAppendPath'] = $this->mTempAppendPath;
}
return $status;
} else {
if( is_file( $this->getRealPath( $this->mTempAppendPath ) ) ){
$status = $this->appendToUploadFile( $this->mTempAppendPath, $this->mTempPath );
} else {
$status = Status::newFatal( 'filenotfound', $this->mTempAppendPath );
}
//check to make sure we have not expanded beyond $wgMaxUploadSize
if( filesize( $this->getRealPath( $this->mTempAppendPath ) ) > $wgMaxUploadSize )
$status = Status::newFatal( 'largefileserver' );
return $status;
}
}
}