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