Skip to content

Commit

Permalink
Merge pull request #6629 from nextcloud/feature/support-lnk-files-in-…
Browse files Browse the repository at this point in the history
…vfs-win

Support Windows .lnk files with VFS Cf API.
  • Loading branch information
mgallien authored Jul 11, 2024
2 parents d5c4c7e + 28d69e9 commit bc98a6d
Show file tree
Hide file tree
Showing 29 changed files with 348 additions and 104 deletions.
187 changes: 181 additions & 6 deletions src/common/filesystembase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,11 @@ QString FileSystem::longWinPath(const QString &inpath)

void FileSystem::setFileHidden(const QString &filename, bool hidden)
{
if (filename.isEmpty()) {
return;
}
#ifdef _WIN32
QString fName = longWinPath(filename);
const QString fName = longWinPath(filename);
DWORD dwAttrs = 0;

dwAttrs = GetFileAttributesW((wchar_t *)fName.utf16());
Expand All @@ -71,6 +74,24 @@ void FileSystem::setFileHidden(const QString &filename, bool hidden)
#endif
}

bool FileSystem::isFileHidden(const QString &filename)
{
#ifdef _WIN32
if (isLnkFile(filename)) {
const QString fName = longWinPath(filename);
DWORD dwAttrs = 0;

dwAttrs = GetFileAttributesW((wchar_t *)fName.utf16());

if (dwAttrs == INVALID_FILE_ATTRIBUTES) {
return false;
}
return dwAttrs & FILE_ATTRIBUTE_HIDDEN;
}
#endif
return QFileInfo(filename).isHidden();
}

