MediaWiki
REL1_21
|
00001 <?php 00063 abstract class FileBackend { 00064 protected $name; // string; unique backend name 00065 protected $wikiId; // string; unique wiki name 00066 protected $readOnly; // string; read-only explanation message 00067 protected $parallelize; // string; when to do operations in parallel 00068 protected $concurrency; // integer; how many operations can be done in parallel 00069 00071 protected $lockManager; 00073 protected $fileJournal; 00074 00101 public function __construct( array $config ) { 00102 $this->name = $config['name']; 00103 if ( !preg_match( '!^[a-zA-Z0-9-_]{1,255}$!', $this->name ) ) { 00104 throw new MWException( "Backend name `{$this->name}` is invalid." ); 00105 } 00106 $this->wikiId = isset( $config['wikiId'] ) 00107 ? $config['wikiId'] 00108 : wfWikiID(); // e.g. "my_wiki-en_" 00109 $this->lockManager = ( $config['lockManager'] instanceof LockManager ) 00110 ? $config['lockManager'] 00111 : LockManagerGroup::singleton( $this->wikiId )->get( $config['lockManager'] ); 00112 $this->fileJournal = isset( $config['fileJournal'] ) 00113 ? ( ( $config['fileJournal'] instanceof FileJournal ) 00114 ? $config['fileJournal'] 00115 : FileJournal::factory( $config['fileJournal'], $this->name ) ) 00116 : FileJournal::factory( array( 'class' => 'NullFileJournal' ), $this->name ); 00117 $this->readOnly = isset( $config['readOnly'] ) 00118 ? (string)$config['readOnly'] 00119 : ''; 00120 $this->parallelize = isset( $config['parallelize'] ) 00121 ? (string)$config['parallelize'] 00122 : 'off'; 00123 $this->concurrency = isset( $config['concurrency'] ) 00124 ? (int)$config['concurrency'] 00125 : 50; 00126 } 00127 00135 final public function getName() { 00136 return $this->name; 00137 } 00138 00146 final public function getWikiId() { 00147 return $this->wikiId; 00148 } 00149 00155 final public function isReadOnly() { 00156 return ( $this->readOnly != '' ); 00157 } 00158 00164 final public function getReadOnlyReason() { 00165 return ( $this->readOnly != '' ) ? $this->readOnly : false; 00166 } 00167 00317 final public function doOperations( array $ops, array $opts = array() ) { 00318 if ( empty( $opts['bypassReadOnly'] ) && $this->isReadOnly() ) { 00319 return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly ); 00320 } 00321 if ( empty( $opts['force'] ) ) { // sanity 00322 unset( $opts['nonLocking'] ); 00323 } 00324 $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts 00325 return $this->doOperationsInternal( $ops, $opts ); 00326 } 00327 00331 abstract protected function doOperationsInternal( array $ops, array $opts ); 00332 00344 final public function doOperation( array $op, array $opts = array() ) { 00345 return $this->doOperations( array( $op ), $opts ); 00346 } 00347 00358 final public function create( array $params, array $opts = array() ) { 00359 return $this->doOperation( array( 'op' => 'create' ) + $params, $opts ); 00360 } 00361 00372 final public function store( array $params, array $opts = array() ) { 00373 return $this->doOperation( array( 'op' => 'store' ) + $params, $opts ); 00374 } 00375 00386 final public function copy( array $params, array $opts = array() ) { 00387 return $this->doOperation( array( 'op' => 'copy' ) + $params, $opts ); 00388 } 00389 00400 final public function move( array $params, array $opts = array() ) { 00401 return $this->doOperation( array( 'op' => 'move' ) + $params, $opts ); 00402 } 00403 00414 final public function delete( array $params, array $opts = array() ) { 00415 return $this->doOperation( array( 'op' => 'delete' ) + $params, $opts ); 00416 } 00417 00429 final public function describe( array $params, array $opts = array() ) { 00430 return $this->doOperation( array( 'op' => 'describe' ) + $params, $opts ); 00431 } 00432 00548 final public function doQuickOperations( array $ops, array $opts = array() ) { 00549 if ( empty( $opts['bypassReadOnly'] ) && $this->isReadOnly() ) { 00550 return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly ); 00551 } 00552 foreach ( $ops as &$op ) { 00553 $op['overwrite'] = true; // avoids RTTs in key/value stores 00554 } 00555 $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts 00556 return $this->doQuickOperationsInternal( $ops ); 00557 } 00558 00563 abstract protected function doQuickOperationsInternal( array $ops ); 00564 00575 final public function doQuickOperation( array $op ) { 00576 return $this->doQuickOperations( array( $op ) ); 00577 } 00578 00589 final public function quickCreate( array $params ) { 00590 return $this->doQuickOperation( array( 'op' => 'create' ) + $params ); 00591 } 00592 00603 final public function quickStore( array $params ) { 00604 return $this->doQuickOperation( array( 'op' => 'store' ) + $params ); 00605 } 00606 00617 final public function quickCopy( array $params ) { 00618 return $this->doQuickOperation( array( 'op' => 'copy' ) + $params ); 00619 } 00620 00631 final public function quickMove( array $params ) { 00632 return $this->doQuickOperation( array( 'op' => 'move' ) + $params ); 00633 } 00634 00645 final public function quickDelete( array $params ) { 00646 return $this->doQuickOperation( array( 'op' => 'delete' ) + $params ); 00647 } 00648 00659 final public function quickDescribe( array $params ) { 00660 return $this->doQuickOperation( array( 'op' => 'describe' ) + $params ); 00661 } 00662 00676 abstract public function concatenate( array $params ); 00677 00695 final public function prepare( array $params ) { 00696 if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) { 00697 return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly ); 00698 } 00699 $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts 00700 return $this->doPrepare( $params ); 00701 } 00702 00706 abstract protected function doPrepare( array $params ); 00707 00723 final public function secure( array $params ) { 00724 if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) { 00725 return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly ); 00726 } 00727 $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts 00728 return $this->doSecure( $params ); 00729 } 00730 00734 abstract protected function doSecure( array $params ); 00735 00752 final public function publish( array $params ) { 00753 if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) { 00754 return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly ); 00755 } 00756 $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts 00757 return $this->doPublish( $params ); 00758 } 00759 00763 abstract protected function doPublish( array $params ); 00764 00777 final public function clean( array $params ) { 00778 if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) { 00779 return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly ); 00780 } 00781 $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts 00782 return $this->doClean( $params ); 00783 } 00784 00788 abstract protected function doClean( array $params ); 00789 00797 final protected function getScopedPHPBehaviorForOps() { 00798 if ( php_sapi_name() != 'cli' ) { // http://bugs.php.net/bug.php?id=47540 00799 $old = ignore_user_abort( true ); // avoid half-finished operations 00800 return new ScopedCallback( function() use ( $old ) { ignore_user_abort( $old ); } ); 00801 } 00802 return null; 00803 } 00804 00815 abstract public function fileExists( array $params ); 00816 00826 abstract public function getFileTimestamp( array $params ); 00827 00838 final public function getFileContents( array $params ) { 00839 $contents = $this->getFileContentsMulti( 00840 array( 'srcs' => array( $params['src'] ) ) + $params ); 00841 00842 return $contents[$params['src']]; 00843 } 00844 00860 abstract public function getFileContentsMulti( array $params ); 00861 00871 abstract public function getFileSize( array $params ); 00872 00887 abstract public function getFileStat( array $params ); 00888 00898 abstract public function getFileSha1Base36( array $params ); 00899 00910 abstract public function getFileProps( array $params ); 00911 00926 abstract public function streamFile( array $params ); 00927 00947 final public function getLocalReference( array $params ) { 00948 $fsFiles = $this->getLocalReferenceMulti( 00949 array( 'srcs' => array( $params['src'] ) ) + $params ); 00950 00951 return $fsFiles[$params['src']]; 00952 } 00953 00969 abstract public function getLocalReferenceMulti( array $params ); 00970 00982 final public function getLocalCopy( array $params ) { 00983 $tmpFiles = $this->getLocalCopyMulti( 00984 array( 'srcs' => array( $params['src'] ) ) + $params ); 00985 00986 return $tmpFiles[$params['src']]; 00987 } 00988 01004 abstract public function getLocalCopyMulti( array $params ); 01005 01023 abstract public function getFileHttpUrl( array $params ); 01024 01038 abstract public function directoryExists( array $params ); 01039 01057 abstract public function getDirectoryList( array $params ); 01058 01071 final public function getTopDirectoryList( array $params ) { 01072 return $this->getDirectoryList( array( 'topOnly' => true ) + $params ); 01073 } 01074 01091 abstract public function getFileList( array $params ); 01092 01105 final public function getTopFileList( array $params ) { 01106 return $this->getFileList( array( 'topOnly' => true ) + $params ); 01107 } 01108 01116 public function preloadCache( array $paths ) {} 01117 01125 public function clearCache( array $paths = null ) {} 01126 01137 final public function lockFiles( array $paths, $type ) { 01138 return $this->lockManager->lock( $paths, $type ); 01139 } 01140 01148 final public function unlockFiles( array $paths, $type ) { 01149 return $this->lockManager->unlock( $paths, $type ); 01150 } 01151 01165 final public function getScopedFileLocks( array $paths, $type, Status $status ) { 01166 return ScopedLock::factory( $this->lockManager, $paths, $type, $status ); 01167 } 01168 01185 abstract public function getScopedLocksForOps( array $ops, Status $status ); 01186 01194 final public function getRootStoragePath() { 01195 return "mwstore://{$this->name}"; 01196 } 01197 01205 final public function getContainerStoragePath( $container ) { 01206 return $this->getRootStoragePath() . "/{$container}"; 01207 } 01208 01214 final public function getJournal() { 01215 return $this->fileJournal; 01216 } 01217 01225 final public static function isStoragePath( $path ) { 01226 return ( strpos( $path, 'mwstore://' ) === 0 ); 01227 } 01228 01237 final public static function splitStoragePath( $storagePath ) { 01238 if ( self::isStoragePath( $storagePath ) ) { 01239 // Remove the "mwstore://" prefix and split the path 01240 $parts = explode( '/', substr( $storagePath, 10 ), 3 ); 01241 if ( count( $parts ) >= 2 && $parts[0] != '' && $parts[1] != '' ) { 01242 if ( count( $parts ) == 3 ) { 01243 return $parts; // e.g. "backend/container/path" 01244 } else { 01245 return array( $parts[0], $parts[1], '' ); // e.g. "backend/container" 01246 } 01247 } 01248 } 01249 return array( null, null, null ); 01250 } 01251 01259 final public static function normalizeStoragePath( $storagePath ) { 01260 list( $backend, $container, $relPath ) = self::splitStoragePath( $storagePath ); 01261 if ( $relPath !== null ) { // must be for this backend 01262 $relPath = self::normalizeContainerPath( $relPath ); 01263 if ( $relPath !== null ) { 01264 return ( $relPath != '' ) 01265 ? "mwstore://{$backend}/{$container}/{$relPath}" 01266 : "mwstore://{$backend}/{$container}"; 01267 } 01268 } 01269 return null; 01270 } 01271 01280 final public static function parentStoragePath( $storagePath ) { 01281 $storagePath = dirname( $storagePath ); 01282 list( , , $rel ) = self::splitStoragePath( $storagePath ); 01283 return ( $rel === null ) ? null : $storagePath; 01284 } 01285 01292 final public static function extensionFromPath( $path ) { 01293 $i = strrpos( $path, '.' ); 01294 return strtolower( $i ? substr( $path, $i + 1 ) : '' ); 01295 } 01296 01304 final public static function isPathTraversalFree( $path ) { 01305 return ( self::normalizeContainerPath( $path ) !== null ); 01306 } 01307 01317 final public static function makeContentDisposition( $type, $filename = '' ) { 01318 $parts = array(); 01319 01320 $type = strtolower( $type ); 01321 if ( !in_array( $type, array( 'inline', 'attachment' ) ) ) { 01322 throw new MWException( "Invalid Content-Disposition type '$type'." ); 01323 } 01324 $parts[] = $type; 01325 01326 if ( strlen( $filename ) ) { 01327 $parts[] = "filename*=UTF-8''" . rawurlencode( basename( $filename ) ); 01328 } 01329 01330 return implode( ';', $parts ); 01331 } 01332 01343 final protected static function normalizeContainerPath( $path ) { 01344 // Normalize directory separators 01345 $path = strtr( $path, '\\', '/' ); 01346 // Collapse any consecutive directory separators 01347 $path = preg_replace( '![/]{2,}!', '/', $path ); 01348 // Remove any leading directory separator 01349 $path = ltrim( $path, '/' ); 01350 // Use the same traversal protection as Title::secureAndSplit() 01351 if ( strpos( $path, '.' ) !== false ) { 01352 if ( 01353 $path === '.' || 01354 $path === '..' || 01355 strpos( $path, './' ) === 0 || 01356 strpos( $path, '../' ) === 0 || 01357 strpos( $path, '/./' ) !== false || 01358 strpos( $path, '/../' ) !== false 01359 ) { 01360 return null; 01361 } 01362 } 01363 return $path; 01364 } 01365 }