Line data Source code
1 : //
2 : // Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot 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/fresh.hpp>
11 : #include <boost/http/field.hpp>
12 :
13 : namespace boost {
14 : namespace http {
15 :
16 : namespace {
17 :
18 : // Check if ETag matches If-None-Match
19 : // Returns true if they match (response is fresh)
20 : bool
21 0 : etag_matches(
22 : core::string_view if_none_match,
23 : core::string_view etag ) noexcept
24 : {
25 0 : if( if_none_match.empty() || etag.empty() )
26 0 : return false;
27 :
28 : // "*" matches any ETag
29 0 : if( if_none_match == "*" )
30 0 : return true;
31 :
32 : // Simple comparison - check if ETag appears in the list
33 : // In full implementation, would need to handle weak vs strong
34 : // and parse comma-separated list properly
35 :
36 : // Remove W/ prefix for comparison if present
37 0 : auto strip_weak = []( core::string_view s ) -> core::string_view
38 : {
39 0 : if( s.size() >= 2 &&
40 0 : ( s[0] == 'W' || s[0] == 'w' ) &&
41 0 : s[1] == '/' )
42 0 : return s.substr( 2 );
43 0 : return s;
44 : };
45 :
46 0 : auto const etag_val = strip_weak( etag );
47 :
48 : // Simple contains check for the ETag value
49 0 : auto pos = if_none_match.find( etag_val );
50 0 : if( pos != core::string_view::npos )
51 0 : return true;
52 :
53 : // Also check without weak prefix in if_none_match
54 0 : auto const inm_stripped = strip_weak( if_none_match );
55 0 : if( inm_stripped == etag_val )
56 0 : return true;
57 :
58 0 : return false;
59 : }
60 :
61 : // Parse HTTP date and compare
62 : // Returns true if response's Last-Modified <= request's If-Modified-Since
63 : // For simplicity, doing string comparison (works for RFC 7231 dates)
64 : bool
65 0 : not_modified_since(
66 : core::string_view if_modified_since,
67 : core::string_view last_modified ) noexcept
68 : {
69 0 : if( if_modified_since.empty() || last_modified.empty() )
70 0 : return false;
71 :
72 : // HTTP dates in RFC 7231 format are lexicographically comparable
73 : // when in the same format (preferred format)
74 : // For a robust implementation, would parse dates properly
75 0 : return last_modified <= if_modified_since;
76 : }
77 :
78 : } // (anon)
79 :
80 : bool
81 0 : is_fresh(
82 : request const& req,
83 : response const& res ) noexcept
84 : {
85 : // Get conditional request headers
86 0 : auto const if_none_match = req.value_or(
87 : field::if_none_match, "" );
88 0 : auto const if_modified_since = req.value_or(
89 : field::if_modified_since, "" );
90 :
91 : // If no conditional headers, not fresh
92 0 : if( if_none_match.empty() && if_modified_since.empty() )
93 0 : return false;
94 :
95 : // Get response caching headers
96 0 : auto const etag = res.value_or( field::etag, "" );
97 0 : auto const last_modified = res.value_or(
98 : field::last_modified, "" );
99 :
100 : // Check ETag first (stronger validator)
101 0 : if( ! if_none_match.empty() )
102 : {
103 0 : if( ! etag.empty() && etag_matches( if_none_match, etag ) )
104 0 : return true;
105 : // If If-None-Match present but doesn't match, not fresh
106 0 : return false;
107 : }
108 :
109 : // Fall back to If-Modified-Since
110 0 : if( ! if_modified_since.empty() && ! last_modified.empty() )
111 : {
112 0 : return not_modified_since( if_modified_since, last_modified );
113 : }
114 :
115 0 : return false;
116 : }
117 :
118 : } // http
119 : } // boost
|