blob: 499e7515b8ea37b653ed95bd719016aab9b961a6 [file] [log] [blame]
/*
* 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.cache;
import static freemarker.test.hamcerst.Matchers.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.io.IOException;
import java.util.Locale;
import org.junit.Test;
import com.google.common.collect.ImmutableList;
import freemarker.core.ParseException;
import freemarker.template.Configuration;
import freemarker.template.MalformedTemplateNameException;
import freemarker.template.Template;
import freemarker.template.TemplateNotFoundException;
import freemarker.test.MonitoredTemplateLoader;
public class TemplateNameFormatTest {
@Test
public void testToAbsoluteName() throws MalformedTemplateNameException {
// Path that are treated the same both in 2.3 and 2.4 format:
for (TemplateNameFormat tnf : new TemplateNameFormat[] {
TemplateNameFormat.DEFAULT_2_3_0, TemplateNameFormat.DEFAULT_2_4_0 }) {
// Relative paths:
// - No scheme:
assertEquals("a/b", tnf.toAbsoluteName("a/", "b"));
assertEquals("/a/b", tnf.toAbsoluteName("/a/", "b"));
assertEquals("a/b", tnf.toAbsoluteName("a/f", "b"));
assertEquals("/a/b", tnf.toAbsoluteName("/a/f", "b"));
// - Scheme:
assertEquals("s://a/b", tnf.toAbsoluteName("s://a/", "b"));
assertEquals("s:///a/b", tnf.toAbsoluteName("s:///a/", "b"));
assertEquals("s://a/b", tnf.toAbsoluteName("s://a/f", "b"));
assertEquals("s:///a/b", tnf.toAbsoluteName("s:///a/f", "b"));
assertEquals("s://b", tnf.toAbsoluteName("s://f", "b"));
assertEquals("s:///b", tnf.toAbsoluteName("s:///f", "b"));
// Absolute paths:
// - No scheme:
assertEquals("b", tnf.toAbsoluteName("a/", "/b"));
assertEquals("b", tnf.toAbsoluteName("/a/", "/b"));
assertEquals("b", tnf.toAbsoluteName("a/s:/f/", "/b"));
// - Scheme:
assertEquals("s://b", tnf.toAbsoluteName("s://x/", "/b"));
assertEquals("s://b", tnf.toAbsoluteName("s:///x/", "/b"));
// Schemed absolute paths:
assertEquals("s://b", tnf.toAbsoluteName("a/", "s://b"));
assertEquals("s://b", tnf.toAbsoluteName("i://a/", "s://b"));
}
// Scheme names in 2.4 format only:
{
final TemplateNameFormat tnf = TemplateNameFormat.DEFAULT_2_4_0;
assertEquals("s:b", tnf.toAbsoluteName("s:f", "b"));
assertEquals("s:/b", tnf.toAbsoluteName("s:/f", "b"));
assertEquals("s:b", tnf.toAbsoluteName("s:f", "/b"));
assertEquals("s:b", tnf.toAbsoluteName("s:/f", "/b"));
assertEquals("s:f/b", tnf.toAbsoluteName("s:f/", "b"));
assertEquals("s:/f/b", tnf.toAbsoluteName("s:/f/", "b"));
assertEquals("s:b", tnf.toAbsoluteName("s:f/", "/b"));
assertEquals("s:b", tnf.toAbsoluteName("s:/f/", "/b"));
assertEquals("s:b", tnf.toAbsoluteName("s:/f/", "/b"));
assertEquals("b", tnf.toAbsoluteName("a/s://f/", "/b"));
}
// Scheme names in 2.3 format only:
{
final TemplateNameFormat tnf = TemplateNameFormat.DEFAULT_2_3_0;
assertEquals("a/s://b", tnf.toAbsoluteName("a/s://f/", "/b"));
}
}
@Test
public void testNormalizeAbsoluteName() throws MalformedTemplateNameException {
// Normalizations that are the same in legacy and modern format:
for (TemplateNameFormat tnf : new TemplateNameFormat[] {
TemplateNameFormat.DEFAULT_2_3_0, TemplateNameFormat.DEFAULT_2_4_0 }) {
assertEquals("", tnf.normalizeAbsoluteName(""));
for (String lead : new String[] { "", "/" }) {
assertEquals("foo", tnf.normalizeAbsoluteName(lead + "foo"));
assertEquals("foo", tnf.normalizeAbsoluteName(lead + "./foo"));
assertEquals("foo", tnf.normalizeAbsoluteName(lead + "./././foo"));
assertEquals("foo", tnf.normalizeAbsoluteName(lead + "bar/../foo"));
assertEquals("a/b/", tnf.normalizeAbsoluteName("a/b/"));
assertEquals("a/", tnf.normalizeAbsoluteName("a/b/../"));
assertEquals("a/c../..d/e*/*f", tnf.normalizeAbsoluteName("a/c../..d/e*/*f"));
assertEquals("", tnf.normalizeAbsoluteName(""));
assertEquals("foo/bar/*", tnf.normalizeAbsoluteName("foo/bar/*"));
assertEquals("schema://", tnf.normalizeAbsoluteName("schema://"));
assertThrowsWithBackingOutException(lead + "bar/../../x/foo", tnf);
assertThrowsWithBackingOutException(lead + "../x", tnf);
assertThrowsWithBackingOutException(lead + "../../../x", tnf);
assertThrowsWithBackingOutException(lead + "../../../x", tnf);
assertThrowsWithBackingOutException("x://../../../foo", tnf);
{
final String name = lead + "foo\u0000";
try {
tnf.normalizeAbsoluteName(name);
fail();
} catch (MalformedTemplateNameException e) {
assertEquals(name, e.getTemplateName());
assertThat(e.getMalformednessDescription(), containsStringIgnoringCase("null character"));
}
}
}
}
// ".." and "."
assertEqualsOn23AndOn24("bar/foo", "foo", "bar/./../foo");
// Even number of leading ".."-s bug:
assertNormAbsNameEqualsOn23ButThrowsBackOutExcOn24("foo", "../../foo");
assertNormAbsNameEqualsOn23ButThrowsBackOutExcOn24("foo", "../../../../foo");
// ".." and "*"
assertEqualsOn23AndOn24("a/b/foo", "a/*/foo", "a/b/*/../foo");
//
assertEqualsOn23AndOn24("a/foo", "foo", "a/b/*/../../foo");
//
assertNormAbsNameEqualsOn23ButThrowsBackOutExcOn24("foo", "a/b/*/../../../foo");
//
assertEqualsOn23AndOn24("a/b/*/foo", "a/*/foo", "a/b/*/*/../foo");
//
assertEqualsOn23AndOn24("a/b/*/c/foo", "a/b/*/foo", "a/b/*/c/*/../foo");
//
assertEqualsOn23AndOn24("a/b/*/c/foo", "a/b/*/foo", "a/b/*/c/d/*/../../foo");
//
assertEqualsOn23AndOn24("a/*//b/*/c/foo", "a/*/b/*/foo", "a/*//b/*/c/d/*/../../foo");
//
assertEqualsOn23AndOn24("*", "", "a/../*");
//
assertEqualsOn23AndOn24("*/", "", "a/../*/");
// ".." and "scheme"
assertNormAbsNameEqualsOn23ButThrowsBackOutExcOn24("x:/foo", "x://../foo");
//
assertNormAbsNameEqualsOn23ButThrowsBackOutExcOn24("foo", "x://../../foo");
//
assertNormAbsNameEqualsOn23ButThrowsBackOutExcOn24("x:../foo", "x:../foo");
//
assertNormAbsNameEqualsOn23ButThrowsBackOutExcOn24("foo", "x:../../foo");
// Tricky cases with terminating "/":
assertEqualsOn23AndOn24("/", "", "/");
// Terminating "/.." (produces terminating "/"):
assertEqualsOn23AndOn24("foo/bar/..", "foo/", "foo/bar/..");
// Terminating "/." (produces terminating "/"):
assertEqualsOn23AndOn24("foo/bar/.", "foo/bar/", "foo/bar/.");
// Lonely "."
assertEqualsOn23AndOn24(".", "", ".");
// Lonely ".."
assertNormAbsNameEqualsOn23ButThrowsBackOutExcOn24("..", "..");
// Lonely "*"
// Eliminating redundant "//":
assertEqualsOn23AndOn24("foo//bar", "foo/bar", "foo//bar");
//
assertEqualsOn23AndOn24("///foo//bar///baaz////wombat", "foo/bar/baaz/wombat", "////foo//bar///baaz////wombat");
//
assertEqualsOn23AndOn24("scheme://foo", "scheme://foo", "scheme://foo");
//
assertEqualsOn23AndOn24("scheme://foo//x/y", "scheme://foo/x/y", "scheme://foo//x/y");
//
assertEqualsOn23AndOn24("scheme:///foo", "scheme://foo", "scheme:///foo");
//
assertEqualsOn23AndOn24("scheme:////foo", "scheme://foo", "scheme:////foo");
// Eliminating redundant "*"-s:
assertEqualsOn23AndOn24("a/*/*/b", "a/*/b", "a/*/*/b");
//
assertEqualsOn23AndOn24("a/*/*/*/b", "a/*/b", "a/*/*/*/b");
//
assertEqualsOn23AndOn24("*/*/b", "b", "*/*/b");
//
assertEqualsOn23AndOn24("*/*/b", "b", "/*/*/b");
//
assertEqualsOn23AndOn24("b/*/*", "b/*", "b/*/*");
//
assertEqualsOn23AndOn24("b/*/*/*", "b/*", "b/*/*/*");
//
assertEqualsOn23AndOn24("*/a/*/b/*/*/c", "a/*/b/*/c", "*/a/*/b/*/*/c");
// New kind of scheme handling:
assertEquals("s:a/b", TemplateNameFormat.DEFAULT_2_4_0.normalizeAbsoluteName("s:a/b"));
assertEquals("s:a/b", TemplateNameFormat.DEFAULT_2_4_0.normalizeAbsoluteName("s:/a/b"));
assertEquals("s://a/b", TemplateNameFormat.DEFAULT_2_4_0.normalizeAbsoluteName("s://a/b"));
assertEquals("s://a/b", TemplateNameFormat.DEFAULT_2_4_0.normalizeAbsoluteName("s:///a/b"));
assertEquals("s://a/b", TemplateNameFormat.DEFAULT_2_4_0.normalizeAbsoluteName("s:////a/b"));
// Illegal use a of ":":
assertNormAbsNameThrowsColonExceptionOn24("a/b:c/d");
assertNormAbsNameThrowsColonExceptionOn24("a/b:/..");
}
@Test
public void assertBackslashNotSpecialWith23() throws MalformedTemplateNameException, ParseException, IOException {
Configuration cfg = new Configuration(Configuration.VERSION_2_3_22);
MonitoredTemplateLoader tl = new MonitoredTemplateLoader();
tl.putTemplate("foo\\bar.ftl", "");
cfg.setTemplateLoader(tl);
{
final String name = "foo\\bar.ftl";
Template t = cfg.getTemplate(name, Locale.US);
assertEquals(name, t.getName());
assertEquals(name, t.getSourceName());
assertEquals(
ImmutableList.of(
"foo\\bar_en_US.ftl",
"foo\\bar_en.ftl",
name),
tl.getTemplatesTried());
tl.clear();
}
try {
cfg.getTemplate("foo\\missing.ftl", Locale.US);
fail();
} catch (TemplateNotFoundException e) {
assertEquals("foo\\missing.ftl", e.getTemplateName());
assertEquals(
ImmutableList.of(
"foo\\missing_en_US.ftl",
"foo\\missing_en.ftl",
"foo\\missing.ftl"),
tl.getTemplatesTried());
tl.clear();
cfg.clearTemplateCache();
}
{
final String name = "foo/bar\\..\\bar.ftl";
try {
cfg.getTemplate(name, Locale.US);
fail();
} catch (TemplateNotFoundException e) {
assertEquals(name, e.getTemplateName());
}
}
}
@Test
public void assertBackslashNotAllowedWith24() throws MalformedTemplateNameException, ParseException, IOException {
Configuration cfg = new Configuration(Configuration.VERSION_2_3_0);
cfg.setTemplateNameFormat(TemplateNameFormat.DEFAULT_2_4_0);
try {
cfg.getTemplate("././foo\\bar.ftl", Locale.US);
fail();
} catch (MalformedTemplateNameException e) {
assertThat(e.getMessage(), containsStringIgnoringCase("backslash"));
}
}
private void assertEqualsOn23AndOn24(String expected23, String expected24, String name)
throws MalformedTemplateNameException {
assertEquals(expected23, TemplateNameFormat.DEFAULT_2_3_0.normalizeAbsoluteName(name));
assertEquals(expected24, TemplateNameFormat.DEFAULT_2_4_0.normalizeAbsoluteName(name));
}
private void assertNormAbsNameEqualsOn23ButThrowsBackOutExcOn24(final String expected23, final String name)
throws MalformedTemplateNameException {
assertEquals(expected23, TemplateNameFormat.DEFAULT_2_3_0.normalizeAbsoluteName(name));
assertThrowsWithBackingOutException(name, TemplateNameFormat.DEFAULT_2_4_0);
}
private void assertThrowsWithBackingOutException(final String name, final TemplateNameFormat tnf) {
try {
tnf.normalizeAbsoluteName(name);
fail();
} catch (MalformedTemplateNameException e) {
assertEquals(name, e.getTemplateName());
assertBackingOutFromRootException(e);
}
}
private void assertNormAbsNameThrowsColonExceptionOn24(final String name) throws MalformedTemplateNameException {
try {
TemplateNameFormat.DEFAULT_2_4_0.normalizeAbsoluteName(name);
fail();
} catch (MalformedTemplateNameException e) {
assertEquals(name, e.getTemplateName());
assertColonException(e);
}
}
private void assertBackingOutFromRootException(MalformedTemplateNameException e) {
assertThat(e.getMessage(), containsStringIgnoringCase("backing out"));
}
private void assertColonException(MalformedTemplateNameException e) {
assertThat(e.getMessage(), containsString("':'"));
}
}