@@ -1258,6 +1258,10 @@ class Server {
12581258
12591259 bool routing (Request &req, Response &res, Stream &strm);
12601260 bool handle_file_request (Request &req, Response &res);
1261+ bool check_if_not_modified (const Request &req, Response &res,
1262+ const std::string &etag, time_t mtime) const ;
1263+ bool check_if_range (Request &req, const std::string &etag,
1264+ time_t mtime) const ;
12611265 bool dispatch_request (Request &req, Response &res,
12621266 const Handlers &handlers) const ;
12631267 bool dispatch_request_for_content_reader (
@@ -8357,7 +8361,6 @@ inline bool Server::handle_file_request(Request &req, Response &res) {
83578361 res.set_header (kv.first , kv.second );
83588362 }
83598363
8360- // Compute and set weak ETag based on mtime+size.
83618364 auto etag = detail::compute_etag (stat);
83628365 if (!etag.empty ()) { res.set_header (" ETag" , etag); }
83638366
@@ -8368,70 +8371,9 @@ inline bool Server::handle_file_request(Request &req, Response &res) {
83688371 res.set_header (" Last-Modified" , last_modified);
83698372 }
83708373
8371- // Handle conditional GET:
8372- // 1. If-None-Match takes precedence (RFC 9110 Section 13.1.2)
8373- // 2. If-Modified-Since is checked only when If-None-Match is absent
8374- if (req.has_header (" If-None-Match" )) {
8375- if (!etag.empty ()) {
8376- auto val = req.get_header_value (" If-None-Match" );
8377- auto matched = false ;
8378-
8379- // NOTE: We use exact string matching here. This works correctly
8380- // because our server always generates weak ETags (W/"..."), and
8381- // clients typically send back the same ETag they received.
8382- // RFC 9110 Section 8.8.3.2 allows weak comparison for
8383- // If-None-Match, where W/"x" and "x" would match, but this
8384- // simplified implementation requires exact matches.
8385- detail::split (val.data (), val.data () + val.size (), ' ,' ,
8386- [&](const char *b, const char *e) {
8387- if (!matched) {
8388- auto tag = std::string (b, e);
8389- matched = tag == " *" || tag == etag;
8390- }
8391- });
8392-
8393- if (matched) {
8394- res.status = StatusCode::NotModified_304;
8395- return true ;
8396- }
8397- }
8398- } else if (req.has_header (" If-Modified-Since" )) {
8399- auto val = req.get_header_value (" If-Modified-Since" );
8400- auto t = detail::parse_http_date (val);
8401-
8402- if (t != static_cast <time_t >(-1 ) && mtime <= t) {
8403- res.status = StatusCode::NotModified_304;
8404- return true ;
8405- }
8406- }
8407-
8408- // Handle If-Range for partial content requests (RFC 9110
8409- // Section 13.1.5). If-Range is only evaluated when Range header is
8410- // present. If the validator matches, serve partial content; otherwise
8411- // serve full content.
8412- if (!req.ranges .empty () && req.has_header (" If-Range" )) {
8413- auto val = req.get_header_value (" If-Range" );
8414- auto valid = false ;
8415-
8416- if (detail::is_strong_etag (val)) {
8417- // RFC 9110 Section 13.1.5: If-Range requires strong ETag
8418- // comparison.
8419- valid = (!etag.empty () && val == etag);
8420- } else if (detail::is_weak_etag (val)) {
8421- // Weak ETags are not valid for If-Range (RFC 9110 Section 13.1.5)
8422- valid = false ;
8423- } else {
8424- // HTTP-date comparison
8425- auto if_range_time = detail::parse_http_date (val);
8426- valid = (if_range_time != static_cast <time_t >(-1 ) &&
8427- mtime <= if_range_time);
8428- }
8374+ if (check_if_not_modified (req, res, etag, mtime)) { return true ; }
84298375
8430- if (!valid) {
8431- // Validator doesn't match: ignore Range and serve full content
8432- req.ranges .clear ();
8433- }
8434- }
8376+ check_if_range (req, etag, mtime);
84358377
84368378 auto mm = std::make_shared<detail::mmap>(path.c_str ());
84378379 if (!mm->is_open ()) {
@@ -8462,6 +8404,81 @@ inline bool Server::handle_file_request(Request &req, Response &res) {
84628404 return false ;
84638405}
84648406
8407+ inline bool Server::check_if_not_modified (const Request &req, Response &res,
8408+ const std::string &etag,
8409+ time_t mtime) const {
8410+ // Handle conditional GET:
8411+ // 1. If-None-Match takes precedence (RFC 9110 Section 13.1.2)
8412+ // 2. If-Modified-Since is checked only when If-None-Match is absent
8413+ if (req.has_header (" If-None-Match" )) {
8414+ if (!etag.empty ()) {
8415+ auto val = req.get_header_value (" If-None-Match" );
8416+ auto matched = false ;
8417+
8418+ // NOTE: We use exact string matching here. This works correctly
8419+ // because our server always generates weak ETags (W/"..."), and
8420+ // clients typically send back the same ETag they received.
8421+ // RFC 9110 Section 8.8.3.2 allows weak comparison for
8422+ // If-None-Match, where W/"x" and "x" would match, but this
8423+ // simplified implementation requires exact matches.
8424+ detail::split (val.data (), val.data () + val.size (), ' ,' ,
8425+ [&](const char *b, const char *e) {
8426+ if (!matched) {
8427+ auto tag = std::string (b, e);
8428+ matched = tag == " *" || tag == etag;
8429+ }
8430+ });
8431+
8432+ if (matched) {
8433+ res.status = StatusCode::NotModified_304;
8434+ return true ;
8435+ }
8436+ }
8437+ } else if (req.has_header (" If-Modified-Since" )) {
8438+ auto val = req.get_header_value (" If-Modified-Since" );
8439+ auto t = detail::parse_http_date (val);
8440+
8441+ if (t != static_cast <time_t >(-1 ) && mtime <= t) {
8442+ res.status = StatusCode::NotModified_304;
8443+ return true ;
8444+ }
8445+ }
8446+ return false ;
8447+ }
8448+
8449+ inline bool Server::check_if_range (Request &req, const std::string &etag,
8450+ time_t mtime) const {
8451+ // Handle If-Range for partial content requests (RFC 9110
8452+ // Section 13.1.5). If-Range is only evaluated when Range header is
8453+ // present. If the validator matches, serve partial content; otherwise
8454+ // serve full content.
8455+ if (!req.ranges .empty () && req.has_header (" If-Range" )) {
8456+ auto val = req.get_header_value (" If-Range" );
8457+ auto valid = false ;
8458+
8459+ if (detail::is_strong_etag (val)) {
8460+ // RFC 9110 Section 13.1.5: If-Range requires strong ETag
8461+ // comparison.
8462+ valid = (!etag.empty () && val == etag);
8463+ } else if (detail::is_weak_etag (val)) {
8464+ // Weak ETags are not valid for If-Range (RFC 9110 Section 13.1.5)
8465+ valid = false ;
8466+ } else {
8467+ // HTTP-date comparison
8468+ auto if_range_time = detail::parse_http_date (val);
8469+ valid =
8470+ (if_range_time != static_cast <time_t >(-1 ) && mtime <= if_range_time);
8471+ }
8472+
8473+ if (!valid) {
8474+ // Validator doesn't match: ignore Range and serve full content
8475+ req.ranges .clear ();
8476+ return false ;
8477+ }
8478+ }
8479+ return true ;
8480+ }
8481+
84658482inline socket_t
84668483Server::create_server_socket (const std::string &host, int port,
84678484 int socket_flags,
0 commit comments