Skip to content

Commit

Permalink
Merge pull request #15 from The-EDev/multipart
Browse files Browse the repository at this point in the history
multipart/form-data support
  • Loading branch information
mrozigor authored Oct 21, 2020
2 parents 8a99167 + 7fa7390 commit f85e62f
Show file tree
Hide file tree
Showing 4 changed files with 238 additions and 2 deletions.
8 changes: 8 additions & 0 deletions examples/example.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,14 @@ int main()
return std::string(512*1024, ' ');
});

// Take a multipart/form-data request and print out its body
CROW_ROUTE(app,"/multipart")
([](const crow::request& req){
crow::multipart::message msg(req);
CROW_LOG_INFO << "body of the first part " << msg.parts[0].body;
return "it works!";
});

// enables all log
app.loglevel(crow::LogLevel::DEBUG);
//crow::logger::setHandler(std::make_shared<ExampleLogHandler>());
Expand Down
1 change: 1 addition & 0 deletions include/crow.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "crow/websocket.h"
#include "crow/parser.h"
#include "crow/http_response.h"
#include "crow/multipart.h"
#include "crow/middleware.h"
#include "crow/routing.h"
#include "crow/middleware_context.h"
Expand Down
195 changes: 195 additions & 0 deletions include/crow/multipart.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
#pragma once
#include <string>
#include <vector>
#include <sstream>
#include "crow/http_request.h"
#include "crow/http_response.h"

namespace crow
{
///Encapsulates anything related to processing and organizing `multipart/xyz` messages
namespace multipart
{
const std::string dd = "--";
const std::string crlf = "\r\n";

///The first part in a section, contains metadata about the part
struct header
{
std::pair<std::string, std::string> value; ///< The first part of the header, usually `Content-Type` or `Content-Disposition`
std::unordered_map<std::string, std::string> params; ///< The parameters of the header, come after the `value`
};

///One part of the multipart message

///It is usually separated from other sections by a `boundary`
///
struct part
{
std::vector<header> headers; ///< (optional) The first part before the data, Contains information regarding the type of data and encoding
std::string body; ///< The actual data in the part
};

///The parsed multipart request/response
struct message
{
ci_map headers;
std::string boundary; ///< The text boundary that separates different `parts`
std::vector<part> parts; ///< The individual parts of the message

const std::string& get_header_value(const std::string& key) const
{
return crow::get_header_value(headers, key);
}

///Represent all parts as a string (**does not include message headers**)
const std::string dump()
{
std::stringstream str;
std::string delimiter = dd + boundary;

for (uint i=0 ; i<parts.size(); i++)
{
str << delimiter << crlf;
str << dump(i);
}
str << delimiter << dd << crlf;
return str.str();
}

///Represent an individual part as a string
const std::string dump(int part_)
{
std::stringstream str;
part item = parts[part_];
for (header item_h: item.headers)
{
str << item_h.value.first << ": " << item_h.value.second;
for (auto& it: item_h.params)
{
str << "; " << it.first << '=' << pad(it.second);
}
str << crlf;
}
str << crlf;
str << item.body << crlf;
return str.str();
}

///Default constructor using default values
message(const ci_map& headers, const std::string& boundary, const std::vector<part>& sections)
: headers(headers), boundary(boundary), parts(sections){}

///Create a multipart message from a request data
message(const request& req)
: headers(req.headers),
boundary(get_boundary(get_header_value("Content-Type"))),
parts(parse_body(req.body))
{}

private:

std::string get_boundary(const std::string& header)
{
size_t found = header.find("boundary=");
if (found)
return header.substr(found+9);
return std::string();
}

std::vector<part> parse_body(std::string body)
{

std::vector<part> sections;

std::string delimiter = dd + boundary;

while(body != (crlf))
{
size_t found = body.find(delimiter);
std::string section = body.substr(0, found);

//+2 is the CRLF
//We don't check it and delete it so that the same delimiter can be used for
//the last delimiter (--delimiter--CRLF).
body.erase(0, found + delimiter.length() + 2);
if (!section.empty())
{
sections.emplace_back(parse_section(section));
}
}
return sections;
}

part parse_section(std::string& section)
{
struct part to_return;

size_t found = section.find(crlf+crlf);
std::string head_line = section.substr(0, found+2);
section.erase(0, found + 4);

parse_section_head(head_line, to_return);
to_return.body = section.substr(0, section.length()-2);
return to_return;
}

void parse_section_head(std::string& lines, part& part)
{
while (!lines.empty())
{
header to_add;

size_t found = lines.find(crlf);
std::string line = lines.substr(0, found);
lines.erase(0, found+2);
//add the header if available
if (!line.empty())
{
size_t found = line.find("; ");
std::string header = line.substr(0, found);
if (found != std::string::npos)
line.erase(0, found+2);
else
line = std::string();

size_t header_split = header.find(": ");

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

//add the parameters
while (!line.empty())
{
size_t found = line.find("; ");
std::string param = line.substr(0, found);
if (found != std::string::npos)
line.erase(0, found+2);
else
line = std::string();

size_t param_split = param.find('=');

std::string value = param.substr(param_split+1);

to_add.params.emplace(param.substr(0, param_split), trim(value));
}
part.headers.emplace_back(to_add);
}
}

inline std::string trim (std::string& string, const char& excess = '"')
{
if (string.length() > 1 && string[0] == excess && string[string.length()-1] == excess)
return string.substr(1, string.length()-2);
return string;
}

inline std::string pad (std::string& string, const char& padding = '"')
{
return (padding + string + padding);
}

};
}
}
36 changes: 34 additions & 2 deletions tests/unittest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1234,14 +1234,46 @@ TEST_CASE("send_file")
res.headers.find("Content-Length")->second);
}

{
request req;
response res;

req.url = "/jpg2";

app.handle(req, res);


REQUIRE(404 == res.code);
}
}

TEST_CASE("multipart")
{
std::string test_string = "--CROW-BOUNDARY\r\nContent-Disposition: form-data; name=\"hello\"\r\n\r\nworld\r\n--CROW-BOUNDARY\r\nContent-Disposition: form-data; name=\"world\"\r\n\r\nhello\r\n--CROW-BOUNDARY\r\nContent-Disposition: form-data; name=\"multiline\"\r\n\r\ntext\ntext\ntext\r\n--CROW-BOUNDARY--\r\n";

SimpleApp app;

CROW_ROUTE(app, "/multipart")
([](const crow::request& req, crow::response& res)
{
multipart::message msg(req);
res.add_header("Content-Type", "multipart/form-data; boundary=CROW-BOUNDARY");
res.body = msg.dump();
res.end();
});

app.validate();

{
request req;
response res;

req.url = "/jpg2";
req.url = "/multipart";
req.add_header("Content-Type", "multipart/form-data; boundary=CROW-BOUNDARY");
req.body = test_string;

app.handle(req, res);

REQUIRE(404 == res.code);
REQUIRE(test_string == res.body);
}
}

0 comments on commit f85e62f

Please sign in to comment.