mirror of
https://github.com/nextcloud/desktop.git
synced 2024-11-22 04:55:48 +03:00
Merge pull request #6343 from nextcloud/feature/applyPermissionsFromServerToFolders
newly created folders will be read-only when needed
This commit is contained in:
commit
61251cde5d
17 changed files with 668 additions and 82 deletions
16
.drone.yml
16
.drone.yml
|
@ -4,7 +4,7 @@ name: qt-5.15
|
|||
|
||||
steps:
|
||||
- name: cmake
|
||||
image: ghcr.io/nextcloud/continuous-integration-client:client-5.15-14
|
||||
image: ghcr.io/nextcloud/continuous-integration-client:client-5.15-15
|
||||
volumes:
|
||||
- name: build
|
||||
path: /drone/build
|
||||
|
@ -13,7 +13,7 @@ steps:
|
|||
- cmake -G Ninja -DCMAKE_C_COMPILER=gcc-11 -DCMAKE_CXX_COMPILER=g++-11 -DCMAKE_BUILD_TYPE=Debug -DQUICK_COMPILER=ON -DBUILD_UPDATER=ON -DBUILD_TESTING=1 -DADD_E2E_TESTS=ON -DECM_ENABLE_SANITIZERS=address -DCMAKE_CXX_FLAGS=-Werror -DOPENSSL_ROOT_DIR=/usr/local/lib64 ../src
|
||||
|
||||
- name: compile
|
||||
image: ghcr.io/nextcloud/continuous-integration-client:client-5.15-14
|
||||
image: ghcr.io/nextcloud/continuous-integration-client:client-5.15-15
|
||||
volumes:
|
||||
- name: build
|
||||
path: /drone/build
|
||||
|
@ -22,7 +22,7 @@ steps:
|
|||
- ninja
|
||||
|
||||
- name: test
|
||||
image: ghcr.io/nextcloud/continuous-integration-client:client-5.15-14
|
||||
image: ghcr.io/nextcloud/continuous-integration-client:client-5.15-15
|
||||
volumes:
|
||||
- name: build
|
||||
path: /drone/build
|
||||
|
@ -74,7 +74,7 @@ name: qt-5.15-clang
|
|||
|
||||
steps:
|
||||
- name: cmake
|
||||
image: ghcr.io/nextcloud/continuous-integration-client:client-5.15-14
|
||||
image: ghcr.io/nextcloud/continuous-integration-client:client-5.15-15
|
||||
volumes:
|
||||
- name: build
|
||||
path: /drone/build
|
||||
|
@ -82,7 +82,7 @@ steps:
|
|||
- cd /drone/build
|
||||
- cmake -G Ninja -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_C_COMPILER=clang-14 -DCMAKE_CXX_COMPILER=clang++-14 -DCMAKE_BUILD_TYPE=Debug -DQUICK_COMPILER=ON -DBUILD_UPDATER=ON -DBUILD_TESTING=1 -DADD_E2E_TESTS=ON -DECM_ENABLE_SANITIZERS=address -DCMAKE_CXX_FLAGS=-Werror -DOPENSSL_ROOT_DIR=/usr/local/lib64 ../src
|
||||
- name: compile
|
||||
image: ghcr.io/nextcloud/continuous-integration-client:client-5.15-14
|
||||
image: ghcr.io/nextcloud/continuous-integration-client:client-5.15-15
|
||||
volumes:
|
||||
- name: build
|
||||
path: /drone/build
|
||||
|
@ -90,7 +90,7 @@ steps:
|
|||
- cd /drone/build
|
||||
- ninja
|
||||
- name: test
|
||||
image: ghcr.io/nextcloud/continuous-integration-client:client-5.15-14
|
||||
image: ghcr.io/nextcloud/continuous-integration-client:client-5.15-15
|
||||
volumes:
|
||||
- name: build
|
||||
path: /drone/build
|
||||
|
@ -142,7 +142,7 @@ name: AppImage
|
|||
|
||||
steps:
|
||||
- name: build
|
||||
image: ghcr.io/nextcloud/continuous-integration-client-appimage:client-appimage-9
|
||||
image: ghcr.io/nextcloud/continuous-integration-client-appimage:client-appimage-10
|
||||
environment:
|
||||
CI_UPLOAD_GIT_TOKEN:
|
||||
from_secret: CI_UPLOAD_GIT_TOKEN
|
||||
|
@ -196,6 +196,6 @@ trigger:
|
|||
- push
|
||||
---
|
||||
kind: signature
|
||||
hmac: d72110d7f9cba086ca21f9f4f4032ae87f3d9555ab4c5f55d3aeb3df99cab540
|
||||
hmac: a8fd97516ee53ca8c938ad413e030dc7df483f116c4b19b5811e359960b7b44d
|
||||
|
||||
...
|
||||
|
|
|
@ -6,7 +6,7 @@ jobs:
|
|||
build:
|
||||
name: Linux Clang compilation and tests
|
||||
runs-on: ubuntu-22.04
|
||||
container: ghcr.io/nextcloud/continuous-integration-client:client-5.15-14
|
||||
container: ghcr.io/nextcloud/continuous-integration-client:client-5.15-15
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
|
|
|
@ -6,7 +6,7 @@ jobs:
|
|||
build:
|
||||
name: Linux GCC compilation and tests
|
||||
runs-on: ubuntu-22.04
|
||||
container: ghcr.io/nextcloud/continuous-integration-client:client-5.15-14
|
||||
container: ghcr.io/nextcloud/continuous-integration-client:client-5.15-15
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
|
|
2
.github/workflows/sonarcloud.yml
vendored
2
.github/workflows/sonarcloud.yml
vendored
|
@ -6,7 +6,7 @@ jobs:
|
|||
build:
|
||||
name: SonarCloud analysis
|
||||
runs-on: ubuntu-22.04
|
||||
container: ghcr.io/nextcloud/continuous-integration-client:client-5.15-14
|
||||
container: ghcr.io/nextcloud/continuous-integration-client:client-5.15-15
|
||||
env:
|
||||
SONAR_SERVER_URL: "https://sonarcloud.io"
|
||||
BUILD_WRAPPER_OUT_DIR: build_wrapper_output_directory # Directory where build-wrapper output will be placed
|
||||
|
|
|
@ -1,20 +1,22 @@
|
|||
cmake_minimum_required(VERSION 3.16)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
cmake_policy(SET CMP0071 NEW) # Enable use of QtQuick compiler/generated code
|
||||
|
||||
find_program(CLANG_TIDY_EXE NAMES "clang-tidy")
|
||||
if (CLANG_TIDY_EXE)
|
||||
set(CMAKE_CXX_CLANG_TIDY ${CLANG_TIDY_EXE} -checks=-*,modernize-use-auto,modernize-use-using,modernize-use-nodiscard,modernize-use-nullptr,modernize-use-override,cppcoreguidelines-pro-type-static-cast-downcast,modernize-use-default-member-init,cppcoreguidelines-pro-type-member-init,cppcoreguidelines-init-variables)
|
||||
endif()
|
||||
|
||||
project(client)
|
||||
|
||||
if(APPLE)
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET "12.0" CACHE STRING "Minimum OSX deployment version")
|
||||
endif()
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED 17)
|
||||
|
||||
include(FeatureSummary)
|
||||
|
||||
find_program(CLANG_TIDY_EXE NAMES "clang-tidy")
|
||||
if (CLANG_TIDY_EXE)
|
||||
set(CMAKE_CXX_CLANG_TIDY ${CLANG_TIDY_EXE} -checks=-*,modernize-use-auto,modernize-use-using,modernize-use-nodiscard,modernize-use-nullptr,modernize-use-override,cppcoreguidelines-pro-type-static-cast-downcast,modernize-use-default-member-init,cppcoreguidelines-pro-type-member-init,cppcoreguidelines-init-variables)
|
||||
endif()
|
||||
|
||||
set(CMAKE_XCODE_ATTRIBUTE_ENABLE_HARDENED_RUNTIME YES)
|
||||
|
||||
set(BIN_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
|
||||
|
|
|
@ -85,7 +85,7 @@ chmod a+x linuxdeployqt.AppImage
|
|||
rm ./linuxdeployqt.AppImage
|
||||
cp -r ./squashfs-root ./linuxdeployqt-squashfs-root
|
||||
unset QTDIR; unset QT_PLUGIN_PATH ; unset LD_LIBRARY_PATH
|
||||
export LD_LIBRARY_PATH=/usr/local/lib/x86_64-linux-gnu
|
||||
export LD_LIBRARY_PATH=/usr/local/lib:/usr/local/lib/x86_64-linux-gnu
|
||||
./squashfs-root/AppRun ${DESKTOP_FILE} -bundle-non-qt-libs -qmldir=${DESKTOP_CLIENT_ROOT}/src/gui
|
||||
|
||||
# Set origin
|
||||
|
|
|
@ -20,12 +20,13 @@
|
|||
|
||||
#include "config.h"
|
||||
|
||||
#include "csync/ocsynclib.h"
|
||||
|
||||
#include <QString>
|
||||
#include <ctime>
|
||||
#include <QFileInfo>
|
||||
#include <QLoggingCategory>
|
||||
|
||||
#include <csync/ocsynclib.h>
|
||||
#include <ctime>
|
||||
|
||||
class QFile;
|
||||
|
||||
|
@ -42,6 +43,10 @@ OCSYNC_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcFileSystem)
|
|||
* @brief This file contains file system helper
|
||||
*/
|
||||
namespace FileSystem {
|
||||
enum class FolderPermissions {
|
||||
ReadOnly,
|
||||
ReadWrite,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Mark the file as hidden (only has effects on windows)
|
||||
|
|
|
@ -214,6 +214,11 @@ target_link_libraries(nextcloudsync
|
|||
KF5::Archive
|
||||
)
|
||||
|
||||
target_compile_features(nextcloudsync
|
||||
PRIVATE
|
||||
cxx_std_17
|
||||
)
|
||||
|
||||
find_package(Qt5 REQUIRED COMPONENTS Gui Widgets Svg)
|
||||
target_link_libraries(nextcloudsync PUBLIC Qt5::Gui Qt5::Widgets Qt5::Svg)
|
||||
|
||||
|
|
|
@ -15,15 +15,20 @@
|
|||
#include "filesystem.h"
|
||||
|
||||
#include "common/utility.h"
|
||||
#include "csync.h"
|
||||
#include "vio/csync_vio_local.h"
|
||||
#include "std/c_time.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QDir>
|
||||
#include <QDirIterator>
|
||||
#include <QCoreApplication>
|
||||
|
||||
#include "csync.h"
|
||||
#include "vio/csync_vio_local.h"
|
||||
#include "std/c_time.h"
|
||||
#ifdef Q_OS_WIN
|
||||
#include <securitybaseapi.h>
|
||||
#include <sddl.h>
|
||||
#endif
|
||||
|
||||
namespace OCC {
|
||||
|
||||
|
@ -189,5 +194,173 @@ bool FileSystem::getInode(const QString &filename, quint64 *inode)
|
|||
return false;
|
||||
}
|
||||
|
||||
bool FileSystem::setFolderPermissions(const QString &path,
|
||||
FileSystem::FolderPermissions permissions) noexcept
|
||||
{
|
||||
try {
|
||||
switch (permissions) {
|
||||
case OCC::FileSystem::FolderPermissions::ReadOnly:
|
||||
std::filesystem::permissions(path.toStdWString(), std::filesystem::perms::owner_write | std::filesystem::perms::group_write | std::filesystem::perms::others_write, std::filesystem::perm_options::remove);
|
||||
break;
|
||||
case OCC::FileSystem::FolderPermissions::ReadWrite:
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (const std::filesystem::filesystem_error &e)
|
||||
{
|
||||
qCWarning(lcFileSystem()) << "exception when modifying folder permissions" << e.what() << e.path1().c_str() << e.path2().c_str();
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
SECURITY_INFORMATION info = DACL_SECURITY_INFORMATION;
|
||||
std::unique_ptr<char[]> securityDescriptor;
|
||||
auto neededLength = 0ul;
|
||||
|
||||
if (!GetFileSecurityW(path.toStdWString().c_str(), info, nullptr, 0, &neededLength)) {
|
||||
const auto lastError = GetLastError();
|
||||
if (lastError != ERROR_INSUFFICIENT_BUFFER) {
|
||||
qCWarning(lcFileSystem) << "error when calling GetFileSecurityW" << path << lastError;
|
||||
return false;
|
||||
}
|
||||
|
||||
securityDescriptor.reset(new char[neededLength]);
|
||||
|
||||
if (!GetFileSecurityW(path.toStdWString().c_str(), info, securityDescriptor.get(), neededLength, &neededLength)) {
|
||||
qCWarning(lcFileSystem) << "error when calling GetFileSecurityW" << path << GetLastError();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int daclPresent = false, daclDefault = false;
|
||||
PACL resultDacl = nullptr;
|
||||
if (!GetSecurityDescriptorDacl(securityDescriptor.get(), &daclPresent, &resultDacl, &daclDefault)) {
|
||||
qCWarning(lcFileSystem) << "error when calling GetSecurityDescriptorDacl" << path << GetLastError();
|
||||
return false;
|
||||
}
|
||||
if (!daclPresent) {
|
||||
qCWarning(lcFileSystem) << "error when calling DACL needed to set a folder read-only or read-write is missing" << path;
|
||||
return false;
|
||||
}
|
||||
|
||||
PSID sid = nullptr;
|
||||
if (!ConvertStringSidToSidW(L"S-1-5-32-545", &sid))
|
||||
{
|
||||
qCWarning(lcFileSystem) << "error when calling ConvertStringSidToSidA" << path << GetLastError();
|
||||
return false;
|
||||
}
|
||||
|
||||
ACL_SIZE_INFORMATION aclSize;
|
||||
if (!GetAclInformation(resultDacl, &aclSize, sizeof(aclSize), AclSizeInformation)) {
|
||||
qCWarning(lcFileSystem) << "error when calling GetAclInformation" << path << GetLastError();
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto newAclSize = aclSize.AclBytesInUse + sizeof(ACCESS_DENIED_ACE) + GetLengthSid(sid);
|
||||
qCDebug(lcFileSystem) << "allocated a new DACL object of size" << newAclSize;
|
||||
|
||||
std::unique_ptr<ACL> newDacl{reinterpret_cast<PACL>(new char[newAclSize])};
|
||||
if (!InitializeAcl(newDacl.get(), newAclSize, ACL_REVISION)) {
|
||||
const auto lastError = GetLastError();
|
||||
if (lastError != ERROR_INSUFFICIENT_BUFFER) {
|
||||
qCWarning(lcFileSystem) << "insufficient memory error when calling InitializeAcl" << path;
|
||||
return false;
|
||||
}
|
||||
|
||||
qCWarning(lcFileSystem) << "error when calling InitializeAcl" << path << lastError;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (permissions == FileSystem::FolderPermissions::ReadOnly) {
|
||||
qCInfo(lcFileSystem) << path << "will be read only";
|
||||
if (!AddAccessDeniedAce(newDacl.get(), ACL_REVISION, FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | FILE_APPEND_DATA | FILE_DELETE_CHILD, sid)) {
|
||||
qCWarning(lcFileSystem) << "error when calling AddAccessDeniedAce << path" << GetLastError();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < aclSize.AceCount; ++i) {
|
||||
void *currentAce = nullptr;
|
||||
if (!GetAce(resultDacl, i, ¤tAce)) {
|
||||
qCWarning(lcFileSystem) << "error when calling GetAce" << path << GetLastError();
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto currentAceHeader = reinterpret_cast<PACE_HEADER>(currentAce);
|
||||
|
||||
if (permissions == FileSystem::FolderPermissions::ReadWrite) {
|
||||
qCInfo(lcFileSystem) << path << "will be read write";
|
||||
}
|
||||
if (permissions == FileSystem::FolderPermissions::ReadWrite && (ACCESS_DENIED_ACE_TYPE == (currentAceHeader->AceType & ACCESS_DENIED_ACE_TYPE))) {
|
||||
qCWarning(lcFileSystem) << "AceHeader" << path << currentAceHeader->AceFlags << currentAceHeader->AceSize << currentAceHeader->AceType;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!AddAce(newDacl.get(), ACL_REVISION, i + 1, currentAce, currentAceHeader->AceSize)) {
|
||||
const auto lastError = GetLastError();
|
||||
if (lastError != ERROR_INSUFFICIENT_BUFFER) {
|
||||
qCWarning(lcFileSystem) << "insufficient memory error when calling AddAce" << path;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (lastError != ERROR_INVALID_PARAMETER) {
|
||||
qCWarning(lcFileSystem) << "invalid parameter error when calling AddAce" << path << "ACL size" << newAclSize;
|
||||
return false;
|
||||
}
|
||||
|
||||
qCWarning(lcFileSystem) << "error when calling AddAce" << path << lastError << "acl index" << (i + 1);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
SECURITY_DESCRIPTOR newSecurityDescriptor;
|
||||
if (!InitializeSecurityDescriptor(&newSecurityDescriptor, SECURITY_DESCRIPTOR_REVISION)) {
|
||||
qCWarning(lcFileSystem) << "error when calling InitializeSecurityDescriptor" << path << GetLastError();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SetSecurityDescriptorDacl(&newSecurityDescriptor, true, newDacl.get(), false)) {
|
||||
qCWarning(lcFileSystem) << "error when calling SetSecurityDescriptorDacl" << path << GetLastError();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SetFileSecurityW(path.toStdWString().c_str(), info, &newSecurityDescriptor)) {
|
||||
qCWarning(lcFileSystem) << "error when calling SetFileSecurityW" << path << GetLastError();
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
try {
|
||||
switch (permissions) {
|
||||
case OCC::FileSystem::FolderPermissions::ReadOnly:
|
||||
break;
|
||||
case OCC::FileSystem::FolderPermissions::ReadWrite:
|
||||
std::filesystem::permissions(path.toStdWString(), std::filesystem::perms::owner_write, std::filesystem::perm_options::add);
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (const std::filesystem::filesystem_error &e)
|
||||
{
|
||||
qCWarning(lcFileSystem()) << "exception when modifying folder permissions" << e.what() << e.path1().c_str() << e.path2().c_str();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileSystem::isFolderReadOnly(const std::filesystem::path &path) noexcept
|
||||
{
|
||||
try {
|
||||
const auto folderStatus = std::filesystem::status(path);
|
||||
const auto folderPermissions = folderStatus.permissions();
|
||||
return (folderPermissions & std::filesystem::perms::owner_write) != std::filesystem::perms::owner_write;
|
||||
}
|
||||
catch (const std::filesystem::filesystem_error &e)
|
||||
{
|
||||
qCWarning(lcFileSystem()) << "exception when checking folder permissions" << e.what() << e.path1().c_str() << e.path2().c_str();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace OCC
|
||||
|
|
|
@ -16,13 +16,14 @@
|
|||
|
||||
#include "config.h"
|
||||
|
||||
#include "owncloudlib.h"
|
||||
#include "common/filesystembase.h"
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include <ctime>
|
||||
#include <functional>
|
||||
|
||||
#include <owncloudlib.h>
|
||||
// Chain in the base include and extend the namespace
|
||||
#include "common/filesystembase.h"
|
||||
#include <filesystem>
|
||||
|
||||
class QFile;
|
||||
|
||||
|
@ -96,6 +97,11 @@ namespace FileSystem {
|
|||
bool OWNCLOUDSYNC_EXPORT removeRecursively(const QString &path,
|
||||
const std::function<void(const QString &path, bool isDir)> &onDeleted = nullptr,
|
||||
QStringList *errors = nullptr);
|
||||
|
||||
bool OWNCLOUDSYNC_EXPORT setFolderPermissions(const QString &path,
|
||||
FileSystem::FolderPermissions permissions) noexcept;
|
||||
|
||||
bool OWNCLOUDSYNC_EXPORT isFolderReadOnly(const std::filesystem::path &path) noexcept;
|
||||
}
|
||||
|
||||
/** @} */
|
||||
|
|
|
@ -1442,6 +1442,59 @@ void PropagateDirectory::slotSubJobsFinished(SyncFileItem::Status status)
|
|||
if (_item->_instruction == CSYNC_INSTRUCTION_RENAME
|
||||
|| _item->_instruction == CSYNC_INSTRUCTION_NEW
|
||||
|| _item->_instruction == CSYNC_INSTRUCTION_UPDATE_METADATA) {
|
||||
|
||||
if (!_item->_remotePerm.isNull() &&
|
||||
!_item->_remotePerm.hasPermission(RemotePermissions::CanAddFile) &&
|
||||
!_item->_remotePerm.hasPermission(RemotePermissions::CanRename) &&
|
||||
!_item->_remotePerm.hasPermission(RemotePermissions::CanMove) &&
|
||||
!_item->_remotePerm.hasPermission(RemotePermissions::CanAddSubDirectories)) {
|
||||
try {
|
||||
if (QFileInfo::exists(propagator()->fullLocalPath(_item->_file))) {
|
||||
FileSystem::setFolderPermissions(propagator()->fullLocalPath(_item->_file), FileSystem::FolderPermissions::ReadOnly);
|
||||
qCDebug(lcDirectory) << "old permissions" << static_cast<int>(std::filesystem::status(propagator()->fullLocalPath(_item->_file).toStdWString()).permissions());
|
||||
std::filesystem::permissions(propagator()->fullLocalPath(_item->_file).toStdWString(), std::filesystem::perms::owner_write | std::filesystem::perms::group_write | std::filesystem::perms::others_write, std::filesystem::perm_options::remove);
|
||||
qCDebug(lcDirectory) << "new permissions" << static_cast<int>(std::filesystem::status(propagator()->fullLocalPath(_item->_file).toStdWString()).permissions());
|
||||
}
|
||||
if (!_item->_renameTarget.isEmpty() && QFileInfo::exists(propagator()->fullLocalPath(_item->_renameTarget))) {
|
||||
FileSystem::setFolderPermissions(propagator()->fullLocalPath(_item->_renameTarget), FileSystem::FolderPermissions::ReadOnly);
|
||||
qCDebug(lcDirectory) << "old permissions" << static_cast<int>(std::filesystem::status(propagator()->fullLocalPath(_item->_renameTarget).toStdWString()).permissions());
|
||||
std::filesystem::permissions(propagator()->fullLocalPath(_item->_renameTarget).toStdWString(), std::filesystem::perms::owner_write | std::filesystem::perms::group_write | std::filesystem::perms::others_write, std::filesystem::perm_options::remove);
|
||||
qCDebug(lcDirectory) << "new permissions" << static_cast<int>(std::filesystem::status(propagator()->fullLocalPath(_item->_renameTarget).toStdWString()).permissions());
|
||||
}
|
||||
}
|
||||
catch (const std::filesystem::filesystem_error &e)
|
||||
{
|
||||
qCWarning(lcDirectory) << "exception when checking parent folder access rights" << e.what() << e.path1().c_str() << e.path2().c_str();
|
||||
_item->_status = SyncFileItem::NormalError;
|
||||
_item->_errorString = tr("The folder %1 cannot be made read-only: %2").arg(_item->_file, e.what());
|
||||
}
|
||||
} else if (!_item->_remotePerm.isNull() &&
|
||||
(_item->_remotePerm.hasPermission(RemotePermissions::CanAddFile) ||
|
||||
!_item->_remotePerm.hasPermission(RemotePermissions::CanRename) ||
|
||||
!_item->_remotePerm.hasPermission(RemotePermissions::CanMove) ||
|
||||
!_item->_remotePerm.hasPermission(RemotePermissions::CanAddSubDirectories))) {
|
||||
try {
|
||||
if (QFileInfo::exists(propagator()->fullLocalPath(_item->_file))) {
|
||||
FileSystem::setFolderPermissions(propagator()->fullLocalPath(_item->_file), FileSystem::FolderPermissions::ReadWrite);
|
||||
qCDebug(lcDirectory) << "old permissions" << static_cast<int>(std::filesystem::status(propagator()->fullLocalPath(_item->_file).toStdWString()).permissions());
|
||||
std::filesystem::permissions(propagator()->fullLocalPath(_item->_file).toStdWString(), std::filesystem::perms::owner_write, std::filesystem::perm_options::add);
|
||||
qCDebug(lcDirectory) << "new permissions" << static_cast<int>(std::filesystem::status(propagator()->fullLocalPath(_item->_file).toStdWString()).permissions());
|
||||
}
|
||||
if (!_item->_renameTarget.isEmpty() && QFileInfo::exists(propagator()->fullLocalPath(_item->_renameTarget))) {
|
||||
FileSystem::setFolderPermissions(propagator()->fullLocalPath(_item->_renameTarget), FileSystem::FolderPermissions::ReadWrite);
|
||||
qCDebug(lcDirectory) << "old permissions" << static_cast<int>(std::filesystem::status(propagator()->fullLocalPath(_item->_renameTarget).toStdWString()).permissions());
|
||||
std::filesystem::permissions(propagator()->fullLocalPath(_item->_renameTarget).toStdWString(), std::filesystem::perms::owner_write, std::filesystem::perm_options::add);
|
||||
qCDebug(lcDirectory) << "new permissions" << static_cast<int>(std::filesystem::status(propagator()->fullLocalPath(_item->_renameTarget).toStdWString()).permissions());
|
||||
}
|
||||
}
|
||||
catch (const std::filesystem::filesystem_error &e)
|
||||
{
|
||||
qCWarning(lcDirectory) << "exception when checking parent folder access rights" << e.what() << e.path1().c_str() << e.path2().c_str();
|
||||
_item->_status = SyncFileItem::NormalError;
|
||||
_item->_errorString = tr("The folder %1 cannot be made read-only: %2").arg(e.path1().c_str(), e.what());
|
||||
}
|
||||
}
|
||||
|
||||
const auto result = propagator()->updateMetadata(*_item);
|
||||
if (!result) {
|
||||
status = _item->_status = SyncFileItem::FatalError;
|
||||
|
@ -1454,7 +1507,7 @@ void PropagateDirectory::slotSubJobsFinished(SyncFileItem::Status status)
|
|||
}
|
||||
}
|
||||
_state = Finished;
|
||||
qCInfo(lcPropagator) << "PropagateDirectory::slotSubJobsFinished" << "emit finished" << status;
|
||||
qCInfo(lcPropagator) << "PropagateDirectory::slotSubJobsFinished" << "emit finished" << status << _item->_file;
|
||||
emit finished(status);
|
||||
}
|
||||
|
||||
|
|
|
@ -32,11 +32,8 @@
|
|||
#include <QNetworkAccessManager>
|
||||
#include <QFileInfo>
|
||||
#include <QDir>
|
||||
#include <cmath>
|
||||
|
||||
#ifdef Q_OS_UNIX
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include <cmath>
|
||||
|
||||
namespace OCC {
|
||||
|
||||
|
@ -672,8 +669,26 @@ void PropagateDownloadFile::startDownload()
|
|||
|
||||
// Can't open(Append) read-only files, make sure to make
|
||||
// file writable if it exists.
|
||||
if (_tmpFile.exists())
|
||||
if (_tmpFile.exists()) {
|
||||
FileSystem::setFileReadOnly(_tmpFile.fileName(), false);
|
||||
}
|
||||
|
||||
try {
|
||||
const auto newDirPath = std::filesystem::path{_tmpFile.fileName().toStdWString()};
|
||||
Q_ASSERT(newDirPath.has_parent_path());
|
||||
_parentPath = newDirPath.parent_path();
|
||||
}
|
||||
catch (const std::filesystem::filesystem_error &e)
|
||||
{
|
||||
qCWarning(lcPropagateDownload) << "exception when checking parent folder access rights" << e.what() << e.path1().c_str() << e.path2().c_str();
|
||||
}
|
||||
|
||||
if (FileSystem::isFolderReadOnly(_parentPath)) {
|
||||
FileSystem::setFolderPermissions(QString::fromStdWString(_parentPath.wstring()), FileSystem::FolderPermissions::ReadWrite);
|
||||
emit propagator()->touchedFile(QString::fromStdWString(_parentPath.wstring()));
|
||||
_needParentFolderRestorePermissions = true;
|
||||
}
|
||||
|
||||
if (!_tmpFile.open(QIODevice::Append | QIODevice::Unbuffered)) {
|
||||
qCWarning(lcPropagateDownload) << "could not open temporary file" << _tmpFile.fileName();
|
||||
done(SyncFileItem::NormalError, _tmpFile.errorString(), ErrorCategory::GenericError);
|
||||
|
@ -1272,6 +1287,12 @@ void PropagateDownloadFile::downloadFinished()
|
|||
return;
|
||||
}
|
||||
|
||||
if (_needParentFolderRestorePermissions) {
|
||||
FileSystem::setFolderPermissions(QString::fromStdWString(_parentPath.wstring()), FileSystem::FolderPermissions::ReadWrite);
|
||||
emit propagator()->touchedFile(QString::fromStdWString(_parentPath.wstring()));
|
||||
_needParentFolderRestorePermissions = false;
|
||||
}
|
||||
|
||||
FileSystem::setFileHidden(filename, false);
|
||||
|
||||
// Maybe we downloaded a newer version of the file than we thought we would...
|
||||
|
|
|
@ -23,6 +23,8 @@
|
|||
#include <QBuffer>
|
||||
#include <QFile>
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
namespace OCC {
|
||||
class PropagateDownloadEncrypted;
|
||||
|
||||
|
@ -260,5 +262,8 @@ private:
|
|||
QElapsedTimer _stopwatch;
|
||||
|
||||
PropagateDownloadEncrypted *_downloadEncryptedHelper = nullptr;
|
||||
|
||||
std::filesystem::path _parentPath;
|
||||
bool _needParentFolderRestorePermissions = false;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
#include <qstack.h>
|
||||
#include <QCoreApplication>
|
||||
|
||||
#include <filesystem>
|
||||
#include <ctime>
|
||||
|
||||
|
||||
|
@ -59,6 +60,7 @@ bool PropagateLocalRemove::removeRecursively(const QString &path)
|
|||
QString absolute = propagator()->fullLocalPath(_item->_file + path);
|
||||
QStringList errors;
|
||||
QList<QPair<QString, bool>> deleted;
|
||||
FileSystem::setFolderPermissions(absolute, FileSystem::FolderPermissions::ReadWrite);
|
||||
bool success = FileSystem::removeRecursively(
|
||||
absolute,
|
||||
[&deleted](const QString &path, bool isDir) {
|
||||
|
@ -129,7 +131,7 @@ void PropagateLocalRemove::start()
|
|||
}
|
||||
propagator()->reportProgress(*_item, 0);
|
||||
if (!propagator()->_journal->deleteFileRecord(_item->_originalFile, _item->isDirectory())) {
|
||||
qCWarning(lcPropagateLocalRename) << "could not delete file from local DB" << _item->_originalFile;
|
||||
qCWarning(lcPropagateLocalRemove()) << "could not delete file from local DB" << _item->_originalFile;
|
||||
done(SyncFileItem::NormalError, tr("Could not delete file record %1 from local DB").arg(_item->_originalFile), ErrorCategory::GenericError);
|
||||
return;
|
||||
}
|
||||
|
@ -181,6 +183,24 @@ void PropagateLocalMkdir::startLocalMkdir()
|
|||
done(SyncFileItem::FileNameClash, tr("Folder %1 cannot be created because of a local file or folder name clash!").arg(newDirStr), ErrorCategory::GenericError);
|
||||
return;
|
||||
}
|
||||
|
||||
auto parentFolderPath = std::filesystem::path{};
|
||||
auto parentNeedRollbackPermissions = false;
|
||||
try {
|
||||
const auto newDirPath = std::filesystem::path{newDirStr.toStdWString()};
|
||||
Q_ASSERT(newDirPath.has_parent_path());
|
||||
parentFolderPath = newDirPath.parent_path();
|
||||
if (FileSystem::isFolderReadOnly(parentFolderPath)) {
|
||||
FileSystem::setFolderPermissions(QString::fromStdWString(parentFolderPath.wstring()), FileSystem::FolderPermissions::ReadWrite);
|
||||
parentNeedRollbackPermissions = true;
|
||||
emit propagator()->touchedFile(QString::fromStdWString(parentFolderPath.wstring()));
|
||||
}
|
||||
}
|
||||
catch (const std::filesystem::filesystem_error &e)
|
||||
{
|
||||
qCWarning(lcPropagateLocalMkdir) << "exception when checking parent folder access rights" << e.what() << e.path1().c_str() << e.path2().c_str();
|
||||
}
|
||||
|
||||
emit propagator()->touchedFile(newDirStr);
|
||||
QDir localDir(propagator()->localPath());
|
||||
if (!localDir.mkpath(_item->_file)) {
|
||||
|
@ -188,6 +208,33 @@ void PropagateLocalMkdir::startLocalMkdir()
|
|||
return;
|
||||
}
|
||||
|
||||
if (!_item->_remotePerm.isNull() &&
|
||||
!_item->_remotePerm.hasPermission(RemotePermissions::CanAddFile) &&
|
||||
!_item->_remotePerm.hasPermission(RemotePermissions::CanRename) &&
|
||||
!_item->_remotePerm.hasPermission(RemotePermissions::CanMove) &&
|
||||
!_item->_remotePerm.hasPermission(RemotePermissions::CanAddSubDirectories)) {
|
||||
try {
|
||||
FileSystem::setFolderPermissions(newDirStr, FileSystem::FolderPermissions::ReadOnly);
|
||||
}
|
||||
catch (const std::filesystem::filesystem_error &e)
|
||||
{
|
||||
qCWarning(lcPropagateLocalMkdir) << "exception when checking parent folder access rights" << e.what() << e.path1().c_str() << e.path2().c_str();
|
||||
done(SyncFileItem::NormalError, tr("The folder %1 cannot be made read-only: %2").arg(_item->_file, e.what()), ErrorCategory::GenericError);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (parentNeedRollbackPermissions) {
|
||||
FileSystem::setFolderPermissions(QString::fromStdWString(parentFolderPath.wstring()), FileSystem::FolderPermissions::ReadOnly);
|
||||
emit propagator()->touchedFile(QString::fromStdWString(parentFolderPath.wstring()));
|
||||
}
|
||||
}
|
||||
catch (const std::filesystem::filesystem_error &e)
|
||||
{
|
||||
qCWarning(lcPropagateLocalMkdir) << "exception when checking parent folder access rights" << e.what() << e.path1().c_str() << e.path2().c_str();
|
||||
}
|
||||
|
||||
// Insert the directory into the database. The correct etag will be set later,
|
||||
// once all contents have been propagated, because should_update_metadata is true.
|
||||
// Adding an entry with a dummy etag to the database still makes sense here
|
||||
|
@ -257,12 +304,71 @@ void PropagateLocalRename::start()
|
|||
return;
|
||||
}
|
||||
|
||||
auto targetParentFolderPath = std::filesystem::path{};
|
||||
auto targetParentFolderWasReadOnly = false;
|
||||
try {
|
||||
const auto newDirPath = std::filesystem::path{targetFile.toStdWString()};
|
||||
Q_ASSERT(newDirPath.has_parent_path());
|
||||
targetParentFolderPath = newDirPath.parent_path();
|
||||
if (FileSystem::isFolderReadOnly(targetParentFolderPath)) {
|
||||
targetParentFolderWasReadOnly = true;
|
||||
FileSystem::setFolderPermissions(QString::fromStdWString(targetParentFolderPath.wstring()), FileSystem::FolderPermissions::ReadWrite);
|
||||
emit propagator()->touchedFile(QString::fromStdWString(targetParentFolderPath.wstring()));
|
||||
}
|
||||
}
|
||||
catch (const std::filesystem::filesystem_error &e)
|
||||
{
|
||||
qCWarning(lcPropagateLocalRename) << "exception when checking parent folder access rights" << e.what() << e.path1().c_str() << e.path2().c_str();
|
||||
}
|
||||
|
||||
auto originParentFolderPath = std::filesystem::path{};
|
||||
auto originParentFolderWasReadOnly = false;
|
||||
try {
|
||||
const auto newDirPath = std::filesystem::path{existingFile.toStdWString()};
|
||||
Q_ASSERT(newDirPath.has_parent_path());
|
||||
originParentFolderPath = newDirPath.parent_path();
|
||||
if (FileSystem::isFolderReadOnly(originParentFolderPath)) {
|
||||
originParentFolderWasReadOnly = true;
|
||||
FileSystem::setFolderPermissions(QString::fromStdWString(originParentFolderPath.wstring()), FileSystem::FolderPermissions::ReadWrite);
|
||||
emit propagator()->touchedFile(QString::fromStdWString(originParentFolderPath.wstring()));
|
||||
}
|
||||
}
|
||||
catch (const std::filesystem::filesystem_error &e)
|
||||
{
|
||||
qCWarning(lcPropagateLocalRename) << "exception when checking parent folder access rights" << e.what() << e.path1().c_str() << e.path2().c_str();
|
||||
}
|
||||
|
||||
const auto restoreTargetPermissions = [this] (const auto &parentFolderPath) {
|
||||
try {
|
||||
FileSystem::setFolderPermissions(QString::fromStdWString(parentFolderPath.wstring()), FileSystem::FolderPermissions::ReadOnly);
|
||||
emit propagator()->touchedFile(QString::fromStdWString(parentFolderPath.wstring()));
|
||||
}
|
||||
catch (const std::filesystem::filesystem_error &e)
|
||||
{
|
||||
qCWarning(lcPropagateLocalRename) << "exception when checking parent folder access rights" << e.what() << e.path1().c_str() << e.path2().c_str();
|
||||
}
|
||||
};
|
||||
|
||||
emit propagator()->touchedFile(existingFile);
|
||||
emit propagator()->touchedFile(targetFile);
|
||||
if (QString renameError; !FileSystem::rename(existingFile, targetFile, &renameError)) {
|
||||
if (targetParentFolderWasReadOnly) {
|
||||
restoreTargetPermissions(targetParentFolderPath);
|
||||
}
|
||||
if (originParentFolderWasReadOnly) {
|
||||
restoreTargetPermissions(originParentFolderPath);
|
||||
}
|
||||
|
||||
done(SyncFileItem::NormalError, renameError, ErrorCategory::GenericError);
|
||||
return;
|
||||
}
|
||||
|
||||
if (targetParentFolderWasReadOnly) {
|
||||
restoreTargetPermissions(targetParentFolderPath);
|
||||
}
|
||||
if (originParentFolderWasReadOnly) {
|
||||
restoreTargetPermissions(originParentFolderPath);
|
||||
}
|
||||
}
|
||||
|
||||
SyncJournalFileRecord oldRecord;
|
||||
|
|
|
@ -17,8 +17,7 @@
|
|||
#include <QJsonValue>
|
||||
|
||||
#include <memory>
|
||||
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
PathComponents::PathComponents(const char *path)
|
||||
: PathComponents { QString::fromUtf8(path) }
|
||||
|
@ -48,10 +47,13 @@ PathComponents PathComponents::subComponents() const &
|
|||
void DiskFileModifier::remove(const QString &relativePath)
|
||||
{
|
||||
QFileInfo fi { _rootDir.filePath(relativePath) };
|
||||
if (fi.isFile())
|
||||
if (fi.isFile()) {
|
||||
QVERIFY(_rootDir.remove(relativePath));
|
||||
else
|
||||
QVERIFY(QDir { fi.filePath() }.removeRecursively());
|
||||
} else {
|
||||
const auto pathToDelete = fi.filePath().toStdWString();
|
||||
std::filesystem::permissions(pathToDelete, std::filesystem::perms::owner_exec, std::filesystem::perm_options::add);
|
||||
QVERIFY(std::filesystem::remove_all(pathToDelete));
|
||||
}
|
||||
}
|
||||
|
||||
void DiskFileModifier::insert(const QString &relativePath, qint64 size, char contentChar)
|
||||
|
|
|
@ -5,11 +5,15 @@
|
|||
*
|
||||
*/
|
||||
|
||||
#include <QtTest>
|
||||
#include "syncenginetestutils.h"
|
||||
#include <syncengine.h>
|
||||
#include "common/ownsql.h"
|
||||
|
||||
#include <QtTest>
|
||||
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
|
||||
using namespace OCC;
|
||||
|
||||
static void applyPermissionsFromName(FileInfo &info) {
|
||||
|
@ -69,7 +73,8 @@ class TestPermissions : public QObject
|
|||
private slots:
|
||||
void initTestCase()
|
||||
{
|
||||
OCC::Logger::instance()->setLogDebug(true);
|
||||
Logger::instance()->setLogFlush(true);
|
||||
Logger::instance()->setLogDebug(true);
|
||||
}
|
||||
|
||||
void t7pl()
|
||||
|
@ -111,18 +116,58 @@ private slots:
|
|||
assertCsyncJournalOk(fakeFolder.syncJournal());
|
||||
qInfo("Do some changes and see how they propagate");
|
||||
|
||||
const auto removeReadOnly = [&] (const QString &file) {
|
||||
const auto fileInfoToDelete = QFileInfo(fakeFolder.localPath() + file);
|
||||
QFile(fakeFolder.localPath() + file).setPermissions(QFile::WriteOwner | QFile::ReadOwner);
|
||||
const auto isReadOnly = !static_cast<bool>(std::filesystem::status(fileInfoToDelete.absolutePath().toStdWString()).permissions() & std::filesystem::perms::owner_write);
|
||||
if (isReadOnly) {
|
||||
std::filesystem::permissions(fileInfoToDelete.absolutePath().toStdWString(), std::filesystem::perms::owner_write, std::filesystem::perm_options::add);
|
||||
}
|
||||
fakeFolder.localModifier().remove(file);
|
||||
if (isReadOnly) {
|
||||
std::filesystem::permissions(fileInfoToDelete.absolutePath().toStdWString(), std::filesystem::perms::owner_write, std::filesystem::perm_options::remove);
|
||||
}
|
||||
};
|
||||
|
||||
const auto renameReadOnly = [&] (const QString &relativePath, const QString &relativeDestinationDirectory) {
|
||||
const auto sourceFileInfo = QFileInfo(fakeFolder.localPath() + relativePath);
|
||||
const auto destinationFileInfo = QFileInfo(fakeFolder.localPath() + relativeDestinationDirectory);
|
||||
const auto isSourceReadOnly = !static_cast<bool>(std::filesystem::status(sourceFileInfo.absolutePath().toStdWString()).permissions() & std::filesystem::perms::owner_write);
|
||||
const auto isDestinationReadOnly = !static_cast<bool>(std::filesystem::status(destinationFileInfo.absolutePath().toStdWString()).permissions() & std::filesystem::perms::owner_write);
|
||||
if (isSourceReadOnly) {
|
||||
std::filesystem::permissions(sourceFileInfo.absolutePath().toStdWString(), std::filesystem::perms::owner_write, std::filesystem::perm_options::add);
|
||||
}
|
||||
if (isDestinationReadOnly) {
|
||||
std::filesystem::permissions(destinationFileInfo.absolutePath().toStdWString(), std::filesystem::perms::owner_write, std::filesystem::perm_options::add);
|
||||
}
|
||||
fakeFolder.localModifier().rename(relativePath, relativeDestinationDirectory);
|
||||
if (isSourceReadOnly) {
|
||||
std::filesystem::permissions(sourceFileInfo.absolutePath().toStdWString(), std::filesystem::perms::owner_write, std::filesystem::perm_options::remove);
|
||||
}
|
||||
if (isDestinationReadOnly) {
|
||||
std::filesystem::permissions(destinationFileInfo.absolutePath().toStdWString(), std::filesystem::perms::owner_write, std::filesystem::perm_options::remove);
|
||||
}
|
||||
};
|
||||
|
||||
const auto insertReadOnly = [&] (const QString &file, const int fileSize) {
|
||||
const auto fileInfo = QFileInfo(fakeFolder.localPath() + file);
|
||||
const auto isReadOnly = !static_cast<bool>(std::filesystem::status(fileInfo.absolutePath().toStdWString()).permissions() & std::filesystem::perms::owner_write);
|
||||
if (isReadOnly) {
|
||||
std::filesystem::permissions(fileInfo.absolutePath().toStdWString(), std::filesystem::perms::owner_write, std::filesystem::perm_options::add);
|
||||
}
|
||||
fakeFolder.localModifier().insert(file, fileSize);
|
||||
if (isReadOnly) {
|
||||
std::filesystem::permissions(fileInfo.absolutePath().toStdWString(), std::filesystem::perms::owner_write, std::filesystem::perm_options::remove);
|
||||
}
|
||||
};
|
||||
|
||||
//1. remove the file than cannot be removed
|
||||
// (they should be recovered)
|
||||
fakeFolder.localModifier().remove("normalDirectory_PERM_CKDNV_/cannotBeRemoved_PERM_WVN_.data");
|
||||
fakeFolder.localModifier().remove("readonlyDirectory_PERM_M_/cannotBeRemoved_PERM_WVN_.data");
|
||||
removeReadOnly("readonlyDirectory_PERM_M_/cannotBeRemoved_PERM_WVN_.data");
|
||||
|
||||
//2. remove the file that can be removed
|
||||
// (they should properly be gone)
|
||||
auto removeReadOnly = [&] (const QString &file) {
|
||||
QVERIFY(!QFileInfo(fakeFolder.localPath() + file).permission(QFile::WriteOwner));
|
||||
QFile(fakeFolder.localPath() + file).setPermissions(QFile::WriteOwner | QFile::ReadOwner);
|
||||
fakeFolder.localModifier().remove(file);
|
||||
};
|
||||
removeReadOnly("normalDirectory_PERM_CKDNV_/canBeRemoved_PERM_D_.data");
|
||||
removeReadOnly("readonlyDirectory_PERM_M_/canBeRemoved_PERM_D_.data");
|
||||
|
||||
|
@ -174,7 +219,7 @@ private slots:
|
|||
QCOMPARE(c2->size, cannotBeModifiedSize + 1);
|
||||
// remove the conflicts for the next state comparison
|
||||
fakeFolder.localModifier().remove(c1->path());
|
||||
fakeFolder.localModifier().remove(c2->path());
|
||||
removeReadOnly(c2->path());
|
||||
|
||||
//4. File should be updated, that's tested by assertLocalAndRemoteDir
|
||||
QCOMPARE(currentLocalState.find("normalDirectory_PERM_CKDNV_/canBeModified_PERM_W_.data")->size, canBeModifiedSize + 1);
|
||||
|
@ -191,7 +236,7 @@ private slots:
|
|||
|
||||
//6. Create a new file in a read only folder
|
||||
// (they should not be uploaded)
|
||||
fakeFolder.localModifier().insert("readonlyDirectory_PERM_M_/newFile_PERM_WDNV_.data", 105 );
|
||||
insertReadOnly("readonlyDirectory_PERM_M_/newFile_PERM_WDNV_.data", 105 );
|
||||
|
||||
applyPermissionsFromName(fakeFolder.remoteModifier());
|
||||
// error: can't upload to readonly
|
||||
|
@ -205,7 +250,7 @@ private slots:
|
|||
QVERIFY(currentLocalState.find("readonlyDirectory_PERM_M_/newFile_PERM_WDNV_.data"));
|
||||
QVERIFY(!fakeFolder.currentRemoteState().find("readonlyDirectory_PERM_M_/newFile_PERM_WDNV_.data"));
|
||||
// remove it so next test succeed.
|
||||
fakeFolder.localModifier().remove("readonlyDirectory_PERM_M_/newFile_PERM_WDNV_.data");
|
||||
removeReadOnly("readonlyDirectory_PERM_M_/newFile_PERM_WDNV_.data");
|
||||
// Both side should still be the same
|
||||
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
||||
|
||||
|
@ -213,7 +258,7 @@ private slots:
|
|||
//######################################################################
|
||||
qInfo( "remove the read only directory" );
|
||||
// -> It must be recovered
|
||||
fakeFolder.localModifier().remove("readonlyDirectory_PERM_M_");
|
||||
removeReadOnly("readonlyDirectory_PERM_M_");
|
||||
applyPermissionsFromName(fakeFolder.remoteModifier());
|
||||
QVERIFY(fakeFolder.syncOnce());
|
||||
assertCsyncJournalOk(fakeFolder.syncJournal());
|
||||
|
@ -234,7 +279,7 @@ private slots:
|
|||
|
||||
//Missing directory should be restored
|
||||
//new directory should be uploaded
|
||||
fakeFolder.localModifier().rename("readonlyDirectory_PERM_M_/subdir_PERM_CK_", "normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_");
|
||||
renameReadOnly("readonlyDirectory_PERM_M_/subdir_PERM_CK_", "normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_");
|
||||
applyPermissionsFromName(fakeFolder.remoteModifier());
|
||||
QVERIFY(fakeFolder.syncOnce());
|
||||
currentLocalState = fakeFolder.currentLocalState();
|
||||
|
@ -271,10 +316,10 @@ private slots:
|
|||
//1. rename a directory in a read only folder
|
||||
//Missing directory should be restored
|
||||
//new directory should stay but not be uploaded
|
||||
fakeFolder.localModifier().rename("readonlyDirectory_PERM_M_/subdir_PERM_CK_", "readonlyDirectory_PERM_M_/newname_PERM_CK_" );
|
||||
renameReadOnly("readonlyDirectory_PERM_M_/subdir_PERM_CK_", "readonlyDirectory_PERM_M_/newname_PERM_CK_" );
|
||||
|
||||
//2. move a directory from read to read only (move the directory from previous step)
|
||||
fakeFolder.localModifier().rename("normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_", "readonlyDirectory_PERM_M_/moved_PERM_CK_" );
|
||||
renameReadOnly("normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_", "readonlyDirectory_PERM_M_/moved_PERM_CK_" );
|
||||
|
||||
// error: can't upload to readonly!
|
||||
QVERIFY(!fakeFolder.syncOnce());
|
||||
|
@ -288,7 +333,7 @@ private slots:
|
|||
// new still exist
|
||||
QVERIFY(currentLocalState.find("readonlyDirectory_PERM_M_/newname_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data" ));
|
||||
// but is not on server: so remove it locally for the future comparison
|
||||
fakeFolder.localModifier().remove("readonlyDirectory_PERM_M_/newname_PERM_CK_");
|
||||
removeReadOnly("readonlyDirectory_PERM_M_/newname_PERM_CK_");
|
||||
|
||||
//2.
|
||||
// old removed
|
||||
|
@ -298,7 +343,7 @@ private slots:
|
|||
// new still there
|
||||
QVERIFY(currentLocalState.find("readonlyDirectory_PERM_M_/moved_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data" ));
|
||||
//but not on server
|
||||
fakeFolder.localModifier().remove("readonlyDirectory_PERM_M_/moved_PERM_CK_");
|
||||
removeReadOnly("readonlyDirectory_PERM_M_/moved_PERM_CK_");
|
||||
fakeFolder.remoteModifier().remove("normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_");
|
||||
|
||||
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
||||
|
@ -332,7 +377,7 @@ private slots:
|
|||
int count = 0;
|
||||
while (auto i = findConflict(currentLocalState, "readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data")) {
|
||||
QVERIFY((i->contentChar == 's') || (i->contentChar == 'd'));
|
||||
fakeFolder.localModifier().remove(i->path());
|
||||
removeReadOnly(i->path());
|
||||
currentLocalState = fakeFolder.currentLocalState();
|
||||
count++;
|
||||
}
|
||||
|
@ -546,6 +591,156 @@ private slots:
|
|||
|
||||
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
||||
}
|
||||
|
||||
static void demo_perms(std::filesystem::perms p)
|
||||
{
|
||||
using std::filesystem::perms;
|
||||
auto show = [=](char op, perms perm)
|
||||
{
|
||||
std::cout << (perms::none == (perm & p) ? '-' : op);
|
||||
};
|
||||
show('r', perms::owner_read);
|
||||
show('w', perms::owner_write);
|
||||
show('x', perms::owner_exec);
|
||||
show('r', perms::group_read);
|
||||
show('w', perms::group_write);
|
||||
show('x', perms::group_exec);
|
||||
show('r', perms::others_read);
|
||||
show('w', perms::others_write);
|
||||
show('x', perms::others_exec);
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
void testReadOnlyFolderIsReallyReadOnly()
|
||||
{
|
||||
FakeFolder fakeFolder{FileInfo{}};
|
||||
|
||||
auto &remote = fakeFolder.remoteModifier();
|
||||
|
||||
remote.mkdir("readOnlyFolder");
|
||||
|
||||
remote.find("readOnlyFolder")->permissions = RemotePermissions::fromServerString("M");
|
||||
|
||||
QVERIFY(fakeFolder.syncOnce());
|
||||
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
||||
|
||||
const auto folderStatus = std::filesystem::status(static_cast<QString>(fakeFolder.localPath() + QStringLiteral("/readOnlyFolder")).toStdWString());
|
||||
QVERIFY(folderStatus.permissions() & std::filesystem::perms::owner_read);
|
||||
}
|
||||
|
||||
void testReadWriteFolderIsReallyReadWrite()
|
||||
{
|
||||
FakeFolder fakeFolder{FileInfo{}};
|
||||
|
||||
auto &remote = fakeFolder.remoteModifier();
|
||||
|
||||
remote.mkdir("readWriteFolder");
|
||||
|
||||
remote.find("readWriteFolder")->permissions = RemotePermissions::fromServerString("WDNVRSM");
|
||||
|
||||
QVERIFY(fakeFolder.syncOnce());
|
||||
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
||||
|
||||
const auto folderStatus = std::filesystem::status(static_cast<QString>(fakeFolder.localPath() + QStringLiteral("/readWriteFolder")).toStdWString());
|
||||
QVERIFY(folderStatus.permissions() & std::filesystem::perms::owner_read);
|
||||
QVERIFY(folderStatus.permissions() & std::filesystem::perms::owner_write);
|
||||
}
|
||||
|
||||
void testChangePermissionsFolder()
|
||||
{
|
||||
FakeFolder fakeFolder{FileInfo{}};
|
||||
|
||||
auto &remote = fakeFolder.remoteModifier();
|
||||
|
||||
remote.mkdir("testFolder");
|
||||
|
||||
remote.find("testFolder")->permissions = RemotePermissions::fromServerString("M");
|
||||
|
||||
QVERIFY(fakeFolder.syncOnce());
|
||||
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
||||
|
||||
auto folderStatus = std::filesystem::status(static_cast<QString>(fakeFolder.localPath() + QStringLiteral("/testFolder")).toStdWString());
|
||||
QVERIFY(folderStatus.permissions() & std::filesystem::perms::owner_read);
|
||||
QVERIFY(!static_cast<bool>(folderStatus.permissions() & std::filesystem::perms::owner_write));
|
||||
|
||||
remote.find("testFolder")->permissions = RemotePermissions::fromServerString("WDNVRSM");
|
||||
|
||||
QVERIFY(fakeFolder.syncOnce());
|
||||
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
||||
|
||||
folderStatus = std::filesystem::status(static_cast<QString>(fakeFolder.localPath() + QStringLiteral("/testFolder")).toStdWString());
|
||||
QVERIFY(folderStatus.permissions() & std::filesystem::perms::owner_read);
|
||||
QVERIFY(folderStatus.permissions() & std::filesystem::perms::owner_write);
|
||||
|
||||
remote.find("testFolder")->permissions = RemotePermissions::fromServerString("M");
|
||||
|
||||
QVERIFY(fakeFolder.syncOnce());
|
||||
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
||||
|
||||
folderStatus = std::filesystem::status(static_cast<QString>(fakeFolder.localPath() + QStringLiteral("/testFolder")).toStdWString());
|
||||
QVERIFY(folderStatus.permissions() & std::filesystem::perms::owner_read);
|
||||
QVERIFY(!static_cast<bool>(folderStatus.permissions() & std::filesystem::perms::owner_write));
|
||||
}
|
||||
|
||||
void testChangePermissionsForFolderHierarchy()
|
||||
{
|
||||
FakeFolder fakeFolder{FileInfo{}};
|
||||
|
||||
auto &remote = fakeFolder.remoteModifier();
|
||||
|
||||
remote.mkdir("testFolder");
|
||||
|
||||
remote.find("testFolder")->permissions = RemotePermissions::fromServerString("M");
|
||||
|
||||
QVERIFY(fakeFolder.syncOnce());
|
||||
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
||||
|
||||
remote.mkdir("testFolder/subFolderReadWrite");
|
||||
remote.mkdir("testFolder/subFolderReadOnly");
|
||||
|
||||
remote.find("testFolder/subFolderReadOnly")->permissions = RemotePermissions::fromServerString("m");
|
||||
|
||||
QVERIFY(fakeFolder.syncOnce());
|
||||
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
||||
|
||||
auto testFolderStatus = std::filesystem::status(static_cast<QString>(fakeFolder.localPath() + QStringLiteral("/testFolder")).toStdWString());
|
||||
QVERIFY(testFolderStatus.permissions() & std::filesystem::perms::owner_read);
|
||||
QVERIFY(!static_cast<bool>(testFolderStatus.permissions() & std::filesystem::perms::owner_write));
|
||||
auto subFolderReadWriteStatus = std::filesystem::status(static_cast<QString>(fakeFolder.localPath() + QStringLiteral("/testFolder/subFolderReadWrite")).toStdWString());
|
||||
QVERIFY(subFolderReadWriteStatus.permissions() & std::filesystem::perms::owner_read);
|
||||
QVERIFY(subFolderReadWriteStatus.permissions() & std::filesystem::perms::owner_write);
|
||||
auto subFolderReadOnlyStatus = std::filesystem::status(static_cast<QString>(fakeFolder.localPath() + QStringLiteral("/testFolder/subFolderReadOnly")).toStdWString());
|
||||
QVERIFY(subFolderReadOnlyStatus.permissions() & std::filesystem::perms::owner_read);
|
||||
QVERIFY(!static_cast<bool>(subFolderReadOnlyStatus.permissions() & std::filesystem::perms::owner_write));
|
||||
|
||||
remote.find("testFolder/subFolderReadOnly")->permissions = RemotePermissions::fromServerString("WDNVRSm");
|
||||
remote.find("testFolder/subFolderReadWrite")->permissions = RemotePermissions::fromServerString("m");
|
||||
remote.mkdir("testFolder/newSubFolder");
|
||||
remote.create("testFolder/testFile", 12, '9');
|
||||
remote.create("testFolder/testReadOnlyFile", 13, '8');
|
||||
remote.find("testFolder/testReadOnlyFile")->permissions = RemotePermissions::fromServerString("m");
|
||||
|
||||
QVERIFY(fakeFolder.syncOnce());
|
||||
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
||||
|
||||
subFolderReadWriteStatus = std::filesystem::status(static_cast<QString>(fakeFolder.localPath() + QStringLiteral("/testFolder/subFolderReadWrite")).toStdWString());
|
||||
QVERIFY(subFolderReadWriteStatus.permissions() & std::filesystem::perms::owner_read);
|
||||
QVERIFY(!static_cast<bool>(subFolderReadWriteStatus.permissions() & std::filesystem::perms::owner_write));
|
||||
subFolderReadOnlyStatus = std::filesystem::status(static_cast<QString>(fakeFolder.localPath() + QStringLiteral("/testFolder/subFolderReadOnly")).toStdWString());
|
||||
QVERIFY(subFolderReadOnlyStatus.permissions() & std::filesystem::perms::owner_read);
|
||||
QVERIFY(subFolderReadOnlyStatus.permissions() & std::filesystem::perms::owner_write);
|
||||
|
||||
remote.rename("testFolder/subFolderReadOnly", "testFolder/subFolderReadWriteNew");
|
||||
remote.rename("testFolder/subFolderReadWrite", "testFolder/subFolderReadOnlyNew");
|
||||
remote.rename("testFolder/testFile", "testFolder/testFileNew");
|
||||
remote.rename("testFolder/testReadOnlyFile", "testFolder/testReadOnlyFileNew");
|
||||
|
||||
QVERIFY(fakeFolder.syncOnce());
|
||||
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
||||
testFolderStatus = std::filesystem::status(static_cast<QString>(fakeFolder.localPath() + QStringLiteral("/testFolder")).toStdWString());
|
||||
QVERIFY(testFolderStatus.permissions() & std::filesystem::perms::owner_read);
|
||||
QVERIFY(!static_cast<bool>(testFolderStatus.permissions() & std::filesystem::perms::owner_write));
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_GUILESS_MAIN(TestPermissions)
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
#include <QFile>
|
||||
#include <QtTest>
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
using namespace OCC;
|
||||
|
||||
namespace {
|
||||
|
@ -93,6 +95,11 @@ private slots:
|
|||
Logger::instance()->setLogDebug(true);
|
||||
}
|
||||
|
||||
void init()
|
||||
{
|
||||
QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8"));
|
||||
}
|
||||
|
||||
void testFileDownload() {
|
||||
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
|
||||
ItemCompletedSpy completeSpy(fakeFolder);
|
||||
|
@ -813,35 +820,41 @@ private slots:
|
|||
QVERIFY(fakeFolder.currentLocalState().find("A/t𠜎t"));
|
||||
|
||||
#if !defined(Q_OS_MAC) && !defined(Q_OS_WIN)
|
||||
// Try again with a locale that can represent ö but not 𠜎 (4-byte utf8).
|
||||
QTextCodec::setCodecForLocale(QTextCodec::codecForName("ISO-8859-15"));
|
||||
QVERIFY(QTextCodec::codecForLocale()->mibEnum() == 111);
|
||||
try {
|
||||
// Try again with a locale that can represent ö but not 𠜎 (4-byte utf8).
|
||||
QTextCodec::setCodecForLocale(QTextCodec::codecForName("ISO-8859-15"));
|
||||
QVERIFY(QTextCodec::codecForLocale()->mibEnum() == 111);
|
||||
|
||||
fakeFolder.remoteModifier().insert("B/tößt");
|
||||
fakeFolder.remoteModifier().insert("B/t𠜎t");
|
||||
QVERIFY(fakeFolder.syncOnce());
|
||||
QVERIFY(fakeFolder.currentLocalState().find("B/tößt"));
|
||||
QVERIFY(!fakeFolder.currentLocalState().find("B/t𠜎t"));
|
||||
QVERIFY(!fakeFolder.currentLocalState().find("B/t?t"));
|
||||
QVERIFY(!fakeFolder.currentLocalState().find("B/t??t"));
|
||||
QVERIFY(!fakeFolder.currentLocalState().find("B/t???t"));
|
||||
QVERIFY(!fakeFolder.currentLocalState().find("B/t????t"));
|
||||
QVERIFY(fakeFolder.syncOnce());
|
||||
QVERIFY(fakeFolder.currentRemoteState().find("B/tößt"));
|
||||
QVERIFY(fakeFolder.currentRemoteState().find("B/t𠜎t"));
|
||||
fakeFolder.remoteModifier().insert("B/tößt");
|
||||
fakeFolder.remoteModifier().insert("B/t𠜎t");
|
||||
QVERIFY(fakeFolder.syncOnce());
|
||||
QVERIFY(fakeFolder.currentLocalState().find("B/tößt"));
|
||||
QVERIFY(!fakeFolder.currentLocalState().find("B/t𠜎t"));
|
||||
QVERIFY(!fakeFolder.currentLocalState().find("B/t?t"));
|
||||
QVERIFY(!fakeFolder.currentLocalState().find("B/t??t"));
|
||||
QVERIFY(!fakeFolder.currentLocalState().find("B/t???t"));
|
||||
QVERIFY(!fakeFolder.currentLocalState().find("B/t????t"));
|
||||
QVERIFY(fakeFolder.syncOnce());
|
||||
QVERIFY(fakeFolder.currentRemoteState().find("B/tößt"));
|
||||
QVERIFY(fakeFolder.currentRemoteState().find("B/t𠜎t"));
|
||||
|
||||
// Try again with plain ascii
|
||||
QTextCodec::setCodecForLocale(QTextCodec::codecForName("ASCII"));
|
||||
QVERIFY(QTextCodec::codecForLocale()->mibEnum() == 3);
|
||||
// Try again with plain ascii
|
||||
QTextCodec::setCodecForLocale(QTextCodec::codecForName("ASCII"));
|
||||
QVERIFY(QTextCodec::codecForLocale()->mibEnum() == 3);
|
||||
|
||||
fakeFolder.remoteModifier().insert("C/tößt");
|
||||
QVERIFY(fakeFolder.syncOnce());
|
||||
QVERIFY(!fakeFolder.currentLocalState().find("C/tößt"));
|
||||
QVERIFY(!fakeFolder.currentLocalState().find("C/t??t"));
|
||||
QVERIFY(!fakeFolder.currentLocalState().find("C/t????t"));
|
||||
QVERIFY(fakeFolder.syncOnce());
|
||||
QVERIFY(fakeFolder.currentRemoteState().find("C/tößt"));
|
||||
fakeFolder.remoteModifier().insert("C/tößt");
|
||||
QVERIFY(fakeFolder.syncOnce());
|
||||
QVERIFY(!fakeFolder.currentLocalState().find("C/tößt"));
|
||||
QVERIFY(!fakeFolder.currentLocalState().find("C/t??t"));
|
||||
QVERIFY(!fakeFolder.currentLocalState().find("C/t????t"));
|
||||
QVERIFY(fakeFolder.syncOnce());
|
||||
QVERIFY(fakeFolder.currentRemoteState().find("C/tößt"));
|
||||
|
||||
}
|
||||
catch (const std::filesystem::filesystem_error &e)
|
||||
{
|
||||
qCritical() << e.what() << e.path1().c_str() << e.path2().c_str() << e.code().message().c_str();
|
||||
}
|
||||
QTextCodec::setCodecForLocale(utf8Locale);
|
||||
#endif
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue