LinuxQuestions.org
Share your knowledge at the LQ Wiki.
Go Back   LinuxQuestions.org > Forums > Non-*NIX Forums > Programming
User Name
Password
Programming This forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.

Notices


Reply
  Search this Thread
Old 03-31-2016, 05:21 PM   #1
KarenWest
Member
 
Registered: Jun 2011
Location: North Easton,MA
Distribution: currently Ubuntu Linux on my netbook while job searching, but also use Cygwin GNU tools on MSXP nb
Posts: 33

Rep: Reputation: Disabled
Web Server in C - having trouble writing back to client browser from server code


A web server in C - most of which is from an online course, except:
The functions I wrote in this code are: lookup(), load(), indexes() and parse(). Everything seems fine up until the code they wrote is called (the response() function that sends the file content allocated and loaded in the load() function is attempted to send to the client browser. There are no errors returned from write(), but zero bytes are written to the client browser from the server, yet you can see that they are allocated (realloc()) and can print them out in the load() function. I am including all the code given for context, but the only code I wrote was in 4 small functions here: load(), parse(), indexes() and lookup().

To run this code after compiling, I do:
./server public

The public directory contains a test directory with an index.html file in it.
After starting the server on my Suse Linux machine, I have my browser (currently
using Chrome) send this command to the server:
http://localhost:8080/home/karen/dev...est/index.html

Code:
// limits on an HTTP request's size, based on Apache's
// http://httpd.apache.org/docs/2.2/mod/core.html
#define LimitRequestFields 50
#define LimitRequestFieldSize 4094
#define LimitRequestLine 8190

char pathChars[LimitRequestLine + 1];
char pathCopy[LimitRequestLine + 1];

// number of bytes for buffers
#define BYTES 512

int main(int argc, char* argv[])
{
    // parse command-line arguments
    // ensure port is a non-negative short and path to server's root is specified

    //start server
    start(port, argv[optind]);

    // listen for SIGINT (aka control-c)
    struct sigaction act;
    act.sa_handler = handler;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    sigaction(SIGINT, &act, NULL);

    // a message and its length
    char* message = NULL;
    size_t length = 0;

    // path requested
    char* path = NULL;

    // accept connections one at a time
    while (true)
    {
        // free last path, if any
        if (path != NULL)
        {
            free(path);
            path = NULL;
        }

        // free last message, if any
        if (message != NULL)
        {
            free(message);
            message = NULL;
        }
        length = 0;

        // close last client's socket, if any
        if (cfd != -1)
        {
            close(cfd);
            cfd = -1;
        }

        // check for control-c
        if (signaled)
        {
            stop();
        }
        // check whether client has connected
        if (connected())
        {
            // check for request
            if (request(&message, &length))
            {
                // extract message's request-line
                // http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html
                const char* haystack = message;
                const char* needle = strstr(haystack, "\r\n");
                if (needle == NULL)
                {
                    error(500);
                    continue;
                }
                char line[needle - haystack + 2 + 1];
                strncpy(line, haystack, needle - haystack + 2);
                line[needle - haystack + 2] = '\0';

                // log request-line
                printf("%s", line);

                // parse request-line
                char abs_path[LimitRequestLine + 1];
                char query[LimitRequestLine + 1];
                if (parse(line, abs_path, query))
                {
                    // URL-decode absolute-path
                    char* p = urldecode(abs_path);
                    if (p == NULL)
                    {
                        error(500);
                        continue;
                    }

                    // resolve absolute-path to local path
                    path = malloc(strlen(root) + strlen(p) + 1);
                    if (path == NULL)
                    {
                        error(500);
                        continue;
                    }
                    strcpy(path, root);
                    strcat(path, p);
                    free(p);

                    // ensure path exists
                    if (access(path, F_OK) == -1)
                    {
                        error(404);
                        continue;
                    }

                    // if path to directory
                    struct stat sb;
                    if (stat(path, &sb) == 0 && S_ISDIR(sb.st_mode))
                    {
                        // redirect from absolute-path to absolute-path/
                        if (abs_path[strlen(abs_path) - 1] != '/')
                        {
                            char uri[strlen(abs_path) + 1 + 1];
                            strcpy(uri, abs_path);
                            strcat(uri, "/");
                            redirect(uri);
                            continue;
                        }

                        // use path/index.php or path/index.html, if present, instead of directory's path
			printf("calling indexes with path = %s\n", path);
                        char* index = indexes(path);
                        if (index != NULL)
                        {
                            free(path);
                            path = index;
                        }
			
                        // list contents of directory
                        else
                        {
			  printf("calling list with path = %s\n", path);
                          list(path);
                          continue;
                        }
			printf("index = %s\n", index);
                    }

                    // look up MIME type for file at path
		    printf("calling lookup on path = %s\n", path);
                    const char* type = lookup(path);
                    if (type == NULL)
                    {
                        error(501);
                        continue;
                    }
		    printf("type = %s\n", type);
                    // interpret PHP script at path
                    if (strcasecmp("text/x-php", type) == 0)
                    {
		      printf("interpret being called with path = %s and query = %s\n", path, query);
                      interpret(path, query);
                    }

                    // transfer file at path
                    else
                    {
		      printf("transfer being called with path = %s and query = %s\n", path, type);
                      transfer(path, type);
                    }
                }
            }
        }
    }
}

bool connected(void)
{
    struct sockaddr_in cli_addr;
    memset(&cli_addr, 0, sizeof(cli_addr));
    socklen_t cli_len = sizeof(cli_addr);
    cfd = accept(sfd, (struct sockaddr*) &cli_addr, &cli_len);
    
    if (cfd == -1)
    {
        return false;
    }
    return true;
}

void error(unsigned short code)
{
    // determine code's reason-phrase
    const char* phrase = reason(code);
    if (phrase == NULL)
    {
        return;
    }

    // template for response's content
    char* template = "<html><head><title>%i %s</title></head><body><h1>%i %s</h1></body></html>";

    // render template
    char body[(strlen(template) - 2 - ((int) log10(code) + 1) - 2 + strlen(phrase)) * 2 + 1];
    int length = sprintf(body, template, code, phrase, code, phrase);
    if (length < 0)
    {
        body[0] = '\0';
        length = 0;
    }

    // respond with error
    char* headers = "Content-Type: text/html\r\n";
    respond(code, headers, body, length);
}

void freedir(struct dirent** namelist, int n)
{
    if (namelist != NULL)
    {
        for (int i = 0; i < n; i++)
        {
            free(namelist[i]);
        }
        free(namelist);
    }
}
 
void handler(int signal)
{
    // control-c
    if (signal == SIGINT)
    {
        signaled = true;
    }
}

/**
 * Escapes string for HTML. Returns dynamically allocated memory for escaped
 * string that must be deallocated by caller.
 */
char* htmlspecialchars(const char* s)
{
    // ensure s is not NULL
    if (s == NULL)
    {
        return NULL;
    }

    // allocate enough space for an unescaped copy of s
    char* t = malloc(strlen(s) + 1);
    if (t == NULL)
    {
        return NULL;
    }
    t[0] = '\0';

    // iterate over characters in s, escaping as needed
    for (int i = 0, old = strlen(s), new = old; i < old; i++)
    {
        // escape &
        if (s[i] == '&')
        {
            const char* entity = "&amp;";
            new += strlen(entity);
            t = realloc(t, new);
            if (t == NULL)
            {
                return NULL;
            }
            strcat(t, entity);
        }

        // escape "
        else if (s[i] == '"')
        {
            const char* entity = "&quot;";
            new += strlen(entity);
            t = realloc(t, new);
            if (t == NULL)
            {
                return NULL;
            }
            strcat(t, entity);
        }

        // escape '
        else if (s[i] == '\'')
        {
            const char* entity = "'";
            new += strlen(entity);
            t = realloc(t, new);
            if (t == NULL)
            {
                return NULL;
            }
            strcat(t, entity);
        }

        // escape <
        else if (s[i] == '<')
        {
            const char* entity = "&lt;";
            new += strlen(entity);
            t = realloc(t, new);
            if (t == NULL)
            {
                return NULL;
            }
            strcat(t, entity);
        }

        // escape >
        else if (s[i] == '>')
        {
            const char* entity = "&gt;";
            new += strlen(entity);
            t = realloc(t, new);
            if (t == NULL)
            {
                return NULL;
            }
            strcat(t, entity);
        }

        // don't escape
        else
        {
            strncat(t, s + i, 1);
        }
    }

    // escaped string
    return t;
}

char* indexes(const char* path)
{
  char *returnPath = NULL;
  int pathLen = 0;
  int len = 0;
  

    // TODO
  pathLen = strlen(path);
  if (strstr(path, "index.php"))
    {
      len = pathLen + 9 + 2; //add space for "/index.php" plus null
      returnPath = (char *) calloc(1, len);
      strncpy(returnPath, path, pathLen); //copy initial path to returnPath 
      strcat(returnPath, "/index.php"); 
    }
  else if (strstr(path, "index.html"))
    {
      len = pathLen + 10 + 2; //add space for "/index.html" plus null
      returnPath = (char *) calloc(1, len);
      strncpy(returnPath, path, pathLen); //copy initial path to returnPath 
      strcat(returnPath, "/index.html"); 
    }

  return returnPath;
}

void interpret(const char* path, const char* query)
{
    // ensure path is readable
    if (access(path, R_OK) == -1)
    {
        error(403);
        return;
    }

    // open pipe to PHP interpreter
    char* format = "QUERY_STRING="%s" REDIRECT_STATUS=200 SCRIPT_FILENAME="%s" php-cgi";
    char command[strlen(format) + (strlen(path) - 2) + (strlen(query) - 2) + 1];
    if (sprintf(command, format, query, path) < 0)
    {
        error(500);
        return;
    }
    FILE* file = popen(command, "r");
    if (file == NULL)
    {
        error(500);
        return;
    }

    // load interpreter's content
    char* content;
    size_t length;
    if (load(file, &content, &length) == false)
    {
        error(500);
        return;
    }

    // close pipe
    pclose(file);

    // subtract php-cgi's headers from content's length to get body's length
    char* haystack = content;
    char* needle = strstr(haystack, "\r\n\r\n");
    if (needle == NULL)
    {
        free(content);
        error(500);
        return;
    }
    // extract headers
    char headers[needle + 2 - haystack + 1];
    strncpy(headers, content, needle + 2 - haystack);
    headers[needle + 2 - haystack] = '\0';
    // respond with interpreter's content
    respond(200, headers, needle + 4, length - (needle - haystack + 4));

    // free interpreter's content
    free(content);
}

void list(const char* path)
{
    // ensure path is readable and executable
    if (access(path, R_OK | X_OK) == -1)
    {
        error(403);
        return;
    }

    // open directory
    DIR* dir = opendir(path);
    if (dir == NULL)
    {
        return;
    }

    // buffer for list items
    char* list = malloc(1);
    list[0] = '\0';

    // iterate over directory entries
    struct dirent** namelist = NULL;
    int n = scandir(path, &namelist, NULL, alphasort);
    for (int i = 0; i < n; i++)
    {
        // omit . from list
        if (strcmp(namelist[i]->d_name, ".") == 0)
        {
            continue;
        }

        // escape entry's name
        char* name = htmlspecialchars(namelist[i]->d_name);
        if (name == NULL)
        {
            free(list);
            freedir(namelist, n);
            error(500);
            return;
        }

        // append list item to buffer
        char* template = "<li><a href="%s">%s</a></li>";
        list = realloc(list, strlen(list) + strlen(template) - 2 + strlen(name) - 2 + strlen(name) + 1);
        if (list == NULL)
        {
            free(name);
            freedir(namelist, n);
            error(500);
            return;
        }
        if (sprintf(list + strlen(list), template, name, name) < 0)
        {
            free(name);
            freedir(namelist, n);
            free(list);
            error(500);
            return;
        }

        // free escaped name
        free(name);
    }

    // free memory allocated by scandir
    freedir(namelist, n);

    // prepare response
    const char* relative = path + strlen(root);
    char* template = "<html><head><title>%s</title></head><body><h1>%s</h1><ul>%s</ul></body></html>";
    char body[strlen(template) - 2 + strlen(relative) - 2 + strlen(relative) - 2 + strlen(list) + 1];
    int length = sprintf(body, template, relative, relative, list);
    if (length < 0)
    {
        free(list);
        closedir(dir);
        error(500);
        return;
    }

    // free buffer
    free(list);

    // close directory
    closedir(dir);

    // respond with list
    char* headers = "Content-Type: text/html\r\n";
    respond(200, headers, body, length);
}

bool load(FILE* file, BYTE** content, size_t* length)
{
  long filesize = 0;
  long numBuffers = 0; 
  long numBytesToRead = 0;
  bool returnValue = false;
  long numBytesRead = 0;
  long numBytesLastBuf = 0;
  long saveNumBufs = 0;
  BYTE *contentPtr = NULL;

  // TODO
  *length = 0;
    if (file != NULL)
    {
      fseek (file , 0 , SEEK_END);
      filesize = ftell (file);
      rewind (file);
      if (filesize > READMEGABYTEFROMFILE)
	{
	  numBuffers = filesize / READMEGABYTEFROMFILE;
	  numBytesToRead = READMEGABYTEFROMFILE;
	  if(filesize%READMEGABYTEFROMFILE > 0)
	    {
	      numBuffers++;
	      numBytesLastBuf = filesize%READMEGABYTEFROMFILE;
	    }
    	}
      else 
	{
	  numBuffers = 1;
	  numBytesToRead = filesize;
	}
      saveNumBufs = numBuffers;
      while (numBuffers > 0)
	{
	  if ((numBuffers == 1) && (saveNumBufs > 1))
	    {
	      numBytesToRead = numBytesLastBuf;
	    }
	  *content = realloc(*content, numBytesToRead);
	  if (*content == NULL)
	    {
	      returnValue = false;
	      break;
	    }
	  else
	    returnValue = true;

	  numBytesRead = fread (*content,1, numBytesToRead,file);
	  contentPtr = *content;
	  if ( (numBytesRead == 0) || (numBytesRead < numBytesToRead) )
	    {
	      if (ferror(file))
		{
		  returnValue = false;
		  break;
		}
	      if (feof(file))
		{
		  returnValue = true;
		}
	    }
	  *length += numBytesRead;
	  numBuffers--;
	}
    }
    else
      {
	returnValue = false;
      }
    
    return returnValue;
}

const char* lookup(const char* path)
{
  char *pathToPeriod;
  char *lastPeriodInPath;
  
    // TODO
  strcpy(pathCopy, path);
  pathToPeriod = pathCopy;
  pathToPeriod = strtok(pathToPeriod, ".");
  while(pathToPeriod != NULL)
    {
      pathToPeriod = strtok(NULL, ".");
      if (pathToPeriod != NULL)
	lastPeriodInPath = pathToPeriod;
    }
  if (!strcasecmp(lastPeriodInPath, "css"))
    return "text/css";
  else if (!strcasecmp(lastPeriodInPath, "html"))
    return "text/html";
  else if (!strcasecmp(lastPeriodInPath, "gif"))
    return "image/gif";
  else if (!strcasecmp(lastPeriodInPath, "ico"))
    return "image/x-icon";
  else if (!strcasecmp(lastPeriodInPath, "jpg"))
    return "image/jpeg";
  else if (!strcasecmp(lastPeriodInPath, "js"))
    return "text/javascript";
  else if (!strcasecmp(lastPeriodInPath, "php"))
    return "text/x-php";
  else if (!strcasecmp(lastPeriodInPath, "png"))
    return "image/png";

  return NULL;
}

bool parse(const char* line, char* abs_path, char* query)
{
    // TODO
  char *queryPtr = NULL;
  int countAbsPath = 0;
  int countQuery = 0;
  char *ptrMethod = NULL;
  char *ptrReqTarget = NULL;
  char *ptrDblQuoteInReqTarget = NULL;
  char *ptrHTTPversion = NULL;
  char *ptrVersion = NULL;
  char *ptrIsThereAqForQuery = NULL;
  int len;
  
  ptrMethod = strstr(line, "GET");
  if (ptrMethod == NULL) 
  {
    error(405);
    return false;
  }
  while (*ptrMethod != ' ') //point to space past GET command
    ptrMethod++;
  ptrMethod++; //should now be pointing to request-target
  ptrReqTarget = ptrMethod;
  if (*ptrReqTarget != '/')
  {
    error(501);
    return false;
  }
  else
    countAbsPath++;
  ptrReqTarget++;
  while (*ptrReqTarget != ' ')
    {
      ptrReqTarget++;
      countAbsPath++;
    }
  strcpy(pathChars, ptrMethod);
  pathChars[countAbsPath] = NULL;
  ptrMethod = &pathChars[0];
  strcpy(abs_path, pathChars);

  ptrIsThereAqForQuery = ptrReqTarget;
  if (strstr(line, "?q"))
    {
      while (*ptrIsThereAqForQuery != '?') //point to end start of query
	{
	  ptrIsThereAqForQuery++;
	}
      ptrIsThereAqForQuery += 3; //should now be pointing just past "?q=" and at start of query string
      ptrDblQuoteInReqTarget = strchr(line, '"');
      if (ptrDblQuoteInReqTarget != NULL)
	{
	  error(400);
	  return false;
	}
      queryPtr = ptrIsThereAqForQuery; 
      while (*ptrIsThereAqForQuery != ' ') //point to end of query
	{
	  ptrIsThereAqForQuery++;
	  countQuery++;
	}
	strncpy(query, queryPtr, countQuery);
	query[countQuery + 1] = NULL;
    }
  else //no query found
    { //even if there is a ?, query = NULL
      query = "";
    }
  ptrIsThereAqForQuery++;
 
  ptrHTTPversion = ptrIsThereAqForQuery;
  if (strncmp(ptrHTTPversion, "HTTP/1.1", 8))
    {
      error(505);
      return false;
    }
  else
    printf("HTTP/1.1 found - correct version for now\n");

  return true;  
  
}

/**
 * Returns status code's reason phrase.
 *
 * http://www.w3.org/Protocols/rfc2616/...sec6.html#sec6
 * https://tools.ietf.org/html/rfc2324
 */
const char* reason(unsigned short code)
{
    switch (code)
    {
        case 200: return "OK";
        case 301: return "Moved Permanently";
        case 400: return "Bad Request";
        case 403: return "Forbidden";
        case 404: return "Not Found";
        case 405: return "Method Not Allowed";
        case 414: return "Request-URI Too Long";
        case 418: return "I'm a teapot";
        case 500: return "Internal Server Error";
        case 501: return "Not Implemented";
        case 505: return "HTTP Version Not Supported";
        default: return NULL;
    }
}

/**
 * Redirects client to uri.
 */
void redirect(const char* uri)
{
    char* template = "Location: %s\r\n";
    char headers[strlen(template) - 2 + strlen(uri) + 1];
    if (sprintf(headers, template, uri) < 0)
    {
        error(500);
        return;
    }
    respond(301, headers, NULL, 0);
}

/**
 * Reads (without blocking) an HTTP request's headers into memory dynamically allocated on heap.
 * Stores address thereof in *message and length thereof in *length.
 */
bool request(char** message, size_t* length)
{
    // ensure socket is open
    if (cfd == -1)
    {
        return false;
    }

    // initialize message and its length
    *message = NULL;
    *length = 0;
    
    printf("request: reading message\n");
    // read message 
    while (*length < LimitRequestLine + LimitRequestFields * LimitRequestFieldSize + 4)
    {
        // read from socket
        BYTE buffer[BYTES];
        ssize_t bytes = read(cfd, buffer, BYTES);
        if (bytes < 0)
        {
            if (*message != NULL)
            {
                free(*message);
                *message = NULL;
            }
            *length = 0;
            break;
        }

        // append bytes to message 
        *message = realloc(*message, *length + bytes + 1);
        if (*message == NULL)
        {
            *length = 0;
            break;
        }
        memcpy(*message + *length, buffer, bytes);
        *length += bytes;

        // null-terminate message thus far
        *(*message + *length) = '\0';

        // search for CRLF CRLF
        int offset = (*length - bytes < 3) ? *length - bytes : 3;
        char* haystack = *message + *length - bytes - offset;
        char* needle = strstr(haystack, "\r\n\r\n");
        if (needle != NULL)
        {
            // trim to one CRLF and null-terminate
            *length = needle - *message + 2;
            *message = realloc(*message, *length + 1);
            if (*message == NULL)
            {
                break;
            }
            *(*message + *length) = '\0';

            // ensure request-line is no longer than LimitRequestLine
            haystack = *message;
            needle = strstr(haystack, "\r\n");
            if (needle == NULL || (needle - haystack + 2) > LimitRequestLine)
            {
                break;
            }

            // count fields in message
            int fields = 0;
            haystack = needle + 2;
            while (*haystack != '\0')
            {
                // look for CRLF
                needle = strstr(haystack, "\r\n");
                if (needle == NULL)
                {
                    break;
                }

                // ensure field is no longer than LimitRequestFieldSize
                if (needle - haystack + 2 > LimitRequestFieldSize)
                {
                    break;
                }

                // look beyond CRLF
                haystack = needle + 2;
            }

            // if we didn't get to end of message, we must have erred
            if (*haystack != '\0')
            {
                break;
            }

            // ensure message has no more than LimitRequestFields
            if (fields > LimitRequestFields)
            {
                break;
            }

            // valid
            return true;
        }
    }

    // invalid
    if (*message != NULL)
    {
        free(*message);
    }
    *message = NULL;
    *length = 0;
    return false;
}

/**
 * Responds to a client with status code, headers, and body of specified length.
 */
void respond(int code, const char* headers, const char* body, size_t length)
{
  unsigned long numBytesWrittenToClient = 0;
  unsigned char *bodyPtr = &body[0];

    // determine Status-Line's phrase
    // http://www.w3.org/Protocols/rfc2616/...c6.html#sec6.1
    const char* phrase = reason(code);
    if (phrase == NULL)
    {
        return;
    }

    // respond with Status-Line
    if (numBytesWrittenToClient = dprintf(cfd, "HTTP/1.1 %i %s\r\n", code, phrase) < 0)
    {
        return;
    }
    else 
      printf("num bytes written to client browser = %d\n", numBytesWrittenToClient);

    // respond with headers
    if (numBytesWrittenToClient = dprintf(cfd, "%s", headers) < 0)
    {
      printf("return from dprintf() less than zero--returning from respond()\n");
        return;
    }
    else 
      printf("num bytes written to client browser = %d\n", numBytesWrittenToClient);


    // respond with CRLF
    if (numBytesWrittenToClient = dprintf(cfd, "\r\n") < 0)
    {
        return;
    }
    else 
      printf("num bytes written to client browser = %d\n", numBytesWrittenToClient);

    // respond with body
    if (numBytesWrittenToClient = write(cfd, body, length) == -1)
    {
        return;
    }
    else 
      printf("num bytes written to client browser = %d\n", numBytesWrittenToClient);

    // log response line
    if (code == 200)
    {
        // green
        printf("\033[32m");
    }
    else
    {
        // red
        printf("\033[33m");
    }
    printf("HTTP/1.1 %i %s", code, phrase);
    printf("\033[39m\n");
}

/**
 * Starts server on specified port rooted at path.
 */
void start(short port, const char* path)
{
    // path to server's root
    root = realpath(path, NULL);
    if (root == NULL)
    {
        stop();
    }

    // ensure root is executable
    if (access(root, X_OK) == -1)
    {
        stop();
    }

    // announce root
    printf("\033[33m");
    printf("Using %s for server's root", root);
    printf("\033[39m\n");

    // create a socket
    sfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sfd == -1)
    {
        stop();
    }

    // allow reuse of address (to avoid "Address already in use")
    int optval = 1;
    setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));

    // assign name to socket
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(port);
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    if (bind(sfd, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1)
    {
        printf("\033[33m");
        printf("Port %i already in use", port);
        printf("\033[39m\n");
        stop();
    }
    
    // listen for connections
    if (listen(sfd, SOMAXCONN) == -1)
    {
        stop();
    }

    // announce port in use
    struct sockaddr_in addr;
    socklen_t addrlen = sizeof(addr);
    if (getsockname(sfd, (struct sockaddr*) &addr, &addrlen) == -1)
    {
        stop();
    }
    printf("\033[33m");
    printf("Listening on port %i", ntohs(addr.sin_port));
    printf("\033[39m\n");
}

