/*
    Copyright (C) 1999 Torben Weis <weis@kde.org>
    Copyright (C) 2005-2006 David Faure <faure@kde.org>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.
*/

/// KDE4 TODO: maybe we should use QUrl::resolved()

/*
 * The currently active RFC for URL/URIs is RFC3986
 * Previous (and now deprecated) RFCs are RFC1738 and RFC2396
 */

#include "kurl.h"
#include "kurlmimedata.h"

#include <stdio.h>
#include <assert.h>
#include <ctype.h>
#include <stdlib.h>
#include <unistd.h>

#include <QtCore/QDebug>
#include <QtCore/QDir>
#include <QtCore/QStringList>
#include <QtCore/QRegExp>
#include <QtCore/QMimeData>
#include <QtCore/QTextCodec>

#ifdef DEBUG_KURL
static int kurlDebugArea()
{
    static int s_area = KDebug::registerArea("kdecore (KUrl)");
    return s_area;
}
#endif

static QString cleanpath(const QString &_path, bool cleanDirSeparator, bool decodeDots)
{
    if (_path.isEmpty()) {
        return QString();
    }

    if (QFileInfo(_path).isRelative()) {
        return _path;    // Don't mangle mailto-style URLs
    }

    QString path = _path;

    int len = path.length();

    if (decodeDots) {
        static const QLatin1String encodedDot("%2e");
        if (path.indexOf(encodedDot, 0, Qt::CaseInsensitive) != -1) {
            static const QLatin1String encodedDOT("%2E"); // Uppercase!
            path.replace(encodedDot, QString(QLatin1Char('.')));
            path.replace(encodedDOT, QString(QLatin1Char('.')));
            len = path.length();
        }
    }

    const bool slash = (len && path[len - 1] == QLatin1Char('/')) ||
                       (len > 1 && path[len - 2] == QLatin1Char('/') && path[len - 1] == QLatin1Char('.'));

    // The following code cleans up directory path much like
    // QDir::cleanPath() except it can be made to ignore multiple
    // directory separators by setting the flag to false.  That fixes
    // bug# 15044, mail.altavista.com and other similar brain-dead server
    // implementations that do not follow what has been specified in
    // RFC 2396!! (dA)
    QString result;
    int cdUp, orig_pos, pos;

    cdUp = 0;
    pos = orig_pos = len;
    while (pos && (pos = path.lastIndexOf(QLatin1Char('/'), --pos)) != -1) {
        len = orig_pos - pos - 1;
        if (len == 2 && path[pos + 1] == QLatin1Char('.') && path[pos + 2] == QLatin1Char('.')) {
            cdUp++;
        } else {
            // Ignore any occurrences of '.'
            // This includes entries that simply do not make sense like /..../
            if ((len || !cleanDirSeparator) &&
                    (len != 1 || path[pos + 1] != QLatin1Char('.'))) {
                if (!cdUp) {
                    result.prepend(path.mid(pos, len + 1));
                } else {
                    cdUp--;
                }
            }
        }
        orig_pos = pos;
    }

#ifdef Q_OS_WIN // prepend drive letter if exists (js)
    if (orig_pos >= 2 && path[0].isLetter() && path[1] == QLatin1Char(':')) {
        result.prepend(QString(path[0]) + QLatin1Char(':'));
    }
#endif

    if (result.isEmpty()) {
        result = QLatin1Char('/');
    } else if (slash && result[result.length() - 1] != QLatin1Char('/')) {
        result.append(QLatin1Char('/'));
    }

    return result;
}

#ifdef Q_OS_WIN

// returns true if provided arguments desinate letter+colon or double slash
#define IS_DRIVE_OR_DOUBLESLASH(isletter, char1, char2, colon, slash) \
    ((isletter && char2 == colon) || (char1 == slash && char2 == slash))

// Removes file:/// or file:// or file:/ or / prefix assuming that str
// is (nonempty) Windows absolute path with a drive letter or double slash.
// If there was file protocol, the path is decoded from percent encoding
static QString removeSlashOrFilePrefix(const QString &str)
{
    // FIXME this should maybe be replaced with some (faster?)/nicer logic
    const int len = str.length();
    if (str[0] == QLatin1Char('f')) {
        if (len > 10 && str.startsWith(QLatin1String("file:///"))
                && IS_DRIVE_OR_DOUBLESLASH(str[8].isLetter(), str[8], str[9], QLatin1Char(':'), QLatin1Char('/'))) {
            return QUrl::fromPercentEncoding(str.toLatin1()).mid(8);
        } else if (len > 9 && str.startsWith(QLatin1String("file://"))
                   && IS_DRIVE_OR_DOUBLESLASH(str[7].isLetter(), str[7], str[8], QLatin1Char(':'), QLatin1Char('/'))) {
            return QUrl::fromPercentEncoding(str.toLatin1()).mid(7);
        } else if (len > 8 && str.startsWith(QLatin1String("file:/"))
                   && IS_DRIVE_OR_DOUBLESLASH(str[6].isLetter(), str[6], str[7], QLatin1Char(':'), QLatin1Char('/'))) {
            return QUrl::fromPercentEncoding(str.toLatin1()).mid(6);
        }
    }
    /* No 'else' here since there can be "f:/" path. */

    /* '/' + drive letter or // */
    if (len > 2 && str[0] == QLatin1Char('/')
            && IS_DRIVE_OR_DOUBLESLASH(str[1].isLetter(), str[1], str[2], QLatin1Char(':'), QLatin1Char('/'))) {
        return str.mid(1);
    }
    /* drive letter or // */
    else if (len >= 2 && IS_DRIVE_OR_DOUBLESLASH(str[0].isLetter(), str[0], str[1], QLatin1Char(':'), QLatin1Char('/'))) {
        return str;
    }
    return QString();
}
#endif

bool KUrl::isRelativeUrl(const QString &_url)
{
    int len = _url.length();
    if (!len) {
        return true;    // Very short relative URL.
    }
    const QChar *str = _url.unicode();

    // Absolute URL must start with alpha-character
    if (!isalpha(str[0].toLatin1())) {
        return true;    // Relative URL
    }

    for (int i = 1; i < len; i++) {
        char c = str[i].toLatin1(); // Note: non-latin1 chars return 0!
        if (c == ':') {
            return false;    // Absolute URL
        }

        // Protocol part may only contain alpha, digit, + or -
        if (!isalpha(c) && !isdigit(c) && (c != '+') && (c != '-')) {
            return true;    // Relative URL
        }
    }
    // URL did not contain ':'
    return true; // Relative URL
}

KUrl::List::List(const KUrl &url)
{
    append(url);
}

KUrl::List::List(const QList<KUrl> &list)
    : QList<KUrl>(list)
{
}

