LCOV - code coverage report
Current view: top level - src/server - mime_types.cpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 50.7 % 67 34 33
Test Date: 2026-06-13 19:44:58 Functions: 57.1 % 7 4 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/mime_types.hpp>
      11                 : #include <boost/http/server/mime_db.hpp>
      12                 : #include <algorithm>
      13                 : #include <cctype>
      14                 : 
      15                 : namespace boost {
      16                 : namespace http {
      17                 : namespace mime_types {
      18                 : 
      19                 : namespace {
      20                 : 
      21                 : struct ext_entry
      22                 : {
      23                 :     core::string_view ext;
      24                 :     core::string_view type;
      25                 : };
      26                 : 
      27                 : // Sorted by extension for binary search
      28                 : constexpr ext_entry ext_db[] = {
      29                 :     { "aac", "audio/aac" },
      30                 :     { "avif", "image/avif" },
      31                 :     { "bmp", "image/bmp" },
      32                 :     { "bz", "application/x-bzip" },
      33                 :     { "bz2", "application/x-bzip2" },
      34                 :     { "cjs", "application/javascript" },
      35                 :     { "css", "text/css" },
      36                 :     { "csv", "text/csv" },
      37                 :     { "flac", "audio/flac" },
      38                 :     { "gif", "image/gif" },
      39                 :     { "gz", "application/gzip" },
      40                 :     { "htm", "text/html" },
      41                 :     { "html", "text/html" },
      42                 :     { "ico", "image/x-icon" },
      43                 :     { "ics", "text/calendar" },
      44                 :     { "jpeg", "image/jpeg" },
      45                 :     { "jpg", "image/jpeg" },
      46                 :     { "js", "text/javascript" },
      47                 :     { "json", "application/json" },
      48                 :     { "m4a", "audio/mp4" },
      49                 :     { "m4v", "video/mp4" },
      50                 :     { "manifest", "text/cache-manifest" },
      51                 :     { "md", "text/markdown" },
      52                 :     { "mjs", "text/javascript" },
      53                 :     { "mp3", "audio/mpeg" },
      54                 :     { "mp4", "video/mp4" },
      55                 :     { "mpeg", "video/mpeg" },
      56                 :     { "mpg", "video/mpeg" },
      57                 :     { "oga", "audio/ogg" },
      58                 :     { "ogg", "audio/ogg" },
      59                 :     { "ogv", "video/ogg" },
      60                 :     { "otf", "font/otf" },
      61                 :     { "pdf", "application/pdf" },
      62                 :     { "png", "image/png" },
      63                 :     { "rtf", "application/rtf" },
      64                 :     { "svg", "image/svg+xml" },
      65                 :     { "tar", "application/x-tar" },
      66                 :     { "tif", "image/tiff" },
      67                 :     { "tiff", "image/tiff" },
      68                 :     { "ttf", "font/ttf" },
      69                 :     { "txt", "text/plain" },
      70                 :     { "wasm", "application/wasm" },
      71                 :     { "wav", "audio/wav" },
      72                 :     { "weba", "audio/webm" },
      73                 :     { "webm", "video/webm" },
      74                 :     { "webp", "image/webp" },
      75                 :     { "woff", "font/woff" },
      76                 :     { "woff2", "font/woff2" },
      77                 :     { "xhtml", "application/xhtml+xml" },
      78                 :     { "xml", "application/xml" },
      79                 :     { "zip", "application/zip" },
      80                 :     { "7z", "application/x-7z-compressed" },
      81                 : };
      82                 : 
      83                 : constexpr std::size_t ext_db_size = sizeof( ext_db ) / sizeof( ext_db[0] );
      84                 : 
      85                 : // Case-insensitive comparison
      86                 : int
      87 HIT          48 : compare_icase( core::string_view a, core::string_view b ) noexcept
      88                 : {
      89              48 :     auto const n = ( std::min )( a.size(), b.size() );
      90              91 :     for( std::size_t i = 0; i < n; ++i )
      91                 :     {
      92                 :         auto const ca = static_cast<unsigned char>(
      93              79 :             std::tolower( static_cast<unsigned char>( a[i] ) ) );
      94                 :         auto const cb = static_cast<unsigned char>(
      95              79 :             std::tolower( static_cast<unsigned char>( b[i] ) ) );
      96              79 :         if( ca < cb )
      97              14 :             return -1;
      98              65 :         if( ca > cb )
      99              22 :             return 1;
     100                 :     }
     101              12 :     if( a.size() < b.size() )
     102               4 :         return -1;
     103               8 :     if( a.size() > b.size() )
     104 MIS           0 :         return 1;
     105 HIT           8 :     return 0;
     106                 : }
     107                 : 
     108                 : // Extract extension from path
     109                 : core::string_view
     110               9 : get_extension( core::string_view path ) noexcept
     111                 : {
     112                 :     // Find last dot
     113               9 :     auto const pos = path.rfind( '.' );
     114               9 :     if( pos == core::string_view::npos )
     115               9 :         return path; // Assume it's just an extension
     116 MIS           0 :     return path.substr( pos + 1 );
     117                 : }
     118                 : 
     119                 : // Binary search for extension
     120                 : core::string_view
     121 HIT           9 : lookup_ext( core::string_view ext ) noexcept
     122                 : {
     123               9 :     std::size_t lo = 0;
     124               9 :     std::size_t hi = ext_db_size;
     125              49 :     while( lo < hi )
     126                 :     {
     127              48 :         auto const mid = lo + ( hi - lo ) / 2;
     128              48 :         auto const cmp = compare_icase( ext_db[mid].ext, ext );
     129              48 :         if( cmp < 0 )
     130              18 :             lo = mid + 1;
     131              30 :         else if( cmp > 0 )
     132              22 :             hi = mid;
     133                 :         else
     134               8 :             return ext_db[mid].type;
     135                 :     }
     136               1 :     return {};
     137                 : }
     138                 : 
     139                 : } // (anon)
     140                 : 
     141                 : core::string_view
     142               9 : lookup( core::string_view path_or_ext ) noexcept
     143                 : {
     144               9 :     if( path_or_ext.empty() )
     145 MIS           0 :         return {};
     146                 : 
     147                 :     // Skip leading dot if present
     148 HIT           9 :     if( path_or_ext[0] == '.' )
     149 MIS           0 :         path_or_ext.remove_prefix( 1 );
     150                 : 
     151 HIT           9 :     auto const ext = get_extension( path_or_ext );
     152               9 :     return lookup_ext( ext );
     153                 : }
     154                 : 
     155                 : core::string_view
     156 MIS           0 : extension( core::string_view type ) noexcept
     157                 : {
     158                 :     // Linear search for type -> extension
     159                 :     // Could optimize with reverse map if needed
     160               0 :     for( std::size_t i = 0; i < ext_db_size; ++i )
     161                 :     {
     162               0 :         if( compare_icase( ext_db[i].type, type ) == 0 )
     163               0 :             return ext_db[i].ext;
     164                 :     }
     165               0 :     return {};
     166                 : }
     167                 : 
     168                 : core::string_view
     169               0 : charset( core::string_view type ) noexcept
     170                 : {
     171               0 :     auto const* entry = mime_db::lookup( type );
     172               0 :     if( entry )
     173               0 :         return entry->charset;
     174               0 :     return {};
     175                 : }
     176                 : 
     177                 : std::string
     178               0 : content_type( core::string_view type_or_ext )
     179                 : {
     180               0 :     core::string_view type;
     181                 : 
     182                 :     // Check if it looks like an extension
     183               0 :     if( ! type_or_ext.empty() &&
     184               0 :         ( type_or_ext[0] == '.' ||
     185               0 :           type_or_ext.find( '/' ) == core::string_view::npos ) )
     186                 :     {
     187               0 :         type = lookup( type_or_ext );
     188               0 :         if( type.empty() )
     189               0 :             return {};
     190                 :     }
     191                 :     else
     192                 :     {
     193               0 :         type = type_or_ext;
     194                 :     }
     195                 : 
     196               0 :     auto const cs = charset( type );
     197               0 :     if( cs.empty() )
     198               0 :         return std::string( type );
     199                 : 
     200               0 :     std::string result;
     201               0 :     result.reserve( type.size() + 10 + cs.size() );
     202               0 :     result.append( type.data(), type.size() );
     203               0 :     result.append( "; charset=" );
     204               0 :     result.append( cs.data(), cs.size() );
     205               0 :     return result;
     206               0 : }
     207                 : 
     208                 : } // mime_types
     209                 : } // http
     210                 : } // boost
        

Generated by: LCOV version 2.3