/**
 * Stop server, deallocating any resources.
 */
void stop(void)
{
    // preserve errno across this function's library calls
    int errsv = errno;

    // announce stop
    printf("\033[33m");
    printf("Stopping server\n");
    printf("\033[39m");

    // free root, which was allocated by realpath
    if (root != NULL)
    {
        free(root);
    }

    // close server socket
    if (sfd != -1)
    {
        close(sfd);
    }

    // stop server
    exit(errsv);
}

/**
 * Transfers file at path with specified type to client.
 */
void transfer(const char* path, const char* type)
{
  BYTE *contentPtr;
    // ensure path is readable
    if (access(path, R_OK) == -1)
    {
        error(403);
        return;
    }

    // open file
    FILE* file = fopen(path, "r");
    if (file == NULL)
    {
        error(500);
        return;
    }

    // load file's content
    BYTE* content = NULL;
    size_t length;
    if (load(file, &content, &length) == false)
    {
        error(500);
        return;
    }

    // close file
    fclose(file);

    // prepare response
    char* template = "Content-Type: %s\r\n";
    char headers[strlen(template) - 2 + strlen(type) + 1];
    if (sprintf(headers, template, type) < 0)
    {
        error(500);
        return;
    }

    // respond with file's content
    respond(200, headers, content, length);

    // free file's content
    free(content);
}

