diff --git a/lib/httpserver/HTTPRequest.cpp b/lib/httpserver/HTTPRequest.cpp index 7fa3dee94..4a896dfc1 100644 --- a/lib/httpserver/HTTPRequest.cpp +++ b/lib/httpserver/HTTPRequest.cpp @@ -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) diff --git a/lib/httpserver/HTTPRequest.h b/lib/httpserver/HTTPRequest.h index ad8de3f35..3071e1e3b 100644 --- a/lib/httpserver/HTTPRequest.h +++ b/lib/httpserver/HTTPRequest.h @@ -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), @@ -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; @@ -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();} @@ -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; diff --git a/test/httpserver/testhttpserver.cpp b/test/httpserver/testhttpserver.cpp index 97ed1499c..70560b6b6 100644 --- a/test/httpserver/testhttpserver.cpp +++ b/test/httpserver/testhttpserver.cpp @@ -272,6 +272,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"); @@ -283,15 +331,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);