#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 #include #include #include #include #include #include #include #include /** * 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(¤t_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; }