Browse Source

Merge pull request #11759 from cakephp/bug/utf8-file

#11749 Attempt to fix UTF8 filenames
Mark Story 8 years ago
parent
commit
e081c01077
2 changed files with 116 additions and 4 deletions
  1. 33 4
      src/Filesystem/File.php
  2. 83 0
      tests/TestCase/Filesystem/FileTest.php

+ 33 - 4
src/Filesystem/File.php

@@ -15,6 +15,7 @@
 namespace Cake\Filesystem;
 namespace Cake\Filesystem;
 
 
 use finfo;
 use finfo;
+use SplFileInfo;
 
 
 /**
 /**
  * Convenience class for reading, writing and appending to files.
  * Convenience class for reading, writing and appending to files.
@@ -82,9 +83,10 @@ class File
      */
      */
     public function __construct($path, $create = false, $mode = 0755)
     public function __construct($path, $create = false, $mode = 0755)
     {
     {
-        $this->Folder = new Folder(dirname($path), $create, $mode);
+        $splInfo = new SplFileInfo($path);
+        $this->Folder = new Folder($splInfo->getPath(), $create, $mode);
         if (!is_dir($path)) {
         if (!is_dir($path)) {
-            $this->name = basename($path);
+            $this->name = ltrim($splInfo->getFilename(), '/\\');
         }
         }
         $this->pwd();
         $this->pwd();
         $create && !$this->exists() && $this->safe($path) && $this->create();
         $create && !$this->exists() && $this->safe($path) && $this->create();
@@ -343,7 +345,7 @@ class File
             $this->info();
             $this->info();
         }
         }
         if (isset($this->info['extension'])) {
         if (isset($this->info['extension'])) {
-            return basename($this->name, '.' . $this->info['extension']);
+            return static::_basename($this->name, '.' . $this->info['extension']);
         }
         }
         if ($this->name) {
         if ($this->name) {
             return $this->name;
             return $this->name;
@@ -353,6 +355,33 @@ class File
     }
     }
 
 
     /**
     /**
+     * Returns the file basename. simulate the php basename() for multibyte (mb_basename).
+     *
+     * @param string $path Path to file
+     * @param string|null $ext The name of the extension
+     * @return string the file basename.
+     */
+    protected static function _basename($path, $ext = null)
+    {
+        //check for multibyte string and use basename() if not found
+        if (mb_strlen($path) === strlen($path)) {
+            return ($ext === null)? basename($path) : basename($path, $ext);
+        }
+
+        $splInfo = new SplFileInfo($path);
+        $name = ltrim($splInfo->getFilename(), '/\\');
+
+        if ($ext === null || $ext === '') {
+            return $name;
+        }
+        $ext = preg_quote($ext);
+        $new = preg_replace("/({$ext})$/u", "", $name);
+
+        // basename of '/etc/.d' is '.d' not ''
+        return ($new === '')? $name : $new;
+    }
+
+    /**
      * Makes file name safe for saving
      * Makes file name safe for saving
      *
      *
      * @param string|null $name The name of the file to make safe if different from $this->name
      * @param string|null $name The name of the file to make safe if different from $this->name
@@ -368,7 +397,7 @@ class File
             $ext = $this->ext();
             $ext = $this->ext();
         }
         }
 
 
-        return preg_replace("/(?:[^\w\.-]+)/", '_', basename($name, $ext));
+        return preg_replace("/(?:[^\w\.-]+)/", '_', static::_basename($name, $ext));
     }
     }
 
 
     /**
     /**

+ 83 - 0
tests/TestCase/Filesystem/FileTest.php

@@ -17,6 +17,7 @@ namespace Cake\Test\TestCase\Filesystem;
 use Cake\Filesystem\File;
 use Cake\Filesystem\File;
 use Cake\Filesystem\Folder;
 use Cake\Filesystem\Folder;
 use Cake\TestSuite\TestCase;
 use Cake\TestSuite\TestCase;
+use SplFileInfo;
 
 
 /**
 /**
  * FileTest class
  * FileTest class
@@ -124,6 +125,88 @@ class FileTest extends TestCase
     }
     }
 
 
     /**
     /**
+     * testUtf8Filenames
+     *
+     * @link https://github.com/cakephp/cakephp/issues/11749
+     * @return void
+     */
+    public function testUtf8Filenames()
+    {
+        $File = new File(TMP . 'tests/permissions/نام فارسی.php', true);
+        $this->assertEquals('نام فارسی', $File->name());
+        $this->assertTrue($File->exists());
+        $this->assertTrue($File->readable());
+    }
+
+    /**
+     * Test _basename method
+     * @dataProvider baseNameValueProvider
+     * @return void
+     */
+    public function testBasename($path, $suffix, $isRoot)
+    {
+        if (!$isRoot) {
+            $path = TMP . 'tests/permissions' . $path;
+        }
+        $File = new File($path, false);
+
+        //some of paths is directory like '/etc/sudoers.d'
+        if (!is_dir($path)) {
+            //test the name after running __construct()
+            $result = $File->name;
+            $expecting = basename($path);
+            $this->assertEquals($expecting, $result);
+        }
+
+        //test the name()
+        $splInfo = new SplFileInfo($path);
+        $File->name = ltrim($splInfo->getFilename(), '/\\');
+        if ($suffix === null) {
+            $File->info();//to set and unset 'extension' in bellow
+            if (isset($File->info['extension'])) {
+                unset($File->info['extension']);
+            }
+            $this->assertEquals(basename($path), $File->name());
+        } else {
+            $File->info['extension'] = $suffix;
+            $this->assertEquals(basename($path, '.' . $suffix), $File->name());
+        }
+    }
+
+    /**
+     * Data provider for testBasename().
+     *
+     * @return array
+     */
+    public function baseNameValueProvider()
+    {
+        return [
+            ['folder/نام.txt', null, false],
+            ['folder/نام فارسی.txt', null, false],
+            ['نام.txt', null, true],
+            ['نام فارسی.txt', null, true],
+            ['/نام.txt', null, true],
+            ['/نام فارسی.txt', null, true],
+            //
+            ['folder/نام.txt', 'txt', false],
+            ['folder/نام فارسی.txt', 'txt', false],
+            ['نام.txt', 'txt', true],
+            ['نام فارسی.txt', 'txt', true],
+            ['/نام.txt', 'txt', true],
+            ['/نام فارسی.txt', 'txt', true],
+            //
+            ['abcde.ab', 'abe', false],
+            ['/etc/sudoers.d', null, true],
+            ['/etc/.d', 'd', true],
+            ['/etc/sudoers.d', 'd', true],
+            ['/etc/passwd', null, true],
+            ['/etc/', null, true],
+            ['.', null, true],
+            ['/', null, true],
+        ];
+    }
+
+    /**
      * testPermission method
      * testPermission method
      *
      *
      * @return void
      * @return void