HTTP C++ Programming

profileL_er254
lab2_skeleton.tar_.gz

lab2_skeleton/client.cc

lab2_skeleton/client.cc

// Simple HTTP client for CSE422 SS17 lab 02.
#include   "client.h"

int  main ( int  argc ,   char *  argv [])   {
  signal ( SIGPIPE ,  SIG_IGN );    // To ignore SIGPIPE

   char *  serverAddr  =  NULL ;
   char *  proxyAddr  =  NULL ;
  URL *  serverUrl  =  NULL ;
  URL *  proxyUrl  =  NULL ;
   HTTPRequest *  request  =  NULL ;
   HTTPResponse *  response  =  NULL ;
  FILE *  out  =  NULL ;

  parseArgs ( argc ,  argv ,   & serverAddr ,   & proxyAddr );

   /***PARSE THE ADDRS RECEIVED TO URL OBJECTS***/
   // Must have a server to get data from
   if   ( ! serverAddr )   {
    std :: cerr  <<   "You did not specify the host address."   <<  std :: endl ;
    helpMessage ( argv [ 0 ],  std :: cout );
    exit ( 1 );
   }

  serverUrl  =  URL :: parse ( serverAddr );
   if   ( ! serverUrl )   {    // If URL parsing is failed
    std :: cerr  <<   "Unable to parse host address: "   <<  serverAddr
               <<  std :: endl ;
    helpMessage ( argv [ 0 ],  std :: cout );
    exit ( 1 );
   }

   // Proxy is an otional argument
   // If a proxy URL is specified, the client connects to the proxy and
   // interacts with the proxy. Otherwise, the client connects to the host
   // and interacts with the host.
  proxyUrl  =  NULL ;
   if   ( proxyAddr )   {
    proxyUrl  =  URL :: parse ( proxyAddr );
     if   ( ! proxyUrl )   {
      std :: cerr  <<   "Unable to parse proxy address: "   <<  proxyAddr
                 <<  std :: endl ;
      helpMessage ( argv [ 0 ],  std :: cout );
      exit ( 1 );
     }
     if   ( ! ( proxyUrl -> isPortDefined ()))   {
      std :: cout  <<   "Proxy port is not defined, assumed to be 8080"   <<  std :: endl ;
      proxyUrl -> setPort ( 8080 );
     }
   }
   /***END OF PARSING THE ADDRS RECEIVED TO URL OBJECTS***/


   /***CREATE A TCPSocket OBJECT AND CONNECT TO THE URL***/
   // TCPSocket class to handle TCP communications.
   TCPSocket  clientSock ;
   if   ( ! proxyAddr )   {    // proxy not specified, connect to the host directly.
     try   {
      clientSock . Connect ( * serverUrl );    // Connect to the target server.
     }   catch ( std :: string msg )   {
       // Give up if sock is not created correctly.
      std :: cerr  <<  msg  <<  std :: endl ;
      std :: cerr  <<   "Unable to connect to server: "
                 <<  serverUrl -> getHost ()   <<  std :: endl ;
       delete  serverUrl ;
      exit ( 1 );
     }
   }   else   {    // proxy is specified, connect to proxy
     try   {
      clientSock . Connect ( * proxyUrl );    // Connect to the proxy, instead of server
     }   catch ( std :: string msg )   {
       // Give up if sock is not created correctly.
      std :: cout  <<  msg  <<  std :: endl ;
      std :: cout  <<   "Unable to connect to proxy: "
                 <<  proxyUrl -> getHost ()   <<  std :: endl ;
       delete  serverUrl ;
       delete  proxyUrl ;
      exit ( 1 );
     }
   }
   /***END OF CREATING A TCPSocket OBJECT AND CONNECT TO THE URL***/




   /***SEND THE REQUEST TO THE SERVER***/
   // Send a GET request for the specified file.
   // No matter connecting to the server or the proxy, the request is
   // alwasy destined to the server.
  request  =   HTTPRequest :: createGetRequest ( serverUrl -> getPath ());
  request -> setHost ( serverUrl -> getHost ());
   // set this request to non-persistent.
  request -> setHeaderField ( "Connection" ,   "close" );
   // For real browsers, If-Modified-Since field is always set.
   // if the local object is the latest copy, the browser does not
   // respond the object.
  request -> setHeaderField ( "If-Modified-Since" ,   "0" );

   try   {    // send the request to the sock
    request -> send ( clientSock );
   }   catch ( std :: string msg )   {    // something is wrong, send failed
    std :: cerr  <<  msg  <<  std :: endl ;
    exit ( 1 );
   }

   // get the request as a std::string
  std :: string printBuffer ;
  request -> print ( printBuffer );

   // output the request
  std :: cout  <<   "Request sent..."   <<  std :: endl ;
  std :: cout  <<   "=========================================================="
             <<  std :: endl ;
  std :: cout  <<  printBuffer . substr ( 0 ,  printBuffer . size ()   -   4 )   <<  std :: endl ;
  std :: cout  <<   "=========================================================="
             <<  std :: endl ;

   delete  request ;    // We do not need it anymore
   /***END OF SENDING REQUEST***/








   /***RECEIVE RESPONSE HEADER FROM THE SERVER***/
   // The server response is a stream starts with a header and then
   // the body/data. A blank line separates the header and the body/data.
   //
   // Read enough of the server's response to get all of the headers,
   // then have that response interpreted so we at least know what
   // happened.
   //
   // We create two std::strings to hold the incoming data. As described in the
   // hanout, a HTTP message is composed of two portions, a header and a body.
  std :: string responseHeader ,  responseBody ;

   // The client receives the response stream. Check if the data it has
   // contains the whole header.
   // read_header separates the header and data by finding the blank line.
   try   {
    response -> receiveHeader ( clientSock ,  responseHeader ,  responseBody );
   }   catch   ( std :: string msg )   {
    std :: cerr  <<  msg  <<  std :: endl ;
   }

   // The HTTPResponse::parse construct a response object. and check if
   // the response is constructed correctly. Also it tries to determine
   // if the response is chunked transfer encoding or not.
  response  =   HTTPResponse :: parse ( responseHeader . c_str (),
                                 responseHeader . length ());

   // The response is illegal.
   if   ( ! response )   {
    std :: cerr  <<   "Client: Unable to parse the response header."   <<  std :: endl ;
     // clean up if there's something wrong
     delete  response ;
     if   ( proxyUrl )   {
       delete  proxyUrl ;
     }
     delete  serverUrl ;
    exit ( 1 );
   }

   // get the response as a std::string
  response -> print ( printBuffer );

   // output the response header
  std :: cout  <<  std :: endl  <<   "Response header received"   <<  std :: endl ;
  std :: cout  <<   "=========================================================="
             <<  std :: endl ;
  std :: cout  <<  printBuffer . substr ( 0 ,  printBuffer . length ()   -   4 )   <<  std :: endl ;
  std :: cout  <<   "=========================================================="
             <<  std :: endl ;

   /***END OF RECEIVING RESPONSE HEADER FROM THE SERVER***/












   /***GET REST OF THE MESSAGE BODY AND STORE IT***/
   // Open a local copy in which to store the file.
  out  =   OpenLocalCopy ( serverUrl );
   // check
   if   ( ! out )   {
    std :: cerr  <<   "Error opening local copy for writing."   <<  std :: endl ;
     // clean up if failed
     if   ( ! proxyAddr )   {
       delete  proxyUrl ;
     }
     delete  serverUrl ;
    exit ( 1 );
   }


   int  bytesWritten  =   0 ,  bytesLeft ;

   if   ( ! ( response -> isChunked ())   &&    // neither chunked transfer encoding
      response -> getContentLen ()   ==   - 1 )   {    // nor default transfer encoding
    std :: cout  <<   "The response is neither default tranfer encoding "
               <<   "nor chunked transfer encoding. This response is not "
               <<   "supported. Terminating the program without saving the file."
               <<  std :: endl ;
   }   else   if   ( ! ( response -> isChunked ()))   {
    std :: cout  <<  std :: endl  <<   "Downloading rest of the file ... "   <<  std :: endl ;
     // default transfer encoding does not split the data into
     // chunks. The header specifies a Content-Length field. The client knows
     // exactly how many data it is expecting. The client keeps receiving
     // the response until it gets the amount specified.

    std :: cout  <<   "Default transfer encoding"   <<  std :: endl ;
    std :: cout  <<   "Content-length: "   <<  response -> getContentLen ()   <<  std :: endl ;
    bytesLeft  =  response -> getContentLen ();

     do   {
       // If we got a piece of the file in our buffer for the headers,
       // have that piece written out to the file, so we don't lose it.
      fwrite ( responseBody . c_str (),   1 ,  responseBody . length (),  out );
      bytesWritten  +=  responseBody . length ();
      bytesLeft  -=  responseBody . length ();

      std :: cout  <<   "bytes written:"   <<   bytesWritten  <<  std :: endl ;
      std :: cout  <<   "data gotten:"   <<   responseBody . length ()   <<  std :: endl ;

      responseBody . clear ();
       try   {
         // Keeps receiving until it gets the amount it expects.
        response -> receiveBody ( clientSock ,  responseBody ,  bytesLeft );
       }   catch ( std :: string msg )   {
         // something bad happend
        std :: cerr  <<  msg  <<  std :: endl ;
         // clean up
         delete  response ;
         delete  serverUrl ;
         if   ( proxyAddr )   {
           delete  proxyUrl ;
         }
        fclose ( out );
        clientSock . Close ();
        exit ( 1 );
       }
     }   while   ( bytesLeft  >   0 );
   }   else   {    // chunked encoding
    std :: cout  <<  std :: endl  <<   "Downloading rest of the file ... "   <<  std :: endl ;
    std :: cout  <<   "Chunked transfer encoding"   <<  std :: endl ;

     // As mentioned above, receiveHeader function already split the
     // body from the header from us. The beginning of this respnse_data
     // now holds the first chunk size.
     int  chunkLen  =   HTTPResponse :: getChunkSize ( responseBody );
     int  totalData  =  chunkLen ;

     while   ( 1 )   {
      std :: cout  <<   "       chunk length: "   <<  chunkLen  <<  std :: endl ;
      std :: cout  <<   "responseBody length: "   <<  responseBody . length ()
                 <<  std :: endl ;
       if   ( chunkLen  ==   0 )   {    // the end of response body
         break ;
       }   else   if   ( chunkLen  ==   - 1 )   {
         // If chunk length is not found
         // It is possible that the receieveHeader gets exactly only the
         // header and the first chunk length is not recevied yet. In this
         // case, getChunkSize returns -1. Receive more to get the chunk length
        response -> receiveLine ( clientSock ,  responseBody );
        chunkLen  =   HTTPResponse :: getChunkSize ( responseBody );
       }   else   if   ( responseBody . length ()   <  chunkLen )   {
         try   {
           // If current data holding is less than the chunkLen, this
           // piece of data contains only part of this chunk. Receive more
           // until we have a complete chunk to store!
           // receive more until we have the whole chunk.
          response -> receiveBody ( clientSock ,  responseBody ,
                                 ( chunkLen  -  responseBody . length ()));
          response -> receiveLine ( clientSock ,  responseBody );
           // get the blank line between chunks
          response -> receiveLine ( clientSock ,  responseBody );
           // get next chunk, at least get the chunk size
         }   catch ( std :: string msg )   {
           // something bad happend
          std :: cerr  <<  msg  <<  std :: endl ;
           // clean up
           delete  response ;
           delete  serverUrl ;
           if   ( proxyAddr )   {
             delete  proxyUrl ;
           }
          fclose ( out );
          clientSock . Close ();
          exit ( 1 );
         }
       }   else   {
         // If current data holding is longer than the chunk size, this
         // piece of data contains more than one chunk. Store the chunk.
        fwrite ( responseBody . c_str (),   1 ,  chunkLen ,  out );
        bytesWritten  +=  chunkLen ;

         // reorganize the data, remove the chunk from it
         // the + 2 here is to consume the extra CLRF

        responseBody  =  responseBody . substr ( chunkLen  +   2 ,
                           responseBody . length ()   -  chunkLen  -   2 );
        response -> receiveLine ( clientSock ,  responseBody );
         // get the blank line between chunks
        response -> receiveLine ( clientSock ,  responseBody );
         // get next chunk, at least get the chunk size

         // get next chunk size
        chunkLen  =   HTTPResponse :: getChunkSize ( responseBody );

        totalData  +=  chunkLen ;
       }
     }

     // This checks if the chunked encoding transfer mode is downloading
     // the contents correctly.
     if   (( totalData  !=  bytesWritten )   &&  response -> isChunked ())   {
      std :: cout  <<   "WARNING"   <<  std :: endl
                 <<   "Data received does not match the sum of chunks."
                 <<  std :: endl ;
     }
    std :: cout  <<   "Download complete ("   <<  bytesWritten
               <<   " bytes written)"   <<  std :: endl ;
   }


   // If the response is not OK, something is wrong.
   // However, we still downloaded the content, because even the response
   // is not 200. The server still replies with an error page (403, 404 ...)
   if   ( response -> getStatusCode ()   !=   200 )   {
    std :: cerr  <<  response -> getStatusCode ()   <<   " "
               <<  response -> getStatusDesc ()   <<  std :: endl ;
   }

   // everything's done.
  clientSock . Close ();

   delete  response ;
   delete  serverUrl ;
   if   ( proxyAddr )   {
     delete  proxyUrl ;
   }
  fclose ( out );

   return   0 ;
}

