<|start_filename|>tests/HttpResponseTest.cpp<|end_filename|>
//
// Created by fred.nicolson on 25/09/17.
//

#include <gtest/gtest.h>
#include <frnetlib/HttpResponse.h>

TEST(HttpResponseTest, response_parse_v1)
{
    const std::string raw_response =
            "HTTP/1.0 301 Moved Permanently\n"
            "Server: nginx/1.10.2\n"
            "Date: Mon, 25 Sep 2017 13:51:56 GMT\n"
            "Content-Type: text/html\n"
            "Content-Length: 0\n"
            "Connection: keep-alive\n"
            "Location: https://frednicolson.co.uk/\n\n";

    //Parse response
    fr::HttpResponse test;
    ASSERT_EQ(test.parse(raw_response.c_str(), raw_response.size()), fr::Socket::Status::Success);

    //Verify it
    ASSERT_EQ(test.get_version(), fr::Http::RequestVersion::V1);
}

TEST(HttpResponseTest, response_parse_v2)
{
    const std::string raw_response =
            "HTTP/1.1 301 Moved Permanently\n"
            "Server: nginx/1.10.2\n"
            "Date: Mon, 25 Sep 2017 13:51:56 GMT\n"
            "Content-Type: text/html\n"
            "Content-Length: 177\n"
            "Connection: keep-alive\n"
            "Location: https://frednicolson.co.uk/\n"
            "\n"
            "<html>\n"
            "<head><title>301 Moved Permanently</title></head>\n"
            "<body bgcolor=\"white\">\n"
            "<center><h1>301 Moved Permanently</h1></center>\n"
            "<hr><center>nginx/1.10.2</center>\n"
            "</body>\n"
            "</html>";

    const std::string response_body =
            "<html>\n"
            "<head><title>301 Moved Permanently</title></head>\n"
            "<body bgcolor=\"white\">\n"
            "<center><h1>301 Moved Permanently</h1></center>\n"
            "<hr><center>nginx/1.10.2</center>\n"
            "</body>\n"
            "</html>";

    //Parse response
    fr::HttpResponse test;
    ASSERT_EQ(test.parse(raw_response.c_str(), raw_response.size()), fr::Socket::Status::Success);

    //Verify it
    ASSERT_EQ(test.get_version(), fr::Http::RequestVersion::V1_1);
    ASSERT_EQ(test.get_status(), fr::Http::RequestStatus::MovedPermanently);
    ASSERT_EQ(test.header("Content-length"), "177");
    ASSERT_EQ(test.get_body(), response_body);
}

TEST(HttpResponseTest, response_partial_parse)
{
    const std::string raw_response1 =
            "HTTP/1.1 301 Moved Permanently\n"
                    "Server: nginx/1.10.2\n"
                    "Date: Mon, 25 Sep 2017 13:51:56 GMT\n"
                    "Content-Type: text/html\n"
                    "Content-Length: 177\n"
                    "Connection: keep-alive\n";

    std::string raw_response2 =
                    "Location: https://frednicolson.co.uk/\n"
                    "\n"
                    "<html>\n"
                    "<head><title>301 Moved Permanently</title></head>\n"
                    "<body bgcolor=\"white\">\n";

    std::string raw_response3 =
                    "<center><h1>301 Moved Permanently</h1></center>\n"
                    "<hr><center>nginx/1.10.2</center>\n"
                    "</body>\n"
                    "</html>";

    const std::string response_body =
            "<html>\n"
            "<head><title>301 Moved Permanently</title></head>\n"
            "<body bgcolor=\"white\">\n"
            "<center><h1>301 Moved Permanently</h1></center>\n"
            "<hr><center>nginx/1.10.2</center>\n"
            "</body>\n"
            "</html>";

    //Parse response
    fr::HttpResponse test;
    ASSERT_EQ(test.parse(raw_response1.c_str(), raw_response1.size()), fr::Socket::Status::NotEnoughData);
    ASSERT_EQ(test.parse(raw_response2.c_str(), raw_response2.size()), fr::Socket::Status::NotEnoughData);
    ASSERT_EQ(test.parse(raw_response3.c_str(), raw_response3.size()), fr::Socket::Status::Success);

    //Verify it
    ASSERT_EQ(test.get_status(), fr::Http::RequestStatus::MovedPermanently);
    ASSERT_EQ(test.header("Content-length"), "177");
    ASSERT_EQ(test.get_body(), response_body);
}

TEST(HttpResponseTest, parse_chunked_response_test)
{
    const std::string raw_response1 =
                                    "HTTP/1.1 200 OK\r\n"
                                    "Content-Type: text/plain\r\n"
                                    "Transfer-Encoding: chunked\r\n"
                                    "\r\n"
                                    "7\r\n"
                                    "Mozilla\r\n"
                                    "9\r\n"
                                    "Developer\r\n"
                                    "7\r\n"
                                    "Network\r\n"
                                    "0\r\n"
                                    "\r\n";

    //Parse response
    fr::HttpResponse test;
    ASSERT_EQ(test.parse(raw_response1.c_str(), raw_response1.size()), fr::Socket::Status::Success);

    //Verify it
    ASSERT_EQ(test.get_status(), fr::Http::RequestStatus::Ok);
    ASSERT_EQ(test.get_body(), "MozillaDeveloperNetwork");
}

TEST(HttpResponseTest, parse_partial_chunked_response_test)
{
    const std::string raw_response1 =
            "HTTP/1.1 200 OK\r\n"
            "Content-Type: text/plain\r\n"
            "Transfer-Encoding: chunked\r\n";

    const std::string raw_response2 =
            "\r\n"
            "7\r\n"
            "Mozilla\r\n";

    const std::string raw_response3 =
            "9\r\n";

    const std::string raw_response4 =
            "Developer\r\n"
            "7\r\n"
            "Netw";
    const std::string raw_response5 =
            "ork\r\n"
            "0\r\n";

    const std::string raw_response6 =
            "\r\n";

    //Parse response
    fr::HttpResponse test;
    ASSERT_EQ(test.parse(raw_response1.c_str(), raw_response1.size()), fr::Socket::Status::NotEnoughData);
    ASSERT_EQ(test.parse(raw_response2.c_str(), raw_response2.size()), fr::Socket::Status::NotEnoughData);
    ASSERT_EQ(test.parse(raw_response3.c_str(), raw_response3.size()), fr::Socket::Status::NotEnoughData);
    ASSERT_EQ(test.parse(raw_response4.c_str(), raw_response4.size()), fr::Socket::Status::NotEnoughData);
    ASSERT_EQ(test.parse(raw_response5.c_str(), raw_response5.size()), fr::Socket::Status::NotEnoughData);
    ASSERT_EQ(test.parse(raw_response6.c_str(), raw_response6.size()), fr::Socket::Status::Success);

    //Verify it

    ASSERT_EQ(test.get_status(), fr::Http::RequestStatus::Ok);
    ASSERT_EQ(test.get_body(), "MozillaDeveloperNetwork");
}

TEST(HttpResponseTest, header_length_test)
{
    //Try data with no header end first
    std::string buff(MAX_HTTP_HEADER_SIZE + 1, '\0');
    fr::HttpResponse response;
    buff.insert(0, "HTTP");
    ASSERT_EQ(response.parse(buff.c_str(), buff.size()), fr::Socket::Status::HttpHeaderTooBig);
    response = {};

    //Now try short header but long data, this should work
    buff = "HTTP/1.1 301 Moved Permanently\n"
                    "Content-Type: text/html\n"
                    "Content-Length: " + std::to_string(MAX_HTTP_BODY_SIZE - 1) + "\n"
                    "Connection: keep-alive\n"
                    "\n" + std::string(MAX_HTTP_BODY_SIZE - 1, '\0');
    ASSERT_EQ(response.parse(buff.c_str(), buff.size()), fr::Socket::Status::Success);
}

TEST(HttpResponseTest, body_length_test)
{
    std::string buff =
            "HTTP/1.1 301 Moved Permanently\n"
            "Content-Type: text/html\n"
            "Content-Length: " + std::to_string(MAX_HTTP_BODY_SIZE + 1) + "\n"
            "Connection: keep-alive\n"
            "\n";
    buff += std::string(MAX_HTTP_BODY_SIZE + 1, '\0');
    fr::HttpResponse response;
    ASSERT_EQ(response.parse(buff.c_str(), buff.size()), fr::Socket::Status::HttpBodyTooBig);
}

TEST(HttpResponseTest, HttpResponseConstruction)
{
    {
        fr::HttpResponse response;
        response.set_status(fr::Http::RequestStatus::ImATeapot);
        response.header("bob") = "trob";
        response.set_body("lob");
        auto constructed = response.construct("frednicolson.co.uk");
        response = {};
        ASSERT_EQ(response.parse(constructed.c_str(), constructed.size()), fr::Socket::Status::Success);

        ASSERT_EQ(response.get_version(), fr::Http::RequestVersion::V1_1);
        ASSERT_EQ(response.get_status(), fr::Http::RequestStatus::ImATeapot);
        ASSERT_EQ(response.get_body(), "lob");
        ASSERT_EQ(response.header("bob"), "trob");
    }

    {
        fr::HttpResponse response;
        response.set_version(fr::Http::RequestVersion::V1);
        auto constructed = response.construct("frednicolson.co.uk");
        response = {};
        response.parse(constructed.c_str(), constructed.size());

        ASSERT_EQ(response.get_version(), fr::Http::RequestVersion::V1);
    }

}

<|start_filename|>tests/NetworkEncodingTest.cpp<|end_filename|>
//
// Created by fred.nicolson on 01/02/18.
//

#include <gtest/gtest.h>
#include <limits>
#include <algorithm>
#include "frnetlib/NetworkEncoding.h"

bool is_little_endian()
{
    unsigned short x=0x0001;
    auto p = (unsigned char*)(&x);
    return *p != 0;
}

TEST(NetworkEncodingTest, test_htonf)
{
    float input = std::numeric_limits<float>::max() - 50;
    float result = fr::htonf(input);

    if(is_little_endian())
    {
        float manual;
        std::reverse_copy((char*)&input, (char*)&input + sizeof(input), (uint8_t*)&manual);
        ASSERT_EQ(memcmp(&result, &manual, sizeof(manual)), 0);
    }
    else
    {
        ASSERT_EQ(result, input);
    }
}

TEST(NetworkEncodingTest, test_ntohf)
{
    float input = std::numeric_limits<float>::max() - 50;
    float encoded = fr::htonf(input);
    float decoded = fr::ntohf(encoded);
    ASSERT_EQ(input, decoded);
}

TEST(NetworkEncodingTest, test_htond)
{
    double input = std::numeric_limits<double>::max() - 50;
    double result = fr::htond(input);

    if(is_little_endian())
    {
        double manual;
        std::reverse_copy((char*)&input, (char*)&input + sizeof(input), (uint8_t*)&manual);
        ASSERT_EQ(memcmp(&result, &manual, sizeof(manual)), 0);
    }
    else
    {
        ASSERT_EQ(result, input);
    }
}

TEST(NetworkEncodingTest, test_ntohd)
{
    double input = std::numeric_limits<double>::max();
    double encoded = fr::htond(input);
    double decoded = fr::ntohd(encoded);
    ASSERT_EQ(input, decoded);
}

TEST(NetworkEncodingTest, test_htonll)
{
    uint64_t input = std::numeric_limits<uint64_t>::max() - 50;
    uint64_t result = fr_htonll(input);

    if(is_little_endian())
    {
        uint64_t manual;
        std::reverse_copy((char*)&input, (char*)&input + sizeof(input), (uint8_t*)&manual);
        ASSERT_EQ(manual, result);
    }
    else
    {
        ASSERT_EQ(result, input);
    }
}

TEST(NetworkEncodingTest, test_ntohll)
{
    uint64_t input = std::numeric_limits<uint64_t>::max() - 50;
    uint64_t encoded = fr_htonll(input);
    uint64_t decoded = fr_ntohll(encoded);
    ASSERT_EQ(input, decoded);
}

<|start_filename|>include/frnetlib/Packet.h<|end_filename|>
//
// Created by fred on 06/12/16.
//

#ifndef FRNETLIB_PACKET_H
#define FRNETLIB_PACKET_H
#include <string>
#include <cstring>
#include <iostream>
#include <stdexcept>
#include <vector>
#include <map>
#include <unordered_map>
#include "NetworkEncoding.h"
#include "Packetable.h"
#include "Sendable.h"

#ifdef __cpp_lib_string_view
#include <string_view>
#endif

namespace fr
{
#define PACKET_HEADER_LENGTH sizeof(uint32_t)
    class Packet : public Sendable
    {
    public:
        Packet() noexcept
        : buffer(PACKET_HEADER_LENGTH, '0'),
          buffer_read_index(PACKET_HEADER_LENGTH)
        {

        }

        //Nasty constructor to allow things like Packet{1, 2, 3, "bob"}.
        template <typename T, typename ...Args>
        Packet(T const &part, Args &&...args)
        : Packet()
        {
            add(part, std::forward<Args>(args)...);
        }

        /*!
         * Variadic add function for adding multiple values at once to the packet,
         * used by packet constructor.
         *
         * @param part The first argument to add
         * @param args The remaining arguments to add
         */
        template<typename T, typename ...Args>
        inline void add(T const& part, Args &&...args)
        {
            *this << part;
            add(std::forward<Args>(args)...);
        }

        /*!
         * Part of the variadic add function.
         *
         * @param part The argument to add to the packet
         */
        template<typename T>
        inline void add(T const &part)
        {
            *this << part;
        }

        /*!
         * Add function to allow adding iterator ranges to the packet.
         *
         * @note std::distance() is used, and so this function should ideally be used with random access
         * iterators to achieve constant time complexity.
         * @tparam Iter The iterator type
         * @param begin An iterator to the first element to add from
         * @param end A past-the-end iterator to stop adding at
         */
        template<typename Iter>
        inline void add_range(Iter begin, Iter end)
        {
            *this << static_cast<uint32_t>(std::distance(begin, end));
            for(auto iter = begin; iter != end; ++iter)
                *this << *iter;
        }

        /*!
         * Adds raw data to packet
         *
         * @param data The data to add
         * @param datasz The length of data
         */
        inline void add_raw(const char *data, size_t datasz)
        {
            buffer.append(data, datasz);
        }

        /*!
         * Extracts raw data from a packet
         *
         * @param data Where to store the extracted data
         * @param datasz The number of bytes to extract
         */
        inline void extract_raw(char *data, size_t datasz)
        {
            assert_bytes_remaining(datasz);

            memcpy(data, &buffer[buffer_read_index], datasz);
            buffer_read_index += datasz;
        }

        /*
         * Adds a vector to a packet
         */
        template<typename T>
        inline Packet &operator<<(const std::vector<T> &vec)
        {
            //First store its length
            *this << static_cast<uint32_t>(vec.size());

            //Now each of the elements
            for(const auto &iter : vec)
            {
                *this << iter;
            }

            return *this;
        }

        /*
         * Extracts a vector from the packet
         */
        template<typename T>
        inline Packet &operator>>(std::vector<T> &vec)
        {
            uint32_t length;

            //First extract the length
            *this >> length;
            vec.resize(length);

            //Now take each of the elements out of the packet
            for(auto &&iter : vec)
            {
                *this >> iter;
            }
            return *this;
        }

        /*
         * Adds a map to a packet
         */
        template<typename A, typename B>
        inline Packet &operator<<(const std::map<A, B> &m)
        {
            //First store its length
            *this << static_cast<uint32_t>(m.size());

            //Now each of the elements
            for(const auto &iter : m)
            {
                *this << iter;
            }

            return *this;
        }

        /*
         * Extracts a map from the packet
         */
        template<typename A, typename B>
        inline Packet &operator>>(std::map<A, B> &m)
        {
            uint32_t length;

            //First extract the length
            *this >> length;

            //Now take each of the elements out of the packet
            for(uint32_t a = 0; a < length; a++)
            {
                std::pair<A, B> pair;
                *this >> pair;
                m.emplace(std::move(pair));
            }

            return *this;
        }

