// Copyright (c) 2006, Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // --- // Author: Craig Silverstein #include "config_for_unittests.h" #include #include // for assert() #if defined(HAVE_PTHREAD) && !defined(NO_THREADS) #include // for pthread_t, pthread_create(), etc #endif #include // for size_t #include // for printf(), FILE, snprintf(), fclose(), etc #include // for exit() #include // for strcmp(), memchr(), strlen(), strstr() #include // for mode_t #include // for time_t, time() #ifdef HAVE_UNISTD_H #include // for link(), unlink() #endif #include // for list<>::size_type #include // for vector<> #include // for PerExpandData #include // for TextTemplateAnnotator #include // for TemplateDictionary #include // for ExpandEmitter #include // for STRIP_WHITESPACE, Strip, etc #include // for AddModifier(), HtmlEscape, etc #include // for TemplateNamelist, etc #include // for PathJoin(), IsAbspath(), etc #include // for TemplateString, StringHash, etc #include "tests/template_test_util.h" // for StringToTemplate(), etc using std::vector; using std::string; using GOOGLE_NAMESPACE::ExpandEmitter; using GOOGLE_NAMESPACE::PerExpandData; using GOOGLE_NAMESPACE::Template; using GOOGLE_NAMESPACE::TemplateDictionary; using GOOGLE_NAMESPACE::TemplateString; using GOOGLE_NAMESPACE::StaticTemplateString; using GOOGLE_NAMESPACE::TemplateNamelist; using GOOGLE_NAMESPACE::TemplateContext; using GOOGLE_NAMESPACE::Strip; using GOOGLE_NAMESPACE::DO_NOT_STRIP; using GOOGLE_NAMESPACE::STRIP_BLANK_LINES; using GOOGLE_NAMESPACE::STRIP_WHITESPACE; using GOOGLE_NAMESPACE::TC_HTML; using GOOGLE_NAMESPACE::TC_CSS; using GOOGLE_NAMESPACE::TC_JS; using GOOGLE_NAMESPACE::TC_JSON; using GOOGLE_NAMESPACE::TC_XML; using GOOGLE_NAMESPACE::TC_MANUAL; using GOOGLE_NAMESPACE::TC_UNUSED; using GOOGLE_NAMESPACE::FLAGS_test_tmpdir; using GOOGLE_NAMESPACE::PathJoin; using GOOGLE_NAMESPACE::IsAbspath; using GOOGLE_NAMESPACE::kRootdir; using GOOGLE_NAMESPACE::Now; using GOOGLE_NAMESPACE::CreateOrCleanTestDir; using GOOGLE_NAMESPACE::CreateOrCleanTestDirAndSetAsTmpdir; using GOOGLE_NAMESPACE::StringToFile; using GOOGLE_NAMESPACE::StringToTemplateFile; using GOOGLE_NAMESPACE::StringToTemplate; using GOOGLE_NAMESPACE::AssertExpandWithDataIs; using GOOGLE_NAMESPACE::AssertExpandIs; using GOOGLE_NAMESPACE::ExpandTemplate; using GOOGLE_NAMESPACE::ExpandWithData; using GOOGLE_NAMESPACE::StringToTemplateCache; static const StaticTemplateString kHello = STS_INIT(kHello, "Hello"); static const StaticTemplateString kWorld = STS_INIT(kWorld, "World"); static const char* kPragmaHtml = "{{%AUTOESCAPE context=\"HTML\"}}\n"; static const char* kPragmaJs = "{{%AUTOESCAPE context=\"JAVASCRIPT\"}}\n"; static const char* kPragmaCss = "{{%AUTOESCAPE context=\"CSS\"}}\n"; static const char* kPragmaXml = "{{%AUTOESCAPE context=\"XML\"}}\n"; static const char* kPragmaJson = "{{%AUTOESCAPE context=\"JSON\"}}\n"; // How many threads to use for our threading test. // This is a #define instead of a const int so we can use it in array-sizes // even on c++ compilers that don't support var-length arrays. #define kNumThreads 10 #define PFATAL(s) do { perror(s); exit(1); } while (0) #define ASSERT(cond) do { \ if (!(cond)) { \ printf("ASSERT FAILED, line %d: %s\n", __LINE__, #cond); \ assert(cond); \ exit(1); \ } \ } while (0) #define ASSERT_STREQ_EXCEPT(a, b, except) ASSERT(StreqExcept(a, b, except)) #define ASSERT_STREQ(a, b) ASSERT(strcmp(a, b) == 0) #define ASSERT_NOT_STREQ(a, b) ASSERT(strcmp(a, b) != 0) #define ASSERT_STREQ_VERBOSE(a, b, c) ASSERT(StrEqVerbose(a, b, c)) #define ASSERT_INTEQ(a, b) ASSERT(IntEqVerbose(a, b)) namespace { // First, (conceptually) remove all chars in "except" from both a and b. // Then return true iff munged_a == munged_b. bool StreqExcept(const char* a, const char* b, const char* except) { const char* pa = a, *pb = b; const size_t exceptlen = strlen(except); while (1) { // Use memchr isntead of strchr because memchr(foo, '\0') always fails while (memchr(except, *pa, exceptlen)) pa++; // ignore "except" chars in a while (memchr(except, *pb, exceptlen)) pb++; // ignore "except" chars in b if ((*pa == '\0') && (*pb == '\0')) return true; if (*pa++ != *pb++) // includes case where one is at \0 return false; } } // If a and b do not match, print their values and that of text // and return false. bool StrEqVerbose(const string& a, const string& b, const string& text) { if (a != b) { printf("EXPECTED: %s\n", a.c_str()); printf("ACTUAL: %s\n", b.c_str()); printf("TEXT: %s\n", text.c_str()); return false; } return true; } bool IntEqVerbose(int a, int b) { if (a != b) { printf("EXPECTED: %d\n", a); printf("ACTUAL: %d\n", b); return false; } return true; } // This test emitter writes to a string, but writes X's of the right // length, rather than the actual content passed in. class SizeofEmitter : public ExpandEmitter { string* const outbuf_; public: SizeofEmitter(string* outbuf) : outbuf_(outbuf) {} virtual void Emit(char c) { Emit(&c, 1); } virtual void Emit(const string& s) { Emit(s.data(), s.length()); } virtual void Emit(const char* s) { Emit(s, strlen(s)); } virtual void Emit(const char*, size_t slen) { outbuf_->append(slen, 'X'); } }; } // unnamed namespace RegisterTemplateFilename(VALID1_FN, "template_unittest_test_valid1.in"); RegisterTemplateFilename(INVALID1_FN, "template_unittest_test_invalid1.in"); RegisterTemplateFilename(INVALID2_FN, "template_unittest_test_invalid2.in"); RegisterTemplateFilename(NONEXISTENT_FN, "nonexistent__file.tpl"); // Returns the proper AUTOESCAPE pragma that corresponds to the // given TemplateContext. static string GetPragmaForContext(TemplateContext context) { switch(context) { case TC_HTML: return kPragmaHtml; case TC_JS: return kPragmaJs; case TC_CSS: return kPragmaCss; case TC_JSON: return kPragmaJson; case TC_XML: return kPragmaXml; case TC_MANUAL: return ""; // No AUTOESCAPE pragma. case TC_UNUSED: ASSERT(false); // Developer error, this TC is not to be used. } ASSERT(false); // Developer error - invalid TemplateContext. return ""; } // This writes s to a file with the AUTOESCAPE pragma corresponding // to the given TemplateContext and then loads it into a template object. static Template* StringToTemplateWithAutoEscaping(const string& s, Strip strip, TemplateContext context) { string text = GetPragmaForContext(context) + s; return Template::GetTemplate(StringToTemplateFile(text), strip); } // A helper method used by TestCorrectModifiersForAutoEscape. // Populates out with lines of the form: // VARNAME:mod1[=val1][:mod2[=val2]]...\n from the dump of the template // and compares against the expected string. static void AssertCorrectModifiersInTemplate(Template* tpl, const string& text, const string& expected_out) { ASSERT(tpl); string dump_out, out; tpl->DumpToString("bogus_filename", &dump_out); string::size_type i, j; i = 0; while ((i = dump_out.find("Variable Node: ", i)) != string::npos) { i += strlen("Variable Node: "); j = dump_out.find("\n", i); out.append(dump_out.substr(i, j - i)); // should be safe. out.append("\n"); } ASSERT_STREQ_VERBOSE(expected_out, out, text); } // Wrapper on top of AssertCorrectModifiersInTemplate which first // obtains a template from the given contents and template context. static void AssertCorrectModifiers(TemplateContext template_type, const string& text, const string& expected_out) { Strip strip = STRIP_WHITESPACE; Template *tpl = StringToTemplateWithAutoEscaping(text, strip, template_type); AssertCorrectModifiersInTemplate(tpl, text, expected_out); } // A helper method used by TestCorrectModifiersForAutoEscape. // Initializes the template in the Auto Escape mode with the // given TemplateContext, expands it with the given dictionary // and checks that the output matches the expected value. static void AssertCorrectEscaping(TemplateContext template_type, const TemplateDictionary& dict, const string& text, const string& expected_out) { Strip strip = STRIP_WHITESPACE; Template *tpl = StringToTemplateWithAutoEscaping(text, strip, template_type); string outstring; tpl->Expand(&outstring, &dict); ASSERT_STREQ_VERBOSE(expected_out, outstring, text); } class DynamicModifier : public GOOGLE_NAMESPACE::TemplateModifier { public: void Modify(const char* in, size_t inlen, const PerExpandData* per_expand_data, ExpandEmitter* outbuf, const string& arg) const { assert(arg.empty()); // we don't take an argument assert(per_expand_data); const char* value = per_expand_data->LookupForModifiersAsString("value"); if (value) outbuf->Emit(value); } }; class EmphasizeTemplateModifier : public GOOGLE_NAMESPACE::TemplateModifier { public: EmphasizeTemplateModifier(const string& match) : match_(match) { } bool MightModify(const PerExpandData* per_expand_data, const string& arg) const { return strstr(arg.c_str(), match_.c_str()); } void Modify(const char* in, size_t inlen, const PerExpandData* per_expand_data, ExpandEmitter* outbuf, const string& arg) const { outbuf->Emit(">>"); outbuf->Emit(in, inlen); outbuf->Emit("<<"); } private: string match_; }; class TemplateForTest : public Template { public: using Template::kSafeWhitelistedVariables; using Template::kNumSafeWhitelistedVariables; private: // This quiets gcc3, which otherwise complains: "base `Template' // with only non-default constructor in class without a constructor". TemplateForTest(); }; class TemplateUnittest { public: static void CheckWhitelistedVariablesSorted() { // NOTE(williasr): kSafeWhitelistedVariables must be sorted, it's accessed // using binary search. for (size_t i = 1; i < TemplateForTest::kNumSafeWhitelistedVariables; i++) { assert(strcmp(TemplateForTest::kSafeWhitelistedVariables[i-1], TemplateForTest::kSafeWhitelistedVariables[i]) < 0); } } // The following tests test various aspects of how Expand() should behave. static void TestWeirdSyntax() { TemplateDictionary dict("dict"); // When we see {{{, we should match the second {{, not the first. Template* tpl1 = StringToTemplate("hi {{{! VAR {{!VAR} }} lo", STRIP_WHITESPACE); AssertExpandIs(tpl1, &dict, "hi { lo", true); // Likewise for }}} Template* tpl2 = StringToTemplate("fn(){{{BI_NEWLINE}} x=4;{{BI_NEWLINE}}}", DO_NOT_STRIP); AssertExpandIs(tpl2, &dict, "fn(){\n x=4;\n}", true); // Try lots of {'s! Template* tpl3 = StringToTemplate("{{{{{{VAR}}}}}}}}", DO_NOT_STRIP); AssertExpandIs(tpl3, &dict, "{{{{}}}}}}", true); } static void TestComment() { TemplateDictionary dict("dict"); Template* tpl1 = StringToTemplate("hi {{!VAR}} lo", STRIP_WHITESPACE); AssertExpandIs(tpl1, &dict, "hi lo", true); Template* tpl2 = StringToTemplate("hi {{!VAR {VAR} }} lo", STRIP_WHITESPACE); AssertExpandIs(tpl2, &dict, "hi lo", true); Template* tpl3 = StringToTemplate("hi {{! VAR {{!VAR} }} lo", STRIP_WHITESPACE); AssertExpandIs(tpl3, &dict, "hi lo", true); } static void TestSetMarkerDelimiters() { TemplateDictionary dict("dict"); dict.SetValue("VAR", "yo"); Template* tpl1 = StringToTemplate("{{=| |=}}\nhi |VAR| {{lo}}", STRIP_WHITESPACE); AssertExpandIs(tpl1, &dict, "hi yo {{lo}}", true); Template* tpl2 = StringToTemplate("{{=| |=}}hi |VAR| {{lo}}", STRIP_WHITESPACE); AssertExpandIs(tpl2, &dict, "hi yo {{lo}}", true); Template* tpl3 = StringToTemplate("{{=| ||=}}hi ||VAR|||VAR|| {{lo}}", STRIP_WHITESPACE); AssertExpandIs(tpl3, &dict, "hi |yoyo {{lo}}", true); Template* tpl4 = StringToTemplate("{{=< >=}}hi <> {{lo}}", STRIP_WHITESPACE); AssertExpandIs(tpl4, &dict, "hi {{lo}}", true); Template* tpl4b = StringToTemplate("{{=<< >>=}}hi <> {{lo}}", STRIP_WHITESPACE); AssertExpandIs(tpl4b, &dict, "hi yo {{lo}}", true); Template* tpl4c = StringToTemplate("{{=<< <<=}}hi <=}}\n" "hi {{VAR}} lo\n" "hi lo\n<={ }=>\n" "hi {{VAR}} lo\n{={{ }}=}\n" "hi {{VAR}} lo\n", STRIP_WHITESPACE); AssertExpandIs(tpl5, &dict, "hi yo lohi {{VAR}} lohi yo lohi {yo} lohi yo lo", true); Template* tpl6 = StringToTemplate("hi {{VAR}} lo\n{{=< >}}\n", STRIP_WHITESPACE); ASSERT(tpl6 == NULL); Template* tpl7 = StringToTemplate("hi {{VAR}} lo\n{{=<>}}\n", STRIP_WHITESPACE); ASSERT(tpl7 == NULL); Template* tpl8 = StringToTemplate("hi {{VAR}} lo\n{{=< >=}}\n", STRIP_WHITESPACE); ASSERT(tpl8 == NULL); Template* tpl9 = StringToTemplate("hi {{VAR}} lo\n{{==}}\n", STRIP_WHITESPACE); ASSERT(tpl9 == NULL); Template* tpl10 = StringToTemplate("hi {{VAR}} lo\n{{=}}\n", STRIP_WHITESPACE); ASSERT(tpl10 == NULL); // Test that {{= =}} is a "removable" marker. Template* tpl11 = StringToTemplate("line\n {{=| |=}} \nhi |VAR| {{lo}}\n", STRIP_BLANK_LINES); AssertExpandIs(tpl11, &dict, "line\nhi yo {{lo}}\n", true); // Test that "removable" markers survive marker-modification. Template* tpl12 = StringToTemplate(" {{#SEC1}} \n" "{{=| |=}} |VAR|\n" " |/SEC1|\ntada! |VAR|\n" "hello|=<< >>=|\n" " <> \n" "done", STRIP_BLANK_LINES); AssertExpandIs(tpl12, &dict, "tada! yo\nhello\ndone", true); } static void TestVariable() { Template* tpl = StringToTemplate("hi {{VAR}} lo", STRIP_WHITESPACE); TemplateDictionary dict("dict"); AssertExpandIs(tpl, &dict, "hi lo", true); dict.SetValue("VAR", "yo"); AssertExpandIs(tpl, &dict, "hi yo lo", true); dict.SetValue("VAR", "yoyo"); 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", true); // Sanity check string template behaviour while we're at it. Template* tpl2 = Template::StringToTemplate("hi {{VAR}} lo", STRIP_WHITESPACE); TemplateDictionary dict2("dict"); AssertExpandIs(tpl2, &dict2, "hi lo", true); dict2.SetValue("VAR", "yo"); AssertExpandIs(tpl2, &dict2, "hi yo lo", true); dict2.SetValue("VAR", "yoyo"); AssertExpandIs(tpl2, &dict2, "hi yoyo lo", true); dict2.SetValue("VA", "noyo"); dict2.SetValue("VAR ", "noyo2"); dict2.SetValue("var", "noyo3"); AssertExpandIs(tpl2, &dict2, "hi yoyo lo", true); delete tpl2; // You have to delete StringToTemplate strings } static void TestVariableWithModifiers() { Template* tpl = StringToTemplate("hi {{VAR:html_escape}} lo", STRIP_WHITESPACE); TemplateDictionary dict("dict"); // 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", true); tpl = StringToTemplate("hi {{VAR:h:h}} lo", STRIP_WHITESPACE); AssertExpandIs(tpl, &dict, "hi yo&amp;yo lo", true); // Test special HTML escaping dict.SetValue("URL_VAR", "javascript:void"); dict.SetValue("SNIPPET_VAR", "foo & bar"); tpl = StringToTemplate("hi {{VAR:H=attribute}} {{URL_VAR:H=url}} " "{{SNIPPET_VAR:H=snippet}} lo", STRIP_WHITESPACE); AssertExpandIs(tpl, &dict, "hi yo_yo # foo & bar lo", true); // Test with custom modifiers [regular or XssSafe should not matter]. ASSERT(GOOGLE_NAMESPACE::AddModifier("x-test", &GOOGLE_NAMESPACE::html_escape)); ASSERT(GOOGLE_NAMESPACE::AddModifier("x-test-arg=", &GOOGLE_NAMESPACE::html_escape)); ASSERT(GOOGLE_NAMESPACE::AddXssSafeModifier("x-test-arg=snippet", &GOOGLE_NAMESPACE::snippet_escape)); tpl = StringToTemplate("hi {{VAR:x-test}} lo", STRIP_WHITESPACE); AssertExpandIs(tpl, &dict, "hi yo&yo lo", true); tpl = StringToTemplate("hi {{SNIPPET_VAR:x-test-arg=snippet}} lo", STRIP_WHITESPACE); AssertExpandIs(tpl, &dict, "hi foo & bar lo", true); tpl = StringToTemplate("hi {{VAR:x-unknown}} lo", STRIP_WHITESPACE); AssertExpandIs(tpl, &dict, "hi yo&yo lo", true); // Test with a modifier taking per-expand data DynamicModifier dynamic_modifier; ASSERT(GOOGLE_NAMESPACE::AddModifier("x-dynamic", &dynamic_modifier)); PerExpandData per_expand_data; tpl = StringToTemplate("hi {{VAR:x-dynamic}} lo", STRIP_WHITESPACE); AssertExpandWithDataIs(tpl, &dict, &per_expand_data, "hi lo", true); per_expand_data.InsertForModifiers("value", "foo"); AssertExpandWithDataIs(tpl, &dict, &per_expand_data, "hi foo lo", true); per_expand_data.InsertForModifiers("value", "bar"); AssertExpandWithDataIs(tpl, &dict, &per_expand_data, "hi bar lo", true); per_expand_data.InsertForModifiers("value", NULL); AssertExpandWithDataIs(tpl, &dict, &per_expand_data, "hi lo", true); // Test with no modifiers. tpl = StringToTemplate("hi {{VAR}} lo", STRIP_WHITESPACE); 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", 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", true); tpl = StringToTemplate("hi {{VAR:h:j}} lo", STRIP_WHITESPACE); AssertExpandIs(tpl, &dict, "hi yo yo lo", true); tpl = StringToTemplate("hi {{VAR:j:h}} lo", STRIP_WHITESPACE); 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", 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); ASSERT(tpl == NULL); tpl = StringToTemplate("hi {{VAR:html_ecap}} lo", STRIP_WHITESPACE); ASSERT(tpl == NULL); tpl = StringToTemplate("hi {{VAR:javascript_escaper}} lo", STRIP_WHITESPACE); ASSERT(tpl == NULL); tpl = StringToTemplate("hi {{VAR:js:j}} lo", STRIP_WHITESPACE); ASSERT(tpl == NULL); tpl = StringToTemplate("hi {{VAR:}} lo", STRIP_WHITESPACE); ASSERT(tpl == NULL); // Check we reject modifier-values when we ought to tpl = StringToTemplate("hi {{VAR:j=4}} lo", STRIP_WHITESPACE); 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); ASSERT(tpl == NULL); // Test when expanded grows by more than 12% per modifier. dict.SetValue("VAR", "http://a.com?b=c&d=e&f=g&q=a>b"); tpl = StringToTemplate("{{VAR:u:j:h}}", STRIP_WHITESPACE); AssertExpandIs(tpl, &dict, "http%3A//a.com%3Fb%3Dc%26d%3De%26f%3Dg%26q%3Da%3Eb", true); // As above with 4 modifiers. dict.SetValue("VAR", "http://a.com?b=c&d=e&f=g&q=a>b"); tpl = StringToTemplate("{{VAR:u:j:h:h}}", STRIP_WHITESPACE); AssertExpandIs(tpl, &dict, "http%3A//a.com%3Fb%3Dc%26d%3De%26f%3Dg%26q%3Da%3Eb", true); } static void TestSection() { Template* tpl = StringToTemplate( "boo!\nhi {{#SEC}}lo{{#SUBSEC}}jo{{/SUBSEC}}{{/SEC}} bar", STRIP_WHITESPACE); TemplateDictionary dict("dict"); AssertExpandIs(tpl, &dict, "boo!hi bar", true); dict.ShowSection("SEC"); AssertExpandIs(tpl, &dict, "boo!hi lo bar", true); dict.ShowSection("SEC"); 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", true); TemplateDictionary dict2("dict2"); dict2.AddSectionDictionary("SEC"); AssertExpandIs(tpl, &dict2, "boo!hi lo bar", true); dict2.AddSectionDictionary("SEC"); AssertExpandIs(tpl, &dict2, "boo!hi lolo bar", true); dict2.AddSectionDictionary("sec"); AssertExpandIs(tpl, &dict2, "boo!hi lolo bar", true); dict2.ShowSection("SUBSEC"); AssertExpandIs(tpl, &dict2, "boo!hi lojolojo bar", true); } static void TestSectionSeparator() { Template* tpl = StringToTemplate( "hi {{#SEC}}lo{{#SEC_separator}}jo{{JO}}{{/SEC_separator}}{{/SEC}} bar", STRIP_WHITESPACE); TemplateDictionary dict("dict"); AssertExpandIs(tpl, &dict, "hi bar", true); // Since SEC is only expanded once, the separator section shouldn't show. dict.ShowSection("SEC"); AssertExpandIs(tpl, &dict, "hi lo bar", true); dict.ShowSection("SEC"); AssertExpandIs(tpl, &dict, "hi lo bar", true); // This should work even though SEC_separator isn't a child of the // main dict. It verifies SEC_separator is just a normal section, too. dict.ShowSection("SEC_separator"); AssertExpandIs(tpl, &dict, "hi lojo bar", true); TemplateDictionary dict2("dict2"); dict2.AddSectionDictionary("SEC"); AssertExpandIs(tpl, &dict2, "hi lo bar", true); dict2.AddSectionDictionary("SEC"); AssertExpandIs(tpl, &dict2, "hi lojolo bar", true); // This is a weird case: using separator and specifying manually. dict2.ShowSection("SEC_separator"); AssertExpandIs(tpl, &dict2, "hi lojojolojo bar", true); TemplateDictionary dict3("dict3"); TemplateDictionary* sec1 = dict3.AddSectionDictionary("SEC"); TemplateDictionary* sec2 = dict3.AddSectionDictionary("SEC"); TemplateDictionary* sec3 = dict3.AddSectionDictionary("SEC"); dict3.SetValue("JO", "J"); AssertExpandIs(tpl, &dict3, "hi lojoJlojoJlo bar", true); sec1->SetValue("JO", "JO"); AssertExpandIs(tpl, &dict3, "hi lojoJOlojoJlo bar", true); sec2->SetValue("JO", "JOO"); AssertExpandIs(tpl, &dict3, "hi lojoJOlojoJOOlo bar", true); dict3.AddSectionDictionary("SEC"); AssertExpandIs(tpl, &dict3, "hi lojoJOlojoJOOlojoJlo bar", true); sec3->AddSectionDictionary("SEC_separator"); AssertExpandIs(tpl, &dict3, "hi lojoJOlojoJOOlojoJjoJlo bar", true); // Make sure we don't do anything special with var or include names Template* tpl2 = StringToTemplate( "hi {{#SEC}}lo{{>SEC_separator}}{{/SEC}} bar", STRIP_WHITESPACE); AssertExpandIs(tpl2, &dict2, "hi lolo bar", true); Template* tpl3 = StringToTemplate( "hi {{#SEC}}lo{{SEC_separator}}{{/SEC}} bar", STRIP_WHITESPACE); dict2.SetValue("SEC_separator", "-"); AssertExpandIs(tpl3, &dict2, "hi lo-lo- bar", true); } static void TestInclude() { string incname = StringToTemplateFile("include file\n"); string incname2 = StringToTemplateFile("inc2a\ninc2b\n"); string incname_bad = StringToTemplateFile("{{syntax_error"); Template* tpl = StringToTemplate("hi {{>INC}} bar\n", STRIP_WHITESPACE); TemplateDictionary dict("dict"); AssertExpandIs(tpl, &dict, "hi bar", true); dict.AddIncludeDictionary("INC"); AssertExpandIs(tpl, &dict, "hi bar", true); // noop: no filename was set dict.AddIncludeDictionary("INC")->SetFilename("/notarealfile "); AssertExpandIs(tpl, &dict, "hi bar", false); // noop: illegal filename dict.AddIncludeDictionary("INC")->SetFilename(incname); AssertExpandIs(tpl, &dict, "hi include file bar", false); dict.AddIncludeDictionary("INC")->SetFilename(incname_bad); AssertExpandIs(tpl, &dict, "hi include file bar", false); // noop: syntax error dict.AddIncludeDictionary("INC")->SetFilename(incname); AssertExpandIs(tpl, &dict, "hi include fileinclude file bar", false); dict.AddIncludeDictionary("inc")->SetFilename(incname); AssertExpandIs(tpl, &dict, "hi include fileinclude file bar", false); dict.AddIncludeDictionary("INC")->SetFilename(incname2); AssertExpandIs(tpl, &dict, "hi include fileinclude fileinc2ainc2b 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\ninc2a\ninc2b\n bar", false); // Test that if we indent the include, every line on the include // is indented. Template* tpl3 = StringToTemplate("hi\n {{>INC}} bar", DO_NOT_STRIP); AssertExpandIs(tpl3, &dict, "hi\n include file\n include file\n" " inc2a\n inc2b\n bar", false); // But obviously, if we strip leading whitespace, no indentation. Template* tpl4 = StringToTemplate("hi\n {{>INC}} bar", STRIP_WHITESPACE); AssertExpandIs(tpl4, &dict, "hiinclude fileinclude fileinc2ainc2b bar", false); // And if it's not a whitespace indent, we don't indent either. Template* tpl5 = StringToTemplate("hi\n - {{>INC}} bar", DO_NOT_STRIP); AssertExpandIs(tpl5, &dict, "hi\n - include file\ninclude file\n" "inc2a\ninc2b\n bar", false); // Make sure we indent properly at the beginning. Template* tpl6 = StringToTemplate(" {{>INC}}\nbar", DO_NOT_STRIP); AssertExpandIs(tpl6, &dict, " include file\n include file\n" " inc2a\n inc2b\n \nbar", false); // And deal correctly when we include twice in a row. Template* tpl7 = StringToTemplate(" {{>INC}}-{{>INC}}", DO_NOT_STRIP); AssertExpandIs(tpl7, &dict, " include file\n include file\n inc2a\n inc2b\n " "-include file\ninclude file\ninc2a\ninc2b\n", false); } static void TestIncludeWithModifiers() { string incname = StringToTemplateFile("include & print file\n"); string incname2 = StringToTemplateFile("inc2\n"); 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); // Test that if we include the same template twice, once with a modifer // and once without, they each get applied properly. Template* tpl5 = StringToTemplate("hi {{>INC:h}} bar {{>INC}} baz\n", DO_NOT_STRIP); TemplateDictionary dict("dict"); AssertExpandIs(tpl1, &dict, "hi bar\n", true); dict.AddIncludeDictionary("INC")->SetFilename(incname); 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", 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); AssertExpandIs(tpl5, &dict, "hi include & print file inc2 yo&yo bar " "include & print file\ninc2\nyo&yo baz\n", true); // Don't test modifier syntax here; that's in TestVariableWithModifiers() } // Make sure we don't deadlock when a template includes itself. // This also tests we handle recursive indentation properly. static void TestRecursiveInclude() { string incname = StringToTemplateFile("hi {{>INC}} bar\n {{>INC}}!"); Template* tpl = Template::GetTemplate(incname, DO_NOT_STRIP); TemplateDictionary dict("dict"); dict.AddIncludeDictionary("INC")->SetFilename(incname); // Note the last line is indented 4 spaces instead of 2. This is // because the last sub-include is indented. AssertExpandIs(tpl, &dict, "hi hi bar\n ! bar\n hi bar\n !!", true); } // Tests that vars inherit/override their parents properly static void TestInheritence() { Template* tpl = StringToTemplate("{{FOO}}{{#SEC}}{{FOO}}{{#SEC}}{{FOO}}{{/SEC}}{{/SEC}}", STRIP_WHITESPACE); TemplateDictionary dict("dict"); dict.SetValue("FOO", "foo"); dict.ShowSection("SEC"); AssertExpandIs(tpl, &dict, "foofoofoo", true); TemplateDictionary dict2("dict2"); dict2.SetValue("FOO", "foo"); TemplateDictionary* sec = dict2.AddSectionDictionary("SEC"); AssertExpandIs(tpl, &dict2, "foofoofoo", true); sec->SetValue("FOO", "bar"); AssertExpandIs(tpl, &dict2, "foobarbar", true); TemplateDictionary* sec2 = sec->AddSectionDictionary("SEC"); AssertExpandIs(tpl, &dict2, "foobarbar", true); sec2->SetValue("FOO", "baz"); 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}}", STRIP_WHITESPACE); string incname = StringToTemplateFile( "include {{FOO}}{{#SEC}}invisible{{/SEC}}file\n"); TemplateDictionary incdict("dict"); incdict.ShowSection("SEC"); incdict.SetValue("FOO", "foo"); incdict.AddIncludeDictionary("INC")->SetFilename(incname); AssertExpandIs(tpl, &incdict, "foohiinclude file", true); } static void TestTemplateString() { // Make sure using TemplateString and StaticTemplateString for the // dictionary expands the same as using char*'s. Template* tpl = StringToTemplate("hi {{VAR}} lo", STRIP_WHITESPACE); TemplateDictionary dict("dict"); dict.SetValue("VAR", TemplateString("short-lived", strlen("short"))); AssertExpandIs(tpl, &dict, "hi short lo", true); dict.SetValue("VAR", kHello); AssertExpandIs(tpl, &dict, "hi Hello lo", true); } // Tests that we append to the output string, rather than overwrite static void TestExpand() { Template* tpl = StringToTemplate("hi", STRIP_WHITESPACE); TemplateDictionary dict("test_expand"); string output("premade"); ASSERT(tpl->Expand(&output, &dict)); ASSERT_STREQ(output.c_str(), "premadehi"); tpl = StringToTemplate(" lo ", STRIP_WHITESPACE); ASSERT(tpl->Expand(&output, &dict)); ASSERT_STREQ(output.c_str(), "premadehilo"); } static void TestExpandTemplate() { string filename = StringToTemplateFile(" hi {{THERE}}"); TemplateDictionary dict("test_expand"); dict.SetValue("THERE", "test"); string output; ASSERT(ExpandTemplate(filename, STRIP_WHITESPACE, &dict, &output)); ASSERT_STREQ(output.c_str(), "hi test"); // This will append to output, so we see both together. ASSERT(ExpandWithData(filename, DO_NOT_STRIP, &dict, NULL, &output)); ASSERT_STREQ(output.c_str(), "hi test hi test"); ASSERT(!ExpandTemplate(filename + " not found", DO_NOT_STRIP, &dict, &output)); } static void TestExpandWithCustomEmitter() { Template* tpl = StringToTemplate("{{VAR}} {{VAR}}", STRIP_WHITESPACE); TemplateDictionary dict("test_expand"); dict.SetValue("VAR", "this song is just six words long"); string output; SizeofEmitter e(&output); ASSERT(tpl->Expand(&e, &dict)); ASSERT_STREQ("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", output.c_str()); } // Tests annotation, in particular inheriting annotation among children // This should be called first, so the filenames don't change as we add // more tests. static void TestAnnotation() { string incname = StringToTemplateFile("include {{#ISEC}}file{{/ISEC}}\n"); string incname2 = StringToTemplateFile("include #2\n"); Template* tpl = StringToTemplate( "boo!\n{{>INC}}\nhi {{#SEC}}lo{{#SUBSEC}}jo{{/SUBSEC}}{{/SEC}} bar " "{{VAR:x-foo}}", DO_NOT_STRIP); TemplateDictionary dict("dict"); PerExpandData per_expand_data; dict.ShowSection("SEC"); TemplateDictionary* incdict = dict.AddIncludeDictionary("INC"); incdict->SetFilename(incname); incdict->ShowSection("ISEC"); dict.AddIncludeDictionary("INC")->SetFilename(incname2); dict.SetValue("VAR", "var"); // This string is equivalent to "/template." (at least on unix) string slash_tpl(PathJoin(kRootdir, "template.")); per_expand_data.SetAnnotateOutput(""); char expected[10240]; // 10k should be big enough! snprintf(expected, sizeof(expected), "{{#FILE=%s003}}{{#SEC=__{{MAIN}}__}}boo!\n" "{{#INC=INC}}{{#FILE=%s001}}" "{{#SEC=__{{MAIN}}__}}include {{#SEC=ISEC}}file{{/SEC}}\n" "{{/SEC}}{{/FILE}}{{/INC}}" "{{#INC=INC}}{{#FILE=%s002}}" "{{#SEC=__{{MAIN}}__}}include #2\n{{/SEC}}{{/FILE}}{{/INC}}" "\nhi {{#SEC=SEC}}lo{{/SEC}} bar " "{{#VAR=VAR:x-foo}}var{{/VAR}}{{/SEC}}{{/FILE}}", (FLAGS_test_tmpdir + slash_tpl).c_str(), (FLAGS_test_tmpdir + slash_tpl).c_str(), (FLAGS_test_tmpdir + slash_tpl).c_str()); AssertExpandWithDataIs(tpl, &dict, &per_expand_data, expected, true); // Test ability to set custom annotator. CustomTestAnnotator custom_annotator; per_expand_data.SetAnnotator(&custom_annotator); snprintf(expected, sizeof(expected), "{{EVENT=1}}{{#FILE=%s003}}" "{{EVENT=2}}{{#SEC=__{{MAIN}}__}}boo!\n" "{{EVENT=3}}{{#INC=INC}}" "{{EVENT=4}}{{#FILE=%s001}}" "{{EVENT=5}}{{#SEC=__{{MAIN}}__}}include " "{{EVENT=6}}{{#SEC=ISEC}}file" "{{EVENT=7}}{{/SEC}}\n" "{{EVENT=8}}{{/SEC}}" "{{EVENT=9}}{{/FILE}}" "{{EVENT=10}}{{/INC}}" "{{EVENT=11}}{{#INC=INC}}" "{{EVENT=12}}{{#FILE=%s002}}" "{{EVENT=13}}{{#SEC=__{{MAIN}}__}}include #2\n" "{{EVENT=14}}{{/SEC}}" "{{EVENT=15}}{{/FILE}}" "{{EVENT=16}}{{/INC}}\nhi " "{{EVENT=17}}{{#SEC=SEC}}lo" "{{EVENT=18}}{{/SEC}} bar " "{{EVENT=19}}{{#VAR=VAR:x-foo}}var" "{{EVENT=20}}{{/VAR}}" "{{EVENT=21}}{{/SEC}}" "{{EVENT=22}}{{/FILE}}", (FLAGS_test_tmpdir + slash_tpl).c_str(), (FLAGS_test_tmpdir + slash_tpl).c_str(), (FLAGS_test_tmpdir + slash_tpl).c_str()); // We can't use AssertExpandWithDataIs() on our deliberately stateful // test annotator because it internally does a second expansion // assuming no state change between calls. string custom_outstring; ASSERT(tpl->ExpandWithData(&custom_outstring, &dict, &per_expand_data)); ASSERT_STREQ(custom_outstring.c_str(), expected); // Unset annotator and continue with next test as test of ability // to revert to built-in annotator. per_expand_data.SetAnnotator(NULL); per_expand_data.SetAnnotateOutput(slash_tpl.c_str()); snprintf(expected, sizeof(expected), "{{#FILE=%s003}}{{#SEC=__{{MAIN}}__}}boo!\n" "{{#INC=INC}}{{#FILE=%s001}}" "{{#SEC=__{{MAIN}}__}}include {{#SEC=ISEC}}file{{/SEC}}\n" "{{/SEC}}{{/FILE}}{{/INC}}" "{{#INC=INC}}{{#FILE=%s002}}" "{{#SEC=__{{MAIN}}__}}include #2\n{{/SEC}}{{/FILE}}{{/INC}}" "\nhi {{#SEC=SEC}}lo{{/SEC}} bar " "{{#VAR=VAR:x-foo}}var{{/VAR}}{{/SEC}}{{/FILE}}", (slash_tpl).c_str(), (slash_tpl).c_str(), (slash_tpl).c_str()); AssertExpandWithDataIs(tpl, &dict, &per_expand_data, expected, true); per_expand_data.SetAnnotateOutput(NULL); // should turn off annotations AssertExpandWithDataIs(tpl, &dict, &per_expand_data, "boo!\ninclude file\ninclude #2\n\nhi lo bar var", true); // Test that even if we set an annotator we shouldn't get annotation // if it is not turned on with SetAnnotateOutput(). per_expand_data.SetAnnotator(&custom_annotator); AssertExpandWithDataIs(tpl, &dict, &per_expand_data, "boo!\ninclude file\ninclude #2\n\nhi lo bar var", true); // Test annotation of "missing include" condition. Template* one_inc_tpl = StringToTemplate("File contents: {{>INC}}\n", DO_NOT_STRIP); TemplateDictionary dict_missing_file("dict_with_missing_file"); dict_missing_file.AddIncludeDictionary("INC")->SetFilename("missing.tpl"); per_expand_data.SetAnnotateOutput(""); per_expand_data.SetAnnotator(NULL); snprintf(expected, sizeof(expected), "{{#FILE=%s004}}{{#SEC=__{{MAIN}}__}}File contents: " "{{#INC=INC}}{{MISSING_FILE=missing.tpl}}{{/INC}}\n" "{{/SEC}}{{/FILE}}", (FLAGS_test_tmpdir + slash_tpl).c_str()); // We expect a false return value because of the missing file. AssertExpandWithDataIs(one_inc_tpl, &dict_missing_file, &per_expand_data, expected, false); // Same missing include test with custom annotator custom_annotator.Reset(); per_expand_data.SetAnnotator(&custom_annotator); snprintf(expected, sizeof(expected), "{{EVENT=1}}{{#FILE=%s004}}" "{{EVENT=2}}{{#SEC=__{{MAIN}}__}}File contents: " "{{EVENT=3}}{{#INC=INC}}" "{{EVENT=4}}{{MISSING_FILE=missing.tpl}}" "{{EVENT=5}}{{/INC}}\n" "{{EVENT=6}}{{/SEC}}" "{{EVENT=7}}{{/FILE}}", (FLAGS_test_tmpdir + slash_tpl).c_str()); // See comment above on why we can't use AssertExpandWithDataIs() for // our stateful test annotator. custom_outstring.clear(); ASSERT(!one_inc_tpl->ExpandWithData(&custom_outstring, &dict_missing_file, &per_expand_data)); ASSERT_STREQ(custom_outstring.c_str(), expected); } static void TestTemplateExpansionModifier() { string parent_tpl_name = StringToTemplateFile("before {{>INC}} after"); string child_tpl_name1 = StringToTemplateFile("child1"); string child_tpl_name2 = StringToTemplateFile("child2"); Template* tpl = Template::GetTemplate(parent_tpl_name, DO_NOT_STRIP); TemplateDictionary dict("parent dict"); dict.AddIncludeDictionary("INC")->SetFilename(child_tpl_name1); dict.AddIncludeDictionary("INC")->SetFilename(child_tpl_name2); PerExpandData per_expand_data; EmphasizeTemplateModifier modifier1(child_tpl_name1); per_expand_data.SetTemplateExpansionModifier(&modifier1); AssertExpandWithDataIs(tpl, &dict, &per_expand_data, "before >>child1<>child2<< after", true); EmphasizeTemplateModifier modifier3(parent_tpl_name); per_expand_data.SetTemplateExpansionModifier(&modifier3); AssertExpandWithDataIs(tpl, &dict, &per_expand_data, ">>before child1child2 after<<", true); per_expand_data.SetTemplateExpansionModifier(NULL); AssertExpandWithDataIs(tpl, &dict, &per_expand_data, "before child1child2 after", true); } static void TestGetTemplate() { // Tests the cache string filename = StringToTemplateFile("{This is perfectly valid} yay!"); Template* tpl1 = Template::GetTemplate(filename, DO_NOT_STRIP); Template* tpl2 = Template::GetTemplate(filename.c_str(), DO_NOT_STRIP); Template* tpl3 = Template::GetTemplate(filename, STRIP_WHITESPACE); ASSERT(tpl1 && tpl2 && tpl3); ASSERT(tpl1 == tpl2); ASSERT(tpl1 != tpl3); // Tests that a nonexistent template returns NULL Template* tpl4 = Template::GetTemplate("/yakakak", STRIP_WHITESPACE); ASSERT(!tpl4); // Tests that syntax errors cause us to return NULL Template* tpl5 = StringToTemplate("{{This has spaces in it}}", DO_NOT_STRIP); ASSERT(!tpl5); Template* tpl6 = StringToTemplate("{{#SEC}}foo", DO_NOT_STRIP); ASSERT(!tpl6); Template* tpl7 = StringToTemplate("{{#S1}}foo{{/S2}}", DO_NOT_STRIP); ASSERT(!tpl7); Template* tpl8 = StringToTemplate("{{#S1}}foo{{#S2}}bar{{/S1}{{/S2}", DO_NOT_STRIP); ASSERT(!tpl8); Template* tpl9 = StringToTemplate("{{noend", DO_NOT_STRIP); ASSERT(!tpl9); } static void TestStringCacheKey() { // If you use these same cache keys somewhere else, // call Template::ClearCache first. const string cache_key_a = "cache key a"; const string text = "Test template 1"; TemplateDictionary empty_dict("dict"); // When a string template is registered via StringToTemplateCache, // we can use GetTemplate for that same cache-key under any other // Strip because we cache the contents. Template *tpl1, *tpl2; ASSERT(Template::StringToTemplateCache(cache_key_a, text)); tpl1 = Template::GetTemplate(cache_key_a, DO_NOT_STRIP); AssertExpandIs(tpl1, &empty_dict, text, true); // Different strip. ASSERT(tpl2 = Template::GetTemplate(cache_key_a, STRIP_BLANK_LINES)); ASSERT(tpl2 != tpl1); AssertExpandIs(tpl2, &empty_dict, text, true); Template::ClearCache(); } static void TestStringGetTemplate() { TemplateDictionary dict("dict"); // Test cache lookups const char* const tpltext = "{This is perfectly valid} yay!"; ASSERT(Template::StringToTemplateCache("tgt", tpltext)); Template* tpl1 = Template::GetTemplate("tgt", DO_NOT_STRIP); Template* tpl2 = Template::GetTemplate("tgt", STRIP_WHITESPACE); ASSERT(tpl1 && tpl2); ASSERT(tpl1 != tpl2); AssertExpandIs(tpl1, &dict, tpltext, true); AssertExpandIs(tpl2, &dict, tpltext, true); // If we register a new string under the same text, it should be // ignored. ASSERT(!Template::StringToTemplateCache("tgt", tpltext)); ASSERT(!Template::StringToTemplateCache("tgt", "new text")); Template* tpl3 = Template::GetTemplate("tgt", DO_NOT_STRIP); ASSERT(tpl3 == tpl1); AssertExpandIs(tpl3, &dict, tpltext, true); // Tests that syntax errors cause us to return NULL ASSERT(!Template::StringToTemplateCache("tgt2", "{{This has spaces}}")); ASSERT(!Template::StringToTemplateCache("tgt3", "{{#SEC}}foo")); ASSERT(!Template::StringToTemplateCache("tgt4", "{{#S1}}foo{{/S2}}")); ASSERT(!Template::StringToTemplateCache("tgt5", "{{#S1}}foo{{#S2}}bar{{/S1}{{/S2}")); ASSERT(!Template::StringToTemplateCache("tgt6", "{{noend")); // And that we didn't cache them by mistake ASSERT(!Template::GetTemplate("tgt2", STRIP_WHITESPACE)); Template::ClearCache(); } static void TestStringTemplateInclude() { Template::ClearCache(); // just for exercise. const string cache_key = "TestStringTemplateInclude"; const string cache_key_inc = "TestStringTemplateInclude-inc"; const string cache_key_indent = "TestStringTemplateInclude-indent"; const string text = "{{>INC}}"; const string text_inc = "
\n

