diff --git a/CHANGES.txt b/CHANGES.txt index 5889578efe3..e9e918c814d 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -175,6 +175,10 @@ New Features This will register: Luke/SystemInfo/PluginInfo/ThreadDump/PropertiesRequestHandler. (ryan) + +35. SOLR-142: Added RawResponseWriter and ShowFileRequestHandler. This returns config + files directly. If the AdminHandlers is configured, this will be added automatically. + (ryan) Changes in runtime behavior diff --git a/example/solr/conf/solrconfig.xml b/example/solr/conf/solrconfig.xml index d4b550ad8c2..4019718c5d6 100755 --- a/example/solr/conf/solrconfig.xml +++ b/example/solr/conf/solrconfig.xml @@ -502,7 +502,15 @@ + + If you wish to hide files under ${solr.home}/conf, explicitly register the ShowFileRequestHandler using: + + + synonyms.txt + anotherfile.txt + + --> diff --git a/src/java/org/apache/solr/core/SolrCore.java b/src/java/org/apache/solr/core/SolrCore.java index 251508a1528..b46bf8b242d 100644 --- a/src/java/org/apache/solr/core/SolrCore.java +++ b/src/java/org/apache/solr/core/SolrCore.java @@ -54,6 +54,7 @@ import org.apache.solr.highlight.SolrHighlighter; import org.apache.solr.request.JSONResponseWriter; import org.apache.solr.request.PythonResponseWriter; import org.apache.solr.request.QueryResponseWriter; +import org.apache.solr.request.RawResponseWriter; import org.apache.solr.request.RubyResponseWriter; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.request.SolrQueryResponse; @@ -968,7 +969,9 @@ public final class SolrCore { if (responseWriters.get("ruby")==null) { responseWriters.put("ruby", new RubyResponseWriter()); } - + if (responseWriters.get("raw")==null) { + responseWriters.put("raw", new RawResponseWriter()); + } } /** Finds a writer by name, or returns the default writer if not found. */ diff --git a/src/java/org/apache/solr/handler/admin/AdminHandlers.java b/src/java/org/apache/solr/handler/admin/AdminHandlers.java index 90564bf9ace..a017772c1e3 100644 --- a/src/java/org/apache/solr/handler/admin/AdminHandlers.java +++ b/src/java/org/apache/solr/handler/admin/AdminHandlers.java @@ -84,7 +84,8 @@ public class AdminHandlers implements SolrCoreAware, SolrRequestHandler new StandardHandler( "system", new SystemInfoHandler() ), new StandardHandler( "plugins", new PluginInfoHandler() ), new StandardHandler( "threads", new ThreadDumpHandler() ), - new StandardHandler( "properties", new PropertiesRequestHandler() ) + new StandardHandler( "properties", new PropertiesRequestHandler() ), + new StandardHandler( "file", new ShowFileRequestHandler() ) }; for( StandardHandler handler : list ) { diff --git a/src/java/org/apache/solr/handler/admin/ShowFileRequestHandler.java b/src/java/org/apache/solr/handler/admin/ShowFileRequestHandler.java new file mode 100644 index 00000000000..0fd5d83f0f2 --- /dev/null +++ b/src/java/org/apache/solr/handler/admin/ShowFileRequestHandler.java @@ -0,0 +1,255 @@ +/** + * 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.handler.admin; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; + +import org.apache.commons.io.IOUtils; +import org.apache.solr.common.SolrException; +import org.apache.solr.common.SolrException.ErrorCode; +import org.apache.solr.common.params.CommonParams; +import org.apache.solr.common.params.ModifiableSolrParams; +import org.apache.solr.common.util.ContentStream; +import org.apache.solr.common.util.NamedList; +import org.apache.solr.common.util.SimpleOrderedMap; +import org.apache.solr.core.SolrCore; +import org.apache.solr.core.SolrResourceLoader; +import org.apache.solr.handler.RequestHandlerBase; +import org.apache.solr.handler.RequestHandlerUtils; +import org.apache.solr.request.RawResponseWriter; +import org.apache.solr.request.SolrQueryRequest; +import org.apache.solr.request.SolrQueryResponse; + +/** + * This handler uses the RawResponseWriter to give client access to + * files inside ${solr.home}/conf + * + * If you want to selectively restrict access some configuration files, you can list + * these files in the {@link #HIDDEN} invariants. For example to hide + * synonyms.txt and anotherfile.txt, you would register: + * + *
+ * <requestHandler name="/admin/file" class="org.apache.solr.handler.admin.ShowFileRequestHandler" >
+ *   <lst name="defaults">
+ *    <str name="echoParams">explicit</str>
+ *   </lst>
+ *   <lst name="invariants">
+ *    <str name="hidden">synonyms.txt</str> 
+ *    <str name="hidden">anotherfile.txt</str> 
+ *   </lst>
+ * </requestHandler>
+ * 
+ * + * The ShowFileRequestHandler uses the {@link RawResponseWriter} (wt=raw) to return + * file contents. If you need to use a different writer, you will need to change + * the registered invarient param for wt. + * + * If you want to override the contentType header returned for a given file, you can + * set it directly using: {@link #USE_CONTENT_TYPE}. For example, to get a plain text + * version of schema.xml, try: + *
+ *   http://localhost:8983/solr/admin/file?file=schema.xml&contentType=text/plain
+ * 
+ * + * @version $Id$ + * @since solr 1.3 + */ +public class ShowFileRequestHandler extends RequestHandlerBase +{ + public static final String HIDDEN = "hidden"; + public static final String USE_CONTENT_TYPE = "contentType"; + + protected Set hiddenFiles; + + private static ShowFileRequestHandler instance; + public ShowFileRequestHandler() + { + super(); + instance = this; // used so that getFileContents can access hiddenFiles + } + + @Override + public void init(NamedList args) { + super.init( args ); + + // by default, use wt=raw + ModifiableSolrParams params = new ModifiableSolrParams( invariants ); + if( params.get( CommonParams.WT ) == null ) { + params.set( CommonParams.WT, "raw" ); + } + this.invariants = params; + + // Build a list of hidden files + hiddenFiles = new HashSet(); + if( invariants != null ) { + String[] hidden = invariants.getParams( HIDDEN ); + if( hidden != null ) { + for( String s : hidden ) { + hiddenFiles.add( s.toUpperCase() ); + } + } + } + } + + @Override + public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws IOException + { + File adminFile = null; + + final SolrResourceLoader loader = req.getCore().getResourceLoader(); + File configdir = new File( loader.getConfigDir() ); + String fname = req.getParams().get("file", null); + if( fname == null ) { + adminFile = configdir; + } + else { + fname = fname.replace( '\\', '/' ); // normalize slashes + if( hiddenFiles.contains( fname.toUpperCase() ) ) { + throw new SolrException( ErrorCode.FORBIDDEN, "Can not access: "+fname ); + } + if( fname.indexOf( ".." ) >= 0 ) { + throw new SolrException( ErrorCode.FORBIDDEN, "Invalid path: "+fname ); + } + adminFile = new File( configdir, fname ); + } + + // Make sure the file exists, is readable and is not a hidden file + if( !adminFile.exists() ) { + throw new SolrException( ErrorCode.BAD_REQUEST, "Can not find: "+adminFile.getName() + + " ["+adminFile.getAbsolutePath()+"]" ); + } + if( !adminFile.canRead() || adminFile.isHidden() ) { + throw new SolrException( ErrorCode.BAD_REQUEST, "Can not show: "+adminFile.getName() + + " ["+adminFile.getAbsolutePath()+"]" ); + } + + // Add a warning + RequestHandlerUtils.addExperimentalFormatWarning(rsp); + + // Show a directory listing + if( adminFile.isDirectory() ) { + + int basePath = configdir.getAbsolutePath().length() + 1; + NamedList> files = new SimpleOrderedMap>(); + for( File f : adminFile.listFiles() ) { + String path = f.getAbsolutePath().substring( basePath ); + path = path.replace( '\\', '/' ); // normalize slashes + if( hiddenFiles.contains( path.toUpperCase() ) ) { + continue; // don't show 'hidden' files + } + if( f.isHidden() || f.getName().startsWith( "." ) ) { + continue; // skip hidden system files... + } + + SimpleOrderedMap fileInfo = new SimpleOrderedMap(); + files.add( path, fileInfo ); + if( f.isDirectory() ) { + fileInfo.add( "directory", true ); + } + else { + // TODO? content type + fileInfo.add( "size", f.length() ); + } + fileInfo.add( "modified", new Date( f.lastModified() ) ); + } + rsp.add( "files", files ); + } + else { + // Check if they want the file as text + final String contentType = req.getParams().get( USE_CONTENT_TYPE ); + + final File file = adminFile; + //final URLConnection conn = adminFile.toURI().toURL().openConnection(); + + ContentStream stream = new ContentStream() { + public String getName() { return file.getName(); } + public Long getSize() { return file.length(); } + public String getSourceInfo() { return null; } + + public String getContentType() { + if( contentType != null ) { + return contentType; + } + return null; //conn.getContentType(); + } + + public InputStream getStream() throws IOException { + return loader.openResource( file.getPath() ); //conn.getInputStream(); + } + + public Reader getReader() throws IOException { + return new FileReader( file ); + } + }; + + rsp.add( RawResponseWriter.CONTENT, stream ); + } + } + + /** + * This is a utility function that lets you get the contents of an admin file + * + * It is only used so that we can get rid of "/admin/get-file.jsp" and include + * "admin-extra.html" in "/admin/index.html" using jsp scriptlets + */ + @Deprecated + public static String getFileContents( String path ) + { + if( instance != null && instance.hiddenFiles != null ) { + if( instance.hiddenFiles.contains( path ) ) { + return ""; // ignore it... + } + } + try { + SolrCore core = SolrCore.getSolrCore(); + InputStream input = core.getResourceLoader().openResource(path); + return IOUtils.toString( input ); + } + catch( Exception ex ) {} // ignore it + return ""; + } + + //////////////////////// SolrInfoMBeans methods ////////////////////// + + @Override + public String getDescription() { + return "Admin Get File -- view config files directly"; + } + + @Override + public String getVersion() { + return "$Revision$"; + } + + @Override + public String getSourceId() { + return "$Id$"; + } + + @Override + public String getSource() { + return "$URL$"; + } +} diff --git a/src/java/org/apache/solr/request/RawResponseWriter.java b/src/java/org/apache/solr/request/RawResponseWriter.java new file mode 100644 index 00000000000..dd02689f048 --- /dev/null +++ b/src/java/org/apache/solr/request/RawResponseWriter.java @@ -0,0 +1,91 @@ +/** + * 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.request; + +import java.io.IOException; +import java.io.Writer; + +import org.apache.commons.io.IOUtils; +import org.apache.solr.common.util.ContentStream; +import org.apache.solr.common.util.NamedList; + +/** + * Writes a ContentStream directly to the output. + * + *

+ * This writer is a special case that extends and alters the + * QueryResponseWriter contract. If SolrQueryResponse contains a + * ContentStream added with the key {@link #CONTENT} + * then this writer will output that stream exactly as is (with it's + * Content-Type). if no such ContentStream has been added, then a + * "base" QueryResponseWriter will be used to write the response + * according to the usual contract. The name of the "base" writer can + * be specified as an initialization param for this writer, or it + * defaults to the "standard" writer. + *

+ * + * @version $Id$ + * @since solr 1.3 + */ +public class RawResponseWriter implements QueryResponseWriter +{ + /** + * The key that should be used to add a ContentStream to the + * SolrQueryResponse if you intend to use this Writer. + */ + public static final String CONTENT = "content"; + private String _baseWriter = null; + + public void init(NamedList n) { + if( n != null ) { + Object base = n.get( "base" ); + if( base != null ) { + _baseWriter = base.toString(); + } + } + } + + // Even if this is null, it should be ok + protected QueryResponseWriter getBaseWriter( SolrQueryRequest request ) + { + return request.getCore().getQueryResponseWriter( _baseWriter ); + } + + public String getContentType(SolrQueryRequest request, SolrQueryResponse response) { + Object obj = response.getValues().get( CONTENT ); + if( obj != null && (obj instanceof ContentStream ) ) { + // copy the contents to the writer... + ContentStream content = (ContentStream)obj; + return content.getContentType(); + } + return getBaseWriter( request ).getContentType( request, response ); + } + + public void write(Writer writer, SolrQueryRequest request, SolrQueryResponse response) throws IOException + { + Object obj = response.getValues().get( CONTENT ); + if( obj != null && (obj instanceof ContentStream ) ) { + // copy the contents to the writer... + ContentStream content = (ContentStream)obj; + IOUtils.copy( content.getStream(), writer ); + } + else { + getBaseWriter( request ).write( writer, request, response ); + } + } +} diff --git a/src/webapp/resources/admin/get-file.jsp b/src/webapp/resources/admin/get-file.jsp index 462f63544d7..48721493c3a 100644 --- a/src/webapp/resources/admin/get-file.jsp +++ b/src/webapp/resources/admin/get-file.jsp @@ -23,6 +23,13 @@ java.io.Reader, java.util.StringTokenizer"%> <% +// +// NOTE -- this file is Deprecated - should use the ShowFileRequestHandler instead +// + +System.out.println( "WARNING -- using deprecated jsp file: " + request.getServletPath() ); + + Object ocore = request.getAttribute("org.apache.solr.SolrCore"); SolrCore core = ocore instanceof SolrCore? (SolrCore) ocore : SolrCore.getSolrCore(); String fname = request.getParameter("file"); diff --git a/src/webapp/resources/admin/index.jsp b/src/webapp/resources/admin/index.jsp index e8012991926..4ffe724408f 100644 --- a/src/webapp/resources/admin/index.jsp +++ b/src/webapp/resources/admin/index.jsp @@ -34,8 +34,8 @@

Solr

- [Schema] - [Config] + [Schema] + [Config] [Analysis]
[Statistics] @@ -80,7 +80,10 @@ if (cores.size() > 1) {%>Cores:
<% - +<% + // a quick hack to get rid of get-file.jsp -- note this still spits out invalid HTML + out.write( org.apache.solr.handler.admin.ShowFileRequestHandler.getFileContents( "admin-extra.html" ) ); +%>