1 | // Copyright (C) 2021 Intel Corporation. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qurlquery.h" |
5 | #include "qurl_p.h" |
6 | |
7 | #include <QtCore/qhashfunctions.h> |
8 | #include <QtCore/qstringlist.h> |
9 | |
10 | #include <algorithm> |
11 | |
12 | QT_BEGIN_NAMESPACE |
13 | |
14 | /*! |
15 | \class QUrlQuery |
16 | \inmodule QtCore |
17 | \since 5.0 |
18 | |
19 | \brief The QUrlQuery class provides a way to manipulate a key-value pairs in |
20 | a URL's query. |
21 | |
22 | \reentrant |
23 | \ingroup io |
24 | \ingroup network |
25 | \ingroup shared |
26 | |
27 | It is used to parse the query strings found in URLs like the following: |
28 | |
29 | \image qurl-querystring.png |
30 | |
31 | Query strings like the above are used to transmit options in the URL and are |
32 | usually decoded into multiple key-value pairs. The one above would contain |
33 | two entries in its list, with keys "type" and "color". QUrlQuery can also be |
34 | used to create a query string suitable for use in QUrl::setQuery() from the |
35 | individual components of the query. |
36 | |
37 | The most common way of parsing a query string is to initialize it in the |
38 | constructor by passing it the query string. Otherwise, the setQuery() method |
39 | can be used to set the query to be parsed. That method can also be used to |
40 | parse a query with non-standard delimiters, after having set them using the |
41 | setQueryDelimiters() function. |
42 | |
43 | The encoded query string can be obtained again using query(). This will take |
44 | all the internally-stored items and encode the string using the delimiters. |
45 | |
46 | \section1 Encoding |
47 | |
48 | All of the getter methods in QUrlQuery support an optional parameter of type |
49 | QUrl::ComponentFormattingOptions, including query(), which dictate how to |
50 | encode the data in question. Except for QUrl::FullyDecoded, the returned value must |
51 | still be considered a percent-encoded string, as there are certain values |
52 | which cannot be expressed in decoded form (like control characters, byte |
53 | sequences not decodable to UTF-8). For that reason, the percent character is |
54 | always represented by the string "%25". |
55 | |
56 | All of the setter methods and the query methods like hasQueryItem() in |
57 | QUrlQuery take encoded forms only. Unlike in QUrl, there's no optional |
58 | parameter to specify that the strings being passed are decoded. If |
59 | improperly-encoded strings are passed to the setter or query methods, |
60 | QUrlQuery will attempt to recover instead of failing. That is to say, all |
61 | functions in this class parse their string arguments as if the |
62 | QUrl::TolerantMode decoding mode was specified. |
63 | |
64 | Application code should strive to always ensure proper encoding and not rely |
65 | on TolerantMode parsing fixing the strings. Notably, all user input must be |
66 | first percent-encoded using QUrl::toPercentEncoding() or similar functions |
67 | before being passed to the functions in this class. |
68 | |
69 | \section2 Handling of spaces and plus ("+") |
70 | |
71 | Web browsers usually encode spaces found in HTML FORM elements to a plus sign |
72 | ("+") and plus signs to its percent-encoded form (%2B). However, the Internet |
73 | specifications governing URLs do not consider spaces and the plus character |
74 | equivalent. |
75 | |
76 | For that reason, QUrlQuery never encodes the space character to "+" and will |
77 | never decode "+" to a space character. Instead, space characters will be |
78 | rendered "%20" in encoded form. |
79 | |
80 | To support encoding like that of HTML forms, QUrlQuery also never decodes the |
81 | "%2B" sequence to a plus sign nor encode a plus sign. In fact, any "%2B" or |
82 | "+" sequences found in the keys, values, or query string are left exactly |
83 | like written (except for the uppercasing of "%2b" to "%2B"). |
84 | |
85 | \section2 Full decoding |
86 | |
87 | With QUrl::FullyDecoded formatting, all percent-encoded sequences will be |
88 | decoded fully and the '%' character is used to represent itself. |
89 | QUrl::FullyDecoded should be used with care, since it may cause data loss. |
90 | See the documentation of QUrl::FullyDecoded for information on what data may |
91 | be lost. |
92 | |
93 | This formatting mode should be used only when dealing with text presented to |
94 | the user in contexts where percent-encoding is not desired. Note that |
95 | QUrlQuery setters and query methods do not support the counterpart |
96 | QUrl::DecodedMode parsing, so using QUrl::FullyDecoded to obtain a listing of |
97 | keys may result in keys not found in the object. |
98 | |
99 | \section1 Non-standard delimiters |
100 | |
101 | By default, QUrlQuery uses an equal sign ("=") to separate a key from its |
102 | value, and an ampersand ("&") to separate key-value pairs from each other. It |
103 | is possible to change the delimiters that QUrlQuery uses for parsing and for |
104 | reconstructing the query by calling setQueryDelimiters(). |
105 | |
106 | Non-standard delimiters should be chosen from among what RFC 3986 calls |
107 | "sub-delimiters". They are: |
108 | |
109 | \snippet code/src_corelib_io_qurlquery.cpp 0 |
110 | |
111 | Use of other characters is not supported and may result in unexpected |
112 | behaviour. QUrlQuery does not verify that you passed a valid delimiter. |
113 | |
114 | \sa QUrl |
115 | */ |
116 | |
117 | /*! |
118 | \fn QUrlQuery &QUrlQuery::operator=(QUrlQuery &&other) |
119 | |
120 | Move-assigns \a other to this QUrlQuery instance. |
121 | |
122 | \since 5.2 |
123 | */ |
124 | |
125 | /*! |
126 | \fn QUrlQuery::QUrlQuery(std::initializer_list<QPair<QString, QString>> list) |
127 | |
128 | \since 5.13 |
129 | |
130 | Constructs a QUrlQuery object from the \a list of key/value pair. |
131 | */ |
132 | |
133 | typedef QList<QPair<QString, QString> > Map; |
134 | |
135 | class QUrlQueryPrivate : public QSharedData |
136 | { |
137 | public: |
138 | QUrlQueryPrivate(const QString &query = QString()) |
139 | : valueDelimiter(QUrlQuery::defaultQueryValueDelimiter()), |
140 | pairDelimiter(QUrlQuery::defaultQueryPairDelimiter()) |
141 | { if (!query.isEmpty()) setQuery(query); } |
142 | |
143 | QString recodeFromUser(const QString &input) const; |
144 | QString recodeToUser(const QString &input, QUrl::ComponentFormattingOptions encoding) const; |
145 | |
146 | void setQuery(const QString &query); |
147 | |
148 | void addQueryItem(const QString &key, const QString &value) |
149 | { itemList.append(t: qMakePair(value1: recodeFromUser(input: key), value2: recodeFromUser(input: value))); } |
150 | int findRecodedKey(const QString &key, int from = 0) const |
151 | { |
152 | for (int i = from; i < itemList.size(); ++i) |
153 | if (itemList.at(i).first == key) |
154 | return i; |
155 | return itemList.size(); |
156 | } |
157 | Map::const_iterator findKey(const QString &key) const |
158 | { return itemList.constBegin() + findRecodedKey(key: recodeFromUser(input: key)); } |
159 | Map::iterator findKey(const QString &key) |
160 | { return itemList.begin() + findRecodedKey(key: recodeFromUser(input: key)); } |
161 | |
162 | Map itemList; |
163 | QChar valueDelimiter; |
164 | QChar pairDelimiter; |
165 | }; |
166 | |
167 | template<> void QSharedDataPointer<QUrlQueryPrivate>::detach() |
168 | { |
169 | if (d && d->ref.loadRelaxed() == 1) |
170 | return; |
171 | QUrlQueryPrivate *x = (d ? new QUrlQueryPrivate(*d) |
172 | : new QUrlQueryPrivate); |
173 | x->ref.ref(); |
174 | if (d && !d->ref.deref()) |
175 | delete d; |
176 | d = x; |
177 | } |
178 | |
179 | // Here's how we do the encoding in QUrlQuery |
180 | // The RFC says these are the delimiters: |
181 | // gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@" |
182 | // sub-delims = "!" / "$" / "&" / "'" / "(" / ")" |
183 | // / "*" / "+" / "," / ";" / "=" |
184 | // And the definition of query is: |
185 | // query = *( pchar / "/" / "?" ) |
186 | // pchar = unreserved / pct-encoded / sub-delims / ":" / "@" |
187 | // |
188 | // The strict definition of query says that it can have unencoded any |
189 | // unreserved, sub-delim, ":", "@", "/" and "?". Or, by exclusion, excluded |
190 | // delimiters are "#", "[" and "]" -- if those are present, they must be |
191 | // percent-encoded. The fact that "[" and "]" should be encoded is probably a |
192 | // mistake in the spec, so we ignore it and leave the decoded. |
193 | // |
194 | // The internal storage in the Map is equivalent to PrettyDecoded. That means |
195 | // the getter methods, when called with the default encoding value, will not |
196 | // have to recode anything (except for toString()). |
197 | // |
198 | // QUrlQuery handling of delimiters is quite simple: we never touch any of |
199 | // them, except for the "#" character and the pair and value delimiters. Those |
200 | // are always kept in their decoded forms. |
201 | // |
202 | // But when recreating the query string, in toString(), we must take care of |
203 | // the special delimiters: the pair and value delimiters, as well as the "#" |
204 | // character if unambiguous decoding is requested. |
205 | |
206 | #define decode(x) ushort(x) |
207 | #define leave(x) ushort(0x100 | (x)) |
208 | #define encode(x) ushort(0x200 | (x)) |
209 | |
210 | inline QString QUrlQueryPrivate::recodeFromUser(const QString &input) const |
211 | { |
212 | // note: duplicated in setQuery() |
213 | QString output; |
214 | ushort prettyDecodedActions[] = { |
215 | decode(pairDelimiter.unicode()), |
216 | decode(valueDelimiter.unicode()), |
217 | decode('#'), |
218 | 0 |
219 | }; |
220 | if (qt_urlRecode(appendTo&: output, url: input, |
221 | encoding: QUrl::DecodeReserved, |
222 | tableModifications: prettyDecodedActions)) |
223 | return output; |
224 | return input; |
225 | } |
226 | |
227 | inline bool idempotentRecodeToUser(QUrl::ComponentFormattingOptions encoding) |
228 | { |
229 | return encoding == QUrl::PrettyDecoded; |
230 | } |
231 | |
232 | inline QString QUrlQueryPrivate::recodeToUser(const QString &input, QUrl::ComponentFormattingOptions encoding) const |
233 | { |
234 | // our internal formats are stored in "PrettyDecoded" form |
235 | // and there are no ambiguous characters |
236 | if (idempotentRecodeToUser(encoding)) |
237 | return input; |
238 | |
239 | if (!(encoding & QUrl::EncodeDelimiters)) { |
240 | QString output; |
241 | if (qt_urlRecode(appendTo&: output, url: input, |
242 | encoding, tableModifications: nullptr)) |
243 | return output; |
244 | return input; |
245 | } |
246 | |
247 | // re-encode the "#" character and the query delimiter pair |
248 | ushort actions[] = { encode(pairDelimiter.unicode()), encode(valueDelimiter.unicode()), |
249 | encode('#'), 0 }; |
250 | QString output; |
251 | if (qt_urlRecode(appendTo&: output, url: input, encoding, tableModifications: actions)) |
252 | return output; |
253 | return input; |
254 | } |
255 | |
256 | void QUrlQueryPrivate::setQuery(const QString &query) |
257 | { |
258 | ushort prettyDecodedActions[] = { |
259 | decode(pairDelimiter.unicode()), |
260 | decode(valueDelimiter.unicode()), |
261 | decode('#'), |
262 | 0 |
263 | }; |
264 | |
265 | itemList.clear(); |
266 | const QChar *pos = query.constData(); |
267 | const QChar *const end = pos + query.size(); |
268 | while (pos != end) { |
269 | const QChar *begin = pos; |
270 | const QChar *delimiter = nullptr; |
271 | while (pos != end) { |
272 | // scan for the component parts of this pair |
273 | if (!delimiter && *pos == valueDelimiter) |
274 | delimiter = pos; |
275 | if (*pos == pairDelimiter) |
276 | break; |
277 | ++pos; |
278 | } |
279 | if (!delimiter) |
280 | delimiter = pos; |
281 | |
282 | // pos is the end of this pair (the end of the string or the pair delimiter) |
283 | // delimiter points to the value delimiter or to the end of this pair |
284 | |
285 | QString key; |
286 | if (!qt_urlRecode(appendTo&: key, url: QStringView{begin, delimiter}, |
287 | encoding: QUrl::DecodeReserved, |
288 | tableModifications: prettyDecodedActions)) |
289 | key = QString(begin, delimiter - begin); |
290 | |
291 | if (delimiter == pos) { |
292 | // the value delimiter wasn't found, store a null value |
293 | itemList.append(t: qMakePair(value1&: key, value2: QString())); |
294 | } else if (delimiter + 1 == pos) { |
295 | // if the delimiter was found but the value is empty, store empty-but-not-null |
296 | itemList.append(t: qMakePair(value1&: key, value2: QString(0, Qt::Uninitialized))); |
297 | } else { |
298 | QString value; |
299 | if (!qt_urlRecode(appendTo&: value, url: QStringView{delimiter + 1, pos}, |
300 | encoding: QUrl::DecodeReserved, |
301 | tableModifications: prettyDecodedActions)) |
302 | value = QString(delimiter + 1, pos - delimiter - 1); |
303 | itemList.append(t: qMakePair(value1&: key, value2&: value)); |
304 | } |
305 | |
306 | if (pos != end) |
307 | ++pos; |
308 | } |
309 | } |
310 | |
311 | // allow QUrlQueryPrivate to detach from null |
312 | template <> inline QUrlQueryPrivate * |
313 | QSharedDataPointer<QUrlQueryPrivate>::clone() |
314 | { |
315 | return d ? new QUrlQueryPrivate(*d) : new QUrlQueryPrivate; |
316 | } |
317 | |
318 | /*! |
319 | Constructs an empty QUrlQuery object. A query can be set afterwards by |
320 | calling setQuery() or items can be added by using addQueryItem(). |
321 | |
322 | \sa setQuery(), addQueryItem() |
323 | */ |
324 | QUrlQuery::QUrlQuery() |
325 | : d(nullptr) |
326 | { |
327 | } |
328 | |
329 | /*! |
330 | Constructs a QUrlQuery object and parses the \a queryString query string, |
331 | using the default query delimiters. To parse a query string using other |
332 | delimiters, you should first set them using setQueryDelimiters() and then |
333 | set the query with setQuery(). |
334 | */ |
335 | QUrlQuery::QUrlQuery(const QString &queryString) |
336 | : d(queryString.isEmpty() ? nullptr : new QUrlQueryPrivate(queryString)) |
337 | { |
338 | } |
339 | |
340 | /*! |
341 | Constructs a QUrlQuery object and parses the query string found in the \a |
342 | url URL, using the default query delimiters. To parse a query string using |
343 | other delimiters, you should first set them using setQueryDelimiters() and |
344 | then set the query with setQuery(). |
345 | |
346 | \sa QUrl::query() |
347 | */ |
348 | QUrlQuery::QUrlQuery(const QUrl &url) |
349 | : d(nullptr) |
350 | { |
351 | // use internals to avoid unnecessary recoding |
352 | // ### FIXME: actually do it |
353 | if (url.hasQuery()) |
354 | d = new QUrlQueryPrivate(url.query()); |
355 | } |
356 | |
357 | /*! |
358 | Copies the contents of the \a other QUrlQuery object, including the query |
359 | delimiters. |
360 | */ |
361 | QUrlQuery::QUrlQuery(const QUrlQuery &other) |
362 | : d(other.d) |
363 | { |
364 | } |
365 | |
366 | /*! |
367 | \since 6.5 |
368 | Moves the contents of the \a other QUrlQuery object, including the query |
369 | delimiters. |
370 | */ |
371 | QUrlQuery::QUrlQuery(QUrlQuery &&other) noexcept |
372 | : d(std::move(other.d)) |
373 | { |
374 | } |
375 | |
376 | /*! |
377 | Copies the contents of the \a other QUrlQuery object, including the query |
378 | delimiters. |
379 | */ |
380 | QUrlQuery &QUrlQuery::operator =(const QUrlQuery &other) |
381 | { |
382 | d = other.d; |
383 | return *this; |
384 | } |
385 | |
386 | /*! |
387 | \fn void QUrlQuery::swap(QUrlQuery &other) |
388 | |
389 | Swaps this URL query instance with \a other. This function is very |
390 | fast and never fails. |
391 | */ |
392 | |
393 | /*! |
394 | Destroys this QUrlQuery object. |
395 | */ |
396 | QUrlQuery::~QUrlQuery() |
397 | { |
398 | // d auto-deletes |
399 | } |
400 | |
401 | /*! |
402 | Returns \c true if this object and the \a other object contain the same |
403 | contents, in the same order, and use the same query delimiters. |
404 | */ |
405 | bool QUrlQuery::operator ==(const QUrlQuery &other) const |
406 | { |
407 | if (d == other.d) |
408 | return true; |
409 | if (d && other.d) |
410 | // keep in sync with qHash(QUrlQuery): |
411 | return d->valueDelimiter == other.d->valueDelimiter && |
412 | d->pairDelimiter == other.d->pairDelimiter && |
413 | d->itemList == other.d->itemList; |
414 | |
415 | const QUrlQueryPrivate *x = d ? d.data() : other.d.data(); |
416 | return x->valueDelimiter == defaultQueryValueDelimiter() && |
417 | x->pairDelimiter == defaultQueryPairDelimiter() && |
418 | x->itemList.isEmpty(); |
419 | } |
420 | |
421 | /*! |
422 | \since 5.6 |
423 | \relates QUrlQuery |
424 | |
425 | Returns the hash value for \a key, |
426 | using \a seed to seed the calculation. |
427 | */ |
428 | size_t qHash(const QUrlQuery &key, size_t seed) noexcept |
429 | { |
430 | if (const QUrlQueryPrivate *d = key.d) { |
431 | QtPrivate::QHashCombine hash; |
432 | // keep in sync with operator==: |
433 | seed = hash(seed, d->valueDelimiter); |
434 | seed = hash(seed, d->pairDelimiter); |
435 | seed = hash(seed, d->itemList); |
436 | } |
437 | return seed; |
438 | } |
439 | |
440 | /*! |
441 | Returns \c true if this QUrlQuery object contains no key-value pairs, such as |
442 | after being default-constructed or after parsing an empty query string. |
443 | |
444 | \sa setQuery(), clear() |
445 | */ |
446 | bool QUrlQuery::isEmpty() const |
447 | { |
448 | return d ? d->itemList.isEmpty() : true; |
449 | } |
450 | |
451 | /*! |
452 | \internal |
453 | */ |
454 | bool QUrlQuery::isDetached() const |
455 | { |
456 | return d && d->ref.loadRelaxed() == 1; |
457 | } |
458 | |
459 | /*! |
460 | Clears this QUrlQuery object by removing all of the key-value pairs |
461 | currently stored. If the query delimiters have been changed, this function |
462 | will leave them with their changed values. |
463 | |
464 | \sa isEmpty(), setQueryDelimiters() |
465 | */ |
466 | void QUrlQuery::clear() |
467 | { |
468 | if (d.constData()) |
469 | d->itemList.clear(); |
470 | } |
471 | |
472 | /*! |
473 | Parses the query string in \a queryString and sets the internal items to |
474 | the values found there. If any delimiters have been specified with |
475 | setQueryDelimiters(), this function will use them instead of the default |
476 | delimiters to parse the string. |
477 | */ |
478 | void QUrlQuery::setQuery(const QString &queryString) |
479 | { |
480 | d->setQuery(queryString); |
481 | } |
482 | |
483 | static void recodeAndAppend(QString &to, const QString &input, |
484 | QUrl::ComponentFormattingOptions encoding, const ushort *tableModifications) |
485 | { |
486 | if (!qt_urlRecode(appendTo&: to, url: input, encoding, tableModifications)) |
487 | to += input; |
488 | } |
489 | |
490 | /*! |
491 | Returns the reconstructed query string, formed from the key-value pairs |
492 | currently stored in this QUrlQuery object and separated by the query |
493 | delimiters chosen for this object. The keys and values are encoded using |
494 | the options given by the \a encoding parameter. |
495 | |
496 | For this function, the only ambiguous delimiter is the hash ("#"), as in |
497 | URLs it is used to separate the query string from the fragment that may |
498 | follow. |
499 | |
500 | The order of the key-value pairs in the returned string is exactly the same |
501 | as in the original query. |
502 | |
503 | \sa setQuery(), QUrl::setQuery(), QUrl::fragment(), {encoding}{Encoding} |
504 | */ |
505 | QString QUrlQuery::query(QUrl::ComponentFormattingOptions encoding) const |
506 | { |
507 | if (!d) |
508 | return QString(); |
509 | |
510 | // unlike the component encoding, for the whole query we need to modify a little: |
511 | // - the "#" character is unambiguous, so we encode it in EncodeDelimiters mode |
512 | // - the query delimiter pair must always be encoded |
513 | |
514 | // start with what's always encoded |
515 | ushort tableActions[] = { |
516 | encode(d->pairDelimiter.unicode()), // 0 |
517 | encode(d->valueDelimiter.unicode()), // 1 |
518 | 0, // 2 |
519 | 0 |
520 | }; |
521 | if (encoding & QUrl::EncodeDelimiters) { |
522 | tableActions[2] = encode('#'); |
523 | } |
524 | |
525 | QString result; |
526 | Map::const_iterator it = d->itemList.constBegin(); |
527 | Map::const_iterator end = d->itemList.constEnd(); |
528 | |
529 | { |
530 | int size = 0; |
531 | for ( ; it != end; ++it) |
532 | size += it->first.size() + 1 + it->second.size() + 1; |
533 | result.reserve(asize: size + size / 4); |
534 | } |
535 | |
536 | for (it = d->itemList.constBegin(); it != end; ++it) { |
537 | if (!result.isEmpty()) |
538 | result += QChar(d->pairDelimiter); |
539 | recodeAndAppend(to&: result, input: it->first, encoding, tableModifications: tableActions); |
540 | if (!it->second.isNull()) { |
541 | result += QChar(d->valueDelimiter); |
542 | recodeAndAppend(to&: result, input: it->second, encoding, tableModifications: tableActions); |
543 | } |
544 | } |
545 | return result; |
546 | } |
547 | |
548 | /*! |
549 | Sets the characters used for delimiting between keys and values, |
550 | and between key-value pairs in the URL's query string. The default |
551 | value delimiter is '=' and the default pair delimiter is '&'. |
552 | |
553 | \image qurl-querystring.png |
554 | |
555 | \a valueDelimiter will be used for separating keys from values, |
556 | and \a pairDelimiter will be used to separate key-value pairs. |
557 | Any occurrences of these delimiting characters in the encoded |
558 | representation of the keys and values of the query string are |
559 | percent encoded when returned in query(). |
560 | |
561 | If \a valueDelimiter is set to '(' and \a pairDelimiter is ')', |
562 | the above query string would instead be represented like this: |
563 | |
564 | \snippet code/src_corelib_io_qurl.cpp 4 |
565 | |
566 | \note Non-standard delimiters should be chosen from among what RFC 3986 calls |
567 | "sub-delimiters". They are: |
568 | |
569 | \snippet code/src_corelib_io_qurlquery.cpp 0 |
570 | |
571 | Use of other characters is not supported and may result in unexpected |
572 | behaviour. This method does not verify that you passed a valid delimiter. |
573 | |
574 | \sa queryValueDelimiter(), queryPairDelimiter() |
575 | */ |
576 | void QUrlQuery::setQueryDelimiters(QChar valueDelimiter, QChar pairDelimiter) |
577 | { |
578 | d->valueDelimiter = valueDelimiter; |
579 | d->pairDelimiter = pairDelimiter; |
580 | } |
581 | |
582 | /*! |
583 | Returns the character used to delimit between keys and values when |
584 | reconstructing the query string in query() or when parsing in setQuery(). |
585 | |
586 | \sa setQueryDelimiters(), queryPairDelimiter() |
587 | */ |
588 | QChar QUrlQuery::queryValueDelimiter() const |
589 | { |
590 | return d ? d->valueDelimiter : defaultQueryValueDelimiter(); |
591 | } |
592 | |
593 | /*! |
594 | Returns the character used to delimit between keys-value pairs when |
595 | reconstructing the query string in query() or when parsing in setQuery(). |
596 | |
597 | \sa setQueryDelimiters(), queryValueDelimiter() |
598 | */ |
599 | QChar QUrlQuery::queryPairDelimiter() const |
600 | { |
601 | return d ? d->pairDelimiter : defaultQueryPairDelimiter(); |
602 | } |
603 | |
604 | /*! |
605 | Sets the items in this QUrlQuery object to \a query. The order of the |
606 | elements in \a query is preserved. |
607 | |
608 | \note This method does not treat spaces (ASCII 0x20) and plus ("+") signs |
609 | as the same, like HTML forms do. If you need spaces to be represented as |
610 | plus signs, use actual plus signs. |
611 | |
612 | \note The keys and values are expected to be in percent-encoded form. |
613 | |
614 | \sa queryItems(), isEmpty() |
615 | */ |
616 | void QUrlQuery::setQueryItems(const QList<QPair<QString, QString> > &query) |
617 | { |
618 | clear(); |
619 | if (query.isEmpty()) |
620 | return; |
621 | |
622 | QUrlQueryPrivate *dd = d; |
623 | QList<QPair<QString, QString> >::const_iterator it = query.constBegin(), |
624 | end = query.constEnd(); |
625 | for ( ; it != end; ++it) |
626 | dd->addQueryItem(key: it->first, value: it->second); |
627 | } |
628 | |
629 | /*! |
630 | Returns the query string of the URL, as a map of keys and values, using the |
631 | options specified in \a encoding to encode the items. The order of the |
632 | elements is the same as the one found in the query string or set with |
633 | setQueryItems(). |
634 | |
635 | \sa setQueryItems(), {encoding}{Encoding} |
636 | */ |
637 | QList<QPair<QString, QString> > QUrlQuery::queryItems(QUrl::ComponentFormattingOptions encoding) const |
638 | { |
639 | if (!d) |
640 | return QList<QPair<QString, QString> >(); |
641 | if (idempotentRecodeToUser(encoding)) |
642 | return d->itemList; |
643 | |
644 | QList<QPair<QString, QString> > result; |
645 | Map::const_iterator it = d->itemList.constBegin(); |
646 | Map::const_iterator end = d->itemList.constEnd(); |
647 | result.reserve(asize: d->itemList.size()); |
648 | for ( ; it != end; ++it) |
649 | result << qMakePair(value1: d->recodeToUser(input: it->first, encoding), |
650 | value2: d->recodeToUser(input: it->second, encoding)); |
651 | return result; |
652 | } |
653 | |
654 | /*! |
655 | Returns \c true if there is a query string pair whose key is equal |
656 | to \a key from the URL. |
657 | |
658 | \note The key expected to be in percent-encoded form. |
659 | |
660 | \sa addQueryItem(), queryItemValue() |
661 | */ |
662 | bool QUrlQuery::hasQueryItem(const QString &key) const |
663 | { |
664 | if (!d) |
665 | return false; |
666 | return d->findKey(key) != d->itemList.constEnd(); |
667 | } |
668 | |
669 | /*! |
670 | Appends the pair \a key = \a value to the end of the query string of the |
671 | URL. This method does not overwrite existing items that might exist with |
672 | the same key. |
673 | |
674 | \note This method does not treat spaces (ASCII 0x20) and plus ("+") signs |
675 | as the same, like HTML forms do. If you need spaces to be represented as |
676 | plus signs, use actual plus signs. |
677 | |
678 | \note The key and value strings are expected to be in percent-encoded form. |
679 | |
680 | \sa hasQueryItem(), queryItemValue() |
681 | */ |
682 | void QUrlQuery::addQueryItem(const QString &key, const QString &value) |
683 | { |
684 | d->addQueryItem(key, value); |
685 | } |
686 | |
687 | /*! |
688 | Returns the query value associated with key \a key from the URL, using the |
689 | options specified in \a encoding to encode the return value. If the key \a |
690 | key is not found, this function returns an empty string. If you need to |
691 | distinguish between an empty value and a non-existent key, you should check |
692 | for the key's presence first using hasQueryItem(). |
693 | |
694 | If the key \a key is multiply defined, this function will return the first |
695 | one found, in the order they were present in the query string or added |
696 | using addQueryItem(). |
697 | |
698 | \note The key is expected to be in percent-encoded form. |
699 | |
700 | \sa addQueryItem(), allQueryItemValues(), {encoding}{Encoding} |
701 | */ |
702 | QString QUrlQuery::queryItemValue(const QString &key, QUrl::ComponentFormattingOptions encoding) const |
703 | { |
704 | QString result; |
705 | if (d) { |
706 | Map::const_iterator it = d->findKey(key); |
707 | if (it != d->itemList.constEnd()) |
708 | result = d->recodeToUser(input: it->second, encoding); |
709 | } |
710 | return result; |
711 | } |
712 | |
713 | /*! |
714 | Returns the a list of query string values whose key is equal to \a key from |
715 | the URL, using the options specified in \a encoding to encode the return |
716 | value. If the key \a key is not found, this function returns an empty list. |
717 | |
718 | \note The key is expected to be in percent-encoded form. |
719 | |
720 | \sa queryItemValue(), addQueryItem() |
721 | */ |
722 | QStringList QUrlQuery::allQueryItemValues(const QString &key, QUrl::ComponentFormattingOptions encoding) const |
723 | { |
724 | QStringList result; |
725 | if (d) { |
726 | QString encodedKey = d->recodeFromUser(input: key); |
727 | int idx = d->findRecodedKey(key: encodedKey); |
728 | while (idx < d->itemList.size()) { |
729 | result << d->recodeToUser(input: d->itemList.at(i: idx).second, encoding); |
730 | idx = d->findRecodedKey(key: encodedKey, from: idx + 1); |
731 | } |
732 | } |
733 | return result; |
734 | } |
735 | |
736 | /*! |
737 | Removes the query string pair whose key is equal to \a key from the URL. If |
738 | there are multiple items with a key equal to \a key, it removes the first |
739 | item in the order they were present in the query string or added with |
740 | addQueryItem(). |
741 | |
742 | \note The key is expected to be in percent-encoded form. |
743 | |
744 | \sa removeAllQueryItems() |
745 | */ |
746 | void QUrlQuery::removeQueryItem(const QString &key) |
747 | { |
748 | if (d.constData()) { |
749 | auto *p = d.data(); |
750 | Map::iterator it = p->findKey(key); |
751 | if (it != p->itemList.end()) |
752 | p->itemList.erase(pos: it); |
753 | } |
754 | } |
755 | |
756 | /*! |
757 | Removes all the query string pairs whose key is equal to \a key |
758 | from the URL. |
759 | |
760 | \note The key is expected to be in percent-encoded form. |
761 | |
762 | \sa removeQueryItem() |
763 | */ |
764 | void QUrlQuery::removeAllQueryItems(const QString &key) |
765 | { |
766 | if (d.constData()) { |
767 | auto *p = d.data(); |
768 | const QString encodedKey = p->recodeFromUser(input: key); |
769 | auto firstEqualsEncodedKey = [&encodedKey](const QPair<QString, QString> &item) { |
770 | return item.first == encodedKey; |
771 | }; |
772 | p->itemList.removeIf(pred: firstEqualsEncodedKey); |
773 | } |
774 | } |
775 | |
776 | /*! |
777 | \fn QUrlQuery::defaultQueryValueDelimiter() |
778 | Returns the default character for separating keys from values in the query, |
779 | an equal sign ("="). |
780 | |
781 | \note Prior to Qt 6, this function returned QChar. |
782 | |
783 | \sa setQueryDelimiters(), queryValueDelimiter(), defaultQueryPairDelimiter() |
784 | */ |
785 | |
786 | /*! |
787 | \fn QUrlQuery::defaultQueryPairDelimiter() |
788 | Returns the default character for separating keys-value pairs from each |
789 | other, an ampersand ("&"). |
790 | |
791 | \note Prior to Qt 6, this function returned QChar. |
792 | |
793 | \sa setQueryDelimiters(), queryPairDelimiter(), defaultQueryValueDelimiter() |
794 | */ |
795 | |
796 | /*! |
797 | \typedef QUrlQuery::DataPtr |
798 | \internal |
799 | */ |
800 | |
801 | /*! |
802 | \fn DataPtr &QUrlQuery::data_ptr() |
803 | \internal |
804 | */ |
805 | |
806 | /*! |
807 | \fn QString QUrlQuery::toString(QUrl::ComponentFormattingOptions encoding = QUrl::PrettyDecoded) const |
808 | |
809 | Returns this QUrlQuery as a QString. \a encoding can be used to specify the URL string encoding of the return value. |
810 | */ |
811 | |
812 | /*! |
813 | \fn bool QUrlQuery::operator!=(const QUrlQuery &other) const |
814 | |
815 | Returns \c true if \a other is not equal to this QUrlQuery. Otherwise, returns \c false. |
816 | |
817 | \sa operator==() |
818 | */ |
819 | QT_END_NAMESPACE |
820 | |
821 | #undef decode |
822 | #undef leave |
823 | #undef encode |
824 | |