#include "http_loop.h" #include "log.h" // cppcheck-suppress syntaxError static const char *http_response_format = "HTTP/1.1 %i %s\n" "Server: lander/" LANDER_VERSION "\n" "Content-Length: %lu\n"; /* * This function precalculates the size of the total buffer required using * snprintf. When this function is called with a buf size of 0, it never tries * to write any data, but it does return the amount of bytes that would be * written. */ void http_loop_init_header(http_response *res) { if (res->status == 0) { res->status = http_ok; } const char *response_type_name = http_status_names[res->status / 100 - 1][res->status % 100]; // First we calculate the size of the start of the header int buf_size = snprintf(NULL, 0, http_response_format, res->status, response_type_name, res->body.expected_len); // We add each header's required size for (size_t i = 0; i < res->header_count; i++) { buf_size += snprintf(NULL, 0, "%s: %s\n", http_header_names[res->headers[i].type], res->headers[i].value); } // The + 1 is required to store the final null byte, but we will replace it // with the required final newline char *buf = malloc(buf_size + 1); buf_size = sprintf(buf, http_response_format, res->status, response_type_name, res->body.expected_len); for (size_t i = 0; i < res->header_count; i++) { buf_size += sprintf(&buf[buf_size], "%s: %s\n", http_header_names[res->headers[i].type], res->headers[i].value); } buf[buf_size] = '\n'; res->head = buf; res->head_len = buf_size + 1; } bool http_loop_step_write_header(event_loop_conn *conn) { http_response *res = &((http_loop_ctx *)conn->ctx)->res; // Create head response if (res->head == NULL) { http_loop_init_header(res); } // Step has finished its work if (res->head_written == res->head_len) { return true; } size_t bytes_to_write = MIN(res->head_len - res->head_written, EVENT_LOOP_BUFFER_SIZE - conn->wbuf_size); memcpy(&conn->wbuf[conn->wbuf_size], &res->head[res->head_written], bytes_to_write); conn->wbuf_size += bytes_to_write; res->head_written += bytes_to_write; return false; } bool http_loop_step_write_body(event_loop_conn *conn) { http_response *res = &((http_loop_ctx *)conn->ctx)->res; if (res->body.expected_len == res->body.len) { return true; } size_t bytes_to_write = MIN(res->body.expected_len - res->body.len, EVENT_LOOP_BUFFER_SIZE - conn->wbuf_size); size_t bytes_written; switch (res->body.type) { case http_body_buf: memcpy(&conn->wbuf[conn->wbuf_size], &(res->body.buf)[res->body.len], bytes_to_write); conn->wbuf_size += bytes_to_write; res->body.len += bytes_to_write; break; case http_body_file: bytes_written = fread(&conn->wbuf[conn->wbuf_size], sizeof(uint8_t), bytes_to_write, res->body.file); conn->wbuf_size += bytes_written; res->body.len += bytes_written; break; } return false; } void http_loop_handle_response(event_loop_conn *conn) { http_loop_ctx *ctx = conn->ctx; // Non-routed requests also need to be processed const http_step *steps = ctx->route != NULL ? ctx->route->steps_res : http_default_res_steps; while ((conn->state == event_loop_conn_state_res) && (steps[ctx->current_step] != NULL) && steps[ctx->current_step](conn)) { ctx->current_step++; } // Response processing can stop early be switching the connection state // After response processing has finished its work, we reset the context to // prepare for a new request if ((conn->state != event_loop_conn_state_res) || (steps[ctx->current_step] == NULL)) { http_loop_ctx_reset(ctx); conn->state = event_loop_conn_state_req; } }