alba.components.FilteredShowFileRequestHandler.java Source code

Java tutorial

Introduction

Here is the source code for alba.components.FilteredShowFileRequestHandler.java

Source

package alba.components;

/*
 * 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.
 */

//THIS IS COPIED FROM ShowFileRequestHandler !!

import java.io.BufferedReader;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;

import org.apache.commons.io.IOUtils;
import org.apache.solr.cloud.ZkSolrResourceLoader;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.common.cloud.SolrZkClient;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.ContentStreamBase;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.SimpleOrderedMap;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.core.SolrCore;
import org.apache.solr.core.SolrResourceLoader;
import org.apache.solr.handler.RequestHandlerBase;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.RawResponseWriter;
import org.apache.solr.response.SolrQueryResponse;
import org.apache.solr.schema.IndexSchema;
import org.apache.solr.schema.ManagedIndexSchema;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.runtime.RuntimeServices;
import org.apache.velocity.runtime.RuntimeSingleton;
import org.apache.velocity.runtime.parser.ParseException;
import org.apache.velocity.runtime.parser.node.SimpleNode;
import org.apache.zookeeper.KeeperException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This handler uses the RawResponseWriter to give client access to
 * files inside ${solr.home}/conf
 * <p>
 * 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:
 * <br>
 * <pre>
 * &lt;requestHandler name="/admin/file" class="org.apache.solr.handler.admin.ShowFileRequestHandler" &gt;
 *   &lt;lst name="defaults"&gt;
 *    &lt;str name="echoParams"&gt;explicit&lt;/str&gt;
 *   &lt;/lst&gt;
 *   &lt;lst name="invariants"&gt;
 *    &lt;str name="hidden"&gt;synonyms.txt&lt;/str&gt; 
 *    &lt;str name="hidden"&gt;anotherfile.txt&lt;/str&gt;
 *    &lt;str name="hidden"&gt;*&lt;/str&gt;
 *   &lt;/lst&gt;
 * &lt;/requestHandler&gt;
 * </pre>
 *
 * At present, there is only explicit file names (including path) or the glob '*' are supported. Variants like '*.xml'
 * are NOT supported.ere
 *
 * <p>
 * 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 invariant param for wt.
 * <p>
 * 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:
 * <pre>
 *   http://localhost:8983/solr/admin/file?file=schema.xml&amp;contentType=text/plain
 * </pre>
 *
 *
 * @since solr 1.3
 */

// THIS IS COPIED FROM ShowFileRequestHandler !!

public class FilteredShowFileRequestHandler extends RequestHandlerBase {
    public static final String HIDDEN = "hidden";
    public static final String USE_CONTENT_TYPE = "contentType";

    Logger logger = LoggerFactory.getLogger(this.getClass().getName());

    protected Set<String> hiddenFiles;

    protected static final Logger log = LoggerFactory.getLogger(FilteredShowFileRequestHandler.class);

    public FilteredShowFileRequestHandler() {
        super();
    }

    @Override
    public void init(NamedList args) {
        super.init(args);
        hiddenFiles = initHidden(invariants);
    }

    public static Set<String> initHidden(SolrParams invariants) {

        Set<String> hiddenRet = new HashSet<>();
        // Build a list of hidden files
        if (invariants != null) {
            String[] hidden = invariants.getParams(HIDDEN);
            if (hidden != null) {
                for (String s : hidden) {
                    hiddenRet.add(s.toUpperCase(Locale.ROOT));
                }
            }
        }
        return hiddenRet;
    }

