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 | |
19 | QT_BEGIN_NAMESPACE |
20 | |
21 | Q_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 |
89 | static 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 | |
272 | namespace { |
273 | struct 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 | } |
292 | private: |
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 |
306 | static 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 | }; |
485 | static_assert(std::size(orderedHeaderNameIndexes) == size_t(headerNames.count())); |
486 | static_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 | |
675 | static QByteArray fieldToByteArray(QLatin1StringView s) noexcept |
676 | { |
677 | return QByteArray(s.data(), s.size()); |
678 | } |
679 | |
680 | static QByteArray fieldToByteArray(QUtf8StringView s) noexcept |
681 | { |
682 | return QByteArray(s.data(), s.size()); |
683 | } |
684 | |
685 | static QByteArray fieldToByteArray(QStringView s) |
686 | { |
687 | return s.toLatin1(); |
688 | } |
689 | |
690 | static QByteArray normalizedName(QAnyStringView name) |
691 | { |
692 | return name.visit(v: [](auto name){ return fieldToByteArray(name); }).toLower(); |
693 | } |
694 | |
695 | struct 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 | |
752 | private: |
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) |
773 | struct Header { |
774 | HeaderName name; |
775 | QByteArray value; |
776 | }; |
777 | |
778 | auto headerNameMatches(const HeaderName &name) |
779 | { |
780 | return [&name](const Header &header) { return header.name == name; }; |
781 | } |
782 | |
783 | class QHttpHeadersPrivate : public QSharedData |
784 | { |
785 | public: |
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 | |
801 | QT_DEFINE_QESDP_SPECIALIZATION_DTOR(QHttpHeadersPrivate) |
802 | template <> 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 | |
812 | void 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 | |
827 | void 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 | |
839 | void 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 | |
847 | QByteArrayView 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 | |
856 | void 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 | */ |
876 | QHttpHeaders::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 | */ |
886 | QHttpHeaders 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 | */ |
901 | QHttpHeaders 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 | */ |
916 | QHttpHeaders 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 | */ |
928 | QHttpHeaders::~QHttpHeaders() |
929 | = default; |
930 | |
931 | /*! |
932 | Creates a copy of \a other. |
933 | */ |
934 | QHttpHeaders::QHttpHeaders(const QHttpHeaders &other) |
935 | = default; |
936 | |
937 | /*! |
938 | Assigns the contents of \a other and returns a reference to this object. |
939 | */ |
940 | QHttpHeaders &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 | \memberswap{QHttpHeaders} |
961 | */ |
962 | |
963 | #ifndef QT_NO_DEBUG_STREAM |
964 | /*! |
965 | \fn QDebug QHttpHeaders::operator<<(QDebug debug, |
966 | const QHttpHeaders &headers) |
967 | |
968 | Writes \a headers into \a debug stream. |
969 | */ |
970 | QDebug operator<<(QDebug debug, const QHttpHeaders &headers) |
971 | { |
972 | const QDebugStateSaver saver(debug); |
973 | debug.resetFormat().nospace(); |
974 | |
975 | debug << "QHttpHeaders("; |
976 | if (headers.d) { |
977 | debug << "headers = "; |
978 | const char *separator = ""; |
979 | for (const auto &h : headers.d->headers) { |
980 | debug << separator << h.name.asView() << ':' << h.value; |
981 | separator = " | "; |
982 | } |
983 | } |
984 | debug << ")"; |
985 | return debug; |
986 | } |
987 | #endif |
988 | |
989 | static constexpr auto isValidHttpHeaderNameChar = [](uchar c) noexcept |
990 | { |
991 | // RFC 9110 Chapters "5.1 Field Names" and "5.6.2 Tokens" |
992 | // field-name = token |
993 | // token = 1*tchar |
994 | // tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / |
995 | // "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" |
996 | // / DIGIT / ALPHA |
997 | // ; any VCHAR, except delimiters |
998 | // (for explanation on VCHAR see isValidHttpHeaderValueChar) |
999 | return (('A' <= c && c <= 'Z') |
1000 | || ('a' <= c && c <= 'z') |
1001 | || ('0' <= c && c <= '9') |
1002 | || ('#' <= c && c <= '\'') |
1003 | || ('^' <= c && c <= '`') |
1004 | || c == '|' || c == '~' || c == '!' || c == '*' || c == '+' || c == '-' || c == '.'); |
1005 | }; |
1006 | |
1007 | static bool headerNameValidImpl(QLatin1StringView name) noexcept |
1008 | { |
1009 | return std::all_of(first: name.begin(), last: name.end(), pred: isValidHttpHeaderNameChar); |
1010 | } |
1011 | |
1012 | static bool headerNameValidImpl(QUtf8StringView name) noexcept |
1013 | { |
1014 | // Traversing the UTF-8 string char-by-char is fine in this case as |
1015 | // the isValidHttpHeaderNameChar rejects any value above 0x7E. UTF-8 |
1016 | // only has bytes <= 0x7F if they truly represent that ASCII character. |
1017 | return headerNameValidImpl(name: QLatin1StringView(QByteArrayView(name))); |
1018 | } |
1019 | |
1020 | static bool headerNameValidImpl(QStringView name) noexcept |
1021 | { |
1022 | return std::all_of(first: name.begin(), last: name.end(), pred: [](QChar c) { |
1023 | return isValidHttpHeaderNameChar(c.toLatin1()); |
1024 | }); |
1025 | } |
1026 | |
1027 | static bool isValidHttpHeaderNameField(QAnyStringView name) noexcept |
1028 | { |
1029 | if (name.isEmpty()) { |
1030 | qCWarning(lcQHttpHeaders, "HTTP header name cannot be empty"); |
1031 | return false; |
1032 | } |
1033 | const bool valid = name.visit(v: [](auto name){ return headerNameValidImpl(name); }); |
1034 | if (!valid) |
1035 | qCWarning(lcQHttpHeaders, "HTTP header name contained illegal character(s)"); |
1036 | return valid; |
1037 | } |
1038 | |
1039 | static constexpr auto isValidHttpHeaderValueChar = [](uchar c) noexcept |
1040 | { |
1041 | // RFC 9110 Chapter 5.5, Field Values |
1042 | // field-value = *field-content |
1043 | // field-content = field-vchar |
1044 | // [ 1*( SP / HTAB / field-vchar ) field-vchar ] |
1045 | // field-vchar = VCHAR / obs-text |
1046 | // obs-text = %x80-FF |
1047 | // VCHAR is defined as "any visible US-ASCII character", and RFC 5234 B.1. |
1048 | // defines it as %x21-7E |
1049 | // Note: The ABNF above states that field-content and thus field-value cannot |
1050 | // start or end with SP/HTAB. The caller should handle this. |
1051 | return (c >= 0x80 // obs-text (extended ASCII) |
1052 | || (0x20 <= c && c <= 0x7E) // SP (0x20) + VCHAR |
1053 | || (c == 0x09)); // HTAB |
1054 | }; |
1055 | |
1056 | static bool headerValueValidImpl(QLatin1StringView value) noexcept |
1057 | { |
1058 | return std::all_of(first: value.begin(), last: value.end(), pred: isValidHttpHeaderValueChar); |
1059 | } |
1060 | |
1061 | static bool headerValueValidImpl(QUtf8StringView value) noexcept |
1062 | { |
1063 | // UTF-8 byte sequences are also used as values directly |
1064 | // => allow them as such. UTF-8 byte sequences for characters |
1065 | // outside of ASCII should all fit into obs-text (>= 0x80) |
1066 | // (see isValidHttpHeaderValueChar) |
1067 | return std::all_of(first: value.begin(), last: value.end(), pred: isValidHttpHeaderValueChar); |
1068 | } |
1069 | |
1070 | static bool headerValueValidImpl(QStringView value) noexcept |
1071 | { |
1072 | return std::all_of(first: value.begin(), last: value.end(), pred: [](QChar c) { |
1073 | return isValidHttpHeaderValueChar(c.toLatin1()); |
1074 | }); |
1075 | } |
1076 | |
1077 | static bool isValidHttpHeaderValueField(QAnyStringView value) noexcept |
1078 | { |
1079 | const bool valid = value.visit(v: [](auto value){ return headerValueValidImpl(value); }); |
1080 | if (!valid) |
1081 | qCWarning(lcQHttpHeaders, "HTTP header value contained illegal character(s)"); |
1082 | return valid; |
1083 | } |
1084 | |
1085 | static QByteArray normalizedValue(QAnyStringView value) |
1086 | { |
1087 | // Note on trimming away any leading or trailing whitespace of 'value': |
1088 | // RFC 9110 (HTTP 1.1, 2022, Chapter 5.5) does not allow leading or trailing whitespace |
1089 | // RFC 7230 (HTTP 1.1, 2014, Chapter 3.2) allows them optionally, but also mandates that |
1090 | // they are ignored during processing |
1091 | // RFC 7540 (HTTP/2) does not seem explicit about it |
1092 | // => for maximum compatibility, trim away any leading or trailing whitespace |
1093 | return value.visit(v: [](auto value){ return fieldToByteArray(value); }).trimmed(); |
1094 | } |
1095 | |
1096 | /*! |
1097 | Appends a header entry with \a name and \a value and returns \c true |
1098 | if successful. |
1099 | |
1100 | \sa append(QHttpHeaders::WellKnownHeader, QAnyStringView) |
1101 | \sa {Allowed field name and value characters} |
1102 | */ |
1103 | bool QHttpHeaders::append(QAnyStringView name, QAnyStringView value) |
1104 | { |
1105 | if (!isValidHttpHeaderNameField(name) || !isValidHttpHeaderValueField(value)) |
1106 | return false; |
1107 | |
1108 | d.detach(); |
1109 | d->headers.push_back(t: {.name: HeaderName{name}, .value: normalizedValue(value)}); |
1110 | return true; |
1111 | } |
1112 | |
1113 | /*! |
1114 | \overload append(QAnyStringView, QAnyStringView) |
1115 | */ |
1116 | bool QHttpHeaders::append(WellKnownHeader name, QAnyStringView value) |
1117 | { |
1118 | if (!isValidHttpHeaderValueField(value)) |
1119 | return false; |
1120 | |
1121 | d.detach(); |
1122 | d->headers.push_back(t: {.name: HeaderName{name}, .value: normalizedValue(value)}); |
1123 | return true; |
1124 | } |
1125 | |
1126 | /*! |
1127 | Inserts a header entry at index \a i, with \a name and \a value. The index |
1128 | must be valid (see \l size()). Returns whether the insert succeeded. |
1129 | |
1130 | \sa append(), |
1131 | insert(qsizetype, QHttpHeaders::WellKnownHeader, QAnyStringView), size() |
1132 | \sa {Allowed field name and value characters} |
1133 | */ |
1134 | bool QHttpHeaders::insert(qsizetype i, QAnyStringView name, QAnyStringView value) |
1135 | { |
1136 | verify(pos: i, n: 0); |
1137 | if (!isValidHttpHeaderNameField(name) || !isValidHttpHeaderValueField(value)) |
1138 | return false; |
1139 | |
1140 | d.detach(); |
1141 | d->headers.insert(i, t: {.name: HeaderName{name}, .value: normalizedValue(value)}); |
1142 | return true; |
1143 | } |
1144 | |
1145 | /*! |
1146 | \overload insert(qsizetype, QAnyStringView, QAnyStringView) |
1147 | */ |
1148 | bool QHttpHeaders::insert(qsizetype i, WellKnownHeader name, QAnyStringView value) |
1149 | { |
1150 | verify(pos: i, n: 0); |
1151 | if (!isValidHttpHeaderValueField(value)) |
1152 | return false; |
1153 | |
1154 | d.detach(); |
1155 | d->headers.insert(i, t: {.name: HeaderName{name}, .value: normalizedValue(value)}); |
1156 | return true; |
1157 | } |
1158 | |
1159 | /*! |
1160 | Replaces the header entry at index \a i, with \a name and \a newValue. |
1161 | The index must be valid (see \l size()). Returns whether the replace |
1162 | succeeded. |
1163 | |
1164 | \sa append(), |
1165 | replace(qsizetype, QHttpHeaders::WellKnownHeader, QAnyStringView), size() |
1166 | \sa {Allowed field name and value characters} |
1167 | */ |
1168 | bool QHttpHeaders::replace(qsizetype i, QAnyStringView name, QAnyStringView newValue) |
1169 | { |
1170 | verify(pos: i); |
1171 | if (!isValidHttpHeaderNameField(name) || !isValidHttpHeaderValueField(value: newValue)) |
1172 | return false; |
1173 | |
1174 | d.detach(); |
1175 | d->headers.replace(i, t: {.name: HeaderName{name}, .value: normalizedValue(value: newValue)}); |
1176 | return true; |
1177 | } |
1178 | |
1179 | /*! |
1180 | \overload replace(qsizetype, QAnyStringView, QAnyStringView) |
1181 | */ |
1182 | bool QHttpHeaders::replace(qsizetype i, WellKnownHeader name, QAnyStringView newValue) |
1183 | { |
1184 | verify(pos: i); |
1185 | if (!isValidHttpHeaderValueField(value: newValue)) |
1186 | return false; |
1187 | |
1188 | d.detach(); |
1189 | d->headers.replace(i, t: {.name: HeaderName{name}, .value: normalizedValue(value: newValue)}); |
1190 | return true; |
1191 | } |
1192 | |
1193 | /*! |
1194 | \since 6.8 |
1195 | |
1196 | If QHttpHeaders already contains \a name, replaces its value with |
1197 | \a newValue and removes possible additional \a name entries. |
1198 | If \a name didn't exist, appends a new entry. Returns \c true |
1199 | if successful. |
1200 | |
1201 | This function is a convenience method for setting a unique |
1202 | \a name : \a newValue header. For most headers the relative order does not |
1203 | matter, which allows reusing an existing entry if one exists. |
1204 | |
1205 | \sa replaceOrAppend(QAnyStringView, QAnyStringView) |
1206 | */ |
1207 | bool QHttpHeaders::replaceOrAppend(WellKnownHeader name, QAnyStringView newValue) |
1208 | { |
1209 | if (isEmpty()) |
1210 | return append(name, value: newValue); |
1211 | |
1212 | if (!isValidHttpHeaderValueField(value: newValue)) |
1213 | return false; |
1214 | |
1215 | QHttpHeadersPrivate::replaceOrAppend(d, name: HeaderName{name}, value: normalizedValue(value: newValue)); |
1216 | return true; |
1217 | } |
1218 | |
1219 | /*! |
1220 | \overload replaceOrAppend(WellKnownHeader, QAnyStringView) |
1221 | */ |
1222 | bool QHttpHeaders::replaceOrAppend(QAnyStringView name, QAnyStringView newValue) |
1223 | { |
1224 | if (isEmpty()) |
1225 | return append(name, value: newValue); |
1226 | |
1227 | if (!isValidHttpHeaderNameField(name) || !isValidHttpHeaderValueField(value: newValue)) |
1228 | return false; |
1229 | |
1230 | QHttpHeadersPrivate::replaceOrAppend(d, name: HeaderName{name}, value: normalizedValue(value: newValue)); |
1231 | return true; |
1232 | } |
1233 | |
1234 | /*! |
1235 | Returns whether the headers contain header with \a name. |
1236 | |
1237 | \sa contains(QHttpHeaders::WellKnownHeader) |
1238 | */ |
1239 | bool QHttpHeaders::contains(QAnyStringView name) const |
1240 | { |
1241 | if (isEmpty()) |
1242 | return false; |
1243 | |
1244 | return std::any_of(first: d->headers.cbegin(), last: d->headers.cend(), pred: headerNameMatches(name: HeaderName{name})); |
1245 | } |
1246 | |
1247 | /*! |
1248 | \overload has(QAnyStringView) |
1249 | */ |
1250 | bool QHttpHeaders::contains(WellKnownHeader name) const |
1251 | { |
1252 | if (isEmpty()) |
1253 | return false; |
1254 | |
1255 | return std::any_of(first: d->headers.cbegin(), last: d->headers.cend(), pred: headerNameMatches(name: HeaderName{name})); |
1256 | } |
1257 | |
1258 | /*! |
1259 | Removes the header \a name. |
1260 | |
1261 | \sa removeAt(), removeAll(QHttpHeaders::WellKnownHeader) |
1262 | */ |
1263 | void QHttpHeaders::removeAll(QAnyStringView name) |
1264 | { |
1265 | if (isEmpty()) |
1266 | return; |
1267 | |
1268 | return QHttpHeadersPrivate::removeAll(d, name: HeaderName(name)); |
1269 | } |
1270 | |
1271 | /*! |
1272 | \overload removeAll(QAnyStringView) |
1273 | */ |
1274 | void QHttpHeaders::removeAll(WellKnownHeader name) |
1275 | { |
1276 | if (isEmpty()) |
1277 | return; |
1278 | |
1279 | return QHttpHeadersPrivate::removeAll(d, name: HeaderName(name)); |
1280 | } |
1281 | |
1282 | /*! |
1283 | Removes the header at index \a i. The index \a i must be valid |
1284 | (see \l size()). |
1285 | |
1286 | \sa removeAll(QHttpHeaders::WellKnownHeader), |
1287 | removeAll(QAnyStringView), size() |
1288 | */ |
1289 | void QHttpHeaders::removeAt(qsizetype i) |
1290 | { |
1291 | verify(pos: i); |
1292 | d.detach(); |
1293 | d->headers.removeAt(i); |
1294 | } |
1295 | |
1296 | /*! |
1297 | Returns the value of the (first) header \a name, or \a defaultValue if it |
1298 | doesn't exist. |
1299 | |
1300 | \sa value(QHttpHeaders::WellKnownHeader, QByteArrayView) |
1301 | */ |
1302 | QByteArrayView QHttpHeaders::value(QAnyStringView name, QByteArrayView defaultValue) const noexcept |
1303 | { |
1304 | if (isEmpty()) |
1305 | return defaultValue; |
1306 | |
1307 | return d->value(name: HeaderName{name}, defaultValue); |
1308 | } |
1309 | |
1310 | /*! |
1311 | \overload value(QAnyStringView, QByteArrayView) |
1312 | */ |
1313 | QByteArrayView QHttpHeaders::value(WellKnownHeader name, QByteArrayView defaultValue) const noexcept |
1314 | { |
1315 | if (isEmpty()) |
1316 | return defaultValue; |
1317 | |
1318 | return d->value(name: HeaderName{name}, defaultValue); |
1319 | } |
1320 | |
1321 | /*! |
1322 | Returns the values of header \a name in a list. Returns an empty |
1323 | list if header with \a name doesn't exist. |
1324 | |
1325 | \sa values(QHttpHeaders::WellKnownHeader) |
1326 | */ |
1327 | QList<QByteArray> QHttpHeaders::values(QAnyStringView name) const |
1328 | { |
1329 | QList<QByteArray> result; |
1330 | if (isEmpty()) |
1331 | return result; |
1332 | |
1333 | d->values(name: HeaderName{name}, result); |
1334 | return result; |
1335 | } |
1336 | |
1337 | /*! |
1338 | \overload values(QAnyStringView) |
1339 | */ |
1340 | QList<QByteArray> QHttpHeaders::values(WellKnownHeader name) const |
1341 | { |
1342 | QList<QByteArray> result; |
1343 | if (isEmpty()) |
1344 | return result; |
1345 | |
1346 | d->values(name: HeaderName{name}, result); |
1347 | return result; |
1348 | } |
1349 | |
1350 | /*! |
1351 | Returns the header value at index \a i. The index \a i must be valid |
1352 | (see \l size()). |
1353 | |
1354 | \sa size(), value(), values(), combinedValue(), nameAt() |
1355 | */ |
1356 | QByteArrayView QHttpHeaders::valueAt(qsizetype i) const noexcept |
1357 | { |
1358 | verify(pos: i); |
1359 | return d->headers.at(i).value; |
1360 | } |
1361 | |
1362 | /*! |
1363 | Returns the header name at index \a i. The index \a i must be valid |
1364 | (see \l size()). |
1365 | |
1366 | Header names are case-insensitive, and the returned names are lower-cased. |
1367 | |
1368 | \sa size(), valueAt() |
1369 | */ |
1370 | QLatin1StringView QHttpHeaders::nameAt(qsizetype i) const noexcept |
1371 | { |
1372 | verify(pos: i); |
1373 | return QLatin1StringView{d->headers.at(i).name.asView()}; |
1374 | } |
1375 | |
1376 | /*! |
1377 | Returns the values of header \a name in a comma-combined string. |
1378 | Returns a \c null QByteArray if the header with \a name doesn't |
1379 | exist. |
1380 | |
1381 | \note Accessing the value(s) of 'Set-Cookie' header this way may not work |
1382 | as intended. It is a notable exception in the |
1383 | \l {https://datatracker.ietf.org/doc/html/rfc9110#name-field-order}{HTTP RFC} |
1384 | in that its values cannot be combined this way. Prefer \l values() instead. |
1385 | |
1386 | \sa values(QAnyStringView) |
1387 | */ |
1388 | QByteArray QHttpHeaders::combinedValue(QAnyStringView name) const |
1389 | { |
1390 | QByteArray result; |
1391 | if (isEmpty()) |
1392 | return result; |
1393 | |
1394 | d->combinedValue(name: HeaderName{name}, result); |
1395 | return result; |
1396 | } |
1397 | |
1398 | /*! |
1399 | \overload combinedValue(QAnyStringView) |
1400 | */ |
1401 | QByteArray QHttpHeaders::combinedValue(WellKnownHeader name) const |
1402 | { |
1403 | QByteArray result; |
1404 | if (isEmpty()) |
1405 | return result; |
1406 | |
1407 | d->combinedValue(name: HeaderName{name}, result); |
1408 | return result; |
1409 | } |
1410 | |
1411 | /*! |
1412 | Returns the number of header entries. |
1413 | */ |
1414 | qsizetype QHttpHeaders::size() const noexcept |
1415 | { |
1416 | if (!d) |
1417 | return 0; |
1418 | return d->headers.size(); |
1419 | } |
1420 | |
1421 | /*! |
1422 | Attempts to allocate memory for at least \a size header entries. |
1423 | |
1424 | If you know in advance how how many header entries there will |
1425 | be, you may call this function to prevent reallocations |
1426 | and memory fragmentation. |
1427 | */ |
1428 | void QHttpHeaders::reserve(qsizetype size) |
1429 | { |
1430 | d.detach(); |
1431 | d->headers.reserve(asize: size); |
1432 | } |
1433 | |
1434 | /*! |
1435 | \fn bool QHttpHeaders::isEmpty() const noexcept |
1436 | |
1437 | Returns \c true if the headers have size 0; otherwise returns \c false. |
1438 | |
1439 | \sa size() |
1440 | */ |
1441 | |
1442 | /*! |
1443 | Returns a header name corresponding to the provided \a name as a view. |
1444 | */ |
1445 | QByteArrayView QHttpHeaders::wellKnownHeaderName(WellKnownHeader name) noexcept |
1446 | { |
1447 | return headerNames[qToUnderlying(e: name)]; |
1448 | } |
1449 | |
1450 | /*! |
1451 | Returns the header entries as a list of (name, value) pairs. |
1452 | Header names are case-insensitive, and the returned names are lower-cased. |
1453 | */ |
1454 | QList<std::pair<QByteArray, QByteArray>> QHttpHeaders::toListOfPairs() const |
1455 | { |
1456 | QList<std::pair<QByteArray, QByteArray>> list; |
1457 | if (isEmpty()) |
1458 | return list; |
1459 | list.reserve(asize: size()); |
1460 | for (const auto & h : std::as_const(t&: d->headers)) |
1461 | list.append(t: {h.name.asByteArray(), h.value}); |
1462 | return list; |
1463 | } |
1464 | |
1465 | /*! |
1466 | Returns the header entries as a map from name to value(s). |
1467 | Header names are case-insensitive, and the returned names are lower-cased. |
1468 | */ |
1469 | QMultiMap<QByteArray, QByteArray> QHttpHeaders::toMultiMap() const |
1470 | { |
1471 | QMultiMap<QByteArray, QByteArray> map; |
1472 | if (isEmpty()) |
1473 | return map; |
1474 | for (const auto &h : std::as_const(t&: d->headers)) |
1475 | map.insert(key: h.name.asByteArray(), value: h.value); |
1476 | return map; |
1477 | } |
1478 | |
1479 | /*! |
1480 | Returns the header entries as a hash from name to value(s). |
1481 | Header names are case-insensitive, and the returned names are lower-cased. |
1482 | */ |
1483 | QMultiHash<QByteArray, QByteArray> QHttpHeaders::toMultiHash() const |
1484 | { |
1485 | QMultiHash<QByteArray, QByteArray> hash; |
1486 | if (isEmpty()) |
1487 | return hash; |
1488 | hash.reserve(size: size()); |
1489 | for (const auto &h : std::as_const(t&: d->headers)) |
1490 | hash.insert(key: h.name.asByteArray(), value: h.value); |
1491 | return hash; |
1492 | } |
1493 | |
1494 | /*! |
1495 | Clears all header entries. |
1496 | |
1497 | \sa size() |
1498 | */ |
1499 | void QHttpHeaders::clear() |
1500 | { |
1501 | if (isEmpty()) |
1502 | return; |
1503 | d.detach(); |
1504 | d->headers.clear(); |
1505 | } |
1506 | |
1507 | QT_END_NAMESPACE |
1508 |
Definitions
- lcQHttpHeaders
- headerNames
- ByIndirectHeaderName
- operator()
- operator()
- operator()
- operator()
- map
- orderedHeaderNameIndexes
- fieldToByteArray
- fieldToByteArray
- fieldToByteArray
- normalizedName
- HeaderName
- HeaderName
- HeaderName
- toWellKnownHeader
- asView
- asByteArray
- comparesEqual
- Header
- headerNameMatches
- QHttpHeadersPrivate
- QHttpHeadersPrivate
- detach
- removeAll
- combinedValue
- values
- value
- replaceOrAppend
- QHttpHeaders
- fromListOfPairs
- fromMultiMap
- fromMultiHash
- ~QHttpHeaders
- QHttpHeaders
- operator=
- operator<<
- isValidHttpHeaderNameChar
- headerNameValidImpl
- headerNameValidImpl
- headerNameValidImpl
- isValidHttpHeaderNameField
- isValidHttpHeaderValueChar
- headerValueValidImpl
- headerValueValidImpl
- headerValueValidImpl
- isValidHttpHeaderValueField
- normalizedValue
- append
- append
- insert
- insert
- replace
- replace
- replaceOrAppend
- replaceOrAppend
- contains
- contains
- removeAll
- removeAll
- removeAt
- value
- value
- values
- values
- valueAt
- nameAt
- combinedValue
- combinedValue
- size
- reserve
- wellKnownHeaderName
- toListOfPairs
- toMultiMap
- toMultiHash
Learn to use CMake with our Intro Training
Find out more