LCOV - code coverage report
Current view: top level - src/server - send_file.cpp (source / functions) Coverage Total Missed
Test: coverage_remapped.info Lines: 0.0 % 85 85
Test Date: 2026-06-13 19:44:58 Functions: 0.0 % 3 3

           TLA  Line data    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/send_file.hpp>
      11                 : #include <boost/http/server/etag.hpp>
      12                 : #include <boost/http/server/fresh.hpp>
      13                 : #include <boost/http/server/mime_types.hpp>
      14                 : #include <boost/http/server/range_parser.hpp>
      15                 : #include <boost/http/field.hpp>
      16                 : #include <boost/http/status.hpp>
      17                 : #include <ctime>
      18                 : #include <filesystem>
      19                 : 
      20                 : namespace boost {
      21                 : namespace http {
      22                 : 
      23                 : namespace {
      24                 : 
      25                 : // Get file stats
      26                 : bool
      27 MIS           0 : get_file_stats(
      28                 :     core::string_view path,
      29                 :     std::uint64_t& size,
      30                 :     std::uint64_t& mtime)
      31                 : {
      32               0 :     system::error_code ec;
      33               0 :     std::filesystem::path p(path.begin(), path.end());
      34                 : 
      35               0 :     auto status = std::filesystem::status(p, ec);
      36               0 :     if(ec.failed() || ! std::filesystem::is_regular_file(status))
      37               0 :         return false;
      38                 : 
      39               0 :     size = static_cast<std::uint64_t>(
      40               0 :         std::filesystem::file_size(p, ec));
      41               0 :     if(ec.failed())
      42               0 :         return false;
      43                 : 
      44               0 :     auto ftime = std::filesystem::last_write_time(p, ec);
      45               0 :     if(ec.failed())
      46               0 :         return false;
      47                 : 
      48                 :     // Convert to Unix timestamp
      49                 :     auto const sctp = std::chrono::time_point_cast<
      50               0 :         std::chrono::system_clock::duration>(
      51               0 :             ftime - std::filesystem::file_time_type::clock::now() +
      52               0 :             std::chrono::system_clock::now());
      53               0 :     mtime = static_cast<std::uint64_t>(
      54               0 :         std::chrono::system_clock::to_time_t(sctp));
      55                 : 
      56               0 :     return true;
      57               0 : }
      58                 : 
      59                 : } // (anon)
      60                 : 
      61                 : std::string
      62               0 : format_http_date(std::uint64_t mtime)
      63                 : {
      64               0 :     std::time_t t = static_cast<std::time_t>(mtime);
      65                 :     std::tm tm;
      66                 : #ifdef _WIN32
      67                 :     gmtime_s(&tm, &t);
      68                 : #else
      69               0 :     gmtime_r(&t, &tm);
      70                 : #endif
      71                 : 
      72                 :     char buf[64];
      73               0 :     std::strftime(buf, sizeof(buf),
      74                 :         "%a, %d %b %Y %H:%M:%S GMT", &tm);
      75               0 :     return std::string(buf);
      76                 : }
      77                 : 
      78                 : void
      79               0 : send_file_init(
      80                 :     send_file_info& info,
      81                 :     route_params& rp,
      82                 :     core::string_view path,
      83                 :     send_file_options const& opts)
      84                 : {
      85               0 :     info = send_file_info{};
      86                 : 
      87                 :     // Get file stats
      88               0 :     if(! get_file_stats(path, info.size, info.mtime))
      89                 :     {
      90               0 :         info.result = send_file_result::not_found;
      91               0 :         return;
      92                 :     }
      93                 : 
      94                 :     // Determine content type
      95               0 :     if(! opts.content_type.empty())
      96                 :     {
      97               0 :         info.content_type = opts.content_type;
      98                 :     }
      99                 :     else
     100                 :     {
     101               0 :         auto ct = mime_types::content_type(path);
     102               0 :         if(ct.empty())
     103               0 :             ct = "application/octet-stream";
     104               0 :         info.content_type = std::move(ct);
     105               0 :     }
     106                 : 
     107                 :     // Generate ETag if enabled
     108               0 :     if(opts.etag)
     109                 :     {
     110               0 :         info.etag = etag(info.size, info.mtime);
     111               0 :         rp.res.set(field::etag, info.etag);
     112                 :     }
     113                 : 
     114                 :     // Set Last-Modified if enabled
     115               0 :     if(opts.last_modified)
     116                 :     {
     117               0 :         info.last_modified = format_http_date(info.mtime);
     118               0 :         rp.res.set(field::last_modified, info.last_modified);
     119                 :     }
     120                 : 
     121                 :     // Set Cache-Control
     122               0 :     if(opts.max_age > 0)
     123                 :     {
     124                 :         std::string cc = "public, max-age=" +
     125               0 :             std::to_string(opts.max_age);
     126               0 :         rp.res.set(field::cache_control, cc);
     127               0 :     }
     128                 : 
     129                 :     // Check freshness (conditional GET)
     130               0 :     if(is_fresh(rp.req, rp.res))
     131                 :     {
     132               0 :         info.result = send_file_result::not_modified;
     133               0 :         return;
     134                 :     }
     135                 : 
     136                 :     // Set Content-Type
     137               0 :     rp.res.set(field::content_type, info.content_type);
     138                 : 
     139                 :     // Handle Range header
     140               0 :     auto range_header = rp.req.value_or(field::range, "");
     141               0 :     if(! range_header.empty())
     142                 :     {
     143                 :         auto range_result = parse_range(
     144               0 :             static_cast<std::int64_t>(info.size),
     145               0 :             range_header);
     146                 : 
     147               0 :         if(range_result.type == range_result_type::ok &&
     148               0 :             ! range_result.ranges.empty())
     149                 :         {
     150                 :             // Use first range only (simplification)
     151               0 :             auto const& range = range_result.ranges[0];
     152               0 :             info.is_range = true;
     153               0 :             info.range_start = range.start;
     154               0 :             info.range_end = range.end;
     155                 : 
     156                 :             // Set 206 Partial Content
     157               0 :             rp.res.set_status(status::partial_content);
     158                 : 
     159               0 :             auto const content_length =
     160               0 :                 range.end - range.start + 1;
     161               0 :             rp.res.set_payload_size(
     162                 :                 static_cast<std::uint64_t>(content_length));
     163                 : 
     164                 :             // Content-Range header
     165               0 :             std::string cr = "bytes " +
     166               0 :                 std::to_string(range.start) + "-" +
     167               0 :                 std::to_string(range.end) + "/" +
     168               0 :                 std::to_string(info.size);
     169               0 :             rp.res.set(field::content_range, cr);
     170                 : 
     171               0 :             info.result = send_file_result::ok;
     172               0 :             return;
     173               0 :         }
     174                 : 
     175               0 :         if(range_result.type == range_result_type::unsatisfiable)
     176                 :         {
     177               0 :             rp.res.set_status(
     178                 :                 status::range_not_satisfiable);
     179               0 :             rp.res.set(field::content_range,
     180               0 :                 "bytes */" + std::to_string(info.size));
     181               0 :             info.result = send_file_result::error;
     182               0 :             return;
     183                 :         }
     184                 :         // If malformed, ignore and serve full content
     185               0 :     }
     186                 : 
     187                 :     // Full content response
     188               0 :     rp.res.set_status(status::ok);
     189               0 :     rp.res.set_payload_size(info.size);
     190               0 :     info.range_start = 0;
     191               0 :     info.range_end = static_cast<std::int64_t>(info.size) - 1;
     192               0 :     info.result = send_file_result::ok;
     193                 : }
     194                 : 
     195                 : } // http
     196                 : } // boost
        

Generated by: LCOV version 2.3