Long live QUrlQuery

This class is meant to replace the QUrl functionality that handled
key-value pairs in the query part of an URL. We therefore split the
URL parsing code from the code dealing with the pairs: QUrl now only
needs to deal with one encoded string, without knowing what it is.

Since it doesn't know how to decode the query, QUrl also becomes
limited in what it can decode. Following the letter of the RFC,
queries will not encode "gen-delims" nor "sub-delims" nor the plus
sign (+), thus allowing the most common delimiters options to remain
unchanged.

QUrlQuery has some undefined behaviour when it comes to empty query
keys. It may drop them or keep them; it may merge them, etc.

Change-Id: Ia61096fe5060b486196ffb8532e7494eff58fec1
Reviewed-by: Lars Knoll <lars.knoll@nokia.com>
This commit is contained in:
Thiago Macieira 2011-09-03 15:05:56 +02:00 committed by Qt by Nokia
parent b75aa795fe
commit 1aeb180386
6 changed files with 1565 additions and 1 deletions

View File

@ -68,7 +68,7 @@ sock.connectToHost(url.host(), url.port(80));
//! [4]
http://www.example.com/cgi-bin/drawgraph.cgi?type-pie/color-green
http://www.example.com/cgi-bin/drawgraph.cgi?type(pie)color(green)
//! [4]

View File

@ -28,6 +28,7 @@ HEADERS += \
io/qresource_iterator_p.h \
io/qstandardpaths.h \
io/qurl.h \
io/qurlquery.h \
io/qurltlds_p.h \
io/qtldurl_p.h \
io/qsettings.h \
@ -65,6 +66,7 @@ SOURCES += \
io/qresource_iterator.cpp \
io/qstandardpaths.cpp \
io/qurl.cpp \
io/qurlquery.cpp \
io/qurlrecode.cpp \
io/qsettings.cpp \
io/qfsfileengine.cpp \

View File

