Merge pull request #9897 from owncloud/support_webdav_copy
add support for webdav copy to the encryption and versions app
This commit is contained in:
commit
ef3c0c508f
|
@ -412,18 +412,44 @@ class Hooks {
|
|||
'uid' => $ownerOld,
|
||||
'path' => $pathOld,
|
||||
'type' => $type,
|
||||
'operation' => 'rename',
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* after a file is renamed, rename its keyfile and share-keys also fix the file size and fix also the sharing
|
||||
* @param array $params array with oldpath and newpath
|
||||
*
|
||||
* This function is connected to the rename signal of OC_Filesystem and adjust the name and location
|
||||
* of the stored versions along the actual file
|
||||
* mark file as renamed so that we know the original source after the file was renamed
|
||||
* @param array $params with the old path and the new path
|
||||
*/
|
||||
public static function postRename($params) {
|
||||
public static function preCopy($params) {
|
||||
$user = \OCP\User::getUser();
|
||||
$view = new \OC\Files\View('/');
|
||||
$util = new Util($view, $user);
|
||||
list($ownerOld, $pathOld) = $util->getUidAndFilename($params['oldpath']);
|
||||
|
||||
// we only need to rename the keys if the rename happens on the same mountpoint
|
||||
// otherwise we perform a stream copy, so we get a new set of keys
|
||||
$mp1 = $view->getMountPoint('/' . $user . '/files/' . $params['oldpath']);
|
||||
$mp2 = $view->getMountPoint('/' . $user . '/files/' . $params['newpath']);
|
||||
|
||||
$type = $view->is_dir('/' . $user . '/files/' . $params['oldpath']) ? 'folder' : 'file';
|
||||
|
||||
if ($mp1 === $mp2) {
|
||||
self::$renamedFiles[$params['oldpath']] = array(
|
||||
'uid' => $ownerOld,
|
||||
'path' => $pathOld,
|
||||
'type' => $type,
|
||||
'operation' => 'copy');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* after a file is renamed/copied, rename/copy its keyfile and share-keys also fix the file size and fix also the sharing
|
||||
*
|
||||
* @param array $params array with oldpath and newpath
|
||||
*/
|
||||
public static function postRenameOrCopy($params) {
|
||||
|
||||
if (\OCP\App::isEnabled('files_encryption') === false) {
|
||||
return true;
|
||||
|
@ -442,6 +468,7 @@ class Hooks {
|
|||
$ownerOld = self::$renamedFiles[$params['oldpath']]['uid'];
|
||||
$pathOld = self::$renamedFiles[$params['oldpath']]['path'];
|
||||
$type = self::$renamedFiles[$params['oldpath']]['type'];
|
||||
$operation = self::$renamedFiles[$params['oldpath']]['operation'];
|
||||
unset(self::$renamedFiles[$params['oldpath']]);
|
||||
} else {
|
||||
\OCP\Util::writeLog('Encryption library', "can't get path and owner from the file before it was renamed", \OCP\Util::DEBUG);
|
||||
|
@ -484,17 +511,17 @@ class Hooks {
|
|||
$matches = Helper::findShareKeys($oldShareKeyPath, $view);
|
||||
foreach ($matches as $src) {
|
||||
$dst = \OC\Files\Filesystem::normalizePath(str_replace($pathOld, $pathNew, $src));
|
||||
$view->rename($src, $dst);
|
||||
$view->$operation($src, $dst);
|
||||
}
|
||||
|
||||
} else {
|
||||
// handle share-keys folders
|
||||
$view->rename($oldShareKeyPath, $newShareKeyPath);
|
||||
$view->$operation($oldShareKeyPath, $newShareKeyPath);
|
||||
}
|
||||
|
||||
// Rename keyfile so it isn't orphaned
|
||||
if ($view->file_exists($oldKeyfilePath)) {
|
||||
$view->rename($oldKeyfilePath, $newKeyfilePath);
|
||||
$view->$operation($oldKeyfilePath, $newKeyfilePath);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -62,7 +62,9 @@ class Helper {
|
|||
public static function registerFilesystemHooks() {
|
||||
|
||||
\OCP\Util::connectHook('OC_Filesystem', 'rename', 'OCA\Encryption\Hooks', 'preRename');
|
||||
\OCP\Util::connectHook('OC_Filesystem', 'post_rename', 'OCA\Encryption\Hooks', 'postRename');
|
||||
\OCP\Util::connectHook('OC_Filesystem', 'post_rename', 'OCA\Encryption\Hooks', 'postRenameOrCopy');
|
||||
\OCP\Util::connectHook('OC_Filesystem', 'copy', 'OCA\Encryption\Hooks', 'preCopy');
|
||||
\OCP\Util::connectHook('OC_Filesystem', 'post_copy', 'OCA\Encryption\Hooks', 'postRenameOrCopy');
|
||||
\OCP\Util::connectHook('OC_Filesystem', 'post_delete', 'OCA\Encryption\Hooks', 'postDelete');
|
||||
\OCP\Util::connectHook('OC_Filesystem', 'delete', 'OCA\Encryption\Hooks', 'preDelete');
|
||||
\OCP\Util::connectHook('OC_Filesystem', 'post_umount', 'OCA\Encryption\Hooks', 'postUmount');
|
||||
|
|
|
@ -335,6 +335,58 @@ class Test_Encryption_Hooks extends \PHPUnit_Framework_TestCase {
|
|||
$this->rootView->unlink('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder);
|
||||
}
|
||||
|
||||
/**
|
||||
* test rename operation
|
||||
*/
|
||||
function testCopyHook() {
|
||||
|
||||
// save file with content
|
||||
$cryptedFile = file_put_contents('crypt:///' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->filename, $this->data);
|
||||
|
||||
// test that data was successfully written
|
||||
$this->assertTrue(is_int($cryptedFile));
|
||||
|
||||
// check if keys exists
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/share-keys/'
|
||||
. $this->filename . '.' . self::TEST_ENCRYPTION_HOOKS_USER1 . '.shareKey'));
|
||||
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/keyfiles/'
|
||||
. $this->filename . '.key'));
|
||||
|
||||
// make subfolder and sub-subfolder
|
||||
$this->rootView->mkdir('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder);
|
||||
$this->rootView->mkdir('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder . '/' . $this->folder);
|
||||
|
||||
$this->assertTrue($this->rootView->is_dir('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder . '/' . $this->folder));
|
||||
|
||||
// copy the file to the sub-subfolder
|
||||
\OC\Files\Filesystem::copy($this->filename, '/' . $this->folder . '/' . $this->folder . '/' . $this->filename);
|
||||
|
||||
$this->assertTrue($this->rootView->file_exists('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->filename));
|
||||
$this->assertTrue($this->rootView->file_exists('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder . '/' . $this->folder . '/' . $this->filename));
|
||||
|
||||
// keys should be copied too
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/share-keys/'
|
||||
. $this->filename . '.' . self::TEST_ENCRYPTION_HOOKS_USER1 . '.shareKey'));
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/keyfiles/'
|
||||
. $this->filename . '.key'));
|
||||
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/share-keys/' . $this->folder . '/' . $this->folder . '/'
|
||||
. $this->filename . '.' . self::TEST_ENCRYPTION_HOOKS_USER1 . '.shareKey'));
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/keyfiles/' . $this->folder . '/' . $this->folder . '/'
|
||||
. $this->filename . '.key'));
|
||||
|
||||
// cleanup
|
||||
$this->rootView->unlink('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder);
|
||||
$this->rootView->unlink('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief replacing encryption keys during password change should be allowed
|
||||
* until the user logged in for the first time
|
||||
|
|
|
@ -8,9 +8,4 @@ OC::$CLASSPATH['OCA\Files_Versions\Capabilities'] = 'files_versions/lib/capabili
|
|||
OCP\Util::addscript('files_versions', 'versions');
|
||||
OCP\Util::addStyle('files_versions', 'versions');
|
||||
|
||||
// Listen to write signals
|
||||
OCP\Util::connectHook('OC_Filesystem', 'write', "OCA\Files_Versions\Hooks", "write_hook");
|
||||
// Listen to delete and rename signals
|
||||
OCP\Util::connectHook('OC_Filesystem', 'post_delete', "OCA\Files_Versions\Hooks", "remove_hook");
|
||||
OCP\Util::connectHook('OC_Filesystem', 'delete', "OCA\Files_Versions\Hooks", "pre_remove_hook");
|
||||
OCP\Util::connectHook('OC_Filesystem', 'rename', "OCA\Files_Versions\Hooks", "rename_hook");
|
||||
\OCA\Files_Versions\Hooks::connectHooks();
|
||||
|
|
|
@ -14,6 +14,16 @@ namespace OCA\Files_Versions;
|
|||
|
||||
class Hooks {
|
||||
|
||||
public static function connectHooks() {
|
||||
// Listen to write signals
|
||||
\OCP\Util::connectHook('OC_Filesystem', 'write', "OCA\Files_Versions\Hooks", "write_hook");
|
||||
// Listen to delete and rename signals
|
||||
\OCP\Util::connectHook('OC_Filesystem', 'post_delete', "OCA\Files_Versions\Hooks", "remove_hook");
|
||||
\OCP\Util::connectHook('OC_Filesystem', 'delete', "OCA\Files_Versions\Hooks", "pre_remove_hook");
|
||||
\OCP\Util::connectHook('OC_Filesystem', 'rename', "OCA\Files_Versions\Hooks", "rename_hook");
|
||||
\OCP\Util::connectHook('OC_Filesystem', 'copy', "OCA\Files_Versions\Hooks", "copy_hook");
|
||||
}
|
||||
|
||||
/**
|
||||
* listen to write event.
|
||||
*/
|
||||
|
@ -69,7 +79,25 @@ class Hooks {
|
|||
$oldpath = $params['oldpath'];
|
||||
$newpath = $params['newpath'];
|
||||
if($oldpath<>'' && $newpath<>'') {
|
||||
Storage::rename( $oldpath, $newpath );
|
||||
Storage::renameOrCopy($oldpath, $newpath, 'rename');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* copy versions of copied files
|
||||
* @param array $params array with oldpath and newpath
|
||||
*
|
||||
* This function is connected to the copy signal of OC_Filesystem and copies the
|
||||
* the stored versions to the new location
|
||||
*/
|
||||
public static function copy_hook($params) {
|
||||
|
||||
if (\OCP\App::isEnabled('files_versions')) {
|
||||
$oldpath = $params['oldpath'];
|
||||
$newpath = $params['newpath'];
|
||||
if($oldpath<>'' && $newpath<>'') {
|
||||
Storage::renameOrCopy($oldpath, $newpath, 'copy');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -174,9 +174,12 @@ class Storage {
|
|||
}
|
||||
|
||||
/**
|
||||
* rename versions of a file
|
||||
* rename or copy versions of a file
|
||||
* @param string $old_path
|
||||
* @param string $new_path
|
||||
* @param string $operation can be 'copy' or 'rename'
|
||||
*/
|
||||
public static function rename($old_path, $new_path) {
|
||||
public static function renameOrCopy($old_path, $new_path, $operation) {
|
||||
list($uid, $oldpath) = self::getUidAndFilename($old_path);
|
||||
list($uidn, $newpath) = self::getUidAndFilename($new_path);
|
||||
$versions_view = new \OC\Files\View('/'.$uid .'/files_versions');
|
||||
|
@ -188,18 +191,21 @@ class Storage {
|
|||
return self::store($new_path);
|
||||
}
|
||||
|
||||
self::expire($newpath);
|
||||
|
||||
if ( $files_view->is_dir($oldpath) && $versions_view->is_dir($oldpath) ) {
|
||||
$versions_view->rename($oldpath, $newpath);
|
||||
$versions_view->$operation($oldpath, $newpath);
|
||||
} else if ( ($versions = Storage::getVersions($uid, $oldpath)) ) {
|
||||
// create missing dirs if necessary
|
||||
self::createMissingDirectories($newpath, new \OC\Files\View('/'. $uidn));
|
||||
|
||||
foreach ($versions as $v) {
|
||||
$versions_view->rename($oldpath.'.v'.$v['version'], $newpath.'.v'.$v['version']);
|
||||
$versions_view->$operation($oldpath.'.v'.$v['version'], $newpath.'.v'.$v['version']);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$files_view->is_dir($newpath)) {
|
||||
self::expire($newpath);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -254,34 +260,46 @@ class Storage {
|
|||
public static function getVersions($uid, $filename, $userFullPath = '') {
|
||||
$versions = array();
|
||||
// fetch for old versions
|
||||
$view = new \OC\Files\View('/' . $uid . '/' . self::VERSIONS_ROOT);
|
||||
$view = new \OC\Files\View('/' . $uid . '/');
|
||||
|
||||
$pathinfo = pathinfo($filename);
|
||||
|
||||
$files = $view->getDirectoryContent($pathinfo['dirname']);
|
||||
|
||||
$versionedFile = $pathinfo['basename'];
|
||||
|
||||
foreach ($files as $file) {
|
||||
if ($file['type'] === 'file') {
|
||||
$pos = strrpos($file['path'], '.v');
|
||||
$currentFile = substr($file['name'], 0, strrpos($file['name'], '.v'));
|
||||
if ($currentFile === $versionedFile) {
|
||||
$version = substr($file['path'], $pos + 2);
|
||||
$key = $version . '#' . $filename;
|
||||
$versions[$key]['cur'] = 0;
|
||||
$versions[$key]['version'] = $version;
|
||||
$versions[$key]['humanReadableTimestamp'] = self::getHumanReadableTimestamp($version);
|
||||
if (empty($userFullPath)) {
|
||||
$versions[$key]['preview'] = '';
|
||||
} else {
|
||||
$versions[$key]['preview'] = \OCP\Util::linkToRoute('core_ajax_versions_preview', array('file' => $userFullPath, 'version' => $version));
|
||||
$dir = self::VERSIONS_ROOT . '/' . $pathinfo['dirname'];
|
||||
|
||||
$dirContent = false;
|
||||
if ($view->is_dir($dir)) {
|
||||
$dirContent = $view->opendir($dir);
|
||||
}
|
||||
|
||||
if ($dirContent === false) {
|
||||
return $versions;
|
||||
}
|
||||
|
||||
if (is_resource($dirContent)) {
|
||||
while (($entryName = readdir($dirContent)) !== false) {
|
||||
if (!\OC\Files\Filesystem::isIgnoredDir($entryName)) {
|
||||
$pathparts = pathinfo($entryName);
|
||||
$filename = $pathparts['filename'];
|
||||
if ($filename === $versionedFile) {
|
||||
$pathparts = pathinfo($entryName);
|
||||
$timestamp = substr($pathparts['extension'], 1);
|
||||
$filename = $pathparts['filename'];
|
||||
$key = $timestamp . '#' . $filename;
|
||||
$versions[$key]['version'] = $timestamp;
|
||||
$versions[$key]['humanReadableTimestamp'] = self::getHumanReadableTimestamp($timestamp);
|
||||
if (empty($userFullPath)) {
|
||||
$versions[$key]['preview'] = '';
|
||||
} else {
|
||||
$versions[$key]['preview'] = \OCP\Util::linkToRoute('core_ajax_versions_preview', array('file' => $userFullPath, 'version' => $timestamp));
|
||||
}
|
||||
$versions[$key]['path'] = $filename;
|
||||
$versions[$key]['name'] = $versionedFile;
|
||||
$versions[$key]['size'] = $view->filesize($dir . '/' . $entryName);
|
||||
}
|
||||
$versions[$key]['path'] = $filename;
|
||||
$versions[$key]['name'] = $versionedFile;
|
||||
$versions[$key]['size'] = $file['size'];
|
||||
}
|
||||
}
|
||||
closedir($dirContent);
|
||||
}
|
||||
|
||||
// sort with newest version first
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
*
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../appinfo/app.php';
|
||||
require_once __DIR__ . '/../lib/versions.php';
|
||||
|
||||
/**
|
||||
|
@ -28,6 +29,32 @@ require_once __DIR__ . '/../lib/versions.php';
|
|||
*/
|
||||
class Test_Files_Versioning extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
const TEST_VERSIONS_USER = 'test-versions-user';
|
||||
const USERS_VERSIONS_ROOT = '/test-versions-user/files_versions';
|
||||
|
||||
private $rootView;
|
||||
|
||||
public static function setUpBeforeClass() {
|
||||
// create test user
|
||||
self::loginHelper(self::TEST_VERSIONS_USER, true);
|
||||
}
|
||||
|
||||
public static function tearDownAfterClass() {
|
||||
// cleanup test user
|
||||
\OC_User::deleteUser(self::TEST_VERSIONS_USER);
|
||||
}
|
||||
|
||||
function setUp() {
|
||||
self::loginHelper(self::TEST_VERSIONS_USER);
|
||||
$this->rootView = new \OC\Files\View();
|
||||
if (!$this->rootView->file_exists(self::USERS_VERSIONS_ROOT)) {
|
||||
$this->rootView->mkdir(self::USERS_VERSIONS_ROOT);
|
||||
}
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
$this->rootView->deleteAll(self::USERS_VERSIONS_ROOT);
|
||||
}
|
||||
|
||||
/**
|
||||
* @medium
|
||||
|
@ -176,6 +203,87 @@ class Test_Files_Versioning extends \PHPUnit_Framework_TestCase {
|
|||
);
|
||||
}
|
||||
|
||||
function testRename() {
|
||||
|
||||
\OC\Files\Filesystem::file_put_contents("test.txt", "test file");
|
||||
|
||||
$t1 = time();
|
||||
// second version is two weeks older, this way we make sure that no
|
||||
// version will be expired
|
||||
$t2 = $t1 - 60 * 60 * 24 * 14;
|
||||
|
||||
// create some versions
|
||||
$v1 = self::USERS_VERSIONS_ROOT . '/test.txt.v' . $t1;
|
||||
$v2 = self::USERS_VERSIONS_ROOT . '/test.txt.v' . $t2;
|
||||
$v1Renamed = self::USERS_VERSIONS_ROOT . '/test2.txt.v' . $t1;
|
||||
$v2Renamed = self::USERS_VERSIONS_ROOT . '/test2.txt.v' . $t2;
|
||||
|
||||
$this->rootView->file_put_contents($v1, 'version1');
|
||||
$this->rootView->file_put_contents($v2, 'version2');
|
||||
|
||||
// execute rename hook of versions app
|
||||
\OCA\Files_Versions\Storage::renameOrCopy("test.txt", "test2.txt", 'rename');
|
||||
|
||||
$this->assertFalse($this->rootView->file_exists($v1));
|
||||
$this->assertFalse($this->rootView->file_exists($v2));
|
||||
|
||||
$this->assertTrue($this->rootView->file_exists($v1Renamed));
|
||||
$this->assertTrue($this->rootView->file_exists($v2Renamed));
|
||||
|
||||
//cleanup
|
||||
\OC\Files\Filesystem::unlink('test2.txt');
|
||||
}
|
||||
|
||||
function testCopy() {
|
||||
|
||||
\OC\Files\Filesystem::file_put_contents("test.txt", "test file");
|
||||
|
||||
$t1 = time();
|
||||
// second version is two weeks older, this way we make sure that no
|
||||
// version will be expired
|
||||
$t2 = $t1 - 60 * 60 * 24 * 14;
|
||||
|
||||
// create some versions
|
||||
$v1 = self::USERS_VERSIONS_ROOT . '/test.txt.v' . $t1;
|
||||
$v2 = self::USERS_VERSIONS_ROOT . '/test.txt.v' . $t2;
|
||||
$v1Copied = self::USERS_VERSIONS_ROOT . '/test2.txt.v' . $t1;
|
||||
$v2Copied = self::USERS_VERSIONS_ROOT . '/test2.txt.v' . $t2;
|
||||
|
||||
$this->rootView->file_put_contents($v1, 'version1');
|
||||
$this->rootView->file_put_contents($v2, 'version2');
|
||||
|
||||
// execute copy hook of versions app
|
||||
\OCA\Files_Versions\Storage::renameOrCopy("test.txt", "test2.txt", 'copy');
|
||||
|
||||
$this->assertTrue($this->rootView->file_exists($v1));
|
||||
$this->assertTrue($this->rootView->file_exists($v2));
|
||||
|
||||
$this->assertTrue($this->rootView->file_exists($v1Copied));
|
||||
$this->assertTrue($this->rootView->file_exists($v2Copied));
|
||||
|
||||
//cleanup
|
||||
\OC\Files\Filesystem::unlink('test.txt');
|
||||
\OC\Files\Filesystem::unlink('test2.txt');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
* @param bool $create
|
||||
* @param bool $password
|
||||
*/
|
||||
public static function loginHelper($user, $create = false) {
|
||||
|
||||
if ($create) {
|
||||
\OC_User::createUser($user, $user);
|
||||
}
|
||||
|
||||
\OC_Util::tearDownFS();
|
||||
\OC_User::setUserId('');
|
||||
\OC\Files\Filesystem::tearDown();
|
||||
\OC_User::setUserId($user);
|
||||
\OC_Util::setupFS($user);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// extend the original class to make it possible to test protected methods
|
||||
|
|
Loading…
Reference in New Issue