diff options
Diffstat (limited to 'src/corelib/io')
-rw-r--r-- | src/corelib/io/qdir.cpp | 93 | ||||
-rw-r--r-- | src/corelib/io/qdir_p.h | 12 | ||||
-rw-r--r-- | src/corelib/io/qurl.cpp | 8 |
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); |