##### Coverage Report - org.melati.servlet.MultipartFormDataDecoder

Classes in this File | Line Coverage | Branch Coverage | Complexity
MultipartFormDataDecoder |
91% | 82/90
73% | 31/42
| 5.285714285714286;5.286

1 |  |
     /**
2 |  |
      * $Source: /usr/cvsroot/melati/melati/src/site/resources/withWebmacro/org.melati.servlet.MultipartFormDataDecoder.html,v $
3 |  |
      * $Revision: 1.1 $
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 MultipartFormDataDecoder {
62 |  |
63 | 2 |
       private static int MAX_SIZE = 2048;
64 |  |
       private int maxSize;
65 | 38 |
       private Melati melati = null;
66 |  |
       DelimitedBufferedInputStream in;
67 |  |
       String contentType;
68 |  |
       FormDataAdaptorFactory factory;
69 | 38 |
       Hashtable<String,MultipartFormField> fields = new Hashtable<String,MultipartFormField>();
70 |  |
71 |  |
       private static final int FIELD_START = 0;
72 |  |
       private static final int IN_FIELD_HEADER = 1;
73 |  |
       private static final int IN_FIELD_DATA = 2;
74 |  |
       private static final int STOP = 3;
75 |  |
76 | 38 |
       private int state = FIELD_START;
77 |  |
78 |  |
       /**
79 |  |
        * Constructor with default maximum size.
80 |  |
        *
81 |  |
        * @param melati      The {@link Melati}
82 |  |
        * @param in          An {@link InputStream} from which to read the data
83 |  |
        * @param contentType A valid MIME type
84 |  |
        * @param factory     A {@link FormDataAdaptorFactory} to determine how to
85 |  |
        *                    store the object's data
86 |  |
        */
87 |  |
       public MultipartFormDataDecoder(Melati melati,
88 |  |
                                   InputStream in,
89 |  |
                                   String contentType,
90 |  |
                                   FormDataAdaptorFactory factory) {
91 | 38 |
         this(melati,in, contentType, factory, MAX_SIZE);
92 | 38 |
       }
93 |  |
94 |  |
       /**
95 |  |
        * Constructor specifying maximum size.
96 |  |
        *
97 |  |
        * @param melati      The {@link Melati}
98 |  |
        * @param in          An {@link InputStream} from which to read the data
99 |  |
        * @param contentType A valid MIME type
100 |  |
        * @param factory     A {@link FormDataAdaptorFactory} to determine how to
101 |  |
        *                    store the object's data
102 |  |
        * @param maxSize     The maximum size of the data
103 |  |
        */
104 |  |
       public MultipartFormDataDecoder(Melati melati,
105 |  |
                                   InputStream in,
106 |  |
                                   String contentType,
107 |  |
                                   FormDataAdaptorFactory factory,
108 | 38 |
                                   int maxSize) {
109 | 38 |
         this.melati = melati;
110 | 38 |
         this.in = new DelimitedBufferedInputStream(in, maxSize);
111 | 38 |
         this.contentType = contentType;
112 | 38 |
         this.factory = factory;
113 | 38 |
         this.maxSize = maxSize;
114 | 38 |
       }
115 |  |
116 |  |
      /**
117 |  |
       * Parse the uploaded data into its constituents.
118 |  |
       *
119 |  |
       * @return a <code>Hashtable</code> of the constituents
120 |  |
       * @throws IOException
121 |  |
       *         if an error occurs reading the input stream
122 |  |
       */
123 |  |
       public Hashtable<String,MultipartFormField> parseData() throws IOException {
124 |  |
         try {
125 | 38 |
           return parseData(in, contentType, maxSize);
126 |  |
         }
127 | 0 |
         catch (IOException e) {
128 | 0 |
           throw e;
129 |  |
         }
130 |  |
         finally {
131 | 38 |
           in.close();
132 | 38 |
           in = null;
133 |  |
         }
134 |  |
       }
135 |  |
136 |  |
       private Hashtable<String,MultipartFormField> parseData(DelimitedBufferedInputStream inP,
137 |  |
                                   String contentTypeP,
138 |  |
                                   int maxSizeP)
