summaryrefslogtreecommitdiff
path: root/include/http_utility.hpp
blob: 8f2478fcd2984310d0c30d81f5595519f41b20fe (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#pragma once

#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/constants.hpp>
#include <boost/iterator/iterator_facade.hpp>
#include <boost/type_index/type_index_facade.hpp>

#include <cctype>
#include <iomanip>
#include <ostream>
#include <span>
#include <string>
#include <string_view>
#include <vector>

// IWYU pragma: no_include <ctype.h>

namespace http_helpers
{

enum class ContentType
{
    NoMatch,
    ANY, // Accepts: */*
    CBOR,
    HTML,
    JSON,
    OctetStream,
};

struct ContentTypePair
{
    std::string_view contentTypeString;
    ContentType contentTypeEnum;
};

constexpr std::array<ContentTypePair, 4> contentTypes{{
    {"application/cbor", ContentType::CBOR},
    {"application/json", ContentType::JSON},
    {"application/octet-stream", ContentType::OctetStream},
    {"text/html", ContentType::HTML},
}};

inline ContentType
    getPreferedContentType(std::string_view header,
                           std::span<const ContentType> preferedOrder)
{
    size_t lastIndex = 0;
    while (lastIndex < header.size() + 1)
    {
        size_t index = header.find(',', lastIndex);
        if (index == std::string_view::npos)
        {
            index = header.size();
        }
        std::string_view encoding = header.substr(lastIndex, index);

        if (!header.empty())
        {
            header.remove_prefix(1);
        }
        lastIndex = index + 1;
        // ignore any q-factor weighting (;q=)
        std::size_t separator = encoding.find(";q=");

        if (separator != std::string_view::npos)
        {
            encoding = encoding.substr(0, separator);
        }
        // If the client allows any encoding, given them the first one on the
        // servers list
        if (encoding == "*/*")
        {
            return ContentType::ANY;
        }
        const auto* knownContentType =
            std::find_if(contentTypes.begin(), contentTypes.end(),
                         [encoding](const ContentTypePair& pair) {
            return pair.contentTypeString == encoding;
            });

        if (knownContentType == contentTypes.end())
        {
            // not able to find content type in list
            continue;
        }

        // Not one of the types requested
        if (std::find(preferedOrder.begin(), preferedOrder.end(),
                      knownContentType->contentTypeEnum) == preferedOrder.end())
        {
            continue;
        }
        return knownContentType->contentTypeEnum;
    }
    return ContentType::NoMatch;
}

inline bool isContentTypeAllowed(std::string_view header, ContentType type,
                                 bool allowWildcard)
{
    auto types = std::to_array({type});
    ContentType allowed = getPreferedContentType(header, types);
    if (allowed == ContentType::ANY)
    {
        return allowWildcard;
    }

    return type == allowed;
}

inline std::string urlEncode(std::string_view value)
{
    std::ostringstream escaped;
    escaped.fill('0');
    escaped << std::hex;

    for (const char c : value)
    {
        // Keep alphanumeric and other accepted characters intact
        if ((isalnum(c) != 0) || c == '-' || c == '_' || c == '.' || c == '~')
        {
            escaped << c;
            continue;
        }

        // Any other characters are percent-encoded
        escaped << std::uppercase;
        escaped << '%' << std::setw(2)
                << static_cast<int>(static_cast<unsigned char>(c));
        escaped << std::nouppercase;
    }

    return escaped.str();
}
} // namespace http_helpers