aisl/library/http.c

429 lines
11 KiB
C
Raw Normal View History

2019-07-13 14:04:06 +02:00
/******************************************************************************
*
* Copyright (c) 2017-2019 by Löwenware Ltd
* Please, refer LICENSE file for legal information
*
******************************************************************************/
/**
* @file http.c
* @author Ilja Kartašov <ik@lowenware.com>
* @brief HTTP module source file
*
* @see https://lowenware.com/aisl/
*/
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include "client.h"
#include "stream.h"
#include "debug.h"
#include "http.h"
static AislHttpMethod
http_method_from_string(const char *method, int32_t length)
{
int i;
AislHttpMethod methods[3] = {0, 0, 0};
switch(length) {
case 3:
methods[0] = AISL_HTTP_GET;
methods[1] = AISL_HTTP_PUT;
methods[2] = AISL_HTTP_PRI;
break;
case 4:
methods[0] = AISL_HTTP_POST;
methods[1] = AISL_HTTP_HEAD;
break;
case 5:
methods[0] = AISL_HTTP_TRACE;
break;
case 6:
methods[0] = AISL_HTTP_DELETE;
break;
case 7:
methods[0] = AISL_HTTP_OPTIONS;
methods[1] = AISL_HTTP_CONNECT;
break;
}
for (i=0; i<sizeof (methods)/sizeof (AislHttpMethod); i++) {
if (!(methods[i]))
break;
if (strcmp(method, aisl_http_method_to_string(methods[i]))==0)
return methods[i];
}
return AISL_HTTP_METHOD_UNKNOWN;
}
static AislHttpVersion
http_version_from_string(const char *version_string)
{
if (strncmp(version_string, "HTTP/", 5)==0) {
if (strncmp(&version_string[5], "0.9", 3)==0) return AISL_HTTP_0_9;
if (strncmp(&version_string[5], "1.0", 3)==0) return AISL_HTTP_1_0;
if (strncmp(&version_string[5], "1.1", 3)==0) return AISL_HTTP_1_1;
if (strncmp(&version_string[5], "2.0", 3)==0) return AISL_HTTP_2_0;
}
return 0;
}
/* Library Level */
ParserStatus
http_10_parse_request(char *data, int32_t *p_size, AislStream stream)
{
/* STEP 1. Split data according to HTTP request format
*
* GET http://lowenware.com:80/index.html?param=value HTTP/1.1\r\n
* ^ ^ ^ ^ ^ ^ ^ ^
* | | | | | | | |
* | | | | | | | +--- newline
* | | | | | | +------------- version
* | | | | | +------------------------- query
* | | | | +------------------------------------- path
* | | | +--------------------------------------- port
* | | +----------------------------------------------------- host
* | +------------------------------------------------------------ uri
* +---------------------------------------------------------------- method
*/
char *uri = NULL,
*uri_end = NULL,
*host = NULL,
*port = NULL,
*path = NULL,
*query = NULL,
*version = NULL,
*newline = NULL,
*method = data,
*method_end = NULL;
AislHttpMethod http_method;
AislHttpVersion http_version;
int32_t size = *p_size;
while(!newline && size--) {
switch(*data)
{
case ' ':
if (!method_end)
method_end = data;
else if (path && !uri_end)
uri_end = data;
break;
case ':':
if (uri && !host) {
host = data+3;
} else if (host && !port) {
port = data+1;
} else if (version) {
DPRINTF("bad character in HTTP version (%c)", *data);
return HTTP_PARSER_ERROR;
}
break;
case '/':
if (!path && data > host) {
path = data;
if (!uri)
uri = path;
} else if (version && data-version != 4) {
DPRINTF("wrong HTTP version length");
return HTTP_PARSER_ERROR;
}
break;
case '?':
if (!query) {
query = data+1;
} else if (version) {
DPRINTF("bad character in HTTP version (%c)", *data);
return HTTP_PARSER_ERROR;
}
break;
case '\n':
newline = data;
break;
case '\r':
if (!version) {
DPRINTF("unexpected end of HTTP request");
return HTTP_PARSER_ERROR;
}
break;
default:
if (!uri && method_end) {
uri = data;
} else if (!version && uri_end) {
version = data;
} else if (version && data-version > 7) {
DPRINTF("bad HTTP version length");
return HTTP_PARSER_ERROR;
}
}
data++;
}
/* STEP 2. Verify splitting was completed */
/* Was request sent? */
if (!newline)
return HTTP_PARSER_HUNGRY;
/* Check mandatory parts presence */
if (!method_end || !path || !uri_end || !version) {
DPRINTF("parser error: method=%d, path=%d, uri_end=%d, version=%d",
(method_end ? 1 : 0), (path ? 1 : 0), (uri_end ? 1 : 0),
(version ? 1: 0));
return HTTP_PARSER_ERROR;
}
*method_end = 0;
*newline = 0;
*uri_end = 0;
http_method = http_method_from_string(method, method_end - method);
if (http_method == AISL_HTTP_METHOD_UNKNOWN) {
DPRINTF("invalid HTTP method");
return HTTP_PARSER_ERROR;
}
if ((http_version = http_version_from_string(version))==0) {
DPRINTF("invalid HTTP version");
return HTTP_PARSER_ERROR;
}
if (query) {
*(query - 1) = 0;
} else {
query = uri_end;
}
if (host) {
if (strncmp(uri, "http://", 7) || strncmp(uri, "https://", 8)) {
DPRINTF("invalid HTTP uri");
return HTTP_PARSER_ERROR;
}
if (port)
*(port - 1) = 0;
}
stream->client->http_version = http_version;
aisl_stream_set_request(stream, http_method, path, query);
if (host)
aisl_stream_set_header(stream, "host", host);
/* how many characters has been read */
*(p_size) = size;
return HTTP_PARSER_SUCCESS;
}
ParserStatus
http_10_parse_header(char *data, int32_t *p_size, AislStream stream)
{
int32_t size = *p_size;
char *key = data,
*colon = NULL,
*val = NULL,
*val_end = NULL,
*newline = NULL;
DPRINTF("parse header: %p, %d, %02X %02X", data, *p_size, *data & 0xFF, *(data+1) & 0xFF);
while(!newline && size-- ) {
switch(*data) {
case ' ':
if (val && !val_end)
val_end = data;
break;
case ':':
if (!colon) {
if (colon == key) {
DPRINTF("parser error: nameless HTTP header");
return HTTP_PARSER_ERROR;
}
colon = data;
}
break;
case '\n':
newline = data;
case '\r':
if (!val_end && val)
val_end = data;
break;
default:
if (!colon) {
*data = tolower(*data);
} else if (!val) {
if (colon)
val = data;
}
if (val_end)
val_end = NULL;
}
data++;
}
if (!newline)
return HTTP_PARSER_HUNGRY;
/* DPRINTF("(%p == %p); *key == 0x%02x", newline, key, *key & 0xFF); */
if (colon && val && val_end) {
*colon = 0;
*val_end = 0;
aisl_stream_set_header(stream, key, val);
*p_size = size;
return HTTP_PARSER_SUCCESS;
} else if (newline == key || (newline == key+1 && *key == '\r')) {
*p_size = size;
DPRINTF("end of headers received");
return (aisl_stream_set_end_of_headers(stream) == 0) ?
HTTP_PARSER_READY : HTTP_PARSER_SUCCESS;
}
DPRINTF("parser error: invalid HTTP header");
return HTTP_PARSER_ERROR;
}
ParserStatus
http_10_parse_body(char *data, int32_t *p_size, AislStream stream)
{
int32_t size = *p_size;
if (!size)
return HTTP_PARSER_HUNGRY;
*p_size = 0;
switch (aisl_stream_set_body(stream, data, size)) {
case 0:
return HTTP_PARSER_READY;
case -1:
DPRINTF("parser error: invalid HTTP body length");
return HTTP_PARSER_ERROR;
default:
return HTTP_PARSER_SUCCESS;
}
}
/* API Level */
__attribute__ ((visibility ("default") ))
const char *
aisl_http_version_to_string(AislHttpVersion version)
{
switch (version) {
case AISL_HTTP_0_9:
return "HTTP/0.9";
case AISL_HTTP_1_0:
return "HTTP/1.0";
case AISL_HTTP_1_1:
return "HTTP/1.1";
case AISL_HTTP_2_0:
return "HTTP/2.0";
}
return "";
}
__attribute__ ((visibility ("default") ))
const char *
aisl_http_response_to_string(AislHttpResponse code)
{
switch (code) {
/* most common for faster behavior */
case AISL_HTTP_OK: return "OK";
case AISL_HTTP_MOVED_PERMANENTLY: return "Moved Permanently";
/* informational */
case AISL_HTTP_CONTINUE: return "Continue";
case AISL_HTTP_SWITCHING_PROTOCOLS: return "Switching Protocols";
/* Successful */
case AISL_HTTP_CREATED: return "Created";
case AISL_HTTP_ACCEPTED: return "Accepted";
case AISL_HTTP_NON_AUTHORITATIVE_INFORMATION: return "Non-Authoritative Information";
case AISL_HTTP_NO_CONTENT: return "No Content";
case AISL_HTTP_RESET_CONTENT: return "Reset Content";
case AISL_HTTP_PARTIAL_CONTENT: return "Partial Content";
/* redirection */
case AISL_HTTP_MULTIPLE_CHOICES: return "Multiple Choices";
case AISL_HTTP_FOUND: return "Found";
case AISL_HTTP_SEE_OTHER: return "See other";
case AISL_HTTP_NOT_MODIFIED: return "Not Modified";
case AISL_HTTP_USE_PROXY: return "Use Proxy";
case AISL_HTTP_UNUSED: return "(unused)";
case AISL_HTTP_TEMPORARY_REDIRECT: return "Temporary Redirect";
/* client error */
case AISL_HTTP_BAD_REQUEST: return "Bad Request";
case AISL_HTTP_UNAUTHORIZED: return "Unauthorized";
case AISL_HTTP_PAYMENT_REQUIRED: return "Payment Required";
case AISL_HTTP_FORBIDDEN: return "Forbidden";
case AISL_HTTP_NOT_FOUND: return "Not Found";
case AISL_HTTP_METHOD_NOT_ALLOWED: return "Method Not Allowed";
case AISL_HTTP_NOT_ACCEPTABLE: return "Not Acceptable";
case AISL_HTTP_PROXY_AUTHENTICATION_REQUIRED: return "Proxy Authentication Required";
case AISL_HTTP_REQUEST_TIMEOUT: return "Request Timeout";
case AISL_HTTP_CONFLICT: return "Conflict";
case AISL_HTTP_GONE: return "Gone";
case AISL_HTTP_LENGTH_REQUIRED: return "Length Required";
case AISL_HTTP_PRECONDITION_FAILED: return "Precondition Failed";
case AISL_HTTP_REQUEST_ENTITY_TOO_LARGE: return "Request Entity Too Large";
case AISL_HTTP_REQUEST_URI_TOO_LONG: return "Request-URI Too Long";
case AISL_HTTP_UNSUPPORTED_MEDIA_TYPE: return "Unsupported Media Type";
case AISL_HTTP_REQUESTED_RANGE_NOT_SATISFIABLE: return "Requested Range Not Satisfiable";
case AISL_HTTP_EXPECTATION_FAILED: return "Expectation Failed";
/* server error */
case AISL_HTTP_INTERNAL_SERVER_ERROR: return "Internal Server Error";
case AISL_HTTP_NOT_IMPLEMENTED: return "Not Implemented";
case AISL_HTTP_BAD_GATEWAY: return "Bad Gateway";
case AISL_HTTP_SERVICE_UNAVAILABLE: return "Service Unavailable";
case AISL_HTTP_GATEWAY_TIMEOUT: return "Gateway Timeout";
case AISL_HTTP_VERSION_NOT_SUPPORTED: return "HTTP Version Not Supported";
}
return "";
}
__attribute__ ((visibility ("default") ))
const char *
aisl_http_method_to_string( AislHttpMethod method )
{
switch(method) {
case AISL_HTTP_GET: return "GET";
case AISL_HTTP_PUT: return "PUT";
case AISL_HTTP_POST: return "POST";
case AISL_HTTP_HEAD: return "HEAD";
case AISL_HTTP_TRACE: return "TRACE";
case AISL_HTTP_DELETE: return "DELETE";
case AISL_HTTP_OPTIONS: return "OPTIONS";
case AISL_HTTP_CONNECT: return "CONNECT";
case AISL_HTTP_PRI: return "PRI";
case AISL_HTTP_METHOD_UNKNOWN: break;
}
return "";
}