Last active
September 30, 2021 11:30
-
-
Save ThePotatoChronicler/f570c7dcda2d3664d7710d077889d83a to your computer and use it in GitHub Desktop.
A simple Linux C webserver
This file contains hidden or 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
/** | |
* Program: cnet | |
* Author: Potato Chronicler <https://github.com/ThePotatoChronicler> | |
* | |
* Configuration: | |
* All configuration is done through the cnet.conf file, | |
* using directives. Each directive is composed of it's name | |
* and it's argument. | |
* | |
* Directives: | |
* file - Allows serving of the file, relative to cnetroot directory | |
* port - Changes the serving port | |
* indexfile - Changes the index file (file served when URI is /) | |
* | |
* Features: | |
* Features are optional additions to cnet, added to the | |
* program through defines. | |
* | |
* php - CNET_FEATURE_PHP | |
* Support for PHP files. Any served file with .php extension | |
* will be executed by the embedded PHP SAPI. | |
*/ | |
#include <stdlib.h> | |
#include <stdint.h> | |
#include <limits.h> | |
#include <signal.h> | |
#include <stdbool.h> | |
#include <errno.h> | |
#include <asm-generic/errno-base.h> | |
#include <asm-generic/errno.h> | |
#include <string.h> | |
#include <fcntl.h> | |
#include <stdio.h> | |
#include <sys/socket.h> | |
#include <asm-generic/socket.h> | |
#include <sys/stat.h> | |
#include <sys/poll.h> | |
#include <netinet/in.h> | |
#include <netinet/tcp.h> | |
#include <unistd.h> | |
#ifdef CNET_FEATURE_PHP | |
#include <php/sapi/embed/php_embed.h> | |
#endif | |
#define CNET_INF "\x1b[37mInfo:\x1b[39m " | |
#define CNET_ERR "\x1b[91mError:\x1b[39m " | |
#define CNET_WRN "\x1b[33mWarning:\x1b[39m " | |
#define CNET_FAT "\x1b[31mFatal:\x1b[39m " | |
int cnet_accepted_signal; | |
struct { | |
int dir; | |
int off; | |
} *cnet_config = NULL; | |
int cnet_config_len = 0; | |
void *cnet_config_buf = NULL; | |
int cnet_config_buf_len = 0; | |
enum cnet_conf_directive { | |
CCD_INVALID, | |
CCD_FILE, | |
CCD_PORT, | |
CCD_INDEXFILE | |
}; | |
__attribute__((unused)) static char *cnet_directive_to_str(int directive) { | |
static char *dirs[] = { | |
"CCD_INVALID", | |
"CCD_FILE", | |
"CCD_PORT", | |
"CCD_INDEXFILE" | |
}; | |
if ((directive >= 0) && (directive < (int)(sizeof(dirs) / sizeof(*dirs)))) { | |
return dirs[directive]; | |
} else { | |
return NULL; | |
} | |
} | |
static void cnet_sigint_handler(int sig) { | |
cnet_accepted_signal = sig; | |
} | |
/** | |
* A strtol wrapper, which returns an error on failure | |
*/ | |
static char sstrtol (char *restrict str, int base, long *restrict retaddr) { | |
char *endptr; | |
long l = strtol(str, &endptr, base); | |
if (str + strlen(str) != endptr) | |
return -1; | |
*retaddr = l; | |
return 0; | |
} | |
int main() { | |
int rootdirfd; | |
{ // Check if cnetroot exists and is a directory | |
const char* root_filename = "cnetroot"; | |
struct stat rootinfo; | |
if (stat(root_filename, &rootinfo)) { | |
switch (errno) { | |
case ENOENT: | |
fprintf(stderr, CNET_ERR"The required root directory (%s) does not exist\n", root_filename); | |
exit(1); | |
default: | |
fprintf(stderr, CNET_FAT"stat(%s) has failed (errno %i): \"%s\"\n", root_filename, errno, strerror(errno)); | |
exit(1); | |
} | |
} | |
if (!(S_ISDIR(rootinfo.st_mode))) { | |
fprintf(stderr, CNET_ERR"The root directory (%s) is not a directory\n", root_filename); | |
exit(1); | |
} | |
if ((rootdirfd = open(root_filename, O_RDONLY)) == -1) { | |
switch (errno) { | |
default: | |
fprintf(stderr, CNET_FAT"open(%s) has failed (errno %i): \"%s\"\n", root_filename, errno, strerror(errno)); | |
exit(1); | |
} | |
} | |
} | |
{ // Get config and parse it | |
const char* conf_filename = "cnet.conf"; | |
struct stat confinfo; | |
if (stat(conf_filename, &confinfo)) { | |
switch (errno) { | |
case ENOENT: | |
fprintf(stderr, CNET_ERR"The required configuration file (%s) does not exist\n", conf_filename); | |
exit(1); | |
default: | |
fprintf(stderr, CNET_FAT"stat(%s) has failed (errno %i): \"%s\"\n", conf_filename, errno, strerror(errno)); | |
exit(1); | |
} | |
} | |
char *conf_databuf = malloc(confinfo.st_size); | |
{ // Open config and read it in | |
int conf_fd; | |
if ((conf_fd = open(conf_filename, O_RDONLY)) == -1) { | |
switch (errno) { | |
default: | |
fprintf(stderr, CNET_FAT"open(%s) has failed (errno %i): \"%s\"\n", conf_filename, errno, strerror(errno)); | |
exit(1); | |
} | |
} | |
int bytesread; | |
if ((bytesread = read(conf_fd, conf_databuf, confinfo.st_size)) == -1) { | |
switch (errno) { | |
case EISDIR: | |
fprintf(stderr, CNET_ERR"The config file (%s) is a directory, not a file\n", conf_filename); | |
exit(1); | |
default: | |
fprintf(stderr, CNET_FAT"read(%s) has failed (errno %i): \"%s\"\n", conf_filename, errno, strerror(errno)); | |
exit(1); | |
} | |
} | |
if (bytesread != confinfo.st_size) { | |
fprintf(stderr, CNET_ERR"read(%s) didn't read the entire file somehow T_T", conf_filename); | |
exit(1); | |
} | |
if (close(conf_fd)) { | |
switch (errno) { | |
default: | |
fprintf(stderr, CNET_FAT"close(%s) has failed (errno %i): \"%s\"\n", conf_filename, errno, strerror(errno)); | |
exit(1); | |
} | |
} | |
} | |
char *conf_at = conf_databuf; | |
const char *conf_end = conf_databuf + confinfo.st_size; | |
char *buf = malloc(1 << 16); | |
int bufat = 0; | |
while (conf_at < conf_end) { // Goes line by line and gets directives and args | |
char *dirstr; // directive string | |
int dirstr_len; | |
char *dirargs; // directive arguments | |
int dirargs_len; // Length is required, since NULL is a possible value | |
{ // Get dirstr and dirargs | |
while ((conf_at < conf_end) && (*conf_at == ' ')) conf_at++; | |
if (conf_at >= conf_end) break; | |
if (*conf_at == '\n') { | |
conf_at++; | |
continue; | |
} | |
if (*conf_at == '#') { | |
while ((conf_at < conf_end) && (*conf_at != '\n')) conf_at++; | |
} | |
if (conf_at >= conf_end) break; | |
bufat = 0; | |
dirstr = buf; | |
char c = *conf_at; | |
while // Getting dirstr | |
( | |
(conf_at < conf_end) && | |
( | |
((c >= 'a') && (c <= 'z')) || | |
((c >= 'A') && (c <= 'Z')) || | |
((c >= '0') && (c <= '9')) || | |
(c == '_') || | |
(c == '-') | |
) | |
) { | |
buf[bufat] = c; | |
bufat++; | |
conf_at++; | |
c = *conf_at; | |
} | |
buf[bufat] = 0; | |
dirstr_len = bufat; | |
bufat++; | |
// Guarantees that dirargs is atleast an empty string | |
dirargs = buf + bufat; | |
buf[bufat] = 0; | |
dirargs_len = 0; | |
if (conf_at >= conf_end) goto EXIT; | |
while ((conf_at < conf_end) && (*conf_at == ' ')) conf_at++; | |
while ((conf_at < conf_end) && (*conf_at != '\n') && (*conf_at != '#')) { | |
buf[bufat] = *conf_at; | |
bufat++; | |
conf_at++; | |
} | |
while (buf[bufat - 1] == ' ') bufat--; | |
buf[bufat] = 0; | |
dirargs_len = bufat - (dirstr_len + 1); | |
while ((conf_at < conf_end) && (*conf_at != '\n')) conf_at++; | |
conf_at++; | |
} | |
EXIT: | |
if (dirstr_len > 0) { | |
//printf(CNET_INF"Directive (%i): '%s', Args (%i): '%s'\n", dirstr_len, dirstr, dirargs_len, dirargs); | |
int directive; | |
if (!((dirstr_len >= 4) && (dirstr_len <= 9))) { // If the length of dirstr is not in range of any directive, mark it as invalid | |
directive = CCD_INVALID; | |
} else { | |
if (0 == strcmp(dirstr, "file")) directive = CCD_FILE; | |
else if (0 == strcmp(dirstr, "port")) directive = CCD_PORT; | |
else if (0 == strcmp(dirstr, "indexfile")) directive = CCD_INDEXFILE; | |
else directive = CCD_INVALID; | |
} | |
switch (directive) { | |
case CCD_INVALID: | |
fprintf(stderr, CNET_WRN"Invalid directive '%s', ignoring\n", dirstr); | |
break; | |
case CCD_FILE: { | |
if (dirargs_len == 0) { | |
fprintf(stderr, CNET_WRN"%s directive with no argument, ignoring\n", dirstr); | |
} else { | |
cnet_config = realloc(cnet_config, (cnet_config_len + 1) * sizeof(*cnet_config)); | |
cnet_config[cnet_config_len].dir = CCD_FILE; | |
int tmp = cnet_config_buf_len; | |
cnet_config_buf_len += dirargs_len + 1; | |
cnet_config_buf = realloc(cnet_config_buf, cnet_config_buf_len); | |
memcpy(cnet_config_buf + tmp, dirargs, dirargs_len + 1); | |
cnet_config[cnet_config_len].off = tmp; | |
cnet_config_len++; | |
} | |
break; | |
} | |
case CCD_PORT: { | |
if (dirargs_len == 0) { | |
fprintf(stderr, CNET_WRN"%s directive with no argument, ignoring\n", dirstr); | |
} else { | |
long l; | |
if (sstrtol(dirargs, 10, &l)) { | |
fprintf(stderr, CNET_WRN"%s directive argument is NaN, ignoring\n", dirstr); | |
} else { | |
if ((l > 0) && (l <= UINT16_MAX)) { | |
cnet_config = realloc(cnet_config, (cnet_config_len + 1) * sizeof(*cnet_config)); | |
cnet_config[cnet_config_len].dir = CCD_PORT; | |
int tmp = cnet_config_buf_len; | |
cnet_config_buf_len += sizeof(uint16_t); | |
cnet_config_buf = realloc(cnet_config_buf, cnet_config_buf_len); | |
*(uint16_t*)(cnet_config_buf + tmp) = (uint16_t)l; | |
cnet_config[cnet_config_len].off = tmp; | |
cnet_config_len++; | |
} else { | |
fprintf(stderr, | |
CNET_WRN"%s directive argument '%s' is not a valid port, ignoring\n", | |
dirstr, dirargs); | |
} | |
} | |
} | |
break; | |
case CCD_INDEXFILE: { | |
if (dirargs_len == 0) { | |
fprintf(stderr, CNET_WRN"%s directive with no argument, ignoring\n", dirstr); | |
} else { | |
cnet_config = realloc(cnet_config, (cnet_config_len + 1) * sizeof(*cnet_config)); | |
cnet_config[cnet_config_len].dir = CCD_INDEXFILE; | |
int tmp = cnet_config_buf_len; | |
cnet_config_buf_len += dirargs_len + 1; | |
cnet_config_buf = realloc(cnet_config_buf, cnet_config_buf_len); | |
memcpy(cnet_config_buf + tmp, dirargs, dirargs_len + 1); | |
cnet_config[cnet_config_len].off = tmp; | |
cnet_config_len++; | |
} | |
break; | |
} | |
} | |
} | |
} | |
} | |
free(conf_databuf); | |
free(buf); | |
} | |
{ // Check if there is atleast one file directive, if not, warn the user | |
char count = 0; | |
for (int i = 0; i < cnet_config_len; i++) { | |
if (cnet_config[i].dir == CCD_FILE) { | |
count++; | |
break; | |
} | |
} | |
if (!count) { | |
fprintf(stderr, CNET_WRN"There are no file directives\n"); | |
} | |
} | |
uint16_t port = 8080; | |
{ // Check if there is exactly one port directive, if not, warn the user | |
int count = 0; | |
int index = -1; | |
for (int i = 0; i < cnet_config_len; i++) { | |
if (cnet_config[i].dir == CCD_PORT) { | |
count++; | |
index = i; | |
} | |
} | |
if (count == 0) { | |
fprintf(stderr, CNET_WRN"There are no port directives, defaulting to 8080\n"); | |
} else if (count > 1) { | |
fprintf(stderr, CNET_WRN"There are more than one port directives, using the last\n"); | |
} | |
if (count > 0) { | |
port = *(uint16_t*)(cnet_config_buf + cnet_config[index].off); | |
} | |
} | |
char *indexfile = "index.html"; | |
{ // Check if there is exactly one indexfile directive, if not, warn the user | |
int count = 0; | |
int index = -1; | |
for (int i = 0; i < cnet_config_len; i++) { | |
if (cnet_config[i].dir == CCD_INDEXFILE) { | |
count++; | |
index = i; | |
} | |
} | |
if (count == 0) { | |
fprintf(stderr, CNET_WRN"There are no indexfile directives, defaulting to %s\n", indexfile); | |
} else if (count > 1) { | |
fprintf(stderr, CNET_WRN"There are more than one indexfile directives, using the last\n"); | |
} | |
if (count > 0) { | |
indexfile = (char *)(cnet_config_buf + cnet_config[index].off); | |
} | |
} | |
{ | |
struct sigaction act; | |
act.sa_handler = cnet_sigint_handler; | |
act.sa_flags = 0; | |
sigemptyset(&act.sa_mask); | |
if (sigaction(SIGINT, &act, NULL)) { | |
switch (errno) { | |
default: | |
fprintf(stderr, CNET_FAT"sigaction(%i) has failed (errno %i): \"%s\"\n", SIGINT, errno, strerror(errno)); | |
exit(1); | |
} | |
} | |
} | |
int netsock = socket(AF_INET, SOCK_STREAM, 0); | |
{ | |
int enable = true; | |
setsockopt(netsock, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)); | |
} | |
{ | |
struct sockaddr_in addr; | |
addr.sin_family = AF_INET; | |
addr.sin_addr.s_addr = htonl(INADDR_ANY); | |
addr.sin_port = htons(port); | |
if (bind(netsock, (struct sockaddr*) &addr, sizeof(addr))) { | |
switch (errno) { | |
case EACCES: | |
fprintf(stderr, CNET_ERR"Lack of permission to bind to port %u\n", port); | |
exit(1); | |
default: { | |
unsigned char *ip = (unsigned char *) &addr.sin_addr.s_addr; | |
fprintf(stderr, | |
CNET_FAT"bind(%u.%u.%u.%u:%u) has failed (errno %i): \"%s\"\n", | |
ip[0], ip[1], ip[2], ip[3], port, errno, strerror(errno)); | |
exit(1); | |
} | |
} | |
} | |
unsigned char *ip = (unsigned char *) &addr.sin_addr.s_addr; | |
printf(CNET_INF"Bound socket to address: %u.%u.%u.%u:%u\n", ip[0], ip[1], ip[2], ip[3], port); | |
} | |
if (listen(netsock, 1 << 10)) { | |
switch (errno) { | |
default: | |
fprintf(stderr, CNET_FAT"listen() has failed (errno %i): \"%s\"\n", errno, strerror(errno)); | |
exit(1); | |
} | |
} | |
int inconn; | |
struct sockaddr_in inaddr; | |
while (true) { | |
socklen_t inaddrsize = sizeof(inaddr); | |
if ((inconn = accept(netsock, (struct sockaddr *) &inaddr, &inaddrsize)) == -1) { | |
switch (errno) { | |
case EINTR: | |
puts("\x0dInterrupted"); // Carriage return to hide ^C in case of SIGINT | |
break; | |
default: | |
fprintf(stderr, CNET_FAT"accept() has failed (errno %i): \"%s\"\n", errno, strerror(errno)); | |
exit(1); | |
} | |
} | |
if (inconn == -1) { | |
if (close(netsock)) { | |
switch (errno) { | |
default: | |
fprintf(stderr, CNET_FAT"close(%i) has failed (errno %i): \"%s\"\n", netsock, errno, strerror(errno)); | |
exit(1); | |
} | |
} | |
if (cnet_accepted_signal == SIGINT) { | |
puts("Received SIGINT, exiting"); | |
exit(0); | |
} else { | |
fputs(CNET_FAT"Something has gone terribly wrong for you to get this message", stderr); | |
exit(1); | |
} | |
} | |
{ | |
unsigned char *ip = (unsigned char *) &inaddr.sin_addr.s_addr; | |
printf(CNET_INF"\x1b[92mAccepted connection: %u.%u.%u.%u:%u\x1b[39m\n", ip[0], ip[1], ip[2], ip[3], ntohs(inaddr.sin_port)); | |
} | |
const int defaultlen = 2048; | |
int totalread = 0; | |
int databuflen = defaultlen; | |
char *read_databuf = malloc(databuflen); | |
memset(read_databuf, 0, databuflen); | |
{ | |
{ // Polls for data | |
struct pollfd fds; | |
fds.fd = inconn; | |
fds.events = POLLIN; | |
int eventscnt; | |
if ((eventscnt = poll(&fds, 1, 1000)) == -1) { | |
switch (errno) { | |
default: | |
fprintf(stderr, CNET_FAT"poll() has failed (errno %i): \"%s\"\n", errno, strerror(errno)); | |
exit(1); | |
} | |
} | |
} | |
int read; | |
do { | |
if ((read = recv(inconn, read_databuf + totalread, defaultlen, 0)) == -1) { | |
switch (errno) { | |
default: | |
fprintf(stderr, CNET_FAT"recv() has failed (errno %i): \"%s\"\n", errno, strerror(errno)); | |
exit(1); | |
} | |
} | |
totalread += read; | |
if (read == defaultlen) { | |
databuflen += defaultlen; | |
bool moredata = false; | |
{ | |
struct pollfd fds; | |
fds.fd = inconn; | |
fds.events = POLLIN; | |
int eventscnt; | |
if ((eventscnt = poll(&fds, 1, 20)) == -1) { | |
switch (errno) { | |
default: | |
fprintf(stderr, CNET_FAT"poll() has failed (errno %i): \"%s\"\n", errno, strerror(errno)); | |
exit(1); | |
} | |
} | |
moredata = eventscnt; | |
} | |
if (moredata) { | |
read_databuf = realloc(read_databuf, databuflen); | |
} else break; | |
} else break; | |
} while (true); | |
printf(CNET_INF"Read bytes: %i\n", totalread); | |
} | |
if (shutdown(inconn, SHUT_RD)) { | |
switch (errno) { | |
default: | |
fprintf(stderr, CNET_FAT"shutdown(%i) has failed (errno %i): \"%s\"\n", inconn, errno, strerror(errno)); | |
exit(1); | |
} | |
} | |
char *decoded_uri = NULL; | |
int decoded_uri_len = -1; | |
{ // URI decoding | |
if (totalread == 0) { | |
fputs(CNET_WRN"There is no data to read, can't get URI\n", stderr); | |
} else { | |
char *url_start = read_databuf; | |
while ((*url_start != ' ') && (*url_start != 0) && (url_start < (read_databuf + totalread))) url_start++; | |
if (*url_start == 0) { | |
fputs(CNET_WRN"Never found start of URI, probably malformed HTTP request\n", stderr); | |
} else { | |
int decoded_uri_bufsize = 512; | |
int decoded_uri_pos = 0; | |
decoded_uri = malloc(decoded_uri_bufsize); | |
url_start++; | |
while ((*url_start != ' ') && (*url_start != 0)) { | |
if (*url_start == '%') { | |
char c1 = *(url_start + 1), c2 = *(url_start + 2), res; | |
if ( | |
( | |
((c1 >= '0') && (c1 <= '9')) || | |
((c1 >= 'a') && (c1 <= 'f')) || | |
((c1 >= 'A') && (c1 <= 'F')) | |
) | |
&& | |
( | |
((c2 >= '0') && (c2 <= '9')) || | |
((c2 >= 'a') && (c2 <= 'f')) || | |
((c2 >= 'A') && (c2 <= 'F')) | |
) | |
) { | |
if (c1 >= 'a') { | |
res = 16 * (10 + c1 - 'a'); | |
} else if (c1 >= 'A') { | |
res = 16 * (10 + c1 - 'A'); | |
} else { | |
res = 16 * (c1 - '0'); | |
} | |
if (c2 >= 'a') { | |
res += 10 + c2 - 'a'; | |
} else if (c2 >= 'A') { | |
res += 10 + c2 - 'A'; | |
} else { | |
res += c2 - '0'; | |
} | |
url_start += 2; | |
} else { | |
res = '%'; | |
} | |
decoded_uri[decoded_uri_pos] = res; | |
} else { | |
decoded_uri[decoded_uri_pos] = *url_start; | |
} | |
decoded_uri_pos++; | |
url_start++; | |
}; | |
decoded_uri[decoded_uri_pos] = 0; | |
decoded_uri_len= decoded_uri_pos; | |
} | |
} | |
} | |
if (decoded_uri) printf(CNET_INF"Decoded URI (len %i): '%s'\n", decoded_uri_len, decoded_uri); | |
{ | |
int response_type = -1; | |
int servefilefd = -1; | |
if (decoded_uri_len == -1) { | |
response_type = 400; | |
} else { | |
{ // Check if there is a file to serve and if we can serve it | |
char *uri; | |
int uri_len; | |
if (*decoded_uri == '/') { // It is very unlikely that this will be false | |
uri = decoded_uri + 1; | |
uri_len = decoded_uri_len - 1; | |
} else { | |
uri = decoded_uri; | |
uri_len = decoded_uri_len; | |
} | |
// A simple way to redirect / to /index.html | |
// TODO: Better way that doesn't hide the fact the URI is / | |
if (uri_len == 0) { | |
uri = indexfile; | |
uri_len = strlen(uri); | |
} | |
bool valid = false; | |
for (int i = 0; i < cnet_config_len; i++) { | |
if ((cnet_config[i].dir == CCD_FILE) || (cnet_config[i].dir == CCD_INDEXFILE)) { | |
if (strcmp(uri, (cnet_config_buf + cnet_config[i].off)) == 0) { | |
valid = true; | |
break; | |
} | |
} | |
} | |
if (valid) { | |
int tmpfd; | |
if ((tmpfd = openat(rootdirfd, uri, O_RDONLY)) == -1) { | |
switch (errno) { | |
case ENOENT: | |
fputs(CNET_WRN"A requested allowed file doesn't exist\n", stderr); | |
response_type = 404; | |
break; | |
default: | |
fprintf(stderr, CNET_FAT"open(%s) has failed (errno %i): \"%s\"\n", uri, errno, strerror(errno)); | |
exit(1); | |
} | |
} else { | |
servefilefd = tmpfd; | |
if (0 == strcmp("favicon.ico", uri)) response_type = 2; | |
else if (0 == strcmp(".php", uri + uri_len - 4)) response_type = 3; | |
else response_type = 1; | |
} | |
} else { | |
response_type = 404; | |
} | |
} | |
} | |
{ // Response | |
int totalsent = 0; | |
switch (response_type) { | |
case 1: { // Generic response with whatever | |
struct stat filestat; | |
if (fstat(servefilefd, &filestat) == -1) { | |
switch (errno) { | |
default: | |
fprintf(stderr, CNET_FAT"fstat(%i) has failed (errno %i): \"%s\"\n", servefilefd, errno, strerror(errno)); | |
exit(1); | |
} | |
} | |
char *header = | |
"HTTP/1.1 200 OK\r\n" | |
"Server: cnet\r\n" | |
"Connection: close\r\n" | |
"Content-Length: %li\r\n" | |
"\r\n"; | |
char *resp_buf = malloc(strlen(header) + 16 + filestat.st_size); | |
int header_size = sprintf(resp_buf, header, filestat.st_size); | |
int fileread; | |
if ((fileread = read(servefilefd, resp_buf + header_size, filestat.st_size) == -1)) { | |
switch (errno) { | |
default: | |
fprintf(stderr, CNET_FAT"read(%i) has failed (errno %i): \"%s\"\n", servefilefd, errno, strerror(errno)); | |
exit(1); | |
} | |
} | |
int sent; | |
if ((sent = send(inconn, resp_buf, header_size + filestat.st_size, 0)) == -1) { | |
switch (errno) { | |
default: | |
fprintf(stderr, CNET_FAT"send(%i) has failed (errno %i): \"%s\"\n", inconn, errno, strerror(errno)); | |
exit(1); | |
} | |
} | |
free(resp_buf); | |
totalsent += sent; | |
break; | |
} | |
case 2: { // favicon.ico request | |
struct stat filestat; | |
if (fstat(servefilefd, &filestat) == -1) { | |
switch (errno) { | |
default: | |
fprintf(stderr, CNET_FAT"fstat(%i) has failed (errno %i): \"%s\"\n", servefilefd, errno, strerror(errno)); | |
exit(1); | |
} | |
} | |
char *header = | |
"HTTP/1.1 200 OK\r\n" | |
"Server: cnet\r\n" | |
"Connection: close\r\n" | |
"Content-Type: image/x-icon\r\n" | |
"Content-Length: %li\r\n" | |
"\r\n"; | |
char *resp_buf = malloc(strlen(header) + 16 + filestat.st_size); | |
int header_size = sprintf(resp_buf, | |
header, | |
filestat.st_size); | |
int fileread; | |
if ((fileread = read(servefilefd, resp_buf, header_size + filestat.st_size) == -1)) { | |
switch (errno) { | |
default: | |
fprintf(stderr, CNET_FAT"read(%i) has failed (errno %i): \"%s\"\n", servefilefd, errno, strerror(errno)); | |
exit(1); | |
} | |
} | |
int sent; | |
if ((sent = send(inconn, resp_buf, header_size + filestat.st_size, 0)) == -1) { | |
switch (errno) { | |
default: | |
fprintf(stderr, CNET_FAT"send(%i) has failed (errno %i): \"%s\"\n", inconn, errno, strerror(errno)); | |
exit(1); | |
} | |
} | |
totalsent += sent; | |
free(resp_buf); | |
break; | |
} | |
case 3: { // PHP response | |
#ifdef CNET_FEATURE_PHP | |
#error "PHP support is not finished yet, come back at a later date!" | |
#else | |
fputs( | |
CNET_INF"PHP file was requested, " | |
"but this binary is compiled without PHP support.\n" | |
CNET_INF"Compile with feature 'php' for PHP support.\n", | |
stderr | |
); | |
struct stat filestat; | |
if (fstat(servefilefd, &filestat) == -1) { | |
switch (errno) { | |
default: | |
fprintf(stderr, CNET_FAT"fstat(%i) has failed (errno %i): \"%s\"\n", servefilefd, errno, strerror(errno)); | |
exit(1); | |
} | |
} | |
char *header = | |
"HTTP/1.1 200 OK\r\n" | |
"Server: cnet\r\n" | |
"Connection: close\r\n" | |
"Content-Disposition: inline\r\n" | |
"Content-Type: text/php\r\n" | |
"Content-Length: %li\r\n" | |
"\r\n"; | |
char *resp_buf = malloc(strlen(header) + 16 + filestat.st_size); | |
int header_size = sprintf(resp_buf, header, filestat.st_size); | |
int fileread; | |
if ((fileread = read(servefilefd, resp_buf + header_size, filestat.st_size) == -1)) { | |
switch (errno) { | |
default: | |
fprintf(stderr, CNET_FAT"read(%i) has failed (errno %i): \"%s\"\n", servefilefd, errno, strerror(errno)); | |
exit(1); | |
} | |
} | |
int sent; | |
if ((sent = send(inconn, resp_buf, header_size + filestat.st_size, 0)) == -1) { | |
switch (errno) { | |
default: | |
fprintf(stderr, CNET_FAT"send(%i) has failed (errno %i): \"%s\"\n", inconn, errno, strerror(errno)); | |
exit(1); | |
} | |
} | |
free(resp_buf); | |
totalsent += sent; | |
#endif | |
break; | |
} | |
case 400: { | |
char *resp = | |
"HTTP/1.1 400 Bad Request\r\n" | |
"Server: cnet\r\n" | |
"Connection: close\r\n" | |
"Content-Type: text/plain; charset=utf-8\r\n" | |
"\r\n" | |
"400 Bad Request"; | |
int resp_len = strlen(resp); | |
int sent; | |
if ((sent = send(inconn, resp, resp_len, 0)) == -1) { | |
switch (errno) { | |
default: | |
fprintf(stderr, CNET_FAT"send(%i) has failed (errno %i): \"%s\"\n", inconn, errno, strerror(errno)); | |
exit(1); | |
} | |
} | |
totalsent += sent; | |
break; | |
} | |
case 404: { | |
char *resp = | |
"HTTP/1.1 404 Not Found\r\n" | |
"Server: cnet\r\n" | |
"Connection: close\r\n" | |
"Content-Type: text/plain; charset=utf-8\r\n" | |
"\r\n" | |
"404 Not Found"; | |
int resp_len = strlen(resp); | |
int sent; | |
if ((sent = send(inconn, resp, resp_len, 0)) == -1) { | |
switch (errno) { | |
default: | |
fprintf(stderr, CNET_FAT"send(%i) has failed (errno %i): \"%s\"\n", inconn, errno, strerror(errno)); | |
exit(1); | |
} | |
} | |
totalsent += sent; | |
break; | |
} | |
case -1: | |
default: { // Default error response | |
char *resp = | |
"HTTP/1.1 500 Internal Server Error\r\n" | |
"Server: cnet\r\n" | |
"Connection: close\r\n" | |
"Content-Type: text/plain; charset=utf-8\r\n" | |
"\r\n" | |
"500 Internal Server Error"; | |
int resp_len = strlen(resp); | |
int sent; | |
if ((sent = send(inconn, resp, resp_len, 0)) == -1) { | |
switch (errno) { | |
default: | |
fprintf(stderr, CNET_FAT"send(%i) has failed (errno %i): \"%s\"\n", inconn, errno, strerror(errno)); | |
exit(1); | |
} | |
} | |
totalsent += sent; | |
break; | |
} | |
} | |
printf(CNET_INF"Sent bytes: %i\n", totalsent); | |
} | |
if (servefilefd != -1) { | |
if (close(servefilefd)) { | |
switch (errno) { | |
default: | |
fprintf(stderr, CNET_FAT"close(%i) has failed (errno %i): \"%s\"\n", servefilefd, errno, strerror(errno)); | |
exit(1); | |
} | |
} | |
} | |
} | |
if (close(inconn)) { | |
switch (errno) { | |
default: | |
fprintf(stderr, CNET_FAT"close(%i) has failed (errno %i): \"%s\"\n", inconn, errno, strerror(errno)); | |
exit(1); | |
} | |
} | |
free(read_databuf); | |
free(decoded_uri); | |
} | |
} |
This file contains hidden or 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
#!/usr/bin/env bash | |
CC=gcc | |
WARNINGS="-Wall -Wextra -Wshadow" | |
function usage { | |
cat <<- EOF | |
make [profile] | |
Builds profile. Default profile is 'release'. | |
Profiles: | |
release - Optimized stripped build | |
debug - Debug optimized build with debug symbols | |
features [features...] | |
Enable specified features. | |
Features: | |
php - PHP support | |
clean | |
Cleans up the working directory | |
ci | |
Watches source for updates, and automatically builds | |
a debug build. Uses inotifywait from inotify-tools. | |
EOF | |
} | |
case "$1" in | |
make) | |
[ -r features ] && FEATURES=$(tail +2 features) | |
FTRS=$(head -n +1 features) | |
[ -z "$FTRS" ] && FTRS=None | |
case "$2" in | |
release | "") | |
echo "Building release build with features: $FTRS" | |
gcc main.c -o cnet -O2 $WARNINGS $FEATURES | |
strip cnet | |
;; | |
debug) | |
echo "Building debug build with features: $FTRS" | |
gcc main.c -o cnet -Og -g $WARNINGS $FEATURES | |
;; | |
*) | |
echo "Unknown build profile" | |
exit 1 | |
;; | |
esac | |
;; | |
features) | |
( | |
if [ $# -le 1 ]; then | |
echo "Removing all features" | |
printf "" > features # Empty | |
exit 0 | |
fi | |
ARGS=( $@ ) | |
FTRS=${ARGS[*]:1} | |
echo "$FTRS" > features | |
for FTR in $FTRS; do | |
case $FTR in | |
php) | |
cat >> features <<- EOF | |
-DCNET_FEATURE_PHP | |
$(php-config --includes) | |
-L$(php-config --prefix)/lib | |
-lphp | |
-Wl,-rpath=$(php-config --prefix)/lib | |
EOF | |
;; | |
*) | |
echo "Unknown feature '$FTR'" | |
printf "" > features # Empty | |
exit 1 | |
;; | |
esac | |
done | |
) | |
;; | |
clean) | |
rm -rf features | |
;; | |
ci) | |
while true; do | |
inotifywait -e move_self -e modify main.c 2> /dev/null | \ | |
read && ./build make debug | |
done | |
;; | |
"") | |
usage | |
exit 1 | |
;; | |
*) | |
echo "Invalid subcommand" | |
exit 1 | |
;; | |
esac |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment