Index: trunk/phase3/maintenance/tests/UploadFromChunksTest.php |
— | — | @@ -1,27 +1,30 @@ |
2 | 2 | <?php |
3 | 3 | |
4 | | -require_once("ApiSetup.php"); |
| 4 | +require_once( "ApiSetup.php" ); |
5 | 5 | |
6 | 6 | class UploadFromChunksTest extends ApiSetup { |
7 | 7 | |
8 | 8 | function setUp() { |
9 | 9 | global $wgEnableUploads; |
10 | 10 | |
11 | | - $wgEnableUploads=true; |
12 | | - ini_set('file_loads', true); |
| 11 | + $wgEnableUploads = true; |
13 | 12 | parent::setup(); |
14 | 13 | |
| 14 | + ini_set( 'file_loads', 1 ); |
| 15 | + ini_set( 'log_errors', 1 ); |
| 16 | + ini_set( 'error_reporting', 1 ); |
| 17 | + ini_set( 'display_errors', 1 ); |
15 | 18 | } |
16 | 19 | |
17 | | - function makeChunk() { |
| 20 | + function makeChunk( $content ) { |
18 | 21 | $file = tempnam( wfTempDir(), "" ); |
19 | | - $fh = fopen($file, "w"); |
20 | | - if($fh == false) { |
21 | | - $this->markTestIncomplete("Couldn't open $file!\n"); |
| 22 | + $fh = fopen( $file, "w" ); |
| 23 | + if ( $fh == false ) { |
| 24 | + $this->markTestIncomplete( "Couldn't open $file!\n" ); |
22 | 25 | return; |
23 | 26 | } |
24 | | - fwrite($fh, "123"); |
25 | | - fclose($fh); |
| 27 | + fwrite( $fh, $content ); |
| 28 | + fclose( $fh ); |
26 | 29 | |
27 | 30 | $_FILES['chunk']['tmp_name'] = $file; |
28 | 31 | $_FILES['chunk']['size'] = 3; |
— | — | @@ -30,133 +33,286 @@ |
31 | 34 | } |
32 | 35 | |
33 | 36 | function cleanChunk() { |
34 | | - if(file_exists($_FILES['chunk']['tmp_name'])) |
35 | | - unlink($_FILES['chunk']['tmp_name']); |
| 37 | + if ( file_exists( $_FILES['chunk']['tmp_name'] ) ) |
| 38 | + unlink( $_FILES['chunk']['tmp_name'] ); |
36 | 39 | } |
37 | 40 | |
38 | | - function doApiRequest($params) { |
39 | | - $session = isset( $_SESSION ) ? $_SESSION : array(); |
40 | | - $req = new FauxRequest($params, true, $session); |
41 | | - $module = new ApiMain($req, true); |
| 41 | + function doApiRequest( $params, $data = null ) { |
| 42 | + $session = isset( $data[2] ) ? $data[2] : array(); |
| 43 | + $_SESSION = $session; |
| 44 | + |
| 45 | + $req = new FauxRequest( $params, true, $session ); |
| 46 | + $module = new ApiMain( $req, true ); |
42 | 47 | $module->execute(); |
43 | 48 | |
44 | | - return $module->getResultData(); |
| 49 | + return array( $module->getResultData(), $req, $_SESSION ); |
45 | 50 | } |
46 | 51 | |
47 | 52 | function testGetTitle() { |
48 | 53 | $filename = tempnam( wfTempDir(), "" ); |
49 | 54 | $c = new UploadFromChunks(); |
50 | | - $c->initialize(false, "temp.txt", null, $filename, 0, null); |
51 | | - $this->assertEquals(null, $c->getTitle()); |
| 55 | + $c->initialize( false, "temp.txt", null, $filename, 0, null ); |
| 56 | + $this->assertEquals( null, $c->getTitle() ); |
52 | 57 | |
53 | 58 | $c = new UploadFromChunks(); |
54 | | - $c->initialize(false, "temp.png", null, $filename, 0, null); |
55 | | - $this->assertEquals(Title::makeTitleSafe(NS_FILE, "Temp.png"), $c->getTitle()); |
| 59 | + $c->initialize( false, "temp.png", null, $filename, 0, null ); |
| 60 | + $this->assertEquals( Title::makeTitleSafe( NS_FILE, "Temp.png" ), $c->getTitle() ); |
56 | 61 | } |
57 | 62 | |
58 | 63 | function testLogin() { |
59 | | - $data = $this->doApiRequest(array('action' => 'login', |
60 | | - "lgname" => self::$userName, |
61 | | - "lgpassword" => self::$passWord ) ); |
62 | | - $this->assertArrayHasKey("login", $data); |
63 | | - $this->assertArrayHasKey("result", $data['login']); |
64 | | - $this->assertEquals("Success", $data['login']['result']); |
| 64 | + $data = $this->doApiRequest( array( |
| 65 | + 'action' => 'login', |
| 66 | + 'lgname' => self::$userName, |
| 67 | + 'lgpassword' => self::$passWord ) ); |
| 68 | + $this->assertArrayHasKey( "login", $data[0] ); |
| 69 | + $this->assertArrayHasKey( "result", $data[0]['login'] ); |
| 70 | + $this->assertEquals( "Success", $data[0]['login']['result'] ); |
| 71 | + $this->assertArrayHasKey( 'lgtoken', $data[0]['login'] ); |
65 | 72 | |
66 | 73 | return $data; |
67 | 74 | } |
68 | 75 | |
69 | | - |
70 | 76 | /** |
71 | 77 | * @depends testLogin |
72 | 78 | */ |
73 | | - function testGetEditToken($data) { |
| 79 | + function testSetupChunkSession( $data ) { |
74 | 80 | global $wgUser; |
75 | | - $wgUser = User::newFromName(self::$userName); |
| 81 | + $wgUser = User::newFromName( self::$userName ); |
76 | 82 | $wgUser->load(); |
| 83 | + $data[2]['wsEditToken'] = $data[2]['wsToken']; |
| 84 | + $token = md5( $data[2]['wsToken'] ) . EDIT_TOKEN_SUFFIX; |
| 85 | + $exception = false; |
77 | 86 | |
78 | | - $data = $this->doApiRequest(array('action' => 'query', |
79 | | - 'prop' => 'info', |
80 | | - 'intoken' => 'edit')); |
81 | | - } |
| 87 | + $data = $this->doApiRequest( array( |
| 88 | + 'filename' => 'tmp.txt', |
| 89 | + 'action' => 'upload', |
| 90 | + 'enablechunks' => true, |
| 91 | + 'token' => $token ), $data ); |
| 92 | + $this->assertArrayHasKey( 'uploadUrl', $data[0] ); |
| 93 | + $this->assertRegexp( '/action=upload/', $data[0]['uploadUrl'] ); |
| 94 | + $this->assertRegexp( '/enablechunks=true/', $data[0]['uploadUrl'] ); |
| 95 | + $this->assertRegexp( '/format=json/', $data[0]['uploadUrl'] ); |
| 96 | + $this->assertRegexp( '/chunksession=/', $data[0]['uploadUrl'] ); |
| 97 | + $this->assertRegexp( '/token=/', $data[0]['uploadUrl'] ); |
82 | 98 | |
83 | | - function testSetupChunkSession() { |
| 99 | + return $data; |
84 | 100 | } |
85 | 101 | |
86 | | - |
87 | | - /** |
88 | | - * @expectedException UsageException |
89 | | - */ |
90 | | - function testPerformUploadInitError() { |
| 102 | + /** |
| 103 | + * @depends testSetupChunkSession |
| 104 | + */ |
| 105 | + function testAppendChunkTypeBanned( $data ) { |
91 | 106 | global $wgUser; |
92 | | - $wgUser = User::newFromId(1); |
| 107 | + $wgUser = User::newFromName( self::$userName ); |
93 | 108 | |
94 | | - $req = new FauxRequest( |
95 | | - array('action' => 'upload', |
96 | | - 'enablechunks' => '1', |
97 | | - 'filename' => 'test.png', |
98 | | - )); |
99 | | - $module = new ApiMain($req, true); |
100 | | - $module->execute(); |
| 109 | + $url = $data[0]['uploadUrl']; |
| 110 | + $params = wfCgiToArray( substr( $url, strpos( $url, "?" ) ) ); |
| 111 | + |
| 112 | + $size = 0; |
| 113 | + for ( $i = 0; $i < 4; $i++ ) { |
| 114 | + $this->makeChunk( "123" ); |
| 115 | + $size += $_FILES['chunk']['size']; |
| 116 | + |
| 117 | + $data = $this->doApiRequest( $params, $data ); |
| 118 | + $this->assertArrayHasKey( "result", $data[0] ); |
| 119 | + $this->assertTrue( (bool)$data[0]["result"] ); |
| 120 | + |
| 121 | + $this->assertArrayHasKey( "filesize", $data[0] ); |
| 122 | + $this->assertEquals( $size, $data[0]['filesize'] ); |
| 123 | + |
| 124 | + $this->cleanChunk(); |
| 125 | + } |
| 126 | + |
| 127 | + $data['param'] = $params; |
| 128 | + return $data; |
101 | 129 | } |
102 | 130 | |
103 | 131 | /** |
104 | 132 | * @depends testLogin |
105 | 133 | */ |
106 | | - function testPerformUploadInitSuccess($login) { |
| 134 | + function testInvalidSessionKey( $data ) { |
107 | 135 | global $wgUser; |
| 136 | + $wgUser = User::newFromName( self::$userName ); |
| 137 | + $wgUser->load(); |
| 138 | + $data[2]['wsEditToken'] = $data[2]['wsToken']; |
| 139 | + $token = md5( $data[2]['wsToken'] ) . EDIT_TOKEN_SUFFIX; |
| 140 | + $exception = false; |
108 | 141 | |
109 | | - $wgUser = User::newFromName(self::$userName); |
110 | | - $token = $wgUser->editToken(); |
| 142 | + try { |
| 143 | + $this->doApiRequest( array( |
| 144 | + 'action' => 'upload', |
| 145 | + 'enablechunks' => true, |
| 146 | + 'token' => $token, |
| 147 | + 'chunksession' => 'bogus' ), $data ); |
| 148 | + } catch ( UsageException $e ) { |
| 149 | + $exception = true; |
| 150 | + $this->assertEquals( "Not a valid session key", $e->getMessage() ); |
| 151 | + } |
111 | 152 | |
112 | | - $data = $this->doApiRequest( |
113 | | - array('action' => 'upload', |
114 | | - 'enablechunks' => '1', |
115 | | - 'filename' => 'test.png', |
116 | | - 'token' => $token, |
117 | | - )); |
| 153 | + $this->assertTrue( $exception, "Got exception" ); |
| 154 | + } |
118 | 155 | |
119 | | - $this->assertArrayHasKey("uploadUrl", $data); |
| 156 | + function testPerformUploadInitError() { |
| 157 | + global $wgUser; |
| 158 | + $wgUser = User::newFromId( 1 ); |
120 | 159 | |
121 | | - return array('data' => $data, 'session' => $_SESSION, 'token' => $token); |
| 160 | + $req = new FauxRequest( |
| 161 | + array( |
| 162 | + 'action' => 'upload', |
| 163 | + 'enablechunks' => 'false', |
| 164 | + 'sessionkey' => '1', |
| 165 | + 'filename' => 'test.png', |
| 166 | + ) ); |
| 167 | + $module = new ApiMain( $req, true ); |
| 168 | + $gotException = false; |
| 169 | + try { |
| 170 | + $module->execute(); |
| 171 | + } catch ( UsageException $e ) { |
| 172 | + $this->assertEquals( "The token parameter must be set", $e->getMessage() ); |
| 173 | + $gotException = true; |
| 174 | + } |
| 175 | + |
| 176 | + $this->assertTrue( $gotException ); |
122 | 177 | } |
123 | 178 | |
124 | 179 | /** |
125 | | - * @depends testPerformUploadInitSuccess |
| 180 | + * @depends testAppendChunkTypeBanned |
126 | 181 | */ |
127 | | - function testAppendChunk($combo) { |
| 182 | + function testUploadChunkDoneTypeBanned( $data ) { |
128 | 183 | global $wgUser; |
129 | | - $data = $combo['data']; |
130 | | - $_SESSION = $combo['session']; |
131 | | - $wgUser = User::newFromName(self::$userName); |
| 184 | + $wgUser = User::newFromName( self::$userName ); |
132 | 185 | $token = $wgUser->editToken(); |
| 186 | + $params = $data['param']; |
| 187 | + $params['done'] = 1; |
133 | 188 | |
134 | | - $url = $data['uploadUrl']; |
135 | | - $params = wfCgiToArray(substr($url, strpos($url, "?"))); |
| 189 | + $this->makeChunk( "123" ); |
136 | 190 | |
137 | | - for($i=0;$i<10;$i++) { |
138 | | - $this->makeChunk(); |
139 | | - $data = $this->doApiRequest($params); |
| 191 | + $gotException = false; |
| 192 | + try { |
| 193 | + $data = $this->doApiRequest( $params, $data ); |
| 194 | + } catch ( UsageException $e ) { |
| 195 | + $this->assertEquals( "This type of file is banned", |
| 196 | + $e->getMessage() ); |
| 197 | + $gotException = true; |
| 198 | + } |
| 199 | + $this->cleanChunk(); |
| 200 | + $this->assertTrue( $gotException ); |
| 201 | + } |
| 202 | + |
| 203 | + /** |
| 204 | + * @depends testLogin |
| 205 | + */ |
| 206 | + function testUploadChunkDoneDuplicate( $data ) { |
| 207 | + global $wgUser, $wgVerifyMimeType; |
| 208 | + |
| 209 | + $wgVerifyMimeType = false; |
| 210 | + $wgUser = User::newFromName( self::$userName ); |
| 211 | + $data[2]['wsEditToken'] = $data[2]['wsToken']; |
| 212 | + $token = md5( $data[2]['wsToken'] ) . EDIT_TOKEN_SUFFIX; |
| 213 | + $data = $this->doApiRequest( array( |
| 214 | + 'filename' => 'tmp.png', |
| 215 | + 'action' => 'upload', |
| 216 | + 'enablechunks' => true, |
| 217 | + 'token' => $token ), $data ); |
| 218 | + |
| 219 | + $url = $data[0]['uploadUrl']; |
| 220 | + $params = wfCgiToArray( substr( $url, strpos( $url, "?" ) ) ); |
| 221 | + $size = 0; |
| 222 | + for ( $i = 0; $i < 4; $i++ ) { |
| 223 | + $this->makeChunk( "123" ); |
| 224 | + $size += $_FILES['chunk']['size']; |
| 225 | + |
| 226 | + $data = $this->doApiRequest( $params, $data ); |
| 227 | + $this->assertArrayHasKey( "result", $data[0] ); |
| 228 | + $this->assertTrue( (bool)$data[0]["result"] ); |
| 229 | + |
| 230 | + $this->assertArrayHasKey( "filesize", $data[0] ); |
| 231 | + $this->assertEquals( $size, $data[0]['filesize'] ); |
| 232 | + |
140 | 233 | $this->cleanChunk(); |
141 | 234 | } |
142 | 235 | |
143 | | - return array('data' => $data, 'session' => $_SESSION, 'token' => $token, 'params' => $params); |
| 236 | + $params['done'] = true; |
| 237 | + |
| 238 | + $this->makeChunk( "123" ); |
| 239 | + $data = $this->doApiRequest( $params, $data ); |
| 240 | + $this->cleanChunk(); |
| 241 | + |
| 242 | + $this->assertArrayHasKey( 'upload', $data[0] ); |
| 243 | + $this->assertArrayHasKey( 'result', $data[0]['upload'] ); |
| 244 | + $this->assertEquals( 'Warning', $data[0]['upload']['result'] ); |
| 245 | + |
| 246 | + $this->assertArrayHasKey( 'warnings', $data[0]['upload'] ); |
| 247 | + $this->assertArrayHasKey( 'exists', |
| 248 | + $data[0]['upload']['warnings'] ); |
| 249 | + $this->assertEquals( 'Tmp.png', |
| 250 | + $data[0]['upload']['warnings']['exists'] ); |
| 251 | + |
144 | 252 | } |
145 | 253 | |
146 | 254 | /** |
147 | | - * @depends testAppendChunk |
| 255 | + * @depends testLogin |
148 | 256 | */ |
149 | | - function testUploadChunkDone($combo) { |
150 | | - global $wgUser; |
151 | | - $data = $combo['data']; |
152 | | - $params = $combo['params']; |
153 | | - $_SESSION = $combo['session']; |
154 | | - $wgUser = User::newFromName(self::$userName); |
155 | | - $token = $wgUser->editToken(); |
| 257 | + function testUploadChunkDoneGood( $data ) { |
| 258 | + global $wgUser, $wgVerifyMimeType; |
| 259 | + $wgVerifyMimeType = false; |
156 | 260 | |
157 | | - $params['done'] = 1; |
| 261 | + $id = Title::newFromText( "Twar.png", NS_FILE )->getArticleID(); |
158 | 262 | |
159 | | - $this->makeChunk(); |
160 | | - $data = $this->doApiRequest($params); |
| 263 | + $oldFile = Article::newFromID( $id ); |
| 264 | + if ( $oldFile ) { |
| 265 | + $oldFile->doDeleteArticle(); |
| 266 | + $oldFile->doPurge(); |
| 267 | + } |
| 268 | + $oldFile = wfFindFile( "Twar.png" ); |
| 269 | + if ( $oldFile ) { |
| 270 | + $oldFile->delete(); |
| 271 | + } |
| 272 | + |
| 273 | + $wgUser = User::newFromName( self::$userName ); |
| 274 | + $data[2]['wsEditToken'] = $data[2]['wsToken']; |
| 275 | + $token = md5( $data[2]['wsToken'] ) . EDIT_TOKEN_SUFFIX; |
| 276 | + $data = $this->doApiRequest( array( |
| 277 | + 'filename' => 'twar.png', |
| 278 | + 'action' => 'upload', |
| 279 | + 'enablechunks' => true, |
| 280 | + 'token' => $token ), $data ); |
| 281 | + |
| 282 | + $url = $data[0]['uploadUrl']; |
| 283 | + $params = wfCgiToArray( substr( $url, strpos( $url, "?" ) ) ); |
| 284 | + $size = 0; |
| 285 | + for ( $i = 0; $i < 5; $i++ ) { |
| 286 | + $this->makeChunk( "123" ); |
| 287 | + $size += $_FILES['chunk']['size']; |
| 288 | + |
| 289 | + $data = $this->doApiRequest( $params, $data ); |
| 290 | + $this->assertArrayHasKey( "result", $data[0] ); |
| 291 | + $this->assertTrue( (bool)$data[0]["result"] ); |
| 292 | + |
| 293 | + $this->assertArrayHasKey( "filesize", $data[0] ); |
| 294 | + $this->assertEquals( $size, $data[0]['filesize'] ); |
| 295 | + |
| 296 | + $this->cleanChunk(); |
| 297 | + } |
| 298 | + |
| 299 | + $params['done'] = true; |
| 300 | + |
| 301 | + $this->makeChunk( "456" ); |
| 302 | + $data = $this->doApiRequest( $params, $data ); |
| 303 | + |
161 | 304 | $this->cleanChunk(); |
| 305 | + |
| 306 | + if ( isset( $data[0]['upload'] ) ) { |
| 307 | + $this->markTestSkipped( "Please run 'php maintenance/deleteArchivedFiles.php --delete --force' and 'php maintenance/deleteArchivedRevisions.php --delete'" ); |
| 308 | + } |
| 309 | + |
| 310 | + $this->assertArrayHasKey( 'result', $data[0] ); |
| 311 | + $this->assertEquals( 1, $data[0]['result'] ); |
| 312 | + |
| 313 | + $this->assertArrayHasKey( 'done', $data[0] ); |
| 314 | + $this->assertEquals( 1, $data[0]['done'] ); |
| 315 | + |
| 316 | + $this->assertArrayHasKey( 'resultUrl', $data[0] ); |
| 317 | + $this->assertRegExp( '/File:Twar.png/', $data[0]['resultUrl'] ); |
162 | 318 | } |
163 | 319 | } |
Index: trunk/phase3/includes/upload/UploadFromChunks.php |
— | — | @@ -37,23 +37,26 @@ |
38 | 38 | } |
39 | 39 | |
40 | 40 | public function initialize( $done, $filename, $sessionKey, $path, $fileSize, $sessionData ) { |
41 | | - global $wgTmpDirectory; |
42 | | - $this->status = new Status; |
| 41 | + $this->status = Status::newGood(); |
43 | 42 | |
44 | | - $this->initFromSessionKey( $sessionKey, $sessionData ); |
| 43 | + $this->initializePathInfo( $filename, $path, 0, true ); |
| 44 | + if ( $sessionKey !== null ) { |
| 45 | + $this->initFromSessionKey( $sessionKey, $sessionData, $fileSize ); |
45 | 46 | |
46 | | - if ( !$this->sessionKey && !$done ) { |
| 47 | + if ( $done ) { |
| 48 | + $this->chunkMode = self::DONE; |
| 49 | + } else { |
| 50 | + $this->mTempPath = $path; |
| 51 | + $this->chunkMode = self::CHUNK; |
| 52 | + } |
| 53 | + } else { |
47 | 54 | // session key not set, init the chunk upload system: |
48 | 55 | $this->chunkMode = self::INIT; |
49 | | - $this->initializePathInfo( $filename, $path, 0, true); |
50 | | - } else if ( $this->sessionKey && !$done ) { |
51 | | - $this->chunkMode = self::CHUNK; |
52 | | - } else if ( $this->sessionKey && $done ) { |
53 | | - $this->chunkMode = self::DONE; |
54 | 56 | } |
55 | | - if ( $this->chunkMode == self::CHUNK || $this->chunkMode == self::DONE ) { |
56 | | - $this->mTempPath = $path; |
57 | | - $this->mFileSize += $fileSize; |
| 57 | + |
| 58 | + if ( $this->status->isOk() |
| 59 | + && ( $this->mDesiredDestName === null || $this->mFileSize === null ) ) { |
| 60 | + $this->status = Status::newFatal( 'chunk-init-error' ); |
58 | 61 | } |
59 | 62 | } |
60 | 63 | |
— | — | @@ -66,16 +69,26 @@ |
67 | 70 | * @returns string the session key for this chunked upload |
68 | 71 | */ |
69 | 72 | protected function setupChunkSession( $comment, $pageText, $watch ) { |
70 | | - $this->sessionKey = $this->getSessionKey(); |
71 | | - $_SESSION['wsUploadData'][$this->sessionKey] = array( |
72 | | - 'comment' => $comment, |
73 | | - 'pageText' => $pageText, |
74 | | - 'watch' => $watch, |
75 | | - 'mFilteredName' => $this->mFilteredName, |
76 | | - 'repoPath' => null, |
77 | | - 'mDesiredDestName' => $this->mDesiredDestName, |
78 | | - 'version' => self::SESSION_VERSION, |
79 | | - ); |
| 73 | + if ( !isset( $this->sessionKey ) ) { |
| 74 | + $this->sessionKey = $this->getSessionKey(); |
| 75 | + } |
| 76 | + foreach ( array( 'mFilteredName', 'repoPath', 'mFileSize', 'mDesiredDestName' ) |
| 77 | + as $key ) { |
| 78 | + if ( isset( $this->$key ) ) { |
| 79 | + $_SESSION['wsUploadData'][$this->sessionKey][$key] = $this->$key; |
| 80 | + } |
| 81 | + } |
| 82 | + if ( isset( $comment ) ) { |
| 83 | + $_SESSION['wsUploadData'][$this->sessionKey]['commment'] = $comment; |
| 84 | + } |
| 85 | + if ( isset( $pageText ) ) { |
| 86 | + $_SESSION['wsUploadData'][$this->sessionKey]['pageText'] = $pageText; |
| 87 | + } |
| 88 | + if ( isset( $watch ) ) { |
| 89 | + $_SESSION['wsUploadData'][$this->sessionKey]['watch'] = $watch; |
| 90 | + } |
| 91 | + $_SESSION['wsUploadData'][$this->sessionKey]['version'] = self::SESSION_VERSION; |
| 92 | + |
80 | 93 | return $this->sessionKey; |
81 | 94 | } |
82 | 95 | |
— | — | @@ -83,26 +96,26 @@ |
84 | 97 | * Initialize a continuation of a chunked upload from a session key |
85 | 98 | * @param $sessionKey string |
86 | 99 | * @param $request WebRequest |
| 100 | + * @param $fileSize int Size of this chunk |
87 | 101 | * |
88 | 102 | * @returns void |
89 | 103 | */ |
90 | | - protected function initFromSessionKey( $sessionKey, $sessionData ) { |
| 104 | + protected function initFromSessionKey( $sessionKey, $sessionData, $fileSize ) { |
91 | 105 | // testing against null because we don't want to cause obscure |
92 | 106 | // bugs when $sessionKey is full of "0" |
93 | | - if ( $sessionKey === null ) { |
94 | | - return; |
95 | | - } |
96 | 107 | $this->sessionKey = $sessionKey; |
97 | 108 | |
98 | 109 | if ( isset( $sessionData[$this->sessionKey]['version'] ) |
99 | 110 | && $sessionData[$this->sessionKey]['version'] == self::SESSION_VERSION ) |
100 | 111 | { |
101 | | - $this->comment = $sessionData[$this->sessionKey]['comment']; |
102 | | - $this->pageText = $sessionData[$this->sessionKey]['pageText']; |
103 | | - $this->watch = $sessionData[$this->sessionKey]['watch']; |
104 | | - $this->mFilteredName = $sessionData[$this->sessionKey]['mFilteredName']; |
105 | | - $this->repoPath = $sessionData[$this->sessionKey]['repoPath']; |
106 | | - $this->mDesiredDestName = $sessionData[$this->sessionKey]['mDesiredDestName']; |
| 112 | + foreach ( array( 'comment', 'pageText', 'watch', 'mFilteredName', 'repoPath', 'mFileSize', 'mDesiredDestName' ) |
| 113 | + as $key ) { |
| 114 | + if ( isset( $sessionData[$this->sessionKey][$key] ) ) { |
| 115 | + $this->$key = $sessionData[$this->sessionKey][$key]; |
| 116 | + } |
| 117 | + } |
| 118 | + |
| 119 | + $this->mFileSize += $fileSize; |
107 | 120 | } else { |
108 | 121 | $this->status = Status::newFatal( 'invalid-session-key' ); |
109 | 122 | } |
— | — | @@ -127,14 +140,17 @@ |
128 | 141 | // b) should only happen over POST |
129 | 142 | // c) we need the token to validate chunks are coming from a non-xss request |
130 | 143 | return Status::newGood( |
131 | | - array('uploadUrl' => wfExpandUrl( wfScript( 'api' ) ) . "?" . |
132 | | - wfArrayToCGI(array('action' => 'upload', |
133 | | - 'token' => $wgUser->editToken(), |
134 | | - 'format' => 'json', |
135 | | - 'filename' => $pageText, |
136 | | - 'enablechunks' => 'true', |
137 | | - 'chunksession' => $this->setupChunkSession( $comment, $pageText, $watch ) ) ) ) ); |
| 144 | + array( 'uploadUrl' => wfExpandUrl( wfScript( 'api' ) ) . "?" . |
| 145 | + wfArrayToCGI( array( |
| 146 | + 'action' => 'upload', |
| 147 | + 'token' => $wgUser->editToken(), |
| 148 | + 'format' => 'json', |
| 149 | + 'filename' => $this->mDesiredDestName, |
| 150 | + 'enablechunks' => 'true', |
| 151 | + 'chunksession' => |
| 152 | + $this->setupChunkSession( $comment, $pageText, $watch ) ) ) ) ); |
138 | 153 | } else if ( $this->chunkMode == self::CHUNK ) { |
| 154 | + $this->setupChunkSession(); |
139 | 155 | $this->appendChunk(); |
140 | 156 | if ( !$this->status->isOK() ) { |
141 | 157 | return $this->status; |
— | — | @@ -146,16 +162,10 @@ |
147 | 163 | array( 'result' => 1, 'filesize' => $this->mFileSize ) |
148 | 164 | ); |
149 | 165 | } else if ( $this->chunkMode == self::DONE ) { |
150 | | - if ( !$comment ) |
151 | | - $comment = $this->comment; |
| 166 | + $this->finalizeFile(); |
| 167 | + // We ignore the passed-in parameters because these were set on the first contact. |
| 168 | + $status = parent::performUpload( $this->comment, $this->pageText, $this->watch, $user ); |
152 | 169 | |
153 | | - if ( !$pageText ) |
154 | | - $pageText = $this->pageText; |
155 | | - |
156 | | - if ( !$watch ) |
157 | | - $watch = $this->watch; |
158 | | - |
159 | | - $status = parent::performUpload( $comment, $pageText, $watch, $user ); |
160 | 170 | if ( !$status->isGood() ) { |
161 | 171 | return $status; |
162 | 172 | } |
— | — | @@ -164,7 +174,7 @@ |
165 | 175 | // firefogg expects a specific result |
166 | 176 | // http://www.firefogg.org/dev/chunk_post.html |
167 | 177 | return Status::newGood( |
168 | | - array('result' => 1, 'done' => 1, 'resultUrl' => wfExpandUrl( $file->getDescriptionUrl() ) ) |
| 178 | + array( 'result' => 1, 'done' => 1, 'resultUrl' => wfExpandUrl( $file->getDescriptionUrl() ) ) |
169 | 179 | ); |
170 | 180 | } |
171 | 181 | |
— | — | @@ -203,16 +213,27 @@ |
204 | 214 | } |
205 | 215 | if ( $this->getRealPath( $this->repoPath ) ) { |
206 | 216 | $this->status = $this->appendToUploadFile( $this->repoPath, $this->mTempPath ); |
| 217 | + |
| 218 | + if ( $this->mFileSize > $wgMaxUploadSize ) |
| 219 | + $this->status = Status::newFatal( 'largefileserver' ); |
| 220 | + |
207 | 221 | } else { |
208 | 222 | $this->status = Status::newFatal( 'filenotfound', $this->repoPath ); |
209 | 223 | } |
210 | | - if ( $this->mFileSize > $wgMaxUploadSize ) |
211 | | - $this->status = Status::newFatal( 'largefileserver' ); |
212 | 224 | } |
213 | 225 | |
| 226 | + /** |
| 227 | + * Append the final chunk and ready file for parent::performUpload() |
| 228 | + * @return void |
| 229 | + */ |
| 230 | + protected function finalizeFile() { |
| 231 | + $this->appendChunk(); |
| 232 | + $this->mTempPath = $this->getRealPath( $this->repoPath ); |
| 233 | + } |
| 234 | + |
214 | 235 | public function verifyUpload() { |
215 | 236 | if ( $this->chunkMode != self::DONE ) { |
216 | | - return array('status' => UploadBase::OK); |
| 237 | + return array( 'status' => UploadBase::OK ); |
217 | 238 | } |
218 | 239 | return parent::verifyUpload(); |
219 | 240 | } |
Index: trunk/phase3/includes/filerepo/FSRepo.php |
— | — | @@ -227,7 +227,7 @@ |
228 | 228 | return $status; |
229 | 229 | } |
230 | 230 | |
231 | | - function append( $srcPath, $toAppendPath ) { |
| 231 | + function append( $srcPath, $toAppendPath, $flags = 0 ) { |
232 | 232 | $status = $this->newGood(); |
233 | 233 | |
234 | 234 | // Resolve the virtual URL |
— | — | @@ -236,21 +236,31 @@ |
237 | 237 | } |
238 | 238 | // Make sure the files are there |
239 | 239 | if ( !is_file( $srcPath ) ) |
240 | | - $status->fatal( 'append-src-filenotfound', $srcPath ); |
| 240 | + $status->fatal( 'filenotfound', $srcPath ); |
241 | 241 | |
242 | 242 | if ( !is_file( $toAppendPath ) ) |
243 | | - $status->fatal( 'append-toappend-filenotfound', $toAppendPath ); |
| 243 | + $status->fatal( 'filenotfound', $toAppendPath ); |
244 | 244 | |
| 245 | + if ( !$status->isOk() ) return $status; |
| 246 | + |
245 | 247 | // Do the append |
246 | | - if( file_put_contents( $srcPath, file_get_contents( $toAppendPath ), FILE_APPEND ) ) { |
247 | | - $status->value = $srcPath; |
248 | | - } else { |
249 | | - $status->fatal( 'fileappenderror', $toAppendPath, $srcPath); |
| 248 | + $chunk = file_get_contents( $toAppendPath ); |
| 249 | + if( $chunk === false ) { |
| 250 | + $status->fatal( 'fileappenderrorread', $toAppendPath ); |
250 | 251 | } |
251 | 252 | |
252 | | - // Remove the source file |
253 | | - unlink( $toAppendPath ); |
| 253 | + if( $status->isOk() ) { |
| 254 | + if ( file_put_contents( $srcPath, $chunk, FILE_APPEND ) ) { |
| 255 | + $status->value = $srcPath; |
| 256 | + } else { |
| 257 | + $status->fatal( 'fileappenderror', $toAppendPath, $srcPath); |
| 258 | + } |
| 259 | + } |
254 | 260 | |
| 261 | + if ( $flags & self::DELETE_SOURCE ) { |
| 262 | + unlink( $toAppendPath ); |
| 263 | + } |
| 264 | + |
255 | 265 | return $status; |
256 | 266 | } |
257 | 267 | |
Index: trunk/phase3/includes/filerepo/NullRepo.php |
— | — | @@ -14,7 +14,7 @@ |
15 | 15 | function storeTemp( $originalName, $srcPath ) { |
16 | 16 | return false; |
17 | 17 | } |
18 | | - function append( $srcPath, $toAppendPath ){ |
| 18 | + function append( $srcPath, $toAppendPath, $flags = 0 ){ |
19 | 19 | return false; |
20 | 20 | } |
21 | 21 | function publishBatch( $triplets, $flags = 0 ) { |
Index: trunk/phase3/includes/filerepo/FileRepo.php |
— | — | @@ -402,9 +402,11 @@ |
403 | 403 | * Append the contents of the source path to the given file. |
404 | 404 | * @param $srcPath string location of the source file |
405 | 405 | * @param $toAppendPath string path to append to. |
| 406 | + * @param $flags Bitfield, may be FileRepo::DELETE_SOURCE to indicate |
| 407 | + * that the source file should be deleted if possible |
406 | 408 | * @return mixed Status or false |
407 | 409 | */ |
408 | | - abstract function append( $srcPath, $toAppendPath ); |
| 410 | + abstract function append( $srcPath, $toAppendPath, $flags ); |
409 | 411 | |
410 | 412 | /** |
411 | 413 | * Remove a temporary file or mark it for garbage collection |
Index: trunk/phase3/includes/filerepo/ForeignAPIRepo.php |
— | — | @@ -63,7 +63,7 @@ |
64 | 64 | function storeTemp( $originalName, $srcPath ) { |
65 | 65 | return false; |
66 | 66 | } |
67 | | - function append( $srcPath, $toAppendPath ){ |
| 67 | + function append( $srcPath, $toAppendPath, $flags = 0 ){ |
68 | 68 | return false; |
69 | 69 | } |
70 | 70 | function publishBatch( $triplets, $flags = 0 ) { |
Index: trunk/phase3/includes/api/ApiBase.php |
— | — | @@ -921,6 +921,7 @@ |
922 | 922 | 'nouploadmodule' => array( 'code' => 'nouploadmodule', 'info' => 'No upload module set' ), |
923 | 923 | 'uploaddisabled' => array( 'code' => 'uploaddisabled', 'info' => 'Uploads are not enabled. Make sure $wgEnableUploads is set to true in LocalSettings.php and the PHP ini setting file_uploads is true' ), |
924 | 924 | 'chunked-error' => array( 'code' => 'chunked-error', 'info' => 'There was a problem initializing the chunked upload.' ), |
| 925 | + 'chunk-init-error' => array( 'code' => 'chunk-init-error', 'info' => 'Insufficient information for initialization.' ), |
925 | 926 | ); |
926 | 927 | |
927 | 928 | /** |
Index: trunk/phase3/includes/api/ApiUpload.php |
— | — | @@ -68,7 +68,7 @@ |
69 | 69 | ); |
70 | 70 | |
71 | 71 | if ( !$this->mUpload->status->isOK() ) { |
72 | | - return $this->dieUsageMsg( $this->mUpload->status->getErrorsArray() ); |
| 72 | + $this->dieUsageMsg( $this->mUpload->status->getErrorsArray() ); |
73 | 73 | } |
74 | 74 | } elseif ( $this->mParams['sessionkey'] ) { |
75 | 75 | /** |
— | — | @@ -76,7 +76,7 @@ |
77 | 77 | */ |
78 | 78 | // Check the session key |
79 | 79 | if ( !isset( $_SESSION['wsUploadData'][$this->mParams['sessionkey']] ) ) |
80 | | - return $this->dieUsageMsg( array( 'invalid-session-key' ) ); |
| 80 | + $this->dieUsageMsg( array( 'invalid-session-key' ) ); |
81 | 81 | |
82 | 82 | $this->mUpload = new UploadFromStash(); |
83 | 83 | $this->mUpload->initialize( $this->mParams['filename'], |
— | — | @@ -109,7 +109,7 @@ |
110 | 110 | |
111 | 111 | $status = $this->mUpload->fetchFile(); |
112 | 112 | if ( !$status->isOK() ) { |
113 | | - return $this->dieUsage( $status->getWikiText(), 'fetchfileerror' ); |
| 113 | + $this->dieUsage( $status->getWikiText(), 'fetchfileerror' ); |
114 | 114 | } |
115 | 115 | } |
116 | 116 | } else $this->dieUsageMsg( array( 'missingparam', 'filename' ) ); |
Index: trunk/phase3/includes/Revision.php |
— | — | @@ -835,6 +835,8 @@ |
836 | 836 | $this->mTextId = $dbw->insertId(); |
837 | 837 | } |
838 | 838 | |
| 839 | + if ( $this->mComment === null ) $this->mComment = ""; |
| 840 | + |
839 | 841 | # Record the edit in revisions |
840 | 842 | $rev_id = isset( $this->mId ) |
841 | 843 | ? $this->mId |
Index: trunk/phase3/includes/LogPage.php |
— | — | @@ -378,6 +378,8 @@ |
379 | 379 | $params = array( $params ); |
380 | 380 | } |
381 | 381 | |
| 382 | + if ( $comment === null ) $comment = ""; |
| 383 | + |
382 | 384 | $this->action = $action; |
383 | 385 | $this->target = $target; |
384 | 386 | $this->comment = $comment; |
Index: trunk/phase3/languages/messages/MessagesEn.php |
— | — | @@ -979,6 +979,8 @@ |
980 | 980 | 'readonly_lag' => 'The database has been automatically locked while the slave database servers catch up to the master', |
981 | 981 | 'internalerror' => 'Internal error', |
982 | 982 | 'internalerror_info' => 'Internal error: $1', |
| 983 | +'fileappenderrorread' => 'Could not read "$1" during append.', |
| 984 | +'fileappenderror' => 'Could not append "$1" to "$2".', |
983 | 985 | 'filecopyerror' => 'Could not copy file "$1" to "$2".', |
984 | 986 | 'filerenameerror' => 'Could not rename file "$1" to "$2".', |
985 | 987 | 'filedeleteerror' => 'Could not delete file "$1".', |