Skip to content

Commit

Permalink
Moved common parsing functions into HTTPMessage. Finished preliminary…
Browse files Browse the repository at this point in the history
… work on HTTPResponse. Corrected getLine() to only read up to one line and not move past blank lines
  • Loading branch information
Ramsey Kant committed Apr 1, 2012
1 parent ead89bf commit 570fbb6
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 89 deletions.
109 changes: 96 additions & 13 deletions src/examples/http/HTTPMessage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,22 @@ void HTTPMessage::putLine(string str, bool crlf_end) {
putBytes((byte*)str.c_str(), str.size());
}

/**
* Put Headers
* Write all headers currently in the 'headers' map to the ByteBuffer.
* 'Header: value'
*/
void HTTPMessage::putHeaders() {
map<string,string>::const_iterator it;
for(it = headers->begin(); it != headers->end(); it++) {
string final = it->first + ": " + it->second;
putLine(final, true);
}

// End with a blank line
putLine();
}

/**
* Get Line
* Retrive the entire contents of a line: string from current position until CR or LF, whichever comes first, then increment the read position
Expand Down Expand Up @@ -87,7 +103,11 @@ string HTTPMessage::getLine() {
}

// Increment the read position until the end of a CR or LF chain, so the read position will then point to the next line
// Also, only read a maximum of 2 characters so as to not skip a blank line that is only \r\n
unsigned int k = 0;
for(unsigned int i = getReadPos(); i < size(); i++) {
if(k++ >= 2)
break;
c = getChar();
if((c != 13) && (c != 10)) {
// Set the Read position back one because the retrived character wasn't a LF or CR
Expand Down Expand Up @@ -130,6 +150,82 @@ string HTTPMessage::getStrElement(char delim) {
return ret;
}

/**
* Parse Headers
* When an HTTP message (request & response) has reached the point where headers are present, this method
* should be called to parse and populate the internal map of headers.
* Parse headers will move the read position past the blank line that signals the end of the headers
*/
void HTTPMessage::parseHeaders() {
string hline = "", app = "";

// Get the first header
hline = getLine();

// Keep pulling headers until a blank line has been reached (signaling the end of headers)
while(hline.size() > 0) {
// Case where values are on multiple lines ending with a comma
app = hline;
while(app[app.size()-1] == ',') {
app = getLine();
hline += app;
}

addHeader(hline);
hline = getLine();
}
}

/**
* Parse Body
* Parses everything after the headers section of an HTTP message. Handles chuncked responses/requests
*
* @return True if successful. False on error, parseErrorStr is set with a reason
*/
bool HTTPMessage::parseBody() {
// Content-Length should exist (size of the Body data) if there is body data
string hlenstr = "";
int contentLen = 0;
hlenstr = getHeaderValue("Content-Length");

// No body data to read:
if(hlenstr.empty())
return true;

contentLen = atoi(hlenstr.c_str());

// contentLen should NOT exceed the remaining number of bytes in the buffer
// Add 1 to bytesRemaining so it includes the byte at the current read position
if(contentLen > bytesRemaining()+1) {
/*
// If it exceeds, read only up to the number of bytes remaining
dataLen = bytesRemaining();
*/
// If it exceeds, there's a potential security issue and we can't reliably parse
stringstream pes;
pes << "Content-Length (" << hlenstr << ") is greater than remaining bytes (" << bytesRemaining() << ")";
parseErrorStr = pes.str();
return false;
} else {
// Otherwise, we ca probably trust Content-Length is valid and read the specificed number of bytes
dataLen = contentLen;
}

// Create a big enough buffer to store the data
unsigned int dIdx = 0, s = size();
data = new byte[dataLen];

// Grab all the bytes from the current position to the end
for(unsigned int i = getReadPos(); i < s; i++) {
data[dIdx] = get(i);
dIdx++;
}

// TODO: Handle chuncked Request/Response parsing (with footers) here

return true;
}

/**
* Add Header to the Map from string
* Takes a formatted header string "Header: value", parse it, and put it into the map as a key,value pair.
Expand Down Expand Up @@ -168,19 +264,6 @@ void HTTPMessage::addHeader(string key, string value) {
headers->insert(pair<string, string>(key, value));
}

/**
* Put Headers
* Write all headers currently in the 'headers' map to the ByteBuffer.
* 'Header: value'
*/
void HTTPMessage::putHeaders() {
map<string,string>::const_iterator it;
for(it = headers->begin(); it != headers->end(); it++) {
string final = it->first + ": " + it->second;
putLine(final, true);
}
}

