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