+
| value | description |
=number |
@@ -1149,12 +1181,6 @@ the javascript_escape_with_arg modifier:
-NOTE: At the moment, there are no filters for
-handling XML attributes and text nodes. For HTML snippets, use the
-html filter; in other situations, it may be appropriate to use CDATA
-blocks.
-
-
Custom Modifiers
In addition to the built-in modifiers, you can write your own
@@ -1480,7 +1506,7 @@ workflow: the common template use-case would be:
Template* tpl = Template::GetTemplate(filename, strip_mode);
TemplateDictionary dict(name);
- tpl->Expand(&dict, &outstring);
+ tpl->Expand(&dict, &outstring);
In current use, this model is deprecated in favor of the single
diff --git a/packages/deb/control b/packages/deb/control
index 0ddf5dc..5c2c8e1 100644
--- a/packages/deb/control
+++ b/packages/deb/control
@@ -1,7 +1,7 @@
Source: ctemplate
Section: libdevel
Priority: optional
-Maintainer: Google Inc.
+Maintainer: Google Inc.
Build-Depends: debhelper (>= 4.0.0)
Standards-Version: 3.6.1
diff --git a/packages/rpm/rpm.spec b/packages/rpm/rpm.spec
index 0ce14fc..123df93 100644
--- a/packages/rpm/rpm.spec
+++ b/packages/rpm/rpm.spec
@@ -10,7 +10,7 @@ Group: Development/Libraries
URL: http://code.google.com/p/google-ctemplate
License: BSD
Vendor: Google
-Packager: Google Inc.
+Packager: Google Inc.
Source: http://%{NAME}.googlecode.com/files/%{NAME}-%{VERSION}.tar.gz
Distribution: Redhat 7 and above.
Buildroot: %{_tmppath}/%{name}-root
diff --git a/src/ctemplate/per_expand_data.h.in b/src/ctemplate/per_expand_data.h.in
index b991f6b..14401b0 100644
--- a/src/ctemplate/per_expand_data.h.in
+++ b/src/ctemplate/per_expand_data.h.in
@@ -60,7 +60,10 @@ class @ac_windows_dllexport@ PerExpandData {
PerExpandData()
: annotate_path_(NULL),
annotator_(NULL),
- expand_modifier_(NULL) { }
+ expand_modifier_(NULL),
+ map_(NULL) { }
+
+ ~PerExpandData();
// Indicate that annotations should be inserted during template expansion.
// template_path_start - the start of a template path. When
@@ -110,16 +113,11 @@ class @ac_windows_dllexport@ PerExpandData {
// (see template_modifiers.h). Call with value set to NULL to clear
// any value previously set. Caller is responsible for ensuring key
// and value point to valid data for the lifetime of this object.
- void InsertForModifiers(const char* key, const void* value) {
- map_[key] = value;
- }
+ void InsertForModifiers(const char* key, const void* value);
// Retrieve data specific to this Expand call. Returns NULL if key
// is not found. This should only be used by template modifiers.
- const void* LookupForModifiers(const char* key) const {
- const DataMap::const_iterator it = map_.find(key);
- return it == map_.end() ? NULL : it->second;
- }
+ const void* LookupForModifiers(const char* key) const;
// Same as Lookup, but casts the result to a c string.
const char* LookupForModifiersAsString(const char* key) const {
@@ -140,7 +138,7 @@ class @ac_windows_dllexport@ PerExpandData {
const char* annotate_path_;
TemplateAnnotator* annotator_;
const TemplateModifier* expand_modifier_;
- DataMap map_;
+ DataMap* map_;
PerExpandData(const PerExpandData&); // disallow evil copy constructor
void operator=(const PerExpandData&); // disallow evil operator=
diff --git a/src/ctemplate/template.h.in b/src/ctemplate/template.h.in
index 5e2f5ea..502b89d 100644
--- a/src/ctemplate/template.h.in
+++ b/src/ctemplate/template.h.in
@@ -400,7 +400,7 @@ class @ac_windows_dllexport@ Template {
// Template markers have the form {{VARIABLE}}, etc. These constants
// define the {{ and }} that delimit template markers.
- struct MarkerDelimiters {
+ struct @ac_windows_dllexport@ MarkerDelimiters {
const char* start_marker;
size_t start_marker_len;
const char* end_marker;
@@ -415,7 +415,7 @@ class @ac_windows_dllexport@ Template {
};
// The current parsing state. Used in BuildTree() and subroutines
- struct ParseState {
+ struct @ac_windows_dllexport@ ParseState {
const char* bufstart;
const char* bufend;
enum { PS_UNUSED, GETTING_TEXT, GETTING_NAME } phase;
diff --git a/src/ctemplate/template_modifiers.h.in b/src/ctemplate/template_modifiers.h.in
index b681ad8..51e83ec 100644
--- a/src/ctemplate/template_modifiers.h.in
+++ b/src/ctemplate/template_modifiers.h.in
@@ -182,18 +182,31 @@ extern @ac_windows_dllexport@ CleanseCss cleanse_css;
// url that doesn't have a protocol hidden in it (ie [foo.html] is
// fine, but not [javascript:foo]) and then performs another type of
// escaping. Returns the url escaped with the specified modifier if
-// good, otherwise returns "#".
+// good, otherwise returns a safe replacement URL.
+// This is normally "#", but for
tags, it is not safe to set
+// the src attribute to "#". This is because this causes some browsers
+// to reload the page, which can cause a DoS.
class @ac_windows_dllexport@ ValidateUrl : public TemplateModifier {
public:
- explicit ValidateUrl(const TemplateModifier& chained_modifier)
- : chained_modifier_(chained_modifier) { }
+ explicit ValidateUrl(const TemplateModifier& chained_modifier,
+ const char* unsafe_url_replacement)
+ : chained_modifier_(chained_modifier),
+ unsafe_url_replacement_(unsafe_url_replacement),
+ unsafe_url_replacement_length_(strlen(unsafe_url_replacement)) { }
MODIFY_SIGNATURE_;
+ static const char* const kUnsafeUrlReplacement;
+ static const char* const kUnsafeImgSrcUrlReplacement;
private:
const TemplateModifier& chained_modifier_;
+ const char* unsafe_url_replacement_;
+ int unsafe_url_replacement_length_;
};
extern @ac_windows_dllexport@ ValidateUrl validate_url_and_html_escape;
extern @ac_windows_dllexport@ ValidateUrl validate_url_and_javascript_escape;
extern @ac_windows_dllexport@ ValidateUrl validate_url_and_css_escape;
+extern @ac_windows_dllexport@ ValidateUrl validate_img_src_url_and_html_escape;
+extern @ac_windows_dllexport@ ValidateUrl validate_img_src_url_and_javascript_escape;
+extern @ac_windows_dllexport@ ValidateUrl validate_img_src_url_and_css_escape;
// Escapes < > & " ' to < > & " ' (same as in HtmlEscape).
// If you use it within a CDATA section, you may be escaping more characters
diff --git a/src/make_tpl_varnames_h.cc b/src/make_tpl_varnames_h.cc
index 1321db2..5e9a78a 100644
--- a/src/make_tpl_varnames_h.cc
+++ b/src/make_tpl_varnames_h.cc
@@ -124,12 +124,12 @@ static void Version(FILE* outfile) {
// Removes all non alphanumeric characters from a string to form a
// valid C identifier to use as a double-inclusion guard.
-static void ConvertToIdentifier(string& s) {
- for (string::size_type i = 0; i < s.size(); i++) {
- if (!isalnum(s[i]))
- s[i] = '_';
+static void ConvertToIdentifier(string* s) {
+ for (string::size_type i = 0; i < s->size(); i++) {
+ if (!isalnum((*s)[i]))
+ (*s)[i] = '_';
else
- s[i] = toupper(s[i]);
+ (*s)[i] = toupper((*s)[i]);
}
}
@@ -256,7 +256,7 @@ int main(int argc, char **argv) {
"//\n");
string guard(string("TPL_") + header_file);
- ConvertToIdentifier(guard);
+ ConvertToIdentifier(&guard);
guard.append("_H_");
contents.append(string("#ifndef ") + guard + "\n");
diff --git a/src/per_expand_data.cc b/src/per_expand_data.cc
index 52b4f72..dbbe041 100644
--- a/src/per_expand_data.cc
+++ b/src/per_expand_data.cc
@@ -49,6 +49,10 @@ bool PerExpandData::DataEq::operator()(const char* s1, const char* s2) const {
}
#endif
+PerExpandData::~PerExpandData() {
+ delete map_;
+}
+
TemplateAnnotator* PerExpandData::annotator() const {
if (annotator_ != NULL) {
return annotator_;
@@ -59,4 +63,19 @@ TemplateAnnotator* PerExpandData::annotator() const {
return &g_default_annotator;
}
+void PerExpandData::InsertForModifiers(const char* key, const void* value) {
+ if (!map_)
+ map_ = new DataMap;
+ (*map_)[key] = value;
+}
+
+ // Retrieve data specific to this Expand call. Returns NULL if key
+ // is not found. This should only be used by template modifiers.
+const void* PerExpandData::LookupForModifiers(const char* key) const {
+ if (!map_)
+ return NULL;
+ const DataMap::const_iterator it = map_->find(key);
+ return it == map_->end() ? NULL : it->second;
+}
+
} // namespace ctemplate
diff --git a/src/template.cc b/src/template.cc
index 5b3b1fc..07a3fb6 100644
--- a/src/template.cc
+++ b/src/template.cc
@@ -35,8 +35,8 @@
#include "base/mutex.h" // This must go first so we get _XOPEN_SOURCE
#include
-#include
#include
+#include
#include // for fwrite, fflush
#include
#include
@@ -97,6 +97,15 @@ using HTMLPARSER_NAMESPACE::HtmlParser;
#define arraysize(x) ( sizeof(x) / sizeof(*(x)) )
+// TODO(csilvers): use our own tables for these?
+static bool ascii_isalnum(char c) {
+ return ((c & 0x80) == 0) && isalnum(c); // 7-bit ascii, and an alnum
+}
+
+static bool ascii_isspace(char c) {
+ return ((c & 0x80) == 0) && isspace(c); // 7-bit ascii, and a space
+}
+
TemplateId GlobalIdForSTS_INIT(const TemplateString& s) {
return s.GetGlobalId(); // normally this method is private
}
@@ -1779,7 +1788,7 @@ bool SectionTemplateNode::AddSubnode(Template *my_template) {
// nothing else.
static bool IsValidName(const char* name, int namelen) {
for (const char *cur_char = name; cur_char - name < namelen; ++cur_char) {
- if (!isalnum(*cur_char) && *cur_char != '_')
+ if (!ascii_isalnum(*cur_char) && *cur_char != '_')
return false;
}
return true;
@@ -2386,12 +2395,12 @@ bool Template::ParseDelimiters(const char* text, size_t textlen,
// so we can take a size_t instead of an int. The code is simple enough.
static void StripTemplateWhiteSpace(const char** str, size_t* len) {
// Strip off trailing whitespace.
- while ((*len) > 0 && isspace((*str)[(*len)-1])) {
+ while ((*len) > 0 && ascii_isspace((*str)[(*len)-1])) {
(*len)--;
}
// Strip off leading whitespace.
- while ((*len) > 0 && isspace((*str)[0])) {
+ while ((*len) > 0 && ascii_isspace((*str)[0])) {
(*len)--;
(*str)++;
}
diff --git a/src/template_modifiers.cc b/src/template_modifiers.cc
index 1f48e1b..241dc22 100644
--- a/src/template_modifiers.cc
+++ b/src/template_modifiers.cc
@@ -339,6 +339,11 @@ void CssUrlEscape::Modify(const char* in, size_t inlen,
}
CssUrlEscape css_url_escape;
+// These URLs replace unsafe URLs for :U and :I url-escaping modes.
+const char* const ValidateUrl::kUnsafeUrlReplacement = "#";
+const char* const ValidateUrl::kUnsafeImgSrcUrlReplacement =
+ "/images/cleardot.gif";
+
void ValidateUrl::Modify(const char* in, size_t inlen,
const PerExpandData* per_expand_data,
ExpandEmitter* out, const string& arg) const {
@@ -359,16 +364,35 @@ void ValidateUrl::Modify(const char* in, size_t inlen,
// and ftp
} else {
// It's a bad protocol, so return something safe
- chained_modifier_.Modify("#", 1, per_expand_data, out, "");
+ chained_modifier_.Modify(unsafe_url_replacement_,
+ unsafe_url_replacement_length_,
+ per_expand_data,
+ out,
+ "");
return;
}
}
// If we get here, it's a valid url, so just escape it
chained_modifier_.Modify(in, inlen, per_expand_data, out, "");
}
-ValidateUrl validate_url_and_html_escape(html_escape);
-ValidateUrl validate_url_and_javascript_escape(javascript_escape);
-ValidateUrl validate_url_and_css_escape(css_url_escape);
+ValidateUrl validate_url_and_html_escape(
+ html_escape,
+ ValidateUrl::kUnsafeUrlReplacement);
+ValidateUrl validate_url_and_javascript_escape(
+ javascript_escape,
+ ValidateUrl::kUnsafeUrlReplacement);
+ValidateUrl validate_url_and_css_escape(
+ css_url_escape,
+ ValidateUrl::kUnsafeUrlReplacement);
+ValidateUrl validate_img_src_url_and_html_escape(
+ html_escape,
+ ValidateUrl::kUnsafeImgSrcUrlReplacement);
+ValidateUrl validate_img_src_url_and_javascript_escape(
+ javascript_escape,
+ ValidateUrl::kUnsafeImgSrcUrlReplacement);
+ValidateUrl validate_img_src_url_and_css_escape(
+ css_url_escape,
+ ValidateUrl::kUnsafeImgSrcUrlReplacement);
void XmlEscape::Modify(const char* in, size_t inlen,
const PerExpandData*,
@@ -675,7 +699,9 @@ static struct ModifierWithAlternatives {
} g_modifiers[] = {
/* 0 */ { ModifierInfo("cleanse_css", 'c',
XSS_WEB_STANDARD, &cleanse_css),
- {&g_modifiers[16].modifier_info} }, // url_escape_with_arg=css
+ {&g_modifiers[16].modifier_info, // url_escape_with_arg=css
+ // img_src_url_escape_with_arg=css
+ &g_modifiers[19].modifier_info} },
/* 1 */ { ModifierInfo("html_escape", 'h',
XSS_WEB_STANDARD, &html_escape),
{&g_modifiers[2].modifier_info, // html_escape_with_arg=snippet
@@ -685,7 +711,9 @@ static struct ModifierWithAlternatives {
&g_modifiers[8].modifier_info, // pre_escape
&g_modifiers[9].modifier_info, // url_query_escape
&g_modifiers[11].modifier_info, // url_escape_with_arg=html
- &g_modifiers[12].modifier_info} }, // url_escape_with_arg=query
+ &g_modifiers[12].modifier_info, // url_escape_with_arg=query
+ // img_src_url_escape_with_arg=html
+ &g_modifiers[18].modifier_info} },
/* 2 */ { ModifierInfo("html_escape_with_arg=snippet", 'H',
XSS_WEB_STANDARD, &snippet_escape),
@@ -736,6 +764,15 @@ static struct ModifierWithAlternatives {
XSS_WEB_STANDARD, &javascript_number), {} },
/* 16 */ { ModifierInfo("url_escape_with_arg=css", 'U',
XSS_WEB_STANDARD, &validate_url_and_css_escape), {} },
+ /* 17 */ { ModifierInfo("img_src_url_escape_with_arg=javascript", 'I',
+ XSS_WEB_STANDARD,
+ &validate_img_src_url_and_javascript_escape), {} },
+ /* 18 */ { ModifierInfo("img_src_url_escape_with_arg=html", 'I',
+ XSS_WEB_STANDARD,
+ &validate_img_src_url_and_html_escape), {} },
+ /* 19 */ { ModifierInfo("img_src_url_escape_with_arg=css", 'I',
+ XSS_WEB_STANDARD,
+ &validate_img_src_url_and_css_escape), {} },
};
static vector g_extension_modifiers;
diff --git a/src/tests/template_cache_test.cc b/src/tests/template_cache_test.cc
index 0a1c4b4..a8b089c 100644
--- a/src/tests/template_cache_test.cc
+++ b/src/tests/template_cache_test.cc
@@ -876,6 +876,7 @@ class TemplateCacheUnittest {
AssertExpandIs(cache_tpl1, &dict, "{valid template}", true);
const Template* cache_tpl2 = cache.GetTemplate(filename2, DO_NOT_STRIP);
assert(cache_tpl2);
+ static_cast(cache_tpl2); // avoid unused var warning in opt mode
AssertExpandWithCacheIs(&cache, filename2, DO_NOT_STRIP, &dict, NULL,
"hi bar\n", true);
@@ -892,6 +893,7 @@ class TemplateCacheUnittest {
string filename3 = StringToTemplateFile("{yet another valid template}");
const Template* cache_tpl3 = cache.GetTemplate(filename3, STRIP_WHITESPACE);
assert(!cache_tpl3);
+ static_cast(cache_tpl3); // avoid unused var warning in opt mode
// 2. Reloading existing templates fails.
StringToFile("{file1 contents changed}", filename1);
diff --git a/src/tests/template_modifiers_unittest.cc b/src/tests/template_modifiers_unittest.cc
index 5872625..ebb18f5 100644
--- a/src/tests/template_modifiers_unittest.cc
+++ b/src/tests/template_modifiers_unittest.cc
@@ -230,9 +230,49 @@ class TemplateModifiersUnittest {
ASSERT_STREQ(peer.GetSectionValue("harder https URL"),
"https://www.google.com/search?q=f&hl=en");
ASSERT_STREQ(peer.GetSectionValue("easy javascript URL"),
- "#");
+ ctemplate::ValidateUrl::kUnsafeUrlReplacement);
ASSERT_STREQ(peer.GetSectionValue("harder javascript URL"),
- "#");
+ ctemplate::ValidateUrl::kUnsafeUrlReplacement);
+ ASSERT_STREQ(peer.GetSectionValue("easy relative URL"),
+ "foobar.html");
+ ASSERT_STREQ(peer.GetSectionValue("harder relative URL"),
+ "/search?q=green flowers&hl=en");
+ ASSERT_STREQ(peer.GetSectionValue("ftp URL"),
+ "ftp://ftp.example.org/pub/file.txt");
+ }
+
+ static void TestValidateImgSrcUrlHtmlEscape() {
+ TemplateDictionary dict("TestValidateImgSrcUrlHtmlEscape", NULL);
+ dict.SetEscapedValue("easy http URL", "http://www.google.com",
+ ctemplate::validate_img_src_url_and_html_escape);
+ dict.SetEscapedValue("harder https URL",
+ "https://www.google.com/search?q=f&hl=en",
+ ctemplate::validate_img_src_url_and_html_escape);
+ dict.SetEscapedValue("easy javascript URL",
+ "javascript:alert(document.cookie)",
+ ctemplate::validate_img_src_url_and_html_escape);
+ dict.SetEscapedValue("harder javascript URL",
+ "javascript:alert(10/5)",
+ ctemplate::validate_img_src_url_and_html_escape);
+ dict.SetEscapedValue("easy relative URL",
+ "foobar.html",
+ ctemplate::validate_img_src_url_and_html_escape);
+ dict.SetEscapedValue("harder relative URL",
+ "/search?q=green flowers&hl=en",
+ ctemplate::validate_img_src_url_and_html_escape);
+ dict.SetEscapedValue("ftp URL",
+ "ftp://ftp.example.org/pub/file.txt",
+ ctemplate::validate_img_src_url_and_html_escape);
+
+ TemplateDictionaryPeer peer(&dict); // peer can look inside the dict
+ ASSERT_STREQ(peer.GetSectionValue("easy http URL"),
+ "http://www.google.com");
+ ASSERT_STREQ(peer.GetSectionValue("harder https URL"),
+ "https://www.google.com/search?q=f&hl=en");
+ ASSERT_STREQ(peer.GetSectionValue("easy javascript URL"),
+ ctemplate::ValidateUrl::kUnsafeImgSrcUrlReplacement);
+ ASSERT_STREQ(peer.GetSectionValue("harder javascript URL"),
+ ctemplate::ValidateUrl::kUnsafeImgSrcUrlReplacement);
ASSERT_STREQ(peer.GetSectionValue("easy relative URL"),
"foobar.html");
ASSERT_STREQ(peer.GetSectionValue("harder relative URL"),
@@ -290,20 +330,84 @@ class TemplateModifiersUnittest {
"https://www.google.com/search?q\\x3df\\x26hl\\x3den");
ASSERT_STREQ(peer.GetSectionValue("mangled http URL"),
"HTTP://www.google.com");
- ASSERT_STREQ(peer.GetSectionValue("easy javascript URL"),
- "#");
+ ASSERT_STREQ(peer.GetSectionValue("easy javascript URL"), "#");
ASSERT_STREQ(peer.GetSectionValue("harder javascript URL"),
- "#");
+ ctemplate::ValidateUrl::kUnsafeUrlReplacement);
ASSERT_STREQ(peer.GetSectionValue("easy relative URL"),
"foobar.html");
ASSERT_STREQ(peer.GetSectionValue("harder relative URL"),
"/search?q\\x3dgreen flowers\\x26hl\\x3den");
ASSERT_STREQ(peer.GetSectionValue("data URL"),
- "#");
+ ctemplate::ValidateUrl::kUnsafeUrlReplacement);
ASSERT_STREQ(peer.GetSectionValue("mangled javascript URL"),
- "#");
+ ctemplate::ValidateUrl::kUnsafeUrlReplacement);
ASSERT_STREQ(peer.GetSectionValue("harder mangled javascript URL"),
- "#");
+ ctemplate::ValidateUrl::kUnsafeUrlReplacement);
+ }
+
+ static void TestValidateImgSrcUrlJavascriptEscape() {
+ TemplateDictionary dict("TestValidateImgSrcUrlJavascriptEscape", NULL);
+ dict.SetEscapedValue(
+ "easy http URL", "http://www.google.com",
+ ctemplate::validate_img_src_url_and_javascript_escape);
+ dict.SetEscapedValue(
+ "harder https URL",
+ "https://www.google.com/search?q=f&hl=en",
+ ctemplate::validate_img_src_url_and_javascript_escape);
+ dict.SetEscapedValue(
+ "mangled http URL", "HTTP://www.google.com",
+ ctemplate::validate_img_src_url_and_javascript_escape);
+ dict.SetEscapedValue(
+ "easy javascript URL",
+ "javascript:alert(document.cookie)",
+ ctemplate::validate_img_src_url_and_javascript_escape);
+ dict.SetEscapedValue(
+ "harder javascript URL",
+ "javascript:alert(10/5)",
+ ctemplate::validate_img_src_url_and_javascript_escape);
+ dict.SetEscapedValue(
+ "easy relative URL",
+ "foobar.html",
+ ctemplate::validate_img_src_url_and_javascript_escape);
+ dict.SetEscapedValue(
+ "harder relative URL",
+ "/search?q=green flowers&hl=en",
+ ctemplate::validate_img_src_url_and_javascript_escape);
+ dict.SetEscapedValue(
+ "data URL",
+ "data: text/html",
+ ctemplate::validate_img_src_url_and_javascript_escape);
+ dict.SetEscapedValue(
+ "mangled javascript URL",
+ "javaSCRIPT:alert(5)",
+ ctemplate::validate_img_src_url_and_javascript_escape);
+ dict.SetEscapedValue(
+ "harder mangled javascript URL",
+ "java\nSCRIPT:alert(5)",
+ ctemplate::validate_img_src_url_and_javascript_escape);
+
+
+ TemplateDictionaryPeer peer(&dict); // peer can look inside the dict
+ ASSERT_STREQ(peer.GetSectionValue("easy http URL"),
+ "http://www.google.com");
+ ASSERT_STREQ(peer.GetSectionValue("harder https URL"),
+ "https://www.google.com/search?q\\x3df\\x26hl\\x3den");
+ ASSERT_STREQ(peer.GetSectionValue("mangled http URL"),
+ "HTTP://www.google.com");
+ ASSERT_STREQ(peer.GetSectionValue("easy javascript URL"),
+ ctemplate::ValidateUrl::kUnsafeImgSrcUrlReplacement);
+ ASSERT_STREQ(peer.GetSectionValue("harder javascript URL"),
+ ctemplate::ValidateUrl::kUnsafeImgSrcUrlReplacement);
+ ASSERT_STREQ(peer.GetSectionValue("easy relative URL"),
+ "foobar.html");
+ ASSERT_STREQ(peer.GetSectionValue("harder relative URL"),
+ "/search?q\\x3dgreen flowers\\x26hl\\x3den");
+ ASSERT_STREQ(peer.GetSectionValue("data URL"),
+ "/images/cleardot.gif");
+ ASSERT_STREQ(peer.GetSectionValue("mangled javascript URL"),
+ ctemplate::ValidateUrl::kUnsafeImgSrcUrlReplacement);
+ ASSERT_STREQ(peer.GetSectionValue("harder mangled javascript URL"),
+ ctemplate::ValidateUrl::kUnsafeImgSrcUrlReplacement);
}
static void TestValidateUrlCssEscape() {
@@ -327,7 +431,38 @@ class TemplateModifiersUnittest {
"http://www.google.com");
ASSERT_STREQ(peer.GetSectionValue("harder https URL"),
"https://www.google.com/search?q=f&hl=en");
- ASSERT_STREQ(peer.GetSectionValue("javascript URL"), "#");
+ ASSERT_STREQ(peer.GetSectionValue("javascript URL"),
+ ctemplate::ValidateUrl::kUnsafeUrlReplacement);
+ ASSERT_STREQ(peer.GetSectionValue("relative URL"),
+ "/search?q=green flowers&hl=en");
+ ASSERT_STREQ(peer.GetSectionValue("hardest URL"),
+ "http://www.google.com/s?q=%27bla%27"
+ "&a=%22%22&b=%28%3Ctag%3E%29&c=%2A%0D%0A%5C%5Cbla");
+ }
+
+ static void TestValidateImgSrcUrlCssEscape() {
+ TemplateDictionary dict("TestValidateImgSrcUrlCssEscape", NULL);
+ dict.SetEscapedValue("easy http URL", "http://www.google.com",
+ ctemplate::validate_img_src_url_and_css_escape);
+ dict.SetEscapedValue("harder https URL",
+ "https://www.google.com/search?q=f&hl=en",
+ ctemplate::validate_img_src_url_and_css_escape);
+ dict.SetEscapedValue("javascript URL",
+ "javascript:alert(document.cookie)",
+ ctemplate::validate_img_src_url_and_css_escape);
+ dict.SetEscapedValue("relative URL", "/search?q=green flowers&hl=en",
+ ctemplate::validate_img_src_url_and_css_escape);
+ dict.SetEscapedValue("hardest URL", "http://www.google.com/s?q='bla'"
+ "&a=\"\"&b=()&c=*\r\n\\\\bla",
+ ctemplate::validate_img_src_url_and_css_escape);
+
+ TemplateDictionaryPeer peer(&dict); // peer can look inside the dict
+ ASSERT_STREQ(peer.GetSectionValue("easy http URL"),
+ "http://www.google.com");
+ ASSERT_STREQ(peer.GetSectionValue("harder https URL"),
+ "https://www.google.com/search?q=f&hl=en");
+ ASSERT_STREQ(peer.GetSectionValue("javascript URL"),
+ ctemplate::ValidateUrl::kUnsafeImgSrcUrlReplacement);
ASSERT_STREQ(peer.GetSectionValue("relative URL"),
"/search?q=green flowers&hl=en");
ASSERT_STREQ(peer.GetSectionValue("hardest URL"),
@@ -900,8 +1035,11 @@ int main(int argc, char** argv) {
TemplateModifiersUnittest::TestPreEscape();
TemplateModifiersUnittest::TestXmlEscape();
TemplateModifiersUnittest::TestValidateUrlHtmlEscape();
+ TemplateModifiersUnittest::TestValidateImgSrcUrlHtmlEscape();
TemplateModifiersUnittest::TestValidateUrlJavascriptEscape();
+ TemplateModifiersUnittest::TestValidateImgSrcUrlJavascriptEscape();
TemplateModifiersUnittest::TestValidateUrlCssEscape();
+ TemplateModifiersUnittest::TestValidateImgSrcUrlCssEscape();
TemplateModifiersUnittest::TestCleanseAttribute();
TemplateModifiersUnittest::TestCleanseCss();
TemplateModifiersUnittest::TestJavascriptEscape();
diff --git a/src/windows/ctemplate/per_expand_data.h b/src/windows/ctemplate/per_expand_data.h
index 0cbd6de..d72961a 100644
--- a/src/windows/ctemplate/per_expand_data.h
+++ b/src/windows/ctemplate/per_expand_data.h
@@ -65,7 +65,10 @@ class CTEMPLATE_DLL_DECL PerExpandData {
PerExpandData()
: annotate_path_(NULL),
annotator_(NULL),
- expand_modifier_(NULL) { }
+ expand_modifier_(NULL),
+ map_(NULL) { }
+
+ ~PerExpandData();
// Indicate that annotations should be inserted during template expansion.
// template_path_start - the start of a template path. When
@@ -115,16 +118,11 @@ class CTEMPLATE_DLL_DECL PerExpandData {
// (see template_modifiers.h). Call with value set to NULL to clear
// any value previously set. Caller is responsible for ensuring key
// and value point to valid data for the lifetime of this object.
- void InsertForModifiers(const char* key, const void* value) {
- map_[key] = value;
- }
+ void InsertForModifiers(const char* key, const void* value);
// Retrieve data specific to this Expand call. Returns NULL if key
// is not found. This should only be used by template modifiers.
- const void* LookupForModifiers(const char* key) const {
- const DataMap::const_iterator it = map_.find(key);
- return it == map_.end() ? NULL : it->second;
- }
+ const void* LookupForModifiers(const char* key) const;
// Same as Lookup, but casts the result to a c string.
const char* LookupForModifiersAsString(const char* key) const {
@@ -145,7 +143,7 @@ class CTEMPLATE_DLL_DECL PerExpandData {
const char* annotate_path_;
TemplateAnnotator* annotator_;
const TemplateModifier* expand_modifier_;
- DataMap map_;
+ DataMap* map_;
PerExpandData(const PerExpandData&); // disallow evil copy constructor
void operator=(const PerExpandData&); // disallow evil operator=
diff --git a/src/windows/ctemplate/template.h b/src/windows/ctemplate/template.h
index e493104..ab01407 100644
--- a/src/windows/ctemplate/template.h
+++ b/src/windows/ctemplate/template.h
@@ -405,7 +405,7 @@ class CTEMPLATE_DLL_DECL Template {
// Template markers have the form {{VARIABLE}}, etc. These constants
// define the {{ and }} that delimit template markers.
- struct MarkerDelimiters {
+ struct CTEMPLATE_DLL_DECL MarkerDelimiters {
const char* start_marker;
size_t start_marker_len;
const char* end_marker;
@@ -420,7 +420,7 @@ class CTEMPLATE_DLL_DECL Template {
};
// The current parsing state. Used in BuildTree() and subroutines
- struct ParseState {
+ struct CTEMPLATE_DLL_DECL ParseState {
const char* bufstart;
const char* bufend;
enum { PS_UNUSED, GETTING_TEXT, GETTING_NAME } phase;
diff --git a/src/windows/ctemplate/template_cache.h b/src/windows/ctemplate/template_cache.h
index 14a8d66..5cef34e 100644
--- a/src/windows/ctemplate/template_cache.h
+++ b/src/windows/ctemplate/template_cache.h
@@ -53,6 +53,11 @@ struct stat;
// as a compiler flag in your project file to turn off the dllimports.
#ifndef CTEMPLATE_DLL_DECL
# define CTEMPLATE_DLL_DECL __declspec(dllimport)
+extern template class __declspec(dllimport) std::allocator;
+extern template class __declspec(dllimport) std::vector;
+#else
+template class __declspec(dllexport) std::allocator;
+template class __declspec(dllexport) std::vector;
#endif
namespace ctemplate {
diff --git a/src/windows/ctemplate/template_modifiers.h b/src/windows/ctemplate/template_modifiers.h
index cb5e28e..4975737 100644
--- a/src/windows/ctemplate/template_modifiers.h
+++ b/src/windows/ctemplate/template_modifiers.h
@@ -187,18 +187,31 @@ extern CTEMPLATE_DLL_DECL CleanseCss cleanse_css;
// url that doesn't have a protocol hidden in it (ie [foo.html] is
// fine, but not [javascript:foo]) and then performs another type of
// escaping. Returns the url escaped with the specified modifier if
-// good, otherwise returns "#".
+// good, otherwise returns a safe replacement URL.
+// This is normally "#", but for
tags, it is not safe to set
+// the src attribute to "#". This is because this causes some browsers
+// to reload the page, which can cause a DoS.
class CTEMPLATE_DLL_DECL ValidateUrl : public TemplateModifier {
public:
- explicit ValidateUrl(const TemplateModifier& chained_modifier)
- : chained_modifier_(chained_modifier) { }
+ explicit ValidateUrl(const TemplateModifier& chained_modifier,
+ const char* unsafe_url_replacement)
+ : chained_modifier_(chained_modifier),
+ unsafe_url_replacement_(unsafe_url_replacement),
+ unsafe_url_replacement_length_(strlen(unsafe_url_replacement)) { }
MODIFY_SIGNATURE_;
+ static const char* const kUnsafeUrlReplacement;
+ static const char* const kUnsafeImgSrcUrlReplacement;
private:
const TemplateModifier& chained_modifier_;
+ const char* unsafe_url_replacement_;
+ int unsafe_url_replacement_length_;
};
extern CTEMPLATE_DLL_DECL ValidateUrl validate_url_and_html_escape;
extern CTEMPLATE_DLL_DECL ValidateUrl validate_url_and_javascript_escape;
extern CTEMPLATE_DLL_DECL ValidateUrl validate_url_and_css_escape;
+extern CTEMPLATE_DLL_DECL ValidateUrl validate_img_src_url_and_html_escape;
+extern CTEMPLATE_DLL_DECL ValidateUrl validate_img_src_url_and_javascript_escape;
+extern CTEMPLATE_DLL_DECL ValidateUrl validate_img_src_url_and_css_escape;
// Escapes < > & " ' to < > & " ' (same as in HtmlEscape).
// If you use it within a CDATA section, you may be escaping more characters
diff --git a/src/windows/preprocess.sh b/src/windows/preprocess.sh
index af6bf34..c6d691f 100755
--- a/src/windows/preprocess.sh
+++ b/src/windows/preprocess.sh
@@ -58,6 +58,21 @@ DLLDEF_DEFINES="\
# define $DLLDEF_MACRO_NAME __declspec(dllimport)\n\
#endif"
+# template_cache.h gets a special DEFINE to work around the
+# difficulties in dll-exporting stl containers. Ugh.
+TEMPLATE_CACHE_DLLDEF_DEFINES="\
+// NOTE: if you are statically linking the template library into your binary\n\
+// (rather than using the template .dll), set '/D $DLLDEF_MACRO_NAME='\n\
+// as a compiler flag in your project file to turn off the dllimports.\n\
+#ifndef $DLLDEF_MACRO_NAME\n\
+# define $DLLDEF_MACRO_NAME __declspec(dllimport)\n\
+extern template class __declspec(dllimport) std::allocator;\n\
+extern template class __declspec(dllimport) std::vector;\n\
+#else\n\
+template class __declspec(dllexport) std::allocator;\n\
+template class __declspec(dllexport) std::vector;\n\
+#endif"
+
# Read all the windows config info into variables
# In order for the 'set' to take, this requires putting all in a subshell.
(
@@ -72,13 +87,16 @@ DLLDEF_DEFINES="\
echo "Processing $file"
outfile="$1/windows/ctemplate/`basename $file .in`"
+ if test "`basename $file`" = template_cache.h.in; then
+ MY_DLLDEF_DEFINES=$TEMPLATE_CACHE_DLLDEF_DEFINES
+ else
+ MY_DLLDEF_DEFINES=$DLLDEF_DEFINES
+ fi
+
# Besides replacing @...@, we also need to turn on dllimport
# We also need to replace hash by hash_compare (annoying we hard-code :-( )
- # Finally, we get rid of the header files that try to find @ac_cv_unit64@
- # We tell them by their comment: "a place @ac_cv_uint64@ might live".
- # Except it has a typo and says "unit64", so I check for both.
sed -e "s!@ac_windows_dllexport@!$DLLDEF_MACRO_NAME!g" \
- -e "s!@ac_windows_dllexport_defines@!$DLLDEF_DEFINES!g" \
+ -e "s!@ac_windows_dllexport_defines@!$MY_DLLDEF_DEFINES!g" \
-e "s!@ac_cv_cxx_hash_map@!$HASH_MAP_H!g" \
-e "s!@ac_cv_cxx_hash_set@!$HASH_SET_H!g" \
-e "s!@ac_cv_cxx_hash_map_class@!$HASH_NAMESPACE::hash_map!g" \