You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@turbine.apache.org by jv...@apache.org on 2001/06/25 06:56:39 UTC

cvs commit: jakarta-turbine/src/java/org/apache/turbine/services/upload DefaultFileItem.java FileItem.java MultipartStream.java

jvanzyl     01/06/24 21:56:39

  Added:       src/java/org/apache/turbine/services/upload
                        DefaultFileItem.java FileItem.java
                        MultipartStream.java
  Log:
  - files that have been moved into the service.
  
  Revision  Changes    Path
  1.1                  jakarta-turbine/src/java/org/apache/turbine/services/upload/DefaultFileItem.java
  
  Index: DefaultFileItem.java
  ===================================================================
  package org.apache.turbine.services.upload;
  
  /* ====================================================================
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 2001 The Apache Software Foundation.  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.
   *
   * 3. The end-user documentation included with the redistribution,
   *    if any, must include the following acknowledgment:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowledgment may appear in the software itself,
   *    if and wherever such third-party acknowledgments normally appear.
   *
   * 4. The names "Apache" and "Apache Software Foundation" and 
   *    "Apache Turbine" must not be used to endorse or promote products 
   *    derived from this software without prior written permission. For 
   *    written permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache",
   *    "Apache Turbine", nor may "Apache" appear in their name, without 
   *    prior written permission of the Apache Software Foundation.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 APACHE SOFTWARE FOUNDATION OR
   * ITS 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.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   */
  
  import java.io.ByteArrayInputStream;
  import java.io.ByteArrayOutputStream;
  import java.io.File;
  import java.io.FileInputStream;
  import java.io.FileOutputStream;
  import java.io.FileWriter;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.UnsupportedEncodingException;
  import java.io.OutputStream;
  
  import javax.activation.DataSource;
  
  import org.apache.turbine.services.uniqueid.TurbineUniqueId;
  import org.apache.turbine.services.upload.TurbineUpload;
  
  /**
   * <p> This class represents a file that was received by Turbine using
   * <code>multipart/form-data</code> POST request.
   *
   * <p> After retrieving an instance of this class from the {@link
   * org.apache.turbine.util.ParameterParser ParameterParser} (see
   * {@link org.apache.turbine.util.ParameterParser#getFileItem(String)
   * ParameterParser.getFileItem(String)} and {@link
   * org.apache.turbine.util.ParameterParser#getFileItems(String)
   * ParameterParser.getFileItems(String)}) you can use it to acces the
   * data that was sent by the browser.  You may either request all
   * contents of file at once using {@link #get()} or request an {@link
   * java.io.InputStream InputStream} with {@link #getStream()} and
   * process the file without attempting to load it into memory, which
   * may come handy with large files.
   *
   * Implements the javax.activation.DataSource interface (which allows
   * for example the adding of a FileItem as an attachment to a multipart
   * email).
   *
   * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
   * @author <a href="mailto:sean@informage.net">Sean Legassick</a>
   * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
   * @version $Id: DefaultFileItem.java,v 1.1 2001/06/25 04:56:38 jvanzyl Exp $
   */
  public class DefaultFileItem implements FileItem
  {
      /** The original filename in the user's filesystem. */
      protected String fileName;
  
      /**
       * The content type passed by the browser or <code>null</code> if
       * not defined.
       */
      protected String contentType;
  
      /** Cached contents of the file. */
      protected byte[] content;
  
      /** Temporary storage location. */
      protected File storeLocation;
  
      /** Temporary storage for in-memory files. */
      protected ByteArrayOutputStream byteStream;
  
      protected String fieldName;
  
      /**
       * Default constructor.
       */
      public DefaultFileItem()
      {
      }
  
      public void setFieldName(String fieldName)
      {
          this.fieldName = fieldName;
      }
      
      public String getFieldName()
      {
          return fieldName;
      }        
  
      /**
       * Constructs a new <code>DefaultFileItem</code>.
       *
       * <p>Use {@link #newInstance(String,String,String,int)} to
       * instantiate <code>DefaultFileItems</code>.
       *
       * @param fileName The original filename in the user's filesystem.
       * @param contentType The content type passed by the browser or
       * <code>null</code> if not defined.
       */
      protected DefaultFileItem( String fileName,
                          String contentType )
      {
          this.fileName = fileName;
          this.contentType = contentType;
      }
  
      /**
       * Returns the original filename in the user's filesystem.
       * (implements DataSource method)
       *
       * @return The original filename in the user's filesystem.
       */
      public String getName()
      {
          return getFileName();
      }
  
      /**
       * Returns the original filename in the user's filesystem.
       *
       * @return The original filename in the user's filesystem.
       */
      public String getFileName()
      {
          return fileName;
      }
  
      /**
      * Returns the content type passed by the browser or
      * <code>null</code> if not defined. (implements
      * DataSource method).
      *
      * @return The content type passed by the browser or
      * <code>null</code> if not defined.
      */
      public String getContentType()
      {
          return contentType;
      }
  
      /**
       * Provides a hint if the file contents will be read from memory.
       *
       * @return <code>True</code> if the file contents will be read
       * from memory.
       */
      public boolean inMemory()
      {
          return (content != null || byteStream != null);
      }
  
      /**
       * Returns the size of the file.
       *
       * @return The size of the file.
       */
      public long getSize()
      {
          if(storeLocation != null)
          {
              return storeLocation.length();
          }
          else if(byteStream != null)
          {
              return byteStream.size();
          }
          else
          {
              return content.length;
          }
      }
  
      /**
       * Returns the contents of the file as an array of bytes.  If the
       * contents of the file were not yet cached int the memory, they
       * will be loaded from the disk storage and chached.
       *
       * @return The contents of the file as an array of bytes.
       */
      public byte[] get()
      {
          if(content == null)
          {
              if(storeLocation != null)
              {
                  content = new byte[(int)getSize()];
                  try
                  {
                      FileInputStream fis = new FileInputStream(storeLocation);
                      fis.read(content);
                  }
                  catch (Exception e)
                  {
                      content = null;
                  }
              }
              else
              {
                  content = byteStream.toByteArray();
                  byteStream = null;
              }
          }
          return content;
      }
  
      /**
       * Returns the contents of the file as a String, using specified
       * encoding.  This method uses {@link #get()} to retireve the
       * contents of the file.<br>
       *
       * @param encoding The encoding to use.
       * @return The contents of the file.
       * @exception UnsupportedEncodingException.
       */
      public String getString( String encoding )
          throws UnsupportedEncodingException
      {
          return new String(get(), encoding);
      }
  
      public String getString()
      {
          return new String(get());
      }        
  
      /**
       * Returns an {@link java.io.InputStream InputStream} that can be
       * used to retrieve the contents of the file. (implements DataSource
       * method)
       *
       * @return An {@link java.io.InputStream InputStream} that can be
       * used to retrieve the contents of the file.
       * @exception Exception, a generic exception.
       */
      public InputStream getInputStream()
          throws IOException
      {
          return getStream();
      }
      
      /**
       * Returns an {@link java.io.InputStream InputStream} that can be
       * used to retrieve the contents of the file.
       *
       * @return An {@link java.io.InputStream InputStream} that can be
       * used to retrieve the contents of the file.
       * @exception Exception, a generic exception.
       */
      public InputStream getStream()
          throws IOException
      {
          if(content == null)
          {
              if(storeLocation != null)
              {
                  return new FileInputStream(storeLocation);
              }
              else
              {
                  content = byteStream.toByteArray();
                  byteStream = null;
              }
          }
          return new ByteArrayInputStream(content);
      }
  
      /**
       * Returns the {@link java.io.File} objects for the DefaultFileItems's
       * data temporary location on the disk.  Note that for
       * <code>DefaultFileItems</code> that have their data stored in memory
       * this method will return <code>null</code>.  When handling large
       * files, you can use {@link java.io.File#renameTo(File)} to
       * move the file to new location without copying the data, if the
       * source and destination locations reside within the same logical
       * volume.
       *
       * @return A File.
       */
      public File getStoreLocation()
      {
          return storeLocation;
      }
  
      /**
       * Removes the file contents from the temporary storage.
       */
      protected void finalize()
      {
          if(storeLocation != null && storeLocation.exists())
          {
              storeLocation.delete();
          }
      }
  
      /**
       * Returns an {@link java.io.OutputStream OutputStream} that can
       * be used for storing the contents of the file.
       * (implements DataSource method)
       *
       * @return an {@link java.io.OutputStream OutputStream} that can be
       * used for storing the contensts of the file.
       * @exception IOException.
       */
      public OutputStream getOutputStream()
          throws IOException
      {
          if(storeLocation == null)
          {
              return byteStream;
          }
          else
          {
              return new FileOutputStream(storeLocation);
          }
      }
  
      /**
       * Instantiates a DefaultFileItem.  It uses <code>requestSize</code> to
       * decide what temporary storage approach the new item should
       * take.  The largest request that will have its items cached in
       * memory can be configured in
       * <code>TurbineResources.properties</code> in the entry named
       * <code>file.upload.size.threshold</code>
       *
       * @param path A String.
       * @param name The original filename in the user's filesystem.
       * @param contentType The content type passed by the browser or
       * <code>null</code> if not defined.
       * @param requestSize The total size of the POST request this item
       * belongs to.
       * @return A DefaultFileItem.
       */
      public static FileItem newInstance(String path,
                                         String name,
                                         String contentType,
                                         int requestSize)
      {
          DefaultFileItem item = new DefaultFileItem(name, contentType);
          if(requestSize > TurbineUpload.getSizeThreshold())
          {
              String instanceName = TurbineUniqueId.getInstanceId();
              String fileName = TurbineUniqueId.getUniqueId();
              fileName = instanceName + "_upload_" + fileName + ".tmp";
              fileName = path + "/" + fileName;
              item.storeLocation = new File(fileName);
          }
          else
          {
              item.byteStream = new ByteArrayOutputStream();
          }
          return item;
      }
  
      /**
       * A convenience method to write an uploaded
       * file to disk. The client code is not concerned
       * whether or not the file is stored in memory,
       * or on disk in a temporary location. They just
       * want to write the uploaded file to disk.
       *
       * @param String full path to location where uploaded
       *               should be stored.
       */
      public void write(String file) throws Exception
      {
          if (inMemory())
          {
              FileWriter writer = new FileWriter(file);
              writer.write(getString());
          }
          else if (storeLocation != null)
          {
              /*
               * The uploaded file is being stored on disk
               * in a temporary location so move it to the
               * desired file.
               */
              if (storeLocation.renameTo(new File(file)) == false)
              {
                  throw new Exception(
                      "Cannot write uploaded file to disk!");
              }                
          }
          else
          {
              /*
               * For whatever reason we cannot write the
               * file to disk.
               */
              throw new Exception("Cannot write uploaded file to disk!");
          }
      }
  }
  
  
  
  1.1                  jakarta-turbine/src/java/org/apache/turbine/services/upload/FileItem.java
  
  Index: FileItem.java
  ===================================================================
  package org.apache.turbine.services.upload;
  
  /* ====================================================================
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 2001 The Apache Software Foundation.  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.
   *
   * 3. The end-user documentation included with the redistribution,
   *    if any, must include the following acknowledgment:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowledgment may appear in the software itself,
   *    if and wherever such third-party acknowledgments normally appear.
   *
   * 4. The names "Apache" and "Apache Software Foundation" and 
   *    "Apache Turbine" must not be used to endorse or promote products 
   *    derived from this software without prior written permission. For 
   *    written permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache",
   *    "Apache Turbine", nor may "Apache" appear in their name, without 
   *    prior written permission of the Apache Software Foundation.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 APACHE SOFTWARE FOUNDATION OR
   * ITS 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.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   */
  
  import java.io.File;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.UnsupportedEncodingException;
  import java.io.OutputStream;
  
  import javax.activation.DataSource;
  
  /**
   * <p> This class represents a file that was received by Turbine using
   * <code>multipart/form-data</code> POST request.
   *
   * <p> After retrieving an instance of this class from the {@link
   * org.apache.turbine.util.ParameterParser ParameterParser} (see
   * {@link org.apache.turbine.util.ParameterParser#getFileItem(String)
   * ParameterParser.getFileItem(String)} and {@link
   * org.apache.turbine.util.ParameterParser#getFileItems(String)
   * ParameterParser.getFileItems(String)}) you can use it to acces the
   * data that was sent by the browser.  You may either request all
   * contents of file at once using {@link #get()} or request an {@link
   * java.io.InputStream InputStream} with {@link #getStream()} and
   * process the file without attempting to load it into memory, which
   * may come handy with large files.
   *
   * Implements the javax.activation.DataSource interface (which allows
   * for example the adding of a FileItem as an attachment to a multipart
   * email).
   *
   * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
   * @author <a href="mailto:sean@informage.net">Sean Legassick</a>
   * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
   * @version $Id: FileItem.java,v 1.1 2001/06/25 04:56:38 jvanzyl Exp $
   */
  public interface FileItem 
      extends DataSource
  {
      /**
       * The maximal size of request that will have it's elements stored
       * in memory.
       */
      public static final int DEFAULT_UPLOAD_SIZE_THRESHOLD = 10240;
  
      /**
       * Returns the original filename in the user's filesystem.
       * (implements DataSource method)
       *
       * @return The original filename in the user's filesystem.
       */
      public String getName();
  
      /**
       * Returns the original filename in the user's filesystem.
       *
       * @return The original filename in the user's filesystem.
       */
      public String getFileName();
  
      /**
      * Returns the content type passed by the browser or
      * <code>null</code> if not defined. (implements
      * DataSource method).
      *
      * @return The content type passed by the browser or
      * <code>null</code> if not defined.
      */
      public String getContentType();
  
      /**
       * Provides a hint if the file contents will be read from memory.
       *
       * @return <code>True</code> if the file contents will be read
       * from memory.
       */
      public boolean inMemory();
  
      /**
       * Returns the size of the file.
       *
       * @return The size of the file.
       */
      public long getSize();
  
      /**
       * Returns the contents of the file as an array of bytes.  If the
       * contents of the file were not yet cached int the memory, they
       * will be loaded from the disk storage and chached.
       *
       * @return The contents of the file as an array of bytes.
       */
      public byte[] get();
  
      /**
       * Returns the contents of the file as a String, using specified
       * encoding.  This method uses {@link #get()} to retireve the
       * contents of the file.<br>
       *
       * @param encoding The encoding to use.
       * @return The contents of the file.
       * @exception UnsupportedEncodingException.
       */
      public String getString( String encoding )
          throws UnsupportedEncodingException;
  
      public String getString();
  
      /**
       * Returns an {@link java.io.InputStream InputStream} that can be
       * used to retrieve the contents of the file. (implements DataSource
       * method)
       *
       * @return An {@link java.io.InputStream InputStream} that can be
       * used to retrieve the contents of the file.
       * @exception Exception, a generic exception.
       */
      public InputStream getInputStream()
          throws IOException;
      
      /**
       * Returns an {@link java.io.InputStream InputStream} that can be
       * used to retrieve the contents of the file.
       *
       * @return An {@link java.io.InputStream InputStream} that can be
       * used to retrieve the contents of the file.
       * @exception Exception, a generic exception.
       */
      public InputStream getStream()
          throws IOException;
  
      /**
       * Returns the {@link java.io.File} objects for the DefaultFileItems's
       * data temporary location on the disk.  Note that for
       * <code>DefaultFileItems</code> that have their data stored in memory
       * this method will return <code>null</code>.  When handling large
       * files, you can use {@link java.io.File#renameTo(File)} to
       * move the file to new location without copying the data, if the
       * source and destination locations reside within the same logical
       * volume.
       *
       * @return A File.
       */
      public File getStoreLocation();
  
      /**
       * Returns an {@link java.io.OutputStream OutputStream} that can
       * be used for storing the contents of the file.
       * (implements DataSource method)
       *
       * @return an {@link java.io.OutputStream OutputStream} that can be
       * used for storing the contensts of the file.
       * @exception IOException.
       */
      public OutputStream getOutputStream() throws IOException;
  
      /**
       * A convenience method to write an uploaded
       * file to disk. The client code is not concerned
       * whether or not the file is stored in memory,
       * or on disk in a temporary location. They just
       * want to write the uploaded file to disk.
       *
       * @param String full path to location where uploaded
       *               should be stored.
       */
      public void write(String file) throws Exception;
  
      public String getFieldName();
      
      public void setFieldName(String name);
  }
  
  
  
  1.1                  jakarta-turbine/src/java/org/apache/turbine/services/upload/MultipartStream.java
  
  Index: MultipartStream.java
  ===================================================================
  package org.apache.turbine.services.upload;
  
  /* ====================================================================
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 2001 The Apache Software Foundation.  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.
   *
   * 3. The end-user documentation included with the redistribution,
   *    if any, must include the following acknowledgment:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowledgment may appear in the software itself,
   *    if and wherever such third-party acknowledgments normally appear.
   *
   * 4. The names "Apache" and "Apache Software Foundation" and 
   *    "Apache Turbine" must not be used to endorse or promote products 
   *    derived from this software without prior written permission. For 
   *    written permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache",
   *    "Apache Turbine", nor may "Apache" appear in their name, without 
   *    prior written permission of the Apache Software Foundation.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 APACHE SOFTWARE FOUNDATION OR
   * ITS 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.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   */
  
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.OutputStream;
  
  /**
   * This class can be used to process data streams conforming to MIME
   * 'multipart' format as defined in <a
   * href="http://rf.cs/rfc1521.html">RFC&nbsp;1251</a>.  Arbitrary
   * large amouns of data in the stream can be processed under constant
   * memory usage.
   *
   * <p>The format of the stream is defined in the following way:<br>
   *
   * <code>
   *   multipart-body := preamble 1*encapsulation close-delimiter epilogue<br>
   *   encapsulation := delimiter body CRLF<br>
   *   delimiter := "--" boundary CRLF<br>
   *   close-delimiter := "--" boudary "--"<br>
   *   preamble := &lt;ignore&gt;<br>
   *   epilogue := &lt;ignore&gt;<br>
   *   body := header-part CRLF body-part<br>
   *   header-part := 1*header CRLF<br>
   *   header := header-name ":" header-value<br>
   *   header-name := &lt;printable ascii characters except ":"&gt;<br>
   *   header-value := &lt;any ascii characters except CR & LF&gt;<br>
   *   body-data := &lt;arbitrary data&gt;<br>
   * </code>
   *
   * <p>Note that body-data can contain another mulipart entity.  There
   * is limited support for single pass processing of such nested
   * streams.  The nested stream is <strong>required</strong> to have a
   * boundary token of the same length as the parent stream (see {@link
   * #setBoundary(byte[])}).
   *
   * <p>Here is an exaple of usage of this class.<br>
   *
   * <pre>
   *    try {
   *        MultipartStream multipartStream = new MultipartStream(input,
   *                                                              boundary);
   *        boolean nextPart = malitPartStream.skipPreamble();
   *        OutputStream output;
   *        while(nextPart) {
   *            header = chunks.readHeader();
   *            // process headers
   *            // create some output stream
   *            multipartStream.readBodyPart(output);
   *            nextPart = multipartStream.readBoundary();
   *        }
   *    } catch(MultipartStream.MalformedStreamException e) {
   *          // the stream failed to follow required syntax
   *    } catch(IOException) {
   *          // a read or write error occurred
   *    }
   *
   * </pre>
   *
   * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
   * @version $Id: MultipartStream.java,v 1.1 2001/06/25 04:56:39 jvanzyl Exp $
   */
  public class MultipartStream
  {
      /**
       * The maximum lenght of <code>header-part</code> that will be
       * processed (10 kilobytes = 10240 bytes.
       )*/
      public static final int HEADER_PART_SIZE_MAX = 10240;
  
      /** The stream were data is read from. */
      protected InputStream input;
  
      /**
       * The lenght of boundary token plus leading <code>CRLF--</code>.
       */
      protected int boundaryLength;
  
      /**
       * The amount of data that must be kept in the buffer in order to
       * detect delimiters reliably.
       */
      protected int keepRegion;
  
      /** A byte sequence that partitions the stream. */
      protected byte[] boundary;
  
      /** The lenght of the buffer used for processing. */
      protected int bufSize;
  
      /** The default lenght of the buffer used for processing. */
      protected static final int DEFAULT_BUFSIZE = 4096;
  
      /** The buffer used for processing. */
      protected byte[] buffer;
  
      /**
       * The index of first valid character in the buffer.
       *
       * 0 <= head < bufSize
       */
      protected int head;
  
      /**
       * The index of last valid characer in the buffer + 1.
       *
       * 0 <= tail <= bufSize
       */
      protected int tail;
  
      /**
       * A byte sequence that marks the end of <code>header-part</code>
       * (<code>CRLFCRLF</code>).
       */
      protected static final byte[] HEADER_SEPARATOR = {0x0D, 0x0A, 0x0D, 0x0A};
  
      /**
       * A byte sequence that that follows a delimiter that will be
       * followed by an encapsulation (<code>CRLF</code>).
       */
      protected static final byte[] FIELD_SEPARATOR = { 0x0D, 0x0A };
  
      /**
       * A byte sequence that that follows a delimiter of the last
       * encapsulation in the stream (<code>--</code>).
       */
      protected static final byte[] STREAM_TERMINATOR = { 0x2D, 0x2D };
      
      /**
       * Required by the proxy.
       */
      public MultipartStream()
      {
      }
  
      /**
       * Constructs a MultipartStream with a custom size buffer.
       *
       * <p>Note that the buffer must be at least big enough to contain
       * the boundary string, plus 4 characters for CR/LF and double
       * dash, plus at least one byte of data.  Too small buffer size
       * setting will degrade performance.
       *
       * @param input The <code>InputStream</code> to serve as a data
       * source.
       * @param boundary The token used for dividing the stream into
       * <code>encapsulations</code>.
       * @param bufSize The size of the buffer to be used in bytes.
       * @exception MalformedStreamException.
       * @exception IOException.
       */
      public MultipartStream( InputStream input,
                              byte[] boundary,
                              int bufSize )
          throws MalformedStreamException,
                 IOException
      {
          this.input = input;
          this.bufSize = bufSize;
          this.buffer = new byte[bufSize];
  
          // We prepend CR/LF to the boundary to chop trailng CR/LF from
          // body-data tokens.
          this.boundary = new byte[boundary.length+4];
          this.boundaryLength = boundary.length+4;
          this.keepRegion = boundary.length+3;
          this.boundary[0] = 0x0D;
          this.boundary[1] = 0x0A;
          this.boundary[2] = 0x2D;
          this.boundary[3] = 0x2D;
          System.arraycopy(boundary, 0, this.boundary, 4, boundary.length);
  
          head = 0;
          tail = 0;
      }
  
      /**
       * Constructs a MultipartStream with a defalut size buffer.
       *
       * @param input The <code>InputStream</code> to serve as a data
       * source.
       * @param boundary The token used for dividing the stream into
       * <code>encapsulations</code>.
       * @exception IOException.
       */
      public MultipartStream( InputStream input,
                              byte[] boundary )
          throws IOException
      {
          this(input, boundary, DEFAULT_BUFSIZE);
      }
  
      /**
       * Reads a byte from the <code>buffer</code>, and refills it as
       * neccessary.
       *
       * @return Next byte from the input stream.
       * @exception IOException, if there isn't any more data available.
       */
      public byte readByte()
          throws IOException
      {
          // Buffer depleted ?
          if(head == tail)
          {
              head = 0;
              // Refill.
              tail = input.read(buffer, head, bufSize);
              if(tail == -1)
              {
                  // No more data available.
                  throw new IOException("No more data is available");
              }
          }
          return buffer[head++];
      }
  
      /**
       * Skips a <code>boundary</code> token, and checks wether more
       * <code>encapsulations</code> are contained in the stream.
       *
       * @return <code>True</code> if there are more encapsulations in
       * this stream.
       * @exception MalformedStreamException if the stream ends
       * unexpecetedly or fails to follow required syntax.
       */
      public boolean readBoundary()
          throws MalformedStreamException
      {
          byte[] marker = new byte[2];
          boolean nextChunk = false;
  
          head += boundaryLength;
          try
          {
              marker[0] = readByte();
              marker[1] = readByte();
              if (arrayequals(marker, STREAM_TERMINATOR, 2))
              {
                  nextChunk = false;
              }
              else if(arrayequals(marker, FIELD_SEPARATOR, 2))
              {
                  nextChunk = true;
              }
              else
              {
                  throw new MalformedStreamException("Unexpected characters follow a boundary");
              }
          }
          catch(IOException e)
          {
              throw new MalformedStreamException("Stream ended unexpectedly");
          }
          return nextChunk;
      }
  
      /**
       * Changes the boundary token used for partitioning the stream.
       *
       * <p>This method allows single pass processing of nested
       * multipart streams.
       *
       * <p>The boundary token of the nested stream is
       * <code>required</code> to be of the same length as the boundary
       * token in parent stream.
       *
       * <p>Restoring parent stream boundary token after processing of a
       * nested stream is left ot the application. <br>
       *
       * @param boundary A boundary to be used for parsing of the nested
       * stream.
       * @exception IllegalBoundaryException, if <code>boundary</code>
       * has diffrent lenght than the one being currently in use.
       */
      public void setBoundary( byte[] boundary )
          throws IllegalBoundaryException
      {
          if (boundary.length != boundaryLength-4)
          {
              throw new IllegalBoundaryException("The length of a boundary token can not be changed");
          }
          System.arraycopy(boundary, 0, this.boundary, 4, boundary.length);
      }
  
      /**
       * <p>Reads <code>header-part</code> of the current
       * <code>encapsulation</code>
       *
       * <p>Headers are returned verbatim to the input stream, including
       * traling <code>CRLF</code> marker. Parsing is left to the
       * application.
       *
       * <p><strong>TODO</strong> allow limiting maximum header size to
       * protect against abuse.<br>
       *
       * @return <code>header-part</code> of the current encapsulation.
       * @exception MalformedStreamException, if the stream ends
       * unexpecetedly.
       */
      public String readHeaders()
          throws MalformedStreamException
      {
          int i = 0;
          byte b[] = new byte[1];
          StringBuffer buf = new StringBuffer();
          int sizeMax = HEADER_PART_SIZE_MAX;
          int size = 0;
          while(i<4)
          {
              try
              {
                  b[0] = readByte();
              }
              catch(IOException e)
              {
                  throw new MalformedStreamException("Stream ended unexpectedly");
              }
              size++;
              if(b[0] == HEADER_SEPARATOR[i])
              {
                  i++;
              }
              else
              {
                  i = 0;
              }
              if(size <= sizeMax)
              {
                  buf.append(new String(b));
              }
          }
          return buf.toString();
      }
  
      /**
       * Reads <code>body-data</code> from the current
       * <code>encapsulation</code> and writes its contents into the
       * output <code>Stream</code>.
       *
       * <p>Arbitrary large amouts of data can be processed by this
       * method using a constant size buffer. (see {@link
       * #MultipartStream(InputStream,byte[],int) constructor}).
       *
       * @param output The <code>Stream</code> to write data into.
       * @return the amount of data written.
       * @exception MalformedStreamException
       * @exception IOException
       */
      public int readBodyData( OutputStream output )
          throws MalformedStreamException,
                 IOException
      {
          boolean done = false;
          int pad;
          int pos;
          int bytesRead;
          int total = 0;
          while(!done)
          {
              // Is boundary token present somewere in the buffer?
              pos = findSeparator();
              if(pos != -1)
              {
                  // Write the rest of the data before the boundary.
                  output.write(buffer, head, pos-head);
                  total += pos-head;
                  head = pos;
                  done = true;
              }
              else
              {
                  // Determine how much data should be kept in the
                  // buffer.
                  if(tail-head>keepRegion)
                  {
                      pad = keepRegion;
                  }
                  else
                  {
                      pad = tail-head;
                  }
                  // Write out the data belonging to the body-data.
                  output.write(buffer, head, tail-head-pad);
  
                  // Move the data to the beging of the buffer.
                  total += tail-head-pad;
                  System.arraycopy(buffer, tail-pad, buffer, 0, pad);
  
                  // Refill buffer with new data.
                  head = 0;
                  bytesRead = input.read(buffer, pad, bufSize-pad);
  
                  // [pprrrrrrr]
                  if(bytesRead != -1)
                  {
                      tail = pad+bytesRead;
                  }
                  else
                  {
                      // The last pad amount is left in the buffer.
                      // Boundary can't be in there so write out the
                      // data you have and signal an error condition.
                      output.write(buffer,0,pad);
                      output.flush();
                      total += pad;
                      throw new MalformedStreamException("Stream ended unexpectedly");
                  }
              }
          }
          output.flush();
          return total;
      }
  
      /**
       * Reads <code>body-data</code> from the current
       * <code>encapsulation</code> and discards it.
       *
       * <p>Use this method to skip encapsulations you don't need or
       * don't understand.
       *
       * @return The amount of data discarded.
       * @exception MalformedStreamException
       * @exception IOException
       */
      public int discardBodyData()
          throws MalformedStreamException,
                 IOException
      {
          boolean done = false;
          int pad;
          int pos;
          int bytesRead;
          int total = 0;
          while(!done)
          {
              // Is boundary token present somewere in the buffer?
              pos = findSeparator();
              if(pos != -1)
              {
                  // Write the rest of the data before the boundary.
                  total += pos-head;
                  head = pos;
                  done = true;
              }
              else
              {
                  // Determine how much data should be kept in the
                  // buffer.
                  if(tail-head>keepRegion)
                  {
                      pad = keepRegion;
                  }
                  else
                  {
                      pad = tail-head;
                  }
                  total += tail-head-pad;
  
                  // Move the data to the beging of the buffer.
                  System.arraycopy(buffer, tail-pad, buffer, 0, pad);
  
                  // Refill buffer with new data.
                  head = 0;
                  bytesRead = input.read(buffer, pad, bufSize-pad);
  
                  // [pprrrrrrr]
                  if(bytesRead != -1)
                  {
                      tail = pad+bytesRead;
                  }
                  else
                  {
                      // The last pad amount is left in the buffer.
                      // Boundary can't be in there so signal an error
                      // condition.
                      total += pad;
                      throw new MalformedStreamException("Stream ended unexpectedly");
                  }
              }
          }
          return total;
      }
  
      /**
       * Finds the beginning of the first <code>encapsulation</code>.
       *
       * @return <code>True</code> if an <code>encapsulation</code> was
       * found in the stream.
       * @exception IOException
       */
      public boolean skipPreamble()
          throws IOException
      {
          // First delimiter may be not preceeded with a CRLF.
          System.arraycopy(boundary, 2, boundary, 0, boundary.length-2);
          boundaryLength = boundary.length-2;
          try
          {
              // Discard all data up to the delimiter.
              discardBodyData();
  
              // Read boundary - if succeded, the stream contains an
              // encapsulation.
              return readBoundary();
          }
          catch(MalformedStreamException e)
          {
              return false;
          }
          finally
          {
              // Restore delimiter.
              System.arraycopy(boundary,0, boundary, 2, boundary.length-2);
              boundaryLength = boundary.length;
              boundary[0] = 0x0D;
              boundary[1] = 0x0A;
          }
      }
  
      /**
       * Compares <code>count</code> first bytes in the arrays
       * <code>a</code> and <code>b</code>.
       *
       * @param a The first array to compare.
       * @param b The second array to compare.
       * @param count How many bytes should be compared.
       * @return <code>true</code> if <code>count</code> first bytes in
       * arrays <code>a</code> and <code>b</code> are equal.
       */
      public static boolean arrayequals( byte[] a,
                                         byte[] b,
                                         int count )
      {
          for(int i=0; i<count; i++)
          {
              if(a[i] != b[i])
              {
                  return false;
              }
          }
          return true;
      }
  
      /**
       * Searches a byte of specified value in the <code>buffer</code>
       * starting at specified <code>position</code>.
       *
       * @param value the value to find.
       * @param pos The starting position for searching.
       * @return The position of byte found, counting from beginning of
       * the <code>buffer</code>, or <code>-1</code> if not found.
       */
      protected int findByte(byte value,
                             int pos)
      {
          for (int i = pos; i < tail; i++)
              if(buffer[i] == value)
                  return i;
  
              return -1;
      }
  
      /**
       * Searches the <code>boundary</code> in <code>buffer</code>
       * region delimited by <code>head</code> and <code>tail</code>.
       *
       * @return The position of the boundary found, counting from
       * beginning of the <code>buffer</code>, or <code>-1</code> if not
       * found.
       */
      protected int findSeparator()
      {
          int first;
          int match = 0;
          int maxpos = tail - boundaryLength;
          for(first = head;
              (first <= maxpos) && (match != boundaryLength);
              first++)
          {
              first = findByte(boundary[0], first);
              if(first == -1 || (first > maxpos))
                  return -1;
              for(match = 1; match<boundaryLength; match++)
              {
                  if(buffer[first+match] != boundary[match])
                      break;
              }
          }
          if(match == boundaryLength)
          {
              return first-1;
          }
          return -1;
      }
  
  
      /**
       * Thrown to indicate that the input stream fails to follow the
       * required syntax.
       */
      public class MalformedStreamException
          extends IOException
      {
          /**
           * Constructs a <code>MalformedStreamException</code> with no
           * detail message.
           */
          public MalformedStreamException()
          {
              super();
          }
  
          /**
           * Constructs an <code>MalformedStreamException</code> with
           * the specified detail message.
           *
           * @param message The detail message.
           */
          public MalformedStreamException(String message)
          {
              super(message);
          }
      }
  
      /**
       * Thrown upon attempt of setting an invalid boundary token.
       */
      public class IllegalBoundaryException
          extends IOException
      {
          /**
           * Constructs an <code>IllegalBoundaryException</code> with no
           * detail message.
           */
          public IllegalBoundaryException()
          {
              super();
          }
  
          /**
           * Constructs an <code>IllegalBoundaryException</code> with
           * the specified detail message.
           *
           * @param message The detail message.
           */
          public IllegalBoundaryException(String message)
          {
              super(message);
          }
      }
  
      /*-------------------------------------------------------------
  
      // These are the methods that were used to debug this stuff.
  
      // Dump data.
      protected void dump()
      {
          System.out.println("01234567890");
          byte[] temp = new byte[buffer.length];
          for(int i=0; i<buffer.length; i++)
          {
              if(buffer[i] == 0x0D || buffer[i] == 0x0A)
              {
                  temp[i] = 0x21;
              }
              else
              {
                  temp[i] = buffer[i];
              }
          }
          System.out.println(new String(temp));
          int i;
          for(i=0; i<head; i++)
              System.out.print(" ");
          System.out.println("h");
          for(i=0; i<tail; i++)
              System.out.print(" ");
          System.out.println("t");
          System.out.flush();
      }
  
      // Main routine, for testing purposes only.
      //
      // @param args A String[] with the command line arguments.
      // @exception Exception, a generic exception.
      public static void main( String[] args )
          throws Exception
      {
          File boundaryFile = new File("boundary.dat");
          int boundarySize = (int)boundaryFile.length();
          byte[] boundary = new byte[boundarySize];
          FileInputStream input = new FileInputStream(boundaryFile);
          input.read(boundary,0,boundarySize);
  
          input = new FileInputStream("multipart.dat");
          MultipartStream chunks = new MultipartStream(input, boundary);
  
          int i = 0;
          String header;
          OutputStream output;
          boolean nextChunk = chunks.skipPreamble();
          while(nextChunk)
          {
              header = chunks.readHeaders();
              System.out.println("!"+header+"!");
              System.out.println("wrote part"+i+".dat");
              output = new FileOutputStream("part"+(i++)+".dat");
              chunks.readBodyData(output);
              nextChunk = chunks.readBoundary();
          }
      }
  
      */
  }
  
  
  

---------------------------------------------------------------------
To unsubscribe, e-mail: turbine-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: turbine-dev-help@jakarta.apache.org