/**
* Get Header Value
* Given a header name (key), return the value associated with it in the headers map
Expand Down
13 changes: 9 additions & 4 deletions src/examples/http/HTTPMessage.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,9 @@

// Constants
#define HTTP_VERSION "HTTP/1.1"
#define SERVER_HEAD "httpserver/1.0 ramsey"
#define NUM_METHODS 9

// HTTP Methods (Requests)
const static unsigned int NUM_METHODS = 9;

enum Method {
HEAD = 0,
Expand Down Expand Up @@ -68,6 +67,7 @@ enum Status {
// 3xx Redirection

// 4xx Client Error
BAD_REQUEST = 400,
NOT_FOUND = 404,

// 5xx Server Error
Expand Down Expand Up @@ -104,14 +104,19 @@ class HTTPMessage : public ByteBuffer {
virtual byte* create(bool freshCreate=false) {return NULL;};
virtual bool parse() {return false;};

void putLine(string str, bool crlf_end=true);
// Create helpers
void putLine(string str = "", bool crlf_end=true);
void putHeaders();

// Parse helpers
string getLine();
string getStrElement(char delim = 0x20); // 0x20 = "space"
void parseHeaders();
bool parseBody();

// Header Map manipulation
void addHeader(string line);
void addHeader(string key, string value);
void putHeaders();
string getHeaderValue(string key);
string getHeaderStr(int index);
int getNumHeaders();
Expand Down
42 changes: 7 additions & 35 deletions src/examples/http/HTTPRequest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ byte* HTTPRequest::create(bool freshCreate) {
* @param True if successful. If false, sets parseErrorStr for reason of failure
*/
bool HTTPRequest::parse() {
string initial = "", methodName = "", hline = "", app = "";
string initial = "", methodName = "";

// Get elements from the initial line: <method> <path> <version>\r\n
methodName = getStrElement();
Expand All @@ -151,45 +151,17 @@ bool HTTPRequest::parse() {
return false;
}

// Get the first header
hline = getLine();

// Keep pulling headers until a blank line has been reached (signaling the end of headers)
while(hline.size() > 0) {
// Case where values are on multiple lines ending with a comma
app = hline;
while(app[app.size()-1] == ',') {
app = getLine();
hline += app;
}

addHeader(hline);
hline = getLine();
}
// Parse and populate the headers map using the parseHeaders helper
parseHeaders();

// Only POST and PUT can have Content (data after headers)
if((method != POST) && (method != PUT))
return true;

// Content-Length should exist. If it does, pull the data starting from here until the end of the buffer
// The reason we don't use the Content-Length header's value is because I don't want to assume where the byte count starts from
string hlen = "";
hlen = getHeaderValue("Content-Length");
if(hlen.empty()) {
parseErrorStr = "Something went wrong with retriving the Content-Length";
return false;
}

// Create a big enough buffer to store the data
unsigned int dIdx = 0, s = size();
dataLen = bytesRemaining();
data = new byte[dataLen];

// Grab all the bytes from the current position to the end
for(unsigned int i = getReadPos(); i < s; i++) {
data[dIdx] = get(i);
dIdx++;
}
// Parse the body of the message
if(!parseBody())
return false;

return true;
}

38 changes: 10 additions & 28 deletions src/examples/http/HTTPResponse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ string HTTPResponse::getStatusStr() {
case Status(OK):
ret << "OK";
break;
case Status(BAD_REQUEST):
ret << "Bad Request";
break;
case Status(NOT_FOUND):
ret << "Not Found";
break;
Expand Down Expand Up @@ -137,35 +140,14 @@ bool HTTPResponse::parse() {
return false;
}

// Parse and populate the headers map using the parseHeaders helper
parseHeaders();

// If the body of the message
if(!parseBody())
return false;

return true;
}

/*
string HTTPResponse::generateResponse() {
stringstream resp;
// Add common headers
addHeader("Server", SERVER_HEAD);
// Date Header. Ex: Date: Fri, 31 Dec 1999 23:59:59 GMT
char date[80];
time_t tm;
struct tm *gmt;
time(&tm);
gmt = gmtime(&tm);
strftime(date, 80, "%a, %d %b %Y %H:%M:%S GMT", gmt);
addHeader("Date", date);
// Status Line: HTTP-Version SP Status-Code SP Reason-Phrase CRLF
resp << HTTP_VERSION << " " << status << " " << getStatusStr() << "\r\n";
// Loop through headers and dump each line by line
// Message Body
// End with a new line
resp << "\n";
return resp.str();
}*/

