Files
terminal-web-server/src/server/request_parser.cpp

334 lines
11 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "request_parser.hpp"
#include <sstream>
#include "request.hpp"
namespace http::server {
constexpr int HTTP_MAX_HEADERS = 64;
/**
* Функция, позволяющая или запрещающая выделение размера тела для запросов.
* @return true, если тело удовлетворяет размерам
*/
static bool requestBodySizeResolver(Request& req, size_t reqSize) {
// разрешаем тело только для POST запросов
if (req.method == "POST") {
return reqSize < 0x4000; // 16кб на все POST-запросы к API будет более чем достаточно
}
// это для обновления прошивки
if (req.method == "PUT" && req.url->path == "/api/firmwareUpdate") {
return reqSize <= HTTP_MAX_PAYLOAD;
}
return false;
}
static void parseParams(Url& u, const std::string& query) {
std::istringstream iss(query);
std::string param;
while (std::getline(iss, param, '&')) {
size_t equal_pos = param.find('=');
if (equal_pos != std::string::npos) {
const std::string key = param.substr(0, equal_pos);
const std::string value = param.substr(equal_pos + 1);
u.params[key] = value;
}
}
}
Url::Url(const std::string &url) {
size_t question_mark_pos = url.find('?');
if (question_mark_pos != std::string::npos) {
path = url.substr(0, question_mark_pos);
const std::string query = url.substr(question_mark_pos + 1);
parseParams(*this, query);
} else {
path = url;
}
}
Url::~Url() = default;
Request::Request(bool secure): isSecure(secure) {}
void Request::reset() {
method = "";
queryUri = "";
if (url != nullptr) {
url.reset(nullptr);
}
isKeepAlive = false;
httpVersionMajor = 0;
httpVersionMinor = 0;
headers.clear();
payload.clear();
}
std::string Request::getHeaderValue(const std::string &headerName) const {
for (const auto& header: headers) {
if (boost::iequals(header.name, headerName)) {
return header.value;
}
}
return "";
}
Request::~Request() = default;
RequestParser::RequestParser()
: state_(method_start) {
}
void RequestParser::reset() {
state_ = method_start;
contentLenghtHeader = 0;
}
RequestParser::result_type RequestParser::consume(Request &req, char input) {
switch (state_) {
case expecting_payload:
req.payload.push_back(input);
if (req.payload.size() < contentLenghtHeader) {
return indeterminate;
}
return good;
case method_start:
if (!is_char(input) || is_ctl(input) || is_tspecial(input)) {
return bad;
} else {
state_ = method;
req.method.push_back(input);
return indeterminate;
}
case method:
if (input == ' ') {
state_ = uri;
return indeterminate;
}
if (!is_char(input) || is_ctl(input) || is_tspecial(input)) {
return bad;
}
req.method.push_back(input);
return indeterminate;
case uri:
if (input == ' ') {
state_ = http_version_h;
return indeterminate;
}
if (is_ctl(input)) {
return bad;
}
req.queryUri.push_back(input);
return indeterminate;
case http_version_h:
if (input == 'H') {
state_ = http_version_t_1;
return indeterminate;
}
return bad;
case http_version_t_1:
if (input == 'T') {
state_ = http_version_t_2;
return indeterminate;
}
return bad;
case http_version_t_2:
if (input == 'T') {
state_ = http_version_p;
return indeterminate;
} else {
return bad;
}
case http_version_p:
if (input == 'P') {
state_ = http_version_slash;
return indeterminate;
} else {
return bad;
}
case http_version_slash:
if (input == '/') {
req.httpVersionMajor = 0;
req.httpVersionMinor = 0;
state_ = http_version_major_start;
return indeterminate;
} else {
return bad;
}
case http_version_major_start:
if (is_digit(input)) {
req.httpVersionMajor = req.httpVersionMajor * 10 + input - '0';
state_ = http_version_major;
return indeterminate;
} else {
return bad;
}
case http_version_major:
if (input == '.') {
state_ = http_version_minor_start;
return indeterminate;
} else if (is_digit(input)) {
req.httpVersionMajor = req.httpVersionMajor * 10 + input - '0';
return indeterminate;
} else {
return bad;
}
case http_version_minor_start:
if (is_digit(input)) {
req.httpVersionMinor = req.httpVersionMinor * 10 + input - '0';
state_ = http_version_minor;
return indeterminate;
} else {
return bad;
}
case http_version_minor:
if (input == '\r') {
state_ = expecting_newline_1;
return indeterminate;
} else if (is_digit(input)) {
req.httpVersionMinor = req.httpVersionMinor * 10 + input - '0';
return indeterminate;
} else {
return bad;
}
case expecting_newline_1:
if (input == '\n') {
state_ = header_line_start;
return indeterminate;
} else {
return bad;
}
case header_line_start:
if (input == '\r') {
state_ = expecting_newline_3;
return indeterminate;
}
if (!req.headers.empty() && (input == ' ' || input == '\t')) {
state_ = header_lws;
return indeterminate;
}
if (!is_char(input) || is_ctl(input) || is_tspecial(input)) {
return bad;
}
if (req.headers.size() > HTTP_MAX_HEADERS) {
return bad;
}
req.headers.emplace_back();
req.headers.back().name.push_back(input);
state_ = header_name;
return indeterminate;
case header_lws:
if (input == '\r') {
state_ = expecting_newline_2;
return indeterminate;
} else if (input == ' ' || input == '\t') {
return indeterminate;
} else if (is_ctl(input)) {
return bad;
} else {
state_ = header_value;
req.headers.back().value.push_back(input);
return indeterminate;
}
case header_name:
if (input == ':') {
state_ = space_before_header_value;
return indeterminate;
}
if (!is_char(input) || is_ctl(input) || is_tspecial(input)) {
return bad;
}
req.headers.back().name.push_back(input);
return indeterminate;
case space_before_header_value:
if (input == ' ') {
state_ = header_value;
return indeterminate;
} else {
return bad;
}
case header_value:
if (input == '\r') {
state_ = expecting_newline_2;
return indeterminate;
} else if (is_ctl(input)) {
return bad;
} else {
req.headers.back().value.push_back(input);
return indeterminate;
}
case expecting_newline_2:
if (input == '\n') {
state_ = header_line_start;
return indeterminate;
} else {
return bad;
}
case expecting_newline_3:
if (input == '\n') {
req.url = std::make_unique<Url>(req.queryUri);
auto content_len = req.getHeaderValue("content-length");
if (content_len.empty()) {
return good;
}
contentLenghtHeader = std::stoul(content_len);
if (contentLenghtHeader == 0) {
return good;
}
if (requestBodySizeResolver(req, contentLenghtHeader)) {
state_ = expecting_payload;
return indeterminate;
}
}
return bad;
default:
return bad;
}
}
bool RequestParser::is_char(int c) {
return c >= 0 && c <= 127;
}
bool RequestParser::is_ctl(int c) {
return (c >= 0 && c <= 31) || (c == 127);
}
bool RequestParser::is_tspecial(int c) {
switch (c) {
case '(':
case ')':
case '<':
case '>':
case '@':
case ',':
case ';':
case ':':
case '\\':
case '"':
case '/':
case '[':
case ']':
case '?':
case '=':
case '{':
case '}':
case ' ':
case '\t':
return true;
default:
return false;
}
}
bool RequestParser::is_digit(int c) {
return c >= '0' && c <= '9';
}
} // namespace http::server