Fix multipart form data parsing

master
Chunting Gu 6 years ago
parent e621025cc3
commit 4721d50363

@ -15,8 +15,8 @@ public:
std::cout << "files: " << request.http->form_parts().size() << std::endl;
for (auto& part : request.http->form_parts()) {
std::cout << "name: " << part.name() << std::endl;
std::cout << "data: " << std::endl << part.data() << std::endl;
std::cout << "name: " << part->name() << std::endl;
std::cout << "data: " << std::endl << part->data() << std::endl;
}
response->content = "OK";

@ -8,7 +8,7 @@
#include <iostream>
// -----------------------------------------------------------------------------
#if 0
// HTTP GET request parser test fixture.
class GetRequestParserTest : public testing::Test {
protected:
@ -145,3 +145,91 @@ TEST_F(PostRequestParserTest, ParseByteWise) {
CheckResult();
}
#endif
// -----------------------------------------------------------------------------
class MultipartRequestParserTest : public testing::Test {
protected:
MultipartRequestParserTest() : parser_(&request_) {
}
void SetUp() override {
data_ =
"--e81381de-436b-4314-8662-7362d5593b12\r\n"
"Content-Disposition: form-data; name=\"file\"; filename=\"remember.txt\"\r\n"
"\r\n"
"Remember\r\n"
"BY CHRISTINA ROSSETTI\r\n"
"\r\n"
"Remember me when I am gone away,\r\n"
" Gone far away into the silent land;\r\n"
" When you can no more hold me by the hand,\r\n"
"Nor I half turn to go yet turning stay.\r\n"
"Remember me when no more day by day\r\n"
" You tell me of our future that you plann'd:\r\n"
" Only remember me; you understand\r\n"
"It will be late to counsel then or pray.\r\n"
"Yet if you should forget me for a while\r\n"
" And afterwards remember, do not grieve:\r\n"
" For if the darkness and corruption leave\r\n"
" A vestige of the thoughts that once I had,\r\n"
"Better by far you should forget and smile\r\n"
" Than that you should remember and be sad.\r\n"
"\r\n"
"--e81381de-436b-4314-8662-7362d5593b12\r\n"
"Content-Disposition: form-data; name=\"json\"\r\n"
"Content-Type: application/json\r\n"
"\r\n"
"{}\r\n"
"--e81381de-436b-4314-8662-7362d5593b12--\r\n";
payload_ =
"POST / HTTP/1.1\r\n"
"User-Agent: Webcc/0.1.0\r\n"
"Accept-Encoding: gzip, deflate\r\n"
"Accept: */*\r\n"
"Connection: Keep-Alive\r\n"
"Host: localhost:8080\r\n"
"Content-Type: multipart/form-data; boundary=e81381de-436b-4314-8662-7362d5593b12\r\n"
"Content-Length: " + std::to_string(data_.size()) + "\r\n"
"\r\n";
payload_ += data_;
}
void CheckResult() {
EXPECT_EQ("POST", request_.method());
EXPECT_EQ("localhost:8080", request_.GetHeader("Host"));
EXPECT_EQ("*/*", request_.GetHeader("Accept"));
EXPECT_EQ("Keep-Alive", request_.GetHeader("Connection"));
EXPECT_EQ(2, request_.form_parts().size());
}
std::string payload_;
std::string data_;
webcc::Request request_;
webcc::RequestParser parser_;
};
TEST_F(MultipartRequestParserTest, ParseFullDataOnce) {
bool ok = parser_.Parse(payload_.data(), payload_.size());
EXPECT_TRUE(ok);
EXPECT_TRUE(parser_.finished());
CheckResult();
}
TEST_F(MultipartRequestParserTest, ParseByteWise) {
for (std::size_t i = 0; i < payload_.size(); ++i) {
bool ok = parser_.Parse(payload_.data() + i, 1);
EXPECT_TRUE(ok);
}
EXPECT_TRUE(parser_.finished());
CheckResult();
}

@ -169,31 +169,8 @@ public:
FormPart(const std::string& name, std::string&& data,
const std::string& media_type = "");
#if WEBCC_DEFAULT_MOVE_COPY_ASSIGN
FormPart(FormPart&&) = default;
FormPart& operator=(FormPart&&) = default;
#else
FormPart(FormPart&& rhs)
: name_(std::move(rhs.name_)),
file_name_(std::move(rhs.file_name_)),
media_type_(std::move(rhs.media_type_)),
data_(std::move(rhs.data_)) {
}
FormPart& operator=(FormPart&& rhs) {
if (&rhs != this) {
name_ = std::move(rhs.name_);
file_name_ = std::move(rhs.file_name_);
media_type_ = std::move(rhs.media_type_);
data_ = std::move(rhs.data_);
}
return *this;
}
#endif // WEBCC_DEFAULT_MOVE_COPY_ASSIGN
FormPart(const FormPart&) = delete;
FormPart& operator=(const FormPart&) = delete;
// API: SERVER
const std::string& name() const {
@ -266,6 +243,8 @@ private:
std::string data_;
};
using FormPartPtr = std::shared_ptr<FormPart>;
} // namespace webcc
#endif // WEBCC_COMMON_H_

