Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: bypass Qt's QFile::encodeName() in csync #12039

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 61 additions & 14 deletions src/common/filesystembase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@

#include <QCoreApplication>
#include <QDateTime>
#include <QDir>
#include <QFile>
#include <QSettings>
#include <QStorageInfo>

#include <sys/stat.h>
#include <sys/types.h>
Expand All @@ -35,6 +35,10 @@
#include <io.h>
#endif

#ifdef Q_OS_MAC
#include <CoreServices/CoreServices.h>
#endif

namespace {
// Regarding
// https://en.wikipedia.org/wiki/Comparison_of_file_systems#Limits
Expand All @@ -49,6 +53,16 @@ namespace OCC {

Q_LOGGING_CATEGORY(lcFileSystem, "sync.filesystem", QtInfoMsg)

QByteArray FileSystem::encodeFileName(const QString &fileName)
{
return fileName.toLocal8Bit();
}

QString FileSystem::decodeFileName(const char *localFileName)
{
return QString::fromLocal8Bit(localFileName);
}

QString FileSystem::longWinPath(const QString &inpath)
{
#ifndef Q_OS_WIN
Expand Down Expand Up @@ -198,9 +212,20 @@ bool FileSystem::uncheckedRenameReplace(const QString &originFileName,
{
Q_ASSERT(errorString);
#ifndef Q_OS_WIN

#ifdef Q_OS_MAC
// Don't use QFile::rename, it will normalize the destination filename to NFC
auto src = QFile::encodeName(originFileName);
auto dest = encodeFileName(destinationFileName);
if (::renameatx_np(AT_FDCWD, src.constData(), AT_FDCWD, dest.constData(), 0) != 0) {
*errorString = QString::fromLocal8Bit(strerror(errno));
qCWarning(lcFileSystem) << "Renaming temp file to final failed: " << *errorString;
return false;
}
#else
bool success;
QFile orig(originFileName);
// We want a rename that also overwites. QFile::rename does not overwite.
// We want a rename that also overwrites. QFile::rename does not overwrite.
// Qt 5.1 has QSaveFile::renameOverwrite we could use.
// ### FIXME
success = true;
Expand All @@ -218,6 +243,7 @@ bool FileSystem::uncheckedRenameReplace(const QString &originFileName,
qCWarning(lcFileSystem) << "Renaming temp file to final failed: " << *errorString;
return false;
}
#endif

#else //Q_OS_WIN
// You can not overwrite a read-only file on windows.
Expand Down Expand Up @@ -343,32 +369,53 @@ bool FileSystem::fileExists(const QString &filename, const QFileInfo &fileInfo)
#endif
bool re = fileInfo.exists();
// if the filename is different from the filename in fileInfo, the fileInfo is
// not valid. There needs to be one initialised here. Otherwise the incoming
// not valid. There needs to be one initialised here. Otherwise, the incoming
// fileInfo is re-used.
if (fileInfo.filePath() != filename) {
re = QFileInfo::exists(filename);
}
return re;
}

bool FileSystem::mkpath(const QString &parent, const QString &newDir)
{
#ifdef Q_OS_WIN
return QDir(parent).mkpath(newDir);
#else // POSIX
auto parts = newDir.split(u'/');
QString parentIt = parent;
while (!parts.isEmpty()) {
auto part = parts.takeFirst();
parentIt.append(u'/' + part);
if (::mkdir(encodeFileName(QDir::toNativeSeparators(parentIt)).constData(), 0777) != 0) {
if (errno != EEXIST) {
return false;
}
}
}
return true;
#endif
}

QString FileSystem::fileSystemForPath(const QString &path)
{
// See also QStorageInfo (Qt >=5.4) and GetVolumeInformationByHandleW (>= Vista)
QString drive = path.left(2);
if (!drive.endsWith(QLatin1Char(':')))
return QString();
drive.append(QLatin1Char('\\'));

const size_t fileSystemBufferSize = 4096;
TCHAR fileSystemBuffer[fileSystemBufferSize];
QString p = path;
while (true) {
if (!fileExists(p)) {
QFileInfo file(p);
p = file.absolutePath();
continue;
}
const QStorageInfo storage(p);
if (!storage.isValid() || !storage.isReady()) {
return {};
}

if (!GetVolumeInformationW(reinterpret_cast<LPCWSTR>(drive.utf16()), nullptr, 0, nullptr, nullptr, nullptr, fileSystemBuffer, fileSystemBufferSize)) {
return QString();
return QString::fromUtf8(storage.fileSystemType());
}
return QString::fromUtf16(reinterpret_cast<const char16_t *>(fileSystemBuffer));
}

#ifdef Q_OS_WIN
bool FileSystem::longPathsEnabledOnWindows()
{
static std::optional<bool> longPathsEnabledCached = {};
Expand Down
7 changes: 6 additions & 1 deletion src/common/filesystembase.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ OCSYNC_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcFileSystem)
namespace FileSystem {
OCSYNC_EXPORT Q_NAMESPACE;

QByteArray OCSYNC_EXPORT encodeFileName(const QString &fileName);
QString OCSYNC_EXPORT decodeFileName(const char *localFileName);

/**
* List of characters not allowd in filenames on Windows
*/
Expand Down Expand Up @@ -105,6 +108,8 @@ namespace FileSystem {
*/
bool OCSYNC_EXPORT fileExists(const QString &filename, const QFileInfo & = QFileInfo());

bool OCSYNC_EXPORT mkpath(const QString &parent, const QString &newDir);

/**
* @brief Rename the file \a originFileName to \a destinationFileName.
*
Expand Down Expand Up @@ -154,11 +159,11 @@ namespace FileSystem {

bool OCSYNC_EXPORT longPathsEnabledOnWindows();

#endif
/**
* Returns the file system used at the given path.
*/
QString OCSYNC_EXPORT fileSystemForPath(const QString &path);
#endif

/**
* Returns whether the file is a shortcut file (ends with .lnk)
Expand Down
4 changes: 1 addition & 3 deletions src/csync/std/c_time.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,9 @@

#include "common/filesystembase.h"

#include <QFile>

#ifdef HAVE_UTIMES
int c_utimes(const QString &uri, const struct timeval *times) {
int ret = utimes(QFile::encodeName(uri).constData(), times);
int ret = utimes(uri.toLocal8Bit().constData(), times);
return ret;
}
#else // HAVE_UTIMES
Expand Down
12 changes: 6 additions & 6 deletions src/csync/vio/csync_vio_local_unix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@

#include "csync.h"

#include "vio/csync_vio_local.h"
#include "common/filesystembase.h"
#include "common/vfs.h"
#include "vio/csync_vio_local.h"

#include <QtCore/QLoggingCategory>
#include <QtCore/QFile>

Q_LOGGING_CATEGORY(lcCSyncVIOLocal, "sync.csync.vio_local", QtInfoMsg)

Expand All @@ -48,7 +48,7 @@ struct csync_vio_handle_t {
csync_vio_handle_t *csync_vio_local_opendir(const QString &name) {
std::unique_ptr<csync_vio_handle_t> handle(new csync_vio_handle_t{});

auto dirname = QFile::encodeName(name);
auto dirname = OCC::FileSystem::encodeFileName(name);

handle->dh = opendir(dirname.constData());
if (!handle->dh) {
Expand Down Expand Up @@ -77,7 +77,7 @@ std::unique_ptr<csync_file_stat_t> csync_vio_local_readdir(csync_vio_handle_t *h
} while (qstrcmp(dirent->d_name, ".") == 0 || qstrcmp(dirent->d_name, "..") == 0);

file_stat.reset(new csync_file_stat_t);
file_stat->path = QFile::decodeName(dirent->d_name);
file_stat->path = OCC::FileSystem::decodeFileName(dirent->d_name);

/* Check for availability of d_type, see manpage. */
#if defined(_DIRENT_HAVE_D_TYPE) || defined(__APPLE__)
Expand Down Expand Up @@ -120,8 +120,8 @@ int csync_vio_local_stat(const QString &uri, csync_file_stat_t *buf)
{
struct stat sb;

if (lstat(QFile::encodeName(uri).constData(), &sb) < 0) {
return -1;
if (lstat(OCC::FileSystem::encodeFileName(uri).constData(), &sb) < 0) {
return -1;
}

switch (sb.st_mode & S_IFMT) {
Expand Down
20 changes: 20 additions & 0 deletions src/gui/folderman.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,22 @@ bool FolderMan::ensureJournalGone(const QString &journalDbFile)
return true;
}

bool FolderMan::ensureFilesystemSupported(const FolderDefinition &folderDefinition)
{
#ifndef Q_OS_MAC
return true;
#endif

QString filesystemType = FileSystem::fileSystemForPath(folderDefinition.localPath());
if (filesystemType != QStringLiteral("apfs")) {
QMessageBox::warning(nullptr, tr("Unsupported filesystem"), tr("On MacOS only Apple File System is supported."), QMessageBox::Ok);

return false;
}

return true;
}

SocketApi *FolderMan::socketApi()
{
return _socketApi.get();
Expand Down Expand Up @@ -491,6 +507,10 @@ Folder *FolderMan::addFolder(const AccountStatePtr &accountState, const FolderDe
return nullptr;
}

if (!ensureFilesystemSupported(definition)) {
return nullptr;
}

auto vfs = VfsPluginManager::instance().createVfsFromPlugin(folderDefinition.virtualFilesMode);
if (!vfs) {
qCWarning(lcFolderMan) << "Could not load plugin for mode" << folderDefinition.virtualFilesMode;
Expand Down
1 change: 1 addition & 0 deletions src/gui/folderman.h
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ class OWNCLOUDGUI_EXPORT FolderMan : public QObject
* @returns false if the journal could not be removed, true otherwise.
*/
static bool ensureJournalGone(const QString &journalDbFile);
static bool ensureFilesystemSupported(const FolderDefinition &folderDefinition);

/// Produce text for use in the tray tooltip
static QString trayTooltipStatusString(const SyncResult &result, bool paused);
Expand Down
4 changes: 1 addition & 3 deletions src/gui/socketapi/socketapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -235,9 +235,7 @@ void SocketApi::slotReadSocket()
static auto invalidListener = QSharedPointer<SocketListener>::create(nullptr);
const auto listener = _listeners.value(socket, invalidListener);
while (socket->canReadLine()) {
// Make sure to normalize the input from the socket to
// make sure that the path will match, especially on OS X.
QString line = QString::fromUtf8(socket->readLine()).normalized(QString::NormalizationForm_C);
QString line = QString::fromUtf8(socket->readLine());
// Note: do NOT use QString::trimmed() here! That will also remove any trailing spaces (which _are_ part of the filename)!
line.chop(1); // remove the '\n'

Expand Down
2 changes: 1 addition & 1 deletion src/libsync/propagateremotedelete.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class DeleteJob : public AbstractNetworkJob
};

/**
* @brief The PropagateRemoteDelete class
* @brief Propagate a local delete to the server
* @ingroup libsync
*/
class PropagateRemoteDelete : public PropagateItemJob
Expand Down
2 changes: 1 addition & 1 deletion src/libsync/propagateremotemkdir.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
namespace OCC {

/**
* @brief The PropagateRemoteMkdir class
* @brief Propagate a local mkdir to the server
* @ingroup libsync
*/
class PropagateRemoteMkdir : public PropagateItemJob
Expand Down
2 changes: 1 addition & 1 deletion src/libsync/propagateremotemove.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class MoveJob : public AbstractNetworkJob
};

/**
* @brief The PropagateRemoteMove class
* @brief Propagate a local move (or rename) to the server
* @ingroup libsync
*/
class PropagateRemoteMove : public PropagateItemJob
Expand Down
4 changes: 2 additions & 2 deletions src/libsync/propagatorjobs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,8 @@ void PropagateLocalMkdir::start()
done(SyncFileItem::NormalError, tr("Can not create local folder %1 because of a local file name clash with %2").arg(newDirStr, QDir::toNativeSeparators(clash.get())));
return;
}
QDir localDir(propagator()->localPath());
if (!localDir.mkpath(_item->localName())) {

if (!FileSystem::mkpath(propagator()->localPath(), _item->localName())) {
done(SyncFileItem::NormalError, tr("could not create folder %1").arg(newDirStr));
return;
}
Expand Down
6 changes: 3 additions & 3 deletions src/libsync/propagatorjobs.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ static const char checkSumHeaderC[] = "OC-Checksum";
static const char contentMd5HeaderC[] = "Content-MD5";

/**
* @brief Declaration of the other propagation jobs
* @brief Remove (or move to the trash) a file or folder that was removed remotely
* @ingroup libsync
*/
class PropagateLocalRemove : public PropagateItemJob
Expand All @@ -47,7 +47,7 @@ class PropagateLocalRemove : public PropagateItemJob
};

/**
* @brief The PropagateLocalMkdir class
* @brief Make a local directory after discovering it on the server
* @ingroup libsync
*/
class PropagateLocalMkdir : public PropagateItemJob
Expand All @@ -74,7 +74,7 @@ class PropagateLocalMkdir : public PropagateItemJob
};

/**
* @brief The PropagateLocalRename class
* @brief Rename a local file/directory after discovering a rename on the server
* @ingroup libsync
*/
class PropagateLocalRename : public PropagateItemJob
Expand Down
48 changes: 48 additions & 0 deletions test/testlocaldiscovery.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,54 @@ private Q_SLOTS:
QVERIFY(!fakeFolder.currentRemoteState().find(QStringLiteral("C/.foo")));
QVERIFY(!fakeFolder.currentRemoteState().find(QStringLiteral("C/bar")));
}

void testDirNameEncoding()
{
QFETCH_GLOBAL(Vfs::Mode, vfsMode);
QFETCH_GLOBAL(bool, filesAreDehydrated);

const unsigned char a_umlaut_composed_bytes[] = {0xc3, 0xa4, 0x00};
const QString a_umlaut_composed = QString::fromUtf8(reinterpret_cast<const char *>(a_umlaut_composed_bytes));
const QString a_umlaut_decomposed = a_umlaut_composed.normalized(QString::NormalizationForm_D);

FakeFolder fakeFolder({FileInfo{}}, vfsMode, filesAreDehydrated);
fakeFolder.remoteModifier().mkdir(QStringLiteral("P"));
fakeFolder.remoteModifier().mkdir(QStringLiteral("P/A"));
fakeFolder.remoteModifier().insert(QStringLiteral("P/A/") + a_umlaut_decomposed);
fakeFolder.remoteModifier().mkdir(QStringLiteral("P/B") + a_umlaut_decomposed);
fakeFolder.remoteModifier().insert(QStringLiteral("P/B") + a_umlaut_decomposed + QStringLiteral("/b"));

LocalDiscoveryTracker tracker;
connect(&fakeFolder.syncEngine(), &SyncEngine::itemCompleted, &tracker, &LocalDiscoveryTracker::slotItemCompleted);
connect(&fakeFolder.syncEngine(), &SyncEngine::finished, &tracker, &LocalDiscoveryTracker::slotSyncFinished);

QVERIFY(fakeFolder.applyLocalModificationsAndSync());

{
auto localState = fakeFolder.currentLocalState();
FileInfo *localFile = localState.find(QStringLiteral("P/A/") + a_umlaut_decomposed);
QVERIFY(localFile != nullptr); // check if the file exists
}
{
auto localState = fakeFolder.currentLocalState();
FileInfo *localFile = localState.find(QStringLiteral("P/B") + a_umlaut_decomposed + QStringLiteral("/b"));
QVERIFY(localFile != nullptr); // check if the file exists
}

qDebug() << "*** MARK"; // Log marker to check if a PUT/DELETE shows up in the second sync

fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, {QStringLiteral("P")});
tracker.startSyncFullDiscovery();
QVERIFY(fakeFolder.applyLocalModificationsAndSync());

auto remoteState = fakeFolder.currentRemoteState();
QVERIFY(remoteState.find(QStringLiteral("P/A/") + a_umlaut_decomposed) != nullptr); // check if the file still exists in the original normalization
QVERIFY(remoteState.find(QStringLiteral("P/A/") + a_umlaut_composed) == nullptr); // there should NOT be a file with another normalization
QVERIFY(remoteState.find(QStringLiteral("P/B") + a_umlaut_decomposed + QStringLiteral("/b"))
!= nullptr); // check if the directory still exists in the original normalization
QVERIFY(remoteState.find(QStringLiteral("P/B") + a_umlaut_composed + QStringLiteral("/b"))
== nullptr); // there should NOT be a directory with another normalization
}
};

QTEST_GUILESS_MAIN(TestLocalDiscovery)
Expand Down
Loading
Loading