| Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
| MultipartDataDecoder |
|
| 5.142857142857143;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 |