server.c

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

Reply to "server.c"

Here you can reply to the paste above

captcha