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