diff --git a/apps/dav/lib/Connector/Sabre/File.php b/apps/dav/lib/Connector/Sabre/File.php index 122bb69eb25f..1dc69ed80a84 100644 --- a/apps/dav/lib/Connector/Sabre/File.php +++ b/apps/dav/lib/Connector/Sabre/File.php @@ -56,9 +56,28 @@ use Sabre\DAV\Exception\ServiceUnavailable; use Sabre\DAV\IFile; use Sabre\DAV\Exception\NotFound; +use OC\AppFramework\Http\Request; class File extends Node implements IFile { + protected $request; + + /** + * Sets up the node, expects a full path name + * + * @param \OC\Files\View $view + * @param \OCP\Files\FileInfo $info + * @param \OCP\Share\IManager $shareManager + */ + public function __construct($view, $info, $shareManager = null, Request $request = null) { + if (isset($request)) { + $this->request = $request; + } else { + $this->request = \OC::$server->getRequest(); + } + parent::__construct($view, $info, $shareManager); + } + /** * Updates the data * @@ -210,9 +229,11 @@ public function put($data) { } // allow sync clients to send the mtime along in a header - $request = \OC::$server->getRequest(); - if (isset($request->server['HTTP_X_OC_MTIME'])) { - if ($this->fileView->touch($this->path, $request->server['HTTP_X_OC_MTIME'])) { + if (isset($this->request->server['HTTP_X_OC_MTIME'])) { + $mtime = $this->sanitizeMtime( + $this->request->server ['HTTP_X_OC_MTIME'] + ); + if ($this->fileView->touch($this->path, $mtime)) { header('X-OC-MTime: accepted'); } } @@ -473,9 +494,11 @@ private function createFileChunked($data) { } // allow sync clients to send the mtime along in a header - $request = \OC::$server->getRequest(); - if (isset($request->server['HTTP_X_OC_MTIME'])) { - if ($targetStorage->touch($targetInternalPath, $request->server['HTTP_X_OC_MTIME'])) { + if (isset($this->request->server['HTTP_X_OC_MTIME'])) { + $mtime = $this->sanitizeMtime( + $this->request->server ['HTTP_X_OC_MTIME'] + ); + if ($targetStorage->touch($targetInternalPath, $mtime)) { header('X-OC-MTime: accepted'); } } @@ -604,6 +627,17 @@ private function convertToSabreException(\Exception $e) { throw new \Sabre\DAV\Exception($e->getMessage(), 0, $e); } + private function sanitizeMtime ($mtimeFromRequest) { + $mtime = (float) $mtimeFromRequest; + if ($mtime >= PHP_INT_MAX) { + $mtime = PHP_INT_MAX; + } elseif ($mtime <= (PHP_INT_MAX*-1)) { + $mtime = (PHP_INT_MAX*-1); + } else { + $mtime = (int) $mtimeFromRequest; + } + return $mtime; + } /** * Set $algo to get a specific checksum, leave null to get all checksums diff --git a/apps/dav/tests/unit/Connector/Sabre/FileTest.php b/apps/dav/tests/unit/Connector/Sabre/FileTest.php index 5d3a5f226345..5954cb66a11d 100644 --- a/apps/dav/tests/unit/Connector/Sabre/FileTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/FileTest.php @@ -62,6 +62,9 @@ class FileTest extends TestCase { */ private $user; + /** @var IConfig | \PHPUnit_Framework_MockObject_MockObject */ + protected $config; + public function setUp() { parent::setUp(); unset($_SERVER['HTTP_OC_CHUNKED']); @@ -75,6 +78,8 @@ public function setUp() { $userManager->createUser($this->user, 'pass'); $this->loginAsUser($this->user); + + $this->config = $this->getMockBuilder('\OCP\IConfig')->getMock(); } public function tearDown() { @@ -299,7 +304,7 @@ function ($path) use ($storage) { * * @return null|string of the PUT operaiton which is usually the etag */ - private function doPut($path, $viewRoot = null) { + private function doPut($path, $viewRoot = null, \OC\AppFramework\Http\Request $request = null) { $view = Filesystem::getView(); if (!is_null($viewRoot)) { $view = new View($viewRoot); @@ -315,7 +320,7 @@ private function doPut($path, $viewRoot = null) { null ); - $file = new File($view, $info); + $file = new File($view, $info, null, $request); // beforeMethod locks $view->lockFile($path, ILockingProvider::LOCK_SHARED); @@ -335,6 +340,105 @@ public function testPutSingleFile() { $this->assertNotEmpty($this->doPut('/foo.txt')); } + /** + * Determine if the underlying storage supports a negative mtime value + * + * @return boolean true if negative mtime is supported + */ + private function supportsNegativeMtime() { + return (getenv("PRIMARY_STORAGE_CONFIG") !== "swift"); + } + + public function legalMtimeProvider() { + return [ + "string" => [ + 'HTTP_X_OC_MTIME' => "string", + 'expected result' => 0 + ], + "castable string (int)" => [ + 'HTTP_X_OC_MTIME' => "34", + 'expected result' => 34 + ], + "castable string (float)" => [ + 'HTTP_X_OC_MTIME' => "34.56", + 'expected result' => 34 + ], + "float" => [ + 'HTTP_X_OC_MTIME' => 34.56, + 'expected result' => 34 + ], + "zero" => [ + 'HTTP_X_OC_MTIME' => 0, + 'expected result' => 0 + ], + "zero string" => [ + 'HTTP_X_OC_MTIME' => "0", + 'expected result' => 0 + ], + "negative zero string" => [ + 'HTTP_X_OC_MTIME' => "-0", + 'expected result' => 0 + ], + "string starting with number following by char" => [ + 'HTTP_X_OC_MTIME' => "2345asdf", + 'expected result' => 2345 + ], + "string castable hex int" => [ + 'HTTP_X_OC_MTIME' => "0x45adf", + 'expected result' => 0 + ], + "string that looks like invalid hex int" => [ + 'HTTP_X_OC_MTIME' => "0x123g", + 'expected result' => 0 + ], + "negative int" => [ + 'HTTP_X_OC_MTIME' => -34, + 'expected result' => ($this->supportsNegativeMtime() ? -34 : 0) + ], + "negative float" => [ + 'HTTP_X_OC_MTIME' => -34.43, + 'expected result' => ($this->supportsNegativeMtime() ? -34 : 0) + ], + ]; + } + + /** + * Test putting a file with string Mtime + * @runInSeparateProcess + * @preserveGlobalState disabled + * @dataProvider legalMtimeProvider + */ + public function testPutSingleFileLegalMtime($requestMtime, $resultMtime) { + $request = new \OC\AppFramework\Http\Request([ + 'server' => [ + 'HTTP_X_OC_MTIME' => $requestMtime, + ] + ], null, $this->config, null); + $file = 'foo.txt'; + $this->doPut($file, null, $request); + $this->assertEquals($resultMtime, $this->getFileInfos($file)['mtime']); + } + + /** + * Test putting a file with string Mtime using chunking + * @runInSeparateProcess + * @preserveGlobalState disabled + * @dataProvider legalMtimeProvider + */ + public function testChunkedPutLegalMtime($requestMtime, $resultMtime) { + $request = new \OC\AppFramework\Http\Request([ + 'server' => [ + 'HTTP_X_OC_MTIME' => $requestMtime, + ] + ], null, $this->config, null); + + $_SERVER['HTTP_OC_CHUNKED'] = true; + $file = 'foo.txt'; + $this->doPut($file.'-chunking-12345-2-0', null, $request); + $this->doPut($file.'-chunking-12345-2-1', null, $request); + $this->assertEquals($resultMtime, $this->getFileInfos($file)['mtime']); + } + /** * Test putting a file using chunking */ @@ -973,6 +1077,25 @@ private function listPartFiles(View $userView = null, $path = '') { return $files; } + /** + * returns an array of file information filesize, mtime, filetype, mimetype + * + * @param string $path + * @param View $userView + * @return array + */ + private function getFileInfos($path = '', View $userView = null) { + if ($userView === null) { + $userView = Filesystem::getView(); + } + return [ + "filesize" => $userView->filesize($path), + "mtime" => $userView->filemtime($path), + "filetype" => $userView->filetype($path), + "mimetype" => $userView->getMimeType($path) + ]; + } + /** * @expectedException \Sabre\DAV\Exception\ServiceUnavailable */