Advertisement
Guest User

Untitled

a guest
Dec 14th, 2015
162
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C 20.60 KB | None | 0 0
  1. //
  2. // server.c
  3. //
  4. // David J. Malan
  5. // malan@harvard.edu
  6. //
  7.  
  8. // feature test macro requirements
  9. #define _GNU_SOURCE
  10. #define _XOPEN_SOURCE 700
  11. #define _XOPEN_SOURCE_EXTENDED
  12.  
  13. // limits on an HTTP request's size, based on Apache's
  14. // http://httpd.apache.org/docs/2.2/mod/core.html
  15. #define LimitRequestFields 50
  16. #define LimitRequestFieldSize 4094
  17. #define LimitRequestLine 8190
  18.  
  19. // number of octets for buffered reads
  20. #define OCTETS 512
  21.  
  22. // header files
  23. #include <arpa/inet.h>
  24. #include <errno.h>
  25. #include <limits.h>
  26. #include <math.h>
  27. #include <signal.h>
  28. #include <stdbool.h>
  29. #include <stdio.h>
  30. #include <stdlib.h>
  31. #include <string.h>
  32. #include <strings.h>
  33. #include <unistd.h>
  34.  
  35. // types
  36. typedef char octet;
  37.  
  38. // prototypes
  39. bool connected(void);
  40. bool error(unsigned short code);
  41. void handler(int signal);
  42. ssize_t load(void);
  43. const char* lookup(const char* extension);
  44. ssize_t parse(void);
  45. void reset(void);
  46. void start(short port, const char* path);
  47. void stop(void);
  48.  
  49. // server's root
  50. char* root = NULL;
  51.  
  52. // file descriptor for sockets
  53. int cfd = -1, sfd = -1;
  54.  
  55. // buffer for request
  56. octet* request = NULL;
  57.  
  58. // FILE pointer for files
  59. FILE* file = NULL;
  60.  
  61. // buffer for response-body
  62. octet* body = NULL;
  63.  
  64. int main(int argc, char* argv[])
  65. {
  66.     // a global variable defined in errno.h that's "set by system
  67.     // calls and some library functions [to a nonzero value]
  68.     // in the event of an error to indicate what went wrong"
  69.     errno = 0;
  70.  
  71.     // default to a random port
  72.     int port = 0;
  73.  
  74.     // usage
  75.     const char* usage = "Usage: server [-p port] /path/to/root";
  76.  
  77.     // parse command-line arguments
  78.     int opt;
  79.     while ((opt = getopt(argc, argv, "hp:")) != -1)
  80.     {
  81.         switch (opt)
  82.         {
  83.             // -h
  84.             case 'h':
  85.                 printf("%s\n", usage);
  86.                 return 0;
  87.  
  88.             // -p port
  89.             case 'p':
  90.                 port = atoi(optarg);
  91.                 break;
  92.         }
  93.     }
  94.  
  95.     // ensure port is a non-negative short and path to server's root is specified
  96.     if (port < 0 || port > SHRT_MAX || argv[optind] == NULL || strlen(argv[optind]) == 0)
  97.     {
  98.         // announce usage
  99.         printf("%s\n", usage);
  100.  
  101.         // return 2 just like bash's builtins
  102.         return 2;
  103.     }
  104.  
  105.     // start server
  106.     start(port, argv[optind]);
  107.  
  108.     // listen for SIGINT (aka control-c)
  109.     signal(SIGINT, handler);
  110.  
  111.     // accept connections one at a time
  112.     while (true)
  113.     {
  114.         // reset server's state
  115.         reset();
  116.  
  117.         // wait until client is connected
  118.         if (connected())
  119.         {
  120.             // parse client's HTTP request
  121.             ssize_t octets = parse();
  122.             if (octets == -1)
  123.             {
  124.                 continue;
  125.             }
  126.  
  127.             // extract request's request-line
  128.             // http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html
  129.             const char* haystack = request;
  130.             char* needle = strstr(haystack, "\r\n");
  131.             if (needle == NULL)
  132.             {
  133.                 error(400);
  134.                 continue;
  135.             }
  136.             else if (needle - haystack + 2 > LimitRequestLine)
  137.             {
  138.                 error(414);
  139.                 continue;
  140.             }  
  141.             char line[needle - haystack + 2 + 1];
  142.             strncpy(line, haystack, needle - haystack + 2);
  143.             line[needle - haystack + 2] = '\0';
  144.  
  145.             // log request-line
  146.             printf("%s", line);
  147.  
  148.             // TODO: validate request-line
  149.             // if method is not GET, respond to the browser with 405 Method Not Allowed;
  150.             const char escape[2] = " ";
  151.             char *token;
  152.             token = strtok(line, escape);
  153.             if (strcmp(token, "GET") != 0)
  154.             {
  155.                 error(405);
  156.                 continue;
  157.             }
  158.            
  159.             // if request-target does not begin with /, respond to the browser with 501 Not Implemented;
  160.             token = strtok(NULL, escape);
  161.             if (token[0] != '/')
  162.             {
  163.                 error(501);
  164.                 continue;
  165.             }
  166.            
  167.             // if request-target contains a ", respond to the browser with 400 Bad Request;
  168.             if (strchr(token, '\"') != NULL)
  169.             {
  170.                 error(400);
  171.                 continue;
  172.             }
  173.            
  174.             // if absolute-path does not contain a . (and thus a file extension), respond to the browser with 501 Not Implemented.
  175.             if (strchr(token, '.') == NULL)
  176.             {
  177.                 error(501);
  178.                 continue;
  179.             }
  180.            
  181.             // if HTTP-version is not HTTP/1.1, respond to the browser with 505 HTTP Version Not Supported; or
  182.             token = strtok(NULL, escape);
  183.             if (strcmp(token, "HTTP/1.1") != 0)
  184.             {
  185.                 error(505);
  186.                 continue;
  187.             }
  188.  
  189.            
  190.  
  191.             // TODO: extract query from request-target
  192.             token = strtok(line, escape);
  193.             token = strtok(NULL, escape);
  194.             //char *tempstr;
  195.            
  196.             //ret = strchr(token, '?');
  197.  
  198.             int qlength = strlen(strchr(token, '?'));
  199.             char* tempquery = strchr(token, '?');
  200.             char query[qlength+1] ;//= NULL/*"TODO"*/;
  201.            
  202.            
  203.             if (tempquery == NULL)
  204.             {
  205.                 query[0] = '\0';    
  206.             }
  207.             else if (tempquery[1] == '\0')
  208.             {
  209.                 query[0] = '\0';                
  210.             }
  211.             else
  212.             {
  213.                 int i;
  214.                 for(i = 0; i < strlen(tempquery); i++)
  215.                 {
  216.                     query[i] = tempquery[1 + i];
  217.                 }
  218.                 query[i] = '\0';
  219.             }
  220.  
  221.  
  222.             // TODO: concatenate root and absolute-path
  223.              
  224.             int absolute_path_length = strlen(token) - strlen(strchr(token, '?'));
  225.             char absolute_path[absolute_path_length+1];
  226.             strncpy(absolute_path, token, absolute_path_length);
  227.             char path[strlen(root) + absolute_path_length + 1];
  228.             strcpy(path, root);
  229.             strcat(path, absolute_path);
  230.  
  231.             // TODO: ensure path exists
  232.             if (access(path, F_OK) == -1)
  233.             {
  234.                 error(404);
  235.                 continue;
  236.             }
  237.            
  238.            
  239.             // TODO: ensure path is readable
  240.             if (access(path, R_OK) == -1)
  241.             {
  242.                 error(403);
  243.                 continue;
  244.             }
  245.            
  246.  
  247.             // TODO: extract path's extension
  248.            
  249.             char extension[strlen(strrchr(absolute_path, '.'))+ 1];
  250.             strncpy(extension, strrchr(absolute_path, '.'), strlen(strrchr(absolute_path, '.')));
  251.  
  252.  
  253.  
  254.             // dynamic content
  255.             if (strcasecmp("php", extension) == 0)
  256.             {
  257.                 // open pipe to PHP interpreter
  258.                 char* format = "QUERY_STRING=\"%s\" REDIRECT_STATUS=200 SCRIPT_FILENAME=\"%s\" php-cgi";
  259.                 char command[strlen(format) + (strlen(path) - 2) + (strlen(query) - 2) + 1];
  260.                 sprintf(command, format, query, path);
  261.                 file = popen(command, "r");
  262.                 if (file == NULL)
  263.                 {
  264.                     error(500);
  265.                     continue;
  266.                 }
  267.  
  268.                 // load file
  269.                 ssize_t size = load();
  270.                 if (size == -1)
  271.                 {
  272.                     error(500);
  273.                     continue;
  274.                 }
  275.  
  276.                 // subtract php-cgi's headers from body's size to get content's length
  277.                 haystack = body;
  278.                 needle = memmem(haystack, size, "\r\n\r\n", 4);
  279.                 if (needle == NULL)
  280.                 {
  281.                     error(500);
  282.                     continue;
  283.                 }
  284.                 size_t length = size - (needle - haystack + 4);
  285.  
  286.                 // respond to client
  287.                 if (dprintf(cfd, "HTTP/1.1 200 OK\r\n") < 0)
  288.                 {
  289.                     continue;
  290.                 }
  291.                 if (dprintf(cfd, "Connection: close\r\n") < 0)
  292.                 {
  293.                     continue;
  294.                 }
  295.                 if (dprintf(cfd, "Content-Length: %i\r\n", length) < 0)
  296.                 {
  297.                     continue;
  298.                 }
  299.                 if (write(cfd, body, size) == -1)
  300.                 {
  301.                     continue;
  302.                 }
  303.             }
  304.  
  305.             // static content
  306.             else
  307.             {
  308.                 // look up file's MIME type
  309.                 const char* type = lookup(extension);
  310.                 if (type == NULL)
  311.                 {
  312.                     error(501);
  313.                     continue;
  314.                 }
  315.  
  316.                 // open file
  317.                 file = fopen(path, "r");
  318.                 if (file == NULL)
  319.                 {
  320.                     error(500);
  321.                     continue;
  322.                 }
  323.  
  324.                 // load file
  325.                 ssize_t length = load();
  326.                 if (length == -1)
  327.                 {
  328.                     error(500);
  329.                     continue;
  330.                 }
  331.  
  332.                 // TODO: respond to client
  333.                 // respond to client
  334.                 if (dprintf(cfd, "HTTP/1.1 200 OK\r\n") < 0)
  335.                 {
  336.                     continue;
  337.                 }
  338.                 if (dprintf(cfd, "Connection: close\r\n") < 0)
  339.                 {
  340.                     continue;
  341.                 }
  342.                 if (dprintf(cfd, "Content-Length: %i\r\n", length) < 0)
  343.                 {
  344.                     continue;
  345.                 }
  346.                 if (dprintf(cfd, "Content-Type: %s\r\n", type) < 0)
  347.                 {
  348.                     continue;
  349.                 }
  350.                 if (write(cfd, body, length) == -1)
  351.                 {
  352.                     continue;
  353.                 }
  354.                
  355.             }
  356.            
  357.             // announce OK
  358.             printf("\033[32m");
  359.             printf("HTTP/1.1 200 OK");
  360.             printf("\033[39m\n");
  361.         }
  362.     }
  363. }
  364.  
  365. /**
  366.  * Accepts a connection from a client, blocking (i.e., waiting) until one is heard.
  367.  * Upon success, returns true; upon failure, returns false.
  368.  */
  369. bool connected(void)
  370. {
  371.     struct sockaddr_in cli_addr;
  372.     memset(&cli_addr, 0, sizeof(cli_addr));
  373.     socklen_t cli_len = sizeof(cli_addr);
  374.     cfd = accept(sfd, (struct sockaddr*) &cli_addr, &cli_len);
  375.     if (cfd == -1)
  376.     {
  377.         return false;
  378.     }
  379.     return true;
  380. }
  381.  
  382. /**
  383.  * Handles client errors (4xx) and server errors (5xx).
  384.  */
  385. bool error(unsigned short code)
  386. {
  387.     // ensure client's socket is open
  388.     if (cfd == -1)
  389.     {
  390.         return false;
  391.     }
  392.  
  393.     // ensure code is within range
  394.     if (code < 400 || code > 599)
  395.     {
  396.         return false;
  397.     }
  398.  
  399.     // determine Status-Line's phrase
  400.     // http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1
  401.     const char* phrase = NULL;
  402.     switch (code)
  403.     {
  404.         case 400: phrase = "Bad Request"; break;
  405.         case 403: phrase = "Forbidden"; break;
  406.         case 404: phrase = "Not Found"; break;
  407.         case 405: phrase = "Method Not Allowed"; break;
  408.         case 413: phrase = "Request Entity Too Large"; break;
  409.         case 414: phrase = "Request-URI Too Long"; break;
  410.         case 418: phrase = "I'm a teapot"; break;
  411.         case 500: phrase = "Internal Server Error"; break;
  412.         case 501: phrase = "Not Implemented"; break;
  413.         case 505: phrase = "HTTP Version Not Supported"; break;
  414.     }
  415.     if (phrase == NULL)
  416.     {
  417.         return false;
  418.     }
  419.  
  420.     // template
  421.     char* template = "<html><head><title>%i %s</title></head><body><h1>%i %s</h1></body></html>";
  422.     char content[strlen(template) + 2 * ((int) log10(code) + 1 - 2) + 2 * (strlen(phrase) - 2) + 1];
  423.     int length = sprintf(content, template, code, phrase, code, phrase);
  424.  
  425.     // respond with Status-Line
  426.     if (dprintf(cfd, "HTTP/1.1 %i %s\r\n", code, phrase) < 0)
  427.     {
  428.         return false;
  429.     }
  430.  
  431.     // respond with Connection header
  432.     if (dprintf(cfd, "Connection: close\r\n") < 0)
  433.     {
  434.         return false;
  435.     }
  436.  
  437.     // respond with Content-Length header
  438.     if (dprintf(cfd, "Content-Length: %i\r\n", length) < 0)
  439.     {
  440.         return false;
  441.     }
  442.  
  443.     // respond with Content-Type header
  444.     if (dprintf(cfd, "Content-Type: text/html\r\n") < 0)
  445.     {
  446.         return false;
  447.     }
  448.  
  449.     // respond with CRLF
  450.     if (dprintf(cfd, "\r\n") < 0)
  451.     {
  452.         return false;
  453.     }
  454.  
  455.     // respond with message-body
  456.     if (write(cfd, content, length) == -1)
  457.     {
  458.         return false;
  459.     }
  460.  
  461.     // announce Response-Line
  462.     printf("\033[31m");
  463.     printf("HTTP/1.1 %i %s", code, phrase);
  464.     printf("\033[39m\n");
  465.  
  466.     return true;
  467. }
  468.  
  469. /**
  470.  * Loads file into message-body.
  471.  */
  472. ssize_t load(void)
  473. {
  474.     // ensure file is open
  475.     if (file == NULL)
  476.     {
  477.         return -1;
  478.     }
  479.  
  480.     // ensure body isn't already loaded
  481.     if (body != NULL)
  482.     {
  483.         return -1;
  484.     }
  485.  
  486.     // buffer for octets
  487.     octet buffer[OCTETS];
  488.  
  489.     // read file
  490.     ssize_t size = 0;
  491.     while (true)
  492.     {
  493.         // try to read a buffer's worth of octets
  494.         ssize_t octets = fread(buffer, sizeof(octet), OCTETS, file);
  495.  
  496.         // check for error
  497.         if (ferror(file) != 0)
  498.         {
  499.             if (body != NULL)
  500.             {
  501.                 free(body);
  502.                 body = NULL;
  503.             }
  504.             return -1;
  505.         }
  506.  
  507.         // if octets were read, append to body
  508.         if (octets > 0)
  509.         {
  510.             body = realloc(body, size + octets);
  511.             if (body == NULL)
  512.             {
  513.                 return -1;
  514.             }
  515.             memcpy(body + size, buffer, octets);
  516.             size += octets;
  517.         }
  518.  
  519.         // check for EOF
  520.         if (feof(file) != 0)
  521.         {
  522.             break;
  523.         }
  524.     }
  525.     return size;
  526. }
  527.  
  528. /**
  529.  * Handles signals.
  530.  */
  531. void handler(int signal)
  532. {
  533.     // control-c
  534.     if (signal == SIGINT)
  535.     {
  536.         // ensure this isn't considered an error
  537.         // (as might otherwise happen after a recent 404)
  538.         errno = 0;
  539.  
  540.         // announce stop
  541.         printf("\033[33m");
  542.         printf("Stopping server\n");
  543.         printf("\033[39m");
  544.  
  545.         // stop server
  546.         stop();
  547.     }
  548. }
  549.  
  550. /**
  551.  * Returns MIME type for supported extensions, else NULL.
  552.  */
  553. const char* lookup(const char* extension)
  554. {
  555.     // TODO
  556.     if (extension != NULL)
  557.     {
  558.         // return text/css if extension is css (or any capitalization thereof),
  559.         if (strcasecmp(extension, "css") == 0)
  560.         {
  561.             return "text/css";
  562.         }
  563.         // return text/html if extension is html (or any capitalization thereof),
  564.         else if (strcasecmp(extension, "html") == 0)
  565.         {
  566.             return "text/html";
  567.         }
  568.         // return image/gif if extension is gif (or any capitalization thereof),
  569.         else if (strcasecmp(extension, "gif") == 0)
  570.         {
  571.             return "image/gif";
  572.         }
  573.         // return image/x-icon if extension is ico (or any capitalization thereof),
  574.         else if (strcasecmp(extension, "ico") == 0)
  575.         {
  576.             return "image/x-icon";
  577.         }
  578.         // return image/jpeg (not image/jpg) if extension is jpg (or any capitalization thereof),
  579.         else if (strcasecmp(extension, "jpg") == 0)
  580.         {
  581.             return "image/jpeg";
  582.         }
  583.         // return text/javascript if extension is js (or any capitalization thereof),
  584.         else if (strcasecmp(extension, "js") == 0)
  585.         {
  586.             return "text/javascript";
  587.         }
  588.         // return image/png if extension is png (or any capitalization thereof), or
  589.         else if (strcasecmp(extension, "png") == 0)
  590.         {
  591.             return "image/png";
  592.         }
  593.     }
  594.     return NULL;    
  595. }
  596.  
  597. /**
  598.  * Parses an HTTP request.
  599.  */
  600. ssize_t parse(void)
  601. {
  602.     // ensure client's socket is open
  603.     if (cfd == -1)
  604.     {
  605.         return -1;
  606.     }
  607.  
  608.     // ensure request isn't already parsed
  609.     if (request != NULL)
  610.     {
  611.         return -1;
  612.     }
  613.  
  614.     // buffer for octets
  615.     octet buffer[OCTETS];
  616.  
  617.     // parse request
  618.     ssize_t length = 0;
  619.     while (true)
  620.     {
  621.         // read from socket
  622.         ssize_t octets = read(cfd, buffer, sizeof(octet) * OCTETS);
  623.         if (octets == -1)
  624.         {
  625.             error(500);
  626.             return -1;
  627.         }
  628.  
  629.         // if octets have been read, remember new length
  630.         if (octets > 0)
  631.         {
  632.             request = realloc(request, length + octets);
  633.             if (request == NULL)
  634.             {
  635.                 return -1;
  636.             }
  637.             memcpy(request + length, buffer, octets);
  638.             length += octets;
  639.         }
  640.  
  641.         // else if nothing's been read, socket's been closed
  642.         else
  643.         {
  644.             return -1;
  645.         }
  646.  
  647.         // search for CRLF CRLF
  648.         int offset = (length - octets < 3) ? length - octets : 3;
  649.         char* haystack = request + length - octets - offset;
  650.         char* needle = memmem(haystack, request + length - haystack, "\r\n\r\n", 4);
  651.         if (needle != NULL)
  652.         {
  653.             // trim to one CRLF and null-terminate
  654.             length = needle - request + 2 + 1;
  655.             request = realloc(request, length);
  656.             if (request == NULL)
  657.             {
  658.                 return -1;
  659.             }
  660.             request[length - 1] = '\0';
  661.             break;
  662.         }
  663.  
  664.         // if buffer's full and we still haven't found CRLF CRLF,
  665.         // then request is too large
  666.         if (length - 1 >= LimitRequestLine + LimitRequestFields * LimitRequestFieldSize)
  667.         {
  668.             error(413);
  669.             return -1;
  670.         }
  671.     }
  672.     return length;
  673. }
  674.  
  675. /**
  676.  * Resets server's state, deallocating any resources.
  677.  */
  678. void reset(void)
  679. {
  680.     // free response's body
  681.     if (body != NULL)
  682.     {
  683.         free(body);
  684.         body = NULL;
  685.     }
  686.  
  687.     // close file
  688.     if (file != NULL)
  689.     {
  690.         fclose(file);
  691.         file = NULL;
  692.     }
  693.  
  694.     // free request
  695.     if (request != NULL)
  696.     {
  697.         free(request);
  698.         request = NULL;
  699.     }
  700.  
  701.     // close client's socket
  702.     if (cfd != -1)
  703.     {
  704.         close(cfd);
  705.         cfd = -1;
  706.     }
  707. }
  708.  
  709. /**
  710.  * Starts server.
  711.  */
  712. void start(short port, const char* path)
  713. {
  714.     // path to server's root
  715.     root = realpath(path, NULL);
  716.     if (root == NULL)
  717.     {
  718.         stop();
  719.     }
  720.  
  721.     // ensure root exists
  722.     if (access(root, F_OK) == -1)
  723.     {
  724.         stop();
  725.     }
  726.  
  727.     // ensure root is executable
  728.     if (access(root, X_OK) == -1)
  729.     {
  730.         stop();
  731.     }
  732.  
  733.     // announce root
  734.     printf("\033[33m");
  735.     printf("Using %s for server's root", root);
  736.     printf("\033[39m\n");
  737.  
  738.     // create a socket
  739.     sfd = socket(AF_INET, SOCK_STREAM, 0);
  740.     if (sfd == -1)
  741.     {
  742.         stop();
  743.     }
  744.  
  745.     // allow reuse of address (to avoid "Address already in use")
  746.     int optval = 1;
  747.     setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
  748.  
  749.     // assign name to socket
  750.     struct sockaddr_in serv_addr;
  751.     memset(&serv_addr, 0, sizeof(serv_addr));
  752.     serv_addr.sin_family = AF_INET;
  753.     serv_addr.sin_port = htons(port);
  754.     serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  755.     if (bind(sfd, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1)
  756.     {
  757.         stop();
  758.     }
  759.  
  760.     // listen for connections
  761.     if (listen(sfd, SOMAXCONN) == -1)
  762.     {
  763.         stop();
  764.     }
  765.  
  766.     // announce port in use
  767.     struct sockaddr_in addr;
  768.     socklen_t addrlen = sizeof(addr);
  769.     if (getsockname(sfd, (struct sockaddr*) &addr, &addrlen) == -1)
  770.     {
  771.         stop();
  772.     }
  773.     printf("\033[33m");
  774.     printf("Listening on port %i", ntohs(addr.sin_port));
  775.     printf("\033[39m\n");
  776. }
  777.  
  778. /**
  779.  * Stop server, deallocating any resources.
  780.  */
  781. void stop(void)
  782. {
  783.     // preserve errno across this function's library calls
  784.     int errsv = errno;
  785.  
  786.     // reset server's state
  787.     reset();
  788.  
  789.     // free root, which was allocated by realpath
  790.     if (root != NULL)
  791.     {
  792.         free(root);
  793.     }
  794.  
  795.     // close server socket
  796.     if (sfd != -1)
  797.     {
  798.         close(sfd);
  799.     }
  800.  
  801.     // terminate process
  802.     if (errsv == 0)
  803.     {
  804.         // success
  805.         exit(0);
  806.     }
  807.     else
  808.     {
  809.         // announce error
  810.         printf("\033[33m");
  811.         printf("%s", strerror(errsv));
  812.         printf("\033[39m\n");
  813.  
  814.         // failure
  815.         exit(1);
  816.     }
  817. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement