diff --git a/docs/user/netservices/HttpRequest.dox b/docs/user/netservices/HttpRequest.dox
new file mode 100644
index 0000000000..2461b1d528
--- /dev/null
+++ b/docs/user/netservices/HttpRequest.dox
@@ -0,0 +1,255 @@
+/*
+ * Copyright 2022 Haiku, Inc. All rights reserved.
+ * Distributed under the terms of the MIT License.
+ *
+ * Authors:
+ * Niels Sascha Reedijk, niels.reedijk@gmail.com
+ *
+ * Corresponds to:
+ * headers/private/netservices2/HttpRequest.h hrev?????
+ * src/kits/network/libnetservices2/HttpRequest.cpp hrev?????
+ */
+
+
+#if __cplusplus >= 201703L
+
+
+/*!
+ \file HttpRequest.h
+ \ingroup netservices
+ \brief Provides the classes and tools to build HTTP Requests.
+
+ \since Haiku R1
+*/
+
+
+namespace BPrivate {
+
+namespace Network {
+
+
+/*!
+ \class BHttpMethod
+ \ingroup netservices
+ \brief Represent a HTTP method.
+
+ The HTTP standard
+ specifies that HTTP requests have a method. Common methods are \c GET and \c HEAD methods.
+ Standardized and common methods are in the form of \em verbs and are in capitalized letters
+ from the ASCII token set, though any valid token can be used.
+
+ It is most likely that you will not use the methods of this class directly, instead you will
+ use the implicit constructors while interacting with the \ref BHttpRequest class.
+
+ \code
+ auto url = BUrl2("https://www.haiku-os.org/");
+ // implicitly construct a standard get request
+ auto standard = BHttpRequest(url, BHttpMethod::Get);
+ // implicitly construct a nonstandard patch request
+ auto custom = BHttpRequest(url, "PATCH"sv);
+ \endcode
+
+ \note When you are using the standard list of verbs, there will never be an exception when
+ creating objects of this type. When you create a custom method, exceptions may be raised
+ when the system runs out of memory, or when your custom method contains invalid characters.
+ In almost all cases, you can probably safely assume you will not run into these exceptions,
+ except for cases where you use user input to create methods or you are very defensive
+ about memory management.
+
+ \since Haiku R1
+*/
+
+
+/*!
+ \class BHttpMethod::InvalidMethod
+ \ingroup netservices
+ \brief Error that represents when a custom method does not conform to the HTTP standard.
+
+ \since Haiku R1
+*/
+
+
+/*!
+ \var BString BHttpMethod::InvalidMethod::input
+ \brief The input that contains the invalid contents.
+
+ \since Haiku R1
+*/
+
+
+/*!
+ \fn BHttpMethod::InvalidMethod::InvalidMethod(const char *origin, BString input)
+ \brief Constructor that sets the \a origin and the invalid \a input.
+
+ \since Haiku R1
+*/
+
+
+/*!
+ \enum BHttpMethod::Verb
+ \ingroup netservices
+ \brief A list of standard HTTP methods.
+
+ \since Haiku R1
+*/
+
+
+/*!
+ \var BHttpMethod::Verb BHttpMethod::Get
+ \brief Represents the \c GET method.
+
+ \since Haiku R1
+*/
+
+
+/*!
+ \var BHttpMethod::Verb BHttpMethod::Head
+ \brief Represents the \c HEAD method.
+
+ \since Haiku R1
+*/
+
+
+/*!
+ \var BHttpMethod::Verb BHttpMethod::Post
+ \brief Represents the \c POST method.
+
+ \since Haiku R1
+*/
+
+
+/*!
+ \var BHttpMethod::Verb BHttpMethod::Put
+ \brief Represents the \c PUT method.
+
+ \since Haiku R1
+*/
+
+
+/*!
+ \var BHttpMethod::Verb BHttpMethod::Delete
+ \brief Represents the \c DELETE method.
+
+ \since Haiku R1
+*/
+
+
+/*!
+ \var BHttpMethod::Verb BHttpMethod::Connect
+ \brief Represents the \c CONNECT method.
+
+ \since Haiku R1
+*/
+
+
+/*!
+ \var BHttpMethod::Verb BHttpMethod::Options
+ \brief Represents the \c OPTIONS method.
+
+ \since Haiku R1
+*/
+
+
+/*!
+ \var BHttpMethod::Verb BHttpMethod::Trace
+ \brief Represents the \c TRACE method.
+
+ \since Haiku R1
+*/
+
+
+/*!
+ \fn BHttpMethod::BHttpMethod(BHttpMethod &&other) noexcept
+ \brief Move constructor.
+
+ Moves the data from the \a other to this object. The \a other object will be set to
+ \ref BHttpMethod::Get.
+
+ \since Haiku R1
+*/
+
+
+/*!
+ \fn BHttpMethod::BHttpMethod(const BHttpMethod &other)
+ \brief Copy constructor.
+
+ Copy data from an \a other object.
+
+ \exception std::bad_alloc When the \a other object contains a custom verb, this exception
+ will be raised if it is impossible to allocate memory.
+
+ \since Haiku R1
+*/
+
+
+/*!
+ \fn BHttpMethod::BHttpMethod(const std::string_view &method)
+ \brief Construct a custom method.
+
+ \param method The verb for the method.
+
+ \exception std::bad_alloc In case it is not possible to allocate memory for the custom string.
+ \exception BHttpMethod::InvalidMethod In case the \a method is empty or contains invalid
+ characters.
+
+ \since Haiku R1
+*/
+
+
+/*!
+ \fn BHttpMethod::BHttpMethod(Verb verb) noexcept
+ \brief Construct a standard method.
+
+ \param verb The chosen method.
+
+ \since Haiku R1
+*/
+
+
+/*!
+ \fn BHttpMethod::~BHttpMethod()
+ \brief Destructor.
+
+ \since Haiku R1
+*/
+
+
+/*!
+ \fn const std::string_view BHttpMethod::Method() const noexcept
+ \brief Get a string representation of the method.
+
+ \return A \c std::string_view that is a string representation of the standard or custom method
+ in this object. The lifetime of the string view is bound to the lifetime of this method.
+
+ \since Haiku R1
+*/
+
+
+/*!
+ \fn BHttpMethod& BHttpMethod::operator=(BHttpMethod &&other) noexcept
+ \brief Move assignment.
+ Moves the data from the \a other to this object. The \a other object will be set to
+ \ref BHttpMethod::Get.
+
+ \since Haiku R1
+*/
+
+
+/*!
+ \fn BHttpMethod& BPrivate::Network::BHttpMethod::operator=(const BHttpMethod &other)
+ \brief Copy assignment.
+
+ Copy data from an \a other object.
+
+ \exception std::bad_alloc When the \a other object contains a custom verb, this exception
+ will be raised if it is impossible to allocate memory.
+
+ \since Haiku R1
+*/
+
+
+} // namespace Network
+
+} // namespace BPrivate
+
+#endif
diff --git a/headers/private/netservices2/HttpRequest.h b/headers/private/netservices2/HttpRequest.h
new file mode 100644
index 0000000000..341b08f773
--- /dev/null
+++ b/headers/private/netservices2/HttpRequest.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2022 Haiku Inc. All rights reserved.
+ * Distributed under the terms of the MIT License.
+ */
+
+#ifndef _B_HTTP_REQUEST_H_
+#define _B_HTTP_REQUEST_H_
+
+#include
+#include
+
+#include
+#include
+
+
+namespace BPrivate {
+
+namespace Network {
+
+
+class BHttpMethod {
+public:
+ // Constants for default methods in RFC 7230 section 4.2
+ enum Verb {
+ Get,
+ Head,
+ Post,
+ Put,
+ Delete,
+ Connect,
+ Options,
+ Trace
+ };
+
+ // Error type when constructing with a custom method
+ class InvalidMethod : public BError {
+ public:
+ InvalidMethod(const char* origin, BString input);
+
+ virtual const char* Message() const noexcept override;
+ virtual BString DebugMessage() const override;
+
+ BString input;
+ };
+
+ // Constructors & Destructor
+ BHttpMethod(Verb verb) noexcept;
+ BHttpMethod(const std::string_view& method);
+ BHttpMethod(const BHttpMethod& other);
+ BHttpMethod(BHttpMethod&& other) noexcept;
+ ~BHttpMethod();
+
+ // Assignment operators
+ BHttpMethod& operator=(const BHttpMethod& other);
+ BHttpMethod& operator=(BHttpMethod&& other) noexcept;
+
+ // Get the method as a string
+ const std::string_view Method() const noexcept;
+
+private:
+ std::variant fMethod;
+};
+
+
+} // namespace Network
+
+} // namespace BPrivate
+
+#endif // B_HTTP_REQUEST
diff --git a/src/kits/network/libnetservices2/HttpRequest.cpp b/src/kits/network/libnetservices2/HttpRequest.cpp
new file mode 100644
index 0000000000..892a9b266a
--- /dev/null
+++ b/src/kits/network/libnetservices2/HttpRequest.cpp
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2022 Haiku Inc. All rights reserved.
+ * Distributed under the terms of the MIT License.
+ *
+ * Authors:
+ * Niels Sascha Reedijk, niels.reedijk@gmail.com
+ */
+
+#include
+
+#include
+#include
+#include
+
+#include "HttpPrivate.h"
+
+using namespace std::literals;
+using namespace BPrivate::Network;
+
+
+// #pragma mark -- BHttpMethod::InvalidMethod
+
+
+BHttpMethod::InvalidMethod::InvalidMethod(const char* origin, BString input)
+ :
+ BError(origin),
+ input(std::move(input))
+{
+
+}
+
+
+const char*
+BHttpMethod::InvalidMethod::Message() const noexcept
+{
+ if (input.IsEmpty())
+ return "The HTTP method cannot be empty";
+ else
+ return "Unsupported characters in the HTTP method";
+}
+
+
+BString
+BHttpMethod::InvalidMethod::DebugMessage() const
+{
+ BString output = BError::DebugMessage();
+ if (!input.IsEmpty())
+ output << ":\t " << input << "\n";
+ return output;
+}
+
+
+// #pragma mark -- BHttpMethod
+
+
+BHttpMethod::BHttpMethod(Verb verb) noexcept
+ : fMethod(verb)
+{
+
+}
+
+
+BHttpMethod::BHttpMethod(const std::string_view& verb)
+ : fMethod(BString(verb.data(), verb.length()))
+{
+ if (verb.size() == 0 || !validate_http_token_string(verb))
+ throw BHttpMethod::InvalidMethod(__PRETTY_FUNCTION__, std::move(std::get(fMethod)));
+}
+
+
+BHttpMethod::BHttpMethod(const BHttpMethod& other) = default;
+
+
+BHttpMethod::BHttpMethod(BHttpMethod&& other) noexcept
+ : fMethod(std::move(other.fMethod))
+{
+ other.fMethod = Get;
+}
+
+
+BHttpMethod::~BHttpMethod() = default;
+
+
+BHttpMethod&
+BHttpMethod::operator=(const BHttpMethod& other) = default;
+
+
+BHttpMethod&
+BHttpMethod::operator=(BHttpMethod&& other) noexcept
+{
+ fMethod = std::move(other.fMethod);
+ other.fMethod = Get;
+ return *this;
+}
+
+
+const std::string_view
+BHttpMethod::Method() const noexcept
+{
+ if (std::holds_alternative(fMethod)) {
+ switch (std::get(fMethod)) {
+ case Get:
+ return "GET"sv;
+ case Head:
+ return "HEAD"sv;
+ case Post:
+ return "POST"sv;
+ case Put:
+ return "PUT"sv;
+ case Delete:
+ return "DELETE"sv;
+ case Connect:
+ return "CONNECT"sv;
+ case Options:
+ return "OPTIONS"sv;
+ case Trace:
+ return "TRACE"sv;
+ default:
+ // should never be reached
+ std::abort();
+ }
+ } else {
+ const auto& methodString = std::get(fMethod);
+ // the following constructor is not noexcept, but we know we pass in valid data
+ return std::string_view(methodString.String());
+ }
+}
diff --git a/src/kits/network/libnetservices2/Jamfile b/src/kits/network/libnetservices2/Jamfile
index 05bb8ca126..1c33f214ec 100644
--- a/src/kits/network/libnetservices2/Jamfile
+++ b/src/kits/network/libnetservices2/Jamfile
@@ -17,6 +17,7 @@ for architectureObject in [ MultiArchSubDirSetup ] {
StaticLibrary [ MultiArchDefaultGristFiles libnetservices2.a ] :
ErrorsExt.cpp
HttpFields.cpp
+ HttpRequest.cpp
HttpSession.cpp
NetServicesMisc.cpp
;
diff --git a/src/tests/kits/net/netservices2/HttpProtocolTest.cpp b/src/tests/kits/net/netservices2/HttpProtocolTest.cpp
index c965ff8920..1b6d5a7292 100644
--- a/src/tests/kits/net/netservices2/HttpProtocolTest.cpp
+++ b/src/tests/kits/net/netservices2/HttpProtocolTest.cpp
@@ -13,8 +13,10 @@
#include
#include
+#include
using BPrivate::Network::BHttpFields;
+using BPrivate::Network::BHttpMethod;
using BPrivate::Network::BHttpSession;
@@ -168,6 +170,55 @@ HttpProtocolTest::HttpFieldsTest()
}
+void
+HttpProtocolTest::HttpMethodTest()
+{
+ using namespace std::literals;
+
+ // Default methods
+ {
+ CPPUNIT_ASSERT_EQUAL(BHttpMethod(BHttpMethod::Get).Method(), "GET"sv);
+ CPPUNIT_ASSERT_EQUAL(BHttpMethod(BHttpMethod::Head).Method(), "HEAD"sv);
+ CPPUNIT_ASSERT_EQUAL(BHttpMethod(BHttpMethod::Post).Method(), "POST"sv);
+ CPPUNIT_ASSERT_EQUAL(BHttpMethod(BHttpMethod::Put).Method(), "PUT"sv);
+ CPPUNIT_ASSERT_EQUAL(BHttpMethod(BHttpMethod::Delete).Method(), "DELETE"sv);
+ CPPUNIT_ASSERT_EQUAL(BHttpMethod(BHttpMethod::Connect).Method(), "CONNECT"sv);
+ CPPUNIT_ASSERT_EQUAL(BHttpMethod(BHttpMethod::Options).Method(), "OPTIONS"sv);
+ CPPUNIT_ASSERT_EQUAL(BHttpMethod(BHttpMethod::Trace).Method(), "TRACE"sv);
+ }
+
+ // Valid custom method
+ {
+ try {
+ auto method = BHttpMethod("PATCH"sv);
+ CPPUNIT_ASSERT_EQUAL(method.Method(), "PATCH"sv);
+ } catch (...) {
+ CPPUNIT_FAIL("Unexpected error when creating valid method");
+ }
+ }
+
+ // Invalid empty method
+ try {
+ auto method = BHttpMethod("");
+ CPPUNIT_FAIL("Creating an empty method was succesful unexpectedly");
+ } catch (BHttpMethod::InvalidMethod&) {
+ // success
+ } catch (...) {
+ CPPUNIT_FAIL("Unexpected exception type when creating an empty method");
+ }
+
+ // Method with invalid characters (arabic translation of GET)
+ try {
+ auto method = BHttpMethod("جلب");
+ CPPUNIT_FAIL("Creating a method with invalid characters was succesful unexpectedly");
+ } catch (BHttpMethod::InvalidMethod&) {
+ // success
+ } catch (...) {
+ CPPUNIT_FAIL("Unexpected exception type when creating a method with invalid characters");
+ }
+}
+
+
/* static */ void
HttpProtocolTest::AddTests(BTestSuite& parent)
{
@@ -175,6 +226,8 @@ HttpProtocolTest::AddTests(BTestSuite& parent)
suite.addTest(new CppUnit::TestCaller(
"HttpProtocolTest::HttpFieldsTest", &HttpProtocolTest::HttpFieldsTest));
+ suite.addTest(new CppUnit::TestCaller(
+ "HttpProtocolTest::HttpMethodTest", &HttpProtocolTest::HttpMethodTest));
parent.addTest("HttpProtocolTest", &suite);
}
diff --git a/src/tests/kits/net/netservices2/HttpProtocolTest.h b/src/tests/kits/net/netservices2/HttpProtocolTest.h
index 10e673f004..9519ba4354 100644
--- a/src/tests/kits/net/netservices2/HttpProtocolTest.h
+++ b/src/tests/kits/net/netservices2/HttpProtocolTest.h
@@ -18,6 +18,7 @@ public:
HttpProtocolTest();
void HttpFieldsTest();
+ void HttpMethodTest();
static void AddTests(BTestSuite& suite);