diff --git a/plugins/filesystem/local/src/Adapter/LocalAdapter.php b/plugins/filesystem/local/src/Adapter/LocalAdapter.php index 95479b451d918..e2fd366b7a49f 100644 --- a/plugins/filesystem/local/src/Adapter/LocalAdapter.php +++ b/plugins/filesystem/local/src/Adapter/LocalAdapter.php @@ -263,6 +263,8 @@ public function createFile(string $name, string $path, $data): string } catch (FilesystemException) { } + $this->setFilePermissions($localPath); + if ($this->thumbnails && MediaHelper::isImage(pathinfo($localPath)['basename'])) { $thumbnailPaths = $this->getLocalThumbnailPaths($localPath); @@ -304,6 +306,8 @@ public function updateFile(string $name, string $path, $data) } catch (FilesystemException) { } + $this->setFilePermissions($localPath); + if ($this->thumbnails && MediaHelper::isImage(pathinfo($localPath)['basename'])) { $thumbnailPaths = $this->getLocalThumbnailPaths($localPath); @@ -995,6 +999,23 @@ private function getThumbnail(string $path): string return Uri::root() . $this->getEncodedPath($thumbnailPaths['url']); } + /** + * Sets the Joomla default file permissions on a media file. + * + * @param string $path The path of the file + * + * @return void + * + * @since __DEPLOY_VERSION__ + * @throws \Exception + */ + private function setFilePermissions(string $path): void + { + if (!Path::setPermissions($path, '0644')) { + throw new \Exception(Text::_('JLIB_FILESYSTEM_ERROR_WARNFS_ERR01')); + } + } + /** * Create a thumbnail of the given image. * diff --git a/tests/Unit/Plugin/Filesystem/Local/Adapter/LocalAdapterTest.php b/tests/Unit/Plugin/Filesystem/Local/Adapter/LocalAdapterTest.php index 86f8263d9206d..e63d1a690c97f 100644 --- a/tests/Unit/Plugin/Filesystem/Local/Adapter/LocalAdapterTest.php +++ b/tests/Unit/Plugin/Filesystem/Local/Adapter/LocalAdapterTest.php @@ -191,4 +191,33 @@ public function testUpdateFileDoesNotTouchExistingSafeNamedNeighbour() $this->assertSame($expected, $remaining, 'No stray files should be left behind.'); } + + /** + * @testdox creates media files with Joomla's default file permissions + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function testCreateFileAppliesDefaultFilePermissions() + { + if (DIRECTORY_SEPARATOR === '\\') { + $this->markTestSkipped('File permission mode assertions are not portable on Windows.'); + } + + $oldUmask = umask(0077); + + try { + $adapter = new LocalAdapter($this->workDir, 'test-media'); + $adapter->createFile('upload.jpg', '/', $this->jpegBytes([0, 255, 0])); + } finally { + umask($oldUmask); + } + + $this->assertSame( + 0644, + fileperms($this->workDir . '/upload.jpg') & 0777, + 'Media uploads should be readable through the web server.' + ); + } }