Skip to content

Commit 0bbb195

Browse files
authored
Merge pull request #27354 from nextcloud/escape-download-response
Escape filename in Content-Disposition
2 parents 949102c + 377514a commit 0bbb195

File tree

2 files changed

+27
-17
lines changed

2 files changed

+27
-17
lines changed

lib/public/AppFramework/Http/DownloadResponse.php

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,16 @@
3030
* @since 7.0.0
3131
*/
3232
class DownloadResponse extends Response {
33-
private $filename;
34-
private $contentType;
35-
3633
/**
3734
* Creates a response that prompts the user to download the file
3835
* @param string $filename the name that the downloaded file should have
3936
* @param string $contentType the mimetype that the downloaded file should have
4037
* @since 7.0.0
4138
*/
42-
public function __construct($filename, $contentType) {
39+
public function __construct(string $filename, string $contentType) {
4340
parent::__construct();
4441

45-
$this->filename = $filename;
46-
$this->contentType = $contentType;
42+
$filename = strtr($filename, ['"' => '\\"', '\\' => '\\\\']);
4743

4844
$this->addHeader('Content-Disposition', 'attachment; filename="' . $filename . '"');
4945
$this->addHeader('Content-Type', $contentType);

tests/lib/AppFramework/Http/DownloadResponseTest.php

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,22 +30,36 @@ class ChildDownloadResponse extends DownloadResponse {
3030

3131

3232
class DownloadResponseTest extends \Test\TestCase {
33-
34-
/**
35-
* @var ChildDownloadResponse
36-
*/
37-
protected $response;
38-
3933
protected function setUp(): void {
4034
parent::setUp();
41-
$this->response = new ChildDownloadResponse('file', 'content');
4235
}
4336

44-
4537
public function testHeaders() {
46-
$headers = $this->response->getHeaders();
38+
$response = new ChildDownloadResponse('file', 'content');
39+
$headers = $response->getHeaders();
40+
41+
$this->assertEquals('attachment; filename="file"', $headers['Content-Disposition']);
42+
$this->assertEquals('content', $headers['Content-Type']);
43+
}
44+
45+
/**
46+
* @dataProvider filenameEncodingProvider
47+
*/
48+
public function testFilenameEncoding(string $input, string $expected) {
49+
$response = new ChildDownloadResponse($input, 'content');
50+
$headers = $response->getHeaders();
51+
52+
$this->assertEquals('attachment; filename="'.$expected.'"', $headers['Content-Disposition']);
53+
}
4754

48-
$this->assertStringContainsString('attachment; filename="file"', $headers['Content-Disposition']);
49-
$this->assertStringContainsString('content', $headers['Content-Type']);
55+
public function filenameEncodingProvider() : array {
56+
return [
57+
['TestName.txt', 'TestName.txt'],
58+
['A "Quoted" Filename.txt', 'A \\"Quoted\\" Filename.txt'],
59+
['A "Quoted" Filename.txt', 'A \\"Quoted\\" Filename.txt'],
60+
['A "Quoted" Filename With A Backslash \\.txt', 'A \\"Quoted\\" Filename With A Backslash \\\\.txt'],
61+
['A "Very" Weird Filename \ / & <> " >\'""""\.text', 'A \\"Very\\" Weird Filename \\\\ / & <> \\" >\'\\"\\"\\"\\"\\\\.text'],
62+
['\\\\\\\\\\\\', '\\\\\\\\\\\\\\\\\\\\\\\\'],
63+
];
5064
}
5165
}

0 commit comments

Comments
 (0)