/* * Copyright (c) 2002-2012 Alibaba Group Holding Limited. * All rights reserved. * * Licensed 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 com.alibaba.citrus.service.velocity.support; import static com.alibaba.citrus.test.TestUtil.*; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; import java.lang.reflect.Method; import java.util.Locale; import java.util.Map; import com.alibaba.citrus.service.template.TemplateContext; import com.alibaba.citrus.service.template.TemplateException; import com.alibaba.citrus.service.template.support.MappedTemplateContext; import com.alibaba.citrus.service.velocity.AbstractVelocityEngineTests; import com.alibaba.citrus.service.velocity.VelocityEngineTests.Counter; import com.alibaba.citrus.service.velocity.VelocityEngineTests.MyRenderable; import com.alibaba.citrus.service.velocity.support.EscapeSupport.EscapeType; import com.alibaba.citrus.test.TestUtil; import com.alibaba.citrus.util.StringEscapeUtil; import com.alibaba.citrus.util.i18n.LocaleUtil; import org.apache.velocity.app.event.ReferenceInsertionEventHandler; import org.apache.velocity.exception.ParseErrorException; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.springframework.context.ApplicationContext; public class EscapeTests extends AbstractVelocityEngineTests { @BeforeClass public static void initFactory() { factory = createFactory("services_escape.xml"); } @Before public void initLocale() { LocaleUtil.setContext(Locale.CHINA, "GBK"); } @After public void resetLocale() { LocaleUtil.resetContext(); } @Test public void normalizeReference_() throws Exception { // empty assertEquals(" ", normalizeReference(" ")); assertEquals("", normalizeReference(null)); assertEquals("", normalizeReference("$ ")); assertEquals("", normalizeReference("$! ")); // 有{} assertEquals("\" a${b}c \"", normalizeReference(" $ { \" a${b}c \" } ")); assertEquals("\" a${b}c \"", normalizeReference(" $ ! { \" a${b}c \" } ")); // 无{} assertEquals("\" a${b}c \"", normalizeReference(" $ \" a${b}c \" ")); assertEquals("\" a${b}c \"", normalizeReference(" $ ! \" a${b}c \" ")); assertEquals("abc", normalizeReference(" $ ! abc ")); assertEquals("abc", normalizeReference(" $ abc ")); // 非法 assertEquals(" { \" a${b}c \" ", normalizeReference(" { \" a${b}c \" ")); } private String normalizeReference(String ref) throws Exception { Method method = TestUtil.getAccessibleMethod(EscapeSupport.class, "normalizeReference", new Class<?>[] { String.class }); return (String) method.invoke(null, ref); } @Test public void escape_directive_wrong() throws Exception { getEngine("with_escape", factory); TemplateContext ctx = new MappedTemplateContext(); try { templateService.getText("escape/test_escape_wrong_args_0.vm", ctx); fail(); } catch (TemplateException e) { assertThat( e, exception(ParseErrorException.class, "Error rendering Velocity template: /escape/test_escape_wrong_args_0.vm", "Invalid args for #escape. Expected 1 and only 1 string arg.")); } try { templateService.getText("escape/test_escape_wrong_args_1.vm", ctx); fail(); } catch (TemplateException e) { assertThat( e, exception(ParseErrorException.class, "Error rendering Velocity template: /escape/test_escape_wrong_args_1.vm", "Invalid args for #noescape. Expected 0 args.")); } try { templateService.getText("escape/test_escape_wrong_args_2.vm", ctx); fail(); } catch (TemplateException e) { assertThat( e, exception(ParseErrorException.class, "Error rendering Velocity template: /escape/test_escape_wrong_args_2.vm", "Invalid args for #escape. Expected 1 and only 1 string arg.")); } try { templateService.getText("escape/test_escape_wrong_type.vm", ctx); fail(); } catch (TemplateException e) { assertThat( e, exception(ParseErrorException.class, "Error rendering Velocity template: /escape/test_escape_wrong_type.vm", "Invalid escape type: unknown at /escape/test_escape_wrong_type.vm", "Available escape types: [noescape, java, javascript, html, xml, url, sql]")); } } @Test public void escape_directive() throws Exception { getEngine("with_escape", factory); TemplateContext ctx = new MappedTemplateContext(); ctx.put("count", new Counter()); ctx.put("object", new MyRenderable("<world name=\"中国\" />")); String content = templateService.getText("escape/test_escape.vm", ctx); // original assertThat(content, containsString("1. <world name=\"中国\" />")); // html assertThat(content, containsString("2. <world name="中国" />")); // javascript assertThat(content, containsString("3. <world name=\\\"中国\\\" \\/>")); // html (restored) assertThat(content, containsString("4. <world name="中国" />")); // noescape assertThat(content, containsString("5. <world name=\"中国\" />")); // url encoding assertThat(content, containsString("6. %3Cworld+name%3D%22%D6%D0%B9%FA%22+%2F%3E")); // noescape (restored) assertThat(content, containsString("7. <world name=\"中国\" />")); // html (restored) assertThat(content, containsString("8. <world name="中国" />")); // original (restored) assertThat(content, containsString("9. <world name=\"中国\" />")); assertFalse(ctx.containsKey("_ESCAPE_SUPPORT_TYPE_")); } @Test public void escape_directive_default() throws Exception { getEngine("with_defaultEscape", factory); TemplateContext ctx = new MappedTemplateContext(); ctx.put("count", new Counter()); ctx.put("object", new MyRenderable("<world name=\"中国\" />")); String content = templateService.getText("escape/test_escape.vm", ctx); // original - default html assertThat(content, containsString("1. <world name="中国" />")); // html assertThat(content, containsString("2. <world name="中国" />")); // javascript assertThat(content, containsString("3. <world name=\\\"中国\\\" \\/>")); // html (restored) assertThat(content, containsString("4. <world name="中国" />")); // noescape assertThat(content, containsString("5. <world name=\"中国\" />")); // url encoding assertThat(content, containsString("6. %3Cworld+name%3D%22%D6%D0%B9%FA%22+%2F%3E")); // noescape (restored) assertThat(content, containsString("7. <world name=\"中国\" />")); // html (restored) assertThat(content, containsString("8. <world name="中国" />")); // original (restored) - default html assertThat(content, containsString("9. <world name="中国" />")); assertFalse(ctx.containsKey("_ESCAPE_SUPPORT_TYPE_")); } @Test public void escape_directive_default_interplate() throws Exception { getEngine("with_defaultEscape", factory); TemplateContext ctx = new MappedTemplateContext(); ctx.put("count", new Counter()); ctx.put("object", new MyRenderable("<world name=\"中国\" />")); String content = templateService.getText("escape/test_escape_interpolation.vm", ctx); assertThat(content, containsString("1. <world name="中国" />")); } @Test public void escape_directive_parse() throws Exception { getEngine("with_escape", factory); TemplateContext ctx = new MappedTemplateContext(); ctx.put("count", new Counter()); ctx.put("object", new MyRenderable("<world name=\"中国\" />")); String content = templateService.getText("escape/test_escape_parse.vm", ctx); assertThat(content, containsString("1. <world name="中国" />")); // escape将会影响parse的结果:这未必是我们想要的结果,但目前没有办法。 assertThat(content, containsString("2. <world name="中国" />")); } @Test public void escape_directive_all_types() throws Exception { getEngine("with_escape", factory); TemplateContext ctx = new MappedTemplateContext(); ctx.put("count", new Counter()); ctx.put("object", new MyRenderable("<world name=\"'中国'\" />")); String content = templateService.getText("escape/test_escape_all_types.vm", ctx); // noescape assertThat(content, containsString("1. <world name=\"'中国'\" />")); // java assertThat(content, containsString("2. <world name=\\\"'中国'\\\" />")); // javascript assertThat(content, containsString("3. <world name=\\\"\\'中国\\'\\\" \\/>")); // html assertThat(content, containsString("4. <world name="'中国'" />")); // xml assertThat(content, containsString("5. <world name="'中国'" />")); // url assertThat(content, containsString("6. %3Cworld+name%3D%22'%D6%D0%B9%FA'%22+%2F%3E")); // sql assertThat(content, containsString("7. <world name=\"''中国''\" />")); assertFalse(ctx.containsKey("_ESCAPE_SUPPORT_TYPE_")); } @Test public void escape_directive_macros() throws Exception { getEngine("with_escape", factory); TemplateContext ctx = new MappedTemplateContext(); ctx.put("count", new Counter()); ctx.put("object", new MyRenderable("<world name=\"'中国'\" />")); String content = templateService.getText("escape/test_escape_macros.vm", ctx); // noescape assertThat(content, containsString("1. <world name=\"'中国'\" />")); // java assertThat(content, containsString("2. <world name=\\\"'中国'\\\" />")); // javascript/js assertThat(content, containsString("3. <world name=\\\"\\'中国\\'\\\" \\/>")); assertThat(content, containsString("4. <world name=\\\"\\'中国\\'\\\" \\/>")); // html assertThat(content, containsString("5. <world name="'中国'" />")); // xml assertThat(content, containsString("6. <world name="'中国'" />")); // url assertThat(content, containsString("7. %3Cworld+name%3D%22'%D6%D0%B9%FA'%22+%2F%3E")); // sql assertThat(content, containsString("8. <world name=\"''中国''\" />")); assertFalse(ctx.containsKey("_ESCAPE_SUPPORT_TYPE_")); } @Test public void escape_with_rules() throws Exception { escape_with_rules_internal(factory); assertFalse(velocityEngine.getConfiguration().isProductionMode()); EscapeSupport escapeSupport = (EscapeSupport) getFieldValue(velocityEngine.getConfiguration(), "plugins", Object[].class)[0]; assertFalse(getFieldValue(escapeSupport, "cacheReferences", Boolean.class)); Map<String, EscapeType> cacheReferences = getFieldValue(escapeSupport, "referenceCache", null); assertTrue(cacheReferences.isEmpty()); } @Test public void escape_with_rules_productionMode() throws Exception { ApplicationContext factory; try { System.setProperty("productionMode", "true"); factory = createFactory("services_escape.xml"); escape_with_rules_internal(factory); } finally { System.clearProperty("productionMode"); } assertTrue(velocityEngine.getConfiguration().isProductionMode()); EscapeSupport escapeSupport = (EscapeSupport) getFieldValue(velocityEngine.getConfiguration(), "plugins", Object[].class)[0]; assertTrue(getFieldValue(escapeSupport, "cacheReferences", Boolean.class)); Map<String, EscapeType> cacheReferences = getFieldValue(escapeSupport, "referenceCache", null); assertFalse(cacheReferences.isEmpty()); } private void escape_with_rules_internal(ApplicationContext factory) throws Exception { getEngine("with_rules", factory); TemplateContext ctx = new MappedTemplateContext(); ctx.put("value", "<world name=\"'中国'\" />"); ctx.put("jsValue", "<world name=\"'中国'\" />"); ctx.put("control", new MyControl()); ctx.put("Screen_Placeholder", "<world name=\"'中国'\" />"); ctx.put("stringescapeutil", new StringEscapeUtil()); String content = templateService.getText("escape/test_escape_rules.vm", ctx); // default = html assertThat(content, containsString("1. <world name="'中国'" />")); // js* = javascript assertThat(content, containsString("2. <world name=\\\"\\'中国\\'\\\" \\/>")); // control* = noescape assertThat(content, containsString("3. <hello />")); // screen_placeholder* = noescape, case insensitive assertThat(content, containsString("4. <world name=\"'中国'\" />")); // stringescapeutil.escape* = noescape, case insensitive assertThat(content, containsString("5. %3Chello+%2F%3E")); // 指定escape = html assertThat(content, containsString("6. <world name="'中国'" />")); // 指定noescape assertThat(content, containsString("7. <world name=\"'中国'\" />")); } @Test public void escape_define() throws Exception { getEngine("with_defaultEscape", factory); TemplateContext ctx = new MappedTemplateContext(); String content = templateService.getText("escape/test_escape_define.vm", ctx); assertThat(content, containsString("1. Hello World!")); assertThat(content, containsString("2. Hello <World />")); } /** 测试noescape时,直接返回value object,而不是对其执行toString方法。 */ @Test public void escape_object_noescape() throws Exception { getEngine("with_rules_for_object", factory); TemplateContext ctx = new MappedTemplateContext(); // EscapeSupport先执行,由于是noescape,所以直接返回value object,而不是value.toString() // 后续的Renderable2Handler得以执行,并调用render方法。 ctx.put("myref", new Renderable2() { public String render2() { return "render"; } @Override public String toString() { return "toString"; } }); String content = templateService.getText("escape/test_escape_object_noescape.vm", ctx); assertEquals("render", content); } public static interface Renderable2 { String render2(); } public static class Renderable2Handler implements ReferenceInsertionEventHandler { public Object referenceInsert(String reference, Object value) { if (value instanceof Renderable2) { return ((Renderable2) value).render2(); } return value; } } public static class MyControl { private String value; public MyControl setValue(String value) { this.value = value; return this; } @Override public String toString() { return value; } } }