tomcat-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From r...@locus.apache.org
Subject cvs commit: jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/servlets DefaultServlet.java
Date Sun, 04 Jun 2000 23:18:48 GMT
remm        00/06/04 16:18:48

  Modified:    proposals/catalina STATUS.html
               proposals/catalina/src/share/org/apache/tomcat/connector/http
                        HttpResponseStream.java LocalStrings.properties
               proposals/catalina/src/share/org/apache/tomcat/realm
                        RealmBase.java
               proposals/catalina/src/share/org/apache/tomcat/servlets
                        DefaultServlet.java
  Log:
  - Realm base now provides a default implementation for
  the Lifecycle interface methods.
  
  - Updated STATUS.html.
  
  - Support for most (all ?) the If related HTTP/1.1 headers.
  
  - Support for ranged requests (resuming), MIME multipart responses.
  Support for GetRight (and probably other similar tools).
  
  - ETag support.
  
  Revision  Changes    Path
  1.9       +4 -30     jakarta-tomcat/proposals/catalina/STATUS.html
  
  Index: STATUS.html
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat/proposals/catalina/STATUS.html,v
  retrieving revision 1.8
  retrieving revision 1.9
  diff -u -r1.8 -r1.9
  --- STATUS.html	2000/06/01 16:26:34	1.8
  +++ STATUS.html	2000/06/04 23:18:46	1.9
  @@ -92,13 +92,6 @@
       <th width="30%">Volunteers</th>
     </tr>
     <tr>
  -    <td align="center">High</td>
  -    <td>Design and implement a high-performance Connector implementation
  -        that fully supports HTTP/1.1 for use in stand-alone applications.
  -        [org.apache.tomcat.connector.http]</td>
  -    <td><a href="mailto:pier@apache.org">Pier P. Fumagalli</a></td>
  -  </tr>
  -  <tr>
       <td align="center">Medium</td>
       <td>Design and implement an extended version of the HTTP/1.1 connector
           that supports communication over secure sockets using SSL/TLS,
  @@ -197,12 +190,6 @@
       <th width="30%">Volunteers</th>
     </tr>
     <tr>
  -    <td align="center">Medium</td>
  -    <td>Design and implement a servlet that supports full WebDAV
  -        functionality.  [org.apache.tomcat.webdav]</td>
  -    <td><a href="mailto:remm@exoffice.com">Remy Maucherat</a></td>
  -  </tr>
  -  <tr>
       <td align="center">Low</td>
       <td>Design and implement a servlet that supports interpretation of
           web-server-like "server side include" directives, typically
  @@ -274,13 +261,6 @@
     <tr>
       <td align="center">Medium</td>
       <td>Design and implement a <code>Realm</code> implementation that
accesses
  -        authentication and role information from a JDBC-accessed database.
  -        [org.apache.tomcat.realm]</td>
  -    <td>---</td>
  -  </tr>
  -  <tr>
  -    <td align="center">Medium</td>
  -    <td>Design and implement a <code>Realm</code> implementation that
