| /* |
| * 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 org.apache.solr.velocity; |
| |
| import java.io.IOException; |
| import java.io.StringReader; |
| import java.io.StringWriter; |
| import java.security.AccessControlException; |
| import java.util.Properties; |
| |
| import org.apache.solr.SolrTestCaseJ4; |
| import org.apache.solr.common.SolrException; |
| import org.apache.solr.common.util.NamedList; |
| import org.apache.solr.request.SolrQueryRequest; |
| import org.apache.solr.response.QueryResponseWriter; |
| import org.apache.solr.response.SolrQueryResponse; |
| import org.apache.solr.response.VelocityResponseWriter; |
| import org.apache.velocity.exception.MethodInvocationException; |
| import org.junit.AfterClass; |
| import org.junit.BeforeClass; |
| import org.junit.Ignore; |
| import org.junit.Test; |
| |
| public class VelocityResponseWriterTest extends SolrTestCaseJ4 { |
| @BeforeClass |
| public static void beforeClass() throws Exception { |
| initCore("solrconfig.xml", "schema.xml", getFile("velocity/solr").getAbsolutePath()); |
| } |
| |
| @AfterClass |
| public static void afterClass() throws Exception { |
| } |
| |
| @Override |
| public void setUp() throws Exception { |
| // This test case toggles the configset used from trusted to untrusted - return to default of trusted for each test |
| h.getCoreContainer().getCoreDescriptor(h.coreName).setConfigSetTrusted(true); |
| super.setUp(); |
| } |
| |
| @Test |
| public void testVelocityResponseWriterRegistered() { |
| QueryResponseWriter writer = h.getCore().getQueryResponseWriter("velocity"); |
| assertTrue("VrW registered check", writer instanceof VelocityResponseWriter); |
| } |
| |
| @Test |
| public void testSecureUberspector() throws Exception { |
| VelocityResponseWriter vrw = new VelocityResponseWriter(); |
| NamedList<String> nl = new NamedList<>(); |
| nl.add("template.base.dir", getFile("velocity").getAbsolutePath()); |
| vrw.init(nl); |
| SolrQueryRequest req = req(VelocityResponseWriter.TEMPLATE,"outside_the_box"); |
| SolrQueryResponse rsp = new SolrQueryResponse(); |
| StringWriter buf = new StringWriter(); |
| vrw.write(buf, req, rsp); |
| assertEquals("$ex",buf.toString()); // $ex rendered literally because it is null, and thus did not succeed to break outside the box |
| } |
| |
| @Test |
| @Ignore("SOLR-14025: Velocity's SecureUberspector addresses this") |
| public void testTemplateSandbox() throws Exception { |
| assumeTrue("This test only works with security manager", System.getSecurityManager() != null); |
| VelocityResponseWriter vrw = new VelocityResponseWriter(); |
| NamedList<String> nl = new NamedList<>(); |
| nl.add("template.base.dir", getFile("velocity").getAbsolutePath()); |
| vrw.init(nl); |
| SolrQueryRequest req = req(VelocityResponseWriter.TEMPLATE,"outside_the_box"); |
| SolrQueryResponse rsp = new SolrQueryResponse(); |
| StringWriter buf = new StringWriter(); |
| try { |
| vrw.write(buf, req, rsp); |
| fail("template broke outside the box, retrieved: " + buf); |
| } catch (MethodInvocationException e) { |
| assertNotNull(e.getCause()); |
| assertEquals(AccessControlException.class, e.getCause().getClass()); |
| // expected failure, can't get outside the box |
| } |
| } |
| |
| @Test |
| @Ignore("SOLR-14025: Velocity's SecureUberspector addresses this") |
| public void testSandboxIntersection() throws Exception { |
| assumeTrue("This test only works with security manager", System.getSecurityManager() != null); |
| VelocityResponseWriter vrw = new VelocityResponseWriter(); |
| NamedList<String> nl = new NamedList<>(); |
| nl.add("template.base.dir", getFile("velocity").getAbsolutePath()); |
| vrw.init(nl); |
| SolrQueryRequest req = req(VelocityResponseWriter.TEMPLATE,"sandbox_intersection"); |
| SolrQueryResponse rsp = new SolrQueryResponse(); |
| StringWriter buf = new StringWriter(); |
| try { |
| vrw.write(buf, req, rsp); |
| fail("template broke outside the box, retrieved: " + buf); |
| } catch (MethodInvocationException e) { |
| assertNotNull(e.getCause()); |
| assertEquals(AccessControlException.class, e.getCause().getClass()); |
| // expected failure, can't get outside the box |
| } |
| } |
| |
| @Test |
| public void testFileResourceLoader() throws Exception { |
| VelocityResponseWriter vrw = new VelocityResponseWriter(); |
| NamedList<String> nl = new NamedList<>(); |
| nl.add("template.base.dir", getFile("velocity").getAbsolutePath()); |
| vrw.init(nl); |
| SolrQueryRequest req = req(VelocityResponseWriter.TEMPLATE,"file"); |
| SolrQueryResponse rsp = new SolrQueryResponse(); |
| StringWriter buf = new StringWriter(); |
| vrw.write(buf, req, rsp); |
| assertEquals("testing", buf.toString()); |
| } |
| |
| @Test |
| public void testTemplateTrust() throws Exception { |
| // Try on trusted configset.... |
| assertEquals("0", h.query(req("q","*:*", "wt","velocity",VelocityResponseWriter.TEMPLATE,"numFound"))); |
| |
| // Turn off trusted configset, which disables the Solr resource loader |
| h.getCoreContainer().getCoreDescriptor(h.coreName).setConfigSetTrusted(false); |
| assertFalse(h.getCoreContainer().getCoreDescriptor(coreName).isConfigSetTrusted()); |
| |
| try { |
| assertEquals("0", h.query(req("q","*:*", "wt","velocity",VelocityResponseWriter.TEMPLATE,"numFound"))); |
| fail("template rendering should have failed, from an untrusted configset"); |
| } catch (IOException e) { |
| // expected exception |
| assertEquals(IOException.class, e.getClass()); |
| } |
| |
| // set the harness back to the default of trusted |
| h.getCoreContainer().getCoreDescriptor(h.coreName).setConfigSetTrusted(true); |
| } |
| |
| |
| @Test |
| public void testSolrResourceLoaderTemplate() throws Exception { |
| assertEquals("0", h.query(req("q","*:*", "wt","velocity",VelocityResponseWriter.TEMPLATE,"numFound"))); |
| } |
| |
| @Test |
| public void testEncoding() throws Exception { |
| assertEquals("éñçø∂îñg", h.query(req("q","*:*", "wt","velocity",VelocityResponseWriter.TEMPLATE,"encoding"))); |
| } |
| |
| @Test |
| public void testMacros() throws Exception { |
| // tests that a macro in a custom macros.vm is visible |
| assertEquals("test_macro_SUCCESS", h.query(req("q","*:*", "wt","velocity",VelocityResponseWriter.TEMPLATE,"test_macro_visible"))); |
| |
| // tests that a builtin (_macros.vm) macro, #url_root in this case, can be overridden in a custom macros.vm |
| // the macro is also defined in VM_global_library.vm, which should also be overridden by macros.vm |
| assertEquals("Loaded from: macros.vm", h.query(req("q","*:*", "wt","velocity",VelocityResponseWriter.TEMPLATE,"test_macro_overridden"))); |
| |
| // tests that macros defined in VM_global_library.vm are visible. This file was where macros in pre-5.0 versions were defined |
| assertEquals("legacy_macro_SUCCESS", h.query(req("q","*:*", "wt","velocity",VelocityResponseWriter.TEMPLATE,"test_macro_legacy_support"))); |
| } |
| |
| @Test |
| public void testInitProps() throws Exception { |
| // The test init properties file turns off being able to use $foreach.index (the implicit loop counter) |
| // The foreach.vm template uses $!foreach.index, with ! suppressing the literal "$foreach.index" output |
| |
| assertEquals("01", h.query(req("q","*:*", "wt","velocity",VelocityResponseWriter.TEMPLATE,"foreach"))); |
| assertEquals("", h.query(req("q","*:*", "wt","velocityWithInitProps",VelocityResponseWriter.TEMPLATE,"foreach"))); |
| |
| // Turn off trusted configset, which disables the init properties |
| h.getCoreContainer().getCoreDescriptor(h.coreName).setConfigSetTrusted(false); |
| assertFalse(h.getCoreContainer().getCoreDescriptor(coreName).isConfigSetTrusted()); |
| |
| assertEquals("01", h.query(req("q","*:*", "wt","velocityWithInitProps",VelocityResponseWriter.TEMPLATE,"foreach"))); |
| |
| // set the harness back to the default of trusted |
| h.getCoreContainer().getCoreDescriptor(h.coreName).setConfigSetTrusted(true); |
| } |
| |
| @Test |
| public void testCustomTools() throws Exception { |
| // Render this template once without a custom tool defined, and once with it defined. The tool has a `.star` method. |
| // The tool added as `mytool`, `log`, and `response`. `log` is designed to be overridable, but not `response` |
| // mytool.star=$!mytool.star("LATERALUS") |
| // mytool.locale=$!mytool.locale |
| // log.star=$!log.star("log overridden") |
| // response.star=$!response.star("response overridden??") |
| |
| // First without the tool defined, with `$!` turning null object/method references into empty string |
| Properties rendered_props = new Properties(); |
| String rsp = h.query(req("q","*:*", "wt","velocity",VelocityResponseWriter.TEMPLATE,"custom_tool")); |
| rendered_props.load(new StringReader(rsp)); |
| // ignore mytool.locale here, as it will be the random test one |
| assertEquals("",rendered_props.getProperty("mytool.star")); |
| assertEquals("",rendered_props.getProperty("log.star")); |
| assertEquals("",rendered_props.getProperty("response.star")); |
| |
| // Now with custom tools defined: |
| rsp = h.query(req("q","*:*", "wt","velocityWithCustomTools",VelocityResponseWriter.TEMPLATE,"custom_tool",VelocityResponseWriter.LOCALE, "de_DE")); |
| rendered_props.clear(); |
| rendered_props.load(new StringReader(rsp)); |
| assertEquals("** LATERALUS **",rendered_props.getProperty("mytool.star")); |
| assertEquals("** log overridden **",rendered_props.getProperty("log.star")); |
| assertEquals("",rendered_props.getProperty("response.star")); |
| assertEquals("de_DE",rendered_props.getProperty("mytool.locale")); |
| |
| |
| // Turn off trusted configset, which disables the custom tool injection |
| h.getCoreContainer().getCoreDescriptor(h.coreName).setConfigSetTrusted(false); |
| assertFalse(h.getCoreContainer().getCoreDescriptor(coreName).isConfigSetTrusted()); |
| |
| rsp = h.query(req("q","*:*", "wt","velocityWithCustomTools",VelocityResponseWriter.TEMPLATE,"custom_tool",VelocityResponseWriter.LOCALE, "de_DE")); |
| rendered_props.clear(); |
| rendered_props.load(new StringReader(rsp)); |
| assertEquals("",rendered_props.getProperty("mytool.star")); |
| assertEquals("",rendered_props.getProperty("log.star")); |
| assertEquals("",rendered_props.getProperty("response.star")); |
| assertEquals("",rendered_props.getProperty("mytool.locale")); |
| |
| // set the harness back to the default of trusted |
| h.getCoreContainer().getCoreDescriptor(h.coreName).setConfigSetTrusted(true); |
| |
| |
| // Custom tools can also have a SolrCore-arg constructor because they are instantiated with SolrCore.createInstance |
| // TODO: do we really need to support this? no great loss, as a custom tool could take a SolrCore object as a parameter to |
| // TODO: any method, so one could do $mytool.my_method($request.core) |
| // I'm currently inclined to make this feature undocumented/unsupported, as we may want to instantiate classes |
| // in a different manner that only supports no-arg constructors, commented (passing) test case out |
| // assertEquals("collection1", h.query(req("q","*:*", "wt","velocityWithCustomTools",VelocityResponseWriter.TEMPLATE,"t", |
| // SolrParamResourceLoader.TEMPLATE_PARAM_PREFIX+"t", "$mytool.core.name"))) |
| // - NOTE: example uses removed inline param; convert to external template as needed |
| } |
| |
| @Test |
| public void testLocaleFeature() throws Exception { |
| assertEquals("Color", h.query(req("q", "*:*", "wt", "velocity", VelocityResponseWriter.TEMPLATE, "locale", |
| VelocityResponseWriter.LOCALE,"en_US"))); |
| assertEquals("Colour", h.query(req("q", "*:*", "wt", "velocity", VelocityResponseWriter.TEMPLATE, "locale", |
| VelocityResponseWriter.LOCALE,"en_UK"))); |
| |
| // Test that $resource.get(key,baseName,locale) works with specified locale |
| assertEquals("Colour", h.query(req("q","*:*", "wt","velocity",VelocityResponseWriter.TEMPLATE,"resource_get"))); |
| |
| // Test that $number tool uses the specified locale |
| assertEquals("2,112", h.query(req("q","*:*", "wt","velocity",VelocityResponseWriter.TEMPLATE,"locale_number", |
| VelocityResponseWriter.LOCALE, "en_US"))); |
| assertEquals("2.112", h.query(req("q","*:*", "wt","velocity",VelocityResponseWriter.TEMPLATE,"locale_number", |
| VelocityResponseWriter.LOCALE, "de_DE"))); |
| } |
| |
| @Test |
| public void testLayoutFeature() throws Exception { |
| assertEquals("{{{0}}}", h.query(req("q","*:*", "wt","velocity", |
| VelocityResponseWriter.TEMPLATE,"numFound", VelocityResponseWriter.LAYOUT,"layout"))); |
| |
| // even with v.layout specified, layout can be disabled explicitly |
| assertEquals("0", h.query(req("q","*:*", "wt","velocity", |
| VelocityResponseWriter.TEMPLATE,"numFound", |
| VelocityResponseWriter.LAYOUT,"layout", |
| VelocityResponseWriter.LAYOUT_ENABLED,"false"))); |
| } |
| |
| @Test |
| public void testJSONWrapper() throws Exception { |
| assertEquals("foo({\"result\":\"0\"})", h.query(req("q", "*:*", "wt", "velocity", |
| VelocityResponseWriter.TEMPLATE, "numFound", |
| VelocityResponseWriter.JSON,"foo"))); |
| |
| // Now with layout, for good measure |
| assertEquals("foo({\"result\":\"{{{0}}}\"})", h.query(req("q", "*:*", "wt", "velocity", |
| VelocityResponseWriter.TEMPLATE, "numFound", |
| VelocityResponseWriter.JSON,"foo", |
| VelocityResponseWriter.LAYOUT,"layout"))); |
| |
| assertQEx("Bad function name should throw exception", req("q", "*:*", "wt", "velocity", |
| VelocityResponseWriter.TEMPLATE, "numFound", |
| VelocityResponseWriter.JSON,"<foo>"), SolrException.ErrorCode.BAD_REQUEST |
| ); |
| } |
| |
| @Test |
| public void testContentType() { |
| VelocityResponseWriter vrw = new VelocityResponseWriter(); |
| NamedList<String> nl = new NamedList<>(); |
| vrw.init(nl); |
| SolrQueryResponse rsp = new SolrQueryResponse(); |
| |
| // with v.json=wrf, content type should default to application/json |
| assertEquals("application/json;charset=UTF-8", |
| vrw.getContentType(req(VelocityResponseWriter.TEMPLATE, "numFound", |
| VelocityResponseWriter.JSON, "wrf"), rsp)); |
| |
| // with no v.json specified, the default text/html should be returned |
| assertEquals("text/html;charset=UTF-8", |
| vrw.getContentType(req(VelocityResponseWriter.TEMPLATE, "numFound"), rsp)); |
| |
| // if v.contentType is specified, that should be used, even if v.json is specified |
| assertEquals("text/plain", |
| vrw.getContentType(req(VelocityResponseWriter.TEMPLATE, "numFound", |
| VelocityResponseWriter.CONTENT_TYPE,"text/plain"), rsp)); |
| assertEquals("text/plain", |
| vrw.getContentType(req(VelocityResponseWriter.TEMPLATE, "numFound", |
| VelocityResponseWriter.JSON,"wrf", |
| VelocityResponseWriter.CONTENT_TYPE,"text/plain"), rsp)); |
| } |
| } |