cs-3516-assignment-1/server/socket_server.c

309 lines
11 KiB
C

#include "socket_server.h"
#include "request_handling.h"
#include "../common/http_types.h"
#include "../common/socket_helper.h"
#include "../common/buffer_helper.h"
#include "status_codes.h"
#include <errno.h>
#include <math.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <time.h>
#include <unistd.h>
/**
* Gets the address info for the given port, such that the server will listen on 0.0.0.
*
* @param port The port to listen on.
* @param info A pointer to a location where getaddrinfo can store data.
*
* @return The result of getaddrinfo. See `man getaddrinfo` for details.
*/
static int build_addr_info(int port, struct addrinfo **info) {
struct addrinfo hints = {.ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM};
// Get the number of chars in the port
int port_length = floor(log10(port)) + 1;
char *port_string = malloc((port_length + 1) * sizeof(char));
sprintf(port_string, "%d", port);
int addr_status = getaddrinfo(LISTEN_ADDR, port_string, &hints, info);
free(port_string);
return addr_status;
}
/**
* Setup the server's socket for listening.
*
* @param port The port to listen on.
*
* @return A server_info representing the result of setting up the socket.
*/
struct server_info setup(int port) {
struct server_info info = {.sock_fd = -1};
struct addrinfo *addr_info;
int addr_status = build_addr_info(port, &addr_info);
if (addr_status != 0) {
freeaddrinfo(addr_info);
info.status = STATUS_ERROR;
return info;
}
info.sock_fd = socket(addr_info->ai_family, addr_info->ai_socktype, addr_info->ai_protocol);
if (info.sock_fd == -1) {
freeaddrinfo(addr_info);
info.status = STATUS_ERROR;
return info;
}
int bind_result = bind(info.sock_fd, addr_info->ai_addr, addr_info->ai_addrlen);
if (bind_result == -1) {
freeaddrinfo(addr_info);
close(info.sock_fd);
info.sock_fd = -1;
info.status = STATUS_ERROR;
return info;
}
freeaddrinfo(addr_info);
int listen_result = listen(info.sock_fd, BACKLOG_SIZE);
if (listen_result == -1) {
close(info.sock_fd);
info.sock_fd = -1;
info.status = STATUS_ERROR;
return info;
}
info.status = STATUS_LISTENING;
return info;
}
/**
* Make an HTTP date for the date header
*
* @param buffer The buffer to write into
* @param buffer_size The max size of the buffer
*
* @return The number of bytes the format takes up, excluding the null term.
*/
static size_t make_http_date(char *buffer, size_t buffer_size) {
time_t current_time = time(NULL);
struct tm *utc_time = gmtime(&current_time);
return strftime(buffer, buffer_size, DATE_FORMAT, utc_time);
}
/**
* Sends the headers and status line of the response
*
* @param status_code The status code to send
* @param client_socket The socket to send the message on
* @param content_length The content length of the file-to-send
*
* @return -1 on error, 0 otherwise.
*/
static int send_headers(int status_code, int client_socket, long content_length) {
const char *status_code_message = get_message_from_status_code(status_code);
if (status_code_message == NULL) {
return -1;
}
// All status codes are three digits, plus the space for the null term.
char *status_code_string = malloc(4 * sizeof(char));
snprintf(status_code_string, 4, "%d", status_code);
const char *status_line_components[] = {HTTP_VERSION, " ", status_code_string, " ", status_code_message, "\r\n"};
int status_line_buffer_size = get_buffer_size(status_line_components, 6) + 1;
char *status_line_buffer = malloc(status_line_buffer_size * sizeof(char));
sprintf(status_line_buffer, "%s %s %s\r\n", HTTP_VERSION, status_code_string, status_code_message);
free(status_code_string);
// RFC2616 states that if we don't intend to persist the connection, we must send "Connection: close"
const char *connection_header_components[] = {HEADER_CONNECTION, ": close\r\n"};
char *content_length_header_buffer = "";
// Include space for the CRLF and null term
int header_buffer_size = get_buffer_size(connection_header_components, 2) + 3;
if (content_length > 0) {
// Get the number of chars in the port
int content_length_length = floor(log10(content_length)) + 1;
char *content_length_string = malloc((content_length_length + 1) * sizeof(char));
sprintf(content_length_string, "%ld", content_length);
const char *content_length_header_components[] = {HEADER_CONTENT_LENGTH, ": ", content_length_string, "\r\n"};
int content_length_header_length = get_buffer_size(content_length_header_components, 4) + 1;
header_buffer_size += content_length_header_length - 1;
content_length_header_buffer = malloc(content_length_header_length * sizeof(char));
sprintf(content_length_header_buffer, "%s: %s\r\n", HEADER_CONTENT_LENGTH, content_length_string);
free(content_length_string);
}
// The 1024 is somewhat arbitrary, but everything should fit.
char *date_header_buffer = malloc(1024 * sizeof(char));
header_buffer_size += make_http_date(date_header_buffer, 1024);
// +4 is ': \r\n'
header_buffer_size += strlen(HEADER_DATE) + 4;
char *header_buffer = malloc(header_buffer_size * sizeof(char));
sprintf(header_buffer, "%s: close\r\n%s: %s\r\n%s\r\n", HEADER_CONNECTION, HEADER_DATE, date_header_buffer, content_length_header_buffer);
free(date_header_buffer);
if (content_length > 0) {
free(content_length_header_buffer);
}
int send_result = send(client_socket, status_line_buffer, status_line_buffer_size - 1, 0);
if (send_result == -1) {
free(status_line_buffer);
free(header_buffer);
return -1;
}
send_result = send(client_socket, header_buffer, header_buffer_size - 1, 0);
if (send_result == -1) {
free(status_line_buffer);
free(header_buffer);
return -1;
}
free(status_line_buffer);
free(header_buffer);
return 0;
}
/**
* Send a file over the socket.
*
* @param client_fd The file descriptor to send on.
* @param file The file to read
*
* @return -1 on error, 0 on success.
*/
static int send_file(int client_fd, struct requested_file file) {
char *buffer = malloc(FILE_BUFFER_SIZE * sizeof(char));
int bytes_read;
while ((bytes_read = fread(buffer, sizeof(char), FILE_BUFFER_SIZE, file.file)) != 0) {
int send_result = send(client_fd, buffer, bytes_read, 0);
if (send_result == -1) {
free(buffer);
return -1;
}
}
free(buffer);
return 0;
}
/**
* Parse and validate a request line
*
* @param message An http_message with a start_line.
* @param req_line The request line of the request to write to.
*
* @return PARSE_RESULT_OK if ok, other value otherwise, depending on the state of the start line..
*/
static enum request_parse_result parse_and_validate_request(struct http_message message, struct request_line *req_line) {
int parse_result = parse_request_line(message.start_line, req_line);
if (parse_result == -1) {
return PARSE_RESULT_MALFORMED;
} else if (strcmp(req_line->http_version, HTTP_VERSION) != 0) {
return PARSE_RESULT_BAD_VERSION;
} else if (strcmp(req_line->method, "GET") != 0) {
return PARSE_RESULT_BAD_METHOD;
}
return PARSE_RESULT_OK;
}
static void free_request_items(struct http_message message) {
if (message.start_line != NULL) {
free(message.start_line);
}
if (message.contents != NULL) {
free(message.contents);
}
if (message.headers != NULL) {
free_headers(message.headers);
}
}
/**
* Serve a single request.
*
* @param sock_fd The socket file descriptor of the server
*
* @return A socket_result representing the status of the serve.
*/
enum socket_result serve_one_request(int sock_fd) {
int client_fd = accept(sock_fd, NULL, NULL);
if (client_fd == -1) {
return RESULT_WRITE_ERROR;
}
struct http_message message = {};
enum socket_result read_result = get_all_remote_parts(client_fd, TYPE_SERVER, &message);
if (read_result == RESULT_MALFORMED) {
send_headers(400, client_fd, -1);
close(client_fd);
free_request_items(message);
return RESULT_MALFORMED;
} else if (read_result != RESULT_OK){
send_headers(500, client_fd, -1);
free_request_items(message);
close(client_fd);
return RESULT_READ_ERROR;
}
struct request_line parsed_request_line = {};
enum request_parse_result parse_result = parse_and_validate_request(message, &parsed_request_line);
// Ensure our the request line's info is congruent with the server's expectations.
if (parse_result != PARSE_RESULT_OK) {
int status_code = 500;
if (parse_result == PARSE_RESULT_MALFORMED) {
status_code = 400;
} else if (parse_result == PARSE_RESULT_BAD_VERSION) {
status_code = 505;
} else if (parse_result == PARSE_RESULT_BAD_METHOD) {
status_code = 405;
}
send_headers(status_code, client_fd, -1);
free_request_line_items(&parsed_request_line);
free_request_items(message);
close(client_fd);
return RESULT_PROCESSING_ERROR;
}
// Attempt to read the file and return it.
struct requested_file file = {};
enum file_result file_get_result = get_file_from_uri(parsed_request_line.uri, &file);
if (file_get_result != RESULT_FILE_OK) {
int send_result = 0;
if (file_get_result == RESULT_NOT_FILE || (file_get_result == RESULT_IO_ERROR && errno == ENOENT)) {
send_result = send_headers(404, client_fd, -1);
} else if (file_get_result == RESULT_IO_ERROR && errno == EACCES) {
send_result = send_headers(403, client_fd, -1);
} else {
free_request_line_items(&parsed_request_line);
free_request_items(message);
send_headers(500, client_fd, -1);
shutdown(client_fd, SHUT_RDWR);
close(client_fd);
return RESULT_PROCESSING_ERROR;
}
if (send_result != 0) {
free_request_line_items(&parsed_request_line);
free_request_items(message);
shutdown(client_fd, SHUT_RDWR);
close(client_fd);
return RESULT_WRITE_ERROR;
}
} else {
send_headers(200, client_fd, file.size);
send_file(client_fd, file);
}
free_request_line_items(&parsed_request_line);
free_request_items(message);
if (file_get_result == RESULT_FILE_OK) {
fclose(file.file);
}
shutdown(client_fd, SHUT_RDWR);
close(client_fd);
return RESULT_OK;
}