summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/src/snippets/code/src_corelib_io_qurl.cpp2
-rw-r--r--src/corelib/io/io.pri2
-rw-r--r--src/corelib/io/qurlquery.cpp745
-rw-r--r--src/corelib/io/qurlquery.h117
-rw-r--r--tests/auto/corelib/io/qurlquery/qurlquery.pro5
-rw-r--r--tests/auto/corelib/io/qurlquery/tst_qurlquery.cpp695
6 files changed, 1565 insertions, 1 deletions
diff --git a/doc/src/snippets/code/src_corelib_io_qurl.cpp b/doc/src/snippets/code/src_corelib_io_qurl.cpp
index ba17c02fb3..8fd4b8ee9f 100644
--- a/doc/src/snippets/code/src_corelib_io_qurl.cpp
+++ b/doc/src/snippets/code/src_corelib_io_qurl.cpp
@@ -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]
diff --git a/src/corelib/io/io.pri b/src/corelib/io/io.pri
index a70b3c9012..8602e60744 100644
--- a/src/corelib/io/io.pri
+++ b/src/corelib/io/io.pri
@@ -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 \
diff --git a/src/corelib/io/qurlquery.cpp b/src/corelib/io/qurlquery.cpp
new file mode 100644
index 0000000000..2f1975722f
--- /dev/null
+++ b/src/corelib/io/qurlquery.cpp
@@ -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
diff --git a/src/corelib/io/qurlquery.h b/src/corelib/io/qurlquery.h
new file mode 100644
index 0000000000..e2b28f78e7
--- /dev/null
+++ b/src/corelib/io/qurlquery.h
@@ -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
diff --git a/tests/auto/corelib/io/qurlquery/qurlquery.pro b/tests/auto/corelib/io/qurlquery/qurlquery.pro
new file mode 100644
index 0000000000..d344e48337
--- /dev/null
+++ b/tests/auto/corelib/io/qurlquery/qurlquery.pro
@@ -0,0 +1,5 @@
+QT = core core-private testlib
+TARGET = tst_qurlquery
+CONFIG += parallel_test testcase
+SOURCES += tst_qurlquery.cpp
+DEFINES += SRCDIR=\\\"$$PWD/\\\"
diff --git a/tests/auto/corelib/io/qurlquery/tst_qurlquery.cpp b/tests/auto/corelib/io/qurlquery/tst_qurlquery.cpp
new file mode 100644
index 0000000000..4ce621c4ba
--- /dev/null
+++ b/tests/auto/corelib/io/qurlquery/tst_qurlquery.cpp
@@ -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"