// $HeadURL$
// $Id$
//
// Copyright © 2010 by the President and Fellows of Harvard College.
//
// Screensaver is an open-source project developed by the ICCB-L and NSRB labs
// at Harvard Medical School. This software is distributed under the terms of
// the GNU General Public License.
package edu.harvard.med.screensaver.ui.arch.util.servlet;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
import edu.harvard.med.screensaver.ScreensaverConstants;
import edu.harvard.med.screensaver.ui.ApplicationInfo;
import edu.harvard.med.screensaver.util.StringUtils;
import edu.harvard.med.screensaver.util.UrlEncrypter;
/**
* This servlet allows access to images located outside the web application directory on the server.<br>
* This is a spring-managed bean implementing the {@link Servlet} interface, intended for use with the
* {@link DelegatingServletProxy}<br>
* <br>
* Function:<br>
* <br>
* Http requests to this servlet are be treated as image requests. The image is located by prepending a filesystem
* location to the "extra path info" of the URL requested by the client. The image, if found, is written to the
* response.
* "extra path info" refers to the portion of the URL after the servlet name and before any query parameters, see
* {@link HttpServletRequest#getPathInfo()}. <br>
* <br>
* <ul>
* Usage:
* <li>This is a Spring aware {@link Servlet} implementation that can be invoked from {@link DelegatingServletProxy}.
* <li>This class must be instantiated in a Spring configuration file (i.e. spring-context.xml).
* <li>The application property <b>"screensaver.ui.imageproviderservlet.filesystempath"</b> is required. This
* should be {@link File#isAbsolute()}; however, if the path is relative, it will be interpreted as relative to the web
* application root directory.
*
* TODO: consider replacing this class with a REST-ful service now that we have begun implementing that. -sde4
*
* </ul>
*/
public class ImageProviderServlet extends HttpServlet
{
private static final long serialVersionUID = 1L;
private static Logger log = Logger.getLogger(ImageProviderServlet.class);
private String _imagesFileSystemPath;
private ApplicationInfo _appInfo;
private UrlEncrypter _urlEncrypter;
// private Pattern filenamePattern = Pattern.compile("(.*\\/)?((.+?)(\\.[^.]*$|$))");
/**
* @throws IllegalStateException if the application property
* <b>"screensaver.ui.imageproviderservlet.filesystempath"</b> is not defined.
*/
public ImageProviderServlet(ApplicationInfo appInfo)
{
_appInfo = appInfo;
_imagesFileSystemPath = appInfo.getApplicationProperties().getProperty(ScreensaverConstants.IMAGES_BASE_DIR);
if (StringUtils.isEmpty(_imagesFileSystemPath)) {
throw new IllegalStateException("The application property: " + ScreensaverConstants.IMAGES_BASE_DIR + " must be defined.");
}
log.debug("ImageProviderServlet: \"" + ScreensaverConstants.IMAGES_BASE_DIR + "\": " + _imagesFileSystemPath);
}
public ImageProviderServlet(ApplicationInfo appInfo, UrlEncrypter urlEncrypter) {
this(appInfo);
_urlEncrypter = urlEncrypter;
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
service(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
service(req, resp);
}
/**
* Http requests to this servlet are be treated as image requests. The image is
* located by prepending a filesystem location to the "extra path info" URL
* requested by the client. The image, if found, is written to the response.<br>
*
* @throws IOException if the image file cannot be found
*/
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
if (log.isDebugEnabled()) {
log.debug("ImageProviderServlet: \"" + ScreensaverConstants.IMAGES_BASE_DIR
+ "\": " + _imagesFileSystemPath);
log.debug("service: req.getPathInfo(): " + req.getPathInfo());
}
String pathInfo = req.getPathInfo();
if (pathInfo != null) {
// FYI... setting the content type is not necessary for most (modern) browsers
if (pathInfo.toLowerCase().matches(".*\\.png")) {
resp.setContentType("image/png");
}
else if (pathInfo.toLowerCase().matches(".*\\.gif")) {
resp.setContentType("image/gif");
}
else if (pathInfo.toLowerCase().matches(".*\\.bmp")) {
resp.setContentType("image/bmp");
}
else if (pathInfo.toLowerCase().matches(".*\\.tiff")) {
resp.setContentType("image/tiff");
}
else {
resp.setContentType("image/jpeg"); // default should be ok, since not strictly req'd
}
FileInputStream fis = null;
try {
File file = getImage(req.getSession().getServletContext().getRealPath("/"), pathInfo);
if (log.isDebugEnabled()) {
log.debug("retrieve the image: " + file.getAbsolutePath());
}
if(!file.exists()) {
log.warn("image file not found: " + file);
}
fis = new FileInputStream(file);
resp.getOutputStream().write( IOUtils.toByteArray(fis));
} catch (Exception e) {
log.error("on serving the image for req: " + req.getPathInfo() + ", interpreted as: " + pathInfo + ", " + e.getMessage());
resp.sendError(resp.SC_NOT_FOUND,
"Image not found: " + pathInfo);
} finally {
if (fis!= null) fis.close();
}
}
else {
throw new ServletException("No image pathInfo specified");
}
}
/**
* Factor the getImage method out so that clients may invoke this method directly,
* instead of only through the service method (via HTTP).
*/
public File getImage(String servletContextPath, String pathInfo) {
String temp = _imagesFileSystemPath;
// make a relative path if its not absolute, this is not uber-useful,
// but it allows a *default* screensaver.properties to specify a valid system file path
// as simply a relative one, the actual deployment should specify absolute system paths, however.
File file = new File(temp);
if (!file.isAbsolute())
{
temp = servletContextPath + temp;
file = new File(temp);
}
if(_urlEncrypter != null)
{
Pattern filenamePattern = Pattern.compile("(.*)" + _urlEncrypter.getDelimiter() + "(.*)" + _urlEncrypter.getDelimiter() + "(.*)");
String name = pathInfo;
Matcher m = filenamePattern.matcher(pathInfo);
if(m.matches())
{
if(log.isDebugEnabled()) log.debug("path: " + m.group(1) + ", filename: " + m.group(2) + ", ext: " + m.group(3));
name = m.group(1) + _urlEncrypter.decryptUrl(m.group(2));
name += m.group(3);
}else {
name = _urlEncrypter.decryptUrl(name);
}
if(log.isDebugEnabled()) log.info("pathInfo decrypted from: " + pathInfo + " to " + name);
pathInfo = name;
}
return new File(file, pathInfo);
}
// TODO: add the ImageProviderServlet (as a spring-bean) to beans that serve images to enable performing this check - sde4
public boolean canFindImage(URL url)
{
String baseUrl = _appInfo.getApplicationProperties().getProperty("screensaver.images.base_url");
String relativePath = url.toString().substring(baseUrl.length());
File file = new File(_imagesFileSystemPath, relativePath);
if (!file.isAbsolute())
{
// if path is relative, bypass this check, since those paths are used during testing
return true;
}
if(! file.exists()) {
log.info("can't find the image at the url: " + url + ", mapped to the file: " + file);
return false;
}
return true;
}
}