/**
 * URL-decodes string, returning dynamically allocated memory for decoded string
 * that must be deallocated by caller.
 */
char* urldecode(const char* s)
{
    // check whether s is NULL
    if (s == NULL)
    {
        return NULL;
    }

    // allocate enough (zeroed) memory for an undecoded copy of s
    char* t = calloc(strlen(s) + 1, 1);
    if (t == NULL)
    {
        return NULL;
    }
    
    // iterate over characters in s, decoding percent-encoded octets, per
    // https://www.ietf.org/rfc/rfc3986.txt
    for (int i = 0, j = 0, n = strlen(s); i < n; i++, j++)
    {
        if (s[i] == '%' && i < n - 2)
        {
            char octet[3];
            octet[0] = s[i + 1];
            octet[1] = s[i + 2];
            octet[2] = '\0';
            t[j] = (char) strtol(octet, NULL, 16);
            i += 2;
        }
        else if (s[i] == '+')
        {
            t[j] = ' ';
        }
        else
        {
            t[j] = s[i];
        }
    }

    // escaped string
    return t;
}
 
Old 04-04-2016, 06:05 AM   #2
jpollard
Senior Member
 
Registered: Dec 2012
Location: Washington DC area
Distribution: Fedora, CentOS, Slackware
Posts: 4,912

