From 4a61bf4e95713c9d0b54a5e65546b050cce870f4 Mon Sep 17 00:00:00 2001 From: csilvers Date: Wed, 21 Mar 2007 23:22:48 +0000 Subject: [PATCH] ctemplate 0.4 --- ChangeLog | 10 + Makefile.am | 3 +- Makefile.in | 3 +- configure | 20 +- configure.ac | 2 +- doc/example.html | 41 ++-- doc/howto.html | 187 ++++++++++++++++-- doc/tips.html | 71 +++++-- doc/xss_resources.html | 73 +++++++ packages/rpm/rpm.spec | 2 +- src/google/template.h.in | 7 +- src/google/template_dictionary.h.in | 12 +- src/google/template_namelist.h.in | 3 +- src/template.cc | 83 +++++--- src/template_dictionary.cc | 61 +++++- src/tests/template_dictionary_unittest.cc | 76 ++++++- src/tests/template_unittest.cc | 177 +++++++++++------ ...emplate_unittest_test_modifiers_dict01.out | 2 +- 18 files changed, 658 insertions(+), 175 deletions(-) create mode 100644 doc/xss_resources.html diff --git a/ChangeLog b/ChangeLog index d42dad4..93daca6 100644 --- a/ChangeLog +++ b/ChangeLog @@ -23,3 +23,13 @@ Mon Aug 21 17:44:32 2006 Google Inc. * 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. + + * 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) diff --git a/Makefile.am b/Makefile.am index 38ddbaf..3f774c3 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 = diff --git a/Makefile.in b/Makefile.in index 610e972..89769cd 100644 --- a/Makefile.in +++ b/Makefile.in @@ -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 diff --git a/configure b/configure index 569bcea..cd3d598 100755 --- a/configure +++ b/configure @@ -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 . # @@ -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'`\\" diff --git a/configure.ac b/configure.ac index 53b94ad..7e6f37a 100644 --- a/configure.ac +++ b/configure.ac @@ -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) diff --git a/doc/example.html b/doc/example.html index 8685612..a59bca0 100644 --- a/doc/example.html +++ b/doc/example.html @@ -7,7 +7,7 @@ - + + + +

Cross-Site Scripting Resources

+
Status: Current   +(as of 17 August 2006)
+
+ +

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. + +

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. + +

The following online resources provide further information on XSS +vulnerabilities and how to avoid them: + +

+ + diff --git a/packages/rpm/rpm.spec b/packages/rpm/rpm.spec index b1a4046..7fa4c85 100644 --- a/packages/rpm/rpm.spec +++ b/packages/rpm/rpm.spec @@ -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 diff --git a/src/google/template.h.in b/src/google/template.h.in index 08c6a4c..703a6b7 100644 --- a/src/google/template.h.in +++ b/src/google/template.h.in @@ -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; diff --git a/src/google/template_dictionary.h.in b/src/google/template_dictionary.h.in index fdc567d..51cc959 100644 --- a/src/google/template_dictionary.h.in +++ b/src/google/template_dictionary.h.in @@ -198,10 +198,15 @@ class TemplateDictionary { // --- ESCAPE FUNCTORS // Some commonly-used escape functors. - // Escapes < > " & to < > " & + // Escapes < > " ' & to < > " + // ' & struct HtmlEscape { std::string operator()(const std::string&) const; }; static HtmlEscape html_escape; + // Same as HtmlEscape but leaves all whitespace alone. Eg. for
..
+ 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 " \ / to \" \\ \/ \f \r \n \b \t struct JsonEscape { std::string operator()(const std::string&) const; }; static JsonEscape json_escape; diff --git a/src/google/template_namelist.h.in b/src/google/template_namelist.h.in index 45489f3..ee62c30 100644 --- a/src/google/template_namelist.h.in +++ b/src/google/template_namelist.h.in @@ -74,10 +74,11 @@ // exist and are syntactically correct. class TemplateNamelist { + friend class TemporaryRegisterTemplate; private: // Standard hash libs don't define hash, but do define hash struct TemplateHasher { - bool operator()(const std::string& s) const { + size_t operator()(const std::string& s) const { return @ac_cv_cxx_hash_namespace@::hash()(s.c_str()); } }; diff --git a/src/template.cc b/src/template.cc index 8ed1762..d96fb8a 100644 --- a/src/template.cc +++ b/src/template.cc @@ -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 *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_ diff --git a/src/template_dictionary.cc b/src/template_dictionary.cc index 9c40762..4326dba 100644 --- a/src/template_dictionary.cc +++ b/src/template_dictionary.cc @@ -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 < > " & to < > " & +// Escapes < > " ' & to < > " ' +// & 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 " / \ to \" \/ \\ \b \f \r \n \t string TemplateDictionary::JsonEscape::operator()(const string& in) const { string out; diff --git a/src/tests/template_dictionary_unittest.cc b/src/tests/template_dictionary_unittest.cc index b5b2509..8941331 100644 --- a/src/tests/template_dictionary_unittest.cc +++ b/src/tests/template_dictionary_unittest.cc @@ -194,6 +194,13 @@ class TemplateDictionaryUnittest { dict.SetEscapedValue("hardest HTML", "", 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", + "//-->", + 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?"); } diff --git a/src/tests/template_unittest.cc b/src/tests/template_unittest.cc index d89b000..985d643 100644 --- a/src/tests/template_unittest.cc +++ b/src/tests/template_unittest.cc @@ -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("", + STRIP_WHITESPACE); + AssertExpandIs(tpl, &dict, "", true); + tpl = StringToTemplate("", + STRIP_WHITESPACE); + AssertExpandIs(tpl, &dict, "", true); + + // Test with multiple URL escaping. + tpl = StringToTemplate("", + STRIP_WHITESPACE); + AssertExpandIs(tpl, &dict, "", 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 + string incname3 = StringToTemplateFile("yo&yo"); + // Note this also tests that html-escape, but not javascript-escape or + // pre-escape, escapes \n to 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(); diff --git a/src/tests/template_unittest_test_modifiers_dict01.out b/src/tests/template_unittest_test_modifiers_dict01.out index 644e24e..4bae76d 100644 --- a/src/tests/template_unittest_test_modifiers_dict01.out +++ b/src/tests/template_unittest_test_modifiers_dict01.out @@ -1 +1 @@ -monday & tuesdaymonday &amp; tuesdaymonday & tuesday<html><head></head><body></body></html> +monday & tuesdaymonday &amp; tuesdaymonday \x26amp; tuesday<html><head></head><body></body></html>