GCC Code Coverage Report


Directory: ./
File: libs/http/src/server/mime_types.cpp
Date: 2026-01-20 00:11:35
Exec Total Coverage
Lines: 0 67 0.0%
Functions: 0 7 0.0%
Branches: 0 45 0.0%

Line Branch Exec Source
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/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 compare_icase( core::string_view a, core::string_view b ) noexcept
88 {
89 auto const n = ( std::min )( a.size(), b.size() );
90 for( std::size_t i = 0; i < n; ++i )
91 {
92 auto const ca = static_cast<unsigned char>(
93 std::tolower( static_cast<unsigned char>( a[i] ) ) );
94 auto const cb = static_cast<unsigned char>(
95 std::tolower( static_cast<unsigned char>( b[i] ) ) );
96 if( ca < cb )
97 return -1;
98 if( ca > cb )
99 return 1;
100 }
101 if( a.size() < b.size() )
102 return -1;
103 if( a.size() > b.size() )
104 return 1;
105 return 0;
106 }
107
108 // Extract extension from path
109 core::string_view
110 get_extension( core::string_view path ) noexcept
111 {
112 // Find last dot
113 auto const pos = path.rfind( '.' );
114 if( pos == core::string_view::npos )
115 return path; // Assume it's just an extension
116 return path.substr( pos + 1 );
117 }
118
119 // Binary search for extension
120 core::string_view
121 lookup_ext( core::string_view ext ) noexcept
122 {
123 std::size_t lo = 0;
124 std::size_t hi = ext_db_size;
125 while( lo < hi )
126 {
127 auto const mid = lo + ( hi - lo ) / 2;
128 auto const cmp = compare_icase( ext_db[mid].ext, ext );
129 if( cmp < 0 )
130 lo = mid + 1;
131 else if( cmp > 0 )
132 hi = mid;
133 else
134 return ext_db[mid].type;
135 }
136 return {};
137 }
138
139 } // (anon)
140
141 core::string_view
142 lookup( core::string_view path_or_ext ) noexcept
143 {
144 if( path_or_ext.empty() )
145 return {};
146
147 // Skip leading dot if present
148 if( path_or_ext[0] == '.' )
149 path_or_ext.remove_prefix( 1 );
150
151 auto const ext = get_extension( path_or_ext );
152 return lookup_ext( ext );
153 }
154
155 core::string_view
156 extension( core::string_view type ) noexcept
157 {
158 // Linear search for type -> extension
159 // Could optimize with reverse map if needed
160 for( std::size_t i = 0; i < ext_db_size; ++i )
161 {
162 if( compare_icase( ext_db[i].type, type ) == 0 )
163 return ext_db[i].ext;
164 }
165 return {};
166 }
167
168 core::string_view
169 charset( core::string_view type ) noexcept
170 {
171 auto const* entry = mime_db::lookup( type );
172 if( entry )
173 return entry->charset;
174 return {};
175 }
176
177 std::string
178 content_type( core::string_view type_or_ext )
179 {
180 core::string_view type;
181
182 // Check if it looks like an extension
183 if( ! type_or_ext.empty() &&
184 ( type_or_ext[0] == '.' ||
185 type_or_ext.find( '/' ) == core::string_view::npos ) )
186 {
187 type = lookup( type_or_ext );
188 if( type.empty() )
189 return {};
190 }
191 else
192 {
193 type = type_or_ext;
194 }
195
196 auto const cs = charset( type );
197 if( cs.empty() )
198 return std::string( type );
199
200 std::string result;
201 result.reserve( type.size() + 10 + cs.size() );
202 result.append( type.data(), type.size() );
203 result.append( "; charset=" );
204 result.append( cs.data(), cs.size() );
205 return result;
206 }
207
208 } // mime_types
209 } // http
210 } // boost
211