From 8a00638425de4016cd86b5e1b9e0b3ac193eb7e6 Mon Sep 17 00:00:00 2001 From: Lukas Reschke Date: Wed, 14 Dec 2016 17:35:27 +0100 Subject: [PATCH] Don't set Content-Disposition header if one already exists If a Content-Disposition header is already set by another plugin we don't need to set another one as this breaks clients. Fixes https://github.com/nextcloud/server/issues/1992 Signed-off-by: Lukas Reschke --- apps/dav/lib/Connector/Sabre/FilesPlugin.php | 6 +- build/integration/data/bjoern.vcf | 51 ++++++++ .../features/bootstrap/CardDavContext.php | 111 ++++++++++++++++++ build/integration/features/carddav.feature | 30 +++++ 4 files changed, 196 insertions(+), 2 deletions(-) create mode 100644 build/integration/data/bjoern.vcf diff --git a/apps/dav/lib/Connector/Sabre/FilesPlugin.php b/apps/dav/lib/Connector/Sabre/FilesPlugin.php index 59b326243ee..54d91db5c06 100644 --- a/apps/dav/lib/Connector/Sabre/FilesPlugin.php +++ b/apps/dav/lib/Connector/Sabre/FilesPlugin.php @@ -247,8 +247,10 @@ class FilesPlugin extends ServerPlugin { $node = $this->tree->getNodeForPath($request->getPath()); if (!($node instanceof IFile)) return; - // adds a 'Content-Disposition: attachment' header - if ($this->downloadAttachment) { + // adds a 'Content-Disposition: attachment' header in case no disposition + // header has been set before + if ($this->downloadAttachment && + $response->getHeader('Content-Disposition') === null) { $filename = $node->getName(); if ($this->request->isUserAgent( [ diff --git a/build/integration/data/bjoern.vcf b/build/integration/data/bjoern.vcf new file mode 100644 index 00000000000..03decaf997e --- /dev/null +++ b/build/integration/data/bjoern.vcf @@ -0,0 +1,51 @@ +BEGIN:VCARD +VERSION:3.0 +FN:Björn Schießle +ORG:Nextcloud +PHOTO;ENCODING=b;TYPE=jpeg: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 +UID:6454bec7-6f5b-46f2-ba22-15537ab215d9 +CATEGORIES:Engineering +END:VCARD \ No newline at end of file diff --git a/build/integration/features/bootstrap/CardDavContext.php b/build/integration/features/bootstrap/CardDavContext.php index bfdd51bfdb0..4ee882cc2e6 100644 --- a/build/integration/features/bootstrap/CardDavContext.php +++ b/build/integration/features/bootstrap/CardDavContext.php @@ -202,4 +202,115 @@ class CardDavContext implements \Behat\Behat\Context\Context { } } + /** + * @Given :user uploads the contact :fileName to the addressbook :addressbook + */ + public function uploadsTheContactToTheAddressbook($user, $fileName, $addressBook) { + $davUrl = $this->baseUrl . '/remote.php/dav/addressbooks/users/'.$user.'/'.$addressBook . '/' . $fileName; + $password = ($user === 'admin') ? 'admin' : '123456'; + + $request = $this->client->createRequest( + 'PUT', + $davUrl, + [ + 'body' => file_get_contents(__DIR__ . '/../../data/' . $fileName), + 'auth' => [ + $user, + $password, + ], + 'headers' => [ + 'Content-Type' => 'application/xml;charset=UTF-8', + ], + ] + ); + + $this->response = $this->client->send($request); + + if($this->response->getStatusCode() !== 201) { + throw new \Exception( + sprintf( + 'Expected %s got %s', + 201, + $this->response->getStatusCode() + ) + ); + } + } + + /** + * @When Exporting the picture of contact :fileName from addressbook :addressBook as user :user + */ + public function whenExportingThePictureOfContactFromAddressbookAsUser($fileName, $addressBook, $user) { + $davUrl = $this->baseUrl . '/remote.php/dav/addressbooks/users/'.$user.'/'.$addressBook . '/' . $fileName . '?photo=true'; + $password = ($user === 'admin') ? 'admin' : '123456'; + + try { + $request = $this->client->createRequest( + 'GET', + $davUrl, + [ + 'auth' => [ + $user, + $password, + ], + 'headers' => [ + 'Content-Type' => 'application/xml;charset=UTF-8', + ], + ] + ); + $this->response = $this->client->send($request); + } catch (\GuzzleHttp\Exception\ClientException $e) { + $this->response = $e->getResponse(); + } + } + + /** + * @When Downloading the contact :fileName from addressbook :addressBook as user :user + */ + public function whenDownloadingTheContactFromAddressbookAsUser($fileName, $addressBook, $user) { + $davUrl = $this->baseUrl . '/remote.php/dav/addressbooks/users/'.$user.'/'.$addressBook . '/' . $fileName; + $password = ($user === 'admin') ? 'admin' : '123456'; + + try { + $request = $this->client->createRequest( + 'GET', + $davUrl, + [ + 'auth' => [ + $user, + $password, + ], + 'headers' => [ + 'Content-Type' => 'application/xml;charset=UTF-8', + ], + ] + ); + $this->response = $this->client->send($request); + } catch (\GuzzleHttp\Exception\ClientException $e) { + $this->response = $e->getResponse(); + } + } + + /** + * @Then The following HTTP headers should be set + * @param \Behat\Gherkin\Node\TableNode $table + * @throws \Exception + */ + public function theFollowingHttpHeadersShouldBeSet(\Behat\Gherkin\Node\TableNode $table) { + foreach($table->getTable() as $header) { + $headerName = $header[0]; + $expectedHeaderValue = $header[1]; + $returnedHeader = $this->response->getHeader($headerName); + if($returnedHeader !== $expectedHeaderValue) { + throw new \Exception( + sprintf( + "Expected value '%s' for header '%s', got '%s'", + $expectedHeaderValue, + $headerName, + $returnedHeader + ) + ); + } + } + } } diff --git a/build/integration/features/carddav.feature b/build/integration/features/carddav.feature index ee9d877085d..4fbe403c7db 100644 --- a/build/integration/features/carddav.feature +++ b/build/integration/features/carddav.feature @@ -21,3 +21,33 @@ Feature: carddav Scenario: Creating a new addressbook When "admin" creates an addressbook named "MyAddressbook" with statuscode "201" Then "admin" requests addressbook "admin/MyAddressbook" with statuscode "200" + + Scenario: Accessing ones own contact + Given "admin" creates an addressbook named "MyAddressbook" with statuscode "201" + Given "admin" uploads the contact "bjoern.vcf" to the addressbook "MyAddressbook" + When Downloading the contact "bjoern.vcf" from addressbook "MyAddressbook" as user "admin" + Then The following HTTP headers should be set + |Content-Disposition|attachment; filename*=UTF-8''bjoern.vcf; filename="bjoern.vcf"| + |Content-Type|text/vcard; charset=utf-8| + |Content-Security-Policy|default-src 'none';| + |X-Content-Type-Options |nosniff| + |X-Download-Options|noopen| + |X-Frame-Options|Sameorigin| + |X-Permitted-Cross-Domain-Policies|none| + |X-Robots-Tag|none| + |X-XSS-Protection|1; mode=block| + + Scenario: Exporting the picture of ones own contact + Given "admin" creates an addressbook named "MyAddressbook" with statuscode "201" + Given "admin" uploads the contact "bjoern.vcf" to the addressbook "MyAddressbook" + When Exporting the picture of contact "bjoern.vcf" from addressbook "MyAddressbook" as user "admin" + Then The following HTTP headers should be set + |Content-Disposition|attachment| + |Content-Type|image/jpeg| + |Content-Security-Policy|default-src 'none';| + |X-Content-Type-Options |nosniff| + |X-Download-Options|noopen| + |X-Frame-Options|Sameorigin| + |X-Permitted-Cross-Domain-Policies|none| + |X-Robots-Tag|none| + |X-XSS-Protection|1; mode=block|