Merge pull request #251 from nextcloud/clientSideEncryptionV4

Client side encryption v4.
This commit is contained in:
Camila Ayres 2018-04-26 19:56:55 +02:00 committed by GitHub
commit 64cbc88474
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
96 changed files with 19178 additions and 176 deletions

View file

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.1)
cmake_minimum_required(VERSION 3.2)
set(CMAKE_CXX_STANDARD 14)
project(client)
@ -183,6 +183,7 @@ if(BUILD_CLIENT)
endif()
find_package(Sphinx)
find_package(PdfLatex)
find_package(OpenSSL REQUIRED VERSION 1.0)
find_package(ZLIB REQUIRED)
find_package(GLib2)

13003
src/3rdparty/nlohmann/json.hpp vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -40,6 +40,10 @@ elseif(UNIX AND NOT APPLE)
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,relro -Wl,-z,now")
endif()
include_directories(
${CMAKE_SOURCE_DIR}/src/3rdparty
)
add_subdirectory(csync)
add_subdirectory(libsync)
if (NOT BUILD_LIBRARIES_ONLY)

View file

@ -78,7 +78,7 @@
namespace OCC {
Q_LOGGING_CATEGORY(lcChecksums, "sync.checksums", QtInfoMsg)
Q_LOGGING_CATEGORY(lcChecksums, "nextcloud.sync.checksums", QtInfoMsg)
QByteArray makeChecksumHeader(const QByteArray &checksumType, const QByteArray &checksum)
{

View file

@ -39,7 +39,7 @@
namespace OCC {
Q_LOGGING_CATEGORY(lcFileSystem, "sync.filesystem", QtInfoMsg)
Q_LOGGING_CATEGORY(lcFileSystem, "nextcloud.sync.filesystem", QtInfoMsg)
QString FileSystem::longWinPath(const QString &inpath)
{

View file

@ -41,7 +41,7 @@
namespace OCC {
Q_LOGGING_CATEGORY(lcSql, "sync.database.sql", QtInfoMsg)
Q_LOGGING_CATEGORY(lcSql, "nextcloud.sync.database.sql", QtInfoMsg)
SqlDatabase::SqlDatabase()
: _db(0)

View file

@ -35,11 +35,11 @@
namespace OCC {
Q_LOGGING_CATEGORY(lcDb, "sync.database", QtInfoMsg)
Q_LOGGING_CATEGORY(lcDb, "nextcloud.sync.database", QtInfoMsg)
#define GET_FILE_RECORD_QUERY \
"SELECT path, inode, modtime, type, md5, fileid, remotePerm, filesize," \
" ignoredChildrenRemote, contentchecksumtype.name || ':' || contentChecksum" \
" ignoredChildrenRemote, contentchecksumtype.name || ':' || contentChecksum, e2eMangledName " \
" FROM metadata" \
" LEFT JOIN checksumtype as contentchecksumtype ON metadata.contentChecksumTypeId == contentchecksumtype.id"
@ -55,6 +55,7 @@ static void fillFileRecordFromGetQuery(SyncJournalFileRecord &rec, SqlQuery &que
rec._fileSize = query.int64Value(7);
rec._serverHasIgnoredFiles = (query.intValue(8) > 0);
rec._checksumHeader = query.baValue(9);
rec._e2eMangledName = query.baValue(10);
}
static QString defaultJournalMode(const QString &dbPath)
@ -546,6 +547,13 @@ bool SyncJournalDb::checkConnect()
return sqlFail("prepare _getFileRecordQuery", *_getFileRecordQuery);
}
_getFileRecordQueryByMangledName.reset(new SqlQuery(_db));
if (_getFileRecordQueryByMangledName->prepare(
GET_FILE_RECORD_QUERY
" WHERE e2eMangledName=?1")) {
return sqlFail("prepare _getFileRecordQueryByMangledName", *_getFileRecordQueryByMangledName);
}
_getFileRecordQueryByInode.reset(new SqlQuery(_db));
if (_getFileRecordQueryByInode->prepare(
GET_FILE_RECORD_QUERY
@ -584,8 +592,8 @@ bool SyncJournalDb::checkConnect()
_setFileRecordQuery.reset(new SqlQuery(_db));
if (_setFileRecordQuery->prepare("INSERT OR REPLACE INTO metadata "
"(phash, pathlen, path, inode, uid, gid, mode, modtime, type, md5, fileid, remotePerm, filesize, ignoredChildrenRemote, contentChecksum, contentChecksumTypeId) "
"VALUES (?1 , ?2, ?3 , ?4 , ?5 , ?6 , ?7, ?8 , ?9 , ?10, ?11, ?12, ?13, ?14, ?15, ?16);")) {
"(phash, pathlen, path, inode, uid, gid, mode, modtime, type, md5, fileid, remotePerm, filesize, ignoredChildrenRemote, contentChecksum, contentChecksumTypeId, e2eMangledName) "
"VALUES (?1 , ?2, ?3 , ?4 , ?5 , ?6 , ?7, ?8 , ?9 , ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17);")) {
return sqlFail("prepare _setFileRecordQuery", *_setFileRecordQuery);
}
@ -746,6 +754,7 @@ void SyncJournalDb::close()
commitTransaction();
_getFileRecordQuery.reset(0);
_getFileRecordQueryByMangledName.reset(0);
_getFileRecordQueryByInode.reset(0);
_getFileRecordQueryByFileId.reset(0);
_getFilesBelowPathQuery.reset(0);
@ -791,7 +800,7 @@ bool SyncJournalDb::updateDatabaseStructure()
bool SyncJournalDb::updateMetadataTableStructure()
{
QStringList columns = tableColumns("metadata");
const QStringList columns = tableColumns("metadata");
bool re = true;
// check if the file_id column is there and create it if not
@ -882,6 +891,16 @@ bool SyncJournalDb::updateMetadataTableStructure()
commitInternal("update database structure: add contentChecksumTypeId col");
}
if (!columns.contains(QLatin1String("e2eMangledName"))) {
SqlQuery query(_db);
query.prepare("ALTER TABLE metadata ADD COLUMN e2eMangledName TEXT;");
if (!query.exec()) {
sqlFail("updateMetadataTableStructure: add e2eMangledName column", query);
re = false;
}
commitInternal("update database structure: add e2eMangledName col");
}
if (!tableColumns("uploadinfo").contains("contentChecksum")) {
SqlQuery query(_db);
query.prepare("ALTER TABLE uploadinfo ADD COLUMN contentChecksum TEXT;");
@ -1007,7 +1026,7 @@ bool SyncJournalDb::setFileRecord(const SyncJournalFileRecord &_record)
qCInfo(lcDb) << "Updating file record for path:" << record._path << "inode:" << record._inode
<< "modtime:" << record._modtime << "type:" << record._type
<< "etag:" << record._etag << "fileId:" << record._fileId << "remotePerm:" << record._remotePerm.toString()
<< "fileSize:" << record._fileSize << "checksum:" << record._checksumHeader;
<< "fileSize:" << record._fileSize << "checksum:" << record._checksumHeader << "e2eMangledName:" << record._e2eMangledName;
qlonglong phash = getPHash(record._path);
if (checkConnect()) {
@ -1040,6 +1059,7 @@ bool SyncJournalDb::setFileRecord(const SyncJournalFileRecord &_record)
_setFileRecordQuery->bindValue(14, record._serverHasIgnoredFiles ? 1 : 0);
_setFileRecordQuery->bindValue(15, checksum);
_setFileRecordQuery->bindValue(16, contentChecksumTypeId);
_setFileRecordQuery->bindValue(17, record._e2eMangledName);
if (!_setFileRecordQuery->exec()) {
return false;
@ -1124,6 +1144,44 @@ bool SyncJournalDb::getFileRecord(const QByteArray &filename, SyncJournalFileRec
return true;
}
bool SyncJournalDb::getFileRecordByE2eMangledName(const QString &mangledName, SyncJournalFileRecord *rec)
{
QMutexLocker locker(&_mutex);
// Reset the output var in case the caller is reusing it.
Q_ASSERT(rec);
rec->_path.clear();
Q_ASSERT(!rec->isValid());
if (_metadataTableIsEmpty)
return true; // no error, yet nothing found (rec->isValid() == false)
if (!checkConnect())
return false;
if (!mangledName.isEmpty()) {
_getFileRecordQueryByMangledName->reset_and_clear_bindings();
_getFileRecordQueryByMangledName->bindValue(1, mangledName);
if (!_getFileRecordQueryByMangledName->exec()) {
close();
return false;
}
if (_getFileRecordQueryByMangledName->next()) {
fillFileRecordFromGetQuery(*rec, *_getFileRecordQueryByMangledName);
} else {
int errId = _getFileRecordQueryByMangledName->errorId();
if (errId != SQLITE_DONE) { // only do this if the problem is different from SQLITE_DONE
QString err = _getFileRecordQueryByMangledName->error();
qCWarning(lcDb) << "No journal entry found for mangled name" << mangledName << "Error: " << err;
close();
}
}
}
return true;
}
bool SyncJournalDb::getFileRecordByInode(quint64 inode, SyncJournalFileRecord *rec)
{
QMutexLocker locker(&_mutex);
@ -1359,6 +1417,7 @@ bool SyncJournalDb::setFileRecordMetadata(const SyncJournalFileRecord &record)
existing._remotePerm = record._remotePerm;
existing._fileSize = record._fileSize;
existing._serverHasIgnoredFiles = record._serverHasIgnoredFiles;
existing._e2eMangledName = record._e2eMangledName;
return setFileRecord(existing);
}

View file

@ -57,6 +57,7 @@ public:
// To verify that the record could be found check with SyncJournalFileRecord::isValid()
bool getFileRecord(const QString &filename, SyncJournalFileRecord *rec) { return getFileRecord(filename.toUtf8(), rec); }
bool getFileRecord(const QByteArray &filename, SyncJournalFileRecord *rec);
bool getFileRecordByE2eMangledName(const QString &mangledName, SyncJournalFileRecord *rec);
bool getFileRecordByInode(quint64 inode, SyncJournalFileRecord *rec);
bool getFileRecordsByFileId(const QByteArray &fileId, const std::function<void(const SyncJournalFileRecord &)> &rowCallback);
bool getFilesBelowPath(const QByteArray &path, const std::function<void(const SyncJournalFileRecord&)> &rowCallback);
@ -138,6 +139,7 @@ public:
void avoidRenamesOnNextSync(const QString &path) { avoidRenamesOnNextSync(path.toUtf8()); }
void avoidRenamesOnNextSync(const QByteArray &path);
void setPollInfo(const PollInfo &);
QVector<PollInfo> getPollInfos();
enum SelectiveSyncListType {
@ -258,6 +260,7 @@ private:
// NOTE! when adding a query, don't forget to reset it in SyncJournalDb::close
QScopedPointer<SqlQuery> _getFileRecordQuery;
QScopedPointer<SqlQuery> _getFileRecordQueryByMangledName;
QScopedPointer<SqlQuery> _getFileRecordQueryByInode;
QScopedPointer<SqlQuery> _getFileRecordQueryByFileId;
QScopedPointer<SqlQuery> _getFilesBelowPathQuery;

View file

@ -64,6 +64,7 @@ public:
RemotePermissions _remotePerm;
bool _serverHasIgnoredFiles;
QByteArray _checksumHeader;
QByteArray _e2eMangledName;
};
bool OCSYNC_EXPORT

View file

@ -58,7 +58,7 @@
namespace OCC {
Q_LOGGING_CATEGORY(lcUtility, "sync.utility", QtInfoMsg)
Q_LOGGING_CATEGORY(lcUtility, "nextcloud.sync.utility", QtInfoMsg)
bool Utility::writeRandomFile(const QString &fname, int size)
{

View file

@ -409,5 +409,6 @@ std::unique_ptr<csync_file_stat_t> csync_file_stat_s::fromSyncJournalFileRecord(
st->size = rec._fileSize;
st->has_ignored_files = rec._serverHasIgnoredFiles;
st->checksumHeader = rec._checksumHeader;
st->e2eMangledName = rec._e2eMangledName;
return st;
}

View file

@ -194,6 +194,7 @@ struct OCSYNC_EXPORT csync_file_stat_s {
// In the remote tree, this will have the server checksum, if available.
// In both cases, the format is "SHA1:baff".
QByteArray checksumHeader;
QByteArray e2eMangledName;
CSYNC_STATUS error_status;

View file

@ -120,6 +120,17 @@ struct OCSYNC_EXPORT csync_s {
auto it = find(key);
return it != end() ? it->second.get() : nullptr;
}
csync_file_stat_t *findFileMangledName(const ByteArrayRef &key) const {
auto it = begin();
while (it != end()) {
csync_file_stat_t *fs = it->second.get();
if (fs->e2eMangledName == key) {
return fs;
}
++it;
}
return nullptr;
}
};
struct {

View file

@ -30,7 +30,7 @@
#include "common/syncjournalfilerecord.h"
#include <QLoggingCategory>
Q_LOGGING_CATEGORY(lcReconcile, "sync.csync.reconciler", QtInfoMsg)
Q_LOGGING_CATEGORY(lcReconcile, "nextcloud.sync.csync.reconciler", QtInfoMsg)
// Needed for PRIu64 on MinGW in C++ mode.
#define __STDC_FORMAT_MACROS
@ -110,7 +110,16 @@ static int _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx) {
break;
}
csync_file_stat_t *other = other_tree->findFile(cur->path);;
csync_file_stat_t *other = other_tree->findFile(cur->path);
if (!other) {
if (ctx->current == REMOTE_REPLICA) {
// The file was not found and the other tree is the local one
// check if the path doesn't match a mangled file name
other = other_tree->findFileMangledName(cur->path);
} else {
other = other_tree->findFile(cur->e2eMangledName);
}
}
if (!other) {
/* Check the renamed path as well. */
@ -318,7 +327,6 @@ static int _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx) {
auto localNode = ctx->current == REMOTE_REPLICA ? other : cur;
remoteNode->instruction = CSYNC_INSTRUCTION_NONE;
localNode->instruction = up._modtime == localNode->modtime ? CSYNC_INSTRUCTION_UPDATE_METADATA : CSYNC_INSTRUCTION_SYNC;
// Update the etag and other server metadata in the journal already
// (We can't use a typical CSYNC_INSTRUCTION_UPDATE_METADATA because
// we must not store the size/modtime from the file system)

View file

@ -52,7 +52,7 @@
#define __STDC_FORMAT_MACROS
#include <inttypes.h>
Q_LOGGING_CATEGORY(lcUpdate, "sync.csync.updater", QtInfoMsg)
Q_LOGGING_CATEGORY(lcUpdate, "nextcloud.sync.csync.updater", QtInfoMsg)
#ifdef NO_RENAME_EXTENSION
/* Return true if the two path have the same extension. false otherwise. */
@ -192,14 +192,38 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> f
return -1;
}
/*
* When file is encrypted it's phash (path hash) will not match the local file phash,
* we could match the e2eMangledName but that might be slow wihout index, and it's
* not UNIQUE at the moment.
*/
if (!base.isValid()) {
if(!ctx->statedb->getFileRecordByE2eMangledName(fs->path, &base)) {
ctx->status_code = CSYNC_STATUS_UNSUCCESSFUL;
return -1;
}
}
if(base.isValid()) { /* there is an entry in the database */
// When the file is loaded from the file system it misses
// the e2e mangled name
if (fs->e2eMangledName.isEmpty() && !base._e2eMangledName.isEmpty()) {
fs->e2eMangledName = base._e2eMangledName;
fs->path = base._path;
}
/* we have an update! */
qCInfo(lcUpdate, "Database entry found, compare: %" PRId64 " <-> %" PRId64
qCInfo(lcUpdate, "Database entry found for %s, compare: %" PRId64 " <-> %" PRId64
", etag: %s <-> %s, inode: %" PRId64 " <-> %" PRId64
", size: %" PRId64 " <-> %" PRId64 ", perms: %x <-> %x, ignore: %d",
", size: %" PRId64 " <-> %" PRId64 ", perms: %x <-> %x, ignore: %d, e2e: %s",
base._path.constData(),
((int64_t) fs->modtime), ((int64_t) base._modtime),
fs->etag.constData(), base._etag.constData(), (uint64_t) fs->inode, (uint64_t) base._inode,
(uint64_t) fs->size, (uint64_t) base._fileSize, *reinterpret_cast<short*>(&fs->remotePerm), *reinterpret_cast<short*>(&base._remotePerm), base._serverHasIgnoredFiles );
(uint64_t) fs->size, (uint64_t) base._fileSize,
*reinterpret_cast<short*>(&fs->remotePerm), *reinterpret_cast<short*>(&base._remotePerm),
base._serverHasIgnoredFiles,
base._e2eMangledName.constData());
if (ctx->current == REMOTE_REPLICA && fs->etag != base._etag) {
fs->instruction = CSYNC_INSTRUCTION_EVAL;
@ -351,7 +375,16 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> f
csync_rename_record(ctx, base._path, fs->path);
}
qCDebug(lcUpdate, "remote rename detected based on fileid %s --> %s", base._path.constData(), fs->path.constData());
/* A remote rename can also mean Encryption Mangled Name.
* if we find one of those in the database, we ignore it.
*/
if (!base._e2eMangledName.isEmpty()) {
qCWarning(lcUpdate, "Encrypted file can not rename");
done = true;
return;
}
qCDebug(lcUpdate, "remote rename detected based on fileid %s --> %s", qPrintable(base._path), qPrintable(fs->path.constData()));
fs->instruction = CSYNC_INSTRUCTION_EVAL_RENAME;
done = true;
};

View file

@ -23,6 +23,8 @@
#include <QSettings>
#include <QDir>
#include <QNetworkAccessManager>
#include <QMessageBox>
#include "clientsideencryption.h"
namespace {
static const char urlC[] = "url";
@ -38,7 +40,7 @@ static const char serverVersionC[] = "serverVersion";
namespace OCC {
Q_LOGGING_CATEGORY(lcAccountManager, "gui.account.manager", QtInfoMsg)
Q_LOGGING_CATEGORY(lcAccountManager, "nextcloud.gui.account.manager", QtInfoMsg)
AccountManager *AccountManager::instance()
{
@ -312,6 +314,9 @@ void AccountManager::deleteAccount(AccountState *account)
auto settings = ConfigFile::settingsWithGroup(QLatin1String(accountsC));
settings->remove(account->account()->id());
// Forget E2E keys
account->account()->e2e()->forgetSensitiveData();
emit accountRemoved(account);
}
@ -321,10 +326,26 @@ AccountPtr AccountManager::createAccount()
acc->setSslErrorHandler(new SslDialogErrorHandler);
connect(acc.data(), &Account::proxyAuthenticationRequired,
ProxyAuthHandler::instance(), &ProxyAuthHandler::handleProxyAuthenticationRequired);
connect(acc.data()->e2e(), &ClientSideEncryption::mnemonicGenerated,
&AccountManager::displayMnemonic);
return acc;
}
void AccountManager::displayMnemonic(const QString& mnemonic)
{
QMessageBox msgBox;
msgBox.setText(tr("All 12 words together make a very strong password, "
"letting only you view and make use of your encrypted files. "
"Please write it down and keep it somewhere safe."));
msgBox.setDetailedText(mnemonic);
msgBox.setWindowTitle(tr("Make note of your 12 word encryption password"));
msgBox.setIcon(QMessageBox::Information);
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.exec();
}
void AccountManager::shutdown()
{
auto accountsCopy = _accounts;

View file

@ -97,6 +97,9 @@ public slots:
/// Saves account state data, not including the account
void saveAccountState(AccountState *a);
/// Display a Box with the mnemonic so the user can copy it to a safe place.
static void displayMnemonic(const QString& mnemonic);
Q_SIGNALS:
void accountAdded(AccountState *account);

View file

@ -33,6 +33,7 @@
#include "creds/httpcredentialsgui.h"
#include "tooltipupdater.h"
#include "filesystem.h"
#include "clientsideencryptionjobs.h"
#include <math.h>
@ -46,6 +47,7 @@
#include <QKeySequence>
#include <QIcon>
#include <QVariant>
#include <QJsonDocument>
#include <QToolTip>
#include <qstringlistmodel.h>
#include <qpropertyanimation.h>
@ -58,7 +60,7 @@
namespace OCC {
Q_LOGGING_CATEGORY(lcAccountSettings, "gui.account.settings", QtInfoMsg)
Q_LOGGING_CATEGORY(lcAccountSettings, "nextcloud.gui.account.settings", QtInfoMsg)
static const char progressBarStyleC[] =
"QProgressBar {"
@ -254,6 +256,206 @@ void AccountSettings::doExpand()
ui->_folderList->expandToDepth(0);
}
void AccountSettings::slotEncryptionFlagSuccess(const QByteArray& fileId)
{
if (auto info = _model->infoForFileId(fileId)) {
accountsState()->account()->e2e()->setFolderEncryptedStatus(info->_path, true);
} else {
qCInfo(lcAccountSettings()) << "Could not get information from the current folder.";
}
auto lockJob = new LockEncryptFolderApiJob(accountsState()->account(), fileId);
connect(lockJob, &LockEncryptFolderApiJob::success,
this, &AccountSettings::slotLockForEncryptionSuccess);
connect(lockJob, &LockEncryptFolderApiJob::error,
this, &AccountSettings::slotLockForEncryptionError);
lockJob->start();
}
void AccountSettings::slotEncryptionFlagError(const QByteArray& fileId, int httpErrorCode)
{
qDebug() << "Error on the encryption flag";
}
void AccountSettings::slotLockForEncryptionSuccess(const QByteArray& fileId, const QByteArray &token)
{
accountsState()->account()->e2e()->setTokenForFolder(fileId, token);
FolderMetadata emptyMetadata(accountsState()->account());
auto encryptedMetadata = emptyMetadata.encryptedMetadata();
if (encryptedMetadata.isEmpty()) {
//TODO: Mark the folder as unencrypted as the metadata generation failed.
QMessageBox::warning(nullptr, "Warning",
"Could not generate the metadata for encryption, Unlocking the folder. \n"
"This can be an issue with your OpenSSL libraries, please note that OpenSSL 1.1 is \n"
"not compatible with Nextcloud yet."
);
return;
}
auto storeMetadataJob = new StoreMetaDataApiJob(accountsState()->account(), fileId, emptyMetadata.encryptedMetadata());
connect(storeMetadataJob, &StoreMetaDataApiJob::success,
this, &AccountSettings::slotUploadMetadataSuccess);
connect(storeMetadataJob, &StoreMetaDataApiJob::error,
this, &AccountSettings::slotUpdateMetadataError);
storeMetadataJob->start();
}
void AccountSettings::slotUploadMetadataSuccess(const QByteArray& folderId)
{
const auto token = accountsState()->account()->e2e()->tokenForFolder(folderId);
auto unlockJob = new UnlockEncryptFolderApiJob(accountsState()->account(), folderId, token);
connect(unlockJob, &UnlockEncryptFolderApiJob::success,
this, &AccountSettings::slotUnlockFolderSuccess);
connect(unlockJob, &UnlockEncryptFolderApiJob::error,
this, &AccountSettings::slotUnlockFolderError);
unlockJob->start();
}
void AccountSettings::slotUpdateMetadataError(const QByteArray& folderId, int httpReturnCode)
{
const auto token = accountsState()->account()->e2e()->tokenForFolder(folderId);
auto unlockJob = new UnlockEncryptFolderApiJob(accountsState()->account(), folderId, token);
connect(unlockJob, &UnlockEncryptFolderApiJob::success,
this, &AccountSettings::slotUnlockFolderSuccess);
connect(unlockJob, &UnlockEncryptFolderApiJob::error,
this, &AccountSettings::slotUnlockFolderError);
unlockJob->start();
}
void AccountSettings::slotLockForEncryptionError(const QByteArray& fileId, int httpErrorCode)
{
qCInfo(lcAccountSettings()) << "Locking error" << httpErrorCode;
}
void AccountSettings::slotUnlockFolderError(const QByteArray& fileId, int httpErrorCode)
{
qCInfo(lcAccountSettings()) << "Unlocking error!";
}
void AccountSettings::slotUnlockFolderSuccess(const QByteArray& fileId)
{
qCInfo(lcAccountSettings()) << "Unlocking success!";
}
void AccountSettings::slotMarkSubfolderEncrpted(const QByteArray& fileId)
{
auto job = new OCC::SetEncryptionFlagApiJob(accountsState()->account(), fileId);
connect(job, &OCC::SetEncryptionFlagApiJob::success, this, &AccountSettings::slotEncryptionFlagSuccess);
connect(job, &OCC::SetEncryptionFlagApiJob::error, this, &AccountSettings::slotEncryptionFlagError);
job->start();
}
// Order:
// 1 - Lock folder,
// 2 - Delete Metadata,
// 3 - Unlock Folder,
// 4 - Mark as Decrypted.
void AccountSettings::slotMarkSubfolderDecrypted(const QByteArray& fileId)
{
qDebug() << "Starting to mark as decrypted";
qDebug() << "Locking the folder";
auto lockJob = new LockEncryptFolderApiJob(accountsState()->account(), fileId);
connect(lockJob, &LockEncryptFolderApiJob::success,
this, &AccountSettings::slotLockForDecryptionSuccess);
connect(lockJob, &LockEncryptFolderApiJob::error,
this, &AccountSettings::slotLockForDecryptionError);
lockJob->start();
}
void AccountSettings::slotLockForDecryptionSuccess(const QByteArray& fileId, const QByteArray& token)
{
qDebug() << "Locking success, trying to delete the metadata";
accountsState()->account()->e2e()->setTokenForFolder(fileId, token);
auto job = new DeleteMetadataApiJob(accountsState()->account(), fileId);
connect(job, &DeleteMetadataApiJob::success,
this, &AccountSettings::slotDeleteMetadataSuccess);
connect(job, &DeleteMetadataApiJob::error,
this, &AccountSettings::slotDeleteMetadataError);
job->start();
}
void AccountSettings::slotDeleteMetadataSuccess(const QByteArray& fileId)
{
qDebug() << "Metadata successfully deleted, unlocking the folder";
auto token = accountsState()->account()->e2e()->tokenForFolder(fileId);
auto job = new UnlockEncryptFolderApiJob(accountsState()->account(), fileId, token);
connect(job, &UnlockEncryptFolderApiJob::success,
this, &AccountSettings::slotUnlockForDecryptionSuccess);
connect(job, &UnlockEncryptFolderApiJob::error,
this, &AccountSettings::slotUnlockForDecryptionError);
job->start();
}
void AccountSettings::slotUnlockForDecryptionSuccess(const QByteArray& fileId)
{
qDebug() << "Unlocked the folder successfully, removing the encrypted bit.";
auto job = new OCC::DeleteApiJob(accountsState()->account(),
"ocs/v2.php/apps/end_to_end_encryption/api/v1/encrypted/" + QString(fileId));
// This Delete ApiJob is different than all other jobs used here, sigh.
connect(job, &OCC::DeleteApiJob::result, [this, &fileId](int httpResponse) {
if (httpResponse == 200) {
slotDecryptionFlagSuccess(fileId);
} else {
slotDecryptionFlagError(fileId, httpResponse);
}
});
job->start();
}
void AccountSettings::slotDecryptionFlagSuccess(const QByteArray& fileId)
{
if (auto info = _model->infoForFileId(fileId)) {
accountsState()->account()->e2e()->setFolderEncryptedStatus(info->_path, false);
} else {
qCInfo(lcAccountSettings()) << "Could not get information for the current path.";
}
}
void AccountSettings::slotDecryptionFlagError(const QByteArray& fileID, int httpReturnCode)
{
qDebug() << "Error Setting the Decryption Flag";
}
void AccountSettings::slotUnlockForDecryptionError(const QByteArray& fileId, int httpReturnCode)
{
qDebug() << "Error unlocking folder after decryption";
}
void AccountSettings::slotDeleteMetadataError(const QByteArray& fileId, int httpReturnCode)
{
qDebug() << "Error deleting the metadata";
}
void AccountSettings::slotLockForDecryptionError(const QByteArray& fileId, int httpReturnCode)
{
qDebug() << "Error Locking for decryption";
}
void AccountSettings::slotSubfolderContextMenuRequested(const QModelIndex& index, const QPoint& pos)
{
QMenu menu;
auto ac = menu.addAction(tr("Open folder"));
connect(ac, &QAction::triggered, this, &AccountSettings::slotOpenCurrentLocalSubFolder);
auto fileName = _model->data(index, FolderStatusDelegate::FolderPathRole).toString();
if (!QFile::exists(fileName)) {
ac->setEnabled(false);
}
auto info = _model->infoForIndex(index);
auto acc = _accountState->account();
if (acc->capabilities().clientSideEncryptionAvaliable()) {
bool isEncrypted = acc->e2e()->isFolderEncrypted(info->_path);
ac = menu.addAction( isEncrypted ? tr("Decrypt") : tr("Encrypt"));
if (not isEncrypted) {
connect(ac, &QAction::triggered, [this, &info] { slotMarkSubfolderEncrpted(info->_fileId); });
} else {
connect(ac, &QAction::triggered, [this, &info] { slotMarkSubfolderDecrypted(info->_fileId); });
}
}
menu.exec(QCursor::pos());
}
void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos)
{
QTreeView *tv = ui->_folderList;
@ -263,18 +465,7 @@ void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos)
}
if (_model->classify(index) == FolderStatusModel::SubFolder) {
QMenu *menu = new QMenu(tv);
menu->setAttribute(Qt::WA_DeleteOnClose);
QAction *ac = menu->addAction(tr("Open folder"));
connect(ac, &QAction::triggered, this, &AccountSettings::slotOpenCurrentLocalSubFolder);
QString fileName = _model->data(index, FolderStatusDelegate::FolderPathRole).toString();
if (!QFile::exists(fileName)) {
ac->setEnabled(false);
}
menu->popup(tv->mapToGlobal(pos));
slotSubfolderContextMenuRequested(index, pos);
return;
}
@ -711,6 +902,15 @@ void AccountSettings::slotAccountStateChanged()
_toggleSignInOutAction->setText(tr("Log out"));
}
}
if (state == AccountState::State::Connected) {
/* TODO: We should probably do something better here.
* Verify if the user has a private key already uploaded to the server,
* if it has, do not offer to create one.
*/
qCInfo(lcAccountSettings) << "Accout" << accountsState()->account()->displayName()
<< "Client Side Encryption" << accountsState()->account()->capabilities().clientSideEncryptionAvaliable();
}
}
void AccountSettings::slotLinkActivated(const QString &link)

View file

@ -85,11 +85,34 @@ protected slots:
void slotOpenAccountWizard();
void slotAccountAdded(AccountState *);
void refreshSelectiveSyncStatus();
void slotMarkSubfolderEncrpted(const QByteArray& fileId);
void slotMarkSubfolderDecrypted(const QByteArray& fileId);
void slotSubfolderContextMenuRequested(const QModelIndex& idx, const QPoint& point);
void slotCustomContextMenuRequested(const QPoint &);
void slotFolderListClicked(const QModelIndex &indx);
void doExpand();
void slotLinkActivated(const QString &link);
// Encryption Related Stuff.
void slotEncryptionFlagSuccess(const QByteArray &folderId);
void slotEncryptionFlagError(const QByteArray &folderId, int httpReturnCode);
void slotLockForEncryptionSuccess(const QByteArray& folderId, const QByteArray& token);
void slotLockForEncryptionError(const QByteArray &folderId, int httpReturnCode);
void slotUnlockFolderSuccess(const QByteArray& folderId);
void slotUnlockFolderError(const QByteArray& folderId, int httpReturnCode);
void slotUploadMetadataSuccess(const QByteArray& folderId);
void slotUpdateMetadataError(const QByteArray& folderId, int httpReturnCode);
// Remove Encryotion Bit.
void slotLockForDecryptionSuccess(const QByteArray& folderId, const QByteArray& token);
void slotLockForDecryptionError(const QByteArray& folderId, int httpReturnCode);
void slotDeleteMetadataSuccess(const QByteArray& folderId);
void slotDeleteMetadataError(const QByteArray& folderId, int httpReturnCode);
void slotUnlockForDecryptionSuccess(const QByteArray& folderId);
void slotUnlockForDecryptionError(const QByteArray& folderId, int httpReturnCode);
void slotDecryptionFlagSuccess(const QByteArray& folderId);
void slotDecryptionFlagError(const QByteArray& folderId, int httpReturnCode);
private:
void showConnectionLabel(const QString &message,
QStringList errors = QStringList());

View file

@ -26,7 +26,7 @@
namespace OCC {
Q_LOGGING_CATEGORY(lcAccountState, "gui.account.state", QtInfoMsg)
Q_LOGGING_CATEGORY(lcAccountState, "nextcloud.gui.account.state", QtInfoMsg)
AccountState::AccountState(AccountPtr account)
: QObject()

View file

@ -31,7 +31,7 @@
namespace OCC {
Q_LOGGING_CATEGORY(lcActivity, "gui.activity", QtInfoMsg)
Q_LOGGING_CATEGORY(lcActivity, "nextcloud.gui.activity", QtInfoMsg)
ActivityListModel::ActivityListModel(QWidget *parent)
: QAbstractListModel(parent)

View file

@ -57,7 +57,7 @@ class QSocket;
namespace OCC {
Q_LOGGING_CATEGORY(lcApplication, "gui.application", QtInfoMsg)
Q_LOGGING_CATEGORY(lcApplication, "nextcloud.gui.application", QtInfoMsg)
namespace {

View file

@ -24,7 +24,7 @@
namespace OCC {
Q_LOGGING_CATEGORY(lcGuiCredentials, "gui.credentials", QtInfoMsg)
Q_LOGGING_CATEGORY(lcGuiCredentials, "nextcloud.gui.credentials", QtInfoMsg)
namespace CredentialsFactory {

View file

@ -30,7 +30,7 @@ using namespace QKeychain;
namespace OCC {
Q_LOGGING_CATEGORY(lcHttpCredentialsGui, "sync.credentials.http.gui", QtInfoMsg)
Q_LOGGING_CATEGORY(lcHttpCredentialsGui, "nextcloud.sync.credentials.http.gui", QtInfoMsg)
void HttpCredentialsGui::askFromUser()
{
@ -139,22 +139,21 @@ void HttpCredentialsGui::showDialog()
QString HttpCredentialsGui::requestAppPasswordText(const Account *account)
{
int version = account->serverVersionInt();
QString path;
auto url = account->url().toString();
if (url.endsWith('/'))
url.chop(1);
if (version >= Account::makeServerVersion(13, 0, 0)) {
path = QLatin1String("index.php/settings/user/security");
url += QLatin1String("/index.php/settings/user/security");
} else if (version >= Account::makeServerVersion(12, 0, 0)) {
path = QLatin1String("index.php/settings/personal#security");
url += QLatin1String("/index.php/settings/personal#security");
} else if (version >= Account::makeServerVersion(11, 0, 0)) {
path = QLatin1String("index.php/settings/personal#apppasswords");
url += QLatin1String("/index.php/settings/personal#apppasswords");
} else {
return QString();
}
auto baseUrl = account->url().toString();
if (baseUrl.endsWith('/'))
baseUrl.chop(1);
return tr("<a href=\"%1\">Click here</a> to request an app password from the web interface.")
.arg(baseUrl + path);
.arg(url);
}
} // namespace OCC

View file

@ -25,7 +25,7 @@
namespace OCC {
Q_LOGGING_CATEGORY(lcOauth, "sync.credentials.oauth", QtInfoMsg)
Q_LOGGING_CATEGORY(lcOauth, "nextcloud.sync.credentials.oauth", QtInfoMsg)
OAuth::~OAuth()
{

View file

@ -39,7 +39,7 @@ using namespace QKeychain;
namespace OCC {
Q_LOGGING_CATEGORY(lcShibboleth, "gui.credentials.shibboleth", QtInfoMsg)
Q_LOGGING_CATEGORY(lcShibboleth, "nextcloud.gui.credentials.shibboleth", QtInfoMsg)
namespace {

View file

@ -43,7 +43,7 @@
namespace OCC {
Q_LOGGING_CATEGORY(lcFolder, "gui.folder", QtInfoMsg)
Q_LOGGING_CATEGORY(lcFolder, "nextcloud.gui.folder", QtInfoMsg)
Folder::Folder(const FolderDefinition &definition,
AccountState *accountState,

View file

@ -65,6 +65,8 @@ public:
bool paused;
/// whether the folder syncs hidden files
bool ignoreHiddenFiles;
/// the folder has client side encryption
bool isClientSideEncrypted;
/// The CLSID where this folder appears in registry for the Explorer navigation pane entry.
QUuid navigationPaneClsid;

View file

@ -40,7 +40,7 @@
namespace OCC {
Q_LOGGING_CATEGORY(lcFolderMan, "gui.folder.manager", QtInfoMsg)
Q_LOGGING_CATEGORY(lcFolderMan, "nextcloud.gui.folder.manager", QtInfoMsg)
FolderMan *FolderMan::_instance = 0;

View file

@ -28,7 +28,7 @@ Q_DECLARE_METATYPE(QPersistentModelIndex)
namespace OCC {
Q_LOGGING_CATEGORY(lcFolderStatus, "gui.folder.model", QtInfoMsg)
Q_LOGGING_CATEGORY(lcFolderStatus, "nextcloud.gui.folder.model", QtInfoMsg)
static const char propertyParentIndexC[] = "oc_parentIndex";
static const char propertyPermissionMap[] = "oc_permissionMap";
@ -46,6 +46,7 @@ FolderStatusModel::FolderStatusModel(QObject *parent)
, _accountState(0)
, _dirty(false)
{
}
FolderStatusModel::~FolderStatusModel()
@ -163,6 +164,8 @@ QVariant FolderStatusModel::data(const QModelIndex &index, int role) const
return QColor(Qt::red);
}
break;
case FileIdRole:
return x._fileId;
case FolderStatusDelegate::FolderPathRole: {
auto f = x._folder;
if (!f)
@ -393,6 +396,24 @@ FolderStatusModel::SubFolderInfo *FolderStatusModel::infoForIndex(const QModelIn
}
}
/* Recursivelly traverse the file info looking for the id */
FolderStatusModel::SubFolderInfo *FolderStatusModel::infoForFileId(const QByteArray& fileId, SubFolderInfo* info) const
{
const QVector<SubFolderInfo>& infoVec = info ? info->_subs : _folders;
for(int i = 0, end = infoVec.size(); i < end; i++) {
auto *info = const_cast<SubFolderInfo *>(&infoVec[i]);
if (info->_fileId == fileId) {
return info;
} else if (info->_subs.size()) {
if (auto *subInfo = infoForFileId(fileId, info)) {
return subInfo;
}
}
}
return nullptr;
}
QModelIndex FolderStatusModel::indexForPath(Folder *f, const QString &path) const
{
if (!f) {
@ -551,10 +572,19 @@ void FolderStatusModel::fetchMore(const QModelIndex &parent)
}
path += info->_path;
}
//TODO: This is the correct place, but this doesn't seems to be the right
// Way to call fetchFolderEncryptedStatus.
if (_accountState->account()->capabilities().clientSideEncryptionAvaliable()) {
_accountState->account()->e2e()->fetchFolderEncryptedStatus();
}
LsColJob *job = new LsColJob(_accountState->account(), path, this);
job->setProperties(QList<QByteArray>() << "resourcetype"
<< "http://owncloud.org/ns:size"
<< "http://owncloud.org/ns:permissions");
<< "http://owncloud.org/ns:permissions"
<< "http://owncloud.org/ns:fileid");
job->setTimeout(60 * 1000);
connect(job, &LsColJob::directoryListingSubfolders,
this, &FolderStatusModel::slotUpdateDirectories);
@ -655,11 +685,13 @@ void FolderStatusModel::slotUpdateDirectories(const QStringList &list)
newInfo._folder = parentInfo->_folder;
newInfo._pathIdx = parentInfo->_pathIdx;
newInfo._pathIdx << newSubs.size();
newInfo._size = job->_sizes.value(path);
newInfo._isExternal = permissionMap.value(removeTrailingSlash(path)).toString().contains("M");
newInfo._path = relativePath;
newInfo._name = removeTrailingSlash(relativePath).split('/').last();
const auto& folderInfo = job->_folderInfos.value(path);
newInfo._size = folderInfo.size;
newInfo._fileId = folderInfo.fileId;
if (relativePath.isEmpty())
continue;

View file

@ -37,6 +37,8 @@ class FolderStatusModel : public QAbstractItemModel
{
Q_OBJECT
public:
enum {FileIdRole = Qt::UserRole+1};
FolderStatusModel(QObject *parent = 0);
~FolderStatusModel();
void setAccountState(const AccountState *accountState);
@ -79,9 +81,9 @@ public:
bool _hasError; // If the last fetching job ended in an error
QString _lastErrorString;
bool _fetchingLabel; // Whether a 'fetching in progress' label is shown.
// undecided folders are the big folders that the user has not accepted yet
bool _isUndecided;
QByteArray _fileId; // the file id for this folder on the server.
Qt::CheckState _checked;
@ -118,7 +120,7 @@ public:
FetchLabel };
ItemType classify(const QModelIndex &index) const;
SubFolderInfo *infoForIndex(const QModelIndex &index) const;
SubFolderInfo *infoForFileId(const QByteArray &fileId, SubFolderInfo *info = nullptr) const;
// If the selective sync check boxes were changed
bool isDirty() { return _dirty; }
@ -168,7 +170,6 @@ signals:
// Tell the view that this item should be expanded because it has an undecided item
void suggestExpand(const QModelIndex &);
friend struct SubFolderInfo;
};

View file

@ -36,7 +36,7 @@
namespace OCC {
Q_LOGGING_CATEGORY(lcFolderWatcher, "gui.folderwatcher", QtInfoMsg)
Q_LOGGING_CATEGORY(lcFolderWatcher, "nextcloud.gui.folderwatcher", QtInfoMsg)
FolderWatcher::FolderWatcher(const QString &root, Folder *folder)
: QObject(folder)

View file

@ -23,7 +23,7 @@
using namespace OCC;
Q_LOGGING_CATEGORY(lcUtility, "gui.utility", QtInfoMsg)
Q_LOGGING_CATEGORY(lcUtility, "nextcloud.gui.utility", QtInfoMsg)
bool Utility::openBrowser(const QUrl &url, QWidget *errorWidgetParent)
{

View file

@ -20,7 +20,7 @@
using namespace OCC;
Q_LOGGING_CATEGORY(lcLockWatcher, "gui.lockwatcher", QtInfoMsg)
Q_LOGGING_CATEGORY(lcLockWatcher, "nextcloud.gui.lockwatcher", QtInfoMsg)
static const int check_frequency = 20 * 1000; // ms

View file

@ -21,6 +21,9 @@
#include <sys/resource.h>
#endif
#include <openssl/conf.h>
#include <openssl/err.h>
#include "application.h"
#include "theme.h"
#include "common/utility.h"
@ -30,6 +33,7 @@
#include <QTimer>
#include <QMessageBox>
#include <QDebug>
using namespace OCC;
@ -47,6 +51,11 @@ int main(int argc, char **argv)
{
Q_INIT_RESOURCE(client);
/* Initialise the library */
ERR_load_crypto_strings();
OpenSSL_add_all_algorithms();
OPENSSL_config(NULL);
#ifdef Q_OS_WIN
// If the font size ratio is set on Windows, we need to
// enable the auto pixelRatio in Qt since we don't

View file

@ -22,7 +22,7 @@
namespace OCC {
Q_LOGGING_CATEGORY(lcNavPane, "gui.folder.navigationpane", QtInfoMsg)
Q_LOGGING_CATEGORY(lcNavPane, "nextcloud.gui.folder.navigationpane", QtInfoMsg)
NavigationPaneHelper::NavigationPaneHelper(FolderMan *folderMan)
: _folderMan(folderMan)

View file

@ -24,7 +24,7 @@
namespace OCC {
Q_LOGGING_CATEGORY(lcNotifications, "gui.notifications", QtInfoMsg)
Q_LOGGING_CATEGORY(lcNotifications, "nextcloud.gui.notifications", QtInfoMsg)
NotificationWidget::NotificationWidget(QWidget *parent)
: QWidget(parent)

View file

@ -22,7 +22,7 @@
namespace OCC {
Q_LOGGING_CATEGORY(lcOcs, "gui.sharing.ocs", QtInfoMsg)
Q_LOGGING_CATEGORY(lcOcs, "nextcloud.gui.sharing.ocs", QtInfoMsg)
OcsJob::OcsJob(AccountPtr account)
: AbstractNetworkJob(account, "")

View file

@ -25,7 +25,7 @@
using namespace OCC;
Q_LOGGING_CATEGORY(lcProxy, "gui.credentials.proxy", QtInfoMsg)
Q_LOGGING_CATEGORY(lcProxy, "nextcloud.gui.credentials.proxy", QtInfoMsg)
ProxyAuthHandler *ProxyAuthHandler::instance()
{

View file

@ -237,7 +237,7 @@ void SelectiveSyncWidget::slotUpdateDirectories(QStringList list)
root->setIcon(0, Theme::instance()->applicationIcon());
root->setData(0, Qt::UserRole, QString());
root->setCheckState(0, Qt::Checked);
qint64 size = job ? job->_sizes.value(pathToRemove, -1) : -1;
qint64 size = job ? job->_folderInfos[pathToRemove].size : -1;
if (size >= 0) {
root->setText(1, Utility::octetsToString(size));
root->setData(1, Qt::UserRole, size);
@ -246,7 +246,7 @@ void SelectiveSyncWidget::slotUpdateDirectories(QStringList list)
Utility::sortFilenames(list);
foreach (QString path, list) {
auto size = job ? job->_sizes.value(path) : 0;
auto size = job ? job->_folderInfos[path].size : 0;
path.remove(pathToRemove);
QStringList paths = path.split('/');
if (paths.last().isEmpty())

View file

@ -22,7 +22,7 @@
namespace OCC {
Q_LOGGING_CATEGORY(lcServerNotification, "gui.servernotification", QtInfoMsg)
Q_LOGGING_CATEGORY(lcServerNotification, "nextcloud.gui.servernotification", QtInfoMsg)
const QString notificationsPath = QLatin1String("ocs/v2.php/apps/notifications/api/v2/notifications");
const char propertyAccountStateC[] = "oc_account_state";

View file

@ -21,7 +21,7 @@
namespace OCC {
Q_LOGGING_CATEGORY(lcSharing, "gui.sharing", QtInfoMsg)
Q_LOGGING_CATEGORY(lcSharing, "nextcloud.gui.sharing", QtInfoMsg)
Sharee::Sharee(const QString shareWith,
const QString displayName,

View file

@ -81,7 +81,7 @@ static QString buildMessage(const QString &verb, const QString &path, const QStr
namespace OCC {
Q_LOGGING_CATEGORY(lcSocketApi, "gui.socketapi", QtInfoMsg)
Q_LOGGING_CATEGORY(lcSocketApi, "nextcloud.gui.socketapi", QtInfoMsg)
class BloomFilter
{

View file

@ -26,7 +26,7 @@
namespace OCC {
Q_LOGGING_CATEGORY(lcSsl, "gui.ssl", QtInfoMsg)
Q_LOGGING_CATEGORY(lcSsl, "nextcloud.gui.ssl", QtInfoMsg)
SslButton::SslButton(QWidget *parent)
: QToolButton(parent)

View file

@ -23,7 +23,7 @@
namespace OCC {
Q_LOGGING_CATEGORY(lcSslErrorDialog, "gui.sslerrordialog", QtInfoMsg)
Q_LOGGING_CATEGORY(lcSslErrorDialog, "nextcloud.gui.sslerrordialog", QtInfoMsg)
namespace Utility {
// Used for QSSLCertificate::subjectInfo which returns a QStringList in Qt5, but a QString in Qt4

View file

@ -28,7 +28,7 @@
namespace OCC {
Q_LOGGING_CATEGORY(lcUpdater, "gui.updater", QtInfoMsg)
Q_LOGGING_CATEGORY(lcUpdater, "nextcloud.gui.updater", QtInfoMsg)
Updater *Updater::_instance = 0;

View file

@ -36,7 +36,7 @@
namespace OCC {
Q_LOGGING_CATEGORY(lcWizard, "gui.wizard", QtInfoMsg)
Q_LOGGING_CATEGORY(lcWizard, "nextcloud.gui.wizard", QtInfoMsg)
OwncloudWizard::OwncloudWizard(QWidget *parent)
: QWizard(parent)

View file

@ -1,7 +1,6 @@
project(libsync)
set(CMAKE_AUTOMOC TRUE)
if ( APPLE )
list(APPEND OS_SPECIFIC_LINK_LIBRARIES
/System/Library/Frameworks/CoreServices.framework
@ -22,6 +21,7 @@ endif()
set(libsync_SRCS
account.cpp
wordlist.cpp
bandwidthmanager.cpp
capabilities.cpp
clientproxy.cpp
@ -43,14 +43,19 @@ set(libsync_SRCS
propagateuploadv1.cpp
propagateuploadng.cpp
propagateremotedelete.cpp
propagateremotedeleteencrypted.cpp
propagateremotemove.cpp
propagateremotemkdir.cpp
propagateuploadencrypted.cpp
propagatedownloadencrypted.cpp
syncengine.cpp
syncfileitem.cpp
syncfilestatus.cpp
syncfilestatustracker.cpp
syncresult.cpp
theme.cpp
clientsideencryption.cpp
clientsideencryptionjobs.cpp
creds/dummycredentials.cpp
creds/abstractcredentials.cpp
creds/credentialscommon.cpp
@ -92,6 +97,8 @@ ENDIF(NOT APPLE)
add_library(${synclib_NAME} SHARED ${libsync_SRCS})
target_link_libraries(${synclib_NAME}
ocsync
OpenSSL::Crypto
OpenSSL::SSL
${OS_SPECIFIC_LINK_LIBRARIES}
Qt5::Core Qt5::Network
)

View file

@ -38,7 +38,7 @@ Q_DECLARE_METATYPE(QTimer *)
namespace OCC {
Q_LOGGING_CATEGORY(lcNetworkJob, "sync.networkjob", QtInfoMsg)
Q_LOGGING_CATEGORY(lcNetworkJob, "nextcloud.sync.networkjob", QtInfoMsg)
// If not set, it is overwritten by the Application constructor with the value from the config
int AbstractNetworkJob::httpTimeout = qEnvironmentVariableIntValue("OWNCLOUD_TIMEOUT");

View file

@ -29,7 +29,7 @@
namespace OCC {
Q_LOGGING_CATEGORY(lcAccessManager, "sync.accessmanager", QtInfoMsg)
Q_LOGGING_CATEGORY(lcAccessManager, "nextcloud.sync.accessmanager", QtInfoMsg)
AccessManager::AccessManager(QObject *parent)
: QNetworkAccessManager(parent)

View file

@ -15,19 +15,21 @@
#include "account.h"
#include "cookiejar.h"
#include "networkjobs.h"
#include "configfile.h"
#include "accessmanager.h"
#include "creds/abstractcredentials.h"
#include "capabilities.h"
#include "theme.h"
#include "common/asserts.h"
#include <QSettings>
#include "common/asserts.h"
#include "clientsideencryption.h"
#include <QLoggingCategory>
#include <QMutex>
#include <QNetworkReply>
#include <QNetworkAccessManager>
#include <QSslSocket>
#include <QNetworkCookieJar>
#include <QFileInfo>
#include <QDir>
#include <QSslKey>
@ -36,7 +38,7 @@
namespace OCC {
Q_LOGGING_CATEGORY(lcAccount, "sync.account", QtInfoMsg)
Q_LOGGING_CATEGORY(lcAccount, "nextcloud.sync.account", QtInfoMsg)
Account::Account(QObject *parent)
: QObject(parent)
@ -50,9 +52,19 @@ AccountPtr Account::create()
{
AccountPtr acc = AccountPtr(new Account);
acc->setSharedThis(acc);
//TODO: This probably needs to have a better
// coupling, but it should work for now.
acc->e2e()->setAccount(acc);
return acc;
}
ClientSideEncryption* Account::e2e()
{
// Qt expects everything in the connect to be a pointer, so return a pointer.
return &_e2e;
}
Account::~Account()
{
}
@ -491,5 +503,4 @@ void Account::setNonShib(bool nonShib)
}
}
} // namespace OCC

View file

@ -34,6 +34,7 @@
#include "common/utility.h"
#include <memory>
#include "capabilities.h"
#include "clientsideencryption.h"
class QSettings;
class QNetworkReply;
@ -233,6 +234,8 @@ public:
/// Called by network jobs on credential errors, emits invalidCredentials()
void handleInvalidCredentials();
ClientSideEncryption* e2e();
public slots:
/// Used when forgetting credentials
void clearQNAMCache();
@ -301,6 +304,8 @@ private:
static QString _configFileName;
QString _davPath; // defaults to value from theme, might be overwritten in brandings
ClientSideEncryption _e2e;
friend class AccountManager;
};
}

View file

@ -29,7 +29,7 @@
namespace OCC {
Q_LOGGING_CATEGORY(lcBandwidthManager, "sync.bandwidthmanager", QtInfoMsg)
Q_LOGGING_CATEGORY(lcBandwidthManager, "nextcloud.sync.bandwidthmanager", QtInfoMsg)
// Because of the many layers of buffering inside Qt (and probably the OS and the network)
// we cannot lower this value much more. If we do, the estimated bw will be very high

View file

@ -15,10 +15,14 @@
#include "capabilities.h"
#include <QVariantMap>
#include <QLoggingCategory>
#include <QDebug>
namespace OCC {
Q_LOGGING_CATEGORY(lcServerCapabilities, "nextcloud.sync.server.capabilities", QtInfoMsg)
Capabilities::Capabilities(const QVariantMap &capabilities)
: _capabilities(capabilities)
@ -80,6 +84,14 @@ bool Capabilities::shareResharing() const
return _capabilities["files_sharing"].toMap()["resharing"].toBool();
}
bool Capabilities::clientSideEncryptionAvaliable() const
{
auto it = _capabilities.constFind(QStringLiteral("end-to-end-encryption"));
if (it != _capabilities.constEnd())
return (*it).toMap().value(QStringLiteral("enabled"), false).toBool();
return false;
}
bool Capabilities::notificationsAvailable() const
{
// We require the OCS style API in 9.x, can't deal with the REST one only found in 8.2

View file

@ -53,6 +53,9 @@ public:
/// returns true if the capabilities report notifications
bool notificationsAvailable() const;
/// returns true if the server supports client side encryption
bool clientSideEncryptionAvaliable() const;
/// returns true if the capabilities are loaded already.
bool isValid() const;

View file

@ -21,7 +21,7 @@
namespace OCC {
Q_LOGGING_CATEGORY(lcClientProxy, "sync.clientproxy", QtInfoMsg)
Q_LOGGING_CATEGORY(lcClientProxy, "nextcloud.sync.clientproxy", QtInfoMsg)
ClientProxy::ClientProxy(QObject *parent)
: QObject(parent)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,169 @@
#ifndef CLIENTSIDEENCRYPTION_H
#define CLIENTSIDEENCRYPTION_H
#include <QString>
#include <QObject>
#include <QJsonDocument>
#include <QSslCertificate>
#include <QSslKey>
#include <QFile>
#include <QVector>
#include <QMap>
#include <openssl/rsa.h>
#include <openssl/evp.h>
#include "accountfwd.h"
#include "networkjobs.h"
namespace QKeychain {
class Job;
class WritePasswordJob;
class ReadPasswordJob;
}
namespace OCC {
QString baseUrl();
namespace EncryptionHelper {
QByteArray generateRandomFilename();
QByteArray generateRandom(int size);
QByteArray generatePassword(const QString &wordlist, const QByteArray& salt);
QByteArray encryptPrivateKey(
const QByteArray& key,
const QByteArray& privateKey,
const QByteArray &salt
);
QByteArray decryptPrivateKey(
const QByteArray& key,
const QByteArray& data
);
QByteArray encryptStringSymmetric(
const QByteArray& key,
const QByteArray& data
);
QByteArray decryptStringSymmetric(
const QByteArray& key,
const QByteArray& data
);
QByteArray privateKeyToPem(const QSslKey key);
//TODO: change those two EVP_PKEY into QSslKey.
QByteArray encryptStringAsymmetric(
EVP_PKEY *publicKey,
const QByteArray& data
);
QByteArray decryptStringAsymmetric(
EVP_PKEY *privateKey,
const QByteArray& data
);
bool fileEncryption(const QByteArray &key, const QByteArray &iv,
QFile *input, QFile *output, QByteArray& returnTag);
bool fileDecryption(const QByteArray &key, const QByteArray& iv,
QFile *input, QFile *output);
}
class OWNCLOUDSYNC_EXPORT ClientSideEncryption : public QObject {
Q_OBJECT
public:
ClientSideEncryption();
void initialize();
void setAccount(AccountPtr account);
bool hasPrivateKey() const;
bool hasPublicKey() const;
void generateKeyPair();
void generateCSR(EVP_PKEY *keyPair);
void encryptPrivateKey();
void setTokenForFolder(const QByteArray& folder, const QByteArray& token);
QByteArray tokenForFolder(const QByteArray& folder) const;
void fetchFolderEncryptedStatus();
// to be used together with FolderStatusModel::FolderInfo::_path.
bool isFolderEncrypted(const QString& path) const;
void setFolderEncryptedStatus(const QString& path, bool status);
void forgetSensitiveData();
private slots:
void folderEncryptedStatusFetched(const QMap<QString, bool> &values);
void folderEncryptedStatusError(int error);
void publicKeyFetched(QKeychain::Job *incoming);
void privateKeyFetched(QKeychain::Job *incoming);
void mnemonicKeyFetched(QKeychain::Job *incoming);
signals:
void initializationFinished();
void mnemonicGenerated(const QString& mnemonic);
private:
void getPrivateKeyFromServer();
void getPublicKeyFromServer();
void decryptPrivateKey(const QByteArray &key);
void fetchFromKeyChain();
void writePrivateKey();
void writeCertificate();
void writeMnemonic();
AccountPtr _account;
bool isInitialized = false;
bool _refreshingEncryptionStatus = false;
//TODO: Save this on disk.
QMap<QByteArray, QByteArray> _folder2token;
QMap<QString, bool> _folder2encryptedStatus;
public:
QSslKey _privateKey;
QSslKey _publicKey;
QSslCertificate _certificate;
QString _mnemonic;
};
/* Generates the Metadata for the folder */
struct EncryptedFile {
QByteArray encryptionKey;
QByteArray mimetype;
QByteArray initializationVector;
QByteArray authenticationTag;
QString encryptedFilename;
QString originalFilename;
int fileVersion;
int metadataKey;
};
class OWNCLOUDSYNC_EXPORT FolderMetadata {
public:
FolderMetadata(AccountPtr account, const QByteArray& metadata = QByteArray(), int statusCode = -1);
QByteArray encryptedMetadata();
void addEncryptedFile(const EncryptedFile& f);
void removeEncryptedFile(const EncryptedFile& f);
QVector<EncryptedFile> files() const;
private:
/* Use std::string and std::vector internally on this class
* to ease the port to Nlohmann Json API
*/
void setupEmptyMetadata();
void setupExistingMetadata(const QByteArray& metadata);
QByteArray encryptMetadataKey(const QByteArray& metadataKey) const;
QByteArray decryptMetadataKey(const QByteArray& encryptedKey) const;
QByteArray encryptJsonObject(const QByteArray& obj, const QByteArray pass) const;
QByteArray decryptJsonObject(const QByteArray& encryptedJsonBlob, const QByteArray& pass) const;
QVector<EncryptedFile> _files;
QMap<int, QByteArray> _metadataKeys;
AccountPtr _account;
QVector<QPair<QString, QString>> _sharing;
};
} // namespace OCC
#endif

View file

@ -0,0 +1,455 @@
#include "clientsideencryptionjobs.h"
#include <QDebug>
#include <QLoggingCategory>
#include <QFileInfo>
#include <QDir>
#include <QJsonObject>
#include <QXmlStreamReader>
#include <QXmlStreamNamespaceDeclaration>
#include <QStack>
#include <QInputDialog>
#include <QLineEdit>
#include "clientsideencryption.h"
#include "account.h"
#include "capabilities.h"
#include "networkjobs.h"
#include "clientsideencryptionjobs.h"
#include "theme.h"
#include "creds/abstractcredentials.h"
Q_LOGGING_CATEGORY(lcSignPublicKeyApiJob, "nextcloud.sync.networkjob.sendcsr", QtInfoMsg)
Q_LOGGING_CATEGORY(lcStorePrivateKeyApiJob, "nextcloud.sync.networkjob.storeprivatekey", QtInfoMsg)
Q_LOGGING_CATEGORY(lcCseJob, "nextcloud.sync.networkjob.clientsideencrypt", QtInfoMsg)
namespace OCC {
GetFolderEncryptStatusJob::GetFolderEncryptStatusJob(const AccountPtr& account, const QString& folder, QObject *parent)
: OCC::AbstractNetworkJob(account, QStringLiteral("remote.php/webdav"), parent), _folder(folder)
{
}
void GetFolderEncryptStatusJob::start()
{
QNetworkRequest req;
req.setPriority(QNetworkRequest::HighPriority);
req.setRawHeader("OCS-APIREQUEST", "true");
req.setHeader(QNetworkRequest::ContentTypeHeader, QByteArrayLiteral("application/xml"));
QByteArray xml = "<d:propfind xmlns:d=\"DAV:\"> <d:prop xmlns:nc=\"http://nextcloud.org/ns\"> <nc:is-encrypted/> </d:prop> </d:propfind>";
QBuffer *buf = new QBuffer(this);
buf->setData(xml);
buf->open(QIODevice::ReadOnly);
QString tmpPath = path() + (!_folder.isEmpty() ? "/" + _folder : QString());
sendRequest("PROPFIND", Utility::concatUrlPath(account()->url(), tmpPath), req, buf);
AbstractNetworkJob::start();
}
bool GetFolderEncryptStatusJob::finished()
{
qCInfo(lcCseJob()) << "GetFolderEncryptStatus of" << reply()->request().url() << "finished with status"
<< reply()->error()
<< (reply()->error() == QNetworkReply::NoError ? QLatin1String("") : errorString());
int http_result_code = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (http_result_code == 207) {
// Parse DAV response
QXmlStreamReader reader(reply());
reader.addExtraNamespaceDeclaration(QXmlStreamNamespaceDeclaration("d", "DAV:"));
/* Example Xml
<?xml version="1.0"?>
<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns">
<d:response>
<d:href>/remote.php/webdav/</d:href>
<d:propstat>
<d:prop>
<nc:is-encrypted>0</nc:is-encrypted>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
</d:multistatus>
*/
QString currFile;
int currEncryptedStatus = -1;
QMap<QString, bool> folderStatus;
while (!reader.atEnd()) {
auto type = reader.readNext();
if (type == QXmlStreamReader::StartElement) {
if (reader.name() == QLatin1String("href")) {
// If the current file is not a folder, ignore it.
QString base = account()->url().path();
if (base.endsWith(QLatin1Char('/')))
base.chop(1);
currFile = reader.readElementText(QXmlStreamReader::SkipChildElements);
currFile.remove(base + QLatin1String("/remote.php/webdav/"));
if (!currFile.endsWith('/'))
currFile.clear();
currEncryptedStatus = -1;
}
if (not currFile.isEmpty() && reader.name() == QLatin1String("is-encrypted")) {
currEncryptedStatus = (bool) reader.readElementText(QXmlStreamReader::SkipChildElements).toInt();
}
}
if (!currFile.isEmpty() && currEncryptedStatus != -1) {
folderStatus.insert(currFile, currEncryptedStatus);
currFile.clear();
currEncryptedStatus = -1;
}
}
emit encryptStatusReceived(folderStatus);
emit encryptStatusFolderReceived(_folder, folderStatus.value(_folder + QLatin1Char('/')));
} else {
qCWarning(lcCseJob()) << "*not* successful, http result code is" << http_result_code
<< (http_result_code == 302 ? reply()->header(QNetworkRequest::LocationHeader).toString() : QLatin1String(""));
emit encryptStatusError(http_result_code);
// emit finishedWithError(reply());
}
return true;
}
GetMetadataApiJob::GetMetadataApiJob(const AccountPtr& account,
const QByteArray& fileId,
QObject* parent)
: AbstractNetworkJob(account, baseUrl() + QStringLiteral("meta-data/") + fileId, parent), _fileId(fileId)
{
}
void GetMetadataApiJob::start()
{
QNetworkRequest req;
req.setRawHeader("OCS-APIREQUEST", "true");
QUrlQuery query;
query.addQueryItem(QLatin1String("format"), QLatin1String("json"));
QUrl url = Utility::concatUrlPath(account()->url(), path());
url.setQuery(query);
qCInfo(lcCseJob()) << "Requesting the metadata for the fileId" << _fileId << "as encrypted";
sendRequest("GET", url, req);
AbstractNetworkJob::start();
}
bool GetMetadataApiJob::finished()
{
int retCode = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (retCode != 200) {
qCInfo(lcCseJob()) << "error requesting the metadata" << path() << errorString() << retCode;
emit error(_fileId, retCode);
return true;
}
QJsonParseError error;
auto json = QJsonDocument::fromJson(reply()->readAll(), &error);
emit jsonReceived(json, reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
return true;
}
StoreMetaDataApiJob::StoreMetaDataApiJob(const AccountPtr& account,
const QByteArray& fileId,
const QByteArray& b64Metadata,
QObject* parent)
: AbstractNetworkJob(account, baseUrl() + QStringLiteral("meta-data/") + fileId, parent), _fileId(fileId), _b64Metadata(b64Metadata)
{
}
void StoreMetaDataApiJob::start()
{
QNetworkRequest req;
req.setRawHeader("OCS-APIREQUEST", "true");
req.setHeader(QNetworkRequest::ContentTypeHeader, QByteArrayLiteral("application/x-www-form-urlencoded"));
QUrlQuery query;
query.addQueryItem(QLatin1String("format"), QLatin1String("json"));
QUrl url = Utility::concatUrlPath(account()->url(), path());
url.setQuery(query);
QByteArray data = QByteArray("metaData=") + QUrl::toPercentEncoding(_b64Metadata);
auto buffer = new QBuffer(this);
buffer->setData(data);
qCInfo(lcCseJob()) << "sending the metadata for the fileId" << _fileId << "as encrypted";
sendRequest("POST", url, req, buffer);
AbstractNetworkJob::start();
}
bool StoreMetaDataApiJob::finished()
{
int retCode = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (retCode != 200) {
qCInfo(lcCseJob()) << "error sending the metadata" << path() << errorString() << retCode;
emit error(_fileId, retCode);
}
qCInfo(lcCseJob()) << "Metadata submited to the server successfully";
emit success(_fileId);
return true;
}
UpdateMetadataApiJob::UpdateMetadataApiJob(const AccountPtr& account,
const QByteArray& fileId,
const QByteArray& b64Metadata,
const QByteArray& token,
QObject* parent)
: AbstractNetworkJob(account, baseUrl() + QStringLiteral("meta-data/") + fileId, parent),
_fileId(fileId),
_b64Metadata(b64Metadata),
_token(token)
{
}
void UpdateMetadataApiJob::start()
{
QNetworkRequest req;
req.setRawHeader("OCS-APIREQUEST", "true");
req.setHeader(QNetworkRequest::ContentTypeHeader, QByteArrayLiteral("application/x-www-form-urlencoded"));
QUrlQuery urlQuery;
urlQuery.addQueryItem(QStringLiteral("format"), QStringLiteral("json"));
urlQuery.addQueryItem(QStringLiteral("token"), _token);
QUrl url = Utility::concatUrlPath(account()->url(), path());
url.setQuery(urlQuery);
QUrlQuery params;
params.addQueryItem("metaData",QUrl::toPercentEncoding(_b64Metadata));
params.addQueryItem("token",_token);
QByteArray data = params.query().toLocal8Bit();
auto buffer = new QBuffer(this);
buffer->setData(data);
qCInfo(lcCseJob()) << "updating the metadata for the fileId" << _fileId << "as encrypted";
sendRequest("PUT", url, req, buffer);
AbstractNetworkJob::start();
}
bool UpdateMetadataApiJob::finished()
{
int retCode = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (retCode != 200) {
qCInfo(lcCseJob()) << "error updating the metadata" << path() << errorString() << retCode;
emit error(_fileId, retCode);
}
qCInfo(lcCseJob()) << "Metadata submited to the server successfully";
emit success(_fileId);
return true;
}
UnlockEncryptFolderApiJob::UnlockEncryptFolderApiJob(const AccountPtr& account,
const QByteArray& fileId,
const QByteArray& token,
QObject* parent)
: AbstractNetworkJob(account, baseUrl() + QStringLiteral("lock/") + fileId, parent), _fileId(fileId), _token(token)
{
}
void UnlockEncryptFolderApiJob::start()
{
QNetworkRequest req;
req.setRawHeader("OCS-APIREQUEST", "true");
req.setRawHeader("token", _token);
QUrl url = Utility::concatUrlPath(account()->url(), path());
sendRequest("DELETE", url, req);
AbstractNetworkJob::start();
qCInfo(lcCseJob()) << "Starting the request to unlock.";
}
bool UnlockEncryptFolderApiJob::finished()
{
int retCode = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (retCode != 200) {
qCInfo(lcCseJob()) << "error unlocking file" << path() << errorString() << retCode;
qCInfo(lcCseJob()) << "Full Error Log" << reply()->readAll();
emit error(_fileId, retCode);
return true;
}
emit success(_fileId);
return true;
}
DeleteMetadataApiJob::DeleteMetadataApiJob(const AccountPtr& account,
const QByteArray& fileId,
QObject* parent)
: AbstractNetworkJob(account, baseUrl() + QStringLiteral("meta-data/") + fileId, parent), _fileId(fileId)
{
}
void DeleteMetadataApiJob::start()
{
QNetworkRequest req;
req.setRawHeader("OCS-APIREQUEST", "true");
QUrl url = Utility::concatUrlPath(account()->url(), path());
sendRequest("DELETE", url, req);
AbstractNetworkJob::start();
qCInfo(lcCseJob()) << "Starting the request to remove the metadata.";
}
bool DeleteMetadataApiJob::finished()
{
int retCode = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (retCode != 200) {
qCInfo(lcCseJob()) << "error removing metadata for" << path() << errorString() << retCode;
qCInfo(lcCseJob()) << "Full Error Log" << reply()->readAll();
emit error(_fileId, retCode);
return true;
}
emit success(_fileId);
return true;
}
LockEncryptFolderApiJob::LockEncryptFolderApiJob(const AccountPtr& account, const QByteArray& fileId, QObject* parent)
: AbstractNetworkJob(account, baseUrl() + QStringLiteral("lock/") + fileId, parent), _fileId(fileId)
{
}
void LockEncryptFolderApiJob::start()
{
QNetworkRequest req;
req.setRawHeader("OCS-APIREQUEST", "true");
QUrlQuery query;
query.addQueryItem(QLatin1String("format"), QLatin1String("json"));
QUrl url = Utility::concatUrlPath(account()->url(), path());
url.setQuery(query);
qCInfo(lcCseJob()) << "locking the folder with id" << _fileId << "as encrypted";
sendRequest("POST", url, req);
AbstractNetworkJob::start();
}
bool LockEncryptFolderApiJob::finished()
{
int retCode = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (retCode != 200) {
qCInfo(lcCseJob()) << "error locking file" << path() << errorString() << retCode;
emit error(_fileId, retCode);
return true;
}
QJsonParseError error;
auto json = QJsonDocument::fromJson(reply()->readAll(), &error);
auto obj = json.object().toVariantMap();
auto token = obj["ocs"].toMap()["data"].toMap()["token"].toByteArray();
qCInfo(lcCseJob()) << "got json:" << token;
//TODO: Parse the token and submit.
emit success(_fileId, token);
return true;
}
SetEncryptionFlagApiJob::SetEncryptionFlagApiJob(const AccountPtr& account, const QByteArray& fileId, QObject* parent)
: AbstractNetworkJob(account, baseUrl() + QStringLiteral("encrypted/") + fileId, parent), _fileId(fileId)
{
}
void SetEncryptionFlagApiJob::start()
{
QNetworkRequest req;
req.setRawHeader("OCS-APIREQUEST", "true");
QUrl url = Utility::concatUrlPath(account()->url(), path());
qCInfo(lcCseJob()) << "marking the file with id" << _fileId << "as encrypted";
sendRequest("PUT", url, req);
AbstractNetworkJob::start();
}
bool SetEncryptionFlagApiJob::finished()
{
int retCode = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
qCInfo(lcCseJob()) << "Encryption Flag Return" << reply()->readAll();
if (retCode == 200) {
emit success(_fileId);
} else {
qCInfo(lcCseJob()) << "Setting the encrypted flag failed with" << path() << errorString() << retCode;
emit error(_fileId, retCode);
}
return true;
}
StorePrivateKeyApiJob::StorePrivateKeyApiJob(const AccountPtr& account, const QString& path, QObject* parent)
: AbstractNetworkJob(account, path, parent)
{
}
void StorePrivateKeyApiJob::setPrivateKey(const QByteArray& privKey)
{
QByteArray data = "privateKey=";
data += QUrl::toPercentEncoding(privKey);
_privKey.setData(data);
}
void StorePrivateKeyApiJob::start()
{
QNetworkRequest req;
req.setRawHeader("OCS-APIREQUEST", "true");
QUrlQuery query;
query.addQueryItem(QLatin1String("format"), QLatin1String("json"));
QUrl url = Utility::concatUrlPath(account()->url(), path());
url.setQuery(query);
qCInfo(lcStorePrivateKeyApiJob) << "Sending the private key" << _privKey.data();
sendRequest("POST", url, req, &_privKey);
AbstractNetworkJob::start();
}
bool StorePrivateKeyApiJob::finished()
{
int retCode = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (retCode != 200)
qCInfo(lcStorePrivateKeyApiJob()) << "Sending private key ended with" << path() << errorString() << retCode;
QJsonParseError error;
auto json = QJsonDocument::fromJson(reply()->readAll(), &error);
emit jsonReceived(json, reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
return true;
}
SignPublicKeyApiJob::SignPublicKeyApiJob(const AccountPtr& account, const QString& path, QObject* parent)
: AbstractNetworkJob(account, path, parent)
{
}
void SignPublicKeyApiJob::setCsr(const QByteArray& csr)
{
QByteArray data = "csr=";
data += QUrl::toPercentEncoding(csr);
_csr.setData(data);
}
void SignPublicKeyApiJob::start()
{
QNetworkRequest req;
req.setRawHeader("OCS-APIREQUEST", "true");
QUrlQuery query;
query.addQueryItem(QLatin1String("format"), QLatin1String("json"));
QUrl url = Utility::concatUrlPath(account()->url(), path());
url.setQuery(query);
qCInfo(lcSignPublicKeyApiJob) << "Sending the CSR" << _csr.data();
sendRequest("POST", url, req, &_csr);
AbstractNetworkJob::start();
}
bool SignPublicKeyApiJob::finished()
{
qCInfo(lcStorePrivateKeyApiJob()) << "Sending CSR ended with" << path() << errorString() << reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute);
QJsonParseError error;
auto json = QJsonDocument::fromJson(reply()->readAll(), &error);
emit jsonReceived(json, reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
return true;
}
}

View file

@ -0,0 +1,304 @@
#ifndef CLIENTSIDEENCRYPTIONJOBS_H
#define CLIENTSIDEENCRYPTIONJOBS_H
#include "networkjobs.h"
#include "accountfwd.h"
#include <QString>
#include <QJsonDocument>
namespace OCC {
/* Here are all of the network jobs for the client side encryption.
* anything that goes thru the server and expects a response, is here.
*/
/*
* @brief Job to sigh the CSR that return JSON
*
* To be used like this:
* \code
* _job = new SignPublicKeyApiJob(account, QLatin1String("ocs/v1.php/foo/bar"), this);
* _job->setCsr( csr );
* connect(_job...);
* _job->start();
* \encode
*
* @ingroup libsync
*/
class OWNCLOUDSYNC_EXPORT SignPublicKeyApiJob : public AbstractNetworkJob
{
Q_OBJECT
public:
explicit SignPublicKeyApiJob(const AccountPtr &account, const QString &path, QObject *parent = 0);
/**
* @brief setCsr - the CSR with the public key.
* This function needs to be called before start() obviously.
*/
void setCsr(const QByteArray& csr);
public slots:
void start() override;
protected:
bool finished() override;
signals:
/**
* @brief jsonReceived - signal to report the json answer from ocs
* @param json - the parsed json document
* @param statusCode - the OCS status code: 100 (!) for success
*/
void jsonReceived(const QJsonDocument &json, int statusCode);
private:
QBuffer _csr;
};
/*
* @brief Job to upload the PrivateKey that return JSON
*
* To be used like this:
* \code
* _job = new StorePrivateKeyApiJob(account, QLatin1String("ocs/v1.php/foo/bar"), this);
* _job->setPrivateKey( privKey );
* connect(_job...);
* _job->start();
* \encode
*
* @ingroup libsync
*/
class OWNCLOUDSYNC_EXPORT StorePrivateKeyApiJob : public AbstractNetworkJob
{
Q_OBJECT
public:
explicit StorePrivateKeyApiJob(const AccountPtr &account, const QString &path, QObject *parent = 0);
/**
* @brief setCsr - the CSR with the public key.
* This function needs to be called before start() obviously.
*/
void setPrivateKey(const QByteArray& privateKey);
public slots:
void start() override;
protected:
bool finished() override;
signals:
/**
* @brief jsonReceived - signal to report the json answer from ocs
* @param json - the parsed json document
* @param statusCode - the OCS status code: 100 (!) for success
*/
void jsonReceived(const QJsonDocument &json, int statusCode);
private:
QBuffer _privKey;
};
/*
* @brief Job to mark a folder as encrypted JSON
*
* To be used like this:
* \code
* _job = new SetEncryptionFlagApiJob(account, 2, this);
* connect(_job...);
* _job->start();
* \encode
*
* @ingroup libsync
*/
class OWNCLOUDSYNC_EXPORT SetEncryptionFlagApiJob : public AbstractNetworkJob
{
Q_OBJECT
public:
explicit SetEncryptionFlagApiJob(const AccountPtr &account, const QByteArray& fileId, QObject *parent = 0);
public slots:
void start() override;
protected:
bool finished() override;
signals:
void success(const QByteArray fileId);
void error(const QByteArray fileId, int httpReturnCode);
private:
QByteArray _fileId;
};
class OWNCLOUDSYNC_EXPORT LockEncryptFolderApiJob : public AbstractNetworkJob
{
Q_OBJECT
public:
explicit LockEncryptFolderApiJob(const AccountPtr &account, const QByteArray& fileId, QObject *parent = 0);
public slots:
void start() override;
protected:
bool finished() override;
signals:
void success(const QByteArray& fileId, const QByteArray& token);
void error(const QByteArray& fileId, int httpdErrorCode);
private:
QByteArray _fileId;
};
class OWNCLOUDSYNC_EXPORT UnlockEncryptFolderApiJob : public AbstractNetworkJob
{
Q_OBJECT
public:
explicit UnlockEncryptFolderApiJob (
const AccountPtr &account,
const QByteArray& fileId,
const QByteArray& token,
QObject *parent = 0);
public slots:
void start() override;
protected:
bool finished() override;
signals:
void success(const QByteArray& fileId);
void error(const QByteArray& fileId, int httpReturnCode);
private:
QByteArray _fileId;
QByteArray _token;
QBuffer *_tokenBuf;
};
class OWNCLOUDSYNC_EXPORT StoreMetaDataApiJob : public AbstractNetworkJob
{
Q_OBJECT
public:
explicit StoreMetaDataApiJob (
const AccountPtr &account,
const QByteArray& fileId,
const QByteArray& b64Metadata,
QObject *parent = 0);
public slots:
void start() override;
protected:
bool finished() override;
signals:
void success(const QByteArray& fileId);
void error(const QByteArray& fileId, int httpReturnCode);
private:
QByteArray _fileId;
QByteArray _b64Metadata;
};
class OWNCLOUDSYNC_EXPORT UpdateMetadataApiJob : public AbstractNetworkJob
{
Q_OBJECT
public:
explicit UpdateMetadataApiJob (
const AccountPtr &account,
const QByteArray& fileId,
const QByteArray& b64Metadata,
const QByteArray& lockedToken,
QObject *parent = 0);
public slots:
void start() override;
protected:
bool finished() override;
signals:
void success(const QByteArray& fileId);
void error(const QByteArray& fileId, int httpReturnCode);
private:
QByteArray _fileId;
QByteArray _b64Metadata;
QByteArray _token;
};
class OWNCLOUDSYNC_EXPORT GetMetadataApiJob : public AbstractNetworkJob
{
Q_OBJECT
public:
explicit GetMetadataApiJob (
const AccountPtr &account,
const QByteArray& fileId,
QObject *parent = 0);
public slots:
void start() override;
protected:
bool finished() override;
signals:
void jsonReceived(const QJsonDocument &json, int statusCode);
void error(const QByteArray& fileId, int httpReturnCode);
private:
QByteArray _fileId;
};
class OWNCLOUDSYNC_EXPORT DeleteMetadataApiJob : public AbstractNetworkJob
{
Q_OBJECT
public:
explicit DeleteMetadataApiJob (
const AccountPtr &account,
const QByteArray& fileId,
QObject *parent = 0);
public slots:
void start() override;
protected:
bool finished() override;
signals:
void success(const QByteArray& fileId);
void error(const QByteArray& fileId, int httpErrorCode);
private:
QByteArray _fileId;
};
/* I cant use the propfind network job because it defaults to the
* wrong dav url.
*/
class OWNCLOUDSYNC_EXPORT GetFolderEncryptStatusJob : public AbstractNetworkJob
{
Q_OBJECT
public:
explicit GetFolderEncryptStatusJob (const AccountPtr &account, const QString& folder, QObject *parent = 0);
public slots:
void start() override;
protected:
bool finished() override;
signals:
void encryptStatusReceived(const QMap<QString, bool> folderMetadata2EncryptionStatus);
void encryptStatusFolderReceived(const QString &folder, bool isEncrypted);
void encryptStatusError(int statusCode);
private:
QString _folder;
};
}
#endif

View file

@ -43,7 +43,7 @@
namespace OCC {
Q_LOGGING_CATEGORY(lcConfigFile, "sync.configfile", QtInfoMsg)
Q_LOGGING_CATEGORY(lcConfigFile, "nextcloud.sync.configfile", QtInfoMsg)
//static const char caCertsKeyC[] = "CaCertificates"; only used from account.cpp
static const char remotePollIntervalC[] = "remotePollInterval";

View file

@ -14,6 +14,7 @@
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QLoggingCategory>
#include <QNetworkReply>
#include <QNetworkProxyFactory>
@ -27,7 +28,7 @@
namespace OCC {
Q_LOGGING_CATEGORY(lcConnectionValidator, "sync.connectionvalidator", QtInfoMsg)
Q_LOGGING_CATEGORY(lcConnectionValidator, "nextcloud.sync.connectionvalidator", QtInfoMsg)
// Make sure the timeout for this job is less than how often we get called
// This makes sure we get tried often enough without "ConnectionValidator already running"
@ -351,6 +352,11 @@ void ConnectionValidator::slotUserFetched(const QJsonDocument &json)
void ConnectionValidator::slotAvatarImage(const QImage &img)
{
_account->setAvatar(img);
connect(_account->e2e(), &ClientSideEncryption::initializationFinished, this, &ConnectionValidator::reportConnected);
_account->e2e()->initialize();
}
void ConnectionValidator::reportConnected() {
reportResult(Connected);
}
#endif

View file

@ -21,6 +21,7 @@
#include <QVariantMap>
#include <QNetworkReply>
#include "accountfwd.h"
#include "clientsideencryption.h"
namespace OCC {
@ -63,18 +64,21 @@ namespace OCC {
| +-> ocsConfigReceived
+-> slotCapabilitiesRecieved -+
|
+-----------------------------------+
|
+-> fetchUser
+---------------------------------+
|
fetchUser
PropfindJob
|
+-> slotUserFetched
AvatarJob
|
+-> slotAvatarImage --> reportResult()
+-> slotAvatarImage -->
+-----------------------------------+
|
+-> Client Side Encryption Checks --+ --reportResult()
\endcode
*/
class OWNCLOUDSYNC_EXPORT ConnectionValidator : public QObject
{
Q_OBJECT
@ -128,6 +132,7 @@ protected slots:
#endif
private:
void reportConnected();
void reportResult(Status status);
void checkServerCapabilities();
void fetchUser();

View file

@ -24,7 +24,7 @@
namespace OCC {
Q_LOGGING_CATEGORY(lcCookieJar, "sync.cookiejar", QtInfoMsg)
Q_LOGGING_CATEGORY(lcCookieJar, "nextcloud.sync.cookiejar", QtInfoMsg)
namespace {
const unsigned int JAR_VERSION = 23;

View file

@ -21,7 +21,7 @@
namespace OCC {
Q_LOGGING_CATEGORY(lcCredentials, "sync.credentials", QtInfoMsg)
Q_LOGGING_CATEGORY(lcCredentials, "nextcloud.sync.credentials", QtInfoMsg)
AbstractCredentials::AbstractCredentials()
: _account(0)

View file

@ -37,7 +37,7 @@ using namespace QKeychain;
namespace OCC {
Q_LOGGING_CATEGORY(lcHttpCredentials, "sync.credentials.http", QtInfoMsg)
Q_LOGGING_CATEGORY(lcHttpCredentials, "nextcloud.sync.credentials.http", QtInfoMsg)
namespace {
const char userC[] = "user";

View file

@ -29,7 +29,7 @@
namespace OCC {
Q_LOGGING_CATEGORY(lcTokenCredentials, "sync.credentials.token", QtInfoMsg)
Q_LOGGING_CATEGORY(lcTokenCredentials, "nextcloud.sync.credentials.token", QtInfoMsg)
namespace {

View file

@ -30,7 +30,7 @@
namespace OCC {
Q_LOGGING_CATEGORY(lcDiscovery, "sync.discovery", QtInfoMsg)
Q_LOGGING_CATEGORY(lcDiscovery, "nextcloud.sync.discovery", QtInfoMsg)
/* Given a sorted list of paths ending with '/', return whether or not the given path is within one of the paths of the list*/
static bool findPathInList(const QStringList &list, const QString &path)

View file

@ -44,9 +44,9 @@ Logger::Logger(QObject *parent)
, _logExpire(0)
, _logDebug(false)
{
qSetMessagePattern("%{time MM-dd hh:mm:ss:zzz} [ %{type} %{category} ]%{if-debug}\t[ %{function} ]%{endif}:\t%{message}");
qSetMessagePattern("[%{function} \t%{message}");
#ifndef NO_MSG_HANDLER
qInstallMessageHandler(mirallLogCatcher);
// qInstallMessageHandler(mirallLogCatcher);
#else
Q_UNUSED(mirallLogCatcher)
#endif

View file

@ -34,21 +34,22 @@
#include "networkjobs.h"
#include "account.h"
#include "owncloudpropagator.h"
#include "clientsideencryption.h"
#include "creds/abstractcredentials.h"
#include "creds/httpcredentials.h"
namespace OCC {
Q_LOGGING_CATEGORY(lcEtagJob, "sync.networkjob.etag", QtInfoMsg)
Q_LOGGING_CATEGORY(lcLsColJob, "sync.networkjob.lscol", QtInfoMsg)
Q_LOGGING_CATEGORY(lcCheckServerJob, "sync.networkjob.checkserver", QtInfoMsg)
Q_LOGGING_CATEGORY(lcPropfindJob, "sync.networkjob.propfind", QtInfoMsg)
Q_LOGGING_CATEGORY(lcAvatarJob, "sync.networkjob.avatar", QtInfoMsg)
Q_LOGGING_CATEGORY(lcMkColJob, "sync.networkjob.mkcol", QtInfoMsg)
Q_LOGGING_CATEGORY(lcProppatchJob, "sync.networkjob.proppatch", QtInfoMsg)
Q_LOGGING_CATEGORY(lcJsonApiJob, "sync.networkjob.jsonapi", QtInfoMsg)
Q_LOGGING_CATEGORY(lcDetermineAuthTypeJob, "sync.networkjob.determineauthtype", QtInfoMsg)
Q_LOGGING_CATEGORY(lcEtagJob, "nextcloud.sync.networkjob.etag", QtInfoMsg)
Q_LOGGING_CATEGORY(lcLsColJob, "nextcloud.sync.networkjob.lscol", QtInfoMsg)
Q_LOGGING_CATEGORY(lcCheckServerJob, "nextcloud.sync.networkjob.checkserver", QtInfoMsg)
Q_LOGGING_CATEGORY(lcPropfindJob, "nextcloud.sync.networkjob.propfind", QtInfoMsg)
Q_LOGGING_CATEGORY(lcAvatarJob, "nextcloud.sync.networkjob.avatar", QtInfoMsg)
Q_LOGGING_CATEGORY(lcMkColJob, "nextcloud.sync.networkjob.mkcol", QtInfoMsg)
Q_LOGGING_CATEGORY(lcProppatchJob, "nextcloud.sync.networkjob.proppatch", QtInfoMsg)
Q_LOGGING_CATEGORY(lcJsonApiJob, "nextcloud.sync.networkjob.jsonapi", QtInfoMsg)
Q_LOGGING_CATEGORY(lcDetermineAuthTypeJob, "nextcloud.sync.networkjob.determineauthtype", QtInfoMsg)
const int notModifiedStatusCode = 304;
RequestEtagJob::RequestEtagJob(AccountPtr account, const QString &path, QObject *parent)
@ -186,7 +187,7 @@ LsColXMLParser::LsColXMLParser()
{
}
bool LsColXMLParser::parse(const QByteArray &xml, QHash<QString, qint64> *sizes, const QString &expectedPath)
bool LsColXMLParser::parse(const QByteArray &xml, QHash<QString, ExtraFolderInfo> *fileInfo, const QString &expectedPath)
{
// Parse DAV response
QXmlStreamReader reader(xml);
@ -242,9 +243,11 @@ bool LsColXMLParser::parse(const QByteArray &xml, QHash<QString, qint64> *sizes,
} else if (name == QLatin1String("size")) {
bool ok = false;
auto s = propertyContent.toLongLong(&ok);
if (ok && sizes) {
sizes->insert(currentHref, s);
if (ok && fileInfo) {
(*fileInfo)[currentHref].size = s;
}
} else if (name == QLatin1String("fileid")) {
(*fileInfo)[currentHref].fileId = propertyContent.toUtf8();
}
currentTmpProperties.insert(reader.name().toString(), propertyContent);
}
@ -373,7 +376,7 @@ bool LsColJob::finished()
this, &LsColJob::finishedWithoutError);
QString expectedPath = reply()->request().url().path(); // something like "/owncloud/remote.php/webdav/folder"
if (!parser.parse(reply()->readAll(), &_sizes, expectedPath)) {
if (!parser.parse(reply()->readAll(), &_folderInfos, expectedPath)) {
// XML parse error
emit finishedWithError(reply());
}
@ -566,6 +569,7 @@ void PropfindJob::start()
buf->setData(xml);
buf->open(QIODevice::ReadOnly);
sendRequest("PROPFIND", makeDavUrl(path()), req, buf);
AbstractNetworkJob::start();
}
@ -814,7 +818,8 @@ bool JsonApiJob::finished()
int statusCode = 0;
int httpStatusCode = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (reply()->error() != QNetworkReply::NoError) {
qCWarning(lcJsonApiJob) << "Network error: " << path() << errorString() << httpStatusCode;
qCWarning(lcJsonApiJob) << "Network error: " << path() << errorString() << reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute);
statusCode = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
emit jsonReceived(QJsonDocument(), statusCode);
return true;
}
@ -854,6 +859,7 @@ bool JsonApiJob::finished()
return true;
}
DetermineAuthTypeJob::DetermineAuthTypeJob(AccountPtr account, QObject *parent)
: QObject(parent)
, _account(account)
@ -939,6 +945,43 @@ bool SimpleNetworkJob::finished()
return true;
}
DeleteApiJob::DeleteApiJob(AccountPtr account, const QString &path, QObject *parent)
: AbstractNetworkJob(account, path, parent)
{
}
void DeleteApiJob::start()
{
QNetworkRequest req;
req.setRawHeader("OCS-APIREQUEST", "true");
QUrl url = Utility::concatUrlPath(account()->url(), path());
sendRequest("DELETE", url, req);
AbstractNetworkJob::start();
}
bool DeleteApiJob::finished()
{
qCInfo(lcJsonApiJob) << "JsonApiJob of" << reply()->request().url() << "FINISHED WITH STATUS"
<< reply()->error()
<< (reply()->error() == QNetworkReply::NoError ? QLatin1String("") : errorString());
int httpStatus = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (reply()->error() != QNetworkReply::NoError) {
qCWarning(lcJsonApiJob) << "Network error: " << path() << errorString() << httpStatus;
emit result(httpStatus);
return true;
}
const auto replyData = QString::fromUtf8(reply()->readAll());
qCInfo(lcJsonApiJob()) << "TMX Delete Job" << replyData;
emit result(httpStatus);
return true;
}
void fetchPrivateLinkUrl(AccountPtr account, const QString &remotePath,
const QByteArray &numericFileId, QObject *target,
std::function<void(const QString &url)> targetFun)
@ -972,3 +1015,4 @@ void fetchPrivateLinkUrl(AccountPtr account, const QString &remotePath,
}
} // namespace OCC

View file

@ -17,6 +17,8 @@
#define NETWORKJOBS_H
#include "abstractnetworkjob.h"
#include <QBuffer>
#include <QUrlQuery>
#include <functional>
@ -43,6 +45,32 @@ private slots:
virtual bool finished() Q_DECL_OVERRIDE;
};
/**
* @brief sends a DELETE http request to a url.
*
* See Nextcloud API usage for the possible DELETE requests.
*
* This does *not* delete files, it does a http request.
*/
class OWNCLOUDSYNC_EXPORT DeleteApiJob : public AbstractNetworkJob
{
Q_OBJECT
public:
explicit DeleteApiJob(AccountPtr account, const QString &path, QObject *parent = 0);
void start() override;
signals:
void result(int httpCode);
private slots:
virtual bool finished() override;
};
struct ExtraFolderInfo {
QByteArray fileId;
qint64 size = -1;
};
/**
* @brief The LsColJob class
* @ingroup libsync
@ -53,7 +81,9 @@ class OWNCLOUDSYNC_EXPORT LsColXMLParser : public QObject
public:
explicit LsColXMLParser();
bool parse(const QByteArray &xml, QHash<QString, qint64> *sizes, const QString &expectedPath);
bool parse(const QByteArray &xml,
QHash<QString, ExtraFolderInfo> *sizes,
const QString &expectedPath);
signals:
void directoryListingSubfolders(const QStringList &items);
@ -69,7 +99,7 @@ public:
explicit LsColJob(AccountPtr account, const QString &path, QObject *parent = 0);
explicit LsColJob(AccountPtr account, const QUrl &url, QObject *parent = 0);
void start() Q_DECL_OVERRIDE;
QHash<QString, qint64> _sizes;
QHash<QString, ExtraFolderInfo> _folderInfos;
/**
* Used to specify which properties shall be retrieved.

View file

@ -43,9 +43,9 @@
namespace OCC {
Q_LOGGING_CATEGORY(lcPropagator, "sync.propagator", QtInfoMsg)
Q_LOGGING_CATEGORY(lcDirectory, "sync.propagator.directory", QtInfoMsg)
Q_LOGGING_CATEGORY(lcCleanupPolls, "sync.propagator.cleanuppolls", QtInfoMsg)
Q_LOGGING_CATEGORY(lcPropagator, "nextcloud.sync.propagator", QtInfoMsg)
Q_LOGGING_CATEGORY(lcDirectory, "nextcloud.sync.propagator.directory", QtInfoMsg)
Q_LOGGING_CATEGORY(lcCleanupPolls, "nextcloud.sync.propagator.cleanuppolls", QtInfoMsg)
qint64 criticalFreeSpaceLimit()
{

View file

@ -454,6 +454,7 @@ public:
*/
bool hasCaseClashAccessibilityProblem(const QString &relfile);
/* returns the local file path for the given tmp_file_name */
QString getFilePath(const QString &tmp_file_name) const;
/** Creates the job for an item.

View file

@ -24,6 +24,8 @@
#include "propagatorjobs.h"
#include "common/checksums.h"
#include "common/asserts.h"
#include "clientsideencryptionjobs.h"
#include "propagatedownloadencrypted.h"
#include <QLoggingCategory>
#include <QNetworkAccessManager>
@ -37,8 +39,8 @@
namespace OCC {
Q_LOGGING_CATEGORY(lcGetJob, "sync.networkjob.get", QtInfoMsg)
Q_LOGGING_CATEGORY(lcPropagateDownload, "sync.propagator.download", QtInfoMsg)
Q_LOGGING_CATEGORY(lcGetJob, "nextcloud.sync.networkjob.get", QtInfoMsg)
Q_LOGGING_CATEGORY(lcPropagateDownload, "nextcloud.sync.propagator.download", QtInfoMsg)
// Always coming in with forward slashes.
// In csync_excluded_no_ctx we ignore all files with longer than 254 chars
@ -344,8 +346,31 @@ void PropagateDownloadFile::start()
{
if (propagator()->_abortRequested.fetchAndAddRelaxed(0))
return;
_isEncrypted = false;
qCDebug(lcPropagateDownload) << _item->_file << propagator()->_activeJobList.count();
if (propagator()->account()->capabilities().clientSideEncryptionAvaliable()) {
_downloadEncryptedHelper = new PropagateDownloadEncrypted(propagator(), _item);
connect(_downloadEncryptedHelper, &PropagateDownloadEncrypted::folderStatusNotEncrypted, [this] {
startAfterIsEncryptedIsChecked();
});
connect(_downloadEncryptedHelper, &PropagateDownloadEncrypted::folderStatusEncrypted, [this] {
_isEncrypted = true;
startAfterIsEncryptedIsChecked();
});
connect(_downloadEncryptedHelper, &PropagateDownloadEncrypted::failed, [this] {
done(SyncFileItem::NormalError,
tr("File %1 can not be downloaded because encryption information is missing.").arg(QDir::toNativeSeparators(_item->_file)));
});
_downloadEncryptedHelper->start();
} else {
startAfterIsEncryptedIsChecked();
}
}
void PropagateDownloadFile::startAfterIsEncryptedIsChecked()
{
_stopwatch.start();
if (_deleteExisting) {
@ -800,7 +825,16 @@ void PropagateDownloadFile::contentChecksumComputed(const QByteArray &checksumTy
{
_item->_checksumHeader = makeChecksumHeader(checksumType, checksum);
downloadFinished();
if (_isEncrypted) {
if (_downloadEncryptedHelper->decryptFile(_tmpFile)) {
downloadFinished();
} else {
done(SyncFileItem::NormalError, _downloadEncryptedHelper->errorString());
}
} else {
downloadFinished();
}
}
void PropagateDownloadFile::downloadFinished()
@ -904,7 +938,13 @@ void PropagateDownloadFile::updateMetadata(bool isConflict)
done(SyncFileItem::FatalError, tr("Error writing metadata to the database"));
return;
}
propagator()->_journal->setDownloadInfo(_item->_file, SyncJournalDb::DownloadInfo());
if (_isEncrypted) {
propagator()->_journal->setDownloadInfo(_item->_file, SyncJournalDb::DownloadInfo());
} else {
propagator()->_journal->setDownloadInfo(_item->_encryptedFileName, SyncJournalDb::DownloadInfo());
}
propagator()->_journal->commit("download file start2");
done(isConflict ? SyncFileItem::Conflict : SyncFileItem::Success);

View file

@ -15,11 +15,13 @@
#include "owncloudpropagator.h"
#include "networkjobs.h"
#include "clientsideencryption.h"
#include <QBuffer>
#include <QFile>
namespace OCC {
class PropagateDownloadEncrypted;
/**
* @brief The GETFileJob class
@ -195,6 +197,7 @@ private slots:
void slotChecksumFail(const QString &errMsg);
private:
void startAfterIsEncryptedIsChecked();
void deleteExistingFolder();
quint64 _resumeStart;
@ -202,8 +205,12 @@ private:
QPointer<GETFileJob> _job;
QFile _tmpFile;
bool _deleteExisting;
bool _isEncrypted = false;
EncryptedFile _encryptedInfo;
ConflictRecord _conflictRecord;
QElapsedTimer _stopwatch;
PropagateDownloadEncrypted *_downloadEncryptedHelper;
};
}

View file

@ -0,0 +1,144 @@
#include "propagatedownloadencrypted.h"
#include "clientsideencryptionjobs.h"
Q_LOGGING_CATEGORY(lcPropagateDownloadEncrypted, "nextcloud.sync.propagator.download.encrypted", QtInfoMsg)
namespace OCC {
PropagateDownloadEncrypted::PropagateDownloadEncrypted(OwncloudPropagator *propagator, SyncFileItemPtr item) :
_propagator(propagator), _item(item), _info(_item->_file)
{
}
void PropagateDownloadEncrypted::start() {
checkFolderEncryptedStatus();
}
void PropagateDownloadEncrypted::checkFolderEncryptedStatus()
{
auto getEncryptedStatus = new GetFolderEncryptStatusJob(_propagator->account(), _info.path());
connect(getEncryptedStatus, &GetFolderEncryptStatusJob::encryptStatusFolderReceived,
this, &PropagateDownloadEncrypted::folderStatusReceived);
connect(getEncryptedStatus, &GetFolderEncryptStatusJob::encryptStatusError,
this, &PropagateDownloadEncrypted::folderStatusError);
getEncryptedStatus->start();
}
void PropagateDownloadEncrypted::folderStatusError(int statusCode)
{
qCDebug(lcPropagateDownloadEncrypted) << "Failed to get encrypted status of folder" << statusCode;
}
void PropagateDownloadEncrypted::folderStatusReceived(const QString &folder, bool isEncrypted)
{
qCDebug(lcPropagateDownloadEncrypted) << "Get Folder is Encrypted Received" << folder << isEncrypted;
if (!isEncrypted) {
emit folderStatusNotEncrypted();
return;
}
// Is encrypted Now we need the folder-id
auto job = new LsColJob(_propagator->account(), folder, this);
job->setProperties({"resourcetype", "http://owncloud.org/ns:fileid"});
connect(job, &LsColJob::directoryListingSubfolders,
this, &PropagateDownloadEncrypted::checkFolderId);
connect(job, &LsColJob::finishedWithError,
this, &PropagateDownloadEncrypted::folderIdError);
job->start();
}
void PropagateDownloadEncrypted::folderIdError()
{
qCDebug(lcPropagateDownloadEncrypted) << "Failed to get encrypted metadata of folder";
}
void PropagateDownloadEncrypted::checkFolderId(const QStringList &list)
{
auto job = qobject_cast<LsColJob*>(sender());
const QString folderId = list.first();
qCDebug(lcPropagateDownloadEncrypted) << "Received id of folder" << folderId;
const ExtraFolderInfo &folderInfo = job->_folderInfos.value(folderId);
// Now that we have the folder-id we need it's JSON metadata
auto metadataJob = new GetMetadataApiJob(_propagator->account(), folderInfo.fileId);
connect(metadataJob, &GetMetadataApiJob::jsonReceived,
this, &PropagateDownloadEncrypted::checkFolderEncryptedMetadata);
metadataJob->start();
}
void PropagateDownloadEncrypted::checkFolderEncryptedMetadata(const QJsonDocument &json)
{
qCDebug(lcPropagateDownloadEncrypted) << "Metadata Received reading" <<
csync_instruction_str(_item->_instruction) << _item->_file << _item->_encryptedFileName;
const QString filename = _info.fileName();
auto meta = new FolderMetadata(_propagator->account(), json.toJson(QJsonDocument::Compact));
const QVector<EncryptedFile> files = meta->files();
const QString encryptedFilename = _item->_instruction == CSYNC_INSTRUCTION_NEW ?
_item->_file.section(QLatin1Char('/'), -1) :
_item->_encryptedFileName.section(QLatin1Char('/'), -1);
for (const EncryptedFile &file : files) {
if (encryptedFilename == file.encryptedFilename) {
_encryptedInfo = file;
qCDebug(lcPropagateDownloadEncrypted) << "Found matching encrypted metadata for file, starting download";
emit folderStatusEncrypted();
return;
}
}
emit failed();
qCCritical(lcPropagateDownloadEncrypted) << "Failed to find encrypted metadata information of remote file" << filename;
}
// TODO: Fix this. Exported in the wrong place.
QString createDownloadTmpFileName(const QString &previous);
bool PropagateDownloadEncrypted::decryptFile(QFile& tmpFile)
{
const QString tmpFileName = createDownloadTmpFileName(_item->_file + QLatin1String("_dec"));
qCDebug(lcPropagateDownloadEncrypted) << "Content Checksum Computed starting decryption" << tmpFileName;
tmpFile.close();
QFile _tmpOutput(_propagator->getFilePath(tmpFileName), this);
bool fileDecrypted = EncryptionHelper::fileDecryption(_encryptedInfo.encryptionKey,
_encryptedInfo.initializationVector,
&tmpFile,
&_tmpOutput);
qCDebug(lcPropagateDownloadEncrypted) << "Decryption finished" << tmpFile.fileName() << _tmpOutput.fileName();
tmpFile.close();
_tmpOutput.close();
// we decripted the temporary into another temporary, so good bye old one
if (!tmpFile.remove()) {
qCDebug(lcPropagateDownloadEncrypted) << "Failed to remove temporary file" << tmpFile.errorString();
_errorString = tmpFile.errorString();
return false;
}
// Let's fool the rest of the logic into thinking this was the actual download
tmpFile.setFileName(_tmpOutput.fileName());
//TODO: This seems what's breaking the logic.
// Let's fool the rest of the logic into thinking this is the right name of the DAV file
_item->_encryptedFileName = _item->_file;
_item->_file = _item->_file.section(QLatin1Char('/'), 0, -2)
+ QLatin1Char('/') + _encryptedInfo.originalFilename;
return true;
}
QString PropagateDownloadEncrypted::errorString() const
{
return _errorString;
}
}

View file

@ -0,0 +1,47 @@
#ifndef PROPAGATEDOWNLOADENCRYPTED_H
#define PROPAGATEDOWNLOADENCRYPTED_H
#include <QObject>
#include <QFileInfo>
#include "syncfileitem.h"
#include "owncloudpropagator.h"
#include "clientsideencryption.h"
class QJsonDocument;
namespace OCC {
class PropagateDownloadEncrypted : public QObject {
Q_OBJECT
public:
PropagateDownloadEncrypted(OwncloudPropagator *propagator, SyncFileItemPtr item);
void start();
void checkFolderId(const QStringList &list);
bool decryptFile(QFile& tmpFile);
QString errorString() const;
public slots:
void checkFolderEncryptedStatus();
void checkFolderEncryptedMetadata(const QJsonDocument &json);
void folderStatusReceived(const QString &folder, bool isEncrypted);
void folderStatusError(int httpErrorCode);
void folderIdError();
signals:
void folderStatusEncrypted();
void folderStatusNotEncrypted();
void failed();
void decryptionFinished();
private:
OwncloudPropagator *_propagator;
SyncFileItemPtr _item;
QFileInfo _info;
EncryptedFile _encryptedInfo;
QString _errorString;
};
}
#endif

View file

@ -13,6 +13,7 @@
*/
#include "propagateremotedelete.h"
#include "propagateremotedeleteencrypted.h"
#include "owncloudpropagator_p.h"
#include "account.h"
#include "common/asserts.h"
@ -21,8 +22,8 @@
namespace OCC {
Q_LOGGING_CATEGORY(lcDeleteJob, "sync.networkjob.delete", QtInfoMsg)
Q_LOGGING_CATEGORY(lcPropagateRemoteDelete, "sync.propagator.remotedelete", QtInfoMsg)
Q_LOGGING_CATEGORY(lcDeleteJob, "nextcloud.sync.networkjob.delete", QtInfoMsg)
Q_LOGGING_CATEGORY(lcPropagateRemoteDelete, "nextcloud.sync.propagator.remotedelete", QtInfoMsg)
DeleteJob::DeleteJob(AccountPtr account, const QString &path, QObject *parent)
: AbstractNetworkJob(account, path, parent)
@ -65,11 +66,25 @@ void PropagateRemoteDelete::start()
if (propagator()->_abortRequested.fetchAndAddRelaxed(0))
return;
qCDebug(lcPropagateRemoteDelete) << _item->_file;
if (!_item->_encryptedFileName.isEmpty()) {
auto job = new PropagateRemoteDeleteEncrypted(propagator(), _item, this);
connect(job, &PropagateRemoteDeleteEncrypted::finished, this, [this] (bool success) {
Q_UNUSED(success) // Should we skip file deletion in case of failure?
createDeleteJob(_item->_encryptedFileName);
});
job->start();
} else {
createDeleteJob(_item->_file);
}
}
void PropagateRemoteDelete::createDeleteJob(const QString &filename)
{
qCInfo(lcPropagateRemoteDelete) << "Deleting file, local" << _item->_file << "remote" << filename;
_job = new DeleteJob(propagator()->account(),
propagator()->_remoteFolder + _item->_file,
this);
propagator()->_remoteFolder + filename,
this);
connect(_job.data(), &DeleteJob::finishedSignal, this, &PropagateRemoteDelete::slotDeleteJobFinished);
propagator()->_activeJobList.append(this);
_job->start();

View file

@ -52,6 +52,7 @@ public:
{
}
void start() Q_DECL_OVERRIDE;
void createDeleteJob(const QString &filename);
void abort(PropagatorJob::AbortType abortType) Q_DECL_OVERRIDE;
bool isLikelyFinishedQuickly() Q_DECL_OVERRIDE { return !_item->isDirectory(); }

View file

@ -0,0 +1,130 @@
#include "propagateremotedeleteencrypted.h"
#include "clientsideencryptionjobs.h"
#include "clientsideencryption.h"
#include "owncloudpropagator.h"
#include <QLoggingCategory>
#include <QMimeDatabase>
#include <QFileInfo>
#include <QDir>
using namespace OCC;
Q_LOGGING_CATEGORY(PROPAGATE_REMOVE_ENCRYPTED, "nextcloud.sync.propagator.remove.encrypted")
PropagateRemoteDeleteEncrypted::PropagateRemoteDeleteEncrypted(OwncloudPropagator *propagator, SyncFileItemPtr item, QObject *parent)
: QObject(parent)
, _propagator(propagator)
, _item(item)
{
}
void PropagateRemoteDeleteEncrypted::start()
{
QFileInfo info(_item->_file);
qCDebug(PROPAGATE_REMOVE_ENCRYPTED) << "Folder is encrypted, let's get the Id from it.";
auto job = new LsColJob(_propagator->account(), info.path(), this);
job->setProperties({"resourcetype", "http://owncloud.org/ns:fileid"});
connect(job, &LsColJob::directoryListingSubfolders, this, &PropagateRemoteDeleteEncrypted::slotFolderEncryptedIdReceived);
connect(job, &LsColJob::finishedWithError, this, &PropagateRemoteDeleteEncrypted::taskFailed);
job->start();
}
void PropagateRemoteDeleteEncrypted::slotFolderEncryptedIdReceived(const QStringList &list)
{
qCDebug(PROPAGATE_REMOVE_ENCRYPTED) << "Received id of folder, trying to lock it so we can prepare the metadata";
auto job = qobject_cast<LsColJob *>(sender());
const ExtraFolderInfo folderInfo = job->_folderInfos.value(list.first());
slotTryLock(folderInfo.fileId);
}
void PropagateRemoteDeleteEncrypted::slotTryLock(const QByteArray &folderId)
{
auto lockJob = new LockEncryptFolderApiJob(_propagator->account(), folderId, this);
connect(lockJob, &LockEncryptFolderApiJob::success, this, &PropagateRemoteDeleteEncrypted::slotFolderLockedSuccessfully);
connect(lockJob, &LockEncryptFolderApiJob::error, this, &PropagateRemoteDeleteEncrypted::taskFailed);
lockJob->start();
}
void PropagateRemoteDeleteEncrypted::slotFolderLockedSuccessfully(const QByteArray &folderId, const QByteArray &token)
{
qCDebug(PROPAGATE_REMOVE_ENCRYPTED) << "Folder id" << folderId << "Locked Successfully for Upload, Fetching Metadata";
_folderLocked = true;
_folderToken = token;
_folderId = folderId;
auto job = new GetMetadataApiJob(_propagator->account(), _folderId);
connect(job, &GetMetadataApiJob::jsonReceived, this, &PropagateRemoteDeleteEncrypted::slotFolderEncryptedMetadataReceived);
connect(job, &GetMetadataApiJob::error, this, &PropagateRemoteDeleteEncrypted::taskFailed);
job->start();
}
void PropagateRemoteDeleteEncrypted::slotFolderEncryptedMetadataReceived(const QJsonDocument &json, int statusCode)
{
if (statusCode == 404) {
qCDebug(PROPAGATE_REMOVE_ENCRYPTED) << "Metadata not found, ignoring.";
unlockFolder();
return;
}
qCDebug(PROPAGATE_REMOVE_ENCRYPTED) << "Metadata Received, Preparing it for the new file.";
// Encrypt File!
FolderMetadata metadata(_propagator->account(), json.toJson(QJsonDocument::Compact), statusCode);
QFileInfo info(_propagator->_localDir + QDir::separator() + _item->_file);
const QString fileName = info.fileName();
// Find existing metadata for this file
bool found = false;
const QVector<EncryptedFile> files = metadata.files();
for (const EncryptedFile &file : files) {
if (file.encryptedFilename == fileName) {
metadata.removeEncryptedFile(file);
found = true;
break;
}
}
if (!found) {
// The removed file was not in the JSON so nothing else to do
unlockFolder();
}
qCDebug(PROPAGATE_REMOVE_ENCRYPTED) << "Metadata updated, sending to the server.";
auto job = new UpdateMetadataApiJob(_propagator->account(),
_folderId,
metadata.encryptedMetadata(),
_folderToken);
connect(job, &UpdateMetadataApiJob::success, this, &PropagateRemoteDeleteEncrypted::unlockFolder);
connect(job, &UpdateMetadataApiJob::error, this, &PropagateRemoteDeleteEncrypted::taskFailed);
job->start();
}
void PropagateRemoteDeleteEncrypted::unlockFolder()
{
qCDebug(PROPAGATE_REMOVE_ENCRYPTED) << "Unlocking folder" << _folderId;
auto unlockJob = new UnlockEncryptFolderApiJob(_propagator->account(),
_folderId, _folderToken, this);
connect(unlockJob, &UnlockEncryptFolderApiJob::success, [this] {
qCDebug(PROPAGATE_REMOVE_ENCRYPTED) << "Folder successfully unlocked" << _folderId;
_folderLocked = false;
emit finished(true);
});
connect(unlockJob, &UnlockEncryptFolderApiJob::error, this, &PropagateRemoteDeleteEncrypted::taskFailed);
unlockJob->start();
}
void PropagateRemoteDeleteEncrypted::taskFailed()
{
qCDebug(PROPAGATE_REMOVE_ENCRYPTED) << "Task failed of job" << sender();
if (_folderLocked) {
unlockFolder();
} else {
emit finished(false);
}
}

View file

@ -0,0 +1,40 @@
#ifndef PROPAGATEREMOTEDELETEENCRYPTED_H
#define PROPAGATEREMOTEDELETEENCRYPTED_H
#include <QObject>
#include <QElapsedTimer>
#include "syncfileitem.h"
namespace OCC {
class OwncloudPropagator;
class PropagateRemoteDeleteEncrypted : public QObject
{
Q_OBJECT
public:
PropagateRemoteDeleteEncrypted(OwncloudPropagator *_propagator, SyncFileItemPtr item, QObject *parent);
void start();
signals:
void finished(bool success);
private:
void slotFolderEncryptedIdReceived(const QStringList &list);
void slotTryLock(const QByteArray &folderId);
void slotFolderLockedSuccessfully(const QByteArray &fileId, const QByteArray &token);
void slotFolderEncryptedMetadataReceived(const QJsonDocument &json, int statusCode);
void unlockFolder();
void taskFailed();
OwncloudPropagator *_propagator;
SyncFileItemPtr _item;
QByteArray _folderToken;
QByteArray _folderId;
bool _folderLocked = false;
};
}
#endif // PROPAGATEREMOTEDELETEENCRYPTED_H

View file

@ -24,7 +24,7 @@
namespace OCC {
Q_LOGGING_CATEGORY(lcPropagateRemoteMkdir, "sync.propagator.remotemkdir", QtInfoMsg)
Q_LOGGING_CATEGORY(lcPropagateRemoteMkdir, "nextcloud.sync.propagator.remotemkdir", QtInfoMsg)
void PropagateRemoteMkdir::start()
{

View file

@ -25,8 +25,8 @@
namespace OCC {
Q_LOGGING_CATEGORY(lcMoveJob, "sync.networkjob.move", QtInfoMsg)
Q_LOGGING_CATEGORY(lcPropagateRemoteMove, "sync.propagator.remotemove", QtInfoMsg)
Q_LOGGING_CATEGORY(lcMoveJob, "nextcloud.sync.networkjob.move", QtInfoMsg)
Q_LOGGING_CATEGORY(lcPropagateRemoteMove, "nextcloud.sync.propagator.remotemove", QtInfoMsg)
MoveJob::MoveJob(AccountPtr account, const QString &path,
const QString &destination, QObject *parent)

View file

@ -14,6 +14,7 @@
#include "config.h"
#include "propagateupload.h"
#include "propagateuploadencrypted.h"
#include "owncloudpropagator_p.h"
#include "networkjobs.h"
#include "account.h"
@ -26,20 +27,25 @@
#include "syncengine.h"
#include "propagateremotedelete.h"
#include "common/asserts.h"
#include "networkjobs.h"
#include "clientsideencryption.h"
#include "clientsideencryptionjobs.h"
#include <QNetworkAccessManager>
#include <QFileInfo>
#include <QDir>
#include <QJsonDocument>
#include <QJsonObject>
#include <QFileInfo>
#include <cmath>
#include <cstring>
namespace OCC {
Q_LOGGING_CATEGORY(lcPutJob, "sync.networkjob.put", QtInfoMsg)
Q_LOGGING_CATEGORY(lcPollJob, "sync.networkjob.poll", QtInfoMsg)
Q_LOGGING_CATEGORY(lcPropagateUpload, "sync.propagator.upload", QtInfoMsg)
Q_LOGGING_CATEGORY(lcPutJob, "nextcloud.sync.networkjob.put", QtInfoMsg)
Q_LOGGING_CATEGORY(lcPollJob, "nextcloud.sync.networkjob.poll", QtInfoMsg)
Q_LOGGING_CATEGORY(lcPropagateUpload, "nextcloud.sync.propagator.upload", QtInfoMsg)
/**
* We do not want to upload files that are currently being modified.
@ -163,38 +169,73 @@ void PropagateUploadFileCommon::setDeleteExisting(bool enabled)
_deleteExisting = enabled;
}
void PropagateUploadFileCommon::start()
{
if (propagator()->account()->capabilities().clientSideEncryptionAvaliable()) {
_uploadEncryptedHelper = new PropagateUploadEncrypted(propagator(), _item);
connect(_uploadEncryptedHelper, &PropagateUploadEncrypted::folerNotEncrypted,
this, &PropagateUploadFileCommon::setupUnencryptedFile);
connect(_uploadEncryptedHelper, &PropagateUploadEncrypted::finalized,
this, &PropagateUploadFileCommon::setupEncryptedFile);
connect(_uploadEncryptedHelper, &PropagateUploadEncrypted::error,
[]{ qCDebug(lcPropagateUpload) << "Error setting up encryption."; });
_uploadEncryptedHelper->start();
} else {
setupUnencryptedFile();
}
}
void PropagateUploadFileCommon::setupEncryptedFile(const QString& path, const QString& filename, quint64 size)
{
qCDebug(lcPropagateUpload) << "Starting to upload encrypted file" << path << filename << size;
_uploadingEncrypted = true;
_fileToUpload._path = path;
_fileToUpload._file = filename;
_fileToUpload._size = size;
startUploadFile();
}
void PropagateUploadFileCommon::setupUnencryptedFile()
{
_uploadingEncrypted = false;
_fileToUpload._file = _item->_file;
_fileToUpload._size = _item->_size;
_fileToUpload._path = propagator()->getFilePath(_fileToUpload._file);
startUploadFile();
}
void PropagateUploadFileCommon::startUploadFile() {
if (propagator()->_abortRequested.fetchAndAddRelaxed(0)) {
return;
}
// Check if the specific file can be accessed
if (propagator()->hasCaseClashAccessibilityProblem(_item->_file)) {
if (propagator()->hasCaseClashAccessibilityProblem(_fileToUpload._file)) {
done(SyncFileItem::NormalError, tr("File %1 cannot be uploaded because another file with the same name, differing only in case, exists").arg(QDir::toNativeSeparators(_item->_file)));
return;
}
// Check if we believe that the upload will fail due to remote quota limits
const quint64 quotaGuess = propagator()->_folderQuota.value(
QFileInfo(_item->_file).path(), std::numeric_limits<quint64>::max());
if (_item->_size > quotaGuess) {
QFileInfo(_fileToUpload._file).path(), std::numeric_limits<quint64>::max());
if (_fileToUpload._size > quotaGuess) {
// Necessary for blacklisting logic
_item->_httpErrorCode = 507;
emit propagator()->insufficientRemoteStorage();
done(SyncFileItem::DetailError, tr("Upload of %1 exceeds the quota for the folder").arg(Utility::octetsToString(_item->_size)));
done(SyncFileItem::DetailError, tr("Upload of %1 exceeds the quota for the folder").arg(Utility::octetsToString(_fileToUpload._size)));
return;
}
propagator()->_activeJobList.append(this);
if (!_deleteExisting) {
qDebug() << "Running the compute checksum";
return slotComputeContentChecksum();
}
qDebug() << "Deleting the current";
auto job = new DeleteJob(propagator()->account(),
propagator()->_remoteFolder + _item->_file,
propagator()->_remoteFolder + _fileToUpload._file,
this);
_jobs.append(job);
connect(job, &DeleteJob::finishedSignal, this, &PropagateUploadFileCommon::slotComputeContentChecksum);
@ -204,6 +245,8 @@ void PropagateUploadFileCommon::start()
void PropagateUploadFileCommon::slotComputeContentChecksum()
{
qDebug() << "Tryint to compute the checksum of the file";
qDebug() << "Still trying to understand if this is the local file or the uploaded one";
if (propagator()->_abortRequested.fetchAndAddRelaxed(0)) {
return;
}
@ -211,12 +254,17 @@ void PropagateUploadFileCommon::slotComputeContentChecksum()
const QString filePath = propagator()->getFilePath(_item->_file);
// remember the modtime before checksumming to be able to detect a file
// change during the checksum calculation
// change during the checksum calculation - This goes inside of the _item->_file
// and not the _fileToUpload because we are checking the original file, not there
// probably temporary one.
_item->_modtime = FileSystem::getModTime(filePath);
QByteArray checksumType = contentChecksumType();
// Maybe the discovery already computed the checksum?
// Should I compute the checksum of the original (_item->_file)
// or the maybe-modified? (_fileToUpload._file) ?
QByteArray existingChecksumType, existingChecksum;
parseChecksumHeader(_item->_checksumHeader, &existingChecksumType, &existingChecksum);
if (existingChecksumType == checksumType) {
@ -276,31 +324,41 @@ void PropagateUploadFileCommon::slotStartUpload(const QByteArray &transmissionCh
_item->_checksumHeader = _transmissionChecksumHeader;
}
const QString fullFilePath = propagator()->getFilePath(_item->_file);
const QString fullFilePath = _fileToUpload._path;
const QString originalFilePath = propagator()->getFilePath(_item->_file);
if (!FileSystem::fileExists(fullFilePath)) {
done(SyncFileItem::SoftError, tr("File Removed"));
if (_uploadingEncrypted) {
_uploadEncryptedHelper->unlockFolder();
}
done(SyncFileItem::SoftError, tr("File Removed (start upload) %1").arg(fullFilePath));
return;
}
time_t prevModtime = _item->_modtime; // the _item value was set in PropagateUploadFile::start()
// but a potential checksum calculation could have taken some time during which the file could
// have been changed again, so better check again here.
_item->_modtime = FileSystem::getModTime(fullFilePath);
_item->_modtime = FileSystem::getModTime(originalFilePath);
if (prevModtime != _item->_modtime) {
propagator()->_anotherSyncNeeded = true;
if (_uploadingEncrypted) {
_uploadEncryptedHelper->unlockFolder();
}
qDebug() << "prevModtime" << prevModtime << "Curr" << _item->_modtime;
done(SyncFileItem::SoftError, tr("Local file changed during syncing. It will be resumed."));
return;
}
quint64 fileSize = FileSystem::getSize(fullFilePath);
_item->_size = fileSize;
_fileToUpload._size = fileSize;
// But skip the file if the mtime is too close to 'now'!
// That usually indicates a file that is still being changed
// or not yet fully copied to the destination.
if (fileIsStillChanging(*_item)) {
propagator()->_anotherSyncNeeded = true;
if (_uploadingEncrypted) {
_uploadEncryptedHelper->unlockFolder();
}
done(SyncFileItem::SoftError, tr("Local file changed during sync."));
return;
}
@ -523,17 +581,20 @@ void PropagateUploadFileCommon::commonErrorHandling(AbstractNetworkJob *job)
// Insufficient remote storage.
if (_item->_httpErrorCode == 507) {
// Update the quota expectation
/* store the quota for the real local file using the information
* on the file to upload, that could have been modified by
* filters or something. */
const auto path = QFileInfo(_item->_file).path();
auto quotaIt = propagator()->_folderQuota.find(path);
if (quotaIt != propagator()->_folderQuota.end()) {
quotaIt.value() = qMin(quotaIt.value(), _item->_size - 1);
quotaIt.value() = qMin(quotaIt.value(), _fileToUpload._size - 1);
} else {
propagator()->_folderQuota[path] = _item->_size - 1;
propagator()->_folderQuota[path] = _fileToUpload._size - 1;
}
// Set up the error
status = SyncFileItem::DetailError;
errorString = tr("Upload of %1 exceeds the quota for the folder").arg(Utility::octetsToString(_item->_size));
errorString = tr("Upload of %1 exceeds the quota for the folder").arg(Utility::octetsToString(_fileToUpload._size));
emit propagator()->insufficientRemoteStorage();
}
@ -610,15 +671,18 @@ QMap<QByteArray, QByteArray> PropagateUploadFileCommon::headers()
void PropagateUploadFileCommon::finalize()
{
qDebug() << "Finalizing the upload. Check later if this is encrypted";
_finished = true;
// Update the quota, if known
auto quotaIt = propagator()->_folderQuota.find(QFileInfo(_item->_file).path());
if (quotaIt != propagator()->_folderQuota.end())
quotaIt.value() -= _item->_size;
quotaIt.value() -= _fileToUpload._size;
// Update the database entry
if (!propagator()->_journal->setFileRecord(_item->toSyncJournalFileRecordWithInode(propagator()->getFilePath(_item->_file)))) {
// Update the database entry - use the local file, not the temporary one.
const auto filePath = propagator()->getFilePath(_item->_file);
const auto fileRecord = _item->toSyncJournalFileRecordWithInode(filePath);
if (!propagator()->_journal->setFileRecord(fileRecord)) {
done(SyncFileItem::FatalError, tr("Error writing metadata to the database"));
return;
}
@ -627,6 +691,9 @@ void PropagateUploadFileCommon::finalize()
propagator()->_journal->setUploadInfo(_item->_file, SyncJournalDb::UploadInfo());
propagator()->_journal->commit("upload file start");
if (_uploadingEncrypted) {
_uploadEncryptedHelper->unlockFolder();
}
done(SyncFileItem::Success);
}

View file

@ -182,6 +182,8 @@ signals:
void finishedSignal();
};
class PropagateUploadEncrypted;
/**
* @brief The PropagateUploadFileCommon class is the code common between all chunking algorithms
* @ingroup libsync
@ -211,6 +213,20 @@ protected:
bool _finished BITFIELD(1); /// Tells that all the jobs have been finished
bool _deleteExisting BITFIELD(1);
quint64 _abortCount; /// Keep track of number of aborted items
/* This is a minified version of the SyncFileItem,
* that holds only the specifics about the file that's
* being uploaded.
*
* This is needed if we wanna apply changes on the file
* that's being uploaded while keeping the original on disk.
*/
struct UploadFileInfo {
QString _file; /// I'm still unsure if I should use a SyncFilePtr here.
QString _path; /// the full path on disk.
quint64 _size;
};
UploadFileInfo _fileToUpload;
QByteArray _transmissionChecksumHeader;
public:
@ -219,6 +235,8 @@ public:
, _finished(false)
, _deleteExisting(false)
, _abortCount(0)
, _uploadEncryptedHelper(0)
, _uploadingEncrypted(false)
{
}
@ -230,8 +248,12 @@ public:
*/
void setDeleteExisting(bool enabled);
/* start should setup the file, path and size that will be send to the server */
void start() Q_DECL_OVERRIDE;
void setupEncryptedFile(const QString& path, const QString& filename, quint64 size);
void setupUnencryptedFile();
void startUploadFile();
void callUnlockFolder();
bool isLikelyFinishedQuickly() Q_DECL_OVERRIDE { return _item->_size < propagator()->smallFileSize(); }
private slots:
@ -277,6 +299,9 @@ protected:
// Bases headers that need to be sent with every chunk
QMap<QByteArray, QByteArray> headers();
private:
PropagateUploadEncrypted *_uploadEncryptedHelper;
bool _uploadingEncrypted;
};
/**

View file

@ -0,0 +1,271 @@
#include "propagateuploadencrypted.h"
#include "clientsideencryptionjobs.h"
#include "networkjobs.h"
#include "clientsideencryption.h"
#include "account.h"
#include <QFileInfo>
#include <QDir>
#include <QUrl>
#include <QFile>
#include <QTemporaryFile>
#include <QLoggingCategory>
#include <QMimeDatabase>
namespace OCC {
Q_LOGGING_CATEGORY(lcPropagateUploadEncrypted, "nextcloud.sync.propagator.upload.encrypted", QtInfoMsg)
PropagateUploadEncrypted::PropagateUploadEncrypted(OwncloudPropagator *propagator, SyncFileItemPtr item)
: _propagator(propagator),
_item(item),
_metadata(nullptr)
{
}
void PropagateUploadEncrypted::start()
{
/* If the file is in a encrypted-enabled nextcloud instance, we need to
* do the long road: Fetch the folder status of the encrypted bit,
* if it's encrypted, find the ID of the folder.
* lock the folder using it's id.
* download the metadata
* update the metadata
* upload the file
* upload the metadata
* unlock the folder.
*
* If the folder is unencrypted we just follow the old way.
*/
qCDebug(lcPropagateUploadEncrypted) << "Starting to send an encrypted file!";
QFileInfo info(_item->_file);
auto getEncryptedStatus = new GetFolderEncryptStatusJob(_propagator->account(),
info.path());
connect(getEncryptedStatus, &GetFolderEncryptStatusJob::encryptStatusFolderReceived,
this, &PropagateUploadEncrypted::slotFolderEncryptedStatusFetched);
connect(getEncryptedStatus, &GetFolderEncryptStatusJob::encryptStatusError,
this, &PropagateUploadEncrypted::slotFolderEncryptedStatusError);
getEncryptedStatus->start();
}
void PropagateUploadEncrypted::slotFolderEncryptedStatusFetched(const QString &folder, bool isEncrypted)
{
qCDebug(lcPropagateUploadEncrypted) << "Encrypted Status Fetched" << folder << isEncrypted;
/* We are inside an encrypted folder, we need to find it's Id. */
if (isEncrypted) {
qCDebug(lcPropagateUploadEncrypted) << "Folder is encrypted, let's get the Id from it.";
auto job = new LsColJob(_propagator->account(), folder, this);
job->setProperties({"resourcetype", "http://owncloud.org/ns:fileid"});
connect(job, &LsColJob::directoryListingSubfolders, this, &PropagateUploadEncrypted::slotFolderEncryptedIdReceived);
connect(job, &LsColJob::finishedWithError, this, &PropagateUploadEncrypted::slotFolderEncryptedIdError);
job->start();
} else {
qCDebug(lcPropagateUploadEncrypted) << "Folder is not encrypted, getting back to default.";
emit folerNotEncrypted();
}
}
/* We try to lock a folder, if it's locked we try again in one second.
* if it's still locked we try again in one second. looping untill one minute.
* -> fail.
* the 'loop': /
* slotFolderEncryptedIdReceived -> slotTryLock -> lockError -> stillTime? -> slotTryLock
* \
* -> success.
*/
void PropagateUploadEncrypted::slotFolderEncryptedIdReceived(const QStringList &list)
{
qCDebug(lcPropagateUploadEncrypted) << "Received id of folder, trying to lock it so we can prepare the metadata";
auto job = qobject_cast<LsColJob *>(sender());
const auto& folderInfo = job->_folderInfos.value(list.first());
_folderLockFirstTry.start();
slotTryLock(folderInfo.fileId);
}
void PropagateUploadEncrypted::slotTryLock(const QByteArray& fileId)
{
auto *lockJob = new LockEncryptFolderApiJob(_propagator->account(), fileId, this);
connect(lockJob, &LockEncryptFolderApiJob::success, this, &PropagateUploadEncrypted::slotFolderLockedSuccessfully);
connect(lockJob, &LockEncryptFolderApiJob::error, this, &PropagateUploadEncrypted::slotFolderLockedError);
lockJob->start();
}
void PropagateUploadEncrypted::slotFolderLockedSuccessfully(const QByteArray& fileId, const QByteArray& token)
{
qCDebug(lcPropagateUploadEncrypted) << "Folder" << fileId << "Locked Successfully for Upload, Fetching Metadata";
// Should I use a mutex here?
_currentLockingInProgress = true;
_folderToken = token;
_folderId = fileId;
auto job = new GetMetadataApiJob(_propagator->account(), _folderId);
connect(job, &GetMetadataApiJob::jsonReceived,
this, &PropagateUploadEncrypted::slotFolderEncryptedMetadataReceived);
connect(job, &GetMetadataApiJob::error,
this, &PropagateUploadEncrypted::slotFolderEncryptedMetadataError);
job->start();
}
void PropagateUploadEncrypted::slotFolderEncryptedMetadataError(const QByteArray& fileId, int httpReturnCode)
{
qCDebug(lcPropagateUploadEncrypted()) << "Error Getting the encrypted metadata. unlock the folder.";
unlockFolder();
}
void PropagateUploadEncrypted::slotFolderEncryptedMetadataReceived(const QJsonDocument &json, int statusCode)
{
qCDebug(lcPropagateUploadEncrypted) << "Metadata Received, Preparing it for the new file." << json.toVariant();
// Encrypt File!
_metadata = new FolderMetadata(_propagator->account(), json.toJson(QJsonDocument::Compact), statusCode);
QFileInfo info(_propagator->_localDir + QDir::separator() + _item->_file);
const QString fileName = info.fileName();
// Find existing metadata for this file
bool found = false;
EncryptedFile encryptedFile;
const QVector<EncryptedFile> files = _metadata->files();
for(const EncryptedFile &file : files) {
if (file.originalFilename == fileName) {
encryptedFile = file;
found = true;
}
}
// New encrypted file so set it all up!
if (!found) {
encryptedFile.encryptionKey = EncryptionHelper::generateRandom(16);
encryptedFile.encryptedFilename = EncryptionHelper::generateRandomFilename();
encryptedFile.initializationVector = EncryptionHelper::generateRandom(16);
encryptedFile.fileVersion = 1;
encryptedFile.metadataKey = 1;
encryptedFile.originalFilename = fileName;
static thread_local QMimeDatabase mdb;
encryptedFile.mimetype = mdb.mimeTypeForFile(info).name().toLocal8Bit();
}
_item->_encryptedFileName = _item->_file.section(QLatin1Char('/'), 0, -2)
+ QLatin1Char('/') + encryptedFile.encryptedFilename;
qCDebug(lcPropagateUploadEncrypted) << "Creating the encrypted file.";
QFile input(info.absoluteFilePath());
QFile output(QDir::tempPath() + QDir::separator() + encryptedFile.encryptedFilename);
QByteArray tag;
bool encryptionResult = EncryptionHelper::fileEncryption(
encryptedFile.encryptionKey,
encryptedFile.initializationVector,
&input, &output, tag);
if (!encryptionResult) {
qCDebug(lcPropagateUploadEncrypted()) << "There was an error encrypting the file, aborting upload.";
unlockFolder();
return;
}
_completeFileName = output.fileName();
qCDebug(lcPropagateUploadEncrypted) << "Creating the metadata for the encrypted file.";
encryptedFile.authenticationTag = tag;
_metadata->addEncryptedFile(encryptedFile);
_encryptedFile = encryptedFile;
qCDebug(lcPropagateUploadEncrypted) << "Metadata created, sending to the server.";
if (statusCode == 404) {
auto job = new StoreMetaDataApiJob(_propagator->account(),
_folderId,
_metadata->encryptedMetadata());
connect(job, &StoreMetaDataApiJob::success, this, &PropagateUploadEncrypted::slotUpdateMetadataSuccess);
connect(job, &StoreMetaDataApiJob::error, this, &PropagateUploadEncrypted::slotUpdateMetadataError);
job->start();
} else {
auto job = new UpdateMetadataApiJob(_propagator->account(),
_folderId,
_metadata->encryptedMetadata(),
_folderToken);
connect(job, &UpdateMetadataApiJob::success, this, &PropagateUploadEncrypted::slotUpdateMetadataSuccess);
connect(job, &UpdateMetadataApiJob::error, this, &PropagateUploadEncrypted::slotUpdateMetadataError);
job->start();
}
}
void PropagateUploadEncrypted::slotUpdateMetadataSuccess(const QByteArray& fileId)
{
qCDebug(lcPropagateUploadEncrypted) << "Uploading of the metadata success, Encrypting the file";
QFileInfo outputInfo(_completeFileName);
qCDebug(lcPropagateUploadEncrypted) << "Encrypted Info:" << outputInfo.path() << outputInfo.fileName() << outputInfo.size();
qCDebug(lcPropagateUploadEncrypted) << "Finalizing the upload part, now the actuall uploader will take over";
emit finalized(outputInfo.path() + QLatin1Char('/') + outputInfo.fileName(),
_item->_file.section(QLatin1Char('/'), 0, -2) + QLatin1Char('/') + outputInfo.fileName(),
outputInfo.size());
}
void PropagateUploadEncrypted::slotUpdateMetadataError(const QByteArray& fileId, int httpErrorResponse)
{
qCDebug(lcPropagateUploadEncrypted) << "Update metadata error for folder" << fileId << "with error" << httpErrorResponse;
qCDebug(lcPropagateUploadEncrypted()) << "Unlocking the folder.";
unlockFolder();
}
void PropagateUploadEncrypted::slotFolderLockedError(const QByteArray& fileId, int httpErrorCode)
{
/* try to call the lock from 5 to 5 seconds
and fail if it's more than 5 minutes. */
QTimer::singleShot(5000, this, [this, fileId]{
if (!_currentLockingInProgress) {
qCDebug(lcPropagateUploadEncrypted) << "Error locking the folder while no other update is locking it up.";
qCDebug(lcPropagateUploadEncrypted) << "Perhaps another client locked it.";
qCDebug(lcPropagateUploadEncrypted) << "Abort";
return;
}
// Perhaps I should remove the elapsed timer if the lock is from this client?
if (_folderLockFirstTry.elapsed() > /* five minutes */ 1000 * 60 * 5 ) {
qCDebug(lcPropagateUploadEncrypted) << "One minute passed, ignoring more attemps to lock the folder.";
return;
}
slotTryLock(fileId);
});
qCDebug(lcPropagateUploadEncrypted) << "Folder" << fileId << "Coundn't be locked.";
}
void PropagateUploadEncrypted::slotFolderEncryptedIdError(QNetworkReply *r)
{
qCDebug(lcPropagateUploadEncrypted) << "Error retrieving the Id of the encrypted folder.";
}
void PropagateUploadEncrypted::slotFolderEncryptedStatusError(int error)
{
qCDebug(lcPropagateUploadEncrypted) << "Failed to retrieve the status of the folders." << error;
}
void PropagateUploadEncrypted::unlockFolder()
{
qDebug() << "Calling Unlock";
auto *unlockJob = new UnlockEncryptFolderApiJob(_propagator->account(),
_folderId, _folderToken, this);
connect(unlockJob, &UnlockEncryptFolderApiJob::success, []{ qDebug() << "Successfully Unlocked"; });
connect(unlockJob, &UnlockEncryptFolderApiJob::error, []{ qDebug() << "Unlock Error"; });
unlockJob->start();
}
} // namespace OCC

View file

@ -0,0 +1,81 @@
#ifndef PROPAGATEUPLOADENCRYPTED_H
#define PROPAGATEUPLOADENCRYPTED_H
#include <QObject>
#include <QString>
#include <QMap>
#include <QByteArray>
#include <QJsonDocument>
#include <QNetworkReply>
#include <QFile>
#include <QTemporaryFile>
#include "owncloudpropagator.h"
#include "clientsideencryption.h"
namespace OCC {
class FolderMetadata;
/* This class is used if the server supports end to end encryption.
* It will fire for *any* folder, encrypted or not, because when the
* client starts the upload request we don't know if the folder is
* encrypted on the server.
*
* emits:
* finalized() if the encrypted file is ready to be uploaded
* error() if there was an error with the encryption
* folerNotEncrypted() if the file is within a folder that's not encrypted.
*
*/
class PropagateUploadEncrypted : public QObject
{
Q_OBJECT
public:
PropagateUploadEncrypted(OwncloudPropagator *propagator, SyncFileItemPtr item);
void start();
/* unlocks the current folder that holds this file */
void unlockFolder();
// Used by propagateupload
QByteArray _folderToken;
QByteArray _folderId;
private slots:
void slotFolderEncryptedStatusFetched(const QString &folder, bool isEncrypted);
void slotFolderEncryptedStatusError(int error);
void slotFolderEncryptedIdReceived(const QStringList &list);
void slotFolderEncryptedIdError(QNetworkReply *r);
void slotFolderLockedSuccessfully(const QByteArray& fileId, const QByteArray& token);
void slotFolderLockedError(const QByteArray& fileId, int httpErrorCode);
void slotTryLock(const QByteArray& fileId);
void slotFolderEncryptedMetadataReceived(const QJsonDocument &json, int statusCode);
void slotFolderEncryptedMetadataError(const QByteArray& fileId, int httpReturnCode);
void slotUpdateMetadataSuccess(const QByteArray& fileId);
void slotUpdateMetadataError(const QByteArray& fileId, int httpReturnCode);
signals:
// Emmited after the file is encrypted and everythign is setup.
void finalized(const QString& path, const QString& filename, quint64 size);
void error();
// Emited if the file is not in a encrypted folder.
void folerNotEncrypted();
private:
OwncloudPropagator *_propagator;
SyncFileItemPtr _item;
QElapsedTimer _folderLockFirstTry;
bool _currentLockingInProgress = false;
QByteArray _generatedKey;
QByteArray _generatedIv;
FolderMetadata *_metadata;
EncryptedFile _encryptedFile;
QString _completeFileName;
};
}
#endif

View file

@ -137,12 +137,12 @@ void PropagateUploadFileNG::slotPropfindFinished()
++_currentChunk;
}
if (_sent > _item->_size) {
if (_sent > _fileToUpload._size) {
// Normally this can't happen because the size is xor'ed with the transfer id, and it is
// therefore impossible that there is more data on the server than on the file.
qCCritical(lcPropagateUpload) << "Inconsistency while resuming " << _item->_file
<< ": the size on the server (" << _sent << ") is bigger than the size of the file ("
<< _item->_size << ")";
<< _fileToUpload._size << ")";
startNewUpload();
return;
}
@ -220,7 +220,7 @@ void PropagateUploadFileNG::slotDeleteJobFinished()
void PropagateUploadFileNG::startNewUpload()
{
ASSERT(propagator()->_activeJobList.count(this) == 1);
_transferId = qrand() ^ _item->_modtime ^ (_item->_size << 16) ^ qHash(_item->_file);
_transferId = qrand() ^ _item->_modtime ^ (_fileToUpload._size << 16) ^ qHash(_fileToUpload._file);
_sent = 0;
_currentChunk = 0;
@ -234,7 +234,9 @@ void PropagateUploadFileNG::startNewUpload()
propagator()->_journal->setUploadInfo(_item->_file, pi);
propagator()->_journal->commit("Upload info");
QMap<QByteArray, QByteArray> headers;
headers["OC-Total-Length"] = QByteArray::number(_item->_size);
// But we should send the temporary (or something) one.
headers["OC-Total-Length"] = QByteArray::number(_fileToUpload._size);
auto job = new MkColJob(propagator()->account(), chunkUrl(), headers, this);
connect(job, SIGNAL(finished(QNetworkReply::NetworkError)),
@ -265,7 +267,7 @@ void PropagateUploadFileNG::startNextChunk()
if (propagator()->_abortRequested.fetchAndAddRelaxed(0))
return;
quint64 fileSize = _item->_size;
quint64 fileSize = _fileToUpload._size;
ENFORCE(fileSize >= _sent, "Sent data exceeds file size");
// prevent situation that chunk size is bigger then required one to send
@ -275,8 +277,9 @@ void PropagateUploadFileNG::startNextChunk()
Q_ASSERT(_jobs.isEmpty()); // There should be no running job anymore
_finished = true;
// Finish with a MOVE
// If we changed the file name, we must store the changed filename in the remote folder, not the original one.
QString destination = QDir::cleanPath(propagator()->account()->url().path() + QLatin1Char('/')
+ propagator()->account()->davPath() + propagator()->_remoteFolder + _item->_file);
+ propagator()->account()->davPath() + propagator()->_remoteFolder + _fileToUpload._file);
auto headers = PropagateUploadFileCommon::headers();
// "If-Match applies to the source, but we are interested in comparing the etag of the destination
@ -301,7 +304,7 @@ void PropagateUploadFileNG::startNextChunk()
}
auto device = new UploadDevice(&propagator()->_bandwidthManager);
const QString fileName = propagator()->getFilePath(_item->_file);
const QString fileName = _fileToUpload._path;
if (!device->prepareAndOpen(fileName, _sent, _currentChunkSize)) {
qCWarning(lcPropagateUpload) << "Could not prepare upload device: " << device->errorString();
@ -358,7 +361,7 @@ void PropagateUploadFileNG::slotPutFinished()
return;
}
ENFORCE(_sent <= _item->_size, "can't send more than size");
ENFORCE(_sent <= _fileToUpload._size, "can't send more than size");
// Adjust the chunk size for the time taken.
//
@ -391,11 +394,18 @@ void PropagateUploadFileNG::slotPutFinished()
<< propagator()->_chunkSize << "bytes";
}
bool finished = _sent == _item->_size;
bool finished = _sent == _fileToUpload._size;
// Check if the file still exists
/* Check if the file still exists,
* but we could be operating in a temporary file, so check both if
* the file to upload is different than the file on disk
*/
const QString fileToUploadPath = _fileToUpload._path;
const QString fullFilePath(propagator()->getFilePath(_item->_file));
if (!FileSystem::fileExists(fullFilePath)) {
bool fileExists = fileToUploadPath == fullFilePath ? FileSystem::fileExists(fullFilePath)
: (FileSystem::fileExists(fileToUploadPath) && FileSystem::fileExists(fullFilePath));
if (!fileExists) {
if (!finished) {
abortWithError(SyncFileItem::SoftError, tr("The local file was removed during sync."));
return;
@ -404,7 +414,7 @@ void PropagateUploadFileNG::slotPutFinished()
}
}
// Check whether the file changed since discovery.
// Check whether the file changed since discovery - this acts on the original file.
if (!FileSystem::verifyFileUnchanged(fullFilePath, _item->_size, _item->_modtime)) {
propagator()->_anotherSyncNeeded = true;
if (!finished) {

View file

@ -37,9 +37,9 @@ namespace OCC {
void PropagateUploadFileV1::doStartUpload()
{
_chunkCount = std::ceil(_item->_size / double(chunkSize()));
_chunkCount = std::ceil(_fileToUpload._size / double(chunkSize()));
_startChunk = 0;
_transferId = qrand() ^ _item->_modtime ^ (_item->_size << 16);
_transferId = qrand() ^ _item->_modtime ^ (_fileToUpload._size << 16);
const SyncJournalDb::UploadInfo progressInfo = propagator()->_journal->getUploadInfo(_item->_file);
@ -82,12 +82,12 @@ void PropagateUploadFileV1::startNextChunk()
// is sent last.
return;
}
quint64 fileSize = _item->_size;
quint64 fileSize = _fileToUpload._size;
auto headers = PropagateUploadFileCommon::headers();
headers["OC-Total-Length"] = QByteArray::number(fileSize);
headers["OC-Chunk-Size"] = QByteArray::number(quint64(chunkSize()));
QString path = _item->_file;
QString path = _fileToUpload._file;
UploadDevice *device = new UploadDevice(&propagator()->_bandwidthManager);
qint64 chunkStart = 0;
@ -122,7 +122,8 @@ void PropagateUploadFileV1::startNextChunk()
headers[checkSumHeaderC] = _transmissionChecksumHeader;
}
const QString fileName = propagator()->getFilePath(_item->_file);
const QString fileName = _fileToUpload._path;
qDebug() << "Trying to upload" << fileName;
if (!device->prepareAndOpen(fileName, chunkStart, currentChunkSize)) {
qCWarning(lcPropagateUpload) << "Could not prepare upload device: " << device->errorString();
@ -167,7 +168,6 @@ void PropagateUploadFileV1::startNextChunk()
}
}
if (_currentChunk + _startChunk >= _chunkCount - 1) {
// Don't do parallel upload of chunk if this might be the last chunk because the server cannot handle that
// https://github.com/owncloud/core/issues/11106
@ -235,9 +235,16 @@ void PropagateUploadFileV1::slotPutFinished()
QByteArray etag = getEtagFromReply(job->reply());
bool finished = etag.length() > 0;
// Check if the file still exists
/* Check if the file still exists,
* but we could be operating in a temporary file, so check both if
* the file to upload is different than the file on disk
*/
const QString fileToUploadPath = _fileToUpload._path;
const QString fullFilePath(propagator()->getFilePath(_item->_file));
if (!FileSystem::fileExists(fullFilePath)) {
bool fileExists = fileToUploadPath == fullFilePath ? FileSystem::fileExists(fullFilePath)
: (FileSystem::fileExists(fileToUploadPath) && FileSystem::fileExists(fullFilePath));
if (!fileExists) {
if (!finished) {
abortWithError(SyncFileItem::SoftError, tr("The local file was removed during sync."));
return;
@ -246,7 +253,7 @@ void PropagateUploadFileV1::slotPutFinished()
}
}
// Check whether the file changed since discovery.
// Check whether the file changed since discovery. the file check here is the original and not the temprary.
if (!FileSystem::verifyFileUnchanged(fullFilePath, _item->_size, _item->_modtime)) {
propagator()->_anotherSyncNeeded = true;
if (!finished) {

View file

@ -34,9 +34,9 @@
namespace OCC {
Q_LOGGING_CATEGORY(lcPropagateLocalRemove, "sync.propagator.localremove", QtInfoMsg)
Q_LOGGING_CATEGORY(lcPropagateLocalMkdir, "sync.propagator.localmkdir", QtInfoMsg)
Q_LOGGING_CATEGORY(lcPropagateLocalRename, "sync.propagator.localrename", QtInfoMsg)
Q_LOGGING_CATEGORY(lcPropagateLocalRemove, "nextcloud.sync.propagator.localremove", QtInfoMsg)
Q_LOGGING_CATEGORY(lcPropagateLocalMkdir, "nextcloud.sync.propagator.localmkdir", QtInfoMsg)
Q_LOGGING_CATEGORY(lcPropagateLocalRename, "nextcloud.sync.propagator.localrename", QtInfoMsg)
QByteArray localFileIdFromFullId(const QByteArray &id)
{

View file

@ -53,7 +53,7 @@
namespace OCC {
Q_LOGGING_CATEGORY(lcEngine, "sync.engine", QtInfoMsg)
Q_LOGGING_CATEGORY(lcEngine, "nextcloud.sync.engine", QtInfoMsg)
static const int s_touchedFilesMaxAgeMs = 15 * 1000;
bool SyncEngine::s_anySyncRunning = false;
@ -424,6 +424,7 @@ int SyncEngine::treewalkFile(csync_file_stat_t *file, csync_file_stat_t *other,
item->_file = fileUtf8;
}
item->_originalFile = item->_file;
item->_encryptedFileName = file->e2eMangledName;
if (item->_instruction == CSYNC_INSTRUCTION_NONE
|| (item->_instruction == CSYNC_INSTRUCTION_IGNORE && instruction != CSYNC_INSTRUCTION_NONE)) {

View file

@ -21,7 +21,7 @@
namespace OCC {
Q_LOGGING_CATEGORY(lcFileItem, "sync.fileitem", QtInfoMsg)
Q_LOGGING_CATEGORY(lcFileItem, "nextcloud.sync.fileitem", QtInfoMsg)
SyncJournalFileRecord SyncFileItem::toSyncJournalFileRecordWithInode(const QString &localFileName)
{
@ -35,6 +35,7 @@ SyncJournalFileRecord SyncFileItem::toSyncJournalFileRecordWithInode(const QStri
rec._remotePerm = _remotePerm;
rec._serverHasIgnoredFiles = _serverHasIgnoredFiles;
rec._checksumHeader = _checksumHeader;
rec._e2eMangledName = _encryptedFileName.toUtf8();
// Go through csync vio just to get the inode.
csync_file_stat_t fs;
@ -55,7 +56,7 @@ SyncJournalFileRecord SyncFileItem::toSyncJournalFileRecordWithInode(const QStri
SyncFileItemPtr SyncFileItem::fromSyncJournalFileRecord(const SyncJournalFileRecord &rec)
{
SyncFileItemPtr item(new SyncFileItem);
item->_file = rec._path;
item->_file = QString::fromUtf8(rec._path);
item->_inode = rec._inode;
item->_modtime = rec._modtime;
item->_type = rec._type;
@ -65,6 +66,7 @@ SyncFileItemPtr SyncFileItem::fromSyncJournalFileRecord(const SyncJournalFileRec
item->_remotePerm = rec._remotePerm;
item->_serverHasIgnoredFiles = rec._serverHasIgnoredFiles;
item->_checksumHeader = rec._checksumHeader;
item->_encryptedFileName = QString::fromUtf8(rec._e2eMangledName);
return item;
}

View file

@ -201,6 +201,11 @@ public:
// Variables useful for everybody
QString _file;
QString _renameTarget;
/// Whether there's end to end encryption on this file.
/// If the file is encrypted, the _encryptedFilename is
/// the encrypted name on the server.
QString _encryptedFileName;
ItemType _type BITFIELD(3);
Direction _direction BITFIELD(3);
bool _serverHasIgnoredFiles BITFIELD(1);

View file

@ -23,7 +23,7 @@
namespace OCC {
Q_LOGGING_CATEGORY(lcStatusTracker, "sync.statustracker", QtInfoMsg)
Q_LOGGING_CATEGORY(lcStatusTracker, "nextcloud.sync.statustracker", QtInfoMsg)
static int pathCompare( const QString& lhs, const QString& rhs )
{

2098
src/libsync/wordlist.cpp Normal file

File diff suppressed because it is too large Load diff

14
src/libsync/wordlist.h Normal file
View file

@ -0,0 +1,14 @@
#ifndef WORDLIST_H
#define WORDLIST_H
#include <QList>
#include <QString>
namespace OCC {
namespace WordList {
QStringList getRandomWords(int nr);
QString getUnifiedString(const QStringList& l);
}
}
#endif