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
19QT_BEGIN_NAMESPACE
20
21Q_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 strings: "a-im",
92 strings: "accept",
93 strings: "accept-additions",
94 strings: "accept-ch",
95 strings: "accept-datetime",
96 strings: "accept-encoding",
97 strings: "accept-features",
98 strings: "accept-language",
99 strings: "accept-patch",
100 strings: "accept-post",
101 strings: "accept-ranges",
102 strings: "accept-signature",
103 strings: "access-control-allow-credentials",
104 strings: "access-control-allow-headers",
105 strings: "access-control-allow-methods",
106 strings: "access-control-allow-origin",
107 strings: "access-control-expose-headers",
108 strings: "access-control-max-age",
109 strings: "access-control-request-headers",
110 strings: "access-control-request-method",
111 strings: "age",
112 strings: "allow",
113 strings: "alpn",
114 strings: "alt-svc",
115 strings: "alt-used",
116 strings: "alternates",
117 strings: "apply-to-redirect-ref",
118 strings: "authentication-control",
119 strings: "authentication-info",
120 strings: "authorization",
121 strings: "cache-control",
122 strings: "cache-status",
123 strings: "cal-managed-id",
124 strings: "caldav-timezones",
125 strings: "capsule-protocol",
126 strings: "cdn-cache-control",
127 strings: "cdn-loop",
128 strings: "cert-not-after",
129 strings: "cert-not-before",
130 strings: "clear-site-data",
131 strings: "client-cert",
132 strings: "client-cert-chain",
133 strings: "close",
134 strings: "connection",
135 strings: "content-digest",
136 strings: "content-disposition",
137 strings: "content-encoding",
138 strings: "content-id",
139 strings: "content-language",
140 strings: "content-length",
141 strings: "content-location",
142 strings: "content-range",
143 strings: "content-security-policy",
144 strings: "content-security-policy-report-only",
145 strings: "content-type",
146 strings: "cookie",
147 strings: "cross-origin-embedder-policy",
148 strings: "cross-origin-embedder-policy-report-only",
149 strings: "cross-origin-opener-policy",
150 strings: "cross-origin-opener-policy-report-only",
151 strings: "cross-origin-resource-policy",
152 strings: "dasl",
153 strings: "date",
154 strings: "dav",
155 strings: "delta-base",
156 strings: "depth",
157 strings: "destination",
158 strings: "differential-id",
159 strings: "dpop",
160 strings: "dpop-nonce",
161 strings: "early-data",
162 strings: "etag",
163 strings: "expect",
164 strings: "expect-ct",
165 strings: "expires",
166 strings: "forwarded",
167 strings: "from",
168 strings: "hobareg",
169 strings: "host",
170 strings: "if",
171 strings: "if-match",
172 strings: "if-modified-since",
173 strings: "if-none-match",
174 strings: "if-range",
175 strings: "if-schedule-tag-match",
176 strings: "if-unmodified-since",
177 strings: "im",
178 strings: "include-referred-token-binding-id",
179 strings: "keep-alive",
180 strings: "label",
181 strings: "last-event-id",
182 strings: "last-modified",
183 strings: "link",
184 strings: "location",
185 strings: "lock-token",
186 strings: "max-forwards",
187 strings: "memento-datetime",
188 strings: "meter",
189 strings: "mime-version",
190 strings: "negotiate",
191 strings: "nel",
192 strings: "odata-entityid",
193 strings: "odata-isolation",
194 strings: "odata-maxversion",
195 strings: "odata-version",
196 strings: "optional-www-authenticate",
197 strings: "ordering-type",
198 strings: "origin",
199 strings: "origin-agent-cluster",
200 strings: "oscore",
201 strings: "oslc-core-version",
202 strings: "overwrite",
203 strings: "ping-from",
204 strings: "ping-to",
205 strings: "position",
206 strings: "prefer",
207 strings: "preference-applied",
208 strings: "priority",
209 strings: "proxy-authenticate",
210 strings: "proxy-authentication-info",
211 strings: "proxy-authorization",
212 strings: "proxy-status",
213 strings: "public-key-pins",
214 strings: "public-key-pins-report-only",
215 strings: "range",
216 strings: "redirect-ref",
217 strings: "referer",
218 strings: "refresh",
219 strings: "replay-nonce",
220 strings: "repr-digest",
221 strings: "retry-after",
222 strings: "schedule-reply",
223 strings: "schedule-tag",
224 strings: "sec-purpose",
225 strings: "sec-token-binding",
226 strings: "sec-websocket-accept",
227 strings: "sec-websocket-extensions",
228 strings: "sec-websocket-key",
229 strings: "sec-websocket-protocol",
230 strings: "sec-websocket-version",
231 strings: "server",
232 strings: "server-timing",
233 strings: "set-cookie",
234 strings: "signature",
235 strings: "signature-input",
236 strings: "slug",
237 strings: "soapaction",
238 strings: "status-uri",
239 strings: "strict-transport-security",
240 strings: "sunset",
241 strings: "surrogate-capability",
242 strings: "surrogate-control",
243 strings: "tcn",
244 strings: "te",
245 strings: "timeout",
246 strings: "topic",
247 strings: "traceparent",
248 strings: "tracestate",
249 strings: "trailer",
250 strings: "transfer-encoding",
251 strings: "ttl",
252 strings: "upgrade",
253 strings: "urgency",
254 strings: "user-agent",
255 strings: "variant-vary",
256 strings: "vary",
257 strings: "via",
258 strings: "want-content-digest",
259 strings: "want-repr-digest",
260 strings: "www-authenticate",
261 strings: "x-content-type-options",
262 strings: "x-frame-options",
263 // IANA Deprecated status:
264 strings: "accept-charset",
265 strings: "c-pep-info",
266 strings: "pragma",
267 strings: "protocol-info",
268 strings: "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(i: lhs), map(i: rhs));
278 }
279 constexpr bool operator()(quint8 lhs, QByteArrayView rhs) const noexcept
280 {
281 return (*this)(map(i: lhs), rhs);
282 }
283 constexpr bool operator()(QByteArrayView lhs, quint8 rhs) const noexcept
284 {
285 return (*this)(lhs, map(i: 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(index: 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()));
486static_assert(q20::is_sorted(first: std::begin(arr: orderedHeaderNameIndexes),
487 last: std::end(arr: orderedHeaderNameIndexes),
488 p: ByIndirectHeaderName{}));
489
490/*!
491 \enum QHttpHeaders::WellKnownHeader
492
493 List of well known headers as per
494 \l {https://www.iana.org/assignments/http-fields}{IANA registry}.
495
496 \value AIM
497 \value Accept
498 \value AcceptAdditions
499 \value AcceptCH
500 \value AcceptDatetime
501 \value AcceptEncoding
502 \value AcceptFeatures
503 \value AcceptLanguage
504 \value AcceptPatch
505 \value AcceptPost
506 \value AcceptRanges
507 \value AcceptSignature
508 \value AccessControlAllowCredentials
509 \value AccessControlAllowHeaders
510 \value AccessControlAllowMethods
511 \value AccessControlAllowOrigin
512 \value AccessControlExposeHeaders
513 \value AccessControlMaxAge
514 \value AccessControlRequestHeaders
515 \value AccessControlRequestMethod
516 \value Age
517 \value Allow
518 \value ALPN
519 \value AltSvc
520 \value AltUsed
521 \value Alternates
522 \value ApplyToRedirectRef
523 \value AuthenticationControl
524 \value AuthenticationInfo
525 \value Authorization
526 \value CacheControl
527 \value CacheStatus
528 \value CalManagedID
529 \value CalDAVTimezones
530 \value CapsuleProtocol
531 \value CDNCacheControl
532 \value CDNLoop
533 \value CertNotAfter
534 \value CertNotBefore
535 \value ClearSiteData
536 \value ClientCert
537 \value ClientCertChain
538 \value Close
539 \value Connection
540 \value ContentDigest
541 \value ContentDisposition
542 \value ContentEncoding
543 \value ContentID
544 \value ContentLanguage
545 \value ContentLength
546 \value ContentLocation
547 \value ContentRange
548 \value ContentSecurityPolicy
549 \value ContentSecurityPolicyReportOnly
550 \value ContentType
551 \value Cookie
552 \value CrossOriginEmbedderPolicy
553 \value CrossOriginEmbedderPolicyReportOnly
554 \value CrossOriginOpenerPolicy
555 \value CrossOriginOpenerPolicyReportOnly
556 \value CrossOriginResourcePolicy
557 \value DASL
558 \value Date
559 \value DAV
560 \value DeltaBase
561 \value Depth
562 \value Destination
563 \value DifferentialID
564 \value DPoP
565 \value DPoPNonce
566 \value EarlyData
567 \value ETag
568 \value Expect
569 \value ExpectCT
570 \value Expires
571 \value Forwarded
572 \value From
573 \value Hobareg
574 \value Host
575 \value If
576 \value IfMatch
577 \value IfModifiedSince
578 \value IfNoneMatch
579 \value IfRange
580 \value IfScheduleTagMatch
581 \value IfUnmodifiedSince
582 \value IM
583 \value IncludeReferredTokenBindingID
584 \value KeepAlive
585 \value Label
586 \value LastEventID
587 \value LastModified
588 \value Link
589 \value Location
590 \value LockToken
591 \value MaxForwards
592 \value MementoDatetime
593 \value Meter
594 \value MIMEVersion
595 \value Negotiate
596 \value NEL
597 \value ODataEntityId
598 \value ODataIsolation
599 \value ODataMaxVersion
600 \value ODataVersion
601 \value OptionalWWWAuthenticate
602 \value OrderingType
603 \value Origin
604 \value OriginAgentCluster
605 \value OSCORE
606 \value OSLCCoreVersion
607 \value Overwrite
608 \value PingFrom
609 \value PingTo
610 \value Position
611 \value Prefer
612 \value PreferenceApplied
613 \value Priority
614 \value ProxyAuthenticate
615 \value ProxyAuthenticationInfo
616 \value ProxyAuthorization
617 \value ProxyStatus
618 \value PublicKeyPins
619 \value PublicKeyPinsReportOnly
620 \value Range
621 \value RedirectRef
622 \value Referer
623 \value Refresh
624 \value ReplayNonce
625 \value ReprDigest
626 \value RetryAfter
627 \value ScheduleReply
628 \value ScheduleTag
629 \value SecPurpose
630 \value SecTokenBinding
631 \value SecWebSocketAccept
632 \value SecWebSocketExtensions
633 \value SecWebSocketKey
634 \value SecWebSocketProtocol
635 \value SecWebSocketVersion
636 \value Server
637 \value ServerTiming
638 \value SetCookie
639 \value Signature
640 \value SignatureInput
641 \value SLUG
642 \value SoapAction
643 \value StatusURI
644 \value StrictTransportSecurity
645 \value Sunset
646 \value SurrogateCapability
647 \value SurrogateControl
648 \value TCN
649 \value TE
650 \value Timeout
651 \value Topic
652 \value Traceparent
653 \value Tracestate
654 \value Trailer
655 \value TransferEncoding
656 \value TTL
657 \value Upgrade
658 \value Urgency
659 \value UserAgent
660 \value VariantVary
661 \value Vary
662 \value Via
663 \value WantContentDigest
664 \value WantReprDigest
665 \value WWWAuthenticate
666 \value XContentTypeOptions
667 \value XFrameOptions
668 \value AcceptCharset
669 \value CPEPInfo
670 \value Pragma
671 \value ProtocolInfo
672 \value ProtocolQuery
673*/
674
675static QByteArray fieldToByteArray(QLatin1StringView s) noexcept
676{
677 return QByteArray(s.data(), s.size());
678}
679
680static QByteArray fieldToByteArray(QUtf8StringView s) noexcept
681{
682 return QByteArray(s.data(), s.size());
683}
684
685static QByteArray fieldToByteArray(QStringView s)
686{
687 return s.toLatin1();
688}
689
690static QByteArray normalizedName(QAnyStringView name)
691{
692 return name.visit(v: [](auto name){ return fieldToByteArray(name); }).toLower();
693}
694
695struct HeaderName
696{
697 explicit HeaderName(QHttpHeaders::WellKnownHeader name) : data(name)
698 {
699 }
700
701 explicit HeaderName(QAnyStringView name)
702 {
703 auto nname = normalizedName(name);
704 if (auto h = HeaderName::toWellKnownHeader(name: nname))
705 data = *h;
706 else
707 data = std::move(nname);
708 }
709
710 // Returns an enum corresponding with the 'name' if possible. Uses binary search (O(logN)).
711 // The function doesn't normalize the data; needs to be done by the caller if needed
712 static std::optional<QHttpHeaders::WellKnownHeader> toWellKnownHeader(QByteArrayView name) noexcept
713 {
714 auto indexesBegin = std::cbegin(cont: orderedHeaderNameIndexes);
715 auto indexesEnd = std::cend(cont: orderedHeaderNameIndexes);
716
717 auto result = std::lower_bound(first: indexesBegin, last: indexesEnd, val: name, comp: ByIndirectHeaderName{});
718
719 if (result != indexesEnd && name == headerNames[*result])
720 return static_cast<QHttpHeaders::WellKnownHeader>(*result);
721 return std::nullopt;
722 }
723
724 QByteArrayView asView() const noexcept
725 {
726 return std::visit(visitor: [](const auto &arg) -> QByteArrayView {
727 using T = decltype(arg);
728 if constexpr (std::is_same_v<T, const QByteArray &>)
729 return arg;
730 else if constexpr (std::is_same_v<T, const QHttpHeaders::WellKnownHeader &>)
731 return headerNames.viewAt(index: qToUnderlying(arg));
732 else
733 static_assert(QtPrivate::type_dependent_false<T>());
734 }, variants: data);
735 }
736
737 QByteArray asByteArray() const noexcept
738 {
739 return std::visit(visitor: [](const auto &arg) -> QByteArray {
740 using T = decltype(arg);
741 if constexpr (std::is_same_v<T, const QByteArray &>) {
742 return arg;
743 } else if constexpr (std::is_same_v<T, const QHttpHeaders::WellKnownHeader &>) {
744 const auto view = headerNames.viewAt(index: qToUnderlying(arg));
745 return QByteArray::fromRawData(data: view.constData(), size: view.size());
746 } else {
747 static_assert(QtPrivate::type_dependent_false<T>());
748 }
749 }, variants: data);
750 }
751
752private:
753 // Store the data as 'enum' whenever possible; more performant, and comparison relies on that
754 std::variant<QHttpHeaders::WellKnownHeader, QByteArray> data;
755
756 friend bool comparesEqual(const HeaderName &lhs, const HeaderName &rhs) noexcept
757 {
758 // Here we compare two std::variants, which will return false if the types don't match.
759 // That is beneficial here because we avoid unnecessary comparisons; but it also means
760 // we must always store the data as WellKnownHeader when possible (in other words, if
761 // we get a string that is mappable to a WellKnownHeader). To guard against accidental
762 // misuse, the 'data' is private and the constructors must be used.
763 return lhs.data == rhs.data;
764 }
765 Q_DECLARE_EQUALITY_COMPARABLE(HeaderName)
766};
767
768// A clarification on case-sensitivity:
769// - Header *names* are case-insensitive; Content-Type and content-type are considered equal
770// - Header *values* are case-sensitive
771// (In addition, the HTTP/2 and HTTP/3 standards mandate that all headers must be lower-cased when
772// encoded into transmission)
773struct Header {
774 HeaderName name;
775 QByteArray value;
776};
777
778auto headerNameMatches(const HeaderName &name)
779{
780 return [&name](const Header &header) { return header.name == name; };
781}
782
783class QHttpHeadersPrivate : public QSharedData
784{
785public:
786 QHttpHeadersPrivate() = default;
787
788 // The 'Self' is supplied as parameter to static functions so that
789 // we can define common methods which 'detach()' the private itself.
790 using Self = QExplicitlySharedDataPointer<QHttpHeadersPrivate>;
791 static void removeAll(Self &d, const HeaderName &name);
792 static void replaceOrAppend(Self &d, const HeaderName &name, const QByteArray &value);
793
794 void combinedValue(const HeaderName &name, QByteArray &result) const;
795 void values(const HeaderName &name, QList<QByteArray> &result) const;
796 QByteArrayView value(const HeaderName &name, QByteArrayView defaultValue) const noexcept;
797
798 QList<Header> headers;
799};
800
801QT_DEFINE_QESDP_SPECIALIZATION_DTOR(QHttpHeadersPrivate)
802template <> void QExplicitlySharedDataPointer<QHttpHeadersPrivate>::detach()
803{
804 if (!d) {
805 d = new QHttpHeadersPrivate();
806 d->ref.ref();
807 } else if (d->ref.loadRelaxed() != 1) {
808 detach_helper();
809 }
810}
811
812void QHttpHeadersPrivate::removeAll(Self &d, const HeaderName &name)
813{
814 const auto it = std::find_if(first: d->headers.cbegin(), last: d->headers.cend(), pred: headerNameMatches(name));
815
816 if (it != d->headers.cend()) {
817 // Found something to remove, calculate offset so we can proceed from the match-location
818 const auto matchOffset = it - d->headers.cbegin();
819 d.detach();
820 // Rearrange all matches to the end and erase them
821 d->headers.erase(abegin: std::remove_if(first: d->headers.begin() + matchOffset, last: d->headers.end(),
822 pred: headerNameMatches(name)),
823 aend: d->headers.end());
824 }
825}
826
827void QHttpHeadersPrivate::combinedValue(const HeaderName &name, QByteArray &result) const
828{
829 const char* separator = "";
830 for (const auto &h : std::as_const(t: headers)) {
831 if (h.name == name) {
832 result.append(s: separator);
833 result.append(a: h.value);
834 separator = ", ";
835 }
836 }
837}
838
839void QHttpHeadersPrivate::values(const HeaderName &name, QList<QByteArray> &result) const
840{
841 for (const auto &h : std::as_const(t: headers)) {
842 if (h.name == name)
843 result.append(t: h.value);
844 }
845}
846
847QByteArrayView QHttpHeadersPrivate::value(const HeaderName &name, QByteArrayView defaultValue) const noexcept
848{
849 for (const auto &h : std::as_const(t: headers)) {
850 if (h.name == name)
851 return h.value;
852 }
853 return defaultValue;
854}
855
856void QHttpHeadersPrivate::replaceOrAppend(Self &d, const HeaderName &name, const QByteArray &value)
857{
858 d.detach();
859 auto it = std::find_if(first: d->headers.begin(), last: d->headers.end(), pred: headerNameMatches(name));
860 if (it != d->headers.end()) {
861 // Found something to replace => replace, and then rearrange any remaining
862 // matches to the end and erase them
863 it->value = value;
864 d->headers.erase(
865 abegin: std::remove_if(first: it + 1, last: d->headers.end(), pred: headerNameMatches(name)),
866 aend: d->headers.end());
867 } else {
868 // Found nothing to replace => append
869 d->headers.append(t: Header{.name: name, .value: value});
870 }
871}
872
873/*!
874 Creates a new QHttpHeaders object.
875*/
876QHttpHeaders::QHttpHeaders() noexcept : d()
877{
878}
879
880/*!
881 Creates a new QHttpHeaders object that is populated with
882 \a headers.
883
884 \sa {Allowed field name and value characters}
885*/
886QHttpHeaders QHttpHeaders::fromListOfPairs(const QList<std::pair<QByteArray, QByteArray>> &headers)
887{
888 QHttpHeaders h;
889 h.reserve(size: headers.size());
890 for (const auto &header : headers)
891 h.append(name: header.first, value: header.second);
892 return h;
893}
894
895/*!
896 Creates a new QHttpHeaders object that is populated with
897 \a headers.
898
899 \sa {Allowed field name and value characters}
900*/
901QHttpHeaders QHttpHeaders::fromMultiMap(const QMultiMap<QByteArray, QByteArray> &headers)
902{
903 QHttpHeaders h;
904 h.reserve(size: headers.size());
905 for (const auto &[name,value] : headers.asKeyValueRange())
906 h.append(name, value);
907 return h;
908}
909
910/*!
911 Creates a new QHttpHeaders object that is populated with
912 \a headers.
913
914 \sa {Allowed field name and value characters}
915*/
916QHttpHeaders QHttpHeaders::fromMultiHash(const QMultiHash<QByteArray, QByteArray> &headers)
917{
918 QHttpHeaders h;
919 h.reserve(size: headers.size());
920 for (const auto &[name,value] : headers.asKeyValueRange())
921 h.append(name, value);
922 return h;
923}
924
925/*!
926 Disposes of the headers object.
927*/
928QHttpHeaders::~QHttpHeaders()
929 = default;
930
931/*!
932 Creates a copy of \a other.
933*/
934QHttpHeaders::QHttpHeaders(const QHttpHeaders &other)
935 = default;
936
937/*!
938 Assigns the contents of \a other and returns a reference to this object.
939*/
940QHttpHeaders &QHttpHeaders::operator=(const QHttpHeaders &other)
941 = default;
942
943/*!
944 \fn QHttpHeaders::QHttpHeaders(QHttpHeaders &&other) noexcept
945
946 Move-constructs the object from \a other, which will be left
947 \l{isEmpty()}{empty}.
948*/
949
950/*!
951 \fn QHttpHeaders &QHttpHeaders::operator=(QHttpHeaders &&other) noexcept
952
953 Move-assigns \a other and returns a reference to this object.
954
955 \a other will be left \l{isEmpty()}{empty}.
956*/
957
958/*!
959 \fn void QHttpHeaders::swap(QHttpHeaders &other)
960
961 Swaps this QHttpHeaders with \a other. This function is very fast and
962 never fails.
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(first: name.begin(), last: name.end(), pred: 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(name: QLatin1StringView(QByteArrayView(name)));
1020}
1021
1022static bool headerNameValidImpl(QStringView name) noexcept
1023{
1024 return std::all_of(first: name.begin(), last: name.end(), pred: [](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(v: [](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(first: value.begin(), last: value.end(), pred: 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(first: value.begin(), last: value.end(), pred: isValidHttpHeaderValueChar);
1070}
1071
1072static bool headerValueValidImpl(QStringView value) noexcept
1073{
1074 return std::all_of(first: value.begin(), last: value.end(), pred: [](QChar c) {
1075 return isValidHttpHeaderValueChar(c.toLatin1());
1076 });
1077}
1078
1079static bool isValidHttpHeaderValueField(QAnyStringView value) noexcept
1080{
1081 const bool valid = value.visit(v: [](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(v: [](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(t: {.name: HeaderName{name}, .value: 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(t: {.name: HeaderName{name}, .value: 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(pos: i, n: 0);
1139 if (!isValidHttpHeaderNameField(name) || !isValidHttpHeaderValueField(value))
1140 return false;
1141
1142 d.detach();
1143 d->headers.insert(i, t: {.name: HeaderName{name}, .value: 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(pos: i, n: 0);
1153 if (!isValidHttpHeaderValueField(value))
1154 return false;
1155
1156 d.detach();
1157 d->headers.insert(i, t: {.name: HeaderName{name}, .value: 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(pos: i);
1173 if (!isValidHttpHeaderNameField(name) || !isValidHttpHeaderValueField(value: newValue))
1174 return false;
1175
1176 d.detach();
1177 d->headers.replace(i, t: {.name: HeaderName{name}, .value: normalizedValue(value: 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(pos: i);
1187 if (!isValidHttpHeaderValueField(value: newValue))
1188 return false;
1189
1190 d.detach();
1191 d->headers.replace(i, t: {.name: HeaderName{name}, .value: normalizedValue(value: 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, value: newValue);
1213
1214 if (!isValidHttpHeaderValueField(value: newValue))
1215 return false;
1216
1217 QHttpHeadersPrivate::replaceOrAppend(d, name: HeaderName{name}, value: normalizedValue(value: 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, value: newValue);
1228
1229 if (!isValidHttpHeaderNameField(name) || !isValidHttpHeaderValueField(value: newValue))
1230 return false;
1231
1232 QHttpHeadersPrivate::replaceOrAppend(d, name: HeaderName{name}, value: normalizedValue(value: 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(first: d->headers.cbegin(), last: d->headers.cend(), pred: headerNameMatches(name: 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(first: d->headers.cbegin(), last: d->headers.cend(), pred: headerNameMatches(name: 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, name: 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, name: 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(pos: 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(name: 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(name: 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(name: 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(name: 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(pos: 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(pos: 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(name: 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(name: 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(asize: 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(e: 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(asize: size());
1462 for (const auto & h : std::as_const(t&: d->headers))
1463 list.append(t: {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(t&: d->headers))
1477 map.insert(key: h.name.asByteArray(), value: 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: size());
1491 for (const auto &h : std::as_const(t&: d->headers))
1492 hash.insert(key: h.name.asByteArray(), value: h.value);
1493 return hash;
1494}
1495
1496/*!
1497 Clears all header entries.
1498
1499 \sa size()
1500*/
1501void QHttpHeaders::clear()
1502{
1503 if (isEmpty())
1504 return;
1505 d.detach();
1506 d->headers.clear();
1507}
1508
1509QT_END_NAMESPACE
1510

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

source code of qtbase/src/network/access/qhttpheaders.cpp