From ec865cb87e0430bfa8e6661ee165af7d10616a68 Mon Sep 17 00:00:00 2001 From: Niels Sascha Reedijk Date: Sun, 20 Feb 2022 09:40:20 +0000 Subject: [PATCH] NetServices: Add BHttpMethod that represents a HTTP method This class provides defaults and performs basic validation for HTTP Methods as defined by the standard. They will be used in conjunction with a future BHttpRequest class. Change-Id: If69a7ec186d9d1165e8efe5ab5df50d5a089208d --- docs/user/netservices/HttpRequest.dox | 255 ++++++++++++++++++ headers/private/netservices2/HttpRequest.h | 69 +++++ .../network/libnetservices2/HttpRequest.cpp | 127 +++++++++ src/kits/network/libnetservices2/Jamfile | 1 + .../net/netservices2/HttpProtocolTest.cpp | 53 ++++ .../kits/net/netservices2/HttpProtocolTest.h | 1 + 6 files changed, 506 insertions(+) create mode 100644 docs/user/netservices/HttpRequest.dox create mode 100644 headers/private/netservices2/HttpRequest.h create mode 100644 src/kits/network/libnetservices2/HttpRequest.cpp 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);