Merge pull request #5801 from nextcloud/bugfix/unsupported-filename-on-server

Bugfix/unsupported filename on server
This commit is contained in:
Matthieu Gallien 2023-06-14 16:17:15 +02:00 committed by GitHub
commit d57eba8890
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 99 additions and 14 deletions

View file

@ -64,12 +64,13 @@ QString illegalCharacterListToString(const QVector<QChar> &illegalCharacters)
namespace OCC {
InvalidFilenameDialog::InvalidFilenameDialog(AccountPtr account, Folder *folder, QString filePath, QWidget *parent)
InvalidFilenameDialog::InvalidFilenameDialog(AccountPtr account, Folder *folder, QString filePath, FileLocation fileLocation, QWidget *parent)
: QDialog(parent)
, _ui(new Ui::InvalidFilenameDialog)
, _account(account)
, _folder(folder)
, _filePath(std::move(filePath))
, _fileLocation(fileLocation)
{
Q_ASSERT(_account);
Q_ASSERT(_folder);
@ -103,7 +104,12 @@ InvalidFilenameDialog::InvalidFilenameDialog(AccountPtr account, Folder *folder,
connect(_ui->filenameLineEdit, &QLineEdit::textChanged, this,
&InvalidFilenameDialog::onFilenameLineEditTextChanged);
if (_fileLocation == FileLocation::NewLocalFile) {
allowRenaming();
_ui->errorLabel->setText({});
} else {
checkIfAllowedToRename();
}
}
InvalidFilenameDialog::~InvalidFilenameDialog() = default;
@ -136,13 +142,7 @@ void InvalidFilenameDialog::onCheckIfAllowedToRenameComplete(const QVariantMap &
}
}
_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
_ui->filenameLineEdit->setEnabled(true);
_ui->filenameLineEdit->selectAll();
const auto filePathFileInfo = QFileInfo(_filePath);
const auto fileName = filePathFileInfo.fileName();
processLeadingOrTrailingSpacesError(fileName);
allowRenaming();
}
bool InvalidFilenameDialog::processLeadingOrTrailingSpacesError(const QString &fileName)
@ -184,6 +184,17 @@ void InvalidFilenameDialog::onPropfindPermissionError(QNetworkReply *reply)
onCheckIfAllowedToRenameComplete({}, reply);
}
void InvalidFilenameDialog::allowRenaming()
{
_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
_ui->filenameLineEdit->setEnabled(true);
_ui->filenameLineEdit->selectAll();
const auto filePathFileInfo = QFileInfo(_filePath);
const auto fileName = filePathFileInfo.fileName();
processLeadingOrTrailingSpacesError(fileName);
}
void InvalidFilenameDialog::useInvalidName()
{
emit acceptedInvalidName(_filePath);

View file

@ -35,7 +35,12 @@ class InvalidFilenameDialog : public QDialog
Q_OBJECT
public:
explicit InvalidFilenameDialog(AccountPtr account, Folder *folder, QString filePath, QWidget *parent = nullptr);
enum class FileLocation {
Default = 0,
NewLocalFile,
};
explicit InvalidFilenameDialog(AccountPtr account, Folder *folder, QString filePath, FileLocation fileLocation = FileLocation::Default, QWidget *parent = nullptr);
~InvalidFilenameDialog() override;
@ -53,6 +58,7 @@ private:
QString _relativeFilePath;
QString _originalFileName;
QString _newFilename;
FileLocation _fileLocation = FileLocation::Default;
void onFilenameLineEditTextChanged(const QString &text);
void onMoveJobFinished();
@ -65,6 +71,7 @@ private:
bool processLeadingOrTrailingSpacesError(const QString &fileName);
void onPropfindPermissionSuccess(const QVariantMap &values);
void onPropfindPermissionError(QNetworkReply *reply = nullptr);
void allowRenaming();
private slots:
void useInvalidName();
};

View file

