#2565: HTMLForm: optional enforcement of Content-Length instead of Chunked Transfer-Encoding

This commit is contained in:
Günter Obiltschnig 2018-12-12 18:00:58 +01:00
parent e1aac5ff95
commit db86fec382
4 changed files with 73 additions and 39 deletions

View File

@ -46,17 +46,23 @@ class Net_API HTMLForm: public NameValueCollection
/// form fields programmatically. The default limit is 100. /// form fields programmatically. The default limit is 100.
{ {
public: public:
enum Options
{
OPT_USE_CONTENT_LENGTH = 0x01
/// Don't use Chunked Transfer-Encoding for multipart requests.
};
HTMLForm(); HTMLForm();
/// Creates an empty HTMLForm and sets the /// Creates an empty HTMLForm and sets the
/// encoding to "application/x-www-form-urlencoded". /// encoding to "application/x-www-form-urlencoded".
explicit HTMLForm(const std::string& encoding); explicit HTMLForm(const std::string& encoding);
/// Creates an empty HTMLForm that uses /// Creates an empty HTMLForm that uses
/// the given encoding. /// the given encoding.
/// ///
/// Encoding must be either "application/x-www-form-urlencoded" /// Encoding must be either "application/x-www-form-urlencoded"
/// (which is the default) or "multipart/form-data". /// (which is the default) or "multipart/form-data".
HTMLForm(const HTTPRequest& request, std::istream& requestBody, PartHandler& handler); HTMLForm(const HTTPRequest& request, std::istream& requestBody, PartHandler& handler);
/// Creates a HTMLForm from the given HTTP request. /// Creates a HTMLForm from the given HTTP request.
/// ///
@ -75,7 +81,7 @@ public:
/// ///
/// For POST requests, you must use one of the constructors /// For POST requests, you must use one of the constructors
/// taking an additional input stream for the request body. /// taking an additional input stream for the request body.
~HTMLForm(); ~HTMLForm();
/// Destroys the HTMLForm. /// Destroys the HTMLForm.
@ -84,7 +90,7 @@ public:
/// ///
/// Encoding must be either "application/x-www-form-urlencoded" /// Encoding must be either "application/x-www-form-urlencoded"
/// (which is the default) or "multipart/form-data". /// (which is the default) or "multipart/form-data".
const std::string& getEncoding() const; const std::string& getEncoding() const;
/// Returns the encoding used for posting the form. /// Returns the encoding used for posting the form.
@ -130,14 +136,14 @@ public:
/// ///
/// Note that read() does not clear the form before /// Note that read() does not clear the form before
/// reading the new values. /// reading the new values.
void read(const std::string& queryString); void read(const std::string& queryString);
/// Reads the form data from the given HTTP query string. /// Reads the form data from the given HTTP query string.
/// ///
/// Note that read() does not clear the form before /// Note that read() does not clear the form before
/// reading the new values. /// reading the new values.
void prepareSubmit(HTTPRequest& request); void prepareSubmit(HTTPRequest& request, int options = 0);
/// Fills out the request object for submitting the form. /// Fills out the request object for submitting the form.
/// ///
/// If the request method is GET, the encoded form is appended to the /// If the request method is GET, the encoded form is appended to the
@ -150,7 +156,12 @@ public:
/// - the content transfer encoding is set to identity encoding /// - the content transfer encoding is set to identity encoding
/// Otherwise, if the request's HTTP version is HTTP/1.1: /// Otherwise, if the request's HTTP version is HTTP/1.1:
/// - the request's persistent connection state is left unchanged /// - the request's persistent connection state is left unchanged
/// - the content transfer encoding is set to chunked /// - the content transfer encoding is set to chunked, unless
/// the OPT_USE_CONTENT_LENGTH is given in options
///
/// Note: Not using chunked transfer encoding for multipart forms
/// degrades performance, as the request content must be generated
/// twice, first to determine its size, then to actually send it.
std::streamsize calculateContentLength(); std::streamsize calculateContentLength();
/// Calculate the content length for the form. /// Calculate the content length for the form.
@ -174,7 +185,7 @@ public:
/// allowed. /// allowed.
/// ///
/// See setFieldLimit() for more information. /// See setFieldLimit() for more information.
void setFieldLimit(int limit); void setFieldLimit(int limit);
/// Sets the maximum number of header fields /// Sets the maximum number of header fields
/// allowed. This limit is used to defend certain /// allowed. This limit is used to defend certain
@ -182,11 +193,11 @@ public:
/// Specify 0 for unlimited (not recommended). /// Specify 0 for unlimited (not recommended).
/// ///
/// The default limit is 100. /// The default limit is 100.
void setValueLengthLimit(int limit); void setValueLengthLimit(int limit);
/// Sets the maximum size for form field values /// Sets the maximum size for form field values
/// stored as strings. /// stored as strings.
int getValueLengthLimit() const; int getValueLengthLimit() const;
/// Returns the maximum size for form field values /// Returns the maximum size for form field values
/// stored as strings. /// stored as strings.
@ -194,7 +205,7 @@ public:
static const std::string ENCODING_URL; /// "application/x-www-form-urlencoded" static const std::string ENCODING_URL; /// "application/x-www-form-urlencoded"
static const std::string ENCODING_MULTIPART; /// "multipart/form-data" static const std::string ENCODING_MULTIPART; /// "multipart/form-data"
static const int UNKNOWN_CONTENT_LENGTH; static const int UNKNOWN_CONTENT_LENGTH;
protected: protected:
void readUrl(std::istream& istr); void readUrl(std::istream& istr);
void readMultipart(std::istream& istr, PartHandler& handler); void readMultipart(std::istream& istr, PartHandler& handler);
@ -217,9 +228,9 @@ private:
std::string name; std::string name;
PartSource* pSource; PartSource* pSource;
}; };
typedef std::vector<Part> PartVec; typedef std::vector<Part> PartVec;
int _fieldLimit; int _fieldLimit;
int _valueLengthLimit; int _valueLengthLimit;
std::string _encoding; std::string _encoding;