KUrl::List::List(const QList<QUrl> &list)
{
    Q_FOREACH (const QUrl &url, list) {
        append(KUrl(url));
    }
}

KUrl::List::List(const QStringList &list)
{
    for (QStringList::ConstIterator it = list.begin();
            it != list.end();
            ++it) {
        append(KUrl(*it));
    }
}

QStringList KUrl::List::toStringList() const
{
    return toStringList(KUrl::LeaveTrailingSlash);
}

QStringList KUrl::List::toStringList(KUrl::AdjustPathOption trailing) const
{
    QStringList lst;
    for (KUrl::List::ConstIterator it = constBegin();
            it != constEnd(); ++it) {
        lst.append(it->url(trailing));
    }
    return lst;
}

static void populateMimeDataHelper(const KUrl::List &urls,
                                   QMimeData *mimeData,
                                   const KUrl::MetaDataMap &metaData,
                                   KUrl::MimeDataFlags flags)
{
    const QString oldText = mimeData->text();
    mimeData->setUrls(urls); // set text/uri-list and text/plain

    if ((flags & KUrl::NoTextExport) == 0) {
        mimeData->setText(oldText);
    }

    if (!metaData.isEmpty()) {
        KUrlMimeData::setMetaData(metaData, mimeData);
    }
}

void KUrl::List::populateMimeData(QMimeData *mimeData,
                                  const KUrl::MetaDataMap &metaData,
                                  MimeDataFlags flags) const
{
    populateMimeDataHelper(*this, mimeData, metaData, flags);
}

void KUrl::List::populateMimeData(const KUrl::List &mostLocalUrls,
                                  QMimeData *mimeData,
                                  const KUrl::MetaDataMap &metaData,
                                  MimeDataFlags flags) const
{
    const QString oldText = mimeData->text();
    KUrlMimeData::setUrls(*this, mostLocalUrls, mimeData);

    if ((flags & KUrl::NoTextExport) == 0) {
        mimeData->setText(oldText);
    }

    if (!metaData.isEmpty()) {
        KUrlMimeData::setMetaData(metaData, mimeData);
    }
}

bool KUrl::List::canDecode(const QMimeData *mimeData)
{
    return mimeData->hasUrls();
}

QStringList KUrl::List::mimeDataTypes()
{
    return KUrlMimeData::mimeDataTypes();
}

KUrl::List KUrl::List::fromMimeData(const QMimeData *mimeData,
                                    DecodeOptions decodeOptions,
                                    KUrl::MetaDataMap *metaData)
{
    KUrlMimeData::DecodeOptions options = KUrlMimeData::PreferKdeUrls;
    if (decodeOptions == PreferLocalUrls) {
        options = KUrlMimeData::PreferLocalUrls;
    }
    return KUrlMimeData::urlsFromMimeData(mimeData, options, metaData);
}

KUrl::List::operator QVariant() const
{
    return qVariantFromValue(*this);
}

KUrl::List::operator QList<QUrl>() const
{
    QList<QUrl> list;
    Q_FOREACH (const KUrl &url, *this) {
        list << url;
    }
    return list;
}

///

KUrl::KUrl()
    : QUrl()
{
}

KUrl::~KUrl()
{
}

KUrl::KUrl(const QString &str)
    : QUrl()
{
    if (!str.isEmpty()) {
#ifdef Q_OS_WIN
#ifdef DEBUG_KURL
        qDebug() << "KUrl::KUrl ( const QString &str = " << str.toLatin1().data() << " )";
#endif
        QString pathToSet;
        // when it starts with file:// it's a url and must be valid. we don't care if the
        // path exist/ is valid or not
        if (!str.startsWith(QLatin1String("file://"))) {
            pathToSet = removeSlashOrFilePrefix(QDir::fromNativeSeparators(str));
        }
        if (!pathToSet.isEmpty()) {
            // we have a prefix indicating this is a local URL
            // remember the possible query using _setEncodedUrl(), then set up the correct path without query protocol part
            int index = pathToSet.lastIndexOf(QLatin1Char('?'));
            if (index == -1) {
                setPath(pathToSet);
            } else {
                setPath(pathToSet.left(index));
                _setQuery(pathToSet.mid(index + 1));
            }
            return;
        }
#endif
        if (str[0] == QLatin1Char('/') || str[0] == QLatin1Char('~')) {
            setPath(str);
        } else {
            _setEncodedUrl(str.toUtf8());
        }
    }
}

KUrl::KUrl(const char *str)
    : QUrl()
{
#ifdef Q_OS_WIN
    // true if @a c is letter
#define IS_LETTER(c) \
    ((c >= QLatin1Char('A') && c <= QLatin1Char('Z')) || (c >= QLatin1Char('a') && c <= QLatin1Char('z')))

    // like IS_DRIVE_OR_DOUBLESLASH, but slash is prepended
#define IS_SLASH_AND_DRIVE_OR_DOUBLESLASH_0 \
    ( QLatin1Char(str[0]) == QLatin1Char('/') && IS_DRIVE_OR_DOUBLESLASH(IS_LETTER(QLatin1Char(str[1])), QLatin1Char(str[1]), QLatin1Char(str[2]), QLatin1Char(':'), QLatin1Char('/')) )

    // like IS_DRIVE_OR_DOUBLESLASH, with characters == str[0] and str[1]
#define IS_DRIVE_OR_DOUBLESLASH_0 \
    ( IS_DRIVE_OR_DOUBLESLASH(IS_LETTER(QLatin1Char(str[0])), QLatin1Char(str[0]), QLatin1Char(str[1]), QLatin1Char(':'), QLatin1Char('/')) )

#if defined(DEBUG_KURL)
    qDebug() << "KUrl::KUrl " << " " << str;
#endif
    if (str && str[0] && str[1] && str[2]) {
        if (IS_SLASH_AND_DRIVE_OR_DOUBLESLASH_0) {
            setPath(QString::fromUtf8(str + 1));
        } else if (IS_DRIVE_OR_DOUBLESLASH_0) {
            setPath(QString::fromUtf8(str));
        }
    }
#endif
    if (str && str[0]) {
        if (str[0] == '/' || str[0] == '~') {
            setPath(QString::fromUtf8(str));
        } else {
            _setEncodedUrl(str);
        }
    }
}

KUrl::KUrl(const QByteArray &str)
    : QUrl()
{
    if (!str.isEmpty()) {
#ifdef Q_OS_WIN
#ifdef DEBUG_KURL
        qDebug() << "KUrl::KUrl " << " " << str.data();
#endif
        if (IS_SLASH_AND_DRIVE_OR_DOUBLESLASH_0) {
            setPath(QString::fromUtf8(str.mid(1)));
        } else if (IS_DRIVE_OR_DOUBLESLASH_0) {
            setPath(QString::fromUtf8(str));
        }
#else
        if (str[0] == '/' || str[0] == '~') {
            setPath(QString::fromUtf8(str.data()));
        }
#endif
        else {
            _setEncodedUrl(str);
        }
    }
}

