/* 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);
}
}
}