diff --git a/apps/files_encryption/lib/helper.php b/apps/files_encryption/lib/helper.php index 17bcac5c585dd050c5f82546f77b33892094c257..f893cf16e5a4718e4dfee491885685e4c6501de6 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 7a37d2200a4f18d779bcad5a9cd656321334565d..b3bf34ddb827b6e509a172b96059e60e3b1f418b 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; @@ -163,15 +175,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); } @@ -477,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; @@ -498,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 a3852312200e342b0b9cbcbcec38c75c5d41b1be..6007d4e0e2edd892d740a1dd34fff011a43f4726 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -429,8 +429,22 @@ 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)) { + // 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 + // 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 @@ -482,7 +496,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); diff --git a/apps/files_encryption/tests/stream.php b/apps/files_encryption/tests/stream.php index 530ee3a7b2d25c05fd5a827cba8662a973113856..2767bbe512bd096dc1e113aa0540fbde36438c27 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); + + } } diff --git a/apps/files_encryption/tests/util.php b/apps/files_encryption/tests/util.php index e8dfb74f3f398c12022ae7dc0a671f30453fbf18..7d425dc122ac38657781b3450cb8451906e6293f 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