MediaWiki
master
|
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 }