KUrl::KUrl(const KUrl &_u)
    : QUrl(_u)
{
#if defined(Q_OS_WIN) && defined(DEBUG_KURL)
    qDebug() << "KUrl::KUrl(KUrl) " << " path " << _u.path() << " toLocalFile " << _u.toLocalFile();
#endif
}

KUrl::KUrl(const QUrl &u)
    : QUrl(u)
{
#if defined(Q_OS_WIN) && defined(DEBUG_KURL)
    qDebug() << "KUrl::KUrl(Qurl) " << " path " << u.path() << " toLocalFile " << u.toLocalFile();
#endif
}

KUrl::KUrl(const KUrl &_u, const QString &_rel_url)
    : QUrl()
{
#if defined(Q_OS_WIN) && defined(DEBUG_KURL)
    qDebug() << "KUrl::KUrl(KUrl,QString rel_url) " << " path " << _u.path() << " toLocalFile " << _u.toLocalFile();
#endif
#if 0
    if (_u.hasSubUrl()) { // Operate on the last suburl, not the first
        KUrl::List lst = split(_u);
        KUrl u(lst.last(), _rel_url);
        lst.erase(--lst.end());
        lst.append(u);
        *this = join(lst);
        return;
    }
#endif
    QString rUrl = _rel_url;

    // WORKAROUND THE RFC 1606 LOOPHOLE THAT ALLOWS
    // http:/index.html AS A VALID SYNTAX FOR RELATIVE
    // URLS. ( RFC 2396 section 5.2 item # 3 )
    const int len = _u.scheme().length();
    if (!_u.host().isEmpty() && !rUrl.isEmpty() &&
            rUrl.indexOf(_u.scheme(), 0, Qt::CaseInsensitive) == 0 &&
            rUrl[len] == QLatin1Char(':') && (rUrl[len + 1] != QLatin1Char('/') ||
                    (rUrl[len + 1] == QLatin1Char('/') && rUrl[len + 2] != QLatin1Char('/')))) {
        rUrl.remove(0, rUrl.indexOf(QLatin1Char(':')) + 1);
    }

    if (rUrl.isEmpty()) {
        *this = _u;
    } else if (rUrl[0] == QLatin1Char('#')) {
        *this = _u;
        QByteArray strRef_encoded = rUrl.mid(1).toLatin1();
        if (strRef_encoded.isNull()) {
            setFragment(QString::fromLatin1(""));    // we know there was an (empty) html ref, we saw the '#'
        } else {
            setFragment(QUrl::fromPercentEncoding(strRef_encoded));
        }
    } else if (isRelativeUrl(rUrl)) {
        *this = _u;
        setFragment(QString());
        setEncodedQuery(QByteArray());
        QString strPath = path();
        if (rUrl[0] == QLatin1Char('/')) {
            if ((rUrl.length() > 1) && (rUrl[1] == QLatin1Char('/'))) {
                setHost(QString());
                setPort(-1);
                // File protocol returns file:/// without host, strip // from rUrl
                if (_u.isLocalFile()) {
                    rUrl.remove(0, 2);
                }
            }
            strPath.clear();
        } else if (rUrl[0] != QLatin1Char('?')) {
            const int pos = strPath.lastIndexOf(QLatin1Char('/'));
            if (pos >= 0) {
                strPath.truncate(pos);
            }
            strPath += QLatin1Char('/');
        } else {
            if (strPath.isEmpty()) {
                strPath = QLatin1Char('/');
            }
        }
        setPath(strPath);
        //kDebug(kurlDebugArea()) << "url()=" << url() << " rUrl=" << rUrl;
        const KUrl tmp(url() + rUrl);
        //kDebug(kurlDebugArea()) << "assigning tmp=" << tmp.url();
        *this = tmp;
        cleanPath(KeepDirSeparators);
    } else {
        const KUrl tmp(rUrl);
        //kDebug(kurlDebugArea()) << "not relative; assigning tmp=" << tmp.url();
        *this = tmp;
        // Preserve userinfo if applicable.
        if (!_u.userInfo().isEmpty() && userInfo().isEmpty()
                && (_u.host() == host()) && (_u.scheme() == scheme())) {
            setUserInfo(_u.userInfo());
        }
        cleanPath(KeepDirSeparators);
    }
}

KUrl &KUrl::operator=(const KUrl &_u)
{
    QUrl::operator=(_u);
    return *this;
}

bool KUrl::operator==(const KUrl &_u) const
{
    return QUrl::operator==(_u);
}

bool KUrl::operator==(const QString &_u) const
{
    KUrl u(_u);
    return (*this == u);
}

KUrl::operator QVariant() const
{
    return qVariantFromValue(*this);
}

#ifndef KDELIBS4SUPPORT_NO_DEPRECATED
bool KUrl::cmp(const KUrl &u, bool ignore_trailing) const
{
    return equals(u, ignore_trailing ? CompareWithoutTrailingSlash : EqualsOptions(0));
}
#endif

bool KUrl::equals(const KUrl &_u, const EqualsOptions &options) const
{
    if (!isValid() || !_u.isValid()) {
        return false;
    }

    if (options & CompareWithoutTrailingSlash || options & CompareWithoutFragment) {
        QString path1 = path((options & CompareWithoutTrailingSlash) ? RemoveTrailingSlash : LeaveTrailingSlash);
        QString path2 = _u.path((options & CompareWithoutTrailingSlash) ? RemoveTrailingSlash : LeaveTrailingSlash);

        if (options & AllowEmptyPath) {
            if (path1 == QLatin1String("/")) {
                path1.clear();
            }
            if (path2 == QLatin1String("/")) {
                path2.clear();
            }
        }

#ifdef Q_OS_WIN
        const bool bLocal1 = isLocalFile();
        const bool bLocal2 = _u.isLocalFile();
        if (!bLocal1 && bLocal2 || bLocal1 && !bLocal2) {
            return false;
        }
        // local files are case insensitive
        if (bLocal1 && bLocal2 && 0 != QString::compare(path1, path2, Qt::CaseInsensitive)) {
            return false;
        }
#endif
        if (path1 != path2) {
            return false;
        }

        if (scheme() == _u.scheme() &&
                authority() == _u.authority() && // user+pass+host+port
                encodedQuery() == _u.encodedQuery() &&
                (fragment() == _u.fragment() || options & CompareWithoutFragment)) {
            return true;
        }

        return false;
    }

    return (*this == _u);
}

QString KUrl::protocol() const
{
    return scheme().toLower();
}

void KUrl::setProtocol(const QString &proto)
{
    setScheme(proto);
}

