#define _GNU_SOURCE ///< in order for optarg to be defined #include #include #include #include #include #include #include #include #include #include #include "shared/http.h" #define LISTEN_BACKLOG 5 //< Define maximum size of the input queue static int create_server_socket(uint16_t port, struct sockaddr_in incoming); static char * setup_server_response(char* dir, char* filename, int current_session, int sockfd); static char * get_file_contents(char* dir, char* filename); static char * wday_to_string(int day); static char * month_to_string(int month); int main(int argc, char *argv[]) { check_opts_number(argc); char *cport = "8080"; char *ifile = "index.html"; char *dir = NULL; char *response; //< To hold the server response int c; /// Process the options while((c = getopt(argc, argv, "p:i:h")) != -1) { switch(c) { case 'p': cport = optarg; break; case 'i': ifile = optarg; break; case 'h': printf("Command usage:\n"); print_usage(); exit(EXIT_SUCCESS); case '?': print_usage(); exit(EXIT_FAILURE); default: abort(); } } /// Check if the required argument is there if(argv[optind] == NULL) { fprintf(stderr, "Mandatory argument 'doc_root' missing.\n"); print_usage(); exit(EXIT_FAILURE); } else { dir = argv[optind]; } uint16_t port = atoi(cport); //< Convert the argument to integer struct sockaddr_in incoming; //< Basic incoming config incoming.sin_addr.s_addr = INADDR_ANY; //< Allow 0.0.0.0 to access the server incoming.sin_port = htons(port); //< Set the port to work on incoming.sin_family = AF_INET; //< Use IPv4 int sockfd = create_server_socket(port, incoming); //< Define a server socket socklen_t peer_addr_size = sizeof(struct sockaddr_in); /// Start accepting connections int current_session = accept(sockfd, (struct sockaddr*) &incoming, &peer_addr_size); //< Save the current session /// Setup the server response response = setup_server_response(dir, ifile, current_session, sockfd); /// Send the response to the client if(send(current_session, response, strlen(response) + 1, 0) < 0) { (void) fprintf(stderr, "ERROR: Could not send the response to the client\n"); close(current_session); close(sockfd); exit(EXIT_FAILURE); } close(current_session); close(sockfd); exit(EXIT_SUCCESS); } static void print_usage(void) { printf("server [-p PORT] [ -i INDEX ] DOC_ROOT\n"); } /** * @brief Function to check the number of arguments * @details Checks the number of arguments supplied to the command line. * If the arguments are less than the required or more than possible, it exits * @param argc - Number of arguments obtained from the command line * @return none * **/ static void check_opts_number(int argc) { if(argc <= 1) { fprintf(stderr, "At least one argument expected.\n"); print_usage(); exit(EXIT_FAILURE); } else if(argc > 6) { fprintf(stderr, "Too many arguments supplied.\n"); print_usage(); exit(EXIT_FAILURE); } } /** * @brief Function to create a socket using a default configuration for a server * @details Creates a socket using a default configuration for a server * If there was no error, it returns the socket * If there was an error, it exits and displays an error message * @param port - the port to bind the server to * incoming - basic incoming sockaddr config * @return sockfd - The socket obtained from the socket creation **/ static int create_server_socket(uint16_t port, struct sockaddr_in incoming) { int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //< Define socket with default configuration if(sockfd < 0) { (void) fprintf(stderr, "ERROR: Socket creation failed.\n"); exit(EXIT_FAILURE); } /// Bind to a port if(bind(sockfd, (struct sockaddr*) &incoming, sizeof(struct sockaddr_in)) < 0) { (void) fprintf(stderr, "ERROR: Could not bind server to port %d!\n", port); close(sockfd); exit(EXIT_FAILURE); } /// Start listening on the socket if(listen(sockfd, LISTEN_BACKLOG) < 0) { (void) fprintf(stderr, "ERROR: Could not start listening\n"); close(sockfd); exit(EXIT_FAILURE); } else { (void) fprintf(stdout, "Listening on port %d...\n", port); } return sockfd; } /** * @brief Function to create the HTTP response * @details Gets the client request * If there is no current session, it exits with an error * If the server could not receive a client connection, it exits with an error * If a GET request is received, the function tries to open the resource: * - if it could open it, it builds a 200 header and the file contents as body and adds them to response * - if it could not open it, it builds a 404 header and adds it to response * If anything other than a GET request is received, a 501 header is build, adds it to response and logs it * At the end, it returns the server response with header and body (if available) * @param dir - the directory of the file * filename - the name of the file * current_session - the current client session * sockfd - the socket configuration * @return response - A header with the status code and a body (if available) **/ static char * setup_server_response(char* dir, char* filename, int current_session, int sockfd) { if(current_session < 0) { (void) fprintf(stderr, "ERROR: Could not start accepting connections\n"); close(current_session); close(sockfd); exit(EXIT_FAILURE); } else { char request[__INT16_MAX__]; static char response[__INT16_MAX__]; if(recv(current_session, request, strlen(request) - 1, 0) < 0) { (void) fprintf(stderr, "ERROR: Could not receive connection from client\n"); close(current_session); close(sockfd); exit(EXIT_FAILURE); } else { char method[4]; strncpy(method, request, 4); //< Get the request type method[3] = 0; //< Add terminating char char header[__INT8_MAX__ + 100]; if(strcmp(method, "GET") == 0) { char *body = get_file_contents(dir, filename); if(body != NULL) { time_t t = time(NULL); struct tm tm = *gmtime(&t); char *wday = wday_to_string(tm.tm_wday); char *month = month_to_string(tm.tm_mon + 1); snprintf( header, sizeof header, "HTTP/1.1 200 OK\r\nDate: %s, %d %s %d %d:%d:%d %s\r\nContent-Length: %ld\r\nConnection: close\r\n\r\n", wday, tm.tm_mday, month, tm.tm_year + 1900, tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_zone, strlen(body) ); //< Build the 200 header strcat(response, header); strcat(response, body); } else { snprintf( header, sizeof header, "HTTP/1.1 404 Not Found\r\nConnection: close\r\n\r\n" ); //< Build the 404 header strcat(response, header); } } else { fprintf(stderr, "ERROR: Unhandled HTTP request method received\n"); snprintf( header, sizeof header, "HTTP/1.1 501 Not ImplementedConnection: close\r\n\r\n" ); //< Build 501 the header strcat(response, header); } return response; } } } /** * @brief Function to take the contents of a specified file * @details Gets the contents of the file specified * If there is no file, it returns NULL * If the file is empty, it returns " " * If the file is not empty, it returns its contents * @param dir - the directory of the file * filename - the name of the file * @return res - The contents of the file, formatted as a string **/ static char * get_file_contents(char *dir, char *filename) { char path[strlen(dir) + strlen(filename) + 100]; //< set the variable for the path with a reasonable length snprintf(path, sizeof path, "%s%s", dir, filename); FILE *file = NULL; file = fopen(path, "r"); if(file == NULL) { return NULL; } else { char *res = 0; fseek(file, 0, SEEK_END); //< Set position to end of stream int file_length = ftell(file); //< Get file length fseek(file, 0, SEEK_SET); //< Set the position to begining of stream res = malloc(file_length); //< Allocate memory for the file contents if(res != 0) { fread(res, 1, file_length, file); //< Read the contents into res } else { return " "; } return res; } } /** * @brief Function to convert a int weekday to string weekday * @details Gets the weekday as int, converts it to string and returns the string * @param day - the weekday as int * @return the weekday as a string **/ static char * wday_to_string(int day) { switch(day) { case 1: return "Mon"; case 2: return "Tue"; case 3: return "Wed"; case 4: return "Thu"; case 5: return "Fri"; case 6: return "Sat"; case 7: return "Sun"; default: return NULL; } } /** * @brief Function to convert a int month to string month * @details Gets the month as int, converts it to string and returns the string * @param day - the month as int * @return the month as a string **/ static char * month_to_string(int month) { switch(month) { case 1: return "Jan"; case 2: return "Feb"; case 3: return "Mar"; case 4: return "Apr"; case 5: return "May"; case 6: return "Jun"; case 7: return "Jul"; case 8: return "Aug"; case 9: return "Sep"; case 10: return "Oct"; case 11: return "Nov"; case 12: return "Dec"; default: return NULL; } }