src/server/accepts.cpp

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