diff --git a/program/lib/Roundcube/rcube_output.php b/program/lib/Roundcube/rcube_output.php index a74a964ea..e66eb8b9e 100644 --- a/program/lib/Roundcube/rcube_output.php +++ b/program/lib/Roundcube/rcube_output.php @@ -256,15 +256,15 @@ abstract class rcube_output // @phpstan-ignore-next-line if (is_string($filename) && $filename !== '' && strlen($filename) <= 1024) { // For non-ascii characters we'll use RFC2231 syntax - if (!preg_match('/[^a-zA-Z0-9_.:,?;@+ -]/', $filename)) { - $disposition .= "; filename=\"{$filename}\""; - } else { + $fallback_filename = preg_replace('/[^a-zA-Z0-9_.(),;@+ -]/', '_', $filename); + $disposition .= "; filename=\"{$fallback_filename}\""; + + if ($fallback_filename != $filename) { $filename = rawurlencode($filename); $charset = $this->charset; if (!empty($params['charset']) && rcube_charset::is_valid($params['charset'])) { $charset = $params['charset']; } - $disposition .= "; filename*={$charset}''{$filename}"; } } diff --git a/tests/Framework/OutputTest.php b/tests/Framework/OutputTest.php index 5c23d67ef..889c073ea 100644 --- a/tests/Framework/OutputTest.php +++ b/tests/Framework/OutputTest.php @@ -26,6 +26,15 @@ class OutputTest extends TestCase $this->assertContains('Content-Type: application/octet-stream', $output->headers); $this->assertContains('Content-Security-Policy: default-src \'none\'; img-src \'self\'', $output->headers); + // Test handling of filename* + $output->reset(); + $output->download_headers('test ? test'); + + $this->assertCount(3, $output->headers); + $this->assertContains('Content-Disposition: attachment; filename="test _ test"; filename*=' . RCUBE_CHARSET . "''" . rawurlencode('test ? test'), $output->headers); + $this->assertContains('Content-Type: application/octet-stream', $output->headers); + $this->assertContains('Content-Security-Policy: default-src \'none\'; img-src \'self\'', $output->headers); + // Invalid content type $output->reset(); $params = ['type' => 'invalid'];