QString KUrl::user() const
{
    return userName();
}

void KUrl::setUser(const QString &user)
{
    setUserName(user);
}

bool KUrl::hasUser() const
{
    return !userName().isEmpty();
}

QString KUrl::pass() const
{
    return password();
}

void KUrl::setPass(const QString &pass)
{
    setPassword(pass);
}

bool KUrl::hasPass() const
{
    return !password().isEmpty();
}

bool KUrl::hasHost() const
{
    return !host().isEmpty();
}

bool KUrl::hasPath() const
{
    return !path().isEmpty();
}

void KUrl::setFileName(const QString &_txt)
{
    setFragment(QString());
    int i = 0;
    while (i < _txt.length() && _txt[i] == QLatin1Char('/')) {
        ++i;
    }
    QString tmp = i ? _txt.mid(i) : _txt;

    QString path = this->path();
    if (path.isEmpty())
#ifdef Q_OS_WIN
        path = isLocalFile() ? QDir::rootPath() : QLatin1String("/");
#else
        path = QDir::rootPath();
#endif
    else {
        int lastSlash = path.lastIndexOf(QLatin1Char('/'));
        if (lastSlash == -1) {
            path.clear();    // there's only the file name, remove it
        } else if (!path.endsWith(QLatin1Char('/'))) {
            path.truncate(lastSlash + 1);    // keep the "/"
        }
    }

    path += tmp;
    setPath(path);

    cleanPath();
}

void KUrl::cleanPath(const CleanPathOption &options)
{
    //if (m_iUriMode != URL) return;
    const QString newPath = cleanpath(path(), !(options & KeepDirSeparators), false);
    if (path() != newPath) {
        setPath(newPath);
    }
}

static QString trailingSlash(KUrl::AdjustPathOption trailing, const QString &path)
{
    if (trailing == KUrl::LeaveTrailingSlash) {
        return path;
    }

    QString result = path;

    if (trailing == KUrl::AddTrailingSlash) {
        int len = result.length();
        if ((len > 0) && (result[ len - 1 ] != QLatin1Char('/'))) {
            result += QLatin1Char('/');
        }
        return result;
    } else if (trailing == KUrl::RemoveTrailingSlash) {
        if (result == QLatin1String("/")) {
            return result;
        }
        int len = result.length();
        while (len > 1 && result[ len - 1 ] == QLatin1Char('/')) {
            len--;
        }
        result.truncate(len);
        return result;
    } else {
        assert(0);
        return result;
    }
}

void KUrl::adjustPath(AdjustPathOption trailing)
{
    const QString newPath = trailingSlash(trailing, path());
    if (path() != newPath) {
        setPath(newPath);
    }
}

QString KUrl::encodedPathAndQuery(AdjustPathOption trailing, const EncodedPathAndQueryOptions &options) const
{
    QString encodedPath;
#ifdef Q_OS_WIN
    // see KUrl::path()
    if (isLocalFile()) {
        // ### this is probably broken
        encodedPath = trailingSlash(trailing, QUrl::toLocalFile());
        encodedPath = QString::fromLatin1(QUrl::toPercentEncoding(encodedPath, "!$&'()*+,;=:@/"));
    } else {
        encodedPath = trailingSlash(trailing, QString::fromLatin1(QUrl::encodedPath()));
    }
#else
    encodedPath = trailingSlash(trailing, QString::fromLatin1(QUrl::encodedPath().data()));
#endif

    if ((options & AvoidEmptyPath) && encodedPath.isEmpty()) {
        encodedPath.append(QLatin1Char('/'));
    }

    if (hasQuery()) {
        return encodedPath + QLatin1Char('?') + QString::fromLatin1(encodedQuery().data());
    } else {
        return encodedPath;
    }
}

void KUrl::setEncodedPathAndQuery(const QString &_txt)
{
    const int pos = _txt.indexOf(QLatin1Char('?'));
    if (pos == -1) {
        setPath(QUrl::fromPercentEncoding(_txt.toLatin1()));
        setEncodedQuery(QByteArray());
    } else {
        setPath(QUrl::fromPercentEncoding(_txt.toLatin1().left(pos)));
        _setQuery(_txt.right(_txt.length() - pos - 1));
    }
}

QString KUrl::path(AdjustPathOption trailing) const
{
    const QString decodedPath = QUrl::path(QUrl::FullyDecoded);

#ifdef Q_OS_WIN
#ifdef DEBUG_KURL
    kWarning() << (isLocalFile() ? "converted to local file - the related call should be converted to toLocalFile()" : "") << QUrl::path();
#endif
    return trailingSlash(trailing, isLocalFile() ? QUrl::toLocalFile() : decodedPath);
#else
    return trailingSlash(trailing, decodedPath);
#endif
}

QString KUrl::toLocalFile(AdjustPathOption trailing) const
{
    if (hasHost() && isLocalFile()) {
        KUrl urlWithoutHost(*this);
        urlWithoutHost.setHost(QString());
        return trailingSlash(trailing, urlWithoutHost.toLocalFile());
    }

    return trailingSlash(trailing, QUrl::toLocalFile());
}

inline static bool hasSubUrl(const QUrl &url);

static inline bool isLocalFile(const QUrl &url)
{
    if (url.scheme().compare(QLatin1String("file"), Qt::CaseInsensitive) != 0 || hasSubUrl(url)) {
        return false;
    }

    if (url.host().isEmpty() || (url.host() == QLatin1String("localhost"))) {
        return true;
    }

    char hostname[ 256 ];
    hostname[ 0 ] = '\0';
    if (!gethostname(hostname, 255)) {
        hostname[sizeof(hostname) - 1] = '\0';
    }

    for (char *p = hostname; *p; p++) {
        *p = tolower(*p);
    }

    return (url.host() == QString::fromLatin1(hostname));
}

bool KUrl::isLocalFile() const
{
    return ::isLocalFile(*this);
}

void KUrl::setFileEncoding(const QString &encoding)
{
    if (!isLocalFile()) {
        return;
    }

    QString q = query();

    if (!q.isEmpty() && q[0] == QLatin1Char('?')) {
        q = q.mid(1);
    }

    QStringList args = q.split(QLatin1Char('&'), QString::SkipEmptyParts);
    for (QStringList::Iterator it = args.begin();
            it != args.end();) {
        QString s = QUrl::fromPercentEncoding((*it).toLatin1());
        if (s.startsWith(QLatin1String("charset="))) {
            it = args.erase(it);
        } else {
            ++it;
        }
    }
    if (!encoding.isEmpty()) {
        args.append(QLatin1String("charset=") + QString::fromLatin1(QUrl::toPercentEncoding(encoding).data()));
    }

    if (args.isEmpty()) {
        _setQuery(QString());
    } else {
        _setQuery(args.join(QString(QLatin1Char('&'))));
    }
}