lab2_skeleton/TCPSocket.cc

#include "TCPSocket.h" #include <sstream> void TCPSocket::createSocket() { // close the socket if it's already open Close(); // first try to make the TCP socket sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) { throw std::string("TCPSocket Exception: Unable to create socket"); } } void TCPSocket::Connect(const std::string& serverName, unsigned short serverPort) { hostent *hostEnt; createSocket(); // create a socket // convert the server name to a valid inet address if ((hostEnt = gethostbyname(serverName.c_str())) == NULL) { throw std::string("TCPSocket Exception: could not resolve hostname"); } Connect(hostEnt, serverPort); } void TCPSocket::Connect(hostent *host, unsigned short serverPort) { // create the socket createSocket(); // make sure it's zero to start memset(&serverAddr, 0, sizeof(serverAddr)); // designate it as part of the Internet address family serverAddr.sin_family = AF_INET; // specify the port, host to network short serverAddr.sin_port = htons(serverPort); // specify the server IP address in network byte order memcpy(&serverAddr.sin_addr, host->h_addr, host->h_length); // now actually try to connect if (connect(sock, (struct sockaddr *) &serverAddr, sizeof(serverAddr)) < 0) { throw std::string("TCPSocket Exception: connect failed"); } } void TCPSocket::Connect(const URL& url) { hostent *hp = gethostbyname(url.getHost().c_str()); if (hp == NULL) { throw std::string("TCPSocket Exception: Unable to resolve URL"); } else { // URL resolved successfully // If the port is not defined, connect to 80 if (url.isPortDefined()) { Connect(hp, url.getPort()); } else { Connect(hp, 80); } } } void TCPSocket::Bind(unsigned short serverPort) { // create the socket createSocket(); // make sure it's zero to start memset(&serverAddr, 0, sizeof(serverAddr)); // designate it as part of the Internet address family serverAddr.sin_family = AF_INET; // specify the port, host to network short serverAddr.sin_port = htons(serverPort); // specify the server IP address in network byte order serverAddr.sin_addr.s_addr = INADDR_ANY; if (bind(sock, (sockaddr *) &serverAddr, sizeof(serverAddr)) < 0) { throw std::string("TCPSocket Exception: could not bind to interface"); } } void TCPSocket::Listen() { // listen on socket sock, report error when fail if (listen(sock, 1) < 0) { throw std::string("TCPSocket Exception: listen call failed"); } socklen_t serverAddrLen = sizeof(serverAddr); if (getsockname(sock, (sockaddr *) &serverAddr, &serverAddrLen) < 0) { throw std::string("TCPSocket Exception: Unable to obtain socket information."); } } bool TCPSocket::Accept(TCPSocket& dataSock) { int newSock; socklen_t sinSize; sinSize = sizeof(struct sockaddr_in); // waiting for new incoming connection if ((newSock = accept(sock, (struct sockaddr *) &(dataSock.serverAddr), &sinSize)) < 0) { throw std::string("TCPSocket Exception: could not accept incoming connection"); return false; } dataSock.sock = newSock; return true; } TCPSocket *TCPSocket::Accept() { TCPSocket* newSock = new TCPSocket(); Accept(*newSock); return newSock; } int TCPSocket::Close() { if (sock != -1) { // If this socket is in use if (close(sock) < 0) { return -1; } } sock = -1; return 0; } int TCPSocket::writeString(const std::string& data) { int bytesSent = 0; if ((bytesSent = send(sock, (void *)data.data(), data.size(), 0)) < 0) { throw std::string("TCPSocket Exception: error sending data"); } return bytesSent; } int TCPSocket::readString(std::string& data) { int bytesReceived; if ((bytesReceived = recv(sock, (void *)data.data(), data.size(), 0)) < 0) { throw std::string("TCPSocket Exception: error reading data from socket"); } data = data.substr(0, bytesReceived); data += '\0'; return bytesReceived; } int TCPSocket::readNBytes(void* vptr, unsigned int n) { size_t nLeft; ssize_t nRead; char *ptr; ptr = (char *) vptr; nLeft = n; while (nLeft > 0) { // keeps reading until n is satisfied if ((nRead = read(sock, ptr, nLeft)) < 0) { // something is wrong return -1; } else if (nRead == 0) { // nothing's in the socket, stop break; } nLeft -= nRead; ptr += nRead; } return (n - nLeft); } int TCPSocket::readLine(void *vptr, unsigned int maxLen) { int n, readCount; char c, *ptr; ptr = (char *) vptr; for (n = 1; n < maxLen; n++) { readCount = read(sock, &c, 1); // Keeps receiving, one byte by one byte if (readCount == 1) { *ptr++ = c; if (c == '\n') { // check if the byte is newline break; // break and end this function is yes } } else if (readCount == 0) { if (n == 1) { return 0; } else { return n; } } else { // readCount < 0 return -1; } } *ptr = 0; return n; } int TCPSocket::receiveHeaders(char *buffer, unsigned int bufferLen, unsigned int& totalReceivedLen) { static const char headerEnd[] = {'\r', '\n', '\r', '\n'}; static const unsigned headerEndLen = sizeof(headerEnd); // Piece-by-piece, buffer the server's response and look for the end // of the headers. Make a note of where in the buffer the end occurs. int bytesReceived = 0; int headerEndPos = -1; int headerEndRead = 0; while ((bytesReceived < bufferLen) && (headerEndPos < 0)) { // Grab however many bytes are waiting for us right now. int receivedPiece = read(sock, buffer + bytesReceived, bufferLen - bytesReceived); if (receivedPiece == -1) { // Something's wrong. If we cannot receive, reutrn -1 return -1; } // Go over what we got in the buffer and look for the end of headers int i; for (i = bytesReceived; i < (bytesReceived + receivedPiece) && (headerEndRead < headerEndLen); i++) { if (buffer[i] == headerEnd[headerEndRead]) { headerEndRead++; } else { headerEndRead = 0; } } // If we found the end, mark it. Also keep track of how much // we've read total, for several reasons (not filling the // buffer; knowing how much we've read past the header, etc.). if (headerEndRead >= headerEndLen) { headerEndPos = i; } bytesReceived += receivedPiece; } totalReceivedLen = bytesReceived; return headerEndPos; // Note that this headerEndPos here includes \r\n\r\n } // Receive a piece of response and extract the header portion from it. // Stores the header in the std::string header and store the rest in the // std::string data. // One can check if the header is good by checking the length of header. void TCPSocket::readHeader(std::string& header, std::string& data) { char buffer[BUFFER_SIZE]; unsigned int total = 0; int headerEndPos = receiveHeaders(buffer, BUFFER_SIZE - 1, total); if (headerEndPos < 0) { throw std::string("TCPSocket Exception: Error receiving response header."); } else { // Store the received header and data into header.append(buffer, headerEndPos); data.append(buffer + headerEndPos, total - headerEndPos); } } int TCPSocket::readData(std::string& data, unsigned int bytesLeft) { int total = 0, bytesRead; char buffer[BUFFER_SIZE]; while (total < bytesLeft) { memset(buffer, 0, sizeof(buffer)); bytesRead = readNBytes(buffer, bytesLeft); if (bytesRead < 0) { throw std::string("TCPSocket Exception: error reading data from socket"); return -1; } else if (bytesRead == 0) { break; } data.append(buffer, bytesRead); total += bytesRead; } return total; } int TCPSocket::readLine(std::string& data) { char buffer[BUFFER_SIZE]; int bytesRead; memset(buffer, 0, BUFFER_SIZE); if ((bytesRead = readLine(buffer, BUFFER_SIZE)) < 0) { throw std::string("TCPSocket Exception: error reading line from socket"); } buffer[bytesRead] = 0; data += buffer; return bytesRead; } void TCPSocket::getPort(unsigned short& gettingPort) { gettingPort = ntohs(serverAddr.sin_port); }

lab2_skeleton/README

Student NetID: alice999, I am working with bob99999. Compilation tested on: ned, skinner, marge ... Command for compile: make Logs:

lab2_skeleton/HTTPResponse.h

