LCOV - code coverage report
Current view: top level - libs/http/src/server - send_file.cpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 0.0 % 85 0
Test Date: 2026-01-20 00:11:34 Functions: 0.0 % 3 0

            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/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            0 : get_file_stats(
      28              :     core::string_view path,
      29              :     std::uint64_t& size,
      30              :     std::uint64_t& mtime)
      31              : {
      32            0 :     std::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 || ! 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)
      42            0 :         return false;
      43              : 
      44            0 :     auto ftime = std::filesystem::last_write_time(p, ec);
      45            0 :     if(ec)
      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