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