1 | // Copyright (C) 2023 The Qt Company Ltd. |
---|---|
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 "qhttpheaders.h" |
5 | |
6 | #include <private/qoffsetstringarray_p.h> |
7 | |
8 | #include <QtCore/qcompare.h> |
9 | #include <QtCore/qhash.h> |
10 | #include <QtCore/qloggingcategory.h> |
11 | #include <QtCore/qmap.h> |
12 | #include <QtCore/qset.h> |
13 | #include <QtCore/qttypetraits.h> |
14 | |
15 | #include <q20algorithm.h> |
16 | #include <string_view> |
17 | #include <variant> |
18 | |
19 | QT_BEGIN_NAMESPACE |
20 | |
21 | Q_LOGGING_CATEGORY(lcQHttpHeaders, "qt.network.http.headers"); |
22 | |
23 | /*! |
24 | \class QHttpHeaders |
25 | \since 6.7 |
26 | \ingroup |
27 | \inmodule QtNetwork |
28 | |
29 | \brief QHttpHeaders is a class for holding HTTP headers. |
30 | |
31 | The class is an interface type for Qt networking APIs that |
32 | use or consume such headers. |
33 | |
34 | \section1 Allowed field name and value characters |
35 | |
36 | An HTTP header consists of \e name and \e value. |
37 | When setting these, QHttpHeaders validates \e name and \e value |
38 | to only contain characters allowed by the HTTP RFCs. For detailed |
39 | information see |
40 | \l {https://datatracker.ietf.org/doc/html/rfc9110#name-field-values} |
41 | {RFC 9110 Chapters 5.1 and 5.5}. |
42 | |
43 | In all, this means: |
44 | \list |
45 | \li \c name must consist of visible ASCII characters, and must not be |
46 | empty |
47 | \li \c value may consist of arbitrary bytes, as long as header |
48 | and use case specific encoding rules are adhered to. \c value |
49 | may be empty |
50 | \endlist |
51 | |
52 | The setters of this class automatically remove any leading or trailing |
53 | whitespaces from \e value, as they must be ignored during the |
54 | \e value processing. |
55 | |
56 | \section1 Combining values |
57 | |
58 | Most HTTP header values can be combined with a single comma \c {','} |
59 | plus an optional whitespace, and the semantic meaning is preserved. |
60 | As an example, these two should be semantically similar: |
61 | \badcode |
62 | // Values as separate header entries |
63 | myheadername: myheadervalue1 |
64 | myheadername: myheadervalue2 |
65 | // Combined value |
66 | myheadername: myheadervalue1, myheadervalue2 |
67 | \endcode |
68 | |
69 | However, there is a notable exception to this rule: |
70 | \l {https://datatracker.ietf.org/doc/html/rfc9110#name-field-order} |
71 | {Set-Cookie}. Due to this and the possibility of custom use cases, |
72 | QHttpHeaders does not automatically combine the values. |
73 | |
74 | \section1 Performance |
75 | |
76 | Most QHttpHeaders functions provide both |
77 | \l QHttpHeaders::WellKnownHeader and \l QAnyStringView overloads. |
78 | From a memory-usage and computation point of view it is recommended |
79 | to use the \l QHttpHeaders::WellKnownHeader overloads. |
80 | */ |
81 | |
82 | // This list is from IANA HTTP Field Name Registry |
83 | // https://www.iana.org/assignments/http-fields |
84 | // It contains entries that are either "permanent" |
85 | // or "deprecated" as of October 2023. |
86 | // Usage relies on enum values keeping in same order. |
87 | // ### Qt7 check if some of these headers have been obsoleted, |
88 | // and also check if the enums benefit from reordering |
89 | static constexpr auto headerNames = qOffsetStringArray( |
90 | // IANA Permanent status: |
91 | strings: "a-im", |
92 | strings: "accept", |
93 | strings: "accept-additions", |
94 | strings: "accept-ch", |
95 | strings: "accept-datetime", |
96 | strings: "accept-encoding", |
97 | strings: "accept-features", |
98 | strings: "accept-language", |
99 | strings: "accept-patch", |
100 | strings: "accept-post", |
101 | strings: "accept-ranges", |
102 | strings: "accept-signature", |
103 | strings: "access-control-allow-credentials", |
104 | strings: "access-control-allow-headers", |
105 | strings: "access-control-allow-methods", |
106 | strings: "access-control-allow-origin", |
107 | strings: "access-control-expose-headers", |
108 | strings: "access-control-max-age", |
109 | strings: "access-control-request-headers", |
110 | strings: "access-control-request-method", |
111 | strings: "age", |
112 | strings: "allow", |
113 | strings: "alpn", |
114 | strings: "alt-svc", |
115 | strings: "alt-used", |
116 | strings: "alternates", |
117 | strings: "apply-to-redirect-ref", |
118 | strings: "authentication-control", |
119 | strings: "authentication-info", |
120 | strings: "authorization", |
121 | strings: "cache-control", |
122 | strings: "cache-status", |
123 | strings: "cal-managed-id", |
124 | strings: "caldav-timezones", |
125 | strings: "capsule-protocol", |
126 | strings: "cdn-cache-control", |
127 | strings: "cdn-loop", |
128 | strings: "cert-not-after", |
129 | strings: "cert-not-before", |
130 | strings: "clear-site-data", |
131 | strings: "client-cert", |
132 | strings: "client-cert-chain", |
133 | strings: "close", |
134 | strings: "connection", |
135 | strings: "content-digest", |
136 | strings: "content-disposition", |
137 | strings: "content-encoding", |
138 | strings: "content-id", |
139 | strings: "content-language", |
140 | strings: "content-length", |
141 | strings: "content-location", |
142 | strings: "content-range", |
143 | strings: "content-security-policy", |
144 | strings: "content-security-policy-report-only", |
145 | strings: "content-type", |
146 | strings: "cookie", |
147 | strings: "cross-origin-embedder-policy", |
148 | strings: "cross-origin-embedder-policy-report-only", |
149 | strings: "cross-origin-opener-policy", |
150 | strings: "cross-origin-opener-policy-report-only", |
151 | strings: "cross-origin-resource-policy", |
152 | strings: "dasl", |
153 | strings: "date", |
154 | strings: "dav", |
155 | strings: "delta-base", |
156 | strings: "depth", |
157 | strings: "destination", |
158 | strings: "differential-id", |
159 | strings: "dpop", |
160 | strings: "dpop-nonce", |
161 | strings: "early-data", |
162 | strings: "etag", |
163 | strings: "expect", |
164 | strings: "expect-ct", |
165 | strings: "expires", |
166 | strings: "forwarded", |
167 | strings: "from", |
168 | strings: "hobareg", |
169 | strings: "host", |
170 | strings: "if", |
171 | strings: "if-match", |
172 | strings: "if-modified-since", |
173 | strings: "if-none-match", |
174 | strings: "if-range", |
175 | strings: "if-schedule-tag-match", |
176 | strings: "if-unmodified-since", |
177 | strings: "im", |
178 | strings: "include-referred-token-binding-id", |
179 | strings: "keep-alive", |
180 | strings: "label", |
181 | strings: "last-event-id", |
182 | strings: "last-modified", |
183 | strings: "link", |
184 | strings: "location", |
185 | strings: "lock-token", |
186 | strings: "max-forwards", |
187 | strings: "memento-datetime", |
188 | strings: "meter", |
189 | strings: "mime-version", |
190 | strings: "negotiate", |
191 | strings: "nel", |
192 | strings: "odata-entityid", |
193 | strings: "odata-isolation", |
194 | strings: "odata-maxversion", |
195 | strings: "odata-version", |
196 | strings: "optional-www-authenticate", |
197 | strings: "ordering-type", |
198 | strings: "origin", |
199 | strings: "origin-agent-cluster", |
200 | strings: "oscore", |
201 | strings: "oslc-core-version", |
202 | strings: "overwrite", |
203 | strings: "ping-from", |
204 | strings: "ping-to", |
205 | strings: "position", |
206 | strings: "prefer", |
207 | strings: "preference-applied", |
208 | strings: "priority", |
209 | strings: "proxy-authenticate", |
210 | strings: "proxy-authentication-info", |
211 | strings: "proxy-authorization", |
212 | strings: "proxy-status", |
213 | strings: "public-key-pins", |
214 | strings: "public-key-pins-report-only", |
215 | strings: "range", |
216 | strings: "redirect-ref", |
217 | strings: "referer", |
218 | strings: "refresh", |
219 | strings: "replay-nonce", |
220 | strings: "repr-digest", |
221 | strings: "retry-after", |
222 | strings: "schedule-reply", |
223 | strings: "schedule-tag", |
224 | strings: "sec-purpose", |
225 | strings: "sec-token-binding", |
226 | strings: "sec-websocket-accept", |
227 | strings: "sec-websocket-extensions", |
228 | strings: "sec-websocket-key", |
229 | strings: "sec-websocket-protocol", |
230 | strings: "sec-websocket-version", |
231 | strings: "server", |
232 | strings: "server-timing", |
233 | strings: "set-cookie", |
234 | strings: "signature", |
235 | strings: "signature-input", |
236 | strings: "slug", |
237 | strings: "soapaction", |
238 | strings: "status-uri", |
239 | strings: "strict-transport-security", |
240 | strings: "sunset", |
241 | strings: "surrogate-capability", |
242 | strings: "surrogate-control", |
243 | strings: "tcn", |
244 | strings: "te", |
245 | strings: "timeout", |
246 | strings: "topic", |
247 | strings: "traceparent", |
248 | strings: "tracestate", |
249 | strings: "trailer", |
250 | strings: "transfer-encoding", |
251 | strings: "ttl", |
252 | strings: "upgrade", |
253 | strings: "urgency", |
254 | strings: "user-agent", |
255 | strings: "variant-vary", |
256 | strings: "vary", |
257 | strings: "via", |
258 | strings: "want-content-digest", |
259 | strings: "want-repr-digest", |
260 | strings: "www-authenticate", |
261 | strings: "x-content-type-options", |
262 | strings: "x-frame-options", |
263 | // IANA Deprecated status: |
264 | strings: "accept-charset", |
265 | strings: "c-pep-info", |
266 | strings: "pragma", |
267 | strings: "protocol-info", |
268 | strings: "protocol-query" |
269 | // If you append here, regenerate the index table |
270 | ); |
271 | |
272 | namespace { |
273 | struct ByIndirectHeaderName |
274 | { |
275 | constexpr bool operator()(quint8 lhs, quint8 rhs) const noexcept |
276 | { |
277 | return (*this)(map(i: lhs), map(i: rhs)); |
278 | } |
279 | constexpr bool operator()(quint8 lhs, QByteArrayView rhs) const noexcept |
280 | { |
281 | return (*this)(map(i: lhs), rhs); |
282 | } |
283 | constexpr bool operator()(QByteArrayView lhs, quint8 rhs) const noexcept |
284 | { |
285 | return (*this)(lhs, map(i: rhs)); |
286 | } |
287 | constexpr bool operator()(QByteArrayView lhs, QByteArrayView rhs) const noexcept |
288 | { |
289 | // ### just `lhs < rhs` when QByteArrayView relational operators are constexpr |
290 | return std::string_view(lhs) < std::string_view(rhs); |
291 | } |
292 | private: |
293 | static constexpr QByteArrayView map(quint8 i) noexcept |
294 | { |
295 | return headerNames.viewAt(index: i); |
296 | } |
297 | }; |
298 | } // unnamed namespace |
299 | |
300 | // This index table contains the indexes of 'headerNames' entries (above) in alphabetical order. |
301 | // This allows a more efficient binary search for the names [O(logN)]. The 'headerNames' itself |
302 | // cannot be guaranteed to be in alphabetical order, as it must keep the same order as the |
303 | // WellKnownHeader enum, which may get appended over time. |
304 | // |
305 | // Note: when appending new enums, this must be regenerated |
306 | static constexpr quint8 orderedHeaderNameIndexes[] = { |
307 | 0, // a-im |
308 | 1, // accept |
309 | 2, // accept-additions |
310 | 3, // accept-ch |
311 | 172, // accept-charset |
312 | 4, // accept-datetime |
313 | 5, // accept-encoding |
314 | 6, // accept-features |
315 | 7, // accept-language |
316 | 8, // accept-patch |
317 | 9, // accept-post |
318 | 10, // accept-ranges |
319 | 11, // accept-signature |
320 | 12, // access-control-allow-credentials |
321 | 13, // access-control-allow-headers |
322 | 14, // access-control-allow-methods |
323 | 15, // access-control-allow-origin |
324 | 16, // access-control-expose-headers |
325 | 17, // access-control-max-age |
326 | 18, // access-control-request-headers |
327 | 19, // access-control-request-method |
328 | 20, // age |
329 | 21, // allow |
330 | 22, // alpn |
331 | 23, // alt-svc |
332 | 24, // alt-used |
333 | 25, // alternates |
334 | 26, // apply-to-redirect-ref |
335 | 27, // authentication-control |
336 | 28, // authentication-info |
337 | 29, // authorization |
338 | 173, // c-pep-info |
339 | 30, // cache-control |
340 | 31, // cache-status |
341 | 32, // cal-managed-id |
342 | 33, // caldav-timezones |
343 | 34, // capsule-protocol |
344 | 35, // cdn-cache-control |
345 | 36, // cdn-loop |
346 | 37, // cert-not-after |
347 | 38, // cert-not-before |
348 | 39, // clear-site-data |
349 | 40, // client-cert |
350 | 41, // client-cert-chain |
351 | 42, // close |
352 | 43, // connection |
353 | 44, // content-digest |
354 | 45, // content-disposition |
355 | 46, // content-encoding |
356 | 47, // content-id |
357 | 48, // content-language |
358 | 49, // content-length |
359 | 50, // content-location |
360 | 51, // content-range |
361 | 52, // content-security-policy |
362 | 53, // content-security-policy-report-only |
363 | 54, // content-type |
364 | 55, // cookie |
365 | 56, // cross-origin-embedder-policy |
366 | 57, // cross-origin-embedder-policy-report-only |
367 | 58, // cross-origin-opener-policy |
368 | 59, // cross-origin-opener-policy-report-only |
369 | 60, // cross-origin-resource-policy |
370 | 61, // dasl |
371 | 62, // date |
372 | 63, // dav |
373 | 64, // delta-base |
374 | 65, // depth |
375 | 66, // destination |
376 | 67, // differential-id |
377 | 68, // dpop |
378 | 69, // dpop-nonce |
379 | 70, // early-data |
380 | 71, // etag |
381 | 72, // expect |
382 | 73, // expect-ct |
383 | 74, // expires |
384 | 75, // forwarded |
385 | 76, // from |
386 | 77, // hobareg |
387 | 78, // host |
388 | 79, // if |
389 | 80, // if-match |
390 | 81, // if-modified-since |
391 | 82, // if-none-match |
392 | 83, // if-range |
393 | 84, // if-schedule-tag-match |
394 | 85, // if-unmodified-since |
395 | 86, // im |
396 | 87, // include-referred-token-binding-id |
397 | 88, // keep-alive |
398 | 89, // label |
399 | 90, // last-event-id |
400 | 91, // last-modified |
401 | 92, // link |
402 | 93, // location |
403 | 94, // lock-token |
404 | 95, // max-forwards |
405 | 96, // memento-datetime |
406 | 97, // meter |
407 | 98, // mime-version |
408 | 99, // negotiate |
409 | 100, // nel |
410 | 101, // odata-entityid |
411 | 102, // odata-isolation |
412 | 103, // odata-maxversion |
413 | 104, // odata-version |
414 | 105, // optional-www-authenticate |
415 | 106, // ordering-type |
416 | 107, // origin |
417 | 108, // origin-agent-cluster |
418 | 109, // oscore |
419 | 110, // oslc-core-version |
420 | 111, // overwrite |
421 | 112, // ping-from |
422 | 113, // ping-to |
423 | 114, // position |
424 | 174, // pragma |
425 | 115, // prefer |
426 | 116, // preference-applied |
427 | 117, // priority |
428 | 175, // protocol-info |
429 | 176, // protocol-query |
430 | 118, // proxy-authenticate |
431 | 119, // proxy-authentication-info |
432 | 120, // proxy-authorization |
433 | 121, // proxy-status |
434 | 122, // public-key-pins |
435 | 123, // public-key-pins-report-only |
436 | 124, // range |
437 | 125, // redirect-ref |
438 | 126, // referer |
439 | 127, // refresh |
440 | 128, // replay-nonce |
441 | 129, // repr-digest |
442 | 130, // retry-after |
443 | 131, // schedule-reply |
444 | 132, // schedule-tag |
445 | 133, // sec-purpose |
446 | 134, // sec-token-binding |
447 | 135, // sec-websocket-accept |
448 | 136, // sec-websocket-extensions |
449 | 137, // sec-websocket-key |
450 | 138, // sec-websocket-protocol |
451 | 139, // sec-websocket-version |
452 | 140, // server |
453 | 141, // server-timing |
454 | 142, // set-cookie |
455 | 143, // signature |
456 | 144, // signature-input |
457 | 145, // slug |
458 | 146, // soapaction |
459 | 147, // status-uri |
460 | 148, // strict-transport-security |
461 | 149, // sunset |
462 | 150, // surrogate-capability |
463 | 151, // surrogate-control |
464 | 152, // tcn |
465 | 153, // te |
466 | 154, // timeout |
467 | 155, // topic |
468 | 156, // traceparent |
469 | 157, // tracestate |
470 | 158, // trailer |
471 | 159, // transfer-encoding |
472 | 160, // ttl |
473 | 161, // upgrade |
474 | 162, // urgency |
475 | 163, // user-agent |
476 | 164, // variant-vary |
477 | 165, // vary |
478 | 166, // via |
479 | 167, // want-content-digest |
480 | 168, // want-repr-digest |
481 | 169, // www-authenticate |
482 | 170, // x-content-type-options |
483 | 171, // x-frame-options |
484 | }; |
485 | static_assert(std::size(orderedHeaderNameIndexes) == size_t(headerNames.count())); |
486 | static_assert(q20::is_sorted(first: std::begin(arr: orderedHeaderNameIndexes), |
487 | last: std::end(arr: orderedHeaderNameIndexes), |
488 | p: ByIndirectHeaderName{})); |
489 | |
490 | /*! |
491 | \enum QHttpHeaders::WellKnownHeader |
492 | |
493 | List of well known headers as per |
494 | \l {https://www.iana.org/assignments/http-fields}{IANA registry}. |
495 | |
496 | \value AIM |
497 | \value Accept |
498 | \value AcceptAdditions |
499 | \value AcceptCH |
500 | \value AcceptDatetime |
501 | \value AcceptEncoding |
502 | \value AcceptFeatures |
503 | \value AcceptLanguage |
504 | \value AcceptPatch |
505 | \value AcceptPost |
506 | \value AcceptRanges |
507 | \value AcceptSignature |
508 | \value AccessControlAllowCredentials |
509 | \value AccessControlAllowHeaders |
510 | \value AccessControlAllowMethods |
511 | \value AccessControlAllowOrigin |
512 | \value AccessControlExposeHeaders |
513 | \value AccessControlMaxAge |
514 | \value AccessControlRequestHeaders |
515 | \value AccessControlRequestMethod |
516 | \value Age |
517 | \value Allow |
518 | \value ALPN |
519 | \value AltSvc |
520 | \value AltUsed |
521 | \value Alternates |
522 | \value ApplyToRedirectRef |
523 | \value AuthenticationControl |
524 | \value AuthenticationInfo |
525 | \value Authorization |
526 | \value CacheControl |
527 | \value CacheStatus |
528 | \value CalManagedID |
529 | \value CalDAVTimezones |
530 | \value CapsuleProtocol |
531 | \value CDNCacheControl |
532 | \value CDNLoop |
533 | \value CertNotAfter |
534 | \value CertNotBefore |
535 | \value ClearSiteData |
536 | \value ClientCert |
537 | \value ClientCertChain |
538 | \value Close |
539 | \value Connection |
540 | \value ContentDigest |
541 | \value ContentDisposition |
542 | \value ContentEncoding |
543 | \value ContentID |
544 | \value ContentLanguage |
545 | \value ContentLength |
546 | \value ContentLocation |
547 | \value ContentRange |
548 | \value ContentSecurityPolicy |
549 | \value ContentSecurityPolicyReportOnly |
550 | \value ContentType |
551 | \value Cookie |
552 | \value CrossOriginEmbedderPolicy |
553 | \value CrossOriginEmbedderPolicyReportOnly |
554 | \value CrossOriginOpenerPolicy |
555 | \value CrossOriginOpenerPolicyReportOnly |
556 | \value CrossOriginResourcePolicy |
557 | \value DASL |
558 | \value Date |
559 | \value DAV |
560 | \value DeltaBase |
561 | \value Depth |
562 | \value Destination |
563 | \value DifferentialID |
564 | \value DPoP |
565 | \value DPoPNonce |
566 | \value EarlyData |
567 | \value ETag |
568 | \value Expect |
569 | \value ExpectCT |
570 | \value Expires |
571 | \value Forwarded |
572 | \value From |
573 | \value Hobareg |
574 | \value Host |
575 | \value If |
576 | \value IfMatch |
577 | \value IfModifiedSince |
578 | \value IfNoneMatch |
579 | \value IfRange |
580 | \value IfScheduleTagMatch |
581 | \value IfUnmodifiedSince |
582 | \value IM |
583 | \value IncludeReferredTokenBindingID |
584 | \value KeepAlive |
585 | \value Label |
586 | \value LastEventID |
587 | \value LastModified |
588 | \value Link |
589 | \value Location |
590 | \value LockToken |
591 | \value MaxForwards |
592 | \value MementoDatetime |
593 | \value Meter |
594 | \value MIMEVersion |
595 | \value Negotiate |
596 | \value NEL |
597 | \value ODataEntityId |
598 | \value ODataIsolation |
599 | \value ODataMaxVersion |
600 | \value ODataVersion |
601 | \value OptionalWWWAuthenticate |
602 | \value OrderingType |
603 | \value Origin |
604 | \value OriginAgentCluster |
605 | \value OSCORE |
606 | \value OSLCCoreVersion |
607 | \value Overwrite |
608 | \value PingFrom |
609 | \value PingTo |
610 | \value Position |
611 | \value Prefer |
612 | \value PreferenceApplied |
613 | \value Priority |
614 | \value ProxyAuthenticate |
615 | \value ProxyAuthenticationInfo |
616 | \value ProxyAuthorization |
617 | \value ProxyStatus |
618 | \value PublicKeyPins |
619 | \value PublicKeyPinsReportOnly |
620 | \value Range |
621 | \value RedirectRef |
622 | \value Referer |
623 | \value Refresh |
624 | \value ReplayNonce |
625 | \value ReprDigest |
626 | \value RetryAfter |
627 | \value ScheduleReply |
628 | \value ScheduleTag |
629 | \value SecPurpose |
630 | \value SecTokenBinding |
631 | \value SecWebSocketAccept |
632 | \value SecWebSocketExtensions |
633 | \value SecWebSocketKey |
634 | \value SecWebSocketProtocol |
635 | \value SecWebSocketVersion |
636 | \value Server |
637 | \value ServerTiming |
638 | \value SetCookie |
639 | \value Signature |
640 | \value SignatureInput |
641 | \value SLUG |
642 | \value SoapAction |
643 | \value StatusURI |
644 | \value StrictTransportSecurity |
645 | \value Sunset |
646 | \value SurrogateCapability |
647 | \value SurrogateControl |
648 | \value TCN |
649 | \value TE |
650 | \value Timeout |
651 | \value Topic |
652 | \value Traceparent |
653 | \value Tracestate |
654 | \value Trailer |
655 | \value TransferEncoding |
656 | \value TTL |
657 | \value Upgrade |
658 | \value Urgency |
659 | \value UserAgent |
660 | \value VariantVary |
661 | \value Vary |
662 | \value Via |
663 | \value WantContentDigest |
664 | \value WantReprDigest |
665 | \value WWWAuthenticate |
666 | \value XContentTypeOptions |
667 | \value XFrameOptions |
668 | \value AcceptCharset |
669 | \value CPEPInfo |
670 | \value Pragma |
671 | \value ProtocolInfo |
672 | \value ProtocolQuery |
673 | */ |
674 | |
675 | static QByteArray fieldToByteArray(QLatin1StringView s) noexcept |
676 | { |
677 | return QByteArray(s.data(), s.size()); |
678 | } |
679 | |
680 | static QByteArray fieldToByteArray(QUtf8StringView s) noexcept |
681 | { |
682 | return QByteArray(s.data(), s.size()); |
683 | } |
684 | |
685 | static QByteArray fieldToByteArray(QStringView s) |
686 | { |
687 | return s.toLatin1(); |
688 | } |
689 | |
690 | static QByteArray normalizedName(QAnyStringView name) |
691 | { |
692 | return name.visit(v: [](auto name){ return fieldToByteArray(name); }).toLower(); |
693 | } |
694 | |
695 | struct HeaderName |
696 | { |
697 | explicit HeaderName(QHttpHeaders::WellKnownHeader name) : data(name) |
698 | { |
699 | } |
700 | |
701 | explicit HeaderName(QAnyStringView name) |
702 | { |
703 | auto nname = normalizedName(name); |
704 | if (auto h = HeaderName::toWellKnownHeader(name: nname)) |
705 | data = *h; |
706 | else |
707 | data = std::move(nname); |
708 | } |
709 | |
710 | // Returns an enum corresponding with the 'name' if possible. Uses binary search (O(logN)). |
711 | // The function doesn't normalize the data; needs to be done by the caller if needed |
712 | static std::optional<QHttpHeaders::WellKnownHeader> toWellKnownHeader(QByteArrayView name) noexcept |
713 | { |
714 | auto indexesBegin = std::cbegin(cont: orderedHeaderNameIndexes); |
715 | auto indexesEnd = std::cend(cont: orderedHeaderNameIndexes); |
716 | |
717 | auto result = std::lower_bound(first: indexesBegin, last: indexesEnd, val: name, comp: ByIndirectHeaderName{}); |
718 | |
719 | if (result != indexesEnd && name == headerNames[*result]) |
720 | return static_cast<QHttpHeaders::WellKnownHeader>(*result); |
721 | return std::nullopt; |
722 | } |
723 | |
724 | QByteArrayView asView() const noexcept |
725 | { |
726 | return std::visit(visitor: [](const auto &arg) -> QByteArrayView { |
727 | using T = decltype(arg); |
728 | if constexpr (std::is_same_v<T, const QByteArray &>) |
729 | return arg; |
730 | else if constexpr (std::is_same_v<T, const QHttpHeaders::WellKnownHeader &>) |
731 | return headerNames.viewAt(index: qToUnderlying(arg)); |
732 | else |
733 | static_assert(QtPrivate::type_dependent_false<T>()); |
734 | }, variants: data); |
735 | } |
736 | |
737 | QByteArray asByteArray() const noexcept |
738 | { |
739 | return std::visit(visitor: [](const auto &arg) -> QByteArray { |
740 | using T = decltype(arg); |
741 | if constexpr (std::is_same_v<T, const QByteArray &>) { |
742 | return arg; |
743 | } else if constexpr (std::is_same_v<T, const QHttpHeaders::WellKnownHeader &>) { |
744 | const auto view = headerNames.viewAt(index: qToUnderlying(arg)); |
745 | return QByteArray::fromRawData(data: view.constData(), size: view.size()); |
746 | } else { |
747 | static_assert(QtPrivate::type_dependent_false<T>()); |
748 | } |
749 | }, variants: data); |
750 | } |
751 | |
752 | private: |
753 | // Store the data as 'enum' whenever possible; more performant, and comparison relies on that |
754 | std::variant<QHttpHeaders::WellKnownHeader, QByteArray> data; |
755 | |
756 | friend bool comparesEqual(const HeaderName &lhs, const HeaderName &rhs) noexcept |
757 | { |
758 | // Here we compare two std::variants, which will return false if the types don't match. |
759 | // That is beneficial here because we avoid unnecessary comparisons; but it also means |
760 | // we must always store the data as WellKnownHeader when possible (in other words, if |
761 | // we get a string that is mappable to a WellKnownHeader). To guard against accidental |
762 | // misuse, the 'data' is private and the constructors must be used. |
763 | return lhs.data == rhs.data; |
764 | } |
765 | Q_DECLARE_EQUALITY_COMPARABLE(HeaderName) |
766 | }; |
767 | |
768 | // A clarification on case-sensitivity: |
769 | // - Header *names* are case-insensitive; Content-Type and content-type are considered equal |
770 | // - Header *values* are case-sensitive |
771 | // (In addition, the HTTP/2 and HTTP/3 standards mandate that all headers must be lower-cased when |
772 | // encoded into transmission) |
773 | struct Header { |
774 | HeaderName name; |
775 | QByteArray value; |
776 | }; |
777 | |
778 | auto headerNameMatches(const HeaderName &name) |
779 | { |
780 | return [&name](const Header &header) { return header.name == name; }; |
781 | } |
782 | |
783 | class QHttpHeadersPrivate : public QSharedData |
784 | { |
785 | public: |
786 | QHttpHeadersPrivate() = default; |
787 | |
788 | // The 'Self' is supplied as parameter to static functions so that |
789 | // we can define common methods which 'detach()' the private itself. |
790 | using Self = QExplicitlySharedDataPointer<QHttpHeadersPrivate>; |
791 | static void removeAll(Self &d, const HeaderName &name); |
792 | static void replaceOrAppend(Self &d, const HeaderName &name, const QByteArray &value); |
793 | |
794 | void combinedValue(const HeaderName &name, QByteArray &result) const; |
795 | void values(const HeaderName &name, QList<QByteArray> &result) const; |
796 | QByteArrayView value(const HeaderName &name, QByteArrayView defaultValue) const noexcept; |
797 | |
798 | QList<Header> headers; |
799 | }; |
800 | |
801 | QT_DEFINE_QESDP_SPECIALIZATION_DTOR(QHttpHeadersPrivate) |
802 | template <> void QExplicitlySharedDataPointer<QHttpHeadersPrivate>::detach() |
803 | { |
804 | if (!d) { |
805 | d = new QHttpHeadersPrivate(); |
806 | d->ref.ref(); |
807 | } else if (d->ref.loadRelaxed() != 1) { |
808 | detach_helper(); |
809 | } |
810 | } |
811 | |
812 | void QHttpHeadersPrivate::removeAll(Self &d, const HeaderName &name) |
813 | { |
814 | const auto it = std::find_if(first: d->headers.cbegin(), last: d->headers.cend(), pred: headerNameMatches(name)); |
815 | |
816 | if (it != d->headers.cend()) { |
817 | // Found something to remove, calculate offset so we can proceed from the match-location |
818 | const auto matchOffset = it - d->headers.cbegin(); |
819 | d.detach(); |
820 | // Rearrange all matches to the end and erase them |
821 | d->headers.erase(abegin: std::remove_if(first: d->headers.begin() + matchOffset, last: d->headers.end(), |
822 | pred: headerNameMatches(name)), |
823 | aend: d->headers.end()); |
824 | } |
825 | } |
826 | |
827 | void QHttpHeadersPrivate::combinedValue(const HeaderName &name, QByteArray &result) const |
828 | { |
829 | const char* separator = ""; |
830 | for (const auto &h : std::as_const(t: headers)) { |
831 | if (h.name == name) { |
832 | result.append(s: separator); |
833 | result.append(a: h.value); |
834 | separator = ", "; |
835 | } |
836 | } |
837 | } |
838 | |
839 | void QHttpHeadersPrivate::values(const HeaderName &name, QList<QByteArray> &result) const |
840 | { |
841 | for (const auto &h : std::as_const(t: headers)) { |
842 | if (h.name == name) |
843 | result.append(t: h.value); |
844 | } |
845 | } |
846 | |
847 | QByteArrayView QHttpHeadersPrivate::value(const HeaderName &name, QByteArrayView defaultValue) const noexcept |
848 | { |
849 | for (const auto &h : std::as_const(t: headers)) { |
850 | if (h.name == name) |
851 | return h.value; |
852 | } |
853 | return defaultValue; |
854 | } |
855 | |
856 | void QHttpHeadersPrivate::replaceOrAppend(Self &d, const HeaderName &name, const QByteArray &value) |
857 | { |
858 | d.detach(); |
859 | auto it = std::find_if(first: d->headers.begin(), last: d->headers.end(), pred: headerNameMatches(name)); |
860 | if (it != d->headers.end()) { |
861 | // Found something to replace => replace, and then rearrange any remaining |
862 | // matches to the end and erase them |
863 | it->value = value; |
864 | d->headers.erase( |
865 | abegin: std::remove_if(first: it + 1, last: d->headers.end(), pred: headerNameMatches(name)), |
866 | aend: d->headers.end()); |
867 | } else { |
868 | // Found nothing to replace => append |
869 | d->headers.append(t: Header{.name: name, .value: value}); |
870 | } |
871 | } |
872 | |
873 | /*! |
874 | Creates a new QHttpHeaders object. |
875 | */ |
876 | QHttpHeaders::QHttpHeaders() noexcept : d() |
877 | { |
878 | } |
879 | |
880 | /*! |
881 | Creates a new QHttpHeaders object that is populated with |
882 | \a headers. |
883 | |
884 | \sa {Allowed field name and value characters} |
885 | */ |
886 | QHttpHeaders QHttpHeaders::fromListOfPairs(const QList<std::pair<QByteArray, QByteArray>> &headers) |
887 | { |
888 | QHttpHeaders h; |
889 | h.reserve(size: headers.size()); |
890 | for (const auto &header : headers) |
891 | h.append(name: header.first, value: header.second); |
892 | return h; |
893 | } |
894 | |
895 | /*! |
896 | Creates a new QHttpHeaders object that is populated with |
897 | \a headers. |
898 | |
899 | \sa {Allowed field name and value characters} |
900 | */ |
901 | QHttpHeaders QHttpHeaders::fromMultiMap(const QMultiMap<QByteArray, QByteArray> &headers) |
902 | { |
903 | QHttpHeaders h; |
904 | h.reserve(size: headers.size()); |
905 | for (const auto &[name,value] : headers.asKeyValueRange()) |
906 | h.append(name, value); |
907 | return h; |
908 | } |
909 | |
910 | /*! |
911 | Creates a new QHttpHeaders object that is populated with |
912 | \a headers. |
913 | |
914 | \sa {Allowed field name and value characters} |
915 | */ |
916 | QHttpHeaders QHttpHeaders::fromMultiHash(const QMultiHash<QByteArray, QByteArray> &headers) |
917 | { |
918 | QHttpHeaders h; |
919 | h.reserve(size: headers.size()); |
920 | for (const auto &[name,value] : headers.asKeyValueRange()) |
921 | h.append(name, value); |
922 | return h; |
923 | } |
924 | |
925 | /*! |
926 | Disposes of the headers object. |
927 | */ |
928 | QHttpHeaders::~QHttpHeaders() |
929 | = default; |
930 | |
931 | /*! |
932 | Creates a copy of \a other. |
933 | */ |
934 | QHttpHeaders::QHttpHeaders(const QHttpHeaders &other) |
935 | = default; |
936 | |
937 | /*! |
938 | Assigns the contents of \a other and returns a reference to this object. |
939 | */ |
940 | QHttpHeaders &QHttpHeaders::operator=(const QHttpHeaders &other) |
941 | = default; |
942 | |
943 | /*! |
944 | \fn QHttpHeaders::QHttpHeaders(QHttpHeaders &&other) noexcept |
945 | |
946 | Move-constructs the object from \a other, which will be left |
947 | \l{isEmpty()}{empty}. |
948 | */ |
949 | |
950 | /*! |
951 | \fn QHttpHeaders &QHttpHeaders::operator=(QHttpHeaders &&other) noexcept |
952 | |
953 | Move-assigns \a other and returns a reference to this object. |
954 | |
955 | \a other will be left \l{isEmpty()}{empty}. |
956 | */ |
957 | |
958 | /*! |
959 | \fn void QHttpHeaders::swap(QHttpHeaders &other) |
960 | |
961 | Swaps this QHttpHeaders with \a other. This function is very fast and |
962 | never fails. |
963 | */ |
964 | |
965 | #ifndef QT_NO_DEBUG_STREAM |
966 | /*! |
967 | \fn QDebug QHttpHeaders::operator<<(QDebug debug, |
968 | const QHttpHeaders &headers) |
969 | |
970 | Writes \a headers into \a debug stream. |
971 | */ |
972 | QDebug operator<<(QDebug debug, const QHttpHeaders &headers) |
973 | { |
974 | const QDebugStateSaver saver(debug); |
975 | debug.resetFormat().nospace(); |
976 | |
977 | debug << "QHttpHeaders("; |
978 | if (headers.d) { |
979 | debug << "headers = "; |
980 | const char *separator = ""; |
981 | for (const auto &h : headers.d->headers) { |
982 | debug << separator << h.name.asView() << ':' << h.value; |
983 | separator = " | "; |
984 | } |
985 | } |
986 | debug << ")"; |
987 | return debug; |
988 | } |
989 | #endif |
990 | |
991 | static constexpr auto isValidHttpHeaderNameChar = [](uchar c) noexcept |
992 | { |
993 | // RFC 9110 Chapters "5.1 Field Names" and "5.6.2 Tokens" |
994 | // field-name = token |
995 | // token = 1*tchar |
996 | // tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / |
997 | // "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" |
998 | // / DIGIT / ALPHA |
999 | // ; any VCHAR, except delimiters |
1000 | // (for explanation on VCHAR see isValidHttpHeaderValueChar) |
1001 | return (('A' <= c && c <= 'Z') |
1002 | || ('a' <= c && c <= 'z') |
1003 | || ('0' <= c && c <= '9') |
1004 | || ('#' <= c && c <= '\'') |
1005 | || ('^' <= c && c <= '`') |
1006 | || c == '|' || c == '~' || c == '!' || c == '*' || c == '+' || c == '-' || c == '.'); |
1007 | }; |
1008 | |
1009 | static bool headerNameValidImpl(QLatin1StringView name) noexcept |
1010 | { |
1011 | return std::all_of(first: name.begin(), last: name.end(), pred: isValidHttpHeaderNameChar); |
1012 | } |
1013 | |
1014 | static bool headerNameValidImpl(QUtf8StringView name) noexcept |
1015 | { |
1016 | // Traversing the UTF-8 string char-by-char is fine in this case as |
1017 | // the isValidHttpHeaderNameChar rejects any value above 0x7E. UTF-8 |
1018 | // only has bytes <= 0x7F if they truly represent that ASCII character. |
1019 | return headerNameValidImpl(name: QLatin1StringView(QByteArrayView(name))); |
1020 | } |
1021 | |
1022 | static bool headerNameValidImpl(QStringView name) noexcept |
1023 | { |
1024 | return std::all_of(first: name.begin(), last: name.end(), pred: [](QChar c) { |
1025 | return isValidHttpHeaderNameChar(c.toLatin1()); |
1026 | }); |
1027 | } |
1028 | |
1029 | static bool isValidHttpHeaderNameField(QAnyStringView name) noexcept |
1030 | { |
1031 | if (name.isEmpty()) { |
1032 | qCWarning(lcQHttpHeaders, "HTTP header name cannot be empty"); |
1033 | return false; |
1034 | } |
1035 | const bool valid = name.visit(v: [](auto name){ return headerNameValidImpl(name); }); |
1036 | if (!valid) |
1037 | qCWarning(lcQHttpHeaders, "HTTP header name contained illegal character(s)"); |
1038 | return valid; |
1039 | } |
1040 | |
1041 | static constexpr auto isValidHttpHeaderValueChar = [](uchar c) noexcept |
1042 | { |
1043 | // RFC 9110 Chapter 5.5, Field Values |
1044 | // field-value = *field-content |
1045 | // field-content = field-vchar |
1046 | // [ 1*( SP / HTAB / field-vchar ) field-vchar ] |
1047 | // field-vchar = VCHAR / obs-text |
1048 | // obs-text = %x80-FF |
1049 | // VCHAR is defined as "any visible US-ASCII character", and RFC 5234 B.1. |
1050 | // defines it as %x21-7E |
1051 | // Note: The ABNF above states that field-content and thus field-value cannot |
1052 | // start or end with SP/HTAB. The caller should handle this. |
1053 | return (c >= 0x80 // obs-text (extended ASCII) |
1054 | || (0x20 <= c && c <= 0x7E) // SP (0x20) + VCHAR |
1055 | || (c == 0x09)); // HTAB |
1056 | }; |
1057 | |
1058 | static bool headerValueValidImpl(QLatin1StringView value) noexcept |
1059 | { |
1060 | return std::all_of(first: value.begin(), last: value.end(), pred: isValidHttpHeaderValueChar); |
1061 | } |
1062 | |
1063 | static bool headerValueValidImpl(QUtf8StringView value) noexcept |
1064 | { |
1065 | // UTF-8 byte sequences are also used as values directly |
1066 | // => allow them as such. UTF-8 byte sequences for characters |
1067 | // outside of ASCII should all fit into obs-text (>= 0x80) |
1068 | // (see isValidHttpHeaderValueChar) |
1069 | return std::all_of(first: value.begin(), last: value.end(), pred: isValidHttpHeaderValueChar); |
1070 | } |
1071 | |
1072 | static bool headerValueValidImpl(QStringView value) noexcept |
1073 | { |
1074 | return std::all_of(first: value.begin(), last: value.end(), pred: [](QChar c) { |
1075 | return isValidHttpHeaderValueChar(c.toLatin1()); |
1076 | }); |
1077 | } |
1078 | |
1079 | static bool isValidHttpHeaderValueField(QAnyStringView value) noexcept |
1080 | { |
1081 | const bool valid = value.visit(v: [](auto value){ return headerValueValidImpl(value); }); |
1082 | if (!valid) |
1083 | qCWarning(lcQHttpHeaders, "HTTP header value contained illegal character(s)"); |
1084 | return valid; |
1085 | } |
1086 | |
1087 | static QByteArray normalizedValue(QAnyStringView value) |
1088 | { |
1089 | // Note on trimming away any leading or trailing whitespace of 'value': |
1090 | // RFC 9110 (HTTP 1.1, 2022, Chapter 5.5) does not allow leading or trailing whitespace |
1091 | // RFC 7230 (HTTP 1.1, 2014, Chapter 3.2) allows them optionally, but also mandates that |
1092 | // they are ignored during processing |
1093 | // RFC 7540 (HTTP/2) does not seem explicit about it |
1094 | // => for maximum compatibility, trim away any leading or trailing whitespace |
1095 | return value.visit(v: [](auto value){ return fieldToByteArray(value); }).trimmed(); |
1096 | } |
1097 | |
1098 | /*! |
1099 | Appends a header entry with \a name and \a value and returns \c true |
1100 | if successful. |
1101 | |
1102 | \sa append(QHttpHeaders::WellKnownHeader, QAnyStringView) |
1103 | \sa {Allowed field name and value characters} |
1104 | */ |
1105 | bool QHttpHeaders::append(QAnyStringView name, QAnyStringView value) |
1106 | { |
1107 | if (!isValidHttpHeaderNameField(name) || !isValidHttpHeaderValueField(value)) |
1108 | return false; |
1109 | |
1110 | d.detach(); |
1111 | d->headers.push_back(t: {.name: HeaderName{name}, .value: normalizedValue(value)}); |
1112 | return true; |
1113 | } |
1114 | |
1115 | /*! |
1116 | \overload append(QAnyStringView, QAnyStringView) |
1117 | */ |
1118 | bool QHttpHeaders::append(WellKnownHeader name, QAnyStringView value) |
1119 | { |
1120 | if (!isValidHttpHeaderValueField(value)) |
1121 | return false; |
1122 | |
1123 | d.detach(); |
1124 | d->headers.push_back(t: {.name: HeaderName{name}, .value: normalizedValue(value)}); |
1125 | return true; |
1126 | } |
1127 | |
1128 | /*! |
1129 | Inserts a header entry at index \a i, with \a name and \a value. The index |
1130 | must be valid (see \l size()). Returns whether the insert succeeded. |
1131 | |
1132 | \sa append(), |
1133 | insert(qsizetype, QHttpHeaders::WellKnownHeader, QAnyStringView), size() |
1134 | \sa {Allowed field name and value characters} |
1135 | */ |
1136 | bool QHttpHeaders::insert(qsizetype i, QAnyStringView name, QAnyStringView value) |
1137 | { |
1138 | verify(pos: i, n: 0); |
1139 | if (!isValidHttpHeaderNameField(name) || !isValidHttpHeaderValueField(value)) |
1140 | return false; |
1141 | |
1142 | d.detach(); |
1143 | d->headers.insert(i, t: {.name: HeaderName{name}, .value: normalizedValue(value)}); |
1144 | return true; |
1145 | } |
1146 | |
1147 | /*! |
1148 | \overload insert(qsizetype, QAnyStringView, QAnyStringView) |
1149 | */ |
1150 | bool QHttpHeaders::insert(qsizetype i, WellKnownHeader name, QAnyStringView value) |
1151 | { |
1152 | verify(pos: i, n: 0); |
1153 | if (!isValidHttpHeaderValueField(value)) |
1154 | return false; |
1155 | |
1156 | d.detach(); |
1157 | d->headers.insert(i, t: {.name: HeaderName{name}, .value: normalizedValue(value)}); |
1158 | return true; |
1159 | } |
1160 | |
1161 | /*! |
1162 | Replaces the header entry at index \a i, with \a name and \a newValue. |
1163 | The index must be valid (see \l size()). Returns whether the replace |
1164 | succeeded. |
1165 | |
1166 | \sa append(), |
1167 | replace(qsizetype, QHttpHeaders::WellKnownHeader, QAnyStringView), size() |
1168 | \sa {Allowed field name and value characters} |
1169 | */ |
1170 | bool QHttpHeaders::replace(qsizetype i, QAnyStringView name, QAnyStringView newValue) |
1171 | { |
1172 | verify(pos: i); |
1173 | if (!isValidHttpHeaderNameField(name) || !isValidHttpHeaderValueField(value: newValue)) |
1174 | return false; |
1175 | |
1176 | d.detach(); |
1177 | d->headers.replace(i, t: {.name: HeaderName{name}, .value: normalizedValue(value: newValue)}); |
1178 | return true; |
1179 | } |
1180 | |
1181 | /*! |
1182 | \overload replace(qsizetype, QAnyStringView, QAnyStringView) |
1183 | */ |
1184 | bool QHttpHeaders::replace(qsizetype i, WellKnownHeader name, QAnyStringView newValue) |
1185 | { |
1186 | verify(pos: i); |
1187 | if (!isValidHttpHeaderValueField(value: newValue)) |
1188 | return false; |
1189 | |
1190 | d.detach(); |
1191 | d->headers.replace(i, t: {.name: HeaderName{name}, .value: normalizedValue(value: newValue)}); |
1192 | return true; |
1193 | } |
1194 | |
1195 | /*! |
1196 | \since 6.8 |
1197 | |
1198 | If QHttpHeaders already contains \a name, replaces its value with |
1199 | \a newValue and removes possible additional \a name entries. |
1200 | If \a name didn't exist, appends a new entry. Returns \c true |
1201 | if successful. |
1202 | |
1203 | This function is a convenience method for setting a unique |
1204 | \a name : \a newValue header. For most headers the relative order does not |
1205 | matter, which allows reusing an existing entry if one exists. |
1206 | |
1207 | \sa replaceOrAppend(QAnyStringView, QAnyStringView) |
1208 | */ |
1209 | bool QHttpHeaders::replaceOrAppend(WellKnownHeader name, QAnyStringView newValue) |
1210 | { |
1211 | if (isEmpty()) |
1212 | return append(name, value: newValue); |
1213 | |
1214 | if (!isValidHttpHeaderValueField(value: newValue)) |
1215 | return false; |
1216 | |
1217 | QHttpHeadersPrivate::replaceOrAppend(d, name: HeaderName{name}, value: normalizedValue(value: newValue)); |
1218 | return true; |
1219 | } |
1220 | |
1221 | /*! |
1222 | \overload replaceOrAppend(WellKnownHeader, QAnyStringView) |
1223 | */ |
1224 | bool QHttpHeaders::replaceOrAppend(QAnyStringView name, QAnyStringView newValue) |
1225 | { |
1226 | if (isEmpty()) |
1227 | return append(name, value: newValue); |
1228 | |
1229 | if (!isValidHttpHeaderNameField(name) || !isValidHttpHeaderValueField(value: newValue)) |
1230 | return false; |
1231 | |
1232 | QHttpHeadersPrivate::replaceOrAppend(d, name: HeaderName{name}, value: normalizedValue(value: newValue)); |
1233 | return true; |
1234 | } |
1235 | |
1236 | /*! |
1237 | Returns whether the headers contain header with \a name. |
1238 | |
1239 | \sa contains(QHttpHeaders::WellKnownHeader) |
1240 | */ |
1241 | bool QHttpHeaders::contains(QAnyStringView name) const |
1242 | { |
1243 | if (isEmpty()) |
1244 | return false; |
1245 | |
1246 | return std::any_of(first: d->headers.cbegin(), last: d->headers.cend(), pred: headerNameMatches(name: HeaderName{name})); |
1247 | } |
1248 | |
1249 | /*! |
1250 | \overload has(QAnyStringView) |
1251 | */ |
1252 | bool QHttpHeaders::contains(WellKnownHeader name) const |
1253 | { |
1254 | if (isEmpty()) |
1255 | return false; |
1256 | |
1257 | return std::any_of(first: d->headers.cbegin(), last: d->headers.cend(), pred: headerNameMatches(name: HeaderName{name})); |
1258 | } |
1259 | |
1260 | /*! |
1261 | Removes the header \a name. |
1262 | |
1263 | \sa removeAt(), removeAll(QHttpHeaders::WellKnownHeader) |
1264 | */ |
1265 | void QHttpHeaders::removeAll(QAnyStringView name) |
1266 | { |
1267 | if (isEmpty()) |
1268 | return; |
1269 | |
1270 | return QHttpHeadersPrivate::removeAll(d, name: HeaderName(name)); |
1271 | } |
1272 | |
1273 | /*! |
1274 | \overload removeAll(QAnyStringView) |
1275 | */ |
1276 | void QHttpHeaders::removeAll(WellKnownHeader name) |
1277 | { |
1278 | if (isEmpty()) |
1279 | return; |
1280 | |
1281 | return QHttpHeadersPrivate::removeAll(d, name: HeaderName(name)); |
1282 | } |
1283 | |
1284 | /*! |
1285 | Removes the header at index \a i. The index \a i must be valid |
1286 | (see \l size()). |
1287 | |
1288 | \sa removeAll(QHttpHeaders::WellKnownHeader), |
1289 | removeAll(QAnyStringView), size() |
1290 | */ |
1291 | void QHttpHeaders::removeAt(qsizetype i) |
1292 | { |
1293 | verify(pos: i); |
1294 | d.detach(); |
1295 | d->headers.removeAt(i); |
1296 | } |
1297 | |
1298 | /*! |
1299 | Returns the value of the (first) header \a name, or \a defaultValue if it |
1300 | doesn't exist. |
1301 | |
1302 | \sa value(QHttpHeaders::WellKnownHeader, QByteArrayView) |
1303 | */ |
1304 | QByteArrayView QHttpHeaders::value(QAnyStringView name, QByteArrayView defaultValue) const noexcept |
1305 | { |
1306 | if (isEmpty()) |
1307 | return defaultValue; |
1308 | |
1309 | return d->value(name: HeaderName{name}, defaultValue); |
1310 | } |
1311 | |
1312 | /*! |
1313 | \overload value(QAnyStringView, QByteArrayView) |
1314 | */ |
1315 | QByteArrayView QHttpHeaders::value(WellKnownHeader name, QByteArrayView defaultValue) const noexcept |
1316 | { |
1317 | if (isEmpty()) |
1318 | return defaultValue; |
1319 | |
1320 | return d->value(name: HeaderName{name}, defaultValue); |
1321 | } |
1322 | |
1323 | /*! |
1324 | Returns the values of header \a name in a list. Returns an empty |
1325 | list if header with \a name doesn't exist. |
1326 | |
1327 | \sa values(QHttpHeaders::WellKnownHeader) |
1328 | */ |
1329 | QList<QByteArray> QHttpHeaders::values(QAnyStringView name) const |
1330 | { |
1331 | QList<QByteArray> result; |
1332 | if (isEmpty()) |
1333 | return result; |
1334 | |
1335 | d->values(name: HeaderName{name}, result); |
1336 | return result; |
1337 | } |
1338 | |
1339 | /*! |
1340 | \overload values(QAnyStringView) |
1341 | */ |
1342 | QList<QByteArray> QHttpHeaders::values(WellKnownHeader name) const |
1343 | { |
1344 | QList<QByteArray> result; |
1345 | if (isEmpty()) |
1346 | return result; |
1347 | |
1348 | d->values(name: HeaderName{name}, result); |
1349 | return result; |
1350 | } |
1351 | |
1352 | /*! |
1353 | Returns the header value at index \a i. The index \a i must be valid |
1354 | (see \l size()). |
1355 | |
1356 | \sa size(), value(), values(), combinedValue(), nameAt() |
1357 | */ |
1358 | QByteArrayView QHttpHeaders::valueAt(qsizetype i) const noexcept |
1359 | { |
1360 | verify(pos: i); |
1361 | return d->headers.at(i).value; |
1362 | } |
1363 | |
1364 | /*! |
1365 | Returns the header name at index \a i. The index \a i must be valid |
1366 | (see \l size()). |
1367 | |
1368 | Header names are case-insensitive, and the returned names are lower-cased. |
1369 | |
1370 | \sa size(), valueAt() |
1371 | */ |
1372 | QLatin1StringView QHttpHeaders::nameAt(qsizetype i) const noexcept |
1373 | { |
1374 | verify(pos: i); |
1375 | return QLatin1StringView{d->headers.at(i).name.asView()}; |
1376 | } |
1377 | |
1378 | /*! |
1379 | Returns the values of header \a name in a comma-combined string. |
1380 | Returns a \c null QByteArray if the header with \a name doesn't |
1381 | exist. |
1382 | |
1383 | \note Accessing the value(s) of 'Set-Cookie' header this way may not work |
1384 | as intended. It is a notable exception in the |
1385 | \l {https://datatracker.ietf.org/doc/html/rfc9110#name-field-order}{HTTP RFC} |
1386 | in that its values cannot be combined this way. Prefer \l values() instead. |
1387 | |
1388 | \sa values(QAnyStringView) |
1389 | */ |
1390 | QByteArray QHttpHeaders::combinedValue(QAnyStringView name) const |
1391 | { |
1392 | QByteArray result; |
1393 | if (isEmpty()) |
1394 | return result; |
1395 | |
1396 | d->combinedValue(name: HeaderName{name}, result); |
1397 | return result; |
1398 | } |
1399 | |
1400 | /*! |
1401 | \overload combinedValue(QAnyStringView) |
1402 | */ |
1403 | QByteArray QHttpHeaders::combinedValue(WellKnownHeader name) const |
1404 | { |
1405 | QByteArray result; |
1406 | if (isEmpty()) |
1407 | return result; |
1408 | |
1409 | d->combinedValue(name: HeaderName{name}, result); |
1410 | return result; |
1411 | } |
1412 | |
1413 | /*! |
1414 | Returns the number of header entries. |
1415 | */ |
1416 | qsizetype QHttpHeaders::size() const noexcept |
1417 | { |
1418 | if (!d) |
1419 | return 0; |
1420 | return d->headers.size(); |
1421 | } |
1422 | |
1423 | /*! |
1424 | Attempts to allocate memory for at least \a size header entries. |
1425 | |
1426 | If you know in advance how how many header entries there will |
1427 | be, you may call this function to prevent reallocations |
1428 | and memory fragmentation. |
1429 | */ |
1430 | void QHttpHeaders::reserve(qsizetype size) |
1431 | { |
1432 | d.detach(); |
1433 | d->headers.reserve(asize: size); |
1434 | } |
1435 | |
1436 | /*! |
1437 | \fn bool QHttpHeaders::isEmpty() const noexcept |
1438 | |
1439 | Returns \c true if the headers have size 0; otherwise returns \c false. |
1440 | |
1441 | \sa size() |
1442 | */ |
1443 | |
1444 | /*! |
1445 | Returns a header name corresponding to the provided \a name as a view. |
1446 | */ |
1447 | QByteArrayView QHttpHeaders::wellKnownHeaderName(WellKnownHeader name) noexcept |
1448 | { |
1449 | return headerNames[qToUnderlying(e: name)]; |
1450 | } |
1451 | |
1452 | /*! |
1453 | Returns the header entries as a list of (name, value) pairs. |
1454 | Header names are case-insensitive, and the returned names are lower-cased. |
1455 | */ |
1456 | QList<std::pair<QByteArray, QByteArray>> QHttpHeaders::toListOfPairs() const |
1457 | { |
1458 | QList<std::pair<QByteArray, QByteArray>> list; |
1459 | if (isEmpty()) |
1460 | return list; |
1461 | list.reserve(asize: size()); |
1462 | for (const auto & h : std::as_const(t&: d->headers)) |
1463 | list.append(t: {h.name.asByteArray(), h.value}); |
1464 | return list; |
1465 | } |
1466 | |
1467 | /*! |
1468 | Returns the header entries as a map from name to value(s). |
1469 | Header names are case-insensitive, and the returned names are lower-cased. |
1470 | */ |
1471 | QMultiMap<QByteArray, QByteArray> QHttpHeaders::toMultiMap() const |
1472 | { |
1473 | QMultiMap<QByteArray, QByteArray> map; |
1474 | if (isEmpty()) |
1475 | return map; |
1476 | for (const auto &h : std::as_const(t&: d->headers)) |
1477 | map.insert(key: h.name.asByteArray(), value: h.value); |
1478 | return map; |
1479 | } |
1480 | |
1481 | /*! |
1482 | Returns the header entries as a hash from name to value(s). |
1483 | Header names are case-insensitive, and the returned names are lower-cased. |
1484 | */ |
1485 | QMultiHash<QByteArray, QByteArray> QHttpHeaders::toMultiHash() const |
1486 | { |
1487 | QMultiHash<QByteArray, QByteArray> hash; |
1488 | if (isEmpty()) |
1489 | return hash; |
1490 | hash.reserve(size: size()); |
1491 | for (const auto &h : std::as_const(t&: d->headers)) |
1492 | hash.insert(key: h.name.asByteArray(), value: h.value); |
1493 | return hash; |
1494 | } |
1495 | |
1496 | /*! |
1497 | Clears all header entries. |
1498 | |
1499 | \sa size() |
1500 | */ |
1501 | void QHttpHeaders::clear() |
1502 | { |
1503 | if (isEmpty()) |
1504 | return; |
1505 | d.detach(); |
1506 | d->headers.clear(); |
1507 | } |
1508 | |
1509 | QT_END_NAMESPACE |
1510 |
Definitions
- lcQHttpHeaders
- headerNames
- ByIndirectHeaderName
- operator()
- operator()
- operator()
- operator()
- map
- orderedHeaderNameIndexes
- fieldToByteArray
- fieldToByteArray
- fieldToByteArray
- normalizedName
- HeaderName
- HeaderName
- HeaderName
- toWellKnownHeader
- asView
- asByteArray
- comparesEqual
- Header
- headerNameMatches
- QHttpHeadersPrivate
- QHttpHeadersPrivate
- detach
- removeAll
- combinedValue
- values
- value
- replaceOrAppend
- QHttpHeaders
- fromListOfPairs
- fromMultiMap
- fromMultiHash
- ~QHttpHeaders
- QHttpHeaders
- operator=
- operator<<
- isValidHttpHeaderNameChar
- headerNameValidImpl
- headerNameValidImpl
- headerNameValidImpl
- isValidHttpHeaderNameField
- isValidHttpHeaderValueChar
- headerValueValidImpl
- headerValueValidImpl
- headerValueValidImpl
- isValidHttpHeaderValueField
- normalizedValue
- append
- append
- insert
- insert
- replace
- replace
- replaceOrAppend
- replaceOrAppend
- contains
- contains
- removeAll
- removeAll
- removeAt
- value
- value
- values
- values
- valueAt
- nameAt
- combinedValue
- combinedValue
- size
- reserve
- wellKnownHeaderName
- toListOfPairs
- toMultiMap
- toMultiHash
Start learning QML with our Intro Training
Find out more