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 | |||||