QString KUrl::fileEncoding() const
{
    if (!isLocalFile()) {
        return QString();
    }

    QString q = query();

    if (q.isEmpty()) {
        return QString();
    }

    if (q[0] == QLatin1Char('?')) {
        q = q.mid(1);
    }

    const QStringList args = q.split(QLatin1Char('&'), QString::SkipEmptyParts);
    for (QStringList::ConstIterator it = args.begin();
            it != args.end();
            ++it) {
        QString s = QUrl::fromPercentEncoding((*it).toLatin1());
        if (s.startsWith(QLatin1String("charset="))) {
            return s.mid(8);
        }
    }
    return QString();
}

inline static bool hasSubUrl(const QUrl &url)
{
    // The isValid call triggers QUrlPrivate::validate which needs the full encoded url,
    // all this takes too much time for isLocalFile()
    const QString scheme = url.scheme();
    if (scheme.isEmpty() /*|| !isValid()*/) {
        return false;
    }
    const QString ref(url.fragment());
    if (ref.isEmpty()) {
        return false;
    }
    switch (ref.at(0).unicode()) {
    case 'g':
        if (ref.startsWith(QLatin1String("gzip:"))) {
            return true;
        }
        break;
    case 'b':
        if (ref.startsWith(QLatin1String("bzip:")) || ref.startsWith(QLatin1String("bzip2:"))) {
            return true;
        }
        break;
    case 'l':
        if (ref.startsWith(QLatin1String("lzma:"))) {
            return true;
        }
        break;
    case 'x':
        if (ref.startsWith(QLatin1String("xz:"))) {
            return true;
        }
        break;
    case 't':
        if (ref.startsWith(QLatin1String("tar:"))) {
            return true;
        }
        break;
    case 'a':
        if (ref.startsWith(QLatin1String("ar:"))) {
            return true;
        }
        break;
    case 'z':
        if (ref.startsWith(QLatin1String("zip:"))) {
            return true;
        }
        break;
    default:
        break;
    }
    if (scheme == QLatin1String("error")) { // anything that starts with error: has suburls
        return true;
    }
    return false;
}

bool KUrl::hasSubUrl() const
{
    return ::hasSubUrl(*this);
}

QString KUrl::url(AdjustPathOption trailing) const
{
    if (QString::compare(scheme(), QLatin1String("mailto"), Qt::CaseInsensitive) == 0) {
        // mailto urls should be prettified, see the url183433 testcase.
        return prettyUrl(trailing);
    }
    if (trailing == AddTrailingSlash && !path().endsWith(QLatin1Char('/'))) {
        // -1 and 0 are provided by QUrl, but not +1, so that one is a bit tricky.
        // To avoid reimplementing toEncoded() all over again, I just use another QUrl
        // Let's hope this is fast, or not called often...
        QUrl newUrl(*this);
        newUrl.setPath(path() + QLatin1Char('/'));
        return QString::fromLatin1(newUrl.toEncoded().data());
    } else if (trailing == RemoveTrailingSlash) {
        const QString cleanedPath = trailingSlash(trailing, path());
        if (cleanedPath == QLatin1String("/")) {
            if (path() != QLatin1String("/")) {
                QUrl fixedUrl = *this;
                fixedUrl.setPath(cleanedPath);
                return QLatin1String(fixedUrl.toEncoded(None).data());
            }
            return QLatin1String(toEncoded(None).data());
        }
    }
    return QString::fromLatin1(toEncoded(trailing == RemoveTrailingSlash ? StripTrailingSlash : None).data());
}

static QString toPrettyPercentEncoding(const QString &input, bool forFragment)
{
    QString result;
    result.reserve(input.length());
    for (int i = 0; i < input.length(); ++i) {
        const QChar c = input.at(i);
        register ushort u = c.unicode();
        if (u < 0x20
                || (!forFragment && u == '?') // don't escape '?' in fragments, not needed and wrong (#173101)
                || u == '#' || u == '%'
                || (u == ' ' && (i + 1 == input.length() || input.at(i + 1).unicode() == ' '))) {
            static const char hexdigits[] = "0123456789ABCDEF";
            result += QLatin1Char('%');
            result += QLatin1Char(hexdigits[(u & 0xf0) >> 4]);
            result += QLatin1Char(hexdigits[u & 0xf]);
        } else {
            result += c;
        }
    }

    return result;
}

QString KUrl::prettyUrl(AdjustPathOption trailing) const
{
    // reconstruct the URL in a "pretty" form
    // a "pretty" URL is NOT suitable for data transfer. It's only for showing data to the user.
    // however, it must be parseable back to its original state, since
    // notably Konqueror displays it in the Location address.

    // A pretty URL is the same as a normal URL, except that:
    // - the password is removed
    // - the hostname is shown in Unicode (as opposed to ACE/Punycode)
    // - the pathname and fragment parts are shown in Unicode (as opposed to %-encoding)
    QString result = scheme();
    if (!result.isEmpty()) {
        if (!authority().isEmpty() || result == QLatin1String("file") || path().isEmpty()) {
            result += QLatin1String("://");
        } else {
            result += QLatin1Char(':');
        }
    }

    QString tmp = userName();
    if (!tmp.isEmpty()) {
        result += QString::fromLatin1(QUrl::toPercentEncoding(tmp).data());
        result += QLatin1Char('@');
    }

    // Check if host is an ipv6 address
    tmp = host();
    if (tmp.contains(QLatin1Char(':'))) {
        result += QLatin1Char('[') + tmp + QLatin1Char(']');
    } else {
        result += tmp;
    }

    if (port() != -1) {
        result += QLatin1Char(':');
        result += QString::number(port());
    }

    tmp = path();
#ifdef Q_OS_WIN
    if (isLocalFile()) {
        tmp.prepend(QLatin1Char('/'));    // KUrl::path() returns toLocalFile() on windows so we need to add the / back to create a proper url
    }
#endif
    result += toPrettyPercentEncoding(tmp, false);

    // adjust the trailing slash, if necessary
    if (trailing == AddTrailingSlash && !tmp.endsWith(QLatin1Char('/'))) {
        result += QLatin1Char('/');
    } else if (trailing == RemoveTrailingSlash && tmp.length() > 1 && tmp.endsWith(QLatin1Char('/'))) {
        result.chop(1);
    }

    if (hasQuery()) {
        result += QLatin1Char('?');
        result += QString::fromLatin1(encodedQuery().data());
    }

    if (hasFragment()) {
        result += QLatin1Char('#');
        result += toPrettyPercentEncoding(fragment(), true);
    }

    return result;
}

