added Poco::Net::escapeHTML() and Poco::Net::EscapeHTMLStream; PageCompiler now supports optional HTML-escaping of <%= %> expressions.

This commit is contained in:
Günter Obiltschnig 2019-11-20 14:22:41 +01:00
parent 44c7d97d2e
commit 665a840692
10 changed files with 319 additions and 9 deletions

View File

@ -33,7 +33,8 @@ objects = \
WebSocket WebSocketImpl \ WebSocket WebSocketImpl \
OAuth10Credentials OAuth20Credentials \ OAuth10Credentials OAuth20Credentials \
PollSet UDPClient UDPServerParams \ PollSet UDPClient UDPServerParams \
NTLMCredentials SSPINTLMCredentials HTTPNTLMCredentials NTLMCredentials SSPINTLMCredentials HTTPNTLMCredentials \
EscapeHTMLStream
target = PocoNet target = PocoNet
target_version = $(LIBVERSION) target_version = $(LIBVERSION)

View File

@ -0,0 +1,89 @@
//
// EscapeHTMLStream.h
//
// Library: Net
// Package: HTTP
// Module: EscapeHTMLStream
//
// Definition of the EscapeHTMLStream class.
//
// Copyright (c) 1029, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
//
#ifndef Net_EscapeHTMLStream_INCLUDED
#define Net_EscapeHTMLStream_INCLUDED
#include "Poco/Net/Net.h"
#include "Poco/UnbufferedStreamBuf.h"
#include <ostream>
namespace Poco {
namespace Net {
class Net_API EscapeHTMLStreamBuf: public Poco::UnbufferedStreamBuf
/// This stream buffer replaces all occurrences of special HTML
/// characters < > " & with their respective character
/// entities &lt; &gt; &quot; &amp;.
{
public:
EscapeHTMLStreamBuf(std::ostream& ostr);
/// Creates the EscapeHTMLStreamBuf and connects it
/// to the given output stream.
~EscapeHTMLStreamBuf();
/// Destroys the EscapeHTMLStreamBuf.
protected:
int readFromDevice();
int writeToDevice(char c);
private:
std::ostream* _pOstr;
};
class Net_API EscapeHTMLIOS: public virtual std::ios
/// The base class for EscapeHTMLOutputStream.
{
public:
EscapeHTMLIOS(std::ostream& ostr);
/// Creates the MailIOS and connects it
/// to the given output stream.
~EscapeHTMLIOS();
/// Destroys the stream.
EscapeHTMLStreamBuf* rdbuf();
/// Returns a pointer to the underlying streambuf.
protected:
EscapeHTMLStreamBuf _buf;
};
class Net_API EscapeHTMLOutputStream: public EscapeHTMLIOS, public std::ostream
/// This stream replaces all occurrences of special HTML
/// characters < > " & with their respective character
/// entities &lt; &gt; &quot; &amp;.
{
public:
EscapeHTMLOutputStream(std::ostream& ostr);
/// Creates the MailOutputStream and connects it
/// to the given input stream.
~EscapeHTMLOutputStream();
/// Destroys the MailOutputStream.
};
} } // namespace Poco::Net
#endif // Net_EscapeHTMLStream_INCLUDED

View File

@ -81,7 +81,12 @@ void Net_API uninitializeNetwork();
/// (Windows only, no-op elsewhere) /// (Windows only, no-op elsewhere)
}} // namespace Poco::Net std::string htmlize(const std::string& str);
/// Returns a copy of html with reserved HTML
/// characters (<, >, ", &) propery escaped.
} } // namespace Poco::Net
// //

View File

