#include "http_types.h" #include "socket_helper.h" #include #include #include #include #include /** * 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; }