diff options
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | Makefile | 24 | ||||
-rw-r--r-- | http.c | 183 | ||||
-rw-r--r-- | http.h | 16 | ||||
-rw-r--r-- | main.c | 159 | ||||
-rw-r--r-- | memory.c | 11 | ||||
-rw-r--r-- | memory.h | 13 | ||||
-rw-r--r-- | util.c | 24 | ||||
-rw-r--r-- | util.h | 7 |
9 files changed, 440 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7304726 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +cserver +*.o +*.dSYM diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1583287 --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +CC = gcc +CFLAGS = -Wall -Wextra -std=c11 -fwrapv +ifneq ($(DEBUG),) + CFLAGS += -g +else + CFLAGS += -O2 +endif +BIN = cserver + +.PHONY: all clean remake + +all: $(BIN) + +clean: + rm -rf $(BIN) *.o *.dSYM + +remake: clean all + + +$(BIN): $(patsubst %.c,%.o,$(wildcard *.c)) + $(CC) -o $@ $^ + +%.o: %.cpp $(wildcard *.h) + $(CC) $(CFLAGS) -c -o $@ $< @@ -0,0 +1,183 @@ +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> +#include <ctype.h> +#include <sys/socket.h> +#include "http.h" +#include "memory.h" +#include "util.h" + + +static void free_list(char **list,int nitems){ + for(int i=0;i<nitems;i++){ + free(list[i]); + } + free(list); +} + +// -2: error; -1: socket closed; >=0: length of string +static int sock_get_line(char **dst,int sock){ + int linesz=128,linelen=0; + char *line=malloc(linesz,char); + bool haveCR=false,alsoCR=false; + while(true){ + char c; + ssize_t ret=recv(sock,&c,1,0); + if(ret<=0){ + free(line); + *dst=NULL; + return ret-1; + } + if(c=='\r'){ + haveCR=true; + continue; + } else if(c=='\n'){ + break; + } else if(haveCR){ + haveCR=false; + alsoCR=true; + } + if(linelen+1+alsoCR==linesz){ + linesz*=2; + if(linesz==0){ + exit(255); // Integer overflow! + } + line=realloc(line,linesz,char); + } + if(alsoCR){ + line[linelen++]='\r'; + alsoCR=false; + } + line[linelen++]=c; + } + line[linelen]='\0'; + *dst=line; + return linelen; +} + +// Returns number of lines, or -1 if error +static int get_header_lines(char ***dst,int sock){ + int arrsz=16,arrlen=0; + char **arr=malloc(arrsz,char*); + while(true){ + char *line; + int linelen=sock_get_line(&line,sock); + if(linelen<0){ + free_list(arr,arrlen); + *dst=NULL; + return -1; + } + if(linelen==0){ + break; + } + if(arrlen+1==arrsz){ + arrsz*=2; + if(arrsz==0){ + exit(255); // Integer overflow! + } + arr=realloc(arr,arrsz,char*); + } + arr[arrlen++]=line; + } + arr[arrlen]=NULL; + *dst=arr; + return arrlen; +} + +static Headers* alloc_headers(void){ + Headers *headers=malloc(1,Headers); + headers->method=NULL; + headers->url=NULL; + headers->version=NULL; + headers->nheaders=0; + headers->headers=NULL; + return headers; +} + +void free_headers(Headers *headers){ + if(headers==NULL){ + return; + } + + if(headers->method)free(headers->method); + if(headers->url)free(headers->url); + if(headers->version)free(headers->version); + + if(headers->headers){ + for(int i=0;i<headers->nheaders;i++){ + free(headers->headers[i][0]); + free(headers->headers[i][1]); + } + free(headers->headers); + } + + free(headers); +} + +Headers* http_get_headers(int sock){ + char **lines; + int nlines=get_header_lines(&lines,sock); + if(nlines<0){ + return NULL; + } + if(nlines==0){ + free_list(lines,nlines); + return NULL; + } + + char *spacep=strchr(lines[0],' '); + char *space2p=strrchr(lines[0],' '); + if(spacep==NULL||space2p==NULL){ + free_list(lines,nlines); + return NULL; + } + char *urlstart=spacep+1; + while(isspace(*urlstart)){ + urlstart++; + } + if(urlstart>space2p){ // No url? + free_list(lines,nlines); + return NULL; + } + + Headers *headers=alloc_headers(); + + headers->method=copy_buf(lines[0],spacep-lines[0]); + str_toupper(headers->method); + headers->url=copy_buf(urlstart,space2p-urlstart); + headers->version=copy_str(space2p); + str_toupper(headers->version); + + int hsz=16; + headers->nheaders=0; + headers->headers=malloc(hsz,Header); + + for(int i=1;i<nlines;i++){ + char *colonp=strchr(lines[i],':'); + if(colonp==NULL){ + free_list(lines,nlines); + free_headers(headers); + return NULL; + } + char *valuep=colonp+1; + while(isspace(*valuep)){ + valuep++; + } + + if(headers->nheaders+1==hsz){ + hsz*=2; + // Don't need to check overflow, because nlines would have + // overflowed already + headers->headers=realloc(headers->headers,hsz,Header); + } + headers->headers[headers->nheaders][0]=copy_buf(lines[i],colonp-lines[i]); + headers->headers[headers->nheaders][1]=copy_str(valuep); + headers->nheaders++; + } + headers->headers[headers->nheaders][0]=NULL; + headers->headers[headers->nheaders][1]=NULL; + + free_list(lines,nlines); + return headers; +} @@ -0,0 +1,16 @@ +#pragma once + + +typedef char *Header[2]; + +typedef struct Headers{ + char *method,*url,*version; + + int nheaders; + Header *headers; +} Headers; + + +void free_headers(Headers *headers); + +Headers* http_get_headers(int sock); @@ -0,0 +1,159 @@ +#define _GNU_SOURCE +#include <stdio.h> +#include <stdbool.h> +#include <stdarg.h> +#include <string.h> +#include <signal.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <unistd.h> +#include <errno.h> +#include "http.h" +#include "memory.h" + + +#define PORT 8080 + + +int num_threads=0; + + +static void signal_handler(int sig){ + if(sig==SIGCHLD){ + int saved_errno=errno; + while(true){ + int status; + pid_t pid=waitpid(-1,&status,WNOHANG); + if(pid<=0){ + break; + } + if(WIFEXITED(status)){ + num_threads--; + } + } + errno=saved_errno; + } +} + + +// Returns -1 on error, 0 on success +static int sendall(int sock,const char *buf,ssize_t len){ + if(len==-1){ + len=strlen(buf); + } + + ssize_t sent=0; + while(sent<len){ + ssize_t ret=send(sock,buf+sent,len-sent,0); + if(ret<0){ + if(errno==EINTR){ + continue; + } else { + return -1; + } + } + sent+=ret; + } + + return 0; +} + +__attribute__((format (printf, 2, 3))) +static int sendallf(int sock,const char *format,...){ + va_list ap; + va_start(ap,format); + char *buf; + int len=vasprintf(&buf,format,ap); + va_end(ap); + if(len<0){ + return -1; + } + int ret=sendall(sock,buf,len); + free(buf); + return ret; +} + +static void connection_handler(int sock){ + Headers *headers=http_get_headers(sock); + if(headers==NULL){ + return; + } + + sendall(sock,"HTTP/1.1 200 OK\r\n" + "Content-Type: text/plain; charset=UTF-8\r\n" + "Server: cserver\r\n" + "\r\n",-1); + + sendallf(sock,"Method: %s\n",headers->method); + sendallf(sock,"URL: %s\n",headers->url); + sendallf(sock,"Version: %s\n",headers->version); + for(int i=0;i<headers->nheaders;i++){ + sendallf(sock,"Header '%s': '%s'\n",headers->headers[i][0],headers->headers[i][1]); + } +} + + +int main(void){ + int sock=socket(PF_INET,SOCK_STREAM,0); + if(sock<0){ + perror("socket"); + return 1; + } + + int one=1; + if(setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&one,sizeof(int))<0){ + perror("setsockopt"); + return 1; + } + + struct sockaddr_in sin; + sin.sin_family=AF_INET; + sin.sin_addr.s_addr=htonl(INADDR_ANY); + sin.sin_port=htons(PORT); + + if(bind(sock,(struct sockaddr*)&sin,sizeof(sin))<0){ + close(sock); + perror("bind"); + return 1; + } + + if(listen(sock,8)<0){ + perror("listen"); + return 1; + } + + signal(SIGCHLD,signal_handler); + + printf("Listening on port %d\n",PORT); + + while(true){ + struct sockaddr_storage client_addr; + socklen_t client_addr_sz=sizeof(client_addr); + int clientsock=accept(sock,(struct sockaddr*)&client_addr,&client_addr_sz); + if(clientsock<0){ + perror("accept"); + continue; + } + + char str[INET_ADDRSTRLEN]; + inet_ntop(client_addr.ss_family,&((struct sockaddr_in*)&client_addr)->sin_addr, + str,sizeof(str)); + + pid_t pid=fork(); + if(pid==0){ + close(sock); + connection_handler(clientsock); + close(clientsock); + exit(0); + } + if(pid<0){ + perror("fork"); + return 1; + } + num_threads++; + printf("Accept from %s; child pid %d, %d thread%s\n",str,pid,num_threads, + num_threads==1?"":"s"); + close(clientsock); + } +} diff --git a/memory.c b/memory.c new file mode 100644 index 0000000..6f8253f --- /dev/null +++ b/memory.c @@ -0,0 +1,11 @@ +#include <stdio.h> +#include "memory.h" + + +void* check_after_allocation(const char *funcname, void *ptr){ + if(ptr == NULL){ + perror(funcname); + exit(1); + } + return ptr; +} diff --git a/memory.h b/memory.h new file mode 100644 index 0000000..076261d --- /dev/null +++ b/memory.h @@ -0,0 +1,13 @@ +#pragma once + +#include <stdlib.h> + + +#define malloc(num, type) \ + ((type*)check_after_allocation("malloc", malloc((num)*sizeof(type)))) +#define calloc(num, type) \ + ((type*)check_after_allocation("calloc", calloc((num), sizeof(type)))) +#define realloc(ptr, num, type) \ + ((type*)check_after_allocation("realloc", realloc(ptr, (num)*sizeof(type)))) + +void* check_after_allocation(const char *funcname, void *ptr); @@ -0,0 +1,24 @@ +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include "memory.h" +#include "util.h" + + +char* copy_buf(char *buf,int len){ + char *dst=malloc(len+1,char); + memcpy(dst,buf,len); + dst[len]='\0'; + return dst; +} + +char* copy_str(char *str){ + return copy_buf(str,strlen(str)); +} + +void str_toupper(char *str){ + while(*str!='\0'){ + *str=toupper(*str); + str++; + } +} @@ -0,0 +1,7 @@ +#pragma once + + +char* copy_buf(char *buf,int len); +char* copy_str(char *str); + +void str_toupper(char *str); |