Coverage Report - org.mule.transport.http.multipart.MultiPartInputStream
 
Classes in this File Line Coverage Branch Coverage Complexity
MultiPartInputStream
0%
0/169
0%
0/134
0
MultiPartInputStream$MultiPart
0%
0/63
0%
0/36
0
 
 1  
 /*
 2  
  * $Id:  $
 3  
  * -------------------------------------------------------------------------------------
 4  
  * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
 5  
  *
 6  
  * The software in this package is published under the terms of the CPAL v1.0
 7  
  * license, a copy of which has been included with this distribution in the
 8  
  * LICENSE.txt file.
 9  
  */
 10  
 package org.mule.transport.http.multipart;
 11  
 // ========================================================================
 12  
 // Copyright (c) 2006-2010 Mort Bay Consulting Pty. Ltd.
 13  
 // ------------------------------------------------------------------------
 14  
 // All rights reserved. This program and the accompanying materials
 15  
 // are made available under the terms of the Eclipse Public License v1.0
 16  
 // and Apache License v2.0 which accompanies this distribution.
 17  
 // The Eclipse Public License is available at
 18  
 // http://www.eclipse.org/legal/epl-v10.html
 19  
 // The Apache License v2.0 is available at
 20  
 // http://www.opensource.org/licenses/apache2.0.php
 21  
 // You may elect to redistribute this code under either of these licenses.
 22  
 // ========================================================================
 23  
 
 24  
 
 25  
 import java.io.BufferedInputStream;
 26  
 import java.io.BufferedOutputStream;
 27  
 import java.io.ByteArrayInputStream;
 28  
 import java.io.ByteArrayOutputStream;
 29  
 import java.io.File;
 30  
 import java.io.FileInputStream;
 31  
 import java.io.FileNotFoundException;
 32  
 import java.io.FileOutputStream;
 33  
 import java.io.IOException;
 34  
 import java.io.InputStream;
 35  
 import java.io.OutputStream;
 36  
 import java.util.Collection;
 37  
 import java.util.HashMap;
 38  
 import java.util.Map;
 39  
 import java.util.StringTokenizer;
 40  
 
 41  
 import javax.servlet.ServletException;
 42  
 
 43  
 
 44  
 /**
 45  
 * MultipartInputStream
 46  
 *
 47  
 * Handle a MultiPart Mime input stream, breaking it up on the boundary into files and strings.
 48  
 */
 49  
 public class MultiPartInputStream
 50  
 {
 51  0
     public static final MultipartConfiguration __DEFAULT_MULTIPART_CONFIG = new MultipartConfiguration(System.getProperty("java.io.tmpdir"));
 52  
     protected InputStream _in;
 53  
     protected MultipartConfiguration _config;
 54  
     protected String _contentType;
 55  
     protected MultiMap _map;
 56  
     protected Map<String, Part> _parts;
 57  
     protected File _tmpDir;
 58  
 
 59  
 
 60  
 
 61  
 
 62  
     public class MultiPart implements Part
 63  
     {
 64  
         protected String _name;
 65  
         protected String _filename;
 66  
         protected File _file;
 67  
         protected OutputStream _out;
 68  
         protected String _contentType;
 69  
         protected MultiMap<String> _headers;
 70  0
         protected long _size = 0;
 71  
 
 72  
         public MultiPart (String name, String filename)
 73  
         throws IOException
 74  0
         {
 75  0
             _name = name;
 76  0
             _filename = filename;
 77  0
         }
 78  
 
 79  
         protected void setContentType (String contentType)
 80  
         {
 81  0
             _contentType = contentType;
 82  0
         }
 83  
 
 84  
 
 85  
         protected void open()
 86  
         throws FileNotFoundException, IOException
 87  
         {
 88  
             //We will either be writing to a file, if it has a filename on the content-disposition
 89  
             //and otherwise a byte-array-input-stream, OR if we exceed the getFileSizeThreshold, we
 90  
             //will need to change to write to a file.
 91  0
             if (_filename != null && _filename.trim().length() > 0)
 92  
             {
 93  0
                 createFile();
 94  
             }
 95  
             else
 96  
             {
 97  
                 //Write to a buffer in memory until we discover we've exceed the
 98  
                 //MultipartConfig fileSizeThreshold
 99  0
                 _out = new ByteArrayOutputStream();
 100  
             }
 101  0
         }
 102  
 
 103  
         protected void close()
 104  
         throws IOException
 105  
         {
 106  0
             _out.close();
 107  0
         }
 108  
 
 109  
 
 110  
         protected void write (int b)
 111  
         throws IOException
 112  
         {
 113  0
             if (MultiPartInputStream.this._config.getMaxFileSize() > 0 && _size + 1 > MultiPartInputStream.this._config.getMaxFileSize())
 114  0
                 throw new IOException ("Multipart Mime part "+_name+" exceeds max filesize");
 115  
 
 116  0
             if (MultiPartInputStream.this._config.getFileSizeThreshold() > 0 && _size + 1 > MultiPartInputStream.this._config.getFileSizeThreshold() && _file==null)
 117  0
                 createFile();
 118  0
             _out.write(b);
 119  0
             _size ++;
 120  0
         }
 121  
 
 122  
         protected void write (byte[] bytes, int offset, int length)
 123  
         throws IOException
 124  
         {
 125  0
             if (MultiPartInputStream.this._config.getMaxFileSize() > 0 && _size + length > MultiPartInputStream.this._config.getMaxFileSize())
 126  0
                 throw new IOException ("Multipart Mime part "+_name+" exceeds max filesize");
 127  
 
 128  0
             if (MultiPartInputStream.this._config.getFileSizeThreshold() > 0 && _size + length > MultiPartInputStream.this._config.getFileSizeThreshold() && _file==null)
 129  0
                 createFile();
 130  
 
 131  0
             _out.write(bytes, offset, length);
 132  0
             _size += length;
 133  0
         }
 134  
 
 135  
         protected void createFile ()
 136  
         throws IOException
 137  
         {
 138  0
             _file = File.createTempFile("MultiPart", "", MultiPartInputStream.this._tmpDir);
 139  0
             FileOutputStream fos = new FileOutputStream(_file);
 140  0
             BufferedOutputStream bos = new BufferedOutputStream(fos);
 141  
 
 142  0
             if (_size > 0 && _out != null)
 143  
             {
 144  
                 //already written some bytes, so need to copy them into the file
 145  0
                 _out.flush();
 146  0
                 ((ByteArrayOutputStream)_out).writeTo(bos);
 147  0
                 _out.close();
 148  
             }
 149  0
             _out = bos;
 150  0
         }
 151  
 
 152  
 
 153  
 
 154  
         protected void setHeaders(MultiMap<String> headers)
 155  
         {
 156  0
             _headers = headers;
 157  0
         }
 158  
 
 159  
         /**
 160  
          * @see Part#getContentType()
 161  
          */
 162  
         public String getContentType()
 163  
         {
 164  0
             return _contentType;
 165  
         }
 166  
 
 167  
         /**
 168  
          * @see Part#getHeader(java.lang.String)
 169  
          */
 170  
         public String getHeader(String name)
 171  
         {
 172  0
             return (String)_headers.getValue(name, 0);
 173  
         }
 174  
 
 175  
         /**
 176  
          * @see Part#getHeaderNames()
 177  
          */
 178  
         public Collection<String> getHeaderNames()
 179  
         {
 180  0
             return _headers.keySet();
 181  
         }
 182  
 
 183  
         /**
 184  
          * @see Part#getHeaders(java.lang.String)
 185  
          */
 186  
         public Collection<String> getHeaders(String name)
 187  
         {
 188  0
            return _headers.getValues(name);
 189  
         }
 190  
 
 191  
         /**
 192  
          * @see Part#getInputStream()
 193  
          */
 194  
         public InputStream getInputStream() throws IOException
 195  
         {
 196  0
            if (_file != null)
 197  
            {
 198  0
                return new BufferedInputStream (new FileInputStream(_file));
 199  
            }
 200  
            else
 201  
            {
 202  
                //part content is in a ByteArrayOutputStream
 203  0
                return new ByteArrayInputStream(((ByteArrayOutputStream)_out).toByteArray());
 204  
            }
 205  
         }
 206  
 
 207  
         /**
 208  
          * @see Part#getName()
 209  
          */
 210  
         public String getName()
 211  
         {
 212  0
            return _name;
 213  
         }
 214  
 
 215  
         /**
 216  
          * @see Part#getSize()
 217  
          */
 218  
         public long getSize()
 219  
         {
 220  0
             return _size;
 221  
         }
 222  
 
 223  
         /**
 224  
          * @see Part#write(java.lang.String)
 225  
          */
 226  
         public void write(String fileName) throws IOException
 227  
         {
 228  0
             if (_file == null)
 229  
             {
 230  
                 //part data is only in the ByteArrayOutputStream and never been written to disk
 231  0
                 _file = new File (_tmpDir, fileName);
 232  0
                 BufferedOutputStream bos = null;
 233  
                 try
 234  
                 {
 235  0
                     bos = new BufferedOutputStream(new FileOutputStream(_file));
 236  0
                     ((ByteArrayOutputStream)_out).writeTo(bos);
 237  0
                     bos.flush();
 238  
                 }
 239  
                 finally
 240  
                 {
 241  0
                     if (bos != null)
 242  0
                         bos.close();
 243  
                 }
 244  0
             }
 245  
             else
 246  
             {
 247  
                 //the part data is already written to a temporary file, just rename it
 248  0
                 _file.renameTo(new File(_tmpDir, fileName));
 249  
             }
 250  0
         }
 251  
 
 252  
         /**
 253  
          * @see Part#delete()
 254  
          */
 255  
         public void delete() throws IOException
 256  
         {
 257  0
             if (_file != null)
 258  0
                 _file.delete();
 259  0
         }
 260  
 
 261  
 
 262  
         /**
 263  
          * Get the file, if any, the data has been written to.
 264  
          * @return
 265  
          */
 266  
         public File getFile ()
 267  
         {
 268  0
             return _file;
 269  
         }
 270  
 
 271  
 
 272  
         /**
 273  
          * Get the filename from the content-disposition.
 274  
          * @return null or the filename
 275  
          */
 276  
         public String getContentDispositionFilename ()
 277  
         {
 278  0
             return _filename;
 279  
         }
 280  
     }
 281  
 
 282  
 
 283  
 
 284  
 
 285  
     /**
 286  
      * @param in Request input stream
 287  
      * @param contentType Content-Type header
 288  
      * @param config MultipartConfiguration
 289  
      */
 290  
     public MultiPartInputStream(InputStream in, String contentType, MultipartConfiguration config)
 291  0
     {
 292  0
         _in = new BufferedInputStream(in);
 293  0
        _contentType = contentType;
 294  0
        _config = config;
 295  0
        if (_config == null)
 296  0
            _config = __DEFAULT_MULTIPART_CONFIG;
 297  0
     }
 298  
 
 299  
 
 300  
 
 301  
     public Collection<Part> getParts()
 302  
     throws IOException
 303  
     {
 304  0
         parse();
 305  0
         return _parts.values();
 306  
     }
 307  
 
 308  
 
 309  
     public Part getPart(String name)
 310  
     throws IOException, ServletException
 311  
     {
 312  0
         parse();
 313  0
         return _parts.get(name);
 314  
     }
 315  
 
 316  
 
 317  
     public MultiMap getMap ()
 318  
     throws IOException, ServletException
 319  
     {
 320  0
         parse();
 321  0
         return _map;
 322  
     }
 323  
 
 324  
 
 325  
     protected void parse ()
 326  
     throws IOException
 327  
     {
 328  
         //have we already parsed the input?
 329  0
         if (_parts != null)
 330  0
             return;
 331  
 
 332  
         //initialize
 333  0
         long total = 0; //keep running total of size of bytes read from input and throw an exception if exceeds MultipartConfiguration._maxRequestSize
 334  0
         _parts = new HashMap<String, Part>();
 335  
 
 336  
         //if its not a multipart request, don't parse it
 337  0
         if (_contentType == null || !_contentType.startsWith("multipart/form-data"))
 338  0
             return;
 339  
 
 340  
         //sort out the location to which to write the files
 341  0
         String location = __DEFAULT_MULTIPART_CONFIG.getLocation();
 342  0
         location = ("".equals(_config.getLocation())? location : _config.getLocation());
 343  
 
 344  0
         _tmpDir = new File(location);
 345  0
         if (!_tmpDir.exists())
 346  0
             _tmpDir.mkdirs();
 347  
 
 348  
 
 349  0
         String boundary="--"+value(_contentType.substring(_contentType.indexOf("boundary=")));
 350  0
         byte[] byteBoundary=(boundary+"--").getBytes("ISO-8859-1");
 351  
 
 352  
         // Get first boundary
 353  0
         byte[] bytes=readLine(_in);
 354  0
         String line=bytes==null?null:new String(bytes,"UTF-8");
 355  0
         if(line==null || !line.equals(boundary))
 356  
         {
 357  0
             throw new IOException("Missing initial multi part boundary");
 358  
         }
 359  
 
 360  
         // Read each part
 361  0
         boolean lastPart=false;
 362  0
         String contentDisposition=null;
 363  0
         String contentType=null;
 364  0
         outer:while(!lastPart)
 365  
         {
 366  0
             MultiMap<String> headers = new MultiMap<String>();
 367  
             while(true)
 368  
             {
 369  0
                 bytes=readLine(_in);
 370  0
                 if(bytes==null)
 371  0
                     break outer;
 372  
 
 373  
                 // If blank line, end of part headers
 374  0
                 if(bytes.length==0)
 375  0
                     break;
 376  
 
 377  0
                 total += bytes.length;
 378  0
                 if (_config.getMaxRequestSize() > 0 && total > _config.getMaxRequestSize())
 379  0
                     throw new IOException ("Request exceeds maxRequestSize ("+_config.getMaxRequestSize()+")");
 380  
 
 381  0
                 line=new String(bytes,"UTF-8");
 382  
 
 383  
                 //get content-disposition and content-type
 384  0
                 int c=line.indexOf(':',0);
 385  0
                 if(c>0)
 386  
                 {
 387  0
                     String key=line.substring(0,c).trim().toLowerCase();
 388  0
                     String value=line.substring(c+1,line.length()).trim();
 389  0
                     headers.put(key, value);
 390  0
                     if (key.equalsIgnoreCase("content-disposition"))
 391  0
                         contentDisposition=value;
 392  0
                     if (key.equalsIgnoreCase("content-type"))
 393  0
                         contentType = value;
 394  
                 }
 395  0
             }
 396  
 
 397  
             // Extract content-disposition
 398  0
             boolean form_data=false;
 399  0
             if(contentDisposition==null)
 400  
             {
 401  0
                 throw new IOException("Missing content-disposition");
 402  
             }
 403  
 
 404  0
             StringTokenizer tok=new StringTokenizer(contentDisposition,";");
 405  0
             String name=null;
 406  0
             String filename=null;
 407  0
             while(tok.hasMoreTokens())
 408  
             {
 409  0
                 String t=tok.nextToken().trim();
 410  0
                 String tl=t.toLowerCase();
 411  0
                 if(t.startsWith("form-data"))
 412  0
                     form_data=true;
 413  0
                 else if(tl.startsWith("name="))
 414  0
                     name=value(t);
 415  0
                 else if(tl.startsWith("filename="))
 416  0
                     filename=value(t);
 417  0
             }
 418  
 
 419  
             // Check disposition
 420  0
             if(!form_data)
 421  
             {
 422  0
                 continue;
 423  
             }
 424  
             //It is valid for reset and submit buttons to have an empty name.
 425  
             //If no name is supplied, the browser skips sending the info for that field.
 426  
             //However, if you supply the empty string as the name, the browser sends the
 427  
             //field, with name as the empty string. So, only continue this loop if we
 428  
             //have not yet seen a name field.
 429  0
             if(name==null)
 430  
             {
 431  0
                 continue;
 432  
             }
 433  
 
 434  
             //Have a new Part
 435  0
             MultiPart part = new MultiPart(name, filename);
 436  0
             part.setHeaders(headers);
 437  0
             part.setContentType(contentType);
 438  0
             _parts.put(name, part);
 439  
 
 440  0
             part.open();
 441  
 
 442  
             try
 443  
             {
 444  0
                 int state=-2;
 445  
                 int c;
 446  0
                 boolean cr=false;
 447  0
                 boolean lf=false;
 448  
 
 449  
                 // loop for all lines`
 450  
                 while(true)
 451  
                 {
 452  0
                     int b=0;
 453  0
                     while((c=(state!=-2)?state:_in.read())!=-1)
 454  
                     {
 455  0
                         total ++;
 456  0
                         if (_config.getMaxRequestSize() > 0 && total > _config.getMaxRequestSize())
 457  0
                             throw new IOException("Request exceeds maxRequestSize ("+_config.getMaxRequestSize()+")");
 458  
 
 459  0
                         state=-2;
 460  
                         // look for CR and/or LF
 461  0
                         if(c==13||c==10)
 462  
                         {
 463  0
                             if(c==13)
 464  0
                                 state=_in.read();
 465  
                             break;
 466  
                         }
 467  
                         // look for boundary
 468  0
                         if(b>=0&&b<byteBoundary.length&&c==byteBoundary[b])
 469  0
                             b++;
 470  
                         else
 471  
                         {
 472  
                             // this is not a boundary
 473  0
                             if(cr)
 474  0
                                 part.write(13);
 475  
 
 476  0
                             if(lf)
 477  0
                                 part.write(10);
 478  
 
 479  0
                             cr=lf=false;
 480  0
                             if(b>0)
 481  0
                                 part.write(byteBoundary,0,b);
 482  
 
 483  0
                             b=-1;
 484  0
                             part.write(c);
 485  
                         }
 486  
                     }
 487  
                     // check partial boundary
 488  0
                     if((b>0&&b<byteBoundary.length-2)||(b==byteBoundary.length-1))
 489  
                     {
 490  0
                         if(cr)
 491  0
                             part.write(13);
 492  
 
 493  0
                         if(lf)
 494  0
                             part.write(10);
 495  
 
 496  0
                         cr=lf=false;
 497  0
                         part.write(byteBoundary,0,b);
 498  0
                         b=-1;
 499  
                     }
 500  
                     // boundary match
 501  0
                     if(b>0||c==-1)
 502  
                     {
 503  0
                         if(b==byteBoundary.length)
 504  0
                             lastPart=true;
 505  0
                         if(state==10)
 506  0
                             state=-2;
 507  
                         break;
 508  
                     }
 509  
                     // handle CR LF
 510  0
                     if(cr)
 511  0
                         part.write(13);
 512  
 
 513  0
                     if(lf)
 514  0
                         part.write(10);
 515  
 
 516  0
                     cr=(c==13);
 517  0
                     lf=(c==10||state==10);
 518  0
                     if(state==10)
 519  0
                         state=-2;
 520  0
                 }
 521  
             }
 522  
             finally
 523  
             {
 524  
 
 525  0
                 part.close();
 526  0
             }
 527  0
         }
 528  0
     }
 529  
 
 530  
 
 531  
     /* ------------------------------------------------------------ */
 532  
     private String value(String nameEqualsValue)
 533  
     {
 534  0
         String value=nameEqualsValue.substring(nameEqualsValue.indexOf('=')+1).trim();
 535  0
         int i=value.indexOf(';');
 536  0
         if(i>0)
 537  0
             value=value.substring(0,i);
 538  0
         if(value.startsWith("\""))
 539  
         {
 540  0
             value=value.substring(1,value.indexOf('"',1));
 541  
         }
 542  
         else
 543  
         {
 544  0
             i=value.indexOf(' ');
 545  0
             if(i>0)
 546  0
                 value=value.substring(0,i);
 547  
         }
 548  0
         return value;
 549  
     }
 550  
 
 551  0
     public static int CR = '\015';
 552  0
     public static int LF = '\012';
 553  
 
 554  
     private byte[] readLine(InputStream in) throws IOException
 555  
     {
 556  0
         byte[] buf = new byte[256];
 557  
 
 558  0
         int i=0;
 559  0
         int loops=0;
 560  0
         int ch=0;
 561  
 
 562  
         while (true)
 563  
         {
 564  0
             ch=in.read();
 565  0
             if (ch<0)
 566  0
                 break;
 567  0
             loops++;
 568  
 
 569  
             // skip a leading LF's
 570  0
             if (loops==1 && ch==LF)
 571  0
                 continue;
 572  
 
 573  0
             if (ch==CR || ch==LF)
 574  0
                 break;
 575  
 
 576  0
             if (i>=buf.length)
 577  
             {
 578  0
                 byte[] old_buf=buf;
 579  0
                 buf=new byte[old_buf.length+256];
 580  0
                 System.arraycopy(old_buf, 0, buf, 0, old_buf.length);
 581  
             }
 582  0
             buf[i++]=(byte)ch;
 583  
         }
 584  
 
 585  0
         if (ch==-1 && i==0)
 586  0
             return null;
 587  
 
 588  
         // skip a trailing LF if it exists
 589  0
         if (ch==CR && in.available()>=1 && in.markSupported())
 590  
         {
 591  0
             in.mark(1);
 592  0
             ch=in.read();
 593  0
             if (ch!=LF)
 594  0
                 in.reset();
 595  
         }
 596  
 
 597  0
         byte[] old_buf=buf;
 598  0
         buf=new byte[i];
 599  0
         System.arraycopy(old_buf, 0, buf, 0, i);
 600  
 
 601  0
         return buf;
 602  
     }
 603  
 
 604  
 }