You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

156 lines
3.5 KiB
C++

#include "webcc/http_parser.h"
#include "boost/algorithm/string.hpp"
#include "webcc/http_message.h"
#include "webcc/logger.h"
namespace webcc {
HttpParser::HttpParser(HttpMessage* message)
: message_(message),
content_length_(kInvalidLength),
start_line_parsed_(false),
content_length_parsed_(false),
header_parsed_(false),
finished_(false) {
}
bool HttpParser::Parse(const char* data, std::size_t length) {
if (header_parsed_) {
// Append the data to the content.
AppendContent(data, length);
if (IsContentFull()) {
// All content has been read.
Finish();
}
return true;
}
// Continue to parse headers.
pending_data_.append(data, length);
std::size_t off = 0;
while (true) {
std::size_t pos = pending_data_.find(CRLF, off);
if (pos == std::string::npos) {
break;
}
if (pos == off) { // End of headers.
off = pos + 2; // Skip CRLF.
header_parsed_ = true;
break;
}
std::string line = pending_data_.substr(off, pos - off);
if (!start_line_parsed_) {
start_line_parsed_ = true;
message_->set_start_line(line + CRLF);
if (!ParseStartLine(line)) {
return false;
}
} else {
ParseHeader(line);
}
off = pos + 2; // Skip CRLF.
}
if (header_parsed_) {
// Headers just ended.
LOG_INFO("HTTP headers parsed.");
if (!content_length_parsed_) {
// No Content-Length, no content.
Finish();
return true;
} else {
// Invalid Content-Length in the request.
if (content_length_ == kInvalidLength) {
return false;
}
}
AppendContent(pending_data_.substr(off));
if (IsContentFull()) {
// All content has been read.
Finish();
}
} else {
// Save the unparsed piece for next parsing.
pending_data_ = pending_data_.substr(off);
}
return true;
}
bool HttpParser::ParseHeader(const std::string& line) {
std::vector<std::string> splitted;
boost::split(splitted, line, boost::is_any_of(":"));
if (splitted.size() != 2) {
return false;
}
std::string& name = splitted[0];
std::string& value = splitted[1];
boost::trim(name);
boost::trim(value);
if (!content_length_parsed_ && boost::iequals(name, kContentLength)) {
content_length_parsed_ = true;
try {
content_length_ = static_cast<std::size_t>(std::stoul(value));
} catch (const std::exception&) {
LOG_ERRO("Invalid content length: %s.", value.c_str());
return false;
}
LOG_INFO("Content length: %u.", content_length_);
try {
// Reserve memory to avoid frequent reallocation when append.
content_.reserve(content_length_);
} catch (const std::exception& e) {
LOG_ERRO("Failed to reserve content memory: %s.", e.what());
return false;
}
}
message_->SetHeader(std::move(name), std::move(value));
return true;
}
void HttpParser::Finish() {
if (!content_.empty()) {
// Move content to message.
// "Content-Length" already set in ParseHeader().
message_->SetContent(std::move(content_), /*set_length*/false);
}
finished_ = true;
}
void HttpParser::AppendContent(const char* data, std::size_t count) {
content_.append(data, count);
}
void HttpParser::AppendContent(const std::string& data) {
content_.append(data);
}
bool HttpParser::IsContentFull() const {
return content_length_ != kInvalidLength &&
content_length_ <= content_.length();
}
} // namespace webcc