@ -230,6 +230,7 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
|| a._syncFileItemStatus == SyncFileItem::Restoration
|| a._syncFileItemStatus == SyncFileItem::FileLocked
|| a._syncFileItemStatus == SyncFileItem::FileNameInvalid
|| a._syncFileItemStatus == SyncFileItem::FileNameInvalidOnServer
|| a._syncFileItemStatus == SyncFileItem::FileNameClash) {
colorIconPath.append("state-warning.svg");
return colorIconPath;
@ -348,7 +349,8 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
return !a._links.isEmpty() &&
a._syncFileItemStatus != SyncFileItem::FileNameClash &&
a._syncFileItemStatus != SyncFileItem::Conflict &&
a._syncFileItemStatus != SyncFileItem::FileNameInvalid;
a._syncFileItemStatus != SyncFileItem::FileNameInvalid &&
a._syncFileItemStatus != SyncFileItem::FileNameInvalidOnServer;
case IsCurrentUserFileActivityRole:
return a._isCurrentUserFileActivity;
case ThumbnailRole: {
@ -656,15 +658,20 @@ void ActivityListModel::slotTriggerDefaultAction(const int activityIndex)
} else if (activity._syncFileItemStatus == SyncFileItem::FileNameClash) {
triggerCaseClashAction(activity);
return;
} else if (activity._syncFileItemStatus == SyncFileItem::FileNameInvalid) {
} else if (activity._syncFileItemStatus == SyncFileItem::FileNameInvalid
|| activity._syncFileItemStatus == SyncFileItem::FileNameInvalidOnServer) {
if (!_currentInvalidFilenameDialog.isNull()) {
_currentInvalidFilenameDialog->close();
}
auto folder = FolderMan::instance()->folder(activity._folder);
const auto folderDir = QDir(folder->path());
const auto fileLocation = activity._syncFileItemStatus == SyncFileItem::FileNameInvalidOnServer
? InvalidFilenameDialog::FileLocation::NewLocalFile
: InvalidFilenameDialog::FileLocation::Default;
_currentInvalidFilenameDialog = new InvalidFilenameDialog(_accountState->account(), folder,
folderDir.filePath(activity._file));
folderDir.filePath(activity._file), fileLocation);
connect(_currentInvalidFilenameDialog, &InvalidFilenameDialog::accepted, folder, [folder]() {
folder->scheduleThisFolderSoon();
});

View file

@ -342,6 +342,11 @@ QString AbstractNetworkJob::errorStringParsingBody(QByteArray *body)
return base;
}
QString AbstractNetworkJob::errorStringParsingBodyException(const QByteArray &body) const
{
return extractException(body);
}
AbstractNetworkJob::~AbstractNetworkJob()
{
setReply(nullptr);
@ -423,6 +428,23 @@ QString extractErrorMessage(const QByteArray &errorResponse)
return exception;
}
QString extractException(const QByteArray &errorResponse)
{
QXmlStreamReader reader(errorResponse);
reader.readNextStartElement();
if (reader.name() != QLatin1String("error")) {
return {};
}
while (!reader.atEnd() && !reader.hasError()) {
reader.readNextStartElement();
if (reader.name() == QLatin1String("exception")) {
return reader.readElementText();
}
}
return {};
}
QString errorMessage(const QString &baseError, const QByteArray &body)
{
QString msg = baseError;

View file

@ -91,6 +91,18 @@ public:
*/
QString errorStringParsingBody(QByteArray *body = nullptr);
/** Like errorString, but also checking the reply body for information.
*
* Specifically, sometimes xml bodies have extra error information.
* This function reads the body of the reply and parses out the
* error information, if possible.
*
* \a body is optinally filled with the reply body.
*
* Warning: Needs to call reply()->readAll().
*/
[[nodiscard]] QString errorStringParsingBodyException(const QByteArray &body) const;
/** Make a new request */
void retry();
@ -233,6 +245,16 @@ private:
*/
QString OWNCLOUDSYNC_EXPORT extractErrorMessage(const QByteArray &errorResponse);
/** Gets the SabreDAV-style exception from an error response.
*
* This assumes the response is XML with a 'exception' tag that has a
* 'exception' tag that contains the data to extract.
*
* Returns a null string if no message was found.
*/
[[nodiscard]] QString OWNCLOUDSYNC_EXPORT extractException(const QByteArray &errorResponse);
/** Builds a error message based on the error and the reply body. */
QString OWNCLOUDSYNC_EXPORT errorMessage(const QString &baseError, const QByteArray &body);

View file

@ -733,6 +733,7 @@ void BulkPropagatorJob::handleJobDoneErrors(SyncFileItemPtr item,
case SyncFileItem::FileIgnored:
case SyncFileItem::FileLocked:
case SyncFileItem::FileNameInvalid:
case SyncFileItem::FileNameInvalidOnServer:
case SyncFileItem::FileNameClash:
case SyncFileItem::NoStatus:
case SyncFileItem::NormalError:

View file

@ -274,6 +274,7 @@ void PropagateItemJob::done(const SyncFileItem::Status statusArg, const QString
case SyncFileItem::BlacklistedError:
case SyncFileItem::FileLocked:
case SyncFileItem::FileNameInvalid:
case SyncFileItem::FileNameInvalidOnServer:
case SyncFileItem::FileNameClash:
// nothing
break;
@ -1507,6 +1508,7 @@ void PropagateRootDirectory::slotSubJobsFinished(SyncFileItem::Status status)
case SyncFileItem::FileLocked:
case SyncFileItem::Restoration:
case SyncFileItem::FileNameInvalid:
case SyncFileItem::FileNameInvalidOnServer:
case SyncFileItem::DetailError:
case SyncFileItem::Success:
break;

View file

@ -101,6 +101,7 @@ bool Progress::isWarningKind(SyncFileItem::Status kind)
|| kind == SyncFileItem::Conflict || kind == SyncFileItem::Restoration
|| kind == SyncFileItem::DetailError || kind == SyncFileItem::BlacklistedError
|| kind == SyncFileItem::FileLocked || kind == SyncFileItem::FileNameInvalid
|| kind == SyncFileItem::FileNameInvalidOnServer
|| kind == SyncFileItem::FileNameClash;
}

View file

@ -694,6 +694,13 @@ void PropagateUploadFileCommon::commonErrorHandling(AbstractNetworkJob *job)
status = SyncFileItem::DetailError;
errorString = tr("Upload of %1 exceeds the quota for the folder").arg(Utility::octetsToString(_fileToUpload._size));
emit propagator()->insufficientRemoteStorage();
} else if (_item->_httpErrorCode == 400) {
const auto exception = job->errorStringParsingBodyException(replyContent);
if (exception.endsWith(QStringLiteral("\\InvalidPath"))) {
errorString = tr("Unable to upload an item with invalid characters");
status = SyncFileItem::FileNameInvalidOnServer;
}
}
abortWithError(status, errorString);

View file

@ -72,6 +72,11 @@ public:
*/
FileNameInvalid,
/**
* The filename contains invalid characters and can not be uploaded to the server
*/
FileNameInvalidOnServer,
/**
* There is a file name clash (e.g. attempting to download test.txt when TEST.TXT already exists
* on a platform where the filesystem is case-insensitive

View file

@ -141,7 +141,7 @@ void SyncResult::processCompletedItem(const SyncFileItemPtr &item)
if (!_firstItemError) {
_firstItemError = item;
}
} else if (item->_status == SyncFileItem::Conflict || item->_status == SyncFileItem::FileNameInvalid || item->_status == SyncFileItem::FileNameClash) {
} else if (item->_status == SyncFileItem::Conflict || item->_status == SyncFileItem::FileNameInvalid || item->_status == SyncFileItem::FileNameInvalidOnServer || item->_status == SyncFileItem::FileNameClash) {
if (item->_instruction == CSYNC_INSTRUCTION_CONFLICT) {
_numNewConflictItems++;
if (!_firstNewConflictItem) {