MultipartStream.java

00001 /*
00002  * $Header: /home/cvs/jakarta-commons/fileupload/src/java/org/apache/commons/fileupload/MultipartStream.java,v 1.12 2003/06/01 00:18:13 martinc Exp $
00003  * $Revision: 1.12 $
00004  * $Date: 2003/06/01 00:18:13 $
00005  *
00006  * ====================================================================
00007  *
00008  * The Apache Software License, Version 1.1
00009  *
00010  * Copyright (c) 2001-2003 The Apache Software Foundation.  All rights
00011  * reserved.
00012  *
00013  * Redistribution and use in source and binary forms, with or without
00014  * modification, are permitted provided that the following conditions
00015  * are met:
00016  *
00017  * 1. Redistributions of source code must retain the above copyright
00018  *    notice, this list of conditions and the following disclaimer.
00019  *
00020  * 2. Redistributions in binary form must reproduce the above copyright
00021  *    notice, this list of conditions and the following disclaimer in
00022  *    the documentation and/or other materials provided with the
00023  *    distribution.
00024  *
00025  * 3. The end-user documentation included with the redistribution, if
00026  *    any, must include the following acknowlegement:
00027  *       "This product includes software developed by the
00028  *        Apache Software Foundation (http://www.apache.org/)."
00029  *    Alternately, this acknowlegement may appear in the software itself,
00030  *    if and wherever such third-party acknowlegements normally appear.
00031  *
00032  * 4. The names "The Jakarta Project", "Commons", and "Apache Software
00033  *    Foundation" must not be used to endorse or promote products derived
00034  *    from this software without prior written permission. For written
00035  *    permission, please contact apache@apache.org.
00036  *
00037  * 5. Products derived from this software may not be called "Apache"
00038  *    nor may "Apache" appear in their names without prior written
00039  *    permission of the Apache Group.
00040  *
00041  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
00042  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
00043  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
00044  * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
00045  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
00046  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
00047  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
00048  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
00049  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
00050  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
00051  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
00052  * SUCH DAMAGE.
00053  * ====================================================================
00054  *
00055  * This software consists of voluntary contributions made by many
00056  * individuals on behalf of the Apache Software Foundation.  For more
00057  * information on the Apache Software Foundation, please see
00058  * <http://www.apache.org/>.
00059  *
00060  */
00061 
00062 
00063 package org.apache.commons.fileupload;
00064 
00065 
00066 import java.io.ByteArrayOutputStream;
00067 import java.io.IOException;
00068 import java.io.InputStream;
00069 import java.io.OutputStream;
00070 import java.io.UnsupportedEncodingException;
00071 
00072 
00134 public class MultipartStream
00135 {
00136 
00137     // ----------------------------------------------------- Manifest constants
00138 
00139 
00144     public static final int HEADER_PART_SIZE_MAX = 10240;
00145 
00146 
00150     protected static final int DEFAULT_BUFSIZE = 4096;
00151 
00152 
00157     protected static final byte[] HEADER_SEPARATOR = {0x0D, 0x0A, 0x0D, 0x0A};
00158 
00159 
00164     protected static final byte[] FIELD_SEPARATOR = { 0x0D, 0x0A };
00165 
00166 
00171     protected static final byte[] STREAM_TERMINATOR = { 0x2D, 0x2D };
00172 
00173 
00174     // ----------------------------------------------------------- Data members
00175 
00176 
00180     private InputStream input;
00181 
00182 
00186     private int boundaryLength;
00187 
00188 
00193     private int keepRegion;
00194 
00195 
00199     private byte[] boundary;
00200 
00201 
00205     private int bufSize;
00206 
00207 
00211     private byte[] buffer;
00212 
00213 
00219     private int head;
00220 
00221 
00227     private int tail;
00228 
00229 
00233     private String headerEncoding;
00234 
00235 
00236     // ----------------------------------------------------------- Constructors
00237 
00238 
00246     public MultipartStream()
00247     {
00248     }
00249 
00250 
00269     public MultipartStream(InputStream input,
00270                            byte[] boundary,
00271                            int bufSize)
00272     {
00273         this.input = input;
00274         this.bufSize = bufSize;
00275         this.buffer = new byte[bufSize];
00276 
00277         // We prepend CR/LF to the boundary to chop trailng CR/LF from
00278         // body-data tokens.
00279         this.boundary = new byte[boundary.length + 4];
00280         this.boundaryLength = boundary.length + 4;
00281         this.keepRegion = boundary.length + 3;
00282         this.boundary[0] = 0x0D;
00283         this.boundary[1] = 0x0A;
00284         this.boundary[2] = 0x2D;
00285         this.boundary[3] = 0x2D;
00286         System.arraycopy(boundary, 0, this.boundary, 4, boundary.length);
00287 
00288         head = 0;
00289         tail = 0;
00290     }
00291 
00292 
00306     public MultipartStream(InputStream input,
00307                            byte[] boundary)
00308         throws IOException
00309     {
00310         this(input, boundary, DEFAULT_BUFSIZE);
00311     }
00312 
00313 
00314     // --------------------------------------------------------- Public methods
00315 
00316 
00325     public String getHeaderEncoding()
00326     {
00327         return headerEncoding;
00328     }
00329 
00330 
00338     public void setHeaderEncoding(String encoding)
00339     {
00340         headerEncoding = encoding;
00341     }
00342 
00343 
00352     public byte readByte()
00353         throws IOException
00354     {
00355         // Buffer depleted ?
00356         if (head == tail)
00357         {
00358             head = 0;
00359             // Refill.
00360             tail = input.read(buffer, head, bufSize);
00361             if (tail == -1)
00362             {
00363                 // No more data available.
00364                 throw new IOException("No more data is available");
00365             }
00366         }
00367         return buffer[head++];
00368     }
00369 
00370 
00381     public boolean readBoundary()
00382         throws MalformedStreamException
00383     {
00384         byte[] marker = new byte[2];
00385         boolean nextChunk = false;
00386 
00387         head += boundaryLength;
00388         try
00389         {
00390             marker[0] = readByte();
00391             marker[1] = readByte();
00392             if (arrayequals(marker, STREAM_TERMINATOR, 2))
00393             {
00394                 nextChunk = false;
00395             }
00396             else if (arrayequals(marker, FIELD_SEPARATOR, 2))
00397             {
00398                 nextChunk = true;
00399             }
00400             else
00401             {
00402                 throw new MalformedStreamException(
00403                         "Unexpected characters follow a boundary");
00404             }
00405         }
00406         catch (IOException e)
00407         {
00408             throw new MalformedStreamException("Stream ended unexpectedly");
00409         }
00410         return nextChunk;
00411     }
00412 
00413 
00433     public void setBoundary(byte[] boundary)
00434         throws IllegalBoundaryException
00435     {
00436         if (boundary.length != boundaryLength - 4)
00437         {
00438             throw new IllegalBoundaryException(
00439                     "The length of a boundary token can not be changed");
00440         }
00441         System.arraycopy(boundary, 0, this.boundary, 4, boundary.length);
00442     }
00443 
00444 
00460     public String readHeaders()
00461         throws MalformedStreamException
00462     {
00463         int i = 0;
00464         byte b[] = new byte[1];
00465         // to support multi-byte characters
00466         ByteArrayOutputStream baos = new ByteArrayOutputStream();
00467         int sizeMax = HEADER_PART_SIZE_MAX;
00468         int size = 0;
00469         while (i < 4)
00470         {
00471             try
00472             {
00473                 b[0] = readByte();
00474             }
00475             catch (IOException e)
00476             {
00477                 throw new MalformedStreamException("Stream ended unexpectedly");
00478             }
00479             size++;
00480             if (b[0] == HEADER_SEPARATOR[i])
00481             {
00482                 i++;
00483             }
00484             else
00485             {
00486                 i = 0;
00487             }
00488             if (size <= sizeMax)
00489             {
00490                 baos.write(b[0]);
00491             }
00492         }
00493 
00494         String headers = null;
00495         if (headerEncoding != null)
00496         {
00497             try
00498             {
00499                 headers = baos.toString(headerEncoding);
00500             }
00501             catch (UnsupportedEncodingException e)
00502             {
00503                 // Fall back to platform default if specified encoding is not
00504                 // supported.
00505                 headers = baos.toString();
00506             }
00507         }
00508         else
00509         {
00510             headers = baos.toString();
00511         }
00512 
00513         return headers;
00514     }
00515 
00516 
00533     public int readBodyData(OutputStream output)
00534         throws MalformedStreamException,
00535                IOException
00536     {
00537         boolean done = false;
00538         int pad;
00539         int pos;
00540         int bytesRead;
00541         int total = 0;
00542         while (!done)
00543         {
00544             // Is boundary token present somewere in the buffer?
00545             pos = findSeparator();
00546             if (pos != -1)
00547             {
00548                 // Write the rest of the data before the boundary.
00549                 output.write(buffer, head, pos - head);
00550                 total += pos - head;
00551                 head = pos;
00552                 done = true;
00553             }
00554             else
00555             {
00556                 // Determine how much data should be kept in the
00557                 // buffer.
00558                 if (tail - head > keepRegion)
00559                 {
00560                     pad = keepRegion;
00561                 }
00562                 else
00563                 {
00564                     pad = tail - head;
00565                 }
00566                 // Write out the data belonging to the body-data.
00567                 output.write(buffer, head, tail - head - pad);
00568 
00569                 // Move the data to the beging of the buffer.
00570                 total += tail - head - pad;
00571                 System.arraycopy(buffer, tail - pad, buffer, 0, pad);
00572 
00573                 // Refill buffer with new data.
00574                 head = 0;
00575                 bytesRead = input.read(buffer, pad, bufSize - pad);
00576 
00577                 // [pprrrrrrr]
00578                 if (bytesRead != -1)
00579                 {
00580                     tail = pad + bytesRead;
00581                 }
00582                 else
00583                 {
00584                     // The last pad amount is left in the buffer.
00585                     // Boundary can't be in there so write out the
00586                     // data you have and signal an error condition.
00587                     output.write(buffer, 0, pad);
00588                     output.flush();
00589                     total += pad;
00590                     throw new MalformedStreamException(
00591                             "Stream ended unexpectedly");
00592                 }
00593             }
00594         }
00595         output.flush();
00596         return total;
00597     }
00598 
00599 
00612     public int discardBodyData()
00613         throws MalformedStreamException,
00614                IOException
00615     {
00616         boolean done = false;
00617         int pad;
00618         int pos;
00619         int bytesRead;
00620         int total = 0;
00621         while (!done)
00622         {
00623             // Is boundary token present somewere in the buffer?
00624             pos = findSeparator();
00625             if (pos != -1)
00626             {
00627                 // Write the rest of the data before the boundary.
00628                 total += pos - head;
00629                 head = pos;
00630                 done = true;
00631             }
00632             else
00633             {
00634                 // Determine how much data should be kept in the
00635                 // buffer.
00636                 if (tail - head > keepRegion)
00637                 {
00638                     pad = keepRegion;
00639                 }
00640                 else
00641                 {
00642                     pad = tail - head;
00643                 }
00644                 total += tail - head - pad;
00645 
00646                 // Move the data to the beging of the buffer.
00647                 System.arraycopy(buffer, tail - pad, buffer, 0, pad);
00648 
00649                 // Refill buffer with new data.
00650                 head = 0;
00651                 bytesRead = input.read(buffer, pad, bufSize - pad);
00652 
00653                 // [pprrrrrrr]
00654                 if (bytesRead != -1)
00655                 {
00656                     tail = pad + bytesRead;
00657                 }
00658                 else
00659                 {
00660                     // The last pad amount is left in the buffer.
00661                     // Boundary can't be in there so signal an error
00662                     // condition.
00663                     total += pad;
00664                     throw new MalformedStreamException(
00665                             "Stream ended unexpectedly");
00666                 }
00667             }
00668         }
00669         return total;
00670     }
00671 
00672 
00681     public boolean skipPreamble()
00682         throws IOException
00683     {
00684         // First delimiter may be not preceeded with a CRLF.
00685         System.arraycopy(boundary, 2, boundary, 0, boundary.length - 2);
00686         boundaryLength = boundary.length - 2;
00687         try
00688         {
00689             // Discard all data up to the delimiter.
00690             discardBodyData();
00691 
00692             // Read boundary - if succeded, the stream contains an
00693             // encapsulation.
00694             return readBoundary();
00695         }
00696         catch (MalformedStreamException e)
00697         {
00698             return false;
00699         }
00700         finally
00701         {
00702             // Restore delimiter.
00703             System.arraycopy(boundary, 0, boundary, 2, boundary.length - 2);
00704             boundaryLength = boundary.length;
00705             boundary[0] = 0x0D;
00706             boundary[1] = 0x0A;
00707         }
00708     }
00709 
00710 
00722     public static boolean arrayequals(byte[] a,
00723                                       byte[] b,
00724                                       int count)
00725     {
00726         for (int i = 0; i < count; i++)
00727         {
00728             if (a[i] != b[i])
00729             {
00730                 return false;
00731             }
00732         }
00733         return true;
00734     }
00735 
00736 
00747     protected int findByte(byte value,
00748                            int pos)
00749     {
00750         for (int i = pos; i < tail; i++)
00751         {
00752             if (buffer[i] == value)
00753             {
00754                 return i;
00755             }
00756         }
00757 
00758         return -1;
00759     }
00760 
00761 
00770     protected int findSeparator()
00771     {
00772         int first;
00773         int match = 0;
00774         int maxpos = tail - boundaryLength;
00775         for (first = head;
00776              (first <= maxpos) && (match != boundaryLength);
00777              first++)
00778         {
00779             first = findByte(boundary[0], first);
00780             if (first == -1 || (first > maxpos))
00781             {
00782                 return -1;
00783             }
00784             for (match = 1; match < boundaryLength; match++)
00785             {
00786                 if (buffer[first + match] != boundary[match])
00787                 {
00788                     break;
00789                 }
00790             }
00791         }
00792         if (match == boundaryLength)
00793         {
00794             return first - 1;
00795         }
00796         return -1;
00797     }
00798 
00804     public String toString()
00805     {
00806         StringBuffer sbTemp = new StringBuffer();
00807         sbTemp.append("boundary='");
00808         sbTemp.append(String.valueOf(boundary));
00809         sbTemp.append("'\nbufSize=");
00810         sbTemp.append(bufSize);
00811         return sbTemp.toString();
00812     }
00813 
00818     public class MalformedStreamException
00819         extends IOException
00820     {
00825         public MalformedStreamException()
00826         {
00827             super();
00828         }
00829 
00836         public MalformedStreamException(String message)
00837         {
00838             super(message);
00839         }
00840     }
00841 
00842 
00846     public class IllegalBoundaryException
00847         extends IOException
00848     {
00853         public IllegalBoundaryException()
00854         {
00855             super();
00856         }
00857 
00864         public IllegalBoundaryException(String message)
00865         {
00866             super(message);
00867         }
00868     }
00869 
00870 
00871     // ------------------------------------------------------ Debugging methods
00872 
00873 
00874     // These are the methods that were used to debug this stuff.
00875     /*
00876 
00877     // Dump data.
00878     protected void dump()
00879     {
00880         System.out.println("01234567890");
00881         byte[] temp = new byte[buffer.length];
00882         for(int i=0; i<buffer.length; i++)
00883         {
00884             if (buffer[i] == 0x0D || buffer[i] == 0x0A)
00885             {
00886                 temp[i] = 0x21;
00887             }
00888             else
00889             {
00890                 temp[i] = buffer[i];
00891             }
00892         }
00893         System.out.println(new String(temp));
00894         int i;
00895         for (i=0; i<head; i++)
00896             System.out.print(" ");
00897         System.out.println("h");
00898         for (i=0; i<tail; i++)
00899             System.out.print(" ");
00900         System.out.println("t");
00901         System.out.flush();
00902     }
00903 
00904     // Main routine, for testing purposes only.
00905     //
00906     // @param args A String[] with the command line arguments.
00907     // @exception Exception, a generic exception.
00908     public static void main( String[] args )
00909         throws Exception
00910     {
00911         File boundaryFile = new File("boundary.dat");
00912         int boundarySize = (int)boundaryFile.length();
00913         byte[] boundary = new byte[boundarySize];
00914         FileInputStream input = new FileInputStream(boundaryFile);
00915         input.read(boundary,0,boundarySize);
00916 
00917         input = new FileInputStream("multipart.dat");
00918         MultipartStream chunks = new MultipartStream(input, boundary);
00919 
00920         int i = 0;
00921         String header;
00922         OutputStream output;
00923         boolean nextChunk = chunks.skipPreamble();
00924         while (nextChunk)
00925         {
00926             header = chunks.readHeaders();
00927             System.out.println("!"+header+"!");
00928             System.out.println("wrote part"+i+".dat");
00929             output = new FileOutputStream("part"+(i++)+".dat");
00930             chunks.readBodyData(output);
00931             nextChunk = chunks.readBoundary();
00932         }
00933     }
00934 
00935     */
00936 }

Generated on Mon Jan 11 21:19:15 2010 for OpenMobileIS by  doxygen 1.5.4