@ -2,6 +2,7 @@
#include "webcc/logger.h"
#include "webcc/utility.h"
#include <iostream>
namespace webcc {
@ -52,7 +53,7 @@ void Request::Prepare() {
data_payload.push_back(buffer(boundary_));
data_payload.push_back(buffer(misc_strings::CRLF));
part.Prepare(&data_payload);
part->Prepare(&data_payload);
}
// Boundary end
@ -73,6 +74,10 @@ void Request::Prepare() {
// Append payload of content data.
payload_.insert(payload_.end(), data_payload.begin(), data_payload.end());
std::string str;
CopyPayload(&str);
std::cout << str;
}
void Request::CreateStartLine() {

@ -55,16 +55,16 @@ public:
return port().empty() ? default_port : port();
}
const std::vector<FormPart>& form_parts() const {
const std::vector<FormPartPtr>& form_parts() const {
return form_parts_;
}
void set_form_parts(std::vector<FormPart>&& form_parts) {
void set_form_parts(std::vector<FormPartPtr>&& form_parts) {
form_parts_ = std::move(form_parts);
}
void AddFormPart(FormPart&& form_part) {
form_parts_.push_back(std::move(form_part));
void AddFormPart(FormPartPtr form_part) {
form_parts_.push_back(form_part);
}
// Prepare payload.
@ -79,7 +79,7 @@ private:
Url url_;
// Files to upload for a POST request.
std::vector<FormPart> form_parts_;
std::vector<FormPartPtr> form_parts_;
std::string boundary_;
};

@ -7,7 +7,7 @@
namespace webcc {
RequestPtr RequestBuilder::Build() {
RequestPtr RequestBuilder::operator()() {
assert(parameters_.size() % 2 == 0);
assert(headers_.size() % 2 == 0);
@ -44,7 +44,8 @@ RequestBuilder& RequestBuilder::File(const std::string& name,
const Path& path,
const std::string& media_type) {
assert(!name.empty());
form_parts_.push_back(FormPart{ name, path, media_type });
auto part = std::make_shared<FormPart>(name, path, media_type);
form_parts_.push_back(part);
return *this;
}
@ -52,7 +53,8 @@ RequestBuilder& RequestBuilder::Form(const std::string& name,
std::string&& data,
const std::string& media_type) {
assert(!name.empty());
form_parts_.push_back(FormPart{ name, std::move(data), media_type });
auto part = std::make_shared<FormPart>(name, std::move(data), media_type);
form_parts_.push_back(part);
return *this;
}

@ -10,21 +10,14 @@ namespace webcc {
class RequestBuilder {
public:
explicit RequestBuilder(const std::string& method = "")
: method_(method) {
}
RequestBuilder() = default;
~RequestBuilder() = default;
RequestBuilder(const RequestBuilder&) = delete;
RequestBuilder& operator=(const RequestBuilder&) = delete;
// Build the request.
RequestPtr Build();
RequestPtr operator()() {
return Build();
}
RequestPtr operator()();
RequestBuilder& Get() { return Method(methods::kGet); }
RequestBuilder& Head() { return Method(methods::kHead); }
@ -72,8 +65,8 @@ public:
RequestBuilder& File(const std::string& name, const Path& path,
const std::string& media_type = "");
RequestBuilder& Form(FormPart&& part) {
form_parts_.push_back(std::move(part));
RequestBuilder& Form(FormPartPtr part) {
form_parts_.push_back(part);
return *this;
}
@ -121,7 +114,7 @@ private:
bool json_ = false;
// Files to upload for a POST request.
std::vector<FormPart> form_parts_;
std::vector<FormPartPtr> form_parts_;
// Compress the request content.
// NOTE: Most servers don't support compressed requests.

@ -47,12 +47,12 @@ bool RequestParser::ParseContent(const char* data, std::size_t length) {
}
bool RequestParser::ParseMultipartContent(const char* data,
std::size_t length) {
std::size_t length) {
// Append the new data to the pending data.
// NOTE: It's more difficult to avoid this than normal fixed-length content.
pending_data_.append(data, length);
LOG_VERB("Parse multipart content (pending data size: %u).",
LOG_VERB("Parse multipart content (3pending data size: %u).",
pending_data_.size());
if (!content_length_parsed_ || content_length_ == kInvalidLength) {
@ -82,6 +82,9 @@ bool RequestParser::ParseMultipartContent(const char* data,
}
if (step_ == Step::kBoundaryParsed) {
if (!part_) {
part_.reset(new FormPart{});
}
bool need_more_data = false;
if (ParsePartHeaders(&need_more_data)) {
// Go to next step.
@ -103,9 +106,7 @@ bool RequestParser::ParseMultipartContent(const char* data,
std::size_t count = 0;
bool ended = false;
if (!GetNextBoundaryLine(&off, &count, &ended)) {
// All pending data belongs to this part.
part_.AppendData(pending_data_);
pending_data_.clear();
// Wait until next boundary.
break;
}
@ -113,11 +114,22 @@ bool RequestParser::ParseMultipartContent(const char* data,
// This part has ended.
if (off > 2) {
// -2 for exluding the CRLF after the data.
part_.AppendData(pending_data_.data(), off - 2);
// -2 for excluding the CRLF after the data.
part_->AppendData(pending_data_.data(), off - 2);
// Erase the data of this part and the next boundary.
// +2 for including the CRLF after the boundary.
pending_data_.erase(0, off + count + 2);
} else {
LOG_ERRO("Invalid part data.");
return false;
}
request_->AddFormPart(std::move(part_));
// Add this part to request.
request_->AddFormPart(part_);
// Reset for next part.
part_.reset();
if (ended) {
// Go to the end step.
@ -174,10 +186,10 @@ bool RequestParser::ParsePartHeaders(bool* need_more_data) {
header.second.c_str());
return false;
}
part_.set_name(content_disposition.name());
part_.set_file_name(content_disposition.file_name());
part_->set_name(content_disposition.name());
part_->set_file_name(content_disposition.file_name());
LOG_INFO("Content-Disposition (name=%s; filename=%s)",
part_.name().c_str(), part_.file_name().c_str());
part_->name().c_str(), part_->file_name().c_str());
}
// TODO: Parse other headers.
@ -190,8 +202,8 @@ bool RequestParser::ParsePartHeaders(bool* need_more_data) {
}
bool RequestParser::GetNextBoundaryLine(std::size_t* b_off,
std::size_t* b_count,
bool* ended) {
std::size_t* b_count,
bool* ended) {
std::size_t off = 0;
while (true) {

@ -43,7 +43,7 @@ private:
};
Step step_ = kStart;
FormPart part_;
FormPartPtr part_;
};
} // namespace webcc

Loading…
Cancel
Save