libs/http/src/server/accepts.cpp

86.1% Lines (272/316) 100.0% Functions (24/24) 71.9% Branches (194/270)
libs/http/src/server/accepts.cpp
Line Branch Hits Source Code
1 //
2 // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
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/http
8 //
9
10 #include <boost/http/server/accepts.hpp>
11 #include <boost/http/server/mime_types.hpp>
12 #include <boost/http/field.hpp>
13 #include <algorithm>
14
15 namespace boost {
16 namespace http {
17
18 namespace {
19
20 //----------------------------------------------------------
21 // Helpers
22 //----------------------------------------------------------
23
24 std::string_view
25 142 trim_ows( std::string_view s ) noexcept
26 {
27
3/4
✓ Branch 1 taken 169 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 27 times.
✓ Branch 4 taken 142 times.
338 while( ! s.empty() &&
28
3/4
✓ Branch 1 taken 142 times.
✓ Branch 2 taken 27 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 142 times.
169 ( s.front() == ' ' || s.front() == '\t' ) )
29 27 s.remove_prefix( 1 );
30
2/4
✓ Branch 1 taken 142 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 142 times.
284 while( ! s.empty() &&
31
2/4
✓ Branch 1 taken 142 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 142 times.
142 ( s.back() == ' ' || s.back() == '\t' ) )
32 s.remove_suffix( 1 );
33 142 return s;
34 }
35
36 bool
37 75 iequals(
38 std::string_view a,
39 std::string_view b ) noexcept
40 {
41
2/2
✓ Branch 2 taken 26 times.
✓ Branch 3 taken 49 times.
75 if( a.size() != b.size() )
42 26 return false;
43
2/2
✓ Branch 1 taken 174 times.
✓ Branch 2 taken 28 times.
202 for( std::size_t i = 0; i < a.size(); ++i )
44 {
45 174 unsigned char ca = a[i];
46 174 unsigned char cb = b[i];
47
3/4
✓ Branch 0 taken 155 times.
✓ Branch 1 taken 19 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 155 times.
174 if( ca >= 'A' && ca <= 'Z' )
48 ca += 32;
49
3/4
✓ Branch 0 taken 155 times.
✓ Branch 1 taken 19 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 155 times.
174 if( cb >= 'A' && cb <= 'Z' )
50 cb += 32;
51
2/2
✓ Branch 0 taken 21 times.
✓ Branch 1 taken 153 times.
174 if( ca != cb )
52 21 return false;
53 }
54 28 return true;
55 }
56
57 // Returns quality as integer 0-1000
58 int
59 16 parse_q( std::string_view s ) noexcept
60 {
61 16 s = trim_ows( s );
62
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
16 if( s.empty() )
63 return 1000;
64
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
16 if( s[0] == '1' )
65 return 1000;
66
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 15 times.
16 if( s[0] != '0' )
67 1 return 0;
68
5/6
✓ Branch 1 taken 14 times.
✓ Branch 2 taken 1 time.
✗ Branch 4 not taken.
✓ Branch 5 taken 14 times.
✓ Branch 6 taken 1 time.
✓ Branch 7 taken 14 times.
15 if( s.size() < 2 || s[1] != '.' )
69 1 return 0;
70 14 int result = 0;
71 14 int mult = 100;
72 14 for( std::size_t i = 2;
73
5/6
✓ Branch 1 taken 14 times.
✓ Branch 2 taken 14 times.
✓ Branch 3 taken 14 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 14 times.
✓ Branch 6 taken 14 times.
28 i < s.size() && i < 5; ++i )
74 {
75
3/6
✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 14 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 14 times.
14 if( s[i] < '0' || s[i] > '9' )
76 break;
77 14 result += ( s[i] - '0' ) * mult;
78 14 mult /= 10;
79 }
80 14 return result;
81 }
82
83 // Extract q-value from parameters after first semicolon
84 int
85 16 extract_q( std::string_view params ) noexcept
86 {
87
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
16 while( ! params.empty() )
88 {
89 16 auto semi = params.find( ';' );
90
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 auto param = trim_ows(
91 semi != std::string_view::npos
92 ? params.substr( 0, semi )
93 : params );
94 16 if( param.size() >= 2 &&
95
3/8
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 16 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 16 times.
✗ Branch 9 not taken.
32 ( param[0] == 'q' || param[0] == 'Q' ) &&
96
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
16 param[1] == '=' )
97 {
98 16 return parse_q( param.substr( 2 ) );
99 }
100 if( semi != std::string_view::npos )
101 params.remove_prefix( semi + 1 );
102 else
103 break;
104 }
105 return 1000;
106 }
107
108 //----------------------------------------------------------
109 // Negotiation priority
110 //----------------------------------------------------------
111
112 struct priority
113 {
114 int q;
115 int specificity;
116 int order;
117 };
118
119 bool
120 6 is_better(
121 priority const& a,
122 priority const& b ) noexcept
123 {
124
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 2 times.
6 if( a.q != b.q )
125 4 return a.q > b.q;
126
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
2 if( a.specificity != b.specificity )
127 1 return a.specificity > b.specificity;
128 1 return a.order < b.order;
129 }
130
131 //----------------------------------------------------------
132 // Media type parsing (Accept header)
133 //----------------------------------------------------------
134
135 struct media_range
136 {
137 std::string_view type;
138 std::string_view subtype;
139 std::string_view full;
140 int q;
141 int order;
142 };
143
144 std::vector<media_range>
145 13 parse_accept( std::string_view header )
146 {
147 13 std::vector<media_range> result;
148 13 int order = 0;
149
150
2/2
✓ Branch 1 taken 22 times.
✓ Branch 2 taken 13 times.
35 while( ! header.empty() )
151 {
152 22 auto comma = header.find( ',' );
153 auto entry = ( comma != std::string_view::npos )
154
3/3
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 13 times.
✓ Branch 3 taken 9 times.
22 ? header.substr( 0, comma )
155 13 : header;
156
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 13 times.
22 if( comma != std::string_view::npos )
157 9 header.remove_prefix( comma + 1 );
158 else
159 13 header = {};
160
161 22 entry = trim_ows( entry );
162
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 22 times.
22 if( entry.empty() )
163 continue;
164
165 22 auto semi = entry.find( ';' );
166
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 18 times.
26 auto mime_part = trim_ows(
167 semi != std::string_view::npos
168
1/1
✓ Branch 1 taken 4 times.
4 ? entry.substr( 0, semi )
169 : entry );
170
171 22 auto slash = mime_part.find( '/' );
172
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
22 if( slash == std::string_view::npos )
173 continue;
174
175 22 media_range mr;
176
1/1
✓ Branch 1 taken 22 times.
22 mr.type = mime_part.substr( 0, slash );
177
1/1
✓ Branch 1 taken 22 times.
22 mr.subtype = mime_part.substr( slash + 1 );
178 22 mr.full = mime_part;
179 22 mr.q = ( semi != std::string_view::npos )
180
3/3
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 18 times.
✓ Branch 3 taken 4 times.
22 ? extract_q( entry.substr( semi + 1 ) )
181 : 1000;
182 22 mr.order = order++;
183
1/1
✓ Branch 1 taken 22 times.
22 result.push_back( mr );
184 }
185
186 13 return result;
187 }
188
189 // Returns specificity (0-6) or -1 for no match
190 int
191 16 match_media(
192 media_range const& range,
193 std::string_view type,
194 std::string_view subtype ) noexcept
195 {
196 16 int s = 0;
197
198
2/2
✓ Branch 2 taken 15 times.
✓ Branch 3 taken 1 time.
16 if( range.type == "*" )
199 {
200 // wildcard type
201 }
202
2/2
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 8 times.
15 else if( iequals( range.type, type ) )
203 {
204 7 s |= 4;
205 }
206 else
207 {
208 8 return -1;
209 }
210
211
2/2
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 3 times.
8 if( range.subtype == "*" )
212 {
213 // wildcard subtype
214 }
215
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 else if( iequals( range.subtype, subtype ) )
216 {
217 5 s |= 2;
218 }
219 else
220 {
221 return -1;
222 }
223
224 8 return s;
225 }
226
227 //----------------------------------------------------------
228 // Simple token parsing (Accept-Encoding/Charset/Language)
229 //----------------------------------------------------------
230
231 struct simple_entry
232 {
233 std::string_view value;
234 int q;
235 int order;
236 };
237
238 std::vector<simple_entry>
239 15 parse_simple( std::string_view header )
240 {
241 15 std::vector<simple_entry> result;
242 15 int order = 0;
243
244
2/2
✓ Branch 1 taken 33 times.
✓ Branch 2 taken 15 times.
48 while( ! header.empty() )
245 {
246 33 auto comma = header.find( ',' );
247 auto entry = ( comma != std::string_view::npos )
248
3/3
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 15 times.
✓ Branch 3 taken 18 times.
33 ? header.substr( 0, comma )
249 15 : header;
250
2/2
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 15 times.
33 if( comma != std::string_view::npos )
251 18 header.remove_prefix( comma + 1 );
252 else
253 15 header = {};
254
255 33 entry = trim_ows( entry );
256
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 33 times.
33 if( entry.empty() )
257 continue;
258
259 33 auto semi = entry.find( ';' );
260
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 21 times.
45 auto value = trim_ows(
261 semi != std::string_view::npos
262
1/1
✓ Branch 1 taken 12 times.
12 ? entry.substr( 0, semi )
263 : entry );
264
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 33 times.
33 if( value.empty() )
265 continue;
266
267 33 simple_entry se;
268 33 se.value = value;
269 33 se.q = ( semi != std::string_view::npos )
270
3/3
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 21 times.
✓ Branch 3 taken 12 times.
33 ? extract_q( entry.substr( semi + 1 ) )
271 : 1000;
272 33 se.order = order++;
273
1/1
✓ Branch 1 taken 33 times.
33 result.push_back( se );
274 }
275
276 15 return result;
277 }
278
279 //----------------------------------------------------------
280 // Matching helpers
281 //----------------------------------------------------------
282
283 // Exact or wildcard match (encoding, charset)
284 int
285 25 match_exact(
286 std::string_view spec,
287 std::string_view offered ) noexcept
288 {
289
2/2
✓ Branch 1 taken 9 times.
✓ Branch 2 taken 16 times.
25 if( iequals( spec, offered ) )
290 9 return 1;
291
2/2
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 15 times.
16 if( spec == "*" )
292 1 return 0;
293 15 return -1;
294 }
295
296 // Language prefix: "en-US" -> "en"
297 std::string_view
298 17 lang_prefix( std::string_view tag ) noexcept
299 {
300 17 auto dash = tag.find( '-' );
301
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 14 times.
17 if( dash != std::string_view::npos )
302 3 return tag.substr( 0, dash );
303 14 return tag;
304 }
305
306 // Language match with prefix support
307 int
308 13 match_language(
309 std::string_view spec,
310 std::string_view offered ) noexcept
311 {
312
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 9 times.
13 if( iequals( spec, offered ) )
313 4 return 4;
314
2/2
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 8 times.
9 if( iequals( lang_prefix( spec ), offered ) )
315 1 return 2;
316
2/2
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 6 times.
8 if( iequals( spec, lang_prefix( offered ) ) )
317 2 return 1;
318
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
6 if( spec == "*" )
319 return 0;
320 6 return -1;
321 }
322
323 //----------------------------------------------------------
324 // Generic negotiation for simple headers
325 //----------------------------------------------------------
326
327 template< class MatchFn >
328 std::string_view
329 12 negotiate(
330 std::vector<simple_entry> const& entries,
331 std::initializer_list<std::string_view> offered,
332 MatchFn match )
333 {
334 12 std::string_view best_val;
335 12 priority best_pri{ -1, -1, 0 };
336 12 bool found = false;
337
338
2/2
✓ Branch 2 taken 18 times.
✓ Branch 3 taken 12 times.
30 for( auto const& o : offered )
339 {
340 18 priority pri{ -1, -1, 0 };
341 18 bool matched = false;
342
343
2/2
✓ Branch 5 taken 38 times.
✓ Branch 6 taken 18 times.
56 for( auto const& e : entries )
344 {
345
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 38 times.
38 if( e.q <= 0 )
346 21 continue;
347 38 auto s = match( e.value, o );
348
2/2
✓ Branch 0 taken 21 times.
✓ Branch 1 taken 17 times.
38 if( s < 0 )
349 21 continue;
350 17 priority p{ e.q, s, e.order };
351
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 17 times.
17 if( ! matched ||
352 p.specificity > pri.specificity ||
353 ( p.specificity == pri.specificity &&
354 p.q > pri.q ) ||
355 ( p.specificity == pri.specificity &&
356 p.q == pri.q &&
357 p.order < pri.order ) )
358 {
359 17 pri = p;
360 17 matched = true;
361 }
362 }
363
364
3/4
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
✓ Branch 3 taken 17 times.
18 if( ! matched || pri.q <= 0 )
365 1 continue;
366
367
6/6
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 11 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 4 times.
✓ Branch 5 taken 13 times.
✓ Branch 6 taken 4 times.
17 if( ! found || is_better( pri, best_pri ) )
368 {
369 13 best_val = o;
370 13 best_pri = pri;
371 13 found = true;
372 }
373 }
374
375
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 1 time.
12 return found ? best_val : std::string_view{};
376 }
377
378 // Return sorted values from simple entries
379 std::vector<std::string_view>
380 3 sorted_values(
381 std::vector<simple_entry>& entries )
382 {
383 3 std::sort( entries.begin(), entries.end(),
384 13 []( simple_entry const& a,
385 simple_entry const& b )
386 {
387
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 2 times.
13 if( a.q != b.q )
388 11 return a.q > b.q;
389 2 return a.order < b.order;
390 });
391
392 3 std::vector<std::string_view> result;
393
1/1
✓ Branch 2 taken 3 times.
3 result.reserve( entries.size() );
394
2/2
✓ Branch 5 taken 9 times.
✓ Branch 6 taken 3 times.
12 for( auto const& e : entries )
395 {
396
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 if( e.q <= 0 )
397 continue;
398
1/1
✓ Branch 1 taken 9 times.
9 result.push_back( e.value );
399 }
400 3 return result;
401 }
402
403 } // (anon)
404
405 //----------------------------------------------------------
406
407 26 accepts::accepts(
408 26 fields_base const& fields ) noexcept
409 26 : fields_( fields )
410 {
411 26 }
412
413 std::string_view
414 12 accepts::type(
415 std::initializer_list<
416 std::string_view> offered ) const
417 {
418
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 11 times.
12 if( offered.size() == 0 )
419 1 return {};
420
421 11 auto accept = fields_.value_or(
422 field::accept, "" );
423
424
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 10 times.
11 if( accept.empty() )
425 1 return *offered.begin();
426
427
1/1
✓ Branch 2 taken 10 times.
10 auto ranges = parse_accept( accept );
428
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
10 if( ranges.empty() )
429 return *offered.begin();
430
431 10 std::string_view best_val;
432 10 priority best_pri{ -1, -1, 0 };
433 10 bool found = false;
434
435
2/2
✓ Branch 2 taken 12 times.
✓ Branch 3 taken 10 times.
22 for( auto const& o : offered )
436 {
437 // Convert extension to MIME if needed
438 12 std::string_view mime_str = o;
439
2/2
✓ Branch 1 taken 9 times.
✓ Branch 2 taken 3 times.
12 if( o.find( '/' ) == std::string_view::npos )
440 {
441 9 auto looked = mime_types::lookup( o );
442
2/2
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 1 time.
9 if( ! looked.empty() )
443 8 mime_str = looked;
444 else
445 1 continue;
446 }
447
448 11 auto slash = mime_str.find( '/' );
449
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 11 times.
11 if( slash == std::string_view::npos )
450 continue;
451
452
1/1
✓ Branch 1 taken 11 times.
11 auto type = mime_str.substr( 0, slash );
453
1/1
✓ Branch 1 taken 11 times.
11 auto subtype = mime_str.substr( slash + 1 );
454
455 // Find best matching range for this type
456 11 priority pri{ -1, -1, 0 };
457 11 bool matched = false;
458
459
2/2
✓ Branch 5 taken 18 times.
✓ Branch 6 taken 11 times.
29 for( auto const& r : ranges )
460 {
461
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 16 times.
18 if( r.q <= 0 )
462 10 continue;
463 16 auto s = match_media( r, type, subtype );
464
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 8 times.
16 if( s < 0 )
465 8 continue;
466 8 priority p{ r.q, s, r.order };
467
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if( ! matched ||
468 p.specificity > pri.specificity ||
469 ( p.specificity == pri.specificity &&
470 p.q > pri.q ) ||
471 ( p.specificity == pri.specificity &&
472 p.q == pri.q &&
473 p.order < pri.order ) )
474 {
475 8 pri = p;
476 8 matched = true;
477 }
478 }
479
480
3/4
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
11 if( ! matched || pri.q <= 0 )
481 3 continue;
482
483
2/6
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 8 times.
✗ Branch 6 not taken.
8 if( ! found || is_better( pri, best_pri ) )
484 {
485 8 best_val = o;
486 8 best_pri = pri;
487 8 found = true;
488 }
489 }
490
491
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 2 times.
10 return found ? best_val : std::string_view{};
492 10 }
493
494 std::vector<std::string_view>
495 4 accepts::types() const
496 {
497 4 auto accept = fields_.value_or(
498 field::accept, "" );
499
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 3 times.
4 if( accept.empty() )
500 1 return {};
501
502
1/1
✓ Branch 2 taken 3 times.
3 auto ranges = parse_accept( accept );
503
504
1/1
✓ Branch 3 taken 3 times.
3 std::sort( ranges.begin(), ranges.end(),
505 6 []( media_range const& a,
506 media_range const& b )
507 {
508
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 if( a.q != b.q )
509 6 return a.q > b.q;
510 return a.order < b.order;
511 });
512
513 3 std::vector<std::string_view> result;
514
1/1
✓ Branch 2 taken 3 times.
3 result.reserve( ranges.size() );
515
2/2
✓ Branch 5 taken 6 times.
✓ Branch 6 taken 3 times.
9 for( auto const& r : ranges )
516 {
517
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 5 times.
6 if( r.q <= 0 )
518 1 continue;
519
1/1
✓ Branch 1 taken 5 times.
5 result.push_back( r.full );
520 }
521 3 return result;
522 3 }
523
524 std::string_view
525 6 accepts::encoding(
526 std::initializer_list<
527 std::string_view> offered ) const
528 {
529
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
6 if( offered.size() == 0 )
530 return {};
531
532 6 auto header = fields_.value_or(
533 field::accept_encoding, "" );
534
535
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 5 times.
6 if( header.empty() )
536 1 return *offered.begin();
537
538
1/1
✓ Branch 2 taken 5 times.
5 auto entries = parse_simple( header );
539
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
5 if( entries.empty() )
540 return *offered.begin();
541
542 5 return negotiate( entries, offered, match_exact );
543 5 }
544
545 std::vector<std::string_view>
546 2 accepts::encodings() const
547 {
548 2 auto header = fields_.value_or(
549 field::accept_encoding, "" );
550
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
2 if( header.empty() )
551 1 return {};
552
553
1/1
✓ Branch 2 taken 1 time.
1 auto entries = parse_simple( header );
554
1/1
✓ Branch 1 taken 1 time.
1 return sorted_values( entries );
555 1 }
556
557 std::string_view
558 3 accepts::charset(
559 std::initializer_list<
560 std::string_view> offered ) const
561 {
562
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
3 if( offered.size() == 0 )
563 return {};
564
565 3 auto header = fields_.value_or(
566 field::accept_charset, "" );
567
568
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 2 times.
3 if( header.empty() )
569 1 return *offered.begin();
570
571
1/1
✓ Branch 2 taken 2 times.
2 auto entries = parse_simple( header );
572
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
2 if( entries.empty() )
573 return *offered.begin();
574
575 2 return negotiate( entries, offered, match_exact );
576 2 }
577
578 std::vector<std::string_view>
579 1 accepts::charsets() const
580 {
581 1 auto header = fields_.value_or(
582 field::accept_charset, "" );
583
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
1 if( header.empty() )
584 return {};
585
586
1/1
✓ Branch 2 taken 1 time.
1 auto entries = parse_simple( header );
587
1/1
✓ Branch 1 taken 1 time.
1 return sorted_values( entries );
588 1 }
589
590 std::string_view
591 6 accepts::language(
592 std::initializer_list<
593 std::string_view> offered ) const
594 {
595
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
6 if( offered.size() == 0 )
596 return {};
597
598 6 auto header = fields_.value_or(
599 field::accept_language, "" );
600
601
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 5 times.
6 if( header.empty() )
602 1 return *offered.begin();
603
604
1/1
✓ Branch 2 taken 5 times.
5 auto entries = parse_simple( header );
605
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
5 if( entries.empty() )
606 return *offered.begin();
607
608 5 return negotiate( entries, offered, match_language );
609 5 }
610
611 std::vector<std::string_view>
612 1 accepts::languages() const
613 {
614 1 auto header = fields_.value_or(
615 field::accept_language, "" );
616
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
1 if( header.empty() )
617 return {};
618
619
1/1
✓ Branch 2 taken 1 time.
1 auto entries = parse_simple( header );
620
1/1
✓ Branch 1 taken 1 time.
1 return sorted_values( entries );
621 1 }
622
623 } // http
624 } // boost
625