001: /***
002: * jwma Java WebMail
003: * Copyright (c) 2000-2003 jwma team
004: *
005: * jwma is free software; you can distribute and use this source
006: * under the terms of the BSD-style license received along with
007: * the distribution.
008: ***/package dtw.webmail.util;
009:
010: import java.io.*;
011: import java.util.Hashtable;
012: import javax.mail.*;
013: import javax.mail.internet.*;
014: import javax.activation.*;
015:
016: import org.apache.log4j.Logger;
017:
018: //import dtw.webmail.JwmaKernel;
019:
020: /**
021: * Class that implements a Multipart that handles
022: * the <tt>multipart/form-data</tt> content type.
023: *
024: * @author Dieter Wimberger
025: * @version 0.9.7 07/02/2003
026: */
027: public class FormdataMultipart extends MimeMultipart {
028:
029: //logging
030: private static Logger log = Logger
031: .getLogger(FormdataMultipart.class);
032:
033: //instance attributes
034: private Hashtable m_Params = new Hashtable();
035: private boolean m_Removed = false;
036:
037: /**
038: * Constructs a <tt>FormdataMultipart</tt> instance.<br>
039: * This implementation just calls the superclass constructor.
040: *
041: * @return the newly created <tt>FormdataMultipart</tt> instance.
042: */
043: public FormdataMultipart() {
044: super ();
045: }//constructor
046:
047: /**
048: * Constructs a <tt>FormdataMultipart</tt> instance.<br>
049: * Automatically processes the body parts to extract parameters,
050: * and attachments.
051: *
052: * @param DataSource to construct the Multipart from, will be a
053: * <tt>MultipartInputStream</tt>.
054: *
055: * @return the newly created <tt>FormdataMultipart</tt> instance.
056: */
057: public FormdataMultipart(DataSource ds) throws MessagingException,
058: IOException {
059:
060: super (ds);
061: processBodyParts();
062: updateHeaders();
063: }//constructor
064:
065: /**
066: * Returns the extracted parameters (with the extrcted values)
067: * as <tt>Hashtable</tt>.
068: *
069: * @return the extracted parameter data as <tt>Hashtable</tt>
070: */
071: public Hashtable getParameters() {
072: return m_Params;
073: }//getParameters
074:
075: /**
076: * Processes the body parts of the form-data.
077: * Extracts parameters and set values, and
078: * leaves over the attachments.
079: *
080: * @throws IOException if i/o operations fail.
081: * @throws MessagingException if parsing or part handling with
082: * Mail API classes fails.
083: */
084: private void processBodyParts() throws IOException,
085: MessagingException {
086:
087: //if write out to log for debug reasons!
088: //ByteArrayOutputStream bout=new ByteArrayOutputStream();
089: //writeTo(bout);
090: //JwmaKernel.getReference().debugLog().write(bout.toString());
091:
092: for (int i = 0; i < getCount(); i++) {
093: MimeBodyPart mbp = (MimeBodyPart) getBodyPart(i);
094: processBodyPart(mbp);
095: if (m_Removed) {
096: m_Removed = false;
097: //decrease index i approbiately
098: i--;
099: }
100: }
101:
102: setSubType("mixed");
103: //JwmaKernel.getReference().debugLog().write(
104: // "Processed multipart/form-data. Attachment parts:"+getCount()
105: //);
106: }//processParts
107:
108: /**
109: * Processes a body part of the form-data.
110: * Extracts parameters and set values, and
111: * leaves over the attachments.
112: *
113: * @param mbp the <tt>MimeBodyPart</tt> to be processed.
114: *
115: * @throws IOException if i/o operations fail.
116: * @throws MessagingException if parsing or part handling with
117: * Mail API classes fails.
118: */
119: private void processBodyPart(MimeBodyPart mbp)
120: throws MessagingException, IOException {
121:
122: //String contenttype=new String(mbp.getContentType());
123: //JwmaKernel.getReference().debugLog().write("Processing "+contenttype);
124:
125: //check if a content-type is given
126: String[] cts = mbp.getHeader("Content-Type");
127: if (cts == null || cts.length == 0) {
128: //this is a parameter, get it out and
129: //remove the part.
130: String controlname = extractName((mbp
131: .getHeader("Content-Disposition"))[0]);
132:
133: //JwmaKernel.getReference().debugLog().write("Processing control:"+controlname);
134: //retrieve value observing encoding
135: InputStream in = mbp.getInputStream();
136: String[] encoding = mbp
137: .getHeader("Content-Transfer-Encoding");
138: if (encoding != null && encoding.length > 0) {
139: in = MimeUtility.decode(in, encoding[0]);
140: }
141:
142: String value = extractValue(in);
143: if (value != null || !value.trim().equals("")) {
144: addParameter(controlname, value);
145: }
146: //flag removal
147: m_Removed = true;
148: removeBodyPart(mbp);
149: } else {
150: String filename = extractFileName((mbp
151: .getHeader("Content-Disposition"))[0]);
152:
153: //normally without file the control should be not successful.
154: //but neither netscape nor mircosoft iexploder care much.
155: //the only feature is an empty filename.
156: if (filename.equals("")) {
157: //kick it out too
158: m_Removed = true;
159: removeBodyPart(mbp);
160: } else {
161:
162: //JwmaKernel.getReference().debugLog().write("Incoming filename="+filename);
163:
164: //IExploder sends files with complete path.
165: //jwma doesnt want this.
166: int lastindex = filename.lastIndexOf("\\");
167: if (lastindex != -1) {
168: filename = filename.substring(lastindex + 1,
169: filename.length());
170: }
171:
172: //JwmaKernel.getReference().debugLog().write("Outgoing filename="+filename);
173:
174: //Observe a possible encoding
175: InputStream in = mbp.getInputStream();
176: String[] encoding = mbp
177: .getHeader("Content-Transfer-Encoding");
178: if (encoding != null && encoding.length > 0) {
179: in = MimeUtility.decode(in, encoding[0]);
180: }
181: ByteArrayOutputStream bout = new ByteArrayOutputStream();
182: OutputStream out = (OutputStream) bout;
183:
184: int i = 0;
185: while ((i = in.read()) != -1) {
186: //maybe more efficient in buffers, but well
187: out.write(i);
188: }
189: out.flush();
190: out.close();
191:
192: //create the datasource
193: MimeBodyPartDataSource mbpds = new MimeBodyPartDataSource(
194: // contenttype,filename,bout.toByteArray()
195: cts[0], filename, bout.toByteArray());
196:
197: //Re-set the Content-Disposition header, in case
198: //the file name was changed
199: mbp.removeHeader("Content-Disposition");
200: mbp.addHeader("Content-Disposition",
201: "attachment; filename=\"" + filename + "\"");
202:
203: //set a base64 transferencoding und the data handler
204: mbp.addHeader("Content-Transfer-Encoding", "base64");
205: mbp.setDataHandler(new DataHandler(mbpds));
206: }
207: }
208: }//processBodyPart
209:
210: /**
211: * Returns the name of a parameter by extracting it
212: * from the content-disposition header line.
213: *
214: * @param disposition the content-disposition header line as
215: * <tt>String</tt>.
216: *
217: * @return the name of the parameter as <tt>String</tt>.
218: *
219: * @throws IOException if the header line is malformed.
220: */
221: private String extractName(String disposition) throws IOException {
222:
223: int end = 0;
224: int start = -1;
225:
226: start = disposition.indexOf("name=\"");
227: end = disposition.indexOf("\"", start + 7); //offset is to skip name=\"
228: if (start == -1 || end == -1) {
229: throw new IOException("Mime header malformed.");
230: }
231: return disposition.substring(start + 6, end);
232: }//extractName
233:
234: /**
235: * Returns the filename of an attachment by extracting it
236: * from the content-disposition header line.
237: *
238: * @param disposition the content-disposition header line as
239: * <tt>String</tt>.
240: *
241: * @return the filename of the attachment as <tt>String</tt>.
242: *
243: * @throws IOException if the header line is malformed.
244: */
245: private String extractFileName(String disposition)
246: throws IOException {
247:
248: int end = 0;
249: int start = -1;
250:
251: start = disposition.indexOf("filename=\"");
252: end = disposition.indexOf("\"", start + 10); //offset is to skip filename=\"
253: if (start == -1 || end == -1) {
254: throw new IOException("Mime header malformed.");
255: }
256: return disposition.substring(start + 10, end);
257: }//extractFileName
258:
259: /**
260: * Returns the value of a parameter by extracting it
261: * from the <tt>InputStream</tt> that represents the content
262: * of the (parameter) part.
263: *
264: * @param in <tt>InputStream</tt> that reads from the content
265: * of the (parameter) part.
266: *
267: * @return the value of the parameter as <tt>String</tt>.
268: *
269: * @throws IOException if reading from the stream fails.
270: */
271: private String extractValue(InputStream in) throws IOException {
272:
273: ByteArrayOutputStream out = new ByteArrayOutputStream();
274: int i = 0;
275: while ((i = in.read()) != -1) {
276: out.write(i);
277: }
278: out.flush();
279: out.close();
280: in.close();
281:
282: //JwmaKernel.getReference().debugLog().write("Retrieved value="+out.toString());
283: //apply a little bit of magic when returning
284: return out.toString("iso-8859-1");
285: }//extractValue
286:
287: /**
288: * Adds a parameter and mapped value to the parameters collection.
289: * If the parameter already exists, it adds another value to
290: * an already existing parameter by extending the array of strings.
291: *
292: * @param name the name of the parameter as <tt>String</tt>.
293: * @param value the value of the parameter as <tt>String</tt>.
294: */
295: private void addParameter(String name, String value) {
296: String values[];
297:
298: //JwmaKernel.getReference().debugLog().write("Adding "+name+"="+value);
299:
300: if (m_Params.containsKey(name)) {
301: String oldValues[] = (String[]) m_Params.get(name);
302: values = new String[oldValues.length + 1];
303: for (int i = 0; i < oldValues.length; i++) {
304: values[i] = oldValues[i];
305: }
306: values[oldValues.length] = value;
307: } else {
308: values = new String[1];
309: values[0] = value;
310: }
311: m_Params.put(name, values);
312: }//addParameter
313:
314: }//FormdataMultipart
|