@ -0,0 +1,92 @@
//
// EscapeHTMLStream.cpp
//
// Library: Net
// Package: Mail
// Module: EscapeHTMLStream
//
// Copyright (c) 2019, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
//
#include "Poco/Net/EscapeHTMLStream.h"
namespace Poco {
namespace Net {
EscapeHTMLStreamBuf::EscapeHTMLStreamBuf(std::ostream& ostr):
_pOstr(&ostr)
{
}
EscapeHTMLStreamBuf::~EscapeHTMLStreamBuf()
{
}
int EscapeHTMLStreamBuf::readFromDevice()
{
return std::char_traits<char>::eof();
}
int EscapeHTMLStreamBuf::writeToDevice(char c)
{
switch (c)
{
case '<':
*_pOstr << "&lt;";
break;
case '>':
*_pOstr << "&gt;";
break;
case '"':
*_pOstr << "&quot;";
break;
case '&':
*_pOstr << "&amp;";
break;
default:
_pOstr->put(c);
break;
}
return charToInt(c);
}
EscapeHTMLIOS::EscapeHTMLIOS(std::ostream& ostr): _buf(ostr)
{
poco_ios_init(&_buf);
}
EscapeHTMLIOS::~EscapeHTMLIOS()
{
}
EscapeHTMLStreamBuf* EscapeHTMLIOS::rdbuf()
{
return &_buf;
}
EscapeHTMLOutputStream::EscapeHTMLOutputStream(std::ostream& ostr):
EscapeHTMLIOS(ostr),
std::ostream(&_buf)
{
}
EscapeHTMLOutputStream::~EscapeHTMLOutputStream()
{
}
} } // namespace Poco::Net

View File

@ -42,6 +42,26 @@ void Net_API uninitializeNetwork()
} }
std::string htmlize(const std::string& str)
{
std::string::const_iterator it(str.begin());
std::string::const_iterator end(str.end());
std::string html;
for (; it != end; ++it)
{
switch (*it)
{
case '<': html += "&lt;"; break;
case '>': html += "&gt;"; break;
case '"': html += "&quot;"; break;
case '&': html += "&amp;"; break;
default: html += *it; break;
}
}
return html;
}
} } // namespace Poco::Net } } // namespace Poco::Net

View File

@ -2,11 +2,16 @@ POCO C++ Server Page Compiler User Guide
POCO PageCompiler POCO PageCompiler
!!!Introduction !!!Introduction
PageCompiler is a command line tool that translates HTML files (and other kinds of files) into PageCompiler is a command line tool that translates HTML files (and other kinds of files) into
C++ code, more precisely, subclasses of Poco::Net::HTTPRequestHandler. C++ code, more precisely, subclasses of Poco::Net::HTTPRequestHandler.
The source files can contain special directives that allow embedding of C++ code. The source files can contain special directives that allow embedding of C++ code.
The syntax of these directives is based on the syntax used for The syntax of these directives is based on the syntax used for
Java Server Pages (JSP) and Active Server Pages (ASP). Java Server Pages (JSP), Active Server Pages (ASP) or Embedded JavaScript (EJS) templating.
This makes PageCompiler a C++-based HTML templating system. Since the translation of
the HTML template into a C++ class happens at compile time, runtime performance is
excellent.
The following introductory sample shows the code for a simple page that displays the The following introductory sample shows the code for a simple page that displays the
current date and time. current date and time.
@ -16,8 +21,8 @@ current date and time.
#include "Poco/DateTime.h" #include "Poco/DateTime.h"
#include "Poco/DateTimeFormatter.h" #include "Poco/DateTimeFormatter.h"
#include "Poco/DateTimeFormat.h" #include "Poco/DateTimeFormat.h"
using Poco::DateTime; using Poco::DateTime;
using Poco::DateTimeFormatter; using Poco::DateTimeFormatter;
using Poco::DateTimeFormat; using Poco::DateTimeFormat;
@ -88,11 +93,24 @@ is required.
The result of any valid C++ expression can be directly inserted into the page, The result of any valid C++ expression can be directly inserted into the page,
provided the result can be written to an output stream. Note that the expression provided the result can be written to an output stream. Note that the expression
must not end with a semicolon. must not end with a semicolon. If HTML escape mode is enabled (new in POCO C++ Libraries
release 1.10.0), HTML special characters < > " &
will be replaced with corresponding character entities.
<%= <expression> %> <%= <expression> %>
---- ----
An alternative form of this is:
<%- <expression> %>
----
The latter form always copies the result of expression to the page verbatim,
without escaping special HTML characters.
Note that if escape mode is disabled, both forms are equivalent.
!!Scriptlet !!Scriptlet
Arbitrary C++ code fragments can be included using the Scriptlet directive. Arbitrary C++ code fragments can be included using the Scriptlet directive.
@ -214,6 +232,13 @@ constructors of the base class must also accept a single argument of the specifi
Cannot be used together with <[context]>. Cannot be used together with <[context]>.
!escape
Enable (set to <[true]>) or disable (<[false]>, default) HTML escape mode.
If enabled, the result of any expression enclosed in <%= %> tags will be HTML-escaped,
which means HTML special characters < > " & will be replaced by corresponding
character entities.
!export !export
Allows to specify a DLL import/export directive that is being added to the request Allows to specify a DLL import/export directive that is being added to the request
@ -378,6 +403,10 @@ system (e.g., <[/help]> on Windows, <[--help]> or <[-h]> on Unix).
* config-file (f): load configuration properties from a file * config-file (f): load configuration properties from a file
* osp (O): add factory class definition/implementation for use with OSP * osp (O): add factory class definition/implementation for use with OSP
* apache (A): add factory class definition/implementation and shared library manifest for use with ApacheConnector * apache (A): add factory class definition/implementation and shared library manifest for use with ApacheConnector
* escape (e): make HTML-escape mode default for all pages
Run the PageCompiler with the --help option to see all available options.
!!Configuration Properties !!Configuration Properties

View File

@ -55,6 +55,10 @@ void CodeWriter::writeImpl(std::ostream& ostr, const std::string& headerFileName
{ {
ostr << "#include \"" << headerFileName << "\"\n"; ostr << "#include \"" << headerFileName << "\"\n";
writeImplIncludes(ostr); writeImplIncludes(ostr);
if (_page.getBool("page.escape", false))
{
ostr << "#include \"Poco/Net/EscapeHTMLStream.h\"\n";
}
if (_page.getBool("page.compressed", false)) if (_page.getBool("page.compressed", false))
{ {
ostr << "#include \"Poco/DeflatingStream.h\"\n"; ostr << "#include \"Poco/DeflatingStream.h\"\n";
@ -349,6 +353,7 @@ void CodeWriter::writeResponse(std::ostream& ostr)
void CodeWriter::writeContent(std::ostream& ostr) void CodeWriter::writeContent(std::ostream& ostr)
{ {
bool escape(_page.getBool("page.escape", false));
bool buffered(_page.getBool("page.buffered", false)); bool buffered(_page.getBool("page.buffered", false));
bool chunked(_page.getBool("page.chunked", !buffered)); bool chunked(_page.getBool("page.chunked", !buffered));
bool compressed(_page.getBool("page.compressed", false)); bool compressed(_page.getBool("page.compressed", false));
@ -359,6 +364,10 @@ void CodeWriter::writeContent(std::ostream& ostr)
if (buffered) if (buffered)
{ {
ostr << "\tstd::stringstream responseStream;\n"; ostr << "\tstd::stringstream responseStream;\n";
if (escape)
{
ostr << "\tPoco::Net::EscapeHTMLOutputStream _escapeStream(responseStream);\n";
}
ostr << cleanupHandler(_page.handler().str()); ostr << cleanupHandler(_page.handler().str());
if (!chunked) if (!chunked)
{ {
@ -371,12 +380,20 @@ void CodeWriter::writeContent(std::ostream& ostr)
ostr << "\tstd::ostream& _responseStream = response.send();\n" ostr << "\tstd::ostream& _responseStream = response.send();\n"
<< "\tPoco::DeflatingOutputStream _gzipStream(_responseStream, Poco::DeflatingStreamBuf::STREAM_GZIP, " << compressionLevel << ");\n" << "\tPoco::DeflatingOutputStream _gzipStream(_responseStream, Poco::DeflatingStreamBuf::STREAM_GZIP, " << compressionLevel << ");\n"
<< "\tstd::ostream& responseStream = _compressResponse ? _gzipStream : _responseStream;\n"; << "\tstd::ostream& responseStream = _compressResponse ? _gzipStream : _responseStream;\n";
if (escape)
{
ostr << "\tPoco::Net::EscapeHTMLOutputStream _escapeStream(responseStream);\n";
}
ostr << cleanupHandler(_page.handler().str()); ostr << cleanupHandler(_page.handler().str());
ostr << "\tif (_compressResponse) _gzipStream.close();\n"; ostr << "\tif (_compressResponse) _gzipStream.close();\n";
} }
else else
{ {
ostr << "\tstd::ostream& responseStream = response.send();\n"; ostr << "\tstd::ostream& responseStream = response.send();\n";
if (escape)
{
ostr << "\tPoco::Net::EscapeHTMLOutputStream _escapeStream(responseStream);\n";
}
ostr << cleanupHandler(_page.handler().str()); ostr << cleanupHandler(_page.handler().str());
} }
} }

View File

@ -58,7 +58,8 @@ public:
_helpRequested(false), _helpRequested(false),
_generateOSPCode(false), _generateOSPCode(false),
_generateApacheCode(false), _generateApacheCode(false),
_emitLineDirectives(true) _emitLineDirectives(true),
_escape(false)
{ {
} }
@ -141,6 +142,12 @@ protected:
.required(false) .required(false)
.repeatable(false) .repeatable(false)
.callback(OptionCallback<CompilerApp>(this, &CompilerApp::handleNoLine))); .callback(OptionCallback<CompilerApp>(this, &CompilerApp::handleNoLine)));
options.addOption(
Option("escape", "e", "Escape special HTML characters (<, >, \", &) in <%= %> expressions.")
.required(false)
.repeatable(false)
.callback(OptionCallback<CompilerApp>(this, &CompilerApp::handleEscape)));
} }
void handleHelp(const std::string& name, const std::string& value) void handleHelp(const std::string& name, const std::string& value)
@ -196,6 +203,11 @@ protected:
_emitLineDirectives = false; _emitLineDirectives = false;
} }
void handleEscape(const std::string& name, const std::string& value)
{
_escape = true;
}
void displayHelp() void displayHelp()
{ {
HelpFormatter helpFormatter(options()); HelpFormatter helpFormatter(options());
@ -329,6 +341,12 @@ protected:
void compile(const std::string& path) void compile(const std::string& path)
{ {
Page page; Page page;
if (_escape)
{
page.set("page.escape", "true");
}
std::string clazz; std::string clazz;
parse(path, page, clazz); parse(path, page, clazz);
write(path, page, clazz); write(path, page, clazz);
@ -365,6 +383,7 @@ private:
bool _generateOSPCode; bool _generateOSPCode;
bool _generateApacheCode; bool _generateApacheCode;
bool _emitLineDirectives; bool _emitLineDirectives;
bool _escape;
std::string _outputDir; std::string _outputDir;
std::string _headerOutputDir; std::string _headerOutputDir;
std::string _headerPrefix; std::string _headerPrefix;

View File

@ -21,6 +21,8 @@ const std::string PageReader::MARKUP_BEGIN("\tresponseStream << \"");
const std::string PageReader::MARKUP_END("\";\n"); const std::string PageReader::MARKUP_END("\";\n");
const std::string PageReader::EXPR_BEGIN("\tresponseStream << ("); const std::string PageReader::EXPR_BEGIN("\tresponseStream << (");
const std::string PageReader::EXPR_END(");\n"); const std::string PageReader::EXPR_END(");\n");
const std::string PageReader::ESC_EXPR_BEGIN("\t_escapeStream << (");
const std::string PageReader::ESC_EXPR_END(");\n");
PageReader::PageReader(Page& page, const std::string& path): PageReader::PageReader(Page& page, const std::string& path):
@ -128,6 +130,25 @@ void PageReader::parse(std::istream& pageStream)
else _page.handler() << token; else _page.handler() << token;
} }
else if (token == "<%=") else if (token == "<%=")
{
if (state == STATE_MARKUP)
{
_page.handler() << MARKUP_END;
generateLineDirective(_page.handler());
if (escape())
{
_page.handler() << ESC_EXPR_BEGIN;
state = STATE_ESC_EXPR;
}
else
{
_page.handler() << EXPR_BEGIN;
state = STATE_EXPR;
}
}
else _page.handler() << token;
}
else if (token == "<%-")
{ {
if (state == STATE_MARKUP) if (state == STATE_MARKUP)
{ {
@ -146,6 +167,12 @@ void PageReader::parse(std::istream& pageStream)
_page.handler() << MARKUP_BEGIN; _page.handler() << MARKUP_BEGIN;
state = STATE_MARKUP; state = STATE_MARKUP;
} }
else if (state == STATE_ESC_EXPR)
{
_page.handler() << ESC_EXPR_END;
_page.handler() << MARKUP_BEGIN;
state = STATE_MARKUP;
}
else if (state == STATE_ATTR) else if (state == STATE_ATTR)
{ {
parseAttributes(); parseAttributes();
@ -201,6 +228,7 @@ void PageReader::parse(std::istream& pageStream)
_page.handler() << token; _page.handler() << token;
break; break;
case STATE_EXPR: case STATE_EXPR:
case STATE_ESC_EXPR:
_page.handler() << token; _page.handler() << token;
break; break;
case STATE_COMMENT: case STATE_COMMENT:
@ -387,3 +415,9 @@ void PageReader::generateLineDirective(std::ostream& ostr)
ostr << "\"\n"; ostr << "\"\n";
} }
} }
bool PageReader::escape() const
{
return _page.getBool("page.escape", false);
}

View File

@ -35,7 +35,7 @@ public:
~PageReader(); ~PageReader();
/// Destroys the PageReader. /// Destroys the PageReader.
void parse(std::istream& pageStream); void parse(std::istream& pageStream);
/// Parses a HTML file containing server page directives, /// Parses a HTML file containing server page directives,
/// converts the file into C++ code and adds the code /// converts the file into C++ code and adds the code
/// to the reader's Page object. Also parses page /// to the reader's Page object. Also parses page
@ -53,6 +53,7 @@ protected:
STATE_PREHANDLER, STATE_PREHANDLER,
STATE_BLOCK, STATE_BLOCK,
STATE_EXPR, STATE_EXPR,
STATE_ESC_EXPR,
STATE_COMMENT, STATE_COMMENT,
STATE_ATTR STATE_ATTR
}; };
@ -61,16 +62,19 @@ protected:
static const std::string MARKUP_END; static const std::string MARKUP_END;
static const std::string EXPR_BEGIN; static const std::string EXPR_BEGIN;
static const std::string EXPR_END; static const std::string EXPR_END;
static const std::string ESC_EXPR_BEGIN;
static const std::string ESC_EXPR_END;
void include(const std::string& path); void include(const std::string& path);
void parseAttributes(); void parseAttributes();
void nextToken(std::istream& istr, std::string& token); void nextToken(std::istream& istr, std::string& token);
void handleAttribute(const std::string& name, const std::string& value); void handleAttribute(const std::string& name, const std::string& value);
std::string where() const; std::string where() const;
bool escape() const;
protected: protected:
void generateLineDirective(std::ostream& ostr); void generateLineDirective(std::ostream& ostr);
private: private:
PageReader(); PageReader();
PageReader(const PageReader&); PageReader(const PageReader&);