[web-downloads] main: Reject trailing newlines in Winlibs deletion requests

From: Date: Fri, 02 Oct 2026 14:49:06 +0000
Subject: [web-downloads] main: Reject trailing newlines in Winlibs deletion requests
Groups: php.webmaster 
Request: Send a blank email to php-webmaster+get-34064@lists.php.net to get a copy of this message
Author: Shivam Mathur (shivammathur) Date: 2026-10-02T20:00:38+05:30 Commit: https://github.com/php/web-downloads/commit/a915aed4ac6cc641fb37a2cb48e2444a1ca2aa53 Raw diff: https://github.com/php/web-downloads/commit/a915aed4ac6cc641fb37a2cb48e2444a1ca2aa53.diff Reject trailing newlines in Winlibs deletion requests Changed paths: M src/Actions/DeleteWinlibsBuild.php M src/Http/Controllers/WinlibsDeleteController.php M tests/Console/Command/WinlibsCommandDeleteTest.php M tests/Http/Controllers/WinlibsDeleteControllerTest.php Diff: diff --git a/src/Actions/DeleteWinlibsBuild.php b/src/Actions/DeleteWinlibsBuild.php index a117899..81b62e3 100644 --- a/src/Actions/DeleteWinlibsBuild.php +++ b/src/Actions/DeleteWinlibsBuild.php @@ -44,7 +44,7 @@ public function handle(): void $filename = $data['filename'] ?? null; if (!in_array($type, ['php', 'pecl'], true) || !is_string($filename) - || preg_match('/^[A-Za-z0-9][A-Za-z0-9._-]*\.zip$/', $filename) !== 1) { + || preg_match('/\A[A-Za-z0-9][A-Za-z0-9._-]*\.zip\z/', $filename) !== 1) { throw new Exception("Invalid Winlibs deletion task: $taskFile"); } diff --git a/src/Http/Controllers/WinlibsDeleteController.php b/src/Http/Controllers/WinlibsDeleteController.php index 269bf33..c72d84a 100644 --- a/src/Http/Controllers/WinlibsDeleteController.php +++ b/src/Http/Controllers/WinlibsDeleteController.php @@ -11,8 +11,8 @@ class WinlibsDeleteController extends BaseController protected function validate(array $data): bool { $validator = new Validator([ - 'type' => 'required|string|regex:/^(php|pecl)$/', - 'filename' => 'required|string|regex:/^[A-Za-z0-9][A-Za-z0-9._-]*\.zip$/', + 'type' => 'required|string|regex:/\A(php|pecl)\z/', + 'filename' => 'required|string|regex:/\A[A-Za-z0-9][A-Za-z0-9._-]*\.zip\z/', ]); $validator->validate($data); diff --git a/tests/Console/Command/WinlibsCommandDeleteTest.php b/tests/Console/Command/WinlibsCommandDeleteTest.php index b31bd62..0fcdb6a 100644 --- a/tests/Console/Command/WinlibsCommandDeleteTest.php +++ b/tests/Console/Command/WinlibsCommandDeleteTest.php @@ -5,6 +5,7 @@ use App\Console\Command\WinlibsCommand; use App\Helpers\Helpers; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; class WinlibsCommandDeleteTest extends TestCase @@ -136,9 +137,10 @@ public function testMissingBuildAndIndexEntriesAreIdempotent(): void $this->assertSame([], $this->queuedTasks()); } - public function testInvalidTaskFailsAndRemainsQueued(): void + #[DataProvider('invalidTasks')] + public function testInvalidTaskFailsAndRemainsQueued(string $type, string $filename): void { - $task = $this->queue('php', '../outside.zip'); + $task = $this->queue($type, $filename); ob_start(); $result = $this->runCommand(); $output = (string) ob_get_clean(); @@ -147,6 +149,17 @@ public function testInvalidTaskFailsAndRemainsQueued(): void $this->assertFileExists($task); } + public static function invalidTasks(): array + { + return [ + 'path traversal' => ['php', '../outside.zip'], + 'php type with trailing newline' => ["php\n", 'zlib-1.3.2-vs18-x64.zip'], + 'pecl type with trailing newline' => ["pecl\n", 'zlib-1.3.2-vs18-x64.zip'], + 'php filename with trailing newline' => ['php', "zlib-1.3.2-vs18-x64.zip\n"], + 'pecl filename with trailing newline' => ['pecl', "zlib-1.3.2-vs18-x64.zip\n"], + ]; + } + public function testIndexFailureRetainsTaskAndZipForRetry(): void { $directory = $this->baseDirectory . '/pecl/deps'; diff --git a/tests/Http/Controllers/WinlibsDeleteControllerTest.php b/tests/Http/Controllers/WinlibsDeleteControllerTest.php index 8c9a9f5..011960a 100644 --- a/tests/Http/Controllers/WinlibsDeleteControllerTest.php +++ b/tests/Http/Controllers/WinlibsDeleteControllerTest.php @@ -63,7 +63,11 @@ public static function invalidPayloads(): array return [ 'missing type' => [['filename' => 'zlib-1.3.2-vs18-x64.zip']], 'bad type' => [['type' => 'other', 'filename' => 'zlib-1.3.2-vs18-x64.zip']], + 'php type with trailing newline' => [['type' => "php\n", 'filename' => 'zlib-1.3.2-vs18-x64.zip']], + 'pecl type with trailing newline' => [['type' => "pecl\n", 'filename' => 'zlib-1.3.2-vs18-x64.zip']], 'missing filename' => [['type' => 'php']], + 'php filename with trailing newline' => [['type' => 'php', 'filename' => "zlib-1.3.2-vs18-x64.zip\n"]], + 'pecl filename with trailing newline' => [['type' => 'pecl', 'filename' => "zlib-1.3.2-vs18-x64.zip\n"]], 'path traversal' => [['type' => 'php', 'filename' => '../zlib.zip']], 'nested path' => [['type' => 'php', 'filename' => 'vs18/x64/zlib.zip']], 'non zip' => [['type' => 'pecl', 'filename' => 'packages.txt']],

« previous php.webmaster (#34064) next »