001: /*
002: * This file is part of PFIXCORE.
003: *
004: * PFIXCORE is free software; you can redistribute it and/or modify
005: * it under the terms of the GNU Lesser General Public License as published by
006: * the Free Software Foundation; either version 2 of the License, or
007: * (at your option) any later version.
008: *
009: * PFIXCORE is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
012: * GNU Lesser General Public License for more details.
013: *
014: * You should have received a copy of the GNU Lesser General Public License
015: * along with PFIXCORE; if not, write to the Free Software
016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: *
018: */
019:
020: package de.schlund.pfixxml.multipart;
021:
022: import java.io.BufferedOutputStream;
023: import java.io.File;
024: import java.io.FileOutputStream;
025: import java.io.IOException;
026: import java.io.InputStream;
027: import java.io.InputStreamReader;
028: import java.io.OutputStream;
029: import java.io.UnsupportedEncodingException;
030: import java.text.DecimalFormat;
031: import java.util.ArrayList;
032: import java.util.Collections;
033: import java.util.Date;
034: import java.util.Enumeration;
035: import java.util.HashMap;
036: import java.util.List;
037:
038: import javax.mail.MessagingException;
039: import javax.mail.internet.ContentType;
040: import javax.mail.internet.MailDateFormat;
041: import javax.mail.internet.MimeUtility;
042: import javax.servlet.GenericServlet;
043: import javax.servlet.ServletInputStream;
044: import javax.servlet.http.HttpServletRequest;
045:
046: import org.apache.log4j.Logger;
047: import org.apache.oro.text.perl.Perl5Util;
048:
049: import de.schlund.pfixxml.AbstractXMLServlet;
050:
051: /**
052: *
053: *
054: */
055:
056: public class MultipartHandler {
057:
058: public static final String CTYPE_HEADER = "Content-Type";
059: public static final String MULTI_FORM_DATA = "multipart/form-data";
060: public static final String PARAM_BOUNDARY = "boundary";
061: public static final String DEFAULT_CHARSET = "iso-8859-1";
062: public static final String DEFAULT_TRANS_ENC = "7bit";
063: public static final String DEFAULT_CTYPE = "text/plain; charset="
064: + DEFAULT_CHARSET;
065: public static final String CHARSET_PARAM = "charset";
066: public static final String CONTENT_DISP_HEADER = "Content-Disposition";
067: public static final String CONTENT_TRANS_ENC_HEADER = "Content-Transfer-Encoding";
068: public static final String NAME_PARAM = "name";
069: public static final String FILENAME_PARAM = "filename";
070: public static final String CREATION_DATE_PARAM = "creation-date";
071: public static final String MODIFICATION_DATE_PARAM = "modification-date";
072: public static final String READ_DATE_PARAM = "read-date";
073: public static final String SIZE_PARAM = "size";
074: public static final String FNAME_PATTERN = "00000000";
075:
076: private final static Object lock = new Object();
077: private final static DecimalFormat format = new DecimalFormat(
078: FNAME_PATTERN);
079:
080: private HttpServletRequest req = null;
081: private String dir = null;
082: private File dirFile = null;
083: private HashMap<String, ArrayList<PartData>> parameter = null;
084: private GenericServlet logBase = null;
085: private ArrayList<FileData> fileuploads = null;
086: private List<Exception> failedParts = null;
087: private long maxPartSize = -1;
088:
089: private final static Logger LOG = Logger
090: .getLogger(MultipartHandler.class);
091:
092: protected static File getDestFile(File dir, String fName)
093: throws IOException {
094: TempFile rc = null;
095: synchronized (lock) {
096: StringBuffer buf = new StringBuffer(fName);
097: buf.append('-');
098: long curSec = new Date().getTime();
099: buf.append(curSec).append(FNAME_PATTERN);
100: rc = new TempFile(dir, buf.toString());
101: if (rc.exists()) {
102: long count = 1;
103: String base = buf.substring(0, buf.length()
104: - FNAME_PATTERN.length());
105: while (rc.exists()) {
106: rc = new TempFile(dir, base
107: + format.format(count++));
108: }
109: }
110: rc.createNewFile();
111: }
112: return rc;
113: }
114:
115: public MultipartHandler(HttpServletRequest pReq, String pDir)
116: throws IllegalArgumentException {
117: this (null, pReq, pDir);
118: }
119:
120: /**
121: * Constructor for MulitpartHandler.
122: */
123: public MultipartHandler(GenericServlet base,
124: HttpServletRequest pReq, String pDir)
125: throws IllegalArgumentException {
126: logBase = base;
127: req = pReq;
128: dir = pDir;
129: if (req == null) {
130: throw new IllegalArgumentException(
131: "Got no request req == null.");
132: }
133:
134: if (!req.getContentType().toLowerCase().startsWith(
135: MULTI_FORM_DATA)) {
136: throw new IllegalArgumentException(
137: "Request is not a multipart/form-data");
138: }
139:
140: if (dir == null) {
141: dir = System
142: .getProperty(AbstractXMLServlet.DEF_PROP_TMPDIR);
143: }
144: dirFile = new File(dir);
145: if (!dirFile.isDirectory()) {
146: throw new IllegalArgumentException("File '" + dir
147: + "' isn't a directory");
148: }
149: if (!dirFile.canWrite()) {
150: throw new IllegalArgumentException("Can't write to dir '"
151: + dir + "'.");
152: }
153: parameter = new HashMap<String, ArrayList<PartData>>();
154: fileuploads = new ArrayList<FileData>();
155: failedParts = new ArrayList<Exception>();
156: }
157:
158: public Enumeration<String> getParameterNames() {
159: return Collections.enumeration(parameter.keySet());
160: }
161:
162: public PartData getParameter(String name) {
163: PartData rc = null;
164: List<PartData> list = parameter.get(name);
165: if (list != null && 0 < list.size()) {
166: rc = list.get(0);
167: }
168: return rc;
169: }
170:
171: public List<PartData> getAllParameter(String name) {
172: return parameter.get(name);
173: }
174:
175: public List<FileData> getFileUploads() {
176: return fileuploads;
177: }
178:
179: public List<Exception> getExceptionList() {
180: return failedParts;
181: }
182:
183: public void parseRequest() throws IllegalArgumentException,
184: MessagingException, IOException {
185: String baseCType = req.getHeader(CTYPE_HEADER);
186: printDebug("******** Content-Type is: '" + baseCType + "'");
187: ContentType cType = new ContentType(baseCType);
188: String boundary = cType.getParameter(PARAM_BOUNDARY);
189: printDebug("******** Boundary is: '" + boundary + "'");
190: if (boundary == null || boundary.length() <= 0)
191: return;
192:
193: ServletInputStream inS = req.getInputStream();
194:
195: MultipartStream ms = new MultipartStream(inS);
196: if (0 < maxPartSize)
197: ms.setMaxPartSize(maxPartSize);
198:
199: ms.setBoundary(boundary);
200: ms.skipBoundary();
201: boolean doLoop = true;
202: RFC822Headers curHeaders = null;
203: ContentType curCt = null;
204: String curCtStr = null;
205: String[] headers = null;
206: do {
207: printDebug("\n");
208: curHeaders = new RFC822Headers(ms, req
209: .getCharacterEncoding());
210:
211: headers = curHeaders.getHeader(CTYPE_HEADER);
212: if (headers != null && 0 < headers.length) {
213: curCtStr = headers[0];
214: curCt = new ContentType(curCtStr);
215: } else {
216: curCt = new ContentType("text/plain; charset="
217: + req.getCharacterEncoding());
218: }
219:
220: boolean isfile = false;
221: HeaderStruct struct = getHeaderStruct(curHeaders,
222: CONTENT_DISP_HEADER);
223: if (struct != null) {
224: String filename = struct.getParam(FILENAME_PARAM);
225: if (filename != null) {
226: isfile = true;
227: //System.out.println("File============"+filename);
228: }
229: }
230: printDebug("*** IS FILE? " + isfile);
231:
232: if (curCt.match("text/*") && !isfile) {
233: printDebug("parsing text field (" + curCt + ")");
234: parseTextField(ms, curHeaders, curCt);
235: } else if (curCt.match("multipart/mixed")) {
236: printDebug("parsing multipart (" + curCt + ")");
237: parseMultiFile(ms, curHeaders, curCt, cType);
238: } else {
239: printDebug("parsing file (" + curCt + ")");
240: parseFile(ms, curHeaders, curCt, null);
241: }
242:
243: if (!ms.isEndOfMultipart()) {
244: printDebug("skipping boundary");
245: ms.skipBoundary();
246: } else {
247: printDebug("end of multipart");
248: doLoop = false;
249: }
250: } while (doLoop);
251: }
252:
253: protected void parseTextField(MultipartStream ms,
254: RFC822Headers headers, ContentType ct)
255: throws MessagingException, IOException {
256: String charEnc = ct.getParameter(CHARSET_PARAM);
257: if (charEnc == null) {
258: charEnc = DEFAULT_CHARSET;
259: }
260: printDebug("charEnc: " + charEnc);
261: HeaderStruct cdStruct = getHeaderStruct(headers,
262: CONTENT_DISP_HEADER);
263: HeaderStruct cteStruct = getHeaderStruct(headers,
264: CONTENT_TRANS_ENC_HEADER);
265: String transEnc = cteStruct.getValue();
266: if (transEnc == null) {
267: transEnc = DEFAULT_TRANS_ENC;
268: }
269: printDebug("transEnc: " + transEnc);
270: String fieldName = cdStruct.getParam(NAME_PARAM);
271: printDebug("fieldname: " + fieldName);
272: if (fieldName == null)
273: return;
274:
275: InputStream dataIn = MimeUtility.decode(ms, transEnc);
276: InputStreamReader inReader = new InputStreamReader(dataIn,
277: charEnc);
278: int tmpVal = -1;
279: StringBuffer tmpBuf = new StringBuffer();
280: PartToLongException ex = null;
281: try {
282: do {
283: tmpVal = inReader.read();
284: if (tmpVal != -1) {
285: tmpBuf.append((char) tmpVal);
286: }
287: } while (tmpVal != -1);
288: } catch (PartToLongException e) {
289: ex = e;
290: }
291: if (ex == null) {
292: FieldData fPart = new FieldData();
293: fPart.setFieldname(fieldName);
294: fPart.setPrimaryType(ct.getPrimaryType());
295: fPart.setSubType(ct.getSubType());
296: fPart.setCharacterset(charEnc);
297: fPart.setTransferEncoding(transEnc);
298: fPart.setValue(tmpBuf.toString());
299:
300: addParameterPart(fPart);
301: } else {
302: ex.setFieldName(fieldName);
303: failedParts.add(ex);
304: }
305: }
306:
307: protected void addParameterPart(PartData part) {
308: try {
309: String fieldName = part.getFieldname();
310: ArrayList<PartData> params = (ArrayList<PartData>) parameter
311: .get(fieldName);
312: if (params == null) {
313: params = new ArrayList<PartData>();
314: parameter.put(fieldName, params);
315: }
316: params.add(part);
317: } catch (NullPointerException e) {
318: LOG.error(e, e);
319: }
320: }
321:
322: protected void parseMultiFile(MultipartStream ms,
323: RFC822Headers headers, ContentType ct, ContentType parentCt)
324: throws MessagingException, IOException {
325: String localBoundary = ct.getParameter(PARAM_BOUNDARY);
326: ms.setBoundary(localBoundary);
327: ms.skipBoundary();
328: RFC822Headers localHeaders = null;
329: ContentType tmpCt = null;
330: String[] tmpHeaders = null;
331: HeaderStruct cdStruct = getHeaderStruct(headers,
332: CONTENT_DISP_HEADER);
333: String fieldName = cdStruct.getParam(NAME_PARAM);
334: if (fieldName != null && fieldName.length() <= 0)
335: fieldName = null;
336: while (!ms.isEndOfMultipart()) {
337: localHeaders = new RFC822Headers(ms, req
338: .getCharacterEncoding());
339: tmpHeaders = localHeaders.getHeader(CTYPE_HEADER);
340: if (tmpHeaders != null && 0 < tmpHeaders.length) {
341: tmpCt = new ContentType(tmpHeaders[0]);
342: parseFile(ms, localHeaders, tmpCt, fieldName);
343: }
344: if (!ms.isEndOfMultipart())
345: ms.skipBoundary();
346: }
347: ms.setBoundary(localBoundary);
348: }
349:
350: protected void parseFile(MultipartStream ms, RFC822Headers headers,
351: ContentType ct, String defFieldName)
352: throws MessagingException, IOException {
353: HeaderStruct cdStruct = getHeaderStruct(headers,
354: CONTENT_DISP_HEADER);
355: HeaderStruct cteStruct = getHeaderStruct(headers,
356: CONTENT_TRANS_ENC_HEADER);
357: String transEnc = cteStruct.getValue();
358: if (transEnc == null) {
359: transEnc = DEFAULT_TRANS_ENC;
360: }
361: String fieldName = null;
362: if (defFieldName != null) {
363: fieldName = defFieldName;
364: }
365: if (fieldName == null) {
366: fieldName = cdStruct.getParam(NAME_PARAM);
367: }
368: if (fieldName == null)
369: return;
370: File localFile = null;
371: String filename = cdStruct.getParam(FILENAME_PARAM);
372: PartToLongException ex = null;
373: if (filename != null && 0 < filename.length()) {
374: localFile = getDestFile(dirFile, filename);
375: InputStream dataIn = MimeUtility.decode(ms, transEnc);
376: OutputStream dataOut = new BufferedOutputStream(
377: new FileOutputStream(localFile));
378: try {
379: int tmpVal = -1;
380: do {
381: tmpVal = dataIn.read();
382: if (tmpVal != -1) {
383: dataOut.write(tmpVal);
384: }
385: } while (tmpVal != -1);
386: } catch (PartToLongException e) {
387: ex = e;
388: } finally {
389: dataOut.flush();
390: dataOut.close();
391: }
392: }
393:
394: if (localFile != null) {
395: FileData fileP = new FileData();
396: fileP.setPrimaryType(ct.getPrimaryType());
397: fileP.setSubType(ct.getSubType());
398: fileP.setFieldname(fieldName);
399: fileP.setFilename(filename);
400: fileP.setTransferEncoding(transEnc);
401: fileP.setModificationDate(getDateFromParam(cdStruct
402: .getParam(MODIFICATION_DATE_PARAM)));
403: fileP.setReadDate(getDateFromParam(cdStruct
404: .getParam(READ_DATE_PARAM)));
405: if (ex == null) {
406: fileP.setLocalFilename(localFile.getAbsolutePath());
407: fileP.setSize(localFile.length());
408: fileP.setLocalFile(localFile);
409: fileuploads.add(fileP);
410: addParameterPart(fileP);
411: } else {
412: //delete file resulting from part exceeding the size limit
413: if (localFile != null && localFile.exists())
414: localFile.delete();
415: ex.setFieldName(fieldName);
416: failedParts.add(ex);
417: //mark as exceeded
418: fileP.setExceedsSizeLimit(true);
419: fileP.setLocalFilename("");
420: fileP.setSize(0);
421: addParameterPart(fileP);
422: }
423: }
424:
425: }
426:
427: protected Date getDateFromParam(String inStr) {
428: Date rc = null;
429: try {
430: if (inStr != null && 0 < inStr.length()) {
431: MailDateFormat formatter = new MailDateFormat();
432: rc = formatter.parse(inStr);
433: }
434: } catch (java.text.ParseException e) {
435: rc = null;
436: }
437: return rc;
438: }
439:
440: protected HeaderStruct getHeaderStruct(RFC822Headers headers,
441: String name) {
442: HeaderStruct rc = null;
443: if (name != null) {
444: rc = new HeaderStruct();
445: rc.setName(name);
446: try {
447: String[] valArr = headers.getHeader(name);
448: if (valArr != null && 0 < valArr.length) {
449: if (name.equals("Content-Disposition")) {
450: //System.out.println("val before:"+valArr[0]);
451: valArr[0] = removeIEDirtyPath(valArr[0]);
452: //System.out.println("val after :"+valArr[0]);
453: }
454: String value = MimeUtility.decodeText(valArr[0]);
455: int idx = value.indexOf(';');
456: if (0 <= idx) {
457: rc.setValue(value.substring(0, idx).trim());
458:
459: rc.initParams(value.substring(idx));
460: } else {
461: rc.setValue(value.trim());
462: }
463: }
464: } catch (UnsupportedEncodingException e) {
465: rc.resetParams();
466: } catch (IndexOutOfBoundsException e) {
467: rc.resetParams();
468: }
469: }
470: return rc;
471: }
472:
473: protected void printDebug(String msg) {
474: try {
475: if (LOG != null) {
476: LOG.debug(msg);
477: } else if (logBase != null) {
478: logBase.log("[DEBUG] " + msg);
479: }
480: } catch (Exception e) {
481: }
482: }
483:
484: /**
485: * Gets the maxPartSize.
486: * @return Returns a long
487: */
488: public long getMaxPartSize() {
489: return maxPartSize;
490: }
491:
492: /**
493: * Sets the maxPartSize.
494: * @param maxPartSize The maxPartSize to set
495: */
496: public void setMaxPartSize(long maxPartSize) {
497: this .maxPartSize = maxPartSize;
498: }
499:
500: private String removeIEDirtyPath(String str) {
501: Perl5Util perl = new Perl5Util();
502: String ret = str;
503:
504: int fnindex = str.indexOf("filename");
505: if (fnindex != -1) {
506: int blindex = str.indexOf("\\");
507: if (blindex > -1 && blindex > fnindex) {
508: perl.match("/filename=\"(.*[^\"])\"/", str);
509: String fullpath = "";
510: fullpath = perl.group(1);
511:
512: int index = fullpath.lastIndexOf("\\");
513: if (index != -1) {
514: String file = "";
515: file = fullpath.substring(index + 1);
516: file = "\"" + file + "\"";
517: ret = perl.substitute(
518: "s/filename=\"(.*[^\"])\"/filename=" + file
519: + "/", str);
520: }
521: }
522: }
523: return ret;
524: }
525:
526: }
|