diff options
Diffstat (limited to 'src/corelib/io/qurl.cpp')
-rw-r--r-- | src/corelib/io/qurl.cpp | 270 |
1 files changed, 152 insertions, 118 deletions
diff --git a/src/corelib/io/qurl.cpp b/src/corelib/io/qurl.cpp index f2e1f9bbc7..f8a8bd18e3 100644 --- a/src/corelib/io/qurl.cpp +++ b/src/corelib/io/qurl.cpp @@ -225,9 +225,14 @@ \value RemoveQuery The query part of the URL (following a '?' character) is removed. \value RemoveFragment + \value RemoveFilename The filename (i.e. everything after the last '/' in the path) is removed. + The trailing '/' is kept, unless StripTrailingSlash is set. + Only valid if RemovePath is not set. \value PreferLocalFile If the URL is a local file according to isLocalFile() and contains no query or fragment, a local file path is returned. \value StripTrailingSlash The trailing slash is removed if one is present. + \value NormalizePathSegments Modifies the path to remove redundant directory separators, + and to resolve "."s and ".."s (as far as possible). 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, @@ -321,6 +326,7 @@ #endif QT_BEGIN_NAMESPACE +extern QString qt_normalizePathSegments(const QString &name, bool allowUncPaths); // qdir.cpp inline static bool isHex(char c) { @@ -368,6 +374,7 @@ public: InvalidRegNameError = Host << 8, InvalidIPv4AddressError, InvalidIPv6AddressError, + InvalidCharacterInIPv6Error, InvalidIPvFutureError, HostMissingEndBracket, @@ -418,7 +425,7 @@ public: void appendHost(QString &appendTo, QUrl::FormattingOptions options) const; void appendPath(QString &appendTo, QUrl::FormattingOptions options, Section appendingTo) const; void appendQuery(QString &appendTo, QUrl::FormattingOptions options, Section appendingTo) const; - void appendFragment(QString &appendTo, QUrl::FormattingOptions options) const; + void appendFragment(QString &appendTo, QUrl::FormattingOptions options, Section appendingTo) const; // the "end" parameters are like STL iterators: they point to one past the last valid element bool setScheme(const QString &value, int len, bool doSetError); @@ -799,16 +806,32 @@ inline void QUrlPrivate::appendPassword(QString &appendTo, QUrl::FormattingOptio inline void QUrlPrivate::appendPath(QString &appendTo, QUrl::FormattingOptions options, Section appendingTo) const { + QString thePath = path; + if (options & QUrl::NormalizePathSegments) { + thePath = qt_normalizePathSegments(path, false); + } + if (options & QUrl::RemoveFilename) { + const int slash = path.lastIndexOf(QLatin1Char('/')); + if (slash == -1) + return; + thePath = path.left(slash+1); + } + // check if we need to remove trailing slashes + if (options & QUrl::StripTrailingSlash) { + while (thePath.length() > 1 && thePath.endsWith(QLatin1Char('/'))) + thePath.chop(1); + } + if (appendingTo != Path && !(options & QUrl::EncodeDelimiters)) { - if (!qt_urlRecode(appendTo, path.constData(), path.constEnd(), options, decodedPathInUrlActions)) - appendTo += path; + if (!qt_urlRecode(appendTo, thePath.constData(), thePath.constEnd(), options, decodedPathInUrlActions)) + appendTo += thePath; } else { - appendToUser(appendTo, path, options, encodedPathActions, decodedPathInIsolationActions); + appendToUser(appendTo, thePath, options, encodedPathActions, decodedPathInIsolationActions); } } -inline void QUrlPrivate::appendFragment(QString &appendTo, QUrl::FormattingOptions options) const +inline void QUrlPrivate::appendFragment(QString &appendTo, QUrl::FormattingOptions options, Section appendingTo) const { appendToUser(appendTo, fragment, options, encodedFragmentActions, decodedFragmentInIsolationActions); } @@ -1059,7 +1082,7 @@ inline void QUrlPrivate::appendHost(QString &appendTo, QUrl::FormattingOptions o // this is either an IPv4Address or a reg-name // if it is a reg-name, it is already stored in Unicode form if (options == QUrl::EncodeUnicode) - appendTo += qt_ACE_do(host, ToAceOnly); + appendTo += qt_ACE_do(host, ToAceOnly, AllowLeadingDot); else appendTo += host; } @@ -1067,7 +1090,7 @@ inline void QUrlPrivate::appendHost(QString &appendTo, QUrl::FormattingOptions o // the whole IPvFuture is passed and parsed here, including brackets; // returns null if the parsing was successful, or the QChar of the first failure -static const QChar *parseIpFuture(QString &host, const QChar *begin, const QChar *end) +static const QChar *parseIpFuture(QString &host, const QChar *begin, const QChar *end, QUrl::ParsingMode mode) { // IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" ) static const char acceptable[] = @@ -1076,19 +1099,25 @@ static const QChar *parseIpFuture(QString &host, const QChar *begin, const QChar "-._~"; // unreserved // the brackets and the "v" have been checked + const QChar *const origBegin = begin; if (begin[3].unicode() != '.') return &begin[3]; - if ((begin[2].unicode() >= 'A' && begin[2].unicode() >= 'F') || + if ((begin[2].unicode() >= 'A' && begin[2].unicode() <= 'F') || (begin[2].unicode() >= 'a' && begin[2].unicode() <= 'f') || (begin[2].unicode() >= '0' && begin[2].unicode() <= '9')) { // this is so unlikely that we'll just go down the slow path // decode the whole string, skipping the "[vH." and "]" which we already know to be there host += QString::fromRawData(begin, 4); + + // uppercase the version, if necessary + if (begin[2].unicode() >= 'a') + host[host.length() - 2] = begin[2].unicode() - 0x20; + begin += 4; --end; QString decoded; - if (qt_urlRecode(decoded, begin, end, QUrl::FullyEncoded, 0)) { + if (mode == QUrl::TolerantMode && qt_urlRecode(decoded, begin, end, QUrl::FullyDecoded, 0)) { begin = decoded.constBegin(); end = decoded.constEnd(); } @@ -1103,37 +1132,44 @@ static const QChar *parseIpFuture(QString &host, const QChar *begin, const QChar else if (begin->unicode() < 0x80 && strchr(acceptable, begin->unicode()) != 0) host += *begin; else - return begin; + return decoded.isEmpty() ? begin : &origBegin[2]; } host += QLatin1Char(']'); return 0; } - return &begin[2]; + return &origBegin[2]; } // ONLY the IPv6 address is parsed here, WITHOUT the brackets -static bool parseIp6(QString &host, const QChar *begin, const QChar *end) +static const QChar *parseIp6(QString &host, const QChar *begin, const QChar *end, QUrl::ParsingMode mode) { QIPAddressUtils::IPv6Address address; - if (!QIPAddressUtils::parseIp6(address, begin, end)) { + const QChar *ret = QIPAddressUtils::parseIp6(address, begin, end); + if (ret) { + // this struct is kept in automatic storage because it's only 4 bytes + const ushort decodeColon[] = { decode(':'), 0 }; + // IPv6 failed parsing, check if it was a percent-encoded character in // the middle and try again QString decoded; - if (!qt_urlRecode(decoded, begin, end, QUrl::FullyEncoded, 0)) { - // no transformation, nothing to re-parse - return false; + if (mode == QUrl::TolerantMode && qt_urlRecode(decoded, begin, end, 0, decodeColon)) { + // recurse + // if the parsing fails again, the qt_urlRecode above will return 0 + ret = parseIp6(host, decoded.constBegin(), decoded.constEnd(), mode); + + // we can't return ret, otherwise it would be dangling + return ret ? end : 0; } - // recurse - // if the parsing fails again, the qt_urlRecode above will return 0 - return parseIp6(host, decoded.constBegin(), decoded.constEnd()); + // no transformation, nothing to re-parse + return ret; } host.reserve(host.size() + (end - begin)); host += QLatin1Char('['); QIPAddressUtils::toString(host, address); host += QLatin1Char(']'); - return true; + return 0; } inline bool QUrlPrivate::setHost(const QString &value, int from, int iend, QUrl::ParsingMode mode) @@ -1157,17 +1193,22 @@ inline bool QUrlPrivate::setHost(const QString &value, int from, int iend, QUrl: } if (len > 5 && begin[1].unicode() == 'v') { - const QChar *c = parseIpFuture(host, begin, end); + const QChar *c = parseIpFuture(host, begin, end, mode); if (c) setError(InvalidIPvFutureError, value, c - value.constData()); return !c; + } else if (begin[1].unicode() == 'v') { + setError(InvalidIPvFutureError, value, from); } - if (parseIp6(host, begin + 1, end - 1)) + const QChar *c = parseIp6(host, begin + 1, end - 1, mode); + if (!c) return true; - setError(begin[1].unicode() == 'v' ? InvalidIPvFutureError : InvalidIPv6AddressError, - value, from); + if (c == end - 1) + setError(InvalidIPv6AddressError, value, from); + else + setError(InvalidCharacterInIPv6Error, value, c - value.constData()); return false; } @@ -1194,7 +1235,7 @@ inline bool QUrlPrivate::setHost(const QString &value, int from, int iend, QUrl: // check for percent-encoding first QString s; - if (mode == QUrl::TolerantMode && qt_urlRecode(s, begin, end, QUrl::DecodeReserved, 0)) { + if (mode == QUrl::TolerantMode && qt_urlRecode(s, begin, end, 0, 0)) { // something was decoded // anything encoded left? int pos = s.indexOf(QChar(0x25)); // '%' @@ -1207,7 +1248,7 @@ inline bool QUrlPrivate::setHost(const QString &value, int from, int iend, QUrl: return setHost(s, 0, s.length(), QUrl::StrictMode); } - s = qt_ACE_do(QString::fromRawData(begin, len), NormalizeAce); + s = qt_ACE_do(QString::fromRawData(begin, len), NormalizeAce, ForbidLeadingDot); if (s.isEmpty()) { setError(InvalidRegNameError, value); return false; @@ -1583,87 +1624,6 @@ inline void QUrlPrivate::validate() const } } } - -inline const QByteArray &QUrlPrivate::normalized() const -{ - if (QURL_HASFLAG(stateFlags, QUrlPrivate::Normalized)) - return encodedNormalized; - - QUrlPrivate *that = const_cast<QUrlPrivate *>(this); - QURL_SETFLAG(that->stateFlags, QUrlPrivate::Normalized); - - QUrlPrivate tmp = *this; - tmp.scheme = tmp.scheme.toLower(); - tmp.host = tmp.canonicalHost(); - - // ensure the encoded and normalized parts of the URL - tmp.ensureEncodedParts(); - if (tmp.encodedUserName.contains('%')) - q_normalizePercentEncoding(&tmp.encodedUserName, userNameExcludeChars); - if (tmp.encodedPassword.contains('%')) - q_normalizePercentEncoding(&tmp.encodedPassword, passwordExcludeChars); - if (tmp.encodedFragment.contains('%')) - q_normalizePercentEncoding(&tmp.encodedFragment, fragmentExcludeChars); - - if (tmp.encodedPath.contains('%')) { - // the path is a bit special: - // the slashes shouldn't be encoded or decoded. - // They should remain exactly like they are right now - // - // treat the path as a slash-separated sequence of pchar - QByteArray result; - result.reserve(tmp.encodedPath.length()); - if (tmp.encodedPath.startsWith('/')) - result.append('/'); - - const char *data = tmp.encodedPath.constData(); - int lastSlash = 0; - int nextSlash; - do { - ++lastSlash; - nextSlash = tmp.encodedPath.indexOf('/', lastSlash); - int len; - if (nextSlash == -1) - len = tmp.encodedPath.length() - lastSlash; - else - len = nextSlash - lastSlash; - - if (memchr(data + lastSlash, '%', len)) { - // there's at least one percent before the next slash - QByteArray block = QByteArray(data + lastSlash, len); - q_normalizePercentEncoding(&block, pathExcludeChars); - result.append(block); - } else { - // no percents in this path segment, append wholesale - result.append(data + lastSlash, len); - } - - // append the slash too, if it's there - if (nextSlash != -1) - result.append('/'); - - lastSlash = nextSlash; - } while (lastSlash != -1); - - tmp.encodedPath = result; - } - - if (!tmp.scheme.isEmpty()) // relative test - removeDotsFromPath(&tmp.encodedPath); - - int qLen = tmp.query.length(); - for (int i = 0; i < qLen; i++) { - if (qLen - i > 2 && tmp.query.at(i) == '%') { - ++i; - tmp.query[i] = qToLower(tmp.query.at(i)); - ++i; - tmp.query[i] = qToLower(tmp.query.at(i)); - } - } - encodedNormalized = tmp.toEncoded(); - - return encodedNormalized; -} #endif /*! @@ -2458,6 +2418,36 @@ QString QUrl::path(ComponentFormattingOptions options) const */ /*! + \since 5.2 + + Returns the name of the file, excluding the directory path. + + Note that, if this QUrl object is given a path ending in a slash, the name of the file is considered empty. + + If the path doesn't contain any slash, it is fully returned as the fileName. + + Example: + + \snippet code/src_corelib_io_qurl.cpp 7 + + The \a options argument controls how to format the file name component. All + values produce an unambiguous result. With QUrl::FullyDecoded, all + percent-encoded sequences are decoded; otherwise, the returned value may + contain some percent-encoded sequences for some control sequences not + representable in decoded form in QString. + + \sa path() +*/ +QString QUrl::fileName(ComponentFormattingOptions options) const +{ + const QString ourPath = path(options); + const int slash = ourPath.lastIndexOf(QLatin1Char('/')); + if (slash == -1) + return ourPath; + return ourPath.mid(slash + 1); +} + +/*! \since 4.2 Returns true if this URL contains a Query (i.e., if ? was seen on it). @@ -2903,7 +2893,7 @@ QString QUrl::fragment(ComponentFormattingOptions options) const if (!d) return QString(); QString result; - d->appendFragment(result, options); + d->appendFragment(result, options, QUrlPrivate::Fragment); if (d->hasFragment() && result.isNull()) result.detach(); return result; @@ -2976,7 +2966,7 @@ QString QUrl::topLevelDomain(ComponentFormattingOptions options) const { QString tld = qTopLevelDomain(host()); if (options & EncodeUnicode) { - return qt_ACE_do(tld, ToAceOnly); + return qt_ACE_do(tld, ToAceOnly, AllowLeadingDot); } return tld; } @@ -3148,12 +3138,8 @@ QString QUrl::toString(FormattingOptions options) const url += QLatin1String("//"); } - if (!(options & QUrl::RemovePath)) { + if (!(options & QUrl::RemovePath)) d->appendPath(url, options, QUrlPrivate::FullUrl); - // check if we need to remove trailing slashes - if ((options & StripTrailingSlash) && !d->path.isEmpty() && d->path != QLatin1String("/") && url.endsWith(QLatin1Char('/'))) - url.chop(1); - } if (!(options & QUrl::RemoveQuery) && d->hasQuery()) { url += QLatin1Char('?'); @@ -3161,7 +3147,7 @@ QString QUrl::toString(FormattingOptions options) const } if (!(options & QUrl::RemoveFragment) && d->hasFragment()) { url += QLatin1Char('#'); - d->appendFragment(url, options); + d->appendFragment(url, options, QUrlPrivate::FullUrl); } return url; @@ -3188,6 +3174,52 @@ QString QUrl::toDisplayString(FormattingOptions options) const } /*! + \since 5.2 + + Returns an adjusted version of the URL. + The output can be customized by passing flags with \a options. + + The encoding options from QUrl::ComponentFormattingOption don't make + much sense for this method, nor does QUrl::PreferLocalFile. + + This is always equivalent to QUrl(url.toString(options)). + + \sa FormattingOptions, toEncoded(), toString() +*/ +QUrl QUrl::adjusted(QUrl::FormattingOptions options) const +{ + if (!isValid()) { + // also catches isEmpty() + return QUrl(); + } + QUrl that = *this; + if (options & RemoveScheme) + that.setScheme(QString()); + if ((options & RemoveAuthority) == RemoveAuthority) { + that.setAuthority(QString()); + } else { + if ((options & RemoveUserInfo) == RemoveUserInfo) + that.setUserInfo(QString()); + else if (options & RemovePassword) + that.setPassword(QString()); + if (options & RemovePort) + that.setPort(-1); + } + if (options & RemoveQuery) + that.setQuery(QString()); + if (options & RemoveFragment) + that.setFragment(QString()); + if (options & RemovePath) { + that.setPath(QString()); + } else if (options & (StripTrailingSlash | RemoveFilename | NormalizePathSegments)) { + QString path; + d->appendPath(path, options, QUrlPrivate::Path); + that.setPath(path); + } + return that; +} + +/*! Returns the encoded representation of the URL if it's valid; otherwise an empty QByteArray is returned. The output can be customized by passing flags with \a options. @@ -3296,7 +3328,7 @@ QString QUrl::fromEncodedComponent_helper(const QByteArray &ba) */ QString QUrl::fromAce(const QByteArray &domain) { - return qt_ACE_do(QString::fromLatin1(domain), NormalizeAce); + return qt_ACE_do(QString::fromLatin1(domain), NormalizeAce, ForbidLeadingDot /*FIXME: make configurable*/); } /*! @@ -3317,7 +3349,7 @@ QString QUrl::fromAce(const QByteArray &domain) */ QByteArray QUrl::toAce(const QString &domain) { - QString result = qt_ACE_do(domain, ToAceOnly); + QString result = qt_ACE_do(domain, ToAceOnly, ForbidLeadingDot /*FIXME: make configurable*/); return result.toLatin1(); } @@ -3668,8 +3700,10 @@ static QString errorMessage(QUrlPrivate::ErrorCode errorCode, const QString &err return QString(); // doesn't happen yet case QUrlPrivate::InvalidIPv6AddressError: return QStringLiteral("Invalid IPv6 address"); + case QUrlPrivate::InvalidCharacterInIPv6Error: + return QStringLiteral("Invalid IPv6 address (character '%1' not permitted)").arg(c); case QUrlPrivate::InvalidIPvFutureError: - return QStringLiteral("Invalid IPvFuture address"); + return QStringLiteral("Invalid IPvFuture address (character '%1' not permitted)").arg(c); case QUrlPrivate::HostMissingEndBracket: return QStringLiteral("Expected ']' to match '[' in hostname"); |