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