Rep: Reputation: 1513Reputation: 1513Reputation: 1513Reputation: 1513Reputation: 1513Reputation: 1513Reputation: 1513Reputation: 1513Reputation: 1513Reputation: 1513Reputation: 1513
The code you show won't even compile.

Missing includes, undefined variables... Roughly every three or four lines has something wrong.
 
Old 04-04-2016, 07:08 AM   #3
KarenWest
Member
 
Registered: Jun 2011
Location: North Easton,MA
Distribution: currently Ubuntu Linux on my netbook while job searching, but also use Cygwin GNU tools on MSXP nb
Posts: 33

Original Poster
Rep: Reputation: Disabled
Yes, someone else had mentioned that. I removed the includes because otherwise the code exceeded the limit allowed to post here. I may re-post at a later date if I do not find the error myself before then, when I remove enough comments so that the includes will then fit in. Thank you for your response.
 
Old 04-04-2016, 09:29 AM   #4
jpollard
Senior Member
 
Registered: Dec 2012
Location: Washington DC area
Distribution: Fedora, CentOS, Slackware
Posts: 4,912

Rep: Reputation: 1513Reputation: 1513Reputation: 1513Reputation: 1513Reputation: 1513Reputation: 1513Reputation: 1513Reputation: 1513Reputation: 1513Reputation: 1513Reputation: 1513
The problem is that it makes it impossible to trace.
 
