2021-01-05 13:48:30 +03:00
/*
* 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 :
* - 1 st step is to obtain the folderID via LsColJob so it then can be used for the next step
* - 2 nd 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.
* - 3 rd step is to obtain the root folder ' s metadata ( it contains list of nested files and folders )
* - 4 th step is to remove the nested files and folders from the metadata and send it to the server via UpdateMetadataApiJob
* - 5 th step is to trigger DeleteJob for every nested file and folder of the root folder
* - 6 th step is to unlock the root folder using the previously obtained token from locking
* - 7 th 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 ) {
2022-08-03 17:55:04 +03:00
const auto errorCode = ( networkError ( ) ! = QNetworkReply : : NetworkError : : NoError ? static_cast < int > ( networkError ( ) ) : _item - > _httpErrorCode ) ;
2021-01-05 13:48:30 +03:00
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 ( ) ;
}