View File

@ -54,11 +54,11 @@ public:
{ {
} }
bool isValid() const bool isValid() const
{ {
return _valid; return _valid;
} }
void setValid(bool v) void setValid(bool v)
{ {
_valid = v; _valid = v;
@ -76,7 +76,7 @@ HTMLForm::HTMLForm():
{ {
} }
HTMLForm::HTMLForm(const std::string& encoding): HTMLForm::HTMLForm(const std::string& encoding):
_fieldLimit(DFL_FIELD_LIMIT), _fieldLimit(DFL_FIELD_LIMIT),
_valueLengthLimit(DFL_MAX_VALUE_LENGTH), _valueLengthLimit(DFL_MAX_VALUE_LENGTH),
@ -108,7 +108,7 @@ HTMLForm::HTMLForm(const HTTPRequest& request):
load(request); load(request);
} }
HTMLForm::~HTMLForm() HTMLForm::~HTMLForm()
{ {
for (PartVec::iterator it = _parts.begin(); it != _parts.end(); ++it) for (PartVec::iterator it = _parts.begin(); it != _parts.end(); ++it)
@ -151,7 +151,7 @@ void HTMLForm::load(const HTTPRequest& request, std::istream& requestBody, PartH
{ {
std::string mediaType; std::string mediaType;
NameValueCollection params; NameValueCollection params;
MessageHeader::splitParameters(request.getContentType(), mediaType, params); MessageHeader::splitParameters(request.getContentType(), mediaType, params);
_encoding = mediaType; _encoding = mediaType;
if (_encoding == ENCODING_MULTIPART) if (_encoding == ENCODING_MULTIPART)
{ {
@ -203,7 +203,7 @@ void HTMLForm::read(const std::string& queryString)
} }
void HTMLForm::prepareSubmit(HTTPRequest& request) void HTMLForm::prepareSubmit(HTTPRequest& request, int options)
{ {
if (request.getMethod() == HTTPRequest::HTTP_POST || request.getMethod() == HTTPRequest::HTTP_PUT) if (request.getMethod() == HTTPRequest::HTTP_POST || request.getMethod() == HTTPRequest::HTTP_PUT)
{ {
@ -229,11 +229,11 @@ void HTMLForm::prepareSubmit(HTTPRequest& request)
request.setKeepAlive(false); request.setKeepAlive(false);
request.setChunkedTransferEncoding(false); request.setChunkedTransferEncoding(false);
} }
else if (_encoding != ENCODING_URL) else if (_encoding != ENCODING_URL && (options & OPT_USE_CONTENT_LENGTH) == 0)
{ {
request.setChunkedTransferEncoding(true); request.setChunkedTransferEncoding(true);
} }
if (!request.getChunkedTransferEncoding()) if (!request.getChunkedTransferEncoding() && !request.hasContentLength())
{ {
request.setContentLength(calculateContentLength()); request.setContentLength(calculateContentLength());
} }
@ -413,7 +413,7 @@ void HTMLForm::writeMultipart(std::ostream& ostr)
header.set("Content-Disposition", disp); header.set("Content-Disposition", disp);
writer.nextPart(header); writer.nextPart(header);
ostr << it->second; ostr << it->second;
} }
for (PartVec::iterator ita = _parts.begin(); ita != _parts.end(); ++ita) for (PartVec::iterator ita = _parts.begin(); ita != _parts.end(); ++ita)
{ {
MessageHeader header(ita->pSource->headers()); MessageHeader header(ita->pSource->headers());
@ -452,7 +452,7 @@ void HTMLForm::writeMultipart(std::ostream& ostr)
void HTMLForm::setFieldLimit(int limit) void HTMLForm::setFieldLimit(int limit)
{ {
poco_assert (limit >= 0); poco_assert (limit >= 0);
_fieldLimit = limit; _fieldLimit = limit;
} }
@ -460,7 +460,7 @@ void HTMLForm::setFieldLimit(int limit)
void HTMLForm::setValueLengthLimit(int limit) void HTMLForm::setValueLengthLimit(int limit)
{ {
poco_assert (limit >= 0); poco_assert (limit >= 0);
_valueLengthLimit = limit; _valueLengthLimit = limit;
} }

View File

@ -19,6 +19,7 @@
#include "Poco/Net/NetException.h" #include "Poco/Net/NetException.h"
#include <sstream> #include <sstream>
#include <iostream>
using Poco::Net::HTMLForm; using Poco::Net::HTMLForm;
using Poco::Net::PartSource; using Poco::Net::PartSource;
@ -37,7 +38,7 @@ namespace
StringPartHandler() StringPartHandler()
{ {
} }
void handlePart(const MessageHeader& header, std::istream& stream) void handlePart(const MessageHeader& header, std::istream& stream)
{ {
_disp = header["Content-Disposition"]; _disp = header["Content-Disposition"];
@ -49,7 +50,7 @@ namespace
ch = stream.get(); ch = stream.get();
} }
} }
const std::string& data() const const std::string& data() const
{ {
return _data; return _data;
@ -64,7 +65,7 @@ namespace
{ {
return _type; return _type;
} }
private: private:
std::string _data; std::string _data;
std::string _disp; std::string _disp;
@ -91,7 +92,7 @@ void HTMLFormTest::testWriteUrl()
form.set("field3", "value=3"); form.set("field3", "value=3");
form.set("field4", "value&4"); form.set("field4", "value&4");
form.set("field5", "value+5"); form.set("field5", "value+5");
std::ostringstream ostr; std::ostringstream ostr;
form.write(ostr); form.write(ostr);
std::string s = ostr.str(); std::string s = ostr.str();
@ -106,16 +107,16 @@ void HTMLFormTest::testWriteMultipart()
form.set("field2", "value 2"); form.set("field2", "value 2");
form.set("field3", "value=3"); form.set("field3", "value=3");
form.set("field4", "value&4"); form.set("field4", "value&4");
form.addPart("attachment1", new StringPartSource("This is an attachment")); form.addPart("attachment1", new StringPartSource("This is an attachment"));
StringPartSource* pSPS = new StringPartSource("This is another attachment", "text/plain", "att2.txt"); StringPartSource* pSPS = new StringPartSource("This is another attachment", "text/plain", "att2.txt");
pSPS->headers().set("Content-ID", "1234abcd"); pSPS->headers().set("Content-ID", "1234abcd");
form.addPart("attachment2", pSPS); form.addPart("attachment2", pSPS);
std::ostringstream ostr; std::ostringstream ostr;
form.write(ostr, "MIME_boundary_0123456789"); form.write(ostr, "MIME_boundary_0123456789");
std::string s = ostr.str(); std::string s = ostr.str();
assertTrue (s == assertTrue (s ==
"--MIME_boundary_0123456789\r\n" "--MIME_boundary_0123456789\r\n"
"Content-Disposition: form-data; name=\"field1\"\r\n" "Content-Disposition: form-data; name=\"field1\"\r\n"
"\r\n" "\r\n"
@ -166,7 +167,7 @@ void HTMLFormTest::testReadUrlGETMultiple()
HTTPRequest req("GET", "/form.cgi?field1=value1&field1=value%202&field1=value%3D3&field1=value%264"); HTTPRequest req("GET", "/form.cgi?field1=value1&field1=value%202&field1=value%3D3&field1=value%264");
HTMLForm form(req); HTMLForm form(req);
assertTrue (form.size() == 4); assertTrue (form.size() == 4);
HTMLForm::ConstIterator it = form.find("field1"); HTMLForm::ConstIterator it = form.find("field1");
assertTrue (it != form.end()); assertTrue (it != form.end());
assertTrue (it->first == "field1" && it->second == "value1"); assertTrue (it->first == "field1" && it->second == "value1");
@ -256,7 +257,7 @@ void HTMLFormTest::testReadMultipart()
HTTPRequest req("POST", "/form.cgi"); HTTPRequest req("POST", "/form.cgi");
req.setContentType(HTMLForm::ENCODING_MULTIPART + "; boundary=\"MIME_boundary_0123456789\""); req.setContentType(HTMLForm::ENCODING_MULTIPART + "; boundary=\"MIME_boundary_0123456789\"");
StringPartHandler sah; StringPartHandler sah;
HTMLForm form(req, istr, sah); HTMLForm form(req, istr, sah);
assertTrue (form.size() == 4); assertTrue (form.size() == 4);
assertTrue (form["field1"] == "value1"); assertTrue (form["field1"] == "value1");
assertTrue (form["field2"] == "value 2"); assertTrue (form["field2"] == "value 2");
@ -276,7 +277,7 @@ void HTMLFormTest::testSubmit1()
form.set("field2", "value 2"); form.set("field2", "value 2");
form.set("field3", "value=3"); form.set("field3", "value=3");
form.set("field4", "value&4"); form.set("field4", "value&4");
HTTPRequest req("GET", "/form.cgi"); HTTPRequest req("GET", "/form.cgi");
form.prepareSubmit(req); form.prepareSubmit(req);
assertTrue (req.getURI() == "/form.cgi?field1=value1&field2=value%202&field3=value%3D3&field4=value%264"); assertTrue (req.getURI() == "/form.cgi?field1=value1&field2=value%202&field3=value%3D3&field4=value%264");
@ -290,10 +291,11 @@ void HTMLFormTest::testSubmit2()
form.set("field2", "value 2"); form.set("field2", "value 2");
form.set("field3", "value=3"); form.set("field3", "value=3");
form.set("field4", "value&4"); form.set("field4", "value&4");
HTTPRequest req("POST", "/form.cgi"); HTTPRequest req("POST", "/form.cgi");
form.prepareSubmit(req); form.prepareSubmit(req);
assertTrue (req.getContentType() == HTMLForm::ENCODING_URL); assertTrue (req.getContentType() == HTMLForm::ENCODING_URL);
assertTrue (req.getContentLength() == 64);
} }
@ -304,7 +306,7 @@ void HTMLFormTest::testSubmit3()
form.set("field2", "value 2"); form.set("field2", "value 2");
form.set("field3", "value=3"); form.set("field3", "value=3");
form.set("field4", "value&4"); form.set("field4", "value&4");
HTTPRequest req("POST", "/form.cgi", HTTPMessage::HTTP_1_1); HTTPRequest req("POST", "/form.cgi", HTTPMessage::HTTP_1_1);
form.prepareSubmit(req); form.prepareSubmit(req);
std::string expCT(HTMLForm::ENCODING_MULTIPART); std::string expCT(HTMLForm::ENCODING_MULTIPART);
@ -323,7 +325,7 @@ void HTMLFormTest::testSubmit4()
form.add("field1", "value 2"); form.add("field1", "value 2");
form.add("field1", "value=3"); form.add("field1", "value=3");
form.add("field1", "value&4"); form.add("field1", "value&4");
HTTPRequest req("GET", "/form.cgi"); HTTPRequest req("GET", "/form.cgi");
form.prepareSubmit(req); form.prepareSubmit(req);
@ -331,6 +333,25 @@ void HTMLFormTest::testSubmit4()
} }
void HTMLFormTest::testSubmit5()
{
HTMLForm form(HTMLForm::ENCODING_MULTIPART);
form.set("field1", "value1");
form.set("field2", "value 2");
form.set("field3", "value=3");
form.set("field4", "value&4");
HTTPRequest req("POST", "/form.cgi", HTTPMessage::HTTP_1_1);
form.prepareSubmit(req, HTMLForm::OPT_USE_CONTENT_LENGTH);
std::string expCT(HTMLForm::ENCODING_MULTIPART);
expCT.append("; boundary=\"");
expCT.append(form.boundary());
expCT.append("\"");
assertTrue (req.getContentType() == expCT);
assertTrue (req.getContentLength() == 403);
}
void HTMLFormTest::testFieldLimitUrl() void HTMLFormTest::testFieldLimitUrl()
{ {
HTTPRequest req("GET", "/form.cgi?field1=value1&field2=value%202&field3=value%3D3&field4=value%264"); HTTPRequest req("GET", "/form.cgi?field1=value1&field2=value%202&field3=value%3D3&field4=value%264");
@ -381,7 +402,7 @@ void HTMLFormTest::testFieldLimitMultipart()
form.setFieldLimit(3); form.setFieldLimit(3);
try try
{ {
form.load(req, istr, sah); form.load(req, istr, sah);
fail("field limit violated - must throw"); fail("field limit violated - must throw");
} }
catch (Poco::Net::HTMLFormException&) catch (Poco::Net::HTMLFormException&)
@ -416,6 +437,7 @@ CppUnit::Test* HTMLFormTest::suite()
CppUnit_addTest(pSuite, HTMLFormTest, testSubmit2); CppUnit_addTest(pSuite, HTMLFormTest, testSubmit2);
CppUnit_addTest(pSuite, HTMLFormTest, testSubmit3); CppUnit_addTest(pSuite, HTMLFormTest, testSubmit3);
CppUnit_addTest(pSuite, HTMLFormTest, testSubmit4); CppUnit_addTest(pSuite, HTMLFormTest, testSubmit4);
CppUnit_addTest(pSuite, HTMLFormTest, testSubmit5);
CppUnit_addTest(pSuite, HTMLFormTest, testFieldLimitUrl); CppUnit_addTest(pSuite, HTMLFormTest, testFieldLimitUrl);
CppUnit_addTest(pSuite, HTMLFormTest, testFieldLimitMultipart); CppUnit_addTest(pSuite, HTMLFormTest, testFieldLimitMultipart);

View File

@ -36,6 +36,7 @@ public:
void testSubmit2(); void testSubmit2();
void testSubmit3(); void testSubmit3();
void testSubmit4(); void testSubmit4();
void testSubmit5();
void testFieldLimitUrl(); void testFieldLimitUrl();
void testFieldLimitMultipart(); void testFieldLimitMultipart();