Coverage Report - org.melati.servlet.MultipartDataDecoder
 
Classes in this File Line Coverage Branch Coverage Complexity
MultipartDataDecoder
0%
0/94
0%
0/42
5.143
 
 1  
 /**
 2  
  * $Source: /usr/cvsroot/melati/melati/src/main/java/org/melati/servlet/MultipartDataDecoder.java,v $
 3  
  * $Revision: 1.16 $
 4  
  *
 5  
  * Copyright (C) 2000 Myles Chippendale
 6  
  *
 7  
  * Part of Melati (http://melati.org), a framework for the rapid
 8  
  * development of clean, maintainable web applications.
 9  
  *
 10  
  * Melati is free software; Permission is granted to copy, distribute
 11  
  * and/or modify this software under the terms either:
 12  
  *
 13  
  * a) the GNU General Public License as published by the Free Software
 14  
  *    Foundation; either version 2 of the License, or (at your option)
 15  
  *    any later version,
 16  
  *
 17  
  *    or
 18  
  *
 19  
  * b) any version of the Melati Software License, as published
 20  
  *    at http://melati.org
 21  
  *
 22  
  * You should have received a copy of the GNU General Public License and
 23  
  * the Melati Software License along with this program;
 24  
  * if not, write to the Free Software Foundation, Inc.,
 25  
  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA to obtain the
 26  
  * GNU General Public License and visit http://melati.org to obtain the
 27  
  * Melati Software License.
 28  
  *
 29  
  * Feel free to contact the Developers of Melati (http://melati.org),
 30  
  * if you would like to work out a different arrangement than the options
 31  
  * outlined here.  It is our intention to allow Melati to be used by as
 32  
  * wide an audience as possible.
 33  
  *
 34  
  * This program is distributed in the hope that it will be useful,
 35  
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 36  
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 37  
  * GNU General Public License for more details.
 38  
  *
 39  
  * Contact details for copyright holder:
 40  
  *
 41  
  *     Myles Chippendale <mylesc@paneris.org>
 42  
  *     http://paneris.org/
 43  
  *     29 Stanley Road, Oxford, OX4 1QY, UK
 44  
  *
 45  
  * Based on code by
 46  
  *   Vasily Pozhidaev <voodoo@knastu.ru; vpozhidaev@mail.ru>
 47  
  * */
 48  
 
 49  
 package org.melati.servlet;
 50  
 
 51  
 import java.io.IOException;
 52  
 import java.io.InputStream;
 53  
 import java.util.Hashtable;
 54  
 import org.melati.Melati;
 55  
 import org.melati.util.DelimitedBufferedInputStream;
 56  
 
 57  
 /**
 58  
  * Parses a multipart/form-data request into its different
 59  
  * fields, saving any files it finds along the way.
 60  
  */
 61  
 public class MultipartDataDecoder {
 62  
 
 63  0
   private int maxSize = 2048;
 64  0
   private Melati melati = null;
 65  
   DelimitedBufferedInputStream in;
 66  
   String contentType;
 67  
   FormDataAdaptorFactory factory;
 68  0
   Hashtable fields = new Hashtable();
 69  
 
 70  
   private static final int FIELD_START = 0;
 71  
   private static final int IN_FIELD_HEADER = 1;
 72  
   private static final int IN_FIELD_DATA = 2;
 73  
   private static final int STOP = 3;
 74  
 
 75  0
   private int state = FIELD_START;
 76  
 
 77  
   /**
 78  
    * Constructor with default maximum size.
 79  
    *
 80  
    * @param melati      The {@link Melati}
 81  
    * @param in          An {@link InputStream} from which to read the data
 82  
    * @param contentType A valid MIME type
 83  
    * @param factory     A {@link FormDataAdaptorFactory} to determine how to 
 84  
    *                    store the object's data
 85  
    */
 86  
   public MultipartDataDecoder(Melati melati,
 87  
                               InputStream in,
 88  
                               String contentType,
 89  0
                               FormDataAdaptorFactory factory) {
 90  0
     this.melati = melati;
 91  0
     this.in = new DelimitedBufferedInputStream(in, maxSize);
 92  0
     this.contentType = contentType;
 93  0
     this.factory = factory;
 94  0
   }
 95  
 
 96  
   /**
 97  
    * Constructor specifying maximum size.
 98  
    *
 99  
    * @param melati      The {@link Melati}
 100  
    * @param in          An {@link InputStream} from which to read the data
 101  
    * @param contentType A valid MIME type
 102  
    * @param factory     A {@link FormDataAdaptorFactory} to determine how to 
 103  
    *                    store the object's data
 104  
    * @param maxSize     The maximum size of the data
 105  
    */
 106  
   public MultipartDataDecoder(Melati melati,
 107  
                               InputStream in,
 108  
                               String contentType,
 109  
                               FormDataAdaptorFactory factory,
 110  0
                               int maxSize) {
 111  0
     this.melati = melati;
 112  0
     this.in = new DelimitedBufferedInputStream(in, maxSize);
 113  0
     this.contentType = contentType;
 114  0
     this.factory = factory;
 115  0
     this.maxSize = maxSize;
 116  0
   }
 117  
 
 118  
  /**
 119  
   * Parse the uploaded data into its constituents. 
 120  
   * 
 121  
   * @return a <code>Hashtable</code> of the constituents
 122  
   * @throws IOException
 123  
   *         if an error occurs reading the input stream
 124  
   */
 125  
   public Hashtable parseData() throws IOException {
 126  
     try {
 127  0
       return parseData(in, contentType, maxSize);
 128  
     }
 129  0
     catch (IOException e) {
 130  0
       throw e;
 131  
     }
 132  
     finally {
 133  0
       in.close();
 134  0
       in = null;
 135  
     }
 136  
   }
 137  
 
 138  
   private Hashtable parseData(DelimitedBufferedInputStream inP,
 139  
                               String contentTypeP,
 140  
                               int maxSizeP)
 141  
       throws IOException {
 142  0
     String boundary = getBoundary(contentTypeP);
 143  
     String line;
 144  0
     String header = "";
 145  0
     MultipartFormField field = null;
 146  0
     byte[] CRLF = {13,10};
 147  0
     byte[] buff = new byte[maxSizeP];
 148  
     int count;
 149  
 
 150  0
     while (state != STOP) {
 151  
 
 152  
        // Look for the start of a field (a boundary)
 153  0
       if (state == FIELD_START) {
 154  0
         count = inP.readToDelimiter(buff, 0, buff.length, boundary.getBytes());
 155  0
         if (count == buff.length) {
 156  0
           throw new IOException(
 157  
               "Didn't find a boundary in the first " 
 158  
               + buff.length + " bytes");
 159  
         }
 160  0
         count = inP.readToDelimiter(buff, 0, buff.length, CRLF);
 161  0
         line = new String(buff, 0, count);
 162  
 
 163  0
         if (line.equals(boundary)) {
 164  0
           state = IN_FIELD_HEADER;
 165  0
           header = "";
 166  0
           if (inP.read(buff, 0, 2) != 2) // snarf the crlf
 167  0
             throw new IOException(
 168  
                 "Boundary wasn't followed by 2 bytes (\\r\\n)");
 169  
         }
 170  0
         else if (line.equals(boundary+"--")) {
 171  0
           state = STOP;
 172  
         }
 173  
         else
 174  0
           throw new IOException(
 175  
               "Didn't find the boundary I was expecting before a field");
 176  
       }
 177  
       
 178  
        // Read headers (i.e. until the first blank line)
 179  0
       if (state == IN_FIELD_HEADER) {
 180  0
         count = inP.readToDelimiter(buff, 0, buff.length, CRLF);
 181  0
         if (count != 0)     // a header line
 182  0
           header += new String(buff, 0, count) + "\r\n";
 183  
         else {              // end of headers
 184  0
           state = IN_FIELD_DATA;
 185  0
           field = new MultipartFormField();
 186  0
           readField(field, header);
 187  0
           fields.put(field.getFieldName(), field);
 188  
         }
 189  0
         if (inP.read(buff, 0, 2) != 2) // snarf the crlf
 190  0
           throw new IOException(
 191  
               "Header line wasn't followed by 2 bytes (\\r\\n)");
 192  
       } 
 193  
 
 194  
        // Read data (i.e. until the next boundary);
 195  0
       if (state == IN_FIELD_DATA) {
 196  0
         String dataBoundary = "\r\n" + boundary;
 197  
 
 198  
         // get an adaptor to save the field data 
 199  0
         FormDataAdaptor adaptor = null;
 200  
         // Field should never be null but eclipse doesn't know that 
 201  0
         if (field != null && field.getUploadedFileName().equals("")) { // no file uploaded
 202  0
           adaptor = new MemoryDataAdaptor(); // store data in memory
 203  
         }
 204  
         else {
 205  0
           adaptor = factory.get(melati, field); 
 206  
         }
 207  0
         adaptor.readData(field, inP, dataBoundary.getBytes());
 208  
         // Field should never be null but eclipse doesn't know that 
 209  0
         if (field != null) field.setFormDataAdaptor(adaptor);
 210  0
         state = FIELD_START;
 211  0
       }
 212  
 
 213  
     }  // end of while (state != STOP)
 214  0
     return fields;
 215  
   }
 216  
 
 217  
   private void readField(MultipartFormField field, String header) {
 218  0
     field.setContentDisposition(extractField(header, 
 219  
                                              "content-disposition:", ";"));
 220  0
     String fieldName = extractField(header, "name=",";");
 221  0
     if (fieldName.length() != 0) {
 222  0
       if(fieldName.charAt(0) == '\"')
 223  0
         fieldName = fieldName.substring(1, fieldName.length() - 1);
 224  0
       field.setFieldName(fieldName);
 225  
     }
 226  0
     String fileName = extractField(header, "filename=", ";");
 227  0
     if(fileName.length() != 0) {
 228  0
       if(fileName.charAt(0) == '\"')
 229  0
         fileName = fileName.substring(1, fileName.length()-1);
 230  0
       field.setUploadedFilePath(fileName);
 231  
     }
 232  0
     field.setContentType(extractField(header, "content-type:", ";"));
 233  0
   }
 234  
 
 235  
  /**
 236  
   * Extract a String from header bounded by lBound and either: 
 237  
   * rBound or a "\r\n"
 238  
   * or the end of the String.
 239  
   *
 240  
   * @param header The field metadata to read
 241  
   * @param lBound Where to start reading from 
 242  
   * @param rBound where to stop reading
 243  
   * @return The substring required
 244  
   */
 245  
   protected String extractField(String header, String lBound, 
 246  
                                 String rBound) {
 247  0
     String lheader = header.toLowerCase();
 248  0
     int begin = 0, end = 0;
 249  0
     begin = lheader.indexOf(lBound);
 250  0
     if(begin == -1)
 251  0
       return "";
 252  0
     begin = begin + lBound.length();
 253  0
     end = lheader.indexOf(rBound, begin);
 254  0
     if(end == -1)
 255  0
       end = lheader.indexOf("\r\n", begin);
 256  0
     if(end == -1)
 257  0
       end = lheader.length();
 258  0
     return header.substring(begin, end).trim();
 259  
   }
 260  
 
 261  
  /**
 262  
   * Extract boundary from the header.
 263  
   * No longer attempts to get it from the data.
 264  
   * 
 265  
   */
 266  
   private String getBoundary(/*byte[] data, */ String header) 
 267  
       throws IOException {
 268  0
     String boundary="";
 269  
     int index;
 270  
     // 9 - number of symbols in "boundary="
 271  0
     if ((index = header.lastIndexOf("boundary=")) != -1) {
 272  0
        boundary = header.substring(index + 9);
 273  
        // as since real boundary two chars '-' longer 
 274  
        // than written in CONTENT_TYPE header
 275  0
        boundary = "--" + boundary;
 276  
     } else {
 277  
 
 278  
 /*
 279  
        // HotJava does not send boundary within CONTENT_TYPE header:
 280  
        // and as I seen HotJava has errors with sending binary data.
 281  
        int begin = 0, end = 0;
 282  
        byte[] str1 = {45, 45}, str2 = {13, 10};
 283  
        begin = indexOf(data, str1, 0);
 284  
        end = indexOf(data, str2, begin);
 285  
 
 286  
        // Boundary length should be in reasonable limits
 287  
        if (begin != -1 && end != -1 && 
 288  
            ((end - begin) > 5 && 
 289  
            (end - begin) < 100)) {
 290  
          byte[] buf = new byte[end - begin];
 291  
          System.arraycopy(data, begin, buf, 0, end - begin);
 292  
          boundary = new String(buf);
 293  
        } else {
 294  
          throw new IOException("Boundary not found");
 295  
        }
 296  
 */
 297  0
       throw new IOException("Boundary not found in header");
 298  
     }
 299  0
      return boundary;
 300  
   }
 301  
 
 302  
 }
 303  
 
 304  
 
 305  
 
 306  
     
 307  
 
 308  
 
 309