    @Override
    public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp)
            throws InterruptedException, KeeperException, IOException {

        CoreContainer coreContainer = req.getCore().getCoreDescriptor().getCoreContainer();
        if (coreContainer.isZooKeeperAware()) {
            showFromZooKeeper(req, rsp, coreContainer);
        } else {
            showFromFileSystem(req, rsp);
        }
    }

    // Get a list of files from ZooKeeper for from the path in the file= parameter.
    private void showFromZooKeeper(SolrQueryRequest req, SolrQueryResponse rsp, CoreContainer coreContainer)
            throws KeeperException, InterruptedException, UnsupportedEncodingException {

        SolrZkClient zkClient = coreContainer.getZkController().getZkClient();

        String adminFile = getAdminFileFromZooKeeper(req, rsp, zkClient, hiddenFiles);

        if (adminFile == null) {
            return;
        }

        // Show a directory listing
        List<String> children = zkClient.getChildren(adminFile, null, true);
        if (children.size() > 0) {

            NamedList<SimpleOrderedMap<Object>> files = new SimpleOrderedMap<>();
            for (String f : children) {
                if (isHiddenFile(req, rsp, f, false, hiddenFiles)) {
                    continue;
                }

                SimpleOrderedMap<Object> fileInfo = new SimpleOrderedMap<>();
                files.add(f, fileInfo);
                List<String> fchildren = zkClient.getChildren(adminFile + "/" + f, null, true);
                if (fchildren.size() > 0) {
                    fileInfo.add("directory", true);
                } else {
                    // TODO? content type
                    fileInfo.add("size", f.length());
                }
                // TODO: ?
                // fileInfo.add( "modified", new Date( f.lastModified() ) );
            }
            rsp.add("files", files);
        } else {
            // Include the file contents
            // The file logic depends on RawResponseWriter, so force its use.
            ModifiableSolrParams params = new ModifiableSolrParams(req.getParams());
            params.set(CommonParams.WT, "raw");
            req.setParams(params);

            ContentStreamBase content = new ContentStreamBase.ByteArrayStream(
                    zkClient.getData(adminFile, null, null, true), adminFile);
            content.setContentType(req.getParams().get(USE_CONTENT_TYPE));

            // Velocity parsing here!

            // http://velocity.apache.org/engine/devel/developer-guide.html#The_Context
            Velocity.init();

            VelocityContext context = new VelocityContext();

            //add some vars??
            //context.put( "context", new String("Velocity") );

            for (int i = 0; i < rsp.getValues().size(); i++) {
                context.put(rsp.getValues().getName(i), rsp.getValues().getVal(i));
            }

            Template template = null;

            String fname = req.getParams().get("file", null);

            try {
                //TODO what if fname is null?
                template = Velocity.getTemplate(fname);
            } catch (ResourceNotFoundException rnfe) {
                // couldn't find the template, try to load it

                // TODO it should be fired only for SOME mimetypes (..through an annotation??)
                StringBuilder sb = this.getTemplate(content);

                RuntimeServices runtimeServices = RuntimeSingleton.getRuntimeServices();
                StringReader reader = new StringReader(sb.toString());
                SimpleNode node = null;
                try {
                    node = runtimeServices.parse(reader, fname);
                } catch (ParseException e) {
                    // TODO Auto-generated catch block
                    logger.error("error while parsing new template", e);
                }

                template = new Template();

                template.setRuntimeServices(runtimeServices);

                if (node != null) {
                    template.setData(node);
                } else {
                    logger.error("node null, can't set on template");
                }

                template.initDocument();

            } catch (ParseErrorException pee) {
                // syntax error: problem parsing the template
                logger.error("error while parsing template: ", pee);

            } catch (MethodInvocationException mie) {
                // something invoked in the template
                // threw an exception
                logger.error("error while parsing temaplate: ", mie);
            } catch (Exception e) {
                logger.error("error while parsing temaplate: ", e);
            }

            StringWriter sw = new StringWriter();

            template.merge(context, sw);

            // http://stackoverflow.com/questions/18571223/how-to-convert-java-string-into-byte
            content = new ContentStreamBase.ByteArrayStream(
                    sw.getBuffer().toString().getBytes(Charset.forName("UTF-8")), adminFile);
            content.setContentType(req.getParams().get(USE_CONTENT_TYPE));

            rsp.add(RawResponseWriter.CONTENT, content);
        }
        rsp.setHttpCaching(false);
    }

    private StringBuilder getTemplate(ContentStreamBase content) {
        InputStream stream = null;

        StringBuilder sb = new StringBuilder();

        BufferedReader br;

        String line = "";

        try {
            stream = content.getStream();

            br = new BufferedReader(new InputStreamReader(stream));
            while ((line = br.readLine()) != null) {
                if (line.matches(".*src=\"\\/.*")) {
                    //TODO solr/dbpedia OFCOURSE must be parametric / automatic (better!) 
                    line = line.replaceAll("src=\"", "src=\"/solr/dbpedia_shard1_replica1/admin/file?file=");
                }
                sb.append(line);

            }

        } catch (IOException e) {
            // TODO Auto-generated catch block
            logger.error("error on filtering ", e);
        } finally {
            IOUtils.closeQuietly(stream);
        }

        return sb;
    }

    // Return the file indicated (or the directory listing) from the local file system.
    private void showFromFileSystem(SolrQueryRequest req, SolrQueryResponse rsp) {
        File adminFile = getAdminFileFromFileSystem(req, rsp, hiddenFiles);

        if (adminFile == null) { // exception already recorded
            return;
        }

        // Make sure the file exists, is readable and is not a hidden file
        if (!adminFile.exists()) {
            log.error("Can not find: " + adminFile.getName() + " [" + adminFile.getAbsolutePath() + "]");
            rsp.setException(new SolrException(ErrorCode.NOT_FOUND,
                    "Can not find: " + adminFile.getName() + " [" + adminFile.getAbsolutePath() + "]"));
            return;
        }
        if (!adminFile.canRead() || adminFile.isHidden()) {
            log.error("Can not show: " + adminFile.getName() + " [" + adminFile.getAbsolutePath() + "]");
            rsp.setException(new SolrException(ErrorCode.NOT_FOUND,
                    "Can not show: " + adminFile.getName() + " [" + adminFile.getAbsolutePath() + "]"));
            return;
        }

        // Show a directory listing
        if (adminFile.isDirectory()) {
            // it's really a directory, just go for it.
            int basePath = adminFile.getAbsolutePath().length() + 1;
            NamedList<SimpleOrderedMap<Object>> files = new SimpleOrderedMap<>();
            for (File f : adminFile.listFiles()) {
                String path = f.getAbsolutePath().substring(basePath);
                path = path.replace('\\', '/'); // normalize slashes

                if (isHiddenFile(req, rsp, f.getName().replace('\\', '/'), false, hiddenFiles)) {
                    continue;
                }

                SimpleOrderedMap<Object> 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 {
            // Include the file contents
            //The file logic depends on RawResponseWriter, so force its use.
            ModifiableSolrParams params = new ModifiableSolrParams(req.getParams());
            params.set(CommonParams.WT, "raw");
            req.setParams(params);

            ContentStreamBase content = new ContentStreamBase.FileStream(adminFile);
            content.setContentType(req.getParams().get(USE_CONTENT_TYPE));

            rsp.add(RawResponseWriter.CONTENT, content);
        }
        rsp.setHttpCaching(false);
    }

    //////////////////////// Static methods //////////////////////////////

    public static boolean isHiddenFile(SolrQueryRequest req, SolrQueryResponse rsp, String fnameIn,
            boolean reportError, Set<String> hiddenFiles) {
        String fname = fnameIn.toUpperCase(Locale.ROOT);
        if (hiddenFiles.contains(fname) || hiddenFiles.contains("*")) {
            if (reportError) {
                log.error("Cannot access " + fname);
                rsp.setException(
                        new SolrException(SolrException.ErrorCode.FORBIDDEN, "Can not access: " + fnameIn));
            }
            return true;
        }

        // This is slightly off, a valid path is something like ./schema.xml. I don't think it's worth the effort though
        // to fix it to handle all possibilities though.
        if (fname.indexOf("..") >= 0 || fname.startsWith(".")) {
            if (reportError) {
                log.error("Invalid path: " + fname);
                rsp.setException(new SolrException(SolrException.ErrorCode.FORBIDDEN, "Invalid path: " + fnameIn));
            }
            return true;
        }

        // Make sure that if the schema is managed, we don't allow editing. Don't really want to put
        // this in the init since we're not entirely sure when the managed schema will get initialized relative to this
        // handler.
        SolrCore core = req.getCore();
        IndexSchema schema = core.getLatestSchema();
        if (schema instanceof ManagedIndexSchema) {
            String managed = schema.getResourceName();

            if (fname.equalsIgnoreCase(managed)) {
                return true;
            }
        }
        return false;
    }

    // Refactored to be usable from multiple methods. Gets the path of the requested file from ZK.
    // Returns null if the file is not found.
    //
    // Assumes that the file is in a parameter called "file".

    public static String getAdminFileFromZooKeeper(SolrQueryRequest req, SolrQueryResponse rsp,
            SolrZkClient zkClient, Set<String> hiddenFiles) throws KeeperException, InterruptedException {
        String adminFile = null;
        SolrCore core = req.getCore();

        final ZkSolrResourceLoader loader = (ZkSolrResourceLoader) core.getResourceLoader();
        String confPath = loader.getConfigSetZkPath();

        String fname = req.getParams().get("file", null);
        if (fname == null) {
            adminFile = confPath;
        } else {
            fname = fname.replace('\\', '/'); // normalize slashes
            if (isHiddenFile(req, rsp, fname, true, hiddenFiles)) {
                return null;
            }
            if (fname.startsWith("/")) { // Only files relative to conf are valid
                fname = fname.substring(1);
            }
            adminFile = confPath + "/" + fname;
        }

        // Make sure the file exists, is readable and is not a hidden file
        if (!zkClient.exists(adminFile, true)) {
            log.error("Can not find: " + adminFile);
            rsp.setException(new SolrException(SolrException.ErrorCode.NOT_FOUND, "Can not find: " + adminFile));
            return null;
        }

        return adminFile;
    }

    // Find the file indicated by the "file=XXX" parameter or the root of the conf directory on the local
    // file system. Respects all the "interesting" stuff around what the resource loader does to find files.
    public static File getAdminFileFromFileSystem(SolrQueryRequest req, SolrQueryResponse rsp,
            Set<String> hiddenFiles) {
        File adminFile = null;
        final SolrResourceLoader loader = req.getCore().getResourceLoader();
        File configdir = new File(loader.getConfigDir());
        if (!configdir.exists()) {
            // TODO: maybe we should just open it this way to start with?
            try {
                configdir = new File(loader.getClassLoader().getResource(loader.getConfigDir()).toURI());
            } catch (URISyntaxException e) {
                log.error("Can not access configuration directory!");
                rsp.setException(new SolrException(SolrException.ErrorCode.FORBIDDEN,
                        "Can not access configuration directory!", e));
                return null;
            }
        }
        String fname = req.getParams().get("file", null);
        if (fname == null) {
            adminFile = configdir;
        } else {
            fname = fname.replace('\\', '/'); // normalize slashes
            if (hiddenFiles.contains(fname.toUpperCase(Locale.ROOT))) {
                log.error("Can not access: " + fname);
                rsp.setException(new SolrException(SolrException.ErrorCode.FORBIDDEN, "Can not access: " + fname));
                return null;
            }
            if (fname.indexOf("..") >= 0) {
                log.error("Invalid path: " + fname);
                rsp.setException(new SolrException(SolrException.ErrorCode.FORBIDDEN, "Invalid path: " + fname));
                return null;
            }
            adminFile = new File(configdir, fname);
        }
        return adminFile;
    }

    public final Set<String> getHiddenFiles() {
        return hiddenFiles;
    }

    //////////////////////// SolrInfoMBeans methods //////////////////////

    @Override
    public String getDescription() {
        return "Admin Config File -- view or update config files directly";
    }
}