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