442 lines
11 KiB
C
442 lines
11 KiB
C
/******************************************************************************
|
|
*
|
|
* 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"
|
|
|
|
struct http_request
|
|
{
|
|
char * method;
|
|
char * schema;
|
|
char * host;
|
|
char * port;
|
|
char * path;
|
|
char * version;
|
|
char * newline;
|
|
int32_t method_len;
|
|
int32_t schema_len;
|
|
int32_t host_len;
|
|
int32_t port_len;
|
|
int32_t path_len;
|
|
int32_t version_len;
|
|
};
|
|
|
|
typedef struct http_request * http_request_t;
|
|
|
|
static aisl_http_method_t
|
|
http_method_from_string( const char * method, int32_t length )
|
|
{
|
|
int i;
|
|
aisl_http_method_t 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(aisl_http_method_t); 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 aisl_http_version_t
|
|
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 */
|
|
|
|
http_parser_t
|
|
http_10_parse_request(char * data, int32_t * p_size, aisl_stream_t 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;
|
|
|
|
aisl_http_method_t http_method;
|
|
aisl_http_version_t 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)
|
|
return HTTP_PARSER_ERROR;
|
|
break;
|
|
|
|
case '/':
|
|
if (!path && data > host)
|
|
{
|
|
path = data;
|
|
if (!uri)
|
|
uri = path;
|
|
}
|
|
else if (version && data-version != 4)
|
|
return HTTP_PARSER_ERROR;
|
|
break;
|
|
|
|
case '?':
|
|
if (!query)
|
|
query = data+1;
|
|
else if (version)
|
|
return HTTP_PARSER_ERROR;
|
|
break;
|
|
|
|
case '\n':
|
|
newline = data;
|
|
break;
|
|
|
|
case '\r':
|
|
if (!version)
|
|
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)
|
|
return HTTP_PARSER_ERROR;
|
|
|
|
}
|
|
data++;
|
|
}
|
|
|
|
|
|
/* STEP 2. Verifly splitting was completed */
|
|
|
|
/* Was request sent? */
|
|
if (!newline)
|
|
return HTTP_PARSER_HUNGRY;
|
|
|
|
|
|
/* Check mandatory parts presence */
|
|
if (!method_end || !path || !uri_end || !version)
|
|
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)
|
|
return HTTP_PARSER_ERROR;
|
|
|
|
if ((http_version = http_version_from_string(version))==0)
|
|
return HTTP_PARSER_ERROR;
|
|
|
|
if (query)
|
|
{
|
|
*(query-1)=0;
|
|
}
|
|
else
|
|
query = uri_end;
|
|
|
|
if (host)
|
|
{
|
|
if (strncmp(uri, "http://", 7) || strncmp(uri, "https://", 8))
|
|
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;
|
|
}
|
|
|
|
|
|
http_parser_t
|
|
http_10_parse_header(char * data, int32_t * p_size, aisl_stream_t stream)
|
|
{
|
|
int32_t size = *p_size;
|
|
char * key = data,
|
|
* colon = NULL,
|
|
* val = NULL,
|
|
* val_end = NULL,
|
|
* newline = NULL;
|
|
|
|
while(!newline && size-- )
|
|
{
|
|
switch(*data)
|
|
{
|
|
case ' ':
|
|
if (val && !val_end)
|
|
val_end = data;
|
|
break;
|
|
|
|
case ':':
|
|
if (!colon)
|
|
{
|
|
if (colon == key)
|
|
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;
|
|
|
|
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'))
|
|
{
|
|
return (aisl_stream_set_end_of_headers(stream) == 0) ? HTTP_PARSER_READY :
|
|
HTTP_PARSER_SUCCESS;
|
|
}
|
|
|
|
return HTTP_PARSER_ERROR;
|
|
}
|
|
|
|
|
|
http_parser_t
|
|
http_10_parse_body(char * data, int32_t * p_size, aisl_stream_t stream)
|
|
{
|
|
switch (aisl_stream_set_body(stream, data, *p_size))
|
|
{
|
|
case 0: return HTTP_PARSER_READY;
|
|
case -1: return HTTP_PARSER_ERROR;
|
|
default: return HTTP_PARSER_SUCCESS;
|
|
}
|
|
}
|
|
|
|
|
|
/* API Level */
|
|
|
|
__attribute__ ((visibility ("default") ))
|
|
const char *
|
|
aisl_http_version_to_string(aisl_http_version_t 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(aisl_http_response_t 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( aisl_http_method_t 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 "";
|
|
}
|
|
|
|
|
|
|