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

* ctemplate: version 0.99 release * Accept . as part of tag/attr names when autoescaping (falmeida) * Optimize javascript template escaping (blount) * Allow <span dir=...> inside :H=snippet modifiers (jdtang) * make_tpl_varnames can write multiple tpls to one file (jad) * Add a few escaping modifier equivalences (jad) * BUGFIX: Fix ReloadAllIfChanged() with path (panicker) * PORTING: Relace tr with more portable sed, in tests (csilvers) * Updated from autoconf 2.64 to autoconf 2.65
2326 lines
103 KiB
C++
2326 lines
103 KiB
C++
// 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 <ctemplate/template.h>
|
|
#include <assert.h> // for assert()
|
|
#if defined(HAVE_PTHREAD) && !defined(NO_THREADS)
|
|
#include <pthread.h> // for pthread_t, pthread_create(), etc
|
|
#endif
|
|
#include <stddef.h> // for size_t
|
|
#include <stdio.h> // for printf(), FILE, snprintf(), fclose(), etc
|
|
#include <stdlib.h> // for exit()
|
|
#include <string.h> // for strcmp(), memchr(), strlen(), strstr()
|
|
#include <sys/types.h> // for mode_t
|
|
#include <time.h> // for time_t, time()
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h> // for link(), unlink()
|
|
#endif
|
|
#include <list> // for list<>::size_type
|
|
#include <vector> // for vector<>
|
|
#include <ctemplate/per_expand_data.h> // for PerExpandData
|
|
#include <ctemplate/template_annotator.h> // for TextTemplateAnnotator
|
|
#include <ctemplate/template_dictionary.h> // for TemplateDictionary
|
|
#include <ctemplate/template_emitter.h> // for ExpandEmitter
|
|
#include <ctemplate/template_enums.h> // for STRIP_WHITESPACE, Strip, etc
|
|
#include <ctemplate/template_modifiers.h> // for AddModifier(), HtmlEscape, etc
|
|
#include <ctemplate/template_namelist.h> // for TemplateNamelist, etc
|
|
#include <ctemplate/template_pathops.h> // for PathJoin(), IsAbspath(), etc
|
|
#include <ctemplate/template_string.h> // 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 <<VAR>> {{lo}}",
|
|
STRIP_WHITESPACE);
|
|
AssertExpandIs(tpl4, &dict, "hi <yo> {{lo}}", true);
|
|
|
|
Template* tpl4b = StringToTemplate("{{=<< >>=}}hi <<VAR>> {{lo}}",
|
|
STRIP_WHITESPACE);
|
|
AssertExpandIs(tpl4b, &dict, "hi yo {{lo}}", true);
|
|
|
|
Template* tpl4c = StringToTemplate("{{=<< <<=}}hi <<VAR<< {{lo}}",
|
|
STRIP_WHITESPACE);
|
|
AssertExpandIs(tpl4c, &dict, "hi yo {{lo}}", true);
|
|
|
|
Template* tpl5 = StringToTemplate("hi {{VAR}} lo\n{{=< >=}}\n"
|
|
"hi {{VAR}} lo\n"
|
|
"hi <VAR> 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"
|
|
" <<! a blank line>> \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("<a href=\"/servlet?param={{VAR:u}}\">",
|
|
STRIP_WHITESPACE);
|
|
AssertExpandIs(tpl, &dict, "<a href=\"/servlet?param=yo%26yo\">", true);
|
|
tpl = StringToTemplate("<a href='/servlet?param={{VAR:url_query_escape}}'>",
|
|
STRIP_WHITESPACE);
|
|
AssertExpandIs(tpl, &dict, "<a href='/servlet?param=yo%26yo'>", true);
|
|
|
|
// Test with multiple URL escaping.
|
|
tpl = StringToTemplate("<a href=\"/servlet?param={{VAR:u:u}}\">",
|
|
STRIP_WHITESPACE);
|
|
AssertExpandIs(tpl, &dict, "<a href=\"/servlet?param=yo%2526yo\">", true);
|
|
|
|
// Test HTML escaping.
|
|
tpl = StringToTemplate("hi {{VAR:h}} lo", STRIP_WHITESPACE);
|
|
AssertExpandIs(tpl, &dict, "hi yo&yo lo", 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", "<b>foo & bar</b>");
|
|
tpl = StringToTemplate("hi {{VAR:H=attribute}} {{URL_VAR:H=url}} "
|
|
"{{SNIPPET_VAR:H=snippet}} lo", STRIP_WHITESPACE);
|
|
AssertExpandIs(tpl, &dict, "hi yo_yo # <b>foo & bar</b> 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 <b>foo & bar</b> 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 <space>
|
|
Template* tpl1 = StringToTemplate("hi {{>INC:h}} bar\n", DO_NOT_STRIP);
|
|
Template* tpl2 = StringToTemplate("hi {{>INC:javascript_escape}} bar\n",
|
|
DO_NOT_STRIP);
|
|
Template* tpl3 = StringToTemplate("hi {{>INC:pre_escape}} bar\n",
|
|
DO_NOT_STRIP);
|
|
Template* tpl4 = StringToTemplate("hi {{>INC:u}} bar\n", DO_NOT_STRIP);
|
|
// 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<not registered>}}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<not registered>}}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<not registered>}}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 modifier2(child_tpl_name2);
|
|
per_expand_data.SetTemplateExpansionModifier(&modifier2);
|
|
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 = "<html>{{>INC}}</html>";
|
|
const string text_inc = "<div>\n<p>\nUser {{USER}}\n</div>";
|
|
const string text_indent = "<html>\n {{>INC}}</html>";
|
|
|
|
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 = "<html><div>\n<p>\nUser John<>Doe\n</div></html>";
|
|
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 =
|
|
"<html>\n"
|
|
" <div>\n"
|
|
" <p>\n"
|
|
" User John<>Doe\n"
|
|
" </div>"
|
|
"</html>";
|
|
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 = "<html>here today...</html>";
|
|
|
|
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", "<bla>"); // Escaped differently by JS and HTML.
|
|
|
|
const string kHtmlPragma("{{%AUTOESCAPE context=\"HTML\"}}\n");
|
|
const string kJsPragma("{{%AUTOESCAPE context=\"JAVASCRIPT\"}}\n");
|
|
const string kHtmltext("<p>{{USER}}"); // some HTML
|
|
const string kHtmltextOther("<span>{{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, "<p><bla>", true); // haven't reloaded
|
|
ASSERT(tpl->ReloadIfChanged()); // true: content changed
|
|
AssertExpandIs(tpl, &dict2, "<p><bla>", true); // reloaded & escaped
|
|
tpl2 = Template::GetTemplate(filename, STRIP_WHITESPACE);
|
|
AssertExpandIs(tpl2, &dict2, "<p><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, "<span><bla>", true); // reloaded
|
|
tpl2 = Template::GetTemplate(filename, STRIP_WHITESPACE);
|
|
AssertExpandIs(tpl2, &dict2, "<span><bla>", true); // same as tpl
|
|
|
|
// Case 3: Change auto-escape context to Javascript.
|
|
StringToFile(kJsPragma + kJstext, filename);
|
|
AssertExpandIs(tpl, &dict2, "<span><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, "<p><bla>", true); // reloaded => not escaped
|
|
tpl2 = Template::GetTemplate(filename, STRIP_WHITESPACE);
|
|
AssertExpandIs(tpl2, &dict2, "<p><bla>", 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<const char*>(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<ThreadReturn*>(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 = "<a href=\"{{URL}}\">bla</a>";
|
|
AssertCorrectModifiers(TC_HTML, text, "URL:U=html\n");
|
|
text = "<script src=\"{{URL}}\"></script>";
|
|
AssertCorrectModifiers(TC_HTML, text, "URL:U=html\n");
|
|
text = "<img src=\"{{URL}}\">";
|
|
AssertCorrectModifiers(TC_HTML, text, "URL:U=html\n");
|
|
// URL fragment only so just html_escape.
|
|
text = "<img src=\"/bla?q={{QUERY}}\">";
|
|
AssertCorrectModifiers(TC_HTML, text, "QUERY:h\n");
|
|
// URL fragment not quoted, so url_escape.
|
|
text = "<img src=/bla?q={{QUERY}}>";
|
|
AssertCorrectModifiers(TC_HTML, text, "QUERY:u\n");
|
|
|
|
text = "<br class=\"{{CLASS}}\">";
|
|
AssertCorrectModifiers(TC_HTML, text, "CLASS:h\n");
|
|
text = "<br class={{CLASS}}>";
|
|
AssertCorrectModifiers(TC_HTML, text, "CLASS:H=attribute\n");
|
|
text = "<br {{CLASS}}>"; // CLASS here is name/value pair.
|
|
AssertCorrectModifiers(TC_HTML, text, "CLASS:H=attribute\n");
|
|
text = "<br style=\"display:{{DISPLAY}}\">"; // Style attribute.
|
|
AssertCorrectModifiers(TC_HTML, text, "DISPLAY:c\n");
|
|
|
|
// Content inside a style tag should have :c regardless of quoting.
|
|
text = "<style>color:{{COLOR}}; font:\"{{FONT}}\"</style>";
|
|
AssertCorrectModifiers(TC_HTML, text, "COLOR:c\nFONT:c\n");
|
|
|
|
// onMouseEvent and onKeyUp accept javascript.
|
|
text = "<a href=\"url\" onkeyup=\"doX('{{ID}}');\">"; // ID quoted
|
|
AssertCorrectModifiers(TC_HTML, text, "ID:j\n");
|
|
text = "<a href=\"url\" onclick=\"doX({{ID}});\">"; // ID not quoted
|
|
AssertCorrectModifiers(TC_HTML, text, "ID:J=number\n");
|
|
text = "<a href=\"url\" onclick=\"'{{ID}}'\">"; // not common
|
|
AssertCorrectModifiers(TC_HTML, text, "ID:j\n");
|
|
// If ID is javascript code, J=number will break it, for good and bad.
|
|
text = "<a href=\"url\" onclick=\"{{ID}}\">";
|
|
AssertCorrectModifiers(TC_HTML, text, "ID:J=number\n");
|
|
|
|
// Target just needs html escaping.
|
|
text = "<a href=\"url\" target=\"{{TARGET}}\">";
|
|
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 = "<img class={{CLASS}} src=/bla?q={{QUERY}}>";
|
|
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 = "<a href=\"javascript:foo('{{VAR}}')>bla</a>";
|
|
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 = "<a href=/bla{{BI_SPACE}}style=\"{{VAR}}\">text</a>";
|
|
AssertCorrectModifiers(TC_HTML, text, "BI_SPACE\nVAR:c\n");
|
|
|
|
// XML and JSON modes.
|
|
text = "<PARAM name=\"{{VAL}}\">{{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 = "<a href=\"url\" onkeyup=\"doX('{{ID:none}}');\">";
|
|
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 = "<a href=\"{{URL:U=html}}\">bla</a>";
|
|
AssertCorrectModifiers(TC_HTML, text, "URL:U=html\n");
|
|
text = "<a href=\"/bla?q={{QUERY:h}}\">bla</a>"; // :h is valid.
|
|
AssertCorrectModifiers(TC_HTML, text, "QUERY:h\n");
|
|
text = "<a href=\"/bla?q={{QUERY:u}}\">bla</a>"; // so is :u.
|
|
AssertCorrectModifiers(TC_HTML, text, "QUERY:u\n");
|
|
text = "<a href=\"url\" onclick=\"doX('{{ID:j}}');\">";
|
|
AssertCorrectModifiers(TC_HTML, text, "ID:j\n");
|
|
text = "<a href=\"url\" onclick=\"doX({{ID:J=number}});\">";
|
|
AssertCorrectModifiers(TC_HTML, text, "ID:J=number\n");
|
|
text = "<style>@import url(\"{{URL:U=css}}\")</style>"; // 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 = "<script>var a = \"{{VAR:h}}\";</script>"; // Missing :j
|
|
AssertCorrectModifiers(TC_HTML, text, "VAR:h:j\n");
|
|
text = "<script>var a = \"{{VAR:j:h:j}}\";</script>"; // Extra :h:j
|
|
AssertCorrectModifiers(TC_HTML, text, "VAR:j:h:j\n");
|
|
text = "<a href=\"url\" onclick=\"doX({{ID:j}});\">"; // 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 = "<a href=\"{{URL:H=url}}\">bla</a>";
|
|
AssertCorrectModifiers(TC_HTML, text, "URL:H=url\n");
|
|
text = "<a href=\"{{URL:U=html}}\">bla</a>";
|
|
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 = "<PARAM name=\"{{VAL:xml_escape}}\">"; // Correct escaping
|
|
AssertCorrectModifiers(TC_XML, text, "VAL:xml_escape\n");
|
|
text = "<PARAM name=\"{{VAL:H=attribute}}\">"; // XSS equivalent
|
|
AssertCorrectModifiers(TC_XML, text, "VAL:H=attribute\n");
|
|
text = "<PARAM name=\"{{VAL:h}}\">"; // XSS equivalent
|
|
AssertCorrectModifiers(TC_XML, text, "VAL:h\n");
|
|
text = "<PARAM name=\"{{VAL:H=pre}}\">"; // Not XSS equivalent
|
|
AssertCorrectModifiers(TC_XML, text, "VAL:H=pre:xml_escape\n");
|
|
text = "<PARAM name=\"{{VAL:c}}\">"; // 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 = "<html><head>\n"
|
|
"<style>\n"
|
|
"@import url(\"{{CSS_URL:U=css}}\");\n"
|
|
"color:{{COLOR}}</style></head><body>\n"
|
|
"<h1>{{TITLE}}</h1>\n"
|
|
"<img src=\"{{IMG_URL}}\">\n"
|
|
"<form action=\"/search\">\n"
|
|
" <input name=\"hl\" value={{HL}}>\n"
|
|
" <input name=\"m\" value=\"{{FORM_MSG}}\">\n"
|
|
"</form>\n"
|
|
"<div style=\"background:{{BG_COLOR}}\">\n"
|
|
"</div>\n"
|
|
"<script>\n"
|
|
" var msg_text = '{{MSG_TEXT}}';\n"
|
|
"</script>\n"
|
|
"<a href=\"url\" onmouseover=\"'{{MOUSE}}'\">bla</a>\n"
|
|
"Goodbye friend {{USER}}!\n</body></html>\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", "<bad>yo");
|
|
AssertCorrectEscaping(TC_HTML, dict, text, "hi <bad>yo lo");
|
|
|
|
text = "<a href=\"{{URL}}\">bla</a>";
|
|
dict.SetValue("URL", good_url);
|
|
expected_out = "<a href=\"" + good_url + "\">bla</a>";
|
|
AssertCorrectEscaping(TC_HTML, dict, text, expected_out);
|
|
dict.SetValue("URL", bad_url);
|
|
expected_out = "<a href=\"#\">bla</a>";
|
|
AssertCorrectEscaping(TC_HTML, dict, text, expected_out);
|
|
|
|
text = "<br style=\"display:{{DISPLAY}}\">";
|
|
dict.SetValue("DISPLAY", "none");
|
|
expected_out = "<br style=\"display:none\">";
|
|
AssertCorrectEscaping(TC_HTML, dict, text, expected_out);
|
|
// Bad characters are simply removed in CleanseCss.
|
|
dict.SetValue("URL", "!#none_ ");
|
|
expected_out = "<br style=\"display:none\">";
|
|
AssertCorrectEscaping(TC_HTML, dict, text, expected_out);
|
|
|
|
text = "<a href=\"url\" onkeyup=\"'{{EVENT}}'\">";
|
|
dict.SetValue("EVENT", "safe");
|
|
expected_out = "<a href=\"url\" onkeyup=\"'safe'\">";
|
|
AssertCorrectEscaping(TC_HTML, dict, text, expected_out);
|
|
dict.SetValue("EVENT", "f = 'y';");
|
|
expected_out = "<a href=\"url\" onkeyup=\"'f \\x3d \\x27y\\x27;'\">";
|
|
|
|
// 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 = "<Q>{{DATA}}</Q>";
|
|
dict.SetValue("DATA", "good-data");
|
|
AssertCorrectEscaping(TC_XML, dict, text, "<Q>good-data</Q>");
|
|
dict.SetValue("DATA", "<BAD>FOO</BAD>");
|
|
AssertCorrectEscaping(TC_XML, dict, text,
|
|
"<Q><BAD>FOO</BAD></Q>");
|
|
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 = "<a href='http://www.google.com' ''>\n";
|
|
ASSERT(NULL == StringToTemplateWithAutoEscaping(bad_html, strip, TC_HTML));
|
|
|
|
// Missing quotes around URL, not accepted in URL-taking attributes.
|
|
bad_html = "<a href={{URL}}>bla</a>";
|
|
ASSERT(NULL == StringToTemplateWithAutoEscaping(bad_html, strip, TC_HTML));
|
|
|
|
// Missing quotes around STYLE, not accepted in style-taking attributes.
|
|
bad_html = "<div style={{STYLE}}>";
|
|
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}}<a href=\"{{URL}}\" class={{CLASS:h}}</a>";
|
|
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 += ">Hello</a><span>Some text</span></body></html>";
|
|
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
|
|
"<PARAM name=\"{{VAL}}\">{{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
|
|
"<PARAM name=\"{{VAL}}\">{{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;
|
|
}
|