334 lines
11 KiB
C++
334 lines
11 KiB
C++
#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
|