@ -0,0 +1,745 @@
/****************************************************************************
**
** Copyright (C) 2012 Intel Corporation
** Contact: http://www.qt-project.org/
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** GNU Lesser General Public License Usage
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this
** file. Please review the following information to ensure the GNU Lesser
** General Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU General
** Public License version 3.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of this
** file. Please review the following information to ensure the GNU General
** Public License version 3.0 requirements will be met:
** http://www.gnu.org/copyleft/gpl.html.
**
** Other Usage
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qurlquery.h"
QT_BEGIN_NAMESPACE
/*!
\class QUrlQuery
\brief The QUrlQuery class provides a way to manipulate a key-value pairs in
a URL's query.
\reentrant
\ingroup io
\ingroup network
\ingroup shared
It is used to parse the query strings found in URLs like the following:
\img qurl-querystring.png
Query strings like the above are used to transmit options in the URL and are
usually decoded into multiple key-value pairs. The one above would contain
two entries in its list, with keys "type" and "color". QUrlQuery can also be
used to create a query string suitable for use in QUrl::setQuery() from the
individual components of the query.
The most common way of parsing a query string is to initialize it in the
constructor by passing it the query string. Otherwise, the setQuery() method
can be used to set the query to be parsed. That method can also be used to
parse a query with non-standard delimiters, after having set them using the
setQueryDelimiters() function.
The encoded query string can be obtained again using query(). This will take
all the internally-stored items and encode the string using the delimiters.
\section1 Encoding
All of the getter methods in QUrlQuery support an optional parameter of type
QUrl::ComponentFormattingOptions, including query(), which dictate how to
encode the data in question. Regardless of the mode, the returned value must
still be considered a percent-encoded string, as there are certain values
which cannot be expressed in decoded form (like control characters, byte
sequences not decodable to UTF-8). For that reason, the percent character is
always represented by the string "%25".
\section2 Handling of spaces and plus ("+")
Web browsers usually encode spaces found in HTML FORM elements to a plus sign
("+") and plus signs to its percent-encoded form (%2B). However, the Internet
specifications governing URLs do not consider spaces and the plus character
equivalent.
For that reason, QUrlQuery never encodes the space character to "+" and will
never decode "+" to a space character. Instead, space characters will be
rendered "%20" in encoded form.
To support encoding like that of HTML forms, QUrlQuery also never decodes the
"%2B" sequence to a plus sign nor encode a plus sign. In fact, any "%2B" or
"+" sequences found in the keys, values, or query string are left exactly
like written (except for the uppercasing of "%2b" to "%2B").
\section1 Non-standard delimiters
By default, QUrlQuery uses an equal sign ("=") to separate a key from its
value, and an ampersand ("&") to separate key-value pairs from each other. It
is possible to change the delimiters that QUrlQuery uses for parsing and for
reconstructing the query by calling setQueryDelimiters().
Non-standard delimiters should be chosen from among what RFC 3986 calls
"sub-delimiters". They are:
\code
sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
/ "*" / "+" / "," / ";" / "="
\endcode
Use of other characters is not supported and may result in unexpected
behaviour. QUrlQuery does not verify that you passed a valid delimiter.
\sa QUrl
*/
typedef QList<QPair<QString, QString> > Map;
int qt_urlRecode(QString &appendTo, const QChar *begin, const QChar *end,
QUrl::ComponentFormattingOptions encoding, const ushort *tableModifications);
class QUrlQueryPrivate : public QSharedData
{
public:
QUrlQueryPrivate(const QString &query = QString())
: valueDelimiter(QUrlQuery::defaultQueryValueDelimiter()),
pairDelimiter(QUrlQuery::defaultQueryPairDelimiter())
{ if (!query.isEmpty()) setQuery(query); }
QString recodeFromUser(const QString &input) const;
QString recodeToUser(const QString &input, QUrl::ComponentFormattingOptions encoding) const;
void setQuery(const QString &query);
void addQueryItem(const QString &key, const QString &value)
{ itemList.append(qMakePair(recodeFromUser(key), recodeFromUser(value))); }
int findRecodedKey(const QString &key, int from = 0) const
{
for (int i = from; i < itemList.size(); ++i)
if (itemList.at(i).first == key)
return i;
return itemList.size();
}
Map::const_iterator findKey(const QString &key) const
{ return itemList.constBegin() + findRecodedKey(recodeFromUser(key)); }
Map::iterator findKey(const QString &key)
{ return itemList.begin() + findRecodedKey(recodeFromUser(key)); }
// use QMap so we end up sorting the items by key
Map itemList;
QChar valueDelimiter;
QChar pairDelimiter;
};
template<> void QSharedDataPointer<QUrlQueryPrivate>::detach()
{
if (d && d->ref.load() == 1)
return;
QUrlQueryPrivate *x = (d ? new QUrlQueryPrivate(*d)
: new QUrlQueryPrivate);
x->ref.ref();
if (d && !d->ref.deref())
delete d;
d = x;
}
// Here's how we do the encoding in QUrlQuery
// The RFC says these are the delimiters:
// gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
// sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
// / "*" / "+" / "," / ";" / "="
// And the definition of query is:
// query = *( pchar / "/" / "?" )
// pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
//
// The strict definition of query says that it can have unencoded any
// unreserved, sub-delim, ":", "@", "/" and "?". Or, by exclusion, excluded
// delimiters are "#", "[" and "]" -- if those are present, they must be
// percent-encoded.
//
// The internal storage in the Map is equivalent to PrettyDecoded. That means
// the getter methods, when called with the default encoding value, will not
// have to recode anything (except for toString()).
//
// The "+" sub-delimiter is always left untouched. We never encode "+" to "%2B"
// nor do we decode "%2B" to "+", no matter what the user asks.
//
// The rest of the delimiters are kept in their decoded forms and that's
// considered non-ambiguous. That includes the pair and value delimiters
// themselves.
//
// But when recreating the query string, in toString(), we must take care of
// the special delimiters: the pair and value delimiters and those that the
// definition says must be encoded ("#" / "[" / "]")
#define decode(x) ushort(x)
#define leave(x) ushort(0x100 | (x))
#define encode(x) ushort(0x200 | (x))
static const ushort prettyDecodedActions[] = { leave('+'), 0 };
inline QString QUrlQueryPrivate::recodeFromUser(const QString &input) const
{
// note: duplicated in setQuery()
QString output;
if (qt_urlRecode(output, input.constData(), input.constData() + input.length(),
QUrl::DecodeUnicode | QUrl::DecodeAllDelimiters | QUrl::DecodeSpaces,
prettyDecodedActions))
return output;
return input;
}
inline bool idempotentRecodeToUser(QUrl::ComponentFormattingOptions encoding)
{
return encoding == QUrl::PrettyDecoded || encoding == (QUrl::PrettyDecoded | QUrl::DecodeAllDelimiters);
}
inline QString QUrlQueryPrivate::recodeToUser(const QString &input, QUrl::ComponentFormattingOptions encoding) const
{
// our internal formats are stored in "PrettyDecoded" form
// and there are no ambiguous characters
if (idempotentRecodeToUser(encoding))
return input;
bool decodeUnambiguous = encoding & QUrl::DecodeUnambiguousDelimiters;
encoding &= ~QUrl::DecodeAllDelimiters;
if (decodeUnambiguous) {
QString output;
if (qt_urlRecode(output, input.constData(), input.constData() + input.length(),
encoding | QUrl::DecodeAllDelimiters, prettyDecodedActions))
return output;
return input;
}
// re-encode the gen-delims that the RFC says must be encoded the
// query delimiter pair; the non-delimiters will get encoded too
ushort actions[] = { encode(pairDelimiter.unicode()), encode(valueDelimiter.unicode()),
encode('#'), encode('['), encode(']'), 0 };
QString output;
if (qt_urlRecode(output, input.constData(), input.constData() + input.length(), encoding, actions))
return output;
return input;
}
void QUrlQueryPrivate::setQuery(const QString &query)
{
itemList.clear();
const QChar *pos = query.constData();
const QChar *const end = pos + query.size();
while (pos != end) {
const QChar *begin = pos;
const QChar *delimiter = 0;
while (pos != end) {
// scan for the component parts of this pair
if (!delimiter && pos->unicode() == valueDelimiter)
delimiter = pos;
if (pos->unicode() == pairDelimiter)
break;
++pos;
}
if (!delimiter)
delimiter = pos;
// pos is the end of this pair (the end of the string or the pair delimiter)
// delimiter points to the value delimiter or to the end of this pair
QString key;
if (!qt_urlRecode(key, begin, delimiter,
QUrl::DecodeUnicode | QUrl::DecodeAllDelimiters | QUrl::DecodeSpaces,
prettyDecodedActions))
key = QString(begin, delimiter - begin);
if (delimiter == pos) {
// the value delimiter wasn't found, store a null value
itemList.append(qMakePair(key, QString()));
} else if (delimiter + 1 == pos) {
// if the delimiter was found but the value is empty, store empty-but-not-null
itemList.append(qMakePair(key, QString(0, Qt::Uninitialized)));
} else {
QString value;
if (!qt_urlRecode(value, delimiter + 1, pos,
QUrl::DecodeUnicode | QUrl::DecodeAllDelimiters | QUrl::DecodeSpaces,
prettyDecodedActions))
value = QString(delimiter + 1, pos - delimiter - 1);
itemList.append(qMakePair(key, value));
}
if (pos != end)
++pos;
}
}
// allow QUrlQueryPrivate to detach from null
template <> inline QUrlQueryPrivate *
QSharedDataPointer<QUrlQueryPrivate>::clone()
{
return d ? new QUrlQueryPrivate(*d) : new QUrlQueryPrivate;
}
/*!
Constructs an empty QUrlQuery object. A query can be set afterwards by
calling setQuery() or items can be added by using addQueryItem().
\sa setQuery(), addQueryItem()
*/
QUrlQuery::QUrlQuery()
: d(0)
{
}
/*!
Constructs a QUrlQuery object and parses the \a queryString query string,
using the default query delimiters. To parse a query string using other
delimiters, you should first set them using setQueryDelimiters() and then
set the query with setQuery().
*/
QUrlQuery::QUrlQuery(const QString &queryString)
: d(queryString.isEmpty() ? 0 : new QUrlQueryPrivate(queryString))
{
}
/*!
Constructs a QUrlQuery object and parses the query string found in the \a
url URL, using the default query delimiters. To parse a query string using
other delimiters, you should first set them using setQueryDelimiters() and
then set the query with setQuery().
\sa QUrl::query()
*/
QUrlQuery::QUrlQuery(const QUrl &url)
: d(0)
{
// use internals to avoid unnecessary recoding
// ### FIXME: actually do it
if (url.hasQuery())
d = new QUrlQueryPrivate(QString::fromUtf8(url.encodedQuery()));
}
/*!
Copies the contents of the \a other QUrlQuery object, including the query
delimiters.
*/
QUrlQuery::QUrlQuery(const QUrlQuery &other)
: d(other.d)
{
}
/*!
Copies the contents of the \a other QUrlQuery object, including the query
delimiters.
*/
QUrlQuery &QUrlQuery::operator =(const QUrlQuery &other)
{
d = other.d;
return *this;
}
/*!
Destroys this QUrlQuery object.
*/
QUrlQuery::~QUrlQuery()
{
// d auto-deletes
}
/*!
Returns true if this object and the \a other object contain the same
contents, in the same order, and use the same query delimiters.
*/
bool QUrlQuery::operator ==(const QUrlQuery &other) const
{
if (d == other.d)
return true;
if (d && other.d)
return d->valueDelimiter == other.d->valueDelimiter &&
d->pairDelimiter == other.d->pairDelimiter &&
d->itemList == other.d->itemList;
return false;
}
/*!
Returns true if this QUrlQUery object contains no key-value pairs, such as
after being default-constructed or after parsing an empty query string.
\sa setQuery(), clear()
*/
bool QUrlQuery::isEmpty() const
{
return d ? d->itemList.isEmpty() : true;
}
/*!
\internal
*/
bool QUrlQuery::isDetached() const
{
return d && d->ref.load() == 1;
}
/*!
Clears this QUrlQuery object by removing all of the key-value pairs
currently stored. If the query delimiters have been changed, this function
will leave them with their changed values.
\sa isEmpty(), setQueryDelimiters()
*/
void QUrlQuery::clear()
{
if (d.constData())
d->itemList.clear();
}
/*!
Parses the query string in \a queryString and sets the internal items to
the values found there. If any delimiters have been specified with
setQueryDelimiters(), this function will use them instead of the default
delimiters to parse the string.
*/
void QUrlQuery::setQuery(const QString &queryString)
{
d->setQuery(queryString);
}
static void recodeAndAppend(QString &to, const QString &input,
QUrl::ComponentFormattingOptions encoding, const ushort *tableModifications)
{
if (!qt_urlRecode(to, input.constData(), input.constData() + input.length(), encoding, tableModifications))
to += input;
}
/*!
Returns the reconstructed query string, formed from the key-value pairs
currently stored in this QUrlQuery object and separated by the query
delimiters chosen for this object. The keys and values are encoded using
the options given by the \a encoding paramter.
For this function, the only ambiguous delimiter is the hash ("#"), as in
URLs it is used to separate the query string from the fragment that may
follow.
The order of the key-value pairs in the returned string is exactly the same
as in the original query.
\sa setQuery(), QUrl::setQuery(), QUrl::fragment(), \l{#Encoding}{Encoding}
*/
QString QUrlQuery::query(QUrl::ComponentFormattingOptions encoding) const
{
if (!d)
return QString();
// unlike the component encoding, for the whole query we need to modify a little:
// - the "#" character is ambiguous, so we decode it only in DecodeAllDelimiters mode
// - the query delimiter pair must always be encoded
// - the "[" and "]" sub-delimiters and the non-delimiters very on DecodeUnambiguousDelimiters
// so:
// - full encoding: encode the non-delimiters, the pair, "#", "[" and "]"
// - pretty decode: decode the non-delimiters, "[" and "]"; encode the pair and "#"
// - decode all: decode the non-delimiters, "[", "]", "#"; encode the pair
// start with what's always encoded
ushort tableActions[7] = {
leave('+'), // 0
encode(d->pairDelimiter.unicode()), // 1
encode(d->valueDelimiter.unicode()), // 2
};
if ((encoding & QUrl::DecodeAllDelimiters) == QUrl::DecodeAllDelimiters) {
// full decoding: we only encode the characters above
tableActions[3] = 0;
encoding |= QUrl::DecodeAllDelimiters;
} else {
tableActions[3] = encode('#');
if (encoding & QUrl::DecodeUnambiguousDelimiters) {
tableActions[4] = 0;
encoding |= QUrl::DecodeAllDelimiters;
} else {
tableActions[4] = encode('[');
tableActions[5] = encode(']');
tableActions[6] = 0;
encoding &= ~QUrl::DecodeAllDelimiters;
}
}
QString result;
Map::const_iterator it = d->itemList.constBegin();
Map::const_iterator end = d->itemList.constEnd();
{
int size = 0;
for ( ; it != end; ++it)
size += it->first.length() + 1 + it->second.length() + 1;
result.reserve(size + size / 4);
}
for (it = d->itemList.constBegin(); it != end; ++it) {
if (!result.isEmpty())
result += QChar(d->pairDelimiter);
recodeAndAppend(result, it->first, encoding, tableActions);
if (!it->second.isNull()) {
result += QChar(d->valueDelimiter);
recodeAndAppend(result, it->second, encoding, tableActions);
}
}
return result;
}
/*!
Sets the characters used for delimiting between keys and values,
and between key-value pairs in the URL's query string. The default
value delimiter is '=' and the default pair delimiter is '&'.
\img qurl-querystring.png
\a valueDelimiter will be used for separating keys from values,
and \a pairDelimiter will be used to separate key-value pairs.
Any occurrences of these delimiting characters in the encoded
representation of the keys and values of the query string are
percent encoded when returned in query().
If \a valueDelimiter is set to '(' and \a pairDelimiter is ')',
the above query string would instead be represented like this:
\snippet doc/src/snippets/code/src_corelib_io_qurl.cpp 4
\note Non-standard delimiters should be chosen from among what RFC 3986 calls
"sub-delimiters". They are:
\code
sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
/ "*" / "+" / "," / ";" / "="
\endcode
Use of other characters is not supported and may result in unexpected
behaviour. This method does not verify that you passed a valid delimiter.
\sa queryValueDelimiter(), queryPairDelimiter()
*/
void QUrlQuery::setQueryDelimiters(QChar valueDelimiter, QChar pairDelimiter)
{
d->valueDelimiter = valueDelimiter.unicode();
d->pairDelimiter = pairDelimiter.unicode();
}
/*!
Returns the character used to delimit between keys and values when
reconstructing the query string in query() or when parsing in setQuery().
\sa setQueryDelimiters(), queryPairDelimiter()
*/
QChar QUrlQuery::queryValueDelimiter() const
{
return d ? d->valueDelimiter : defaultQueryValueDelimiter();
}
/*!
Returns the character used to delimit between keys-value pairs when
reconstructing the query string in query() or when parsing in setQuery().
\sa setQueryDelimiters(), queryValueDelimiter()
*/
QChar QUrlQuery::queryPairDelimiter() const
{
return d ? d->pairDelimiter : defaultQueryPairDelimiter();
}
/*!
Sets the items in this QUrlQuery object to \a query. The order of the
elements in \a query is preserved.
\note This method does not treat spaces (ASCII 0x20) and plus ("+") signs
as the same, like HTML forms do. If you need spaces to be represented as
plus signs, use actual plus signs.
\sa queryItems(), isEmpty()
*/
void QUrlQuery::setQueryItems(const QList<QPair<QString, QString> > &query)
{
clear();
if (query.isEmpty())
return;
QUrlQueryPrivate *dd = d;
QList<QPair<QString, QString> >::const_iterator it = query.constBegin(),
end = query.constEnd();
for ( ; it != end; ++it)
dd->addQueryItem(it->first, it->second);
}
/*!
Returns the query string of the URL, as a map of keys and values, using the
options specified in \a encoding to encode the items. The order of the
elements is the same as the one found in the query string or set with
setQueryItems().
\sa setQueryItems(), \l{#Encoding}{Encoding}
*/
QList<QPair<QString, QString> > QUrlQuery::queryItems(QUrl::ComponentFormattingOptions encoding) const
{
if (!d)
return QList<QPair<QString, QString> >();
if (idempotentRecodeToUser(encoding))
return d->itemList;
QList<QPair<QString, QString> > result;
Map::const_iterator it = d->itemList.constBegin();
Map::const_iterator end = d->itemList.constEnd();
for ( ; it != end; ++it)
result << qMakePair(d->recodeToUser(it->first, encoding),
d->recodeToUser(it->second, encoding));
return result;
}
/*!
Returns true if there is a query string pair whose key is equal
to \a key from the URL.
\sa addQueryItem(), queryItemValue()
*/
bool QUrlQuery::hasQueryItem(const QString &key) const
{
if (!d)
return false;
return d->findKey(key) != d->itemList.constEnd();
}
/*!
Appends the pair \a key = \a value to the end of the query string of the
URL. This method does not overwrite existing items that might exist with
the same key.
\note This method does not treat spaces (ASCII 0x20) and plus ("+") signs
as the same, like HTML forms do. If you need spaces to be represented as
plus signs, use actual plus signs.
\sa hasQueryItem(), queryItemValue()
*/
void QUrlQuery::addQueryItem(const QString &key, const QString &value)
{
d->addQueryItem(key, value);
}
/*!
Returns the query value associated with key \a key from the URL, using the
options specified in \a encoding to encode the return value. If the key \a
key is not found, this function returns an empty string. If you need to
distinguish between an empty value and a non-existent key, you should check
for the key's presence first using hasQueryItem().
If the key \a key is multiply defined, this function will return the first
one found, in the order they were present in the query string or added
using addQueryItem().
\sa addQueryItem(), allQueryItemValues(), \l{#Encoding}{Encoding}
*/
QString QUrlQuery::queryItemValue(const QString &key, QUrl::ComponentFormattingOptions encoding) const
{
QString result;
if (d) {
Map::const_iterator it = d->findKey(key);
if (it != d->itemList.constEnd())
result = d->recodeToUser(it->second, encoding);
}
return result;
}
/*!
Returns the a list of query string values whose key is equal to \a key from
the URL, using the options specified in \a encoding to encode the return
value. If the key \a key is not found, this function returns an empty list.
\sa queryItemValue(), addQueryItem()
*/
QStringList QUrlQuery::allQueryItemValues(const QString &key, QUrl::ComponentFormattingOptions encoding) const
{
QStringList result;
if (d) {
QString encodedKey = d->recodeFromUser(key);
int idx = d->findRecodedKey(encodedKey);
while (idx < d->itemList.size()) {
result << d->recodeToUser(d->itemList.at(idx).second, encoding);
idx = d->findRecodedKey(encodedKey, idx + 1);
}
}
return result;
}
/*!
Removes the query string pair whose key is equal to \a key from the URL. If
there are multiple items with a key equal to \a key, it removes the first
item in the order they were present in the query string or added with
addQueryItem().
\sa removeAllQueryItems()
*/
void QUrlQuery::removeQueryItem(const QString &key)
{
if (d) {
Map::iterator it = d->findKey(key);
if (it != d->itemList.end())
d->itemList.erase(it);
}
}
/*!
Removes all the query string pairs whose key is equal to \a key
from the URL.
\sa removeQueryItem()
*/
void QUrlQuery::removeAllQueryItems(const QString &key)
{
if (d.constData()) {
QString encodedKey = d->recodeFromUser(key);
Map::iterator it = d->itemList.begin();
while (it != d->itemList.end()) {
if (it->first == encodedKey)
it = d->itemList.erase(it);
else
++it;
}
}
}
/*!
\fn QChar QUrlQuery::defaultQueryValueDelimiter()
Returns the default character for separating keys from values in the query,
an equal sign ("=").
\sa setQueryDelimiters(), queryValueDelimiter(), defaultQueryPairDelimiter()
*/
/*!
\fn QChar QUrlQuery::defaultQueryPairDelimiter()
Returns the default character for separating keys-value pairs from each
other, an ampersand ("&").
\sa setQueryDelimiters(), queryPairDelimiter(), defaultQueryValueDelimiter()
*/
QT_END_NAMESPACE

