diff --git a/src/base/small_map.h b/src/base/small_map.h index d3cdb5d..9d4ea9d 100644 --- a/src/base/small_map.h +++ b/src/base/small_map.h @@ -66,13 +66,13 @@ _START_GOOGLE_NAMESPACE_ // map type has a "key_equal" member (hash_map does), then that // will be used by default. Otherwise you must specify this // manually. -// MapInitFunctor: A functor that takes a ManualConstructor* -// and uses it to initialize the map. This functor will be -// called at most once per small_map, when the map exceeds the -// threshold of kArraySize and we are about to copy values from -// the array to the map. The functor *must* call one of the Init() -// methods provided by ManualConstructor, since after it runs we -// assume that the NormalMap has been initialized. +// MapInit: A functor that takes a ManualConstructor* and uses it to +// initialize the map. This functor will be called at most once per +// small_map, when the map exceeds the threshold of kArraySize and we +// are about to copy values from the array to the map. The functor +// *must* call one of the Init() methods provided by +// ManualConstructor, since after it runs we assume that the NormalMap +// has been initialized. // // example: // small_map > days; @@ -83,6 +83,9 @@ _START_GOOGLE_NAMESPACE_ // days["thursday" ] = 4; // days["friday" ] = 5; // days["saturday" ] = 6; +// +// You should assume that small_map might invalidate all the iterators +// on any call to erase(), insert() and operator[]. template class small_map_default_init { public: @@ -105,7 +108,7 @@ class small_map { small_map() : size_(0), functor_(MapInit()) {} - small_map(const MapInit& functor) : size_(0), functor_(functor) {} + explicit small_map(const MapInit& functor) : size_(0), functor_(functor) {} // Allow copy-constructor and assignment, since STL allows them too. small_map(const small_map& src) { @@ -274,6 +277,7 @@ class small_map { return iterator(map()->find(key)); } } + const_iterator find(const key_type& key) const { key_equal compare; if (size_ >= 0) { @@ -288,11 +292,14 @@ class small_map { } } + // Invalidates iterators. data_type& operator[](const key_type& key) { key_equal compare; if (size_ >= 0) { - for (int i = 0; i < size_; i++) { + // operator[] searches backwards, favoring recently-added + // elements. + for (int i = size_-1; i >= 0; --i) { if (compare(array_[i]->first, key)) { return array_[i]->second; } @@ -309,6 +316,7 @@ class small_map { } } + // Invalidates iterators. std::pair insert(const value_type& x) { key_equal compare; @@ -319,7 +327,7 @@ class small_map { } } if (size_ == kArraySize) { - ConvertToRealMap(); + ConvertToRealMap(); // Invalidates all iterators! std::pair ret = map_->insert(x); return make_pair(iterator(ret.first), ret.second); } else { @@ -332,6 +340,7 @@ class small_map { } } + // Invalidates iterators. template void insert(InputIterator f, InputIterator l) { while (f != l) { @@ -381,16 +390,16 @@ class small_map { size_ = 0; } + // Invalidates iterators. void erase(const iterator& position) { if (size_ >= 0) { int i = position.array_iter_ - array_; - array_[i++].Destroy(); - // Move the rest of the array back one. - for (; i < size_; i++) { - array_[i - 1].Init(*array_[i]); - array_[i].Destroy(); - } + array_[i].Destroy(); --size_; + if (i != size_) { + array_[i].Init(*array_[size_]); + array_[size_].Destroy(); + } } else { map_->erase(position.hash_iter_); } @@ -440,6 +449,7 @@ class small_map { private: int size_; // negative = using hash_map + MapInit functor_; // We want to call constructors and destructors manually, but we don't diff --git a/src/ctemplate/template_pathops.h.in b/src/ctemplate/template_pathops.h.in index 30dc3ab..0192fac 100644 --- a/src/ctemplate/template_pathops.h.in +++ b/src/ctemplate/template_pathops.h.in @@ -52,6 +52,14 @@ bool @ac_windows_dllexport@ IsDirectory(const std::string& path); // ends in " void @ac_windows_dllexport@ NormalizeDirectory(std::string* dir); // appends / std::string @ac_windows_dllexport@ Basename(const std::string& path); +// Returns true iff text contains the word as a full word, i.e. delimited by one +// of [.,_-#*?:] on both the sides. +// This is used while loading a template, to check that the file's name matches +// the auto-escape mode specified by it. +// NOTE: This assumes that the word doesn't contain any of the delimiter +// characters. +bool @ac_windows_dllexport@ ContainsFullWord(const std::string& text, const std::string& word); + @ac_google_end_namespace@ #endif // TEMPLATE_TEMPLATE_PATHOPS_H_ diff --git a/src/template.cc b/src/template.cc index 07a3fb6..d5c0818 100644 --- a/src/template.cc +++ b/src/template.cc @@ -445,18 +445,19 @@ static const char *memmatch(const char *haystack, size_t haystack_len, // we ignore it and just rely on the LOG(WARNING) in the logs. static bool FilenameValidForContext(const string& filename, TemplateContext context) { - // TODO(jad): Improve by also checking for "word" boundaries. - if ( filename.find("css") != string::npos || - filename.find("stylesheet") != string::npos || - filename.find("style") != string::npos) { + string stripped_filename = Basename(filename); + + if (ctemplate::ContainsFullWord(stripped_filename, "css") || + ctemplate::ContainsFullWord(stripped_filename, "stylesheet") || + ctemplate::ContainsFullWord(stripped_filename, "style")) { if (context != TC_CSS) { LOG(WARNING) << "Template filename " << filename << " indicates CSS but given TemplateContext" << " was not TC_CSS." << endl; return false; } - } else if (filename.find("js") != string::npos || - filename.find("javascript") != string::npos) { + } else if (ctemplate::ContainsFullWord(stripped_filename, "js") || + ctemplate::ContainsFullWord(stripped_filename, "javascript")) { if (context != TC_JS) { LOG(WARNING) << "Template filename " << filename << " indicates javascript but given TemplateContext" @@ -489,15 +490,15 @@ static TemplateContext GetTemplateContextFromPragma( const string* context = pragma.GetAttributeValue("context"); if (context == NULL) return TC_MANUAL; - if (*context == "HTML") + if (*context == "HTML" || *context == "html") return TC_HTML; - else if (*context == "JAVASCRIPT") + else if (*context == "JAVASCRIPT" || *context == "javascript") return TC_JS; - else if (*context == "CSS") + else if (*context == "CSS" || *context == "css") return TC_CSS; - else if (*context == "JSON") + else if (*context == "JSON" || *context == "json") return TC_JSON; - else if (*context == "XML") + else if (*context == "XML" || *context == "xml") return TC_XML; return TC_MANUAL; } @@ -1940,7 +1941,8 @@ TemplateToken SectionTemplateNode::GetNextToken(Template *my_template) { const string* parser_state = pragma.GetAttributeValue("state"); bool in_tag = false; if (parser_state != NULL) { - if (context == TC_HTML && *parser_state == "IN_TAG") + if (context == TC_HTML && (*parser_state == "IN_TAG" || + *parser_state == "in_tag")) in_tag = true; else if (*parser_state != "default") FAIL("Unsupported state '" + *parser_state + diff --git a/src/template_cache.cc b/src/template_cache.cc index e9554a1..05c8276 100644 --- a/src/template_cache.cc +++ b/src/template_cache.cc @@ -311,8 +311,10 @@ TemplateCache::RefcountedTemplate* TemplateCache::GetTemplateLocked( // Create a new template, insert it into the cache under // template_cache_key, and DecRef() the old one to indicate // the cache no longer has a reference to it. - it->second.refcounted_tpl->DecRef(); const Template* tpl = new Template(filename, strip, this); + // DecRef after creating the new template since DecRef may free up + // the storage for filename, + it->second.refcounted_tpl->DecRef(); it->second = CachedTemplate(tpl, CachedTemplate::FILE_BASED); } it->second.should_reload = false; @@ -670,10 +672,7 @@ void TemplateCache::ReloadAllIfChanged(ReloadType reload_type) { it->second.should_reload = true; if (reload_type == IMMEDIATE_RELOAD) { const Template* tpl = it->second.refcounted_tpl->tpl(); - // TODO(csilvers): have template_file() return a TemplateString? - TemplateCacheKey template_cache_key = TemplateCacheKey( - TemplateString(tpl->template_file()).GetGlobalId(), tpl->strip()); - GetTemplateLocked(tpl->template_file(), tpl->strip(), template_cache_key); + GetTemplateLocked(tpl->template_file(), tpl->strip(), it->first); } } } diff --git a/src/template_dictionary.cc b/src/template_dictionary.cc index 2bc670e..13f0b4d 100644 --- a/src/template_dictionary.cc +++ b/src/template_dictionary.cc @@ -87,9 +87,8 @@ template void ArenaAllocator::deallocate(pointer p, size_type n) { arena_->Free(p, n * sizeof(T)); } -// TODO(csilvers): sizeof(void*) may be too big. But is probably ok. /*static*/ template const int ArenaAllocator::kAlignment = - (1 == sizeof(T) ? 1 : sizeof(void*)); + (1 == sizeof(T) ? 1 : BaseArena::kDefaultAlignment); // ---------------------------------------------------------------------- // TemplateDictionary::map_arena_init @@ -123,7 +122,8 @@ inline void TemplateDictionary::LazilyCreateDict(T** dict) { if (*dict != NULL) return; // Placement new: construct the map in the memory used by *dict. - void* buffer = arena_->AllocAligned(sizeof(**dict), sizeof(void*)); + void* buffer = arena_->AllocAligned(sizeof(**dict), + BaseArena::kDefaultAlignment); new (buffer) T(arena_); *dict = reinterpret_cast(buffer); } @@ -138,7 +138,8 @@ inline void TemplateDictionary::LazyCreateTemplateGlobalDict() { } inline TemplateDictionary::DictVector* TemplateDictionary::CreateDictVector() { - void* buffer = arena_->AllocAligned(sizeof(DictVector), sizeof(void*)); + void* buffer = arena_->AllocAligned(sizeof(DictVector), + BaseArena::kDefaultAlignment); // Placement new: construct the vector in the memory used by buffer. new (buffer) DictVector(arena_); return reinterpret_cast(buffer); @@ -149,7 +150,8 @@ inline TemplateDictionary* TemplateDictionary::CreateTemplateSubdict( UnsafeArena* arena, TemplateDictionary* parent_dict, TemplateDictionary* template_global_dict_owner) { - void* buffer = arena->AllocAligned(sizeof(TemplateDictionary), sizeof(void*)); + void* buffer = arena->AllocAligned(sizeof(TemplateDictionary), + BaseArena::kDefaultAlignment); // Placement new: construct the sub-tpl in the memory used by tplbuf. new (buffer) TemplateDictionary(name, arena, parent_dict, template_global_dict_owner); diff --git a/src/template_namelist.cc b/src/template_namelist.cc index d06f842..5f8f633 100644 --- a/src/template_namelist.cc +++ b/src/template_namelist.cc @@ -102,7 +102,9 @@ const TemplateNamelist::MissingListType& TemplateNamelist::GetMissingList( const string path = Template::FindTemplateFilename(*iter); if (path.empty() || access(path.c_str(), R_OK) != 0) { missing_list_->push_back(*iter); - std::cerr << "ERROR: Template file missing: " << path << std::endl; + std::cerr << "ERROR: Template file missing: " << *iter + << " at path: " << (path.empty() ? "(empty path)" : path) + << std::endl; } } } diff --git a/src/template_pathops.cc b/src/template_pathops.cc index 5ac70a4..15cd569 100644 --- a/src/template_pathops.cc +++ b/src/template_pathops.cc @@ -37,6 +37,7 @@ #include "config.h" #include #include // for isalpha, used on windows +#include // for strchr #include using std::string; @@ -109,5 +110,37 @@ string Basename(const string& path) { return path; // no path-separator found, so whole string is the basename } +bool ContainsFullWord(const string& text, const string& word) { + // List of delimiter characters to be considered. Please update the comment in + // the header file if you change this list. + static const char* delim = ".,_-#*?:"; + + const int inputlength = text.length(); + const int wordlength = word.length(); + + // corner cases + if (inputlength == 0 || wordlength == 0 || wordlength > inputlength) { + return false; + } + + int nextmatchpos = 0; // position from where search in the input string + while (nextmatchpos < inputlength) { + const int pos = text.find(word, nextmatchpos); + if (pos == string::npos) { + return false; // no match at all + } + + // if found, check that it is surrounded by delimiter characters. + bool pre_delimited = (pos == 0) || + (strchr(delim, text.at(pos - 1)) != NULL); + bool post_delimited = (pos >= inputlength - wordlength) || + (strchr(delim, text.at(pos + wordlength)) != NULL); + if (pre_delimited && post_delimited) return true; + + nextmatchpos = (pos + wordlength + 1); + } + + return false; +} _END_GOOGLE_NAMESPACE_ diff --git a/src/template_string.cc b/src/template_string.cc index 2994b4f..333b68d 100644 --- a/src/template_string.cc +++ b/src/template_string.cc @@ -216,15 +216,12 @@ struct TemplateStringHasher { assert(IsTemplateIdInitialized(id_b)); return hasher(id_a, id_b); } - // static makes this compile under MSVC (shrug) - static const TemplateIdHasher hasher; + TemplateIdHasher hasher; // These two public members are required by msvc. 4 and 8 are defaults. static const size_t bucket_size = 4; static const size_t min_buckets = 8; }; -/*static*/ const TemplateIdHasher TemplateStringHasher::hasher = {}; - namespace { Mutex mutex(Mutex::LINKER_INITIALIZED); diff --git a/src/tests/template_unittest.cc b/src/tests/template_unittest.cc index 676bdf9..70416c9 100644 --- a/src/tests/template_unittest.cc +++ b/src/tests/template_unittest.cc @@ -1956,6 +1956,12 @@ class TemplateUnittest { expected_mods = "URL:U=html\nCLASS:h:H=attribute\nCOLOR:c\n"; AssertCorrectModifiersInTemplate(tpl, text, expected_mods); + text = "{{%AUTOESCAPE context=\"HTML\" state=\"in_tag\"}}" // lowercase ok + "href=\"{{URL}}\" class={{CLASS:h}} style=\"font:{{COLOR}}\""; + ASSERT(tpl = StringToTemplate(text, strip)); + expected_mods = "URL:U=html\nCLASS:h:H=attribute\nCOLOR:c\n"; + AssertCorrectModifiersInTemplate(tpl, text, expected_mods); + // Repeat the test with trailing HTML that closes the tag. This is // undefined behavior. We test it to ensure the parser does not choke. text += ">HelloSome text"; @@ -1987,6 +1993,12 @@ class TemplateUnittest { expected_mods = "VAL:xml_escape\nDATA:h\n"; AssertCorrectModifiersInTemplate(tpl, text, expected_mods); + text = "{{%AUTOESCAPE context=\"xml\"}}" // lower-case XML + "{{DATA:h}}"; + ASSERT(tpl = StringToTemplate(text, strip)); + expected_mods = "VAL:xml_escape\nDATA:h\n"; + AssertCorrectModifiersInTemplate(tpl, text, expected_mods); + text = "{{!bla}}{{%AUTOESCAPE context=\"HTML\"}}"; // after comment ASSERT(tpl = StringToTemplate(text, strip)); text = "{{%AUTOESCAPE context=\"HTML\" state=\"default\"}}"; @@ -2034,7 +2046,7 @@ class TemplateUnittest { ASSERT((tpl = StringToTemplate(text, strip)) == NULL); text = "{{%AUTOESCAPE context=\"HTML\" }}"; // extra whitesp ASSERT((tpl = StringToTemplate(text, strip)) == NULL); - text = "{{%AUTOESCAPE context=\"xml\"}}"; // lower-case xml + text = "{{%AUTOESCAPE context=\"Xml\"}}"; // mixed-case xml ASSERT((tpl = StringToTemplate(text, strip)) == NULL); text = "{{%AUTOESCAPE context=\"HTML\" state=\"tag\"}}"; // bad state ASSERT((tpl = StringToTemplate(text, strip)) == NULL); diff --git a/src/windows/ctemplate/template_pathops.h b/src/windows/ctemplate/template_pathops.h index ed29333..34a9581 100644 --- a/src/windows/ctemplate/template_pathops.h +++ b/src/windows/ctemplate/template_pathops.h @@ -57,6 +57,14 @@ bool CTEMPLATE_DLL_DECL IsDirectory(const std::string& path); // ends in "/"? void CTEMPLATE_DLL_DECL NormalizeDirectory(std::string* dir); // appends / std::string CTEMPLATE_DLL_DECL Basename(const std::string& path); +// Returns true iff text contains the word as a full word, i.e. delimited by one +// of [.,_-#*?:] on both the sides. +// This is used while loading a template, to check that the file's name matches +// the auto-escape mode specified by it. +// NOTE: This assumes that the word doesn't contain any of the delimiter +// characters. +bool CTEMPLATE_DLL_DECL ContainsFullWord(const std::string& text, const std::string& word); + } #endif // TEMPLATE_TEMPLATE_PATHOPS_H_