Skip to content

Commit

Permalink
Merge pull request #1619 from lutraconsulting/1618-infinite-loop-sele…
Browse files Browse the repository at this point in the history
…ctive-sync

Fix inifnite loop when config is invalid
  • Loading branch information
tomasMizera committed Sep 1, 2021
2 parents 71b9236 + 4a08b4c commit be2a00b
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 5 deletions.
63 changes: 63 additions & 0 deletions app/test/testmerginapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ void TestMerginApi::initTestCase()
deleteRemoteProject( mApiExtra, mUsername, "testSelectiveSyncRemoveConfig" );
deleteRemoteProject( mApiExtra, mUsername, "testSelectiveSyncChangeSyncFolder" );
deleteRemoteProject( mApiExtra, mUsername, "testSelectiveSyncDisabledInConfig" );
deleteRemoteProject( mApiExtra, mUsername, "testSelectiveSyncCorruptedFormat" );

deleteLocalDir( mApi, "testExcludeFromSync" );
}
Expand Down Expand Up @@ -2181,6 +2182,68 @@ void TestMerginApi::testSelectiveSyncChangeSyncFolder()
delete serverMirrorProjects;
}

void TestMerginApi::testSelectiveSyncCorruptedFormat()
{

/*
* Case: Test what happens when someone uploads not valid config file (not valid json)
*
* We will create another API client that will serve as a server mirror, it will not use selective sync,
* but will simulate as if someone manipulated project from browser via Mergin
*/
QString serverMirrorDataPath = mApi->projectsPath() + "/" + "serverMirror";
QDir serverMirrorDataDir( serverMirrorDataPath );
if ( !serverMirrorDataDir.exists() )
serverMirrorDataDir.mkpath( serverMirrorDataPath );

LocalProjectsManager *serverMirrorProjects = new LocalProjectsManager( serverMirrorDataPath + "/" );
MerginApi *serverMirror = new MerginApi( *serverMirrorProjects, this );
serverMirror->setSupportsSelectiveSync( false );

// Create a project with photos and mergin-config
QString projectName = "testSelectiveSyncCorruptedFormat";

QString projectClient1 = mApi->projectsPath() + "/" + projectName;
QString projectClient2 = mApiExtra->projectsPath() + "/" + projectName;
QString projectServer = serverMirror->projectsPath() + "/" + projectName;

createRemoteProject( mApi, mUsername, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" );
downloadRemoteProject( mApi, mUsername, projectName );

// Create photo files
QDir dir;
QString photoPathClient1( projectClient1 + "/" + "photos" );
if ( !dir.exists( photoPathClient1 ) )
dir.mkpath( photoPathClient1 );

QFile file( photoPathClient1 + "/" + "photoC1-A.jpg" );
file.open( QIODevice::WriteOnly );
file.close();

QFile file1( photoPathClient1 + "/" + "photoC1-B.png" );
file1.open( QIODevice::WriteOnly );
file1.close();

uploadRemoteProject( mApi, mUsername, projectName );
downloadRemoteProject( serverMirror, mUsername, projectName );

// add corrupted config file
QString configFilePath = projectServer + "/" + "mergin-config.json";
QVERIFY( QFile::copy( mTestDataPath + "/mergin-config-corrupted.json", configFilePath ) );

uploadRemoteProject( serverMirror, mUsername, projectName );
downloadRemoteProject( mApiExtra, mUsername, projectName );

// client 2 should have all photos from client 1
QString photoPathClient2( projectClient2 + "/" + "photos" );

QFile fileExtra( photoPathClient2 + "/" + "photoC1-A.jpg" );
QVERIFY( fileExtra.exists() );

QFile fileExtra1( photoPathClient2 + "/" + "photoC1-B.png" );
QVERIFY( fileExtra1.exists() );
}

void TestMerginApi::testRegister()
{
QString password = mApi->userAuth()->password();
Expand Down
1 change: 1 addition & 0 deletions app/test/testmerginapi.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ class TestMerginApi: public QObject
void testSelectiveSyncRemoveConfig();
void testSelectiveSyncDisabledInConfig();
void testSelectiveSyncChangeSyncFolder();
void testSelectiveSyncCorruptedFormat();

void testRegister();

Expand Down
14 changes: 10 additions & 4 deletions core/merginapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ void MerginApi::cacheServerConfig()
transaction.replyDownloadItem->deleteLater();
transaction.replyDownloadItem = nullptr;

prepareDownloadConfig( projectFullName );
prepareDownloadConfig( projectFullName, true );
}
else
{
Expand Down Expand Up @@ -1953,7 +1953,7 @@ void MerginApi::startProjectUpdate( const QString &projectFullName )
downloadNextItem( projectFullName );
}

void MerginApi::prepareDownloadConfig( const QString &projectFullName )
void MerginApi::prepareDownloadConfig( const QString &projectFullName, bool downloaded )
{
Q_ASSERT( mTransactionalStatus.contains( projectFullName ) );
TransactionStatus &transaction = mTransactionalStatus[projectFullName];
Expand All @@ -1968,7 +1968,7 @@ void MerginApi::prepareDownloadConfig( const QString &projectFullName )

if ( serverContainsConfig )
{
if ( !transaction.config.isValid )
if ( !downloaded )
{
// we should have server config but we do not have it yet
return requestServerConfig( projectFullName );
Expand All @@ -1984,7 +1984,13 @@ void MerginApi::prepareDownloadConfig( const QString &projectFullName )

bool previousVersionContainedConfig = ( resOld != oldServerVersion.files.end() ) && !transaction.firstTimeDownload;

if ( serverContainsConfig && previousVersionContainedConfig )
if ( !transaction.config.isValid )
{
// if transaction is not valid, consider it as deleted
transaction.config.downloadMissingFiles = true;
CoreUtils::log( "MerginConfig", "Config has invalid structure, continuing as if project had no config" );
}
else if ( serverContainsConfig && previousVersionContainedConfig )
{
// config was there, check if there are changes
QString newChk = newServerVersion.fileInfo( sMerginConfigFile ).checksum;
Expand Down
2 changes: 1 addition & 1 deletion core/merginapi.h
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,7 @@ class MerginApi: public QObject
void startProjectUpdate( const QString &projectFullName );

//! Takes care of finding the correct config file, appends it to current transaction and proceeds with project update
void prepareDownloadConfig( const QString &projectFullName );
void prepareDownloadConfig( const QString &projectFullName, bool downloaded = false );
void requestServerConfig( const QString &projectFullName );

//! Starts download request of another item
Expand Down
4 changes: 4 additions & 0 deletions test/test_data/mergin-config-corrupted.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"input-selective-sync": true
"input-selective-sync-dir": "photos"
}

3 comments on commit be2a00b

@inputapp-bot
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

signed apk: armeabi-v7a (SDK: android-22)

@inputapp-bot
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

signed apk: arm64-v8a (SDK: android-22)

@inputapp-bot
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ios-2.15.210901100516 (SDK: ios-15)

Please sign in to comment.