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