Skip to content

Commit

Permalink
Add query parameter editing and encoding to HTTPRequest
Browse files Browse the repository at this point in the history
  • Loading branch information
qris committed Jul 27, 2017
1 parent 5263aa3 commit a7f3aec
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 5 deletions.
7 changes: 7 additions & 0 deletions lib/httpserver/HTTPRequest.cpp
Expand Up @@ -504,6 +504,13 @@ void HTTPRequest::SendHeaders(IOStream &rStream, int Timeout, bool ExpectContinu

rStream.Write(" ");
rStream.Write(mRequestURI.c_str());
for(Query_t::iterator i = mQuery.begin(); i != mQuery.end(); i++)
{
rStream.Write(
((i == mQuery.begin()) ? "?" : "&") +
HTTPQueryDecoder::URLEncode(i->first) + "=" +
HTTPQueryDecoder::URLEncode(i->second));
}
rStream.Write(" ");

switch (mHTTPVersion)
Expand Down
53 changes: 53 additions & 0 deletions lib/httpserver/HTTPRequest.h
Expand Up @@ -56,6 +56,7 @@ class HTTPRequest : public CollectInBufferStream
mRequestURI(to_copy.mRequestURI),
mQueryString(to_copy.mQueryString),
mHTTPVersion(to_copy.mHTTPVersion),
mQuery(to_copy.mQuery),
// it's not safe to copy this, as it may be consumed or destroyed:
mpCookies(NULL),
mHeaders(to_copy.mHeaders),
Expand All @@ -72,6 +73,7 @@ class HTTPRequest : public CollectInBufferStream
mRequestURI = to_copy.mRequestURI;
mQueryString = to_copy.mQueryString;
mHTTPVersion = to_copy.mHTTPVersion;
mQuery = to_copy.mQuery;
// it's not safe to copy this; as it may be modified or destroyed:
mpCookies = NULL;
mHeaders = to_copy.mHeaders;
Expand Down Expand Up @@ -119,6 +121,32 @@ class HTTPRequest : public CollectInBufferStream
const std::string &GetQueryString() const {return mQueryString;}
int GetHTTPVersion() const {return mHTTPVersion;}
const Query_t &GetQuery() const {return mQuery;}
void AddParameter(const std::string& name, const std::string& value)
{
mQuery.insert(QueryEn_t(name, value));
}
void SetParameter(const std::string& name, const std::string& value)
{
mQuery.erase(name);
mQuery.insert(QueryEn_t(name, value));
}
void RemoveParameter(const std::string& name)
{
mQuery.erase(name);
}
std::string GetParameterString(const std::string& name,
const std::string& default_value)
{
return GetParameterString(name, default_value, false); // !required
}
std::string GetParameterString(const std::string& name)
{
return GetParameterString(name, "", true); // required
}
const Query_t GetParameters() const
{
return mQuery;
}

int GetContentLength() const {return mHeaders.GetContentLength();}
const std::string &GetContentType() const {return mHeaders.GetContentType();}
Expand Down Expand Up @@ -161,6 +189,31 @@ class HTTPRequest : public CollectInBufferStream
}

private:
std::string GetParameterString(const std::string& name,
const std::string& default_value, bool required)
{
Query_t::iterator i = mQuery.find(name);
if(i == mQuery.end())
{
if(required)
{
THROW_EXCEPTION_MESSAGE(HTTPException, ParameterNotFound,
name);
}
else
{
return default_value;
}
}
const std::string& value(i->second);
i++;
if(i != mQuery.end() && i->first == name)
{
THROW_EXCEPTION_MESSAGE(HTTPException, DuplicateParameter, name);
}
return value;
}

void ParseCookies(const std::string &rCookieString);

enum Method mMethod;
Expand Down
73 changes: 68 additions & 5 deletions test/httpserver/testhttpserver.cpp
Expand Up @@ -249,6 +249,54 @@ bool test_httpserver()
TEST_EQUAL("dc3b8c5e57e71d31a0a9d7cbeee2e011", digest);
}

