diff --git a/Tests/FileTest.php b/Tests/FileTest.php index b93d63c0..035cbf9f 100644 --- a/Tests/FileTest.php +++ b/Tests/FileTest.php @@ -44,7 +44,7 @@ public function dataTestStripExt(): \Generator * * @dataProvider dataTestStripExt */ - public function testStripExt($fileName, $nameWithoutExt) + public function testStripExt(string $fileName, string $nameWithoutExt): void { $this->assertEquals( File::stripExt($fileName), @@ -101,6 +101,14 @@ public function dataTestMakeSafe(): \Generator '.gitignore', 'Files starting with a fullstop should be allowed when strip chars parameter is empty', ]; + + $longName = str_repeat('0123456789abcdef', 16); + yield [ + $longName . '.png', + [], + substr($longName, 0, 236) . '.png', + 'Filenames with more than 240 characters should be shortened to 240 characters preserving the extension', + ]; } /** @@ -111,18 +119,18 @@ public function dataTestMakeSafe(): \Generator * @param string $expected The expected safe file name * @param string $message The message to show on failure of test * - * @covers Joomla\Filesystem\File::makeSafe + * @covers \Joomla\Filesystem\File::makeSafe * @dataProvider dataTestMakeSafe */ - public function testMakeSafe($name, $stripChars, $expected, $message) + public function testMakeSafe(string $name, array $stripChars, string $expected, string $message): void { - $this->assertEquals(File::makeSafe($name, $stripChars), $expected, $message); + $this->assertEquals($expected, File::makeSafe($name, $stripChars), $message); } /** * Test copy method. */ - public function testCopyWithPathArgPassed() + public function testCopyWithPathArgPassed(): void { $name = 'tempFile'; $copiedName = 'tempCopiedFileName'; @@ -148,7 +156,7 @@ public function testCopyWithPathArgPassed() /** * Test copy method. */ - public function testCopyWithoutPathArgPassed() + public function testCopyWithoutPathArgPassed(): void { $name = 'tempFile'; $copiedName = 'tempCopiedFileName'; @@ -174,7 +182,7 @@ public function testCopyWithoutPathArgPassed() /** * Test copy method using streams. */ - public function testCopyWithStreams() + public function testCopyWithStreams(): void { $name = 'tempFile'; $copiedName = 'tempCopiedFileName'; @@ -200,7 +208,7 @@ public function testCopyWithStreams() /** * Test makeCopy method for an exception */ - public function testCopySrcDontExist() + public function testCopySrcDontExist(): void { $this->expectException(\UnexpectedValueException::class); @@ -213,7 +221,7 @@ public function testCopySrcDontExist() /** * Test delete method. */ - public function testDeleteForSingleFile() + public function testDeleteForSingleFile(): void { $name = 'tempFile'; $data = 'Lorem ipsum dolor sit amet'; @@ -232,7 +240,7 @@ public function testDeleteForSingleFile() /** * Test delete method. */ - public function testDeleteForArrayOfFiles() + public function testDeleteForArrayOfFiles(): void { $name1 = 'tempFile1'; $name2 = 'tempFile2'; @@ -257,7 +265,7 @@ public function testDeleteForArrayOfFiles() /** * Tests the File::move method. */ - public function testMoveWithPathArgPassed() + public function testMoveWithPathArgPassed(): void { $name = 'tempFile'; $movedName = 'tempCopiedFileName'; @@ -277,7 +285,7 @@ public function testMoveWithPathArgPassed() /** * Tests the File::move method. */ - public function testMoveWithoutPathArgPassed() + public function testMoveWithoutPathArgPassed(): void { $name = 'tempFile'; $movedName = 'tempCopiedFileName'; @@ -297,7 +305,7 @@ public function testMoveWithoutPathArgPassed() /** * Tests the File::move method. */ - public function testMoveWithStreams() + public function testMoveWithStreams(): void { $name = 'tempFile'; $movedName = 'tempCopiedFileName'; @@ -314,11 +322,10 @@ public function testMoveWithStreams() ); } - /** * Test the File::move method where source file doesn't exist. */ - public function testMoveSrcDontExist() + public function testMoveSrcDontExist(): void { $name = 'tempFile'; $movedName = 'tempCopiedFileName'; @@ -332,7 +339,7 @@ public function testMoveSrcDontExist() /** * Test write method. */ - public function testWrite() + public function testWrite(): void { $name = 'tempFile'; $data = 'Lorem ipsum dolor sit amet'; @@ -352,10 +359,10 @@ public function testWrite() /** * Test write method when appending to a file. */ - public function testWriteWithAppend() + public function testWriteWithAppend(): void { - $name = 'tempFile.txt'; - $data = 'Lorem ipsum dolor sit amet'; + $name = 'tempFile.txt'; + $data = 'Lorem ipsum dolor sit amet'; $appendData = PHP_EOL . $data; $this->assertTrue( @@ -378,7 +385,7 @@ public function testWriteWithAppend() /** * Test write method. */ - public function testWriteCreatesMissingDirectory() + public function testWriteCreatesMissingDirectory(): void { $name = 'tempFile'; $data = 'Lorem ipsum dolor sit amet'; @@ -398,7 +405,7 @@ public function testWriteCreatesMissingDirectory() /** * Test write method. */ - public function testWriteWithStreams() + public function testWriteWithStreams(): void { $name = 'tempFile'; $data = 'Lorem ipsum dolor sit amet'; @@ -420,7 +427,7 @@ public function testWriteWithStreams() * * @backupGlobals enabled */ - public function testUpload() + public function testUpload(): void { include_once __DIR__ . '/Stubs/PHPUploadStub.php'; @@ -450,7 +457,7 @@ public function testUpload() * * @backupGlobals enabled */ - public function testUploadWithStreams() + public function testUploadWithStreams(): void { include_once __DIR__ . '/Stubs/PHPUploadStub.php'; @@ -480,12 +487,12 @@ public function testUploadWithStreams() * * @backupGlobals enabled */ - public function testUploadToNestedDirectory() + public function testUploadToNestedDirectory(): void { include_once __DIR__ . '/Stubs/PHPUploadStub.php'; - $name = 'tempFile'; - $data = 'Lorem ipsum dolor sit amet'; + $name = 'tempFile'; + $data = 'Lorem ipsum dolor sit amet'; $uploadedFileName = 'uploadedFileName'; if (!File::write($this->testPath . '/' . $name . '.txt', $data)) @@ -501,7 +508,10 @@ public function testUploadToNestedDirectory() ]; $this->assertTrue( - File::upload($this->testPath . '/' . $name . '.txt', $this->testPath . '/' . $name . '/' . $uploadedFileName) + File::upload( + $this->testPath . '/' . $name . '.txt', + $this->testPath . '/' . $name . '/' . $uploadedFileName + ) ); } } diff --git a/Tests/HelperTest.php b/Tests/HelperTest.php index 45296739..0274f403 100644 --- a/Tests/HelperTest.php +++ b/Tests/HelperTest.php @@ -25,7 +25,7 @@ public function testRemotefsize() ); $this->assertTrue( - is_numeric(Helper::remotefsize('https://www.joomla.org')), + is_numeric(Helper::remotefsize('https://www.example.org')), 'Line:' . __LINE__ . ' for a valid remote file, returned size should be numeric.' ); diff --git a/Tests/fixtures/tempFile.txt b/Tests/fixtures/tempFile.txt new file mode 100644 index 00000000..dc8344c1 --- /dev/null +++ b/Tests/fixtures/tempFile.txt @@ -0,0 +1 @@ +Lorem ipsum dolor sit amet diff --git a/src/File.php b/src/File.php index 2197f3df..f51b0df2 100644 --- a/src/File.php +++ b/src/File.php @@ -50,6 +50,9 @@ public static function makeSafe($file, array $stripChars = ['#^\.#']) // Remove any trailing dots, as those aren't ever valid file names. $file = rtrim($file, '.'); + // Shorten to maximum length, if necessary + $file = self::shortenIfTooLong($file); + return $file; } @@ -88,7 +91,9 @@ public static function copy($src, $dest, $path = null, $useStreams = false) if (!$stream->copy($src, $dest, null, false)) { - throw new FilesystemException(sprintf('%1$s(%2$s, %3$s): %4$s', __METHOD__, $src, $dest, $stream->getError())); + throw new FilesystemException( + sprintf('%1$s(%2$s, %3$s): %4$s', __METHOD__, $src, $dest, $stream->getError()) + ); } self::invalidateFileCache($dest); @@ -281,7 +286,9 @@ public static function upload($src, $dest, $useStreams = false) if (!$stream->upload($src, $dest, null, false)) { - throw new FilesystemException(sprintf('%1$s(%2$s, %3$s): %4$s', __METHOD__, $src, $dest, $stream->getError())); + throw new FilesystemException( + sprintf('%1$s(%2$s, %3$s): %4$s', __METHOD__, $src, $dest, $stream->getError()) + ); } self::invalidateFileCache($dest); @@ -325,4 +332,40 @@ public static function invalidateFileCache($file) } } } + + /** + * Shorten a filename to a maximum allowed length + * + * The directory name is not changed. + * + * @param string $filename The filename + * @param int $maxLen The maximum length including extension. Defaults to 240. + * + * @return string The original filename, if it is shorter than the maximum length, a shortened filename otherwise. + */ + private static function shortenIfTooLong(string $filename, int $maxLen = 240): string + { + $info = pathinfo($filename); + + if (strlen($info['filename']) > $maxLen) + { + $path = $info['dirname'] === '.' ? '' : $info['dirname']; + + if ($path > '') + { + $path .= '/'; + } + + $ext = $info['extension'] ?? ''; + + if ($ext > '') + { + $ext = '.' . $ext; + } + + $filename = $path . substr($info['filename'], 0, $maxLen - strlen($ext)) . $ext; + } + + return $filename; + } }