/* * BitstreamServlet.java * * Version: $Revision: 4430 $ * * Date: $Date: 2009-10-10 17:21:30 +0000 (Sat, 10 Oct 2009) $ * * Copyright (c) 2002-2005, Hewlett-Packard Company and Massachusetts * Institute of Technology. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * - Neither the name of the Hewlett-Packard Company nor the name of the * Massachusetts Institute of Technology nor the names of their * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. */ package org.dspace.app.webui.servlet; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.sql.SQLException; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.mail.internet.MimeUtility; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.log4j.Logger; import org.dspace.app.webui.util.JSPManager; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.core.ConfigurationManager; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.LogManager; import org.dspace.core.Utils; import org.dspace.handle.HandleManager; import org.dspace.services.model.Event; import org.dspace.usage.UsageEvent; import org.dspace.utils.DSpace; /** * Servlet for retrieving bitstreams. The bits are simply piped to the user. If * there is an <code>If-Modified-Since</code> header, only a 304 status code * is returned if the containing item has not been modified since that date. * <P> * <code>/bitstream/handle/sequence_id/filename</code> * * @author Robert Tansley * @version $Revision: 4430 $ */ public class BitstreamServlet extends DSpaceServlet { /** log4j category */ private static Logger log = Logger.getLogger(BitstreamServlet.class); /** * Threshold on Bitstream size before content-disposition will be set. */ private int threshold; /** * Pattern used to get file.ext from filename (which can be a path) */ private static Pattern p = Pattern.compile("[^/]*$"); @Override public void init(ServletConfig arg0) throws ServletException { super.init(arg0); threshold = ConfigurationManager .getIntProperty("webui.content_disposition_threshold"); } @Override protected void doDSGet(Context context, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException, SQLException, AuthorizeException { Item item = null; Bitstream bitstream = null; // Get the ID from the URL String idString = request.getPathInfo(); String handle = ""; String sequenceText = ""; String filename = null; int sequenceID; if (idString == null) { idString = ""; } // Parse 'handle' and 'sequence' (bitstream seq. number) out // of remaining URL path, which is typically of the format: // {handle}/{sequence}/{bitstream-name} // But since the bitstream name MAY have any number of "/"s in // it, and the handle is guaranteed to have one slash, we // scan from the start to pick out handle and sequence: // Remove leading slash if any: if (idString.startsWith("/")) { idString = idString.substring(1); } // skip first slash within handle int slashIndex = idString.indexOf('/'); if (slashIndex != -1) { slashIndex = idString.indexOf('/', slashIndex + 1); if (slashIndex != -1) { handle = idString.substring(0, slashIndex); int slash2 = idString.indexOf('/', slashIndex + 1); if (slash2 != -1) { sequenceText = idString.substring(slashIndex+1,slash2); filename = idString.substring(slash2+1); } } } try { sequenceID = Integer.parseInt(sequenceText); } catch (NumberFormatException nfe) { sequenceID = -1; } // Now try and retrieve the item DSpaceObject dso = HandleManager.resolveToObject(context, handle); // Make sure we have valid item and sequence number if (dso != null && dso.getType() == Constants.ITEM && sequenceID >= 0) { item = (Item) dso; if (item.isWithdrawn()) { log.info(LogManager.getHeader(context, "view_bitstream", "handle=" + handle + ",withdrawn=true")); JSPManager.showJSP(request, response, "/tombstone.jsp"); return; } boolean found = false; Bundle[] bundles = item.getBundles(); for (int i = 0; (i < bundles.length) && !found; i++) { Bitstream[] bitstreams = bundles[i].getBitstreams(); for (int k = 0; (k < bitstreams.length) && !found; k++) { if (sequenceID == bitstreams[k].getSequenceID()) { bitstream = bitstreams[k]; found = true; } } } } if (bitstream == null || filename == null || !filename.equals(bitstream.getName())) { // No bitstream found or filename was wrong -- ID invalid log.info(LogManager.getHeader(context, "invalid_id", "path=" + idString)); JSPManager.showInvalidIDError(request, response, idString, Constants.BITSTREAM); return; } log.info(LogManager.getHeader(context, "view_bitstream", "bitstream_id=" + bitstream.getID())); //new UsageEvent().fire(request, context, AbstractUsageEvent.VIEW, // Constants.BITSTREAM, bitstream.getID()); new DSpace().getEventService().fireEvent( new UsageEvent( UsageEvent.Action.VIEW, request, context, bitstream)); // Modification date // Only use last-modified if this is an anonymous access // - caching content that may be generated under authorisation // is a security problem if (context.getCurrentUser() == null) { // TODO: Currently the date of the item, since we don't have dates // for files response.setDateHeader("Last-Modified", item.getLastModified() .getTime()); // Check for if-modified-since header long modSince = request.getDateHeader("If-Modified-Since"); if (modSince != -1 && item.getLastModified().getTime() < modSince) { // Item has not been modified since requested date, // hence bitstream has not; return 304 response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); return; } } // Pipe the bits InputStream is = bitstream.retrieve(); // Set the response MIME type response.setContentType(bitstream.getFormat().getMIMEType()); // Response length response.setHeader("Content-Length", String .valueOf(bitstream.getSize())); if(threshold != -1 && bitstream.getSize() >= threshold) { setBitstreamDisposition(bitstream.getName(), request, response); } Utils.bufferedCopy(is, response.getOutputStream()); is.close(); response.getOutputStream().flush(); } /** * Evaluate filename and client and encode appropriate disposition * * @param uri * @param request * @param response * @throws UnsupportedEncodingException */ private void setBitstreamDisposition(String filename, HttpServletRequest request, HttpServletResponse response) { String name = filename; Matcher m = p.matcher(name); if (m.find() && !m.group().equals("")) { name = m.group(); } try { String agent = request.getHeader("USER-AGENT"); if (null != agent && -1 != agent.indexOf("MSIE")) { name = URLEncoder.encode(name, "UTF8"); } else if (null != agent && -1 != agent.indexOf("Mozilla")) { name = MimeUtility.encodeText(name, "UTF8", "B"); } } catch (UnsupportedEncodingException e) { log.error(e.getMessage(),e); } finally { response.setHeader("Content-Disposition", "attachment;filename=" + name); } } }