// Test that HTTPRequest with parameters is encoded correctly
{
HTTPRequest request(HTTPRequest::Method_GET, "/newfile");
CollectInBufferStream request_buffer;
request.SendHeaders(request_buffer, IOStream::TimeOutInfinite);
request_buffer.SetForReading();

std::string request_str((const char *)request_buffer.GetBuffer(),
request_buffer.GetSize());
const std::string expected_str("GET /newfile HTTP/1.1\r\nConnection: close\r\n\r\n");
TEST_EQUAL(expected_str, request_str);

request.AddParameter("foo", "Bar");
request_buffer.Reset();
request.SendHeaders(request_buffer, IOStream::TimeOutInfinite);
request_str = std::string((const char *)request_buffer.GetBuffer(),
request_buffer.GetSize());
TEST_EQUAL("GET /newfile?foo=Bar HTTP/1.1\r\nConnection: close\r\n\r\n", request_str);

request.AddParameter("foo", "baz");
request_buffer.Reset();
request.SendHeaders(request_buffer, IOStream::TimeOutInfinite);
request_str = std::string((const char *)request_buffer.GetBuffer(),
request_buffer.GetSize());
TEST_EQUAL("GET /newfile?foo=Bar&foo=baz HTTP/1.1\r\nConnection: close\r\n\r\n", request_str);

request.SetParameter("whee", "bonk");
request_buffer.Reset();
request.SendHeaders(request_buffer, IOStream::TimeOutInfinite);
request_str = std::string((const char *)request_buffer.GetBuffer(),
request_buffer.GetSize());
TEST_EQUAL("GET /newfile?foo=Bar&foo=baz&whee=bonk HTTP/1.1\r\nConnection: close\r\n\r\n", request_str);

request.SetParameter("foo", "bolt");
request_buffer.Reset();
request.SendHeaders(request_buffer, IOStream::TimeOutInfinite);
request_str = std::string((const char *)request_buffer.GetBuffer(),
request_buffer.GetSize());
TEST_EQUAL("GET /newfile?foo=bolt&whee=bonk HTTP/1.1\r\nConnection: close\r\n\r\n", request_str);

HTTPRequest newreq = request;
TEST_EQUAL("bolt", newreq.GetParameterString("foo"));
TEST_EQUAL("bonk", newreq.GetParameterString("whee"));
TEST_EQUAL("blue", newreq.GetParameterString("colour", "blue"));
TEST_CHECK_THROWS(newreq.GetParameterString("colour"), HTTPException,
ParameterNotFound);
}

// Test that HTTPRequest can be written to and read from a stream.
{
HTTPRequest request(HTTPRequest::Method_PUT, "/newfile");
Expand All @@ -260,15 +308,30 @@ bool test_httpserver()
request.AddHeader("Content-Type", "text/plain");
request.SetClientKeepAliveRequested(true);

// Stream it to a CollectInBufferStream
// First stream just the headers into a CollectInBufferStream, and check the
// exact contents written:
CollectInBufferStream request_buffer;

// Because there isn't an HTTP server to respond to us, we can't use
// SendWithStream, so just send the content after the request.
request.SendHeaders(request_buffer, IOStream::TimeOutInfinite);
request_buffer.SetForReading();
const std::string request_str((const char *)request_buffer.GetBuffer(),
request_buffer.GetSize());
const std::string expected_str(
"PUT /newfile HTTP/1.1\r\n"
"Content-Type: text/plain\r\n"
"Host: quotes.s3.amazonaws.com\r\n"
"Connection: keep-alive\r\n"
"date: Wed, 01 Mar 2006 12:00:00 GMT\r\n"
"authorization: AWS " EXAMPLE_S3_ACCESS_KEY ":XtMYZf0hdOo4TdPYQknZk0Lz7rw=\r\n"
"\r\n");
TEST_EQUAL(expected_str, request_str);

// Now stream the entire request into the CollectInBufferStream. Because there
// isn't an HTTP server to respond to us, we can't use SendWithStream, so just
// send the headers and then the content separately:
request_buffer.Reset();
request.SendHeaders(request_buffer, IOStream::TimeOutInfinite);
FileStream fs("testfiles/photos/puppy.jpg");
fs.CopyStreamTo(request_buffer);

request_buffer.SetForReading();

IOStreamGetLine getLine(request_buffer);
Expand Down

0 comments on commit a7f3aec

Please sign in to comment.