/****************************************************************************** * * Copyright (c) 2017-2019 by Löwenware Ltd * Please, refer LICENSE file for legal information * ******************************************************************************/ /** * @file http.c * @author Ilja Kartašov * @brief HTTP module source file * * @see https://lowenware.com/aisl/ */ #include #include #include #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 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 ""; }