001: package com.sun.portal.proxylet.servlet;
002:
003: /*
004: * @(#) MultipartRequest.java 1.2
005: *
006: */
007:
008: import java.io.DataInputStream;
009: import java.io.IOException;
010: import java.util.Enumeration;
011: import java.util.Vector;
012:
013: import javax.servlet.http.HttpServletRequest;
014: import javax.servlet.http.HttpUtils;
015: import javax.servlet.ServletException;
016: import javax.servlet.ServletInputStream;
017:
018: /**
019: * Extracts parameter values from "multipart; form-data"
020: * contained in incoming HTTP servlet requests. For further
021: * details concerning multipart requests see RFC 1867.
022: */
023: public class MultipartRequest {
024:
025: /** End of line sequence in multipart data sections */
026: final public static String fEOLN = "\r\n";
027: /**
028: * Sequence containing two end of line sequences in multipart data sections
029: * thus creating an empty line.
030: */
031: final public static String fEmptyLine = fEOLN + fEOLN;
032: /** Terminating sequence at end of multipart section */
033: final public static String fDoubleDash = "--";
034:
035: private HttpServletRequest mRequest;
036: private String mBoundary;
037: private byte[] mDataBytes;
038: private String mDataString;
039: private int mMaxSize;
040:
041: // ==================== class / static methods ==================== //
042:
043: /**
044: * Creates a new String out of an array of bytes or out of null
045: * @param bytes an array of bytes or null
046: * @return an empty string if bytes is null otherwise a new String
047: * out of bytes
048: */
049: final static public String newString(byte[] bytes) {
050: if (bytes == null) {
051: return "";
052: }
053: return new String(bytes);
054: }
055:
056: // ==================== constructors ==================== //
057:
058: /**
059: * A convenience constructor allowing size of multipart content of incoming request to be
060: * of maximum size.
061: * @param request HttpServletRequest containing multipart message
062: * @exception ServletException is thrown if thrown by this(request, -1);
063: */
064: public MultipartRequest(HttpServletRequest request)
065: throws ServletException {
066: this (request, -1);
067: }
068:
069: /**
070: * Creates new IncomingMultiPartHttpServlet out of <request>.
071: * @param request HttpServletRequest containing multipart message
072: * @param maxSize max. allowable size of multipart message. If smaller than 0 it is assumed to
073: * be Integer.MAX_VALUE
074: * @exception ServletException is thrown if either of the following occurs:
075: * <ul><li>incoming request does not have a multipart message
076: * <li>multipart message is too large too process
077: * <li>IOException occurs while reading multipart message
078: * </ul>
079: * @see #MultipartRequest(HttpServletRequest, int)
080: */
081: public MultipartRequest(HttpServletRequest request, int maxSize)
082: throws ServletException {
083: mRequest = request;
084: if (getContentType().indexOf("multipart") == -1) {
085: throw new ServletException(
086: "ServletException: content-type should contain \"multipart\"\n"
087: + "Instead content-type is: "
088: + getContentType());
089: }
090:
091: if (maxSize < 0) {
092: mMaxSize = Integer.MAX_VALUE;
093: } else {
094: mMaxSize = maxSize;
095: }
096:
097: //open input stream from client to capture upload file
098: DataInputStream in = null;
099: try {
100: in = new DataInputStream(getDataInputStream());
101: } catch (IOException ex) {
102: throw new ServletException(ex.toString());
103: }
104:
105: //get length of content data
106: int formDataLength = getContentLength();
107:
108: //allocate a byte array to store content data
109: mDataBytes = new byte[formDataLength];
110: //read file into byte array
111: int bytesRead = 0;
112: int totalBytesRead = 0;
113: while (totalBytesRead < formDataLength) {
114: try {
115: bytesRead = in.read(mDataBytes, totalBytesRead,
116: formDataLength - totalBytesRead);
117: } catch (IOException ex) {
118: throw new ServletException(ex.toString());
119: }
120: totalBytesRead += bytesRead;
121: }
122:
123: mDataString = newString(mDataBytes);
124:
125: String contentType = mRequest.getContentType();
126: int lastIndex = contentType.lastIndexOf("=");
127: mBoundary = contentType.substring(lastIndex + 1, contentType
128: .length());
129: }
130:
131: // ==================== Header data ==================== //
132:
133: /**
134: * Extracts DataInputStream from #mRequest.
135: * @return DataInputStream associtated with #mRequest
136: */
137: public ServletInputStream getDataInputStream() throws IOException {
138: return mRequest.getInputStream();
139: }
140:
141: /**
142: * Extracts ContentLength from #mRequest.
143: * @return ContentLength associtated with #mRequest.
144: */
145: public int getContentLength() {
146: return mRequest.getContentLength();
147: }
148:
149: /**
150: * Extracts ContentType from #mRequest.
151: * @return ContentType associtated with #mRequest.
152: */
153: public String getContentType() {
154: return mRequest.getContentType();
155: }
156:
157: /**
158: * Getter method to access #mDataBytes.
159: * @return mDataBytes Array of bytes containing byte content of original request.
160: */
161: public byte[] getByteContent() {
162: return mDataBytes;
163: }
164:
165: /**
166: * Extracts boundary from ContentType.
167: * @return ContentType associtated with #mRequest.
168: */
169: public String getBoundary() {
170: return mBoundary;
171: }
172:
173: /**
174: * Gets any query string that is part of the HTTP request URI (behind the '?').
175: * Same as the CGI variable QUERY_STRING.
176: * @return query string that is part of this request's URI, or null if it contains no query string
177: */
178: public String getQueryString() {
179: return mRequest.getQueryString();
180: }
181:
182: /**
183: * Get IP address of calling client host from embedded HttpServletRequest.
184: * @return String with IP address of calling client host.
185: */
186: public String getRemoteAddr() {
187: return mRequest.getRemoteAddr();
188: }
189:
190: /**
191: * To be consistent the name of this method should actually be
192: * getParameter. However, getParameter will be used to
193: * access the parameters included in the multipart body. The same
194: * applies to the other methods for accessing parameter names and
195: * values. Methods for accessing the HttpServletRequest parameters
196: * include the word "Header".
197: * @param name String containing name of requested parameter value
198: * @return String containing header parameter value
199: */
200: public String getHeaderParameter(String name) {
201: return mRequest.getParameter(name);
202: }
203:
204: // ==================== Multipart data ==================== //
205:
206: /**
207: * Returns a string containing value of the specified parameter, or null if
208: * parameter does not exist.
209: * @param name String containing the name of the requested parameter value.
210: * @return String or null
211: */
212: public String getMultipartHeader(String name) {
213: DataBlock dblock = getBinaryHeaderBlock(name);
214: if (dblock == null) {
215: return null;
216: }
217:
218: byte bytes[] = dblock.getBlock();
219: return newString(bytes);
220: }
221:
222: /**
223: * Returns a string containing value of the specified parameter, or null if
224: * parameter does not exist.
225: * @param name String containing the name of the requested parameter value.
226: * @return String or null
227: */
228: public String getMultipartValue(String name) {
229: DataBlock dblock = getBinaryValueBlock(name);
230: if (dblock == null) {
231: return null;
232: }
233: byte bytes[] = dblock.getBlock();
234: return newString(bytes);
235: }
236:
237: /**
238: * Extracts the binary content of parameter if available.
239: * @param name String containing name of parameter of which binary content is requested
240: * @return String Containing substring of mDataString with requested binary content
241: */
242: public byte[] getFileValue(String name) {
243: DataBlock db = getBinaryFileValue(name);
244: if (db == null) {
245: return null;
246: }
247: return db.getBlock();
248: }
249:
250: /**
251: * Extracts content-type from parameter block with the name specified if available.
252: * @param name String containing name of parameter of which content-type is requested
253: * @return String Containing requested content-type if name belongs to a file data block
254: * otherwise null
255: */
256: public String getFileContentType(String name) {
257: DataBlock db = getBinaryFileBlock(name);
258: if (db == null) {
259: return null;
260: }
261: return db.getContentType();
262: }
263:
264: /**
265: * Extracts file name from parameter block with the name specified if available.
266: * @param name String containing name of parameter of which file name is requested
267: * @return String Containing requested file name if name belongs to a file data block
268: * otherwise null
269: */
270: public String getFileName(String name) {
271: DataBlock db = getBinaryFileBlock(name);
272: if (db == null) {
273: return null;
274: }
275: return db.getFileName();
276: }
277:
278: /**
279: * Retrieves the file name contained in the DataBlock with the specified
280: * name stripped of any references to its path.
281: * @param name String containing name of data block of which file name is
282: * requested.
283: * @return String containing file name extracted from DataBlock with given name
284: */
285: public String getShortFileName(String name) {
286: DataBlock db = getBinaryFileBlock(name);
287: if (db == null) {
288: return null;
289: }
290: return getShortFileName(db);
291: }
292:
293: /**
294: * To be consistent the name of this method should actually be
295: * getParameter. However, as the depatcher uses the getParameter
296: * method to access the object path. The object path parameter, however,
297: * is included in the multipart body of mRequest and is not really
298: * a HttpServletRequest. Thus getParameter will be used to
299: * access the parameters included in the multipart body. The same
300: * applies to the other methods for accessing parameter names and
301: * values. Methods for accessing the HttpServletRequest parameters
302: * include the word "Header".
303: * @param name String containing name of requested parameter value
304: * @return String containing header parameter value
305: * @see #getHeaderParameter
306: */
307: public String getParameter(String name) {
308: return getMultipartValue(name);
309: }
310:
311: // ==================== request area ==================== //
312:
313: /**
314: * Retrieves URL used to contact server
315: * @return String with URL used to send request
316: */
317: public String getRequestURL() {
318: return new String(HttpUtils.getRequestURL(mRequest));
319: }
320:
321: /**
322: * Retrieves scheme used to make request, such as ftp, http, https
323: * @return String with scheme used to make request.
324: */
325: public String getScheme() {
326: return mRequest.getScheme();
327: }
328:
329: /**
330: * Determines whether request was made using a safe channel or not.
331: * @return true if channel was safe (eg https) otherwise false
332: */
333: public boolean isSecure() {
334: return mRequest.isSecure();
335: }
336:
337: /**
338: * Retrieves name of server from incoming HttpServletRequest
339: * @return String with name of server
340: */
341: public String getServerName() {
342: return mRequest.getServerName();
343: }
344:
345: /**
346: * Retrieves port id of server from incoming HttpServletRequest
347: * @return int with port id of server
348: */
349: public int getServerPort() {
350: return mRequest.getServerPort();
351: }
352:
353: // ==================== convenience/private area ==================== //
354:
355: /**
356: * Class to be used for storing multipart request parameter blocks as
357: * well as parameter block headers and parameter block values.
358: */
359: class DataBlock {
360: /** Start position of mBlock within multipart request */
361: private int mFrom;
362:
363: private int getFrom() {
364: return mFrom;
365: }
366:
367: /** End position of mBlock within multipart request */
368: private int mTo;
369:
370: private int getTo() {
371: return mTo;
372: }
373:
374: /** Block containing parameter block with mName */
375: private byte[] mBlock;
376:
377: private byte[] getBlock() {
378: return mBlock;
379: }
380:
381: /** Name of parameter block */
382: private String mName;
383:
384: private String getName() {
385: return mName;
386: }
387:
388: /** States whether block contains file data (true) or not (false) */
389: private boolean mIsFileBlock = false;
390:
391: private boolean isFileBlock() {
392: return mIsFileBlock;
393: }
394:
395: private void setIsFileBlock(boolean isFB) {
396: mIsFileBlock = isFB;
397: }
398:
399: /** Name of file from/to which block data is downloaded/uploaded */
400: private String mFileName = "";
401:
402: private String getFileName() {
403: return mFileName;
404: }
405:
406: private void setFileName(String fn) {
407: mFileName = (mIsFileBlock) ? fn : "";
408: }
409:
410: /** Content-type of downloaded/uploaded file, if block contains file data */
411: private String mContentType = "";
412:
413: private String getContentType() {
414: return mContentType;
415: }
416:
417: private void setContentType(String ct) {
418: mContentType = (mIsFileBlock) ? ct : "";
419: }
420:
421: /**
422: * @param from int containing start position of block within request
423: * @param to int containing end position of block within request
424: * @param block Array of bytes containing block data
425: * @param name String containing name of block
426: */
427: private DataBlock(int from, int to, byte[] block, String name) {
428: mFrom = from;
429: mTo = to;
430: mBlock = block;
431: mName = name;
432: }
433: }
434:
435: /**
436: * A multipart request consists of a general header and of parameters delineated
437: * by the boundary defined in the request header. The boundary is also used to
438: * separate header and parameters. Also each parameter can be split into a header
439: * and a body separated by an empty line (ie two consecutive line feeds). Each
440: * parameter is assigned a single form-data item. The name of a parameter block
441: * is the name given to the form-data item and is contained in the content-disposition
442: * header entry.
443: * @param name String containing the name of the requested form-data item
444: * (ie parameter block).
445: * @return DataBlock containing parameter block, position within the request
446: * and its name. Returns null if DataBlock with specified
447: * name cannot be found within request or if parameter
448: * block is not well-formed (ie does not contain a empty
449: * line between header and value).
450: * @see #getBinaryHeaderBlock
451: * @see #getBinaryValueBlock
452: */
453: private DataBlock getBinaryParameterBlock(String name) {
454: // Parameter block starts here:
455: String find = mBoundary + fEOLN
456: + "Content-Disposition: form-data; name=\"" + name
457: + "\"";
458: int index = mDataString.indexOf(find);
459: if (index == -1) {
460: return null;
461: }
462:
463: // Parameter block ends here:
464: int index1 = mDataString.indexOf(fEOLN + fDoubleDash
465: + mBoundary, index + fEOLN.length());
466: if (index1 == -1) {
467: return null;
468: }
469:
470: // Parameter block should contain an empty line between index and index1
471: int index2 = mDataString.indexOf(fEmptyLine, index)
472: + fEmptyLine.length();
473: if (index2 == -1 || index2 > index1) {
474: return null;
475: }
476:
477: index2 = mDataString.indexOf(fEOLN, index) + fEOLN.length();
478: int uploadSize = index1 - index2;
479: if (uploadSize < 0) {
480: return null;
481: }
482: byte[] uploadBytes = new byte[uploadSize];
483: for (int i = 0; i < uploadSize; i++) {
484: uploadBytes[i] = mDataBytes[index2 + i];
485: }
486: return new DataBlock(index2, index1, uploadBytes, name);
487: }
488:
489: /**
490: * Retrieves the header part from the specified parameter block contained
491: * within a multipart request.
492: * @param name String containing name of parameter block of which
493: * header data is requested.
494: * @return DataBlock containing the header block of the specified parameter
495: * block, its name as well as position within the total
496: * request. Returns null if DataBlock with specified
497: * name cannot be found within request or if parameter
498: * block is not well-formed (ie does not contain a empty
499: * line between header and value).
500: * @see #getBinaryParameterBlock
501: * @see #getBinaryValueBlock
502: */
503: private DataBlock getBinaryHeaderBlock(String name) {
504: DataBlock dataBlock = getBinaryParameterBlock(name);
505: if (dataBlock == null) {
506: return null;
507: }
508:
509: String block = newString(dataBlock.getBlock());
510: int index = block.indexOf(fEmptyLine) + fEmptyLine.length();
511: if (index == -1) {
512: return null;
513: }
514:
515: byte[] bytes = new byte[index];
516: for (int i = 0; i < index; i++) {
517: bytes[i] = dataBlock.getBlock()[index + i];
518: }
519:
520: int from = dataBlock.getFrom();
521: return new DataBlock(from, from + index, bytes, name);
522: }
523:
524: /**
525: * Retrieves the value part from the specified parameter block contained
526: * within a multipart request.
527: * @param name String containing name of parameter block of which
528: * value data is requested.
529: * @return DataBlock containing the value block of the specified parameter
530: * block, its name as well as position within the total
531: * request. Returns null if DataBlock with specified
532: * name cannot be found within request or if parameter
533: * block is not well-formed (ie does not contain a empty
534: * line between header and value).
535: * @see #getBinaryParameterBlock
536: * @see #getBinaryHeaderBlock
537: */
538: private DataBlock getBinaryValueBlock(String name) {
539: DataBlock dataBlock = getBinaryParameterBlock(name);
540: if (dataBlock == null) {
541: return null;
542: }
543:
544: String block = newString(dataBlock.getBlock());
545: int index = block.indexOf(fEmptyLine) + fEmptyLine.length();
546: if (index == -1) {
547: return null;
548: }
549:
550: int size = block.length() - index;
551: byte[] bytes = null;
552: if (size > 0) {
553: bytes = new byte[size];
554: for (int i = 0; i < size; i++) {
555: bytes[i] = dataBlock.getBlock()[index + i];
556: }
557: }
558:
559: int from = dataBlock.getFrom();
560: return new DataBlock(from + index, from + size, bytes, name);
561: }
562:
563: /**
564: * Multipart requests are used amonst other things for uploading and
565: * downloading file content. This convennience method can therefore
566: * be used for accessing file content if available.
567: * @param name String containing name of parameter block of which
568: * file data is requested.
569: * @return DataBlock containing the requested parameter block, its name
570: * as well as position within the total request. Returns
571: * null if DataBlock with specified name cannot be found
572: * within request or if parameter block is not well-formed
573: * (ie does not contain a empty line between header and value)
574: * or if parameter block does not contain any file data.
575: * @see #getBinaryParameterBlock
576: * @see #getBinaryHeaderBlock
577: * @see #getBinaryValueBlock
578: * @see #getBinaryFileHeader
579: * @see #getBinaryFileValue
580: */
581: private DataBlock getBinaryFileBlock(String name) {
582: DataBlock dataBlock = getBinaryParameterBlock(name);
583: if (dataBlock == null) {
584: return null;
585: }
586:
587: String parameter = mDataString.substring(dataBlock.getFrom(),
588: dataBlock.getTo());
589: String filename = "filename=\"";
590: int index = parameter.indexOf(filename);
591: if (index == -1) {
592: return null;
593: }
594:
595: int index1 = parameter.indexOf("\"" + fEOLN, index);
596: if (index1 == -1) {
597: return null;
598: }
599:
600: dataBlock.setIsFileBlock(true);
601: dataBlock.setFileName(parameter.substring(index
602: + filename.length(), index1));
603:
604: String contentType = fEOLN + "Content-Type: ";
605: index = parameter.indexOf(contentType);
606: if (index != -1) {
607: index1 = parameter.indexOf("\"" + fEOLN, index);
608: if (index1 != -1) {
609: dataBlock.setContentType(parameter.substring(index
610: + contentType.length(), index1));
611: } else {
612: dataBlock.setContentType("Content-Type: text/plain");
613: }
614: } else {
615: dataBlock.setContentType("Content-Type: text/plain");
616: }
617: return dataBlock;
618: }
619:
620: /**
621: * Retrieves the header part of a file block.
622: * @param name String containing name of file block of which
623: * header data is requested.
624: * @return DataBlock containing the requested file header block, if available,
625: * or null.
626: * @see #getBinaryParameterBlock
627: * @see #getBinaryHeaderBlock
628: * @see #getBinaryValueBlock
629: * @see #getBinaryFileHeader
630: * @see #getBinaryFileValue
631: */
632: private DataBlock getBinaryFileHeader(String name) {
633: DataBlock dataBlock = getBinaryFileBlock(name);
634: return getBinaryFileHeader(dataBlock);
635: }
636:
637: /**
638: * @param dataBlock DataBlock containing block of which header data is requested.
639: * @return DataBlock containing the requested file header block, its name
640: * as well as position within the total request. Returns
641: * null if DataBlock with specified name cannot be found
642: * within request or if parameter block is not well-formed
643: * (ie does not contain a empty line between header and value)
644: * or if parameter block does not contain any file data.
645: * @see #getBinaryFileHeader(String)
646: */
647: private DataBlock getBinaryFileHeader(DataBlock dataBlock) {
648: if (dataBlock == null) {
649: return null;
650: }
651:
652: String block = newString(dataBlock.getBlock());
653: int index = block.indexOf(fEmptyLine) + fEmptyLine.length();
654: if (index == -1) {
655: return null;
656: }
657:
658: byte[] bytes = new byte[index];
659: for (int i = 0; i < index; i++) {
660: bytes[i] = dataBlock.getBlock()[i];
661: }
662:
663: int from = dataBlock.getFrom();
664: DataBlock db = new DataBlock(from, from + index, bytes,
665: dataBlock.getName());
666: db.setIsFileBlock(dataBlock.isFileBlock());
667: db.setFileName(dataBlock.getFileName());
668: return db;
669: }
670:
671: /**
672: * Retrieves the value part of a file block.
673: * @param dataBlock DataBlock containing block of which value block is requested.
674: * @return DataBlock containing the requested file value block, if available,
675: * or null.
676: * @see #getBinaryParameterBlock
677: * @see #getBinaryHeaderBlock
678: * @see #getBinaryValueBlock
679: * @see #getBinaryFileHeader
680: * @see #getBinaryFileValue
681: */
682: private DataBlock getBinaryFileValue(String name) {
683: DataBlock dataBlock = getBinaryFileBlock(name);
684: return getBinaryFileValue(dataBlock);
685: }
686:
687: /**
688: * @param name String containing name of file block of which
689: * value block is requested.
690: * @return DataBlock containing the requested file value block, its name
691: * as well as position within the total request. Returns
692: * null if DataBlock with specified name cannot be found
693: * within request or if parameter block is not well-formed
694: * (ie does not contain a empty line between header and value)
695: * or if parameter block does not contain any file data.
696: * @see #getBinaryParameterBlock
697: * @see #getBinaryHeaderBlock
698: * @see #getBinaryValueBlock
699: * @see #getBinaryFileHeader
700: * @see #getBinaryFileValue
701: */
702: private DataBlock getBinaryFileValue(DataBlock dataBlock) {
703: if (dataBlock == null) {
704: return null;
705: }
706:
707: String block = newString(dataBlock.getBlock());
708: int index = block.indexOf(fEmptyLine) + fEmptyLine.length();
709: if (index == -1) {
710: return null;
711: }
712:
713: byte[] bytes = null;
714: int size = block.length() - index;
715: if (size > 0) {
716: bytes = new byte[size];
717: for (int i = 0; i < size; i++) {
718: bytes[i] = dataBlock.getBlock()[index + i];
719: }
720: }
721:
722: int from = dataBlock.getFrom();
723: DataBlock db = new DataBlock(from + index, from + size, bytes,
724: dataBlock.getName());
725: db.setIsFileBlock(dataBlock.isFileBlock());
726: db.setFileName(dataBlock.getFileName());
727: return db;
728: }
729:
730: /**
731: * @param dataBlock DataBlock of which content-type is required.
732: * @return String containing content-type of data block if data block is a file
733: * block or an empty String
734: */
735: private String getFileContentType(DataBlock dataBlock) {
736: return dataBlock.getContentType();
737: }
738:
739: /**
740: * @param dataBlock DataBlock of which content-type is required.
741: * @return String containing file name of data block if data block is a file
742: * block or an empty String
743: */
744: private String getFileName(DataBlock dataBlock) {
745: return dataBlock.getFileName();
746: }
747:
748: /**
749: * Retrieves the file name contained in the given DataBlock object.
750: * @param dataBlock dataBlock of which file name is requested.
751: * @return String containing file name extracted from DataBlock
752: * with given name
753: */
754: private String getShortFileName(DataBlock dataBlock) {
755: String name = dataBlock.getFileName();
756: if (name == null) {
757: return "";
758: }
759: int index = name.lastIndexOf("/");
760: if (index == -1) {
761: index = name.lastIndexOf("\\");
762: if (index == -1) {
763: return name;
764: }
765: }
766:
767: if (index == name.length() - 1) {
768: return "";
769: }
770:
771: return name.substring(index + 1, name.length());
772: }
773:
774: }
|