优化文件上传速度,增加请求头预校验功能
This commit is contained in:
+288
-209
@@ -1,4 +1,4 @@
|
||||
#include "webcc/request_parser.h"
|
||||
#include "webcc/request_parser.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
@@ -9,266 +9,345 @@
|
||||
#include "webcc/string.h"
|
||||
#include "webcc/utility.h"
|
||||
|
||||
namespace webcc {
|
||||
namespace webcc
|
||||
{
|
||||
|
||||
RequestParser::RequestParser() : request_(nullptr) {
|
||||
}
|
||||
RequestParser::RequestParser() : request_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
void RequestParser::Init(Request* request, ViewMatcher view_matcher) {
|
||||
assert(view_matcher);
|
||||
void RequestParser::Init(Request* request, ViewMatcher view_matcher)
|
||||
{
|
||||
assert(view_matcher);
|
||||
|
||||
Parser::Init(request);
|
||||
Parser::Init(request);
|
||||
|
||||
request_ = request;
|
||||
view_matcher_ = view_matcher;
|
||||
}
|
||||
request_ = request;
|
||||
view_matcher_ = view_matcher;
|
||||
header_parsed_ = false; // 【归位】:在 Init 时重置状态,防止对象复用残留
|
||||
}
|
||||
|
||||
bool RequestParser::OnHeadersEnd() {
|
||||
bool matched = view_matcher_(request_->method(), request_->url().path(),
|
||||
&stream_);
|
||||
bool RequestParser::OnHeadersEnd()
|
||||
{
|
||||
header_parsed_ = true; // 【核心位置】:请求头解析完毕,触发标记!
|
||||
|
||||
if (!matched) {
|
||||
LOG_WARN("No view matches the request: %s %s", request_->method().c_str(),
|
||||
request_->url().path().c_str());
|
||||
}
|
||||
bool matched = view_matcher_(request_->method(), request_->url().path(), &stream_, &matched_view_);
|
||||
|
||||
return matched;
|
||||
}
|
||||
if (!matched)
|
||||
{
|
||||
LOG_WARN("No view matches the request: %s %s", request_->method().c_str(), request_->url().path().c_str());
|
||||
}
|
||||
|
||||
bool RequestParser::ParseStartLine(const std::string& line) {
|
||||
std::vector<string_view> parts;
|
||||
Split(line, ' ', true, &parts);
|
||||
return matched;
|
||||
}
|
||||
|
||||
if (parts.size() != 3) {
|
||||
return false;
|
||||
}
|
||||
bool RequestParser::ParseStartLine(const std::string& line)
|
||||
{
|
||||
std::vector<string_view> parts;
|
||||
Split(line, ' ', true, &parts);
|
||||
|
||||
request_->set_method(parts[0]);
|
||||
request_->set_url(Url{ parts[1] });
|
||||
if (parts.size() != 3)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// HTTP version is ignored.
|
||||
request_->set_method(parts[0]);
|
||||
request_->set_url(Url{ parts[1] });
|
||||
|
||||
return true;
|
||||
}
|
||||
// HTTP version is ignored.
|
||||
|
||||
bool RequestParser::ParseContent(const char* data, std::size_t length) {
|
||||
if (content_type_.multipart()) {
|
||||
return ParseMultipartContent(data, length);
|
||||
} else {
|
||||
return Parser::ParseContent(data, length);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RequestParser::ParseMultipartContent(const char* data,
|
||||
std::size_t length) {
|
||||
pending_data_.append(data, length);
|
||||
bool RequestParser::ParseContent(const char* data, std::size_t length)
|
||||
{
|
||||
if (content_type_.multipart())
|
||||
{
|
||||
return ParseMultipartContent(data, length);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Parser::ParseContent(data, length);
|
||||
}
|
||||
}
|
||||
|
||||
if (!content_length_parsed_ || content_length_ == kInvalidLength) {
|
||||
// Invalid content length (syntax error).
|
||||
return false;
|
||||
}
|
||||
bool RequestParser::ParseMultipartContent(const char* data, std::size_t length)
|
||||
{
|
||||
pending_data_.append(data, length);
|
||||
|
||||
while (true) {
|
||||
if (pending_data_.empty()) {
|
||||
// Wait data from next read.
|
||||
break;
|
||||
}
|
||||
if (!content_length_parsed_ || content_length_ == kInvalidLength)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (step_ == Step::kStart) {
|
||||
std::string line;
|
||||
if (!GetNextLine(0, &line, true)) {
|
||||
break; // Not enough data
|
||||
}
|
||||
if (!IsBoundary(line, 0, line.size())) {
|
||||
LOG_ERRO("Invalid boundary: %s", line.c_str());
|
||||
return false;
|
||||
}
|
||||
LOG_INFO("Boundary line: %s", line.c_str());
|
||||
// Go to next step.
|
||||
step_ = Step::kBoundaryParsed;
|
||||
continue;
|
||||
}
|
||||
const std::size_t kMaxAllowedMultipartSize = g_max_multipart_size; // 1 GB
|
||||
|
||||
if (step_ == Step::kBoundaryParsed) {
|
||||
if (!part_) {
|
||||
part_.reset(new FormPart{});
|
||||
}
|
||||
bool need_more_data = false;
|
||||
if (ParsePartHeaders(&need_more_data)) {
|
||||
// Go to next step.
|
||||
step_ = Step::kHeadersParsed;
|
||||
LOG_INFO("Part headers just ended");
|
||||
continue;
|
||||
} else {
|
||||
if (need_more_data) {
|
||||
// Need more data from next read.
|
||||
break;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
while (true)
|
||||
{
|
||||
if (pending_data_.empty())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (step_ == Step::kHeadersParsed) {
|
||||
std::size_t off = 0;
|
||||
std::size_t count = 0;
|
||||
bool ended = false;
|
||||
if (step_ == Step::kStart)
|
||||
{
|
||||
std::string line;
|
||||
if (!GetNextLine(0, &line, true))
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (!IsBoundary(line, 0, line.size()))
|
||||
{
|
||||
LOG_ERRO("Invalid boundary: %s", line.c_str());
|
||||
return false;
|
||||
}
|
||||
step_ = Step::kBoundaryParsed;
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: Remember last CRLF position.
|
||||
if (!GetNextBoundaryLine(&off, &count, &ended)) {
|
||||
break;
|
||||
}
|
||||
if (step_ == Step::kBoundaryParsed)
|
||||
{
|
||||
if (!part_)
|
||||
{
|
||||
part_.reset(new FormPart{});
|
||||
if (content_length_ > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
part_->ReserveData(content_length_);
|
||||
}
|
||||
catch (const std::bad_alloc&)
|
||||
{
|
||||
LOG_ERRO("Failed to allocate memory for size: %zu", content_length_);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
bool need_more_data = false;
|
||||
if (ParsePartHeaders(&need_more_data))
|
||||
{
|
||||
step_ = Step::kHeadersParsed;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (need_more_data)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Next boundary found.
|
||||
LOG_INFO("Next boundary found, off=%u", off);
|
||||
if (step_ == Step::kHeadersParsed)
|
||||
{
|
||||
std::string strict_boundary = "\r\n--" + content_type_.boundary();
|
||||
std::size_t pos = pending_data_.find(strict_boundary);
|
||||
|
||||
// This part has ended.
|
||||
if (off >= 2) {
|
||||
// -2 for excluding the CRLF after the data.
|
||||
part_->AppendData(pending_data_.data(), off - 2);
|
||||
if (pos == std::string::npos)
|
||||
{
|
||||
std::size_t reserve_len = strict_boundary.size() + 8;
|
||||
if (pending_data_.size() > reserve_len)
|
||||
{
|
||||
std::size_t safe_len = pending_data_.size() - reserve_len;
|
||||
|
||||
// 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, off=%u", off);
|
||||
return false;
|
||||
}
|
||||
// 【修正点】:使用 part_->data().size() 替代 part_->GetDataSize(),避免触发异常!
|
||||
if (part_->data().size() + safe_len > kMaxAllowedMultipartSize)
|
||||
{
|
||||
LOG_ERRO("Multipart payload size exceeded max limit without boundary!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Save this part
|
||||
form_parts_.push_back(part_);
|
||||
part_->AppendData(pending_data_.data(), safe_len);
|
||||
pending_data_.erase(0, safe_len);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Reset for next part.
|
||||
part_.reset();
|
||||
std::size_t boundary_start = pos;
|
||||
std::size_t line_end = pending_data_.find(kCRLF, boundary_start + 2);
|
||||
if (line_end == std::string::npos)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (ended) {
|
||||
// Go to the end step.
|
||||
step_ = Step::kEnded;
|
||||
break;
|
||||
} else {
|
||||
// Go to next step.
|
||||
step_ = Step::kBoundaryParsed;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::size_t count = line_end - (boundary_start + 2);
|
||||
bool ended = false;
|
||||
|
||||
if (step_ == Step::kEnded) {
|
||||
LOG_INFO("Multipart data has ended");
|
||||
if (!IsBoundary(pending_data_, boundary_start + 2, count, &ended))
|
||||
{
|
||||
std::size_t safe_len = boundary_start + 2;
|
||||
|
||||
// Create a body and set to the request.
|
||||
// 【修正点】:同理替换为 part_->data().size()
|
||||
if (part_->data().size() + safe_len > kMaxAllowedMultipartSize)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto body = std::make_shared<FormBody>(form_parts_,
|
||||
content_type_.boundary());
|
||||
part_->AppendData(pending_data_.data(), safe_len);
|
||||
pending_data_.erase(0, safe_len);
|
||||
continue;
|
||||
}
|
||||
|
||||
request_->SetBody(body, false); // TODO: set_length?
|
||||
if (boundary_start > 0)
|
||||
{
|
||||
if (part_->data().size() + boundary_start > kMaxAllowedMultipartSize)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
part_->AppendData(pending_data_.data(), boundary_start);
|
||||
}
|
||||
|
||||
Finish();
|
||||
}
|
||||
pending_data_.erase(0, line_end + 2);
|
||||
|
||||
return true;
|
||||
}
|
||||
form_parts_.push_back(part_);
|
||||
part_.reset();
|
||||
|
||||
bool RequestParser::ParsePartHeaders(bool* need_more_data) {
|
||||
std::size_t off = 0;
|
||||
if (ended)
|
||||
{
|
||||
step_ = Step::kEnded;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
step_ = Step::kBoundaryParsed;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (true) {
|
||||
std::string line;
|
||||
if (!GetNextLine(off, &line, false)) {
|
||||
// Need more data from next read.
|
||||
*need_more_data = true;
|
||||
return false;
|
||||
}
|
||||
if (step_ == Step::kEnded)
|
||||
{
|
||||
LOG_INFO("Multipart data has ended");
|
||||
auto body = std::make_shared<FormBody>(form_parts_, content_type_.boundary());
|
||||
request_->SetBody(body, false);
|
||||
Finish();
|
||||
}
|
||||
|
||||
off = off + line.size() + 2; // +2 for CRLF
|
||||
return true;
|
||||
}
|
||||
|
||||
if (line.empty()) {
|
||||
// Headers finished.
|
||||
break;
|
||||
}
|
||||
bool RequestParser::ParsePartHeaders(bool* need_more_data)
|
||||
{
|
||||
std::size_t off = 0;
|
||||
|
||||
Header header;
|
||||
if (!SplitKV(line, ':', true, &header.first, &header.second)) {
|
||||
LOG_ERRO("Invalid part header line: %s", line.c_str());
|
||||
return false;
|
||||
}
|
||||
while (true)
|
||||
{
|
||||
std::string line;
|
||||
if (!GetNextLine(off, &line, false))
|
||||
{
|
||||
// Need more data from next read.
|
||||
*need_more_data = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_INFO("Part header (%s: %s)", header.first.c_str(),
|
||||
header.second.c_str());
|
||||
off = off + line.size() + 2; // +2 for CRLF
|
||||
|
||||
// Parse Content-Disposition.
|
||||
if (boost::iequals(header.first, headers::kContentDisposition)) {
|
||||
ContentDisposition content_disposition{ header.second };
|
||||
if (!content_disposition.valid()) {
|
||||
LOG_ERRO("Invalid content-disposition header: %s",
|
||||
header.second.c_str());
|
||||
return false;
|
||||
}
|
||||
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());
|
||||
}
|
||||
if (line.empty())
|
||||
{
|
||||
// Headers finished.
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: Parse other headers.
|
||||
}
|
||||
Header header;
|
||||
if (!SplitKV(line, ':', true, &header.first, &header.second))
|
||||
{
|
||||
LOG_ERRO("Invalid part header line: %s", line.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove the data which has just been parsed.
|
||||
pending_data_.erase(0, off);
|
||||
LOG_INFO("Part header (%s: %s)", header.first.c_str(),
|
||||
header.second.c_str());
|
||||
|
||||
return true;
|
||||
}
|
||||
// Parse Content-Disposition.
|
||||
if (boost::iequals(header.first, headers::kContentDisposition))
|
||||
{
|
||||
ContentDisposition content_disposition{ header.second };
|
||||
if (!content_disposition.valid())
|
||||
{
|
||||
LOG_ERRO("Invalid content-disposition header: %s",
|
||||
header.second.c_str());
|
||||
return false;
|
||||
}
|
||||
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());
|
||||
}
|
||||
|
||||
bool RequestParser::GetNextBoundaryLine(std::size_t* b_off, std::size_t* b_len,
|
||||
bool* ended) {
|
||||
std::size_t off = 0;
|
||||
// TODO: Parse other headers.
|
||||
}
|
||||
|
||||
while (true) {
|
||||
std::size_t pos = pending_data_.find(kCRLF, off);
|
||||
if (pos == std::string::npos) {
|
||||
break;
|
||||
}
|
||||
// Remove the data which has just been parsed.
|
||||
pending_data_.erase(0, off);
|
||||
|
||||
std::size_t len = pos - off;
|
||||
if (len == 0) {
|
||||
off = pos + 2;
|
||||
continue; // Empty line
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (IsBoundary(pending_data_, off, len, ended)) {
|
||||
*b_off = off;
|
||||
*b_len = len;
|
||||
return true;
|
||||
}
|
||||
bool RequestParser::GetNextBoundaryLine(std::size_t* b_off, std::size_t* b_len,
|
||||
bool* ended)
|
||||
{
|
||||
std::size_t off = 0;
|
||||
|
||||
off = pos + 2;
|
||||
}
|
||||
while (true)
|
||||
{
|
||||
std::size_t pos = pending_data_.find(kCRLF, off);
|
||||
if (pos == std::string::npos)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
std::size_t len = pos - off;
|
||||
if (len == 0)
|
||||
{
|
||||
off = pos + 2;
|
||||
continue; // Empty line
|
||||
}
|
||||
|
||||
bool RequestParser::IsBoundary(const std::string& str, std::size_t off,
|
||||
std::size_t count, bool* end) const {
|
||||
const std::string& boundary = content_type_.boundary();
|
||||
if (IsBoundary(pending_data_, off, len, ended))
|
||||
{
|
||||
*b_off = off;
|
||||
*b_len = len;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (count != boundary.size() + 2 && count != boundary.size() + 4) {
|
||||
return false;
|
||||
}
|
||||
off = pos + 2;
|
||||
}
|
||||
|
||||
if (str[off] != '-' || str[off + 1] != '-') {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (count == boundary.size() + 4) {
|
||||
if (str[off + count - 1] != '-' || str[off + count - 2] != '-') {
|
||||
return false;
|
||||
}
|
||||
if (end != nullptr) {
|
||||
*end = true;
|
||||
}
|
||||
}
|
||||
bool RequestParser::IsBoundary(const std::string& str, std::size_t off,
|
||||
std::size_t count, bool* end) const
|
||||
{
|
||||
const std::string& boundary = content_type_.boundary();
|
||||
|
||||
return strncmp(boundary.c_str(), &str[off + 2], boundary.size()) == 0;
|
||||
}
|
||||
if (count != boundary.size() + 2 && count != boundary.size() + 4)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (str[off] != '-' || str[off + 1] != '-')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (count == boundary.size() + 4)
|
||||
{
|
||||
if (str[off + count - 1] != '-' || str[off + count - 2] != '-')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (end != nullptr)
|
||||
{
|
||||
*end = true;
|
||||
}
|
||||
}
|
||||
|
||||
return strncmp(boundary.c_str(), &str[off + 2], boundary.size()) == 0;
|
||||
}
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
Reference in New Issue
Block a user