diff --git a/docs/user/netservices/HttpRequest.dox b/docs/user/netservices/HttpRequest.dox index d8d32d7808..575ba74b7d 100644 --- a/docs/user/netservices/HttpRequest.dox +++ b/docs/user/netservices/HttpRequest.dox @@ -329,6 +329,12 @@ namespace Network { The URL. This must start with http or https. Defaults to an empty \ref BUrl + + \ref Fields() + \ref SetFields() + Additional fields set in the request header. + Defaults with no additional fields + \ref Method() \ref SetMethod() @@ -472,6 +478,16 @@ namespace Network { //! @{ +/*! + \fn const BHttpFields& BHttpRequest::Fields() const noexcept + \brief Get the additional header fields set for the request. + + The returned header fields may be empty if no additional header fields were set. + + \since Haiku R1 +*/ + + /*! \fn const BHttpMethod& BHttpRequest::Method() const noexcept \brief Get the current method for the request. @@ -515,6 +531,26 @@ namespace Network { //! @{ +/*! + \fn void BHttpRequest::SetFields(const BHttpFields &fields) + \brief Set additional header \a fields for this request. + + There are a few reserved fields, which cannot be set as optional fields. These currently are: + * \c Host + * \c Accept + * \c Accept-Encoding + * \c Connection + + \param fields Additional fields for the header of the request. + + \exception std::bad_alloc This exception may be raised if it is impossible to allocate memory. + \exception BHttpFields::InvalidData This exception is raised when the \a fields contain + reserved fields. + + \since Haiku R1 +*/ + + /*! \fn void BHttpRequest::SetMethod(const BHttpMethod &method) \brief Set the \a method for this request. diff --git a/headers/private/netservices2/HttpRequest.h b/headers/private/netservices2/HttpRequest.h index b9dcac4fe2..fe1c905f00 100644 --- a/headers/private/netservices2/HttpRequest.h +++ b/headers/private/netservices2/HttpRequest.h @@ -23,6 +23,9 @@ namespace BPrivate { namespace Network { +class BHttpFields; + + class BHttpMethod { public: // Constants for default methods in RFC 7230 section 4.2 @@ -92,11 +95,13 @@ public: // Access bool IsEmpty() const noexcept; + const BHttpFields& Fields() const noexcept; const BHttpMethod& Method() const noexcept; const BHttpRedirectOptions& Redirect() const noexcept; const BUrl& Url() const noexcept; // Named Setters + void SetFields(const BHttpFields& fields); void SetMethod(const BHttpMethod& method); void SetRedirect(const BHttpRedirectOptions& redirectOptions); void SetUrl(const BUrl& url); diff --git a/src/kits/network/libnetservices2/HttpRequest.cpp b/src/kits/network/libnetservices2/HttpRequest.cpp index 4b11831b09..07e8fb6e9b 100644 --- a/src/kits/network/libnetservices2/HttpRequest.cpp +++ b/src/kits/network/libnetservices2/HttpRequest.cpp @@ -157,12 +157,13 @@ BHttpMethod::Method() const noexcept static const BUrl kDefaultUrl = BUrl(); static const BHttpMethod kDefaultMethod = BHttpMethod::Get; static const BHttpRedirectOptions kDefaultRedirectOptions = BHttpRedirectOptions(); - +static const BHttpFields kDefaultOptionalFields = BHttpFields(); struct BHttpRequest::Data { BUrl url = kDefaultUrl; BHttpMethod method = kDefaultMethod; BHttpRedirectOptions redirectOptions; + BHttpFields optionalFields; }; @@ -200,6 +201,15 @@ BHttpRequest::IsEmpty() const noexcept } +const BHttpFields& +BHttpRequest::Fields() const noexcept +{ + if (!fData) + return kDefaultOptionalFields; + return fData->optionalFields; +} + + const BHttpMethod& BHttpRequest::Method() const noexcept { @@ -227,6 +237,32 @@ BHttpRequest::Url() const noexcept } +static constexpr std::array fReservedOptionalFieldNames = { + "Host"sv, + "Accept"sv, + "Accept-Encoding"sv, + "Connection"sv +}; + + +void +BHttpRequest::SetFields(const BHttpFields& fields) +{ + if (!fData) + fData = std::make_unique(); + + for (auto& field: fields) { + if (std::find(fReservedOptionalFieldNames.begin(), fReservedOptionalFieldNames.end(), + field.Name()) != fReservedOptionalFieldNames.end()) + { + std::string_view fieldName = field.Name(); + throw BHttpFields::InvalidInput(__PRETTY_FUNCTION__, BString(fieldName.data(), fieldName.size())); + } + } + fData->optionalFields = fields; +} + + void BHttpRequest::SetMethod(const BHttpMethod& method) { @@ -326,6 +362,11 @@ BHttpRequest::SerializeHeaderTo(BDataIO* target) const bytesWritten += _write_to_dataio(target, "\r\n"sv); } + for (const auto& field: fData->optionalFields) { + bytesWritten += _write_to_dataio(target, field.RawField()); + bytesWritten += _write_to_dataio(target, "\r\n"sv); + } + bytesWritten += _write_to_dataio(target, "\r\n"sv); return bytesWritten; } diff --git a/src/tests/kits/net/netservices2/HttpProtocolTest.cpp b/src/tests/kits/net/netservices2/HttpProtocolTest.cpp index 24412b7845..533c190b56 100644 --- a/src/tests/kits/net/netservices2/HttpProtocolTest.cpp +++ b/src/tests/kits/net/netservices2/HttpProtocolTest.cpp @@ -276,12 +276,13 @@ HttpProtocolTest::HttpMethodTest() } -constexpr std::string_view kHaikuGetRequestText = +constexpr std::string_view kExpectedRequestText = "GET / HTTP/1.1\r\n" "Host: www.haiku-os.org\r\n" "Accept: *\r\n" "Accept-Encoding: gzip\r\n" - "Connection: close\r\n\r\n"; + "Connection: close\r\n" + "Api-Key: 01234567890abcdef\r\n\r\n"; void @@ -294,9 +295,22 @@ HttpProtocolTest::HttpRequestTest() request.SetUrl(url); CPPUNIT_ASSERT(request.Url() == url); + // Add Invalid HTTP fields (should throw) + try { + BHttpFields invalidField = {{"Host"sv, "haiku-os.org"sv}}; + request.SetFields(invalidField); + CPPUNIT_FAIL("Should not be able to add the invalid \"Host\" field to a request"); + } catch (BHttpFields::InvalidInput& e) { + // Correct; do nothing + } + + // Add valid HTTP field + BHttpFields validField = {{"Api-Key"sv, "01234567890abcdef"}}; + request.SetFields(validField); + // Validate header serialization BString header = request.HeaderToString(); - CPPUNIT_ASSERT(header.Compare(kHaikuGetRequestText.data(), kHaikuGetRequestText.size()) == 0); + CPPUNIT_ASSERT(header.Compare(kExpectedRequestText.data(), kExpectedRequestText.size()) == 0); } @@ -326,6 +340,14 @@ private: }; +constexpr std::string_view kExpectedStreamText = + "GET / HTTP/1.1\r\n" + "Host: www.haiku-os.org\r\n" + "Accept: *\r\n" + "Accept-Encoding: gzip\r\n" + "Connection: close\r\n\r\n"; + + void HttpProtocolTest::HttpRequestStreamTest() { @@ -336,11 +358,11 @@ HttpProtocolTest::HttpRequestStreamTest() // Test streaming the request BHttpRequestStream requestStream(request); - RequestStreamTestIO testIO(kHaikuGetRequestText.data()); + RequestStreamTestIO testIO(kExpectedStreamText.data()); bool finished = false; ssize_t expectedBytesWritten = 8; ssize_t expectedTotalBytesWritten = 8; - const ssize_t expectedTotalSize = kHaikuGetRequestText.size(); + const ssize_t expectedTotalSize = kExpectedStreamText.size(); if (expectedTotalSize < 8) { expectedBytesWritten = expectedTotalSize; expectedTotalBytesWritten = expectedTotalSize;