mirror of
https://github.com/nextcloud/desktop.git
synced 2024-11-26 15:06:08 +03:00
Merge pull request #251 from nextcloud/clientSideEncryptionV4
Client side encryption v4.
This commit is contained in:
commit
64cbc88474
96 changed files with 19178 additions and 176 deletions
|
@ -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
13003
src/3rdparty/nlohmann/json.hpp
vendored
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -64,6 +64,7 @@ public:
|
|||
RemotePermissions _remotePerm;
|
||||
bool _serverHasIgnoredFiles;
|
||||
QByteArray _checksumHeader;
|
||||
QByteArray _e2eMangledName;
|
||||
};
|
||||
|
||||
bool OCSYNC_EXPORT
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -57,7 +57,7 @@ class QSocket;
|
|||
|
||||
namespace OCC {
|
||||
|
||||
Q_LOGGING_CATEGORY(lcApplication, "gui.application", QtInfoMsg)
|
||||
Q_LOGGING_CATEGORY(lcApplication, "nextcloud.gui.application", QtInfoMsg)
|
||||
|
||||
namespace {
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
|
||||
namespace OCC {
|
||||
|
||||
Q_LOGGING_CATEGORY(lcGuiCredentials, "gui.credentials", QtInfoMsg)
|
||||
Q_LOGGING_CATEGORY(lcGuiCredentials, "nextcloud.gui.credentials", QtInfoMsg)
|
||||
|
||||
namespace CredentialsFactory {
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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, "")
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
1530
src/libsync/clientsideencryption.cpp
Normal file
1530
src/libsync/clientsideencryption.cpp
Normal file
File diff suppressed because it is too large
Load diff
169
src/libsync/clientsideencryption.h
Normal file
169
src/libsync/clientsideencryption.h
Normal 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
|
455
src/libsync/clientsideencryptionjobs.cpp
Normal file
455
src/libsync/clientsideencryptionjobs.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
304
src/libsync/clientsideencryptionjobs.h
Normal file
304
src/libsync/clientsideencryptionjobs.h
Normal 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
|
|
@ -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";
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
namespace OCC {
|
||||
|
||||
Q_LOGGING_CATEGORY(lcTokenCredentials, "sync.credentials.token", QtInfoMsg)
|
||||
Q_LOGGING_CATEGORY(lcTokenCredentials, "nextcloud.sync.credentials.token", QtInfoMsg)
|
||||
|
||||
namespace {
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
144
src/libsync/propagatedownloadencrypted.cpp
Normal file
144
src/libsync/propagatedownloadencrypted.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
47
src/libsync/propagatedownloadencrypted.h
Normal file
47
src/libsync/propagatedownloadencrypted.h
Normal 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
|
|
@ -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,10 +66,24 @@ 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,
|
||||
propagator()->_remoteFolder + filename,
|
||||
this);
|
||||
connect(_job.data(), &DeleteJob::finishedSignal, this, &PropagateRemoteDelete::slotDeleteJobFinished);
|
||||
propagator()->_activeJobList.append(this);
|
||||
|
|
|
@ -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(); }
|
||||
|
|
130
src/libsync/propagateremotedeleteencrypted.cpp
Normal file
130
src/libsync/propagateremotedeleteencrypted.cpp
Normal 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);
|
||||
}
|
||||
}
|
40
src/libsync/propagateremotedeleteencrypted.h
Normal file
40
src/libsync/propagateremotedeleteencrypted.h
Normal 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
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
271
src/libsync/propagateuploadencrypted.cpp
Normal file
271
src/libsync/propagateuploadencrypted.cpp
Normal 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
|
81
src/libsync/propagateuploadencrypted.h
Normal file
81
src/libsync/propagateuploadencrypted.h
Normal 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
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
2098
src/libsync/wordlist.cpp
Normal file
File diff suppressed because it is too large
Load diff
14
src/libsync/wordlist.h
Normal file
14
src/libsync/wordlist.h
Normal 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
|
Loading…
Reference in a new issue