316 lines
9.8 KiB
Java
316 lines
9.8 KiB
Java
// FileServlet - servlet similar to a standard httpd
|
|
//
|
|
// Copyright (C)1996,1998 by Jef Poskanzer <jef@acme.com>. All rights reserved.
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions
|
|
// are met:
|
|
// 1. Redistributions of source code must retain the above copyright
|
|
// notice, this list of conditions and the following disclaimer.
|
|
// 2. 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.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
|
|
//
|
|
// Visit the ACME Labs Java page for up-to-date versions of this and other
|
|
// fine Java utilities: http://www.acme.com/java/
|
|
|
|
package Acme.Serve;
|
|
|
|
import java.io.*;
|
|
import java.util.*;
|
|
import java.text.*;
|
|
import javax.servlet.*;
|
|
import javax.servlet.http.*;
|
|
|
|
/// Servlet similar to a standard httpd.
|
|
// <P>
|
|
// Implements the "GET" and "HEAD" methods for files and directories.
|
|
// Handles index.html.
|
|
// Redirects directory URLs that lack a trailing /.
|
|
// Handles If-Modified-Since and Range.
|
|
// <P>
|
|
// <A HREF="/resources/classes/Acme/Serve/FileServlet.java">Fetch the software.</A><BR>
|
|
// <A HREF="/resources/classes/Acme.tar.gz">Fetch the entire Acme package.</A>
|
|
// <P>
|
|
// @see Acme.Serve.Serve
|
|
|
|
public class FileServlet extends HttpServlet
|
|
{
|
|
|
|
// We keep a single throttle table for all instances of the servlet.
|
|
// Normally there is only one instance; the exception is subclasses.
|
|
static Acme.WildcardDictionary throttleTab = null;
|
|
|
|
/// Constructor.
|
|
public FileServlet()
|
|
{
|
|
}
|
|
|
|
/// Constructor with throttling.
|
|
// @param throttles filename containing throttle settings
|
|
// @see ThrottledOutputStream
|
|
public FileServlet( String throttles ) throws IOException
|
|
{
|
|
this();
|
|
readThrottles( throttles );
|
|
}
|
|
|
|
private void readThrottles( String throttles ) throws IOException
|
|
{
|
|
Acme.WildcardDictionary newThrottleTab =
|
|
ThrottledOutputStream.parseThrottleFile( throttles );
|
|
if ( throttleTab == null )
|
|
throttleTab = newThrottleTab;
|
|
else
|
|
{
|
|
// Merge the new one into the old one.
|
|
Enumeration keys = newThrottleTab.keys();
|
|
Enumeration elements = newThrottleTab.elements();
|
|
while ( keys.hasMoreElements() )
|
|
{
|
|
Object key = keys.nextElement();
|
|
Object element = elements.nextElement();
|
|
throttleTab.put( key, element );
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Returns a string containing information about the author, version, and
|
|
// copyright of the servlet.
|
|
public String getServletInfo()
|
|
{
|
|
return "servlet similar to a standard httpd";
|
|
}
|
|
|
|
|
|
/// Services a single request from the client.
|
|
// @param req the servlet request
|
|
// @param req the servlet response
|
|
// @exception ServletException when an exception has occurred
|
|
public void service( HttpServletRequest req, HttpServletResponse res ) throws ServletException, IOException
|
|
{
|
|
boolean headOnly;
|
|
if ( req.getMethod().equalsIgnoreCase( "get" ) )
|
|
headOnly = false;
|
|
else if ( ! req.getMethod().equalsIgnoreCase( "head" ) )
|
|
headOnly = true;
|
|
else
|
|
{
|
|
res.sendError( HttpServletResponse.SC_NOT_IMPLEMENTED );
|
|
return;
|
|
}
|
|
|
|
String path = req.getServletPath();
|
|
if ( path == null || path.charAt( 0 ) != '/' )
|
|
{
|
|
res.sendError( HttpServletResponse.SC_BAD_REQUEST );
|
|
return;
|
|
}
|
|
if ( path.indexOf( "/../" ) != -1 || path.endsWith( "/.." ) )
|
|
{
|
|
res.sendError( HttpServletResponse.SC_FORBIDDEN );
|
|
return;
|
|
}
|
|
|
|
// Make a version without the leading /.
|
|
String pathname = path.substring( 1 );
|
|
if ( pathname.length() == 0 )
|
|
pathname = "./";
|
|
|
|
dispatchPathname( req, res, headOnly, path, pathname );
|
|
}
|
|
|
|
|
|
private void dispatchPathname( HttpServletRequest req, HttpServletResponse res, boolean headOnly, String path, String pathname ) throws IOException
|
|
{
|
|
String filename = pathname.replace( '/', File.separatorChar );
|
|
if ( filename.charAt( filename.length() - 1 ) == File.separatorChar )
|
|
filename = filename.substring( 0, filename.length() - 1 );
|
|
filename = getServletContext().getRealPath( filename );
|
|
File file = new File( filename );
|
|
if ( file.exists() )
|
|
{
|
|
if ( ! file.isDirectory() )
|
|
serveFile( req, res, headOnly, path, filename, file );
|
|
else
|
|
{
|
|
if ( pathname.charAt( pathname.length() - 1 ) != '/' )
|
|
redirectDirectory( req, res, path, file );
|
|
else
|
|
{
|
|
String indexFilename =
|
|
filename + File.separatorChar + "index.html";
|
|
File indexFile = new File( indexFilename );
|
|
if ( indexFile.exists() )
|
|
serveFile(
|
|
req, res, headOnly, path, indexFilename,
|
|
indexFile );
|
|
else
|
|
serveDirectory(
|
|
req, res, headOnly, path, filename, file );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( pathname.endsWith( "/index.html" ) )
|
|
dispatchPathname(
|
|
req, res, headOnly, path,
|
|
pathname.substring( 0, pathname.length() - 10 ) );
|
|
else if ( pathname.equals( "index.html" ) )
|
|
dispatchPathname( req, res, headOnly, path, "./" );
|
|
else
|
|
res.sendError( HttpServletResponse.SC_NOT_FOUND );
|
|
}
|
|
}
|
|
|
|
|
|
protected void serveFile( HttpServletRequest req, HttpServletResponse res, boolean headOnly, String path, String filename, File file ) throws IOException
|
|
{
|
|
// log( "getting " + path );
|
|
if ( ! file.canRead() )
|
|
{
|
|
res.sendError( HttpServletResponse.SC_FORBIDDEN );
|
|
return;
|
|
}
|
|
|
|
// Handle If-Modified-Since.
|
|
res.setStatus( HttpServletResponse.SC_OK );
|
|
long lastMod = file.lastModified();
|
|
String ifModSinceStr = req.getHeader( "If-Modified-Since" );
|
|
long ifModSince = -1;
|
|
if ( ifModSinceStr != null )
|
|
{
|
|
int semi = ifModSinceStr.indexOf( ';' );
|
|
if ( semi != -1 )
|
|
ifModSinceStr = ifModSinceStr.substring( 0, semi );
|
|
try
|
|
{
|
|
ifModSince =
|
|
DateFormat.getDateInstance().parse( ifModSinceStr ).getTime();
|
|
}
|
|
catch ( Exception ignore ) {}
|
|
}
|
|
if ( ifModSince != -1 && ifModSince >= lastMod )
|
|
{
|
|
res.setStatus( HttpServletResponse.SC_NOT_MODIFIED );
|
|
headOnly = true;
|
|
}
|
|
|
|
String rangeStr = req.getHeader( "Range" );
|
|
if ( rangeStr != null )
|
|
{
|
|
//!!!
|
|
}
|
|
|
|
res.setContentType( getServletContext().getMimeType( filename ) );
|
|
res.setContentLength( (int) file.length() );
|
|
res.setDateHeader( "Last-modified", lastMod );
|
|
OutputStream out = res.getOutputStream();
|
|
if ( ! headOnly )
|
|
{
|
|
// Check throttle.
|
|
if ( throttleTab != null )
|
|
{
|
|
ThrottleItem throttleItem =
|
|
(ThrottleItem) throttleTab.get( path );
|
|
if ( throttleItem != null )
|
|
{
|
|
// !!! Need to account for multiple simultaneous fetches.
|
|
out = new ThrottledOutputStream(
|
|
out, throttleItem.getMaxBps() );
|
|
}
|
|
}
|
|
|
|
InputStream in = new FileInputStream( file );
|
|
copyStream( in, out );
|
|
in.close();
|
|
}
|
|
out.close();
|
|
}
|
|
|
|
/// Copy a file from in to out.
|
|
// Sub-classes can override this in order to do filtering of some sort.
|
|
public void copyStream( InputStream in, OutputStream out ) throws IOException
|
|
{
|
|
Acme.Utils.copyStream( in, out );
|
|
}
|
|
|
|
|
|
private void serveDirectory( HttpServletRequest req, HttpServletResponse res, boolean headOnly, String path, String filename, File file ) throws IOException
|
|
{
|
|
log( "indexing " + path );
|
|
if ( ! file.canRead() )
|
|
{
|
|
res.sendError( HttpServletResponse.SC_FORBIDDEN );
|
|
return;
|
|
}
|
|
res.setStatus( HttpServletResponse.SC_OK );
|
|
res.setContentType( "text/html" );
|
|
OutputStream out = res.getOutputStream();
|
|
if ( ! headOnly )
|
|
{
|
|
PrintStream p = new PrintStream( new BufferedOutputStream( out ) );
|
|
p.println( "<HTML><HEAD>" );
|
|
p.println( "<TITLE>Index of " + path + "</TITLE>" );
|
|
p.println( "</HEAD><BODY BGCOLOR=\"#ffffff\">" );
|
|
p.println( "<H2>Index of " + path + "</H2>" );
|
|
p.println( "<PRE>" );
|
|
p.println( "mode bytes last-changed name" );
|
|
p.println( "<HR>" );
|
|
String[] names = file.list();
|
|
Acme.Utils.sortStrings( names );
|
|
for ( int i = 0; i < names.length; ++i )
|
|
{
|
|
String aFilename = filename + File.separatorChar + names[i];
|
|
File aFile = new File( aFilename );
|
|
String aFileType;
|
|
if ( aFile.isDirectory() )
|
|
aFileType = "d";
|
|
else if ( aFile.isFile() )
|
|
aFileType = "-";
|
|
else
|
|
aFileType = "?";
|
|
String aFileRead = ( aFile.canRead() ? "r" : "-" );
|
|
String aFileWrite = ( aFile.canWrite() ? "w" : "-" );
|
|
String aFileExe = "-";
|
|
String aFileSize = Acme.Fmt.fmt( aFile.length(), 8 );
|
|
String aFileDate =
|
|
Acme.Utils.lsDateStr( new Date( aFile.lastModified() ) );
|
|
String aFileDirsuf = ( aFile.isDirectory() ? "/" : "" );
|
|
String aFileSuf = ( aFile.isDirectory() ? "/" : "" );
|
|
p.println(
|
|
aFileType + aFileRead + aFileWrite + aFileExe +
|
|
" " + aFileSize + " " + aFileDate + " " +
|
|
"<A HREF=\"" + names[i] + aFileDirsuf + "\">" +
|
|
names[i] + aFileSuf + "</A>" );
|
|
}
|
|
p.println( "</PRE>" );
|
|
p.println( "<HR>" );
|
|
ServeUtils.writeAddress( p );
|
|
p.println( "</BODY></HTML>" );
|
|
p.flush();
|
|
}
|
|
out.close();
|
|
}
|
|
|
|
|
|
protected void redirectDirectory( HttpServletRequest req, HttpServletResponse res, String path, File file ) throws IOException
|
|
{
|
|
log( "redirecting " + path );
|
|
res.sendRedirect( path + "/" );
|
|
}
|
|
|
|
}
|