File indexing completed on 2024-04-14 14:20:10

0001 /*
0002     Copyright (C) 1999 Torben Weis <weis@kde.org>
0003     Copyright (C) 2005-2006 David Faure <faure@kde.org>
0004 
0005     This library is free software; you can redistribute it and/or
0006     modify it under the terms of the GNU Library General Public
0007     License as published by the Free Software Foundation; either
0008     version 2 of the License, or (at your option) any later version.
0009 
0010     This library is distributed in the hope that it will be useful,
0011     but WITHOUT ANY WARRANTY; without even the implied warranty of
0012     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0013     Library General Public License for more details.
0014 
0015     You should have received a copy of the GNU Library General Public License
0016     along with this library; see the file COPYING.LIB.  If not, write to
0017     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0018     Boston, MA 02110-1301, USA.
0019 */
0020 
0021 /// KDE4 TODO: maybe we should use QUrl::resolved()
0022 
0023 /*
0024  * The currently active RFC for URL/URIs is RFC3986
0025  * Previous (and now deprecated) RFCs are RFC1738 and RFC2396
0026  */
0027 
0028 #include "kurl.h"
0029 #include "kurlmimedata.h"
0030 
0031 #include <stdio.h>
0032 #include <assert.h>
0033 #include <ctype.h>
0034 #include <stdlib.h>
0035 #include <unistd.h>
0036 
0037 #include <QDebug>
0038 #include <QDir>
0039 #include <QStringList>
0040 #include <QRegExp>
0041 #include <QMimeData>
0042 #include <QTextCodec>
0043 
0044 #ifdef DEBUG_KURL
0045 static int kurlDebugArea()
0046 {
0047     static int s_area = KDebug::registerArea("kdecore (KUrl)");
0048     return s_area;
0049 }
0050 #endif
0051 
0052 static QString cleanpath(const QString &_path, bool cleanDirSeparator, bool decodeDots)
0053 {
0054     if (_path.isEmpty()) {
0055         return QString();
0056     }
0057 
0058     if (QFileInfo(_path).isRelative()) {
0059         return _path;    // Don't mangle mailto-style URLs
0060     }
0061 
0062     QString path = _path;
0063 
0064     int len = path.length();
0065 
0066     if (decodeDots) {
0067         static const QLatin1String encodedDot("%2e");
0068         if (path.indexOf(encodedDot, 0, Qt::CaseInsensitive) != -1) {
0069             static const QLatin1String encodedDOT("%2E"); // Uppercase!
0070             path.replace(encodedDot, QString(QLatin1Char('.')));
0071             path.replace(encodedDOT, QString(QLatin1Char('.')));
0072             len = path.length();
0073         }
0074     }
0075 
0076     const bool slash = (len && path[len - 1] == QLatin1Char('/')) ||
0077                        (len > 1 && path[len - 2] == QLatin1Char('/') && path[len - 1] == QLatin1Char('.'));
0078 
0079     // The following code cleans up directory path much like
0080     // QDir::cleanPath() except it can be made to ignore multiple
0081     // directory separators by setting the flag to false.  That fixes
0082     // bug# 15044, mail.altavista.com and other similar brain-dead server
0083     // implementations that do not follow what has been specified in
0084     // RFC 2396!! (dA)
0085     QString result;
0086     int cdUp, orig_pos, pos;
0087 
0088     cdUp = 0;
0089     pos = orig_pos = len;
0090     while (pos && (pos = path.lastIndexOf(QLatin1Char('/'), --pos)) != -1) {
0091         len = orig_pos - pos - 1;
0092         if (len == 2 && path[pos + 1] == QLatin1Char('.') && path[pos + 2] == QLatin1Char('.')) {
0093             cdUp++;
0094         } else {
0095             // Ignore any occurrences of '.'
0096             // This includes entries that simply do not make sense like /..../
0097             if ((len || !cleanDirSeparator) &&
0098                     (len != 1 || path[pos + 1] != QLatin1Char('.'))) {
0099                 if (!cdUp) {
0100                     result.prepend(path.mid(pos, len + 1));
0101                 } else {
0102                     cdUp--;
0103                 }
0104             }
0105         }
0106         orig_pos = pos;
0107     }
0108 
0109 #ifdef Q_OS_WIN // prepend drive letter if exists (js)
0110     if (orig_pos >= 2 && path[0].isLetter() && path[1] == QLatin1Char(':')) {
0111         result.prepend(QString(path[0]) + QLatin1Char(':'));
0112     }
0113 #endif
0114 
0115     if (result.isEmpty()) {
0116         result = QLatin1Char('/');
0117     } else if (slash && result[result.length() - 1] != QLatin1Char('/')) {
0118         result.append(QLatin1Char('/'));
0119     }
0120 
0121     return result;
0122 }
0123 
0124 #ifdef Q_OS_WIN
0125 
0126 // returns true if provided arguments desinate letter+colon or double slash
0127 #define IS_DRIVE_OR_DOUBLESLASH(isletter, char1, char2, colon, slash) \
0128     ((isletter && char2 == colon) || (char1 == slash && char2 == slash))
0129 
0130 // Removes file:/// or file:// or file:/ or / prefix assuming that str
0131 // is (nonempty) Windows absolute path with a drive letter or double slash.
0132 // If there was file protocol, the path is decoded from percent encoding
0133 static QString removeSlashOrFilePrefix(const QString &str)
0134 {
0135     // FIXME this should maybe be replaced with some (faster?)/nicer logic
0136     const int len = str.length();
0137     if (str[0] == QLatin1Char('f')) {
0138         if (len > 10 && str.startsWith(QLatin1String("file:///"))
0139                 && IS_DRIVE_OR_DOUBLESLASH(str[8].isLetter(), str[8], str[9], QLatin1Char(':'), QLatin1Char('/'))) {
0140             return QUrl::fromPercentEncoding(str.toLatin1()).mid(8);
0141         } else if (len > 9 && str.startsWith(QLatin1String("file://"))
0142                    && IS_DRIVE_OR_DOUBLESLASH(str[7].isLetter(), str[7], str[8], QLatin1Char(':'), QLatin1Char('/'))) {
0143             return QUrl::fromPercentEncoding(str.toLatin1()).mid(7);
0144         } else if (len > 8 && str.startsWith(QLatin1String("file:/"))
0145                    && IS_DRIVE_OR_DOUBLESLASH(str[6].isLetter(), str[6], str[7], QLatin1Char(':'), QLatin1Char('/'))) {
0146             return QUrl::fromPercentEncoding(str.toLatin1()).mid(6);
0147         }
0148     }
0149     /* No 'else' here since there can be "f:/" path. */
0150 
0151     /* '/' + drive letter or // */
0152     if (len > 2 && str[0] == QLatin1Char('/')
0153             && IS_DRIVE_OR_DOUBLESLASH(str[1].isLetter(), str[1], str[2], QLatin1Char(':'), QLatin1Char('/'))) {
0154         return str.mid(1);
0155     }
0156     /* drive letter or // */
0157     else if (len >= 2 && IS_DRIVE_OR_DOUBLESLASH(str[0].isLetter(), str[0], str[1], QLatin1Char(':'), QLatin1Char('/'))) {
0158         return str;
0159     }
0160     return QString();
0161 }
0162 #endif
0163 
0164 bool KUrl::isRelativeUrl(const QString &_url)
0165 {
0166     int len = _url.length();
0167     if (!len) {
0168         return true;    // Very short relative URL.
0169     }
0170     const QChar *str = _url.unicode();
0171 
0172     // Absolute URL must start with alpha-character
0173     if (!isalpha(str[0].toLatin1())) {
0174         return true;    // Relative URL
0175     }
0176 
0177     for (int i = 1; i < len; i++) {
0178         char c = str[i].toLatin1(); // Note: non-latin1 chars return 0!
0179         if (c == ':') {
0180             return false;    // Absolute URL
0181         }
0182 
0183         // Protocol part may only contain alpha, digit, + or -
0184         if (!isalpha(c) && !isdigit(c) && (c != '+') && (c != '-')) {
0185             return true;    // Relative URL
0186         }
0187     }
0188     // URL did not contain ':'
0189     return true; // Relative URL
0190 }
0191 
0192 KUrl::List::List(const KUrl &url)
0193 {
0194     append(url);
0195 }
0196 
0197 KUrl::List::List(const QList<KUrl> &list)
0198     : QList<KUrl>(list)
0199 {
0200 }
0201 
0202 KUrl::List::List(const QList<QUrl> &list)
0203 {
0204     Q_FOREACH (const QUrl &url, list) {
0205         append(KUrl(url));
0206     }
0207 }
0208 
0209 KUrl::List::List(const QStringList &list)
0210 {
0211     for (QStringList::ConstIterator it = list.begin();
0212             it != list.end();
0213             ++it) {
0214         append(KUrl(*it));
0215     }
0216 }
0217 
0218 QStringList KUrl::List::toStringList() const
0219 {
0220     return toStringList(KUrl::LeaveTrailingSlash);
0221 }
0222 
0223 QStringList KUrl::List::toStringList(KUrl::AdjustPathOption trailing) const
0224 {
0225     QStringList lst;
0226     for (KUrl::List::ConstIterator it = constBegin();
0227             it != constEnd(); ++it) {
0228         lst.append(it->url(trailing));
0229     }
0230     return lst;
0231 }
0232 
0233 static void populateMimeDataHelper(const KUrl::List &urls,
0234                                    QMimeData *mimeData,
0235                                    const KUrl::MetaDataMap &metaData,
0236                                    KUrl::MimeDataFlags flags)
0237 {
0238     const QString oldText = mimeData->text();
0239     mimeData->setUrls(urls); // set text/uri-list and text/plain
0240 
0241     if ((flags & KUrl::NoTextExport) == 0) {
0242         mimeData->setText(oldText);
0243     }
0244 
0245     if (!metaData.isEmpty()) {
0246         KUrlMimeData::setMetaData(metaData, mimeData);
0247     }
0248 }
0249 
0250 void KUrl::List::populateMimeData(QMimeData *mimeData,
0251                                   const KUrl::MetaDataMap &metaData,
0252                                   MimeDataFlags flags) const
0253 {
0254     populateMimeDataHelper(*this, mimeData, metaData, flags);
0255 }
0256 
0257 void KUrl::List::populateMimeData(const KUrl::List &mostLocalUrls,
0258                                   QMimeData *mimeData,
0259                                   const KUrl::MetaDataMap &metaData,
0260                                   MimeDataFlags flags) const
0261 {
0262     const QString oldText = mimeData->text();
0263     KUrlMimeData::setUrls(*this, mostLocalUrls, mimeData);
0264 
0265     if ((flags & KUrl::NoTextExport) == 0) {
0266         mimeData->setText(oldText);
0267     }
0268 
0269     if (!metaData.isEmpty()) {
0270         KUrlMimeData::setMetaData(metaData, mimeData);
0271     }
0272 }
0273 
0274 bool KUrl::List::canDecode(const QMimeData *mimeData)
0275 {
0276     return mimeData->hasUrls();
0277 }
0278 
0279 QStringList KUrl::List::mimeDataTypes()
0280 {
0281     return KUrlMimeData::mimeDataTypes();
0282 }
0283 
0284 KUrl::List KUrl::List::fromMimeData(const QMimeData *mimeData,
0285                                     DecodeOptions decodeOptions,
0286                                     KUrl::MetaDataMap *metaData)
0287 {
0288     KUrlMimeData::DecodeOptions options = KUrlMimeData::PreferKdeUrls;
0289     if (decodeOptions == PreferLocalUrls) {
0290         options = KUrlMimeData::PreferLocalUrls;
0291     }
0292     return KUrlMimeData::urlsFromMimeData(mimeData, options, metaData);
0293 }
0294 
0295 KUrl::List::operator QVariant() const
0296 {
0297     return QVariant::fromValue(*this);
0298 }
0299 
0300 KUrl::List::operator QList<QUrl>() const
0301 {
0302     QList<QUrl> list;
0303     Q_FOREACH (const KUrl &url, *this) {
0304         list << url;
0305     }
0306     return list;
0307 }
0308 
0309 ///
0310 
0311 KUrl::KUrl()
0312     : QUrl()
0313 {
0314 }
0315 
0316 KUrl::~KUrl()
0317 {
0318 }
0319 
0320 KUrl::KUrl(const QString &str)
0321     : QUrl()
0322 {
0323     if (!str.isEmpty()) {
0324 #ifdef Q_OS_WIN
0325 #ifdef DEBUG_KURL
0326         qDebug() << "KUrl::KUrl ( const QString &str = " << str.toLatin1().data() << " )";
0327 #endif
0328         QString pathToSet;
0329         // when it starts with file:// it's a url and must be valid. we don't care if the
0330         // path exist/ is valid or not
0331         if (!str.startsWith(QLatin1String("file://"))) {
0332             pathToSet = removeSlashOrFilePrefix(QDir::fromNativeSeparators(str));
0333         }
0334         if (!pathToSet.isEmpty()) {
0335             // we have a prefix indicating this is a local URL
0336             // remember the possible query using _setEncodedUrl(), then set up the correct path without query protocol part
0337             int index = pathToSet.lastIndexOf(QLatin1Char('?'));
0338             if (index == -1) {
0339                 setPath(pathToSet);
0340             } else {
0341                 setPath(pathToSet.left(index));
0342                 _setQuery(pathToSet.mid(index + 1));
0343             }
0344             return;
0345         }
0346 #endif
0347         if (str[0] == QLatin1Char('/') || str[0] == QLatin1Char('~')) {
0348             setPath(str);
0349         } else {
0350             _setEncodedUrl(str.toUtf8());
0351         }
0352     }
0353 }
0354 
0355 KUrl::KUrl(const char *str)
0356     : QUrl()
0357 {
0358 #ifdef Q_OS_WIN
0359     // true if @a c is letter
0360 #define IS_LETTER(c) \
0361     ((c >= QLatin1Char('A') && c <= QLatin1Char('Z')) || (c >= QLatin1Char('a') && c <= QLatin1Char('z')))
0362 
0363     // like IS_DRIVE_OR_DOUBLESLASH, but slash is prepended
0364 #define IS_SLASH_AND_DRIVE_OR_DOUBLESLASH_0 \
0365     ( QLatin1Char(str[0]) == QLatin1Char('/') && IS_DRIVE_OR_DOUBLESLASH(IS_LETTER(QLatin1Char(str[1])), QLatin1Char(str[1]), QLatin1Char(str[2]), QLatin1Char(':'), QLatin1Char('/')) )
0366 
0367     // like IS_DRIVE_OR_DOUBLESLASH, with characters == str[0] and str[1]
0368 #define IS_DRIVE_OR_DOUBLESLASH_0 \
0369     ( IS_DRIVE_OR_DOUBLESLASH(IS_LETTER(QLatin1Char(str[0])), QLatin1Char(str[0]), QLatin1Char(str[1]), QLatin1Char(':'), QLatin1Char('/')) )
0370 
0371 #if defined(DEBUG_KURL)
0372     qDebug() << "KUrl::KUrl " << " " << str;
0373 #endif
0374     if (str && str[0] && str[1] && str[2]) {
0375         if (IS_SLASH_AND_DRIVE_OR_DOUBLESLASH_0) {
0376             setPath(QString::fromUtf8(str + 1));
0377         } else if (IS_DRIVE_OR_DOUBLESLASH_0) {
0378             setPath(QString::fromUtf8(str));
0379         }
0380     }
0381 #endif
0382     if (str && str[0]) {
0383         if (str[0] == '/' || str[0] == '~') {
0384             setPath(QString::fromUtf8(str));
0385         } else {
0386             _setEncodedUrl(str);
0387         }
0388     }
0389 }
0390 
0391 KUrl::KUrl(const QByteArray &str)
0392     : QUrl()
0393 {
0394     if (!str.isEmpty()) {
0395 #ifdef Q_OS_WIN
0396 #ifdef DEBUG_KURL
0397         qDebug() << "KUrl::KUrl " << " " << str.data();
0398 #endif
0399         if (IS_SLASH_AND_DRIVE_OR_DOUBLESLASH_0) {
0400             setPath(QString::fromUtf8(str.mid(1)));
0401         } else if (IS_DRIVE_OR_DOUBLESLASH_0) {
0402             setPath(QString::fromUtf8(str));
0403         }
0404 #else
0405         if (str[0] == '/' || str[0] == '~') {
0406             setPath(QString::fromUtf8(str.data()));
0407         }
0408 #endif
0409         else {
0410             _setEncodedUrl(str);
0411         }
0412     }
0413 }
0414 
0415 KUrl::KUrl(const KUrl &_u)
0416     : QUrl(_u)
0417 {
0418 #if defined(Q_OS_WIN) && defined(DEBUG_KURL)
0419     qDebug() << "KUrl::KUrl(KUrl) " << " path " << _u.path() << " toLocalFile " << _u.toLocalFile();
0420 #endif
0421 }
0422 
0423 KUrl::KUrl(const QUrl &u)
0424     : QUrl(u)
0425 {
0426 #if defined(Q_OS_WIN) && defined(DEBUG_KURL)
0427     qDebug() << "KUrl::KUrl(Qurl) " << " path " << u.path() << " toLocalFile " << u.toLocalFile();
0428 #endif
0429 }
0430 
0431 KUrl::KUrl(const KUrl &_u, const QString &_rel_url)
0432     : QUrl()
0433 {
0434 #if defined(Q_OS_WIN) && defined(DEBUG_KURL)
0435     qDebug() << "KUrl::KUrl(KUrl,QString rel_url) " << " path " << _u.path() << " toLocalFile " << _u.toLocalFile();
0436 #endif
0437 #if 0
0438     if (_u.hasSubUrl()) { // Operate on the last suburl, not the first
0439         KUrl::List lst = split(_u);
0440         KUrl u(lst.last(), _rel_url);
0441         lst.erase(--lst.end());
0442         lst.append(u);
0443         *this = join(lst);
0444         return;
0445     }
0446 #endif
0447     QString rUrl = _rel_url;
0448 
0449     // WORKAROUND THE RFC 1606 LOOPHOLE THAT ALLOWS
0450     // http:/index.html AS A VALID SYNTAX FOR RELATIVE
0451     // URLS. ( RFC 2396 section 5.2 item # 3 )
0452     const int len = _u.scheme().length();
0453     if (!_u.host().isEmpty() && !rUrl.isEmpty() &&
0454             rUrl.indexOf(_u.scheme(), 0, Qt::CaseInsensitive) == 0 &&
0455             rUrl[len] == QLatin1Char(':') && (rUrl[len + 1] != QLatin1Char('/') ||
0456                     (rUrl[len + 1] == QLatin1Char('/') && rUrl[len + 2] != QLatin1Char('/')))) {
0457         rUrl.remove(0, rUrl.indexOf(QLatin1Char(':')) + 1);
0458     }
0459 
0460     if (rUrl.isEmpty()) {
0461         *this = _u;
0462     } else if (rUrl[0] == QLatin1Char('#')) {
0463         *this = _u;
0464         QByteArray strRef_encoded = rUrl.mid(1).toLatin1();
0465         if (strRef_encoded.isNull()) {
0466             setFragment(QString::fromLatin1(""));    // we know there was an (empty) html ref, we saw the '#'
0467         } else {
0468             setFragment(QUrl::fromPercentEncoding(strRef_encoded));
0469         }
0470     } else if (isRelativeUrl(rUrl)) {
0471         *this = _u;
0472         setFragment(QString());
0473         setEncodedQuery(QByteArray());
0474         QString strPath = path();
0475         if (rUrl[0] == QLatin1Char('/')) {
0476             if ((rUrl.length() > 1) && (rUrl[1] == QLatin1Char('/'))) {
0477                 setHost(QString());
0478                 setPort(-1);
0479                 // File protocol returns file:/// without host, strip // from rUrl
0480                 if (_u.isLocalFile()) {
0481                     rUrl.remove(0, 2);
0482                 }
0483             }
0484             strPath.clear();
0485         } else if (rUrl[0] != QLatin1Char('?')) {
0486             const int pos = strPath.lastIndexOf(QLatin1Char('/'));
0487             if (pos >= 0) {
0488                 strPath.truncate(pos);
0489             }
0490             strPath += QLatin1Char('/');
0491         } else {
0492             if (strPath.isEmpty()) {
0493                 strPath = QLatin1Char('/');
0494             }
0495         }
0496         setPath(strPath);
0497         //kDebug(kurlDebugArea()) << "url()=" << url() << " rUrl=" << rUrl;
0498         const KUrl tmp(url() + rUrl);
0499         //kDebug(kurlDebugArea()) << "assigning tmp=" << tmp.url();
0500         *this = tmp;
0501         cleanPath(KeepDirSeparators);
0502     } else {
0503         const KUrl tmp(rUrl);
0504         //kDebug(kurlDebugArea()) << "not relative; assigning tmp=" << tmp.url();
0505         *this = tmp;
0506         // Preserve userinfo if applicable.
0507         if (!_u.userInfo().isEmpty() && userInfo().isEmpty()
0508                 && (_u.host() == host()) && (_u.scheme() == scheme())) {
0509             setUserInfo(_u.userInfo());
0510         }
0511         cleanPath(KeepDirSeparators);
0512     }
0513 }
0514 
0515 KUrl &KUrl::operator=(const KUrl &_u)
0516 {
0517     QUrl::operator=(_u);
0518     return *this;
0519 }
0520 
0521 bool KUrl::operator==(const KUrl &_u) const
0522 {
0523     return QUrl::operator==(_u);
0524 }
0525 
0526 bool KUrl::operator==(const QString &_u) const
0527 {
0528     KUrl u(_u);
0529     return (*this == u);
0530 }
0531 
0532 KUrl::operator QVariant() const
0533 {
0534     return QVariant::fromValue(*this);
0535 }
0536 
0537 #ifndef KDELIBS4SUPPORT_NO_DEPRECATED
0538 bool KUrl::cmp(const KUrl &u, bool ignore_trailing) const
0539 {
0540     return equals(u, ignore_trailing ? CompareWithoutTrailingSlash : EqualsOptions());
0541 }
0542 #endif
0543 
0544 bool KUrl::equals(const KUrl &_u, const EqualsOptions &options) const
0545 {
0546     if (!isValid() || !_u.isValid()) {
0547         return false;
0548     }
0549 
0550     if (options & CompareWithoutTrailingSlash || options & CompareWithoutFragment) {
0551         QString path1 = path((options & CompareWithoutTrailingSlash) ? RemoveTrailingSlash : LeaveTrailingSlash);
0552         QString path2 = _u.path((options & CompareWithoutTrailingSlash) ? RemoveTrailingSlash : LeaveTrailingSlash);
0553 
0554         if (options & AllowEmptyPath) {
0555             if (path1 == QLatin1String("/")) {
0556                 path1.clear();
0557             }
0558             if (path2 == QLatin1String("/")) {
0559                 path2.clear();
0560             }
0561         }
0562 
0563 #ifdef Q_OS_WIN
0564         const bool bLocal1 = isLocalFile();
0565         const bool bLocal2 = _u.isLocalFile();
0566         if (!bLocal1 && bLocal2 || bLocal1 && !bLocal2) {
0567             return false;
0568         }
0569         // local files are case insensitive
0570         if (bLocal1 && bLocal2 && 0 != QString::compare(path1, path2, Qt::CaseInsensitive)) {
0571             return false;
0572         }
0573 #endif
0574         if (path1 != path2) {
0575             return false;
0576         }
0577 
0578         if (scheme() == _u.scheme() &&
0579                 authority() == _u.authority() && // user+pass+host+port
0580                 encodedQuery() == _u.encodedQuery() &&
0581                 (fragment() == _u.fragment() || options & CompareWithoutFragment)) {
0582             return true;
0583         }
0584 
0585         return false;
0586     }
0587 
0588     return (*this == _u);
0589 }
0590 
0591 QString KUrl::protocol() const
0592 {
0593     return scheme().toLower();
0594 }
0595 
0596 void KUrl::setProtocol(const QString &proto)
0597 {
0598     setScheme(proto);
0599 }
0600 
0601 QString KUrl::user() const
0602 {
0603     return userName();
0604 }
0605 
0606 void KUrl::setUser(const QString &user)
0607 {
0608     setUserName(user);
0609 }
0610 
0611 bool KUrl::hasUser() const
0612 {
0613     return !userName().isEmpty();
0614 }
0615 
0616 QString KUrl::pass() const
0617 {
0618     return password();
0619 }
0620 
0621 void KUrl::setPass(const QString &pass)
0622 {
0623     setPassword(pass);
0624 }
0625 
0626 bool KUrl::hasPass() const
0627 {
0628     return !password().isEmpty();
0629 }
0630 
0631 bool KUrl::hasHost() const
0632 {
0633     return !host().isEmpty();
0634 }
0635 
0636 bool KUrl::hasPath() const
0637 {
0638     return !path().isEmpty();
0639 }
0640 
0641 void KUrl::setFileName(const QString &_txt)
0642 {
0643     setFragment(QString());
0644     int i = 0;
0645     while (i < _txt.length() && _txt[i] == QLatin1Char('/')) {
0646         ++i;
0647     }
0648     QString tmp = i ? _txt.mid(i) : _txt;
0649 
0650     QString path = this->path();
0651     if (path.isEmpty())
0652 #ifdef Q_OS_WIN
0653         path = isLocalFile() ? QDir::rootPath() : QLatin1String("/");
0654 #else
0655         path = QDir::rootPath();
0656 #endif
0657     else {
0658         int lastSlash = path.lastIndexOf(QLatin1Char('/'));
0659         if (lastSlash == -1) {
0660             path.clear();    // there's only the file name, remove it
0661         } else if (!path.endsWith(QLatin1Char('/'))) {
0662             path.truncate(lastSlash + 1);    // keep the "/"
0663         }
0664     }
0665 
0666     path += tmp;
0667     setPath(path);
0668 
0669     cleanPath();
0670 }
0671 
0672 void KUrl::cleanPath(const CleanPathOption &options)
0673 {
0674     //if (m_iUriMode != URL) return;
0675     const QString newPath = cleanpath(path(), !(options & KeepDirSeparators), false);
0676     if (path() != newPath) {
0677         setPath(newPath);
0678     }
0679 }
0680 
0681 static QString trailingSlash(KUrl::AdjustPathOption trailing, const QString &path)
0682 {
0683     if (trailing == KUrl::LeaveTrailingSlash) {
0684         return path;
0685     }
0686 
0687     QString result = path;
0688 
0689     if (trailing == KUrl::AddTrailingSlash) {
0690         int len = result.length();
0691         if ((len > 0) && (result[ len - 1 ] != QLatin1Char('/'))) {
0692             result += QLatin1Char('/');
0693         }
0694         return result;
0695     } else if (trailing == KUrl::RemoveTrailingSlash) {
0696         if (result == QLatin1String("/")) {
0697             return result;
0698         }
0699         int len = result.length();
0700         while (len > 1 && result[ len - 1 ] == QLatin1Char('/')) {
0701             len--;
0702         }
0703         result.truncate(len);
0704         return result;
0705     } else {
0706         assert(0);
0707         return result;
0708     }
0709 }
0710 
0711 void KUrl::adjustPath(AdjustPathOption trailing)
0712 {
0713     const QString newPath = trailingSlash(trailing, path());
0714     if (path() != newPath) {
0715         setPath(newPath);
0716     }
0717 }
0718 
0719 QString KUrl::encodedPathAndQuery(AdjustPathOption trailing, const EncodedPathAndQueryOptions &options) const
0720 {
0721     QString encodedPath;
0722 #ifdef Q_OS_WIN
0723     // see KUrl::path()
0724     if (isLocalFile()) {
0725         // ### this is probably broken
0726         encodedPath = trailingSlash(trailing, QUrl::toLocalFile());
0727         encodedPath = QString::fromLatin1(QUrl::toPercentEncoding(encodedPath, "!$&'()*+,;=:@/"));
0728     } else {
0729         encodedPath = trailingSlash(trailing, QString::fromLatin1(QUrl::encodedPath()));
0730     }
0731 #else
0732     encodedPath = trailingSlash(trailing, QString::fromLatin1(QUrl::encodedPath().data()));
0733 #endif
0734 
0735     if ((options & AvoidEmptyPath) && encodedPath.isEmpty()) {
0736         encodedPath.append(QLatin1Char('/'));
0737     }
0738 
0739     if (hasQuery()) {
0740         return encodedPath + QLatin1Char('?') + QString::fromLatin1(encodedQuery().data());
0741     } else {
0742         return encodedPath;
0743     }
0744 }
0745 
0746 void KUrl::setEncodedPathAndQuery(const QString &_txt)
0747 {
0748     const int pos = _txt.indexOf(QLatin1Char('?'));
0749     if (pos == -1) {
0750         setPath(QUrl::fromPercentEncoding(_txt.toLatin1()));
0751         setEncodedQuery(QByteArray());
0752     } else {
0753         setPath(QUrl::fromPercentEncoding(_txt.toLatin1().left(pos)));
0754         _setQuery(_txt.right(_txt.length() - pos - 1));
0755     }
0756 }
0757 
0758 QString KUrl::path(AdjustPathOption trailing) const
0759 {
0760     const QString decodedPath = QUrl::path(QUrl::FullyDecoded);
0761 
0762 #ifdef Q_OS_WIN
0763 #ifdef DEBUG_KURL
0764     kWarning() << (isLocalFile() ? "converted to local file - the related call should be converted to toLocalFile()" : "") << QUrl::path();
0765 #endif
0766     return trailingSlash(trailing, isLocalFile() ? QUrl::toLocalFile() : decodedPath);
0767 #else
0768     return trailingSlash(trailing, decodedPath);
0769 #endif
0770 }
0771 
0772 QString KUrl::toLocalFile(AdjustPathOption trailing) const
0773 {
0774     if (hasHost() && isLocalFile()) {
0775         KUrl urlWithoutHost(*this);
0776         urlWithoutHost.setHost(QString());
0777         return trailingSlash(trailing, urlWithoutHost.toLocalFile());
0778     }
0779 
0780     return trailingSlash(trailing, QUrl::toLocalFile());
0781 }
0782 
0783 inline static bool hasSubUrl(const QUrl &url);
0784 
0785 static inline bool isLocalFile(const QUrl &url)
0786 {
0787     if (url.scheme().compare(QLatin1String("file"), Qt::CaseInsensitive) != 0 || hasSubUrl(url)) {
0788         return false;
0789     }
0790 
0791     if (url.host().isEmpty() || (url.host() == QLatin1String("localhost"))) {
0792         return true;
0793     }
0794 
0795     char hostname[ 256 ];
0796     hostname[ 0 ] = '\0';
0797     if (!gethostname(hostname, 255)) {
0798         hostname[sizeof(hostname) - 1] = '\0';
0799     }
0800 
0801     for (char *p = hostname; *p; p++) {
0802         *p = tolower(*p);
0803     }
0804 
0805     return (url.host() == QString::fromLatin1(hostname));
0806 }
0807 
0808 bool KUrl::isLocalFile() const
0809 {
0810     return ::isLocalFile(*this);
0811 }
0812 
0813 void KUrl::setFileEncoding(const QString &encoding)
0814 {
0815     if (!isLocalFile()) {
0816         return;
0817     }
0818 
0819     QString q = query();
0820 
0821     if (!q.isEmpty() && q[0] == QLatin1Char('?')) {
0822         q = q.mid(1);
0823     }
0824 
0825     QStringList args = q.split(QLatin1Char('&'), QString::SkipEmptyParts);
0826     for (QStringList::Iterator it = args.begin();
0827             it != args.end();) {
0828         QString s = QUrl::fromPercentEncoding((*it).toLatin1());
0829         if (s.startsWith(QLatin1String("charset="))) {
0830             it = args.erase(it);
0831         } else {
0832             ++it;
0833         }
0834     }
0835     if (!encoding.isEmpty()) {
0836         args.append(QLatin1String("charset=") + QString::fromLatin1(QUrl::toPercentEncoding(encoding).data()));
0837     }
0838 
0839     if (args.isEmpty()) {
0840         _setQuery(QString());
0841     } else {
0842         _setQuery(args.join(QString(QLatin1Char('&'))));
0843     }
0844 }
0845 
0846 QString KUrl::fileEncoding() const
0847 {
0848     if (!isLocalFile()) {
0849         return QString();
0850     }
0851 
0852     QString q = query();
0853 
0854     if (q.isEmpty()) {
0855         return QString();
0856     }
0857 
0858     if (q[0] == QLatin1Char('?')) {
0859         q = q.mid(1);
0860     }
0861 
0862     const QStringList args = q.split(QLatin1Char('&'), QString::SkipEmptyParts);
0863     for (QStringList::ConstIterator it = args.begin();
0864             it != args.end();
0865             ++it) {
0866         QString s = QUrl::fromPercentEncoding((*it).toLatin1());
0867         if (s.startsWith(QLatin1String("charset="))) {
0868             return s.mid(8);
0869         }
0870     }
0871     return QString();
0872 }
0873 
0874 inline static bool hasSubUrl(const QUrl &url)
0875 {
0876     // The isValid call triggers QUrlPrivate::validate which needs the full encoded url,
0877     // all this takes too much time for isLocalFile()
0878     const QString scheme = url.scheme();
0879     if (scheme.isEmpty() /*|| !isValid()*/) {
0880         return false;
0881     }
0882     const QString ref(url.fragment());
0883     if (ref.isEmpty()) {
0884         return false;
0885     }
0886     switch (ref.at(0).unicode()) {
0887     case 'g':
0888         if (ref.startsWith(QLatin1String("gzip:"))) {
0889             return true;
0890         }
0891         break;
0892     case 'b':
0893         if (ref.startsWith(QLatin1String("bzip:")) || ref.startsWith(QLatin1String("bzip2:"))) {
0894             return true;
0895         }
0896         break;
0897     case 'l':
0898         if (ref.startsWith(QLatin1String("lzma:"))) {
0899             return true;
0900         }
0901         break;
0902     case 'x':
0903         if (ref.startsWith(QLatin1String("xz:"))) {
0904             return true;
0905         }
0906         break;
0907     case 't':
0908         if (ref.startsWith(QLatin1String("tar:"))) {
0909             return true;
0910         }
0911         break;
0912     case 'a':
0913         if (ref.startsWith(QLatin1String("ar:"))) {
0914             return true;
0915         }
0916         break;
0917     case 'z':
0918         if (ref.startsWith(QLatin1String("zip:"))) {
0919             return true;
0920         }
0921         break;
0922     default:
0923         break;
0924     }
0925     if (scheme == QLatin1String("error")) { // anything that starts with error: has suburls
0926         return true;
0927     }
0928     return false;
0929 }
0930 
0931 bool KUrl::hasSubUrl() const
0932 {
0933     return ::hasSubUrl(*this);
0934 }
0935 
0936 QString KUrl::url(AdjustPathOption trailing) const
0937 {
0938     if (QString::compare(scheme(), QLatin1String("mailto"), Qt::CaseInsensitive) == 0) {
0939         // mailto urls should be prettified, see the url183433 testcase.
0940         return prettyUrl(trailing);
0941     }
0942     if (trailing == AddTrailingSlash && !path().endsWith(QLatin1Char('/'))) {
0943         // -1 and 0 are provided by QUrl, but not +1, so that one is a bit tricky.
0944         // To avoid reimplementing toEncoded() all over again, I just use another QUrl
0945         // Let's hope this is fast, or not called often...
0946         QUrl newUrl(*this);
0947         newUrl.setPath(path() + QLatin1Char('/'));
0948         return QString::fromLatin1(newUrl.toEncoded().data());
0949     } else if (trailing == RemoveTrailingSlash) {
0950         const QString cleanedPath = trailingSlash(trailing, path());
0951         if (cleanedPath == QLatin1String("/")) {
0952             if (path() != QLatin1String("/")) {
0953                 QUrl fixedUrl = *this;
0954                 fixedUrl.setPath(cleanedPath);
0955                 return QLatin1String(fixedUrl.toEncoded(None).data());
0956             }
0957             return QLatin1String(toEncoded(None).data());
0958         }
0959     }
0960     return QString::fromLatin1(toEncoded(trailing == RemoveTrailingSlash ? StripTrailingSlash : None).data());
0961 }
0962 
0963 static QString toPrettyPercentEncoding(const QString &input, bool forFragment)
0964 {
0965     QString result;
0966     result.reserve(input.length());
0967     for (int i = 0; i < input.length(); ++i) {
0968         const QChar c = input.at(i);
0969         ushort u = c.unicode();
0970         if (u < 0x20
0971                 || (!forFragment && u == '?') // don't escape '?' in fragments, not needed and wrong (#173101)
0972                 || u == '#' || u == '%'
0973                 || (u == ' ' && (i + 1 == input.length() || input.at(i + 1).unicode() == ' '))) {
0974             static const char hexdigits[] = "0123456789ABCDEF";
0975             result += QLatin1Char('%');
0976             result += QLatin1Char(hexdigits[(u & 0xf0) >> 4]);
0977             result += QLatin1Char(hexdigits[u & 0xf]);
0978         } else {
0979             result += c;
0980         }
0981     }
0982 
0983     return result;
0984 }
0985 
0986 QString KUrl::prettyUrl(AdjustPathOption trailing) const
0987 {
0988     // reconstruct the URL in a "pretty" form
0989     // a "pretty" URL is NOT suitable for data transfer. It's only for showing data to the user.
0990     // however, it must be parseable back to its original state, since
0991     // notably Konqueror displays it in the Location address.
0992 
0993     // A pretty URL is the same as a normal URL, except that:
0994     // - the password is removed
0995     // - the hostname is shown in Unicode (as opposed to ACE/Punycode)
0996     // - the pathname and fragment parts are shown in Unicode (as opposed to %-encoding)
0997     QString result = scheme();
0998     if (!result.isEmpty()) {
0999         if (!authority().isEmpty() || result == QLatin1String("file") || path().isEmpty()) {
1000             result += QLatin1String("://");
1001         } else {
1002             result += QLatin1Char(':');
1003         }
1004     }
1005 
1006     QString tmp = userName();
1007     if (!tmp.isEmpty()) {
1008         result += QString::fromLatin1(QUrl::toPercentEncoding(tmp).data());
1009         result += QLatin1Char('@');
1010     }
1011 
1012     // Check if host is an ipv6 address
1013     tmp = host();
1014     if (tmp.contains(QLatin1Char(':'))) {
1015         result += QLatin1Char('[') + tmp + QLatin1Char(']');
1016     } else {
1017         result += tmp;
1018     }
1019 
1020     if (port() != -1) {
1021         result += QLatin1Char(':');
1022         result += QString::number(port());
1023     }
1024 
1025     tmp = path();
1026 #ifdef Q_OS_WIN
1027     if (isLocalFile()) {
1028         tmp.prepend(QLatin1Char('/'));    // KUrl::path() returns toLocalFile() on windows so we need to add the / back to create a proper url
1029     }
1030 #endif
1031     result += toPrettyPercentEncoding(tmp, false);
1032 
1033     // adjust the trailing slash, if necessary
1034     if (trailing == AddTrailingSlash && !tmp.endsWith(QLatin1Char('/'))) {
1035         result += QLatin1Char('/');
1036     } else if (trailing == RemoveTrailingSlash && tmp.length() > 1 && tmp.endsWith(QLatin1Char('/'))) {
1037         result.chop(1);
1038     }
1039 
1040     if (hasQuery()) {
1041         result += QLatin1Char('?');
1042         result += QString::fromLatin1(encodedQuery().data());
1043     }
1044 
1045     if (hasFragment()) {
1046         result += QLatin1Char('#');
1047         result += toPrettyPercentEncoding(fragment(), true);
1048     }
1049 
1050     return result;
1051 }
1052 
1053 QString KUrl::pathOrUrl(AdjustPathOption trailing) const
1054 {
1055     if (isLocalFile() && fragment().isNull() && encodedQuery().isNull()) {
1056         return toLocalFile(trailing);
1057     } else {
1058         return prettyUrl(trailing);
1059     }
1060 }
1061 
1062 // Used for text/uri-list in the mime data
1063 QString KUrl::toMimeDataString() const // don't fold this into populateMimeData, it's also needed by other code like konqdrag
1064 {
1065     if (isLocalFile()) {
1066 #if 1
1067         return url();
1068 #else
1069         // According to the XDND spec, file:/ URLs for DND must have
1070         // the hostname part. But in really it just breaks many apps,
1071         // so it's disabled for now.
1072         const QString s = url();
1073         if (!s.startsWith(QLatin1String("file://"))) {
1074             char hostname[257];
1075             if (gethostname(hostname, 255) == 0) {
1076                 hostname[256] = '\0';
1077                 return QString("file://") + hostname + s.mid(5);
1078             }
1079         }
1080 #endif
1081     }
1082 
1083     if (hasPass()) {
1084         KUrl safeUrl(*this);
1085         safeUrl.setPassword(QString());
1086         return safeUrl.url();
1087     }
1088     return url();
1089 }
1090 
1091 KUrl::List KUrl::split(const KUrl &_url)
1092 {
1093     QString ref;
1094     bool hasRef;
1095     KUrl::List lst;
1096     KUrl url = _url;
1097 
1098     while (true) {
1099         KUrl u = url;
1100         u.setFragment(QString());
1101         lst.append(u);
1102         if (url.hasSubUrl()) {
1103             url = KUrl(url.fragment());
1104         } else {
1105             ref = url.fragment();
1106             hasRef = url.hasFragment();
1107             break;
1108         }
1109     }
1110 
1111     if (hasRef) {
1112         // Set HTML ref in all URLs.
1113         KUrl::List::Iterator it;
1114         for (it = lst.begin(); it != lst.end(); ++it) {
1115             (*it).setFragment(ref);
1116         }
1117     }
1118 
1119     return lst;
1120 }
1121 
1122 KUrl::List KUrl::split(const QString &_url)
1123 {
1124     return split(KUrl(_url));
1125 }
1126 
1127 KUrl KUrl::join(const KUrl::List &lst)
1128 {
1129     if (lst.isEmpty()) {
1130         return KUrl();
1131     }
1132     KUrl tmp;
1133 
1134     bool first = true;
1135     QListIterator<KUrl> it(lst);
1136     it.toBack();
1137     while (it.hasPrevious()) {
1138         KUrl u(it.previous());
1139         if (!first) {
1140             u.setEncodedFragment(tmp.url().toLatin1() /* TODO double check encoding */);
1141         }
1142         tmp = u;
1143 
1144         first = false;
1145     }
1146 
1147     return tmp;
1148 }
1149 
1150 QString KUrl::fileName(const DirectoryOptions &options) const
1151 {
1152     Q_ASSERT(options != 0);   //Disallow options == false
1153     QString fname;
1154     if (hasSubUrl()) { // If we have a suburl, then return the filename from there
1155         const KUrl::List list = KUrl::split(*this);
1156         return list.last().fileName(options);
1157     }
1158     const QString path = this->path();
1159 
1160     int len = path.length();
1161     if (len == 0) {
1162         return fname;
1163     }
1164 
1165     if (!(options & ObeyTrailingSlash)) {
1166         while (len >= 1 && path[ len - 1 ] == QLatin1Char('/')) {
1167             len--;
1168         }
1169     } else if (path[ len - 1 ] == QLatin1Char('/')) {
1170         return fname;
1171     }
1172 
1173     // Does the path only consist of '/' characters ?
1174     if (len == 1 && path[ 0 ] == QLatin1Char('/')) {
1175         return fname;
1176     }
1177 
1178     // Skip last n slashes
1179     int n = 1;
1180     int i = len;
1181     do {
1182         i = path.lastIndexOf(QLatin1Char('/'), i - 1);
1183     } while (--n && (i > 0));
1184 
1185     // If ( i == -1 ) => the first character is not a '/'
1186     // So it's some URL like file:blah.tgz, return the whole path
1187     if (i == -1) {
1188         if (len == (int)path.length()) {
1189             fname = path;
1190         } else
1191             // Might get here if _strip_trailing_slash is true
1192         {
1193             fname = path.left(len);
1194         }
1195     } else {
1196         fname = path.mid(i + 1, len - i - 1);   // TO CHECK
1197     }
1198     return fname;
1199 }
1200 
1201 void KUrl::addPath(const QString &_txt)
1202 {
1203     if (hasSubUrl()) {
1204         KUrl::List lst = split(*this);
1205         KUrl &u = lst.last();
1206         u.addPath(_txt);
1207         *this = join(lst);
1208         return;
1209     }
1210 
1211     if (_txt.isEmpty()) {
1212         return;
1213     }
1214 
1215     QString strPath = path();
1216     int i = 0;
1217     int len = strPath.length();
1218     // Add the trailing '/' if it is missing
1219     if (_txt[0] != QLatin1Char('/') && (len == 0 || strPath[ len - 1 ] != QLatin1Char('/'))) {
1220         strPath += QLatin1Char('/');
1221     }
1222 
1223     // No double '/' characters
1224     i = 0;
1225     const int _txtlen = _txt.length();
1226     if (strPath.endsWith(QLatin1Char('/'))) {
1227         while ((i < _txtlen) && (_txt[i] == QLatin1Char('/'))) {
1228             ++i;
1229         }
1230     }
1231 
1232     setPath(strPath + _txt.mid(i));
1233     //kDebug(kurlDebugArea())<<"addPath: resultpath="<<path();
1234 }
1235 
1236 QString KUrl::directory(const DirectoryOptions &options) const
1237 {
1238     Q_ASSERT(options != 0);   //Disallow options == false
1239     QString result = path();
1240     if (!(options & ObeyTrailingSlash)) {
1241         result = trailingSlash(RemoveTrailingSlash, result);
1242     }
1243 
1244     if (result.isEmpty() || result == QLatin1String("/")) {
1245         return result;
1246     }
1247 
1248     int i = result.lastIndexOf(QLatin1Char('/'));
1249     // If ( i == -1 ) => the first character is not a '/'
1250     // So it's some URL like file:blah.tgz, with no path
1251     if (i == -1) {
1252         return QString();
1253     }
1254 
1255     if (i == 0) {
1256         return QString(QLatin1Char('/'));
1257     }
1258 
1259 #ifdef Q_OS_WIN
1260     if (i == 2 && result[1] == QLatin1Char(':')) {
1261         return result.left(3);
1262     }
1263 #endif
1264 
1265     if (options & AppendTrailingSlash) {
1266         result = result.left(i + 1);
1267     } else {
1268         result = result.left(i);
1269     }
1270 
1271     return result;
1272 }
1273 
1274 bool KUrl::cd(const QString &_dir)
1275 {
1276     if (_dir.isEmpty() || !isValid()) {
1277         return false;
1278     }
1279 
1280     if (hasSubUrl()) {
1281         KUrl::List lst = split(*this);
1282         KUrl &u = lst.last();
1283         u.cd(_dir);
1284         *this = join(lst);
1285         return true;
1286     }
1287 
1288     // absolute path ?
1289 #ifdef Q_OS_WIN
1290     if (!QFileInfo(_dir).isRelative())
1291 #else
1292     if (_dir[0] == QLatin1Char('/'))
1293 #endif
1294     {
1295         setPath(_dir);
1296         setHTMLRef(QString());
1297         setEncodedQuery(QByteArray());
1298         return true;
1299     }
1300 
1301     // Users home directory on the local disk ?
1302     if (_dir[0] == QLatin1Char('~') && scheme() == QLatin1String("file")) {
1303         QString strPath = QDir::homePath();
1304         strPath += QLatin1Char('/');
1305         strPath += _dir.right(strPath.length() - 1);
1306         setPath(strPath);
1307         setHTMLRef(QString());
1308         setEncodedQuery(QByteArray());
1309         return true;
1310     }
1311 
1312     // relative path
1313     // we always work on the past of the first url.
1314     // Sub URLs are not touched.
1315 
1316     // append '/' if necessary
1317     QString p = path(AddTrailingSlash);
1318     p += _dir;
1319     p = cleanpath(p, true, false);
1320     setPath(p);
1321 
1322     setHTMLRef(QString());
1323     setEncodedQuery(QByteArray());
1324 
1325     return true;
1326 }
1327 
1328 KUrl KUrl::upUrl() const
1329 {
1330     if (!isValid() || isRelative()) {
1331         return KUrl();
1332     }
1333 
1334     if (!encodedQuery().isEmpty()) {
1335         KUrl u(*this);
1336         u.setEncodedQuery(QByteArray());
1337         return u;
1338     }
1339 
1340     if (!hasSubUrl()) {
1341         KUrl u(*this);
1342         u.cd(QLatin1String("../"));
1343         return u;
1344     }
1345 
1346     // We have a subURL.
1347     KUrl::List lst = split(*this);
1348     if (lst.isEmpty()) {
1349         return KUrl();    // Huh?
1350     }
1351     while (true) {
1352         KUrl &u = lst.last();
1353         const QString old = u.path();
1354         u.cd(QLatin1String("../"));
1355         if (u.path() != old) {
1356             break;    // Finished.
1357         }
1358         if (lst.count() == 1) {
1359             break;    // Finished.
1360         }
1361         lst.removeLast();
1362     }
1363     return join(lst);
1364 }
1365 
1366 QString KUrl::htmlRef() const
1367 {
1368     if (!hasSubUrl()) {
1369         return fragment(QUrl::FullyDecoded);
1370     }
1371 
1372     const List lst = split(*this);
1373     return (*lst.begin()).fragment(QUrl::FullyDecoded);
1374 }
1375 
1376 QString KUrl::encodedHtmlRef() const
1377 {
1378     if (!hasSubUrl()) {
1379         return ref();
1380     }
1381 
1382     const List lst = split(*this);
1383     return (*lst.begin()).ref();
1384 }
1385 
1386 void KUrl::setHTMLRef(const QString &_ref)
1387 {
1388     if (!hasSubUrl()) {
1389         setFragment(_ref);
1390         return;
1391     }
1392 
1393     List lst = split(*this);
1394 
1395     (*lst.begin()).setFragment(_ref);
1396 
1397     *this = join(lst);
1398 }
1399 
1400 bool KUrl::hasHTMLRef() const
1401 {
1402     if (!hasSubUrl()) {
1403         return hasRef();
1404     }
1405 
1406     const List lst = split(*this);
1407     return (*lst.begin()).hasRef();
1408 }
1409 
1410 void KUrl::setDirectory(const QString &dir)
1411 {
1412     if (dir.endsWith(QLatin1Char('/'))) {
1413         setPath(dir);
1414     } else {
1415         setPath(dir + QLatin1Char('/'));
1416     }
1417 }
1418 
1419 void KUrl::setQuery(const QString &_txt)
1420 {
1421     if (!_txt.isEmpty() && _txt[0] == QLatin1Char('?')) {
1422         _setQuery(_txt.length() > 1 ? _txt.mid(1) : QString::fromLatin1("") /*empty, not null*/);
1423     } else {
1424         _setQuery(_txt);
1425     }
1426 }
1427 
1428 void KUrl::_setQuery(const QString &query)
1429 {
1430     if (query.isNull()) {
1431         setEncodedQuery(QByteArray());
1432     } else if (query.isEmpty()) {
1433         setEncodedQuery("");
1434     } else {
1435         setEncodedQuery(query.toLatin1());   // already percent-escaped, so toLatin1 is ok
1436     }
1437 }
1438 
1439 QString KUrl::query() const
1440 {
1441     if (!hasQuery()) {
1442         return QString();
1443     }
1444     return QString(QLatin1Char('?')) + QString::fromLatin1(encodedQuery().data());
1445 }
1446 
1447 void KUrl::_setEncodedUrl(const QByteArray &url)
1448 {
1449     setEncodedUrl(url, QUrl::TolerantMode);
1450     if (!isValid()) { // see unit tests referring to N183630/task 183874
1451         setUrl(QString::fromUtf8(url.data()), QUrl::TolerantMode);
1452     }
1453 }
1454 
1455 #ifndef KDELIBS4SUPPORT_NO_DEPRECATED
1456 bool urlcmp(const QString &_url1, const QString &_url2)
1457 {
1458     return QUrl(_url1, QUrl::TolerantMode) == QUrl(_url2, QUrl::TolerantMode);
1459 #if 0
1460     // Both empty ?
1461     if (_url1.isEmpty() && _url2.isEmpty()) {
1462         return true;
1463     }
1464     // Only one empty ?
1465     if (_url1.isEmpty() || _url2.isEmpty()) {
1466         return false;
1467     }
1468 
1469     KUrl::List list1 = KUrl::split(_url1);
1470     KUrl::List list2 = KUrl::split(_url2);
1471 
1472     // Malformed ?
1473     if (list1.isEmpty() || list2.isEmpty()) {
1474         return false;
1475     }
1476 
1477     return (list1 == list2);
1478 #endif
1479 }
1480 #endif
1481 
1482 #ifndef KDELIBS4SUPPORT_NO_DEPRECATED
1483 bool urlcmp(const QString &_url1, const QString &_url2, const KUrl::EqualsOptions &_options)
1484 {
1485     // Both empty ?
1486     if (_url1.isEmpty() && _url2.isEmpty()) {
1487         return true;
1488     }
1489     // Only one empty ?
1490     if (_url1.isEmpty() || _url2.isEmpty()) {
1491         return false;
1492     }
1493 
1494     KUrl u1(_url1);
1495     KUrl u2(_url2);
1496     return u1.equals(u2, _options);
1497 
1498 #if 0 // kde3 code that supported nested urls
1499 
1500     KUrl::List list1 = KUrl::split(_url1);
1501     KUrl::List list2 = KUrl::split(_url2);
1502 
1503     // Malformed ?
1504     if (list1.isEmpty() || list2.isEmpty()) {
1505         return false;
1506     }
1507 
1508     int size = list1.count();
1509     if (list2.count() != size) {
1510         return false;
1511     }
1512 
1513     if (_ignore_ref) {
1514         (*list1.begin()).setRef(QString());
1515         (*list2.begin()).setRef(QString());
1516     }
1517 
1518     KUrl::List::Iterator it1 = list1.begin();
1519     KUrl::List::Iterator it2 = list2.begin();
1520     for (; it1 != list1.end(); ++it1, ++it2)
1521         if (!(*it1).equals(*it2, _ignore_trailing)) {
1522             return false;
1523         }
1524     return true;
1525 #endif
1526 }
1527 #endif
1528 
1529 // static
1530 #ifndef KDELIBS4SUPPORT_NO_DEPRECATED
1531 KUrl KUrl::fromPathOrUrl(const QString &text)
1532 {
1533     KUrl url;
1534     if (!text.isEmpty()) {
1535         if (!QDir::isRelativePath(text) || text[0] == QLatin1Char('~')) {
1536             url.setPath(text);
1537         } else {
1538             url = KUrl(text);
1539         }
1540     }
1541 
1542     return url;
1543 }
1544 #endif
1545 
1546 static QString _relativePath(const QString &base_dir, const QString &path, bool &isParent)
1547 {
1548     QString _base_dir(QDir::cleanPath(base_dir));
1549     QString _path(QDir::cleanPath(path.isEmpty() || QDir::isRelativePath(path) ? _base_dir + QLatin1Char('/') + path : path));
1550 
1551     if (_base_dir.isEmpty()) {
1552         return _path;
1553     }
1554 
1555     if (_base_dir[_base_dir.length() - 1] != QLatin1Char('/')) {
1556         _base_dir.append(QLatin1Char('/'));
1557     }
1558 
1559     const QStringList list1 = _base_dir.split(QLatin1Char('/'), QString::SkipEmptyParts);
1560     const QStringList list2 = _path.split(QLatin1Char('/'), QString::SkipEmptyParts);
1561 
1562     // Find where they meet
1563     int level = 0;
1564     int maxLevel = qMin(list1.count(), list2.count());
1565     while ((level < maxLevel) && (list1[level] == list2[level])) {
1566         level++;
1567     }
1568 
1569     QString result;
1570     // Need to go down out of the first path to the common branch.
1571     for (int i = level; i < list1.count(); i++) {
1572         result.append(QLatin1String("../"));
1573     }
1574 
1575     // Now up up from the common branch to the second path.
1576     for (int i = level; i < list2.count(); i++) {
1577         result.append(list2[i]).append(QLatin1Char('/'));
1578     }
1579 
1580     if ((level < list2.count()) && (path[path.length() - 1] != QLatin1Char('/'))) {
1581         result.truncate(result.length() - 1);
1582     }
1583 
1584     isParent = (level == list1.count());
1585 
1586     return result;
1587 }
1588 
1589 QString KUrl::relativePath(const QString &base_dir, const QString &path, bool *isParent)
1590 {
1591     bool parent = false;
1592     QString result = _relativePath(base_dir, path, parent);
1593     if (parent) {
1594         result.prepend(QLatin1String("./"));
1595     }
1596 
1597     if (isParent) {
1598         *isParent = parent;
1599     }
1600 
1601     return result;
1602 }
1603 
1604 QString KUrl::relativeUrl(const KUrl &base_url, const KUrl &url)
1605 {
1606     if ((url.protocol() != base_url.protocol()) ||
1607             (url.host() != base_url.host()) ||
1608             (url.port() && url.port() != base_url.port()) ||
1609             (url.hasUser() && url.user() != base_url.user()) ||
1610             (url.hasPass() && url.pass() != base_url.pass())) {
1611         return url.url();
1612     }
1613 
1614     QString relURL;
1615 
1616     if ((base_url.path() != url.path()) || (base_url.query() != url.query())) {
1617         bool dummy;
1618         QString basePath = base_url.directory(KUrl::ObeyTrailingSlash);
1619         static const char s_pathExcludeChars[] = "!$&'()*+,;=:@/";
1620         relURL = QString::fromLatin1(QUrl::toPercentEncoding(_relativePath(basePath, url.path(), dummy), s_pathExcludeChars));
1621         relURL += url.query();
1622     }
1623 
1624     if (url.hasRef()) {
1625         relURL += QLatin1Char('#');
1626         relURL += url.ref();
1627     }
1628 
1629     if (relURL.isEmpty()) {
1630         return QLatin1String("./");
1631     }
1632 
1633     return relURL;
1634 }
1635 
1636 void KUrl::setPath(const QString &_path)
1637 {
1638 #if defined(Q_OS_WIN) && defined(DEBUG_KURL)
1639     qDebug() << "KUrl::setPath " << " " << _path.toLatin1().data();
1640 #endif
1641     if (scheme().isEmpty()) {
1642         setScheme(QLatin1String("file"));
1643     }
1644 #pragma message("KDE5 TODO: Remove tildeExpand feature for local paths")
1645 #if 0
1646     QString path = KShell::tildeExpand(_path);
1647     if (path.isEmpty())
1648 #endif
1649         QString path = _path;
1650 #ifdef Q_OS_WIN
1651     const int len = path.length();
1652     if (len == 2 && IS_LETTER(path[0]) && path[1] == QLatin1Char(':')) {
1653         path += QLatin1Char('/');
1654     }
1655     //This is necessary because QUrl has the "path" part including the first slash
1656     //Without this QUrl doesn't understand that this is a path, and some operations fail
1657     //e.g. C:/blah needs to become /C:/blah
1658     else if (len > 0 && path[0] != QLatin1Char('/') && scheme() == QLatin1String("file")) {
1659         path = QLatin1Char('/') + path;
1660     }
1661 #endif
1662     QUrl::setPath(path, QUrl::DecodedMode);
1663 }
1664 
1665 #if 0 // this would be if we didn't decode '+' into ' '
1666 QMap< QString, QString > KUrl::queryItems(int options) const
1667 {
1668     QMap< QString, QString > result;
1669     const QList<QPair<QString, QString> > items = QUrl::queryItems();
1670     QPair<QString, QString> item;
1671     Q_FOREACH (item, items) {
1672         result.insert(options & CaseInsensitiveKeys ? item.first.toLower() : item.first, item.second);
1673     }
1674     return result;
1675 }
1676 #endif
1677 
1678 QMap< QString, QString > KUrl::queryItems(const QueryItemsOptions &options) const
1679 {
1680     const QString strQueryEncoded = QString::fromLatin1(encodedQuery().data());
1681     if (strQueryEncoded.isEmpty()) {
1682         return QMap<QString, QString>();
1683     }
1684 
1685     QMap< QString, QString > result;
1686     const QStringList items = strQueryEncoded.split(QLatin1Char('&'), QString::SkipEmptyParts);
1687     for (QStringList::const_iterator it = items.begin(); it != items.end(); ++it) {
1688         const int equal_pos = (*it).indexOf(QLatin1Char('='));
1689         if (equal_pos > 0) {   // = is not the first char...
1690             QString name = (*it).left(equal_pos);
1691             if (options & CaseInsensitiveKeys) {
1692                 name = name.toLower();
1693             }
1694             QString value = (*it).mid(equal_pos + 1);
1695             if (value.isEmpty()) {
1696                 result.insert(name, QString::fromLatin1(""));
1697             } else {
1698                 // ### why is decoding name not necessary?
1699                 value.replace(QLatin1Char('+'), QLatin1Char(' '));   // + in queries means space
1700                 result.insert(name, QUrl::fromPercentEncoding(value.toLatin1()));
1701             }
1702         } else if (equal_pos < 0) {   // no =
1703             QString name = (*it);
1704             if (options & CaseInsensitiveKeys) {
1705                 name = name.toLower();
1706             }
1707             result.insert(name, QString());
1708         }
1709     }
1710 
1711     return result;
1712 }
1713 
1714 QString KUrl::queryItem(const QString &_item) const
1715 {
1716     const QString strQueryEncoded = QString::fromLatin1(encodedQuery().data());
1717     const QString item = _item + QLatin1Char('=');
1718     if (strQueryEncoded.length() <= 1) {
1719         return QString();
1720     }
1721 
1722     const QStringList items = strQueryEncoded.split(QString(QLatin1Char('&')), QString::SkipEmptyParts);
1723     const int _len = item.length();
1724     for (QStringList::ConstIterator it = items.begin(); it != items.end(); ++it) {
1725         if ((*it).startsWith(item)) {
1726             if ((*it).length() > _len) {
1727                 QString str = (*it).mid(_len);
1728                 str.replace(QLatin1Char('+'), QLatin1Char(' '));   // + in queries means space.
1729                 return QUrl::fromPercentEncoding(str.toLatin1());
1730             } else { // empty value
1731                 return QString::fromLatin1("");
1732             }
1733         }
1734     }
1735 
1736     return QString();
1737 }
1738 
1739 void KUrl::addQueryItem(const QString &_item, const QString &_value)
1740 {
1741     QString item = _item + QLatin1Char('=');
1742     QString value = QString::fromLatin1(QUrl::toPercentEncoding(_value).data());
1743 
1744     QString strQueryEncoded = QString::fromLatin1(encodedQuery().data());
1745     if (!strQueryEncoded.isEmpty()) {
1746         strQueryEncoded += QLatin1Char('&');
1747     }
1748     strQueryEncoded += item + value;
1749     setEncodedQuery(strQueryEncoded.toLatin1());
1750 }
1751 
1752 void KUrl::populateMimeData(QMimeData *mimeData,
1753                             const MetaDataMap &metaData,
1754                             MimeDataFlags flags) const
1755 {
1756     populateMimeDataHelper(KUrl::List(*this), mimeData, metaData, flags);
1757 }
1758 
1759 bool KUrl::hasRef() const
1760 {
1761     return hasFragment();
1762 }
1763 
1764 void KUrl::setRef(const QString &fragment)
1765 {
1766     setFragment(fragment);
1767 }
1768 
1769 QString KUrl::ref() const
1770 {
1771     if (!hasFragment()) {
1772         return QString();
1773     } else {
1774         return fragment(QUrl::FullyEncoded);
1775     }
1776 }
1777 
1778 bool KUrl::isParentOf(const KUrl &u) const
1779 {
1780     return QUrl::isParentOf(u) || equals(u, CompareWithoutTrailingSlash);
1781 }