Created
September 21, 2016 22:04
-
-
Save CraigRodrigues/79aefed14a6ccf7278423f021221d6c5 to your computer and use it in GitHub Desktop.
CS50 Problem Set 6: Web Server
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// server.c | |
// | |
// Computer Science 50 | |
// Problem Set 6 | |
// | |
// 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 bytes for buffers | |
#define BYTES 512 | |
// header files | |
#include <arpa/inet.h> | |
#include <dirent.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 <sys/socket.h> | |
#include <sys/stat.h> | |
#include <sys/types.h> | |
#include <unistd.h> | |
// types | |
typedef char BYTE; | |
// prototypes | |
bool connected(void); | |
void error(unsigned short code); | |
void freedir(struct dirent** namelist, int n); | |
void handler(int signal); | |
char* htmlspecialchars(const char* s); | |
char* indexes(const char* path); | |
void interpret(const char* path, const char* query); | |
void list(const char* path); | |
bool load(FILE* file, BYTE** content, size_t* length); | |
const char* lookup(const char* path); | |
bool parse(const char* line, char* path, char* query); | |
const char* reason(unsigned short code); | |
void redirect(const char* uri); | |
bool request(char** message, size_t* length); | |
void respond(int code, const char* headers, const char* body, size_t length); | |
void start(short port, const char* path); | |
void stop(void); | |
void transfer(const char* path, const char* type); | |
char* urldecode(const char* s); | |
// server's root | |
char* root = NULL; | |
// file descriptor for sockets | |
int cfd = -1, sfd = -1; | |
// flag indicating whether control-c has been heard | |
bool signaled = false; | |
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 port 8080 | |
int port = 8080; | |
// 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) | |
struct sigaction act; | |
act.sa_handler = handler; | |
act.sa_flags = 0; | |
sigemptyset(&act.sa_mask); | |
sigaction(SIGINT, &act, NULL); | |
// a message and its length | |
char* message = NULL; | |
size_t length = 0; | |
// path requested | |
char* path = NULL; | |
// accept connections one at a time | |
while (true) | |
{ | |
// free last path, if any | |
if (path != NULL) | |
{ | |
free(path); | |
path = NULL; | |
} | |
// free last message, if any | |
if (message != NULL) | |
{ | |
free(message); | |
message = NULL; | |
} | |
length = 0; | |
// close last client's socket, if any | |
if (cfd != -1) | |
{ | |
close(cfd); | |
cfd = -1; | |
} | |
// check for control-c | |
if (signaled) | |
{ | |
stop(); | |
} | |
// check whether client has connected | |
if (connected()) | |
{ | |
// check for request | |
if (request(&message, &length)) | |
{ | |
// extract message's request-line | |
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html | |
const char* haystack = message; | |
const char* needle = strstr(haystack, "\r\n"); | |
if (needle == NULL) | |
{ | |
error(500); | |
continue; | |
} | |
char line[needle - haystack + 2 + 1]; | |
strncpy(line, haystack, needle - haystack + 2); | |
line[needle - haystack + 2] = '\0'; | |
// log request-line | |
printf("%s", line); | |
// parse request-line | |
char abs_path[LimitRequestLine + 1]; | |
char query[LimitRequestLine + 1]; | |
if (parse(line, abs_path, query)) | |
{ | |
// URL-decode absolute-path | |
char* p = urldecode(abs_path); | |
if (p == NULL) | |
{ | |
error(500); | |
continue; | |
} | |
// resolve absolute-path to local path | |
path = malloc(strlen(root) + strlen(p) + 1); | |
if (path == NULL) | |
{ | |
error(500); | |
continue; | |
} | |
strcpy(path, root); | |
strcat(path, p); | |
free(p); | |
// ensure path exists | |
if (access(path, F_OK) == -1) | |
{ | |
error(404); | |
continue; | |
} | |
// if path to directory | |
struct stat sb; | |
if (stat(path, &sb) == 0 && S_ISDIR(sb.st_mode)) | |
{ | |
// redirect from absolute-path to absolute-path/ | |
if (abs_path[strlen(abs_path) - 1] != '/') | |
{ | |
char uri[strlen(abs_path) + 1 + 1]; | |
strcpy(uri, abs_path); | |
strcat(uri, "/"); | |
redirect(uri); | |
continue; | |
} | |
// use path/index.php or path/index.html, if present, instead of directory's path | |
char* index = indexes(path); | |
if (index != NULL) | |
{ | |
free(path); | |
path = index; | |
} | |
// list contents of directory | |
else | |
{ | |
list(path); | |
continue; | |
} | |
} | |
// look up MIME type for file at path | |
const char* type = lookup(path); | |
if (type == NULL) | |
{ | |
error(501); | |
continue; | |
} | |
// interpret PHP script at path | |
if (strcasecmp("text/x-php", type) == 0) | |
{ | |
interpret(path, query); | |
} | |
// transfer file at path | |
else | |
{ | |
transfer(path, type); | |
} | |
} | |
} | |
} | |
} | |
} | |
/** | |
* Checks (without blocking) whether a client has connected to server. | |
* Returns true iff so. | |
*/ | |
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; | |
} | |
/** | |
* Responds to client with specified status code. | |
*/ | |
void error(unsigned short code) | |
{ | |
// determine code's reason-phrase | |
const char* phrase = reason(code); | |
if (phrase == NULL) | |
{ | |
return; | |
} | |
// template for response's content | |
char* template = "<html><head><title>%i %s</title></head><body><h1>%i %s</h1></body></html>"; | |
// render template | |
char body[(strlen(template) - 2 - ((int) log10(code) + 1) - 2 + strlen(phrase)) * 2 + 1]; | |
int length = sprintf(body, template, code, phrase, code, phrase); | |
if (length < 0) | |
{ | |
body[0] = '\0'; | |
length = 0; | |
} | |
// respond with error | |
char* headers = "Content-Type: text/html\r\n"; | |
respond(code, headers, body, length); | |
} | |
/** | |
* Frees memory allocated by scandir. | |
*/ | |
void freedir(struct dirent** namelist, int n) | |
{ | |
if (namelist != NULL) | |
{ | |
for (int i = 0; i < n; i++) | |
{ | |
free(namelist[i]); | |
} | |
free(namelist); | |
} | |
} | |
/** | |
* Handles signals. | |
*/ | |
void handler(int signal) | |
{ | |
// control-c | |
if (signal == SIGINT) | |
{ | |
signaled = true; | |
} | |
} | |
/** | |
* Escapes string for HTML. Returns dynamically allocated memory for escaped | |
* string that must be deallocated by caller. | |
*/ | |
char* htmlspecialchars(const char* s) | |
{ | |
// ensure s is not NULL | |
if (s == NULL) | |
{ | |
return NULL; | |
} | |
// allocate enough space for an unescaped copy of s | |
char* t = malloc(strlen(s) + 1); | |
if (t == NULL) | |
{ | |
return NULL; | |
} | |
t[0] = '\0'; | |
// iterate over characters in s, escaping as needed | |
for (int i = 0, old = strlen(s), new = old; i < old; i++) | |
{ | |
// escape & | |
if (s[i] == '&') | |
{ | |
const char* entity = "&"; | |
new += strlen(entity); | |
t = realloc(t, new); | |
if (t == NULL) | |
{ | |
return NULL; | |
} | |
strcat(t, entity); | |
} | |
// escape " | |
else if (s[i] == '"') | |
{ | |
const char* entity = """; | |
new += strlen(entity); | |
t = realloc(t, new); | |
if (t == NULL) | |
{ | |
return NULL; | |
} | |
strcat(t, entity); | |
} | |
// escape ' | |
else if (s[i] == '\'') | |
{ | |
const char* entity = "'"; | |
new += strlen(entity); | |
t = realloc(t, new); | |
if (t == NULL) | |
{ | |
return NULL; | |
} | |
strcat(t, entity); | |
} | |
// escape < | |
else if (s[i] == '<') | |
{ | |
const char* entity = "<"; | |
new += strlen(entity); | |
t = realloc(t, new); | |
if (t == NULL) | |
{ | |
return NULL; | |
} | |
strcat(t, entity); | |
} | |
// escape > | |
else if (s[i] == '>') | |
{ | |
const char* entity = ">"; | |
new += strlen(entity); | |
t = realloc(t, new); | |
if (t == NULL) | |
{ | |
return NULL; | |
} | |
strcat(t, entity); | |
} | |
// don't escape | |
else | |
{ | |
strncat(t, s + i, 1); | |
} | |
} | |
// escaped string | |
return t; | |
} | |
/** | |
* Checks, in order, whether index.php or index.html exists inside of path. | |
* Returns path to first match if so, else NULL. | |
*/ | |
char* indexes(const char* path) | |
{ | |
char* php = "index.php"; | |
char* html = "index.html"; | |
int len = strlen(path); | |
char* php_path = malloc(len + 9); | |
char* html_path = malloc(len + 11); | |
//check for index.php first | |
strcpy(php_path, path); | |
strcat(php_path, php); | |
if (access(php_path, F_OK) == 0) | |
{ | |
return php_path; | |
} | |
//check for index.html second | |
strcpy(html_path, path); | |
strcat(html_path, html); | |
if (access(html_path, F_OK) == 0) | |
{ | |
return html_path; | |
} | |
//if neither is found | |
return NULL; | |
} | |
/** | |
* Interprets PHP file at path using query string. | |
*/ | |
void interpret(const char* path, const char* query) | |
{ | |
// ensure path is readable | |
if (access(path, R_OK) == -1) | |
{ | |
error(403); | |
return; | |
} | |
// 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]; | |
if (sprintf(command, format, query, path) < 0) | |
{ | |
error(500); | |
return; | |
} | |
FILE* file = popen(command, "r"); | |
if (file == NULL) | |
{ | |
error(500); | |
return; | |
} | |
// load interpreter's content | |
char* content; | |
size_t length; | |
if (load(file, &content, &length) == false) | |
{ | |
error(500); | |
return; | |
} | |
// close pipe | |
pclose(file); | |
// subtract php-cgi's headers from content's length to get body's length | |
char* haystack = content; | |
char* needle = strstr(haystack, "\r\n\r\n"); | |
if (needle == NULL) | |
{ | |
free(content); | |
error(500); | |
return; | |
} | |
// extract headers | |
char headers[needle + 2 - haystack + 1]; | |
strncpy(headers, content, needle + 2 - haystack); | |
headers[needle + 2 - haystack] = '\0'; | |
// respond with interpreter's content | |
respond(200, headers, needle + 4, length - (needle - haystack + 4)); | |
// free interpreter's content | |
free(content); | |
} | |
/** | |
* Responds to client with directory listing of path. | |
*/ | |
void list(const char* path) | |
{ | |
// ensure path is readable and executable | |
if (access(path, R_OK | X_OK) == -1) | |
{ | |
error(403); | |
return; | |
} | |
// open directory | |
DIR* dir = opendir(path); | |
if (dir == NULL) | |
{ | |
return; | |
} | |
// buffer for list items | |
char* list = malloc(1); | |
list[0] = '\0'; | |
// iterate over directory entries | |
struct dirent** namelist = NULL; | |
int n = scandir(path, &namelist, NULL, alphasort); | |
for (int i = 0; i < n; i++) | |
{ | |
// omit . from list | |
if (strcmp(namelist[i]->d_name, ".") == 0) | |
{ | |
continue; | |
} | |
// escape entry's name | |
char* name = htmlspecialchars(namelist[i]->d_name); | |
if (name == NULL) | |
{ | |
free(list); | |
freedir(namelist, n); | |
error(500); | |
return; | |
} | |
// append list item to buffer | |
char* template = "<li><a href=\"%s\">%s</a></li>"; | |
list = realloc(list, strlen(list) + strlen(template) - 2 + strlen(name) - 2 + strlen(name) + 1); | |
if (list == NULL) | |
{ | |
free(name); | |
freedir(namelist, n); | |
error(500); | |
return; | |
} | |
if (sprintf(list + strlen(list), template, name, name) < 0) | |
{ | |
free(name); | |
freedir(namelist, n); | |
free(list); | |
error(500); | |
return; | |
} | |
// free escaped name | |
free(name); | |
} | |
// free memory allocated by scandir | |
freedir(namelist, n); | |
// prepare response | |
const char* relative = path + strlen(root); | |
char* template = "<html><head><title>%s</title></head><body><h1>%s</h1><ul>%s</ul></body></html>"; | |
char body[strlen(template) - 2 + strlen(relative) - 2 + strlen(relative) - 2 + strlen(list) + 1]; | |
int length = sprintf(body, template, relative, relative, list); | |
if (length < 0) | |
{ | |
free(list); | |
closedir(dir); | |
error(500); | |
return; | |
} | |
// free buffer | |
free(list); | |
// close directory | |
closedir(dir); | |
// respond with list | |
char* headers = "Content-Type: text/html\r\n"; | |
respond(200, headers, body, length); | |
} | |
/** | |
* Loads a file into memory dynamically allocated on heap. | |
* Stores address thereof in *content and length thereof in *length. | |
*/ | |
bool load(FILE* file, BYTE** content, size_t* length) | |
{ | |
// initialize length and content | |
*length = 0; | |
*content = NULL; | |
if (file == NULL) | |
return false; | |
BYTE buffer[512]; | |
// loop over entire file and putting one chunk of 512 bytes into buffer | |
for(int c = fread(buffer, 1, 512, file); c != 0; c = fread(buffer, 1, 512, file)) | |
{ | |
// keep rellocating memory | |
*content = realloc(*content, *length + c + 1); | |
if (content == NULL) | |
{ | |
return false; | |
} | |
// copy buffer to the end of our currently filled content | |
memcpy(*content + *length, buffer, c); | |
*length = *length + c; | |
} | |
return true; | |
} | |
/** | |
* Returns MIME type for supported extensions, else NULL. | |
*/ | |
const char* lookup(const char* path) | |
{ | |
char *css = ".css"; | |
char *html = ".html"; | |
char *gif = ".gif"; | |
char *ico = ".ico"; | |
char *jpg = ".jpg"; | |
char *js = ".js"; | |
char *php = ".php"; | |
char *png = ".png"; | |
char *extension; | |
extension = strrchr(path, '.'); | |
if (strcasecmp(extension, css) == 0) | |
return "text/css"; | |
else if (strcasecmp(extension, html) == 0) | |
return "text/html"; | |
else if (strcasecmp(extension, gif) == 0) | |
return "image/gif"; | |
else if (strcasecmp(extension, ico) == 0) | |
return "image/x-icon"; | |
else if (strcasecmp(extension, jpg) == 0) | |
return "image/jpeg"; | |
else if (strcasecmp(extension, js) == 0) | |
return "text/javascript"; | |
else if (strcasecmp(extension, php) == 0) | |
return "text/x-php"; | |
else if (strcasecmp(extension, png) == 0) | |
return "image/png"; | |
else | |
return NULL; | |
} | |
/** | |
* Parses a request-line, storing its absolute-path at abs_path | |
* and its query string at query, both of which are assumed | |
* to be at least of length LimitRequestLine + 1. | |
*/ | |
bool parse(const char* line, char* abs_path, char* query) | |
{ | |
//1. Check the first 4 chars of line | |
if (strncmp(line, "GET ", 4) != 0) | |
{ | |
error(405); | |
return false; | |
} | |
//2. Check that line[4] is / | |
if (line[4] != '/') | |
{ | |
error(501); | |
return false; | |
} | |
//3. Get haystack of request-target (strchr) | |
char* haystack = strchr(line, '/'); | |
//4. Get "needle" of HTTP-Version | |
char* http_ver = strrchr(line, 'H'); | |
//5. Check that the version is HTTP/1.1 exactly without extra spaces etc | |
if (strncmp(http_ver, "HTTP/1.1", 8) != 0) | |
{ | |
error(505); | |
return false; | |
} | |
//7. Init target char array | |
int len = strlen(haystack) - strlen(http_ver); | |
char req_targ[len]; | |
//8. Copy the proper number of characters into the target from the haystack | |
strncpy(req_targ, haystack, len-1); | |
//9. Null terminate the target | |
req_targ[len-1] = '\0'; | |
//10. Check that the target doesn’t contain a “ | |
if (strchr(req_targ, '"') != 0) | |
{ | |
error(400); | |
return false; | |
} | |
//11. Split the target not by the ? to get the query | |
strtok(req_targ, "?"); | |
strcpy(abs_path, req_targ); | |
//12. If there is no query present (NULL) then make the query a blank string | |
char* query_copy = strtok(NULL, " "); | |
if (query_copy == NULL) | |
{ | |
query_copy = ""; | |
} | |
strcpy(query, query_copy); | |
return true; | |
} | |
/** | |
* Returns status code's reason phrase. | |
* | |
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6 | |
* https://tools.ietf.org/html/rfc2324 | |
*/ | |
const char* reason(unsigned short code) | |
{ | |
switch (code) | |
{ | |
case 200: return "OK"; | |
case 301: return "Moved Permanently"; | |
case 400: return "Bad Request"; | |
case 403: return "Forbidden"; | |
case 404: return "Not Found"; | |
case 405: return "Method Not Allowed"; | |
case 414: return "Request-URI Too Long"; | |
case 418: return "I'm a teapot"; | |
case 500: return "Internal Server Error"; | |
case 501: return "Not Implemented"; | |
case 505: return "HTTP Version Not Supported"; | |
default: return NULL; | |
} | |
} | |
/** | |
* Redirects client to uri. | |
*/ | |
void redirect(const char* uri) | |
{ | |
char* template = "Location: %s\r\n"; | |
char headers[strlen(template) - 2 + strlen(uri) + 1]; | |
if (sprintf(headers, template, uri) < 0) | |
{ | |
error(500); | |
return; | |
} | |
respond(301, headers, NULL, 0); | |
} | |
/** | |
* Reads (without blocking) an HTTP request's headers into memory dynamically allocated on heap. | |
* Stores address thereof in *message and length thereof in *length. | |
*/ | |
bool request(char** message, size_t* length) | |
{ | |
// ensure socket is open | |
if (cfd == -1) | |
{ | |
return false; | |
} | |
// initialize message and its length | |
*message = NULL; | |
*length = 0; | |
// read message | |
while (*length < LimitRequestLine + LimitRequestFields * LimitRequestFieldSize + 4) | |
{ | |
// read from socket | |
BYTE buffer[BYTES]; | |
ssize_t bytes = read(cfd, buffer, BYTES); | |
if (bytes < 0) | |
{ | |
if (*message != NULL) | |
{ | |
free(*message); | |
*message = NULL; | |
} | |
*length = 0; | |
break; | |
} | |
// append bytes to message | |
*message = realloc(*message, *length + bytes + 1); | |
if (*message == NULL) | |
{ | |
*length = 0; | |
break; | |
} | |
memcpy(*message + *length, buffer, bytes); | |
*length += bytes; | |
// null-terminate message thus far | |
*(*message + *length) = '\0'; | |
// search for CRLF CRLF | |
int offset = (*length - bytes < 3) ? *length - bytes : 3; | |
char* haystack = *message + *length - bytes - offset; | |
char* needle = strstr(haystack, "\r\n\r\n"); | |
if (needle != NULL) | |
{ | |
// trim to one CRLF and null-terminate | |
*length = needle - *message + 2; | |
*message = realloc(*message, *length + 1); | |
if (*message == NULL) | |
{ | |
break; | |
} | |
*(*message + *length) = '\0'; | |
// ensure request-line is no longer than LimitRequestLine | |
haystack = *message; | |
needle = strstr(haystack, "\r\n"); | |
if (needle == NULL || (needle - haystack + 2) > LimitRequestLine) | |
{ | |
break; | |
} | |
// count fields in message | |
int fields = 0; | |
haystack = needle + 2; | |
while (*haystack != '\0') | |
{ | |
// look for CRLF | |
needle = strstr(haystack, "\r\n"); | |
if (needle == NULL) | |
{ | |
break; | |
} | |
// ensure field is no longer than LimitRequestFieldSize | |
if (needle - haystack + 2 > LimitRequestFieldSize) | |
{ | |
break; | |
} | |
// look beyond CRLF | |
haystack = needle + 2; | |
} | |
// if we didn't get to end of message, we must have erred | |
if (*haystack != '\0') | |
{ | |
break; | |
} | |
// ensure message has no more than LimitRequestFields | |
if (fields > LimitRequestFields) | |
{ | |
break; | |
} | |
// valid | |
return true; | |
} | |
} | |
// invalid | |
if (*message != NULL) | |
{ | |
free(*message); | |
} | |
*message = NULL; | |
*length = 0; | |
return false; | |
} | |
/** | |
* Responds to a client with status code, headers, and body of specified length. | |
*/ | |
void respond(int code, const char* headers, const char* body, size_t length) | |
{ | |
// determine Status-Line's phrase | |
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1 | |
const char* phrase = reason(code); | |
if (phrase == NULL) | |
{ | |
return; | |
} | |
// respond with Status-Line | |
if (dprintf(cfd, "HTTP/1.1 %i %s\r\n", code, phrase) < 0) | |
{ | |
return; | |
} | |
// respond with headers | |
if (dprintf(cfd, "%s", headers) < 0) | |
{ | |
return; | |
} | |
// respond with CRLF | |
if (dprintf(cfd, "\r\n") < 0) | |
{ | |
return; | |
} | |
// respond with body | |
if (write(cfd, body, length) == -1) | |
{ | |
return; | |
} | |
// log response line | |
if (code == 200) | |
{ | |
// green | |
printf("\033[32m"); | |
} | |
else | |
{ | |
// red | |
printf("\033[33m"); | |
} | |
printf("HTTP/1.1 %i %s", code, phrase); | |
printf("\033[39m\n"); | |
} | |
/** | |
* Starts server on specified port rooted at path. | |
*/ | |
void start(short port, const char* path) | |
{ | |
// path to server's root | |
root = realpath(path, NULL); | |
if (root == NULL) | |
{ | |
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) | |
{ | |
printf("\033[33m"); | |
printf("Port %i already in use", port); | |
printf("\033[39m\n"); | |
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; | |
// announce stop | |
printf("\033[33m"); | |
printf("Stopping server\n"); | |
printf("\033[39m"); | |
// free root, which was allocated by realpath | |
if (root != NULL) | |
{ | |
free(root); | |
} | |
// close server socket | |
if (sfd != -1) | |
{ | |
close(sfd); | |
} | |
// stop server | |
exit(errsv); | |
} | |
/** | |
* Transfers file at path with specified type to client. | |
*/ | |
void transfer(const char* path, const char* type) | |
{ | |
// ensure path is readable | |
if (access(path, R_OK) == -1) | |
{ | |
error(403); | |
return; | |
} | |
// open file | |
FILE* file = fopen(path, "r"); | |
if (file == NULL) | |
{ | |
error(500); | |
return; | |
} | |
// load file's content | |
BYTE* content; | |
size_t length; | |
if (load(file, &content, &length) == false) | |
{ | |
error(500); | |
return; | |
} | |
// close file | |
fclose(file); | |
// prepare response | |
char* template = "Content-Type: %s\r\n"; | |
char headers[strlen(template) - 2 + strlen(type) + 1]; | |
if (sprintf(headers, template, type) < 0) | |
{ | |
error(500); | |
return; | |
} | |
// respond with file's content | |
respond(200, headers, content, length); | |
// free file's content | |
free(content); | |
} | |
/** | |
* URL-decodes string, returning dynamically allocated memory for decoded string | |
* that must be deallocated by caller. | |
*/ | |
char* urldecode(const char* s) | |
{ | |
// check whether s is NULL | |
if (s == NULL) | |
{ | |
return NULL; | |
} | |
// allocate enough (zeroed) memory for an undecoded copy of s | |
char* t = calloc(strlen(s) + 1, 1); | |
if (t == NULL) | |
{ | |
return NULL; | |
} | |
// iterate over characters in s, decoding percent-encoded octets, per | |
// https://www.ietf.org/rfc/rfc3986.txt | |
for (int i = 0, j = 0, n = strlen(s); i < n; i++, j++) | |
{ | |
if (s[i] == '%' && i < n - 2) | |
{ | |
char octet[3]; | |
octet[0] = s[i + 1]; | |
octet[1] = s[i + 2]; | |
octet[2] = '\0'; | |
t[j] = (char) strtol(octet, NULL, 16); | |
i += 2; | |
} | |
else if (s[i] == '+') | |
{ | |
t[j] = ' '; | |
} | |
else | |
{ | |
t[j] = s[i]; | |
} | |
} | |
// escaped string | |
return t; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment