1
0
mirror of https://github.com/OlafvdSpek/ctemplate.git synced 2025-09-28 19:05:49 +08:00

ctemplate 0.4

This commit is contained in:
csilvers 2007-03-21 23:22:48 +00:00
parent 22ab1314c3
commit 4a61bf4e95
18 changed files with 658 additions and 175 deletions

View File

@ -23,3 +23,13 @@ Mon Aug 21 17:44:32 2006 Google Inc. <opensource@google.com>
* New contrib/ directory entry: emacs syntax highlighting (tonyg)
* Allow escape-modifiers to affect includes, not just vars (csilvers)
* Add JSON escape-functor (majewski)
Mon Jan 15 14:10:42 2007 Google Inc. <opensource@google.com>
* ctemplate: version 0.4 release
* Improve html-escaping by adding single-quote (bdangelo)
* Improve javascript-escaping by adding more characters too (smknappy)
* Add url-escaping, for url query parameters (dcoker)
* Add support for "pre" escaping, which preserves whitespace (dboswell)
* Typo fixes in documentation (csilvers)
* Expand() returns false if a template file failed to load (jmittleman)

View File

@ -40,7 +40,8 @@ docdir = $(prefix)/share/doc/$(PACKAGE)-$(VERSION)
## top-level boilerplate files. Also add a TODO file if you have one.
dist_doc_DATA = AUTHORS COPYING ChangeLog INSTALL NEWS README \
doc/designstyle.css doc/index.html \
doc/howto.html doc/tips.html doc/example.html
doc/howto.html doc/tips.html doc/example.html \
doc/xss_resources.html
## The libraries (.so's) you want to install
lib_LTLIBRARIES =

View File

@ -137,7 +137,8 @@ ctemplateinclude_HEADERS = \
docdir = $(prefix)/share/doc/$(PACKAGE)-$(VERSION)
dist_doc_DATA = AUTHORS COPYING ChangeLog INSTALL NEWS README \
doc/designstyle.css doc/index.html \
doc/howto.html doc/tips.html doc/example.html
doc/howto.html doc/tips.html doc/example.html \
doc/xss_resources.html

20
configure vendored
View File

