Skip to content

Commit 293ed2c

Browse files
authored
Merge pull request ipkn#358 from CrowCpp/multipart_improvements
Improved how multipart messages work
2 parents 81a1b49 + af81ca9 commit 293ed2c

1 file changed

Lines changed: 80 additions & 19 deletions

File tree

include/crow/multipart.h

Lines changed: 80 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,44 +6,88 @@
66

77
#include "crow/http_request.h"
88
#include "crow/returnable.h"
9+
#include "crow/ci_map.h"
910

1011
namespace crow
1112
{
13+
1214
/// Encapsulates anything related to processing and organizing `multipart/xyz` messages
1315
namespace multipart
1416
{
17+
1518
const std::string dd = "--";
1619

1720
/// The first part in a section, contains metadata about the part
1821
struct header
1922
{
20-
std::pair<std::string, std::string> value; ///< The first part of the header, usually `Content-Type` or `Content-Disposition`
23+
std::string value; ///< The first part of the header, usually `Content-Type` or `Content-Disposition`
2124
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
2228
};
2329

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+
2452
///One part of the multipart message
2553

2654
///
2755
/// It is usually separated from other sections by a `boundary`
2856
struct part
2957
{
30-
//TODO(EDev): restructure this to an `unordered_map<string, header>` with string being `header::value.first`
31-
std::vector<header> headers; ///< (optional) The first part before the data, Contains information regarding the type of data and encoding
32-
std::string body; ///< The actual data in the part
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+
{
66+
return multipart::get_header_object(headers, key);
67+
}
3368
};
3469

70+
/// Multipart map (key is the name parameter).
71+
using mp_map = std::unordered_multimap<std::string, part, ci_hash, ci_key_eq>;
72+
3573
/// The parsed multipart request/response
3674
struct message : public returnable
3775
{
3876
ci_map headers; ///< The request/response headers
3977
std::string boundary; ///< The text boundary that separates different `parts`
4078
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
4180

4281
const std::string& get_header_value(const std::string& key) const
4382
{
4483
return crow::get_header_value(headers, key);
4584
}
4685

86+
part get_part_by_name(const std::string& name)
87+
{
88+
return part_map.find(name)->second;
89+
}
90+
4791
/// Represent all parts as a string (**does not include message headers**)
4892
std::string dump() const override
4993
{
@@ -64,10 +108,10 @@ namespace crow
64108
{
65109
std::stringstream str;
66110
part item = parts[part_];
67-
for (header item_h : item.headers)
111+
for (auto& item_h : item.headers)
68112
{
69-
str << item_h.value.first << ": " << item_h.value.second;
70-
for (auto& it : item_h.params)
113+
str << item_h.first << ": " << item_h.second.value;
114+
for (auto& it : item_h.second.params)
71115
{
72116
str << "; " << it.first << '=' << pad(it.second);
73117
}
@@ -80,15 +124,28 @@ namespace crow
80124

81125
/// Default constructor using default values
82126
message(const ci_map& headers, const std::string& boundary, const std::vector<part>& sections):
83-
returnable("multipart/form-data"), headers(headers), boundary(boundary), parts(sections) {}
127+
returnable("multipart/form-data; boundary=CROW-BOUNDARY"), headers(headers), boundary(boundary), parts(sections)
128+
{
129+
if (!boundary.empty())
130+
content_type = "multipart/form-data; boundary=" + boundary;
131+
for (auto& item : parts)
132+
{
133+
part_map.emplace(
134+
(get_header_object(item.headers, "Content-Disposition").params.find("name")->second),
135+
item);
136+
}
137+
}
84138

85139
/// Create a multipart message from a request data
86140
message(const request& req):
87-
returnable("multipart/form-data"),
141+
returnable("multipart/form-data; boundary=CROW-BOUNDARY"),
88142
headers(req.headers),
89-
boundary(get_boundary(get_header_value("Content-Type"))),
90-
parts(parse_body(req.body))
91-
{}
143+
boundary(get_boundary(get_header_value("Content-Type")))
144+
{
145+
if (!boundary.empty())
146+
content_type = "multipart/form-data; boundary=" + boundary;
147+
parse_body(req.body, parts, part_map);
148+
}
92149

93150
private:
94151
std::string get_boundary(const std::string& header) const
@@ -107,13 +164,12 @@ namespace crow
107164
return std::string();
108165
}
109166

110-
std::vector<part> parse_body(std::string body)
167+
void parse_body(std::string body, std::vector<part>& sections, mp_map& part_map)
111168
{
112169

113-
std::vector<part> sections;
114-
115170
std::string delimiter = dd + boundary;
116171

172+
// TODO(EDev): Exit on error
117173
while (body != (crlf))
118174
{
119175
size_t found = body.find(delimiter);
@@ -124,10 +180,13 @@ namespace crow
124180
body.erase(0, found + delimiter.length() + 2);
125181
if (!section.empty())
126182
{
127-
sections.emplace_back(parse_section(section));
183+
part parsed_section(parse_section(section));
184+
part_map.emplace(
185+
(get_header_object(parsed_section.headers, "Content-Disposition").params.find("name")->second),
186+
parsed_section);
187+
sections.push_back(std::move(parsed_section));
128188
}
129189
}
130-
return sections;
131190
}
132191

133192
part parse_section(std::string& section)
@@ -151,6 +210,7 @@ namespace crow
151210

152211
size_t found = lines.find(crlf);
153212
std::string line = lines.substr(0, found);
213+
std::string key;
154214
lines.erase(0, found + 2);
155215
// Add the header if available
156216
if (!line.empty())
@@ -163,8 +223,9 @@ namespace crow
163223
line = std::string();
164224

165225
size_t header_split = header.find(": ");
226+
key = header.substr(0, header_split);
166227

167-
to_add.value = std::pair<std::string, std::string>(header.substr(0, header_split), header.substr(header_split + 2));
228+
to_add.value = header.substr(header_split + 2);
168229
}
169230

170231
// Add the parameters
@@ -183,7 +244,7 @@ namespace crow
183244

184245
to_add.params.emplace(param.substr(0, param_split), trim(value));
185246
}
186-
part.headers.emplace_back(to_add);
247+
part.headers.emplace(key, to_add);
187248
}
188249
}
189250

0 commit comments

Comments
 (0)