/********************************* * HTTPResponse - Class representing an HTTP response message. May be used * both to parse existing HTTP response into a comprehensible object, and to * construct new responses from scratch and print them out to a text string. * Makes no attempt to handle the body of the response -- only the response code * and the headers will be captured. * * If you're planning on servicing GET and HEAD requests only, you can use * the createStandardResponse() method to have a lot of headers automatically * set up for you. The HTTP specification mandates these headers, and some * clients may expect them. * * Also see the HTTPMessage class for methods that can be used to query and * set the response headers. *********************************/ #ifndef _HTTP_RESPONSE_H_ #define _HTTP_RESPONSE_H_ #include "HTTPMessage.h" #include "TCPSocket.h" #include <string> #include <cstdlib> #include <cstdio> #include <ctime> #include <sstream> class HTTPResponse : public HTTPMessage { public: /********************************* * Name: HTTPResponse * Purpose: Constructs a new HTTPResponse. Note that nothing is done * to check the validity of the arguments -- make sure you * trust your input and/or yourself. * Receive: statusCode - The code representing the response status (e.g. * 200, 403). * statusDesc - A one-line textual description of the response. * version - The HTTP version used to transmit the response. * content - The text string to set as the response's description. * Return: None *********************************/ HTTPResponse(unsigned statusCode = 0, const std::string& statusDesc = "", const std::string& version = "HTTP/1.1", const std::string& content = ""); /********************************* * Name: ~HTTPRequest * Purpose: Destructor of HTTPResponse class objects * Receive: None * Return: None *********************************/ virtual ~HTTPResponse(); /********************************* * Name: parse * Purpose: Parse the response from server in the given buffer to construct * an HTTPResponse object. Check if the response is formatted correctly. * Receive: data - the received data piece stored in a buffer * length - the length of the data, in bytes * Return: a pointer to an HTTPResponse object, if this data is good. * NULL otherwise *********************************/ static HTTPResponse *parse(const char* data, unsigned length); /********************************* * Name: createStandardResponse * Purpose: Constructs a new HTTPResponse with some mandatory header * fields convenienty set for you (unlike the constructor, * which sets no fields for you at all). Assumes that you * will be sending back some kind of message body, and that * the message body will be sent verbatim (i.e. not compressed). * Also assumes that the connection will be closed immediately * (non-persistent connection) after the send ends. * Receive: contentLen - The length of the message body that will * be sent following this response. * statusCode - The code representing the response status (e.g. 500). * statusDesc - A short description of the response code's meaning. * version - The HTTP version used to transmit the response. * Return: An HTTPResponse created for the given input, containing * all of the mandatory headers. *********************************/ static HTTPResponse* createStandardResponse(unsigned contentLen, unsigned statusCode = 0, const std::string& statusDesc = "", const std::string& version = "HTTP/1.1"); /********************************* * Name: getChunkSize * Purpose: for a given data, extract the chunk length * Receive: data - the data as std::string * Return: the extracted chunk length * note that the chunk length is removed from data std::string. *********************************/ static int getChunkSize(std::string& data); /********************************* * Name: receiveHeader * Purpose: receive a piece of data from the socket sock. Slice the * received data into two parts, header and the body * Receive: sock - the TCPSocket to receive from * header - the string to hold the incoming header string * data - the string to hold the incoming body string, * probably just partial * Return: None *********************************/ void receiveHeader(TCPSocket& sock, std::string& header, std::string& data); /********************************* * Name: receiveBody * Purpose: receive the desired number of bytes of data from the socket * Receive: sock - the TCPSocket to receive from * data - the string to hold the incoming response body as string * bytesLeft - the number of bytes to receive * Return: the number of bytes received. *********************************/ int receiveBody(TCPSocket& sock, std::string& body, int bytesLeft = BUFFER_SIZE); /********************************* * Name: receiveLine * Purpose: receive until a newline char is found * Receive: sock - the TCPSocket to receive from * data - the string to hold incoming data * Return: the number of bytes received *********************************/ int receiveLine(TCPSocket& sock, std::string& data); /********************************* * Name: getContentLen * Purpose: from the header, extract the "Content-Length" * Receive: None * Return: the content length as int *********************************/ const int getContentLen() const; /********************************* * Name: getVersion * Purpose: Looks up the version of the HTTP response (e.g. HTTP/1.1). * Receive: None * Returns: the response's HTTP version. *********************************/ const std::string& getVersion() const { return version; } /********************************* * Name: * Purpose: Looks up the status code of the HTTP response (e.g. 404, * 500). * Receive: None * Return: The response's status code. *********************************/ unsigned getStatusCode() const { return statusCode; } /********************************* * Name: getStatusDesc * Purpose: Looks up the description of the response (e.g. "OK"). * Receive: None * Return: The response's associated statusDesc string. *********************************/ const std::string& getStatusDesc() const { return statusDesc; } /********************************* * Name: getContent * Purpose: Looks up the content, the response body * Receive: None * Return: the content as a string *********************************/ const std::string& getContent() const { return content; } /********************************* * Name: isChunked * Purpose: Looks up if the response is chunked transfer encoding * Receive: None * Return: true if this response is chunked, false otherwise *********************************/ const bool isChunked() const { return chunked; } /********************************* * Name: print * Purpose: prints the response object to a text string, suitable * for sending to an HTTP client. Includes the terminating * blank line and all response headers. * Recieve: output_string - Will be set to the response text. * Return: None *********************************/ void print(std::string& output_string) const; /********************************* * Name: print * Purpose: prints the response object to a text string, suitable for * sending to an HTTP client. Includes the terminating blank * line and all response headers. * Receive: output_buffer - The text buffer into which the response * should be printed. Will be null-terminated. * bufferLen - The number of characters available for writing * in the buffer. printing stops after this * many characters have been written. * Return: None *********************************/ void print(char* output_buffer, unsigned bufferLen) const; /********************************* * Name: setVersion * Purpose: Sets the HTTP version of the response (e.g. HTTP/1.1). * Receive: version - The version to set. * Return: None *********************************/ void setVersion(const std::string& version) { this->version = version; } /********************************* * Name: setStatusCode * Purpose: Sets the status code to indicate in the response. * Receive: statusCode - The HTTP status code to set. * Return: None *********************************/ void setStatusCode(const unsigned statusCode) { this->statusCode = statusCode; } /********************************* * Name: setStatusCode * Purpose: Sets the status code to indicate in the response. * Receive: statusCodeStr - The HTTP status code (in string) to set. * Return: None *********************************/ void setStatusCode(const std::string& statusCodeStr) { std::istringstream iss(statusCodeStr); iss >> this->statusCode; } /********************************* * Name: setStatusDesc * Purpose: Sets the desc for the given status code being sent. * Receive: statusDesc - The text string to set as the response's description. * Return: None *********************************/ void setStatusDesc(const std::string& statusDesc) { this->statusDesc = statusDesc; } /********************************* * Name: setContent * Purpose: Sets the content/response body for the HTTPResponse * Receive: content - The text string to set as the response's description. * Return: None *********************************/ void setContent(const std::string& content) { this->content = content; std::stringstream out; out << content.size(); setHeaderField("Content-Length", out.str().c_str()); } /********************************* * Name: send * Purpose: Send this response to this TCP socket sock * Receive: sock - the socket to send to * Return: None *********************************/ void send(TCPSocket& sock); private: /********************************* * Name: buildStatus * Purpose: private function that builds a data header field that matches the * spec of HTTP * Receive: None * Return: None *********************************/ void buildStatus(); /********************************* * Name: buildTime * Purpose: private function that creates a current time for time-stamping * this response * Receive: None * Return: the string for currnet time *********************************/ std::string buildTime(); unsigned int statusCode; std::string version; std::string statusDesc; std::string content; bool chunked; }; #endif // _HTTP_RESPONSE_H_

lab2_skeleton/client.h

#include "HTTPRequest.h" #include "HTTPResponse.h" #include "URL.h" #include <netdb.h> #include <signal.h> #include <sys/stat.h> #include <climits> #include <cstdlib> #include <cstring> #include <cstdio> #include <iostream> #include <string> #include <sstream> /********************************* * Name: helpMessage * Purpose: prints a brief usage std::string describing how to use the application, * in case the user passes in something that just doesn't work. * Receive: exeName - the name of the executable * out - the ostream * Return: None *********************************/ void helpMessage(const char* exeName, std::ostream& out) { out << "Usage: " << exeName << " [options]" << std::endl; out << "The following options are available:" << std::endl; out << " -s host URL" << std::endl; out << " -p proxy URL" << std::endl; out << " -h display help message" << std::endl; out << std::endl; out << "Example: " << exeName << " -s http://www.some_server.com/ -p 100.200.50.150:8080" << std::endl; } /********************************* * Name: parseArgs * Purpose: parse the parameters * Receive: argv and argc * targetUrl - the target object we are going to download * proxyAddr - the address of the proxy * Return: None *********************************/ void parseArgs(int argc, char *argv[], char **targetAddr, char **proxyAddr) { for (int i = 1; i < argc; i++) { if ((!strncmp(argv[i], "-s", 2)) || (!strncmp(argv[i], "-S", 2))) { *targetAddr = argv[++i]; } else if ((!strncmp(argv[i], "-p", 2)) || (!strncmp(argv[i], "-P", 2))) { *proxyAddr = argv[++i]; } else if ((!strncmp(argv[i], "-h", 2)) || (!strncmp(argv[i], "-H", 2))) { helpMessage(argv[0], std::cout); exit(1); } else { std::cerr << "Invalid parameter: argv[i]" << std::endl; helpMessage(argv[0], std::cout); exit(1); } } } /********************************* * Name: OpenLocalCopy * Purpose: Open a file pointer to store the data in ./Downloads * Receive: url - the url for the object * Return: The file pointer *********************************/ // Opens a local copy of the file referenced by the given request URL, for // writing. Ignores any directories in the URL path, instead opening the file // in the ./Downloads. Makes up a filename if none is given. // // Returns a pointer to the open file, or a NULL pointer if the open fails. FILE* OpenLocalCopy(const URL* url) { FILE* outfile = NULL; struct stat sb; // For checking if ./Download exists if (stat("./Downloads", &sb) == -1) { // if ./Downloads does not exist mkdir("./Downloads", 0700); // create it } const std::string& fullPath = url->getPath(); size_t filenamePos = fullPath.rfind('/'); // find the last '/', the substring after it should be the filename if ((filenamePos != std::string::npos) && // if found a '/' ((filenamePos + 1) < fullPath.length())) { // or / is not the end of the std::string, there is a filename in the URL std::string fn = std::string("Downloads/") + fullPath.substr(filenamePos + 1); outfile = fopen(fn.c_str(), "wb"); } else { // there is no filename in the URL, name it index.html outfile = fopen("Downloads/index.html", "wb"); } return outfile; }

lab2_skeleton/HTTPRequest.cc

