diff options
author | Lena Biliaieva <[email protected]> | 2024-03-14 22:48:25 +0200 |
---|---|---|
committer | Magdalena Stojek <[email protected]> | 2025-03-27 13:05:22 +0100 |
commit | ed341fcdd18fb4bb58ea4fa9df0be6350a81578c (patch) | |
tree | 4ac2c1cca331058054e70a86f6eded1f04a032d7 | |
parent | 23fd2cfb015226d6894b9cbe5fa50b43f0134e99 (diff) |
Add QHttpHeaders convenience methods for converting to known types
Introduce new methods in QHttpHeaders to convert header values to common
types, including integers and date-times. These functions
return std::optional<T> to indicate conversion success or failure.
During the porting of QtNetwork internals to QHttpHeaders (specifically
replacing cooked headers with QHttpHeaders in QNetworkRequest), these
methods proved to be necessary.
[ChangeLog][QtNetwork][QHttpHeaders] Added convenience methods for
parsing header values to integers and date-times.
Task-number: QTBUG-124766
Change-Id: I95cc8c29c522b23b027d5a79beb9b882aeffa105
Reviewed-by: Juha Vuolle <[email protected]>
Reviewed-by: Marc Mutz <[email protected]>
-rw-r--r-- | src/network/access/qhttpheaders.cpp | 181 | ||||
-rw-r--r-- | src/network/access/qhttpheaders.h | 18 | ||||
-rw-r--r-- | tests/auto/network/access/qhttpheaders/tst_qhttpheaders.cpp | 87 |
3 files changed, 286 insertions, 0 deletions
diff --git a/src/network/access/qhttpheaders.cpp b/src/network/access/qhttpheaders.cpp index ec109df5c3a..3786a17ae23 100644 --- a/src/network/access/qhttpheaders.cpp +++ b/src/network/access/qhttpheaders.cpp @@ -3,6 +3,8 @@ #include "qhttpheaders.h" +#include <QtNetwork/private/qnetworkrequest_p.h> + #include <private/qoffsetstringarray_p.h> #include <QtCore/qcompare.h> @@ -11,6 +13,7 @@ #include <QtCore/qmap.h> #include <QtCore/qset.h> #include <QtCore/qttypetraits.h> +#include <QtCore/qxpfunctional.h> #include <q20algorithm.h> #include <string_view> @@ -796,6 +799,9 @@ public: void combinedValue(const HeaderName &name, QByteArray &result) const; void values(const HeaderName &name, QList<QByteArray> &result) const; QByteArrayView value(const HeaderName &name, QByteArrayView defaultValue) const noexcept; + void forEachHeader(QAnyStringView name, + qxp::function_ref<void(QByteArrayView)> yield); + std::optional<QByteArrayView> findValue(const HeaderName &name) const noexcept; QList<Header> headers; }; @@ -872,6 +878,24 @@ void QHttpHeadersPrivate::replaceOrAppend(Self &d, const HeaderName &name, QByte } } +void QHttpHeadersPrivate::forEachHeader(QAnyStringView name, + qxp::function_ref<void(QByteArrayView)> yield) +{ + for (const auto &h : std::as_const(headers)) { + if (h.name.asView() == name) + yield(h.value); + } +} + +std::optional<QByteArrayView> QHttpHeadersPrivate::findValue(const HeaderName &name) const noexcept +{ + for (const auto &h : headers) { + if (h.name == name) + return h.value; + } + return std::nullopt; +} + /*! Creates a new QHttpHeaders object. */ @@ -1411,6 +1435,163 @@ QByteArray QHttpHeaders::combinedValue(WellKnownHeader name) const } /*! + \since 6.10 + + Returns the value of the first valid header \a name interpreted as a + 64-bit integer. + If the header does not exist or cannot be parsed as an integer, returns + \c std::nullopt. + + \sa intValues(QAnyStringView name), intValueAt(qsizetype i) +*/ +std::optional<qint64> QHttpHeaders::intValue(QAnyStringView name) const noexcept +{ + std::optional<QByteArrayView> v = d->findValue(HeaderName{name}); + if (!v) + return std::nullopt; + bool ok = false; + const qint64 result = v->toLongLong(&ok); + if (ok) + return result; + return std::nullopt; +} + +/*! + \since 6.10 + \overload intValue(QAnyStringView) +*/ +std::optional<qint64> QHttpHeaders::intValue(WellKnownHeader name) const noexcept +{ + return intValue(wellKnownHeaderName(name)); +} + +/*! + \since 6.10 + + Returns the values of the header \a name interpreted as 64-bit integer + in a list. If the header does not exist or cannot be parsed as an integer, + returns \c std::nullopt. + + \sa intValue(QAnyStringView name), intValueAt(qsizetype i) +*/ +std::optional<QList<qint64>> QHttpHeaders::intValues(QAnyStringView name) const +{ + QList<qint64> results; + d->forEachHeader(name, [&](QByteArrayView value) { + bool ok = false; + qint64 result = value.toLongLong(&ok); + if (ok) + results.append(result); + }); + return results.isEmpty() ? std::nullopt : + std::make_optional(std::move(results)); +} + +/*! + \since 6.10 + \overload intValues(QAnyStringView) +*/ +std::optional<QList<qint64>> QHttpHeaders::intValues(WellKnownHeader name) const +{ + return intValues(wellKnownHeaderName(name)); +} + +/*! + \since 6.10 + + Returns the header value interpreted as 64-bit integer at index \a i. + The index \a i must be valid. + + \sa intValues(QAnyStringView name), intValue(QAnyStringView name) +*/ +std::optional<qint64> QHttpHeaders::intValueAt(qsizetype i) const noexcept +{ + verify(i); + QByteArrayView v = valueAt(i); + if (v.isEmpty()) + return std::nullopt; + bool ok = false; + const qint64 result = v.toLongLong(&ok); + return ok ? std::optional<qint64>(result) : + std::nullopt; +} + +/*! + \since 6.10 + + Converts the first found header value of \a name to a QDateTime object, following + the standard HTTP date formats. If the header does not exist or contains an invalid + QDateTime, returns \c std::nullopt. + + \sa dateTimeValues(QAnyStringView name), dateTimeValueAt(qsizetype i) +*/ +std::optional<QDateTime> QHttpHeaders::dateTimeValue(QAnyStringView name) const +{ + std::optional<QByteArrayView> v = d->findValue(HeaderName{name}); + if (!v) + return std::nullopt; + QDateTime dt = QNetworkHeadersPrivate::fromHttpDate(*v); + if (dt.isValid()) + return std::move(dt); + return std::nullopt; +} + +/*! + \since 6.10 + \overload dateTimeValue(QAnyStringView) +*/ +std::optional<QDateTime> QHttpHeaders::dateTimeValue(WellKnownHeader name) const +{ + return dateTimeValue(wellKnownHeaderName(name)); +} + +/*! + \since 6.10 + + Returns all the header values of \a name in a list of QDateTime objects, following + the standard HTTP date formats. If no valid date-time values are found, returns + \c std::nullopt. + + \sa dateTimeValue(QAnyStringView name), dateTimeValueAt(qsizetype i) +*/ +std::optional<QList<QDateTime>> QHttpHeaders::dateTimeValues(QAnyStringView name) const +{ + QList<QDateTime> results; + d->forEachHeader(name, [&](QByteArrayView value) { + QDateTime dt = QNetworkHeadersPrivate::fromHttpDate(value); + if (dt.isValid()) + results.append(std::move(dt)); + }); + return results.isEmpty() ? std::nullopt : + std::make_optional(std::move(results)); +} + +/*! + \since 6.10 + \overload dateTimeValues(QAnyStringView) +*/ +std::optional<QList<QDateTime>> QHttpHeaders::dateTimeValues(WellKnownHeader name) const +{ + return dateTimeValues(wellKnownHeaderName(name)); +} + +/*! + \since 6.10 + + Converts the header value at index \a i to a QDateTime object following the standard + HTTP date formats. The index \a i must be valid. + + \sa dateTimeValue(QAnyStringView name), dateTimeValues(QAnyStringView name) +*/ +std::optional<QDateTime> QHttpHeaders::dateTimeValueAt(qsizetype i) const +{ + verify(i); + QDateTime dt = QNetworkHeadersPrivate::fromHttpDate(valueAt(i)); + return dt.isValid() ? std::make_optional(std::move(dt)) : + std::nullopt; +} + +/*! Returns the number of header entries. */ qsizetype QHttpHeaders::size() const noexcept diff --git a/src/network/access/qhttpheaders.h b/src/network/access/qhttpheaders.h index 760d9037c29..c9087724a64 100644 --- a/src/network/access/qhttpheaders.h +++ b/src/network/access/qhttpheaders.h @@ -6,6 +6,8 @@ #include <QtNetwork/qtnetworkglobal.h> +#include <QtCore/qdatetime.h> +#include <QtCore/qmetaobject.h> #include <QtCore/qobjectdefs.h> #include <QtCore/qshareddata.h> #include <QtCore/qcontainerfwd.h> @@ -244,6 +246,22 @@ public: Q_NETWORK_EXPORT QByteArray combinedValue(QAnyStringView name) const; Q_NETWORK_EXPORT QByteArray combinedValue(WellKnownHeader name) const; + Q_NETWORK_EXPORT std::optional<qint64> intValue(QAnyStringView name) const noexcept; + Q_NETWORK_EXPORT std::optional<qint64> intValue(WellKnownHeader name) const noexcept; + + Q_NETWORK_EXPORT std::optional<QList<qint64>> intValues(QAnyStringView name) const; + Q_NETWORK_EXPORT std::optional<QList<qint64>> intValues(WellKnownHeader name) const; + + Q_NETWORK_EXPORT std::optional<qint64> intValueAt(qsizetype i) const noexcept; + + Q_NETWORK_EXPORT std::optional<QDateTime> dateTimeValue(QAnyStringView name) const; + Q_NETWORK_EXPORT std::optional<QDateTime> dateTimeValue(WellKnownHeader name) const; + + Q_NETWORK_EXPORT std::optional<QList<QDateTime>> dateTimeValues(QAnyStringView name) const; + Q_NETWORK_EXPORT std::optional<QList<QDateTime>> dateTimeValues(WellKnownHeader name) const; + + Q_NETWORK_EXPORT std::optional<QDateTime> dateTimeValueAt(qsizetype i) const; + Q_NETWORK_EXPORT qsizetype size() const noexcept; Q_NETWORK_EXPORT void reserve(qsizetype size); bool isEmpty() const noexcept { return size() == 0; } diff --git a/tests/auto/network/access/qhttpheaders/tst_qhttpheaders.cpp b/tests/auto/network/access/qhttpheaders/tst_qhttpheaders.cpp index 5b6be3c7b4c..dc21bf2dab3 100644 --- a/tests/auto/network/access/qhttpheaders/tst_qhttpheaders.cpp +++ b/tests/auto/network/access/qhttpheaders/tst_qhttpheaders.cpp @@ -22,6 +22,8 @@ private slots: void headerValueField(); void valueEncoding(); void replaceOrAppend(); + void intValues(); + void dateTimeValues(); private: static constexpr QAnyStringView n1{"name1"}; @@ -545,5 +547,90 @@ void tst_QHttpHeaders::replaceOrAppend() QVERIFY(!h1.replaceOrAppend(v1, "foo\x08")); } +void tst_QHttpHeaders::intValues() +{ + QHttpHeaders h1; + h1.append("Content-Length", "12345"); + h1.append(QHttpHeaders::WellKnownHeader::ContentLength, "67890"); + + std::optional<qint64> intValueString = h1.intValue("content-length"); + QCOMPARE(intValueString, 12345); + + std::optional<qint64> intValueWKH = + h1.intValue(QHttpHeaders::WellKnownHeader::ContentLength); + QCOMPARE(intValueWKH, 12345); + + std::optional<QList<qint64>> intStringValuesList = h1.intValues("content-length"); + QVERIFY(intStringValuesList); + QCOMPARE(intStringValuesList->size(), 2); + QCOMPARE(intStringValuesList->at(0), 12345); + QCOMPARE(intStringValuesList->at(1), 67890); + + std::optional<QList<qint64>> intWKHValuesList = + h1.intValues(QHttpHeaders::WellKnownHeader::ContentLength); + QVERIFY(intWKHValuesList); + QCOMPARE(intWKHValuesList->size(), 2); + QCOMPARE(intWKHValuesList->at(0), 12345); + QCOMPARE(intWKHValuesList->at(1), 67890); + + std::optional<qint64> intValueAtIndex = h1.intValueAt(1); + QCOMPARE(intValueAtIndex, 67890); + + h1.clear(); + h1.append("Content-Length", "Invalid Number"); + h1.append("Content-Length", ""); + QCOMPARE(h1.intValueAt(0), std::nullopt); + QCOMPARE(h1.intValue("content-length"), std::nullopt); + QCOMPARE(h1.intValue("non-existing-header"), std::nullopt); + QCOMPARE(h1.intValues("content-length"), std::nullopt); +} + +void tst_QHttpHeaders::dateTimeValues() +{ + QHttpHeaders h1; + h1.append("Date", "Tue, 25 Feb 2025 10:10:10 GMT"); + h1.append(QHttpHeaders::WellKnownHeader::Date, "Mon, 24 Feb 2025 11:11:11 GMT"); + + std::optional<QDateTime> dateTimeString = h1.dateTimeValue("date"); + QVERIFY(dateTimeString); + QCOMPARE(dateTimeString->date(), QDate(2025, 2, 25)); + QCOMPARE(dateTimeString->time(), QTime(10, 10, 10, 0)); + std::optional<QDateTime> dateTimeWKH = + h1.dateTimeValue(QHttpHeaders::WellKnownHeader::Date); + QVERIFY(dateTimeWKH); + QCOMPARE(dateTimeWKH->date(), QDate(2025, 2, 25)); + QCOMPARE(dateTimeWKH->time(), QTime(10, 10, 10, 0)); + + std::optional<QList<QDateTime>> dateTimeStringValueList = h1.dateTimeValues("date"); + QVERIFY(dateTimeStringValueList); + QCOMPARE(dateTimeStringValueList->size(), 2); + QCOMPARE(dateTimeStringValueList->at(0).date(), QDate(2025, 2, 25)); + QCOMPARE(dateTimeStringValueList->at(0).time(), QTime(10, 10, 10, 0)); + QCOMPARE(dateTimeStringValueList->at(1).date(), QDate(2025, 2, 24)); + QCOMPARE(dateTimeStringValueList->at(1).time(), QTime(11, 11, 11, 0)); + + std::optional<QList<QDateTime>> dateTimeWHKValueList = + h1.dateTimeValues(QHttpHeaders::WellKnownHeader::Date); + QVERIFY(dateTimeWHKValueList); + QCOMPARE(dateTimeWHKValueList->size(), 2); + QCOMPARE(dateTimeWHKValueList->at(0).date(), QDate(2025, 2, 25)); + QCOMPARE(dateTimeWHKValueList->at(0).time(), QTime(10, 10, 10, 0)); + QCOMPARE(dateTimeWHKValueList->at(1).date(), QDate(2025, 2, 24)); + QCOMPARE(dateTimeWHKValueList->at(1).time(), QTime(11, 11, 11, 0)); + + std::optional<QDateTime> dateTimeValueAtIndex = h1.dateTimeValueAt(1); + QVERIFY(dateTimeValueAtIndex); + QCOMPARE(dateTimeValueAtIndex->date(), QDate(2025, 2, 24)); + QCOMPARE(dateTimeValueAtIndex->time(), QTime(11, 11, 11, 0)); + + h1.clear(); + h1.append("Date", "InvalidDateFormat"); + h1.append("Date", ""); + QCOMPARE(h1.dateTimeValueAt(0), std::nullopt); + QCOMPARE(h1.dateTimeValue("date"), std::nullopt); + QCOMPARE(h1.dateTimeValue("non-existing-header"), std::nullopt); + QCOMPARE(h1.dateTimeValues("date"), std::nullopt); +} + QTEST_MAIN(tst_QHttpHeaders) #include "tst_qhttpheaders.moc" |