QString KUrl::pathOrUrl(AdjustPathOption trailing) const
{
    if (isLocalFile() && fragment().isNull() && encodedQuery().isNull()) {
        return toLocalFile(trailing);
    } else {
        return prettyUrl(trailing);
    }
}

// Used for text/uri-list in the mime data
QString KUrl::toMimeDataString() const // don't fold this into populateMimeData, it's also needed by other code like konqdrag
{
    if (isLocalFile()) {
#if 1
        return url();
#else
        // According to the XDND spec, file:/ URLs for DND must have
        // the hostname part. But in really it just breaks many apps,
        // so it's disabled for now.
        const QString s = url();
        if (!s.startsWith(QLatin1String("file://"))) {
            char hostname[257];
            if (gethostname(hostname, 255) == 0) {
                hostname[256] = '\0';
                return QString("file://") + hostname + s.mid(5);
            }
        }
#endif
    }

    if (hasPass()) {
        KUrl safeUrl(*this);
        safeUrl.setPassword(QString());
        return safeUrl.url();
    }
    return url();
}

KUrl::List KUrl::split(const KUrl &_url)
{
    QString ref;
    bool hasRef;
    KUrl::List lst;
    KUrl url = _url;

    while (true) {
        KUrl u = url;
        u.setFragment(QString());
        lst.append(u);
        if (url.hasSubUrl()) {
            url = KUrl(url.fragment());
        } else {
            ref = url.fragment();
            hasRef = url.hasFragment();
            break;
        }
    }

    if (hasRef) {
        // Set HTML ref in all URLs.
        KUrl::List::Iterator it;
        for (it = lst.begin(); it != lst.end(); ++it) {
            (*it).setFragment(ref);
        }
    }

    return lst;
}

KUrl::List KUrl::split(const QString &_url)
{
    return split(KUrl(_url));
}

KUrl KUrl::join(const KUrl::List &lst)
{
    if (lst.isEmpty()) {
        return KUrl();
    }
    KUrl tmp;

    bool first = true;
    QListIterator<KUrl> it(lst);
    it.toBack();
    while (it.hasPrevious()) {
        KUrl u(it.previous());
        if (!first) {
            u.setEncodedFragment(tmp.url().toLatin1() /* TODO double check encoding */);
        }
        tmp = u;

        first = false;
    }

    return tmp;
}

QString KUrl::fileName(const DirectoryOptions &options) const
{
    Q_ASSERT(options != 0);   //Disallow options == false
    QString fname;
    if (hasSubUrl()) { // If we have a suburl, then return the filename from there
        const KUrl::List list = KUrl::split(*this);
        return list.last().fileName(options);
    }
    const QString path = this->path();

    int len = path.length();
    if (len == 0) {
        return fname;
    }

    if (!(options & ObeyTrailingSlash)) {
        while (len >= 1 && path[ len - 1 ] == QLatin1Char('/')) {
            len--;
        }
    } else if (path[ len - 1 ] == QLatin1Char('/')) {
        return fname;
    }

    // Does the path only consist of '/' characters ?
    if (len == 1 && path[ 0 ] == QLatin1Char('/')) {
        return fname;
    }

    // Skip last n slashes
    int n = 1;
    int i = len;
    do {
        i = path.lastIndexOf(QLatin1Char('/'), i - 1);
    } while (--n && (i > 0));

    // If ( i == -1 ) => the first character is not a '/'
    // So it's some URL like file:blah.tgz, return the whole path
    if (i == -1) {
        if (len == (int)path.length()) {
            fname = path;
        } else
            // Might get here if _strip_trailing_slash is true
        {
            fname = path.left(len);
        }
    } else {
        fname = path.mid(i + 1, len - i - 1);   // TO CHECK
    }
    return fname;
}

void KUrl::addPath(const QString &_txt)
{
    if (hasSubUrl()) {
        KUrl::List lst = split(*this);
        KUrl &u = lst.last();
        u.addPath(_txt);
        *this = join(lst);
        return;
    }

    if (_txt.isEmpty()) {
        return;
    }

    QString strPath = path();
    int i = 0;
    int len = strPath.length();
    // Add the trailing '/' if it is missing
    if (_txt[0] != QLatin1Char('/') && (len == 0 || strPath[ len - 1 ] != QLatin1Char('/'))) {
        strPath += QLatin1Char('/');
    }

    // No double '/' characters
    i = 0;
    const int _txtlen = _txt.length();
    if (strPath.endsWith(QLatin1Char('/'))) {
        while ((i < _txtlen) && (_txt[i] == QLatin1Char('/'))) {
            ++i;
        }
    }

    setPath(strPath + _txt.mid(i));
    //kDebug(kurlDebugArea())<<"addPath: resultpath="<<path();
}

QString KUrl::directory(const DirectoryOptions &options) const
{
    Q_ASSERT(options != 0);   //Disallow options == false
    QString result = path();
    if (!(options & ObeyTrailingSlash)) {
        result = trailingSlash(RemoveTrailingSlash, result);
    }

    if (result.isEmpty() || result == QLatin1String("/")) {
        return result;
    }

    int i = result.lastIndexOf(QLatin1Char('/'));
    // If ( i == -1 ) => the first character is not a '/'
    // So it's some URL like file:blah.tgz, with no path
    if (i == -1) {
        return QString();
    }

    if (i == 0) {
        return QString(QLatin1Char('/'));
    }

#ifdef Q_OS_WIN
    if (i == 2 && result[1] == QLatin1Char(':')) {
        return result.left(3);
    }
#endif

    if (options & AppendTrailingSlash) {
        result = result.left(i + 1);
    } else {
        result = result.left(i);
    }

    return result;
}

bool KUrl::cd(const QString &_dir)
{
    if (_dir.isEmpty() || !isValid()) {
        return false;
    }

    if (hasSubUrl()) {
        KUrl::List lst = split(*this);
        KUrl &u = lst.last();
        u.cd(_dir);
        *this = join(lst);
        return true;
    }

    // absolute path ?
#ifdef Q_OS_WIN
    if (!QFileInfo(_dir).isRelative())
#else
    if (_dir[0] == QLatin1Char('/'))
#endif
    {
        setPath(_dir);
        setHTMLRef(QString());
        setEncodedQuery(QByteArray());
        return true;
    }

    // Users home directory on the local disk ?
    if (_dir[0] == QLatin1Char('~') && scheme() == QLatin1String("file")) {
        QString strPath = QDir::homePath();
        strPath += QLatin1Char('/');
        strPath += _dir.right(strPath.length() - 1);
        setPath(strPath);
        setHTMLRef(QString());
        setEncodedQuery(QByteArray());
        return true;
    }

    // relative path
    // we always work on the past of the first url.
    // Sub URLs are not touched.

    // append '/' if necessary
    QString p = path(AddTrailingSlash);
    p += _dir;
    p = cleanpath(p, true, false);
    setPath(p);

    setHTMLRef(QString());
    setEncodedQuery(QByteArray());

    return true;
}