#include "HTTPRequest.h" using namespace std; HTTPRequest::HTTPRequest(const std::string& method, const std::string& path, const std::string& version) : method(method), path(path), version(version) { } HTTPRequest::~HTTPRequest() { method.clear(); path.clear(); version.clear(); } HTTPRequest *HTTPRequest::receive(TCPSocket& sock) { HTTPRequest *request; std::string incomingRequestString; std::string aLine; int zeroCount = 0; // number of attempt to read from socket sock.readLine(aLine); // get a line from the TCPSocket while (aLine != "\r\n") { incomingRequestString += aLine; aLine.clear(); // clear the variable before using it again if (sock.readLine(aLine) == 0) { // failed to get data from the TCPSocket zeroCount++; if (zeroCount >= 1000) { break; } } } incomingRequestString.append("\r\n"); request = HTTPRequest::parse(incomingRequestString.c_str(), incomingRequestString.size()); return request; } HTTPRequest* HTTPRequest::parse(const char* data, unsigned length) { HTTPRequest* request = new HTTPRequest(); // Separate the opening line (for the request) from the rest. // find the starting position of the first header (which is the second line) const char* firstHeader = request->findNextLine(data, length); if (firstHeader == NULL) { // Ouch, not even a complete first line... delete request; return NULL; } // first line should look something like this // GET /~cse422/ HTTP/1.1\r\n size_t firstLineLen = static_cast<size_t>(firstHeader - data); // Figure out that opening request line. Look for the spaces that // separate the method, URL, and version. Set as appropriate. // firstLineLen - 2 is to get rid of \r\n // requestLine == "GET /~cse422/ HTTP/1.1" std::string requestLine(data, firstLineLen - 2); size_t urlPos = requestLine.find(" "); size_t versionPos = std::string::npos; if (urlPos != std::string::npos) { request->setMethod(requestLine.substr(0, urlPos)); // obtained "GET" versionPos = requestLine.find(" ", urlPos + 1); } std::string path; if (versionPos != std::string::npos) { path = requestLine.substr(urlPos + 1, versionPos - urlPos - 1); // obtained "/~cse422/" // We are not sure if the path field here is the whole URL or just the path. // URL = http://host/path // For example: http://www.cse.msu.edu/~cse422 // host: www.cse.msu.edu // path: ~cse422 // We will parse it later when we get the host. request->setVersion(requestLine.substr(versionPos + 1)); } else { // If we couldn't get those three fields out of it, it's a bad // request, and we should stop trying to handle it. delete request; return NULL; } // Go on and handle the remaining header lines in the request. If // they're good, we're good. If not... bool headersOkay = request->parseFields(firstHeader, length - firstLineLen); std::string host; request->getHost(host); // Get rid of the extra "http://" in path field. int pos = path.find("http://"); if (pos != std::string::npos) { path.replace(pos, 7, ""); } // Get rid of the extra host field in path field pos = path.find(host); if (pos != std::string::npos) { path.replace(pos, host.length(), ""); } request->setPath(path); if (headersOkay) { return request; } else { delete request; return NULL; } } HTTPRequest* HTTPRequest::parse(const std::string& requestString) { return HTTPRequest::parse(requestString.c_str(), requestString.size()); } HTTPRequest* HTTPRequest::createGetRequest(const std::string& path, const std::string& version) { HTTPRequest* request = new HTTPRequest("GET", path, version); return request; } void HTTPRequest::send(TCPSocket& sock) { std::string outgoingBuffer; print(outgoingBuffer); sock.writeString(outgoingBuffer); } const std::string HTTPRequest::getUrl() const { std::string urlString = "http://"; std::string serverHost, serverPath; getHost(serverHost); // www.cse.msu.edu serverPath = getPath(); // /~cse422/ urlString += serverHost; urlString += serverPath; return urlString; } void HTTPRequest::getHost(std::string& outHost) const { if (!getHeaderValue("Host", outHost)) { outHost = ""; } } void HTTPRequest::print(std::string& outputString) const { outputString.clear(); // Throw in our one request line. outputString = method; outputString += ' '; outputString += path; outputString += ' '; outputString += version; outputString += lineEnding; // Now have all the headers thrown in on top of that. HTTPMessage::print(outputString); } void HTTPRequest::print(char* outputBuffer, unsigned bufferLength) const { // Similar model as the above print, except with a character buffer. copyIfRoom(outputBuffer, method.c_str(), bufferLength); copyIfRoom(outputBuffer, " ", bufferLength); copyIfRoom(outputBuffer, path.c_str(), bufferLength); copyIfRoom(outputBuffer, " ", bufferLength); copyIfRoom(outputBuffer, version.c_str(), bufferLength); copyIfRoom(outputBuffer, lineEnding.c_str(), bufferLength); HTTPMessage::print(outputBuffer, bufferLength); }

lab2_skeleton/HTTPMessage.cc

#include "HTTPMessage.h" #include <algorithm> #include <string> HTTPMessage::HTTPMessage() { // Nothing to do... } HTTPMessage::~HTTPMessage() { // Nothing to do here, either... headers.clear(); } unsigned HTTPMessage::getNumHeaderFields() const { return headers.size(); } void HTTPMessage::getHeaderSet( std::vector<std::pair<std::string, std::string> >& outSet) const { outSet.clear(); for (std::map<std::string, std::string>::const_iterator it = headers.begin(); it != headers.end(); it++) { // iterate thourhg all headers std::pair<std::string, std::string> header(it->first, it->second); outSet.push_back(header); } } bool HTTPMessage::getHeaderValue(const std::string& name, std::string& outValue) const { std::map<std::string, std::string>::const_iterator it = headers.find(name); if (it != headers.end()) { // found the header name outValue = it->second; // get the header value return true; } else { return false; } } void HTTPMessage::setHeaderField(const std::pair<std::string, std::string>& headerPair) { setHeaderField(headerPair.first, headerPair.second); } void HTTPMessage::setHeaderField(const std::string& name, const std::string& value) { headers[name] = value; } bool HTTPMessage::parseFields(const char* data, unsigned length) { // Keep parsing fields until we run up against the end of the data // or we reach the end-of-lines marking the end of the headers. const char* dataEnd = data + length; bool foundEoh = false; // found end-of-header CRLF while (data < dataEnd) { // Figure out where this header line ends. Check if it's a // blank line (signifying the end of the headers), and make // sure it has an ending at all (if it doesn't, we haven't read // the complete header yet). const char* lineEnd = findNextLine(data, length); if (lineEnd == (data + lineEnding.length())) { // lineEnding == "\r\n" foundEoh = true; break; } else if (lineEnd == NULL) { // The current data does not have a complete // line to parse break; } // We won't be working with the EOL characters, so skip 'em. lineEnd -= lineEnding.length(); // Figure out where the break between the header name and // value appears. const char* delimPos = data; for (; (delimPos < lineEnd) && (*delimPos != headerDelimiter); delimPos++) { } // If it doesn't, we've got a bad header. if (delimPos >= lineEnd) { break; } // Grab out the name & value. Trim any crud off the value // that we can. std::string name, value; name = std::string(data, static_cast<size_t>(delimPos - data)); value = std::string(delimPos + 1, static_cast<size_t>(lineEnd - delimPos - 1)); size_t ltrimPos = value.find_first_not_of(" \t\r\n"); size_t rtrimPos = value.find_last_not_of(" \t\r\n"); if (ltrimPos != std::string::npos) { value = std::string(value, ltrimPos, rtrimPos); } else { value = ""; } setHeaderField(name, value); // Jump to the next line, for the next header. data = lineEnd + lineEnding.length(); } return foundEoh; } void HTTPMessage::print(std::string& outputString) const { // Append the contents of our headers one-by-one. for (std::map<std::string, std::string>::const_iterator it = headers.begin(); it != headers.end(); it++) { outputString += it->first; outputString += headerDelimiter; outputString += " "; outputString += it->second; outputString += lineEnding; } // Toss in a final line ending to signify the headers' end. outputString += lineEnding; } void HTTPMessage::print(char* outputBuffer, unsigned bufferLength) const { const char delimString[] = {headerDelimiter, ' ', '\0'}; for (std::map<std::string, std::string>::const_iterator it = headers.begin(); it != headers.end(); it++) { copyIfRoom(outputBuffer, it->first.c_str(), bufferLength); copyIfRoom(outputBuffer, delimString, bufferLength); copyIfRoom(outputBuffer, it->second.c_str(), bufferLength); copyIfRoom(outputBuffer, lineEnding.c_str(), bufferLength); } copyIfRoom(outputBuffer, lineEnding.c_str(), bufferLength); } void HTTPMessage::copyIfRoom(char*& outputBuffer, const char* dataString, unsigned& remainingLength) const { // Quit now if there's nothing at all that we can do. if (remainingLength == 0) { return; } // Figure out how much data we've got to copy, given the remaining // space. unsigned dataLength = strlen(dataString); if (dataLength > remainingLength) { dataLength = remainingLength; } // copy exactly that much. Advance the buffer pointer accordingly. memcpy(outputBuffer, dataString, dataLength); remainingLength -= dataLength; outputBuffer += dataLength; // Be nice and null-terminate what we've written so far. *outputBuffer = '\0'; } const char* HTTPMessage::findNextLine(const char* data, unsigned length) const { // Go character-by-character through the data until we either get past // the end or we get past a line-ending std::string. Note that in the latter // case, we intentionally move a character past the line ending, so // the returned pointer will point to the *next* line. const char* dataEnd = data + length; unsigned endCharsFound = 0; while ((data < dataEnd) && (endCharsFound < lineEnding.length())) { if (*data == lineEnding[endCharsFound]) { endCharsFound++; } else { endCharsFound = 0; } data++; } // If we found the line end, great. If not, boo. if (endCharsFound >= lineEnding.length()) { return data; } else { return NULL; } }

lab2_skeleton/HTTPMessage.h

/********************************* * HTTPMessage - Base class for HTTP requests and responses. Defines the * methods for accessing the various headers on a request/response. Also * defines some internal things that are shared by the request/response classes. *********************************/ #ifndef _HTTP_MESSAGE_H_ #define _HTTP_MESSAGE_H_ #include <string.h> #include <map> #include <string> #include <utility> #include <vector> namespace { // The exact string of characters used to represent HTTP line endings. const std::string lineEnding = "\r\n"; // The character used to separate the name of a header from its value. const char headerDelimiter = ':'; } class HTTPMessage { public: /********************************* * Name: ~HTTPMessage * Purpose: destructor of HTTPMessage class objects * Receive: None * Return: None *********************************/ virtual ~HTTPMessage(); /********************************* * Name: getNumHeaderFields * Purpose: indicates how many header fields the message has. * Receive: None * Return: the number of header fields stored in the message. *********************************/ unsigned getNumHeaderFields() const; /********************************* * Name: getHeaderSet * Purpose: copies all of the message's headers into the given vector. * Use this if you need to iterate through the headers. If you * know the name of the header you want, getHeaderValue() is * far more useful. * Receive: outSet - will be set to a collection of * std::pair<std::string, std::string> representing all of * the header<name, value> stored in the message * Return: None *********************************/ void getHeaderSet(std::vector<std::pair<std::string, std::string> >& outSet) const; /********************************* * Name: * Purpose: retrieves the value of the header with the given name. * Receive: name - the name of the header to look up. * outValue - Will be set to that header's value, if it is * found. If no header with that name is found, * value will be undefined. * Return: true if the requested header name was found in the message * (in which case, outValue is valid); * false if the requested header name was not found. *********************************/ bool getHeaderValue(const std::string& name, std::string& outValue) const; /********************************* * Name: setHeaderField * Purpose: Updates the message to have the given header field. * Overwrites the old value of the specified header if * the message already had it. * Receive: field - The name/value of the header to set. * Return: None *********************************/ void setHeaderField(const std::pair<std::string, std::string>& field); /********************************* * Name: setHeaderField * Purpose: Updates the given header field in the message. If the * header is not already present, it will be added to the * message. If the header *is* already present, its previous * value will be overwritten. * Receive: name - The name of the header to set. * value - The new value to set. * Return: None *********************************/ void setHeaderField(const std::string& name, const std::string& value); protected: /********************************* * Name: ~HTTPMessage * Purpose: destructor of HTTPMessage class objects * Receive: None * Return: the number of headers stored in the message. *********************************/ HTTPMessage(); /********************************* * Name: parseFields * Purpose: parse the received data to construct the object * Receive: the data to be parsed and the length of the data * Return: true if the data is valid, false otherwise. *********************************/ bool parseFields(const char* data, unsigned length); /********************************* * Name: print * Purpose: construct a string that represents this message, for * sending or other purposes. * Receive: outputString - the string to hold the message. * Return: None *********************************/ virtual void print(std::string& outputString) const; /********************************* * Name: print * Purpose: construct a string that represents this message, for * sending or other purposes * Receive: outputBuffer - the char array to hold the message. * bufferLength - the lengthe of the buffer * Return: None *********************************/ virtual void print(char* outputBuffer, unsigned bufferLength) const; /********************************* * Name: copyIfRoom * Purpose: copy the dataString into the buffer, if the buffer stil * has room for this dataString. * Receive: outputBuffer - the char array to store dataString * dataString - the string to be copied * remainingLength - the remaining room of the buffer * Return: None *********************************/ virtual void copyIfRoom(char*& outputBuffer, const char* dataString, unsigned& remainingLength) const; /********************************* * Name: findNextLine * Purpose: scan the data char-by-char until a newline char is found. * Receive: data - the char array to be scaned * length - the length of the data * Return: the pointer points to the beginning of next line. *********************************/ const char* findNextLine(const char* data, unsigned length) const; private: std::map<std::string, std::string> headers; }; #endif // _HTTP_MESSAGE_H_

