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