Skip to content

Instantly share code, notes, and snippets.

@ThePotatoChronicler
Last active September 30, 2021 11:30
Show Gist options
  • Save ThePotatoChronicler/f570c7dcda2d3664d7710d077889d83a to your computer and use it in GitHub Desktop.
Save ThePotatoChronicler/f570c7dcda2d3664d7710d077889d83a to your computer and use it in GitHub Desktop.
A simple Linux C webserver
/**
* 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);
}
}
#!/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