Skip to content

Instantly share code, notes, and snippets.

@farhaven
Created December 23, 2010 21:55
Show Gist options
  • Save farhaven/753600 to your computer and use it in GitHub Desktop.
Save farhaven/753600 to your computer and use it in GitHub Desktop.
#define _POSIX_SOURCE
#define _BSD_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <strings.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <limits.h>
#include <libgen.h>
#include <ctype.h>
#include <dirent.h>
#define LISTEN_BACKLOG 50
#define LOCALPORT 8080
#define BUFLEN 1024
struct cookie {
char *name;
char *value;
struct cookie *next;
};
struct url {
char *proto;
char *host;
char *path;
char *para; /* url parameters, i.e. everything after the first ? */
};
struct http_request {
struct url u;
struct cookie *cookies;
};
struct client {
int s;
struct sockaddr_in a;
};
char *
get_filetype(char *fname) {
char *t = strrchr(fname, '.');
if (!t) {
return "application/octet-stream";
}
t++;
char *ext = (char *)calloc(sizeof(char), strlen(t));
memcpy(ext, t, strlen(t));
t = ext;
do { *t |= (1 << 5); } while(*(++t) != 0x00);
if(!strcmp(ext, "c")) return "text/x-c";
if(strstr(ext, "html") != NULL) return "text/html";
if(!strcmp(ext, "txt")) return "text/plain";
return "application/octet-stream";
}
int
write_response(int s, char *msg) {
return write(s, msg, strlen(msg));
}
void
write_headers(int s, int status, char *type) {
char *tmp = (char *) calloc(sizeof(char), BUFLEN);
snprintf(tmp, BUFLEN - 1, "HTTP/1.0 %d\r\nContent-Type: %s\r\n\r\n", status, type);
write_response(s, tmp);
free(tmp);
}
void
cookie_add(struct http_request *r, char *name, char *value) {
struct cookie *c = r->cookies;
if (c == NULL) {
r->cookies = (struct cookie *) calloc(sizeof(struct cookie), 1);
c = r->cookies;
} else {
while(c->next != NULL) c = c->next;
c->next = (struct cookie *) calloc(sizeof(struct cookie), 1);
c = c->next;
}
c->name = (char *) calloc(sizeof(char), strlen(name));
c->value = (char *) calloc(sizeof(char), strlen(value));
memcpy(c->name, name, strlen(name));
memcpy(c->value, value, strlen(value));
}
void
cookies_add(struct http_request *r, char *buf) {
const int len = strlen("cookies: ");
const int n = strlen(buf);
char *c_r, *c;
memmove(buf, buf + len - 1, n - len + 1);
buf[n - len + 1] = 0x00;
c = strtok_r(buf, "; ", &c_r);
while (c != NULL) {
char *v = strtok(c, "=");
char *name;
if (v != NULL) {
name = (char *) calloc(sizeof(char), strlen(v));
memcpy(name, v, strlen(v));
v = strtok(NULL, "=");
if (v != NULL) cookie_add(r, name, v);
}
c = strtok_r(NULL, "; ", &c_r);
}
}
int
readline(char *buf, int fd, size_t len) {
int bytes_read = 0;
while(bytes_read < len) {
char rbuf;
int n = read(fd, &rbuf, 1);
if (n == 0) break;
buf[bytes_read] = rbuf;
bytes_read++;
if (rbuf == '\n') break;
}
buf[len - 1] = 0x00;
return bytes_read;
}
struct url
split_url(char *path) {
fprintf(stderr, "split_url: enter... parsing URL \"%s\"\n", path);
char *tmp = (char *) calloc(sizeof(char), strlen(path) + 1);
memcpy(tmp, path, strlen(path));
struct url u = {
.host = NULL,
.proto = NULL,
.path = NULL,
.para = NULL
};
char *t = strstr(tmp, "://");
if (t) {
u.proto = (char *) calloc(sizeof(char), t - tmp);
memcpy(u.proto, tmp, t - tmp);
memmove(tmp, t + 3, strlen(tmp) - (t - tmp + 3) + 1);
}
t = strchr(tmp, '/');
if (t != tmp) {
u.host = (char *) calloc(sizeof(char), t - tmp);
memcpy(u.host, tmp, t - tmp);
memmove(tmp, tmp + (t - tmp), strlen(tmp) - (t - tmp) + 1);
}
t = strchr(tmp, '?');
if (t != NULL) {
u.para = (char *) calloc(sizeof(char), strlen(tmp) - (t - tmp) + 1);
memcpy(u.para, t + 1, strlen(tmp) - (t - tmp - 1));
*t = 0x00;
}
if (strlen(tmp) != 0) {
u.path = (char *) calloc(sizeof(char), strlen(tmp) + 2);
memcpy(u.path + 2, tmp + 1, strlen(tmp) - 1);
memcpy(u.path, "./", 2);
}
char *rp = (char *) calloc(sizeof(char), PATH_MAX + 1);
char *cwd = (char *) calloc(sizeof(char), PATH_MAX + 1);
cwd = getcwd(cwd, PATH_MAX + 1);
rp = realpath(u.path, rp);
if(rp) {
if (!strncmp(rp, cwd, strlen(cwd))) {
memmove(rp + 1, rp + strlen(cwd), strlen(rp) - strlen(cwd) + 1);
rp[0] = '.';
u.path = (char *) calloc(sizeof(char), strlen(rp));
memcpy(u.path, rp, strlen(rp) + 1);
} else {
rp = realpath(rp, NULL);
if (rp[0] == '/') {
u.path = (char *) calloc(sizeof(char), strlen(rp) + 2);
memcpy(u.path + 1, rp, strlen(rp));
u.path[0] = '.';
} else {
u.path = (char *) calloc(sizeof(char), strlen(rp) + 3);
memcpy(u.path + 2, rp, strlen(rp));
memcpy(u.path, "./", 2);
}
}
}
fprintf(stderr, " freeing pointers\n");
free(rp);
fprintf(stderr, " rp freed\n");
free(cwd);
fprintf(stderr, " cwd freed\n");
fprintf(stderr, "split_url: leave\n");
return u;
}
void
list_directory(struct client c, char *path) {
struct dirent **dir;
struct stat st_buf;
char *fullpath, *line;
int n, idx;
write_headers(c.s, 200, "text/html; encoding=us-ascii");
write_response(c.s, "<html>\r\n<head><title>Directory listing of ");
write_response(c.s, path);
write_response(c.s, "</title></head>\r\n<body>\r\n");
write_response(c.s, "<h1>Content listing of directory ");
write_response(c.s, path);
write_response(c.s, "</h1>\r\n<table>\r\n");
fullpath = (char *) calloc(sizeof(char), BUFLEN);
line = (char *) calloc(sizeof(char), BUFLEN);
n = scandir(path, &dir, 0, alphasort);
for(idx = 0; idx < n; idx++) {
int err;
char type = 'U';
snprintf(fullpath, BUFLEN - 1, "%s/%s", path, dir[idx]->d_name);
err = stat(fullpath, &st_buf);
int m = st_buf.st_mode;
if (!err) {
if (!(m & S_IROTH)) type = 'X';
else if (S_ISDIR(m)) type = 'D';
else if (S_ISREG(m)) type = 'R';
}
snprintf(line, BUFLEN - 1, "<tr><td>[%c]</td><td><a href=\"%s\">%s%s</a><br/></td></tr>\r\n",
type, fullpath, dir[idx]->d_name, ((S_ISDIR(m) && (!err)) ? "/" : ""));
write_response(c.s, line);
free(dir[idx]);
}
free(dir);
free(line);
write_response(c.s, "\r\n</table><br/>");
write_response(c.s, "Meaning:<br/>\r\n<table>");
write_response(c.s, "<tr><td>[X]</td><td>Not world readable</td><td>[D]</td><td>Directory</td></tr>\r\n");
write_response(c.s, "<tr><td>[R]</td><td>Regular file </td><td>[U]</td><td>Unknown</td></tr>\r\n");
write_response(c.s, "</table>\r\n");
write_response(c.s, "</body>\r\n</html>");
}
void
transmit_file(struct client c, char *fname) {
struct stat st_buf;
char *buf;
int n, fd, err = stat(fname, &st_buf);
if ((S_ISDIR(st_buf.st_mode)) && (err == 0)) {
list_directory(c, fname);
return;
}
if (!(st_buf.st_mode & S_IROTH) && (err == 0)) {
write_headers(c.s, 403, "text/plain; encoding=us-ascii");
write_response(c.s, fname);
write_response(c.s, " is not world readable.\r\n");
return;
}
fd = open(fname, O_RDONLY);
if (fd < 0) {
write_headers(c.s, 404, "text/plain; encoding=us-ascii");
write_response(c.s, fname);
write_response(c.s, " could not be opened: ");
write_response(c.s, strerror(errno));
write_response(c.s, "\r\n");
return;
}
write_headers(c.s, 200, get_filetype(fname));
buf = (char *) calloc(sizeof(char), BUFLEN);
while((n = read(fd, buf, BUFLEN)) > 0) n = write(c.s, buf, n);
free(buf);
}
void
handle_client(struct client cl) {
struct http_request *r;
char *buf;
r = (struct http_request *)calloc(sizeof(struct http_request), 1);
buf = (char *) calloc(sizeof(char), BUFLEN);
/* read HTTP headers */
while (1) {
int n, len, buflen;
char *p;
n = readline(buf, cl.s, BUFLEN);
if (n <= 2) break; /* empty line received */
/* cut off \r\n at EOL */
p = strstr(buf, "\n");
if (p) *p = 0x00;
p = strstr(buf, "\r");
if (p) *p = 0x00;
if (!strncasecmp(buf, "cookie: ", strlen("cookie: "))) cookies_add(r, buf);
else if (!strncasecmp(buf, "get ", strlen("get "))) {
len = strlen("get ");
buflen = strlen(buf);
memmove(buf, buf + len, buflen - len + 1);
buf[buflen - len + 1] = 0x00;
buf = strtok(buf, " ");
r->u = split_url(buf);
}
}
free(buf);
if (r->u.path == NULL) {
write_headers(cl.s, 501, "text/plain; encoding=us-ascii");
write_response(cl.s, "Requests other than GET are not implemented.\r\n");
} else
transmit_file(cl, r->u.path);
close(cl.s);
}
void
usage(char *n) {
fprintf(stderr, "Usage: %s [port]\n", n);
exit(1);
}
int
main(int argc, char *argv[]) {
struct sockaddr_in server_sa, peer_sa;
struct client c;
int server_socket, peer_socket;
int err, port = LOCALPORT;
if (argc > 2) {
usage(argv[0]);
}
if (argc == 2) {
port = atoi(argv[1]);
if (port <= 0) usage(argv[0]);
}
server_socket = socket(AF_INET, SOCK_STREAM, 0);
server_sa.sin_addr.s_addr = INADDR_ANY;
server_sa.sin_family = AF_INET;
while (1) {
server_sa.sin_port = htons(port);
err = bind(server_socket, (struct sockaddr *) &server_sa, sizeof(struct sockaddr_in));
if (!err) {
fprintf(stderr, "bound to port %d on all interfaces\n", port);
break;
}
port++;
}
err = listen(server_socket, LISTEN_BACKLOG);
if (err) {
fprintf(stderr, "listen: %s\n", strerror(errno));
exit(1);
}
while (1) {
size_t peer_addr_size;
pid_t cpid;
peer_socket = accept(server_socket, (struct sockaddr *) &peer_sa, &peer_addr_size);
if (peer_socket < 0) {
fprintf(stderr, "accept: %s\n", strerror(errno));
continue;
}
cpid = fork();
if (cpid == 0) { /* children don't need the accept loop */
close(server_socket);
break;
}
fprintf(stderr, "forked child %d\n", cpid);
close(peer_socket);
}
/* child */
fprintf(stderr, "accepted: ip=%s fd=%d\n", inet_ntoa(peer_sa.sin_addr), peer_socket);
c.s = peer_socket;
c.a = peer_sa;
handle_client(c);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment