/**
* 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;
}