set(NEXTCLOUD_SERVER_VERSION_SECURE_FILEDROP_MIN_SUPPORTED_MINOR 0)
set(NEXTCLOUD_SERVER_VERSION_SECURE_FILEDROP_MIN_SUPPORTED_PATCH 0)
+set(NEXTCLOUD_SERVER_VERSION_MOUNT_ROOT_PROPERTY_SUPPORTED_MAJOR 28)
+set(NEXTCLOUD_SERVER_VERSION_MOUNT_ROOT_PROPERTY_SUPPORTED_MINOR 0)
+set(NEXTCLOUD_SERVER_VERSION_MOUNT_ROOT_PROPERTY_SUPPORTED_PATCH 3)
+
if ( NOT DEFINED MIRALL_VERSION_SUFFIX )
set( MIRALL_VERSION_SUFFIX "git") #e.g. beta1, beta2, rc1
endif( NOT DEFINED MIRALL_VERSION_SUFFIX )
*/
#include "remotepermissions.h"
+
+#include <QLoggingCategory>
+
#include <cstring>
namespace OCC {
+Q_LOGGING_CATEGORY(lcRemotePermissions, "nextcloud.sync.remotepermissions", QtInfoMsg)
+
static const char letters[] = " WDNVCKRSMm";
return perm;
}
-RemotePermissions RemotePermissions::fromServerString(const QString &value)
+template <typename T>
+RemotePermissions RemotePermissions::internalFromServerString(const QString &value,
+ const T&otherProperties,
+ MountedPermissionAlgorithm algorithm)
{
RemotePermissions perm;
perm.fromArray(value.utf16());
+
+ if (algorithm == MountedPermissionAlgorithm::WildGuessMountedSubProperty) {
+ return perm;
+ }
+
+ if ((otherProperties.contains(QStringLiteral("is-mount-root")) && otherProperties.value(QStringLiteral("is-mount-root")) == QStringLiteral("false") && perm.hasPermission(RemotePermissions::IsMounted)) ||
+ (!otherProperties.contains(QStringLiteral("is-mount-root")) && perm.hasPermission(RemotePermissions::IsMounted))) {
+ /* All the entries in a external storage have 'M' in their permission. However, for all
+ purposes in the desktop client, we only need to know about the mount points.
+ So replace the 'M' by a 'm' for every sub entries in an external storage */
+ perm.unsetPermission(RemotePermissions::IsMounted);
+ perm.setPermission(RemotePermissions::IsMountedSub);
+ qCInfo(lcRemotePermissions()) << otherProperties.value(QStringLiteral("permissions")) << "replacing M permissions by m for subfolders inside a group folder";
+ }
+
return perm;
}
+RemotePermissions RemotePermissions::fromServerString(const QString &value,
+ MountedPermissionAlgorithm algorithm,
+ const QMap<QString, QString> &otherProperties)
+{
+ return internalFromServerString(value, otherProperties, algorithm);
+}
+
+RemotePermissions RemotePermissions::fromServerString(const QString &value,
+ MountedPermissionAlgorithm algorithm,
+ const QVariantMap &otherProperties)
+{
+ return internalFromServerString(value, otherProperties, algorithm);
+}
+
} // namespace OCC
PermissionsCount = IsMountedSub
};
+ enum class MountedPermissionAlgorithm {
+ UseMountRootProperty,
+ WildGuessMountedSubProperty,
+ };
+
/// null permissions
RemotePermissions() = default;
static RemotePermissions fromDbValue(const QByteArray &);
/// read a permissions string received from the server, never null
- static RemotePermissions fromServerString(const QString &);
+ static RemotePermissions fromServerString(const QString &value,
+ MountedPermissionAlgorithm algorithm = MountedPermissionAlgorithm::WildGuessMountedSubProperty,
+ const QMap<QString, QString> &otherProperties = {});
+
+ /// read a permissions string received from the server, never null
+ static RemotePermissions fromServerString(const QString &value,
+ MountedPermissionAlgorithm algorithm,
+ const QVariantMap &otherProperties = {});
[[nodiscard]] bool hasPermission(Permissions p) const
{
{
return dbg << p.toString();
}
+
+private:
+
+ template <typename T>
+ static RemotePermissions internalFromServerString(const QString &value,
+ const T&otherProperties,
+ MountedPermissionAlgorithm algorithm);
};
QByteArrayLiteral("http://owncloud.org/ns:size"),
QByteArrayLiteral("http://owncloud.org/ns:id"),
QByteArrayLiteral("http://owncloud.org/ns:permissions"),
- QByteArrayLiteral("http://owncloud.org/ns:checksums")};
+ QByteArrayLiteral("http://owncloud.org/ns:checksums"),
+ QByteArrayLiteral("http://nextcloud.org/ns:is-mount-root")};
job->setProperties(props);
connect(job, &LsColJob::directoryListingIterated, this, &EditLocallyJob::slotDirectoryListingIterated);
const auto cleanName = nameWithoutDavPath.startsWith(remoteFolderPathWithoutLeadingSlash)
? nameWithoutDavPath.mid(remoteFolderPathWithoutLeadingSlash.size()) : nameWithoutDavPath;
disconnect(job, &LsColJob::directoryListingIterated, this, &EditLocallyJob::slotDirectoryListingIterated);
- _fileParentItem = SyncFileItem::fromProperties(cleanName, properties);
+ _fileParentItem = SyncFileItem::fromProperties(cleanName,
+ properties,
+ _accountState->account()->serverHasMountRootProperty() ? RemotePermissions::MountedPermissionAlgorithm::UseMountRootProperty : RemotePermissions::MountedPermissionAlgorithm::WildGuessMountedSubProperty);
}
}
auto props = QList<QByteArray>() << "resourcetype"
<< "http://owncloud.org/ns:size"
<< "http://owncloud.org/ns:permissions"
+ << "http://nextcloud.org/ns:is-mount-root"
<< "http://owncloud.org/ns:fileid";
if (_accountState->account()->capabilities().clientSideEncryptionAvailable()) {
props << "http://nextcloud.org/ns:is-encrypted";
void InvalidFilenameDialog::checkIfAllowedToRename()
{
const auto propfindJob = new PropfindJob(_account, QDir::cleanPath(_folder->remotePath() + _originalFileName));
- propfindJob->setProperties({ "http://owncloud.org/ns:permissions" });
+ propfindJob->setProperties({"http://owncloud.org/ns:permissions", "http://nextcloud.org/ns:is-mount-root"});
connect(propfindJob, &PropfindJob::result, this, &InvalidFilenameDialog::onPropfindPermissionSuccess);
connect(propfindJob, &PropfindJob::finishedWithError, this, &InvalidFilenameDialog::onPropfindPermissionError);
propfindJob->start();
}));
auto *const lsColJob = new LsColJob(folder->accountState()->account(), QDir::cleanPath(folder->remotePath() + lsColJobPath));
- lsColJob->setProperties({QByteArrayLiteral("http://owncloud.org/ns:share-types"), QByteArrayLiteral("http://owncloud.org/ns:permissions")});
+ lsColJob->setProperties({QByteArrayLiteral("http://owncloud.org/ns:share-types"), QByteArrayLiteral("http://owncloud.org/ns:permissions"), QByteArrayLiteral("http://nextcloud.org/ns:is-mount-root")});
const auto folderAlias = customStateRequestInfo.folderAlias;
components.value(2).toInt());
}
+bool Account::serverHasMountRootProperty() const
+{
+ if (serverVersionInt() == 0) {
+ return false;
+ }
+
+ return serverVersionInt() >= Account::makeServerVersion(NEXTCLOUD_SERVER_VERSION_MOUNT_ROOT_PROPERTY_SUPPORTED_MAJOR,
+ NEXTCLOUD_SERVER_VERSION_MOUNT_ROOT_PROPERTY_SUPPORTED_MINOR,
+ NEXTCLOUD_SERVER_VERSION_MOUNT_ROOT_PROPERTY_SUPPORTED_PATCH);
+}
+
bool Account::serverVersionUnsupported() const
{
if (serverVersionInt() == 0) {
*/
[[nodiscard]] int serverVersionInt() const;
+ [[nodiscard]] bool serverHasMountRootProperty() const;
+
static constexpr int makeServerVersion(const int majorVersion, const int minorVersion, const int patchVersion) {
return (majorVersion << 16) + (minorVersion << 8) + patchVersion;
};
void CaseClashConflictSolver::checkIfAllowedToRename()
{
const auto propfindJob = new PropfindJob(_account, QDir::cleanPath(remoteTargetFilePath()));
- propfindJob->setProperties({ "http://owncloud.org/ns:permissions" });
+ propfindJob->setProperties({"http://owncloud.org/ns:permissions", "http://nextcloud.org/ns:is-mount-root"});
connect(propfindJob, &PropfindJob::result, this, &CaseClashConflictSolver::onPropfindPermissionSuccess);
connect(propfindJob, &PropfindJob::finishedWithError, this, &CaseClashConflictSolver::onPropfindPermissionError);
propfindJob->start();
// Check local permission if we are allowed to put move the file here
// Technically we should use the permissions from the server, but we'll assume it is the same
+ const auto serverHasMountRootProperty = _discoveryData->_account->serverHasMountRootProperty();
const auto isExternalStorage = base._remotePerm.hasPermission(RemotePermissions::IsMounted);
const auto movePerms = checkMovePermissions(base._remotePerm, originalPath, item->isDirectory());
- if (!movePerms.sourceOk || !movePerms.destinationOk || isExternalStorage || isE2eeMoveOnlineOnlyItemWithCfApi) {
+ if (!movePerms.sourceOk || !movePerms.destinationOk || (serverHasMountRootProperty && isExternalStorage) || isE2eeMoveOnlineOnlyItemWithCfApi) {
qCInfo(lcDisco) << "Move without permission to rename base file, "
<< "source:" << movePerms.sourceOk
<< ", target:" << movePerms.destinationOk
<< ", targetNew:" << movePerms.destinationNewOk
- << ", isExternalStorage:" << isExternalStorage;
+ << ", isExternalStorage:" << isExternalStorage
+ << ", serverHasMountRootProperty:" << serverHasMountRootProperty
+ << ", base._remotePerm:" << base._remotePerm.toString()
+ << ", base.path():" << base.path();
// If we can create the destination, do that.
// Permission errors on the destination will be handled by checkPermissions later.
<< "http://nextcloud.org/ns:lock-time"
<< "http://nextcloud.org/ns:lock-timeout";
}
+ props << "http://nextcloud.org/ns:is-mount-root";
lsColJob->setProperties(props);
return _encryptionStatusRequired;
}
-static void propertyMapToRemoteInfo(const QMap<QString, QString> &map, RemoteInfo &result)
+static void propertyMapToRemoteInfo(const QMap<QString, QString> &map, RemotePermissions::MountedPermissionAlgorithm algorithm, RemoteInfo &result)
{
for (auto it = map.constBegin(); it != map.constEnd(); ++it) {
QString property = it.key();
} else if (property == "dDC") {
result.directDownloadCookies = value;
} else if (property == "permissions") {
- result.remotePerm = RemotePermissions::fromServerString(value);
+ result.remotePerm = RemotePermissions::fromServerString(value, algorithm, map);
} else if (property == "checksums") {
result.checksumHeader = findBestChecksum(value.toUtf8());
} else if (property == "share-types" && !value.isEmpty()) {
// The first entry is for the folder itself, we should process it differently.
_ignoredFirst = true;
if (map.contains("permissions")) {
- auto perm = RemotePermissions::fromServerString(map.value("permissions"));
+ auto perm = RemotePermissions::fromServerString(map.value("permissions"),
+ _account->serverHasMountRootProperty() ? RemotePermissions::MountedPermissionAlgorithm::UseMountRootProperty : RemotePermissions::MountedPermissionAlgorithm::WildGuessMountedSubProperty,
+ map);
+ qCInfo(lcDiscovery()) << file << map.value("permissions") << map;
emit firstDirectoryPermissions(perm);
_isExternalStorage = perm.hasPermission(RemotePermissions::IsMounted);
}
_size = map.value("size").toInt();
}
} else {
-
RemoteInfo result;
int slash = file.lastIndexOf('/');
result.name = file.mid(slash + 1);
result.size = -1;
- propertyMapToRemoteInfo(map, result);
+ propertyMapToRemoteInfo(map,
+ _account->serverHasMountRootProperty() ? RemotePermissions::MountedPermissionAlgorithm::UseMountRootProperty : RemotePermissions::MountedPermissionAlgorithm::WildGuessMountedSubProperty,
+ result);
if (result.isDirectory)
result.size = 0;
- if (_isExternalStorage && result.remotePerm.hasPermission(RemotePermissions::IsMounted)) {
- /* All the entries in a external storage have 'M' in their permission. However, for all
- purposes in the desktop client, we only need to know about the mount points.
- So replace the 'M' by a 'm' for every sub entries in an external storage */
- result.remotePerm.unsetPermission(RemotePermissions::IsMounted);
- result.remotePerm.setPermission(RemotePermissions::IsMountedSub);
- }
+ qCInfo(lcDiscovery()) << file << map.value("permissions") << result.remotePerm.toString() << map;
_results.push_back(std::move(result));
}
propagator()->_activeJobList.append(this);
auto propfindJob = new PropfindJob(propagator()->account(), jobPath, this);
- propfindJob->setProperties({QByteArrayLiteral("http://owncloud.org/ns:share-types"), QByteArrayLiteral("http://owncloud.org/ns:permissions")});
+ propfindJob->setProperties({QByteArrayLiteral("http://owncloud.org/ns:share-types"), QByteArrayLiteral("http://owncloud.org/ns:permissions"), QByteArrayLiteral("http://nextcloud.org/ns:is-mount-root")});
connect(propfindJob, &PropfindJob::result, this, [this, jobPath](const QVariantMap &result){
propagator()->_activeJobList.removeOne(this);
- _item->_remotePerm = RemotePermissions::fromServerString(result.value(QStringLiteral("permissions")).toString());
+ _item->_remotePerm = RemotePermissions::fromServerString(result.value(QStringLiteral("permissions")).toString(),
+ propagator()->account()->serverHasMountRootProperty() ? RemotePermissions::MountedPermissionAlgorithm::UseMountRootProperty : RemotePermissions::MountedPermissionAlgorithm::WildGuessMountedSubProperty,
+ result);
+
_item->_sharedByMe = !result.value(QStringLiteral("share-types")).toString().isEmpty();
_item->_isShared = _item->_remotePerm.hasPermission(RemotePermissions::IsShared) || _item->_sharedByMe;
_item->_lastShareStateFetchedTimestamp = QDateTime::currentMSecsSinceEpoch();
_item->_fileId = _job->reply()->rawHeader("OC-FileId");
+ qCInfo(lcPropagateRemoteMkdir()) << "mkcol job error string:" << _item->_errorString << _job->errorString();
_item->_errorString = _job->errorString();
const auto jobHttpReasonPhraseString = _job->reply()->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
return item;
}
-SyncFileItemPtr SyncFileItem::fromProperties(const QString &filePath, const QMap<QString, QString> &properties)
+SyncFileItemPtr SyncFileItem::fromProperties(const QString &filePath, const QMap<QString, QString> &properties, RemotePermissions::MountedPermissionAlgorithm algorithm)
{
SyncFileItemPtr item(new SyncFileItem);
item->_file = filePath;
item->_fileId = properties.value(QStringLiteral("id")).toUtf8();
if (properties.contains(QStringLiteral("permissions"))) {
- item->_remotePerm = RemotePermissions::fromServerString(properties.value("permissions"));
+ item->_remotePerm = RemotePermissions::fromServerString(properties.value("permissions"), algorithm, properties);
}
if (!properties.value(QStringLiteral("share-types")).isEmpty()) {
/** Creates a basic SyncFileItem from remote properties
*/
- [[nodiscard]] static SyncFileItemPtr fromProperties(const QString &filePath, const QMap<QString, QString> &properties);
+ [[nodiscard]] static SyncFileItemPtr fromProperties(const QString &filePath, const QMap<QString, QString> &properties, RemotePermissions::MountedPermissionAlgorithm algorithm);
SyncFileItem()
constexpr int NEXTCLOUD_SERVER_VERSION_SECURE_FILEDROP_MIN_SUPPORTED_MINOR = @NEXTCLOUD_SERVER_VERSION_SECURE_FILEDROP_MIN_SUPPORTED_MINOR@;
constexpr int NEXTCLOUD_SERVER_VERSION_SECURE_FILEDROP_MIN_SUPPORTED_PATCH = @NEXTCLOUD_SERVER_VERSION_SECURE_FILEDROP_MIN_SUPPORTED_PATCH@;
+constexpr int NEXTCLOUD_SERVER_VERSION_MOUNT_ROOT_PROPERTY_SUPPORTED_MAJOR = @NEXTCLOUD_SERVER_VERSION_MOUNT_ROOT_PROPERTY_SUPPORTED_MAJOR@;
+constexpr int NEXTCLOUD_SERVER_VERSION_MOUNT_ROOT_PROPERTY_SUPPORTED_MINOR = @NEXTCLOUD_SERVER_VERSION_MOUNT_ROOT_PROPERTY_SUPPORTED_MINOR@;
+constexpr int NEXTCLOUD_SERVER_VERSION_MOUNT_ROOT_PROPERTY_SUPPORTED_PATCH = @NEXTCLOUD_SERVER_VERSION_MOUNT_ROOT_PROPERTY_SUPPORTED_PATCH@;
+
#endif // VERSION_H