/* This file is part of leafdigital picstory. picstory 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, either version 3 of the License, or (at your option) any later version. picstory 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. You should have received a copy of the GNU General Public License along with picstory. If not, see <http://www.gnu.org/licenses/>. Copyright 2010 Samuel Marshall. */ package com.leafdigital.picstory; import java.io.*; import java.security.NoSuchAlgorithmException; import java.util.*; import javax.servlet.http.HttpServletResponse; /** * Handles requests for static resources (CSS, etc). */ public class ResourceHandler extends RequestHandler { private File resourceFolder; private Map<String, Resource> resources = new HashMap<String, Resource>(); private class Resource { byte[] data; String hash; Resource(byte[] data) throws NoSuchAlgorithmException { this.data = data; this.hash = Util.hash(data).substring(0, 8); } /** * @return Data */ public byte[] getData() { return data; } /** * @return Hash of data */ public String getShortHash() { return hash; } } private enum Type { CSS(".css", "text/css", true), JAVASCRIPT(".js", "text/js", true), PNG(".png", "image/png", false), GIF(".gif", "image/gif", false), JPEG(".jpg", "image/jpeg", false); private String extension, type; private boolean text; Type(String extension, String type, boolean text) { this.extension = extension; this.type = type; this.text = text; } /** * @return True if file is a text file */ public boolean isText() { return text; } /** * @return MIME content type */ public String getType() { return type; } /** * @param file Filename * @return Type * @throws InternalException If type unknown */ public static Type get(String file) throws InternalException { for(Type type : Type.values()) { if(file.endsWith(type.extension)) { return type; } } throw new InternalException("File type unknown for '" + file + "'"); } } /** * @param mainServlet Main servlet * @param resourceFolder Resource folder */ public ResourceHandler(MainServlet mainServlet, File resourceFolder) { super(mainServlet); this.resourceFolder = resourceFolder; } /** * @param r HTTP request * @param base Base part of filename * @param hash Expected hash * @param extension Extension part of filename including . * @throws UserException File not found, etc * @throws IOException Error sending data * @throws NoSuchAlgorithmException Problem with Java install */ public void get(Request r, String base, String hash, String extension) throws UserException, IOException, NoSuchAlgorithmException { // Check for If-Modified-Since (resources use hash so are never modified) if(r.handleIfModifiedSince()) { return; } String file = base + extension; // Obtain resource Resource resource; try { resource = getResource(file); } catch(IllegalArgumentException e) { throw new UserException(HttpServletResponse.SC_NOT_FOUND, e.getMessage()); } // Check hash if(!resource.getShortHash().equals(hash)) { r.redirect("./" + base + "." + resource.getShortHash() + extension); return; } // Get content type Type type = Type.get(file); // Don't let this expire r.preventExpiry(); // Send it if(type.isText()) { r.outputText(HttpServletResponse.SC_OK, type.getType(), new String(resource.getData(), "UTF-8")); } else { r.outputBinary(HttpServletResponse.SC_OK, type.getType(), resource.getData()); } } /** * @param file Filename * @return Resource * @throws IllegalArgumentException File not found etc * @throws NoSuchAlgorithmException Stupid configuration error * @throws IOException Read error */ private Resource getResource(String file) throws IllegalArgumentException, NoSuchAlgorithmException, IOException { Resource resource; synchronized(this) { if(!resources.containsKey(file)) { // Check filename is safe if(!file.matches(MainServlet.REGEX_PART_LCNAME + "\\." + MainServlet.REGEX_PART_LCNAME)) { throw new IllegalArgumentException( "Resource '" + Util.esc(file) + "' contains illegal characters"); } // Look for file File f = new File(resourceFolder, file); if(!f.exists()) { throw new IllegalArgumentException( "Resource '" + Util.esc(file) + "' not found"); } // Read file resources.put(file, new Resource(Util.loadBytes(new FileInputStream(f)))); } resource = resources.get(file); } return resource; } /** * Forces the system to reload all resources next time they are accessed. */ public void reload() { synchronized(this) { resources.clear(); } } /** * @param file Filename * @return Short hash * @throws IllegalArgumentException File not known * @throws InternalException Unexpected error */ public String getHash(String file) throws IllegalArgumentException, InternalException { try { return getResource(file).getShortHash(); } catch(NoSuchAlgorithmException e) { throw new InternalException(e); } catch(IOException e) { throw new InternalException(e); } } }