Index: branches/FileBackend/phase3/includes/filerepo/backend/FileBackendMultiWrite.php |
— | — | @@ -164,6 +164,14 @@ |
165 | 165 | return $status; |
166 | 166 | } |
167 | 167 | |
| 168 | + function secure( array $params ) { |
| 169 | + $status = Status::newGood(); |
| 170 | + foreach ( $this->backends as $backend ) { |
| 171 | + $status->merge( $backend->prepare( $params ) ); |
| 172 | + } |
| 173 | + return $status; |
| 174 | + } |
| 175 | + |
168 | 176 | function fileExists( array $params ) { |
169 | 177 | foreach ( $this->backends as $backend ) { |
170 | 178 | if ( $backend->fileExists( $params ) ) { |
Index: branches/FileBackend/phase3/includes/filerepo/backend/FSFileBackend.php |
— | — | @@ -5,7 +5,9 @@ |
6 | 6 | */ |
7 | 7 | |
8 | 8 | /** |
9 | | - * Class for a file-system based file backend |
| 9 | + * Class for a file-system based file backend. |
| 10 | + * Status messages should avoid mentioning the internal FS paths. |
| 11 | + * Likewise, error suppression should be used to path disclosure. |
10 | 12 | */ |
11 | 13 | class FSFileBackend extends FileBackend { |
12 | 14 | /** @var Array Map of container names to paths */ |
— | — | @@ -14,8 +16,8 @@ |
15 | 17 | |
16 | 18 | function __construct( array $config ) { |
17 | 19 | $this->name = $config['name']; |
18 | | - $this->containerPaths = (array)$config['containers']; |
19 | | - $this->lockManager = $config['lockManger']; |
| 20 | + $this->containerPaths = (array)$config['containerPaths']; |
| 21 | + $this->lockManager = $config['lockManager']; |
20 | 22 | $this->fileMode = isset( $config['fileMode'] ) |
21 | 23 | ? $config['fileMode'] |
22 | 24 | : 0644; |
— | — | @@ -40,7 +42,9 @@ |
41 | 43 | |
42 | 44 | if ( file_exists( $dest ) ) { |
43 | 45 | if ( isset( $params['overwriteDest'] ) ) { |
| 46 | + wfSuppressWarnings(); |
44 | 47 | $ok = unlink( $dest ); |
| 48 | + wfRestoreWarnings(); |
45 | 49 | if ( !$ok ) { |
46 | 50 | $status->fatal( 'backend-fail-delete', $param['dest'] ); |
47 | 51 | return $status; |
— | — | @@ -95,7 +99,9 @@ |
96 | 100 | if ( isset( $params['overwriteDest'] ) ) { |
97 | 101 | // Windows does not support moving over existing files |
98 | 102 | if ( wfIsWindows() ) { |
| 103 | + wfSuppressWarnings(); |
99 | 104 | $ok = unlink( $dest ); |
| 105 | + wfRestoreWarnings(); |
100 | 106 | if ( !$ok ) { |
101 | 107 | $status->fatal( 'backend-fail-delete', $params['dest'] ); |
102 | 108 | return $status; |
— | — | @@ -261,7 +267,9 @@ |
262 | 268 | |
263 | 269 | if ( file_exists( $dest ) ) { |
264 | 270 | if ( isset( $params['overwriteDest'] ) ) { |
| 271 | + wfSuppressWarnings(); |
265 | 272 | $ok = unlink( $dest ); |
| 273 | + wfRestoreWarnings(); |
266 | 274 | if ( !$ok ) { |
267 | 275 | $status->fatal( 'backend-fail-delete', $param['dest'] ); |
268 | 276 | return $status; |
— | — | @@ -291,9 +299,11 @@ |
292 | 300 | } |
293 | 301 | |
294 | 302 | function prepare( array $params ) { |
| 303 | + $status = Status::newGood(); |
295 | 304 | list( $c, $dir ) = $this->resolveVirtualPath( $params['directory'] ); |
296 | 305 | if ( $dir === null ) { |
297 | | - return false; // invalid storage path |
| 306 | + $status->fatal( 'backend-fail-invalidpath', $params['directory'] ); |
| 307 | + return $status; // invalid storage path |
298 | 308 | } |
299 | 309 | if ( !wfMkdirParents( $dir ) ) { |
300 | 310 | $status->fatal( 'directorycreateerror', $param['directory'] ); |
— | — | @@ -310,6 +320,40 @@ |
311 | 321 | return $status; |
312 | 322 | } |
313 | 323 | |
| 324 | + function secure( array $params ) { |
| 325 | + $status = Status::newGood(); |
| 326 | + list( $c, $dir ) = $this->resolveVirtualPath( $params['directory'] ); |
| 327 | + if ( $dir === null ) { |
| 328 | + $status->fatal( 'backend-fail-invalidpath', $params['directory'] ); |
| 329 | + return $status; // invalid storage path |
| 330 | + } |
| 331 | + if ( !wfMkdirParents( $dir ) ) { |
| 332 | + $status->fatal( 'directorycreateerror', $param['directory'] ); |
| 333 | + return $status; |
| 334 | + } |
| 335 | + // Add a .htaccess file to the root of the deleted zone |
| 336 | + if ( !empty( $params['noAccess'] ) && !file_exists( "{$dir}/.htaccess" ) ) { |
| 337 | + wfSuppressWarnings(); |
| 338 | + $ok = file_put_contents( "{$dir}/.htaccess", "Deny from all\n" ); |
| 339 | + wfRestoreWarnings(); |
| 340 | + if ( !$ok ) { |
| 341 | + $status->fatal( 'backend-fail-create', $params['directory'] . '/.htaccess' ); |
| 342 | + return $status; |
| 343 | + } |
| 344 | + } |
| 345 | + // Seed new directories with a blank index.html, to prevent crawling |
| 346 | + if ( !empty( $params['noListing'] ) && !file_exists( "{$dir}/index.html" ) ) { |
| 347 | + wfSuppressWarnings(); |
| 348 | + $ok = file_put_contents( "{$dir}/index.html", '' ); |
| 349 | + wfRestoreWarnings(); |
| 350 | + if ( !$ok ) { |
| 351 | + $status->fatal( 'backend-fail-create', $params['dest'] . '/index.html' ); |
| 352 | + return $status; |
| 353 | + } |
| 354 | + } |
| 355 | + return $status; |
| 356 | + } |
| 357 | + |
314 | 358 | function fileExists( array $params ) { |
315 | 359 | list( $c, $source ) = $this->resolveVirtualPath( $params['source'] ); |
316 | 360 | if ( $source === null ) { |
— | — | @@ -399,27 +443,16 @@ |
400 | 444 | * @return bool |
401 | 445 | */ |
402 | 446 | protected function filesAreSame( $path1, $path2 ) { |
403 | | - return ( // check size first since it's faster |
| 447 | + wfSuppressWarnings(); |
| 448 | + $same = ( // check size first since it's faster |
404 | 449 | filesize( $path1 ) === filesize( $path2 ) && |
405 | 450 | sha1_file( $path1 ) === sha1_file( $path2 ) |
406 | 451 | ); |
| 452 | + wfRestoreWarnings(); |
| 453 | + return $same; |
407 | 454 | } |
408 | 455 | |
409 | 456 | /** |
410 | | - * Check if a file has identical contents as a string |
411 | | - * |
412 | | - * @param $path string Absolute filesystem path |
413 | | - * @param $content string Raw file data |
414 | | - * @return bool |
415 | | - */ |
416 | | - protected function fileAndDataAreSame( $path, $content ) { |
417 | | - return ( // check size first since it's faster |
418 | | - filesize( $path ) === strlen( $content ) && |
419 | | - sha1_file( $path ) === sha1( $content ) |
420 | | - ); |
421 | | - } |
422 | | - |
423 | | - /** |
424 | 457 | * Chmod a file, suppressing the warnings |
425 | 458 | * |
426 | 459 | * @param $path string Absolute file system path |
— | — | @@ -459,7 +492,7 @@ |
460 | 493 | if ( !$this->loaded ) { |
461 | 494 | $this->loaded = true; |
462 | 495 | // If we get an invalid directory then the result is an empty list |
463 | | - if ( is_dir( $this->directory ) ) { |
| 496 | + if ( file_exists( $this->directory ) && is_dir( $this->directory ) ) { |
464 | 497 | $handle = opendir( $this->directory ); |
465 | 498 | if ( $handle ) { |
466 | 499 | $this->pushDirectory( $this->directory, $handle ); |
Index: branches/FileBackend/phase3/includes/filerepo/backend/FileLockManager.php |
— | — | @@ -488,7 +488,7 @@ |
489 | 489 | $votesLeft--; |
490 | 490 | $votesNeeded = $quorum - $yesVotes; |
491 | 491 | if ( $votesNeeded > $votesLeft && !$this->trustCache ) { |
492 | | - // In "trust cache" mode we don't have to meet the quorum. |
| 492 | + // In "trust cache" mode we don't have to meet the quorum |
493 | 493 | break; // short-circuit |
494 | 494 | } |
495 | 495 | } |
Index: branches/FileBackend/phase3/includes/filerepo/backend/FileBackend.php |
— | — | @@ -86,6 +86,20 @@ |
87 | 87 | abstract public function prepare( array $params ); |
88 | 88 | |
89 | 89 | /** |
| 90 | + * Take measures to block web access to a directory. |
| 91 | + * This is not guaranteed to actually do anything. |
| 92 | + * Do not call this function from places outside FileBackend and FileOp. |
| 93 | + * $params include: |
| 94 | + * directory : destination storage path |
| 95 | + * noAccess : try to deny file access |
| 96 | + * noListing : try to deny file listing |
| 97 | + * |
| 98 | + * @param Array $params |
| 99 | + * @return Status |
| 100 | + */ |
| 101 | + abstract public function secure( array $params ); |
| 102 | + |
| 103 | + /** |
90 | 104 | * Check if a file exits at a storage path in the backend. |
91 | 105 | * Do not call this function from places outside FileBackend and FileOp. |
92 | 106 | * $params include: |
— | — | @@ -112,9 +126,7 @@ |
113 | 127 | * |
114 | 128 | * @return string (md5, sha1, unknown, ...) |
115 | 129 | */ |
116 | | - public function getHashType() { |
117 | | - return 'unknown'; |
118 | | - } |
| 130 | + abstract public function getHashType(); |
119 | 131 | |
120 | 132 | /** |
121 | 133 | * Get the properties of the file that exists at a storage path in the backend |
— | — | @@ -141,10 +153,10 @@ |
142 | 154 | |
143 | 155 | /** |
144 | 156 | * Get an iterator to list out all object files under a storage directory. |
| 157 | + * If the directory is of the form "mwstore://container", then all items in |
| 158 | + * the container should be listed. If of the form "mwstore://container/dir", |
| 159 | + * then all items under that container directory should be listed. |
145 | 160 | * Results should be storage paths relative to the given directory. |
146 | | - * If the directory is of the form "mwstore://container", then all items |
147 | | - * in the container should be listed. If of the form "mwstore://container/dir", |
148 | | - * then all items under that container directory should be listed. |
149 | 161 | * $params include: |
150 | 162 | * directory : storage path directory. |
151 | 163 | * |
— | — | @@ -271,6 +283,14 @@ |
272 | 284 | */ |
273 | 285 | abstract public function create( array $params ); |
274 | 286 | |
| 287 | + public function prepare( array $params ) { |
| 288 | + return Status::newGood(); |
| 289 | + } |
| 290 | + |
| 291 | + public function secure( array $params ) { |
| 292 | + return Status::newGood(); |
| 293 | + } |
| 294 | + |
275 | 295 | /** |
276 | 296 | * Whether this backend implements move() and is applies to a potential |
277 | 297 | * move from one storage path to another. No backends hits are required. |