139 |  |
           throws IOException {
140 | 38 |
         String boundary = getBoundary(contentTypeP);
141 |  |
         String line;
142 | 38 |
         String header = "";
143 | 38 |
         MultipartFormField field = null;
144 | 38 |
         byte[] CRLF = {13,10};
145 | 38 |
         byte[] buff = new byte[maxSizeP];
146 |  |
         int count;
147 |  |
148 | 374 |
         while (state != STOP) {
149 |  |
150 |  |
            // Look for the start of a field (a boundary)
151 | 336 |
           if (state == FIELD_START) {
152 | 166 |
             count = inP.readToDelimiter(buff, 0, buff.length, boundary.getBytes());
153 | 166 |
             if (count == buff.length) {
154 | 0 |
               throw new IOException(
155 |  |
                   "Didn't find a boundary in the first "
156 |  |
                   + buff.length + " bytes");
157 |  |
             }
158 | 166 |
             count = inP.readToDelimiter(buff, 0, buff.length, CRLF);
159 | 166 |
             line = new String(buff, 0, count);
160 |  |
161 | 166 |
             if (line.equals(boundary)) {
162 | 128 |
               state = IN_FIELD_HEADER;
163 | 128 |
               header = "";
164 | 128 |
               if (inP.read(buff, 0, 2) != 2) // snarf the crlf
165 | 0 |
                 throw new IOException(
166 |  |
                     "Boundary wasn't followed by 2 bytes (\\r\\n)");
167 |  |
             }
168 | 38 |
             else if (line.equals(boundary+"--")) {
169 | 38 |
               state = STOP;
170 |  |
             }
171 |  |
             else
172 | 0 |
               throw new IOException(
173 |  |
                   "Didn't find the boundary I was expecting before a field");
174 |  |
           }
175 |  |
176 |  |
            // Read headers (i.e. until the first blank line)
177 | 336 |
           if (state == IN_FIELD_HEADER) {
178 | 298 |
             count = inP.readToDelimiter(buff, 0, buff.length, CRLF);
179 | 298 |
             if (count != 0)     // a header line
180 | 170 |
               header += new String(buff, 0, count) + "\r\n";
181 |  |
             else {              // end of headers
182 | 128 |
               state = IN_FIELD_DATA;
183 | 128 |
               field = new MultipartFormField();
184 | 128 |
               readField(field, header);
185 | 128 |
               fields.put(field.getFieldName(), field);
186 |  |
             }
187 | 298 |
             if (inP.read(buff, 0, 2) != 2) // snarf the crlf
188 | 0 |
               throw new IOException(
189 |  |
                   "Header line wasn't followed by 2 bytes (\\r\\n)");
190 |  |
           }
191 |  |
192 |  |
            // Read data (i.e. until the next boundary);
193 | 336 |
           if (state == IN_FIELD_DATA) {
194 | 128 |
             String dataBoundary = "\r\n" + boundary;
195 |  |
196 |  |
             // get an adaptor to save the field data
197 | 128 |
             FormDataAdaptor adaptor = null;
198 |  |
             // Field should never be null but eclipse doesn't know that
199 | 128 |
             if (field != null && field.getUploadedFileName().equals("")) { // no file uploaded
200 | 102 |
               adaptor = new MemoryFormDataAdaptor(); // store data in memory
201 |  |
             }
202 |  |
             else {
203 | 26 |
               adaptor = factory.get(melati, field);
204 |  |
             }
205 | 128 |
             adaptor.readData(field, inP, dataBoundary.getBytes());
206 |  |
             // Field should never be null but eclipse doesn't know that
207 | 128 |
             if (field != null) field.setFormDataAdaptor(adaptor);
208 | 128 |
             state = FIELD_START;
209 | 128 |
           }
210 |  |
211 |  |
         }  // end of while (state != STOP)
212 | 38 |
         return fields;
213 |  |
       }
