diff options
-rw-r--r-- | apps/dav/composer/composer/ClassLoader.php | 2 | ||||
-rw-r--r-- | apps/dav/composer/composer/autoload_classmap.php | 1 | ||||
-rw-r--r-- | apps/dav/composer/composer/autoload_static.php | 1 | ||||
-rw-r--r-- | apps/dav/composer/composer/installed.php | 4 | ||||
-rw-r--r-- | apps/dav/lib/BulkUpload/BulkUploadPlugin.php | 12 | ||||
-rw-r--r-- | apps/dav/lib/Connector/Sabre/MtimeSanitizer.php | 42 | ||||
-rw-r--r-- | apps/dav/lib/Connector/Sabre/Node.php | 15 | ||||
-rwxr-xr-x | apps/dav/tests/benchmarks/bulk_upload.sh | 2 | ||||
-rw-r--r-- | build/integration/features/bootstrap/WebDav.php | 42 | ||||
-rw-r--r-- | build/integration/features/webdav-related.feature | 3 |
10 files changed, 105 insertions, 19 deletions
diff --git a/apps/dav/composer/composer/ClassLoader.php b/apps/dav/composer/composer/ClassLoader.php index 0cd6055d1b7..afef3fa2ad8 100644 --- a/apps/dav/composer/composer/ClassLoader.php +++ b/apps/dav/composer/composer/ClassLoader.php @@ -149,7 +149,7 @@ class ClassLoader /** * @return string[] Array of classname => path - * @psalm-var array<string, string> + * @psalm-return array<string, string> */ public function getClassMap() { diff --git a/apps/dav/composer/composer/autoload_classmap.php b/apps/dav/composer/composer/autoload_classmap.php index fa83c05578d..184949ff023 100644 --- a/apps/dav/composer/composer/autoload_classmap.php +++ b/apps/dav/composer/composer/autoload_classmap.php @@ -163,6 +163,7 @@ return array( 'OCA\\DAV\\Connector\\Sabre\\FilesReportPlugin' => $baseDir . '/../lib/Connector/Sabre/FilesReportPlugin.php', 'OCA\\DAV\\Connector\\Sabre\\LockPlugin' => $baseDir . '/../lib/Connector/Sabre/LockPlugin.php', 'OCA\\DAV\\Connector\\Sabre\\MaintenancePlugin' => $baseDir . '/../lib/Connector/Sabre/MaintenancePlugin.php', + 'OCA\\DAV\\Connector\\Sabre\\MtimeSanitizer' => $baseDir . '/../lib/Connector/Sabre/MtimeSanitizer.php', 'OCA\\DAV\\Connector\\Sabre\\Node' => $baseDir . '/../lib/Connector/Sabre/Node.php', 'OCA\\DAV\\Connector\\Sabre\\ObjectTree' => $baseDir . '/../lib/Connector/Sabre/ObjectTree.php', 'OCA\\DAV\\Connector\\Sabre\\Principal' => $baseDir . '/../lib/Connector/Sabre/Principal.php', diff --git a/apps/dav/composer/composer/autoload_static.php b/apps/dav/composer/composer/autoload_static.php index 8f8ca0da10b..2e5fcd56aee 100644 --- a/apps/dav/composer/composer/autoload_static.php +++ b/apps/dav/composer/composer/autoload_static.php @@ -178,6 +178,7 @@ class ComposerStaticInitDAV 'OCA\\DAV\\Connector\\Sabre\\FilesReportPlugin' => __DIR__ . '/..' . '/../lib/Connector/Sabre/FilesReportPlugin.php', 'OCA\\DAV\\Connector\\Sabre\\LockPlugin' => __DIR__ . '/..' . '/../lib/Connector/Sabre/LockPlugin.php', 'OCA\\DAV\\Connector\\Sabre\\MaintenancePlugin' => __DIR__ . '/..' . '/../lib/Connector/Sabre/MaintenancePlugin.php', + 'OCA\\DAV\\Connector\\Sabre\\MtimeSanitizer' => __DIR__ . '/..' . '/../lib/Connector/Sabre/MtimeSanitizer.php', 'OCA\\DAV\\Connector\\Sabre\\Node' => __DIR__ . '/..' . '/../lib/Connector/Sabre/Node.php', 'OCA\\DAV\\Connector\\Sabre\\ObjectTree' => __DIR__ . '/..' . '/../lib/Connector/Sabre/ObjectTree.php', 'OCA\\DAV\\Connector\\Sabre\\Principal' => __DIR__ . '/..' . '/../lib/Connector/Sabre/Principal.php', diff --git a/apps/dav/composer/composer/installed.php b/apps/dav/composer/composer/installed.php index 5440719fa40..baf72c4fb34 100644 --- a/apps/dav/composer/composer/installed.php +++ b/apps/dav/composer/composer/installed.php @@ -5,7 +5,7 @@ 'type' => 'library', 'install_path' => __DIR__ . '/../', 'aliases' => array(), - 'reference' => 'c6429e6cd19c57582364338362e543580821cf99', + 'reference' => 'e2c675724fc4ea50f1275bf0027b96f277c32578', 'name' => '__root__', 'dev' => false, ), @@ -16,7 +16,7 @@ 'type' => 'library', 'install_path' => __DIR__ . '/../', 'aliases' => array(), - 'reference' => 'c6429e6cd19c57582364338362e543580821cf99', + 'reference' => 'e2c675724fc4ea50f1275bf0027b96f277c32578', 'dev_requirement' => false, ), ), diff --git a/apps/dav/lib/BulkUpload/BulkUploadPlugin.php b/apps/dav/lib/BulkUpload/BulkUploadPlugin.php index 0766ae37a17..bb6baf48b56 100644 --- a/apps/dav/lib/BulkUpload/BulkUploadPlugin.php +++ b/apps/dav/lib/BulkUpload/BulkUploadPlugin.php @@ -29,6 +29,7 @@ use Sabre\HTTP\RequestInterface; use Sabre\HTTP\ResponseInterface; use OCP\Files\Folder; use OCP\AppFramework\Http; +use OCA\DAV\Connector\Sabre\MtimeSanitizer; class BulkUploadPlugin extends ServerPlugin { @@ -78,7 +79,18 @@ class BulkUploadPlugin extends ServerPlugin { } try { + // TODO: Remove 'x-file-mtime' when the desktop client no longer use it. + if (isset($headers['x-file-mtime'])) { + $mtime = MtimeSanitizer::sanitizeMtime($headers['x-file-mtime']); + } elseif (isset($headers['x-oc-mtime'])) { + $mtime = MtimeSanitizer::sanitizeMtime($headers['x-oc-mtime']); + } else { + $mtime = null; + } + $node = $this->userFolder->newFile($headers['x-file-path'], $content); + $node->touch($mtime); + $writtenFiles[$headers['x-file-path']] = [ "error" => false, "etag" => $node->getETag(), diff --git a/apps/dav/lib/Connector/Sabre/MtimeSanitizer.php b/apps/dav/lib/Connector/Sabre/MtimeSanitizer.php new file mode 100644 index 00000000000..6700b1eb81b --- /dev/null +++ b/apps/dav/lib/Connector/Sabre/MtimeSanitizer.php @@ -0,0 +1,42 @@ +<?php +/** + * @copyright Copyright (c) 2021, Louis Chemineau <louis@chmn.me> + * + * @author Louis Chemineau <louis@chmn.me> + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\DAV\Connector\Sabre; + +class MtimeSanitizer { + public static function sanitizeMtime(string $mtimeFromRequest): int { + // In PHP 5.X "is_numeric" returns true for strings in hexadecimal + // notation. This is no longer the case in PHP 7.X, so this check + // ensures that strings with hexadecimal notations fail too in PHP 5.X. + $isHexadecimal = preg_match('/^\s*0[xX]/', $mtimeFromRequest); + if ($isHexadecimal || !is_numeric($mtimeFromRequest)) { + throw new \InvalidArgumentException('X-OC-MTime header must be an integer (unix timestamp).'); + } + + // Prevent writing invalid mtime (timezone-proof) + if ((int)$mtimeFromRequest <= 24 * 60 * 60) { + throw new \InvalidArgumentException('X-OC-MTime header must be a valid positive integer'); + } + + return (int)$mtimeFromRequest; + } +} diff --git a/apps/dav/lib/Connector/Sabre/Node.php b/apps/dav/lib/Connector/Sabre/Node.php index 0fc8a441277..79b4db0e327 100644 --- a/apps/dav/lib/Connector/Sabre/Node.php +++ b/apps/dav/lib/Connector/Sabre/Node.php @@ -404,19 +404,6 @@ abstract class Node implements \Sabre\DAV\INode { } protected function sanitizeMtime($mtimeFromRequest) { - // In PHP 5.X "is_numeric" returns true for strings in hexadecimal - // notation. This is no longer the case in PHP 7.X, so this check - // ensures that strings with hexadecimal notations fail too in PHP 5.X. - $isHexadecimal = is_string($mtimeFromRequest) && preg_match('/^\s*0[xX]/', $mtimeFromRequest); - if ($isHexadecimal || !is_numeric($mtimeFromRequest)) { - throw new \InvalidArgumentException('X-OC-MTime header must be an integer (unix timestamp).'); - } - - // Prevent writing invalid mtime (timezone-proof) - if ((int)$mtimeFromRequest <= 24 * 60 * 60) { - throw new \InvalidArgumentException('X-OC-MTime header must be a valid positive integer'); - } - - return (int)$mtimeFromRequest; + return MtimeSanitizer::sanitizeMtime($mtimeFromRequest); } } diff --git a/apps/dav/tests/benchmarks/bulk_upload.sh b/apps/dav/tests/benchmarks/bulk_upload.sh index 862ddfe461f..e2099188654 100755 --- a/apps/dav/tests/benchmarks/bulk_upload.sh +++ b/apps/dav/tests/benchmarks/bulk_upload.sh @@ -37,7 +37,7 @@ do echo -en "--$BOUNDARY\r\n" # echo -en "Content-ID: $file_name\r\n" echo -en "X-File-Path: $file_remote_path\r\n" - echo -en "X-File-Mtime: $file_mtime\r\n" + echo -en "X-OC-Mtime: $file_mtime\r\n" # echo -en "X-File-Id: $file_id\r\n" echo -en "X-File-Md5: $file_hash\r\n" echo -en "Content-Length: $file_size\r\n" diff --git a/build/integration/features/bootstrap/WebDav.php b/build/integration/features/bootstrap/WebDav.php index 2db51d8b22f..09e2680e5ed 100644 --- a/build/integration/features/bootstrap/WebDav.php +++ b/build/integration/features/bootstrap/WebDav.php @@ -208,6 +208,19 @@ trait WebDav { } /** + * @Then /^File "([^"]*)" should have prop "([^"]*):([^"]*)" equal to "([^"]*)"$/ + * @param string $file + * @param string $prefix + * @param string $prop + * @param string $value + */ + public function checkPropForFile($file, $prefix, $prop, $value) { + $elementList = $this->propfindFile($this->currentUser, $file, "<$prefix:$prop/>"); + $property = $elementList['/'.$this->getDavFilesPath($this->currentUser).$file][200]["{DAV:}$prop"]; + Assert::assertEquals($property, $value); + } + + /** * @Then /^Downloaded content when downloading file "([^"]*)" with range "([^"]*)" should be "([^"]*)"$/ * @param string $fileSource * @param string $range @@ -386,6 +399,30 @@ trait WebDav { * @param string $properties properties which needs to be included in the report * @param string $filterRules filter-rules to choose what needs to appear in the report */ + public function propfindFile($user, $path, $properties = '') { + $client = $this->getSabreClient($user); + + $body = '<?xml version="1.0" encoding="utf-8" ?> + <d:propfind xmlns:d="DAV:" + xmlns:oc="http://owncloud.org/ns" + xmlns:nc="http://nextcloud.org/ns" + xmlns:ocs="http://open-collaboration-services.org/ns"> + <d:prop> + ' . $properties . ' + </d:prop> + </d:propfind>'; + + $response = $client->request('PROPFIND', $this->makeSabrePath($user, $path), $body); + $parsedResponse = $client->parseMultistatus($response['body']); + return $parsedResponse; + } + + /* Returns the elements of a report command + * @param string $user + * @param string $path + * @param string $properties properties which needs to be included in the report + * @param string $filterRules filter-rules to choose what needs to appear in the report + */ public function reportFolder($user, $path, $properties, $filterRules) { $client = $this->getSabreClient($user); @@ -561,25 +598,28 @@ trait WebDav { * @param string $name3 * @param string $content3 */ - public function userUploadsChunkedFiles($user, $name1, $content1, $name2, $content2, $name3, $content3) { + public function userUploadsBulkedFiles($user, $name1, $content1, $name2, $content2, $name3, $content3) { $boundary = "boundary_azertyuiop"; $body = ""; $body .= '--'.$boundary."\r\n"; $body .= "X-File-Path: ".$name1."\r\n"; $body .= "X-File-MD5: f6a6263167c92de8644ac998b3c4e4d1\r\n"; + $body .= "X-OC-Mtime: 1111111111\r\n"; $body .= "Content-Length: ".strlen($content1)."\r\n"; $body .= "\r\n"; $body .= $content1."\r\n"; $body .= '--'.$boundary."\r\n"; $body .= "X-File-Path: ".$name2."\r\n"; $body .= "X-File-MD5: 87c7d4068be07d390a1fffd21bf1e944\r\n"; + $body .= "X-OC-Mtime: 2222222222\r\n"; $body .= "Content-Length: ".strlen($content2)."\r\n"; $body .= "\r\n"; $body .= $content2."\r\n"; $body .= '--'.$boundary."\r\n"; $body .= "X-File-Path: ".$name3."\r\n"; $body .= "X-File-MD5: e86a1cf0678099986a901c79086f5617\r\n"; + $body .= "X-File-Mtime: 3333333333\r\n"; $body .= "Content-Length: ".strlen($content3)."\r\n"; $body .= "\r\n"; $body .= $content3."\r\n"; diff --git a/build/integration/features/webdav-related.feature b/build/integration/features/webdav-related.feature index 78ec6a93c50..4470e317cdf 100644 --- a/build/integration/features/webdav-related.feature +++ b/build/integration/features/webdav-related.feature @@ -615,10 +615,13 @@ Feature: webdav-related When As an "user0" Then Downloading file "/A.txt" And Downloaded content should be "AAAAA" + And File "/A.txt" should have prop "d:getlastmodified" equal to "Fri, 18 Mar 2005 01:58:31 GMT" And Downloading file "/B.txt" And Downloaded content should be "BBBBB" + And File "/B.txt" should have prop "d:getlastmodified" equal to "Sat, 02 Jun 2040 03:57:02 GMT" And Downloading file "/C.txt" And Downloaded content should be "CCCCC" + And File "/C.txt" should have prop "d:getlastmodified" equal to "Sun, 18 Aug 2075 05:55:33 GMT" Scenario: Creating a folder with invalid characters Given using new dav path |