MediaWiki  master
FileBackend.php
Go to the documentation of this file.
00001 <?php
00059 abstract class FileBackend {
00060         protected $name; // string; unique backend name
00061         protected $wikiId; // string; unique wiki name
00062         protected $readOnly; // string; read-only explanation message
00063         protected $parallelize; // string; when to do operations in parallel
00064         protected $concurrency; // integer; how many operations can be done in parallel
00065 
00067         protected $lockManager;
00069         protected $fileJournal;
00070 
00093         public function __construct( array $config ) {
00094                 $this->name = $config['name'];
00095                 if ( !preg_match( '!^[a-zA-Z0-9-_]{1,255}$!', $this->name ) ) {
00096                         throw new MWException( "Backend name `{$this->name}` is invalid." );
00097                 }
00098                 $this->wikiId = isset( $config['wikiId'] )
00099                         ? $config['wikiId']
00100                         : wfWikiID(); // e.g. "my_wiki-en_"
00101                 $this->lockManager = ( $config['lockManager'] instanceof LockManager )
00102                         ? $config['lockManager']
00103                         : LockManagerGroup::singleton()->get( $config['lockManager'] );
00104                 $this->fileJournal = isset( $config['fileJournal'] )
00105                         ? ( ( $config['fileJournal'] instanceof FileJournal )
00106                                 ? $config['fileJournal']
00107                                 : FileJournal::factory( $config['fileJournal'], $this->name ) )
00108                         : FileJournal::factory( array( 'class' => 'NullFileJournal' ), $this->name );
00109                 $this->readOnly = isset( $config['readOnly'] )
00110                         ? (string)$config['readOnly']
00111                         : '';
00112                 $this->parallelize = isset( $config['parallelize'] )
00113                         ? (string)$config['parallelize']
00114                         : 'off';
00115                 $this->concurrency = isset( $config['concurrency'] )
00116                         ? (int)$config['concurrency']
00117                         : 50;
00118         }
00119 
00127         final public function getName() {
00128                 return $this->name;
00129         }
00130 
00138         final public function getWikiId() {
00139                 return $this->wikiId;
00140         }
00141 
00147         final public function isReadOnly() {
00148                 return ( $this->readOnly != '' );
00149         }
00150 
00156         final public function getReadOnlyReason() {
00157                 return ( $this->readOnly != '' ) ? $this->readOnly : false;
00158         }
00159 
00292         final public function doOperations( array $ops, array $opts = array() ) {
00293                 if ( empty( $opts['bypassReadOnly'] ) && $this->isReadOnly() ) {
00294                         return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
00295                 }
00296                 if ( empty( $opts['force'] ) ) { // sanity
00297                         unset( $opts['nonLocking'] );
00298                         unset( $opts['allowStale'] );
00299                 }
00300                 return $this->doOperationsInternal( $ops, $opts );
00301         }
00302 
00306         abstract protected function doOperationsInternal( array $ops, array $opts );
00307 
00319         final public function doOperation( array $op, array $opts = array() ) {
00320                 return $this->doOperations( array( $op ), $opts );
00321         }
00322 
00333         final public function create( array $params, array $opts = array() ) {
00334                 return $this->doOperation( array( 'op' => 'create' ) + $params, $opts );
00335         }
00336 
00347         final public function store( array $params, array $opts = array() ) {
00348                 return $this->doOperation( array( 'op' => 'store' ) + $params, $opts );
00349         }
00350 
00361         final public function copy( array $params, array $opts = array() ) {
00362                 return $this->doOperation( array( 'op' => 'copy' ) + $params, $opts );
00363         }
00364 
00375         final public function move( array $params, array $opts = array() ) {
00376                 return $this->doOperation( array( 'op' => 'move' ) + $params, $opts );
00377         }
00378 
00389         final public function delete( array $params, array $opts = array() ) {
00390                 return $this->doOperation( array( 'op' => 'delete' ) + $params, $opts );
00391         }
00392 
00483         final public function doQuickOperations( array $ops, array $opts = array() ) {
00484                 if ( empty( $opts['bypassReadOnly'] ) && $this->isReadOnly() ) {
00485                         return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
00486                 }
00487                 foreach ( $ops as &$op ) {
00488                         $op['overwrite'] = true; // avoids RTTs in key/value stores
00489                 }
00490                 return $this->doQuickOperationsInternal( $ops );
00491         }
00492 
00497         abstract protected function doQuickOperationsInternal( array $ops );
00498 
00509         final public function doQuickOperation( array $op ) {
00510                 return $this->doQuickOperations( array( $op ) );
00511         }
00512 
00523         final public function quickCreate( array $params ) {
00524                 return $this->doQuickOperation( array( 'op' => 'create' ) + $params );
00525         }
00526 
00537         final public function quickStore( array $params ) {
00538                 return $this->doQuickOperation( array( 'op' => 'store' ) + $params );
00539         }
00540 
00551         final public function quickCopy( array $params ) {
00552                 return $this->doQuickOperation( array( 'op' => 'copy' ) + $params );
00553         }
00554 
00565         final public function quickMove( array $params ) {
00566                 return $this->doQuickOperation( array( 'op' => 'move' ) + $params );
00567         }
00568 
00579         final public function quickDelete( array $params ) {
00580                 return $this->doQuickOperation( array( 'op' => 'delete' ) + $params );
00581         }
00582 
00596         abstract public function concatenate( array $params );
00597 
00615         final public function prepare( array $params ) {
00616                 if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
00617                         return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
00618                 }
00619                 return $this->doPrepare( $params );
00620         }
00621 
00625         abstract protected function doPrepare( array $params );
00626 
00642         final public function secure( array $params ) {
00643                 if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
00644                         return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
00645                 }
00646                 return $this->doSecure( $params );
00647         }
00648 
00652         abstract protected function doSecure( array $params );
00653 
00670         final public function publish( array $params ) {
00671                 if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
00672                         return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
00673                 }
00674                 return $this->doPublish( $params );
00675         }
00676 
00680         abstract protected function doPublish( array $params );
00681 
00694         final public function clean( array $params ) {
00695                 if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
00696                         return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
00697                 }
00698                 return $this->doClean( $params );
00699         }
00700 
00704         abstract protected function doClean( array $params );
00705 
00716         abstract public function fileExists( array $params );
00717 
00727         abstract public function getFileTimestamp( array $params );
00728 
00739         final public function getFileContents( array $params ) {
00740                 $contents = $this->getFileContentsMulti(
00741                         array( 'srcs' => array( $params['src'] ) ) + $params );
00742 
00743                 return $contents[$params['src']];
00744         }
00745 
00761         abstract public function getFileContentsMulti( array $params );
00762 
00772         abstract public function getFileSize( array $params );
00773 
00788         abstract public function getFileStat( array $params );
00789 
00799         abstract public function getFileSha1Base36( array $params );
00800 
00811         abstract public function getFileProps( array $params );
00812 
00827         abstract public function streamFile( array $params );
00828 
00848         final public function getLocalReference( array $params ) {
00849                 $fsFiles = $this->getLocalReferenceMulti(
00850                         array( 'srcs' => array( $params['src'] ) ) + $params );
00851 
00852                 return $fsFiles[$params['src']];
00853         }
00854 
00870         abstract public function getLocalReferenceMulti( array $params );
00871 
00883         final public function getLocalCopy( array $params ) {
00884                 $tmpFiles = $this->getLocalCopyMulti(
00885                         array( 'srcs' => array( $params['src'] ) ) + $params );
00886 
00887                 return $tmpFiles[$params['src']];
00888         }
00889 
00905         abstract public function getLocalCopyMulti( array $params );
00906 
00923         abstract public function getFileHttpUrl( array $params );
00924 
00938         abstract public function directoryExists( array $params );
00939 
00957         abstract public function getDirectoryList( array $params );
00958 
00971         final public function getTopDirectoryList( array $params ) {
00972                 return $this->getDirectoryList( array( 'topOnly' => true ) + $params );
00973         }
00974 
00991         abstract public function getFileList( array $params );
00992 
01005         final public function getTopFileList( array $params ) {
01006                 return $this->getFileList( array( 'topOnly' => true ) + $params );
01007         }
01008 
01016         public function preloadCache( array $paths ) {}
01017 
01025         public function clearCache( array $paths = null ) {}
01026 
01037         final public function lockFiles( array $paths, $type ) {
01038                 return $this->lockManager->lock( $paths, $type );
01039         }
01040 
01048         final public function unlockFiles( array $paths, $type ) {
01049                 return $this->lockManager->unlock( $paths, $type );
01050         }
01051 
01065         final public function getScopedFileLocks( array $paths, $type, Status $status ) {
01066                 return ScopedLock::factory( $this->lockManager, $paths, $type, $status );
01067         }
01068 
01085         abstract public function getScopedLocksForOps( array $ops, Status $status );
01086 
01094         final public function getRootStoragePath() {
01095                 return "mwstore://{$this->name}";
01096         }
01097 
01105         final public function getContainerStoragePath( $container ) {
01106                 return $this->getRootStoragePath() . "/{$container}";
01107         }
01108 
01114         final public function getJournal() {
01115                 return $this->fileJournal;
01116         }
01117 
01125         final public static function isStoragePath( $path ) {
01126                 return ( strpos( $path, 'mwstore://' ) === 0 );
01127         }
01128 
01137         final public static function splitStoragePath( $storagePath ) {
01138                 if ( self::isStoragePath( $storagePath ) ) {
01139                         // Remove the "mwstore://" prefix and split the path
01140                         $parts = explode( '/', substr( $storagePath, 10 ), 3 );
01141                         if ( count( $parts ) >= 2 && $parts[0] != '' && $parts[1] != '' ) {
01142                                 if ( count( $parts ) == 3 ) {
01143                                         return $parts; // e.g. "backend/container/path"
01144                                 } else {
01145                                         return array( $parts[0], $parts[1], '' ); // e.g. "backend/container"
01146                                 }
01147                         }
01148                 }
01149                 return array( null, null, null );
01150         }
01151 
01159         final public static function normalizeStoragePath( $storagePath ) {
01160                 list( $backend, $container, $relPath ) = self::splitStoragePath( $storagePath );
01161                 if ( $relPath !== null ) { // must be for this backend
01162                         $relPath = self::normalizeContainerPath( $relPath );
01163                         if ( $relPath !== null ) {
01164                                 return ( $relPath != '' )
01165                                         ? "mwstore://{$backend}/{$container}/{$relPath}"
01166                                         : "mwstore://{$backend}/{$container}";
01167                         }
01168                 }
01169                 return null;
01170         }
01171 
01180         final public static function parentStoragePath( $storagePath ) {
01181                 $storagePath = dirname( $storagePath );
01182                 list( $b, $cont, $rel ) = self::splitStoragePath( $storagePath );
01183                 return ( $rel === null ) ? null : $storagePath;
01184         }
01185 
01192         final public static function extensionFromPath( $path ) {
01193                 $i = strrpos( $path, '.' );
01194                 return strtolower( $i ? substr( $path, $i + 1 ) : '' );
01195         }
01196 
01204         final public static function isPathTraversalFree( $path ) {
01205                 return ( self::normalizeContainerPath( $path ) !== null );
01206         }
01207 
01217         final public static function makeContentDisposition( $type, $filename = '' ) {
01218                 $parts = array();
01219 
01220                 $type = strtolower( $type );
01221                 if ( !in_array( $type, array( 'inline', 'attachment' ) ) ) {
01222                         throw new MWException( "Invalid Content-Disposition type '$type'." );
01223                 }
01224                 $parts[] = $type;
01225 
01226                 if ( strlen( $filename ) ) {
01227                         $parts[] = "filename*=UTF-8''" . rawurlencode( basename( $filename ) );
01228                 }
01229 
01230                 return implode( ';', $parts );
01231         }
01232 
01243         final protected static function normalizeContainerPath( $path ) {
01244                 // Normalize directory separators
01245                 $path = strtr( $path, '\\', '/' );
01246                 // Collapse any consecutive directory separators
01247                 $path = preg_replace( '![/]{2,}!', '/', $path );
01248                 // Remove any leading directory separator
01249                 $path = ltrim( $path, '/' );
01250                 // Use the same traversal protection as Title::secureAndSplit()
01251                 if ( strpos( $path, '.' ) !== false ) {
01252                         if (
01253                                 $path === '.' ||
01254                                 $path === '..' ||
01255                                 strpos( $path, './' ) === 0 ||
01256                                 strpos( $path, '../' ) === 0 ||
01257                                 strpos( $path, '/./' ) !== false ||
01258                                 strpos( $path, '/../' ) !== false
01259                         ) {
01260                                 return null;
01261                         }
01262                 }
01263                 return $path;
01264         }
01265 }