cs-3516-assignment-1/common/socket_helper.c
2018-09-10 00:27:04 -04:00

273 lines
9.8 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.
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;
}