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']],