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:
parent
22ab1314c3
commit
4a61bf4e95
10
ChangeLog
10
ChangeLog
|
@ -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)
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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
20
configure
vendored
|
@ -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'`\\"
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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}}<blockquote>{{/SUBITEM_SECTION}}
|
||||
<p><a href={{JUMP_TO_URL:html_escape}} target=nw>{{LEAD_LINE}}</a><font size=-1>
|
||||
{{! LEAD_LINE is received HTML-escaped from the backend.}}
|
||||
<p><a href="{{JUMP_TO_URL:html_escape}}" target=nw>{{LEAD_LINE}}</a><font size=-1>
|
||||
|
||||
{{! SNIPPET1, SNIPPET2 are HTML-escaped in the snippet generator.}}
|
||||
{{#SNIPPET1_SECTION}}
|
||||
<br>{{SNIPPET1}}
|
||||
{{/SNIPPET1_SECTION}}
|
||||
|
@ -59,30 +61,31 @@ search results page:</p>
|
|||
{{/SNIPPET2_SECTION}}
|
||||
|
||||
{{#DESCRIPTION_SECTION}}
|
||||
{{! DESC is received HTML-escaped from the backend.}}
|
||||
<br><span class=f>Description:</span> {{DESC}}
|
||||
{{/DESCRIPTION_SECTION}}
|
||||
|
||||
{{#CATEGORY_SECTION}}
|
||||
<br><span class=f>Category:</span> <a href={{CAT_URL:html_escape}} class=f>
|
||||
{{CATEGORY}}</a>
|
||||
<br><span class=f>Category:</span> <a href="{{CAT_URL:html_escape}}" class=f>
|
||||
{{CATEGORY:html_escape}}</a>
|
||||
{{/CATEGORY_SECTION}}
|
||||
|
||||
{{#LASTLINE_SECTION}}
|
||||
<br><font color={{ALT_TEXT_COLOR}}>{{URL}}
|
||||
{{#KS_SECTION}}} - {{KSIZE}}{{/KS_SECTION}}}
|
||||
{{#CACHE_SECTION}}} - <a href={{CACHE_URL:html_escape}} class=f>Cached</A>
|
||||
<br><font color="{{ALT_TEXT_COLOR:h}}">{{URL:h}}
|
||||
{{#KS_SECTION}}} - {{KSIZE:h}}{{/KS_SECTION}}}
|
||||
{{#CACHE_SECTION}}} - <a href="{{CACHE_URL:h}}" class=f>Cached</A>
|
||||
{{/CACHE_SECTION}}}
|
||||
{{#SIM_SECTION}}} - <a href={{SIM_PAGES_URL:html_escape}} class=f>Similar pages</A>
|
||||
{{#SIM_SECTION}}} - <a href="{{SIM_PAGES_URL:h}}" class=f>Similar pages</A>
|
||||
{{/SIM_SECTION}}}
|
||||
|
||||
{{#STOCK_SECTION}}
|
||||
- <a href={{STOCK_URL:html_escape}} class=f>Stock quotes: {{STOCK_SYMBOL}}</a>
|
||||
- <a href="{{STOCK_URL:h}}" class=f>Stock quotes: {{STOCK_SYMBOL:h}}</a>
|
||||
{{/STOCK_SECTION}}
|
||||
</font>
|
||||
{{/LASTLINE_SECTION}}
|
||||
|
||||
{{#MORE_SECTION}}
|
||||
<br>[ <a href={{MORE_URL:html_escape}} class=f>More results from {{MORE_LABEL}}</a> ]
|
||||
<br>[ <a href="{{MORE_URL:h}}" class=f>More results from {{MORE_LABEL:h}}</a> ]
|
||||
{{/MORE_SECTION}}
|
||||
|
||||
</font><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 &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 <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()) &&
|
||||
IsEmpty(qr->GetPageSize()) &&
|
||||
IsEmpty(qr->GetCachedUrl()) &&
|
||||
IsEmpty(qr->GetSimilarPagesUrl()) &&
|
||||
(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(&dict, query);
|
||||
tpl->Expand(&output, &dict);
|
||||
// output now holds the expanded template
|
||||
}
|
||||
|
||||
|
|
187
doc/howto.html
187
doc/howto.html
|
@ -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>
|
||||
<html><head><title>{{TITLE}}</title>{{META_TAGS}}</head>
|
||||
<body>{{BODY}}</body></html>
|
||||
<body>{{BODY}}</body></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>&</code> -> <code>&amp</code>)</td>
|
||||
(eg <code>&</code> -> <code>&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 <pre>...</pre>)</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><a href="http://google.com/search?q={{QUERY:u}}">{{QUERY:h}}</a></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:<ol>
|
||||
{{#PREV_SEARCHES}
|
||||
<li> {{PREV_SEARCH}}
|
||||
{{/PREV_SEARCH}}
|
||||
{{/PREV_SEARCHES}}
|
||||
</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& 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, &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, &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(<filename>, google::STRIP_WHITESPACE);
|
||||
google::TemplateDictionary dict("debug-name");
|
||||
FillDictionary(&dict, ...);
|
||||
FillDictionary(&dict, ...);
|
||||
string output;
|
||||
<font color=red>tpl->Expand(&output, &dict);</font>
|
||||
bool error_free = <font color=red>tpl->Expand(&output, &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><script></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>
|
||||
<h1>{{HEADING:h}}</h1>
|
||||
</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>
|
||||
<form ...
|
||||
<input name=q value="{{QUERY:h}}">
|
||||
</form>
|
||||
</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>
|
||||
<img src="{{IMAGE_URL:h}}">
|
||||
</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>
|
||||
<script>
|
||||
// ...
|
||||
var msg_text = '{{MESSAGE:j}}';
|
||||
// ...
|
||||
</script>
|
||||
</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>
|
||||
<script>
|
||||
// ...
|
||||
var num_items = {{NUM_ITEMS}};
|
||||
// ...
|
||||
</script>
|
||||
</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>
|
||||
<button ...
|
||||
onclick='GotoUrl("{{TARGET_URL:j:h}}");'>
|
||||
</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 <template filename></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>
|
||||
|
|
|
@ -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 }}
|
||||
<br>{{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>
|
||||
<table border=0 {{BI_SPACE}}
|
||||
align=center></pre></p>
|
||||
align=center></pre>
|
||||
|
||||
<p>Correct:<pre>
|
||||
<p>Correct:</p><pre>
|
||||
<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
73
doc/xss_resources.html
Normal 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>
|
||||
<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>
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -198,10 +198,15 @@ class TemplateDictionary {
|
|||
// --- ESCAPE FUNCTORS
|
||||
// Some commonly-used escape functors.
|
||||
|
||||
// Escapes < > " & <non-space whitespace> to < > " & <space>
|
||||
// Escapes < > " ' & <non-space whitespace> to < > "
|
||||
// ' & <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 to  
|
||||
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;
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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_
|
||||
|
|
|
@ -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 < > " & <space>
|
||||
// Escapes < > " ' & <non-space whitespace> to < > " '
|
||||
// & <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 += "&"; break;
|
||||
case '"': out += """; break;
|
||||
case '\'': out += "'"; break;
|
||||
case '<': out += "<"; break;
|
||||
case '>': out += ">"; 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 < > " ' &
|
||||
// (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 += "&"; break;
|
||||
case '"': out += """; break;
|
||||
case '\'': out += "'"; break;
|
||||
case '<': out += "<"; break;
|
||||
case '>': out += ">"; break;
|
||||
// All other whitespace we leave alone!
|
||||
default: out += in[i];
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Escapes to  
|
||||
// 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;
|
||||
|
|
|
@ -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 & bar");
|
||||
ASSERT_STREQ(dict.GetSectionValue("hardest HTML"),
|
||||
"<A HREF='foo' id="bar && baz">");
|
||||
"<A HREF='foo' id="bar && "
|
||||
"baz">");
|
||||
ASSERT_STREQ(dict.GetSectionValue("easy PRE"), "foo");
|
||||
ASSERT_STREQ(dict.GetSectionValue("harder PRE"), "foo & bar");
|
||||
ASSERT_STREQ(dict.GetSectionValue("hardest PRE"),
|
||||
" "--\v--\f--\n--\t--&--<-->--'--"");
|
||||
ASSERT_STREQ(dict.GetSectionValue("easy XML"), "xoo");
|
||||
ASSERT_STREQ(dict.GetSectionValue("harder XML"), "xoo & xar");
|
||||
ASSERT_STREQ(dict.GetSectionValue("hardest XML"),
|
||||
"xoo   xar xaz  ");
|
||||
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"),
|
||||
"<A HREF=\\'foo\\'>\\n");
|
||||
"\\x3cA HREF\\x3d\\'foo\\'\\x3e\\n");
|
||||
ASSERT_STREQ(dict.GetSectionValue("hardest double"),
|
||||
"print \\"<A HREF=\\'foo\\'>\\";\\r\\n\\\\1;");
|
||||
"print \\"\\x3cA HREF\\x3d\\'foo\\'\\x3e\\";"
|
||||
"\\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 is&nb%s -- ok?", "sp; #1 ");
|
||||
|
||||
ASSERT_STREQ(dict.GetSectionValue("HTML"),
|
||||
"This is <a & b> #0.3333");
|
||||
ASSERT_STREQ(dict.GetSectionValue("PRE"),
|
||||
"if (a < 1 && b > 2)\n\t x = 0.3333;");
|
||||
ASSERT_STREQ(dict.GetSectionValue("URL"), "pageviews-r%3Fegex");
|
||||
|
||||
ASSERT_STREQ(dict.GetSectionValue("XML"),
|
||||
"This is  #1  -- ok?");
|
||||
}
|
||||
|
|
|
@ -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&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&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&yo lo");
|
||||
AssertExpandIs(tpl, &dict, "hi yo&yo lo", true);
|
||||
|
||||
tpl = StringToTemplate("hi {{VAR:h:h}} lo", STRIP_WHITESPACE);
|
||||
AssertExpandIs(tpl, &dict, "hi yo&amp;yo lo");
|
||||
AssertExpandIs(tpl, &dict, "hi yo&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 & print file bar\n");
|
||||
AssertExpandIs(tpl1, &dict, "hi include & print file bar\n", true);
|
||||
dict.AddIncludeDictionary("INC")->SetFilename(incname2);
|
||||
AssertExpandIs(tpl1, &dict, "hi include & print file inc2 bar\n");
|
||||
AssertExpandIs(tpl2, &dict, "hi include & print file\\ninc2\\n bar\n");
|
||||
AssertExpandIs(tpl1, &dict, "hi include & print file inc2 bar\n",
|
||||
true);
|
||||
AssertExpandIs(tpl2, &dict, "hi include \\x26 print file\\ninc2\\n bar\n",
|
||||
true);
|
||||
AssertExpandIs(tpl3, &dict, "hi include & 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();
|
||||
|
|
|
@ -1 +1 @@
|
|||
<html><body>monday & tuesdaymonday &amp; tuesdaymonday & tuesday<IMG src=foo.jpg align="right"><IMG src="mouseover() {img=\'foo.jpg\' align=\"right\"}"><html><head></head><body></body></html> </body></html>
|
||||
<html><body>monday & tuesdaymonday &amp; tuesdaymonday \x26amp; tuesday<IMG src=foo.jpg align="right"><IMG src="mouseover() {img=\'foo.jpg\' align=\"right\"}"><html><head></head><body></body></html> </body></html>
|
||||
|
|
Loading…
Reference in New Issue
Block a user