diff --git a/backup/util/helper/backup_helper.class.php b/backup/util/helper/backup_helper.class.php
index efc0c49..0693143 100644
--- a/backup/util/helper/backup_helper.class.php
+++ b/backup/util/helper/backup_helper.class.php
@@ -308,7 +308,7 @@ abstract class backup_helper {
         if ($fs->file_exists($fr['contextid'], $fr['component'], $fr['filearea'], $fr['itemid'], $fr['filepath'], $fr['filename'])) {
             $pathnamehash = $fs->get_pathname_hash($fr['contextid'], $fr['component'], $fr['filearea'], $fr['itemid'], $fr['filepath'], $fr['filename']);
             $sf = $fs->get_file_by_hash($pathnamehash);
-            $sf->delete();
+            $sf->delete(false);
         }
         $file = $fs->create_file_from_pathname($fr, $filepath);
         unlink($filepath);
diff --git a/lib/filestorage/file_storage.php b/lib/filestorage/file_storage.php
index 97df075..b6d52ae 100644
--- a/lib/filestorage/file_storage.php
+++ b/lib/filestorage/file_storage.php
@@ -1385,9 +1385,8 @@ class file_storage {
         $newrecord->sortorder    = $filerecord->sortorder;
 
         list($newrecord->contenthash, $newrecord->filesize, $newfile) = $this->add_string_to_pool($content);
-        $filepathname = $this->path_from_hash($newrecord->contenthash) . '/' . $newrecord->contenthash;
         // get mimetype by magic bytes
-        $newrecord->mimetype = empty($filerecord->mimetype) ? $this->mimetype($filepathname, $filerecord->filename) : $filerecord->mimetype;
+        $newrecord->mimetype = empty($filerecord->mimetype) ? $this->mimetype_buf($content, $filerecord->filename) : $filerecord->mimetype;
 
         $newrecord->pathnamehash = $this->get_pathname_hash($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->filename);
 
@@ -1506,8 +1505,7 @@ class file_storage {
         if (isset($filerecord->contenthash) && $this->content_exists($filerecord->contenthash)) {
             // there was specified the contenthash for a file already stored in moodle filepool
             if (empty($filerecord->filesize)) {
-                $filepathname = $this->path_from_hash($filerecord->contenthash) . '/' . $filerecord->contenthash;
-                $filerecord->filesize = filesize($filepathname);
+                $filerecord->filesize = $this->get_content_size($filerecord->contenthash);
             } else {
                 $filerecord->filesize = clean_param($filerecord->filesize, PARAM_INT);
             }
@@ -1922,7 +1920,7 @@ class file_storage {
      *
      * @param string $contenthash
      */
-    public function deleted_file_cleanup($contenthash) {
+    public function deleted_file_cleanup($contenthash, $trash = true) {
         global $DB;
 
         if ($contenthash === sha1('')) {
@@ -1942,6 +1940,10 @@ class file_storage {
             //weird, but no problem
             return;
         }
+        if (!$trash) {
+            @unlink($contentfile);
+            return;
+        }
         $trashpath = $this->trash_path_from_hash($contenthash);
         $trashfile = $trashpath.'/'.$contenthash;
         if (file_exists($trashfile)) {
@@ -2199,6 +2201,15 @@ class file_storage {
         return $type;
     }
 
+    public static function mimetype_buf($buf = null, $filename = null) {
+        $type = !empty($filename) ? mimeinfo('type', $filename) : 'document/unknown';
+        if ($type === 'document/unknown' && class_exists('finfo') && $buf !== null) {
+            $finfo = new finfo(FILEINFO_MIME_TYPE);
+            $type = mimeinfo_from_type('type', $finfo->buffer($buf));
+        }
+        return $type;
+    }
+
     /**
      * Cron cleanup job.
      */
@@ -2397,4 +2408,9 @@ class file_storage {
         $data = array('id' => $referencefileid, 'lastsync' => $lastsync, 'lifetime' => $lifetime);
         $DB->update_record('files_reference', (object)$data);
     }
+
+    protected function get_content_size($contenthash) {
+        $filepathname = $this->path_from_hash($contenthash) . '/' . $contenthash;
+        return filesize($filepathname);
+    }
 }
diff --git a/lib/filestorage/stored_file.php b/lib/filestorage/stored_file.php
index fbce9af..b101649 100644
--- a/lib/filestorage/stored_file.php
+++ b/lib/filestorage/stored_file.php
@@ -39,13 +39,13 @@ defined('MOODLE_INTERNAL') || die();
  */
 class stored_file {
     /** @var file_storage file storage pool instance */
-    private $fs;
+    protected $fs;
     /** @var stdClass record from the files table left join files_reference table */
-    private $file_record;
+    protected $file_record;
     /** @var string location of content files */
-    private $filedir;
+    protected $filedir;
     /** @var repository repository plugin instance */
-    private $repository;
+    protected $repository;
 
     /**
      * Constructor, this constructor should be called ONLY from the file_storage class!
@@ -168,16 +168,19 @@ class stored_file {
         }
         // Validate mimetype field
         // we don't use {@link stored_file::get_content_file_location()} here becaues it will try to update file_record
-        $pathname = $this->get_pathname_by_contenthash();
-        // try to recover the content from trash
-        if (!is_readable($pathname)) {
-            if (!$this->fs->try_content_recovery($this) or !is_readable($pathname)) {
-                throw new file_exception('storedfilecannotread', '', $pathname);
+        $mimetype = $this->fs->mimetype_buf(null, $this->file_record->filename);
+        if ($mimetype == 'document/unknown') {
+            $pathname = $this->get_pathname_by_contenthash();
+            // try to recover the content from trash
+            if (!is_readable($pathname)) {
+                if (!$this->fs->try_content_recovery($this) or !is_readable($pathname)) {
+                    throw new file_exception('storedfilecannotread', '', $pathname);
+                }
             }
+            $mimetype = $this->fs->mimetype($pathname, $this->file_record->filename);
         }
-        $mimetype = $this->fs->mimetype($pathname, $this->file_record->filename);
-        $this->file_record->mimetype = $mimetype;
 
+        $this->file_record->mimetype = $mimetype;
         $DB->update_record('files', $this->file_record);
     }
 
@@ -306,7 +309,7 @@ class stored_file {
      *
      * @return bool always true or exception if error occurred
      */
-    public function delete() {
+    public function delete($trash = true) {
         global $DB;
 
         if ($this->is_directory()) {
@@ -335,7 +338,7 @@ class stored_file {
         }
 
         // Move pool file to trash if content not needed any more.
-        $this->fs->deleted_file_cleanup($this->file_record->contenthash);
+        $this->fs->deleted_file_cleanup($this->file_record->contenthash, $trash);
         return true; // BC only
     }
 
@@ -387,7 +390,7 @@ class stored_file {
      *
      * @return resource file handle
      */
-    public function get_content_file_handle() {
+    public function get_content_file_handle($seekable = true) {
         $path = $this->get_content_file_location();
         if (!is_readable($path)) {
             if (!$this->fs->try_content_recovery($this) or !is_readable($path)) {
@@ -519,7 +522,7 @@ class stored_file {
             if (!is_readable($path)) {
                 return false;
             }
-            return $filearch->add_file_from_pathname($archivepath, $path);
+            return $filearch->add_file_from_pathname($archivepath, $path, $this);
         }
     }
 
@@ -530,13 +533,18 @@ class stored_file {
      * @return mixed array with width, height and mimetype; false if not an image
      */
     public function get_imageinfo() {
+        // don't go fetching the file unless we need to
+        $mimetype = $this->get_mimetype();
+        if (!preg_match('|^image/|', $mimetype)) {
+            return false;
+        }
+
         $path = $this->get_content_file_location();
         if (!is_readable($path)) {
             if (!$this->fs->try_content_recovery($this) or !is_readable($path)) {
                 throw new file_exception('storedfilecannotread', '', $path);
             }
         }
-        $mimetype = $this->get_mimetype();
         if (!preg_match('|^image/|', $mimetype) || !filesize($path) || !($imageinfo = getimagesize($path))) {
             return false;
         }
@@ -990,4 +998,30 @@ class stored_file {
             $this->repository->import_external_file_contents($this, $maxbytes);
         }
     }
+
+    /**
+     * Return the sha1 hash for the real underlying stored content i.e on disk
+     * This can be used to validate the DB and stored data agree for validation purposes.
+     *
+     * @return string calculated sha1 hash of the stored data for this file
+     */
+    public function get_real_hash() {
+        if (!$fh = $this->get_content_file_handle(false)) {
+            throw new file_exception('storedfilecannotread');
+        }
+        $hash = hash_init('sha1');
+        hash_update_stream($hash, $fh);
+        @fclose($fh);
+        return hash_final($hash);
+    }
+
+
+    /**
+     * Throw a file_exception if the hash of the stored file doesn't match excepted
+     */
+    public function validate() {
+        if ($this->get_contenthash() !== $this->get_real_hash()) {
+            throw new file_exception('storedfileproblem', 'Stored content does not match expected');
+        }
+    }
 }
diff --git a/lib/filestorage/tests/file_storage_test.php b/lib/filestorage/tests/file_storage_test.php
index 709e886..ba0ff01 100644
--- a/lib/filestorage/tests/file_storage_test.php
+++ b/lib/filestorage/tests/file_storage_test.php
@@ -84,7 +84,12 @@ class filestoragelib_testcase extends advanced_testcase {
         $file2 = $fs->create_file_from_string($filerecord, $content);
         $this->assertInstanceOf('stored_file', $file2);
         $this->assertSame($file->get_contenthash(), $file2->get_contenthash());
-        $this->assertFileExists($location);
+        if (!$fs instanceof cloudstore_file_storage) {
+            $this->assertFileExists($location);
+        } else {
+            $this->assertTrue($fs->file_exists_by_hash($file2->get_pathnamehash()));
+        }
+
 
         $this->assertEquals(4, $DB->count_records('files', array()));
 
@@ -99,8 +104,10 @@ class filestoragelib_testcase extends advanced_testcase {
         $this->assertSame($file->get_contenthash(), $file3->get_contenthash());
         $this->assertFileExists($location);
 
-        $this->assertSame($content, file_get_contents($location));
-        $this->assertDebuggingCalled();
+        $this->assertSame($content, $file3->get_content());
+        if (!$fs instanceof cloudstore_file_storage) {
+            $this->assertDebuggingCalled();
+        }
 
         $this->assertEquals(5, $DB->count_records('files', array()));
     }
@@ -138,7 +145,11 @@ class filestoragelib_testcase extends advanced_testcase {
 
         $location = test_stored_file_inspection::get_pretected_pathname($file);
 
-        $this->assertFileExists($location);
+        if (!$fs instanceof cloudstore_file_storage) {
+            $this->assertFileExists($location);
+        } else {
+            $this->assertTrue($fs->file_exists_by_hash($file->get_pathnamehash()));
+        }
 
 
         // Verify the dir placeholder files are created.
@@ -156,7 +167,11 @@ class filestoragelib_testcase extends advanced_testcase {
         $file2 = $fs->create_file_from_pathname($filerecord, $filepath);
         $this->assertInstanceOf('stored_file', $file2);
         $this->assertSame($file->get_contenthash(), $file2->get_contenthash());
-        $this->assertFileExists($location);
+        if (!$fs instanceof cloudstore_file_storage) {
+            $this->assertFileExists($location);
+        } else {
+            $this->assertTrue($fs->file_exists_by_hash($file2->get_pathnamehash()));
+        }
 
         $this->assertEquals(4, $DB->count_records('files', array()));
 
@@ -169,10 +184,16 @@ class filestoragelib_testcase extends advanced_testcase {
         $file3 = $fs->create_file_from_pathname($filerecord, $filepath);
         $this->assertInstanceOf('stored_file', $file3);
         $this->assertSame($file->get_contenthash(), $file3->get_contenthash());
-        $this->assertFileExists($location);
+        if (!$fs instanceof cloudstore_file_storage) {
+            $this->assertFileExists($location);
+        } else {
+            $this->assertTrue($fs->file_exists_by_hash($file3->get_pathnamehash()));
+        }
 
-        $this->assertSame(file_get_contents($filepath), file_get_contents($location));
-        $this->assertDebuggingCalled();
+        $this->assertSame(file_get_contents($filepath), $file3->get_content());
+        if (!$fs instanceof cloudstore_file_storage) {
+            $this->assertDebuggingCalled();
+        }
 
         $this->assertEquals(5, $DB->count_records('files', array()));
 
@@ -1524,6 +1545,41 @@ class filestoragelib_testcase extends advanced_testcase {
         $this->assertTrue($symlink2->is_external_file());
     }
 
+    public function test_file_zip_pack() {
+        global $CFG;
+
+        $this->resetAfterTest(true);
+
+        $fs = get_file_storage();
+        $syscontext = context_system::instance();
+        $content = 'copy_content_test_'.microtime(true);
+
+        $filerecord = array(
+            'contextid' => $syscontext->id,
+            'component' => 'core',
+            'filearea'  => 'unittest',
+            'itemid'    => time(),
+            'filepath'  => '/contenttest/',
+            'filename'  => 'testtext.txt',
+        );
+
+        $file = $fs->create_file_from_string($filerecord, $content);
+        $file2 = $fs->get_file_by_hash($file->get_pathnamehash());
+
+        $key = 'unittest-'.time();
+        $key2 = $key.'_2';
+
+        $tmppath = tempnam($CFG->tempdir, $key);
+        $this->assertNotEmpty($tmppath);
+
+        $zipper = new zip_packer();
+        $zipper->archive_to_pathname(array($key => $file, $key2 => $file2), $tmppath);
+
+        unset($zipper);
+        unset($file);
+        @unlink($tmppath);
+    }
+
     public function test_get_unused_filename() {
         global $USER;
         $this->resetAfterTest(true);
diff --git a/lib/filestorage/zip_archive.php b/lib/filestorage/zip_archive.php
index 75908d7..b6ec4c4 100644
--- a/lib/filestorage/zip_archive.php
+++ b/lib/filestorage/zip_archive.php
@@ -63,6 +63,9 @@ class zip_archive extends file_archive {
     /** @var bool ugly hack for broken empty zip handling in < PHP 5.3.10 */
     protected $emptyziphack = false;
 
+    /** maintain references to stored_files used in this zip */
+    protected $stored_files = array();
+
     /**
      * Create new zip_archive instance.
      */
@@ -328,7 +331,7 @@ class zip_archive extends file_archive {
      * @param string $pathname location of file
      * @return bool success
      */
-    public function add_file_from_pathname($localname, $pathname) {
+    public function add_file_from_pathname($localname, $pathname, $stored_file = null) {
         if ($this->emptyziphack) {
             $this->close();
             $this->open($this->archivepathname, file_archive::OVERWRITE, $this->encoding);
@@ -365,6 +368,12 @@ class zip_archive extends file_archive {
             return false;
         }
         $this->modified = true;
+        if ($stored_file) {
+            // the zip archive now contains a reference to the content belonging to a stored_file
+            // maintain a reference to the stored_file so that it doesn't get destroyed and clean up the content.
+            // (this is required for cloudstore only).
+            $this->stored_files[] = $stored_file;
+        }
         return true;
     }
 
diff --git a/lib/moodlelib.php b/lib/moodlelib.php
index b0cfef0..3435f80 100644
--- a/lib/moodlelib.php
+++ b/lib/moodlelib.php
@@ -6000,11 +6000,16 @@ function email_is_not_allowed($email) {
  * @return file_storage
  */
 function get_file_storage() {
-    global $CFG;
+    global $CFG, $CS_FORCED;
 
     static $fs = null;
 
-    if ($fs) {
+    $cs_enabled = !empty($CFG->cloudstore_enabled) || !empty($CS_FORCED);
+    $cs_used = !empty($fs) && $fs instanceof cloudstore_file_storage;
+
+    // it is weird that file storage might change at runtime, but this can happen in phpunit
+    // so we check if the file storage present at the moment is correct and recreate if not
+    if ($fs && (($cs_enabled && $cs_used) || (!$cs_enabled && !$cs_used))) {
         return $fs;
     }
 
@@ -6022,7 +6027,12 @@ function get_file_storage() {
         $trashdirdir = $CFG->dataroot.'/trashdir';
     }
 
-    $fs = new file_storage($filedir, $trashdirdir, "$CFG->tempdir/filestorage", $CFG->directorypermissions, $CFG->filepermissions);
+    if ($cs_enabled) {
+        require_once($CFG->dirroot . '/local/cloudstore/cloudstore_file_storage.php');
+        $fs = new cloudstore_file_storage($filedir, $trashdirdir, "$CFG->tempdir/filestorage", $CFG->directorypermissions, $CFG->filepermissions);
+    } else {
+        $fs = new file_storage($filedir, $trashdirdir, "$CFG->tempdir/filestorage", $CFG->directorypermissions, $CFG->filepermissions);
+    }
 
     return $fs;
 }
diff --git a/lib/phpunit/bootstrap.php b/lib/phpunit/bootstrap.php
index 98d06dc..e920c62 100644
--- a/lib/phpunit/bootstrap.php
+++ b/lib/phpunit/bootstrap.php
@@ -196,6 +196,10 @@ $CFG->cachetext = 0; // disable this very nasty setting
 $CFG->themerev = 1;
 $CFG->jsrev = 1;
 
+// disable cloudstore for most tests
+// there is a special suite which will enable it
+$CFG->cloudstore_enabled = false;
+
 // load test case stub classes and other stuff
 require_once("$CFG->dirroot/lib/phpunit/lib.php");
 
diff --git a/repository/upload/lib.php b/repository/upload/lib.php
index 0c663fc..29c8965 100644
--- a/repository/upload/lib.php
+++ b/repository/upload/lib.php
@@ -206,6 +206,13 @@ class repository_upload extends repository {
             $unused_filename = repository::get_unused_filename($record->itemid, $record->filepath, $record->filename);
             $record->filename = $unused_filename;
             $stored_file = $fs->create_file_from_pathname($record, $_FILES[$elname]['tmp_name']);
+            try {
+                if ($stored_file->get_filesize() < 128 * 1024 * 1024) { // 128MB limit
+                    $stored_file->validate();
+                }
+            } catch (file_exception $e) {
+                throw new file_exception('storedfileproblem', 'There was a problem storing your uploaded file. Please try again, and if the problem persists, contact support');
+            }
             if ($overwriteexisting) {
                 repository::overwrite_existing_draftfile($record->itemid, $record->filepath, $existingfilename, $record->filepath, $record->filename);
                 $record->filename = $existingfilename;
@@ -225,6 +232,13 @@ class repository_upload extends repository {
             }
         } else {
             $stored_file = $fs->create_file_from_pathname($record, $_FILES[$elname]['tmp_name']);
+            try {
+                if ($stored_file->get_filesize() < 128 * 1024 * 1024) { // 128MB limit
+                    $stored_file->validate();
+                }
+            } catch (file_exception $e) {
+                throw new file_exception('storedfileproblem', 'There was a problem storing your uploaded file. Please try again, and if the problem persists, contact support');
+            }
         }
 
         return array(
