Merge "Improve MIME detection in FileBackend"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Tue, 3 Nov 2015 11:10:41 +0000 (11:10 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Tue, 3 Nov 2015 11:10:41 +0000 (11:10 +0000)
includes/filebackend/FileBackendGroup.php
includes/filebackend/FileBackendStore.php
tests/phpunit/includes/filebackend/FileBackendTest.php

index b6ddbad..c043106 100644 (file)
@@ -166,6 +166,7 @@ class FileBackendGroup {
                                ? FileJournal::factory( $config['fileJournal'], $name )
                                : FileJournal::factory( array( 'class' => 'NullFileJournal' ), $name );
                        $config['wanCache'] = ObjectCache::getMainWANInstance();
+                       $config['mimeCallback'] = array( $this, 'guessMimeInternal' );
 
                        $this->backends[$name]['instance'] = new $class( $config );
                }
@@ -203,4 +204,27 @@ class FileBackendGroup {
 
                return null;
        }
+
+       /**
+        * @param string $storagePath
+        * @param string|null $content
+        * @param string|null $fsPath
+        * @return string
+        * @since 1.27
+        */
+       public function guessMimeInternal( $storagePath, $content, $fsPath ) {
+               $magic = MimeMagic::singleton();
+               // Trust the extension of the storage path (caller must validate)
+               $ext = FileBackend::extensionFromPath( $storagePath );
+               $type = $magic->guessTypesForExtension( $ext );
+               // For files without a valid extension (or one at all), inspect the contents
+               if ( !$type && $fsPath ) {
+                       $type = $magic->guessMimeType( $fsPath, false );
+               } elseif ( !$type && strlen( $content ) ) {
+                       $tmpFile = TempFSFile::factory( 'mime_' );
+                       file_put_contents( $tmpFile->getPath(), $content );
+                       $type = $magic->guessMimeType( $tmpFile->getPath(), false );
+               }
+               return $type ?: 'unknown/unknown';
+       }
 }
index 4ec81ec..1432bb9 100644 (file)
@@ -58,7 +58,7 @@ abstract class FileBackendStore extends FileBackend {
        /**
         * @see FileBackend::__construct()
         * Additional $config params include:
-        *   - wanCache     : WANOBjectCache object to use for persistent caching.
+        *   - wanCache     : WANObjectCache object to use for persistent caching.
         *   - mimeCallback : Callback that takes (storage path, content, file system path) and
         *                    returns the MIME type of the file or 'unknown/unknown'. The file
         *                    system path parameter should be used if the content one is null.
@@ -69,10 +69,7 @@ abstract class FileBackendStore extends FileBackend {
                parent::__construct( $config );
                $this->mimeCallback = isset( $config['mimeCallback'] )
                        ? $config['mimeCallback']
-                       : function ( $storagePath, $content, $fsPath ) {
-                               // @todo handle the case of extension-less files using the contents
-                               return StreamFile::contentTypeFromPath( $storagePath ) ?: 'unknown/unknown';
-                       };
+                       : null;
                $this->memCache = WANObjectCache::newEmpty(); // disabled by default
                $this->cheapCache = new ProcessCacheLRU( self::CACHE_CHEAP_SIZE );
                $this->expensiveCache = new ProcessCacheLRU( self::CACHE_EXPENSIVE_SIZE );
@@ -1828,7 +1825,18 @@ abstract class FileBackendStore extends FileBackend {
         * @return string MIME type
         */
        protected function getContentType( $storagePath, $content, $fsPath ) {
-               return call_user_func_array( $this->mimeCallback, func_get_args() );
+               if ( $this->mimeCallback ) {
+                       return call_user_func_array( $this->mimeCallback, func_get_args() );
+               }
+
+               $mime = null;
+               if ( $fsPath !== null && function_exists( 'finfo_file' ) ) {
+                       $finfo = finfo_open( FILEINFO_MIME_TYPE );
+                       $mime = finfo_file( $finfo, $fsPath );
+                       finfo_close( $finfo );
+               }
+
+               return is_string( $mime ) ? $mime : 'unknown/unknown';
        }
 }
 
index e7d092f..95c6092 100644 (file)
@@ -2397,6 +2397,42 @@ class FileBackendTest extends MediaWikiTestCase {
                        "Scoped unlocking of files succeeded with OK status ($backendName)." );
        }
 
+       /**
+        * @dataProvider provider_testGetContentType
+        */
+       public function testGetContentType( $mimeCallback, $mimeFromString ) {
+               global $IP;
+
+               $be = TestingAccessWrapper::newFromObject( new MemoryFileBackend(
+                       array(
+                               'name' => 'testing',
+                               'class' => 'MemoryFileBackend',
+                               'wikiId' => 'meow',
+                               'mimeCallback' => $mimeCallback
+                       )
+               ) );
+
+               $dst = 'mwstore://testing/container/path/to/file_no_ext';
+               $src = "$IP/tests/phpunit/data/media/srgb.jpg";
+               $this->assertEquals( 'image/jpeg', $be->getContentType( $dst, null, $src ) );
+               $this->assertEquals(
+                       $mimeFromString ? 'image/jpeg' : 'unknown/unknown',
+                       $be->getContentType( $dst, file_get_contents( $src ), null ) );
+
+               $src = "$IP/tests/phpunit/data/media/Png-native-test.png";
+               $this->assertEquals( 'image/png', $be->getContentType( $dst, null, $src ) );
+               $this->assertEquals(
+                       $mimeFromString ? 'image/png' : 'unknown/unknown',
+                       $be->getContentType( $dst, file_get_contents( $src ), null ) );
+       }
+
+       public static function provider_testGetContentType() {
+               return array(
+                       array( null, false ),
+                       array( array( FileBackendGroup::singleton(), 'guessMimeInternal' ), true )
+               );
+       }
+
        public function testReadAffinity() {
                $be = TestingAccessWrapper::newFromObject(
                        new FileBackendMultiWrite( array(