xref: /openbmc/bmcweb/include/http_utility.hpp (revision cef8cf9a)
1 #pragma once
2 
3 #include <algorithm>
4 #include <cctype>
5 #include <iomanip>
6 #include <ostream>
7 #include <ranges>
8 #include <span>
9 #include <string>
10 #include <string_view>
11 #include <vector>
12 
13 // IWYU pragma: no_include <ctype.h>
14 
15 namespace http_helpers
16 {
17 
18 enum class ContentType
19 {
20     NoMatch,
21     ANY, // Accepts: */*
22     CBOR,
23     HTML,
24     JSON,
25     OctetStream,
26     EventStream,
27 };
28 
29 struct ContentTypePair
30 {
31     std::string_view contentTypeString;
32     ContentType contentTypeEnum;
33 };
34 
35 constexpr std::array<ContentTypePair, 5> contentTypes{{
36     {"application/cbor", ContentType::CBOR},
37     {"application/json", ContentType::JSON},
38     {"application/octet-stream", ContentType::OctetStream},
39     {"text/html", ContentType::HTML},
40     {"text/event-stream", ContentType::EventStream},
41 }};
42 
43 inline ContentType
44     getPreferredContentType(std::string_view header,
45                             std::span<const ContentType> preferedOrder)
46 {
47     size_t lastIndex = 0;
48     while (lastIndex < header.size() + 1)
49     {
50         size_t index = header.find(',', lastIndex);
51         if (index == std::string_view::npos)
52         {
53             index = header.size();
54         }
55         std::string_view encoding = header.substr(lastIndex, index);
56 
57         if (!header.empty())
58         {
59             header.remove_prefix(1);
60         }
61         lastIndex = index + 1;
62         // ignore any q-factor weighting (;q=)
63         std::size_t separator = encoding.find(";q=");
64 
65         if (separator != std::string_view::npos)
66         {
67             encoding = encoding.substr(0, separator);
68         }
69         // If the client allows any encoding, given them the first one on the
70         // servers list
71         if (encoding == "*/*")
72         {
73             return ContentType::ANY;
74         }
75         const auto* knownContentType = std::ranges::find_if(
76             contentTypes, [encoding](const ContentTypePair& pair) {
77             return pair.contentTypeString == encoding;
78         });
79 
80         if (knownContentType == contentTypes.end())
81         {
82             // not able to find content type in list
83             continue;
84         }
85 
86         // Not one of the types requested
87         if (std::ranges::find(preferedOrder,
88                               knownContentType->contentTypeEnum) ==
89             preferedOrder.end())
90         {
91             continue;
92         }
93         return knownContentType->contentTypeEnum;
94     }
95     return ContentType::NoMatch;
96 }
97 
98 inline bool isContentTypeAllowed(std::string_view header, ContentType type,
99                                  bool allowWildcard)
100 {
101     auto types = std::to_array({type});
102     ContentType allowed = getPreferredContentType(header, types);
103     if (allowed == ContentType::ANY)
104     {
105         return allowWildcard;
106     }
107 
108     return type == allowed;
109 }
110 
111 } // namespace http_helpers
112