added a maximum request size to CHTTPProtocol so we can bail

on clients that cause us to use too much memory.  also put
methods in CHTTPRequest to get/set headers and changed the
data structure used to store them.  fixed a couple of other
miscellaneous bugs in CHTTPProtocol.cpp.
This commit is contained in:
crs 2002-06-02 13:34:35 +00:00
parent fa4d24216f
commit 1da9be88c9
4 changed files with 190 additions and 67 deletions

View File

@ -56,15 +56,86 @@ bool CHTTPUtil::CaselessCmp::operator()(
return less(a, b); return less(a, b);
} }
//
// CHTTPRequest
//
CHTTPRequest::CHTTPRequest()
{
// do nothing
}
CHTTPRequest::~CHTTPRequest()
{
// do nothing
}
void CHTTPRequest::insertHeader(
const CString& name, const CString& value)
{
CHeaderMap::iterator index = m_headerByName.find(name);
if (index != m_headerByName.end()) {
index->second->second = value;
}
else {
CHeaderList::iterator pos = m_headers.insert(
m_headers.end(), std::make_pair(name, value));
m_headerByName.insert(std::make_pair(name, pos));
}
}
void CHTTPRequest::appendHeader(
const CString& name, const CString& value)
{
CHeaderMap::iterator index = m_headerByName.find(name);
if (index != m_headerByName.end()) {
index->second->second += ",";
index->second->second += value;
}
else {
CHeaderList::iterator pos = m_headers.insert(
m_headers.end(), std::make_pair(name, value));
m_headerByName.insert(std::make_pair(name, pos));
}
}
void CHTTPRequest::eraseHeader(const CString& name)
{
CHeaderMap::iterator index = m_headerByName.find(name);
if (index != m_headerByName.end()) {
m_headers.erase(index->second);
}
}
bool CHTTPRequest::isHeader(const CString& name) const
{
return (m_headerByName.find(name) != m_headerByName.end());
}
CString CHTTPRequest::getHeader(const CString& name) const
{
CHeaderMap::const_iterator index = m_headerByName.find(name);
if (index != m_headerByName.end()) {
return index->second->second;
}
else {
return CString();
}
}
// //
// CHTTPProtocol // CHTTPProtocol
// //
CHTTPRequest* CHTTPProtocol::readRequest(IInputStream* stream) CHTTPRequest* CHTTPProtocol::readRequest(
IInputStream* stream, UInt32 maxSize)
{ {
CString scratch; CString scratch;
// note if we should limit the request size
const bool checkSize = (maxSize > 0);
// parse request line by line // parse request line by line
CHTTPRequest* request = new CHTTPRequest; CHTTPRequest* request = new CHTTPRequest;
try { try {
@ -73,6 +144,12 @@ CHTTPRequest* CHTTPProtocol::readRequest(IInputStream* stream)
// read request line. accept and discard leading empty lines. // read request line. accept and discard leading empty lines.
do { do {
line = readLine(stream, scratch); line = readLine(stream, scratch);
if (checkSize) {
if (line.size() + 2 > maxSize) {
throw XHTTP(413);
}
maxSize -= line.size() + 2;
}
} while (line.empty()); } while (line.empty());
// parse request line: <method> <uri> <version> // parse request line: <method> <uri> <version>
@ -109,12 +186,13 @@ CHTTPRequest* CHTTPProtocol::readRequest(IInputStream* stream)
} }
// parse headers // parse headers
readHeaders(stream, request, false, scratch); readHeaders(stream, request, false, scratch,
checkSize ? &maxSize : NULL);
// HTTP/1.1 requests must have a Host header // HTTP/1.1 requests must have a Host header
if (request->m_majorVersion > 1 || if (request->m_majorVersion > 1 ||
(request->m_majorVersion == 1 && request->m_minorVersion >= 1)) { (request->m_majorVersion == 1 && request->m_minorVersion >= 1)) {
if (request->m_headerIndexByName.count("Host") == 0) { if (request->isHeader("Host") == 0) {
log((CLOG_DEBUG1 "Host header missing")); log((CLOG_DEBUG1 "Host header missing"));
throw XHTTP(400); throw XHTTP(400);
} }
@ -123,8 +201,8 @@ CHTTPRequest* CHTTPProtocol::readRequest(IInputStream* stream)
// some methods may not have a body. ensure that the headers // some methods may not have a body. ensure that the headers
// that indicate the body length do not exist for those methods // that indicate the body length do not exist for those methods
// and do exist for others. // and do exist for others.
if ((request->m_headerIndexByName.count("Transfer-Encoding") == 0 && if ((request->isHeader("Transfer-Encoding") ||
request->m_headerIndexByName.count("Content-Length") == 0) != request->isHeader("Content-Length")) ==
(request->m_method == "GET" || (request->m_method == "GET" ||
request->m_method == "HEAD")) { request->m_method == "HEAD")) {
log((CLOG_DEBUG1 "HTTP method (%s)/body mismatch", request->m_method.c_str())); log((CLOG_DEBUG1 "HTTP method (%s)/body mismatch", request->m_method.c_str()));
@ -136,13 +214,11 @@ CHTTPRequest* CHTTPProtocol::readRequest(IInputStream* stream)
// 1. Transfer-Encoding indicates a "chunked" transfer // 1. Transfer-Encoding indicates a "chunked" transfer
// 2. Content-Length is present // 2. Content-Length is present
// Content-Length is ignored for "chunked" transfers. // Content-Length is ignored for "chunked" transfers.
CHTTPRequest::CHeaderMap::iterator index = request-> CString header;
m_headerIndexByName.find("Transfer-Encoding"); if (!(header = request->getHeader("Transfer-Encoding")).empty()) {
if (index != request->m_headerIndexByName.end()) {
// we only understand "chunked" encodings // we only understand "chunked" encodings
if (!CHTTPUtil::CaselessCmp::equal( if (!CHTTPUtil::CaselessCmp::equal(header, "chunked")) {
request->m_headers[index->second], "chunked")) { log((CLOG_DEBUG1 "unsupported Transfer-Encoding %s", header.c_str()));
log((CLOG_DEBUG1 "unsupported Transfer-Encoding %s", request->m_headers[index->second].c_str()));
throw XHTTP(501); throw XHTTP(501);
} }
@ -150,36 +226,39 @@ CHTTPRequest* CHTTPProtocol::readRequest(IInputStream* stream)
UInt32 oldSize; UInt32 oldSize;
do { do {
oldSize = request->m_body.size(); oldSize = request->m_body.size();
request->m_body += readChunk(stream, scratch); request->m_body += readChunk(stream, scratch,
checkSize ? &maxSize : NULL);
} while (request->m_body.size() != oldSize); } while (request->m_body.size() != oldSize);
// read footer // read footer
readHeaders(stream, request, true, scratch); readHeaders(stream, request, true, scratch,
checkSize ? &maxSize : NULL);
// remove "chunked" from Transfer-Encoding and set the // remove "chunked" from Transfer-Encoding and set the
// Content-Length. // Content-Length.
// FIXME std::ostringstream s;
// FIXME -- note that just deleting Transfer-Encoding will s << std::dec << request->m_body.size();
// mess up indices in m_headerIndexByName, and replacing request->eraseHeader("Transfer-Encoding");
// it with Content-Length could lead to two of those. request->insertHeader("Content-Length", s.str());
} }
else if ((index = request->m_headerIndexByName. else if (!(header = request->getHeader("Content-Length")).empty()) {
find("Content-Length")) !=
request->m_headerIndexByName.end()) {
// FIXME -- check for overly-long requests
// parse content-length // parse content-length
UInt32 length; UInt32 length;
{ {
std::istringstream s(request->m_headers[index->second]); std::istringstream s(header);
s.exceptions(std::ios::goodbit); s.exceptions(std::ios::goodbit);
s >> length; s >> length;
if (!s) { if (!s) {
log((CLOG_DEBUG1 "cannot parse Content-Length", request->m_headers[index->second].c_str())); log((CLOG_DEBUG1 "cannot parse Content-Length", header.c_str()));
throw XHTTP(400); throw XHTTP(400);
} }
} }
// check against expected size
if (checkSize && length > maxSize) {
throw XHTTP(413);
}
// use content length // use content length
request->m_body = readBlock(stream, length, scratch); request->m_body = readBlock(stream, length, scratch);
if (request->m_body.size() != length) { if (request->m_body.size() != length) {
@ -287,13 +366,11 @@ bool CHTTPProtocol::parseFormData(
static const char quote[] = "\""; static const char quote[] = "\"";
// find the Content-Type header // find the Content-Type header
CHTTPRequest::CHeaderMap::const_iterator contentTypeIndex = const CString contentType = request.getHeader("Content-Type");
request.m_headerIndexByName.find("Content-Type"); if (contentType.empty()) {
if (contentTypeIndex == request.m_headerIndexByName.end()) {
// missing required Content-Type header // missing required Content-Type header
return false; return false;
} }
const CString contentType = request.m_headers[contentTypeIndex->second];
// parse type // parse type
CString::const_iterator index = std::search( CString::const_iterator index = std::search(
@ -335,8 +412,7 @@ bool CHTTPProtocol::parseFormData(
if (body.size() >= partIndex + 2 && if (body.size() >= partIndex + 2 &&
body[partIndex ] == '-' && body[partIndex ] == '-' &&
body[partIndex + 1] == '-') { body[partIndex + 1] == '-') {
// found last part. success if there's no trailing data. // found last part. ignore trailing data, if any.
// FIXME -- check for trailing data (other than a single CRLF)
return true; return true;
} }
@ -500,7 +576,8 @@ CString CHTTPProtocol::readBlock(
CString CHTTPProtocol::readChunk( CString CHTTPProtocol::readChunk(
IInputStream* stream, IInputStream* stream,
CString& tmpBuffer) CString& tmpBuffer,
UInt32* maxSize)
{ {
CString line; CString line;
@ -522,8 +599,15 @@ CString CHTTPProtocol::readChunk(
return CString(); return CString();
} }
// check size
if (maxSize != NULL) {
if (line.size() + 2 + size + 2 > *maxSize) {
throw XHTTP(413);
}
maxSize -= line.size() + 2 + size + 2;
}
// read size bytes // read size bytes
// FIXME -- check for overly-long requests
CString data = readBlock(stream, size, tmpBuffer); CString data = readBlock(stream, size, tmpBuffer);
if (data.size() != size) { if (data.size() != size) {
log((CLOG_DEBUG1 "expected/actual chunk size mismatch", size, data.size())); log((CLOG_DEBUG1 "expected/actual chunk size mismatch", size, data.size()));
@ -544,27 +628,36 @@ void CHTTPProtocol::readHeaders(
IInputStream* stream, IInputStream* stream,
CHTTPRequest* request, CHTTPRequest* request,
bool isFooter, bool isFooter,
CString& tmpBuffer) CString& tmpBuffer,
UInt32* maxSize)
{ {
// parse headers. done with headers when we get a blank line. // parse headers. done with headers when we get a blank line.
CString name;
CString line = readLine(stream, tmpBuffer); CString line = readLine(stream, tmpBuffer);
while (!line.empty()) { while (!line.empty()) {
// check size
if (maxSize != NULL) {
if (line.size() + 2 > *maxSize) {
throw XHTTP(413);
}
*maxSize -= line.size() + 2;
}
// if line starts with space or tab then append it to the // if line starts with space or tab then append it to the
// previous header. if there is no previous header then // previous header. if there is no previous header then
// throw. // throw.
if (line[0] == ' ' || line[0] == '\t') { if (line[0] == ' ' || line[0] == '\t') {
if (request->m_headers.size() == 0) { if (name.empty()) {
log((CLOG_DEBUG1 "first header is a continuation")); log((CLOG_DEBUG1 "first header is a continuation"));
throw XHTTP(400); throw XHTTP(400);
} }
request->m_headers.back() += ","; request->appendHeader(name, line);
request->m_headers.back() == line;
} }
// line should have the form: <name>:[<value>] // line should have the form: <name>:[<value>]
else { else {
// parse // parse
CString name, value; CString value;
std::istringstream s(line); std::istringstream s(line);
s.exceptions(std::ios::goodbit); s.exceptions(std::ios::goodbit);
std::getline(s, name, ':'); std::getline(s, name, ':');
@ -577,29 +670,14 @@ void CHTTPProtocol::readHeaders(
// check validity of name // check validity of name
if (isFooter) { if (isFooter) {
// FIXME -- only certain names are allowed in footers // FIXME -- only certain names are allowed in footers
// but which ones?
} }
// check if we've seen this header before request->appendHeader(name, value);
CHTTPRequest::CHeaderMap::iterator index =
request->m_headerIndexByName.find(name);
if (index == request->m_headerIndexByName.end()) {
// it's a new header
request->m_headerIndexByName.insert(std::make_pair(name,
request->m_headers.size()));
request->m_headers.push_back(value);
}
else {
// it's an existing header. append value to previous
// header, separated by a comma.
request->m_headers[index->second] += ',';
request->m_headers[index->second] += value;
}
} }
// next header // next header
line = readLine(stream, tmpBuffer); line = readLine(stream, tmpBuffer);
// FIXME -- should check for overly-long requests
} }
} }

View File

@ -3,6 +3,7 @@
#include "BasicTypes.h" #include "BasicTypes.h"
#include "CString.h" #include "CString.h"
#include "stdlist.h"
#include "stdmap.h" #include "stdmap.h"
#include "stdvector.h" #include "stdvector.h"
@ -25,19 +26,52 @@ public:
class CHTTPRequest { class CHTTPRequest {
public: public:
typedef std::map<CString, UInt32, CHTTPUtil::CaselessCmp> CHeaderMap; typedef std::list<std::pair<CString, CString> > CHeaderList;
typedef std::vector<CString> CHeaderList; typedef std::map<CString, CHeaderList::iterator,
CHTTPUtil::CaselessCmp> CHeaderMap;
typedef CHeaderList::const_iterator const_iterator;
CHTTPRequest();
~CHTTPRequest();
// manipulators
// add a header by name. replaces existing header, if any.
// headers are sent in the order they're inserted. replacing
// a header does not change its original position in the order.
void insertHeader(const CString& name, const CString& value);
// append a header. equivalent to insertHeader() if the header
// doesn't exist, otherwise it appends a comma and the value to
// the existing header.
void appendHeader(const CString& name, const CString& value);
// remove a header by name. does nothing if no such header.
void eraseHeader(const CString& name);
// accessors
// returns true iff the header exists
bool isHeader(const CString& name) const;
// get a header by name. returns the empty string if no such header.
CString getHeader(const CString& name) const;
// get iterator over all headers in the order they were added
const_iterator begin() const { return m_headers.begin(); }
const_iterator end() const { return m_headers.end(); }
public:
// note -- these members are public for convenience
CString m_method; CString m_method;
CString m_uri; CString m_uri;
SInt32 m_majorVersion; SInt32 m_majorVersion;
SInt32 m_minorVersion; SInt32 m_minorVersion;
CHeaderList m_headers;
CHeaderMap m_headerIndexByName;
CString m_body; CString m_body;
// FIXME -- need parts-of-body for POST messages
private:
CHeaderList m_headers;
CHeaderMap m_headerByName;
}; };
class CHTTPReply { class CHTTPReply {
@ -59,9 +93,11 @@ class CHTTPProtocol {
public: public:
// read and parse an HTTP request. result is returned in a // read and parse an HTTP request. result is returned in a
// CHTTPRequest which the client must delete. throws an // CHTTPRequest which the client must delete. throws an
// XHTTP if there was a parse error. throws an XIO // XHTTP if there was a parse error. throws an XIO exception
// exception if there was a read error. // if there was a read error. if maxSize is greater than
static CHTTPRequest* readRequest(IInputStream*); // zero and the request is larger than maxSize bytes then
// throws XHTTP(413).
static CHTTPRequest* readRequest(IInputStream*, UInt32 maxSize = 0);
// send an HTTP reply on the stream // send an HTTP reply on the stream
static void reply(IOutputStream*, CHTTPReply&); static void reply(IOutputStream*, CHTTPReply&);
@ -77,10 +113,12 @@ private:
static CString readLine(IInputStream*, CString& tmpBuffer); static CString readLine(IInputStream*, CString& tmpBuffer);
static CString readBlock(IInputStream*, static CString readBlock(IInputStream*,
UInt32 numBytes, CString& tmpBuffer); UInt32 numBytes, CString& tmpBuffer);
static CString readChunk(IInputStream*, CString& tmpBuffer); static CString readChunk(IInputStream*, CString& tmpBuffer,
UInt32* maxSize);
static void readHeaders(IInputStream*, static void readHeaders(IInputStream*,
CHTTPRequest*, bool isFooter, CHTTPRequest*, bool isFooter,
CString& tmpBuffer); CString& tmpBuffer,
UInt32* maxSize);
static bool isValidToken(const CString&); static bool isValidToken(const CString&);
}; };

View File

@ -14,6 +14,11 @@
// CHTTPServer // CHTTPServer
// //
// maximum size of an HTTP request. this should be large enough to
// handle any reasonable request but small enough to prevent a
// malicious client from causing us to use too much memory.
const UInt32 CHTTPServer::s_maxRequestSize = 32768;
CHTTPServer::CHTTPServer(CServer* server) : m_server(server) CHTTPServer::CHTTPServer(CServer* server) : m_server(server)
{ {
// do nothing // do nothing
@ -31,7 +36,8 @@ void CHTTPServer::processRequest(ISocket* socket)
CHTTPRequest* request = NULL; CHTTPRequest* request = NULL;
try { try {
// parse request // parse request
request = CHTTPProtocol::readRequest(socket->getInputStream()); request = CHTTPProtocol::readRequest(
socket->getInputStream(), s_maxRequestSize);
if (request == NULL) { if (request == NULL) {
throw XHTTP(400); throw XHTTP(400);
} }

View File

@ -96,6 +96,7 @@ protected:
private: private:
CServer* m_server; CServer* m_server;
static const UInt32 s_maxRequestSize;
}; };
#endif #endif