        /*
         * Adds an unordered_map to a packet
         */
        template<typename A, typename B>
        inline Packet &operator<<(const std::unordered_map<A, B> &m)
        {
            //First store its length
            *this << static_cast<uint32_t>(m.size());

            //Now each of the elements
            for(const auto &iter : m)
            {
                *this << iter;
            }

            return *this;
        }

        /*
         * Extracts an unordered_map from the packet
         */
        template<typename A, typename B>
        inline Packet &operator>>(std::unordered_map<A, B> &m)
        {
            uint32_t length;

            //First extract the length
            *this >> length;

            //Now take each of the elements out of the packet
            for(uint32_t a = 0; a < length; a++)
            {
                std::pair<A, B> pair;
                *this >> pair;
                m.emplace(std::move(pair));
            }

            return *this;
        }

        /*
         * For packing pairs
         */
        template<typename A, typename B>
        inline Packet &operator<<(const std::pair<A, B> &var)
        {
            *this << var.first;
            *this << var.second;
            return *this;
        }

        /*
         * For extracting pairs
         */
        template<typename A, typename B>
        inline Packet &operator>>(std::pair<A, B> &var)
        {
            *this >> var.first;
            *this >> var.second;
            return *this;
        }


        /*
         * Adds a boolean variable to the packet
         */
        inline Packet &operator<<(bool var)
        {
            buffer.append((char*)&var, sizeof(var));
            return *this;
        }

        /*
         * Extracts a boolean variable from the packet
         */
        inline Packet &operator>>(bool &var)
        {
            assert_bytes_remaining(sizeof(var));

            memcpy(&var, &buffer[buffer_read_index], sizeof(var));
            buffer_read_index += sizeof(var);
            return *this;
        }

        /*
         * Adds an 8bit variable to the packet
         */
        inline Packet &operator<<(uint8_t var)
        {
            buffer.append((char*)&var, sizeof(var));
            return *this;
        }

        /*
         * Extracts an 8bit variable from the packet
         */
        inline Packet &operator>>(uint8_t &var)
        {
            assert_bytes_remaining(sizeof(var));

            memcpy(&var, &buffer[buffer_read_index], sizeof(var));
            buffer_read_index += sizeof(var);
            return *this;
        }

        /*
         * Adds a 16bit variable to the packet
         */
        inline Packet &operator<<(uint16_t var)
        {
            var = htons(var);
            buffer.append((char*)&var, sizeof(var));
            return *this;
        }

        /*
         * Extracts a 16bit variable from the packet
         */
        inline Packet &operator>>(uint16_t &var)
        {
            assert_bytes_remaining(sizeof(var));

            memcpy(&var, &buffer[buffer_read_index], sizeof(var));
            buffer_read_index += sizeof(var);
            var = ntohs(var);
            return *this;
        }

        /*
         * Adds a 32bit variable to the packet
         */
        inline Packet &operator<<(uint32_t var)
        {
            var = htonl(var);
            buffer.append((char*)&var, sizeof(var));
            return *this;
        }

        /*
         * Extracts a 32bit variable from the packet
         */
        inline Packet &operator>>(uint32_t &var)
        {
            assert_bytes_remaining(sizeof(var));
            memcpy(&var, &buffer[buffer_read_index], sizeof(var));
            buffer_read_index += sizeof(var);
            var = ntohl(var);
            return *this;
        }

        /*
         * Adds a 64bit variable to the packet
         */
        inline Packet &operator<<(uint64_t var)
        {
            var = fr_htonll(var);
            buffer.append((char*)&var, sizeof(var));
            return *this;
        }

        /*
         * Extracts a 64bit variable from the packet
         */
        inline Packet &operator>>(uint64_t &var)
        {
            assert_bytes_remaining(sizeof(var));

            memcpy(&var, &buffer[buffer_read_index], sizeof(var));
            buffer_read_index += sizeof(var);
            var = fr_ntohll(var);
            return *this;
        }

        /*
         * Adds a 16bit variable to the packet
         */
        inline Packet &operator<<(int16_t var)
        {
            var = htons((uint16_t)var);
            buffer.append((char*)&var, sizeof(var));
            return *this;
        }

        /*
         * Extracts a 16bit variable from the packet
         */
        inline Packet &operator>>(int16_t &var)
        {
            assert_bytes_remaining(sizeof(var));

            memcpy(&var, &buffer[buffer_read_index], sizeof(var));
            buffer_read_index += sizeof(var);
            var = ntohs((uint16_t)var);
            return *this;
        }

        /*
         * Adds a 32bit variable to the packet
         */
        inline Packet &operator<<(int32_t var)
        {
            var = htonl((uint32_t)var);
            buffer.append((char*)&var, sizeof(var));
            return *this;
        }

        /*
         * Extracts a 32bit variable from the packet
         */
        inline Packet &operator>>(int32_t &var)
        {
            assert_bytes_remaining(sizeof(var));

            memcpy(&var, &buffer[buffer_read_index], sizeof(var));
            buffer_read_index += sizeof(var);
            var = ntohl((uint32_t)var);
            return *this;
        }

        /*
         * Adds a 64bit variable to the packet
         */
        inline Packet &operator<<(int64_t var)
        {
            var = fr_htonll((uint64_t)var);
            buffer.append((char*)&var, sizeof(var));
            return *this;
        }

        /*
         * Extracts a 64bit variable from the packet
         */
        inline Packet &operator>>(int64_t &var)
        {
            assert_bytes_remaining(sizeof(var));

            memcpy(&var, &buffer[buffer_read_index], sizeof(var));
            buffer_read_index += sizeof(var);
            var = fr_ntohll((uint64_t)var);
            return *this;
        }

        /*
         * Adds an enum, or enum class to the packet.
         *
         * Underlying type must not be larger than 32bits.
         * The enum's underlying type should be specified like so:
         *
         * enum Enum : type{};
         */
        template<typename T, typename std::enable_if<std::is_enum<T>::value>::type* = nullptr>
        inline Packet &operator<<(T var)
        {
            static_assert(sizeof(typename std::underlying_type<T>::type) <= 4, "Enum types must not be larger than 32bits");
            *this << (uint32_t)static_cast<typename std::underlying_type<T>::type>(var);
            return *this;
        }

        /*
         * Extracts en enum, or enum class from the packet.
         *
         * Underlying type must not be larger than 32bits.
         * The enum's underlying type should be specified like so:
         *
         * enum Enum : type{};
         */
        template<typename T, typename std::enable_if<std::is_enum<T>::value>::type* = nullptr>
        inline Packet &operator>>(T &var)
        {
            static_assert(sizeof(typename std::underlying_type<T>::type) <= 4, "Enum types must not be larger than 32bits");
            uint32_t val;
            *this >> val;
            var = static_cast<T>(val);
            return *this;
        }


        /*
         * Adds a float variable to the packet
         */
        inline Packet &operator<<(float var)
        {
            var = htonf(var);
            buffer.append((char*)&var, sizeof(var));
            return *this;
        }

        /*
         * Extracts a float variable from the packet
         */
        inline Packet &operator>>(float &var)
        {
            assert_bytes_remaining(sizeof(var));

            memcpy(&var, &buffer[buffer_read_index], sizeof(var));
            buffer_read_index += sizeof(var);
            var = ntohf(var);
            return *this;
        }

        /*
         * Adds a double variable to the packet
         */
        inline Packet &operator<<(double var)
        {
            var = htond(var);
            buffer.append((char*)&var, sizeof(var));
            return *this;
        }

        /*
         * Extracts a double variable from the packet
         */
        inline Packet &operator>>(double &var)
        {
            assert_bytes_remaining(sizeof(var));

            memcpy(&var, &buffer[buffer_read_index], sizeof(var));
            buffer_read_index += sizeof(var);;
            var = ntohd(var);
            return *this;
        }

#ifdef __cpp_lib_string_view
        /*!
         * Pack an std::string_view. This will make a copy.
         * May be extracted as either an std::string or an std::string_view
         */
        inline Packet &operator<<(std::string_view view)
        {
            //Same as std::string
            *this << (uint32_t)view.size();
            buffer.append(view.data(), view.size());
            return *this;
        }

        /*!
         * Extracts a string as an std::string_view.
         *
         * @note Be very careful with this. If the packet is destroyed then the string_view will be too!
         */
        inline Packet&operator>>(std::string_view &var)
        {
            uint32_t length;
            *this >> length;

            assert_bytes_remaining(length);
            var = std::string_view(&buffer[buffer_read_index], length);
            buffer_read_index += length;
            return *this;
        }
#endif

        /*
         * Adds a string variable to the packet
         */
        inline Packet &operator<<(const std::string &var)
        {
            //Strings are prefixed with their length as a 32bit uint :)
            *this << (uint32_t)var.length();
            buffer.append(var);
            return *this;
        }

        /*
         * Removes a string variable from the packet
         */
        inline Packet&operator>>(std::string &var)
        {
            uint32_t length;
            *this >> length;

            assert_bytes_remaining(length);
            var.assign(&buffer[buffer_read_index], length);
            buffer_read_index += length;

            return *this;
        }

        /*
         * Removes a boolean from the packet into a reference
         */
        inline Packet&operator>>(std::vector<bool>::reference &ref)
        {
            bool b;
            *this >> b;
            ref = b;
            return *this;
        }


        /*
         * Adds a char array
         */
        inline Packet &operator<<(const char *var)
        {
            auto len = (uint32_t)strlen(var);
            *this << len;
            buffer.append(var, len);
            return *this;
        }

        /*!
         * Should be called to pack an object that inherits
         * fr::Packetable to the packet.
         *
         * @param object The class to pack
         * @return The current packet object for chaining
         */
        inline Packet &operator<<(const fr::Packetable &object)
        {
            object.pack(*this);
            return *this;
        }


        /*!
         * Should be called to unpack an object that inherits
         * fr::Packetable.
         *
         * @param object The object to unpack into
         * @return The current packet object for chaining
         */
        inline Packet &operator>>(fr::Packetable &object)
        {
            object.unpack(*this);
            return *this;
        }

        /*!
         * Sets the internal data buffer
         *
         * @param data What to set the packet to
         */
        inline void set_buffer(std::string &&data)
        {
            buffer = std::move(data);
        }

        /*!
         * Clears all data from the packet
         */
        inline void clear()
        {
            //Leave enough for the header
            buffer.clear();
            buffer.append(PACKET_HEADER_LENGTH, '\0');
            buffer_read_index = PACKET_HEADER_LENGTH;
        }

        /*!
         * Sets the read cursor back to 0, or a specific position.
         *
         * @note THIS IS AN *ABSOLUTE* SEEK
         * @param pos The buffer index to continue reading from.
         */
        inline void set_cursor(size_t pos = 0)
        {
            buffer_read_index = PACKET_HEADER_LENGTH + pos;
            if(buffer_read_index > buffer.size()) buffer_read_index = buffer.size();
        }

        /*!
         * Gets the current read cursor position within the packet
         *
         * @return Current read position
         */
        inline size_t get_cursor() const
        {
            return buffer_read_index - PACKET_HEADER_LENGTH;
        }

        /*!
         * Relative seek of the read cursor, to seek to a specific position within the packet.
         * If the seek is out of bounds, then the offset will be set to the nearest valid value.
         * So seeking to -10, would seek to 0. Seeking to 110/100 would seek to 100.
         *
         * @param pos The number of bytes to seek in either direction
         */
        inline void seek_cursor(ssize_t pos)
        {
            ssize_t new_offset = buffer_read_index + pos;
            if(new_offset < (ssize_t)PACKET_HEADER_LENGTH)
                new_offset = PACKET_HEADER_LENGTH;
            if(new_offset > (ssize_t)buffer.size())
                new_offset = buffer.size();
            buffer_read_index = static_cast<size_t>(new_offset);
        }

        /*!
         * Reserves space in the internal packet buffer,
         * for if you know how much data you expect to receive
         * or send.
         *
         * @param bytes The number of bytes to reserve
         */
        inline void reserve(size_t bytes)
        {
            buffer.reserve(PACKET_HEADER_LENGTH + bytes);
        }

        /*!
         * Checks that there's enough data in the buffer to extract
         * a given number of bytes to prevent buffer overflows.
         *
         * @throws an std::out_of_range exception if there is not enough space.
         * @param required_space The number of bytes needed
         */
        inline void assert_bytes_remaining(size_t required_space) const
        {
            if(buffer_read_index + required_space > buffer.size())
            {
                throw std::out_of_range("Not enough bytes remaining in packet to extract requested data");
            }
        }

        /*!
         * Gets the number of bytes available until the end of the packet
         *
         * @return The number of bytes available until the end of the packet
         */
        inline size_t get_bytes_remaining() const
        {
            return buffer.size() - buffer_read_index;
        }

        /*!
         * Gets the size of the packet in bytes, this does not take into
         * account the cursor position, use get_bytes_remaining() if you want to
         * see how many more bytes can be extracted.
         *
         * @note This does not include the packet header length
         *
         * @return The size in bytes of the packet
         */
        inline size_t size() const
        {
            return buffer.size() <= PACKET_HEADER_LENGTH ? 0 : buffer.size() - PACKET_HEADER_LENGTH;
        }

        /*!
         * Overridable send, to allow
         * custom types to be directly sent through
         * sockets.
         *
         * @param socket The socket to send through
         * @return Status indicating if the send succeeded or not.
         */
        Socket::Status send(Socket *socket) const override
        {
            uint32_t length = htonl((uint32_t)buffer.size() - PACKET_HEADER_LENGTH);
            memcpy(&buffer[0], &length, sizeof(uint32_t));
            fr::Socket::Status state;
            size_t sent = 0;
            do
            {
                state = socket->send_raw(&buffer[0], buffer.size(), sent);
            } while(state == fr::Socket::Status::WouldBlock);
            return state;
        }

        /*!
         * Overrideable receive, to allow
         * custom types to be directly received through
         * sockets.
         *
         * @return Status indicating if the send succeeded or not:
         * 'Success': All good, object still valid.
         * 'WouldBlock' or 'Timeout': No data received. Object still valid though.
         * Anything else: Object invalid. Call disconnect().
         */
        Socket::Status receive(Socket *socket) override
        {
            Socket::Status status;

            //Try to read packet length
            uint32_t packet_length = 0;
            status = socket->receive_all(&packet_length, sizeof(packet_length));
            if(status != Socket::Status::Success)
                return status;
            packet_length = ntohl(packet_length);

            //Check that packet_length doesn't exceed the limit, if any
            if(socket->get_max_receive_size() && packet_length > socket->get_max_receive_size())
                return fr::Socket::Status::MaxPacketSizeExceeded;

            //Now we've got the length, read the rest of the data in
            if(packet_length + PACKET_HEADER_LENGTH > buffer.size())
                buffer.resize(packet_length + PACKET_HEADER_LENGTH);

            do
            {
                status = socket->receive_all(&buffer[PACKET_HEADER_LENGTH], packet_length);
            } while(status == fr::Socket::Status::WouldBlock);
            if(status == fr::Socket::Status::Timeout)
                status = fr::Socket::Status::Disconnected;
            return status;
        }

    private:
        friend class Socket;


        mutable std::string buffer; //Packet data buffer
        size_t buffer_read_index; //Current read position
    };
}


#endif //FRNETLIB_PACKET_H


<|start_filename|>include/frnetlib/Http.h<|end_filename|>
//
// Created by fred on 11/12/16.
//

#ifndef FRNETLIB_HTTP_H
#define FRNETLIB_HTTP_H
#include <string>
#include <vector>
#include <unordered_map>
#include <set>
#include <iostream>
#include <algorithm>
#include "Socket.h"
#include "Sendable.h"

