diff options
| -rw-r--r-- | README.md | 15 | ||||
| -rwxr-xr-x | build.sh | 69 | ||||
| -rw-r--r-- | files/index.html | 4 | ||||
| -rw-r--r-- | src/main.c | 121 | 
4 files changed, 150 insertions, 59 deletions
diff --git a/README.md b/README.md new file mode 100644 index 0000000..b2438cc --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +### Web Server + +This is a simple http web server written in C with minimal dependencies. +It uses the unix socket api and optionally libmagic for file information. + +### Build + +I don't like Makefile, so I wrote a shell script that compiles the program. +Use `./build.sh help` to learn more about the build options and `./build.sh run` just to run the program + +### Limitation + +I have implemented only the GET request method, which just send the requested +file from the `files` directory. There also isn't a hash table for the headers +because I am lazy and it will make the project more unnessairly complex. @@ -3,20 +3,53 @@  cd ${0%/*} # go to project root  NAME="web-server" -FLAGS="-std=c99 -Wall -Wextra -g -pedantic -D_POSIX_C_SOURCE=200112L" +FLAGS="-std=c99 -Wall -Wextra -g -pedantic" +  SRCD="src"  ODIR="obj"  BIN="bin"  FILES="files"  RUN=0 +LIBS=("magic") +INC_LIBS= +INC_MACROS="-D_POSIX_C_SOURCE=200112L -DFILES=\"$FILES\" " + +function add_libs { +    for lib in ${LIBS[@]}; do +        INC_LIBS+="-l$lib " +        INC_MACROS+="-DUSE_LIB${lib^^} " +    done +} + +function __help__ { +    echo "Usage:" +    echo "  ./build.sh [command | option] ..." +    echo "" +    echo "Options:" +    echo "  -(library)   add the libarary into the list of libraries" +    echo "" +    echo "Commands:" +    echo "     help      print this text" +    echo "     run       will run the program after all commands are interpreted" +    echo "     valgrind  use valgrind when running" +    echo "  -, nolib     clear the list of libraries that are going to be loaded" +    echo "     clean     will clean the temporary folders like 'obj' or 'bin'" +    echo "" +    echo "Note: Each command and option is interpreted in order" +    exit 0 +} + +function __nolib__ { +    LIBS=() +} +  function __run__ {      RUN=1  } -function __leak__ { +function __valgrind__ {      VALGRND="valgrind --leak-check=full --show-leak-kinds=all -s" -    RUN=1  }  function __clean__ { @@ -25,23 +58,37 @@ function __clean__ {      exit 0  } + +if ! [[ $# -eq 0 ]] +then +    for cmd in "$@"; do +        if [ $(cut -c1-1 <<< $cmd) == "-" ]; then +           if [ $(expr length $cmd) == 1 ]; then +               __nolib__ +           else +               LIBS+=("$(cut -c2- <<< $cmd)") +           fi +        else +            __"$cmd"__ +        fi +    done +fi +  set -xe  mkdir -p $BIN  mkdir -p $ODIR  mkdir -p $FILES -if ! { [[ $# -eq 0 ]]; } 2> /dev/null -then -    __$1__ -fi +{ add_libs; } 2> /dev/null -gcc -c $SRCD/server.c -o $ODIR/server.o  $FLAGS -gcc -c $SRCD/main.c   -o $ODIR/main.o    $FLAGS -DFILES=\"$FILES\" - -gcc -o $BIN/$NAME        $ODIR/main.o $ODIR/server.o $FLAGS +gcc -c $SRCD/server.c -o $ODIR/server.o  $FLAGS $INC_MACROS +gcc -c $SRCD/main.c   -o $ODIR/main.o    $FLAGS $INC_MACROS +gcc -o $BIN/$NAME        $ODIR/main.o $ODIR/server.o $FLAGS $INC_MACROS $INC_LIBS  if ! { [[ $RUN -eq 0 ]]; } 2> /dev/null  then      $VALGRND $BIN/$NAME  fi + +exit 0 diff --git a/files/index.html b/files/index.html index b94ee21..5a0ce18 100644 --- a/files/index.html +++ b/files/index.html @@ -5,8 +5,8 @@      <link rel="icon" href="/favicon.png">    </head>    <body> -    <h1>Server</h1> -    <p>This is a paragraph</p> +    <h1>Web Server Written in C</h1> +    <p>Please don't hack</p>      <a href="/test/test1.html">Test 1</a>    </body>  </html> @@ -13,7 +13,7 @@  #define PORT "8079"  #define RM_LF(str) do {                                     \ -        signed long len = strlen(str)-1;                  \ +        signed long len = strlen(str)-1;                    \          if(len >= 0 && str[len] == '\n') str[len] = '\0';   \      } while(0) @@ -35,58 +35,60 @@          SEND_BUF_ADD("\r\n");                        \      } while(0) -#define LOAD_FILE() do {                                                \ -        if(is_binary) {                                                 \ -            fseek(fp, 0, SEEK_END);                                     \ -            size_t file_sz = ftell(fp);                                 \ -            rewind(fp);                                                 \ -                                                                        \ -            fread(&send_buf[*send_buf_sz], sizeof(char), file_sz, fp);  \ -            *send_buf_sz += file_sz;                                    \ -        } else {                                                        \ -            char line[BUF_CAP];                                         \ -            while(fgets(line, sizeof(line), fp) != NULL)                \ -            {                                                           \ -                RM_LF(line);                                            \ -                SEND_BUF_ADD_LINE(line);                                \ -            }                                                           \ -        }                                                               \ +#define SEND_BUF_ADD_LINE_LONG(lon) do {        \ +        char str[COMMON_CAP];                   \ +        sprintf(str, "%ld", lon);               \ +        SEND_BUF_ADD_LINE(str);                 \      } while(0) +#ifdef USE_LIBMAGIC +#include <magic.h>  static int get_content_type(char *file_path, char *content_type)  { -    char cmd[COMMON_CAP + 64]; -    sprintf(cmd, "/usr/bin/file -ib %s", file_path); +    int ret = 1; +    magic_t magic; -    FILE *fp = popen(cmd, "r"); -    if(!fp) { -        err("popen: cmd %s: %s", cmd, strerror(errno)); -        return 1; +    if((magic = magic_open(MAGIC_MIME)) == NULL) { +        err("magic_open: %s", magic_error(magic)); +        return ret;      } -    if(fgets(content_type, COMMON_CAP, fp) == NULL) { -        err("get_mime_type: no output from fgets"); -        return 1; +    if(magic_load(magic, NULL) != 0) { +        err("magic_load: %s", magic_error(magic)); +        goto close;      } -    pclose(fp); -    RM_LF(content_type); - -    char ct_cpy[COMMON_CAP]; -    strcpy(ct_cpy, content_type); -    strtok(ct_cpy, "="); - -    // the only thing after the '=' should be the either binary or something else (text) -    char *charset = strtok(NULL, "="); -    if(charset == NULL || strcmp(charset, "binary") != 0) { -        return 0; +    const char *mime = magic_file(magic, file_path); +    if(mime == NULL) { +        err("magic_file: %s", magic_error(magic)); +        goto close;      } -    return -1; +    memcpy(content_type, mime, strlen(mime)); + +    ret = 0; +close: +    magic_close(magic); +    return ret; +} +#else +static int get_content_type(char *file_path, char *content_type) +{ +    (void)file_path; +    (void)content_type; +    return 0;  } +#endif  static int try_file(char *req_path, FILE **fp, char *content_type)  { +    for(size_t i = 1; i < strlen(req_path); i++) { +        if(req_path[i-1] == '.' && req_path[i] == '.') { +            err("try_file: the requested path %s includes ..", req_path); +            return 1; +        } +    } +      char file_path[COMMON_CAP];      sprintf(file_path, "%s%s", FILES,              (strlen(req_path) == 1) ? "/index.html" : req_path); @@ -96,24 +98,31 @@ static int try_file(char *req_path, FILE **fp, char *content_type)          err("fopen: file %s: %s", file_path, strerror(errno));          return 1;      } +      info("fopen: file %s was opened", file_path); -    return get_content_type(file_path, content_type); +    if(get_content_type(file_path, content_type) != 0) { +        err("get_content_type: failed"); +        fclose(*fp); +        return 1; +    } + +    return 0;  }  static int on_get(char *send_buf, ssize_t *send_buf_sz, char *req_path)  { -    char content_type[COMMON_CAP]; -    int is_binary; // 0 - no; -1 - yes; 1 - error +    int ret = 1; +    char content_type[COMMON_CAP] = {0};      FILE *fp; -    if((is_binary = try_file(req_path, &fp, content_type)) == 1) { +    if(try_file(req_path, &fp, content_type) != 0) {          if(errno == 2) { // 2 is 'No such file or directory'              SEND_BUF_ADD_LINE("404"); -            if((is_binary = try_file("/404.html", &fp, content_type)) != 0) return 1; +            if(try_file("/404.html", &fp, content_type) != 0) return ret;          } else {              SEND_BUF_ADD_LINE("500"); -            if((is_binary = try_file("/500.html", &fp, content_type)) != 0) return 1; +            if(try_file("/500.html", &fp, content_type) != 0) return ret;          }      } else {          SEND_BUF_ADD_LINE("200"); @@ -122,11 +131,31 @@ static int on_get(char *send_buf, ssize_t *send_buf_sz, char *req_path)      SEND_BUF_ADD_LINE("Server: potato");      SEND_BUF_ADD("Content-Type: ");      SEND_BUF_ADD_LINE(content_type); + +    if(fseek(fp, 0, SEEK_END) != 0) { +        err("fseek: %s", strerror(errno)); +        goto close; +    } + +    size_t file_sz = ftell(fp); +    if(file_sz == 0) { +        err("ftell: %s", strerror(errno)); +        goto close; +    } + +    rewind(fp); + +    SEND_BUF_ADD("Content-Lenght: "); +    SEND_BUF_ADD_LINE_LONG(file_sz);      SEND_BUF_ADD("\r\n"); -    LOAD_FILE(); +    fread(&send_buf[*send_buf_sz], sizeof(char), file_sz, fp); +    *send_buf_sz += file_sz; + +    ret = 0; +close:      fclose(fp); -    return 0; +    return ret;  }  static int handle_connection(sock_t *conn)  | 
