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  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, parts, part_map);
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, std::vector<part>& sections, mp_map& part_map)
172  {
173 
174  std::string delimiter = dd + boundary;
175 
176  // TODO(EDev): Exit on error
177  while (body != (crlf))
178  {
179  size_t found = body.find(delimiter);
180  if (found == std::string::npos)
181  {
182  // did not find delimiter; probably an ill-formed body; ignore the rest
183  break;
184  }
185  std::string section = body.substr(0, found);
186 
187  // +2 is the CRLF.
188  // We don't check it and delete it so that the same delimiter can be used for The last delimiter (--delimiter--CRLF).
189  body.erase(0, found + delimiter.length() + 2);
190  if (!section.empty())
191  {
192  part parsed_section(parse_section(section));
193  part_map.emplace(
194  (get_header_object(parsed_section.headers, "Content-Disposition").params.find("name")->second),
195  parsed_section);
196  sections.push_back(std::move(parsed_section));
197  }
198  }
199  }
200 
201  part parse_section(std::string& section)
202  {
203  struct part to_return;
204 
205  size_t found = section.find(crlf + crlf);
206  std::string head_line = section.substr(0, found + 2);
207  section.erase(0, found + 4);
208 
209  parse_section_head(head_line, to_return);
210  to_return.body = section.substr(0, section.length() - 2);
211  return to_return;
212  }
213 
214  void parse_section_head(std::string& lines, part& part)
215  {
216  while (!lines.empty())
217  {
218  header to_add;
219 
220  size_t found = lines.find(crlf);
221  std::string line = lines.substr(0, found);
222  std::string key;
223  lines.erase(0, found + 2);
224  // Add the header if available
225  if (!line.empty())
226  {
227  size_t found = line.find("; ");
228  std::string header = line.substr(0, found);
229  if (found != std::string::npos)
230  line.erase(0, found + 2);
231  else
232  line = std::string();
233 
234  size_t header_split = header.find(": ");
235  key = header.substr(0, header_split);
236 
237  to_add.value = header.substr(header_split + 2);
238  }
239 
240  // Add the parameters
241  while (!line.empty())
242  {
243  size_t found = line.find("; ");
244  std::string param = line.substr(0, found);
245  if (found != std::string::npos)
246  line.erase(0, found + 2);
247  else
248  line = std::string();
249 
250  size_t param_split = param.find('=');
251 
252  std::string value = param.substr(param_split + 1);
253 
254  to_add.params.emplace(param.substr(0, param_split), trim(value));
255  }
256  part.headers.emplace(key, to_add);
257  }
258  }
259 
260  inline std::string trim(std::string& string, const char& excess = '"') const
261  {
262  if (string.length() > 1 && string[0] == excess && string[string.length() - 1] == excess)
263  return string.substr(1, string.length() - 2);
264  return string;
265  }
266 
267  inline std::string pad(std::string& string, const char& padding = '"') const
268  {
269  return (padding + string + padding);
270  }
271  };
272  } // namespace multipart
273 } // 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