namespace fr
{
    class Http : public Sendable
    {
    public:
        enum class RequestVersion
        {
            V1 = 1,          // HTTP/1.0
            V1_1 = 2,        // HTTP/1.1
            VersionCount = 3
        };
        enum class RequestType
        {
            Get = 0,
            Post = 1,
            Put = 2,
            Delete = 3,
            Patch = 4,
            RequestTypeCount = 5, //Keep me at the end of valid HTTP request types, and updated
            Unknown = 6,
            Partial = 7,
        };
        enum class TransferEncoding
        {
            None = 0,
            Chunked = 1,
            Compress = 2,
            Deflate = 3,
            Gzip = 4,
            Identity = 5,
            EncodingCount = 6,
        };
        enum class RequestStatus
        {
            Continue = 100,
            SwitchingProtocols = 101,
            Ok = 200,
            Created = 201,
            Accepted = 202,
            NonAuthorativeInformation = 203,
            NoContent = 204,
            ResetContent = 205,
            PartialContent = 206,
            IMUsed = 226,
            MultipleChoices = 300,
            MovedPermanently = 301,
            Found = 302,
            SeeOther = 303,
            NotModified = 304,
            UseProxy = 305,
            TemporaryRedirect = 307,
            PermanentRedirect = 308,
            BadRequest = 400,
            Unauthorised = 401,
            PaymentRequired = 402,
            Forbidden = 403,
            NotFound = 404,
            MethodNotAllowed = 405,
            NotAcceptable = 406,
            ProxyAuthenticationRequired = 407,
            RequestTimeout = 408,
            Conflict = 409,
            Gone = 410,
            LengthRequired = 411,
            PreconditionFailed = 412,
            RequestEntityTooLarge = 413,
            RequestURITooLong = 414,
            UnsupportedMediaType = 415,
            RequestedRangeNotSatisfiable = 416,
            ExpectationFailed = 417,
            ImATeapot = 418,
            UnprocessableEntity = 422,
            UpgradeRequired = 426,
            PreconditionRequired = 428,
            TooManyRequests = 429,
            RequestHeaderFieldTooLarge = 431,
            UnvailableForLegalReasons = 451,
            InternalServerError = 500,
            NotImplemented = 501,
            BadGateway = 502,
            ServiceUnavailable = 503,
            GatewayTimeout = 504,
            HTTPVersionNotSupported = 505,
            VariantAlsoNegotiates = 506,
            NotExtended = 510,
            NetworkAuthenticationRequired = 511,
            NetworkReadTimeoutError = 598,
            NetworkConnectTimeoutError = 599,
        };

        Http();
        Http(Http&&)=default;
        Http(const Http&)= default;
        Http &operator=(const Http &)=default;
        Http &operator=(Http &&)=default;


        /*!
         * Parse a raw request or response from a string
         * into the object.
         *
         * @param data The request/response to parse
         * @param datasz The length of data in bytes
         * @return NotEnoughData if parse needs to be called again. Success on success, other on error.
         */
        virtual fr::Socket::Status parse(const char *data, size_t datasz)=0;

        /*!
         * Constructs a HTTP request/response to send.
         *
         * @param host The host that we're connected to.
         * @return The HTTP request
         */
        virtual std::string construct(const std::string &host) const=0;

        /*!
         * Gets the request type (post, get etc)
         *
         * @return The request type
         */
        RequestType get_type() const;

        /*!
         * Sets the request type (post, get etc)
         *
         * @param type The type of request to set it to
         */
        void set_type(RequestType type);

        /*!
         * Sets the request body
         *
         * @param body_ The request body
         */
        void set_body(const std::string &body_);

        /*!
         * Returns a reference to a get variable.
         * Can be used to either set/get the value.
         * If the key does not exist, then it will be
         * created and an empty value will be returned.
         *
         * @param key The name of the GET variable
         * @return A reference to the GET variable
         */
        std::string &get(std::string key);

        /*!
        * Returns a reference to a POST variable.
        * Can be used to either set/get the value.
        * If the key does not exist, then it will be
        * created and an empty value will be returned.
        *
        * @param key The name of the POST variable
        * @return A reference to the POST variable
        */
        std::string &post(std::string key);

        /*!
        * Returns a reference to a header.
        * Can be used to either set/get the value.
        * If the key does not exist, then it will be
        * created and an empty value will be returned.
        *
        * @param key The name of the header
        * @return A reference to the header
        */
        std::string &header(std::string key);


        /*!
         * Checks to see if a given GET variable exists
         *
         * @param key The name of the GET variable
         * @return True if it does. False otherwise.
         */
        bool get_exists(std::string key) const;

        /*!
         * Checks to see if a given POST variable exists
         *
         * @param key The name of the POST variable
         * @return True if it does. False otherwise.
         */
        bool post_exists(std::string key) const;

        /*!
         * Checks to see if a given header exists.
         *
         * @param key The name of the header
         * @return True if it does. False otherwise.
         */
        bool header_exists(std::string key) const;

        /*!
         * Returns the requested URI
         *
         * @return The URI
         */
        const std::string &get_uri() const;

        /*!
         * Sets the response status (400, 200, etc)
         *
         * @param status The status to send back
         */
        void set_status(RequestStatus status);

        /*!
         * Gets the reponse status
         *
         * @return The status
         */
        RequestStatus get_status() const;

        /*!
         * Sets the request URI.
         *
         * @param str What to set the URI to.
         */
        void set_uri(const std::string &str);

        /*!
         * Gets the body of the HTTP request
         *
         * @return The request body
         */
        const std::string &get_body() const;

        /*!
         * Sets the HTTP version
         *
         * @param version The HTTP version to use
         */
        void set_version(RequestVersion version);

        /*!
         * Gets the HTTP version
         *
         * @return The current HTTP version
         */
        RequestVersion get_version() const;

        /*!
         * URL Encodes a given string
         *
         * @param str The string to URL encode
         * @return The URL encoded string
         */
        static std::string url_encode(const std::string &str);

        /*!
         * Decodes a URL encoded string.
         *
         * @param str The string to decode
         * @return The decoded string
         */
        static std::string url_decode(const std::string &str);

        /*!
         * Gets the mimetype of a given filename, or file extention.
         *
         * @param filename The filename, or file extention to find the mime type of
         * @return The mime type. Returns 'application/octet-stream' if it couldn't be found.
         */
        const static std::string &get_mimetype(const std::string &filename);

        /*!
         * Converts a string to a TransferEncoding enum.
         *
         * @note case insentive
         * @param encoding String to convert.
         * @return The encoding type, or TransferEncoding::None if no match was found
         */
        static TransferEncoding string_to_transfer_encoding(std::string encoding)
        {
            static_assert((uint32_t)TransferEncoding::EncodingCount == 6, "Update transfer_encoding_to_string");
            const static std::unordered_map<std::string, TransferEncoding> string_list = {{"none", TransferEncoding::None},
                                                                                          {"chunked", TransferEncoding::Chunked},
                                                                                          {"compress", TransferEncoding::Compress},
                                                                                          {"deflate", TransferEncoding::Deflate},
                                                                                          {"gzip", TransferEncoding::Gzip},
                                                                                          {"identity", TransferEncoding::Identity}};

            std::transform(encoding.begin(), encoding.end(), encoding.begin(), tolower);
            auto iter = string_list.find(encoding);
            return iter == string_list.end() ? TransferEncoding::None : iter->second;
        }

        /*!
         * Converts a 'RequestType' enum value to
         * a printable string.
         *
         * @param type The RequestType to convert
         * @return The printable version of the enum value
         */
        static std::string request_type_to_string(RequestType type)
        {
            static_assert((uint32_t)RequestType::RequestTypeCount == 5, "Update request_type_to_string");
            const static std::string request_type_strings[(uint32_t)RequestType::RequestTypeCount] = {"GET",
                                                                                                      "POST",
                                                                                                      "PUT",
                                                                                                      "DELETE",
                                                                                                      "PATCH"};

            if(type >= RequestType::RequestTypeCount)
                return "UNKNOWN";
            return request_type_strings[(uint32_t)type];
        }

        /*!
         * Converts a string value into a 'RequestType' enum value.
         *
         * @param str The string to convert
         * @return The converted RequestType. Unknown on failure. Or Partial if str is part of a request type.
         */
        static RequestType string_to_request_type(const std::string &str)
        {
            //Find the request type
            static_assert((uint32_t)Http::RequestType::RequestTypeCount == 5, "Update parse_header_type()");

            RequestType type = Http::RequestType::Unknown;
            for(size_t a = 0; a < (uint32_t)Http::RequestType::RequestTypeCount; ++a)
            {
                std::string type_string = request_type_to_string(static_cast<RequestType>(a));
                int cmp_ret = str.compare(0, type_string.size(), type_string);
                if(cmp_ret == 0)
                    return static_cast<RequestType>(a);
                if(str.size() < type_string.size() && cmp_ret < 0)
                    type = Http::RequestType::Partial;
                if(type != Http::RequestType::Partial && str.size() < type_string.size() && cmp_ret > 0)
                    type = Http::RequestType::Unknown;
            }

            return type;
        }

        /*!
         * Overridable send, to allow
         * custom types to be directly sent through
         * sockets.
         *
         * @param socket The socket to send through
         * @return Status indicating if the send succeeded or not.
         */
        Socket::Status send(Socket *socket) const override;

        /*!
         * Overrideable receive, to allow
         * custom types to be directly received through
         * sockets.
         *
         * @param socket The socket to send through
         * @return Status indicating if the send succeeded or not:
         * 'Success': All good, object still valid.
         * 'WouldBlock' or 'Timeout': No data received. Object still valid though.
         * Anything else: Object invalid. Call disconnect().
         */
        Socket::Status receive(Socket *socket) override;

    protected:
        /*!
         * Splits a string by a given token. Allows for escaping.
         *
         * @param token Token to split by
         * @param strip_spacing True to remove ALL spacing from entries. False to leave intact.
         * @return The split string
         */
        static std::vector<std::string> split_string(const std::string &str, char token = '\n', bool strip_spacing = false);

        /*!
         * Converts a parameter list to a vector pair.
         * i.e: ?bob=10&fish=hey
         * to: <bob, 10>, <fish, hey>
         *
         * @param str The string to parse
         * @return The vector containing the results pairs
         */
        static std::vector<std::pair<std::string, std::string>> parse_argument_list(const std::string &str);

        /*!
         * Parses a header line in a HTTP request/response
         *
         * @param str The header. E.g: header: value
         */
        void parse_header_line(const std::string &str);



        //Other request info
        std::unordered_map<std::string, std::string> header_data;
        std::unordered_map<std::string, std::string> post_data;
        std::unordered_map<std::string, std::string> get_data;
        std::set<TransferEncoding> transfer_encodings;
        std::string body;
        RequestType request_type;
        std::string uri;
        RequestStatus status;
        RequestVersion version;

    };
}


#endif //FRNETLIB_HTTP_H


<|start_filename|>tests/HttpRequestTest.cpp<|end_filename|>
#include "gtest/gtest.h"
#include <frnetlib/HttpRequest.h>

TEST(HttpRequestTest, get_request_parse)
{
    //The test request to parse
    const std::string raw_request =
            "GET /index.html?var=bob&other=trob HTTP/1.1\n"
            "Host: frednicolson.co.uk\r\n"
            "Content-Type: application/x-www-form-urlencoded\r\n"
            "My-Header:      header1\n"
            "My-Other-Header:header2\r\n"
            "Cache-Control: no-cache\r\n\r\n";

    //Parse it
    fr::HttpRequest request;
    ASSERT_EQ(request.parse(raw_request.c_str(), raw_request.size()), fr::Socket::Status::Success);

    //Check that the request type is intact
    ASSERT_EQ(request.get_type(), fr::Http::RequestType::Get);

    //Test that URI is intact
    ASSERT_EQ(request.get_uri(), "/index.html");

    //Test that headers exist
    ASSERT_EQ(request.header_exists("Host"), true);
    ASSERT_EQ(request.header_exists("Content-Type"), true);
    ASSERT_EQ(request.header_exists("My-Header"), true);
    ASSERT_EQ(request.header_exists("My-Other-Header"), true);
    ASSERT_EQ(request.header_exists("Cache-Control"), true);
    ASSERT_EQ(request.header_exists("non-existant"), false);

    //Check that headers are intact
    ASSERT_EQ(request.get_version(), fr::Http::RequestVersion::V1_1);
    ASSERT_EQ(request.header("Host"), "frednicolson.co.uk");
    ASSERT_EQ(request.header("Content-Type"), "application/x-www-form-urlencoded");
    ASSERT_EQ(request.header("My-Other-Header"), "header2");
    ASSERT_EQ(request.header("My-Header"), "header1");

    //Test that GET variables exist
    ASSERT_EQ(request.get_exists("var"), true);
    ASSERT_EQ(request.get_exists("other"), true);
    ASSERT_EQ(request.get_exists("fake"), false);

    //Ensure that GET variables are valid
    ASSERT_EQ(request.get("var"), "bob");
    ASSERT_EQ(request.get("other"), "trob");
    ASSERT_EQ(request.get("fake"), "");
}

TEST(HttpRequestTest, post_request_parse)
{
    const std::string raw_request =
            "POST /index.html HTTP/1.1\r\n"
            "\r\n"
            "post_data=data1&some_more_post_data=data23\r\n\r\n";

    //Parse it
    fr::HttpRequest request;
    ASSERT_EQ(request.parse(raw_request.c_str(), raw_request.size()), fr::Socket::Status::Success);

    //Check that the request type is intact
    ASSERT_EQ(request.get_type(), fr::Http::RequestType::Post);

    //Test that URI is intact
    ASSERT_EQ(request.get_version(), fr::Http::RequestVersion::V1_1);
    ASSERT_EQ(request.get_uri(), "/index.html");

    //Parse code is the same for GET, so skip header checks. Test if POST data exists.
    ASSERT_EQ(request.post_exists("post_data"), true);
    ASSERT_EQ(request.post_exists("some_more_post_data"), true);
    ASSERT_EQ(request.post_exists("non_existant"), false);

    //Check that the POST data is valid
    ASSERT_EQ(request.post("post_data"), "data1");
    ASSERT_EQ(request.post("some_more_post_data"), "data23");
}

