Removed the $tmpFile parameter to UploadBase::verifyUpload in favour of $this->mTempFile.
[lhc/web/wiklou.git] / includes / upload / UploadFromChunks.php
1 <?php
2 /**
3 * @file
4 * @ingroup upload
5 *
6 * @author Michael Dale
7 *
8 * first destination checks are made (if ignorewarnings is not checked) errors / warning is returned.
9 *
10 * we return the uploadUrl
11 * we then accept chunk uploads from the client.
12 * return chunk id on each POSTED chunk
13 * once the client posts done=1 concatenated the files together.
14 * more info at: http://firefogg.org/dev/chunk_post.html
15 */
16 class UploadFromChunks extends UploadBase {
17
18 var $chunk_mode; // init, chunk, done
19 var $mSessionKey = false;
20 var $status = array();
21
22 const INIT = 1;
23 const CHUNK = 2;
24 const DONE = 3;
25
26 function initializeFromParams( $param, &$request ) {
27 $this->initFromSessionKey( $param['chunksessionkey'], $request );
28 // set the chunk mode:
29 if( !$this->mSessionKey && !$param['done'] ){
30 // session key not set init the chunk upload system:
31 $this->chunk_mode = UploadFromChunks::INIT;
32 $this->mDesiredDestName = $param['filename'];
33 } else if( $this->mSessionKey && !$param['done'] ){
34 // this is a chunk piece
35 $this->chunk_mode = UploadFromChunks::CHUNK;
36 } else if( $this->mSessionKey && $param['done'] ){
37 // this is the last chunk
38 $this->chunk_mode = UploadFromChunks::DONE;
39 }
40 if( $this->chunk_mode == UploadFromChunks::CHUNK ||
41 $this->chunk_mode == UploadFromChunks::DONE ){
42 // set chunk related vars:
43 $this->mTempPath = $request->getFileTempName( 'chunk' );
44 $this->mFileSize = $request->getFileSize( 'chunk' );
45 }
46
47 return $this->status;
48 }
49
50 static function isValidRequest( $request ) {
51 $sessionData = $request->getSessionData( 'wsUploadData' );
52 if( !self::isValidSessionKey(
53 $request->getInt( 'wpSessionKey' ),
54 $sessionData ) )
55 return false;
56 // check for the file:
57 return (bool)$request->getFileTempName( 'file' );
58 }
59
60 /* check warnings depending on chunk_mode */
61 function checkWarnings(){
62 $warning = array();
63 return $warning;
64 }
65
66 function isEmptyFile(){
67 // does not apply to chunk init
68 if( $this->chunk_mode == UploadFromChunks::INIT ){
69 return false;
70 } else {
71 return parent::isEmptyFile();
72 }
73 }
74
75 /**
76 * Verify whether the upload is sane.
77 * Returns self::OK or else an array with error information
78 */
79 function verifyUpload() {
80 // no checks on chunk upload mode:
81 if( $this->chunk_mode == UploadFromChunks::INIT )
82 return self::OK;
83
84 // verify on init and last chunk request
85 if( $this->chunk_mode == UploadFromChunks::CHUNK ||
86 $this->chunk_mode == UploadFromChunks::DONE )
87 return parent::verifyUpload();
88 }
89
90 // only run verifyFile on completed uploaded chunks
91 function verifyFile(){
92 if( $this->chunk_mode == UploadFromChunks::DONE ){
93 // first append last chunk (so we can do a real verifyFile check... (check file type etc)
94 $status = $this->doChunkAppend();
95 if( $status->isOK() ){
96 $this->mTempPath = $this->getRealPath( $this->mTempAppendPath );
97 // verify the completed merged chunks as if it was the file that got uploaded:
98 return parent::verifyFile( $this->mTempPath );
99 } else {
100 // conflict of status returns (have to return the error ary) ... why we don't consistantly use a status object is beyond me..
101 return $status->getErrorsArray();
102 }
103 } else {
104 return true;
105 }
106 }
107
108 function getRealPath( $srcPath ){
109 $repo = RepoGroup::singleton()->getLocalRepo();
110 if ( $repo->isVirtualUrl( $srcPath ) ) {
111 return $repo->resolveVirtualUrl( $srcPath );
112 }
113 }
114
115 // pretty ugly inter-mixing of mParam and local vars
116 function setupChunkSession( $summary, $comment, $watch ) {
117 $this->mSessionKey = $this->getSessionKey();
118 $_SESSION['wsUploadData'][$this->mSessionKey] = array(
119 'mComment' => $comment,
120 'mSummary' => $summary,
121 'mWatch' => $watch,
122 'mIgnorewarnings' => true, //ignore warning on chunk uploads (for now)
123 'mFilteredName' => $this->mFilteredName,
124 'mTempAppendPath' => null, // the repo append path (not temporary local node mTempPath)
125 'mDesiredDestName' => $this->mDesiredDestName,
126 'version' => self::SESSION_VERSION,
127 );
128 return $this->mSessionKey;
129 }
130
131 function initFromSessionKey( $sessionKey, $request ){
132 if( !$sessionKey || empty( $sessionKey ) ){
133 return false;
134 }
135 $this->mSessionKey = $sessionKey;
136 // load the sessionData array:
137 $sessionData = $request->getSessionData( 'wsUploadData' );
138
139 if( isset( $sessionData[$this->mSessionKey]['version'] ) &&
140 $sessionData[$this->mSessionKey]['version'] == self::SESSION_VERSION ) {
141 // update the local object from the session
142 $this->mComment = $sessionData[$this->mSessionKey]['mComment'];
143 $this->mSummary = $sessionData[$this->mSessionKey]['mSummary'];
144 $this->mWatch = $sessionData[$this->mSessionKey]['mWatch'];
145 $this->mIgnorewarnings = $sessionData[$this->mSessionKey]['mIgnorewarnings'];
146 $this->mFilteredName = $sessionData[$this->mSessionKey]['mFilteredName'];
147 $this->mTempAppendPath = $sessionData[$this->mSessionKey]['mTempAppendPath'];
148 $this->mDesiredDestName = $sessionData[$this->mSessionKey]['mDesiredDestName'];
149 } else {
150 $this->status = array( 'error' => 'missing session data' );
151 return false;
152 }
153 }
154
155 // Lets us return an api result (as flow for chunk uploads is kind of different than others.
156 function performUpload( $summary = '', $comment = '', $watch = '', $user ){
157 global $wgServer, $wgScriptPath, $wgUser;
158
159 if( $this->chunk_mode == UploadFromChunks::INIT ){
160 // firefogg expects a specific result per:
161 // http://www.firefogg.org/dev/chunk_post.html
162
163 // it's okay to return the token here because
164 // a) the user must have requested the token to get here and
165 // b) should only happen over POST
166 // c) (we need the token to validate chunks are coming from a non-xss request)
167 $token = urlencode( $wgUser->editToken() );
168 ob_clean();
169 echo ApiFormatJson::getJsonEncode( array(
170 'uploadUrl' => "{$wgServer}{$wgScriptPath}/api.php?action=upload&".
171 "token={$token}&format=json&enablechunks=true&chunksessionkey=".
172 $this->setupChunkSession( $summary, $comment, $watch ) ) );
173 exit( 0 );
174 } else if( $this->chunk_mode == UploadFromChunks::CHUNK ){
175 $status = $this->doChunkAppend();
176 if( $status->isOK() ){
177 // return success:
178 // firefogg expects a specific result per:
179 // http://www.firefogg.org/dev/chunk_post.html
180 ob_clean();
181 echo ApiFormatJson::getJsonEncode( array(
182 'result' => 1,
183 'filesize' => filesize( $this->getRealPath( $this->mTempAppendPath ) )
184 )
185 );
186 exit( 0 );
187 /*return array(
188 'result' => 1
189 );*/
190 } else {
191 return $status;
192 }
193 } else if( $this->chunk_mode == UploadFromChunks::DONE ){
194 // update the values from the local (session init) if not paseed again)
195 if( $summary == '' )
196 $summary = $this->mSummary;
197
198 if( $comment == '' )
199 $comment = $this->mComment;
200
201 if( $watch == '' )
202 $watch = $this->mWatch;
203 $status = parent::performUpload( $summary, $comment, $watch, $user );
204 if( !$status->isGood() ) {
205 return $status;
206 }
207 $file = $this->getLocalFile();
208 // firefogg expects a specific result per:
209 // http://www.firefogg.org/dev/chunk_post.html
210 ob_clean();
211 echo ApiFormatJson::getJsonEncode( array(
212 'result' => 1,
213 'done' => 1,
214 'resultUrl' => $file->getDescriptionUrl()
215 )
216 );
217 exit( 0 );
218
219 }
220 }
221
222 // append the given chunk to the temporary uploaded file. (if no temporary uploaded file exists created it.
223 function doChunkAppend(){
224 global $wgMaxUploadSize;
225 // if we don't have a mTempAppendPath to generate a file from the chunk packaged var:
226 if( !$this->mTempAppendPath ){
227 // get temp name:
228 // make a chunk store path. (append tmp file to chunk)
229 $status = $this->saveTempUploadedFile( $this->mDestName, $this->mTempPath );
230
231 if( $status->isOK() ) {
232 $this->mTempAppendPath = $status->value;
233 $_SESSION['wsUploadData'][$this->mSessionKey]['mTempAppendPath'] = $this->mTempAppendPath;
234 }
235 return $status;
236 } else {
237 if( is_file( $this->getRealPath( $this->mTempAppendPath ) ) ){
238 $status = $this->appendToUploadFile( $this->mTempAppendPath, $this->mTempPath );
239 } else {
240 $status = Status::newFatal( 'filenotfound', $this->mTempAppendPath );
241 }
242 //check to make sure we have not expanded beyond $wgMaxUploadSize
243 if( filesize( $this->getRealPath( $this->mTempAppendPath ) ) > $wgMaxUploadSize )
244 $status = Status::newFatal( 'largefileserver' );
245
246 return $status;
247 }
248 }
249
250 }