#include "request_parser.hpp" #include #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(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