TEST(HttpRequestTest, request_type_parse)
{
    const std::string get_request = "GET / HTTP/1.1\r\n\r\n";
    const std::string get_request_v2 = "GET / HTTP/1.0\r\n\r\n";
    const std::string post_request = "POST / HTTP/1.1\r\n\r\n";
    const std::string post_request_v2 = "POST / HTTP/1.0\r\n\r\n";
    const std::string put_request = "PUT / HTTP/1.1\r\n\r\n";
    const std::string delete_request = "DELETE / HTTP/1.1\r\n\r\n";
    const std::string patch_request = "PATCH / HTTP/1.1\r\n\r\n";
    const std::string invalid_request = "INVALID / HTTP/1.1\r\n\r\n";
    const std::string invalid_request2 = "PU / HTTP/1.1\r\n\r\n";

    fr::HttpRequest request;
    ASSERT_EQ(request.parse(get_request.c_str(), get_request.size()), fr::Socket::Status::Success);
    ASSERT_EQ(request.get_type(), fr::Http::RequestType::Get);
    ASSERT_EQ(request.get_version(), fr::Http::RequestVersion::V1_1);
    request = {};

    ASSERT_EQ(request.parse(post_request.c_str(), post_request.size()), fr::Socket::Status::Success);
    ASSERT_EQ(request.get_type(), fr::Http::RequestType::Post);
    ASSERT_EQ(request.get_version(), fr::Http::RequestVersion::V1_1);
    request = {};

    ASSERT_EQ(request.parse(get_request_v2.c_str(), get_request_v2.size()), fr::Socket::Status::Success);
    ASSERT_EQ(request.get_type(), fr::Http::RequestType::Get);
    ASSERT_EQ(request.get_version(), fr::Http::RequestVersion::V1);
    request = {};

    ASSERT_EQ(request.parse(post_request_v2.c_str(), post_request_v2.size()), fr::Socket::Status::Success);
    ASSERT_EQ(request.get_type(), fr::Http::RequestType::Post);
    ASSERT_EQ(request.get_version(), fr::Http::RequestVersion::V1);
    request = {};

    ASSERT_EQ(request.parse(put_request.c_str(), put_request.size()), fr::Socket::Status::Success);
    ASSERT_EQ(request.get_type(), fr::Http::RequestType::Put);
    ASSERT_EQ(request.get_version(), fr::Http::RequestVersion::V1_1);
    request = {};

    ASSERT_EQ(request.parse(delete_request.c_str(), delete_request.size()), fr::Socket::Status::Success);
    ASSERT_EQ(request.get_type(), fr::Http::RequestType::Delete);
    ASSERT_EQ(request.get_version(), fr::Http::RequestVersion::V1_1);
    request = {};

    ASSERT_EQ(request.parse(patch_request.c_str(), patch_request.size()), fr::Socket::Status::Success);
    ASSERT_EQ(request.get_type(), fr::Http::RequestType::Patch);
    ASSERT_EQ(request.get_version(), fr::Http::RequestVersion::V1_1);
    request = {};

    ASSERT_EQ(request.parse(invalid_request.c_str(), invalid_request.size()), fr::Socket::Status::ParseError);
    ASSERT_EQ(request.get_type(), fr::Http::RequestType::Unknown);
    ASSERT_EQ(request.get_version(), fr::Http::RequestVersion::V1_1);
    request = {};

    ASSERT_EQ(request.parse(invalid_request2.c_str(), invalid_request2.size()), fr::Socket::Status::ParseError);
    ASSERT_EQ(request.get_type(), fr::Http::RequestType::Unknown);
    ASSERT_EQ(request.get_version(), fr::Http::RequestVersion::V1_1);
    request = {};
}

TEST(HttpRequestTest, get_request_construction)
{
    //Create a request
    fr::HttpRequest request;
    ASSERT_EQ(request.get_uri(), "/");
    ASSERT_EQ(request.get_version(), fr::Http::RequestVersion::V1_1);

    request.header("MyHeader") = "header1";
    request.header("MyOther-Header") = "header2";
    request.get("my_get") = "var1";
    request.get("my_other_get") = "var2";
    request.set_uri("heyo/bobby");
    request.set_type(fr::Http::RequestType::Get);
    std::string constructed_request = request.construct("frednicolson.co.uk");

    //Parse it and check that everything's correct
    request = {};
    request.parse(constructed_request.c_str(), constructed_request.size());
    ASSERT_EQ(request.header("MyHeader"), "header1");
    ASSERT_EQ(request.header("MyOther-Header"), "header2");
    ASSERT_EQ(request.get("my_get"), "var1");
    ASSERT_EQ(request.get("my_other_get"), "var2");
    ASSERT_EQ(request.get_uri(), "/heyo/bobby");
    ASSERT_EQ(request.get_type(), fr::Http::RequestType::Get);
    ASSERT_EQ(request.get_version(), fr::Http::RequestVersion::V1_1);

    //Quick v1 test
    request.set_version(fr::Http::RequestVersion::V1);
    constructed_request = request.construct("frednicolson.co.uk");
    request = {};
    request.parse(constructed_request.c_str(), constructed_request.size());
    ASSERT_EQ(request.get_version(), fr::Http::RequestVersion::V1);
}

TEST(HttpRequestTest, post_request_construction)
{
    //Create a request
    fr::HttpRequest request;
    request.header("MyHeader") = "header1";
    request.header("myotherheader") = "header2";
    request.set_uri("/heyo/bobby");
    request.get("var") = "20";
    request.post("my_post") = "post_data";
    request.post("some_post") = "more_post";
    request.set_type(fr::Http::RequestType::Post);
    const std::string constructed_request = request.construct("frednicolson.co.uk");

    //Parse it
    request = {};
    request.parse(constructed_request.c_str(), constructed_request.size());
    ASSERT_EQ(request.header("myheader"), "header1");
    ASSERT_EQ(request.header("MyOtherHeader"), "header2");
    ASSERT_EQ(request.get_uri(), "/heyo/bobby");
    ASSERT_EQ(request.get("var"), "20");
    ASSERT_EQ(request.post("my_post"), "post_data");
    ASSERT_EQ(request.post("some_post"), "more_post");
    ASSERT_EQ(request.get_type(), fr::Http::RequestType::Post);
}

TEST(HttpRequestTest, partial_parse)
{
    //The test request to parse
    const std::string raw_request1 =
            "GET /index.html?var=bob&other=trob HTTP/1.1\n"
                    "Host: frednicolson.co.uk\r\n"
                    "Content-Type: application/x-www-form-urlencoded\r\n"
                    "My-Header:   ";

    const std::string raw_request2 =
            " header1\n"
            "My-Other-Header:header2\r\n"
            "Cache-Control: no-cache\r\n\r\n";


    //Parse part 1
    fr::HttpRequest request;
    ASSERT_EQ(request.parse(raw_request1.c_str(), raw_request1.size()), fr::Socket::Status::NotEnoughData);

    //Parse part 2
    ASSERT_EQ(request.parse(raw_request2.c_str(), raw_request2.size()), fr::Socket::Status::Success);

    //Verify it
    ASSERT_EQ(request.get_type(), fr::Http::RequestType::Get);
    ASSERT_EQ(request.header("content-type"), "application/x-www-form-urlencoded");
    ASSERT_EQ(request.header("Cache-Control"), "no-cache");
}

TEST(HttpRequestTest, awkward_parse)
{
    std::string request_data = ""
            "POST /my/url? HTTP/1.1\n"
            "\n"
            "Test=bob\n"
            "\n";
    fr::HttpRequest request;
    ASSERT_EQ(request.parse(request_data.c_str(), request_data.size()), fr::Socket::Status::Success);
    ASSERT_EQ(request.get_uri(), "/my/url");
    ASSERT_EQ(request.post("Test"), "bob");
}

TEST(HttpRequestTest, awkward_parse2)
{
    std::string request_data = "POST /my/url?Bob=10 HTTP/1.1\n"
            "\n"
            "Test=bob";

    fr::HttpRequest request;
    ASSERT_EQ(request.parse(request_data.c_str(), request_data.size()), fr::Socket::Status::Success);
    ASSERT_EQ(request.get_uri(), "/my/url");
    ASSERT_EQ(request.get("Bob"), "10");
    ASSERT_EQ(request.post("Test"), "bob");
}

<|start_filename|>src/HttpResponse.cpp<|end_filename|>
//
// Created by fred on 10/12/16.
//

#include <iostream>
#include "frnetlib/HttpResponse.h"

namespace fr
{
    fr::Socket::Status HttpResponse::parse(const char *response_data, size_t datasz)
    {
        body.append(response_data, datasz);

        //Ensure that the whole header has been parsed first
        if(!header_ended)
        {
            //Verify that it's a valid HTTP response if there's enough data
            if(body.size() >= 4 && body.compare(0, 4, "HTTP") != 0)
                return fr::Socket::Status::ParseError;

            //Check to see if this request data contains the end of the header
            uint16_t header_end_size = 4;
            auto header_end = body.find("\r\n\r\n");
            if(header_end == std::string::npos)
            {
                header_end = body.find("\n\n");
                header_end_size = 2;
            }
            header_ended = header_end != std::string::npos;

            //Ensure that the header doesn't exceed max length
            if((!header_ended && body.size() > MAX_HTTP_HEADER_SIZE) || (header_ended && header_end > MAX_HTTP_HEADER_SIZE))
            {
                return fr::Socket::Status::HttpHeaderTooBig;
            }

            //If the header end has not been found, ask for more data.
            if(!header_ended)
                return fr::Socket::Status::NotEnoughData;

            //Else parse it
            if(!parse_header(header_end))
                return fr::Socket::Status::ParseError;

            //Leave things after the header intact
            body.erase(0, header_end + header_end_size);
        }

        //Ensure that body doesn't exceed maximum length
        if(body.size() > MAX_HTTP_BODY_SIZE)
            return fr::Socket::Status::HttpBodyTooBig;

        //Check if chunked encoding
        if(transfer_encodings.find(TransferEncoding::Chunked) != transfer_encodings.end())
        {
            //It's chunked, find the length of the chunk in hex!
            unsigned int chunk_len = 1;
            while(chunk_len > 0 && chunk_offset < body.size())
            {
                auto length_end = body.find("\r\n", chunk_offset);
                if(length_end == std::string::npos)
                    return fr::Socket::Status::NotEnoughData;
                std::string hex_length = body.substr(chunk_offset, length_end - chunk_offset);
                chunk_len = std::stoul(hex_length, nullptr, 16);

                //If the current chunk hasn't fully been received yet then ask for more data
                if(body.size() - length_end < chunk_len + 4) //+4 because of the \r\n after the hex value and the chunk end
                    return fr::Socket::Status::NotEnoughData;

                //Now we have the full chunk, erase the padding \r\n
                body.erase(chunk_offset + hex_length.size() + chunk_len + 2, 2); //delete \r\n after chunk
                body.erase(chunk_offset, hex_length.size() + 2); //delete both hex length and following \r\n
                chunk_offset += chunk_len;
            }

            //If this chunk is 0, we have everything!
            if(chunk_len == 0)
                return fr::Socket::Status::Success;
            return fr::Socket::Status::NotEnoughData;
        }

        //Cut off any data if it exceeds content length, provided that a content length is specified
        if(content_length > 0 && body.size() > content_length)
            body.resize(content_length);
        else if(body.size() < content_length)
            return fr::Socket::Status::NotEnoughData;
        return fr::Socket::Status::Success;

    }

    std::string HttpResponse::construct(const std::string &host) const
    {
        //Add HTTP header

        static_assert((uint32_t)RequestVersion::VersionCount == 3, "Update me");
        std::string response = ((version == RequestVersion::V1) ? "HTTP/1.0 " : "HTTP/1.1 ") + std::to_string((uint32_t)status) + " \r\n";

        //Add the headers to the response
        for(const auto &header : header_data)
        {
            std::string data = header.first + ": " + header.second + "\r\n";
            response += data;
        }

        //Add in required headers if they're missing
        if(header_data.find("connection") == header_data.end())
            response += "connection: keep-alive\r\n";
        if(header_data.find("content-type") == header_data.end())
            response += "content-type: text/html\r\n";
        if(header_data.find("content-length") == header_data.end() && !body.empty())
            response += "content-length: " + std::to_string(body.size()) + "\r\n";

        //Add in space
        response += "\r\n";

        //Add in the body
        response += body;
        return response;
    }

    bool HttpResponse::parse_header(size_t header_end_pos)
    {
        try
        {
            //Split the header into lines
            size_t line = 0;
            std::vector<std::string> header_lines = split_string(body.substr(0, header_end_pos));
            if(header_lines.empty())
                return false;

            //Get response code
            auto status_begin = header_lines[0].find(' ');
            if(status_begin == std::string::npos)
                return false;
            auto end_pos = header_lines[0].find(' ', status_begin + 1);
            status = (RequestStatus)std::stoi(header_lines[0].substr(status_begin, end_pos - status_begin));

            //Get HTTP version
            static_assert((uint32_t)RequestVersion::VersionCount == 3, "Update me");
            version = header_lines[0].compare(0, status_begin, "HTTP/1.0") == 0 ? RequestVersion::V1 : RequestVersion::V1_1;
            line++;

            //Read in headers
            for(; line < header_lines.size(); line++)
            {
                parse_header_line(header_lines[line]);
            }

            //Store content length value if it exists
            auto length_header_iter = header_data.find("content-length");
            if(length_header_iter != header_data.end())
                content_length = std::stoull(length_header_iter->second);
        }
        catch(const std::exception &e)
        {
            return false;
        }
        return true;
    }
}

<|start_filename|>tests/TcpListenerTest.cpp<|end_filename|>
//
// Created by fred on 05/06/17.
//

#include <gtest/gtest.h>
#include <frnetlib/TcpListener.h>
#include <thread>

TEST(TcpListenerTest, listner_listen)
{
    fr::TcpListener listener;
    ASSERT_EQ(listener.get_socket_descriptor(), -1);
    fr::Socket::Status ret = listener.listen("9090");
    ASSERT_EQ(ret, fr::Socket::Status::Success);
    listener.close_socket();
    ASSERT_EQ(listener.get_socket_descriptor(), -1);
}


TEST(TcpListenerTest, listener_accept)
{
    fr::TcpListener listener;
    listener.set_inet_version(fr::Socket::IP::v4);
    if(listener.listen("9095") != fr::Socket::Status::Success)
        FAIL();

    auto client_thread = []()
    {
        fr::TcpSocket socket;
        socket.set_inet_version(fr::Socket::IP::v4);
        auto ret = socket.connect("127.0.0.1", "9095", std::chrono::seconds(5));
        ASSERT_EQ(ret, fr::Socket::Status::Success);
    };

    std::thread t1(client_thread);
    fr::TcpSocket socket;
    auto ret = listener.accept(socket);
    ASSERT_EQ(ret, fr::Socket::Status::Success);
    t1.join();
}

TEST(TcpListenerTest, set_descriptor)
{
    fr::TcpListener listener;
    listener.set_socket_descriptor(-20);
    ASSERT_EQ(listener.get_socket_descriptor(), -20);
}


<|start_filename|>tests/URLTest.cpp<|end_filename|>
//
// Created by fred.nicolson on 30/05/17.
//

#include <gtest/gtest.h>
#include <frnetlib/URL.h>

TEST(URLTest, full_parse)
{
    fr::URL url("http://example.com:80/path/path?query=10&bob=20#frag");
    ASSERT_EQ(url.get_host(), "example.com");
    ASSERT_EQ(url.get_scheme(), fr::URL::HTTP);
    ASSERT_EQ(url.get_path(), "/path/path");
    ASSERT_EQ(url.get_query(), "query=10&bob=20");
    ASSERT_EQ(url.get_fragment(), "frag");
}

TEST(URLTest, port_guess)
{
    fr::URL url("https://example.com");
    ASSERT_EQ(url.get_port(), "443");
}

TEST(URLTest, partial_parse)
{
    fr::URL url("example.com/?query=10#frag");
    ASSERT_EQ(url.get_host(), "example.com");
    ASSERT_EQ(url.get_query(), "query=10");
    ASSERT_EQ(url.get_fragment(), "frag");
}

TEST(URLTest, fragment_test)
{
    fr::URL url("example.com/#frag");
    ASSERT_EQ(url.get_fragment(), "frag");
}

TEST(URLTest, path_test)
{
    fr::URL url("example.com/path/hey#frag");
    ASSERT_EQ(url.get_path(), "/path/hey");
    ASSERT_EQ(url.get_fragment(), "frag");
}

TEST(URLTest, uri_test)
{
    fr::URL url("http://example.com:80/path/path?query=10#frag");
    ASSERT_EQ(url.get_uri(), "/path/path?query=10#frag");
}

TEST(URLTest, uri_test2)
{
    fr::URL url("http://example.com:80/path/path?query=10");
    ASSERT_EQ(url.get_uri(), "/path/path?query=10");
}

TEST(URLTest, uri_test3)
{
    fr::URL url("http://example.com:80/path/path#frag");
    ASSERT_EQ(url.get_uri(), "/path/path#frag");
}

TEST(URLTest, uri_test4)
{
    fr::URL url("http://example.com:80/?bob=10#frag");
    ASSERT_EQ(url.get_uri(), "/?bob=10#frag");
}

TEST(URLTest, schema_parse_test)
{
    fr::URL url("127.0.0.1:2020");
    fr::URL url2;
    url2.parse("127.0.0.1:2020");
    ASSERT_TRUE(url == url2);
}