KUrl KUrl::upUrl() const
{
    if (!isValid() || isRelative()) {
        return KUrl();
    }

    if (!encodedQuery().isEmpty()) {
        KUrl u(*this);
        u.setEncodedQuery(QByteArray());
        return u;
    }

    if (!hasSubUrl()) {
        KUrl u(*this);
        u.cd(QLatin1String("../"));
        return u;
    }

    // We have a subURL.
    KUrl::List lst = split(*this);
    if (lst.isEmpty()) {
        return KUrl();    // Huh?
    }
    while (true) {
        KUrl &u = lst.last();
        const QString old = u.path();
        u.cd(QLatin1String("../"));
        if (u.path() != old) {
            break;    // Finished.
        }
        if (lst.count() == 1) {
            break;    // Finished.
        }
        lst.removeLast();
    }
    return join(lst);
}

QString KUrl::htmlRef() const
{
    if (!hasSubUrl()) {
        return fragment(QUrl::FullyDecoded);
    }

    const List lst = split(*this);
    return (*lst.begin()).fragment(QUrl::FullyDecoded);
}

QString KUrl::encodedHtmlRef() const
{
    if (!hasSubUrl()) {
        return ref();
    }

    const List lst = split(*this);
    return (*lst.begin()).ref();
}

void KUrl::setHTMLRef(const QString &_ref)
{
    if (!hasSubUrl()) {
        setFragment(_ref);
        return;
    }

    List lst = split(*this);

    (*lst.begin()).setFragment(_ref);

    *this = join(lst);
}

bool KUrl::hasHTMLRef() const
{
    if (!hasSubUrl()) {
        return hasRef();
    }

    const List lst = split(*this);
    return (*lst.begin()).hasRef();
}

void KUrl::setDirectory(const QString &dir)
{
    if (dir.endsWith(QLatin1Char('/'))) {
        setPath(dir);
    } else {
        setPath(dir + QLatin1Char('/'));
    }
}

void KUrl::setQuery(const QString &_txt)
{
    if (!_txt.isEmpty() && _txt[0] == QLatin1Char('?')) {
        _setQuery(_txt.length() > 1 ? _txt.mid(1) : QString::fromLatin1("") /*empty, not null*/);
    } else {
        _setQuery(_txt);
    }
}

void KUrl::_setQuery(const QString &query)
{
    if (query.isNull()) {
        setEncodedQuery(QByteArray());
    } else if (query.isEmpty()) {
        setEncodedQuery("");
    } else {
        setEncodedQuery(query.toLatin1());   // already percent-escaped, so toLatin1 is ok
    }
}

QString KUrl::query() const
{
    if (!hasQuery()) {
        return QString();
    }
    return QString(QLatin1Char('?')) + QString::fromLatin1(encodedQuery().data());
}

void KUrl::_setEncodedUrl(const QByteArray &url)
{
    setEncodedUrl(url, QUrl::TolerantMode);
    if (!isValid()) { // see unit tests referring to N183630/task 183874
        setUrl(QString::fromUtf8(url.data()), QUrl::TolerantMode);
    }
}

#ifndef KDELIBS4SUPPORT_NO_DEPRECATED
bool urlcmp(const QString &_url1, const QString &_url2)
{
    return QUrl(_url1, QUrl::TolerantMode) == QUrl(_url2, QUrl::TolerantMode);
#if 0
    // Both empty ?
    if (_url1.isEmpty() && _url2.isEmpty()) {
        return true;
    }
    // Only one empty ?
    if (_url1.isEmpty() || _url2.isEmpty()) {
        return false;
    }

    KUrl::List list1 = KUrl::split(_url1);
    KUrl::List list2 = KUrl::split(_url2);

    // Malformed ?
    if (list1.isEmpty() || list2.isEmpty()) {
        return false;
    }

    return (list1 == list2);
#endif
}
#endif

#ifndef KDELIBS4SUPPORT_NO_DEPRECATED
bool urlcmp(const QString &_url1, const QString &_url2, const KUrl::EqualsOptions &_options)
{
    // Both empty ?
    if (_url1.isEmpty() && _url2.isEmpty()) {
        return true;
    }
    // Only one empty ?
    if (_url1.isEmpty() || _url2.isEmpty()) {
        return false;
    }

    KUrl u1(_url1);
    KUrl u2(_url2);
    return u1.equals(u2, _options);

#if 0 // kde3 code that supported nested urls

    KUrl::List list1 = KUrl::split(_url1);
    KUrl::List list2 = KUrl::split(_url2);

    // Malformed ?
    if (list1.isEmpty() || list2.isEmpty()) {
        return false;
    }

    int size = list1.count();
    if (list2.count() != size) {
        return false;
    }

    if (_ignore_ref) {
        (*list1.begin()).setRef(QString());
        (*list2.begin()).setRef(QString());
    }

    KUrl::List::Iterator it1 = list1.begin();
    KUrl::List::Iterator it2 = list2.begin();
    for (; it1 != list1.end(); ++it1, ++it2)
        if (!(*it1).equals(*it2, _ignore_trailing)) {
            return false;
        }
    return true;
#endif
}
#endif

// static
#ifndef KDELIBS4SUPPORT_NO_DEPRECATED
KUrl KUrl::fromPathOrUrl(const QString &text)
{
    KUrl url;
    if (!text.isEmpty()) {
        if (!QDir::isRelativePath(text) || text[0] == QLatin1Char('~')) {
            url.setPath(text);
        } else {
            url = KUrl(text);
        }
    }

    return url;
}
#endif

static QString _relativePath(const QString &base_dir, const QString &path, bool &isParent)
{
    QString _base_dir(QDir::cleanPath(base_dir));
    QString _path(QDir::cleanPath(path.isEmpty() || QDir::isRelativePath(path) ? _base_dir + QLatin1Char('/') + path : path));

    if (_base_dir.isEmpty()) {
        return _path;
    }

    if (_base_dir[_base_dir.length() - 1] != QLatin1Char('/')) {
        _base_dir.append(QLatin1Char('/'));
    }

    const QStringList list1 = _base_dir.split(QLatin1Char('/'), QString::SkipEmptyParts);
    const QStringList list2 = _path.split(QLatin1Char('/'), QString::SkipEmptyParts);

    // Find where they meet
    int level = 0;
    int maxLevel = qMin(list1.count(), list2.count());
    while ((level < maxLevel) && (list1[level] == list2[level])) {
        level++;
    }

    QString result;
    // Need to go down out of the first path to the common branch.
    for (int i = level; i < list1.count(); i++) {
        result.append(QLatin1String("../"));
    }

    // Now up up from the common branch to the second path.
    for (int i = level; i < list2.count(); i++) {
        result.append(list2[i]).append(QLatin1Char('/'));
    }

    if ((level < list2.count()) && (path[path.length() - 1] != QLatin1Char('/'))) {
        result.truncate(result.length() - 1);
    }

    isParent = (level == list1.count());

    return result;
}