Old 04-04-2016, 11:30 AM   #5
KarenWest
Member
 
Registered: Jun 2011
Location: North Easton,MA
Distribution: currently Ubuntu Linux on my netbook while job searching, but also use Cygwin GNU tools on MSXP nb
Posts: 33

Original Poster
Rep: Reputation: Disabled
Yes, I understand. I have a phone interview I'm prepping for today and later I'll be back to working on my skills again.
I'll see if I can reduce the code size and post a version which compiles if I have not yet found the answer myself. Thanks!
 
Old 04-04-2016, 12:24 PM   #6
KarenWest
Member
 
Registered: Jun 2011
Location: North Easton,MA
Distribution: currently Ubuntu Linux on my netbook while job searching, but also use Cygwin GNU tools on MSXP nb
Posts: 33

Original Poster
Rep: Reputation: Disabled
Here are the includes, definitions, prototypes, etc, that were not included with the code above.
I think this is all I removed besides the comments to make it fit.

Code:
// feature test macro requirements
#define _GNU_SOURCE
#define _XOPEN_SOURCE 700
#define _XOPEN_SOURCE_EXTENDED

// limits on an HTTP request's size, based on Apache's
// http://httpd.apache.org/docs/2.2/mod/core.html
#define LimitRequestFields 50
#define LimitRequestFieldSize 4094
#define LimitRequestLine 8190

