Crow  1.1
A C++ microframework for the web
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 
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  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  {
84  return crow::get_header_value(headers, key);
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
std::unordered_multimap< std::string, part, ci_hash, ci_key_eq > mp_map
Multipart map (key is the name parameter).
Definition: multipart.h:72
std::unordered_multimap< std::string, header, ci_hash, ci_key_eq > mph_map
Multipart header map (key is header key).
Definition: multipart.h:32
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
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
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