117
src/corelib/io/qurlquery.h Normal file
View File

@ -0,0 +1,117 @@
/****************************************************************************
**
** Copyright (C) 2012 Intel Corporation
** Contact: http://www.qt-project.org/
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** GNU Lesser General Public License Usage
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this
** file. Please review the following information to ensure the GNU Lesser
** General Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU General
** Public License version 3.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of this
** file. Please review the following information to ensure the GNU General
** Public License version 3.0 requirements will be met:
** http://www.gnu.org/copyleft/gpl.html.
**
** Other Usage
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QURLQUERY_H
#define QURLQUERY_H
#include <QtCore/qpair.h>
#include <QtCore/qshareddata.h>
#include <QtCore/qurl.h>
QT_BEGIN_HEADER
QT_BEGIN_NAMESPACE
class QUrlQueryPrivate;
class Q_CORE_EXPORT QUrlQuery
{
public:
QUrlQuery();
explicit QUrlQuery(const QUrl &url);
explicit QUrlQuery(const QString &queryString);
QUrlQuery(const QUrlQuery &other);
QUrlQuery &operator=(const QUrlQuery &other);
#ifdef Q_COMPILER_RVALUE_REFS
QUrlQuery &operator=(QUrlQuery &&other)
{ qSwap(d, other.d); return *this; }
#endif
~QUrlQuery();
bool operator==(const QUrlQuery &other) const;
bool operator!=(const QUrlQuery &other) const
{ return !(*this == other); }
void swap(QUrlQuery &other) { qSwap(d, other.d); }
bool isEmpty() const;
bool isDetached() const;
void clear();
QString query(QUrl::ComponentFormattingOptions encoding = QUrl::PrettyDecoded) const;
void setQuery(const QString &queryString);
QString toString(QUrl::ComponentFormattingOptions encoding = QUrl::PrettyDecoded) const
{ return query(encoding); }
void setQueryDelimiters(QChar valueDelimiter, QChar pairDelimiter);
QChar queryValueDelimiter() const;
QChar queryPairDelimiter() const;
void setQueryItems(const QList<QPair<QString, QString> > &query);
QList<QPair<QString, QString> > queryItems(QUrl::ComponentFormattingOptions encoding = QUrl::PrettyDecoded) const;
bool hasQueryItem(const QString &key) const;
void addQueryItem(const QString &key, const QString &value);
void removeQueryItem(const QString &key);
QString queryItemValue(const QString &key, QUrl::ComponentFormattingOptions encoding = QUrl::PrettyDecoded) const;
QStringList allQueryItemValues(const QString &key, QUrl::ComponentFormattingOptions encoding = QUrl::PrettyDecoded) const;
void removeAllQueryItems(const QString &key);
static QChar defaultQueryValueDelimiter()
{ return QChar(ushort('=')); }
static QChar defaultQueryPairDelimiter()
{ return QChar(ushort('&')); }
private:
friend class QUrl;
QSharedDataPointer<QUrlQueryPrivate> d;
public:
typedef QSharedDataPointer<QUrlQueryPrivate> DataPtr;
inline DataPtr &data_ptr() { return d; }
};
Q_DECLARE_TYPEINFO(QUrlQuery, Q_MOVABLE_TYPE);
Q_DECLARE_SHARED(QUrlQuery)
QT_END_NAMESPACE
QT_END_HEADER
#endif // QURLQUERY_H

View File

@ -0,0 +1,5 @@
QT = core core-private testlib
TARGET = tst_qurlquery
CONFIG += parallel_test testcase
SOURCES += tst_qurlquery.cpp
DEFINES += SRCDIR=\\\"$$PWD/\\\"

View File

@ -0,0 +1,695 @@
/****************************************************************************
**
** Copyright (C) 2012 Intel Corporation.
** Contact: http://www.qt-project.org/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** GNU Lesser General Public License Usage
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this
** file. Please review the following information to ensure the GNU Lesser
** General Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU General
** Public License version 3.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of this
** file. Please review the following information to ensure the GNU General
** Public License version 3.0 requirements will be met:
** http://www.gnu.org/copyleft/gpl.html.
**
** Other Usage
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtCore/QUrlQuery>
#include <QtTest/QtTest>
typedef QList<QPair<QString, QString> > QueryItems;
Q_DECLARE_METATYPE(QueryItems)
Q_DECLARE_METATYPE(QUrl::ComponentFormattingOptions)
class tst_QUrlQuery : public QObject
{
Q_OBJECT
public:
tst_QUrlQuery()
{
qRegisterMetaType<QueryItems>();
}
private Q_SLOTS:
void constructing();
void addRemove();
void multiAddRemove();
void multiplyAddSamePair();
void setQueryItems_data();
void setQueryItems();
void basicParsing_data();
void basicParsing();
void reconstructQuery_data();
void reconstructQuery();
void encodedSetQueryItems_data();
void encodedSetQueryItems();
void encodedParsing_data();
void encodedParsing();
void differentDelimiters();
};
static QString prettyElement(const QString &key, const QString &value)
{
QString result;
if (key.isNull())
result += "null -> ";
else
result += '"' % key % "\" -> ";
if (value.isNull())
result += "null";
else
result += '"' % value % '"';
return result;
}
static QString prettyPair(QList<QPair<QString, QString> >::const_iterator it)
{
return prettyElement(it->first, it->second);
}
template <typename T>
static QByteArray prettyList(const T &items)
{
QString result = "(";
bool first = true;
typename T::const_iterator it = items.constBegin();
for ( ; it != items.constEnd(); ++it) {
if (!first)
result += ", ";
first = false;
result += prettyPair(it);
}
result += ")";
return result.toLocal8Bit();
}
static bool compare(const QList<QPair<QString, QString> > &actual, const QueryItems &expected,
const char *actualStr, const char *expectedStr, const char *file, int line)
{
return QTest::compare_helper(actual == expected, "Compared values are not the same",
qstrdup(prettyList(actual)), qstrdup(prettyList(expected).data()),
actualStr, expectedStr, file, line);
}
#define COMPARE_ITEMS(actual, expected) \
do { \
if (!compare(actual, expected, #actual, #expected, __FILE__, __LINE__)) \
return; \
} while (0)
inline QueryItems operator+(QueryItems items, const QPair<QString, QString> &pair)
{
// items is already a copy
items.append(pair);
return items;
}
inline QueryItems operator+(const QPair<QString, QString> &pair, QueryItems items)
{
// items is already a copy
items.prepend(pair);
return items;
}
inline QPair<QString, QString> qItem(const QString &first, const QString &second)
{
return qMakePair(first, second);
}
inline QPair<QString, QString> qItem(const char *first, const QString &second)
{
return qMakePair(QString::fromUtf8(first), second);
}
inline QPair<QString, QString> qItem(const char *first, const char *second)
{
return qMakePair(QString::fromUtf8(first), QString::fromUtf8(second));
}
inline QPair<QString, QString> qItem(const QString &first, const char *second)
{
return qMakePair(first, QString::fromUtf8(second));
}
static QUrlQuery emptyQuery()
{
return QUrlQuery();
}
void tst_QUrlQuery::constructing()
{
QUrlQuery empty;
QVERIFY(empty.isEmpty());
QCOMPARE(empty.queryPairDelimiter(), QUrlQuery::defaultQueryPairDelimiter());
QCOMPARE(empty.queryValueDelimiter(), QUrlQuery::defaultQueryValueDelimiter());
// undefined whether it is detached, but don't crash
QVERIFY(empty.isDetached() || !empty.isDetached());
empty.clear();
QVERIFY(empty.isEmpty());
{
QUrlQuery copy(empty);
QVERIFY(copy.isEmpty());
QVERIFY(!copy.isDetached());
QVERIFY(copy == empty);
QVERIFY(!(copy != empty));
copy = empty;
QVERIFY(copy == empty);
copy = QUrlQuery();
QVERIFY(copy == empty);
}
{
QUrlQuery copy(emptyQuery());
QVERIFY(copy == empty);
}
QVERIFY(!empty.hasQueryItem("a"));
QVERIFY(empty.queryItemValue("a").isEmpty());
QVERIFY(empty.allQueryItemValues("a").isEmpty());
QVERIFY(!empty.hasQueryItem(""));
QVERIFY(empty.queryItemValue("").isEmpty());
QVERIFY(empty.allQueryItemValues("").isEmpty());
QVERIFY(!empty.hasQueryItem(QString()));
QVERIFY(empty.queryItemValue(QString()).isEmpty());
QVERIFY(empty.allQueryItemValues(QString()).isEmpty());
QVERIFY(empty.queryItems().isEmpty());
QUrlQuery other;
other.addQueryItem("a", "b");
QVERIFY(!other.isEmpty());
QVERIFY(other.isDetached());
QVERIFY(other != empty);
QVERIFY(!(other == empty));
QUrlQuery copy(other);
QVERIFY(copy == other);
copy.clear();
QVERIFY(copy.isEmpty());
QVERIFY(copy != other);
copy = other;
QVERIFY(!copy.isEmpty());
QVERIFY(copy == other);
copy = QUrlQuery();
QVERIFY(copy.isEmpty());
empty.setQueryDelimiters('(', ')');
QCOMPARE(empty.queryValueDelimiter(), QChar(QLatin1Char('(')));
QCOMPARE(empty.queryPairDelimiter(), QChar(QLatin1Char(')')));
}
void tst_QUrlQuery::addRemove()
{
QUrlQuery query;
{
// one item
query.addQueryItem("a", "b");
QVERIFY(!query.isEmpty());
QVERIFY(query.hasQueryItem("a"));
QCOMPARE(query.queryItemValue("a"), QString("b"));
QCOMPARE(query.allQueryItemValues("a"), QStringList() << "b");
QList<QPair<QString, QString> > allItems = query.queryItems();
QCOMPARE(allItems.count(), 1);
QCOMPARE(allItems.at(0).first, QString("a"));
QCOMPARE(allItems.at(0).second, QString("b"));
}
QUrlQuery original = query;
{
// two items
query.addQueryItem("c", "d");
QVERIFY(query.hasQueryItem("a"));
QCOMPARE(query.queryItemValue("a"), QString("b"));
QCOMPARE(query.allQueryItemValues("a"), QStringList() << "b");
QVERIFY(query.hasQueryItem("c"));
QCOMPARE(query.queryItemValue("c"), QString("d"));
QCOMPARE(query.allQueryItemValues("c"), QStringList() << "d");
QList<QPair<QString, QString> > allItems = query.queryItems();
QCOMPARE(allItems.count(), 2);
QVERIFY(allItems.contains(qItem("a", "b")));
QVERIFY(allItems.contains(qItem("c", "d")));
QVERIFY(query != original);
QVERIFY(!(query == original));
}
{
// remove an item that isn't there
QUrlQuery copy = query;
query.removeQueryItem("e");
QCOMPARE(query, copy);
}
{
// remove an item
query.removeQueryItem("c");
QVERIFY(query.hasQueryItem("a"));
QCOMPARE(query.queryItemValue("a"), QString("b"));
QCOMPARE(query.allQueryItemValues("a"), QStringList() << "b");
QList<QPair<QString, QString> > allItems = query.queryItems();
QCOMPARE(allItems.count(), 1);
QCOMPARE(allItems.at(0).first, QString("a"));
QCOMPARE(allItems.at(0).second, QString("b"));
QVERIFY(query == original);
QVERIFY(!(query != original));
}
{
// add an item with en empty value
QString emptyButNotNull(0, Qt::Uninitialized);
QVERIFY(emptyButNotNull.isEmpty());
QVERIFY(!emptyButNotNull.isNull());
query.addQueryItem("e", "");
QVERIFY(query.hasQueryItem("a"));
QCOMPARE(query.queryItemValue("a"), QString("b"));
QCOMPARE(query.allQueryItemValues("a"), QStringList() << "b");
QVERIFY(query.hasQueryItem("e"));
QCOMPARE(query.queryItemValue("e"), emptyButNotNull);
QCOMPARE(query.allQueryItemValues("e"), QStringList() << emptyButNotNull);
QList<QPair<QString, QString> > allItems = query.queryItems();
QCOMPARE(allItems.count(), 2);
QVERIFY(allItems.contains(qItem("a", "b")));
QVERIFY(allItems.contains(qItem("e", emptyButNotNull)));
QVERIFY(query != original);
QVERIFY(!(query == original));
}
{
// remove the items
query.removeQueryItem("a");
query.removeQueryItem("e");
QVERIFY(query.isEmpty());
}
}
void tst_QUrlQuery::multiAddRemove()
{
QUrlQuery query;
{
// one item, two values
query.addQueryItem("a", "b");
query.addQueryItem("a", "c");
QVERIFY(!query.isEmpty());
QVERIFY(query.hasQueryItem("a"));
// returns the first one
QVERIFY(query.queryItemValue("a") == "b");
// order is the order we set them in
QVERIFY(query.allQueryItemValues("a") == QStringList() << "b" << "c");
}
{
// add another item, two values
query.addQueryItem("A", "B");
query.addQueryItem("A", "C");
QVERIFY(query.hasQueryItem("A"));
QVERIFY(query.hasQueryItem("a"));
QVERIFY(query.queryItemValue("a") == "b");
QVERIFY(query.allQueryItemValues("a") == QStringList() << "b" << "c");
QVERIFY(query.queryItemValue("A") == "B");
QVERIFY(query.allQueryItemValues("A") == QStringList() << "B" << "C");
}
{
// remove one of the original items
query.removeQueryItem("a");
QVERIFY(query.hasQueryItem("a"));
// it must have removed the first one
QVERIFY(query.queryItemValue("a") == "c");
}
{
// remove the items we added later
query.removeAllQueryItems("A");
QVERIFY(!query.isEmpty());
QVERIFY(!query.hasQueryItem("A"));
}
{
// add one element to the current, then remove them
query.addQueryItem("a", "d");
query.removeAllQueryItems("a");
QVERIFY(!query.hasQueryItem("a"));
QVERIFY(query.isEmpty());
}
}
void tst_QUrlQuery::multiplyAddSamePair()
{
QUrlQuery query;
query.addQueryItem("a", "a");
query.addQueryItem("a", "a");
QCOMPARE(query.allQueryItemValues("a"), QStringList() << "a" << "a");
query.addQueryItem("a", "a");
QCOMPARE(query.allQueryItemValues("a"), QStringList() << "a" << "a" << "a");
query.removeQueryItem("a");
QCOMPARE(query.allQueryItemValues("a"), QStringList() << "a" << "a");
}
void tst_QUrlQuery::setQueryItems_data()
{
QTest::addColumn<QueryItems>("items");
QString emptyButNotNull(0, Qt::Uninitialized);
QTest::newRow("empty") << QueryItems();
QTest::newRow("1-novalue") << (QueryItems() << qItem("a", QString()));
QTest::newRow("1-emptyvalue") << (QueryItems() << qItem("a", emptyButNotNull));
QueryItems list;
list << qItem("a", "b");
QTest::newRow("1-value") << list;
QTest::newRow("1-multi") << (list + qItem("a", "c"));
QTest::newRow("1-duplicated") << (list + qItem("a", "b"));
list << qItem("c", "d");
QTest::newRow("2") << list;
list << qItem("c", "e");
QTest::newRow("2-multi") << list;
}
void tst_QUrlQuery::setQueryItems()
{
QFETCH(QueryItems, items);
QUrlQuery query;
QueryItems::const_iterator it = items.constBegin();
for ( ; it != items.constEnd(); ++it)
query.addQueryItem(it->first, it->second);
COMPARE_ITEMS(query.queryItems(), items);
query.clear();
query.setQueryItems(items);
COMPARE_ITEMS(query.queryItems(), items);
}
void tst_QUrlQuery::basicParsing_data()
{
QTest::addColumn<QString>("queryString");
QTest::addColumn<QueryItems>("items");
QString emptyButNotNull(0, Qt::Uninitialized);
QTest::newRow("null") << QString() << QueryItems();
QTest::newRow("empty") << "" << QueryItems();
QTest::newRow("1-novalue") << "a" << (QueryItems() << qItem("a", QString()));
QTest::newRow("1-emptyvalue") << "a=" << (QueryItems() << qItem("a", emptyButNotNull));
QTest::newRow("1-value") << "a=b" << (QueryItems() << qItem("a", "b"));
// some longer keys
QTest::newRow("1-longkey-novalue") << "thisisalongkey" << (QueryItems() << qItem("thisisalongkey", QString()));
QTest::newRow("1-longkey-emptyvalue") << "thisisalongkey=" << (QueryItems() << qItem("thisisalongkey", emptyButNotNull));
QTest::newRow("1-longkey-value") << "thisisalongkey=b" << (QueryItems() << qItem("thisisalongkey", "b"));
// longer values
QTest::newRow("1-longvalue-value") << "a=thisisalongreasonablyvalue"
<< (QueryItems() << qItem("a", "thisisalongreasonablyvalue"));
QTest::newRow("1-longboth-value") << "thisisalongkey=thisisalongreasonablyvalue"
<< (QueryItems() << qItem("thisisalongkey", "thisisalongreasonablyvalue"));
// two or more entries
QueryItems baselist;
baselist << qItem("a", "b") << qItem("c", "d");
QTest::newRow("2-ab-cd") << "a=b&c=d" << baselist;
QTest::newRow("2-cd-ab") << "c=d&a=b" << (QueryItems() << qItem("c", "d") << qItem("a", "b"));
// the same entry multiply defined
QTest::newRow("2-a-a") << "a&a" << (QueryItems() << qItem("a", QString()) << qItem("a", QString()));
QTest::newRow("2-ab-a") << "a=b&a" << (QueryItems() << qItem("a", "b") << qItem("a", QString()));
QTest::newRow("2-ab-ab") << "a=b&a=b" << (QueryItems() << qItem("a", "b") << qItem("a", "b"));
QTest::newRow("2-ab-ac") << "a=b&a=c" << (QueryItems() << qItem("a", "b") << qItem("a", "c"));
QPair<QString, QString> novalue = qItem("somekey", QString());
QueryItems list2 = baselist + novalue;
QTest::newRow("3-novalue-ab-cd") << "somekey&a=b&c=d" << (novalue + baselist);
QTest::newRow("3-ab-novalue-cd") << "a=b&somekey&c=d" << (QueryItems() << qItem("a", "b") << novalue << qItem("c", "d"));
QTest::newRow("3-ab-cd-novalue") << "a=b&c=d&somekey" << list2;
list2 << qItem("otherkeynovalue", QString());
QTest::newRow("4-ab-cd-novalue-novalue") << "a=b&c=d&somekey&otherkeynovalue" << list2;
QPair<QString, QString> emptyvalue = qItem("somekey", emptyButNotNull);
list2 = baselist + emptyvalue;
QTest::newRow("3-emptyvalue-ab-cd") << "somekey=&a=b&c=d" << (emptyvalue + baselist);
QTest::newRow("3-ab-emptyvalue-cd") << "a=b&somekey=&c=d" << (QueryItems() << qItem("a", "b") << emptyvalue << qItem("c", "d"));
QTest::newRow("3-ab-cd-emptyvalue") << "a=b&c=d&somekey=" << list2;
}
void tst_QUrlQuery::basicParsing()
{
QFETCH(QString, queryString);
QFETCH(QueryItems, items);
QUrlQuery query(queryString);
QCOMPARE(query.isEmpty(), items.isEmpty());
COMPARE_ITEMS(query.queryItems(), items);
}
void tst_QUrlQuery::reconstructQuery_data()
{
QTest::addColumn<QString>("queryString");
QTest::addColumn<QueryItems>("items");
QString emptyButNotNull(0, Qt::Uninitialized);
QTest::newRow("null") << QString() << QueryItems();
QTest::newRow("empty") << "" << QueryItems();
QTest::newRow("1-novalue") << "a" << (QueryItems() << qItem("a", QString()));
QTest::newRow("1-emptyvalue") << "a=" << (QueryItems() << qItem("a", emptyButNotNull));
QTest::newRow("1-value") << "a=b" << (QueryItems() << qItem("a", "b"));
// some longer keys
QTest::newRow("1-longkey-novalue") << "thisisalongkey" << (QueryItems() << qItem("thisisalongkey", QString()));
QTest::newRow("1-longkey-emptyvalue") << "thisisalongkey=" << (QueryItems() << qItem("thisisalongkey", emptyButNotNull));
QTest::newRow("1-longkey-value") << "thisisalongkey=b" << (QueryItems() << qItem("thisisalongkey", "b"));
// longer values
QTest::newRow("1-longvalue-value") << "a=thisisalongreasonablyvalue"
<< (QueryItems() << qItem("a", "thisisalongreasonablyvalue"));
QTest::newRow("1-longboth-value") << "thisisalongkey=thisisalongreasonablyvalue"
<< (QueryItems() << qItem("thisisalongkey", "thisisalongreasonablyvalue"));
// two or more entries
QueryItems baselist;
baselist << qItem("a", "b") << qItem("c", "d");
QTest::newRow("2-ab-cd") << "a=b&c=d" << baselist;
// the same entry multiply defined
QTest::newRow("2-a-a") << "a&a" << (QueryItems() << qItem("a", QString()) << qItem("a", QString()));
QTest::newRow("2-ab-ab") << "a=b&a=b" << (QueryItems() << qItem("a", "b") << qItem("a", "b"));
QTest::newRow("2-ab-ac") << "a=b&a=c" << (QueryItems() << qItem("a", "b") << qItem("a", "c"));
QTest::newRow("2-ac-ab") << "a=c&a=b" << (QueryItems() << qItem("a", "c") << qItem("a", "b"));
QTest::newRow("2-ab-cd") << "a=b&c=d" << (QueryItems() << qItem("a", "b") << qItem("c", "d"));
QTest::newRow("2-cd-ab") << "c=d&a=b" << (QueryItems() << qItem("c", "d") << qItem("a", "b"));
QueryItems list2 = baselist + qItem("somekey", QString());
QTest::newRow("3-ab-cd-novalue") << "a=b&c=d&somekey" << list2;
list2 << qItem("otherkeynovalue", QString());
QTest::newRow("4-ab-cd-novalue-novalue") << "a=b&c=d&somekey&otherkeynovalue" << list2;
list2 = baselist + qItem("somekey", emptyButNotNull);
QTest::newRow("3-ab-cd-emptyvalue") << "a=b&c=d&somekey=" << list2;
}
void tst_QUrlQuery::reconstructQuery()
{
QFETCH(QString, queryString);
QFETCH(QueryItems, items);
QUrlQuery query;
// add the items
for (QueryItems::ConstIterator it = items.constBegin(); it != items.constEnd(); ++it) {
query.addQueryItem(it->first, it->second);
}
QCOMPARE(query.query(), queryString);
}
void tst_QUrlQuery::encodedSetQueryItems_data()
{
QTest::addColumn<QString>("queryString");
QTest::addColumn<QString>("key");
QTest::addColumn<QString>("value");
QTest::addColumn<QUrl::ComponentFormattingOptions>("encoding");
QTest::addColumn<QString>("expectedQuery");
QTest::addColumn<QString>("expectedKey");
QTest::addColumn<QString>("expectedValue");
typedef QUrl::ComponentFormattingOptions F;
QTest::newRow("nul") << "f%00=bar%00" << "f%00" << "bar%00" << F(QUrl::PrettyDecoded)
<< "f%00=bar%00" << "f%00" << "bar%00";
QTest::newRow("non-decodable-1") << "foo%01%7f=b%1ar" << "foo%01%7f" << "b%1ar" << F(QUrl::PrettyDecoded)
<< "foo%01%7F=b%1Ar" << "foo%01%7F" << "b%1Ar";
QTest::newRow("non-decodable-2") << "foo\x01\x7f=b\x1ar" << "foo\x01\x7f" << "b\x1Ar" << F(QUrl::PrettyDecoded)
<< "foo%01%7F=b%1Ar" << "foo%01%7F" << "b%1Ar";
QTest::newRow("space") << "%20=%20" << "%20" << "%20" << F(QUrl::PrettyDecoded)
<< " = " << " " << " ";
QTest::newRow("encode-space") << " = " << " " << " " << F(QUrl::FullyEncoded)
<< "%20=%20" << "%20" << "%20";
QTest::newRow("non-delimiters") << "%3C%5C%3E=%7B%7C%7D%5E%60" << "%3C%5C%3E" << "%7B%7C%7D%5E%60" << F(QUrl::PrettyDecoded)
<< "<\\>={|}^`" << "<\\>" << "{|}^`";
QTest::newRow("encode-non-delimiters") << "<\\>={|}^`" << "<\\>" << "{|}^`" << F(QUrl::FullyEncoded)
<< "%3C%5C%3E=%7B%7C%7D%5E%60" << "%3C%5C%3E" << "%7B%7C%7D%5E%60";
QTest::newRow("equals") << "%3D=%3D" << "%3D" << "%3D" << F(QUrl::PrettyDecoded)
<< "%3D=%3D" << "=" << "=";
QTest::newRow("equals-2") << "%3D==" << "=" << "=" << F(QUrl::PrettyDecoded)
<< "%3D=%3D" << "=" << "=";
QTest::newRow("ampersand") << "%26=%26" << "%26" << "%26" << F(QUrl::PrettyDecoded)
<< "%26=%26" << "&" << "&";
QTest::newRow("hash") << "#=#" << "%23" << "%23" << F(QUrl::PrettyDecoded)
<< "%23=%23" << "#" << "#";
QTest::newRow("decode-hash") << "%23=%23" << "%23" << "%23" << F(QUrl::DecodeAllDelimiters)
<< "#=#" << "#" << "#";
QTest::newRow("percent") << "%25=%25" << "%25" << "%25" << F(QUrl::PrettyDecoded)
<< "%25=%25" << "%25" << "%25";
QTest::newRow("bad-percent-1") << "%=%" << "%" << "%" << F(QUrl::PrettyDecoded)
<< "%25=%25" << "%25" << "%25";
QTest::newRow("bad-percent-2") << "%2=%2" << "%2" << "%2" << F(QUrl::PrettyDecoded)
<< "%252=%252" << "%252" << "%252";
QTest::newRow("plus") << "+=+" << "+" << "+" << F(QUrl::PrettyDecoded)
<< "+=+" << "+" << "+";
QTest::newRow("2b") << "%2b=%2b" << "%2b" << "%2b" << F(QUrl::PrettyDecoded)
<< "%2B=%2B" << "%2B" << "%2B";
// plus signs must not be touched
QTest::newRow("encode-plus") << "+=+" << "+" << "+" << F(QUrl::FullyEncoded)
<< "+=+" << "+" << "+";
QTest::newRow("decode-2b") << "%2b=%2b" << "%2b" << "%2b" << F(QUrl::DecodeAllDelimiters)
<< "%2B=%2B" << "%2B" << "%2B";
QTest::newRow("unicode") << "q=R%C3%a9sum%c3%A9" << "q" << "R%C3%a9sum%c3%A9" << F(QUrl::PrettyDecoded)
<< QString::fromUtf8("q=R\xc3\xa9sum\xc3\xa9") << "q" << QString::fromUtf8("R\xc3\xa9sum\xc3\xa9");
QTest::newRow("encode-unicode") << QString::fromUtf8("q=R\xc3\xa9sum\xc3\xa9") << "q" << QString::fromUtf8("R\xc3\xa9sum\xc3\xa9")
<< F(QUrl::FullyEncoded)
<< "q=R%C3%A9sum%C3%A9" << "q" << "R%C3%A9sum%C3%A9";
}
void tst_QUrlQuery::encodedSetQueryItems()
{
QFETCH(QString, key);
QFETCH(QString, value);
QFETCH(QString, expectedQuery);
QFETCH(QString, expectedKey);
QFETCH(QString, expectedValue);
QFETCH(QUrl::ComponentFormattingOptions, encoding);
QUrlQuery query;
query.addQueryItem(key, value);
COMPARE_ITEMS(query.queryItems(encoding), QueryItems() << qItem(expectedKey, expectedValue));
QCOMPARE(query.query(encoding), expectedQuery);
}
void tst_QUrlQuery::encodedParsing_data()
{
encodedSetQueryItems_data();
}
void tst_QUrlQuery::encodedParsing()
{
QFETCH(QString, queryString);
QFETCH(QString, expectedQuery);
QFETCH(QString, expectedKey);
QFETCH(QString, expectedValue);
QFETCH(QUrl::ComponentFormattingOptions, encoding);
QUrlQuery query(queryString);
COMPARE_ITEMS(query.queryItems(encoding), QueryItems() << qItem(expectedKey, expectedValue));
QCOMPARE(query.query(encoding), expectedQuery);
}
void tst_QUrlQuery::differentDelimiters()
{
QUrlQuery query;
query.setQueryDelimiters('(', ')');
{
// parse:
query.setQuery("foo(bar)hello(world)");
QueryItems expected;
expected << qItem("foo", "bar") << qItem("hello", "world");
COMPARE_ITEMS(query.queryItems(), expected);
COMPARE_ITEMS(query.queryItems(QUrl::FullyEncoded), expected);
COMPARE_ITEMS(query.queryItems(QUrl::DecodeAllDelimiters), expected);
}
{
// reconstruct:
// note the final ')' is missing because there are no further items
QCOMPARE(query.query(), QString("foo(bar)hello(world"));
}
{
// set items containing the new delimiters and the old ones
query.clear();
query.addQueryItem("z(=)", "y(&)");
QCOMPARE(query.query(), QString("z%28=%29(y%28&%29"));
QUrlQuery copy = query;
QCOMPARE(query.query(), QString("z%28=%29(y%28&%29"));
copy.setQueryDelimiters(QUrlQuery::defaultQueryValueDelimiter(),
QUrlQuery::defaultQueryPairDelimiter());
QCOMPARE(copy.query(), QString("z(%3D)=y(%26)"));
}
}
QTEST_APPLESS_MAIN(tst_QUrlQuery)
#include "tst_qurlquery.moc"