You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
362 lines
9.6 KiB
362 lines
9.6 KiB
/**
|
|
* Digital Voice Modem - Host Software
|
|
* GPLv2 Open Source. Use is subject to license terms.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* @package DVM / Host Software
|
|
*
|
|
*/
|
|
//
|
|
// Based on code from the CRUD project. (https://github.com/venediktov/CRUD)
|
|
// Licensed under the BPL-1.0 License (https://opensource.org/license/bsl1-0-html)
|
|
//
|
|
/*
|
|
* Copyright (c) 2003-2013 Christopher M. Kohlhoff
|
|
* Copyright (C) 2023 by Bryan Biedenkapp N2PLL
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person or organization
|
|
* obtaining a copy of the software and accompanying documentation covered by
|
|
* this license (the “Software”) to use, reproduce, display, distribute, execute,
|
|
* and transmit the Software, and to prepare derivative works of the Software, and
|
|
* to permit third-parties to whom the Software is furnished to do so, all subject
|
|
* to the following:
|
|
*
|
|
* The copyright notices in the Software and this entire statement, including the
|
|
* above license grant, this restriction and the following disclaimer, must be included
|
|
* in all copies of the Software, in whole or in part, and all derivative works of the
|
|
* Software, unless such copies or derivative works are solely in the form of
|
|
* machine-executable object code generated by a source language processor.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
* PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE
|
|
* DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN
|
|
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
|
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
#include "Defines.h"
|
|
#include "network/rest/http/HTTPRequestLexer.h"
|
|
#include "network/rest/http/HTTPRequest.h"
|
|
|
|
using namespace rest::server;
|
|
|
|
#include <cctype>
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Public Class Members
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the HTTPRequestLexer class.
|
|
/// </summary>
|
|
|
|
HTTPRequestLexer::HTTPRequestLexer() :
|
|
m_state(METHOD_START)
|
|
{
|
|
/* stub */
|
|
}
|
|
|
|
/// <summary>Reset to initial parser state.</summary>
|
|
void HTTPRequestLexer::reset()
|
|
{
|
|
m_state = METHOD_START;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Private Class Members
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/// <summary>
|
|
/// Handle the next character of input.
|
|
/// </summary>
|
|
/// <param name="req"></param>
|
|
/// <param name="input"></param>
|
|
/// <returns></returns>
|
|
HTTPRequestLexer::ResultType HTTPRequestLexer::consume(HTTPRequest& req, char input)
|
|
{
|
|
switch (m_state)
|
|
{
|
|
/*
|
|
** HTTP Method
|
|
*/
|
|
|
|
case METHOD_START:
|
|
if (!isChar(input) || isControl(input) || isSpecial(input))
|
|
return BAD;
|
|
else {
|
|
m_state = METHOD;
|
|
req.method.push_back(input);
|
|
return INDETERMINATE;
|
|
}
|
|
case METHOD:
|
|
if (input == ' ') {
|
|
m_state = URI;
|
|
return INDETERMINATE;
|
|
}
|
|
else if (!isChar(input) || isControl(input) || isSpecial(input)) {
|
|
return BAD;
|
|
}
|
|
else {
|
|
req.method.push_back(input);
|
|
return INDETERMINATE;
|
|
}
|
|
|
|
/*
|
|
** URI
|
|
*/
|
|
|
|
case URI:
|
|
if (input == ' ') {
|
|
m_state = HTTP_VERSION_H;
|
|
return INDETERMINATE;
|
|
}
|
|
else if (isControl(input)) {
|
|
return BAD;
|
|
}
|
|
else {
|
|
req.uri.push_back(input);
|
|
return INDETERMINATE;
|
|
}
|
|
|
|
/*
|
|
** HTTP/1.0
|
|
*/
|
|
case HTTP_VERSION_H:
|
|
if (input == 'H') {
|
|
m_state = HTTP_VERSION_T_1;
|
|
return INDETERMINATE;
|
|
}
|
|
else {
|
|
return BAD;
|
|
}
|
|
case HTTP_VERSION_T_1:
|
|
if (input == 'T') {
|
|
m_state = HTTP_VERSION_T_2;
|
|
return INDETERMINATE;
|
|
}
|
|
else {
|
|
return BAD;
|
|
}
|
|
case HTTP_VERSION_T_2:
|
|
if (input == 'T') {
|
|
m_state = HTTP_VERSION_P;
|
|
return INDETERMINATE;
|
|
}
|
|
else {
|
|
return BAD;
|
|
}
|
|
case HTTP_VERSION_P:
|
|
if (input == 'P') {
|
|
m_state = HTTP_VERSION_SLASH;
|
|
return INDETERMINATE;
|
|
}
|
|
else {
|
|
return BAD;
|
|
}
|
|
case HTTP_VERSION_SLASH:
|
|
if (input == '/') {
|
|
req.httpVersionMajor = 0;
|
|
req.httpVersionMinor = 0;
|
|
m_state = HTTP_VERSION_MAJOR_START;
|
|
return INDETERMINATE;
|
|
}
|
|
else {
|
|
return BAD;
|
|
}
|
|
case HTTP_VERSION_MAJOR_START:
|
|
if (isDigit(input)) {
|
|
req.httpVersionMajor = req.httpVersionMajor * 10 + input - '0';
|
|
m_state = HTTP_VERSION_MAJOR;
|
|
return INDETERMINATE;
|
|
}
|
|
else {
|
|
return BAD;
|
|
}
|
|
case HTTP_VERSION_MAJOR:
|
|
if (input == '.') {
|
|
m_state = HTTP_VERSION_MINOR_START;
|
|
return INDETERMINATE;
|
|
}
|
|
else if (isDigit(input)) {
|
|
req.httpVersionMajor = req.httpVersionMajor * 10 + input - '0';
|
|
return INDETERMINATE;
|
|
}
|
|
else {
|
|
return BAD;
|
|
}
|
|
case HTTP_VERSION_MINOR_START:
|
|
if (isDigit(input)) {
|
|
req.httpVersionMinor = req.httpVersionMinor * 10 + input - '0';
|
|
m_state = HTTP_VERSION_MINOR;
|
|
return INDETERMINATE;
|
|
}
|
|
else {
|
|
return BAD;
|
|
}
|
|
case HTTP_VERSION_MINOR:
|
|
if (input == '\r')
|
|
{
|
|
m_state = EXPECTING_NEWLINE_1;
|
|
return INDETERMINATE;
|
|
}
|
|
else if (isDigit(input))
|
|
{
|
|
req.httpVersionMinor = req.httpVersionMinor * 10 + input - '0';
|
|
return INDETERMINATE;
|
|
}
|
|
else {
|
|
return BAD;
|
|
}
|
|
|
|
case EXPECTING_NEWLINE_1:
|
|
if (input == '\n') {
|
|
m_state = HEADER_LINE_START;
|
|
return INDETERMINATE;
|
|
}
|
|
else {
|
|
return BAD;
|
|
}
|
|
|
|
/*
|
|
** Headers
|
|
*/
|
|
|
|
case HEADER_LINE_START:
|
|
if (input == '\r') {
|
|
m_state = EXPECTING_NEWLINE_3;
|
|
return INDETERMINATE;
|
|
}
|
|
else if (!req.headers.empty() && (input == ' ' || input == '\t')) {
|
|
m_state = HEADER_LWS;
|
|
return INDETERMINATE;
|
|
}
|
|
else if (!isChar(input) || isControl(input) || isSpecial(input)) {
|
|
return BAD;
|
|
}
|
|
else {
|
|
req.headers.push_back(HTTPHeader());
|
|
req.headers.back().name.push_back(std::tolower(input));
|
|
m_state = HEADER_NAME;
|
|
return INDETERMINATE;
|
|
}
|
|
|
|
case HEADER_LWS:
|
|
if (input == '\r') {
|
|
m_state = EXPECTING_NEWLINE_2;
|
|
return INDETERMINATE;
|
|
}
|
|
else if (input == ' ' || input == '\t') {
|
|
return INDETERMINATE;
|
|
}
|
|
else if (isControl(input)) {
|
|
return BAD;
|
|
}
|
|
else {
|
|
m_state = HEADER_VALUE;
|
|
req.headers.back().value.push_back(input);
|
|
return INDETERMINATE;
|
|
}
|
|
|
|
case HEADER_NAME:
|
|
if (input == ':') {
|
|
m_state = SPACE_BEFORE_HEADER_VALUE;
|
|
return INDETERMINATE;
|
|
}
|
|
else if (!isChar(input) || isControl(input) || isSpecial(input)) {
|
|
return BAD;
|
|
}
|
|
else
|
|
{
|
|
req.headers.back().name.push_back(std::tolower(input));
|
|
return INDETERMINATE;
|
|
}
|
|
|
|
case SPACE_BEFORE_HEADER_VALUE:
|
|
if (input == ' ')
|
|
{
|
|
m_state = HEADER_VALUE;
|
|
return INDETERMINATE;
|
|
}
|
|
else {
|
|
return BAD;
|
|
}
|
|
|
|
case HEADER_VALUE:
|
|
if (input == '\r') {
|
|
m_state = EXPECTING_NEWLINE_2;
|
|
return INDETERMINATE;
|
|
}
|
|
else if (isControl(input)) {
|
|
return BAD;
|
|
}
|
|
else {
|
|
req.headers.back().value.push_back(input);
|
|
return INDETERMINATE;
|
|
}
|
|
|
|
case EXPECTING_NEWLINE_2:
|
|
if (input == '\n') {
|
|
m_state = HEADER_LINE_START;
|
|
return INDETERMINATE;
|
|
}
|
|
else {
|
|
return BAD;
|
|
}
|
|
|
|
case EXPECTING_NEWLINE_3:
|
|
return (input == '\n') ? GOOD : BAD;
|
|
|
|
default:
|
|
return BAD;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if a byte is an HTTP character.
|
|
/// </summary>
|
|
/// <param name="c"></param>
|
|
/// <returns></returns>
|
|
bool HTTPRequestLexer::isChar(int c)
|
|
{
|
|
return c >= 0 && c <= 127;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if a byte is an HTTP control character.
|
|
/// </summary>
|
|
/// <param name="c"></param>
|
|
/// <returns></returns>
|
|
bool HTTPRequestLexer::isControl(int c)
|
|
{
|
|
return (c >= 0 && c <= 31) || (c == 127);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if a byte is an HTTP special character.
|
|
/// </summary>
|
|
/// <param name="c"></param>
|
|
/// <returns></returns>
|
|
bool HTTPRequestLexer::isSpecial(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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if a byte is an digit.
|
|
/// </summary>
|
|
/// <param name="c"></param>
|
|
/// <returns></returns>
|
|
bool HTTPRequestLexer::isDigit(int c)
|
|
{
|
|
return c >= '0' && c <= '9';
|
|
}
|