diff --git a/.gitignore b/.gitignore index b57dd3d205..68977ad077 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,7 @@ config/mount.php apps/inc.php # ignore all apps except core ones -apps/* +apps* !apps/files !apps/files_encryption !apps/files_external diff --git a/3rdparty b/3rdparty index a13af72fbe..3ef9f738a9 160000 --- a/3rdparty +++ b/3rdparty @@ -1 +1 @@ -Subproject commit a13af72fbe8983686fc47489a750e60319f68ac2 +Subproject commit 3ef9f738a9107879dddc7d97842cf4d2198fae4c diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fd87513ec2..a79fcc08d6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,11 +1,15 @@ ## Submitting issues -If you have questions about how to use ownCloud, please direct these to the [mailing list][mailinglist] or our [forum][forum]. We are also available on [IRC][irc]. +If you have questions about how to install or use ownCloud, please direct these to the [mailing list][mailinglist] or our [forum][forum]. We are also available on [IRC][irc]. + +### TL;DR + + * The [issue template can be found here][template] but be aware of the different repositories! See list below. ### Guidelines -* Please search the existing issues first, it's likely that your issue was already reported. -* [Report the issue](https://github.com/owncloud/core/issues/new) using our [template][template], it includes all the informations we need to track down the issue. -* This repository is *only* for issues within the ownCloud core code. Issues in other compontents should be reported in their own repositores: +* Please search the existing issues first, it's likely that your issue was already reported or even fixed. +* This repository is *only* for issues within the ownCloud core code. This also includes the apps: files, encryption, external storage, sharing, deleted files, versions, LDAP, and WebDAV Auth +* The issues in other components should be reported in their respective repositories: - [Android client](https://github.com/owncloud/android/issues) - [iOS client](https://github.com/owncloud/ios-issues/issues) - [Desktop client](https://github.com/owncloud/mirall/issues) @@ -14,12 +18,12 @@ If you have questions about how to use ownCloud, please direct these to the [mai - [Calendar](https://github.com/owncloud/calendar/issues) - [Contacts](https://github.com/owncloud/contacts/issues) - [Mail](https://github.com/owncloud/mail/issues) + - [Media/Music](https://github.com/owncloud/media/issues) - [News](https://github.com/owncloud/news/issues) - [Notes](https://github.com/owncloud/notes/issues) - [Shorty](https://github.com/owncloud/shorty/issues) - - [other apps](https://github.com/owncloud/apps/issues) (e.g. Pictures, Music, Tasks, ...) - -If your issue appears to be a bug, and hasn't been reported, open a new issue. + - [All other apps](https://github.com/owncloud/apps/issues) (e.g. Pictures, Tasks, ...) +* Report the issue using our [template][template], it includes all the information we need to track down the issue. Help us to maximize the effort we can spend fixing issues and adding new features, by not reporting duplicate issues. @@ -34,7 +38,7 @@ Thanks for wanting to contribute source code to ownCloud. That's great! Before we're able to merge your code into the ownCloud core, you need to sign our [Contributor Agreement][agreement]. -Please read the [Developer Manuals][devmanual] to get useful infos like how to create your first application or how to test the ownCloud code with phpunit. +Please read the [Developer Manuals][devmanual] to learn how to create your first application or how to test the ownCloud code with PHPUnit. [agreement]: http://owncloud.org/about/contributor-agreement/ [devmanual]: http://owncloud.org/dev/ diff --git a/apps/files/ajax/rawlist.php b/apps/files/ajax/rawlist.php index 1cd2944483..f568afad4d 100644 --- a/apps/files/ajax/rawlist.php +++ b/apps/files/ajax/rawlist.php @@ -15,6 +15,14 @@ $mimetype = isset($_GET['mimetype']) ? $_GET['mimetype'] : ''; // make filelist $files = array(); +// If a type other than directory is requested first load them. +if($mimetype && strpos($mimetype, 'httpd/unix-directory') === false) { + foreach( \OC\Files\Filesystem::getDirectoryContent( $dir, 'httpd/unix-directory' ) as $i ) { + $i["date"] = OCP\Util::formatDate($i["mtime"] ); + $i['mimetype_icon'] = $i['type'] == 'dir' ? \mimetype_icon('dir'): \mimetype_icon($i['mimetype']); + $files[] = $i; + } +} foreach( \OC\Files\Filesystem::getDirectoryContent( $dir, $mimetype ) as $i ) { $i["date"] = OCP\Util::formatDate($i["mtime"] ); $i['mimetype_icon'] = $i['type'] == 'dir' ? \mimetype_icon('dir'): \mimetype_icon($i['mimetype']); diff --git a/apps/files/appinfo/app.php b/apps/files/appinfo/app.php index 05ab1722b3..99739cb4ce 100644 --- a/apps/files/appinfo/app.php +++ b/apps/files/appinfo/app.php @@ -20,4 +20,4 @@ OC_Search::registerProvider('OC_Search_Provider_File'); \OC_Hook::connect('OC_Filesystem', 'post_delete', '\OC\Files\Cache\Updater', 'deleteHook'); \OC_Hook::connect('OC_Filesystem', 'post_rename', '\OC\Files\Cache\Updater', 'renameHook'); -\OC_BackgroundJob_RegularTask::register('\OC\Files\Cache\BackgroundWatcher', 'checkNext'); +\OCP\BackgroundJob::addRegularTask('\OC\Files\Cache\BackgroundWatcher', 'checkNext'); diff --git a/apps/files/css/files.css b/apps/files/css/files.css index f788949b1b..108dcd741c 100644 --- a/apps/files/css/files.css +++ b/apps/files/css/files.css @@ -63,8 +63,12 @@ } #filestable { position: relative; top:37px; width:100%; } 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; } +tbody tr:hover, tbody tr:active { + background-color: rgb(240,240,240); +} +tbody tr.selected { + background-color: rgb(230,230,230); +} 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; } @@ -81,7 +85,12 @@ table th#headerDate, table td.date { min-width:11em; padding:0 .1em 0 1em; text- /* Multiselect bar */ #filestable.multiselect { top:63px; } table.multiselect thead { position:fixed; top:82px; z-index:1; -moz-box-sizing: border-box; box-sizing: border-box; left: 0; padding-left: 64px; width:100%; } -table.multiselect thead th { background:rgba(230,230,230,.8); color:#000; font-weight:bold; border-bottom:0; } +table.multiselect thead th { + background-color: rgba(210,210,210,.7); + color: #000; + font-weight: bold; + border-bottom: 0; +} table.multiselect #headerName { width: 100%; } table td.selection, table th.selection, table td.fileaction { width:2em; text-align:center; } table td.filename a.name { display:block; height:1.5em; vertical-align:middle; margin-left:3em; } @@ -115,10 +124,12 @@ table td.filename form { font-size:.85em; margin-left:3em; margin-right:3em; } } #fileList .name { position:relative; /* Firefox needs to explicitly have this default set … */ } #fileList tr:hover .fileactions { /* background to distinguish when overlaying with file names */ - background:rgba(248,248,248,.9); box-shadow:-5px 0 7px rgba(248,248,248,.9); + background-color: rgba(240,240,240,0.898); + box-shadow: -5px 0 7px rgba(240,240,240,0.898); } #fileList tr.selected:hover .fileactions, #fileList tr.mouseOver .fileactions { /* slightly darker color for selected rows */ - background:rgba(238,238,238,.9); box-shadow:-5px 0 7px rgba(238,238,238,.9); + background-color: rgba(230,230,230,.9); + box-shadow: -5px 0 7px rgba(230,230,230,.9); } #fileList .fileactions a.action img { position:relative; top:.2em; } #fileList a.action { display:inline; margin:-.5em 0; padding:1em .5em 1em .5em !important; } diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js index c24d1fd824..e19a35bbc5 100644 --- a/apps/files/js/filelist.js +++ b/apps/files/js/filelist.js @@ -51,7 +51,7 @@ var FileList={ }else{ simpleSize=t('files', 'Pending'); } - var sizeColor = Math.round(200-Math.pow((size/(1024*1024)),2)); + var sizeColor = Math.round(160-Math.pow((size/(1024*1024)),2)); var lastModifiedTime = Math.round(lastModified.getTime() / 1000); td = $('').attr({ "class": "filesize", diff --git a/apps/files/js/files.js b/apps/files/js/files.js index a15f0588f9..a79d34c9b2 100644 --- a/apps/files/js/files.js +++ b/apps/files/js/files.js @@ -708,14 +708,14 @@ function scanFiles(force, dir){ var scannerEventSource = new OC.EventSource(OC.filePath('files','ajax','scan.php'),{force:force,dir:dir}); scanFiles.cancel = scannerEventSource.close.bind(scannerEventSource); scannerEventSource.listen('count',function(count){ - console.log(count + 'files scanned') + console.log(count + ' files scanned') }); scannerEventSource.listen('folder',function(path){ console.log('now scanning ' + path) }); scannerEventSource.listen('done',function(count){ scanFiles.scanning=false; - console.log('done after ' + count + 'files'); + console.log('done after ' + count + ' files'); }); } scanFiles.scanning=false; diff --git a/apps/files/l10n/ca.php b/apps/files/l10n/ca.php index f34c9f59cf..c1c94b9900 100644 --- a/apps/files/l10n/ca.php +++ b/apps/files/l10n/ca.php @@ -57,7 +57,7 @@ "0 is unlimited" => "0 és sense límit", "Maximum input size for ZIP files" => "Mida màxima d'entrada per fitxers ZIP", "Save" => "Desa", -"New" => "Nova", +"New" => "Nou", "Text file" => "Fitxer de text", "Folder" => "Carpeta", "From link" => "Des d'enllaç", diff --git a/apps/files/l10n/cs_CZ.php b/apps/files/l10n/cs_CZ.php index de6a154242..8c6b637265 100644 --- a/apps/files/l10n/cs_CZ.php +++ b/apps/files/l10n/cs_CZ.php @@ -46,6 +46,7 @@ "{count} folders" => "{count} složky", "1 file" => "1 soubor", "{count} files" => "{count} soubory", +"Invalid folder name. Usage of 'Shared' is reserved by ownCloud" => "Název složky nelze použít. Použití názvu 'Shared' je ownCloudem rezervováno", "Unable to rename file" => "Nelze přejmenovat soubor", "Upload" => "Odeslat", "File handling" => "Zacházení se soubory", diff --git a/apps/files/l10n/da.php b/apps/files/l10n/da.php index 879fbc8451..542e0d05d6 100644 --- a/apps/files/l10n/da.php +++ b/apps/files/l10n/da.php @@ -46,6 +46,7 @@ "{count} folders" => "{count} mapper", "1 file" => "1 fil", "{count} files" => "{count} filer", +"Invalid folder name. Usage of 'Shared' is reserved by ownCloud" => "Ugyldigt mappenavn. Brug af 'Shared' er forbeholdt af ownCloud", "Unable to rename file" => "Kunne ikke omdøbe fil", "Upload" => "Upload", "File handling" => "Filhåndtering", diff --git a/apps/files/l10n/de.php b/apps/files/l10n/de.php index bcc3a4c6c9..e9a6ad6bde 100644 --- a/apps/files/l10n/de.php +++ b/apps/files/l10n/de.php @@ -46,6 +46,7 @@ "{count} folders" => "{count} Ordner", "1 file" => "1 Datei", "{count} files" => "{count} Dateien", +"Invalid folder name. Usage of 'Shared' is reserved by ownCloud" => "Der Ordnername ist ungültig. Nur ownCloud kann den Ordner \"Shared\" anlegen", "Unable to rename file" => "Konnte Datei nicht umbenennen", "Upload" => "Hochladen", "File handling" => "Dateibehandlung", diff --git a/apps/files/l10n/el.php b/apps/files/l10n/el.php index a8bb96cdfc..b273f6b522 100644 --- a/apps/files/l10n/el.php +++ b/apps/files/l10n/el.php @@ -46,6 +46,7 @@ "{count} folders" => "{count} φάκελοι", "1 file" => "1 αρχείο", "{count} files" => "{count} αρχεία", +"Invalid folder name. Usage of 'Shared' is reserved by ownCloud" => "Μη έγκυρο όνομα φακέλου. Η χρήση του 'Κοινόχρηστος' χρησιμοποιείται από το ownCloud", "Unable to rename file" => "Αδυναμία μετονομασίας αρχείου", "Upload" => "Μεταφόρτωση", "File handling" => "Διαχείριση αρχείων", diff --git a/apps/files/l10n/he.php b/apps/files/l10n/he.php index 963f25ebed..b15c7a5649 100644 --- a/apps/files/l10n/he.php +++ b/apps/files/l10n/he.php @@ -1,4 +1,6 @@ "לא ניתן להעביר את %s - קובץ בשם הזה כבר קיים", +"Could not move %s" => "לא ניתן להעביר את %s", "No file was uploaded. Unknown error" => "לא הועלה קובץ. טעות בלתי מזוהה.", "There is no error, the file uploaded with success" => "לא התרחשה שגיאה, הקובץ הועלה בהצלחה", "The uploaded file exceeds the upload_max_filesize directive in php.ini: " => "הקבצים שנשלחו חורגים מהגודל שצוין בהגדרה upload_max_filesize שבקובץ php.ini:", @@ -7,6 +9,8 @@ "No file was uploaded" => "שום קובץ לא הועלה", "Missing a temporary folder" => "תקיה זמנית חסרה", "Failed to write to disk" => "הכתיבה לכונן נכשלה", +"Not enough storage available" => "אין די שטח פנוי באחסון", +"Invalid directory." => "תיקייה שגויה.", "Files" => "קבצים", "Share" => "שתף", "Delete permanently" => "מחק לצמיתות", @@ -19,7 +23,9 @@ "cancel" => "ביטול", "replaced {new_name} with {old_name}" => "{new_name} הוחלף ב־{old_name}", "undo" => "ביטול", +"perform delete operation" => "ביצוע פעולת מחיקה", "1 file uploading" => "קובץ אחד נשלח", +"files uploading" => "קבצים בהעלאה", "Invalid name, '\\', '/', '<', '>', ':', '\"', '|', '?' and '*' are not allowed." => "השם שגוי, אסור להשתמש בתווים '\\', '/', '<', '>', ':', '\"', '|', '?' ו־'*'.", "Unable to upload your file as it is a directory or has 0 bytes" => "לא יכול להעלות את הקובץ מכיוון שזו תקיה או שמשקל הקובץ 0 בתים", "Upload cancelled." => "ההעלאה בוטלה.", @@ -33,6 +39,7 @@ "{count} folders" => "{count} תיקיות", "1 file" => "קובץ אחד", "{count} files" => "{count} קבצים", +"Unable to rename file" => "לא ניתן לשנות את שם הקובץ", "Upload" => "העלאה", "File handling" => "טיפול בקבצים", "Maximum upload size" => "גודל העלאה מקסימלי", diff --git a/apps/files/l10n/hi.php b/apps/files/l10n/hi.php new file mode 100644 index 0000000000..df57abe28b --- /dev/null +++ b/apps/files/l10n/hi.php @@ -0,0 +1,4 @@ + "साझा करें", +"Save" => "सहेजें" +); diff --git a/apps/files/l10n/hu_HU.php b/apps/files/l10n/hu_HU.php index 4520bfdd08..76b8bd420d 100644 --- a/apps/files/l10n/hu_HU.php +++ b/apps/files/l10n/hu_HU.php @@ -46,6 +46,7 @@ "{count} folders" => "{count} mappa", "1 file" => "1 fájl", "{count} files" => "{count} fájl", +"Invalid folder name. Usage of 'Shared' is reserved by ownCloud" => "Érvénytelen mappanév. A 'Shared' az ownCloud számára fenntartott elnevezés", "Unable to rename file" => "Nem lehet átnevezni a fájlt", "Upload" => "Feltöltés", "File handling" => "Fájlkezelés", diff --git a/apps/files/l10n/lt_LT.php b/apps/files/l10n/lt_LT.php index 3e2ea80c94..5938521bea 100644 --- a/apps/files/l10n/lt_LT.php +++ b/apps/files/l10n/lt_LT.php @@ -1,12 +1,19 @@ "Nepavyko perkelti %s - failas su tokiu pavadinimu jau egzistuoja", +"Could not move %s" => "Nepavyko perkelti %s", +"No file was uploaded. Unknown error" => "Failai nebuvo įkelti dėl nežinomos priežasties", "There is no error, the file uploaded with success" => "Failas įkeltas sėkmingai, be klaidų", +"The uploaded file exceeds the upload_max_filesize directive in php.ini: " => "Įkeliamas failas yra didesnis nei leidžia upload_max_filesize php.ini faile:", "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" => "Įkeliamo failo dydis viršija MAX_FILE_SIZE nustatymą, kuris naudojamas HTML formoje.", "The uploaded file was only partially uploaded" => "Failas buvo įkeltas tik dalinai", "No file was uploaded" => "Nebuvo įkeltas joks failas", "Missing a temporary folder" => "Nėra laikinojo katalogo", "Failed to write to disk" => "Nepavyko įrašyti į diską", +"Not enough storage available" => "Nepakanka vietos serveryje", +"Invalid directory." => "Neteisingas aplankas", "Files" => "Failai", "Share" => "Dalintis", +"Delete permanently" => "Ištrinti negrįžtamai", "Delete" => "Ištrinti", "Rename" => "Pervadinti", "Pending" => "Laukiantis", @@ -16,10 +23,21 @@ "cancel" => "atšaukti", "replaced {new_name} with {old_name}" => "pakeiskite {new_name} į {old_name}", "undo" => "anuliuoti", +"perform delete operation" => "ištrinti", "1 file uploading" => "įkeliamas 1 failas", +"files uploading" => "įkeliami failai", +"'.' is an invalid file name." => "'.' yra neleidžiamas failo pavadinime.", +"File name cannot be empty." => "Failo pavadinimas negali būti tuščias.", +"Invalid name, '\\', '/', '<', '>', ':', '\"', '|', '?' and '*' are not allowed." => "Neleistinas pavadinimas, '\\', '/', '<', '>', ':', '\"', '|', '?' ir '*' yra neleidžiami.", +"Your storage is full, files can not be updated or synced anymore!" => "Jūsų visa vieta serveryje užimta", +"Your storage is almost full ({usedSpacePercent}%)" => "Jūsų vieta serveryje beveik visa užimta ({usedSpacePercent}%)", +"Your download is being prepared. This might take some time if the files are big." => "Jūsų atsisiuntimas yra paruošiamas. tai gali užtrukti jei atsisiunčiamas didelis failas.", "Unable to upload your file as it is a directory or has 0 bytes" => "Neįmanoma įkelti failo - jo dydis gali būti 0 bitų arba tai katalogas", +"Not enough space available" => "Nepakanka vietos", "Upload cancelled." => "Įkėlimas atšauktas.", "File upload is in progress. Leaving the page now will cancel the upload." => "Failo įkėlimas pradėtas. Jei paliksite šį puslapį, įkėlimas nutrūks.", +"URL cannot be empty." => "URL negali būti tuščias.", +"Invalid folder name. Usage of 'Shared' is reserved by Owncloud" => "Negalimas aplanko pavadinimas. 'Shared' pavadinimas yra rezervuotas ownCloud", "Error" => "Klaida", "Name" => "Pavadinimas", "Size" => "Dydis", @@ -28,6 +46,8 @@ "{count} folders" => "{count} aplankalai", "1 file" => "1 failas", "{count} files" => "{count} failai", +"Invalid folder name. Usage of 'Shared' is reserved by ownCloud" => "Negalimas aplanko pavadinimas. 'Shared' pavadinimas yra rezervuotas ownCloud", +"Unable to rename file" => "Nepavyko pervadinti failo", "Upload" => "Įkelti", "File handling" => "Failų tvarkymas", "Maximum upload size" => "Maksimalus įkeliamo failo dydis", @@ -40,12 +60,16 @@ "New" => "Naujas", "Text file" => "Teksto failas", "Folder" => "Katalogas", +"From link" => "Iš nuorodos", +"Deleted files" => "Ištrinti failai", "Cancel upload" => "Atšaukti siuntimą", +"You don’t have write permissions here." => "Jūs neturite rašymo leidimo.", "Nothing in here. Upload something!" => "Čia tuščia. Įkelkite ką nors!", "Download" => "Atsisiųsti", "Unshare" => "Nebesidalinti", "Upload too large" => "Įkėlimui failas per didelis", "The files you are trying to upload exceed the maximum size for file uploads on this server." => "Bandomų įkelti failų dydis viršija maksimalų, kuris leidžiamas šiame serveryje", "Files are being scanned, please wait." => "Skenuojami failai, prašome palaukti.", -"Current scanning" => "Šiuo metu skenuojama" +"Current scanning" => "Šiuo metu skenuojama", +"Upgrading filesystem cache..." => "Atnaujinamas sistemos kešavimas..." ); diff --git a/apps/files/l10n/pt_PT.php b/apps/files/l10n/pt_PT.php index 15d6fc80bd..d90e299970 100644 --- a/apps/files/l10n/pt_PT.php +++ b/apps/files/l10n/pt_PT.php @@ -46,6 +46,7 @@ "{count} folders" => "{count} pastas", "1 file" => "1 ficheiro", "{count} files" => "{count} ficheiros", +"Invalid folder name. Usage of 'Shared' is reserved by ownCloud" => "Nome da pasta inválido. Palavra 'Shared' é reservado pela ownCloud", "Unable to rename file" => "Não foi possível renomear o ficheiro", "Upload" => "Carregar", "File handling" => "Manuseamento de ficheiros", diff --git a/apps/files/l10n/ru.php b/apps/files/l10n/ru.php index 83412bf2be..43a8dc7008 100644 --- a/apps/files/l10n/ru.php +++ b/apps/files/l10n/ru.php @@ -46,6 +46,7 @@ "{count} folders" => "{count} папок", "1 file" => "1 файл", "{count} files" => "{count} файлов", +"Invalid folder name. Usage of 'Shared' is reserved by ownCloud" => "Неправильное имя каталога. Имя 'Shared' зарезервировано.", "Unable to rename file" => "Невозможно переименовать файл", "Upload" => "Загрузка", "File handling" => "Управление файлами", diff --git a/apps/files/l10n/ru_RU.php b/apps/files/l10n/ru_RU.php index 1ef163d48f..e0bfab3321 100644 --- a/apps/files/l10n/ru_RU.php +++ b/apps/files/l10n/ru_RU.php @@ -1,3 +1,16 @@ "Ошибка" +"No file was uploaded. Unknown error" => "Файл не был загружен. Неизвестная ошибка", +"There is no error, the file uploaded with success" => "Ошибки нет, файл успешно загружен", +"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" => "Размер загружаемого файла превысил максимально допустимый в директиве MAX_FILE_SIZE, специфицированной в HTML-форме", +"The uploaded file was only partially uploaded" => "Загружаемый файл был загружен лишь частично", +"No file was uploaded" => "Файл не был загружен", +"Missing a temporary folder" => "Отсутствие временной папки", +"Failed to write to disk" => "Не удалось записать на диск", +"Not enough storage available" => "Недостаточно места в хранилище", +"Share" => "Сделать общим", +"Delete" => "Удалить", +"Error" => "Ошибка", +"Name" => "Имя", +"Save" => "Сохранить", +"Download" => "Загрузка" ); diff --git a/apps/files/l10n/sk_SK.php b/apps/files/l10n/sk_SK.php index b7f329c362..ad33c9b4ee 100644 --- a/apps/files/l10n/sk_SK.php +++ b/apps/files/l10n/sk_SK.php @@ -46,6 +46,7 @@ "{count} folders" => "{count} priečinkov", "1 file" => "1 súbor", "{count} files" => "{count} súborov", +"Invalid folder name. Usage of 'Shared' is reserved by ownCloud" => "Neplatný názov priečinka. Názov \"Shared\" je rezervovaný pre ownCloud", "Unable to rename file" => "Nemožno premenovať súbor", "Upload" => "Odoslať", "File handling" => "Nastavenie správania sa k súborom", diff --git a/apps/files/l10n/sv.php b/apps/files/l10n/sv.php index 82d169d569..f433176159 100644 --- a/apps/files/l10n/sv.php +++ b/apps/files/l10n/sv.php @@ -29,7 +29,7 @@ "'.' is an invalid file name." => "'.' är ett ogiltigt filnamn.", "File name cannot be empty." => "Filnamn kan inte vara tomt.", "Invalid name, '\\', '/', '<', '>', ':', '\"', '|', '?' and '*' are not allowed." => "Ogiltigt namn, '\\', '/', '<', '>', ':', '\"', '|', '?' och '*' är inte tillåtet.", -"Your storage is full, files can not be updated or synced anymore!" => "Ditt lagringsutrymme är fullt, filer kan ej längre laddas upp eller synkas!", +"Your storage is full, files can not be updated or synced anymore!" => "Ditt lagringsutrymme är fullt, filer kan inte längre uppdateras eller synkroniseras!", "Your storage is almost full ({usedSpacePercent}%)" => "Ditt lagringsutrymme är nästan fullt ({usedSpacePercent}%)", "Your download is being prepared. This might take some time if the files are big." => "Din nedladdning förbereds. Det kan ta tid om det är stora filer.", "Unable to upload your file as it is a directory or has 0 bytes" => "Kan inte ladda upp din fil eftersom det är en katalog eller har 0 bytes", @@ -46,6 +46,7 @@ "{count} folders" => "{count} mappar", "1 file" => "1 fil", "{count} files" => "{count} filer", +"Invalid folder name. Usage of 'Shared' is reserved by ownCloud" => "Ogiltigt mappnamn. Användning av 'Shared' är reserverad av ownCloud", "Unable to rename file" => "Kan inte byta namn på filen", "Upload" => "Ladda upp", "File handling" => "Filhantering", diff --git a/apps/files/l10n/tr.php b/apps/files/l10n/tr.php index fd5c6bc6f0..6a096d2703 100644 --- a/apps/files/l10n/tr.php +++ b/apps/files/l10n/tr.php @@ -46,6 +46,7 @@ "{count} folders" => "{count} dizin", "1 file" => "1 dosya", "{count} files" => "{count} dosya", +"Invalid folder name. Usage of 'Shared' is reserved by ownCloud" => "Geçersiz dizin adı. 'Shared' dizin ismi kullanımı ownCloud tarafından rezerve edilmiştir.", "Unable to rename file" => "Dosya adı değiştirilemedi", "Upload" => "Yükle", "File handling" => "Dosya taşıma", diff --git a/apps/files/l10n/zh_TW.php b/apps/files/l10n/zh_TW.php index 600048a321..0bd207888d 100644 --- a/apps/files/l10n/zh_TW.php +++ b/apps/files/l10n/zh_TW.php @@ -46,6 +46,7 @@ "{count} folders" => "{count} 個資料夾", "1 file" => "1 個檔案", "{count} files" => "{count} 個檔案", +"Invalid folder name. Usage of 'Shared' is reserved by ownCloud" => "無效的資料夾名稱,'Shared' 的使用被 ownCloud 保留", "Unable to rename file" => "無法重新命名檔案", "Upload" => "上傳", "File handling" => "檔案處理", diff --git a/apps/files/templates/part.breadcrumb.php b/apps/files/templates/part.breadcrumb.php index 7ea1755d1d..9886b42e42 100644 --- a/apps/files/templates/part.breadcrumb.php +++ b/apps/files/templates/part.breadcrumb.php @@ -1,5 +1,5 @@ -
+
diff --git a/apps/files/templates/part.list.php b/apps/files/templates/part.list.php index 1719d25e66..1e94275dcb 100644 --- a/apps/files/templates/part.list.php +++ b/apps/files/templates/part.list.php @@ -3,12 +3,12 @@ 200) $relative_date_color = 200; + if($relative_date_color>160) $relative_date_color = 160; $name = rawurlencode($file['name']); $name = str_replace('%2F', '/', $name); $directory = rawurlencode($file['directory']); diff --git a/apps/files_encryption/3rdparty/Crypt_Blowfish/Blowfish.php b/apps/files_encryption/3rdparty/Crypt_Blowfish/Blowfish.php new file mode 100644 index 0000000000..4ccacb963e --- /dev/null +++ b/apps/files_encryption/3rdparty/Crypt_Blowfish/Blowfish.php @@ -0,0 +1,317 @@ + + * @copyright 2005 Matthew Fonda + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: Blowfish.php,v 1.81 2005/05/30 18:40:36 mfonda Exp $ + * @link http://pear.php.net/package/Crypt_Blowfish + */ + + +require_once 'PEAR.php'; + + +/** + * + * Example usage: + * $bf = new Crypt_Blowfish('some secret key!'); + * $encrypted = $bf->encrypt('this is some example plain text'); + * $plaintext = $bf->decrypt($encrypted); + * echo "plain text: $plaintext"; + * + * + * @category Encryption + * @package Crypt_Blowfish + * @author Matthew Fonda + * @copyright 2005 Matthew Fonda + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @link http://pear.php.net/package/Crypt_Blowfish + * @version @package_version@ + * @access public + */ +class Crypt_Blowfish +{ + /** + * P-Array contains 18 32-bit subkeys + * + * @var array + * @access private + */ + var $_P = array(); + + + /** + * Array of four S-Blocks each containing 256 32-bit entries + * + * @var array + * @access private + */ + var $_S = array(); + + /** + * Mcrypt td resource + * + * @var resource + * @access private + */ + var $_td = null; + + /** + * Initialization vector + * + * @var string + * @access private + */ + var $_iv = null; + + + /** + * Crypt_Blowfish Constructor + * Initializes the Crypt_Blowfish object, and gives a sets + * the secret key + * + * @param string $key + * @access public + */ + function Crypt_Blowfish($key) + { + if (extension_loaded('mcrypt')) { + $this->_td = mcrypt_module_open(MCRYPT_BLOWFISH, '', 'ecb', ''); + $this->_iv = mcrypt_create_iv(8, MCRYPT_RAND); + } + $this->setKey($key); + } + + /** + * Deprecated isReady method + * + * @return bool + * @access public + * @deprecated + */ + function isReady() + { + return true; + } + + /** + * Deprecated init method - init is now a private + * method and has been replaced with _init + * + * @return bool + * @access public + * @deprecated + * @see Crypt_Blowfish::_init() + */ + function init() + { + $this->_init(); + } + + /** + * Initializes the Crypt_Blowfish object + * + * @access private + */ + function _init() + { + $defaults = new Crypt_Blowfish_DefaultKey(); + $this->_P = $defaults->P; + $this->_S = $defaults->S; + } + + /** + * Enciphers a single 64 bit block + * + * @param int &$Xl + * @param int &$Xr + * @access private + */ + function _encipher(&$Xl, &$Xr) + { + for ($i = 0; $i < 16; $i++) { + $temp = $Xl ^ $this->_P[$i]; + $Xl = ((($this->_S[0][($temp>>24) & 255] + + $this->_S[1][($temp>>16) & 255]) ^ + $this->_S[2][($temp>>8) & 255]) + + $this->_S[3][$temp & 255]) ^ $Xr; + $Xr = $temp; + } + $Xr = $Xl ^ $this->_P[16]; + $Xl = $temp ^ $this->_P[17]; + } + + + /** + * Deciphers a single 64 bit block + * + * @param int &$Xl + * @param int &$Xr + * @access private + */ + function _decipher(&$Xl, &$Xr) + { + for ($i = 17; $i > 1; $i--) { + $temp = $Xl ^ $this->_P[$i]; + $Xl = ((($this->_S[0][($temp>>24) & 255] + + $this->_S[1][($temp>>16) & 255]) ^ + $this->_S[2][($temp>>8) & 255]) + + $this->_S[3][$temp & 255]) ^ $Xr; + $Xr = $temp; + } + $Xr = $Xl ^ $this->_P[1]; + $Xl = $temp ^ $this->_P[0]; + } + + + /** + * Encrypts a string + * + * @param string $plainText + * @return string Returns cipher text on success, PEAR_Error on failure + * @access public + */ + function encrypt($plainText) + { + if (!is_string($plainText)) { + PEAR::raiseError('Plain text must be a string', 0, PEAR_ERROR_DIE); + } + + if (extension_loaded('mcrypt')) { + return mcrypt_generic($this->_td, $plainText); + } + + $cipherText = ''; + $len = strlen($plainText); + $plainText .= str_repeat(chr(0),(8 - ($len%8))%8); + for ($i = 0; $i < $len; $i += 8) { + list(,$Xl,$Xr) = unpack("N2",substr($plainText,$i,8)); + $this->_encipher($Xl, $Xr); + $cipherText .= pack("N2", $Xl, $Xr); + } + return $cipherText; + } + + + /** + * Decrypts an encrypted string + * + * @param string $cipherText + * @return string Returns plain text on success, PEAR_Error on failure + * @access public + */ + function decrypt($cipherText) + { + if (!is_string($cipherText)) { + PEAR::raiseError('Cipher text must be a string', 1, PEAR_ERROR_DIE); + } + + if (extension_loaded('mcrypt')) { + return mdecrypt_generic($this->_td, $cipherText); + } + + $plainText = ''; + $len = strlen($cipherText); + $cipherText .= str_repeat(chr(0),(8 - ($len%8))%8); + for ($i = 0; $i < $len; $i += 8) { + list(,$Xl,$Xr) = unpack("N2",substr($cipherText,$i,8)); + $this->_decipher($Xl, $Xr); + $plainText .= pack("N2", $Xl, $Xr); + } + return $plainText; + } + + + /** + * Sets the secret key + * The key must be non-zero, and less than or equal to + * 56 characters in length. + * + * @param string $key + * @return bool Returns true on success, PEAR_Error on failure + * @access public + */ + function setKey($key) + { + if (!is_string($key)) { + PEAR::raiseError('Key must be a string', 2, PEAR_ERROR_DIE); + } + + $len = strlen($key); + + if ($len > 56 || $len == 0) { + PEAR::raiseError('Key must be less than 56 characters and non-zero. Supplied key length: ' . $len, 3, PEAR_ERROR_DIE); + } + + if (extension_loaded('mcrypt')) { + mcrypt_generic_init($this->_td, $key, $this->_iv); + return true; + } + + require_once 'Blowfish/DefaultKey.php'; + $this->_init(); + + $k = 0; + $data = 0; + $datal = 0; + $datar = 0; + + for ($i = 0; $i < 18; $i++) { + $data = 0; + for ($j = 4; $j > 0; $j--) { + $data = $data << 8 | ord($key{$k}); + $k = ($k+1) % $len; + } + $this->_P[$i] ^= $data; + } + + for ($i = 0; $i <= 16; $i += 2) { + $this->_encipher($datal, $datar); + $this->_P[$i] = $datal; + $this->_P[$i+1] = $datar; + } + for ($i = 0; $i < 256; $i += 2) { + $this->_encipher($datal, $datar); + $this->_S[0][$i] = $datal; + $this->_S[0][$i+1] = $datar; + } + for ($i = 0; $i < 256; $i += 2) { + $this->_encipher($datal, $datar); + $this->_S[1][$i] = $datal; + $this->_S[1][$i+1] = $datar; + } + for ($i = 0; $i < 256; $i += 2) { + $this->_encipher($datal, $datar); + $this->_S[2][$i] = $datal; + $this->_S[2][$i+1] = $datar; + } + for ($i = 0; $i < 256; $i += 2) { + $this->_encipher($datal, $datar); + $this->_S[3][$i] = $datal; + $this->_S[3][$i+1] = $datar; + } + + return true; + } + +} + +?> diff --git a/apps/files_encryption/3rdparty/Crypt_Blowfish/Blowfish/DefaultKey.php b/apps/files_encryption/3rdparty/Crypt_Blowfish/Blowfish/DefaultKey.php new file mode 100644 index 0000000000..2ff8ac788a --- /dev/null +++ b/apps/files_encryption/3rdparty/Crypt_Blowfish/Blowfish/DefaultKey.php @@ -0,0 +1,327 @@ + + * @copyright 2005 Matthew Fonda + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: DefaultKey.php,v 1.81 2005/05/30 18:40:37 mfonda Exp $ + * @link http://pear.php.net/package/Crypt_Blowfish + */ + + +/** + * Class containing default key + * + * @category Encryption + * @package Crypt_Blowfish + * @author Matthew Fonda + * @copyright 2005 Matthew Fonda + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @link http://pear.php.net/package/Crypt_Blowfish + * @version @package_version@ + * @access public + */ +class Crypt_Blowfish_DefaultKey +{ + var $P = array(); + + var $S = array(); + + function Crypt_Blowfish_DefaultKey() + { + $this->P = array( + 0x243F6A88, 0x85A308D3, 0x13198A2E, 0x03707344, + 0xA4093822, 0x299F31D0, 0x082EFA98, 0xEC4E6C89, + 0x452821E6, 0x38D01377, 0xBE5466CF, 0x34E90C6C, + 0xC0AC29B7, 0xC97C50DD, 0x3F84D5B5, 0xB5470917, + 0x9216D5D9, 0x8979FB1B + ); + + $this->S = array( + array( + 0xD1310BA6, 0x98DFB5AC, 0x2FFD72DB, 0xD01ADFB7, + 0xB8E1AFED, 0x6A267E96, 0xBA7C9045, 0xF12C7F99, + 0x24A19947, 0xB3916CF7, 0x0801F2E2, 0x858EFC16, + 0x636920D8, 0x71574E69, 0xA458FEA3, 0xF4933D7E, + 0x0D95748F, 0x728EB658, 0x718BCD58, 0x82154AEE, + 0x7B54A41D, 0xC25A59B5, 0x9C30D539, 0x2AF26013, + 0xC5D1B023, 0x286085F0, 0xCA417918, 0xB8DB38EF, + 0x8E79DCB0, 0x603A180E, 0x6C9E0E8B, 0xB01E8A3E, + 0xD71577C1, 0xBD314B27, 0x78AF2FDA, 0x55605C60, + 0xE65525F3, 0xAA55AB94, 0x57489862, 0x63E81440, + 0x55CA396A, 0x2AAB10B6, 0xB4CC5C34, 0x1141E8CE, + 0xA15486AF, 0x7C72E993, 0xB3EE1411, 0x636FBC2A, + 0x2BA9C55D, 0x741831F6, 0xCE5C3E16, 0x9B87931E, + 0xAFD6BA33, 0x6C24CF5C, 0x7A325381, 0x28958677, + 0x3B8F4898, 0x6B4BB9AF, 0xC4BFE81B, 0x66282193, + 0x61D809CC, 0xFB21A991, 0x487CAC60, 0x5DEC8032, + 0xEF845D5D, 0xE98575B1, 0xDC262302, 0xEB651B88, + 0x23893E81, 0xD396ACC5, 0x0F6D6FF3, 0x83F44239, + 0x2E0B4482, 0xA4842004, 0x69C8F04A, 0x9E1F9B5E, + 0x21C66842, 0xF6E96C9A, 0x670C9C61, 0xABD388F0, + 0x6A51A0D2, 0xD8542F68, 0x960FA728, 0xAB5133A3, + 0x6EEF0B6C, 0x137A3BE4, 0xBA3BF050, 0x7EFB2A98, + 0xA1F1651D, 0x39AF0176, 0x66CA593E, 0x82430E88, + 0x8CEE8619, 0x456F9FB4, 0x7D84A5C3, 0x3B8B5EBE, + 0xE06F75D8, 0x85C12073, 0x401A449F, 0x56C16AA6, + 0x4ED3AA62, 0x363F7706, 0x1BFEDF72, 0x429B023D, + 0x37D0D724, 0xD00A1248, 0xDB0FEAD3, 0x49F1C09B, + 0x075372C9, 0x80991B7B, 0x25D479D8, 0xF6E8DEF7, + 0xE3FE501A, 0xB6794C3B, 0x976CE0BD, 0x04C006BA, + 0xC1A94FB6, 0x409F60C4, 0x5E5C9EC2, 0x196A2463, + 0x68FB6FAF, 0x3E6C53B5, 0x1339B2EB, 0x3B52EC6F, + 0x6DFC511F, 0x9B30952C, 0xCC814544, 0xAF5EBD09, + 0xBEE3D004, 0xDE334AFD, 0x660F2807, 0x192E4BB3, + 0xC0CBA857, 0x45C8740F, 0xD20B5F39, 0xB9D3FBDB, + 0x5579C0BD, 0x1A60320A, 0xD6A100C6, 0x402C7279, + 0x679F25FE, 0xFB1FA3CC, 0x8EA5E9F8, 0xDB3222F8, + 0x3C7516DF, 0xFD616B15, 0x2F501EC8, 0xAD0552AB, + 0x323DB5FA, 0xFD238760, 0x53317B48, 0x3E00DF82, + 0x9E5C57BB, 0xCA6F8CA0, 0x1A87562E, 0xDF1769DB, + 0xD542A8F6, 0x287EFFC3, 0xAC6732C6, 0x8C4F5573, + 0x695B27B0, 0xBBCA58C8, 0xE1FFA35D, 0xB8F011A0, + 0x10FA3D98, 0xFD2183B8, 0x4AFCB56C, 0x2DD1D35B, + 0x9A53E479, 0xB6F84565, 0xD28E49BC, 0x4BFB9790, + 0xE1DDF2DA, 0xA4CB7E33, 0x62FB1341, 0xCEE4C6E8, + 0xEF20CADA, 0x36774C01, 0xD07E9EFE, 0x2BF11FB4, + 0x95DBDA4D, 0xAE909198, 0xEAAD8E71, 0x6B93D5A0, + 0xD08ED1D0, 0xAFC725E0, 0x8E3C5B2F, 0x8E7594B7, + 0x8FF6E2FB, 0xF2122B64, 0x8888B812, 0x900DF01C, + 0x4FAD5EA0, 0x688FC31C, 0xD1CFF191, 0xB3A8C1AD, + 0x2F2F2218, 0xBE0E1777, 0xEA752DFE, 0x8B021FA1, + 0xE5A0CC0F, 0xB56F74E8, 0x18ACF3D6, 0xCE89E299, + 0xB4A84FE0, 0xFD13E0B7, 0x7CC43B81, 0xD2ADA8D9, + 0x165FA266, 0x80957705, 0x93CC7314, 0x211A1477, + 0xE6AD2065, 0x77B5FA86, 0xC75442F5, 0xFB9D35CF, + 0xEBCDAF0C, 0x7B3E89A0, 0xD6411BD3, 0xAE1E7E49, + 0x00250E2D, 0x2071B35E, 0x226800BB, 0x57B8E0AF, + 0x2464369B, 0xF009B91E, 0x5563911D, 0x59DFA6AA, + 0x78C14389, 0xD95A537F, 0x207D5BA2, 0x02E5B9C5, + 0x83260376, 0x6295CFA9, 0x11C81968, 0x4E734A41, + 0xB3472DCA, 0x7B14A94A, 0x1B510052, 0x9A532915, + 0xD60F573F, 0xBC9BC6E4, 0x2B60A476, 0x81E67400, + 0x08BA6FB5, 0x571BE91F, 0xF296EC6B, 0x2A0DD915, + 0xB6636521, 0xE7B9F9B6, 0xFF34052E, 0xC5855664, + 0x53B02D5D, 0xA99F8FA1, 0x08BA4799, 0x6E85076A + ), + array( + 0x4B7A70E9, 0xB5B32944, 0xDB75092E, 0xC4192623, + 0xAD6EA6B0, 0x49A7DF7D, 0x9CEE60B8, 0x8FEDB266, + 0xECAA8C71, 0x699A17FF, 0x5664526C, 0xC2B19EE1, + 0x193602A5, 0x75094C29, 0xA0591340, 0xE4183A3E, + 0x3F54989A, 0x5B429D65, 0x6B8FE4D6, 0x99F73FD6, + 0xA1D29C07, 0xEFE830F5, 0x4D2D38E6, 0xF0255DC1, + 0x4CDD2086, 0x8470EB26, 0x6382E9C6, 0x021ECC5E, + 0x09686B3F, 0x3EBAEFC9, 0x3C971814, 0x6B6A70A1, + 0x687F3584, 0x52A0E286, 0xB79C5305, 0xAA500737, + 0x3E07841C, 0x7FDEAE5C, 0x8E7D44EC, 0x5716F2B8, + 0xB03ADA37, 0xF0500C0D, 0xF01C1F04, 0x0200B3FF, + 0xAE0CF51A, 0x3CB574B2, 0x25837A58, 0xDC0921BD, + 0xD19113F9, 0x7CA92FF6, 0x94324773, 0x22F54701, + 0x3AE5E581, 0x37C2DADC, 0xC8B57634, 0x9AF3DDA7, + 0xA9446146, 0x0FD0030E, 0xECC8C73E, 0xA4751E41, + 0xE238CD99, 0x3BEA0E2F, 0x3280BBA1, 0x183EB331, + 0x4E548B38, 0x4F6DB908, 0x6F420D03, 0xF60A04BF, + 0x2CB81290, 0x24977C79, 0x5679B072, 0xBCAF89AF, + 0xDE9A771F, 0xD9930810, 0xB38BAE12, 0xDCCF3F2E, + 0x5512721F, 0x2E6B7124, 0x501ADDE6, 0x9F84CD87, + 0x7A584718, 0x7408DA17, 0xBC9F9ABC, 0xE94B7D8C, + 0xEC7AEC3A, 0xDB851DFA, 0x63094366, 0xC464C3D2, + 0xEF1C1847, 0x3215D908, 0xDD433B37, 0x24C2BA16, + 0x12A14D43, 0x2A65C451, 0x50940002, 0x133AE4DD, + 0x71DFF89E, 0x10314E55, 0x81AC77D6, 0x5F11199B, + 0x043556F1, 0xD7A3C76B, 0x3C11183B, 0x5924A509, + 0xF28FE6ED, 0x97F1FBFA, 0x9EBABF2C, 0x1E153C6E, + 0x86E34570, 0xEAE96FB1, 0x860E5E0A, 0x5A3E2AB3, + 0x771FE71C, 0x4E3D06FA, 0x2965DCB9, 0x99E71D0F, + 0x803E89D6, 0x5266C825, 0x2E4CC978, 0x9C10B36A, + 0xC6150EBA, 0x94E2EA78, 0xA5FC3C53, 0x1E0A2DF4, + 0xF2F74EA7, 0x361D2B3D, 0x1939260F, 0x19C27960, + 0x5223A708, 0xF71312B6, 0xEBADFE6E, 0xEAC31F66, + 0xE3BC4595, 0xA67BC883, 0xB17F37D1, 0x018CFF28, + 0xC332DDEF, 0xBE6C5AA5, 0x65582185, 0x68AB9802, + 0xEECEA50F, 0xDB2F953B, 0x2AEF7DAD, 0x5B6E2F84, + 0x1521B628, 0x29076170, 0xECDD4775, 0x619F1510, + 0x13CCA830, 0xEB61BD96, 0x0334FE1E, 0xAA0363CF, + 0xB5735C90, 0x4C70A239, 0xD59E9E0B, 0xCBAADE14, + 0xEECC86BC, 0x60622CA7, 0x9CAB5CAB, 0xB2F3846E, + 0x648B1EAF, 0x19BDF0CA, 0xA02369B9, 0x655ABB50, + 0x40685A32, 0x3C2AB4B3, 0x319EE9D5, 0xC021B8F7, + 0x9B540B19, 0x875FA099, 0x95F7997E, 0x623D7DA8, + 0xF837889A, 0x97E32D77, 0x11ED935F, 0x16681281, + 0x0E358829, 0xC7E61FD6, 0x96DEDFA1, 0x7858BA99, + 0x57F584A5, 0x1B227263, 0x9B83C3FF, 0x1AC24696, + 0xCDB30AEB, 0x532E3054, 0x8FD948E4, 0x6DBC3128, + 0x58EBF2EF, 0x34C6FFEA, 0xFE28ED61, 0xEE7C3C73, + 0x5D4A14D9, 0xE864B7E3, 0x42105D14, 0x203E13E0, + 0x45EEE2B6, 0xA3AAABEA, 0xDB6C4F15, 0xFACB4FD0, + 0xC742F442, 0xEF6ABBB5, 0x654F3B1D, 0x41CD2105, + 0xD81E799E, 0x86854DC7, 0xE44B476A, 0x3D816250, + 0xCF62A1F2, 0x5B8D2646, 0xFC8883A0, 0xC1C7B6A3, + 0x7F1524C3, 0x69CB7492, 0x47848A0B, 0x5692B285, + 0x095BBF00, 0xAD19489D, 0x1462B174, 0x23820E00, + 0x58428D2A, 0x0C55F5EA, 0x1DADF43E, 0x233F7061, + 0x3372F092, 0x8D937E41, 0xD65FECF1, 0x6C223BDB, + 0x7CDE3759, 0xCBEE7460, 0x4085F2A7, 0xCE77326E, + 0xA6078084, 0x19F8509E, 0xE8EFD855, 0x61D99735, + 0xA969A7AA, 0xC50C06C2, 0x5A04ABFC, 0x800BCADC, + 0x9E447A2E, 0xC3453484, 0xFDD56705, 0x0E1E9EC9, + 0xDB73DBD3, 0x105588CD, 0x675FDA79, 0xE3674340, + 0xC5C43465, 0x713E38D8, 0x3D28F89E, 0xF16DFF20, + 0x153E21E7, 0x8FB03D4A, 0xE6E39F2B, 0xDB83ADF7 + ), + array( + 0xE93D5A68, 0x948140F7, 0xF64C261C, 0x94692934, + 0x411520F7, 0x7602D4F7, 0xBCF46B2E, 0xD4A20068, + 0xD4082471, 0x3320F46A, 0x43B7D4B7, 0x500061AF, + 0x1E39F62E, 0x97244546, 0x14214F74, 0xBF8B8840, + 0x4D95FC1D, 0x96B591AF, 0x70F4DDD3, 0x66A02F45, + 0xBFBC09EC, 0x03BD9785, 0x7FAC6DD0, 0x31CB8504, + 0x96EB27B3, 0x55FD3941, 0xDA2547E6, 0xABCA0A9A, + 0x28507825, 0x530429F4, 0x0A2C86DA, 0xE9B66DFB, + 0x68DC1462, 0xD7486900, 0x680EC0A4, 0x27A18DEE, + 0x4F3FFEA2, 0xE887AD8C, 0xB58CE006, 0x7AF4D6B6, + 0xAACE1E7C, 0xD3375FEC, 0xCE78A399, 0x406B2A42, + 0x20FE9E35, 0xD9F385B9, 0xEE39D7AB, 0x3B124E8B, + 0x1DC9FAF7, 0x4B6D1856, 0x26A36631, 0xEAE397B2, + 0x3A6EFA74, 0xDD5B4332, 0x6841E7F7, 0xCA7820FB, + 0xFB0AF54E, 0xD8FEB397, 0x454056AC, 0xBA489527, + 0x55533A3A, 0x20838D87, 0xFE6BA9B7, 0xD096954B, + 0x55A867BC, 0xA1159A58, 0xCCA92963, 0x99E1DB33, + 0xA62A4A56, 0x3F3125F9, 0x5EF47E1C, 0x9029317C, + 0xFDF8E802, 0x04272F70, 0x80BB155C, 0x05282CE3, + 0x95C11548, 0xE4C66D22, 0x48C1133F, 0xC70F86DC, + 0x07F9C9EE, 0x41041F0F, 0x404779A4, 0x5D886E17, + 0x325F51EB, 0xD59BC0D1, 0xF2BCC18F, 0x41113564, + 0x257B7834, 0x602A9C60, 0xDFF8E8A3, 0x1F636C1B, + 0x0E12B4C2, 0x02E1329E, 0xAF664FD1, 0xCAD18115, + 0x6B2395E0, 0x333E92E1, 0x3B240B62, 0xEEBEB922, + 0x85B2A20E, 0xE6BA0D99, 0xDE720C8C, 0x2DA2F728, + 0xD0127845, 0x95B794FD, 0x647D0862, 0xE7CCF5F0, + 0x5449A36F, 0x877D48FA, 0xC39DFD27, 0xF33E8D1E, + 0x0A476341, 0x992EFF74, 0x3A6F6EAB, 0xF4F8FD37, + 0xA812DC60, 0xA1EBDDF8, 0x991BE14C, 0xDB6E6B0D, + 0xC67B5510, 0x6D672C37, 0x2765D43B, 0xDCD0E804, + 0xF1290DC7, 0xCC00FFA3, 0xB5390F92, 0x690FED0B, + 0x667B9FFB, 0xCEDB7D9C, 0xA091CF0B, 0xD9155EA3, + 0xBB132F88, 0x515BAD24, 0x7B9479BF, 0x763BD6EB, + 0x37392EB3, 0xCC115979, 0x8026E297, 0xF42E312D, + 0x6842ADA7, 0xC66A2B3B, 0x12754CCC, 0x782EF11C, + 0x6A124237, 0xB79251E7, 0x06A1BBE6, 0x4BFB6350, + 0x1A6B1018, 0x11CAEDFA, 0x3D25BDD8, 0xE2E1C3C9, + 0x44421659, 0x0A121386, 0xD90CEC6E, 0xD5ABEA2A, + 0x64AF674E, 0xDA86A85F, 0xBEBFE988, 0x64E4C3FE, + 0x9DBC8057, 0xF0F7C086, 0x60787BF8, 0x6003604D, + 0xD1FD8346, 0xF6381FB0, 0x7745AE04, 0xD736FCCC, + 0x83426B33, 0xF01EAB71, 0xB0804187, 0x3C005E5F, + 0x77A057BE, 0xBDE8AE24, 0x55464299, 0xBF582E61, + 0x4E58F48F, 0xF2DDFDA2, 0xF474EF38, 0x8789BDC2, + 0x5366F9C3, 0xC8B38E74, 0xB475F255, 0x46FCD9B9, + 0x7AEB2661, 0x8B1DDF84, 0x846A0E79, 0x915F95E2, + 0x466E598E, 0x20B45770, 0x8CD55591, 0xC902DE4C, + 0xB90BACE1, 0xBB8205D0, 0x11A86248, 0x7574A99E, + 0xB77F19B6, 0xE0A9DC09, 0x662D09A1, 0xC4324633, + 0xE85A1F02, 0x09F0BE8C, 0x4A99A025, 0x1D6EFE10, + 0x1AB93D1D, 0x0BA5A4DF, 0xA186F20F, 0x2868F169, + 0xDCB7DA83, 0x573906FE, 0xA1E2CE9B, 0x4FCD7F52, + 0x50115E01, 0xA70683FA, 0xA002B5C4, 0x0DE6D027, + 0x9AF88C27, 0x773F8641, 0xC3604C06, 0x61A806B5, + 0xF0177A28, 0xC0F586E0, 0x006058AA, 0x30DC7D62, + 0x11E69ED7, 0x2338EA63, 0x53C2DD94, 0xC2C21634, + 0xBBCBEE56, 0x90BCB6DE, 0xEBFC7DA1, 0xCE591D76, + 0x6F05E409, 0x4B7C0188, 0x39720A3D, 0x7C927C24, + 0x86E3725F, 0x724D9DB9, 0x1AC15BB4, 0xD39EB8FC, + 0xED545578, 0x08FCA5B5, 0xD83D7CD3, 0x4DAD0FC4, + 0x1E50EF5E, 0xB161E6F8, 0xA28514D9, 0x6C51133C, + 0x6FD5C7E7, 0x56E14EC4, 0x362ABFCE, 0xDDC6C837, + 0xD79A3234, 0x92638212, 0x670EFA8E, 0x406000E0 + ), + array( + 0x3A39CE37, 0xD3FAF5CF, 0xABC27737, 0x5AC52D1B, + 0x5CB0679E, 0x4FA33742, 0xD3822740, 0x99BC9BBE, + 0xD5118E9D, 0xBF0F7315, 0xD62D1C7E, 0xC700C47B, + 0xB78C1B6B, 0x21A19045, 0xB26EB1BE, 0x6A366EB4, + 0x5748AB2F, 0xBC946E79, 0xC6A376D2, 0x6549C2C8, + 0x530FF8EE, 0x468DDE7D, 0xD5730A1D, 0x4CD04DC6, + 0x2939BBDB, 0xA9BA4650, 0xAC9526E8, 0xBE5EE304, + 0xA1FAD5F0, 0x6A2D519A, 0x63EF8CE2, 0x9A86EE22, + 0xC089C2B8, 0x43242EF6, 0xA51E03AA, 0x9CF2D0A4, + 0x83C061BA, 0x9BE96A4D, 0x8FE51550, 0xBA645BD6, + 0x2826A2F9, 0xA73A3AE1, 0x4BA99586, 0xEF5562E9, + 0xC72FEFD3, 0xF752F7DA, 0x3F046F69, 0x77FA0A59, + 0x80E4A915, 0x87B08601, 0x9B09E6AD, 0x3B3EE593, + 0xE990FD5A, 0x9E34D797, 0x2CF0B7D9, 0x022B8B51, + 0x96D5AC3A, 0x017DA67D, 0xD1CF3ED6, 0x7C7D2D28, + 0x1F9F25CF, 0xADF2B89B, 0x5AD6B472, 0x5A88F54C, + 0xE029AC71, 0xE019A5E6, 0x47B0ACFD, 0xED93FA9B, + 0xE8D3C48D, 0x283B57CC, 0xF8D56629, 0x79132E28, + 0x785F0191, 0xED756055, 0xF7960E44, 0xE3D35E8C, + 0x15056DD4, 0x88F46DBA, 0x03A16125, 0x0564F0BD, + 0xC3EB9E15, 0x3C9057A2, 0x97271AEC, 0xA93A072A, + 0x1B3F6D9B, 0x1E6321F5, 0xF59C66FB, 0x26DCF319, + 0x7533D928, 0xB155FDF5, 0x03563482, 0x8ABA3CBB, + 0x28517711, 0xC20AD9F8, 0xABCC5167, 0xCCAD925F, + 0x4DE81751, 0x3830DC8E, 0x379D5862, 0x9320F991, + 0xEA7A90C2, 0xFB3E7BCE, 0x5121CE64, 0x774FBE32, + 0xA8B6E37E, 0xC3293D46, 0x48DE5369, 0x6413E680, + 0xA2AE0810, 0xDD6DB224, 0x69852DFD, 0x09072166, + 0xB39A460A, 0x6445C0DD, 0x586CDECF, 0x1C20C8AE, + 0x5BBEF7DD, 0x1B588D40, 0xCCD2017F, 0x6BB4E3BB, + 0xDDA26A7E, 0x3A59FF45, 0x3E350A44, 0xBCB4CDD5, + 0x72EACEA8, 0xFA6484BB, 0x8D6612AE, 0xBF3C6F47, + 0xD29BE463, 0x542F5D9E, 0xAEC2771B, 0xF64E6370, + 0x740E0D8D, 0xE75B1357, 0xF8721671, 0xAF537D5D, + 0x4040CB08, 0x4EB4E2CC, 0x34D2466A, 0x0115AF84, + 0xE1B00428, 0x95983A1D, 0x06B89FB4, 0xCE6EA048, + 0x6F3F3B82, 0x3520AB82, 0x011A1D4B, 0x277227F8, + 0x611560B1, 0xE7933FDC, 0xBB3A792B, 0x344525BD, + 0xA08839E1, 0x51CE794B, 0x2F32C9B7, 0xA01FBAC9, + 0xE01CC87E, 0xBCC7D1F6, 0xCF0111C3, 0xA1E8AAC7, + 0x1A908749, 0xD44FBD9A, 0xD0DADECB, 0xD50ADA38, + 0x0339C32A, 0xC6913667, 0x8DF9317C, 0xE0B12B4F, + 0xF79E59B7, 0x43F5BB3A, 0xF2D519FF, 0x27D9459C, + 0xBF97222C, 0x15E6FC2A, 0x0F91FC71, 0x9B941525, + 0xFAE59361, 0xCEB69CEB, 0xC2A86459, 0x12BAA8D1, + 0xB6C1075E, 0xE3056A0C, 0x10D25065, 0xCB03A442, + 0xE0EC6E0E, 0x1698DB3B, 0x4C98A0BE, 0x3278E964, + 0x9F1F9532, 0xE0D392DF, 0xD3A0342B, 0x8971F21E, + 0x1B0A7441, 0x4BA3348C, 0xC5BE7120, 0xC37632D8, + 0xDF359F8D, 0x9B992F2E, 0xE60B6F47, 0x0FE3F11D, + 0xE54CDA54, 0x1EDAD891, 0xCE6279CF, 0xCD3E7E6F, + 0x1618B166, 0xFD2C1D05, 0x848FD2C5, 0xF6FB2299, + 0xF523F357, 0xA6327623, 0x93A83531, 0x56CCCD02, + 0xACF08162, 0x5A75EBB5, 0x6E163697, 0x88D273CC, + 0xDE966292, 0x81B949D0, 0x4C50901B, 0x71C65614, + 0xE6C6C7BD, 0x327A140A, 0x45E1D006, 0xC3F27B9A, + 0xC9AA53FD, 0x62A80F00, 0xBB25BFE2, 0x35BDD2F6, + 0x71126905, 0xB2040222, 0xB6CBCF7C, 0xCD769C2B, + 0x53113EC0, 0x1640E3D3, 0x38ABBD60, 0x2547ADF0, + 0xBA38209C, 0xF746CE76, 0x77AFA1C5, 0x20756060, + 0x85CBFE4E, 0x8AE88DD8, 0x7AAAF9B0, 0x4CF9AA7E, + 0x1948C25C, 0x02FB8A8C, 0x01C36AE4, 0xD6EBE1F9, + 0x90D4F869, 0xA65CDEA0, 0x3F09252D, 0xC208E69F, + 0xB74E6132, 0xCE77E25B, 0x578FDFE3, 0x3AC372E6 + ) + ); + } + +} + +?> diff --git a/apps/files_encryption/ajax/adminrecovery.php b/apps/files_encryption/ajax/adminrecovery.php new file mode 100644 index 0000000000..6a0186d5a9 --- /dev/null +++ b/apps/files_encryption/ajax/adminrecovery.php @@ -0,0 +1,59 @@ + + * This file is licensed under the Affero General Public License version 3 or later. + * See the COPYING-README file. + * + * @brief Script to handle admin settings for encrypted key recovery + */ +use OCA\Encryption; + +\OCP\JSON::checkAdminUser(); +\OCP\JSON::checkAppEnabled('files_encryption'); +\OCP\JSON::callCheck(); + +$l = OC_L10N::get('files_encryption'); + +$return = false; +// Enable recoveryAdmin + +$recoveryKeyId = OC_Appconfig::getValue('files_encryption', 'recoveryKeyId'); + +if (isset($_POST['adminEnableRecovery']) && $_POST['adminEnableRecovery'] === '1') { + + $return = \OCA\Encryption\Helper::adminEnableRecovery($recoveryKeyId, $_POST['recoveryPassword']); + + // Return success or failure + if ($return) { + \OCP\JSON::success(array('data' => array('message' => $l->t('Recovery key successfully enabled')))); + } else { + \OCP\JSON::error(array( + 'data' => array( + 'message' => $l->t( + 'Could not enable recovery key. Please check your recovery key password!') + ) + )); + } + +// Disable recoveryAdmin +} elseif ( + isset($_POST['adminEnableRecovery']) + && '0' === $_POST['adminEnableRecovery'] +) { + $return = \OCA\Encryption\Helper::adminDisableRecovery($_POST['recoveryPassword']); + + // Return success or failure + if ($return) { + \OCP\JSON::success(array('data' => array('message' => $l->t('Recovery key successfully disabled')))); + } else { + \OCP\JSON::error(array( + 'data' => array( + 'message' => $l->t( + 'Could not disable recovery key. Please check your recovery key password!') + ) + )); + } +} + + diff --git a/apps/files_encryption/ajax/changeRecoveryPassword.php b/apps/files_encryption/ajax/changeRecoveryPassword.php new file mode 100644 index 0000000000..366f634a51 --- /dev/null +++ b/apps/files_encryption/ajax/changeRecoveryPassword.php @@ -0,0 +1,52 @@ + + * This file is licensed under the Affero General Public License version 3 or later. + * See the COPYING-README file. + * + * @brief Script to change recovery key password + * + */ + +use OCA\Encryption; + +\OCP\JSON::checkAdminUser(); +\OCP\JSON::checkAppEnabled('files_encryption'); +\OCP\JSON::callCheck(); + +$l = OC_L10N::get('core'); + +$return = false; + +$oldPassword = $_POST['oldPassword']; +$newPassword = $_POST['newPassword']; + +$view = new \OC\Files\View('/'); +$util = new \OCA\Encryption\Util(new \OC_FilesystemView('/'), \OCP\User::getUser()); + +$proxyStatus = \OC_FileProxy::$enabled; +\OC_FileProxy::$enabled = false; + +$keyId = $util->getRecoveryKeyId(); +$keyPath = '/owncloud_private_key/' . $keyId . '.private.key'; + +$encryptedRecoveryKey = $view->file_get_contents($keyPath); +$decryptedRecoveryKey = \OCA\Encryption\Crypt::decryptPrivateKey($encryptedRecoveryKey, $oldPassword); + +if ($decryptedRecoveryKey) { + + $encryptedRecoveryKey = \OCA\Encryption\Crypt::symmetricEncryptFileContent($decryptedRecoveryKey, $newPassword); + $view->file_put_contents($keyPath, $encryptedRecoveryKey); + + $return = true; +} + +\OC_FileProxy::$enabled = $proxyStatus; + +// success or failure +if ($return) { + \OCP\JSON::success(array('data' => array('message' => $l->t('Password successfully changed.')))); +} else { + \OCP\JSON::error(array('data' => array('message' => $l->t('Could not change the password. Maybe the old password was not correct.')))); +} \ No newline at end of file diff --git a/apps/files_encryption/ajax/updatePrivateKeyPassword.php b/apps/files_encryption/ajax/updatePrivateKeyPassword.php new file mode 100644 index 0000000000..6fd63dae9c --- /dev/null +++ b/apps/files_encryption/ajax/updatePrivateKeyPassword.php @@ -0,0 +1,54 @@ + + * This file is licensed under the Affero General Public License version 3 or later. + * See the COPYING-README file. + * + * @brief Script to change recovery key password + * + */ + +use OCA\Encryption; + +\OCP\JSON::checkLoggedIn(); +\OCP\JSON::checkAppEnabled('files_encryption'); +\OCP\JSON::callCheck(); + +$l = OC_L10N::get('core'); + +$return = false; + +$oldPassword = $_POST['oldPassword']; +$newPassword = $_POST['newPassword']; + +$view = new \OC\Files\View('/'); +$session = new \OCA\Encryption\Session($view); +$user = \OCP\User::getUser(); + +$proxyStatus = \OC_FileProxy::$enabled; +\OC_FileProxy::$enabled = false; + +$keyPath = '/' . $user . '/files_encryption/' . $user . '.private.key'; + +$encryptedKey = $view->file_get_contents($keyPath); +$decryptedKey = \OCA\Encryption\Crypt::decryptPrivateKey($encryptedKey, $oldPassword); + +if ($decryptedKey) { + + $encryptedKey = \OCA\Encryption\Crypt::symmetricEncryptFileContent($decryptedKey, $newPassword); + $view->file_put_contents($keyPath, $encryptedKey); + + $session->setPrivateKey($decryptedKey); + + $return = true; +} + +\OC_FileProxy::$enabled = $proxyStatus; + +// success or failure +if ($return) { + \OCP\JSON::success(array('data' => array('message' => $l->t('Private key password successfully updated.')))); +} else { + \OCP\JSON::error(array('data' => array('message' => $l->t('Could not update the private key password. Maybe the old password was not correct.')))); +} \ No newline at end of file diff --git a/apps/files_encryption/ajax/userrecovery.php b/apps/files_encryption/ajax/userrecovery.php new file mode 100644 index 0000000000..1d0f1ac2d1 --- /dev/null +++ b/apps/files_encryption/ajax/userrecovery.php @@ -0,0 +1,41 @@ + + * This file is licensed under the Affero General Public License version 3 or later. + * See the COPYING-README file. + * + * @brief Script to handle admin settings for encrypted key recovery + */ + +use OCA\Encryption; + +\OCP\JSON::checkLoggedIn(); +\OCP\JSON::checkAppEnabled('files_encryption'); +\OCP\JSON::callCheck(); + +if ( + isset($_POST['userEnableRecovery']) + && (0 == $_POST['userEnableRecovery'] || '1' === $_POST['userEnableRecovery']) +) { + + $userId = \OCP\USER::getUser(); + $view = new \OC_FilesystemView('/'); + $util = new \OCA\Encryption\Util($view, $userId); + + // Save recovery preference to DB + $return = $util->setRecoveryForUser($_POST['userEnableRecovery']); + + if ($_POST['userEnableRecovery'] === '1') { + $util->addRecoveryKeys(); + } else { + $util->removeRecoveryKeys(); + } + +} else { + + $return = false; + +} + +// Return success or failure +($return) ? \OCP\JSON::success() : \OCP\JSON::error(); \ No newline at end of file diff --git a/apps/files_encryption/appinfo/app.php b/apps/files_encryption/appinfo/app.php index bf16fec3ae..419bef1ede 100644 --- a/apps/files_encryption/appinfo/app.php +++ b/apps/files_encryption/appinfo/app.php @@ -8,42 +8,56 @@ OC::$CLASSPATH['OCA\Encryption\Stream'] = 'files_encryption/lib/stream.php'; OC::$CLASSPATH['OCA\Encryption\Proxy'] = 'files_encryption/lib/proxy.php'; OC::$CLASSPATH['OCA\Encryption\Session'] = 'files_encryption/lib/session.php'; OC::$CLASSPATH['OCA\Encryption\Capabilities'] = 'files_encryption/lib/capabilities.php'; +OC::$CLASSPATH['OCA\Encryption\Helper'] = 'files_encryption/lib/helper.php'; -OC_FileProxy::register( new OCA\Encryption\Proxy() ); +if (!OC_Config::getValue('maintenance', false)) { + OC_FileProxy::register(new OCA\Encryption\Proxy()); -// User-related hooks -OCP\Util::connectHook( 'OC_User', 'post_login', 'OCA\Encryption\Hooks', 'login' ); -OCP\Util::connectHook( 'OC_User', 'pre_setPassword', 'OCA\Encryption\Hooks', 'setPassphrase' ); + // User related hooks + OCA\Encryption\Helper::registerUserHooks(); -// Sharing-related hooks -OCP\Util::connectHook( 'OCP\Share', 'post_shared', 'OCA\Encryption\Hooks', 'postShared' ); -OCP\Util::connectHook( 'OCP\Share', 'pre_unshare', 'OCA\Encryption\Hooks', 'preUnshare' ); -OCP\Util::connectHook( 'OCP\Share', 'pre_unshareAll', 'OCA\Encryption\Hooks', 'preUnshareAll' ); + // Sharing related hooks + OCA\Encryption\Helper::registerShareHooks(); -// Webdav-related hooks -OCP\Util::connectHook( 'OC_Webdav_Properties', 'update', 'OCA\Encryption\Hooks', 'updateKeyfile' ); + // Filesystem related hooks + OCA\Encryption\Helper::registerFilesystemHooks(); -stream_wrapper_register( 'crypt', 'OCA\Encryption\Stream' ); + stream_wrapper_register('crypt', 'OCA\Encryption\Stream'); -$session = new OCA\Encryption\Session(); + // check if we are logged in + if (OCP\User::isLoggedIn()) { -if ( - ! $session->getPrivateKey( \OCP\USER::getUser() ) - && OCP\User::isLoggedIn() - && OCA\Encryption\Crypt::mode() == 'server' -) { + // ensure filesystem is loaded + if (!\OC\Files\Filesystem::$loaded) { + \OC_Util::setupFS(); + } - // Force the user to log-in again if the encryption key isn't unlocked - // (happens when a user is logged in before the encryption app is - // enabled) + $view = new OC_FilesystemView('/'); + $session = new \OCA\Encryption\Session($view); + + $user = \OCP\USER::getUser(); + // check if user has a private key + if ( + !$view->file_exists('/' . $user . '/files_encryption/' . $user . '.private.key') + && OCA\Encryption\Crypt::mode() === 'server' + ) { + + // Force the user to log-in again if the encryption key isn't unlocked + // (happens when a user is logged in before the encryption app is + // enabled) + OCP\User::logout(); + + header("Location: " . OC::$WEBROOT . '/'); + + exit(); + } + } +} else { + // logout user if we are in maintenance to force re-login OCP\User::logout(); - - header( "Location: " . OC::$WEBROOT.'/' ); - - exit(); - } // Register settings scripts -OCP\App::registerAdmin( 'files_encryption', 'settings' ); -OCP\App::registerPersonal( 'files_encryption', 'settings-personal' ); +OCP\App::registerAdmin('files_encryption', 'settings-admin'); +OCP\App::registerPersonal('files_encryption', 'settings-personal'); + diff --git a/apps/files_encryption/appinfo/database.xml b/apps/files_encryption/appinfo/database.xml index d294c35d63..4587930da0 100644 --- a/apps/files_encryption/appinfo/database.xml +++ b/apps/files_encryption/appinfo/database.xml @@ -18,6 +18,21 @@ text true 64 + What client-side / server-side configuration is used + + + recovery_enabled + integer + true + 0 + Whether encryption key recovery is enabled + + + migration_status + integer + true + 0 + Whether encryption migration has been performed diff --git a/apps/files_encryption/appinfo/info.xml b/apps/files_encryption/appinfo/info.xml index 39ea155488..ea8f6cf6f3 100644 --- a/apps/files_encryption/appinfo/info.xml +++ b/apps/files_encryption/appinfo/info.xml @@ -2,9 +2,9 @@ files_encryption Encryption - Server side encryption of files. Warning: You will lose your data if you enable this App and forget your password. Encryption is not yet compatible with LDAP. + WARNING: This is a preview release of the new ownCloud 5 encryption system. Testing and feedback is very welcome but don't use this in production yet. Encryption is not yet compatible with LDAP. AGPL - Sam Tuke + Sam Tuke, Bjoern Schiessle, Florin Peter 4 true diff --git a/apps/files_encryption/appinfo/spec.txt b/apps/files_encryption/appinfo/spec.txt index 2d22dffe08..ddd3983a9e 100644 --- a/apps/files_encryption/appinfo/spec.txt +++ b/apps/files_encryption/appinfo/spec.txt @@ -9,6 +9,57 @@ Encrypted files [encrypted data string][delimiter][IV][padding] [anhAAjAmcGXqj1X9g==][00iv00][MSHU5N5gECP7aAg7][xx] (square braces added) + +- Directory structure: + - Encrypted user data (catfiles) are stored in the usual /data/user/files dir + - Keyfiles are stored in /data/user/files_encryption/keyfiles + - Sharekey are stored in /data/user/files_encryption/share-files + +- File extensions: + - Catfiles have to keep the file extension of the original file, pre-encryption + - Keyfiles use .keyfile + - Sharekeys have .shareKey + +Shared files +------------ + +Shared files have a centrally stored catfile and keyfile, and one sharekey for +each user that shares it. + +When sharing is used, a different encryption method is used to encrypt the +keyfile (openssl_seal). Although shared files have a keyfile, its contents +use a different format therefore. + +Each time a shared file is edited or deleted, all sharekeys for users sharing +that file must have their sharekeys changed also. The keyfile and catfile +however need only changing in the owners files, as there is only one copy of +these. + +Publicly shared files (public links) +------------------------------------ + +Files shared via public links use a separate system user account called 'ownCloud'. All public files are shared to that user's public key, and the private key is used to access the files when the public link is used in browser. + +This means that files shared via public links are accessible only to users who know the shared URL, or to admins who know the 'ownCloud' user password. + +Lost password recovery +---------------------- + +In order to enable users to read their encrypted files in the event of a password loss/reset scenario, administrators can choose to enable a 'recoveryAdmin' account. This is a user that all user files will automatically be shared to of the option is enabled. This allows the recoveryAdmin user to generate new keyfiles for the user. By default the UID of the recoveryAdmin is 'recoveryAdmin'. + +OC_FilesystemView +----------------- + +files_encryption deals extensively with paths and the filesystem. In order to minimise bugs, it makes calls to filesystem methods in a consistent way: OC_FilesystemView{} objects always use '/' as their root, and specify paths each time particular methods are called. e.g. do this: + +$view->file_exists( 'path/to/file' ); + +Not: + +$view->chroot( 'path/to' ); +$view->file_exists( 'file' ); + +Using this convention means that $view objects are more predictable and less likely to break. Problems with paths are the #1 cause of bugs in this app, and consistent $view handling is an important way to prevent them. Notes ----- @@ -16,4 +67,11 @@ Notes - The user passphrase is required in order to set up or upgrade the app. New keypair generation, and the re-encryption of legacy encrypted files requires it. Therefore an appinfo/update.php script cannot be used, and upgrade logic - is handled in the login hook listener. \ No newline at end of file + is handled in the login hook listener. Therefore each time the user logs in + their files are scanned to detect unencrypted and legacy encrypted files, and + they are (re)encrypted as necessary. This may present a performance issue; we + need to monitor this. +- When files are saved to ownCloud via WebDAV, a .part file extension is used so + that the file isn't cached before the upload has been completed. .part files + are not compatible with files_encrytion's key management system however, so + we have to always sanitise such paths manually before using them. \ No newline at end of file diff --git a/apps/files_encryption/appinfo/version b/apps/files_encryption/appinfo/version index 1d71ef9744..bd73f47072 100644 --- a/apps/files_encryption/appinfo/version +++ b/apps/files_encryption/appinfo/version @@ -1 +1 @@ -0.3 \ No newline at end of file +0.4 diff --git a/apps/files_encryption/css/settings-personal.css b/apps/files_encryption/css/settings-personal.css new file mode 100644 index 0000000000..4ee0acc976 --- /dev/null +++ b/apps/files_encryption/css/settings-personal.css @@ -0,0 +1,10 @@ +/* Copyright (c) 2013, Sam Tuke, + This file is licensed under the Affero General Public License version 3 or later. + See the COPYING-README file. */ + +#encryptAllError +, #encryptAllSuccess +, #recoveryEnabledError +, #recoveryEnabledSuccess { + display: none; +} \ No newline at end of file diff --git a/apps/files_encryption/files/error.php b/apps/files_encryption/files/error.php new file mode 100644 index 0000000000..63c74e4e79 --- /dev/null +++ b/apps/files_encryption/files/error.php @@ -0,0 +1,24 @@ +t('Your private key is not valid! Maybe your password was changed from outside. You can update your private key password in your personal settings to regain access to your files'); + + if(isset($_GET['p']) && $_GET['p'] === '1') { + header('HTTP/1.0 404 ' . $errorMsg); + } + + // check if ajax request + if(!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') { + \OCP\JSON::error(array('data' => array('message' => $errorMsg))); + } else { + header('HTTP/1.0 404 ' . $errorMsg); + $tmpl = new OC_Template('files_encryption', 'invalid_private_key', 'guest'); + $tmpl->printPage(); + } + + exit; +} +?> diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index 2731d5a92f..7698b95cfd 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -23,10 +23,11 @@ namespace OCA\Encryption; +use OC\Files\Filesystem; + /** * Class for hook specific logic */ - class Hooks { // TODO: use passphrase for encrypting private key that is separate to @@ -36,156 +37,506 @@ class Hooks { * @brief Startup encryption backend upon user login * @note This method should never be called for users using client side encryption */ - public static function login( $params ) { - + public static function login($params) { + // Manually initialise Filesystem{} singleton with correct // fake root path, in order to avoid fatal webdav errors - \OC\Files\Filesystem::init( $params['uid'], $params['uid'] . '/' . 'files' . '/' ); - - $view = new \OC_FilesystemView( '/' ); + // NOTE: disabled because this give errors on webdav! + //\OC\Files\Filesystem::init( $params['uid'], '/' . 'files' . '/' ); - $util = new Util( $view, $params['uid'] ); - - // Check files_encryption infrastructure is ready for action - if ( ! $util->ready() ) { - - \OC_Log::write( 'Encryption library', 'User account "' . $params['uid'] . '" is not ready for encryption; configuration started', \OC_Log::DEBUG ); - - return $util->setupServerSide( $params['password'] ); + $view = new \OC_FilesystemView('/'); + // ensure filesystem is loaded + if(!\OC\Files\Filesystem::$loaded) { + \OC_Util::setupFS($params['uid']); } - - \OC_FileProxy::$enabled = false; - - $encryptedKey = Keymanager::getPrivateKey( $view, $params['uid'] ); - - \OC_FileProxy::$enabled = true; - - $privateKey = Crypt::symmetricDecryptFileContent( $encryptedKey, $params['password'] ); - - $session = new Session(); - - $session->setPrivateKey( $privateKey, $params['uid'] ); - - $view1 = new \OC_FilesystemView( '/' . $params['uid'] ); - - // Set legacy encryption key if it exists, to support - // depreciated encryption system - if ( - $view1->file_exists( 'encryption.key' ) - && $encLegacyKey = $view1->file_get_contents( 'encryption.key' ) - ) { - - $plainLegacyKey = Crypt::legacyDecrypt( $encLegacyKey, $params['password'] ); - - $session->setLegacyKey( $plainLegacyKey ); - + + $util = new Util($view, $params['uid']); + + // setup user, if user not ready force relogin + if (Helper::setupUser($util, $params['password']) === false) { + return false; } - - $publicKey = Keymanager::getPublicKey( $view, $params['uid'] ); - - // Encrypt existing user files: - // This serves to upgrade old versions of the encryption - // app (see appinfo/spec.txt) - if ( - $util->encryptAll( $publicKey, '/' . $params['uid'] . '/' . 'files', $session->getLegacyKey(), $params['password'] ) - ) { - - \OC_Log::write( - 'Encryption library', 'Encryption of existing files belonging to "' . $params['uid'] . '" started at login' - , \OC_Log::INFO - ); - + + $encryptedKey = Keymanager::getPrivateKey($view, $params['uid']); + + $privateKey = Crypt::decryptPrivateKey($encryptedKey, $params['password']); + + if ($privateKey === false) { + \OCP\Util::writeLog('Encryption library', 'Private key for user "' . $params['uid'] + . '" is not valid! Maybe the user password was changed from outside if so please change it back to gain access', \OCP\Util::ERROR); + } + + $session = new \OCA\Encryption\Session($view); + + $session->setPrivateKey($privateKey); + + // Check if first-run file migration has already been performed + $ready = false; + if ($util->getMigrationStatus() === Util::MIGRATION_OPEN) { + $ready = $util->beginMigration(); + } + + // If migration not yet done + if ($ready) { + + $userView = new \OC_FilesystemView('/' . $params['uid']); + + // Set legacy encryption key if it exists, to support + // depreciated encryption system + if ( + $userView->file_exists('encryption.key') + && $encLegacyKey = $userView->file_get_contents('encryption.key') + ) { + + $plainLegacyKey = Crypt::legacyDecrypt($encLegacyKey, $params['password']); + + $session->setLegacyKey($plainLegacyKey); + + } + + // 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']) + ) { + + \OC_Log::write( + 'Encryption library', 'Encryption of existing files belonging to "' . $params['uid'] . '" completed' + , \OC_Log::INFO + ); + + } + + // Register successful migration in DB + $util->finishMigration(); + } return true; } - + + /** + * @brief setup encryption backend upon user created + * @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']); + } + + /** + * @brief cleanup encryption backend upon user deleted + * @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'; + + // Disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + $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); + } + } + /** * @brief Change a user's encryption passphrase * @param array $params keys: uid, password */ - public static function setPassphrase( $params ) { - + public static function setPassphrase($params) { + // Only attempt to change passphrase if server-side encryption - // is in use (client-side encryption does not have access to + // is in use (client-side encryption does not have access to // the necessary keys) - if ( Crypt::mode() == 'server' ) { - - $session = new Session(); - - // Get existing decrypted private key - $privateKey = $session->getPrivateKey(); - - // Encrypt private key with new user pwd as passphrase - $encryptedPrivateKey = Crypt::symmetricEncryptFileContent( $privateKey, $params['password'] ); - - // Save private key - Keymanager::setPrivateKey( $encryptedPrivateKey ); - - // NOTE: Session does not need to be updated as the - // private key has not changed, only the passphrase - // used to decrypt it has changed - - } - - } - - /** - * @brief update the encryption key of the file uploaded by the client - */ - public static function updateKeyfile( $params ) { - - if ( Crypt::mode() == 'client' ) { - - if ( isset( $params['properties']['key'] ) ) { - - $view = new \OC_FilesystemView( '/' ); - $userId = \OCP\User::getUser(); - - Keymanager::setFileKey( $view, $params['path'], $userId, $params['properties']['key'] ); - - } else { - - \OC_Log::write( - 'Encryption library', "Client side encryption is enabled but the client doesn't provide a encryption key for the file!" - , \OC_Log::ERROR - ); - - error_log( "Client side encryption is enabled but the client doesn't provide an encryption key for the file!" ); - + if (Crypt::mode() === 'server') { + + if ($params['uid'] === \OCP\User::getUser()) { + + $view = new \OC_FilesystemView('/'); + + $session = new \OCA\Encryption\Session($view); + + // Get existing decrypted private key + $privateKey = $session->getPrivateKey(); + + // Encrypt private key with new user pwd as passphrase + $encryptedPrivateKey = Crypt::symmetricEncryptFileContent($privateKey, $params['password']); + + // Save private key + Keymanager::setPrivateKey($encryptedPrivateKey); + + // NOTE: Session does not need to be updated as the + // private key has not changed, only the passphrase + // used to decrypt it has changed + + + } else { // admin changed the password for a different user, create new keys and reencrypt file keys + + $user = $params['uid']; + $recoveryPassword = $params['recoveryPassword']; + $newUserPassword = $params['password']; + + $view = new \OC_FilesystemView('/'); + + // make sure that the users home is mounted + \OC\Files\Filesystem::initMountPoints($user); + + $keypair = Crypt::createKeypair(); + + // Disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + // Save public key + $view->file_put_contents('/public-keys/' . $user . '.public.key', $keypair['publicKey']); + + // Encrypt private key empty passphrase + $encryptedPrivateKey = Crypt::symmetricEncryptFileContent($keypair['privateKey'], $newUserPassword); + + // Save private key + $view->file_put_contents( + '/' . $user . '/files_encryption/' . $user . '.private.key', $encryptedPrivateKey); + + if ($recoveryPassword) { // if recovery key is set we can re-encrypt the key files + $util = new Util($view, $user); + $util->recoverUsersFiles($recoveryPassword); + } + + \OC_FileProxy::$enabled = $proxyStatus; } - } - } - - /** - * @brief + + /* + * @brief check if files can be encrypted to every user. */ - public static function postShared( $params ) { - } - /** - * @brief + * @param $params */ - public static function preUnshare( $params ) { - - // Delete existing catfile - - // Generate new catfile and env keys - - // Save env keys to user folders + public static function preShared($params) { + + $users = array(); + $view = new \OC\Files\View('/public-keys/'); + + switch ($params['shareType']) { + case \OCP\Share::SHARE_TYPE_USER: + $users[] = $params['shareWith']; + break; + case \OCP\Share::SHARE_TYPE_GROUP: + $users = \OC_Group::usersInGroup($params['shareWith']); + break; + } + + $error = false; + foreach ($users as $user) { + if (!$view->file_exists($user . '.public.key')) { + $error = true; + break; + } + } + + if ($error) // Set flag var 'run' to notify emitting + // script that hook execution failed + { + $params['run']->run = false; + } + // TODO: Make sure files_sharing provides user + // feedback on failed share } - + /** - * @brief + * @brief */ - public static function preUnshareAll( $params ) { - - trigger_error( "preUnshareAll" ); - + public static function postShared($params) { + + // NOTE: $params has keys: + // [itemType] => file + // itemSource -> int, filecache file ID + // [parent] => + // [itemTarget] => /13 + // shareWith -> string, uid of user being shared to + // fileTarget -> path of file being shared + // uidOwner -> owner of the original file being shared + // [shareType] => 0 + // [shareWith] => test1 + // [uidOwner] => admin + // [permissions] => 17 + // [fileSource] => 13 + // [fileTarget] => /test8 + // [id] => 10 + // [token] => + // [run] => whether emitting script should continue to run + // TODO: Should other kinds of item be encrypted too? + + if ($params['itemType'] === 'file' || $params['itemType'] === 'folder') { + + $view = new \OC_FilesystemView('/'); + $session = new \OCA\Encryption\Session($view); + $userId = \OCP\User::getUser(); + $util = new Util($view, $userId); + $path = $util->fileIdToPath($params['itemSource']); + + $share = $util->getParentFromShare($params['id']); + //if parent is set, then this is a re-share action + if ($share['parent'] !== null) { + + // get the parent from current share + $parent = $util->getShareParent($params['parent']); + + // if parent is file the it is an 1:1 share + if ($parent['item_type'] === 'file') { + + // prefix path with Shared + $path = '/Shared' . $parent['file_target']; + } else { + + // NOTE: parent is folder but shared was a file! + // we try to rebuild the missing path + // some examples we face here + // user1 share folder1 with user2 folder1 has + // the following structure + // /folder1/subfolder1/subsubfolder1/somefile.txt + // user2 re-share subfolder2 with user3 + // user3 re-share somefile.txt user4 + // so our path should be + // /Shared/subfolder1/subsubfolder1/somefile.txt + // while user3 is sharing + + if ($params['itemType'] === 'file') { + // get target path + $targetPath = $util->fileIdToPath($params['fileSource']); + $targetPathSplit = array_reverse(explode('/', $targetPath)); + + // init values + $path = ''; + $sharedPart = ltrim($parent['file_target'], '/'); + + // rebuild path + foreach ($targetPathSplit as $pathPart) { + if ($pathPart !== $sharedPart) { + $path = '/' . $pathPart . $path; + } else { + break; + } + } + // prefix path with Shared + $path = '/Shared' . $parent['file_target'] . $path; + } else { + // prefix path with Shared + $path = '/Shared' . $parent['file_target'] . $params['fileTarget']; + } + } + } + + $sharingEnabled = \OCP\Share::isEnabled(); + + // get the path including mount point only if not a shared folder + if (strncmp($path, '/Shared', strlen('/Shared') !== 0)) { + // get path including the the storage mount point + $path = $util->getPathWithMountPoint($params['itemSource']); + } + + // if a folder was shared, get a list of all (sub-)folders + if ($params['itemType'] === 'folder') { + $allFiles = $util->getAllFiles($path); + } else { + $allFiles = array($path); + } + + foreach ($allFiles as $path) { + $usersSharing = $util->getSharingUsersArray($sharingEnabled, $path); + $util->setSharedFileKeyfiles($session, $usersSharing, $path); + } + } + } + + /** + * @brief + */ + public static function postUnshare($params) { + + // NOTE: $params has keys: + // [itemType] => file + // [itemSource] => 13 + // [shareType] => 0 + // [shareWith] => test1 + // [itemParent] => + + if ($params['itemType'] === 'file' || $params['itemType'] === 'folder') { + + $view = new \OC_FilesystemView('/'); + $userId = \OCP\User::getUser(); + $util = new Util($view, $userId); + $path = $util->fileIdToPath($params['itemSource']); + + // check if this is a re-share + if ($params['itemParent']) { + + // get the parent from current share + $parent = $util->getShareParent($params['itemParent']); + + // get target path + $targetPath = $util->fileIdToPath($params['itemSource']); + $targetPathSplit = array_reverse(explode('/', $targetPath)); + + // init values + $path = ''; + $sharedPart = ltrim($parent['file_target'], '/'); + + // rebuild path + foreach ($targetPathSplit as $pathPart) { + if ($pathPart !== $sharedPart) { + $path = '/' . $pathPart . $path; + } else { + break; + } + } + + // prefix path with Shared + $path = '/Shared' . $parent['file_target'] . $path; + } + + // for group shares get a list of the group members + if ($params['shareType'] === \OCP\Share::SHARE_TYPE_GROUP) { + $userIds = \OC_Group::usersInGroup($params['shareWith']); + } else { + if ($params['shareType'] === \OCP\Share::SHARE_TYPE_LINK) { + $userIds = array($util->getPublicShareKeyId()); + } else { + $userIds = array($params['shareWith']); + } + } + + // get the path including mount point only if not a shared folder + if (strncmp($path, '/Shared', strlen('/Shared') !== 0)) { + // get path including the the storage mount point + $path = $util->getPathWithMountPoint($params['itemSource']); + } + + // if we unshare a folder we need a list of all (sub-)files + if ($params['itemType'] === 'folder') { + $allFiles = $util->getAllFiles($path); + } else { + $allFiles = array($path); + } + + foreach ($allFiles as $path) { + + // check if the user still has access to the file, otherwise delete share key + $sharingUsers = $util->getSharingUsersArray(true, $path); + + // Unshare every user who no longer has access to the file + $delUsers = array_diff($userIds, $sharingUsers); + + // delete share key + Keymanager::delShareKey($view, $delUsers, $path); + } + + } + } + + /** + * @brief after a file is renamed, rename its keyfile and share-keys also fix the file size and fix also the sharing + * @param array with oldpath and newpath + * + * This function is connected to the rename signal of OC_Filesystem and adjust the name and location + * of the stored versions along the actual file + */ + public static function postRename($params) { + // Disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + $view = new \OC_FilesystemView('/'); + $session = new \OCA\Encryption\Session($view); + $userId = \OCP\User::getUser(); + $util = new Util($view, $userId); + + // Format paths to be relative to user files dir + $oldKeyfilePath = \OC\Files\Filesystem::normalizePath( + $userId . '/' . 'files_encryption' . '/' . 'keyfiles' . '/' . $params['oldpath']); + $newKeyfilePath = \OC\Files\Filesystem::normalizePath( + $userId . '/' . 'files_encryption' . '/' . 'keyfiles' . '/' . $params['newpath']); + + // add key ext if this is not an folder + if (!$view->is_dir($oldKeyfilePath)) { + $oldKeyfilePath .= '.key'; + $newKeyfilePath .= '.key'; + + // handle share-keys + $localKeyPath = $view->getLocalFile($userId . '/files_encryption/share-keys/' . $params['oldpath']); + $matches = glob(preg_quote($localKeyPath) . '*.shareKey'); + foreach ($matches as $src) { + $dst = \OC\Files\Filesystem::normalizePath(str_replace($params['oldpath'], $params['newpath'], $src)); + + // create destination folder if not exists + if (!file_exists(dirname($dst))) { + mkdir(dirname($dst), 0750, true); + } + + rename($src, $dst); + } + + } else { + // handle share-keys folders + $oldShareKeyfilePath = \OC\Files\Filesystem::normalizePath( + $userId . '/' . 'files_encryption' . '/' . 'share-keys' . '/' . $params['oldpath']); + $newShareKeyfilePath = \OC\Files\Filesystem::normalizePath( + $userId . '/' . 'files_encryption' . '/' . 'share-keys' . '/' . $params['newpath']); + + // create destination folder if not exists + if (!$view->file_exists(dirname($newShareKeyfilePath))) { + $view->mkdir(dirname($newShareKeyfilePath), 0750, true); + } + + $view->rename($oldShareKeyfilePath, $newShareKeyfilePath); + } + + // Rename keyfile so it isn't orphaned + if ($view->file_exists($oldKeyfilePath)) { + + // create destination folder if not exists + if (!$view->file_exists(dirname($newKeyfilePath))) { + $view->mkdir(dirname($newKeyfilePath), 0750, true); + } + + $view->rename($oldKeyfilePath, $newKeyfilePath); + } + + // build the path to the file + $newPath = '/' . $userId . '/files' . $params['newpath']; + $newPathRelative = $params['newpath']; + + if ($util->fixFileSize($newPath)) { + // get sharing app state + $sharingEnabled = \OCP\Share::isEnabled(); + + // get users + $usersSharing = $util->getSharingUsersArray($sharingEnabled, $newPathRelative); + + // update sharing-keys + $util->setSharedFileKeyfiles($session, $usersSharing, $newPathRelative); + } + + \OC_FileProxy::$enabled = $proxyStatus; } - } diff --git a/apps/files_encryption/js/settings-admin.js b/apps/files_encryption/js/settings-admin.js new file mode 100644 index 0000000000..7c1866445e --- /dev/null +++ b/apps/files_encryption/js/settings-admin.js @@ -0,0 +1,102 @@ +/** + * Copyright (c) 2013, Sam Tuke , Robin Appelman + * + * This file is licensed under the Affero General Public License version 3 or later. + * See the COPYING-README file. + */ + +OC.msg={ + startSaving:function(selector){ + $(selector) + .html( t('settings', 'Saving...') ) + .removeClass('success') + .removeClass('error') + .stop(true, true) + .show(); + }, + finishedSaving:function(selector, data){ + if( data.status === "success" ){ + $(selector).html( data.data.message ) + .addClass('success') + .stop(true, true) + .delay(3000) + .fadeOut(900); + }else{ + $(selector).html( data.data.message ).addClass('error'); + } + } +}; + +$(document).ready(function(){ + // Trigger ajax on recoveryAdmin status change + var enabledStatus = $('#adminEnableRecovery').val(); + + $('input:password[name="recoveryPassword"]').keyup(function(event) { + var recoveryPassword = $( '#recoveryPassword' ).val(); + var checkedButton = $('input:radio[name="adminEnableRecovery"]:checked').val(); + var uncheckedValue = (1+parseInt(checkedButton)) % 2; + if (recoveryPassword != '' ) { + $('input:radio[name="adminEnableRecovery"][value="'+uncheckedValue.toString()+'"]').removeAttr("disabled"); + } else { + $('input:radio[name="adminEnableRecovery"][value="'+uncheckedValue.toString()+'"]').attr("disabled", "true"); + } + }); + + $( 'input:radio[name="adminEnableRecovery"]' ).change( + function() { + var recoveryStatus = $( this ).val(); + var oldStatus = (1+parseInt(recoveryStatus)) % 2; + var recoveryPassword = $( '#recoveryPassword' ).val(); + $.post( + OC.filePath( 'files_encryption', 'ajax', 'adminrecovery.php' ) + , { adminEnableRecovery: recoveryStatus, recoveryPassword: recoveryPassword } + , function( result ) { + if (result.status === "error") { + OC.Notification.show(t('admin', result.data.message)); + $('input:radio[name="adminEnableRecovery"][value="'+oldStatus.toString()+'"]').attr("checked", "true"); + } else { + OC.Notification.hide(); + if (recoveryStatus === "0") { + $('button:button[name="submitChangeRecoveryKey"]').attr("disabled", "true"); + $('input:password[name="changeRecoveryPassword"]').attr("disabled", "true"); + $('input:password[name="changeRecoveryPassword"]').val(""); + } else { + $('input:password[name="changeRecoveryPassword"]').removeAttr("disabled"); + } + } + } + ); + } + ); + + // change recovery password + + $('input:password[name="changeRecoveryPassword"]').keyup(function(event) { + var oldRecoveryPassword = $('input:password[id="oldRecoveryPassword"]').val(); + var newRecoveryPassword = $('input:password[id="newRecoveryPassword"]').val(); + if (newRecoveryPassword != '' && oldRecoveryPassword != '' ) { + $('button:button[name="submitChangeRecoveryKey"]').removeAttr("disabled"); + } else { + $('button:button[name="submitChangeRecoveryKey"]').attr("disabled", "true"); + } + }); + + + $('button:button[name="submitChangeRecoveryKey"]').click(function() { + var oldRecoveryPassword = $('input:password[id="oldRecoveryPassword"]').val(); + var newRecoveryPassword = $('input:password[id="newRecoveryPassword"]').val(); + OC.msg.startSaving('#encryption .msg'); + $.post( + OC.filePath( 'files_encryption', 'ajax', 'changeRecoveryPassword.php' ) + , { oldPassword: oldRecoveryPassword, newPassword: newRecoveryPassword } + , function( data ) { + if (data.status == "error") { + OC.msg.finishedSaving('#encryption .msg', data); + } else { + OC.msg.finishedSaving('#encryption .msg', data); + } + } + ); + }); + +}); \ No newline at end of file diff --git a/apps/files_encryption/js/settings-personal.js b/apps/files_encryption/js/settings-personal.js new file mode 100644 index 0000000000..d6535a25b7 --- /dev/null +++ b/apps/files_encryption/js/settings-personal.js @@ -0,0 +1,98 @@ +/** + * Copyright (c) 2013, Sam Tuke + * This file is licensed under the Affero General Public License version 3 or later. + * See the COPYING-README file. + */ + +function updatePrivateKeyPasswd() { + var oldPrivateKeyPassword = $('input:password[id="oldPrivateKeyPassword"]').val(); + var newPrivateKeyPassword = $('input:password[id="newPrivateKeyPassword"]').val(); + OC.msg.startSaving('#encryption .msg'); + $.post( + OC.filePath( 'files_encryption', 'ajax', 'updatePrivateKeyPassword.php' ) + , { oldPassword: oldPrivateKeyPassword, newPassword: newPrivateKeyPassword } + , function( data ) { + if (data.status === "error") { + OC.msg.finishedSaving('#encryption .msg', data); + } else { + OC.msg.finishedSaving('#encryption .msg', data); + } + } + ); +} + +$(document).ready(function(){ + + // Trigger ajax on recoveryAdmin status change + $( 'input:radio[name="userEnableRecovery"]' ).change( + function() { + + // Hide feedback messages in case they're already visible + $('#recoveryEnabledSuccess').hide(); + $('#recoveryEnabledError').hide(); + + var recoveryStatus = $( this ).val(); + + $.post( + OC.filePath( 'files_encryption', 'ajax', 'userrecovery.php' ) + , { userEnableRecovery: recoveryStatus } + , function( data ) { + if ( data.status == "success" ) { + $('#recoveryEnabledSuccess').show(); + } else { + $('#recoveryEnabledError').show(); + } + } + ); + // Ensure page is not reloaded on form submit + return false; + } + ); + + $("#encryptAll").click( + function(){ + + // Hide feedback messages in case they're already visible + $('#encryptAllSuccess').hide(); + $('#encryptAllError').hide(); + + var userPassword = $( '#userPassword' ).val(); + var encryptAll = $( '#encryptAll' ).val(); + + $.post( + OC.filePath( 'files_encryption', 'ajax', 'encryptall.php' ) + , { encryptAll: encryptAll, userPassword: userPassword } + , function( data ) { + if ( data.status == "success" ) { + $('#encryptAllSuccess').show(); + } else { + $('#encryptAllError').show(); + } + } + ); + // Ensure page is not reloaded on form submit + return false; + } + + ); + + // update private key password + + $('input:password[name="changePrivateKeyPassword"]').keyup(function(event) { + var oldPrivateKeyPassword = $('input:password[id="oldPrivateKeyPassword"]').val(); + var newPrivateKeyPassword = $('input:password[id="newPrivateKeyPassword"]').val(); + if (newPrivateKeyPassword !== '' && oldPrivateKeyPassword !== '' ) { + $('button:button[name="submitChangePrivateKeyPassword"]').removeAttr("disabled"); + if(event.which === 13) { + updatePrivateKeyPasswd(); + } + } else { + $('button:button[name="submitChangePrivateKeyPassword"]').attr("disabled", "true"); + } + }); + + $('button:button[name="submitChangePrivateKeyPassword"]').click(function() { + updatePrivateKeyPasswd(); + }); + +}); \ No newline at end of file diff --git a/apps/files_encryption/js/settings.js b/apps/files_encryption/js/settings.js deleted file mode 100644 index 0be857bb73..0000000000 --- a/apps/files_encryption/js/settings.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright (c) 2011, Robin Appelman - * This file is licensed under the Affero General Public License version 3 or later. - * See the COPYING-README file. - */ - - -$(document).ready(function(){ - $('#encryption_blacklist').multiSelect({ - oncheck:blackListChange, - onuncheck:blackListChange, - createText:'...' - }); - - function blackListChange(){ - var blackList=$('#encryption_blacklist').val().join(','); - OC.AppConfig.setValue('files_encryption','type_blacklist',blackList); - } -}) \ No newline at end of file diff --git a/apps/files_encryption/l10n/ar.php b/apps/files_encryption/l10n/ar.php index c8a475afd6..1adc158c6b 100644 --- a/apps/files_encryption/l10n/ar.php +++ b/apps/files_encryption/l10n/ar.php @@ -1,7 +1,4 @@ "التشفير", -"File encryption is enabled." => "تشفير الملفات فعال.", -"The following file types will not be encrypted:" => "الملفات الاتية لن يتم تشفيرها:", -"Exclude the following file types from encryption:" => "إستثناء أنواع الملفات الاتية من التشفير: ", -"None" => "لا شيء" +"Saving..." => "جاري الحفظ...", +"Encryption" => "التشفير" ); diff --git a/apps/files_encryption/l10n/bg_BG.php b/apps/files_encryption/l10n/bg_BG.php index 07a97f5f8a..f21f7641c1 100644 --- a/apps/files_encryption/l10n/bg_BG.php +++ b/apps/files_encryption/l10n/bg_BG.php @@ -1,4 +1,4 @@ "Криптиране", -"None" => "Няма" +"Saving..." => "Записване...", +"Encryption" => "Криптиране" ); diff --git a/apps/files_encryption/l10n/bn_BD.php b/apps/files_encryption/l10n/bn_BD.php index 43767d5651..068de46e7a 100644 --- a/apps/files_encryption/l10n/bn_BD.php +++ b/apps/files_encryption/l10n/bn_BD.php @@ -1,4 +1,4 @@ "সংকেতায়ন", -"None" => "কোনটিই নয়" +"Saving..." => "সংরক্ষণ করা হচ্ছে..", +"Encryption" => "সংকেতায়ন" ); diff --git a/apps/files_encryption/l10n/ca.php b/apps/files_encryption/l10n/ca.php index 2d59a306d3..01a0e6501d 100644 --- a/apps/files_encryption/l10n/ca.php +++ b/apps/files_encryption/l10n/ca.php @@ -1,7 +1,20 @@ "La clau de recuperació s'ha activat", +"Could not enable recovery key. Please check your recovery key password!" => "No s'ha pogut activar la clau de recuperació. Comproveu contrasenya de la clau de recuperació!", +"Recovery key successfully disabled" => "La clau de recuperació s'ha descativat", +"Could not disable recovery key. Please check your recovery key password!" => "No s'ha pogut desactivar la calu de recuperació. Comproveu la contrasenya de la clau de recuperació!", +"Password successfully changed." => "La contrasenya s'ha canviat.", +"Could not change the password. Maybe the old password was not correct." => "No s'ha pogut canviar la contrasenya. Potser la contrasenya anterior no era correcta.", +"Saving..." => "Desant...", "Encryption" => "Xifrat", -"File encryption is enabled." => "El xifrat de fitxers està activat.", -"The following file types will not be encrypted:" => "Els tipus de fitxers següents no es xifraran:", -"Exclude the following file types from encryption:" => "Exclou els tipus de fitxers següents del xifratge:", -"None" => "Cap" +"Enable encryption passwords recovery key (allow sharing to recovery key):" => "Activa la clau de recuperació de contrasenya (permet compartir la clau de recuperació):", +"Recovery account password" => "Contrasenya de recuperació del compte", +"Enabled" => "Activat", +"Disabled" => "Desactivat", +"Change encryption passwords recovery key:" => "Canvia la clau de recuperació de la contrasenya:", +"Old Recovery account password" => "Contrasenya de recuperació anterior", +"New Recovery account password" => "Nova contrasenya de recuperació de compte", +"Change Password" => "Canvia la contrasenya", +"File recovery settings updated" => "S'han actualitzat els arranjaments de recuperació de fitxers", +"Could not update file recovery" => "No s'ha pogut actualitzar la recuperació de fitxers" ); diff --git a/apps/files_encryption/l10n/cs_CZ.php b/apps/files_encryption/l10n/cs_CZ.php index d225688a07..d0f4b4a9a0 100644 --- a/apps/files_encryption/l10n/cs_CZ.php +++ b/apps/files_encryption/l10n/cs_CZ.php @@ -1,7 +1,20 @@ "Záchranný klíč byl úspěšně povolen", +"Could not enable recovery key. Please check your recovery key password!" => "Nepodařilo se povolit záchranný klíč. Zkontrolujte prosím vaše heslo záchranného klíče!", +"Recovery key successfully disabled" => "Záchranný klíč byl úspěšně zakázán", +"Could not disable recovery key. Please check your recovery key password!" => "Nelze zakázat záchranný klíč. Zkontrolujte prosím heslo vašeho záchranného klíče.", +"Password successfully changed." => "Heslo bylo úspěšně změněno.", +"Could not change the password. Maybe the old password was not correct." => "Nelze změnit heslo. Pravděpodobně nebylo stávající heslo zadáno správně.", +"Saving..." => "Ukládám...", "Encryption" => "Šifrování", -"File encryption is enabled." => "Šifrování je povoleno.", -"The following file types will not be encrypted:" => "Následující typy souborů nebudou šifrovány:", -"Exclude the following file types from encryption:" => "Vyjmout následující typy souborů ze šifrování:", -"None" => "Žádné" +"Enable encryption passwords recovery key (allow sharing to recovery key):" => "Povolit záchranný klíč šifrovacích hesel (povolí sdílený záchranný klíč):", +"Recovery account password" => "Heslo pro obnovu účtu", +"Enabled" => "Povoleno", +"Disabled" => "Zakázáno", +"Change encryption passwords recovery key:" => "Změnit záchranný klíč šifrovacích hesel:", +"Old Recovery account password" => "Stávající heslo pro obnovu účtu", +"New Recovery account password" => "Nové heslo pro obnovu účtu", +"Change Password" => "Změnit heslo", +"File recovery settings updated" => "Možnosti obnovy souborů aktualizovány", +"Could not update file recovery" => "Nelze aktualizovat obnovu souborů" ); diff --git a/apps/files_encryption/l10n/cy_GB.php b/apps/files_encryption/l10n/cy_GB.php index 523b5dd73d..6e18a7913c 100644 --- a/apps/files_encryption/l10n/cy_GB.php +++ b/apps/files_encryption/l10n/cy_GB.php @@ -1,7 +1,4 @@ "Amgryptiad", -"File encryption is enabled." => "Galluogwyd amgryptio ffeiliau.", -"The following file types will not be encrypted:" => "Ni fydd ffeiliau o'r math yma'n cael eu hamgryptio:", -"Exclude the following file types from encryption:" => "Eithrio'r mathau canlynol o ffeiliau rhag cael eu hamgryptio:", -"None" => "Dim" +"Saving..." => "Yn cadw...", +"Encryption" => "Amgryptiad" ); diff --git a/apps/files_encryption/l10n/da.php b/apps/files_encryption/l10n/da.php index b085381ea7..1cd43390aa 100644 --- a/apps/files_encryption/l10n/da.php +++ b/apps/files_encryption/l10n/da.php @@ -1,7 +1,4 @@ "Kryptering", -"File encryption is enabled." => "Fil kryptering aktiveret.", -"The following file types will not be encrypted:" => "De følgende filtyper vil ikke blive krypteret:", -"Exclude the following file types from encryption:" => "Ekskluder de følgende fil typer fra kryptering:", -"None" => "Ingen" +"Saving..." => "Gemmer...", +"Encryption" => "Kryptering" ); diff --git a/apps/files_encryption/l10n/de.php b/apps/files_encryption/l10n/de.php index bcf0ca5ad6..f5dd0dc1e2 100644 --- a/apps/files_encryption/l10n/de.php +++ b/apps/files_encryption/l10n/de.php @@ -1,7 +1,16 @@ "Wiederherstellungsschlüssel wurde erfolgreich aktiviert", +"Could not enable recovery key. Please check your recovery key password!" => "Der Wiederherstellungsschlüssel konnte nicht aktiviert werden. Überprüfen Sie Ihr Wiederherstellungspasswort!", +"Recovery key successfully disabled" => "Wiederherstellungsschlüssel deaktiviert.", +"Could not disable recovery key. Please check your recovery key password!" => "Der Wiederherstellungsschlüssel konnte nicht deaktiviert werden. Überprüfen Sie Ihr Wiederherstellungspasswort!", +"Password successfully changed." => "Dein Passwort wurde geändert.", +"Could not change the password. Maybe the old password was not correct." => "Das Passwort konnte nicht geändert werden. Vielleicht war das alte Passwort falsch.", +"Saving..." => "Speichern...", "Encryption" => "Verschlüsselung", -"File encryption is enabled." => "Dateiverschlüsselung ist aktiviert", -"The following file types will not be encrypted:" => "Die folgenden Dateitypen werden nicht verschlüsselt:", -"Exclude the following file types from encryption:" => "Schließe die folgenden Dateitypen von der Verschlüsselung aus:", -"None" => "Nichts" +"Recovery account password" => "Password zurücksetzen", +"Enabled" => "Aktiviert", +"Disabled" => "Deaktiviert", +"Change encryption passwords recovery key:" => "Wiederherstellungsschlüssel für Passwörter ändern:", +"Change Password" => "Passwort ändern", +"File recovery settings updated" => "Einstellungen zur Wiederherstellung von Dateien wurden aktualisiert" ); diff --git a/apps/files_encryption/l10n/de_DE.php b/apps/files_encryption/l10n/de_DE.php index 71fd7d9671..b12bda42f8 100644 --- a/apps/files_encryption/l10n/de_DE.php +++ b/apps/files_encryption/l10n/de_DE.php @@ -1,7 +1,18 @@ "Der Wiederherstellungsschlüssel wurde erfolgreich aktiviert.", +"Could not enable recovery key. Please check your recovery key password!" => "Der Wiederherstellungsschlüssel konnte nicht aktiviert werden. Bitte überprüfen Sie das Passwort für den Wiederherstellungsschlüssel!", +"Recovery key successfully disabled" => "Der Wiederherstellungsschlüssel wurde erfolgreich deaktiviert.", +"Could not disable recovery key. Please check your recovery key password!" => "Der Wiederherstellungsschlüssel konnte nicht deaktiviert werden. Bitte überprüfen Sie das Passwort für den Wiederherstellungsschlüssel!", +"Password successfully changed." => "Das Passwort wurde erfolgreich geändert.", +"Could not change the password. Maybe the old password was not correct." => "Das Passwort konnte nicht geändert werden. Vielleicht war das alte Passwort nicht richtig.", +"Saving..." => "Speichern...", "Encryption" => "Verschlüsselung", -"File encryption is enabled." => "Datei-Verschlüsselung ist aktiviert", -"The following file types will not be encrypted:" => "Die folgenden Dateitypen werden nicht verschlüsselt:", -"Exclude the following file types from encryption:" => "Die folgenden Dateitypen von der Verschlüsselung ausnehmen:", -"None" => "Nichts" +"Recovery account password" => "Account-Passwort wiederherstellen", +"Enabled" => "Aktiviert", +"Disabled" => "Deaktiviert", +"Old Recovery account password" => "Altes Passwort für die Account-Wiederherstellung", +"New Recovery account password" => "Neues Passwort für die Account-Wiederherstellung", +"Change Password" => "Passwort ändern", +"File recovery settings updated" => "Die Einstellungen für die Dateiwiederherstellung wurden aktualisiert.", +"Could not update file recovery" => "Die Dateiwiederherstellung konnte nicht aktualisiert werden." ); diff --git a/apps/files_encryption/l10n/el.php b/apps/files_encryption/l10n/el.php index 82a4c92ec2..7bf9be11a0 100644 --- a/apps/files_encryption/l10n/el.php +++ b/apps/files_encryption/l10n/el.php @@ -1,7 +1,11 @@ "Ο κωδικός αλλάχτηκε επιτυχώς.", +"Could not change the password. Maybe the old password was not correct." => "Αποτυχία αλλαγής κωδικού ίσως ο παλιός κωδικός να μην ήταν σωστός.", +"Saving..." => "Γίνεται αποθήκευση...", "Encryption" => "Κρυπτογράφηση", -"File encryption is enabled." => "Η κρυπτογράφηση αρχείων είναι ενεργή.", -"The following file types will not be encrypted:" => "Οι παρακάτω τύποι αρχείων δεν θα κρυπτογραφηθούν:", -"Exclude the following file types from encryption:" => "Εξαίρεση των παρακάτω τύπων αρχείων από την κρυπτογράφηση:", -"None" => "Τίποτα" +"Recovery account password" => "Επαναφορά κωδικού πρόσβασης λογαριασμού", +"Enabled" => "Ενεργοποιημένο", +"Disabled" => "Απενεργοποιημένο", +"Change Password" => "Αλλαγή Κωδικού Πρόσβασης", +"File recovery settings updated" => "Οι ρυθμίσεις επαναφοράς αρχείων ανανεώθηκαν" ); diff --git a/apps/files_encryption/l10n/eo.php b/apps/files_encryption/l10n/eo.php index 50847062c3..ea405fda1a 100644 --- a/apps/files_encryption/l10n/eo.php +++ b/apps/files_encryption/l10n/eo.php @@ -1,4 +1,4 @@ "Ĉifrado", -"None" => "Nenio" +"Saving..." => "Konservante...", +"Encryption" => "Ĉifrado" ); diff --git a/apps/files_encryption/l10n/es.php b/apps/files_encryption/l10n/es.php index 4ea87b92e7..0875872e9d 100644 --- a/apps/files_encryption/l10n/es.php +++ b/apps/files_encryption/l10n/es.php @@ -1,7 +1,20 @@ "Se ha habilitado la recuperación de archivos", +"Could not enable recovery key. Please check your recovery key password!" => "No se pudo habilitar la clave de recuperación. Por favor compruebe su contraseña.", +"Recovery key successfully disabled" => "Clave de recuperación deshabilitada", +"Could not disable recovery key. Please check your recovery key password!" => "No se pudo deshabilitar la clave de recuperación. Por favor compruebe su contraseña!", +"Password successfully changed." => "Su contraseña ha sido cambiada", +"Could not change the password. Maybe the old password was not correct." => "No se pudo cambiar la contraseña. Compruebe que la contraseña actual sea correcta.", +"Saving..." => "Guardando...", "Encryption" => "Cifrado", -"File encryption is enabled." => "La encriptacion de archivo esta activada.", -"The following file types will not be encrypted:" => "Los siguientes tipos de archivo no seran encriptados:", -"Exclude the following file types from encryption:" => "Excluir los siguientes tipos de archivo de la encriptacion:", -"None" => "Ninguno" +"Enable encryption passwords recovery key (allow sharing to recovery key):" => "Habilitar clave de recuperación de contraseñas ():", +"Recovery account password" => "Recuperar contraseña", +"Enabled" => "Habilitar", +"Disabled" => "Deshabilitado", +"Change encryption passwords recovery key:" => "Cambiar clave de cifrado de contraseñas:", +"Old Recovery account password" => "Contraseña de recuperación actual", +"New Recovery account password" => "Contraseña de recuperación nueva", +"Change Password" => "Cambiar contraseña", +"File recovery settings updated" => "Opciones de recuperación de archivos actualizada", +"Could not update file recovery" => "No se pudo actualizar la recuperación de archivos" ); diff --git a/apps/files_encryption/l10n/es_AR.php b/apps/files_encryption/l10n/es_AR.php index af522879e1..9d003a3751 100644 --- a/apps/files_encryption/l10n/es_AR.php +++ b/apps/files_encryption/l10n/es_AR.php @@ -1,7 +1,8 @@ "Tu contraseña fue cambiada", +"Could not change the password. Maybe the old password was not correct." => "No se pudo cambiar la contraseña. Comprobá que la contraseña actual sea correcta.", +"Saving..." => "Guardando...", "Encryption" => "Encriptación", -"File encryption is enabled." => "La encriptación de archivos no está habilitada", -"The following file types will not be encrypted:" => "Los siguientes tipos de archivos no serán encriptados", -"Exclude the following file types from encryption:" => "Excluir los siguientes tipos de archivos de encriptación:", -"None" => "Ninguno" +"Enable encryption passwords recovery key (allow sharing to recovery key):" => "Habilitar clave de recuperación de contraseñas (permite compartir clave de contraseñas):", +"Recovery account password" => "Recuperar contraseña" ); diff --git a/apps/files_encryption/l10n/et_EE.php b/apps/files_encryption/l10n/et_EE.php index 0d189ac062..e24da6ac67 100644 --- a/apps/files_encryption/l10n/et_EE.php +++ b/apps/files_encryption/l10n/et_EE.php @@ -1,7 +1,20 @@ "Taastevõtme lubamine õnnestus", +"Could not enable recovery key. Please check your recovery key password!" => "Ei suutnud lubada taastevõtit. Palun kontrolli oma taastevõtme parooli!", +"Recovery key successfully disabled" => "Taastevõtme keelamine õnnestus", +"Could not disable recovery key. Please check your recovery key password!" => "Ei suuda keelata taastevõtit. Palun kontrolli oma taastevõtme parooli!", +"Password successfully changed." => "Parool edukalt vahetatud.", +"Could not change the password. Maybe the old password was not correct." => "Ei suutnud vahetada parooli. Võib-olla on vana parool valesti sisestatud.", +"Saving..." => "Salvestamine...", "Encryption" => "Krüpteerimine", -"File encryption is enabled." => "Faili krüpteerimine on sisse lülitatud.", -"The following file types will not be encrypted:" => "Järgnevaid failitüüpe ei krüpteerita:", -"Exclude the following file types from encryption:" => "Järgnevaid failitüüpe ei krüpteerita:", -"None" => "Pole" +"Enable encryption passwords recovery key (allow sharing to recovery key):" => "Luba krüpteerimise paroolide taastevõti (võimalda parooli jagamine taastevõtmesse):", +"Recovery account password" => "Konto taasteparool", +"Enabled" => "Sisse lülitatud", +"Disabled" => "Väljalülitatud", +"Change encryption passwords recovery key:" => "Muuda taaste võtme krüpteerimise paroole:", +"Old Recovery account password" => "Konto vana taaste parool", +"New Recovery account password" => "Konto uus taasteparool", +"Change Password" => "Muuda parooli", +"File recovery settings updated" => "Faili taaste seaded uuendatud", +"Could not update file recovery" => "Ei suuda uuendada taastefaili" ); diff --git a/apps/files_encryption/l10n/eu.php b/apps/files_encryption/l10n/eu.php index 7e3b7611ff..253953e5c5 100644 --- a/apps/files_encryption/l10n/eu.php +++ b/apps/files_encryption/l10n/eu.php @@ -1,7 +1,4 @@ "Enkriptazioa", -"File encryption is enabled." => "Fitxategien enkriptazioa gaituta dago.", -"The following file types will not be encrypted:" => "Hurrengo fitxategi motak ez dira enkriptatuko:", -"Exclude the following file types from encryption:" => "Baztertu hurrengo fitxategi motak enkriptatzetik:", -"None" => "Ezer" +"Saving..." => "Gordetzen...", +"Encryption" => "Enkriptazioa" ); diff --git a/apps/files_encryption/l10n/fa.php b/apps/files_encryption/l10n/fa.php index 7acf196b79..af2e36b2a8 100644 --- a/apps/files_encryption/l10n/fa.php +++ b/apps/files_encryption/l10n/fa.php @@ -1,7 +1,4 @@ "رمزگذاری", -"File encryption is enabled." => "رمزنگاری فایلها فعال شد.", -"The following file types will not be encrypted:" => "فایلهای زیر رمزنگاری نخواهند شد:", -"Exclude the following file types from encryption:" => "فایلهای زیر از رمزنگاری نادیده گرفته می شوند:", -"None" => "هیچ‌کدام" +"Saving..." => "در حال ذخیره سازی...", +"Encryption" => "رمزگذاری" ); diff --git a/apps/files_encryption/l10n/fi_FI.php b/apps/files_encryption/l10n/fi_FI.php index 6352d396b3..a00cc8ab96 100644 --- a/apps/files_encryption/l10n/fi_FI.php +++ b/apps/files_encryption/l10n/fi_FI.php @@ -1,7 +1,9 @@ "Salasana vaihdettiin onnistuneesti.", +"Could not change the password. Maybe the old password was not correct." => "Salasanan vaihto epäonnistui. Kenties vanha salasana oli väärin.", +"Saving..." => "Tallennetaan...", "Encryption" => "Salaus", -"File encryption is enabled." => "Tiedostojen salaus on käytössä.", -"The following file types will not be encrypted:" => "Seuraavia tiedostotyyppejä ei salata:", -"Exclude the following file types from encryption:" => "Älä salaa seuravia tiedostotyyppejä:", -"None" => "Ei mitään" +"Enabled" => "Käytössä", +"Disabled" => "Ei käytössä", +"Change Password" => "Vaihda salasana" ); diff --git a/apps/files_encryption/l10n/fr.php b/apps/files_encryption/l10n/fr.php index 88f1e4a393..145c4b07a4 100644 --- a/apps/files_encryption/l10n/fr.php +++ b/apps/files_encryption/l10n/fr.php @@ -1,7 +1,20 @@ "Clé de récupération activée avec succès", +"Could not enable recovery key. Please check your recovery key password!" => "Ne peut pas activer la clé de récupération. s'il vous plait vérifiez votre mot de passe de clé de récupération!", +"Recovery key successfully disabled" => "Clé de récupération désactivée avc succès", +"Could not disable recovery key. Please check your recovery key password!" => "Ne peut pas désactiver la clé de récupération. S'il vous plait vérifiez votre mot de passe de clé de récupération!", +"Password successfully changed." => "Mot de passe changé avec succès ", +"Could not change the password. Maybe the old password was not correct." => "Ne peut pas changer le mot de passe. L'ancien mot de passe est peut-être incorrect.", +"Saving..." => "Enregistrement...", "Encryption" => "Chiffrement", -"File encryption is enabled." => "Le chiffrement des fichiers est activé", -"The following file types will not be encrypted:" => "Les fichiers de types suivants ne seront pas chiffrés :", -"Exclude the following file types from encryption:" => "Ne pas chiffrer les fichiers dont les types sont les suivants :", -"None" => "Aucun" +"Enable encryption passwords recovery key (allow sharing to recovery key):" => "Activer la clé de récupération par mots de passe de cryptage (autoriser le partage de la clé de récupération) ", +"Recovery account password" => "Rétablissement du compte mot de passe ", +"Enabled" => "Activer", +"Disabled" => "Désactiver", +"Change encryption passwords recovery key:" => "Changer les mots de passe de cryptage par la clé de récupération", +"Old Recovery account password" => "Ancien compte de récupération de mots de passe", +"New Recovery account password" => "Nouveau compte de récupération de mots de passe", +"Change Password" => "Changer de mot de passe", +"File recovery settings updated" => "Mise à jour des paramètres de récupération de fichiers ", +"Could not update file recovery" => "Ne peut pas remettre à jour les fichiers de récupération" ); diff --git a/apps/files_encryption/l10n/gl.php b/apps/files_encryption/l10n/gl.php index 3210f71545..9405ad063e 100644 --- a/apps/files_encryption/l10n/gl.php +++ b/apps/files_encryption/l10n/gl.php @@ -1,7 +1,20 @@ "Activada satisfactoriamente a chave de recuperación", +"Could not enable recovery key. Please check your recovery key password!" => "Non foi posíbel activar a chave de recuperación. Comprobe o contrasinal da chave de recuperación!", +"Recovery key successfully disabled" => "Desactivada satisfactoriamente a chave de recuperación", +"Could not disable recovery key. Please check your recovery key password!" => "Non foi posíbel desactivar a chave de recuperación. Comprobe o contrasinal da chave de recuperación!", +"Password successfully changed." => "O contrasinal foi cambiado satisfactoriamente", +"Could not change the password. Maybe the old password was not correct." => "Non foi posíbel cambiar o contrasinal. Probabelmente o contrasinal antigo non é o correcto.", +"Saving..." => "Gardando...", "Encryption" => "Cifrado", -"File encryption is enabled." => "O cifrado de ficheiros está activado", -"The following file types will not be encrypted:" => "Os seguintes tipos de ficheiros non van seren cifrados:", -"Exclude the following file types from encryption:" => "Excluír os seguintes tipos de ficheiros do cifrado:", -"None" => "Ningún" +"Enable encryption passwords recovery key (allow sharing to recovery key):" => "Activar a chave de recuperación do cifrado de contrasinais (permite compartir a chave de recuperación):", +"Recovery account password" => "Recuperación do contrasinal da conta", +"Enabled" => "Activado", +"Disabled" => "Desactivado", +"Change encryption passwords recovery key:" => "Cambiar a chave de la recuperación do cifrado de contrasinais:", +"Old Recovery account password" => "Antigo contrasinal de recuperación da conta", +"New Recovery account password" => "Novo contrasinal de recuperación da conta", +"Change Password" => "Cambiar o contrasinal", +"File recovery settings updated" => "Actualizouse o ficheiro de axustes de recuperación", +"Could not update file recovery" => "Non foi posíbel actualizar o ficheiro de recuperación" ); diff --git a/apps/files_encryption/l10n/he.php b/apps/files_encryption/l10n/he.php index cbb74bfee9..7a80cfa2f9 100644 --- a/apps/files_encryption/l10n/he.php +++ b/apps/files_encryption/l10n/he.php @@ -1,4 +1,4 @@ "הצפנה", -"None" => "כלום" +"Saving..." => "שמירה…", +"Encryption" => "הצפנה" ); diff --git a/apps/files_encryption/l10n/hr.php b/apps/files_encryption/l10n/hr.php new file mode 100644 index 0000000000..9b9284ddc5 --- /dev/null +++ b/apps/files_encryption/l10n/hr.php @@ -0,0 +1,3 @@ + "Spremanje..." +); diff --git a/apps/files_encryption/l10n/hu_HU.php b/apps/files_encryption/l10n/hu_HU.php index 4043da108c..bf95c31f2c 100644 --- a/apps/files_encryption/l10n/hu_HU.php +++ b/apps/files_encryption/l10n/hu_HU.php @@ -1,7 +1,4 @@ "Titkosítás", -"File encryption is enabled." => "Az állományok titkosítása be van kapcsolva.", -"The following file types will not be encrypted:" => "A következő fájltípusok nem kerülnek titkosításra:", -"Exclude the following file types from encryption:" => "Zárjuk ki a titkosításból a következő fájltípusokat:", -"None" => "Egyik sem" +"Saving..." => "Mentés...", +"Encryption" => "Titkosítás" ); diff --git a/apps/files_encryption/l10n/id.php b/apps/files_encryption/l10n/id.php index 6044348e72..ad827b5379 100644 --- a/apps/files_encryption/l10n/id.php +++ b/apps/files_encryption/l10n/id.php @@ -1,7 +1,4 @@ "Enkripsi", -"File encryption is enabled." => "Enkripsi berkas aktif.", -"The following file types will not be encrypted:" => "Tipe berkas berikut tidak akan dienkripsi:", -"Exclude the following file types from encryption:" => "Kecualikan tipe berkas berikut dari enkripsi:", -"None" => "Tidak ada" +"Saving..." => "Menyimpan...", +"Encryption" => "Enkripsi" ); diff --git a/apps/files_encryption/l10n/is.php b/apps/files_encryption/l10n/is.php index bd964185c4..0f98c6bd3b 100644 --- a/apps/files_encryption/l10n/is.php +++ b/apps/files_encryption/l10n/is.php @@ -1,4 +1,4 @@ "Dulkóðun", -"None" => "Ekkert" +"Saving..." => "Er að vista ...", +"Encryption" => "Dulkóðun" ); diff --git a/apps/files_encryption/l10n/it.php b/apps/files_encryption/l10n/it.php index c717134526..91a910baf6 100644 --- a/apps/files_encryption/l10n/it.php +++ b/apps/files_encryption/l10n/it.php @@ -1,7 +1,20 @@ "Chiave di ripristino abilitata correttamente", +"Could not enable recovery key. Please check your recovery key password!" => "Impossibile abilitare la chiave di ripristino. Verifica la password della chiave di ripristino.", +"Recovery key successfully disabled" => "Chiave di ripristinata disabilitata correttamente", +"Could not disable recovery key. Please check your recovery key password!" => "Impossibile disabilitare la chiave di ripristino. Verifica la password della chiave di ripristino.", +"Password successfully changed." => "Password modificata correttamente.", +"Could not change the password. Maybe the old password was not correct." => "Impossibile cambiare la password. Forse la vecchia password non era corretta.", +"Saving..." => "Salvataggio in corso...", "Encryption" => "Cifratura", -"File encryption is enabled." => "La cifratura dei file è abilitata.", -"The following file types will not be encrypted:" => "I seguenti tipi di file non saranno cifrati:", -"Exclude the following file types from encryption:" => "Escludi i seguenti tipi di file dalla cifratura:", -"None" => "Nessuno" +"Enable encryption passwords recovery key (allow sharing to recovery key):" => "Abilita la chiave di ripristino delle password di cifratura (consente di condividere la chiave di ripristino):", +"Recovery account password" => "Password di ripristino dell'account", +"Enabled" => "Abilitata", +"Disabled" => "Disabilitata", +"Change encryption passwords recovery key:" => "Cambia la chiave di ripristino delle password di cifratura:", +"Old Recovery account password" => "Vecchia password di ripristino dell'account", +"New Recovery account password" => "Nuova password di ripristino dell'account", +"Change Password" => "Modifica password", +"File recovery settings updated" => "Impostazioni di ripristino dei file aggiornate", +"Could not update file recovery" => "Impossibile aggiornare il ripristino dei file" ); diff --git a/apps/files_encryption/l10n/ja_JP.php b/apps/files_encryption/l10n/ja_JP.php index 35fba615ae..5fefff5754 100644 --- a/apps/files_encryption/l10n/ja_JP.php +++ b/apps/files_encryption/l10n/ja_JP.php @@ -1,7 +1,20 @@ "リカバリ用のキーは正常に有効化されました", +"Could not enable recovery key. Please check your recovery key password!" => "リカバリ用のキーを有効にできませんでした。リカバリ用のキーのパスワードを確認して下さい!", +"Recovery key successfully disabled" => "リカバリ用のキーを正常に無効化しました", +"Could not disable recovery key. Please check your recovery key password!" => "リカバリ用のキーを無効化できませんでした。リカバリ用のキーのパスワードを確認して下さい!", +"Password successfully changed." => "パスワードを変更できました。", +"Could not change the password. Maybe the old password was not correct." => "パスワードを変更できませんでした。古いパスワードが間違っているかもしれません。", +"Saving..." => "保存中...", "Encryption" => "暗号化", -"File encryption is enabled." => "ファイルの暗号化は有効です。", -"The following file types will not be encrypted:" => "次のファイルタイプは暗号化されません:", -"Exclude the following file types from encryption:" => "次のファイルタイプを暗号化から除外:", -"None" => "なし" +"Enable encryption passwords recovery key (allow sharing to recovery key):" => "暗号化パスワードの復旧キーを有効にする(復旧キーを共有することを許可):", +"Recovery account password" => "復旧アカウントのパスワード", +"Enabled" => "有効", +"Disabled" => "無効", +"Change encryption passwords recovery key:" => "復旧キーの暗号化パスワードを変更:", +"Old Recovery account password" => "古い復旧アカウントのパスワード", +"New Recovery account password" => "新しい復旧アカウントのパスワード", +"Change Password" => "パスワードを変更", +"File recovery settings updated" => "ファイル復旧設定が更新されました", +"Could not update file recovery" => "ファイル復旧を更新できませんでした" ); diff --git a/apps/files_encryption/l10n/ka_GE.php b/apps/files_encryption/l10n/ka_GE.php index 0362c676f0..55a59f4434 100644 --- a/apps/files_encryption/l10n/ka_GE.php +++ b/apps/files_encryption/l10n/ka_GE.php @@ -1,7 +1,4 @@ "ენკრიპცია", -"File encryption is enabled." => "ფაილის ენკრიპცია ჩართულია.", -"The following file types will not be encrypted:" => "შემდეგი ფაილური ტიპების ენკრიპცია არ მოხდება:", -"Exclude the following file types from encryption:" => "ამოიღე შემდეგი ფაილის ტიპები ენკრიპციიდან:", -"None" => "არა" +"Saving..." => "შენახვა...", +"Encryption" => "ენკრიპცია" ); diff --git a/apps/files_encryption/l10n/ko.php b/apps/files_encryption/l10n/ko.php index bd1580578c..cf8149da3a 100644 --- a/apps/files_encryption/l10n/ko.php +++ b/apps/files_encryption/l10n/ko.php @@ -1,4 +1,4 @@ "암호화", -"None" => "없음" +"Saving..." => "저장 중...", +"Encryption" => "암호화" ); diff --git a/apps/files_encryption/l10n/ku_IQ.php b/apps/files_encryption/l10n/ku_IQ.php index 02c030014f..61b720372e 100644 --- a/apps/files_encryption/l10n/ku_IQ.php +++ b/apps/files_encryption/l10n/ku_IQ.php @@ -1,4 +1,4 @@ "نهێنیکردن", -"None" => "هیچ" +"Saving..." => "پاشکه‌وتده‌کات...", +"Encryption" => "نهێنیکردن" ); diff --git a/apps/files_encryption/l10n/lb.php b/apps/files_encryption/l10n/lb.php new file mode 100644 index 0000000000..77bad68173 --- /dev/null +++ b/apps/files_encryption/l10n/lb.php @@ -0,0 +1,3 @@ + "Speicheren..." +); diff --git a/apps/files_encryption/l10n/lt_LT.php b/apps/files_encryption/l10n/lt_LT.php index 67769c8f36..16c6cdb40f 100644 --- a/apps/files_encryption/l10n/lt_LT.php +++ b/apps/files_encryption/l10n/lt_LT.php @@ -1,4 +1,20 @@ "Atkūrimo raktas sėkmingai įjungtas", +"Could not enable recovery key. Please check your recovery key password!" => "Neišėjo įjungti jūsų atkūrimo rakto. Prašome jį patikrinti!", +"Recovery key successfully disabled" => "Atkūrimo raktas sėkmingai išjungtas", +"Could not disable recovery key. Please check your recovery key password!" => "Neišėjo išjungti jūsų atkūrimo rakto. Prašome jį patikrinti!", +"Password successfully changed." => "Slaptažodis sėkmingai pakeistas", +"Could not change the password. Maybe the old password was not correct." => "Slaptažodis nebuvo pakeistas. Gali būti, kad buvo neteisingai suvestas senasis.", +"Saving..." => "Saugoma...", "Encryption" => "Šifravimas", -"None" => "Nieko" +"Enable encryption passwords recovery key (allow sharing to recovery key):" => "Įjungti šifravimo slaptažodžio atstatymo raktą (leidžia dalintis su atstatymo raktu):", +"Recovery account password" => "Atstatymo vartotojo slaptažodis", +"Enabled" => "Įjungta", +"Disabled" => "Išjungta", +"Change encryption passwords recovery key:" => "Pakeisti šifravimo slaptažodžio atstatymo raktą:", +"Old Recovery account password" => "Seno atstatymo vartotojo slaptažodis", +"New Recovery account password" => "naujo atstatymo vartotojo slaptažodis", +"Change Password" => "Pakeisti slaptažodį", +"File recovery settings updated" => "Failų atstatymo nustatymai pakeisti", +"Could not update file recovery" => "Neišėjo atnaujinti failų atkūrimo" ); diff --git a/apps/files_encryption/l10n/lv.php b/apps/files_encryption/l10n/lv.php index fc31ccdb92..04922854ce 100644 --- a/apps/files_encryption/l10n/lv.php +++ b/apps/files_encryption/l10n/lv.php @@ -1,7 +1,4 @@ "Šifrēšana", -"File encryption is enabled." => "Datņu šifrēšana ir aktivēta.", -"The following file types will not be encrypted:" => "Sekojošās datnes netiks šifrētas:", -"Exclude the following file types from encryption:" => "Sekojošos datņu tipus izslēgt no šifrēšanas:", -"None" => "Nav" +"Saving..." => "Saglabā...", +"Encryption" => "Šifrēšana" ); diff --git a/apps/files_encryption/l10n/mk.php b/apps/files_encryption/l10n/mk.php index 513606fadc..a7216f205a 100644 --- a/apps/files_encryption/l10n/mk.php +++ b/apps/files_encryption/l10n/mk.php @@ -1,4 +1,4 @@ "Енкрипција", -"None" => "Ништо" +"Saving..." => "Снимам...", +"Encryption" => "Енкрипција" ); diff --git a/apps/files_encryption/l10n/ms_MY.php b/apps/files_encryption/l10n/ms_MY.php new file mode 100644 index 0000000000..bb963cb72d --- /dev/null +++ b/apps/files_encryption/l10n/ms_MY.php @@ -0,0 +1,3 @@ + "Simpan..." +); diff --git a/apps/files_encryption/l10n/nb_NO.php b/apps/files_encryption/l10n/nb_NO.php index a5e16a0342..d4e2b1ffb5 100644 --- a/apps/files_encryption/l10n/nb_NO.php +++ b/apps/files_encryption/l10n/nb_NO.php @@ -1,7 +1,4 @@ "Kryptering", -"File encryption is enabled." => "Fil-kryptering er aktivert.", -"The following file types will not be encrypted:" => "Følgende filtyper vil ikke bli kryptert:", -"Exclude the following file types from encryption:" => "Ekskluder følgende filtyper fra kryptering:", -"None" => "Ingen" +"Saving..." => "Lagrer...", +"Encryption" => "Kryptering" ); diff --git a/apps/files_encryption/l10n/nl.php b/apps/files_encryption/l10n/nl.php index b1cba96aad..b97d27adc6 100644 --- a/apps/files_encryption/l10n/nl.php +++ b/apps/files_encryption/l10n/nl.php @@ -1,7 +1,20 @@ "Herstelsleutel succesvol geactiveerd", +"Could not enable recovery key. Please check your recovery key password!" => "Kon herstelsleutel niet activeren. Controleer het wachtwoord van uw herstelsleutel!", +"Recovery key successfully disabled" => "Herstelsleutel succesvol gedeactiveerd", +"Could not disable recovery key. Please check your recovery key password!" => "Kon herstelsleutel niet deactiveren. Controleer het wachtwoord van uw herstelsleutel!", +"Password successfully changed." => "Wachtwoord succesvol gewijzigd.", +"Could not change the password. Maybe the old password was not correct." => "Kon wachtwoord niet wijzigen. Wellicht oude wachtwoord niet juist ingevoerd.", +"Saving..." => "Opslaan", "Encryption" => "Versleuteling", -"File encryption is enabled." => "Bestandsversleuteling geactiveerd.", -"The following file types will not be encrypted:" => "De volgende bestandstypen zullen niet worden versleuteld:", -"Exclude the following file types from encryption:" => "Sluit de volgende bestandstypen uit van versleuteling:", -"None" => "Geen" +"Enable encryption passwords recovery key (allow sharing to recovery key):" => "Activeer versleuteling van wachtwoorden herstelsleutel (maak delen met herstel sleutel mogelijk):", +"Recovery account password" => "Herstel account wachtwoord", +"Enabled" => "Geactiveerd", +"Disabled" => "Gedeactiveerd", +"Change encryption passwords recovery key:" => "Wijzig versleuteling wachtwoord herstelsleutel", +"Old Recovery account password" => "Oude herstel account wachtwoord", +"New Recovery account password" => "Nieuwe herstel account wachtwoord", +"Change Password" => "Wijzigen wachtwoord", +"File recovery settings updated" => "Bestandsherstel instellingen bijgewerkt", +"Could not update file recovery" => "Kon bestandsherstel niet bijwerken" ); diff --git a/apps/files_encryption/l10n/nn_NO.php b/apps/files_encryption/l10n/nn_NO.php new file mode 100644 index 0000000000..97b3a27a9d --- /dev/null +++ b/apps/files_encryption/l10n/nn_NO.php @@ -0,0 +1,3 @@ + "Lagrar …" +); diff --git a/apps/files_encryption/l10n/oc.php b/apps/files_encryption/l10n/oc.php new file mode 100644 index 0000000000..0a34c4cda1 --- /dev/null +++ b/apps/files_encryption/l10n/oc.php @@ -0,0 +1,3 @@ + "Enregistra..." +); diff --git a/apps/files_encryption/l10n/pl.php b/apps/files_encryption/l10n/pl.php index 836f545359..f840aa7dda 100644 --- a/apps/files_encryption/l10n/pl.php +++ b/apps/files_encryption/l10n/pl.php @@ -1,7 +1,16 @@ "Zmiana hasła udana.", +"Could not change the password. Maybe the old password was not correct." => "Nie można zmienić hasła. Może stare hasło nie było poprawne.", +"Saving..." => "Zapisywanie...", "Encryption" => "Szyfrowanie", -"File encryption is enabled." => "Szyfrowanie plików jest włączone", -"The following file types will not be encrypted:" => "Poniższe typy plików nie będą szyfrowane:", -"Exclude the following file types from encryption:" => "Wyłącz poniższe typy plików z szyfrowania:", -"None" => "Nic" +"Enable encryption passwords recovery key (allow sharing to recovery key):" => "Włącz szyfrowanie odzyskiwanych haseł klucza (zezwalaj na odzyskiwanie klucza):", +"Recovery account password" => "Odzyskiwanie hasła konta", +"Enabled" => "Włączone", +"Disabled" => "Wyłączone", +"Change encryption passwords recovery key:" => "Zmiana klucza szyfrowania haseł odzyskiwania:", +"Old Recovery account password" => "Stare hasło odzyskiwania", +"New Recovery account password" => "Nowe hasło odzyskiwania", +"Change Password" => "Zmień hasło", +"File recovery settings updated" => "Ustawienia odzyskiwania plików zmienione", +"Could not update file recovery" => "Nie można zmienić pliku odzyskiwania" ); diff --git a/apps/files_encryption/l10n/pt_BR.php b/apps/files_encryption/l10n/pt_BR.php index b41c6ed315..868dfc4453 100644 --- a/apps/files_encryption/l10n/pt_BR.php +++ b/apps/files_encryption/l10n/pt_BR.php @@ -1,7 +1,20 @@ "Recuperação de chave habilitada com sucesso", +"Could not enable recovery key. Please check your recovery key password!" => "Impossível habilitar recuperação de chave. Por favor verifique sua senha para recuperação de chave!", +"Recovery key successfully disabled" => "Recuperação de chave desabilitada com sucesso", +"Could not disable recovery key. Please check your recovery key password!" => "Impossível desabilitar recuperação de chave. Por favor verifique sua senha para recuperação de chave!", +"Password successfully changed." => "Senha alterada com sucesso.", +"Could not change the password. Maybe the old password was not correct." => "Não foi possível alterar a senha. Talvez a senha antiga não estava correta.", +"Saving..." => "Salvando...", "Encryption" => "Criptografia", -"File encryption is enabled." => "A criptografia de arquivos está ativada.", -"The following file types will not be encrypted:" => "Os seguintes tipos de arquivo não serão criptografados:", -"Exclude the following file types from encryption:" => "Excluir os seguintes tipos de arquivo da criptografia:", -"None" => "Nada" +"Enable encryption passwords recovery key (allow sharing to recovery key):" => "Ativar a criptografia de chave de recuperação de senhas (permitir compartilhar a chave de recuperação):", +"Recovery account password" => "Recuperar a senha da conta", +"Enabled" => "Habilidado", +"Disabled" => "Desabilitado", +"Change encryption passwords recovery key:" => "Mudar a criptografia de chave de recuperação de senhas:", +"Old Recovery account password" => "Recuperação de senha de conta antiga", +"New Recovery account password" => "Senha Nova da conta de Recuperação", +"Change Password" => "Trocar Senha", +"File recovery settings updated" => "Configurações de recuperação de arquivo atualizado", +"Could not update file recovery" => "Não foi possível atualizar a recuperação de arquivos" ); diff --git a/apps/files_encryption/l10n/pt_PT.php b/apps/files_encryption/l10n/pt_PT.php index 1c46011fc1..8993cb6369 100644 --- a/apps/files_encryption/l10n/pt_PT.php +++ b/apps/files_encryption/l10n/pt_PT.php @@ -1,7 +1,20 @@ "Chave de recuperação activada com sucesso", +"Could not enable recovery key. Please check your recovery key password!" => "Não foi possível activar a chave de recuperação. Por favor verifique a password da chave de recuperação!", +"Recovery key successfully disabled" => "Chave de recuperação descativada com sucesso", +"Could not disable recovery key. Please check your recovery key password!" => "Não foi possível desactivar a chave de recuperação. Por favor verifique a password da chave de recuperação.", +"Password successfully changed." => "Password alterada com sucesso.", +"Could not change the password. Maybe the old password was not correct." => "Não foi possivel alterar a password. Possivelmente a password antiga não está correcta.", +"Saving..." => "A guardar...", "Encryption" => "Encriptação", -"File encryption is enabled." => "A encriptação de ficheiros está ligada", -"The following file types will not be encrypted:" => "Os seguintes ficheiros não serão encriptados:", -"Exclude the following file types from encryption:" => "Excluir da encriptação os seguintes tipos de ficheiro:", -"None" => "Nenhum" +"Enable encryption passwords recovery key (allow sharing to recovery key):" => "Activar a chave de recuperação das passwords de encriptação (permitir partilha da chave de recuperação):", +"Recovery account password" => "Password de recuperação de conta", +"Enabled" => "Activado", +"Disabled" => "Desactivado", +"Change encryption passwords recovery key:" => "Alterar a chave de recuperação da password de encriptação:", +"Old Recovery account password" => "Password de recuperação de conta antiga:", +"New Recovery account password" => "Nova password de recuperação de conta", +"Change Password" => "Mudar a Password", +"File recovery settings updated" => "Actualizadas as definições de recuperação de ficheiros", +"Could not update file recovery" => "Não foi possível actualizar a recuperação de ficheiros" ); diff --git a/apps/files_encryption/l10n/ro.php b/apps/files_encryption/l10n/ro.php index a5a6fb3cb7..9e04b627c4 100644 --- a/apps/files_encryption/l10n/ro.php +++ b/apps/files_encryption/l10n/ro.php @@ -1,4 +1,4 @@ "Încriptare", -"None" => "Niciuna" +"Saving..." => "Se salvează...", +"Encryption" => "Încriptare" ); diff --git a/apps/files_encryption/l10n/ru.php b/apps/files_encryption/l10n/ru.php index f07dec621d..d10bfa7e78 100644 --- a/apps/files_encryption/l10n/ru.php +++ b/apps/files_encryption/l10n/ru.php @@ -1,7 +1,16 @@ "Пароль изменен удачно.", +"Could not change the password. Maybe the old password was not correct." => "Невозможно изменить пароль. Возможно старый пароль не был верен.", +"Saving..." => "Сохранение...", "Encryption" => "Шифрование", -"File encryption is enabled." => "Шифрование файла включено.", -"The following file types will not be encrypted:" => "Следующие типы файлов не будут зашифрованы:", -"Exclude the following file types from encryption:" => "Исключить следующие типы файлов из шифрованных:", -"None" => "Нет новостей" +"Enable encryption passwords recovery key (allow sharing to recovery key):" => "Включить шифрование пароля ключа восстановления (понадобится разрешение для восстановления ключа)", +"Recovery account password" => "Восстановление пароля учетной записи", +"Enabled" => "Включено", +"Disabled" => "Отключено", +"Change encryption passwords recovery key:" => "Изменить шифрование пароля ключа восстановления:", +"Old Recovery account password" => "Старое Восстановление пароля учетной записи", +"New Recovery account password" => "Новое Восстановление пароля учетной записи", +"Change Password" => "Изменить пароль", +"File recovery settings updated" => "Настройки файла восстановления обновлены", +"Could not update file recovery" => "Невозможно обновить файл восстановления" ); diff --git a/apps/files_encryption/l10n/ru_RU.php b/apps/files_encryption/l10n/ru_RU.php index 7222235485..1351f63f89 100644 --- a/apps/files_encryption/l10n/ru_RU.php +++ b/apps/files_encryption/l10n/ru_RU.php @@ -1,4 +1,3 @@ "Шифрование", -"None" => "Ни один" +"Saving..." => "Сохранение" ); diff --git a/apps/files_encryption/l10n/si_LK.php b/apps/files_encryption/l10n/si_LK.php index d9cec4b722..6c678bb3a4 100644 --- a/apps/files_encryption/l10n/si_LK.php +++ b/apps/files_encryption/l10n/si_LK.php @@ -1,4 +1,4 @@ "ගුප්ත කේතනය", -"None" => "කිසිවක් නැත" +"Saving..." => "සුරැකෙමින් පවතී...", +"Encryption" => "ගුප්ත කේතනය" ); diff --git a/apps/files_encryption/l10n/sk_SK.php b/apps/files_encryption/l10n/sk_SK.php index aaea9da21b..279481fbd4 100644 --- a/apps/files_encryption/l10n/sk_SK.php +++ b/apps/files_encryption/l10n/sk_SK.php @@ -1,7 +1,11 @@ "Heslo úspešne zmenené.", +"Saving..." => "Ukladám...", "Encryption" => "Šifrovanie", -"File encryption is enabled." => "Šifrovanie súborov nastavené.", -"The following file types will not be encrypted:" => "Uvedené typy súborov nebudú šifrované:", -"Exclude the following file types from encryption:" => "Nešifrovať uvedené typy súborov", -"None" => "Žiadny" +"Enabled" => "Povolené", +"Disabled" => "Zakázané", +"Change encryption passwords recovery key:" => "Zmeniť šifrovacie heslo obnovovacieho kľúča:", +"Change Password" => "Zmeniť heslo", +"File recovery settings updated" => "Nastavenie obnovy súborov aktualizované", +"Could not update file recovery" => "Nemožno aktualizovať obnovenie súborov" ); diff --git a/apps/files_encryption/l10n/sl.php b/apps/files_encryption/l10n/sl.php index 4754e21214..a420fe161d 100644 --- a/apps/files_encryption/l10n/sl.php +++ b/apps/files_encryption/l10n/sl.php @@ -1,7 +1,4 @@ "Šifriranje", -"File encryption is enabled." => "Šifriranje datotek je omogočeno.", -"The following file types will not be encrypted:" => "Navedene vrste datotek ne bodo šifrirane:", -"Exclude the following file types from encryption:" => "Ne šifriraj navedenih vrst datotek:", -"None" => "Brez" +"Saving..." => "Poteka shranjevanje ...", +"Encryption" => "Šifriranje" ); diff --git a/apps/files_encryption/l10n/sr.php b/apps/files_encryption/l10n/sr.php index 91f7fc62a9..a36e37c179 100644 --- a/apps/files_encryption/l10n/sr.php +++ b/apps/files_encryption/l10n/sr.php @@ -1,4 +1,4 @@ "Шифровање", -"None" => "Ништа" +"Saving..." => "Чување у току...", +"Encryption" => "Шифровање" ); diff --git a/apps/files_encryption/l10n/sv.php b/apps/files_encryption/l10n/sv.php index e214a937a1..17d174df93 100644 --- a/apps/files_encryption/l10n/sv.php +++ b/apps/files_encryption/l10n/sv.php @@ -1,7 +1,20 @@ "Återställningsnyckeln har framgångsrikt aktiverats", +"Could not enable recovery key. Please check your recovery key password!" => "Kunde inte aktivera återställningsnyckeln. Vänligen kontrollera ditt lösenord för återställningsnyckeln!", +"Recovery key successfully disabled" => "Återställningsnyckeln har framgångsrikt inaktiverats", +"Could not disable recovery key. Please check your recovery key password!" => "Kunde inte inaktivera återställningsnyckeln. Vänligen kontrollera ditt lösenord för återställningsnyckeln!", +"Password successfully changed." => "Ändringen av lösenordet lyckades.", +"Could not change the password. Maybe the old password was not correct." => "Kunde inte ändra lösenordet. Kanske det gamla lösenordet inte var rätt.", +"Saving..." => "Sparar...", "Encryption" => "Kryptering", -"File encryption is enabled." => "Filkryptering är aktiverat.", -"The following file types will not be encrypted:" => "Följande filtyper kommer inte att krypteras:", -"Exclude the following file types from encryption:" => "Exkludera följande filtyper från kryptering:", -"None" => "Ingen" +"Enable encryption passwords recovery key (allow sharing to recovery key):" => "Aktivera återställningsnyckel för krypterade lösenord. (tillåt delning till återställningsnyckeln):", +"Recovery account password" => "Återställning av kontolösenord", +"Enabled" => "Aktiverad", +"Disabled" => "Inaktiverad", +"Change encryption passwords recovery key:" => "Ändra återställningsnyckeln för krypterade lösenord:", +"Old Recovery account password" => "Gamla lösenordet för återställningskontot", +"New Recovery account password" => "Nytt återställningslösenord för kontot", +"Change Password" => "Byt lösenord", +"File recovery settings updated" => "Inställningarna för filåterställning har uppdaterats", +"Could not update file recovery" => "Kunde inte uppdatera filåterställning" ); diff --git a/apps/files_encryption/l10n/ta_LK.php b/apps/files_encryption/l10n/ta_LK.php index 152e631d0f..63fe9ecde8 100644 --- a/apps/files_encryption/l10n/ta_LK.php +++ b/apps/files_encryption/l10n/ta_LK.php @@ -1,4 +1,4 @@ "மறைக்குறியீடு", -"None" => "ஒன்றுமில்லை" +"Saving..." => "சேமிக்கப்படுகிறது...", +"Encryption" => "மறைக்குறியீடு" ); diff --git a/apps/files_encryption/l10n/th_TH.php b/apps/files_encryption/l10n/th_TH.php index 30c0324a98..6cab4370cc 100644 --- a/apps/files_encryption/l10n/th_TH.php +++ b/apps/files_encryption/l10n/th_TH.php @@ -1,4 +1,4 @@ "การเข้ารหัส", -"None" => "ไม่มี" +"Saving..." => "กำลังบันทึกข้อมูล...", +"Encryption" => "การเข้ารหัส" ); diff --git a/apps/files_encryption/l10n/tr.php b/apps/files_encryption/l10n/tr.php index 6b42c757e6..59b91de928 100644 --- a/apps/files_encryption/l10n/tr.php +++ b/apps/files_encryption/l10n/tr.php @@ -1,7 +1,20 @@ "Kurtarma anahtarı başarıyla etkinleştirildi", +"Could not enable recovery key. Please check your recovery key password!" => "Kurtarma anahtarı etkinleştirilemedi. Lütfen kurtarma anahtarı parolanızı kontrol edin!", +"Recovery key successfully disabled" => "Kurtarma anahtarı başarıyla devre dışı bırakıldı", +"Could not disable recovery key. Please check your recovery key password!" => "Kurtarma anahtarı devre dışı bırakılamadı. Lütfen kurtarma anahtarı parolanızı kontrol edin!", +"Password successfully changed." => "Şifreniz başarıyla değiştirildi.", +"Could not change the password. Maybe the old password was not correct." => "Parola değiştirilemedi. Eski parolanız doğru olmayabilir", +"Saving..." => "Kaydediliyor...", "Encryption" => "Şifreleme", -"File encryption is enabled." => "Dosya şifreleme aktif.", -"The following file types will not be encrypted:" => "Belirtilen dosya tipleri şifrelenmeyecek:", -"Exclude the following file types from encryption:" => "Seçilen dosya tiplerini şifreleme:", -"None" => "Hiçbiri" +"Enable encryption passwords recovery key (allow sharing to recovery key):" => "Şifreli parola kurtarma anahtarını etkinleştir(kurtarma anahtarı paylaşımına izin ver)", +"Recovery account password" => "Kurtarma hesabı parolası", +"Enabled" => "Etkinleştirildi", +"Disabled" => "Devre dışı", +"Change encryption passwords recovery key:" => "Şifreli parolalar kurtarma anahtarını değiştir:", +"Old Recovery account password" => "Eski kurtarma hesabı parolası", +"New Recovery account password" => "Yeni kurtarma hesabı parolası", +"Change Password" => "Parola değiştir", +"File recovery settings updated" => "Dosya kurtarma ayarları güncellendi", +"Could not update file recovery" => "Dosya kurtarma güncellenemedi" ); diff --git a/apps/files_encryption/l10n/ug.php b/apps/files_encryption/l10n/ug.php index 34eeb373b3..954d95b413 100644 --- a/apps/files_encryption/l10n/ug.php +++ b/apps/files_encryption/l10n/ug.php @@ -1,7 +1,4 @@ "شىفىرلاش", -"File encryption is enabled." => "ھۆججەت شىفىرلاش قوزغىتىلدى.", -"The following file types will not be encrypted:" => "تۆۋەندىكى ھۆججەت تىپلىرى شىفىرلانمايدۇ:", -"Exclude the following file types from encryption:" => "تۆۋەندىكى ھۆججەت تىپلىرى شىفىرلاشنىڭ سىرتىدا:", -"None" => "يوق" +"Saving..." => "ساقلاۋاتىدۇ…", +"Encryption" => "شىفىرلاش" ); diff --git a/apps/files_encryption/l10n/uk.php b/apps/files_encryption/l10n/uk.php index d495714119..1c176a3914 100644 --- a/apps/files_encryption/l10n/uk.php +++ b/apps/files_encryption/l10n/uk.php @@ -1,7 +1,4 @@ "Шифрування", -"File encryption is enabled." => "Увімкнуто шифрування файлів.", -"The following file types will not be encrypted:" => "Такі типи файлів шифруватись не будуть:", -"Exclude the following file types from encryption:" => "Виключити наступні типи файлів з ​​шифрування:", -"None" => "Жоден" +"Saving..." => "Зберігаю...", +"Encryption" => "Шифрування" ); diff --git a/apps/files_encryption/l10n/vi.php b/apps/files_encryption/l10n/vi.php index 40d4b1d0fe..3e76060b1d 100644 --- a/apps/files_encryption/l10n/vi.php +++ b/apps/files_encryption/l10n/vi.php @@ -1,7 +1,10 @@ "Đã đổi mật khẩu.", +"Could not change the password. Maybe the old password was not correct." => "Không thể đổi mật khẩu. Có lẽ do mật khẩu cũ không đúng.", +"Saving..." => "Đang lưu...", "Encryption" => "Mã hóa", -"File encryption is enabled." => "Mã hóa file đã mở", -"The following file types will not be encrypted:" => "Loại file sau sẽ không được mã hóa", -"Exclude the following file types from encryption:" => "Việc mã hóa không bao gồm loại file sau", -"None" => "Không gì cả" +"Recovery account password" => "Mật khẩu cho tài khoản cứu hộ", +"Enabled" => "Bật", +"Disabled" => "Tắt", +"Change Password" => "Đổi Mật khẩu" ); diff --git a/apps/files_encryption/l10n/zh_CN.GB2312.php b/apps/files_encryption/l10n/zh_CN.GB2312.php index 12d903e656..3c405a81ac 100644 --- a/apps/files_encryption/l10n/zh_CN.GB2312.php +++ b/apps/files_encryption/l10n/zh_CN.GB2312.php @@ -1,4 +1,4 @@ "加密", -"None" => "无" +"Saving..." => "保存中...", +"Encryption" => "加密" ); diff --git a/apps/files_encryption/l10n/zh_CN.php b/apps/files_encryption/l10n/zh_CN.php index 13fa95203e..c64ae9dcd4 100644 --- a/apps/files_encryption/l10n/zh_CN.php +++ b/apps/files_encryption/l10n/zh_CN.php @@ -1,7 +1,20 @@ "恢复密钥成功启用", +"Could not enable recovery key. Please check your recovery key password!" => "不能启用恢复密钥。请检查恢复密钥密码!", +"Recovery key successfully disabled" => "恢复密钥成功禁用", +"Could not disable recovery key. Please check your recovery key password!" => "不能禁用恢复密钥。请检查恢复密钥密码!", +"Password successfully changed." => "密码修改成功。", +"Could not change the password. Maybe the old password was not correct." => "不能修改密码。旧密码可能不正确。", +"Saving..." => "保存中", "Encryption" => "加密", -"File encryption is enabled." => "文件加密已启用.", -"The following file types will not be encrypted:" => "如下的文件类型将不会被加密:", -"Exclude the following file types from encryption:" => "从加密中排除如下的文件类型:", -"None" => "无" +"Enable encryption passwords recovery key (allow sharing to recovery key):" => "启用加密密码恢复密钥(允许共享恢复密钥):", +"Recovery account password" => "恢复账户密码", +"Enabled" => "开启", +"Disabled" => "禁用", +"Change encryption passwords recovery key:" => "变更加密密码恢复密钥:", +"Old Recovery account password" => "旧恢复账号密码", +"New Recovery account password" => "新恢复账号密码", +"Change Password" => "修改密码", +"File recovery settings updated" => "文件恢复设置已更新", +"Could not update file recovery" => "不能更新文件恢复" ); diff --git a/apps/files_encryption/l10n/zh_HK.php b/apps/files_encryption/l10n/zh_HK.php index 0c0b709fdc..0a38a2ddf8 100644 --- a/apps/files_encryption/l10n/zh_HK.php +++ b/apps/files_encryption/l10n/zh_HK.php @@ -1,6 +1,3 @@ "加密", -"File encryption is enabled." => "檔案加密已開啟", -"The following file types will not be encrypted:" => "以下文件類別將不會被加密", -"None" => "空" +"Encryption" => "加密" ); diff --git a/apps/files_encryption/l10n/zh_TW.php b/apps/files_encryption/l10n/zh_TW.php index 95e61b45dc..77efdb3e56 100644 --- a/apps/files_encryption/l10n/zh_TW.php +++ b/apps/files_encryption/l10n/zh_TW.php @@ -1,7 +1,12 @@ "成功變更密碼。", +"Could not change the password. Maybe the old password was not correct." => "無法變更密碼,或許是輸入的舊密碼不正確。", +"Saving..." => "儲存中...", "Encryption" => "加密", -"File encryption is enabled." => "檔案加密已被啟用", -"The following file types will not be encrypted:" => "以下的文件類型不會被加密:", -"Exclude the following file types from encryption:" => "從加密中排除的檔案類型:", -"None" => "無" +"Enabled" => "已啓用", +"Disabled" => "已停用", +"Change encryption passwords recovery key:" => "變更加密密碼還原金鑰:", +"Change Password" => "變更密碼", +"File recovery settings updated" => "檔案還原設定已更新", +"Could not update file recovery" => "無法更新檔案還原設定" ); diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 437a18669e..cd41390d1c 100755 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -25,13 +25,8 @@ namespace OCA\Encryption; -require_once 'Crypt_Blowfish/Blowfish.php'; - -// Todo: -// - Add a setting "Don´t encrypt files larger than xx because of performance" -// - Don't use a password directly as encryption key. but a key which is -// stored on the server and encrypted with the user password. -> change pass -// faster +//require_once '../3rdparty/Crypt_Blowfish/Blowfish.php'; +require_once realpath(dirname(__FILE__) . '/../3rdparty/Crypt_Blowfish/Blowfish.php'); /** * Class for common cryptography functionality @@ -41,10 +36,10 @@ class Crypt { /** * @brief return encryption mode client or server side encryption - * @param string user name (use system wide setting if name=null) + * @param string $user name (use system wide setting if name=null) * @return string 'client' or 'server' */ - public static function mode( $user = null ) { + public static function mode($user = null) { return 'server'; @@ -56,30 +51,33 @@ class Crypt { */ public static function createKeypair() { - $res = openssl_pkey_new(); + $res = openssl_pkey_new(array('private_key_bits' => 4096)); // Get private key - openssl_pkey_export( $res, $privateKey ); + openssl_pkey_export($res, $privateKey); // Get public key - $publicKey = openssl_pkey_get_details( $res ); + $publicKey = openssl_pkey_get_details($res); $publicKey = $publicKey['key']; - return( array( 'publicKey' => $publicKey, 'privateKey' => $privateKey ) ); + return (array( + 'publicKey' => $publicKey, + 'privateKey' => $privateKey + )); } /** * @brief Add arbitrary padding to encrypted data * @param string $data data to be padded - * @return padded data + * @return string padded data * @note In order to end up with data exactly 8192 bytes long we must * add two letters. It is impossible to achieve exactly 8192 length * blocks with encryption alone, hence padding is added to achieve the * required length. */ - public static function addPadding( $data ) { + public static function addPadding($data) { $padded = $data . 'xx'; @@ -90,13 +88,13 @@ class Crypt { /** * @brief Remove arbitrary padding to encrypted data * @param string $padded padded data to remove padding from - * @return unpadded data on success, false on error + * @return string unpadded data on success, false on error */ - public static function removePadding( $padded ) { + public static function removePadding($padded) { - if ( substr( $padded, -2 ) == 'xx' ) { + if (substr($padded, -2) === 'xx') { - $data = substr( $padded, 0, -2 ); + $data = substr($padded, 0, -2); return $data; @@ -111,29 +109,30 @@ class Crypt { /** * @brief Check if a file's contents contains an IV and is symmetrically encrypted - * @return true / false + * @param $content + * @return boolean * @note see also OCA\Encryption\Util->isEncryptedPath() */ - public static function isCatfile( $content ) { + public static function isCatfileContent($content) { - if ( !$content ) { + if (!$content) { return false; } - $noPadding = self::removePadding( $content ); + $noPadding = self::removePadding($content); // Fetch encryption metadata from end of file - $meta = substr( $noPadding, -22 ); + $meta = substr($noPadding, -22); // Fetch IV from end of file - $iv = substr( $meta, -16 ); + $iv = substr($meta, -16); // Fetch identifier from start of metadata - $identifier = substr( $meta, 0, 6 ); + $identifier = substr($meta, 0, 6); - if ( $identifier == '00iv00') { + if ($identifier === '00iv00') { return true; @@ -150,36 +149,36 @@ class Crypt { * @param string $path * @return bool */ - public static function isEncryptedMeta( $path ) { + public static function isEncryptedMeta($path) { // TODO: Use DI to get \OC\Files\Filesystem out of here // Fetch all file metadata from DB - $metadata = \OC\Files\Filesystem::getFileInfo( $path, '' ); + $metadata = \OC\Files\Filesystem::getFileInfo($path); // Return encryption status - return isset( $metadata['encrypted'] ) and ( bool )$metadata['encrypted']; + return isset($metadata['encrypted']) && ( bool )$metadata['encrypted']; } /** * @brief Check if a file is encrypted via legacy system + * @param $data * @param string $relPath The path of the file, relative to user/data; * e.g. filename or /Docs/filename, NOT admin/files/filename - * @return true / false + * @return boolean */ - public static function isLegacyEncryptedContent( $data, $relPath ) { + public static function isLegacyEncryptedContent($data, $relPath) { // Fetch all file metadata from DB - $metadata = \OC\Files\Filesystem::getFileInfo( $relPath, '' ); + $metadata = \OC\Files\Filesystem::getFileInfo($relPath, ''); // If a file is flagged with encryption in DB, but isn't a // valid content + IV combination, it's probably using the // legacy encryption system - if ( - isset( $metadata['encrypted'] ) - and $metadata['encrypted'] === true - and ! self::isCatfile( $data ) + if (isset($metadata['encrypted']) + && $metadata['encrypted'] === true + && !self::isCatfileContent($data) ) { return true; @@ -194,17 +193,20 @@ class Crypt { /** * @brief Symmetrically encrypt a string - * @returns encrypted file + * @param $plainContent + * @param $iv + * @param string $passphrase + * @return string encrypted file content */ - public static function encrypt( $plainContent, $iv, $passphrase = '' ) { + public static function encrypt($plainContent, $iv, $passphrase = '') { - if ( $encryptedContent = openssl_encrypt( $plainContent, 'AES-128-CFB', $passphrase, false, $iv ) ) { + if ($encryptedContent = openssl_encrypt($plainContent, 'AES-128-CFB', $passphrase, false, $iv)) { return $encryptedContent; } else { - \OC_Log::write( 'Encryption library', 'Encryption (symmetric) of content failed', \OC_Log::ERROR ); + \OCP\Util::writeLog('Encryption library', 'Encryption (symmetric) of content failed', \OCP\Util::ERROR); return false; @@ -214,18 +216,21 @@ class Crypt { /** * @brief Symmetrically decrypt a string - * @returns decrypted file + * @param $encryptedContent + * @param $iv + * @param $passphrase + * @throws \Exception + * @return string decrypted file content */ - public static function decrypt( $encryptedContent, $iv, $passphrase ) { + public static function decrypt($encryptedContent, $iv, $passphrase) { - if ( $plainContent = openssl_decrypt( $encryptedContent, 'AES-128-CFB', $passphrase, false, $iv ) ) { + if ($plainContent = openssl_decrypt($encryptedContent, 'AES-128-CFB', $passphrase, false, $iv)) { return $plainContent; - } else { - throw new \Exception( 'Encryption library: Decryption (symmetric) of content failed' ); + throw new \Exception('Encryption library: Decryption (symmetric) of content failed'); } @@ -237,7 +242,7 @@ class Crypt { * @param string $iv IV to be concatenated * @returns string concatenated content */ - public static function concatIv ( $content, $iv ) { + public static function concatIv($content, $iv) { $combined = $content . '00iv00' . $iv; @@ -250,20 +255,20 @@ class Crypt { * @param string $catFile concatenated data to be split * @returns array keys: encrypted, iv */ - public static function splitIv ( $catFile ) { + public static function splitIv($catFile) { // Fetch encryption metadata from end of file - $meta = substr( $catFile, -22 ); + $meta = substr($catFile, -22); // Fetch IV from end of file - $iv = substr( $meta, -16 ); + $iv = substr($meta, -16); // Remove IV and IV identifier text to expose encrypted content - $encrypted = substr( $catFile, 0, -22 ); + $encrypted = substr($catFile, 0, -22); $split = array( - 'encrypted' => $encrypted - , 'iv' => $iv + 'encrypted' => $encrypted, + 'iv' => $iv ); return $split; @@ -272,14 +277,16 @@ class Crypt { /** * @brief Symmetrically encrypts a string and returns keyfile content - * @param $plainContent content to be encrypted in keyfile - * @returns encrypted content combined with IV + * @param string $plainContent content to be encrypted in keyfile + * @param string $passphrase + * @return bool|string + * @return string encrypted content combined with IV * @note IV need not be specified, as it will be stored in the returned keyfile * and remain accessible therein. */ - public static function symmetricEncryptFileContent( $plainContent, $passphrase = '' ) { + public static function symmetricEncryptFileContent($plainContent, $passphrase = '') { - if ( !$plainContent ) { + if (!$plainContent) { return false; @@ -287,18 +294,18 @@ class Crypt { $iv = self::generateIv(); - if ( $encryptedContent = self::encrypt( $plainContent, $iv, $passphrase ) ) { + if ($encryptedContent = self::encrypt($plainContent, $iv, $passphrase)) { // Combine content to encrypt with IV identifier and actual IV - $catfile = self::concatIv( $encryptedContent, $iv ); + $catfile = self::concatIv($encryptedContent, $iv); - $padded = self::addPadding( $catfile ); + $padded = self::addPadding($catfile); return $padded; } else { - \OC_Log::write( 'Encryption library', 'Encryption (symmetric) of keyfile content failed', \OC_Log::ERROR ); + \OCP\Util::writeLog('Encryption library', 'Encryption (symmetric) of keyfile content failed', \OCP\Util::ERROR); return false; @@ -309,35 +316,69 @@ class Crypt { /** * @brief Symmetrically decrypts keyfile content - * @param string $source - * @param string $target - * @param string $key the decryption key - * @returns decrypted content + * @param $keyfileContent + * @param string $passphrase + * @throws \Exception + * @return bool|string + * @internal param string $source + * @internal param string $target + * @internal param string $key the decryption key + * @returns string decrypted content * * This function decrypts a file */ - public static function symmetricDecryptFileContent( $keyfileContent, $passphrase = '' ) { + public static function symmetricDecryptFileContent($keyfileContent, $passphrase = '') { - if ( !$keyfileContent ) { + if (!$keyfileContent) { - throw new \Exception( 'Encryption library: no data provided for decryption' ); + throw new \Exception('Encryption library: no data provided for decryption'); } // Remove padding - $noPadding = self::removePadding( $keyfileContent ); + $noPadding = self::removePadding($keyfileContent); // Split into enc data and catfile - $catfile = self::splitIv( $noPadding ); + $catfile = self::splitIv($noPadding); - if ( $plainContent = self::decrypt( $catfile['encrypted'], $catfile['iv'], $passphrase ) ) { + if ($plainContent = self::decrypt($catfile['encrypted'], $catfile['iv'], $passphrase)) { return $plainContent; + } else { + return false; } } + /** + * @brief Decrypt private key and check if the result is a valid keyfile + * @param string $encryptedKey encrypted keyfile + * @param string $passphrase to decrypt keyfile + * @returns encrypted private key or false + * + * This function decrypts a file + */ + public static function decryptPrivateKey($encryptedKey, $passphrase) { + + $plainKey = self::symmetricDecryptFileContent($encryptedKey, $passphrase); + + // check if this a valid private key + $res = openssl_pkey_get_private($plainKey); + if (is_resource($res)) { + $sslInfo = openssl_pkey_get_details($res); + if (!isset($sslInfo['key'])) { + $plainKey = false; + } + } else { + $plainKey = false; + } + + return $plainKey; + + } + + /** * @brief Creates symmetric keyfile content using a generated key * @param string $plainContent content to be encrypted @@ -346,15 +387,15 @@ class Crypt { * * This function decrypts a file */ - public static function symmetricEncryptFileContentKeyfile( $plainContent ) { + public static function symmetricEncryptFileContentKeyfile($plainContent) { $key = self::generateKey(); - if( $encryptedContent = self::symmetricEncryptFileContent( $plainContent, $key ) ) { + if ($encryptedContent = self::symmetricEncryptFileContent($plainContent, $key)) { return array( - 'key' => $key - , 'encrypted' => $encryptedContent + 'key' => $key, + 'encrypted' => $encryptedContent ); } else { @@ -368,22 +409,41 @@ class Crypt { /** * @brief Create asymmetrically encrypted keyfile content using a generated key * @param string $plainContent content to be encrypted - * @returns array keys: key, encrypted - * @note symmetricDecryptFileContent() can be used to decrypt files created using this method - * - * This function decrypts a file + * @param array $publicKeys array keys must be the userId of corresponding user + * @returns array keys: keys (array, key = userId), data + * @note symmetricDecryptFileContent() can decrypt files created using this method */ - public static function multiKeyEncrypt( $plainContent, array $publicKeys ) { + public static function multiKeyEncrypt($plainContent, array $publicKeys) { + + // openssl_seal returns false without errors if $plainContent + // is empty, so trigger our own error + if (empty($plainContent)) { + + throw new \Exception('Cannot mutliKeyEncrypt empty plain content'); + + } // Set empty vars to be set by openssl by reference $sealed = ''; - $envKeys = array(); + $shareKeys = array(); + $mappedShareKeys = array(); - if( openssl_seal( $plainContent, $sealed, $envKeys, $publicKeys ) ) { + if (openssl_seal($plainContent, $sealed, $shareKeys, $publicKeys)) { + + $i = 0; + + // Ensure each shareKey is labelled with its + // corresponding userId + foreach ($publicKeys as $userId => $publicKey) { + + $mappedShareKeys[$userId] = $shareKeys[$i]; + $i++; + + } return array( - 'keys' => $envKeys - , 'encrypted' => $sealed + 'keys' => $mappedShareKeys, + 'data' => $sealed ); } else { @@ -396,27 +456,31 @@ class Crypt { /** * @brief Asymmetrically encrypt a file using multiple public keys - * @param string $plainContent content to be encrypted + * @param $encryptedContent + * @param $shareKey + * @param $privateKey + * @return bool + * @internal param string $plainContent content to be encrypted * @returns string $plainContent decrypted string * @note symmetricDecryptFileContent() can be used to decrypt files created using this method * * This function decrypts a file */ - public static function multiKeyDecrypt( $encryptedContent, $envKey, $privateKey ) { + public static function multiKeyDecrypt($encryptedContent, $shareKey, $privateKey) { - if ( !$encryptedContent ) { + if (!$encryptedContent) { return false; } - if ( openssl_open( $encryptedContent, $plainContent, $envKey, $privateKey ) ) { + if (openssl_open($encryptedContent, $plainContent, $shareKey, $privateKey)) { return $plainContent; } else { - \OC_Log::write( 'Encryption library', 'Decryption (asymmetric) of sealed content failed', \OC_Log::ERROR ); + \OCP\Util::writeLog('Encryption library', 'Decryption (asymmetric) of sealed content failed', \OCP\Util::ERROR); return false; @@ -425,12 +489,14 @@ class Crypt { } /** - * @brief Asymmetrically encrypt a string using a public key - * @returns encrypted file + * @brief Asymetrically encrypt a string using a public key + * @param $plainContent + * @param $publicKey + * @return string encrypted file */ - public static function keyEncrypt( $plainContent, $publicKey ) { + public static function keyEncrypt($plainContent, $publicKey) { - openssl_public_encrypt( $plainContent, $encryptedContent, $publicKey ); + openssl_public_encrypt($plainContent, $encryptedContent, $publicKey); return $encryptedContent; @@ -438,110 +504,19 @@ class Crypt { /** * @brief Asymetrically decrypt a file using a private key - * @returns decrypted file + * @param $encryptedContent + * @param $privatekey + * @return string decrypted file */ - public static function keyDecrypt( $encryptedContent, $privatekey ) { + public static function keyDecrypt($encryptedContent, $privatekey) { - openssl_private_decrypt( $encryptedContent, $plainContent, $privatekey ); - - return $plainContent; - - } - - /** - * @brief Encrypts content symmetrically and generates keyfile asymmetrically - * @returns array containing catfile and new keyfile. - * keys: data, key - * @note this method is a wrapper for combining other crypt class methods - */ - public static function keyEncryptKeyfile( $plainContent, $publicKey ) { - - // Encrypt plain data, generate keyfile & encrypted file - $cryptedData = self::symmetricEncryptFileContentKeyfile( $plainContent ); - - // Encrypt keyfile - $cryptedKey = self::keyEncrypt( $cryptedData['key'], $publicKey ); - - return array( 'data' => $cryptedData['encrypted'], 'key' => $cryptedKey ); - - } - - /** - * @brief Takes catfile, keyfile, and private key, and - * performs decryption - * @returns decrypted content - * @note this method is a wrapper for combining other crypt class methods - */ - public static function keyDecryptKeyfile( $catfile, $keyfile, $privateKey ) { - - // Decrypt the keyfile with the user's private key - $decryptedKeyfile = self::keyDecrypt( $keyfile, $privateKey ); - - // Decrypt the catfile symmetrically using the decrypted keyfile - $decryptedData = self::symmetricDecryptFileContent( $catfile, $decryptedKeyfile ); - - return $decryptedData; - - } - - /** - * @brief Symmetrically encrypt a file by combining encrypted component data blocks - */ - public static function symmetricBlockEncryptFileContent( $plainContent, $key ) { - - $crypted = ''; - - $remaining = $plainContent; - - $testarray = array(); - - while( strlen( $remaining ) ) { - - //echo "\n\n\$block = ".substr( $remaining, 0, 6126 ); - - // Encrypt a chunk of unencrypted data and add it to the rest - $block = self::symmetricEncryptFileContent( substr( $remaining, 0, 6126 ), $key ); - - $padded = self::addPadding( $block ); - - $crypted .= $block; - - $testarray[] = $block; - - // Remove the data already encrypted from remaining unencrypted data - $remaining = substr( $remaining, 6126 ); + $result = @openssl_private_decrypt($encryptedContent, $plainContent, $privatekey); + if ($result) { + return $plainContent; } - return $crypted; - - } - - - /** - * @brief Symmetrically decrypt a file by combining encrypted component data blocks - */ - public static function symmetricBlockDecryptFileContent( $crypted, $key ) { - - $decrypted = ''; - - $remaining = $crypted; - - $testarray = array(); - - while( strlen( $remaining ) ) { - - $testarray[] = substr( $remaining, 0, 8192 ); - - // Decrypt a chunk of unencrypted data and add it to the rest - $decrypted .= self::symmetricDecryptFileContent( $remaining, $key ); - - // Remove the data already encrypted from remaining unencrypted data - $remaining = substr( $remaining, 8192 ); - - } - - return $decrypted; + return $result; } @@ -551,24 +526,24 @@ class Crypt { */ public static function generateIv() { - if ( $random = openssl_random_pseudo_bytes( 12, $strong ) ) { + if ($random = openssl_random_pseudo_bytes(12, $strong)) { - if ( !$strong ) { + if (!$strong) { // If OpenSSL indicates randomness is insecure, log error - \OC_Log::write( 'Encryption library', 'Insecure symmetric key was generated using openssl_random_pseudo_bytes()', \OC_Log::WARN ); + \OCP\Util::writeLog('Encryption library', 'Insecure symmetric key was generated using openssl_random_pseudo_bytes()', \OCP\Util::WARN); } // We encode the iv purely for string manipulation // purposes - it gets decoded before use - $iv = base64_encode( $random ); + $iv = base64_encode($random); return $iv; } else { - throw new \Exception( 'Generating IV failed' ); + throw new \Exception('Generating IV failed'); } @@ -581,12 +556,12 @@ class Crypt { public static function generateKey() { // Generate key - if ( $key = base64_encode( openssl_random_pseudo_bytes( 183, $strong ) ) ) { + if ($key = base64_encode(openssl_random_pseudo_bytes(183, $strong))) { - if ( !$strong ) { + if (!$strong) { // If OpenSSL indicates randomness is insecure, log error - throw new \Exception ( 'Encryption library, Insecure symmetric key was generated using openssl_random_pseudo_bytes()' ); + throw new \Exception('Encryption library, Insecure symmetric key was generated using openssl_random_pseudo_bytes()'); } @@ -603,15 +578,15 @@ class Crypt { /** * @brief Get the blowfish encryption handeler for a key * @param $key string (optional) - * @return Crypt_Blowfish blowfish object + * @return \Crypt_Blowfish blowfish object * * if the key is left out, the default handeler will be used */ - public static function getBlowfish( $key = '' ) { + public static function getBlowfish($key = '') { - if ( $key ) { + if ($key) { - return new \Crypt_Blowfish( $key ); + return new \Crypt_Blowfish($key); } else { @@ -621,13 +596,17 @@ class Crypt { } - public static function legacyCreateKey( $passphrase ) { + /** + * @param $passphrase + * @return mixed + */ + public static function legacyCreateKey($passphrase) { // Generate a random integer - $key = mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ); + $key = mt_rand(10000, 99999) . mt_rand(10000, 99999) . mt_rand(10000, 99999) . mt_rand(10000, 99999); // Encrypt the key with the passphrase - $legacyEncKey = self::legacyEncrypt( $key, $passphrase ); + $legacyEncKey = self::legacyEncrypt($key, $passphrase); return $legacyEncKey; @@ -635,61 +614,55 @@ class Crypt { /** * @brief encrypts content using legacy blowfish system - * @param $content the cleartext message you want to encrypt - * @param $key the encryption key (optional) - * @returns encrypted content + * @param string $content the cleartext message you want to encrypt + * @param string $passphrase + * @returns string encrypted content * * This function encrypts an content */ - public static function legacyEncrypt( $content, $passphrase = '' ) { + public static function legacyEncrypt($content, $passphrase = '') { - $bf = self::getBlowfish( $passphrase ); + $bf = self::getBlowfish($passphrase); - return $bf->encrypt( $content ); + return $bf->encrypt($content); } /** * @brief decrypts content using legacy blowfish system - * @param $content the cleartext message you want to decrypt - * @param $key the encryption key (optional) - * @returns cleartext content + * @param string $content the cleartext message you want to decrypt + * @param string $passphrase + * @return string cleartext content * * This function decrypts an content */ - public static function legacyDecrypt( $content, $passphrase = '' ) { + public static function legacyDecrypt($content, $passphrase = '') { - $bf = self::getBlowfish( $passphrase ); + $bf = self::getBlowfish($passphrase); - $decrypted = $bf->decrypt( $content ); - - $trimmed = rtrim( $decrypted, "\0" ); - - return $trimmed; - - } - - public static function legacyKeyRecryptKeyfile( $legacyEncryptedContent, $legacyPassphrase, $publicKey, $newPassphrase ) { - - $decrypted = self::legacyDecrypt( $legacyEncryptedContent, $legacyPassphrase ); - - $recrypted = self::keyEncryptKeyfile( $decrypted, $publicKey ); - - return $recrypted; + $decrypted = $bf->decrypt($content); + return $decrypted; } /** - * @brief Re-encryptes a legacy blowfish encrypted file using AES with integrated IV - * @param $legacyContent the legacy encrypted content to re-encrypt - * @returns cleartext content - * - * This function decrypts an content + * @param $data + * @param string $key + * @param int $maxLength + * @return string */ - public static function legacyRecrypt( $legacyContent, $legacyPassphrase, $newPassphrase ) { - - // TODO: write me + public static function legacyBlockDecrypt($data, $key = '', $maxLength = 0) { + $result = ''; + while (strlen($data)) { + $result .= self::legacyDecrypt(substr($data, 0, 8192), $key); + $data = substr($data, 8192); + } + if ($maxLength > 0) { + return substr($result, 0, $maxLength); + } else { + return rtrim($result, "\0"); + } } -} +} \ No newline at end of file diff --git a/apps/files_encryption/lib/helper.php b/apps/files_encryption/lib/helper.php new file mode 100755 index 0000000000..a22c139c50 --- /dev/null +++ b/apps/files_encryption/lib/helper.php @@ -0,0 +1,211 @@ + + * + * 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 . + * + */ + +namespace OCA\Encryption; + +/** + * @brief Class to manage registration of hooks an various helper methods + * @package OCA\Encryption + */ +class Helper { + + /** + * @brief register share related hooks + * + */ + public static function registerShareHooks() { + + \OCP\Util::connectHook('OCP\Share', 'pre_shared', 'OCA\Encryption\Hooks', 'preShared'); + \OCP\Util::connectHook('OCP\Share', 'post_shared', 'OCA\Encryption\Hooks', 'postShared'); + \OCP\Util::connectHook('OCP\Share', 'post_unshare', 'OCA\Encryption\Hooks', 'postUnshare'); + } + + /** + * @brief register user related hooks + * + */ + public static function registerUserHooks() { + + \OCP\Util::connectHook('OC_User', 'post_login', 'OCA\Encryption\Hooks', 'login'); + \OCP\Util::connectHook('OC_User', 'post_setPassword', 'OCA\Encryption\Hooks', 'setPassphrase'); + \OCP\Util::connectHook('OC_User', 'pre_setPassword', 'OCA\Encryption\Hooks', 'preSetPassphrase'); + \OCP\Util::connectHook('OC_User', 'post_createUser', 'OCA\Encryption\Hooks', 'postCreateUser'); + \OCP\Util::connectHook('OC_User', 'post_deleteUser', 'OCA\Encryption\Hooks', 'postDeleteUser'); + } + + /** + * @brief register filesystem related hooks + * + */ + public static function registerFilesystemHooks() { + + \OCP\Util::connectHook('OC_Filesystem', 'post_rename', 'OCA\Encryption\Hooks', 'postRename'); + } + + /** + * @brief setup user for files_encryption + * + * @param Util $util + * @param string $password + * @return bool + */ + public static function setupUser($util, $password) { + // Check files_encryption infrastructure is ready for action + if (!$util->ready()) { + + \OCP\Util::writeLog('Encryption library', 'User account "' . $util->getUserId() + . '" is not ready for encryption; configuration started', \OCP\Util::DEBUG); + + if (!$util->setupServerSide($password)) { + return false; + } + } + + return true; + } + + /** + * @brief enable recovery + * + * @param $recoveryKeyId + * @param $recoveryPassword + * @internal param \OCA\Encryption\Util $util + * @internal param string $password + * @return bool + */ + public static function adminEnableRecovery($recoveryKeyId, $recoveryPassword) { + + $view = new \OC\Files\View('/'); + + if ($recoveryKeyId === null) { + $recoveryKeyId = 'recovery_' . substr(md5(time()), 0, 8); + \OC_Appconfig::setValue('files_encryption', 'recoveryKeyId', $recoveryKeyId); + } + + if (!$view->is_dir('/owncloud_private_key')) { + $view->mkdir('/owncloud_private_key'); + } + + if ( + (!$view->file_exists("/public-keys/" . $recoveryKeyId . ".public.key") + || !$view->file_exists("/owncloud_private_key/" . $recoveryKeyId . ".private.key")) + ) { + + $keypair = \OCA\Encryption\Crypt::createKeypair(); + + \OC_FileProxy::$enabled = false; + + // Save public key + + if (!$view->is_dir('/public-keys')) { + $view->mkdir('/public-keys'); + } + + $view->file_put_contents('/public-keys/' . $recoveryKeyId . '.public.key', $keypair['publicKey']); + + // Encrypt private key empthy passphrase + $encryptedPrivateKey = \OCA\Encryption\Crypt::symmetricEncryptFileContent($keypair['privateKey'], $recoveryPassword); + + // Save private key + $view->file_put_contents('/owncloud_private_key/' . $recoveryKeyId . '.private.key', $encryptedPrivateKey); + + \OC_FileProxy::$enabled = true; + + // Set recoveryAdmin as enabled + \OC_Appconfig::setValue('files_encryption', 'recoveryAdminEnabled', 1); + + $return = true; + + } else { // get recovery key and check the password + $util = new \OCA\Encryption\Util(new \OC_FilesystemView('/'), \OCP\User::getUser()); + $return = $util->checkRecoveryPassword($recoveryPassword); + if ($return) { + \OC_Appconfig::setValue('files_encryption', 'recoveryAdminEnabled', 1); + } + } + + return $return; + } + + + /** + * @brief disable recovery + * + * @param $recoveryPassword + * @return bool + */ + public static function adminDisableRecovery($recoveryPassword) { + $util = new Util(new \OC_FilesystemView('/'), \OCP\User::getUser()); + $return = $util->checkRecoveryPassword($recoveryPassword); + + if ($return) { + // Set recoveryAdmin as disabled + \OC_Appconfig::setValue('files_encryption', 'recoveryAdminEnabled', 0); + } + + return $return; + } + + + /** + * @brief checks if access is public/anonymous user + * @return bool + */ + public static function isPublicAccess() { + if (\OCP\USER::getUser() === false + || (isset($_GET['service']) && $_GET['service'] == 'files' + && isset($_GET['t'])) + ) { + return true; + } else { + return false; + } + } + + /** + * @brief Format a path to be relative to the /user/files/ directory + * @param string $path the absolute path + * @return string e.g. turns '/admin/files/test.txt' into 'test.txt' + */ + public static function stripUserFilesPath($path) { + $trimmed = ltrim($path, '/'); + $split = explode('/', $trimmed); + $sliced = array_slice($split, 2); + $relPath = implode('/', $sliced); + + return $relPath; + } + + /** + * @brief redirect to a error page + */ + public static function redirectToErrorPage() { + $location = \OC_Helper::linkToAbsolute('apps/files_encryption/files', 'error.php'); + $post = 0; + if(count($_POST) > 0) { + $post = 1; + } + header('Location: ' . $location . '?p=' . $post); + exit(); + } +} \ No newline at end of file diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 9558779715..e911c1785d 100755 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -28,19 +28,26 @@ namespace OCA\Encryption; * @note Where a method requires a view object, it's root must be '/' */ class Keymanager { - + /** * @brief retrieve the ENCRYPTED private key from a user - * - * @return string private key or false + * + * @param \OC_FilesystemView $view + * @param string $user + * @return string private key or false (hopefully) * @note the key returned by this method must be decrypted before use */ - public static function getPrivateKey( \OC_FilesystemView $view, $user ) { - - $path = '/' . $user . '/' . 'files_encryption' . '/' . $user.'.private.key'; - - $key = $view->file_get_contents( $path ); - + public static function getPrivateKey(\OC_FilesystemView $view, $user) { + + $path = '/' . $user . '/' . 'files_encryption' . '/' . $user . '.private.key'; + + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + $key = $view->file_get_contents($path); + + \OC_FileProxy::$enabled = $proxyStatus; + return $key; } @@ -50,102 +57,152 @@ class Keymanager { * @param $userId * @return string public key or false */ - public static function getPublicKey( \OC_FilesystemView $view, $userId ) { - - return $view->file_get_contents( '/public-keys/' . '/' . $userId . '.public.key' ); - + public static function getPublicKey(\OC_FilesystemView $view, $userId) { + + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + $result = $view->file_get_contents('/public-keys/' . $userId . '.public.key'); + + \OC_FileProxy::$enabled = $proxyStatus; + + return $result; + } - + /** - * @brief retrieve both keys from a user (private and public) + * @brief Retrieve a user's public and private key * @param \OC_FilesystemView $view * @param $userId * @return array keys: privateKey, publicKey */ - public static function getUserKeys( \OC_FilesystemView $view, $userId ) { - - return array( - 'publicKey' => self::getPublicKey( $view, $userId ) - , 'privateKey' => self::getPrivateKey( $view, $userId ) - ); - - } - - /** - * @brief Retrieve public keys of all users with access to a file - * @param string $path Path to file - * @return array of public keys for the given file - * @note Checks that the sharing app is enabled should be performed - * by client code, that isn't checked here - */ - public static function getPublicKeys( \OC_FilesystemView $view, $userId, $filePath ) { - - $path = ltrim( $path, '/' ); - - $filepath = '/' . $userId . '/files/' . $filePath; - - // Check if sharing is enabled - if ( OC_App::isEnabled( 'files_sharing' ) ) { - + public static function getUserKeys(\OC_FilesystemView $view, $userId) { + + return array( + 'publicKey' => self::getPublicKey($view, $userId), + 'privateKey' => self::getPrivateKey($view, $userId) + ); - - } else { - - // check if it is a file owned by the user and not shared at all - $userview = new \OC_FilesystemView( '/'.$userId.'/files/' ); - - if ( $userview->file_exists( $path ) ) { - - $users[] = $userId; - - } - - } - - $view = new \OC_FilesystemView( '/public-keys/' ); - - $keylist = array(); - - $count = 0; - - foreach ( $users as $user ) { - - $keylist['key'.++$count] = $view->file_get_contents( $user.'.public.key' ); - - } - - return $keylist; - } - + + /** + * @brief Retrieve public keys for given users + * @param \OC_FilesystemView $view + * @param array $userIds + * @return array of public keys for the specified users + */ + public static function getPublicKeys(\OC_FilesystemView $view, array $userIds) { + + $keys = array(); + + foreach ($userIds as $userId) { + + $keys[$userId] = self::getPublicKey($view, $userId); + + } + + return $keys; + + } + /** * @brief store file encryption key * + * @param \OC_FilesystemView $view * @param string $path relative path of the file, including filename - * @param string $key + * @param $userId + * @param $catfile + * @internal param string $key * @return bool true/false - * @note The keyfile is not encrypted here. Client code must + * @note The keyfile is not encrypted here. Client code must * asymmetrically encrypt the keyfile before passing it to this method */ - public static function setFileKey( \OC_FilesystemView $view, $path, $userId, $catfile ) { - - $basePath = '/' . $userId . '/files_encryption/keyfiles'; - - $targetPath = self::keySetPreparation( $view, $path, $basePath, $userId ); - - if ( $view->is_dir( $basePath . '/' . $targetPath ) ) { - - - + public static function setFileKey(\OC_FilesystemView $view, $path, $userId, $catfile) { + + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + //here we need the currently logged in user, while userId can be a different user + $util = new Util($view, \OCP\User::getUser()); + list($owner, $filename) = $util->getUidAndFilename($path); + + $basePath = '/' . $owner . '/files_encryption/keyfiles'; + + $targetPath = self::keySetPreparation($view, $filename, $basePath, $owner); + + if (!$view->is_dir($basePath . '/' . $targetPath)) { + + // create all parent folders + $info = pathinfo($basePath . '/' . $targetPath); + $keyfileFolderName = $view->getLocalFolder($info['dirname']); + + if (!file_exists($keyfileFolderName)) { + + mkdir($keyfileFolderName, 0750, true); + + } + } + + // try reusing key file if part file + if (self::isPartialFilePath($targetPath)) { + + $result = $view->file_put_contents( + $basePath . '/' . self::fixPartialFilePath($targetPath) . '.key', $catfile); + } else { - // Save the keyfile in parallel directory - return $view->file_put_contents( $basePath . '/' . $targetPath . '.key', $catfile ); - + $result = $view->file_put_contents($basePath . '/' . $targetPath . '.key', $catfile); + } - + + \OC_FileProxy::$enabled = $proxyStatus; + + return $result; + } - + + /** + * @brief Remove .path extension from a file path + * @param string $path Path that may identify a .part file + * @return string File path without .part extension + * @note this is needed for reusing keys + */ + public static function fixPartialFilePath($path) { + + if (preg_match('/\.part$/', $path) || preg_match('/\.etmp$/', $path)) { + + $newLength = strlen($path) - 5; + $fPath = substr($path, 0, $newLength); + + return $fPath; + + } else { + + return $path; + + } + + } + + /** + * @brief Check if a path is a .part file + * @param string $path Path that may identify a .part file + * @return bool + */ + public static function isPartialFilePath($path) { + + if (preg_match('/\.part$/', $path) || preg_match('/\.etmp$/', $path)) { + + return true; + + } else { + + return false; + + } + + } + /** * @brief retrieve keyfile for an encrypted file * @param \OC_FilesystemView $view @@ -156,168 +213,359 @@ class Keymanager { * @note The keyfile returned is asymmetrically encrypted. Decryption * of the keyfile must be performed by client code */ - public static function getFileKey( \OC_FilesystemView $view, $userId, $filePath ) { - - $filePath_f = ltrim( $filePath, '/' ); - - $catfilePath = '/' . $userId . '/files_encryption/keyfiles/' . $filePath_f . '.key'; - - if ( $view->file_exists( $catfilePath ) ) { + public static function getFileKey(\OC_FilesystemView $view, $userId, $filePath) { + + // try reusing key file if part file + if (self::isPartialFilePath($filePath)) { + + $result = self::getFileKey($view, $userId, self::fixPartialFilePath($filePath)); + + if ($result) { + + return $result; + + } - return $view->file_get_contents( $catfilePath ); - - } else { - - return false; - } - + + $util = new Util($view, \OCP\User::getUser()); + + list($owner, $filename) = $util->getUidAndFilename($filePath); + $filePath_f = ltrim($filename, '/'); + + $keyfilePath = '/' . $owner . '/files_encryption/keyfiles/' . $filePath_f . '.key'; + + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + if ($view->file_exists($keyfilePath)) { + + $result = $view->file_get_contents($keyfilePath); + + } else { + + $result = false; + + } + + \OC_FileProxy::$enabled = $proxyStatus; + + return $result; + } - + /** * @brief Delete a keyfile * - * @param OC_FilesystemView $view + * @param \OC_FilesystemView $view * @param string $userId username * @param string $path path of the file the key belongs to * @return bool Outcome of unlink operation * @note $path must be relative to data/user/files. e.g. mydoc.txt NOT * /data/admin/files/mydoc.txt */ - public static function deleteFileKey( \OC_FilesystemView $view, $userId, $path ) { - - $trimmed = ltrim( $path, '/' ); - $keyPath = '/' . $userId . '/files_encryption/keyfiles/' . $trimmed . '.key'; - - // Unlink doesn't tell us if file was deleted (not found returns - // true), so we perform our own test - if ( $view->file_exists( $keyPath ) ) { - - return $view->unlink( $keyPath ); - + public static function deleteFileKey(\OC_FilesystemView $view, $userId, $path) { + + $trimmed = ltrim($path, '/'); + $keyPath = '/' . $userId . '/files_encryption/keyfiles/' . $trimmed; + + $result = false; + + if ($view->is_dir($keyPath)) { + + $result = $view->unlink($keyPath); + } else { - - \OC_Log::write( 'Encryption library', 'Could not delete keyfile; does not exist: "' . $keyPath, \OC_Log::ERROR ); - - return false; - + if ($view->file_exists($keyPath . '.key')) { + + $result = $view->unlink($keyPath . '.key'); + + } } - + + if (!$result) { + + \OCP\Util::writeLog('Encryption library', + 'Could not delete keyfile; does not exist: "' . $keyPath, \OCP\Util::ERROR); + + } + + return $result; + } - + /** * @brief store private key from the user - * @param string key + * @param string $key * @return bool * @note Encryption of the private key must be performed by client code * as no encryption takes place here */ - public static function setPrivateKey( $key ) { - + public static function setPrivateKey($key) { + $user = \OCP\User::getUser(); - - $view = new \OC_FilesystemView( '/' . $user . '/files_encryption' ); - + + $view = new \OC_FilesystemView('/' . $user . '/files_encryption'); + + $proxyStatus = \OC_FileProxy::$enabled; \OC_FileProxy::$enabled = false; - - if ( !$view->file_exists( '' ) ) - $view->mkdir( '' ); - - return $view->file_put_contents( $user . '.private.key', $key ); + + if (!$view->file_exists('')) + $view->mkdir(''); + + $result = $view->file_put_contents($user . '.private.key', $key); + + \OC_FileProxy::$enabled = $proxyStatus; + + return $result; } - - /** - * @brief store private keys from the user - * - * @param string privatekey - * @param string publickey - * @return bool true/false - */ - public static function setUserKeys($privatekey, $publickey) { - - return ( self::setPrivateKey( $privatekey ) && self::setPublicKey( $publickey ) ); - - } - - /** - * @brief store public key of the user - * - * @param string key - * @return bool true/false - */ - public static function setPublicKey( $key ) { - - $view = new \OC_FilesystemView( '/public-keys' ); - - \OC_FileProxy::$enabled = false; - - if ( !$view->file_exists( '' ) ) - $view->mkdir( '' ); - - return $view->file_put_contents( \OCP\User::getUser() . '.public.key', $key ); - - } - /** - * @brief store file encryption key + * @brief store share key * + * @param \OC_FilesystemView $view * @param string $path relative path of the file, including filename - * @param string $key - * @param null $view - * @param string $dbClassName + * @param $userId + * @param $shareKey + * @internal param string $key + * @internal param string $dbClassName * @return bool true/false * @note The keyfile is not encrypted here. Client code must * asymmetrically encrypt the keyfile before passing it to this method */ - public static function setShareKey( \OC_FilesystemView $view, $path, $userId, $shareKey ) { - - $basePath = '/' . $userId . '/files_encryption/share-keys'; - - $shareKeyPath = self::keySetPreparation( $view, $path, $basePath, $userId ); - - return $view->file_put_contents( $basePath . '/' . $shareKeyPath . '.shareKey', $shareKey ); - - } - - /** - * @brief Make preparations to vars and filesystem for saving a keyfile - */ - public static function keySetPreparation( \OC_FilesystemView $view, $path, $basePath, $userId ) { - - $targetPath = ltrim( $path, '/' ); - - $path_parts = pathinfo( $targetPath ); - - // If the file resides within a subdirectory, create it - if ( - isset( $path_parts['dirname'] ) - && ! $view->file_exists( $basePath . '/' . $path_parts['dirname'] ) - ) { - - $view->mkdir( $basePath . '/' . $path_parts['dirname'] ); - + public static function setShareKey(\OC_FilesystemView $view, $path, $userId, $shareKey) { + + // Here we need the currently logged in user, while userId can be a different user + $util = new Util($view, \OCP\User::getUser()); + + list($owner, $filename) = $util->getUidAndFilename($path); + + $basePath = '/' . $owner . '/files_encryption/share-keys'; + + $shareKeyPath = self::keySetPreparation($view, $filename, $basePath, $owner); + + // try reusing key file if part file + if (self::isPartialFilePath($shareKeyPath)) { + + $writePath = $basePath . '/' . self::fixPartialFilePath($shareKeyPath) . '.' . $userId . '.shareKey'; + + } else { + + $writePath = $basePath . '/' . $shareKeyPath . '.' . $userId . '.shareKey'; + } - - return $targetPath; - + + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + $result = $view->file_put_contents($writePath, $shareKey); + + \OC_FileProxy::$enabled = $proxyStatus; + + if ( + is_int($result) + && $result > 0 + ) { + + return true; + + } else { + + return false; + + } + } /** - * @brief Fetch the legacy encryption key from user files - * @param string $login used to locate the legacy key - * @param string $passphrase used to decrypt the legacy key - * @return true / false - * - * if the key is left out, the default handler will be used + * @brief store multiple share keys for a single file + * @param \OC_FilesystemView $view + * @param $path + * @param array $shareKeys + * @return bool */ - public function getLegacyKey() { - - $user = \OCP\User::getUser(); - $view = new \OC_FilesystemView( '/' . $user ); - return $view->file_get_contents( 'encryption.key' ); - + public static function setShareKeys(\OC_FilesystemView $view, $path, array $shareKeys) { + + // $shareKeys must be an array with the following format: + // [userId] => [encrypted key] + + $result = true; + + foreach ($shareKeys as $userId => $shareKey) { + + if (!self::setShareKey($view, $path, $userId, $shareKey)) { + + // If any of the keys are not set, flag false + $result = false; + + } + + } + + // Returns false if any of the keys weren't set + return $result; + + } + + /** + * @brief retrieve shareKey for an encrypted file + * @param \OC_FilesystemView $view + * @param string $userId + * @param string $filePath + * @internal param \OCA\Encryption\file $string name + * @return string file key or false + * @note The sharekey returned is encrypted. Decryption + * of the keyfile must be performed by client code + */ + public static function getShareKey(\OC_FilesystemView $view, $userId, $filePath) { + + // try reusing key file if part file + if (self::isPartialFilePath($filePath)) { + + $result = self::getShareKey($view, $userId, self::fixPartialFilePath($filePath)); + + if ($result) { + + return $result; + + } + + } + + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + //here we need the currently logged in user, while userId can be a different user + $util = new Util($view, \OCP\User::getUser()); + + list($owner, $filename) = $util->getUidAndFilename($filePath); + $shareKeyPath = \OC\Files\Filesystem::normalizePath( + '/' . $owner . '/files_encryption/share-keys/' . $filename . '.' . $userId . '.shareKey'); + + if ($view->file_exists($shareKeyPath)) { + + $result = $view->file_get_contents($shareKeyPath); + + } else { + + $result = false; + + } + + \OC_FileProxy::$enabled = $proxyStatus; + + return $result; + + } + + /** + * @brief delete all share keys of a given file + * @param \OC_FilesystemView $view + * @param string $userId owner of the file + * @param string $filePath path to the file, relative to the owners file dir + */ + public static function delAllShareKeys(\OC_FilesystemView $view, $userId, $filePath) { + + if ($view->is_dir($userId . '/files/' . $filePath)) { + $view->unlink($userId . '/files_encryption/share-keys/' . $filePath); + } else { + $localKeyPath = $view->getLocalFile($userId . '/files_encryption/share-keys/' . $filePath); + $matches = glob(preg_quote($localKeyPath) . '*.shareKey'); + foreach ($matches as $ma) { + $result = unlink($ma); + if (!$result) { + \OCP\Util::writeLog('Encryption library', + 'Keyfile or shareKey could not be deleted for file "' . $filePath . '"', \OCP\Util::ERROR); + } + } + } + } + + /** + * @brief Delete a single user's shareKey for a single file + */ + public static function delShareKey(\OC_FilesystemView $view, $userIds, $filePath) { + + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + //here we need the currently logged in user, while userId can be a different user + $util = new Util($view, \OCP\User::getUser()); + + list($owner, $filename) = $util->getUidAndFilename($filePath); + + $shareKeyPath = \OC\Files\Filesystem::normalizePath('/' . $owner . '/files_encryption/share-keys/' . $filename); + + if ($view->is_dir($shareKeyPath)) { + + $localPath = \OC\Files\Filesystem::normalizePath($view->getLocalFolder($shareKeyPath)); + self::recursiveDelShareKeys($localPath, $userIds); + + } else { + + foreach ($userIds as $userId) { + + if (!$view->unlink($shareKeyPath . '.' . $userId . '.shareKey')) { + \OCP\Util::writeLog('Encryption library', + 'Could not delete shareKey; does not exist: "' . $shareKeyPath . '.' . $userId + . '.shareKey"', \OCP\Util::ERROR); + } + + } + } + + \OC_FileProxy::$enabled = $proxyStatus; + } + + /** + * @brief recursively delete share keys from given users + * + * @param string $dir directory + * @param array $userIds user ids for which the share keys should be deleted + */ + private static function recursiveDelShareKeys($dir, $userIds) { + foreach ($userIds as $userId) { + $matches = glob(preg_quote($dir) . '/*' . preg_quote('.' . $userId . '.shareKey')); + } + /** @var $matches array */ + foreach ($matches as $ma) { + if (!unlink($ma)) { + \OCP\Util::writeLog('Encryption library', + 'Could not delete shareKey; does not exist: "' . $ma . '"', \OCP\Util::ERROR); + } + } + $subdirs = $directories = glob(preg_quote($dir) . '/*', GLOB_ONLYDIR); + foreach ($subdirs as $subdir) { + self::recursiveDelShareKeys($subdir, $userIds); + } + } + + /** + * @brief Make preparations to vars and filesystem for saving a keyfile + */ + public static function keySetPreparation(\OC_FilesystemView $view, $path, $basePath, $userId) { + + $targetPath = ltrim($path, '/'); + + $path_parts = pathinfo($targetPath); + + // If the file resides within a subdirectory, create it + if ( + isset($path_parts['dirname']) + && !$view->file_exists($basePath . '/' . $path_parts['dirname']) + ) { + $sub_dirs = explode(DIRECTORY_SEPARATOR, $basePath . '/' . $path_parts['dirname']); + $dir = ''; + foreach ($sub_dirs as $sub_dir) { + $dir .= '/' . $sub_dir; + if (!$view->is_dir($dir)) { + $view->mkdir($dir); + } + } + } + + 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 55cddf2bec..735eba911a 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -1,41 +1,45 @@ . -* -*/ + * ownCloud + * + * @author Sam Tuke, Robin Appelman + * @copyright 2012 Sam Tuke samtuke@owncloud.com, Robin Appelman + * icewind1991@gmail.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 . + * + */ /** -* @brief Encryption proxy which handles filesystem operations before and after -* execution and encrypts, and handles keyfiles accordingly. Used for -* webui. -*/ + * @brief Encryption proxy which handles filesystem operations before and after + * execution and encrypts, and handles keyfiles accordingly. Used for + * webui. + */ namespace OCA\Encryption; +/** + * Class Proxy + * @package OCA\Encryption + */ 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 @@ -43,347 +47,385 @@ class Proxy extends \OC_FileProxy { * * Tests if server side encryption is enabled, and file is allowed by blacklists */ - private static function shouldEncrypt( $path ) { - - if ( is_null( self::$enableEncryption ) ) { - - if ( - \OCP\Config::getAppValue( 'files_encryption', 'enable_encryption', 'true' ) == 'true' - && Crypt::mode() == 'server' + 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 (!self::$enableEncryption) { + return false; - + } - - if ( is_null(self::$blackList ) ) { - - self::$blackList = explode(',', \OCP\Config::getAppValue( 'files_encryption', 'type_blacklist', 'jpg,png,jpeg,avi,mpg,mpeg,mkv,mp3,oga,ogv,ogg' ) ); - + + if (is_null(self::$blackList)) { + + self::$blackList = explode(',', \OCP\Config::getAppValue('files_encryption', 'type_blacklist', '')); + } - - if ( Crypt::isCatfile( $path ) ) { - + + if (Crypt::isCatfileContent($path)) { + return true; - + } - - $extension = substr( $path, strrpos( $path, '.' ) +1 ); - - if ( array_search( $extension, self::$blackList ) === false ) { - + + $extension = substr($path, strrpos($path, '.') + 1); + + if (array_search($extension, self::$blackList) === false) { + return true; - + } - + return false; } - - public function preFile_put_contents( $path, &$data ) { - - if ( self::shouldEncrypt( $path ) ) { - - if ( !is_resource( $data ) ) { //stream put contents should have been converted to fopen - - $userId = \OCP\USER::getUser(); - - $rootView = new \OC_FilesystemView( '/' ); - - // Set the filesize for userland, before encrypting - $size = strlen( $data ); - - // Disable encryption proxy to prevent recursive calls - \OC_FileProxy::$enabled = false; - - // TODO: Check if file is shared, if so, use multiKeyEncrypt - - // Encrypt plain data and fetch key - $encrypted = Crypt::keyEncryptKeyfile( $data, Keymanager::getPublicKey( $rootView, $userId ) ); - - // Replace plain content with encrypted content by reference - $data = $encrypted['data']; - - $filePath = explode( '/', $path ); - - $filePath = array_slice( $filePath, 3 ); - - $filePath = '/' . implode( '/', $filePath ); - - // TODO: make keyfile dir dynamic from app config - - $view = new \OC_FilesystemView( '/' ); - - // Save keyfile for newly encrypted file in parallel directory tree - Keymanager::setFileKey( $view, $filePath, $userId, $encrypted['key'] ); - - // Update the file cache with file info - \OC\Files\Filesystem::putFileInfo( $path, array( 'encrypted'=>true, 'size' => $size ), '' ); - - // Re-enable proxy - our work is done - \OC_FileProxy::$enabled = true; - + + /** + * @param $path + * @param $data + * @return bool + */ + public function preFile_put_contents($path, &$data) { + + if (self::shouldEncrypt($path)) { + + if (!is_resource($data)) { + + // get root view + $view = new \OC_FilesystemView('/'); + + // get relative path + $relativePath = \OCA\Encryption\Helper::stripUserFilesPath($path); + + if (!isset($relativePath)) { + return true; + } + + $handle = fopen('crypt://' . $relativePath . '.etmp', 'w'); + if (is_resource($handle)) { + + // write data to stream + fwrite($handle, $data); + + // close stream + fclose($handle); + + // disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + // get encrypted content + $data = $view->file_get_contents($path . '.etmp'); + + // remove our temp file + $view->unlink($path . '.etmp'); + + // re-enable proxy - our work is done + \OC_FileProxy::$enabled = $proxyStatus; + } } } - + + return true; + } - + /** * @param string $path Path of file from which has been read * @param string $data Data that has been read from file */ - public function postFile_get_contents( $path, $data ) { - - // TODO: Use dependency injection to add required args for view and user etc. to this method + public function postFile_get_contents($path, $data) { + + $plainData = null; + $view = new \OC_FilesystemView('/'); + + // get relative path + $relativePath = \OCA\Encryption\Helper::stripUserFilesPath($path); + + // init session + $session = new \OCA\Encryption\Session($view); - // Disable encryption proxy to prevent recursive calls - \OC_FileProxy::$enabled = false; - // If data is a catfile - if ( - Crypt::mode() == 'server' - && Crypt::isCatfile( $data ) + if ( + Crypt::mode() === 'server' + && Crypt::isCatfileContent($data) ) { - - $split = explode( '/', $path ); - - $filePath = array_slice( $split, 3 ); - - $filePath = '/' . implode( '/', $filePath ); - - //$cached = \OC\Files\Filesystem::getFileInfo( $path, '' ); - - $view = new \OC_FilesystemView( '' ); - - $userId = \OCP\USER::getUser(); - - // TODO: Check if file is shared, if so, use multiKeyDecrypt - - $encryptedKeyfile = Keymanager::getFileKey( $view, $userId, $filePath ); - $session = new Session(); - - $decrypted = Crypt::keyDecryptKeyfile( $data, $encryptedKeyfile, $session->getPrivateKey( $split[1] ) ); - + $handle = fopen('crypt://' . $relativePath, 'r'); + + if (is_resource($handle)) { + while (($plainDataChunk = fgets($handle, 8192)) !== false) { + $plainData .= $plainDataChunk; + } + } + } elseif ( - Crypt::mode() == 'server' - && isset( $_SESSION['legacyenckey'] ) - && Crypt::isEncryptedMeta( $path ) + Crypt::mode() == 'server' + && \OC::$session->exists('legacyenckey') + && Crypt::isEncryptedMeta($path) ) { - - $decrypted = Crypt::legacyDecrypt( $data, $_SESSION['legacyenckey'] ); - + // Disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + $plainData = Crypt::legacyBlockDecrypt($data, $session->getLegacyKey()); + + \OC_FileProxy::$enabled = $proxyStatus; } - - \OC_FileProxy::$enabled = true; - - if ( ! isset( $decrypted ) ) { - - $decrypted = $data; - + + if (!isset($plainData)) { + + $plainData = $data; + } - - return $decrypted; - + + return $plainData; + } - + /** * @brief When a file is deleted, remove its keyfile also */ - public function preUnlink( $path ) { - - // Disable encryption proxy to prevent recursive calls - \OC_FileProxy::$enabled = false; - - $view = new \OC_FilesystemView( '/' ); - - $userId = \OCP\USER::getUser(); - - // Format path to be relative to user files dir - $trimmed = ltrim( $path, '/' ); - $split = explode( '/', $trimmed ); - $sliced = array_slice( $split, 2 ); - $relPath = implode( '/', $sliced ); - - if ( $view->is_dir( $path ) ) { - - // Dirs must be handled separately as deleteFileKey - // doesn't handle them - $view->unlink( $userId . '/' . 'files_encryption' . '/' . 'keyfiles' . '/'. $relPath ); - - } else { - - // Delete keyfile so it isn't orphaned - $result = Keymanager::deleteFileKey( $view, $userId, $relPath ); - - \OC_FileProxy::$enabled = true; - - return $result; - + public function preUnlink($path) { + + // let the trashbin handle this + if (\OCP\App::isEnabled('files_trashbin')) { + return true; } - + + // Disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + $view = new \OC_FilesystemView('/'); + + $userId = \OCP\USER::getUser(); + + $util = new Util($view, $userId); + + // get relative path + $relativePath = \OCA\Encryption\Helper::stripUserFilesPath($path); + + list($owner, $ownerPath) = $util->getUidAndFilename($relativePath); + + // Delete keyfile & shareKey so it isn't orphaned + if (!Keymanager::deleteFileKey($view, $owner, $ownerPath)) { + \OCP\Util::writeLog('Encryption library', + 'Keyfile or shareKey could not be deleted for file "' . $ownerPath . '"', \OCP\Util::ERROR); + } + + Keymanager::delAllShareKeys($view, $owner, $ownerPath); + + \OC_FileProxy::$enabled = $proxyStatus; + + // If we don't return true then file delete will fail; better + // to leave orphaned keyfiles than to disallow file deletion + return true; + } /** - * @brief When a file is renamed, rename its keyfile also - * @return bool Result of rename() - * @note This is pre rather than post because using post didn't work + * @param $path + * @return bool */ - public function preRename( $oldPath, $newPath ) { - - // Disable encryption proxy to prevent recursive calls - \OC_FileProxy::$enabled = false; - - $view = new \OC_FilesystemView( '/' ); - - $userId = \OCP\USER::getUser(); - - // Format paths to be relative to user files dir - $oldTrimmed = ltrim( $oldPath, '/' ); - $oldSplit = explode( '/', $oldTrimmed ); - $oldSliced = array_slice( $oldSplit, 2 ); - $oldRelPath = implode( '/', $oldSliced ); - $oldKeyfilePath = $userId . '/' . 'files_encryption' . '/' . 'keyfiles' . '/' . $oldRelPath . '.key'; - - $newTrimmed = ltrim( $newPath, '/' ); - $newSplit = explode( '/', $newTrimmed ); - $newSliced = array_slice( $newSplit, 2 ); - $newRelPath = implode( '/', $newSliced ); - $newKeyfilePath = $userId . '/' . 'files_encryption' . '/' . 'keyfiles' . '/' . $newRelPath . '.key'; - - // Rename keyfile so it isn't orphaned - $result = $view->rename( $oldKeyfilePath, $newKeyfilePath ); - - \OC_FileProxy::$enabled = true; - - return $result; - + public function postTouch($path) { + $this->handleFile($path); + + return true; } - - public function postFopen( $path, &$result ){ - - if ( !$result ) { - + + /** + * @param $path + * @param $result + * @return resource + */ + public function postFopen($path, &$result) { + + $path = \OC\Files\Filesystem::normalizePath($path); + + if (!$result) { + return $result; - + } - - // Reformat path for use with OC_FSV - $path_split = explode( '/', $path ); - $path_f = implode( array_slice( $path_split, 3 ) ); - + + // split the path parts + $pathParts = explode('/', $path); + + // get relative path + $relativePath = \OCA\Encryption\Helper::stripUserFilesPath($path); + + // FIXME: handling for /userId/cache used by webdav for chunking. The cache chunks are NOT encrypted + if (isset($pathParts[2]) && $pathParts[2] === 'cache') { + return $result; + } + // Disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; \OC_FileProxy::$enabled = false; - - $meta = stream_get_meta_data( $result ); - - $view = new \OC_FilesystemView( '' ); - - $util = new Util( $view, \OCP\USER::getUser()); - + + $meta = stream_get_meta_data($result); + + $view = new \OC_FilesystemView(''); + + $util = new Util($view, \OCP\USER::getUser()); + // If file is already encrypted, decrypt using crypto protocol - if ( - Crypt::mode() == 'server' - && $util->isEncryptedPath( $path ) + if ( + Crypt::mode() === 'server' + && $util->isEncryptedPath($path) ) { - + // Close the original encrypted file - fclose( $result ); - + fclose($result); + // Open the file using the crypto stream wrapper // protocol and let it do the decryption work instead - $result = fopen( 'crypt://' . $path_f, $meta['mode'] ); - - - } elseif ( - self::shouldEncrypt( $path ) - and $meta ['mode'] != 'r' - and $meta['mode'] != 'rb' + $result = fopen('crypt://' . $relativePath, $meta['mode']); + + } elseif ( + self::shouldEncrypt($path) + and $meta ['mode'] !== 'r' + and $meta['mode'] !== 'rb' ) { - // If the file is not yet encrypted, but should be - // encrypted when it's saved (it's not read only) - - // NOTE: this is the case for new files saved via WebDAV - - if ( - $view->file_exists( $path ) - and $view->filesize( $path ) > 0 - ) { - $x = $view->file_get_contents( $path ); - - $tmp = tmpfile(); - -// // Make a temporary copy of the original file -// \OCP\Files::streamCopy( $result, $tmp ); -// -// // Close the original stream, we'll return another one -// fclose( $result ); -// -// $view->file_put_contents( $path_f, $tmp ); -// -// fclose( $tmp ); - - } - - $result = fopen( 'crypt://'.$path_f, $meta['mode'] ); - + $result = fopen('crypt://' . $relativePath, $meta['mode']); } - + // Re-enable the proxy - \OC_FileProxy::$enabled = true; - + \OC_FileProxy::$enabled = $proxyStatus; + return $result; - + } - public function postGetMimeType( $path, $mime ) { - - if ( Crypt::isCatfile( $path ) ) { - - $mime = \OCP\Files::getMimeType( 'crypt://' . $path, 'w' ); - - } - - return $mime; - - } + /** + * @param $path + * @param $data + * @return array + */ + public function postGetFileInfo($path, $data) { - public function postStat( $path, $data ) { - - if ( Crypt::isCatfile( $path ) ) { - - $cached = \OC\Files\Filesystem::getFileInfo( $path, '' ); - - $data['size'] = $cached['size']; - + // if path is a folder do nothing + if (is_array($data) && array_key_exists('size', $data)) { + + // Disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + // get file size + $data['size'] = self::postFileSize($path, $data['size']); + + // Re-enable the proxy + \OC_FileProxy::$enabled = $proxyStatus; } - + return $data; } - public function postFileSize( $path, $size ) { - - if ( Crypt::isCatfile( $path ) ) { - - $cached = \OC\Files\Filesystem::getFileInfo( $path, '' ); - - return $cached['size']; - - } else { - + /** + * @param $path + * @param $size + * @return bool + */ + public function postFileSize($path, $size) { + + $view = new \OC_FilesystemView('/'); + + // if path is a folder do nothing + if ($view->is_dir($path)) { return $size; - } + + // get relative path + $relativePath = \OCA\Encryption\Helper::stripUserFilesPath($path); + + // if path is empty we cannot resolve anything + if (empty($relativePath)) { + return $size; + } + + $fileInfo = false; + // get file info from database/cache if not .part file + if (!Keymanager::isPartialFilePath($path)) { + $fileInfo = $view->getFileInfo($path); + } + + // if file is encrypted return real file size + if (is_array($fileInfo) && $fileInfo['encrypted'] === true) { + $size = $fileInfo['unencrypted_size']; + } else { + // self healing if file was removed from file cache + if (!is_array($fileInfo)) { + $fileInfo = array(); + } + + $userId = \OCP\User::getUser(); + $util = new Util($view, $userId); + $fixSize = $util->getFileSize($path); + if ($fixSize > 0) { + $size = $fixSize; + + $fileInfo['encrypted'] = true; + $fileInfo['unencrypted_size'] = $size; + + // put file info if not .part file + if (!Keymanager::isPartialFilePath($relativePath)) { + $view->putFileInfo($path, $fileInfo); + } + } + + } + return $size; + } + + /** + * @param $path + */ + public function handleFile($path) { + + // Disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + $view = new \OC_FilesystemView('/'); + $session = new \OCA\Encryption\Session($view); + $userId = \OCP\User::getUser(); + $util = new Util($view, $userId); + + // split the path parts + $pathParts = explode('/', $path); + + // get relative path + $relativePath = \OCA\Encryption\Helper::stripUserFilesPath($path); + + // only if file is on 'files' folder fix file size and sharing + if (isset($pathParts[2]) && $pathParts[2] === 'files' && $util->fixFileSize($path)) { + + // get sharing app state + $sharingEnabled = \OCP\Share::isEnabled(); + + // get users + $usersSharing = $util->getSharingUsersArray($sharingEnabled, $relativePath); + + // update sharing-keys + $util->setSharedFileKeyfiles($session, $usersSharing, $relativePath); + } + + \OC_FileProxy::$enabled = $proxyStatus; } } diff --git a/apps/files_encryption/lib/session.php b/apps/files_encryption/lib/session.php index 769a40b359..1911386cd1 100644 --- a/apps/files_encryption/lib/session.php +++ b/apps/files_encryption/lib/session.php @@ -28,76 +28,165 @@ namespace OCA\Encryption; class Session { + private $view; + + /** + * @brief if session is started, check if ownCloud key pair is set up, if not create it + * @param \OC_FilesystemView $view + * + * @note The ownCloud key pair is used to allow public link sharing even if encryption is enabled + */ + public function __construct($view) { + + $this->view = $view; + + if (!$this->view->is_dir('owncloud_private_key')) { + + $this->view->mkdir('owncloud_private_key'); + + } + + $publicShareKeyId = \OC_Appconfig::getValue('files_encryption', 'publicShareKeyId'); + + if ($publicShareKeyId === null) { + $publicShareKeyId = 'pubShare_' . substr(md5(time()), 0, 8); + \OC_Appconfig::setValue('files_encryption', 'publicShareKeyId', $publicShareKeyId); + } + + if ( + !$this->view->file_exists("/public-keys/" . $publicShareKeyId . ".public.key") + || !$this->view->file_exists("/owncloud_private_key/" . $publicShareKeyId . ".private.key") + ) { + + $keypair = Crypt::createKeypair(); + + // Disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + // Save public key + + if (!$view->is_dir('/public-keys')) { + $view->mkdir('/public-keys'); + } + + $this->view->file_put_contents('/public-keys/' . $publicShareKeyId . '.public.key', $keypair['publicKey']); + + // Encrypt private key empty passphrase + $encryptedPrivateKey = Crypt::symmetricEncryptFileContent($keypair['privateKey'], ''); + + // Save private key + $this->view->file_put_contents( + '/owncloud_private_key/' . $publicShareKeyId . '.private.key', $encryptedPrivateKey); + + \OC_FileProxy::$enabled = $proxyStatus; + + } + + if (\OCA\Encryption\Helper::isPublicAccess()) { + // Disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + $encryptedKey = $this->view->file_get_contents( + '/owncloud_private_key/' . $publicShareKeyId . '.private.key'); + $privateKey = Crypt::decryptPrivateKey($encryptedKey, ''); + $this->setPublicSharePrivateKey($privateKey); + + \OC_FileProxy::$enabled = $proxyStatus; + } + } + /** * @brief Sets user private key to session + * @param string $privateKey * @return bool * + * @note this should only be set on login */ - public function setPrivateKey( $privateKey ) { - - $_SESSION['privateKey'] = $privateKey; - + public function setPrivateKey($privateKey) { + + \OC::$session->set('privateKey', $privateKey); + return true; - + } - + /** - * @brief Gets user private key from session + * @brief Gets user or public share private key from session * @returns string $privateKey The user's plaintext private key * */ public function getPrivateKey() { - - if ( - isset( $_SESSION['privateKey'] ) - && !empty( $_SESSION['privateKey'] ) - ) { - - return $_SESSION['privateKey']; - + // return the public share private key if this is a public access + if (\OCA\Encryption\Helper::isPublicAccess()) { + return $this->getPublicSharePrivateKey(); } else { - - return false; - + if (!is_null(\OC::$session->get('privateKey'))) { + return \OC::$session->get('privateKey'); + } else { + return false; + } } - } - + /** - * @brief Sets user legacy key to session + * @brief Sets public user private key to session + * @param string $privateKey * @return bool + */ + public function setPublicSharePrivateKey($privateKey) { + + \OC::$session->set('publicSharePrivateKey', $privateKey); + + return true; + + } + + /** + * @brief Gets public share private key from session + * @returns string $privateKey * */ - public function setLegacyKey( $legacyKey ) { - - if ( $_SESSION['legacyKey'] = $legacyKey ) { - - return true; - + public function getPublicSharePrivateKey() { + + if (!is_null(\OC::$session->get('publicSharePrivateKey'))) { + return \OC::$session->get('publicSharePrivateKey'); + } else { + return false; } - } - + + + /** + * @brief Sets user legacy key to session + * @param $legacyKey + * @return bool + */ + public function setLegacyKey($legacyKey) { + + \OC::$session->set('legacyKey', $legacyKey); + + return true; + } + /** * @brief Gets user legacy key from session * @returns string $legacyKey The user's plaintext legacy key * */ public function getLegacyKey() { - - if ( - isset( $_SESSION['legacyKey'] ) - && !empty( $_SESSION['legacyKey'] ) - ) { - - return $_SESSION['legacyKey']; - + + if (!is_null(\OC::$session->get('legacyKey'))) { + + return \OC::$session->get('legacyKey'); + } else { - + return false; - + } - + } -} \ No newline at end of file +} diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php index 65d7d57a05..3c1eb2c5f5 100644 --- a/apps/files_encryption/lib/stream.php +++ b/apps/files_encryption/lib/stream.php @@ -3,7 +3,7 @@ * ownCloud * * @author Robin Appelman - * @copyright 2012 Sam Tuke , 2011 Robin Appelman + * @copyright 2012 Sam Tuke , 2011 Robin Appelman * * * This library is free software; you can redistribute it and/or @@ -32,254 +32,238 @@ namespace OCA\Encryption; /** * @brief Provides 'crypt://' stream wrapper protocol. - * @note We use a stream wrapper because it is the most secure way to handle + * @note We use a stream wrapper because it is the most secure way to handle * decrypted content transfers. There is no safe way to decrypt the entire file * somewhere on the server, so we have to encrypt and decrypt blocks on the fly. * @note Paths used with this protocol MUST BE RELATIVE. Use URLs like: - * crypt://filename, or crypt://subdirectory/filename, NOT - * crypt:///home/user/owncloud/data. Otherwise keyfiles will be put in - * [owncloud]/data/user/files_encryption/keyfiles/home/user/owncloud/data and + * crypt://filename, or crypt://subdirectory/filename, NOT + * crypt:///home/user/owncloud/data. Otherwise keyfiles will be put in + * [owncloud]/data/user/files_encryption/keyfiles/home/user/owncloud/data and * will not be accessible to other methods. - * @note Data read and written must always be 8192 bytes long, as this is the - * buffer size used internally by PHP. The encryption process makes the input - * data longer, and input is chunked into smaller pieces in order to result in + * @note Data read and written must always be 8192 bytes long, as this is the + * buffer size used internally by PHP. The encryption process makes the input + * data longer, and input is chunked into smaller pieces in order to result in * a 8192 encrypted block size. + * @note When files are deleted via webdav, or when they are updated and the + * previous version deleted, this is handled by OC\Files\View, and thus the + * encryption proxies are used and keyfiles deleted. */ class Stream { + private $plainKey; + private $encKeyfiles; - public static $sourceStreams = array(); - - // TODO: make all below properties private again once unit testing is - // configured correctly - public $rawPath; // The raw path received by stream_open - public $path_f; // The raw path formatted to include username and data dir + private $rawPath; // The raw path relative to the data dir + private $relPath; // rel path to users file dir private $userId; private $handle; // Resource returned by fopen - private $path; - private $readBuffer; // For streams that dont support seeking private $meta = array(); // Header / meta for source stream - private $count; private $writeCache; - public $size; + private $size; + private $unencryptedSize; private $publicKey; - private $keyfile; private $encKeyfile; - private static $view; // a fsview object set to user dir + /** + * @var \OC\Files\View + */ private $rootView; // a fsview object set to '/' + /** + * @var \OCA\Encryption\Session + */ + private $session; + private $privateKey; - public function stream_open( $path, $mode, $options, &$opened_path ) { - - // Get access to filesystem via filesystemview object - if ( !self::$view ) { - - self::$view = new \OC_FilesystemView( $this->userId . '/' ); + /** + * @param $path + * @param $mode + * @param $options + * @param $opened_path + * @return bool + */ + public function stream_open($path, $mode, $options, &$opened_path) { + if (!isset($this->rootView)) { + $this->rootView = new \OC_FilesystemView('/'); } - - // Set rootview object if necessary - if ( ! $this->rootView ) { - $this->rootView = new \OC_FilesystemView( $this->userId . '/' ); + $this->session = new \OCA\Encryption\Session($this->rootView); - } - - $this->userId = \OCP\User::getUser(); - - // Get the bare file path - $path = str_replace( 'crypt://', '', $path ); - - $this->rawPath = $path; - - $this->path_f = $this->userId . '/files/' . $path; - - if ( - dirname( $path ) == 'streams' - and isset( self::$sourceStreams[basename( $path )] ) + $this->privateKey = $this->session->getPrivateKey($this->userId); + + $util = new Util($this->rootView, \OCP\USER::getUser()); + + $this->userId = $util->getUserId(); + + // Strip identifier text from path, this gives us the path relative to data//files + $this->relPath = \OC\Files\Filesystem::normalizePath(str_replace('crypt://', '', $path)); + + // rawPath is relative to the data directory + $this->rawPath = $util->getUserFilesDir() . $this->relPath; + + // Disable fileproxies so we can get the file size and open the source file without recursive encryption + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + if ( + $mode === 'w' + or $mode === 'w+' + or $mode === 'wb' + or $mode === 'wb+' ) { - - // Is this just for unit testing purposes? - $this->handle = self::$sourceStreams[basename( $path )]['stream']; - - $this->path = self::$sourceStreams[basename( $path )]['path']; - - $this->size = self::$sourceStreams[basename( $path )]['size']; + // We're writing a new file so start write counter with 0 bytes + $this->size = 0; + $this->unencryptedSize = 0; } else { - if ( - $mode == 'w' - or $mode == 'w+' - or $mode == 'wb' - or $mode == 'wb+' - ) { - - $this->size = 0; - - } else { - - - - $this->size = self::$view->filesize( $this->path_f, $mode ); - - //$this->size = filesize( $path ); - + if($this->privateKey === false) { + // if private key is not valid redirect user to a error page + \OCA\Encryption\Helper::redirectToErrorPage(); } - // Disable fileproxies so we can open the source file without recursive encryption - \OC_FileProxy::$enabled = false; + $this->size = $this->rootView->filesize($this->rawPath, $mode); + } - //$this->handle = fopen( $path, $mode ); - - $this->handle = self::$view->fopen( $this->path_f, $mode ); - - \OC_FileProxy::$enabled = true; + $this->handle = $this->rootView->fopen($this->rawPath, $mode); - if ( !is_resource( $this->handle ) ) { + \OC_FileProxy::$enabled = $proxyStatus; - \OCP\Util::writeLog( 'files_encryption', 'failed to open '.$path, \OCP\Util::ERROR ); + if (!is_resource($this->handle)) { - } + \OCP\Util::writeLog('Encryption library', 'failed to open file "' . $this->rawPath . '"', \OCP\Util::ERROR); + + } else { + + $this->meta = stream_get_meta_data($this->handle); } - if ( is_resource( $this->handle ) ) { - $this->meta = stream_get_meta_data( $this->handle ); - - } - - return is_resource( $this->handle ); + return is_resource($this->handle); } - - public function stream_seek( $offset, $whence = SEEK_SET ) { - + + /** + * @param $offset + * @param int $whence + */ + public function stream_seek($offset, $whence = SEEK_SET) { + $this->flush(); - - fseek( $this->handle, $offset, $whence ); - + + fseek($this->handle, $offset, $whence); + } - - public function stream_tell() { - return ftell($this->handle); - } - - public function stream_read( $count ) { - + + /** + * @param $count + * @return bool|string + * @throws \Exception + */ + public function stream_read($count) { + $this->writeCache = ''; - if ( $count != 8192 ) { - + if ($count !== 8192) { + // $count will always be 8192 https://bugs.php.net/bug.php?id=21641 // This makes this function a lot simpler, but will break this class if the above 'bug' gets 'fixed' - \OCP\Util::writeLog( 'files_encryption', 'PHP "bug" 21641 no longer holds, decryption system requires refactoring', \OCP\Util::FATAL ); + \OCP\Util::writeLog('Encryption library', 'PHP "bug" 21641 no longer holds, decryption system requires refactoring', \OCP\Util::FATAL); die(); } -// $pos = ftell( $this->handle ); -// // Get the data from the file handle - $data = fread( $this->handle, 8192 ); - - if ( strlen( $data ) ) { - - $this->getKey(); - - $result = Crypt::symmetricDecryptFileContent( $data, $this->keyfile ); - - } else { + $data = fread($this->handle, 8192); - $result = ''; + $result = null; + + if (strlen($data)) { + + if (!$this->getKey()) { + + // Error! We don't have a key to decrypt the file with + throw new \Exception( + 'Encryption key not found for "' . $this->rawPath . '" during attempted read via stream'); + + } else { + + // Decrypt data + $result = Crypt::symmetricDecryptFileContent($data, $this->plainKey); + } } -// $length = $this->size - $pos; -// -// if ( $length < 8192 ) { -// -// $result = substr( $result, 0, $length ); -// -// } - return $result; } - + /** * @brief Encrypt and pad data ready for writing to disk * @param string $plainData data to be encrypted * @param string $key key to use for encryption - * @return encrypted data on success, false on failure + * @return string encrypted data on success, false on failure */ - public function preWriteEncrypt( $plainData, $key ) { - + public function preWriteEncrypt($plainData, $key) { + // Encrypt data to 'catfile', which includes IV - if ( $encrypted = Crypt::symmetricEncryptFileContent( $plainData, $key ) ) { - - return $encrypted; - + if ($encrypted = Crypt::symmetricEncryptFileContent($plainData, $key)) { + + return $encrypted; + } else { - + return false; - + } - + } - + /** - * @brief Get the keyfile for the current file, generate one if necessary - * @param bool $generate if true, a new key will be generated if none can be found + * @brief Fetch the plain encryption key for the file and set it as plainKey property + * @internal param bool $generate if true, a new key will be generated if none can be found * @return bool true on key found and set, false on key not found and new key generated and set */ public function getKey() { - - // If a keyfile already exists for a file named identically to - // file to be written - if ( self::$view->file_exists( $this->userId . '/'. 'files_encryption' . '/' . 'keyfiles' . '/' . $this->rawPath . '.key' ) ) { - - // TODO: add error handling for when file exists but no - // keyfile - - // Fetch existing keyfile - $this->encKeyfile = Keymanager::getFileKey( $this->rootView, $this->userId, $this->rawPath ); - - $this->getUser(); - - $session = new Session(); - - $privateKey = $session->getPrivateKey( $this->userId ); - - $this->keyfile = Crypt::keyDecrypt( $this->encKeyfile, $privateKey ); - + + // Check if key is already set + if (isset($this->plainKey) && isset($this->encKeyfile)) { + return true; - + + } + + // Fetch and decrypt keyfile + // Fetch existing keyfile + $this->encKeyfile = Keymanager::getFileKey($this->rootView, $this->userId, $this->relPath); + + // If a keyfile already exists + if ($this->encKeyfile) { + + // if there is no valid private key return false + if ($this->privateKey === false) { + + // if private key is not valid redirect user to a error page + \OCA\Encryption\Helper::redirectToErrorPage(); + + return false; + } + + $shareKey = Keymanager::getShareKey($this->rootView, $this->userId, $this->relPath); + + $this->plainKey = Crypt::multiKeyDecrypt($this->encKeyfile, $shareKey, $this->privateKey); + + return true; + } else { - + return false; - + } - + } - - public function getuser() { - - // Only get the user again if it isn't already set - if ( empty( $this->userId ) ) { - - // TODO: Move this user call out of here - it belongs - // elsewhere - $this->userId = \OCP\User::getUser(); - - } - - // TODO: Add a method for getting the user in case OCP\User:: - // getUser() doesn't work (can that scenario ever occur?) - - } - + /** * @brief Handle plain data from the stream, and write it in 8192 byte blocks * @param string $data data to be written to disk @@ -289,99 +273,61 @@ class Stream { * @note Padding is added to each encrypted block to ensure that the resulting block is exactly 8192 bytes. This is removed during stream_read * @note PHP automatically updates the file pointer after writing data to reflect it's length. There is generally no need to update the poitner manually using fseek */ - public function stream_write( $data ) { - + public function stream_write($data) { + + // if there is no valid private key return false + if ($this->privateKey === false) { + $this->size = 0; + return strlen($data); + } + // Disable the file proxies so that encryption is not // automatically attempted when the file is written to disk - // we are handling that separately here and we don't want to // get into an infinite loop + $proxyStatus = \OC_FileProxy::$enabled; \OC_FileProxy::$enabled = false; - + // Get the length of the unencrypted data that we are handling - $length = strlen( $data ); - - // So far this round, no data has been written - $written = 0; - - // Find out where we are up to in the writing of data to the + $length = strlen($data); + + // Find out where we are up to in the writing of data to the // file - $pointer = ftell( $this->handle ); - - // Make sure the userId is set - $this->getuser(); - - // TODO: Check if file is shared, if so, use multiKeyEncrypt and - // save shareKeys in necessary user directories - + $pointer = ftell($this->handle); + // Get / generate the keyfile for the file we're handling // If we're writing a new file (not overwriting an existing // one), save the newly generated keyfile - if ( ! $this->getKey() ) { - - $this->keyfile = Crypt::generateKey(); - - $this->publicKey = Keymanager::getPublicKey( $this->rootView, $this->userId ); - - $this->encKeyfile = Crypt::keyEncrypt( $this->keyfile, $this->publicKey ); - - $view = new \OC_FilesystemView( '/' ); - $userId = \OCP\User::getUser(); - - // Save the new encrypted file key - Keymanager::setFileKey( $view, $this->rawPath, $userId, $this->encKeyfile ); - + if (!$this->getKey()) { + + $this->plainKey = Crypt::generateKey(); + } // If extra data is left over from the last round, make sure it // is integrated into the next 6126 / 8192 block - if ( $this->writeCache ) { - + if ($this->writeCache) { + // Concat writeCache to start of $data $data = $this->writeCache . $data; - - // Clear the write cache, ready for resuse - it has been + + // Clear the write cache, ready for reuse - it has been // flushed and its old contents processed $this->writeCache = ''; } -// -// // Make sure we always start on a block start - if ( 0 != ( $pointer % 8192 ) ) { - // if the current position of - // file indicator is not aligned to a 8192 byte block, fix it - // so that it is -// fseek( $this->handle, - ( $pointer % 8192 ), SEEK_CUR ); -// -// $pointer = ftell( $this->handle ); -// -// $unencryptedNewBlock = fread( $this->handle, 8192 ); -// -// fseek( $this->handle, - ( $currentPos % 8192 ), SEEK_CUR ); -// -// $block = Crypt::symmetricDecryptFileContent( $unencryptedNewBlock, $this->keyfile ); -// -// $x = substr( $block, 0, $currentPos % 8192 ); -// -// $data = $x . $data; -// -// fseek( $this->handle, - ( $currentPos % 8192 ), SEEK_CUR ); -// - } + // While there still remains some data to be processed & written + while (strlen($data) > 0) { + + // Remaining length for this iteration, not of the + // entire file (may be greater than 8192 bytes) + $remainingLength = strlen($data); + + // If data remaining to be written is less than the + // size of 1 6126 byte block + if ($remainingLength < 6126) { -// $currentPos = ftell( $this->handle ); - -// // While there still remains somed data to be processed & written - while( strlen( $data ) > 0 ) { -// -// // Remaining length for this iteration, not of the -// // entire file (may be greater than 8192 bytes) -// $remainingLength = strlen( $data ); -// -// // If data remaining to be written is less than the -// // size of 1 6126 byte block - if ( strlen( $data ) < 6126 ) { - // Set writeCache to contents of $data // The writeCache will be carried over to the // next write round, and added to the start of @@ -394,101 +340,187 @@ class Stream { // Clear $data ready for next round $data = ''; -// + } else { - + // Read the chunk from the start of $data - $chunk = substr( $data, 0, 6126 ); - - $encrypted = $this->preWriteEncrypt( $chunk, $this->keyfile ); - + $chunk = substr($data, 0, 6126); + + $encrypted = $this->preWriteEncrypt($chunk, $this->plainKey); + // Write the data chunk to disk. This will be // attended to the last data chunk if the file // being handled totals more than 6126 bytes - fwrite( $this->handle, $encrypted ); - - $writtenLen = strlen( $encrypted ); - //fseek( $this->handle, $writtenLen, SEEK_CUR ); + fwrite($this->handle, $encrypted); - // Remove the chunk we just processed from + // Remove the chunk we just processed from // $data, leaving only unprocessed data in $data // var, for handling on the next round - $data = substr( $data, 6126 ); + $data = substr($data, 6126); } - + } - $this->size = max( $this->size, $pointer + $length ); - + $this->size = max($this->size, $pointer + $length); + $this->unencryptedSize += $length; + + \OC_FileProxy::$enabled = $proxyStatus; + return $length; } - public function stream_set_option( $option, $arg1, $arg2 ) { - switch($option) { + /** + * @param $option + * @param $arg1 + * @param $arg2 + */ + public function stream_set_option($option, $arg1, $arg2) { + $return = false; + switch ($option) { case STREAM_OPTION_BLOCKING: - stream_set_blocking( $this->handle, $arg1 ); + $return = stream_set_blocking($this->handle, $arg1); break; case STREAM_OPTION_READ_TIMEOUT: - stream_set_timeout( $this->handle, $arg1, $arg2 ); + $return = stream_set_timeout($this->handle, $arg1, $arg2); break; case STREAM_OPTION_WRITE_BUFFER: - stream_set_write_buffer( $this->handle, $arg1, $arg2 ); + $return = stream_set_write_buffer($this->handle, $arg1); } + + return $return; } + /** + * @return array + */ public function stream_stat() { return fstat($this->handle); } - - public function stream_lock( $mode ) { - flock( $this->handle, $mode ); - } - - public function stream_flush() { - - return fflush( $this->handle ); - // Not a typo: http://php.net/manual/en/function.fflush.php - + + /** + * @param $mode + */ + public function stream_lock($mode) { + return flock($this->handle, $mode); } + /** + * @return bool + */ + public function stream_flush() { + + return fflush($this->handle); + // Not a typo: http://php.net/manual/en/function.fflush.php + + } + + /** + * @return bool + */ public function stream_eof() { return feof($this->handle); } private function flush() { - - if ( $this->writeCache ) { - + + if ($this->writeCache) { + // Set keyfile property for file in question $this->getKey(); - - $encrypted = $this->preWriteEncrypt( $this->writeCache, $this->keyfile ); - - fwrite( $this->handle, $encrypted ); - + + $encrypted = $this->preWriteEncrypt($this->writeCache, $this->plainKey); + + fwrite($this->handle, $encrypted); + $this->writeCache = ''; - + } - + } + /** + * @return bool + */ public function stream_close() { - + $this->flush(); - if ( - $this->meta['mode']!='r' - and $this->meta['mode']!='rb' - ) { + // if there is no valid private key return false + if ($this->privateKey === false) { - \OC\Files\Filesystem::putFileInfo( $this->path, array( 'encrypted' => true, 'size' => $this->size ), '' ); + // cleanup + if ($this->meta['mode'] !== 'r' && $this->meta['mode'] !== 'rb') { + // Disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + if ($this->rootView->file_exists($this->rawPath) && $this->size === 0) { + $this->rootView->unlink($this->rawPath); + } + + // Re-enable proxy - our work is done + \OC_FileProxy::$enabled = $proxyStatus; + } + + // if private key is not valid redirect user to a error page + \OCA\Encryption\Helper::redirectToErrorPage(); } - return fclose( $this->handle ); + if ( + $this->meta['mode'] !== 'r' + and $this->meta['mode'] !== 'rb' + and $this->size > 0 + ) { + // Disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + // Fetch user's public key + $this->publicKey = Keymanager::getPublicKey($this->rootView, $this->userId); + + // Check if OC sharing api is enabled + $sharingEnabled = \OCP\Share::isEnabled(); + + $util = new Util($this->rootView, $this->userId); + + // Get all users sharing the file includes current user + $uniqueUserIds = $util->getSharingUsersArray($sharingEnabled, $this->relPath, $this->userId); + + // Fetch public keys for all sharing users + $publicKeys = Keymanager::getPublicKeys($this->rootView, $uniqueUserIds); + + // Encrypt enc key for all sharing users + $this->encKeyfiles = Crypt::multiKeyEncrypt($this->plainKey, $publicKeys); + + // Save the new encrypted file key + Keymanager::setFileKey($this->rootView, $this->relPath, $this->userId, $this->encKeyfiles['data']); + + // Save the sharekeys + Keymanager::setShareKeys($this->rootView, $this->relPath, $this->encKeyfiles['keys']); + + // get file info + $fileInfo = $this->rootView->getFileInfo($this->rawPath); + if (!is_array($fileInfo)) { + $fileInfo = array(); + } + + // Re-enable proxy - our work is done + \OC_FileProxy::$enabled = $proxyStatus; + + // set encryption data + $fileInfo['encrypted'] = true; + $fileInfo['size'] = $this->size; + $fileInfo['unencrypted_size'] = $this->unencryptedSize; + + // set fileinfo + $this->rootView->putFileInfo($this->rawPath, $fileInfo); + } + + return fclose($this->handle); } diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index 52bc74db27..94defa726a 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -3,8 +3,8 @@ * ownCloud * * @author Sam Tuke, Frank Karlitschek - * @copyright 2012 Sam Tuke samtuke@owncloud.com, - * Frank Karlitschek frank@owncloud.org + * @copyright 2012 Sam Tuke , + * Frank Karlitschek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE @@ -21,81 +21,88 @@ * */ -// Todo: +# Bugs +# ---- +# Sharing a file to a user without encryption set up will not provide them with access but won't notify the sharer +# Sharing all files to admin for recovery purposes still in progress +# Possibly public links are broken (not tested since last merge of master) + + +# Missing features +# ---------------- +# Make sure user knows if large files weren't encrypted + + +# Test +# ---- +# Test that writing files works when recovery is enabled, and sharing API is disabled +# Test trashbin support + + +// Old Todo: // - Crypt/decrypt button in the userinterface // - Setting if crypto should be on by default // - Add a setting "Don´t encrypt files larger than xx because of performance // reasons" -// - Transparent decrypt/encrypt in filesystem.php. Autodetect if a file is -// encrypted (.encrypted extension) -// - Don't use a password directly as encryption key. but a key which is -// stored on the server and encrypted with the user password. -> password -// change faster -// - IMPORTANT! Check if the block lenght of the encrypted data stays the same namespace OCA\Encryption; /** * @brief Class for utilities relating to encrypted file storage system - * @param OC_FilesystemView $view expected to have OC '/' as root path + * @param \OC_FilesystemView $view expected to have OC '/' as root path * @param string $userId ID of the logged in user * @param int $client indicating status of client side encryption. Currently * unused, likely to become obsolete shortly */ class Util { - - + // Web UI: - + //// DONE: files created via web ui are encrypted //// DONE: file created & encrypted via web ui are readable in web ui //// DONE: file created & encrypted via web ui are readable via webdav - - + + // WebDAV: - + //// DONE: new data filled files added via webdav get encrypted //// DONE: new data filled files added via webdav are readable via webdav //// DONE: reading unencrypted files when encryption is enabled works via //// webdav //// DONE: files created & encrypted via web ui are readable via webdav - - + + // Legacy support: - + //// DONE: add method to check if file is encrypted using new system //// DONE: add method to check if file is encrypted using old system //// DONE: add method to fetch legacy key //// DONE: add method to decrypt legacy encrypted data - - + + // Admin UI: - + //// DONE: changing user password also changes encryption passphrase - + //// TODO: add support for optional recovery in case of lost passphrase / keys //// TODO: add admin optional required long passphrase for users - //// TODO: add UI buttons for encrypt / decrypt everything //// TODO: implement flag system to allow user to specify encryption by folder, subfolder, etc. - - - // Sharing: - - //// TODO: add support for encrypting to multiple public keys - //// TODO: add support for decrypting to multiple private keys - - + + // Integration testing: - + //// TODO: test new encryption with versioning - //// TODO: test new encryption with sharing + //// DONE: test new encryption with sharing //// TODO: test new encryption with proxies - - + + const MIGRATION_COMPLETED = 1; // migration to new encryption completed + const MIGRATION_IN_PROGRESS = -1; // migration is running + const MIGRATION_OPEN = 0; // user still needs to be migrated + + private $view; // OC_FilesystemView object for filesystem operations private $userId; // ID of the currently logged-in user - private $pwd; // User Password private $client; // Client side encryption mode flag private $publicKeyDir; // Dir containing all public user keys private $encryptionDir; // Dir containing user's files_encryption @@ -103,166 +110,322 @@ class Util { private $shareKeysPath; // Dir containing env keys for shared files private $publicKeyPath; // Path to user's public key private $privateKeyPath; // Path to user's private key + private $publicShareKeyId; + private $recoveryKeyId; + private $isPublic; + + /** + * @param \OC_FilesystemView $view + * @param $userId + * @param bool $client + */ + public function __construct(\OC_FilesystemView $view, $userId, $client = false) { - public function __construct( \OC_FilesystemView $view, $userId, $client = false ) { - $this->view = $view; $this->userId = $userId; $this->client = $client; - $this->userDir = '/' . $this->userId; - $this->userFilesDir = '/' . $this->userId . '/' . 'files'; - $this->publicKeyDir = '/' . 'public-keys'; - $this->encryptionDir = '/' . $this->userId . '/' . 'files_encryption'; - $this->keyfilesPath = $this->encryptionDir . '/' . 'keyfiles'; - $this->shareKeysPath = $this->encryptionDir . '/' . 'share-keys'; - $this->publicKeyPath = $this->publicKeyDir . '/' . $this->userId . '.public.key'; // e.g. data/public-keys/admin.public.key - $this->privateKeyPath = $this->encryptionDir . '/' . $this->userId . '.private.key'; // e.g. data/admin/admin.private.key - - } - - public function ready() { - - if( - !$this->view->file_exists( $this->encryptionDir ) - or !$this->view->file_exists( $this->keyfilesPath ) - or !$this->view->file_exists( $this->shareKeysPath ) - or !$this->view->file_exists( $this->publicKeyPath ) - or !$this->view->file_exists( $this->privateKeyPath ) - ) { - - return false; - + $this->isPublic = false; + + $this->publicShareKeyId = \OC_Appconfig::getValue('files_encryption', 'publicShareKeyId'); + $this->recoveryKeyId = \OC_Appconfig::getValue('files_encryption', 'recoveryKeyId'); + + // if we are anonymous/public + if (\OCA\Encryption\Helper::isPublicAccess()) { + $this->userId = $this->publicShareKeyId; + + // only handle for files_sharing app + if (isset($GLOBALS['app']) && $GLOBALS['app'] === 'files_sharing') { + $this->userDir = '/' . $GLOBALS['fileOwner']; + $this->fileFolderName = 'files'; + $this->userFilesDir = '/' . $GLOBALS['fileOwner'] . '/' + . $this->fileFolderName; // TODO: Does this need to be user configurable? + $this->publicKeyDir = '/' . 'public-keys'; + $this->encryptionDir = '/' . $GLOBALS['fileOwner'] . '/' . 'files_encryption'; + $this->keyfilesPath = $this->encryptionDir . '/' . 'keyfiles'; + $this->shareKeysPath = $this->encryptionDir . '/' . 'share-keys'; + $this->publicKeyPath = + $this->publicKeyDir . '/' . $this->userId . '.public.key'; // e.g. data/public-keys/admin.public.key + $this->privateKeyPath = + '/owncloud_private_key/' . $this->userId . '.private.key'; // e.g. data/admin/admin.private.key + $this->isPublic = true; + } + } else { - - return true; - + $this->userDir = '/' . $this->userId; + $this->fileFolderName = 'files'; + $this->userFilesDir = + '/' . $this->userId . '/' . $this->fileFolderName; // TODO: Does this need to be user configurable? + $this->publicKeyDir = '/' . 'public-keys'; + $this->encryptionDir = '/' . $this->userId . '/' . 'files_encryption'; + $this->keyfilesPath = $this->encryptionDir . '/' . 'keyfiles'; + $this->shareKeysPath = $this->encryptionDir . '/' . 'share-keys'; + $this->publicKeyPath = + $this->publicKeyDir . '/' . $this->userId . '.public.key'; // e.g. data/public-keys/admin.public.key + $this->privateKeyPath = + $this->encryptionDir . '/' . $this->userId . '.private.key'; // e.g. data/admin/admin.private.key } - } - - /** - * @brief Sets up user folders and keys for serverside encryption - * @param $passphrase passphrase to encrypt server-stored private key with - */ - public function setupServerSide( $passphrase = null ) { - - // Create user dir - if( !$this->view->file_exists( $this->userDir ) ) { - - $this->view->mkdir( $this->userDir ); - - } - - // Create user files dir - if( !$this->view->file_exists( $this->userFilesDir ) ) { - - $this->view->mkdir( $this->userFilesDir ); - - } - - // Create shared public key directory - if( !$this->view->file_exists( $this->publicKeyDir ) ) { - - $this->view->mkdir( $this->publicKeyDir ); - - } - - // Create encryption app directory - if( !$this->view->file_exists( $this->encryptionDir ) ) { - - $this->view->mkdir( $this->encryptionDir ); - - } - - // Create mirrored keyfile directory - if( !$this->view->file_exists( $this->keyfilesPath ) ) { - - $this->view->mkdir( $this->keyfilesPath ); - + + /** + * @return bool + */ + public function ready() { + + if ( + !$this->view->file_exists($this->encryptionDir) + or !$this->view->file_exists($this->keyfilesPath) + or !$this->view->file_exists($this->shareKeysPath) + or !$this->view->file_exists($this->publicKeyPath) + or !$this->view->file_exists($this->privateKeyPath) + ) { + + return false; + + } else { + + return true; + } - // Create mirrored share env keys directory - if( !$this->view->file_exists( $this->shareKeysPath ) ) { - - $this->view->mkdir( $this->shareKeysPath ); - + } + + /** + * @brief Sets up user folders and keys for serverside encryption + * + * @param string $passphrase to encrypt server-stored private key with + * @return bool + */ + public function setupServerSide($passphrase = null) { + + // Set directories to check / create + $setUpDirs = array( + $this->userDir, + $this->userFilesDir, + $this->publicKeyDir, + $this->encryptionDir, + $this->keyfilesPath, + $this->shareKeysPath + ); + + // Check / create all necessary dirs + foreach ($setUpDirs as $dirPath) { + + if (!$this->view->file_exists($dirPath)) { + + $this->view->mkdir($dirPath); + + } + } - + // Create user keypair - if ( - ! $this->view->file_exists( $this->publicKeyPath ) - or ! $this->view->file_exists( $this->privateKeyPath ) + // we should never override a keyfile + if ( + !$this->view->file_exists($this->publicKeyPath) + && !$this->view->file_exists($this->privateKeyPath) ) { - + // Generate keypair $keypair = Crypt::createKeypair(); - + \OC_FileProxy::$enabled = false; - + // Save public key - $this->view->file_put_contents( $this->publicKeyPath, $keypair['publicKey'] ); - + $this->view->file_put_contents($this->publicKeyPath, $keypair['publicKey']); + // Encrypt private key with user pwd as passphrase - $encryptedPrivateKey = Crypt::symmetricEncryptFileContent( $keypair['privateKey'], $passphrase ); - + $encryptedPrivateKey = Crypt::symmetricEncryptFileContent($keypair['privateKey'], $passphrase); + // Save private key - $this->view->file_put_contents( $this->privateKeyPath, $encryptedPrivateKey ); - + $this->view->file_put_contents($this->privateKeyPath, $encryptedPrivateKey); + \OC_FileProxy::$enabled = true; - + + } else { + // check if public-key exists but private-key is missing + if ($this->view->file_exists($this->publicKeyPath) && !$this->view->file_exists($this->privateKeyPath)) { + \OCP\Util::writeLog('Encryption library', + 'public key exists but private key is missing for "' . $this->userId . '"', \OCP\Util::FATAL); + return false; + } else { + if (!$this->view->file_exists($this->publicKeyPath) && $this->view->file_exists($this->privateKeyPath) + ) { + \OCP\Util::writeLog('Encryption library', + 'private key exists but public key is missing for "' . $this->userId . '"', \OCP\Util::FATAL); + return false; + } + } } - + + // If there's no record for this user's encryption preferences + if (false === $this->recoveryEnabledForUser()) { + + // create database configuration + $sql = 'INSERT INTO `*PREFIX*encryption` (`uid`,`mode`,`recovery_enabled`) VALUES (?,?,?)'; + $args = array( + $this->userId, + 'server-side', + 0 + ); + $query = \OCP\DB::prepare($sql); + $query->execute($args); + + } + return true; - + } - + + /** + * @return string + */ + public function getPublicShareKeyId() { + return $this->publicShareKeyId; + } + + /** + * @brief Check whether pwd recovery is enabled for a given user + * @return bool 1 = yes, 0 = no, false = no record + * + * @note If records are not being returned, check for a hidden space + * at the start of the uid in db + */ + public function recoveryEnabledForUser() { + + $sql = 'SELECT `recovery_enabled` FROM `*PREFIX*encryption` WHERE `uid` = ?'; + + $args = array($this->userId); + + $query = \OCP\DB::prepare($sql); + + $result = $query->execute($args); + + $recoveryEnabled = array(); + + if (\OCP\DB::isError($result)) { + \OCP\Util::writeLog('Encryption library', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR); + } else { + if ($result->numRows() > 0) { + $row = $result->fetchRow(); + if (isset($row['recovery_enabled'])) { + $recoveryEnabled[] = $row['recovery_enabled']; + } + } + } + + // If no record is found + if (empty($recoveryEnabled)) { + + return false; + + // If a record is found + } else { + + return $recoveryEnabled[0]; + + } + + } + + /** + * @brief Enable / disable pwd recovery for a given user + * @param bool $enabled Whether to enable or disable recovery + * @return bool + */ + public function setRecoveryForUser($enabled) { + + $recoveryStatus = $this->recoveryEnabledForUser(); + + // If a record for this user already exists, update it + if (false === $recoveryStatus) { + + $sql = 'INSERT INTO `*PREFIX*encryption` (`uid`,`mode`,`recovery_enabled`) VALUES (?,?,?)'; + + $args = array( + $this->userId, + 'server-side', + $enabled + ); + + // Create a new record instead + } else { + + $sql = 'UPDATE `*PREFIX*encryption` SET `recovery_enabled` = ? WHERE `uid` = ?'; + + $args = array( + $enabled, + $this->userId + ); + + } + + $query = \OCP\DB::prepare($sql); + + if ($query->execute($args)) { + + return true; + + } else { + + return false; + + } + + } + /** * @brief Find all files and their encryption status within a directory * @param string $directory The path of the parent directory to search + * @param bool $found the founded files if called again * @return mixed false if 0 found, array on success. Keys: name, path - * @note $directory needs to be a path relative to OC data dir. e.g. * /admin/files NOT /backup OR /home/www/oc/data/admin/files */ - public function findFiles( $directory ) { - + public function findEncFiles($directory, &$found = false) { + // Disable proxy - we don't want files to be decrypted before // we handle them \OC_FileProxy::$enabled = false; - - $found = array( 'plain' => array(), 'encrypted' => array(), 'legacy' => array() ); - - if ( - $this->view->is_dir( $directory ) - && $handle = $this->view->opendir( $directory ) + + if ($found === false) { + $found = array( + 'plain' => array(), + 'encrypted' => array(), + 'legacy' => array() + ); + } + + if ( + $this->view->is_dir($directory) + && $handle = $this->view->opendir($directory) ) { - - while ( false !== ( $file = readdir( $handle ) ) ) { - + + while (false !== ($file = readdir($handle))) { + if ( - $file != "." - && $file != ".." + $file !== "." + && $file !== ".." ) { - - $filePath = $directory . '/' . $this->view->getRelativePath( '/' . $file ); - $relPath = $this->stripUserFilesPath( $filePath ); - + + $filePath = $directory . '/' . $this->view->getRelativePath('/' . $file); + $relPath = \OCA\Encryption\Helper::stripUserFilesPath($filePath); + // If the path is a directory, search // its contents - if ( $this->view->is_dir( $filePath ) ) { - - $this->findFiles( $filePath ); - - // If the path is a file, determine - // its encryption status - } elseif ( $this->view->is_file( $filePath ) ) { - + if ($this->view->is_dir($filePath)) { + + $this->findEncFiles($filePath, $found); + + // If the path is a file, determine + // its encryption status + } elseif ($this->view->is_file($filePath)) { + // Disable proxies again, some- // where they got re-enabled :/ \OC_FileProxy::$enabled = false; - - $data = $this->view->file_get_contents( $filePath ); - + + $data = $this->view->file_get_contents($filePath); + // If the file is encrypted // NOTE: If the userId is // empty or not set, file will @@ -270,207 +433,1128 @@ class Util { // NOTE: This is inefficient; // scanning every file like this // will eat server resources :( - if ( - Keymanager::getFileKey( $this->view, $this->userId, $file ) - && Crypt::isCatfile( $data ) + if ( + Keymanager::getFileKey($this->view, $this->userId, $relPath) + && Crypt::isCatfileContent($data) ) { - - $found['encrypted'][] = array( 'name' => $file, 'path' => $filePath ); - - // If the file uses old - // encryption system - } elseif ( Crypt::isLegacyEncryptedContent( $this->view->file_get_contents( $filePath ), $relPath ) ) { - - $found['legacy'][] = array( 'name' => $file, 'path' => $filePath ); - - // If the file is not encrypted + + $found['encrypted'][] = array( + 'name' => $file, + 'path' => $filePath + ); + + // If the file uses old + // encryption system + } elseif (Crypt::isLegacyEncryptedContent($data, $relPath)) { + + $found['legacy'][] = array( + 'name' => $file, + 'path' => $filePath + ); + + // If the file is not encrypted } else { - - $found['plain'][] = array( 'name' => $file, 'path' => $filePath ); - + + $found['plain'][] = array( + 'name' => $file, + 'path' => $relPath + ); + } - + } - + } - + } - + \OC_FileProxy::$enabled = true; - - if ( empty( $found ) ) { - + + if (empty($found)) { + return false; - + } else { - + return $found; - + } - + } - + \OC_FileProxy::$enabled = true; - + return false; } - - /** - * @brief Check if a given path identifies an encrypted file - * @return true / false - */ - public function isEncryptedPath( $path ) { - - // Disable encryption proxy so data retreived is in its - // original form - \OC_FileProxy::$enabled = false; - - $data = $this->view->file_get_contents( $path ); - - \OC_FileProxy::$enabled = true; - - return Crypt::isCatfile( $data ); - - } - + /** - * @brief Format a path to be relative to the /user/files/ directory + * @brief Fetch the last lines of a file efficiently + * @note Safe to use on large files; does not read entire file to memory + * @note Derivative of http://tekkie.flashbit.net/php/tail-functionality-in-php */ - public function stripUserFilesPath( $path ) { - - $trimmed = ltrim( $path, '/' ); - $split = explode( '/', $trimmed ); - $sliced = array_slice( $split, 2 ); - $relPath = implode( '/', $sliced ); - - return $relPath; - + public function tail($filename, $numLines) { + + \OC_FileProxy::$enabled = false; + + $text = ''; + $pos = -1; + $handle = $this->view->fopen($filename, 'r'); + + while ($numLines > 0) { + + --$pos; + + if (fseek($handle, $pos, SEEK_END) !== 0) { + + rewind($handle); + $numLines = 0; + + } elseif (fgetc($handle) === "\n") { + + --$numLines; + + } + + $block_size = (-$pos) % 8192; + if ($block_size === 0 || $numLines === 0) { + + $text = fread($handle, ($block_size === 0 ? 8192 : $block_size)) . $text; + + } + } + + fclose($handle); + + \OC_FileProxy::$enabled = true; + + return $text; } - + + /** + * @brief Check if a given path identifies an encrypted file + * @param string $path + * @return boolean + */ + public function isEncryptedPath($path) { + + // Disable encryption proxy so data retrieved is in its + // original form + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + // we only need 24 byte from the last chunk + $data = ''; + $handle = $this->view->fopen($path, 'r'); + if (is_resource($handle) && !fseek($handle, -24, SEEK_END)) { + $data = fgets($handle); + } + + // re-enable proxy + \OC_FileProxy::$enabled = $proxyStatus; + + return Crypt::isCatfileContent($data); + + } + + /** + * @brief get the file size of the unencrypted file + * @param string $path absolute path + * @return bool + */ + public function getFileSize($path) { + + $result = 0; + + // Disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + // split the path parts + $pathParts = explode('/', $path); + + // get relative path + $relativePath = \OCA\Encryption\Helper::stripUserFilesPath($path); + + if (isset($pathParts[2]) && $pathParts[2] === 'files' && $this->view->file_exists($path) + && $this->isEncryptedPath($path) + ) { + + // get the size from filesystem + $fullPath = $this->view->getLocalFile($path); + $size = filesize($fullPath); + + // calculate last chunk nr + $lastChunkNr = floor($size / 8192); + + // open stream + $stream = fopen('crypt://' . $relativePath, "r"); + + if (is_resource($stream)) { + // calculate last chunk position + $lastChunckPos = ($lastChunkNr * 8192); + + // seek to end + fseek($stream, $lastChunckPos); + + // get the content of the last chunk + $lastChunkContent = fread($stream, 8192); + + // calc the real file size with the size of the last chunk + $realSize = (($lastChunkNr * 6126) + strlen($lastChunkContent)); + + // store file size + $result = $realSize; + } + } + + \OC_FileProxy::$enabled = $proxyStatus; + + return $result; + } + + /** + * @brief fix the file size of the encrypted file + * @param string $path absolute path + * @return boolean true / false if file is encrypted + */ + public function fixFileSize($path) { + + $result = false; + + // Disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + $realSize = $this->getFileSize($path); + + if ($realSize > 0) { + + $cached = $this->view->getFileInfo($path); + $cached['encrypted'] = true; + + // set the size + $cached['unencrypted_size'] = $realSize; + + // put file info + $this->view->putFileInfo($path, $cached); + + $result = true; + + } + + \OC_FileProxy::$enabled = $proxyStatus; + + return $result; + } + + + /** + * @param $path + * @return bool + */ + public function isSharedPath($path) { + + $trimmed = ltrim($path, '/'); + $split = explode('/', $trimmed); + + if (isset($split[2]) && $split[2] === 'Shared') { + + return true; + + } else { + + return false; + + } + + } + /** * @brief Encrypt all files in a directory - * @param string $publicKey the public key to encrypt files with * @param string $dirPath the directory whose files will be encrypted + * @param null $legacyPassphrase + * @param null $newPassphrase + * @return bool * @note Encryption is recursive */ - public function encryptAll( $publicKey, $dirPath, $legacyPassphrase = null, $newPassphrase = null ) { - - if ( $found = $this->findFiles( $dirPath ) ) { - + public function encryptAll($dirPath, $legacyPassphrase = null, $newPassphrase = null) { + + if ($found = $this->findEncFiles($dirPath)) { + // Disable proxy to prevent file being encrypted twice \OC_FileProxy::$enabled = false; - + // Encrypt unencrypted files - foreach ( $found['plain'] as $plainFile ) { - - // Fetch data from file - $plainData = $this->view->file_get_contents( $plainFile['path'] ); - - // Encrypt data, generate catfile - $encrypted = Crypt::keyEncryptKeyfile( $plainData, $publicKey ); - - $relPath = $this->stripUserFilesPath( $plainFile['path'] ); - - // Save keyfile - Keymanager::setFileKey( $this->view, $relPath, $this->userId, $encrypted['key'] ); - - // Overwrite the existing file with the encrypted one - $this->view->file_put_contents( $plainFile['path'], $encrypted['data'] ); - - $size = strlen( $encrypted['data'] ); - + foreach ($found['plain'] as $plainFile) { + + //relative to data//file + $relPath = $plainFile['path']; + + //relative to /data + $rawPath = '/' . $this->userId . '/files/' . $plainFile['path']; + + // Open plain file handle for binary reading + $plainHandle = $this->view->fopen($rawPath, 'rb'); + + // Open enc file handle for binary writing, with same filename as original plain file + $encHandle = fopen('crypt://' . $relPath . '.tmp', 'wb'); + + // Move plain file to a temporary location + $size = stream_copy_to_stream($plainHandle, $encHandle); + + fclose($encHandle); + + $fakeRoot = $this->view->getRoot(); + $this->view->chroot('/' . $this->userId . '/files'); + + $this->view->rename($relPath . '.tmp', $relPath); + + $this->view->chroot($fakeRoot); + // Add the file to the cache - \OC\Files\Filesystem::putFileInfo( $plainFile['path'], array( 'encrypted'=>true, 'size' => $size ), '' ); - + \OC\Files\Filesystem::putFileInfo($relPath, array( + 'encrypted' => true, + 'size' => $size, + 'unencrypted_size' => $size + )); } - + // Encrypt legacy encrypted files - if ( - ! empty( $legacyPassphrase ) - && ! empty( $newPassphrase ) + if ( + !empty($legacyPassphrase) + && !empty($newPassphrase) ) { - - foreach ( $found['legacy'] as $legacyFile ) { - + + foreach ($found['legacy'] as $legacyFile) { + // Fetch data from file - $legacyData = $this->view->file_get_contents( $legacyFile['path'] ); - - // Recrypt data, generate catfile - $recrypted = Crypt::legacyKeyRecryptKeyfile( $legacyData, $legacyPassphrase, $publicKey, $newPassphrase ); - - $relPath = $this->stripUserFilesPath( $legacyFile['path'] ); - - // Save keyfile - Keymanager::setFileKey( $this->view, $relPath, $this->userId, $recrypted['key'] ); - - // Overwrite the existing file with the encrypted one - $this->view->file_put_contents( $legacyFile['path'], $recrypted['data'] ); - - $size = strlen( $recrypted['data'] ); - - // Add the file to the cache - \OC\Files\Filesystem::putFileInfo( $legacyFile['path'], array( 'encrypted'=>true, 'size' => $size ), '' ); - + $legacyData = $this->view->file_get_contents($legacyFile['path']); + + // decrypt data, generate catfile + $decrypted = Crypt::legacyBlockDecrypt($legacyData, $legacyPassphrase); + + $rawPath = $legacyFile['path']; + + // enable proxy the ensure encryption is handled + \OC_FileProxy::$enabled = true; + + // Open enc file handle for binary writing, with same filename as original plain file + $encHandle = $this->view->fopen( $rawPath, 'wb' ); + + if (is_resource($encHandle)) { + + // write data to stream + fwrite($encHandle, $decrypted); + + // close stream + fclose($encHandle); + } + + // disable proxy to prevent file being encrypted twice + \OC_FileProxy::$enabled = false; } - } - + \OC_FileProxy::$enabled = true; - + // If files were found, return true return true; - } else { - + // If no files were found, return false return false; - } - } - + /** * @brief Return important encryption related paths * @param string $pathName Name of the directory to return the path of * @return string path */ - public function getPath( $pathName ) { - - switch ( $pathName ) { - + public function getPath($pathName) { + + switch ($pathName) { + case 'publicKeyDir': - + return $this->publicKeyDir; - + break; - + case 'encryptionDir': - + return $this->encryptionDir; - + break; - + case 'keyfilesPath': - + return $this->keyfilesPath; - + break; - + case 'publicKeyPath': - + return $this->publicKeyPath; - + break; - + case 'privateKeyPath': - + return $this->privateKeyPath; - + break; - } - + + return false; + + } + + /** + * @brief get path of a file. + * @param int $fileId id of the file + * @return string path of the file + */ + public static function fileIdToPath($fileId) { + + $sql = 'SELECT `path` FROM `*PREFIX*filecache` WHERE `fileid` = ?'; + + $query = \OCP\DB::prepare($sql); + + $result = $query->execute(array($fileId)); + + $path = false; + if (\OCP\DB::isError($result)) { + \OCP\Util::writeLog('Encryption library', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR); + } else { + if ($result->numRows() > 0) { + $row = $result->fetchRow(); + $path = substr($row['path'], strlen('files')); + } + } + + return $path; + + } + + /** + * @brief Filter an array of UIDs to return only ones ready for sharing + * @param array $unfilteredUsers users to be checked for sharing readiness + * @return array as multi-dimensional array. keys: ready, unready + */ + public function filterShareReadyUsers($unfilteredUsers) { + + // This array will collect the filtered IDs + $readyIds = $unreadyIds = array(); + + // Loop through users and create array of UIDs that need new keyfiles + foreach ($unfilteredUsers as $user) { + + $util = new Util($this->view, $user); + + // Check that the user is encryption capable, or is the + // public system user 'ownCloud' (for public shares) + if ( + $user === $this->publicShareKeyId + or $user === $this->recoveryKeyId + or $util->ready() + ) { + + // Construct array of ready UIDs for Keymanager{} + $readyIds[] = $user; + + } else { + + // Construct array of unready UIDs for Keymanager{} + $unreadyIds[] = $user; + + // Log warning; we can't do necessary setup here + // because we don't have the user passphrase + \OCP\Util::writeLog('Encryption library', + '"' . $user . '" is not setup for encryption', \OCP\Util::WARN); + + } + + } + + return array( + 'ready' => $readyIds, + 'unready' => $unreadyIds + ); + + } + + /** + * @brief Decrypt a keyfile without knowing how it was encrypted + * @param string $filePath + * @param string $fileOwner + * @param string $privateKey + * @return bool|string + * @note Checks whether file was encrypted with openssl_seal or + * openssl_encrypt, and decrypts accrdingly + * @note This was used when 2 types of encryption for keyfiles was used, + * but now we've switched to exclusively using openssl_seal() + */ + public function decryptUnknownKeyfile($filePath, $fileOwner, $privateKey) { + + // Get the encrypted keyfile + // NOTE: the keyfile format depends on how it was encrypted! At + // this stage we don't know how it was encrypted + $encKeyfile = Keymanager::getFileKey($this->view, $this->userId, $filePath); + + // We need to decrypt the keyfile + // Has the file been shared yet? + if ( + $this->userId === $fileOwner + && !Keymanager::getShareKey($this->view, $this->userId, $filePath) // NOTE: we can't use isShared() here because it's a post share hook so it always returns true + ) { + + // The file has no shareKey, and its keyfile must be + // decrypted conventionally + $plainKeyfile = Crypt::keyDecrypt($encKeyfile, $privateKey); + + + } else { + + // The file has a shareKey and must use it for decryption + $shareKey = Keymanager::getShareKey($this->view, $this->userId, $filePath); + + $plainKeyfile = Crypt::multiKeyDecrypt($encKeyfile, $shareKey, $privateKey); + + } + + return $plainKeyfile; + + } + + /** + * @brief Encrypt keyfile to multiple users + * @param Session $session + * @param array $users list of users which should be able to access the file + * @param string $filePath path of the file to be shared + * @return bool + */ + public function setSharedFileKeyfiles(Session $session, array $users, $filePath) { + + // Make sure users are capable of sharing + $filteredUids = $this->filterShareReadyUsers($users); + + // If we're attempting to share to unready users + if (!empty($filteredUids['unready'])) { + + \OCP\Util::writeLog('Encryption library', + 'Sharing to these user(s) failed as they are unready for encryption:"' + . print_r($filteredUids['unready'], 1), \OCP\Util::WARN); + + return false; + + } + + // Get public keys for each user, ready for generating sharekeys + $userPubKeys = Keymanager::getPublicKeys($this->view, $filteredUids['ready']); + + // Note proxy status then disable it + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + // Get the current users's private key for decrypting existing keyfile + $privateKey = $session->getPrivateKey(); + + $fileOwner = \OC\Files\Filesystem::getOwner($filePath); + + // Decrypt keyfile + $plainKeyfile = $this->decryptUnknownKeyfile($filePath, $fileOwner, $privateKey); + + // Re-enc keyfile to (additional) sharekeys + $multiEncKey = Crypt::multiKeyEncrypt($plainKeyfile, $userPubKeys); + + // Save the recrypted key to it's owner's keyfiles directory + // Save new sharekeys to all necessary user directory + if ( + !Keymanager::setFileKey($this->view, $filePath, $fileOwner, $multiEncKey['data']) + || !Keymanager::setShareKeys($this->view, $filePath, $multiEncKey['keys']) + ) { + + \OCP\Util::writeLog('Encryption library', + 'Keyfiles could not be saved for users sharing ' . $filePath, \OCP\Util::ERROR); + + return false; + + } + + // Return proxy to original status + \OC_FileProxy::$enabled = $proxyStatus; + + return true; + } + + /** + * @brief Find, sanitise and format users sharing a file + * @note This wraps other methods into a portable bundle + */ + public function getSharingUsersArray($sharingEnabled, $filePath, $currentUserId = false) { + + // Check if key recovery is enabled + if ( + \OC_Appconfig::getValue('files_encryption', 'recoveryAdminEnabled') + && $this->recoveryEnabledForUser() + ) { + + $recoveryEnabled = true; + + } else { + + $recoveryEnabled = false; + + } + + // Make sure that a share key is generated for the owner too + list($owner, $ownerPath) = $this->getUidAndFilename($filePath); + + $userIds = array(); + if ($sharingEnabled) { + + // Find out who, if anyone, is sharing the file + $result = \OCP\Share::getUsersSharingFile($ownerPath, $owner, true); + $userIds = $result['users']; + if ($result['public']) { + $userIds[] = $this->publicShareKeyId; + } + + } + + // If recovery is enabled, add the + // Admin UID to list of users to share to + if ($recoveryEnabled) { + + // Find recoveryAdmin user ID + $recoveryKeyId = \OC_Appconfig::getValue('files_encryption', 'recoveryKeyId'); + + // Add recoveryAdmin to list of users sharing + $userIds[] = $recoveryKeyId; + + } + + // add current user if given + if ($currentUserId !== false) { + + $userIds[] = $currentUserId; + + } + + // Remove duplicate UIDs + $uniqueUserIds = array_unique($userIds); + + return $uniqueUserIds; + + } + + /** + * @brief start migration mode to initially encrypt users data + * @return boolean + */ + public function beginMigration() { + + $return = false; + + $sql = 'UPDATE `*PREFIX*encryption` SET `migration_status` = ? WHERE `uid` = ? and `migration_status` = ?'; + $args = array(self::MIGRATION_IN_PROGRESS, $this->userId, self::MIGRATION_OPEN); + $query = \OCP\DB::prepare($sql); + $result = $query->execute($args); + $manipulatedRows = $result->numRows(); + + if ($manipulatedRows === 1) { + $return = true; + \OCP\Util::writeLog('Encryption library', "Start migration to encryption mode for " . $this->userId, \OCP\Util::INFO); + } else { + \OCP\Util::writeLog('Encryption library', "Could not activate migration mode for " . $this->userId . ". Probably another process already started the initial encryption", \OCP\Util::WARN); + } + + return $return; + } + + /** + * @brief close migration mode after users data has been encrypted successfully + * @return boolean + */ + public function finishMigration() { + + $return = false; + + $sql = 'UPDATE `*PREFIX*encryption` SET `migration_status` = ? WHERE `uid` = ? and `migration_status` = ?'; + $args = array(self::MIGRATION_COMPLETED, $this->userId, self::MIGRATION_IN_PROGRESS); + $query = \OCP\DB::prepare($sql); + $result = $query->execute($args); + $manipulatedRows = $result->numRows(); + + if ($manipulatedRows === 1) { + $return = true; + \OCP\Util::writeLog('Encryption library', "Finish migration successfully for " . $this->userId, \OCP\Util::INFO); + } else { + \OCP\Util::writeLog('Encryption library', "Could not deactivate migration mode for " . $this->userId, \OCP\Util::WARN); + } + + return $return; + } + + /** + * @brief check if files are already migrated to the encryption system + * @return migration status, false = in case of no record + * @note If records are not being returned, check for a hidden space + * at the start of the uid in db + */ + public function getMigrationStatus() { + + $sql = 'SELECT `migration_status` FROM `*PREFIX*encryption` WHERE `uid` = ?'; + + $args = array($this->userId); + + $query = \OCP\DB::prepare($sql); + + $result = $query->execute($args); + + $migrationStatus = array(); + + if (\OCP\DB::isError($result)) { + \OCP\Util::writeLog('Encryption library', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR); + } else { + if ($result->numRows() > 0) { + $row = $result->fetchRow(); + if (isset($row['migration_status'])) { + $migrationStatus[] = $row['migration_status']; + } + } + } + + // If no record is found + if (empty($migrationStatus)) { + \OCP\Util::writeLog('Encryption library', "Could not get migration status for " . $this->userId . ", no record found", \OCP\Util::ERROR); + return false; + // If a record is found + } else { + return (int)$migrationStatus[0]; + } + + } + + /** + * @brief get uid of the owners of the file and the path to the file + * @param string $path Path of the file to check + * @throws \Exception + * @note $shareFilePath must be relative to data/UID/files. Files + * relative to /Shared are also acceptable + * @return array + */ + public function getUidAndFilename($path) { + + $view = new \OC\Files\View($this->userFilesDir); + $fileOwnerUid = $view->getOwner($path); + + // handle public access + if ($this->isPublic) { + $filename = $path; + $fileOwnerUid = $GLOBALS['fileOwner']; + + return array( + $fileOwnerUid, + $filename + ); + } else { + + // Check that UID is valid + if (!\OCP\User::userExists($fileOwnerUid)) { + throw new \Exception( + 'Could not find owner (UID = "' . var_export($fileOwnerUid, 1) . '") of file "' . $path . '"'); + } + + // NOTE: Bah, this dependency should be elsewhere + \OC\Files\Filesystem::initMountPoints($fileOwnerUid); + + // If the file owner is the currently logged in user + if ($fileOwnerUid === $this->userId) { + + // Assume the path supplied is correct + $filename = $path; + + } else { + + $info = $view->getFileInfo($path); + $ownerView = new \OC\Files\View('/' . $fileOwnerUid . '/files'); + + // Fetch real file path from DB + $filename = $ownerView->getPath($info['fileid']); // TODO: Check that this returns a path without including the user data dir + + } + + return array( + $fileOwnerUid, + $filename + ); + } + + + } + + /** + * @brief go recursively through a dir and collect all files and sub files. + * @param string $dir relative to the users files folder + * @return array with list of files relative to the users files folder + */ + public function getAllFiles($dir) { + + $result = array(); + + $content = $this->view->getDirectoryContent(\OC\Files\Filesystem::normalizePath( + $this->userFilesDir . '/' . $dir)); + + // handling for re shared folders + $pathSplit = explode('/', $dir); + + foreach ($content as $c) { + + $sharedPart = $pathSplit[sizeof($pathSplit) - 1]; + $targetPathSplit = array_reverse(explode('/', $c['path'])); + + $path = ''; + + // rebuild path + foreach ($targetPathSplit as $pathPart) { + + if ($pathPart !== $sharedPart) { + + $path = '/' . $pathPart . $path; + + } else { + + break; + + } + + } + + $path = $dir . $path; + + if ($c['type'] === 'dir') { + + $result = array_merge($result, $this->getAllFiles($path)); + + } else { + + $result[] = $path; + + } + } + + return $result; + + } + + /** + * @brief get shares parent. + * @param int $id of the current share + * @return array of the parent + */ + public static function getShareParent($id) { + + $sql = 'SELECT `file_target`, `item_type` FROM `*PREFIX*share` WHERE `id` = ?'; + + $query = \OCP\DB::prepare($sql); + + $result = $query->execute(array($id)); + + $row = array(); + if (\OCP\DB::isError($result)) { + \OCP\Util::writeLog('Encryption library', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR); + } else { + if ($result->numRows() > 0) { + $row = $result->fetchRow(); + } + } + + return $row; + + } + + /** + * @brief get shares parent. + * @param int $id of the current share + * @return array of the parent + */ + public static function getParentFromShare($id) { + + $sql = 'SELECT `parent` FROM `*PREFIX*share` WHERE `id` = ?'; + + $query = \OCP\DB::prepare($sql); + + $result = $query->execute(array($id)); + + $row = array(); + if (\OCP\DB::isError($result)) { + \OCP\Util::writeLog('Encryption library', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR); + } else { + if ($result->numRows() > 0) { + $row = $result->fetchRow(); + } + } + + return $row; + + } + + /** + * @brief get owner of the shared files. + * @param $id + * @internal param int $Id of a share + * @return string owner + */ + public function getOwnerFromSharedFile($id) { + + $query = \OCP\DB::prepare('SELECT `parent`, `uid_owner` FROM `*PREFIX*share` WHERE `id` = ?', 1); + + $result = $query->execute(array($id)); + + $source = array(); + if (\OCP\DB::isError($result)) { + \OCP\Util::writeLog('Encryption library', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR); + } else { + if ($result->numRows() > 0) { + $source = $result->fetchRow(); + } + } + + $fileOwner = false; + + if (isset($source['parent'])) { + + $parent = $source['parent']; + + while (isset($parent)) { + + $query = \OCP\DB::prepare('SELECT `parent`, `uid_owner` FROM `*PREFIX*share` WHERE `id` = ?', 1); + + $result = $query->execute(array($parent)); + + $item = array(); + if (\OCP\DB::isError($result)) { + \OCP\Util::writeLog('Encryption library', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR); + } else { + if ($result->numRows() > 0) { + $item = $result->fetchRow(); + } + } + + if (isset($item['parent'])) { + + $parent = $item['parent']; + + } else { + + $fileOwner = $item['uid_owner']; + + break; + + } + } + + } else { + + $fileOwner = $source['uid_owner']; + + } + + return $fileOwner; + + } + + /** + * @return string + */ + public function getUserId() { + return $this->userId; + } + + /** + * @return string + */ + public function getUserFilesDir() { + return $this->userFilesDir; + } + + /** + * @param $password + * @return bool + */ + public function checkRecoveryPassword($password) { + + $result = false; + $pathKey = '/owncloud_private_key/' . $this->recoveryKeyId . ".private.key"; + + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + $recoveryKey = $this->view->file_get_contents($pathKey); + + $decryptedRecoveryKey = Crypt::decryptPrivateKey($recoveryKey, $password); + + if ($decryptedRecoveryKey) { + $result = true; + } + + \OC_FileProxy::$enabled = $proxyStatus; + + + return $result; + } + + /** + * @return string + */ + public function getRecoveryKeyId() { + return $this->recoveryKeyId; + } + + /** + * @brief add recovery key to all encrypted files + */ + public function addRecoveryKeys($path = '/') { + $dirContent = $this->view->getDirectoryContent($this->keyfilesPath . $path); + foreach ($dirContent as $item) { + // get relative path from files_encryption/keyfiles/ + $filePath = substr($item['path'], strlen('files_encryption/keyfiles')); + if ($item['type'] === 'dir') { + $this->addRecoveryKeys($filePath . '/'); + } else { + $session = new \OCA\Encryption\Session(new \OC_FilesystemView('/')); + $sharingEnabled = \OCP\Share::isEnabled(); + // remove '.key' extension from path e.g. 'file.txt.key' to 'file.txt' + $file = substr($filePath, 0, -4); + $usersSharing = $this->getSharingUsersArray($sharingEnabled, $file); + $this->setSharedFileKeyfiles($session, $usersSharing, $file); + } + } + } + + /** + * @brief remove recovery key to all encrypted files + */ + public function removeRecoveryKeys($path = '/') { + $dirContent = $this->view->getDirectoryContent($this->keyfilesPath . $path); + foreach ($dirContent as $item) { + // get relative path from files_encryption/keyfiles + $filePath = substr($item['path'], strlen('files_encryption/keyfiles')); + if ($item['type'] === 'dir') { + $this->removeRecoveryKeys($filePath . '/'); + } else { + // remove '.key' extension from path e.g. 'file.txt.key' to 'file.txt' + $file = substr($filePath, 0, -4); + $this->view->unlink($this->shareKeysPath . '/' . $file . '.' . $this->recoveryKeyId . '.shareKey'); + } + } + } + + /** + * @brief decrypt given file with recovery key and encrypt it again to the owner and his new key + * @param string $file + * @param string $privateKey recovery key to decrypt the file + */ + private function recoverFile($file, $privateKey) { + + $sharingEnabled = \OCP\Share::isEnabled(); + + // Find out who, if anyone, is sharing the file + if ($sharingEnabled) { + $result = \OCP\Share::getUsersSharingFile($file, $this->userId, true); + $userIds = $result['users']; + $userIds[] = $this->recoveryKeyId; + if ($result['public']) { + $userIds[] = $this->publicShareKeyId; + } + } else { + $userIds = array( + $this->userId, + $this->recoveryKeyId + ); + } + $filteredUids = $this->filterShareReadyUsers($userIds); + + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + //decrypt file key + $encKeyfile = $this->view->file_get_contents($this->keyfilesPath . $file . ".key"); + $shareKey = $this->view->file_get_contents( + $this->shareKeysPath . $file . "." . $this->recoveryKeyId . ".shareKey"); + $plainKeyfile = Crypt::multiKeyDecrypt($encKeyfile, $shareKey, $privateKey); + // encrypt file key again to all users, this time with the new public key for the recovered use + $userPubKeys = Keymanager::getPublicKeys($this->view, $filteredUids['ready']); + $multiEncKey = Crypt::multiKeyEncrypt($plainKeyfile, $userPubKeys); + + // write new keys to filesystem TDOO! + $this->view->file_put_contents($this->keyfilesPath . $file . '.key', $multiEncKey['data']); + foreach ($multiEncKey['keys'] as $userId => $shareKey) { + $shareKeyPath = $this->shareKeysPath . $file . '.' . $userId . '.shareKey'; + $this->view->file_put_contents($shareKeyPath, $shareKey); + } + + // Return proxy to original status + \OC_FileProxy::$enabled = $proxyStatus; + } + + /** + * @brief collect all files and recover them one by one + * @param string $path to look for files keys + * @param string $privateKey private recovery key which is used to decrypt the files + */ + private function recoverAllFiles($path, $privateKey) { + $dirContent = $this->view->getDirectoryContent($this->keyfilesPath . $path); + foreach ($dirContent as $item) { + // get relative path from files_encryption/keyfiles + $filePath = substr($item['path'], strlen('files_encryption/keyfiles')); + if ($item['type'] === 'dir') { + $this->recoverAllFiles($filePath . '/', $privateKey); + } else { + // remove '.key' extension from path e.g. 'file.txt.key' to 'file.txt' + $file = substr($filePath, 0, -4); + $this->recoverFile($file, $privateKey); + } + } + } + + /** + * @brief recover users files in case of password lost + * @param string $recoveryPassword + */ + public function recoverUsersFiles($recoveryPassword) { + + // Disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + $encryptedKey = $this->view->file_get_contents( + '/owncloud_private_key/' . $this->recoveryKeyId . '.private.key'); + $privateKey = Crypt::decryptPrivateKey($encryptedKey, $recoveryPassword); + + \OC_FileProxy::$enabled = $proxyStatus; + + $this->recoverAllFiles('/', $privateKey); + } + + /** + * Get the path including the storage mount point + * @param int $id + * @return string the path including the mount point like AmazonS3/folder/file.txt + */ + public function getPathWithMountPoint($id) { + list($storage, $internalPath) = \OC\Files\Cache\Cache::getById($id); + $mount = \OC\Files\Filesystem::getMountByStorageId($storage); + $mountPoint = $mount[0]->getMountPoint(); + $path = \OC\Files\Filesystem::normalizePath($mountPoint . '/' . $internalPath); + + // reformat the path to be relative e.g. /user/files/folder becomes /folder/ + $relativePath = \OCA\Encryption\Helper::stripUserFilesPath($path); + + return $relativePath; } } diff --git a/apps/files_encryption/settings-admin.php b/apps/files_encryption/settings-admin.php new file mode 100644 index 0000000000..5367605898 --- /dev/null +++ b/apps/files_encryption/settings-admin.php @@ -0,0 +1,23 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +\OC_Util::checkAdminUser(); + +$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'); + +$tmpl->assign('recoveryEnabled', $recoveryAdminEnabled); + +\OCP\Util::addscript('files_encryption', 'settings-admin'); +\OCP\Util::addscript('core', 'multiselect'); + +return $tmpl->fetchPage(); diff --git a/apps/files_encryption/settings-personal.php b/apps/files_encryption/settings-personal.php index af0273cfdc..fddc3ea5ee 100644 --- a/apps/files_encryption/settings-personal.php +++ b/apps/files_encryption/settings-personal.php @@ -6,12 +6,34 @@ * See the COPYING-README file. */ -$tmpl = new OCP\Template( 'files_encryption', 'settings-personal'); +// Add CSS stylesheet +\OC_Util::addStyle('files_encryption', 'settings-personal'); -$blackList = explode( ',', \OCP\Config::getAppValue( 'files_encryption', 'type_blacklist', 'jpg,png,jpeg,avi,mpg,mpeg,mkv,mp3,oga,ogv,ogg' ) ); +$tmpl = new OCP\Template('files_encryption', 'settings-personal'); -$tmpl->assign( 'blacklist', $blackList ); +$user = \OCP\USER::getUser(); +$view = new \OC_FilesystemView('/'); +$util = new \OCA\Encryption\Util($view, $user); +$session = new \OCA\Encryption\Session($view); -return $tmpl->fetchPage(); +$privateKeySet = ($session->getPrivateKey() !== false) ? true : false; + +$recoveryAdminEnabled = OC_Appconfig::getValue('files_encryption', 'recoveryAdminEnabled'); +$recoveryEnabledForUser = $util->recoveryEnabledForUser(); + +$result = false; + +if ($recoveryAdminEnabled || !$privateKeySet) { + + \OCP\Util::addscript('files_encryption', 'settings-personal'); + \OCP\Util::addScript('settings', 'personal'); + + $tmpl->assign('recoveryEnabled', $recoveryAdminEnabled); + $tmpl->assign('recoveryEnabledForUser', $recoveryEnabledForUser); + $tmpl->assign('privateKeySet', $privateKeySet); + + $result = $tmpl->fetchPage(); +} + +return $result; -return null; diff --git a/apps/files_encryption/settings.php b/apps/files_encryption/settings.php deleted file mode 100644 index d1260f44e9..0000000000 --- a/apps/files_encryption/settings.php +++ /dev/null @@ -1,21 +0,0 @@ - - * This file is licensed under the Affero General Public License version 3 or - * later. - * See the COPYING-README file. - */ - -\OC_Util::checkAdminUser(); - -$tmpl = new OCP\Template( 'files_encryption', 'settings' ); - -$blackList = explode( ',', \OCP\Config::getAppValue( 'files_encryption', 'type_blacklist', 'jpg,png,jpeg,avi,mpg,mpeg,mkv,mp3,oga,ogv,ogg' ) ); - -$tmpl->assign( 'blacklist', $blackList ); -$tmpl->assign( 'encryption_mode', \OC_Appconfig::getValue( 'files_encryption', 'mode', 'none' ) ); - -\OCP\Util::addscript( 'files_encryption', 'settings' ); -\OCP\Util::addscript( 'core', 'multiselect' ); - -return $tmpl->fetchPage(); diff --git a/apps/files_encryption/templates/invalid_private_key.php b/apps/files_encryption/templates/invalid_private_key.php new file mode 100644 index 0000000000..5c086d6514 --- /dev/null +++ b/apps/files_encryption/templates/invalid_private_key.php @@ -0,0 +1,10 @@ +
    +
  • + + + t('Your private key is not valid! Maybe the your password was changed from outside.')); ?> +
    + t('You can unlock your private key in your ')); ?> t('personal settings')); ?>. +
    +
  • +
diff --git a/apps/files_encryption/templates/settings-admin.php b/apps/files_encryption/templates/settings-admin.php new file mode 100644 index 0000000000..c420b006c4 --- /dev/null +++ b/apps/files_encryption/templates/settings-admin.php @@ -0,0 +1,58 @@ +
+
+ +

+ t('Encryption')); ?> +
+

+ +

+ t("Enable encryption passwords recovery key (allow sharing to recovery key):")); ?> +
+
+ + +
+ /> + t("Enabled")); ?> +
+ + /> + t("Disabled")); ?> +

+

+ +

+ t("Change encryption passwords recovery key:")); ?> +

+ /> + +
+ /> + +
+ + +

+
+
diff --git a/apps/files_encryption/templates/settings-personal.php b/apps/files_encryption/templates/settings-personal.php index 5f0accaed5..3851245320 100644 --- a/apps/files_encryption/templates/settings-personal.php +++ b/apps/files_encryption/templates/settings-personal.php @@ -1,22 +1,70 @@
- t( 'Encryption' )); ?> + t( 'Encryption' ) ); ?> -

- t( 'File encryption is enabled.' )); ?> -

- -

- t( 'The following file types will not be encrypted:' )); ?> -

-
    - -
  • - -
  • - -
+ + +

+ + +
+ t( "Set your old private key password to your current log-in password." ) ); ?> + t( " If you don't remember your old password you can ask your administrator to recover your files." ) ); + endif; ?> + +
+ + +
+ + +
+ + +

+ +
+ + +

+ +
+ t( "Enabling this option will allow you to reobtain access to your encrypted files in case of password loss" ) ); ?> +
+ /> + t( "Enabled" ) ); ?> +
+ + /> + t( "Disabled" ) ); ?> +

t( 'File recovery settings updated' ) ); ?>
+
t( 'Could not update file recovery' ) ); ?>
+

+ + +
diff --git a/apps/files_encryption/templates/settings.php b/apps/files_encryption/templates/settings.php deleted file mode 100644 index b873d7f5aa..0000000000 --- a/apps/files_encryption/templates/settings.php +++ /dev/null @@ -1,20 +0,0 @@ -
-
- -

- t( 'Encryption' )); ?> - - t( "Exclude the following file types from encryption:" )); ?> -
- - -

-
-
diff --git a/apps/files_encryption/test/crypt.php b/apps/files_encryption/test/crypt.php deleted file mode 100755 index aa87ec3282..0000000000 --- a/apps/files_encryption/test/crypt.php +++ /dev/null @@ -1,667 +0,0 @@ -, and - * Robin Appelman - * This file is licensed under the Affero General Public License version 3 or - * later. - * See the COPYING-README file. - */ - -//require_once "PHPUnit/Framework/TestCase.php"; -require_once realpath( dirname(__FILE__).'/../../../3rdparty/Crypt_Blowfish/Blowfish.php' ); -require_once realpath( dirname(__FILE__).'/../../../lib/base.php' ); -require_once realpath( dirname(__FILE__).'/../lib/crypt.php' ); -require_once realpath( dirname(__FILE__).'/../lib/keymanager.php' ); -require_once realpath( dirname(__FILE__).'/../lib/proxy.php' ); -require_once realpath( dirname(__FILE__).'/../lib/stream.php' ); -require_once realpath( dirname(__FILE__).'/../lib/util.php' ); -require_once realpath( dirname(__FILE__).'/../appinfo/app.php' ); - -use OCA\Encryption; - -// This has to go here because otherwise session errors arise, and the private -// encryption key needs to be saved in the session -\OC_User::login( 'admin', 'admin' ); - -/** - * @note It would be better to use Mockery here for mocking out the session - * handling process, and isolate calls to session class and data from the unit - * tests relating to them (stream etc.). However getting mockery to work and - * overload classes whilst also using the OC autoloader is difficult due to - * load order Pear errors. - */ - -class Test_Crypt extends \PHPUnit_Framework_TestCase { - - function setUp() { - - // set content for encrypting / decrypting in tests - $this->dataLong = file_get_contents( realpath( dirname(__FILE__).'/../lib/crypt.php' ) ); - $this->dataShort = 'hats'; - $this->dataUrl = realpath( dirname(__FILE__).'/../lib/crypt.php' ); - $this->legacyData = realpath( dirname(__FILE__).'/legacy-text.txt' ); - $this->legacyEncryptedData = realpath( dirname(__FILE__).'/legacy-encrypted-text.txt' ); - $this->randomKey = Encryption\Crypt::generateKey(); - - $keypair = Encryption\Crypt::createKeypair(); - $this->genPublicKey = $keypair['publicKey']; - $this->genPrivateKey = $keypair['privateKey']; - - $this->view = new \OC_FilesystemView( '/' ); - - \OC_User::setUserId( 'admin' ); - $this->userId = 'admin'; - $this->pass = 'admin'; - - \OC_Filesystem::init( '/' ); - \OC_Filesystem::mount( 'OC_Filestorage_Local', array('datadir' => \OC_User::getHome($this->userId)), '/' ); - - } - - function tearDown() { - - } - - function testGenerateKey() { - - # TODO: use more accurate (larger) string length for test confirmation - - $key = Encryption\Crypt::generateKey(); - - $this->assertTrue( strlen( $key ) > 16 ); - - } - - function testGenerateIv() { - - $iv = Encryption\Crypt::generateIv(); - - $this->assertEquals( 16, strlen( $iv ) ); - - return $iv; - - } - - /** - * @depends testGenerateIv - */ - function testConcatIv( $iv ) { - - $catFile = Encryption\Crypt::concatIv( $this->dataLong, $iv ); - - // Fetch encryption metadata from end of file - $meta = substr( $catFile, -22 ); - - $identifier = substr( $meta, 0, 6); - - // Fetch IV from end of file - $foundIv = substr( $meta, 6 ); - - $this->assertEquals( '00iv00', $identifier ); - - $this->assertEquals( $iv, $foundIv ); - - // Remove IV and IV identifier text to expose encrypted content - $data = substr( $catFile, 0, -22 ); - - $this->assertEquals( $this->dataLong, $data ); - - return array( - 'iv' => $iv - , 'catfile' => $catFile - ); - - } - - /** - * @depends testConcatIv - */ - function testSplitIv( $testConcatIv ) { - - // Split catfile into components - $splitCatfile = Encryption\Crypt::splitIv( $testConcatIv['catfile'] ); - - // Check that original IV and split IV match - $this->assertEquals( $testConcatIv['iv'], $splitCatfile['iv'] ); - - // Check that original data and split data match - $this->assertEquals( $this->dataLong, $splitCatfile['encrypted'] ); - - } - - function testAddPadding() { - - $padded = Encryption\Crypt::addPadding( $this->dataLong ); - - $padding = substr( $padded, -2 ); - - $this->assertEquals( 'xx' , $padding ); - - return $padded; - - } - - /** - * @depends testAddPadding - */ - function testRemovePadding( $padded ) { - - $noPadding = Encryption\Crypt::RemovePadding( $padded ); - - $this->assertEquals( $this->dataLong, $noPadding ); - - } - - function testEncrypt() { - - $random = openssl_random_pseudo_bytes( 13 ); - - $iv = substr( base64_encode( $random ), 0, -4 ); // i.e. E5IG033j+mRNKrht - - $crypted = Encryption\Crypt::encrypt( $this->dataUrl, $iv, 'hat' ); - - $this->assertNotEquals( $this->dataUrl, $crypted ); - - } - - function testDecrypt() { - - $random = openssl_random_pseudo_bytes( 13 ); - - $iv = substr( base64_encode( $random ), 0, -4 ); // i.e. E5IG033j+mRNKrht - - $crypted = Encryption\Crypt::encrypt( $this->dataUrl, $iv, 'hat' ); - - $decrypt = Encryption\Crypt::decrypt( $crypted, $iv, 'hat' ); - - $this->assertEquals( $this->dataUrl, $decrypt ); - - } - - function testSymmetricEncryptFileContent() { - - # TODO: search in keyfile for actual content as IV will ensure this test always passes - - $crypted = Encryption\Crypt::symmetricEncryptFileContent( $this->dataShort, 'hat' ); - - $this->assertNotEquals( $this->dataShort, $crypted ); - - - $decrypt = Encryption\Crypt::symmetricDecryptFileContent( $crypted, 'hat' ); - - $this->assertEquals( $this->dataShort, $decrypt ); - - } - - // These aren't used for now -// function testSymmetricBlockEncryptShortFileContent() { -// -// $crypted = Encryption\Crypt::symmetricBlockEncryptFileContent( $this->dataShort, $this->randomKey ); -// -// $this->assertNotEquals( $this->dataShort, $crypted ); -// -// -// $decrypt = Encryption\Crypt::symmetricBlockDecryptFileContent( $crypted, $this->randomKey ); -// -// $this->assertEquals( $this->dataShort, $decrypt ); -// -// } -// -// function testSymmetricBlockEncryptLongFileContent() { -// -// $crypted = Encryption\Crypt::symmetricBlockEncryptFileContent( $this->dataLong, $this->randomKey ); -// -// $this->assertNotEquals( $this->dataLong, $crypted ); -// -// -// $decrypt = Encryption\Crypt::symmetricBlockDecryptFileContent( $crypted, $this->randomKey ); -// -// $this->assertEquals( $this->dataLong, $decrypt ); -// -// } - - function testSymmetricStreamEncryptShortFileContent() { - - $filename = 'tmp-'.time(); - - $cryptedFile = file_put_contents( 'crypt://' . $filename, $this->dataShort ); - - // Test that data was successfully written - $this->assertTrue( is_int( $cryptedFile ) ); - - - // Get file contents without using any wrapper to get it's actual contents on disk - $retreivedCryptedFile = $this->view->file_get_contents( $this->userId . '/files/' . $filename ); - - // Check that the file was encrypted before being written to disk - $this->assertNotEquals( $this->dataShort, $retreivedCryptedFile ); - - // Get private key - $encryptedPrivateKey = Encryption\Keymanager::getPrivateKey( $this->view, $this->userId ); - - $decryptedPrivateKey = Encryption\Crypt::symmetricDecryptFileContent( $encryptedPrivateKey, $this->pass ); - - - // Get keyfile - $encryptedKeyfile = Encryption\Keymanager::getFileKey( $this->view, $this->userId, $filename ); - - $decryptedKeyfile = Encryption\Crypt::keyDecrypt( $encryptedKeyfile, $decryptedPrivateKey ); - - - // Manually decrypt - $manualDecrypt = Encryption\Crypt::symmetricBlockDecryptFileContent( $retreivedCryptedFile, $decryptedKeyfile ); - - // Check that decrypted data matches - $this->assertEquals( $this->dataShort, $manualDecrypt ); - - } - - /** - * @brief Test that data that is written by the crypto stream wrapper - * @note Encrypted data is manually prepared and decrypted here to avoid dependency on success of stream_read - * @note If this test fails with truncate content, check that enough array slices are being rejoined to form $e, as the crypt.php file may have gotten longer and broken the manual - * reassembly of its data - */ - function testSymmetricStreamEncryptLongFileContent() { - - // Generate a a random filename - $filename = 'tmp-'.time(); - - // Save long data as encrypted file using stream wrapper - $cryptedFile = file_put_contents( 'crypt://' . $filename, $this->dataLong.$this->dataLong ); - - // Test that data was successfully written - $this->assertTrue( is_int( $cryptedFile ) ); - - // Get file contents without using any wrapper to get it's actual contents on disk - $retreivedCryptedFile = $this->view->file_get_contents( $this->userId . '/files/' . $filename ); - -// echo "\n\n\$retreivedCryptedFile = $retreivedCryptedFile\n\n"; - - // Check that the file was encrypted before being written to disk - $this->assertNotEquals( $this->dataLong.$this->dataLong, $retreivedCryptedFile ); - - // Manuallly split saved file into separate IVs and encrypted chunks - $r = preg_split('/(00iv00.{16,18})/', $retreivedCryptedFile, NULL, PREG_SPLIT_DELIM_CAPTURE); - - //print_r($r); - - // Join IVs and their respective data chunks - $e = array( $r[0].$r[1], $r[2].$r[3], $r[4].$r[5], $r[6].$r[7], $r[8].$r[9], $r[10].$r[11], $r[12].$r[13] );//.$r[11], $r[12].$r[13], $r[14] ); - - //print_r($e); - - - // Get private key - $encryptedPrivateKey = Encryption\Keymanager::getPrivateKey( $this->view, $this->userId ); - - $decryptedPrivateKey = Encryption\Crypt::symmetricDecryptFileContent( $encryptedPrivateKey, $this->pass ); - - - // Get keyfile - $encryptedKeyfile = Encryption\Keymanager::getFileKey( $this->view, $this->userId, $filename ); - - $decryptedKeyfile = Encryption\Crypt::keyDecrypt( $encryptedKeyfile, $decryptedPrivateKey ); - - - // Set var for reassembling decrypted content - $decrypt = ''; - - // Manually decrypt chunk - foreach ($e as $e) { - -// echo "\n\$e = $e"; - - $chunkDecrypt = Encryption\Crypt::symmetricDecryptFileContent( $e, $decryptedKeyfile ); - - // Assemble decrypted chunks - $decrypt .= $chunkDecrypt; - -// echo "\n\$chunkDecrypt = $chunkDecrypt"; - - } - -// echo "\n\$decrypt = $decrypt"; - - $this->assertEquals( $this->dataLong.$this->dataLong, $decrypt ); - - // Teardown - - $this->view->unlink( $filename ); - - Encryption\Keymanager::deleteFileKey( $filename ); - - } - - /** - * @brief Test that data that is read by the crypto stream wrapper - */ - function testSymmetricStreamDecryptShortFileContent() { - - $filename = 'tmp-'.time(); - - // Save long data as encrypted file using stream wrapper - $cryptedFile = file_put_contents( 'crypt://' . $filename, $this->dataShort ); - - // Test that data was successfully written - $this->assertTrue( is_int( $cryptedFile ) ); - - - // Get file contents without using any wrapper to get it's actual contents on disk - $retreivedCryptedFile = $this->view->file_get_contents( $this->userId . '/files/' . $filename ); - - $decrypt = file_get_contents( 'crypt://' . $filename ); - - $this->assertEquals( $this->dataShort, $decrypt ); - - } - - function testSymmetricStreamDecryptLongFileContent() { - - $filename = 'tmp-'.time(); - - // Save long data as encrypted file using stream wrapper - $cryptedFile = file_put_contents( 'crypt://' . $filename, $this->dataLong ); - - // Test that data was successfully written - $this->assertTrue( is_int( $cryptedFile ) ); - - - // Get file contents without using any wrapper to get it's actual contents on disk - $retreivedCryptedFile = $this->view->file_get_contents( $this->userId . '/files/' . $filename ); - - $decrypt = file_get_contents( 'crypt://' . $filename ); - - $this->assertEquals( $this->dataLong, $decrypt ); - - } - - // Is this test still necessary? -// function testSymmetricBlockStreamDecryptFileContent() { -// -// \OC_User::setUserId( 'admin' ); -// -// // Disable encryption proxy to prevent unwanted en/decryption -// \OC_FileProxy::$enabled = false; -// -// $cryptedFile = file_put_contents( 'crypt://' . '/blockEncrypt', $this->dataUrl ); -// -// // Disable encryption proxy to prevent unwanted en/decryption -// \OC_FileProxy::$enabled = false; -// -// echo "\n\n\$cryptedFile = " . $this->view->file_get_contents( '/blockEncrypt' ); -// -// $retreivedCryptedFile = file_get_contents( 'crypt://' . '/blockEncrypt' ); -// -// $this->assertEquals( $this->dataUrl, $retreivedCryptedFile ); -// -// \OC_FileProxy::$enabled = false; -// -// } - - function testSymmetricEncryptFileContentKeyfile() { - - # TODO: search in keyfile for actual content as IV will ensure this test always passes - - $crypted = Encryption\Crypt::symmetricEncryptFileContentKeyfile( $this->dataUrl ); - - $this->assertNotEquals( $this->dataUrl, $crypted['encrypted'] ); - - - $decrypt = Encryption\Crypt::symmetricDecryptFileContent( $crypted['encrypted'], $crypted['key'] ); - - $this->assertEquals( $this->dataUrl, $decrypt ); - - } - - function testIsEncryptedContent() { - - $this->assertFalse( Encryption\Crypt::isCatfile( $this->dataUrl ) ); - - $this->assertFalse( Encryption\Crypt::isCatfile( $this->legacyEncryptedData ) ); - - $keyfileContent = Encryption\Crypt::symmetricEncryptFileContent( $this->dataUrl, 'hat' ); - - $this->assertTrue( Encryption\Crypt::isCatfile( $keyfileContent ) ); - - } - - function testMultiKeyEncrypt() { - - # TODO: search in keyfile for actual content as IV will ensure this test always passes - - $pair1 = Encryption\Crypt::createKeypair(); - - $this->assertEquals( 2, count( $pair1 ) ); - - $this->assertTrue( strlen( $pair1['publicKey'] ) > 1 ); - - $this->assertTrue( strlen( $pair1['privateKey'] ) > 1 ); - - - $crypted = Encryption\Crypt::multiKeyEncrypt( $this->dataUrl, array( $pair1['publicKey'] ) ); - - $this->assertNotEquals( $this->dataUrl, $crypted['encrypted'] ); - - - $decrypt = Encryption\Crypt::multiKeyDecrypt( $crypted['encrypted'], $crypted['keys'][0], $pair1['privateKey'] ); - - $this->assertEquals( $this->dataUrl, $decrypt ); - - } - - function testKeyEncrypt() { - - // Generate keypair - $pair1 = Encryption\Crypt::createKeypair(); - - // Encrypt data - $crypted = Encryption\Crypt::keyEncrypt( $this->dataUrl, $pair1['publicKey'] ); - - $this->assertNotEquals( $this->dataUrl, $crypted ); - - // Decrypt data - $decrypt = Encryption\Crypt::keyDecrypt( $crypted, $pair1['privateKey'] ); - - $this->assertEquals( $this->dataUrl, $decrypt ); - - } - - // What is the point of this test? It doesn't use keyEncryptKeyfile() - function testKeyEncryptKeyfile() { - - # TODO: Don't repeat encryption from previous tests, use PHPUnit test interdependency instead - - // Generate keypair - $pair1 = Encryption\Crypt::createKeypair(); - - // Encrypt plain data, generate keyfile & encrypted file - $cryptedData = Encryption\Crypt::symmetricEncryptFileContentKeyfile( $this->dataUrl ); - - // Encrypt keyfile - $cryptedKey = Encryption\Crypt::keyEncrypt( $cryptedData['key'], $pair1['publicKey'] ); - - // Decrypt keyfile - $decryptKey = Encryption\Crypt::keyDecrypt( $cryptedKey, $pair1['privateKey'] ); - - // Decrypt encrypted file - $decryptData = Encryption\Crypt::symmetricDecryptFileContent( $cryptedData['encrypted'], $decryptKey ); - - $this->assertEquals( $this->dataUrl, $decryptData ); - - } - - /** - * @brief test functionality of keyEncryptKeyfile() and - * keyDecryptKeyfile() - */ - function testKeyDecryptKeyfile() { - - $encrypted = Encryption\Crypt::keyEncryptKeyfile( $this->dataShort, $this->genPublicKey ); - - $this->assertNotEquals( $encrypted['data'], $this->dataShort ); - - $decrypted = Encryption\Crypt::keyDecryptKeyfile( $encrypted['data'], $encrypted['key'], $this->genPrivateKey ); - - $this->assertEquals( $decrypted, $this->dataShort ); - - } - - - /** - * @brief test encryption using legacy blowfish method - */ - function testLegacyEncryptShort() { - - $crypted = Encryption\Crypt::legacyEncrypt( $this->dataShort, $this->pass ); - - $this->assertNotEquals( $this->dataShort, $crypted ); - - # TODO: search inencrypted text for actual content to ensure it - # genuine transformation - - return $crypted; - - } - - /** - * @brief test decryption using legacy blowfish method - * @depends testLegacyEncryptShort - */ - function testLegacyDecryptShort( $crypted ) { - - $decrypted = Encryption\Crypt::legacyDecrypt( $crypted, $this->pass ); - - $this->assertEquals( $this->dataShort, $decrypted ); - - } - - /** - * @brief test encryption using legacy blowfish method - */ - function testLegacyEncryptLong() { - - $crypted = Encryption\Crypt::legacyEncrypt( $this->dataLong, $this->pass ); - - $this->assertNotEquals( $this->dataLong, $crypted ); - - # TODO: search inencrypted text for actual content to ensure it - # genuine transformation - - return $crypted; - - } - - /** - * @brief test decryption using legacy blowfish method - * @depends testLegacyEncryptLong - */ - function testLegacyDecryptLong( $crypted ) { - - $decrypted = Encryption\Crypt::legacyDecrypt( $crypted, $this->pass ); - - $this->assertEquals( $this->dataLong, $decrypted ); - - } - - /** - * @brief test generation of legacy encryption key - * @depends testLegacyDecryptShort - */ - function testLegacyCreateKey() { - - // Create encrypted key - $encKey = Encryption\Crypt::legacyCreateKey( $this->pass ); - - // Decrypt key - $key = Encryption\Crypt::legacyDecrypt( $encKey, $this->pass ); - - $this->assertTrue( is_numeric( $key ) ); - - // Check that key is correct length - $this->assertEquals( 20, strlen( $key ) ); - - } - - /** - * @brief test decryption using legacy blowfish method - * @depends testLegacyEncryptLong - */ - function testLegacyKeyRecryptKeyfileEncrypt( $crypted ) { - - $recrypted = Encryption\Crypt::LegacyKeyRecryptKeyfile( $crypted, $this->pass, $this->genPublicKey, $this->pass ); - - $this->assertNotEquals( $this->dataLong, $recrypted['data'] ); - - return $recrypted; - - # TODO: search inencrypted text for actual content to ensure it - # genuine transformation - - } - -// function testEncryption(){ -// -// $key=uniqid(); -// $file=OC::$SERVERROOT.'/3rdparty/MDB2.php'; -// $source=file_get_contents($file); //nice large text file -// $encrypted=OC_Encryption\Crypt::encrypt($source,$key); -// $decrypted=OC_Encryption\Crypt::decrypt($encrypted,$key); -// $decrypted=rtrim($decrypted, "\0"); -// $this->assertNotEquals($encrypted,$source); -// $this->assertEquals($decrypted,$source); -// -// $chunk=substr($source,0,8192); -// $encrypted=OC_Encryption\Crypt::encrypt($chunk,$key); -// $this->assertEquals(strlen($chunk),strlen($encrypted)); -// $decrypted=OC_Encryption\Crypt::decrypt($encrypted,$key); -// $decrypted=rtrim($decrypted, "\0"); -// $this->assertEquals($decrypted,$chunk); -// -// $encrypted=OC_Encryption\Crypt::blockEncrypt($source,$key); -// $decrypted=OC_Encryption\Crypt::blockDecrypt($encrypted,$key); -// $this->assertNotEquals($encrypted,$source); -// $this->assertEquals($decrypted,$source); -// -// $tmpFileEncrypted=OCP\Files::tmpFile(); -// OC_Encryption\Crypt::encryptfile($file,$tmpFileEncrypted,$key); -// $encrypted=file_get_contents($tmpFileEncrypted); -// $decrypted=OC_Encryption\Crypt::blockDecrypt($encrypted,$key); -// $this->assertNotEquals($encrypted,$source); -// $this->assertEquals($decrypted,$source); -// -// $tmpFileDecrypted=OCP\Files::tmpFile(); -// OC_Encryption\Crypt::decryptfile($tmpFileEncrypted,$tmpFileDecrypted,$key); -// $decrypted=file_get_contents($tmpFileDecrypted); -// $this->assertEquals($decrypted,$source); -// -// $file=OC::$SERVERROOT.'/core/img/weather-clear.png'; -// $source=file_get_contents($file); //binary file -// $encrypted=OC_Encryption\Crypt::encrypt($source,$key); -// $decrypted=OC_Encryption\Crypt::decrypt($encrypted,$key); -// $decrypted=rtrim($decrypted, "\0"); -// $this->assertEquals($decrypted,$source); -// -// $encrypted=OC_Encryption\Crypt::blockEncrypt($source,$key); -// $decrypted=OC_Encryption\Crypt::blockDecrypt($encrypted,$key); -// $this->assertEquals($decrypted,$source); -// -// } -// -// function testBinary(){ -// $key=uniqid(); -// -// $file=__DIR__.'/binary'; -// $source=file_get_contents($file); //binary file -// $encrypted=OC_Encryption\Crypt::encrypt($source,$key); -// $decrypted=OC_Encryption\Crypt::decrypt($encrypted,$key); -// -// $decrypted=rtrim($decrypted, "\0"); -// $this->assertEquals($decrypted,$source); -// -// $encrypted=OC_Encryption\Crypt::blockEncrypt($source,$key); -// $decrypted=OC_Encryption\Crypt::blockDecrypt($encrypted,$key,strlen($source)); -// $this->assertEquals($decrypted,$source); -// } - -} diff --git a/apps/files_encryption/test/keymanager.php b/apps/files_encryption/test/keymanager.php deleted file mode 100644 index bf453fe316..0000000000 --- a/apps/files_encryption/test/keymanager.php +++ /dev/null @@ -1,130 +0,0 @@ - - * This file is licensed under the Affero General Public License version 3 or - * later. - * See the COPYING-README file. - */ - -//require_once "PHPUnit/Framework/TestCase.php"; -require_once realpath( dirname(__FILE__).'/../../../lib/base.php' ); -require_once realpath( dirname(__FILE__).'/../lib/crypt.php' ); -require_once realpath( dirname(__FILE__).'/../lib/keymanager.php' ); -require_once realpath( dirname(__FILE__).'/../lib/proxy.php' ); -require_once realpath( dirname(__FILE__).'/../lib/stream.php' ); -require_once realpath( dirname(__FILE__).'/../lib/util.php' ); -require_once realpath( dirname(__FILE__).'/../appinfo/app.php' ); - -use OCA\Encryption; - -// This has to go here because otherwise session errors arise, and the private -// encryption key needs to be saved in the session -\OC_User::login( 'admin', 'admin' ); - -class Test_Keymanager extends \PHPUnit_Framework_TestCase { - - function setUp() { - - \OC_FileProxy::$enabled = false; - - // set content for encrypting / decrypting in tests - $this->dataLong = file_get_contents( realpath( dirname(__FILE__).'/../lib/crypt.php' ) ); - $this->dataShort = 'hats'; - $this->dataUrl = realpath( dirname(__FILE__).'/../lib/crypt.php' ); - $this->legacyData = realpath( dirname(__FILE__).'/legacy-text.txt' ); - $this->legacyEncryptedData = realpath( dirname(__FILE__).'/legacy-encrypted-text.txt' ); - $this->randomKey = Encryption\Crypt::generateKey(); - - $keypair = Encryption\Crypt::createKeypair(); - $this->genPublicKey = $keypair['publicKey']; - $this->genPrivateKey = $keypair['privateKey']; - - $this->view = new \OC_FilesystemView( '/' ); - - \OC_User::setUserId( 'admin' ); - $this->userId = 'admin'; - $this->pass = 'admin'; - - \OC_Filesystem::init( '/' ); - \OC_Filesystem::mount( 'OC_Filestorage_Local', array('datadir' => \OC_User::getHome($this->userId)), '/' ); - - } - - function tearDown(){ - - \OC_FileProxy::$enabled = true; - - } - - function testGetPrivateKey() { - - $key = Encryption\Keymanager::getPrivateKey( $this->view, $this->userId ); - - // Will this length vary? Perhaps we should use a range instead - $this->assertEquals( 2296, strlen( $key ) ); - - } - - function testGetPublicKey() { - - $key = Encryption\Keymanager::getPublicKey( $this->view, $this->userId ); - - $this->assertEquals( 451, strlen( $key ) ); - - $this->assertEquals( '-----BEGIN PUBLIC KEY-----', substr( $key, 0, 26 ) ); - } - - function testSetFileKey() { - - # NOTE: This cannot be tested until we are able to break out - # of the FileSystemView data directory root - - $key = Encryption\Crypt::symmetricEncryptFileContentKeyfile( $this->randomKey, 'hat' ); - - $path = 'unittest-'.time().'txt'; - - //$view = new \OC_FilesystemView( '/' . $this->userId . '/files_encryption/keyfiles' ); - - Encryption\Keymanager::setFileKey( $this->view, $path, $this->userId, $key['key'] ); - - } - -// /** -// * @depends testGetPrivateKey -// */ -// function testGetPrivateKey_decrypt() { -// -// $key = Encryption\Keymanager::getPrivateKey( $this->view, $this->userId ); -// -// # TODO: replace call to Crypt with a mock object? -// $decrypted = Encryption\Crypt::symmetricDecryptFileContent( $key, $this->passphrase ); -// -// $this->assertEquals( 1704, strlen( $decrypted ) ); -// -// $this->assertEquals( '-----BEGIN PRIVATE KEY-----', substr( $decrypted, 0, 27 ) ); -// -// } - - function testGetUserKeys() { - - $keys = Encryption\Keymanager::getUserKeys( $this->view, $this->userId ); - - $this->assertEquals( 451, strlen( $keys['publicKey'] ) ); - $this->assertEquals( '-----BEGIN PUBLIC KEY-----', substr( $keys['publicKey'], 0, 26 ) ); - $this->assertEquals( 2296, strlen( $keys['privateKey'] ) ); - - } - - function testGetPublicKeys() { - - # TODO: write me - - } - - function testGetFileKey() { - -// Encryption\Keymanager::getFileKey( $this->view, $this->userId, $this->filePath ); - - } - -} diff --git a/apps/files_encryption/test/legacy-encrypted-text.txt b/apps/files_encryption/test/legacy-encrypted-text.txt deleted file mode 100644 index cb5bf50550..0000000000 Binary files a/apps/files_encryption/test/legacy-encrypted-text.txt and /dev/null differ diff --git a/apps/files_encryption/test/stream.php b/apps/files_encryption/test/stream.php deleted file mode 100644 index ba82ac80ea..0000000000 --- a/apps/files_encryption/test/stream.php +++ /dev/null @@ -1,226 +0,0 @@ -// -// * This file is licensed under the Affero General Public License version 3 or -// * later. -// * See the COPYING-README file. -// */ -// -// namespace OCA\Encryption; -// -// class Test_Stream extends \PHPUnit_Framework_TestCase { -// -// function setUp() { -// -// \OC_Filesystem::mount( 'OC_Filestorage_Local', array(), '/' ); -// -// $this->empty = ''; -// -// $this->stream = new Stream(); -// -// $this->dataLong = file_get_contents( realpath( dirname(__FILE__).'/../lib/crypt.php' ) ); -// $this->dataShort = 'hats'; -// -// $this->emptyTmpFilePath = \OCP\Files::tmpFile(); -// -// $this->dataTmpFilePath = \OCP\Files::tmpFile(); -// -// file_put_contents( $this->dataTmpFilePath, "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ut gravida lorem. Ut turpis felis, pulvinar a semper sed, adipiscing id dolor. Pellentesque auctor nisi id magna consequat sagittis. Curabitur dapibus enim sit amet elit pharetra tincidunt feugiat nisl imperdiet. Ut convallis libero in urna ultrices accumsan. Donec sed odio eros. Donec viverra mi quis quam pulvinar at malesuada arcu rhoncus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In rutrum accumsan ultricies. Mauris vitae nisi at sem facilisis semper ac in est." ); -// -// } -// -// function testStreamOpen() { -// -// $stream1 = new Stream(); -// -// $handle1 = $stream1->stream_open( $this->emptyTmpFilePath, 'wb', array(), $this->empty ); -// -// // Test that resource was returned successfully -// $this->assertTrue( $handle1 ); -// -// // Test that file has correct size -// $this->assertEquals( 0, $stream1->size ); -// -// // Test that path is correct -// $this->assertEquals( $this->emptyTmpFilePath, $stream1->rawPath ); -// -// $stream2 = new Stream(); -// -// $handle2 = $stream2->stream_open( 'crypt://' . $this->emptyTmpFilePath, 'wb', array(), $this->empty ); -// -// // Test that protocol identifier is removed from path -// $this->assertEquals( $this->emptyTmpFilePath, $stream2->rawPath ); -// -// // "Stat failed error" prevents this test from executing -// // $stream3 = new Stream(); -// // -// // $handle3 = $stream3->stream_open( $this->dataTmpFilePath, 'r', array(), $this->empty ); -// // -// // $this->assertEquals( 0, $stream3->size ); -// -// } -// -// function testStreamWrite() { -// -// $stream1 = new Stream(); -// -// $handle1 = $stream1->stream_open( $this->emptyTmpFilePath, 'r+b', array(), $this->empty ); -// -// # what about the keymanager? there is no key for the newly created temporary file! -// -// $stream1->stream_write( $this->dataShort ); -// -// } -// -// // function getStream( $id, $mode, $size ) { -// // -// // if ( $id === '' ) { -// // -// // $id = uniqid(); -// // } -// // -// // -// // if ( !isset( $this->tmpFiles[$id] ) ) { -// // -// // // If tempfile with given name does not already exist, create it -// // -// // $file = OCP\Files::tmpFile(); -// // -// // $this->tmpFiles[$id] = $file; -// // -// // } else { -// // -// // $file = $this->tmpFiles[$id]; -// // -// // } -// // -// // $stream = fopen( $file, $mode ); -// // -// // Stream::$sourceStreams[$id] = array( 'path' => 'dummy' . $id, 'stream' => $stream, 'size' => $size ); -// // -// // return fopen( 'crypt://streams/'.$id, $mode ); -// // -// // } -// // -// // function testStream( ){ -// // -// // $stream = $this->getStream( 'test1', 'w', strlen( 'foobar' ) ); -// // -// // fwrite( $stream, 'foobar' ); -// // -// // fclose( $stream ); -// // -// // -// // $stream = $this->getStream( 'test1', 'r', strlen( 'foobar' ) ); -// // -// // $data = fread( $stream, 6 ); -// // -// // fclose( $stream ); -// // -// // $this->assertEquals( 'foobar', $data ); -// // -// // -// // $file = OC::$SERVERROOT.'/3rdparty/MDB2.php'; -// // -// // $source = fopen( $file, 'r' ); -// // -// // $target = $this->getStream( 'test2', 'w', 0 ); -// // -// // OCP\Files::streamCopy( $source, $target ); -// // -// // fclose( $target ); -// // -// // fclose( $source ); -// // -// // -// // $stream = $this->getStream( 'test2', 'r', filesize( $file ) ); -// // -// // $data = stream_get_contents( $stream ); -// // -// // $original = file_get_contents( $file ); -// // -// // $this->assertEquals( strlen( $original ), strlen( $data ) ); -// // -// // $this->assertEquals( $original, $data ); -// // -// // } -// -// } -// -// // class Test_CryptStream extends PHPUnit_Framework_TestCase { -// // private $tmpFiles=array(); -// // -// // function testStream(){ -// // $stream=$this->getStream('test1','w',strlen('foobar')); -// // fwrite($stream,'foobar'); -// // fclose($stream); -// // -// // $stream=$this->getStream('test1','r',strlen('foobar')); -// // $data=fread($stream,6); -// // fclose($stream); -// // $this->assertEquals('foobar',$data); -// // -// // $file=OC::$SERVERROOT.'/3rdparty/MDB2.php'; -// // $source=fopen($file,'r'); -// // $target=$this->getStream('test2','w',0); -// // OCP\Files::streamCopy($source,$target); -// // fclose($target); -// // fclose($source); -// // -// // $stream=$this->getStream('test2','r',filesize($file)); -// // $data=stream_get_contents($stream); -// // $original=file_get_contents($file); -// // $this->assertEquals(strlen($original),strlen($data)); -// // $this->assertEquals($original,$data); -// // } -// // -// // /** -// // * get a cryptstream to a temporary file -// // * @param string $id -// // * @param string $mode -// // * @param int size -// // * @return resource -// // */ -// // function getStream($id,$mode,$size){ -// // if($id===''){ -// // $id=uniqid(); -// // } -// // if(!isset($this->tmpFiles[$id])){ -// // $file=OCP\Files::tmpFile(); -// // $this->tmpFiles[$id]=$file; -// // }else{ -// // $file=$this->tmpFiles[$id]; -// // } -// // $stream=fopen($file,$mode); -// // OC_CryptStream::$sourceStreams[$id]=array('path'=>'dummy'.$id,'stream'=>$stream,'size'=>$size); -// // return fopen('crypt://streams/'.$id,$mode); -// // } -// // -// // function testBinary(){ -// // $file=__DIR__.'/binary'; -// // $source=file_get_contents($file); -// // -// // $stream=$this->getStream('test','w',strlen($source)); -// // fwrite($stream,$source); -// // fclose($stream); -// // -// // $stream=$this->getStream('test','r',strlen($source)); -// // $data=stream_get_contents($stream); -// // fclose($stream); -// // $this->assertEquals(strlen($data),strlen($source)); -// // $this->assertEquals($source,$data); -// // -// // $file=__DIR__.'/zeros'; -// // $source=file_get_contents($file); -// // -// // $stream=$this->getStream('test2','w',strlen($source)); -// // fwrite($stream,$source); -// // fclose($stream); -// // -// // $stream=$this->getStream('test2','r',strlen($source)); -// // $data=stream_get_contents($stream); -// // fclose($stream); -// // $this->assertEquals(strlen($data),strlen($source)); -// // $this->assertEquals($source,$data); -// // } -// // } diff --git a/apps/files_encryption/test/util.php b/apps/files_encryption/test/util.php deleted file mode 100755 index 1cdeff8008..0000000000 --- a/apps/files_encryption/test/util.php +++ /dev/null @@ -1,225 +0,0 @@ - - * This file is licensed under the Affero General Public License version 3 or - * later. - * See the COPYING-README file. - */ - -//require_once "PHPUnit/Framework/TestCase.php"; -require_once realpath( dirname(__FILE__).'/../../../lib/base.php' ); -require_once realpath( dirname(__FILE__).'/../lib/crypt.php' ); -require_once realpath( dirname(__FILE__).'/../lib/keymanager.php' ); -require_once realpath( dirname(__FILE__).'/../lib/proxy.php' ); -require_once realpath( dirname(__FILE__).'/../lib/stream.php' ); -require_once realpath( dirname(__FILE__).'/../lib/util.php' ); -require_once realpath( dirname(__FILE__).'/../appinfo/app.php' ); - -// Load mockery files -require_once 'Mockery/Loader.php'; -require_once 'Hamcrest/Hamcrest.php'; -$loader = new \Mockery\Loader; -$loader->register(); - -use \Mockery as m; -use OCA\Encryption; - -class Test_Enc_Util extends \PHPUnit_Framework_TestCase { - - function setUp() { - - \OC_Filesystem::mount( 'OC_Filestorage_Local', array(), '/' ); - - // set content for encrypting / decrypting in tests - $this->dataUrl = realpath( dirname(__FILE__).'/../lib/crypt.php' ); - $this->dataShort = 'hats'; - $this->dataLong = file_get_contents( realpath( dirname(__FILE__).'/../lib/crypt.php' ) ); - $this->legacyData = realpath( dirname(__FILE__).'/legacy-text.txt' ); - $this->legacyEncryptedData = realpath( dirname(__FILE__).'/legacy-encrypted-text.txt' ); - - $this->userId = 'admin'; - $this->pass = 'admin'; - - $keypair = Encryption\Crypt::createKeypair(); - - $this->genPublicKey = $keypair['publicKey']; - $this->genPrivateKey = $keypair['privateKey']; - - $this->publicKeyDir = '/' . 'public-keys'; - $this->encryptionDir = '/' . $this->userId . '/' . 'files_encryption'; - $this->keyfilesPath = $this->encryptionDir . '/' . 'keyfiles'; - $this->publicKeyPath = $this->publicKeyDir . '/' . $this->userId . '.public.key'; // e.g. data/public-keys/admin.public.key - $this->privateKeyPath = $this->encryptionDir . '/' . $this->userId . '.private.key'; // e.g. data/admin/admin.private.key - - $this->view = new \OC_FilesystemView( '/' ); - - $this->mockView = m::mock('OC_FilesystemView'); - $this->util = new Encryption\Util( $this->mockView, $this->userId ); - - } - - function tearDown(){ - - m::close(); - - } - - /** - * @brief test that paths set during User construction are correct - */ - function testKeyPaths() { - - $mockView = m::mock('OC_FilesystemView'); - - $util = new Encryption\Util( $mockView, $this->userId ); - - $this->assertEquals( $this->publicKeyDir, $util->getPath( 'publicKeyDir' ) ); - $this->assertEquals( $this->encryptionDir, $util->getPath( 'encryptionDir' ) ); - $this->assertEquals( $this->keyfilesPath, $util->getPath( 'keyfilesPath' ) ); - $this->assertEquals( $this->publicKeyPath, $util->getPath( 'publicKeyPath' ) ); - $this->assertEquals( $this->privateKeyPath, $util->getPath( 'privateKeyPath' ) ); - - } - - /** - * @brief test setup of encryption directories when they don't yet exist - */ - function testSetupServerSideNotSetup() { - - $mockView = m::mock('OC_FilesystemView'); - - $mockView->shouldReceive( 'file_exists' )->times(5)->andReturn( false ); - $mockView->shouldReceive( 'mkdir' )->times(4)->andReturn( true ); - $mockView->shouldReceive( 'file_put_contents' )->withAnyArgs(); - - $util = new Encryption\Util( $mockView, $this->userId ); - - $this->assertEquals( true, $util->setupServerSide( $this->pass ) ); - - } - - /** - * @brief test setup of encryption directories when they already exist - */ - function testSetupServerSideIsSetup() { - - $mockView = m::mock('OC_FilesystemView'); - - $mockView->shouldReceive( 'file_exists' )->times(6)->andReturn( true ); - $mockView->shouldReceive( 'file_put_contents' )->withAnyArgs(); - - $util = new Encryption\Util( $mockView, $this->userId ); - - $this->assertEquals( true, $util->setupServerSide( $this->pass ) ); - - } - - /** - * @brief test checking whether account is ready for encryption, when it isn't ready - */ - function testReadyNotReady() { - - $mockView = m::mock('OC_FilesystemView'); - - $mockView->shouldReceive( 'file_exists' )->times(1)->andReturn( false ); - - $util = new Encryption\Util( $mockView, $this->userId ); - - $this->assertEquals( false, $util->ready() ); - - # TODO: Add more tests here to check that if any of the dirs are - # then false will be returned. Use strict ordering? - - } - - /** - * @brief test checking whether account is ready for encryption, when it is ready - */ - function testReadyIsReady() { - - $mockView = m::mock('OC_FilesystemView'); - - $mockView->shouldReceive( 'file_exists' )->times(3)->andReturn( true ); - - $util = new Encryption\Util( $mockView, $this->userId ); - - $this->assertEquals( true, $util->ready() ); - - # TODO: Add more tests here to check that if any of the dirs are - # then false will be returned. Use strict ordering? - - } - - function testFindFiles() { - -// $this->view->chroot( "/data/{$this->userId}/files" ); - - $util = new Encryption\Util( $this->view, $this->userId ); - - $files = $util->findFiles( '/', 'encrypted' ); - - var_dump( $files ); - - # TODO: Add more tests here to check that if any of the dirs are - # then false will be returned. Use strict ordering? - - } - -// /** -// * @brief test decryption using legacy blowfish method -// * @depends testLegacyEncryptLong -// */ -// function testLegacyKeyRecryptKeyfileDecrypt( $recrypted ) { -// -// $decrypted = Encryption\Crypt::keyDecryptKeyfile( $recrypted['data'], $recrypted['key'], $this->genPrivateKey ); -// -// $this->assertEquals( $this->dataLong, $decrypted ); -// -// } - -// // Cannot use this test for now due to hidden dependencies in OC_FileCache -// function testIsLegacyEncryptedContent() { -// -// $keyfileContent = OCA\Encryption\Crypt::symmetricEncryptFileContent( $this->legacyEncryptedData, 'hat' ); -// -// $this->assertFalse( OCA\Encryption\Crypt::isLegacyEncryptedContent( $keyfileContent, '/files/admin/test.txt' ) ); -// -// OC_FileCache::put( '/admin/files/legacy-encrypted-test.txt', $this->legacyEncryptedData ); -// -// $this->assertTrue( OCA\Encryption\Crypt::isLegacyEncryptedContent( $this->legacyEncryptedData, '/files/admin/test.txt' ) ); -// -// } - -// // Cannot use this test for now due to need for different root in OC_Filesystem_view class -// function testGetLegacyKey() { -// -// $c = new \OCA\Encryption\Util( $view, false ); -// -// $bool = $c->getLegacyKey( 'admin' ); -// -// $this->assertTrue( $bool ); -// -// $this->assertTrue( $c->legacyKey ); -// -// $this->assertTrue( is_int( $c->legacyKey ) ); -// -// $this->assertTrue( strlen( $c->legacyKey ) == 20 ); -// -// } - -// // Cannot use this test for now due to need for different root in OC_Filesystem_view class -// function testLegacyDecrypt() { -// -// $c = new OCA\Encryption\Util( $this->view, false ); -// -// $bool = $c->getLegacyKey( 'admin' ); -// -// $encrypted = $c->legacyEncrypt( $this->data, $c->legacyKey ); -// -// $decrypted = $c->legacyDecrypt( $encrypted, $c->legacyKey ); -// -// $this->assertEquals( $decrypted, $this->data ); -// -// } - -} \ No newline at end of file diff --git a/apps/files_encryption/test/binary b/apps/files_encryption/tests/binary similarity index 100% rename from apps/files_encryption/test/binary rename to apps/files_encryption/tests/binary diff --git a/apps/files_encryption/tests/crypt.php b/apps/files_encryption/tests/crypt.php new file mode 100755 index 0000000000..9b97df22d1 --- /dev/null +++ b/apps/files_encryption/tests/crypt.php @@ -0,0 +1,874 @@ +, and + * Robin Appelman + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +require_once realpath(dirname(__FILE__) . '/../3rdparty/Crypt_Blowfish/Blowfish.php'); +require_once realpath(dirname(__FILE__) . '/../../../lib/base.php'); +require_once realpath(dirname(__FILE__) . '/../lib/crypt.php'); +require_once realpath(dirname(__FILE__) . '/../lib/keymanager.php'); +require_once realpath(dirname(__FILE__) . '/../lib/proxy.php'); +require_once realpath(dirname(__FILE__) . '/../lib/stream.php'); +require_once realpath(dirname(__FILE__) . '/../lib/util.php'); +require_once realpath(dirname(__FILE__) . '/../lib/helper.php'); +require_once realpath(dirname(__FILE__) . '/../appinfo/app.php'); +require_once realpath(dirname(__FILE__) . '/util.php'); + +use OCA\Encryption; + +/** + * Class Test_Encryption_Crypt + */ +class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase { + + const TEST_ENCRYPTION_CRYPT_USER1 = "test-crypt-user1"; + + public $userId; + public $pass; + public $stateFilesTrashbin; + public $dataLong; + public $dataUrl; + public $dataShort; + /** + * @var OC_FilesystemView + */ + public $view; + public $legacyEncryptedData; + public $genPrivateKey; + public $genPublicKey; + + public static function setUpBeforeClass() { + // reset backend + \OC_User::clearBackends(); + \OC_User::useBackend('database'); + + // Filesystem related hooks + \OCA\Encryption\Helper::registerFilesystemHooks(); + + // Filesystem related hooks + \OCA\Encryption\Helper::registerUserHooks(); + + // clear and register hooks + \OC_FileProxy::clearProxies(); + \OC_FileProxy::register(new OCA\Encryption\Proxy()); + + // create test user + \Test_Encryption_Util::loginHelper(\Test_Encryption_Crypt::TEST_ENCRYPTION_CRYPT_USER1, true); + } + + function setUp() { + // set user id + \OC_User::setUserId(\Test_Encryption_Crypt::TEST_ENCRYPTION_CRYPT_USER1); + $this->userId = \Test_Encryption_Crypt::TEST_ENCRYPTION_CRYPT_USER1; + $this->pass = \Test_Encryption_Crypt::TEST_ENCRYPTION_CRYPT_USER1; + + // set content for encrypting / decrypting in tests + $this->dataLong = file_get_contents(realpath(dirname(__FILE__) . '/../lib/crypt.php')); + $this->dataShort = 'hats'; + $this->dataUrl = realpath(dirname(__FILE__) . '/../lib/crypt.php'); + $this->legacyData = realpath(dirname(__FILE__) . '/legacy-text.txt'); + $this->legacyEncryptedData = realpath(dirname(__FILE__) . '/legacy-encrypted-text.txt'); + $this->legacyEncryptedDataKey = realpath(dirname(__FILE__) . '/encryption.key'); + $this->randomKey = Encryption\Crypt::generateKey(); + + $keypair = Encryption\Crypt::createKeypair(); + $this->genPublicKey = $keypair['publicKey']; + $this->genPrivateKey = $keypair['privateKey']; + + $this->view = new \OC_FilesystemView('/'); + + // remember files_trashbin state + $this->stateFilesTrashbin = OC_App::isEnabled('files_trashbin'); + + // we don't want to tests with app files_trashbin enabled + \OC_App::disable('files_trashbin'); + } + + function tearDown() { + // reset app files_trashbin + if ($this->stateFilesTrashbin) { + OC_App::enable('files_trashbin'); + } else { + OC_App::disable('files_trashbin'); + } + } + + public static function tearDownAfterClass() { + // cleanup test user + \OC_User::deleteUser(\Test_Encryption_Crypt::TEST_ENCRYPTION_CRYPT_USER1); + } + + /** + * @medium + */ + function testGenerateKey() { + + # TODO: use more accurate (larger) string length for test confirmation + + $key = Encryption\Crypt::generateKey(); + + $this->assertTrue(strlen($key) > 16); + + } + + /** + * @large + * @return String + */ + function testGenerateIv() { + + $iv = Encryption\Crypt::generateIv(); + + $this->assertEquals(16, strlen($iv)); + + return $iv; + + } + + /** + * @large + * @depends testGenerateIv + */ + function testConcatIv($iv) { + + $catFile = Encryption\Crypt::concatIv($this->dataLong, $iv); + + // Fetch encryption metadata from end of file + $meta = substr($catFile, -22); + + $identifier = substr($meta, 0, 6); + + // Fetch IV from end of file + $foundIv = substr($meta, 6); + + $this->assertEquals('00iv00', $identifier); + + $this->assertEquals($iv, $foundIv); + + // Remove IV and IV identifier text to expose encrypted content + $data = substr($catFile, 0, -22); + + $this->assertEquals($this->dataLong, $data); + + return array( + 'iv' => $iv + , + 'catfile' => $catFile + ); + + } + + /** + * @medium + * @depends testConcatIv + */ + function testSplitIv($testConcatIv) { + + // Split catfile into components + $splitCatfile = Encryption\Crypt::splitIv($testConcatIv['catfile']); + + // Check that original IV and split IV match + $this->assertEquals($testConcatIv['iv'], $splitCatfile['iv']); + + // Check that original data and split data match + $this->assertEquals($this->dataLong, $splitCatfile['encrypted']); + + } + + /** + * @medium + * @return string padded + */ + function testAddPadding() { + + $padded = Encryption\Crypt::addPadding($this->dataLong); + + $padding = substr($padded, -2); + + $this->assertEquals('xx', $padding); + + return $padded; + + } + + /** + * @medium + * @depends testAddPadding + */ + function testRemovePadding($padded) { + + $noPadding = Encryption\Crypt::RemovePadding($padded); + + $this->assertEquals($this->dataLong, $noPadding); + + } + + /** + * @medium + */ + function testEncrypt() { + + $random = openssl_random_pseudo_bytes(13); + + $iv = substr(base64_encode($random), 0, -4); // i.e. E5IG033j+mRNKrht + + $crypted = Encryption\Crypt::encrypt($this->dataUrl, $iv, 'hat'); + + $this->assertNotEquals($this->dataUrl, $crypted); + + } + + /** + * @medium + */ + function testDecrypt() { + + $random = openssl_random_pseudo_bytes(13); + + $iv = substr(base64_encode($random), 0, -4); // i.e. E5IG033j+mRNKrht + + $crypted = Encryption\Crypt::encrypt($this->dataUrl, $iv, 'hat'); + + $decrypt = Encryption\Crypt::decrypt($crypted, $iv, 'hat'); + + $this->assertEquals($this->dataUrl, $decrypt); + + } + + function testDecryptPrivateKey() { + + // test successful decrypt + $crypted = Encryption\Crypt::symmetricEncryptFileContent($this->genPrivateKey, 'hat'); + + $decrypted = Encryption\Crypt::decryptPrivateKey($crypted, 'hat'); + + $this->assertEquals($this->genPrivateKey, $decrypted); + + //test private key decrypt with wrong password + $wrongPasswd = Encryption\Crypt::decryptPrivateKey($crypted, 'hat2'); + + $this->assertEquals(false, $wrongPasswd); + + } + + + /** + * @medium + */ + function testSymmetricEncryptFileContent() { + + # TODO: search in keyfile for actual content as IV will ensure this test always passes + + $crypted = Encryption\Crypt::symmetricEncryptFileContent($this->dataShort, 'hat'); + + $this->assertNotEquals($this->dataShort, $crypted); + + + $decrypt = Encryption\Crypt::symmetricDecryptFileContent($crypted, 'hat'); + + $this->assertEquals($this->dataShort, $decrypt); + + } + + /** + * @medium + */ + function testSymmetricStreamEncryptShortFileContent() { + + $filename = 'tmp-' . time() . '.test'; + + $cryptedFile = file_put_contents('crypt://' . $filename, $this->dataShort); + + // Test that data was successfully written + $this->assertTrue(is_int($cryptedFile)); + + // Disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + // Get file contents without using any wrapper to get it's actual contents on disk + $retreivedCryptedFile = $this->view->file_get_contents($this->userId . '/files/' . $filename); + + // Re-enable proxy - our work is done + \OC_FileProxy::$enabled = $proxyStatus; + + // Check that the file was encrypted before being written to disk + $this->assertNotEquals($this->dataShort, $retreivedCryptedFile); + + // Get the encrypted keyfile + $encKeyfile = Encryption\Keymanager::getFileKey($this->view, $this->userId, $filename); + + // Attempt to fetch the user's shareKey + $shareKey = Encryption\Keymanager::getShareKey($this->view, $this->userId, $filename); + + // get session + $session = new \OCA\Encryption\Session($this->view); + + // get private key + $privateKey = $session->getPrivateKey($this->userId); + + // Decrypt keyfile with shareKey + $plainKeyfile = Encryption\Crypt::multiKeyDecrypt($encKeyfile, $shareKey, $privateKey); + + // Manually decrypt + $manualDecrypt = Encryption\Crypt::symmetricDecryptFileContent($retreivedCryptedFile, $plainKeyfile); + + // Check that decrypted data matches + $this->assertEquals($this->dataShort, $manualDecrypt); + + // Teardown + $this->view->unlink($this->userId . '/files/' . $filename); + + Encryption\Keymanager::deleteFileKey($this->view, $this->userId, $filename); + } + + /** + * @medium + * @brief Test that data that is written by the crypto stream wrapper + * @note Encrypted data is manually prepared and decrypted here to avoid dependency on success of stream_read + * @note If this test fails with truncate content, check that enough array slices are being rejoined to form $e, as the crypt.php file may have gotten longer and broken the manual + * reassembly of its data + */ + function testSymmetricStreamEncryptLongFileContent() { + + // Generate a a random filename + $filename = 'tmp-' . time() . '.test'; + + // Save long data as encrypted file using stream wrapper + $cryptedFile = file_put_contents('crypt://' . $filename, $this->dataLong . $this->dataLong); + + // Test that data was successfully written + $this->assertTrue(is_int($cryptedFile)); + + // Disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + // Get file contents without using any wrapper to get it's actual contents on disk + $retreivedCryptedFile = $this->view->file_get_contents($this->userId . '/files/' . $filename); + + // Re-enable proxy - our work is done + \OC_FileProxy::$enabled = $proxyStatus; + + + // Check that the file was encrypted before being written to disk + $this->assertNotEquals($this->dataLong . $this->dataLong, $retreivedCryptedFile); + + // Manuallly split saved file into separate IVs and encrypted chunks + $r = preg_split('/(00iv00.{16,18})/', $retreivedCryptedFile, NULL, PREG_SPLIT_DELIM_CAPTURE); + + //print_r($r); + + // Join IVs and their respective data chunks + $e = array( + $r[0] . $r[1], + $r[2] . $r[3], + $r[4] . $r[5], + $r[6] . $r[7], + $r[8] . $r[9], + $r[10] . $r[11] + ); //.$r[11], $r[12].$r[13], $r[14] ); + + //print_r($e); + + // Get the encrypted keyfile + $encKeyfile = Encryption\Keymanager::getFileKey($this->view, $this->userId, $filename); + + // Attempt to fetch the user's shareKey + $shareKey = Encryption\Keymanager::getShareKey($this->view, $this->userId, $filename); + + // get session + $session = new \OCA\Encryption\Session($this->view); + + // get private key + $privateKey = $session->getPrivateKey($this->userId); + + // Decrypt keyfile with shareKey + $plainKeyfile = Encryption\Crypt::multiKeyDecrypt($encKeyfile, $shareKey, $privateKey); + + // Set var for reassembling decrypted content + $decrypt = ''; + + // Manually decrypt chunk + foreach ($e as $chunk) { + + $chunkDecrypt = Encryption\Crypt::symmetricDecryptFileContent($chunk, $plainKeyfile); + + // Assemble decrypted chunks + $decrypt .= $chunkDecrypt; + + } + + $this->assertEquals($this->dataLong . $this->dataLong, $decrypt); + + // Teardown + + $this->view->unlink($this->userId . '/files/' . $filename); + + Encryption\Keymanager::deleteFileKey($this->view, $this->userId, $filename); + + } + + /** + * @medium + * @brief Test that data that is read by the crypto stream wrapper + */ + function testSymmetricStreamDecryptShortFileContent() { + + $filename = 'tmp-' . time(); + + // Save long data as encrypted file using stream wrapper + $cryptedFile = file_put_contents('crypt://' . $filename, $this->dataShort); + + // Test that data was successfully written + $this->assertTrue(is_int($cryptedFile)); + + // Disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + $this->assertTrue(Encryption\Crypt::isEncryptedMeta($filename)); + + \OC_FileProxy::$enabled = $proxyStatus; + + // Get file decrypted contents + $decrypt = file_get_contents('crypt://' . $filename); + + $this->assertEquals($this->dataShort, $decrypt); + + // tear down + $this->view->unlink($this->userId . '/files/' . $filename); + } + + /** + * @medium + */ + function testSymmetricStreamDecryptLongFileContent() { + + $filename = 'tmp-' . time(); + + // Save long data as encrypted file using stream wrapper + $cryptedFile = file_put_contents('crypt://' . $filename, $this->dataLong); + + // Test that data was successfully written + $this->assertTrue(is_int($cryptedFile)); + + // Get file decrypted contents + $decrypt = file_get_contents('crypt://' . $filename); + + $this->assertEquals($this->dataLong, $decrypt); + + // tear down + $this->view->unlink($this->userId . '/files/' . $filename); + } + + /** + * @medium + */ + function testSymmetricEncryptFileContentKeyfile() { + + # TODO: search in keyfile for actual content as IV will ensure this test always passes + + $crypted = Encryption\Crypt::symmetricEncryptFileContentKeyfile($this->dataUrl); + + $this->assertNotEquals($this->dataUrl, $crypted['encrypted']); + + + $decrypt = Encryption\Crypt::symmetricDecryptFileContent($crypted['encrypted'], $crypted['key']); + + $this->assertEquals($this->dataUrl, $decrypt); + + } + + /** + * @medium + */ + function testIsEncryptedContent() { + + $this->assertFalse(Encryption\Crypt::isCatfileContent($this->dataUrl)); + + $this->assertFalse(Encryption\Crypt::isCatfileContent($this->legacyEncryptedData)); + + $keyfileContent = Encryption\Crypt::symmetricEncryptFileContent($this->dataUrl, 'hat'); + + $this->assertTrue(Encryption\Crypt::isCatfileContent($keyfileContent)); + + } + + /** + * @large + */ + function testMultiKeyEncrypt() { + + # TODO: search in keyfile for actual content as IV will ensure this test always passes + + $pair1 = Encryption\Crypt::createKeypair(); + + $this->assertEquals(2, count($pair1)); + + $this->assertTrue(strlen($pair1['publicKey']) > 1); + + $this->assertTrue(strlen($pair1['privateKey']) > 1); + + + $crypted = Encryption\Crypt::multiKeyEncrypt($this->dataShort, array($pair1['publicKey'])); + + $this->assertNotEquals($this->dataShort, $crypted['data']); + + + $decrypt = Encryption\Crypt::multiKeyDecrypt($crypted['data'], $crypted['keys'][0], $pair1['privateKey']); + + $this->assertEquals($this->dataShort, $decrypt); + + } + + /** + * @medium + */ + function testKeyEncrypt() { + + // Generate keypair + $pair1 = Encryption\Crypt::createKeypair(); + + // Encrypt data + $crypted = Encryption\Crypt::keyEncrypt($this->dataUrl, $pair1['publicKey']); + + $this->assertNotEquals($this->dataUrl, $crypted); + + // Decrypt data + $decrypt = Encryption\Crypt::keyDecrypt($crypted, $pair1['privateKey']); + + $this->assertEquals($this->dataUrl, $decrypt); + + } + + /** + * @medium + * @brief test encryption using legacy blowfish method + */ + function testLegacyEncryptShort() { + + $crypted = Encryption\Crypt::legacyEncrypt($this->dataShort, $this->pass); + + $this->assertNotEquals($this->dataShort, $crypted); + + # TODO: search inencrypted text for actual content to ensure it + # genuine transformation + + return $crypted; + + } + + /** + * @medium + * @brief test decryption using legacy blowfish method + * @depends testLegacyEncryptShort + */ + function testLegacyDecryptShort($crypted) { + + $decrypted = Encryption\Crypt::legacyBlockDecrypt($crypted, $this->pass); + + $this->assertEquals($this->dataShort, $decrypted); + + } + + /** + * @medium + * @brief test encryption using legacy blowfish method + */ + function testLegacyEncryptLong() { + + $crypted = Encryption\Crypt::legacyEncrypt($this->dataLong, $this->pass); + + $this->assertNotEquals($this->dataLong, $crypted); + + # TODO: search inencrypted text for actual content to ensure it + # genuine transformation + + return $crypted; + + } + + /** + * @medium + * @brief test decryption using legacy blowfish method + * @depends testLegacyEncryptLong + */ + function testLegacyDecryptLong($crypted) { + + $decrypted = Encryption\Crypt::legacyBlockDecrypt($crypted, $this->pass); + + $this->assertEquals($this->dataLong, $decrypted); + + $this->assertFalse(Encryption\Crypt::getBlowfish('')); + } + + /** + * @medium + * @brief test generation of legacy encryption key + * @depends testLegacyDecryptShort + */ + function testLegacyCreateKey() { + + // Create encrypted key + $encKey = Encryption\Crypt::legacyCreateKey($this->pass); + + // Decrypt key + $key = Encryption\Crypt::legacyBlockDecrypt($encKey, $this->pass); + + $this->assertTrue(is_numeric($key)); + + // Check that key is correct length + $this->assertEquals(20, strlen($key)); + + } + + /** + * @medium + */ + function testRenameFile() { + + $filename = 'tmp-' . time(); + + // Save long data as encrypted file using stream wrapper + $cryptedFile = file_put_contents('crypt://' . $filename, $this->dataLong); + + // Test that data was successfully written + $this->assertTrue(is_int($cryptedFile)); + + // Get file decrypted contents + $decrypt = file_get_contents('crypt://' . $filename); + + $this->assertEquals($this->dataLong, $decrypt); + + $newFilename = 'tmp-new-' . time(); + $view = new \OC\Files\View('/' . $this->userId . '/files'); + $view->rename($filename, $newFilename); + + // Get file decrypted contents + $newDecrypt = file_get_contents('crypt://' . $newFilename); + + $this->assertEquals($this->dataLong, $newDecrypt); + + // tear down + $view->unlink($newFilename); + } + + /** + * @medium + */ + function testMoveFileIntoFolder() { + + $filename = 'tmp-' . time(); + + // Save long data as encrypted file using stream wrapper + $cryptedFile = file_put_contents('crypt://' . $filename, $this->dataLong); + + // Test that data was successfully written + $this->assertTrue(is_int($cryptedFile)); + + // Get file decrypted contents + $decrypt = file_get_contents('crypt://' . $filename); + + $this->assertEquals($this->dataLong, $decrypt); + + $newFolder = '/newfolder' . time(); + $newFilename = 'tmp-new-' . time(); + $view = new \OC\Files\View('/' . $this->userId . '/files'); + $view->mkdir($newFolder); + $view->rename($filename, $newFolder . '/' . $newFilename); + + // Get file decrypted contents + $newDecrypt = file_get_contents('crypt://' . $newFolder . '/' . $newFilename); + + $this->assertEquals($this->dataLong, $newDecrypt); + + // tear down + $view->unlink($newFolder); + } + + /** + * @medium + */ + function testMoveFolder() { + + $view = new \OC\Files\View('/' . $this->userId . '/files'); + + $filename = '/tmp-' . time(); + $folder = '/folder' . time(); + + $view->mkdir($folder); + + // Save long data as encrypted file using stream wrapper + $cryptedFile = file_put_contents('crypt://' . $folder . $filename, $this->dataLong); + + // Test that data was successfully written + $this->assertTrue(is_int($cryptedFile)); + + // Get file decrypted contents + $decrypt = file_get_contents('crypt://' . $folder . $filename); + + $this->assertEquals($this->dataLong, $decrypt); + + $newFolder = '/newfolder/subfolder' . time(); + $view->mkdir('/newfolder'); + + $view->rename($folder, $newFolder); + + // Get file decrypted contents + $newDecrypt = file_get_contents('crypt://' . $newFolder . $filename); + + $this->assertEquals($this->dataLong, $newDecrypt); + + // tear down + $view->unlink($newFolder); + $view->unlink('/newfolder'); + } + + /** + * @medium + */ + function testChangePassphrase() { + $filename = 'tmp-' . time(); + + // Save long data as encrypted file using stream wrapper + $cryptedFile = file_put_contents('crypt://' . $filename, $this->dataLong); + + // Test that data was successfully written + $this->assertTrue(is_int($cryptedFile)); + + // Get file decrypted contents + $decrypt = file_get_contents('crypt://' . $filename); + + $this->assertEquals($this->dataLong, $decrypt); + + // change password + \OC_User::setPassword($this->userId, 'test', null); + + // relogin + $params['uid'] = $this->userId; + $params['password'] = 'test'; + OCA\Encryption\Hooks::login($params); + + // Get file decrypted contents + $newDecrypt = file_get_contents('crypt://' . $filename); + + $this->assertEquals($this->dataLong, $newDecrypt); + + // tear down + // change password back + \OC_User::setPassword($this->userId, $this->pass); + $view = new \OC\Files\View('/' . $this->userId . '/files'); + $view->unlink($filename); + } + + /** + * @medium + */ + function testViewFilePutAndGetContents() { + + $filename = '/tmp-' . time(); + $view = new \OC\Files\View('/' . $this->userId . '/files'); + + // Save short data as encrypted file using stream wrapper + $cryptedFile = $view->file_put_contents($filename, $this->dataShort); + + // Test that data was successfully written + $this->assertTrue(is_int($cryptedFile)); + + // Get file decrypted contents + $decrypt = $view->file_get_contents($filename); + + $this->assertEquals($this->dataShort, $decrypt); + + // Save long data as encrypted file using stream wrapper + $cryptedFileLong = $view->file_put_contents($filename, $this->dataLong); + + // Test that data was successfully written + $this->assertTrue(is_int($cryptedFileLong)); + + // Get file decrypted contents + $decryptLong = $view->file_get_contents($filename); + + $this->assertEquals($this->dataLong, $decryptLong); + + // tear down + $view->unlink($filename); + } + + /** + * @large + */ + function testTouchExistingFile() { + $filename = '/tmp-' . time(); + $view = new \OC\Files\View('/' . $this->userId . '/files'); + + // Save short data as encrypted file using stream wrapper + $cryptedFile = $view->file_put_contents($filename, $this->dataShort); + + // Test that data was successfully written + $this->assertTrue(is_int($cryptedFile)); + + $view->touch($filename); + + // Get file decrypted contents + $decrypt = $view->file_get_contents($filename); + + $this->assertEquals($this->dataShort, $decrypt); + + // tear down + $view->unlink($filename); + } + + /** + * @medium + */ + function testTouchFile() { + $filename = '/tmp-' . time(); + $view = new \OC\Files\View('/' . $this->userId . '/files'); + + $view->touch($filename); + + // Save short data as encrypted file using stream wrapper + $cryptedFile = $view->file_put_contents($filename, $this->dataShort); + + // Test that data was successfully written + $this->assertTrue(is_int($cryptedFile)); + + // Get file decrypted contents + $decrypt = $view->file_get_contents($filename); + + $this->assertEquals($this->dataShort, $decrypt); + + // tear down + $view->unlink($filename); + } + + /** + * @medium + */ + function testFopenFile() { + $filename = '/tmp-' . time(); + $view = new \OC\Files\View('/' . $this->userId . '/files'); + + // Save short data as encrypted file using stream wrapper + $cryptedFile = $view->file_put_contents($filename, $this->dataShort); + + // Test that data was successfully written + $this->assertTrue(is_int($cryptedFile)); + + $handle = $view->fopen($filename, 'r'); + + // Get file decrypted contents + $decrypt = fgets($handle); + + $this->assertEquals($this->dataShort, $decrypt); + + // tear down + $view->unlink($filename); + } +} diff --git a/apps/files_encryption/tests/encryption.key b/apps/files_encryption/tests/encryption.key new file mode 100644 index 0000000000..4ee962145c Binary files /dev/null and b/apps/files_encryption/tests/encryption.key differ diff --git a/apps/files_encryption/tests/keymanager.php b/apps/files_encryption/tests/keymanager.php new file mode 100644 index 0000000000..19ba9a8117 --- /dev/null +++ b/apps/files_encryption/tests/keymanager.php @@ -0,0 +1,263 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +require_once realpath(dirname(__FILE__) . '/../../../lib/base.php'); +require_once realpath(dirname(__FILE__) . '/../lib/crypt.php'); +require_once realpath(dirname(__FILE__) . '/../lib/keymanager.php'); +require_once realpath(dirname(__FILE__) . '/../lib/proxy.php'); +require_once realpath(dirname(__FILE__) . '/../lib/stream.php'); +require_once realpath(dirname(__FILE__) . '/../lib/util.php'); +require_once realpath(dirname(__FILE__) . '/../lib/helper.php'); +require_once realpath(dirname(__FILE__) . '/../appinfo/app.php'); + +use OCA\Encryption; + +/** + * Class Test_Encryption_Keymanager + */ +class Test_Encryption_Keymanager extends \PHPUnit_Framework_TestCase { + + public $userId; + public $pass; + public $stateFilesTrashbin; + /** + * @var OC_FilesystemView + */ + public $view; + public $randomKey; + public $dataShort; + + public static function setUpBeforeClass() { + // reset backend + \OC_User::clearBackends(); + \OC_User::useBackend('database'); + + // Filesystem related hooks + \OCA\Encryption\Helper::registerFilesystemHooks(); + + // clear and register hooks + \OC_FileProxy::clearProxies(); + \OC_FileProxy::register(new OCA\Encryption\Proxy()); + + // disable file proxy by default + \OC_FileProxy::$enabled = false; + + // setup filesystem + \OC_Util::tearDownFS(); + \OC_User::setUserId(''); + \OC\Files\Filesystem::tearDown(); + \OC_Util::setupFS('admin'); + \OC_User::setUserId('admin'); + + // login admin + $params['uid'] = 'admin'; + $params['password'] = 'admin'; + OCA\Encryption\Hooks::login($params); + } + + function setUp() { + // set content for encrypting / decrypting in tests + $this->dataLong = file_get_contents(realpath(dirname(__FILE__) . '/../lib/crypt.php')); + $this->dataShort = 'hats'; + $this->dataUrl = realpath(dirname(__FILE__) . '/../lib/crypt.php'); + $this->legacyData = realpath(dirname(__FILE__) . '/legacy-text.txt'); + $this->legacyEncryptedData = realpath(dirname(__FILE__) . '/legacy-encrypted-text.txt'); + $this->randomKey = Encryption\Crypt::generateKey(); + + $keypair = Encryption\Crypt::createKeypair(); + $this->genPublicKey = $keypair['publicKey']; + $this->genPrivateKey = $keypair['privateKey']; + + $this->view = new \OC_FilesystemView('/'); + + \OC_User::setUserId('admin'); + $this->userId = 'admin'; + $this->pass = 'admin'; + + $userHome = \OC_User::getHome($this->userId); + $this->dataDir = str_replace('/' . $this->userId, '', $userHome); + + // remember files_trashbin state + $this->stateFilesTrashbin = OC_App::isEnabled('files_trashbin'); + + // we don't want to tests with app files_trashbin enabled + \OC_App::disable('files_trashbin'); + } + + function tearDown() { + // reset app files_trashbin + if ($this->stateFilesTrashbin) { + OC_App::enable('files_trashbin'); + } + else { + OC_App::disable('files_trashbin'); + } + } + + public static function tearDownAfterClass() { + \OC_FileProxy::$enabled = true; + } + + /** + * @medium + */ + function testGetPrivateKey() { + + $key = Encryption\Keymanager::getPrivateKey($this->view, $this->userId); + + $privateKey = Encryption\Crypt::symmetricDecryptFileContent($key, $this->pass); + + $res = openssl_pkey_get_private($privateKey); + + $this->assertTrue(is_resource($res)); + + $sslInfo = openssl_pkey_get_details($res); + + $this->assertArrayHasKey('key', $sslInfo); + + } + + /** + * @medium + */ + function testGetPublicKey() { + + $publiceKey = Encryption\Keymanager::getPublicKey($this->view, $this->userId); + + $res = openssl_pkey_get_public($publiceKey); + + $this->assertTrue(is_resource($res)); + + $sslInfo = openssl_pkey_get_details($res); + + $this->assertArrayHasKey('key', $sslInfo); + } + + /** + * @medium + */ + function testSetFileKey() { + + # NOTE: This cannot be tested until we are able to break out + # of the FileSystemView data directory root + + $key = Encryption\Crypt::symmetricEncryptFileContentKeyfile($this->randomKey, 'hat'); + + $file = 'unittest-' . time() . '.txt'; + + // Disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + $this->view->file_put_contents($this->userId . '/files/' . $file, $key['encrypted']); + + // Re-enable proxy - our work is done + \OC_FileProxy::$enabled = $proxyStatus; + + //$view = new \OC_FilesystemView( '/' . $this->userId . '/files_encryption/keyfiles' ); + Encryption\Keymanager::setFileKey($this->view, $file, $this->userId, $key['key']); + + // enable encryption proxy + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = true; + + // cleanup + $this->view->unlink('/' . $this->userId . '/files/' . $file); + + // change encryption proxy to previous state + \OC_FileProxy::$enabled = $proxyStatus; + + } + + /** + * @medium + */ + function testGetUserKeys() { + + $keys = Encryption\Keymanager::getUserKeys($this->view, $this->userId); + + $resPublic = openssl_pkey_get_public($keys['publicKey']); + + $this->assertTrue(is_resource($resPublic)); + + $sslInfoPublic = openssl_pkey_get_details($resPublic); + + $this->assertArrayHasKey('key', $sslInfoPublic); + + $privateKey = Encryption\Crypt::symmetricDecryptFileContent($keys['privateKey'], $this->pass); + + $resPrivate = openssl_pkey_get_private($privateKey); + + $this->assertTrue(is_resource($resPrivate)); + + $sslInfoPrivate = openssl_pkey_get_details($resPrivate); + + $this->assertArrayHasKey('key', $sslInfoPrivate); + } + + /** + * @medium + */ + function testFixPartialFilePath() { + + $partFilename = 'testfile.txt.part'; + $filename = 'testfile.txt'; + + $this->assertTrue(Encryption\Keymanager::isPartialFilePath($partFilename)); + + $this->assertEquals('testfile.txt', Encryption\Keymanager::fixPartialFilePath($partFilename)); + + $this->assertFalse(Encryption\Keymanager::isPartialFilePath($filename)); + + $this->assertEquals('testfile.txt', Encryption\Keymanager::fixPartialFilePath($filename)); + } + + /** + * @medium + */ + function testRecursiveDelShareKeys() { + + // generate filename + $filename = '/tmp-' . time() . '.txt'; + + // create folder structure + $this->view->mkdir('/admin/files/folder1'); + $this->view->mkdir('/admin/files/folder1/subfolder'); + $this->view->mkdir('/admin/files/folder1/subfolder/subsubfolder'); + + // enable encryption proxy + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = true; + + // save file with content + $cryptedFile = file_put_contents('crypt:///folder1/subfolder/subsubfolder/' . $filename, $this->dataShort); + + // test that data was successfully written + $this->assertTrue(is_int($cryptedFile)); + + // change encryption proxy to previous state + \OC_FileProxy::$enabled = $proxyStatus; + + // recursive delete keys + Encryption\Keymanager::delShareKey($this->view, array('admin'), '/folder1/'); + + // check if share key not exists + $this->assertFalse($this->view->file_exists( + '/admin/files_encryption/share-keys/folder1/subfolder/subsubfolder/' . $filename . '.admin.shareKey')); + + // enable encryption proxy + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = true; + + // cleanup + $this->view->unlink('/admin/files/folder1'); + + // change encryption proxy to previous state + \OC_FileProxy::$enabled = $proxyStatus; + } +} diff --git a/apps/files_encryption/tests/legacy-encrypted-text.txt b/apps/files_encryption/tests/legacy-encrypted-text.txt new file mode 100644 index 0000000000..1f5087178c --- /dev/null +++ b/apps/files_encryption/tests/legacy-encrypted-text.txt @@ -0,0 +1 @@ +5ǡiZgESlF= \ No newline at end of file diff --git a/apps/files_encryption/test/proxy.php b/apps/files_encryption/tests/proxy.php similarity index 98% rename from apps/files_encryption/test/proxy.php rename to apps/files_encryption/tests/proxy.php index 709730f760..5a2d851ff7 100644 --- a/apps/files_encryption/test/proxy.php +++ b/apps/files_encryption/tests/proxy.php @@ -52,7 +52,7 @@ // $this->userId = 'admin'; // $this->pass = 'admin'; // -// $this->session = new Encryption\Session(); +// $this->session = new Encryption\Session( $view ); // FIXME: Provide a $view object for use here // // $this->session->setPrivateKey( // '-----BEGIN PRIVATE KEY----- diff --git a/apps/files_encryption/tests/share.php b/apps/files_encryption/tests/share.php new file mode 100755 index 0000000000..6b53031585 --- /dev/null +++ b/apps/files_encryption/tests/share.php @@ -0,0 +1,920 @@ + + * + * 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 . + * + */ + +require_once realpath(dirname(__FILE__) . '/../3rdparty/Crypt_Blowfish/Blowfish.php'); +require_once realpath(dirname(__FILE__) . '/../../../lib/base.php'); +require_once realpath(dirname(__FILE__) . '/../lib/crypt.php'); +require_once realpath(dirname(__FILE__) . '/../lib/keymanager.php'); +require_once realpath(dirname(__FILE__) . '/../lib/proxy.php'); +require_once realpath(dirname(__FILE__) . '/../lib/stream.php'); +require_once realpath(dirname(__FILE__) . '/../lib/util.php'); +require_once realpath(dirname(__FILE__) . '/../lib/helper.php'); +require_once realpath(dirname(__FILE__) . '/../appinfo/app.php'); +require_once realpath(dirname(__FILE__) . '/util.php'); + +use OCA\Encryption; + +/** + * Class Test_Encryption_Share + */ +class Test_Encryption_Share extends \PHPUnit_Framework_TestCase { + + const TEST_ENCRYPTION_SHARE_USER1 = "test-share-user1"; + const TEST_ENCRYPTION_SHARE_USER2 = "test-share-user2"; + const TEST_ENCRYPTION_SHARE_USER3 = "test-share-user3"; + const TEST_ENCRYPTION_SHARE_USER4 = "test-share-user4"; + const TEST_ENCRYPTION_SHARE_GROUP1 = "test-share-group1"; + + public $stateFilesTrashbin; + public $filename; + public $dataShort; + /** + * @var OC_FilesystemView + */ + public $view; + public $folder1; + public $subfolder; + public $subsubfolder; + + public static function setUpBeforeClass() { + // reset backend + \OC_User::clearBackends(); + \OC_User::useBackend('database'); + + // enable resharing + \OC_Appconfig::setValue('core', 'shareapi_allow_resharing', 'yes'); + + // clear share hooks + \OC_Hook::clear('OCP\\Share'); + \OC::registerShareHooks(); + \OCP\Util::connectHook('OC_Filesystem', 'setup', '\OC\Files\Storage\Shared', 'setup'); + + // Sharing related hooks + \OCA\Encryption\Helper::registerShareHooks(); + + // Filesystem related hooks + \OCA\Encryption\Helper::registerFilesystemHooks(); + + // clear and register hooks + \OC_FileProxy::clearProxies(); + \OC_FileProxy::register(new OCA\Encryption\Proxy()); + + // create users + \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1, true); + \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2, true); + \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER3, true); + \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER4, true); + + // create group and assign users + \OC_Group::createGroup(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_GROUP1); + \OC_Group::addToGroup(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER3, \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_GROUP1); + \OC_Group::addToGroup(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER4, \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_GROUP1); + } + + function setUp() { + $this->dataShort = 'hats'; + $this->view = new \OC_FilesystemView('/'); + + $this->folder1 = '/folder1'; + $this->subfolder = '/subfolder1'; + $this->subsubfolder = '/subsubfolder1'; + + $this->filename = 'share-tmp.test'; + + // we don't want to tests with app files_trashbin enabled + \OC_App::disable('files_trashbin'); + + // remember files_trashbin state + $this->stateFilesTrashbin = OC_App::isEnabled('files_trashbin'); + } + + function tearDown() { + // reset app files_trashbin + if ($this->stateFilesTrashbin) { + OC_App::enable('files_trashbin'); + } else { + OC_App::disable('files_trashbin'); + } + } + + public static function tearDownAfterClass() { + // clean group + \OC_Group::deleteGroup(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_GROUP1); + + // cleanup users + \OC_User::deleteUser(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1); + \OC_User::deleteUser(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2); + \OC_User::deleteUser(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER3); + \OC_User::deleteUser(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER4); + } + + /** + * @medium + * @param bool $withTeardown + */ + function testShareFile($withTeardown = true) { + // login as admin + \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1); + + // save file with content + $cryptedFile = file_put_contents('crypt://' . $this->filename, $this->dataShort); + + // test that data was successfully written + $this->assertTrue(is_int($cryptedFile)); + + // disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + // get the file info from previous created file + $fileInfo = $this->view->getFileInfo( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files/' . $this->filename); + + // check if we have a valid file info + $this->assertTrue(is_array($fileInfo)); + + // check if the unencrypted file size is stored + $this->assertGreaterThan(0, $fileInfo['unencrypted_size']); + + // re-enable the file proxy + \OC_FileProxy::$enabled = $proxyStatus; + + // share the file + \OCP\Share::shareItem('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2, OCP\PERMISSION_ALL); + + // login as admin + \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1); + + // check if share key for user1 exists + $this->assertTrue($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/share-keys/' + . $this->filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '.shareKey')); + + // login as user1 + \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2); + + // get file contents + $retrievedCryptedFile = $this->view->file_get_contents( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files/Shared/' . $this->filename); + + // check if data is the same as we previously written + $this->assertEquals($this->dataShort, $retrievedCryptedFile); + + // cleanup + if ($withTeardown) { + + // login as admin + \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1); + + // unshare the file + \OCP\Share::unshare('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2); + + // check if share key not exists + $this->assertFalse($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/share-keys/' + . $this->filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '.shareKey')); + + // cleanup + $this->view->unlink( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files/' . $this->filename); + + // check if share key not exists + $this->assertFalse($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/share-keys/' + . $this->filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '.shareKey')); + } + } + + /** + * @medium + * @param bool $withTeardown + */ + function testReShareFile($withTeardown = true) { + $this->testShareFile(false); + + // login as user1 + \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2); + + // get the file info + $fileInfo = $this->view->getFileInfo( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files/Shared/' . $this->filename); + + // share the file with user2 + \OCP\Share::shareItem('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER3, OCP\PERMISSION_ALL); + + // login as admin + \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1); + + // check if share key for user2 exists + $this->assertTrue($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/share-keys/' + . $this->filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER3 . '.shareKey')); + + // login as user2 + \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER3); + + // get file contents + $retrievedCryptedFile = $this->view->file_get_contents( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER3 . '/files/Shared/' . $this->filename); + + // check if data is the same as previously written + $this->assertEquals($this->dataShort, $retrievedCryptedFile); + + // cleanup + if ($withTeardown) { + + // login as user1 + \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2); + + // unshare the file with user2 + \OCP\Share::unshare('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER3); + + // login as admin + \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1); + + // check if share key not exists + $this->assertFalse($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/share-keys/' + . $this->filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER3 . '.shareKey')); + + // unshare the file with user1 + \OCP\Share::unshare('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2); + + // check if share key not exists + $this->assertFalse($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/share-keys/' + . $this->filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '.shareKey')); + + // cleanup + $this->view->unlink( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files/' . $this->filename); + + // check if share key not exists + $this->assertFalse($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/share-keys/' + . $this->filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '.shareKey')); + } + } + + /** + * @medium + * @param bool $withTeardown + * @return array + */ + function testShareFolder($withTeardown = true) { + // login as admin + \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1); + + // create folder structure + $this->view->mkdir('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files' . $this->folder1); + $this->view->mkdir( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files' . $this->folder1 . $this->subfolder); + $this->view->mkdir( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files' . $this->folder1 . $this->subfolder + . $this->subsubfolder); + + // save file with content + $cryptedFile = file_put_contents('crypt://' . $this->folder1 . $this->subfolder . $this->subsubfolder . '/' + . $this->filename, $this->dataShort); + + // test that data was successfully written + $this->assertTrue(is_int($cryptedFile)); + + // disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + // get the file info from previous created folder + $fileInfo = $this->view->getFileInfo( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files' . $this->folder1); + + // check if we have a valid file info + $this->assertTrue(is_array($fileInfo)); + + // re-enable the file proxy + \OC_FileProxy::$enabled = $proxyStatus; + + // share the folder with user1 + \OCP\Share::shareItem('folder', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2, OCP\PERMISSION_ALL); + + // login as admin + \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1); + + // check if share key for user1 exists + $this->assertTrue($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/share-keys' . $this->folder1 + . $this->subfolder . $this->subsubfolder . '/' + . $this->filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '.shareKey')); + + // login as user1 + \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2); + + // get file contents + $retrievedCryptedFile = $this->view->file_get_contents( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files/Shared' . $this->folder1 + . $this->subfolder . $this->subsubfolder . '/' . $this->filename); + + // check if data is the same + $this->assertEquals($this->dataShort, $retrievedCryptedFile); + + // cleanup + if ($withTeardown) { + + // login as admin + \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1); + + // unshare the folder with user1 + \OCP\Share::unshare('folder', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2); + + // check if share key not exists + $this->assertFalse($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/share-keys' + . $this->folder1 . $this->subfolder . $this->subsubfolder . '/' + . $this->filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '.shareKey')); + + // cleanup + $this->view->unlink('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files' . $this->folder1); + + // check if share key not exists + $this->assertFalse($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/share-keys' + . $this->folder1 . $this->subfolder . $this->subsubfolder . '/' + . $this->filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '.shareKey')); + } + + return $fileInfo; + } + + /** + * @medium + * @param bool $withTeardown + */ + function testReShareFolder($withTeardown = true) { + $fileInfoFolder1 = $this->testShareFolder(false); + + // login as user1 + \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2); + + // disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + // get the file info from previous created folder + $fileInfoSubFolder = $this->view->getFileInfo( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files/Shared' . $this->folder1 + . $this->subfolder); + + // check if we have a valid file info + $this->assertTrue(is_array($fileInfoSubFolder)); + + // re-enable the file proxy + \OC_FileProxy::$enabled = $proxyStatus; + + // share the file with user2 + \OCP\Share::shareItem('folder', $fileInfoSubFolder['fileid'], \OCP\Share::SHARE_TYPE_USER, \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER3, OCP\PERMISSION_ALL); + + // login as admin + \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1); + + // check if share key for user2 exists + $this->assertTrue($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/share-keys' . $this->folder1 + . $this->subfolder . $this->subsubfolder . '/' + . $this->filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER3 . '.shareKey')); + + // login as user2 + \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER3); + + // get file contents + $retrievedCryptedFile = $this->view->file_get_contents( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER3 . '/files/Shared' . $this->subfolder + . $this->subsubfolder . '/' . $this->filename); + + // check if data is the same + $this->assertEquals($this->dataShort, $retrievedCryptedFile); + + // get the file info + $fileInfo = $this->view->getFileInfo( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER3 . '/files/Shared' . $this->subfolder + . $this->subsubfolder . '/' . $this->filename); + + // check if we have fileInfos + $this->assertTrue(is_array($fileInfo)); + + // share the file with user3 + \OCP\Share::shareItem('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER4, OCP\PERMISSION_ALL); + + // login as admin + \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1); + + // check if share key for user3 exists + $this->assertTrue($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/share-keys' . $this->folder1 + . $this->subfolder . $this->subsubfolder . '/' + . $this->filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER4 . '.shareKey')); + + // login as user3 + \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER4); + + // get file contents + $retrievedCryptedFile = $this->view->file_get_contents( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER4 . '/files/Shared/' . $this->filename); + + // check if data is the same + $this->assertEquals($this->dataShort, $retrievedCryptedFile); + + // cleanup + if ($withTeardown) { + + // login as user2 + \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER3); + + // unshare the file with user3 + \OCP\Share::unshare('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER4); + + // check if share key not exists + $this->assertFalse($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/share-keys' + . $this->folder1 . $this->subfolder . $this->subsubfolder . '/' + . $this->filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER4 . '.shareKey')); + + // login as user1 + \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2); + + // unshare the folder with user2 + \OCP\Share::unshare('folder', $fileInfoSubFolder['fileid'], \OCP\Share::SHARE_TYPE_USER, \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER3); + + // check if share key not exists + $this->assertFalse($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/share-keys' + . $this->folder1 . $this->subfolder . $this->subsubfolder . '/' + . $this->filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER3 . '.shareKey')); + + // login as admin + \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1); + + // unshare the folder1 with user1 + \OCP\Share::unshare('folder', $fileInfoFolder1['fileid'], \OCP\Share::SHARE_TYPE_USER, \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2); + + // check if share key not exists + $this->assertFalse($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/share-keys' + . $this->folder1 . $this->subfolder . $this->subsubfolder . '/' + . $this->filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '.shareKey')); + + // cleanup + $this->view->unlink( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files' . $this->folder1 . $this->subfolder + . $this->subsubfolder . '/' . $this->filename); + + // check if share key not exists + $this->assertFalse($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/share-keys' + . $this->folder1 . $this->subfolder . $this->subsubfolder . '/' + . $this->filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '.shareKey')); + } + } + + function testPublicShareFile() { + // login as admin + \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1); + + // save file with content + $cryptedFile = file_put_contents('crypt://' . $this->filename, $this->dataShort); + + // test that data was successfully written + $this->assertTrue(is_int($cryptedFile)); + + // disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + // get the file info from previous created file + $fileInfo = $this->view->getFileInfo( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files/' . $this->filename); + + // check if we have a valid file info + $this->assertTrue(is_array($fileInfo)); + + // check if the unencrypted file size is stored + $this->assertGreaterThan(0, $fileInfo['unencrypted_size']); + + // re-enable the file proxy + \OC_FileProxy::$enabled = $proxyStatus; + + // share the file + \OCP\Share::shareItem('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_LINK, false, OCP\PERMISSION_ALL); + + // login as admin + \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1); + + $publicShareKeyId = \OC_Appconfig::getValue('files_encryption', 'publicShareKeyId'); + + // check if share key for public exists + $this->assertTrue($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/share-keys/' + . $this->filename . '.' . $publicShareKeyId . '.shareKey')); + + // some hacking to simulate public link + $GLOBALS['app'] = 'files_sharing'; + $GLOBALS['fileOwner'] = \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1; + \OC_User::setUserId(false); + + // get file contents + $retrievedCryptedFile = file_get_contents('crypt://' . $this->filename); + + // check if data is the same as we previously written + $this->assertEquals($this->dataShort, $retrievedCryptedFile); + + // tear down + + // login as admin + \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1); + + // unshare the file + \OCP\Share::unshare('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_LINK, null); + + // check if share key not exists + $this->assertFalse($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/share-keys/' + . $this->filename . '.' . $publicShareKeyId . '.shareKey')); + + // cleanup + $this->view->unlink('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files/' . $this->filename); + + // check if share key not exists + $this->assertFalse($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/share-keys/' + . $this->filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '.shareKey')); + } + + /** + * @medium + */ + function testShareFileWithGroup() { + // login as admin + \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1); + + // save file with content + $cryptedFile = file_put_contents('crypt://' . $this->filename, $this->dataShort); + + // test that data was successfully written + $this->assertTrue(is_int($cryptedFile)); + + // disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + // get the file info from previous created file + $fileInfo = $this->view->getFileInfo( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files/' . $this->filename); + + // check if we have a valid file info + $this->assertTrue(is_array($fileInfo)); + + // check if the unencrypted file size is stored + $this->assertGreaterThan(0, $fileInfo['unencrypted_size']); + + // re-enable the file proxy + \OC_FileProxy::$enabled = $proxyStatus; + + // share the file + \OCP\Share::shareItem('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_GROUP, \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_GROUP1, OCP\PERMISSION_ALL); + + // login as admin + \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1); + + // check if share key for user2 and user3 exists + $this->assertTrue($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/share-keys/' + . $this->filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER3 . '.shareKey')); + $this->assertTrue($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/share-keys/' + . $this->filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER4 . '.shareKey')); + + // login as user1 + \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER3); + + // get file contents + $retrievedCryptedFile = $this->view->file_get_contents( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER3 . '/files/Shared/' . $this->filename); + + // check if data is the same as we previously written + $this->assertEquals($this->dataShort, $retrievedCryptedFile); + + // login as admin + \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1); + + // unshare the file + \OCP\Share::unshare('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_GROUP, \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_GROUP1); + + // check if share key not exists + $this->assertFalse($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/share-keys/' + . $this->filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER3 . '.shareKey')); + $this->assertFalse($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/share-keys/' + . $this->filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER4 . '.shareKey')); + + // cleanup + $this->view->unlink('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files/' . $this->filename); + + // check if share key not exists + $this->assertFalse($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/share-keys/' + . $this->filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '.shareKey')); + + } + + /** + * @large + */ + function testRecoveryFile() { + // login as admin + \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1); + + \OCA\Encryption\Helper::adminEnableRecovery(null, 'test123'); + $recoveryKeyId = OC_Appconfig::getValue('files_encryption', 'recoveryKeyId'); + + // login as admin + \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1); + + $util = new \OCA\Encryption\Util(new \OC_FilesystemView('/'), \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1); + + // check if recovery password match + $this->assertTrue($util->checkRecoveryPassword('test123')); + + // enable recovery for admin + $this->assertTrue($util->setRecoveryForUser(1)); + + // create folder structure + $this->view->mkdir('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files' . $this->folder1); + $this->view->mkdir( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files' . $this->folder1 . $this->subfolder); + $this->view->mkdir( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files' . $this->folder1 . $this->subfolder + . $this->subsubfolder); + + // save file with content + $cryptedFile1 = file_put_contents('crypt://' . $this->filename, $this->dataShort); + $cryptedFile2 = file_put_contents('crypt://' . $this->folder1 . $this->subfolder . $this->subsubfolder . '/' + . $this->filename, $this->dataShort); + + // test that data was successfully written + $this->assertTrue(is_int($cryptedFile1)); + $this->assertTrue(is_int($cryptedFile2)); + + // check if share key for admin and recovery exists + $this->assertTrue($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/share-keys/' + . $this->filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '.shareKey')); + $this->assertTrue($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/share-keys/' + . $this->filename . '.' . $recoveryKeyId . '.shareKey')); + $this->assertTrue($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/share-keys/' . $this->folder1 + . $this->subfolder . $this->subsubfolder . '/' + . $this->filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '.shareKey')); + $this->assertTrue($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/share-keys/' . $this->folder1 + . $this->subfolder . $this->subsubfolder . '/' + . $this->filename . '.' . $recoveryKeyId . '.shareKey')); + + // disable recovery for admin + $this->assertTrue($util->setRecoveryForUser(0)); + + // remove all recovery keys + $util->removeRecoveryKeys('/'); + + // check if share key for recovery not exists + $this->assertFalse($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/share-keys/' + . $this->filename . '.' . $recoveryKeyId . '.shareKey')); + $this->assertFalse($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/share-keys/' . $this->folder1 + . $this->subfolder . $this->subsubfolder . '/' + . $this->filename . '.' . $recoveryKeyId . '.shareKey')); + + // enable recovery for admin + $this->assertTrue($util->setRecoveryForUser(1)); + + // remove all recovery keys + $util->addRecoveryKeys('/'); + + // check if share key for admin and recovery exists + $this->assertTrue($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/share-keys/' + . $this->filename . '.' . $recoveryKeyId . '.shareKey')); + $this->assertTrue($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/share-keys/' . $this->folder1 + . $this->subfolder . $this->subsubfolder . '/' + . $this->filename . '.' . $recoveryKeyId . '.shareKey')); + + // cleanup + $this->view->unlink('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files/' . $this->filename); + $this->view->unlink('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files/' . $this->folder1); + + // check if share key for recovery not exists + $this->assertFalse($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/share-keys/' + . $this->filename . '.' . $recoveryKeyId . '.shareKey')); + $this->assertFalse($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/share-keys/' . $this->folder1 + . $this->subfolder . $this->subsubfolder . '/' + . $this->filename . '.' . $recoveryKeyId . '.shareKey')); + + $this->assertTrue(\OCA\Encryption\Helper::adminEnableRecovery(null, 'test123')); + $this->assertTrue(\OCA\Encryption\Helper::adminDisableRecovery('test123')); + $this->assertEquals(0, \OC_Appconfig::getValue('files_encryption', 'recoveryAdminEnabled')); + } + + /** + * @large + */ + function testRecoveryForUser() { + // login as admin + \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1); + + \OCA\Encryption\Helper::adminEnableRecovery(null, 'test123'); + $recoveryKeyId = OC_Appconfig::getValue('files_encryption', 'recoveryKeyId'); + + // login as user1 + \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2); + + $util = new \OCA\Encryption\Util(new \OC_FilesystemView('/'), \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2); + + // enable recovery for admin + $this->assertTrue($util->setRecoveryForUser(1)); + + // create folder structure + $this->view->mkdir('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files' . $this->folder1); + $this->view->mkdir( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files' . $this->folder1 . $this->subfolder); + $this->view->mkdir( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files' . $this->folder1 . $this->subfolder + . $this->subsubfolder); + + // save file with content + $cryptedFile1 = file_put_contents('crypt://' . $this->filename, $this->dataShort); + $cryptedFile2 = file_put_contents('crypt://' . $this->folder1 . $this->subfolder . $this->subsubfolder . '/' + . $this->filename, $this->dataShort); + + // test that data was successfully written + $this->assertTrue(is_int($cryptedFile1)); + $this->assertTrue(is_int($cryptedFile2)); + + // check if share key for user and recovery exists + $this->assertTrue($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files_encryption/share-keys/' + . $this->filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '.shareKey')); + $this->assertTrue($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files_encryption/share-keys/' + . $this->filename . '.' . $recoveryKeyId . '.shareKey')); + $this->assertTrue($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files_encryption/share-keys/' . $this->folder1 + . $this->subfolder . $this->subsubfolder . '/' + . $this->filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '.shareKey')); + $this->assertTrue($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files_encryption/share-keys/' . $this->folder1 + . $this->subfolder . $this->subsubfolder . '/' + . $this->filename . '.' . $recoveryKeyId . '.shareKey')); + + // login as admin + \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1); + + // change password + \OC_User::setPassword(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2, 'test', 'test123'); + + // login as user1 + \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2, false, 'test'); + + // get file contents + $retrievedCryptedFile1 = file_get_contents('crypt://' . $this->filename); + $retrievedCryptedFile2 = file_get_contents( + 'crypt://' . $this->folder1 . $this->subfolder . $this->subsubfolder . '/' . $this->filename); + + // check if data is the same as we previously written + $this->assertEquals($this->dataShort, $retrievedCryptedFile1); + $this->assertEquals($this->dataShort, $retrievedCryptedFile2); + + // cleanup + $this->view->unlink('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files' . $this->folder1); + $this->view->unlink('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files' . $this->filename); + + // check if share key for user and recovery exists + $this->assertFalse($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files_encryption/share-keys/' + . $this->filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '.shareKey')); + $this->assertFalse($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files_encryption/share-keys/' + . $this->filename . '.' . $recoveryKeyId . '.shareKey')); + $this->assertFalse($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files_encryption/share-keys/' . $this->folder1 + . $this->subfolder . $this->subsubfolder . '/' + . $this->filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '.shareKey')); + $this->assertFalse($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files_encryption/share-keys/' . $this->folder1 + . $this->subfolder . $this->subsubfolder . '/' + . $this->filename . '.' . $recoveryKeyId . '.shareKey')); + + // enable recovery for admin + $this->assertTrue($util->setRecoveryForUser(0)); + + \OCA\Encryption\Helper::adminDisableRecovery('test123'); + $this->assertEquals(0, \OC_Appconfig::getValue('files_encryption', 'recoveryAdminEnabled')); + } + + /** + * @medium + */ + function testFailShareFile() { + // login as admin + \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1); + + // save file with content + $cryptedFile = file_put_contents('crypt://' . $this->filename, $this->dataShort); + + // test that data was successfully written + $this->assertTrue(is_int($cryptedFile)); + + // disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + // get the file info from previous created file + $fileInfo = $this->view->getFileInfo( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files/' . $this->filename); + + // check if we have a valid file info + $this->assertTrue(is_array($fileInfo)); + + // check if the unencrypted file size is stored + $this->assertGreaterThan(0, $fileInfo['unencrypted_size']); + + // break users public key + $this->view->rename('/public-keys/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER3 . '.public.key', + '/public-keys/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER3 . '.public.key_backup'); + + // re-enable the file proxy + \OC_FileProxy::$enabled = $proxyStatus; + + // share the file + \OCP\Share::shareItem('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_GROUP, \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_GROUP1, OCP\PERMISSION_ALL); + + // login as admin + \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1); + + // check if share key for user1 not exists + $this->assertFalse($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/share-keys/' + . $this->filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER3 . '.shareKey')); + + // disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + // break user1 public key + $this->view->rename( + '/public-keys/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER3 . '.public.key_backup', + '/public-keys/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER3 . '.public.key'); + + // remove share file + $this->view->unlink('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/share-keys/' + . $this->filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER3 + . '.shareKey'); + + // re-enable the file proxy + \OC_FileProxy::$enabled = $proxyStatus; + + // unshare the file with user1 + \OCP\Share::unshare('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_GROUP, \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_GROUP1); + + // check if share key not exists + $this->assertFalse($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/share-keys/' + . $this->filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER3 . '.shareKey')); + + // cleanup + $this->view->unlink('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files/' . $this->filename); + } + +} diff --git a/apps/files_encryption/tests/stream.php b/apps/files_encryption/tests/stream.php new file mode 100644 index 0000000000..50ac41e453 --- /dev/null +++ b/apps/files_encryption/tests/stream.php @@ -0,0 +1,183 @@ + + * + * 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 . + * + */ + +require_once realpath(dirname(__FILE__) . '/../../../lib/base.php'); +require_once realpath(dirname(__FILE__) . '/../lib/crypt.php'); +require_once realpath(dirname(__FILE__) . '/../lib/keymanager.php'); +require_once realpath(dirname(__FILE__) . '/../lib/proxy.php'); +require_once realpath(dirname(__FILE__) . '/../lib/stream.php'); +require_once realpath(dirname(__FILE__) . '/../lib/util.php'); +require_once realpath(dirname(__FILE__) . '/../appinfo/app.php'); +require_once realpath(dirname(__FILE__) . '/util.php'); + +use OCA\Encryption; + +/** + * Class Test_Encryption_Stream + * @brief this class provide basic stream tests + */ +class Test_Encryption_Stream extends \PHPUnit_Framework_TestCase { + + const TEST_ENCRYPTION_STREAM_USER1 = "test-stream-user1"; + + public $userId; + public $pass; + /** + * @var \OC_FilesystemView + */ + public $view; + public $dataShort; + public $stateFilesTrashbin; + + public static function setUpBeforeClass() { + // reset backend + \OC_User::clearBackends(); + \OC_User::useBackend('database'); + + // Filesystem related hooks + \OCA\Encryption\Helper::registerFilesystemHooks(); + + // clear and register hooks + \OC_FileProxy::clearProxies(); + \OC_FileProxy::register(new OCA\Encryption\Proxy()); + + // create test user + \Test_Encryption_Util::loginHelper(\Test_Encryption_Stream::TEST_ENCRYPTION_STREAM_USER1, true); + } + + function setUp() { + // set user id + \OC_User::setUserId(\Test_Encryption_Stream::TEST_ENCRYPTION_STREAM_USER1); + $this->userId = \Test_Encryption_Stream::TEST_ENCRYPTION_STREAM_USER1; + $this->pass = \Test_Encryption_Stream::TEST_ENCRYPTION_STREAM_USER1; + + // init filesystem view + $this->view = new \OC_FilesystemView('/'); + + // init short data + $this->dataShort = 'hats'; + + // remember files_trashbin state + $this->stateFilesTrashbin = OC_App::isEnabled('files_trashbin'); + + // we don't want to tests with app files_trashbin enabled + \OC_App::disable('files_trashbin'); + } + + function tearDown() { + // reset app files_trashbin + if ($this->stateFilesTrashbin) { + OC_App::enable('files_trashbin'); + } + else { + OC_App::disable('files_trashbin'); + } + } + + public static function tearDownAfterClass() { + // cleanup test user + \OC_User::deleteUser(\Test_Encryption_Stream::TEST_ENCRYPTION_STREAM_USER1); + } + + function testStreamOptions() { + $filename = '/tmp-' . time(); + $view = new \OC\Files\View('/' . $this->userId . '/files'); + + // Save short data as encrypted file using stream wrapper + $cryptedFile = $view->file_put_contents($filename, $this->dataShort); + + // Test that data was successfully written + $this->assertTrue(is_int($cryptedFile)); + + $handle = $view->fopen($filename, 'r'); + + // check if stream is at position zero + $this->assertEquals(0, ftell($handle)); + + // set stream options + $this->assertTrue(flock($handle, LOCK_SH)); + $this->assertTrue(flock($handle, LOCK_UN)); + + // tear down + $view->unlink($filename); + } + + function testStreamSetBlocking() { + $filename = '/tmp-' . time(); + $view = new \OC\Files\View('/' . $this->userId . '/files'); + + // Save short data as encrypted file using stream wrapper + $cryptedFile = $view->file_put_contents($filename, $this->dataShort); + + // Test that data was successfully written + $this->assertTrue(is_int($cryptedFile)); + + $handle = $view->fopen($filename, 'r'); + + // set stream options + $this->assertTrue(stream_set_blocking($handle, 1)); + + // tear down + $view->unlink($filename); + } + + /** + * @medium + */ + function testStreamSetTimeout() { + $filename = '/tmp-' . time(); + $view = new \OC\Files\View('/' . $this->userId . '/files'); + + // Save short data as encrypted file using stream wrapper + $cryptedFile = $view->file_put_contents($filename, $this->dataShort); + + // Test that data was successfully written + $this->assertTrue(is_int($cryptedFile)); + + $handle = $view->fopen($filename, 'r'); + + // set stream options + $this->assertFalse(stream_set_timeout($handle, 1)); + + // tear down + $view->unlink($filename); + } + + function testStreamSetWriteBuffer() { + $filename = '/tmp-' . time(); + $view = new \OC\Files\View('/' . $this->userId . '/files'); + + // Save short data as encrypted file using stream wrapper + $cryptedFile = $view->file_put_contents($filename, $this->dataShort); + + // Test that data was successfully written + $this->assertTrue(is_int($cryptedFile)); + + $handle = $view->fopen($filename, 'r'); + + // set stream options + $this->assertEquals(0, stream_set_write_buffer($handle, 1024)); + + // tear down + $view->unlink($filename); + } +} \ No newline at end of file diff --git a/apps/files_encryption/tests/trashbin.php b/apps/files_encryption/tests/trashbin.php new file mode 100755 index 0000000000..ade968fbec --- /dev/null +++ b/apps/files_encryption/tests/trashbin.php @@ -0,0 +1,303 @@ + + * + * 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 . + * + */ + +require_once realpath(dirname(__FILE__) . '/../../../lib/base.php'); +require_once realpath(dirname(__FILE__) . '/../lib/crypt.php'); +require_once realpath(dirname(__FILE__) . '/../lib/keymanager.php'); +require_once realpath(dirname(__FILE__) . '/../lib/proxy.php'); +require_once realpath(dirname(__FILE__) . '/../lib/stream.php'); +require_once realpath(dirname(__FILE__) . '/../lib/util.php'); +require_once realpath(dirname(__FILE__) . '/../appinfo/app.php'); +require_once realpath(dirname(__FILE__) . '/../../files_trashbin/appinfo/app.php'); +require_once realpath(dirname(__FILE__) . '/util.php'); + +use OCA\Encryption; + +/** + * Class Test_Encryption_Trashbin + * @brief this class provide basic trashbin app tests + */ +class Test_Encryption_Trashbin extends \PHPUnit_Framework_TestCase { + + const TEST_ENCRYPTION_TRASHBIN_USER1 = "test-trashbin-user1"; + + public $userId; + public $pass; + /** + * @var \OC_FilesystemView + */ + public $view; + public $dataShort; + public $stateFilesTrashbin; + public $folder1; + public $subfolder; + public $subsubfolder; + + public static function setUpBeforeClass() { + // reset backend + \OC_User::clearBackends(); + \OC_User::useBackend('database'); + + \OC_Hook::clear('OC_Filesystem'); + \OC_Hook::clear('OC_User'); + + // trashbin hooks + \OCA\Files_Trashbin\Trashbin::registerHooks(); + + // Filesystem related hooks + \OCA\Encryption\Helper::registerFilesystemHooks(); + + // clear and register hooks + \OC_FileProxy::clearProxies(); + \OC_FileProxy::register(new OCA\Encryption\Proxy()); + + // create test user + \Test_Encryption_Util::loginHelper(\Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1, true); + } + + function setUp() { + // set user id + \OC_User::setUserId(\Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1); + $this->userId = \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1; + $this->pass = \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1; + + // init filesystem view + $this->view = new \OC_FilesystemView('/'); + + // init short data + $this->dataShort = 'hats'; + + $this->folder1 = '/folder1'; + $this->subfolder = '/subfolder1'; + $this->subsubfolder = '/subsubfolder1'; + + // remember files_trashbin state + $this->stateFilesTrashbin = OC_App::isEnabled('files_trashbin'); + + // we want to tests with app files_trashbin enabled + \OC_App::enable('files_trashbin'); + } + + function tearDown() { + // reset app files_trashbin + if ($this->stateFilesTrashbin) { + OC_App::enable('files_trashbin'); + } + else { + OC_App::disable('files_trashbin'); + } + } + + public static function tearDownAfterClass() { + // cleanup test user + \OC_User::deleteUser(\Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1); + } + + /** + * @medium + * @brief test delete file + */ + function testDeleteFile() { + + // generate filename + $filename = 'tmp-' . time() . '.txt'; + + // save file with content + $cryptedFile = file_put_contents('crypt:///' . $filename, $this->dataShort); + + // test that data was successfully written + $this->assertTrue(is_int($cryptedFile)); + + // check if key for admin exists + $this->assertTrue($this->view->file_exists( + '/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_encryption/keyfiles/' . $filename + . '.key')); + + // check if share key for admin exists + $this->assertTrue($this->view->file_exists( + '/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_encryption/share-keys/' + . $filename . '.' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '.shareKey')); + + // delete file + \OC\FIles\Filesystem::unlink($filename); + + // check if file not exists + $this->assertFalse($this->view->file_exists( + '/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files/' . $filename)); + + // check if key for admin not exists + $this->assertFalse($this->view->file_exists( + '/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_encryption/keyfiles/' . $filename + . '.key')); + + // check if share key for admin not exists + $this->assertFalse($this->view->file_exists( + '/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_encryption/share-keys/' + . $filename . '.' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '.shareKey')); + + // get files + $trashFiles = $this->view->getDirectoryContent( + '/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_trashbin/files/'); + + $trashFileSuffix = null; + // find created file with timestamp + foreach ($trashFiles as $file) { + if (strncmp($file['path'], $filename, strlen($filename))) { + $path_parts = pathinfo($file['name']); + $trashFileSuffix = $path_parts['extension']; + } + } + + // check if we found the file we created + $this->assertNotNull($trashFileSuffix); + + // check if key for admin not exists + $this->assertTrue($this->view->file_exists( + '/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_trashbin/keyfiles/' . $filename + . '.key.' . $trashFileSuffix)); + + // check if share key for admin not exists + $this->assertTrue($this->view->file_exists( + '/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_trashbin/share-keys/' . $filename + . '.' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '.shareKey.' . $trashFileSuffix)); + + // return filename for next test + return $filename . '.' . $trashFileSuffix; + } + + /** + * @medium + * @brief test restore file + * + * @depends testDeleteFile + */ + function testRestoreFile($filename) { + + // prepare file information + $path_parts = pathinfo($filename); + $trashFileSuffix = $path_parts['extension']; + $timestamp = str_replace('d', '', $trashFileSuffix); + $fileNameWithoutSuffix = str_replace('.' . $trashFileSuffix, '', $filename); + + // restore file + $this->assertTrue(\OCA\Files_Trashbin\Trashbin::restore($filename, $fileNameWithoutSuffix, $timestamp)); + + // check if file exists + $this->assertTrue($this->view->file_exists( + '/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files/' . $fileNameWithoutSuffix)); + + // check if key for admin exists + $this->assertTrue($this->view->file_exists( + '/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_encryption/keyfiles/' + . $fileNameWithoutSuffix . '.key')); + + // check if share key for admin exists + $this->assertTrue($this->view->file_exists( + '/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_encryption/share-keys/' + . $fileNameWithoutSuffix . '.' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '.shareKey')); + } + + /** + * @medium + * @brief test delete file forever + */ + function testPermanentDeleteFile() { + + // generate filename + $filename = 'tmp-' . time() . '.txt'; + + // save file with content + $cryptedFile = file_put_contents('crypt:///' . $filename, $this->dataShort); + + // test that data was successfully written + $this->assertTrue(is_int($cryptedFile)); + + // check if key for admin exists + $this->assertTrue($this->view->file_exists( + '/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_encryption/keyfiles/' . $filename + . '.key')); + + // check if share key for admin exists + $this->assertTrue($this->view->file_exists( + '/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_encryption/share-keys/' + . $filename . '.' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '.shareKey')); + + // delete file + \OC\FIles\Filesystem::unlink($filename); + + // check if file not exists + $this->assertFalse($this->view->file_exists( + '/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files/' . $filename)); + + // check if key for admin not exists + $this->assertFalse($this->view->file_exists( + '/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_encryption/keyfiles/' . $filename + . '.key')); + + // check if share key for admin not exists + $this->assertFalse($this->view->file_exists( + '/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_encryption/share-keys/' + . $filename . '.' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '.shareKey')); + + // find created file with timestamp + $query = \OC_DB::prepare('SELECT `timestamp`,`type` FROM `*PREFIX*files_trash`' + . ' WHERE `id`=?'); + $result = $query->execute(array($filename))->fetchRow(); + + $this->assertTrue(is_array($result)); + + // build suffix + $trashFileSuffix = 'd' . $result['timestamp']; + + // check if key for admin exists + $this->assertTrue($this->view->file_exists( + '/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_trashbin/keyfiles/' . $filename + . '.key.' . $trashFileSuffix)); + + // check if share key for admin exists + $this->assertTrue($this->view->file_exists( + '/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_trashbin/share-keys/' . $filename + . '.' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '.shareKey.' . $trashFileSuffix)); + + // get timestamp from file + $timestamp = str_replace('d', '', $trashFileSuffix); + + // delete file forever + $this->assertGreaterThan(0, \OCA\Files_Trashbin\Trashbin::delete($filename, $timestamp)); + + // check if key for admin not exists + $this->assertFalse($this->view->file_exists( + '/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_trashbin/files/' . $filename . '.' + . $trashFileSuffix)); + + // check if key for admin not exists + $this->assertFalse($this->view->file_exists( + '/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_trashbin/keyfiles/' . $filename + . '.key.' . $trashFileSuffix)); + + // check if share key for admin not exists + $this->assertFalse($this->view->file_exists( + '/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_trashbin/share-keys/' . $filename + . '.' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '.shareKey.' . $trashFileSuffix)); + } + +} \ No newline at end of file diff --git a/apps/files_encryption/tests/util.php b/apps/files_encryption/tests/util.php new file mode 100755 index 0000000000..cb10befc8e --- /dev/null +++ b/apps/files_encryption/tests/util.php @@ -0,0 +1,356 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +require_once realpath(dirname(__FILE__) . '/../../../lib/base.php'); +require_once realpath(dirname(__FILE__) . '/../lib/crypt.php'); +require_once realpath(dirname(__FILE__) . '/../lib/keymanager.php'); +require_once realpath(dirname(__FILE__) . '/../lib/proxy.php'); +require_once realpath(dirname(__FILE__) . '/../lib/stream.php'); +require_once realpath(dirname(__FILE__) . '/../lib/util.php'); +require_once realpath(dirname(__FILE__) . '/../appinfo/app.php'); + +use OCA\Encryption; + +/** + * Class Test_Encryption_Util + */ +class Test_Encryption_Util extends \PHPUnit_Framework_TestCase { + + const TEST_ENCRYPTION_UTIL_USER1 = "test-util-user1"; + const TEST_ENCRYPTION_UTIL_LEGACY_USER = "test-legacy-user"; + + public $userId; + public $encryptionDir; + public $publicKeyDir; + public $pass; + /** + * @var OC_FilesystemView + */ + public $view; + public $keyfilesPath; + public $publicKeyPath; + public $privateKeyPath; + /** + * @var \OCA\Encryption\Util + */ + public $util; + public $dataShort; + public $legacyEncryptedData; + public $legacyEncryptedDataKey; + public $legacyKey; + public $stateFilesTrashbin; + + public static function setUpBeforeClass() { + // reset backend + \OC_User::clearBackends(); + \OC_User::useBackend('database'); + + // Filesystem related hooks + \OCA\Encryption\Helper::registerFilesystemHooks(); + + // clear and register hooks + \OC_FileProxy::clearProxies(); + \OC_FileProxy::register(new OCA\Encryption\Proxy()); + + // create test user + \Test_Encryption_Util::loginHelper(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_USER1, true); + \Test_Encryption_Util::loginHelper(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_LEGACY_USER, true); + } + + + function setUp() { + \OC_User::setUserId(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_USER1); + $this->userId = \Test_Encryption_Util::TEST_ENCRYPTION_UTIL_USER1; + $this->pass = \Test_Encryption_Util::TEST_ENCRYPTION_UTIL_USER1; + + // set content for encrypting / decrypting in tests + $this->dataUrl = realpath(dirname(__FILE__) . '/../lib/crypt.php'); + $this->dataShort = 'hats'; + $this->dataLong = file_get_contents(realpath(dirname(__FILE__) . '/../lib/crypt.php')); + $this->legacyData = realpath(dirname(__FILE__) . '/legacy-text.txt'); + $this->legacyEncryptedData = realpath(dirname(__FILE__) . '/legacy-encrypted-text.txt'); + $this->legacyEncryptedDataKey = realpath(dirname(__FILE__) . '/encryption.key'); + $this->legacyKey = "30943623843030686906\0\0\0\0"; + + $keypair = Encryption\Crypt::createKeypair(); + + $this->genPublicKey = $keypair['publicKey']; + $this->genPrivateKey = $keypair['privateKey']; + + $this->publicKeyDir = '/' . 'public-keys'; + $this->encryptionDir = '/' . $this->userId . '/' . 'files_encryption'; + $this->keyfilesPath = $this->encryptionDir . '/' . 'keyfiles'; + $this->publicKeyPath = + $this->publicKeyDir . '/' . $this->userId . '.public.key'; // e.g. data/public-keys/admin.public.key + $this->privateKeyPath = + $this->encryptionDir . '/' . $this->userId . '.private.key'; // e.g. data/admin/admin.private.key + + $this->view = new \OC_FilesystemView('/'); + + $this->util = new Encryption\Util($this->view, $this->userId); + + // remember files_trashbin state + $this->stateFilesTrashbin = OC_App::isEnabled('files_trashbin'); + + // we don't want to tests with app files_trashbin enabled + \OC_App::disable('files_trashbin'); + } + + function tearDown() { + // reset app files_trashbin + if ($this->stateFilesTrashbin) { + OC_App::enable('files_trashbin'); + } + else { + OC_App::disable('files_trashbin'); + } + } + + public static function tearDownAfterClass() { + // cleanup test user + \OC_User::deleteUser(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_USER1); + \OC_User::deleteUser(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_LEGACY_USER); + } + + /** + * @medium + * @brief test that paths set during User construction are correct + */ + function testKeyPaths() { + $util = new Encryption\Util($this->view, $this->userId); + + $this->assertEquals($this->publicKeyDir, $util->getPath('publicKeyDir')); + $this->assertEquals($this->encryptionDir, $util->getPath('encryptionDir')); + $this->assertEquals($this->keyfilesPath, $util->getPath('keyfilesPath')); + $this->assertEquals($this->publicKeyPath, $util->getPath('publicKeyPath')); + $this->assertEquals($this->privateKeyPath, $util->getPath('privateKeyPath')); + + } + + /** + * @medium + * @brief test setup of encryption directories + */ + function testSetupServerSide() { + $this->assertEquals(true, $this->util->setupServerSide($this->pass)); + } + + /** + * @medium + * @brief test checking whether account is ready for encryption, + */ + function testUserIsReady() { + $this->assertEquals(true, $this->util->ready()); + } + + /** + * @brief test checking whether account is not ready for encryption, + */ +// function testUserIsNotReady() { +// $this->view->unlink($this->publicKeyDir); +// +// $params['uid'] = $this->userId; +// $params['password'] = $this->pass; +// $this->assertFalse(OCA\Encryption\Hooks::login($params)); +// +// $this->view->unlink($this->privateKeyPath); +// } + + /** + * @medium + * @brief test checking whether account is not ready for encryption, + */ + function testIsLegacyUser() { + \Test_Encryption_Util::loginHelper(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_LEGACY_USER); + + $userView = new \OC_FilesystemView('/' . \Test_Encryption_Util::TEST_ENCRYPTION_UTIL_LEGACY_USER); + + // Disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + $encryptionKeyContent = file_get_contents($this->legacyEncryptedDataKey); + $userView->file_put_contents('/encryption.key', $encryptionKeyContent); + + \OC_FileProxy::$enabled = $proxyStatus; + + $params['uid'] = \Test_Encryption_Util::TEST_ENCRYPTION_UTIL_LEGACY_USER; + $params['password'] = \Test_Encryption_Util::TEST_ENCRYPTION_UTIL_LEGACY_USER; + + $this->setMigrationStatus(0, \Test_Encryption_Util::TEST_ENCRYPTION_UTIL_LEGACY_USER); + + $this->assertTrue(OCA\Encryption\Hooks::login($params)); + + $this->assertEquals($this->legacyKey, \OC::$session->get('legacyKey')); + } + + /** + * @medium + */ + function testRecoveryEnabledForUser() { + + $util = new Encryption\Util($this->view, $this->userId); + + // Record the value so we can return it to it's original state later + $enabled = $util->recoveryEnabledForUser(); + + $this->assertTrue($util->setRecoveryForUser(1)); + + $this->assertEquals(1, $util->recoveryEnabledForUser()); + + $this->assertTrue($util->setRecoveryForUser(0)); + + $this->assertEquals(0, $util->recoveryEnabledForUser()); + + // Return the setting to it's previous state + $this->assertTrue($util->setRecoveryForUser($enabled)); + + } + + /** + * @medium + */ + function testGetUidAndFilename() { + + \OC_User::setUserId(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_USER1); + + $filename = 'tmp-' . time() . '.test'; + + // Disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + $this->view->file_put_contents($this->userId . '/files/' . $filename, $this->dataShort); + + // Re-enable proxy - our work is done + \OC_FileProxy::$enabled = $proxyStatus; + + $util = new Encryption\Util($this->view, $this->userId); + + list($fileOwnerUid, $file) = $util->getUidAndFilename($filename); + + $this->assertEquals(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_USER1, $fileOwnerUid); + + $this->assertEquals($file, $filename); + + $this->view->unlink($this->userId . '/files/' . $filename); + } + + /** + * @medium + */ + function testIsSharedPath() { + $sharedPath = '/user1/files/Shared/test'; + $path = '/user1/files/test'; + + $this->assertTrue($this->util->isSharedPath($sharedPath)); + + $this->assertFalse($this->util->isSharedPath($path)); + } + + /** + * @large + */ + function testEncryptLegacyFiles() { + \Test_Encryption_Util::loginHelper(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_LEGACY_USER); + + $userView = new \OC_FilesystemView('/' . \Test_Encryption_Util::TEST_ENCRYPTION_UTIL_LEGACY_USER); + $view = new \OC_FilesystemView('/' . \Test_Encryption_Util::TEST_ENCRYPTION_UTIL_LEGACY_USER . '/files'); + + // Disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + $encryptionKeyContent = file_get_contents($this->legacyEncryptedDataKey); + $userView->file_put_contents('/encryption.key', $encryptionKeyContent); + + $legacyEncryptedData = file_get_contents($this->legacyEncryptedData); + $view->mkdir('/test/'); + $view->mkdir('/test/subtest/'); + $view->file_put_contents('/test/subtest/legacy-encrypted-text.txt', $legacyEncryptedData); + + $fileInfo = $view->getFileInfo('/test/subtest/legacy-encrypted-text.txt'); + $fileInfo['encrypted'] = true; + $view->putFileInfo('/test/subtest/legacy-encrypted-text.txt', $fileInfo); + + \OC_FileProxy::$enabled = $proxyStatus; + + $params['uid'] = \Test_Encryption_Util::TEST_ENCRYPTION_UTIL_LEGACY_USER; + $params['password'] = \Test_Encryption_Util::TEST_ENCRYPTION_UTIL_LEGACY_USER; + + $util = new Encryption\Util($this->view, \Test_Encryption_Util::TEST_ENCRYPTION_UTIL_LEGACY_USER); + $this->setMigrationStatus(0, \Test_Encryption_Util::TEST_ENCRYPTION_UTIL_LEGACY_USER); + + $this->assertTrue(OCA\Encryption\Hooks::login($params)); + + $this->assertEquals($this->legacyKey, \OC::$session->get('legacyKey')); + + $files = $util->findEncFiles('/' . \Test_Encryption_Util::TEST_ENCRYPTION_UTIL_LEGACY_USER . '/files/'); + + $this->assertTrue(is_array($files)); + + $found = false; + foreach ($files['encrypted'] as $encryptedFile) { + if ($encryptedFile['name'] === 'legacy-encrypted-text.txt') { + $found = true; + break; + } + } + + $this->assertTrue($found); + } + + /** + * @param $user + * @param bool $create + * @param bool $password + */ + public 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; + OCA\Encryption\Hooks::login($params); + } + + /** + * helper function to set migration status to the right value + * to be able to test the migration path + * + * @param $status needed migration status for test + * @param $user for which user the status should be set + * @return boolean + */ + private function setMigrationStatus($status, $user) { + $sql = 'UPDATE `*PREFIX*encryption` SET `migration_status` = ? WHERE `uid` = ?'; + $args = array( + $status, + $user + ); + + $query = \OCP\DB::prepare($sql); + if ($query->execute($args)) { + return true; + } else { + return false; + } + } + +} diff --git a/apps/files_encryption/tests/webdav.php b/apps/files_encryption/tests/webdav.php new file mode 100755 index 0000000000..1d406789f0 --- /dev/null +++ b/apps/files_encryption/tests/webdav.php @@ -0,0 +1,262 @@ + + * + * 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 . + * + */ + +require_once realpath(dirname(__FILE__) . '/../../../lib/base.php'); +require_once realpath(dirname(__FILE__) . '/../lib/crypt.php'); +require_once realpath(dirname(__FILE__) . '/../lib/keymanager.php'); +require_once realpath(dirname(__FILE__) . '/../lib/proxy.php'); +require_once realpath(dirname(__FILE__) . '/../lib/stream.php'); +require_once realpath(dirname(__FILE__) . '/../lib/util.php'); +require_once realpath(dirname(__FILE__) . '/../appinfo/app.php'); +require_once realpath(dirname(__FILE__) . '/util.php'); + +use OCA\Encryption; + +/** + * Class Test_Encryption_Webdav + * @brief this class provide basic webdav tests for PUT,GET and DELETE + */ +class Test_Encryption_Webdav extends \PHPUnit_Framework_TestCase { + + const TEST_ENCRYPTION_WEBDAV_USER1 = "test-webdav-user1"; + + public $userId; + public $pass; + /** + * @var \OC_FilesystemView + */ + public $view; + public $dataShort; + public $stateFilesTrashbin; + + public static function setUpBeforeClass() { + // reset backend + \OC_User::clearBackends(); + \OC_User::useBackend('database'); + + // Filesystem related hooks + \OCA\Encryption\Helper::registerFilesystemHooks(); + + // Filesystem related hooks + \OCA\Encryption\Helper::registerUserHooks(); + + // clear and register hooks + \OC_FileProxy::clearProxies(); + \OC_FileProxy::register(new OCA\Encryption\Proxy()); + + // create test user + \Test_Encryption_Util::loginHelper(\Test_Encryption_Webdav::TEST_ENCRYPTION_WEBDAV_USER1, true); + } + + function setUp() { + // reset backend + \OC_User::useBackend('database'); + + // set user id + \OC_User::setUserId(\Test_Encryption_Webdav::TEST_ENCRYPTION_WEBDAV_USER1); + $this->userId = \Test_Encryption_Webdav::TEST_ENCRYPTION_WEBDAV_USER1; + $this->pass = \Test_Encryption_Webdav::TEST_ENCRYPTION_WEBDAV_USER1; + + // init filesystem view + $this->view = new \OC_FilesystemView('/'); + + // init short data + $this->dataShort = 'hats'; + + // remember files_trashbin state + $this->stateFilesTrashbin = OC_App::isEnabled('files_trashbin'); + + // we don't want to tests with app files_trashbin enabled + \OC_App::disable('files_trashbin'); + + // create test user + \Test_Encryption_Util::loginHelper(\Test_Encryption_Webdav::TEST_ENCRYPTION_WEBDAV_USER1); + } + + function tearDown() { + // reset app files_trashbin + if ($this->stateFilesTrashbin) { + OC_App::enable('files_trashbin'); + } + else { + OC_App::disable('files_trashbin'); + } + } + + public static function tearDownAfterClass() { + // cleanup test user + \OC_User::deleteUser(\Test_Encryption_Webdav::TEST_ENCRYPTION_WEBDAV_USER1); + } + + /** + * @brief test webdav put random file + */ + function testWebdavPUT() { + + // generate filename + $filename = '/tmp-' . time() . '.txt'; + + // set server vars + $_SERVER['REQUEST_METHOD'] = 'OPTIONS'; + + $_SERVER['REQUEST_METHOD'] = 'PUT'; + $_SERVER['REQUEST_URI'] = '/remote.php/webdav' . $filename; + $_SERVER['HTTP_AUTHORIZATION'] = 'Basic dGVzdC13ZWJkYXYtdXNlcjE6dGVzdC13ZWJkYXYtdXNlcjE='; + $_SERVER['CONTENT_TYPE'] = 'application/octet-stream'; + $_SERVER['PATH_INFO'] = '/webdav' . $filename; + $_SERVER['CONTENT_LENGTH'] = strlen($this->dataShort); + + // handle webdav request + $this->handleWebdavRequest($this->dataShort); + + // check if file was created + $this->assertTrue($this->view->file_exists('/' . $this->userId . '/files' . $filename)); + + // check if key-file was created + $this->assertTrue($this->view->file_exists( + '/' . $this->userId . '/files_encryption/keyfiles/' . $filename . '.key')); + + // check if shareKey-file was created + $this->assertTrue($this->view->file_exists( + '/' . $this->userId . '/files_encryption/share-keys/' . $filename . '.' . $this->userId . '.shareKey')); + + // disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + // get encrypted file content + $encryptedContent = $this->view->file_get_contents('/' . $this->userId . '/files' . $filename); + + // restore proxy state + \OC_FileProxy::$enabled = $proxyStatus; + + // check if encrypted content is valid + $this->assertTrue(Encryption\Crypt::isCatfileContent($encryptedContent)); + + // get decrypted file contents + $decrypt = file_get_contents('crypt://' . $filename); + + // check if file content match with the written content + $this->assertEquals($this->dataShort, $decrypt); + + // return filename for next test + return $filename; + } + + /** + * @brief test webdav get random file + * + * @depends testWebdavPUT + */ + function testWebdavGET($filename) { + + // set server vars + $_SERVER['REQUEST_METHOD'] = 'GET'; + $_SERVER['REQUEST_URI'] = '/remote.php/webdav' . $filename; + $_SERVER['HTTP_AUTHORIZATION'] = 'Basic dGVzdC13ZWJkYXYtdXNlcjE6dGVzdC13ZWJkYXYtdXNlcjE='; + $_SERVER['PATH_INFO'] = '/webdav' . $filename; + + // handle webdav request + $content = $this->handleWebdavRequest(); + + // check if file content match with the written content + $this->assertEquals($this->dataShort, $content); + + // return filename for next test + return $filename; + } + + /** + * @brief test webdav delete random file + * @depends testWebdavGET + */ + function testWebdavDELETE($filename) { + // set server vars + $_SERVER['REQUEST_METHOD'] = 'DELETE'; + $_SERVER['REQUEST_URI'] = '/remote.php/webdav' . $filename; + $_SERVER['HTTP_AUTHORIZATION'] = 'Basic dGVzdC13ZWJkYXYtdXNlcjE6dGVzdC13ZWJkYXYtdXNlcjE='; + $_SERVER['PATH_INFO'] = '/webdav' . $filename; + + // handle webdav request + $content = $this->handleWebdavRequest(); + + // check if file was removed + $this->assertFalse($this->view->file_exists('/' . $this->userId . '/files' . $filename)); + + // check if key-file was removed + $this->assertFalse($this->view->file_exists( + '/' . $this->userId . '/files_encryption/keyfiles' . $filename . '.key')); + + // check if shareKey-file was removed + $this->assertFalse($this->view->file_exists( + '/' . $this->userId . '/files_encryption/share-keys' . $filename . '.' . $this->userId . '.shareKey')); + } + + /** + * @brief handle webdav request + * + * @param bool $body + * + * @note this init procedure is copied from /apps/files/appinfo/remote.php + */ + function handleWebdavRequest($body = false) { + // Backends + $authBackend = new OC_Connector_Sabre_Auth(); + $lockBackend = new OC_Connector_Sabre_Locks(); + $requestBackend = new OC_Connector_Sabre_Request(); + + // Create ownCloud Dir + $publicDir = new OC_Connector_Sabre_Directory(''); + + // Fire up server + $server = new Sabre_DAV_Server($publicDir); + $server->httpRequest = $requestBackend; + $server->setBaseUri('/remote.php/webdav/'); + + // Load plugins + $server->addPlugin(new Sabre_DAV_Auth_Plugin($authBackend, 'ownCloud')); + $server->addPlugin(new Sabre_DAV_Locks_Plugin($lockBackend)); + $server->addPlugin(new Sabre_DAV_Browser_Plugin(false)); // Show something in the Browser, but no upload + $server->addPlugin(new OC_Connector_Sabre_QuotaPlugin()); + $server->addPlugin(new OC_Connector_Sabre_MaintenancePlugin()); + + // And off we go! + if ($body) { + $server->httpRequest->setBody($body); + } + + // turn on output buffering + ob_start(); + + // handle request + $server->exec(); + + // file content is written in the output buffer + $content = ob_get_contents(); + + // flush the output buffer and turn off output buffering + ob_end_clean(); + + // return captured content + return $content; + } +} \ No newline at end of file diff --git a/apps/files_encryption/test/zeros b/apps/files_encryption/tests/zeros similarity index 100% rename from apps/files_encryption/test/zeros rename to apps/files_encryption/tests/zeros diff --git a/apps/files_external/ajax/addRootCertificate.php b/apps/files_external/ajax/addRootCertificate.php index 43fd6752c4..ae349bfcd3 100644 --- a/apps/files_external/ajax/addRootCertificate.php +++ b/apps/files_external/ajax/addRootCertificate.php @@ -29,8 +29,12 @@ if ($isValid == false) { // add the certificate if it could be verified if ( $isValid ) { + // disable proxy to prevent multiple fopen calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; $view->file_put_contents($filename, $data); OC_Mount_Config::createCertificateBundle(); + \OC_FileProxy::$enabled = $proxyStatus; } else { OCP\Util::writeLog('files_external', 'Couldn\'t import SSL root certificate ('.$filename.'), allowed formats: PEM and DER', diff --git a/apps/files_external/js/settings.js b/apps/files_external/js/settings.js index ac408786ff..3e605c59a9 100644 --- a/apps/files_external/js/settings.js +++ b/apps/files_external/js/settings.js @@ -88,7 +88,7 @@ OC.MountConfig={ url: OC.filePath('files_external', 'ajax', 'removeMountPoint.php'), data: { mountPoint: mountPoint, - class: backendClass, + 'class': backendClass, classOptions: classOptions, mountType: mountType, applicable: applicable, @@ -103,7 +103,7 @@ OC.MountConfig={ url: OC.filePath('files_external', 'ajax', 'removeMountPoint.php'), data: { mountPoint: mountPoint, - class: backendClass, + 'class': backendClass, classOptions: classOptions, mountType: mountType, applicable: applicable, @@ -247,15 +247,18 @@ $(document).ready(function() { OC.MountConfig.saveStorage($(this).parent().parent()); }); + $('#sslCertificate').on('click', 'td.remove>img', function() { + var $tr = $(this).parent().parent(); + var row = this.parentNode.parentNode; + $.post(OC.filePath('files_external', 'ajax', 'removeRootCertificate.php'), {cert: row.id}); + $tr.remove(); + return true; + }); + $('#externalStorage').on('click', 'td.remove>img', function() { var tr = $(this).parent().parent(); var mountPoint = $(tr).find('.mountPoint input').val(); - if ( ! mountPoint) { - var row=this.parentNode.parentNode; - $.post(OC.filePath('files_external', 'ajax', 'removeRootCertificate.php'), { cert: row.id }); - $(tr).remove(); - return true; - } + if ($('#externalStorage').data('admin') === true) { var isPersonal = false; var multiselect = $(tr).find('.chzn-select').val(); diff --git a/apps/files_external/l10n/es_AR.php b/apps/files_external/l10n/es_AR.php index 6706aa43a3..a844615272 100644 --- a/apps/files_external/l10n/es_AR.php +++ b/apps/files_external/l10n/es_AR.php @@ -6,6 +6,7 @@ "Error configuring Google Drive storage" => "Error al configurar el almacenamiento de Google Drive", "Warning: \"smbclient\" is not installed. Mounting of CIFS/SMB shares is not possible. Please ask your system administrator to install it." => "Advertencia: El cliente smb (smbclient) no se encuentra instalado. El montado de archivos o ficheros CIFS/SMB no es posible. Por favor pida al administrador de su sistema que lo instale.", "Warning: The FTP support in PHP is not enabled or installed. Mounting of FTP shares is not possible. Please ask your system administrator to install it." => "Advertencia: El soporte de FTP en PHP no se encuentra instalado. El montado de archivos o ficheros FTP no es posible. Por favor pida al administrador de su sistema que lo instale.", +"Warning: The Curl support in PHP is not enabled or installed. Mounting of ownCloud / WebDAV or GoogleDrive is not possible. Please ask your system administrator to install it." => "Advertencia: El soporte de Curl de PHP no está activado ni instalado. Montar servicios ownCloud, WebDAV y/o GoogleDrive no será posible. Pedile al administrador del sistema que lo instale.", "External Storage" => "Almacenamiento externo", "Folder name" => "Nombre de la carpeta", "External storage" => "Almacenamiento externo", diff --git a/apps/files_external/l10n/lt_LT.php b/apps/files_external/l10n/lt_LT.php index 9bf997d87c..29c962d9a8 100644 --- a/apps/files_external/l10n/lt_LT.php +++ b/apps/files_external/l10n/lt_LT.php @@ -6,6 +6,7 @@ "Error configuring Google Drive storage" => "Klaida nustatinėjant Google Drive talpyklą", "Warning: \"smbclient\" is not installed. Mounting of CIFS/SMB shares is not possible. Please ask your system administrator to install it." => "Įspėjimas: \"smbclient\" nėra įdiegtas. CIFS/SMB dalinimasis nėra galimas. Prašome susisiekti su sistemos administratoriumi kad būtų įdiegtas \"smbclient\"", "Warning: The FTP support in PHP is not enabled or installed. Mounting of FTP shares is not possible. Please ask your system administrator to install it." => "Įspėjimas: FTP palaikymas PHP sistemoje nėra įjungtas arba nėra įdiegtas. FTP dalinimosi įjungimas nėra galimas. Prašome susisiekti su sistemos administratoriumi kad būtų įdiegtas FTP palaikymas. ", +"Warning: The Curl support in PHP is not enabled or installed. Mounting of ownCloud / WebDAV or GoogleDrive is not possible. Please ask your system administrator to install it." => "Įspėjimas: \"Curl\" palaikymas PHP terpėje nėra įjungtas arba įdiegtas. ownCloud/WebDAV ar GoogleDrive įjungimas nebus įmanomas. Prašome susisiekti su sistemos administratoriumi kad būtų įdiegtas arba įjungtas \"Curl\" palaikymas.", "External Storage" => "Išorinės saugyklos", "Folder name" => "Katalogo pavadinimas", "External storage" => "Išorinė saugykla", diff --git a/apps/files_external/l10n/nn_NO.php b/apps/files_external/l10n/nn_NO.php index 4b4b6167d8..998c3f8245 100644 --- a/apps/files_external/l10n/nn_NO.php +++ b/apps/files_external/l10n/nn_NO.php @@ -1,4 +1,5 @@ "Innstillingar", "Groups" => "Grupper", "Users" => "Brukarar", "Delete" => "Slett" diff --git a/apps/files_external/l10n/pl.php b/apps/files_external/l10n/pl.php index cd1b1fe84a..e03ded1e70 100644 --- a/apps/files_external/l10n/pl.php +++ b/apps/files_external/l10n/pl.php @@ -6,6 +6,7 @@ "Error configuring Google Drive storage" => "Wystąpił błąd podczas konfigurowania zasobu Google Drive", "Warning: \"smbclient\" is not installed. Mounting of CIFS/SMB shares is not possible. Please ask your system administrator to install it." => "Ostrzeżenie: \"smbclient\" nie jest zainstalowany. Zamontowanie katalogów CIFS/SMB nie jest możliwe. Skontaktuj sie z administratorem w celu zainstalowania.", "Warning: The FTP support in PHP is not enabled or installed. Mounting of FTP shares is not possible. Please ask your system administrator to install it." => "Ostrzeżenie: Wsparcie dla FTP w PHP nie jest zainstalowane lub włączone. Skontaktuj sie z administratorem w celu zainstalowania lub włączenia go.", +"Warning: The Curl support in PHP is not enabled or installed. Mounting of ownCloud / WebDAV or GoogleDrive is not possible. Please ask your system administrator to install it." => "Ostrzeżenie: Wsparcie dla Curl w PHP nie jest zainstalowane lub włączone. Montowanie WebDAV lub GoogleDrive nie będzie możliwe. Skontaktuj się z administratorem w celu zainstalowania lub włączenia tej opcji.", "External Storage" => "Zewnętrzna zasoby dyskowe", "Folder name" => "Nazwa folderu", "External storage" => "Zewnętrzne zasoby dyskowe", diff --git a/apps/files_external/l10n/ru_RU.php b/apps/files_external/l10n/ru_RU.php index 406e284b27..a43417dfbc 100644 --- a/apps/files_external/l10n/ru_RU.php +++ b/apps/files_external/l10n/ru_RU.php @@ -1,23 +1,4 @@ "Доступ разрешен", -"Error configuring Dropbox storage" => "Ошибка при конфигурировании хранилища Dropbox", -"Grant access" => "Предоставить доступ", -"Please provide a valid Dropbox app key and secret." => "Пожалуйста представьте допустимый ключ приложения Dropbox и пароль.", -"Error configuring Google Drive storage" => "Ошибка настройки хранилища Google Drive", -"Warning: \"smbclient\" is not installed. Mounting of CIFS/SMB shares is not possible. Please ask your system administrator to install it." => "Предупреждение: \"smbclient\" не установлен. Подключение общих папок CIFS/SMB невозможно. Пожалуйста, обратитесь к системному администратору, чтобы установить его.", -"Warning: The FTP support in PHP is not enabled or installed. Mounting of FTP shares is not possible. Please ask your system administrator to install it." => "Предупреждение: Поддержка FTP в PHP не включена или не установлена. Подключение по FTP невозможно. Пожалуйста, обратитесь к системному администратору, чтобы установить ее.", -"External Storage" => "Внешние системы хранения данных", -"Folder name" => "Имя папки", -"Configuration" => "Конфигурация", -"Options" => "Опции", -"Applicable" => "Применимый", -"None set" => "Не задан", -"All Users" => "Все пользователи", "Groups" => "Группы", -"Users" => "Пользователи", -"Delete" => "Удалить", -"Enable User External Storage" => "Включить пользовательскую внешнюю систему хранения данных", -"Allow users to mount their own external storage" => "Разрешить пользователям монтировать их собственную внешнюю систему хранения данных", -"SSL root certificates" => "Корневые сертификаты SSL", -"Import Root Certificate" => "Импортировать корневые сертификаты" +"Delete" => "Удалить" ); diff --git a/apps/files_external/l10n/sv.php b/apps/files_external/l10n/sv.php index 45d3589228..80e68ab6e0 100644 --- a/apps/files_external/l10n/sv.php +++ b/apps/files_external/l10n/sv.php @@ -6,6 +6,7 @@ "Error configuring Google Drive storage" => "Fel vid konfigurering av Google Drive", "Warning: \"smbclient\" is not installed. Mounting of CIFS/SMB shares is not possible. Please ask your system administrator to install it." => "Varning: \"smb-klienten\" är inte installerad. Montering av CIFS/SMB delningar är inte möjligt. Kontakta din systemadministratör för att få den installerad.", "Warning: The FTP support in PHP is not enabled or installed. Mounting of FTP shares is not possible. Please ask your system administrator to install it." => "Varning: Stöd för FTP i PHP är inte aktiverat eller installerat. Montering av FTP-delningar är inte möjligt. Kontakta din systemadministratör för att få det installerat.", +"Warning: The Curl support in PHP is not enabled or installed. Mounting of ownCloud / WebDAV or GoogleDrive is not possible. Please ask your system administrator to install it." => "Varning: Curl-stöd i PHP är inte aktiverat eller installerat. Montering av ownCloud / WebDAV eller GoogleDrive är inte möjligt. Vänligen be din administratör att installera det.", "External Storage" => "Extern lagring", "Folder name" => "Mappnamn", "External storage" => "Extern lagring", diff --git a/apps/files_external/l10n/zh_CN.GB2312.php b/apps/files_external/l10n/zh_CN.GB2312.php index c74323db17..47298f8007 100644 --- a/apps/files_external/l10n/zh_CN.GB2312.php +++ b/apps/files_external/l10n/zh_CN.GB2312.php @@ -6,6 +6,7 @@ "Error configuring Google Drive storage" => "配置 Google Drive 存储失败", "Warning: \"smbclient\" is not installed. Mounting of CIFS/SMB shares is not possible. Please ask your system administrator to install it." => "注意:“SMB客户端”未安装。CIFS/SMB分享不可用。请向您的系统管理员请求安装该客户端。", "Warning: The FTP support in PHP is not enabled or installed. Mounting of FTP shares is not possible. Please ask your system administrator to install it." => "注意:PHP的FTP支持尚未启用或未安装。FTP分享不可用。请向您的系统管理员请求安装。", +"Warning: The Curl support in PHP is not enabled or installed. Mounting of ownCloud / WebDAV or GoogleDrive is not possible. Please ask your system administrator to install it." => "警告: PHP 的 Curl 支持没有安装或打开。挂载 ownCloud、WebDAV 或 Google Drive 的功能将不可用。请询问您的系统管理员去安装它。", "External Storage" => "外部存储", "Folder name" => "文件夹名", "External storage" => "外部存储", diff --git a/apps/files_external/lib/smb.php b/apps/files_external/lib/smb.php index 655c3c9a81..81a6c95638 100644 --- a/apps/files_external/lib/smb.php +++ b/apps/files_external/lib/smb.php @@ -57,12 +57,22 @@ class SMB extends \OC\Files\Storage\StreamWrapper{ public function stat($path) { if ( ! $path and $this->root=='/') {//mtime doesn't work for shares - $mtime=$this->shareMTime(); $stat=stat($this->constructUrl($path)); + if (empty($stat)) { + return false; + } + $mtime=$this->shareMTime(); $stat['mtime']=$mtime; return $stat; } else { - return stat($this->constructUrl($path)); + $stat = stat($this->constructUrl($path)); + + // smb4php can return an empty array if the connection could not be established + if (empty($stat)) { + return false; + } + + return $stat; } } diff --git a/apps/files_external/tests/config.php b/apps/files_external/tests/config.php index 1d4f30c713..bac594b485 100644 --- a/apps/files_external/tests/config.php +++ b/apps/files_external/tests/config.php @@ -1,4 +1,13 @@ use them +$privateConfigFile = $_SERVER['HOME'] . '/owncloud-extfs-test-config.php'; +if (file_exists($privateConfigFile)) { + $config = include($privateConfigFile); + return $config; +} + +// this is now more a template now for your private configurations return array( 'ftp'=>array( 'run'=>false, diff --git a/apps/files_sharing/l10n/af_ZA.php b/apps/files_sharing/l10n/af_ZA.php index 344585a62f..04e194530b 100644 --- a/apps/files_sharing/l10n/af_ZA.php +++ b/apps/files_sharing/l10n/af_ZA.php @@ -1,4 +1,3 @@ "Wagwoord", -"web services under your control" => "webdienste onder jou beheer" +"Password" => "Wagwoord" ); diff --git a/apps/files_sharing/l10n/ar.php b/apps/files_sharing/l10n/ar.php index 4cf3f8c092..768df3d16c 100644 --- a/apps/files_sharing/l10n/ar.php +++ b/apps/files_sharing/l10n/ar.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s شارك المجلد %s معك", "%s shared the file %s with you" => "%s شارك الملف %s معك", "Download" => "تحميل", -"No preview available for" => "لا يوجد عرض مسبق لـ", -"web services under your control" => "خدمات الشبكة تحت سيطرتك" +"No preview available for" => "لا يوجد عرض مسبق لـ" ); diff --git a/apps/files_sharing/l10n/bg_BG.php b/apps/files_sharing/l10n/bg_BG.php index ac94358c4f..9fb9f78340 100644 --- a/apps/files_sharing/l10n/bg_BG.php +++ b/apps/files_sharing/l10n/bg_BG.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s сподели папката %s с Вас", "%s shared the file %s with you" => "%s сподели файла %s с Вас", "Download" => "Изтегляне", -"No preview available for" => "Няма наличен преглед за", -"web services under your control" => "уеб услуги под Ваш контрол" +"No preview available for" => "Няма наличен преглед за" ); diff --git a/apps/files_sharing/l10n/bn_BD.php b/apps/files_sharing/l10n/bn_BD.php index 5fdf6de50c..9fdfee6dfb 100644 --- a/apps/files_sharing/l10n/bn_BD.php +++ b/apps/files_sharing/l10n/bn_BD.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s আপনার সাথে %s ফোল্ডারটি ভাগাভাগি করেছেন", "%s shared the file %s with you" => "%s আপনার সাথে %s ফাইলটি ভাগাভাগি করেছেন", "Download" => "ডাউনলোড", -"No preview available for" => "এর জন্য কোন প্রাকবীক্ষণ সুলভ নয়", -"web services under your control" => "ওয়েব সার্ভিস আপনার হাতের মুঠোয়" +"No preview available for" => "এর জন্য কোন প্রাকবীক্ষণ সুলভ নয়" ); diff --git a/apps/files_sharing/l10n/ca.php b/apps/files_sharing/l10n/ca.php index 223495455f..af924e60dd 100644 --- a/apps/files_sharing/l10n/ca.php +++ b/apps/files_sharing/l10n/ca.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s ha compartit la carpeta %s amb vós", "%s shared the file %s with you" => "%s ha compartit el fitxer %s amb vós", "Download" => "Baixa", -"No preview available for" => "No hi ha vista prèvia disponible per a", -"web services under your control" => "controleu els vostres serveis web" +"No preview available for" => "No hi ha vista prèvia disponible per a" ); diff --git a/apps/files_sharing/l10n/cs_CZ.php b/apps/files_sharing/l10n/cs_CZ.php index 9889fae488..507955d4bd 100644 --- a/apps/files_sharing/l10n/cs_CZ.php +++ b/apps/files_sharing/l10n/cs_CZ.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s s Vámi sdílí složku %s", "%s shared the file %s with you" => "%s s Vámi sdílí soubor %s", "Download" => "Stáhnout", -"No preview available for" => "Náhled není dostupný pro", -"web services under your control" => "služby webu pod Vaší kontrolou" +"No preview available for" => "Náhled není dostupný pro" ); diff --git a/apps/files_sharing/l10n/cy_GB.php b/apps/files_sharing/l10n/cy_GB.php index dec9af4ebe..292f87a41e 100644 --- a/apps/files_sharing/l10n/cy_GB.php +++ b/apps/files_sharing/l10n/cy_GB.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "Rhannodd %s blygell %s â chi", "%s shared the file %s with you" => "Rhannodd %s ffeil %s â chi", "Download" => "Llwytho i lawr", -"No preview available for" => "Does dim rhagolwg ar gael ar gyfer", -"web services under your control" => "gwasanaethau gwe a reolir gennych" +"No preview available for" => "Does dim rhagolwg ar gael ar gyfer" ); diff --git a/apps/files_sharing/l10n/da.php b/apps/files_sharing/l10n/da.php index 75fbdabe16..55d70fec05 100644 --- a/apps/files_sharing/l10n/da.php +++ b/apps/files_sharing/l10n/da.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s delte mappen %s med dig", "%s shared the file %s with you" => "%s delte filen %s med dig", "Download" => "Download", -"No preview available for" => "Forhåndsvisning ikke tilgængelig for", -"web services under your control" => "Webtjenester under din kontrol" +"No preview available for" => "Forhåndsvisning ikke tilgængelig for" ); diff --git a/apps/files_sharing/l10n/de.php b/apps/files_sharing/l10n/de.php index 7f4cbb1ada..90fcdcf0f1 100644 --- a/apps/files_sharing/l10n/de.php +++ b/apps/files_sharing/l10n/de.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s hat den Ordner %s mit Dir geteilt", "%s shared the file %s with you" => "%s hat die Datei %s mit Dir geteilt", "Download" => "Download", -"No preview available for" => "Es ist keine Vorschau verfügbar für", -"web services under your control" => "Web-Services unter Deiner Kontrolle" +"No preview available for" => "Es ist keine Vorschau verfügbar für" ); diff --git a/apps/files_sharing/l10n/de_DE.php b/apps/files_sharing/l10n/de_DE.php index ab81589b0e..4594c7c248 100644 --- a/apps/files_sharing/l10n/de_DE.php +++ b/apps/files_sharing/l10n/de_DE.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s hat den Ordner %s mit Ihnen geteilt", "%s shared the file %s with you" => "%s hat die Datei %s mit Ihnen geteilt", "Download" => "Herunterladen", -"No preview available for" => "Es ist keine Vorschau verfügbar für", -"web services under your control" => "Web-Services unter Ihrer Kontrolle" +"No preview available for" => "Es ist keine Vorschau verfügbar für" ); diff --git a/apps/files_sharing/l10n/el.php b/apps/files_sharing/l10n/el.php index 5305eedd48..28360d03b4 100644 --- a/apps/files_sharing/l10n/el.php +++ b/apps/files_sharing/l10n/el.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s μοιράστηκε τον φάκελο %s μαζί σας", "%s shared the file %s with you" => "%s μοιράστηκε το αρχείο %s μαζί σας", "Download" => "Λήψη", -"No preview available for" => "Δεν υπάρχει διαθέσιμη προεπισκόπηση για", -"web services under your control" => "υπηρεσίες δικτύου υπό τον έλεγχό σας" +"No preview available for" => "Δεν υπάρχει διαθέσιμη προεπισκόπηση για" ); diff --git a/apps/files_sharing/l10n/en@pirate.php b/apps/files_sharing/l10n/en@pirate.php index 02ee844048..cb40c5a168 100644 --- a/apps/files_sharing/l10n/en@pirate.php +++ b/apps/files_sharing/l10n/en@pirate.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s shared the folder %s with you", "%s shared the file %s with you" => "%s shared the file %s with you", "Download" => "Download", -"No preview available for" => "No preview available for", -"web services under your control" => "web services under your control" +"No preview available for" => "No preview available for" ); diff --git a/apps/files_sharing/l10n/eo.php b/apps/files_sharing/l10n/eo.php index c598d3aa2c..5a216f1f1a 100644 --- a/apps/files_sharing/l10n/eo.php +++ b/apps/files_sharing/l10n/eo.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s kunhavigis la dosierujon %s kun vi", "%s shared the file %s with you" => "%s kunhavigis la dosieron %s kun vi", "Download" => "Elŝuti", -"No preview available for" => "Ne haveblas antaŭvido por", -"web services under your control" => "TTT-servoj regataj de vi" +"No preview available for" => "Ne haveblas antaŭvido por" ); diff --git a/apps/files_sharing/l10n/es.php b/apps/files_sharing/l10n/es.php index 2023d35903..61794d9c55 100644 --- a/apps/files_sharing/l10n/es.php +++ b/apps/files_sharing/l10n/es.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s compartió la carpeta %s contigo", "%s shared the file %s with you" => "%s compartió el fichero %s contigo", "Download" => "Descargar", -"No preview available for" => "No hay vista previa disponible para", -"web services under your control" => "Servicios web bajo su control" +"No preview available for" => "No hay vista previa disponible para" ); diff --git a/apps/files_sharing/l10n/es_AR.php b/apps/files_sharing/l10n/es_AR.php index a2d6e232f2..b079d05e52 100644 --- a/apps/files_sharing/l10n/es_AR.php +++ b/apps/files_sharing/l10n/es_AR.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s compartió la carpeta %s con vos", "%s shared the file %s with you" => "%s compartió el archivo %s con vos", "Download" => "Descargar", -"No preview available for" => "La vista preliminar no está disponible para", -"web services under your control" => "servicios web controlados por vos" +"No preview available for" => "La vista preliminar no está disponible para" ); diff --git a/apps/files_sharing/l10n/et_EE.php b/apps/files_sharing/l10n/et_EE.php index 36290ad278..b8f6b5ab06 100644 --- a/apps/files_sharing/l10n/et_EE.php +++ b/apps/files_sharing/l10n/et_EE.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s jagas sinuga kausta %s", "%s shared the file %s with you" => "%s jagas sinuga faili %s", "Download" => "Lae alla", -"No preview available for" => "Eelvaadet pole saadaval", -"web services under your control" => "veebitenused sinu kontrolli all" +"No preview available for" => "Eelvaadet pole saadaval" ); diff --git a/apps/files_sharing/l10n/eu.php b/apps/files_sharing/l10n/eu.php index ebef0f445e..614cdc1717 100644 --- a/apps/files_sharing/l10n/eu.php +++ b/apps/files_sharing/l10n/eu.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%sk zurekin %s karpeta elkarbanatu du", "%s shared the file %s with you" => "%sk zurekin %s fitxategia elkarbanatu du", "Download" => "Deskargatu", -"No preview available for" => "Ez dago aurrebista eskuragarririk hauentzat ", -"web services under your control" => "web zerbitzuak zure kontrolpean" +"No preview available for" => "Ez dago aurrebista eskuragarririk hauentzat " ); diff --git a/apps/files_sharing/l10n/fa.php b/apps/files_sharing/l10n/fa.php index 4313acae1a..d91daa90eb 100644 --- a/apps/files_sharing/l10n/fa.php +++ b/apps/files_sharing/l10n/fa.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%sپوشه %s را با شما به اشتراک گذاشت", "%s shared the file %s with you" => "%sفایل %s را با شما به اشتراک گذاشت", "Download" => "دانلود", -"No preview available for" => "هیچگونه پیش نمایشی موجود نیست", -"web services under your control" => "سرویس های تحت وب در کنترل شما" +"No preview available for" => "هیچگونه پیش نمایشی موجود نیست" ); diff --git a/apps/files_sharing/l10n/fi_FI.php b/apps/files_sharing/l10n/fi_FI.php index 6c44134213..7e9b67de2c 100644 --- a/apps/files_sharing/l10n/fi_FI.php +++ b/apps/files_sharing/l10n/fi_FI.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s jakoi kansion %s kanssasi", "%s shared the file %s with you" => "%s jakoi tiedoston %s kanssasi", "Download" => "Lataa", -"No preview available for" => "Ei esikatselua kohteelle", -"web services under your control" => "verkkopalvelut hallinnassasi" +"No preview available for" => "Ei esikatselua kohteelle" ); diff --git a/apps/files_sharing/l10n/fr.php b/apps/files_sharing/l10n/fr.php index 1038d81933..b4657978f7 100644 --- a/apps/files_sharing/l10n/fr.php +++ b/apps/files_sharing/l10n/fr.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s a partagé le répertoire %s avec vous", "%s shared the file %s with you" => "%s a partagé le fichier %s avec vous", "Download" => "Télécharger", -"No preview available for" => "Pas d'aperçu disponible pour", -"web services under your control" => "services web sous votre contrôle" +"No preview available for" => "Pas d'aperçu disponible pour" ); diff --git a/apps/files_sharing/l10n/gl.php b/apps/files_sharing/l10n/gl.php index d03f1a5005..90f7a22127 100644 --- a/apps/files_sharing/l10n/gl.php +++ b/apps/files_sharing/l10n/gl.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s compartiu o cartafol %s con vostede", "%s shared the file %s with you" => "%s compartiu o ficheiro %s con vostede", "Download" => "Descargar", -"No preview available for" => "Sen vista previa dispoñíbel para", -"web services under your control" => "servizos web baixo o seu control" +"No preview available for" => "Sen vista previa dispoñíbel para" ); diff --git a/apps/files_sharing/l10n/he.php b/apps/files_sharing/l10n/he.php index 2ea5ba76ab..d0c75e6ba5 100644 --- a/apps/files_sharing/l10n/he.php +++ b/apps/files_sharing/l10n/he.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s שיתף עמך את התיקייה %s", "%s shared the file %s with you" => "%s שיתף עמך את הקובץ %s", "Download" => "הורדה", -"No preview available for" => "אין תצוגה מקדימה זמינה עבור", -"web services under your control" => "שירותי רשת תחת השליטה שלך" +"No preview available for" => "אין תצוגה מקדימה זמינה עבור" ); diff --git a/apps/files_sharing/l10n/hr.php b/apps/files_sharing/l10n/hr.php index b2dca866bb..1d09d09bf5 100644 --- a/apps/files_sharing/l10n/hr.php +++ b/apps/files_sharing/l10n/hr.php @@ -1,6 +1,5 @@ "Lozinka", "Submit" => "Pošalji", -"Download" => "Preuzimanje", -"web services under your control" => "web usluge pod vašom kontrolom" +"Download" => "Preuzimanje" ); diff --git a/apps/files_sharing/l10n/hu_HU.php b/apps/files_sharing/l10n/hu_HU.php index f8ca541260..7184cfa4b3 100644 --- a/apps/files_sharing/l10n/hu_HU.php +++ b/apps/files_sharing/l10n/hu_HU.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s megosztotta Önnel ezt a mappát: %s", "%s shared the file %s with you" => "%s megosztotta Önnel ezt az állományt: %s", "Download" => "Letöltés", -"No preview available for" => "Nem áll rendelkezésre előnézet ehhez: ", -"web services under your control" => "webszolgáltatások saját kézben" +"No preview available for" => "Nem áll rendelkezésre előnézet ehhez: " ); diff --git a/apps/files_sharing/l10n/ia.php b/apps/files_sharing/l10n/ia.php index d229135a71..7db49518a4 100644 --- a/apps/files_sharing/l10n/ia.php +++ b/apps/files_sharing/l10n/ia.php @@ -1,6 +1,5 @@ "Contrasigno", "Submit" => "Submitter", -"Download" => "Discargar", -"web services under your control" => "servicios web sub tu controlo" +"Download" => "Discargar" ); diff --git a/apps/files_sharing/l10n/id.php b/apps/files_sharing/l10n/id.php index 95cf84312c..e27e78b5f6 100644 --- a/apps/files_sharing/l10n/id.php +++ b/apps/files_sharing/l10n/id.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s membagikan folder %s dengan Anda", "%s shared the file %s with you" => "%s membagikan file %s dengan Anda", "Download" => "Unduh", -"No preview available for" => "Tidak ada pratinjau tersedia untuk", -"web services under your control" => "layanan web dalam kontrol Anda" +"No preview available for" => "Tidak ada pratinjau tersedia untuk" ); diff --git a/apps/files_sharing/l10n/is.php b/apps/files_sharing/l10n/is.php index bf1975c54a..b76d737e6d 100644 --- a/apps/files_sharing/l10n/is.php +++ b/apps/files_sharing/l10n/is.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s deildi möppunni %s með þér", "%s shared the file %s with you" => "%s deildi skránni %s með þér", "Download" => "Niðurhal", -"No preview available for" => "Yfirlit ekki í boði fyrir", -"web services under your control" => "vefþjónusta undir þinni stjórn" +"No preview available for" => "Yfirlit ekki í boði fyrir" ); diff --git a/apps/files_sharing/l10n/it.php b/apps/files_sharing/l10n/it.php index f83ca1446d..60a1e588e8 100644 --- a/apps/files_sharing/l10n/it.php +++ b/apps/files_sharing/l10n/it.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s ha condiviso la cartella %s con te", "%s shared the file %s with you" => "%s ha condiviso il file %s con te", "Download" => "Scarica", -"No preview available for" => "Nessuna anteprima disponibile per", -"web services under your control" => "servizi web nelle tue mani" +"No preview available for" => "Nessuna anteprima disponibile per" ); diff --git a/apps/files_sharing/l10n/ja_JP.php b/apps/files_sharing/l10n/ja_JP.php index 02142e2879..8f208da10c 100644 --- a/apps/files_sharing/l10n/ja_JP.php +++ b/apps/files_sharing/l10n/ja_JP.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s はフォルダー %s をあなたと共有中です", "%s shared the file %s with you" => "%s はファイル %s をあなたと共有中です", "Download" => "ダウンロード", -"No preview available for" => "プレビューはありません", -"web services under your control" => "管理下のウェブサービス" +"No preview available for" => "プレビューはありません" ); diff --git a/apps/files_sharing/l10n/ka_GE.php b/apps/files_sharing/l10n/ka_GE.php index 6da1a8b019..4577148d7d 100644 --- a/apps/files_sharing/l10n/ka_GE.php +++ b/apps/files_sharing/l10n/ka_GE.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s–მა გაგიზიარათ ფოლდერი %s", "%s shared the file %s with you" => "%s–მა გაგიზიარათ ფაილი %s", "Download" => "ჩამოტვირთვა", -"No preview available for" => "წინასწარი დათვალიერება შეუძლებელია", -"web services under your control" => "web services under your control" +"No preview available for" => "წინასწარი დათვალიერება შეუძლებელია" ); diff --git a/apps/files_sharing/l10n/ko.php b/apps/files_sharing/l10n/ko.php index 600168d9bf..394c8d12b2 100644 --- a/apps/files_sharing/l10n/ko.php +++ b/apps/files_sharing/l10n/ko.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s 님이 폴더 %s을(를) 공유하였습니다", "%s shared the file %s with you" => "%s 님이 파일 %s을(를) 공유하였습니다", "Download" => "다운로드", -"No preview available for" => "다음 항목을 미리 볼 수 없음:", -"web services under your control" => "내가 관리하는 웹 서비스" +"No preview available for" => "다음 항목을 미리 볼 수 없음:" ); diff --git a/apps/files_sharing/l10n/ku_IQ.php b/apps/files_sharing/l10n/ku_IQ.php index 675fc372e1..4a0b53f6c8 100644 --- a/apps/files_sharing/l10n/ku_IQ.php +++ b/apps/files_sharing/l10n/ku_IQ.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s دابه‌شی کردووه‌ بوخچه‌ی %s له‌گه‌ڵ تۆ", "%s shared the file %s with you" => "%s دابه‌شی کردووه‌ په‌ڕگه‌یی %s له‌گه‌ڵ تۆ", "Download" => "داگرتن", -"No preview available for" => "هیچ پێشبینیه‌ك ئاماده‌ نیه بۆ", -"web services under your control" => "ڕاژه‌ی وێب له‌ژێر چاودێریت دایه" +"No preview available for" => "هیچ پێشبینیه‌ك ئاماده‌ نیه بۆ" ); diff --git a/apps/files_sharing/l10n/lb.php b/apps/files_sharing/l10n/lb.php index 630866ab4c..502f934cad 100644 --- a/apps/files_sharing/l10n/lb.php +++ b/apps/files_sharing/l10n/lb.php @@ -1,6 +1,5 @@ "Passwuert", "Submit" => "Fortschécken", -"Download" => "Download", -"web services under your control" => "Web Servicer ënnert denger Kontroll" +"Download" => "Download" ); diff --git a/apps/files_sharing/l10n/lt_LT.php b/apps/files_sharing/l10n/lt_LT.php index 96ab48cd2c..2e09aa206d 100644 --- a/apps/files_sharing/l10n/lt_LT.php +++ b/apps/files_sharing/l10n/lt_LT.php @@ -1,6 +1,8 @@ "Slaptažodis", "Submit" => "Išsaugoti", +"%s shared the folder %s with you" => "%s pasidalino su jumis %s aplanku", +"%s shared the file %s with you" => "%s pasidalino su jumis %s failu", "Download" => "Atsisiųsti", -"web services under your control" => "jūsų valdomos web paslaugos" +"No preview available for" => "Peržiūra nėra galima" ); diff --git a/apps/files_sharing/l10n/lv.php b/apps/files_sharing/l10n/lv.php index 88faeaf9f1..8430f99e6c 100644 --- a/apps/files_sharing/l10n/lv.php +++ b/apps/files_sharing/l10n/lv.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s ar jums dalījās ar mapi %s", "%s shared the file %s with you" => "%s ar jums dalījās ar datni %s", "Download" => "Lejupielādēt", -"No preview available for" => "Nav pieejams priekšskatījums priekš", -"web services under your control" => "tīmekļa servisi tavā varā" +"No preview available for" => "Nav pieejams priekšskatījums priekš" ); diff --git a/apps/files_sharing/l10n/mk.php b/apps/files_sharing/l10n/mk.php index 16c7ee0eb0..3d6e54f52b 100644 --- a/apps/files_sharing/l10n/mk.php +++ b/apps/files_sharing/l10n/mk.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s ја сподели папката %s со Вас", "%s shared the file %s with you" => "%s ја сподели датотеката %s со Вас", "Download" => "Преземи", -"No preview available for" => "Нема достапно преглед за", -"web services under your control" => "веб сервиси под Ваша контрола" +"No preview available for" => "Нема достапно преглед за" ); diff --git a/apps/files_sharing/l10n/ms_MY.php b/apps/files_sharing/l10n/ms_MY.php index 879524afce..5a1cb1018c 100644 --- a/apps/files_sharing/l10n/ms_MY.php +++ b/apps/files_sharing/l10n/ms_MY.php @@ -1,6 +1,5 @@ "Kata laluan", "Submit" => "Hantar", -"Download" => "Muat turun", -"web services under your control" => "Perkhidmatan web di bawah kawalan anda" +"Download" => "Muat turun" ); diff --git a/apps/files_sharing/l10n/my_MM.php b/apps/files_sharing/l10n/my_MM.php index dc7ec17e9c..4b37ab8b48 100644 --- a/apps/files_sharing/l10n/my_MM.php +++ b/apps/files_sharing/l10n/my_MM.php @@ -1,6 +1,5 @@ "စကားဝှက်", "Submit" => "ထည့်သွင်းမည်", -"Download" => "ဒေါင်းလုတ်", -"web services under your control" => "သင်၏ထိန်းချုပ်မှု့အောက်တွင်ရှိသော Web services" +"Download" => "ဒေါင်းလုတ်" ); diff --git a/apps/files_sharing/l10n/nb_NO.php b/apps/files_sharing/l10n/nb_NO.php index 4934c34106..027a07babe 100644 --- a/apps/files_sharing/l10n/nb_NO.php +++ b/apps/files_sharing/l10n/nb_NO.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s delte mappen %s med deg", "%s shared the file %s with you" => "%s delte filen %s med deg", "Download" => "Last ned", -"No preview available for" => "Forhåndsvisning ikke tilgjengelig for", -"web services under your control" => "web tjenester du kontrollerer" +"No preview available for" => "Forhåndsvisning ikke tilgjengelig for" ); diff --git a/apps/files_sharing/l10n/nl.php b/apps/files_sharing/l10n/nl.php index 2cef025439..837547e16b 100644 --- a/apps/files_sharing/l10n/nl.php +++ b/apps/files_sharing/l10n/nl.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s deelt de map %s met u", "%s shared the file %s with you" => "%s deelt het bestand %s met u", "Download" => "Downloaden", -"No preview available for" => "Geen voorbeeldweergave beschikbaar voor", -"web services under your control" => "Webdiensten in eigen beheer" +"No preview available for" => "Geen voorbeeldweergave beschikbaar voor" ); diff --git a/apps/files_sharing/l10n/nn_NO.php b/apps/files_sharing/l10n/nn_NO.php index aeba545dab..328fb038b8 100644 --- a/apps/files_sharing/l10n/nn_NO.php +++ b/apps/files_sharing/l10n/nn_NO.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s delte mappa %s med deg", "%s shared the file %s with you" => "%s delte fila %s med deg", "Download" => "Last ned", -"No preview available for" => "Inga førehandsvising tilgjengeleg for", -"web services under your control" => "Vev tjenester under din kontroll" +"No preview available for" => "Inga førehandsvising tilgjengeleg for" ); diff --git a/apps/files_sharing/l10n/oc.php b/apps/files_sharing/l10n/oc.php index 07bc26ecdd..2fe0c95aa7 100644 --- a/apps/files_sharing/l10n/oc.php +++ b/apps/files_sharing/l10n/oc.php @@ -1,6 +1,5 @@ "Senhal", "Submit" => "Sosmetre", -"Download" => "Avalcarga", -"web services under your control" => "Services web jos ton contraròtle" +"Download" => "Avalcarga" ); diff --git a/apps/files_sharing/l10n/pl.php b/apps/files_sharing/l10n/pl.php index 9db5e87c9b..c85a11863b 100644 --- a/apps/files_sharing/l10n/pl.php +++ b/apps/files_sharing/l10n/pl.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s współdzieli folder z tobą %s", "%s shared the file %s with you" => "%s współdzieli z tobą plik %s", "Download" => "Pobierz", -"No preview available for" => "Podgląd nie jest dostępny dla", -"web services under your control" => "Kontrolowane serwisy" +"No preview available for" => "Podgląd nie jest dostępny dla" ); diff --git a/apps/files_sharing/l10n/pt_BR.php b/apps/files_sharing/l10n/pt_BR.php index ce4c28ddcb..a5dad793c4 100644 --- a/apps/files_sharing/l10n/pt_BR.php +++ b/apps/files_sharing/l10n/pt_BR.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s compartilhou a pasta %s com você", "%s shared the file %s with you" => "%s compartilhou o arquivo %s com você", "Download" => "Baixar", -"No preview available for" => "Nenhuma visualização disponível para", -"web services under your control" => "serviços web sob seu controle" +"No preview available for" => "Nenhuma visualização disponível para" ); diff --git a/apps/files_sharing/l10n/pt_PT.php b/apps/files_sharing/l10n/pt_PT.php index 43e8f3c4b6..de8fcbf02d 100644 --- a/apps/files_sharing/l10n/pt_PT.php +++ b/apps/files_sharing/l10n/pt_PT.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s partilhou a pasta %s consigo", "%s shared the file %s with you" => "%s partilhou o ficheiro %s consigo", "Download" => "Transferir", -"No preview available for" => "Não há pré-visualização para", -"web services under your control" => "serviços web sob o seu controlo" +"No preview available for" => "Não há pré-visualização para" ); diff --git a/apps/files_sharing/l10n/ro.php b/apps/files_sharing/l10n/ro.php index eb9977dc58..8b8eab1354 100644 --- a/apps/files_sharing/l10n/ro.php +++ b/apps/files_sharing/l10n/ro.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s a partajat directorul %s cu tine", "%s shared the file %s with you" => "%s a partajat fișierul %s cu tine", "Download" => "Descarcă", -"No preview available for" => "Nici o previzualizare disponibilă pentru ", -"web services under your control" => "servicii web controlate de tine" +"No preview available for" => "Nici o previzualizare disponibilă pentru " ); diff --git a/apps/files_sharing/l10n/ru.php b/apps/files_sharing/l10n/ru.php index 7fd116e0aa..066096f5b5 100644 --- a/apps/files_sharing/l10n/ru.php +++ b/apps/files_sharing/l10n/ru.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s открыл доступ к папке %s для Вас", "%s shared the file %s with you" => "%s открыл доступ к файлу %s для Вас", "Download" => "Скачать", -"No preview available for" => "Предпросмотр недоступен для", -"web services under your control" => "веб-сервисы под вашим управлением" +"No preview available for" => "Предпросмотр недоступен для" ); diff --git a/apps/files_sharing/l10n/ru_RU.php b/apps/files_sharing/l10n/ru_RU.php index 36e4b2fd0e..2cadd16346 100644 --- a/apps/files_sharing/l10n/ru_RU.php +++ b/apps/files_sharing/l10n/ru_RU.php @@ -1,9 +1,3 @@ "Пароль", -"Submit" => "Передать", -"%s shared the folder %s with you" => "%s имеет общий с Вами доступ к папке %s ", -"%s shared the file %s with you" => "%s имеет общий с Вами доступ к файлу %s ", -"Download" => "Загрузка", -"No preview available for" => "Предварительный просмотр недоступен", -"web services under your control" => "веб-сервисы под Вашим контролем" +"Download" => "Загрузка" ); diff --git a/apps/files_sharing/l10n/si_LK.php b/apps/files_sharing/l10n/si_LK.php index 580f7b1990..b9bcab28c9 100644 --- a/apps/files_sharing/l10n/si_LK.php +++ b/apps/files_sharing/l10n/si_LK.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s ඔබව %s ෆෝල්ඩරයට හවුල් කරගත්තේය", "%s shared the file %s with you" => "%s ඔබ සමඟ %s ගොනුව බෙදාහදාගත්තේය", "Download" => "බාන්න", -"No preview available for" => "පූර්වදර්ශනයක් නොමැත", -"web services under your control" => "ඔබට පාලනය කළ හැකි වෙබ් සේවාවන්" +"No preview available for" => "පූර්වදර්ශනයක් නොමැත" ); diff --git a/apps/files_sharing/l10n/sk_SK.php b/apps/files_sharing/l10n/sk_SK.php index 14124eeb87..0907e3b451 100644 --- a/apps/files_sharing/l10n/sk_SK.php +++ b/apps/files_sharing/l10n/sk_SK.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s zdieľa s vami priečinok %s", "%s shared the file %s with you" => "%s zdieľa s vami súbor %s", "Download" => "Sťahovanie", -"No preview available for" => "Žiaden náhľad k dispozícii pre", -"web services under your control" => "webové služby pod Vašou kontrolou" +"No preview available for" => "Žiaden náhľad k dispozícii pre" ); diff --git a/apps/files_sharing/l10n/sl.php b/apps/files_sharing/l10n/sl.php index 6bcbb0070b..ae84c47289 100644 --- a/apps/files_sharing/l10n/sl.php +++ b/apps/files_sharing/l10n/sl.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "Oseba %s je določila mapo %s za souporabo", "%s shared the file %s with you" => "Oseba %s je določila datoteko %s za souporabo", "Download" => "Prejmi", -"No preview available for" => "Predogled ni na voljo za", -"web services under your control" => "spletne storitve pod vašim nadzorom" +"No preview available for" => "Predogled ni na voljo za" ); diff --git a/apps/files_sharing/l10n/sq.php b/apps/files_sharing/l10n/sq.php index 244ca87c55..7be5f560fa 100644 --- a/apps/files_sharing/l10n/sq.php +++ b/apps/files_sharing/l10n/sq.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s ndau me ju dosjen %s", "%s shared the file %s with you" => "%s ndau me ju skedarin %s", "Download" => "Shkarko", -"No preview available for" => "Shikimi paraprak nuk është i mundur për", -"web services under your control" => "shërbime web nën kontrollin tënd" +"No preview available for" => "Shikimi paraprak nuk është i mundur për" ); diff --git a/apps/files_sharing/l10n/sr.php b/apps/files_sharing/l10n/sr.php index be24c06e46..6e277f6771 100644 --- a/apps/files_sharing/l10n/sr.php +++ b/apps/files_sharing/l10n/sr.php @@ -1,6 +1,5 @@ "Лозинка", "Submit" => "Пошаљи", -"Download" => "Преузми", -"web services under your control" => "веб сервиси под контролом" +"Download" => "Преузми" ); diff --git a/apps/files_sharing/l10n/sv.php b/apps/files_sharing/l10n/sv.php index d1c9afff07..af21d869ad 100644 --- a/apps/files_sharing/l10n/sv.php +++ b/apps/files_sharing/l10n/sv.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s delade mappen %s med dig", "%s shared the file %s with you" => "%s delade filen %s med dig", "Download" => "Ladda ner", -"No preview available for" => "Ingen förhandsgranskning tillgänglig för", -"web services under your control" => "webbtjänster under din kontroll" +"No preview available for" => "Ingen förhandsgranskning tillgänglig för" ); diff --git a/apps/files_sharing/l10n/ta_LK.php b/apps/files_sharing/l10n/ta_LK.php index 6cf6f6236b..6b2ac30bcd 100644 --- a/apps/files_sharing/l10n/ta_LK.php +++ b/apps/files_sharing/l10n/ta_LK.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s கோப்புறையானது %s உடன் பகிரப்பட்டது", "%s shared the file %s with you" => "%s கோப்பானது %s உடன் பகிரப்பட்டது", "Download" => "பதிவிறக்குக", -"No preview available for" => "அதற்கு முன்னோக்கு ஒன்றும் இல்லை", -"web services under your control" => "வலைய சேவைகள் உங்களுடைய கட்டுப்பாட்டின் கீழ் உள்ளது" +"No preview available for" => "அதற்கு முன்னோக்கு ஒன்றும் இல்லை" ); diff --git a/apps/files_sharing/l10n/th_TH.php b/apps/files_sharing/l10n/th_TH.php index 9d53d65f8a..e16ecea96e 100644 --- a/apps/files_sharing/l10n/th_TH.php +++ b/apps/files_sharing/l10n/th_TH.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s ได้แชร์โฟลเดอร์ %s ให้กับคุณ", "%s shared the file %s with you" => "%s ได้แชร์ไฟล์ %s ให้กับคุณ", "Download" => "ดาวน์โหลด", -"No preview available for" => "ไม่สามารถดูตัวอย่างได้สำหรับ", -"web services under your control" => "เว็บเซอร์วิสที่คุณควบคุมการใช้งานได้" +"No preview available for" => "ไม่สามารถดูตัวอย่างได้สำหรับ" ); diff --git a/apps/files_sharing/l10n/tr.php b/apps/files_sharing/l10n/tr.php index 42dfec8cc6..4de3355738 100644 --- a/apps/files_sharing/l10n/tr.php +++ b/apps/files_sharing/l10n/tr.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s sizinle paylaşılan %s klasör", "%s shared the file %s with you" => "%s sizinle paylaşılan %s klasör", "Download" => "İndir", -"No preview available for" => "Kullanılabilir önizleme yok", -"web services under your control" => "Bilgileriniz güvenli ve şifreli" +"No preview available for" => "Kullanılabilir önizleme yok" ); diff --git a/apps/files_sharing/l10n/uk.php b/apps/files_sharing/l10n/uk.php index 8e1fa4bc98..207988ef73 100644 --- a/apps/files_sharing/l10n/uk.php +++ b/apps/files_sharing/l10n/uk.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s опублікував каталог %s для Вас", "%s shared the file %s with you" => "%s опублікував файл %s для Вас", "Download" => "Завантажити", -"No preview available for" => "Попередній перегляд недоступний для", -"web services under your control" => "підконтрольні Вам веб-сервіси" +"No preview available for" => "Попередній перегляд недоступний для" ); diff --git a/apps/files_sharing/l10n/ur_PK.php b/apps/files_sharing/l10n/ur_PK.php index f68b714350..745f2f930d 100644 --- a/apps/files_sharing/l10n/ur_PK.php +++ b/apps/files_sharing/l10n/ur_PK.php @@ -1,4 +1,3 @@ "پاسورڈ", -"web services under your control" => "آپ کے اختیار میں ویب سروسیز" +"Password" => "پاسورڈ" ); diff --git a/apps/files_sharing/l10n/vi.php b/apps/files_sharing/l10n/vi.php index afeec5c648..2a5a2ff17f 100644 --- a/apps/files_sharing/l10n/vi.php +++ b/apps/files_sharing/l10n/vi.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s đã chia sẻ thư mục %s với bạn", "%s shared the file %s with you" => "%s đã chia sẻ tập tin %s với bạn", "Download" => "Tải về", -"No preview available for" => "Không có xem trước cho", -"web services under your control" => "dịch vụ web dưới sự kiểm soát của bạn" +"No preview available for" => "Không có xem trước cho" ); diff --git a/apps/files_sharing/l10n/zh_CN.GB2312.php b/apps/files_sharing/l10n/zh_CN.GB2312.php index 117ec8f406..7df3ee8f9b 100644 --- a/apps/files_sharing/l10n/zh_CN.GB2312.php +++ b/apps/files_sharing/l10n/zh_CN.GB2312.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s 与您分享了文件夹 %s", "%s shared the file %s with you" => "%s 与您分享了文件 %s", "Download" => "下载", -"No preview available for" => "没有预览可用于", -"web services under your control" => "您控制的网络服务" +"No preview available for" => "没有预览可用于" ); diff --git a/apps/files_sharing/l10n/zh_CN.php b/apps/files_sharing/l10n/zh_CN.php index 64e7af3e0c..15c1bb5487 100644 --- a/apps/files_sharing/l10n/zh_CN.php +++ b/apps/files_sharing/l10n/zh_CN.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s与您共享了%s文件夹", "%s shared the file %s with you" => "%s与您共享了%s文件", "Download" => "下载", -"No preview available for" => "没有预览", -"web services under your control" => "您控制的web服务" +"No preview available for" => "没有预览" ); diff --git a/apps/files_sharing/l10n/zh_TW.php b/apps/files_sharing/l10n/zh_TW.php index 14e4466ecb..23b2778994 100644 --- a/apps/files_sharing/l10n/zh_TW.php +++ b/apps/files_sharing/l10n/zh_TW.php @@ -4,6 +4,5 @@ "%s shared the folder %s with you" => "%s 和您分享了資料夾 %s ", "%s shared the file %s with you" => "%s 和您分享了檔案 %s", "Download" => "下載", -"No preview available for" => "無法預覽", -"web services under your control" => "由您控制的網路服務" +"No preview available for" => "無法預覽" ); diff --git a/apps/files_sharing/lib/permissions.php b/apps/files_sharing/lib/permissions.php index 6747faa4d4..b6638564cd 100644 --- a/apps/files_sharing/lib/permissions.php +++ b/apps/files_sharing/lib/permissions.php @@ -70,6 +70,28 @@ class Shared_Permissions extends Permissions { return $filePermissions; } + /** + * get the permissions for all files in a folder + * + * @param int $parentId + * @param string $user + * @return int[] + */ + public function getDirectoryPermissions($parentId, $user) { + // Root of the Shared folder + if ($parentId === -1) { + return \OCP\Share::getItemsSharedWith('file', \OC_Share_Backend_File::FORMAT_PERMISSIONS); + } + $permissions = $this->get($parentId, $user); + $query = \OC_DB::prepare('SELECT `fileid` FROM `*PREFIX*filecache` WHERE `parent` = ?'); + $result = $query->execute(array($parentId)); + $filePermissions = array(); + while ($row = $result->fetchRow()) { + $filePermissions[$row['fileid']] = $permissions; + } + return $filePermissions; + } + /** * remove the permissions for a file * @@ -83,4 +105,5 @@ class Shared_Permissions extends Permissions { public function removeMultiple($fileIds, $user) { // Not a valid action for Shared Permissions } -} + +} \ No newline at end of file diff --git a/apps/files_sharing/lib/share/file.php b/apps/files_sharing/lib/share/file.php index 6294865180..07e7a4ca0c 100644 --- a/apps/files_sharing/lib/share/file.php +++ b/apps/files_sharing/lib/share/file.php @@ -26,6 +26,7 @@ class OC_Share_Backend_File implements OCP\Share_Backend_File_Dependent { const FORMAT_FILE_APP_ROOT = 2; const FORMAT_OPENDIR = 3; const FORMAT_GET_ALL = 4; + const FORMAT_PERMISSIONS = 5; private $path; @@ -125,6 +126,12 @@ class OC_Share_Backend_File implements OCP\Share_Backend_File_Dependent { $ids[] = $item['file_source']; } return $ids; + } else if ($format === self::FORMAT_PERMISSIONS) { + $filePermissions = array(); + foreach ($items as $item) { + $filePermissions[$item['file_source']] = $item['permissions']; + } + return $filePermissions; } return array(); } diff --git a/apps/files_sharing/public.php b/apps/files_sharing/public.php index 2b283375a6..98d2a84fb6 100644 --- a/apps/files_sharing/public.php +++ b/apps/files_sharing/public.php @@ -1,5 +1,4 @@ set('public_link_authenticated', $linkItem['id']); } } else { OCP\Util::writeLog('share', 'Unknown share type '.$linkItem['share_type'] @@ -97,8 +97,8 @@ if (isset($path)) { } else { // Check if item id is set in session - if (!isset($_SESSION['public_link_authenticated']) - || $_SESSION['public_link_authenticated'] !== $linkItem['id'] + if ( ! \OC::$session->exists('public_link_authenticated') + || \OC::$session->get('public_link_authenticated') !== $linkItem['id'] ) { // Prompt for password $tmpl = new OCP\Template('files_sharing', 'authenticate', 'guest'); diff --git a/apps/files_sharing/templates/public.php b/apps/files_sharing/templates/public.php index 88a4cc242b..adf3c3e9cc 100644 --- a/apps/files_sharing/templates/public.php +++ b/apps/files_sharing/templates/public.php @@ -46,5 +46,9 @@
-

ownCloud – -t('web services under your control')); ?>

+
+

+ '); ?> + +

+
diff --git a/apps/files_trashbin/appinfo/app.php b/apps/files_trashbin/appinfo/app.php index e83d3b8fbb..3b1e0ac30c 100644 --- a/apps/files_trashbin/appinfo/app.php +++ b/apps/files_trashbin/appinfo/app.php @@ -3,7 +3,5 @@ OC::$CLASSPATH['OCA\Files_Trashbin\Hooks'] = 'files_trashbin/lib/hooks.php'; OC::$CLASSPATH['OCA\Files_Trashbin\Trashbin'] = 'files_trashbin/lib/trash.php'; -//Listen to delete file signal -OCP\Util::connectHook('OC_Filesystem', 'delete', "OCA\Files_Trashbin\Hooks", "remove_hook"); -//Listen to delete user signal -OCP\Util::connectHook('OC_User', 'pre_deleteUser', "OCA\Files_Trashbin\Hooks", "deleteUser_hook"); \ No newline at end of file +// register hooks +\OCA\Files_Trashbin\Trashbin::registerHooks(); \ No newline at end of file diff --git a/apps/files_trashbin/js/trash.js b/apps/files_trashbin/js/trash.js index eed253d660..691642811b 100644 --- a/apps/files_trashbin/js/trash.js +++ b/apps/files_trashbin/js/trash.js @@ -93,6 +93,7 @@ $(document).ready(function() { }); $('.undelete').click('click',function(event) { + event.preventDefault(); var spinner = ''; var files=getSelectedFiles('file'); var fileslist = JSON.stringify(files); @@ -117,6 +118,7 @@ $(document).ready(function() { }); $('.delete').click('click',function(event) { + event.preventDefault(); console.log("delete selected"); var spinner = ''; var files=getSelectedFiles('file'); diff --git a/apps/files_trashbin/l10n/bg_BG.php b/apps/files_trashbin/l10n/bg_BG.php index 288518e1a4..1e0953b013 100644 --- a/apps/files_trashbin/l10n/bg_BG.php +++ b/apps/files_trashbin/l10n/bg_BG.php @@ -1,5 +1,5 @@ "Невъзможно изтриване на %s завинаги", +"Couldn't delete %s permanently" => "Невъзможно перманентното изтриване на %s", "Couldn't restore %s" => "Невъзможно възтановяване на %s", "perform restore operation" => "извършване на действие по възстановяване", "Error" => "Грешка", diff --git a/apps/files_trashbin/l10n/he.php b/apps/files_trashbin/l10n/he.php index e31fdb952e..853d4e1925 100644 --- a/apps/files_trashbin/l10n/he.php +++ b/apps/files_trashbin/l10n/he.php @@ -1,17 +1,18 @@ "בלתי אפשרי למחוק את %s לצמיתות", -"Couldn't restore %s" => "בלתי אפשרי לשחזר את %s", -"perform restore operation" => "בצע פעולת שחזור", +"Couldn't delete %s permanently" => "לא ניתן למחוק את %s לצמיתות", +"Couldn't restore %s" => "לא ניתן לשחזר את %s", +"perform restore operation" => "ביצוע פעולת שחזור", "Error" => "שגיאה", -"delete file permanently" => "מחק קובץ לצמיתות", -"Delete permanently" => "מחק לצמיתות", +"delete file permanently" => "מחיקת קובץ לצמיתות", +"Delete permanently" => "מחיקה לצמיתות", "Name" => "שם", "Deleted" => "נמחק", "1 folder" => "תיקייה אחת", "{count} folders" => "{count} תיקיות", "1 file" => "קובץ אחד", "{count} files" => "{count} קבצים", -"Nothing in here. Your trash bin is empty!" => "שום דבר כאן. סל המחזור שלך ריק!", -"Restore" => "שחזר", -"Delete" => "מחיקה" +"Nothing in here. Your trash bin is empty!" => "אין כאן שום דבר. סל המיחזור שלך ריק!", +"Restore" => "שחזור", +"Delete" => "מחיקה", +"Deleted Files" => "קבצים שנמחקו" ); diff --git a/apps/files_trashbin/l10n/lt_LT.php b/apps/files_trashbin/l10n/lt_LT.php index 011de161e4..7df63bd7f2 100644 --- a/apps/files_trashbin/l10n/lt_LT.php +++ b/apps/files_trashbin/l10n/lt_LT.php @@ -1,9 +1,18 @@ "Nepavyko negrįžtamai ištrinti %s", +"Couldn't restore %s" => "Nepavyko atkurti %s", +"perform restore operation" => "atkurti", "Error" => "Klaida", +"delete file permanently" => "failą ištrinti negrįžtamai", +"Delete permanently" => "Ištrinti negrįžtamai", "Name" => "Pavadinimas", +"Deleted" => "Ištrinti", "1 folder" => "1 aplankalas", "{count} folders" => "{count} aplankalai", "1 file" => "1 failas", "{count} files" => "{count} failai", -"Delete" => "Ištrinti" +"Nothing in here. Your trash bin is empty!" => "Nieko nėra. Jūsų šiukšliadėžė tuščia!", +"Restore" => "Atstatyti", +"Delete" => "Ištrinti", +"Deleted Files" => "Ištrinti failai" ); diff --git a/apps/files_trashbin/l10n/ru_RU.php b/apps/files_trashbin/l10n/ru_RU.php index 1ef163d48f..8636e417ec 100644 --- a/apps/files_trashbin/l10n/ru_RU.php +++ b/apps/files_trashbin/l10n/ru_RU.php @@ -1,3 +1,5 @@ "Ошибка" +"Error" => "Ошибка", +"Name" => "Имя", +"Delete" => "Удалить" ); diff --git a/apps/files_trashbin/lib/trash.php b/apps/files_trashbin/lib/trash.php index 7fda855d0c..2d1830a38f 100644 --- a/apps/files_trashbin/lib/trash.php +++ b/apps/files_trashbin/lib/trash.php @@ -29,6 +29,17 @@ class Trashbin { // unit: percentage; 50% of available disk space/quota const DEFAULTMAXSIZE=50; + public 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, $filename); + } + /** * move file to the trash bin * @@ -62,8 +73,12 @@ class Trashbin { if ( $trashbinSize === false || $trashbinSize < 0 ) { $trashbinSize = self::calculateSize(new \OC\Files\View('/'. $user.'/files_trashbin')); } - + + // disable proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; $sizeOfAddedFiles = self::copy_recursive($file_path, 'files_trashbin/files/'.$filename.'.d'.$timestamp, $view); + \OC_FileProxy::$enabled = $proxyStatus; if ( $view->file_exists('files_trashbin/files/'.$filename.'.d'.$timestamp) ) { $trashbinSize += $sizeOfAddedFiles; @@ -110,13 +125,17 @@ class Trashbin { \OC_FileProxy::$enabled = false; $user = \OCP\User::getUser(); - if ($view->is_dir('files_versions/' . $file_path)) { - $size += self::calculateSize(new \OC\Files\View('/' . $user . '/files_versions/' . $file_path)); - $view->rename('files_versions/' . $file_path, 'files_trashbin/versions/' . $filename . '.d' . $timestamp); - } else if ($versions = \OCA\Files_Versions\Storage::getVersions($user, $file_path)) { + $rootView = new \OC\Files\View('/'); + + list($owner, $ownerPath) = self::getUidAndFilename($file_path); + + if ($rootView->is_dir($owner.'/files_versions/' . $ownerPath)) { + $size += self::calculateSize(new \OC\Files\View('/' . $owner . '/files_versions/' . $ownerPath)); + $rootView->rename($owner.'/files_versions/' . $ownerPath, $user.'/files_trashbin/versions/' . $filename . '.d' . $timestamp); + } else if ($versions = \OCA\Files_Versions\Storage::getVersions($owner, $ownerPath)) { foreach ($versions as $v) { - $size += $view->filesize('files_versions' . $v['path'] . '.v' . $v['version']); - $view->rename('files_versions' . $v['path'] . '.v' . $v['version'], 'files_trashbin/versions/' . $filename . '.v' . $v['version'] . '.d' . $timestamp); + $size += $rootView->filesize($owner.'/files_versions' . $v['path'] . '.v' . $v['version']); + $rootView->rename($owner.'/files_versions' . $v['path'] . '.v' . $v['version'], $user.'/files_trashbin/versions/' . $filename . '.v' . $v['version'] . '.d' . $timestamp); } } @@ -143,35 +162,38 @@ class Trashbin { if (\OCP\App::isEnabled('files_encryption')) { $user = \OCP\User::getUser(); + $rootView = new \OC\Files\View('/'); + + list($owner, $ownerPath) = self::getUidAndFilename($file_path); + // disable proxy to prevent recursive calls $proxyStatus = \OC_FileProxy::$enabled; \OC_FileProxy::$enabled = false; // retain key files - $keyfile = \OC\Files\Filesystem::normalizePath('files_encryption/keyfiles/' . $file_path); + $keyfile = \OC\Files\Filesystem::normalizePath($owner.'/files_encryption/keyfiles/' . $ownerPath); - if ($view->is_dir($keyfile) || $view->file_exists($keyfile . '.key')) { - $user = \OCP\User::getUser(); + if ($rootView->is_dir($keyfile) || $rootView->file_exists($keyfile . '.key')) { // move keyfiles - if ($view->is_dir($keyfile)) { - $size += self::calculateSize(new \OC\Files\View('/' . $user . '/' . $keyfile)); - $view->rename($keyfile, 'files_trashbin/keyfiles/' . $filename . '.d' . $timestamp); + if ($rootView->is_dir($keyfile)) { + $size += self::calculateSize(new \OC\Files\View($keyfile)); + $rootView->rename($keyfile, $user.'/files_trashbin/keyfiles/' . $filename . '.d' . $timestamp); } else { - $size += $view->filesize($keyfile . '.key'); - $view->rename($keyfile . '.key', 'files_trashbin/keyfiles/' . $filename . '.key.d' . $timestamp); + $size += $rootView->filesize($keyfile . '.key'); + $rootView->rename($keyfile . '.key', $user.'/files_trashbin/keyfiles/' . $filename . '.key.d' . $timestamp); } } // retain share keys - $sharekeys = \OC\Files\Filesystem::normalizePath('files_encryption/share-keys/' . $file_path); + $sharekeys = \OC\Files\Filesystem::normalizePath($owner.'/files_encryption/share-keys/' . $ownerPath); - if ($view->is_dir($sharekeys)) { - $size += self::calculateSize(new \OC\Files\View('/' . $user . '/' . $sharekeys)); - $view->rename($sharekeys, 'files_trashbin/share-keys/' . $filename . '.d' . $timestamp); + if ($rootView->is_dir($sharekeys)) { + $size += self::calculateSize(new \OC\Files\View($sharekeys)); + $rootView->rename($sharekeys, $user.'/files_trashbin/share-keys/' . $filename . '.d' . $timestamp); } else { // get local path to share-keys - $localShareKeysPath = $view->getLocalFile($sharekeys); + $localShareKeysPath = $rootView->getLocalFile($sharekeys); // handle share-keys $matches = glob(preg_quote($localShareKeysPath).'*.shareKey'); @@ -186,10 +208,10 @@ class Trashbin { if($pathinfo['basename'] == $ownerShareKey) { // calculate size - $size += $view->filesize($sharekeys. '.' . $user. '.shareKey'); + $size += $rootView->filesize($sharekeys. '.' . $user. '.shareKey'); // move file - $view->rename($sharekeys. '.' . $user. '.shareKey', 'files_trashbin/share-keys/' . $ownerShareKey . '.d' . $timestamp); + $rootView->rename($sharekeys. '.' . $user. '.shareKey', $user.'/files_trashbin/share-keys/' . $ownerShareKey . '.d' . $timestamp); } else { // calculate size @@ -321,6 +343,12 @@ class Trashbin { \OC_FileProxy::$enabled = false; $user = \OCP\User::getUser(); + $rootView = new \OC\Files\View('/'); + + $target = \OC\Files\Filesystem::normalizePath('/'.$location.'/'.$filename.$ext); + + list($owner, $ownerPath) = self::getUidAndFilename($target); + if ($timestamp) { $versionedFile = $filename; } else { @@ -329,15 +357,15 @@ class Trashbin { if ($view->is_dir('/files_trashbin/versions/'.$file)) { $size += self::calculateSize(new \OC\Files\View('/' . $user . '/' . 'files_trashbin/versions/' . $file)); - $view->rename(\OC\Files\Filesystem::normalizePath('files_trashbin/versions/' . $file), \OC\Files\Filesystem::normalizePath('files_versions/' . $location . '/' . $filename . $ext)); + $rootView->rename(\OC\Files\Filesystem::normalizePath($user.'/files_trashbin/versions/' . $file), \OC\Files\Filesystem::normalizePath($owner.'/files_versions/' . $ownerPath)); } else if ($versions = self::getVersionsFromTrash($versionedFile, $timestamp)) { foreach ($versions as $v) { if ($timestamp) { $size += $view->filesize('files_trashbin/versions/' . $versionedFile . '.v' . $v . '.d' . $timestamp); - $view->rename('files_trashbin/versions/' . $versionedFile . '.v' . $v . '.d' . $timestamp, 'files_versions/' . $location . '/' . $filename . $ext . '.v' . $v); + $rootView->rename($user.'/files_trashbin/versions/' . $versionedFile . '.v' . $v . '.d' . $timestamp, $owner.'/files_versions/' . $ownerPath . '.v' . $v); } else { $size += $view->filesize('files_trashbin/versions/' . $versionedFile . '.v' . $v); - $view->rename('files_trashbin/versions/' . $versionedFile . '.v' . $v, 'files_versions/' . $location . '/' . $filename . $ext . '.v' . $v); + $rootView->rename($user.'/files_trashbin/versions/' . $versionedFile . '.v' . $v, $owner.'/files_versions/' . $ownerPath . '.v' . $v); } } } @@ -356,7 +384,7 @@ class Trashbin { * @param $file complete path to file * @param $filename name of file * @param $ext file extension in case a file with the same $filename already exists - * @param $location location if file + * @param $location location of file * @param $timestamp deleteion time * * @return size of restored encrypted file @@ -366,20 +394,25 @@ class Trashbin { $size = 0; if (\OCP\App::isEnabled('files_encryption')) { $user = \OCP\User::getUser(); + $rootView = new \OC\Files\View('/'); + + $target = \OC\Files\Filesystem::normalizePath('/'.$location.'/'.$filename.$ext); + + list($owner, $ownerPath) = self::getUidAndFilename($target); $path_parts = pathinfo($file); $source_location = $path_parts['dirname']; if ($view->is_dir('/files_trashbin/keyfiles/'.$file)) { if($source_location != '.') { - $keyfile = \OC\Files\Filesystem::normalizePath('files_trashbin/keyfiles/' . $source_location . '/' . $filename); - $sharekey = \OC\Files\Filesystem::normalizePath('files_trashbin/share-keys/' . $source_location . '/' . $filename); + $keyfile = \OC\Files\Filesystem::normalizePath($user.'/files_trashbin/keyfiles/' . $source_location . '/' . $filename); + $sharekey = \OC\Files\Filesystem::normalizePath($user.'/files_trashbin/share-keys/' . $source_location . '/' . $filename); } else { - $keyfile = \OC\Files\Filesystem::normalizePath('files_trashbin/keyfiles/' . $filename); - $sharekey = \OC\Files\Filesystem::normalizePath('files_trashbin/share-keys/' . $filename); + $keyfile = \OC\Files\Filesystem::normalizePath($user.'/files_trashbin/keyfiles/' . $filename); + $sharekey = \OC\Files\Filesystem::normalizePath($user.'/files_trashbin/share-keys/' . $filename); } } else { - $keyfile = \OC\Files\Filesystem::normalizePath('files_trashbin/keyfiles/' . $source_location . '/' . $filename . '.key'); + $keyfile = \OC\Files\Filesystem::normalizePath($user.'/files_trashbin/keyfiles/' . $source_location . '/' . $filename . '.key'); } if ($timestamp) { @@ -390,35 +423,36 @@ class Trashbin { $proxyStatus = \OC_FileProxy::$enabled; \OC_FileProxy::$enabled = false; - if ($view->file_exists($keyfile)) { + if ($rootView->file_exists($keyfile)) { // handle directory - if ($view->is_dir($keyfile)) { + if ($rootView->is_dir($keyfile)) { // handle keyfiles - $size += self::calculateSize(new \OC\Files\View('/' . $user . '/' . $keyfile)); - $view->rename($keyfile, 'files_encryption/keyfiles/' . $location . '/' . $filename . $ext); + $size += self::calculateSize(new \OC\Files\View($keyfile)); + $rootView->rename($keyfile, $owner.'/files_encryption/keyfiles/' . $ownerPath); // handle share-keys if ($timestamp) { $sharekey .= '.d' . $timestamp; } - $view->rename($sharekey, 'files_encryption/share-keys/' . $location . '/' . $filename . $ext); + $size += self::calculateSize(new \OC\Files\View($sharekey)); + $rootView->rename($sharekey, $owner.'/files_encryption/share-keys/' . $ownerPath); } else { // handle keyfiles - $size += $view->filesize($keyfile); - $view->rename($keyfile, 'files_encryption/keyfiles/' . $location . '/' . $filename . $ext . '.key'); + $size += $rootView->filesize($keyfile); + $rootView->rename($keyfile, $owner.'/files_encryption/keyfiles/' . $ownerPath . '.key'); // handle share-keys - $ownerShareKey = \OC\Files\Filesystem::normalizePath('files_trashbin/share-keys/' . $source_location . '/' . $filename . '.' . $user. '.shareKey'); + $ownerShareKey = \OC\Files\Filesystem::normalizePath($user.'/files_trashbin/share-keys/' . $source_location . '/' . $filename . '.' . $user. '.shareKey'); if ($timestamp) { $ownerShareKey .= '.d' . $timestamp; } - $size += $view->filesize($ownerShareKey); + $size += $rootView->filesize($ownerShareKey); // move only owners key - $view->rename($ownerShareKey, 'files_encryption/share-keys/' . $location . '/' . $filename . $ext . '.' . $user. '.shareKey'); + $rootView->rename($ownerShareKey, $owner.'/files_encryption/share-keys/' . $ownerPath . '.' . $user. '.shareKey'); // try to re-share if file is shared $filesystemView = new \OC_FilesystemView('/'); @@ -426,7 +460,7 @@ class Trashbin { $util = new \OCA\Encryption\Util($filesystemView, $user); // fix the file size - $absolutePath = \OC\Files\Filesystem::normalizePath('/' . $user . '/files/'. $location. '/' .$filename); + $absolutePath = \OC\Files\Filesystem::normalizePath('/' . $owner . '/files/'. $ownerPath); $util->fixFileSize($absolutePath); // get current sharing state @@ -475,7 +509,25 @@ class Trashbin { $file = $filename; } + $size += self::deleteVersions($view, $file, $filename, $timestamp); + $size += self::deleteEncryptionKeys($view, $file, $filename, $timestamp); + + if ($view->is_dir('/files_trashbin/files/'.$file)) { + $size += self::calculateSize(new \OC\Files\View('/'.$user.'/files_trashbin/files/'.$file)); + } else { + $size += $view->filesize('/files_trashbin/files/'.$file); + } + $view->unlink('/files_trashbin/files/'.$file); + $trashbinSize -= $size; + self::setTrashbinSize($user, $trashbinSize); + + return $size; + } + + private static function deleteVersions($view, $file, $filename, $timestamp) { + $size = 0; if ( \OCP\App::isEnabled('files_versions') ) { + $user = \OCP\User::getUser(); if ($view->is_dir('files_trashbin/versions/'.$file)) { $size += self::calculateSize(new \OC\Files\view('/'.$user.'/files_trashbin/versions/'.$file)); $view->unlink('files_trashbin/versions/'.$file); @@ -491,35 +543,37 @@ class Trashbin { } } } - - // Take care of encryption keys - $parts = pathinfo($file); - if ( $view->is_dir('/files_trashbin/files/'.$file) ) { - $keyfile = \OC\Files\Filesystem::normalizePath('files_trashbin/keyfiles/'.$filename); - } else { - $keyfile = \OC\Files\Filesystem::normalizePath('files_trashbin/keyfiles/'.$filename.'.key'); - } - if ($timestamp) { - $keyfile .= '.d'.$timestamp; - } - if ( \OCP\App::isEnabled('files_encryption') && $view->file_exists($keyfile) ) { - if ( $view->is_dir($keyfile) ) { - $size += self::calculateSize(new \OC\Files\View('/'.$user.'/'.$keyfile)); + return $size; + } + + private static function deleteEncryptionKeys($view, $file, $filename, $timestamp) { + $size = 0; + if (\OCP\App::isEnabled('files_encryption')) { + $user = \OCP\User::getUser(); + + if ($view->is_dir('/files_trashbin/files/' . $file)) { + $keyfile = \OC\Files\Filesystem::normalizePath('files_trashbin/keyfiles/' . $filename); + $sharekeys = \OC\Files\Filesystem::normalizePath('files_trashbin/share-keys/' . $filename); } else { - $size += $view->filesize($keyfile); + $keyfile = \OC\Files\Filesystem::normalizePath('files_trashbin/keyfiles/' . $filename . '.key'); + $sharekeys = \OC\Files\Filesystem::normalizePath('files_trashbin/share-keys/' . $filename . '.' . $user . '.shareKey'); + } + if ($timestamp) { + $keyfile .= '.d' . $timestamp; + $sharekeys .= '.d' . $timestamp; + } + if ($view->file_exists($keyfile)) { + if ($view->is_dir($keyfile)) { + $size += self::calculateSize(new \OC\Files\View('/' . $user . '/' . $keyfile)); + $size += self::calculateSize(new \OC\Files\View('/' . $user . '/' . $sharekeys)); + } else { + $size += $view->filesize($keyfile); + $size += $view->filesize($sharekeys); + } + $view->unlink($keyfile); + $view->unlink($sharekeys); } - $view->unlink($keyfile); } - - if ($view->is_dir('/files_trashbin/files/'.$file)) { - $size += self::calculateSize(new \OC\Files\View('/'.$user.'/files_trashbin/files/'.$file)); - } else { - $size += $view->filesize('/files_trashbin/files/'.$file); - } - $view->unlink('/files_trashbin/files/'.$file); - $trashbinSize -= $size; - self::setTrashbinSize($user, $trashbinSize); - return $size; } @@ -779,5 +833,14 @@ class Trashbin { } $query->execute(array($size, $user)); } - + + /** + * register hooks + */ + public static function registerHooks() { + //Listen to delete file signal + \OCP\Util::connectHook('OC_Filesystem', 'delete', "OCA\Files_Trashbin\Hooks", "remove_hook"); + //Listen to delete user signal + \OCP\Util::connectHook('OC_User', 'pre_deleteUser', "OCA\Files_Trashbin\Hooks", "deleteUser_hook"); + } } diff --git a/apps/files_versions/l10n/he.php b/apps/files_versions/l10n/he.php index ad2e261d53..577389eb08 100644 --- a/apps/files_versions/l10n/he.php +++ b/apps/files_versions/l10n/he.php @@ -1,3 +1,11 @@ "גרסאות" +"Could not revert: %s" => "לא ניתן להחזיר: %s", +"success" => "הושלם", +"File %s was reverted to version %s" => "הקובץ %s הוחזר לגרסה %s", +"failure" => "נכשל", +"File %s could not be reverted to version %s" => "לא ניתן להחזיר את הקובץ %s לגרסה %s", +"No old versions available" => "אין גרסאות ישנות זמינות", +"No path specified" => "לא צוין נתיב", +"Versions" => "גרסאות", +"Revert a file to a previous version by clicking on its revert button" => "ניתן להחזיר קובץ לגרסה קודמת על ידי לחיצה על לחצן ההחזרה שלו" ); diff --git a/apps/files_versions/l10n/sv.php b/apps/files_versions/l10n/sv.php index bcd21bc599..8317097630 100644 --- a/apps/files_versions/l10n/sv.php +++ b/apps/files_versions/l10n/sv.php @@ -7,5 +7,5 @@ "No old versions available" => "Inga gamla versioner finns tillgängliga", "No path specified" => "Ingen sökväg angiven", "Versions" => "Versioner", -"Revert a file to a previous version by clicking on its revert button" => "Återställ en fil till en tidigare version genom att klicka på knappen" +"Revert a file to a previous version by clicking on its revert button" => "Återställ en fil till en tidigare version genom att klicka på återställningsknappen" ); diff --git a/apps/files_versions/lib/versions.php b/apps/files_versions/lib/versions.php index 5fdbef2774..4beb9e0fe5 100644 --- a/apps/files_versions/lib/versions.php +++ b/apps/files_versions/lib/versions.php @@ -113,8 +113,16 @@ class Storage { mkdir($versionsFolderName.'/'.$info['dirname'], 0750, true); } + // disable proxy to prevent multiple fopen calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + // store a new version of a file $users_view->copy('files'.$filename, 'files_versions'.$filename.'.v'.$users_view->filemtime('files'.$filename)); + + // reset proxy state + \OC_FileProxy::$enabled = $proxyStatus; + $versionsSize = self::getVersionsSize($uid); if ( $versionsSize === false || $versionsSize < 0 ) { $versionsSize = self::calculateSize($uid); @@ -195,7 +203,16 @@ class Storage { //first create a new version $version = 'files_versions'.$filename.'.v'.$users_view->filemtime('files'.$filename); if ( !$users_view->file_exists($version)) { + + // disable proxy to prevent multiple fopen calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + $users_view->copy('files'.$filename, 'files_versions'.$filename.'.v'.$users_view->filemtime('files'.$filename)); + + // reset proxy state + \OC_FileProxy::$enabled = $proxyStatus; + $versionCreated = true; } diff --git a/apps/user_ldap/appinfo/app.php b/apps/user_ldap/appinfo/app.php index 81eaa0404b..593e846bc0 100644 --- a/apps/user_ldap/appinfo/app.php +++ b/apps/user_ldap/appinfo/app.php @@ -49,7 +49,7 @@ $entry = array( 'name' => 'LDAP' ); -OCP\Backgroundjob::addRegularTask('OCA\user_ldap\lib\Jobs', 'updateGroups'); +OCP\Backgroundjob::registerJob('OCA\user_ldap\lib\Jobs'); if(OCP\App::isEnabled('user_webdavauth')) { OCP\Util::writeLog('user_ldap', 'user_ldap and user_webdavauth are incompatible. You may experience unexpected behaviour', diff --git a/apps/user_ldap/js/settings.js b/apps/user_ldap/js/settings.js index f47d49cf22..52d5dbc48d 100644 --- a/apps/user_ldap/js/settings.js +++ b/apps/user_ldap/js/settings.js @@ -14,7 +14,7 @@ var LdapConfiguration = { //deal with Checkboxes if($(elementID).is('input[type=checkbox]')) { - if(configvalue === 1) { + if(parseInt(configvalue) === 1) { $(elementID).attr('checked', 'checked'); } else { $(elementID).removeAttr('checked'); diff --git a/apps/user_ldap/l10n/cs_CZ.php b/apps/user_ldap/l10n/cs_CZ.php index dd7373eb69..65c7c76b5d 100644 --- a/apps/user_ldap/l10n/cs_CZ.php +++ b/apps/user_ldap/l10n/cs_CZ.php @@ -1,4 +1,5 @@ "Selhalo zrušení mapování.", "Failed to delete the server configuration" => "Selhalo smazání nastavení serveru", "The configuration is valid and the connection could be established!" => "Nastavení je v pořádku a spojení bylo navázáno.", "The configuration is valid, but the Bind failed. Please check the server settings and credentials." => "Konfigurace je v pořádku, ale spojení selhalo. Zkontrolujte, prosím, nastavení serveru a přihlašovací údaje.", @@ -7,6 +8,7 @@ "Take over settings from recent server configuration?" => "Převzít nastavení z nedávného nastavení serveru?", "Keep settings?" => "Ponechat nastavení?", "Cannot add server configuration" => "Nelze přidat nastavení serveru", +"mappings cleared" => "mapování zrušeno", "Success" => "Úspěch", "Error" => "Chyba", "Connection test succeeded" => "Test spojení byl úspěšný", @@ -72,6 +74,13 @@ "Email Field" => "Pole e-mailu", "User Home Folder Naming Rule" => "Pravidlo pojmenování domovské složky uživatele", "Leave empty for user name (default). Otherwise, specify an LDAP/AD attribute." => "Ponechte prázdné pro uživatelské jméno (výchozí). Jinak uveďte LDAP/AD parametr.", +"Internal Username" => "Interní uživatelské jméno", +"Internal Username Attribute:" => "Atribut interního uživatelského jména:", +"Override UUID detection" => "Nastavit ručně UUID atribut", +"UUID Attribute:" => "Atribut UUID:", +"Username-LDAP User Mapping" => "Mapování uživatelských jmen z LDAPu", +"Clear Username-LDAP User Mapping" => "Zrušit mapování uživatelských jmen LDAPu", +"Clear Groupname-LDAP Group Mapping" => "Zrušit mapování názvů skupin LDAPu", "Test Configuration" => "Vyzkoušet nastavení", "Help" => "Nápověda" ); diff --git a/apps/user_ldap/l10n/de_DE.php b/apps/user_ldap/l10n/de_DE.php index de2dd118e5..8aa64477e4 100644 --- a/apps/user_ldap/l10n/de_DE.php +++ b/apps/user_ldap/l10n/de_DE.php @@ -73,6 +73,9 @@ "User Home Folder Naming Rule" => "Benennungsregel für das Home-Verzeichnis des Benutzers", "Leave empty for user name (default). Otherwise, specify an LDAP/AD attribute." => "Ohne Eingabe wird der Benutzername (Standard) verwendet. Anderenfalls tragen Sie bitte ein LDAP/AD-Attribut ein.", "Internal Username" => "Interner Benutzername", +"By default the internal username will be created from the UUID attribute. It makes sure that the username is unique and characters do not need to be converted. The internal username has the restriction that only these characters are allowed: [ a-zA-Z0-9_.@- ]. Other characters are replaced with their ASCII correspondence or simply omitted. On collisions a number will be added/increased. The internal username is used to identify a user internally. It is also the default name for the user home folder in ownCloud. It is also a port of remote URLs, for instance for all *DAV services. With this setting, the default behaviour can be overriden. To achieve a similar behaviour as before ownCloud 5 enter the user display name attribute in the following field. Leave it empty for default behaviour. Changes will have effect only on newly mapped (added) LDAP users." => "Standardmäßig wird der interne Benutzername mittels des UUID-Attributes erzeugt. Dies stellt sicher, dass der Benutzername einzigartig ist und keinerlei Zeichen konvertiert werden müssen. Der interne Benutzername unterliegt Beschränkungen, die nur die nachfolgenden Zeichen erlauben: [ a-zA-Z0-9_.@- ]. Andere Zeichenwerden mittels ihrer korrespondierenden Zeichen ersetzt oder einfach ausgelassen. Bei Übereinstimmungen wird ein Zähler hinzugefügt bzw. der Zähler um einen Wert erhöht. Der interne Benutzername wird benutzt, um einen Benutzer intern zu identifizieren. Es ist ebenso der standardmäßig vorausgewählte Namen des Heimatverzeichnisses in ownCloud. Es dient weiterhin als Port für Remote-URLs - zum Beispiel für alle *DAV-Dienste Mit dieser Einstellung kann das Standardverhalten überschrieben werden. Um ein ähnliches Verhalten wie vor ownCloud 5 zu erzielen, fügen Sie das anzuzeigende Attribut des Benutzernamens in das nachfolgende Feld ein. Lassen Sie dies hingegen für das Standardverhalten leer. Die Änderungen werden sich einzig und allein nur auf neu gemappte (hinzugefügte) LDAP-Benutzer auswirken.", +"Override UUID detection" => "UUID-Erkennung überschreiben", +"UUID Attribute:" => "UUID-Attribut:", "Test Configuration" => "Testkonfiguration", "Help" => "Hilfe" ); diff --git a/apps/user_ldap/l10n/es.php b/apps/user_ldap/l10n/es.php index 7c72cc8e63..31d43288e5 100644 --- a/apps/user_ldap/l10n/es.php +++ b/apps/user_ldap/l10n/es.php @@ -1,19 +1,21 @@ "Ocurrió un fallo al borrar las asignaciones.", "Failed to delete the server configuration" => "No se pudo borrar la configuración del servidor", "The configuration is valid and the connection could be established!" => "La configuración es válida y la conexión puede establecerse!", "The configuration is valid, but the Bind failed. Please check the server settings and credentials." => "La configuración es válida, pero falló el Enlace. Por favor, compruebe la configuración del servidor y las credenciales.", "The configuration is invalid. Please look in the ownCloud log for further details." => "La configuración no es válida. Por favor, busque en el log de ownCloud para más detalles.", "Deletion failed" => "Falló el borrado", -"Take over settings from recent server configuration?" => "Hacerse cargo de los ajustes de configuración del servidor reciente?", +"Take over settings from recent server configuration?" => "¿Asumir los ajustes actuales de la configuración del servidor?", "Keep settings?" => "Mantener la configuración?", "Cannot add server configuration" => "No se puede añadir la configuración del servidor", +"mappings cleared" => "Asignaciones borradas", "Success" => "Éxito", "Error" => "Error", "Connection test succeeded" => "La prueba de conexión fue exitosa", "Connection test failed" => "La prueba de conexión falló", "Do you really want to delete the current Server Configuration?" => "¿Realmente desea eliminar la configuración actual del servidor?", "Confirm Deletion" => "Confirmar eliminación", -"Warning: Apps user_ldap and user_webdavauth are incompatible. You may experience unexpected behaviour. Please ask your system administrator to disable one of them." => "Advertencia: Los Apps user_ldap y user_webdavauth son incompatibles. Puede que experimente un comportamiento inesperado. Pregunte al administrador del sistema para desactivar uno de ellos.", +"Warning: Apps user_ldap and user_webdavauth are incompatible. You may experience unexpected behaviour. Please ask your system administrator to disable one of them." => "Advertencia: Las aplicaciones user_ldap y user_webdavauth son incompatibles. Puede que experimente un comportamiento inesperado. Pregunte al administrador del sistema para desactivar uno de ellos.", "Warning: The PHP LDAP module is not installed, the backend will not work. Please ask your system administrator to install it." => "Advertencia: El módulo LDAP de PHP no está instalado, el sistema no funcionará. Por favor consulte al administrador del sistema para instalarlo.", "Server configuration" => "Configuración del Servidor", "Add Server Configuration" => "Agregar configuracion del servidor", @@ -28,30 +30,30 @@ "For anonymous access, leave DN and Password empty." => "Para acceso anónimo, deje DN y contraseña vacíos.", "User Login Filter" => "Filtro de inicio de sesión de usuario", "Defines the filter to apply, when login is attempted. %%uid replaces the username in the login action." => "Define el filtro a aplicar cuando se ha realizado un login. %%uid remplazrá el nombre de usuario en el proceso de login.", -"use %%uid placeholder, e.g. \"uid=%%uid\"" => "usar %%uid como placeholder, ej: \"uid=%%uid\"", +"use %%uid placeholder, e.g. \"uid=%%uid\"" => "usar %%uid como comodín, ej: \"uid=%%uid\"", "User List Filter" => "Lista de filtros de usuario", "Defines the filter to apply, when retrieving users." => "Define el filtro a aplicar, cuando se obtienen usuarios.", -"without any placeholder, e.g. \"objectClass=person\"." => "Sin placeholder, ej: \"objectClass=person\".", +"without any placeholder, e.g. \"objectClass=person\"." => "Sin comodines, ej: \"objectClass=person\".", "Group Filter" => "Filtro de grupo", "Defines the filter to apply, when retrieving groups." => "Define el filtro a aplicar, cuando se obtienen grupos.", -"without any placeholder, e.g. \"objectClass=posixGroup\"." => "Con cualquier placeholder, ej: \"objectClass=posixGroup\".", -"Connection Settings" => "Configuracion de coneccion", +"without any placeholder, e.g. \"objectClass=posixGroup\"." => "sin comodines, ej: \"objectClass=posixGroup\".", +"Connection Settings" => "Configuración de conexión", "Configuration Active" => "Configuracion activa", "When unchecked, this configuration will be skipped." => "Cuando deseleccione, esta configuracion sera omitida.", "Port" => "Puerto", -"Backup (Replica) Host" => "Host para backup (Replica)", -"Give an optional backup host. It must be a replica of the main LDAP/AD server." => "Dar un host de copia de seguridad opcional. Debe ser una réplica del servidor principal LDAP / AD.", -"Backup (Replica) Port" => "Puerto para backup (Replica)", +"Backup (Replica) Host" => "Servidor de copia de seguridad (Replica)", +"Give an optional backup host. It must be a replica of the main LDAP/AD server." => "Dar un servidor de copia de seguridad opcional. Debe ser una réplica del servidor principal LDAP / AD.", +"Backup (Replica) Port" => "Puerto para copias de seguridad (Replica)", "Disable Main Server" => "Deshabilitar servidor principal", -"When switched on, ownCloud will only connect to the replica server." => "Cuando se inicie, ownCloud unicamente estara conectado al servidor replica", +"When switched on, ownCloud will only connect to the replica server." => "Cuando se inicie, ownCloud unicamente conectará al servidor replica", "Use TLS" => "Usar TLS", -"Do not use it additionally for LDAPS connections, it will fail." => "No usar adicionalmente para conecciones LDAPS, estas fallaran", -"Case insensitve LDAP server (Windows)" => "Servidor de LDAP sensible a mayúsculas/minúsculas (Windows)", +"Do not use it additionally for LDAPS connections, it will fail." => "No lo use para conexiones LDAPS, Fallará.", +"Case insensitve LDAP server (Windows)" => "Servidor de LDAP no sensible a mayúsculas/minúsculas (Windows)", "Turn off SSL certificate validation." => "Apagar la validación por certificado SSL.", "If connection only works with this option, import the LDAP server's SSL certificate in your ownCloud server." => "Si la conexión sólo funciona con esta opción, importe el certificado SSL del servidor LDAP en su servidor ownCloud.", "Not recommended, use for testing only." => "No recomendado, sólo para pruebas.", "Cache Time-To-Live" => "Cache TTL", -"in seconds. A change empties the cache." => "en segundos. Un cambio vacía la cache.", +"in seconds. A change empties the cache." => "en segundos. Un cambio vacía la caché.", "Directory Settings" => "Configuracion de directorio", "User Display Name Field" => "Campo de nombre de usuario a mostrar", "The LDAP attribute to use to generate the user`s ownCloud name." => "El atributo LDAP a usar para generar el nombre de usuario de ownCloud.", @@ -73,7 +75,15 @@ "User Home Folder Naming Rule" => "Regla para la carpeta Home de usuario", "Leave empty for user name (default). Otherwise, specify an LDAP/AD attribute." => "Vacío para el nombre de usuario (por defecto). En otro caso, especifique un atributo LDAP/AD.", "Internal Username" => "Nombre de usuario interno", -"ownCloud uses usernames to store and assign (meta) data. In order to precisely identify and recognize users, each LDAP user will have a internal username. This requires a mapping from ownCloud username to LDAP user. The created username is mapped to the UUID of the LDAP user. Additionally the DN is cached as well to reduce LDAP interaction, but it is not used for identification. If the DN changes, the changes will be found by ownCloud. The internal ownCloud name is used all over in ownCloud. Clearing the Mappings will have leftovers everywhere. Clearing the Mappings is not configuration sensitive, it affects all LDAP configurations! Do never clear the mappings in a production environment. Only clear mappings in a testing or experimental stage." => "ownCloud utiliza nombre de usuarios para almacenar y asignar (meta) datos. Con el fin de identificar con precisión y reconocer usuarios, cada usuario LDAP tendrá un nombre de usuario interno. Esto requiere una asignación de nombre de usuario de ownCloud a usuario LDAP. El nombre de usuario creado se asigna al UUID del usuario LDAP. Además el DN se almacena en caché más bien para reducir la interacción de LDAP, pero no se utiliza para la identificación. Si la DN cambia, los cambios serán encontrados por ownCloud. El nombre interno de ownCloud se utiliza para todo en ownCloud. Eliminando las asignaciones tendrá restos por todas partes. Eliminando las asignaciones no es sensible a la configuración, que afecta a todas las configuraciones de LDAP! No limpiar nunca las asignaciones en un entorno de producción. Sólo borrar asignaciones en una situación de prueba o experimental.", +"By default the internal username will be created from the UUID attribute. It makes sure that the username is unique and characters do not need to be converted. The internal username has the restriction that only these characters are allowed: [ a-zA-Z0-9_.@- ]. Other characters are replaced with their ASCII correspondence or simply omitted. On collisions a number will be added/increased. The internal username is used to identify a user internally. It is also the default name for the user home folder in ownCloud. It is also a port of remote URLs, for instance for all *DAV services. With this setting, the default behaviour can be overriden. To achieve a similar behaviour as before ownCloud 5 enter the user display name attribute in the following field. Leave it empty for default behaviour. Changes will have effect only on newly mapped (added) LDAP users." => "Por defecto el nombre de usuario interno será creado desde el atributo UUID. Esto asegura que el nombre de usuario es único y los caracteres no necesitan ser convertidos. En el nombre de usuario interno sólo se pueden usar estos caracteres: [a-zA-Z0-9_.@-]. Otros caracteres son sustituidos por su correspondiente en ASCII o simplemente quitados. En coincidencias un número será añadido o incrementado. El nombre de usuario interno es usado para identificar un usuario internamente. Es también el nombre por defecto para la carpeta personal del usuario in ownCloud. También es un puerto de URLs remotas, por ejemplo, para todos los servicios *DAV. Con esta configuración el comportamiento por defecto puede ser cambiado. Para conseguir un comportamiento similar a como era antes de ownCloud 5, introduce el atributo del nombre en pantalla del usuario en el siguiente campo. Déjalo vacío para el comportamiento por defecto. Los cambios solo tendrán efecto en los nuevos usuarios LDAP.", +"Internal Username Attribute:" => "Atributo Nombre de usuario Interno:", +"Override UUID detection" => "Sobrescribir la detección UUID", +"By default, ownCloud autodetects the UUID attribute. The UUID attribute is used to doubtlessly identify LDAP users and groups. Also, the internal username will be created based on the UUID, if not specified otherwise above. You can override the setting and pass an attribute of your choice. You must make sure that the attribute of your choice can be fetched for both users and groups and it is unique. Leave it empty for default behaviour. Changes will have effect only on newly mapped (added) LDAP users and groups." => "Por defecto, ownCloud autodetecta el atributo UUID. El atributo UUID es usado para identificar indudablemente usuarios y grupos LDAP. Además, el nombre de usuario interno será creado en base al UUID, si no ha sido especificado otro comportamiento arriba. Puedes sobrescribir la configuración y pasar un atributo de tu elección. Debes asegurarte de que el atributo de tu elección sea accesible por los usuarios y grupos y ser único. Déjalo en blanco para usar el comportamiento por defecto. Los cambios tendrán efecto solo en los nuevos usuarios y grupos de LDAP.", +"UUID Attribute:" => "Atributo UUID:", +"Username-LDAP User Mapping" => "Asignación del Nombre de usuario de un usuario LDAP", +"ownCloud uses usernames to store and assign (meta) data. In order to precisely identify and recognize users, each LDAP user will have a internal username. This requires a mapping from ownCloud username to LDAP user. The created username is mapped to the UUID of the LDAP user. Additionally the DN is cached as well to reduce LDAP interaction, but it is not used for identification. If the DN changes, the changes will be found by ownCloud. The internal ownCloud name is used all over in ownCloud. Clearing the Mappings will have leftovers everywhere. Clearing the Mappings is not configuration sensitive, it affects all LDAP configurations! Do never clear the mappings in a production environment. Only clear mappings in a testing or experimental stage." => "ownCloud utiliza nombres de usuario para almacenar y asignar (meta) datos. Con el fin de identificar con precisión y reconocer usuarios, cada usuario LDAP tendrá un nombre de usuario interno. Esto requiere una asignación de nombre de usuario de ownCloud a usuario LDAP. El nombre de usuario creado se asigna al UUID del usuario LDAP. Además el DN se almacena en caché más bien para reducir la interacción de LDAP, pero no se utiliza para la identificación. Si la DN cambia, los cambios serán encontrados por ownCloud. El nombre interno de ownCloud se utiliza para todo en ownCloud. Eliminando las asignaciones tendrá restos por todas partes. Eliminando las asignaciones no es sensible a la configuración, que afecta a todas las configuraciones de LDAP! No limpiar nunca las asignaciones en un entorno de producción. Sólo borrar asignaciones en una situación de prueba o experimental.", +"Clear Username-LDAP User Mapping" => "Borrar la asignación de los Nombres de usuario de los usuarios LDAP", +"Clear Groupname-LDAP Group Mapping" => "Borrar la asignación de los Nombres de grupo de los grupos de LDAP", "Test Configuration" => "Configuración de prueba", "Help" => "Ayuda" ); diff --git a/apps/user_ldap/l10n/es_AR.php b/apps/user_ldap/l10n/es_AR.php index 98fb32b1d2..011ff3e12f 100644 --- a/apps/user_ldap/l10n/es_AR.php +++ b/apps/user_ldap/l10n/es_AR.php @@ -1,6 +1,6 @@ "Fallo al borrar la configuración del servidor", -"The configuration is valid and the connection could be established!" => "La configuración es valida y la conexión pudo ser establecida.", +"The configuration is valid and the connection could be established!" => "La configuración es válida y la conexión pudo ser establecida.", "The configuration is valid, but the Bind failed. Please check the server settings and credentials." => "La configuración es válida, pero el enlace falló. Por favor, comprobá la configuración del servidor y las credenciales.", "The configuration is invalid. Please look in the ownCloud log for further details." => "La configuración no es válida. Por favor, buscá en el log de ownCloud más detalles.", "Deletion failed" => "Error al borrar", diff --git a/apps/user_ldap/l10n/hu_HU.php b/apps/user_ldap/l10n/hu_HU.php index cbbcc69ede..361ae5e040 100644 --- a/apps/user_ldap/l10n/hu_HU.php +++ b/apps/user_ldap/l10n/hu_HU.php @@ -1,4 +1,5 @@ "Nem sikerült törölni a hozzárendeléseket.", "Failed to delete the server configuration" => "Nem sikerült törölni a kiszolgáló konfigurációját", "The configuration is valid and the connection could be established!" => "A konfiguráció érvényes, és a kapcsolat létrehozható!", "The configuration is valid, but the Bind failed. Please check the server settings and credentials." => "A konfiguráció érvényes, de a kapcsolat nem hozható létre. Kérem ellenőrizze a kiszolgáló beállításait, és az elérési adatokat.", @@ -7,6 +8,8 @@ "Take over settings from recent server configuration?" => "Vegyük át a beállításokat az előző konfigurációból?", "Keep settings?" => "Tartsuk meg a beállításokat?", "Cannot add server configuration" => "Az új kiszolgáló konfigurációja nem hozható létre", +"mappings cleared" => "Töröltük a hozzárendeléseket", +"Success" => "Sikeres végrehajtás", "Error" => "Hiba", "Connection test succeeded" => "A kapcsolatellenőrzés eredménye: sikerült", "Connection test failed" => "A kapcsolatellenőrzés eredménye: nem sikerült", @@ -71,6 +74,13 @@ "Email Field" => "Email mező", "User Home Folder Naming Rule" => "A home könyvtár elérési útvonala", "Leave empty for user name (default). Otherwise, specify an LDAP/AD attribute." => "Hagyja üresen, ha a felhasználónevet kívánja használni. Ellenkező esetben adjon meg egy LDAP/AD attribútumot!", +"Internal Username" => "Belső felhasználónév", +"Internal Username Attribute:" => "A belső felhasználónév attribútuma:", +"Override UUID detection" => "Az UUID-felismerés felülbírálása", +"UUID Attribute:" => "UUID attribútum:", +"Username-LDAP User Mapping" => "Felhasználó - LDAP felhasználó hozzárendelés", +"Clear Username-LDAP User Mapping" => "A felhasználó - LDAP felhasználó hozzárendelés törlése", +"Clear Groupname-LDAP Group Mapping" => "A csoport - LDAP csoport hozzárendelés törlése", "Test Configuration" => "A beállítások tesztelése", "Help" => "Súgó" ); diff --git a/apps/user_ldap/l10n/is.php b/apps/user_ldap/l10n/is.php index dadac9eeda..6ea474f56d 100644 --- a/apps/user_ldap/l10n/is.php +++ b/apps/user_ldap/l10n/is.php @@ -1,6 +1,8 @@ "Geyma stillingar ?", "Error" => "Villa", "Host" => "Netþjónn", "Password" => "Lykilorð", +"Test Configuration" => "Prúfa uppsetningu", "Help" => "Hjálp" ); diff --git a/apps/user_ldap/l10n/nl.php b/apps/user_ldap/l10n/nl.php index c935d387cc..c75aae3ea4 100644 --- a/apps/user_ldap/l10n/nl.php +++ b/apps/user_ldap/l10n/nl.php @@ -75,10 +75,13 @@ "User Home Folder Naming Rule" => "Gebruikers Home map naamgevingsregel", "Leave empty for user name (default). Otherwise, specify an LDAP/AD attribute." => "Laat leeg voor de gebruikersnaam (standaard). Of, specificeer een LDAP/AD attribuut.", "Internal Username" => "Interne gebruikersnaam", +"By default the internal username will be created from the UUID attribute. It makes sure that the username is unique and characters do not need to be converted. The internal username has the restriction that only these characters are allowed: [ a-zA-Z0-9_.@- ]. Other characters are replaced with their ASCII correspondence or simply omitted. On collisions a number will be added/increased. The internal username is used to identify a user internally. It is also the default name for the user home folder in ownCloud. It is also a port of remote URLs, for instance for all *DAV services. With this setting, the default behaviour can be overriden. To achieve a similar behaviour as before ownCloud 5 enter the user display name attribute in the following field. Leave it empty for default behaviour. Changes will have effect only on newly mapped (added) LDAP users." => "Standaard wordt de interne gebruikersnaam aangemaakt op basis van de UUID attribuut. Het zorgt ervoor dat de gebruikersnaam uniek is en dat tekens niet hoeven te worden geconverteerd. De interne gebruikersnaam heeft als beperking dat alleen deze tekens zijn toegestaan​​: [a-zA-Z0-9_.@- ]. Andere tekens worden vervangen door hun ASCII vertaling of gewoonweg weggelaten. Bij identieke namen wordt een nummer toegevoegd of verhoogd. De interne gebruikersnaam wordt gebruikt om een ​​gebruiker binnen het systeem te herkennen. Het is ook de standaardnaam voor de standaardmap van de gebruiker in ownCloud. Het is ook een vertaling voor externe URL's, bijvoorbeeld voor alle * DAV diensten. Met deze instelling kan het standaardgedrag worden overschreven. Om een soortgelijk gedrag te bereiken als van voor ownCloud 5, voer het gebruikersweergavenaam attribuut in in het volgende veld. Laat het leeg voor standaard gedrag. Veranderingen worden alleen toegepast op nieuw in kaart gebracht (toegevoegde) LDAP-gebruikers.", "Internal Username Attribute:" => "Interne gebruikersnaam attribuut:", "Override UUID detection" => "Negeren UUID detectie", +"By default, ownCloud autodetects the UUID attribute. The UUID attribute is used to doubtlessly identify LDAP users and groups. Also, the internal username will be created based on the UUID, if not specified otherwise above. You can override the setting and pass an attribute of your choice. You must make sure that the attribute of your choice can be fetched for both users and groups and it is unique. Leave it empty for default behaviour. Changes will have effect only on newly mapped (added) LDAP users and groups." => "Standaard herkent ownCloud het UUID attribuut automatisch. Het UUID attribuut wordt gebruikt om LDAP-gebruikers en -groepen uniek te identificeren. Ook zal de interne gebruikersnaam worden aangemaakt op basis van het UUID, tenzij dit hierboven anders is aangegeven. U kunt de instelling overschrijven en zelf een waarde voor het attribuut opgeven. U moet ervoor zorgen dat het ingestelde attribuut kan worden opgehaald voor zowel gebruikers als groepen en dat het uniek is. Laat het leeg voor standaard gedrag. Veranderingen worden alleen doorgevoerd op nieuw in kaart gebrachte (toegevoegde) LDAP-gebruikers en-groepen.", "UUID Attribute:" => "UUID Attribuut:", "Username-LDAP User Mapping" => "Gebruikersnaam-LDAP gebruikers vertaling", +"ownCloud uses usernames to store and assign (meta) data. In order to precisely identify and recognize users, each LDAP user will have a internal username. This requires a mapping from ownCloud username to LDAP user. The created username is mapped to the UUID of the LDAP user. Additionally the DN is cached as well to reduce LDAP interaction, but it is not used for identification. If the DN changes, the changes will be found by ownCloud. The internal ownCloud name is used all over in ownCloud. Clearing the Mappings will have leftovers everywhere. Clearing the Mappings is not configuration sensitive, it affects all LDAP configurations! Do never clear the mappings in a production environment. Only clear mappings in a testing or experimental stage." => "ownCloud maakt gebruik van gebruikersnamen om (meta) data op te slaan en toe te wijzen. Om gebruikers uniek te identificeren, zal elke LDAP-gebruiker ook een interne gebruikersnaam krijgen. Dit vereist een mapping van de ownCloud gebruikersnaam naar een ​​LDAP-gebruiker. De gecreëerde gebruikersnaam is gekoppeld aan de UUID van de LDAP-gebruiker. Ook de 'DN' wordt gecached om het aantal LDAP transacties te verminderen, maar deze wordt niet gebruikt voor identificatie. Als de DN verandert, zullen de veranderingen worden gevonden door ownCloud. De interne ownCloud naam wordt overal in ownCloud gebruikt. Wissen van de koppeling zal overal overblijfsel laten staan. Het wissen van Mappings is niet configuratiegevoelig, maar het raakt wel alle LDAP instellingen! Zorg ervoor dat deze Mappings nooit in een productieomgeving plaatsvinden. Maak ze alleen leeg in een test-of ontwikkelomgeving.", "Clear Username-LDAP User Mapping" => "Leegmaken Gebruikersnaam-LDAP gebruikers vertaling", "Clear Groupname-LDAP Group Mapping" => "Leegmaken Groepsnaam-LDAP groep vertaling", "Test Configuration" => "Test configuratie", diff --git a/apps/user_ldap/l10n/pl.php b/apps/user_ldap/l10n/pl.php index a7a831e3e5..5495ab9940 100644 --- a/apps/user_ldap/l10n/pl.php +++ b/apps/user_ldap/l10n/pl.php @@ -1,4 +1,5 @@ "Nie udało się wyczyścić mapowania.", "Failed to delete the server configuration" => "Nie można usunąć konfiguracji serwera", "The configuration is valid and the connection could be established!" => "Konfiguracja jest prawidłowa i można ustanowić połączenie!", "The configuration is valid, but the Bind failed. Please check the server settings and credentials." => "Konfiguracja jest prawidłowa, ale Bind nie. Sprawdź ustawienia serwera i poświadczenia.", @@ -7,6 +8,7 @@ "Take over settings from recent server configuration?" => "Przejmij ustawienia z ostatnich konfiguracji serwera?", "Keep settings?" => "Zachować ustawienia?", "Cannot add server configuration" => "Nie można dodać konfiguracji serwera", +"mappings cleared" => "Mapoanie wyczyszczone", "Success" => "Sukces", "Error" => "Błąd", "Connection test succeeded" => "Test połączenia udany", @@ -72,6 +74,14 @@ "Email Field" => "Pole email", "User Home Folder Naming Rule" => "Reguły nazewnictwa folderu domowego użytkownika", "Leave empty for user name (default). Otherwise, specify an LDAP/AD attribute." => "Pozostaw puste dla user name (domyślnie). W przeciwnym razie podaj atrybut LDAP/AD.", +"Internal Username" => "Wewnętrzna nazwa użytkownika", +"By default the internal username will be created from the UUID attribute. It makes sure that the username is unique and characters do not need to be converted. The internal username has the restriction that only these characters are allowed: [ a-zA-Z0-9_.@- ]. Other characters are replaced with their ASCII correspondence or simply omitted. On collisions a number will be added/increased. The internal username is used to identify a user internally. It is also the default name for the user home folder in ownCloud. It is also a port of remote URLs, for instance for all *DAV services. With this setting, the default behaviour can be overriden. To achieve a similar behaviour as before ownCloud 5 enter the user display name attribute in the following field. Leave it empty for default behaviour. Changes will have effect only on newly mapped (added) LDAP users." => "Domyślnie, wewnętrzna nazwa użytkownika zostanie utworzona z atrybutu UUID, ang. Universally unique identifier - Unikalny identyfikator użytkownika. To daje pewność, że nazwa użytkownika jest niepowtarzalna a znaki nie muszą być konwertowane. Wewnętrzna nazwa użytkownika dopuszcza jedynie znaki: [ a-zA-Z0-9_.@- ]. Pozostałe znaki zamieniane są na ich odpowiedniki ASCII lub po prostu pomijane. W przypadku, gdy nazwa się powtarza na końcu dodawana / zwiększana jest cyfra. Wewnętrzna nazwa użytkownika służy do wewnętrznej identyfikacji użytkownika. Jest to również domyślna nazwa głównego folderu w ownCloud. Jest to również klucz zdalnego URL, na przykład dla wszystkich usług *DAV. Dzięki temu ustawieniu można modyfikować domyślne zachowania. Aby osiągnąć podobny efekt jak w ownCloud 5 wpisz atrybut nazwy użytkownika w poniższym polu. Pozostaw puste dla domyślnego zachowania. Zmiany będą miały wpływ tylko na nowo stworzonych (dodane) użytkowników LDAP.", +"Internal Username Attribute:" => "Wewnętrzny atrybut nazwy uzżytkownika:", +"Override UUID detection" => "Zastąp wykrywanie UUID", +"UUID Attribute:" => "Atrybuty UUID:", +"Username-LDAP User Mapping" => "Mapowanie użytkownika LDAP", +"Clear Username-LDAP User Mapping" => "Czyść Mapowanie użytkownika LDAP", +"Clear Groupname-LDAP Group Mapping" => "Czyść Mapowanie nazwy grupy LDAP", "Test Configuration" => "Konfiguracja testowa", "Help" => "Pomoc" ); diff --git a/apps/user_ldap/l10n/pt_PT.php b/apps/user_ldap/l10n/pt_PT.php index ed1e0f376d..308fd34760 100644 --- a/apps/user_ldap/l10n/pt_PT.php +++ b/apps/user_ldap/l10n/pt_PT.php @@ -1,4 +1,5 @@ "Falhou a limpar os mapas", "Failed to delete the server configuration" => "Erro ao eliminar as configurações do servidor", "The configuration is valid and the connection could be established!" => "A configuração está correcta e foi possível estabelecer a ligação!", "The configuration is valid, but the Bind failed. Please check the server settings and credentials." => "A configuração está correcta, mas não foi possível estabelecer o \"laço\", por favor, verifique as configurações do servidor e as credenciais.", @@ -7,6 +8,7 @@ "Take over settings from recent server configuration?" => "Assumir as configurações da configuração do servidor mais recente?", "Keep settings?" => "Manter as definições?", "Cannot add server configuration" => "Não foi possível adicionar as configurações do servidor.", +"mappings cleared" => "Mapas limpos", "Success" => "Sucesso", "Error" => "Erro", "Connection test succeeded" => "Teste de conecção passado com sucesso.", @@ -72,6 +74,13 @@ "Email Field" => "Campo de email", "User Home Folder Naming Rule" => "Regra da pasta inicial do utilizador", "Leave empty for user name (default). Otherwise, specify an LDAP/AD attribute." => "Deixe vazio para nome de utilizador (padrão). De outro modo, especifique um atributo LDAP/AD.", +"Internal Username" => "Nome de utilizador interno", +"Internal Username Attribute:" => "Atributo do nome de utilizador interno", +"Override UUID detection" => "Passar a detecção do UUID", +"UUID Attribute:" => "Atributo UUID:", +"Username-LDAP User Mapping" => "Mapeamento do utilizador LDAP", +"Clear Username-LDAP User Mapping" => "Limpar mapeamento do utilizador-LDAP", +"Clear Groupname-LDAP Group Mapping" => "Limpar o mapeamento do nome de grupo LDAP", "Test Configuration" => "Testar a configuração", "Help" => "Ajuda" ); diff --git a/apps/user_ldap/l10n/ru.php b/apps/user_ldap/l10n/ru.php index eed6d373b9..0573be3993 100644 --- a/apps/user_ldap/l10n/ru.php +++ b/apps/user_ldap/l10n/ru.php @@ -1,4 +1,5 @@ "Не удалось очистить соотвествия.", "Failed to delete the server configuration" => "Не удалось удалить конфигурацию сервера", "The configuration is valid and the connection could be established!" => "Конфигурация правильная и подключение может быть установлено!", "The configuration is valid, but the Bind failed. Please check the server settings and credentials." => "Конфигурация верна, но операция подключения завершилась неудачно. Пожалуйста, проверьте настройки сервера и учетные данные.", @@ -7,6 +8,7 @@ "Take over settings from recent server configuration?" => "Принять настройки из последней конфигурации сервера?", "Keep settings?" => "Сохранить настройки?", "Cannot add server configuration" => "Не получилось добавить конфигурацию сервера", +"mappings cleared" => "Соответствия очищены", "Success" => "Успешно", "Error" => "Ошибка", "Connection test succeeded" => "Проверка соединения удалась", @@ -72,6 +74,13 @@ "Email Field" => "Поле адресса эллектронной почты", "User Home Folder Naming Rule" => "Правило именования Домашней Папки Пользователя", "Leave empty for user name (default). Otherwise, specify an LDAP/AD attribute." => "Оставьте имя пользователя пустым (по умолчанию). Иначе укажите атрибут LDAP/AD.", +"Internal Username" => "Внутреннее имя пользователя", +"Internal Username Attribute:" => "Аттрибут для внутреннего имени:", +"Override UUID detection" => "Переопределить нахождение UUID", +"UUID Attribute:" => "Аттрибут для UUID:", +"Username-LDAP User Mapping" => "Соответствия Имя-Пользователь LDAP", +"Clear Username-LDAP User Mapping" => "Очистить соответствия Имя-Пользователь LDAP", +"Clear Groupname-LDAP Group Mapping" => "Очистить соответствия Группа-Группа LDAP", "Test Configuration" => "Тестовая конфигурация", "Help" => "Помощь" ); diff --git a/apps/user_ldap/l10n/sk_SK.php b/apps/user_ldap/l10n/sk_SK.php index b31fe37756..e36a158936 100644 --- a/apps/user_ldap/l10n/sk_SK.php +++ b/apps/user_ldap/l10n/sk_SK.php @@ -1,4 +1,5 @@ "Nepodarilo sa vymazať mapovania.", "Failed to delete the server configuration" => "Zlyhalo zmazanie nastavenia servera.", "The configuration is valid and the connection could be established!" => "Nastavenie je v poriadku a pripojenie je stabilné.", "The configuration is valid, but the Bind failed. Please check the server settings and credentials." => "Nastavenie je v poriadku, ale pripojenie zlyhalo. Skontrolujte nastavenia servera a prihlasovacie údaje.", @@ -7,6 +8,7 @@ "Take over settings from recent server configuration?" => "Prebrať nastavenia z nedávneho nastavenia servera?", "Keep settings?" => "Ponechať nastavenia?", "Cannot add server configuration" => "Nemožno pridať nastavenie servera", +"mappings cleared" => "mapovanie vymazané", "Success" => "Úspešné", "Error" => "Chyba", "Connection test succeeded" => "Test pripojenia bol úspešný", @@ -72,6 +74,11 @@ "Email Field" => "Pole email", "User Home Folder Naming Rule" => "Pravidlo pre nastavenie mena používateľského priečinka dát", "Leave empty for user name (default). Otherwise, specify an LDAP/AD attribute." => "Nechajte prázdne pre používateľské meno (predvolené). Inak uveďte atribút LDAP/AD.", +"Internal Username" => "Interné používateľské meno", +"By default the internal username will be created from the UUID attribute. It makes sure that the username is unique and characters do not need to be converted. The internal username has the restriction that only these characters are allowed: [ a-zA-Z0-9_.@- ]. Other characters are replaced with their ASCII correspondence or simply omitted. On collisions a number will be added/increased. The internal username is used to identify a user internally. It is also the default name for the user home folder in ownCloud. It is also a port of remote URLs, for instance for all *DAV services. With this setting, the default behaviour can be overriden. To achieve a similar behaviour as before ownCloud 5 enter the user display name attribute in the following field. Leave it empty for default behaviour. Changes will have effect only on newly mapped (added) LDAP users." => "V predvolenom nastavení bude interné používateľské meno vytvorené z UUID atribútu. Zabezpečí sa to, že používateľské meno bude jedinečné a znaky nemusia byť prevedené. Interné meno má obmedzenie, iba tieto znaky sú povolené: [a-zA-Z0-9_ @ -.]. Ostatné znaky sú nahradené ich ASCII alebo jednoducho vynechané. Pri kolíziách bude číslo byť pridané / odobrané. Interné používateľské meno sa používa na identifikáciu používateľa interne. Je to tiež predvolený názov používateľského domovského priečinka v ownCloud. To je tiež port vzdialeného URL, napríklad pre všetky služby * DAV. S týmto nastavením sa dá prepísať predvolené správanie. Pre dosiahnutie podobného správania sa ako pred ownCloud 5 zadajte atribút zobrazenia používateľského mena v tomto poli. Ponechajte prázdne pre predvolené správanie. Zmeny budú mať vplyv iba na novo mapovaných (pridaných) LDAP používateľov.", +"Internal Username Attribute:" => "Atribút interného používateľského mena:", +"Override UUID detection" => "Prepísať UUID detekciu", +"UUID Attribute:" => "UUID atribút:", "Test Configuration" => "Test nastavenia", "Help" => "Pomoc" ); diff --git a/apps/user_ldap/l10n/sv.php b/apps/user_ldap/l10n/sv.php index eb30bd22f0..6ab8a9e5a6 100644 --- a/apps/user_ldap/l10n/sv.php +++ b/apps/user_ldap/l10n/sv.php @@ -1,4 +1,5 @@ "Fel vid rensning av mappningar", "Failed to delete the server configuration" => "Misslyckades med att radera serverinställningen", "The configuration is valid and the connection could be established!" => "Inställningen är giltig och anslutningen kunde upprättas!", "The configuration is valid, but the Bind failed. Please check the server settings and credentials." => "Konfigurationen är riktig, men Bind felade. Var vänlig och kontrollera serverinställningar och logininformation.", @@ -7,6 +8,7 @@ "Take over settings from recent server configuration?" => "Ta över inställningar från tidigare serverkonfiguration?", "Keep settings?" => "Behåll inställningarna?", "Cannot add server configuration" => "Kunde inte lägga till serverinställning", +"mappings cleared" => "mappningar rensade", "Success" => "Lyckat", "Error" => "Fel", "Connection test succeeded" => "Anslutningstestet lyckades", @@ -72,6 +74,16 @@ "Email Field" => "E-postfält", "User Home Folder Naming Rule" => "Namnregel för hemkatalog", "Leave empty for user name (default). Otherwise, specify an LDAP/AD attribute." => "Lämnas tomt för användarnamn (standard). Ange annars ett LDAP/AD-attribut.", +"Internal Username" => "Internt Användarnamn", +"By default the internal username will be created from the UUID attribute. It makes sure that the username is unique and characters do not need to be converted. The internal username has the restriction that only these characters are allowed: [ a-zA-Z0-9_.@- ]. Other characters are replaced with their ASCII correspondence or simply omitted. On collisions a number will be added/increased. The internal username is used to identify a user internally. It is also the default name for the user home folder in ownCloud. It is also a port of remote URLs, for instance for all *DAV services. With this setting, the default behaviour can be overriden. To achieve a similar behaviour as before ownCloud 5 enter the user display name attribute in the following field. Leave it empty for default behaviour. Changes will have effect only on newly mapped (added) LDAP users." => "Som standard skapas det interna användarnamnet från UUID-attributet. Det säkerställer att användarnamnet är unikt och tecken inte behöver konverteras. Det interna användarnamnet har restriktionerna att endast följande tecken är tillåtna: [ a-zA-Z0-9_.@- ]. Andra tecken blir ersatta av deras motsvarighet i ASCII eller utelämnas helt. En siffra kommer att läggas till eller ökas på vid en kollision. Det interna användarnamnet används för att identifiera användaren internt. Det är även förvalt som användarens användarnamn i ownCloud. Det är även en port för fjärråtkomst, t.ex. för alla *DAV-tjänster. Med denna inställning kan det förvalda beteendet åsidosättas. För att uppnå ett liknande beteende som innan ownCloud 5, ange attributet för användarens visningsnamn i detta fält. Lämna det tomt för förvalt beteende. Ändringarna kommer endast att påverka nyligen mappade (tillagda) LDAP-användare", +"Internal Username Attribute:" => "Internt Användarnamn Attribut:", +"Override UUID detection" => "Åsidosätt UUID detektion", +"By default, ownCloud autodetects the UUID attribute. The UUID attribute is used to doubtlessly identify LDAP users and groups. Also, the internal username will be created based on the UUID, if not specified otherwise above. You can override the setting and pass an attribute of your choice. You must make sure that the attribute of your choice can be fetched for both users and groups and it is unique. Leave it empty for default behaviour. Changes will have effect only on newly mapped (added) LDAP users and groups." => "Som standard upptäcker ownCloud automatiskt UUID-attributet. Det UUID-attributet används för att utan tvivel identifiera LDAP-användare och grupper. Dessutom kommer interna användarnamn skapas baserat på detta UUID, om inte annat anges ovan. Du kan åsidosätta inställningen och passera ett attribut som du själv väljer. Du måste se till att attributet som du väljer kan hämtas för både användare och grupper och att det är unikt. Lämna det tomt för standard beteende. Förändringar kommer endast att påverka nyligen mappade (tillagda) LDAP-användare och grupper.", +"UUID Attribute:" => "UUID Attribut:", +"Username-LDAP User Mapping" => "Användarnamn-LDAP User Mapping", +"ownCloud uses usernames to store and assign (meta) data. In order to precisely identify and recognize users, each LDAP user will have a internal username. This requires a mapping from ownCloud username to LDAP user. The created username is mapped to the UUID of the LDAP user. Additionally the DN is cached as well to reduce LDAP interaction, but it is not used for identification. If the DN changes, the changes will be found by ownCloud. The internal ownCloud name is used all over in ownCloud. Clearing the Mappings will have leftovers everywhere. Clearing the Mappings is not configuration sensitive, it affects all LDAP configurations! Do never clear the mappings in a production environment. Only clear mappings in a testing or experimental stage." => "ownCloud använder sig av användarnamn för att lagra och tilldela (meta) data. För att exakt kunna identifiera och känna igen användare, kommer varje LDAP-användare ha ett internt användarnamn. Detta kräver en mappning från ownCloud-användarnamn till LDAP-användare. Det skapade användarnamnet mappas till UUID för LDAP-användaren. Dessutom cachas DN samt minska LDAP-interaktionen, men den används inte för identifiering. Om DN förändras, kommer förändringarna hittas av ownCloud. Det interna ownCloud-namnet används överallt i ownCloud. Om du rensar/raderar mappningarna kommer att lämna referenser överallt i systemet. Men den är inte konfigurationskänslig, den påverkar alla LDAP-konfigurationer! Rensa/radera aldrig mappningarna i en produktionsmiljö. Utan gör detta endast på i testmiljö!", +"Clear Username-LDAP User Mapping" => "Rensa Användarnamn-LDAP User Mapping", +"Clear Groupname-LDAP Group Mapping" => "Rensa Gruppnamn-LDAP Group Mapping", "Test Configuration" => "Testa konfigurationen", "Help" => "Hjälp" ); diff --git a/apps/user_ldap/l10n/tr.php b/apps/user_ldap/l10n/tr.php index c001fa99ce..3835c72313 100644 --- a/apps/user_ldap/l10n/tr.php +++ b/apps/user_ldap/l10n/tr.php @@ -1,16 +1,16 @@ "Sunucu uyunlama basarmadi ", -"The configuration is valid and the connection could be established!" => "Uyunlama mantikli ve baglama yerlestirmek edebilmi.", -"The configuration is valid, but the Bind failed. Please check the server settings and credentials." => "Uyunlama gecerli, fakat Baglama yapamadi. Lutfen kontrol yapmak, eger bu iyi yerlertirdi. ", -"The configuration is invalid. Please look in the ownCloud log for further details." => "Uyunma mantikli degil. Lutfen log daha kontrol yapmak. ", +"Failed to delete the server configuration" => "Sunucu yapılandırmasını silme başarısız oldu", +"The configuration is valid and the connection could be established!" => "Yapılandırma geçerli ve bağlantı kuruldu!", +"The configuration is valid, but the Bind failed. Please check the server settings and credentials." => "Yapılandırma geçerli fakat bağlanma(bind) başarısız. Lütfen Sunucu ayarları ve kimlik bilgilerini kontrol ediniz.", +"The configuration is invalid. Please look in the ownCloud log for further details." => "Yapılandırma geçersiz. Daha fazla detay için lütfen ownCloud günlüklerine bakınız.", "Deletion failed" => "Silme başarısız oldu", -"Take over settings from recent server configuration?" => "Parametri sonadan uyunlama cikarmak mi?", -"Keep settings?" => "Ayarları kalsınmı?", -"Cannot add server configuration" => "Sunucu uyunlama birlemek edemen. ", +"Take over settings from recent server configuration?" => "Ayarları son sunucu yapılandırmalarından devral?", +"Keep settings?" => "Ayarlar kalsın mı?", +"Cannot add server configuration" => "Sunucu yapılandırması eklenemedi", "Error" => "Hata", "Connection test succeeded" => "Bağlantı testi başarılı oldu", "Connection test failed" => "Bağlantı testi başarısız oldu", -"Do you really want to delete the current Server Configuration?" => "Hakikatten, Sonuncu Funksyon durmak istiyor mi?", +"Do you really want to delete the current Server Configuration?" => "Şu anki sunucu yapılandırmasını silmek istediğinizden emin misiniz?", "Confirm Deletion" => "Silmeyi onayla", "Warning: Apps user_ldap and user_webdavauth are incompatible. You may experience unexpected behaviour. Please ask your system administrator to disable one of them." => "Uyari Apps kullanici_Idap ve user_webdavauth uyunmayan. Bu belki sik degil. Lutfen sistem yonetici sormak on aktif yapmaya. ", "Warning: The PHP LDAP module is not installed, the backend will not work. Please ask your system administrator to install it." => "Ihbar Modulu PHP LDAP yuklemdi degil, backend calismacak. Lutfen sistem yonetici sormak yuklemek icin.", diff --git a/apps/user_ldap/l10n/zh_CN.php b/apps/user_ldap/l10n/zh_CN.php index 7b8389227a..675fe4dfc7 100644 --- a/apps/user_ldap/l10n/zh_CN.php +++ b/apps/user_ldap/l10n/zh_CN.php @@ -1,4 +1,5 @@ "清除映射失败。", "Failed to delete the server configuration" => "未能删除服务器配置", "The configuration is valid and the connection could be established!" => "配置有效,能够建立连接!", "The configuration is valid, but the Bind failed. Please check the server settings and credentials." => "配置有效但绑定失败。请检查服务器设置和认证信息。", @@ -7,6 +8,7 @@ "Take over settings from recent server configuration?" => "从近期的服务器配置中导入设置?", "Keep settings?" => "保留设置吗?", "Cannot add server configuration" => "无法添加服务器配置", +"mappings cleared" => "清除映射", "Success" => "成功", "Error" => "错误", "Connection test succeeded" => "连接测试成功", @@ -72,6 +74,16 @@ "Email Field" => "电邮字段", "User Home Folder Naming Rule" => "用户主目录命名规则", "Leave empty for user name (default). Otherwise, specify an LDAP/AD attribute." => "将用户名称留空(默认)。否则指定一个LDAP/AD属性", +"Internal Username" => "内部用户名", +"By default the internal username will be created from the UUID attribute. It makes sure that the username is unique and characters do not need to be converted. The internal username has the restriction that only these characters are allowed: [ a-zA-Z0-9_.@- ]. Other characters are replaced with their ASCII correspondence or simply omitted. On collisions a number will be added/increased. The internal username is used to identify a user internally. It is also the default name for the user home folder in ownCloud. It is also a port of remote URLs, for instance for all *DAV services. With this setting, the default behaviour can be overriden. To achieve a similar behaviour as before ownCloud 5 enter the user display name attribute in the following field. Leave it empty for default behaviour. Changes will have effect only on newly mapped (added) LDAP users." => "默认情况下内部用户名具有唯一识别属性来确保用户名的唯一性和属性不用转换。内部用户名有严格的字符限制,只允许使用 [ a-zA-Z0-9_.@- ]。其他字符会被ASCII码取代或者被活力。当冲突时会增加或者减少一个数字。内部用户名被用于内部识别用户,同时也作为ownCloud中用户根文件夹的默认名。也作为远程URLs的一部分,比如为了所有的*DAV服务。在这种设置下,默认行为可以被超越。实现一个类似的行为,owncloud 5输入用户的显示名称属性在以下领域之前。让它空着的默认行为。更改只对新映射的影响(增加)的LDAP用户。", +"Internal Username Attribute:" => "内部用户名属性:", +"Override UUID detection" => "超越UUID检测", +"By default, ownCloud autodetects the UUID attribute. The UUID attribute is used to doubtlessly identify LDAP users and groups. Also, the internal username will be created based on the UUID, if not specified otherwise above. You can override the setting and pass an attribute of your choice. You must make sure that the attribute of your choice can be fetched for both users and groups and it is unique. Leave it empty for default behaviour. Changes will have effect only on newly mapped (added) LDAP users and groups." => "默认ownCloud自动检测UUID属性。UUID属性用来无误的识别LDAP用户和组。同时内部用户名也基于UUID创建,如果没有上述的指定。也可以超越设置直接指定一种属性。但一定要确保指定的属性取得的用户和组是唯一的。默认行为空。变更基于新映射(增加)LDAP用户和组才会生效。", +"UUID Attribute:" => "UUID属性:", +"Username-LDAP User Mapping" => "用户名-LDAP用户映射", +"ownCloud uses usernames to store and assign (meta) data. In order to precisely identify and recognize users, each LDAP user will have a internal username. This requires a mapping from ownCloud username to LDAP user. The created username is mapped to the UUID of the LDAP user. Additionally the DN is cached as well to reduce LDAP interaction, but it is not used for identification. If the DN changes, the changes will be found by ownCloud. The internal ownCloud name is used all over in ownCloud. Clearing the Mappings will have leftovers everywhere. Clearing the Mappings is not configuration sensitive, it affects all LDAP configurations! Do never clear the mappings in a production environment. Only clear mappings in a testing or experimental stage." => "ownCloud使用用户名存储和分配数据(元)。为了准确地识别和确认用户,每个用户都有一个内部用户名。需要从ownCloud用户名映射到LDAP用户。创建的用户名映射到LDAP用户的UUID。此外,DN是缓存以及减少LDAP交互,但它不用于识别。如果DN变化,ownCloud也会变化。内部ownCloud名在ownCloud的各处使用。清除映射将一片混乱。清除映射不是常用的配置,它影响到所有LDAP配置!千万不要在正式环境中清除映射。只有在测试或试验阶段可以清除映射。", +"Clear Username-LDAP User Mapping" => "清除用户-LDAP用户映射", +"Clear Groupname-LDAP Group Mapping" => "清除组用户-LDAP级映射", "Test Configuration" => "测试配置", "Help" => "帮助" ); diff --git a/apps/user_ldap/lib/access.php b/apps/user_ldap/lib/access.php index a7611eb3e8..04f73cf01f 100644 --- a/apps/user_ldap/lib/access.php +++ b/apps/user_ldap/lib/access.php @@ -441,8 +441,8 @@ abstract class Access { //while loop is just a precaution. If a name is not generated within //20 attempts, something else is very wrong. Avoids infinite loop. while($attempts < 20){ - $altName = $name . '_' . uniqid(); - if(\OCP\User::userExists($altName)) { + $altName = $name . '_' . rand(1000,9999); + if(!\OCP\User::userExists($altName)) { return $altName; } $attempts++; diff --git a/apps/user_ldap/lib/connection.php b/apps/user_ldap/lib/connection.php index ba4de13534..31150a5bec 100644 --- a/apps/user_ldap/lib/connection.php +++ b/apps/user_ldap/lib/connection.php @@ -601,14 +601,13 @@ class Connection { $error = null; } - $error = null; //if LDAP server is not reachable, try the Backup (Replica!) Server - if((!$bindStatus && ($error === -1)) + if((!$bindStatus && ($error !== 0)) || $this->config['ldapOverrideMainServer'] || $this->getFromCache('overrideMainServer')) { $this->doConnect($this->config['ldapBackupHost'], $this->config['ldapBackupPort']); $bindStatus = $this->bind(); - if($bindStatus && $error === -1) { + if(!$bindStatus && $error === -1) { //when bind to backup server succeeded and failed to main server, //skip contacting him until next cache refresh $this->writeToCache('overrideMainServer', true); @@ -622,6 +621,10 @@ class Connection { if(empty($host)) { return false; } + if(strpos($host, '://') !== false) { + //ldap_connect ignores port paramater when URLs are passed + $host .= ':' . $port; + } $this->ldapConnectionRes = ldap_connect($host, $port); if(ldap_set_option($this->ldapConnectionRes, LDAP_OPT_PROTOCOL_VERSION, 3)) { if(ldap_set_option($this->ldapConnectionRes, LDAP_OPT_REFERRALS, 0)) { @@ -636,10 +639,17 @@ class Connection { * Binds to LDAP */ public function bind() { + static $getConnectionResourceAttempt = false; if(!$this->config['ldapConfigurationActive']) { return false; } + if($getConnectionResourceAttempt) { + $getConnectionResourceAttempt = false; + return false; + } + $getConnectionResourceAttempt = true; $cr = $this->getConnectionResource(); + $getConnectionResourceAttempt = false; if(!is_resource($cr)) { return false; } diff --git a/apps/user_ldap/lib/helper.php b/apps/user_ldap/lib/helper.php index 07d13a806a..10ed40ebd6 100644 --- a/apps/user_ldap/lib/helper.php +++ b/apps/user_ldap/lib/helper.php @@ -118,7 +118,13 @@ class Helper { return false; } - $query = \OCP\DB::prepare('TRUNCATE '.$table); + if(strpos(\OCP\Config::getSystemValue('dbtype'), 'sqlite') !== false) { + $query = \OCP\DB::prepare('DELETE FROM '.$table); + } else { + $query = \OCP\DB::prepare('TRUNCATE '.$table); + } + + $res = $query->execute(); if(\OCP\DB::isError($res)) { diff --git a/apps/user_ldap/lib/jobs.php b/apps/user_ldap/lib/jobs.php index 094d11db3d..60ecc0da33 100644 --- a/apps/user_ldap/lib/jobs.php +++ b/apps/user_ldap/lib/jobs.php @@ -23,20 +23,22 @@ namespace OCA\user_ldap\lib; -class Jobs { +class Jobs extends \OC\BackgroundJob\TimedJob { static private $groupsFromDB; static private $groupBE; static private $connector; + public function __construct(){ + $this->interval = self::getRefreshInterval(); + } + + public function run($argument){ + Jobs::updateGroups(); + } + static public function updateGroups() { \OCP\Util::writeLog('user_ldap', 'Run background job "updateGroups"', \OCP\Util::DEBUG); - $lastUpdate = \OCP\Config::getAppValue('user_ldap', 'bgjUpdateGroupsLastRun', 0); - if((time() - $lastUpdate) < self::getRefreshInterval()) { - \OCP\Util::writeLog('user_ldap', 'bgJ "updateGroups" – last run too fresh, aborting.', \OCP\Util::DEBUG); - //komm runter Werner die Maurer geben ein aus - return; - } $knownGroups = array_keys(self::getKnownGroups()); $actualGroups = self::getGroupBE()->getGroups(); @@ -45,7 +47,6 @@ class Jobs { \OCP\Util::writeLog('user_ldap', 'bgJ "updateGroups" – groups do not seem to be configured properly, aborting.', \OCP\Util::INFO); - \OCP\Config::setAppValue('user_ldap', 'bgjUpdateGroupsLastRun', time()); return; } @@ -53,8 +54,6 @@ class Jobs { self::handleCreatedGroups(array_diff($actualGroups, $knownGroups)); self::handleRemovedGroups(array_diff($knownGroups, $actualGroups)); - \OCP\Config::setAppValue('user_ldap', 'bgjUpdateGroupsLastRun', time()); - \OCP\Util::writeLog('user_ldap', 'bgJ "updateGroups" – Finished.', \OCP\Util::DEBUG); } diff --git a/apps/user_webdavauth/l10n/he.php b/apps/user_webdavauth/l10n/he.php new file mode 100644 index 0000000000..9ab6b83bc7 --- /dev/null +++ b/apps/user_webdavauth/l10n/he.php @@ -0,0 +1,5 @@ + "הזדהות מול WebDAV", +"URL: http://" => "כתובת: http://‎", +"ownCloud will send the user credentials to this URL. This plugin checks the response and will interpret the HTTP statuscodes 401 and 403 as invalid credentials, and all other responses as valid credentials." => "מערכת ownCloud תשלח את פרטי המשתמש לכתובת זו. התוסף יבדוק את התגובה ויתרגם את הקודים 401 ו־403 כתגובה לציון פרטי גישה שגויים ואת כל שאר התגובות כפרטי גישה נכונים." +); diff --git a/apps/user_webdavauth/l10n/is.php b/apps/user_webdavauth/l10n/is.php index 8fe0d974b3..bb59ef5e44 100644 --- a/apps/user_webdavauth/l10n/is.php +++ b/apps/user_webdavauth/l10n/is.php @@ -1,3 +1,4 @@ "WebDAV Auðkenni", "URL: http://" => "Vefslóð: http://" ); diff --git a/apps/user_webdavauth/l10n/nn_NO.php b/apps/user_webdavauth/l10n/nn_NO.php new file mode 100644 index 0000000000..772e084b63 --- /dev/null +++ b/apps/user_webdavauth/l10n/nn_NO.php @@ -0,0 +1,5 @@ + "WebDAV-autentisering", +"URL: http://" => "Nettadresse: http://", +"ownCloud will send the user credentials to this URL. This plugin checks the response and will interpret the HTTP statuscodes 401 and 403 as invalid credentials, and all other responses as valid credentials." => "ownCloud sender brukarakkreditiv til denne nettadressa. Dette programtillegget kontrollerer svaret og tolkar HTTP-statuskodane 401 og 403 som ugyldige, og alle andre svar som gyldige." +); diff --git a/autotest.sh b/autotest.sh index 267815e96d..4562b3ed08 100755 --- a/autotest.sh +++ b/autotest.sh @@ -54,6 +54,22 @@ cat > ./tests/autoconfig-pgsql.php < ./tests/autoconfig-oci.php < false, + 'dbtype' => 'oci', + 'dbtableprefix' => 'oc_', + 'adminlogin' => 'admin', + 'adminpass' => 'admin', + 'directory' => '$BASEDIR/$DATADIR', + 'dbuser' => 'oc_autotest', + 'dbname' => 'XE', + 'dbhost' => 'localhost', + 'dbpass' => 'owncloud', +); +DELIM + function execute_tests { echo "Setup environment for $1 testing ..." # back to root folder @@ -77,6 +93,30 @@ function execute_tests { if [ "$1" == "pgsql" ] ; then dropdb -U oc_autotest oc_autotest fi + if [ "$1" == "oci" ] ; then + echo "drop the database" + sqlplus -s -l / as sysdba <li { - overflow:hidden; - white-space:nowrap; - } +ul.multiselectoptions>li { + overflow: hidden; + white-space: nowrap; +} - ul.multiselectoptions>li>input[type="checkbox"] { - margin-top: 3px; - margin-right: 5px; - margin-left: 3px; - } +ul.multiselectoptions > li > input[type="checkbox"] { + margin: 10px 7px; + vertical-align: middle; +} +ul.multiselectoptions > li input[type='checkbox']+label { + font-weight: normal; + display: inline-block; + width: 100%; + padding: 5px 27px; + margin-left: -27px; /* to have area around checkbox clickable as well */ +} +ul.multiselectoptions > li input[type='checkbox']:checked+label { + font-weight: bold; +} - div.multiselect { - display:inline-block; - max-width:400px; - min-width:100px; - padding-right:.6em; - position:relative; - vertical-align:bottom; - } +div.multiselect { + display: inline-block; + max-width: 400px; + min-width: 150px; + padding-right: .6em; + position: relative; + vertical-align: bottom; +} - div.multiselect.active { - background-color:#fff; - position:relative; - z-index:50; - } +div.multiselect.active { + background-color: #fff; + position: relative; + z-index: 50; +} - div.multiselect.up { - border-top:0 none; - border-top-left-radius:0; - border-top-right-radius:0; - } +div.multiselect.up { + border-top: 0 none; + border-top-left-radius: 0; + border-top-right-radius: 0; +} - div.multiselect.down { - border-bottom:none; - border-bottom-left-radius:0; - border-bottom-right-radius:0; - } +div.multiselect.down { + border-bottom: none; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} - div.multiselect>span:first-child { - float:left; - margin-right:2em; - overflow:hidden; - text-overflow:ellipsis; - width:90%; - } +div.multiselect>span:first-child { + float: left; + margin-right: 2em; + overflow: hidden; + text-overflow: ellipsis; + width: 90%; +} - div.multiselect>span:last-child { - position:absolute; - right:.8em; - } +div.multiselect>span:last-child { + position: absolute; + right: .8em; +} - ul.multiselectoptions input.new { - border-top-left-radius:0; - border-top-right-radius:0; - padding-bottom:.2em; - padding-top:.2em; - margin:0; - } +ul.multiselectoptions input.new { + padding-bottom: .2em; + padding-top: .2em; + margin: 0; +} + +ul.multiselectoptions > li.creator { + padding: 10px; + font-weight: bold; +} +ul.multiselectoptions > li.creator > input { + width: 95% !important; /* do not constrain size of text input */ + padding: 5px; + margin: -5px; +} diff --git a/core/css/styles.css b/core/css/styles.css index 93f2cecbfe..58e750da4f 100644 --- a/core/css/styles.css +++ b/core/css/styles.css @@ -37,7 +37,7 @@ filter:progid:DXImageTransform.Microsoft.gradient( startColorstr='#35537a', endC /* INPUTS */ -input[type="text"], input[type="password"], input[type="search"], input[type="number"], input[type="email"], +input[type="text"], input[type="password"], input[type="search"], input[type="number"], input[type="email"], input[type="url"], textarea, select, button, .button, #quota, div.jp-progress, .pager li a { @@ -48,11 +48,11 @@ button, .button, -moz-border-radius:.5em; -webkit-border-radius:.5em; border-radius:.5em; } input[type="hidden"] { height:0; width:0; } -input[type="text"], input[type="password"], input[type="search"], input[type="number"], input[type="email"], textarea { +input[type="text"], input[type="password"], input[type="search"], input[type="number"], input[type="email"], input[type="url"], textarea { background:#f8f8f8; color:#555; cursor:text; font-family: inherit; /* use default ownCloud font instead of default textarea monospace */ } -input[type="text"], input[type="password"], input[type="search"], input[type="number"], input[type="email"] { +input[type="text"], input[type="password"], input[type="search"], input[type="number"], input[type="email"], input[type="url"] { -webkit-appearance:textfield; -moz-appearance:textfield; -webkit-box-sizing:content-box; -moz-box-sizing:content-box; box-sizing:content-box; } @@ -61,6 +61,7 @@ input[type="password"]:hover, input[type="password"]:focus, input[type="password input[type="number"]:hover, input[type="number"]:focus, input[type="number"]:active, .searchbox input[type="search"]:hover, .searchbox input[type="search"]:focus, .searchbox input[type="search"]:active, input[type="email"]:hover, input[type="email"]:focus, input[type="email"]:active, +input[type="url"]:hover, input[type="url"]:focus, input[type="url"]:active, textarea:hover, textarea:focus, textarea:active { background-color:#fff; color:#333; -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; filter:alpha(opacity=100); opacity:1; @@ -140,7 +141,7 @@ input[type="submit"].enabled { background:#66f866; border:1px solid #5e5; -moz-b padding:0 70px 0 0.5em; margin:0; -moz-box-sizing:border-box; box-sizing:border-box; -moz-box-shadow:0 -3px 7px #000; -webkit-box-shadow:0 -3px 7px #000; box-shadow:0 -3px 7px #000; - background:#f7f7f7; border-bottom:1px solid #eee; z-index:50; + background:#eee; border-bottom:1px solid #e7e7e7; z-index:50; } #controls .button { display:inline-block; } @@ -354,14 +355,27 @@ tr .action { width:16px; height:16px; } tr:hover .action:hover, .selectedActions a:hover, .header-action:hover { -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; filter:alpha(opacity=100); opacity:1; } tbody tr:hover, tr:active { background-color:#f8f8f8; } -#body-settings .personalblock, #body-settings .helpblock { padding:.5em 1em; margin:1em; background:#f8f8f8; color:#555; text-shadow:#fff 0 1px 0; -moz-border-radius:.5em; -webkit-border-radius:.5em; border-radius:.5em; } +#body-settings .personalblock, #body-settings .helpblock { + padding: .5em 1em; + margin: 1em; + background-color: rgb(240,240,240); + color: #555; + text-shadow: #fff 0 1px 0; + -moz-border-radius: .5em; -webkit-border-radius: .5em; border-radius: .5em; +} #body-settings .personalblock#quota { position:relative; padding:0; } #body-settings #controls+.helpblock { position:relative; margin-top:3em; } .personalblock > legend { margin-top:2em; } .personalblock > legend, th, dt, label { font-weight:bold; } code { font-family:"Lucida Console", "Lucida Sans Typewriter", "DejaVu Sans Mono", monospace; } -#quota div, div.jp-play-bar, div.jp-seek-bar { padding:0; background:#e6e6e6; font-weight:normal; white-space:nowrap; -moz-border-radius-bottomleft:.4em; -webkit-border-bottom-left-radius:.4em; border-bottom-left-radius:.4em; -moz-border-radius-topleft:.4em; -webkit-border-top-left-radius:.4em; border-top-left-radius:.4em; } +#quota div, div.jp-play-bar, div.jp-seek-bar { + padding: 0; + background-color: rgb(220,220,220); + font-weight: normal; + white-space: nowrap; + -moz-border-radius-bottomleft: .4em; -webkit-border-bottom-left-radius: .4em; border-bottom-left-radius:.4em; + -moz-border-radius-topleft: .4em; -webkit-border-top-left-radius: .4em; border-top-left-radius: .4em; } #quotatext {padding:.6em 1em;} div.jp-play-bar, div.jp-seek-bar { padding:0; } @@ -382,13 +396,27 @@ a.bookmarklet { background-color:#ddd; border:1px solid #ccc; padding:5px;paddin .ui-datepicker-prev,.ui-datepicker-next{ border:1px solid #ddd; background:#fff; } /* ---- DIALOGS ---- */ -#dirup {width:4%;} -#dirtree {width:92%;} -#filelist {height:270px; overflow-y:auto; background-color:white; width:100%;} -.filepicker_element_selected { background-color:lightblue;} -.filepicker_loader {height:170px; width:100%; background-color:#333; -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=30)"; filter:alpha(opacity=30); opacity:.3; visibility:visible; position:absolute; top:0; left:0; text-align:center; padding-top:150px;} +#oc-dialog-filepicker-content .dirtree {width:92%; overflow:hidden; } +#oc-dialog-filepicker-content .dirtree .home { + background-image:url('../img/places/home.svg'); + background-repeat:no-repeat; + background-position: left center; +} +#oc-dialog-filepicker-content .dirtree span:not(:last-child) { cursor: pointer; } +#oc-dialog-filepicker-content .dirtree span:last-child { font-weight: bold; } +#oc-dialog-filepicker-content .dirtree span:not(:last-child)::after { content: '>'; padding: 3px;} +#oc-dialog-filepicker-content .filelist { + overflow-y:auto; + max-height: 300px; + background-color:white; + width:100%; +} +#oc-dialog-filepicker-content .filelist img { margin: 2px 1em 0 4px; } +#oc-dialog-filepicker-content .filelist .date { float:right;margin-right:1em; } +#oc-dialog-filepicker-content .filepicker_element_selected { background-color:lightblue;} .ui-dialog {position:fixed !important;} span.ui-icon {float: left; margin: 3px 7px 30px 0;} +.loading { background: url('../img/loading.gif') no-repeat center; cursor: wait; } /* ---- CATEGORIES ---- */ #categoryform .scrollarea { position:absolute; left:10px; top:10px; right:10px; bottom:50px; overflow:auto; border:1px solid #ddd; background:#f8f8f8; } @@ -412,12 +440,22 @@ span.ui-icon {float: left; margin: 3px 7px 30px 0;} .help-includes {overflow: hidden; width: 100%; height: 100%; -moz-box-sizing: border-box; box-sizing: border-box; padding-top: 2.8em; } .help-iframe {width: 100%; height: 100%; margin: 0;padding: 0; border: 0; overflow: auto;} + /* ---- BREADCRUMB ---- */ div.crumb { float:left; display:block; background:url('../img/breadcrumb.svg') no-repeat right 0; padding:.75em 1.5em 0 1em; height:2.9em; -moz-box-sizing:border-box; box-sizing:border-box; } div.crumb:first-child { padding:10px 20px 10px 5px; } div.crumb.last { font-weight:bold; background:none; padding-right:10px; } div.crumb a{ padding: 0.9em 0 0.7em 0; } +/* some feedback for hover/tap on breadcrumbs */ +div.crumb:hover, +div.crumb:focus, +div.crumb:active { + -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=70)"; + filter:alpha(opacity=70); + opacity:.7; +} + /* ---- APP STYLING ---- */ diff --git a/core/img/actions/add.svg b/core/img/actions/add.svg index 29994747c3..136d6c4b31 100644 --- a/core/img/actions/add.svg +++ b/core/img/actions/add.svg @@ -1,109 +1,10 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - + + + + + + + + diff --git a/core/img/actions/caret-dark.png b/core/img/actions/caret-dark.png index ce7e1e6980..8ac5fbbd19 100644 Binary files a/core/img/actions/caret-dark.png and b/core/img/actions/caret-dark.png differ diff --git a/core/img/actions/caret-dark.svg b/core/img/actions/caret-dark.svg index abb1dc192d..be45ad402b 100644 --- a/core/img/actions/caret-dark.svg +++ b/core/img/actions/caret-dark.svg @@ -1,102 +1,5 @@ - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - + + + diff --git a/core/img/actions/caret.png b/core/img/actions/caret.png index e0ae969a94..00baea9ece 100644 Binary files a/core/img/actions/caret.png and b/core/img/actions/caret.png differ diff --git a/core/img/actions/caret.svg b/core/img/actions/caret.svg index 7bb0c59cde..d1ae8d60a6 100644 --- a/core/img/actions/caret.svg +++ b/core/img/actions/caret.svg @@ -1,112 +1,11 @@ - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - + + + + + + + + + diff --git a/core/img/actions/clock.png b/core/img/actions/clock.png index 671b3f4f0c..9c3a284b8b 100644 Binary files a/core/img/actions/clock.png and b/core/img/actions/clock.png differ diff --git a/core/img/actions/clock.svg b/core/img/actions/clock.svg old mode 100755 new mode 100644 index 1821f474df..f3fcb19031 --- a/core/img/actions/clock.svg +++ b/core/img/actions/clock.svg @@ -1,20 +1,21 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + diff --git a/core/img/actions/close.png b/core/img/actions/close.png index bc0c782882..0d8c89a56e 100644 Binary files a/core/img/actions/close.png and b/core/img/actions/close.png differ diff --git a/core/img/actions/close.svg b/core/img/actions/close.svg index 6a6d98e34a..ef564bfd48 100644 --- a/core/img/actions/close.svg +++ b/core/img/actions/close.svg @@ -1,73 +1,6 @@ - - - - - - - - - - - image/svg+xml - - - - - - - - + + + + diff --git a/core/img/actions/delete-hover.png b/core/img/actions/delete-hover.png index 08b15510d9..048d91cee5 100644 Binary files a/core/img/actions/delete-hover.png and b/core/img/actions/delete-hover.png differ diff --git a/core/img/actions/delete-hover.svg b/core/img/actions/delete-hover.svg index 63cacd5e38..3e8d26c978 100644 --- a/core/img/actions/delete-hover.svg +++ b/core/img/actions/delete-hover.svg @@ -1,73 +1,6 @@ - - - - - - - - - - - image/svg+xml - - - - - - - - + + + + diff --git a/core/img/actions/delete.svg b/core/img/actions/delete.svg index 86c8317d01..ef564bfd48 100644 --- a/core/img/actions/delete.svg +++ b/core/img/actions/delete.svg @@ -1,70 +1,6 @@ - - - - - - - - - - - image/svg+xml - - - - - - - - + + + + diff --git a/core/img/actions/download.svg b/core/img/actions/download.svg index 107a46f07b..a469c3b8a0 100644 --- a/core/img/actions/download.svg +++ b/core/img/actions/download.svg @@ -1,73 +1,6 @@ - - - - - - - - - - - image/svg+xml - - - - - - - - + + + + diff --git a/core/img/actions/history.png b/core/img/actions/history.png index 1d138b8cd5..3234880b25 100644 Binary files a/core/img/actions/history.png and b/core/img/actions/history.png differ diff --git a/core/img/actions/history.svg b/core/img/actions/history.svg index 9c2838d565..94512a2d43 100644 --- a/core/img/actions/history.svg +++ b/core/img/actions/history.svg @@ -1,240 +1,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - + + + + diff --git a/core/img/actions/info.svg b/core/img/actions/info.svg index 1e07aed852..55bdb17f2e 100644 --- a/core/img/actions/info.svg +++ b/core/img/actions/info.svg @@ -1,1758 +1,14 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/core/img/actions/lock.png b/core/img/actions/lock.png index 511bfa615b..dbcffa3990 100644 Binary files a/core/img/actions/lock.png and b/core/img/actions/lock.png differ diff --git a/core/img/actions/lock.svg b/core/img/actions/lock.svg old mode 100755 new mode 100644 index 8fb039b9e3..beef1d3ad3 --- a/core/img/actions/lock.svg +++ b/core/img/actions/lock.svg @@ -1,8 +1,5 @@ - - - - - - + + + + + diff --git a/core/img/actions/logout.png b/core/img/actions/logout.png index e2f4b7af12..e9c89a15a7 100644 Binary files a/core/img/actions/logout.png and b/core/img/actions/logout.png differ diff --git a/core/img/actions/logout.svg b/core/img/actions/logout.svg index e5edc24895..59543875d7 100644 --- a/core/img/actions/logout.svg +++ b/core/img/actions/logout.svg @@ -1,178 +1,5 @@ - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - + + + diff --git a/core/img/actions/mail.svg b/core/img/actions/mail.svg index e82fa3b467..2c63daac03 100644 --- a/core/img/actions/mail.svg +++ b/core/img/actions/mail.svg @@ -1,58 +1,8 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - + + + + + + diff --git a/core/img/actions/password.png b/core/img/actions/password.png index 5167161dfa..edcafdd9bb 100644 Binary files a/core/img/actions/password.png and b/core/img/actions/password.png differ diff --git a/core/img/actions/password.svg b/core/img/actions/password.svg index ee6a9fe018..9ab5d4243d 100644 --- a/core/img/actions/password.svg +++ b/core/img/actions/password.svg @@ -1,2148 +1,5 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + diff --git a/core/img/actions/pause-big.svg b/core/img/actions/pause-big.svg index b521057a35..9c4944223f 100644 --- a/core/img/actions/pause-big.svg +++ b/core/img/actions/pause-big.svg @@ -1,73 +1,6 @@ - - - - - - - - - - - image/svg+xml - - - - - - - - + + + + diff --git a/core/img/actions/pause.svg b/core/img/actions/pause.svg index ff3c69d6c7..d572ad6f5c 100644 --- a/core/img/actions/pause.svg +++ b/core/img/actions/pause.svg @@ -1,72 +1,6 @@ - - - - - - - - - - - image/svg+xml - - - - - - - - + + + + diff --git a/core/img/actions/play-add.svg b/core/img/actions/play-add.svg index 25ff0b57ee..cdf4f6ea9f 100644 --- a/core/img/actions/play-add.svg +++ b/core/img/actions/play-add.svg @@ -1,83 +1,9 @@ - - - - - - - - - - - image/svg+xml - - - - - - - - - - + + + + + + diff --git a/core/img/actions/play-big.svg b/core/img/actions/play-big.svg index 2ef6741532..884171ced8 100644 --- a/core/img/actions/play-big.svg +++ b/core/img/actions/play-big.svg @@ -1,73 +1,6 @@ - - - - - - - - - - - image/svg+xml - - - - - - - - + + + + diff --git a/core/img/actions/play-next.svg b/core/img/actions/play-next.svg index 9a41e4bd9d..8b3d7d6eff 100644 --- a/core/img/actions/play-next.svg +++ b/core/img/actions/play-next.svg @@ -1,79 +1,7 @@ - - - - - - - - - - - image/svg+xml - - - - - - - - - + + + + + diff --git a/core/img/actions/play-previous.svg b/core/img/actions/play-previous.svg index 31d45dedb4..6210b088cb 100644 --- a/core/img/actions/play-previous.svg +++ b/core/img/actions/play-previous.svg @@ -1,79 +1,7 @@ - - - - - - - - - - - image/svg+xml - - - - - - - - - + + + + + diff --git a/core/img/actions/play.svg b/core/img/actions/play.svg index 7bb7b5c262..ae23e6a0d2 100644 --- a/core/img/actions/play.svg +++ b/core/img/actions/play.svg @@ -1,73 +1,6 @@ - - - - - - - - - - - image/svg+xml - - - - - - - - + + + + diff --git a/core/img/actions/public.svg b/core/img/actions/public.svg index b47305fbd0..c70a762778 100644 --- a/core/img/actions/public.svg +++ b/core/img/actions/public.svg @@ -1,292 +1,4 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + diff --git a/core/img/actions/rename.svg b/core/img/actions/rename.svg index 44b464c850..d6779709d9 100644 --- a/core/img/actions/rename.svg +++ b/core/img/actions/rename.svg @@ -1,72 +1,6 @@ - - - - - - - - - - - image/svg+xml - - - - - - - - + + + + diff --git a/core/img/actions/search.png b/core/img/actions/search.png index 98e1d73ee3..312e4f419e 100644 Binary files a/core/img/actions/search.png and b/core/img/actions/search.png differ diff --git a/core/img/actions/search.svg b/core/img/actions/search.svg index c8d9d848c4..4f27369dbb 100644 --- a/core/img/actions/search.svg +++ b/core/img/actions/search.svg @@ -1,1632 +1,14 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/core/img/actions/settings.png b/core/img/actions/settings.png index 8b3acb00a4..9ada308770 100644 Binary files a/core/img/actions/settings.png and b/core/img/actions/settings.png differ diff --git a/core/img/actions/settings.svg b/core/img/actions/settings.svg index da685e8be0..bd7ae3b3d7 100644 --- a/core/img/actions/settings.svg +++ b/core/img/actions/settings.svg @@ -1,270 +1,17 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/core/img/actions/share.svg b/core/img/actions/share.svg index a5f2f8cb4d..d67d35c6e5 100644 --- a/core/img/actions/share.svg +++ b/core/img/actions/share.svg @@ -1,70 +1,6 @@ - - - - - - - - - - - image/svg+xml - - - - - - - - + + + + diff --git a/core/img/actions/shared.svg b/core/img/actions/shared.svg index 2302cc9891..3e63cc5468 100644 --- a/core/img/actions/shared.svg +++ b/core/img/actions/shared.svg @@ -1,1738 +1,5 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + diff --git a/core/img/actions/sound-off.svg b/core/img/actions/sound-off.svg index 053291311f..701d7a1a64 100644 --- a/core/img/actions/sound-off.svg +++ b/core/img/actions/sound-off.svg @@ -1,73 +1,6 @@ - - - - - - - - - - - image/svg+xml - - - - - - - - + + + + diff --git a/core/img/actions/sound.svg b/core/img/actions/sound.svg index 6feea076a4..ecadf7dae9 100644 --- a/core/img/actions/sound.svg +++ b/core/img/actions/sound.svg @@ -1,78 +1,7 @@ - - - - - - - - - - - image/svg+xml - - - - - - - - - + + + + + diff --git a/core/img/actions/toggle.png b/core/img/actions/toggle.png index 6ef3f2227b..d06e5cb32b 100644 Binary files a/core/img/actions/toggle.png and b/core/img/actions/toggle.png differ diff --git a/core/img/actions/toggle.svg b/core/img/actions/toggle.svg index 82a5171477..1b774a19b1 100644 --- a/core/img/actions/toggle.svg +++ b/core/img/actions/toggle.svg @@ -1,61 +1,5 @@ - - -image/svg+xml - - - - - \ No newline at end of file + + + + diff --git a/core/img/actions/triangle-n.png b/core/img/actions/triangle-n.png index 14825f7011..0ffcf6cbc4 100644 Binary files a/core/img/actions/triangle-n.png and b/core/img/actions/triangle-n.png differ diff --git a/core/img/actions/triangle-n.svg b/core/img/actions/triangle-n.svg index e8d70fa8ce..4f866978f4 100644 --- a/core/img/actions/triangle-n.svg +++ b/core/img/actions/triangle-n.svg @@ -1,88 +1,4 @@ - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - + + diff --git a/core/img/actions/triangle-s.png b/core/img/actions/triangle-s.png index f36faef2b8..0f533b142e 100644 Binary files a/core/img/actions/triangle-s.png and b/core/img/actions/triangle-s.png differ diff --git a/core/img/actions/triangle-s.svg b/core/img/actions/triangle-s.svg index 396c61e01e..b178b20a20 100644 --- a/core/img/actions/triangle-s.svg +++ b/core/img/actions/triangle-s.svg @@ -1,88 +1,4 @@ - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - + + diff --git a/core/img/actions/upload-white.svg b/core/img/actions/upload-white.svg index 32ecd8b82b..9c54cac5e1 100644 --- a/core/img/actions/upload-white.svg +++ b/core/img/actions/upload-white.svg @@ -1,73 +1,6 @@ - - - - - - - - - - - image/svg+xml - - - - - - - - + + + + diff --git a/core/img/actions/upload.svg b/core/img/actions/upload.svg index 718da8f4a5..eae4515c72 100644 --- a/core/img/actions/upload.svg +++ b/core/img/actions/upload.svg @@ -1,73 +1,6 @@ - - - - - - - - - - - image/svg+xml - - - - - - - - + + + + diff --git a/core/img/actions/user.svg b/core/img/actions/user.svg index 6d0dc714ce..aa71957370 100644 --- a/core/img/actions/user.svg +++ b/core/img/actions/user.svg @@ -1,1698 +1,5 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + diff --git a/core/img/actions/view-close.png b/core/img/actions/view-close.png index 80339d7822..330ae09ea7 100644 Binary files a/core/img/actions/view-close.png and b/core/img/actions/view-close.png differ diff --git a/core/img/actions/view-close.svg b/core/img/actions/view-close.svg index 45d6697608..1d5b1a9f49 100644 --- a/core/img/actions/view-close.svg +++ b/core/img/actions/view-close.svg @@ -1,73 +1,6 @@ - - - - - - - - - - - image/svg+xml - - - - - - - - + + + + diff --git a/core/img/actions/view-next.png b/core/img/actions/view-next.png index b76bea0671..f9e6174ae3 100644 Binary files a/core/img/actions/view-next.png and b/core/img/actions/view-next.png differ diff --git a/core/img/actions/view-next.svg b/core/img/actions/view-next.svg index d5642f1a11..07c95b73ff 100644 --- a/core/img/actions/view-next.svg +++ b/core/img/actions/view-next.svg @@ -1,73 +1,6 @@ - - - - - - - - - - - image/svg+xml - - - - - - - - + + + + diff --git a/core/img/actions/view-pause.png b/core/img/actions/view-pause.png index 64264ff928..94696bf686 100644 Binary files a/core/img/actions/view-pause.png and b/core/img/actions/view-pause.png differ diff --git a/core/img/actions/view-pause.svg b/core/img/actions/view-pause.svg index 0edc6f14e2..d901a4d789 100644 --- a/core/img/actions/view-pause.svg +++ b/core/img/actions/view-pause.svg @@ -1,72 +1,6 @@ - - - - - - - - - - - image/svg+xml - - - - - - - - + + + + diff --git a/core/img/actions/view-play.png b/core/img/actions/view-play.png index 0080d45b5c..721787d9c4 100644 Binary files a/core/img/actions/view-play.png and b/core/img/actions/view-play.png differ diff --git a/core/img/actions/view-play.svg b/core/img/actions/view-play.svg index 0bdc63bf7e..d9fa355371 100644 --- a/core/img/actions/view-play.svg +++ b/core/img/actions/view-play.svg @@ -1,73 +1,6 @@ - - - - - - - - - - - image/svg+xml - - - - - - - - + + + + diff --git a/core/img/actions/view-previous.png b/core/img/actions/view-previous.png index 82943c23a5..97b41a195f 100644 Binary files a/core/img/actions/view-previous.png and b/core/img/actions/view-previous.png differ diff --git a/core/img/actions/view-previous.svg b/core/img/actions/view-previous.svg index df1f49511d..68a31c0443 100644 --- a/core/img/actions/view-previous.svg +++ b/core/img/actions/view-previous.svg @@ -1,73 +1,6 @@ - - - - - - - - - - - image/svg+xml - - - - - - - - + + + + diff --git a/core/img/appstore.png b/core/img/appstore.png index 009b2b51b9..234aa0bb6b 100644 Binary files a/core/img/appstore.png and b/core/img/appstore.png differ diff --git a/core/img/breadcrumb-start.svg b/core/img/breadcrumb-start.svg index 4197763dc6..7f36231cdf 100644 --- a/core/img/breadcrumb-start.svg +++ b/core/img/breadcrumb-start.svg @@ -1,71 +1,6 @@ - - - - - - - - - - - image/svg+xml - - - - - - - - + + + + diff --git a/core/img/breadcrumb.svg b/core/img/breadcrumb.svg index 9d522b42b7..05a216e50a 100644 --- a/core/img/breadcrumb.svg +++ b/core/img/breadcrumb.svg @@ -1,77 +1,6 @@ - - - - - - - - - - - image/svg+xml - - - - - - - - + + + + diff --git a/core/img/desktopapp.png b/core/img/desktopapp.png index 182ddd2cf1..25dae6f197 100644 Binary files a/core/img/desktopapp.png and b/core/img/desktopapp.png differ diff --git a/core/img/desktopapp.svg b/core/img/desktopapp.svg index 93d91e461a..a983e6f959 100644 --- a/core/img/desktopapp.svg +++ b/core/img/desktopapp.svg @@ -1,100 +1,5 @@ -image/svg+xml - - -Desktop app -Windows, OS X, Linux - \ No newline at end of file + +Desktop app +Windows, OS X, Linux + diff --git a/core/img/favicon-touch.svg b/core/img/favicon-touch.svg index 6d766d3ced..68f36a8a9a 100644 --- a/core/img/favicon-touch.svg +++ b/core/img/favicon-touch.svg @@ -1,787 +1,4 @@ - - -image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + diff --git a/core/img/favicon.png b/core/img/favicon.png index 79b6795f6f..02936243cb 100644 Binary files a/core/img/favicon.png and b/core/img/favicon.png differ diff --git a/core/img/favicon.svg b/core/img/favicon.svg index f055c32efb..39cb174268 100644 --- a/core/img/favicon.svg +++ b/core/img/favicon.svg @@ -1,796 +1,4 @@ - - -image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + diff --git a/core/img/filetypes/application-x-debian-package.png b/core/img/filetypes/application-x-debian-package.png index b3f6b7e5cf..1d6db5f933 100644 Binary files a/core/img/filetypes/application-x-debian-package.png and b/core/img/filetypes/application-x-debian-package.png differ diff --git a/core/img/googleplay.png b/core/img/googleplay.png index 2d9ad62960..1470518272 100644 Binary files a/core/img/googleplay.png and b/core/img/googleplay.png differ diff --git a/core/img/image-optimization.sh b/core/img/image-optimization.sh new file mode 100755 index 0000000000..0a96bf558d --- /dev/null +++ b/core/img/image-optimization.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +function recursive_optimize_images() { +cd $1; +optipng -o6 *.png; +jpegoptim --strip-all *.jpg; +for svg in `ls *.svg`; +do + mv $svg $svg.opttmp; + scour -i $svg.opttmp -o $svg --create-groups --enable-id-stripping --enable-comment-stripping --shorten-ids --remove-metadata; +done; +rm *.opttmp +for dir in `ls -d */`; +do + recursive_optimize_images $dir; + cd ..; +done; +} + +recursive_optimize_images ../../ diff --git a/core/img/logo-wide.png b/core/img/logo-wide.png index b0c90984e4..5b7d4c6f91 100644 Binary files a/core/img/logo-wide.png and b/core/img/logo-wide.png differ diff --git a/core/img/logo-wide.svg b/core/img/logo-wide.svg index cf8eace520..29c617d6f8 100644 --- a/core/img/logo-wide.svg +++ b/core/img/logo-wide.svg @@ -1,796 +1,3 @@ - - -image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + diff --git a/core/img/logo.png b/core/img/logo.png index a84fe145bb..8d112d99be 100644 Binary files a/core/img/logo.png and b/core/img/logo.png differ diff --git a/core/img/logo.svg b/core/img/logo.svg index bd928cccfa..cfb20b60e4 100644 --- a/core/img/logo.svg +++ b/core/img/logo.svg @@ -1,759 +1,4 @@ - - -image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + diff --git a/core/img/noise.png b/core/img/noise.png index 271dd5ebcf..6c06c8a4d6 100644 Binary files a/core/img/noise.png and b/core/img/noise.png differ diff --git a/core/img/places/calendar-dark.png b/core/img/places/calendar-dark.png index e372104a28..920dee610d 100644 Binary files a/core/img/places/calendar-dark.png and b/core/img/places/calendar-dark.png differ diff --git a/core/img/places/calendar-dark.svg b/core/img/places/calendar-dark.svg index 6f7cb8e74d..986be039ab 100644 --- a/core/img/places/calendar-dark.svg +++ b/core/img/places/calendar-dark.svg @@ -1,75 +1,7 @@ - - - - - - - - - image/svg+xml - - - - - - - - - + + + + + diff --git a/core/img/places/contacts-dark.svg b/core/img/places/contacts-dark.svg index df364911c5..3fc10cfe08 100644 --- a/core/img/places/contacts-dark.svg +++ b/core/img/places/contacts-dark.svg @@ -1,73 +1,7 @@ - - - - - - - - - image/svg+xml - - - - - - - - - + + + + + diff --git a/core/img/places/file.svg b/core/img/places/file.svg index 478714b75d..f93f3ef6fa 100644 --- a/core/img/places/file.svg +++ b/core/img/places/file.svg @@ -1,1841 +1,14 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/core/img/places/files.png b/core/img/places/files.png index 9c7ff2642f..52e0c6bf94 100644 Binary files a/core/img/places/files.png and b/core/img/places/files.png differ diff --git a/core/img/places/files.svg b/core/img/places/files.svg index 8ebf861f6d..d446ef655a 100644 --- a/core/img/places/files.svg +++ b/core/img/places/files.svg @@ -1,128 +1,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - + + + + + diff --git a/core/img/places/folder.svg b/core/img/places/folder.svg index c04b00fedc..676f10afe0 100644 --- a/core/img/places/folder.svg +++ b/core/img/places/folder.svg @@ -1,1830 +1,18 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/core/img/places/home.png b/core/img/places/home.png index 2945b84e86..e664719e2e 100644 Binary files a/core/img/places/home.png and b/core/img/places/home.png differ diff --git a/core/img/places/home.svg b/core/img/places/home.svg index a836a5999f..80b7dcc866 100644 --- a/core/img/places/home.svg +++ b/core/img/places/home.svg @@ -1,1819 +1,11 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + diff --git a/core/img/places/music.svg b/core/img/places/music.svg index e8f91f4616..f7eb391d98 100644 --- a/core/img/places/music.svg +++ b/core/img/places/music.svg @@ -1,73 +1,7 @@ - - - - - - - - - image/svg+xml - - - - - - - - - + + + + + diff --git a/core/img/places/picture.png b/core/img/places/picture.png index a278240a6d..7b3af8c7f8 100644 Binary files a/core/img/places/picture.png and b/core/img/places/picture.png differ diff --git a/core/img/places/picture.svg b/core/img/places/picture.svg index aba68e6206..791cbb5909 100644 --- a/core/img/places/picture.svg +++ b/core/img/places/picture.svg @@ -1,75 +1,7 @@ - - - - - - - - - image/svg+xml - - - - - - - - - + + + + + diff --git a/core/img/rating/s1.png b/core/img/rating/s1.png index 445d965ffe..015f948371 100644 Binary files a/core/img/rating/s1.png and b/core/img/rating/s1.png differ diff --git a/core/img/rating/s10.png b/core/img/rating/s10.png index b8d66c2a4c..b47b05e4f8 100644 Binary files a/core/img/rating/s10.png and b/core/img/rating/s10.png differ diff --git a/core/img/rating/s11.png b/core/img/rating/s11.png index aee9f92156..3dcb4bb483 100644 Binary files a/core/img/rating/s11.png and b/core/img/rating/s11.png differ diff --git a/core/img/rating/s2.png b/core/img/rating/s2.png index 4f860e74ca..94ac5bc956 100644 Binary files a/core/img/rating/s2.png and b/core/img/rating/s2.png differ diff --git a/core/img/rating/s3.png b/core/img/rating/s3.png index 26c9baff55..42a814ca08 100644 Binary files a/core/img/rating/s3.png and b/core/img/rating/s3.png differ diff --git a/core/img/rating/s4.png b/core/img/rating/s4.png index 47f1f694bf..5ce3888757 100644 Binary files a/core/img/rating/s4.png and b/core/img/rating/s4.png differ diff --git a/core/img/rating/s5.png b/core/img/rating/s5.png index aa225b6a9a..da4bbc5847 100644 Binary files a/core/img/rating/s5.png and b/core/img/rating/s5.png differ diff --git a/core/img/rating/s6.png b/core/img/rating/s6.png index fd4f42e22c..267c52ad3c 100644 Binary files a/core/img/rating/s6.png and b/core/img/rating/s6.png differ diff --git a/core/img/rating/s7.png b/core/img/rating/s7.png index 0d18a1dc02..3381d066d8 100644 Binary files a/core/img/rating/s7.png and b/core/img/rating/s7.png differ diff --git a/core/img/rating/s8.png b/core/img/rating/s8.png index 951c3fd3be..091dc5b21f 100644 Binary files a/core/img/rating/s8.png and b/core/img/rating/s8.png differ diff --git a/core/img/rating/s9.png b/core/img/rating/s9.png index b1a654c85d..dfe8356343 100644 Binary files a/core/img/rating/s9.png and b/core/img/rating/s9.png differ diff --git a/core/img/remoteStorage-big.png b/core/img/remoteStorage-big.png index f225423303..7e76e21209 100644 Binary files a/core/img/remoteStorage-big.png and b/core/img/remoteStorage-big.png differ diff --git a/core/js/jquery-1.10.0.min.js b/core/js/jquery-1.10.0.min.js new file mode 100644 index 0000000000..01c688164a --- /dev/null +++ b/core/js/jquery-1.10.0.min.js @@ -0,0 +1,6 @@ +/*! jQuery v1.10.0 | (c) 2005, 2013 jQuery Foundation, Inc. | jquery.org/license +//@ sourceMappingURL=jquery-1.10.0.min.map +*/ +(function(e,t){var n,r,i=typeof t,o=e.location,a=e.document,s=a.documentElement,l=e.jQuery,u=e.$,c={},p=[],f="1.10.0",d=p.concat,h=p.push,g=p.slice,m=p.indexOf,y=c.toString,v=c.hasOwnProperty,b=f.trim,x=function(e,t){return new x.fn.init(e,t,r)},w=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,T=/\S+/g,C=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,N=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,k=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,E=/^[\],:{}\s]*$/,S=/(?:^|:|,)(?:\s*\[)+/g,A=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,j=/"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g,D=/^-ms-/,L=/-([\da-z])/gi,H=function(e,t){return t.toUpperCase()},q=function(e){(a.addEventListener||"load"===e.type||"complete"===a.readyState)&&(_(),x.ready())},_=function(){a.addEventListener?(a.removeEventListener("DOMContentLoaded",q,!1),e.removeEventListener("load",q,!1)):(a.detachEvent("onreadystatechange",q),e.detachEvent("onload",q))};x.fn=x.prototype={jquery:f,constructor:x,init:function(e,n,r){var i,o;if(!e)return this;if("string"==typeof e){if(i="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:N.exec(e),!i||!i[1]&&n)return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e);if(i[1]){if(n=n instanceof x?n[0]:n,x.merge(this,x.parseHTML(i[1],n&&n.nodeType?n.ownerDocument||n:a,!0)),k.test(i[1])&&x.isPlainObject(n))for(i in n)x.isFunction(this[i])?this[i](n[i]):this.attr(i,n[i]);return this}if(o=a.getElementById(i[2]),o&&o.parentNode){if(o.id!==i[2])return r.find(e);this.length=1,this[0]=o}return this.context=a,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):x.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),x.makeArray(e,this))},selector:"",length:0,toArray:function(){return g.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=x.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return x.each(this,e,t)},ready:function(e){return x.ready.promise().done(e),this},slice:function(){return this.pushStack(g.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(x.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:h,sort:[].sort,splice:[].splice},x.fn.init.prototype=x.fn,x.extend=x.fn.extend=function(){var e,n,r,i,o,a,s=arguments[0]||{},l=1,u=arguments.length,c=!1;for("boolean"==typeof s&&(c=s,s=arguments[1]||{},l=2),"object"==typeof s||x.isFunction(s)||(s={}),u===l&&(s=this,--l);u>l;l++)if(null!=(o=arguments[l]))for(i in o)e=s[i],r=o[i],s!==r&&(c&&r&&(x.isPlainObject(r)||(n=x.isArray(r)))?(n?(n=!1,a=e&&x.isArray(e)?e:[]):a=e&&x.isPlainObject(e)?e:{},s[i]=x.extend(c,a,r)):r!==t&&(s[i]=r));return s},x.extend({expando:"jQuery"+(f+Math.random()).replace(/\D/g,""),noConflict:function(t){return e.$===x&&(e.$=u),t&&e.jQuery===x&&(e.jQuery=l),x},isReady:!1,readyWait:1,holdReady:function(e){e?x.readyWait++:x.ready(!0)},ready:function(e){if(e===!0?!--x.readyWait:!x.isReady){if(!a.body)return setTimeout(x.ready);x.isReady=!0,e!==!0&&--x.readyWait>0||(n.resolveWith(a,[x]),x.fn.trigger&&x(a).trigger("ready").off("ready"))}},isFunction:function(e){return"function"===x.type(e)},isArray:Array.isArray||function(e){return"array"===x.type(e)},isWindow:function(e){return null!=e&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?c[y.call(e)]||"object":typeof e},isPlainObject:function(e){var n;if(!e||"object"!==x.type(e)||e.nodeType||x.isWindow(e))return!1;try{if(e.constructor&&!v.call(e,"constructor")&&!v.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(r){return!1}if(x.support.ownLast)for(n in e)return v.call(e,n);for(n in e);return n===t||v.call(e,n)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||a;var r=k.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=x.buildFragment([e],t,i),i&&x(i).remove(),x.merge([],r.childNodes))},parseJSON:function(n){return e.JSON&&e.JSON.parse?e.JSON.parse(n):null===n?n:"string"==typeof n&&(n=x.trim(n),n&&E.test(n.replace(A,"@").replace(j,"]").replace(S,"")))?Function("return "+n)():(x.error("Invalid JSON: "+n),t)},parseXML:function(n){var r,i;if(!n||"string"!=typeof n)return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(o){r=t}return r&&r.documentElement&&!r.getElementsByTagName("parsererror").length||x.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&x.trim(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(D,"ms-").replace(L,H)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,a=M(e);if(n){if(a){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(a){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:b&&!b.call("\ufeff\u00a0")?function(e){return null==e?"":b.call(e)}:function(e){return null==e?"":(e+"").replace(C,"")},makeArray:function(e,t){var n=t||[];return null!=e&&(M(Object(e))?x.merge(n,"string"==typeof e?[e]:e):h.call(n,e)),n},inArray:function(e,t,n){var r;if(t){if(m)return m.call(t,e,n);for(r=t.length,n=n?0>n?Math.max(0,r+n):n:0;r>n;n++)if(n in t&&t[n]===e)return n}return-1},merge:function(e,n){var r=n.length,i=e.length,o=0;if("number"==typeof r)for(;r>o;o++)e[i++]=n[o];else while(n[o]!==t)e[i++]=n[o++];return e.length=i,e},grep:function(e,t,n){var r,i=[],o=0,a=e.length;for(n=!!n;a>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,a=M(e),s=[];if(a)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(s[s.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(s[s.length]=r);return d.apply([],s)},guid:1,proxy:function(e,n){var r,i,o;return"string"==typeof n&&(o=e[n],n=e,e=o),x.isFunction(e)?(r=g.call(arguments,2),i=function(){return e.apply(n||this,r.concat(g.call(arguments)))},i.guid=e.guid=e.guid||x.guid++,i):t},access:function(e,n,r,i,o,a,s){var l=0,u=e.length,c=null==r;if("object"===x.type(r)){o=!0;for(l in r)x.access(e,n,l,r[l],!0,a,s)}else if(i!==t&&(o=!0,x.isFunction(i)||(s=!0),c&&(s?(n.call(e,i),n=null):(c=n,n=function(e,t,n){return c.call(x(e),n)})),n))for(;u>l;l++)n(e[l],r,s?i:i.call(e[l],l,n(e[l],r)));return o?e:c?n.call(e):u?n(e[0],r):a},now:function(){return(new Date).getTime()},swap:function(e,t,n,r){var i,o,a={};for(o in t)a[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=a[o];return i}}),x.ready.promise=function(t){if(!n)if(n=x.Deferred(),"complete"===a.readyState)setTimeout(x.ready);else if(a.addEventListener)a.addEventListener("DOMContentLoaded",q,!1),e.addEventListener("load",q,!1);else{a.attachEvent("onreadystatechange",q),e.attachEvent("onload",q);var r=!1;try{r=null==e.frameElement&&a.documentElement}catch(i){}r&&r.doScroll&&function o(){if(!x.isReady){try{r.doScroll("left")}catch(e){return setTimeout(o,50)}_(),x.ready()}}()}return n.promise(t)},x.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){c["[object "+t+"]"]=t.toLowerCase()});function M(e){var t=e.length,n=x.type(e);return x.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}r=x(a),function(e,t){var n,r,i,o,a,s,l,u,c,p,f,d,h,g,m,y,v,b="sizzle"+-new Date,w=e.document,T=0,C=0,N=lt(),k=lt(),E=lt(),S=!1,A=function(){return 0},j=typeof t,D=1<<31,L={}.hasOwnProperty,H=[],q=H.pop,_=H.push,M=H.push,O=H.slice,F=H.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},B="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",P="[\\x20\\t\\r\\n\\f]",R="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",W=R.replace("w","w#"),$="\\["+P+"*("+R+")"+P+"*(?:([*^$|!~]?=)"+P+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+W+")|)|)"+P+"*\\]",I=":("+R+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+$.replace(3,8)+")*)|.*)\\)|)",z=RegExp("^"+P+"+|((?:^|[^\\\\])(?:\\\\.)*)"+P+"+$","g"),X=RegExp("^"+P+"*,"+P+"*"),U=RegExp("^"+P+"*([>+~]|"+P+")"+P+"*"),V=RegExp(P+"*[+~]"),Y=RegExp("="+P+"*([^\\]'\"]*)"+P+"*\\]","g"),J=RegExp(I),G=RegExp("^"+W+"$"),Q={ID:RegExp("^#("+R+")"),CLASS:RegExp("^\\.("+R+")"),TAG:RegExp("^("+R.replace("w","w*")+")"),ATTR:RegExp("^"+$),PSEUDO:RegExp("^"+I),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+P+"*(even|odd|(([+-]|)(\\d*)n|)"+P+"*(?:([+-]|)"+P+"*(\\d+)|))"+P+"*\\)|)","i"),bool:RegExp("^(?:"+B+")$","i"),needsContext:RegExp("^"+P+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+P+"*((?:-\\d)?\\d*)"+P+"*\\)|)(?=[^-]|$)","i")},K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,et=/^(?:input|select|textarea|button)$/i,tt=/^h\d$/i,nt=/'|\\/g,rt=RegExp("\\\\([\\da-f]{1,6}"+P+"?|("+P+")|.)","ig"),it=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:0>r?String.fromCharCode(r+65536):String.fromCharCode(55296|r>>10,56320|1023&r)};try{M.apply(H=O.call(w.childNodes),w.childNodes),H[w.childNodes.length].nodeType}catch(ot){M={apply:H.length?function(e,t){_.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function at(e,t,n,i){var o,a,s,l,u,c,d,m,y,x;if((t?t.ownerDocument||t:w)!==f&&p(t),t=t||f,n=n||[],!e||"string"!=typeof e)return n;if(1!==(l=t.nodeType)&&9!==l)return[];if(h&&!i){if(o=Z.exec(e))if(s=o[1]){if(9===l){if(a=t.getElementById(s),!a||!a.parentNode)return n;if(a.id===s)return n.push(a),n}else if(t.ownerDocument&&(a=t.ownerDocument.getElementById(s))&&v(t,a)&&a.id===s)return n.push(a),n}else{if(o[2])return M.apply(n,t.getElementsByTagName(e)),n;if((s=o[3])&&r.getElementsByClassName&&t.getElementsByClassName)return M.apply(n,t.getElementsByClassName(s)),n}if(r.qsa&&(!g||!g.test(e))){if(m=d=b,y=t,x=9===l&&e,1===l&&"object"!==t.nodeName.toLowerCase()){c=bt(e),(d=t.getAttribute("id"))?m=d.replace(nt,"\\$&"):t.setAttribute("id",m),m="[id='"+m+"'] ",u=c.length;while(u--)c[u]=m+xt(c[u]);y=V.test(e)&&t.parentNode||t,x=c.join(",")}if(x)try{return M.apply(n,y.querySelectorAll(x)),n}catch(T){}finally{d||t.removeAttribute("id")}}}return At(e.replace(z,"$1"),t,n,i)}function st(e){return K.test(e+"")}function lt(){var e=[];function t(n,r){return e.push(n+=" ")>o.cacheLength&&delete t[e.shift()],t[n]=r}return t}function ut(e){return e[b]=!0,e}function ct(e){var t=f.createElement("div");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function pt(e,t,n){e=e.split("|");var r,i=e.length,a=n?null:t;while(i--)(r=o.attrHandle[e[i]])&&r!==t||(o.attrHandle[e[i]]=a)}function ft(e,t){var n=e.getAttributeNode(t);return n&&n.specified?n.value:e[t]===!0?t.toLowerCase():null}function dt(e,t){return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}function ht(e){return"input"===e.nodeName.toLowerCase()?e.defaultValue:t}function gt(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&(~t.sourceIndex||D)-(~e.sourceIndex||D);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function mt(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function yt(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function vt(e){return ut(function(t){return t=+t,ut(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}s=at.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},r=at.support={},p=at.setDocument=function(e){var n=e?e.ownerDocument||e:w;return n!==f&&9===n.nodeType&&n.documentElement?(f=n,d=n.documentElement,h=!s(n),r.attributes=ct(function(e){return e.innerHTML="",pt("type|href|height|width",dt,"#"===e.firstChild.getAttribute("href")),pt(B,ft,null==e.getAttribute("disabled")),e.className="i",!e.getAttribute("className")}),r.input=ct(function(e){return e.innerHTML="
",e.firstChild.className="i",2===e.getElementsByClassName("i").length}),r.getById=ct(function(e){return d.appendChild(e).id=b,!n.getElementsByName||!n.getElementsByName(b).length}),r.getById?(o.find.ID=function(e,t){if(typeof t.getElementById!==j&&h){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){return e.getAttribute("id")===t}}):(delete o.find.ID,o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){var n=typeof e.getAttributeNode!==j&&e.getAttributeNode("id");return n&&n.value===t}}),o.find.TAG=r.getElementsByTagName?function(e,n){return typeof n.getElementsByTagName!==j?n.getElementsByTagName(e):t}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},o.find.CLASS=r.getElementsByClassName&&function(e,n){return typeof n.getElementsByClassName!==j&&h?n.getElementsByClassName(e):t},m=[],g=[],(r.qsa=st(n.querySelectorAll))&&(ct(function(e){e.innerHTML="",e.querySelectorAll("[selected]").length||g.push("\\["+P+"*(?:value|"+B+")"),e.querySelectorAll(":checked").length||g.push(":checked")}),ct(function(e){var t=n.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("t",""),e.querySelectorAll("[t^='']").length&&g.push("[*^$]="+P+"*(?:''|\"\")"),e.querySelectorAll(":enabled").length||g.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),g.push(",.*:")})),(r.matchesSelector=st(y=d.webkitMatchesSelector||d.mozMatchesSelector||d.oMatchesSelector||d.msMatchesSelector))&&ct(function(e){r.disconnectedMatch=y.call(e,"div"),y.call(e,"[s!='']:x"),m.push("!=",I)}),g=g.length&&RegExp(g.join("|")),m=m.length&&RegExp(m.join("|")),v=st(d.contains)||d.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},r.sortDetached=ct(function(e){return 1&e.compareDocumentPosition(n.createElement("div"))}),A=d.compareDocumentPosition?function(e,t){if(e===t)return S=!0,0;var i=t.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(t);return i?1&i||!r.sortDetached&&t.compareDocumentPosition(e)===i?e===n||v(w,e)?-1:t===n||v(w,t)?1:c?F.call(c,e)-F.call(c,t):0:4&i?-1:1:e.compareDocumentPosition?-1:1}:function(e,t){var r,i=0,o=e.parentNode,a=t.parentNode,s=[e],l=[t];if(e===t)return S=!0,0;if(!o||!a)return e===n?-1:t===n?1:o?-1:a?1:c?F.call(c,e)-F.call(c,t):0;if(o===a)return gt(e,t);r=e;while(r=r.parentNode)s.unshift(r);r=t;while(r=r.parentNode)l.unshift(r);while(s[i]===l[i])i++;return i?gt(s[i],l[i]):s[i]===w?-1:l[i]===w?1:0},n):f},at.matches=function(e,t){return at(e,null,null,t)},at.matchesSelector=function(e,t){if((e.ownerDocument||e)!==f&&p(e),t=t.replace(Y,"='$1']"),!(!r.matchesSelector||!h||m&&m.test(t)||g&&g.test(t)))try{var n=y.call(e,t);if(n||r.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(i){}return at(t,f,null,[e]).length>0},at.contains=function(e,t){return(e.ownerDocument||e)!==f&&p(e),v(e,t)},at.attr=function(e,n){(e.ownerDocument||e)!==f&&p(e);var i=o.attrHandle[n.toLowerCase()],a=i&&L.call(o.attrHandle,n.toLowerCase())?i(e,n,!h):t;return a===t?r.attributes||!h?e.getAttribute(n):(a=e.getAttributeNode(n))&&a.specified?a.value:null:a},at.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},at.uniqueSort=function(e){var t,n=[],i=0,o=0;if(S=!r.detectDuplicates,c=!r.sortStable&&e.slice(0),e.sort(A),S){while(t=e[o++])t===e[o]&&(i=n.push(o));while(i--)e.splice(n[i],1)}return e},a=at.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=a(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=a(t);return n},o=at.selectors={cacheLength:50,createPseudo:ut,match:Q,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(rt,it),e[3]=(e[4]||e[5]||"").replace(rt,it),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||at.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&at.error(e[0]),e},PSEUDO:function(e){var n,r=!e[5]&&e[2];return Q.CHILD.test(e[0])?null:(e[3]&&e[4]!==t?e[2]=e[4]:r&&J.test(r)&&(n=bt(r,!0))&&(n=r.indexOf(")",r.length-n)-r.length)&&(e[0]=e[0].slice(0,n),e[2]=r.slice(0,n)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(rt,it).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=N[e+" "];return t||(t=RegExp("(^|"+P+")"+e+"("+P+"|$)"))&&N(e,function(e){return t.test("string"==typeof e.className&&e.className||typeof e.getAttribute!==j&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=at.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,l){var u,c,p,f,d,h,g=o!==a?"nextSibling":"previousSibling",m=t.parentNode,y=s&&t.nodeName.toLowerCase(),v=!l&&!s;if(m){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===y:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?m.firstChild:m.lastChild],a&&v){c=m[b]||(m[b]={}),u=c[e]||[],d=u[0]===T&&u[1],f=u[0]===T&&u[2],p=d&&m.childNodes[d];while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if(1===p.nodeType&&++f&&p===t){c[e]=[T,d,f];break}}else if(v&&(u=(t[b]||(t[b]={}))[e])&&u[0]===T)f=u[1];else while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===y:1===p.nodeType)&&++f&&(v&&((p[b]||(p[b]={}))[e]=[T,f]),p===t))break;return f-=i,f===r||0===f%r&&f/r>=0}}},PSEUDO:function(e,t){var n,r=o.pseudos[e]||o.setFilters[e.toLowerCase()]||at.error("unsupported pseudo: "+e);return r[b]?r(t):r.length>1?(n=[e,e,"",t],o.setFilters.hasOwnProperty(e.toLowerCase())?ut(function(e,n){var i,o=r(e,t),a=o.length;while(a--)i=F.call(e,o[a]),e[i]=!(n[i]=o[a])}):function(e){return r(e,0,n)}):r}},pseudos:{not:ut(function(e){var t=[],n=[],r=l(e.replace(z,"$1"));return r[b]?ut(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:ut(function(e){return function(t){return at(e,t).length>0}}),contains:ut(function(e){return function(t){return(t.textContent||t.innerText||a(t)).indexOf(e)>-1}}),lang:ut(function(e){return G.test(e||"")||at.error("unsupported lang: "+e),e=e.replace(rt,it).toLowerCase(),function(t){var n;do if(n=h?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===d},focus:function(e){return e===f.activeElement&&(!f.hasFocus||f.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!o.pseudos.empty(e)},header:function(e){return tt.test(e.nodeName)},input:function(e){return et.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:vt(function(){return[0]}),last:vt(function(e,t){return[t-1]}),eq:vt(function(e,t,n){return[0>n?n+t:n]}),even:vt(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:vt(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:vt(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:vt(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}};for(n in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})o.pseudos[n]=mt(n);for(n in{submit:!0,reset:!0})o.pseudos[n]=yt(n);function bt(e,t){var n,r,i,a,s,l,u,c=k[e+" "];if(c)return t?0:c.slice(0);s=e,l=[],u=o.preFilter;while(s){(!n||(r=X.exec(s)))&&(r&&(s=s.slice(r[0].length)||s),l.push(i=[])),n=!1,(r=U.exec(s))&&(n=r.shift(),i.push({value:n,type:r[0].replace(z," ")}),s=s.slice(n.length));for(a in o.filter)!(r=Q[a].exec(s))||u[a]&&!(r=u[a](r))||(n=r.shift(),i.push({value:n,type:a,matches:r}),s=s.slice(n.length));if(!n)break}return t?s.length:s?at.error(e):k(e,l).slice(0)}function xt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function wt(e,t,n){var r=t.dir,o=n&&"parentNode"===r,a=C++;return t.first?function(t,n,i){while(t=t[r])if(1===t.nodeType||o)return e(t,n,i)}:function(t,n,s){var l,u,c,p=T+" "+a;if(s){while(t=t[r])if((1===t.nodeType||o)&&e(t,n,s))return!0}else while(t=t[r])if(1===t.nodeType||o)if(c=t[b]||(t[b]={}),(u=c[r])&&u[0]===p){if((l=u[1])===!0||l===i)return l===!0}else if(u=c[r]=[p],u[1]=e(t,n,s)||i,u[1]===!0)return!0}}function Tt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function Ct(e,t,n,r,i){var o,a=[],s=0,l=e.length,u=null!=t;for(;l>s;s++)(o=e[s])&&(!n||n(o,r,i))&&(a.push(o),u&&t.push(s));return a}function Nt(e,t,n,r,i,o){return r&&!r[b]&&(r=Nt(r)),i&&!i[b]&&(i=Nt(i,o)),ut(function(o,a,s,l){var u,c,p,f=[],d=[],h=a.length,g=o||St(t||"*",s.nodeType?[s]:s,[]),m=!e||!o&&t?g:Ct(g,f,e,s,l),y=n?i||(o?e:h||r)?[]:a:m;if(n&&n(m,y,s,l),r){u=Ct(y,d),r(u,[],s,l),c=u.length;while(c--)(p=u[c])&&(y[d[c]]=!(m[d[c]]=p))}if(o){if(i||e){if(i){u=[],c=y.length;while(c--)(p=y[c])&&u.push(m[c]=p);i(null,y=[],u,l)}c=y.length;while(c--)(p=y[c])&&(u=i?F.call(o,p):f[c])>-1&&(o[u]=!(a[u]=p))}}else y=Ct(y===a?y.splice(h,y.length):y),i?i(null,a,y,l):M.apply(a,y)})}function kt(e){var t,n,r,i=e.length,a=o.relative[e[0].type],s=a||o.relative[" "],l=a?1:0,c=wt(function(e){return e===t},s,!0),p=wt(function(e){return F.call(t,e)>-1},s,!0),f=[function(e,n,r){return!a&&(r||n!==u)||((t=n).nodeType?c(e,n,r):p(e,n,r))}];for(;i>l;l++)if(n=o.relative[e[l].type])f=[wt(Tt(f),n)];else{if(n=o.filter[e[l].type].apply(null,e[l].matches),n[b]){for(r=++l;i>r;r++)if(o.relative[e[r].type])break;return Nt(l>1&&Tt(f),l>1&&xt(e.slice(0,l-1).concat({value:" "===e[l-2].type?"*":""})).replace(z,"$1"),n,r>l&&kt(e.slice(l,r)),i>r&&kt(e=e.slice(r)),i>r&&xt(e))}f.push(n)}return Tt(f)}function Et(e,t){var n=0,r=t.length>0,a=e.length>0,s=function(s,l,c,p,d){var h,g,m,y=[],v=0,b="0",x=s&&[],w=null!=d,C=u,N=s||a&&o.find.TAG("*",d&&l.parentNode||l),k=T+=null==C?1:Math.random()||.1;for(w&&(u=l!==f&&l,i=n);null!=(h=N[b]);b++){if(a&&h){g=0;while(m=e[g++])if(m(h,l,c)){p.push(h);break}w&&(T=k,i=++n)}r&&((h=!m&&h)&&v--,s&&x.push(h))}if(v+=b,r&&b!==v){g=0;while(m=t[g++])m(x,y,l,c);if(s){if(v>0)while(b--)x[b]||y[b]||(y[b]=q.call(p));y=Ct(y)}M.apply(p,y),w&&!s&&y.length>0&&v+t.length>1&&at.uniqueSort(p)}return w&&(T=k,u=C),x};return r?ut(s):s}l=at.compile=function(e,t){var n,r=[],i=[],o=E[e+" "];if(!o){t||(t=bt(e)),n=t.length;while(n--)o=kt(t[n]),o[b]?r.push(o):i.push(o);o=E(e,Et(i,r))}return o};function St(e,t,n){var r=0,i=t.length;for(;i>r;r++)at(e,t[r],n);return n}function At(e,t,n,i){var a,s,u,c,p,f=bt(e);if(!i&&1===f.length){if(s=f[0]=f[0].slice(0),s.length>2&&"ID"===(u=s[0]).type&&r.getById&&9===t.nodeType&&h&&o.relative[s[1].type]){if(t=(o.find.ID(u.matches[0].replace(rt,it),t)||[])[0],!t)return n;e=e.slice(s.shift().value.length)}a=Q.needsContext.test(e)?0:s.length;while(a--){if(u=s[a],o.relative[c=u.type])break;if((p=o.find[c])&&(i=p(u.matches[0].replace(rt,it),V.test(s[0].type)&&t.parentNode||t))){if(s.splice(a,1),e=i.length&&xt(s),!e)return M.apply(n,i),n;break}}}return l(e,f)(i,t,!h,n,V.test(e)),n}o.pseudos.nth=o.pseudos.eq;function jt(){}jt.prototype=o.filters=o.pseudos,o.setFilters=new jt,r.sortStable=b.split("").sort(A).join("")===b,p(),[0,0].sort(A),r.detectDuplicates=S,x.find=at,x.expr=at.selectors,x.expr[":"]=x.expr.pseudos,x.unique=at.uniqueSort,x.text=at.getText,x.isXMLDoc=at.isXML,x.contains=at.contains}(e);var O={};function F(e){var t=O[e]={};return x.each(e.match(T)||[],function(e,n){t[n]=!0}),t}x.Callbacks=function(e){e="string"==typeof e?O[e]||F(e):x.extend({},e);var n,r,i,o,a,s,l=[],u=!e.once&&[],c=function(t){for(r=e.memory&&t,i=!0,a=s||0,s=0,o=l.length,n=!0;l&&o>a;a++)if(l[a].apply(t[0],t[1])===!1&&e.stopOnFalse){r=!1;break}n=!1,l&&(u?u.length&&c(u.shift()):r?l=[]:p.disable())},p={add:function(){if(l){var t=l.length;(function i(t){x.each(t,function(t,n){var r=x.type(n);"function"===r?e.unique&&p.has(n)||l.push(n):n&&n.length&&"string"!==r&&i(n)})})(arguments),n?o=l.length:r&&(s=t,c(r))}return this},remove:function(){return l&&x.each(arguments,function(e,t){var r;while((r=x.inArray(t,l,r))>-1)l.splice(r,1),n&&(o>=r&&o--,a>=r&&a--)}),this},has:function(e){return e?x.inArray(e,l)>-1:!(!l||!l.length)},empty:function(){return l=[],o=0,this},disable:function(){return l=u=r=t,this},disabled:function(){return!l},lock:function(){return u=t,r||p.disable(),this},locked:function(){return!u},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],!l||i&&!u||(n?u.push(t):c(t)),this},fire:function(){return p.fireWith(this,arguments),this},fired:function(){return!!i}};return p},x.extend({Deferred:function(e){var t=[["resolve","done",x.Callbacks("once memory"),"resolved"],["reject","fail",x.Callbacks("once memory"),"rejected"],["notify","progress",x.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return x.Deferred(function(n){x.each(t,function(t,o){var a=o[0],s=x.isFunction(e[t])&&e[t];i[o[1]](function(){var e=s&&s.apply(this,arguments);e&&x.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[a+"With"](this===r?n.promise():this,s?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?x.extend(e,r):r}},i={};return r.pipe=r.then,x.each(t,function(e,o){var a=o[2],s=o[3];r[o[1]]=a.add,s&&a.add(function(){n=s},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=a.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=g.call(arguments),r=n.length,i=1!==r||e&&x.isFunction(e.promise)?r:0,o=1===i?e:x.Deferred(),a=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?g.call(arguments):r,n===s?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},s,l,u;if(r>1)for(s=Array(r),l=Array(r),u=Array(r);r>t;t++)n[t]&&x.isFunction(n[t].promise)?n[t].promise().done(a(t,u,n)).fail(o.reject).progress(a(t,l,s)):--i;return i||o.resolveWith(u,n),o.promise()}}),x.support=function(t){var n,r,o,s,l,u,c,p,f,d=a.createElement("div");if(d.setAttribute("className","t"),d.innerHTML="
a",n=d.getElementsByTagName("*")||[],r=d.getElementsByTagName("a")[0],!r||!r.style||!n.length)return t;s=a.createElement("select"),u=s.appendChild(a.createElement("option")),o=d.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t.getSetAttribute="t"!==d.className,t.leadingWhitespace=3===d.firstChild.nodeType,t.tbody=!d.getElementsByTagName("tbody").length,t.htmlSerialize=!!d.getElementsByTagName("link").length,t.style=/top/.test(r.getAttribute("style")),t.hrefNormalized="/a"===r.getAttribute("href"),t.opacity=/^0.5/.test(r.style.opacity),t.cssFloat=!!r.style.cssFloat,t.checkOn=!!o.value,t.optSelected=u.selected,t.enctype=!!a.createElement("form").enctype,t.html5Clone="<:nav>"!==a.createElement("nav").cloneNode(!0).outerHTML,t.inlineBlockNeedsLayout=!1,t.shrinkWrapBlocks=!1,t.pixelPosition=!1,t.deleteExpando=!0,t.noCloneEvent=!0,t.reliableMarginRight=!0,t.boxSizingReliable=!0,o.checked=!0,t.noCloneChecked=o.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!u.disabled;try{delete d.test}catch(h){t.deleteExpando=!1}o=a.createElement("input"),o.setAttribute("value",""),t.input=""===o.getAttribute("value"),o.value="t",o.setAttribute("type","radio"),t.radioValue="t"===o.value,o.setAttribute("checked","t"),o.setAttribute("name","t"),l=a.createDocumentFragment(),l.appendChild(o),t.appendChecked=o.checked,t.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,d.attachEvent&&(d.attachEvent("onclick",function(){t.noCloneEvent=!1}),d.cloneNode(!0).click());for(f in{submit:!0,change:!0,focusin:!0})d.setAttribute(c="on"+f,"t"),t[f+"Bubbles"]=c in e||d.attributes[c].expando===!1;d.style.backgroundClip="content-box",d.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===d.style.backgroundClip;for(f in x(t))break;return t.ownLast="0"!==f,x(function(){var n,r,o,s="padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",l=a.getElementsByTagName("body")[0];l&&(n=a.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",l.appendChild(n).appendChild(d),d.innerHTML="
t
",o=d.getElementsByTagName("td"),o[0].style.cssText="padding:0;margin:0;border:0;display:none",p=0===o[0].offsetHeight,o[0].style.display="",o[1].style.display="none",t.reliableHiddenOffsets=p&&0===o[0].offsetHeight,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",x.swap(l,null!=l.style.zoom?{zoom:1}:{},function(){t.boxSizing=4===d.offsetWidth}),e.getComputedStyle&&(t.pixelPosition="1%"!==(e.getComputedStyle(d,null)||{}).top,t.boxSizingReliable="4px"===(e.getComputedStyle(d,null)||{width:"4px"}).width,r=d.appendChild(a.createElement("div")),r.style.cssText=d.style.cssText=s,r.style.marginRight=r.style.width="0",d.style.width="1px",t.reliableMarginRight=!parseFloat((e.getComputedStyle(r,null)||{}).marginRight)),typeof d.style.zoom!==i&&(d.innerHTML="",d.style.cssText=s+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=3===d.offsetWidth,d.style.display="block",d.innerHTML="
",d.firstChild.style.width="5px",t.shrinkWrapBlocks=3!==d.offsetWidth,t.inlineBlockNeedsLayout&&(l.style.zoom=1)),l.removeChild(n),n=d=o=r=null)}),n=s=l=u=r=o=null,t}({});var B=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,P=/([A-Z])/g;function R(e,n,r,i){if(x.acceptData(e)){var o,a,s=x.expando,l=e.nodeType,u=l?x.cache:e,c=l?e[s]:e[s]&&s; +if(c&&u[c]&&(i||u[c].data)||r!==t||"string"!=typeof n)return c||(c=l?e[s]=p.pop()||x.guid++:s),u[c]||(u[c]=l?{}:{toJSON:x.noop}),("object"==typeof n||"function"==typeof n)&&(i?u[c]=x.extend(u[c],n):u[c].data=x.extend(u[c].data,n)),a=u[c],i||(a.data||(a.data={}),a=a.data),r!==t&&(a[x.camelCase(n)]=r),"string"==typeof n?(o=a[n],null==o&&(o=a[x.camelCase(n)])):o=a,o}}function W(e,t,n){if(x.acceptData(e)){var r,i,o=e.nodeType,a=o?x.cache:e,s=o?e[x.expando]:x.expando;if(a[s]){if(t&&(r=n?a[s]:a[s].data)){x.isArray(t)?t=t.concat(x.map(t,x.camelCase)):t in r?t=[t]:(t=x.camelCase(t),t=t in r?[t]:t.split(" ")),i=t.length;while(i--)delete r[t[i]];if(n?!I(r):!x.isEmptyObject(r))return}(n||(delete a[s].data,I(a[s])))&&(o?x.cleanData([e],!0):x.support.deleteExpando||a!=a.window?delete a[s]:a[s]=null)}}}x.extend({cache:{},noData:{applet:!0,embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(e){return e=e.nodeType?x.cache[e[x.expando]]:e[x.expando],!!e&&!I(e)},data:function(e,t,n){return R(e,t,n)},removeData:function(e,t){return W(e,t)},_data:function(e,t,n){return R(e,t,n,!0)},_removeData:function(e,t){return W(e,t,!0)},acceptData:function(e){if(e.nodeType&&1!==e.nodeType&&9!==e.nodeType)return!1;var t=e.nodeName&&x.noData[e.nodeName.toLowerCase()];return!t||t!==!0&&e.getAttribute("classid")===t}}),x.fn.extend({data:function(e,n){var r,i,o=null,a=0,s=this[0];if(e===t){if(this.length&&(o=x.data(s),1===s.nodeType&&!x._data(s,"parsedAttrs"))){for(r=s.attributes;r.length>a;a++)i=r[a].name,0===i.indexOf("data-")&&(i=x.camelCase(i.slice(5)),$(s,i,o[i]));x._data(s,"parsedAttrs",!0)}return o}return"object"==typeof e?this.each(function(){x.data(this,e)}):arguments.length>1?this.each(function(){x.data(this,e,n)}):s?$(s,e,x.data(s,e)):null},removeData:function(e){return this.each(function(){x.removeData(this,e)})}});function $(e,n,r){if(r===t&&1===e.nodeType){var i="data-"+n.replace(P,"-$1").toLowerCase();if(r=e.getAttribute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r+""===r?+r:B.test(r)?x.parseJSON(r):r}catch(o){}x.data(e,n,r)}else r=t}return r}function I(e){var t;for(t in e)if(("data"!==t||!x.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}x.extend({queue:function(e,n,r){var i;return e?(n=(n||"fx")+"queue",i=x._data(e,n),r&&(!i||x.isArray(r)?i=x._data(e,n,x.makeArray(r)):i.push(r)),i||[]):t},dequeue:function(e,t){t=t||"fx";var n=x.queue(e,t),r=n.length,i=n.shift(),o=x._queueHooks(e,t),a=function(){x.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),o.cur=i,i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return x._data(e,n)||x._data(e,n,{empty:x.Callbacks("once memory").add(function(){x._removeData(e,t+"queue"),x._removeData(e,n)})})}}),x.fn.extend({queue:function(e,n){var r=2;return"string"!=typeof e&&(n=e,e="fx",r--),r>arguments.length?x.queue(this[0],e):n===t?this:this.each(function(){var t=x.queue(this,e,n);x._queueHooks(this,e),"fx"===e&&"inprogress"!==t[0]&&x.dequeue(this,e)})},dequeue:function(e){return this.each(function(){x.dequeue(this,e)})},delay:function(e,t){return e=x.fx?x.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,n){var r,i=1,o=x.Deferred(),a=this,s=this.length,l=function(){--i||o.resolveWith(a,[a])};"string"!=typeof e&&(n=e,e=t),e=e||"fx";while(s--)r=x._data(a[s],e+"queueHooks"),r&&r.empty&&(i++,r.empty.add(l));return l(),o.promise(n)}});var z,X,U=/[\t\r\n\f]/g,V=/\r/g,Y=/^(?:input|select|textarea|button|object)$/i,J=/^(?:a|area)$/i,G=/^(?:checked|selected)$/i,Q=x.support.getSetAttribute,K=x.support.input;x.fn.extend({attr:function(e,t){return x.access(this,x.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){x.removeAttr(this,e)})},prop:function(e,t){return x.access(this,x.prop,e,t,arguments.length>1)},removeProp:function(e){return e=x.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,o,a=0,s=this.length,l="string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).addClass(e.call(this,t,this.className))});if(l)for(t=(e||"").match(T)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(U," "):" ")){o=0;while(i=t[o++])0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=x.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,a=0,s=this.length,l=0===arguments.length||"string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).removeClass(e.call(this,t,this.className))});if(l)for(t=(e||"").match(T)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(U," "):"")){o=0;while(i=t[o++])while(r.indexOf(" "+i+" ")>=0)r=r.replace(" "+i+" "," ");n.className=e?x.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e,r="boolean"==typeof t;return x.isFunction(e)?this.each(function(n){x(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n){var o,a=0,s=x(this),l=t,u=e.match(T)||[];while(o=u[a++])l=r?l:!s.hasClass(o),s[l?"addClass":"removeClass"](o)}else(n===i||"boolean"===n)&&(this.className&&x._data(this,"__className__",this.className),this.className=this.className||e===!1?"":x._data(this,"__className__")||"")})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(U," ").indexOf(t)>=0)return!0;return!1},val:function(e){var n,r,i,o=this[0];{if(arguments.length)return i=x.isFunction(e),this.each(function(n){var o;1===this.nodeType&&(o=i?e.call(this,n,x(this).val()):e,null==o?o="":"number"==typeof o?o+="":x.isArray(o)&&(o=x.map(o,function(e){return null==e?"":e+""})),r=x.valHooks[this.type]||x.valHooks[this.nodeName.toLowerCase()],r&&"set"in r&&r.set(this,o,"value")!==t||(this.value=o))});if(o)return r=x.valHooks[o.type]||x.valHooks[o.nodeName.toLowerCase()],r&&"get"in r&&(n=r.get(o,"value"))!==t?n:(n=o.value,"string"==typeof n?n.replace(V,""):null==n?"":n)}}}),x.extend({valHooks:{option:{get:function(e){var t=x.find.attr(e,"value");return null!=t?t:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,a=o?null:[],s=o?i+1:r.length,l=0>i?s:o?i:0;for(;s>l;l++)if(n=r[l],!(!n.selected&&l!==i||(x.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&x.nodeName(n.parentNode,"optgroup"))){if(t=x(n).val(),o)return t;a.push(t)}return a},set:function(e,t){var n,r,i=e.options,o=x.makeArray(t),a=i.length;while(a--)r=i[a],(r.selected=x.inArray(x(r).val(),o)>=0)&&(n=!0);return n||(e.selectedIndex=-1),o}}},attr:function(e,n,r){var o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return typeof e.getAttribute===i?x.prop(e,n,r):(1===s&&x.isXMLDoc(e)||(n=n.toLowerCase(),o=x.attrHooks[n]||(x.expr.match.bool.test(n)?X:z)),r===t?o&&"get"in o&&null!==(a=o.get(e,n))?a:(a=x.find.attr(e,n),null==a?t:a):null!==r?o&&"set"in o&&(a=o.set(e,r,n))!==t?a:(e.setAttribute(n,r+""),r):(x.removeAttr(e,n),t))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(T);if(o&&1===e.nodeType)while(n=o[i++])r=x.propFix[n]||n,x.expr.match.bool.test(n)?K&&Q||!G.test(n)?e[r]=!1:e[x.camelCase("default-"+n)]=e[r]=!1:x.attr(e,n,""),e.removeAttribute(Q?n:r)},attrHooks:{type:{set:function(e,t){if(!x.support.radioValue&&"radio"===t&&x.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{"for":"htmlFor","class":"className"},prop:function(e,n,r){var i,o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return a=1!==s||!x.isXMLDoc(e),a&&(n=x.propFix[n]||n,o=x.propHooks[n]),r!==t?o&&"set"in o&&(i=o.set(e,r,n))!==t?i:e[n]=r:o&&"get"in o&&null!==(i=o.get(e,n))?i:e[n]},propHooks:{tabIndex:{get:function(e){var t=x.find.attr(e,"tabindex");return t?parseInt(t,10):Y.test(e.nodeName)||J.test(e.nodeName)&&e.href?0:-1}}}}),X={set:function(e,t,n){return t===!1?x.removeAttr(e,n):K&&Q||!G.test(n)?e.setAttribute(!Q&&x.propFix[n]||n,n):e[x.camelCase("default-"+n)]=e[n]=!0,n}},x.each(x.expr.match.bool.source.match(/\w+/g),function(e,n){var r=x.expr.attrHandle[n]||x.find.attr;x.expr.attrHandle[n]=K&&Q||!G.test(n)?function(e,n,i){var o=x.expr.attrHandle[n],a=i?t:(x.expr.attrHandle[n]=t)!=r(e,n,i)?n.toLowerCase():null;return x.expr.attrHandle[n]=o,a}:function(e,n,r){return r?t:e[x.camelCase("default-"+n)]?n.toLowerCase():null}}),K&&Q||(x.attrHooks.value={set:function(e,n,r){return x.nodeName(e,"input")?(e.defaultValue=n,t):z&&z.set(e,n,r)}}),Q||(z={set:function(e,n,r){var i=e.getAttributeNode(r);return i||e.setAttributeNode(i=e.ownerDocument.createAttribute(r)),i.value=n+="","value"===r||n===e.getAttribute(r)?n:t}},x.expr.attrHandle.id=x.expr.attrHandle.name=x.expr.attrHandle.coords=function(e,n,r){var i;return r?t:(i=e.getAttributeNode(n))&&""!==i.value?i.value:null},x.valHooks.button={get:function(e,n){var r=e.getAttributeNode(n);return r&&r.specified?r.value:t},set:z.set},x.attrHooks.contenteditable={set:function(e,t,n){z.set(e,""===t?!1:t,n)}},x.each(["width","height"],function(e,n){x.attrHooks[n]={set:function(e,r){return""===r?(e.setAttribute(n,"auto"),r):t}}})),x.support.hrefNormalized||x.each(["href","src"],function(e,t){x.propHooks[t]={get:function(e){return e.getAttribute(t,4)}}}),x.support.style||(x.attrHooks.style={get:function(e){return e.style.cssText||t},set:function(e,t){return e.style.cssText=t+""}}),x.support.optSelected||(x.propHooks.selected={get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null}}),x.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){x.propFix[this.toLowerCase()]=this}),x.support.enctype||(x.propFix.enctype="encoding"),x.each(["radio","checkbox"],function(){x.valHooks[this]={set:function(e,n){return x.isArray(n)?e.checked=x.inArray(x(e).val(),n)>=0:t}},x.support.checkOn||(x.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var Z=/^(?:input|select|textarea)$/i,et=/^key/,tt=/^(?:mouse|contextmenu)|click/,nt=/^(?:focusinfocus|focusoutblur)$/,rt=/^([^.]*)(?:\.(.+)|)$/;function it(){return!0}function ot(){return!1}function at(){try{return a.activeElement}catch(e){}}x.event={global:{},add:function(e,n,r,o,a){var s,l,u,c,p,f,d,h,g,m,y,v=x._data(e);if(v){r.handler&&(c=r,r=c.handler,a=c.selector),r.guid||(r.guid=x.guid++),(l=v.events)||(l=v.events={}),(f=v.handle)||(f=v.handle=function(e){return typeof x===i||e&&x.event.triggered===e.type?t:x.event.dispatch.apply(f.elem,arguments)},f.elem=e),n=(n||"").match(T)||[""],u=n.length;while(u--)s=rt.exec(n[u])||[],g=y=s[1],m=(s[2]||"").split(".").sort(),g&&(p=x.event.special[g]||{},g=(a?p.delegateType:p.bindType)||g,p=x.event.special[g]||{},d=x.extend({type:g,origType:y,data:o,handler:r,guid:r.guid,selector:a,needsContext:a&&x.expr.match.needsContext.test(a),namespace:m.join(".")},c),(h=l[g])||(h=l[g]=[],h.delegateCount=0,p.setup&&p.setup.call(e,o,m,f)!==!1||(e.addEventListener?e.addEventListener(g,f,!1):e.attachEvent&&e.attachEvent("on"+g,f))),p.add&&(p.add.call(e,d),d.handler.guid||(d.handler.guid=r.guid)),a?h.splice(h.delegateCount++,0,d):h.push(d),x.event.global[g]=!0);e=null}},remove:function(e,t,n,r,i){var o,a,s,l,u,c,p,f,d,h,g,m=x.hasData(e)&&x._data(e);if(m&&(c=m.events)){t=(t||"").match(T)||[""],u=t.length;while(u--)if(s=rt.exec(t[u])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){p=x.event.special[d]||{},d=(r?p.delegateType:p.bindType)||d,f=c[d]||[],s=s[2]&&RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),l=o=f.length;while(o--)a=f[o],!i&&g!==a.origType||n&&n.guid!==a.guid||s&&!s.test(a.namespace)||r&&r!==a.selector&&("**"!==r||!a.selector)||(f.splice(o,1),a.selector&&f.delegateCount--,p.remove&&p.remove.call(e,a));l&&!f.length&&(p.teardown&&p.teardown.call(e,h,m.handle)!==!1||x.removeEvent(e,d,m.handle),delete c[d])}else for(d in c)x.event.remove(e,d+t[u],n,r,!0);x.isEmptyObject(c)&&(delete m.handle,x._removeData(e,"events"))}},trigger:function(n,r,i,o){var s,l,u,c,p,f,d,h=[i||a],g=v.call(n,"type")?n.type:n,m=v.call(n,"namespace")?n.namespace.split("."):[];if(u=f=i=i||a,3!==i.nodeType&&8!==i.nodeType&&!nt.test(g+x.event.triggered)&&(g.indexOf(".")>=0&&(m=g.split("."),g=m.shift(),m.sort()),l=0>g.indexOf(":")&&"on"+g,n=n[x.expando]?n:new x.Event(g,"object"==typeof n&&n),n.isTrigger=o?2:3,n.namespace=m.join("."),n.namespace_re=n.namespace?RegExp("(^|\\.)"+m.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,n.result=t,n.target||(n.target=i),r=null==r?[n]:x.makeArray(r,[n]),p=x.event.special[g]||{},o||!p.trigger||p.trigger.apply(i,r)!==!1)){if(!o&&!p.noBubble&&!x.isWindow(i)){for(c=p.delegateType||g,nt.test(c+g)||(u=u.parentNode);u;u=u.parentNode)h.push(u),f=u;f===(i.ownerDocument||a)&&h.push(f.defaultView||f.parentWindow||e)}d=0;while((u=h[d++])&&!n.isPropagationStopped())n.type=d>1?c:p.bindType||g,s=(x._data(u,"events")||{})[n.type]&&x._data(u,"handle"),s&&s.apply(u,r),s=l&&u[l],s&&x.acceptData(u)&&s.apply&&s.apply(u,r)===!1&&n.preventDefault();if(n.type=g,!o&&!n.isDefaultPrevented()&&(!p._default||p._default.apply(h.pop(),r)===!1)&&x.acceptData(i)&&l&&i[g]&&!x.isWindow(i)){f=i[l],f&&(i[l]=null),x.event.triggered=g;try{i[g]()}catch(y){}x.event.triggered=t,f&&(i[l]=f)}return n.result}},dispatch:function(e){e=x.event.fix(e);var n,r,i,o,a,s=[],l=g.call(arguments),u=(x._data(this,"events")||{})[e.type]||[],c=x.event.special[e.type]||{};if(l[0]=e,e.delegateTarget=this,!c.preDispatch||c.preDispatch.call(this,e)!==!1){s=x.event.handlers.call(this,e,u),n=0;while((o=s[n++])&&!e.isPropagationStopped()){e.currentTarget=o.elem,a=0;while((i=o.handlers[a++])&&!e.isImmediatePropagationStopped())(!e.namespace_re||e.namespace_re.test(i.namespace))&&(e.handleObj=i,e.data=i.data,r=((x.event.special[i.origType]||{}).handle||i.handler).apply(o.elem,l),r!==t&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,e),e.result}},handlers:function(e,n){var r,i,o,a,s=[],l=n.delegateCount,u=e.target;if(l&&u.nodeType&&(!e.button||"click"!==e.type))for(;u!=this;u=u.parentNode||this)if(1===u.nodeType&&(u.disabled!==!0||"click"!==e.type)){for(o=[],a=0;l>a;a++)i=n[a],r=i.selector+" ",o[r]===t&&(o[r]=i.needsContext?x(r,this).index(u)>=0:x.find(r,this,null,[u]).length),o[r]&&o.push(i);o.length&&s.push({elem:u,handlers:o})}return n.length>l&&s.push({elem:this,handlers:n.slice(l)}),s},fix:function(e){if(e[x.expando])return e;var t,n,r,i=e.type,o=e,s=this.fixHooks[i];s||(this.fixHooks[i]=s=tt.test(i)?this.mouseHooks:et.test(i)?this.keyHooks:{}),r=s.props?this.props.concat(s.props):this.props,e=new x.Event(o),t=r.length;while(t--)n=r[t],e[n]=o[n];return e.target||(e.target=o.srcElement||a),3===e.target.nodeType&&(e.target=e.target.parentNode),e.metaKey=!!e.metaKey,s.filter?s.filter(e,o):e},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,n){var r,i,o,s=n.button,l=n.fromElement;return null==e.pageX&&null!=n.clientX&&(i=e.target.ownerDocument||a,o=i.documentElement,r=i.body,e.pageX=n.clientX+(o&&o.scrollLeft||r&&r.scrollLeft||0)-(o&&o.clientLeft||r&&r.clientLeft||0),e.pageY=n.clientY+(o&&o.scrollTop||r&&r.scrollTop||0)-(o&&o.clientTop||r&&r.clientTop||0)),!e.relatedTarget&&l&&(e.relatedTarget=l===e.target?n.toElement:l),e.which||s===t||(e.which=1&s?1:2&s?3:4&s?2:0),e}},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==at()&&this.focus)try{return this.focus(),!1}catch(e){}},delegateType:"focusin"},blur:{trigger:function(){return this===at()&&this.blur?(this.blur(),!1):t},delegateType:"focusout"},click:{trigger:function(){return x.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):t},_default:function(e){return x.nodeName(e.target,"a")}},beforeunload:{postDispatch:function(e){e.result!==t&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=x.extend(new x.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?x.event.trigger(i,null,t):x.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},x.removeEvent=a.removeEventListener?function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)}:function(e,t,n){var r="on"+t;e.detachEvent&&(typeof e[r]===i&&(e[r]=null),e.detachEvent(r,n))},x.Event=function(e,n){return this instanceof x.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.returnValue===!1||e.getPreventDefault&&e.getPreventDefault()?it:ot):this.type=e,n&&x.extend(this,n),this.timeStamp=e&&e.timeStamp||x.now(),this[x.expando]=!0,t):new x.Event(e,n)},x.Event.prototype={isDefaultPrevented:ot,isPropagationStopped:ot,isImmediatePropagationStopped:ot,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=it,e&&(e.preventDefault?e.preventDefault():e.returnValue=!1)},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=it,e&&(e.stopPropagation&&e.stopPropagation(),e.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=it,this.stopPropagation()}},x.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){x.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;return(!i||i!==r&&!x.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),x.support.submitBubbles||(x.event.special.submit={setup:function(){return x.nodeName(this,"form")?!1:(x.event.add(this,"click._submit keypress._submit",function(e){var n=e.target,r=x.nodeName(n,"input")||x.nodeName(n,"button")?n.form:t;r&&!x._data(r,"submitBubbles")&&(x.event.add(r,"submit._submit",function(e){e._submit_bubble=!0}),x._data(r,"submitBubbles",!0))}),t)},postDispatch:function(e){e._submit_bubble&&(delete e._submit_bubble,this.parentNode&&!e.isTrigger&&x.event.simulate("submit",this.parentNode,e,!0))},teardown:function(){return x.nodeName(this,"form")?!1:(x.event.remove(this,"._submit"),t)}}),x.support.changeBubbles||(x.event.special.change={setup:function(){return Z.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(x.event.add(this,"propertychange._change",function(e){"checked"===e.originalEvent.propertyName&&(this._just_changed=!0)}),x.event.add(this,"click._change",function(e){this._just_changed&&!e.isTrigger&&(this._just_changed=!1),x.event.simulate("change",this,e,!0)})),!1):(x.event.add(this,"beforeactivate._change",function(e){var t=e.target;Z.test(t.nodeName)&&!x._data(t,"changeBubbles")&&(x.event.add(t,"change._change",function(e){!this.parentNode||e.isSimulated||e.isTrigger||x.event.simulate("change",this.parentNode,e,!0)}),x._data(t,"changeBubbles",!0))}),t)},handle:function(e){var n=e.target;return this!==n||e.isSimulated||e.isTrigger||"radio"!==n.type&&"checkbox"!==n.type?e.handleObj.handler.apply(this,arguments):t},teardown:function(){return x.event.remove(this,"._change"),!Z.test(this.nodeName)}}),x.support.focusinBubbles||x.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){x.event.simulate(t,e.target,x.event.fix(e),!0)};x.event.special[t]={setup:function(){0===n++&&a.addEventListener(e,r,!0)},teardown:function(){0===--n&&a.removeEventListener(e,r,!0)}}}),x.fn.extend({on:function(e,n,r,i,o){var a,s;if("object"==typeof e){"string"!=typeof n&&(r=r||n,n=t);for(a in e)this.on(a,n,r,e[a],o);return this}if(null==r&&null==i?(i=n,r=n=t):null==i&&("string"==typeof n?(i=r,r=t):(i=r,r=n,n=t)),i===!1)i=ot;else if(!i)return this;return 1===o&&(s=i,i=function(e){return x().off(e),s.apply(this,arguments)},i.guid=s.guid||(s.guid=x.guid++)),this.each(function(){x.event.add(this,e,i,r,n)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,n,r){var i,o;if(e&&e.preventDefault&&e.handleObj)return i=e.handleObj,x(e.delegateTarget).off(i.namespace?i.origType+"."+i.namespace:i.origType,i.selector,i.handler),this;if("object"==typeof e){for(o in e)this.off(o,n,e[o]);return this}return(n===!1||"function"==typeof n)&&(r=n,n=t),r===!1&&(r=ot),this.each(function(){x.event.remove(this,e,r,n)})},trigger:function(e,t){return this.each(function(){x.event.trigger(e,t,this)})},triggerHandler:function(e,n){var r=this[0];return r?x.event.trigger(e,n,r,!0):t}});var st=/^.[^:#\[\.,]*$/,lt=/^(?:parents|prev(?:Until|All))/,ut=x.expr.match.needsContext,ct={children:!0,contents:!0,next:!0,prev:!0};x.fn.extend({find:function(e){var t,n=[],r=this,i=r.length;if("string"!=typeof e)return this.pushStack(x(e).filter(function(){for(t=0;i>t;t++)if(x.contains(r[t],this))return!0}));for(t=0;i>t;t++)x.find(e,r[t],n);return n=this.pushStack(i>1?x.unique(n):n),n.selector=this.selector?this.selector+" "+e:e,n},has:function(e){var t,n=x(e,this),r=n.length;return this.filter(function(){for(t=0;r>t;t++)if(x.contains(this,n[t]))return!0})},not:function(e){return this.pushStack(ft(this,e||[],!0))},filter:function(e){return this.pushStack(ft(this,e||[],!1))},is:function(e){return!!ft(this,"string"==typeof e&&ut.test(e)?x(e):e||[],!1).length},closest:function(e,t){var n,r=0,i=this.length,o=[],a=ut.test(e)||"string"!=typeof e?x(e,t||this.context):0;for(;i>r;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(11>n.nodeType&&(a?a.index(n)>-1:1===n.nodeType&&x.find.matchesSelector(n,e))){n=o.push(n);break}return this.pushStack(o.length>1?x.unique(o):o)},index:function(e){return e?"string"==typeof e?x.inArray(this[0],x(e)):x.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?x(e,t):x.makeArray(e&&e.nodeType?[e]:e),r=x.merge(this.get(),n);return this.pushStack(x.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function pt(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}x.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return x.dir(e,"parentNode")},parentsUntil:function(e,t,n){return x.dir(e,"parentNode",n)},next:function(e){return pt(e,"nextSibling")},prev:function(e){return pt(e,"previousSibling")},nextAll:function(e){return x.dir(e,"nextSibling")},prevAll:function(e){return x.dir(e,"previousSibling")},nextUntil:function(e,t,n){return x.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return x.dir(e,"previousSibling",n)},siblings:function(e){return x.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return x.sibling(e.firstChild)},contents:function(e){return x.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:x.merge([],e.childNodes)}},function(e,t){x.fn[e]=function(n,r){var i=x.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=x.filter(r,i)),this.length>1&&(ct[e]||(i=x.unique(i)),lt.test(e)&&(i=i.reverse())),this.pushStack(i)}}),x.extend({filter:function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?x.find.matchesSelector(r,e)?[r]:[]:x.find.matches(e,x.grep(t,function(e){return 1===e.nodeType}))},dir:function(e,n,r){var i=[],o=e[n];while(o&&9!==o.nodeType&&(r===t||1!==o.nodeType||!x(o).is(r)))1===o.nodeType&&i.push(o),o=o[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});function ft(e,t,n){if(x.isFunction(t))return x.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return x.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(st.test(t))return x.filter(t,e,n);t=x.filter(t,e)}return x.grep(e,function(e){return x.inArray(e,t)>=0!==n})}function dt(e){var t=ht.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}var ht="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",gt=/ jQuery\d+="(?:null|\d+)"/g,mt=RegExp("<(?:"+ht+")[\\s/>]","i"),yt=/^\s+/,vt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bt=/<([\w:]+)/,xt=/\s*$/g,At={option:[1,""],legend:[1,"
","
"],area:[1,"",""],param:[1,"",""],thead:[1,"","
"],tr:[2,"","
"],col:[2,"","
"],td:[3,"","
"],_default:x.support.htmlSerialize?[0,"",""]:[1,"X
","
"]},jt=dt(a),Dt=jt.appendChild(a.createElement("div"));At.optgroup=At.option,At.tbody=At.tfoot=At.colgroup=At.caption=At.thead,At.th=At.td,x.fn.extend({text:function(e){return x.access(this,function(e){return e===t?x.text(this):this.empty().append((this[0]&&this[0].ownerDocument||a).createTextNode(e))},null,e,arguments.length)},append:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Lt(this,e);t.appendChild(e)}})},prepend:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Lt(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=e?x.filter(e,this):this,i=0;for(;null!=(n=r[i]);i++)t||1!==n.nodeType||x.cleanData(Ft(n)),n.parentNode&&(t&&x.contains(n.ownerDocument,n)&&_t(Ft(n,"script")),n.parentNode.removeChild(n));return this},empty:function(){var e,t=0;for(;null!=(e=this[t]);t++){1===e.nodeType&&x.cleanData(Ft(e,!1));while(e.firstChild)e.removeChild(e.firstChild);e.options&&x.nodeName(e,"select")&&(e.options.length=0)}return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return x.clone(this,e,t)})},html:function(e){return x.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return 1===n.nodeType?n.innerHTML.replace(gt,""):t;if(!("string"!=typeof e||Tt.test(e)||!x.support.htmlSerialize&&mt.test(e)||!x.support.leadingWhitespace&&yt.test(e)||At[(bt.exec(e)||["",""])[1].toLowerCase()])){e=e.replace(vt,"<$1>");try{for(;i>r;r++)n=this[r]||{},1===n.nodeType&&(x.cleanData(Ft(n,!1)),n.innerHTML=e);n=0}catch(o){}}n&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var e=x.map(this,function(e){return[e.nextSibling,e.parentNode]}),t=0;return this.domManip(arguments,function(n){var r=e[t++],i=e[t++];i&&(r&&r.parentNode!==i&&(r=this.nextSibling),x(this).remove(),i.insertBefore(n,r))},!0),t?this:this.remove()},detach:function(e){return this.remove(e,!0)},domManip:function(e,t,n){e=d.apply([],e);var r,i,o,a,s,l,u=0,c=this.length,p=this,f=c-1,h=e[0],g=x.isFunction(h);if(g||!(1>=c||"string"!=typeof h||x.support.checkClone)&&Nt.test(h))return this.each(function(r){var i=p.eq(r);g&&(e[0]=h.call(this,r,i.html())),i.domManip(e,t,n)});if(c&&(l=x.buildFragment(e,this[0].ownerDocument,!1,!n&&this),r=l.firstChild,1===l.childNodes.length&&(l=r),r)){for(a=x.map(Ft(l,"script"),Ht),o=a.length;c>u;u++)i=l,u!==f&&(i=x.clone(i,!0,!0),o&&x.merge(a,Ft(i,"script"))),t.call(this[u],i,u);if(o)for(s=a[a.length-1].ownerDocument,x.map(a,qt),u=0;o>u;u++)i=a[u],kt.test(i.type||"")&&!x._data(i,"globalEval")&&x.contains(s,i)&&(i.src?x._evalUrl(i.src):x.globalEval((i.text||i.textContent||i.innerHTML||"").replace(St,"")));l=r=null}return this}});function Lt(e,t){return x.nodeName(e,"table")&&x.nodeName(1===t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function Ht(e){return e.type=(null!==x.find.attr(e,"type"))+"/"+e.type,e}function qt(e){var t=Et.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function _t(e,t){var n,r=0;for(;null!=(n=e[r]);r++)x._data(n,"globalEval",!t||x._data(t[r],"globalEval"))}function Mt(e,t){if(1===t.nodeType&&x.hasData(e)){var n,r,i,o=x._data(e),a=x._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;i>r;r++)x.event.add(t,n,s[n][r])}a.data&&(a.data=x.extend({},a.data))}}function Ot(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!x.support.noCloneEvent&&t[x.expando]){i=x._data(t);for(r in i.events)x.removeEvent(t,r,i.handle);t.removeAttribute(x.expando)}"script"===n&&t.text!==e.text?(Ht(t).text=e.text,qt(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),x.support.html5Clone&&e.innerHTML&&!x.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&Ct.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):"option"===n?t.defaultSelected=t.selected=e.defaultSelected:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}}x.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){x.fn[e]=function(e){var n,r=0,i=[],o=x(e),a=o.length-1;for(;a>=r;r++)n=r===a?this:this.clone(!0),x(o[r])[t](n),h.apply(i,n.get());return this.pushStack(i)}});function Ft(e,n){var r,o,a=0,s=typeof e.getElementsByTagName!==i?e.getElementsByTagName(n||"*"):typeof e.querySelectorAll!==i?e.querySelectorAll(n||"*"):t;if(!s)for(s=[],r=e.childNodes||e;null!=(o=r[a]);a++)!n||x.nodeName(o,n)?s.push(o):x.merge(s,Ft(o,n));return n===t||n&&x.nodeName(e,n)?x.merge([e],s):s}function Bt(e){Ct.test(e.type)&&(e.defaultChecked=e.checked)}x.extend({clone:function(e,t,n){var r,i,o,a,s,l=x.contains(e.ownerDocument,e);if(x.support.html5Clone||x.isXMLDoc(e)||!mt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(Dt.innerHTML=e.outerHTML,Dt.removeChild(o=Dt.firstChild)),!(x.support.noCloneEvent&&x.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||x.isXMLDoc(e)))for(r=Ft(o),s=Ft(e),a=0;null!=(i=s[a]);++a)r[a]&&Ot(i,r[a]);if(t)if(n)for(s=s||Ft(e),r=r||Ft(o),a=0;null!=(i=s[a]);a++)Mt(i,r[a]);else Mt(e,o);return r=Ft(o,"script"),r.length>0&&_t(r,!l&&Ft(e,"script")),r=s=i=null,o},buildFragment:function(e,t,n,r){var i,o,a,s,l,u,c,p=e.length,f=dt(t),d=[],h=0;for(;p>h;h++)if(o=e[h],o||0===o)if("object"===x.type(o))x.merge(d,o.nodeType?[o]:o);else if(wt.test(o)){s=s||f.appendChild(t.createElement("div")),l=(bt.exec(o)||["",""])[1].toLowerCase(),c=At[l]||At._default,s.innerHTML=c[1]+o.replace(vt,"<$1>")+c[2],i=c[0];while(i--)s=s.lastChild;if(!x.support.leadingWhitespace&&yt.test(o)&&d.push(t.createTextNode(yt.exec(o)[0])),!x.support.tbody){o="table"!==l||xt.test(o)?""!==c[1]||xt.test(o)?0:s:s.firstChild,i=o&&o.childNodes.length;while(i--)x.nodeName(u=o.childNodes[i],"tbody")&&!u.childNodes.length&&o.removeChild(u)}x.merge(d,s.childNodes),s.textContent="";while(s.firstChild)s.removeChild(s.firstChild);s=f.lastChild}else d.push(t.createTextNode(o));s&&f.removeChild(s),x.support.appendChecked||x.grep(Ft(d,"input"),Bt),h=0;while(o=d[h++])if((!r||-1===x.inArray(o,r))&&(a=x.contains(o.ownerDocument,o),s=Ft(f.appendChild(o),"script"),a&&_t(s),n)){i=0;while(o=s[i++])kt.test(o.type||"")&&n.push(o)}return s=null,f},cleanData:function(e,t){var n,r,o,a,s=0,l=x.expando,u=x.cache,c=x.support.deleteExpando,f=x.event.special;for(;null!=(n=e[s]);s++)if((t||x.acceptData(n))&&(o=n[l],a=o&&u[o])){if(a.events)for(r in a.events)f[r]?x.event.remove(n,r):x.removeEvent(n,r,a.handle);u[o]&&(delete u[o],c?delete n[l]:typeof n.removeAttribute!==i?n.removeAttribute(l):n[l]=null,p.push(o))}},_evalUrl:function(e){return x.ajax({url:e,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0}) +}}),x.fn.extend({wrapAll:function(e){if(x.isFunction(e))return this.each(function(t){x(this).wrapAll(e.call(this,t))});if(this[0]){var t=x(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&1===e.firstChild.nodeType)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return x.isFunction(e)?this.each(function(t){x(this).wrapInner(e.call(this,t))}):this.each(function(){var t=x(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=x.isFunction(e);return this.each(function(n){x(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){x.nodeName(this,"body")||x(this).replaceWith(this.childNodes)}).end()}});var Pt,Rt,Wt,$t=/alpha\([^)]*\)/i,It=/opacity\s*=\s*([^)]*)/,zt=/^(top|right|bottom|left)$/,Xt=/^(none|table(?!-c[ea]).+)/,Ut=/^margin/,Vt=RegExp("^("+w+")(.*)$","i"),Yt=RegExp("^("+w+")(?!px)[a-z%]+$","i"),Jt=RegExp("^([+-])=("+w+")","i"),Gt={BODY:"block"},Qt={position:"absolute",visibility:"hidden",display:"block"},Kt={letterSpacing:0,fontWeight:400},Zt=["Top","Right","Bottom","Left"],en=["Webkit","O","Moz","ms"];function tn(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=en.length;while(i--)if(t=en[i]+n,t in e)return t;return r}function nn(e,t){return e=t||e,"none"===x.css(e,"display")||!x.contains(e.ownerDocument,e)}function rn(e,t){var n,r,i,o=[],a=0,s=e.length;for(;s>a;a++)r=e[a],r.style&&(o[a]=x._data(r,"olddisplay"),n=r.style.display,t?(o[a]||"none"!==n||(r.style.display=""),""===r.style.display&&nn(r)&&(o[a]=x._data(r,"olddisplay",ln(r.nodeName)))):o[a]||(i=nn(r),(n&&"none"!==n||!i)&&x._data(r,"olddisplay",i?n:x.css(r,"display"))));for(a=0;s>a;a++)r=e[a],r.style&&(t&&"none"!==r.style.display&&""!==r.style.display||(r.style.display=t?o[a]||"":"none"));return e}x.fn.extend({css:function(e,n){return x.access(this,function(e,n,r){var i,o,a={},s=0;if(x.isArray(n)){for(o=Rt(e),i=n.length;i>s;s++)a[n[s]]=x.css(e,n[s],!1,o);return a}return r!==t?x.style(e,n,r):x.css(e,n)},e,n,arguments.length>1)},show:function(){return rn(this,!0)},hide:function(){return rn(this)},toggle:function(e){var t="boolean"==typeof e;return this.each(function(){(t?e:nn(this))?x(this).show():x(this).hide()})}}),x.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Wt(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":x.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var o,a,s,l=x.camelCase(n),u=e.style;if(n=x.cssProps[l]||(x.cssProps[l]=tn(u,l)),s=x.cssHooks[n]||x.cssHooks[l],r===t)return s&&"get"in s&&(o=s.get(e,!1,i))!==t?o:u[n];if(a=typeof r,"string"===a&&(o=Jt.exec(r))&&(r=(o[1]+1)*o[2]+parseFloat(x.css(e,n)),a="number"),!(null==r||"number"===a&&isNaN(r)||("number"!==a||x.cssNumber[l]||(r+="px"),x.support.clearCloneStyle||""!==r||0!==n.indexOf("background")||(u[n]="inherit"),s&&"set"in s&&(r=s.set(e,r,i))===t)))try{u[n]=r}catch(c){}}},css:function(e,n,r,i){var o,a,s,l=x.camelCase(n);return n=x.cssProps[l]||(x.cssProps[l]=tn(e.style,l)),s=x.cssHooks[n]||x.cssHooks[l],s&&"get"in s&&(a=s.get(e,!0,r)),a===t&&(a=Wt(e,n,i)),"normal"===a&&n in Kt&&(a=Kt[n]),""===r||r?(o=parseFloat(a),r===!0||x.isNumeric(o)?o||0:a):a}}),e.getComputedStyle?(Rt=function(t){return e.getComputedStyle(t,null)},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),l=s?s.getPropertyValue(n)||s[n]:t,u=e.style;return s&&(""!==l||x.contains(e.ownerDocument,e)||(l=x.style(e,n)),Yt.test(l)&&Ut.test(n)&&(i=u.width,o=u.minWidth,a=u.maxWidth,u.minWidth=u.maxWidth=u.width=l,l=s.width,u.width=i,u.minWidth=o,u.maxWidth=a)),l}):a.documentElement.currentStyle&&(Rt=function(e){return e.currentStyle},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),l=s?s[n]:t,u=e.style;return null==l&&u&&u[n]&&(l=u[n]),Yt.test(l)&&!zt.test(n)&&(i=u.left,o=e.runtimeStyle,a=o&&o.left,a&&(o.left=e.currentStyle.left),u.left="fontSize"===n?"1em":l,l=u.pixelLeft+"px",u.left=i,a&&(o.left=a)),""===l?"auto":l});function on(e,t,n){var r=Vt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function an(e,t,n,r,i){var o=n===(r?"border":"content")?4:"width"===t?1:0,a=0;for(;4>o;o+=2)"margin"===n&&(a+=x.css(e,n+Zt[o],!0,i)),r?("content"===n&&(a-=x.css(e,"padding"+Zt[o],!0,i)),"margin"!==n&&(a-=x.css(e,"border"+Zt[o]+"Width",!0,i))):(a+=x.css(e,"padding"+Zt[o],!0,i),"padding"!==n&&(a+=x.css(e,"border"+Zt[o]+"Width",!0,i)));return a}function sn(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=Rt(e),a=x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=Wt(e,t,o),(0>i||null==i)&&(i=e.style[t]),Yt.test(i))return i;r=a&&(x.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+an(e,t,n||(a?"border":"content"),r,o)+"px"}function ln(e){var t=a,n=Gt[e];return n||(n=un(e,t),"none"!==n&&n||(Pt=(Pt||x("