\nUser {{USER}}\n

"; const string text_indent = "\n {{>INC}}"; ASSERT(Template::StringToTemplateCache(cache_key, text)); ASSERT(Template::StringToTemplateCache(cache_key_inc, text_inc)); ASSERT(Template::StringToTemplateCache(cache_key_indent, text_indent)); Template *tpl = Template::GetTemplate(cache_key, DO_NOT_STRIP); ASSERT(tpl); TemplateDictionary dict("dict"); TemplateDictionary* sub_dict = dict.AddIncludeDictionary("INC"); sub_dict->SetFilename(cache_key_inc); sub_dict->SetValue("USER", "John<>Doe"); string expected = "
\n

\nUser John<>Doe\n

"; AssertExpandIs(tpl, &dict, expected, true); // Repeat the same except that now the parent has a template-level // directive (by way of the automatic-line-indenter). tpl = Template::GetTemplate(cache_key_indent, DO_NOT_STRIP); ASSERT(tpl); expected = "\n" "
\n" "

\n" " User John<>Doe\n" "

" ""; AssertExpandIs(tpl, &dict, expected, true); Template::ClearCache(); } static void TestTemplateSearchPath() { const string pathA = PathJoin(FLAGS_test_tmpdir, "a/"); const string pathB = PathJoin(FLAGS_test_tmpdir, "b/"); CreateOrCleanTestDir(pathA); CreateOrCleanTestDir(pathB); TemplateDictionary dict(""); Template::SetTemplateRootDirectory(pathA); Template::AddAlternateTemplateRootDirectory(pathB); // 1. Show that a template in the secondary path can be found. const string path_b_bar = PathJoin(pathB, "template_bar"); StringToFile("b/template_bar", path_b_bar); ASSERT_STREQ(path_b_bar.c_str(), Template::FindTemplateFilename("template_bar").c_str()); Template* b_bar = Template::GetTemplate("template_bar", DO_NOT_STRIP); ASSERT(b_bar); AssertExpandIs(b_bar, &dict, "b/template_bar", true); // 2. Show that the search stops once the first match is found. // Create two templates in separate directories with the same name. const string path_a_foo = PathJoin(pathA, "template_foo"); StringToFile("a/template_foo", path_a_foo); StringToFile("b/template_foo", PathJoin(pathB, "template_foo")); ASSERT_STREQ(path_a_foo.c_str(), Template::FindTemplateFilename("template_foo").c_str()); Template* a_foo = Template::GetTemplate("template_foo", DO_NOT_STRIP); ASSERT(a_foo); AssertExpandIs(a_foo, &dict, "a/template_foo", true); // 3. Show that attempting to find a non-existent template gives an // empty path. ASSERT(Template::FindTemplateFilename("baz").empty()); CreateOrCleanTestDir(pathA); CreateOrCleanTestDir(pathB); } static void TestRemoveStringFromTemplateCache() { Template::ClearCache(); // just for exercise. const string cache_key = "TestRemoveStringFromTemplateCache"; const string text = "here today..."; TemplateDictionary dict("test"); ASSERT(Template::StringToTemplateCache(cache_key, text)); Template* tpl = Template::GetTemplate(cache_key, DO_NOT_STRIP); ASSERT(tpl); AssertExpandIs(tpl, &dict, text, true); tpl = Template::GetTemplate(cache_key, STRIP_WHITESPACE); ASSERT(tpl); AssertExpandIs(tpl, &dict, text, true); Template::RemoveStringFromTemplateCache(cache_key); tpl = Template::GetTemplate(cache_key, DO_NOT_STRIP); ASSERT(!tpl); tpl = Template::GetTemplate(cache_key, STRIP_WHITESPACE); ASSERT(!tpl); tpl = Template::GetTemplate(cache_key, STRIP_BLANK_LINES); ASSERT(!tpl); } static void TestTemplateCache() { const string filename_a = StringToTemplateFile("Test template 1"); const string filename_b = StringToTemplateFile("Test template 2."); Template *tpl, *tpl2; ASSERT(tpl = Template::GetTemplate(filename_a, DO_NOT_STRIP)); ASSERT(tpl2 = Template::GetTemplate(filename_b, DO_NOT_STRIP)); ASSERT(tpl2 != tpl); // different filenames. ASSERT(tpl2 = Template::GetTemplate(filename_a, STRIP_BLANK_LINES)); ASSERT(tpl2 != tpl); // different strip. ASSERT(tpl2 = Template::GetTemplate(filename_b, STRIP_BLANK_LINES)); ASSERT(tpl2 != tpl); // different filenames and strip. ASSERT(tpl2 = Template::GetTemplate(filename_a, DO_NOT_STRIP)); ASSERT(tpl2 == tpl); // same filename and strip. } // Tests that the various strip values all do the expected thing. static void TestStrip() { TemplateDictionary dict("dict"); dict.SetValue("FOO", "foo"); const char* tests[][4] = { // 0: in, 1: do-not-strip, 2: blanklines, 3: ws {"hi!\n", "hi!\n", "hi!\n", "hi!"}, {"hi!", "hi!", "hi!", "hi!"}, // These test strip-blank-lines, primarily {"{{FOO}}\n\n{{FOO}}", "foo\n\nfoo", "foo\nfoo", "foofoo"}, {"{{FOO}}\r\n\r\n{{FOO}}", "foo\r\n\r\nfoo", "foo\r\nfoo", "foofoo"}, {"{{FOO}}\n \n{{FOO}}\n", "foo\n \nfoo\n", "foo\nfoo\n", "foofoo"}, {"{{FOO}}\n{{BI_NEWLINE}}\nb", "foo\n\n\nb", "foo\n\n\nb", "foo\nb"}, {"{{FOO}}\n{{!comment}}\nb", "foo\n\nb", "foo\nb", "foob"}, {"{{FOO}}\n{{!comment}}{{!comment2}}\nb", "foo\n\nb", "foo\n\nb", "foob"}, {"{{FOO}}\n{{>ONE_INC}}\nb", "foo\n\nb", "foo\nb", "foob"}, {"{{FOO}}\n\t{{>ONE_INC}} \nb", "foo\n\t \nb", "foo\nb", "foob"}, {"{{FOO}}\n{{>ONE_INC}}{{>TWO_INC}}\nb", "foo\n\nb", "foo\n\nb", "foob"}, {"{{FOO}}\n {{#SEC}}\ntext \n {{/SEC}}\n", "foo\n \n", "foo\n", "foo"}, {"{{%AUTOESCAPE context=\"HTML\"}}\nBLA", "\nBLA", "BLA", "BLA"}, // These test strip-whitespace {"foo\nbar\n", "foo\nbar\n", "foo\nbar\n", "foobar"}, {"{{FOO}}\nbar\n", "foo\nbar\n", "foo\nbar\n", "foobar"}, {" {{FOO}} {{!comment}}\nb", " foo \nb", " foo \nb", "foo b"}, {" {{FOO}} {{BI_SPACE}}\n", " foo \n", " foo \n", "foo "}, {" \t \f\v \n\r\n ", " \t \f\v \n\r\n ", "", ""}, }; for (int i = 0; i < sizeof(tests)/sizeof(*tests); ++i) { 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], true); AssertExpandIs(tpl2, &dict, tests[i][2], true); AssertExpandIs(tpl3, &dict, tests[i][3], true); } } // Tests both static and non-static versions static void TestReloadIfChanged() { TemplateDictionary dict("empty"); string filename = StringToTemplateFile("{valid template}"); string nonexistent = StringToTemplateFile("dummy"); ASSERT(unlink(nonexistent.c_str()) == 0); Template* tpl = Template::GetTemplate(filename, STRIP_WHITESPACE); assert(tpl); Template* tpl2 = Template::GetTemplate(nonexistent, STRIP_WHITESPACE); assert(!tpl2); // TODO(csilvers): figure out how to mock time, rather than sleeping ASSERT(!tpl->ReloadIfChanged()); // false: no reloading needed StringToFile("{valid template}", filename); // no contentful change ASSERT(tpl->ReloadIfChanged()); // true: change, even if not contentful ASSERT(Template::GetTemplate(filename, STRIP_WHITESPACE) == tpl); // needed AssertExpandIs(tpl, &dict, "{valid template}", true); StringToFile("exists now!", nonexistent); tpl2 = Template::GetTemplate(nonexistent, STRIP_WHITESPACE); ASSERT(!tpl2); // because it's cached as not existing Template::ReloadAllIfChanged(); // force the reload ASSERT(Template::GetTemplate(filename, STRIP_WHITESPACE) == tpl); tpl2 = Template::GetTemplate(nonexistent, STRIP_WHITESPACE); ASSERT(tpl2); // file exists now ASSERT(!tpl2->ReloadIfChanged()); // false: file hasn't changed ASSERT(!tpl2->ReloadIfChanged()); // false: file *still* hasn't changed ASSERT(unlink(nonexistent.c_str()) == 0); // here today... ASSERT(!tpl2->ReloadIfChanged()); // false: file has disappeared // The old template content should be forgotten ASSERT(NULL == Template::GetTemplate(nonexistent, STRIP_WHITESPACE)); StringToFile("lazarus", nonexistent); ASSERT(tpl2->ReloadIfChanged()); // true: file exists again ASSERT(Template::GetTemplate(nonexistent, STRIP_WHITESPACE) == tpl2); AssertExpandIs(tpl2, &dict, "lazarus", true); StringToFile("{new template}", filename); tpl = Template::GetTemplate(filename, STRIP_WHITESPACE); // needed AssertExpandIs(tpl, &dict, "{valid template}", true); // haven't reloaded ASSERT(tpl->ReloadIfChanged()); // true: change, even if not contentful ASSERT(Template::GetTemplate(filename, STRIP_WHITESPACE) == tpl); // needed AssertExpandIs(tpl, &dict, "{new template}", true); // Now change both tpl and tpl2 StringToFile("{all-changed}", filename); StringToFile("lazarus2", nonexistent); Template::ReloadAllIfChanged(); // Since the files have actually changed, GetTemplate will return // new template pointers. // The old template pointers are moved to the freelist. Template* tpl_post_reload = Template::GetTemplate( filename, STRIP_WHITESPACE); // needed ASSERT(tpl_post_reload != tpl); tpl = tpl_post_reload; Template* tpl2_post_reload = Template::GetTemplate(nonexistent, STRIP_WHITESPACE); ASSERT(tpl2_post_reload != tpl2); tpl2 = tpl2_post_reload; AssertExpandIs(tpl, &dict, "{all-changed}", true); AssertExpandIs(tpl2, &dict, "lazarus2", true); // // Tests to ensure selective Auto-Escape works correctly across reloads. // TemplateDictionary dict2("user"); dict2.SetValue("USER", ""); // Escaped differently by JS and HTML. const string kHtmlPragma("{{%AUTOESCAPE context=\"HTML\"}}\n"); const string kJsPragma("{{%AUTOESCAPE context=\"JAVASCRIPT\"}}\n"); const string kHtmltext("

{{USER}}"); // some HTML const string kHtmltextOther("{{USER}}"); // different HTML const string kJstext("a = '{{USER}}'"); // some Javascript // Case 1: Change template from non auto-escaped to HTML auto-escaped. filename = StringToTemplateFile(kHtmltext); tpl = Template::GetTemplate(filename, STRIP_WHITESPACE); StringToFile(kHtmlPragma + kHtmltext, filename); AssertExpandIs(tpl, &dict2, "

", true); // haven't reloaded ASSERT(tpl->ReloadIfChanged()); // true: content changed AssertExpandIs(tpl, &dict2, "

<bla>", true); // reloaded & escaped tpl2 = Template::GetTemplate(filename, STRIP_WHITESPACE); AssertExpandIs(tpl2, &dict2, "

<bla>", true); // same as tpl // Case 2: Change template content, same auto-escape context. StringToFile(kHtmlPragma + kHtmltextOther, filename); ASSERT(tpl->ReloadIfChanged()); // true: content changed AssertExpandIs(tpl, &dict2, "<bla>", true); // reloaded tpl2 = Template::GetTemplate(filename, STRIP_WHITESPACE); AssertExpandIs(tpl2, &dict2, "<bla>", true); // same as tpl // Case 3: Change auto-escape context to Javascript. StringToFile(kJsPragma + kJstext, filename); AssertExpandIs(tpl, &dict2, "<bla>", true); // haven't reloaded ASSERT(tpl->ReloadIfChanged()); // true: content changed AssertExpandIs(tpl, &dict2, "a = '\\x3cbla\\x3e'", true); // reloaded tpl2 = Template::GetTemplate(filename, STRIP_WHITESPACE); AssertExpandIs(tpl2, &dict2, "a = '\\x3cbla\\x3e'", true); // same as tpl // Case 4: Change template from auto-escaped to non auto-escaped. StringToFile(kHtmltext, filename); AssertExpandIs(tpl, &dict2, "a = '\\x3cbla\\x3e'", true); ASSERT(tpl->ReloadIfChanged()); // true: content changed AssertExpandIs(tpl, &dict2, "

", true); // reloaded => not escaped tpl2 = Template::GetTemplate(filename, STRIP_WHITESPACE); AssertExpandIs(tpl2, &dict2, "

", true); // same as tpl } static void TestTemplateRootDirectory() { string filename = StringToTemplateFile("Test template"); ASSERT(IsAbspath(filename)); Template* tpl1 = Template::GetTemplate(filename, DO_NOT_STRIP); Template::SetTemplateRootDirectory(kRootdir); // "/" // template-root shouldn't matter for absolute directories Template* tpl2 = Template::GetTemplate(filename, DO_NOT_STRIP); Template::SetTemplateRootDirectory("/sadfadsf/waerfsa/safdg"); Template* tpl3 = Template::GetTemplate(filename, DO_NOT_STRIP); ASSERT(tpl1 != NULL); ASSERT(tpl1 == tpl2); ASSERT(tpl1 == tpl3); // Now test it actually works by breaking the abspath in various places. // We do it twice, since we don't know if the path-sep is "/" or "\". // NOTE: this depends on filename not using "/" or "\" except as a // directory separator (so nothing like "/var/tmp/foo\a/weirdfile"). const char* const kPathSeps = "/\\"; for (const char* path_sep = kPathSeps; *path_sep; path_sep++) { for (string::size_type sep_pos = filename.find(*path_sep, 0); sep_pos != string::npos; sep_pos = filename.find(*path_sep, sep_pos + 1)) { Template::SetTemplateRootDirectory(filename.substr(0, sep_pos + 1)); Template* tpl = Template::GetTemplate(filename.substr(sep_pos + 1), DO_NOT_STRIP); ASSERT(string(tpl->template_file()) == tpl1->template_file()); } } } struct ThreadReturn { Template* file_template; bool string_to_template_cache_return; Template* string_template; }; // RunThread returns a ThreadReturn* that should be deleted. static void* RunThread(void* vfilename) { const char* filename = reinterpret_cast(vfilename); ThreadReturn* ret = new ThreadReturn; ret->file_template = Template::GetTemplate(filename, DO_NOT_STRIP); ASSERT(ret->file_template != NULL); const char* const key = "RunThread key"; ret->string_to_template_cache_return = StringToTemplateCache(key, " RunThread text ", STRIP_WHITESPACE); ret->string_template = Template::GetTemplate(key, STRIP_WHITESPACE); ASSERT(ret->string_template != NULL); return ret; } static void TestThreadSafety() { #if defined(HAVE_PTHREAD) && !defined(NO_THREADS) string filename = StringToTemplateFile("(testing thread-safety)"); // GetTemplate() is the most thread-contended routine. We get a // template in many threads, and assert we get the same template // from each. pthread_t thread_ids[kNumThreads]; for (int i = 0; i < kNumThreads; ++i) { ASSERT(pthread_create(thread_ids+i, NULL, TemplateUnittest::RunThread, (void*)filename.c_str()) == 0); } // Wait for all the threads to terminate (should be very quick!) ThreadReturn* first_thread_return = NULL; int num_times_string_to_template_cache_returned_true = 0; for (int i = 0; i < kNumThreads; ++i) { void* vthread_return; ASSERT(pthread_join(thread_ids[i], &vthread_return) == 0); ThreadReturn* thread_return = reinterpret_cast(vthread_return); if (thread_return->string_to_template_cache_return) { ++num_times_string_to_template_cache_returned_true; } if (first_thread_return == NULL) { // we're the first thread first_thread_return = thread_return; } else { ASSERT(thread_return->file_template == first_thread_return->file_template); ASSERT(thread_return->string_template == first_thread_return->string_template); delete thread_return; } } delete first_thread_return; ASSERT_INTEQ(1, num_times_string_to_template_cache_returned_true); Template::ClearCache(); #endif } // Tests all the static methods in TemplateNamelist static void TestTemplateNamelist() { time_t before_time = Now(); // in template_test_util.cc string f1 = StringToTemplateFile("{{This has spaces in it}}"); string f2 = StringToTemplateFile("{{#SEC}}foo"); string f3 = StringToTemplateFile("{This is ok"); // Where we'll copy f1 - f3 to: these are names known at compile-time string f1_copy = PathJoin(FLAGS_test_tmpdir, INVALID1_FN); string f2_copy = PathJoin(FLAGS_test_tmpdir, INVALID2_FN); string f3_copy = PathJoin(FLAGS_test_tmpdir, VALID1_FN); Template::SetTemplateRootDirectory(FLAGS_test_tmpdir); time_t after_time = Now(); // f1, f2, f3 all written by now TemplateNamelist::NameListType names = TemplateNamelist::GetList(); ASSERT(names.size() == 4); ASSERT(names.find(NONEXISTENT_FN) != names.end()); ASSERT(names.find(INVALID1_FN) != names.end()); ASSERT(names.find(INVALID2_FN) != names.end()); ASSERT(names.find(VALID1_FN) != names.end()); // Before creating the files INVALID1_FN, etc., all should be missing. for (int i = 0; i < 3; ++i) { // should be consistent all 3 times const TemplateNamelist::MissingListType& missing = TemplateNamelist::GetMissingList(false); ASSERT(missing.size() == 4); } // Everyone is missing, but nobody should have bad syntax ASSERT(!TemplateNamelist::AllDoExist()); ASSERT(TemplateNamelist::IsAllSyntaxOkay(DO_NOT_STRIP)); // Now create those files ASSERT(link(f1.c_str(), f1_copy.c_str()) == 0); ASSERT(link(f2.c_str(), f2_copy.c_str()) == 0); ASSERT(link(f3.c_str(), f3_copy.c_str()) == 0); // We also have to clear the template cache, since we created a new file. // ReloadAllIfChanged() would probably work, too. Template::ClearCache(); // When GetMissingList is false, we don't reload, so you still get all-gone TemplateNamelist::MissingListType missing = TemplateNamelist::GetMissingList(false); ASSERT(missing.size() == 4); // But with true, we should have a different story missing = TemplateNamelist::GetMissingList(true); ASSERT(missing.size() == 1); missing = TemplateNamelist::GetMissingList(false); ASSERT(missing.size() == 1); ASSERT(missing[0] == NONEXISTENT_FN); ASSERT(!TemplateNamelist::AllDoExist()); // IsAllSyntaxOK did a badsyntax check, before the files were created. // So with a false arg, should still say everything is ok TemplateNamelist::SyntaxListType badsyntax = TemplateNamelist::GetBadSyntaxList(false, DO_NOT_STRIP); ASSERT(badsyntax.size() == 0); // But IsAllSyntaxOK forces a refresh ASSERT(!TemplateNamelist::IsAllSyntaxOkay(DO_NOT_STRIP)); badsyntax = TemplateNamelist::GetBadSyntaxList(false, DO_NOT_STRIP); ASSERT(badsyntax.size() == 2); ASSERT(badsyntax[0] == INVALID1_FN || badsyntax[1] == INVALID1_FN); ASSERT(badsyntax[0] == INVALID2_FN || badsyntax[1] == INVALID2_FN); ASSERT(!TemplateNamelist::IsAllSyntaxOkay(DO_NOT_STRIP)); badsyntax = TemplateNamelist::GetBadSyntaxList(true, DO_NOT_STRIP); ASSERT(badsyntax.size() == 2); time_t modtime = TemplateNamelist::GetLastmodTime(); ASSERT(modtime >= before_time && modtime <= after_time); // Now update a file and make sure lastmod time is updated. // Note that since TemplateToFile uses "fake" timestamps way // in the past, this append should definitely give a time // that's after after_time. FILE* fp = fopen(f1_copy.c_str(), "ab"); ASSERT(fp); fwrite("\n", 1, 1, fp); fclose(fp); modtime = TemplateNamelist::GetLastmodTime(); ASSERT(modtime > after_time); // Checking if we can register templates at run time. string f4 = StringToTemplateFile("{{ONE_GOOD_TEMPLATE}}"); TemplateNamelist::RegisterTemplate(f4.c_str()); names = TemplateNamelist::GetList(); ASSERT(names.size() == 5); string f5 = StringToTemplateFile("{{ONE BAD TEMPLATE}}"); TemplateNamelist::RegisterTemplate(f5.c_str()); names = TemplateNamelist::GetList(); ASSERT(names.size() == 6); badsyntax = TemplateNamelist::GetBadSyntaxList(false, DO_NOT_STRIP); 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); TemplateNamelist::RegisterTemplate("A_non_existant_file.tpl"); names = TemplateNamelist::GetList(); ASSERT(names.size() == 7); missing = TemplateNamelist::GetMissingList(false); ASSERT(missing.size() == 1); // we did not refresh the missing list missing = TemplateNamelist::GetMissingList(true); // After refresh, the file we just registerd also added in missing list ASSERT(missing.size() == 2); } // This test is not "end-to-end", it doesn't use a dictionary // and only outputs what the template system thinks is the // correct modifier for variables. static void TestCorrectModifiersForAutoEscape() { string text, expected_out; // template with no variable, nothing to emit. text = "Static template."; AssertCorrectModifiers(TC_HTML, text, ""); // Simple templates with one variable substitution. // 1. No in-template modifiers. Auto Escaper sets correct ones. text = "Hello {{USER}}"; AssertCorrectModifiers(TC_HTML, text, "USER:h\n"); // Complete URLs in different attributes that take URLs. text = "bla"; AssertCorrectModifiers(TC_HTML, text, "URL:U=html\n"); text = ""; AssertCorrectModifiers(TC_HTML, text, "URL:U=html\n"); text = ""; AssertCorrectModifiers(TC_HTML, text, "URL:U=html\n"); // URL fragment only so just html_escape. text = ""; AssertCorrectModifiers(TC_HTML, text, "QUERY:h\n"); // URL fragment not quoted, so url_escape. text = ""; AssertCorrectModifiers(TC_HTML, text, "QUERY:u\n"); text = "
"; AssertCorrectModifiers(TC_HTML, text, "CLASS:h\n"); text = "
"; AssertCorrectModifiers(TC_HTML, text, "CLASS:H=attribute\n"); text = "
"; // CLASS here is name/value pair. AssertCorrectModifiers(TC_HTML, text, "CLASS:H=attribute\n"); text = "
"; // Style attribute. AssertCorrectModifiers(TC_HTML, text, "DISPLAY:c\n"); // Content inside a style tag should have :c regardless of quoting. text = ""; AssertCorrectModifiers(TC_HTML, text, "COLOR:c\nFONT:c\n"); // onMouseEvent and onKeyUp accept javascript. text = ""; // ID quoted AssertCorrectModifiers(TC_HTML, text, "ID:j\n"); text = ""; // ID not quoted AssertCorrectModifiers(TC_HTML, text, "ID:J=number\n"); text = ""; // not common AssertCorrectModifiers(TC_HTML, text, "ID:j\n"); // If ID is javascript code, J=number will break it, for good and bad. text = ""; AssertCorrectModifiers(TC_HTML, text, "ID:J=number\n"); // Target just needs html escaping. text = ""; AssertCorrectModifiers(TC_HTML, text, "TARGET:h\n"); // Test a parsing corner case which uses TemplateDirective // call in the parser to change state properly. To reproduce // both variables should be unquoted and the first should // have no value except the variable substitution. text = ""; AssertCorrectModifiers(TC_HTML, text, "CLASS:H=attribute\nQUERY:u\n"); // TODO(jad): Once we have a fix for it in code, fix me. // Javascript URL is not properly supported, we currently // apply :h which is not sufficient. text = "bla"; AssertCorrectModifiers(TC_HTML, text, "VAR:h\n"); // Special handling for BI_SPACE and BI_NEWLINE. text = "{{BI_SPACE}}"; AssertCorrectModifiers(TC_HTML, text, "BI_SPACE\n"); // Untouched. text = "{{BI_NEWLINE}}"; AssertCorrectModifiers(TC_HTML, text, "BI_NEWLINE\n"); // Untouched. // Check that the parser is parsing BI_SPACE, if not, it would have failed. text = "text"; AssertCorrectModifiers(TC_HTML, text, "BI_SPACE\nVAR:c\n"); // XML and JSON modes. text = "{{DATA}}"; AssertCorrectModifiers(TC_XML, text, "VAL:xml_escape\nDATA:xml_escape\n"); text = "{ x = \"{{VAL}}\"}"; AssertCorrectModifiers(TC_JSON, text, "VAL:j\n"); // 2. Escaping modifiers were set, handle them. // 2a: Modifier :none is honored whether the escaping is correct or not. text = "Hello {{USER:none}}"; // :none on its own. AssertCorrectModifiers(TC_HTML, text, "USER:none\n"); text = "Hello {{USER:h:none}}"; // correct escaping. AssertCorrectModifiers(TC_HTML, text, "USER:h:none\n"); text = "Hello {{USER:j:none}}"; // incorrect escaping. AssertCorrectModifiers(TC_HTML, text, "USER:j:none\n"); text = ""; AssertCorrectModifiers(TC_HTML, text, "ID:none\n"); // 2b: Correct modifiers, nothing to change. text = "Hello {{USER:h}}"; AssertCorrectModifiers(TC_HTML, text, "USER:h\n"); text = "Hello {{USER:U=html}}"; // :U=html is a valid replacement for .h AssertCorrectModifiers(TC_HTML, text, "USER:U=html\n"); text = "Hello {{USER:H=url}}"; // :H=url (a.k.a. U=html) is valid too AssertCorrectModifiers(TC_HTML, text, "USER:H=url\n"); text = "Hello {{USER:h:j}}"; // Extra :j, honor it. AssertCorrectModifiers(TC_HTML, text, "USER:h:j\n"); text = "bla"; AssertCorrectModifiers(TC_HTML, text, "URL:U=html\n"); text = "bla"; // :h is valid. AssertCorrectModifiers(TC_HTML, text, "QUERY:h\n"); text = "bla"; // so is :u. AssertCorrectModifiers(TC_HTML, text, "QUERY:u\n"); text = ""; AssertCorrectModifiers(TC_HTML, text, "ID:j\n"); text = ""; AssertCorrectModifiers(TC_HTML, text, "ID:J=number\n"); text = ""; // correct :U=css AssertCorrectModifiers(TC_HTML, text, "URL:U=css\n"); // 2c: Incorrect modifiers, add our own. text = "Hello {{USER:j}}"; // Missing :h AssertCorrectModifiers(TC_HTML, text, "USER:j:h\n"); text = "Hello {{USER:c:c:c:c:c:j}}"; // Still missing :h AssertCorrectModifiers(TC_HTML, text, "USER:c:c:c:c:c:j:h\n"); text = ""; // Missing :j AssertCorrectModifiers(TC_HTML, text, "VAR:h:j\n"); text = ""; // Extra :h:j AssertCorrectModifiers(TC_HTML, text, "VAR:j:h:j\n"); text = ""; // Unquoted AssertCorrectModifiers(TC_HTML, text, "ID:j:J=number\n"); // 2d: Custom modifiers are maintained. text = "Hello {{USER:x-bla}}"; // Missing :h AssertCorrectModifiers(TC_HTML, text, "USER:x-bla:h\n"); text = "Hello {{USER:x-bla:h}}"; // Correct, accept it. AssertCorrectModifiers(TC_HTML, text, "USER:x-bla:h\n"); text = "Hello {{USER:x-bla:x-foo}}"; // Missing :h AssertCorrectModifiers(TC_HTML, text, "USER:x-bla:x-foo:h\n"); text = "Hello {{USER:x-bla:none}}"; // Complete due to :none AssertCorrectModifiers(TC_HTML, text, "USER:x-bla:none\n"); text = "Hello {{USER:h:x-bla}}"; // Still missing :h. AssertCorrectModifiers(TC_HTML, text, "USER:h:x-bla:h\n"); text = "Hello {{USER:x-bla:h:x-foo}}"; // Still missing :h AssertCorrectModifiers(TC_HTML, text, "USER:x-bla:h:x-foo:h\n"); text = "Hello {{USER:x-bla:h:x-foo:h}}"; // Valid, accept it. AssertCorrectModifiers(TC_HTML, text, "USER:x-bla:h:x-foo:h\n"); // 2e: Equivalent modifiers are honored. All HTML Escapes. text = "Hello {{USER:p}}"; AssertCorrectModifiers(TC_HTML, text, "USER:p\n"); text = "Hello {{USER:H=attribute}}"; AssertCorrectModifiers(TC_HTML, text, "USER:H=attribute\n"); text = "Hello {{USER:H=snippet}}"; AssertCorrectModifiers(TC_HTML, text, "USER:H=snippet\n"); text = "Hello {{USER:H=pre}}"; AssertCorrectModifiers(TC_HTML, text, "USER:H=pre\n"); // All URL + HTML Escapes. text = "bla"; AssertCorrectModifiers(TC_HTML, text, "URL:H=url\n"); text = "bla"; AssertCorrectModifiers(TC_HTML, text, "URL:U=html\n"); // 2f: Initialize template in Javascript Context. text = "var a = '{{VAR}}'"; // Escaping not given. AssertCorrectModifiers(TC_JS, text, "VAR:j\n"); text = "var a = '{{VAR:none}}'"; // Variable safe. AssertCorrectModifiers(TC_JS, text, "VAR:none\n"); text = "var a = '{{VAR:j}}'"; // Escaping correct. AssertCorrectModifiers(TC_JS, text, "VAR:j\n"); text = "var a = '{{VAR:h}}'"; // Escaping incorrect. AssertCorrectModifiers(TC_JS, text, "VAR:h:j\n"); text = "var a = '{{VAR:J=number}}'"; // Not considered equiv. AssertCorrectModifiers(TC_JS, text, "VAR:J=number:j\n"); // 2g: Honor any modifiers for BI_SPACE and BI_NEWLINE. text = "{{BI_NEWLINE:j}}"; // An invalid modifier for the context. AssertCorrectModifiers(TC_HTML, text, "BI_NEWLINE:j\n"); text = "{{BI_SPACE:h}}"; // An otherwise valid modifier. AssertCorrectModifiers(TC_HTML, text, "BI_SPACE:h\n"); text = "{{BI_SPACE:x-bla}}"; // Also support custom modifiers. AssertCorrectModifiers(TC_HTML, text, "BI_SPACE:x-bla\n"); // 2h: TC_CSS, TC_XML and TC_JSON text = "H1{margin-{{START_EDGE}}:0;\n text-align:{{END_EDGE}}\n}"; AssertCorrectModifiers(TC_CSS, text, "START_EDGE:c\nEND_EDGE:c\n"); text = "body{background:url('{{URL:U=css}}')}"; // :U=css valid substitute AssertCorrectModifiers(TC_CSS, text, "URL:U=css\n"); text = "body{background:url('{{URL:U=html}}')}"; // Not valid, will add :c. AssertCorrectModifiers(TC_CSS, text, "URL:U=html:c\n"); text = ""; // Correct escaping AssertCorrectModifiers(TC_XML, text, "VAL:xml_escape\n"); text = ""; // XSS equivalent AssertCorrectModifiers(TC_XML, text, "VAL:H=attribute\n"); text = ""; // XSS equivalent AssertCorrectModifiers(TC_XML, text, "VAL:h\n"); text = ""; // Not XSS equivalent AssertCorrectModifiers(TC_XML, text, "VAL:H=pre:xml_escape\n"); text = ""; // Not XSS equivalent AssertCorrectModifiers(TC_XML, text, "VAL:c:xml_escape\n"); text = "{user={{USER:j}}"; // Correct escaping AssertCorrectModifiers(TC_JSON, text, "USER:j\n"); text = "{user={{USER:o}}"; // json_escape is XSS equivalent AssertCorrectModifiers(TC_JSON, text, "USER:o\n"); text = "{user={{USER:h}}"; // but html_escape is not AssertCorrectModifiers(TC_JSON, text, "USER:h:j\n"); // 2i: Variables with XssSafe Custom modifiers are untouched. ASSERT(GOOGLE_NAMESPACE::AddXssSafeModifier("x-test-cm", &GOOGLE_NAMESPACE::html_escape)); text = "Hello {{USER:x-test-cm}}"; // Missing :h AssertCorrectModifiers(TC_HTML, text, "USER:x-test-cm\n"); text = "Hello {{USER:x-test-cm:j}}"; // Extra :j AssertCorrectModifiers(TC_HTML, text, "USER:x-test-cm:j\n"); text = "Hello {{USER:x-test-cm:x-foo}}"; // Non-safe modifier AssertCorrectModifiers(TC_HTML, text, "USER:x-test-cm:x-foo\n"); text = "Hello {{USER:x-foo:x-test-cm}}"; // Non-safe modifier AssertCorrectModifiers(TC_HTML, text, "USER:x-foo:x-test-cm\n"); text = "Hello {{USER:x-test-cm:none}}"; // Complete due to :none AssertCorrectModifiers(TC_HTML, text, "USER:x-test-cm:none\n"); text = "Hello {{USER:h:x-test-cm}}"; // Prior escaping AssertCorrectModifiers(TC_HTML, text, "USER:h:x-test-cm\n"); // 3. Larger test with close to every escaping case. text = "\n" "\n" "

{{TITLE}}

\n" "\n" "
\n" " \n" " \n" "
\n" "
\n" "
\n" "\n" "bla\n" "Goodbye friend {{USER}}!\n\n"; expected_out = "CSS_URL:U=css\n" "COLOR:c\n" "TITLE:h\n" "IMG_URL:U=html\n" "HL:H=attribute\n" "FORM_MSG:h\n" "BG_COLOR:c\n" "MSG_TEXT:j\n" "MOUSE:j\n" // :j also escapes html entities "USER:h\n"; AssertCorrectModifiers(TC_HTML, text, expected_out); } // More "end-to-end" test to ensure that variables are // escaped as expected with auto-escape mode enabled. // Obviously there is a lot more we can test. static void TestVariableWithAutoEscape() { string text, expected_out; TemplateDictionary dict("dict"); string good_url("http://www.google.com/"); string bad_url("javascript:alert();"); text = "hi {{VAR}} lo"; dict.SetValue("VAR", "yo"); AssertCorrectEscaping(TC_HTML, dict, text, "hi <bad>yo lo"); text = "bla"; dict.SetValue("URL", good_url); expected_out = "bla"; AssertCorrectEscaping(TC_HTML, dict, text, expected_out); dict.SetValue("URL", bad_url); expected_out = "bla"; AssertCorrectEscaping(TC_HTML, dict, text, expected_out); text = "
"; dict.SetValue("DISPLAY", "none"); expected_out = "
"; AssertCorrectEscaping(TC_HTML, dict, text, expected_out); // Bad characters are simply removed in CleanseCss. dict.SetValue("URL", "!#none_ "); expected_out = "
"; AssertCorrectEscaping(TC_HTML, dict, text, expected_out); text = ""; dict.SetValue("EVENT", "safe"); expected_out = ""; AssertCorrectEscaping(TC_HTML, dict, text, expected_out); dict.SetValue("EVENT", "f = 'y';"); expected_out = ""; // Check special handling of BI_SPACE and BI_NEWLINE. text = "Hello\n{{BI_SPACE}}bla{{BI_NEWLINE}}foo."; expected_out = "Hello bla\nfoo."; AssertCorrectEscaping(TC_HTML, dict, text, expected_out); // TC_CSS text = "H1{margin-{{EDGE}}:0; text-align:{{BAD_EDGE}}}"; dict.SetValue("EDGE", "left"); dict.SetValue("BAD_EDGE", "$$center()!!"); // Bad chars are removed. AssertCorrectEscaping(TC_CSS, dict, text, "H1{margin-left:0; text-align:center!!}"); // TC_XML and TC_JSON text = "{{DATA}}"; dict.SetValue("DATA", "good-data"); AssertCorrectEscaping(TC_XML, dict, text, "good-data"); dict.SetValue("DATA", "FOO"); AssertCorrectEscaping(TC_XML, dict, text, "<BAD>FOO</BAD>"); text = "{user = \"{{USER}}\"}"; dict.SetValue("USER", "good-user"); AssertCorrectEscaping(TC_JSON, dict, text, "{user = \"good-user\"}"); dict.SetValue("USER", "evil'<>\""); AssertCorrectEscaping(TC_JSON, dict, text, "{user = \"evil\\x27\\x3c\\x3e\\x22\"}"); } // Test that the template initialization fails in auto-escape // mode if the parser failed to parse. static void TestFailedInitWithAutoEscape() { Strip strip = STRIP_WHITESPACE; // Taken from HTML Parser test suite. string bad_html = "\n"; ASSERT(NULL == StringToTemplateWithAutoEscaping(bad_html, strip, TC_HTML)); // Missing quotes around URL, not accepted in URL-taking attributes. bad_html = "bla"; ASSERT(NULL == StringToTemplateWithAutoEscaping(bad_html, strip, TC_HTML)); // Missing quotes around STYLE, not accepted in style-taking attributes. bad_html = "
"; ASSERT(NULL == StringToTemplateWithAutoEscaping(bad_html, strip, TC_HTML)); } static void TestAutoEscaping() { Strip strip = STRIP_WHITESPACE; Template *tpl; string filename; string text; string user = "John<>Doe"; string user_esc = "John<>Doe"; // Positive test cases -- template initialization succeeds. // We also check that modifiers that were missing or given incorrect // have been updated as expected. // TODO(jad): Cut-down redundancy by merging with // TestCorrectModifiersForAutoEscape. text = "{{%AUTOESCAPE context=\"HTML\"}}" // HTML "{{USER:o}}"; ASSERT(tpl = StringToTemplate(text, strip)); string expected_mods = "USER:o:h\nURL:U=html\nCLASS:h:H=attribute\n"; AssertCorrectModifiersInTemplate(tpl, text, expected_mods); text = "{{%AUTOESCAPE context=\"HTML\" state=\"IN_TAG\"}}" // HTML in tag "href=\"{{URL}}\" class={{CLASS:h}} style=\"font:{{COLOR}}\""; ASSERT(tpl = StringToTemplate(text, strip)); expected_mods = "URL:U=html\nCLASS:h:H=attribute\nCOLOR:c\n"; AssertCorrectModifiersInTemplate(tpl, text, expected_mods); text = "{{%AUTOESCAPE context=\"HTML\" state=\"in_tag\"}}" // lowercase ok "href=\"{{URL}}\" class={{CLASS:h}} style=\"font:{{COLOR}}\""; ASSERT(tpl = StringToTemplate(text, strip)); expected_mods = "URL:U=html\nCLASS:h:H=attribute\nCOLOR:c\n"; AssertCorrectModifiersInTemplate(tpl, text, expected_mods); // Repeat the test with trailing HTML that closes the tag. This is // undefined behavior. We test it to ensure the parser does not choke. text += ">HelloSome text"; ASSERT(tpl = StringToTemplate(text, strip)); expected_mods = "URL:U=html\nCLASS:h:H=attribute\nCOLOR:c\n"; AssertCorrectModifiersInTemplate(tpl, text, expected_mods); text = "{{%AUTOESCAPE context=\"JAVASCRIPT\"}}" // JAVASCRIPT "var a = {{A}}; var b = '{{B:h}}';"; ASSERT(tpl = StringToTemplate(text, strip)); expected_mods = "A:J=number\nB:h:j\n"; AssertCorrectModifiersInTemplate(tpl, text, expected_mods); text = "{{%AUTOESCAPE context=\"CSS\"}}" // CSS "body {color:\"{{COLOR}}\"; font-size:{{SIZE:j}}"; ASSERT(tpl = StringToTemplate(text, strip)); expected_mods = "COLOR:c\nSIZE:j:c\n"; AssertCorrectModifiersInTemplate(tpl, text, expected_mods); text = "{{%AUTOESCAPE context=\"JSON\"}}" // JSON "{ 'id': {{ID:j}}, 'value': {{VALUE:h}} }"; ASSERT(tpl = StringToTemplate(text, strip)); expected_mods = "ID:j\nVALUE:h:j\n"; AssertCorrectModifiersInTemplate(tpl, text, expected_mods); text = "{{%AUTOESCAPE context=\"XML\"}}" // XML "{{DATA:h}}"; ASSERT(tpl = StringToTemplate(text, strip)); expected_mods = "VAL:xml_escape\nDATA:h\n"; AssertCorrectModifiersInTemplate(tpl, text, expected_mods); text = "{{%AUTOESCAPE context=\"xml\"}}" // lower-case XML "{{DATA:h}}"; ASSERT(tpl = StringToTemplate(text, strip)); expected_mods = "VAL:xml_escape\nDATA:h\n"; AssertCorrectModifiersInTemplate(tpl, text, expected_mods); text = "{{!bla}}{{%AUTOESCAPE context=\"HTML\"}}"; // after comment ASSERT(tpl = StringToTemplate(text, strip)); text = "{{%AUTOESCAPE context=\"HTML\" state=\"default\"}}"; ASSERT(tpl = StringToTemplate(text, strip)); // adding state // Negative test cases - template initialization fails due to errors // in the marker. Also checks that our parsing is defensive. text = "{{%AUTOESCAPE}}"; // missing context ASSERT((tpl = StringToTemplate(text, strip)) == NULL); text = "{{%AUTOESCAPER context=\"HTML\"}}"; // invalid id ASSERT((tpl = StringToTemplate(text, strip)) == NULL); text = "{{%}}"; // missing id ASSERT((tpl = StringToTemplate(text, strip)) == NULL); text = "{{% }}"; // missing id ASSERT((tpl = StringToTemplate(text, strip)) == NULL); text = "{{% =}}"; // missing id ASSERT((tpl = StringToTemplate(text, strip)) == NULL); text = "{{%AUTOESCAPE =\"HTML\"}}"; // missing name ASSERT((tpl = StringToTemplate(text, strip)) == NULL); text = "{{%AUTOESCAPE foo=\"HTML\"}}"; // bogus name ASSERT((tpl = StringToTemplate(text, strip)) == NULL); text = "{{%AUTOESCAPE =}}"; // lone '=' ASSERT((tpl = StringToTemplate(text, strip)) == NULL); text = "{{%AUTOESCAPE context=HTML}}"; // val not quoted ASSERT((tpl = StringToTemplate(text, strip)) == NULL); text = "{{%AUTOESCAPE context=\"HTML}}"; // no end quotes ASSERT((tpl = StringToTemplate(text, strip)) == NULL); text = "{{%AUTOESCAPE context=\"\\\"HTML\"}}"; // Unescape val ASSERT((tpl = StringToTemplate(text, strip)) == NULL); text = "{{%AUTOESCAPE context=\"\\\"HT\\\"\\\"ML\\\"\"}}"; // more complex ASSERT((tpl = StringToTemplate(text, strip)) == NULL); text = "{{%AUTOESCAPE context=\"\"HTML\"}}"; // Unescape val ASSERT((tpl = StringToTemplate(text, strip)) == NULL); text = "{{%AUTOESCAPE context=\"JAVASCRIPT\" bla}}"; // extra attr ASSERT((tpl = StringToTemplate(text, strip)) == NULL); text = "{{%AUTOESCAPE context=\"JAVASCRIPT\"bla}}"; // invalid value ASSERT((tpl = StringToTemplate(text, strip)) == NULL); text = "{{%AUTOESCAPE context=\"JAVASCRIPT\" foo=bla}}"; // extra attr/val ASSERT((tpl = StringToTemplate(text, strip)) == NULL); text = "{{%AUTOESCAPE context=\"HTML\"}}"; // extra whitesp ASSERT((tpl = StringToTemplate(text, strip)) == NULL); text = "{{%AUTOESCAPE context =\"HTML\"}}"; // extra whitesp ASSERT((tpl = StringToTemplate(text, strip)) == NULL); text = "{{%AUTOESCAPE context= \"HTML\"}}"; // extra whitesp ASSERT((tpl = StringToTemplate(text, strip)) == NULL); text = "{{%AUTOESCAPE context=\"HTML\" }}"; // extra whitesp ASSERT((tpl = StringToTemplate(text, strip)) == NULL); text = "{{%AUTOESCAPE context=\"Xml\"}}"; // mixed-case xml ASSERT((tpl = StringToTemplate(text, strip)) == NULL); text = "{{%AUTOESCAPE context=\"HTML\" state=\"tag\"}}"; // bad state ASSERT((tpl = StringToTemplate(text, strip)) == NULL); text = "{{%AUTOESCAPE context=\"CSS\" state=\"IN_TAG\"}}"; // invalid state ASSERT((tpl = StringToTemplate(text, strip)) == NULL); text = "Hello{{%AUTOESCAPE context=\"HTML\"}}"; // after text ASSERT((tpl = StringToTemplate(text, strip)) == NULL); text = "{{USER}}{{%AUTOESCAPE context=\"HTML\"}}"; // after variable ASSERT((tpl = StringToTemplate(text, strip)) == NULL); text = "{{#SEC}}{{%AUTOESCAPE context=\"HTML\"}}{{/SEC}}"; // not in MAIN ASSERT((tpl = StringToTemplate(text, strip)) == NULL); string kAutoescapeHtmlPragma = "{{%AUTOESCAPE context=\"HTML\"}}"; // Check that Selective Auto-Escape does not auto-escape included templates // unless these are also marked for auto-escape. To attest that, // we check that when no escaping was given in the included template, none // will be applied to it. USER will not get html-escaped. text = kAutoescapeHtmlPragma + "{{>INC}}"; tpl = StringToTemplate(text, strip); ASSERT(tpl); string inc_text = "{{USER}}"; // missing :h escaping. TemplateDictionary dict("dict"); TemplateDictionary *inc_dict = dict.AddIncludeDictionary("INC"); inc_dict->SetFilename(StringToTemplateFile(inc_text)); inc_dict->SetValue("USER", user); AssertExpandIs(tpl, &dict, user, true); // Add AUTOESCAPE pragma to included template and check that it works. inc_text = kAutoescapeHtmlPragma + inc_text; filename = StringToTemplateFile(inc_text); inc_dict->SetFilename(filename); AssertExpandIs(tpl, &dict, user_esc, true); // Check that Selective Auto-Escape works with Template::StringToTemplate. tpl = Template::StringToTemplate(inc_text, strip); ASSERT(tpl); TemplateDictionary dict2("dict2"); dict2.SetValue("USER", user); AssertExpandIs(tpl, &dict2, user_esc, true); delete tpl; // Test that Selective AutoEscape follows included templates: Included // templates 2 and 4 are registered for auto-escaping but not included // templates 1 and 3. Check that only templates 2 and 4 get escaped. text = "Parent: {{USER}}; {{>INCONE}}"; string text_inc1 = "INC1: {{USER1}}; {{>INCTWO}}"; string text_inc2 = kAutoescapeHtmlPragma + "INC2: {{USER2}}; {{>INCTHREE}}"; string text_inc3 = "INC3: {{USER3}}; {{>INCFOUR}}"; string text_inc4 = kAutoescapeHtmlPragma + "INC4: {{USER4}}"; dict.SetValue("USER", user); TemplateDictionary *dict_inc1 = dict.AddIncludeDictionary("INCONE"); dict_inc1->SetFilename(StringToTemplateFile(text_inc1)); dict_inc1->SetValue("USER1", user); TemplateDictionary *dict_inc2 = dict_inc1->AddIncludeDictionary("INCTWO"); filename = StringToTemplateFile(text_inc2); dict_inc2->SetFilename(filename); dict_inc2->SetValue("USER2", user); TemplateDictionary *dict_inc3 = dict_inc2->AddIncludeDictionary("INCTHREE"); dict_inc3->SetFilename(StringToTemplateFile(text_inc3)); dict_inc3->SetValue("USER3", user); TemplateDictionary *dict_inc4 = dict_inc3->AddIncludeDictionary("INCFOUR"); filename = StringToTemplateFile(text_inc4); dict_inc4->SetFilename(filename); dict_inc4->SetValue("USER4", user); tpl = StringToTemplate(text, strip); string expected_out = "Parent: " + user + "; INC1: " + user + "; INC2: " + user_esc + "; INC3: " + user + "; INC4: " + user_esc; AssertExpandIs(tpl, &dict, expected_out, true); // Check that we do not modify template-includes. // Here, xml_escape would have been changed to :h:xml_escape // causing a double-escaping of the USER. text = kAutoescapeHtmlPragma + "{{>INC:xml_escape}}"; inc_text = "{{USER}}"; tpl = StringToTemplate(text, strip); ASSERT(tpl); TemplateDictionary dict3("dict"); inc_dict = dict3.AddIncludeDictionary("INC"); inc_dict->SetFilename(StringToTemplateFile(inc_text)); inc_dict->SetValue("USER", user); AssertExpandIs(tpl, &dict3, user_esc, true); // Test that {{%...}} is a "removable" marker. A related test is // also added to TestStrip(). tpl = StringToTemplate("{{%AUTOESCAPE context=\"HTML\"}}\nText\n Text", STRIP_BLANK_LINES); AssertExpandIs(tpl, &dict, "Text\n Text", true); } static void TestRegisterString() { ASSERT(Template::StringToTemplateCache("file1", "Some text")); Template* tpl = Template::GetTemplate("file1", STRIP_WHITESPACE); ASSERT(tpl); ASSERT(Template::GetTemplate("file1", STRIP_WHITESPACE) == tpl); ASSERT(Template::StringToTemplateCache("file2", "Top {{>INC}}")); TemplateDictionary dict("dict"); string expected = "Some text"; AssertExpandIs(tpl, &dict, expected, true); TemplateDictionary* sub_dict = dict.AddIncludeDictionary("INC"); sub_dict->SetFilename("file1"); tpl = Template::GetTemplate("file2", STRIP_WHITESPACE); expected = "Top Some text"; AssertExpandIs(tpl, &dict, expected, true); } static void TestRegisterStringVsFileCollision() { TemplateDictionary dict("dict"); string filename = StringToTemplateFile("file contents"); Template *tpl = Template::GetTemplate(filename, STRIP_WHITESPACE); string expected = "file contents"; AssertExpandIs(tpl, &dict, expected, true); // Try to register new template contents under the same template key Template::StringToTemplateCache(filename, "string contents"); tpl = Template::GetTemplate(filename, STRIP_WHITESPACE); expected = "file contents"; // contents as of first insert win AssertExpandIs(tpl, &dict, expected, true); StringToFile("new file contents", filename); ASSERT(tpl->ReloadIfChanged()); // should reload - file wins (came first) ASSERT(Template::GetTemplate(filename, STRIP_WHITESPACE) == tpl); expected = "new file contents"; // First registration should win AssertExpandIs(tpl, &dict, expected, true); Template::ReloadAllIfChanged(); ASSERT(Template::GetTemplate(filename, STRIP_WHITESPACE) == tpl); AssertExpandIs(tpl, &dict, expected, true); } private: // This is used by TestAnnotation(). It behaves like // TextTemplateAnnotator but just to test our ability to customize // annotation, and with stateful one, it prefixes each text annotation // with an event (call) count. class CustomTestAnnotator : public GOOGLE_NAMESPACE::TextTemplateAnnotator { public: CustomTestAnnotator() : event_count_(0) { } void Reset() { event_count_ = 0; } virtual void EmitOpenInclude(ExpandEmitter* emitter, const string& value) { EmitTestPrefix(emitter); GOOGLE_NAMESPACE::TextTemplateAnnotator::EmitOpenInclude(emitter, value); } virtual void EmitCloseInclude(ExpandEmitter* emitter) { EmitTestPrefix(emitter); GOOGLE_NAMESPACE::TextTemplateAnnotator::EmitCloseInclude(emitter); } virtual void EmitOpenFile(ExpandEmitter* emitter, const string& value) { EmitTestPrefix(emitter); GOOGLE_NAMESPACE::TextTemplateAnnotator::EmitOpenFile(emitter, value); } virtual void EmitCloseFile(ExpandEmitter* emitter) { EmitTestPrefix(emitter); GOOGLE_NAMESPACE::TextTemplateAnnotator::EmitCloseFile(emitter); } virtual void EmitOpenSection(ExpandEmitter* emitter, const string& value) { EmitTestPrefix(emitter); GOOGLE_NAMESPACE::TextTemplateAnnotator::EmitOpenSection(emitter, value); } virtual void EmitCloseSection(ExpandEmitter* emitter) { EmitTestPrefix(emitter); GOOGLE_NAMESPACE::TextTemplateAnnotator::EmitCloseSection(emitter); } virtual void EmitOpenVariable(ExpandEmitter* emitter, const string& value) { EmitTestPrefix(emitter); GOOGLE_NAMESPACE::TextTemplateAnnotator::EmitOpenVariable(emitter, value); } virtual void EmitCloseVariable(ExpandEmitter* emitter) { EmitTestPrefix(emitter); GOOGLE_NAMESPACE::TextTemplateAnnotator::EmitCloseVariable(emitter); } virtual void EmitFileIsMissing(ExpandEmitter* emitter, const string& value) { EmitTestPrefix(emitter); GOOGLE_NAMESPACE::TextTemplateAnnotator::EmitFileIsMissing(emitter, value); } private: void EmitTestPrefix(ExpandEmitter* emitter) { char buf[128]; snprintf(buf, sizeof(buf), "{{EVENT=%d}}", ++event_count_); emitter->Emit(buf); } int event_count_; }; }; // This tests that StaticTemplateString is sufficiently initialized at // static-initialization time (as opposed to dynamic-initialization // time, which comes later), that we can safely expand templates // during dynamic initialization. This is worth testing, because some // parts of a StaticTemplateString -- especially the hash value, *do* // get computed later at dynamic-initialization time, and we want to // make sure that things still work properly even if we access the // StaticTemplateString before that happens. extern const StaticTemplateString kLateDefine; class DynamicInitializationTemplateExpander { public: DynamicInitializationTemplateExpander() { Template* tpl = Template::StringToTemplate("hi {{VAR}} lo", STRIP_WHITESPACE); TemplateDictionary dict("dict"); dict.SetValue("VAR", TemplateString("short-lived", strlen("short"))); AssertExpandIs(tpl, &dict, "hi short lo", true); dict.SetValue("VAR", kHello); AssertExpandIs(tpl, &dict, "hi Hello lo", true); dict.SetValue("VAR", kLateDefine); AssertExpandIs(tpl, &dict, "hi laterz lo", true); delete tpl; } }; DynamicInitializationTemplateExpander sts_tester; // this runs before main() const StaticTemplateString kLateDefine = STS_INIT(kLateDefine, "laterz"); int main(int argc, char** argv) { CreateOrCleanTestDirAndSetAsTmpdir(FLAGS_test_tmpdir); // This goes first so that future tests don't mess up the filenames TemplateUnittest::TestAnnotation(); TemplateUnittest::CheckWhitelistedVariablesSorted(); TemplateUnittest::TestTemplateExpansionModifier(); TemplateUnittest::TestWeirdSyntax(); TemplateUnittest::TestComment(); TemplateUnittest::TestSetMarkerDelimiters(); TemplateUnittest::TestVariable(); TemplateUnittest::TestVariableWithModifiers(); TemplateUnittest::TestSection(); TemplateUnittest::TestSectionSeparator(); TemplateUnittest::TestInclude(); TemplateUnittest::TestIncludeWithModifiers(); TemplateUnittest::TestRecursiveInclude(); TemplateUnittest::TestInheritence(); TemplateUnittest::TestTemplateString(); TemplateUnittest::TestExpand(); TemplateUnittest::TestExpandTemplate(); TemplateUnittest::TestExpandWithCustomEmitter(); TemplateUnittest::TestTemplateSearchPath(); TemplateUnittest::TestGetTemplate(); TemplateUnittest::TestRegisterString(); TemplateUnittest::TestRegisterStringVsFileCollision(); TemplateUnittest::TestStringCacheKey(); TemplateUnittest::TestStringGetTemplate(); TemplateUnittest::TestStringTemplateInclude(); TemplateUnittest::TestRemoveStringFromTemplateCache(); TemplateUnittest::TestTemplateCache(); TemplateUnittest::TestStrip(); TemplateUnittest::TestReloadIfChanged(); TemplateUnittest::TestTemplateRootDirectory(); TemplateUnittest::TestTemplateNamelist(); TemplateUnittest::TestThreadSafety(); TemplateUnittest::TestCorrectModifiersForAutoEscape(); TemplateUnittest::TestVariableWithAutoEscape(); TemplateUnittest::TestFailedInitWithAutoEscape(); TemplateUnittest::TestAutoEscaping(); printf("DONE\n"); return 0; }