| 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(, "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 = 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 |
| 274 | { |
| 275 | constexpr bool (quint8 lhs, quint8 rhs) const noexcept |
| 276 | { |
| 277 | return (*this)(map(i: lhs), map(i: rhs)); |
| 278 | } |
| 279 | constexpr bool (quint8 lhs, QByteArrayView rhs) const noexcept |
| 280 | { |
| 281 | return (*this)(map(i: lhs), rhs); |
| 282 | } |
| 283 | constexpr bool (QByteArrayView lhs, quint8 rhs) const noexcept |
| 284 | { |
| 285 | return (*this)(lhs, map(i: rhs)); |
| 286 | } |
| 287 | constexpr bool (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 (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 [] = { |
| 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 |
| 696 | { |
| 697 | explicit (QHttpHeaders::WellKnownHeader name) : data(name) |
| 698 | { |
| 699 | } |
| 700 | |
| 701 | explicit (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> (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 () 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 () 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> ; |
| 755 | |
| 756 | friend bool (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 { |
| 774 | HeaderName ; |
| 775 | QByteArray ; |
| 776 | }; |
| 777 | |
| 778 | auto (const HeaderName &name) |
| 779 | { |
| 780 | return [&name](const Header &) { return header.name == name; }; |
| 781 | } |
| 782 | |
| 783 | class : public QSharedData |
| 784 | { |
| 785 | public: |
| 786 | () = 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 = 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> ; |
| 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::(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::(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::(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::(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::(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::() 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::(const QList<std::pair<QByteArray, QByteArray>> &) |
| 887 | { |
| 888 | QHttpHeaders h; |
| 889 | h.reserve(size: headers.size()); |
| 890 | for (const auto & : 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::(const QMultiMap<QByteArray, QByteArray> &) |
| 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::(const QMultiHash<QByteArray, QByteArray> &) |
| 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::() |
| 929 | = default; |
| 930 | |
| 931 | /*! |
| 932 | Creates a copy of \a other. |
| 933 | */ |
| 934 | 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::(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 (QDebug debug, const QHttpHeaders &) |
| 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 = [](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 (QLatin1StringView name) noexcept |
| 1008 | { |
| 1009 | return std::all_of(first: name.begin(), last: name.end(), pred: isValidHttpHeaderNameChar); |
| 1010 | } |
| 1011 | |
| 1012 | static bool (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 (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 (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 = [](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 (QLatin1StringView value) noexcept |
| 1057 | { |
| 1058 | return std::all_of(first: value.begin(), last: value.end(), pred: isValidHttpHeaderValueChar); |
| 1059 | } |
| 1060 | |
| 1061 | static bool (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 (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 (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::(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::(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::(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::(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::(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::(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::(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::(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::(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::(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::(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::(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::(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::(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::(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::(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::(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::(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::(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::(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::(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::() 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::(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::(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::() 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::() 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::() 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::() |
| 1500 | { |
| 1501 | if (isEmpty()) |
| 1502 | return; |
| 1503 | d.detach(); |
| 1504 | d->headers.clear(); |
| 1505 | } |
| 1506 | |
| 1507 | QT_END_NAMESPACE |
| 1508 | |