Brian Silverman | 70325d6 | 2015-09-20 17:00:43 -0400 | [diff] [blame^] | 1 | // 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" |
| 61 | TEST_INIT // defines RUN_ALL_TESTS() |
| 62 | |
| 63 | using std::vector; |
| 64 | using std::string; |
| 65 | using GOOGLE_NAMESPACE::FLAGS_test_tmpdir; |
| 66 | |
| 67 | using GOOGLE_NAMESPACE::AssertExpandIs; |
| 68 | using GOOGLE_NAMESPACE::AssertExpandWithDataIs; |
| 69 | using GOOGLE_NAMESPACE::CreateOrCleanTestDir; |
| 70 | using GOOGLE_NAMESPACE::CreateOrCleanTestDirAndSetAsTmpdir; |
| 71 | using GOOGLE_NAMESPACE::DO_NOT_STRIP; |
| 72 | using GOOGLE_NAMESPACE::ExpandEmitter; |
| 73 | using GOOGLE_NAMESPACE::IsAbspath; |
| 74 | using GOOGLE_NAMESPACE::Now; |
| 75 | using GOOGLE_NAMESPACE::PathJoin; |
| 76 | using GOOGLE_NAMESPACE::PerExpandData; |
| 77 | using GOOGLE_NAMESPACE::STRIP_BLANK_LINES; |
| 78 | using GOOGLE_NAMESPACE::STRIP_WHITESPACE; |
| 79 | using GOOGLE_NAMESPACE::StaticTemplateString; |
| 80 | using GOOGLE_NAMESPACE::StringToFile; |
| 81 | using GOOGLE_NAMESPACE::StringToTemplate; |
| 82 | using GOOGLE_NAMESPACE::StringToTemplateFile; |
| 83 | using GOOGLE_NAMESPACE::Strip; |
| 84 | using GOOGLE_NAMESPACE::TC_CSS; |
| 85 | using GOOGLE_NAMESPACE::TC_HTML; |
| 86 | using GOOGLE_NAMESPACE::TC_JS; |
| 87 | using GOOGLE_NAMESPACE::TC_JSON; |
| 88 | using GOOGLE_NAMESPACE::TC_MANUAL; |
| 89 | using GOOGLE_NAMESPACE::TC_UNUSED; |
| 90 | using GOOGLE_NAMESPACE::TC_XML; |
| 91 | using GOOGLE_NAMESPACE::Template; |
| 92 | using GOOGLE_NAMESPACE::TemplateContext; |
| 93 | using GOOGLE_NAMESPACE::TemplateDictionary; |
| 94 | using GOOGLE_NAMESPACE::TemplateNamelist; |
| 95 | using GOOGLE_NAMESPACE::TemplateString; |
| 96 | using GOOGLE_NAMESPACE::kRootdir; |
| 97 | |
| 98 | using GOOGLE_NAMESPACE::ExpandTemplate; |
| 99 | using GOOGLE_NAMESPACE::ExpandWithData; |
| 100 | using GOOGLE_NAMESPACE::StringToTemplateCache; |
| 101 | |
| 102 | static const StaticTemplateString kHello = STS_INIT(kHello, "Hello"); |
| 103 | static const StaticTemplateString kWorld = STS_INIT(kWorld, "World"); |
| 104 | |
| 105 | static const char* kPragmaHtml = "{{%AUTOESCAPE context=\"HTML\"}}\n"; |
| 106 | static const char* kPragmaJs = "{{%AUTOESCAPE context=\"JAVASCRIPT\"}}\n"; |
| 107 | static const char* kPragmaCss = "{{%AUTOESCAPE context=\"CSS\"}}\n"; |
| 108 | static const char* kPragmaXml = "{{%AUTOESCAPE context=\"XML\"}}\n"; |
| 109 | static 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 | |
| 136 | namespace { |
| 137 | |
| 138 | // First, (conceptually) remove all chars in "except" from both a and b. |
| 139 | // Then return true iff munged_a == munged_b. |
| 140 | bool 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. |
| 156 | bool 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 | |
| 167 | bool 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. |
| 178 | class 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 | |
| 190 | RegisterTemplateFilename(VALID1_FN, "template_unittest_test_valid1.in"); |
| 191 | RegisterTemplateFilename(INVALID1_FN, "template_unittest_test_invalid1.in"); |
| 192 | RegisterTemplateFilename(INVALID2_FN, "template_unittest_test_invalid2.in"); |
| 193 | RegisterTemplateFilename(NONEXISTENT_FN, "nonexistent__file.tpl"); |
| 194 | |
| 195 | // Returns the proper AUTOESCAPE pragma that corresponds to the |
| 196 | // given TemplateContext. |
| 197 | static 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. |
| 220 | static 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. |
| 231 | static 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. |
| 250 | static 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. |
| 262 | static 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 | |
| 273 | class 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 | |
| 286 | class 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. |
| 313 | class 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 | |
| 366 | class 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. |
| 379 | static 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 | |
| 519 | TEST(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. |
| 530 | TEST(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 | |
| 548 | TEST(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 | |
| 563 | TEST(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 | |
| 636 | TEST(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 | |
| 665 | TEST(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&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&yo lo", true); |
| 692 | |
| 693 | tpl = StringToTemplate("hi {{VAR:h:h}} lo", STRIP_WHITESPACE); |
| 694 | AssertExpandIs(tpl, &dict, "hi yo&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&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 | |
| 803 | TEST(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 | |
| 829 | TEST(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 | |
| 882 | TEST(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 | |
| 942 | TEST(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 & print file bar\n", true); |
| 963 | dict.AddIncludeDictionary("INC")->SetFilename(incname2); |
| 964 | AssertExpandIs(tpl1, &dict, "hi include & 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 & 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 & print file inc2 yo&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. |
| 984 | TEST(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 |
| 995 | TEST(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 | |
| 1026 | TEST(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 |
| 1038 | TEST(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 | |
| 1050 | TEST(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 | |
| 1066 | TEST(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 | |
| 1078 | TEST(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 | |
| 1110 | TEST(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 | |
| 1138 | TEST(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 | |
| 1161 | TEST(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 | |
| 1196 | TEST(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 | |
| 1236 | TEST(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 | |
| 1274 | TEST(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 | |
| 1297 | TEST(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. |
| 1315 | TEST(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 | |
| 1352 | TEST(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) |
| 1383 | struct 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. |
| 1390 | static 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 | |
| 1403 | TEST(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 |
| 1444 | TEST(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. |
| 1550 | TEST(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. |
| 1806 | TEST(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 <bad>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><BAD>FOO</BAD></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. |
| 1869 | TEST(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 | |
| 1884 | TEST(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<>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 | |
| 2095 | TEST(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. |
| 2122 | extern const StaticTemplateString kLateDefine; |
| 2123 | class 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 | }; |
| 2138 | DynamicInitializationTemplateExpander sts_tester; // this runs before main() |
| 2139 | const StaticTemplateString kLateDefine = STS_INIT(kLateDefine, "laterz"); |
| 2140 | |
| 2141 | int 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 | } |