kdelibs/kdecore/io/kurl.cpp

1664 lines
43 KiB
C++
Raw Normal View History

2014-11-13 01:04:59 +02:00
/*
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 <kdebug.h>
#include <kglobal.h>
#include <kshell.h>
#include <stdio.h>
#include <assert.h>
#include <ctype.h>
#include <stdlib.h>
#include <unistd.h>
#include <QtCore/QDir>
#include <QtCore/QMutableStringListIterator>
#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 QString &encodedDot = KGlobal::staticQString("%2e");
if (path.indexOf(encodedDot, 0, Qt::CaseInsensitive) != -1)
{
static const QString &encodedDOT = KGlobal::staticQString("%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;
}
if ( result.isEmpty() )
result = QLatin1Char('/');
else if ( slash && result[result.length()-1] != QLatin1Char('/') )
result.append(QLatin1Char('/'));
return result;
}
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)
{
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 QByteArray uriListData(const KUrl::List& urls)
{
QList<QByteArray> urlStringList;
KUrl::List::ConstIterator uit = urls.constBegin();
const KUrl::List::ConstIterator uEnd = urls.constEnd();
for (; uit != uEnd ; ++uit) {
// Get each URL encoded in utf8 - and since we get it in escaped
// form on top of that, .toLatin1() is fine.
urlStringList.append((*uit).toMimeDataString().toLatin1());
}
QByteArray uriListData;
for (int i = 0, n = urlStringList.count(); i < n; ++i) {
uriListData += urlStringList.at(i);
if (i < n-1)
uriListData += "\r\n";
}
return uriListData;
}
static const char s_kdeUriListMime[] = "application/x-kde4-urilist";
void KUrl::List::populateMimeData( QMimeData* mimeData,
const KUrl::MetaDataMap& metaData,
MimeDataFlags flags ) const
{
mimeData->setData(QString::fromLatin1("text/uri-list"), uriListData(*this));
if ( ( flags & KUrl::NoTextExport ) == 0 )
{
QStringList prettyURLsList;
KUrl::List::ConstIterator uit = constBegin();
const KUrl::List::ConstIterator uEnd = constEnd();
for ( ; uit != uEnd ; ++uit ) {
QString prettyURL = (*uit).prettyUrl();
if ( (*uit).protocol() == QLatin1String("mailto") ) {
prettyURL = (*uit).path(); // remove mailto: when pasting into konsole
}
prettyURLsList.append( prettyURL );
}
QByteArray plainTextData = prettyURLsList.join(QString(QLatin1Char('\n'))).toLocal8Bit();
if( count() > 1 ) // terminate last line, unless it's the only line
plainTextData.append( "\n" );
mimeData->setData( QString::fromLatin1("text/plain"), plainTextData );
}
if ( !metaData.isEmpty() )
{
QByteArray metaDataData; // :)
for( KUrl::MetaDataMap::const_iterator it = metaData.begin(); it != metaData.end(); ++it )
{
metaDataData += it.key().toUtf8();
metaDataData += "$@@$";
metaDataData += it.value().toUtf8();
metaDataData += "$@@$";
}
mimeData->setData( QString::fromLatin1("application/x-kio-metadata"), metaDataData );
}
}
void KUrl::List::populateMimeData(const KUrl::List& mostLocalUrls,
QMimeData* mimeData,
const KUrl::MetaDataMap& metaData,
MimeDataFlags flags) const
{
// Export the most local urls as text/uri-list and plain text.
mostLocalUrls.populateMimeData(mimeData, metaData, flags);
mimeData->setData(QString::fromLatin1(s_kdeUriListMime), uriListData(*this));
}
bool KUrl::List::canDecode( const QMimeData *mimeData )
{
return mimeData->hasFormat(QString::fromLatin1("text/uri-list")) ||
mimeData->hasFormat(QString::fromLatin1(s_kdeUriListMime));
}
QStringList KUrl::List::mimeDataTypes()
{
return QStringList() << QString::fromLatin1(s_kdeUriListMime) << QString::fromLatin1("text/uri-list");
}
KUrl::List KUrl::List::fromMimeData(const QMimeData *mimeData,
DecodeOptions decodeOptions,
KUrl::MetaDataMap* metaData)
{
KUrl::List uris;
const char* firstMimeType = s_kdeUriListMime;
const char* secondMimeType = "text/uri-list";
if (decodeOptions == PreferLocalUrls) {
qSwap(firstMimeType, secondMimeType);
}
QByteArray payload = mimeData->data(QString::fromLatin1(firstMimeType));
if (payload.isEmpty())
payload = mimeData->data(QString::fromLatin1(secondMimeType));
if ( !payload.isEmpty() ) {
int c = 0;
const char* d = payload.constData();
while ( c < payload.size() && d[c] ) {
int f = c;
// Find line end
while (c < payload.size() && d[c] && d[c]!='\r'
&& d[c] != '\n')
c++;
QByteArray s( d+f, c-f );
if ( s[0] != '#' ) // non-comment?
uris.append( KUrl::fromMimeDataByteArray( s ) );
// Skip junk
while ( c < payload.size() && d[c] &&
( d[c] == '\n' || d[c] == '\r' ) )
++c;
}
}
if ( metaData )
{
const QByteArray metaDataPayload = mimeData->data(QLatin1String("application/x-kio-metadata"));
if ( !metaDataPayload.isEmpty() )
{
QString str = QString::fromUtf8( metaDataPayload );
Q_ASSERT(str.endsWith(QLatin1String("$@@$")));
str.truncate( str.length() - 4 );
const QStringList lst = str.split(QLatin1String("$@@$"));
QStringList::ConstIterator it = lst.begin();
bool readingKey = true; // true, then false, then true, etc.
QString key;
for ( ; it != lst.end(); ++it ) {
if ( readingKey )
key = *it;
else
metaData->insert( key, *it );
readingKey = !readingKey;
}
Q_ASSERT( readingKey ); // an odd number of items would be, well, odd ;-)
}
}
return uris;
}
KUrl::List KUrl::List::fromMimeData( const QMimeData *mimeData, KUrl::MetaDataMap* metaData )
{
return fromMimeData(mimeData, PreferKdeUrls, metaData);
}
KUrl::List::operator QVariant() const
{
return qVariantFromValue(*this);
}
KUrl::List::operator QList<QUrl>() const
{
QList<QUrl> list;
foreach(const KUrl& url, *this) {
list << url;
}
return list;
}
///
KUrl::KUrl()
: QUrl(), d(0)
{
}
KUrl::~KUrl()
{
}
KUrl::KUrl( const QString &str )
: QUrl(), d(0)
{
if ( !str.isEmpty() ) {
if ( str[0] == QLatin1Char('/') || str[0] == QLatin1Char('~') )
setPath( str );
else {
_setEncodedUrl( str.toUtf8() );
}
}
}
KUrl::KUrl( const char * str )
: QUrl(), d(0)
{
if ( str && str[0] ) {
if ( str[0] == '/' || str[0] == '~' )
setPath( QString::fromUtf8( str ) );
else
_setEncodedUrl( str );
}
}
KUrl::KUrl( const QByteArray& str )
: QUrl(), d(0)
{
if ( !str.isEmpty() ) {
if ( str[0] == '/' || str[0] == '~' )
setPath( QString::fromUtf8( str ) );
else
_setEncodedUrl( str );
}
}
KUrl::KUrl( const KUrl& _u )
: QUrl( _u ), d(0)
{
}
KUrl::KUrl( const QUrl &u )
: QUrl( u ), d(0)
{
}
KUrl::KUrl( const KUrl& _u, const QString& _rel_url )
: QUrl(), d(0)
{
#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);
}
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();
}
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();
}
KUrl KUrl::fromPath( const QString& text )
{
KUrl u;
u.setPath( text );
return u;
}
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() )
path = QDir::rootPath();
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 );
// WABA: Is this safe when "/../" is encoded with %?
//m_strPath_encoded = cleanpath(m_strPath_encoded, cleanDirSeparator, true);
}
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 )
{
#if 0
if (!m_strPath_encoded.isEmpty())
{
m_strPath_encoded = trailingSlash( _trailing, m_strPath_encoded );
}
#endif
const QString newPath = trailingSlash( trailing, path() );
if ( path() != newPath )
setPath( newPath );
}
QString KUrl::encodedPathAndQuery( AdjustPathOption trailing , const EncodedPathAndQueryOptions &options) const
{
QString encodedPath;
encodedPath = trailingSlash(trailing, QString::fromLatin1(QUrl::encodedPath()));
if ((options & AvoidEmptyPath) && encodedPath.isEmpty()) {
encodedPath.append(QLatin1Char('/'));
}
if (hasQuery()) {
return encodedPath + QLatin1Char('?') + QString::fromLatin1(encodedQuery());
} else {
return encodedPath;
}
}
#if 0
void KUrl::setEncodedPath( const QString& _txt, int encoding_hint )
{
m_strPath_encoded = _txt;
decode( m_strPath_encoded, m_strPath, m_strPath_encoded, encoding_hint );
// Throw away encoding for local files, makes file-operations faster.
if (m_strProtocol == "file")
m_strPath_encoded.clear();
if ( m_iUriMode == Auto )
m_iUriMode = URL;
}
#endif
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
{
return trailingSlash( trailing, QUrl::path() );
}
QString KUrl::toLocalFile( AdjustPathOption trailing ) const
{
if (hasHost() && isLocalFile()) {
KUrl urlWithoutHost(*this);
urlWithoutHost.setHost(QString());
return trailingSlash(trailing, urlWithoutHost.toLocalFile());
}
#ifdef __GNUC__
#warning FIXME: Remove #ifdef below once upstream bug, QTBUG-20322, is fixed. Also see BR# 194746.
#endif
if (isLocalFile()) {
return trailingSlash(trailing, QUrl::path());
}
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)));
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());
}
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));
}
return QLatin1String(toEncoded(None));
}
}
return QString::fromLatin1(toEncoded(trailing == RemoveTrailingSlash ? StripTrailingSlash : None));
}
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()
&& scheme().compare(QLatin1String("mailto"), Qt::CaseInsensitive) != 0))
result += QLatin1String("://");
else
result += QLatin1Char(':');
}
QString tmp = userName();
if (!tmp.isEmpty()) {
result += QString::fromLatin1(QUrl::toPercentEncoding(tmp));
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();
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());
}
if (hasFragment()) {
result += QLatin1Char('#');
result += toPrettyPercentEncoding(fragment(), true);
}
return result;
}
#if 0
QString KUrl::prettyUrl( int _trailing, AdjustementFlags _flags) const
{
QString u = prettyUrl(_trailing);
if (_flags & StripFileProtocol && u.startsWith("file://")) {
u.remove(0, 7);
}
return u;
}
#endif
QString KUrl::pathOrUrl() const
{
return pathOrUrl(LeaveTrailingSlash);
}
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( 0, KGlobal::locale()->fileEncodingMib() );
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 KUrl::fromMimeDataByteArray( const QByteArray& str )
{
if ( str.startsWith( "file:" ) ) // krazy:exclude=strings
return KUrl( str /*, QTextCodec::codecForLocale()->mibEnum()*/ );
return KUrl( str /*, 106*/ ); // 106 is mib enum for utf8 codec;
}
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;
#if 0
if (!m_strPath_encoded.isEmpty())
{
// This is hairy, we need the last unencoded slash.
// Count in the encoded string how many encoded slashes follow the last
// unencoded one.
int i = m_strPath_encoded.lastIndexOf( QLatin1Char('/'), len - 1 );
QString fileName_encoded = m_strPath_encoded.mid(i+1);
n += fileName_encoded.count("%2f", Qt::CaseInsensitive);
}
#endif
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;
}
//m_strPath_encoded.clear();
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(); //m_strPath_encoded.isEmpty() ? m_strPath : m_strPath_encoded;
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('/'));
}
if ( options & AppendTrailingSlash )
result = result.left( i + 1 );
else
result = result.left( i );
//if (!m_strPath_encoded.isEmpty())
// result = decode(result);
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 ?
if ( _dir[0] == QLatin1Char('/') )
{
//m_strPath_encoded.clear();
setPath( _dir );
setHTMLRef( QString() );
setEncodedQuery( QByteArray() );
return true;
}
// Users home directory on the local disk ?
if (_dir[0] == QLatin1Char('~') && scheme() == QLatin1String ("file"))
{
//m_strPath_encoded.clear();
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();
}
const List lst = split( *this );
return (*lst.begin()).fragment();
}
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());
}
void KUrl::_setEncodedUrl(const QByteArray& url)
{
setEncodedUrl(url, QUrl::TolerantMode);
if (!isValid()) // see unit tests referring to N183630/task 183874
setUrl(QString::fromUtf8(url), QUrl::TolerantMode);
}
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 ( scheme().isEmpty() )
setScheme( QLatin1String( "file" ) );
QString path = KShell::tildeExpand( _path );
if (path.isEmpty())
path = _path;
QUrl::setPath( path );
}
#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());
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());
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));
QString strQueryEncoded = QString::fromLatin1(encodedQuery());
if (!strQueryEncoded.isEmpty())
strQueryEncoded += QLatin1Char('&');
strQueryEncoded += item + value;
setEncodedQuery( strQueryEncoded.toLatin1() );
}
void KUrl::populateMimeData( QMimeData* mimeData,
const MetaDataMap& metaData,
MimeDataFlags flags ) const
{
KUrl::List lst( *this );
lst.populateMimeData( mimeData, metaData, flags );
}
bool KUrl::hasRef() const
{
return hasFragment();
}
void KUrl::setRef( const QString& fragment )
{
if ( fragment.isEmpty() ) // empty or null
setFragment( fragment );
else
setFragment( QUrl::fromPercentEncoding( fragment.toLatin1() ) );
}
QString KUrl::ref() const
{
if ( !hasFragment() )
return QString();
else
return QString::fromLatin1( encodedFragment() );
}
bool KUrl::isParentOf( const KUrl& u ) const
{
return QUrl::isParentOf( u ) || equals( u, CompareWithoutTrailingSlash );
}
uint qHash(const KUrl& kurl)
{
// qHash(kurl.url()) was the worse implementation possible, since QUrl::toEncoded()
// had to concatenate the bits of the url into the full url every time.
return qHash(kurl.protocol()) ^ qHash(kurl.path()) ^ qHash(kurl.fragment()) ^ qHash(kurl.query());
}