QString KUrl::relativePath(const QString &base_dir, const QString &path, bool *isParent)
{
    bool parent = false;
    QString result = _relativePath(base_dir, path, parent);
    if (parent) {
        result.prepend(QLatin1String("./"));
    }

    if (isParent) {
        *isParent = parent;
    }

    return result;
}

QString KUrl::relativeUrl(const KUrl &base_url, const KUrl &url)
{
    if ((url.protocol() != base_url.protocol()) ||
            (url.host() != base_url.host()) ||
            (url.port() && url.port() != base_url.port()) ||
            (url.hasUser() && url.user() != base_url.user()) ||
            (url.hasPass() && url.pass() != base_url.pass())) {
        return url.url();
    }

    QString relURL;

    if ((base_url.path() != url.path()) || (base_url.query() != url.query())) {
        bool dummy;
        QString basePath = base_url.directory(KUrl::ObeyTrailingSlash);
        static const char s_pathExcludeChars[] = "!$&'()*+,;=:@/";
        relURL = QString::fromLatin1(QUrl::toPercentEncoding(_relativePath(basePath, url.path(), dummy), s_pathExcludeChars));
        relURL += url.query();
    }

    if (url.hasRef()) {
        relURL += QLatin1Char('#');
        relURL += url.ref();
    }

    if (relURL.isEmpty()) {
        return QLatin1String("./");
    }

    return relURL;
}

void KUrl::setPath(const QString &_path)
{
#if defined(Q_OS_WIN) && defined(DEBUG_KURL)
    qDebug() << "KUrl::setPath " << " " << _path.toLatin1().data();
#endif
    if (scheme().isEmpty()) {
        setScheme(QLatin1String("file"));
    }
#pragma message("KDE5 TODO: Remove tildeExpand feature for local paths")
#if 0
    QString path = KShell::tildeExpand(_path);
    if (path.isEmpty())
#endif
        QString path = _path;
#ifdef Q_OS_WIN
    const int len = path.length();
    if (len == 2 && IS_LETTER(path[0]) && path[1] == QLatin1Char(':')) {
        path += QLatin1Char('/');
    }
    //This is necessary because QUrl has the "path" part including the first slash
    //Without this QUrl doesn't understand that this is a path, and some operations fail
    //e.g. C:/blah needs to become /C:/blah
    else if (len > 0 && path[0] != QLatin1Char('/') && scheme() == QLatin1String("file")) {
        path = QLatin1Char('/') + path;
    }
#endif
    QUrl::setPath(path, QUrl::DecodedMode);
}

#if 0 // this would be if we didn't decode '+' into ' '
QMap< QString, QString > KUrl::queryItems(int options) const
{
    QMap< QString, QString > result;
    const QList<QPair<QString, QString> > items = QUrl::queryItems();
    QPair<QString, QString> item;
    Q_FOREACH (item, items) {
        result.insert(options & CaseInsensitiveKeys ? item.first.toLower() : item.first, item.second);
    }
    return result;
}
#endif

QMap< QString, QString > KUrl::queryItems(const QueryItemsOptions &options) const
{
    const QString strQueryEncoded = QString::fromLatin1(encodedQuery().data());
    if (strQueryEncoded.isEmpty()) {
        return QMap<QString, QString>();
    }

    QMap< QString, QString > result;
    const QStringList items = strQueryEncoded.split(QLatin1Char('&'), QString::SkipEmptyParts);
    for (QStringList::const_iterator it = items.begin(); it != items.end(); ++it) {
        const int equal_pos = (*it).indexOf(QLatin1Char('='));
        if (equal_pos > 0) {   // = is not the first char...
            QString name = (*it).left(equal_pos);
            if (options & CaseInsensitiveKeys) {
                name = name.toLower();
            }
            QString value = (*it).mid(equal_pos + 1);
            if (value.isEmpty()) {
                result.insert(name, QString::fromLatin1(""));
            } else {
                // ### why is decoding name not necessary?
                value.replace(QLatin1Char('+'), QLatin1Char(' '));   // + in queries means space
                result.insert(name, QUrl::fromPercentEncoding(value.toLatin1()));
            }
        } else if (equal_pos < 0) {   // no =
            QString name = (*it);
            if (options & CaseInsensitiveKeys) {
                name = name.toLower();
            }
            result.insert(name, QString());
        }
    }

    return result;
}

QString KUrl::queryItem(const QString &_item) const
{
    const QString strQueryEncoded = QString::fromLatin1(encodedQuery().data());
    const QString item = _item + QLatin1Char('=');
    if (strQueryEncoded.length() <= 1) {
        return QString();
    }

    const QStringList items = strQueryEncoded.split(QString(QLatin1Char('&')), QString::SkipEmptyParts);
    const int _len = item.length();
    for (QStringList::ConstIterator it = items.begin(); it != items.end(); ++it) {
        if ((*it).startsWith(item)) {
            if ((*it).length() > _len) {
                QString str = (*it).mid(_len);
                str.replace(QLatin1Char('+'), QLatin1Char(' '));   // + in queries means space.
                return QUrl::fromPercentEncoding(str.toLatin1());
            } else { // empty value
                return QString::fromLatin1("");
            }
        }
    }

    return QString();
}

void KUrl::addQueryItem(const QString &_item, const QString &_value)
{
    QString item = _item + QLatin1Char('=');
    QString value = QString::fromLatin1(QUrl::toPercentEncoding(_value).data());

    QString strQueryEncoded = QString::fromLatin1(encodedQuery().data());
    if (!strQueryEncoded.isEmpty()) {
        strQueryEncoded += QLatin1Char('&');
    }
    strQueryEncoded += item + value;
    setEncodedQuery(strQueryEncoded.toLatin1());
}

void KUrl::populateMimeData(QMimeData *mimeData,
                            const MetaDataMap &metaData,
                            MimeDataFlags flags) const
{
    populateMimeDataHelper(KUrl::List(*this), mimeData, metaData, flags);
}

bool KUrl::hasRef() const
{
    return hasFragment();
}

void KUrl::setRef(const QString &fragment)
{
    setFragment(fragment);
}

QString KUrl::ref() const
{
    if (!hasFragment()) {
        return QString();
    } else {
        return fragment(QUrl::FullyEncoded);
    }
}

bool KUrl::isParentOf(const KUrl &u) const
{
    return QUrl::isParentOf(u) || equals(u, CompareWithoutTrailingSlash);
}