static QFile::Permissions getDefaultWritePermissions()
{
QFile::Permissions result = QFile::WriteUser;
Expand All @@ -89,11 +110,27 @@ static QFile::Permissions getDefaultWritePermissions()

void FileSystem::setFileReadOnly(const QString &filename, bool readonly)
{
#ifdef Q_OS_WIN
if (isLnkFile(filename)) {
if (!fileExists(filename)) {
return;
}
const auto permissions = filePermissionsWin(filename);

std::filesystem::perms allWritePermissions = std::filesystem::perms::_All_write;
static std::filesystem::perms defaultWritePermissions = std::filesystem::perms::others_write;

std::filesystem::permissions(filename.toStdString(), allWritePermissions, std::filesystem::perm_options::remove);

if (!readonly) {
std::filesystem::permissions(filename.toStdString(), defaultWritePermissions, std::filesystem::perm_options::add);
}
}
#endif
QFile file(filename);
QFile::Permissions permissions = file.permissions();

QFile::Permissions allWritePermissions =
QFile::WriteUser | QFile::WriteGroup | QFile::WriteOther | QFile::WriteOwner;
QFile::Permissions allWritePermissions = QFile::WriteUser | QFile::WriteGroup | QFile::WriteOther | QFile::WriteOwner;
static QFile::Permissions defaultWritePermissions = getDefaultWritePermissions();

permissions &= ~allWritePermissions;
Expand All @@ -116,6 +153,18 @@ void FileSystem::setFolderMinimumPermissions(const QString &filename)

bool FileSystem::setFileReadOnlyWeak(const QString &filename, bool readonly)
{
#ifdef Q_OS_WIN
if (isLnkFile(filename)) {
const auto permissions = filePermissionsWin(filename);

if (!readonly && static_cast<bool>((permissions & std::filesystem::perms::owner_write))) {
return false; // already writable enough
}

setFileReadOnly(filename, readonly);
return true;
}
#endif
QFile file(filename);
QFile::Permissions permissions = file.permissions();

Expand Down Expand Up @@ -193,7 +242,7 @@ bool FileSystem::uncheckedRenameReplace(const QString &originFileName,

#else //Q_OS_WIN
// You can not overwrite a read-only file on windows.
if (!QFileInfo(destinationFileName).isWritable()) {
if (!isWritable(destinationFileName)) {
setFileReadOnly(destinationFileName, false);
}

Expand Down Expand Up @@ -289,11 +338,24 @@ bool FileSystem::openAndSeekFileSharedRead(QFile *file, QString *errorOrNull, qi
}

#ifdef Q_OS_WIN
std::filesystem::perms FileSystem::filePermissionsWin(const QString &filename)
{
return std::filesystem::status(filename.toStdString()).permissions();
}

void FileSystem::setFilePermissionsWin(const QString &filename, const std::filesystem::perms &perms)
{
if (!fileExists(filename)) {
return;
}
std::filesystem::permissions(filename.toStdString(), perms);
}

static bool fileExistsWin(const QString &filename)
{
WIN32_FIND_DATA FindFileData;
HANDLE hFind = nullptr;
QString fName = FileSystem::longWinPath(filename);
const QString fName = FileSystem::longWinPath(filename);

hFind = FindFirstFileW((wchar_t *)fName.utf16(), &FindFileData);
if (hFind == INVALID_HANDLE_VALUE) {
Expand All @@ -302,6 +364,25 @@ static bool fileExistsWin(const QString &filename)
FindClose(hFind);
return true;
}

static bool isDirWin(const QString &filename)
{
WIN32_FIND_DATA FindFileData;
HANDLE hFind = nullptr;
const QString fName = FileSystem::longWinPath(filename);

hFind = FindFirstFileW((wchar_t *)fName.utf16(), &FindFileData);
if (hFind == INVALID_HANDLE_VALUE) {
return false;
}
FindClose(hFind);
return FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
}

static bool isFileWin(const QString &filename)
{
return !isDirWin(filename);
}
#endif

bool FileSystem::fileExists(const QString &filename, const QFileInfo &fileInfo)
Expand All @@ -323,6 +404,100 @@ bool FileSystem::fileExists(const QString &filename, const QFileInfo &fileInfo)
return re;
}

bool FileSystem::isDir(const QString &filename, const QFileInfo &fileInfo)
{
#ifdef Q_OS_WIN
if (isLnkFile(filename)) {
// Use a native check.
return isDirWin(filename);
}
#endif
bool re = fileInfo.isDir();
// 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
// fileInfo is re-used.
if (fileInfo.filePath() != filename) {
QFileInfo myFI(filename);
re = myFI.isDir();
}
return re;
}

bool FileSystem::isFile(const QString &filename, const QFileInfo &fileInfo)
{
#ifdef Q_OS_WIN
if (isLnkFile(filename)) {
// Use a native check.
return isFileWin(filename);
}
#endif
bool re = fileInfo.isDir();
// 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
// fileInfo is re-used.
if (fileInfo.filePath() != filename) {
QFileInfo myFI(filename);
re = myFI.isFile();
}
return re;
}

bool FileSystem::isWritable(const QString &filename, const QFileInfo &fileInfo)
{
#ifdef Q_OS_WIN
if (isLnkFile(filename)) {
const auto permissions = filePermissionsWin(filename);
return static_cast<bool>((permissions & std::filesystem::perms::owner_write));
}
#endif
bool re = fileInfo.isWritable();
// 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
// fileInfo is re-used.
if (fileInfo.filePath() != filename) {
QFileInfo myFI(filename);
re = myFI.isWritable();
}
return re;
}

bool FileSystem::isReadable(const QString &filename, const QFileInfo &fileInfo)
{
#ifdef Q_OS_WIN
if (isLnkFile(filename)) {
const auto permissions = filePermissionsWin(filename);
return static_cast<bool>((permissions & std::filesystem::perms::owner_read));
}
#endif
bool re = fileInfo.isReadable();
// 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
// fileInfo is re-used.
if (fileInfo.filePath() != filename) {
QFileInfo myFI(filename);
re = myFI.isReadable();
}
return re;
}

bool FileSystem::isSymLink(const QString &filename, const QFileInfo &fileInfo)
{
#ifdef Q_OS_WIN
if (isLnkFile(filename)) {
return isJunction(filename);
}
#endif
bool re = fileInfo.isSymLink();
// 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
// fileInfo is re-used.
if (fileInfo.filePath() != filename) {
QFileInfo myFI(filename);
re = myFI.isSymLink();
}
return re;
}

#ifdef Q_OS_WIN
QString FileSystem::fileSystemForPath(const QString &path)
{
Expand Down Expand Up @@ -351,7 +526,7 @@ bool FileSystem::remove(const QString &fileName, QString *errorString)
#ifdef Q_OS_WIN
// You cannot delete a read-only file on windows, but we want to
// allow that.
if (!QFileInfo(fileName).isWritable()) {
if (!isWritable(fileName)) {
setFileReadOnly(fileName, false);
}
#endif
Expand Down
37 changes: 37 additions & 0 deletions src/common/filesystembase.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@

#include <ctime>

#if !defined(Q_OS_MACOS) || __MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_15
#include <filesystem>
#endif

class QFile;

namespace OCC {
Expand All @@ -53,6 +57,8 @@ namespace FileSystem {
*/
void OCSYNC_EXPORT setFileHidden(const QString &filename, bool hidden);

bool OCSYNC_EXPORT isFileHidden(const QString &filename);

/**
* @brief Marks the file as read-only.
*
Expand Down Expand Up @@ -89,6 +95,34 @@ namespace FileSystem {
*/
bool OCSYNC_EXPORT fileExists(const QString &filename, const QFileInfo & = QFileInfo());

/**
* @brief Checks whether it is a dir.
*
* Use this over QFileInfo::isDir() and QFile::isDir() to avoid bugs with lnk
* files, see above.
*/
bool OCSYNC_EXPORT isDir(const QString &filename, const QFileInfo& = QFileInfo());

/**
* @brief Checks whether it is a file.
*
* Use this over QFileInfo::isDir() and QFile::isDir() to avoid bugs with lnk
* files, see above.
*/
bool OCSYNC_EXPORT isFile(const QString &filename, const QFileInfo& fileInfo = QFileInfo());

/**
* @brief Checks whether the file is writable.
*
* Use this over QFileInfo::isDir() and QFile::isDir() to avoid bugs with lnk
* files, see above.
*/
bool OCSYNC_EXPORT isWritable(const QString &filename, const QFileInfo &fileInfo = QFileInfo());

bool OCSYNC_EXPORT isReadable(const QString &filename, const QFileInfo &fileInfo = QFileInfo());

bool OCSYNC_EXPORT isSymLink(const QString &filename, const QFileInfo &fileInfo = QFileInfo());

/**
* @brief Rename the file \a originFileName to \a destinationFileName.
*
Expand Down Expand Up @@ -146,6 +180,9 @@ namespace FileSystem {
* the windows API functions work with the normal "unixoid" representation too.
*/
QString OCSYNC_EXPORT pathtoUNC(const QString &str);

std::filesystem::perms OCSYNC_EXPORT filePermissionsWin(const QString &filename);
void OCSYNC_EXPORT setFilePermissionsWin(const QString &filename, const std::filesystem::perms &perms);
#endif

/**
Expand Down
10 changes: 4 additions & 6 deletions src/csync/csync_exclude.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "csync_exclude.h"

#include "common/utility.h"
#include "common/filesystembase.h"
#include "../version.h"

#include <QString>
Expand Down Expand Up @@ -402,7 +403,7 @@ bool ExcludedFiles::isExcluded(
while (path.size() > basePath.size()) {
QFileInfo fi(path);
if (fi.fileName() != QStringLiteral(".sync-exclude.lst")
&& (fi.isHidden() || fi.fileName().startsWith(QLatin1Char('.')))) {
&& (FileSystem::isFileHidden(path) || fi.fileName().startsWith(QLatin1Char('.')))) {
return true;
}

Expand All @@ -411,9 +412,8 @@ bool ExcludedFiles::isExcluded(
}
}

QFileInfo fi(filePath);
ItemType type = ItemTypeFile;
if (fi.isDir()) {
if (OCC::FileSystem::isDir(filePath)) {
type = ItemTypeDirectory;
}

Expand All @@ -437,9 +437,7 @@ CSYNC_EXCLUDE_TYPE ExcludedFiles::traversalPatternMatch(const QString &path, Ite
if (filetype == ItemTypeDirectory) {
const auto basePath = QString(_localPath + path + QLatin1Char('/'));
const QString absolutePath = basePath + QStringLiteral(".sync-exclude.lst");
QFileInfo excludeFileInfo(absolutePath);

if (excludeFileInfo.isReadable()) {
if (FileSystem::isReadable(absolutePath)) {
addExcludeFilePath(absolutePath);
reloadExcludeFiles();
} else {
Expand Down
5 changes: 3 additions & 2 deletions src/gui/caseclashfilenamedialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

#include "account.h"
#include "folder.h"
#include "common/filesystembase.h"

#include <QPushButton>
#include <QDir>
Expand Down Expand Up @@ -180,12 +181,12 @@ QString CaseClashFilenameDialog::caseClashConflictFile(const QString &conflictFi
while(it.hasNext()) {
const auto filePath = it.next();
qCDebug(lcCaseClashConflictFialog) << filePath;
QFileInfo fileInfo(filePath);

if(fileInfo.isDir()) {
if (FileSystem::isDir(filePath)) {
continue;
}

QFileInfo fileInfo(filePath);
const auto currentFileName = fileInfo.fileName();
if (currentFileName.compare(conflictFileName, Qt::CaseInsensitive) == 0 &&
currentFileName != conflictFileName) {
Expand Down
Loading

0 comments on commit bc98a6d

Please sign in to comment.