/src/crow/include/crow/multipart.h
Line | Count | Source |
1 | | #pragma once |
2 | | |
3 | | #include <string> |
4 | | #include <vector> |
5 | | #include <sstream> |
6 | | |
7 | | #include "crow/http_request.h" |
8 | | #include "crow/returnable.h" |
9 | | #include "crow/ci_map.h" |
10 | | #include "crow/exceptions.h" |
11 | | |
12 | | namespace crow |
13 | | { |
14 | | |
15 | | /// Encapsulates anything related to processing and organizing `multipart/xyz` messages |
16 | | namespace multipart |
17 | | { |
18 | | |
19 | | const std::string dd = "--"; |
20 | | |
21 | | /// The first part in a section, contains metadata about the part |
22 | | struct header |
23 | | { |
24 | | std::string value; ///< The first part of the header, usually `Content-Type` or `Content-Disposition` |
25 | | std::unordered_map<std::string, std::string> params; ///< The parameters of the header, come after the `value` |
26 | | |
27 | 0 | operator int() const { return std::stoi(value); } ///< Returns \ref value as integer |
28 | 0 | operator double() const { return std::stod(value); } ///< Returns \ref value as double |
29 | | }; |
30 | | |
31 | | /// Multipart header map (key is header key). |
32 | | using mph_map = std::unordered_multimap<std::string, header, ci_hash, ci_key_eq>; |
33 | | |
34 | | /// Find and return the value object associated with the key. (returns an empty class if nothing is found) |
35 | | template<typename O, typename T> |
36 | | inline const O& get_header_value_object(const T& headers, const std::string& key) |
37 | 0 | { |
38 | 0 | if (headers.count(key)) |
39 | 0 | { |
40 | 0 | return headers.find(key)->second; |
41 | 0 | } |
42 | 0 | static O empty; |
43 | 0 | return empty; |
44 | 0 | } |
45 | | |
46 | | /// Same as \ref get_header_value_object() but for \ref multipart.header |
47 | | template<typename T> |
48 | | inline const header& get_header_object(const T& headers, const std::string& key) |
49 | 0 | { |
50 | 0 | return get_header_value_object<header>(headers, key); |
51 | 0 | } |
52 | | |
53 | | ///One part of the multipart message |
54 | | |
55 | | /// |
56 | | /// It is usually separated from other sections by a `boundary` |
57 | | struct part |
58 | | { |
59 | | mph_map headers; ///< (optional) The first part before the data, Contains information regarding the type of data and encoding |
60 | | std::string body; ///< The actual data in the part |
61 | | |
62 | 0 | operator int() const { return std::stoi(body); } ///< Returns \ref body as integer |
63 | 0 | operator double() const { return std::stod(body); } ///< Returns \ref body as double |
64 | | |
65 | | const header& get_header_object(const std::string& key) const |
66 | 0 | { |
67 | 0 | return multipart::get_header_object(headers, key); |
68 | 0 | } |
69 | | }; |
70 | | |
71 | | /// Multipart map (key is the name parameter). |
72 | | using mp_map = std::unordered_multimap<std::string, part, ci_hash, ci_key_eq>; |
73 | | |
74 | | /// The parsed multipart request/response |
75 | | struct message : public returnable |
76 | | { |
77 | | ci_map headers; ///< The request/response headers |
78 | | std::string boundary; ///< The text boundary that separates different `parts` |
79 | | std::vector<part> parts; ///< The individual parts of the message |
80 | | mp_map part_map; ///< The individual parts of the message, organized in a map with the `name` header parameter being the key |
81 | | |
82 | | const std::string& get_header_value(const std::string& key) const |
83 | 0 | { |
84 | 0 | return crow::get_header_value(headers, key); |
85 | 0 | } |
86 | | |
87 | | part get_part_by_name(const std::string& name) |
88 | 0 | { |
89 | 0 | mp_map::iterator result = part_map.find(name); |
90 | 0 | if (result != part_map.end()) |
91 | 0 | return result->second; |
92 | 0 | else |
93 | 0 | return {}; |
94 | 0 | } |
95 | | |
96 | | /// Represent all parts as a string (**does not include message headers**) |
97 | | std::string dump() const override |
98 | 0 | { |
99 | 0 | std::stringstream str; |
100 | 0 | std::string delimiter = dd + boundary; |
101 | 0 |
|
102 | 0 | for (unsigned i = 0; i < parts.size(); i++) |
103 | 0 | { |
104 | 0 | str << delimiter << crlf; |
105 | 0 | str << dump(i); |
106 | 0 | } |
107 | 0 | str << delimiter << dd << crlf; |
108 | 0 | return str.str(); |
109 | 0 | } |
110 | | |
111 | | /// Represent an individual part as a string |
112 | | std::string dump(int part_) const |
113 | 0 | { |
114 | 0 | std::stringstream str; |
115 | 0 | part item = parts[part_]; |
116 | 0 | for (auto& item_h : item.headers) |
117 | 0 | { |
118 | 0 | str << item_h.first << ": " << item_h.second.value; |
119 | 0 | for (auto& it : item_h.second.params) |
120 | 0 | { |
121 | 0 | str << "; " << it.first << '=' << pad(it.second); |
122 | 0 | } |
123 | 0 | str << crlf; |
124 | 0 | } |
125 | 0 | str << crlf; |
126 | 0 | str << item.body << crlf; |
127 | 0 | return str.str(); |
128 | 0 | } |
129 | | |
130 | | /// Default constructor using default values |
131 | | message(const ci_map& headers_, const std::string& boundary_, const std::vector<part>& sections): |
132 | | returnable("multipart/form-data; boundary=CROW-BOUNDARY"), headers(headers_), boundary(boundary_), parts(sections) |
133 | 0 | { |
134 | 0 | if (!boundary.empty()) |
135 | 0 | content_type = "multipart/form-data; boundary=" + boundary; |
136 | 0 | for (auto& item : parts) |
137 | 0 | { |
138 | 0 | part_map.emplace( |
139 | 0 | (get_header_object(item.headers, "Content-Disposition").params.find("name")->second), |
140 | 0 | item); |
141 | 0 | } |
142 | 0 | } |
143 | | |
144 | | /// Create a multipart message from a request data |
145 | | explicit message(const request& req): |
146 | | returnable("multipart/form-data; boundary=CROW-BOUNDARY"), |
147 | | headers(req.headers), |
148 | | boundary(get_boundary(get_header_value("Content-Type"))) |
149 | 0 | { |
150 | 0 | if (!boundary.empty()) |
151 | 0 | { |
152 | 0 | content_type = "multipart/form-data; boundary=" + boundary; |
153 | 0 | parse_body(req.body); |
154 | 0 | } |
155 | 0 | else |
156 | 0 | { |
157 | 0 | throw bad_request("Empty boundary in multipart message"); |
158 | 0 | } |
159 | 0 | } |
160 | | |
161 | | private: |
162 | | std::string get_boundary(const std::string& header) const |
163 | 0 | { |
164 | 0 | constexpr char boundary_text[] = "boundary="; |
165 | 0 | size_t found = header.find(boundary_text); |
166 | 0 | if (found != std::string::npos) |
167 | 0 | { |
168 | 0 | std::string to_return(header.substr(found + strlen(boundary_text))); |
169 | 0 | if (to_return[0] == '\"') |
170 | 0 | { |
171 | 0 | to_return = to_return.substr(1, to_return.length() - 2); |
172 | 0 | } |
173 | 0 | return to_return; |
174 | 0 | } |
175 | 0 | return std::string(); |
176 | 0 | } |
177 | | |
178 | | void parse_body(std::string body) |
179 | 0 | { |
180 | 0 | std::string delimiter = dd + boundary; |
181 | 0 |
|
182 | 0 | // TODO(EDev): Exit on error |
183 | 0 | while (body != (crlf)) |
184 | 0 | { |
185 | 0 | size_t found = body.find(delimiter); |
186 | 0 | if (found == std::string::npos) |
187 | 0 | { |
188 | 0 | // did not find delimiter; probably an ill-formed body; throw to indicate the issue to user |
189 | 0 | throw bad_request("Unable to find delimiter in multipart message. Probably ill-formed body"); |
190 | 0 | } |
191 | 0 | std::string section = body.substr(0, found); |
192 | 0 |
|
193 | 0 | // +2 is the CRLF. |
194 | 0 | // We don't check it and delete it so that the same delimiter can be used for The last delimiter (--delimiter--CRLF). |
195 | 0 | body.erase(0, found + delimiter.length() + 2); |
196 | 0 | if (!section.empty()) |
197 | 0 | { |
198 | 0 | part parsed_section(parse_section(section)); |
199 | 0 | part_map.emplace( |
200 | 0 | (get_header_object(parsed_section.headers, "Content-Disposition").params.find("name")->second), |
201 | 0 | parsed_section); |
202 | 0 | parts.push_back(std::move(parsed_section)); |
203 | 0 | } |
204 | 0 | } |
205 | 0 | } |
206 | | |
207 | | part parse_section(std::string& section) |
208 | 0 | { |
209 | 0 | struct part to_return; |
210 | 0 |
|
211 | 0 | size_t found = section.find(crlf + crlf); |
212 | 0 | std::string head_line = section.substr(0, found + 2); |
213 | 0 | section.erase(0, found + 4); |
214 | 0 |
|
215 | 0 | parse_section_head(head_line, to_return); |
216 | 0 | to_return.body = section.substr(0, section.length() - 2); |
217 | 0 | return to_return; |
218 | 0 | } |
219 | | |
220 | | void parse_section_head(std::string& lines, part& part) |
221 | 0 | { |
222 | 0 | while (!lines.empty()) |
223 | 0 | { |
224 | 0 | header to_add; |
225 | 0 |
|
226 | 0 | const size_t found_crlf = lines.find(crlf); |
227 | 0 | std::string line = lines.substr(0, found_crlf); |
228 | 0 | std::string key; |
229 | 0 | lines.erase(0, found_crlf + 2); |
230 | 0 | // Add the header if available |
231 | 0 | if (!line.empty()) |
232 | 0 | { |
233 | 0 | const size_t found_semicolon = line.find("; "); |
234 | 0 | std::string header = line.substr(0, found_semicolon); |
235 | 0 | if (found_semicolon != std::string::npos) |
236 | 0 | line.erase(0, found_semicolon + 2); |
237 | 0 | else |
238 | 0 | line = std::string(); |
239 | 0 |
|
240 | 0 | size_t header_split = header.find(": "); |
241 | 0 | key = header.substr(0, header_split); |
242 | 0 |
|
243 | 0 | to_add.value = header.substr(header_split + 2); |
244 | 0 | } |
245 | 0 |
|
246 | 0 | // Add the parameters |
247 | 0 | while (!line.empty()) |
248 | 0 | { |
249 | 0 | const size_t found_semicolon = line.find("; "); |
250 | 0 | std::string param = line.substr(0, found_semicolon); |
251 | 0 | if (found_semicolon != std::string::npos) |
252 | 0 | line.erase(0, found_semicolon + 2); |
253 | 0 | else |
254 | 0 | line = std::string(); |
255 | 0 |
|
256 | 0 | size_t param_split = param.find('='); |
257 | 0 |
|
258 | 0 | std::string value = param.substr(param_split + 1); |
259 | 0 |
|
260 | 0 | to_add.params.emplace(param.substr(0, param_split), trim(value)); |
261 | 0 | } |
262 | 0 | part.headers.emplace(key, to_add); |
263 | 0 | } |
264 | 0 | } |
265 | | |
266 | | inline std::string trim(std::string& string, const char& excess = '"') const |
267 | 0 | { |
268 | 0 | if (string.length() > 1 && string[0] == excess && string[string.length() - 1] == excess) |
269 | 0 | return string.substr(1, string.length() - 2); |
270 | 0 | return string; |
271 | 0 | } |
272 | | |
273 | | inline std::string pad(std::string& string, const char& padding = '"') const |
274 | 0 | { |
275 | 0 | return (padding + string + padding); |
276 | 0 | } |
277 | | }; |
278 | | } // namespace multipart |
279 | | } // namespace crow |