Crow  1.1
A C++ microframework for the web
 
Loading...
Searching...
No Matches
multipart.h
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
12namespace 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 operator int() const { return std::stoi(value); } ///< Returns \ref value as integer
28 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 {
38 if (headers.count(key))
39 {
40 return headers.find(key)->second;
41 }
42 static O empty;
43 return empty;
44 }
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 {
50 return get_header_value_object<header>(headers, key);
51 }
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 operator int() const { return std::stoi(body); } ///< Returns \ref body as integer
63 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 {
68 }
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 {
85 }
86
87 part get_part_by_name(const std::string& name)
88 {
89 mp_map::iterator result = part_map.find(name);
90 if (result != part_map.end())
91 return result->second;
92 else
93 return {};
94 }
95
96 /// Represent all parts as a string (**does not include message headers**)
97 std::string dump() const override
98 {
99 std::stringstream str;
100 std::string delimiter = dd + boundary;
101
102 for (unsigned i = 0; i < parts.size(); i++)
103 {
104 str << delimiter << crlf;
105 str << dump(i);
106 }
107 str << delimiter << dd << crlf;
108 return str.str();
109 }
110
111 /// Represent an individual part as a string
112 std::string dump(int part_) const
113 {
114 std::stringstream str;
115 part item = parts[part_];
116 for (auto& item_h : item.headers)
117 {
118 str << item_h.first << ": " << item_h.second.value;
119 for (auto& it : item_h.second.params)
120 {
121 str << "; " << it.first << '=' << pad(it.second);
122 }
123 str << crlf;
124 }
125 str << crlf;
126 str << item.body << crlf;
127 return str.str();
128 }
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 {
134 if (!boundary.empty())
135 content_type = "multipart/form-data; boundary=" + boundary;
136 for (auto& item : parts)
137 {
138 part_map.emplace(
139 (get_header_object(item.headers, "Content-Disposition").params.find("name")->second),
140 item);
141 }
142 }
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 {
150 if (!boundary.empty())
151 {
152 content_type = "multipart/form-data; boundary=" + boundary;
153 parse_body(req.body);
154 }
155 else
156 {
157 throw bad_request("Empty boundary in multipart message");
158 }
159 }
160
161 private:
162 std::string get_boundary(const std::string& header) const
163 {
164 constexpr char boundary_text[] = "boundary=";
165 size_t found = header.find(boundary_text);
166 if (found != std::string::npos)
167 {
168 std::string to_return(header.substr(found + strlen(boundary_text)));
169 if (to_return[0] == '\"')
170 {
171 to_return = to_return.substr(1, to_return.length() - 2);
172 }
173 return to_return;
174 }
175 return std::string();
176 }
177
178 void parse_body(std::string body)
179 {
180 std::string delimiter = dd + boundary;
181
182 // TODO(EDev): Exit on error
183 while (body != (crlf))
184 {
185 size_t found = body.find(delimiter);
186 if (found == std::string::npos)
187 {
188 // did not find delimiter; probably an ill-formed body; throw to indicate the issue to user
189 throw bad_request("Unable to find delimiter in multipart message. Probably ill-formed body");
190 }
191 std::string section = body.substr(0, found);
192
193 // +2 is the CRLF.
194 // We don't check it and delete it so that the same delimiter can be used for The last delimiter (--delimiter--CRLF).
195 body.erase(0, found + delimiter.length() + 2);
196 if (!section.empty())
197 {
198 part parsed_section(parse_section(section));
199 part_map.emplace(
200 (get_header_object(parsed_section.headers, "Content-Disposition").params.find("name")->second),
201 parsed_section);
202 parts.push_back(std::move(parsed_section));
203 }
204 }
205 }
206
207 part parse_section(std::string& section)
208 {
209 struct part to_return;
210
211 size_t found = section.find(crlf + crlf);
212 std::string head_line = section.substr(0, found + 2);
213 section.erase(0, found + 4);
214
215 parse_section_head(head_line, to_return);
216 to_return.body = section.substr(0, section.length() - 2);
217 return to_return;
218 }
219
220 void parse_section_head(std::string& lines, part& part)
221 {
222 while (!lines.empty())
223 {
224 header to_add;
225
226 const size_t found_crlf = lines.find(crlf);
227 std::string line = lines.substr(0, found_crlf);
228 std::string key;
229 lines.erase(0, found_crlf + 2);
230 // Add the header if available
231 if (!line.empty())
232 {
233 const size_t found_semicolon = line.find("; ");
234 std::string header = line.substr(0, found_semicolon);
235 if (found_semicolon != std::string::npos)
236 line.erase(0, found_semicolon + 2);
237 else
238 line = std::string();
239
240 size_t header_split = header.find(": ");
241 key = header.substr(0, header_split);
242
243 to_add.value = header.substr(header_split + 2);
244 }
245
246 // Add the parameters
247 while (!line.empty())
248 {
249 const size_t found_semicolon = line.find("; ");
250 std::string param = line.substr(0, found_semicolon);
251 if (found_semicolon != std::string::npos)
252 line.erase(0, found_semicolon + 2);
253 else
254 line = std::string();
255
256 size_t param_split = param.find('=');
257
258 std::string value = param.substr(param_split + 1);
259
260 to_add.params.emplace(param.substr(0, param_split), trim(value));
261 }
262 part.headers.emplace(key, to_add);
263 }
264 }
265
266 inline std::string trim(std::string& string, const char& excess = '"') const
267 {
268 if (string.length() > 1 && string[0] == excess && string[string.length() - 1] == excess)
269 return string.substr(1, string.length() - 2);
270 return string;
271 }
272
273 inline std::string pad(std::string& string, const char& padding = '"') const
274 {
275 return (padding + string + padding);
276 }
277 };
278 } // namespace multipart
279} // namespace crow
const O & get_header_value_object(const T &headers, const std::string &key)
Find and return the value object associated with the key. (returns an empty class if nothing is found...
Definition multipart.h:36
std::unordered_multimap< std::string, part, ci_hash, ci_key_eq > mp_map
Multipart map (key is the name parameter).
Definition multipart.h:72
const header & get_header_object(const T &headers, const std::string &key)
Same as get_header_value_object() but for multipart::header.
Definition multipart.h:48
std::unordered_multimap< std::string, header, ci_hash, ci_key_eq > mph_map
Multipart header map (key is header key).
Definition multipart.h:32
The main namespace of the library. In this namespace is defined the most important classes and functi...
const std::string & get_header_value(const T &headers, const std::string &key)
Find and return the value associated with the key. (returns an empty string if nothing is found)
Definition http_request.h:24
Definition exceptions.h:7
The first part in a section, contains metadata about the part.
Definition multipart.h:23
std::unordered_map< std::string, std::string > params
The parameters of the header, come after the value
Definition multipart.h:25
std::string value
The first part of the header, usually Content-Type or Content-Disposition
Definition multipart.h:24
The parsed multipart request/response.
Definition multipart.h:76
message(const ci_map &headers_, const std::string &boundary_, const std::vector< part > &sections)
Default constructor using default values.
Definition multipart.h:131
std::vector< part > parts
The individual parts of the message.
Definition multipart.h:79
message(const request &req)
Create a multipart message from a request data.
Definition multipart.h:145
std::string boundary
The text boundary that separates different parts
Definition multipart.h:78
mp_map part_map
The individual parts of the message, organized in a map with the name header parameter being the key.
Definition multipart.h:80
std::string dump(int part_) const
Represent an individual part as a string.
Definition multipart.h:112
std::string dump() const override
Represent all parts as a string (does not include message headers)
Definition multipart.h:97
ci_map headers
The request/response headers.
Definition multipart.h:77
One part of the multipart message.
Definition multipart.h:58
mph_map headers
(optional) The first part before the data, Contains information regarding the type of data and encodi...
Definition multipart.h:59
std::string body
The actual data in the part.
Definition multipart.h:60
An HTTP request.
Definition http_request.h:36
An abstract class that allows any other class to be returned by a handler.
Definition returnable.h:9