char pathChars[LimitRequestLine + 1];
char pathCopy[LimitRequestLine + 1];

// number of bytes for buffers
#define BYTES 512

// header files
#include <arpa/inet.h>
#include <dirent.h>
#include <errno.h>
#include <limits.h>
#include <math.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "server.h"

// types
typedef char BYTE;

// prototypes
bool connected(void);
void error(unsigned short code);
void freedir(struct dirent** namelist, int n);
void handler(int signal);
char* htmlspecialchars(const char* s);
char* indexes(const char* path);
void interpret(const char* path, const char* query);
void list(const char* path);
bool load(FILE* file, BYTE** content, size_t* length);
const char* lookup(const char* path);
bool parse(const char* line, char* path, char* query);
const char* reason(unsigned short code);
void redirect(const char* uri);
bool request(char** message, size_t* length);
void respond(int code, const char* headers, const char* body, size_t length);
void start(short port, const char* path);
void stop(void);
void transfer(const char* path, const char* type);
char* urldecode(const char* s);

// server's root
char* root = NULL;

// file descriptor for sockets
int cfd = -1, sfd = -1;

// flag indicating whether control-c has been heard
bool signaled = false;
 
Old 04-04-2016, 12:39 PM   #7
jpollard
Senior Member
 
Registered: Dec 2012
Location: Washington DC area
Distribution: Fedora, CentOS, Slackware
Posts: 4,912

