src/cookie_jar.cpp

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