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


Thread (1 message)

  • Shivam Mathur
« previous php.webmaster (#34064) next »