TEST(URLTest, get_url_test)
{
    ASSERT_EQ(fr::URL("127.0.0.1:2020").get_url(), "127.0.0.1:2020");
    ASSERT_EQ(fr::URL("https://127.0.0.1:2020").get_url(), "https://127.0.0.1:2020");
    ASSERT_EQ(fr::URL("https://127.0.0.1").get_url(), "https://127.0.0.1:443");
    ASSERT_EQ(fr::URL("127.0.0.1/hello.php?x=10").get_url(), "127.0.0.1/hello.php?x=10");
    ASSERT_EQ(fr::URL("/hello.php").get_url(), "/hello.php");
}

TEST(URLTest, test_modify_url)
{
    fr::URL url("https://example.com:2020");
    url.set_host("example.co.uk");
    ASSERT_EQ(url.get_url(), "https://example.co.uk:2020");
}

TEST(URLTest, test_path_only_parse)
{
    {
        fr::URL url("/bob");
        ASSERT_TRUE(url.get_host().empty());
        ASSERT_EQ(url.get_path(), "/bob");
    }
    {
        fr::URL url("bob");
        ASSERT_TRUE(url.get_host().empty());
        ASSERT_EQ(url.get_path(), "/bob");
    }
    {
        fr::URL url("http://bob");
        ASSERT_EQ(url.get_host(), "bob");
        ASSERT_TRUE(url.get_path().empty());
    }
    {
        fr::URL url("bob.com/trob");
        ASSERT_EQ(url.get_host(), "bob.com");
        ASSERT_EQ(url.get_path(), "/trob");
    }
}

<|start_filename|>include/frnetlib/SSLListener.h<|end_filename|>
//
// Created by fred on 13/12/16.
//

#ifndef FRNETLIB_SSLLISTENER_H
#define FRNETLIB_SSLLISTENER_H

#include <mbedtls/net_sockets.h>
#include <mbedtls/debug.h>
#include <mbedtls/ssl.h>
#include <mbedtls/entropy.h>
#include <mbedtls/ctr_drbg.h>
#include <mbedtls/error.h>
#include <mbedtls/certs.h>

#include "SSLSocket.h"
#include "Listener.h"


namespace fr
{
    class SSLListener : public Listener
    {
    public:
        explicit SSLListener(std::shared_ptr<SSLContext> ssl_context, const std::string &pem_path, const std::string &private_key_path);
        ~SSLListener() override;
        SSLListener(SSLListener &&) = delete;
        SSLListener(SSLListener &o) = delete;
        void operator=(const SSLListener &) = delete;
        void operator=(SSLListener &&) = delete;

        /*!
         * Listens to the given port for connections
         *
         * @param port The port to bind to
         * @return If the operation was successful
         */
        Socket::Status listen(const std::string &port) override;

        /*!
         * Accepts a new connection.
         *
         * @param client Where to store the connection information
         * @return True on success. False on failure.
         */
        Socket::Status accept(Socket &client) override;

        /*!
         * Closes the socket
         */
        void close_socket() override;

        /*!
         * Calls the shutdown syscall on the socket.
         * So you can receive data but not send.
         *
         * This can be called on a blocking socket to force
         * it to immediately return (you might want to do this if
         * you're exiting and need the blocking socket to return).
         */
        void shutdown() override;

        /*!
         * Sets the socket descriptor.
         *
         * @param descriptor The listen descriptor to use
         */
        void set_socket_descriptor(int32_t descriptor) override;

        /*!
         * Checks to see if the socket is connected or not
         *
         * @return True if connected, false otherwise
         */
        bool connected() const override;

        /*!
         * Gets the underlying socket descriptor.
         *
         * @return The socket descriptor.
         */
        int32_t get_socket_descriptor() const override;

    private:
        mbedtls_net_context listen_fd;
        mbedtls_ssl_config conf;
        mbedtls_x509_crt srvcert;
        mbedtls_pk_context pkey;

        std::shared_ptr<SSLContext> ssl_context;
    };

}
#endif //FRNETLIB_SSLLISTENER_H


<|start_filename|>tests/HttpTest.cpp<|end_filename|>
//
// Created by fred.nicolson on 01/03/18.
//

#include <gtest/gtest.h>
#include <frnetlib/HttpResponse.h>

TEST(HttpTest, test_request_type_to_string)
{
    for(size_t a = 0; a < (uint32_t)fr::Http::RequestType::RequestTypeCount; ++a)
    {
        ASSERT_EQ((size_t)fr::Http::string_to_request_type(fr::Http::request_type_to_string((fr::Http::RequestType)a)), a);
    }

    ASSERT_EQ(fr::Http::request_type_to_string(fr::Http::RequestType::Partial), "UNKNOWN");
    ASSERT_EQ(fr::Http::request_type_to_string(fr::Http::RequestType::RequestTypeCount), "UNKNOWN");
    ASSERT_EQ(fr::Http::request_type_to_string(fr::Http::RequestType::Unknown), "UNKNOWN");
}

TEST(HttpTest, test_string_to_request_type)
{
    std::vector<std::pair<fr::Http::RequestType, std::string>> strings = {
            {fr::Http::RequestType::Get, "GET"},
            {fr::Http::RequestType::Put, "PUT"},
            {fr::Http::RequestType::Delete, "DELETE"},
            {fr::Http::RequestType::Patch, "PATCH"},
            {fr::Http::RequestType::Patch, "PATCHid-=wa"},
            {fr::Http::RequestType::Partial, "PA"},
            {fr::Http::RequestType::Partial, "PU"},
            {fr::Http::RequestType::Partial, "DELET"},
            {fr::Http::RequestType::Unknown, "DELETa"},
            {fr::Http::RequestType::Unknown, "U"},
            {fr::Http::RequestType::Unknown, "dwaouidhwi"},
            {fr::Http::RequestType::Unknown, "get"},
    };

    for(auto &str : strings)
    {
        ASSERT_EQ(fr::Http::string_to_request_type(str.second), str.first);
    }
}

TEST(HttpTest, test_url_encode)
{
    std::string source = "1\"!£FEW$\"931-90%%+-&*0(du%a90dj09=_da.A~";
    ASSERT_EQ(fr::Http::url_encode(source), "1%22!%C2%A3FEW%24%22931-90%25%25%2B-%26*0(du%25a90dj09%3D_da.A~");
}

TEST(HttpTest, test_url_decode)
{
    std::string source = "1%22!%C2%A3FEW%24%22931-90%25%25%2B-%26*0(du%25a90dj09%3D_da.A~";
    ASSERT_EQ(fr::Http::url_decode(source), "1\"!£FEW$\"931-90%%+-&*0(du%a90dj09=_da.A~");
}

TEST(HttpTest, test_get_mimetype)
{
    ASSERT_EQ(fr::Http::get_mimetype(".html"), "text/html");
    ASSERT_EQ(fr::Http::get_mimetype("my_file.html"), "text/html");
    ASSERT_EQ(fr::Http::get_mimetype("file.some_random_type"), "application/octet-stream");
}

TEST(HttpTest, test_string_to_transfer_encoding)
{
    ASSERT_EQ(fr::Http::string_to_transfer_encoding("chunked"), fr::Http::TransferEncoding::Chunked);
    ASSERT_EQ(fr::Http::string_to_transfer_encoding("unknown"), fr::Http::TransferEncoding::None);
    ASSERT_EQ(fr::Http::string_to_transfer_encoding("ioudhweauidhgiwuyahfiuywhafyuhgwayufhg"), fr::Http::TransferEncoding::None);
    ASSERT_EQ(fr::Http::string_to_transfer_encoding("IDenTITy"), fr::Http::TransferEncoding::Identity);
}

<|start_filename|>examples/concurrent_http_server/ConcurrentHTTPServer.cpp<|end_filename|>
//
// Created by fred.nicolson on 01/10/18.
//

#include <frnetlib/TcpListener.h>
#include <iostream>
#include <frnetlib/SocketSelector.h>
#include <frnetlib/Http.h>
#include <frnetlib/HttpRequest.h>
#include <frnetlib/HttpResponse.h>
#include <thread>

class SessionState
{
public:
    fr::HttpRequest partial_request;
};

void process_complete_request(const std::shared_ptr<fr::Socket> &client, fr::HttpRequest request)
{
    //Note: *NEVER* disconnect the client in the handler. Or it will never be removed from
    //the socket selector, and its opaque data will never be free'd. You're better off having a
    //disconnection queue which is processed by the listening thread, and added to here.
    fr::HttpResponse response;
    response.set_body("<h1>Hello World!</h1>");
    client->send(response);
}

int main()
{
    //Bind to port. Note that it is possible to fork/create new threads and then bind to the same
    //port multiple times. Each thread should have its own fr::SocketSelector, this will
    //spread connections over multiple workers.
    auto listener = std::make_shared<fr::TcpListener>();
    if(listener->listen("8080") != fr::Socket::Status::Success)
    {
        std::cerr << "Failed to bind to port" << std::endl;
        return EXIT_FAILURE;
    }

    //Create a socket selector, and add the listener so we get notified on connection requests
    fr::SocketSelector listen_loop_selector;
    listen_loop_selector.add(listener, nullptr);

    bool running = true;
    while(running)
    {
        //Pass a timeout here, so that we can periodically check 'running' to see if we should exit
        auto ready_sockets = listen_loop_selector.wait(std::chrono::milliseconds(100));
        for(auto &ready_socket : ready_sockets)
        {
            //If it's the listener, accept a new connection
            if(ready_socket.first->get_socket_descriptor() == listener->get_socket_descriptor())
            {
                auto client = std::make_shared<fr::TcpSocket>(); //Or fr::SSLSocket
                if(listener->accept(*client) == fr::Socket::Status::Success)
                {
                    client->set_blocking(false); //This is important
                    listen_loop_selector.add(client, new SessionState()); //We assign a 'SessionState' object for each connection as opaque data
                }
                continue;
            }

            //Else, we have new activity on a client socket
            auto client = std::static_pointer_cast<fr::Socket>(ready_socket.first);
            auto session = static_cast<SessionState*>(ready_socket.second);

            //Try and receive data
            char data[0x1000];
            size_t received = 0;
            auto recv_status = client->receive_raw(data, sizeof(data), received);
            if(recv_status == fr::Socket::Status::Success)
            {
                //We received data, so parse it using the partial HTTP request associated with this connection
                auto parse_status = session->partial_request.parse(data, received);
                if(parse_status == fr::Socket::Status::Success)
                {
                    //The client has sent a full request, queue it for processing
                    process_complete_request(client, std::move(session->partial_request));
                    session->partial_request = fr::HttpRequest();
                }
                else if(parse_status != fr::Socket::Status::NotEnoughData)
                {
                    //HTTP error, disconnect the client. Remove from socket selector, and delete opaque data.
                    delete (SessionState*)listen_loop_selector.remove(client);
                }
            }
            else if(recv_status != fr::Socket::Status::WouldBlock)
            {
                //Error, disconnect it. Remove from socket selector, and delete opaque data.
                delete (SessionState*)listen_loop_selector.remove(client);
            }
        }
    }
}

<|start_filename|>tests/PacketableTest.cpp<|end_filename|>
#include <algorithm>
#include "gtest/gtest.h"
#include <frnetlib/Packetable.h>
#include <frnetlib/Packet.h>

class CustomClass : public fr::Packetable
{
public:
    CustomClass()
    {
        member1 = "hello";
        member2 = 7;
    }

    bool operator==(const CustomClass &o) const
    {
        return o.member1 == member1 && o.member2 == member2;
    }

    virtual void pack(fr::Packet &destination) const
    {
        destination << member1 << member2;
    }

    virtual void unpack(fr::Packet &source)
    {
        source >> member1 >> member2;
    }

private:
    std::string member1;
    int member2;
};

TEST(PacketableTest, pack_and_unpack)
{
    fr::Packet packet;
    CustomClass custom;
    packet << custom;

    CustomClass custom2;
    packet >> custom2;

    ASSERT_EQ(custom, custom2);
}


<|start_filename|>include/frnetlib/TcpSocket.h<|end_filename|>
//
// Created by fred on 06/12/16.
//

#ifndef FRNETLIB_TCPSOCKET_H
#define FRNETLIB_TCPSOCKET_H

#include <memory>
#include <mutex>
#include "Socket.h"

namespace fr
{
    class TcpSocket : public Socket
    {
    public:
        TcpSocket() noexcept;
        ~TcpSocket() override;
        TcpSocket(TcpSocket &&) = delete;
        TcpSocket(const TcpSocket &) = delete;
        void operator=(TcpSocket &&)=delete;
        void operator=(const TcpSocket &)=delete;

        /*!
         * Connects the socket to an address.
         *
         * @param address The address of the socket to connect to
         * @param port The port of the socket to connect to
         * @param timeout The number of seconds to wait before timing the connection attempt out. Pass {} for default.
         * @return A Socket::Status indicating the status of the operation. (Success on success, an error type on failure).
         */
        Socket::Status connect(const std::string &address, const std::string &port, std::chrono::seconds timeout) override;

        /*!
         * Attempts to send raw data down the socket, without
         * any of frnetlib's framing. Useful for communicating through
         * different protocols.
         *
         * @param data The data to send.
         * @param size The number of bytes, from data to send. Be careful not to overflow. You must zero this, prior to calling send_raw the first time.
         * @param sent Will be filled with the number of bytes actually sent, might be less than you requested if in non-blocking mode/error.
         * @return The status of the operation:
         * 'WouldBlock' if no data has been received, and the socket is in non-blocking mode or operation has timed out
         * 'SendError' if a send error has disconnected.
         * 'Success' All the bytes you wanted have been read
         */
        Status send_raw(const char *data, size_t size, size_t &sent) override;


        /*!
         * Receives raw data from the socket, without any of
         * frnetlib's framing. Useful for communicating through
         * different protocols. This will attempt to read 'data_size'
         * bytes, but might not succeed. It'll return how many bytes were actually
         * read in 'received'.
         *
         * @param data Where to store the received data.
         * @param buffer_size The number of bytes to try and receive. Be sure that it's not larger than data.
         * @param received Will be filled with the number of bytes actually received, might be less than you requested if in non-blocking mode/error.
         * @return The status of the operation:
         * 'WouldBlock' if no data has been received, and the socket is in non-blocking mode or operation has timed out
         * 'Timeout' Socket is in blocking mode and it timed out.
         * 'Disconnected' if the socket has disconnected. (you must now call disconnect())
         * 'ReceiveError' A receive error occurred.
         * 'Success' All the bytes you wanted have been read
         */
        Status receive_raw(void *data, size_t buffer_size, size_t &received) override;

        /*!
         * Sets if the socket should be blocking or non-blocking.
         *
         * @note This must be set *WHILST* connected
         * @param should_block True to block, false otherwise.
         * @return State of the operation:
         * 'Success' on success.
         * 'Error' on failure.
         */
        Status set_blocking(bool should_block) override;

        /*!
         * Checks if the socket is blocking
         *
         * @return True if it is, false otherwise
         */
        bool get_blocking() const override;

        /*!
         * Sets the socket file descriptor. Internally used.
         *
         * @note For TcpSocket, this should be a pointer to a int32_t. A copy is made.
         * @param descriptor_data The socket descriptor data, set up by the Listener.
         */
        void set_descriptor(void *descriptor_data) override;

        /*!
         * Applies requested socket options to the socket.
         * Should be called when a new socket is created.
         */
        void reconfigure_socket() override;

        /*!
         * Checks to see if the socket is connected or not
         *
         * @return True if connected, false otherwise
         */
        bool connected() const override;

        /*!
         * Gets the underlying socket descriptor.
         *
         * @return The socket descriptor.
         */
        int32_t get_socket_descriptor() const noexcept override;

    protected:

        /*!
         * Close the connection.
         */
        void close_socket() override;

        int32_t socket_descriptor;
        bool is_blocking;
    };

}


#endif //FRNETLIB_TCPSOCKET_H

<|start_filename|>include/frnetlib/URL.h<|end_filename|>
//
// Created by fred.nicolson on 23/05/17.
//