214 |  |
215 |  |
       private void readField(MultipartFormField field, String header) {
216 | 128 |
         field.setContentDisposition(extractField(header,
217 |  |
                                                  "content-disposition:", ";"));
218 | 128 |
         String fieldName = extractField(header, "name=",";");
219 | 128 |
         if (fieldName.length() != 0) {
220 | 128 |
           if(fieldName.charAt(0) == '\"')
221 | 128 |
             fieldName = fieldName.substring(1, fieldName.length() - 1);
222 | 128 |
           field.setFieldName(fieldName);
223 |  |
         }
224 | 128 |
         String fileName = extractField(header, "filename=", ";");
225 | 128 |
         if(fileName.length() != 0) {
226 | 42 |
           if(fileName.charAt(0) == '\"')
227 | 42 |
             fileName = fileName.substring(1, fileName.length()-1);
228 | 42 |
           field.setUploadedFilePath(fileName);
229 |  |
         }
230 | 128 |
         field.setContentType(extractField(header, "content-type:", ";"));
231 | 128 |
       }
232 |  |
233 |  |
      /**
234 |  |
       * Extract a String from header bounded by lBound and either:
235 |  |
       * rBound or a "\r\n"
236 |  |
       * or the end of the String.
237 |  |
       *
238 |  |
       * @param header The field metadata to read
239 |  |
       * @param lBound Where to start reading from
240 |  |
       * @param rBound where to stop reading
241 |  |
       * @return The substring required
242 |  |
       */
243 |  |
       protected String extractField(String header, String lBound,
244 |  |
                                     String rBound) {
245 | 512 |
         String lheader = header.toLowerCase();
246 | 512 |
         int begin = 0, end = 0;
247 | 512 |
         begin = lheader.indexOf(lBound);
248 | 512 |
         if(begin == -1)
249 | 172 |
           return "";
250 | 340 |
         begin = begin + lBound.length();
251 | 340 |
         end = lheader.indexOf(rBound, begin);
252 | 340 |
         if(end == -1)
253 | 170 |
           end = lheader.indexOf("\r\n", begin);
254 | 340 |
         if(end == -1)
255 | 0 |
           end = lheader.length();
256 | 340 |
         return header.substring(begin, end).trim();
257 |  |
       }
258 |  |
259 |  |
      /**
260 |  |
       * Extract boundary from the header.
261 |  |
       * No longer attempts to get it from the data.
262 |  |
       *
263 |  |
       */
264 |  |
       private String getBoundary(/*byte[] data, */ String header)
265 |  |
           throws IOException {
266 | 38 |
         String boundary="";
267 |  |
         int index;
268 |  |
         // 9 - number of symbols in "boundary="
269 | 38 |
         if ((index = header.lastIndexOf("boundary=")) != -1) {
270 | 38 |
            boundary = header.substring(index + 9);
271 |  |
            // as since real boundary two chars '-' longer
272 |  |
            // than written in CONTENT_TYPE header
273 | 38 |
            boundary = "--" + boundary;
274 |  |
         } else {
275 |  |
276 |  |
     /*
277 |  |
            // HotJava does not send boundary within CONTENT_TYPE header:
278 |  |
            // and as I seen HotJava has errors with sending binary data.
279 |  |
            int begin = 0, end = 0;
280 |  |
            byte[] str1 = {45, 45}, str2 = {13, 10};
281 |  |
            begin = indexOf(data, str1, 0);
282 |  |
            end = indexOf(data, str2, begin);
283 |  |
284 |  |
            // Boundary length should be in reasonable limits
285 |  |
            if (begin != -1 && end != -1 &&
286 |  |
                ((end - begin) > 5 &&
287 |  |
                (end - begin) < 100)) {
288 |  |
              byte[] buf = new byte[end - begin];
289 |  |
              System.arraycopy(data, begin, buf, 0, end - begin);
290 |  |
              boundary = new String(buf);
291 |  |
            } else {
292 |  |
              throw new IOException("Boundary not found");
293 |  |
            }
294 |  |
     */
295 | 0 |
           throw new IOException("Boundary not found in header");
296 |  |
         }
297 | 38 |
          return boundary;
298 |  |
       }
299 |  |
300 |  |
     }
301 |  |
302 |  |
303 |  |
304 |  |
305 |  |
306 |  |
307 |  |