org.flowerplatform.communication.public_resources.PublicResourcesServlet.java Source code

Java tutorial

Introduction

Here is the source code for org.flowerplatform.communication.public_resources.PublicResourcesServlet.java

Source

/* license-start
 * 
 * Copyright (C) 2008 - 2013 Crispico, <http://www.crispico.com/>.
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation version 3.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details, at <http://www.gnu.org/licenses/>.
 * 
 * Contributors:
 *   Crispico - Initial API and implementation
 *
 * license-end
 */
package org.flowerplatform.communication.public_resources;

import java.io.BufferedInputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLDecoder;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;
import org.flowerplatform.common.plugin.AbstractFlowerJavaPlugin;
import org.flowerplatform.common.util.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Inspired from FileServlet proposed here: http://balusc.blogspot.de/2007/07/fileservlet.html
 * 
 * @author Cristi
 */
public class PublicResourcesServlet extends HttpServlet {

    private static final Logger logger = LoggerFactory.getLogger(PublicResourcesServlet.class);

    private static final long serialVersionUID = 1L;

    private static final int DEFAULT_BUFFER_SIZE = 10240; // 10KB.

    protected static PublicResourcesServlet INSTANCE;

    public static final String PATH_PREFIX = "/public-resources";

    public static PublicResourcesServlet getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new PublicResourcesServlet();
        }
        return INSTANCE;
    }

    private static void close(Closeable resource) {
        if (resource != null) {
            try {
                resource.close();
            } catch (IOException e) {
                // Do nothing.
            }
        }
    }

    protected void send404(HttpServletRequest request, HttpServletResponse response) {
        try {
            response.sendError(HttpServletResponse.SC_NOT_FOUND); // 404.
        } catch (IOException e) {
            // do nothing
        }
        logger.warn("Resource not found; sending 404: {}", request.getPathInfo());
    }

    protected Pair<InputStream, Closeable> getInputStreamForFileWithinZip(final InputStream fileInputStream,
            String fileWithinZip) throws IOException {
        final BufferedInputStream bis = new BufferedInputStream(fileInputStream, DEFAULT_BUFFER_SIZE);
        final ZipInputStream zis = new ZipInputStream(bis);

        Closeable closeable = new Closeable() {

            @Override
            public void close() throws IOException {
                zis.close();
                bis.close();
                fileInputStream.close();
            }
        };

        for (ZipEntry ze = zis.getNextEntry(); ze != null; ze = zis.getNextEntry()) {
            if (fileWithinZip.equals(ze.getName())) {
                return new Pair<InputStream, Closeable>(zis, closeable);
            }
        }

        closeable.close();
        return null;
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String requestedFile = request.getPathInfo();
        if (logger.isTraceEnabled()) {
            logger.trace("Resource requested: {}", requestedFile);
        }

        // Check if file is actually supplied to the request URI.
        if (requestedFile == null) {
            send404(request, response);
            return;
        }

        // Decode the file name (might contain spaces and on) and prepare file
        // object.
        requestedFile = URLDecoder.decode(requestedFile, "UTF-8");
        if (requestedFile.startsWith(PATH_PREFIX)) {
            requestedFile = requestedFile.substring(PATH_PREFIX.length());
        }

        // this may be an attempt to see files that are not public, i.e. to go to the
        // parent. From my tests, when you put in the browser (or even telnet) something like
        // parent1/parent2/../bla, it seems to be automatically translated to parent1/bla. However,
        // I wanted to make sure that we are all right
        if (requestedFile.contains("..")) {
            send404(request, response);
            return;
        }

        // we need something like /plugin/file....
        int indexOfSecondSlash = requestedFile.indexOf('/', 1); // 1, i.e. skip the first index
        if (indexOfSecondSlash < 0) {
            send404(request, response);
            return;
        }

        // both variables are prefixed with /
        String plugin = requestedFile.substring(0, indexOfSecondSlash);
        String file = requestedFile.substring(indexOfSecondSlash);

        // if | is supplied => the file is a zip, and we want what's in it
        int indexOfZipSeparator = file.indexOf('|');
        String fileInsideZipArchive = null;
        if (indexOfZipSeparator >= 0 && indexOfZipSeparator < file.length() - 1) { // has | and | is not the last char in the string
            fileInsideZipArchive = file.substring(indexOfZipSeparator + 1);
            file = file.substring(0, indexOfZipSeparator);
        }

        requestedFile = "platform:/plugin" + plugin + "/" + AbstractFlowerJavaPlugin.PUBLIC_RESOURCES_DIR + file;

        // Get content type by filename from the file or file inside zip
        String contentType = getServletContext()
                .getMimeType(fileInsideZipArchive != null ? fileInsideZipArchive : file);

        // If content type is unknown, then set the default value.
        // For all content types, see:
        // http://www.w3schools.com/media/media_mimeref.asp
        // To add new content types, add new mime-mapping entry in web.xml.
        if (contentType == null) {
            contentType = "application/octet-stream";
        }

        // Init servlet response.
        response.reset();
        response.setBufferSize(DEFAULT_BUFFER_SIZE);
        response.setContentType(contentType);
        //        response.setHeader("Content-Length", String.valueOf(file.length()));
        //        response.setHeader("Content-Disposition", "attachment; filename=\"" + file.getName() + "\"");

        // Prepare streams.
        URL url;
        InputStream input = null;
        Closeable inputCloseable = null;
        OutputStream output = null;

        try {
            url = new URL(requestedFile);
            try {
                input = url.openConnection().getInputStream();
                inputCloseable = input;
            } catch (IOException e) {
                // may fail if the resource is not available
                send404(request, response);
                return;
            }

            if (fileInsideZipArchive != null) {
                // we need to look for a file in the archive
                Pair<InputStream, Closeable> pair = getInputStreamForFileWithinZip(input, fileInsideZipArchive);
                if (pair == null) {
                    // the file was not found; the input streams are closed in this case
                    send404(request, response);
                    return;
                }

                input = pair.a;
                inputCloseable = pair.b;
            }

            output = response.getOutputStream();

            // according to the doc, no need to use Buffered..., because the method buffers internally
            IOUtils.copy(input, output);

        } finally {
            // Gently close streams.
            close(output);
            close(inputCloseable);
        }

    }

}