From c955381d5691be00053e52a0e43df698478d979c Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Tue, 17 Dec 2013 16:18:05 +0100 Subject: fall back to getLocalFile if storage doesn't support fseek --- apps/files_encryption/lib/util.php | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index d4aa4d31649..577b656077f 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -472,8 +472,20 @@ class Util { // we only need 24 byte from the last chunk $data = ''; $handle = $this->view->fopen($path, 'r'); - if (is_resource($handle) && !fseek($handle, -24, SEEK_END)) { - $data = fgets($handle); + if (is_resource($handle)) { + if (fseek($handle, -24, SEEK_END) === 0) { + $data = fgets($handle); + } else { + // if fseek failed on the storage we create a local copy from the file + // and read this one + fclose($handle); + $localFile = $this->view->getLocalFile($path); + $handle = fopen($localFile, 'r'); + if (is_resource($handle) && fseek($handle, -24, SEEK_END) === 0) { + $data = fgets($handle); + } + } + fclose($handle); } // re-enable proxy -- cgit v1.2.3 From d9668977cd23a3989a412ba3412240bf3cf38c94 Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Wed, 18 Dec 2013 15:40:43 +0100 Subject: implement ftell stream wrapper and fix return value from fseek stream wrapper --- apps/files_encryption/lib/stream.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php index 7a37d2200a4..c3cbdd54f56 100644 --- a/apps/files_encryption/lib/stream.php +++ b/apps/files_encryption/lib/stream.php @@ -163,15 +163,26 @@ class Stream { } + /** + * @brief Returns the current position of the file pointer + * @return int position of the file pointer + */ + public function stream_tell() { + return ftell($this->handle); + } + /** * @param $offset * @param int $whence + * @return bool true if fseek was successful, otherwise false */ public function stream_seek($offset, $whence = SEEK_SET) { $this->flush(); - fseek($this->handle, $offset, $whence); + // this wrapper needs to return "true" for success. + // the fseek call itself returns 0 on succeess + return !fseek($this->handle, $offset, $whence); } -- cgit v1.2.3 From 4f8ae789ae0248401f292c2c2889f3ba566ef5f5 Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Tue, 17 Dec 2013 18:13:46 +0100 Subject: extend the encryption stream wrapper to handle local files and add a fall back for file size calculation if the storage doesn't support fseek --- apps/files_encryption/lib/helper.php | 24 ++++++++++++++++++++++++ apps/files_encryption/lib/stream.php | 25 +++++++++++++++++++------ apps/files_encryption/lib/util.php | 19 +++++++++++++++++-- 3 files changed, 60 insertions(+), 8 deletions(-) diff --git a/apps/files_encryption/lib/helper.php b/apps/files_encryption/lib/helper.php index 17bcac5c585..f893cf16e5a 100755 --- a/apps/files_encryption/lib/helper.php +++ b/apps/files_encryption/lib/helper.php @@ -29,6 +29,8 @@ namespace OCA\Encryption; */ class Helper { + private static $tmpFileMapping; // Map tmp files to files in data/user/files + /** * @brief register share related hooks * @@ -423,5 +425,27 @@ class Helper { public static function escapeGlobPattern($path) { return preg_replace('/(\*|\?|\[)/', '[$1]', $path); } + + /** + * @brief remember from which file the tmp file (getLocalFile() call) was created + * @param string $tmpFile path of tmp file + * @param string $originalFile path of the original file relative to data/ + */ + public static function addTmpFileToMapper($tmpFile, $originalFile) { + self::$tmpFileMapping[$tmpFile] = $originalFile; + } + + /** + * @brief get the path of the original file + * @param string $tmpFile path of the tmp file + * @return mixed path of the original file or false + */ + public static function getPathFromTmpFile($tmpFile) { + if (isset(self::$tmpFileMapping[$tmpFile])) { + return self::$tmpFileMapping[$tmpFile]; + } + + return false; + } } diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php index c3cbdd54f56..b3bf34ddb82 100644 --- a/apps/files_encryption/lib/stream.php +++ b/apps/files_encryption/lib/stream.php @@ -64,6 +64,9 @@ class Stream { private $publicKey; private $encKeyfile; private $newFile; // helper var, we only need to write the keyfile for new files + private $isLocalTmpFile = false; // do we operate on a local tmp file + private $localTmpFile; // path of local tmp file + /** * @var \OC\Files\View */ @@ -91,13 +94,18 @@ class Stream { $this->rootView = new \OC_FilesystemView('/'); } - $this->session = new \OCA\Encryption\Session($this->rootView); $this->privateKey = $this->session->getPrivateKey(); - // rawPath is relative to the data directory - $this->rawPath = \OC\Files\Filesystem::normalizePath(str_replace('crypt://', '', $path)); + $normalizedPath = \OC\Files\Filesystem::normalizePath(str_replace('crypt://', '', $path)); + if ($originalFile = Helper::getPathFromTmpFile($normalizedPath)) { + $this->rawPath = $originalFile; + $this->isLocalTmpFile = true; + $this->localTmpFile = $normalizedPath; + } else { + $this->rawPath = $normalizedPath; + } $this->userId = Helper::getUser($this->rawPath); @@ -141,10 +149,14 @@ class Stream { \OCA\Encryption\Helper::redirectToErrorPage($this->session); } - $this->size = $this->rootView->filesize($this->rawPath, $mode); + $this->size = $this->rootView->filesize($this->rawPath); } - $this->handle = $this->rootView->fopen($this->rawPath, $mode); + if ($this->isLocalTmpFile) { + $this->handle = fopen($this->localTmpFile, $mode); + } else { + $this->handle = $this->rootView->fopen($this->rawPath, $mode); + } \OC_FileProxy::$enabled = $proxyStatus; @@ -488,7 +500,7 @@ class Stream { if ($this->privateKey === false) { // cleanup - if ($this->meta['mode'] !== 'r' && $this->meta['mode'] !== 'rb') { + if ($this->meta['mode'] !== 'r' && $this->meta['mode'] !== 'rb' && !$this->isLocalTmpFile) { // Disable encryption proxy to prevent recursive calls $proxyStatus = \OC_FileProxy::$enabled; @@ -509,6 +521,7 @@ class Stream { if ( $this->meta['mode'] !== 'r' && $this->meta['mode'] !== 'rb' && + $this->isLocalTmpFile === false && $this->size > 0 && $this->unencryptedSize > 0 ) { diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index 577b656077f..70398183f8b 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -473,7 +473,9 @@ class Util { $data = ''; $handle = $this->view->fopen($path, 'r'); if (is_resource($handle)) { - if (fseek($handle, -24, SEEK_END) === 0) { + // suppress fseek warining, we handle the case that fseek doesn't + // work in the else branch + if (@fseek($handle, -24, SEEK_END) === 0) { $data = fgets($handle); } else { // if fseek failed on the storage we create a local copy from the file @@ -537,7 +539,20 @@ class Util { $lastChunckPos = ($lastChunkNr * 8192); // seek to end - fseek($stream, $lastChunckPos); + if (@fseek($stream, $lastChunckPos) === -1) { + // storage doesn't support fseek, we need a local copy + fclose($stream); + $localFile = $this->view->getLocalFile($path); + Helper::addTmpFileToMapper($localFile, $path); + $stream = fopen('crypt://' . $localFile, "r"); + if (fseek($stream, $lastChunckPos) === -1) { + // if fseek also fails on the local storage, than + // there is nothing we can do + fclose($stream); + \OCP\Util::writeLog('Encryption library', 'couldn\'t determine size of "' . $path, \OCP\Util::ERROR); + return $result; + } + } // get the content of the last chunk $lastChunkContent = fread($stream, $lastChunkSize); -- cgit v1.2.3 From f9ec3a71242a5a53c440094d42a75c9fa6f04223 Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Wed, 18 Dec 2013 16:39:34 +0100 Subject: test for isEncryptedPath() --- apps/files_encryption/tests/util.php | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/apps/files_encryption/tests/util.php b/apps/files_encryption/tests/util.php index e8dfb74f3f3..7d425dc122a 100755 --- a/apps/files_encryption/tests/util.php +++ b/apps/files_encryption/tests/util.php @@ -132,6 +132,41 @@ class Test_Encryption_Util extends \PHPUnit_Framework_TestCase { } + /** + * @medium + * @brief test detection of encrypted files + */ + function testIsEncryptedPath() { + + $util = new Encryption\Util($this->view, $this->userId); + + self::loginHelper($this->userId); + + $unencryptedFile = '/tmpUnencrypted-' . time() . '.txt'; + $encryptedFile = '/tmpEncrypted-' . time() . '.txt'; + + // Disable encryption proxy to write a unencrypted file + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + $this->view->file_put_contents($this->userId . '/files/' . $unencryptedFile, $this->dataShort); + + // Re-enable proxy - our work is done + \OC_FileProxy::$enabled = $proxyStatus; + + // write a encrypted file + $this->view->file_put_contents($this->userId . '/files/' . $encryptedFile, $this->dataShort); + + // test if both files are detected correctly + $this->assertFalse($util->isEncryptedPath($this->userId . '/files/' . $unencryptedFile)); + $this->assertTrue($util->isEncryptedPath($this->userId . '/files/' . $encryptedFile)); + + // cleanup + $this->view->unlink($this->userId . '/files/' . $unencryptedFile, $this->dataShort); + $this->view->unlink($this->userId . '/files/' . $encryptedFile, $this->dataShort); + + } + /** * @medium * @brief test setup of encryption directories -- cgit v1.2.3 From df0c1fe7f3abb0a7ec584cf64c19091bf86e1906 Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Wed, 18 Dec 2013 17:07:35 +0100 Subject: add test for the stream wrapper to read encrypted files from the system folder /tmp --- apps/files_encryption/tests/stream.php | 39 ++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/apps/files_encryption/tests/stream.php b/apps/files_encryption/tests/stream.php index 530ee3a7b2d..2767bbe512b 100644 --- a/apps/files_encryption/tests/stream.php +++ b/apps/files_encryption/tests/stream.php @@ -180,4 +180,43 @@ class Test_Encryption_Stream extends \PHPUnit_Framework_TestCase { // tear down $view->unlink($filename); } + + /** + * @medium + * @brief test if stream wrapper can read files outside from the data folder + */ + function testStreamFromLocalFile() { + + $filename = '/' . $this->userId . '/files/' . 'tmp-' . time().'.txt'; + + $tmpFilename = "/tmp/" . time() . ".txt"; + + // write an encrypted file + $cryptedFile = $this->view->file_put_contents($filename, $this->dataShort); + + // Test that data was successfully written + $this->assertTrue(is_int($cryptedFile)); + + // create a copy outside of the data folder in /tmp + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + $encryptedContent = $this->view->file_get_contents($filename); + \OC_FileProxy::$enabled = $proxyStatus; + + file_put_contents($tmpFilename, $encryptedContent); + + \OCA\Encryption\Helper::addTmpFileToMapper($tmpFilename, $filename); + + // try to read the file from /tmp + $handle = fopen("crypt://".$tmpFilename, "r"); + $contentFromTmpFile = stream_get_contents($handle); + + // check if it was successful + $this->assertEquals($this->dataShort, $contentFromTmpFile); + + // clean up + unlink($tmpFilename); + $this->view->unlink($filename); + + } } -- cgit v1.2.3