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; std::cout << "files: " << request.http->form_parts().size() << std::endl;
for (auto& part : request.http->form_parts()) { for (auto& part : request.http->form_parts()) {
std::cout << "name: " << part.name() << std::endl; std::cout << "name: " << part->name() << std::endl;
std::cout << "data: " << std::endl << part.data() << std::endl; std::cout << "data: " << std::endl << part->data() << std::endl;
} }
response->content = "OK"; response->content = "OK";

@ -8,7 +8,7 @@
#include <iostream> #include <iostream>
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
#if 0
// HTTP GET request parser test fixture. // HTTP GET request parser test fixture.
class GetRequestParserTest : public testing::Test { class GetRequestParserTest : public testing::Test {
protected: protected:
@ -145,3 +145,91 @@ TEST_F(PostRequestParserTest, ParseByteWise) {
CheckResult(); 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, FormPart(const std::string& name, std::string&& data,
const std::string& media_type = ""); const std::string& media_type = "");
#if WEBCC_DEFAULT_MOVE_COPY_ASSIGN FormPart(const FormPart&) = delete;
FormPart& operator=(const FormPart&) = delete;
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
// API: SERVER // API: SERVER
const std::string& name() const { const std::string& name() const {
@ -266,6 +243,8 @@ private:
std::string data_; std::string data_;
}; };
using FormPartPtr = std::shared_ptr<FormPart>;
} // namespace webcc } // namespace webcc
#endif // WEBCC_COMMON_H_ #endif // WEBCC_COMMON_H_

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

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

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

@ -10,21 +10,14 @@ namespace webcc {
class RequestBuilder { class RequestBuilder {
public: public:
explicit RequestBuilder(const std::string& method = "") RequestBuilder() = default;
: method_(method) {
}
~RequestBuilder() = default; ~RequestBuilder() = default;
RequestBuilder(const RequestBuilder&) = delete; RequestBuilder(const RequestBuilder&) = delete;
RequestBuilder& operator=(const RequestBuilder&) = delete; RequestBuilder& operator=(const RequestBuilder&) = delete;
// Build the request. // Build the request.
RequestPtr Build(); RequestPtr operator()();
RequestPtr operator()() {
return Build();
}
RequestBuilder& Get() { return Method(methods::kGet); } RequestBuilder& Get() { return Method(methods::kGet); }
RequestBuilder& Head() { return Method(methods::kHead); } RequestBuilder& Head() { return Method(methods::kHead); }
@ -72,8 +65,8 @@ public:
RequestBuilder& File(const std::string& name, const Path& path, RequestBuilder& File(const std::string& name, const Path& path,
const std::string& media_type = ""); const std::string& media_type = "");
RequestBuilder& Form(FormPart&& part) { RequestBuilder& Form(FormPartPtr part) {
form_parts_.push_back(std::move(part)); form_parts_.push_back(part);
return *this; return *this;
} }
@ -121,7 +114,7 @@ private:
bool json_ = false; bool json_ = false;
// Files to upload for a POST request. // Files to upload for a POST request.
std::vector<FormPart> form_parts_; std::vector<FormPartPtr> form_parts_;
// Compress the request content. // Compress the request content.
// NOTE: Most servers don't support compressed requests. // 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, bool RequestParser::ParseMultipartContent(const char* data,
std::size_t length) { std::size_t length) {
// Append the new data to the pending data. // Append the new data to the pending data.
// NOTE: It's more difficult to avoid this than normal fixed-length content. // NOTE: It's more difficult to avoid this than normal fixed-length content.
pending_data_.append(data, length); 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()); pending_data_.size());
if (!content_length_parsed_ || content_length_ == kInvalidLength) { if (!content_length_parsed_ || content_length_ == kInvalidLength) {
@ -82,6 +82,9 @@ bool RequestParser::ParseMultipartContent(const char* data,
} }
if (step_ == Step::kBoundaryParsed) { if (step_ == Step::kBoundaryParsed) {
if (!part_) {
part_.reset(new FormPart{});
}
bool need_more_data = false; bool need_more_data = false;
if (ParsePartHeaders(&need_more_data)) { if (ParsePartHeaders(&need_more_data)) {
// Go to next step. // Go to next step.
@ -103,9 +106,7 @@ bool RequestParser::ParseMultipartContent(const char* data,
std::size_t count = 0; std::size_t count = 0;
bool ended = false; bool ended = false;
if (!GetNextBoundaryLine(&off, &count, &ended)) { if (!GetNextBoundaryLine(&off, &count, &ended)) {
// All pending data belongs to this part. // Wait until next boundary.
part_.AppendData(pending_data_);
pending_data_.clear();
break; break;
} }
@ -113,11 +114,22 @@ bool RequestParser::ParseMultipartContent(const char* data,
// This part has ended. // This part has ended.
if (off > 2) { if (off > 2) {
// -2 for exluding the CRLF after the data. // -2 for excluding the CRLF after the data.
part_.AppendData(pending_data_.data(), off - 2); 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) { if (ended) {
// Go to the end step. // Go to the end step.
@ -174,10 +186,10 @@ bool RequestParser::ParsePartHeaders(bool* need_more_data) {
header.second.c_str()); header.second.c_str());
return false; return false;
} }
part_.set_name(content_disposition.name()); part_->set_name(content_disposition.name());
part_.set_file_name(content_disposition.file_name()); part_->set_file_name(content_disposition.file_name());
LOG_INFO("Content-Disposition (name=%s; filename=%s)", 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. // TODO: Parse other headers.
@ -190,8 +202,8 @@ bool RequestParser::ParsePartHeaders(bool* need_more_data) {
} }
bool RequestParser::GetNextBoundaryLine(std::size_t* b_off, bool RequestParser::GetNextBoundaryLine(std::size_t* b_off,
std::size_t* b_count, std::size_t* b_count,
bool* ended) { bool* ended) {
std::size_t off = 0; std::size_t off = 0;
while (true) { while (true) {

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

Loading…
Cancel
Save