diff options
-rw-r--r-- | src/common/utility.cpp | 28 | ||||
-rw-r--r-- | src/common/utility.h | 17 | ||||
-rw-r--r-- | src/libsync/owncloudpropagator.cpp | 5 | ||||
-rw-r--r-- | test/testsyncconflict.cpp | 9 | ||||
-rw-r--r-- | test/testutility.cpp | 21 |
5 files changed, 75 insertions, 5 deletions
diff --git a/src/common/utility.cpp b/src/common/utility.cpp index e8a3546f0..61de03ba8 100644 --- a/src/common/utility.cpp +++ b/src/common/utility.cpp @@ -553,7 +553,8 @@ QUrl Utility::concatUrlPath(const QUrl &url, const QString &concatPath, return tmpUrl; } -QString Utility::makeConflictFileName(const QString &fn, const QDateTime &dt) +QString Utility::makeConflictFileName( + const QString &fn, const QDateTime &dt, const QString &user) { QString conflictFileName(fn); // Add _conflict-XXXX before the extension. @@ -562,9 +563,15 @@ QString Utility::makeConflictFileName(const QString &fn, const QDateTime &dt) if (dotLocation <= conflictFileName.lastIndexOf('/') + 1) { dotLocation = conflictFileName.size(); } - QString timeString = dt.toString("yyyyMMdd-hhmmss"); - conflictFileName.insert(dotLocation, "_conflict-" + timeString); + QString conflictMarker = QStringLiteral("_conflict-"); + if (!user.isEmpty()) { + conflictMarker.append(sanitizeForFileName(user)); + conflictMarker.append('-'); + } + conflictMarker.append(dt.toString("yyyyMMdd-hhmmss")); + + conflictFileName.insert(dotLocation, conflictMarker); return conflictFileName; } @@ -606,4 +613,19 @@ QByteArray Utility::conflictFileBaseName(const QByteArray &conflictName) return ""; } +QString Utility::sanitizeForFileName(const QString &name) +{ + const auto invalid = QStringLiteral("/?<>\\:*|\""); + QString result; + result.reserve(name.size()); + for (const auto c : name) { + if (!invalid.contains(c) + && c.category() != QChar::Other_Control + && c.category() != QChar::Other_Format) { + result.append(c); + } + } + return result; +} + } // namespace OCC diff --git a/src/common/utility.h b/src/common/utility.h index 7247673ce..5e1ff930e 100644 --- a/src/common/utility.h +++ b/src/common/utility.h @@ -182,9 +182,24 @@ namespace Utility { with the given parent. If no parent is specified, the caller must destroy the settings */ OCSYNC_EXPORT std::unique_ptr<QSettings> settingsWithGroup(const QString &group, QObject *parent = 0); + /** Sanitizes a string that shall become part of a filename. + * + * Filters out reserved characters like + * - unicode control and format characters + * - reserved characters: /, ?, <, >, \, :, *, |, and " + * + * Warning: This does not sanitize the whole resulting string, so + * - unix reserved filenames ('.', '..') + * - trailing periods and spaces + * - windows reserved filenames ('CON' etc) + * will pass unchanged. + */ + OCSYNC_EXPORT QString sanitizeForFileName(const QString &name); + /** Returns a file name based on \a fn that's suitable for a conflict. */ - OCSYNC_EXPORT QString makeConflictFileName(const QString &fn, const QDateTime &dt); + OCSYNC_EXPORT QString makeConflictFileName( + const QString &fn, const QDateTime &dt, const QString &user); /** Returns whether a file name indicates a conflict file */ diff --git a/src/libsync/owncloudpropagator.cpp b/src/libsync/owncloudpropagator.cpp index 1604898a1..f693b1297 100644 --- a/src/libsync/owncloudpropagator.cpp +++ b/src/libsync/owncloudpropagator.cpp @@ -732,8 +732,11 @@ bool OwncloudPropagator::createConflict(const SyncFileItemPtr &item, QString renameError; auto conflictModTime = FileSystem::getModTime(fn); + QString conflictUserName; + if (account()->capabilities().uploadConflictFiles()) + conflictUserName = account()->davDisplayName(); QString conflictFileName = Utility::makeConflictFileName( - item->_file, Utility::qDateTimeFromTime_t(conflictModTime)); + item->_file, Utility::qDateTimeFromTime_t(conflictModTime), conflictUserName); QString conflictFilePath = getFilePath(conflictFileName); emit touchedFile(fn); diff --git a/test/testsyncconflict.cpp b/test/testsyncconflict.cpp index daf7eab0d..645bc466d 100644 --- a/test/testsyncconflict.cpp +++ b/test/testsyncconflict.cpp @@ -80,6 +80,12 @@ private slots: fakeFolder.remoteModifier().appendByte("A/a2"); fakeFolder.remoteModifier().appendByte("A/a2"); QVERIFY(fakeFolder.syncOnce()); + + // Verify that the conflict names don't have the user name + for (const auto &name : findConflicts(fakeFolder.currentLocalState().children["A"])) { + QVERIFY(!name.contains(fakeFolder.syncEngine().account()->davUser())); + } + QVERIFY(expectAndWipeConflict(fakeFolder.localModifier(), fakeFolder.currentLocalState(), "A/a1")); QVERIFY(expectAndWipeConflict(fakeFolder.localModifier(), fakeFolder.currentLocalState(), "A/a2")); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); @@ -121,6 +127,9 @@ private slots: QCOMPARE(conflictMap.size(), 2); QCOMPARE(Utility::conflictFileBaseName(conflictMap[a1FileId].toUtf8()), QByteArray("A/a1")); + // Check that the conflict file contains the username + QVERIFY(conflictMap[a1FileId].contains(QString("-%1-").arg(fakeFolder.syncEngine().account()->davUser()))); + QCOMPARE(remote.find(conflictMap[a1FileId])->contentChar, 'L'); QCOMPARE(remote.find("A/a1")->contentChar, 'R'); diff --git a/test/testutility.cpp b/test/testutility.cpp index c83b14199..112efda73 100644 --- a/test/testutility.cpp +++ b/test/testutility.cpp @@ -187,7 +187,28 @@ private slots: qunsetenv("OWNCLOUD_TEST_CASE_PRESERVING"); } + void testSanitizeForFileName_data() + { + QTest::addColumn<QString>("input"); + QTest::addColumn<QString>("output"); + + QTest::newRow("") + << "foobar" + << "foobar"; + QTest::newRow("") + << "a/b?c<d>e\\f:g*h|i\"j" + << "abcdefghij"; + QTest::newRow("") + << QString::fromLatin1("a\x01 b\x1f c\x80 d\x9f") + << "a b c d"; + } + void testSanitizeForFileName() + { + QFETCH(QString, input); + QFETCH(QString, output); + QCOMPARE(sanitizeForFileName(input), output); + } }; QTEST_GUILESS_MAIN(TestUtility) |