/*
* Copyright (C) 2022 Mikhail Burakov. This file is part of toolbox.
*
* toolbox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* toolbox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with toolbox. If not, see .
*/
#include "http_parser.h"
#include
#include
#include
#define _(...) __VA_ARGS__
/**
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* | |.0|.1|.2|.3|.4|.5|.6|.7|.8|.9|.a|.b|.c|.d|.e|.f|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* |2.| | !| "| #| $| %| &| '| (| )| *| +| ,| -| .| /|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* |3.| 0| 1| 2| 3| 4| 5| 6| 7| 8| 9| :| ;| <| =| >| ?|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* |4.| @| A| B| C| D| E| F| G| H| I| J| K| L| M| N| O|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* |5.| P| Q| R| S| T| U| V| W| X| Y| Z| [| \| ]| ^| _|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* |6.| `| a| b| c| d| e| f| g| h| i| j| k| l| m| n| o|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* |7.| p| q| r| s| t| u| v| w| x| y| z| {| || }| ~| |
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
*/
// mburakov: RFC9110 5.6.2 Tokens
static inline bool IsTchar(char item) {
static const bool kAllowed[] = {
_(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
_(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
_(0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0),
_(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0),
_(0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1),
_(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1),
_(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1),
_(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0),
_(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
_(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
_(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
_(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
_(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
_(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
_(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
_(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
};
return kAllowed[(uint8_t)item];
}
// mburakov: RFC3986 3.3 Path
static inline bool IsPchar(char item) {
static const bool kAllowed[] = {
_(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
_(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
_(0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1),
_(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1),
_(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1),
_(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1),
_(0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1),
_(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0),
_(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
_(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
_(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
_(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
_(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
_(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
_(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
_(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
};
return kAllowed[(uint8_t)item];
}
// mburakov: RFC9110 5.6.3 Whitespace
static inline bool IsOws(char item) {
bool result = item == ' ' || item == '\t';
return result;
}
// mburakov: RFC9110 5.5 Field Values
static inline bool IsVchar(char item) {
uint8_t octet = (uint8_t)item;
return octet > ' ' && octet < 0xff;
}
static enum HttpParserResult ParseMethod(
struct HttpParserState* state, const void* buffer, size_t buffer_size,
const struct HttpParserCallbacks* callbacks, void* user);
static enum HttpParserResult ParseTarget(
struct HttpParserState* state, const void* buffer, size_t buffer_size,
const struct HttpParserCallbacks* callbacks, void* user);
static enum HttpParserResult ParseVersion(
struct HttpParserState* state, const void* buffer, size_t buffer_size,
const struct HttpParserCallbacks* callbacks, void* user);
static enum HttpParserResult ParseFieldName(
struct HttpParserState* state, const void* buffer, size_t buffer_size,
const struct HttpParserCallbacks* callbacks, void* user);
static enum HttpParserResult ParseFieldValue(
struct HttpParserState* state, const void* buffer, size_t buffer_size,
const struct HttpParserCallbacks* callbacks, void* user);
static enum HttpParserResult ParseMethod(
struct HttpParserState* state, const void* buffer, size_t buffer_size,
const struct HttpParserCallbacks* callbacks, void* user) {
(void)callbacks;
(void)user;
for (const char* data = buffer;;) {
if (state->parsing_offset == buffer_size) {
return kHttpParserResultWantMore;
}
if (IsTchar(data[state->parsing_offset])) {
state->parsing_offset++;
continue;
}
if (data[state->parsing_offset] == ' ') {
state->stage = ParseTarget;
state->parsing_offset++;
state->first_size = state->parsing_offset - 1 - state->first_offset;
state->second_offset = state->parsing_offset;
return kHttpParserResultFinished;
}
state->stage = 0;
return kHttpParserResultError;
}
}
static enum HttpParserResult ParseTarget(
struct HttpParserState* state, const void* buffer, size_t buffer_size,
const struct HttpParserCallbacks* callbacks, void* user) {
(void)callbacks;
(void)user;
for (const char* data = buffer;;) {
if (state->parsing_offset == buffer_size) {
return kHttpParserResultWantMore;
}
if (IsPchar(data[state->parsing_offset])) {
state->parsing_offset++;
continue;
}
if (data[state->parsing_offset] == ' ') {
state->stage = ParseVersion;
state->parsing_offset++;
state->second_size = state->parsing_offset - 1 - state->second_offset;
state->first_offset = state->parsing_offset;
return kHttpParserResultFinished;
}
state->stage = 0;
return kHttpParserResultError;
}
}
static enum HttpParserResult ParseVersion(
struct HttpParserState* state, const void* buffer, size_t buffer_size,
const struct HttpParserCallbacks* callbacks, void* user) {
static const char kReferences[][10] = {
{'H', 'T', 'T', 'P', '/', '1', '.', '1', '\r', '\n'},
{'H', 'T', 'T', 'P', '/', '1', '.', '0', '\r', '\n'}};
for (const char* data = buffer;;) {
size_t reference_index = state->parsing_offset - state->first_offset;
if (reference_index == sizeof(kReferences[0])) {
if (callbacks && callbacks->on_request) {
callbacks->on_request(user, buffer, state->first_size,
data + state->second_offset, state->second_size);
}
state->stage = ParseFieldName;
state->first_offset = state->parsing_offset;
state->second_size = 0;
return kHttpParserResultFinished;
}
if (state->parsing_offset == buffer_size) {
return kHttpParserResultWantMore;
}
if (data[state->parsing_offset] == kReferences[0][reference_index] ||
data[state->parsing_offset] == kReferences[1][reference_index]) {
state->parsing_offset++;
continue;
}
state->stage = 0;
return kHttpParserResultError;
}
}
static enum HttpParserResult ParseFieldName(
struct HttpParserState* state, const void* buffer, size_t buffer_size,
const struct HttpParserCallbacks* callbacks, void* user) {
for (const char* data = buffer;;) {
if (state->parsing_offset == buffer_size) {
return kHttpParserResultWantMore;
}
if (data[state->parsing_offset] == '\r' &&
state->parsing_offset == state->first_offset) {
state->maybe_eol = true;
state->parsing_offset++;
continue;
}
if (data[state->parsing_offset] == '\n' &&
state->parsing_offset == state->first_offset + 1 && state->maybe_eol) {
state->stage = 0;
if (callbacks && callbacks->on_finished) {
callbacks->on_finished(user, state->parsing_offset + 1);
}
return kHttpParserResultFinished;
}
if (state->maybe_eol) {
state->stage = 0;
return kHttpParserResultError;
}
if (IsTchar(data[state->parsing_offset])) {
state->parsing_offset++;
continue;
}
if (data[state->parsing_offset] == ':') {
state->stage = ParseFieldValue;
state->parsing_offset++;
state->first_size = state->parsing_offset - 1 - state->first_offset;
if (state->first_size) return kHttpParserResultFinished;
}
state->stage = 0;
return kHttpParserResultError;
}
}
static enum HttpParserResult ParseFieldValue(
struct HttpParserState* state, const void* buffer, size_t buffer_size,
const struct HttpParserCallbacks* callbacks, void* user) {
for (const char* data = buffer;;) {
if (state->parsing_offset == buffer_size) {
return kHttpParserResultWantMore;
}
if (!state->second_size) {
if (IsOws(data[state->parsing_offset])) {
state->parsing_offset++;
continue;
}
state->second_offset = state->parsing_offset;
state->second_size = 0;
}
if (data[state->parsing_offset] == '\r') {
if (!state->second_size) {
state->stage = 0;
return kHttpParserResultError;
}
state->maybe_eol = true;
state->parsing_offset++;
continue;
}
if (data[state->parsing_offset] == '\n') {
if (!state->maybe_eol) {
state->stage = 0;
return kHttpParserResultError;
}
if (callbacks && callbacks->on_field) {
callbacks->on_field(user, data + state->first_offset, state->first_size,
data + state->second_offset, state->second_size);
}
state->stage = ParseFieldName;
state->maybe_eol = false;
state->parsing_offset++;
state->first_offset = state->parsing_offset;
state->second_size = 0;
return kHttpParserResultFinished;
}
if (state->maybe_eol) {
state->stage = 0;
return kHttpParserResultError;
}
if (IsVchar(data[state->parsing_offset])) {
state->parsing_offset++;
state->second_size = state->parsing_offset - state->second_offset;
continue;
}
if (IsOws(data[state->parsing_offset])) {
state->parsing_offset++;
continue;
}
state->stage = 0;
return kHttpParserResultError;
}
}
void HttpParserReset(struct HttpParserState* state) {
struct HttpParserState reset = {.stage = ParseMethod};
*state = reset;
}
enum HttpParserResult HttpParserParse(
struct HttpParserState* state, const void* buffer, size_t buffer_size,
const struct HttpParserCallbacks* callbacks, void* user) {
enum HttpParserResult result = kHttpParserResultFinished;
while (result == kHttpParserResultFinished && state->stage)
result = (state->stage)(state, buffer, buffer_size, callbacks, user);
return result;
}