lab2_skeleton/HTTPResponse.cc

#include "HTTPResponse.h" HTTPResponse::HTTPResponse(unsigned statusCode, const std::string& statusDesc, const std::string& version, const std::string& content) { setStatusCode(statusCode); buildStatus(); setVersion("HTTP/1.1"); setHeaderField("Content-Type", "text/html"); setHeaderField("Server", "MSU/CSE422/SS17-Section001"); setHeaderField("Connection", "close"); // non-persistent setHeaderField("Date", buildTime().c_str()); } HTTPResponse::~HTTPResponse() { version.clear(); statusDesc.clear(); content.clear(); } // NOTE: // People parse the response differently. The way they slice the header // varies as well. In this implementation The header MUST END WITH \r\n\r\n. // // Examines the HTTP response header in the buffer: data. Make sure the // header is good. // // If the request succeeded, the "Content-Length" indicates the length // of the response body. According to that value, we know how many // bytes we need to recevie. // // If the request failed, or if the response is not correctly formatted // return a NULL pointer and release all resource. HTTPResponse *HTTPResponse::parse(const char* data, unsigned length) { HTTPResponse *response = new HTTPResponse(); // Separate the opening line (for the response) from the rest. // find the starting position of the first header (which is the second line) const char* firstHeader = response->findNextLine(data, length); if (firstHeader == NULL) { // Not even a complete first line... delete response; return NULL; } size_t firstLineLen = static_cast<size_t>(firstHeader - data); std::string responseLine(data, firstLineLen - 2); // parse the pieces of the response. size_t statusCodePos = responseLine.find(" "); size_t statusDescPos = std::string::npos; if (statusCodePos != std::string::npos) { response->setVersion(responseLine.substr(0, statusCodePos)); statusDescPos = responseLine.find(" ", statusCodePos + 1); } if (statusDescPos != std::string::npos) { std::string statusCodeStr = responseLine.substr(statusCodePos + 1, statusDescPos - statusCodePos - 1); response->setStatusCode(statusCodeStr); // statusCode is updated in it if ((response->statusCode < 100) || (response->statusCode >= 600)) { // bad status code delete response; return NULL; } response->setStatusDesc(responseLine.substr(statusDescPos + 1)); } else { // Missing fields = bad response. delete response; return NULL; } // Have the header lines parsed now; response line is okay. // Handled in HTTPMessage.cc bool headersOkay = response->parseFields(firstHeader, length - firstLineLen); std::string transferEncoding; response->getHeaderValue("Transfer-Encoding", transferEncoding); if (transferEncoding.find("chunked") != std::string::npos) { // chunked transfer encoding response->chunked = true; } else { // default transfer encoding response->chunked = false; } if (headersOkay) { return response; } else { delete response; return NULL; } } HTTPResponse* HTTPResponse::createStandardResponse( unsigned contentLen, unsigned statusCode, const std::string& statusDesc, const std::string& version) { HTTPResponse* response = new HTTPResponse(statusCode, statusDesc, version); // Assume we're not bothering with chunked/gzipped data. response->setHeaderField("Content-Encoding", "identity"); response->setHeaderField("Transfer-Encoding", "identity"); // Also assume that we don't want to have to keep track of connections and // we use only non-persistent connection response->setHeaderField("Connection", "close"); // HTTP requires responses to include the data of construction. // Therefore, let's set that. char timeBuffer[128]; time_t responseTime = time(NULL); strftime(timeBuffer, sizeof(timeBuffer) / sizeof(char), "%a, %d %b %Y %H:%M:%S %Z", gmtime(&responseTime)); response->setHeaderField("Date", timeBuffer); // Finally, we know how long the body's going to be, so set that, too. std::ostringstream lengthStr; lengthStr << contentLen; response->setHeaderField("Content-Length", lengthStr.str()); return response; } // For the client, it needs to remove the chunkLen from the data std::string // because the client is storing the data as a file. The chunk length // is no longer needed anymore. However, for proxies, they need to keep the // chunk length, so that the forwarded response body can be decoded/received // by the clients. int HTTPResponse::getChunkSize(std::string &data) { int chunkLen; // The value we want to obtain int chunkLenStrEnd; // The var to hold the end of chunk length std::string std::stringstream ss; // For hex to in conversion chunkLenStrEnd = data.find("\r\n"); // Find the first CLRF std::string chunkLenStr; if (chunkLenStrEnd != std::string::npos) { chunkLenStr = data.substr(0, chunkLenStrEnd); } else { return chunkLenStrEnd; } // take the chunk length std::string out // convert the chunk length std::string hex to int ss << std::hex << chunkLenStr; ss >> chunkLen; // reorganize the data // remove the chunk length std::string and the CLRF data = data.substr(chunkLenStrEnd + 2, data.length() - chunkLenStrEnd - 2); // cout << "chunkLenStr: " << chunkLenStr << std::endl; // cout << "chunkLen: " << chunkLen << std::endl; return chunkLen; } void HTTPResponse::receiveHeader(TCPSocket& sock, std::string& responseHeader, std::string& responseBody) { try { sock.readHeader(responseHeader, responseBody); } catch (std::string msg) { std::cout << "HTTPResponse throw" << msg << std::endl; throw msg; } } int HTTPResponse::receiveBody(TCPSocket& sock, std::string& responseBody, int bytesLeft) { if (bytesLeft > BUFFER_SIZE) { return sock.readData(responseBody, BUFFER_SIZE); } else { return sock.readData(responseBody, bytesLeft); } } int HTTPResponse::receiveLine(TCPSocket& sock, std::string& data) { return sock.readLine(data); } const int HTTPResponse::getContentLen() const { int len = 0; std::string len_str; if (getHeaderValue("Content-Length", len_str) == true) { std::istringstream conv(len_str); conv >> len; return len; } return -1; } void HTTPResponse::print(std::string& outputString) const { outputString.clear(); // Have the sstream library format the response line for us, since we // need to turn the status code back into a std::string somehow. std::ostringstream responseLine; responseLine << version << " " << statusCode << " " << statusDesc; // Take that and toss on the ending to get the first line... outputString = responseLine.str(); outputString += lineEnding; // ...and then add the associated headers. HTTPMessage::print(outputString); } void HTTPResponse::print(char* outputBuffer, unsigned bufferLen) const { // Similar business, though we have to be more choosy with how we // apply sstream. std::ostringstream codeStr; codeStr << statusCode; copyIfRoom(outputBuffer, version.c_str(), bufferLen); copyIfRoom(outputBuffer, " ", bufferLen); copyIfRoom(outputBuffer, codeStr.str().c_str(), bufferLen); copyIfRoom(outputBuffer, " ", bufferLen); copyIfRoom(outputBuffer, statusDesc.c_str(), bufferLen); copyIfRoom(outputBuffer, lineEnding.c_str(), bufferLen); HTTPMessage::print(outputBuffer, bufferLen); } void HTTPResponse::send(TCPSocket& sock) { std::string outgoingBuffer; print(outgoingBuffer); outgoingBuffer.append(content); sock.writeString(outgoingBuffer); } std::string HTTPResponse::buildTime() { // format a time time_t t; struct tm *ts; char result[38]; static char wdayName[7][4] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; static char monName[12][4] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; // get the time t = time(NULL); ts = gmtime(&t); // format the time std::string according to the specification, // e.g. Sun, 06 Nov 1994 08:49:37 GMT) snprintf(result, sizeof(result), "%.3s, %.2d %.3s %d %.2d:%.2d:%.2d GMT", wdayName[ts->tm_wday], ts->tm_mday, monName[ts->tm_mon], 1900+ts->tm_year, ts->tm_hour, ts->tm_min, ts->tm_sec); return std::string(result); } void HTTPResponse::buildStatus() { switch (statusCode) { case 200: statusDesc = "OK"; break; case 400: statusDesc = "Bad request"; break; case 403: statusDesc = "Forbidden"; break; case 404: statusDesc = "Not Found"; break; case 500: statusDesc = "Internal server error"; break; case 501: statusDesc = "Not implemented"; break; case 503: statusDesc = "Service unavailable"; break; default: statusDesc = "Code not implemented/recognized"; break; } }

lab2_skeleton/ProxyWorker.cc