#ifndef FRNETLIB_URLPARSER_H
#define FRNETLIB_URLPARSER_H
#include <string>
#include <unordered_map>
#include <algorithm>

//Note, a URL looks like this:  scheme:[//host[:port]][/path][?query][#fragment]
namespace fr
{
    class URL
    {
    public:
        enum Scheme
        {
            HTTP = 0,
            HTTPS = 1,
            FTP = 2,
            MAILTO = 3,
            IRC = 4,
            SFTP = 5,
            Unknown = 6,
        };

        /*!
         * Constructors
         */
        URL() = default;

        explicit URL(const std::string &url);

        /*!
         * Parses a given URL, extracting its various components. This will always
         * try to make something out of the provided URL. It will also try its best
         * to guess any potentially missing information. For example, if the URL is
         * https://example.com, then the 'port' will be guessed as 443.
         *
         * @param url The URL to parse
         */
        void parse(const std::string &url);

        /*!
         * Get the URL scheme
         */
        inline Scheme get_scheme() const
        {
            return scheme;
        }

        /*
         * Set the URL host
         */
        inline void set_scheme(Scheme scheme_)
        {
            scheme = scheme_;
        }

        /*
         * Get the URL host
         */
        inline const std::string &get_host() const
        {
            return host;
        }

        /*
         * Set the URL host
         */
        inline void set_host(std::string host_)
        {
            host = std::move(host_);
        }

        /*
         * Get the URL port
         */
        inline const std::string &get_port() const
        {
            return port;
        }

        /*!
         * Set the port
         */
        inline void set_port(std::string port_)
        {
            port = std::move(port_);
        }

        /*
         * Get the URL path
         */
        inline const std::string &get_path() const
        {
            return path;
        }

        /*!
         * Set the path
         */
        inline void set_path(std::string path_)
        {
            path = std::move(path_);
        }


        /*
         * Get the URL query
         */
        inline const std::string &get_query() const
        {
            return query;
        }

        /*!
         * Set the query
         */
        inline void set_query(std::string query_)
        {
            query = std::move(query_);
        }

        /*
         * Get the URL fragment
         */
        inline const std::string &get_fragment() const
        {
            return fragment;
        }

        /*!
         * Set the fragment
         */
        inline void set_fragment(std::string fragment_)
        {
            fragment = std::move(fragment_);
        }

        /*!
         * Returns the combination of other URL elements into a single URI string.
         *
         * The URL is everything after the IP/Address & Port.
         *
         * @return The URL's URI
         */
        inline std::string get_uri() const
        {
            std::string result;
            result += get_path();
            if(!get_query().empty())
                result += "?" + get_query();
            if(!get_fragment().empty())
                result += "#" + get_fragment();
            return result;
        }

        /*!
         * Gets the *whole* URL, including every single element
         *
         * @return Whole URL
         */
        inline std::string get_url() const
        {
            std::string ret;
            if(!get_host().empty())
            {
                if(scheme != Scheme::Unknown)
                    ret.append(scheme_to_string(scheme)).append("://");
                ret.append(get_host());
                if(!get_port().empty())
                    ret.append(":").append(port);
            }
            ret.append(get_uri());
            return ret;
        }

        /*!
         * Converts a string to a scheme enum.
         *
         * @param scheme The string scheme to convert
         * @return The associated scheme enum value. Scheme::Unknown on failure.
         */
        static Scheme string_to_scheme(const std::string &scheme);

        /*!
         * Converts a scheme enum value to a string
         *
         * @param scheme The scheme value to convert
         * @return The string version
         */
        static const std::string &scheme_to_string(Scheme scheme);

        /*!
         * Comparison operator
         *
         * @param o Object to compare against
         * @return True if equal, false otherwise
         */
        inline bool operator==(const URL &o) const
        {
            return scheme == o.scheme && host == o.host && port == o.port && path == o.path && query == o.query && fragment == o.fragment;
        }
    private:
        /*!
         * Converts a string to lower case
         *
         * @param str The string to convert
         * @return The converted string
         */
        static std::string to_lower(const std::string &str)
        {
            std::string out = str;
            std::transform(out.begin(), out.end(), out.begin(), ::tolower);
            return out;
        }

        //State
        Scheme scheme;
        std::string host;
        std::string port;
        std::string path;
        std::string query;
        std::string fragment;
        static std::unordered_map<std::string, URL::Scheme> scheme_string_map;
    };
}

#endif //FRNETLIB_URLPARSER_H


<|start_filename|>examples/simple_http_server_and_client/SimpleHttpServer.cpp<|end_filename|>
//
// Created by fred.nicolson on 01/02/18.
//

#include <frnetlib/HttpRequest.h>
#include <frnetlib/HttpResponse.h>
#include <frnetlib/TcpListener.h>
#include <iostream>

int main()
{
    fr::Socket::Status  err;
    fr::TcpSocket client;
    fr::TcpListener listener;

    //Bind to a port
    if((err = listener.listen("8081")) != fr::Socket::Status::Success)
    {
        std::cerr << "Failed to bind to port: " << fr::Socket::status_to_string(err) << std::endl;
        return EXIT_FAILURE;
    }

    while(true)
    {
        //Accept a new connection
        if((err = listener.accept(client)) != fr::Socket::Status::Success)
        {
            std::cerr << "Failed to accept new connection: " << fr::Socket::status_to_string(err) << std::endl;
            continue;
        }

        //Receive client HTTP request
        fr::HttpRequest request;
        if((err = client.receive(request)) != fr::Socket::Status::Success)
        {
            std::cerr << "Failed to receive request from client: " << fr::Socket::status_to_string(err) << std::endl;
        }

        //Construct a response
        fr::HttpResponse response;
        response.set_body("<h1>Hello, World!</h1>");

        //Send it
        if((err = client.send(response)) != fr::Socket::Status::Success)
        {
            std::cerr << "Failed to send response to client: " << fr::Socket::status_to_string(err) << std::endl;
        }

        //Close connection
        client.disconnect();
    }
}

<|start_filename|>include/frnetlib/HttpRequest.h<|end_filename|>
//
// Created by fred on 10/12/16.
//

#ifndef FRNETLIB_HTTPREQUEST_H
#define FRNETLIB_HTTPREQUEST_H
#include <string>
#include <vector>
#include <unordered_map>
#include "TcpSocket.h"
#include "Http.h"

namespace fr
{
    class HttpRequest : public Http
    {
    public:
        //Constructors
        HttpRequest();
        HttpRequest(HttpRequest&&)=default;
        HttpRequest(const HttpRequest&)= default;
        HttpRequest &operator=(const HttpRequest &)=default;
        HttpRequest &operator=(HttpRequest &&)=default;

        /*!
         * Parse a HTTP response.
         *
         * @param data The HTTP response to parse
         * @param datasz The length of data in bytes
         * @return Status of the parse:
         * 'NotEnoughData' if parse needs to be re-called with more data.
         * 'Success' if the whole request has been parsed
         * Anything else - on error
         */
        fr::Socket::Status parse(const char *data, size_t datasz) override;

        /*!
         * Constructs a Http Request, ready to send.
         *
         * @return The constructed HTTP request.
         */
        std::string construct(const std::string &host) const override;

    private:
        /*!
         * Parses the request header.
         *
         * @param header_end_pos The position in 'body' of the end of the header
         * @return True on success, false on failure
         */
        bool parse_header(int64_t header_end_pos);

        /*!
         * Parses the POST data from the body
         */
        void parse_post_body();

        /*!
         * Parses the header type (GET/POST) from the given string.
         *
         * @param str The first header line
         * @return The parsed request type
         */
        Http::RequestType parse_header_type(const std::string &str);

        /*!
         * Parses the header URI
         *
         * @param str The first header line
         */
        void parse_header_uri(const std::string &str);

        //State
        bool header_ended;
        size_t content_length;

    };
}


#endif //FRNETLIB_HTTPREQUEST_H


<|start_filename|>tests/PacketTest.cpp<|end_filename|>
#include <stdint.h>
#include <limits>
#include <gtest/gtest.h>
#include <frnetlib/Packet.h>

TEST(PacketTest, range_add)
{
    std::vector<int> var{1, 2, 3, 4, 5};
    fr::Packet packet;
    packet.add_range(var.begin(), var.end());

    std::vector<int> out;
    packet >> out;
    ASSERT_EQ(var, out);
}

TEST(PacketTest, double_range_add)
{
    std::vector<int> var1{1, 2, 3, 4, 5};
    std::vector<int> var2{6, 7, 8, 9, 10};
    fr::Packet packet;
    packet.add_range(var1.begin(), var1.end());
    packet.add_range(var2.begin(), var2.end());

    std::vector<int> out1;
    std::vector<int> out2;
    packet >> out1 >> out2;
    ASSERT_EQ(var1, out1);
    ASSERT_EQ(var2, out2);
}

TEST(PacketTest, pack_vector_bool)
{
    std::vector<bool> my_vec{true, false, true};
    std::vector<bool> my_vec2;
    fr::Packet packet;
    packet << my_vec;
    packet >> my_vec2;
    ASSERT_EQ(my_vec, my_vec2);
}

TEST(PacketTest, pack_and_unpack_ints)
{
    fr::Packet packet;
    double a1 = 11.5f, a2 = 0.f;
    float b1 = 11.52, b2 = 0.0;
    uint8_t c1 = std::numeric_limits<uint8_t>::max() - 50, c2 = 0;
    uint16_t d1 = std::numeric_limits<uint16_t>::max() - 50, d2 = 0;
    uint32_t e1 = std::numeric_limits<uint32_t>::max() - 50, e2 = 0;
    uint64_t f1 = std::numeric_limits<uint64_t>::max() - 50, f2 = 0;

    packet << a1 << b1 << c1 << d1 << e1 << f1;
    packet >> a2 >> b2 >> c2 >> d2 >> e2 >> f2;

    ASSERT_EQ(a1, a2);
    ASSERT_EQ(b1, b2);
    ASSERT_EQ(c1, c2);
    ASSERT_EQ(d1, d2);
    ASSERT_EQ(e1, e2);
    ASSERT_EQ(f1, f2);

}

TEST(PacketTest, pack_and_unpack_stl)
{
    fr::Packet packet;
    std::string a1 = "I'm a string", a2;
    std::vector<std::string> b1 = {"hello", "there", "a"}, b2;
    std::pair<int, std::string> c1 = {1, "a"}, c2;

    packet << a1 << b1 << c1;
    packet >> a2 >> b2 >> c2;

    ASSERT_EQ(a1, a2);
    ASSERT_EQ(b1, b2);
    ASSERT_EQ(c1, c2);
}

TEST(PacketTest, pack_and_unpack_map)
{
    std::map<std::string, std::string> base = {{"a", "b"}, {"bob", "lob"}};
    std::map<std::string, std::string> copy;
    fr::Packet packet;
    packet << base;
    packet >> copy;
    ASSERT_EQ(base, copy);
}

TEST(PacketTest, pack_and_unpack_unordered_map)
{
    std::unordered_map<std::string, std::string> base = {{"a", "b"}, {"bob", "lob"}};
    std::unordered_map<std::string, std::string> copy;
    fr::Packet packet;
    packet << base;
    packet >> copy;
    ASSERT_EQ(base, copy);
}

TEST(PacketTest, variadic_packet_constructor)
{
    int a1 = 10, a2;
    std::string b1 = "hey", b2;
    int64_t c1 = 90, c2;

    fr::Packet packet(a1, b1, c1);
    packet >> a2 >> b2 >> c2;

    ASSERT_EQ(a1, a2);
    ASSERT_EQ(b1, b2);
    ASSERT_EQ(c1, c2);
}

TEST(PacketTest, raw_data)
{
    std::string a1 = "hello", a2;
    std::string b1(13, 'c'), b2(13, '\0');
    uint32_t c1 = std::numeric_limits<uint32_t>::max(), c2;
    fr::Packet packet;

    packet << a1;
    packet.add_raw(b1.c_str(), b1.size());
    packet << c1;

    packet >> a2;
    packet.extract_raw(&b2[0], b1.size());
    packet >> c2;

    ASSERT_EQ(a1, a2);
    ASSERT_EQ(b1, b2);
    ASSERT_EQ(c1, c2);
}

TEST(PacketTest, out_of_bounds_protection)
{
    int32_t a = 99;
    fr::Packet packet(a, a, a, a);
    packet >> a >> a >> a >> a;

    try
    {
        packet >> a;
        FAIL();
    }
    catch(const std::out_of_range &e)
    {

    }
}

TEST(PacketTest, read_cursor)
{
    int32_t a1 = 20, a2;
    std::string b1 = "hello", b2;
    fr::Packet packet(a1, b1);

    packet >> a2 >> b2;
    ASSERT_EQ(a1, a2);
    ASSERT_EQ(b1, b2);

    packet.set_cursor();
    packet >> a2 >> b2;
    ASSERT_EQ(a1, a2);
    ASSERT_EQ(b1, b2);

    packet.set_cursor((sizeof(a1)));
    packet >> b2;
    ASSERT_EQ(b1, b2);
}

TEST(PacketTest, clear)
{
    uint32_t a = 20, b;
    fr::Packet packet;
    packet << a << a << a;
    packet.clear();
    ASSERT_ANY_THROW(packet >> a);

    a = 20;
    packet << a;
    packet >> b;
    ASSERT_TRUE(b == 20);
}

TEST(PacketTest, test_assert_bytes_remaining)
{
    fr::Packet packet;
    ASSERT_THROW(packet.assert_bytes_remaining(1), std::out_of_range);
    packet << (uint32_t)1;
    ASSERT_NO_THROW(packet.assert_bytes_remaining(4));
    uint32_t out;
    packet >> out;
    ASSERT_THROW(packet.assert_bytes_remaining(1), std::out_of_range);
}

TEST(PacketTest, test_size)
{
    uint32_t val = 30;
    fr::Packet packet;
    ASSERT_EQ(packet.size(), 0u);
    packet << val;
    ASSERT_EQ(packet.size(), 4u);
    packet >> val;
    ASSERT_EQ(packet.size(), 4u);
}

TEST(PacketTest, test_get_bytes_remaining)
{
    uint32_t val = 30;
    fr::Packet packet;
    ASSERT_EQ(packet.get_bytes_remaining(), 0u);
    packet << val;
    ASSERT_EQ(packet.get_bytes_remaining(), 4u);

    uint16_t out;
    packet >> out;
    ASSERT_EQ(packet.get_bytes_remaining(), 2u);

    packet.clear();
    ASSERT_EQ(packet.get_bytes_remaining(), 0u);
}

TEST(PacketTest, test_get_cursor)
{
    uint32_t val = 0;
    fr::Packet packet;
    ASSERT_EQ(packet.get_cursor(), 0u);
    packet << val << val;
    ASSERT_EQ(packet.get_cursor(), 0u);
    packet >> val;
    ASSERT_EQ(packet.get_cursor(), 4u);
    packet >> val;
    ASSERT_EQ(packet.get_cursor(), 8u);
    packet.set_cursor(4);
    ASSERT_EQ(packet.get_cursor(), 4u);
    packet.clear();
    ASSERT_EQ(packet.get_cursor(), 0u);
}

TEST(PacketTest, test_set_cursor)
{
    uint32_t val = 0;
    fr::Packet packet;
    packet.set_cursor(5);
    ASSERT_EQ(packet.get_cursor(), 0u);
    packet << val;
    packet.set_cursor(3);
    ASSERT_EQ(packet.get_cursor(), 3u);
}

TEST(PacketTest, test_seek_cursor)
{
    uint32_t val = 0;
    fr::Packet packet;
    packet.seek_cursor(-20);
    ASSERT_EQ(packet.get_cursor(), 0u);
    packet.seek_cursor(20);
    ASSERT_EQ(packet.get_cursor(), 0u);
    packet << val;
    packet.seek_cursor(3);
    ASSERT_EQ(packet.get_cursor(), 3u);
    packet.seek_cursor(-1);
    ASSERT_EQ(packet.get_cursor(), 2u);
    packet.seek_cursor(-10);
    ASSERT_EQ(packet.get_cursor(), 0u);
    packet.seek_cursor(10);
    ASSERT_EQ(packet.get_cursor(), 4u);
}