Rep: Reputation: 1513Reputation: 1513Reputation: 1513Reputation: 1513Reputation: 1513Reputation: 1513Reputation: 1513Reputation: 1513Reputation: 1513Reputation: 1513Reputation: 1513
#include "server.h" ?

One thing I did see that is likely wrong:

Code:
if (numBytesWrittenToClient = dprintf(cfd, "\r\n") < 0)
To actually get the numbytesWrittenToClient correct you need

Code:
if ((numBytesWrittenToClient = dprintf(cfd, "\r\n")) < 0)
This is due to the precedence of the "<" operator. What you actually got was
'numbBytesWrittenToClient = (dprintf(cfd, "\r\n") < 0)' which would set the count to either 1 or 0.

BTW, this error occurs in other places too. You might check anywhere you do this type of construct. It is an easy slip.

Last edited by jpollard; 04-04-2016 at 12:55 PM.
 
Old 04-04-2016, 12:41 PM   #8
KarenWest
Member
 
Registered: Jun 2011
Location: North Easton,MA
Distribution: currently Ubuntu Linux on my netbook while job searching, but also use Cygwin GNU tools on MSXP nb
Posts: 33

Original Poster
Rep: Reputation: Disabled
Sorry - forgot about that include file that I added - all it has in it are these few lines,
so if you comment that include out and add these few defines, that should work.

#define MAXBUF 4096 * 10
#define MAXLINE 80
#define READMEGABYTEFROMFILE 1048576 // 1024 * 1024
 
Old 04-04-2016, 12:52 PM   #9
smallpond
Senior Member
 
Registered: Feb 2011
Location: Massachusetts, USA
Distribution: Fedora
Posts: 4,138

Rep: Reputation: 1263Reputation: 1263Reputation: 1263Reputation: 1263Reputation: 1263Reputation: 1263Reputation: 1263Reputation: 1263Reputation: 1263
What unit testing or debugging have you tried on your own? Load it in a debugger, connect to it with telnet, and trace through it to see where it fails.
 
Old 04-04-2016, 01:08 PM   #10
KarenWest
Member
 
Registered: Jun 2011
Location: North Easton,MA
Distribution: currently Ubuntu Linux on my netbook while job searching, but also use Cygwin GNU tools on MSXP nb
Posts: 33

Original Poster
Rep: Reputation: Disabled
It fails at the write() function. No errors in errno, but zero bytes are written to the client browser.

Also, just before the write(), dprintf() which writes to the client browser also sends zero bytes.

I was not sure why, since *content has data in it, I sent to the correct socket number, etc.
 
Old 04-04-2016, 01:29 PM   #11
jpollard
Senior Member
 
Registered: Dec 2012
Location: Washington DC area
Distribution: Fedora, CentOS, Slackware
Posts: 4,912

Rep: Reputation: 1513Reputation: 1513Reputation: 1513Reputation: 1513Reputation: 1513Reputation: 1513Reputation: 1513Reputation: 1513Reputation: 1513Reputation: 1513Reputation: 1513
Check the writes - I believe you are missing parenthesis around the assignments where you also check for < 0.
 
Old 04-04-2016, 01:56 PM   #12
KarenWest
Member
 
Registered: Jun 2011
Location: North Easton,MA
Distribution: currently Ubuntu Linux on my netbook while job searching, but also use Cygwin GNU tools on MSXP nb
Posts: 33

Original Poster
Rep: Reputation: Disabled
I added the parantheses where you mentioned - it still writes zero bytes to the client browser - not sure why yet.

