src/cookie_jar.cpp

92.4% Lines (183/198) 100.0% List of functions (18/18) 85.6% Branches (190/222)
cookie_jar.cpp
f(x) Functions (18)
Function Calls Lines Branches Blocks
boost::burl::(anonymous namespace)::domain_match(boost::core::basic_string_view<char>, boost::core::basic_string_view<char>, bool) :35 66x 90.0% 87.5% 94.0% boost::burl::(anonymous namespace)::path_match(boost::core::basic_string_view<char>, boost::core::basic_string_view<char>) :58 56x 100.0% 100.0% 100.0% boost::burl::(anonymous namespace)::is_secure_context(boost::urls::url_view const&) :79 108x 83.3% 85.7% 78.0% boost::burl::(anonymous namespace)::normalize_host(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&) :99 129x 100.0% 100.0% 100.0% boost::burl::(anonymous namespace)::canonical_host(boost::urls::url_view const&) :110 114x 100.0% 100.0% 78.0% boost::burl::(anonymous namespace)::is_public_suffix(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) :118 15x 80.0% 66.7% 91.0% boost::burl::(anonymous namespace)::parse_netscape_cookie(boost::core::basic_string_view<char>) :133 8x 75.0% 66.7% 90.0% boost::burl::(anonymous namespace)::parse_netscape_cookie(boost::core::basic_string_view<char>)::{lambda(unsigned int)#1}::operator()(unsigned int) const :161 8x 100.0% 100.0% 100.0% boost::burl::cookie_jar::public_suffix_supported() :191 1x 100.0% 100.0% boost::burl::cookie_jar::add(boost::urls::url_view const&, boost::burl::cookie) :201 58x 96.9% 75.5% 73.0% boost::burl::cookie_jar::add(boost::urls::url_view const&, boost::burl::cookie)::{lambda(boost::burl::cookie const&)#1}::operator()(boost::burl::cookie const&) const :266 18x 72.7% 68.4% 100.0% boost::burl::cookie_jar::cookie_header[abi:cxx11](boost::urls::url_view const&) :289 56x 89.5% 93.1% 83.0% boost::burl::cookie_jar::cookie_header[abi:cxx11](boost::urls::url_view const&)::{lambda(boost::burl::cookie const*, boost::burl::cookie const*)#1}::operator()(boost::burl::cookie const*, boost::burl::cookie const*) const :323 11x 100.0% 100.0% 100.0% boost::burl::cookie_jar::clear() :340 1x 100.0% 100.0% boost::burl::cookie_jar::clear_session_cookies() :346 1x 100.0% 100.0% boost::burl::cookie_jar::clear_session_cookies()::{lambda(boost::burl::cookie const&)#1}::operator()(boost::burl::cookie const&) const :349 2x 100.0% 100.0% boost::burl::cookie_jar::to_netscape[abi:cxx11]() const :353 4x 92.0% 90.6% 76.0% boost::burl::cookie_jar::from_netscape(std::basic_string_view<char, std::char_traits<char> >) :390 7x 89.5% 81.0% 79.0%
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_jar.hpp>
11
12 #include <boost/url/grammar.hpp>
13 #include <boost/url/grammar/all_chars.hpp>
14
15 #include <algorithm>
16 #include <utility>
17 #include <vector>
18
19 #ifdef BOOST_BURL_HAS_LIBPSL
20 #include <libpsl.h>
21 #endif
22
23 namespace boost
24 {
25 namespace burl
26 {
27
28 namespace grammar = urls::grammar;
29 namespace ch = std::chrono;
30
31 namespace
32 {
33
34 bool
35 66x domain_match(
36 core::string_view host,
37 core::string_view domain,
38 bool tailmatch) noexcept
39 {
40
2/2
✓ Branch 0 taken 46 times.
✓ Branch 1 taken 20 times.
66x if(!tailmatch)
41 46x return host == domain;
42
43
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 20 times.
20x if(domain.starts_with('.'))
44 domain.remove_prefix(1);
45
46
2/2
✓ Branch 1 taken 19 times.
✓ Branch 2 taken 1 time.
20x if(host.ends_with(domain))
47 {
48
2/2
✓ Branch 2 taken 7 times.
✓ Branch 3 taken 12 times.
19x if(host.size() == domain.size())
49 7x return true;
50
51 12x return host[host.size() - domain.size() - 1] == '.';
52 }
53
54 1x return false;
55 }
56
57 bool
58 56x path_match(core::string_view r_path, core::string_view c_path) noexcept
59 {
60 // RFC 6265 5.1.4: an empty request path defaults to "/"
61
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 54 times.
56x if(r_path.empty())
62 2x r_path = "/";
63
64
2/2
✓ Branch 1 taken 55 times.
✓ Branch 2 taken 1 time.
56x if(r_path.starts_with(c_path))
65 {
66
2/2
✓ Branch 2 taken 44 times.
✓ Branch 3 taken 11 times.
55x if(r_path.size() == c_path.size())
67 44x return true;
68
69
2/2
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 3 times.
11x if(c_path.ends_with('/'))
70 8x return true;
71
72 3x return r_path[c_path.size()] == '/';
73 }
74
75 1x return false;
76 }
77
78 bool
79 108x is_secure_context(const urls::url_view& url)
80 {
81
2/2
✓ Branch 1 taken 64 times.
✓ Branch 2 taken 44 times.
108x if(url.scheme_id() == urls::scheme::https)
82 64x return true;
83
84 // localhost and the loopback ranges are trustworthy without TLS
85
3/4
✓ Branch 1 taken 25 times.
✓ Branch 2 taken 13 times.
✓ Branch 3 taken 6 times.
✗ Branch 4 not taken.
44x switch(url.host_type())
86 {
87 25x case urls::host_type::name:
88
1/1
✓ Branch 3 taken 25 times.
25x return grammar::ci_is_equal(url.host_address(), "localhost");
89 13x case urls::host_type::ipv4:
90 13x return url.host_ipv4_address().is_loopback();
91 6x case urls::host_type::ipv6:
92 6x return url.host_ipv6_address().is_loopback();
93 default:
94 return false;
95 }
96 }
97
98 void
99 129x normalize_host(std::string& host)
100 {
101 // a trailing dot denotes the same host (CVE-2022-27779)
102
2/2
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 122 times.
129x if(host.ends_with('.'))
103 7x host.pop_back();
104
105
2/2
✓ Branch 4 taken 1315 times.
✓ Branch 5 taken 129 times.
1444x for(auto& ch : host)
106 1315x ch = grammar::to_lower(ch);
107 129x }
108
109 std::string
110 114x canonical_host(const urls::url_view& url)
111 {
112
1/1
✓ Branch 2 taken 114 times.
114x auto ret = url.host_address();
113 114x normalize_host(ret);
114 114x return ret;
115 }
116
117 bool
118 15x is_public_suffix(const std::string& domain) noexcept
119 {
120 #ifdef BOOST_BURL_HAS_LIBPSL
121 return psl_is_public_suffix(psl_builtin(), domain.c_str());
122 #else
123 // weak heuristic:
124 // treat bare TLDs (single-label domains) as public suffixes
125
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
15x if(domain == "localhost")
126 return false;
127 15x const auto pos = domain.find('.');
128
3/4
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 5 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 10 times.
15x return pos == std::string::npos || domain.size() - pos <= 1;
129 #endif
130 }
131
132 system::result<cookie>
133 8x parse_netscape_cookie(core::string_view sv)
134 {
135 static constexpr auto field_chars =
136 grammar::all_chars - grammar::lut_chars{ "\t" };
137
138 static constexpr auto netscape_parser = grammar::tuple_rule(
139 grammar::optional_rule(grammar::literal_rule("#HttpOnly_")),
140 grammar::token_rule(field_chars),
141 grammar::squelch(grammar::delim_rule('\t')),
142 grammar::variant_rule(
143 grammar::literal_rule("FALSE"), grammar::literal_rule("TRUE")),
144 grammar::squelch(grammar::delim_rule('\t')),
145 grammar::token_rule(field_chars),
146 grammar::squelch(grammar::delim_rule('\t')),
147 grammar::variant_rule(
148 grammar::literal_rule("FALSE"), grammar::literal_rule("TRUE")),
149 grammar::squelch(grammar::delim_rule('\t')),
150 grammar::unsigned_rule<std::uint32_t>(),
151 grammar::squelch(grammar::delim_rule('\t')),
152 grammar::token_rule(field_chars),
153 grammar::squelch(grammar::delim_rule('\t')),
154 grammar::optional_rule(grammar::token_rule(field_chars)));
155
156
1/1
✓ Branch 1 taken 8 times.
8x const auto parse_rs = grammar::parse(sv, netscape_parser);
157
158
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
8x if(parse_rs.has_error())
159 return parse_rs.error();
160
161 8x auto epoch_to_expiry = [](std::uint32_t epoch)
162 -> std::optional<ch::system_clock::time_point>
163 {
164
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 2 times.
8x if(epoch == 0)
165 6x return std::nullopt;
166
167 2x return ch::system_clock::from_time_t(static_cast<std::time_t>(epoch));
168 };
169
170 8x auto rs = cookie{};
171
1/1
✓ Branch 1 taken 8 times.
8x rs.http_only = std::get<0>(*parse_rs).has_value();
172
2/2
✓ Branch 1 taken 8 times.
✓ Branch 5 taken 8 times.
8x rs.domain = std::get<1>(*parse_rs);
173 // curl convention: a leading dot also marks the cookie tailmatch; strip it
174
1/1
✓ Branch 1 taken 8 times.
8x auto& dom = rs.domain.value();
175 8x const bool leading_dot = dom.starts_with('.');
176
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 6 times.
8x if(leading_dot)
177
1/1
✓ Branch 1 taken 2 times.
2x dom.erase(0, 1);
178
5/5
✓ Branch 1 taken 8 times.
✓ Branch 5 taken 5 times.
✓ Branch 6 taken 3 times.
✓ Branch 7 taken 1 time.
✓ Branch 8 taken 4 times.
8x rs.tailmatch = std::get<2>(*parse_rs).index() || leading_dot;
179
2/2
✓ Branch 1 taken 8 times.
✓ Branch 5 taken 8 times.
8x rs.path = std::get<3>(*parse_rs);
180
1/1
✓ Branch 1 taken 8 times.
8x rs.secure = std::get<4>(*parse_rs).index();
181
1/1
✓ Branch 1 taken 8 times.
8x rs.expires = epoch_to_expiry(std::get<5>(*parse_rs));
182
2/2
✓ Branch 1 taken 8 times.
✓ Branch 5 taken 8 times.
8x rs.name = std::get<6>(*parse_rs);
183 // an empty value field denotes a value-less cookie (e.g. "name=")
184
3/3
✓ Branch 1 taken 8 times.
✓ Branch 5 taken 7 times.
✓ Branch 6 taken 1 time.
8x if(auto& value = std::get<7>(*parse_rs))
185
2/2
✓ Branch 1 taken 7 times.
✓ Branch 4 taken 7 times.
7x rs.value = *value;
186 8x return rs;
187 8x }
188 } // namespace
189
190 bool
191 1x cookie_jar::public_suffix_supported() noexcept
192 {
193 #ifdef BOOST_BURL_HAS_LIBPSL
194 return true;
195 #else
196 1x return false;
197 #endif
198 }
199
200 void
201 58x cookie_jar::add(const urls::url_view& url, cookie c)
202 {
203 58x const auto r_host_is_name = url.host_type() == urls::host_type::name;
204
1/1
✓ Branch 1 taken 58 times.
58x const auto r_host = canonical_host(url);
205
206
2/2
✓ Branch 1 taken 15 times.
✓ Branch 2 taken 43 times.
58x if(c.domain.has_value())
207 {
208
1/1
✓ Branch 1 taken 15 times.
15x auto& c_domain = c.domain.value();
209 15x normalize_host(c_domain);
210
211 // RFC 6265 5.2.3: a leading dot in the Domain attribute is ignored
212
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 12 times.
15x if(c_domain.starts_with('.'))
213
1/1
✓ Branch 1 taken 3 times.
3x c_domain.erase(0, 1);
214
215
2/2
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 10 times.
15x if(is_public_suffix(c_domain))
216 {
217 // RFC 6265 5.3 step 5: a public-suffix Domain is rejected, unless
218 // it equals the request host, which makes the cookie host-only.
219
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 1 time.
5x if(c_domain != r_host)
220 4x return;
221 1x c.tailmatch = false;
222 }
223
2/2
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 8 times.
10x else if(!domain_match(r_host, c_domain, r_host_is_name))
224 {
225 2x return;
226 }
227 else
228 {
229 8x c.tailmatch = r_host_is_name;
230 }
231 }
232 else
233 {
234
1/1
✓ Branch 2 taken 43 times.
43x c.domain.emplace(std::move(r_host));
235 }
236
237
2/2
✓ Branch 1 taken 46 times.
✓ Branch 2 taken 6 times.
52x if(!c.path.has_value())
238 {
239 46x core::string_view p = url.encoded_path();
240 46x auto pos = p.rfind('/');
241
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 46 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
46x if(pos == 0 || pos == core::string_view::npos)
242
1/1
✓ Branch 1 taken 46 times.
46x c.path = "/";
243 else
244 c.path = { p.substr(0, pos) };
245 }
246
247
3/3
✓ Branch 1 taken 52 times.
✓ Branch 3 taken 12 times.
✓ Branch 4 taken 40 times.
52x if(!is_secure_context(url))
248 {
249
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 10 times.
12x if(c.secure)
250 2x return;
251
252 // RFC 6265bis: an insecure response must not overwrite a Secure cookie.
253
2/2
✓ Branch 5 taken 3 times.
✓ Branch 6 taken 8 times.
11x for(const auto& o : cookies_)
254 {
255
2/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
4x if(o.secure && c.name == o.name &&
256
2/4
✓ Branch 1 taken 2 times.
✓ Branch 5 taken 2 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
2x (domain_match(c.domain.value(), o.domain.value(), true) ||
257
3/6
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 time.
✗ Branch 3 not taken.
✗ Branch 7 not taken.
✓ Branch 11 taken 2 times.
✗ Branch 12 not taken.
5x domain_match(o.domain.value(), c.domain.value(), true)) &&
258
4/4
✓ Branch 1 taken 2 times.
✓ Branch 5 taken 2 times.
✓ Branch 9 taken 2 times.
✓ Branch 10 taken 1 time.
5x path_match(c.path.value(), o.path.value()))
259 2x return;
260 }
261 }
262
263
1/1
✓ Branch 3 taken 48 times.
48x auto it = std::find_if(
264 cookies_.begin(),
265 cookies_.end(),
266 18x [&](const cookie& o)
267 {
268
4/6
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 13 times.
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 5 times.
✗ Branch 7 not taken.
23x return c.name == o.name && c.path == o.path &&
269 23x c.domain == o.domain;
270 });
271
272 // Check expiry date last to allow servers to remove cookies
273
6/8
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 45 times.
✓ Branch 5 taken 3 times.
✓ Branch 8 taken 3 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 3 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 48 times.
48x if(c.expires.has_value() && c.expires.value() <= ch::system_clock::now())
274 {
275 if(it != cookies_.end())
276 cookies_.erase(it);
277 return;
278 }
279
280 // RFC 6265bis 5.3: replacing keeps the old cookie's position so creation
281 // order (used for header ordering) is retained.
282
2/2
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 43 times.
48x if(it != cookies_.end())
283 5x *it = std::move(c);
284 else
285
1/1
✓ Branch 2 taken 43 times.
43x cookies_.push_back(std::move(c));
286 58x }
287
288 std::string
289 56x cookie_jar::cookie_header(const urls::url_view& url)
290 {
291 56x const auto r_host_is_name = url.host_type() == urls::host_type::name;
292
1/1
✓ Branch 1 taken 56 times.
56x const auto r_host = canonical_host(url);
293 56x const auto r_path = url.encoded_path();
294
1/1
✓ Branch 1 taken 56 times.
56x const auto r_is_secure = is_secure_context(url);
295 56x const auto now = ch::system_clock::now();
296
297 56x auto matched = std::vector<const cookie*>{};
298
2/2
✓ Branch 3 taken 54 times.
✓ Branch 4 taken 56 times.
110x for(auto it = cookies_.begin(); it != cookies_.end();)
299 {
300
5/7
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 49 times.
✓ Branch 6 taken 5 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 5 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 54 times.
54x if(it->expires.has_value() && it->expires <= now)
301 {
302 it = cookies_.erase(it);
303 continue;
304 }
305
306
1/1
✓ Branch 2 taken 54 times.
54x bool const path_ok = path_match(r_path, it->path.value());
307 54x bool const domain_ok = domain_match(
308 r_host,
309
1/1
✓ Branch 2 taken 54 times.
54x it->domain.value(),
310
4/4
✓ Branch 1 taken 11 times.
✓ Branch 2 taken 43 times.
✓ Branch 3 taken 10 times.
✓ Branch 4 taken 1 time.
54x it->tailmatch && r_host_is_name);
311
312
10/10
✓ Branch 0 taken 50 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 48 times.
✓ Branch 3 taken 2 times.
✓ Branch 5 taken 9 times.
✓ Branch 6 taken 39 times.
✓ Branch 7 taken 8 times.
✓ Branch 8 taken 1 time.
✓ Branch 9 taken 47 times.
✓ Branch 10 taken 7 times.
54x if(domain_ok && path_ok && (!it->secure || r_is_secure))
313
1/1
✓ Branch 2 taken 47 times.
47x matched.push_back(&*it);
314
315 54x ++it;
316 }
317
318 // RFC 6265 5.4: longer paths first; stable_sort keeps creation order as
319 // the tiebreaker.
320
1/1
✓ Branch 3 taken 56 times.
56x std::stable_sort(
321 matched.begin(),
322 matched.end(),
323 11x [](const cookie* a, const cookie* b)
324 11x { return a->path->size() > b->path->size(); });
325
326 56x auto rs = std::string{};
327
2/2
✓ Branch 5 taken 47 times.
✓ Branch 6 taken 56 times.
103x for(const auto* c : matched)
328 {
329
2/2
✓ Branch 1 taken 9 times.
✓ Branch 2 taken 38 times.
47x if(!rs.empty())
330
1/1
✓ Branch 1 taken 9 times.
9x rs.append("; ");
331
1/1
✓ Branch 1 taken 47 times.
47x rs.append(c->name);
332
1/1
✓ Branch 1 taken 47 times.
47x rs.push_back('=');
333
2/2
✓ Branch 1 taken 46 times.
✓ Branch 2 taken 1 time.
47x if(c->value.has_value())
334
1/1
✓ Branch 2 taken 46 times.
46x rs.append(*c->value);
335 }
336 112x return rs;
337 56x }
338
339 void
340 1x cookie_jar::clear()
341 {
342 1x cookies_.clear();
343 1x }
344
345 void
346 1x cookie_jar::clear_session_cookies()
347 {
348 1x cookies_.remove_if(
349 2x [](const cookie& c) { return !c.expires.has_value(); });
350 1x }
351
352 std::string
353 4x cookie_jar::to_netscape() const
354 {
355
1/1
✓ Branch 1 taken 4 times.
4x auto rs = std::string{ "# Netscape HTTP Cookie File\n\n" };
356
357
2/2
✓ Branch 5 taken 5 times.
✓ Branch 6 taken 4 times.
9x for(const auto& c : cookies_)
358 {
359
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5x if(c.http_only)
360 rs += "#HttpOnly_";
361
2/2
✓ Branch 1 taken 5 times.
✓ Branch 4 taken 5 times.
5x rs += c.domain.value();
362
1/1
✓ Branch 1 taken 5 times.
5x rs += '\t';
363
3/3
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 4 times.
✓ Branch 3 taken 5 times.
5x rs += c.tailmatch ? "TRUE" : "FALSE";
364
1/1
✓ Branch 1 taken 5 times.
5x rs += '\t';
365
2/2
✓ Branch 1 taken 5 times.
✓ Branch 4 taken 5 times.
5x rs += c.path.value();
366
1/1
✓ Branch 1 taken 5 times.
5x rs += '\t';
367
2/3
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
✓ Branch 3 taken 5 times.
5x rs += c.secure ? "TRUE" : "FALSE";
368
1/1
✓ Branch 1 taken 5 times.
5x rs += '\t';
369
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 3 times.
5x if(c.expires)
370 {
371
1/1
✓ Branch 2 taken 2 times.
4x rs += std::to_string(
372
1/1
✓ Branch 1 taken 2 times.
2x ch::duration_cast<ch::seconds>(
373
1/1
✓ Branch 3 taken 2 times.
6x c.expires->time_since_epoch()).count());
374 }
375 else
376 {
377
1/1
✓ Branch 1 taken 3 times.
3x rs += '0';
378 }
379
1/1
✓ Branch 1 taken 5 times.
5x rs += '\t';
380
1/1
✓ Branch 1 taken 5 times.
5x rs += c.name;
381
1/1
✓ Branch 1 taken 5 times.
5x rs += '\t';
382
2/2
✓ Branch 1 taken 5 times.
✓ Branch 4 taken 5 times.
5x rs += c.value.value_or("");
383
1/1
✓ Branch 1 taken 5 times.
5x rs += '\n';
384 }
385
386 4x return rs;
387 }
388
389 system::result<void>
390 7x cookie_jar::from_netscape(std::string_view sv)
391 {
392
2/2
✓ Branch 1 taken 22 times.
✓ Branch 2 taken 7 times.
29x while(!sv.empty())
393 {
394 22x const auto nl = sv.find('\n');
395
1/1
✓ Branch 1 taken 22 times.
22x auto line = sv.substr(0, nl);
396 22x sv = nl == std::string_view::npos
397
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
22x ? std::string_view{}
398
1/1
✓ Branch 1 taken 22 times.
22x : sv.substr(nl + 1);
399
400 // tolerate CRLF line endings
401
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 22 times.
22x if(line.ends_with('\r'))
402 line.remove_suffix(1);
403
404
2/2
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 15 times.
22x if(line.empty())
405 14x continue;
406
407 // skip comments
408
5/6
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 8 times.
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 7 times.
✓ Branch 7 taken 8 times.
15x if(line.starts_with('#') && !line.starts_with("#HttpOnly_"))
409 7x continue;
410
411
1/1
✓ Branch 2 taken 8 times.
8x auto rc = parse_netscape_cookie(line);
412
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
8x if(rc.has_error())
413 return rc.error();
414
2/2
✓ Branch 1 taken 8 times.
✓ Branch 5 taken 8 times.
8x cookies_.push_back(std::move(*rc));
415 8x }
416 7x return {};
417 }
418
419 } // namespace burl
420 } // namespace boost
421