accesses
           authentication and role information from a JNDI-accessed directory
           server.  [org.apache.tomcat.realm]</td>
       <td><a href="mailto:JamesW@cardsetc.com">James W.</a></td>
  @@ -312,12 +292,6 @@
     </tr>
     <tr>
       <td align="center">High</td>
  -    <td>Finish design and implementation of HTTP BASIC authentication.
  -        [org.apache.tomcat.security]</td>
  -    <td><a href="mailto:Craig.McClanahan@eng.sun.com">Craig McClanahan</a></td>
  -  </tr>
  -  <tr>
  -    <td align="center">High</td>
       <td>Design and implement form-based authentication.
           [org.apache.tomcat.security]</td>
       <td><a href="mailto:hoon@raleigh.ibm.com">John Shin</a></td>
  @@ -326,7 +300,7 @@
       <td align="center">Medium</td>
       <td>Design and implement support for SSL/TLS based authentication.
           [org.apache.tomcat.security]</td>
  -    <td><a href="mailto:hoon@raliegh.ibm.com">John Shin</a></td>
  +    <td><a href="mailto:hoon@raleigh.ibm.com">John Shin</a></td>
     </tr>
     <tr>
       <td align="center">Medium</td>
  @@ -345,16 +319,16 @@
     </tr>
     <tr>
       <td align="center">Low</td>
  -    <td>Design and implement HTTP DIGEST authentication.
  +    <td>Improve security of the HTTP DIGEST authentication implementation.
           [org.apache.tomcat.security]</td>
  -    <td><a href="mailto:remm@exoffice.com">Remy Maucherat</a></td>
  +    <td><a href="mailto:remm@apache.org">Remy Maucherat</a></td>
     </tr>
   </table>
   
   
   <br>
   <div align="center"><hr width="75%"><font size="2">
  -$Id: STATUS.html,v 1.8 2000/06/01 16:26:34 craigmcc Exp $
  +$Id: STATUS.html,v 1.9 2000/06/04 23:18:46 remm Exp $
   </font></div>
   
   </body>
  
  
  
  1.3       +6 -4      jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/connector/http/HttpResponseStream.java
  
  Index: HttpResponseStream.java
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/connector/http/HttpResponseStream.java,v
  retrieving revision 1.2
  retrieving revision 1.3
  diff -u -r1.2 -r1.3
  --- HttpResponseStream.java	2000/05/23 06:05:53	1.2
  +++ HttpResponseStream.java	2000/06/04 23:18:47	1.3
  @@ -1,7 +1,7 @@
   /*
  - * $Header: /home/cvs/jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/connector/http/HttpResponseStream.java,v
1.2 2000/05/23 06:05:53 remm Exp $
  - * $Revision: 1.2 $
  - * $Date: 2000/05/23 06:05:53 $
  + * $Header: /home/cvs/jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/connector/http/HttpResponseStream.java,v
1.3 2000/06/04 23:18:47 remm Exp $
  + * $Revision: 1.3 $
  + * $Date: 2000/06/04 23:18:47 $
    *
    * ====================================================================
    * 
  @@ -99,7 +99,9 @@
   
   	super(response);
           this.useChunking = (response.isChunkingAllowed()
  -                            && response.getContentLength() == -1);
  +                            && response.getContentLength() == -1
  +                            && response.getStatus() != 206
  +                            && response.getStatus() != 304);
           if (this.useChunking)
               response.addHeader("Transfer-Encoding", "chunked");
   
  
  
  
  1.6       +1 -1      jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/connector/http/LocalStrings.properties
  
  Index: LocalStrings.properties
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/connector/http/LocalStrings.properties,v
  retrieving revision 1.5
  retrieving revision 1.6
  diff -u -r1.5 -r1.6
  --- LocalStrings.properties	2000/06/01 05:37:31	1.5
  +++ LocalStrings.properties	2000/06/04 23:18:47	1.6
  @@ -14,7 +14,7 @@
   httpProcessor.parseRequest.method=Missing HTTP request method
   httpProcessor.parseRequest.read=Missing HTTP request line
   httpProcessor.parseRequest.uri=Missing HTTP request URI
  -httpProcessor.parseHeaders.unknownExpectation=Unknown expectation header
  +httpProcessor.parseHeaders.unknownExpectation=Unknown Expect header field
   httpProcessor.start=HTTP processor has already been started
   httpProcessor.starting=Starting background thread
   httpProcessor.stopping=Stopping background thread
  
  
  
  1.3       +51 -4     jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/realm/RealmBase.java
  
  Index: RealmBase.java
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/realm/RealmBase.java,v
  retrieving revision 1.2
  retrieving revision 1.3
  diff -u -r1.2 -r1.3
  --- RealmBase.java	2000/05/31 18:33:29	1.2
  +++ RealmBase.java	2000/06/04 23:18:47	1.3
  @@ -1,7 +1,7 @@
   /*
  - * $Header: /home/cvs/jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/realm/RealmBase.java,v
1.2 2000/05/31 18:33:29 remm Exp $
  - * $Revision: 1.2 $
  - * $Date: 2000/05/31 18:33:29 $
  + * $Header: /home/cvs/jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/realm/RealmBase.java,v
1.3 2000/06/04 23:18:47 remm Exp $
  + * $Revision: 1.3 $
  + * $Date: 2000/06/04 23:18:47 $
    *
    * ====================================================================
    *
  @@ -96,7 +96,7 @@
    * location) are identical to those currently supported by Tomcat 3.X.
    *
    * @author Craig R. McClanahan
  - * @version $Revision: 1.2 $ $Date: 2000/05/31 18:33:29 $
  + * @version $Revision: 1.3 $ $Date: 2000/06/04 23:18:47 $
    */
   
   public abstract class RealmBase
  @@ -370,6 +370,53 @@
       public void removeLifecycleListener(LifecycleListener listener) {
   
   	lifecycle.removeLifecycleListener(listener);
  +
  +    }
  +
  +
  +    /**
  +     * Prepare for the beginning of active use of the public methods of this
  +     * component.  This method should be called before any of the public
  +     * methods of this component are utilized.  It should also send a
  +     * LifecycleEvent of type START_EVENT to any registered listeners.
  +     *
  +     * @exception IllegalStateException if this component has already been
  +     *  started
  +     * @exception LifecycleException if this component detects a fatal error
  +     *  that prevents this component from being used
  +     */
  +    public void start()
  +        throws LifecycleException {
  +
  +	// Validate and update our current component state
  +	if (started)
  +	    throw new LifecycleException
  +		(sm.getString("memoryRealm.alreadyStarted"));
  +	lifecycle.fireLifecycleEvent(START_EVENT, null);
  +	started = true;
  +
  +    }
  +
  +
  +    /**
  +     * Gracefully terminate the active use of the public methods of this
  +     * component.  This method should be the last one called on a given
  +     * instance of this component.  It should also send a LifecycleEvent
  +     * of type STOP_EVENT to any registered listeners.
  +     *
  +     * @exception IllegalStateException if this component has not been started
  +     * @exception LifecycleException if this component detects a fatal error
  +     *  that needs to be reported
  +     */
  +    public void stop()
  +        throws LifecycleException {
  +
  +	// Validate and update our current component state
  +	if (!started)
  +	    throw new LifecycleException
  +		(sm.getString("memoryRealm.notStarted"));
  +	lifecycle.fireLifecycleEvent(STOP_EVENT, null);
  +	started = false;
   
       }
   
  
  
  
  1.6       +650 -71   jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/servlets/DefaultServlet.java
  
  Index: DefaultServlet.java
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/servlets/DefaultServlet.java,v
  retrieving revision 1.5
  retrieving revision 1.6
  diff -u -r1.5 -r1.6
  --- DefaultServlet.java	2000/05/14 02:29:01	1.5
  +++ DefaultServlet.java	2000/06/04 23:18:48	1.6
  @@ -1,7 +1,7 @@
   /*
  - * $Header: /home/cvs/jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/servlets/DefaultServlet.java,v
1.5 2000/05/14 02:29:01 craigmcc Exp $
  - * $Revision: 1.5 $
  - * $Date: 2000/05/14 02:29:01 $
  + * $Header: /home/cvs/jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/servlets/DefaultServlet.java,v
1.6 2000/06/04 23:18:48 remm Exp $
  + * $Revision: 1.6 $
  + * $Date: 2000/06/04 23:18:48 $
    *
    * ====================================================================
    *
  @@ -74,14 +74,22 @@
   import java.net.MalformedURLException;
   import java.net.URL;
   import java.sql.Timestamp;
  +import java.util.Date;
   import java.util.Enumeration;
   import java.util.Vector;
  +import java.util.StringTokenizer;
  +import java.util.Locale;
  +import java.text.ParseException;
  +import java.text.SimpleDateFormat;
  +import java.security.MessageDigest;
  +import java.security.NoSuchAlgorithmException;
   import javax.servlet.RequestDispatcher;
   import javax.servlet.ServletException;
   import javax.servlet.ServletOutputStream;
   import javax.servlet.http.HttpServlet;
   import javax.servlet.http.HttpServletRequest;
   import javax.servlet.http.HttpServletResponse;
  +import org.apache.tomcat.util.MD5Encoder;
   import org.apache.tomcat.util.xml.SaxContext;
   import org.apache.tomcat.util.xml.XmlAction;
   import org.apache.tomcat.util.xml.XmlMapper;
  @@ -92,7 +100,8 @@
    * used to serve static resources such as HTML pages and images.
    *
    * @author Craig R. McClanahan
  - * @version $Revision: 1.5 $ $Date: 2000/05/14 02:29:01 $
  + * @author Remy Maucherat
  + * @version $Revision: 1.6 $ $Date: 2000/06/04 23:18:48 $
    */
   
   public final class DefaultServlet
  @@ -126,6 +135,34 @@
       private Vector welcomes = new Vector();
   
   
  +    /**
  +     * MD5 message digest provider.
  +     */
  +    private static MessageDigest md5Helper;
  +
  +
  +    /**
  +     * The MD5 helper object for this class.
  +     */
  +    private static final MD5Encoder md5Encoder = new MD5Encoder();
  +
  +
  +    /**
  +     * The set of SimpleDateFormat formats to use in getDateHeader().
  +     */
  +    private static final SimpleDateFormat formats[] = {
  +	new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US),
  +	new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US),
  +	new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US)
  +    };
  +
  +
  +    /**
  +     * MIME multipart separation string
  +     */
  +    private static final String mimeSeparation = "THIS_STRING_SEPARATES";
  +
  +
       // --------------------------------------------------------- Public Methods
   
   
  @@ -241,6 +278,14 @@
   		    welcomes.elementAt(i));
   	}
   
  +        // Load the MD5 helper used to calculate signatures.
  +        try {
  +            md5Helper = MessageDigest.getInstance("MD5");
  +        } catch (NoSuchAlgorithmException e) {
  +            e.printStackTrace();
  +            throw new IllegalStateException();
  +        }
  +
       }
   
   
  @@ -258,25 +303,60 @@
        *
        * @exception IOException if an input/output error occurs
        */
  -    private void copy(InputStream istream, ServletOutputStream ostream)
  +    private void copy(File file, ServletOutputStream ostream)
   	throws IOException {
   
  +        InputStream istream =	// FIXME: internationalization???????
  +            new BufferedInputStream(new FileInputStream(file), input);
  +        
   	// Copy the input stream to the output stream
   	IOException exception = null;
  -	byte buffer[] = new byte[input];
  -	int len = buffer.length;
  -	while (len >= buffer.length) {
  -	    try {
  -		len = istream.read(buffer);
  -		ostream.write(buffer, 0, len);
  -	    } catch (IOException e) {
  -		exception = e;
  -		len = -1;
  -	    }
  -	    if (len < buffer.length)
  -		break;
  +        if (file.length() > 0)
  +            exception = copyRange(istream, ostream, 0, file.length() - 1);
  +        
  +	// Clean up the input and output streams
  +	try {
  +	    istream.close();
  +	} catch (Throwable t) {
  +	    ;
  +	}
  +	try {
  +	    ostream.flush();
  +	} catch (Throwable t) {
  +	    ;
  +	}
  +	try {
  +	    ostream.close();
  +	} catch (Throwable t) {
  +	    ;
   	}
   
  +	// Rethrow any exception that has occurred
  +	if (exception != null)
  +	    throw exception;
  +
  +    }
  +
  +
  +    /**
  +     * Copy the contents of the specified input stream to the specified
  +     * output stream, and ensure that both streams are closed before returning
  +     * (even in the face of an exception).
  +     *
  +     * @param file The file object
  +     * @param ostream The output stream to write to
  +     * @param range Range the client wanted to retrieve
  +     * @exception IOException if an input/output error occurs
  +     */
  +    private void copy(File file, ServletOutputStream ostream, Range range)
  +	throws IOException {
  +        
  +        IOException exception = null;
  +        
  +        InputStream istream =
  +            new BufferedInputStream(new FileInputStream(file), input);
  +        exception = copyRange(istream, ostream, range.start, range.end);
  +        
   	// Clean up the input and output streams
   	try {
   	    istream.close();
  @@ -297,7 +377,125 @@
   	// Rethrow any exception that has occurred
   	if (exception != null)
   	    throw exception;
  +        
  +    }
  +
   
  +    /**
  +     * Copy the contents of the specified input stream to the specified
  +     * output stream, and ensure that both streams are closed before returning
  +     * (even in the face of an exception).
  +     *
  +     * @param file The file object
  +     * @param ostream The output stream to write to
  +     * @param ranges Enumeration of the ranges the client wanted to retrieve
  +     * @param contentType Content type of the resource
  +     * @exception IOException if an input/output error occurs
  +     */
  +    private void copy(File file, ServletOutputStream ostream,
  +                      Enumeration ranges, String contentType)
  +	throws IOException {
  +        
  +        IOException exception = null;
  +        
  +        while ( (exception == null) && (ranges.hasMoreElements()) ) {
  +            
  +            InputStream istream =	// FIXME: internationalization???????
  +                new BufferedInputStream(new FileInputStream(file), input);
  +        
  +            Range currentRange = (Range) ranges.nextElement();
  +            
  +            // Writing MIME header.
  +            ostream.println("--" + mimeSeparation);
  +            if (contentType != null)
  +                ostream.println("Content-Type: " + contentType);
  +            ostream.println("Content-Range: bytes " + currentRange.start
  +                           + "-" + currentRange.end + "/" 
  +                           + currentRange.length);
  +            ostream.println();
  +            
  +            // Printing content
  +            exception = copyRange(istream, ostream, currentRange.start,
  +                                  currentRange.end);
  +            
  +            ostream.println();
  +            ostream.println();
  +            
  +            try {
  +                istream.close();
  +            } catch (Throwable t) {
  +                ;
  +            }
  +            
  +        }
  +        
  +        ostream.print("--" + mimeSeparation + "--");
  +        
  +	// Clean up the output streams
  +	try {
  +	    ostream.flush();
  +	} catch (Throwable t) {
  +	    ;
  +	}
  +	try {
  +	    ostream.close();
  +	} catch (Throwable t) {
  +	    ;
  +	}
  +
  +	// Rethrow any exception that has occurred
  +	if (exception != null)
  +	    throw exception;
  +        
  +    }
  +
  +
  +    /**
  +     * Copy the contents of the specified input stream to the specified
  +     * output stream, and ensure that both streams are closed before returning
  +     * (even in the face of an exception).
  +     *
  +     * @param istream The input stream to read from
  +     * @param ostream The output stream to write to
  +     * @param start Start of the range which will be copied
  +     * @param end End of the range which will be copied
  +     * @return Exception which occured during processing
  +     */
  +    private IOException copyRange(InputStream istream, 
  +                                  ServletOutputStream ostream,
  +                                  long start, long end) {
  +        
  +        try {
  +            istream.skip(start);
  +        } catch (IOException e) {
  +            return e;
  +        }
  +        
  +	IOException exception = null;
  +        long bytesToRead = end - start + 1;
  +        
  +	byte buffer[] = new byte[input];
  +	int len = buffer.length;
  +	while ( (bytesToRead > 0) && (len >= buffer.length)) {
  +	    try {
  +                len = istream.read(buffer);
  +                if (bytesToRead >= len) {
  +                    ostream.write(buffer, 0, len);
  +                    bytesToRead -= len; 
  +                } else {
  +                    ostream.write(buffer, 0, (int) bytesToRead);
  +                    bytesToRead = 0;
  +                }
  +	    } catch (IOException e) {
  +		exception = e;
  +		len = -1;
  +	    }
  +	    if (len < buffer.length)
  +		break;
  +	}
  +        
  +        return exception;
  +        
       }
   
   
  @@ -313,10 +511,10 @@
        * @exception IOException if an input/output error occurs
        * @exception ServletException if a servlet-specified error occurs
        */
  -    public void serveDirectory(HttpServletRequest request,
  -			       HttpServletResponse response,
  -			       File directory,
  -			       boolean content)
  +    private void serveDirectory(HttpServletRequest request,
  +                                HttpServletResponse response,
  +                                File directory,
  +                                boolean content)
   	throws IOException, ServletException {
   
   	if (!content)
  @@ -396,10 +594,10 @@
        * @exception IOException if an input/output error occurs
        * @exception ServletException if a servlet-specified error occurs
        */
  -    public void serveFile(HttpServletRequest request,
  -			  HttpServletResponse response,
  -			  String pathname,
  -			  boolean content)
  +    private void serveFile(HttpServletRequest request,
  +                           HttpServletResponse response,
  +                           String pathname,
  +                           boolean content)
   	throws IOException, ServletException {
   
   	// Open the file (if it actually exists)
  @@ -415,49 +613,95 @@
   	if (debug > 0)
   	    log("DefaultServlet.serveFile: Serving '" + pathname + "'");
   
  -	// Handle conditional GET requests
  -	long lastModified = file.lastModified();
  -	long modifiedSince = request.getDateHeader("If-Modified-Since");
  -	if ("GET".equals(request.getMethod()) && (modifiedSince >= lastModified)) {
  -	    if (debug > 0)
  -	    	log("DefaultServlet.serveFile:  lastModified='" +
  -	    	    (new Timestamp(lastModified)).toString() +
  -	    	    "', modifiedSince='" +
  -	    	    (new Timestamp(modifiedSince)).toString() +
  -	    	    "', returning 304");
  -	    response.sendError(HttpServletResponse.SC_NOT_MODIFIED);
  -	    return;
  +        // Checking If headers
  +        if ( !checkIfHeaders(request, response, file) )
  +            return;
  +        
  +        // Parse range specifier
  +        Vector ranges = parseRange(request, response, file);
  +        
  +        // Last-Modified header
  +        if (debug > 0)
  +            log("DefaultServlet.serveFile:  lastModified='" +
  +                (new Timestamp(file.lastModified())).toString() + "'");
  +        response.setDateHeader("Last-Modified", file.lastModified());
  +        
  +        // ETag header
  +        response.setHeader("ETag", getETag(file, true));
  +        
  +        // Find content type.
  +        String contentType = getServletContext().getMimeType(pathname);
  +        
  +        if ( (ranges == null) && (request.getHeader("Range") == null) ) {
  +            
  +            // Set the appropriate output headers
  +            if (contentType != null) {
  +                if (debug > 0)
  +                    log("DefaultServlet.serveFile:  contentType='" +
  +                        contentType + "'");
  +                response.setContentType(contentType);
  +            }
  +            long contentLength = file.length();
  +            if (contentLength >= 0) {
  +                if (debug > 0)
  +                    log("DefaultServlet.serveFile:  contentLength=" +
  +                        contentLength);
  +                response.setContentLength((int) contentLength);
  +            }
  +            
  +            InputStream istream =	// FIXME: internationalization???????
  +                new BufferedInputStream(new FileInputStream(pathname), input);
  +            
  +            // Copy the input stream to our output stream (if requested)
  +            if (content) {
  +                response.setBufferSize(output);
  +                copy(file, response.getOutputStream());
  +            }
  +            
  +        } else {
  +            
  +            if (ranges == null)
  +                return;
  +            
  +            // Partial content response.
  +            
  +            response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
  +            
  +            if (ranges.size() == 1) {
  +                
  +                Range range = (Range) ranges.elementAt(0);
  +                response.addHeader("Content-Range", "bytes " 
  +                                   + range.start
  +                                   + "-" + range.end + "/" 
  +                                   + range.length);
  +                
  +                if (contentType != null) {
  +                    if (debug > 0)
  +                        log("DefaultServlet.serveFile:  contentType='" +
  +                            contentType + "'");
  +                    response.setContentType(contentType);
  +                }
  +                
  +                if (content) {
  +                    response.setBufferSize(output);
  +                    copy(file, response.getOutputStream(), range);
  +                }
  +                
  +            } else {
  +                
  +                response.setContentType("multipart/byteranges; boundary="
  +                                        + mimeSeparation);
  +                
  +                if (content) {
  +                    response.setBufferSize(output);
  +                    copy(file, response.getOutputStream(), 
  +                         ranges.elements(), contentType);
  +                }
  +                
  +            }
  +            
           }
  -
  -	InputStream istream =	// FIXME: internationalization???????
  -	    new BufferedInputStream(new FileInputStream(pathname), input);
  -
  -	// Set the appropriate output headers
  -	String contentType = getServletContext().getMimeType(pathname);
  -	if (contentType != null) {
  -	    if (debug > 0)
  -		log("DefaultServlet.serveFile:  contentType='" +
  -		    contentType + "'");
  -	    response.setContentType(contentType);
  -	}
  -	long contentLength = file.length();
  -	if (contentLength >= 0) {
  -	    if (debug > 0)
  -		log("DefaultServlet.serveFile:  contentLength=" +
  -		    contentLength);
  -	    response.setContentLength((int) contentLength);
  -	}
  -	if (debug > 0)
  -	    log("DefaultServlet.serveFile:  lastModified='" +
  -	        (new Timestamp(file.lastModified())).toString() + "'");
  -	response.setDateHeader("Last-Modified", file.lastModified());
  -
  -	// Copy the input stream to our output stream (if requested)
  -	if (content) {
  -	    response.setBufferSize(output);
  -	    copy(istream, response.getOutputStream());
  -	}
  -
  +        
       }
   
   
  @@ -471,9 +715,9 @@
        * @exception IOException if an input/output error occurs
        * @exception ServletException if a servlet-specified error occurs
        */
  -    public void serveResource(HttpServletRequest request,
  -			      HttpServletResponse response,
  -			      boolean content)
  +    private void serveResource(HttpServletRequest request,
  +                               HttpServletResponse response,
  +                               boolean content)
   	throws IOException, ServletException {
   
   	// Identify the requested resource path
  @@ -516,11 +760,346 @@
   
   	// Optimized processing for a "file:" URL
   	if ("file".equals(resourceURL.getProtocol())) {
  -	    serveFile(request, response, resourceURL.getFile(),content);
  +	    serveFile(request, response, resourceURL.getFile(), content);
   	    return;
   	}
   
  +    }
  +
   
  +    /**
  +     * Check if the conditions specified in the optional If headers are 
  +     * satisfied.
  +     * 
  +     * @param request The servlet request we are processing
  +     * @param response The servlet response we are creating
  +     * @param file File object
  +     * @return boolean true if the resource meets all the specified conditions,
  +     * and false if any of the conditions is not satisfied, in which case
  +     * request processing is stopped
  +     */
  +    private boolean checkIfHeaders(HttpServletRequest request,
  +                                   HttpServletResponse response, 
  +                                   File file)
  +        throws IOException {
  +        
  +        String eTag = getETag(file, true);
  +        long fileLength = file.length();
  +        long lastModified = file.lastModified();
  +        
  +        StringTokenizer commaTokenizer;
  +        
  +        String headerValue;
  +        
  +        // Checking If-Match
  +        headerValue = request.getHeader("If-Match");
  +        if (headerValue != null) {
  +            if (headerValue.indexOf("*") == -1) {
  +                
  +                commaTokenizer = new StringTokenizer(headerValue, ",");
  +                boolean conditionSatisfied = false;
  +                
  +                while (!conditionSatisfied && commaTokenizer.hasMoreTokens()) {
  +                    String currentToken = commaTokenizer.nextToken();
  +                    if (currentToken.trim().equals(eTag))
  +                        conditionSatisfied = true;
  +                }
  +                
  +                // If none of the given ETags match, 412 Precodition failed is
  +                // sent back
  +                if (!conditionSatisfied) {
  +                    response.sendError
  +                        (HttpServletResponse.SC_PRECONDITION_FAILED);
  +                    return false;
  +                }
  +                
  +            }
  +        }
  +        
  +        // Checking If-Modified-Since
  +        headerValue = request.getHeader("If-Modified-Since");
  +        if (headerValue != null) {
  +            
  +            // If an If-None-Match header has been specified, if modified since
  +            // is ignored.
  +            if (request.getHeader("If-None-Match") == null) {
  +                
  +                Date date = null;
  +                
  +                // Parsing the HTTP Date
  +                for (int i = 0; (date == null) && (i < formats.length); i++)
{
  +                    try {
  +                        date = formats[i].parse(headerValue);
  +                    } catch (ParseException e) {
  +                        ;
  +                    }
  +                }
  +                
  +                if ((date != null) 
  +                    && (lastModified <= (date.getTime() + 1000)) ) {
  +                    // The entity has not been modified since the date 
  +                    // specified by the client. This is not an error case.
  +                    response.sendError
  +                        (HttpServletResponse.SC_NOT_MODIFIED);
  +                    return false;
  +                }
  +                
  +            }
  +            
  +        }
  +        
  +        // Checking If-None-Match
  +        headerValue = request.getHeader("If-None-Match");
  +        if (headerValue != null) {
  +            if (headerValue.indexOf("*") == -1) {
  +                
  +                commaTokenizer = new StringTokenizer(headerValue, ",");
  +                boolean conditionSatisfied = false;
  +                
  +                while (!conditionSatisfied && commaTokenizer.hasMoreTokens()) {
  +                    String currentToken = commaTokenizer.nextToken();
  +                    if (currentToken.trim().equals(eTag))
  +                        conditionSatisfied = true;
  +                }
  +                
  +                if (conditionSatisfied) {
  +                    
  +                    // For GET and HEAD, we should respond with 
  +                    // 304 Not Modified.
  +                    // For every other method, 412 Precondition Failed is sent
  +                    // back.
  +                    if ( ("GET".equals(request.getMethod()))
  +                         || ("HEAD".equals(request.getMethod())) ) {
  +                        response.sendError
  +                            (HttpServletResponse.SC_NOT_MODIFIED);
  +                        return false;
  +                    } else {
  +                        response.sendError
  +                            (HttpServletResponse.SC_PRECONDITION_FAILED);
  +                        return false;
  +                    }
  +                }
  +                
  +            } else {
  +                if (file.exists()) {
  +                    
  +                }
  +            }
  +        }
  +        
  +        // Checking If-Unmodified-Since
  +        headerValue = request.getHeader("If-Unmodified-Since");
  +        if (headerValue != null) {
  +            
  +            Date date = null;
  +            
  +            // Parsing the HTTP Date
  +            for (int i = 0; (date == null) && (i < formats.length); i++) {
  +                try {
  +                    date = formats[i].parse(headerValue);
  +                } catch (ParseException e) {
  +                    ;
  +                }
  +            }
  +            
  +            if ( (date != null) && (lastModified > date.getTime()) ) {
  +                // The entity has not been modified since the date 
  +                // specified by the client. This is not an error case.
  +                response.sendError
  +                    (HttpServletResponse.SC_PRECONDITION_FAILED);
  +                return false;
  +            }
  +            
  +        }
  +        
  +        return true;
       }
  +
  +
  +    /**
  +     * Get the ETag value associated with a file.
  +     * 
  +     * @param file File object
  +     * @param strong True if we want a strong ETag, in which case a checksum
  +     * of the file has to be calculated
  +     */
  +    private String getETagValue(File file, boolean strong) {
  +        // FIXME : Compute a strong ETag if requested, using an MD5 digest
  +        // of the file contents
  +        return file.length() + "-" + file.lastModified();
  +    }
  +
  +
  +    /**
  +     * Get the ETag associated with a file.
  +     * 
  +     * @param file File object
  +     * @param strong True if we want a strong ETag, in which case a checksum
  +     * of the file has to be calculated
  +     */
  +    private String getETag(File file, boolean strong) {
  +        if (strong)
  +            return "\"" + getETagValue(file, strong) + "\"";
  +        else
  +            return "W/\"" + getETagValue(file, strong) + "\"";
  +    }
  +
  +
  +    /**
  +     * Parse the range header.
  +     * 
  +     * @param request The servlet request we are processing
  +     * @param response The servlet response we are creating
  +     * @return Vector of ranges
  +     */
  +    private Vector parseRange(HttpServletRequest request, 
  +                              HttpServletResponse response, File file) 
  +        throws IOException {
  +        
  +        // Checking If-Range
  +        String headerValue = request.getHeader("If-Range");
  +        if (headerValue != null) {
  +            
  +            String eTag = getETag(file, true);
  +            long lastModified = file.lastModified();
  +            
  +            Date date = null;
  +            
  +            // Parsing the HTTP Date
  +            for (int i = 0; (date == null) && (i < formats.length); i++) {
  +                try {
  +                    date = formats[i].parse(headerValue);
  +                } catch (ParseException e) {
  +                    ;
  +                }
  +            }
  +            
  +            if (date == null) {
  +                
  +                // If the ETag the client gave does not match the entity
  +                // etag, then the entire entity is returned.
  +                if (!eTag.equals(headerValue.trim()))
  +                    return null;
  +                
  +            } else {
  +                
  +                // If the timestamp of the entity the client got is older than
  +                // the last modification date of the entity, the entire entity
  +                // is returned.
  +                if (lastModified > (date.getTime() + 1000))
  +                    return null;
  +                
  +            }
  +            
  +        }
  +        
  +        long fileLength = file.length();
  +        
  +        if (fileLength == 0)
  +            return null;
  +        
  +        // Retrieving the range header (if any is specified
  +        String rangeHeader = request.getHeader("Range");
  +        
  +        if (rangeHeader == null)
  +            return null;
  +        // bytes is the only range unit supported (and I don't see the point
  +        // of adding new ones).
  +        if (!rangeHeader.startsWith("bytes")) {
  +            response.sendError
  +                (HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
  +            return null;
  +        }
  +        
  +        rangeHeader = rangeHeader.substring(6);
  +        
  +        // Vector which will contain all the ranges which are successfully
  +        // parsed.
  +        Vector result = new Vector();
  +        StringTokenizer commaTokenizer = new StringTokenizer(rangeHeader, ",");
  +        
  +        // Parsing the range list
  +        while (commaTokenizer.hasMoreTokens()) {
  +            String rangeDefinition = commaTokenizer.nextToken();
  +            
  +            Range currentRange = new Range();
  +            currentRange.length = fileLength;
  +            
  +            int dashPos = rangeDefinition.indexOf('-');
  +            
  +            if (dashPos == -1) {
  +                response.sendError
  +                    (HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
  +                return null;
  +            }
  +            
  +            if (dashPos == 0) {
  +                
  +                try {
  +                    long offset = Long.parseLong(rangeDefinition);
  +                    currentRange.start = fileLength + offset;
  +                    currentRange.end = fileLength - 1;
  +                } catch (NumberFormatException e) {
  +                    response.sendError
  +                        (HttpServletResponse
  +                         .SC_REQUESTED_RANGE_NOT_SATISFIABLE);
  +                    return null;
  +                }
  +                
  +            } else {
  +                
  +                try {
  +                    currentRange.start = Long.parseLong
  +                        (rangeDefinition.substring(0, dashPos));
  +                    if (dashPos < rangeDefinition.length() - 1)
  +                        currentRange.end = Long.parseLong
  +                            (rangeDefinition.substring
  +                             (dashPos + 1, rangeDefinition.length()));
  +                    else
  +                        currentRange.end = fileLength - 1;
  +                } catch (NumberFormatException e) {
  +                    response.sendError
  +                        (HttpServletResponse
  +                         .SC_REQUESTED_RANGE_NOT_SATISFIABLE);
  +                    return null;
  +                }
  +                
  +            }
  +            
  +            if (!currentRange.validate()) {
  +                response.sendError
  +                    (HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
  +                return null;
  +            }
  +            
  +            result.addElement(currentRange);
  +        }
  +        
  +        return result;
  +    }
  +
  +
  +    // ------------------------------------------------------ Range Inner Class
  +
  +
  +    private class Range {
  +        
  +        public long start;
  +        public long end;
  +        public long length;
  +        
  +        /**
  +         * Validate range.
  +         */
  +        public boolean validate() {
  +            if ( (start >= 0) && (end >= 0) && (length > 0)
  +                 && (start <= end) && (end < length) )
  +                return true;
  +            else
  +                return false;
  +        }
  +        
  +    }
  +
   
   }
  
  
  

Mime
View raw message