diff options
70 files changed, 2648 insertions, 354 deletions
diff --git a/apps/files/ajax/list.php b/apps/files/ajax/list.php index 878e4cb2159..d0f1f58f6e6 100644 --- a/apps/files/ajax/list.php +++ b/apps/files/ajax/list.php @@ -10,6 +10,7 @@ OCP\JSON::checkLoggedIn(); // Load the files $dir = isset( $_GET['dir'] ) ? $_GET['dir'] : ''; +$dir = \OC\Files\Filesystem::normalizePath($dir); $doBreadcrumb = isset( $_GET['breadcrumb'] ) ? true : false; $data = array(); diff --git a/apps/files/ajax/upload.php b/apps/files/ajax/upload.php index 6eaa84f07e2..a81b8edb130 100644 --- a/apps/files/ajax/upload.php +++ b/apps/files/ajax/upload.php @@ -7,6 +7,8 @@ OCP\JSON::setContentTypeHeader('text/plain'); // If not, check the login. // If no token is sent along, rely on login only +$allowedPermissions = OCP\PERMISSION_ALL; + $l = OC_L10N::get('files'); if (empty($_POST['dirToken'])) { // The standard case, files are uploaded through logged in users :) @@ -17,6 +19,9 @@ if (empty($_POST['dirToken'])) { die(); } } else { + // return only read permissions for public upload + $allowedPermissions = OCP\PERMISSION_READ; + $linkItem = OCP\Share::getShareByToken($_POST['dirToken']); if ($linkItem === false) { OCP\JSON::error(array('data' => array_merge(array('message' => $l->t('Invalid Token'))))); @@ -117,7 +122,7 @@ if (strpos($dir, '..') === false) { 'originalname' => $files['name'][$i], 'uploadMaxFilesize' => $maxUploadFileSize, 'maxHumanFilesize' => $maxHumanFileSize, - 'permissions' => $meta['permissions'], + 'permissions' => $meta['permissions'] & $allowedPermissions ); } } diff --git a/apps/files/css/files.css b/apps/files/css/files.css index 37a9c7e2251..e16088b3b45 100644 --- a/apps/files/css/files.css +++ b/apps/files/css/files.css @@ -63,6 +63,9 @@ tbody tr { background-color:#fff; height:2.5em; } tbody tr:hover, tbody tr:active, tbody tr.selected { background-color:#f8f8f8; } tbody tr.selected { background-color:#eee; } +#filestable tbody tr.searchresult { + background-color: rgb(240,240,240); +} tbody a { color:#000; } span.extension, span.uploading, td.date { color:#999; } span.extension { text-transform:lowercase; -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=70)"; filter:alpha(opacity=70); opacity:.7; -webkit-transition:opacity 300ms; -moz-transition:opacity 300ms; -o-transition:opacity 300ms; transition:opacity 300ms; } diff --git a/apps/files/index.php b/apps/files/index.php index dd5ea445daf..2d1ae26bba2 100644 --- a/apps/files/index.php +++ b/apps/files/index.php @@ -35,6 +35,7 @@ OCP\Util::addscript('files', 'filelist'); OCP\App::setActiveNavigationEntry('files_index'); // Load the files $dir = isset($_GET['dir']) ? stripslashes($_GET['dir']) : ''; +$dir = \OC\Files\Filesystem::normalizePath($dir); // Redirect if directory does not exist if (!\OC\Files\Filesystem::is_dir($dir . '/')) { header('Location: ' . OCP\Util::getScriptName() . ''); @@ -132,7 +133,7 @@ if ($needUpgrade) { $tmpl = new OCP\Template('files', 'index', 'user'); $tmpl->assign('fileList', $list->fetchPage()); $tmpl->assign('breadcrumb', $breadcrumbNav->fetchPage()); - $tmpl->assign('dir', \OC\Files\Filesystem::normalizePath($dir)); + $tmpl->assign('dir', $dir); $tmpl->assign('isCreatable', \OC\Files\Filesystem::isCreatable($dir . '/')); $tmpl->assign('permissions', $permissions); $tmpl->assign('files', $files); diff --git a/apps/files/js/file-upload.js b/apps/files/js/file-upload.js index 402dd5075e7..42f08112619 100644 --- a/apps/files/js/file-upload.js +++ b/apps/files/js/file-upload.js @@ -260,6 +260,7 @@ $(document).ready(function() { getMimeIcon(result.data.mime,function(path){ tr.find('td.filename').attr('style','background-image:url('+path+')'); }); + FileActions.display(tr.find('td.filename'), true); } else { OC.dialogs.alert(result.data.message, t('core', 'Error')); } @@ -324,6 +325,7 @@ $(document).ready(function() { getMimeIcon(mime,function(path){ tr.find('td.filename').attr('style','background-image:url('+path+')'); }); + FileActions.display(tr.find('td.filename'), true); }); eventSource.listen('error',function(error){ $('#uploadprogressbar').fadeOut(); diff --git a/apps/files/js/fileactions.js b/apps/files/js/fileactions.js index 5976bb49912..dbbc57c3b6a 100644 --- a/apps/files/js/fileactions.js +++ b/apps/files/js/fileactions.js @@ -61,7 +61,13 @@ var FileActions = { var actions = this.get(mime, type, permissions); return actions[name]; }, - display: function (parent) { + /** + * Display file actions for the given element + * @param parent "td" element of the file for which to display actions + * @param triggerEvent if true, triggers the fileActionsReady on the file + * list afterwards (false by default) + */ + display: function (parent, triggerEvent) { FileActions.currentFile = parent; var actions = FileActions.get(FileActions.getCurrentMimeType(), FileActions.getCurrentType(), FileActions.getCurrentPermissions()); var file = FileActions.getCurrentFile(); @@ -140,6 +146,10 @@ var FileActions = { element.on('click', {a: null, elem: parent, actionFunc: actions['Delete']}, actionHandler); parent.parent().children().last().append(element); } + + if (triggerEvent){ + $('#fileList').trigger(jQuery.Event("fileActionsReady")); + } }, getCurrentFile: function () { return FileActions.currentFile.parent().attr('data-file'); diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js index 40a4ae95c98..7863903fa19 100644 --- a/apps/files/js/filelist.js +++ b/apps/files/js/filelist.js @@ -130,7 +130,7 @@ var FileList={ if (hidden) { tr.hide(); } - FileActions.display(tr.find('td.filename')); + FileActions.display(tr.find('td.filename'), true); return tr; }, refresh:function(data) { @@ -367,6 +367,37 @@ var FileList={ }); } }); + }, + scrollTo:function(file) { + //scroll to and highlight preselected file + var $scrolltorow = $('tr[data-file="'+file+'"]'); + if ($scrolltorow.exists()) { + $scrolltorow.addClass('searchresult'); + $(window).scrollTop($scrolltorow.position().top); + //remove highlight when hovered over + $scrolltorow.one('hover', function() { + $scrolltorow.removeClass('searchresult'); + }); + } + }, + filter:function(query) { + $('#fileList tr:not(.summary)').each(function(i,e) { + if ($(e).data('file').toString().toLowerCase().indexOf(query.toLowerCase()) !== -1) { + $(e).addClass("searchresult"); + } else { + $(e).removeClass("searchresult"); + } + }); + //do not use scrollto to prevent removing searchresult css class + var first = $('#fileList tr.searchresult').first(); + if (first.exists()) { + $(window).scrollTop(first.position().top); + } + }, + unfilter:function() { + $('#fileList tr.searchresult').each(function(i,e) { + $(e).removeClass("searchresult"); + }); } }; @@ -473,7 +504,7 @@ $(document).ready(function(){ data.context.attr('data-permissions', file.permissions); data.context.data('permissions', file.permissions); } - FileActions.display(data.context.find('td.filename')); + FileActions.display(data.context.find('td.filename'), true); if (FileList.loadingDone) { FileList.loadingDone(file.name, file.id); } diff --git a/apps/files/js/files.js b/apps/files/js/files.js index 7581ff3ec56..d8aca4dd26a 100644 --- a/apps/files/js/files.js +++ b/apps/files/js/files.js @@ -546,6 +546,11 @@ $(document).ready(function() { } }); } + + //scroll to and highlight preselected file + if (getURLParameter('scrollto')) { + FileList.scrollTo(getURLParameter('scrollto')); + } }); function scanFiles(force, dir, users){ @@ -666,7 +671,7 @@ var folderDropOptions={ return false; } - var target=$.trim($(this).find('.nametext').text()); + var target = $(this).closest('tr').data('file'); var files = ui.helper.find('tr'); $(files).each(function(i,row){ diff --git a/apps/files_encryption/appinfo/info.xml b/apps/files_encryption/appinfo/info.xml index 46f1375c987..9d495916d26 100644 --- a/apps/files_encryption/appinfo/info.xml +++ b/apps/files_encryption/appinfo/info.xml @@ -7,6 +7,7 @@ <author>Sam Tuke, Bjoern Schiessle, Florin Peter</author> <require>4</require> <shipped>true</shipped> + <rememberlogin>false</rememberlogin> <types> <filesystem/> </types> diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index 1103577ac64..90a327fb988 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -38,6 +38,11 @@ class Hooks { * @note This method should never be called for users using client side encryption
*/
public static function login($params) {
+
+ if (\OCP\App::isEnabled('files_encryption') === false) {
+ return true;
+ }
+
$l = new \OC_L10N('files_encryption');
$view = new \OC_FilesystemView('/');
@@ -104,8 +109,6 @@ class Hooks { }
// Encrypt existing user files:
- // This serves to upgrade old versions of the encryption
- // app (see appinfo/spec.txt)
if (
$util->encryptAll('/' . $params['uid'] . '/' . 'files', $session->getLegacyKey(), $params['password'])
) {
@@ -131,11 +134,12 @@ class Hooks { * @note This method should never be called for users using client side encryption
*/
public static function postCreateUser($params) {
- $view = new \OC_FilesystemView('/');
- $util = new Util($view, $params['uid']);
-
- Helper::setupUser($util, $params['password']);
+ if (\OCP\App::isEnabled('files_encryption')) {
+ $view = new \OC_FilesystemView('/');
+ $util = new Util($view, $params['uid']);
+ Helper::setupUser($util, $params['password']);
+ }
}
/**
@@ -143,26 +147,31 @@ class Hooks { * @note This method should never be called for users using client side encryption
*/
public static function postDeleteUser($params) {
- $view = new \OC_FilesystemView('/');
- // cleanup public key
- $publicKey = '/public-keys/' . $params['uid'] . '.public.key';
+ if (\OCP\App::isEnabled('files_encryption')) {
+ $view = new \OC_FilesystemView('/');
- // Disable encryption proxy to prevent recursive calls
- $proxyStatus = \OC_FileProxy::$enabled;
- \OC_FileProxy::$enabled = false;
+ // cleanup public key
+ $publicKey = '/public-keys/' . $params['uid'] . '.public.key';
- $view->unlink($publicKey);
+ // Disable encryption proxy to prevent recursive calls
+ $proxyStatus = \OC_FileProxy::$enabled;
+ \OC_FileProxy::$enabled = false;
- \OC_FileProxy::$enabled = $proxyStatus;
+ $view->unlink($publicKey);
+
+ \OC_FileProxy::$enabled = $proxyStatus;
+ }
}
/**
* @brief If the password can't be changed within ownCloud, than update the key password in advance.
*/
public static function preSetPassphrase($params) {
- if ( ! \OC_User::canUserChangePassword($params['uid']) ) {
- self::setPassphrase($params);
+ if (\OCP\App::isEnabled('files_encryption')) {
+ if ( ! \OC_User::canUserChangePassword($params['uid']) ) {
+ self::setPassphrase($params);
+ }
}
}
@@ -172,6 +181,10 @@ class Hooks { */
public static function setPassphrase($params) {
+ if (\OCP\App::isEnabled('files_encryption') === false) {
+ return true;
+ }
+
// Only attempt to change passphrase if server-side encryption
// is in use (client-side encryption does not have access to
// the necessary keys)
@@ -242,6 +255,10 @@ class Hooks { */
public static function preShared($params) {
+ if (\OCP\App::isEnabled('files_encryption') === false) {
+ return true;
+ }
+
$l = new \OC_L10N('files_encryption');
$users = array();
$view = new \OC\Files\View('/public-keys/');
@@ -274,6 +291,10 @@ class Hooks { */
public static function postShared($params) {
+ if (\OCP\App::isEnabled('files_encryption') === false) {
+ return true;
+ }
+
// NOTE: $params has keys:
// [itemType] => file
// itemSource -> int, filecache file ID
@@ -380,6 +401,10 @@ class Hooks { */
public static function postUnshare($params) {
+ if (\OCP\App::isEnabled('files_encryption') === false) {
+ return true;
+ }
+
// NOTE: $params has keys:
// [itemType] => file
// [itemSource] => 13
@@ -468,6 +493,11 @@ class Hooks { * of the stored versions along the actual file
*/
public static function postRename($params) {
+
+ if (\OCP\App::isEnabled('files_encryption') === false) {
+ return true;
+ }
+
// Disable encryption proxy to prevent recursive calls
$proxyStatus = \OC_FileProxy::$enabled;
\OC_FileProxy::$enabled = false;
@@ -560,9 +590,11 @@ class Hooks { * @param array $params contains the app ID
*/
public static function preDisable($params) {
- if ($params['app'] === 'files_encryption') {
- $query = \OC_DB::prepare('UPDATE `*PREFIX*encryption` SET `migration_status`=0');
- $query->execute();
+ if (\OCP\App::isEnabled('files_encryption')) {
+ if ($params['app'] === 'files_encryption') {
+ $query = \OC_DB::prepare('UPDATE `*PREFIX*encryption` SET `migration_status`=0');
+ $query->execute();
+ }
}
}
diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 0304d1134c4..7143fcff0f6 100755 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -40,11 +40,14 @@ class Keymanager { public static function getPrivateKey(\OC_FilesystemView $view, $user) { $path = '/' . $user . '/' . 'files_encryption' . '/' . $user . '.private.key'; + $key = false; $proxyStatus = \OC_FileProxy::$enabled; \OC_FileProxy::$enabled = false; - $key = $view->file_get_contents($path); + if ($view->file_exists($path)) { + $key = $view->file_get_contents($path); + } \OC_FileProxy::$enabled = $proxyStatus; @@ -569,4 +572,4 @@ class Keymanager { return $targetPath; } -}
\ No newline at end of file +} diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index ee6458b2fd2..25005e9c41f 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -38,8 +38,6 @@ class Proxy extends \OC_FileProxy { private static $blackList = null; //mimetypes blacklisted from encryption - private static $enableEncryption = null; - /** * Check if a file requires encryption * @param string $path @@ -49,47 +47,22 @@ class Proxy extends \OC_FileProxy { */ private static function shouldEncrypt($path) { - if (is_null(self::$enableEncryption)) { - - if ( - \OCP\Config::getAppValue('files_encryption', 'enable_encryption', 'true') === 'true' - && Crypt::mode() === 'server' - ) { - - self::$enableEncryption = true; - - } else { - - self::$enableEncryption = false; - - } - - } - - if (!self::$enableEncryption) { - + if (\OCP\App::isEnabled('files_encryption') === false || Crypt::mode() !== 'server') { return false; - } if (is_null(self::$blackList)) { - self::$blackList = explode(',', \OCP\Config::getAppValue('files_encryption', 'type_blacklist', '')); - } if (Crypt::isCatfileContent($path)) { - return true; - } $extension = substr($path, strrpos($path, '.') + 1); if (array_search($extension, self::$blackList) === false) { - return true; - } return false; @@ -203,7 +176,7 @@ class Proxy extends \OC_FileProxy { */ public function preUnlink($path) { - // let the trashbin handle this + // let the trashbin handle this if (\OCP\App::isEnabled('files_trashbin')) { return true; } @@ -294,7 +267,7 @@ class Proxy extends \OC_FileProxy { // Close the original encrypted file fclose($result); - // Open the file using the crypto stream wrapper + // Open the file using the crypto stream wrapper // protocol and let it do the decryption work instead $result = fopen('crypt://' . $relativePath, $meta['mode']); @@ -321,7 +294,7 @@ class Proxy extends \OC_FileProxy { public function postGetFileInfo($path, $data) { // if path is a folder do nothing - if (is_array($data) && array_key_exists('size', $data)) { + if (\OCP\App::isEnabled('files_encryption') && is_array($data) && array_key_exists('size', $data)) { // Disable encryption proxy to prevent recursive calls $proxyStatus = \OC_FileProxy::$enabled; @@ -346,6 +319,16 @@ class Proxy extends \OC_FileProxy { $view = new \OC_FilesystemView('/'); + $userId = \OCP\User::getUser(); + $util = new Util($view, $userId); + + // if encryption is no longer enabled or if the files aren't migrated yet + // we return the default file size + if(!\OCP\App::isEnabled('files_encryption') || + $util->getMigrationStatus() !== Util::MIGRATION_COMPLETED) { + return $size; + } + // if path is a folder do nothing if ($view->is_dir($path)) { return $size; @@ -367,6 +350,15 @@ class Proxy extends \OC_FileProxy { // if file is encrypted return real file size if (is_array($fileInfo) && $fileInfo['encrypted'] === true) { + // try to fix unencrypted file size if it doesn't look plausible + if ((int)$fileInfo['size'] > 0 && (int)$fileInfo['unencrypted_size'] === 0) { + $fixSize = $util->getFileSize($path); + $fileInfo['unencrypted_size'] = $fixSize; + // put file info if not .part file + if (!Keymanager::isPartialFilePath($relativePath)) { + $view->putFileInfo($path, $fileInfo); + } + } $size = $fileInfo['unencrypted_size']; } else { // self healing if file was removed from file cache @@ -374,8 +366,6 @@ class Proxy extends \OC_FileProxy { $fileInfo = array(); } - $userId = \OCP\User::getUser(); - $util = new Util($view, $userId); $fixSize = $util->getFileSize($path); if ($fixSize > 0) { $size = $fixSize; diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php index 08024b8090d..80ff87370ab 100644 --- a/apps/files_encryption/lib/stream.php +++ b/apps/files_encryption/lib/stream.php @@ -490,9 +490,10 @@ class Stream { // Get all users sharing the file includes current user $uniqueUserIds = $util->getSharingUsersArray($sharingEnabled, $this->relPath, $this->userId); + $checkedUserIds = $util->filterShareReadyUsers($uniqueUserIds); // Fetch public keys for all sharing users - $publicKeys = Keymanager::getPublicKeys($this->rootView, $uniqueUserIds); + $publicKeys = Keymanager::getPublicKeys($this->rootView, $checkedUserIds['ready']); // Encrypt enc key for all sharing users $this->encKeyfiles = Crypt::multiKeyEncrypt($this->plainKey, $publicKeys); diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index a2aa305b64c..121b1c4f573 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -587,11 +587,18 @@ class Util { ) { // get the size from filesystem - $fullPath = $this->view->getLocalFile($path); $size = $this->view->filesize($path); + // fast path, else the calculation for $lastChunkNr is bogus + if ($size === 0) { + \OC_FileProxy::$enabled = $proxyStatus; + return 0; + } + // calculate last chunk nr - $lastChunkNr = floor($size / 8192); + // next highest is end of chunks, one subtracted is last one + // we have to read the last chunk, we can't just calculate it (because of padding etc) + $lastChunkNr = ceil($size/ 8192) - 1; $lastChunkSize = $size - ($lastChunkNr * 8192); // open stream diff --git a/apps/files_encryption/settings-admin.php b/apps/files_encryption/settings-admin.php index 53676058982..9ad9bfb8877 100644 --- a/apps/files_encryption/settings-admin.php +++ b/apps/files_encryption/settings-admin.php @@ -11,9 +11,7 @@ $tmpl = new OCP\Template('files_encryption', 'settings-admin'); // Check if an adminRecovery account is enabled for recovering files after lost pwd -$view = new OC_FilesystemView(''); - -$recoveryAdminEnabled = OC_Appconfig::getValue('files_encryption', 'recoveryAdminEnabled'); +$recoveryAdminEnabled = OC_Appconfig::getValue('files_encryption', 'recoveryAdminEnabled', '0'); $tmpl->assign('recoveryEnabled', $recoveryAdminEnabled); diff --git a/apps/files_encryption/tests/util.php b/apps/files_encryption/tests/util.php index bf45b9c1d34..acd3ff810c6 100755 --- a/apps/files_encryption/tests/util.php +++ b/apps/files_encryption/tests/util.php @@ -231,6 +231,34 @@ class Test_Encryption_Util extends \PHPUnit_Framework_TestCase { $this->view->unlink($this->userId . '/files/' . $filename); } + /** + * @brief Test that data that is read by the crypto stream wrapper + */ + function testGetFileSize() { + \Test_Encryption_Util::loginHelper(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_USER1); + + $filename = 'tmp-' . time(); + $externalFilename = '/' . $this->userId . '/files/' . $filename; + + // Test for 0 byte files + $problematicFileSizeData = ""; + $cryptedFile = $this->view->file_put_contents($externalFilename, $problematicFileSizeData); + $this->assertTrue(is_int($cryptedFile)); + $this->assertEquals($this->util->getFileSize($externalFilename), 0); + $decrypt = $this->view->file_get_contents($externalFilename); + $this->assertEquals($problematicFileSizeData, $decrypt); + $this->view->unlink($this->userId . '/files/' . $filename); + + // Test a file with 18377 bytes as in https://github.com/owncloud/mirall/issues/1009 + $problematicFileSizeData = str_pad("", 18377, "abc"); + $cryptedFile = $this->view->file_put_contents($externalFilename, $problematicFileSizeData); + $this->assertTrue(is_int($cryptedFile)); + $this->assertEquals($this->util->getFileSize($externalFilename), 18377); + $decrypt = $this->view->file_get_contents($externalFilename); + $this->assertEquals($problematicFileSizeData, $decrypt); + $this->view->unlink($this->userId . '/files/' . $filename); + } + function testIsSharedPath() { $sharedPath = '/user1/files/Shared/test'; $path = '/user1/files/test'; diff --git a/apps/files_external/lib/smb.php b/apps/files_external/lib/smb.php index fede9fe392f..fb6259930ba 100644 --- a/apps/files_external/lib/smb.php +++ b/apps/files_external/lib/smb.php @@ -47,8 +47,13 @@ class SMB extends \OC\Files\Storage\StreamWrapper{ public function constructUrl($path) { if (substr($path, -1)=='/') { - $path=substr($path, 0, -1); + $path = substr($path, 0, -1); } + if (substr($path, 0, 1)=='/') { + $path = substr($path, 1); + } + // remove trailing dots which some versions of samba don't seem to like + $path = rtrim($path, '.'); $path = urlencode($path); $user = urlencode($this->user); $pass = urlencode($this->password); @@ -67,6 +72,18 @@ class SMB extends \OC\Files\Storage\StreamWrapper{ } /** + * Unlinks file + * @param string @path + */ + public function unlink($path) { + unlink($this->constructUrl($path)); + clearstatcache(); + // smb4php still returns false even on success so + // check here whether file was really deleted + return !file_exists($path); + } + + /** * check if a file or folder has been updated since $time * @param string $path * @param int $time diff --git a/apps/files_external/lib/streamwrapper.php b/apps/files_external/lib/streamwrapper.php index beb4ec5605f..4a63dfb6e02 100644 --- a/apps/files_external/lib/streamwrapper.php +++ b/apps/files_external/lib/streamwrapper.php @@ -8,7 +8,7 @@ namespace OC\Files\Storage; -abstract class StreamWrapper extends Common{ +abstract class StreamWrapper extends Common { abstract public function constructUrl($path); public function mkdir($path) { @@ -16,7 +16,15 @@ abstract class StreamWrapper extends Common{ } public function rmdir($path) { - if($this->file_exists($path)) { + if ($this->file_exists($path)) { + $dh = $this->opendir($path); + while (($file = readdir($dh)) !== false) { + if ($this->is_dir($path . '/' . $file)) { + $this->rmdir($path . '/' . $file); + } else { + $this->unlink($path . '/' . $file); + } + } $success = rmdir($this->constructUrl($path)); clearstatcache(); return $success; @@ -34,11 +42,11 @@ abstract class StreamWrapper extends Common{ } public function isReadable($path) { - return true;//not properly supported + return true; //not properly supported } public function isUpdatable($path) { - return true;//not properly supported + return true; //not properly supported } public function file_exists($path) { @@ -55,15 +63,19 @@ abstract class StreamWrapper extends Common{ return fopen($this->constructUrl($path), $mode); } - public function touch($path, $mtime=null) { - if(is_null($mtime)) { - $fh = $this->fopen($path, 'a'); - fwrite($fh, ''); - fclose($fh); - - return true; + public function touch($path, $mtime = null) { + if ($this->file_exists($path)) { + if (is_null($mtime)) { + $fh = $this->fopen($path, 'a'); + fwrite($fh, ''); + fclose($fh); + + return true; + } else { + return false; //not supported + } } else { - return false;//not supported + $this->file_put_contents($path, ''); } } diff --git a/apps/files_external/tests/smbfunctions.php b/apps/files_external/tests/smbfunctions.php new file mode 100644 index 00000000000..749906d0136 --- /dev/null +++ b/apps/files_external/tests/smbfunctions.php @@ -0,0 +1,41 @@ +<?php +/** + * Copyright (c) 2013 Vincent Petry <pvince81@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Files\Storage; + +class SMBFunctions extends \PHPUnit_Framework_TestCase { + + public function setUp() { + $id = uniqid(); + // dummy config + $this->config = array( + 'run'=>false, + 'user'=>'test', + 'password'=>'testpassword', + 'host'=>'smbhost', + 'share'=>'/sharename', + 'root'=>'/rootdir/', + ); + + $this->instance = new \OC\Files\Storage\SMB($this->config); + } + + public function tearDown() { + } + + public function testGetId() { + $this->assertEquals('smb::test@smbhost//sharename//rootdir/', $this->instance->getId()); + } + + public function testConstructUrl() { + $this->assertEquals("smb://test:testpassword@smbhost/sharename/rootdir/abc", $this->instance->constructUrl('/abc')); + $this->assertEquals("smb://test:testpassword@smbhost/sharename/rootdir/abc", $this->instance->constructUrl('/abc/')); + $this->assertEquals("smb://test:testpassword@smbhost/sharename/rootdir/abc%2F", $this->instance->constructUrl('/abc/.')); + $this->assertEquals("smb://test:testpassword@smbhost/sharename/rootdir/abc%2Fdef", $this->instance->constructUrl('/abc/def')); + } +} diff --git a/apps/files_sharing/appinfo/app.php b/apps/files_sharing/appinfo/app.php index 9363a5431fa..5f73a0767eb 100644 --- a/apps/files_sharing/appinfo/app.php +++ b/apps/files_sharing/appinfo/app.php @@ -7,6 +7,7 @@ OC::$CLASSPATH['OC\Files\Cache\Shared_Cache'] = 'files_sharing/lib/cache.php'; OC::$CLASSPATH['OC\Files\Cache\Shared_Permissions'] = 'files_sharing/lib/permissions.php'; OC::$CLASSPATH['OC\Files\Cache\Shared_Updater'] = 'files_sharing/lib/updater.php'; OC::$CLASSPATH['OC\Files\Cache\Shared_Watcher'] = 'files_sharing/lib/watcher.php'; +OC::$CLASSPATH['OCA\Files\Share\Api'] = 'files_sharing/lib/api.php'; OCP\Util::connectHook('OC_Filesystem', 'setup', '\OC\Files\Storage\Shared', 'setup'); OCP\Share::registerBackend('file', 'OC_Share_Backend_File'); OCP\Share::registerBackend('folder', 'OC_Share_Backend_Folder', 'file'); diff --git a/apps/files_sharing/appinfo/routes.php b/apps/files_sharing/appinfo/routes.php new file mode 100644 index 00000000000..1936687e5e4 --- /dev/null +++ b/apps/files_sharing/appinfo/routes.php @@ -0,0 +1,27 @@ +<?php +// OCS API + +OC_API::register('get', + '/apps/files_sharing/api/v1/shares', + array('\OCA\Files\Share\Api', 'getAllShares'), + 'files_sharing'); + +OC_API::register('post', + '/apps/files_sharing/api/v1/shares', + array('\OCA\Files\Share\Api', 'createShare'), + 'files_sharing'); + +OC_API::register('get', + '/apps/files_sharing/api/v1/shares/{id}', + array('\OCA\Files\Share\Api', 'getShare'), + 'files_sharing'); + +OC_API::register('put', + '/apps/files_sharing/api/v1/shares/{id}', + array('\OCA\Files\Share\Api', 'updateShare'), + 'files_sharing'); + +OC_API::register('delete', + '/apps/files_sharing/api/v1/shares/{id}', + array('\OCA\Files\Share\Api', 'deleteShare'), + 'files_sharing'); diff --git a/apps/files_sharing/appinfo/update.php b/apps/files_sharing/appinfo/update.php index 48e41e93048..0d827da28ea 100644 --- a/apps/files_sharing/appinfo/update.php +++ b/apps/files_sharing/appinfo/update.php @@ -68,11 +68,21 @@ if (version_compare($installedVersion, '0.3', '<')) { // $query = OCP\DB::prepare('DROP TABLE `*PREFIX*sharing`'); // $query->execute(); } -if (version_compare($installedVersion, '0.3.3', '<')) { - OC_User::useBackend(new OC_User_Database()); - OC_App::loadApps(array('authentication')); - $users = OC_User::getUsers(); - foreach ($users as $user) { -// OC_FileCache::delete('Shared', '/'.$user.'/files/'); + +// clean up oc_share table from files which are no longer exists +if (version_compare($installedVersion, '0.3.5', '<')) { + + // get all shares where the original file no longer exists + $findShares = \OC_DB::prepare('SELECT `file_source` FROM `*PREFIX*share` LEFT JOIN `*PREFIX*filecache` ON `file_source` = `*PREFIX*filecache`.`fileid` WHERE `*PREFIX*filecache`.`fileid` IS NULL AND `*PREFIX*share`.`item_type` IN (\'file\', \'folder\')'); + $sharesFound = $findShares->execute(array())->fetchAll(); + + // delete those shares from the oc_share table + if (is_array($sharesFound) && !empty($sharesFound)) { + $delArray = array(); + foreach ($sharesFound as $share) { + $delArray[] = $share['file_source']; + } + $removeShares = \OC_DB::prepare('DELETE FROM `*PREFIX*share` WHERE `file_source` IN (?)'); + $result = $removeShares->execute(array(implode(',', $delArray))); } } diff --git a/apps/files_sharing/appinfo/version b/apps/files_sharing/appinfo/version index 87a0871112f..09e9157034c 100644 --- a/apps/files_sharing/appinfo/version +++ b/apps/files_sharing/appinfo/version @@ -1 +1 @@ -0.3.3
\ No newline at end of file +0.3.5
\ No newline at end of file diff --git a/apps/files_sharing/js/share.js b/apps/files_sharing/js/share.js index b2efafde4e7..68f6f3ba76f 100644 --- a/apps/files_sharing/js/share.js +++ b/apps/files_sharing/js/share.js @@ -1,11 +1,19 @@ $(document).ready(function() { - var disableSharing = $('#disableSharing').data('status'); + var disableSharing = $('#disableSharing').data('status'), + sharesLoaded = false; if (typeof OC.Share !== 'undefined' && typeof FileActions !== 'undefined' && !disableSharing) { - - $('#fileList').one('fileActionsReady',function(){ - OC.Share.loadIcons('file'); + $('#fileList').on('fileActionsReady',function(){ + if (!sharesLoaded){ + OC.Share.loadIcons('file'); + // assume that we got all shares, so switching directories + // will not invalidate that list + sharesLoaded = true; + } + else{ + OC.Share.updateIcons('file'); + } }); FileActions.register('all', 'Share', OC.PERMISSION_READ, OC.imagePath('core', 'actions/share'), function(filename) { @@ -38,4 +46,4 @@ $(document).ready(function() { } }); } -});
\ No newline at end of file +}); diff --git a/apps/files_sharing/lib/api.php b/apps/files_sharing/lib/api.php new file mode 100644 index 00000000000..1bd0003ce07 --- /dev/null +++ b/apps/files_sharing/lib/api.php @@ -0,0 +1,531 @@ +<?php +/** + * ownCloud + * + * @author Bjoern Schiessle + * @copyright 2013 Bjoern Schiessle schiessle@owncloud.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Files\Share; + +class Api { + + /** + * @brief get all shares + * + * @param array $params option 'file' to limit the result to a specific file/folder + * @return \OC_OCS_Result share information + */ + public static function getAllShares($params) { + // if a file is specified, get the share for this file + if (isset($_GET['path'])) { + $params['itemSource'] = self::getFileId($_GET['path']); + $params['path'] = $_GET['path']; + $params['itemType'] = self::getItemType($_GET['path']); + + if ( isset($_GET['reshares']) && $_GET['reshares'] !== 'false' ) { + $params['reshares'] = true; + } else { + $params['reshares'] = false; + } + + if (isset($_GET['subfiles']) && $_GET['subfiles'] !== 'false') { + return self::getSharesFromFolder($params); + } + return self::collectShares($params); + } + + $share = \OCP\Share::getItemShared('file', null); + + if ($share === false) { + return new \OC_OCS_Result(null, 404, 'could not get shares'); + } else { + return new \OC_OCS_Result($share); + } + + } + + /** + * @brief get share information for a given share + * + * @param array $params which contains a 'id' + * @return \OC_OCS_Result share information + */ + public static function getShare($params) { + + $s = self::getShareFromId($params['id']); + $params['itemSource'] = $s['item_source']; + $params['itemType'] = $s['item_type']; + $params['specificShare'] = true; + + return self::collectShares($params); + } + + /** + * @brief collect all share information, either of a specific share or all + * shares for a given path + * @param array $params + * @return \OC_OCS_Result + */ + private static function collectShares($params) { + + $itemSource = $params['itemSource']; + $itemType = $params['itemType']; + $getSpecificShare = isset($params['specificShare']) ? $params['specificShare'] : false; + + if ($itemSource !== null) { + $shares = \OCP\Share::getItemShared($itemType, $itemSource); + $receivedFrom = \OCP\Share::getItemSharedWithBySource($itemType, $itemSource); + // if a specific share was specified only return this one + if ($getSpecificShare === true) { + foreach ($shares as $share) { + if ($share['id'] === (int) $params['id']) { + $shares = array('element' => $share); + break; + } + } + } + + // include also reshares in the lists. This means that the result + // will contain every user with access to the file. + if (isset($params['reshares']) && $params['reshares'] === true) { + $shares = self::addReshares($shares, $itemSource); + } + + if ($receivedFrom) { + $shares['received_from'] = $receivedFrom['uid_owner']; + $shares['received_from_displayname'] = \OCP\User::getDisplayName($receivedFrom['uid_owner']); + } + } else { + $shares = null; + } + + if ($shares === null || empty($shares)) { + return new \OC_OCS_Result(null, 404, 'share doesn\'t exist'); + } else { + return new \OC_OCS_Result($shares); + } + } + + /** + * @brief add reshares to a array of shares + * @param array $shares array of shares + * @param int $itemSource item source ID + * @return array new shares array which includes reshares + */ + private static function addReshares($shares, $itemSource) { + + // if there are no shares than there are also no reshares + $firstShare = reset($shares); + if ($firstShare) { + $path = $firstShare['path']; + } else { + return $shares; + } + + $select = '`*PREFIX*share`.`id`, `item_type`, `*PREFIX*share`.`parent`, `share_type`, `share_with`, `file_source`, `path` , `permissions`, `stime`, `expiration`, `token`, `storage`'; + $getReshares = \OC_DB::prepare('SELECT ' . $select . ' FROM `*PREFIX*share` INNER JOIN `*PREFIX*filecache` ON `file_source` = `*PREFIX*filecache`.`fileid` WHERE `*PREFIX*share`.`file_source` = ? AND `*PREFIX*share`.`item_type` IN (\'file\', \'folder\') AND `uid_owner` != ?'); + $reshares = $getReshares->execute(array($itemSource, \OCP\User::getUser()))->fetchAll(); + + foreach ($reshares as $key => $reshare) { + if (isset($reshare['share_with']) && $reshare['share_with'] !== '') { + $reshares[$key]['share_with_displayname'] = \OCP\User::getDisplayName($reshare['share_with']); + } + // add correct path to the result + $reshares[$key]['path'] = $path; + } + + return array_merge($shares, $reshares); + } + + /** + * @brief get share from all files in a given folder (non-recursive) + * @param array $params contains 'path' to the folder + * @return \OC_OCS_Result + */ + private static function getSharesFromFolder($params) { + $path = $params['path']; + $view = new \OC\Files\View('/'.\OCP\User::getUser().'/files'); + + if(!$view->is_dir($path)) { + return new \OC_OCS_Result(null, 404, "not a directory"); + } + + $content = $view->getDirectoryContent($path); + + $result = array(); + foreach ($content as $file) { + // workaround because folders are named 'dir' in this context + $itemType = $file['type'] === 'file' ? 'file' : 'folder'; + $share = \OCP\Share::getItemShared($itemType, $file['fileid']); + $receivedFrom = \OCP\Share::getItemSharedWithBySource($itemType, $file['fileid']); + if ($receivedFrom) { + $share['received_from'] = $receivedFrom['uid_owner']; + $share['received_from_displayname'] = \OCP\User::getDisplayName($receivedFrom['uid_owner']); + } + if ($share) { + $share['filename'] = $file['name']; + $result[] = $share; + } + } + + return new \OC_OCS_Result($result); + } + + /** + * @breif create a new share + * @param array $params + * @return \OC_OCS_Result + */ + public static function createShare($params) { + + $path = isset($_POST['path']) ? $_POST['path'] : null; + + if($path === null) { + return new \OC_OCS_Result(null, 400, "please specify a file or folder path"); + } + $itemSource = self::getFileId($path); + $itemType = self::getItemType($path); + + if($itemSource === null) { + return new \OC_OCS_Result(null, 404, "wrong path, file/folder doesn't exist."); + } + + $shareWith = isset($_POST['shareWith']) ? $_POST['shareWith'] : null; + $shareType = isset($_POST['shareType']) ? (int)$_POST['shareType'] : null; + + switch($shareType) { + case \OCP\Share::SHARE_TYPE_USER: + $permissions = isset($_POST['permissions']) ? (int)$_POST['permissions'] : 31; + break; + case \OCP\Share::SHARE_TYPE_GROUP: + $permissions = isset($_POST['permissions']) ? (int)$_POST['permissions'] : 31; + break; + case \OCP\Share::SHARE_TYPE_LINK: + //allow password protection + $shareWith = isset($_POST['password']) ? $_POST['password'] : null; + //check public link share + $publicUploadEnabled = \OC_Appconfig::getValue('core', 'shareapi_allow_public_upload', 'yes'); + $encryptionEnabled = \OC_App::isEnabled('files_encryption'); + if(isset($_POST['publicUpload']) && + ($encryptionEnabled || $publicUploadEnabled !== 'yes')) { + return new \OC_OCS_Result(null, 404, "public upload disabled by the administrator"); + } + $publicUpload = isset($_POST['publicUpload']) ? $_POST['publicUpload'] : 'false'; + // read, create, update (7) if public upload is enabled or + // read (1) if public upload is disabled + $permissions = $publicUpload === 'true' ? 7 : 1; + break; + default: + return new \OC_OCS_Result(null, 404, "unknown share type"); + } + + try { + $token = \OCP\Share::shareItem( + $itemType, + $itemSource, + $shareType, + $shareWith, + $permissions + ); + } catch (\Exception $e) { + return new \OC_OCS_Result(null, 404, $e->getMessage()); + } + + if ($token) { + $data = array(); + $data['id'] = 'unknown'; + $shares = \OCP\Share::getItemShared($itemType, $itemSource); + if(is_string($token)) { //public link share + foreach ($shares as $share) { + if ($share['token'] === $token) { + $data['id'] = $share['id']; + break; + } + } + $url = \OCP\Util::linkToPublic('files&t='.$token); + $data['url'] = $url; // '&' gets encoded to $amp; + $data['token'] = $token; + + } else { + foreach ($shares as $share) { + if ($share['share_with'] === $shareWith && $share['share_type'] === $shareType) { + $data['id'] = $share['id']; + break; + } + } + } + return new \OC_OCS_Result($data); + } else { + return new \OC_OCS_Result(null, 404, "couldn't share file"); + } + } + + /** + * update shares, e.g. password, permissions, etc + * @param array $params shareId 'id' and the parameter we want to update + * currently supported: permissions, password, publicUpload + * @return \OC_OCS_Result + */ + public static function updateShare($params) { + + $share = self::getShareFromId($params['id']); + $itemSource = isset($share['item_source']) ? $share['item_source'] : null; + + if($itemSource === null) { + return new \OC_OCS_Result(null, 404, "wrong share Id, share doesn't exist."); + } + + try { + if(isset($params['_put']['permissions'])) { + return self::updatePermissions($share, $params); + } elseif (isset($params['_put']['password'])) { + return self::updatePassword($share, $params); + } elseif (isset($params['_put']['publicUpload'])) { + return self::updatePublicUpload($share, $params); + } + } catch (\Exception $e) { + return new \OC_OCS_Result(null, 400, $e->getMessage()); + } + + return new \OC_OCS_Result(null, 400, "Wrong or no update parameter given"); + + } + + /** + * @brief update permissions for a share + * @param array $share information about the share + * @param array $params contains 'permissions' + * @return \OC_OCS_Result + */ + private static function updatePermissions($share, $params) { + + $itemSource = $share['item_source']; + $itemType = $share['item_type']; + $shareWith = $share['share_with']; + $shareType = $share['share_type']; + $permissions = isset($params['_put']['permissions']) ? (int)$params['_put']['permissions'] : null; + + $publicUploadStatus = \OC_Appconfig::getValue('core', 'shareapi_allow_public_upload', 'yes'); + $encryptionEnabled = \OC_App::isEnabled('files_encryption'); + $publicUploadEnabled = false; + if(!$encryptionEnabled && $publicUploadStatus === 'yes') { + $publicUploadEnabled = true; + } + + // only change permissions for public shares if public upload is enabled + // and we want to set permissions to 1 (read only) or 7 (allow upload) + if ( (int)$shareType === \OCP\Share::SHARE_TYPE_LINK ) { + if ($publicUploadEnabled === false || ($permissions !== 7 && $permissions !== 1)) { + return new \OC_OCS_Result(null, 400, "can't change permission for public link share"); + } + } + + try { + $return = \OCP\Share::setPermissions( + $itemType, + $itemSource, + $shareType, + $shareWith, + $permissions + ); + } catch (\Exception $e) { + return new \OC_OCS_Result(null, 404, $e->getMessage()); + } + + if ($return) { + return new \OC_OCS_Result(); + } else { + return new \OC_OCS_Result(null, 404, "couldn't set permissions"); + } + } + + /** + * @brief enable/disable public upload + * @param array $share information about the share + * @param array $params contains 'publicUpload' which can be 'yes' or 'no' + * @return \OC_OCS_Result + */ + private static function updatePublicUpload($share, $params) { + + $publicUploadEnabled = \OC_Appconfig::getValue('core', 'shareapi_allow_public_upload', 'yes'); + $encryptionEnabled = \OC_App::isEnabled('files_encryption'); + if($encryptionEnabled || $publicUploadEnabled !== 'yes') { + return new \OC_OCS_Result(null, 404, "public upload disabled by the administrator"); + } + + if ($share['item_type'] !== 'folder' || + (int)$share['share_type'] !== \OCP\Share::SHARE_TYPE_LINK ) { + return new \OC_OCS_Result(null, 404, "public upload is only possible for public shared folders"); + } + + // read, create, update (7) if public upload is enabled or + // read (1) if public upload is disabled + $params['_put']['permissions'] = $params['_put']['publicUpload'] === 'true' ? 7 : 1; + + return self::updatePermissions($share, $params); + + } + + /** + * @brief update password for public link share + * @param array $share information about the share + * @param type $params 'password' + * @return \OC_OCS_Result + */ + private static function updatePassword($share, $params) { + + $itemSource = $share['item_source']; + $itemType = $share['item_type']; + + if( (int)$share['share_type'] !== \OCP\Share::SHARE_TYPE_LINK) { + return new \OC_OCS_Result(null, 400, "password protection is only supported for public shares"); + } + + $shareWith = isset($params['_put']['password']) ? $params['_put']['password'] : null; + + if($shareWith === '') { + $shareWith = null; + } + + $items = \OCP\Share::getItemShared($itemType, $itemSource); + + $checkExists = false; + foreach ($items as $item) { + if($item['share_type'] === \OCP\Share::SHARE_TYPE_LINK) { + $checkExists = true; + $permissions = $item['permissions']; + } + } + + if (!$checkExists) { + return new \OC_OCS_Result(null, 404, "share doesn't exists, can't change password"); + } + + $result = \OCP\Share::shareItem( + $itemType, + $itemSource, + \OCP\Share::SHARE_TYPE_LINK, + $shareWith, + $permissions + ); + if($result) { + return new \OC_OCS_Result(); + } + + return new \OC_OCS_Result(null, 404, "couldn't set password"); + } + + /** + * @brief unshare a file/folder + * @param array $params contains the shareID 'id' which should be unshared + * @return \OC_OCS_Result + */ + public static function deleteShare($params) { + + $share = self::getShareFromId($params['id']); + $itemSource = isset($share['item_source']) ? $share['item_source'] : null; + $itemType = isset($share['item_type']) ? $share['item_type'] : null;; + + if($itemSource === null) { + return new \OC_OCS_Result(null, 404, "wrong share ID, share doesn't exist."); + } + + $shareWith = isset($share['share_with']) ? $share['share_with'] : null; + $shareType = isset($share['share_type']) ? (int)$share['share_type'] : null; + + if( $shareType === \OCP\Share::SHARE_TYPE_LINK) { + $shareWith = null; + } + + try { + $return = \OCP\Share::unshare( + $itemType, + $itemSource, + $shareType, + $shareWith); + } catch (\Exception $e) { + return new \OC_OCS_Result(null, 404, $e->getMessage()); + } + + if ($return) { + return new \OC_OCS_Result(); + } else { + $msg = "Unshare Failed"; + return new \OC_OCS_Result(null, 404, $msg); + } + } + + /** + * @brief get file ID from a given path + * @param string $path + * @return string fileID or null + */ + private static function getFileId($path) { + + $view = new \OC\Files\View('/'.\OCP\User::getUser().'/files'); + $fileId = null; + $fileInfo = $view->getFileInfo($path); + if ($fileInfo) { + $fileId = $fileInfo['fileid']; + } + + return $fileId; + } + + /** + * @brief get itemType + * @param string $path + * @return string type 'file', 'folder' or null of file/folder doesn't exists + */ + private static function getItemType($path) { + $view = new \OC\Files\View('/'.\OCP\User::getUser().'/files'); + $itemType = null; + + if ($view->is_dir($path)) { + $itemType = "folder"; + } elseif ($view->is_file($path)) { + $itemType = "file"; + } + + return $itemType; + } + + /** + * @brief get some information from a given share + * @param int $shareID + * @return array with: item_source, share_type, share_with, item_type, permissions + */ + private static function getShareFromId($shareID) { + $sql = 'SELECT `item_source`, `share_type`, `share_with`, `item_type`, `permissions` FROM `*PREFIX*share` WHERE `id` = ?'; + $args = array($shareID); + $query = \OCP\DB::prepare($sql); + $result = $query->execute($args); + + if (\OCP\DB::isError($result)) { + \OCP\Util::writeLog('files_sharing', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR); + return null; + } + if ($share = $result->fetchRow()) { + return $share; + } + + return null; + + } + +} diff --git a/apps/files_sharing/lib/cache.php b/apps/files_sharing/lib/cache.php index a2b7e22d1b3..baa632ec9c1 100644 --- a/apps/files_sharing/lib/cache.php +++ b/apps/files_sharing/lib/cache.php @@ -20,6 +20,7 @@ */ namespace OC\Files\Cache; +use OCP\Share_Backend_Collection; /** * Metadata cache for shared files @@ -218,31 +219,86 @@ class Shared_Cache extends Cache { * @return array of file data */ public function search($pattern) { - // TODO + + $where = '`name` LIKE ? AND '; + + // normalize pattern + $value = $this->normalize($pattern); + + // we have one ? in our where clause + $chunksize = self::MAX_SQL_CHUNK_SIZE - 1; + + return $this->searchWithWhere($where, $value, $chunksize); + } /** * search for files by mimetype * - * @param string $part1 - * @param string $part2 + * @param string $mimetype * @return array */ public function searchByMime($mimetype) { + if (strpos($mimetype, '/')) { - $where = '`mimetype` = ?'; + $where = '`mimetype` = ? AND '; } else { - $where = '`mimepart` = ?'; + $where = '`mimepart` = ? AND '; } - $mimetype = $this->getMimetypeId($mimetype); + + $value = $this->getMimetypeId($mimetype); + + // we have one ? in our where clause + $chunksize = self::MAX_SQL_CHUNK_SIZE - 1; + + return $this->searchWithWhere($where, $value, $chunksize); + + } + + /** + * The maximum number of placeholders that can be used in an SQL query. + * Value MUST be < 1000. + * Also see ORA-01795 maximum number of expressions in a list is 1000 + */ + const MAX_SQL_CHUNK_SIZE = 999; + + /** + * search for files with a custom where clause and value + * the $wherevalue will be array_merge()d with the file id chunks + * + * @param string $sqlwhere + * @param string $wherevalue + * @return array + */ + private function searchWithWhere($sqlwhere, $wherevalue, $chunksize = self::MAX_SQL_CHUNK_SIZE) { + $ids = $this->getAll(); - $placeholders = join(',', array_fill(0, count($ids), '?')); - $query = \OC_DB::prepare(' - SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted` - FROM `*PREFIX*filecache` WHERE ' . $where . ' AND `fileid` IN (' . $placeholders . ')' - ); - $result = $query->execute(array_merge(array($mimetype), $ids)); - return $result->fetchAll(); + + $files = array(); + + // divide into 1k chunks + $chunks = array_chunk($ids, $chunksize); + + foreach ($chunks as $chunk) { + $placeholders = join(',', array_fill(0, count($chunk), '?')); + $sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, + `encrypted`, `unencrypted_size`, `etag` + FROM `*PREFIX*filecache` WHERE ' . $sqlwhere . ' `fileid` IN (' . $placeholders . ')'; + + $stmt = \OC_DB::prepare($sql); + + $result = $stmt->execute(array_merge(array($wherevalue), $chunk)); + + while ($row = $result->fetchRow()) { + if (substr($row['path'], 0, 6) === 'files/') { + $row['path'] = substr($row['path'], 6); // remove 'files/' from path as it's relative to '/Shared' + } + $row['mimetype'] = $this->getMimetype($row['mimetype']); + $row['mimepart'] = $this->getMimetype($row['mimepart']); + $files[] = $row; + } + } + return $files; } /** @@ -264,7 +320,20 @@ class Shared_Cache extends Cache { * @return int[] */ public function getAll() { - return \OCP\Share::getItemsSharedWith('file', \OC_Share_Backend_File::FORMAT_GET_ALL); + $ids = \OCP\Share::getItemsSharedWith('file', \OC_Share_Backend_File::FORMAT_GET_ALL); + $folderBackend = \OCP\Share::getBackend('folder'); + if ($folderBackend instanceof Share_Backend_Collection) { + foreach ($ids as $file) { + /** @var $folderBackend Share_Backend_Collection */ + $children = $folderBackend->getChildren($file); + foreach ($children as $child) { + $ids[] = (int)$child['source']; + } + + } + } + + return $ids; } } diff --git a/apps/files_sharing/lib/updater.php b/apps/files_sharing/lib/updater.php index a43ab2e2a0a..9d08f278923 100644 --- a/apps/files_sharing/lib/updater.php +++ b/apps/files_sharing/lib/updater.php @@ -32,28 +32,40 @@ class Shared_Updater { $uid = \OCP\User::getUser(); $uidOwner = \OC\Files\Filesystem::getOwner($target); $info = \OC\Files\Filesystem::getFileInfo($target); + $checkedUser = array($uidOwner); // Correct Shared folders of other users shared with $users = \OCP\Share::getUsersItemShared('file', $info['fileid'], $uidOwner, true); if (!empty($users)) { while (!empty($users)) { $reshareUsers = array(); foreach ($users as $user) { - if ( $user !== $uidOwner ) { + if ( !in_array($user, $checkedUser) ) { $etag = \OC\Files\Filesystem::getETag(''); \OCP\Config::setUserValue($user, 'files_sharing', 'etag', $etag); // Look for reshares $reshareUsers = array_merge($reshareUsers, \OCP\Share::getUsersItemShared('file', $info['fileid'], $user, true)); + $checkedUser[] = $user; } } $users = $reshareUsers; } - // Correct folders of shared file owner - $target = substr($target, 8); - if ($uidOwner !== $uid && $source = \OC_Share_Backend_File::getSource($target)) { - \OC\Files\Filesystem::initMountPoints($uidOwner); - $source = '/'.$uidOwner.'/'.$source['path']; - \OC\Files\Cache\Updater::correctFolder($source, $info['mtime']); - } + } + } + + /** + * @brief remove all shares for a given file if the file was deleted + * + * @param string $path + */ + private static function removeShare($path) { + $fileInfo = \OC\Files\Filesystem::getFileInfo($path); + $fileSource = $fileInfo['fileid']; + + $query = \OC_DB::prepare('DELETE FROM `*PREFIX*share` WHERE `file_source`=?'); + try { + $query->execute(array($fileSource)); + } catch (\Exception $e) { + \OCP\Util::writeLog('files_sharing', "can't remove share: " . $e->getMessage(), \OCP\Util::WARN); } } @@ -77,8 +89,10 @@ class Shared_Updater { */ static public function deleteHook($params) { self::correctFolders($params['path']); + self::removeShare($params['path']); } + /** * @param array $params */ diff --git a/apps/files_sharing/templates/public.php b/apps/files_sharing/templates/public.php index ef5ad424fb6..fd0a861142d 100644 --- a/apps/files_sharing/templates/public.php +++ b/apps/files_sharing/templates/public.php @@ -4,6 +4,7 @@ <?php $defaults = new OCP\Defaults(); // initialize themable default strings and urls ?> +<input type="hidden" id="isPublic" name="isPublic" value="1"> <input type="hidden" name="dir" value="<?php p($_['dir']) ?>" id="dir"> <input type="hidden" name="downloadURL" value="<?php p($_['downloadURL']) ?>" id="downloadURL"> <input type="hidden" name="filename" value="<?php p($_['filename']) ?>" id="filename"> diff --git a/apps/files_sharing/tests/api.php b/apps/files_sharing/tests/api.php new file mode 100644 index 00000000000..44fc4d8b7b3 --- /dev/null +++ b/apps/files_sharing/tests/api.php @@ -0,0 +1,597 @@ +<?php +/** + * ownCloud + * + * @author Bjoern Schiessle + * @copyright 2013 Bjoern Schiessle <schiessle@owncloud.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +require_once __DIR__ . '/../../../lib/base.php'; + +use OCA\Files\Share; + +/** + * Class Test_Files_Sharing_Api + */ +class Test_Files_Sharing_Api extends \PHPUnit_Framework_TestCase { + + const TEST_FILES_SHARING_API_USER1 = "test-share-user1"; + const TEST_FILES_SHARING_API_USER2 = "test-share-user2"; + const TEST_FILES_SHARING_API_USER3 = "test-share-user3"; + + public $stateFilesEncryption; + public $filename; + public $data; + /** + * @var OC_FilesystemView + */ + public $view; + public $folder; + + public static function setUpBeforeClass() { + // reset backend + \OC_User::clearBackends(); + \OC_User::useBackend('database'); + + // clear share hooks + \OC_Hook::clear('OCP\\Share'); + \OC::registerShareHooks(); + \OCP\Util::connectHook('OC_Filesystem', 'setup', '\OC\Files\Storage\Shared', 'setup'); + + // create users + self::loginHelper(\Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER1, true); + self::loginHelper(\Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2, true); + self::loginHelper(\Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER3, true); + + } + + function setUp() { + + //login as user1 + \Test_Files_Sharing_Api::loginHelper(\Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER1); + + $this->data = 'foobar'; + $this->view = new \OC_FilesystemView('/' . \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER1 . '/files'); + + $this->folder = '/folder_share_api_test'; + + $this->filename = 'share-api-test.txt'; + + // remember files_encryption state + $this->stateFilesEncryption = \OC_App::isEnabled('files_encryption'); + + //we don't want to tests with app files_encryption enabled + \OC_App::disable('files_encryption'); + + + $this->assertTrue(!\OC_App::isEnabled('files_encryption')); + + // save file with content + $this->view->file_put_contents($this->filename, $this->data); + $this->view->mkdir($this->folder); + $this->view->file_put_contents($this->folder.'/'.$this->filename, $this->data); + + } + + function tearDown() { + $this->view->unlink($this->filename); + $this->view->deleteAll($this->folder); + // reset app files_encryption + if ($this->stateFilesEncryption) { + \OC_App::enable('files_encryption'); + } else { + \OC_App::disable('files_encryption'); + } + } + + public static function tearDownAfterClass() { + + // cleanup users + \OC_User::deleteUser(\Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER1); + \OC_User::deleteUser(\Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2); + \OC_User::deleteUser(\Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER3); + } + + /** + * @medium + */ + function testCreateShare() { + + // share to user + + // simulate a post request + $_POST['path'] = $this->filename; + $_POST['shareWith'] = \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2; + $_POST['shareType'] = \OCP\Share::SHARE_TYPE_USER; + + $result = Share\Api::createShare(array()); + + $this->assertTrue($result->succeeded()); + $data = $result->getData(); + + $share = $this->getShareFromId($data['id']); + + $items = \OCP\Share::getItemShared('file', $share['item_source']); + + $this->assertTrue(!empty($items)); + + // share link + + // simulate a post request + $_POST['path'] = $this->folder; + $_POST['shareType'] = \OCP\Share::SHARE_TYPE_LINK; + + $result = Share\Api::createShare(array()); + + // check if API call was successful + $this->assertTrue($result->succeeded()); + + $data = $result->getData(); + + // check if we have a token + $this->assertTrue(is_string($data['token'])); + + $share = $this->getShareFromId($data['id']); + + $items = \OCP\Share::getItemShared('file', $share['item_source']); + + $this->assertTrue(!empty($items)); + + $fileinfo = $this->view->getFileInfo($this->filename); + + \OCP\Share::unshare('file', $fileinfo['fileid'], \OCP\Share::SHARE_TYPE_USER, + \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2); + + $fileinfo = $this->view->getFileInfo($this->folder); + + \OCP\Share::unshare('folder', $fileinfo['fileid'], \OCP\Share::SHARE_TYPE_LINK, null); + + + + } + + /** + * @medium + * @depends testCreateShare + */ + function testGetAllShares() { + + $fileinfo = $this->view->getFileInfo($this->filename); + + \OCP\Share::shareItem('file', $fileinfo['fileid'], \OCP\Share::SHARE_TYPE_USER, + \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2, 31); + + $result = Share\Api::getAllShares(array()); + + $this->assertTrue($result->succeeded()); + + // test should return two shares created from testCreateShare() + $this->assertTrue(count($result->getData()) === 1); + + \OCP\Share::unshare('file', $fileinfo['fileid'], \OCP\Share::SHARE_TYPE_USER, + \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2); + } + + /** + * @medium + * @depends testCreateShare + */ + function testGetShareFromSource() { + + $fileInfo = $this->view->getFileInfo($this->filename); + + \OCP\Share::shareItem('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, + \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2, 31); + + \OCP\Share::shareItem('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_LINK, + null, 1); + + $_GET['path'] = $this->filename; + + $result = Share\Api::getAllShares(array()); + + $this->assertTrue($result->succeeded()); + + // test should return one share created from testCreateShare() + $this->assertTrue(count($result->getData()) === 2); + + \OCP\Share::unshare('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, + \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2); + + \OCP\Share::unshare('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_LINK, null); + + } + + /** + * @medium + * @depends testCreateShare + */ + function testGetShareFromSourceWithReshares() { + + $fileInfo = $this->view->getFileInfo($this->filename); + + // share the file as user1 to user2 + \OCP\Share::shareItem('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, + \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2, 31); + + // login as user2 and reshare the file to user3 + \Test_Files_Sharing_Api::loginHelper(\Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2); + + \OCP\Share::shareItem('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, + \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER3, 31); + + // login as user1 again + \Test_Files_Sharing_Api::loginHelper(\Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER1); + + $_GET['path'] = $this->filename; + + $result = Share\Api::getAllShares(array()); + + $this->assertTrue($result->succeeded()); + + // test should return one share + $this->assertTrue(count($result->getData()) === 1); + + // now also ask for the reshares + $_GET['reshares'] = 'true'; + + $result = Share\Api::getAllShares(array()); + + $this->assertTrue($result->succeeded()); + + // now we should get two shares, the initial share and the reshare + $this->assertTrue(count($result->getData()) === 2); + + // unshare files again + + \Test_Files_Sharing_Api::loginHelper(\Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2); + + \OCP\Share::unshare('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, + \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER3); + + \Test_Files_Sharing_Api::loginHelper(\Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER1); + + \OCP\Share::unshare('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, + \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2); + + } + + /** + * @medium + * @depends testCreateShare + */ + function testGetShareFromId() { + + $fileInfo = $this->view->getFileInfo($this->filename); + + $result = \OCP\Share::shareItem('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, + \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2, 31); + + // share was successful? + $this->assertTrue($result); + + // get item to determine share ID + $result = \OCP\Share::getItemShared('file', $fileInfo['fileid']); + + $this->assertEquals(1, count($result)); + + // get first element + $share = reset($result); + + // call getShare() with share ID + $params = array('id' => $share['id']); + $result = Share\Api::getShare($params); + + $this->assertTrue($result->succeeded()); + + // test should return one share created from testCreateShare() + $this->assertEquals(1, count($result->getData())); + + \OCP\Share::unshare('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, + \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2); + + } + + /** + * @medium + */ + function testGetShareFromFolder() { + + $fileInfo1 = $this->view->getFileInfo($this->filename); + $fileInfo2 = $this->view->getFileInfo($this->folder.'/'.$this->filename); + + $result = \OCP\Share::shareItem('file', $fileInfo1['fileid'], \OCP\Share::SHARE_TYPE_USER, + \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2, 31); + + // share was successful? + $this->assertTrue($result); + + $result = \OCP\Share::shareItem('folder', $fileInfo2['fileid'], \OCP\Share::SHARE_TYPE_LINK, + null, 1); + + // share was successful? + $this->assertTrue(is_string($result)); + + $_GET['path'] = $this->folder; + $_GET['subfiles'] = 'true'; + + $result = Share\Api::getAllShares(array()); + + $this->assertTrue($result->succeeded()); + + // test should return one share within $this->folder + $this->assertTrue(count($result->getData()) === 1); + + \OCP\Share::unshare('file', $fileInfo1['fileid'], \OCP\Share::SHARE_TYPE_USER, + \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2); + + \OCP\Share::unshare('folder', $fileInfo2['fileid'], \OCP\Share::SHARE_TYPE_LINK, null); + + } + + /** + * @medium + */ + function testGetShareFromUnknownId() { + + $params = array('id' => 0); + + $result = Share\Api::getShare($params); + + $this->assertEquals(404, $result->getStatusCode()); + $meta = $result->getMeta(); + $this->assertEquals('share doesn\'t exist', $meta['message']); + + } + + /** + * @medium + * @depends testCreateShare + */ + function testUpdateShare() { + + $fileInfo = $this->view->getFileInfo($this->filename); + + $result = \OCP\Share::shareItem('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, + \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2, 31); + + // share was successful? + $this->assertTrue($result); + + $result = \OCP\Share::shareItem('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_LINK, + null, 1); + + // share was successful? + $this->assertTrue(is_string($result)); + + $items = \OCP\Share::getItemShared('file', null); + + // make sure that we found a link share and a user share + $this->assertEquals(count($items), 2); + + $linkShare = null; + $userShare = null; + + foreach ($items as $item) { + if ($item['share_type'] === \OCP\Share::SHARE_TYPE_LINK) { + $linkShare = $item; + } + if ($item['share_type'] === \OCP\Share::SHARE_TYPE_USER) { + $userShare = $item; + } + } + + // make sure that we found a link share and a user share + $this->assertTrue(is_array($linkShare)); + $this->assertTrue(is_array($userShare)); + + // update permissions + + $this->assertEquals('31', $userShare['permissions']); + + $params = array(); + $params['id'] = $userShare['id']; + $params['_put'] = array(); + $params['_put']['permissions'] = 1; + + $result = Share\Api::updateShare($params); + + $meta = $result->getMeta(); + $this->assertTrue($result->succeeded(), $meta['message']); + + $items = \OCP\Share::getItemShared('file', $userShare['file_source']); + + $newUserShare = null; + foreach ($items as $item) { + if ($item['share_with'] === \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2) { + $newUserShare = $item; + break; + } + } + + $this->assertTrue(is_array($newUserShare)); + + $this->assertEquals('1', $newUserShare['permissions']); + + // update password for link share + + $this->assertTrue(empty($linkShare['share_with'])); + + $params = array(); + $params['id'] = $linkShare['id']; + $params['_put'] = array(); + $params['_put']['password'] = 'foo'; + + $result = Share\Api::updateShare($params); + + $this->assertTrue($result->succeeded()); + + $items = \OCP\Share::getItemShared('file', $linkShare['file_source']); + + $newLinkShare = null; + foreach ($items as $item) { + if ($item['share_type'] === \OCP\Share::SHARE_TYPE_LINK) { + $newLinkShare = $item; + break; + } + } + + $this->assertTrue(is_array($newLinkShare)); + $this->assertTrue(!empty($newLinkShare['share_with'])); + + \OCP\Share::unshare('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, + \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2); + + \OCP\Share::unshare('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_LINK, null); + + } + + /** + * @medium + */ + function testUpdateShareUpload() { + + $fileInfo = $this->view->getFileInfo($this->folder); + + $result = \OCP\Share::shareItem('folder', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_LINK, + null, 1); + + // share was successful? + $this->assertTrue(is_string($result)); + + $items = \OCP\Share::getItemShared('file', null); + + // make sure that we found a link share and a user share + $this->assertEquals(count($items), 1); + + $linkShare = null; + + foreach ($items as $item) { + if ($item['share_type'] === \OCP\Share::SHARE_TYPE_LINK) { + $linkShare = $item; + } + } + + // make sure that we found a link share + $this->assertTrue(is_array($linkShare)); + + // update public upload + + $params = array(); + $params['id'] = $linkShare['id']; + $params['_put'] = array(); + $params['_put']['publicUpload'] = 'true'; + + $result = Share\Api::updateShare($params); + + $this->assertTrue($result->succeeded()); + + $items = \OCP\Share::getItemShared('file', $linkShare['file_source']); + + $updatedLinkShare = null; + foreach ($items as $item) { + if ($item['share_type'] === \OCP\Share::SHARE_TYPE_LINK) { + $updatedLinkShare = $item; + break; + } + } + + $this->assertTrue(is_array($updatedLinkShare)); + $this->assertEquals(7, $updatedLinkShare['permissions']); + + // cleanup + + \OCP\Share::unshare('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_LINK, null); + + } + + /** + * @medium + * @depends testCreateShare + */ + function testDeleteShare() { + + $fileInfo = $this->view->getFileInfo($this->filename); + + \OCP\Share::shareItem('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, + \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2, 31); + + \OCP\Share::shareItem('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_LINK, + null, 1); + + $items = \OCP\Share::getItemShared('file', null); + + $this->assertEquals(2, count($items)); + + foreach ($items as $item) { + $result = Share\Api::deleteShare(array('id' => $item['id'])); + + $this->assertTrue($result->succeeded()); + } + + $itemsAfterDelete = \OCP\Share::getItemShared('file', null); + + $this->assertTrue(empty($itemsAfterDelete)); + + } + + /** + * @param $user + * @param bool $create + * @param bool $password + */ + private static function loginHelper($user, $create = false, $password = false) { + if ($create) { + \OC_User::createUser($user, $user); + } + + if ($password === false) { + $password = $user; + } + + \OC_Util::tearDownFS(); + \OC_User::setUserId(''); + \OC\Files\Filesystem::tearDown(); + \OC_Util::setupFS($user); + \OC_User::setUserId($user); + + $params['uid'] = $user; + $params['password'] = $password; + } + + /** + * @brief get some information from a given share + * @param int $shareID + * @return array with: item_source, share_type, share_with, item_type, permissions + */ + private function getShareFromId($shareID) { + $sql = 'SELECT `item_source`, `share_type`, `share_with`, `item_type`, `permissions` FROM `*PREFIX*share` WHERE `id` = ?'; + $args = array($shareID); + $query = \OCP\DB::prepare($sql); + $result = $query->execute($args); + + $share = Null; + + if ($result && $result->numRows() > 0) { + $share = $result->fetchRow(); + } + + return $share; + + } + +} diff --git a/apps/files_versions/js/versions.js b/apps/files_versions/js/versions.js index 2a8b491b9b2..c0a2f7df59b 100644 --- a/apps/files_versions/js/versions.js +++ b/apps/files_versions/js/versions.js @@ -1,4 +1,12 @@ $(document).ready(function(){ + + if ($('#isPublic').val()){ + // no versions actions in public mode + // beware of https://github.com/owncloud/core/issues/4545 + // as enabling this might hang Chrome + return; + } + if (typeof FileActions !== 'undefined') { // Add versions button to 'files/index.php' FileActions.register( diff --git a/apps/user_ldap/lib/access.php b/apps/user_ldap/lib/access.php index dbe998aad8e..aa8db295518 100644 --- a/apps/user_ldap/lib/access.php +++ b/apps/user_ldap/lib/access.php @@ -28,6 +28,8 @@ abstract class Access { //never ever check this var directly, always use getPagedSearchResultState protected $pagedSearchedSuccessful; + protected $cookies = array(); + public function setConnector(Connection &$connection) { $this->connection = $connection; } @@ -59,6 +61,8 @@ abstract class Access { \OCP\Util::writeLog('user_ldap', 'LDAP resource not available.', \OCP\Util::DEBUG); return false; } + //all or nothing! otherwise we get in trouble with. + $this->initPagedSearch($filter, array($dn), $attr, 99999, 0); $dn = $this->DNasBaseParameter($dn); $rr = @ldap_read($cr, $dn, $filter, array($attr)); if(!is_resource($rr)) { @@ -893,7 +897,9 @@ abstract class Access { if(!$testConnection->setConfiguration($credentials)) { return false; } - return $testConnection->bind(); + $result=$testConnection->bind(); + $this->connection->bind(); + return $result; } /** @@ -1004,7 +1010,7 @@ abstract class Access { $bases = $this->sanitizeDN($bases); foreach($bases as $base) { $belongsToBase = true; - if(mb_strripos($dn, $base, 0, 'UTF-8') !== (mb_strlen($dn, 'UTF-8')-mb_strlen($base))) { + if(mb_strripos($dn, $base, 0, 'UTF-8') !== (mb_strlen($dn, 'UTF-8')-mb_strlen($base, 'UTF-8'))) { $belongsToBase = false; } if($belongsToBase) { @@ -1029,9 +1035,12 @@ abstract class Access { $offset -= $limit; //we work with cache here $cachekey = 'lc' . crc32($base) . '-' . crc32($filter) . '-' . $limit . '-' . $offset; - $cookie = $this->connection->getFromCache($cachekey); - if(is_null($cookie)) { - $cookie = ''; + $cookie = ''; + if(isset($this->cookies[$cachekey])) { + $cookie = $this->cookies[$cachekey]; + if(is_null($cookie)) { + $cookie = ''; + } } return $cookie; } @@ -1048,7 +1057,7 @@ abstract class Access { private function setPagedResultCookie($base, $filter, $limit, $offset, $cookie) { if(!empty($cookie)) { $cachekey = 'lc' . crc32($base) . '-' . crc32($filter) . '-' .$limit . '-' . $offset; - $cookie = $this->connection->writeToCache($cachekey, $cookie); + $this->cookies[$cachekey] = $cookie; } } diff --git a/config/config.sample.php b/config/config.sample.php index 5c09269ea3c..092480d4f5d 100644 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -181,5 +181,8 @@ $CONFIG = array( 'customclient_ios' => '', //https://itunes.apple.com/us/app/owncloud/id543672169?mt=8 // date format to be used while writing to the owncloud logfile -'logdateformat' => 'F d, Y H:i:s' +'logdateformat' => 'F d, Y H:i:s', + +/* timezone used while writing to the owncloud logfile (default: UTC) */ +'logtimezone' => 'Europe/Berlin', ); diff --git a/core/ajax/share.php b/core/ajax/share.php index aed1caff378..387ac8bc27f 100644 --- a/core/ajax/share.php +++ b/core/ajax/share.php @@ -188,7 +188,7 @@ if (isset($_POST['action']) && isset($_POST['itemType']) && isset($_POST['itemSo while ($count < 15 && count($users) == $limit) { $limit = 15 - $count; if ($sharePolicy == 'groups_only') { - $users = OC_Group::DisplayNamesInGroups($groups, $_GET['search'], $limit, $offset); + $users = OC_Group::DisplayNamesInGroups($usergroups, $_GET['search'], $limit, $offset); } else { $users = OC_User::getDisplayNames($_GET['search'], $limit, $offset); } diff --git a/core/js/js.js b/core/js/js.js index 586d698f3a2..73711b3fbc5 100644 --- a/core/js/js.js +++ b/core/js/js.js @@ -666,11 +666,17 @@ $(document).ready(function(){ } }else if(event.keyCode===27){//esc OC.search.hide(); + if (FileList && typeof FileList.unfilter === 'function') { //TODO add hook system + FileList.unfilter(); + } }else{ var query=$('#searchbox').val(); if(OC.search.lastQuery!==query){ OC.search.lastQuery=query; OC.search.currentResult=-1; + if (FileList && typeof FileList.filter === 'function') { //TODO add hook system + FileList.filter(query); + } if(query.length>2){ OC.search(query); }else{ @@ -808,6 +814,13 @@ function formatDate(date){ return $.datepicker.formatDate(datepickerFormatDate, date)+' '+date.getHours()+':'+((date.getMinutes()<10)?'0':'')+date.getMinutes(); } +// taken from http://stackoverflow.com/questions/1403888/get-url-parameter-with-jquery +function getURLParameter(name) { + return decodeURI( + (RegExp(name + '=' + '(.+?)(&|$)').exec(location.search) || [, null])[1] + ); +} + /** * takes an absolute timestamp and return a string with a human-friendly relative date * @param int a Unix timestamp @@ -892,6 +905,15 @@ $.fn.selectRange = function(start, end) { }; /** + * check if an element exists. + * allows you to write if ($('#myid').exists()) to increase readability + * @link http://stackoverflow.com/questions/31044/is-there-an-exists-function-for-jquery + */ +jQuery.fn.exists = function(){ + return this.length > 0; +}; + +/** * Calls the server periodically every 15 mins to ensure that session doesnt * time out */ diff --git a/core/js/multiselect.js b/core/js/multiselect.js index bc4223feb64..48d521e1856 100644 --- a/core/js/multiselect.js +++ b/core/js/multiselect.js @@ -176,10 +176,10 @@ }); button.parent().data('preventHide',false); if(settings.createText){ - var li=$('<li class="creator">+ <em>'+settings.createText+'<em></li>'); + var li=$('<li class="creator">+ '+settings.createText+'</li>'); li.click(function(event){ li.empty(); - var input=$('<input class="new">'); + var input=$('<input type="text" class="new">'); li.append(input); input.focus(); input.css('width',button.innerWidth()); @@ -316,4 +316,4 @@ return span; }; -})( jQuery );
\ No newline at end of file +})( jQuery ); diff --git a/core/js/share.js b/core/js/share.js index 4664594e657..a34542a8abd 100644 --- a/core/js/share.js +++ b/core/js/share.js @@ -4,57 +4,76 @@ OC.Share={ SHARE_TYPE_LINK:3, SHARE_TYPE_EMAIL:4, itemShares:[], - statuses:[], + statuses:{}, droppedDown:false, + /** + * Loads ALL share statuses from server, stores them in OC.Share.statuses then + * calls OC.Share.updateIcons() to update the files "Share" icon to "Shared" + * according to their share status and share type. + */ loadIcons:function(itemType) { // Load all share icons $.get(OC.filePath('core', 'ajax', 'share.php'), { fetch: 'getItemsSharedStatuses', itemType: itemType }, function(result) { if (result && result.status === 'success') { + OC.Share.statuses = {}; $.each(result.data, function(item, data) { OC.Share.statuses[item] = data; - var hasLink = data['link']; - // Links override shared in terms of icon display - if (hasLink) { - var image = OC.imagePath('core', 'actions/public'); - } else { - var image = OC.imagePath('core', 'actions/shared'); - } - if (itemType != 'file' && itemType != 'folder') { - $('a.share[data-item="'+item+'"]').css('background', 'url('+image+') no-repeat center'); - } else { - var file = $('tr[data-id="'+item+'"]'); - if (file.length > 0) { - var action = $(file).find('.fileactions .action[data-action="Share"]'); - var img = action.find('img').attr('src', image); - action.addClass('permanent'); - action.html(' '+t('core', 'Shared')).prepend(img); - } else { - var dir = $('#dir').val(); - if (dir.length > 1) { - var last = ''; - var path = dir; - // Search for possible parent folders that are shared - while (path != last) { - if (path == data['path']) { - var actions = $('.fileactions .action[data-action="Share"]'); - $.each(actions, function(index, action) { - var img = $(action).find('img'); - if (img.attr('src') != OC.imagePath('core', 'actions/public')) { - img.attr('src', image); - $(action).addClass('permanent'); - $(action).html(' '+t('core', 'Shared')).prepend(img); - } - }); + }); + OC.Share.updateIcons(itemType); + } + }); + }, + /** + * Updates the files' "Share" icons according to the known + * sharing states stored in OC.Share.statuses. + * (not reloaded from server) + */ + updateIcons:function(itemType){ + var item; + for (item in OC.Share.statuses){ + var data = OC.Share.statuses[item]; + + var hasLink = data['link']; + // Links override shared in terms of icon display + if (hasLink) { + var image = OC.imagePath('core', 'actions/public'); + } else { + var image = OC.imagePath('core', 'actions/shared'); + } + if (itemType != 'file' && itemType != 'folder') { + $('a.share[data-item="'+item+'"]').css('background', 'url('+image+') no-repeat center'); + } else { + var file = $('tr[data-id="'+item+'"]'); + if (file.length > 0) { + var action = $(file).find('.fileactions .action[data-action="Share"]'); + var img = action.find('img').attr('src', image); + action.addClass('permanent'); + action.html(' '+t('core', 'Shared')).prepend(img); + } else { + var dir = $('#dir').val(); + if (dir.length > 1) { + var last = ''; + var path = dir; + // Search for possible parent folders that are shared + while (path != last) { + if (path == data['path'] && !data['link']) { + var actions = $('.fileactions .action[data-action="Share"]'); + $.each(actions, function(index, action) { + var img = $(action).find('img'); + if (img.attr('src') != OC.imagePath('core', 'actions/public')) { + img.attr('src', image); + $(action).addClass('permanent'); + $(action).html(' '+t('core', 'Shared')).prepend(img); } - last = path; - path = OC.Share.dirname(path); - } + }); } + last = path; + path = OC.Share.dirname(path); } } - }); + } } - }); + } }, updateIcon:function(itemType, itemSource) { var shares = false; @@ -214,7 +233,9 @@ OC.Share={ if (data.shares) { $.each(data.shares, function(index, share) { if (share.share_type == OC.Share.SHARE_TYPE_LINK) { - OC.Share.showLink(share.token, share.share_with, itemSource); + if ( !('file_target' in share) ) { + OC.Share.showLink(share.token, share.share_with, itemSource); + } } else { if (share.collection) { OC.Share.addShareWith(share.share_type, share.share_with, share.share_with_displayname, share.permissions, possiblePermissions, share.collection); @@ -233,6 +254,7 @@ OC.Share={ // } else { $.get(OC.filePath('core', 'ajax', 'share.php'), { fetch: 'getShareWith', search: search.term, itemShares: OC.Share.itemShares }, function(result) { if (result.status == 'success' && result.data.length > 0) { + $( "#shareWith" ).autocomplete( "option", "autoFocus", true ); response(result.data); } else { // Suggest sharing via email if valid email address @@ -240,6 +262,7 @@ OC.Share={ // if (pattern.test(search.term)) { // response([{label: t('core', 'Share via email:')+' '+search.term, value: {shareType: OC.Share.SHARE_TYPE_EMAIL, shareWith: search.term}}]); // } else { + $( "#shareWith" ).autocomplete( "option", "autoFocus", false ); response([t('core', 'No people found')]); // } } @@ -412,7 +435,7 @@ OC.Share={ dateFormat : 'dd-mm-yy' }); } -} +}; $(document).ready(function() { @@ -501,7 +524,7 @@ $(document).ready(function() { $(document).on('change', '#dropdown .permissions', function() { if ($(this).attr('name') == 'edit') { - var li = $(this).parent().parent() + var li = $(this).parent().parent(); var checkboxes = $('.permissions', li); var checked = $(this).is(':checked'); // Check/uncheck Create, Update, and Delete checkboxes if Edit is checked/unck diff --git a/core/templates/login.php b/core/templates/login.php index 882ce234cf4..3874e998bf9 100644 --- a/core/templates/login.php +++ b/core/templates/login.php @@ -35,8 +35,10 @@ <label for="password" class="infield"><?php p($l->t('Password')); ?></label> <img class="svg" id="password-icon" src="<?php print_unescaped(image_path('', 'actions/password.svg')); ?>" alt=""/> </p> + <?php if ($_['rememberLoginAllowed'] === true) : ?> <input type="checkbox" name="remember_login" value="1" id="remember_login" checked /><label for="remember_login"><?php p($l->t('remember')); ?></label> + <?php endif; ?> <input type="hidden" name="timezone-offset" id="timezone-offset"/> <input type="submit" id="submit" class="login primary" value="<?php p($l->t('Log in')); ?>"/> </fieldset> diff --git a/lib/appconfig.php b/lib/appconfig.php index e615d838173..8bc68f45de0 100644 --- a/lib/appconfig.php +++ b/lib/appconfig.php @@ -125,14 +125,22 @@ class OC_Appconfig{ public static function setValue( $app, $key, $value ) { // Does the key exist? yes: update. No: insert if(! self::hasKey($app, $key)) { - $query = OC_DB::prepare( 'INSERT INTO `*PREFIX*appconfig` ( `appid`, `configkey`, `configvalue` )' - .' VALUES( ?, ?, ? )' ); - $query->execute( array( $app, $key, $value )); + OC_DB::executeAudited(' + INSERT INTO `*PREFIX*appconfig` ( + `appid`, `configkey`, `configvalue` + ) VALUES ( + ?, ?, ? + ) + ', array( $app, $key, $value ) + ); } else{ - $query = OC_DB::prepare( 'UPDATE `*PREFIX*appconfig` SET `configvalue` = ?' - .' WHERE `appid` = ? AND `configkey` = ?' ); - $query->execute( array( $value, $app, $key )); + OC_DB::executeAudited(' + UPDATE `*PREFIX*appconfig` + SET `configvalue` = ? + WHERE `appid` = ? AND `configkey` = ? + ', array( $value, $app, $key ) + ); } } diff --git a/lib/base.php b/lib/base.php index 5a1bd4ecbe3..e8f67db8881 100644 --- a/lib/base.php +++ b/lib/base.php @@ -746,6 +746,7 @@ class OC { || !isset($_COOKIE["oc_token"]) || !isset($_COOKIE["oc_username"]) || !$_COOKIE["oc_remember_login"] + || !OC_Util::rememberLoginAllowed() ) { return false; } @@ -768,6 +769,7 @@ class OC { OC_User::setMagicInCookie($_COOKIE['oc_username'], $token); // login OC_User::setUserId($_COOKIE['oc_username']); + OC_User::setDisplayName($_COOKIE['oc_username'], $_COOKIE['display_name']); OC_Util::redirectToDefaultPage(); // doesn't return } diff --git a/lib/cache/file.php b/lib/cache/file.php index 361138e4736..eed2637c981 100644 --- a/lib/cache/file.php +++ b/lib/cache/file.php @@ -40,6 +40,24 @@ class OC_Cache_File{ return $result; } + /** + * Returns the size of the stored/cached data + * + * @param $key + * @return int + */ + public function size($key) { + $result = 0; + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + if ($this->hasKey($key)) { + $storage = $this->getStorage(); + $result = $storage->filesize($key); + } + \OC_FileProxy::$enabled = $proxyStatus; + return $result; + } + public function set($key, $value, $ttl=0) { $storage = $this->getStorage(); $result = false; diff --git a/lib/connector/sabre/directory.php b/lib/connector/sabre/directory.php index 1434e970587..3cccf6ef3d0 100644 --- a/lib/connector/sabre/directory.php +++ b/lib/connector/sabre/directory.php @@ -50,23 +50,24 @@ class OC_Connector_Sabre_Directory extends OC_Connector_Sabre_Node implements Sa */ public function createFile($name, $data = null) { - if (!\OC\Files\Filesystem::isCreatable($this->path)) { - throw new \Sabre_DAV_Exception_Forbidden(); - } - if (isset($_SERVER['HTTP_OC_CHUNKED'])) { $info = OC_FileChunking::decodeName($name); if (empty($info)) { throw new Sabre_DAV_Exception_NotImplemented(); } - $chunk_handler = new OC_FileChunking($info); - $chunk_handler->store($info['index'], $data); - if ($chunk_handler->isComplete()) { - $newPath = $this->path . '/' . $info['name']; - $chunk_handler->file_assemble($newPath); - return OC_Connector_Sabre_Node::getETagPropertyForPath($newPath); + + if (!\OC\Files\Filesystem::isCreatable($this->path) && + !\OC\Files\Filesystem::isUpdatable($this->path . '/' . $info['name'])) { + throw new \Sabre_DAV_Exception_Forbidden(); } + + return $this->createFileChunked($name, $data); } else { + + if (!\OC\Files\Filesystem::isCreatable($this->path)) { + throw new \Sabre_DAV_Exception_Forbidden(); + } + $newPath = $this->path . '/' . $name; // mark file as partial while uploading (ignored by the scanner) @@ -88,7 +89,13 @@ class OC_Connector_Sabre_Directory extends OC_Connector_Sabre_Node implements Sa } // rename to correct path - \OC\Files\Filesystem::rename($partpath, $newPath); + $renameOkay = \OC\Files\Filesystem::rename($partpath, $newPath); + $fileExists = \OC\Files\Filesystem::file_exists($newPath); + if ($renameOkay === false || $fileExists === false) { + \OC_Log::write('webdav', '\OC\Files\Filesystem::rename() failed', \OC_Log::ERROR); + \OC\Files\Filesystem::unlink($partpath); + throw new Sabre_DAV_Exception(); + } // allow sync clients to send the mtime along in a header $mtime = OC_Request::hasModificationTime(); @@ -251,7 +258,7 @@ class OC_Connector_Sabre_Directory extends OC_Connector_Sabre_Node implements Sa * If the array is empty, all properties should be returned * * @param array $properties - * @return void + * @return array */ public function getProperties($properties) { $props = parent::getProperties($properties); @@ -261,4 +268,34 @@ class OC_Connector_Sabre_Directory extends OC_Connector_Sabre_Node implements Sa } return $props; } + + private function createFileChunked($name, $data) + { + $info = OC_FileChunking::decodeName($name); + if (empty($info)) { + throw new Sabre_DAV_Exception_NotImplemented(); + } + $chunk_handler = new OC_FileChunking($info); + $bytesWritten = $chunk_handler->store($info['index'], $data); + + //detect aborted upload + if (isset ($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PUT' ) { + if (isset($_SERVER['CONTENT_LENGTH'])) { + $expected = $_SERVER['CONTENT_LENGTH']; + if ($bytesWritten != $expected) { + $chunk_handler->remove($info['index']); + throw new Sabre_DAV_Exception_BadRequest( + 'expected filesize ' . $expected . ' got ' . $bytesWritten); + } + } + } + + if ($chunk_handler->isComplete()) { + $newPath = $this->path . '/' . $info['name']; + $chunk_handler->file_assemble($newPath); + return OC_Connector_Sabre_Node::getETagPropertyForPath($newPath); + } + + return null; + } } diff --git a/lib/connector/sabre/file.php b/lib/connector/sabre/file.php index 06ab73e3e4d..bbfb27a8a9e 100644 --- a/lib/connector/sabre/file.php +++ b/lib/connector/sabre/file.php @@ -53,6 +53,13 @@ class OC_Connector_Sabre_File extends OC_Connector_Sabre_Node implements Sabre_D // mark file as partial while uploading (ignored by the scanner) $partpath = $this->path . '.part'; + // if file is located in /Shared we write the part file to the users + // root folder because we can't create new files in /shared + // we extend the name with a random number to avoid overwriting a existing file + if (dirname($partpath) === '/Shared') { + $partpath = pathinfo($partpath, PATHINFO_FILENAME) . rand() . '.part'; + } + \OC\Files\Filesystem::file_put_contents($partpath, $data); //detect aborted upload @@ -69,7 +76,14 @@ class OC_Connector_Sabre_File extends OC_Connector_Sabre_Node implements Sabre_D } // rename to correct path - \OC\Files\Filesystem::rename($partpath, $this->path); + $renameOkay = \OC\Files\Filesystem::rename($partpath, $this->path); + $fileExists = \OC\Files\Filesystem::file_exists($this->path); + if ($renameOkay === false || $fileExists === false) { + \OC_Log::write('webdav', '\OC\Files\Filesystem::rename() failed', \OC_Log::ERROR); + \OC\Files\Filesystem::unlink($partpath); + throw new Sabre_DAV_Exception(); + } + //allow sync clients to send the mtime along in a header $mtime = OC_Request::hasModificationTime(); diff --git a/lib/connector/sabre/node.php b/lib/connector/sabre/node.php index 1ffa048d6b2..f6a1c56edb8 100644 --- a/lib/connector/sabre/node.php +++ b/lib/connector/sabre/node.php @@ -78,6 +78,11 @@ abstract class OC_Connector_Sabre_Node implements Sabre_DAV_INode, Sabre_DAV_IPr */ public function setName($name) { + // rename is only allowed if the update privilege is granted + if (!\OC\Files\Filesystem::isUpdatable($this->path)) { + throw new \Sabre_DAV_Exception_Forbidden(); + } + list($parentPath, ) = Sabre_DAV_URLUtil::splitPath($this->path); list(, $newName) = Sabre_DAV_URLUtil::splitPath($name); @@ -135,6 +140,12 @@ abstract class OC_Connector_Sabre_Node implements Sabre_DAV_INode, Sabre_DAV_IPr * Even if the modification time is set to a custom value the access time is set to now. */ public function touch($mtime) { + + // touch is only allowed if the update privilege is granted + if (!\OC\Files\Filesystem::isUpdatable($this->path)) { + throw new \Sabre_DAV_Exception_Forbidden(); + } + \OC\Files\Filesystem::touch($this->path, $mtime); } diff --git a/lib/db.php b/lib/db.php index 56fa1ce43e2..84b05dfe6e5 100644 --- a/lib/db.php +++ b/lib/db.php @@ -236,7 +236,7 @@ class OC_DB { // Prepare options array $options = array( - 'portability' => MDB2_PORTABILITY_ALL - MDB2_PORTABILITY_FIX_CASE, + 'portability' => MDB2_PORTABILITY_ALL - MDB2_PORTABILITY_FIX_CASE - MDB2_PORTABILITY_RTRIM, 'log_line_break' => '<br>', 'idxname_format' => '%s', 'debug' => true, @@ -427,7 +427,56 @@ class OC_DB { } return false; } - + + /** + * @brief execute a prepared statement, on error write log and throw exception + * @param mixed $stmt OC_DB_StatementWrapper, + * an array with 'sql' and optionally 'limit' and 'offset' keys + * .. or a simple sql query string + * @param array $parameters + * @return MDB2_Result|PDOStatementWrapper|bool|int result + * @throws DatabaseException + */ + static public function executeAudited( $stmt, array $parameters = null) { + if (is_string($stmt)) { + // convert to an array with 'sql' + if (stripos($stmt,'LIMIT') !== false) { //OFFSET requires LIMIT, se we only neet to check for LIMIT + // TODO try to convert LIMIT OFFSET notation to parameters, see fixLimitClauseForMSSQL + $message = 'LIMIT and OFFSET are forbidden for portability reasons,' + . ' pass an array with \'limit\' and \'offset\' instead'; + throw new DatabaseException($message, $stmt); + } + $stmt = array('sql' => $stmt, 'limit' => null, 'offset' => null); + } + if (is_array($stmt)){ + // convert to prepared statement + if ( ! array_key_exists('sql', $stmt) ) { + $message = 'statement array must at least contain key \'sql\''; + throw new DatabaseException($message, ''); + } + if ( ! array_key_exists('limit', $stmt) ) { + $stmt['limit'] = null; + } + if ( ! array_key_exists('limit', $stmt) ) { + $stmt['offset'] = null; + } + $stmt = self::prepare($stmt['sql'], $stmt['limit'], $stmt['offset']); + } + if ($stmt instanceof PDOStatementWrapper || $stmt instanceof MDB2_Statement_Common) { + /** @var $stmt PDOStatementWrapper|MDB2_Statement_Common */ + $result = $stmt->execute($parameters); + self::raiseExceptionOnError($result, 'Could not execute statement', $parameters); + } else { + if (is_object($stmt)) { + $message = 'Expected a prepared statement or array got ' . get_class($stmt); + } else { + $message = 'Expected a prepared statement or array got ' . gettype($stmt); + } + throw new DatabaseException($message, ''); + } + return $result; + } + /** * @brief gets last value of autoincrement * @param string $table The optional table name (will replace *PREFIX*) and add sequence suffix @@ -512,9 +561,29 @@ class OC_DB { * TODO: write more documentation */ public static function createDbFromStructure( $file ) { - $CONFIG_DBNAME = OC_Config::getValue( "dbname", "owncloud" ); - $CONFIG_DBTABLEPREFIX = OC_Config::getValue( "dbtableprefix", "oc_" ); - $CONFIG_DBTYPE = OC_Config::getValue( "dbtype", "sqlite" ); + $CONFIG_DBNAME = OC_Config::getValue('dbname', 'owncloud'); + $CONFIG_DBTABLEPREFIX = OC_Config::getValue('dbtableprefix', 'oc_'); + $CONFIG_DBTYPE = OC_Config::getValue('dbtype', 'sqlite'); + $CONFIG_DBHOST = OC_Config::getValue('dbhost', ''); + + if( $CONFIG_DBTYPE === 'oci' + && $CONFIG_DBNAME === '' + && ! empty($CONFIG_DBHOST) + ) { + // we are connecting by user name, pwd and SID (host) + $CONFIG_DBUSER = OC_Config::getValue('dbuser', ''); + $CONFIG_DBPASSWORD = OC_Config::getValue('dbpassword'); + if ($CONFIG_DBUSER !== '' + && $CONFIG_DBPASSWORD !== '' + ) { + // use dbuser as dbname + $CONFIG_DBNAME = $CONFIG_DBUSER; + } else { + throw new DatabaseException('Please specify ' + .'username and password when ' + .'connecting via SID as the hostname.', ''); + } + } // cleanup the cached queries self::$preparedQueries = array(); @@ -543,6 +612,8 @@ class OC_DB { file_put_contents( $file2, $content ); + \OC_Log::write('db','creating table from schema: '.$content,\OC_Log::DEBUG); + // Try to create tables $definition = self::$schema->parseDatabaseDefinitionFile( $file2 ); @@ -735,7 +806,8 @@ class OC_DB { $stmt = self::prepare($query); $result = $stmt->execute($inserts); if (self::isError($result)) { - OC_Log::write('core', self::getErrorMessage($result), OC_Log::FATAL); + $entry = self::getErrorMessage($result); + OC_Log::write('core', $entry, OC_Log::FATAL); OC_Template::printErrorPage( $entry ); } @@ -1001,6 +1073,41 @@ class OC_DB { return false; } + /** + * check if a result is an error, writes a log entry and throws an exception, works with MDB2 and PDOException + * @param mixed $result + * @param string $message + * @return void + * @throws DatabaseException + */ + public static function raiseExceptionOnError($result, $message = null, array $params = null) { + if(self::isError($result)) { + if ($message === null) { + $message = self::getErrorMessage($result); + } else { + $message .= ', Root cause:' . self::getErrorMessage($result); + } + if ($params) { + $message .= ', params: ' . json_encode($params); + } + throw new DatabaseException($message, self::getErrorCode($result)); + } + } + + /** + * @param mixed $error + * @return int|mixed|null + */ + public static function getErrorCode($error) { + $code = null; + if ( self::$backend==self::BACKEND_MDB2 and PEAR::isError($error) ) { + /** @var $error PEAR_Error */ + $code = $error->getCode(); + } elseif ( self::$backend==self::BACKEND_PDO and self::$PDO ) { + $code = self::$PDO->errorCode(); + } + return $code; + } /** * returns the error code and message as a string for logging @@ -1059,6 +1166,9 @@ class PDOStatementWrapper{ /** * make execute return the result or updated row count instead of a bool + * + * @param array $input + * @return $this|bool|int */ public function execute($input=array()) { $this->lastArguments = $input; @@ -1183,7 +1293,7 @@ class PDOStatementWrapper{ public function numRows() { $regex = '/^SELECT\s+(?:ALL\s+|DISTINCT\s+)?(?:.*?)\s+FROM\s+(.*)$/i'; if (preg_match($regex, $this->statement->queryString, $output) > 0) { - $query = OC_DB::prepare("SELECT COUNT(*) FROM {$output[1]}", PDO::FETCH_NUM); + $query = OC_DB::prepare("SELECT COUNT(*) FROM {$output[1]}"); return $query->execute($this->lastArguments)->fetchColumn(); }else{ return $this->statement->rowCount(); diff --git a/lib/filechunking.php b/lib/filechunking.php index e6d69273a44..bee6f702298 100644 --- a/lib/filechunking.php +++ b/lib/filechunking.php @@ -34,10 +34,19 @@ class OC_FileChunking { return $this->cache; } + /** + * Stores the given $data under the given $key - the number of stored bytes is returned + * + * @param $index + * @param $data + * @return int + */ public function store($index, $data) { $cache = $this->getCache(); $name = $this->getPrefix().$index; $cache->set($name, $data); + + return $cache->size($name); } public function isComplete() { @@ -58,12 +67,34 @@ class OC_FileChunking { $count = 0; for($i=0; $i < $this->info['chunkcount']; $i++) { $chunk = $cache->get($prefix.$i); - $cache->remove($prefix.$i); $count += fwrite($f, $chunk); } + + $this->cleanup(); return $count; } + /** + * Removes all chunks which belong to this transmission + */ + public function cleanup() { + $cache = $this->getCache(); + $prefix = $this->getPrefix(); + for($i=0; $i < $this->info['chunkcount']; $i++) { + $cache->remove($prefix.$i); + } + } + + /** + * Removes one specific chunk + * @param $index + */ + public function remove($index) { + $cache = $this->getCache(); + $prefix = $this->getPrefix(); + $cache->remove($prefix.$index); + } + public function signature_split($orgfile, $input) { $info = unpack('n', fread($input, 2)); $blocksize = $info[1]; diff --git a/lib/files/cache/cache.php b/lib/files/cache/cache.php index cb48ab446be..32cad6b84b2 100644 --- a/lib/files/cache/cache.php +++ b/lib/files/cache/cache.php @@ -52,13 +52,17 @@ class Cache { $this->storageId = md5($this->storageId); } - $query = \OC_DB::prepare('SELECT `numeric_id` FROM `*PREFIX*storages` WHERE `id` = ?'); - $result = $query->execute(array($this->storageId)); + $result = \OC_DB::executeAudited( + 'SELECT `numeric_id` FROM `*PREFIX*storages` WHERE `id` = ?', + array($this->storageId) + ); if ($row = $result->fetchRow()) { $this->numericId = $row['numeric_id']; } else { - $query = \OC_DB::prepare('INSERT INTO `*PREFIX*storages`(`id`) VALUES(?)'); - $query->execute(array($this->storageId)); + \OC_DB::executeAudited( + 'INSERT INTO `*PREFIX*storages`(`id`) VALUES(?)', + array($this->storageId) + ); $this->numericId = \OC_DB::insertid('*PREFIX*storages'); } } @@ -67,6 +71,19 @@ class Cache { return $this->numericId; } + public static function storageExists($storageId) { + if (strlen($storageId) > 64) { + $storageId = md5($storageId); + } + $query = \OC_DB::prepare('SELECT `numeric_id` FROM `*PREFIX*storages` WHERE `id` = ?'); + $result = $query->execute(array($storageId)); + if ($row = $result->fetchRow()) { + return true; + } else { + return false; + } + } + /** * normalize mimetypes * @@ -75,13 +92,17 @@ class Cache { */ public function getMimetypeId($mime) { if (!isset($this->mimetypeIds[$mime])) { - $query = \OC_DB::prepare('SELECT `id` FROM `*PREFIX*mimetypes` WHERE `mimetype` = ?'); - $result = $query->execute(array($mime)); + $result = \OC_DB::executeAudited( + 'SELECT `id` FROM `*PREFIX*mimetypes` WHERE `mimetype` = ?', + array($mime) + ); if ($row = $result->fetchRow()) { $this->mimetypeIds[$mime] = $row['id']; } else { - $query = \OC_DB::prepare('INSERT INTO `*PREFIX*mimetypes`(`mimetype`) VALUES(?)'); - $query->execute(array($mime)); + \OC_DB::executeAudited( + 'INSERT INTO `*PREFIX*mimetypes`(`mimetype`) VALUES(?)', + array($mime) + ); $this->mimetypeIds[$mime] = \OC_DB::insertid('*PREFIX*mimetypes'); } $this->mimetypes[$this->mimetypeIds[$mime]] = $mime; @@ -91,8 +112,10 @@ class Cache { public function getMimetype($id) { if (!isset($this->mimetypes[$id])) { - $query = \OC_DB::prepare('SELECT `mimetype` FROM `*PREFIX*mimetypes` WHERE `id` = ?'); - $result = $query->execute(array($id)); + $result = \OC_DB::executeAudited( + 'SELECT `mimetype` FROM `*PREFIX*mimetypes` WHERE `id` = ?', + array($id) + ); if ($row = $result->fetchRow()) { $this->mimetypes[$id] = $row['mimetype']; } else { @@ -119,10 +142,13 @@ class Cache { $where = 'WHERE `fileid` = ?'; $params = array($file); } - $query = \OC_DB::prepare( - 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted`, `unencrypted_size`, `etag` - FROM `*PREFIX*filecache` ' . $where); - $result = $query->execute($params); + $result = \OC_DB::executeAudited( + 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, + `mimetype`, `mimepart`, `size`, `mtime`, + `encrypted`, `unencrypted_size`, `etag` + FROM `*PREFIX*filecache` ' . $where, + $params + ); $data = $result->fetchRow(); //FIXME hide this HACK in the next database layer, or just use doctrine and get rid of MDB2 and PDO @@ -160,13 +186,16 @@ class Cache { public function getFolderContents($folder) { $fileId = $this->getId($folder); if ($fileId > -1) { - $query = \OC_DB::prepare( - 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted`, `unencrypted_size` , `etag` - FROM `*PREFIX*filecache` WHERE `parent` = ? ORDER BY `name` ASC'); - $result = $query->execute(array($fileId)); - if (\OC_DB::isError($result)) { - \OCP\Util::writeLog('cache', 'getFolderContents failed: ' . $result->getMessage(), \OCP\Util::ERROR); - } + $result = \OC_DB::executeAudited(' + SELECT `fileid`, `storage`, `path`, `parent`, + `name`, `mimetype`, `mimepart`, `size`, + `mtime`, `encrypted`, + `unencrypted_size`, `etag` + FROM `*PREFIX*filecache` + WHERE `parent` = ? + ORDER BY `name` ASC', + array($fileId) + ); $files = $result->fetchAll(); foreach ($files as &$file) { $file['mimetype'] = $this->getMimetype($file['mimetype']); @@ -216,12 +245,11 @@ class Cache { $params[] = $this->numericId; $valuesPlaceholder = array_fill(0, count($queryParts), '?'); - $query = \OC_DB::prepare('INSERT INTO `*PREFIX*filecache`(' . implode(', ', $queryParts) . ')' - . ' VALUES(' . implode(', ', $valuesPlaceholder) . ')'); - $result = $query->execute($params); - if (\OC_DB::isError($result)) { - \OCP\Util::writeLog('cache', 'Insert to cache failed: ' . $result->getMessage(), \OCP\Util::ERROR); - } + \OC_DB::executeAudited(' + INSERT INTO `*PREFIX*filecache`(' . implode(', ', $queryParts) . ') + VALUES(' . implode(', ', $valuesPlaceholder) . ')', + $params + ); return (int)\OC_DB::insertid('*PREFIX*filecache'); } @@ -247,9 +275,12 @@ class Cache { list($queryParts, $params) = $this->buildParts($data); $params[] = $id; - $query = \OC_DB::prepare('UPDATE `*PREFIX*filecache` SET ' . implode(' = ?, ', $queryParts) . '=?' - . ' WHERE `fileid` = ?'); - $query->execute($params); + \OC_DB::executeAudited(' + UPDATE `*PREFIX*filecache` + SET ' . implode(' = ?, ', $queryParts) . '=? + WHERE `fileid` = ?', + $params + ); } /** @@ -294,8 +325,11 @@ class Cache { $pathHash = md5($file); - $query = \OC_DB::prepare('SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?'); - $result = $query->execute(array($this->numericId, $pathHash)); + $result = \OC_DB::executeAudited(' + SELECT `fileid` FROM `*PREFIX*filecache` + WHERE `storage` = ? AND `path_hash` = ?', + array($this->numericId, $pathHash) + ); if ($row = $result->fetchRow()) { return $row['fileid']; @@ -345,8 +379,10 @@ class Cache { $this->remove($child['path']); } } - $query = \OC_DB::prepare('DELETE FROM `*PREFIX*filecache` WHERE `fileid` = ?'); - $query->execute(array($entry['fileid'])); + \OC_DB::executeAudited(' + DELETE FROM `*PREFIX*filecache` WHERE `fileid` = ?', + array($entry['fileid']) + ); $permissionsCache = new Permissions($this->storageId); $permissionsCache->remove($entry['fileid']); @@ -369,32 +405,48 @@ class Cache { if ($sourceData['mimetype'] === 'httpd/unix-directory') { //find all child entries - $query = \OC_DB::prepare('SELECT `path`, `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path` LIKE ?'); - $result = $query->execute(array($this->getNumericStorageId(), $source . '/%')); + $result = \OC_DB::executeAudited(' + SELECT `path`, `fileid` FROM `*PREFIX*filecache` + WHERE `storage` = ? AND `path` LIKE ?', + array($this->getNumericStorageId(), $source . '/%') + ); $childEntries = $result->fetchAll(); $sourceLength = strlen($source); $query = \OC_DB::prepare('UPDATE `*PREFIX*filecache` SET `path` = ?, `path_hash` = ? WHERE `fileid` = ?'); foreach ($childEntries as $child) { $targetPath = $target . substr($child['path'], $sourceLength); - $query->execute(array($targetPath, md5($targetPath), $child['fileid'])); + \OC_DB::executeAudited($query, + array( + $targetPath, + md5($targetPath), + $child['fileid'] + ) + ); } } - $query = \OC_DB::prepare('UPDATE `*PREFIX*filecache` SET `path` = ?, `path_hash` = ?, `name` = ?, `parent` =?' - . ' WHERE `fileid` = ?'); - $query->execute(array($target, md5($target), basename($target), $newParentId, $sourceId)); + \OC_DB::executeAudited(' + UPDATE `*PREFIX*filecache` + SET `path` = ?, `path_hash` = ?, `name` = ?, `parent` =? + WHERE `fileid` = ?', + array($target, md5($target), basename($target), $newParentId, $sourceId) + ); } /** * remove all entries for files that are stored on the storage from the cache */ public function clear() { - $query = \OC_DB::prepare('DELETE FROM `*PREFIX*filecache` WHERE `storage` = ?'); - $query->execute(array($this->numericId)); + \OC_DB::executeAudited(' + DELETE FROM `*PREFIX*filecache` WHERE `storage` = ?', + array($this->numericId) + ); - $query = \OC_DB::prepare('DELETE FROM `*PREFIX*storages` WHERE `id` = ?'); - $query->execute(array($this->storageId)); + \OC_DB::executeAudited(' + DELETE FROM `*PREFIX*storages` WHERE `id` = ?', + array($this->storageId) + ); } /** @@ -407,11 +459,12 @@ class Cache { $file = $this->normalize($file); $pathHash = md5($file); - $query = \OC_DB::prepare('SELECT `size` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?'); - $result = $query->execute(array($this->numericId, $pathHash)); - if( \OC_DB::isError($result)) { - \OCP\Util::writeLog('cache', 'get status failed: ' . $result->getMessage(), \OCP\Util::ERROR); - } + $result = \OC_DB::executeAudited(' + SELECT `size` + FROM `*PREFIX*filecache` + WHERE `storage` = ? AND `path_hash` = ?', + array($this->numericId, $pathHash) + ); if ($row = $result->fetchRow()) { if ((int)$row['size'] === -1) { return self::SHALLOW; @@ -437,11 +490,14 @@ class Cache { // normalize pattern $pattern = $this->normalize($pattern); - $query = \OC_DB::prepare(' - SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted`, `unencrypted_size`, `etag` - FROM `*PREFIX*filecache` WHERE `name` LIKE ? AND `storage` = ?' + $result = \OC_DB::executeAudited(' + SELECT `fileid`, `storage`, `path`, `parent`, `name`, + `mimetype`, `mimepart`, `size`, `mtime`, + `encrypted`, `unencrypted_size`, `etag` + FROM `*PREFIX*filecache` + WHERE `name` LIKE ? AND `storage` = ?', + array($pattern, $this->numericId) ); - $result = $query->execute(array($pattern, $this->numericId)); $files = array(); while ($row = $result->fetchRow()) { $row['mimetype'] = $this->getMimetype($row['mimetype']); @@ -463,12 +519,17 @@ class Cache { } else { $where = '`mimepart` = ?'; } - $query = \OC_DB::prepare(' - SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted`, `unencrypted_size`, `etag` - FROM `*PREFIX*filecache` WHERE ' . $where . ' AND `storage` = ?' - ); $mimetype = $this->getMimetypeId($mimetype); - $result = $query->execute(array($mimetype, $this->numericId)); + + $result = \OC_DB::executeAudited(' + SELECT `fileid`, `storage`, `path`, `parent`, `name`, + `mimetype`, `mimepart`, `size`, `mtime`, + `encrypted`, `unencrypted_size`, `etag` + FROM `*PREFIX*filecache` + WHERE ' . $where . ' AND `storage` = ?', + array($mimetype, $this->numericId) + ); + $files = array(); while ($row = $result->fetchRow()) { $row['mimetype'] = $this->getMimetype($row['mimetype']); @@ -505,9 +566,12 @@ class Cache { $entry = $this->get($path); if ($entry && $entry['mimetype'] === 'httpd/unix-directory') { $id = $entry['fileid']; - $query = \OC_DB::prepare('SELECT SUM(`size`), MIN(`size`) FROM `*PREFIX*filecache` '. - 'WHERE `parent` = ? AND `storage` = ?'); - $result = $query->execute(array($id, $this->getNumericStorageId())); + $result = \OC_DB::executeAudited(' + SELECT SUM(`size`), MIN(`size`) + FROM `*PREFIX*filecache` + WHERE `parent` = ? AND `storage` = ?', + array($id, $this->getNumericStorageId()) + ); if ($row = $result->fetchRow()) { list($sum, $min) = array_values($row); $sum = (int)$sum; @@ -520,7 +584,7 @@ class Cache { if ($entry['size'] !== $totalSize) { $this->update($id, array('size' => $totalSize)); } - + } } return $totalSize; @@ -532,8 +596,12 @@ class Cache { * @return int[] */ public function getAll() { - $query = \OC_DB::prepare('SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ?'); - $result = $query->execute(array($this->numericId)); + $result = \OC_DB::executeAudited(' + SELECT `fileid` + FROM `*PREFIX*filecache` + WHERE `storage` = ?', + array($this->numericId) + ); $ids = array(); while ($row = $result->fetchRow()) { $ids[] = $row['fileid']; @@ -551,12 +619,15 @@ class Cache { * @return string|bool the path of the folder or false when no folder matched */ public function getIncomplete() { - $query = \OC_DB::prepare('SELECT `path` FROM `*PREFIX*filecache`' - . ' WHERE `storage` = ? AND `size` = -1 ORDER BY `fileid` DESC',1); - $result = $query->execute(array($this->numericId)); - if (\OC_DB::isError($result)) { - \OCP\Util::writeLog('cache', 'getIncomplete failed: ' . $result->getMessage(), \OCP\Util::ERROR); - } + $query = \OC_DB::prepare(' + SELECT `path` + FROM `*PREFIX*filecache` + WHERE `storage` = ? AND `size` = -1 + ORDER BY `fileid` DESC', 1); + $result = \OC_DB::executeAudited( + $query, + array($this->numericId) + ); if ($row = $result->fetchRow()) { return $row['path']; } else { @@ -570,8 +641,12 @@ class Cache { * @return array, first element holding the storage id, second the path */ static public function getById($id) { - $query = \OC_DB::prepare('SELECT `storage`, `path` FROM `*PREFIX*filecache` WHERE `fileid` = ?'); - $result = $query->execute(array($id)); + $result = \OC_DB::executeAudited(' + SELECT `storage`, `path` + FROM `*PREFIX*filecache` + WHERE `fileid` = ?', + array($id) + ); if ($row = $result->fetchRow()) { $numericId = $row['storage']; $path = $row['path']; @@ -579,8 +654,12 @@ class Cache { return null; } - $query = \OC_DB::prepare('SELECT `id` FROM `*PREFIX*storages` WHERE `numeric_id` = ?'); - $result = $query->execute(array($numericId)); + $result = \OC_DB::executeAudited(' + SELECT `id` + FROM `*PREFIX*storages` + WHERE `numeric_id` = ?', + array($numericId) + ); if ($row = $result->fetchRow()) { return array($row['id'], $path); } else { @@ -594,6 +673,7 @@ class Cache { * @return string */ public function normalize($path) { - return \OC_Util::normalizeUnicode($path); - }} + } + +} diff --git a/lib/files/cache/updater.php b/lib/files/cache/updater.php index 9781c76d69c..c9d67f8ee56 100644 --- a/lib/files/cache/updater.php +++ b/lib/files/cache/updater.php @@ -78,30 +78,58 @@ class Updater { } /** + * @brief get file owner and path + * @param string $filename + * @return array with the oweners uid and the owners path + */ + private static function getUidAndFilename($filename) { + $uid = \OC\Files\Filesystem::getOwner($filename); + \OC\Files\Filesystem::initMountPoints($uid); + if ( $uid != \OCP\User::getUser() ) { + $info = \OC\Files\Filesystem::getFileInfo($filename); + $ownerView = new \OC\Files\View('/'.$uid.'/files'); + $filename = $ownerView->getPath($info['fileid']); + } + return array($uid, '/files/'.$filename); + } + + /** * Update the mtime and ETag of all parent folders * * @param string $path * @param string $time */ static public function correctFolder($path, $time) { + if ($path !== '' && $path !== '/') { - $parent = dirname($path); - if ($parent === '.') { - $parent = ''; - } + + list($owner, $realPath) = self::getUidAndFilename(dirname($path)); + /** * @var \OC\Files\Storage\Storage $storage * @var string $internalPath */ - list($storage, $internalPath) = self::resolvePath($parent); - if ($storage) { - $cache = $storage->getCache(); - $id = $cache->getId($internalPath); - if ($id !== -1) { - $cache->update($id, array('mtime' => $time, 'etag' => $storage->getETag($internalPath))); - self::correctFolder($parent, $time); + + $view = new \OC\Files\View('/' . $owner); + + list($storage, $internalPath) = $view->resolvePath($realPath); + $cache = $storage->getCache(); + $id = $cache->getId($internalPath); + + while ($id !== -1) { + $cache->update($id, array('mtime' => $time, 'etag' => $storage->getETag($internalPath))); + $realPath = dirname($realPath); + // check storage for parent in case we change the storage in this step + list($storage, $internalPath) = $view->resolvePath($realPath); + if ($internalPath) { + $cache = $storage->getCache(); + $id = $cache->getId($internalPath); + } else { + $id = -1; } + } + } } diff --git a/lib/files/filesystem.php b/lib/files/filesystem.php index 4b445588a65..1850d50f971 100644 --- a/lib/files/filesystem.php +++ b/lib/files/filesystem.php @@ -221,7 +221,11 @@ class Filesystem { $parser = new \OC\ArrayParser(); $root = \OC_User::getHome($user); - self::mount('\OC\Files\Storage\Local', array('datadir' => $root), $user); + if (\OC\Files\Cache\Cache::storageExists('local::' . $root . '/') or is_null($user)) { + self::mount('\OC\Files\Storage\Local', array('datadir' => $root), $user); + } else { + self::mount('\OC\Files\Storage\Home', array('user' => $user, 'datadir' => $root), $user); + } $datadir = \OC_Config::getValue("datadirectory", \OC::$SERVERROOT . "/data"); //move config file to it's new position @@ -585,18 +589,32 @@ class Filesystem { } //no windows style slashes $path = str_replace('\\', '/', $path); + //add leading slash if ($path[0] !== '/') { $path = '/' . $path; } - //remove duplicate slashes - while (strpos($path, '//') !== false) { - $path = str_replace('//', '/', $path); + + // remove '/./' + // ugly, but str_replace() can't replace them all in one go + // as the replacement itself is part of the search string + // which will only be found during the next iteration + while (strpos($path, '/./') !== false) { + $path = str_replace('/./', '/', $path); } + // remove sequences of slashes + $path = preg_replace('#/{2,}#', '/', $path); + //remove trailing slash if ($stripTrailingSlash and strlen($path) > 1 and substr($path, -1, 1) === '/') { $path = substr($path, 0, -1); } + + // remove trailing '/.' + if (substr($path, -2) == '/.') { + $path = substr($path, 0, -2); + } + //normalize unicode if possible $path = \OC_Util::normalizeUnicode($path); return $path; diff --git a/lib/files/storage/home.php b/lib/files/storage/home.php new file mode 100644 index 00000000000..533fe5fe390 --- /dev/null +++ b/lib/files/storage/home.php @@ -0,0 +1,31 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Storage; + +/** + * Specialized version of Local storage for home directory usage + */ +class Home extends Local { + + /** + * @var string $user + */ + protected $user; + + public function __construct($arguments) { + $this->user = $arguments['user']; + $datadir = $arguments['datadir']; + + parent::__construct(array('datadir' => $datadir)); + } + + public function getId() { + return 'home::' . $this->user; + } +} diff --git a/lib/files/view.php b/lib/files/view.php index 1e2a95751ff..65c5a527a1b 100644 --- a/lib/files/view.php +++ b/lib/files/view.php @@ -110,7 +110,9 @@ class View { * @return array consisting of the storage and the internal path */ public function resolvePath($path) { - return Filesystem::resolvePath($this->getAbsolutePath($path)); + $a = $this->getAbsolutePath($path); + $p = Filesystem::normalizePath($a); + return Filesystem::resolvePath($p); } /** @@ -252,7 +254,11 @@ class View { $hooks[] = 'write'; } - return $this->basicOperation('touch', $path, $hooks, $mtime); + $result = $this->basicOperation('touch', $path, array('write'), $mtime); + if (!$result) { //if native touch fails, we emulate it by changing the mtime in the cache + $this->putFileInfo($path, array('mtime' => $mtime)); + } + return true; } public function file_get_contents($path) { @@ -698,7 +704,10 @@ class View { return false; } $defaultRoot = Filesystem::getRoot(); - return (strlen($this->fakeRoot) >= strlen($defaultRoot)) && (substr($this->fakeRoot, 0, strlen($defaultRoot)) === $defaultRoot); + if($this->fakeRoot === $defaultRoot){ + return true; + } + return (strlen($this->fakeRoot) > strlen($defaultRoot)) && (substr($this->fakeRoot, 0, strlen($defaultRoot) + 1) === $defaultRoot . '/'); } private function runHooks($hooks, $path, $post = false) { diff --git a/lib/helper.php b/lib/helper.php index ddd6ce9a149..819b1e87f4a 100644 --- a/lib/helper.php +++ b/lib/helper.php @@ -162,17 +162,35 @@ class OC_Helper { // Read the selected theme from the config file $theme = OC_Util::getTheme(); + //if a theme has a png but not an svg always use the png + $basename = substr(basename($image),0,-4); + // Check if the app is in the app folder if( file_exists( OC::$SERVERROOT."/themes/$theme/apps/$app/img/$image" )) { return OC::$WEBROOT."/themes/$theme/apps/$app/img/$image"; + }elseif( ! file_exists( OC::$SERVERROOT . "/themes/$theme/apps/$app/img/$basename.svg") + && file_exists( OC::$SERVERROOT . "/themes/$theme/apps/$app/img/$basename.png")) { + return OC::$WEBROOT . "/themes/$theme/apps/$app/img/$basename.png"; }elseif( file_exists(OC_App::getAppPath($app)."/img/$image" )) { return OC_App::getAppWebPath($app)."/img/$image"; + }elseif( ! file_exists( OC_App::getAppPath($app) . "/img/$basename.svg") + && file_exists( OC_App::getAppPath($app) . "/img/$basename.png")) { + return OC_App::getAppPath($app) . "/img/$basename.png"; }elseif( !empty( $app ) and file_exists( OC::$SERVERROOT."/themes/$theme/$app/img/$image" )) { return OC::$WEBROOT."/themes/$theme/$app/img/$image"; + }elseif( !empty($app) and (!file_exists( OC::$SERVERROOT . "/themes/$theme/$app/img/$basename.svg") + && file_exists( OC::$SERVERROOT . "/themes/$theme/$app/img/$basename.png"))) { + return OC::$WEBROOT . "/themes/$theme/$app/img/$basename.png"; }elseif( !empty( $app ) and file_exists( OC::$SERVERROOT."/$app/img/$image" )) { return OC::$WEBROOT."/$app/img/$image"; + }elseif( !empty($app) and (!file_exists( OC::$SERVERROOT . "/$app/img/$basename.svg") + && file_exists( OC::$SERVERROOT . "/$app/img/$basename.png"))) { + return OC::$WEBROOT . "/$app/img/$basename.png"; }elseif( file_exists( OC::$SERVERROOT."/themes/$theme/core/img/$image" )) { return OC::$WEBROOT."/themes/$theme/core/img/$image"; + }elseif( ! file_exists( OC::$SERVERROOT . "/themes/$theme/core/img/$basename.svg") + && file_exists( OC::$SERVERROOT . "/themes/$theme/core/img/$basename.png")) { + return OC::$WEBROOT . "/themes/$theme/core/img/$basename.png"; }elseif( file_exists( OC::$SERVERROOT."/core/img/$image" )) { return OC::$WEBROOT."/core/img/$image"; }else{ @@ -517,11 +535,11 @@ class OC_Helper { * copy the contents of one stream to another * @param resource $source * @param resource $target - * @return int the number of bytes copied + * @return array the number of bytes copied and result */ public static function streamCopy($source, $target) { - if(!$source or !$target) { - return false; + if (!$source or !$target) { + return array(0, false); } $result = true; $count = 0; diff --git a/lib/installer.php b/lib/installer.php index 467e486dd29..3299fb07390 100644 --- a/lib/installer.php +++ b/lib/installer.php @@ -196,7 +196,11 @@ class OC_Installer{ //install the database if(is_file($basedir.'/appinfo/database.xml')) { - OC_DB::createDbFromStructure($basedir.'/appinfo/database.xml'); + if (OC_Appconfig::getValue($info['id'], 'installed_version') === null) { + OC_DB::createDbFromStructure($basedir.'/appinfo/database.xml'); + } else { + OC_DB::updateDbFromStructure($basedir.'/appinfo/database.xml'); + } } //run appinfo/install.php diff --git a/lib/log.php b/lib/log.php index 3f3334801e5..e31e2a893c4 100644 --- a/lib/log.php +++ b/lib/log.php @@ -36,6 +36,8 @@ class OC_Log { call_user_func(array(self::$class, 'init')); } $log_class=self::$class; + // remove username/passswords from URLs before writing the to the log file + $message = preg_replace('/\/\/(.*):(.*)@/', '//xxx:xxx@', $message); $log_class::write($app, $message, $level); } } diff --git a/lib/log/owncloud.php b/lib/log/owncloud.php index d16b9537a16..aecf78b2f3d 100644 --- a/lib/log/owncloud.php +++ b/lib/log/owncloud.php @@ -35,7 +35,17 @@ class OC_Log_Owncloud { public static function init() { $defaultLogFile = OC_Config::getValue("datadirectory", OC::$SERVERROOT.'/data').'/owncloud.log'; self::$logFile = OC_Config::getValue("logfile", $defaultLogFile); - if (!file_exists(self::$logFile)) { + + /* + * Fall back to default log file if specified logfile does not exist + * and can not be created. Error suppression is required in order to + * not end up in the error handler which will try to log the error. + * A better solution (compared to error suppression) would be checking + * !is_writable(dirname(self::$logFile)) before touch(), but + * is_writable() on directories used to be pretty unreliable on Windows + * for at least some time. + */ + if (!file_exists(self::$logFile) && !@touch(self::$logFile)) { self::$logFile = $defaultLogFile; } } @@ -51,8 +61,16 @@ class OC_Log_Owncloud { if($level>=$minLevel) { // default to ISO8601 $format = OC_Config::getValue('logdateformat', 'c'); - $time = date($format, time()); - $entry=array('app'=>$app, 'message'=>$message, 'level'=>$level, 'time'=> $time); + $logtimezone=OC_Config::getValue( "logtimezone", 'UTC' ); + try { + $timezone = new DateTimeZone($logtimezone); + } catch (Exception $e) { + $timezone = new DateTimeZone('UTC'); + } + $time = new DateTime(null, $timezone); + $entry=array('app'=>$app, 'message'=>$message, 'level'=>$level, 'time'=> $time->format($format)); + + $handle = @fopen(self::$logFile, 'a'); if ($handle) { fwrite($handle, json_encode($entry)."\n"); diff --git a/lib/public/share.php b/lib/public/share.php index 0a88ea4ea44..7ba5fa50246 100644 --- a/lib/public/share.php +++ b/lib/public/share.php @@ -155,13 +155,13 @@ class Share { while ($source !== -1) { - // Fetch all shares of this file path from DB + // Fetch all shares with another user $query = \OC_DB::prepare( 'SELECT `share_with` FROM `*PREFIX*share` WHERE - `item_source` = ? AND `share_type` = ?' + `item_source` = ? AND `share_type` = ? AND `item_type` IN (\'file\', \'folder\')' ); $result = $query->execute(array($source, self::SHARE_TYPE_USER)); @@ -180,7 +180,7 @@ class Share { FROM `*PREFIX*share` WHERE - `item_source` = ? AND `share_type` = ?' + `item_source` = ? AND `share_type` = ? AND `item_type` IN (\'file\', \'folder\')' ); $result = $query->execute(array($source, self::SHARE_TYPE_GROUP)); @@ -201,7 +201,7 @@ class Share { FROM `*PREFIX*share` WHERE - `item_source` = ? AND `share_type` = ?' + `item_source` = ? AND `share_type` = ? AND `item_type` IN (\'file\', \'folder\')' ); $result = $query->execute(array($source, self::SHARE_TYPE_LINK)); @@ -293,7 +293,18 @@ class Share { if (\OC_DB::isError($result)) { \OC_Log::write('OCP\Share', \OC_DB::getErrorMessage($result) . ', token=' . $token, \OC_Log::ERROR); } - return $result->fetchRow(); + $row = $result->fetchRow(); + + if (!empty($row['expiration'])) { + $now = new \DateTime(); + $expirationDate = new \DateTime($row['expiration'], new \DateTimeZone('UTC')); + if ($now > $expirationDate) { + self::delete($row['id']); + return false; + } + } + + return $row; } /** @@ -749,10 +760,10 @@ class Share { /** * @brief Get the backend class for the specified item type - * @param string Item type - * @return Sharing backend object + * @param string $itemType + * @return Share_Backend */ - private static function getBackend($itemType) { + public static function getBackend($itemType) { if (isset(self::$backends[$itemType])) { return self::$backends[$itemType]; } else if (isset(self::$backendTypes[$itemType]['class'])) { diff --git a/lib/search/provider/file.php b/lib/search/provider/file.php index 4d88c2a87f1..9bd50931517 100644 --- a/lib/search/provider/file.php +++ b/lib/search/provider/file.php @@ -10,6 +10,7 @@ class OC_Search_Provider_File extends OC_Search_Provider{ $mime = $fileData['mimetype']; $name = basename($path); + $container = dirname($path); $text = ''; $skip = false; if($mime=='httpd/unix-directory') { @@ -37,7 +38,7 @@ class OC_Search_Provider_File extends OC_Search_Provider{ } } if(!$skip) { - $results[] = new OC_Search_Result($name, $text, $link, $type); + $results[] = new OC_Search_Result($name, $text, $link, $type, $container); } } return $results; diff --git a/lib/search/result.php b/lib/search/result.php index 08beaea151c..6a6300bc2fc 100644 --- a/lib/search/result.php +++ b/lib/search/result.php @@ -15,10 +15,11 @@ class OC_Search_Result{ * @param string $link link for the result * @param string $type the type of result as human readable string ('File', 'Music', etc) */ - public function __construct($name, $text, $link, $type) { + public function __construct($name, $text, $link, $type, $container) { $this->name=$name; $this->text=$text; $this->link=$link; $this->type=$type; + $this->container=$container; } } diff --git a/lib/setup.php b/lib/setup.php index 5d5ef77899f..2a43f7b4475 100644 --- a/lib/setup.php +++ b/lib/setup.php @@ -328,7 +328,13 @@ class OC_Setup { $connection_string = "host='$e_host' dbname=postgres user='$e_user' password='$e_password'"; $connection = @pg_connect($connection_string); if(!$connection) { - throw new Exception($l->t('PostgreSQL username and/or password not valid')); + // Try if we can connect to the DB with the specified name + $e_dbname = addslashes($dbname); + $connection_string = "host='$e_host' dbname='$e_dbname' user='$e_user' password='$e_password'"; + $connection = @pg_connect($connection_string); + + if(!$connection) + throw new DatabaseSetupException($l->t('PostgreSQL username and/or password not valid')); } $e_user = pg_escape_string($dbuser); //check for roles creation rights in postgresql diff --git a/lib/updater.php b/lib/updater.php index cbba543217b..d0ae1fb4715 100644 --- a/lib/updater.php +++ b/lib/updater.php @@ -27,7 +27,6 @@ class OC_Updater extends BasicEmitter { * @return array | bool */ static public function check($updaterUrl='http://apps.owncloud.com/updater.php') { - OC_Appconfig::setValue('core', 'lastupdatedat', microtime(true)); if ((\OC_Appconfig::getValue('core', 'lastupdatedat') + 1800) > time()) { return json_decode(\OC_Appconfig::getValue('core', 'lastupdateResult'), true); } @@ -75,7 +74,7 @@ class OC_Updater extends BasicEmitter { if(OC_Config::getValue('updatechecker', true)==true) { $data=OC_Updater::check(); - if(isset($data['version']) and $data['version']<>'') { + if(isset($data['version']) && !empty($data['version'])) { $txt='<span style="color:#AA0000; font-weight:bold;">' .$l->t('%s is available. Get <a href="%s">more information</a>', array($data['versionstring'], $data['web'])).'</span>'; diff --git a/lib/user.php b/lib/user.php index 55ffc26e77f..1bac4839aa9 100644 --- a/lib/user.php +++ b/lib/user.php @@ -372,6 +372,8 @@ class OC_User { return self::determineDisplayName($user); } else if( isset($_SESSION['display_name']) AND $_SESSION['display_name'] ) { return $_SESSION['display_name']; + } else if ( $user = self::getUser() ) { + return self::determineDisplayName($user); } else{ return false; @@ -637,20 +639,25 @@ class OC_User { public static function setMagicInCookie($username, $token) { $secure_cookie = OC_Config::getValue("forcessl", false); $expires = time() + OC_Config::getValue('remember_login_cookie_lifetime', 60*60*24*15); - setcookie("oc_username", $username, $expires, '', '', $secure_cookie); - setcookie("oc_token", $token, $expires, '', '', $secure_cookie, true); - setcookie("oc_remember_login", true, $expires, '', '', $secure_cookie); + setcookie("oc_username", $username, $expires, \OC::$WEBROOT, '', $secure_cookie); + setcookie("oc_token", $token, $expires, \OC::$WEBROOT, '', $secure_cookie, true); + setcookie("oc_remember_login", true, $expires, \OC::$WEBROOT, '', $secure_cookie); } /** * @brief Remove cookie for "remember username" */ public static function unsetMagicInCookie() { - unset($_COOKIE["oc_username"]); + unset($_COOKIE["oc_username"]); //TODO: DI unset($_COOKIE["oc_token"]); unset($_COOKIE["oc_remember_login"]); - setcookie("oc_username", null, -1); - setcookie("oc_token", null, -1); - setcookie("oc_remember_login", null, -1); + setcookie('oc_username', '', time()-3600, \OC::$WEBROOT); + setcookie('oc_token', '', time()-3600, \OC::$WEBROOT); + setcookie('oc_remember_login', '', time()-3600, \OC::$WEBROOT); + // old cookies might be stored under /webroot/ instead of /webroot + // and Firefox doesn't like it! + setcookie('oc_username', '', time()-3600, \OC::$WEBROOT . '/'); + setcookie('oc_token', '', time()-3600, \OC::$WEBROOT . '/'); + setcookie('oc_remember_login', '', time()-3600, \OC::$WEBROOT . '/'); } } diff --git a/lib/user/database.php b/lib/user/database.php index 35cc68e3c05..cbdccd4503a 100644 --- a/lib/user/database.php +++ b/lib/user/database.php @@ -85,7 +85,7 @@ class OC_User_Database extends OC_User_Backend { */ public function deleteUser( $uid ) { // Delete user-group-relation - $query = OC_DB::prepare( 'DELETE FROM `*PREFIX*users` WHERE uid = ?' ); + $query = OC_DB::prepare( 'DELETE FROM `*PREFIX*users` WHERE `uid` = ?' ); $query->execute( array( $uid )); return true; } diff --git a/lib/util.php b/lib/util.php index 71921f0aedd..97e0fdba75b 100755 --- a/lib/util.php +++ b/lib/util.php @@ -77,7 +77,7 @@ class OC_Util { public static function getVersion() { // hint: We only can count up. Reset minor/patchlevel when // updating major/minor version number. - return array(5, 00, 21); + return array(5, 00, 25); } /** @@ -85,7 +85,7 @@ class OC_Util { * @return string */ public static function getVersionString() { - return '5.0.12 RC1'; + return '5.0.13'; } /** @@ -312,9 +312,27 @@ class OC_Util { } /** - * Check for correct file permissions of data directory - * @return array arrays with error messages and hints - */ + * @brief check if there are still some encrypted files stored + * @return boolean + */ + public static function encryptedFiles() { + //check if encryption was enabled in the past + $encryptedFiles = false; + if (OC_App::isEnabled('files_encryption') === false) { + $view = new OC\Files\View('/' . OCP\User::getUser()); + if ($view->file_exists('/files_encryption/keyfiles')) { + $encryptedFiles = true; + } + } + + return $encryptedFiles; + } + + /** + * @brief Check for correct file permissions of data directory + * @paran string $dataDirectory + * @return array arrays with error messages and hints + */ public static function checkDataDirectoryPermissions($dataDirectory) { $errors = array(); if (stristr(PHP_OS, 'WIN')) { @@ -354,6 +372,7 @@ class OC_Util { } $parameters['alt_login'] = OC_App::getAlternativeLogIns(); + $parameters['rememberLoginAllowed'] = self::rememberLoginAllowed(); OC_Template::printGuestPage("", "login", $parameters); } @@ -385,6 +404,7 @@ class OC_Util { * Check if the user is a admin, redirects to home if not */ public static function checkAdminUser() { + \OC_Util::checkLoggedIn(); if( !OC_User::isAdminUser(OC_User::getUser())) { header( 'Location: '.OC_Helper::linkToAbsolute( '', 'index.php' )); exit(); @@ -392,10 +412,32 @@ class OC_Util { } /** - * Check if the user is a subadmin, redirects to home if not + * Check if it is allowed to remember login. + * + * @note Every app can set 'rememberlogin' to 'false' to disable the remember login feature + * + * @return bool + */ + public static function rememberLoginAllowed() { + + $apps = OC_App::getEnabledApps(); + + foreach ($apps as $app) { + $appInfo = OC_App::getAppInfo($app); + if (isset($appInfo['rememberlogin']) && $appInfo['rememberlogin'] === 'false') { + return false; + } + + } + return true; + } + + /** + * @brief Check if the user is a subadmin, redirects to home if not * @return array $groups where the current user is subadmin */ public static function checkSubAdminUser() { + \OC_Util::checkLoggedIn(); if(!OC_SubAdmin::isSubAdmin(OC_User::getUser())) { header( 'Location: '.OC_Helper::linkToAbsolute( '', 'index.php' )); exit(); @@ -536,7 +578,6 @@ class OC_Util { return $value; } - /** * Check if the htaccess file is working by creating a test file in the data directory and trying to access via http */ @@ -677,7 +718,6 @@ class OC_Util { } } - } /** diff --git a/search/js/result.js b/search/js/result.js index 78fa8efc8e9..2bad993f4a4 100644 --- a/search/js/result.js +++ b/search/js/result.js @@ -8,15 +8,23 @@ OC.search.catagorizeResults=function(results){ types[type].push(results[i]); } return types; -} +}; OC.search.hide=function(){ $('#searchresults').hide(); if($('#searchbox').val().length>2){ $('#searchbox').val(''); + if (FileList && typeof FileList.unfilter === 'function') { //TODO add hook system + FileList.unfilter(); + } }; + if ($('#searchbox').val().length === 0) { + if (FileList && typeof FileList.unfilter === 'function') { //TODO add hook system + FileList.unfilter(); } + } +}; OC.search.showResults=function(results){ - if(results.length==0){ + if(results.length === 0){ return; } if(!OC.search.showResults.loaded){ @@ -30,6 +38,9 @@ OC.search.showResults=function(results){ }); $(document).click(function(event){ OC.search.hide(); + if (FileList && typeof FileList.unfilter === 'function') { //TODO add hook system + FileList.unfilter(); + } }); OC.search.lastResults=results; OC.search.showResults(results); @@ -39,30 +50,52 @@ OC.search.showResults=function(results){ $('#searchresults').show(); $('#searchresults tr.result').remove(); var index=0; - for(var name in types){ - var type=types[name]; + for(var typeid in types){ + var type=types[typeid]; if(type.length>0){ for(var i=0;i<type.length;i++){ var row=$('#searchresults tr.template').clone(); row.removeClass('template'); row.addClass('result'); - if (i == 0){ - row.children('td.type').text(name); + row.data('type', typeid); + row.data('name', type[i].name); + row.data('text', type[i].text); + row.data('container', type[i].container); + if (i === 0){ + row.children('td.type').text(typeid); } - row.find('td.result a').attr('href',type[i].link); row.find('td.result div.name').text(type[i].name); row.find('td.result div.text').text(type[i].text); + if (type[i].container) { + var containerName = OC.basename(type[i].container); + if (containerName === '') { + containerName = '/'; + } + var containerLink = OC.linkTo('files', 'index.php') + +'?dir='+encodeURIComponent(type[i].container) + +'&scrollto='+encodeURIComponent(type[i].name); + row.find('td.result a') + .attr('href', containerLink) + .attr('title', t('core', 'Show in {folder}', {folder: containerName})); + } else { + row.find('td.result a').attr('href', type[i].link); + } row.data('index',index); index++; - if(OC.search.customResults[name]){//give plugins the ability to customize the entries in here - OC.search.customResults[name](row,type[i]); + if(OC.search.customResults[typeid]){//give plugins the ability to customize the entries in here + OC.search.customResults[typeid](row,type[i]); } $('#searchresults tbody').append(row); } } } + $('#searchresults').on('click', 'result', function () { + if ($(this).data('type') === 'Files') { + //FIXME use ajax to navigate to folder & highlight file + } + }); } -} +}; OC.search.showResults.loaded=false; OC.search.renderCurrent=function(){ @@ -71,4 +104,4 @@ OC.search.renderCurrent=function(){ $('#searchresults tr.result').removeClass('current'); $(result).addClass('current'); } -} +}; diff --git a/tests/enable_all.php b/tests/enable_all.php index 111ed0e1357..43ee16a72f8 100644 --- a/tests/enable_all.php +++ b/tests/enable_all.php @@ -8,6 +8,7 @@ require_once __DIR__.'/../lib/base.php'; +OC_App::enable('files_sharing'); OC_App::enable('files_encryption'); OC_App::enable('calendar'); OC_App::enable('contacts'); diff --git a/tests/lib/files/cache/updater.php b/tests/lib/files/cache/updater.php index 198d601b1bb..03d061cc444 100644 --- a/tests/lib/files/cache/updater.php +++ b/tests/lib/files/cache/updater.php @@ -21,6 +21,8 @@ class Updater extends \PHPUnit_Framework_TestCase { */ private $scanner; + private $stateFilesEncryption; + /** * @var \OC\Files\Cache\Cache $cache */ @@ -29,6 +31,12 @@ class Updater extends \PHPUnit_Framework_TestCase { private static $user; public function setUp() { + + // remember files_encryption state + $this->stateFilesEncryption = \OC_App::isEnabled('files_encryption'); + // we want to tests with the encryption app disabled + \OC_App::disable('files_encryption'); + $this->storage = new \OC\Files\Storage\Temporary(array()); $textData = "dummy file data\n"; $imgData = file_get_contents(\OC::$SERVERROOT . '/core/img/logo.png'); @@ -46,6 +54,10 @@ class Updater extends \PHPUnit_Framework_TestCase { if (!self::$user) { self::$user = uniqid(); } + + \OC_User::createUser(self::$user, 'password'); + \OC_User::setUserId(self::$user); + \OC\Files\Filesystem::init(self::$user, '/' . self::$user . '/files'); Filesystem::clearMounts(); @@ -63,7 +75,12 @@ class Updater extends \PHPUnit_Framework_TestCase { if ($this->cache) { $this->cache->clear(); } + \OC_User::deleteUser(self::$user); Filesystem::tearDown(); + // reset app files_encryption + if ($this->stateFilesEncryption) { + \OC_App::enable('files_encryption'); + } } public function testWrite() { diff --git a/tests/lib/files/filesystem.php b/tests/lib/files/filesystem.php index bef70cc725b..16b9237150a 100644 --- a/tests/lib/files/filesystem.php +++ b/tests/lib/files/filesystem.php @@ -66,17 +66,72 @@ class Filesystem extends \PHPUnit_Framework_TestCase { } public function testNormalize() { + $this->assertEquals('/', \OC\Files\Filesystem::normalizePath('')); + $this->assertEquals('/', \OC\Files\Filesystem::normalizePath('/')); + $this->assertEquals('/', \OC\Files\Filesystem::normalizePath('/', false)); + $this->assertEquals('/', \OC\Files\Filesystem::normalizePath('//')); + $this->assertEquals('/', \OC\Files\Filesystem::normalizePath('//', false)); $this->assertEquals('/path', \OC\Files\Filesystem::normalizePath('/path/')); $this->assertEquals('/path/', \OC\Files\Filesystem::normalizePath('/path/', false)); $this->assertEquals('/path', \OC\Files\Filesystem::normalizePath('path')); - $this->assertEquals('/path', \OC\Files\Filesystem::normalizePath('\path')); $this->assertEquals('/foo/bar', \OC\Files\Filesystem::normalizePath('/foo//bar/')); + $this->assertEquals('/foo/bar/', \OC\Files\Filesystem::normalizePath('/foo//bar/', false)); $this->assertEquals('/foo/bar', \OC\Files\Filesystem::normalizePath('/foo////bar')); + $this->assertEquals('/foo/bar', \OC\Files\Filesystem::normalizePath('/foo/////bar')); + $this->assertEquals('/foo/bar', \OC\Files\Filesystem::normalizePath('/foo/bar/.')); + $this->assertEquals('/foo/bar', \OC\Files\Filesystem::normalizePath('/foo/bar/./')); + $this->assertEquals('/foo/bar/', \OC\Files\Filesystem::normalizePath('/foo/bar/./', false)); + $this->assertEquals('/foo/bar', \OC\Files\Filesystem::normalizePath('/foo/bar/./.')); + $this->assertEquals('/foo/bar', \OC\Files\Filesystem::normalizePath('/foo/bar/././')); + $this->assertEquals('/foo/bar/', \OC\Files\Filesystem::normalizePath('/foo/bar/././', false)); + $this->assertEquals('/foo/bar', \OC\Files\Filesystem::normalizePath('/foo/./bar/')); + $this->assertEquals('/foo/bar/', \OC\Files\Filesystem::normalizePath('/foo/./bar/', false)); + $this->assertEquals('/foo/.bar', \OC\Files\Filesystem::normalizePath('/foo/.bar/')); + $this->assertEquals('/foo/.bar/', \OC\Files\Filesystem::normalizePath('/foo/.bar/', false)); + $this->assertEquals('/foo/.bar/tee', \OC\Files\Filesystem::normalizePath('/foo/.bar/tee')); + + // normalize does not resolve '..' (by design) + $this->assertEquals('/foo/..', \OC\Files\Filesystem::normalizePath('/foo/../')); + if (class_exists('Patchwork\PHP\Shim\Normalizer')) { $this->assertEquals("/foo/bar\xC3\xBC", \OC\Files\Filesystem::normalizePath("/foo/baru\xCC\x88")); } } + public function testNormalizeWindowsPaths() { + $this->assertEquals('/', \OC\Files\Filesystem::normalizePath('')); + $this->assertEquals('/', \OC\Files\Filesystem::normalizePath('\\')); + $this->assertEquals('/', \OC\Files\Filesystem::normalizePath('\\', false)); + $this->assertEquals('/', \OC\Files\Filesystem::normalizePath('\\\\')); + $this->assertEquals('/', \OC\Files\Filesystem::normalizePath('\\\\', false)); + $this->assertEquals('/path', \OC\Files\Filesystem::normalizePath('\\path')); + $this->assertEquals('/path', \OC\Files\Filesystem::normalizePath('\\path', false)); + $this->assertEquals('/path', \OC\Files\Filesystem::normalizePath('\\path\\')); + $this->assertEquals('/path/', \OC\Files\Filesystem::normalizePath('\\path\\', false)); + $this->assertEquals('/foo/bar', \OC\Files\Filesystem::normalizePath('\\foo\\\\bar\\')); + $this->assertEquals('/foo/bar/', \OC\Files\Filesystem::normalizePath('\\foo\\\\bar\\', false)); + $this->assertEquals('/foo/bar', \OC\Files\Filesystem::normalizePath('\\foo\\\\\\\\bar')); + $this->assertEquals('/foo/bar', \OC\Files\Filesystem::normalizePath('\\foo\\\\\\\\\\bar')); + $this->assertEquals('/foo/bar', \OC\Files\Filesystem::normalizePath('\\foo\\bar\\.')); + $this->assertEquals('/foo/bar', \OC\Files\Filesystem::normalizePath('\\foo\\bar\\.\\')); + $this->assertEquals('/foo/bar/', \OC\Files\Filesystem::normalizePath('\\foo\\bar\\.\\', false)); + $this->assertEquals('/foo/bar', \OC\Files\Filesystem::normalizePath('\\foo\\bar\\.\\.')); + $this->assertEquals('/foo/bar', \OC\Files\Filesystem::normalizePath('\\foo\\bar\\.\\.\\')); + $this->assertEquals('/foo/bar/', \OC\Files\Filesystem::normalizePath('\\foo\\bar\\.\\.\\', false)); + $this->assertEquals('/foo/bar', \OC\Files\Filesystem::normalizePath('\\foo\\.\\bar\\')); + $this->assertEquals('/foo/bar/', \OC\Files\Filesystem::normalizePath('\\foo\\.\\bar\\', false)); + $this->assertEquals('/foo/.bar', \OC\Files\Filesystem::normalizePath('\\foo\\.bar\\')); + $this->assertEquals('/foo/.bar/', \OC\Files\Filesystem::normalizePath('\\foo\\.bar\\', false)); + $this->assertEquals('/foo/.bar/tee', \OC\Files\Filesystem::normalizePath('\\foo\\.bar\\tee')); + + // normalize does not resolve '..' (by design) + $this->assertEquals('/foo/..', \OC\Files\Filesystem::normalizePath('\\foo\\..\\')); + + if (class_exists('Patchwork\PHP\Shim\Normalizer')) { + $this->assertEquals("/foo/bar\xC3\xBC", \OC\Files\Filesystem::normalizePath("\\foo\\baru\xCC\x88")); + } + } + public function testHooks() { if(\OC\Files\Filesystem::getView()){ $user = \OC_User::getUser(); diff --git a/tests/lib/files/storage/storage.php b/tests/lib/files/storage/storage.php index 0e22f26ae83..4587a3b2d06 100644 --- a/tests/lib/files/storage/storage.php +++ b/tests/lib/files/storage/storage.php @@ -42,18 +42,21 @@ abstract class Storage extends \PHPUnit_Framework_TestCase { $this->assertTrue($this->instance->isUpdatable('/'), 'Root folder is not writable'); } - public function testDirectories() { - $this->assertFalse($this->instance->file_exists('/folder')); + /** + * @dataProvider directoryProvider + */ + public function testDirectories($directory) { + $this->assertFalse($this->instance->file_exists('/'.$directory)); - $this->assertTrue($this->instance->mkdir('/folder')); + $this->assertTrue($this->instance->mkdir('/'.$directory)); - $this->assertTrue($this->instance->file_exists('/folder')); - $this->assertTrue($this->instance->is_dir('/folder')); - $this->assertFalse($this->instance->is_file('/folder')); - $this->assertEquals('dir', $this->instance->filetype('/folder')); - $this->assertEquals(0, $this->instance->filesize('/folder')); - $this->assertTrue($this->instance->isReadable('/folder')); - $this->assertTrue($this->instance->isUpdatable('/folder')); + $this->assertTrue($this->instance->file_exists('/'.$directory)); + $this->assertTrue($this->instance->is_dir('/'.$directory)); + $this->assertFalse($this->instance->is_file('/'.$directory)); + $this->assertEquals('dir', $this->instance->filetype('/'.$directory)); + $this->assertEquals(0, $this->instance->filesize('/'.$directory)); + $this->assertTrue($this->instance->isReadable('/'.$directory)); + $this->assertTrue($this->instance->isUpdatable('/'.$directory)); $dh = $this->instance->opendir('/'); $content = array(); @@ -62,14 +65,14 @@ abstract class Storage extends \PHPUnit_Framework_TestCase { $content[] = $file; } } - $this->assertEquals(array('folder'), $content); + $this->assertEquals(array($directory), $content); - $this->assertFalse($this->instance->mkdir('/folder')); //cant create existing folders - $this->assertTrue($this->instance->rmdir('/folder')); + $this->assertFalse($this->instance->mkdir('/'.$directory)); //cant create existing folders + $this->assertTrue($this->instance->rmdir('/'.$directory)); - $this->assertFalse($this->instance->file_exists('/folder')); + $this->assertFalse($this->instance->file_exists('/'.$directory)); - $this->assertFalse($this->instance->rmdir('/folder')); //cant remove non existing folders + $this->assertFalse($this->instance->rmdir('/'.$directory)); //cant remove non existing folders $dh = $this->instance->opendir('/'); $content = array(); @@ -81,6 +84,14 @@ abstract class Storage extends \PHPUnit_Framework_TestCase { $this->assertEquals(array(), $content); } + public function directoryProvider() + { + return array( + array('folder'), + array(' folder'), + array('folder '), + ); + } /** * test the various uses of file_get_contents and file_put_contents */ @@ -171,8 +182,9 @@ abstract class Storage extends \PHPUnit_Framework_TestCase { $this->assertTrue($this->instance->hasUpdated('/lorem.txt', $ctimeStart - 1)); $this->assertTrue($this->instance->hasUpdated('/', $ctimeStart - 1)); - $this->assertTrue(($ctimeStart - 1) <= $mTime); - $this->assertTrue($mTime <= ($ctimeEnd + 1)); + // check that ($ctimeStart - 1) <= $mTime <= ($ctimeEnd + 1) + $this->assertGreaterThanOrEqual(($ctimeStart - 1), $mTime); + $this->assertLessThanOrEqual(($ctimeEnd + 1), $mTime); $this->assertEquals(filesize($textFile), $this->instance->filesize('/lorem.txt')); $stat = $this->instance->stat('/lorem.txt'); @@ -238,6 +250,17 @@ abstract class Storage extends \PHPUnit_Framework_TestCase { $this->assertContains('/sub/logo-wide.png', $result); } + public function testUnlink() { + $textFile = \OC::$SERVERROOT . '/tests/data/lorem.txt'; + $this->instance->file_put_contents('/lorem.txt', file_get_contents($textFile)); + + $this->assertTrue($this->instance->file_exists('/lorem.txt')); + + $this->assertTrue($this->instance->unlink('/lorem.txt')); + + $this->assertFalse($this->instance->file_exists('/lorem.txt')); + } + public function testFOpen() { $textFile = \OC::$SERVERROOT . '/tests/data/lorem.txt'; diff --git a/tests/lib/files/view.php b/tests/lib/files/view.php index 1cd07773513..aceb36aa01e 100644 --- a/tests/lib/files/view.php +++ b/tests/lib/files/view.php @@ -7,6 +7,12 @@ namespace Test\Files; +class TemporaryNoTouch extends \OC\Files\Storage\Temporary { + public function touch($path, $mtime = null) { + return false; + } +} + class View extends \PHPUnit_Framework_TestCase { /** * @var \OC\Files\Storage\Storage[] $storages; @@ -262,12 +268,38 @@ class View extends \PHPUnit_Framework_TestCase { $this->hookPath = $params['path']; } + function testTouch() { + $storage = $this->getTestStorage(true, '\Test\Files\TemporaryNoTouch'); + + \OC\Files\Filesystem::mount($storage, array(), '/'); + + $rootView = new \OC\Files\View(''); + $oldCachedData = $rootView->getFileInfo('foo.txt'); + $newMTime = $oldCachedData['mtime'] + 500; + + $rootView->touch('foo.txt', $newMTime); + + $cachedData = $rootView->getFileInfo('foo.txt'); + $this->assertEquals($newMTime, $cachedData['mtime']); + + // reset mtime to original to make sure the next file access + // gets a higher mtime + $rootView->touch('foo.txt', $oldCachedData['mtime']); + + $rootView->file_put_contents('foo.txt', 'asd'); + $cachedData = $rootView->getFileInfo('foo.txt'); + $this->assertGreaterThanOrEqual($cachedData['mtime'], $oldCachedData['mtime']); + } + /** * @param bool $scan * @return \OC\Files\Storage\Storage */ - private function getTestStorage($scan = true) { - $storage = new \OC\Files\Storage\Temporary(array()); + private function getTestStorage($scan = true, $class = '\OC\Files\Storage\Temporary') { + /** + * @var \OC\Files\Storage\Storage $storage + */ + $storage = new $class(array()); $textData = "dummy file data\n"; $imgData = file_get_contents(\OC::$SERVERROOT . '/core/img/logo.png'); $storage->mkdir('folder'); @@ -282,4 +314,38 @@ class View extends \PHPUnit_Framework_TestCase { $this->storages[] = $storage; return $storage; } + + /** + * @dataProvider resolvePathTestProvider + */ + public function testResolvePath($expected, $pathToTest) { + $storage1 = $this->getTestStorage(); + \OC\Files\Filesystem::mount($storage1, array(), '/'); + + $view = new \OC\Files\View(''); + + $result = $view->resolvePath($pathToTest); + $this->assertEquals($expected, $result[1]); + + $exists = $view->file_exists($pathToTest); + $this->assertTrue($exists); + + $exists = $view->file_exists($result[1]); + $this->assertTrue($exists); + } + + function resolvePathTestProvider() { + return array( + array('foo.txt', 'foo.txt'), + array('foo.txt', '/foo.txt'), + array('folder', 'folder'), + array('folder', '/folder'), + array('folder', 'folder/'), + array('folder', '/folder/'), + array('folder/bar.txt', 'folder/bar.txt'), + array('folder/bar.txt', '/folder/bar.txt'), + array('', ''), + array('', '/'), + ); + } } diff --git a/tests/lib/helper.php b/tests/lib/helper.php index 336e8f8b3c5..7de63b74b49 100644 --- a/tests/lib/helper.php +++ b/tests/lib/helper.php @@ -137,4 +137,38 @@ class Test_Helper extends PHPUnit_Framework_TestCase { $result = OC_Helper::recursiveArraySearch($haystack, "NotFound"); $this->assertFalse($result); } + + /** + * @dataProvider streamCopyDataProvider + */ + public function testStreamCopy($expectedCount, $expectedResult, $source, $target) { + + if (is_string($source)) { + $source = fopen($source, 'r'); + } + if (is_string($target)) { + $target = fopen($target, 'w'); + } + + list($count, $result) = \OC_Helper::streamCopy($source, $target); + + if (is_resource($source)) { + fclose($source); + } + if (is_resource($target)) { + fclose($target); + } + + $this->assertSame($expectedCount, $count); + $this->assertSame($expectedResult, $result); + } + + + function streamCopyDataProvider() { + return array( + array(0, false, false, false), + array(0, false, \OC::$SERVERROOT . '/tests/data/lorem.txt', false), + array(446, true, \OC::$SERVERROOT . '/tests/data/lorem.txt', \OC::$SERVERROOT . '/tests/data/lorem-copy.txt'), + ); + } } diff --git a/tests/lib/share/share.php b/tests/lib/share/share.php index e02b0e4354d..8e9eef65d32 100644 --- a/tests/lib/share/share.php +++ b/tests/lib/share/share.php @@ -535,4 +535,52 @@ class Test_Share extends PHPUnit_Framework_TestCase { 'Failed asserting that user 3 still has access to test.txt after expiration date has been set.' ); } + + protected function getShareByValidToken($token) { + $row = OCP\Share::getShareByToken($token); + $this->assertInternalType( + 'array', + $row, + "Failed asserting that a share for token $token exists." + ); + return $row; + } + + public function testShareItemWithLink() { + OC_User::setUserId($this->user1); + $token = OCP\Share::shareItem('test', 'test.txt', OCP\Share::SHARE_TYPE_LINK, null, OCP\PERMISSION_READ); + $this->assertInternalType( + 'string', + $token, + 'Failed asserting that user 1 successfully shared text.txt as link with token.' + ); + + // testGetShareByTokenNoExpiration + $row = $this->getShareByValidToken($token); + $this->assertEmpty( + $row['expiration'], + 'Failed asserting that the returned row does not have an expiration date.' + ); + + // testGetShareByTokenExpirationValid + $this->assertTrue( + OCP\Share::setExpirationDate('test', 'test.txt', $this->dateInFuture), + 'Failed asserting that user 1 successfully set a future expiration date for the test.txt share.' + ); + $row = $this->getShareByValidToken($token); + $this->assertNotEmpty( + $row['expiration'], + 'Failed asserting that the returned row has an expiration date.' + ); + + // testGetShareByTokenExpirationExpired + $this->assertTrue( + OCP\Share::setExpirationDate('test', 'test.txt', $this->dateInPast), + 'Failed asserting that user 1 successfully set a past expiration date for the test.txt share.' + ); + $this->assertFalse( + OCP\Share::getShareByToken($token), + 'Failed asserting that an expired share could not be found.' + ); + } } |