Line data Source code
1 : #ifndef THINGER_HTTP_COMMON_SYNTAX_HPP
2 : #define THINGER_HTTP_COMMON_SYNTAX_HPP
3 :
4 : #include <algorithm>
5 : #include <cstddef>
6 : #include <string_view>
7 :
8 : // Character classes and common rules of the HTTP grammar (RFC 9110, section 5.6), for the
9 : // parsers and for checking what is sent
10 : namespace thinger::http::syntax {
11 :
12 : // tchar
13 6437390 : constexpr bool is_token_char(unsigned char c) {
14 6437390 : if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) return true;
15 504313 : switch (c) {
16 75418 : case '!': case '#': case '$': case '%': case '&': case '\'': case '*': case '+':
17 : case '-': case '.': case '^': case '_': case '`': case '|': case '~':
18 75418 : return true;
19 428895 : default:
20 428895 : return false;
21 : }
22 : }
23 :
24 : // token = 1*tchar
25 563608 : constexpr bool is_token(std::string_view value) {
26 6916365 : return !value.empty() && std::all_of(value.begin(), value.end(), [](unsigned char c) { return is_token_char(c); });
27 : }
28 :
29 : // SP / HTAB
30 117328 : constexpr bool is_whitespace(unsigned char c) {
31 117328 : return c == ' ' || c == '\t';
32 : }
33 :
34 : // Field values (and reason phrases): HTAB, SP, VCHAR and obs-text; never CR, LF, NUL or
35 : // another control character
36 6553490 : constexpr bool is_field_value_char(unsigned char c) {
37 6553490 : return c == '\t' || (c >= 0x20 && c != 0x7f);
38 : }
39 :
40 485903 : constexpr bool is_field_value(std::string_view value) {
41 7004845 : return std::all_of(value.begin(), value.end(), [](unsigned char c) { return is_field_value_char(c); });
42 : }
43 :
44 : // Without leading and trailing whitespace (OWS)
45 28757 : constexpr std::string_view trim(std::string_view value) {
46 50648 : while (!value.empty() && is_whitespace(value.front())) value.remove_prefix(1);
47 28784 : while (!value.empty() && is_whitespace(value.back())) value.remove_suffix(1);
48 28757 : return value;
49 : }
50 :
51 : // Whether `predicate` holds for an element of a comma-separated list (RFC 9110, 5.6.1),
52 : // given without whitespace around it; empty elements are skipped. Stops at the first one.
53 : template<typename Predicate>
54 7121 : constexpr bool any_element(std::string_view list, Predicate&& predicate) {
55 7807 : while (!list.empty()) {
56 6365 : auto comma = list.find(',');
57 6365 : auto element = trim(list.substr(0, comma));
58 6365 : if (!element.empty() && predicate(element)) return true;
59 2656 : if (comma == std::string_view::npos) break;
60 686 : list.remove_prefix(comma + 1);
61 : }
62 3412 : return false;
63 : }
64 :
65 : template<typename Function>
66 3146 : constexpr void for_each_element(std::string_view list, Function&& function) {
67 4084 : any_element(list, [&](std::string_view element) {
68 2308 : function(element);
69 2308 : return false;
70 : });
71 3146 : }
72 :
73 : // Weight (qvalue, RFC 9110, 12.4.2) in thousandths: "0" or "1" with up to three decimals;
74 : // -1 if invalid
75 165 : constexpr int parse_qvalue(std::string_view value) {
76 165 : if (value.empty() || value.size() > 5) return -1;
77 162 : if (value[0] != '0' && value[0] != '1') return -1;
78 153 : int quality = (value[0] - '0') * 1000;
79 153 : if (value.size() == 1) return quality;
80 105 : if (value[1] != '.') return -1;
81 105 : int scale = 100;
82 216 : for (size_t i = 2; i < value.size(); ++i, scale /= 10) {
83 111 : if (value[i] < '0' || value[i] > '9') return -1;
84 111 : quality += (value[i] - '0') * scale;
85 : }
86 105 : return quality <= 1000 ? quality : -1;
87 : }
88 :
89 : }
90 :
91 : #endif
|