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