mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-26 12:24:46 +00:00
Resume the multipart header search where it stopped
A part header arriving a few bytes at a time was searched for its CRLF from the start on every read, which made parsing it quadratic. Remember how far the search got.
This commit is contained in:
1 parent
cadcc425b8
commit
d5851d7e89
2 files changed
+55
-4
No files matched your search
@@ -9465,11 +9465,14 @@ public:
|
|||||||
}
|
}
|
||||||
case 1: { // New entry
|
case 1: { // New entry
|
||||||
clear_file_info();
|
clear_file_info();
|
||||||
|
header_searched_ = 0;
|
||||||
state_ = 2;
|
state_ = 2;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 2: { // Headers
|
case 2: { // Headers
|
||||||
auto pos = buf_find(crlf_);
|
// Resume where the last call stopped looking, so that a line arriving
|
||||||
|
// a few bytes at a time is not searched from its start each time.
|
||||||
|
auto pos = buf_find(crlf_, header_searched_);
|
||||||
if (pos > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; }
|
if (pos > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; }
|
||||||
while (pos < buf_size()) {
|
while (pos < buf_size()) {
|
||||||
// Empty line
|
// Empty line
|
||||||
@@ -9550,7 +9553,11 @@ public:
|
|||||||
buf_erase(pos + crlf_.size());
|
buf_erase(pos + crlf_.size());
|
||||||
pos = buf_find(crlf_);
|
pos = buf_find(crlf_);
|
||||||
}
|
}
|
||||||
if (state_ != 3) { return true; }
|
if (state_ != 3) {
|
||||||
|
// The CR of the CRLF may be the last byte so far.
|
||||||
|
header_searched_ = buf_size() > 0 ? buf_size() - 1 : 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 3: { // Body
|
case 3: { // Body
|
||||||
@@ -9668,6 +9675,7 @@ private:
|
|||||||
bool is_valid_ = false;
|
bool is_valid_ = false;
|
||||||
FormData file_;
|
FormData file_;
|
||||||
size_t header_count_ = 0;
|
size_t header_count_ = 0;
|
||||||
|
size_t header_searched_ = 0;
|
||||||
|
|
||||||
// Buffer
|
// Buffer
|
||||||
bool start_with(const std::string &a, size_t spos, size_t epos,
|
bool start_with(const std::string &a, size_t spos, size_t epos,
|
||||||
@@ -9689,10 +9697,10 @@ private:
|
|||||||
return start_with(buf_, buf_spos_, buf_epos_, s);
|
return start_with(buf_, buf_spos_, buf_epos_, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t buf_find(const std::string &s) const {
|
size_t buf_find(const std::string &s, size_t from = 0) const {
|
||||||
auto c = s.front();
|
auto c = s.front();
|
||||||
|
|
||||||
size_t off = buf_spos_;
|
size_t off = buf_spos_ + from;
|
||||||
while (off < buf_epos_) {
|
while (off < buf_epos_) {
|
||||||
auto pos = off;
|
auto pos = off;
|
||||||
while (true) {
|
while (true) {
|
||||||
|
|||||||
@@ -1872,6 +1872,49 @@ TEST(ParseDispositionParamsTest, QuotedValues) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(MultipartFormDataTest, PartArrivingAByteAtATime) {
|
||||||
|
Server svr;
|
||||||
|
std::string filename;
|
||||||
|
std::string content;
|
||||||
|
svr.Post("/upload", [&](const Request &req, Response &) {
|
||||||
|
const auto &file = req.form.get_file("file");
|
||||||
|
filename = file.filename;
|
||||||
|
content = file.content;
|
||||||
|
});
|
||||||
|
|
||||||
|
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
|
||||||
|
auto se = detail::scope_exit([&] {
|
||||||
|
svr.stop();
|
||||||
|
thread.join();
|
||||||
|
ASSERT_FALSE(svr.is_running());
|
||||||
|
});
|
||||||
|
svr.wait_until_ready();
|
||||||
|
|
||||||
|
std::string body = "--B\r\n"
|
||||||
|
"Content-Disposition: form-data; name=\"file\"; "
|
||||||
|
"filename=\"a.txt\"\r\n"
|
||||||
|
"Content-Type: text/plain\r\n"
|
||||||
|
"\r\n"
|
||||||
|
"hello\r\n"
|
||||||
|
"--B--\r\n";
|
||||||
|
Client cli(HOST, PORT);
|
||||||
|
auto res = cli.Post(
|
||||||
|
"/upload",
|
||||||
|
[&](size_t offset, DataSink &sink) {
|
||||||
|
if (offset < body.size()) {
|
||||||
|
sink.write(&body[offset], 1);
|
||||||
|
} else {
|
||||||
|
sink.done();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
"multipart/form-data; boundary=B");
|
||||||
|
ASSERT_TRUE(res) << "Error: " << to_string(res.error());
|
||||||
|
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||||
|
EXPECT_EQ("a.txt", filename);
|
||||||
|
EXPECT_EQ("hello", content);
|
||||||
|
}
|
||||||
|
|
||||||
TEST(ParseDispositionParamsTest, QuotedValuesSurviveTheRoundTrip) {
|
TEST(ParseDispositionParamsTest, QuotedValuesSurviveTheRoundTrip) {
|
||||||
// escape_multipart_field() only escapes '"', CR and LF, so a name or
|
// escape_multipart_field() only escapes '"', CR and LF, so a name or
|
||||||
// filename holding '=' or ';' reaches the server's parameter parser as is.
|
// filename holding '=' or ';' reaches the server's parameter parser as is.
|
||||||
|
|||||||
Reference in new issue
Block a user