001: /**
002: * Copyright(c) 2001 iSavvix Corporation (http://www.isavvix.com/)
003: *
004: * All rights reserved
005: *
006: * Permission to use, copy, modify and distribute this material for
007: * any purpose and without fee is hereby granted, provided that the
008: * above copyright notice and this permission notice appear in all
009: * copies, and that the name of iSavvix Corporation not be used in
010: * advertising or publicity pertaining to this material without the
011: * specific, prior written permission of an authorized representative of
012: * iSavvix Corporation.
013: *
014: * ISAVVIX CORPORATION MAKES NO REPRESENTATIONS AND EXTENDS NO WARRANTIES,
015: * EXPRESS OR IMPLIED, WITH RESPECT TO THE SOFTWARE, INCLUDING, BUT
016: * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
017: * FITNESS FOR ANY PARTICULAR PURPOSE, AND THE WARRANTY AGAINST
018: * INFRINGEMENT OF PATENTS OR OTHER INTELLECTUAL PROPERTY RIGHTS. THE
019: * SOFTWARE IS PROVIDED "AS IS", AND IN NO EVENT SHALL ISAVVIX CORPORATION OR
020: * ANY OF ITS AFFILIATES BE LIABLE FOR ANY DAMAGES, INCLUDING ANY
021: * LOST PROFITS OR OTHER INCIDENTAL OR CONSEQUENTIAL DAMAGES RELATING
022: * TO THE SOFTWARE.
023: *
024: */package de.danet.an.util.multipartform;
025:
026: import java.io.*;
027: import java.util.*;
028:
029: import javax.servlet.*;
030:
031: /**
032: * This class provides methods for parsing a HTML multi-part form. Each
033: * method returns a Hashtable which contains keys for all parameters sent
034: * from the web browser. The corresponding values are either type "String"
035: * or "FileInfo" depending on the type of data in the corresponding part.
036: * <P>
037: * The following is a sample InputStream expected by the methods in this
038: * class:<PRE>
039:
040: -----------------------------7ce23a18680
041: Content-Disposition: form-data; name="SomeTextField1"
042:
043: on
044: -----------------------------7ce23a18680
045: Content-Disposition: form-data; name="LocalFile1"; filename="C:\temp\testit.c"
046: Content-Type: text/plain
047:
048: #include <stdlib.h>
049:
050:
051: int main(int argc, char **argv)
052: {
053: printf("Testing\n");
054: return 0;
055: }
056:
057: -----------------------------7ce23a18680--
058: </PRE>
059: * @see de.danet.an.util.multipartform.FileInfo
060: * @author Anil Hemrajani
061: */
062: public class HttpMultiPartParser {
063: private final String lineSeparator = System.getProperty(
064: "line.separator", "\n");
065: private final int ONE_MB = 1024 * 1024 * 1;
066:
067: /**
068: * Parses the InputStream, separates the various parts and returns
069: * them as key=value pairs in a Hashtable. Any incoming files are
070: * saved in directory "saveInDir" using the client's file name; the
071: * file information is stored as FileInfo object in the Hashtable
072: * ("value" part).
073: * @param data input stream of the servlet request
074: * @param boundary boundary value from the Content-Type http header
075: * @param saveInDir directory to save as files
076: * @return Hashtable with <code>FileInfo</code> objects
077: * @throws IllegalArgumentException
078: * @throws IOException
079: */
080: public Hashtable parseData(ServletInputStream data,
081: String boundary, String saveInDir)
082: throws IllegalArgumentException, IOException {
083: return processData(data, boundary, saveInDir);
084: }
085:
086: /**
087: * Parses the InputStream, separates the various parts and returns
088: * them as key=value pairs in a Hashtable. Any incoming files are
089: * saved as byte arrays; the file information is stored as java.io.File
090: * object in the Hashtable ("value" part).
091: * @param data input stream of the servlet request
092: * @param boundary boundary value from the Content-Type http header
093: * @return Hashtable with <code>FileInfo</code> objects
094: * @throws IllegalArgumentException
095: * @throws IOException
096: */
097: public Hashtable parseData(ServletInputStream data, String boundary)
098: throws IllegalArgumentException, IOException {
099: return processData(data, boundary, null);
100: }
101:
102: /**
103: * Process the incoming data.
104: * @param data input stream of the servlet request
105: * @param boundary boundary value from the Content-Type http header
106: * @param saveInDir directory to save as files
107: * @return Hashtable with <code>FileInfo</code> objects
108: * @throws IllegalArgumentException
109: * @throws IOException
110: */
111: private Hashtable processData(ServletInputStream is,
112: String boundary, String saveInDir)
113: throws IllegalArgumentException, IOException {
114: if (is == null)
115: throw new IllegalArgumentException("InputStream");
116:
117: if (boundary == null || boundary.trim().length() < 1)
118: throw new IllegalArgumentException("boundary");
119:
120: // Each content will begin with two dashes "--" plus the actual boundary string
121: boundary = "--" + boundary;
122:
123: StringTokenizer stLine = null, stFields = null;
124: FileInfo fileInfo = null;
125: Hashtable dataTable = new Hashtable(5);
126: String line = null, field = null, paramName = null;
127: boolean saveFiles = (saveInDir != null && saveInDir.trim()
128: .length() > 0), isFile = false;
129:
130: // Create output directory in case it doesn't exist
131: if (saveFiles) {
132: File f = new File(saveInDir);
133: f.mkdirs();
134: }
135:
136: line = getLine(is);
137: if (line == null || !line.startsWith(boundary))
138: throw new IOException("Boundary not found;"
139: + " boundary = " + boundary + ", line = " + line);
140:
141: while (line != null) {
142: // Process boundary line ----------------------------------------
143: if (line == null || !line.startsWith(boundary))
144: return dataTable;
145:
146: // Process "Content-Disposition: " line --------------------------
147: line = getLine(is);
148: if (line == null)
149: return dataTable;
150:
151: // Split line into the following 3 tokens (or 2 if not a file):
152: // 1. Content-Disposition: form-data
153: // 2. name="LocalFile1"
154: // 3. filename="C:\autoexec.bat" (only present if this is part of a HTML file INPUT tag) */
155: stLine = new StringTokenizer(line, ";\r\n");
156: if (stLine.countTokens() < 2)
157: throw new IllegalArgumentException(
158: "Bad data in second line");
159:
160: // Confirm that this is "form-data"
161: line = stLine.nextToken().toLowerCase();
162: if (line.indexOf("form-data") < 0)
163: throw new IllegalArgumentException(
164: "Bad data in second line");
165:
166: // Now split token 2 from above into field "name" and it's "value"
167: // e.g. name="LocalFile1"
168: stFields = new StringTokenizer(stLine.nextToken(), "=\"");
169: if (stFields.countTokens() < 2)
170: throw new IllegalArgumentException(
171: "Bad data in second line");
172:
173: // Get field name
174: fileInfo = new FileInfo();
175: stFields.nextToken();
176: paramName = stFields.nextToken();
177:
178: // Now split token 3 from above into file "name" and it's "value"
179: // e.g. filename="C:\autoexec.bat"
180: isFile = false;
181: if (stLine.hasMoreTokens()) {
182: field = stLine.nextToken();
183: stFields = new StringTokenizer(field, "=\"");
184: if (stFields.countTokens() > 1) {
185: if (stFields.nextToken().trim().equalsIgnoreCase(
186: "filename")) {
187: fileInfo.setName(paramName);
188: String value = stFields.nextToken();
189: if (value != null && value.trim().length() > 0) {
190: fileInfo.setClientFileName(value);
191: isFile = true;
192: } else {
193: // An error condition occurred, skip to next boundary
194: line = getLine(is); // Skip "Content-Type:" line
195: line = getLine(is); // Skip blank line
196: line = getLine(is); // Skip blank line
197: line = getLine(is); // Position to boundary line
198: continue;
199: }
200: }
201: } else if (field.toLowerCase().indexOf("filename") >= 0) {
202: // An error condition occurred, skip to next boundary
203: line = getLine(is); // Skip "Content-Type:" line
204: line = getLine(is); // Skip blank line
205: line = getLine(is); // Skip blank line
206: line = getLine(is); // Position to boundary line
207: continue;
208: }
209: }
210:
211: // Process "Content-Type: " line ----------------------------------
212: // e.g. Content-Type: text/plain
213: boolean skipBlankLine = true;
214: if (isFile) {
215: line = getLine(is);
216: if (line == null)
217: return dataTable;
218:
219: // "Content-type" line not guaranteed to be sent by the browser
220: if (line.trim().length() < 1)
221: skipBlankLine = false; // Prevent re-skipping below
222: else {
223: stLine = new StringTokenizer(line, ": ");
224: if (stLine.countTokens() < 2)
225: throw new IllegalArgumentException(
226: "Bad data in third line");
227:
228: stLine.nextToken(); // Content-Type
229: fileInfo.setFileContentType(stLine.nextToken());
230: }
231: }
232:
233: // Skip blank line -----------------------------------------------
234: if (skipBlankLine) // Blank line already skipped above?
235: {
236: line = getLine(is);
237: if (line == null)
238: return dataTable;
239: }
240:
241: // Process data: If not a file, add to hashtable and continue
242: if (!isFile) {
243: line = getLine(is);
244: if (line == null)
245: return dataTable;
246:
247: dataTable.put(paramName, line);
248: line = getLine(is);
249:
250: continue;
251: }
252:
253: // Either save contents in memory or to a local file
254: try {
255: OutputStream os = null;
256: String path = null;
257: if (saveFiles)
258: os = new FileOutputStream(path = getFileName(
259: saveInDir, fileInfo.getClientFileName()));
260: else
261: os = new ByteArrayOutputStream(ONE_MB);
262:
263: // Read till next boundary and write contents to OutputStream
264: boolean readingContent = true;
265: byte b[] = new byte[2 * ONE_MB], b2[] = null;
266: int read;
267:
268: while (readingContent) {
269: if ((read = is.readLine(b, 0, b.length)) == -1) {
270: line = null;
271: break;
272: }
273:
274: // If it's a blank line, hang on to it for next few lines
275: if (read < 3) // < 3 means CR and LF or just LF
276: {
277: b2 = new byte[read];
278: System.arraycopy(b, 0, b2, 0, b2.length);
279: if ((read = is.readLine(b, 0, b.length)) == -1) {
280: line = null;
281: break;
282: }
283: }
284:
285: if (compareBoundary(boundary, b)) {
286: line = new String(b, 0, read);
287: break;
288: } else if (b2 != null) // Prev line was not a boundary line
289: {
290: os.write(b2);
291: b2 = null;
292: }
293:
294: os.write(b, 0, read);
295: os.flush();
296: }
297:
298: os.close();
299: b = null;
300:
301: if (!saveFiles) {
302: ByteArrayOutputStream baos = (ByteArrayOutputStream) os;
303: fileInfo.setFileContents(baos.toByteArray());
304: } else {
305: fileInfo.setLocalFile(new File(path));
306: os = null;
307: }
308:
309: dataTable.put(paramName, fileInfo);
310: } catch (Exception e) {
311: e.printStackTrace();
312: }
313: }
314:
315: return dataTable;
316: }
317:
318: /**
319: * Compares boundary string to byte array.
320: * @param boundary
321: * @param ba
322: * @return
323: */
324: private boolean compareBoundary(String boundary, byte ba[]) {
325: byte b;
326:
327: if (boundary == null || ba == null)
328: return false;
329:
330: for (int i = 0; i < boundary.length(); i++)
331: if ((byte) boundary.charAt(i) != ba[i])
332: return false;
333:
334: return true;
335: }
336:
337: /**
338: * Convenience method to read HTTP header lines.
339: * @param sis
340: * @return
341: */
342: private synchronized String getLine(ServletInputStream sis)
343: throws IOException {
344: byte b[] = new byte[1024];
345: int read = sis.readLine(b, 0, b.length), index;
346: String line = null;
347:
348: if (read != -1) {
349: line = new String(b, 0, read);
350:
351: if ((index = line.indexOf('\n')) >= 0)
352: line = line.substring(0, index - 1);
353: }
354:
355: b = null;
356: return line;
357: }
358:
359: /**
360: * Concats the directory and file names.
361: * @param dir
362: * @param fileName
363: * @return
364: * @throws IllegalArgumentException
365: */
366: private String getFileName(String dir, String fileName)
367: throws IllegalArgumentException {
368: String path = null;
369:
370: if (dir == null || fileName == null)
371: throw new IllegalArgumentException(
372: "dir or fileName is null");
373:
374: int index = fileName.lastIndexOf('/');
375: String name = null;
376: if (index >= 0)
377: name = fileName.substring(index + 1);
378: else
379: name = fileName;
380:
381: index = name.lastIndexOf('\\');
382: if (index >= 0)
383: fileName = name.substring(index + 1);
384:
385: path = dir + File.separator + fileName;
386: if (File.separatorChar == '/')
387: return path.replace('\\', File.separatorChar);
388: else
389: return path.replace('/', File.separatorChar);
390: }
391: }
|