#include "ProxyWorker.h" #include <sstream> const std::string ProxyWorker::subliminalTag = "CSE422"; ProxyWorker::ProxyWorker(TCPSocket *cs) { clientSock = cs; port = 80; // For a full blown proxy, the server information should be // obtained from each request. However, we simply assume it // to be 80 for our labs. serverUrl = NULL; // Must be obtain from each request. serverResponse = NULL; clientRequest = NULL; } ProxyWorker::~ProxyWorker() { if (serverUrl) { delete serverUrl; } if (clientRequest) { delete clientRequest; } if (serverResponse) { delete serverResponse; } serverSock.Close(); } void ProxyWorker::handleRequest() { std::string buffer; // Get HTTP request from the client, check if the request is valid by // parsing it. (parsing is done using HTTPRequest::receive) // Please refer to HTTPRequest class for the usage of HTTPRequest::receive // and/or HTTPRequest::prarse. // From the parsed request, obtain the server address (in code, serverUrl). std::cout << "New connection established." << std::endl; std::cout << "New proxy child thread started." << std::endl; std::cout << "Getting request from client..." << std::endl; if (!getRequest()) { return; } // get the request as a std::string clientRequest->print(buffer); // Just outputting the requrst. std::cout << std::endl << "Received request:" << std::endl; std::cout << "==========================================================" << std::endl; clientRequest->print(buffer); std::cout << buffer.substr(0, buffer.length() - 4) << std::endl; std::cout << "==========================================================" << std::endl; // Check if the request is valid // Terminate this ProxyWorker if it is not a valid request std::cout << "Checking request..." << std::endl; if (!checkRequest()) { return; } std::cout << "Done. The request is valid." << std::endl; std::string host; clientRequest->getHost(host); std::cout << std::endl << "Forwarding request to server " << host << "..." << std::endl; if (!forwardRequest()) { return; } // Receive the response header and modify the server header field // Receive the response body. Handle the default and chunked transfor // encoding. std::cout << "Getting the response from the server..." << std::endl; if (!getResponse()) { return; } // return the response to the client std::cout << "Returning the response to the client..." << std::endl; if (!returnResponse()) { return; } return; } bool ProxyWorker::getRequest() { /********TO BE IMPLEMENTED********/ // Get the request from the client (HTTPRequest::receive) // Chck if the request is received correctly // // Obtain the serverUrl from the request (HTTPRequest::getUrl) } bool ProxyWorker::checkRequest() { // 1. Make sure we're pointing to a server URL // Respond a 404 Not Found if the server is invalid, parse failed // (That is serverUrl == NULL) // 2. Filter out any "host" with the keyword "umich.edu" // Note that we are filtering out "host" with "umich.edu". // "path" with umich is allowed. // Respond a 403 forbidden for host with umich. // 3. Filter full URL for "harbaugh" or "Harbaugh". If keyword is found, // redirect to host="www.youtube.com" and // path = "/embed/o7iny6VmnlA?autoplay=1" // Note: the request is still valid // 4. Insert subliminal message if the requested object is a html and // does not have a subliminal tag if (true /* 1. complete the condition*/) { /********TO BE IMPLEMENTED********/ } else { // serverUrl is good if (true /* 2. complete the condition*/) { /********TO BE IMPLEMENTED********/ } else if (true /* 3. complete the condition*/) { /********TO BE IMPLEMENTED********/ } else if (URL::isHtml(clientRequest->getPath()) && // 4. (!ProxyWorker::hasSubliminalTag(clientRequest->getUrl()))) { // Check if this request has subliminalTag // If this request does not contain the subliminalTag, the // proxy does not forward this request to the serer. Instead, the proxy // returns a subliminal message response to the client. /********TO BE IMPLEMENTED********/ } else if (ProxyWorker::hasSubliminalTag(clientRequest->getUrl())){ // 4. // If this request contains the subliminalTag, the request has // been served before. The proxy handles the request like a normal proxy. // Therefore, we need to remove the subliminalTag std::string path = clientRequest->getPath(); ProxyWorker::removeSubliminalTag(path); clientRequest->setPath(path); } } return true; } bool ProxyWorker::forwardRequest() { // pass the client request to the server // connected to the server /********TO BE IMPLEMENTED********/ } bool ProxyWorker::getResponse() { /********TO BE IMPLEMENTED********/ } bool ProxyWorker::returnResponse() { /********TO BE IMPLEMENTED********/ } bool ProxyWorker::hasSubliminalTag(const std::string& url) { // Check if the url contains the subliminalTag in its fragment URL* requestUrl = URL::parse(url); // parse it, URL class does it for us if (!requestUrl) { return false; } int tagPos = url.rfind(subliminalTag); // make sure the subliminalTag is at the end of the url. if ((tagPos != std::string::npos) && (tagPos + subliminalTag.size() == url.size())) { return true; } else { return false; } } void ProxyWorker::removeSubliminalTag(std::string& url) { size_t tagPos = url.find(subliminalTag); // make sure the subliminalTag is at the end of the url. if ((tagPos != std::string::npos) && (tagPos + subliminalTag.size() == url.size())) { url = url.replace(tagPos, tagPos + subliminalTag.size(), ""); } } bool ProxyWorker::proxyResponse(int statusCode) { std::string buffer; HTTPResponse proxyResponse(statusCode); std::ostringstream oss; oss << statusCode; std::string page = "<html><body><h1>" + oss.str() + " " + proxyResponse.getStatusDesc() + "</h1></body></html>"; proxyResponse.setContent(page); std::cout << std::endl << "Returning " << statusCode << " to client ..." << std::endl; std::cout << "==========================================================" << std::endl; buffer.clear(); proxyResponse.print(buffer); std::cout << buffer.substr(0, buffer.length() - 4) << std::endl; std::cout << "==========================================================" << std::endl; proxyResponse.send(*clientSock); return true; } bool ProxyWorker::subliminalResponse(const std::string& url, int duration) { std::string buffer; // create a new HTTPResponse HTTPResponse proxyResponse(200); // Randomly choose a image int figNumber = rand() % 4; std::stringstream ss; ss << figNumber; std::string figNumberStr = ss.str(); ss.str(""); ss << duration; std::string durationStr = ss.str(); // create a webpage containing the image and automatically redirects to // original url in "duration" seconds proxyResponse.setContent("<html><head><meta http-equiv=\"refresh\" content=\"" + durationStr + ";url=" + url +subliminalTag + "\" /></head><body><center><font size=72>GO GREEN! GO WHITE!</font><br><img src=\"http://www.cse.msu.edu/~liuchinj/cse422ss17/images/" + figNumberStr + ".jpg\" width=800px><br>Redirecting...</center></body></html>"); std::cout << std::endl << "Returning subliminal to client ..." << std::endl; std::cout << "==========================================================" << std::endl; buffer.clear(); proxyResponse.print(buffer); std::cout << buffer.substr(0, buffer.length() - 4) << std::endl; std::cout << "==========================================================" << std::endl; proxyResponse.send(*clientSock); return true; }

lab2_skeleton/URL.cc

#include "URL.h" #include <sstream> namespace { // Used to signal when the port number is not known. There is an // excellent chance that this will never be a valid port for anything. const unsigned short UNDEFINED_PORT = 0xffff; } // NOTE: // We want to assume that the port hasn't been set until we know otherwise. // We also want to make sure that there's some kind of path, since HTTP // requires a path. The root path is the accepted default there. URL::URL() : port(UNDEFINED_PORT), path("/") { } URL::~URL() { // Nothing to do... } // A few things to note about how the parsing is done: // - The protocol *must* be specified. "http://example.org" will // parse. http:// will be the default protocl if not given // - If no port number is given in the URL, the returned URL object // will have the port clearly indicates as being undefined. // - If no path is given in the URL, it will be set to a forward slash // ("/"), to avoid having a blank std::string there. URL* URL::parse(const std::string& urlString) { URL* newUrl = new URL(); // Obtain the protocol from urlString size_t offset = newUrl->readProtocol(urlString); if (offset == std::string::npos) { delete newUrl; return NULL; } // Obtain the port from urlString offset = newUrl->readHostPort(urlString, offset); // if the offset has not yet read the end of the url std::string, // get the path if (offset < urlString.length()) { // Obtain the path newUrl->readPathDetails(urlString, offset); // If the client somehow input a URL with an empty path, // quietly save them from themselves. if (newUrl->path.length() == 0) { newUrl->path = "/"; } } return newUrl; } const std::string& URL::getProtocol() const { return protocol; } const std::string& URL::getHost() const { return host; } bool URL::isPortDefined() const { return (port != UNDEFINED_PORT); } unsigned URL::getPort() const { return port; } const std::string& URL::getPath() const { return path; } const std::string& URL::getQuery() const { return query; } const std::string& URL::getFragment() const { return fragment; } void URL::print(std::ostream& out) { // Say the URL is http://www.example.org:8080/example.php?example#ex // Each piece follows. Note that we should avoid printing optional // parts of the URL that have associated formatting characters, if // they aren't actually defined. // http:// out << protocol << "://"; // www.example.org out << host; // :8080 (if given) if (isPortDefined()) { out << ":" << port; } // /example.php out << path; // ?example(if given) if (query.length() > 0) { out << "?" << query; } // #ex (if given) if (fragment.length() > 0) { out << "#" << fragment; } } void URL::print(std::string& target) { // Much easier than duplicating the code. std::ostringstream targetOut; print(targetOut); target = targetOut.str(); } size_t URL::readProtocol(const std::string& urlString, size_t offset) { size_t protocolEnd = urlString.find("://", offset); if (protocolEnd == std::string::npos) { // If protocol is not specified, assume protocol is HTTP protocol = "http"; return 0; } else { protocol = urlString.substr(offset, protocolEnd - offset); return protocolEnd + 3; } } size_t URL::readHostPort(const std::string& urlString, size_t offset) { size_t partEnd = urlString.find_first_of("/#?", offset); if (partEnd == std::string::npos) { partEnd = urlString.length(); } size_t portOffset = urlString.find(":", offset); if ((portOffset == std::string::npos) || (portOffset > partEnd)) { portOffset = partEnd; } host = urlString.substr(offset, portOffset - offset); if (portOffset < partEnd) { std::string portString(urlString.substr(portOffset + 1, partEnd - portOffset - 1)); std::istringstream iss(portString); // create a istringstream iss >> port; // get the converted unsigned short int } return partEnd; } size_t URL::readPathDetails(const std::string& urlString, size_t offset) { size_t unparsedEnd = urlString.length(); // Once you hit the beginning of the fragment, that's the end of the // URL. Since it's nice to know where our limits are, let's check for // that first. size_t fragmentOffset = urlString.find("#", offset); if (fragmentOffset != std::string::npos) { fragment = urlString.substr(fragmentOffset + 1); unparsedEnd = fragmentOffset; } size_t queryOffset = urlString.find("?", offset); if ((queryOffset != std::string::npos) && (queryOffset < unparsedEnd)) { query = urlString.substr(queryOffset + 1, unparsedEnd - queryOffset - 1); unparsedEnd = queryOffset; } path = urlString.substr(offset, unparsedEnd - offset); return urlString.length(); } bool URL::isHtml(const std::string& path) { std::string end1 = "/"; std::string end2 = "html"; std::string end3 = "htm"; if ((path.rfind(end1) + end1.size() == path.size()) || (path.rfind(end2) + end2.size() == path.size()) || (path.rfind(end3) + end3.size() == path.size())) { return true; } else { return false; } } void URL::setProtocol(const std::string& protocol) { this->protocol = protocol; } void URL::setHost(const std::string& host) { this->host = host; } void URL::clearPort() { port = UNDEFINED_PORT; } void URL::setPort(unsigned short port) { this->port = port; } void URL::setPath(const std::string& path) { this->path = path; } void URL::setQuery(const std::string& query) { this->query = query; } void URL::setFragment(const std::string& fragment) { this->fragment = fragment; }

lab2_skeleton/HTTPRequest.h

