274 lines
9.9 KiB
C
274 lines
9.9 KiB
C
#include "http_types.h"
|
|
#include "socket_helper.h"
|
|
#include <errno.h>
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
/**
|
|
* Get the next \n in a \r\n sequence within the buffer.
|
|
*
|
|
* @param buffer The buffer to scan.
|
|
* @param buffer_size The max size of the buffer
|
|
*
|
|
* @return A pointer to the terminating \n in the \r\n sequence.
|
|
*/
|
|
static const char *get_line_terminator(const char *buffer, int buffer_size) {
|
|
const char *prev_cursor = NULL;
|
|
const char *cursor = buffer;
|
|
for (int i = 0; i < buffer_size; (i++, cursor++)) {
|
|
if (*cursor == '\0') {
|
|
return NULL;
|
|
}
|
|
if (prev_cursor != NULL && *prev_cursor == '\r' && *cursor == '\n') {
|
|
return cursor;
|
|
}
|
|
prev_cursor = cursor;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Read a line from a buffer, and parse headers
|
|
*
|
|
* @param buffer The buffer to read from
|
|
* @param buffer_size The size of the buffer
|
|
* @param message The http_message to store headers in
|
|
*
|
|
* @return A line_read_result indicating the status of reading the line
|
|
*/
|
|
static struct line_read_result read_line(const char *buffer, int buffer_size, struct http_message *message) {
|
|
const char *line_terminator = get_line_terminator(buffer, buffer_size);
|
|
if (line_terminator == NULL) {
|
|
struct line_read_result result = {RESULT_NO_LINE, NULL, 0};
|
|
return result;
|
|
}
|
|
|
|
int line_length = 0;
|
|
const char *header_delim = NULL;
|
|
for (const char *cursor = buffer; cursor != line_terminator; (cursor++, line_length++)) {
|
|
if (header_delim == NULL && *cursor == ':') {
|
|
header_delim = cursor;
|
|
}
|
|
}
|
|
|
|
struct line_read_result result;
|
|
// Add the space for the \n
|
|
int line_size = line_length + 1;
|
|
result.line = malloc((line_size + 1) * sizeof(char));
|
|
memcpy(result.line, buffer, line_size);
|
|
result.line[line_size] = '\0';
|
|
result.bytes_read = line_size;
|
|
if (header_delim != NULL) {
|
|
result.line_type = RESULT_HEADER;
|
|
int name_size = header_delim - buffer;
|
|
char *header_name = malloc((name_size + 1) * sizeof(char));
|
|
// Need to subtract \r\n and colon.
|
|
// TODO: Support line-continued headers
|
|
int value_size = line_size - name_size - 3;
|
|
char *header_value = malloc((value_size + 1) * sizeof(char));
|
|
memcpy(header_name, buffer, name_size);
|
|
header_name[name_size] = '\0';
|
|
// Must start copying after the :
|
|
memcpy(header_value, (buffer + name_size + 1), value_size);
|
|
header_value[value_size] = '\0';
|
|
struct http_header *new_header = insert_header(header_name, header_value, message->headers);
|
|
if (message->headers == NULL) {
|
|
message->headers = new_header;
|
|
}
|
|
free(header_value);
|
|
free(header_name);
|
|
} else if (strcmp(result.line, "\r\n") == 0) {
|
|
result.line_type = RESULT_BLANK_LINE;
|
|
} else {
|
|
result.line_type = RESULT_START_LINE;
|
|
message->start_line = malloc((line_size + 1) * sizeof(char));
|
|
strcpy(message->start_line, result.line);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Get the strategy to take when reading from the socket
|
|
*
|
|
* @param headers The headers to use to infer this decision.
|
|
*
|
|
* @return A populated socket_read_info, with -1 as length if no length is specified by the strategy.
|
|
*/
|
|
static struct socket_read_info get_read_info(const struct http_header *headers) {
|
|
const struct http_header *header_cursor = headers;
|
|
struct socket_read_info info = {STRATEGY_EOF, -1};
|
|
while (header_cursor != NULL) {
|
|
if (headercmp(header_cursor, HEADER_CONTENT_LENGTH) == 0) {
|
|
info.strategy = STRATEGY_CONTENT_LENGTH;
|
|
info.length = strtol(header_cursor->value, NULL, 10);
|
|
if (errno == EINVAL || errno == ERANGE) {
|
|
info.strategy = STRATEGY_UNDEFINED;
|
|
info.length = -1;
|
|
return info;
|
|
}
|
|
}
|
|
header_cursor = header_cursor->next_header;
|
|
}
|
|
|
|
return info;
|
|
}
|
|
|
|
/**
|
|
* Reads a socket based on a given content_length, usually from a Content-Length header.
|
|
*
|
|
* @param socket_fd The file descriptor of the socket.
|
|
* @param content_length The content length to read from the socket.
|
|
*
|
|
* @return The content in the socket. Must be freed by the caller.
|
|
*/
|
|
static char *read_body_by_content_length(int socket_fd, long content_length) {
|
|
char *result = malloc((content_length + 1) * sizeof(char));
|
|
char *buffer = malloc(content_length * sizeof(char));
|
|
int total_bytes_read = 0;
|
|
int bytes_read = 0;
|
|
int write_offset = 0;
|
|
while ((total_bytes_read += bytes_read = read(socket_fd, buffer, content_length)) <= content_length && bytes_read > 0) {
|
|
memcpy(result + write_offset, buffer, bytes_read);
|
|
write_offset += bytes_read;
|
|
// If we've read exactly the number of bytes we need to, we don't need to wait for more data.
|
|
if (total_bytes_read == content_length) {
|
|
break;
|
|
}
|
|
}
|
|
free(buffer);
|
|
result[total_bytes_read] = '\0';
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Reads a socket until EOF
|
|
*
|
|
* @param socket_fd The file descriptor of the socket.
|
|
*
|
|
* @return The content in the socket. Must be freed by caller.
|
|
*/
|
|
static char *read_body_until_eof(int socket_fd) {
|
|
ssize_t current_result_size = 0;
|
|
int write_offset = 0;
|
|
char *buffer = malloc(BUFFER_SIZE * sizeof(char));
|
|
char *result = NULL;
|
|
ssize_t bytes_read;
|
|
while ((bytes_read = read(socket_fd, buffer, BUFFER_SIZE)) > 0) {
|
|
current_result_size += bytes_read;
|
|
if (result == NULL) {
|
|
result = malloc(bytes_read);
|
|
} else {
|
|
result = realloc(result, current_result_size);
|
|
}
|
|
memcpy(result + write_offset, buffer, bytes_read);
|
|
write_offset += bytes_read;
|
|
}
|
|
free(buffer);
|
|
result = realloc(result, current_result_size + 1);
|
|
result[current_result_size] = '\0';
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Gets all remote pieces of a given socket.
|
|
*
|
|
* @param socket_fd The socket file descriptor.
|
|
* @param type An indication of this socket is for a client or a server
|
|
* @param message A http_message to store the output in.
|
|
*
|
|
* @return A socket_result indicating the result of the read.
|
|
*/
|
|
enum socket_result get_all_remote_parts(int socket_fd, enum socket_type type, struct http_message *message) {
|
|
ssize_t current_result_size = 0;
|
|
char *result = NULL;
|
|
char *buffer = malloc(BUFFER_SIZE * sizeof(char));
|
|
ssize_t bytes_read;
|
|
int write_offset = 0;
|
|
int read_offset = 0;
|
|
bool have_start_line = false;
|
|
bool have_blank_line = false;
|
|
// Loop through all available info until we hit the end of the headers.
|
|
while (!have_blank_line && (bytes_read = read(socket_fd, buffer, BUFFER_SIZE)) > 0) {
|
|
current_result_size += bytes_read;
|
|
//Allocate a new result buffer if we need to, otherwise grow the existing one.
|
|
if (result == NULL) {
|
|
result = malloc(bytes_read);
|
|
} else {
|
|
result = realloc(result, current_result_size);
|
|
}
|
|
memcpy(result + write_offset, buffer, bytes_read);
|
|
struct line_read_result line_result = {};
|
|
while ((line_result = read_line(result + read_offset, current_result_size - read_offset, message),
|
|
line_result.line_type != RESULT_NO_LINE)) {
|
|
read_offset += line_result.bytes_read;
|
|
free(line_result.line);
|
|
if (!have_start_line && line_result.line_type == RESULT_BLANK_LINE) {
|
|
// If we don't have a start line, we can skip any blank lines.
|
|
continue;
|
|
} else if (!have_start_line && line_result.line_type != RESULT_START_LINE) {
|
|
// If we don't start with a start line, we don't have a valid request.
|
|
free(buffer);
|
|
free(result);
|
|
return RESULT_MALFORMED;
|
|
} else if (line_result.line_type == RESULT_BLANK_LINE) {
|
|
// If we hit a blank line, we're done reading headers.
|
|
have_blank_line = true;
|
|
break;
|
|
} else if (line_result.line_type == RESULT_START_LINE) {
|
|
if (have_start_line) {
|
|
// If we have a start line already, we can't have another.
|
|
free(buffer);
|
|
free(result);
|
|
return RESULT_MALFORMED;
|
|
}
|
|
|
|
have_start_line = true;
|
|
}
|
|
}
|
|
write_offset = current_result_size;
|
|
}
|
|
// If we never got a blank line, the message never finished.
|
|
if (!have_blank_line) {
|
|
free(buffer);
|
|
free(result);
|
|
return RESULT_MALFORMED;
|
|
}
|
|
struct socket_read_info read_info = get_read_info(message->headers);
|
|
char *body_result;
|
|
if (read_info.strategy == STRATEGY_UNDEFINED) {
|
|
free(buffer);
|
|
free(result);
|
|
return RESULT_MALFORMED;
|
|
} else if (read_info.strategy == STRATEGY_CONTENT_LENGTH) {
|
|
long net_content_length = read_info.length - (current_result_size - read_offset);
|
|
// Include space for null term
|
|
current_result_size += net_content_length + 1;
|
|
result = realloc(result, current_result_size);
|
|
body_result = read_body_by_content_length(socket_fd, net_content_length);
|
|
} else if (read_info.strategy == STRATEGY_EOF && type == TYPE_SERVER) {
|
|
// Servers cannot just wait for EOF with a client, so we just skip the action.
|
|
body_result = malloc(1 * sizeof(char));
|
|
*body_result = '\0';
|
|
current_result_size += 1;
|
|
result = realloc(result, current_result_size);
|
|
} else {
|
|
// In all other cases, such as if we have EOF strategy on a client.
|
|
body_result = read_body_until_eof(socket_fd);
|
|
current_result_size += strlen(body_result);
|
|
result = realloc(result, current_result_size);
|
|
}
|
|
strcpy(result + write_offset, body_result);
|
|
free(body_result);
|
|
|
|
free(buffer);
|
|
message->contents = result;
|
|
|
|
return RESULT_OK;
|
|
}
|