Crow  1.1
A C++ microframework for the web
multipart_view.h
1 #pragma once
2 
3 #include <charconv>
4 #include <string>
5 #include <vector>
6 #include <string_view>
7 #include <sstream>
8 
9 #include "crow/http_request.h"
10 // for crow::multipart::dd
11 #include "crow/multipart.h"
12 #include "crow/ci_map.h"
13 
14 namespace crow
15 {
16 
17  /// Encapsulates anything related to processing and organizing `multipart/xyz` messages
18  namespace multipart
19  {
20  /// The first part in a section, contains metadata about the part
21  struct header_view
22  {
23  std::string_view value; ///< The first part of the header, usually `Content-Type` or `Content-Disposition`
24  std::unordered_map<std::string_view, std::string_view> params; ///< The parameters of the header, come after the `value`
25 
26  /// Returns \ref value as integer
27  operator int() const
28  {
29  int result = 0;
30  std::from_chars(value.data(), value.data() + value.size(), result);
31  return result;
32  }
33 
34  /// Returns \ref value as double
35  operator double() const
36  {
37  // There's no std::from_chars for floating-point types in a lot of STLs
38  return std::stod(static_cast<std::string>(value));
39  }
40  };
41 
42  /// Multipart header map (key is header key).
43  using mph_view_map = std::unordered_multimap<std::string_view, header_view, ci_hash, ci_key_eq>;
44 
45  /// Finds and returns the header with the specified key. (returns an empty header if nothing is found)
46  inline const header_view& get_header_object(const mph_view_map& headers, const std::string_view key)
47  {
48  const auto header = headers.find(key);
49  if (header != headers.cend())
50  {
51  return header->second;
52  }
53 
54  static header_view empty;
55  return empty;
56  }
57 
58  /// String padded with the specified padding (double quotes by default)
59  struct padded
60  {
61  std::string_view value; ///< String to pad
62  const char padding = '"'; ///< Padding to use
63 
64  /// Outputs padded value to the stream
65  friend std::ostream& operator<<(std::ostream& stream, const padded value_)
66  {
67  return stream << value_.padding << value_.value << value_.padding;
68  }
69  };
70 
71  ///One part of the multipart message
72 
73  ///
74  /// It is usually separated from other sections by a `boundary`
75  struct part_view
76  {
77  mph_view_map headers; ///< (optional) The first part before the data, Contains information regarding the type of data and encoding
78  std::string_view body; ///< The actual data in the part
79 
80  /// Returns \ref body as integer
81  operator int() const
82  {
83  int result = 0;
84  std::from_chars(body.data(), body.data() + body.size(), result);
85  return result;
86  }
87 
88  /// Returns \ref body as double
89  operator double() const
90  {
91  // There's no std::from_chars for floating-point types in a lot of STLs
92  return std::stod(static_cast<std::string>(body));
93  }
94 
95  const header_view& get_header_object(const std::string_view key) const
96  {
98  }
99 
100  friend std::ostream& operator<<(std::ostream& stream, const part_view& part)
101  {
102  for (const auto& [header_key, header_value] : part.headers)
103  {
104  stream << header_key << ": " << header_value.value;
105  for (const auto& [param_key, param_value] : header_value.params)
106  {
107  stream << "; " << param_key << '=' << padded{param_value};
108  }
109  stream << crlf;
110  }
111  stream << crlf;
112  stream << part.body << crlf;
113  return stream;
114  }
115  };
116 
117  /// Multipart map (key is the name parameter).
118  using mp_view_map = std::unordered_multimap<std::string_view, part_view, ci_hash, ci_key_eq>;
119 
120  /// The parsed multipart request/response
122  {
123  std::reference_wrapper<const ci_map> headers; ///< The request/response headers
124  std::string boundary; ///< The text boundary that separates different `parts`
125  std::vector<part_view> parts; ///< The individual parts of the message
126  mp_view_map part_map; ///< The individual parts of the message, organized in a map with the `name` header parameter being the key
127 
128  const std::string& get_header_value(const std::string& key) const
129  {
130  return crow::get_header_value(headers.get(), key);
131  }
132 
133  part_view get_part_by_name(const std::string_view name)
134  {
135  mp_view_map::iterator result = part_map.find(name);
136  if (result != part_map.end())
137  return result->second;
138  else
139  return {};
140  }
141 
142  friend std::ostream& operator<<(std::ostream& stream, const message_view message)
143  {
144  std::string delimiter = dd + message.boundary;
145 
146  for (const part_view& part : message.parts)
147  {
148  stream << delimiter << crlf;
149  stream << part;
150  }
151  stream << delimiter << dd << crlf;
152 
153  return stream;
154  }
155 
156  /// Represent all parts as a string (**does not include message headers**)
157  std::string dump() const
158  {
159  std::ostringstream str;
160  str << *this;
161  return std::move(str).str();
162  }
163 
164  /// Represent an individual part as a string
165  std::string dump(int part_) const
166  {
167  std::ostringstream str;
168  str << parts.at(part_);
169  return std::move(str).str();
170  }
171 
172  /// Default constructor using default values
173  message_view(const ci_map& headers_, const std::string& boundary_, const std::vector<part_view>& sections):
174  headers(headers_), boundary(boundary_), parts(sections)
175  {
176  for (const part_view& item : parts)
177  {
178  part_map.emplace(
179  (get_header_object(item.headers, "Content-Disposition").params.find("name")->second),
180  item);
181  }
182  }
183 
184  /// Create a multipart message from a request data
185  explicit message_view(const request& req):
186  headers(req.headers),
187  boundary(get_boundary(get_header_value("Content-Type")))
188  {
189  parse_body(req.body);
190  }
191 
192  private:
193  std::string_view get_boundary(const std::string_view header) const
194  {
195  constexpr std::string_view boundary_text = "boundary=";
196  const size_t found = header.find(boundary_text);
197  if (found == std::string_view::npos)
198  {
199  return std::string_view();
200  }
201 
202  const std::string_view to_return = header.substr(found + boundary_text.size());
203  if (to_return[0] == '\"')
204  {
205  return to_return.substr(1, to_return.length() - 2);
206  }
207  return to_return;
208  }
209 
210  void parse_body(std::string_view body)
211  {
212  const std::string delimiter = dd + boundary;
213 
214  // TODO(EDev): Exit on error
215  while (body != (crlf))
216  {
217  const size_t found = body.find(delimiter);
218  if (found == std::string_view::npos)
219  {
220  // did not find delimiter; probably an ill-formed body; ignore the rest
221  break;
222  }
223 
224  const std::string_view section = body.substr(0, found);
225 
226  // +2 is the CRLF.
227  // We don't check it and delete it so that the same delimiter can be used for The last delimiter (--delimiter--CRLF).
228  body = body.substr(found + delimiter.length() + 2);
229  if (!section.empty())
230  {
231  part_view parsed_section = parse_section(section);
232  part_map.emplace(
233  (get_header_object(parsed_section.headers, "Content-Disposition").params.find("name")->second),
234  parsed_section);
235  parts.push_back(std::move(parsed_section));
236  }
237  }
238  }
239 
240  part_view parse_section(std::string_view section)
241  {
242  constexpr static std::string_view crlf2 = "\r\n\r\n";
243 
244  const size_t found = section.find(crlf2);
245  const std::string_view head_line = section.substr(0, found + 2);
246  section = section.substr(found + 4);
247 
248  return part_view{
249  parse_section_head(head_line),
250  section.substr(0, section.length() - 2),
251  };
252  }
253 
254  mph_view_map parse_section_head(std::string_view lines)
255  {
256  mph_view_map result;
257 
258  while (!lines.empty())
259  {
260  header_view to_add;
261 
262  const size_t found_crlf = lines.find(crlf);
263  std::string_view line = lines.substr(0, found_crlf);
264  std::string_view key;
265  lines = lines.substr(found_crlf + 2);
266  // Add the header if available
267  if (!line.empty())
268  {
269  const size_t found_semicolon = line.find("; ");
270  std::string_view header = line.substr(0, found_semicolon);
271  if (found_semicolon != std::string_view::npos)
272  line = line.substr(found_semicolon + 2);
273  else
274  line = std::string_view();
275 
276  const size_t header_split = header.find(": ");
277  key = header.substr(0, header_split);
278 
279  to_add.value = header.substr(header_split + 2);
280  }
281 
282  // Add the parameters
283  while (!line.empty())
284  {
285  const size_t found_semicolon = line.find("; ");
286  std::string_view param = line.substr(0, found_semicolon);
287  if (found_semicolon != std::string_view::npos)
288  line = line.substr(found_semicolon + 2);
289  else
290  line = std::string_view();
291 
292  const size_t param_split = param.find('=');
293 
294  const std::string_view value = param.substr(param_split + 1);
295 
296  to_add.params.emplace(param.substr(0, param_split), trim(value));
297  }
298  result.emplace(key, to_add);
299  }
300 
301  return result;
302  }
303 
304  inline std::string_view trim(const std::string_view string, const char excess = '"') const
305  {
306  if (string.length() > 1 && string[0] == excess && string[string.length() - 1] == excess)
307  return string.substr(1, string.length() - 2);
308  return string;
309  }
310  };
311  } // namespace multipart
312 } // namespace crow
std::unordered_multimap< std::string_view, part_view, ci_hash, ci_key_eq > mp_view_map
Multipart map (key is the name parameter).
Definition: multipart_view.h:118
std::unordered_multimap< std::string_view, header_view, ci_hash, ci_key_eq > mph_view_map
Multipart header map (key is header key).
Definition: multipart_view.h:43
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
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_view.h:22
std::unordered_map< std::string_view, std::string_view > params
The parameters of the header, come after the value
Definition: multipart_view.h:24
std::string_view value
The first part of the header, usually Content-Type or Content-Disposition
Definition: multipart_view.h:23
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
The parsed multipart request/response.
Definition: multipart_view.h:122
std::reference_wrapper< const ci_map > headers
The request/response headers.
Definition: multipart_view.h:123
std::vector< part_view > parts
The individual parts of the message.
Definition: multipart_view.h:125
std::string dump(int part_) const
Represent an individual part as a string.
Definition: multipart_view.h:165
mp_view_map part_map
The individual parts of the message, organized in a map with the name header parameter being the key.
Definition: multipart_view.h:126
message_view(const ci_map &headers_, const std::string &boundary_, const std::vector< part_view > &sections)
Default constructor using default values.
Definition: multipart_view.h:173
std::string boundary
The text boundary that separates different parts
Definition: multipart_view.h:124
message_view(const request &req)
Create a multipart message from a request data.
Definition: multipart_view.h:185
std::string dump() const
Represent all parts as a string (does not include message headers)
Definition: multipart_view.h:157
String padded with the specified padding (double quotes by default)
Definition: multipart_view.h:60
std::string_view value
String to pad.
Definition: multipart_view.h:61
friend std::ostream & operator<<(std::ostream &stream, const padded value_)
Outputs padded value to the stream.
Definition: multipart_view.h:65
const char padding
Padding to use.
Definition: multipart_view.h:62
One part of the multipart message.
Definition: multipart_view.h:76
mph_view_map headers
(optional) The first part before the data, Contains information regarding the type of data and encodi...
Definition: multipart_view.h:77
std::string_view body
The actual data in the part.
Definition: multipart_view.h:78
An HTTP request.
Definition: http_request.h:36