Support "forced" auto-escaping policy.
When the "forced auto-escaping" policy is in effect, only output formats
that support auto-escaping may be used; also, the `?no_esc` built-in and
the `<#noautoesc>` directive are disabled.
diff --git a/src/main/java/freemarker/core/BuiltInBannedWhenForcedAutoEscaping.java b/src/main/java/freemarker/core/BuiltInBannedWhenForcedAutoEscaping.java
new file mode 100644
index 0000000..c877fa8
--- /dev/null
+++ b/src/main/java/freemarker/core/BuiltInBannedWhenForcedAutoEscaping.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package freemarker.core;
+
+/**
+ * A built-in whose usage is banned when auto-escaping is in the "forced" state.
+ * This is just a marker; the actual checking is in {@code FTL.jj}.
+ */
+interface BuiltInBannedWhenForcedAutoEscaping {}
diff --git a/src/main/java/freemarker/core/BuiltInsForOutputFormatRelated.java b/src/main/java/freemarker/core/BuiltInsForOutputFormatRelated.java
index b4b9fe8..7397924 100644
--- a/src/main/java/freemarker/core/BuiltInsForOutputFormatRelated.java
+++ b/src/main/java/freemarker/core/BuiltInsForOutputFormatRelated.java
@@ -23,7 +23,7 @@
class BuiltInsForOutputFormatRelated {
- static class no_escBI extends AbstractConverterBI {
+ static class no_escBI extends AbstractConverterBI implements BuiltInBannedWhenForcedAutoEscaping {
@Override
protected TemplateModel calculateResult(String lho, MarkupOutputFormat outputFormat, Environment env)
diff --git a/src/main/java/freemarker/core/Configurable.java b/src/main/java/freemarker/core/Configurable.java
index 8b2e0e0..dfa6cce 100644
--- a/src/main/java/freemarker/core/Configurable.java
+++ b/src/main/java/freemarker/core/Configurable.java
@@ -2388,7 +2388,8 @@
* <br>String value: {@code "enable_if_default"} or {@code "enableIfDefault"} for
* {@link Configuration#ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY},
* {@code "enable_if_supported"} or {@code "enableIfSupported"} for
- * {@link Configuration#ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY}
+ * {@link Configuration#ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY},
+ * {@code "force" for {@link Configuration#FORCE_AUTO_ESCAPING_POLICY}, or
* {@code "disable"} for {@link Configuration#DISABLE_AUTO_ESCAPING_POLICY}.
*
* <li><p>{@code "default_encoding"}:
diff --git a/src/main/java/freemarker/template/Configuration.java b/src/main/java/freemarker/template/Configuration.java
index ed47cba..850cd63 100644
--- a/src/main/java/freemarker/template/Configuration.java
+++ b/src/main/java/freemarker/template/Configuration.java
@@ -432,7 +432,9 @@
public static final int ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY = 21;
/** Enable auto-escaping if the {@link OutputFormat} supports it. */
public static final int ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY = 22;
-
+ /** Require auto-escaping always. */
+ public static final int FORCE_AUTO_ESCAPING_POLICY = 23;
+
/** FreeMarker version 2.3.0 (an {@link #Configuration(Version) incompatible improvements break-point}) */
public static final Version VERSION_2_3_0 = new Version(2, 3, 0);
@@ -2121,7 +2123,8 @@
*
* @param autoEscapingPolicy
* One of the {@link #ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY},
- * {@link #ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY}, and {@link #DISABLE_AUTO_ESCAPING_POLICY} constants.
+ * {@link #ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY}, {@link #DISABLE_AUTO_ESCAPING_POLICY}, and
+ * {@link #FORCE_AUTO_ESCAPING_POLICY} constants.
*
* @see TemplateConfiguration#setAutoEscapingPolicy(int)
* @see Configuration#setOutputFormat(OutputFormat)
@@ -3317,6 +3320,8 @@
setAutoEscapingPolicy(ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY);
} else if ("enable_if_supported".equals(value) || "enableIfSupported".equals(value)) {
setAutoEscapingPolicy(ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY);
+ } else if ("force".equals(value)) {
+ setAutoEscapingPolicy(FORCE_AUTO_ESCAPING_POLICY);
} else if ("disable".equals(value)) {
setAutoEscapingPolicy(DISABLE_AUTO_ESCAPING_POLICY);
} else {
diff --git a/src/main/java/freemarker/template/_TemplateAPI.java b/src/main/java/freemarker/template/_TemplateAPI.java
index a43842f..01d8402 100644
--- a/src/main/java/freemarker/template/_TemplateAPI.java
+++ b/src/main/java/freemarker/template/_TemplateAPI.java
@@ -182,10 +182,12 @@
public static void validateAutoEscapingPolicyValue(int autoEscaping) {
if (autoEscaping != Configuration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY
&& autoEscaping != Configuration.ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY
+ && autoEscaping != Configuration.FORCE_AUTO_ESCAPING_POLICY
&& autoEscaping != Configuration.DISABLE_AUTO_ESCAPING_POLICY) {
throw new IllegalArgumentException("\"auto_escaping\" can only be set to one of these: "
+ "Configuration.ENABLE_AUTO_ESCAPING_IF_DEFAULT, "
+ "or Configuration.ENABLE_AUTO_ESCAPING_IF_SUPPORTED"
+ + "or Configuration.FORCE_AUTO_ESCAPING_POLICY"
+ "or Configuration.DISABLE_AUTO_ESCAPING");
}
}
diff --git a/src/main/javacc/FTL.jj b/src/main/javacc/FTL.jj
index a6ee15b..a64e76d 100644
--- a/src/main/javacc/FTL.jj
+++ b/src/main/javacc/FTL.jj
@@ -237,6 +237,11 @@
outputFormat = outputFormatFromExt;
}
}
+ if (!(outputFormat instanceof MarkupOutputFormat)
+ && autoEscapingPolicy == Configuration.FORCE_AUTO_ESCAPING_POLICY) {
+ throw new IllegalArgumentException(
+ "Non-markup output format cannot be used when auto-escaping is forced.");
+ }
recalculateAutoEscapingField();
token_source.setParser(this);
@@ -358,7 +363,8 @@
if (outputFormat instanceof MarkupOutputFormat) {
if (autoEscapingPolicy == Configuration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY) {
autoEscaping = ((MarkupOutputFormat) outputFormat).isAutoEscapedByDefault();
- } else if (autoEscapingPolicy == Configuration.ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY) {
+ } else if (autoEscapingPolicy == Configuration.ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY
+ || autoEscapingPolicy == Configuration.FORCE_AUTO_ESCAPING_POLICY) {
autoEscaping = true;
} else if (autoEscapingPolicy == Configuration.DISABLE_AUTO_ESCAPING_POLICY) {
autoEscaping = false;
@@ -2214,16 +2220,23 @@
}
if (result instanceof BuiltInBannedWhenAutoEscaping) {
- if (outputFormat instanceof MarkupOutputFormat && autoEscaping) {
- throw new ParseException(
- "Using ?" + t.image + " (legacy escaping) is not allowed when auto-escaping is on with "
- + "a markup output format (" + outputFormat.getName() + "), to avoid double-escaping mistakes.",
- template, t);
- }
+ if (outputFormat instanceof MarkupOutputFormat && autoEscaping) {
+ throw new ParseException(
+ "Using ?" + t.image + " (legacy escaping) is not allowed when auto-escaping is on with "
+ + "a markup output format (" + outputFormat.getName() + "), to avoid double-escaping mistakes.",
+ template, t);
+ }
return result;
}
+ if (result instanceof BuiltInBannedWhenForcedAutoEscaping) {
+ if (autoEscapingPolicy == Configuration.FORCE_AUTO_ESCAPING_POLICY) {
+ throw new ParseException(
+ "Using ?" + t.image + " is not allowed while auto-escaping is forced.", template, t);
+ }
+ }
+
if (result instanceof MarkupOutputFormatBoundBuiltIn) {
if (!(outputFormat instanceof MarkupOutputFormat)) {
throw new ParseException(
@@ -4046,6 +4059,11 @@
} else {
outputFormat = template.getConfiguration().getOutputFormat(paramStr);
}
+ if (!(outputFormat instanceof MarkupOutputFormat)
+ && autoEscapingPolicy == Configuration.FORCE_AUTO_ESCAPING_POLICY) {
+ throw new ParseException(
+ "Non-markup output format cannot be used when auto-escaping is forced.", template, start);
+ }
recalculateAutoEscapingField();
} catch (IllegalArgumentException e) {
throw new ParseException("Invalid format name: " + e.getMessage(), template, start, e.getCause());
@@ -4100,6 +4118,10 @@
{
start = <NOAUTOESC>
{
+ if (autoEscapingPolicy == Configuration.FORCE_AUTO_ESCAPING_POLICY) {
+ throw new ParseException(
+ "Auto-escaping mode is forced; <#noautoesc> cannot be used.", template, start);
+ }
lastAutoEscapingPolicy = autoEscapingPolicy;
autoEscapingPolicy = Configuration.DISABLE_AUTO_ESCAPING_POLICY;
recalculateAutoEscapingField();
@@ -4539,6 +4561,10 @@
autoEscRequester = key;
autoEscapingPolicy = Configuration.ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY;
} else {
+ if (autoEscapingPolicy == Configuration.FORCE_AUTO_ESCAPING_POLICY) {
+ throw new ParseException(
+ "Auto-escaping is forced and cannot be disabled.", exp);
+ }
autoEscapingPolicy = Configuration.DISABLE_AUTO_ESCAPING_POLICY;
}
recalculateAutoEscapingField();
diff --git a/src/test/java/freemarker/core/OutputFormatTest.java b/src/test/java/freemarker/core/OutputFormatTest.java
index 0c72faa..bb442fe 100644
--- a/src/test/java/freemarker/core/OutputFormatTest.java
+++ b/src/test/java/freemarker/core/OutputFormatTest.java
@@ -842,7 +842,29 @@
+ dExpted);
}
}
-
+
+ @Test
+ public void testForcedAutoEsc() throws Exception {
+ Configuration cfg = getConfiguration();
+ cfg.setRegisteredCustomOutputFormats(ImmutableList.of(
+ SeldomEscapedOutputFormat.INSTANCE, DummyOutputFormat.INSTANCE));
+ cfg.setAutoEscapingPolicy(Configuration.FORCE_AUTO_ESCAPING_POLICY);
+
+ String commonFTL = "${'.'} ${.autoEsc?c}";
+ String esced = "\\. true";
+
+ cfg.setOutputFormat(SeldomEscapedOutputFormat.INSTANCE);
+ assertOutput(commonFTL, esced);
+
+ cfg.setOutputFormat(DummyOutputFormat.INSTANCE);
+ assertOutput(commonFTL, esced);
+
+ cfg.setOutputFormat(DummyOutputFormat.INSTANCE);
+ assertErrorContains("<#outputformat 'plainText'></#outputformat>", "auto-escaping is forced");
+ assertErrorContains("<#noAutoEsc></#noAutoEsc>", "Auto-escaping mode is forced");
+ assertErrorContains("<#assign foo='bar'>${foo?noEsc}", "auto-escaping is forced");
+ }
+
@Test
public void testDynamicParsingBIsInherticContextOutputFormat() throws Exception {
// Dynamic parser BI-s are supposed to use the parserConfiguration of the calling template, and ignore anything