src/cookie.cpp

80.0% Lines (60/75) 80.0% List of functions (4/5) 72.1% Branches (101/140)
cookie.cpp
f(x) Functions (5)
Line Branch TLA Hits Source Code
1 //
2 // Copyright (c) 2026 Mohammad Nejati
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 // Official repository: https://github.com/cppalliance/beast2
8 //
9
10 #include <boost/burl/cookie.hpp>
11
12 #include <boost/url/grammar.hpp>
13 #include <boost/url/grammar/all_chars.hpp>
14
15 #include <iomanip>
16 #include <sstream>
17
18 namespace boost
19 {
20 namespace burl
21 {
22
23 namespace grammar = urls::grammar;
24 namespace ch = std::chrono;
25
26 namespace
27 {
28
29 struct name_chars_t
30 {
31 constexpr bool
32 266x operator()(char c) const noexcept
33 {
34 // clang-format off
35 return
36
2/4
✓ Branch 0 taken 266 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 266 times.
✗ Branch 3 not taken.
266x c > 0x20 && c != 0x7F &&
37
5/10
✓ Branch 0 taken 266 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 266 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 266 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 266 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 266 times.
✗ Branch 9 not taken.
266x c != '(' && c != ')' && c != '<' && c != '>' && c != '@' &&
38
5/10
✓ Branch 0 taken 266 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 266 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 266 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 266 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 266 times.
✗ Branch 9 not taken.
266x c != ',' && c != ';' && c != ':' && c != '\\' && c != '"' &&
39
6/10
✓ Branch 0 taken 266 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 266 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 266 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 190 times.
✓ Branch 7 taken 76 times.
✓ Branch 8 taken 190 times.
✗ Branch 9 not taken.
266x c != '/' && c != '[' && c != ']' && c != '?' && c != '=' &&
40
2/4
✓ Branch 0 taken 266 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 190 times.
✗ Branch 3 not taken.
532x c != '{' && c != '}';
41 // clang-format on
42 }
43 };
44
45 constexpr auto name_chars = name_chars_t{};
46
47 struct value_chars_t
48 {
49 constexpr bool
50 178x operator()(char c) const noexcept
51 {
52 // clang-format off
53 return
54
1/2
✓ Branch 0 taken 178 times.
✗ Branch 1 not taken.
178x (c == 0x21 ) ||
55
2/4
✓ Branch 0 taken 178 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 178 times.
✗ Branch 3 not taken.
178x (c >= 0x23 && c <= 0x2B) ||
56
4/4
✓ Branch 0 taken 121 times.
✓ Branch 1 taken 57 times.
✓ Branch 2 taken 74 times.
✓ Branch 3 taken 47 times.
178x (c >= 0x2D && c <= 0x3A) ||
57
5/6
✓ Branch 0 taken 178 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 73 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 73 times.
✓ Branch 5 taken 47 times.
429x (c >= 0x3C && c <= 0x5B) ||
58
1/2
✓ Branch 0 taken 73 times.
✗ Branch 1 not taken.
251x (c >= 0x5D && c <= 0x7E);
59 // clang-format on
60 }
61 };
62
63 constexpr auto value_chars = value_chars_t{};
64
65 constexpr auto attr_chars =
66 urls::grammar::all_chars - urls::grammar::lut_chars("\x1F\x7f;");
67
68 bool
69 148x ci_starts_with(
70 core::string_view s,
71 core::string_view prefix) noexcept
72 {
73
4/4
✓ Branch 2 taken 13 times.
✓ Branch 3 taken 135 times.
✓ Branch 4 taken 10 times.
✓ Branch 5 taken 3 times.
161x return s.size() >= prefix.size() &&
74 161x grammar::ci_is_equal(s.substr(0, prefix.size()), prefix);
75 }
76
77 ch::system_clock::time_point
78 parse_date(core::string_view sv)
79 {
80 // TODO: There are more date formats; we need a
81 // better parsing method.
82 auto tm = std::tm{};
83 auto ss = std::stringstream{ sv };
84
85 ss >> std::get_time(
86 &tm,
87 sv.contains('-') ? "%a, %d-%b-%Y %H:%M:%S GMT"
88 : "%a, %d %b %Y %H:%M:%S GMT");
89
90 return ch::system_clock::from_time_t(std::mktime(&tm));
91 }
92 } // namespace
93
94 system::result<cookie>
95 78x parse_cookie(std::string_view sv)
96 {
97 static constexpr auto cookie_parser = grammar::tuple_rule(
98 grammar::token_rule(name_chars),
99 grammar::squelch(grammar::delim_rule('=')),
100 grammar::optional_rule(grammar::token_rule(value_chars)),
101 grammar::range_rule(
102 grammar::tuple_rule(
103 grammar::squelch(grammar::delim_rule(';')),
104 grammar::squelch(
105 grammar::optional_rule(grammar::delim_rule(' '))),
106 grammar::token_rule(attr_chars - grammar::lut_chars('=')),
107 grammar::squelch(
108 grammar::optional_rule(grammar::delim_rule('='))),
109 grammar::optional_rule(grammar::token_rule(attr_chars)))));
110
111
1/1
✓ Branch 2 taken 78 times.
78x const auto parse_rs = grammar::parse(sv, cookie_parser);
112
113
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 76 times.
78x if(parse_rs.has_error())
114 2x return parse_rs.error();
115
116 76x auto rs = cookie{};
117
2/2
✓ Branch 2 taken 76 times.
✓ Branch 6 taken 76 times.
76x rs.name = std::get<0>(parse_rs.value());
118
3/3
✓ Branch 2 taken 76 times.
✓ Branch 6 taken 74 times.
✓ Branch 7 taken 2 times.
76x if(auto& value = std::get<1>(parse_rs.value()))
119
2/2
✓ Branch 1 taken 74 times.
✓ Branch 4 taken 74 times.
74x rs.value = *value;
120
121
3/3
✓ Branch 2 taken 76 times.
✓ Branch 10 taken 57 times.
✓ Branch 11 taken 75 times.
132x for(auto&& attr : std::get<2>(parse_rs.value()))
122 {
123 57x auto name = std::get<0>(attr);
124
1/1
✓ Branch 2 taken 57 times.
57x auto value = std::get<1>(attr);
125
126
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 57 times.
57x if(grammar::ci_is_equal(name, "Expires"))
127 {
128 if(!value)
129 return grammar::error::invalid;
130
131 rs.expires = parse_date(*value);
132 }
133
2/2
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 53 times.
57x else if(grammar::ci_is_equal(name, "Max-Age"))
134 {
135
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
4x if(!value)
136 return grammar::error::invalid;
137 // Convert to expiry date
138 // TODO: replace std::stoll
139 // TODO: check for overflow
140 rs.expires =
141
4/4
✓ Branch 1 taken 4 times.
✓ Branch 4 taken 4 times.
✓ Branch 7 taken 4 times.
✓ Branch 12 taken 4 times.
4x ch::system_clock::now() + ch::seconds{ std::stoll(*value) };
142 }
143
2/2
✓ Branch 2 taken 17 times.
✓ Branch 3 taken 36 times.
53x else if(grammar::ci_is_equal(name, "Domain"))
144 {
145
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
17x if(!value)
146 return grammar::error::invalid;
147
148
2/2
✓ Branch 1 taken 17 times.
✓ Branch 4 taken 17 times.
17x rs.domain = *value;
149 }
150
2/2
✓ Branch 2 taken 11 times.
✓ Branch 3 taken 25 times.
36x else if(grammar::ci_is_equal(name, "Path"))
151 {
152
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
11x if(!value)
153 return grammar::error::invalid;
154
2/2
✓ Branch 1 taken 11 times.
✓ Branch 4 taken 11 times.
11x rs.path = *value;
155 }
156
2/2
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 21 times.
25x else if(grammar::ci_is_equal(name, "SameSite"))
157 {
158
3/3
✓ Branch 2 taken 4 times.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 3 times.
4x if(grammar::ci_is_equal(value.value_or(""), "Strict"))
159 1x rs.same_site = cookie::same_site_t::strict;
160
3/3
✓ Branch 2 taken 3 times.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 2 times.
3x else if(grammar::ci_is_equal(value.value_or(""), "Lax"))
161 1x rs.same_site = cookie::same_site_t::lax;
162
3/3
✓ Branch 2 taken 2 times.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 1 time.
2x else if(grammar::ci_is_equal(value.value_or(""), "None"))
163 1x rs.same_site = cookie::same_site_t::none;
164 else
165 1x return grammar::error::invalid;
166 }
167
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 21 times.
21x else if(grammar::ci_is_equal(name, "Partitioned"))
168 {
169 rs.partitioned = true;
170 }
171
2/2
✓ Branch 2 taken 20 times.
✓ Branch 3 taken 1 time.
21x else if(grammar::ci_is_equal(name, "Secure"))
172 {
173 20x rs.secure = true;
174 }
175
1/2
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
1x else if(grammar::ci_is_equal(name, "HttpOnly"))
176 {
177 1x rs.http_only = true;
178 }
179 }
180
181 // "__Secure-" prefix requirements
182
2/2
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 71 times.
75x if(ci_starts_with(rs.name, "__Secure-"))
183 {
184
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4x if(!rs.secure)
185 2x return grammar::error::invalid;
186 }
187
188 // "__Host-" prefix requirements
189
2/2
✓ Branch 3 taken 6 times.
✓ Branch 4 taken 67 times.
73x if(ci_starts_with(rs.name, "__Host-"))
190 {
191
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6x if(!rs.secure)
192 return grammar::error::invalid;
193
194
8/8
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 2 times.
✓ Branch 4 taken 4 times.
✓ Branch 7 taken 4 times.
✓ Branch 9 taken 1 time.
✓ Branch 10 taken 3 times.
✓ Branch 11 taken 3 times.
✓ Branch 12 taken 3 times.
6x if(!rs.path || rs.path.value() != "/")
195 3x return grammar::error::invalid;
196
197
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 2 times.
3x if(rs.domain.has_value())
198 1x return grammar::error::invalid;
199 }
200
201 69x return rs;
202 78x }
203
204 } // namespace burl
205 } // namespace boost
206