diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f299d2..3301641 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,24 @@ Semua perubahan penting pada `opensid/laravel-filemanager` didokumentasikan di b Format mengikuti [Keep a Changelog](https://keepachangelog.com/id/1.1.0/), dan proyek ini memakai [Semantic Versioning](https://semver.org/lang/id/). +## [Unreleased] + +### Diperbaiki + +- `FileContentValidator`: alternatif ` 'No file', 'view type number missing' => 'View type number missing', 'Not enough Memory' => 'Not enough Memory', - 'max_size_reached' => 'Your image folder has reach its maximale size of %d MB.', + 'max_size_reached' => 'The uploaded file exceeds the maximum allowed size of :size MB.', 'B' => 'B', 'KB' => 'KB', 'MB' => 'MB', diff --git a/resources/lang/id/filemanager.php b/resources/lang/id/filemanager.php index 004f711..bd9050b 100644 --- a/resources/lang/id/filemanager.php +++ b/resources/lang/id/filemanager.php @@ -136,7 +136,7 @@ 'no file' => 'No file', 'view type number missing' => 'View type number missing', 'Not enough Memory' => 'Not enough Memory', - 'max_size_reached' => 'Your image folder has reach its maximale size of %d MB.', + 'max_size_reached' => 'Ukuran berkas melebihi batas maksimal :size MB.', 'B' => 'B', 'KB' => 'KB', 'MB' => 'MB', diff --git a/src/Http/Controllers/UploadController.php b/src/Http/Controllers/UploadController.php index 3d592ea..ca3a67a 100644 --- a/src/Http/Controllers/UploadController.php +++ b/src/Http/Controllers/UploadController.php @@ -108,7 +108,7 @@ protected function handleUpload(Request $request, UploadedFile $file, string $fo if ($range === null) { if ($file->getSize() > $this->config->maxUploadSizeBytes()) { - return ['name' => $originalName, 'error' => trans('filemanager::filemanager.max_size_reached')]; + return ['name' => $originalName, 'error' => trans('filemanager::filemanager.max_size_reached', ['size' => $this->config->maxUploadSizeMb()])]; } return $this->finalize($file->getRealPath(), $originalName, $folder); @@ -117,7 +117,7 @@ protected function handleUpload(Request $request, UploadedFile $file, string $fo [$start, $end, $total] = $range; if ($total > $this->config->maxUploadSizeBytes()) { - return ['name' => $originalName, 'error' => trans('filemanager::filemanager.max_size_reached')]; + return ['name' => $originalName, 'error' => trans('filemanager::filemanager.max_size_reached', ['size' => $this->config->maxUploadSizeMb()])]; } $stagingPath = $this->stagingPath($folder, $originalName); @@ -146,7 +146,7 @@ protected function handleUpload(Request $request, UploadedFile $file, string $fo if (filesize($stagingPath) > min($total, $this->config->maxUploadSizeBytes())) { @unlink($stagingPath); - return ['name' => $originalName, 'error' => trans('filemanager::filemanager.max_size_reached')]; + return ['name' => $originalName, 'error' => trans('filemanager::filemanager.max_size_reached', ['size' => $this->config->maxUploadSizeMb()])]; } if ($end + 1 < $total) { diff --git a/src/Services/FileContentValidator.php b/src/Services/FileContentValidator.php index dc4ca78..599a97c 100644 --- a/src/Services/FileContentValidator.php +++ b/src/Services/FileContentValidator.php @@ -20,6 +20,14 @@ * * The polyglot scan reads the entire file (no 4 MB cap) and catches the * bare PHP short tag ` [[[4, 'ftyp']], [[4, 'moov']], [[4, 'free']], [[4, 'mdat']], [[4, 'wide']], [[4, 'skip']]], ]; + /** + * Minimum run of printable/text bytes required right after a bare `maxUploadSizeMb() * 1024 * 1024; + } + + public function maxUploadSizeMb(): int + { + return (int) config('filemanager.max_upload_size', 8); } public function maxTotalSizeBytes(): int|false diff --git a/tests/Feature/UploadRouteTest.php b/tests/Feature/UploadRouteTest.php index 31b9658..c19afb9 100644 --- a/tests/Feature/UploadRouteTest.php +++ b/tests/Feature/UploadRouteTest.php @@ -47,6 +47,32 @@ public function uploads_an_allowed_file_and_returns_its_url(): void Storage::disk('filemanager')->assertExists('photo.jpg'); } + #[Test] + public function reports_the_actual_size_limit_when_a_file_is_too_large(): void + { + // Regression: max_size_reached used to be an untranslated stock + // string with a raw "%d" that trans() never substitutes, so the + // user saw the literal text "...maximale size of %d MB." instead + // of the configured limit. 'id' is the locale OpenSID host apps + // actually run under (config/app.php: APP_LOCALE default 'id'). + app()->setLocale('id'); + config(['filemanager.max_upload_size' => 2]); + + $file = UploadedFile::fake()->create('big.jpg', 3 * 1024)->size(3 * 1024 * 1024); + + $response = $this->post(route('filemanager.upload'), [ + 'fldr' => '', + 'files' => [$file], + ]); + + $response->assertOk(); + $error = $response->json()['files'][0]['error']; + + $this->assertStringContainsString('2', $error); + $this->assertStringNotContainsString('%d', $error); + $this->assertStringNotContainsString(':size', $error); + } + #[Test] public function rejects_a_disallowed_extension_without_storing_anything(): void { diff --git a/tests/Unit/FileContentValidatorTest.php b/tests/Unit/FileContentValidatorTest.php index 84dc8e5..e607adc 100644 --- a/tests/Unit/FileContentValidatorTest.php +++ b/tests/Unit/FileContentValidatorTest.php @@ -29,6 +29,27 @@ public function accepts_a_real_jpeg(): void $this->assertTrue($this->validator->isValidUpload($path, 'jpg')); } + #[Test] + public function accepts_real_noisy_pngs_without_false_positives(): void + { + // Regression: the compressed IDAT stream of a real, high-entropy PNG + // (a photo-like screenshot, not a flat GD test swatch) contains + // effectively random bytes. The old POLYGLOT_PATTERN's bare `noisyPng(); + + $this->assertTrue( + $this->validator->isValidUpload($path, 'png'), + "Iteration {$i}: a clean noisy PNG was rejected (false positive)." + ); + } + } + #[Test] public function rejects_a_php_payload_disguised_with_an_image_extension(): void { @@ -166,6 +187,26 @@ protected function realJpeg(): string return $this->tempFile($bytes); } + protected function noisyPng(): string + { + $width = random_int(200, 500); + $height = random_int(200, 500); + $image = imagecreatetruecolor($width, $height); + + for ($x = 0; $x < $width; $x += 2) { + for ($y = 0; $y < $height; $y += 2) { + imagesetpixel($image, $x, $y, random_int(0, 0xFFFFFF)); + } + } + + ob_start(); + imagepng($image); + $bytes = ob_get_clean(); + imagedestroy($image); + + return $this->tempFile($bytes); + } + protected function tempFile(string $contents): string { $path = tempnam(sys_get_temp_dir(), 'fcv');