<|start_filename|>include/frnetlib/Sendable.h<|end_filename|>
//
// Created by fred.nicolson on 18/07/17.
//

#ifndef FRNETLIB_SENDABLE_H
#define FRNETLIB_SENDABLE_H
#include "Socket.h"

namespace fr
{
    class Sendable
    {
    public:
        virtual ~Sendable()=default;

        /*!
         * Overridable send, to allow
         * custom types to be directly sent through
         * sockets.
         *
         * @param socket The socket to send through
         * @return Status indicating if the send succeeded or not. This is dependent on the underlying type.
         */
        virtual Socket::Status send(Socket *socket) const = 0;

        /*!
         * Overrideable receive, to allow
         * custom types to be directly received through
         * sockets.
         *
         * @param socket The socket to send through
         * @return Status indicating if the send succeeded or not. This is dependent on the underlying type.
         */
        virtual Socket::Status receive(Socket *socket) = 0;
    };
}


#endif //FRNETLIB_SENDABLE_H


<|start_filename|>main.cpp<|end_filename|>
#include <iostream>
#include <thread>
#include <atomic>
#include <mutex>
#include "frnetlib/Packet.h"
#include "frnetlib/TcpSocket.h"
#include "frnetlib/TcpListener.h"
#include "frnetlib/SocketSelector.h"
#include "frnetlib/HttpRequest.h"
#include "frnetlib/HttpResponse.h"

enum Enum : uint32_t
{
    E1 = 0,
    E2 = 1,
    E3 = 2,
};

size_t get_vector_size(const std::vector<int> &vec)
{
    return vec.size();
}

int main()
{
    std::vector<int> source{1, 2, 3};
    std::cout << get_vector_size(source) << std::endl;
}

<|start_filename|>include/frnetlib/SocketDescriptor.h<|end_filename|>
//
// Created by fred.nicolson on 01/10/18.
//

#ifndef FRNETLIB_SOCKETDESCRIPTOR_H
#define FRNETLIB_SOCKETDESCRIPTOR_H

#include <stdint.h>
namespace fr
{
class SocketDescriptor
{
public:

    virtual ~SocketDescriptor()=default;

    /*!
     * Checks to see if the socket is connected or not
     *
     * @return True if connected, false otherwise
     */
    virtual bool connected() const = 0;

    /*!
     * Gets the underlying socket descriptor.
     *
     * @return The socket descriptor.
     */
    virtual int32_t get_socket_descriptor() const = 0;
};
}


#endif //FRNETLIB_SOCKETDESCRIPTOR_H


<|start_filename|>include/frnetlib/HttpResponse.h<|end_filename|>
//
// Created by fred on 10/12/16.
//

#ifndef FRNETLIB_HTTPRESPONSE_H
#define FRNETLIB_HTTPRESPONSE_H

#include <string>
#include <vector>
#include <unordered_map>
#include "Http.h"

namespace fr
{
    class HttpResponse : public Http
    {
    public:
        //Constructors
        HttpResponse()=default;
        HttpResponse(HttpResponse&&)=default;
        HttpResponse(const HttpResponse&)= default;
        HttpResponse &operator=(const HttpResponse &)=default;
        HttpResponse &operator=(HttpResponse &&)=default;

        /*!
         * Parse a raw request or response from a string
         * into the object.
         *
         * @param data The request/response to parse
         * @param datasz The length of data in bytes
         * @return NotEnoughData if parse needs to be called again. Success on success, other on error.
         */
        fr::Socket::Status parse(const char *data, size_t datasz) override;

        /*!
         * Constructs a HttpResponse, ready to send.
         *
         * @return The constructed HTTP response.
         */
        std::string construct(const std::string &host) const override;

    private:
        /*!
         * Parses the request header.
         *
         * @param header_end_pos The position in 'body' of the end of the header
         */
        bool parse_header(size_t header_end_pos);

        //State
        bool header_ended{false};
        size_t content_length{0};
        size_t chunk_offset{0};
    };
}

#endif //FRNETLIB_HTTPRESPONSE_H


<|start_filename|>include/frnetlib/SocketSelector.h<|end_filename|>
//
// Created by fred on 09/12/16.
//

#ifndef FRNETLIB_SOCKETSELECTOR_H
#define FRNETLIB_SOCKETSELECTOR_H

#include <chrono>
#include <vector>
#include <iostream>
#include <unordered_map>
#include "NetworkEncoding.h"
#include "Socket.h"
#include "TcpListener.h"

namespace fr
{
    class SocketSelector
    {
    public:
        SocketSelector();
        ~SocketSelector();

        /*!
         * Adds a socket to the selector along with some opaque state
         *
         * @throws An std::exception on failure
         * @param socket The socket to add, can be a Listener/Socket.
         * @param opaque Opaque data which is passed back by wait() when the socket
         * has activity. Can be used for state management.
         */
        void add(const std::shared_ptr<fr::SocketDescriptor> &socket, void *opaque);

        /*!
         * Waits for activity on one of the added sockets. If a socket disconnects,
         * then it will automatically be removed from the selector, and so remove()
         * should not be called.
         *
         * @throws An std::exception on failure
         * @param timeout The maximum time in milliseconds to wait for. Default/-1 for no timeout.
         * @return A list of sockets which either are ready, or have disconnected. This can be empty
         * if there is a timeout, or the wait is interrupted.
         */
        std::vector<std::pair<std::shared_ptr<fr::SocketDescriptor>, void*>> wait(std::chrono::milliseconds timeout = std::chrono::milliseconds(-1));

        /*!
         * Removes a socket from the selector.
         * Does nothing if the socket isn't a member.
         *
         * @throws An std::exception if an internal EPOLL error occurs
         * @param socket The socket to remove. May have been disconnected.
         * @return The opaque data passed to add(). Or nullptr if the socket wasn't found.
         */
        void *remove(const std::shared_ptr<fr::SocketDescriptor> &socket);
    private:

#ifndef _WIN32
        struct Opaque
        {
            Opaque(std::shared_ptr<fr::SocketDescriptor> socket_, void *opaque_, int32_t descriptor_)
            : socket(std::move(socket_)),
              opaque(opaque_),
              descriptor(descriptor_)
            {}

            std::shared_ptr<fr::SocketDescriptor> socket;
            void *opaque;
            int32_t descriptor;
        };
        int epoll_fd;
        std::unordered_map<uintptr_t, Opaque> added_sockets;
#endif
    };
}


#endif //FRNETLIB_SOCKETSELECTOR_H


<|start_filename|>src/Sha1.cpp<|end_filename|>
/*
    Sha1.cpp - source code of
    ============
    SHA-1 in C++
    ============
    100% Public Domain.
    Original C Code
        -- <NAME> <<EMAIL>>
    Small changes to fit into bglibs
        -- <NAME> <<EMAIL>>
    Translation to simpler C++ Code
        -- <NAME> <<EMAIL>>
    Safety fixes
        -- <NAME> <slowriot at voxelstorm dot com>

--------------------------------------------------------------------
|    Sourced from: https://github.com/vog/sha1                     |
|    Altered for inclusion within frnetlib.                        |
--------------------------------------------------------------------
*/

#include "frnetlib/Sha1.h"
#include <sstream>
#include <iomanip>
#include <fstream>


namespace fr
{
    static const size_t BLOCK_INTS = 16;  /* number of 32bit integers per Sha1 block */
    static const size_t BLOCK_BYTES = BLOCK_INTS * 4;


    static void reset(uint32_t digest[], std::string &buffer, uint64_t &transforms)
    {
        /* Sha1 initialization constants */
        digest[0] = 0x67452301;
        digest[1] = 0xefcdab89;
        digest[2] = 0x98badcfe;
        digest[3] = 0x10325476;
        digest[4] = 0xc3d2e1f0;

        /* Reset counters */
        buffer = "";
        transforms = 0;
    }


    static uint32_t rol(const uint32_t value, const size_t bits)
    {
        return (value << bits) | (value >> (32 - bits));
    }


    static uint32_t blk(const uint32_t block[BLOCK_INTS], const size_t i)
    {
        return rol(block[(i + 13) & 15] ^ block[(i + 8) & 15] ^ block[(i + 2) & 15] ^ block[i], 1);
    }


/*
 * (R0+R1), R2, R3, R4 are the different operations used in Sha1
 */

    static void
    R0(const uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z,
       const size_t i)
    {
        z += ((w & (x ^ y)) ^ y) + block[i] + 0x5a827999 + rol(v, 5);
        w = rol(w, 30);
    }


    static void
    R1(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z,
       const size_t i)
    {
        block[i] = blk(block, i);
        z += ((w & (x ^ y)) ^ y) + block[i] + 0x5a827999 + rol(v, 5);
        w = rol(w, 30);
    }


    static void
    R2(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z,
       const size_t i)
    {
        block[i] = blk(block, i);
        z += (w ^ x ^ y) + block[i] + 0x6ed9eba1 + rol(v, 5);
        w = rol(w, 30);
    }


    static void
    R3(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z,
       const size_t i)
    {
        block[i] = blk(block, i);
        z += (((w | x) & y) | (w & x)) + block[i] + 0x8f1bbcdc + rol(v, 5);
        w = rol(w, 30);
    }


    static void
    R4(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z,
       const size_t i)
    {
        block[i] = blk(block, i);
        z += (w ^ x ^ y) + block[i] + 0xca62c1d6 + rol(v, 5);
        w = rol(w, 30);
    }


/*
 * Hash a single 512-bit block. This is the core of the algorithm.
 */

    static void transform(uint32_t digest[], uint32_t block[BLOCK_INTS], uint64_t &transforms)
    {
        /* Copy digest[] to working vars */
        uint32_t a = digest[0];
        uint32_t b = digest[1];
        uint32_t c = digest[2];
        uint32_t d = digest[3];
        uint32_t e = digest[4];

        /* 4 rounds of 20 operations each. Loop unrolled. */
        R0(block, a, b, c, d, e, 0);
        R0(block, e, a, b, c, d, 1);
        R0(block, d, e, a, b, c, 2);
        R0(block, c, d, e, a, b, 3);
        R0(block, b, c, d, e, a, 4);
        R0(block, a, b, c, d, e, 5);
        R0(block, e, a, b, c, d, 6);
        R0(block, d, e, a, b, c, 7);
        R0(block, c, d, e, a, b, 8);
        R0(block, b, c, d, e, a, 9);
        R0(block, a, b, c, d, e, 10);
        R0(block, e, a, b, c, d, 11);
        R0(block, d, e, a, b, c, 12);
        R0(block, c, d, e, a, b, 13);
        R0(block, b, c, d, e, a, 14);
        R0(block, a, b, c, d, e, 15);
        R1(block, e, a, b, c, d, 0);
        R1(block, d, e, a, b, c, 1);
        R1(block, c, d, e, a, b, 2);
        R1(block, b, c, d, e, a, 3);
        R2(block, a, b, c, d, e, 4);
        R2(block, e, a, b, c, d, 5);
        R2(block, d, e, a, b, c, 6);
        R2(block, c, d, e, a, b, 7);
        R2(block, b, c, d, e, a, 8);
        R2(block, a, b, c, d, e, 9);
        R2(block, e, a, b, c, d, 10);
        R2(block, d, e, a, b, c, 11);
        R2(block, c, d, e, a, b, 12);
        R2(block, b, c, d, e, a, 13);
        R2(block, a, b, c, d, e, 14);
        R2(block, e, a, b, c, d, 15);
        R2(block, d, e, a, b, c, 0);
        R2(block, c, d, e, a, b, 1);
        R2(block, b, c, d, e, a, 2);
        R2(block, a, b, c, d, e, 3);
        R2(block, e, a, b, c, d, 4);
        R2(block, d, e, a, b, c, 5);
        R2(block, c, d, e, a, b, 6);
        R2(block, b, c, d, e, a, 7);
        R3(block, a, b, c, d, e, 8);
        R3(block, e, a, b, c, d, 9);
        R3(block, d, e, a, b, c, 10);
        R3(block, c, d, e, a, b, 11);
        R3(block, b, c, d, e, a, 12);
        R3(block, a, b, c, d, e, 13);
        R3(block, e, a, b, c, d, 14);
        R3(block, d, e, a, b, c, 15);
        R3(block, c, d, e, a, b, 0);
        R3(block, b, c, d, e, a, 1);
        R3(block, a, b, c, d, e, 2);
        R3(block, e, a, b, c, d, 3);
        R3(block, d, e, a, b, c, 4);
        R3(block, c, d, e, a, b, 5);
        R3(block, b, c, d, e, a, 6);
        R3(block, a, b, c, d, e, 7);
        R3(block, e, a, b, c, d, 8);
        R3(block, d, e, a, b, c, 9);
        R3(block, c, d, e, a, b, 10);
        R3(block, b, c, d, e, a, 11);
        R4(block, a, b, c, d, e, 12);
        R4(block, e, a, b, c, d, 13);
        R4(block, d, e, a, b, c, 14);
        R4(block, c, d, e, a, b, 15);
        R4(block, b, c, d, e, a, 0);
        R4(block, a, b, c, d, e, 1);
        R4(block, e, a, b, c, d, 2);
        R4(block, d, e, a, b, c, 3);
        R4(block, c, d, e, a, b, 4);
        R4(block, b, c, d, e, a, 5);
        R4(block, a, b, c, d, e, 6);
        R4(block, e, a, b, c, d, 7);
        R4(block, d, e, a, b, c, 8);
        R4(block, c, d, e, a, b, 9);
        R4(block, b, c, d, e, a, 10);
        R4(block, a, b, c, d, e, 11);
        R4(block, e, a, b, c, d, 12);
        R4(block, d, e, a, b, c, 13);
        R4(block, c, d, e, a, b, 14);
        R4(block, b, c, d, e, a, 15);

        /* Add the working vars back into digest[] */
        digest[0] += a;
        digest[1] += b;
        digest[2] += c;
        digest[3] += d;
        digest[4] += e;

        /* Count the number of transformations */
        transforms++;
    }


    static void buffer_to_block(const std::string &buffer, uint32_t block[BLOCK_INTS])
    {
        /* Convert the std::string (byte buffer) to a uint32_t array (MSB) */
        for(size_t i = 0; i < BLOCK_INTS; i++)
        {
            block[i] = (buffer[4 * i + 3] & 0xff)
                       | (buffer[4 * i + 2] & 0xff) << 8
                       | (buffer[4 * i + 1] & 0xff) << 16
                       | (buffer[4 * i + 0] & 0xff) << 24;
        }
    }


    Sha1::Sha1()
    {
        reset(digest, buffer, transforms);
    }


    void Sha1::update(const std::string &s)
    {
        std::istringstream is(s);
        update(is);
    }


    void Sha1::update(std::istream &is)
    {
        while(true)
        {
            char sbuf[BLOCK_BYTES];
            is.read(sbuf, BLOCK_BYTES - buffer.size());
            buffer.append(sbuf, is.gcount());
            if(buffer.size() != BLOCK_BYTES)
            {
                return;
            }
            uint32_t block[BLOCK_INTS];
            buffer_to_block(buffer, block);
            transform(digest, block, transforms);
            buffer.clear();
        }
    }


/*
 * Add padding and finish up
 */

