/** * Jade Cheng * ICS 451 * Assignment 1 */ #include <assert.h> #include <ctype.h> #include <netinet/in.h> #include <netdb.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <time.h> #include <unistd.h> #include "jadesocket.h" /* -------------------------------------------------------------------------- */ /** * The maximum size of the request or response header in bytes. */ #define MAX_HEADER_SIZE 5000 /** * This macro displays an error that includes the file and line number and then * returns EXIT_FAILURE. */ #define RETURN_FAILURE \ { \ fprintf(stderr, "Error on line %d in %s\n", __LINE__, __FILE__); \ return EXIT_FAILURE; \ } /* -------------------------------------------------------------------------- */ /** * The request header fields. */ typedef struct http_request_header { /** * The contents of the request header. The end of line characters are * replaced with nulls. */ char buffer[MAX_HEADER_SIZE]; const char * method; const char * path; const char * protocol; /* ignored */ const char * version; /* ignored */ const char * host; /* ignored */ const char * keep_alive; /* ignored */ const char * connection; /* ignored */ /** * A pointer to the second line, which is the first line containing the * header fields. */ char * start_of_header_fields; } http_request_header; /* -------------------------------------------------------------------------- */ static int jade_strcat(char * dst, const char * src, int count); static int jade_strcpy(char * dst, const char * src, int count); static int jade_time(char * dst, int count); static int parse_header(http_request_header * header); static int parse_header_line_1(http_request_header * header); static int parse_header_line_x(http_request_header * header); static int read_header(int s, http_request_header * header); static int send_response(int s, const http_request_header * header); static int send_response_count(int s, const char * date); static int send_response_date(int s, const char * date); static int send_response_error(int s, const char * reason); static int send_response_file( int s, const char * date, const char * type, const char * path); static int handle_client(int s); /* -------------------------------------------------------------------------- */ /** * The number of times the server has been accessed. */ static int g_web_server_accessed_count; /* -------------------------------------------------------------------------- */ /** * The main entry point of the program. * * @param argc The number of arguments. * @param argv The command line arguments. * * @return EXIT_SUCCESS if successful; EXIT_FAILURE if not. */ int main(int argc, const char * argv[]) { short port; // Check the command line usage. if (argc != 2) { fprintf(stderr, "Usage: webserver port\n"); return EXIT_FAILURE; } // Get the numeric value of the port number. port = atoi(argv[1]); if (port == 0) { fprintf(stderr, "Usage: webserver port\n"); return EXIT_FAILURE; } // Accept connections until the application fails or it is aborted. When a // connection is made, the following function calls handle_client to respond // to the web request. if (0 != js_accept_all(handle_client, port, 5)) return EXIT_FAILURE; // TBD Can the code actually reach this point??? printf("webserver exited successfully.\n"); // Return successfully. return EXIT_SUCCESS; } /** * Re-write of the strcat funtion with the count parameter indicating the * maximum capacity in bytes of the destination buffer size, including the null * terminator. * * @param dst The destination string. * @param src The source string. * @param count The buffer size of the destination string. * * @return EXIT_SUCCESS if successful; EXIT_FAILURE if not. */ static int jade_strcat(char * dst, const char * src, int count) { char * end; assert(dst != NULL); assert(src != NULL); assert(count > 0); // Keep track of the last possible null-terminator. end = dst + (count - 1); // Find the end of the string. while (count-- > 0 && *dst != '\0') dst++; // Copy all the bytes into the destination string. while (count-- > 0) { *dst = *src; if (*src == '\0') return EXIT_SUCCESS; dst++; src++; } // In case of an overflow, null-terminate the string and return a failure. *end = '\0'; RETURN_FAILURE; } /** * Re-write of the strcpy funtion with the count parameter indicating the * maximum capacity in bytes of the destination buffer size, including the null * terminator. * * @param dst The destination string. * @param src The scorce string. * @param count The maximum buffer size of the destination string. * * @return EXIT_SUCCESS if successful; EXIT_FAILURE if not. */ static int jade_strcpy(char * dst, const char * src, int count) { assert(dst != NULL); assert(count > 0); // Truncate the destination string and the append the source string. *dst = '\0'; return jade_strcat(dst, src, count); } /** * This function writes the current time to a destination string. * * @param dst The destination string to store the output. * @param count The maximum buffer size of the destination string. * * @return EXIT_SUCCESS if successful; EXIT_FAILURE if not. */ static int jade_time(char * dst, int count) { time_t raw_time; struct tm * time_info; char * actual_time; size_t length; if (time(&raw_time) == -1) { RETURN_FAILURE; } time_info = localtime(&raw_time); actual_time = asctime(time_info); if (EXIT_SUCCESS != jade_strcpy(dst, actual_time, count)) { RETURN_FAILURE; } length = strlen(dst); if (length > 0 && '\n' == dst[length - 1]) dst[length - 1] = '\0'; return EXIT_SUCCESS; } /** * This function reads the request header and stores it into a structure. Only * the header->buffer is modified. * * @param s The socket descriptor. * @param header The request header structure used to store the header fields. * * @return EXIT_SUCCESS if successful; EXIT_FAILURE if not. */ static int read_header(int s, http_request_header * header) { int count; char ch; assert(header != NULL); memset(header, 0, sizeof(http_request_header)); while (recv(s, &ch, 1, 0) == 1) { if (ch == '\r') continue; header->buffer[count++] = ch; if (count == MAX_HEADER_SIZE - 1) { RETURN_FAILURE; } if (count >= 2 && 0 == strncmp("\n\n", &header->buffer[count - 2], 2)) break; } header->buffer[count - 1] = '\0'; return EXIT_SUCCESS; } /** * This function parses the request header and populates the corresponding * fields in the header structure. * * @param header The request header structure used to store the header fields. * * @return EXIT_SUCCESS if successful; EXIT_FAILURE if not. */ static int parse_header(http_request_header * header) { assert(header != NULL); if (0 == parse_header_line_1(header)) if (0 == parse_header_line_x(header)) return EXIT_SUCCESS; RETURN_FAILURE; } /** * This function parses the first line of the request header and populates the * corresponding fields in the header structure. * * @param header The request header structure used to store the header fields. * * @return EXIT_SUCCESS if successful; EXIT_FAILURE if not. */ static int parse_header_line_1(http_request_header * header) { char * anchor; char * eol; assert(header != NULL); assert(strlen(header->buffer) >= 1); assert(header->buffer[strlen(header->buffer) - 1] == '\n'); anchor = header->buffer; // Look for the end of the first line. eol = strchr(anchor, '\n'); *eol = '\0'; header->start_of_header_fields = eol + 1; // Look for the end of the method. eol = strchr(anchor, ' '); if (eol == NULL) { RETURN_FAILURE; } *eol = '\0'; header->method = anchor; anchor = eol + 1; // Look for the end of the path. eol = strchr(anchor, ' '); if (eol == NULL) { RETURN_FAILURE; } *eol = '\0'; header->path = anchor; anchor = eol + 1; // Look for the end of the protocol. eol = strchr(anchor, '/'); if (eol == NULL) { RETURN_FAILURE; } *eol = '\0'; header->protocol = anchor; anchor = eol + 1; // The rest is the version. header->version = anchor; return EXIT_SUCCESS; } /** * This function parses the rest of the lines of the request header and * populates the corresponding fields in the header structure. * * @param header The request header structure used to store the header fields. * * @return EXIT_SUCCESS if successful; EXIT_FAILURE if not. */ static int parse_header_line_x(http_request_header * header) { char * anchor; assert(header != NULL); assert(header->start_of_header_fields != NULL); anchor = header->start_of_header_fields; // Loop over all lines in the buffer. while (*anchor != '\0') { char * eol; char * value; // Null-terminate the current line. eol = strchr(anchor, '\n'); *eol = '\0'; // Look for the separator between the field name and value. value = strchr(anchor, ':'); if (value == NULL) { RETURN_FAILURE; } // Skip the whitespace before the value. do { *value++ = '\0'; } while (0 != isspace((int)*value)); // Keep track of useful headers and ignore the rest. if (0 == strcmp(anchor, "Host")) header->host = value; else if (0 == strcmp(anchor, "Keep-Alive")) header->keep_alive = value; else if (0 == strcmp(anchor, "Connection")) header->connection = value; // Move to the next line. anchor = eol + 1; } return EXIT_SUCCESS; } /** * This function sends count number data through the specified socket. * * @param s The socket descriptor. * @param date The date of the reply header. * * @return EXIT_SUCCESS if successful; EXIT_FAILURE if not. */ static int send_response_count(int s, const char * date) { static const char * header_template = "HTTP/1.0 200 OK\r\n" "Content-Type: text/plain; charset=utf-8\r\n" "Content-Length: %d\r\n" "Date: %s\r\n" "Connection: close\r\n\r\n"; int result; char header[MAX_HEADER_SIZE]; // g_web_server_accessed_count is a 32-bit number... // 32-bit int ==> -(2^31) = -2,147,483,648 ==> 11 + 1 content size. char content[12]; sprintf(content, "%d", g_web_server_accessed_count); sprintf(header, header_template, strlen(content), date); result = EXIT_FAILURE; if (EXIT_SUCCESS == js_send(s, header, strlen(header))) if (EXIT_SUCCESS == js_send(s, content, strlen(content))) result = EXIT_SUCCESS; return result; } /** * This function sends date data through the specified socket. * * @param s The socket descriptor. * @param date The date of the reply header. * * @return EXIT_SUCCESS if successful; EXIT_FAILURE if not. */ static int send_response_date(int s, const char * date) { static const char * header_template = "HTTP/1.0 200 OK\r\n" "Content-Type: text/plain; charset=utf-8\r\n" "Content-Length: %d\r\n" "Date: %s\r\n" "Connection: close\r\n\r\n"; int result; char header[MAX_HEADER_SIZE]; sprintf(header, header_template, strlen(date), date); result = EXIT_FAILURE; if (EXIT_SUCCESS == js_send(s, header, strlen(header))) if (EXIT_SUCCESS == js_send(s, date, strlen(date))) result = EXIT_SUCCESS; return result; } /** * This function sends file data through the specified socket. * * @param s The socket descriptor. * @param date The date of the reply header. * @param type The type of files to send. Program supports "image/jpeg", * "image/gif", and "text/html". * @param path The path the files in the request header. Program supports * "pic1.jpeg", "pic2.gif", and "x.html". * * @return EXIT_SUCCESS if successful; EXIT_FAILURE if not. */ static int send_response_file( int s, const char * date, const char * type, const char * path) { static const char * header_template = "HTTP/1.0 200 OK\r\n" "Content-Type: %s; charset=utf-8\r\n" "Content-Length: %d\r\n" "Date: %s\r\n" "Connection: close\r\n\r\n"; int result; FILE * f; char * buf; size_t len; char header[MAX_HEADER_SIZE]; assert(date != NULL); assert(path != NULL); assert(type != NULL); // Open the specified file as binary. if ((f = fopen(path, "rb")) == NULL) { send_response_error(s, "404 Not Found"); RETURN_FAILURE; } // Seek to the end of the file and count the length of the file. fseek(f, 0, SEEK_END); len = ftell(f); // Move the pointer back to the beginning. fseek(f, 0, SEEK_SET); // Allocate buffer size accordingly and read the file in the buffer. buf = (char *)malloc(len + 1); fread(buf, 1, len, f); fclose(f); buf[len] = '\0'; // Not necessarily needed except when debugging. sprintf(header, header_template, type, len, date); result = EXIT_FAILURE; if (EXIT_SUCCESS == js_send(s, header, strlen(header))) if (EXIT_SUCCESS == js_send(s, buf, len)) result = EXIT_SUCCESS; free(buf); return result; } /** * This function sends an error reply header to any request that is not * supported. * * @param s The socket descriptor. * @param reason The reason for the error, including the response code. * * @return EXIT_SUCCESS if successful; EXIT_FAILURE if not. */ static int send_response_error(int s, const char * reason) { static const char * header_template = "HTTP/1.0 %s\r\n" "Connection: close\r\n\r\n"; char header[MAX_HEADER_SIZE]; assert(reason != NULL); sprintf(header, header_template, reason); return js_send(s, header, strlen(header)); } /** * This function uses the parsed request header and the specified socket to * send a corresponding reply. * * @param s The socket descriptor. * @param header The parsed header. * * @return EXIT_SUCCESS if successful; EXIT_FAILURE if not. */ static int send_response(int s, const http_request_header * header) { char date[100]; assert(header != NULL); if (NULL == header->method || 0 != strcmp(header->method, "GET")) { if (EXIT_SUCCESS != send_response_error(s, "400 Bad Request")) { RETURN_FAILURE; } } if (EXIT_SUCCESS != jade_time(date, sizeof(date))) { RETURN_FAILURE; } if (0 == strcmp(header->path, "/date")) { if (EXIT_SUCCESS != send_response_date(s, date)) { RETURN_FAILURE; } } else if (0 == strcmp(header->path, "/count")) { if (EXIT_SUCCESS != send_response_count(s, date)) { RETURN_FAILURE; } } else if (0 == strcmp(header->path, "/picture1")) { if (EXIT_SUCCESS != send_response_file(s, date, "image/jpeg", "z.jpeg")) { RETURN_FAILURE; } } else if (0 == strcmp(header->path, "/picture2")) { if (EXIT_SUCCESS != send_response_file(s, date, "image/gif", "y.gif")) { RETURN_FAILURE; } } else if (0 == strcmp(header->path, "/html")) { if (EXIT_SUCCESS != send_response_file(s, date, "text/html", "x.html")) { RETURN_FAILURE; } } else { if (EXIT_SUCCESS != send_response_error(s, "404 Not Found")) { RETURN_FAILURE; } } return EXIT_SUCCESS; } /** * This function handles web requests. When a client connects to the server, * this function executes through the js_accept_all function called from main. * * @param s The socket descriptor. * * @return EXIT_SUCCESS if successful; EXIT_FAILURE if not. */ static int handle_client(int s) { http_request_header header; assert(s != 0); printf("Client connected...\n"); // Increment the number of times the web server has been accessed g_web_server_accessed_count++; // Read the header but do not parse it. If this function does not succeed, // then the connection has been dropped, so there is no reason to send a // response. if (EXIT_SUCCESS == read_header(s, &header)) { printf("REQUEST:\n\n%s\n", header.buffer); // Even if the header is not parsed successfully, send a response. parse_header(&header); send_response(s, &header); } shutdown(s, SHUT_RD); shutdown(s, SHUT_WR); close(s); printf("Client disconnected.\n"); // Always return true so the server continues to listen for connections. return EXIT_SUCCESS; }