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 <juha.vuolle@qt.io>
Reviewed-by: Marc Mutz <marc.mutz@qt.io>
This commit is contained in:
Lena Biliaieva 2024-03-14 22:48:25 +02:00 committed by Magdalena Stojek
parent 23fd2cfb01
commit ed341fcdd1
3 changed files with 286 additions and 0 deletions

View File

@ -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.
*/
@ -1410,6 +1434,163 @@ QByteArray QHttpHeaders::combinedValue(WellKnownHeader name) const
return result;
}
/*!
\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.
*/

View File

@ -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; }

View File

@ -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"