    void Sha1::final()
    {
        /* Total number of hashed bits */
        uint64_t total_bits = (transforms * BLOCK_BYTES + buffer.size()) * 8;

        /* Padding */
        buffer += static_cast<char>(0x80);
        size_t orig_size = buffer.size();
        while(buffer.size() < BLOCK_BYTES)
        {
            buffer += static_cast<char>(0x00);
        }

        uint32_t block[BLOCK_INTS];
        buffer_to_block(buffer, block);

        if(orig_size > BLOCK_BYTES - 8)
        {
            transform(digest, block, transforms);
            for(size_t i = 0; i < BLOCK_INTS - 2; i++)
            {
                block[i] = 0;
            }
        }

        /* Append total_bits, split this uint64_t into two uint32_t */
        block[BLOCK_INTS - 1] = total_bits;
        block[BLOCK_INTS - 2] = (total_bits >> 32);
        transform(digest, block, transforms);
    }
}

<|start_filename|>include/frnetlib/Base64.h<|end_filename|>
//
// Created by fred on 01/03/18.
//

#ifndef FRNETLIB_BASE64_H
#define FRNETLIB_BASE64_H
#include <string>

namespace fr
{
    class Base64
    {
    public:
        /*!
         * Encodes a string into Base64
         *
         * @param input The string to encode
         * @return The resulting encoded string
         */
        static std::string encode(const std::string &input);

        //There's no decode function at the moment. Maybe I'll write one eventually. Sorry.
    private:

    };
}

#endif //FRNETLIB_BASE64_H


<|start_filename|>src/SocketSelector.cpp<|end_filename|>
//
// Created by fred on 09/12/16.
//

#include <thread>
#include <mutex>
#include "frnetlib/SocketSelector.h"

//Linux EPOLL implementation
namespace fr
{
#ifndef _WIN32
#include <sys/epoll.h>

    SocketSelector::SocketSelector()
    : epoll_fd(-1)
    {
        epoll_fd = epoll_create1(O_CLOEXEC);
        if(epoll_fd < 0)
        {
            throw std::runtime_error("Failed to create EPOLL descriptor: " + std::to_string(errno));
        }
    }

    SocketSelector::~SocketSelector()
    {
        close(epoll_fd);
    }

    void SocketSelector::add(const std::shared_ptr<fr::SocketDescriptor> &socket, void *opaque)
    {
        int32_t descriptor = socket->get_socket_descriptor();
        if(!socket->connected())
        {
            throw std::logic_error("Can't add disconnected socket");
        }

        auto added_iter = added_sockets.emplace((uintptr_t)socket.get(), Opaque(socket, opaque, descriptor));
        if(!added_iter.second)
        {
            throw std::logic_error("Can't add duplicate socket");
        }

        epoll_event event = {0};
        event.events = EPOLLIN | EPOLLHUP | EPOLLERR | EPOLLRDHUP;
        event.data.ptr = &added_iter.first->second;

        if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, descriptor, &event) < 0)
        {
            added_sockets.erase(added_iter.first);
            throw std::runtime_error("Failed to add socket: " + std::to_string(errno));
        }
    }

    std::vector<std::pair<std::shared_ptr<fr::SocketDescriptor>, void *>>
    SocketSelector::wait(std::chrono::milliseconds timeout)
    {
        static thread_local epoll_event events[100];
        int event_count = epoll_wait(epoll_fd, events, 100, timeout.count());
        if(event_count < 0)
        {
            if(errno == EINTR)
            {
                return {};
            }
            throw std::runtime_error("epoll_wait returned: " + std::to_string(errno));
        }

        std::vector<std::pair<std::shared_ptr<fr::SocketDescriptor>, void *>> ret;
        for(int a = 0; a < event_count; ++a)
        {
            auto *opaque = static_cast<Opaque *>(events[a].data.ptr);
            ret.emplace_back(opaque->socket, opaque->opaque);
        }
        return ret;
    }

    void *SocketSelector::remove(const std::shared_ptr<fr::SocketDescriptor> &socket)
    {
        auto iter = added_sockets.find((uintptr_t)socket.get());
        if(iter == added_sockets.end())
        {
            return nullptr;
        }

        if(epoll_ctl(epoll_fd, EPOLL_CTL_DEL, iter->second.descriptor, nullptr) < 0)
        {
            throw std::runtime_error("Failed to remove socket: " + std::to_string(iter->second.descriptor) + ". Errno: " + std::to_string(errno));
        }

        void *opaque = iter->second.opaque;
        added_sockets.erase(iter);
        return opaque;
    }

#endif
}

//Windows implementation coming soon(tm)


<|start_filename|>tests/SocketTest.cpp<|end_filename|>
//
// Created by fred.nicolson on 25/09/17.
//

#include <gtest/gtest.h>
#include <frnetlib/Socket.h>

TEST(SocketTest, status_to_string_valid)
{
    ASSERT_EQ(fr::Socket::status_to_string(fr::Socket::Status::Unknown), "Unknown");
    ASSERT_EQ(fr::Socket::status_to_string(fr::Socket::Status::HttpBodyTooBig), "HTTP Body Too Big");
}

TEST(SocketTest, status_to_string_invalid)
{

    auto str = fr::Socket::status_to_string(static_cast<fr::Socket::Status>(-1));
    ASSERT_EQ(str, "Unknown");
    str = fr::Socket::status_to_string(static_cast<fr::Socket::Status>(99999));
    ASSERT_EQ(str, "Unknown");
}

<|start_filename|>include/frnetlib/Listener.h<|end_filename|>
//
// Created by fred.nicolson on 3/31/17.
//

#ifndef FRNETLIB_LISTENER_H
#define FRNETLIB_LISTENER_H
#include "Socket.h"
#include "SocketDescriptor.h"
namespace fr
{
class Listener : public SocketDescriptor
    {
    public:
        Listener()
        : ai_family(AF_UNSPEC)
        {
            init_wsa();
        }

        /*!
         * Listens to the given port for connections
         *
         * @param port The port to bind to
         * @return If the operation was successful
         */
        virtual Socket::Status listen(const std::string &port)=0;

        /*!
         * Accepts a new connection.
         *
         * @param client Where to store the connection information
         * @return True on success. False on failure.
         */
        virtual Socket::Status accept(Socket &client)=0;

        /*!
         * Calls the shutdown syscall on the socket.
         * So you can receive data but not send.
         *
         * Note: THIS DOES NOT CLOSE THE SOCKET. SHUTDOWN AND CLOSE ARE TWO DIFFERENT THINGS.
         *
         * This can be called on a blocking socket to force
         * it to immediately return (you might want to do this if
         * you're exiting and need the blocking socket to return).
         */
        virtual void shutdown()=0;

        /*!
         * Closes the socket
         */
        virtual void close_socket()=0;

        /*!
         * Sets the socket descriptor.
         *
         * @param descriptor The listen descriptor to use
         */
        virtual void set_socket_descriptor(int32_t descriptor)=0;

        /*!
         * Set which IP version to use. IP::any is the default
         * value, so either an IPv4 OR IPv6 interface will be used.
         *
         * @param version Should IPv4, IPv6 be used, or any?
         */
        void set_inet_version(Socket::IP version)
        {
            switch(version)
            {
                case Socket::IP::v4:
                    ai_family = AF_INET;
                    break;
                case Socket::IP::v6:
                    ai_family = AF_INET6;
                    break;
                case Socket::IP::any:
                    ai_family = AF_UNSPEC;
                    break;
                default:
                    throw std::logic_error("Unknown Socket::IP value passed to set_inet_version()");
            }
        }

        /*!
         * Same as the other set_inet_version,
         * but accepts a AF_INET* macro directly.
         *
         * @param version
         */
        void set_inet_version(int version)
        {
            ai_family = version;
        }

    protected:

        int ai_family;
    };
}


#endif //FRNETLIB_LISTENER_H


<|start_filename|>include/frnetlib/Packetable.h<|end_filename|>
//
// Created by fred on 02/02/17.
//

#ifndef FRNETLIB_PACKETABLE_H
#define FRNETLIB_PACKETABLE_H
#include <string>

namespace fr
{
    class Packet;
    class Packetable
    {
    public:
        virtual ~Packetable() = default;

        /*!
         * Called to pack class data into the 'destination'
         * packet.
         *
         * @param destination Where you should store the class data
         */
        virtual void pack(fr::Packet &destination) const=0;

        /*!
         * Called to unpack class data from the 'source' packet.
         *
         * @param source Where to retreive the class data from.
         */
        virtual void unpack(fr::Packet &source)=0;
    };
}

#endif //FRNETLIB_PACKETABLE_H


<|start_filename|>include/frnetlib/TcpListener.h<|end_filename|>
//
// Created by fred on 06/12/16.
//

#ifndef FRNETLIB_TCPLISTENER_H
#define FRNETLIB_TCPLISTENER_H
#include <string>
#include "TcpSocket.h"
#include "Socket.h"
#include "NetworkEncoding.h"
#include "Listener.h"

namespace fr
{

class TcpListener : public Listener
{
public:
    TcpListener();

    ~TcpListener() override;
    TcpListener(TcpListener &&o) = default;

    /*!
     * Listens to the given port for connections
     *
     * @param port The port to bind to
     * @return If the operation was successful
     */
    Socket::Status listen(const std::string &port) override;

    /*!
     * Accepts a new connection.
     *
     * @param client Where to store the connection information
     * @return True on success. False on failure.
     */
    Socket::Status accept(Socket &client) override;

    /*!
     * Calls the shutdown syscall on the socket.
     * So you can receive data but not send.
     *
     * This can be called on a blocking socket to force
     * it to immediately return (you might want to do this if
     * you're exiting and need the blocking socket to return).
     */
    void shutdown() override;

    /*!
     * Sets the socket descriptor.
     *
     * @param descriptor The listen descriptor to use
     */
    void set_socket_descriptor(int32_t descriptor) override;

    /*!
     * Closes the socket
     */
    void close_socket() override;

    /*!
     * Checks to see if the socket is connected or not
     *
     * @return True if connected, false otherwise
     */
    bool connected() const override;

    /*!
     * Gets the underlying socket descriptor.
     *
     * @return The socket descriptor.
     */
    int32_t get_socket_descriptor() const override;


private:
    int32_t socket_descriptor;
};

}


#endif //FRNETLIB_TCPLISTENER_H


<|start_filename|>src/Base64.cpp<|end_filename|>
//
// Created by fred on 01/03/18.
//

#include "frnetlib/Base64.h"

namespace fr
{
    std::string Base64::encode(const std::string &input)
    {
        static const std::string base64_table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

        std::string out;
        out.reserve(input.size() + (input.size() / 3) + 1);
        size_t a;

        //Do as many sets of 3 bytes as we can
        for(a = 0; a < input.size() - 2; a += 3)
        {
            out += base64_table[(input[a] >> 2) & 0x3F]; //Store the first 6 bits of the first byte
            out += base64_table[((input[a] & 0x3) << 4) | (input[a + 1] & 0xF0) >> 4]; //Store the last 2 bits of the first byte combined with the first 4 bits of the second byte
            out += base64_table[((input[a + 1] & 0xF) << 2) | (input[a + 2] & 0xC0) >> 6]; //Store the last 4 bits of the second byte combined with the first 2 bits of the third byte
            out += base64_table[input[a + 2] & 0x3F]; //Store the last 6 bits of the third byte
        }

        //Check if there's a remainder
        if(a < input.size())
        {
            out += base64_table[(input[a] >> 2) & 0x3F]; //Store first 6 bits of the first byte
            if(a == input.size() - 2) //There's 2 bytes left
            {
                out += base64_table[((input[a] & 0x3) << 4) | (input[a + 1] & 0xF0) >> 4]; //Store last 2 bits of first byte, and first 4 bits of second byte
                out += base64_table[(input[a + 1] & 0xF) << 2]; //Finally store the last 4 bits of the second byte
                out += "=";
            }
            else //There's 1 byte left
            {
                out += base64_table[(input[a] & 0x3) << 4]; //Store last 2 bits of first byte
                out += "==";
            }
        }

        return out;
    }

}


<|start_filename|>src/URL.cpp<|end_filename|>
//
// Created by fred.nicolson on 23/05/17.
//

#include <stdexcept>
#include <algorithm>
#include <iostream>
#include "frnetlib/URL.h"

namespace fr
{

    std::unordered_map<std::string, URL::Scheme> URL::scheme_string_map = {
            {"http",    URL::HTTP},
            {"https",   URL::HTTPS},
            {"sftp",    URL::FTP},
            {"mailto",  URL::MAILTO},
            {"irc",     URL::IRC},
            {"sftp",    URL::SFTP},
            {"unknown", URL::Unknown}
    };

    URL::URL(const std::string &url)
    {
        parse(url);
    }


    void URL::parse(const std::string &url)
    {
        scheme = Scheme::Unknown;
        size_t parse_offset = 0;
        size_t pos = 0;

        //Check to see if a scheme exists
        pos = url.find("://");
        if(pos != std::string::npos)
        {
            auto scheme_pos = scheme_string_map.find(to_lower(url.substr(0, pos)));
            scheme = (scheme_pos == scheme_string_map.end()) ? URL::Unknown : scheme_pos->second;
            parse_offset = pos + 3; // skip the ://
        }

        //Check to see if there's a port
        pos = url.find(':', parse_offset);
        if(pos != std::string::npos) //A port is provided
        {
            //Store host
            host = url.substr(parse_offset, pos - parse_offset);
            parse_offset += host.size();

            //Find end of port
            size_t port_end = url.find('/', parse_offset);
            port_end = (port_end == std::string::npos) ? url.size() : port_end;
            port = url.substr(pos + 1, port_end - pos - 1);
            parse_offset = port_end + 1;
        }
        else //There's no port
        {
            //Store host
            pos = url.find('/', parse_offset);
            pos = (pos != std::string::npos) ? pos : url.find('?', parse_offset);
            pos = (pos != std::string::npos) ? pos : url.find('#', parse_offset);
            if(pos != std::string::npos || parse_offset != 0)
            {
                pos = (pos != std::string::npos) ? pos : url.size();
                host = url.substr(parse_offset, pos - parse_offset);
                parse_offset = pos + 1;
            }

            //Guess port based on scheme, if it's not explicitly provided.
            switch(scheme)
            {
                case URL::HTTP:
                    port = "80";
                    break;
                case URL::HTTPS:
                    port = "443";
                    break;
                case URL::IRC:
                    port = "6697";
                    break;
                case URL::FTP:
                    port = "21";
                    break;
                case URL::SFTP:
                    port = "25";
                    break;
                default:
                    break;
            }
        }

        //Exit if done
        if(parse_offset >= url.size())
            return;

        //Extract the path
        pos = url.find('?', parse_offset);
        if(pos != std::string::npos)
        {
            path.append("/").append(url.substr(parse_offset, pos - parse_offset));
            parse_offset = pos + 1;
        }
        else
        {
            pos = url.find('#', parse_offset);
            pos = (pos != std::string::npos) ? pos : url.find('?', parse_offset);
            pos = (pos != std::string::npos) ? pos : url.size();
            path.append("/").append(url.substr(parse_offset, pos - parse_offset));
            parse_offset = pos + 1;
        }

        //Extract the query
        pos = url.find('#', parse_offset - 1);
        if(pos != std::string::npos)
        {
            if(pos + 1 != parse_offset)
                query = url.substr(parse_offset, pos - parse_offset);
            fragment = url.substr(pos + 1, url.size() - pos - 1);
        }
        else
        {
            if(parse_offset >= url.size())
                return;
            query = url.substr(parse_offset, url.size() - parse_offset);
        }

    }

    URL::Scheme URL::string_to_scheme(const std::string &scheme)
    {
        auto iter = scheme_string_map.find(to_lower(scheme));
        if(iter == scheme_string_map.end())
            return URL::Unknown;
        return iter->second;
    }

    const std::string &URL::scheme_to_string(URL::Scheme scheme)
    {
        auto iter = std::find_if(scheme_string_map.begin(), scheme_string_map.end(), [&](const auto &i)
        {
            return i.second == scheme;
        });

        if(iter == scheme_string_map.end())
            throw std::logic_error("Unknown URL::Scheme value " + std::to_string(scheme));
        return iter->first;
    }
}