diff --git a/Net/include/Poco/Net/HTTPServerParams.h b/Net/include/Poco/Net/HTTPServerParams.h index 10bb26eba..7691677bc 100644 --- a/Net/include/Poco/Net/HTTPServerParams.h +++ b/Net/include/Poco/Net/HTTPServerParams.h @@ -77,7 +77,7 @@ public: /// persistent connections. bool getKeepAlive() const; - /// Returns true iff persistent connections are enabled. + /// Returns true if persistent connections are enabled. void setKeepAliveTimeout(const Poco::Timespan& timeout); /// Sets the keep-alive timeout for persistent HTTP connections. @@ -95,6 +95,15 @@ public: /// during a persistent connection, or 0 if /// unlimited connections are allowed. + void setAutoDecodeHeaders(bool autoDecode); + /// Enables or disables automatic HTTP header value conversions, for example + /// RFC2047. + /// Default is true: convert header values when reading HTTP header. + + bool getAutoDecodeHeaders() const; + /// Returns true if automatic conversion of HTTP header values + /// when reading HTTP header. + protected: virtual ~HTTPServerParams(); /// Destroys the HTTPServerParams. @@ -106,6 +115,7 @@ private: bool _keepAlive; int _maxKeepAliveRequests; Poco::Timespan _keepAliveTimeout; + bool _autoDecodeHeaders; }; @@ -148,6 +158,12 @@ inline const Poco::Timespan& HTTPServerParams::getKeepAliveTimeout() const } +inline bool HTTPServerParams::getAutoDecodeHeaders() const +{ + return _autoDecodeHeaders; +} + + } } // namespace Poco::Net diff --git a/Net/include/Poco/Net/MessageHeader.h b/Net/include/Poco/Net/MessageHeader.h index 2500b2bb9..c1da4cc0a 100644 --- a/Net/include/Poco/Net/MessageHeader.h +++ b/Net/include/Poco/Net/MessageHeader.h @@ -87,6 +87,23 @@ public: /// Throws a MessageException if the input stream is /// malformed. + void setAutoDecode(bool convert); + /// Enables or disables automatic conversion of HTTP header values + /// when reading HTTP header. + + bool getAutoDecode() const; + /// Returns true if automatic conversion of HTTP header values + /// when reading HTTP header. + + std::string getDecoded(const std::string& name) const; + /// Get decoded header value. It does conversion if it was not + /// automatically converted when reading. + + std::string getDecoded(const std::string& name, const std::string& defaultValue) const; + /// Get decoded header value. It does conversion if it was not + /// automatically converted when reading. + /// Default value is returned if the name does not exist. + int getFieldLimit() const; /// Returns the maximum number of header fields /// allowed. @@ -122,7 +139,7 @@ public: /// The default limit is 8192. bool hasToken(const std::string& fieldName, const std::string& token) const; - /// Returns true iff the field with the given fieldName contains + /// Returns true if the field with the given fieldName contains /// the given token. Tokens in a header field are expected to be /// comma-separated and are case insensitive. @@ -182,6 +199,8 @@ private: int _fieldLimit; int _nameLengthLimit; int _valueLengthLimit; + bool _autoDecode; + bool _decodedOnRead; }; diff --git a/Net/src/HTTPServerParams.cpp b/Net/src/HTTPServerParams.cpp index 34742c72b..e43a7f5f0 100644 --- a/Net/src/HTTPServerParams.cpp +++ b/Net/src/HTTPServerParams.cpp @@ -23,7 +23,8 @@ HTTPServerParams::HTTPServerParams(): _timeout(60000000), _keepAlive(true), _maxKeepAliveRequests(0), - _keepAliveTimeout(15000000) + _keepAliveTimeout(15000000), + _autoDecodeHeaders(true) { } @@ -70,4 +71,10 @@ void HTTPServerParams::setMaxKeepAliveRequests(int maxKeepAliveRequests) } +void HTTPServerParams::setAutoDecodeHeaders(bool autoDecode) +{ + _autoDecodeHeaders = autoDecode; +} + + } } // namespace Poco::Net diff --git a/Net/src/HTTPServerRequestImpl.cpp b/Net/src/HTTPServerRequestImpl.cpp index e27abd8ee..dd50227e0 100644 --- a/Net/src/HTTPServerRequestImpl.cpp +++ b/Net/src/HTTPServerRequestImpl.cpp @@ -40,6 +40,7 @@ HTTPServerRequestImpl::HTTPServerRequestImpl(HTTPServerResponseImpl& response, H response.attachRequest(this); HTTPHeaderInputStream hs(session); + setAutoDecode(_pParams->getAutoDecodeHeaders()); read(hs); // Now that we know socket is still connected, obtain addresses diff --git a/Net/src/MessageHeader.cpp b/Net/src/MessageHeader.cpp index bddb11c3a..86e7aa0d6 100644 --- a/Net/src/MessageHeader.cpp +++ b/Net/src/MessageHeader.cpp @@ -30,7 +30,9 @@ namespace Net { MessageHeader::MessageHeader(): _fieldLimit(DFL_FIELD_LIMIT), _nameLengthLimit(DFL_NAME_LENGTH_LIMIT), - _valueLengthLimit(DFL_VALUE_LENGTH_LIMIT) + _valueLengthLimit(DFL_VALUE_LENGTH_LIMIT), + _autoDecode(true), + _decodedOnRead(false) { } @@ -104,15 +106,58 @@ void MessageHeader::read(std::istream& istr) else if (ch != eof) throw MessageException("Folded field value too long/no CRLF found"); } + + // TODO: Add to the if below? Poco::trimRightInPlace(value); - add(name, decodeWord(value)); + + if (_autoDecode) + add(name, decodeWord(value)); + else + add(name, value); + ++fields; } + // Save the state of the auto decode at the time of reading. + _decodedOnRead = _autoDecode; if (istr.good() && ch != eof) istr.putback(ch); } +void MessageHeader::setAutoDecode(bool decode) +{ + _autoDecode = decode; +} + + +bool MessageHeader::getAutoDecode() const +{ + return _autoDecode; +} + + +std::string MessageHeader::getDecoded(const std::string& name) const +{ + const auto& value { get(name) }; + if (_decodedOnRead) + { + // Already decoded, just return the value + return value; + } + return decodeWord(value); +} + + +std::string MessageHeader::getDecoded(const std::string& name, const std::string& defaultValue) const +{ + if (!has(name)) + { + return defaultValue; + } + return getDecoded(name); +} + + int MessageHeader::getFieldLimit() const { return _fieldLimit; diff --git a/Net/testsuite/src/MessageHeaderTest.cpp b/Net/testsuite/src/MessageHeaderTest.cpp index cb4cd75d5..399ac6dca 100644 --- a/Net/testsuite/src/MessageHeaderTest.cpp +++ b/Net/testsuite/src/MessageHeaderTest.cpp @@ -407,6 +407,48 @@ void MessageHeaderTest::testDecodeWord() assertTrue (decoded == "Hello Francis, good bye"); } +// Sample HTTP reuest header +static std::string httpRequestHeader{ +R"(GET / HTTP/2 +Host: stackoverflow.com +User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/119.0 +Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 +Accept-Language: en-GB,en;q=0.5 +Accept-Encoding: gzip, deflate, br +Connection: keep-alive +Upgrade-Insecure-Requests: 1 +X-Encoded-Header-A: (=?ISO-8859-1?Q?a?= =?ISO-8859-1?Q?b?=) +X-Encoded-Header-B: Hello =?UTF-8?B?RnJhbmNpcw==?=, good bye + +)" +}; + +void MessageHeaderTest::testAutoDecode() +{ + { + std::istringstream istr(httpRequestHeader); + MessageHeader mh; + mh.read(istr); + + assertEquals(mh.get("X-Encoded-Header-A"), "(a b)"); + assertEquals(mh.get("X-Encoded-Header-B"), "Hello Francis, good bye"); + + assertEquals(mh.getDecoded("X-Encoded-Header-A"), "(a b)"); + assertEquals(mh.getDecoded("X-Encoded-Header-B"), "Hello Francis, good bye"); + } + { + std::istringstream istr(httpRequestHeader); + MessageHeader mh; + mh.setAutoDecode(false); + mh.read(istr); + + assertEquals(mh.get("X-Encoded-Header-A"), "(=?ISO-8859-1?Q?a?= =?ISO-8859-1?Q?b?=)"); + assertEquals(mh.get("X-Encoded-Header-B"), "Hello =?UTF-8?B?RnJhbmNpcw==?=, good bye"); + + assertEquals(mh.getDecoded("X-Encoded-Header-A"), "(a b)"); + assertEquals(mh.getDecoded("X-Encoded-Header-B"), "Hello Francis, good bye"); + } +} void MessageHeaderTest::setUp() @@ -440,6 +482,7 @@ CppUnit::Test* MessageHeaderTest::suite() CppUnit_addTest(pSuite, MessageHeaderTest, testSplitParameters); CppUnit_addTest(pSuite, MessageHeaderTest, testFieldLimit); CppUnit_addTest(pSuite, MessageHeaderTest, testDecodeWord); + CppUnit_addTest(pSuite, MessageHeaderTest, testAutoDecode); return pSuite; } diff --git a/Net/testsuite/src/MessageHeaderTest.h b/Net/testsuite/src/MessageHeaderTest.h index 98d9220d7..e829733f6 100644 --- a/Net/testsuite/src/MessageHeaderTest.h +++ b/Net/testsuite/src/MessageHeaderTest.h @@ -43,6 +43,7 @@ public: void testNameLengthLimit(); void testValueLengthLimit(); void testDecodeWord(); + void testAutoDecode(); void setUp(); void tearDown();