blob: 8d615c0ce8f076a790b5c904ea64de41f25e3f3f [file] [log] [blame]
Brian Silverman70325d62015-09-20 17:00:43 -04001// Copyright (c) 2005, Google Inc.
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are
6// met:
7//
8// * Redistributions of source code must retain the above copyright
9// notice, this list of conditions and the following disclaimer.
10// * Redistributions in binary form must reproduce the above
11// copyright notice, this list of conditions and the following disclaimer
12// in the documentation and/or other materials provided with the
13// distribution.
14// * Neither the name of Google Inc. nor the names of its
15// contributors may be used to endorse or promote products derived from
16// this software without specific prior written permission.
17//
18// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30// ---
31// Author: csilvers@google.com (Craig Silverstein)
32
33#include "config_for_unittests.h"
34#include <ctemplate/template.h>
35#include <assert.h> // for assert()
36#if defined(HAVE_PTHREAD) && !defined(NO_THREADS)
37# include <pthread.h>
38#endif // for pthread_t, pthread_create(), etc
39#include <stddef.h> // for size_t
40#include <stdio.h> // for printf(), FILE, snprintf(), fclose(), etc
41#include <stdlib.h> // for exit()
42#include <string.h> // for strcmp(), memchr(), strlen(), strstr()
43#include <sys/types.h> // for mode_t
44#include <time.h> // for time_t, time()
45#ifdef HAVE_UNISTD_H
46# include <unistd.h>
47#endif // for link(), unlink()
48#include <list> // for list<>::size_type
49#include <vector> // for vector<>
50#include <ctemplate/per_expand_data.h> // for PerExpandData
51#include <ctemplate/template_annotator.h> // for TextTemplateAnnotator
52#include <ctemplate/template_dictionary.h> // for TemplateDictionary
53#include <ctemplate/template_emitter.h> // for ExpandEmitter
54#include <ctemplate/template_enums.h> // for STRIP_WHITESPACE, Strip, etc
55#include <ctemplate/template_modifiers.h> // for AddModifier(), HtmlEscape, etc
56#include <ctemplate/template_namelist.h> // for TemplateNamelist, etc
57#include <ctemplate/template_pathops.h> // for PathJoin(), IsAbspath(), etc
58#include <ctemplate/template_string.h> // for TemplateString, StringHash, etc
59#include "tests/template_test_util.h" // for StringToTemplate(), etc
60#include "base/util.h"
61TEST_INIT // defines RUN_ALL_TESTS()
62
63using std::vector;
64using std::string;
65using GOOGLE_NAMESPACE::FLAGS_test_tmpdir;
66
67using GOOGLE_NAMESPACE::AssertExpandIs;
68using GOOGLE_NAMESPACE::AssertExpandWithDataIs;
69using GOOGLE_NAMESPACE::CreateOrCleanTestDir;
70using GOOGLE_NAMESPACE::CreateOrCleanTestDirAndSetAsTmpdir;
71using GOOGLE_NAMESPACE::DO_NOT_STRIP;
72using GOOGLE_NAMESPACE::ExpandEmitter;
73using GOOGLE_NAMESPACE::IsAbspath;
74using GOOGLE_NAMESPACE::Now;
75using GOOGLE_NAMESPACE::PathJoin;
76using GOOGLE_NAMESPACE::PerExpandData;
77using GOOGLE_NAMESPACE::STRIP_BLANK_LINES;
78using GOOGLE_NAMESPACE::STRIP_WHITESPACE;
79using GOOGLE_NAMESPACE::StaticTemplateString;
80using GOOGLE_NAMESPACE::StringToFile;
81using GOOGLE_NAMESPACE::StringToTemplate;
82using GOOGLE_NAMESPACE::StringToTemplateFile;
83using GOOGLE_NAMESPACE::Strip;
84using GOOGLE_NAMESPACE::TC_CSS;
85using GOOGLE_NAMESPACE::TC_HTML;
86using GOOGLE_NAMESPACE::TC_JS;
87using GOOGLE_NAMESPACE::TC_JSON;
88using GOOGLE_NAMESPACE::TC_MANUAL;
89using GOOGLE_NAMESPACE::TC_UNUSED;
90using GOOGLE_NAMESPACE::TC_XML;
91using GOOGLE_NAMESPACE::Template;
92using GOOGLE_NAMESPACE::TemplateContext;
93using GOOGLE_NAMESPACE::TemplateDictionary;
94using GOOGLE_NAMESPACE::TemplateNamelist;
95using GOOGLE_NAMESPACE::TemplateString;
96using GOOGLE_NAMESPACE::kRootdir;
97
98using GOOGLE_NAMESPACE::ExpandTemplate;
99using GOOGLE_NAMESPACE::ExpandWithData;
100using GOOGLE_NAMESPACE::StringToTemplateCache;
101
102static const StaticTemplateString kHello = STS_INIT(kHello, "Hello");
103static const StaticTemplateString kWorld = STS_INIT(kWorld, "World");
104
105static const char* kPragmaHtml = "{{%AUTOESCAPE context=\"HTML\"}}\n";
106static const char* kPragmaJs = "{{%AUTOESCAPE context=\"JAVASCRIPT\"}}\n";
107static const char* kPragmaCss = "{{%AUTOESCAPE context=\"CSS\"}}\n";
108static const char* kPragmaXml = "{{%AUTOESCAPE context=\"XML\"}}\n";
109static const char* kPragmaJson = "{{%AUTOESCAPE context=\"JSON\"}}\n";
110
111// How many threads to use for our threading test.
112// This is a #define instead of a const int so we can use it in array-sizes
113// even on c++ compilers that don't support var-length arrays.
114#define kNumThreads 10
115
116#define PFATAL(s) do { perror(s); exit(1); } while (0)
117
118// TODO(csilvers): rewrite to be more gunit-like: use expectations
119// instead of asserts, and move assert-checking out of helper routines
120// and into tests proper. Ideally, replace AssertExpandIs() with
121// VerifyExpandIs().
122#define ASSERT(cond) do { \
123 if (!(cond)) { \
124 printf("ASSERT FAILED, line %d: %s\n", __LINE__, #cond); \
125 assert(cond); \
126 exit(1); \
127 } \
128} while (0)
129
130#define ASSERT_STREQ_EXCEPT(a, b, except) ASSERT(StreqExcept(a, b, except))
131#define ASSERT_STREQ(a, b) ASSERT(strcmp(a, b) == 0)
132#define ASSERT_NOT_STREQ(a, b) ASSERT(strcmp(a, b) != 0)
133#define ASSERT_STREQ_VERBOSE(a, b, c) ASSERT(StrEqVerbose(a, b, c))
134#define ASSERT_INTEQ(a, b) ASSERT(IntEqVerbose(a, b))
135
136namespace {
137
138// First, (conceptually) remove all chars in "except" from both a and b.
139// Then return true iff munged_a == munged_b.
140bool StreqExcept(const char* a, const char* b, const char* except) {
141 const char* pa = a, *pb = b;
142 const size_t exceptlen = strlen(except);
143 while (1) {
144 // Use memchr isntead of strchr because memchr(foo, '\0') always fails
145 while (memchr(except, *pa, exceptlen)) pa++; // ignore "except" chars in a
146 while (memchr(except, *pb, exceptlen)) pb++; // ignore "except" chars in b
147 if ((*pa == '\0') && (*pb == '\0'))
148 return true;
149 if (*pa++ != *pb++) // includes case where one is at \0
150 return false;
151 }
152}
153
154// If a and b do not match, print their values and that of text
155// and return false.
156bool StrEqVerbose(const string& a, const string& b,
157 const string& text) {
158 if (a != b) {
159 printf("EXPECTED: %s\n", a.c_str());
160 printf("ACTUAL: %s\n", b.c_str());
161 printf("TEXT: %s\n", text.c_str());
162 return false;
163 }
164 return true;
165}
166
167bool IntEqVerbose(int a, int b) {
168 if (a != b) {
169 printf("EXPECTED: %d\n", a);
170 printf("ACTUAL: %d\n", b);
171 return false;
172 }
173 return true;
174}
175
176// This test emitter writes to a string, but writes X's of the right
177// length, rather than the actual content passed in.
178class SizeofEmitter : public ExpandEmitter {
179 string* const outbuf_;
180 public:
181 SizeofEmitter(string* outbuf) : outbuf_(outbuf) {}
182 virtual void Emit(char c) { Emit(&c, 1); }
183 virtual void Emit(const string& s) { Emit(s.data(), s.length()); }
184 virtual void Emit(const char* s) { Emit(s, strlen(s)); }
185 virtual void Emit(const char*, size_t slen) { outbuf_->append(slen, 'X'); }
186};
187
188} // unnamed namespace
189
190RegisterTemplateFilename(VALID1_FN, "template_unittest_test_valid1.in");
191RegisterTemplateFilename(INVALID1_FN, "template_unittest_test_invalid1.in");
192RegisterTemplateFilename(INVALID2_FN, "template_unittest_test_invalid2.in");
193RegisterTemplateFilename(NONEXISTENT_FN, "nonexistent__file.tpl");
194
195// Returns the proper AUTOESCAPE pragma that corresponds to the
196// given TemplateContext.
197static string GetPragmaForContext(TemplateContext context) {
198 switch(context) {
199 case TC_HTML:
200 return kPragmaHtml;
201 case TC_JS:
202 return kPragmaJs;
203 case TC_CSS:
204 return kPragmaCss;
205 case TC_JSON:
206 return kPragmaJson;
207 case TC_XML:
208 return kPragmaXml;
209 case TC_MANUAL:
210 return ""; // No AUTOESCAPE pragma.
211 case TC_UNUSED:
212 ASSERT(false); // Developer error, this TC is not to be used.
213 }
214 ASSERT(false); // Developer error - invalid TemplateContext.
215 return "";
216}
217
218// This writes s to a file with the AUTOESCAPE pragma corresponding
219// to the given TemplateContext and then loads it into a template object.
220static Template* StringToTemplateWithAutoEscaping(const string& s,
221 Strip strip,
222 TemplateContext context) {
223 string text = GetPragmaForContext(context) + s;
224 return Template::GetTemplate(StringToTemplateFile(text), strip);
225}
226
227// A helper method used by TestCorrectModifiersForAutoEscape.
228// Populates out with lines of the form:
229// VARNAME:mod1[=val1][:mod2[=val2]]...\n from the dump of the template
230// and compares against the expected string.
231static void AssertCorrectModifiersInTemplate(Template* tpl,
232 const string& text,
233 const string& expected_out) {
234 ASSERT(tpl);
235 string dump_out, out;
236 tpl->DumpToString("bogus_filename", &dump_out);
237 string::size_type i, j;
238 i = 0;
239 while ((i = dump_out.find("Variable Node: ", i)) != string::npos) {
240 i += strlen("Variable Node: ");
241 j = dump_out.find("\n", i);
242 out.append(dump_out.substr(i, j - i)); // should be safe.
243 out.append("\n");
244 }
245 ASSERT_STREQ_VERBOSE(expected_out, out, text);
246}
247
248// Wrapper on top of AssertCorrectModifiersInTemplate which first
249// obtains a template from the given contents and template context.
250static void AssertCorrectModifiers(TemplateContext template_type,
251 const string& text,
252 const string& expected_out) {
253 Strip strip = STRIP_WHITESPACE;
254 Template *tpl = StringToTemplateWithAutoEscaping(text, strip, template_type);
255 AssertCorrectModifiersInTemplate(tpl, text, expected_out);
256}
257
258// A helper method used by TestCorrectModifiersForAutoEscape.
259// Initializes the template in the Auto Escape mode with the
260// given TemplateContext, expands it with the given dictionary
261// and checks that the output matches the expected value.
262static void AssertCorrectEscaping(TemplateContext template_type,
263 const TemplateDictionary& dict,
264 const string& text,
265 const string& expected_out) {
266 Strip strip = STRIP_WHITESPACE;
267 Template *tpl = StringToTemplateWithAutoEscaping(text, strip, template_type);
268 string outstring;
269 tpl->Expand(&outstring, &dict);
270 ASSERT_STREQ_VERBOSE(expected_out, outstring, text);
271}
272
273class DynamicModifier : public GOOGLE_NAMESPACE::TemplateModifier {
274 public:
275 void Modify(const char* in, size_t inlen,
276 const PerExpandData* per_expand_data,
277 ExpandEmitter* outbuf, const string& arg) const {
278 assert(arg.empty()); // we don't take an argument
279 assert(per_expand_data);
280 const char* value = per_expand_data->LookupForModifiersAsString("value");
281 if (value)
282 outbuf->Emit(value);
283 }
284};
285
286class EmphasizeTemplateModifier : public GOOGLE_NAMESPACE::TemplateModifier {
287 public:
288 EmphasizeTemplateModifier(const string& match)
289 : match_(match) {
290 }
291
292 bool MightModify(const PerExpandData* per_expand_data,
293 const string& arg) const {
294 return strstr(arg.c_str(), match_.c_str());
295 }
296
297 void Modify(const char* in, size_t inlen,
298 const PerExpandData* per_expand_data,
299 ExpandEmitter* outbuf, const string& arg) const {
300 outbuf->Emit(">>");
301 outbuf->Emit(in, inlen);
302 outbuf->Emit("<<");
303 }
304
305 private:
306 string match_;
307};
308
309// This is used by TestAnnotation(). It behaves like
310// TextTemplateAnnotator but just to test our ability to customize
311// annotation, and with stateful one, it prefixes each text annotation
312// with an event (call) count.
313class CustomTestAnnotator : public GOOGLE_NAMESPACE::TextTemplateAnnotator {
314 public:
315 CustomTestAnnotator() : event_count_(0) { }
316 void Reset() { event_count_ = 0; }
317
318 virtual void EmitOpenInclude(ExpandEmitter* emitter, const string& value) {
319 EmitTestPrefix(emitter);
320 GOOGLE_NAMESPACE::TextTemplateAnnotator::EmitOpenInclude(emitter, value);
321 }
322 virtual void EmitCloseInclude(ExpandEmitter* emitter) {
323 EmitTestPrefix(emitter);
324 GOOGLE_NAMESPACE::TextTemplateAnnotator::EmitCloseInclude(emitter);
325 }
326 virtual void EmitOpenFile(ExpandEmitter* emitter, const string& value) {
327 EmitTestPrefix(emitter);
328 GOOGLE_NAMESPACE::TextTemplateAnnotator::EmitOpenFile(emitter, value);
329 }
330 virtual void EmitCloseFile(ExpandEmitter* emitter) {
331 EmitTestPrefix(emitter);
332 GOOGLE_NAMESPACE::TextTemplateAnnotator::EmitCloseFile(emitter);
333 }
334 virtual void EmitOpenSection(ExpandEmitter* emitter, const string& value) {
335 EmitTestPrefix(emitter);
336 GOOGLE_NAMESPACE::TextTemplateAnnotator::EmitOpenSection(emitter, value);
337 }
338 virtual void EmitCloseSection(ExpandEmitter* emitter) {
339 EmitTestPrefix(emitter);
340 GOOGLE_NAMESPACE::TextTemplateAnnotator::EmitCloseSection(emitter);
341 }
342 virtual void EmitOpenVariable(ExpandEmitter* emitter, const string& value) {
343 EmitTestPrefix(emitter);
344 GOOGLE_NAMESPACE::TextTemplateAnnotator::EmitOpenVariable(emitter, value);
345 }
346 virtual void EmitCloseVariable(ExpandEmitter* emitter) {
347 EmitTestPrefix(emitter);
348 GOOGLE_NAMESPACE::TextTemplateAnnotator::EmitCloseVariable(emitter);
349 }
350 virtual void EmitFileIsMissing(ExpandEmitter* emitter,
351 const string& value) {
352 EmitTestPrefix(emitter);
353 GOOGLE_NAMESPACE::TextTemplateAnnotator::EmitFileIsMissing(emitter, value);
354 }
355
356 private:
357 void EmitTestPrefix(ExpandEmitter* emitter) {
358 char buf[128];
359 snprintf(buf, sizeof(buf), "{{EVENT=%d}}", ++event_count_);
360 emitter->Emit(buf);
361 }
362 int event_count_;
363 DISALLOW_COPY_AND_ASSIGN(CustomTestAnnotator);
364};
365
366class TemplateForTest : public Template {
367 public:
368 using Template::kSafeWhitelistedVariables;
369 using Template::kNumSafeWhitelistedVariables;
370 private:
371 // This quiets gcc3, which otherwise complains: "base `Template'
372 // with only non-default constructor in class without a constructor".
373 TemplateForTest();
374};
375
376// Tests annotation, in particular inheriting annotation among children
377// This should be called first, so the filenames don't change as we add
378// more tests.
379static void TestAnnotation() {
380 string incname = StringToTemplateFile("include {{#ISEC}}file{{/ISEC}}\n");
381 string incname2 = StringToTemplateFile("include #2\n");
382 Template* tpl = StringToTemplate(
383 "boo!\n{{>INC}}\nhi {{#SEC}}lo{{#SUBSEC}}jo{{/SUBSEC}}{{/SEC}} bar "
384 "{{VAR:x-foo}}",
385 DO_NOT_STRIP);
386 TemplateDictionary dict("dict");
387 PerExpandData per_expand_data;
388
389 dict.ShowSection("SEC");
390 TemplateDictionary* incdict = dict.AddIncludeDictionary("INC");
391 incdict->SetFilename(incname);
392 incdict->ShowSection("ISEC");
393 dict.AddIncludeDictionary("INC")->SetFilename(incname2);
394 dict.SetValue("VAR", "var");
395
396 // This string is equivalent to "/template." (at least on unix)
397 string slash_tpl(PathJoin(kRootdir, "template."));
398 per_expand_data.SetAnnotateOutput("");
399 char expected[10240]; // 10k should be big enough!
400 snprintf(expected, sizeof(expected),
401 "{{#FILE=%s003}}{{#SEC=__{{MAIN}}__}}boo!\n"
402 "{{#INC=INC}}{{#FILE=%s001}}"
403 "{{#SEC=__{{MAIN}}__}}include {{#SEC=ISEC}}file{{/SEC}}\n"
404 "{{/SEC}}{{/FILE}}{{/INC}}"
405 "{{#INC=INC}}{{#FILE=%s002}}"
406 "{{#SEC=__{{MAIN}}__}}include #2\n{{/SEC}}{{/FILE}}{{/INC}}"
407 "\nhi {{#SEC=SEC}}lo{{/SEC}} bar "
408 "{{#VAR=VAR:x-foo<not registered>}}var{{/VAR}}{{/SEC}}{{/FILE}}",
409 (FLAGS_test_tmpdir + slash_tpl).c_str(),
410 (FLAGS_test_tmpdir + slash_tpl).c_str(),
411 (FLAGS_test_tmpdir + slash_tpl).c_str());
412 AssertExpandWithDataIs(tpl, &dict, &per_expand_data, expected, true);
413
414 // Test ability to set custom annotator.
415 CustomTestAnnotator custom_annotator;
416 per_expand_data.SetAnnotator(&custom_annotator);
417 snprintf(expected, sizeof(expected),
418 "{{EVENT=1}}{{#FILE=%s003}}"
419 "{{EVENT=2}}{{#SEC=__{{MAIN}}__}}boo!\n"
420 "{{EVENT=3}}{{#INC=INC}}"
421 "{{EVENT=4}}{{#FILE=%s001}}"
422 "{{EVENT=5}}{{#SEC=__{{MAIN}}__}}include "
423 "{{EVENT=6}}{{#SEC=ISEC}}file"
424 "{{EVENT=7}}{{/SEC}}\n"
425 "{{EVENT=8}}{{/SEC}}"
426 "{{EVENT=9}}{{/FILE}}"
427 "{{EVENT=10}}{{/INC}}"
428 "{{EVENT=11}}{{#INC=INC}}"
429 "{{EVENT=12}}{{#FILE=%s002}}"
430 "{{EVENT=13}}{{#SEC=__{{MAIN}}__}}include #2\n"
431 "{{EVENT=14}}{{/SEC}}"
432 "{{EVENT=15}}{{/FILE}}"
433 "{{EVENT=16}}{{/INC}}\nhi "
434 "{{EVENT=17}}{{#SEC=SEC}}lo"
435 "{{EVENT=18}}{{/SEC}} bar "
436 "{{EVENT=19}}{{#VAR=VAR:x-foo<not registered>}}var"
437 "{{EVENT=20}}{{/VAR}}"
438 "{{EVENT=21}}{{/SEC}}"
439 "{{EVENT=22}}{{/FILE}}",
440 (FLAGS_test_tmpdir + slash_tpl).c_str(),
441 (FLAGS_test_tmpdir + slash_tpl).c_str(),
442 (FLAGS_test_tmpdir + slash_tpl).c_str());
443 // We can't use AssertExpandWithDataIs() on our deliberately stateful
444 // test annotator because it internally does a second expansion
445 // assuming no state change between calls.
446 string custom_outstring;
447 ASSERT(tpl->ExpandWithData(&custom_outstring, &dict, &per_expand_data));
448 ASSERT_STREQ(custom_outstring.c_str(), expected);
449
450 // Unset annotator and continue with next test as test of ability
451 // to revert to built-in annotator.
452 per_expand_data.SetAnnotator(NULL);
453
454 per_expand_data.SetAnnotateOutput(slash_tpl.c_str());
455 snprintf(expected, sizeof(expected),
456 "{{#FILE=%s003}}{{#SEC=__{{MAIN}}__}}boo!\n"
457 "{{#INC=INC}}{{#FILE=%s001}}"
458 "{{#SEC=__{{MAIN}}__}}include {{#SEC=ISEC}}file{{/SEC}}\n"
459 "{{/SEC}}{{/FILE}}{{/INC}}"
460 "{{#INC=INC}}{{#FILE=%s002}}"
461 "{{#SEC=__{{MAIN}}__}}include #2\n{{/SEC}}{{/FILE}}{{/INC}}"
462 "\nhi {{#SEC=SEC}}lo{{/SEC}} bar "
463 "{{#VAR=VAR:x-foo<not registered>}}var{{/VAR}}{{/SEC}}{{/FILE}}",
464 (slash_tpl).c_str(),
465 (slash_tpl).c_str(),
466 (slash_tpl).c_str());
467 AssertExpandWithDataIs(tpl, &dict, &per_expand_data, expected, true);
468
469 per_expand_data.SetAnnotateOutput(NULL); // should turn off annotations
470 AssertExpandWithDataIs(tpl, &dict, &per_expand_data,
471 "boo!\ninclude file\ninclude #2\n\nhi lo bar var",
472 true);
473
474 // Test that even if we set an annotator we shouldn't get annotation
475 // if it is not turned on with SetAnnotateOutput().
476 per_expand_data.SetAnnotator(&custom_annotator);
477 AssertExpandWithDataIs(tpl, &dict, &per_expand_data,
478 "boo!\ninclude file\ninclude #2\n\nhi lo bar var",
479 true);
480
481 // Test annotation of "missing include" condition.
482 Template* one_inc_tpl =
483 StringToTemplate("File contents: {{>INC}}\n", DO_NOT_STRIP);
484 TemplateDictionary dict_missing_file("dict_with_missing_file");
485 dict_missing_file.AddIncludeDictionary("INC")->SetFilename("missing.tpl");
486
487 per_expand_data.SetAnnotateOutput("");
488 per_expand_data.SetAnnotator(NULL);
489 snprintf(expected, sizeof(expected),
490 "{{#FILE=%s004}}{{#SEC=__{{MAIN}}__}}File contents: "
491 "{{#INC=INC}}{{MISSING_FILE=missing.tpl}}{{/INC}}\n"
492 "{{/SEC}}{{/FILE}}",
493 (FLAGS_test_tmpdir + slash_tpl).c_str());
494 // We expect a false return value because of the missing file.
495 AssertExpandWithDataIs(one_inc_tpl, &dict_missing_file, &per_expand_data,
496 expected, false);
497
498 // Same missing include test with custom annotator
499 custom_annotator.Reset();
500 per_expand_data.SetAnnotator(&custom_annotator);
501 snprintf(expected, sizeof(expected),
502 "{{EVENT=1}}{{#FILE=%s004}}"
503 "{{EVENT=2}}{{#SEC=__{{MAIN}}__}}File contents: "
504 "{{EVENT=3}}{{#INC=INC}}"
505 "{{EVENT=4}}{{MISSING_FILE=missing.tpl}}"
506 "{{EVENT=5}}{{/INC}}\n"
507 "{{EVENT=6}}{{/SEC}}"
508 "{{EVENT=7}}{{/FILE}}",
509 (FLAGS_test_tmpdir + slash_tpl).c_str());
510 // See comment above on why we can't use AssertExpandWithDataIs() for
511 // our stateful test annotator.
512 custom_outstring.clear();
513 ASSERT(!one_inc_tpl->ExpandWithData(&custom_outstring,
514 &dict_missing_file,
515 &per_expand_data));
516 ASSERT_STREQ(custom_outstring.c_str(), expected);
517}
518
519TEST(Template, CheckWhitelistedVariablesSorted) {
520 // NOTE(williasr): kSafeWhitelistedVariables must be sorted, it's accessed
521 // using binary search.
522 for (size_t i = 1; i < TemplateForTest::kNumSafeWhitelistedVariables; i++) {
523 assert(strcmp(TemplateForTest::kSafeWhitelistedVariables[i-1],
524 TemplateForTest::kSafeWhitelistedVariables[i]) < 0);
525 }
526}
527
528
529// The following tests test various aspects of how Expand() should behave.
530TEST(Template, WeirdSyntax) {
531 TemplateDictionary dict("dict");
532
533 // When we see {{{, we should match the second {{, not the first.
534 Template* tpl1 = StringToTemplate("hi {{{! VAR {{!VAR} }} lo",
535 STRIP_WHITESPACE);
536 AssertExpandIs(tpl1, &dict, "hi { lo", true);
537
538 // Likewise for }}}
539 Template* tpl2 = StringToTemplate("fn(){{{BI_NEWLINE}} x=4;{{BI_NEWLINE}}}",
540 DO_NOT_STRIP);
541 AssertExpandIs(tpl2, &dict, "fn(){\n x=4;\n}", true);
542
543 // Try lots of {'s!
544 Template* tpl3 = StringToTemplate("{{{{{{VAR}}}}}}}}", DO_NOT_STRIP);
545 AssertExpandIs(tpl3, &dict, "{{{{}}}}}}", true);
546}
547
548TEST(Template, Comment) {
549 TemplateDictionary dict("dict");
550 Template* tpl1 = StringToTemplate("hi {{!VAR}} lo",
551 STRIP_WHITESPACE);
552 AssertExpandIs(tpl1, &dict, "hi lo", true);
553
554 Template* tpl2 = StringToTemplate("hi {{!VAR {VAR} }} lo",
555 STRIP_WHITESPACE);
556 AssertExpandIs(tpl2, &dict, "hi lo", true);
557
558 Template* tpl3 = StringToTemplate("hi {{! VAR {{!VAR} }} lo",
559 STRIP_WHITESPACE);
560 AssertExpandIs(tpl3, &dict, "hi lo", true);
561}
562
563TEST(Template, SetMarkerDelimiters) {
564 TemplateDictionary dict("dict");
565 dict.SetValue("VAR", "yo");
566 Template* tpl1 = StringToTemplate("{{=| |=}}\nhi |VAR| {{lo}}",
567 STRIP_WHITESPACE);
568 AssertExpandIs(tpl1, &dict, "hi yo {{lo}}", true);
569
570 Template* tpl2 = StringToTemplate("{{=| |=}}hi |VAR| {{lo}}",
571 STRIP_WHITESPACE);
572 AssertExpandIs(tpl2, &dict, "hi yo {{lo}}", true);
573
574 Template* tpl3 = StringToTemplate("{{=| ||=}}hi ||VAR|||VAR|| {{lo}}",
575 STRIP_WHITESPACE);
576 AssertExpandIs(tpl3, &dict, "hi |yoyo {{lo}}", true);
577
578 Template* tpl4 = StringToTemplate("{{=< >=}}hi <<VAR>> {{lo}}",
579 STRIP_WHITESPACE);
580 AssertExpandIs(tpl4, &dict, "hi <yo> {{lo}}", true);
581
582 Template* tpl4b = StringToTemplate("{{=<< >>=}}hi <<VAR>> {{lo}}",
583 STRIP_WHITESPACE);
584 AssertExpandIs(tpl4b, &dict, "hi yo {{lo}}", true);
585
586 Template* tpl4c = StringToTemplate("{{=<< <<=}}hi <<VAR<< {{lo}}",
587 STRIP_WHITESPACE);
588 AssertExpandIs(tpl4c, &dict, "hi yo {{lo}}", true);
589
590 Template* tpl5 = StringToTemplate("hi {{VAR}} lo\n{{=< >=}}\n"
591 "hi {{VAR}} lo\n"
592 "hi <VAR> lo\n<={ }=>\n"
593 "hi {{VAR}} lo\n{={{ }}=}\n"
594 "hi {{VAR}} lo\n",
595 STRIP_WHITESPACE);
596 AssertExpandIs(tpl5, &dict,
597 "hi yo lohi {{VAR}} lohi yo lohi {yo} lohi yo lo",
598 true);
599
600 Template* tpl6 = StringToTemplate("hi {{VAR}} lo\n{{=< >}}\n",
601 STRIP_WHITESPACE);
602 ASSERT(tpl6 == NULL);
603
604 Template* tpl7 = StringToTemplate("hi {{VAR}} lo\n{{=<>}}\n",
605 STRIP_WHITESPACE);
606 ASSERT(tpl7 == NULL);
607
608 Template* tpl8 = StringToTemplate("hi {{VAR}} lo\n{{=< >=}}\n",
609 STRIP_WHITESPACE);
610 ASSERT(tpl8 == NULL);
611
612 Template* tpl9 = StringToTemplate("hi {{VAR}} lo\n{{==}}\n",
613 STRIP_WHITESPACE);
614 ASSERT(tpl9 == NULL);
615
616 Template* tpl10 = StringToTemplate("hi {{VAR}} lo\n{{=}}\n",
617 STRIP_WHITESPACE);
618 ASSERT(tpl10 == NULL);
619
620 // Test that {{= =}} is a "removable" marker.
621 Template* tpl11 = StringToTemplate("line\n {{=| |=}} \nhi |VAR| {{lo}}\n",
622 STRIP_BLANK_LINES);
623 AssertExpandIs(tpl11, &dict, "line\nhi yo {{lo}}\n", true);
624
625 // Test that "removable" markers survive marker-modification.
626 Template* tpl12 = StringToTemplate(" {{#SEC1}} \n"
627 "{{=| |=}} |VAR|\n"
628 " |/SEC1|\ntada! |VAR|\n"
629 "hello|=<< >>=|\n"
630 " <<! a blank line>> \n"
631 "done",
632 STRIP_BLANK_LINES);
633 AssertExpandIs(tpl12, &dict, "tada! yo\nhello\ndone", true);
634}
635
636TEST(Template, Variable) {
637 Template* tpl = StringToTemplate("hi {{VAR}} lo", STRIP_WHITESPACE);
638 TemplateDictionary dict("dict");
639 AssertExpandIs(tpl, &dict, "hi lo", true);
640 dict.SetValue("VAR", "yo");
641 AssertExpandIs(tpl, &dict, "hi yo lo", true);
642 dict.SetValue("VAR", "yoyo");
643 AssertExpandIs(tpl, &dict, "hi yoyo lo", true);
644 dict.SetValue("VA", "noyo");
645 dict.SetValue("VAR ", "noyo2");
646 dict.SetValue("var", "noyo3");
647 AssertExpandIs(tpl, &dict, "hi yoyo lo", true);
648
649 // Sanity check string template behaviour while we're at it.
650 Template* tpl2 = Template::StringToTemplate("hi {{VAR}} lo",
651 STRIP_WHITESPACE);
652 TemplateDictionary dict2("dict");
653 AssertExpandIs(tpl2, &dict2, "hi lo", true);
654 dict2.SetValue("VAR", "yo");
655 AssertExpandIs(tpl2, &dict2, "hi yo lo", true);
656 dict2.SetValue("VAR", "yoyo");
657 AssertExpandIs(tpl2, &dict2, "hi yoyo lo", true);
658 dict2.SetValue("VA", "noyo");
659 dict2.SetValue("VAR ", "noyo2");
660 dict2.SetValue("var", "noyo3");
661 AssertExpandIs(tpl2, &dict2, "hi yoyo lo", true);
662 delete tpl2; // You have to delete StringToTemplate strings
663}
664
665TEST(Template, VariableWithModifiers) {
666 Template* tpl = StringToTemplate("hi {{VAR:html_escape}} lo",
667 STRIP_WHITESPACE);
668 TemplateDictionary dict("dict");
669
670 // Test with no modifiers.
671 dict.SetValue("VAR", "yo");
672 AssertExpandIs(tpl, &dict, "hi yo lo", true);
673 dict.SetValue("VAR", "yo&yo");
674 AssertExpandIs(tpl, &dict, "hi yo&amp;yo lo", true);
675
676 // Test with URL escaping.
677 tpl = StringToTemplate("<a href=\"/servlet?param={{VAR:u}}\">",
678 STRIP_WHITESPACE);
679 AssertExpandIs(tpl, &dict, "<a href=\"/servlet?param=yo%26yo\">", true);
680 tpl = StringToTemplate("<a href='/servlet?param={{VAR:url_query_escape}}'>",
681 STRIP_WHITESPACE);
682 AssertExpandIs(tpl, &dict, "<a href='/servlet?param=yo%26yo'>", true);
683
684 // Test with multiple URL escaping.
685 tpl = StringToTemplate("<a href=\"/servlet?param={{VAR:u:u}}\">",
686 STRIP_WHITESPACE);
687 AssertExpandIs(tpl, &dict, "<a href=\"/servlet?param=yo%2526yo\">", true);
688
689 // Test HTML escaping.
690 tpl = StringToTemplate("hi {{VAR:h}} lo", STRIP_WHITESPACE);
691 AssertExpandIs(tpl, &dict, "hi yo&amp;yo lo", true);
692
693 tpl = StringToTemplate("hi {{VAR:h:h}} lo", STRIP_WHITESPACE);
694 AssertExpandIs(tpl, &dict, "hi yo&amp;amp;yo lo", true);
695
696 // Test special HTML escaping
697 dict.SetValue("URL_VAR", "javascript:void");
698 dict.SetValue("SNIPPET_VAR", "<b>foo & bar</b>");
699 tpl = StringToTemplate("hi {{VAR:H=attribute}} {{URL_VAR:H=url}} "
700 "{{SNIPPET_VAR:H=snippet}} lo", STRIP_WHITESPACE);
701 AssertExpandIs(tpl, &dict, "hi yo_yo # <b>foo & bar</b> lo", true);
702
703 // Test with custom modifiers [regular or XssSafe should not matter].
704 ASSERT(GOOGLE_NAMESPACE::AddModifier("x-test",
705 &GOOGLE_NAMESPACE::html_escape));
706 ASSERT(GOOGLE_NAMESPACE::AddModifier("x-test-arg=",
707 &GOOGLE_NAMESPACE::html_escape));
708 ASSERT(GOOGLE_NAMESPACE::AddXssSafeModifier("x-test-arg=snippet",
709 &GOOGLE_NAMESPACE::snippet_escape));
710
711 tpl = StringToTemplate("hi {{VAR:x-test}} lo", STRIP_WHITESPACE);
712 AssertExpandIs(tpl, &dict, "hi yo&amp;yo lo", true);
713 tpl = StringToTemplate("hi {{SNIPPET_VAR:x-test-arg=snippet}} lo",
714 STRIP_WHITESPACE);
715 AssertExpandIs(tpl, &dict, "hi <b>foo & bar</b> lo", true);
716 tpl = StringToTemplate("hi {{VAR:x-unknown}} lo", STRIP_WHITESPACE);
717 AssertExpandIs(tpl, &dict, "hi yo&yo lo", true);
718
719 // Test with a modifier taking per-expand data
720 DynamicModifier dynamic_modifier;
721 ASSERT(GOOGLE_NAMESPACE::AddModifier("x-dynamic", &dynamic_modifier));
722 PerExpandData per_expand_data;
723 tpl = StringToTemplate("hi {{VAR:x-dynamic}} lo", STRIP_WHITESPACE);
724 AssertExpandWithDataIs(tpl, &dict, &per_expand_data, "hi lo", true);
725 per_expand_data.InsertForModifiers("value", "foo");
726 AssertExpandWithDataIs(tpl, &dict, &per_expand_data, "hi foo lo", true);
727 per_expand_data.InsertForModifiers("value", "bar");
728 AssertExpandWithDataIs(tpl, &dict, &per_expand_data, "hi bar lo", true);
729 per_expand_data.InsertForModifiers("value", NULL);
730 AssertExpandWithDataIs(tpl, &dict, &per_expand_data, "hi lo", true);
731
732 // Test with no modifiers.
733 tpl = StringToTemplate("hi {{VAR}} lo", STRIP_WHITESPACE);
734 AssertExpandIs(tpl, &dict, "hi yo&yo lo", true);
735
736 // Check that ordering is right
737 dict.SetValue("VAR", "yo\nyo");
738 tpl = StringToTemplate("hi {{VAR:h}} lo", STRIP_WHITESPACE);
739 AssertExpandIs(tpl, &dict, "hi yo yo lo", true);
740 tpl = StringToTemplate("hi {{VAR:p}} lo", STRIP_WHITESPACE);
741 AssertExpandIs(tpl, &dict, "hi yo\nyo lo", true);
742 tpl = StringToTemplate("hi {{VAR:j}} lo", STRIP_WHITESPACE);
743 AssertExpandIs(tpl, &dict, "hi yo\\nyo lo", true);
744 tpl = StringToTemplate("hi {{VAR:h:j}} lo", STRIP_WHITESPACE);
745 AssertExpandIs(tpl, &dict, "hi yo yo lo", true);
746 tpl = StringToTemplate("hi {{VAR:j:h}} lo", STRIP_WHITESPACE);
747 AssertExpandIs(tpl, &dict, "hi yo\\nyo lo", true);
748
749 // Check more complicated modifiers using fullname
750 tpl = StringToTemplate("hi {{VAR:javascript_escape:h}} lo",
751 STRIP_WHITESPACE);
752 AssertExpandIs(tpl, &dict, "hi yo\\nyo lo", true);
753 tpl = StringToTemplate("hi {{VAR:j:html_escape}} lo",
754 STRIP_WHITESPACE);
755 AssertExpandIs(tpl, &dict, "hi yo\\nyo lo", true);
756 tpl = StringToTemplate("hi {{VAR:pre_escape:j}} lo",
757 STRIP_WHITESPACE);
758 AssertExpandIs(tpl, &dict, "hi yo\\nyo lo", true);
759
760 // Check that illegal modifiers are rejected
761 tpl = StringToTemplate("hi {{VAR:j:h2}} lo", STRIP_WHITESPACE);
762 ASSERT(tpl == NULL);
763 tpl = StringToTemplate("hi {{VAR:html_ecap}} lo", STRIP_WHITESPACE);
764 ASSERT(tpl == NULL);
765 tpl = StringToTemplate("hi {{VAR:javascript_escaper}} lo",
766 STRIP_WHITESPACE);
767 ASSERT(tpl == NULL);
768 tpl = StringToTemplate("hi {{VAR:js:j}} lo", STRIP_WHITESPACE);
769 ASSERT(tpl == NULL);
770 tpl = StringToTemplate("hi {{VAR:}} lo", STRIP_WHITESPACE);
771 ASSERT(tpl == NULL);
772
773 // Check we reject modifier-values when we ought to
774 tpl = StringToTemplate("hi {{VAR:j=4}} lo", STRIP_WHITESPACE);
775 ASSERT(tpl == NULL);
776 tpl = StringToTemplate("hi {{VAR:html_escape=yes}} lo", STRIP_WHITESPACE);
777 ASSERT(tpl == NULL);
778 tpl = StringToTemplate("hi {{VAR:url_query_escape=wombats}} lo",
779 STRIP_WHITESPACE);
780 ASSERT(tpl == NULL);
781
782 // Check we don't allow modifiers on sections
783 tpl = StringToTemplate("hi {{#VAR:h}} lo {{/VAR}}", STRIP_WHITESPACE);
784 ASSERT(tpl == NULL);
785
786 // Test when expanded grows by more than 12% per modifier.
787 dict.SetValue("VAR", "http://a.com?b=c&d=e&f=g&q=a>b");
788 tpl = StringToTemplate("{{VAR:u:j:h}}",
789 STRIP_WHITESPACE);
790 AssertExpandIs(tpl, &dict,
791 "http%3A//a.com%3Fb%3Dc%26d%3De%26f%3Dg%26q%3Da%3Eb",
792 true);
793
794 // As above with 4 modifiers.
795 dict.SetValue("VAR", "http://a.com?b=c&d=e&f=g&q=a>b");
796 tpl = StringToTemplate("{{VAR:u:j:h:h}}",
797 STRIP_WHITESPACE);
798 AssertExpandIs(tpl, &dict,
799 "http%3A//a.com%3Fb%3Dc%26d%3De%26f%3Dg%26q%3Da%3Eb",
800 true);
801}
802
803TEST(Template, Section) {
804 Template* tpl = StringToTemplate(
805 "boo!\nhi {{#SEC}}lo{{#SUBSEC}}jo{{/SUBSEC}}{{/SEC}} bar",
806 STRIP_WHITESPACE);
807 TemplateDictionary dict("dict");
808 AssertExpandIs(tpl, &dict, "boo!hi bar", true);
809 dict.ShowSection("SEC");
810 AssertExpandIs(tpl, &dict, "boo!hi lo bar", true);
811 dict.ShowSection("SEC");
812 AssertExpandIs(tpl, &dict, "boo!hi lo bar", true);
813 // This should work even though subsec isn't a child of the main dict
814 dict.ShowSection("SUBSEC");
815 AssertExpandIs(tpl, &dict, "boo!hi lojo bar", true);
816
817 TemplateDictionary dict2("dict2");
818 dict2.AddSectionDictionary("SEC");
819 AssertExpandIs(tpl, &dict2, "boo!hi lo bar", true);
820 dict2.AddSectionDictionary("SEC");
821 AssertExpandIs(tpl, &dict2, "boo!hi lolo bar", true);
822 dict2.AddSectionDictionary("sec");
823 AssertExpandIs(tpl, &dict2, "boo!hi lolo bar", true);
824 dict2.ShowSection("SUBSEC");
825 AssertExpandIs(tpl, &dict2, "boo!hi lojolojo bar", true);
826}
827
828
829TEST(Template, SectionSeparator) {
830 Template* tpl = StringToTemplate(
831 "hi {{#SEC}}lo{{#SEC_separator}}jo{{JO}}{{/SEC_separator}}{{/SEC}} bar",
832 STRIP_WHITESPACE);
833 TemplateDictionary dict("dict");
834 AssertExpandIs(tpl, &dict, "hi bar", true);
835 // Since SEC is only expanded once, the separator section shouldn't show.
836 dict.ShowSection("SEC");
837 AssertExpandIs(tpl, &dict, "hi lo bar", true);
838 dict.ShowSection("SEC");
839 AssertExpandIs(tpl, &dict, "hi lo bar", true);
840 // This should work even though SEC_separator isn't a child of the
841 // main dict. It verifies SEC_separator is just a normal section, too.
842 dict.ShowSection("SEC_separator");
843 AssertExpandIs(tpl, &dict, "hi lojo bar", true);
844
845 TemplateDictionary dict2("dict2");
846 dict2.AddSectionDictionary("SEC");
847 AssertExpandIs(tpl, &dict2, "hi lo bar", true);
848 dict2.AddSectionDictionary("SEC");
849 AssertExpandIs(tpl, &dict2, "hi lojolo bar", true);
850 // This is a weird case: using separator and specifying manually.
851 dict2.ShowSection("SEC_separator");
852 AssertExpandIs(tpl, &dict2, "hi lojojolojo bar", true);
853
854 TemplateDictionary dict3("dict3");
855 TemplateDictionary* sec1 = dict3.AddSectionDictionary("SEC");
856 TemplateDictionary* sec2 = dict3.AddSectionDictionary("SEC");
857 TemplateDictionary* sec3 = dict3.AddSectionDictionary("SEC");
858 dict3.SetValue("JO", "J");
859 AssertExpandIs(tpl, &dict3, "hi lojoJlojoJlo bar", true);
860 sec1->SetValue("JO", "JO");
861 AssertExpandIs(tpl, &dict3, "hi lojoJOlojoJlo bar", true);
862 sec2->SetValue("JO", "JOO");
863 AssertExpandIs(tpl, &dict3, "hi lojoJOlojoJOOlo bar", true);
864 dict3.AddSectionDictionary("SEC");
865 AssertExpandIs(tpl, &dict3, "hi lojoJOlojoJOOlojoJlo bar", true);
866 sec3->AddSectionDictionary("SEC_separator");
867 AssertExpandIs(tpl, &dict3, "hi lojoJOlojoJOOlojoJjoJlo bar", true);
868
869 // Make sure we don't do anything special with var or include names
870 Template* tpl2 = StringToTemplate(
871 "hi {{#SEC}}lo{{>SEC_separator}}{{/SEC}} bar",
872 STRIP_WHITESPACE);
873 AssertExpandIs(tpl2, &dict2, "hi lolo bar", true);
874
875 Template* tpl3 = StringToTemplate(
876 "hi {{#SEC}}lo{{SEC_separator}}{{/SEC}} bar",
877 STRIP_WHITESPACE);
878 dict2.SetValue("SEC_separator", "-");
879 AssertExpandIs(tpl3, &dict2, "hi lo-lo- bar", true);
880}
881
882TEST(Template, Include) {
883 string incname = StringToTemplateFile("include file\n");
884 string incname2 = StringToTemplateFile("inc2a\ninc2b\n");
885 string incname_bad = StringToTemplateFile("{{syntax_error");
886 Template* tpl = StringToTemplate("hi {{>INC}} bar\n", STRIP_WHITESPACE);
887 TemplateDictionary dict("dict");
888 AssertExpandIs(tpl, &dict, "hi bar", true);
889 dict.AddIncludeDictionary("INC");
890 AssertExpandIs(tpl, &dict, "hi bar", true); // noop: no filename was set
891 dict.AddIncludeDictionary("INC")->SetFilename("/notarealfile ");
892 AssertExpandIs(tpl, &dict, "hi bar", false); // noop: illegal filename
893 dict.AddIncludeDictionary("INC")->SetFilename(incname);
894 AssertExpandIs(tpl, &dict, "hi include file bar", false);
895 dict.AddIncludeDictionary("INC")->SetFilename(incname_bad);
896 AssertExpandIs(tpl, &dict, "hi include file bar",
897 false); // noop: syntax error
898 dict.AddIncludeDictionary("INC")->SetFilename(incname);
899 AssertExpandIs(tpl, &dict, "hi include fileinclude file bar", false);
900 dict.AddIncludeDictionary("inc")->SetFilename(incname);
901 AssertExpandIs(tpl, &dict, "hi include fileinclude file bar", false);
902 dict.AddIncludeDictionary("INC")->SetFilename(incname2);
903 AssertExpandIs(tpl, &dict,
904 "hi include fileinclude fileinc2ainc2b bar", false);
905
906 // Now test that includes preserve Strip
907 Template* tpl2 = StringToTemplate("hi {{>INC}} bar", DO_NOT_STRIP);
908 AssertExpandIs(tpl2, &dict,
909 "hi include file\ninclude file\ninc2a\ninc2b\n bar", false);
910
911 // Test that if we indent the include, every line on the include
912 // is indented.
913 Template* tpl3 = StringToTemplate("hi\n {{>INC}} bar", DO_NOT_STRIP);
914 AssertExpandIs(tpl3, &dict,
915 "hi\n include file\n include file\n"
916 " inc2a\n inc2b\n bar",
917 false);
918 // But obviously, if we strip leading whitespace, no indentation.
919 Template* tpl4 = StringToTemplate("hi\n {{>INC}} bar", STRIP_WHITESPACE);
920 AssertExpandIs(tpl4, &dict,
921 "hiinclude fileinclude fileinc2ainc2b bar", false);
922 // And if it's not a whitespace indent, we don't indent either.
923 Template* tpl5 = StringToTemplate("hi\n - {{>INC}} bar", DO_NOT_STRIP);
924 AssertExpandIs(tpl5, &dict,
925 "hi\n - include file\ninclude file\n"
926 "inc2a\ninc2b\n bar",
927 false);
928 // Make sure we indent properly at the beginning.
929 Template* tpl6 = StringToTemplate(" {{>INC}}\nbar", DO_NOT_STRIP);
930 AssertExpandIs(tpl6, &dict,
931 " include file\n include file\n"
932 " inc2a\n inc2b\n \nbar",
933 false);
934 // And deal correctly when we include twice in a row.
935 Template* tpl7 = StringToTemplate(" {{>INC}}-{{>INC}}", DO_NOT_STRIP);
936 AssertExpandIs(tpl7, &dict,
937 " include file\n include file\n inc2a\n inc2b\n "
938 "-include file\ninclude file\ninc2a\ninc2b\n",
939 false);
940}
941
942TEST(Template, IncludeWithModifiers) {
943 string incname = StringToTemplateFile("include & print file\n");
944 string incname2 = StringToTemplateFile("inc2\n");
945 string incname3 = StringToTemplateFile("yo&yo");
946 // Note this also tests that html-escape, but not javascript-escape or
947 // pre-escape, escapes \n to <space>
948 Template* tpl1 = StringToTemplate("hi {{>INC:h}} bar\n", DO_NOT_STRIP);
949 Template* tpl2 = StringToTemplate("hi {{>INC:javascript_escape}} bar\n",
950 DO_NOT_STRIP);
951 Template* tpl3 = StringToTemplate("hi {{>INC:pre_escape}} bar\n",
952 DO_NOT_STRIP);
953 Template* tpl4 = StringToTemplate("hi {{>INC:u}} bar\n", DO_NOT_STRIP);
954 // Test that if we include the same template twice, once with a modifer
955 // and once without, they each get applied properly.
956 Template* tpl5 = StringToTemplate("hi {{>INC:h}} bar {{>INC}} baz\n",
957 DO_NOT_STRIP);
958
959 TemplateDictionary dict("dict");
960 AssertExpandIs(tpl1, &dict, "hi bar\n", true);
961 dict.AddIncludeDictionary("INC")->SetFilename(incname);
962 AssertExpandIs(tpl1, &dict, "hi include &amp; print file bar\n", true);
963 dict.AddIncludeDictionary("INC")->SetFilename(incname2);
964 AssertExpandIs(tpl1, &dict, "hi include &amp; print file inc2 bar\n",
965 true);
966 AssertExpandIs(tpl2, &dict, "hi include \\x26 print file\\ninc2\\n bar\n",
967 true);
968 AssertExpandIs(tpl3, &dict, "hi include &amp; print file\ninc2\n bar\n",
969 true);
970 dict.AddIncludeDictionary("INC")->SetFilename(incname3);
971 AssertExpandIs(tpl4, &dict,
972 "hi include+%26+print+file%0Ainc2%0Ayo%26yo bar\n",
973 true);
974 AssertExpandIs(tpl5, &dict,
975 "hi include &amp; print file inc2 yo&amp;yo bar "
976 "include & print file\ninc2\nyo&yo baz\n",
977 true);
978
979 // Don't test modifier syntax here; that's in TestVariableWithModifiers()
980}
981
982// Make sure we don't deadlock when a template includes itself.
983// This also tests we handle recursive indentation properly.
984TEST(Template, RecursiveInclude) {
985 string incname = StringToTemplateFile("hi {{>INC}} bar\n {{>INC}}!");
986 Template* tpl = Template::GetTemplate(incname, DO_NOT_STRIP);
987 TemplateDictionary dict("dict");
988 dict.AddIncludeDictionary("INC")->SetFilename(incname);
989 // Note the last line is indented 4 spaces instead of 2. This is
990 // because the last sub-include is indented.
991 AssertExpandIs(tpl, &dict, "hi hi bar\n ! bar\n hi bar\n !!", true);
992}
993
994// Tests that vars inherit/override their parents properly
995TEST(Template, Inheritence) {
996 Template* tpl = StringToTemplate("{{FOO}}{{#SEC}}{{FOO}}{{#SEC}}{{FOO}}{{/SEC}}{{/SEC}}",
997 STRIP_WHITESPACE);
998 TemplateDictionary dict("dict");
999 dict.SetValue("FOO", "foo");
1000 dict.ShowSection("SEC");
1001 AssertExpandIs(tpl, &dict, "foofoofoo", true);
1002
1003 TemplateDictionary dict2("dict2");
1004 dict2.SetValue("FOO", "foo");
1005 TemplateDictionary* sec = dict2.AddSectionDictionary("SEC");
1006 AssertExpandIs(tpl, &dict2, "foofoofoo", true);
1007 sec->SetValue("FOO", "bar");
1008 AssertExpandIs(tpl, &dict2, "foobarbar", true);
1009 TemplateDictionary* sec2 = sec->AddSectionDictionary("SEC");
1010 AssertExpandIs(tpl, &dict2, "foobarbar", true);
1011 sec2->SetValue("FOO", "baz");
1012 AssertExpandIs(tpl, &dict2, "foobarbaz", true);
1013
1014 // Now test an include template, which shouldn't inherit from its parents
1015 tpl = StringToTemplate("{{FOO}}{{#SEC}}hi{{/SEC}}\n{{>INC}}",
1016 STRIP_WHITESPACE);
1017 string incname = StringToTemplateFile(
1018 "include {{FOO}}{{#SEC}}invisible{{/SEC}}file\n");
1019 TemplateDictionary incdict("dict");
1020 incdict.ShowSection("SEC");
1021 incdict.SetValue("FOO", "foo");
1022 incdict.AddIncludeDictionary("INC")->SetFilename(incname);
1023 AssertExpandIs(tpl, &incdict, "foohiinclude file", true);
1024}
1025
1026TEST(Template, TemplateString) {
1027 // Make sure using TemplateString and StaticTemplateString for the
1028 // dictionary expands the same as using char*'s.
1029 Template* tpl = StringToTemplate("hi {{VAR}} lo", STRIP_WHITESPACE);
1030 TemplateDictionary dict("dict");
1031 dict.SetValue("VAR", TemplateString("short-lived", strlen("short")));
1032 AssertExpandIs(tpl, &dict, "hi short lo", true);
1033 dict.SetValue("VAR", kHello);
1034 AssertExpandIs(tpl, &dict, "hi Hello lo", true);
1035}
1036
1037// Tests that we append to the output string, rather than overwrite
1038TEST(Template, Expand) {
1039 Template* tpl = StringToTemplate("hi", STRIP_WHITESPACE);
1040 TemplateDictionary dict("test_expand");
1041 string output("premade");
1042 ASSERT(tpl->Expand(&output, &dict));
1043 ASSERT_STREQ(output.c_str(), "premadehi");
1044
1045 tpl = StringToTemplate(" lo ", STRIP_WHITESPACE);
1046 ASSERT(tpl->Expand(&output, &dict));
1047 ASSERT_STREQ(output.c_str(), "premadehilo");
1048}
1049
1050TEST(Template, ExpandTemplate) {
1051 string filename = StringToTemplateFile(" hi {{THERE}}");
1052 TemplateDictionary dict("test_expand");
1053 dict.SetValue("THERE", "test");
1054 string output;
1055 ASSERT(ExpandTemplate(filename, STRIP_WHITESPACE, &dict, &output));
1056 ASSERT_STREQ(output.c_str(), "hi test");
1057
1058 // This will append to output, so we see both together.
1059 ASSERT(ExpandWithData(filename, DO_NOT_STRIP, &dict, NULL, &output));
1060 ASSERT_STREQ(output.c_str(), "hi test hi test");
1061
1062 ASSERT(!ExpandTemplate(filename + " not found", DO_NOT_STRIP, &dict,
1063 &output));
1064}
1065
1066TEST(Template, ExpandWithCustomEmitter) {
1067 Template* tpl = StringToTemplate("{{VAR}} {{VAR}}", STRIP_WHITESPACE);
1068 TemplateDictionary dict("test_expand");
1069 dict.SetValue("VAR", "this song is just six words long");
1070 string output;
1071 SizeofEmitter e(&output);
1072 ASSERT(tpl->Expand(&e, &dict));
1073 ASSERT_STREQ("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
1074 "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
1075 output.c_str());
1076}
1077
1078TEST(Template, TemplateExpansionModifier) {
1079 string parent_tpl_name = StringToTemplateFile("before {{>INC}} after");
1080 string child_tpl_name1 = StringToTemplateFile("child1");
1081 string child_tpl_name2 = StringToTemplateFile("child2");
1082 Template* tpl = Template::GetTemplate(parent_tpl_name, DO_NOT_STRIP);
1083
1084 TemplateDictionary dict("parent dict");
1085 dict.AddIncludeDictionary("INC")->SetFilename(child_tpl_name1);
1086 dict.AddIncludeDictionary("INC")->SetFilename(child_tpl_name2);
1087
1088 PerExpandData per_expand_data;
1089
1090 EmphasizeTemplateModifier modifier1(child_tpl_name1);
1091 per_expand_data.SetTemplateExpansionModifier(&modifier1);
1092 AssertExpandWithDataIs(tpl, &dict, &per_expand_data,
1093 "before >>child1<<child2 after", true);
1094
1095 EmphasizeTemplateModifier modifier2(child_tpl_name2);
1096 per_expand_data.SetTemplateExpansionModifier(&modifier2);
1097 AssertExpandWithDataIs(tpl, &dict, &per_expand_data,
1098 "before child1>>child2<< after", true);
1099
1100 EmphasizeTemplateModifier modifier3(parent_tpl_name);
1101 per_expand_data.SetTemplateExpansionModifier(&modifier3);
1102 AssertExpandWithDataIs(tpl, &dict, &per_expand_data,
1103 ">>before child1child2 after<<", true);
1104
1105 per_expand_data.SetTemplateExpansionModifier(NULL);
1106 AssertExpandWithDataIs(tpl, &dict, &per_expand_data,
1107 "before child1child2 after", true);
1108}
1109
1110TEST(Template, GetTemplate) {
1111 // Tests the cache
1112 string filename = StringToTemplateFile("{This is perfectly valid} yay!");
1113 Template* tpl1 = Template::GetTemplate(filename, DO_NOT_STRIP);
1114 Template* tpl2 = Template::GetTemplate(filename.c_str(), DO_NOT_STRIP);
1115 Template* tpl3 = Template::GetTemplate(filename, STRIP_WHITESPACE);
1116 ASSERT(tpl1 && tpl2 && tpl3);
1117 ASSERT(tpl1 == tpl2);
1118 ASSERT(tpl1 != tpl3);
1119
1120 // Tests that a nonexistent template returns NULL
1121 Template* tpl4 = Template::GetTemplate("/yakakak", STRIP_WHITESPACE);
1122 ASSERT(!tpl4);
1123
1124 // Tests that syntax errors cause us to return NULL
1125 Template* tpl5 = StringToTemplate("{{This has spaces in it}}", DO_NOT_STRIP);
1126 ASSERT(!tpl5);
1127 Template* tpl6 = StringToTemplate("{{#SEC}}foo", DO_NOT_STRIP);
1128 ASSERT(!tpl6);
1129 Template* tpl7 = StringToTemplate("{{#S1}}foo{{/S2}}", DO_NOT_STRIP);
1130 ASSERT(!tpl7);
1131 Template* tpl8 = StringToTemplate("{{#S1}}foo{{#S2}}bar{{/S1}{{/S2}",
1132 DO_NOT_STRIP);
1133 ASSERT(!tpl8);
1134 Template* tpl9 = StringToTemplate("{{noend", DO_NOT_STRIP);
1135 ASSERT(!tpl9);
1136}
1137
1138TEST(Template, StringCacheKey) {
1139 // If you use these same cache keys somewhere else,
1140 // call Template::ClearCache first.
1141 const string cache_key_a = "cache key a";
1142 const string text = "Test template 1";
1143 TemplateDictionary empty_dict("dict");
1144
1145 // When a string template is registered via StringToTemplateCache,
1146 // we can use GetTemplate for that same cache-key under any other
1147 // Strip because we cache the contents.
1148 Template *tpl1, *tpl2;
1149 ASSERT(Template::StringToTemplateCache(cache_key_a, text));
1150 tpl1 = Template::GetTemplate(cache_key_a, DO_NOT_STRIP);
1151 AssertExpandIs(tpl1, &empty_dict, text, true);
1152
1153 // Different strip.
1154 ASSERT(tpl2 = Template::GetTemplate(cache_key_a, STRIP_BLANK_LINES));
1155 ASSERT(tpl2 != tpl1);
1156 AssertExpandIs(tpl2, &empty_dict, text, true);
1157
1158 Template::ClearCache();
1159}
1160
1161TEST(Template, StringGetTemplate) {
1162 TemplateDictionary dict("dict");
1163
1164 // Test cache lookups
1165 const char* const tpltext = "{This is perfectly valid} yay!";
1166 ASSERT(Template::StringToTemplateCache("tgt", tpltext));
1167
1168 Template* tpl1 = Template::GetTemplate("tgt", DO_NOT_STRIP);
1169 Template* tpl2 = Template::GetTemplate("tgt", STRIP_WHITESPACE);
1170 ASSERT(tpl1 && tpl2);
1171 ASSERT(tpl1 != tpl2);
1172 AssertExpandIs(tpl1, &dict, tpltext, true);
1173 AssertExpandIs(tpl2, &dict, tpltext, true);
1174
1175 // If we register a new string under the same text, it should be
1176 // ignored.
1177 ASSERT(!Template::StringToTemplateCache("tgt", tpltext));
1178 ASSERT(!Template::StringToTemplateCache("tgt", "new text"));
1179 Template* tpl3 = Template::GetTemplate("tgt", DO_NOT_STRIP);
1180 ASSERT(tpl3 == tpl1);
1181 AssertExpandIs(tpl3, &dict, tpltext, true);
1182
1183 // Tests that syntax errors cause us to return NULL
1184 ASSERT(!Template::StringToTemplateCache("tgt2", "{{This has spaces}}"));
1185 ASSERT(!Template::StringToTemplateCache("tgt3", "{{#SEC}}foo"));
1186 ASSERT(!Template::StringToTemplateCache("tgt4", "{{#S1}}foo{{/S2}}"));
1187 ASSERT(!Template::StringToTemplateCache("tgt5",
1188 "{{#S1}}foo{{#S2}}bar{{/S1}{{/S2}"));
1189 ASSERT(!Template::StringToTemplateCache("tgt6", "{{noend"));
1190 // And that we didn't cache them by mistake
1191 ASSERT(!Template::GetTemplate("tgt2", STRIP_WHITESPACE));
1192
1193 Template::ClearCache();
1194}
1195
1196TEST(Template, StringTemplateInclude) {
1197 Template::ClearCache(); // just for exercise.
1198 const string cache_key = "TestStringTemplateInclude";
1199 const string cache_key_inc = "TestStringTemplateInclude-inc";
1200 const string cache_key_indent = "TestStringTemplateInclude-indent";
1201 const string text = "<html>{{>INC}}</html>";
1202 const string text_inc = "<div>\n<p>\nUser {{USER}}\n</div>";
1203 const string text_indent = "<html>\n {{>INC}}</html>";
1204
1205 ASSERT(Template::StringToTemplateCache(cache_key, text));
1206 ASSERT(Template::StringToTemplateCache(cache_key_inc, text_inc));
1207 ASSERT(Template::StringToTemplateCache(cache_key_indent, text_indent));
1208
1209 Template *tpl = Template::GetTemplate(cache_key, DO_NOT_STRIP);
1210 ASSERT(tpl);
1211
1212 TemplateDictionary dict("dict");
1213 TemplateDictionary* sub_dict = dict.AddIncludeDictionary("INC");
1214 sub_dict->SetFilename(cache_key_inc);
1215
1216 sub_dict->SetValue("USER", "John<>Doe");
1217 string expected = "<html><div>\n<p>\nUser John<>Doe\n</div></html>";
1218 AssertExpandIs(tpl, &dict, expected, true);
1219
1220 // Repeat the same except that now the parent has a template-level
1221 // directive (by way of the automatic-line-indenter).
1222 tpl = Template::GetTemplate(cache_key_indent, DO_NOT_STRIP);
1223 ASSERT(tpl);
1224 expected =
1225 "<html>\n"
1226 " <div>\n"
1227 " <p>\n"
1228 " User John<>Doe\n"
1229 " </div>"
1230 "</html>";
1231 AssertExpandIs(tpl, &dict, expected, true);
1232
1233 Template::ClearCache();
1234}
1235
1236TEST(Template, TemplateSearchPath) {
1237 const string pathA = PathJoin(FLAGS_test_tmpdir, "a/");
1238 const string pathB = PathJoin(FLAGS_test_tmpdir, "b/");
1239 CreateOrCleanTestDir(pathA);
1240 CreateOrCleanTestDir(pathB);
1241
1242 TemplateDictionary dict("");
1243 Template::SetTemplateRootDirectory(pathA);
1244 Template::AddAlternateTemplateRootDirectory(pathB);
1245
1246 // 1. Show that a template in the secondary path can be found.
1247 const string path_b_bar = PathJoin(pathB, "template_bar");
1248 StringToFile("b/template_bar", path_b_bar);
1249 ASSERT_STREQ(path_b_bar.c_str(),
1250 Template::FindTemplateFilename("template_bar").c_str());
1251 Template* b_bar = Template::GetTemplate("template_bar", DO_NOT_STRIP);
1252 ASSERT(b_bar);
1253 AssertExpandIs(b_bar, &dict, "b/template_bar", true);
1254
1255 // 2. Show that the search stops once the first match is found.
1256 // Create two templates in separate directories with the same name.
1257 const string path_a_foo = PathJoin(pathA, "template_foo");
1258 StringToFile("a/template_foo", path_a_foo);
1259 StringToFile("b/template_foo", PathJoin(pathB, "template_foo"));
1260 ASSERT_STREQ(path_a_foo.c_str(),
1261 Template::FindTemplateFilename("template_foo").c_str());
1262 Template* a_foo = Template::GetTemplate("template_foo", DO_NOT_STRIP);
1263 ASSERT(a_foo);
1264 AssertExpandIs(a_foo, &dict, "a/template_foo", true);
1265
1266 // 3. Show that attempting to find a non-existent template gives an
1267 // empty path.
1268 ASSERT(Template::FindTemplateFilename("baz").empty());
1269
1270 CreateOrCleanTestDir(pathA);
1271 CreateOrCleanTestDir(pathB);
1272}
1273
1274TEST(Template, RemoveStringFromTemplateCache) {
1275 Template::ClearCache(); // just for exercise.
1276 const string cache_key = "TestRemoveStringFromTemplateCache";
1277 const string text = "<html>here today...</html>";
1278
1279 TemplateDictionary dict("test");
1280 ASSERT(Template::StringToTemplateCache(cache_key, text));
1281 Template* tpl = Template::GetTemplate(cache_key, DO_NOT_STRIP);
1282 ASSERT(tpl);
1283 AssertExpandIs(tpl, &dict, text, true);
1284 tpl = Template::GetTemplate(cache_key, STRIP_WHITESPACE);
1285 ASSERT(tpl);
1286 AssertExpandIs(tpl, &dict, text, true);
1287
1288 Template::RemoveStringFromTemplateCache(cache_key);
1289 tpl = Template::GetTemplate(cache_key, DO_NOT_STRIP);
1290 ASSERT(!tpl);
1291 tpl = Template::GetTemplate(cache_key, STRIP_WHITESPACE);
1292 ASSERT(!tpl);
1293 tpl = Template::GetTemplate(cache_key, STRIP_BLANK_LINES);
1294 ASSERT(!tpl);
1295}
1296
1297TEST(Template, TemplateCache) {
1298 const string filename_a = StringToTemplateFile("Test template 1");
1299 const string filename_b = StringToTemplateFile("Test template 2.");
1300
1301 Template *tpl, *tpl2;
1302 ASSERT(tpl = Template::GetTemplate(filename_a, DO_NOT_STRIP));
1303
1304 ASSERT(tpl2 = Template::GetTemplate(filename_b, DO_NOT_STRIP));
1305 ASSERT(tpl2 != tpl); // different filenames.
1306 ASSERT(tpl2 = Template::GetTemplate(filename_a, STRIP_BLANK_LINES));
1307 ASSERT(tpl2 != tpl); // different strip.
1308 ASSERT(tpl2 = Template::GetTemplate(filename_b, STRIP_BLANK_LINES));
1309 ASSERT(tpl2 != tpl); // different filenames and strip.
1310 ASSERT(tpl2 = Template::GetTemplate(filename_a, DO_NOT_STRIP));
1311 ASSERT(tpl2 == tpl); // same filename and strip.
1312}
1313
1314// Tests that the various strip values all do the expected thing.
1315TEST(Template, Strip) {
1316 TemplateDictionary dict("dict");
1317 dict.SetValue("FOO", "foo");
1318
1319 const char* tests[][4] = { // 0: in, 1: do-not-strip, 2: blanklines, 3: ws
1320 {"hi!\n", "hi!\n", "hi!\n", "hi!"},
1321 {"hi!", "hi!", "hi!", "hi!"},
1322 // These test strip-blank-lines, primarily
1323 {"{{FOO}}\n\n{{FOO}}", "foo\n\nfoo", "foo\nfoo", "foofoo"},
1324 {"{{FOO}}\r\n\r\n{{FOO}}", "foo\r\n\r\nfoo", "foo\r\nfoo", "foofoo"},
1325 {"{{FOO}}\n \n{{FOO}}\n", "foo\n \nfoo\n", "foo\nfoo\n", "foofoo"},
1326 {"{{FOO}}\n{{BI_NEWLINE}}\nb", "foo\n\n\nb", "foo\n\n\nb", "foo\nb"},
1327 {"{{FOO}}\n{{!comment}}\nb", "foo\n\nb", "foo\nb", "foob"},
1328 {"{{FOO}}\n{{!comment}}{{!comment2}}\nb", "foo\n\nb", "foo\n\nb", "foob"},
1329 {"{{FOO}}\n{{>ONE_INC}}\nb", "foo\n\nb", "foo\nb", "foob"},
1330 {"{{FOO}}\n\t{{>ONE_INC}} \nb", "foo\n\t \nb", "foo\nb", "foob"},
1331 {"{{FOO}}\n{{>ONE_INC}}{{>TWO_INC}}\nb", "foo\n\nb", "foo\n\nb", "foob"},
1332 {"{{FOO}}\n {{#SEC}}\ntext \n {{/SEC}}\n", "foo\n \n", "foo\n", "foo"},
1333 {"{{%AUTOESCAPE context=\"HTML\"}}\nBLA", "\nBLA", "BLA", "BLA"},
1334 // These test strip-whitespace
1335 {"foo\nbar\n", "foo\nbar\n", "foo\nbar\n", "foobar"},
1336 {"{{FOO}}\nbar\n", "foo\nbar\n", "foo\nbar\n", "foobar"},
1337 {" {{FOO}} {{!comment}}\nb", " foo \nb", " foo \nb", "foo b"},
1338 {" {{FOO}} {{BI_SPACE}}\n", " foo \n", " foo \n", "foo "},
1339 {" \t \f\v \n\r\n ", " \t \f\v \n\r\n ", "", ""},
1340 };
1341
1342 for (int i = 0; i < sizeof(tests)/sizeof(*tests); ++i) {
1343 Template* tpl1 = StringToTemplate(tests[i][0], DO_NOT_STRIP);
1344 Template* tpl2 = StringToTemplate(tests[i][0], STRIP_BLANK_LINES);
1345 Template* tpl3 = StringToTemplate(tests[i][0], STRIP_WHITESPACE);
1346 AssertExpandIs(tpl1, &dict, tests[i][1], true);
1347 AssertExpandIs(tpl2, &dict, tests[i][2], true);
1348 AssertExpandIs(tpl3, &dict, tests[i][3], true);
1349 }
1350}
1351
1352TEST(Template, TemplateRootDirectory) {
1353 string filename = StringToTemplateFile("Test template");
1354 ASSERT(IsAbspath(filename));
1355 Template* tpl1 = Template::GetTemplate(filename, DO_NOT_STRIP);
1356 Template::SetTemplateRootDirectory(kRootdir); // "/"
1357 // template-root shouldn't matter for absolute directories
1358 Template* tpl2 = Template::GetTemplate(filename, DO_NOT_STRIP);
1359 Template::SetTemplateRootDirectory("/sadfadsf/waerfsa/safdg");
1360 Template* tpl3 = Template::GetTemplate(filename, DO_NOT_STRIP);
1361 ASSERT(tpl1 != NULL);
1362 ASSERT(tpl1 == tpl2);
1363 ASSERT(tpl1 == tpl3);
1364
1365 // Now test it actually works by breaking the abspath in various places.
1366 // We do it twice, since we don't know if the path-sep is "/" or "\".
1367 // NOTE: this depends on filename not using "/" or "\" except as a
1368 // directory separator (so nothing like "/var/tmp/foo\a/weirdfile").
1369 const char* const kPathSeps = "/\\";
1370 for (const char* path_sep = kPathSeps; *path_sep; path_sep++) {
1371 for (string::size_type sep_pos = filename.find(*path_sep, 0);
1372 sep_pos != string::npos;
1373 sep_pos = filename.find(*path_sep, sep_pos + 1)) {
1374 Template::SetTemplateRootDirectory(filename.substr(0, sep_pos + 1));
1375 Template* tpl = Template::GetTemplate(filename.substr(sep_pos + 1),
1376 DO_NOT_STRIP);
1377 ASSERT(string(tpl->template_file()) == tpl1->template_file());
1378 }
1379 }
1380}
1381
1382#if defined(HAVE_PTHREAD) && !defined(NO_THREADS)
1383struct ThreadReturn {
1384 Template* file_template;
1385 bool string_to_template_cache_return;
1386 Template* string_template;
1387};
1388
1389// RunThread returns a ThreadReturn* that should be deleted.
1390static void* RunThread(void* vfilename) {
1391 const char* filename = reinterpret_cast<const char*>(vfilename);
1392 ThreadReturn* ret = new ThreadReturn;
1393 ret->file_template = Template::GetTemplate(filename, DO_NOT_STRIP);
1394 ASSERT(ret->file_template != NULL);
1395 const char* const key = "RunThread key";
1396 ret->string_to_template_cache_return =
1397 StringToTemplateCache(key, " RunThread text ", STRIP_WHITESPACE);
1398 ret->string_template = Template::GetTemplate(key, STRIP_WHITESPACE);
1399 ASSERT(ret->string_template != NULL);
1400 return ret;
1401}
1402
1403TEST(Template, ThreadSafety) {
1404 string filename = StringToTemplateFile("(testing thread-safety)");
1405
1406 // GetTemplate() is the most thread-contended routine. We get a
1407 // template in many threads, and assert we get the same template
1408 // from each.
1409 pthread_t thread_ids[kNumThreads];
1410 for (int i = 0; i < kNumThreads; ++i) {
1411 ASSERT(pthread_create(thread_ids+i, NULL, RunThread,
1412 (void*)filename.c_str())
1413 == 0);
1414 }
1415
1416 // Wait for all the threads to terminate (should be very quick!)
1417 ThreadReturn* first_thread_return = NULL;
1418 int num_times_string_to_template_cache_returned_true = 0;
1419 for (int i = 0; i < kNumThreads; ++i) {
1420 void* vthread_return;
1421 ASSERT(pthread_join(thread_ids[i], &vthread_return) == 0);
1422 ThreadReturn* thread_return =
1423 reinterpret_cast<ThreadReturn*>(vthread_return);
1424 if (thread_return->string_to_template_cache_return) {
1425 ++num_times_string_to_template_cache_returned_true;
1426 }
1427 if (first_thread_return == NULL) { // we're the first thread
1428 first_thread_return = thread_return;
1429 } else {
1430 ASSERT(thread_return->file_template ==
1431 first_thread_return->file_template);
1432 ASSERT(thread_return->string_template ==
1433 first_thread_return->string_template);
1434 delete thread_return;
1435 }
1436 }
1437 delete first_thread_return;
1438 ASSERT_INTEQ(1, num_times_string_to_template_cache_returned_true);
1439 Template::ClearCache();
1440}
1441#endif // #if defined(HAVE_PTHREAD) && !defined(NO_THREADS)
1442
1443// Tests all the static methods in TemplateNamelist
1444TEST(Template, TemplateNamelist) {
1445 time_t before_time = Now(); // in template_test_util.cc
1446 string f1 = StringToTemplateFile("{{This has spaces in it}}");
1447 string f2 = StringToTemplateFile("{{#SEC}}foo");
1448 string f3 = StringToTemplateFile("{This is ok");
1449 // Where we'll copy f1 - f3 to: these are names known at compile-time
1450 string f1_copy = PathJoin(FLAGS_test_tmpdir, INVALID1_FN);
1451 string f2_copy = PathJoin(FLAGS_test_tmpdir, INVALID2_FN);
1452 string f3_copy = PathJoin(FLAGS_test_tmpdir, VALID1_FN);
1453 Template::SetTemplateRootDirectory(FLAGS_test_tmpdir);
1454 time_t after_time = Now(); // f1, f2, f3 all written by now
1455
1456 TemplateNamelist::NameListType names = TemplateNamelist::GetList();
1457 ASSERT(names.size() == 4);
1458 ASSERT(names.count(NONEXISTENT_FN));
1459 ASSERT(names.count(INVALID1_FN));
1460 ASSERT(names.count(INVALID2_FN));
1461 ASSERT(names.count(VALID1_FN));
1462
1463 // Before creating the files INVALID1_FN, etc., all should be missing.
1464 for (int i = 0; i < 3; ++i) { // should be consistent all 3 times
1465 const TemplateNamelist::MissingListType& missing =
1466 TemplateNamelist::GetMissingList(false);
1467 ASSERT(missing.size() == 4);
1468 }
1469 // Everyone is missing, but nobody should have bad syntax
1470 ASSERT(!TemplateNamelist::AllDoExist());
1471 ASSERT(TemplateNamelist::IsAllSyntaxOkay(DO_NOT_STRIP));
1472
1473 // Now create those files
1474 ASSERT(link(f1.c_str(), f1_copy.c_str()) == 0);
1475 ASSERT(link(f2.c_str(), f2_copy.c_str()) == 0);
1476 ASSERT(link(f3.c_str(), f3_copy.c_str()) == 0);
1477 // We also have to clear the template cache, since we created a new file.
1478 // ReloadAllIfChanged() would probably work, too.
1479 Template::ClearCache();
1480
1481 // When GetMissingList is false, we don't reload, so you still get all-gone
1482 TemplateNamelist::MissingListType missing =
1483 TemplateNamelist::GetMissingList(false);
1484 ASSERT(missing.size() == 4);
1485 // But with true, we should have a different story
1486 missing = TemplateNamelist::GetMissingList(true);
1487 ASSERT(missing.size() == 1);
1488 missing = TemplateNamelist::GetMissingList(false);
1489 ASSERT(missing.size() == 1);
1490 ASSERT(missing[0] == NONEXISTENT_FN);
1491 ASSERT(!TemplateNamelist::AllDoExist());
1492
1493 // IsAllSyntaxOK did a badsyntax check, before the files were created.
1494 // So with a false arg, should still say everything is ok
1495 TemplateNamelist::SyntaxListType badsyntax =
1496 TemplateNamelist::GetBadSyntaxList(false, DO_NOT_STRIP);
1497 ASSERT(badsyntax.size() == 0);
1498 // But IsAllSyntaxOK forces a refresh
1499 ASSERT(!TemplateNamelist::IsAllSyntaxOkay(DO_NOT_STRIP));
1500 badsyntax = TemplateNamelist::GetBadSyntaxList(false, DO_NOT_STRIP);
1501 ASSERT(badsyntax.size() == 2);
1502 ASSERT(badsyntax[0] == INVALID1_FN || badsyntax[1] == INVALID1_FN);
1503 ASSERT(badsyntax[0] == INVALID2_FN || badsyntax[1] == INVALID2_FN);
1504 ASSERT(!TemplateNamelist::IsAllSyntaxOkay(DO_NOT_STRIP));
1505 badsyntax = TemplateNamelist::GetBadSyntaxList(true, DO_NOT_STRIP);
1506 ASSERT(badsyntax.size() == 2);
1507
1508 time_t modtime = TemplateNamelist::GetLastmodTime();
1509 ASSERT(modtime >= before_time && modtime <= after_time);
1510 // Now update a file and make sure lastmod time is updated.
1511 // Note that since TemplateToFile uses "fake" timestamps way
1512 // in the past, this append should definitely give a time
1513 // that's after after_time.
1514 FILE* fp = fopen(f1_copy.c_str(), "ab");
1515 ASSERT(fp);
1516 fwrite("\n", 1, 1, fp);
1517 fclose(fp);
1518 modtime = TemplateNamelist::GetLastmodTime();
1519 ASSERT(modtime > after_time);
1520
1521 // Checking if we can register templates at run time.
1522 string f4 = StringToTemplateFile("{{ONE_GOOD_TEMPLATE}}");
1523 TemplateNamelist::RegisterTemplate(f4.c_str());
1524 names = TemplateNamelist::GetList();
1525 ASSERT(names.size() == 5);
1526
1527 string f5 = StringToTemplateFile("{{ONE BAD TEMPLATE}}");
1528 TemplateNamelist::RegisterTemplate(f5.c_str());
1529 names = TemplateNamelist::GetList();
1530 ASSERT(names.size() == 6);
1531 badsyntax = TemplateNamelist::GetBadSyntaxList(false, DO_NOT_STRIP);
1532 ASSERT(badsyntax.size() == 2); // we did not refresh the bad syntax list
1533 badsyntax = TemplateNamelist::GetBadSyntaxList(true, DO_NOT_STRIP);
1534 // After refresh, the file we just registerd also added in bad syntax list
1535 ASSERT(badsyntax.size() == 3);
1536
1537 TemplateNamelist::RegisterTemplate("A_non_existant_file.tpl");
1538 names = TemplateNamelist::GetList();
1539 ASSERT(names.size() == 7);
1540 missing = TemplateNamelist::GetMissingList(false);
1541 ASSERT(missing.size() == 1); // we did not refresh the missing list
1542 missing = TemplateNamelist::GetMissingList(true);
1543 // After refresh, the file we just registerd also added in missing list
1544 ASSERT(missing.size() == 2);
1545}
1546
1547// This test is not "end-to-end", it doesn't use a dictionary
1548// and only outputs what the template system thinks is the
1549// correct modifier for variables.
1550TEST(Template, CorrectModifiersForAutoEscape) {
1551 string text, expected_out;
1552
1553 // template with no variable, nothing to emit.
1554 text = "Static template.";
1555 AssertCorrectModifiers(TC_HTML, text, "");
1556
1557 // Simple templates with one variable substitution.
1558
1559 // 1. No in-template modifiers. Auto Escaper sets correct ones.
1560 text = "Hello {{USER}}";
1561 AssertCorrectModifiers(TC_HTML, text, "USER:h\n");
1562
1563 // Complete URLs in different attributes that take URLs.
1564 text = "<a href=\"{{URL}}\">bla</a>";
1565 AssertCorrectModifiers(TC_HTML, text, "URL:U=html\n");
1566 text = "<script src=\"{{URL}}\"></script>";
1567 AssertCorrectModifiers(TC_HTML, text, "URL:U=html\n");
1568 text = "<img src=\"{{URL}}\">";
1569 AssertCorrectModifiers(TC_HTML, text, "URL:U=html\n");
1570 // URL fragment only so just html_escape.
1571 text = "<img src=\"/bla?q={{QUERY}}\">";
1572 AssertCorrectModifiers(TC_HTML, text, "QUERY:h\n");
1573 // URL fragment not quoted, so url_escape.
1574 text = "<img src=/bla?q={{QUERY}}>";
1575 AssertCorrectModifiers(TC_HTML, text, "QUERY:u\n");
1576
1577 text = "<br class=\"{{CLASS}}\">";
1578 AssertCorrectModifiers(TC_HTML, text, "CLASS:h\n");
1579 text = "<br class={{CLASS}}>";
1580 AssertCorrectModifiers(TC_HTML, text, "CLASS:H=attribute\n");
1581 text = "<br {{CLASS}}>"; // CLASS here is name/value pair.
1582 AssertCorrectModifiers(TC_HTML, text, "CLASS:H=attribute\n");
1583 text = "<br style=\"display:{{DISPLAY}}\">"; // Style attribute.
1584 AssertCorrectModifiers(TC_HTML, text, "DISPLAY:c\n");
1585
1586 // Content inside a style tag should have :c regardless of quoting.
1587 text = "<style>color:{{COLOR}}; font:\"{{FONT}}\"</style>";
1588 AssertCorrectModifiers(TC_HTML, text, "COLOR:c\nFONT:c\n");
1589
1590 // onMouseEvent and onKeyUp accept javascript.
1591 text = "<a href=\"url\" onkeyup=\"doX('{{ID}}');\">"; // ID quoted
1592 AssertCorrectModifiers(TC_HTML, text, "ID:j\n");
1593 text = "<a href=\"url\" onclick=\"doX({{ID}});\">"; // ID not quoted
1594 AssertCorrectModifiers(TC_HTML, text, "ID:J=number\n");
1595 text = "<a href=\"url\" onclick=\"'{{ID}}'\">"; // not common
1596 AssertCorrectModifiers(TC_HTML, text, "ID:j\n");
1597 // If ID is javascript code, J=number will break it, for good and bad.
1598 text = "<a href=\"url\" onclick=\"{{ID}}\">";
1599 AssertCorrectModifiers(TC_HTML, text, "ID:J=number\n");
1600
1601 // Target just needs html escaping.
1602 text = "<a href=\"url\" target=\"{{TARGET}}\">";
1603 AssertCorrectModifiers(TC_HTML, text, "TARGET:h\n");
1604
1605 // Test a parsing corner case which uses TemplateDirective
1606 // call in the parser to change state properly. To reproduce
1607 // both variables should be unquoted and the first should
1608 // have no value except the variable substitution.
1609 text = "<img class={{CLASS}} src=/bla?q={{QUERY}}>";
1610 AssertCorrectModifiers(TC_HTML, text, "CLASS:H=attribute\nQUERY:u\n");
1611
1612 // TODO(jad): Once we have a fix for it in code, fix me.
1613 // Javascript URL is not properly supported, we currently
1614 // apply :h which is not sufficient.
1615 text = "<a href=\"javascript:foo('{{VAR}}')>bla</a>";
1616 AssertCorrectModifiers(TC_HTML, text, "VAR:h\n");
1617
1618 // Special handling for BI_SPACE and BI_NEWLINE.
1619 text = "{{BI_SPACE}}";
1620 AssertCorrectModifiers(TC_HTML, text, "BI_SPACE\n"); // Untouched.
1621 text = "{{BI_NEWLINE}}";
1622 AssertCorrectModifiers(TC_HTML, text, "BI_NEWLINE\n"); // Untouched.
1623 // Check that the parser is parsing BI_SPACE, if not, it would have failed.
1624 text = "<a href=/bla{{BI_SPACE}}style=\"{{VAR}}\">text</a>";
1625 AssertCorrectModifiers(TC_HTML, text, "BI_SPACE\nVAR:c\n");
1626
1627
1628 // XML and JSON modes.
1629 text = "<PARAM name=\"{{VAL}}\">{{DATA}}";
1630 AssertCorrectModifiers(TC_XML, text, "VAL:xml_escape\nDATA:xml_escape\n");
1631 text = "{ x = \"{{VAL}}\"}";
1632 AssertCorrectModifiers(TC_JSON, text, "VAL:j\n");
1633
1634 // 2. Escaping modifiers were set, handle them.
1635
1636 // 2a: Modifier :none is honored whether the escaping is correct or not.
1637 text = "Hello {{USER:none}}"; // :none on its own.
1638 AssertCorrectModifiers(TC_HTML, text, "USER:none\n");
1639 text = "Hello {{USER:h:none}}"; // correct escaping.
1640 AssertCorrectModifiers(TC_HTML, text, "USER:h:none\n");
1641 text = "Hello {{USER:j:none}}"; // incorrect escaping.
1642 AssertCorrectModifiers(TC_HTML, text, "USER:j:none\n");
1643 text = "<a href=\"url\" onkeyup=\"doX('{{ID:none}}');\">";
1644 AssertCorrectModifiers(TC_HTML, text, "ID:none\n");
1645
1646 // 2b: Correct modifiers, nothing to change.
1647 text = "Hello {{USER:h}}";
1648 AssertCorrectModifiers(TC_HTML, text, "USER:h\n");
1649 text = "Hello {{USER:U=html}}"; // :U=html is a valid replacement for .h
1650 AssertCorrectModifiers(TC_HTML, text, "USER:U=html\n");
1651 text = "Hello {{USER:H=url}}"; // :H=url (a.k.a. U=html) is valid too
1652 AssertCorrectModifiers(TC_HTML, text, "USER:H=url\n");
1653 text = "Hello {{USER:h:j}}"; // Extra :j, honor it.
1654 AssertCorrectModifiers(TC_HTML, text, "USER:h:j\n");
1655 text = "<a href=\"{{URL:U=html}}\">bla</a>";
1656 AssertCorrectModifiers(TC_HTML, text, "URL:U=html\n");
1657 text = "<a href=\"/bla?q={{QUERY:h}}\">bla</a>"; // :h is valid.
1658 AssertCorrectModifiers(TC_HTML, text, "QUERY:h\n");
1659 text = "<a href=\"/bla?q={{QUERY:u}}\">bla</a>"; // so is :u.
1660 AssertCorrectModifiers(TC_HTML, text, "QUERY:u\n");
1661 text = "<a href=\"url\" onclick=\"doX('{{ID:j}}');\">";
1662 AssertCorrectModifiers(TC_HTML, text, "ID:j\n");
1663 text = "<a href=\"url\" onclick=\"doX({{ID:J=number}});\">";
1664 AssertCorrectModifiers(TC_HTML, text, "ID:J=number\n");
1665 text = "<style>@import url(\"{{URL:U=css}}\")</style>"; // correct :U=css
1666 AssertCorrectModifiers(TC_HTML, text, "URL:U=css\n");
1667
1668 // 2c: Incorrect modifiers, add our own.
1669 text = "Hello {{USER:j}}"; // Missing :h
1670 AssertCorrectModifiers(TC_HTML, text, "USER:j:h\n");
1671 text = "Hello {{USER:c:c:c:c:c:j}}"; // Still missing :h
1672 AssertCorrectModifiers(TC_HTML, text, "USER:c:c:c:c:c:j:h\n");
1673 text = "<script>var a = \"{{VAR:h}}\";</script>"; // Missing :j
1674 AssertCorrectModifiers(TC_HTML, text, "VAR:h:j\n");
1675 text = "<script>var a = \"{{VAR:j:h:j}}\";</script>"; // Extra :h:j
1676 AssertCorrectModifiers(TC_HTML, text, "VAR:j:h:j\n");
1677 text = "<a href=\"url\" onclick=\"doX({{ID:j}});\">"; // Unquoted
1678 AssertCorrectModifiers(TC_HTML, text, "ID:j:J=number\n");
1679
1680 // 2d: Custom modifiers are maintained.
1681 text = "Hello {{USER:x-bla}}"; // Missing :h
1682 AssertCorrectModifiers(TC_HTML, text, "USER:x-bla:h\n");
1683 text = "Hello {{USER:x-bla:h}}"; // Correct, accept it.
1684 AssertCorrectModifiers(TC_HTML, text, "USER:x-bla:h\n");
1685 text = "Hello {{USER:x-bla:x-foo}}"; // Missing :h
1686 AssertCorrectModifiers(TC_HTML, text, "USER:x-bla:x-foo:h\n");
1687 text = "Hello {{USER:x-bla:none}}"; // Complete due to :none
1688 AssertCorrectModifiers(TC_HTML, text, "USER:x-bla:none\n");
1689 text = "Hello {{USER:h:x-bla}}"; // Still missing :h.
1690 AssertCorrectModifiers(TC_HTML, text, "USER:h:x-bla:h\n");
1691 text = "Hello {{USER:x-bla:h:x-foo}}"; // Still missing :h
1692 AssertCorrectModifiers(TC_HTML, text, "USER:x-bla:h:x-foo:h\n");
1693 text = "Hello {{USER:x-bla:h:x-foo:h}}"; // Valid, accept it.
1694 AssertCorrectModifiers(TC_HTML, text, "USER:x-bla:h:x-foo:h\n");
1695
1696 // 2e: Equivalent modifiers are honored. All HTML Escapes.
1697 text = "Hello {{USER:p}}";
1698 AssertCorrectModifiers(TC_HTML, text, "USER:p\n");
1699 text = "Hello {{USER:H=attribute}}";
1700 AssertCorrectModifiers(TC_HTML, text, "USER:H=attribute\n");
1701 text = "Hello {{USER:H=snippet}}";
1702 AssertCorrectModifiers(TC_HTML, text, "USER:H=snippet\n");
1703 text = "Hello {{USER:H=pre}}";
1704 AssertCorrectModifiers(TC_HTML, text, "USER:H=pre\n");
1705 // All URL + HTML Escapes.
1706 text = "<a href=\"{{URL:H=url}}\">bla</a>";
1707 AssertCorrectModifiers(TC_HTML, text, "URL:H=url\n");
1708 text = "<a href=\"{{URL:U=html}}\">bla</a>";
1709 AssertCorrectModifiers(TC_HTML, text, "URL:U=html\n");
1710
1711 // 2f: Initialize template in Javascript Context.
1712 text = "var a = '{{VAR}}'"; // Escaping not given.
1713 AssertCorrectModifiers(TC_JS, text, "VAR:j\n");
1714 text = "var a = '{{VAR:none}}'"; // Variable safe.
1715 AssertCorrectModifiers(TC_JS, text, "VAR:none\n");
1716 text = "var a = '{{VAR:j}}'"; // Escaping correct.
1717 AssertCorrectModifiers(TC_JS, text, "VAR:j\n");
1718 text = "var a = '{{VAR:h}}'"; // Escaping incorrect.
1719 AssertCorrectModifiers(TC_JS, text, "VAR:h:j\n");
1720 text = "var a = '{{VAR:J=number}}'"; // Not considered equiv.
1721 AssertCorrectModifiers(TC_JS, text, "VAR:J=number:j\n");
1722
1723 // 2g: Honor any modifiers for BI_SPACE and BI_NEWLINE.
1724 text = "{{BI_NEWLINE:j}}"; // An invalid modifier for the context.
1725 AssertCorrectModifiers(TC_HTML, text, "BI_NEWLINE:j\n");
1726 text = "{{BI_SPACE:h}}"; // An otherwise valid modifier.
1727 AssertCorrectModifiers(TC_HTML, text, "BI_SPACE:h\n");
1728 text = "{{BI_SPACE:x-bla}}"; // Also support custom modifiers.
1729 AssertCorrectModifiers(TC_HTML, text, "BI_SPACE:x-bla\n");
1730
1731 // 2h: TC_CSS, TC_XML and TC_JSON
1732 text = "H1{margin-{{START_EDGE}}:0;\n text-align:{{END_EDGE}}\n}";
1733 AssertCorrectModifiers(TC_CSS, text, "START_EDGE:c\nEND_EDGE:c\n");
1734 text = "body{background:url('{{URL:U=css}}')}"; // :U=css valid substitute
1735 AssertCorrectModifiers(TC_CSS, text, "URL:U=css\n");
1736 text = "body{background:url('{{URL:U=html}}')}"; // Not valid, will add :c.
1737 AssertCorrectModifiers(TC_CSS, text, "URL:U=html:c\n");
1738 text = "<PARAM name=\"{{VAL:xml_escape}}\">"; // Correct escaping
1739 AssertCorrectModifiers(TC_XML, text, "VAL:xml_escape\n");
1740 text = "<PARAM name=\"{{VAL:H=attribute}}\">"; // XSS equivalent
1741 AssertCorrectModifiers(TC_XML, text, "VAL:H=attribute\n");
1742 text = "<PARAM name=\"{{VAL:h}}\">"; // XSS equivalent
1743 AssertCorrectModifiers(TC_XML, text, "VAL:h\n");
1744 text = "<PARAM name=\"{{VAL:H=pre}}\">"; // Not XSS equivalent
1745 AssertCorrectModifiers(TC_XML, text, "VAL:H=pre:xml_escape\n");
1746 text = "<PARAM name=\"{{VAL:c}}\">"; // Not XSS equivalent
1747 AssertCorrectModifiers(TC_XML, text, "VAL:c:xml_escape\n");
1748 text = "{user={{USER:j}}"; // Correct escaping
1749 AssertCorrectModifiers(TC_JSON, text, "USER:j\n");
1750 text = "{user={{USER:o}}"; // json_escape is XSS equivalent
1751 AssertCorrectModifiers(TC_JSON, text, "USER:o\n");
1752 text = "{user={{USER:h}}"; // but html_escape is not
1753 AssertCorrectModifiers(TC_JSON, text, "USER:h:j\n");
1754
1755 // 2i: Variables with XssSafe Custom modifiers are untouched.
1756 ASSERT(GOOGLE_NAMESPACE::AddXssSafeModifier("x-test-cm",
1757 &GOOGLE_NAMESPACE::html_escape));
1758 text = "Hello {{USER:x-test-cm}}"; // Missing :h
1759 AssertCorrectModifiers(TC_HTML, text, "USER:x-test-cm\n");
1760 text = "Hello {{USER:x-test-cm:j}}"; // Extra :j
1761 AssertCorrectModifiers(TC_HTML, text, "USER:x-test-cm:j\n");
1762 text = "Hello {{USER:x-test-cm:x-foo}}"; // Non-safe modifier
1763 AssertCorrectModifiers(TC_HTML, text, "USER:x-test-cm:x-foo\n");
1764 text = "Hello {{USER:x-foo:x-test-cm}}"; // Non-safe modifier
1765 AssertCorrectModifiers(TC_HTML, text, "USER:x-foo:x-test-cm\n");
1766 text = "Hello {{USER:x-test-cm:none}}"; // Complete due to :none
1767 AssertCorrectModifiers(TC_HTML, text, "USER:x-test-cm:none\n");
1768 text = "Hello {{USER:h:x-test-cm}}"; // Prior escaping
1769 AssertCorrectModifiers(TC_HTML, text, "USER:h:x-test-cm\n");
1770
1771 // 3. Larger test with close to every escaping case.
1772
1773 text = "<html><head>\n"
1774 "<style>\n"
1775 "@import url(\"{{CSS_URL:U=css}}\");\n"
1776 "color:{{COLOR}}</style></head><body>\n"
1777 "<h1>{{TITLE}}</h1>\n"
1778 "<img src=\"{{IMG_URL}}\">\n"
1779 "<form action=\"/search\">\n"
1780 " <input name=\"hl\" value={{HL}}>\n"
1781 " <input name=\"m\" value=\"{{FORM_MSG}}\">\n"
1782 "</form>\n"
1783 "<div style=\"background:{{BG_COLOR}}\">\n"
1784 "</div>\n"
1785 "<script>\n"
1786 " var msg_text = '{{MSG_TEXT}}';\n"
1787 "</script>\n"
1788 "<a href=\"url\" onmouseover=\"'{{MOUSE}}'\">bla</a>\n"
1789 "Goodbye friend {{USER}}!\n</body></html>\n";
1790 expected_out = "CSS_URL:U=css\n"
1791 "COLOR:c\n"
1792 "TITLE:h\n"
1793 "IMG_URL:U=html\n"
1794 "HL:H=attribute\n"
1795 "FORM_MSG:h\n"
1796 "BG_COLOR:c\n"
1797 "MSG_TEXT:j\n"
1798 "MOUSE:j\n" // :j also escapes html entities
1799 "USER:h\n";
1800 AssertCorrectModifiers(TC_HTML, text, expected_out);
1801}
1802
1803// More "end-to-end" test to ensure that variables are
1804// escaped as expected with auto-escape mode enabled.
1805// Obviously there is a lot more we can test.
1806TEST(Template, VariableWithAutoEscape) {
1807 string text, expected_out;
1808 TemplateDictionary dict("dict");
1809 string good_url("http://www.google.com/");
1810 string bad_url("javascript:alert();");
1811
1812 text = "hi {{VAR}} lo";
1813 dict.SetValue("VAR", "<bad>yo");
1814 AssertCorrectEscaping(TC_HTML, dict, text, "hi &lt;bad&gt;yo lo");
1815
1816 text = "<a href=\"{{URL}}\">bla</a>";
1817 dict.SetValue("URL", good_url);
1818 expected_out = "<a href=\"" + good_url + "\">bla</a>";
1819 AssertCorrectEscaping(TC_HTML, dict, text, expected_out);
1820 dict.SetValue("URL", bad_url);
1821 expected_out = "<a href=\"#\">bla</a>";
1822 AssertCorrectEscaping(TC_HTML, dict, text, expected_out);
1823
1824 text = "<br style=\"display:{{DISPLAY}}\">";
1825 dict.SetValue("DISPLAY", "none");
1826 expected_out = "<br style=\"display:none\">";
1827 AssertCorrectEscaping(TC_HTML, dict, text, expected_out);
1828 // Bad characters are simply removed in CleanseCss.
1829 dict.SetValue("URL", "!#none_ ");
1830 expected_out = "<br style=\"display:none\">";
1831 AssertCorrectEscaping(TC_HTML, dict, text, expected_out);
1832
1833 text = "<a href=\"url\" onkeyup=\"'{{EVENT}}'\">";
1834 dict.SetValue("EVENT", "safe");
1835 expected_out = "<a href=\"url\" onkeyup=\"'safe'\">";
1836 AssertCorrectEscaping(TC_HTML, dict, text, expected_out);
1837 dict.SetValue("EVENT", "f = 'y';");
1838 expected_out = "<a href=\"url\" onkeyup=\"'f \\x3d \\x27y\\x27;'\">";
1839
1840 // Check special handling of BI_SPACE and BI_NEWLINE.
1841 text = "Hello\n{{BI_SPACE}}bla{{BI_NEWLINE}}foo.";
1842 expected_out = "Hello bla\nfoo.";
1843 AssertCorrectEscaping(TC_HTML, dict, text, expected_out);
1844
1845 // TC_CSS
1846 text = "H1{margin-{{EDGE}}:0; text-align:{{BAD_EDGE}}}";
1847 dict.SetValue("EDGE", "left");
1848 dict.SetValue("BAD_EDGE", "$$center()!!"); // Bad chars are removed.
1849 AssertCorrectEscaping(TC_CSS, dict, text,
1850 "H1{margin-left:0; text-align:center!!}");
1851
1852 // TC_XML and TC_JSON
1853 text = "<Q>{{DATA}}</Q>";
1854 dict.SetValue("DATA", "good-data");
1855 AssertCorrectEscaping(TC_XML, dict, text, "<Q>good-data</Q>");
1856 dict.SetValue("DATA", "<BAD>FOO</BAD>");
1857 AssertCorrectEscaping(TC_XML, dict, text,
1858 "<Q>&lt;BAD&gt;FOO&lt;/BAD&gt;</Q>");
1859 text = "{user = \"{{USER}}\"}";
1860 dict.SetValue("USER", "good-user");
1861 AssertCorrectEscaping(TC_JSON, dict, text, "{user = \"good-user\"}");
1862 dict.SetValue("USER", "evil'<>\"");
1863 AssertCorrectEscaping(TC_JSON, dict, text,
1864 "{user = \"evil\\x27\\x3c\\x3e\\x22\"}");
1865}
1866
1867// Test that the template initialization fails in auto-escape
1868// mode if the parser failed to parse.
1869TEST(Template, FailedInitWithAutoEscape) {
1870 Strip strip = STRIP_WHITESPACE;
1871 // Taken from HTML Parser test suite.
1872 string bad_html = "<a href='http://www.google.com' ''>\n";
1873 ASSERT(NULL == StringToTemplateWithAutoEscaping(bad_html, strip, TC_HTML));
1874
1875 // Missing quotes around URL, not accepted in URL-taking attributes.
1876 bad_html = "<a href={{URL}}>bla</a>";
1877 ASSERT(NULL == StringToTemplateWithAutoEscaping(bad_html, strip, TC_HTML));
1878
1879 // Missing quotes around STYLE, not accepted in style-taking attributes.
1880 bad_html = "<div style={{STYLE}}>";
1881 ASSERT(NULL == StringToTemplateWithAutoEscaping(bad_html, strip, TC_HTML));
1882}
1883
1884TEST(Template, AutoEscaping) {
1885 Strip strip = STRIP_WHITESPACE;
1886 Template *tpl;
1887 string filename;
1888 string text;
1889 string user = "John<>Doe";
1890 string user_esc = "John&lt;&gt;Doe";
1891
1892 // Positive test cases -- template initialization succeeds.
1893 // We also check that modifiers that were missing or given incorrect
1894 // have been updated as expected.
1895 // TODO(jad): Cut-down redundancy by merging with
1896 // TestCorrectModifiersForAutoEscape.
1897 text = "{{%AUTOESCAPE context=\"HTML\"}}" // HTML
1898 "{{USER:o}}<a href=\"{{URL}}\" class={{CLASS:h}}</a>";
1899 ASSERT(tpl = StringToTemplate(text, strip));
1900 string expected_mods = "USER:o:h\nURL:U=html\nCLASS:h:H=attribute\n";
1901 AssertCorrectModifiersInTemplate(tpl, text, expected_mods);
1902
1903 text = "{{%AUTOESCAPE context=\"HTML\" state=\"IN_TAG\"}}" // HTML in tag
1904 "href=\"{{URL}}\" class={{CLASS:h}} style=\"font:{{COLOR}}\"";
1905 ASSERT(tpl = StringToTemplate(text, strip));
1906 expected_mods = "URL:U=html\nCLASS:h:H=attribute\nCOLOR:c\n";
1907 AssertCorrectModifiersInTemplate(tpl, text, expected_mods);
1908
1909 text = "{{%AUTOESCAPE context=\"HTML\" state=\"in_tag\"}}" // lowercase ok
1910 "href=\"{{URL}}\" class={{CLASS:h}} style=\"font:{{COLOR}}\"";
1911 ASSERT(tpl = StringToTemplate(text, strip));
1912 expected_mods = "URL:U=html\nCLASS:h:H=attribute\nCOLOR:c\n";
1913 AssertCorrectModifiersInTemplate(tpl, text, expected_mods);
1914
1915 // Repeat the test with trailing HTML that closes the tag. This is
1916 // undefined behavior. We test it to ensure the parser does not choke.
1917 text += ">Hello</a><span>Some text</span></body></html>";
1918 ASSERT(tpl = StringToTemplate(text, strip));
1919 expected_mods = "URL:U=html\nCLASS:h:H=attribute\nCOLOR:c\n";
1920 AssertCorrectModifiersInTemplate(tpl, text, expected_mods);
1921
1922 text = "{{%AUTOESCAPE context=\"JAVASCRIPT\"}}" // JAVASCRIPT
1923 "var a = {{A}}; var b = '{{B:h}}';";
1924 ASSERT(tpl = StringToTemplate(text, strip));
1925 expected_mods = "A:J=number\nB:h:j\n";
1926 AssertCorrectModifiersInTemplate(tpl, text, expected_mods);
1927
1928 text = "{{%AUTOESCAPE context=\"CSS\"}}" // CSS
1929 "body {color:\"{{COLOR}}\"; font-size:{{SIZE:j}}";
1930 ASSERT(tpl = StringToTemplate(text, strip));
1931 expected_mods = "COLOR:c\nSIZE:j:c\n";
1932 AssertCorrectModifiersInTemplate(tpl, text, expected_mods);
1933
1934 text = "{{%AUTOESCAPE context=\"JSON\"}}" // JSON
1935 "{ 'id': {{ID:j}}, 'value': {{VALUE:h}} }";
1936 ASSERT(tpl = StringToTemplate(text, strip));
1937 expected_mods = "ID:j\nVALUE:h:j\n";
1938 AssertCorrectModifiersInTemplate(tpl, text, expected_mods);
1939
1940 text = "{{%AUTOESCAPE context=\"XML\"}}" // XML
1941 "<PARAM name=\"{{VAL}}\">{{DATA:h}}";
1942 ASSERT(tpl = StringToTemplate(text, strip));
1943 expected_mods = "VAL:xml_escape\nDATA:h\n";
1944 AssertCorrectModifiersInTemplate(tpl, text, expected_mods);
1945
1946 text = "{{%AUTOESCAPE context=\"xml\"}}" // lower-case XML
1947 "<PARAM name=\"{{VAL}}\">{{DATA:h}}";
1948 ASSERT(tpl = StringToTemplate(text, strip));
1949 expected_mods = "VAL:xml_escape\nDATA:h\n";
1950 AssertCorrectModifiersInTemplate(tpl, text, expected_mods);
1951
1952 text = "{{!bla}}{{%AUTOESCAPE context=\"HTML\"}}"; // after comment
1953 ASSERT(tpl = StringToTemplate(text, strip));
1954 text = "{{%AUTOESCAPE context=\"HTML\" state=\"default\"}}";
1955 ASSERT(tpl = StringToTemplate(text, strip)); // adding state
1956
1957 // Negative test cases - template initialization fails due to errors
1958 // in the marker. Also checks that our parsing is defensive.
1959 text = "{{%AUTOESCAPE}}"; // missing context
1960 ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
1961 text = "{{%AUTOESCAPER context=\"HTML\"}}"; // invalid id
1962 ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
1963 text = "{{%}}"; // missing id
1964 ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
1965 text = "{{% }}"; // missing id
1966 ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
1967 text = "{{% =}}"; // missing id
1968 ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
1969 text = "{{%AUTOESCAPE =\"HTML\"}}"; // missing name
1970 ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
1971 text = "{{%AUTOESCAPE foo=\"HTML\"}}"; // bogus name
1972 ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
1973 text = "{{%AUTOESCAPE =}}"; // lone '='
1974 ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
1975 text = "{{%AUTOESCAPE context=HTML}}"; // val not quoted
1976 ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
1977 text = "{{%AUTOESCAPE context=\"HTML}}"; // no end quotes
1978 ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
1979 text = "{{%AUTOESCAPE context=\"\\\"HTML\"}}"; // Unescape val
1980 ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
1981 text = "{{%AUTOESCAPE context=\"\\\"HT\\\"\\\"ML\\\"\"}}"; // more complex
1982 ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
1983 text = "{{%AUTOESCAPE context=\"\"HTML\"}}"; // Unescape val
1984 ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
1985 text = "{{%AUTOESCAPE context=\"JAVASCRIPT\" bla}}"; // extra attr
1986 ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
1987 text = "{{%AUTOESCAPE context=\"JAVASCRIPT\"bla}}"; // invalid value
1988 ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
1989 text = "{{%AUTOESCAPE context=\"JAVASCRIPT\" foo=bla}}"; // extra attr/val
1990 ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
1991 text = "{{%AUTOESCAPE context=\"HTML\"}}"; // extra whitesp
1992 ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
1993 text = "{{%AUTOESCAPE context =\"HTML\"}}"; // extra whitesp
1994 ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
1995 text = "{{%AUTOESCAPE context= \"HTML\"}}"; // extra whitesp
1996 ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
1997 text = "{{%AUTOESCAPE context=\"HTML\" }}"; // extra whitesp
1998 ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
1999 text = "{{%AUTOESCAPE context=\"Xml\"}}"; // mixed-case xml
2000 ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
2001 text = "{{%AUTOESCAPE context=\"HTML\" state=\"tag\"}}"; // bad state
2002 ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
2003 text = "{{%AUTOESCAPE context=\"CSS\" state=\"IN_TAG\"}}"; // invalid state
2004 ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
2005 text = "Hello{{%AUTOESCAPE context=\"HTML\"}}"; // after text
2006 ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
2007 text = "{{USER}}{{%AUTOESCAPE context=\"HTML\"}}"; // after variable
2008 ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
2009 text = "{{#SEC}}{{%AUTOESCAPE context=\"HTML\"}}{{/SEC}}"; // not in MAIN
2010 ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
2011
2012 string kAutoescapeHtmlPragma = "{{%AUTOESCAPE context=\"HTML\"}}";
2013
2014 // Check that Selective Auto-Escape does not auto-escape included templates
2015 // unless these are also marked for auto-escape. To attest that,
2016 // we check that when no escaping was given in the included template, none
2017 // will be applied to it. USER will not get html-escaped.
2018 text = kAutoescapeHtmlPragma + "{{>INC}}";
2019 tpl = StringToTemplate(text, strip);
2020 ASSERT(tpl);
2021 string inc_text = "{{USER}}"; // missing :h escaping.
2022 TemplateDictionary dict("dict");
2023 TemplateDictionary *inc_dict = dict.AddIncludeDictionary("INC");
2024 inc_dict->SetFilename(StringToTemplateFile(inc_text));
2025 inc_dict->SetValue("USER", user);
2026 AssertExpandIs(tpl, &dict, user, true);
2027
2028 // Add AUTOESCAPE pragma to included template and check that it works.
2029 inc_text = kAutoescapeHtmlPragma + inc_text;
2030 filename = StringToTemplateFile(inc_text);
2031 inc_dict->SetFilename(filename);
2032 AssertExpandIs(tpl, &dict, user_esc, true);
2033
2034 // Check that Selective Auto-Escape works with Template::StringToTemplate.
2035 tpl = Template::StringToTemplate(inc_text, strip);
2036 ASSERT(tpl);
2037 TemplateDictionary dict2("dict2");
2038 dict2.SetValue("USER", user);
2039 AssertExpandIs(tpl, &dict2, user_esc, true);
2040 delete tpl;
2041
2042 // Test that Selective AutoEscape follows included templates: Included
2043 // templates 2 and 4 are registered for auto-escaping but not included
2044 // templates 1 and 3. Check that only templates 2 and 4 get escaped.
2045 text = "Parent: {{USER}}; {{>INCONE}}";
2046 string text_inc1 = "INC1: {{USER1}}; {{>INCTWO}}";
2047 string text_inc2 = kAutoescapeHtmlPragma + "INC2: {{USER2}}; {{>INCTHREE}}";
2048 string text_inc3 = "INC3: {{USER3}}; {{>INCFOUR}}";
2049 string text_inc4 = kAutoescapeHtmlPragma + "INC4: {{USER4}}";
2050 dict.SetValue("USER", user);
2051
2052 TemplateDictionary *dict_inc1 = dict.AddIncludeDictionary("INCONE");
2053 dict_inc1->SetFilename(StringToTemplateFile(text_inc1));
2054 dict_inc1->SetValue("USER1", user);
2055
2056 TemplateDictionary *dict_inc2 = dict_inc1->AddIncludeDictionary("INCTWO");
2057 filename = StringToTemplateFile(text_inc2);
2058 dict_inc2->SetFilename(filename);
2059 dict_inc2->SetValue("USER2", user);
2060
2061 TemplateDictionary *dict_inc3 = dict_inc2->AddIncludeDictionary("INCTHREE");
2062 dict_inc3->SetFilename(StringToTemplateFile(text_inc3));
2063 dict_inc3->SetValue("USER3", user);
2064
2065 TemplateDictionary *dict_inc4 = dict_inc3->AddIncludeDictionary("INCFOUR");
2066 filename = StringToTemplateFile(text_inc4);
2067 dict_inc4->SetFilename(filename);
2068 dict_inc4->SetValue("USER4", user);
2069
2070 tpl = StringToTemplate(text, strip);
2071 string expected_out = "Parent: " + user + "; INC1: " + user +
2072 "; INC2: " + user_esc + "; INC3: " + user + "; INC4: " + user_esc;
2073 AssertExpandIs(tpl, &dict, expected_out, true);
2074
2075 // Check that we do not modify template-includes.
2076 // Here, xml_escape would have been changed to :h:xml_escape
2077 // causing a double-escaping of the USER.
2078 text = kAutoescapeHtmlPragma + "{{>INC:xml_escape}}";
2079 inc_text = "{{USER}}";
2080 tpl = StringToTemplate(text, strip);
2081 ASSERT(tpl);
2082 TemplateDictionary dict3("dict");
2083 inc_dict = dict3.AddIncludeDictionary("INC");
2084 inc_dict->SetFilename(StringToTemplateFile(inc_text));
2085 inc_dict->SetValue("USER", user);
2086 AssertExpandIs(tpl, &dict3, user_esc, true);
2087
2088 // Test that {{%...}} is a "removable" marker. A related test is
2089 // also added to TestStrip().
2090 tpl = StringToTemplate("{{%AUTOESCAPE context=\"HTML\"}}\nText\n Text",
2091 STRIP_BLANK_LINES);
2092 AssertExpandIs(tpl, &dict, "Text\n Text", true);
2093}
2094
2095TEST(Template, RegisterString) {
2096 ASSERT(Template::StringToTemplateCache("file1", "Some text"));
2097 Template* tpl = Template::GetTemplate("file1", STRIP_WHITESPACE);
2098 ASSERT(tpl);
2099 ASSERT(Template::GetTemplate("file1", STRIP_WHITESPACE) == tpl);
2100
2101 ASSERT(Template::StringToTemplateCache("file2", "Top {{>INC}}"));
2102
2103 TemplateDictionary dict("dict");
2104 string expected = "Some text";
2105 AssertExpandIs(tpl, &dict, expected, true);
2106
2107 TemplateDictionary* sub_dict = dict.AddIncludeDictionary("INC");
2108 sub_dict->SetFilename("file1");
2109 tpl = Template::GetTemplate("file2", STRIP_WHITESPACE);
2110 expected = "Top Some text";
2111 AssertExpandIs(tpl, &dict, expected, true);
2112}
2113
2114// This tests that StaticTemplateString is sufficiently initialized at
2115// static-initialization time (as opposed to dynamic-initialization
2116// time, which comes later), that we can safely expand templates
2117// during dynamic initialization. This is worth testing, because some
2118// parts of a StaticTemplateString -- especially the hash value, *do*
2119// get computed later at dynamic-initialization time, and we want to
2120// make sure that things still work properly even if we access the
2121// StaticTemplateString before that happens.
2122extern const StaticTemplateString kLateDefine;
2123class DynamicInitializationTemplateExpander {
2124 public:
2125 DynamicInitializationTemplateExpander() {
2126 Template* tpl = Template::StringToTemplate("hi {{VAR}} lo",
2127 STRIP_WHITESPACE);
2128 TemplateDictionary dict("dict");
2129 dict.SetValue("VAR", TemplateString("short-lived", strlen("short")));
2130 AssertExpandIs(tpl, &dict, "hi short lo", true);
2131 dict.SetValue("VAR", kHello);
2132 AssertExpandIs(tpl, &dict, "hi Hello lo", true);
2133 dict.SetValue("VAR", kLateDefine);
2134 AssertExpandIs(tpl, &dict, "hi laterz lo", true);
2135 delete tpl;
2136 }
2137};
2138DynamicInitializationTemplateExpander sts_tester; // this runs before main()
2139const StaticTemplateString kLateDefine = STS_INIT(kLateDefine, "laterz");
2140
2141int main(int argc, char** argv) {
2142
2143 CreateOrCleanTestDirAndSetAsTmpdir(FLAGS_test_tmpdir);
2144
2145 // This goes first so that future tests don't mess up the filenames.
2146 // So we make it a normal function rather than using TEST() on it.
2147 TestAnnotation();
2148 return RUN_ALL_TESTS();
2149}