* Fix up ApiTest a bit, cookie handling works
[lhc/web/wiklou.git] / includes / api / ApiUpload.php
1 <?php
2 /*
3 * Created on Aug 21, 2008
4 * API for MediaWiki 1.8+
5 *
6 * Copyright (C) 2008 - 2010 Bryan Tong Minh <Bryan.TongMinh@Gmail.com>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License along
19 * with this program; if not, write to the Free Software Foundation, Inc.,
20 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 * http://www.gnu.org/copyleft/gpl.html
22 */
23
24 if ( !defined( 'MEDIAWIKI' ) ) {
25 // Eclipse helper - will be ignored in production
26 require_once( "ApiBase.php" );
27 }
28
29 /**
30 * @ingroup API
31 */
32 class ApiUpload extends ApiBase {
33 protected $mUpload = null;
34 protected $mParams;
35
36 public function __construct( $main, $action ) {
37 parent::__construct( $main, $action );
38 }
39
40 public function execute() {
41 global $wgUser, $wgAllowCopyUploads;
42
43 $this->getMain()->isWriteMode();
44 $this->mParams = $this->extractRequestParams();
45 $request = $this->getMain()->getRequest();
46
47 // Do token checks:
48 if ( is_null( $this->mParams['token'] ) )
49 $this->dieUsageMsg( array( 'missingparam', 'token' ) );
50 if ( !$wgUser->matchEditToken( $this->mParams['token'] ) )
51 $this->dieUsageMsg( array( 'sessionfailure' ) );
52
53 // Add the uploaded file to the params array
54 $this->mParams['file'] = $request->getFileName( 'file' );
55
56 // Check whether upload is enabled
57 if ( !UploadBase::isEnabled() )
58 $this->dieUsageMsg( array( 'uploaddisabled' ) );
59
60 // One and only one of the following parameters is needed
61 $this->requireOnlyOneParameter( $this->mParams,
62 'sessionkey', 'file', 'url', 'enablechunks' );
63
64 if ( $this->mParams['sessionkey'] ) {
65 /**
66 * Upload stashed in a previous request
67 */
68 // Check the session key
69 if ( !isset( $_SESSION['wsUploadData'][$this->mParams['sessionkey']] ) )
70 return $this->dieUsageMsg( array( 'invalid-session-key' ) );
71
72 $this->mUpload = new UploadFromStash();
73 $this->mUpload->initialize( $this->mParams['filename'],
74 $_SESSION['wsUploadData'][$this->mParams['sessionkey']] );
75 } else {
76 /**
77 * Upload from url, etc
78 * Parameter filename is required
79 */
80 if ( !isset( $this->mParams['filename'] ) )
81 $this->dieUsageMsg( array( 'missingparam', 'filename' ) );
82
83 // Initialize $this->mUpload
84 if ( $this->mParams['enablechunks'] ) {
85 $this->mUpload = new UploadFromChunks();
86 $this->mUpload->initialize(
87 $request->getVal( 'done', null ),
88 $request->getVal( 'filename', null ),
89 $request->getVal( 'chunksessionkey', null ),
90 $request->getFileTempName( 'chunk' ),
91 $request->getFileSize( 'chunk' ),
92 $request->getSessionData( 'wsUploadData' )
93 );
94
95 if ( !$this->mUpload->status->isOK() ) {
96 return $this->dieUsageMsg( $this->mUpload->status->getErrorsArray() );
97 }
98 } elseif ( isset( $this->mParams['file'] ) ) {
99 $this->mUpload = new UploadFromFile();
100 $this->mUpload->initialize(
101 $this->mParams['filename'],
102 $request->getFileTempName( 'file' ),
103 $request->getFileSize( 'file' )
104 );
105 } elseif ( isset( $this->mParams['url'] ) ) {
106 // make sure upload by url is enabled:
107 if ( !$wgAllowCopyUploads )
108 $this->dieUsageMsg( array( 'uploaddisabled' ) );
109
110 // make sure the current user can upload
111 if ( ! $wgUser->isAllowed( 'upload_by_url' ) )
112 $this->dieUsageMsg( array( 'badaccess-groups' ) );
113
114 $this->mUpload = new UploadFromUrl();
115 $this->mUpload->initialize( $this->mParams['filename'],
116 $this->mParams['url'] );
117
118 $status = $this->mUpload->fetchFile();
119 if ( !$status->isOK() ) {
120 return $this->dieUsage( $status->getWikiText(), 'fetchfileerror' );
121 }
122 }
123 }
124
125 if ( !isset( $this->mUpload ) )
126 $this->dieUsage( 'No upload module set', 'nomodule' );
127
128 // Check whether the user has the appropriate permissions to upload anyway
129 $permission = $this->mUpload->isAllowed( $wgUser );
130
131 if ( $permission !== true ) {
132 if ( !$wgUser->isLoggedIn() )
133 $this->dieUsageMsg( array( 'mustbeloggedin', 'upload' ) );
134 else
135 $this->dieUsageMsg( array( 'badaccess-groups' ) );
136 }
137 // Perform the upload
138 $result = $this->performUpload();
139
140 // Cleanup any temporary mess
141 $this->mUpload->cleanupTempFile();
142 $this->getResult()->addValue( null, $this->getModuleName(), $result );
143 }
144
145 protected function performUpload() {
146 global $wgUser;
147 $result = array();
148 $permErrors = $this->mUpload->verifyPermissions( $wgUser );
149 if ( $permErrors !== true ) {
150 $this->dieUsageMsg( array( 'badaccess-groups' ) );
151 }
152
153 // TODO: Move them to ApiBase's message map
154 $verification = $this->mUpload->verifyUpload();
155 if ( $verification['status'] !== UploadBase::OK ) {
156 $result['result'] = 'Failure';
157 switch( $verification['status'] ) {
158 case UploadBase::EMPTY_FILE:
159 $this->dieUsage( 'The file you submitted was empty', 'empty-file' );
160 break;
161 case UploadBase::FILETYPE_MISSING:
162 $this->dieUsage( 'The file is missing an extension', 'filetype-missing' );
163 break;
164 case UploadBase::FILETYPE_BADTYPE:
165 global $wgFileExtensions;
166 $this->dieUsage( 'This type of file is banned', 'filetype-banned',
167 0, array(
168 'filetype' => $verification['finalExt'],
169 'allowed' => $wgFileExtensions
170 ) );
171 break;
172 case UploadBase::MIN_LENGTH_PARTNAME:
173 $this->dieUsage( 'The filename is too short', 'filename-tooshort' );
174 break;
175 case UploadBase::ILLEGAL_FILENAME:
176 $this->dieUsage( 'The filename is not allowed', 'illegal-filename',
177 0, array( 'filename' => $verification['filtered'] ) );
178 break;
179 case UploadBase::OVERWRITE_EXISTING_FILE:
180 $this->dieUsage( 'Overwriting an existing file is not allowed', 'overwrite' );
181 break;
182 case UploadBase::VERIFICATION_ERROR:
183 $this->getResult()->setIndexedTagName( $verification['details'], 'detail' );
184 $this->dieUsage( 'This file did not pass file verification', 'verification-error',
185 0, array( 'details' => $verification['details'] ) );
186 break;
187 case UploadBase::HOOK_ABORTED:
188 $this->dieUsage( "The modification you tried to make was aborted by an extension hook",
189 'hookaborted', 0, array( 'error' => $verification['error'] ) );
190 break;
191 default:
192 $this->dieUsage( 'An unknown error occurred', 'unknown-error',
193 0, array( 'code' => $verification['status'] ) );
194 break;
195 }
196 return $result;
197 }
198 if ( !$this->mParams['ignorewarnings'] ) {
199 $warnings = $this->mUpload->checkWarnings();
200 if ( $warnings ) {
201 // Add indices
202 $this->getResult()->setIndexedTagName( $warnings, 'warning' );
203
204 if ( isset( $warnings['duplicate'] ) ) {
205 $dupes = array();
206 foreach ( $warnings['duplicate'] as $key => $dupe )
207 $dupes[] = $dupe->getName();
208 $this->getResult()->setIndexedTagName( $dupes, 'duplicate' );
209 $warnings['duplicate'] = $dupes;
210 }
211
212
213 if ( isset( $warnings['exists'] ) ) {
214 $warning = $warnings['exists'];
215 unset( $warnings['exists'] );
216 $warnings[$warning['warning']] = $warning['file']->getName();
217 }
218
219 $result['result'] = 'Warning';
220 $result['warnings'] = $warnings;
221
222 $sessionKey = $this->mUpload->stashSession();
223 if ( !$sessionKey )
224 $this->dieUsage( 'Stashing temporary file failed', 'stashfailed' );
225
226 $result['sessionkey'] = $sessionKey;
227
228 return $result;
229 }
230 }
231
232 // Use comment as initial page text by default
233 if ( is_null( $this->mParams['text'] ) )
234 $this->mParams['text'] = $this->mParams['comment'];
235
236 // No errors, no warnings: do the upload
237 $status = $this->mUpload->performUpload( $this->mParams['comment'],
238 $this->mParams['text'], $this->mParams['watch'], $wgUser );
239
240 if ( !$status->isGood() ) {
241 $error = $status->getErrorsArray();
242 $this->getResult()->setIndexedTagName( $result['details'], 'error' );
243
244 $this->dieUsage( 'An internal error occurred', 'internal-error', 0, $error );
245 }
246
247 $file = $this->mUpload->getLocalFile();
248 $result['result'] = 'Success';
249 $result['filename'] = $file->getName();
250 $result['imageinfo'] = $this->mUpload->getImageInfo( $this->getResult() );
251
252 return $result;
253 }
254
255 public function mustBePosted() {
256 return true;
257 }
258
259 public function isWriteMode() {
260 return true;
261 }
262
263 public function getAllowedParams() {
264 $params = array(
265 'filename' => null,
266 'comment' => array(
267 ApiBase::PARAM_DFLT => ''
268 ),
269 'text' => null,
270 'token' => null,
271 'watch' => false,
272 'ignorewarnings' => false,
273 'file' => null,
274 'enablechunks' => false,
275 'chunksessionkey' => null,
276 'chunk' => null,
277 'done' => false,
278 'url' => null,
279 'sessionkey' => null,
280 );
281
282 if ( $this->getMain()->isInternalMode() )
283 $params['internalhttpsession'] = null;
284 return $params;
285
286 }
287
288 public function getParamDescription() {
289 return array(
290 'filename' => 'Target filename',
291 'token' => 'Edit token. You can get one of these through prop=info',
292 'comment' => 'Upload comment. Also used as the initial page text for new files if "text" is not specified',
293 'text' => 'Initial page text for new files',
294 'watch' => 'Watch the page',
295 'ignorewarnings' => 'Ignore any warnings',
296 'file' => 'File contents',
297 'enablechunks' => 'Set to use chunk mode; see http://firefogg.org/dev/chunk_post.html for protocol',
298 'chunksessionkey' => 'The session key, established on the first contact during the chunked upload',
299 'chunk' => 'The data in this chunk of a chunked upload',
300 'done' => 'Set to 1 on the last chunk of a chunked upload',
301 'url' => 'Url to fetch the file from',
302 'sessionkey' => array(
303 'Session key returned by a previous upload that failed due to warnings',
304 ),
305 );
306 }
307
308 public function getDescription() {
309 return array(
310 'Upload a file, or get the status of pending uploads. Several methods are available:',
311 ' * Upload file contents directly, using the "file" parameter',
312 ' * Upload a file in chunks, using the "enablechunks",',
313 ' * Have the MediaWiki server fetch a file from a URL, using the "url" parameter',
314 ' * Complete an earlier upload that failed due to warnings, using the "sessionkey" parameter',
315 'Note that the HTTP POST must be done as a file upload (i.e. using multipart/form-data) when',
316 'sending the "file" or "chunk" parameters. Note also that queries using session keys must be',
317 'done in the same login session as the query that originally returned the key (i.e. do not',
318 'log out and then log back in). Also you must get and send an edit token before doing any upload stuff.'
319 );
320 }
321
322 protected function getExamples() {
323 return array(
324 'Upload from a URL:',
325 ' api.php?action=upload&filename=Wiki.png&url=http%3A//upload.wikimedia.org/wikipedia/en/b/bc/Wiki.png',
326 'Complete an upload that failed due to warnings:',
327 ' api.php?action=upload&filename=Wiki.png&sessionkey=sessionkey&ignorewarnings=1',
328 'Begin a chunked upload:',
329 ' api.php?action=upload&filename=Wiki.png&enablechunks=1'
330 );
331 }
332
333 public function getVersion() {
334 return __CLASS__ . ': $Id: ApiUpload.php 51812 2009-06-12 23:45:20Z dale $';
335 }
336 }