@ -1,6 +1,6 @@
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
# Generated by GNU Autoconf 2.57 for ctemplate 0.3.
# Generated by GNU Autoconf 2.57 for ctemplate 0.4.
#
# Report bugs to <opensource@google.com>.
#
@ -422,8 +422,8 @@ SHELL=${CONFIG_SHELL-/bin/sh}
# Identity of this package.
PACKAGE_NAME='ctemplate'
PACKAGE_TARNAME='ctemplate'
PACKAGE_VERSION='0.3'
PACKAGE_STRING='ctemplate 0.3'
PACKAGE_VERSION='0.4'
PACKAGE_STRING='ctemplate 0.4'
PACKAGE_BUGREPORT='opensource@google.com'
ac_unique_file="README"
@ -953,7 +953,7 @@ if test "$ac_init_help" = "long"; then
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
\`configure' configures ctemplate 0.3 to adapt to many kinds of systems.
\`configure' configures ctemplate 0.4 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
@ -1019,7 +1019,7 @@ fi
if test -n "$ac_init_help"; then
case $ac_init_help in
short | recursive ) echo "Configuration of ctemplate 0.3:";;
short | recursive ) echo "Configuration of ctemplate 0.4:";;
esac
cat <<\_ACEOF
@ -1129,7 +1129,7 @@ fi
test -n "$ac_init_help" && exit 0
if $ac_init_version; then
cat <<\_ACEOF
ctemplate configure 0.3
ctemplate configure 0.4
generated by GNU Autoconf 2.57
Copyright 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001, 2002
@ -1144,7 +1144,7 @@ cat >&5 <<_ACEOF
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
It was created by ctemplate $as_me 0.3, which was
It was created by ctemplate $as_me 0.4, which was
generated by GNU Autoconf 2.57. Invocation command line was
$ $0 $@
@ -1737,7 +1737,7 @@ fi
# Define the identity of the package.
PACKAGE=ctemplate
VERSION=0.3
VERSION=0.4
cat >>confdefs.h <<_ACEOF
@ -20554,7 +20554,7 @@ _ASBOX
} >&5
cat >&5 <<_CSEOF
This file was extended by ctemplate $as_me 0.3, which was
This file was extended by ctemplate $as_me 0.4, which was
generated by GNU Autoconf 2.57. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@ -20617,7 +20617,7 @@ _ACEOF
cat >>$CONFIG_STATUS <<_ACEOF
ac_cs_version="\\
ctemplate config.status 0.3
ctemplate config.status 0.4
configured by $0, generated by GNU Autoconf 2.57,
with options \\"`echo "$ac_configure_args" | sed 's/[\\""\`\$]/\\\\&/g'`\\"

View File

@ -5,7 +5,7 @@
# make sure we're interpreted by some minimal autoconf
AC_PREREQ(2.57)
AC_INIT(ctemplate, 0.3, opensource@google.com)
AC_INIT(ctemplate, 0.4, opensource@google.com)
# The argument here is just something that should be in the current directory
# (for sanity checking)
AC_CONFIG_SRCDIR(README)

View File

@ -7,7 +7,7 @@
<link href="http://www.google.com/favicon.ico" type="image/x-icon"
rel="shortcut icon">
<link href="designstyle.css" type="text/css" rel="stylesheet">
<style>
<style type="text/css">
<!--
ol.bluelist li {
color: #3366ff;
@ -28,7 +28,7 @@
<body>
<h1>Template Examples</h1>
<small>(as of 27 February 2006)</small></center>
<small>(as of 1 September 2006)</small>
<br>
@ -48,8 +48,10 @@ search results page:</p>
{{#ONE_RESULT}}
{{! Note: there are two SUBITEM_SECTIONs. They both show or hide together}}
{{#SUBITEM_SECTION}}&lt;blockquote>{{/SUBITEM_SECTION}}
&lt;p>&lt;a href={{JUMP_TO_URL:html_escape}} target=nw>{{LEAD_LINE}}&lt;/a>&lt;font size=-1>
{{! LEAD_LINE is received HTML-escaped from the backend.}}
&lt;p>&lt;a href="{{JUMP_TO_URL:html_escape}}" target=nw>{{LEAD_LINE}}&lt;/a>&lt;font size=-1>
{{! SNIPPET1, SNIPPET2 are HTML-escaped in the snippet generator.}}
{{#SNIPPET1_SECTION}}
&lt;br>{{SNIPPET1}}
{{/SNIPPET1_SECTION}}
@ -59,30 +61,31 @@ search results page:</p>
{{/SNIPPET2_SECTION}}
{{#DESCRIPTION_SECTION}}
{{! DESC is received HTML-escaped from the backend.}}
&lt;br>&lt;span class=f>Description:&lt;/span> {{DESC}}
{{/DESCRIPTION_SECTION}}
{{#CATEGORY_SECTION}}
&lt;br>&lt;span class=f>Category:&lt;/span> &lt;a href={{CAT_URL:html_escape}} class=f>
{{CATEGORY}}&lt;/a>
&lt;br>&lt;span class=f>Category:&lt;/span> &lt;a href="{{CAT_URL:html_escape}}" class=f>
{{CATEGORY:html_escape}}&lt;/a>
{{/CATEGORY_SECTION}}
{{#LASTLINE_SECTION}}
&lt;br>&lt;font color={{ALT_TEXT_COLOR}}>{{URL}}
{{#KS_SECTION}}} - {{KSIZE}}{{/KS_SECTION}}}
{{#CACHE_SECTION}}} - &lt;a href={{CACHE_URL:html_escape}} class=f>Cached&lt;/A>
&lt;br>&lt;font color="{{ALT_TEXT_COLOR:h}}">{{URL:h}}
{{#KS_SECTION}}} - {{KSIZE:h}}{{/KS_SECTION}}}
{{#CACHE_SECTION}}} - &lt;a href="{{CACHE_URL:h}}" class=f>Cached&lt;/A>
{{/CACHE_SECTION}}}
{{#SIM_SECTION}}} - &lt;a href={{SIM_PAGES_URL:html_escape}} class=f>Similar pages&lt;/A>
{{#SIM_SECTION}}} - &lt;a href="{{SIM_PAGES_URL:h}}" class=f>Similar pages&lt;/A>
{{/SIM_SECTION}}}
{{#STOCK_SECTION}}
- &lt;a href={{STOCK_URL:html_escape}} class=f>Stock quotes: {{STOCK_SYMBOL}}&lt;/a>
- &lt;a href="{{STOCK_URL:h}}" class=f>Stock quotes: {{STOCK_SYMBOL:h}}&lt;/a>
{{/STOCK_SECTION}}
&lt;/font>
{{/LASTLINE_SECTION}}
{{#MORE_SECTION}}
&lt;br>[ &lt;a href={{MORE_URL:html_escape}} class=f>More results from {{MORE_LABEL}}&lt;/a> ]
&lt;br>[ &lt;a href="{{MORE_URL:h}}" class=f>More results from {{MORE_LABEL:h}}&lt;/a> ]
{{/MORE_SECTION}}
&lt;/font>&lt;br>
@ -112,7 +115,7 @@ using google::STRIP_WHITESPACE;
// IsEmpty
// A simple utility function
static bool IsEmpty(const string &str) {
static bool IsEmpty(const string &amp;str) {
return str.empty();
}
@ -158,7 +161,7 @@ void fill_search_results_dictionary(TemplateDictionary *dictionary,
++iter) {
QueryResult *qr = (*iter);
// Create a new sub-dictionary named "Result Dict <n>" for this entry
// Create a new sub-dictionary named "Result Dict &lt;n>" for this entry
++resCount;
@ -189,10 +192,10 @@ void fill_search_results_dictionary(TemplateDictionary *dictionary,
"CATEGORY_SECTION");
if (IsEmpty(qr->GetDisplayUrl()) &&
IsEmpty(qr->GetPageSize()) &&
IsEmpty(qr->GetCachedUrl()) &&
IsEmpty(qr->GetSimilarPagesUrl()) &&
if (IsEmpty(qr->GetDisplayUrl()) &amp;&amp;
IsEmpty(qr->GetPageSize()) &amp;&amp;
IsEmpty(qr->GetCachedUrl()) &amp;&amp;
IsEmpty(qr->GetSimilarPagesUrl()) &amp;&amp;
(IsEmpty(qr->GetStockUrl()) ||
IsEmpty(qr->GetStockSymbol())) ) {
// there is nothing on the last line, so hide it altogether
@ -235,8 +238,8 @@ void output_page(const Query* query) {
Template* tpl = Template::GetTemplate(SEARCH_RESULTS_FN, STRIP_WHITESPACE);
TemplateDictionary dict("search-results dict");
string output;
fill_search_results_dictionary(&dict, query);
tpl->Expand(&output, &dict);
fill_search_results_dictionary(&amp;dict, query);
tpl->Expand(&amp;output, &amp;dict);
// output now holds the expanded template
}

View File

@ -8,7 +8,7 @@
<link href="http://www.google.com/favicon.ico" type="image/x-icon"
rel="shortcut icon">
<link href="designstyle.css" type="text/css" rel="stylesheet">
<style>
<style type="text/css">
<!--
ol.bluelist li {
color: #3366ff;
@ -29,7 +29,6 @@
<body>
<h1>How To Use the Google Template System</h1>
<small>(as of 27 February 2006)</small></center>
<br>
@ -80,7 +79,7 @@ embedded in the templates to the data that they will format. Here's
a simple template:</p>
<pre>
&lt;html>&lt;head>&lt;title>{{TITLE}}&lt;/title>{{META_TAGS}}&lt;/head>
&lt;body>{{BODY}}&lt;/body></html>
&lt;body>{{BODY}}&lt;/body>&lt;/html>
</pre>
<p>Here's a dictionary that one could use to instantiate the template:</p>
@ -297,12 +296,26 @@ are the modifiers that are supported:</p>
<table border=1 cellpadding=3>
<tr><th>long name</th><th>short name</th><th>description</th></tr>
<tr><td><code>:html_escape<code></td><td><code>:h</code></td>
<tr><td><code>:html_escape</code></td><td><code>:h</code></td>
<td>html-escapes the variable before output
(eg <code>&amp</code> -> <code>&amp;amp</code>)</td>
(eg <code>&amp;</code> -> <code>&amp;amp;</code>)</td>
</tr>
<tr><td><code>:javascript_escape<code></td><td><code>:j</code></td>
<tr><td><code>:pre_escape</code></td><td><code>:p</code></td>
<td>pre-escapes the variable before output (same as html_escape but
whitespace is preserved; useful for &lt;pre&gt;...&lt;/pre&gt;)</td>
</tr>
<tr><td><code>:url_query_escape</code></td><td><code>:u</code></td>
<td>performs URL escaping on the variable before output.
space is turned into +, and everything other than [0-9a-zA-Z.,_:*/~!()-], is
transformed into %-style escapes. Use this when you are building
URLs with variables as parameters:
<pre>&lt;a href="http://google.com/search?q={{QUERY:u}}"&gt;{{QUERY:h}}&lt;/a&gt;</pre>
</td>
</tr>
<tr><td><code>:javascript_escape</code></td><td><code>:j</code></td>
<td>javascript-escapes the variable before output
(eg <code>"</code> -> <code>\"</code>)</td>
</tr>
@ -469,7 +482,7 @@ the following template:
Last five searches:&lt;ol>
{{#PREV_SEARCHES}
&lt;li> {{PREV_SEARCH}}
{{/PREV_SEARCH}}
{{/PREV_SEARCHES}}
&lt;/ol>
{{>RESULT_TEMPLATE}}
@ -508,7 +521,7 @@ with a different scoping rule. You can call
variable that can be used by all templates in an applications. This
is quite rare.</p>
<p>You can also call <code>dict->SetTemplateGlobalDictionary(name,
<p>You can also call <code>dict->SetTemplateGlobalValue(name,
value)</code>. This sets a variable that is seen by all child
dictionaries of this dictionary: sub-sections you create via
<code>AddSectionDictionary</code>, and included templates you create
@ -516,7 +529,7 @@ via <code>AddIncludeDictionary</code> (both described below). This
differs from <code>SetValue()</code>, because <code>SetValue()</code>
values are never inherited across template-includes. Almost always,
<code>SetValue</code> is what you want;
<code>SetTemplateGlobalDictionary</code> is intended for variables
<code>SetTemplateGlobalValue</code> is intended for variables
that are "global" to a particular template but not all templates, such
as a color scheme to use, a language code, etc.</p>
@ -552,7 +565,7 @@ helper routines to help setting values of a few special forms.</p>
<pre>
google::TemplateDictionary* dict = new google::TemplateDictionary("var example");
dict->SetValue("FOOTER", "Aren't these great results?");
class StarEscape { string operator()(const string& in) const { return string("*") + in + string("*"); } };
class StarEscape { string operator()(const string&amp; in) const { return string("*") + in + string("*"); } };
dict->SetEscapedValue("USERNAME", username, StarEscape());
</pre>
@ -630,10 +643,10 @@ value, escape_functor, section_name)</code>, which lets you escape
dict->SetValueAndShowSection("USERNAME", username, "CHANGE_USER");
// Moving on...
GetPrevSearches(prev_searches, &num_prev_searches);
GetPrevSearches(prev_searches, &amp;num_prev_searches);
if (num_prev_searches > 0) {
for (int i = 0; i < num_prev_searches; ++i) {
TemplateDictionary* sub_dict = dict->AddSectionDictionary("CHANGE_USER");
TemplateDictionary* sub_dict = dict->AddSectionDictionary("PREV_SEARCHES");
sub_dict->SetEscapedValue("PREV_SEARCH", prev_searches[i],
TemplateDictionary::html_escape);
}
@ -647,7 +660,7 @@ value, escape_functor, section_name)</code>, which lets you escape
exactly like <code>SetSectionDictionary(name)</code>. However, since
variable inheritence doesn't work across include boundaries, there is
no template-include equivalent to <code>ShowSection()</code> or
<code>SetValueAndShowSection()</code>.<p>
<code>SetValueAndShowSection()</code>.</p>
<p>One difference bewteen template-includes and sections is that for a
sub-dictionary that you create via
@ -661,7 +674,7 @@ it's relative to <A HREF="#managing">template_root</A>.</p>
<pre>
using google::TemplateDictionary;
TemplateDictionary* dict = new TemplateDictionary("include example");
GetResults(results, &num_results);
GetResults(results, &amp;num_results);
for (int i = 0; i < num_results; ++i) {
TemplateDictionary* sub_dict = dict->AddIncludeDictionary("RESULT_TEMPLATE");
sub_dict->SetFilename("results.tpl");
@ -685,10 +698,12 @@ the output in a string:</p>
<pre>
google::Template* tpl = google::Template::GetTemplate(&lt;filename&gt;, google::STRIP_WHITESPACE);
google::TemplateDictionary dict("debug-name");
FillDictionary(&dict, ...);
FillDictionary(&amp;dict, ...);
string output;
<font color=red>tpl->Expand(&output, &dict);</font>
bool error_free = <font color=red>tpl->Expand(&amp;output, &amp;dict);</font>
// output now holds the expanded template
// Expand returns false if the system cannot load any of the template files
// referenced by the TemplateDictionary.
</pre>
<p>The expanded template is written to the string <code>output</code>.
@ -727,6 +742,143 @@ using a normal, file-based template, and then switch to
template-from-string later if you so desire.</p>
<h2>Security Considerations</h2>
<p>Like all web applications, programs that use the Google Template System
to create HTML documents can be vulnerable to Cross-Site-Scripting (XSS)
attacks unless data inserted into a template is appropriately sanitized
and/or escaped. Which specific form of escaping or sanitization is
required depends on the context in which the template variable appears
within a HTML document (such as, regular "inner text", within a
<code>&lt;script&gt;</code> tag, or within an <code>onClick</code>
handler). The remainder of this section provides a brief summary of
techniques to prevent XSS vulnerabilities due to template variables in
various HTML contexts. Note that while escaping is typically required,
escaping alone is often not enough! You also may need to sanitize or
validate the input, as for instance with URL attributes. For further
information, refer to additional <a
href="xss_resources.html">resources</a> on Cross-Site-Scripting
issues.</p>
<ol class=bluelist>
<li>Regular text (outside of tags and other special situations).
<p>Use the <code>:html_escape</code> or <code>:h</code> modifier to
HTML-escape the variable:</p>
<pre>
&lt;h1&gt;{{HEADING:h}}&lt;/h1&gt;
</pre>
</li>
<li>HTML tag attributes.
<p>Ensure that the attribute is enclosed in double quotes in the template, and
use the <code>:html_escape</code> or <code>:h</code> modifier to escape the
variable:</p>
<pre>
&lt;form ...
&lt;input name=q value=&quot;{{QUERY:h}}&quot;&gt;
&lt;/form&gt;
</pre>
</li>
<li>URL attributes (eg., href/src).
<p>Validate that the URL is a well-formed URL with an appropriate
scheme (e.g., http(s), ftp, mailto). Then enclose the URL in quotes
in the template and use the <code>:html_escape</code> or
<code>:h</code> modifier to escape the variable:</p>
<pre>
&lt;img src="{{IMAGE_URL:h}}"&gt;
</pre>
</li>
<li>Beware of inserting variables containing data from untrusted sources
into the context of a <code>style</code> tag or attribute.
<p>Certain CSS style-sheet constructs can result in the invocation of
javascript. To prevent XSS, the variable must be carefully validated and
sanitized.
</p>
</li>
<li>Populating javascript variables.
<p>For string literals: Ensure that the literal is enclosed in quotes
and apply the <code>:javascript_escape</code> or <code>:j</code>
modifier to escape the variable:</p>
<pre>
&lt;script&gt;
// ...
var msg_text = '{{MESSAGE:j}}';
// ...
&lt;/script&gt;
</pre>
<p>Literals of non-string types cannot be quoted and escaped.
Instead, ensure that the variable's value is set such that it is
guaranteed that the resulting string corresponds to a javascript
literal of the expected type. For example, use</p>
<pre>
dict->SetValueInt("NUM_ITEMS", num_items);
</pre>
<p>to populate an integer javascript variable in the template
fragment</p>
<pre>
&lt;script&gt;
// ...
var num_items = {{NUM_ITEMS}};
// ...
&lt;/script&gt;
</pre>
</li>
<li>Populating javascript variables within event handlers such as
<code>onClick</code>.
<p>Tag attributes whose values are evaluated as a javascript
expression (such as <code>on{Click,Load,etc}</code> handlers) require an
additional consideration, since the attribute's value is HTML-unescaped
by the browser before it is passed to the javascript interpreter.
<p>To avoid XSS vulnerabilities, it is in generally necessary to
HTML-escape after javascript-escaping:</p>
<pre>
&lt;button ...
onclick='GotoUrl(&quot;{{TARGET_URL:j:h}}&quot;);'&gt;
</pre>
</li>
<li>Consider other potential sources of XSS.
<p>There are a number of scenarios in which XSS can arise that are
unrelated to the insertion of values into HTML templates,
including,</p>
<ul class=blacklist>
<li>injection into HTTP headers such as <code>Location</code>,</li>
<li>incorrect browser-side guess of the content-encoding of a HTML
document without explicitly specified <code>charset</code>,</li>
<li>incorrect browser-side guess of a non-HTML document's
content-type that overrides the document's specified
<code>Content-Type</code>,</li>
<li>browser-side handling of documents served for download-to-disk
(<code>Content-Disposition: attachment</code>).</li>
</ul>
<p>Please consult additional <a
href="xss_resources.html">documentation</a> on Cross-Site-Scripting
for more detailed discussion of such issues.</p>
</li>
</ol>
<h2> Working Effectively with Templates </h2>
<h3> <A name="register">Registering Template Strings</A> </h3>
@ -948,7 +1100,7 @@ the executable.</p>
<p>Usage is <code>template-converter &lt;template filename&gt;</code>.
C++ code is output is to stdout; it can be stored in a .h file or
included directly into a C++ file. Perl must be installed to use this
script.<p>
script.</p>
<hr>
<ul>
@ -962,7 +1114,6 @@ script.<p>
<hr>
<address>
Craig Silverstein<br>
27 February 2006
</address>
</body>

View File

@ -7,7 +7,7 @@
<link href="http://www.google.com/favicon.ico" type="image/x-icon"
rel="shortcut icon">
<link href="designstyle.css" type="text/css" rel="stylesheet">
<style>
<style type="text/css">
<!--
ol.bluelist li {
color: #3366ff;
@ -28,7 +28,7 @@
<body>
<h1>Tips and Guidelines for Using the Google Template System</h1>
<small>(as of 27 February 2006)</small></center>
<small>(as of 1 September 2006)</small>
<br>
@ -174,7 +174,8 @@ named <code>fill_one_search_result_dictionary</code>.)
and Header File Generator" below for more explanation about
constant prefixes.)</p> </li>
<li> Use SetFormattedValue discriminately.
<li> <a name="tip_setformattedvalue"></a>Use SetFormattedValue
discriminately.
<p> This method should never be used to sneak HTML into the
executable as in</p>
@ -412,25 +413,51 @@ named <code>fill_one_search_result_dictionary</code>.)
</ul>
</li>
<li> Use variable-modifiers (eg <code>{{VAR:html_escape}}</code>) or
<code>SetEscapedValue</code> when necessary to prevent security
violations.
<li> Use the appropriate variable-modifiers (eg
<code>{{VAR:h}}</code>) to prevent Cross-Site-Scripting
security vulnerabilities.
<p>Variable-modifiers make it very easy to html-escape (or
otherwise escape) text that needs to be escaped for safety. Use
<code>:h</code>, <code>:j</code> and friends liberally.</p>
<p>Apply the appropriate variable-modifiers liberally and omit
them only in those (usually rare) cases where there is a specific
reason the template variable should not be escaped, for example:
<ul class=blacklist>
<li>The template variable contains HTML markup that should be
interpreted by the browser. In this case you must be very careful to
ensure that the variable can in no case contain "harmful" HTML. Also,
keep in mind the <a href="#tip_setformattedvalue">above
recommendation</a> on the use of <code>SetFormattedValue</code> and
consider moving the HTML markup into the template.</li>
<li>The variable is known to be already escaped at the point it
is inserted into the template (for example, the value might be
kept in escaped form in a storage backend). Here, escaping again
via a variable-modifier would result in "double escaping". You
must ensure that the variable has been escaped with the
appropriate escape function for the HTML context into which it
will be inserted into the template (i.e., HTML-escaping versus
javascript-escaping).</li>
</ul>
<p>Applying the modifier even if you don't expect the variable
to contain (malicious) HTML markup keeps you on the safe side.
It also serves to self-document the template by making it
obvious that no XSS can result from the template variable in
question. It is recommended to comment uses of modifier-less
template variables accordingly, for example</p>
<pre>
{{#SNIPPET1_SECTION}}
{{! SNIPPET1 is HTML-escaped in SnippetGenerator::getSnippetForResult }}
&lt;br&gt;{{SNIPPET1}}
{{/SNIPPET1_SECTION}}
</pre>
<p>For situations where you need to provisionally escape, or use
an escape routine other than the built-in ones, the
<code>Escaped</code> versions of the set-value methods
are useful utility functions to use.</p>
<p>As a guide for when to use this: every value accepted
from a user must be HTML-escaped before redisplaying it on
another page. The escaping
prevents the user from executing scripts or displaying raw HTML
via their input values. These methods make it simple to prevent
scripting security violations when used where necessary.</p> </li>
</li>
<li> Do not leave an extra space when using <code>{{BI_SPACE}}</code>
@ -438,17 +465,19 @@ named <code>fill_one_search_result_dictionary</code>.)
replaced by a single space. It is used where you need to make
sure a space is preserved at the end of a line. It is a common
mistake to leave an extra space before this marker, which results
in not one, but two, spaces created in the document.</p> </li>
in not one, but two, spaces created in the document.</p>
<p>Incorrect:<pre>
<p>Incorrect:</p><pre>
&lt;table border=0 {{BI_SPACE}}
align=center></pre></p>
align=center></pre>
<p>Correct:<pre>
<p>Correct:</p><pre>
&lt;table border=0{{BI_SPACE}}
align=center></pre></p>
align=center></pre>
</li>
</ol>
<hr>
<ul>
<li> <A HREF="howto.html">Howto</A> </li>

73
doc/xss_resources.html Normal file
View File

@ -0,0 +1,73 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>Cross-Site Scripting Resources</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link href="http://www.google.com/favicon.ico" type="image/x-icon"
rel="shortcut icon">
<link href="designstyle.css" type="text/css" rel="stylesheet">
<style type="text/css">
<!--
ol.bluelist li {
color: #3366ff;
font-family: sans-serif;
}
ol.bluelist li p {
color: #000;
font-family: "Times Roman", times, serif;
}
ul.blacklist li {
color: #000;
font-family: "Times Roman", times, serif;
}
//-->
</style>
</head>
<body>
<h1> <a name="XSS_Resources"></a>Cross-Site Scripting Resources</h1>
<center><strong>Status: Current</strong> &nbsp;
<small>(as of 17 August 2006)</small></center>
<br>
<p>Cross-Site Scripting (commonly abbreviated as XSS) is a security
issue that arises when an attacker can cause client-side script (such as
JavaScript) of his or her choosing to execute within another user's
browser in the context of a given web-site or web-application. This may
allow the attacker to steal that user's session cookies for the
web-application in question, or otherwise manipulate that user's session
context.
<p>XSS vulnerabilities most often arise if a web-application renders
data that originated from an untrusted source (such as a query
parameter) in a HTML document without carefully validating or escaping
that data.
<p>The following online resources provide further information on XSS
vulnerabilities and how to avoid them:
<ul>
<li>The Open Web Application Security Project (OWASP) has an
<a
href="http://www.owasp.org/index.php/Cross_Site_Scripting">introductory
article</a> on XSS.
</li>
<li>In addition, the OWASP's <a
href="http://www.owasp.org/index.php/Category:OWASP_Guide_Project">Guide to Building Secure Web
Applications and Web Services</a> and the <a
href="http://www.owasp.org/index.php/Category:OWASP_Top_Ten_Project">"Top
Ten" Vulnerabilities</a> include sections on XSS.
</li>
<li>The CERT Coordination Center published <a
href="http://www.cert.org/tech_tips/malicious_code_mitigation.html">Understanding
Malicious Content Mitigation for Web Developers</a> and <a
href="http://www.cert.org/advisories/CA-2000-02.html">Advisory
CA-2000-02 Malicious HTML Tags Embedded in Client Web Requests</a>.
</li>
</ul>
</body>
</html>

View File

@ -54,7 +54,7 @@ rm -rf $RPM_BUILD_ROOT
%files
%defattr(-,root,root)
%doc AUTHORS COPYING ChangeLog INSTALL NEWS README doc/designstyle.css doc/index.html doc/howto.html doc/tips.html doc/example.html contrib/README.contrib contrib/highlighting.vim contrib/tpl-mode.el
%doc AUTHORS COPYING ChangeLog INSTALL NEWS README doc/designstyle.css doc/index.html doc/howto.html doc/tips.html doc/example.html doc/xss_resources.html contrib/README.contrib contrib/highlighting.vim contrib/tpl-mode.el
%{prefix}/lib/libctemplate.so.0
%{prefix}/lib/libctemplate.so.0.0.0

View File

@ -125,8 +125,9 @@ class Template {
// Expand
// Expands the template into a string using the values
// in the supplied dictionary.
void Expand(std::string *output_buffer,
// in the supplied dictionary. Returns true iff all the template
// files load and parse correctly.
bool Expand(std::string *output_buffer,
const TemplateDictionary *dictionary) const;
// Dump
@ -196,7 +197,7 @@ class Template {
// force_annotate_dict is a dictionary that can be used to force
// annotations: even if dictionary->ShouldAnnotateOutput() is false,
// if force_annotate_dict->ShouldAnnotateOutput() is true, we annotate.
void Expand(class ExpandEmitter *expand_emitter,
bool Expand(class ExpandEmitter *expand_emitter,
const TemplateDictionary *dictionary,
const TemplateDictionary *force_annotate_dict) const;

View File

@ -198,10 +198,15 @@ class TemplateDictionary {
// --- ESCAPE FUNCTORS
// Some commonly-used escape functors.
// Escapes < > " & <non-space whitespace> to &lt; &gt; &quot; &amp; <space>
// Escapes < > " ' & <non-space whitespace> to &lt; &gt; &quot;
// &#39; &amp; <space>
struct HtmlEscape { std::string operator()(const std::string&) const; };
static HtmlEscape html_escape;
// Same as HtmlEscape but leaves all whitespace alone. Eg. for <pre>..</pre>
struct PreEscape { std::string operator()(const std::string&) const; };
static PreEscape pre_escape;
// Escapes &nbsp; to &#160;
struct XmlEscape { std::string operator()(const std::string&) const; };
static XmlEscape xml_escape;
@ -210,6 +215,11 @@ class TemplateDictionary {
struct JavascriptEscape { std::string operator()(const std::string&) const; };
static JavascriptEscape javascript_escape;
// Escapes characters not in [0-9a-zA-Z.,_:*/~!()-] as %-prefixed hex.
// Space is encoded as a +.
struct UrlQueryEscape { std::string operator()(const std::string&) const; };
static UrlQueryEscape url_query_escape;
// Escapes " \ / <FF> <CR> <LF> <BS> <TAB> to \" \\ \/ \f \r \n \b \t
struct JsonEscape { std::string operator()(const std::string&) const; };
static JsonEscape json_escape;

View File

@ -74,10 +74,11 @@
// exist and are syntactically correct.
class TemplateNamelist {
friend class TemporaryRegisterTemplate;
private:
// Standard hash libs don't define hash<string>, but do define hash<char*>
struct TemplateHasher {
bool operator()(const std::string& s) const {
size_t operator()(const std::string& s) const {
return @ac_cv_cxx_hash_namespace@::hash<const char*>()(s.c_str());
}
};

View File

@ -187,13 +187,23 @@ static string HtmlModifier(const string& input, const string&) {
return TemplateDictionary::html_escape(input);
}
static string PreModifier(const string& input, const string&) {
return TemplateDictionary::pre_escape(input);
}
static string JavascriptModifier(const string& input, const string&) {
return TemplateDictionary::javascript_escape(input);
}
static string UrlQueryEscapeModifier(const string& input, const string&) {
return TemplateDictionary::url_query_escape(input);
}
static const Modifier g_modifiers[] = {
{ "html_escape", 'h', MODVAL_FORBIDDEN, &HtmlModifier },
{ "pre_escape", 'p', MODVAL_FORBIDDEN, &PreModifier },
{ "javascript_escape", 'j', MODVAL_FORBIDDEN, &JavascriptModifier },
{ "url_query_escape", 'u', MODVAL_FORBIDDEN, &UrlQueryEscapeModifier },
};
@ -384,7 +394,8 @@ class TemplateNode {
// not NULL, and force_annotate_dictionary->ShoudlAnnotateOutput() is
// true, the output is annotated, even if
// dictionary->ShouldAnnotateOutput() is false.
virtual void Expand(ExpandEmitter *output_buffer,
// Returns true iff all the template files load and parse correctly.
virtual bool Expand(ExpandEmitter *output_buffer,
const TemplateDictionary *dictionary,
const TemplateDictionary *force_annotate) const = 0;
@ -464,10 +475,12 @@ class TextTemplateNode : public TemplateNode {
// Expands the text node by simply outputting the text string. This
// virtual method does not use TemplateDictionary or force_annotate.
virtual void Expand(ExpandEmitter *output_buffer,
// Returns true iff all the template files load and parse correctly.
virtual bool Expand(ExpandEmitter *output_buffer,
const TemplateDictionary *,
const TemplateDictionary *) const {
output_buffer->Emit(text_, textlen_);
return true;
}
// A noop for text nodes
@ -510,7 +523,8 @@ class VariableTemplateNode : public TemplateNode {
// Expands the variable node by outputting the value (if there is one)
// of the node variable which is retrieved from the dictionary
virtual void Expand(ExpandEmitter *output_buffer,
// Returns true iff all the template files load and parse correctly.
virtual bool Expand(ExpandEmitter *output_buffer,
const TemplateDictionary *dictionary,
const TemplateDictionary *force_annotate) const;
@ -530,7 +544,7 @@ class VariableTemplateNode : public TemplateNode {
const Token token_;
};
void VariableTemplateNode::Expand(ExpandEmitter *output_buffer,
bool VariableTemplateNode::Expand(ExpandEmitter *output_buffer,
const TemplateDictionary *dictionary,
const TemplateDictionary *force_annotate)
const {
@ -553,6 +567,8 @@ void VariableTemplateNode::Expand(ExpandEmitter *output_buffer,
if (ShouldAnnotateOutput(dictionary, force_annotate)) {
output_buffer->Emit(CloseAnnotation("VAR"));
}
return true;
}
// ----------------------------------------------------------------------
@ -579,7 +595,8 @@ class TemplateTemplateNode : public TemplateNode {
// dictionary if none other is provided in the TemplateDictionary),
// and then outputting this newly expanded template in place of the
// original variable.
virtual void Expand(ExpandEmitter *output_buffer,
// Returns true iff all the template files load and parse correctly.
virtual bool Expand(ExpandEmitter *output_buffer,
const TemplateDictionary *dictionary,
const TemplateDictionary *force_annotate) const;
@ -602,14 +619,16 @@ class TemplateTemplateNode : public TemplateNode {
// If no value is found in the dictionary for the template variable
// in this node, then no output is generated in place of this variable.
void TemplateTemplateNode::Expand(ExpandEmitter *output_buffer,
bool TemplateTemplateNode::Expand(ExpandEmitter *output_buffer,
const TemplateDictionary *dictionary,
const TemplateDictionary *force_annotate)
const {
bool error_free = true;
string variable(token_.text, token_.textlen);
if (dictionary->IsHiddenTemplate(variable)) {
// if this "template include" section is "hidden", do nothing
return;
return true;
}
// see if there is a vector of dictionaries for this template
@ -635,6 +654,7 @@ void TemplateTemplateNode::Expand(ExpandEmitter *output_buffer,
// if there was a problem retrieving the template, bail!
if (!included_template) {
LOG(ERROR) << "Failed to load included template: " << filename << endl;
error_free = false;
continue;
}
@ -653,15 +673,17 @@ void TemplateTemplateNode::Expand(ExpandEmitter *output_buffer,
// modify the string, and append to output_buffer. Otherwise (common
// case), we can just expand into the output-buffer directly.
if (token_.modifier_plus_values.empty()) { // no need to modify sub-template
included_template->Expand(output_buffer,
*dv_iter ? *dv_iter : dictionary,
ShouldAnnotateOutput(dictionary, force_annotate));
error_free &= included_template->Expand(
output_buffer,
*dv_iter ? *dv_iter : dictionary,
ShouldAnnotateOutput(dictionary, force_annotate));
} else {
string sub_template;
StringEmitter subtemplate_buffer(&sub_template);
included_template->Expand(&subtemplate_buffer,
*dv_iter ? *dv_iter : dictionary,
ShouldAnnotateOutput(dictionary, force_annotate));
error_free &= included_template->Expand(
&subtemplate_buffer,
*dv_iter ? *dv_iter : dictionary,
ShouldAnnotateOutput(dictionary, force_annotate));
ModifyString(token_.modifier_plus_values, &sub_template);
output_buffer->Emit(sub_template);
}
@ -669,6 +691,8 @@ void TemplateTemplateNode::Expand(ExpandEmitter *output_buffer,
output_buffer->Emit(CloseAnnotation("INC"));
}
}
return error_free;
}
// ----------------------------------------------------------------------
@ -705,7 +729,8 @@ class SectionTemplateNode : public TemplateNode {
// iteration of the section as well as to show a non-hidden section,
// allowing the section template syntax to be used for both conditional
// and iterative text).
virtual void Expand(ExpandEmitter *output_buffer,
// Returns true iff all the template files load and parse correctly.
virtual bool Expand(ExpandEmitter *output_buffer,
const TemplateDictionary *dictionary,
const TemplateDictionary* force_annotate) const;
@ -770,10 +795,12 @@ SectionTemplateNode::~SectionTemplateNode() {
<< string(token_.text, token_.textlen) << endl;
}
void SectionTemplateNode::Expand(ExpandEmitter *output_buffer,
bool SectionTemplateNode::Expand(ExpandEmitter *output_buffer,
const TemplateDictionary *dictionary,
const TemplateDictionary *force_annotate)
const {
bool error_free = true;
const vector<TemplateDictionary*> *dv;
string variable(token_.text, token_.textlen);
@ -784,7 +811,7 @@ void SectionTemplateNode::Expand(ExpandEmitter *output_buffer,
dv = g_use_current_dict; // 'expand once, using the passed in dictionary'
} else {
if (dictionary->IsHiddenSection(variable)) {
return; // if this section is "hidden", do nothing
return true; // if this section is "hidden", do nothing
}
dv = &dictionary->GetDictionaries(variable);
if (dv->empty()) // empty dict means 'expand once using containing dict'
@ -802,15 +829,18 @@ void SectionTemplateNode::Expand(ExpandEmitter *output_buffer,
// We force children to annotate the output if we have to.
NodeList::const_iterator iter = node_list_.begin();
for (; iter != node_list_.end(); ++iter) {
(*iter)->Expand(output_buffer,
*dv_iter ? *dv_iter : dictionary,
ShouldAnnotateOutput(dictionary, force_annotate));
error_free &=
(*iter)->Expand(output_buffer,
*dv_iter ? *dv_iter : dictionary,
ShouldAnnotateOutput(dictionary, force_annotate));
}
if (ShouldAnnotateOutput(dictionary, force_annotate)) {
output_buffer->Emit(CloseAnnotation("SEC"));
}
}
return error_free;
}
void SectionTemplateNode::WriteHeaderEntries(string *outstring,
@ -1627,9 +1657,12 @@ int Template::InsertFile(const char *file, int len, char* buffer) {
// appropriate value from the passed-in dictionary.
// ----------------------------------------------------------------------
void Template::Expand(ExpandEmitter *expand_emitter,
bool Template::Expand(ExpandEmitter *expand_emitter,
const TemplateDictionary *dict,
const TemplateDictionary *force_annotate_output) const {
// Accumulator for the results of Expand for each sub-tree.
bool error_free = true;
// We hold mutex_ the entire time we expand, because
// ReloadIfChanged(), which also holds mutex_, is allowed to delete
// tree_, and we want to make sure it doesn't do that (in another
@ -1640,7 +1673,7 @@ void Template::Expand(ExpandEmitter *expand_emitter,
if (state() != TS_READY) {
// We'd like to reload if state_ == TS_RELOAD, but we're a const method
mutex_->Unlock();
return;
return false;
}
const bool should_annotate = (dict->ShouldAnnotateOutput() ||
@ -1662,19 +1695,21 @@ void Template::Expand(ExpandEmitter *expand_emitter,
}
// We force our sub-tree to annotate output if we annotate output
tree_->Expand(expand_emitter, dict, force_annotate_output);
error_free &= tree_->Expand(expand_emitter, dict, force_annotate_output);
if (should_annotate) {
expand_emitter->Emit(TemplateNode::CloseAnnotation("FILE"));
}
mutex_->Unlock();
return error_free;
}
void Template::Expand(string *output_buffer,
bool Template::Expand(string *output_buffer,
const TemplateDictionary *dict) const {
StringEmitter e(output_buffer);
Expand(&e, dict, NULL);
return Expand(&e, dict, NULL);
}
_END_GOOGLE_NAMESPACE_

View File

@ -88,9 +88,11 @@ static StaticMutexInit g_static_mutex_initializer; // constructs early
/*static*/ TemplateDictionary::GlobalDict* TemplateDictionary::global_dict_
= NULL;
/*static*/ TemplateDictionary::HtmlEscape TemplateDictionary::html_escape;
/*static*/ TemplateDictionary::PreEscape TemplateDictionary::pre_escape;
/*static*/ TemplateDictionary::XmlEscape TemplateDictionary::xml_escape;
/*static*/ TemplateDictionary::JavascriptEscape TemplateDictionary::javascript_escape;
/*static*/ TemplateDictionary::JsonEscape TemplateDictionary::json_escape;
/*static*/ TemplateDictionary::UrlQueryEscape TemplateDictionary::url_query_escape;
// ----------------------------------------------------------------------
@ -744,13 +746,16 @@ const char *TemplateDictionary::GetIncludeTemplateName(const string& variable,
// ----------------------------------------------------------------------
// HtmlEscape
// PreEscape
// XMLEscape
// UrlQueryEscape
// JavascriptEscape
// Escape functors that can be used by SetEscapedValue().
// Each takes a string as input and gives a string as output.
// ----------------------------------------------------------------------
// Escapes < > " & <non-space whitespace> to &lt; &gt; &quot; &amp; <space>
// Escapes < > " ' & <non-space whitespace> to &lt; &gt; &quot; &#39;
// &amp; <space>
string TemplateDictionary::HtmlEscape::operator()(const string& in) const {
string out;
// we'll reserve some space in out to account for minimal escaping: say 12%
@ -759,6 +764,7 @@ string TemplateDictionary::HtmlEscape::operator()(const string& in) const {
switch (in[i]) {
case '&': out += "&amp;"; break;
case '"': out += "&quot;"; break;
case '\'': out += "&#39;"; break;
case '<': out += "&lt;"; break;
case '>': out += "&gt;"; break;
case '\r': case '\n': case '\v': case '\f':
@ -769,6 +775,26 @@ string TemplateDictionary::HtmlEscape::operator()(const string& in) const {
return out;
}
// Escapes < > " ' & to &lt; &gt; &quot; &#39; &amp;
// (Same as HtmlEscape but leaves whitespace alone.)
string TemplateDictionary::PreEscape::operator()(const string& in) const {
string out;
// we'll reserve some space in out to account for minimal escaping: say 12%
out.reserve(in.size() + in.size()/8 + 16);
for (int i = 0; i < in.length(); ++i) {
switch (in[i]) {
case '&': out += "&amp;"; break;
case '"': out += "&quot;"; break;
case '\'': out += "&#39;"; break;
case '<': out += "&lt;"; break;
case '>': out += "&gt;"; break;
// All other whitespace we leave alone!
default: out += in[i];
}
}
return out;
}
// Escapes &nbsp; to &#160;
// TODO(csilvers): have this do something more useful, once all callers have
// been fixed. Dunno what 'more useful' might be, yet.
@ -799,12 +825,45 @@ string TemplateDictionary::JavascriptEscape::operator()(const string& in) const
case '\r': out += "\\r"; break;
case '\n': out += "\\n"; break;
case '\b': out += "\\b"; break;
case '&': out += "\\x26"; break;
case '<': out += "\\x3c"; break;
case '>': out += "\\x3e"; break;
case '=': out += "\\x3d"; break;
default: out += in[i];
}
}
return out;
}
string TemplateDictionary::UrlQueryEscape::operator()(const string& in) const {
// Everything not matching [0-9a-zA-Z.,_*/~!()-] is escaped.
static unsigned long _safe_characters[8] = {
0x00000000L, 0x03fff702L, 0x87fffffeL, 0x47fffffeL,
0x00000000L, 0x00000000L, 0x00000000L, 0x00000000L
};
int max_string_length = in.size() * 3 + 1;
char out[max_string_length];
int i;
int j;
for (i = 0, j = 0; i < in.size(); i++) {
unsigned char c = in[i];
if (c == ' ') {
out[j++] = '+';
} else if ((_safe_characters[(c)>>5] & (1 << ((c) & 31)))) {
out[j++] = c;
} else {
out[j++] = '%';
out[j++] = ((c>>4) < 10 ? ((c>>4) + '0') : (((c>>4) - 10) + 'A'));
out[j++] = ((c&0xf) < 10 ? ((c&0xf) + '0') : (((c&0xf) - 10) + 'A'));
}
}
out[j++] = '\0';
return string(out);
}
// Escapes " / \ <BS> <FF> <CR> <LF> <TAB> to \" \/ \\ \b \f \r \n \t
string TemplateDictionary::JsonEscape::operator()(const string& in) const {
string out;

View File

@ -194,6 +194,13 @@ class TemplateDictionaryUnittest {
dict.SetEscapedValue("hardest HTML",
"<A HREF='foo'\nid=\"bar\t\t&&\vbaz\">",
TemplateDictionary::html_escape);
dict.SetEscapedValue("easy PRE", "foo",
TemplateDictionary::pre_escape);
dict.SetEscapedValue("harder PRE", "foo & bar",
TemplateDictionary::pre_escape);
dict.SetEscapedValue("hardest PRE",
" \"--\v--\f--\n--\t--&--<-->--'--\"",
TemplateDictionary::pre_escape);
dict.SetEscapedValue("easy XML", "xoo",
TemplateDictionary::xml_escape);
dict.SetEscapedValue("harder XML", "xoo & xar",
@ -207,6 +214,9 @@ class TemplateDictionaryUnittest {
dict.SetEscapedValue("hardest JS",
("f = 'foo';\r\n\tprint \"\\&foo = \b\", \"foo\""),
TemplateDictionary::javascript_escape);
dict.SetEscapedValue("close script JS",
"//--></script><script>alert(123);</script>",
TemplateDictionary::javascript_escape);
dict.SetEscapedValue("easy JSON", "joo",
TemplateDictionary::json_escape);
dict.SetEscapedValue("harder JSON", "f = \"joo\"; e = 'joo';",
@ -214,6 +224,38 @@ class TemplateDictionaryUnittest {
dict.SetEscapedValue("hardest JSON",
("f = 'foo';\r\n\t\fprint \"\\&foo = /\b\", \"foo\""),
TemplateDictionary::json_escape);
// Test data for URL Query Escaping. The first three tests do not need
// escaping.
dict.SetEscapedValue("query escape 0", "",
TemplateDictionary::url_query_escape);
dict.SetEscapedValue("query escape 1", "noop",
TemplateDictionary::url_query_escape);
dict.SetEscapedValue("query escape 2",
"0123456789abcdefghjijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ.-_*/~!(),",
TemplateDictionary::url_query_escape);
dict.SetEscapedValue("query escape 3", " ?a=b;c#d ",
TemplateDictionary::url_query_escape);
dict.SetEscapedValue("query escape 4", "#$%&+<=>?@[\\]^`{|}",
TemplateDictionary::url_query_escape);
dict.SetEscapedValue("query escape 5", "\xDE\xAD\xCA\xFE",
TemplateDictionary::url_query_escape);
dict.SetEscapedValue("query escape 6", "\"':",
TemplateDictionary::url_query_escape);
// Test cases for URL Query Escaping
ASSERT_STREQ(dict.GetSectionValue("query escape 0"), "");
ASSERT_STREQ(dict.GetSectionValue("query escape 1"), "noop");
ASSERT_STREQ(dict.GetSectionValue("query escape 2"),
"0123456789abcdefghjijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ.-_*/~!(),");
ASSERT_STREQ(dict.GetSectionValue("query escape 3"), "+%3Fa%3Db%3Bc%23d+");
ASSERT_STREQ(dict.GetSectionValue("query escape 4"),
"%23%24%25%26%2B%3C%3D%3E%3F%40%5B%5C%5D%5E%60%7B%7C%7D");
ASSERT_STREQ(dict.GetSectionValue("query escape 5"), "%DE%AD%CA%FE");
ASSERT_STREQ(dict.GetSectionValue("query escape 6"), "%22%27%3A");
FooEscaper foo_escaper;
dict.SetEscapedValue("easy foo", "hello there!",
FooEscaper());
@ -231,26 +273,38 @@ class TemplateDictionaryUnittest {
ASSERT_STREQ(dict.GetSectionValue("easy HTML"), "foo");
ASSERT_STREQ(dict.GetSectionValue("harder HTML"), "foo &amp; bar");
ASSERT_STREQ(dict.GetSectionValue("hardest HTML"),
"&lt;A HREF='foo' id=&quot;bar &amp;&amp; baz&quot;&gt;");
"&lt;A HREF=&#39;foo&#39; id=&quot;bar &amp;&amp; "
"baz&quot;&gt;");
ASSERT_STREQ(dict.GetSectionValue("easy PRE"), "foo");
ASSERT_STREQ(dict.GetSectionValue("harder PRE"), "foo &amp; bar");
ASSERT_STREQ(dict.GetSectionValue("hardest PRE"),
" &quot;--\v--\f--\n--\t--&amp;--&lt;--&gt;--&#39;--&quot;");
ASSERT_STREQ(dict.GetSectionValue("easy XML"), "xoo");
ASSERT_STREQ(dict.GetSectionValue("harder XML"), "xoo & xar");
ASSERT_STREQ(dict.GetSectionValue("hardest XML"),
"xoo &#160; xar&#160;xaz &nbsp");
ASSERT_STREQ(dict.GetSectionValue("easy JS"), "joo");
ASSERT_STREQ(dict.GetSectionValue("harder JS"), "f = \\'joo\\';");
ASSERT_STREQ(dict.GetSectionValue("harder JS"), "f \\x3d \\'joo\\';");
ASSERT_STREQ(dict.GetSectionValue("hardest JS"),
"f = \\'foo\\';\\r\\n\tprint \\\"\\\\&foo = \\b\\\", \\\"foo\\\"");
"f \\x3d \\'foo\\';\\r\\n\tprint \\\"\\\\\\x26foo \\x3d "
"\\b\\\", \\\"foo\\\"");
ASSERT_STREQ(dict.GetSectionValue("close script JS"),
"//--\\x3e\\x3c/script\\x3e\\x3cscript\\x3e"
"alert(123);\\x3c/script\\x3e");
ASSERT_STREQ(dict.GetSectionValue("easy JSON"), "joo");
ASSERT_STREQ(dict.GetSectionValue("harder JSON"), "f = \\\"joo\\\"; e = 'joo';");
ASSERT_STREQ(dict.GetSectionValue("harder JSON"), "f = \\\"joo\\\"; "
"e = 'joo';");
ASSERT_STREQ(dict.GetSectionValue("hardest JSON"),
"f = 'foo';\\r\\n\\t\\fprint \\\"\\\\&foo = \\/\\b\\\", \\\"foo\\\"");
"f = 'foo';\\r\\n\\t\\fprint \\\"\\\\&foo = \\/\\b\\\", "
"\\\"foo\\\"");
ASSERT_STREQ(dict.GetSectionValue("easy foo"), "foo");
ASSERT_STREQ(dict.GetSectionValue("harder foo"), "foo");
ASSERT_STREQ(dict.GetSectionValue("easy double"), "doo");
ASSERT_STREQ(dict.GetSectionValue("harder double"),
"&lt;A HREF=\\'foo\\'&gt;\\n");
"\\x3cA HREF\\x3d\\&#39;foo\\&#39;\\x3e\\n");
ASSERT_STREQ(dict.GetSectionValue("hardest double"),
"print \\&quot;&lt;A HREF=\\'foo\\'&gt;\\&quot;;\\r\\n\\\\1;");
"print \\&quot;\\x3cA HREF\\x3d\\&#39;foo\\&#39;\\x3e\\&quot;;"
"\\r\\n\\\\1;");
}
static void TestSetEscapedFormattedValue() {
@ -258,11 +312,19 @@ class TemplateDictionaryUnittest {
dict.SetEscapedFormattedValue("HTML", TemplateDictionary::html_escape,
"This is <%s> #%.4f", "a & b", 1.0/3);
dict.SetEscapedFormattedValue("PRE", TemplateDictionary::pre_escape,
"if %s x = %.4f;", "(a < 1 && b > 2)\n\t", 1.0/3);
dict.SetEscapedFormattedValue("URL", TemplateDictionary::url_query_escape,
"pageviews-%s", "r?egex");
dict.SetEscapedFormattedValue("XML", TemplateDictionary::xml_escape,
"This&nbsp;is&nb%s -- ok?", "sp; #1&nbsp;");
ASSERT_STREQ(dict.GetSectionValue("HTML"),
"This is &lt;a &amp; b&gt; #0.3333");
ASSERT_STREQ(dict.GetSectionValue("PRE"),
"if (a &lt; 1 &amp;&amp; b &gt; 2)\n\t x = 0.3333;");
ASSERT_STREQ(dict.GetSectionValue("URL"), "pageviews-r%3Fegex");
ASSERT_STREQ(dict.GetSectionValue("XML"),
"This&#160;is&#160; #1&#160; -- ok?");
}

View File

@ -156,9 +156,10 @@ static Template* StringToTemplate(const string& s, Strip strip) {
// This is esp. useful for calling from within gdb.
// The gdb nice-ness is balanced by the need for the caller to delete the buf.
static const char* ExpandIs(Template* tpl, TemplateDictionary *dict) {
static const char* ExpandIs(Template* tpl, TemplateDictionary *dict,
bool expected) {
string outstring;
tpl->Expand(&outstring, dict);
ASSERT(expected == tpl->Expand(&outstring, dict));
char* buf = new char [outstring.size()+1];
strcpy(buf, outstring.c_str());
@ -166,8 +167,12 @@ static const char* ExpandIs(Template* tpl, TemplateDictionary *dict) {
}
static void AssertExpandIs(Template* tpl, TemplateDictionary *dict,
const string& is) {
const char* buf = ExpandIs(tpl, dict);
const string& is, bool expected) {
const char* buf = ExpandIs(tpl, dict, expected);
if (strcmp(buf, is.c_str())) {
printf("expected = '%s'\n", is.c_str());
printf("actual = '%s'\n", buf);
}
ASSERT_STREQ(buf, is.c_str());
delete [] buf;
}
@ -182,52 +187,75 @@ class TemplateUnittest {
static void TestVariable() {
Template* tpl = StringToTemplate("hi {{VAR}} lo", STRIP_WHITESPACE);
TemplateDictionary dict("dict");
AssertExpandIs(tpl, &dict, "hi lo");
AssertExpandIs(tpl, &dict, "hi lo", true);
dict.SetValue("VAR", "yo");
AssertExpandIs(tpl, &dict, "hi yo lo");
AssertExpandIs(tpl, &dict, "hi yo lo", true);
dict.SetValue("VAR", "yoyo");
AssertExpandIs(tpl, &dict, "hi yoyo lo");
AssertExpandIs(tpl, &dict, "hi yoyo lo", true);
dict.SetValue("VA", "noyo");
dict.SetValue("VAR ", "noyo2");
dict.SetValue("var", "noyo3");
AssertExpandIs(tpl, &dict, "hi yoyo lo");
AssertExpandIs(tpl, &dict, "hi yoyo lo", true);
}
static void TestVariableWithModifiers() {
Template* tpl = StringToTemplate("hi {{VAR:html_escape}} lo",
STRIP_WHITESPACE);
TemplateDictionary dict("dict");
dict.SetValue("VAR", "yo");
AssertExpandIs(tpl, &dict, "hi yo lo");
dict.SetValue("VAR", "yo&yo");
AssertExpandIs(tpl, &dict, "hi yo&amp;yo lo");
// Test with no modifiers.
dict.SetValue("VAR", "yo");
AssertExpandIs(tpl, &dict, "hi yo lo", true);
dict.SetValue("VAR", "yo&yo");
AssertExpandIs(tpl, &dict, "hi yo&amp;yo lo", true);
// Test with URL escaping.
tpl = StringToTemplate("<a href=\"/servlet?param={{VAR:u}}\">",
STRIP_WHITESPACE);
AssertExpandIs(tpl, &dict, "<a href=\"/servlet?param=yo%26yo\">", true);
tpl = StringToTemplate("<a href='/servlet?param={{VAR:url_query_escape}}'>",
STRIP_WHITESPACE);
AssertExpandIs(tpl, &dict, "<a href='/servlet?param=yo%26yo'>", true);
// Test with multiple URL escaping.
tpl = StringToTemplate("<a href=\"/servlet?param={{VAR:u:u}}\">",
STRIP_WHITESPACE);
AssertExpandIs(tpl, &dict, "<a href=\"/servlet?param=yo%2526yo\">", true);
// Test HTML escaping.
tpl = StringToTemplate("hi {{VAR:h}} lo", STRIP_WHITESPACE);
AssertExpandIs(tpl, &dict, "hi yo&amp;yo lo");
AssertExpandIs(tpl, &dict, "hi yo&amp;yo lo", true);
tpl = StringToTemplate("hi {{VAR:h:h}} lo", STRIP_WHITESPACE);
AssertExpandIs(tpl, &dict, "hi yo&amp;amp;yo lo");
AssertExpandIs(tpl, &dict, "hi yo&amp;amp;yo lo", true);
// Test with no modifiers.
tpl = StringToTemplate("hi {{VAR}} lo", STRIP_WHITESPACE);
AssertExpandIs(tpl, &dict, "hi yo&yo lo");
AssertExpandIs(tpl, &dict, "hi yo&yo lo", true);
// Check that ordering is right
dict.SetValue("VAR", "yo\nyo");
tpl = StringToTemplate("hi {{VAR:h}} lo", STRIP_WHITESPACE);
AssertExpandIs(tpl, &dict, "hi yo yo lo");
AssertExpandIs(tpl, &dict, "hi yo yo lo", true);
tpl = StringToTemplate("hi {{VAR:p}} lo", STRIP_WHITESPACE);
AssertExpandIs(tpl, &dict, "hi yo\nyo lo", true);
tpl = StringToTemplate("hi {{VAR:j}} lo", STRIP_WHITESPACE);
AssertExpandIs(tpl, &dict, "hi yo\\nyo lo");
AssertExpandIs(tpl, &dict, "hi yo\\nyo lo", true);
tpl = StringToTemplate("hi {{VAR:h:j}} lo", STRIP_WHITESPACE);
AssertExpandIs(tpl, &dict, "hi yo yo lo");
AssertExpandIs(tpl, &dict, "hi yo yo lo", true);
tpl = StringToTemplate("hi {{VAR:j:h}} lo", STRIP_WHITESPACE);
AssertExpandIs(tpl, &dict, "hi yo\\nyo lo");
AssertExpandIs(tpl, &dict, "hi yo\\nyo lo", true);
// Check more complicated modifiers using fullname
tpl = StringToTemplate("hi {{VAR:javascript_escape:h}} lo",
STRIP_WHITESPACE);
AssertExpandIs(tpl, &dict, "hi yo\\nyo lo");
AssertExpandIs(tpl, &dict, "hi yo\\nyo lo", true);
tpl = StringToTemplate("hi {{VAR:j:html_escape}} lo",
STRIP_WHITESPACE);
AssertExpandIs(tpl, &dict, "hi yo\\nyo lo", true);
tpl = StringToTemplate("hi {{VAR:pre_escape:j}} lo",
STRIP_WHITESPACE);
AssertExpandIs(tpl, &dict, "hi yo\\nyo lo", true);
// Check that illegal modifiers are rejected
tpl = StringToTemplate("hi {{VAR:j:h2}} lo", STRIP_WHITESPACE);
@ -247,6 +275,9 @@ class TemplateUnittest {
ASSERT(tpl == NULL);
tpl = StringToTemplate("hi {{VAR:html_escape=yes}} lo", STRIP_WHITESPACE);
ASSERT(tpl == NULL);
tpl = StringToTemplate("hi {{VAR:url_query_escape=wombats}} lo",
STRIP_WHITESPACE);
ASSERT(tpl == NULL);
// Check we don't allow modifiers on sections
tpl = StringToTemplate("hi {{#VAR:h}} lo {{/VAR}}", STRIP_WHITESPACE);
@ -258,24 +289,24 @@ class TemplateUnittest {
"boo!\nhi {{#SEC}}lo{{#SUBSEC}}jo{{/SUBSEC}}{{/SEC}} bar",
STRIP_WHITESPACE);
TemplateDictionary dict("dict");
AssertExpandIs(tpl, &dict, "boo!hi bar");
AssertExpandIs(tpl, &dict, "boo!hi bar", true);
dict.ShowSection("SEC");
AssertExpandIs(tpl, &dict, "boo!hi lo bar");
AssertExpandIs(tpl, &dict, "boo!hi lo bar", true);
dict.ShowSection("SEC");
AssertExpandIs(tpl, &dict, "boo!hi lo bar");
AssertExpandIs(tpl, &dict, "boo!hi lo bar", true);
// This should work even though subsec isn't a child of the main dict
dict.ShowSection("SUBSEC");
AssertExpandIs(tpl, &dict, "boo!hi lojo bar");
AssertExpandIs(tpl, &dict, "boo!hi lojo bar", true);
TemplateDictionary dict2("dict2");
dict2.AddSectionDictionary("SEC");
AssertExpandIs(tpl, &dict2, "boo!hi lo bar");
AssertExpandIs(tpl, &dict2, "boo!hi lo bar", true);
dict2.AddSectionDictionary("SEC");
AssertExpandIs(tpl, &dict2, "boo!hi lolo bar");
AssertExpandIs(tpl, &dict2, "boo!hi lolo bar", true);
dict2.AddSectionDictionary("sec");
AssertExpandIs(tpl, &dict2, "boo!hi lolo bar");
AssertExpandIs(tpl, &dict2, "boo!hi lolo bar", true);
dict2.ShowSection("SUBSEC");
AssertExpandIs(tpl, &dict2, "boo!hi lojolojo bar");
AssertExpandIs(tpl, &dict2, "boo!hi lojolojo bar", true);
}
static void TestInclude() {
@ -284,42 +315,57 @@ class TemplateUnittest {
string incname_bad = StringToTemplateFile("{{syntax_error");
Template* tpl = StringToTemplate("hi {{>INC}} bar\n", STRIP_WHITESPACE);
TemplateDictionary dict("dict");
AssertExpandIs(tpl, &dict, "hi bar");
AssertExpandIs(tpl, &dict, "hi bar", true);
dict.AddIncludeDictionary("INC");
AssertExpandIs(tpl, &dict, "hi bar"); // noop: no filename was set
AssertExpandIs(tpl, &dict, "hi bar", true); // noop: no filename was set
dict.AddIncludeDictionary("INC")->SetFilename("/notarealfile ");
AssertExpandIs(tpl, &dict, "hi bar"); // noop: illegal filename
AssertExpandIs(tpl, &dict, "hi bar", false); // noop: illegal filename
dict.AddIncludeDictionary("INC")->SetFilename(incname);
AssertExpandIs(tpl, &dict, "hi include file bar");
AssertExpandIs(tpl, &dict, "hi include file bar", false);
dict.AddIncludeDictionary("INC")->SetFilename(incname_bad);
AssertExpandIs(tpl, &dict, "hi include file bar"); // noop: syntax error
AssertExpandIs(tpl, &dict, "hi include file bar",
false); // noop: syntax error
dict.AddIncludeDictionary("INC")->SetFilename(incname);
AssertExpandIs(tpl, &dict, "hi include fileinclude file bar");
AssertExpandIs(tpl, &dict, "hi include fileinclude file bar", false);
dict.AddIncludeDictionary("inc")->SetFilename(incname);
AssertExpandIs(tpl, &dict, "hi include fileinclude file bar");
AssertExpandIs(tpl, &dict, "hi include fileinclude file bar", false);
dict.AddIncludeDictionary("INC")->SetFilename(incname2);
AssertExpandIs(tpl, &dict, "hi include fileinclude fileinc2 bar");
AssertExpandIs(tpl, &dict, "hi include fileinclude fileinc2 bar", false);
// Now test that includes preserve Strip
Template* tpl2 = StringToTemplate("hi {{>INC}} bar", DO_NOT_STRIP);
AssertExpandIs(tpl2, &dict, "hi include file\ninclude file\ninc2\n bar");
AssertExpandIs(tpl2, &dict, "hi include file\ninclude file\ninc2\n bar",
false);
}
static void TestIncludeWithModifiers() {
string incname = StringToTemplateFile("include & print file\n");
string incname2 = StringToTemplateFile("inc2\n");
// Note this also tests that html-escape, but not javascript-escape,
// escapes \n to <space>
string incname3 = StringToTemplateFile("yo&yo");
// Note this also tests that html-escape, but not javascript-escape or
// pre-escape, escapes \n to <space>
Template* tpl1 = StringToTemplate("hi {{>INC:h}} bar\n", DO_NOT_STRIP);
Template* tpl2 = StringToTemplate("hi {{>INC:javascript_escape}} bar\n",
DO_NOT_STRIP);
Template* tpl3 = StringToTemplate("hi {{>INC:pre_escape}} bar\n",
DO_NOT_STRIP);
Template* tpl4 = StringToTemplate("hi {{>INC:u}} bar\n", DO_NOT_STRIP);
TemplateDictionary dict("dict");
AssertExpandIs(tpl1, &dict, "hi bar\n");
AssertExpandIs(tpl1, &dict, "hi bar\n", true);
dict.AddIncludeDictionary("INC")->SetFilename(incname);
AssertExpandIs(tpl1, &dict, "hi include &amp; print file bar\n");
AssertExpandIs(tpl1, &dict, "hi include &amp; print file bar\n", true);
dict.AddIncludeDictionary("INC")->SetFilename(incname2);
AssertExpandIs(tpl1, &dict, "hi include &amp; print file inc2 bar\n");
AssertExpandIs(tpl2, &dict, "hi include & print file\\ninc2\\n bar\n");
AssertExpandIs(tpl1, &dict, "hi include &amp; print file inc2 bar\n",
true);
AssertExpandIs(tpl2, &dict, "hi include \\x26 print file\\ninc2\\n bar\n",
true);
AssertExpandIs(tpl3, &dict, "hi include &amp; print file\ninc2\n bar\n",
true);
dict.AddIncludeDictionary("INC")->SetFilename(incname3);
AssertExpandIs(tpl4, &dict,
"hi include+%26+print+file%0Ainc2%0Ayo%26yo bar\n",
true);
// Don't test modifier syntax here; that's in TestVariableWithModifiers()
}
@ -331,18 +377,18 @@ class TemplateUnittest {
TemplateDictionary dict("dict");
dict.SetValue("FOO", "foo");
dict.ShowSection("SEC");
AssertExpandIs(tpl, &dict, "foofoofoo");
AssertExpandIs(tpl, &dict, "foofoofoo", true);
TemplateDictionary dict2("dict2");
dict2.SetValue("FOO", "foo");
TemplateDictionary* sec = dict2.AddSectionDictionary("SEC");
AssertExpandIs(tpl, &dict2, "foofoofoo");
AssertExpandIs(tpl, &dict2, "foofoofoo", true);
sec->SetValue("FOO", "bar");
AssertExpandIs(tpl, &dict2, "foobarbar");
AssertExpandIs(tpl, &dict2, "foobarbar", true);
TemplateDictionary* sec2 = sec->AddSectionDictionary("SEC");
AssertExpandIs(tpl, &dict2, "foobarbar");
AssertExpandIs(tpl, &dict2, "foobarbar", true);
sec2->SetValue("FOO", "baz");
AssertExpandIs(tpl, &dict2, "foobarbaz");
AssertExpandIs(tpl, &dict2, "foobarbaz", true);
// Now test an include template, which shouldn't inherit from its parents
tpl = StringToTemplate("{{FOO}}{{#SEC}}hi{{/SEC}}\n{{>INC}}",
@ -353,7 +399,7 @@ class TemplateUnittest {
incdict.ShowSection("SEC");
incdict.SetValue("FOO", "foo");
incdict.AddIncludeDictionary("INC")->SetFilename(incname);
AssertExpandIs(tpl, &incdict, "foohiinclude file");
AssertExpandIs(tpl, &incdict, "foohiinclude file", true);
}
// Tests that we append to the output string, rather than overwrite
@ -361,11 +407,11 @@ class TemplateUnittest {
Template* tpl = StringToTemplate("hi", STRIP_WHITESPACE);
TemplateDictionary dict("test_expand");
string output("premade");
tpl->Expand(&output, &dict);
ASSERT(tpl->Expand(&output, &dict));
ASSERT_STREQ(output.c_str(), "premadehi");
tpl = StringToTemplate(" lo ", STRIP_WHITESPACE);
tpl->Expand(&output, &dict);
ASSERT(tpl->Expand(&output, &dict));
ASSERT_STREQ(output.c_str(), "premadehilo");
}
@ -397,7 +443,7 @@ class TemplateUnittest {
"\nhi {{#SEC=SEC}}lo{{/SEC}} bar{{/SEC}}{{/FILE}}",
FLAGS_test_tmpdir.c_str(), FLAGS_test_tmpdir.c_str(),
FLAGS_test_tmpdir.c_str());
AssertExpandIs(tpl, &dict, expected);
AssertExpandIs(tpl, &dict, expected, true);
dict.SetAnnotateOutput("/template.");
AssertExpandIs(tpl, &dict,
@ -407,10 +453,11 @@ class TemplateUnittest {
"{{/SEC}}{{/FILE}}{{/INC}}"
"{{#INC=INC}}{{#FILE=/template.002}}"
"{{#SEC=__MAIN__}}include #2\n{{/SEC}}{{/FILE}}{{/INC}}"
"\nhi {{#SEC=SEC}}lo{{/SEC}} bar{{/SEC}}{{/FILE}}");
"\nhi {{#SEC=SEC}}lo{{/SEC}} bar{{/SEC}}{{/FILE}}", true);
dict.SetAnnotateOutput(NULL); // should turn off annotations
AssertExpandIs(tpl, &dict, "boo!\ninclude file\ninclude #2\n\nhi lo bar");
AssertExpandIs(tpl, &dict, "boo!\ninclude file\ninclude #2\n\nhi lo bar",
true);
}
static void TestGetTemplate() {
@ -466,9 +513,9 @@ class TemplateUnittest {
Template* tpl1 = StringToTemplate(tests[i][0], DO_NOT_STRIP);
Template* tpl2 = StringToTemplate(tests[i][0], STRIP_BLANK_LINES);
Template* tpl3 = StringToTemplate(tests[i][0], STRIP_WHITESPACE);
AssertExpandIs(tpl1, &dict, tests[i][1]);
AssertExpandIs(tpl2, &dict, tests[i][2]);
AssertExpandIs(tpl3, &dict, tests[i][3]);
AssertExpandIs(tpl1, &dict, tests[i][1], true);
AssertExpandIs(tpl2, &dict, tests[i][2], true);
AssertExpandIs(tpl3, &dict, tests[i][3], true);
}
}
@ -491,7 +538,7 @@ class TemplateUnittest {
sleep(1);
ASSERT(tpl->ReloadIfChanged()); // true: change, even if not contentful
tpl = Template::GetTemplate(filename, STRIP_WHITESPACE); // needed
AssertExpandIs(tpl, &dict, "{valid template}");
AssertExpandIs(tpl, &dict, "{valid template}", true);
StringToFile("exists now!", nonexistent);
tpl2 = Template::GetTemplate(nonexistent, STRIP_WHITESPACE);
@ -507,21 +554,21 @@ class TemplateUnittest {
unlink(nonexistent.c_str()); // here today...
sleep(1);
ASSERT(!tpl2->ReloadIfChanged()); // false: file has disappeared
AssertExpandIs(tpl2, &dict, "exists now!"); // last non-error value
AssertExpandIs(tpl2, &dict, "exists now!", true); // last non-error value
StringToFile("lazarus", nonexistent);
sleep(1);
ASSERT(tpl2->ReloadIfChanged()); // true: file exists again
tpl2 = Template::GetTemplate(nonexistent, STRIP_WHITESPACE);
AssertExpandIs(tpl2, &dict, "lazarus");
AssertExpandIs(tpl2, &dict, "lazarus", true);
StringToFile("{new template}", filename);
tpl = Template::GetTemplate(filename, STRIP_WHITESPACE); // needed
AssertExpandIs(tpl, &dict, "{valid template}"); // haven't reloaded
AssertExpandIs(tpl, &dict, "{valid template}", true); // haven't reloaded
sleep(1);
ASSERT(tpl->ReloadIfChanged()); // true: change, even if not contentful
tpl = Template::GetTemplate(filename, STRIP_WHITESPACE); // needed
AssertExpandIs(tpl, &dict, "{new template}");
AssertExpandIs(tpl, &dict, "{new template}", true);
// Now change both tpl and tpl2
StringToFile("{all-changed}", filename);
@ -529,8 +576,8 @@ class TemplateUnittest {
Template::ReloadAllIfChanged();
tpl = Template::GetTemplate(filename, STRIP_WHITESPACE); // needed
tpl2 = Template::GetTemplate(nonexistent, STRIP_WHITESPACE);
AssertExpandIs(tpl, &dict, "{all-changed}");
AssertExpandIs(tpl2, &dict, "lazarus2");
AssertExpandIs(tpl, &dict, "{all-changed}", true);
AssertExpandIs(tpl2, &dict, "lazarus2", true);
}
static void TestTemplateRootDirectory() {
@ -673,7 +720,7 @@ class TemplateUnittest {
ASSERT(badsyntax.size() == 2); // we did not refresh the bad syntax list
badsyntax = TemplateNamelist::GetBadSyntaxList(true, DO_NOT_STRIP);
// After refresh, the file we just registerd also added in bad syntax list
ASSERT(badsyntax.size() == 3); //
ASSERT(badsyntax.size() == 3);
TemplateNamelist::RegisterTemplate("A_non_existant_file.tpl");
names = TemplateNamelist::GetList();

View File

@ -1 +1 @@
<html><body>monday &amp; tuesdaymonday &amp;amp; tuesdaymonday &amp; tuesday<IMG src=foo.jpg align="right"><IMG src="mouseover() {img=\'foo.jpg\' align=\"right\"}">&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;/body&gt;&lt;/html&gt; </body></html>
<html><body>monday &amp; tuesdaymonday &amp;amp; tuesdaymonday \x26amp; tuesday<IMG src=foo.jpg align="right"><IMG src="mouseover() {img=\'foo.jpg\' align=\"right\"}">&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;/body&gt;&lt;/html&gt; </body></html>