/********************************* * HTTPRequest - Class representing an HTTP request message. May be used both * to parse an existing HTTP request into a comprehensible object, and to * construct new requests from scratch and print them out to a text string. * Makes no attempt to handle the body of the request -- only the request line * and the headers will be captured. * * If all you want to do is download a file, call createGetRequest() with * the path of the file that you want to download, and then call setHost() * on the returned object with the hostname of the server from which you'll * be downloading. You should then be able to Print() the request out to a * character buffer to get something that the server will accept. * * Also see the HTTPMessage class for methods that can be used to query and * set the request's headers. *********************************/ #ifndef _HTTP_REQUEST_H_ #define _HTTP_REQUEST_H_ #include "HTTPMessage.h" #include "TCPSocket.h" #include <string> class HTTPRequest : public HTTPMessage { public: /********************************* * Name: HTTPRequest * Purpose: constructor, constructs a new HTTPRequest. Note that * nothing is done to check the validity of the arguments * -- make sure you trust your input. * Receive: method - The action being requested (e.g. GET, POST, etc). * path - The URL of the resource to which the request * applies. In most cases, this will typically * just be the path of the resource on the server * (e.g. /somedir/something.txt). * version - The HTTP version of the client making the * request. Default is HTTP 1.1 (which ought to * be what you support). * Return: None *********************************/ HTTPRequest(const std::string& method = "", const std::string& path = "", const std::string& version = "HTTP/1.1"); /********************************* * Name: ~HTTPRequest * Purpose: Destructor of HTTPRequest class objects * Receive: None * Return: None *********************************/ virtual ~HTTPRequest(); /********************************* * Name: parse * Purpose: constructs an HTTPRequest object corresponding to the * actual request text in the given buffer. Use this if * you've received a request and want to know what it's * asking. * Receive: data - The text buffer in which the request is stored. * length - The length of the request data, in bytes. * Return: An HTTPRequest parsed from the request text/data. If * parsing fails, a NULL pointer will be returned instead. *********************************/ static HTTPRequest* parse(const char* data, unsigned length); /********************************* * Name: parse * Purpose: constructs an HTTPRequest object corresponding to the * actual request string. Use this if you've received a * request and want to know what it's asking. * Receive: requestString - The string in which the request is stored. * Return: An HTTPRequest parsed from the request string. If * parsing fails, a NULL pointer will be returned instead. *********************************/ static HTTPRequest* parse(const std::string& requestString); /********************************* * Name: createGetRequest * Purpose: Constructs a new HTTP GET request, with a header or * two set to make it more likely that the server will * return an easy-to-handle result. * Receive: path - The URL of the resource to get. * version - The HTTP version to associate with the request. * Return: A new HTTPRequest object for the GET request. *********************************/ static HTTPRequest* createGetRequest(const std::string& path ="", const std::string& version = "HTTP/1.1"); /********************************* * Name: send * Purpose: Send this request to the socket sock * Receive: The TCPSocket we want to send to * Return: None *********************************/ void send(TCPSocket& socket); /********************************* * Name: receive * Purpose: Receive data from the socket sock and create an * HTTPRequest object by parsing that piece of data. * Receive: socket - The TCPSocket we want to receive from * Return: receive a piece of data from the socket, until a line * with only CLRF is found, which means it is the end of * the header. Create an HTTPRequest object from that * header. *********************************/ static HTTPRequest* receive(TCPSocket& socket); /********************************* * Name: getMethod * Purpose: Looks up the method of the request (e.g. GET, PUT, DELETE). * Receive: None * Return: The request method. *********************************/ const std::string& getMethod() const { return method; } /********************************* * Name: getPath * Purpose: Looks up the path targeted by the request (e.g. /stuff.txt). * Receive: None * Return: The request's path. *********************************/ const std::string& getPath() const { return path; } /********************************* * Name: getUrl * Purpose: Looks up the URL targeted by the request * Receive: None * Return: The request's URL *********************************/ const std::string getUrl() const; /********************************* * Name: getVersion * Purpose: Looks up the HTTP version of the requesting client (e.g. * HTTP/1.1). * Receive: None * Return: The request's HTTP version. *********************************/ const std::string& getVersion() const { return version; } /********************************* * Name: getHost * Purpose: Looks up the host for which the request is intended, * from the request's Host header. * Receive: outHost - Will be set to the request's target host. If the host * has not yet been entered, it will be set to an empty string. * Return: None *********************************/ void getHost(std::string& outHost) const; /********************************* * Name: print * Purpose: Prints the request object to a text string, suitable * for transmission to an HTTP server. Includes the * terminating blank line and all request headers. * Receive: outputString - Will be set to the request text. * Return: None *********************************/ void print(std::string& outputString) const; /********************************* * Name: print * Purpose: Prints the request object to a char array, suitable * for transmission to an HTTP server. Includes the * terminating blank line and all request headers. * Receive: outputBuffer - The text buffer into which the request * should be printed. Will be null-terminated. * bufferLength - The number of characters available for * writing in the buffer. Printing stops * after this many characters have been * written. * Return: None *********************************/ void print(char* outputBuffer, unsigned bufferLength) const; /********************************* * Name: setMethod * Purpose: Sets the method of the HTTP request (e.g. GET, PUT, DELETE). * Receive: method - The method to set for the request. * Return: None *********************************/ void setMethod(const std::string& method) { this->method = method; } /********************************* * Name: setPath * Purpose: Sets the path that the request should target (e.g. /stuff.txt). * Receive: path - The path to set for the request. * Return: None *********************************/ void setPath(const std::string& path) { this->path = path; } /********************************* * Name: setVersion * Purpose: Sets the HTTP version supported by the request's client. * Receive: version - The HTTP version the request should indicate. * Return: None *********************************/ void setVersion(const std::string& version) { this->version = version; } /********************************* * Name: setHost * Purpose: Sets the host for which the request is intended, into * the request's Host header. * Receive: host - The host to set for the request. * Return: None *********************************/ void setHost(const std::string& host) { setHeaderField("Host", host); } private: std::string method; std::string path; std::string version; }; #endif // _HTTP_REQUEST_H_

lab2_skeleton/Makefile

# CSE 422 Lab 2 makefile # Modified from CSE422 FS12 # Options to set when compiling/linking the project. CXXFLAGS=-g LDFLAGS=-pthread # The name of the executable to generate. TARGET=client proxy # The objects that should be compiled from the project source files (expected # to correspond to actual source files, e.g. URL.o will come from URL.cc). # # You will want to add the name of your driver object to this list. client_OBJS=HTTPMessage.o \ HTTPRequest.o \ HTTPResponse.o \ TCPSocket.o\ URL.o \ client.o proxy_OBJS=HTTPMessage.o \ HTTPRequest.o \ HTTPResponse.o \ TCPSocket.o\ URL.o \ ProxyWorker.o \ proxy.o # Have everything built automatically based on the above settings. all: $(TARGET) .cc.o: g++ -o $@ $(CXXFLAGS) -c $< -DBUFFER_SIZE=40960 client: $(client_OBJS) g++ -o $@ $^ $(LDFLAGS) proxy: $(proxy_OBJS) g++ -o $@ $^ $(LDFLAGS) clean: $(RM) $(TARGET) $(proxy_OBJS) $(client_OBJS) $(RM) -rf Downloads # Dependencies follow (i.e. which source files and headers a given object is # built from). TCPSocket.o: TCPSocket.h TCPSocket.cc URL.o: URL.cc URL.h HTTPMessage.o: HTTPMessage.cc HTTPMessage.h HTTPRequest.o: HTTPRequest.cc HTTPRequest.h HTTPMessage.h URL.o TCPSocket.o HTTPResponse.o: HTTPResponse.cc HTTPResponse.h HTTPMessage.h URL.o TCPSocket.o client.o: client.cc client.h HTTPRequest.o HTTPResponse.o ProxyWorker.o: ProxyWorker.cc ProxyWorker.h HTTPRequest.o HTTPResponse.o URL.o proxy.o: proxy.cc ProxyWorker.o

lab2_skeleton/ProxyWorker.h

/********************************* * ProxyWorker - Class for handling HTTP connections. Each instance acts as * a single worker to handle a request for a proxy. It passes the request to * the server and return the response to the client. *********************************/ #ifndef _PROXYWORKER_H_ #define _PROXYWORKER_H_ #include <iostream> #include <string> #include "HTTPResponse.h" #include "HTTPRequest.h" #include "TCPSocket.h" #include "URL.h" class ProxyWorker { private: URL *serverUrl; // Server's URL, obtained from each request. unsigned short int port; // For a full blown proxy TCPSocket *clientSock; // The socket for client TCPSocket serverSock; HTTPRequest *clientRequest; // Obj to handle client request HTTPResponse*serverResponse; // Obj to handle server response static const std::string subliminalTag; /********************************* * Name: getRequest * Purpose: Receives a request from a client and parse it * Receive: None * Return: A boolean value indicating if getting the request is * successful or not *********************************/ bool getRequest(); /********************************* * Name: checkRequest * Purpose: Check if the request just gotten is valid * Receive: None * Return: the request is valid or not *********************************/ bool checkRequest(); /********************************* * Name: forwardRequest * Purpose: Forwards a client request to the server and get the response * 1. Forward the request to the server * 2. Receive the response header and modify the server field * 3. Receive the response body. Handle both chunk/default encoding. * Receive: None * Return: A boolean value indicating if forwarding the request is * successful or not *********************************/ bool forwardRequest(); /********************************* * Name: getResponse * Purpose: Get a response from the server * Receive: None * Return: A boolean value indicating if the request is received *********************************/ bool getResponse(); /********************************* * Name: returnResponse * Purpose: Return the response from the server to the client. Also, modify * the server field * Receive: None * Return: A boolean indicating if returning the request was successful or * not *********************************/ bool returnResponse(); /********************************* * Name: proxyResponse * Purpose: Creates a response "locally" and return it to a client * For error status like 403, 404, ... and 500. * Receive: statusCode - the status code * Return: A boolean indicating if the creation is succssful *********************************/ bool proxyResponse(const int statusCode); /********************************* * Name: hasSubliminalTag * Purpose: Check if the url has a subliminal message tag in the path * Receive: url - the url as a string * Return: true if the url has the subliminal tag, false otherwise *********************************/ static bool hasSubliminalTag(const std::string& url); /********************************* * Name: removeSubliminalTag * Purpose: Remove the subliminal message tag from the path * Receive: url - the url as a string * Return: None *********************************/ static void removeSubliminalTag(std::string& url); static const std::string& getSubliminalTag() { return subliminalTag; } /********************************* * Name: subliminalResponse * Purpose: Create a "subliminal message" response "locally" and return * it to the client * Receive: url - the original url * duration - the duration of the subliminal message * Return: a boolean indicating if the creation is succssful *********************************/ bool subliminalResponse(const std::string& url, int duration = 0); /********************************* Name: getChunkSize * Purpose: Extract the chunk size from a std::string * Receive: data - the chunk size in hex string * Return: the chunk size in int *********************************/ int getChunkSize(std::string& data); public: /********************************* * Name: ProxyWorker * Purpose: Constructor of ProxyWorker class objects * Receive: cs - the TCPSocket that is connected to the requesting client * Return: None *********************************/ explicit ProxyWorker(TCPSocket *cs); /********************************* * Name: ~ProxyWorker * Purpose: Destructor of ProxyWorker class objects * Receive: None * Return: None *********************************/ ~ProxyWorker(); /********************************* * Name: handleRequest * Purpose: handles a request by sending it to the serverUrl * Receive: None * Return: None *********************************/ void handleRequest(); }; #endif // _PROXYWORKER_H_

lab2_skeleton/TCPSocket.h

