Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- //
- // server.c
- //
- // David J. Malan
- // malan@harvard.edu
- //
- // feature test macro requirements
- #define _GNU_SOURCE
- #define _XOPEN_SOURCE 700
- #define _XOPEN_SOURCE_EXTENDED
- // limits on an HTTP request's size, based on Apache's
- // http://httpd.apache.org/docs/2.2/mod/core.html
- #define LimitRequestFields 50
- #define LimitRequestFieldSize 4094
- #define LimitRequestLine 8190
- // number of octets for buffered reads
- #define OCTETS 512
- // header files
- #include <arpa/inet.h>
- #include <errno.h>
- #include <limits.h>
- #include <math.h>
- #include <signal.h>
- #include <stdbool.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <strings.h>
- #include <unistd.h>
- // types
- typedef char octet;
- // prototypes
- bool connected(void);
- bool error(unsigned short code);
- void handler(int signal);
- ssize_t load(void);
- const char* lookup(const char* extension);
- ssize_t parse(void);
- void reset(void);
- void start(short port, const char* path);
- void stop(void);
- // server's root
- char* root = NULL;
- // file descriptor for sockets
- int cfd = -1, sfd = -1;
- // buffer for request
- octet* request = NULL;
- // FILE pointer for files
- FILE* file = NULL;
- // buffer for response-body
- octet* body = NULL;
- int main(int argc, char* argv[])
- {
- // a global variable defined in errno.h that's "set by system
- // calls and some library functions [to a nonzero value]
- // in the event of an error to indicate what went wrong"
- errno = 0;
- // default to a random port
- int port = 0;
- // usage
- const char* usage = "Usage: server [-p port] /path/to/root";
- // parse command-line arguments
- int opt;
- while ((opt = getopt(argc, argv, "hp:")) != -1)
- {
- switch (opt)
- {
- // -h
- case 'h':
- printf("%s\n", usage);
- return 0;
- // -p port
- case 'p':
- port = atoi(optarg);
- break;
- }
- }
- // ensure port is a non-negative short and path to server's root is specified
- if (port < 0 || port > SHRT_MAX || argv[optind] == NULL || strlen(argv[optind]) == 0)
- {
- // announce usage
- printf("%s\n", usage);
- // return 2 just like bash's builtins
- return 2;
- }
- // start server
- start(port, argv[optind]);
- // listen for SIGINT (aka control-c)
- signal(SIGINT, handler);
- // accept connections one at a time
- while (true)
- {
- // reset server's state
- reset();
- // wait until client is connected
- if (connected())
- {
- // parse client's HTTP request
- ssize_t octets = parse();
- if (octets == -1)
- {
- continue;
- }
- // extract request's request-line
- // http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html
- const char* haystack = request;
- char* needle = strstr(haystack, "\r\n");
- if (needle == NULL)
- {
- error(400);
- continue;
- }
- else if (needle - haystack + 2 > LimitRequestLine)
- {
- error(414);
- continue;
- }
- char line[needle - haystack + 2 + 1];
- strncpy(line, haystack, needle - haystack + 2);
- line[needle - haystack + 2] = '\0';
- // log request-line
- printf("%s", line);
- // TODO: validate request-line
- // if method is not GET, respond to the browser with 405 Method Not Allowed;
- const char escape[2] = " ";
- char *token;
- token = strtok(line, escape);
- if (strcmp(token, "GET") != 0)
- {
- error(405);
- continue;
- }
- // if request-target does not begin with /, respond to the browser with 501 Not Implemented;
- token = strtok(NULL, escape);
- if (token[0] != '/')
- {
- error(501);
- continue;
- }
- // if request-target contains a ", respond to the browser with 400 Bad Request;
- if (strchr(token, '\"') != NULL)
- {
- error(400);
- continue;
- }
- // if absolute-path does not contain a . (and thus a file extension), respond to the browser with 501 Not Implemented.
- if (strchr(token, '.') == NULL)
- {
- error(501);
- continue;
- }
- // if HTTP-version is not HTTP/1.1, respond to the browser with 505 HTTP Version Not Supported; or
- token = strtok(NULL, escape);
- if (strcmp(token, "HTTP/1.1") != 0)
- {
- error(505);
- continue;
- }
- // TODO: extract query from request-target
- token = strtok(line, escape);
- token = strtok(NULL, escape);
- //char *tempstr;
- //ret = strchr(token, '?');
- int qlength = strlen(strchr(token, '?'));
- char* tempquery = strchr(token, '?');
- char query[qlength+1] ;//= NULL/*"TODO"*/;
- if (tempquery == NULL)
- {
- query[0] = '\0';
- }
- else if (tempquery[1] == '\0')
- {
- query[0] = '\0';
- }
- else
- {
- int i;
- for(i = 0; i < strlen(tempquery); i++)
- {
- query[i] = tempquery[1 + i];
- }
- query[i] = '\0';
- }
- // TODO: concatenate root and absolute-path
- int absolute_path_length = strlen(token) - strlen(strchr(token, '?'));
- char absolute_path[absolute_path_length+1];
- strncpy(absolute_path, token, absolute_path_length);
- char path[strlen(root) + absolute_path_length + 1];
- strcpy(path, root);
- strcat(path, absolute_path);
- // TODO: ensure path exists
- if (access(path, F_OK) == -1)
- {
- error(404);
- continue;
- }
- // TODO: ensure path is readable
- if (access(path, R_OK) == -1)
- {
- error(403);
- continue;
- }
- // TODO: extract path's extension
- char extension[strlen(strrchr(absolute_path, '.'))+ 1];
- strncpy(extension, strrchr(absolute_path, '.'), strlen(strrchr(absolute_path, '.')));
- // dynamic content
- if (strcasecmp("php", extension) == 0)
- {
- // open pipe to PHP interpreter
- char* format = "QUERY_STRING=\"%s\" REDIRECT_STATUS=200 SCRIPT_FILENAME=\"%s\" php-cgi";
- char command[strlen(format) + (strlen(path) - 2) + (strlen(query) - 2) + 1];
- sprintf(command, format, query, path);
- file = popen(command, "r");
- if (file == NULL)
- {
- error(500);
- continue;
- }
- // load file
- ssize_t size = load();
- if (size == -1)
- {
- error(500);
- continue;
- }
- // subtract php-cgi's headers from body's size to get content's length
- haystack = body;
- needle = memmem(haystack, size, "\r\n\r\n", 4);
- if (needle == NULL)
- {
- error(500);
- continue;
- }
- size_t length = size - (needle - haystack + 4);
- // respond to client
- if (dprintf(cfd, "HTTP/1.1 200 OK\r\n") < 0)
- {
- continue;
- }
- if (dprintf(cfd, "Connection: close\r\n") < 0)
- {
- continue;
- }
- if (dprintf(cfd, "Content-Length: %i\r\n", length) < 0)
- {
- continue;
- }
- if (write(cfd, body, size) == -1)
- {
- continue;
- }
- }
- // static content
- else
- {
- // look up file's MIME type
- const char* type = lookup(extension);
- if (type == NULL)
- {
- error(501);
- continue;
- }
- // open file
- file = fopen(path, "r");
- if (file == NULL)
- {
- error(500);
- continue;
- }
- // load file
- ssize_t length = load();
- if (length == -1)
- {
- error(500);
- continue;
- }
- // TODO: respond to client
- // respond to client
- if (dprintf(cfd, "HTTP/1.1 200 OK\r\n") < 0)
- {
- continue;
- }
- if (dprintf(cfd, "Connection: close\r\n") < 0)
- {
- continue;
- }
- if (dprintf(cfd, "Content-Length: %i\r\n", length) < 0)
- {
- continue;
- }
- if (dprintf(cfd, "Content-Type: %s\r\n", type) < 0)
- {
- continue;
- }
- if (write(cfd, body, length) == -1)
- {
- continue;
- }
- }
- // announce OK
- printf("\033[32m");
- printf("HTTP/1.1 200 OK");
- printf("\033[39m\n");
- }
- }
- }
- /**
- * Accepts a connection from a client, blocking (i.e., waiting) until one is heard.
- * Upon success, returns true; upon failure, returns false.
- */
- bool connected(void)
- {
- struct sockaddr_in cli_addr;
- memset(&cli_addr, 0, sizeof(cli_addr));
- socklen_t cli_len = sizeof(cli_addr);
- cfd = accept(sfd, (struct sockaddr*) &cli_addr, &cli_len);
- if (cfd == -1)
- {
- return false;
- }
- return true;
- }
- /**
- * Handles client errors (4xx) and server errors (5xx).
- */
- bool error(unsigned short code)
- {
- // ensure client's socket is open
- if (cfd == -1)
- {
- return false;
- }
- // ensure code is within range
- if (code < 400 || code > 599)
- {
- return false;
- }
- // determine Status-Line's phrase
- // http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1
- const char* phrase = NULL;
- switch (code)
- {
- case 400: phrase = "Bad Request"; break;
- case 403: phrase = "Forbidden"; break;
- case 404: phrase = "Not Found"; break;
- case 405: phrase = "Method Not Allowed"; break;
- case 413: phrase = "Request Entity Too Large"; break;
- case 414: phrase = "Request-URI Too Long"; break;
- case 418: phrase = "I'm a teapot"; break;
- case 500: phrase = "Internal Server Error"; break;
- case 501: phrase = "Not Implemented"; break;
- case 505: phrase = "HTTP Version Not Supported"; break;
- }
- if (phrase == NULL)
- {
- return false;
- }
- // template
- char* template = "<html><head><title>%i %s</title></head><body><h1>%i %s</h1></body></html>";
- char content[strlen(template) + 2 * ((int) log10(code) + 1 - 2) + 2 * (strlen(phrase) - 2) + 1];
- int length = sprintf(content, template, code, phrase, code, phrase);
- // respond with Status-Line
- if (dprintf(cfd, "HTTP/1.1 %i %s\r\n", code, phrase) < 0)
- {
- return false;
- }
- // respond with Connection header
- if (dprintf(cfd, "Connection: close\r\n") < 0)
- {
- return false;
- }
- // respond with Content-Length header
- if (dprintf(cfd, "Content-Length: %i\r\n", length) < 0)
- {
- return false;
- }
- // respond with Content-Type header
- if (dprintf(cfd, "Content-Type: text/html\r\n") < 0)
- {
- return false;
- }
- // respond with CRLF
- if (dprintf(cfd, "\r\n") < 0)
- {
- return false;
- }
- // respond with message-body
- if (write(cfd, content, length) == -1)
- {
- return false;
- }
- // announce Response-Line
- printf("\033[31m");
- printf("HTTP/1.1 %i %s", code, phrase);
- printf("\033[39m\n");
- return true;
- }
- /**
- * Loads file into message-body.
- */
- ssize_t load(void)
- {
- // ensure file is open
- if (file == NULL)
- {
- return -1;
- }
- // ensure body isn't already loaded
- if (body != NULL)
- {
- return -1;
- }
- // buffer for octets
- octet buffer[OCTETS];
- // read file
- ssize_t size = 0;
- while (true)
- {
- // try to read a buffer's worth of octets
- ssize_t octets = fread(buffer, sizeof(octet), OCTETS, file);
- // check for error
- if (ferror(file) != 0)
- {
- if (body != NULL)
- {
- free(body);
- body = NULL;
- }
- return -1;
- }
- // if octets were read, append to body
- if (octets > 0)
- {
- body = realloc(body, size + octets);
- if (body == NULL)
- {
- return -1;
- }
- memcpy(body + size, buffer, octets);
- size += octets;
- }
- // check for EOF
- if (feof(file) != 0)
- {
- break;
- }
- }
- return size;
- }
- /**
- * Handles signals.
- */
- void handler(int signal)
- {
- // control-c
- if (signal == SIGINT)
- {
- // ensure this isn't considered an error
- // (as might otherwise happen after a recent 404)
- errno = 0;
- // announce stop
- printf("\033[33m");
- printf("Stopping server\n");
- printf("\033[39m");
- // stop server
- stop();
- }
- }
- /**
- * Returns MIME type for supported extensions, else NULL.
- */
- const char* lookup(const char* extension)
- {
- // TODO
- if (extension != NULL)
- {
- // return text/css if extension is css (or any capitalization thereof),
- if (strcasecmp(extension, "css") == 0)
- {
- return "text/css";
- }
- // return text/html if extension is html (or any capitalization thereof),
- else if (strcasecmp(extension, "html") == 0)
- {
- return "text/html";
- }
- // return image/gif if extension is gif (or any capitalization thereof),
- else if (strcasecmp(extension, "gif") == 0)
- {
- return "image/gif";
- }
- // return image/x-icon if extension is ico (or any capitalization thereof),
- else if (strcasecmp(extension, "ico") == 0)
- {
- return "image/x-icon";
- }
- // return image/jpeg (not image/jpg) if extension is jpg (or any capitalization thereof),
- else if (strcasecmp(extension, "jpg") == 0)
- {
- return "image/jpeg";
- }
- // return text/javascript if extension is js (or any capitalization thereof),
- else if (strcasecmp(extension, "js") == 0)
- {
- return "text/javascript";
- }
- // return image/png if extension is png (or any capitalization thereof), or
- else if (strcasecmp(extension, "png") == 0)
- {
- return "image/png";
- }
- }
- return NULL;
- }
- /**
- * Parses an HTTP request.
- */
- ssize_t parse(void)
- {
- // ensure client's socket is open
- if (cfd == -1)
- {
- return -1;
- }
- // ensure request isn't already parsed
- if (request != NULL)
- {
- return -1;
- }
- // buffer for octets
- octet buffer[OCTETS];
- // parse request
- ssize_t length = 0;
- while (true)
- {
- // read from socket
- ssize_t octets = read(cfd, buffer, sizeof(octet) * OCTETS);
- if (octets == -1)
- {
- error(500);
- return -1;
- }
- // if octets have been read, remember new length
- if (octets > 0)
- {
- request = realloc(request, length + octets);
- if (request == NULL)
- {
- return -1;
- }
- memcpy(request + length, buffer, octets);
- length += octets;
- }
- // else if nothing's been read, socket's been closed
- else
- {
- return -1;
- }
- // search for CRLF CRLF
- int offset = (length - octets < 3) ? length - octets : 3;
- char* haystack = request + length - octets - offset;
- char* needle = memmem(haystack, request + length - haystack, "\r\n\r\n", 4);
- if (needle != NULL)
- {
- // trim to one CRLF and null-terminate
- length = needle - request + 2 + 1;
- request = realloc(request, length);
- if (request == NULL)
- {
- return -1;
- }
- request[length - 1] = '\0';
- break;
- }
- // if buffer's full and we still haven't found CRLF CRLF,
- // then request is too large
- if (length - 1 >= LimitRequestLine + LimitRequestFields * LimitRequestFieldSize)
- {
- error(413);
- return -1;
- }
- }
- return length;
- }
- /**
- * Resets server's state, deallocating any resources.
- */
- void reset(void)
- {
- // free response's body
- if (body != NULL)
- {
- free(body);
- body = NULL;
- }
- // close file
- if (file != NULL)
- {
- fclose(file);
- file = NULL;
- }
- // free request
- if (request != NULL)
- {
- free(request);
- request = NULL;
- }
- // close client's socket
- if (cfd != -1)
- {
- close(cfd);
- cfd = -1;
- }
- }
- /**
- * Starts server.
- */
- void start(short port, const char* path)
- {
- // path to server's root
- root = realpath(path, NULL);
- if (root == NULL)
- {
- stop();
- }
- // ensure root exists
- if (access(root, F_OK) == -1)
- {
- stop();
- }
- // ensure root is executable
- if (access(root, X_OK) == -1)
- {
- stop();
- }
- // announce root
- printf("\033[33m");
- printf("Using %s for server's root", root);
- printf("\033[39m\n");
- // create a socket
- sfd = socket(AF_INET, SOCK_STREAM, 0);
- if (sfd == -1)
- {
- stop();
- }
- // allow reuse of address (to avoid "Address already in use")
- int optval = 1;
- setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
- // assign name to socket
- struct sockaddr_in serv_addr;
- memset(&serv_addr, 0, sizeof(serv_addr));
- serv_addr.sin_family = AF_INET;
- serv_addr.sin_port = htons(port);
- serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
- if (bind(sfd, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1)
- {
- stop();
- }
- // listen for connections
- if (listen(sfd, SOMAXCONN) == -1)
- {
- stop();
- }
- // announce port in use
- struct sockaddr_in addr;
- socklen_t addrlen = sizeof(addr);
- if (getsockname(sfd, (struct sockaddr*) &addr, &addrlen) == -1)
- {
- stop();
- }
- printf("\033[33m");
- printf("Listening on port %i", ntohs(addr.sin_port));
- printf("\033[39m\n");
- }
- /**
- * Stop server, deallocating any resources.
- */
- void stop(void)
- {
- // preserve errno across this function's library calls
- int errsv = errno;
- // reset server's state
- reset();
- // free root, which was allocated by realpath
- if (root != NULL)
- {
- free(root);
- }
- // close server socket
- if (sfd != -1)
- {
- close(sfd);
- }
- // terminate process
- if (errsv == 0)
- {
- // success
- exit(0);
- }
- else
- {
- // announce error
- printf("\033[33m");
- printf("%s", strerror(errsv));
- printf("\033[39m\n");
- // failure
- exit(1);
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement