Set the file's mtime from the headers in bulk upload

Signed-off-by: Louis Chemineau <louis@chmn.me>
This commit is contained in:
Louis Chemineau 2022-01-11 10:56:49 +01:00
parent b23934a45e
commit 69b8044b8f
10 changed files with 105 additions and 19 deletions

View File

@ -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()
{

View File

@ -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',

View File

@ -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',

View File

@ -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,
),
),

View File

@ -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(),

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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"

View File

@ -207,6 +207,19 @@ trait WebDav {
Assert::assertEquals($content, (string)$this->response->getBody());
}
/**
* @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
@ -380,6 +393,30 @@ trait WebDav {
return $response;
}
/* 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 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
@ -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";

View File

@ -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