/********************************* * TCPSocket - Class wrapping the TCP operations in C++ style class. Errors are * returned by throwing exceptions. *********************************/ #ifndef _TCPSOCKET_H_ #define _TCPSOCKET_H_ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <netdb.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include "URL.h" #include <string> class TCPSocket { private: int sock; struct sockaddr_in serverAddr; /********************************* * Name: readNBytes * Purpose: Reads n bytes from the TCPSocket * Receive: vptr - the pointer to the buffer that will be used to hold the * data * n - the number of bytes to be read * Return: The number of bytes read *********************************/ int readNBytes(void* vptr, unsigned int n); /********************************* * Name: readLine * Purpose: Reads a line from the TCPSocket * Receive: vptr - the pointer to the buffer that will be used to hold the * data * maxLen - the maximum size of the line * Return: The number of bytes read *********************************/ int readLine(void* vptr, unsigned int maxLen); /********************************* * Name: readHeader * Purpose: Reads from a TCPSocket until \r\n\r\n is found, in order to * receive a complete HTTP message header. The received data is * then passed to be parsed by HTTPRequest or HTTPResponse * Receive: buffer - buffer to hold the received data * bufferLen - the maximum length of the bufer * totalReceivedLen - the total number of bytes received. * Return: The end position of the HTTP message header *********************************/ int receiveHeaders(char* buffer, unsigned int bufferLen, unsigned int& totalReceivedLen); /********************************* * Name: createSocket * Purpose: Private function that handles socket creation, despite what * connect function is used. * Receive: None * Return: None *********************************/ void createSocket(); public: /********************************* * Name: TCPSocket * Purpose: Default constructor sets the socket to -1. * Receive: None * Return: None *********************************/ TCPSocket() { sock = -1; } /********************************* * Name: ~TCPSocket * Purpose: Destructor, closes the socket by invoking close() * Receive: None * Return: None *********************************/ ~TCPSocket() { Close(); sock = -1; } /********************************* * Name: Connect, capitalized to avoid confusion with the connect in * socket.h * Purpose: Initiate a connection to a server with serverName and port number * Receive: serverName - the hostname to connect to * serverPort - the port number to connect to * Return: None *********************************/ void Connect(const std::string& serverName, unsigned short serverPort); /********************************* * Name: Connect * Purpose: Initiate a connection to a server with hostEnt and port number * Receive: hostEnt - the hostEnt structure * serverPort - the port number to connect to * Return: None *********************************/ void Connect(hostent* host, unsigned short serverPort); /********************************* * Name: Connect * Purpose: Initiate a connection to a URL * Receive: url - is the URL object holding server name, port number and * resource name * Return: None *********************************/ void Connect(const URL& url); /********************************* * Name: Close * Purpose: Closes an open socket * Receive: None * Return: None *********************************/ int Close(); /********************************* * Name: Bind * Purpose: Creates and binds to a socket in a server process * Receive: serverPort - the port number for the service * Return: None *********************************/ void Bind(unsigned short serverPort); /********************************* * Name: Listen * Purpose: Start to listen to a bound socket * Receive: None * Return: None *********************************/ void Listen(); /********************************* * Name: Accept * Purpose: Accept a connection waiting on a bound port * Receive: dataSock - is the TCPSocket object that holds the new connection * from/to the client * Return: true if the connection is accepted, false otherwise. *********************************/ bool Accept(TCPSocket& dataSock); /********************************* * Name: Accept * Purpose: Alternative form of accept that creates a new TCPSocket object * Receive: None * Return: the pointer to the new TCPSocket object *********************************/ TCPSocket *Accept(); /********************************* * Name: writeString * Purpose: Writes a string on this TCPSocket * Receive: data - the string to be written to the TCPSocket * Return: The number of bytes written, should always equal to data.size() *********************************/ int writeString(const std::string& data); /********************************* * Name: readString * Purpose: Reads a string from this TCPSocket * Receive: data - the string to hold the received bytes * Return: The number of bytes read from the TCPSocket *********************************/ int readString(std::string& data); /********************************* * Name: readHeader * Purpose: Reads from a TCPSocket until \r\n\r\n is found, in order to * receive a complete HTTP message header. * Receive: header - the variable to hold the header * body - the variable to hold the (possibly partial) body * Return: None *********************************/ void readHeader(std::string& header, std::string& body); /********************************* * Name: readData * Purpose: Read bytesLeft bytes from the TCPSocket * Receive: data - the string that will be used to hold the data * bytesLeft- the number of bytes to be read * Return: the number of bytes read *********************************/ int readData(std::string& data, unsigned int bytesLeft); /********************************* * Name: readLine * Purpose: Reads a line from the TCPSocket, terminated by a CRLF (\r\n) * Receive: data - holds the data read from the TCPSocket * Return: The number of bytes read *********************************/ int readLine(std::string& data); /********************************* * Name: getPort * Purpose: Get the port number of the TCPSocket * Receive: gettingPort - holds the port number * Return: None *********************************/ void getPort(unsigned short& gettingPort); }; #endif // _TCPSOCKET_H_

lab2_skeleton/URL.h

/********************************* * URL - Represents most aspects of a uniform resource identifier (URL). Can * be used to parse existing URL strings into their component parts, and to form * new URL strings piece-by-piece from those components. * * Expects URLs to be formatted in the following manner (note that most fields * are optional, depending on which other fields are also present): * * protocol: *host:port/path?query#fragment *********************************/ #ifndef _URL_H_ #define _URL_H_ #include <iostream> #include <string> class URL { public: /********************************* * Name: URL * Purpose: constructor of URL class objects * Receive: None * Return: None *********************************/ URL(); /********************************* * Name: ~URL * Purpose: destructor of URL class objects * Receive: None * Return: None *********************************/ ~URL(); /********************************* * Name: parse * Purpose: Creates a new URL based on the contents of the given string. * Receive: urlString - The URL string to parse. * Return: A URL object with its components taken from the given * string. If the given string is not formatted like a proper * URL and cannot be parsed, a NULL pointer will be returned * instead. *********************************/ static URL* parse(const std::string& urlString); /********************************* * Name: isHtml * Purpose: Check if the path in the given string points to an HTML file * does NOT check if the path is valid * Receive: urlString - The URL string to checked. * Return: true if the path is an html, false otherwise *********************************/ static bool isHtml(const std::string& pathString); /********************************* * Name: getProtocol * Purpose: Looks up the protocol given in the URL. * Receive: None * Return: The URL's protocol. *********************************/ const std::string& getProtocol() const; /********************************* * Name: getHost * Purpose: Looks up the target host of the URL. * Receive: None * Return: The URL's host. *********************************/ const std::string& getHost() const; /********************************* * Name: isPortDefined * Purpose: Checks if the URL refers to a specific port, or none at all. * Receive: None * Return: true if the URL has a defined port, false if not. *********************************/ bool isPortDefined() const; /********************************* * Name: getPort * Purpose: Looks up the port number to which the URL refers. * Receive: None * Return: The URL's port. If isPortDefined() returns false, this * value is meaningless. *********************************/ unsigned getPort() const; /********************************* * Name: getPath * Purpose: Looks up the path of the resource to which the URL refers. * Receive: None * Return: The URL's path. *********************************/ const std::string& getPath() const; /********************************* * Name: getQuery * Purpose: Looks up the query part of the URL (which may be used to identify * a resource in a non-hierarchical manner, unlike the path). * Receive: None * Return: The URL's query. *********************************/ const std::string& getQuery() const; /********************************* * Name: getFragment * Purpose: Looks up the fragment of the primary resource to which the URL * specifically refers (e.g. an anchor in a web page). * Receive: None * Return: The URL's fragment. *********************************/ const std::string& getFragment() const; /********************************* * Name: print * Purpose: Has the URL printed to the given output stream, in standard format. * Receive: out - The output stream to which to print the URL. * Return: None *********************************/ void print(std::ostream& out); /********************************* * Name: print * Purpose: Has the URL printed into the given string, in standard format. * Receive: target - Will be set to a string representation of this URL. * Return: None *********************************/ void print(std::string& target); /********************************* * Name: setProtocol * Purpose: Sets the URL protocol to the given string. * Receive: protocol - The protocol to set. * Return: None *********************************/ void setProtocol(const std::string& protocol); /********************************* * Name: setHost * Purpose: Sets the URL's host to the given string. * Receive: host - The host to set. * Return: None *********************************/ void setHost(const std::string& host); /********************************* * Name: clearPort * Purpose: Throws out the port of the URL, making it undefined. * Receive: None * Return: None *********************************/ void clearPort(); /********************************* * Name: setPort * Purpose: Sets a specific port number for the URL. * Receive: port - The port to set. * Return: None *********************************/ void setPort(unsigned short port); /********************************* * Name: setPath * Purpose: Sets the path of the resource to which the URL refers. * Receive: path - The path to set. * Return: None *********************************/ void setPath(const std::string& path); /********************************* * Name: setQuery * Purpose: Sets the query string for the URL. * Receive: query - The query to set. Can be used to effectively delete the * URL's query by passing in a blank string. * Return: None *********************************/ void setQuery(const std::string& query); /********************************* * Name: setFragment * Purpose: Sets the fragment for the URL. * Receive: fragment - The fragment to set. Will effectively delete the URL's * fragment if you pass in a blank string. * Return: None *********************************/ void setFragment(const std::string& fragment); private: /********************************* * Name: readProtocol * Purpose: read the protocol from the URL string * Receive: urlString - the URL string to be parsed * offset - the offset, the position to start parsing * default = 0 * Return: the offset indicates that the part before this offset * has been parsed *********************************/ size_t readProtocol(const std::string& urlString, size_t offset = 0); /********************************* * Name: readHostPort * Purpose: read the port from the URL string, if specified * Receive: urlString - the URL string to be parsed * offset - the offset, the position to start parsing * Return: the offset indicates that the part before this offset * has been parsed *********************************/ size_t readHostPort(const std::string& urlString, size_t offset); /********************************* * Name: readPathDetails * Purpose: read the path from the URL string, if specified * Receive: urlString - the URL string to be parsed * offset - the offset, the position to start parsing * Return: the offset indicates that the part before this offset * has been parsed *********************************/ size_t readPathDetails(const std::string& urlString, size_t offset); std::string protocol; std::string host; unsigned short port; std::string path; std::string query; std::string fragment; }; #endif // _URL_H_

lab2_skeleton/.URL.h.swp

lab2_skeleton/proxy.cc

#include "proxy.h" /********************************* * Name: ClientHandler * Purpose: Handles a single web request * Receive: The data socket pointer * Return: None *********************************/ void* connectionHandler(void *arg) { TCPSocket *clientSock = (TCPSocket *) arg; /********TO BE IMPLEMENTED********/ // Create a ProxyWorker to handle this connection. // When done handling the connection, remember to close and delete // the socket, and delete the ProxyWorker pthread_exit(0); } /********************************* * Name: main * Purpose: Contains the main server loop that handles requests by * spawing threads * Receive: argc is the number of command line params, argv are the * parameters * Return: 0 on clean exit, and 1 on error *********************************/ int main(int argc, char *argv[]) { signal(SIGPIPE, SIG_IGN); // To ignore SIGPIPE TCPSocket* clientSock; // for accepting connections int rc; // return code for pthread parseArgs(argc, argv); /********TO BE IMPLEMENTED********/ // Creata a socket, bind it and listen for incoming connection. std::cout << "Proxy running at " << port << "..." << std::endl; // start the infinite server loop while (true) { /********TO BE IMPLEMENTED********/ break; // remove this break when you have TCPSocket::Accept. This break // is to stop the infinite loop from creating too many thread and // crashs the program // accept incoming connections // create new thread pthread_t thread; rc = pthread_create(&thread, NULL, connectionHandler, clientSock); // if rc != 0, the creation of threadis failed. if (rc) { std::cerr << "Thread create error! Error code: " << rc << std::endl; exit(1); } } /********TO BE IMPLEMENTED********/ // close the listening sock std::cout << "Parent process termianted." << std::endl; return 0; }

lab2_skeleton/proxy.h

#include "HTTPRequest.h" #include "HTTPResponse.h" #include "URL.h" #include "TCPSocket.h" #include "ProxyWorker.h" #include <pthread.h> #include <signal.h> #include <unistd.h> #include <iostream> #include <string> int port = 80; hostent *server = NULL; void helpMessage(char *argv[]) { std::cout << "Usage " << argv[0] << " [options]" << std::endl; std::cout << "The following option is available:" << std::endl; std::cout << " -h Display help message" << std::endl; } /********************************* * Name: parseAgrv * Purpose: Parse the parameters * Recieve: argv and argc * Return: none *********************************/ void parseArgs(int argc, char *argv[]) { char *endptr; // for strtol for (int i = 1; i < argc; i++) { if ((!strncmp(argv[i], "-h", 2)) || (!strncmp(argv[i], "-H", 2))) { helpMessage(argv); exit(1); } else { std::cerr << "Invalid parameter:" << argv[i] << std::endl; helpMessage(argv); exit(1); } } }