DAV now returns file name with Content-Disposition header

Fixes issue where Chrome would append ".txt" to XML files when
downloaded in the web UI
This commit is contained in:
Vincent Petry 2016-06-09 11:29:20 +02:00
parent 4c26abe228
commit 1399e87d57
No known key found for this signature in database
GPG Key ID: AF8F9EFC56562186
6 changed files with 93 additions and 5 deletions

View File

@ -42,6 +42,7 @@ use \Sabre\HTTP\RequestInterface;
use \Sabre\HTTP\ResponseInterface; use \Sabre\HTTP\ResponseInterface;
use OCP\Files\StorageNotAvailableException; use OCP\Files\StorageNotAvailableException;
use OCP\IConfig; use OCP\IConfig;
use OCP\IRequest;
class FilesPlugin extends ServerPlugin { class FilesPlugin extends ServerPlugin {
@ -95,20 +96,29 @@ class FilesPlugin extends ServerPlugin {
*/ */
private $config; private $config;
/**
* @var IRequest
*/
private $request;
/** /**
* @param Tree $tree * @param Tree $tree
* @param View $view * @param View $view
* @param IConfig $config
* @param IRequest $request
* @param bool $isPublic * @param bool $isPublic
* @param bool $downloadAttachment * @param bool $downloadAttachment
*/ */
public function __construct(Tree $tree, public function __construct(Tree $tree,
View $view, View $view,
IConfig $config, IConfig $config,
IRequest $request,
$isPublic = false, $isPublic = false,
$downloadAttachment = true) { $downloadAttachment = true) {
$this->tree = $tree; $this->tree = $tree;
$this->fileView = $view; $this->fileView = $view;
$this->config = $config; $this->config = $config;
$this->request = $request;
$this->isPublic = $isPublic; $this->isPublic = $isPublic;
$this->downloadAttachment = $downloadAttachment; $this->downloadAttachment = $downloadAttachment;
} }
@ -225,7 +235,18 @@ class FilesPlugin extends ServerPlugin {
// adds a 'Content-Disposition: attachment' header // adds a 'Content-Disposition: attachment' header
if ($this->downloadAttachment) { if ($this->downloadAttachment) {
$response->addHeader('Content-Disposition', 'attachment'); $filename = $node->getName();
if ($this->request->isUserAgent(
[
\OC\AppFramework\Http\Request::USER_AGENT_IE,
\OC\AppFramework\Http\Request::USER_AGENT_ANDROID_MOBILE_CHROME,
\OC\AppFramework\Http\Request::USER_AGENT_FREEBOX,
])) {
$response->addHeader('Content-Disposition', 'attachment; filename="' . rawurlencode($filename) . '"');
} else {
$response->addHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\'' . rawurlencode($filename)
. '; filename="' . rawurlencode($filename) . '"');
}
} }
if ($node instanceof \OCA\DAV\Connector\Sabre\File) { if ($node instanceof \OCA\DAV\Connector\Sabre\File) {

View File

@ -144,6 +144,7 @@ class ServerFactory {
$objectTree, $objectTree,
$view, $view,
$this->config, $this->config,
$this->request,
false, false,
!$this->config->getSystemValue('debug', false) !$this->config->getSystemValue('debug', false)
) )

View File

@ -141,6 +141,7 @@ class Server {
$this->server->tree, $this->server->tree,
$view, $view,
\OC::$server->getConfig(), \OC::$server->getConfig(),
$this->request,
false, false,
!\OC::$server->getConfig()->getSystemValue('debug', false) !\OC::$server->getConfig()->getSystemValue('debug', false)
) )

View File

@ -73,6 +73,11 @@ class FilesPluginTest extends TestCase {
*/ */
private $config; private $config;
/**
* @var \OCP\IRequest | \PHPUnit_Framework_MockObject_MockObject
*/
private $request;
public function setUp() { public function setUp() {
parent::setUp(); parent::setUp();
$this->server = $this->getMockBuilder('\Sabre\DAV\Server') $this->server = $this->getMockBuilder('\Sabre\DAV\Server')
@ -88,11 +93,13 @@ class FilesPluginTest extends TestCase {
$this->config->expects($this->any())->method('getSystemValue') $this->config->expects($this->any())->method('getSystemValue')
->with($this->equalTo('data-fingerprint'), $this->equalTo('')) ->with($this->equalTo('data-fingerprint'), $this->equalTo(''))
->willReturn('my_fingerprint'); ->willReturn('my_fingerprint');
$this->request = $this->getMock('\OCP\IRequest');
$this->plugin = new FilesPlugin( $this->plugin = new FilesPlugin(
$this->tree, $this->tree,
$this->view, $this->view,
$this->config $this->config,
$this->request
); );
$this->plugin->initialize($this->server); $this->plugin->initialize($this->server);
} }
@ -268,6 +275,7 @@ class FilesPluginTest extends TestCase {
$this->tree, $this->tree,
$this->view, $this->view,
$this->config, $this->config,
$this->getMock('\OCP\IRequest'),
true); true);
$this->plugin->initialize($this->server); $this->plugin->initialize($this->server);
@ -484,4 +492,60 @@ class FilesPluginTest extends TestCase {
$this->plugin->checkMove('FolderA/test.txt', 'test.txt'); $this->plugin->checkMove('FolderA/test.txt', 'test.txt');
} }
public function downloadHeadersProvider() {
return [
[
false,
'attachment; filename*=UTF-8\'\'somefile.xml; filename="somefile.xml"'
],
[
true,
'attachment; filename="somefile.xml"'
],
];
}
/**
* @dataProvider downloadHeadersProvider
*/
public function testDownloadHeaders($isClumsyAgent, $contentDispositionHeader) {
$request = $this->getMockBuilder('Sabre\HTTP\RequestInterface')
->disableOriginalConstructor()
->getMock();
$response = $this->getMockBuilder('Sabre\HTTP\ResponseInterface')
->disableOriginalConstructor()
->getMock();
$request
->expects($this->once())
->method('getPath')
->will($this->returnValue('test/somefile.xml'));
$node = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\File')
->disableOriginalConstructor()
->getMock();
$node
->expects($this->once())
->method('getName')
->will($this->returnValue('somefile.xml'));
$this->tree
->expects($this->once())
->method('getNodeForPath')
->with('test/somefile.xml')
->will($this->returnValue($node));
$this->request
->expects($this->once())
->method('isUserAgent')
->will($this->returnValue($isClumsyAgent));
$response
->expects($this->once())
->method('addHeader')
->with('Content-Disposition', $contentDispositionHeader);
$this->plugin->httpGet($request, $response);
}
} }

View File

@ -343,7 +343,8 @@ class FilesReportPluginTest extends \Test\TestCase {
new \OCA\DAV\Connector\Sabre\FilesPlugin( new \OCA\DAV\Connector\Sabre\FilesPlugin(
$this->tree, $this->tree,
$this->view, $this->view,
$config $config,
$this->getMock('\OCP\IRequest')
) )
); );
$this->plugin->initialize($this->server); $this->plugin->initialize($this->server);

View File

@ -82,7 +82,7 @@ Feature: webdav-related
And As an "admin" And As an "admin"
When Downloading file "/welcome.txt" When Downloading file "/welcome.txt"
Then The following headers should be set Then The following headers should be set
|Content-Disposition|attachment| |Content-Disposition|attachment; filename*=UTF-8''welcome.txt; filename="welcome.txt"|
|Content-Security-Policy|default-src 'none';| |Content-Security-Policy|default-src 'none';|
|X-Content-Type-Options |nosniff| |X-Content-Type-Options |nosniff|
|X-Download-Options|noopen| |X-Download-Options|noopen|
@ -97,7 +97,7 @@ Feature: webdav-related
And As an "admin" And As an "admin"
When Downloading file "/welcome.txt" When Downloading file "/welcome.txt"
Then The following headers should be set Then The following headers should be set
|Content-Disposition|attachment| |Content-Disposition|attachment; filename*=UTF-8''welcome.txt; filename="welcome.txt"|
|Content-Security-Policy|default-src 'none';| |Content-Security-Policy|default-src 'none';|
|X-Content-Type-Options |nosniff| |X-Content-Type-Options |nosniff|
|X-Download-Options|noopen| |X-Download-Options|noopen|