]> source.dussan.org Git - nextcloud-server.git/commitdiff
Fixed stream wrapper bugs
authorSam Tuke <samtuke@owncloud.com>
Sat, 9 Mar 2013 18:18:34 +0000 (19:18 +0100)
committerSam Tuke <samtuke@owncloud.com>
Sat, 9 Mar 2013 18:18:34 +0000 (19:18 +0100)
Switched encryptAll() to use stream-based instead of file-at-a-time encryption
Development snapshot

apps/files_encryption/lib/crypt.php
apps/files_encryption/lib/proxy.php
apps/files_encryption/lib/stream.php
apps/files_encryption/lib/util.php
apps/files_encryption/test/crypt.php

index 2be6e3ae5d9729880746b452710e95a9ac582db8..f92930c2cbd90c9cf5adfc875073d9ce2fbfd72d 100755 (executable)
@@ -114,7 +114,7 @@ class Crypt {
          * @return true / false\r
          * @note see also OCA\Encryption\Util->isEncryptedPath()\r
          */\r
-       public static function isCatfile( $content ) {\r
+       public static function isCatfileContent( $content ) {\r
        \r
                if ( !$content ) {\r
                \r
@@ -179,7 +179,7 @@ class Crypt {
                if ( \r
                        isset( $metadata['encrypted'] ) \r
                        and $metadata['encrypted'] === true \r
-                       and ! self::isCatfile( $data ) \r
+                       and ! self::isCatfileContent( $data ) \r
                ) {\r
                \r
                        return true;\r
index c5b1c8154c4aecefe6c929cccf0a25c09108b662..2a738c80e388d43644da3978be9321cdc4b1d904 100644 (file)
@@ -74,7 +74,7 @@ class Proxy extends \OC_FileProxy {
                        
                }
                
-               if ( Crypt::isCatfile( $path ) ) {
+               if ( Crypt::isCatfileContent( $path ) ) {
                
                        return true;
                        
@@ -209,7 +209,7 @@ class Proxy extends \OC_FileProxy {
                // If data is a catfile
                if ( 
                        Crypt::mode() == 'server' 
-                       && Crypt::isCatfile( $data ) 
+                       && Crypt::isCatfileContent( $data ) 
                ) {
                
                        // TODO use get owner to find correct location of key files for shared files
@@ -439,7 +439,7 @@ class Proxy extends \OC_FileProxy {
 
        public function postGetMimeType( $path, $mime ) {
                
-               if ( Crypt::isCatfile( $path ) ) {
+               if ( Crypt::isCatfileContent( $path ) ) {
                
                        $mime = \OCP\Files::getMimeType( 'crypt://' . $path, 'w' );
                
@@ -451,7 +451,7 @@ class Proxy extends \OC_FileProxy {
 
        public function postStat( $path, $data ) {
        
-               if ( Crypt::isCatfile( $path ) ) {
+               if ( Crypt::isCatfileContent( $path ) ) {
                
                        $cached = \OC\Files\Filesystem::getFileInfo( $path, '' );
                        
@@ -464,7 +464,7 @@ class Proxy extends \OC_FileProxy {
 
        public function postFileSize( $path, $size ) {
                
-               if ( Crypt::isCatfile( $path ) ) {
+               if ( Crypt::isCatfileContent( $path ) ) {
                        
                        $cached = \OC\Files\Filesystem::getFileInfo( $path, '' );
                        
index 6074638ab39f152147a293fa42aee492d8b427b7..0b2e6ab3e64d4717de0fd22fa2513cf084532b9f 100644 (file)
@@ -68,42 +68,33 @@ class Stream {
        private $rootView; // a fsview object set to '/'
 
        public function stream_open( $path, $mode, $options, &$opened_path ) {
-
-               $this->userId = \OCP\User::getUser();
                
-               // Get access to filesystem via filesystemview object
-               if ( !self::$view ) {
-
-                       self::$view = new \OC_FilesystemView( $this->userId . '/' );
-
-               }
+               $this->userId = \OCP\User::getUser();
                
-               // Set rootview object if necessary
-               if ( ! $this->rootView ) {
+               if ( ! isset( $this->rootView ) ) {
 
-                       $this->rootView = new \OC_FilesystemView( $this->userId . '/' );
+                       $this->rootView = new \OC_FilesystemView( '/' );
 
                }
 
-               // Get the bare file path
-               $path = str_replace( 'crypt://', '', $path );
+               // Strip identifier text from path
+               $this->rawPath = str_replace( 'crypt://', '', $path );
                
-               $this->rawPath = $path;
-               
-               $this->path_f = $this->userId . '/files/' . $path;
+               // Set file path relative to user files dir
+               $this->relPath = $this->userId . '/files/' . $this->rawPath;
                
                if ( 
-               dirname( $path ) == 'streams' 
-               and isset( self::$sourceStreams[basename( $path )] ) 
+               dirname( $this->rawPath ) == 'streams' 
+               and isset( self::$sourceStreams[basename( $this->rawPath )] ) 
                ) {
                
                        // Is this just for unit testing purposes?
 
-                       $this->handle = self::$sourceStreams[basename( $path )]['stream'];
+                       $this->handle = self::$sourceStreams[basename( $this->rawPath )]['stream'];
 
-                       $this->path = self::$sourceStreams[basename( $path )]['path'];
+                       $this->path = self::$sourceStreams[basename( $this->rawPath )]['path'];
 
-                       $this->size = self::$sourceStreams[basename( $path )]['size'];
+                       $this->size = self::$sourceStreams[basename( $this->rawPath )]['size'];
 
                } else {
 
@@ -114,41 +105,38 @@ class Stream {
                        or $mode == 'wb+' 
                        ) {
 
+                               // We're writing a new file so start write counter with 0 bytes
                                $this->size = 0;
 
                        } else {
                                
+                               $this->size = $this->rootView->filesize( $this->relPath, $mode );
                                
-                               
-                               $this->size = self::$view->filesize( $this->path_f, $mode );
-                               
-                               //$this->size = filesize( $path );
+                               //$this->size = filesize( $this->rawPath );
                                
                        }
 
                        // Disable fileproxies so we can open the source file without recursive encryption
                        \OC_FileProxy::$enabled = false;
 
-                       //$this->handle = fopen( $path, $mode );
+                       //$this->handle = fopen( $this->rawPath, $mode );
                        
-                       $this->handle = self::$view->fopen( $this->path_f, $mode );
+                       $this->handle = $this->rootView->fopen( $this->relPath, $mode );
                        
                        \OC_FileProxy::$enabled = true;
 
-                       if ( !is_resource( $this->handle ) ) {
+                       if ( ! is_resource( $this->handle ) ) {
 
-                               \OCP\Util::writeLog( 'files_encryption', 'failed to open '.$path, \OCP\Util::ERROR );
+                               \OCP\Util::writeLog( 'files_encryption', 'failed to open file "'.$this->rootView . '"', \OCP\Util::ERROR );
 
+                       } else {
+                       
+                               $this->meta = stream_get_meta_data( $this->handle );
+                               
                        }
 
                }
 
-               if ( is_resource( $this->handle ) ) {
-
-                       $this->meta = stream_get_meta_data( $this->handle );
-
-               }
-
                return is_resource( $this->handle );
 
        }
@@ -238,7 +226,7 @@ class Stream {
                
                // If a keyfile already exists for a file named identically to 
                // file to be written
-               if ( self::$view->file_exists( $this->userId . '/'. 'files_encryption' . '/' . 'keyfiles' . '/' . $this->rawPath . '.key' ) ) {
+               if ( $this->rootView->file_exists( $this->userId . '/'. 'files_encryption' . '/' . 'keyfiles' . '/' . $this->rawPath . '.key' ) ) {
                
                        // TODO: add error handling for when file exists but no 
                        // keyfile
index 6a18feea7dfc1d38a3b8d1c68c5877d0c528edc9..e8b5be2de15f4290d5ad9e3f28597a772438cdf3 100644 (file)
 # Bugs
 # ----
 # Sharing a file to a user without encryption set up will not provide them with access but won't notify the sharer
-# When encryption app is disabled files become unreadable
 # Timeouts on first login due to encryption of very large files
 
 
 # Missing features
 # ----------------
-# Re-use existing keyfiles so they don't need version control
+# Re-use existing keyfiles so they don't need version control (part implemented, stream{} and util{} remain)
 # Make sure user knows if large files weren't encrypted
 # Trashbin support
 
@@ -280,14 +279,14 @@ class Util {
                                                // will eat server resources :(
                                                if ( 
                                                        Keymanager::getFileKey( $this->view, $this->userId, $file ) 
-                                                       && Crypt::isCatfile( $data )
+                                                       && Crypt::isCatfileContent( $data )
                                                ) {
                                                
                                                        $found['encrypted'][] = array( 'name' => $file, 'path' => $filePath );
                                                
                                                // If the file uses old 
                                                // encryption system
-                                               } elseif (  Crypt::isLegacyEncryptedContent( $this->view->file_get_contents( $filePath ), $relPath ) ) {
+                                               } elseif (  Crypt::isLegacyEncryptedContent( $this->tail( $filePath, 3 ), $relPath ) ) {
                                                        
                                                        $found['legacy'][] = array( 'name' => $file, 'path' => $filePath );
                                                        
@@ -324,6 +323,49 @@ class Util {
 
        }
        
+        /**
+         * @brief Fetch the last lines of a file efficiently
+         * @note Safe to use on large files; does not read entire file to memory
+         * @note Derivative of http://tekkie.flashbit.net/php/tail-functionality-in-php
+         */
+       public function tail( $filename, $numLines ) {
+               
+               \OC_FileProxy::$enabled = false;
+               
+               $text = '';
+               $pos = -1;
+               $handle = $this->view->fopen( $filename, 'r' );
+
+               while ( $numLines > 0 ) {
+               
+                       --$pos;
+
+                       if( fseek( $handle, $pos, SEEK_END ) !== 0 ) {
+                       
+                               rewind( $handle );
+                               $numLines = 0;
+                               
+                       } elseif ( fgetc( $handle ) === "\n" ) {
+                       
+                               --$numLines;
+                               
+                       }
+
+                       $block_size = ( -$pos ) % 8192;
+                       if ( $block_size === 0 || $numLines === 0 ) {
+                       
+                               $text = fread( $handle, ( $block_size === 0 ? 8192 : $block_size ) ) . $text;
+                               
+                       }
+               }
+
+               fclose( $handle );
+               
+               \OC_FileProxy::$enabled = true;
+               
+               return $text;
+       }
+       
         /**
          * @brief Check if a given path identifies an encrypted file
          * @return true / false
@@ -338,7 +380,7 @@ class Util {
                
                \OC_FileProxy::$enabled = true;
                
-               return Crypt::isCatfile( $data );
+               return Crypt::isCatfileContent( $data );
        
        }
        
@@ -403,22 +445,32 @@ class Util {
                
                        // Encrypt unencrypted files
                        foreach ( $found['plain'] as $plainFile ) {
+                       
+                               // Open plain file handle
+                               
+                               
+                               // Open enc file handle
+                               
                                
-                               // Fetch data from file
-                               $plainData = $this->view->file_get_contents( $plainFile['path'] );
+                               // Read plain file in chunks
                                
-                               // Encrypt data, generate catfile
-                               $encrypted = Crypt::keyEncryptKeyfile( $plainData, $publicKey );
                                
                                $relPath = $this->stripUserFilesPath( $plainFile['path'] );
                                
-                               // Save keyfile
-                               Keymanager::setFileKey( $this->view, $relPath, $this->userId, $encrypted['key'] );
+                               // Open handle with for binary reading
+                               $plainHandle = $this->view->fopen( $plainFile['path'], 'rb' );
+                               // Open handle with for binary writing
+                               $encHandle = fopen( 'crypt://' . 'var/www/oc6/data/' . $plainFile['path'] . '.tmp', 'ab' );
                                
                                // Overwrite the existing file with the encrypted one
-                               $this->view->file_put_contents( $plainFile['path'], $encrypted['data'] );
+                               //$this->view->file_put_contents( $plainFile['path'], $encrypted['data'] );
+                               $size = stream_copy_to_stream( $plainHandle, $encHandle );
+                               
+                               // Fetch the key that has just been set/updated by the stream
+                               $encKey = Keymanager::getFileKey( $relPath );
                                
-                               $size = strlen( $encrypted['data'] );
+                               // Save keyfile
+                               Keymanager::setFileKey( $this->view, $relPath, $this->userId, $encKey );
                                
                                // Add the file to the cache
                                \OC\Files\Filesystem::putFileInfo( $plainFile['path'], array( 'encrypted'=>true, 'size' => $size ), '' );
index 48ad2ee00751a81fd88ecb6585fae04ddeb4f315..b02e63b2ffc667af9a055ec09cee428792c9e143 100755 (executable)
@@ -416,13 +416,13 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase {
        
        function testIsEncryptedContent() {
                
-               $this->assertFalse( Encryption\Crypt::isCatfile( $this->dataUrl ) );
+               $this->assertFalse( Encryption\Crypt::isCatfileContent( $this->dataUrl ) );
                
-               $this->assertFalse( Encryption\Crypt::isCatfile( $this->legacyEncryptedData ) );
+               $this->assertFalse( Encryption\Crypt::isCatfileContent( $this->legacyEncryptedData ) );
                
                $keyfileContent = Encryption\Crypt::symmetricEncryptFileContent( $this->dataUrl, 'hat' );
 
-               $this->assertTrue( Encryption\Crypt::isCatfile( $keyfileContent ) );
+               $this->assertTrue( Encryption\Crypt::isCatfileContent( $keyfileContent ) );
                
        }