summaryrefslogtreecommitdiffstats
path: root/src/corelib/io
diff options
context:
space:
mode:
Diffstat (limited to 'src/corelib/io')
-rw-r--r--src/corelib/io/qdir.cpp93
-rw-r--r--src/corelib/io/qdir_p.h12
-rw-r--r--src/corelib/io/qurl.cpp8
3 files changed, 89 insertions, 24 deletions
diff --git a/src/corelib/io/qdir.cpp b/src/corelib/io/qdir.cpp
index 3007ffb958..f7778943c9 100644
--- a/src/corelib/io/qdir.cpp
+++ b/src/corelib/io/qdir.cpp
@@ -748,6 +748,21 @@ static int drivePrefixLength(const QString &path)
}
#endif // Q_OS_WIN
+static bool treatAsAbsolute(const QString &path)
+{
+ // ### Qt 6: be consistent about absolute paths
+
+ // QFileInfo will use the right FS-engine for virtual file-systems
+ // (e.g. resource paths). Unfortunately, for real file-systems, it relies
+ // on QFileSystemEntry's isRelative(), which is flawed on MS-Win, ignoring
+ // its (correct) isAbsolute(). So only use that isAbsolute() unless there's
+ // a colon in the path.
+ // FIXME: relies on virtual file-systems having colons in their prefixes.
+ // The case of an MS-absolute C:/... path happens to work either way.
+ return (path.contains(QLatin1Char(':')) && QFileInfo(path).isAbsolute())
+ || QFileSystemEntry(path).isAbsolute();
+}
+
/*!
Returns the path name of a file in the directory. Does \e not
check if the file actually exists in the directory; but see
@@ -759,13 +774,10 @@ static int drivePrefixLength(const QString &path)
*/
QString QDir::filePath(const QString &fileName) const
{
- const QDirPrivate* d = d_ptr.constData();
- // Mistrust our own isAbsolutePath() for real files; Q_OS_WIN needs a drive.
- if (fileName.startsWith(QLatin1Char(':')) // i.e. resource path
- ? isAbsolutePath(fileName) : QFileSystemEntry(fileName).isAbsolute()) {
+ if (treatAsAbsolute(fileName))
return fileName;
- }
+ const QDirPrivate* d = d_ptr.constData();
QString ret = d->dirEntry.filePath();
if (fileName.isEmpty())
return ret;
@@ -793,13 +805,10 @@ QString QDir::filePath(const QString &fileName) const
*/
QString QDir::absoluteFilePath(const QString &fileName) const
{
- const QDirPrivate* d = d_ptr.constData();
- // Mistrust our own isAbsolutePath() for real files; Q_OS_WIN needs a drive.
- if (fileName.startsWith(QLatin1Char(':')) // i.e. resource path
- ? isAbsolutePath(fileName) : QFileSystemEntry(fileName).isAbsolute()) {
+ if (treatAsAbsolute(fileName))
return fileName;
- }
+ const QDirPrivate* d = d_ptr.constData();
d->resolveAbsoluteEntry();
const QString absoluteDirPath = d->absoluteDirEntry.filePath();
if (fileName.isEmpty())
@@ -2160,9 +2169,10 @@ bool QDir::match(const QString &filter, const QString &fileName)
This method is shared with QUrl, so it doesn't deal with QDir::separator(),
nor does it remove the trailing slash, if any.
*/
-Q_AUTOTEST_EXPORT QString qt_normalizePathSegments(const QString &name, bool allowUncPaths,
- bool *ok = nullptr)
+QString qt_normalizePathSegments(const QString &name, QDirPrivate::PathNormalizations flags, bool *ok)
{
+ const bool allowUncPaths = QDirPrivate::AllowUncPaths & flags;
+ const bool isRemote = QDirPrivate::RemotePath & flags;
const int len = name.length();
if (ok)
@@ -2184,14 +2194,30 @@ Q_AUTOTEST_EXPORT QString qt_normalizePathSegments(const QString &name, bool all
i -= prefixLength;
// replicate trailing slash (i > 0 checks for emptiness of input string p)
- if (i > 0 && p[i] == '/') {
+ // except for remote paths because there can be /../ or /./ ending
+ if (i > 0 && p[i] == '/' && !isRemote) {
out[--used] = '/';
--i;
}
+ auto isDot = [](const ushort *p, int i) {
+ return i > 1 && p[i - 1] == '.' && p[i - 2] == '/';
+ };
+ auto isDotDot = [](const ushort *p, int i) {
+ return i > 2 && p[i - 1] == '.' && p[i - 2] == '.' && p[i - 3] == '/';
+ };
+
while (i >= 0) {
- // remove trailing slashes
+ // copy trailing slashes for remote urls
if (p[i] == '/') {
+ if (isRemote && !up) {
+ if (isDot(p, i)) {
+ i -= 2;
+ continue;
+ }
+ out[--used] = p[i];
+ }
+
--i;
continue;
}
@@ -2203,10 +2229,17 @@ Q_AUTOTEST_EXPORT QString qt_normalizePathSegments(const QString &name, bool all
}
// detect up dir
- if (i >= 1 && p[i] == '.' && p[i-1] == '.'
- && (i == 1 || (i >= 2 && p[i-2] == '/'))) {
+ if (i >= 1 && p[i] == '.' && p[i-1] == '.' && (i < 2 || p[i - 2] == '/')) {
++up;
- i -= 2;
+ i -= i >= 2 ? 3 : 2;
+
+ if (isRemote) {
+ // moving up should consider empty path segments too (/path//../ -> /path/)
+ while (i > 0 && up && p[i] == '/') {
+ --up;
+ --i;
+ }
+ }
continue;
}
@@ -2216,7 +2249,27 @@ Q_AUTOTEST_EXPORT QString qt_normalizePathSegments(const QString &name, bool all
// skip or copy
while (i >= 0) {
- if (p[i] == '/') { // do not copy slashes
+ if (p[i] == '/') {
+ // copy all slashes as is for remote urls if they are not part of /./ or /../
+ if (isRemote && !up) {
+ while (i > 0 && p[i] == '/' && !isDotDot(p, i)) {
+
+ if (isDot(p, i)) {
+ i -= 2;
+ continue;
+ }
+
+ out[--used] = p[i];
+ --i;
+ }
+
+ // in case of /./, jump over
+ if (isDot(p, i))
+ i -= 2;
+
+ break;
+ }
+
--i;
break;
}
@@ -2237,7 +2290,7 @@ Q_AUTOTEST_EXPORT QString qt_normalizePathSegments(const QString &name, bool all
*ok = prefixLength == 0 || up == 0;
// add remaining '..'
- while (up) {
+ while (up && !isRemote) {
if (used != len && out[used] != '/') // is not empty and there isn't already a '/'
out[--used] = '/';
out[--used] = '.';
@@ -2283,7 +2336,7 @@ static QString qt_cleanPath(const QString &path, bool *ok)
if (dir_separator != QLatin1Char('/'))
name.replace(dir_separator, QLatin1Char('/'));
- QString ret = qt_normalizePathSegments(name, OSSupportsUncPaths, ok);
+ QString ret = qt_normalizePathSegments(name, OSSupportsUncPaths ? QDirPrivate::AllowUncPaths : QDirPrivate::DefaultNormalization, ok);
// Strip away last slash except for root directories
if (ret.length() > 1 && ret.endsWith(QLatin1Char('/'))) {
diff --git a/src/corelib/io/qdir_p.h b/src/corelib/io/qdir_p.h
index 85d915223c..0f3ab7f899 100644
--- a/src/corelib/io/qdir_p.h
+++ b/src/corelib/io/qdir_p.h
@@ -59,6 +59,14 @@ QT_BEGIN_NAMESPACE
class QDirPrivate : public QSharedData
{
public:
+ enum PathNormalization {
+ DefaultNormalization = 0x00,
+ AllowUncPaths = 0x01,
+ RemotePath = 0x02
+ };
+ Q_DECLARE_FLAGS(PathNormalizations, PathNormalization)
+ Q_FLAGS(PathNormalizations)
+
explicit QDirPrivate(const QString &path, const QStringList &nameFilters_ = QStringList(),
QDir::SortFlags sort_ = QDir::SortFlags(QDir::Name | QDir::IgnoreCase),
QDir::Filters filters_ = QDir::AllEntries);
@@ -97,6 +105,10 @@ public:
mutable QFileSystemMetaData metaData;
};
+Q_DECLARE_OPERATORS_FOR_FLAGS(QDirPrivate::PathNormalizations)
+
+Q_AUTOTEST_EXPORT QString qt_normalizePathSegments(const QString &name, QDirPrivate::PathNormalizations flags, bool *ok = nullptr);
+
QT_END_NAMESPACE
#endif
diff --git a/src/corelib/io/qurl.cpp b/src/corelib/io/qurl.cpp
index b324df53b2..6d82981fd6 100644
--- a/src/corelib/io/qurl.cpp
+++ b/src/corelib/io/qurl.cpp
@@ -253,7 +253,8 @@
and contains no query or fragment, a local file path is returned.
\value StripTrailingSlash The trailing slash is removed from the path, if one is present.
\value NormalizePathSegments Modifies the path to remove redundant directory separators,
- and to resolve "."s and ".."s (as far as possible).
+ and to resolve "."s and ".."s (as far as possible). For non-local paths, adjacent
+ slashes are preserved.
Note that the case folding rules in \l{RFC 3491}{Nameprep}, which QUrl
conforms to, require host names to always be converted to lower case,
@@ -419,10 +420,9 @@
#endif
#include "private/qipaddress_p.h"
#include "qurlquery.h"
+#include "private/qdir_p.h"
QT_BEGIN_NAMESPACE
-extern QString qt_normalizePathSegments(const QString &name, bool allowUncPaths,
- bool *ok = nullptr); // qdir.cpp
inline static bool isHex(char c)
{
@@ -930,7 +930,7 @@ inline void QUrlPrivate::appendPath(QString &appendTo, QUrl::FormattingOptions o
{
QString thePath = path;
if (options & QUrl::NormalizePathSegments) {
- thePath = qt_normalizePathSegments(path, false);
+ thePath = qt_normalizePathSegments(path, isLocalFile() ? QDirPrivate::DefaultNormalization : QDirPrivate::RemotePath);
}
QStringRef thePathRef(&thePath);