Skip to content

Commit b7fb465

Browse files
authored
fix: Sanitize filename on download (#9960)
* fix: Sanitize filename on download * fix: filename encoding in the Content-Disposition header This improves the handling of the filename* parameter in the Content-Disposition header. Now, the filename* parameter is only used when it differs from the fallback filename * tests: Add test for the filename* parameter in Content-Disposition
1 parent a032210 commit b7fb465

File tree

2 files changed

+13
-4
lines changed

2 files changed

+13
-4
lines changed

program/lib/Roundcube/rcube_output.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -256,15 +256,15 @@ public function download_headers($filename, $params = [])
256256
// @phpstan-ignore-next-line
257257
if (is_string($filename) && $filename !== '' && strlen($filename) <= 1024) {
258258
// For non-ascii characters we'll use RFC2231 syntax
259-
if (!preg_match('/[^a-zA-Z0-9_.:,?;@+ -]/', $filename)) {
260-
$disposition .= "; filename=\"{$filename}\"";
261-
} else {
259+
$fallback_filename = preg_replace('/[^a-zA-Z0-9_.(),;@+ -]/', '_', $filename);
260+
$disposition .= "; filename=\"{$fallback_filename}\"";
261+
262+
if ($fallback_filename != $filename) {
262263
$filename = rawurlencode($filename);
263264
$charset = $this->charset;
264265
if (!empty($params['charset']) && rcube_charset::is_valid($params['charset'])) {
265266
$charset = $params['charset'];
266267
}
267-
268268
$disposition .= "; filename*={$charset}''{$filename}";
269269
}
270270
}

tests/Framework/OutputTest.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,15 @@ public function test_download_headers()
2626
$this->assertContains('Content-Type: application/octet-stream', $output->headers);
2727
$this->assertContains('Content-Security-Policy: default-src \'none\'; img-src \'self\'', $output->headers);
2828

29+
// Test handling of filename*
30+
$output->reset();
31+
$output->download_headers('test ? test');
32+
33+
$this->assertCount(3, $output->headers);
34+
$this->assertContains('Content-Disposition: attachment; filename="test _ test"; filename*=' . RCUBE_CHARSET . "''" . rawurlencode('test ? test'), $output->headers);
35+
$this->assertContains('Content-Type: application/octet-stream', $output->headers);
36+
$this->assertContains('Content-Security-Policy: default-src \'none\'; img-src \'self\'', $output->headers);
37+
2938
// Invalid content type
3039
$output->reset();
3140
$params = ['type' => 'invalid'];

0 commit comments

Comments
 (0)