/src/pistache/src/common/mime.cc
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * SPDX-FileCopyrightText: 2015 Mathieu Stefani |
3 | | * |
4 | | * SPDX-License-Identifier: Apache-2.0 |
5 | | */ |
6 | | |
7 | | /* mime.cc |
8 | | Mathieu Stefani, 29 August 2015 |
9 | | |
10 | | Implementation of MIME Type parsing |
11 | | */ |
12 | | |
13 | | #include <cstring> |
14 | | |
15 | | #include <pistache/http.h> |
16 | | #include <pistache/mime.h> |
17 | | |
18 | | namespace Pistache::Http::Mime |
19 | | { |
20 | | |
21 | | std::string Q::toString() const |
22 | 0 | { |
23 | 0 | if (val_ == 0) |
24 | 0 | return "q=0"; |
25 | 0 | else if (val_ == 100) |
26 | 0 | return "q=1"; |
27 | | |
28 | 0 | char buff[sizeof("q=0.99")] = {}; |
29 | 0 | const char* const format = val_ % 10 == 0 ? "q=%.1f" : "q=%.2f"; |
30 | |
|
31 | 0 | const int len = snprintf(buff, sizeof(buff), format, val_ / 100.0); |
32 | |
|
33 | 0 | return std::string(buff, len); |
34 | 0 | } |
35 | | |
36 | | MediaType MediaType::fromString(const std::string& str) |
37 | 0 | { |
38 | 0 | return fromRaw(str.c_str(), str.size()); |
39 | 0 | } |
40 | | |
41 | | MediaType MediaType::fromString(std::string&& str) |
42 | 0 | { |
43 | 0 | return fromRaw(str.c_str(), str.size()); |
44 | 0 | } |
45 | | |
46 | | MediaType MediaType::fromRaw(const char* str, size_t len) |
47 | 303k | { |
48 | 303k | MediaType res; |
49 | | |
50 | 303k | res.parseRaw(str, len); |
51 | 303k | return res; |
52 | 303k | } |
53 | | |
54 | | MediaType MediaType::fromFile(const char* fileName) |
55 | 0 | { |
56 | 0 | const char* extensionOffset = nullptr; |
57 | 0 | const char* p = fileName; |
58 | 0 | while (*p) |
59 | 0 | { |
60 | 0 | if (*p == '.') |
61 | 0 | extensionOffset = p; |
62 | 0 | ++p; |
63 | 0 | } |
64 | |
|
65 | 0 | if (!extensionOffset) |
66 | 0 | return MediaType(); |
67 | | |
68 | 0 | ++extensionOffset; |
69 | |
|
70 | 0 | struct Extension |
71 | 0 | { |
72 | 0 | const char* const raw; |
73 | 0 | Mime::Type top; |
74 | 0 | Mime::Subtype sub; |
75 | 0 | }; |
76 | | |
77 | | // @Data: maybe one day try to export |
78 | | // http://www.iana.org/assignments/media-types/media-types.xhtml as an |
79 | | // item-list |
80 | |
|
81 | 0 | static constexpr Extension KnownExtensions[] = { |
82 | 0 | { "jpg", Type::Image, Subtype::Jpeg }, |
83 | 0 | { "jpeg", Type::Image, Subtype::Jpeg }, |
84 | 0 | { "png", Type::Image, Subtype::Png }, |
85 | 0 | { "bmp", Type::Image, Subtype::Bmp }, |
86 | |
|
87 | 0 | { "txt", Type::Text, Subtype::Plain }, |
88 | 0 | { "md", Type::Text, Subtype::Plain }, |
89 | |
|
90 | 0 | { "bin", Type::Application, Subtype::OctetStream }, |
91 | 0 | }; |
92 | |
|
93 | 0 | for (const auto& ext : KnownExtensions) |
94 | 0 | { |
95 | 0 | if (!strcmp(extensionOffset, ext.raw)) |
96 | 0 | { |
97 | 0 | return MediaType(ext.top, ext.sub); |
98 | 0 | } |
99 | 0 | } |
100 | | |
101 | 0 | return MediaType(); |
102 | 0 | } |
103 | | |
104 | | void MediaType::parseRaw(const char* str, size_t len) |
105 | 309k | { |
106 | 309k | auto raise = [](const char* str) { |
107 | | // TODO: eventually, we should throw a more generic exception |
108 | | // that could then be catched in lower stack frames to rethrow |
109 | | // an HttpError |
110 | 7.95k | throw HttpError(Http::Code::Unsupported_Media_Type, str); |
111 | 7.95k | }; |
112 | | |
113 | 309k | RawStreamBuf<char> buf(const_cast<char*>(str), len); |
114 | 309k | StreamCursor cursor(&buf); |
115 | | |
116 | 309k | raw_ = std::string(str, len); |
117 | | |
118 | 309k | Mime::Type top = Type::None; |
119 | | |
120 | | // The reason we are using a do { } while (0); syntax construct here is to |
121 | | // emulate if / else-if. Since we are using items-list macros to compare the |
122 | | // strings, we want to avoid evaluating all the branches when one of them |
123 | | // evaluates to true. |
124 | | // |
125 | | // Instead, we break the loop when a branch evaluates to true so that we do |
126 | | // not evaluate all the subsequent ones. |
127 | | // |
128 | | // Watch out, this pattern is repeated throughout the function |
129 | 309k | do |
130 | 309k | { |
131 | 309k | #define TYPE(val, s) \ |
132 | 363k | if (match_string(s, sizeof s - 1, cursor, CaseSensitivity::Insensitive)) \ |
133 | 363k | { \ |
134 | 302k | top = Type::val; \ |
135 | 302k | break; \ |
136 | 302k | } |
137 | 309k | MIME_TYPES |
138 | 6.76k | #undef TYPE |
139 | 6.76k | raise("Unknown Media Type"); |
140 | 6.76k | } while (false); |
141 | | |
142 | 0 | top_ = top; |
143 | | |
144 | 309k | if (!match_literal('/', cursor)) |
145 | 212 | raise("Malformed Media Type, expected a '/' after the top type"); |
146 | | |
147 | 309k | if (cursor.eof()) |
148 | 34 | raise("Malformed Media type, missing subtype"); |
149 | | |
150 | | // Parse subtype |
151 | 309k | Mime::Subtype sub; |
152 | | |
153 | 309k | StreamCursor::Token subToken(cursor); |
154 | | |
155 | 309k | if (match_raw("vnd.", 4, cursor)) |
156 | 234 | { |
157 | 234 | sub = Subtype::Vendor; |
158 | 234 | } |
159 | 309k | else |
160 | 309k | { |
161 | 309k | do |
162 | 309k | { |
163 | 309k | #define SUB_TYPE(val, s) \ |
164 | 4.74M | if (match_string(s, sizeof s - 1, cursor, CaseSensitivity::Insensitive)) \ |
165 | 4.74M | { \ |
166 | 38.5k | sub = Subtype::val; \ |
167 | 38.5k | break; \ |
168 | 38.5k | } |
169 | 309k | MIME_SUBTYPES |
170 | 270k | #undef SUB_TYPE |
171 | 270k | sub = Subtype::Ext; |
172 | 270k | } while (false); |
173 | 309k | } |
174 | | |
175 | 309k | if (sub == Subtype::Ext || sub == Subtype::Vendor) |
176 | 263k | { |
177 | 263k | (void)match_until({ ';', '+' }, cursor); |
178 | 263k | rawSubIndex.beg = subToken.start(); |
179 | 263k | rawSubIndex.end = subToken.end() - 1; |
180 | 263k | } |
181 | | |
182 | 309k | sub_ = sub; |
183 | | |
184 | 309k | if (cursor.eof()) |
185 | 276k | return; |
186 | | |
187 | | // Parse suffix |
188 | 33.2k | Mime::Suffix suffix = Suffix::None; |
189 | 33.2k | if (match_literal('+', cursor)) |
190 | 1.88k | { |
191 | | |
192 | 1.88k | if (cursor.eof()) |
193 | 7 | raise("Malformed Media Type, expected suffix, got EOF"); |
194 | | |
195 | 1.88k | StreamCursor::Token suffixToken(cursor); |
196 | | |
197 | 1.88k | do |
198 | 1.88k | { |
199 | 1.88k | #define SUFFIX(val, s, _) \ |
200 | 9.54k | if (match_string(s, sizeof s - 1, cursor, CaseSensitivity::Insensitive)) \ |
201 | 9.54k | { \ |
202 | 1.30k | suffix = Suffix::val; \ |
203 | 1.30k | break; \ |
204 | 1.30k | } |
205 | 1.88k | MIME_SUFFIXES |
206 | 581 | #undef SUFFIX |
207 | 581 | suffix = Suffix::Ext; |
208 | 581 | } while (false); |
209 | | |
210 | 1.88k | if (suffix == Suffix::Ext) |
211 | 574 | { |
212 | 574 | (void)match_until({ ';', '+' }, cursor); |
213 | 574 | rawSuffixIndex.beg = suffixToken.start(); |
214 | 574 | rawSuffixIndex.end = suffixToken.end() - 1; |
215 | 574 | } |
216 | | |
217 | 1.88k | suffix_ = suffix; |
218 | 1.88k | } |
219 | | |
220 | | // Parse parameters |
221 | 309k | while (!cursor.eof()) |
222 | 276k | { |
223 | | |
224 | 276k | if (cursor.current() == ';' || cursor.current() == ' ') |
225 | 132k | { |
226 | 132k | int c; |
227 | 132k | if ((c = cursor.next()) == StreamCursor::Eof || c == 0) |
228 | 219 | raise("Malformed Media Type, expected parameter got EOF"); |
229 | 132k | cursor.advance(1); |
230 | 132k | } |
231 | | |
232 | 143k | else if (match_literal('q', cursor)) |
233 | 3.26k | { |
234 | | |
235 | 3.26k | if (cursor.eof()) |
236 | 21 | raise("Invalid quality factor"); |
237 | | |
238 | 3.26k | if (match_literal('=', cursor)) |
239 | 3.22k | { |
240 | 3.22k | float val; |
241 | 3.22k | std::size_t qvalue_len; |
242 | | |
243 | 3.22k | if (!Header::strToQvalue(cursor.offset(), &val, &qvalue_len)) |
244 | 87 | { |
245 | 87 | raise("Invalid quality factor"); |
246 | 87 | } |
247 | 3.22k | cursor.advance(qvalue_len); |
248 | 3.22k | q_ = Q::fromFloat(val); |
249 | 3.22k | } |
250 | 40 | else |
251 | 40 | { |
252 | 40 | raise("Missing quality factor"); |
253 | 40 | } |
254 | 3.26k | } |
255 | 140k | else |
256 | 140k | { |
257 | 140k | StreamCursor::Token keyToken(cursor); |
258 | 140k | (void)match_until('=', cursor); |
259 | | |
260 | 140k | int c; |
261 | 140k | if (cursor.eof() || (c = cursor.next()) == StreamCursor::Eof || c == 0) |
262 | 585 | raise("Unfinished Media Type parameter"); |
263 | | |
264 | 140k | std::string key = keyToken.text(); |
265 | 140k | cursor.advance(1); |
266 | | |
267 | 140k | StreamCursor::Token valueToken(cursor); |
268 | 140k | (void)match_until({ ' ', ';' }, cursor); |
269 | 140k | params.insert(std::make_pair(std::move(key), valueToken.text())); |
270 | 140k | } |
271 | 276k | } |
272 | 33.2k | } |
273 | | |
274 | 0 | void MediaType::setQuality(Q quality) { q_ = quality; } |
275 | | |
276 | | std::optional<std::string> MediaType::getParam(const std::string& name) const |
277 | 0 | { |
278 | 0 | auto it = params.find(name); |
279 | 0 | if (it == std::end(params)) |
280 | 0 | { |
281 | 0 | return std::nullopt; |
282 | 0 | } |
283 | | |
284 | 0 | return std::optional<std::string>(it->second); |
285 | 0 | } |
286 | | |
287 | | void MediaType::setParam(const std::string& name, std::string value) |
288 | 0 | { |
289 | 0 | params[name] = std::move(value); |
290 | 0 | } |
291 | | |
292 | | std::string MediaType::toString() const |
293 | 294k | { |
294 | | |
295 | 294k | if (!raw_.empty()) |
296 | 294k | return raw_; |
297 | | |
298 | 1 | auto topString = [](Mime::Type top) -> const char* { |
299 | 1 | switch (top) |
300 | 1 | { |
301 | 0 | #define TYPE(val, str) \ |
302 | 0 | case Mime::Type::val: \ |
303 | 0 | return str; |
304 | 0 | MIME_TYPES |
305 | 0 | #undef TYPE |
306 | 1 | default: |
307 | 1 | return ""; |
308 | 1 | } |
309 | 1 | }; |
310 | | |
311 | 1 | auto subString = [](Mime::Subtype sub) -> const char* { |
312 | 1 | switch (sub) |
313 | 1 | { |
314 | 0 | #define SUB_TYPE(val, str) \ |
315 | 0 | case Mime::Subtype::val: \ |
316 | 0 | return str; |
317 | 0 | MIME_SUBTYPES |
318 | 0 | #undef TYPE |
319 | 1 | default: |
320 | 1 | return ""; |
321 | 1 | } |
322 | 1 | }; |
323 | | |
324 | 1 | auto suffixString = [](Mime::Suffix suffix) -> const char* { |
325 | 0 | switch (suffix) |
326 | 0 | { |
327 | 0 | #define SUFFIX(val, str, _) \ |
328 | 0 | case Mime::Suffix::val: \ |
329 | 0 | return "+" str; |
330 | 0 | MIME_SUFFIXES |
331 | 0 | #undef SUFFIX |
332 | 0 | default: |
333 | 0 | return ""; |
334 | 0 | } |
335 | 0 | }; |
336 | | |
337 | 1 | std::string res; |
338 | 1 | res.reserve(128); |
339 | 1 | res += topString(top_); |
340 | 1 | res += "/"; |
341 | 1 | res += subString(sub_); |
342 | 1 | if (suffix_ != Suffix::None) |
343 | 0 | { |
344 | 0 | res += suffixString(suffix_); |
345 | 0 | } |
346 | | |
347 | 1 | if (q_.has_value()) |
348 | 0 | { |
349 | 0 | Q quality = *q_; |
350 | 0 | res += "; "; |
351 | 0 | res += quality.toString(); |
352 | 0 | } |
353 | | |
354 | 1 | for (const auto& param : params) |
355 | 0 | { |
356 | 0 | res += "; "; |
357 | 0 | res += param.first + "=" + param.second; |
358 | 0 | } |
359 | | |
360 | 1 | return res; |
361 | 294k | } |
362 | | |
363 | | bool MediaType::isValid() const |
364 | 0 | { |
365 | 0 | return top_ != Type::None && sub_ != Subtype::None; |
366 | 0 | } |
367 | | |
368 | | } // namespace Pistache::Http::Mime |