57 changes: 48 additions & 9 deletions src/examples/http/http.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,26 +30,42 @@ int main() {
HTTPRequest *req = new HTTPRequest("POST /sample/path.html HTTP/1.1\r\nHeader1: value1\r\nHeader2: value2\r\nHeader3: value3\r\nContent-Length: 5\r\n\r\ndata");
HTTPRequest *req2 = new HTTPRequest();
HTTPRequest *req3 = NULL;
//HTTPResponse *resp = new HTTPResponse();
HTTPResponse *res = new HTTPResponse("HTTP/1.1 400 Bad Request\r\nContent-Type: text/html\r\nContent-Length: 111\r\n\r\n<html><body>\n<h2>No Host: header received</h2>\nHTTP 1.1 requests must include the Host: header.\n</body></html>");

printf("HTTP Test Cases:\n");

// Test getLine() in HTTPMessage

string l1 = "", l2 = "", l3 = "", l4 = "";
l1 = msg->getLine(); // Expected: test1
l2 = msg->getLine(); // Expected: test2
l1 = msg->getLine(); // Expected: line1
if(strcmp(l1.c_str(), "line1") != 0) {
printf("l1 mismatch. Got: %s. Expected: line1\n", l1.c_str());
testFailed = true;
}
l2 = msg->getLine(); // Expected: line2
if(strcmp(l2.c_str(), "line2") != 0) {
printf("l2 mismatch. Got: %s. Expected: line2\n", l2.c_str());
testFailed = true;
}
l3 = msg->getLine(); // Expected:
if(!l3.empty()) {
printf("l3 mismatch. Got: %s. Expected to be blank\n", l3.c_str());
testFailed = true;
}
l4 = msg->getLine(); // Expected:
if(!l4.empty()) {
printf("l4 mismatch. Got: %s. Expected to be blank\n", l4.c_str());
testFailed = true;
}

printf("%s (%u)\n%s (%u)\n%s (%u)\n%s (%u)\n\n", l1.c_str(), (unsigned int)l1.size(), l2.c_str(), (unsigned int)l2.size(), l3.c_str(), (unsigned int)l3.size(), l4.c_str(), (unsigned int)l4.size());

// Test HTTPRequest parse()
if(!req->parse()) {
printf("HTTPRequest had a parse error: %s\n", req->getParseError().c_str());
printf("HTTPRequest (req) had a parse error: %s\n", req->getParseError().c_str());
testFailed = true;
} else {
printf("HTTPRequest: %i %s\n", req->getMethod(), req->getVersion().c_str());
printf("HTTPRequest(req): %i %s\n", req->getMethod(), req->getVersion().c_str());
byte *data = req->getData();
printf("Data (%i):\n", req->getDataLength());
for(unsigned int i = 0; i < req->getDataLength(); i++) {
Expand Down Expand Up @@ -88,7 +104,7 @@ int main() {
testFailed = true;
} else {
string req3Header = req3->methodIntToStr(req3->getMethod()) + " " + req3->getRequestUri() + " " + req3->getVersion();
printf("%s\n", req3Header.c_str());
printf("HTTPResponse(res3): %s\n", req3Header.c_str());
printf("req3 headers (%i):\n", req3->getNumHeaders());
for(int i = 0; i < req3->getNumHeaders(); i++) {
printf("%s\n", req3->getHeaderStr(i).c_str());
Expand All @@ -104,17 +120,40 @@ int main() {
}
printf("\n\n");
}

// Test HTTPResponse(res) parse()

if(!res->parse()) {
printf("res parse error: %s\n", res->getParseError().c_str());
testFailed = true;
} else {
printf("HTTPResponse(res): %s %s\n", res->getVersion().c_str(), res->getStatusStr().c_str());
printf("res headers (%i):\n", res->getNumHeaders());
for(int i = 0; i < res->getNumHeaders(); i++) {
printf("%s\n", res->getHeaderStr(i).c_str());
}
printf("res data(%i):\n", res->getDataLength());
byte* resData = res->getData();
for(unsigned int i = 0; i < res->getDataLength(); i++) {
printf("0x%02x ", resData[i]);
}
printf("\n");
for(unsigned int i = 0;i < res->getDataLength(); i++) {
printf("%c", resData[i]);
}
printf("\n\n");
}

delete msg;
delete req;
delete req2;
delete req3;
//delete resp;
delete res;

if(testFailed) {
printf("TEST FAILED: Read through output carefully to find point of failure\n");
printf("TEST PROGRAM FAILED: Read through output carefully to find point of failure\n");
} else {
printf("TEST PASSED\n");
printf("TEST PROGRAM PASSED\n");
}

return 0;
Expand Down

0 comments on commit 570fbb6

Please sign in to comment.