Java tutorial
/* license-start * * Copyright (C) 2008 - 2013 Crispico Software, <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/>. * * license-end */ package org.flowerplatform.util.servlet; import java.io.BufferedInputStream; import java.io.Closeable; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URL; import java.net.URLDecoder; import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.io.IOUtils; import org.flowerplatform.util.Pair; import org.flowerplatform.util.UtilConstants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Inspired from FileServlet proposed here: http://balusc.blogspot.de/2007/07/fileservlet.html. * * <p> * Serves files from <plugin>/public-resources/*. * * @author Cristian Spiescu */ public class PublicResourcesServlet extends ResourcesServlet { private static final Logger logger = LoggerFactory.getLogger(PublicResourcesServlet.class); private static final long serialVersionUID = 1L; private static final int DEFAULT_BUFFER_SIZE = 10240; // 10KB. private static void close(Closeable resource) { if (resource != null) { try { resource.close(); } catch (IOException e) { // Do nothing. } } } 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; } /** * @author Cristian Spiescu * @author Sebastian Solomon * @author Cristina Constantinescu */ @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. // solution for: .../icons/0%.png // The URL decoder expects the %to precede a character code // replace(%) with %25 before decoding // http://stackoverflow.com/questions/6067673/urldecoder-illegal-hex-characters-in-escape-pattern-for-input-string requestedFile = requestedFile.replaceAll("%(?![0-9a-fA-F]{2})", "%25"); requestedFile = URLDecoder.decode(requestedFile, "UTF-8"); if (requestedFile.startsWith(UtilConstants.PUBLIC_RESOURCES_PATH_PREFIX)) { requestedFile = requestedFile.substring(UtilConstants.PUBLIC_RESOURCES_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(UtilConstants.RESOURCE_PATH_SEPARATOR); 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); } String mapValue = null; String mapKey = null; // if the file is in a zip, search first in the Temp folder if (fileInsideZipArchive != null) { mapKey = createMapKey(file, fileInsideZipArchive).intern(); if (useFilesFromTemporaryDirectory) { mapValue = tempFilesMap.get(mapKey); } } else { // we don't need synchronization if the file is not inside archive (so we don't use .intern) mapKey = createMapKey(file, fileInsideZipArchive); } synchronized (mapKey) { if (useFilesFromTemporaryDirectory) { if (fileInsideZipArchive != null) { if (mapValue != null) { // file exists in 'tempFilesMap' if (getTempFile(mapValue).exists()) { response.reset(); response.setBufferSize(DEFAULT_BUFFER_SIZE); response.setContentType(fileInsideZipArchive); InputStream input = new FileInputStream(getTempFilePath(mapValue)); OutputStream output = response.getOutputStream(); IOUtils.copy(input, output); input.close(); output.close(); logger.debug("File {} served from temp", mapValue); return; } else { // temporary file was deleted from disk logger.debug("File {} found to be missing from temp", mapValue); } } else { synchronized (this) { counter++; mapValue = counter + ""; tempFilesMap.put(mapKey, mapValue); logger.debug("mapValue '{}' added", mapValue); } } } } requestedFile = "platform:/plugin" + plugin + "/" + UtilConstants.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; if (useFilesFromTemporaryDirectory) { // write the file from archive in Temp folder if (!UtilConstants.TEMP_FOLDER.exists()) { UtilConstants.TEMP_FOLDER.mkdir(); } Files.copy(input, getTempFile(mapValue).toPath(), StandardCopyOption.REPLACE_EXISTING); logger.debug("file '{}' was writen in temp", mapValue); input.close(); input = new FileInputStream(getTempFilePath(mapValue)); } } 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); close(input); } } } /** * @author Sebastian Solomon */ private String createMapKey(String fileName, String fileInsideZipArchive) { return fileName + UtilConstants.RESOURCE_PATH_SEPARATOR + fileInsideZipArchive; } }