Welcome to mirror list, hosted at ThFree Co, Russian Federation.

propagateremotedeleteencryptedrootfolder.cpp « libsync « src - github.com/nextcloud/desktop.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 6dfe3c1d3024f53ecc015ecf3c91c6341abfa1d7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
/*
 * Copyright (C) by Oleksandr Zolotov <alex@nextcloud.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
 * for more details.
 */

/*
 * Removing the root encrypted folder is consisted of multiple steps:
 * - 1st step is to obtain the folderID via LsColJob so it then can be used for the next step
 * - 2nd step is to lock the root folder useing the folderID from the previous step. !!! NOTE: If there are no nested items in the folder, this, and subsequent steps are skipped until step 7.
 * - 3rd step is to obtain the root folder's metadata (it contains list of nested files and folders)
 * - 4th step is to remove the nested files and folders from the metadata and send it to the server via UpdateMetadataApiJob
 * - 5th step is to trigger DeleteJob for every nested file and folder of the root folder
 * - 6th step is to unlock the root folder using the previously obtained token from locking
 * - 7th step is to decrypt and delete the root folder, because it is now possible as it has become empty
 */

#include <QFileInfo>
#include <QLoggingCategory>

#include "deletejob.h"
#include "clientsideencryptionjobs.h"
#include "clientsideencryption.h"
#include "encryptfolderjob.h"
#include "owncloudpropagator.h"
#include "propagateremotedeleteencryptedrootfolder.h"

namespace {
  const char* encryptedFileNamePropertyKey = "encryptedFileName";
}

using namespace OCC;

Q_LOGGING_CATEGORY(PROPAGATE_REMOVE_ENCRYPTED_ROOTFOLDER, "nextcloud.sync.propagator.remove.encrypted.rootfolder")

PropagateRemoteDeleteEncryptedRootFolder::PropagateRemoteDeleteEncryptedRootFolder(OwncloudPropagator *propagator, SyncFileItemPtr item, QObject *parent)
    : AbstractPropagateRemoteDeleteEncrypted(propagator, item, parent)
{

}

void PropagateRemoteDeleteEncryptedRootFolder::start()
{
    Q_ASSERT(_item->_isEncrypted);

    const bool listFilesResult = _propagator->_journal->listFilesInPath(_item->_file.toUtf8(), [this](const OCC::SyncJournalFileRecord &record) {
        _nestedItems[record._e2eMangledName] = record;
    });

    if (!listFilesResult || _nestedItems.isEmpty()) {
        // if the folder is empty, just decrypt and delete it
        decryptAndRemoteDelete();
        return;
    }

    startLsColJob(_item->_file);
}

void PropagateRemoteDeleteEncryptedRootFolder::slotFolderUnLockedSuccessfully(const QByteArray &folderId)
{
    AbstractPropagateRemoteDeleteEncrypted::slotFolderUnLockedSuccessfully(folderId);
    decryptAndRemoteDelete();
}

void PropagateRemoteDeleteEncryptedRootFolder::slotFolderEncryptedMetadataReceived(const QJsonDocument &json, int statusCode)
{
    if (statusCode == 404) {
        // we've eneded up having no metadata, but, _nestedItems is not empty since we went this far, let's proceed with removing the nested items without modifying the metadata
        qCDebug(PROPAGATE_REMOVE_ENCRYPTED_ROOTFOLDER) << "There is no metadata for this folder. Just remove it's nested items.";
        for (auto it = _nestedItems.constBegin(); it != _nestedItems.constEnd(); ++it) {
            deleteNestedRemoteItem(it.key());
        }
        return;
    }

    FolderMetadata metadata(_propagator->account(), json.toJson(QJsonDocument::Compact), statusCode);

    qCDebug(PROPAGATE_REMOVE_ENCRYPTED_ROOTFOLDER) << "It's a root encrypted folder. Let's remove nested items first.";

    metadata.removeAllEncryptedFiles();

    qCDebug(PROPAGATE_REMOVE_ENCRYPTED_ROOTFOLDER) << "Metadata updated, sending to the server.";

    auto job = new UpdateMetadataApiJob(_propagator->account(), _folderId, metadata.encryptedMetadata(), _folderToken);
    connect(job, &UpdateMetadataApiJob::success, this, [this](const QByteArray& fileId) {
        Q_UNUSED(fileId);
        for (auto it = _nestedItems.constBegin(); it != _nestedItems.constEnd(); ++it) {
            deleteNestedRemoteItem(it.key());
        }
    });
    connect(job, &UpdateMetadataApiJob::error, this, &PropagateRemoteDeleteEncryptedRootFolder::taskFailed);
    job->start();
}