This is output (printf's) that I made in my version (not included in the one I posted since it was too much).

karen pset6 $ ./server public
Using /home/karen/dev/webServerInC/pset6/public for server's root
Listening on port 8080
checking for client connections
starting infinite loop to look for client browser requests
checking if client has connected
calling accept(): extracts the first connection request on the queue of pending connections for the listening socket (server), sockfd, creates a new connected socket, and returns a new file descriptor referring to that socket (client)
client: addr = 16777343
client has connected - check for request
request: reading message
message from client = GET /home/karen/webServerInC/pset6/public/test/index.html HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8
length = 417
GET /home/karen/webServerInC/pset6/public/test/index.html HTTP/1.1
parsing request-line from client: line = GET /home/karen/webServerInC/pset6/public/test/index.html HTTP/1.1

parsing line = GET /home/karen/webServerInC/pset6/public/test/index.html HTTP/1.1

parsing for the GET method
should be pointing to request-target part of line = /home/karen/webServerInC/pset6/public/test/index.html HTTP/1.1
start of abs_path = /
pathChars = /home/karen/webServerInC/pset6/public/test/index.html countAbsPath = 53
len of abs_path = 0 len of pathChars = 53
parse: abs_path = /home/karen/webServerInC/pset6/public/test/index.html
no query found
parse: query =
are we pointing at HTTP's and it's version? HTTP/1.1
and HTTP/1.1

HTTP/1.1 found - correct version for now
abs_path returned from parse = /home/karen/webServerInC/pset6/public/test/index.html
query returned from parse =
respond(): responding to client browser with status code, headers, and body of specified length.
printing body bytes which in the load routine were the content bytes passed to this respond() function
0x3c 0x68 0x74 0x6d 0x6c 0x3e 0x3c 0x68 0x65 0x61 0x64 0x3e 0x3c 0x74 0x69 0x74 0x6c 0x65 0x3e 0x34 0x30 0x34 0x20 0x4e 0x6f 0x74 0x20 0x46 0x6f 0x75 0x6e 0x64 0x3c 0x2f 0x74 0x69 0x74 0x6c 0x65 0x3e 0x3c 0x2f 0x68 0x65 0x61 0x64 0x3e 0x3c 0x62 0x6f 0x64 0x79 0x3e 0x3c 0x68 0x31 0x3e 0x34 0x30 0x34 0x20 0x4e 0x6f 0x74 0x20 0x46 0x6f 0x75 0x6e 0x64 0x3c 0x2f 0x68 0x31 0x3e 0x3c 0x2f 0x62 0x6f 0x64 0x79 0x3e 0x3c 0x2f 0x68 0x74 0x6d 0x6c 0x3e
done printing content bytes
code = 404 phrase = reason(code) = Not Found
responding with status line to client
num bytes written to client browser = 0
calling dprintf() to print to client browser's socket file descriptor 4 with headers = Content-Type: text/html

num bytes written to client browser = 0
calling dprintf() to print to client browser's socket file descriptor with backslash-r backslash-n
num bytes written to client browser = 0
calling write() to client browser's socket file descriptor with body = <html><head><title>404 Not Found</title></head><body><h1>404 Not Found</h1></body></html> length = 89
num bytes written to client browser = 0
code NOT= 200, red
HTTP/1.1 404 Not Found
starting infinite loop to look for client browser requests
checking if client has connected
calling accept(): extracts the first connection request on the queue of pending connections for the listening socket (server), sockfd, creates a new connected socket, and returns a new file descriptor referring to that socket (client)
client: addr = 16777343
client has connected - check for request
request: reading message
 
Old 04-04-2016, 02:29 PM   #13
jpollard
Senior Member
 
Registered: Dec 2012
Location: Washington DC area
Distribution: Fedora, CentOS, Slackware
Posts: 4,912

Rep: Reputation: 1513Reputation: 1513Reputation: 1513Reputation: 1513Reputation: 1513Reputation: 1513Reputation: 1513Reputation: 1513Reputation: 1513Reputation: 1513Reputation: 1513
What are you using to compile this?
 
Old 04-04-2016, 02:29 PM   #14
smallpond
Senior Member
 
Registered: Feb 2011
Location: Massachusetts, USA
Distribution: Fedora
Posts: 4,138

Rep: Reputation: 1263Reputation: 1263Reputation: 1263Reputation: 1263Reputation: 1263Reputation: 1263Reputation: 1263Reputation: 1263Reputation: 1263
What's the value of cfd at the dprintf? Did you check with netstat to see if your program has the socket open and is listening?
 
Old 04-04-2016, 03:54 PM   #15
KarenWest
Member
 
Registered: Jun 2011
Location: North Easton,MA
Distribution: currently Ubuntu Linux on my netbook while job searching, but also use Cygwin GNU tools on MSXP nb
Posts: 33

Original Poster
Rep: Reputation: Disabled
I will check out the value of cfd most likely tomorrow since I do not have time today.
I just looked at the man page for netstat, but do not really know what you mean I should do to
see if my program has the socket open and is still listening. Do you do that from the command
line, or do you put that in the code? It looks like a command line thing you run but I was not
sure what to look for if I used it. I can do a web search but not until tomorrow! Thanks for
your response.
 
  


Reply

Tags
permission, realloc, sockets, webserver, write


Thread Tools Search this Thread
Search this Thread:

Advanced Search

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off



Similar Threads
Thread Thread Starter Forum Replies Last Post
Web server with public IP address not reachable via domain name in the web browser floorripper Linux - Server 11 08-09-2013 09:55 PM
[SOLVED] What topics do I need to study for *web* client server programming - Web service? Aquarius_Girl Programming 9 03-15-2013 09:53 AM
[SOLVED] win7 web client problem with apache2 web server on linux lapinux Linux - Networking 2 03-15-2011 12:21 PM
writing the headers recived from browser to server praveensleeps Linux - Networking 3 11-06-2008 06:50 PM
Help with writing a small web server Diederick Programming 2 08-19-2005 07:46 PM

LinuxQuestions.org > Forums > Non-*NIX Forums > Programming

All times are GMT -5. The time now is 04:35 PM.

Main Menu
Advertisement
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
Open Source Consulting | Domain Registration