blob: 7c8568099125d492bebdb0d90cf6de9b3938ca80 [file] [log] [blame] [raw]
/*
copyright: Boaz segev, 2016-2017
license: MIT
Feel free to copy, use and enjoy according to the license provided.
*/
#include "spnlock.inc"
#include "http.h"
#include "http1.h"
#include "http1_request.h"
#include "http1_simple_parser.h"
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
char *HTTP1_Protocol_String = "facil_http/1.1_protocol";
/* *****************************************************************************
HTTP/1.1 data structures
*/
typedef struct http1_protocol_s {
protocol_s protocol;
http_settings_s *settings;
void (*on_request)(http_request_s *request);
struct http1_protocol_s *next;
http1_request_s request;
ssize_t len; /* used as a persistent socket `read` indication. */
} http1_protocol_s;
static void http1_on_data(intptr_t uuid, http1_protocol_s *protocol);
/* *****************************************************************************
HTTP/1.1 pool
*/
static struct {
spn_lock_i lock;
uint8_t init;
http1_protocol_s *next;
http1_protocol_s protocol_mem[HTTP1_POOL_SIZE];
} http1_pool = {.lock = SPN_LOCK_INIT, .init = 0};
static void http1_free(intptr_t uuid, http1_protocol_s *pr) {
if ((uintptr_t)pr < (uintptr_t)http1_pool.protocol_mem ||
(uintptr_t)pr >= (uintptr_t)(http1_pool.protocol_mem + HTTP1_POOL_SIZE))
goto use_free;
spn_lock(&http1_pool.lock);
pr->next = http1_pool.next;
http1_pool.next = pr;
spn_unlock(&http1_pool.lock);
return;
use_free:
free(pr);
return;
(void)uuid;
}
static inline void http1_set_protocol_data(http1_protocol_s *pr) {
pr->protocol = (protocol_s){
.on_data = (void (*)(intptr_t, protocol_s *))http1_on_data,
.on_close = (void (*)(intptr_t uuid, protocol_s *))http1_free};
pr->request.request = (http_request_s){.fd = 0, .http_version = HTTP_V1};
pr->request.header_pos = pr->request.buffer_pos = pr->len = 0;
}
static http1_protocol_s *http1_alloc(void) {
http1_protocol_s *pr;
spn_lock(&http1_pool.lock);
if (!http1_pool.next)
goto use_malloc;
pr = http1_pool.next;
http1_pool.next = pr->next;
spn_unlock(&http1_pool.lock);
http1_request_clear(&pr->request.request);
pr->request.request.settings = pr->settings;
pr->len = 0;
return pr;
use_malloc:
if (http1_pool.init == 0)
goto initialize;
spn_unlock(&http1_pool.lock);
// fprintf(stderr, "using malloc\n");
pr = malloc(sizeof(*pr));
http1_set_protocol_data(pr);
return pr;
initialize:
http1_pool.init = 1;
for (size_t i = 1; i < (HTTP1_POOL_SIZE - 1); i++) {
http1_set_protocol_data(http1_pool.protocol_mem + i);
http1_pool.protocol_mem[i].next = http1_pool.protocol_mem + (i + 1);
}
http1_pool.protocol_mem[HTTP1_POOL_SIZE - 1].next = NULL;
http1_pool.next = http1_pool.protocol_mem + 1;
spn_unlock(&http1_pool.lock);
http1_set_protocol_data(http1_pool.protocol_mem);
return http1_pool.protocol_mem;
}
/* *****************************************************************************
HTTP callbacks
*/
#define HTTP_BODY_CHUNK_SIZE 3072 // 4096
static void http1_on_header_found(http_request_s *request,
http_header_s *header) {
((http1_request_s *)request)
->headers[((http1_request_s *)request)->header_pos] = *header;
((http1_request_s *)request)->header_pos += 1;
}
static void http1_on_data(intptr_t uuid, http1_protocol_s *pr);
static void http1_on_data_def(intptr_t uuid, protocol_s *pr, void *ignr) {
sock_touch(uuid);
http1_on_data(uuid, (http1_protocol_s *)pr);
(void)ignr;
}
/* parse and call callback */
static void http1_on_data(intptr_t uuid, http1_protocol_s *pr) {
ssize_t result;
char buff[HTTP_BODY_CHUNK_SIZE];
http1_request_s *request = &pr->request;
char *buffer = request->buffer;
for (;;) {
// handle requests with no file data
if (request->request.body_file <= 0) {
// request headers parsing
if (pr->len == 0) {
buffer = request->buffer;
// make sure headers don't overflow
pr->len = sock_read(uuid, buffer + request->buffer_pos,
HTTP1_MAX_HEADER_SIZE - request->buffer_pos);
// update buffer read position.
request->buffer_pos += pr->len;
// if (len > 0) {
// fprintf(stderr, "\n----\nRead from socket, %lu bytes, total
// %lu:\n",
// len, request->buffer_pos);
// for (size_t i = 0; i < request->buffer_pos; i++) {
// fprintf(stderr, "%c", buffer[i] ? buffer[i] : '-');
// }
// fprintf(stderr, "\n");
// }
}
if (pr->len <= 0)
goto finished_reading;
// parse headers
result =
http1_parse_request_headers(buffer, request->buffer_pos,
&request->request, http1_on_header_found);
// review result
if (result >= 0) { // headers comeplete
// are we done?
if (request->request.content_length == 0 || request->request.body_str) {
goto handle_request;
}
if (request->request.content_length > pr->settings->max_body_size) {
goto body_to_big;
}
// initialize or submit body data
result = http1_parse_request_body(buffer + result, pr->len - result,
(http_request_s *)request);
if (result >= 0) {
request->buffer_pos += result;
goto handle_request;
} else if (result == -1) // parser error
goto parser_error;
goto parse_body;
} else if (result == -1) // parser error
goto parser_error;
// assume incomplete (result == -2), even if wrong, we're right.
pr->len = 0;
continue;
}
if (request->request.body_file > 0) {
// fprintf(stderr, "Body File\n");
parse_body:
buffer = buff;
// request body parsing
pr->len = sock_read(uuid, buffer, HTTP_BODY_CHUNK_SIZE);
if (pr->len <= 0)
goto finished_reading;
result = http1_parse_request_body(buffer, pr->len, &request->request);
if (result >= 0) {
goto handle_request;
} else if (result == -1) // parser error
goto parser_error;
if (pr->len < HTTP_BODY_CHUNK_SIZE) // pause parser for more data
goto finished_reading;
goto parse_body;
}
continue;
handle_request:
// review required headers / data
if (request->request.host == NULL)
goto bad_request;
http_settings_s *settings = pr->settings;
request->request.settings = settings;
// call request callback
if (pr && settings &&
(request->request.upgrade || settings->public_folder == NULL ||
http_response_sendfile2(
NULL, &request->request, settings->public_folder,
settings->public_folder_length, request->request.path,
request->request.path_len, settings->log_static))) {
pr->on_request(&request->request);
// fprintf(stderr, "Called on_request\n");
}
// rotate buffer for HTTP pipelining
if ((ssize_t)request->buffer_pos <= result) {
pr->len = 0;
// fprintf(stderr, "\n----\nAll data consumed.\n");
} else {
memmove(request->buffer, buffer + result, request->buffer_pos - result);
pr->len = request->buffer_pos - result;
// fprintf(stderr, "\n----\ndata after move, %lu long:\n%.*s\n", len,
// (int)len, request->buffer);
}
// fprintf(stderr, "data in buffer, %lu long:\n%.*s\n", len, (int)len,
// request->buffer);
// clear request state
http1_request_clear(&request->request);
request->buffer_pos = pr->len;
// make sure to use the correct buffer.
buffer = request->buffer;
if (pr->len) {
/* prevent this connection from hogging the thread by pipelining endless
* requests.
*/
facil_defer(.task = http1_on_data_def, .task_type = FIO_PR_LOCK_TASK,
.uuid = uuid);
return;
}
}
// no routes lead here.
fprintf(stderr,
"I am lost on a deserted island, no code can reach me here :-)\n");
goto finished_reading; // How did we get here?
parser_error:
if (request->request.headers_count >= HTTP1_MAX_HEADER_COUNT)
goto too_big;
bad_request:
/* handle generally bad requests */
{
http_response_s *response = http_response_create(&request->request);
response->status = 400;
if (pr->settings->public_folder &&
!http_response_sendfile2(response, &request->request,
pr->settings->public_folder,
pr->settings->public_folder_length, "400.html",
8, pr->settings->log_static))
http_response_write_body(response, "Bad Request", 11);
http_response_finish(response);
sock_close(uuid);
request->buffer_pos = 0;
goto finished_reading;
}
too_big:
/* handle oversized headers */
{
http_response_s *response = http_response_create(&request->request);
response->status = 431;
if (pr->settings->public_folder &&
!http_response_sendfile2(response, &request->request,
pr->settings->public_folder,
pr->settings->public_folder_length, "431.html",
8, pr->settings->log_static))
http_response_write_body(response, "Request Header Fields Too Large", 31);
http_response_finish(response);
sock_close(uuid);
request->buffer_pos = 0;
goto finished_reading;
body_to_big:
/* handle oversized body */
{
http_response_s *response = http_response_create(&request->request);
response->status = 413;
if (pr->settings->public_folder &&
!http_response_sendfile2(response, &request->request,
pr->settings->public_folder,
pr->settings->public_folder_length,
"413.html", 8, pr->settings->log_static))
http_response_write_body(response, "Payload Too Large", 17);
http_response_finish(response);
sock_close(uuid);
request->buffer_pos = 0;
goto finished_reading;
}
}
finished_reading:
pr->len = 0;
}
/* *****************************************************************************
HTTP listening helpers
*/
/**
Allocates memory for an upgradable HTTP/1.1 protocol.
The protocol self destructs when the `on_close` callback is called.
*/
protocol_s *http1_on_open(intptr_t fd, http_settings_s *settings) {
if (sock_uuid2fd(fd) >= (sock_max_capacity() - HTTP_BUSY_UNLESS_HAS_FDS))
goto is_busy;
http1_protocol_s *pr = http1_alloc();
pr->request.request.fd = fd;
pr->settings = settings;
pr->on_request = settings->on_request;
facil_set_timeout(fd, pr->settings->timeout);
return (protocol_s *)pr;
is_busy:
if (settings->public_folder && settings->public_folder_length) {
size_t p_len = settings->public_folder_length;
struct stat file_data = {.st_mode = 0};
char fname[p_len + 8 + 1];
memcpy(fname, settings->public_folder, p_len);
if (settings->public_folder[p_len - 1] == '/' ||
settings->public_folder[p_len - 1] == '\\')
p_len--;
memcpy(fname + p_len, "/503.html", 9);
p_len += 9;
if (stat(fname, &file_data))
goto busy_no_file;
// check that we have a file and not something else
if (!S_ISREG(file_data.st_mode) && !S_ISLNK(file_data.st_mode))
goto busy_no_file;
int file = open(fname, O_RDONLY);
if (file == -1)
goto busy_no_file;
sock_buffer_s *buffer;
buffer = sock_buffer_checkout();
memcpy(buffer->buf,
"HTTP/1.1 503 Service Unavailable\r\n"
"Content-Type: text/html\r\n"
"Connection: close\r\n"
"Content-Length: ",
94);
p_len = 94 + http_ul2a((char *)buffer->buf + 94, file_data.st_size);
memcpy(buffer->buf + p_len, "\r\n\r\n", 4);
p_len += 4;
if ((off_t)(BUFFER_PACKET_SIZE - p_len) > file_data.st_size) {
if (read(file, buffer->buf + p_len, file_data.st_size) < 0) {
close(file);
sock_buffer_free(buffer);
goto busy_no_file;
}
close(file);
buffer->len = p_len + file_data.st_size;
sock_buffer_send(fd, buffer);
} else {
buffer->len = p_len;
sock_buffer_send(fd, buffer);
sock_sendfile(fd, file, 0, file_data.st_size);
sock_close(fd);
}
return NULL;
}
busy_no_file:
sock_write(fd,
"HTTP/1.1 503 Service Unavailable\r\nContent-Length: "
"13\r\n\r\nServer Busy.",
68);
sock_close(fd);
return NULL;
}