void PropagateRemoteDeleteEncryptedRootFolder::slotDeleteNestedRemoteItemFinished()
{
    auto *deleteJob = qobject_cast<DeleteJob *>(QObject::sender());

    Q_ASSERT(deleteJob);

    if (!deleteJob) {
        return;
    }

    const QString encryptedFileName = deleteJob->property(encryptedFileNamePropertyKey).toString();

    if (!encryptedFileName.isEmpty()) {
        const auto nestedItem = _nestedItems.take(encryptedFileName);

        if (nestedItem.isValid()) {
            _propagator->_journal->deleteFileRecord(nestedItem._path, nestedItem._type == ItemTypeDirectory);
            _propagator->_journal->commit("Remote Remove");
        }
    }

    QNetworkReply::NetworkError err = deleteJob->reply()->error();

    const auto httpErrorCode = deleteJob->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
    _item->_responseTimeStamp = deleteJob->responseTimestamp();
    _item->_requestId = deleteJob->requestId();

    if (err != QNetworkReply::NoError && err != QNetworkReply::ContentNotFoundError) {
        storeFirstError(err);
        storeFirstErrorString(deleteJob->errorString());
        qCWarning(PROPAGATE_REMOVE_ENCRYPTED_ROOTFOLDER) << "Delete nested item finished with error" << err << ".";
    } else if (httpErrorCode != 204 && httpErrorCode != 404) {
        // A 404 reply is also considered a success here: We want to make sure
        // a file is gone from the server. It not being there in the first place
        // is ok. This will happen for files that are in the DB but not on
        // the server or the local file system.

        // Normally we expect "204 No Content"
        // If it is not the case, it might be because of a proxy or gateway intercepting the request, so we must
        // throw an error.
        storeFirstErrorString(tr("Wrong HTTP code returned by server. Expected 204, but received \"%1 %2\".")
                       .arg(httpErrorCode)
                       .arg(deleteJob->reply()->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString()));
        if (_item->_httpErrorCode == 0) {
            _item->_httpErrorCode = httpErrorCode;
        }

        qCWarning(PROPAGATE_REMOVE_ENCRYPTED_ROOTFOLDER) << "Delete nested item finished with error" << httpErrorCode << ".";
    }

    if (_nestedItems.size() == 0) {
        // we wait for all _nestedItems' DeleteJobs to finish, and then - fail if any of those jobs has failed
        if (networkError() != QNetworkReply::NetworkError::NoError || _item->_httpErrorCode != 0) {
            const int errorCode = networkError() != QNetworkReply::NetworkError::NoError ? networkError() : _item->_httpErrorCode;
            qCCritical(PROPAGATE_REMOVE_ENCRYPTED_ROOTFOLDER) << "Delete of nested items finished with error" << errorCode << ". Failing the entire sequence.";
            taskFailed();
            return;
        }
        unlockFolder();
    }
}

void PropagateRemoteDeleteEncryptedRootFolder::deleteNestedRemoteItem(const QString &filename)
{
    qCInfo(PROPAGATE_REMOVE_ENCRYPTED_ROOTFOLDER) << "Deleting nested encrypted remote item" << filename;

    auto deleteJob = new DeleteJob(_propagator->account(), _propagator->fullRemotePath(filename), this);
    deleteJob->setFolderToken(_folderToken);
    deleteJob->setProperty(encryptedFileNamePropertyKey, filename);

    connect(deleteJob, &DeleteJob::finishedSignal, this, &PropagateRemoteDeleteEncryptedRootFolder::slotDeleteNestedRemoteItemFinished);

    deleteJob->start();
}

void PropagateRemoteDeleteEncryptedRootFolder::decryptAndRemoteDelete()
{
    auto job = new OCC::SetEncryptionFlagApiJob(_propagator->account(), _item->_fileId, OCC::SetEncryptionFlagApiJob::Clear, this);
    connect(job, &OCC::SetEncryptionFlagApiJob::success, this, [this] (const QByteArray &fileId) {
        Q_UNUSED(fileId);
        deleteRemoteItem(_item->_file);
    });
    connect(job, &OCC::SetEncryptionFlagApiJob::error, this, [this] (const QByteArray &fileId, int httpReturnCode) {
        Q_UNUSED(fileId);
        _item->_httpErrorCode = httpReturnCode;
        taskFailed();
    });
    job->start();
}