Coverage report

  %line %branch
org.apache.turbine.util.upload.MultipartStream
0% 
0% 

 1  
 package org.apache.turbine.util.upload;
 2  
 
 3  
 /*
 4  
  * Copyright 2001-2005 The Apache Software Foundation.
 5  
  *
 6  
  * Licensed under the Apache License, Version 2.0 (the "License")
 7  
  * you may not use this file except in compliance with the License.
 8  
  * You may obtain a copy of the License at
 9  
  *
 10  
  *     http://www.apache.org/licenses/LICENSE-2.0
 11  
  *
 12  
  * Unless required by applicable law or agreed to in writing, software
 13  
  * distributed under the License is distributed on an "AS IS" BASIS,
 14  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 15  
  * See the License for the specific language governing permissions and
 16  
  * limitations under the License.
 17  
  */
 18  
 
 19  
 import java.io.IOException;
 20  
 import java.io.InputStream;
 21  
 import java.io.OutputStream;
 22  
 
 23  
 /**
 24  
  * This class can be used to process data streams conforming to MIME
 25  
  * 'multipart' format as defined in <a
 26  
  * href="http://rf.cs/rfc1521.html">RFC&nbsp;1251</a>.  Arbitrary
 27  
  * large amouns of data in the stream can be processed under constant
 28  
  * memory usage.
 29  
  *
 30  
  * <p>The format of the stream is defined in the following way:<br>
 31  
  *
 32  
  * <code>
 33  
  *   multipart-body := preamble 1*encapsulation close-delimiter epilogue<br>
 34  
  *   encapsulation := delimiter body CRLF<br>
 35  
  *   delimiter := "--" boundary CRLF<br>
 36  
  *   close-delimiter := "--" boudary "--"<br>
 37  
  *   preamble := &lt;ignore&gt;<br>
 38  
  *   epilogue := &lt;ignore&gt;<br>
 39  
  *   body := header-part CRLF body-part<br>
 40  
  *   header-part := 1*header CRLF<br>
 41  
  *   header := header-name ":" header-value<br>
 42  
  *   header-name := &lt;printable ascii characters except ":"&gt;<br>
 43  
  *   header-value := &lt;any ascii characters except CR & LF&gt;<br>
 44  
  *   body-data := &lt;arbitrary data&gt;<br>
 45  
  * </code>
 46  
  *
 47  
  * <p>Note that body-data can contain another mulipart entity.  There
 48  
  * is limited support for single pass processing of such nested
 49  
  * streams.  The nested stream is <strong>required</strong> to have a
 50  
  * boundary token of the same length as the parent stream (see {@link
 51  
  * #setBoundary(byte[])}).
 52  
  *
 53  
  * <p>Here is an exaple of usage of this class.<br>
 54  
  *
 55  
  * <pre>
 56  
  *    try {
 57  
  *        MultipartStream multipartStream = new MultipartStream(input,
 58  
  *                                                              boundary);
 59  
  *        boolean nextPart = malitPartStream.skipPreamble();
 60  
  *        OutputStream output;
 61  
  *        while(nextPart) {
 62  
  *            header = chunks.readHeader();
 63  
  *            // process headers
 64  
  *            // create some output stream
 65  
  *            multipartStream.readBodyPart(output);
 66  
  *            nextPart = multipartStream.readBoundary();
 67  
  *        }
 68  
  *    } catch(MultipartStream.MalformedStreamException e) {
 69  
  *          // the stream failed to follow required syntax
 70  
  *    } catch(IOException) {
 71  
  *          // a read or write error occurred
 72  
  *    }
 73  
  * </pre>
 74  
  *
 75  
  * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
 76  
  * @version $Id: MultipartStream.java 278822 2005-09-05 19:53:05Z henning $
 77  
  * @deprecated use commons-fileupload instead
 78  
  */
 79  
 public class MultipartStream
 80  
 {
 81  
     /**
 82  
      * The maximum lenght of <code>header-part</code> that will be
 83  
      * processed (10 kilobytes = 10240 bytes.)
 84  
      */
 85  
     public static final int HEADER_PART_SIZE_MAX = 10240;
 86  
 
 87  
     /** The stream were data is read from. */
 88  
     protected InputStream input;
 89  
 
 90  
     /**
 91  
      * The lenght of boundary token plus leading <code>CRLF--</code>.
 92  
      */
 93  
     protected int boundaryLength;
 94  
 
 95  
     /**
 96  
      * The amount of data that must be kept in the buffer in order to
 97  
      * detect delimiters reliably.
 98  
      */
 99  
     protected int keepRegion;
 100  
 
 101  
     /** A byte sequence that partitions the stream. */
 102  
     protected byte[] boundary;
 103  
 
 104  
     /** The lenght of the buffer used for processing. */
 105  
     protected int bufSize;
 106  
 
 107  
     /** The default lenght of the buffer used for processing. */
 108  
     protected static final int DEFAULT_BUFSIZE = 4096;
 109  
 
 110  
     /** The buffer used for processing. */
 111  
     protected byte[] buffer;
 112  
 
 113  
     /**
 114  
      * The index of first valid character in the buffer.
 115  
      *
 116  
      * 0 <= head < bufSize
 117  
      */
 118  
     protected int head;
 119  
 
 120  
     /**
 121  
      * The index of last valid characer in the buffer + 1.
 122  
      *
 123  
      * 0 <= tail <= bufSize
 124  
      */
 125  
     protected int tail;
 126  
 
 127  
     /**
 128  
      * A byte sequence that marks the end of <code>header-part</code>
 129  
      * (<code>CRLFCRLF</code>).
 130  
      */
 131  0
     protected static final byte[] HEADER_SEPARATOR = {0x0D, 0x0A, 0x0D, 0x0A};
 132  
 
 133  
     /**
 134  
      * A byte sequence that that follows a delimiter that will be
 135  
      * followed by an encapsulation (<code>CRLF</code>).
 136  
      */
 137  0
     protected static final byte[] FIELD_SEPARATOR = {0x0D, 0x0A};
 138  
 
 139  
     /**
 140  
      * A byte sequence that that follows a delimiter of the last
 141  
      * encapsulation in the stream (<code>--</code>).
 142  
      */
 143  0
     protected static final byte[] STREAM_TERMINATOR = {0x2D, 0x2D};
 144  
 
 145  
     /**
 146  
      * Constructs a MultipartStream with a custom size buffer.
 147  
      *
 148  
      * <p>Note that the buffer must be at least big enough to contain
 149  
      * the boundary string, plus 4 characters for CR/LF and double
 150  
      * dash, plus at least one byte of data.  Too small buffer size
 151  
      * setting will degrade performance.
 152  
      *
 153  
      * @param input The <code>InputStream</code> to serve as a data
 154  
      * source.
 155  
      * @param boundary The token used for dividing the stream into
 156  
      * <code>encapsulations</code>.
 157  
      * @param bufSize The size of the buffer to be used in bytes.
 158  
      * @exception MalformedStreamException.
 159  
      * @exception IOException.
 160  
      */
 161  
     public MultipartStream(InputStream input,
 162  
                            byte[] boundary,
 163  
                            int bufSize)
 164  
             throws MalformedStreamException,
 165  
             IOException
 166  0
     {
 167  0
         this.input = input;
 168  0
         this.bufSize = bufSize;
 169  0
         this.buffer = new byte[bufSize];
 170  
 
 171  
         // We prepend CR/LF to the boundary to chop trailng CR/LF from
 172  
         // body-data tokens.
 173  0
         this.boundary = new byte[boundary.length + 4];
 174  0
         this.boundaryLength = boundary.length + 4;
 175  0
         this.keepRegion = boundary.length + 3;
 176  0
         this.boundary[0] = 0x0D;
 177  0
         this.boundary[1] = 0x0A;
 178  0
         this.boundary[2] = 0x2D;
 179  0
         this.boundary[3] = 0x2D;
 180  0
         System.arraycopy(boundary, 0, this.boundary, 4, boundary.length);
 181  
 
 182  0
         head = 0;
 183  0
         tail = 0;
 184  0
     }
 185  
 
 186  
     /**
 187  
      * Constructs a MultipartStream with a defalut size buffer.
 188  
      *
 189  
      * @param input The <code>InputStream</code> to serve as a data
 190  
      * source.
 191  
      * @param boundary The token used for dividing the stream into
 192  
      * <code>encapsulations</code>.
 193  
      * @exception IOException.
 194  
      */
 195  
     public MultipartStream(InputStream input,
 196  
                            byte[] boundary)
 197  
             throws IOException
 198  
     {
 199  0
         this(input, boundary, DEFAULT_BUFSIZE);
 200  0
     }
 201  
 
 202  
     /**
 203  
      * Reads a byte from the <code>buffer</code>, and refills it as
 204  
      * neccessary.
 205  
      *
 206  
      * @return Next byte from the input stream.
 207  
      * @exception IOException, if there isn't any more data available.
 208  
      */
 209  
     public byte readByte()
 210  
             throws IOException
 211  
     {
 212  
         // Buffer depleted ?
 213  0
         if (head == tail)
 214  
         {
 215  0
             head = 0;
 216  
             // Refill.
 217  0
             tail = input.read(buffer, head, bufSize);
 218  0
             if (tail == -1)
 219  
             {
 220  
                 // No more data available.
 221  0
                 throw new IOException("No more data is available");
 222  
             }
 223  
         }
 224  0
         return buffer[head++];
 225  
     }
 226  
 
 227  
     /**
 228  
      * Skips a <code>boundary</code> token, and checks wether more
 229  
      * <code>encapsulations</code> are contained in the stream.
 230  
      *
 231  
      * @return <code>True</code> if there are more encapsulations in
 232  
      * this stream.
 233  
      * @exception MalformedStreamException if the stream ends
 234  
      * unexpecetedly or fails to follow required syntax.
 235  
      */
 236  
     public boolean readBoundary()
 237  
             throws MalformedStreamException
 238  
     {
 239  0
         byte[] marker = new byte[2];
 240  0
         boolean nextChunk = false;
 241  
 
 242  0
         head += boundaryLength;
 243  
         try
 244  
         {
 245  0
             marker[0] = readByte();
 246  0
             marker[1] = readByte();
 247  0
             if (arrayequals(marker, STREAM_TERMINATOR, 2))
 248  
             {
 249  0
                 nextChunk = false;
 250  
             }
 251  0
             else if (arrayequals(marker, FIELD_SEPARATOR, 2))
 252  
             {
 253  0
                 nextChunk = true;
 254  
             }
 255  
             else
 256  
             {
 257  0
                 throw new MalformedStreamException("Unexpected characters follow a boundary");
 258  
             }
 259  
         }
 260  0
         catch (IOException e)
 261  
         {
 262  0
             throw new MalformedStreamException("Stream ended unexpectedly");
 263  0
         }
 264  0
         return nextChunk;
 265  
     }
 266  
 
 267  
     /**
 268  
      * Changes the boundary token used for partitioning the stream.
 269  
      *
 270  
      * <p>This method allows single pass processing of nested
 271  
      * multipart streams.
 272  
      *
 273  
      * <p>The boundary token of the nested stream is
 274  
      * <code>required</code> to be of the same length as the boundary
 275  
      * token in parent stream.
 276  
      *
 277  
      * <p>Restoring parent stream boundary token after processing of a
 278  
      * nested stream is left ot the application. <br>
 279  
      *
 280  
      * @param boundary A boundary to be used for parsing of the nested
 281  
      * stream.
 282  
      * @exception IllegalBoundaryException, if <code>boundary</code>
 283  
      * has diffrent lenght than the one being currently in use.
 284  
      */
 285  
     public void setBoundary(byte[] boundary)
 286  
             throws IllegalBoundaryException
 287  
     {
 288  0
         if (boundary.length != boundaryLength - 4)
 289  
         {
 290  0
             throw new IllegalBoundaryException("The length of a boundary token can not be changed");
 291  
         }
 292  0
         System.arraycopy(boundary, 0, this.boundary, 4, boundary.length);
 293  0
     }
 294  
 
 295  
     /**
 296  
      * <p>Reads <code>header-part</code> of the current
 297  
      * <code>encapsulation</code>
 298  
      *
 299  
      * <p>Headers are returned verbatim to the input stream, including
 300  
      * traling <code>CRLF</code> marker. Parsing is left to the
 301  
      * application.
 302  
      *
 303  
      * <p><strong>TODO</strong> allow limiting maximum header size to
 304  
      * protect against abuse.<br>
 305  
      *
 306  
      * @return <code>header-part</code> of the current encapsulation.
 307  
      * @exception MalformedStreamException, if the stream ends
 308  
      * unexpecetedly.
 309  
      */
 310  
     public String readHeaders()
 311  
             throws MalformedStreamException
 312  
     {
 313  0
         int i = 0;
 314  0
         byte b[] = new byte[1];
 315  0
         StringBuffer buf = new StringBuffer();
 316  0
         int sizeMax = HEADER_PART_SIZE_MAX;
 317  0
         int size = 0;
 318  0
         while (i < 4)
 319  
         {
 320  
             try
 321  
             {
 322  0
                 b[0] = readByte();
 323  
             }
 324  0
             catch (IOException e)
 325  
             {
 326  0
                 throw new MalformedStreamException("Stream ended unexpectedly");
 327  0
             }
 328  0
             size++;
 329  0
             if (b[0] == HEADER_SEPARATOR[i])
 330  
             {
 331  0
                 i++;
 332  
             }
 333  
             else
 334  
             {
 335  0
                 i = 0;
 336  
             }
 337  0
             if (size <= sizeMax)
 338  
             {
 339  0
                 buf.append(new String(b));
 340  
             }
 341  
         }
 342  0
         return buf.toString();
 343  
     }
 344  
 
 345  
     /**
 346  
      * Reads <code>body-data</code> from the current
 347  
      * <code>encapsulation</code> and writes its contents into the
 348  
      * output <code>Stream</code>.
 349  
      *
 350  
      * <p>Arbitrary large amouts of data can be processed by this
 351  
      * method using a constant size buffer. (see {@link
 352  
      * #MultipartStream(InputStream,byte[],int) constructor}).
 353  
      *
 354  
      * @param output The <code>Stream</code> to write data into.
 355  
      * @return the amount of data written.
 356  
      * @exception MalformedStreamException
 357  
      * @exception IOException
 358  
      */
 359  
     public int readBodyData(OutputStream output)
 360  
             throws MalformedStreamException,
 361  
             IOException
 362  
     {
 363  0
         boolean done = false;
 364  
         int pad;
 365  
         int pos;
 366  
         int bytesRead;
 367  0
         int total = 0;
 368  0
         while (!done)
 369  
         {
 370  
             // Is boundary token present somewere in the buffer?
 371  0
             pos = findSeparator();
 372  0
             if (pos != -1)
 373  
             {
 374  
                 // Write the rest of the data before the boundary.
 375  0
                 output.write(buffer, head, pos - head);
 376  0
                 total += pos - head;
 377  0
                 head = pos;
 378  0
                 done = true;
 379  
             }
 380  
             else
 381  
             {
 382  
                 // Determine how much data should be kept in the
 383  
                 // buffer.
 384  0
                 if (tail - head > keepRegion)
 385  
                 {
 386  0
                     pad = keepRegion;
 387  
                 }
 388  
                 else
 389  
                 {
 390  0
                     pad = tail - head;
 391  
                 }
 392  
                 // Write out the data belonging to the body-data.
 393  0
                 output.write(buffer, head, tail - head - pad);
 394  
 
 395  
                 // Move the data to the beging of the buffer.
 396  0
                 total += tail - head - pad;
 397  0
                 System.arraycopy(buffer, tail - pad, buffer, 0, pad);
 398  
 
 399  
                 // Refill buffer with new data.
 400  0
                 head = 0;
 401  0
                 bytesRead = input.read(buffer, pad, bufSize - pad);
 402  
 
 403  
                 // [pprrrrrrr]
 404  0
                 if (bytesRead != -1)
 405  
                 {
 406  0
                     tail = pad + bytesRead;
 407  
                 }
 408  
                 else
 409  
                 {
 410  
                     // The last pad amount is left in the buffer.
 411  
                     // Boundary can't be in there so write out the
 412  
                     // data you have and signal an error condition.
 413  0
                     output.write(buffer, 0, pad);
 414  0
                     output.flush();
 415  0
                     total += pad;
 416  0
                     throw new MalformedStreamException("Stream ended unexpectedly");
 417  
                 }
 418  
             }
 419  
         }
 420  0
         output.flush();
 421  0
         return total;
 422  
     }
 423  
 
 424  
     /**
 425  
      * Reads <code>body-data</code> from the current
 426  
      * <code>encapsulation</code> and discards it.
 427  
      *
 428  
      * <p>Use this method to skip encapsulations you don't need or
 429  
      * don't understand.
 430  
      *
 431  
      * @return The amount of data discarded.
 432  
      * @exception MalformedStreamException
 433  
      * @exception IOException
 434  
      */
 435  
     public int discardBodyData()
 436  
             throws MalformedStreamException,
 437  
             IOException
 438  
     {
 439  0
         boolean done = false;
 440  
         int pad;
 441  
         int pos;
 442  
         int bytesRead;
 443  0
         int total = 0;
 444  0
         while (!done)
 445  
         {
 446  
             // Is boundary token present somewere in the buffer?
 447  0
             pos = findSeparator();
 448  0
             if (pos != -1)
 449  
             {
 450  
                 // Write the rest of the data before the boundary.
 451  0
                 total += pos - head;
 452  0
                 head = pos;
 453  0
                 done = true;
 454  
             }
 455  
             else
 456  
             {
 457  
                 // Determine how much data should be kept in the
 458  
                 // buffer.
 459  0
                 if (tail - head > keepRegion)
 460  
                 {
 461  0
                     pad = keepRegion;
 462  
                 }
 463  
                 else
 464  
                 {
 465  0
                     pad = tail - head;
 466  
                 }
 467  0
                 total += tail - head - pad;
 468  
 
 469  
                 // Move the data to the beging of the buffer.
 470  0
                 System.arraycopy(buffer, tail - pad, buffer, 0, pad);
 471  
 
 472  
                 // Refill buffer with new data.
 473  0
                 head = 0;
 474  0
                 bytesRead = input.read(buffer, pad, bufSize - pad);
 475  
 
 476  
                 // [pprrrrrrr]
 477  0
                 if (bytesRead != -1)
 478  
                 {
 479  0
                     tail = pad + bytesRead;
 480  
                 }
 481  
                 else
 482  
                 {
 483  
                     // The last pad amount is left in the buffer.
 484  
                     // Boundary can't be in there so signal an error
 485  
                     // condition.
 486  0
                     total += pad;
 487  0
                     throw new MalformedStreamException("Stream ended unexpectedly");
 488  
                 }
 489  
             }
 490  
         }
 491  0
         return total;
 492  
     }
 493  
 
 494  
     /**
 495  
      * Finds the beginning of the first <code>encapsulation</code>.
 496  
      *
 497  
      * @return <code>True</code> if an <code>encapsulation</code> was
 498  
      * found in the stream.
 499  
      * @exception IOException
 500  
      */
 501  
     public boolean skipPreamble()
 502  
             throws IOException
 503  
     {
 504  
         // First delimiter may be not preceeded with a CRLF.
 505  0
         System.arraycopy(boundary, 2, boundary, 0, boundary.length - 2);
 506  0
         boundaryLength = boundary.length - 2;
 507  
         try
 508  
         {
 509  
             // Discard all data up to the delimiter.
 510  0
             discardBodyData();
 511  
 
 512  
             // Read boundary - if succeded, the stream contains an
 513  
             // encapsulation.
 514  0
             return readBoundary();
 515  
         }
 516  0
         catch (MalformedStreamException e)
 517  
         {
 518  0
             return false;
 519  
         }
 520  
         finally
 521  
         {
 522  
             // Restore delimiter.
 523  0
             System.arraycopy(boundary, 0, boundary, 2, boundary.length - 2);
 524  0
             boundaryLength = boundary.length;
 525  0
             boundary[0] = 0x0D;
 526  0
             boundary[1] = 0x0A;
 527  
         }
 528  
     }
 529  
 
 530  
     /**
 531  
      * Compares <code>count</code> first bytes in the arrays
 532  
      * <code>a</code> and <code>b</code>.
 533  
      *
 534  
      * @param a The first array to compare.
 535  
      * @param b The second array to compare.
 536  
      * @param count How many bytes should be compared.
 537  
      * @return <code>true</code> if <code>count</code> first bytes in
 538  
      * arrays <code>a</code> and <code>b</code> are equal.
 539  
      */
 540  
     public static boolean arrayequals(byte[] a,
 541  
                                       byte[] b,
 542  
                                       int count)
 543  
     {
 544  0
         for (int i = 0; i < count; i++)
 545  
         {
 546  0
             if (a[i] != b[i])
 547  
             {
 548  0
                 return false;
 549  
             }
 550  
         }
 551  0
         return true;
 552  
     }
 553  
 
 554  
     /**
 555  
      * Searches a byte of specified value in the <code>buffer</code>
 556  
      * starting at specified <code>position</code>.
 557  
      *
 558  
      * @param value the value to find.
 559  
      * @param pos The starting position for searching.
 560  
      * @return The position of byte found, counting from beginning of
 561  
      * the <code>buffer</code>, or <code>-1</code> if not found.
 562  
      */
 563  
     protected int findByte(byte value,
 564  
                            int pos)
 565  
     {
 566  0
         for (int i = pos; i < tail; i++)
 567  0
             if (buffer[i] == value)
 568  0
                 return i;
 569  
 
 570  0
         return -1;
 571  
     }
 572  
 
 573  
     /**
 574  
      * Searches the <code>boundary</code> in <code>buffer</code>
 575  
      * region delimited by <code>head</code> and <code>tail</code>.
 576  
      *
 577  
      * @return The position of the boundary found, counting from
 578  
      * beginning of the <code>buffer</code>, or <code>-1</code> if not
 579  
      * found.
 580  
      */
 581  
     protected int findSeparator()
 582  
     {
 583  
         int first;
 584  0
         int match = 0;
 585  0
         int maxpos = tail - boundaryLength;
 586  0
         for (first = head;
 587  0
              (first <= maxpos) && (match != boundaryLength);
 588  0
              first++)
 589  
         {
 590  0
             first = findByte(boundary[0], first);
 591  0
             if (first == -1 || (first > maxpos))
 592  0
                 return -1;
 593  0
             for (match = 1; match < boundaryLength; match++)
 594  
             {
 595  0
                 if (buffer[first + match] != boundary[match])
 596  0
                     break;
 597  
             }
 598  
         }
 599  0
         if (match == boundaryLength)
 600  
         {
 601  0
             return first - 1;
 602  
         }
 603  0
         return -1;
 604  
     }
 605  
 
 606  
     /**
 607  
      * Thrown to indicate that the input stream fails to follow the
 608  
      * required syntax.
 609  
      */
 610  
     public class MalformedStreamException
 611  
             extends IOException
 612  
     {
 613  
         /** Serial Version UID */
 614  
         private static final long serialVersionUID = 3813694874163574138L;
 615  
 
 616  
         /**
 617  
          * Constructs a <code>MalformedStreamException</code> with no
 618  
          * detail message.
 619  
          */
 620  
         public MalformedStreamException()
 621  
         {
 622  
             super();
 623  
         }
 624  
 
 625  
         /**
 626  
          * Constructs an <code>MalformedStreamException</code> with
 627  
          * the specified detail message.
 628  
          *
 629  
          * @param message The detail message.
 630  
          */
 631  
         public MalformedStreamException(String message)
 632  
         {
 633  
             super(message);
 634  
         }
 635  
     }
 636  
 
 637  
     /**
 638  
      * Thrown upon attempt of setting an invalid boundary token.
 639  
      */
 640  
     public class IllegalBoundaryException
 641  
             extends IOException
 642  
     {
 643  
         /** Serial Version UID */
 644  
         private static final long serialVersionUID = -2883885421190362860L;
 645  
 
 646  
         /**
 647  
          * Constructs an <code>IllegalBoundaryException</code> with no
 648  
          * detail message.
 649  
          */
 650  
         public IllegalBoundaryException()
 651  
         {
 652  
             super();
 653  
         }
 654  
 
 655  
         /**
 656  
          * Constructs an <code>IllegalBoundaryException</code> with
 657  
          * the specified detail message.
 658  
          *
 659  
          * @param message The detail message.
 660  
          */
 661  
         public IllegalBoundaryException(String message)
 662  
         {
 663  
             super(message);
 664  
         }
 665  
     }
 666  
 
 667  
     /*-------------------------------------------------------------
 668  
 
 669  
     // These are the methods that were used to debug this stuff.
 670  
 
 671  
     // Dump data.
 672  
     protected void dump()
 673  
     {
 674  
         System.out.println("01234567890");
 675  
         byte[] temp = new byte[buffer.length];
 676  
         for(int i=0; i<buffer.length; i++)
 677  
         {
 678  
             if(buffer[i] == 0x0D || buffer[i] == 0x0A)
 679  
             {
 680  
                 temp[i] = 0x21;
 681  
             }
 682  
             else
 683  
             {
 684  
                 temp[i] = buffer[i];
 685  
             }
 686  
         }
 687  
         System.out.println(new String(temp));
 688  
         int i;
 689  
         for(i=0; i<head; i++)
 690  
             System.out.print(" ");
 691  
         System.out.println("h");
 692  
         for(i=0; i<tail; i++)
 693  
             System.out.print(" ");
 694  
         System.out.println("t");
 695  
         System.out.flush();
 696  
     }
 697  
 
 698  
     // Main routine, for testing purposes only.
 699  
     //
 700  
     // @param args A String[] with the command line arguments.
 701  
     // @exception Exception, a generic exception.
 702  
     public static void main( String[] args )
 703  
         throws Exception
 704  
     {
 705  
         File boundaryFile = new File("boundary.dat");
 706  
         int boundarySize = (int)boundaryFile.length();
 707  
         byte[] boundary = new byte[boundarySize];
 708  
         FileInputStream input = new FileInputStream(boundaryFile);
 709  
         input.read(boundary,0,boundarySize);
 710  
 
 711  
         input = new FileInputStream("multipart.dat");
 712  
         MultipartStream chunks = new MultipartStream(input, boundary);
 713  
 
 714  
         int i = 0;
 715  
         String header;
 716  
         OutputStream output;
 717  
         boolean nextChunk = chunks.skipPreamble();
 718  
         while(nextChunk)
 719  
         {
 720  
             header = chunks.readHeaders();
 721  
             System.out.println("!"+header+"!");
 722  
             System.out.println("wrote part"+i+".dat");
 723  
             output = new FileOutputStream("part"+(i++)+".dat");
 724  
             chunks.readBodyData(output);
 725  
             nextChunk = chunks.readBoundary();
 726  
         }
 727  
     }
 728  
 
 729  
     */
 730  
 }

This report is generated by jcoverage, Maven and Maven JCoverage Plugin.