KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > protomatter > util > MIMEMessage


1 package com.protomatter.util;
2
3 /**
4  * {{{ The Protomatter Software License, Version 1.0
5  * derived from The Apache Software License, Version 1.1
6  *
7  * Copyright (c) 1998-2002 Nate Sammons. All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  *
13  * 1. Redistributions of source code must retain the above copyright
14  * notice, this list of conditions and the following disclaimer.
15  *
16  * 2. Redistributions in binary form must reproduce the above copyright
17  * notice, this list of conditions and the following disclaimer in
18  * the documentation and/or other materials provided with the
19  * distribution.
20  *
21  * 3. The end-user documentation included with the redistribution,
22  * if any, must include the following acknowledgment:
23  * "This product includes software developed for the
24  * Protomatter Software Project
25  * (http://protomatter.sourceforge.net/)."
26  * Alternately, this acknowledgment may appear in the software itself,
27  * if and wherever such third-party acknowledgments normally appear.
28  *
29  * 4. The names "Protomatter" and "Protomatter Software Project" must
30  * not be used to endorse or promote products derived from this
31  * software without prior written permission. For written
32  * permission, please contact support@protomatter.com.
33  *
34  * 5. Products derived from this software may not be called "Protomatter",
35  * nor may "Protomatter" appear in their name, without prior written
36  * permission of the Protomatter Software Project
37  * (support@protomatter.com).
38  *
39  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
40  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
41  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
42  * DISCLAIMED. IN NO EVENT SHALL THE PROTOMATTER SOFTWARE PROJECT OR
43  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
44  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
45  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
46  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
47  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
48  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
49  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
50  * SUCH DAMAGE. }}}
51  */

52
53 import java.io.*;
54 import java.util.*;
55 import java.text.*;
56
57 /**
58  * A MIME encoded message.
59  * This is basically a collection of MIMEAttachment objects. This
60  * class takes care of the ASCII encoding of the message as a whole,
61  * including the segment boundary, etc... It does <b><i>NOT</i></b>
62  * take care of any headers other than the Content-Type, which it
63  * always identifies as "MULTIPART/MIXED".
64  * This class can also be used to parse "file upload" information
65  * out of HTML forms.
66  *
67  * @see MIMEAttachment
68  */

69 public class MIMEMessage
70 implements Serializable
71 {
72   private Vector attachments;
73   private String JavaDoc boundary;
74   private static String JavaDoc CRLF = "\r\n";
75
76   /**
77    * Initialize the MIMEMessage.
78    */

79   public MIMEMessage()
80   {
81     attachments = new Vector();
82     boundary = "--------------74329329-84328432-279-4382"; // some gibberish
83
}
84
85   /**
86    * Get the Content-Type of this message, also includes the boundary.
87    */

88   public String JavaDoc getContentType()
89   {
90     return "MULTIPART/MIXED; BOUNDARY=\"" + boundary + "\"";
91   }
92
93   /**
94    * Add an attachment to this message
95    */

96   public void addAttachment(MIMEAttachment a)
97   {
98     attachments.addElement(a);
99   }
100
101   /**
102    * Remove an attachment to this message
103    */

104   public void removeAttachment(MIMEAttachment a)
105   {
106     attachments.removeElement(a);
107   }
108
109   /**
110    * Get an enumeration of the attachments to this message.
111    */

112   public Enumeration getAttachments()
113   {
114     return attachments.elements();
115   }
116
117   /**
118    * Get the boundary between parts of the message.
119    */

120   public String JavaDoc getBoundary()
121   {
122     return boundary;
123   }
124
125   /**
126    * Set the boundary between parts of the message.
127    */

128   public void setBoundary(String JavaDoc boundary)
129   {
130     this.boundary = boundary;
131   }
132
133   /**
134    * Return the encoded message (including all attachments)
135    */

136   public String JavaDoc toString()
137   {
138     StringWriter sw = new StringWriter();
139     PrintWriter pw = new PrintWriter(sw);
140     write(pw);
141     pw.flush();
142     return sw.toString();
143   }
144
145   /**
146    * Write this message to the given output stream.
147    */

148   public void write(PrintWriter w)
149   {
150     Enumeration e = getAttachments();
151     while (e.hasMoreElements())
152     {
153       MIMEAttachment a = (MIMEAttachment)e.nextElement();
154       w.print("--");
155       w.print(boundary);
156       w.print(CRLF);
157       a.write(w);
158       w.print(CRLF);
159     }
160     w.print("--");
161     w.print(boundary);
162     w.print("--");
163     w.print(CRLF);
164   }
165
166   /**
167    * Return a MIMEMessage built from the InputStream that
168    * points to a MIME message. Reads the stream fully
169    * before parsing, so watch out.
170    */

171   public static MIMEMessage parse(InputStream s)
172   throws MIMEException
173   {
174     byte[] data = null;
175     try
176     {
177       data = readInputStreamFully(s);
178     }
179     catch (Exception JavaDoc x)
180     {
181       throw new MIMEException(MessageFormat.format(
182         UtilResources.getResourceString(MessageConstants.MIME_EXCEPTION_IN_INPUT),
183         new Object JavaDoc[] { x.toString() }));
184     }
185     return parse(data);
186   }
187
188   /**
189    * Return a MIMEMessage built from the data.
190    */

191   public static MIMEMessage parse(byte data[])
192   throws MIMEException
193   {
194     try
195     {
196     MIMEMessage message = new MIMEMessage();
197
198     // set up a vector for passing a second argument back out of methods.
199
// v holds the new current index after calls to readLine() and
200
// readBody()
201
int index = 0;
202     int endIndex = data.length -1;
203
204     // MSIE 4.0Bsomething puts in extra whitespace at the front of
205
// the file, which does not conform to the specification!
206
// In the immortal words of Charlton Heston "Damn You! Damn You!"
207
// also clip whitespace at the end of the message.
208
try
209     {
210       while (Character.isWhitespace((char)data[index])) ++index;
211       while (Character.isWhitespace((char)data[endIndex])) --endIndex;
212       endIndex++;
213     }
214     catch (Exception JavaDoc x)
215     {
216       throw new MIMEException(UtilResources.getResourceString(MessageConstants.MIME_ALL_WHITESPACE));
217     }
218
219     // this vector contains the begin and end indexes of the data. I didn't
220
// truncate the data, since it's a waste of time and memory.
221
Vector v = new Vector(2);
222     v.addElement(new Integer JavaDoc(index));
223     v.addElement(new Integer JavaDoc(endIndex));
224
225     // first line is the separator
226
String JavaDoc sep = null;
227     try
228     {
229       sep = readLine(data, v);
230     }
231     catch (Exception JavaDoc x)
232     {
233       throw new MIMEException(MessageFormat.format(
234         UtilResources.getResourceString(MessageConstants.MIME_EXCEPTION_IN_SEPARATOR),
235         new Object JavaDoc[] { x.toString() }));
236     }
237     if (sep == null)
238     {
239       throw new MIMEException(UtilResources.getResourceString(MessageConstants.MIME_SEPARATOR_NOT_FOUND));
240     }
241
242     try
243     {
244       while (index < endIndex)
245       {
246         // read headers
247
String JavaDoc line = "x";
248         Hashtable headers = new Hashtable();
249         line = readLine(data, v);
250         while (!line.equals("")) // headers are separated from body by a blank line
251
{
252           // header looks like "name: value"
253
int cIndex = line.indexOf(":");
254           if (cIndex != -1) // dodge MSIE lameness.
255
{
256             headers.put(line.substring(0, cIndex), line.substring(cIndex +2));
257           }
258           line = readLine(data, v);
259         }
260
261         // read content
262
StringBuffer JavaDoc info = new StringBuffer JavaDoc(); // either "ascii" or "binary"
263
byte[] content = readBody(sep, data, info, v);
264
265         // add the attachment to the list.
266
if (content != null)
267         {
268           MIMEAttachment a = new MIMEAttachment();
269           //a.setBinary(info.toString().equals("binary"));
270
a.setHeaders(headers); // set headers in the attachment.
271
String JavaDoc encoding = a.getHeader("Content-Transfer-Encoding");
272           if (encoding != null && encoding.equalsIgnoreCase("BASE64"))
273           {
274             byte[] c = Base64.decode(removeWhitespace(content));
275             a.setContent(c);
276             a.setBinary(isBinaryContent(c));
277           }
278           else
279           {
280             a.setContent(content);
281             a.setBinary(isBinaryContent(content));
282           }
283           message.addAttachment(a); // now add the attachment.
284
}
285         else
286         {
287           return message;
288         }
289         index = getIndex(v);
290       }
291     }
292     catch (Exception JavaDoc x)
293     {
294       ; // done reading... not a problem.
295
}
296     return message;
297     }
298     catch (Exception JavaDoc x)
299     {
300       throw new MIMEException(MessageFormat.format(
301         UtilResources.getResourceString(MessageConstants.MIME_EXCEPTION_IN_PARSE),
302         new Object JavaDoc[] { x.toString() }));
303     }
304   }
305
306   // these should get inlined when -O is used to compile
307
private static final int getIndex(Vector v)
308   {
309     return ((Integer JavaDoc)v.firstElement()).intValue();
310   }
311   private static final int getEndIndex(Vector v)
312   {
313     return ((Integer JavaDoc)v.elementAt(1)).intValue();
314   }
315   private static final void setIndex(Vector v, int i)
316   {
317     v.setElementAt(new Integer JavaDoc(i), 0);
318   }
319   private static final void setEndIndex(Vector v, int i)
320   {
321     v.setElementAt(new Integer JavaDoc(i), 1);
322   }
323
324   // reads a line of text. The end can be any of:
325
// CR, LF, CRLF Sets index in v to be the first char
326
// *AFTER* the end of the line marker. Text returned does
327
// *NOT* include the end of line marker.
328
private final static String JavaDoc readLine(byte[] data, Vector v)
329   throws Exception JavaDoc
330   {
331     int index = getIndex(v);
332     int endIndex = getEndIndex(v);
333     if (index == endIndex) return new String JavaDoc();
334     int c;
335     ByteArrayOutputStream b = new ByteArrayOutputStream();
336     while (index < endIndex)
337     {
338       c = (int)data[index];
339       if (isLF(c)) // "\n"
340
{
341         index++;
342         setIndex(v, index);
343         return new String JavaDoc(b.toByteArray());
344       }
345       if (isCR(c)) // "\r"
346
{
347         index++;
348         if (isLF((int)data[index])) ++index;
349         setIndex(v, index);
350         return new String JavaDoc(b.toByteArray());
351       }
352       b.write(c);
353       index++;
354     }
355     setIndex(v, index);
356     return new String JavaDoc(b.toByteArray());
357   }
358
359   /**
360    * Scan the content and decide if it's binary or ASCII data.
361    */

362   public static boolean isBinaryContent(byte[] data)
363   {
364     return isBinaryContent(data, 0, data.length);
365   }
366
367   /**
368    * Scan the content and decide if it's binary or ASCII data.
369    */

370   public static boolean isBinaryContent(byte[] data, int start, int len)
371   {
372     byte[] d = data;
373     for (int i=start; i<len; i++)
374     {
375       if (((int)d[i]) < 0)
376         return true;
377     }
378     return false;
379   }
380
381   //
382
// read until, and including, the separator. Sets index in v to be the first
383
// char after any CRLF action after the separator.
384
//
385
// reads:
386
// binary data<CRLF>
387
// SEPARATOR<CRLF>
388
//
389
// or
390
//
391
// binary data<CRLF>
392
// SEPARATOR--<CRLF>
393
//
394
private final static byte[] readBody(String JavaDoc sep, byte data[], StringBuffer JavaDoc info, Vector v)
395   throws MIMEException
396   {
397     int index = getIndex(v);
398     int endIndex = getEndIndex(v);
399     int sepLen = sep.length();
400     ByteArrayOutputStream buffer = new ByteArrayOutputStream();
401
402     // assume ascii
403
boolean isBinary = false;
404     info.insert(0, "ascii");
405     info.setLength(5);
406
407     while (index < endIndex)
408     {
409       // check to see if we're reading binary or ascii
410
if ((int)data[index] < 0 && !isBinary) // binary (only do this once)
411
{
412         info.insert(0, "binary");
413         info.setLength(6);
414         isBinary = true;
415       }
416
417       // if we get a CRLF, check some stuff.
418
if ( isCR((int)data[index]) && isLF((int)data[index +1]) ||
419            isLF((int)data[index]) && !isCRLF((int)data[index +1]) )
420       {
421         int skip = 0;
422         if (isLF((int)data[index +1]))
423           skip = 2;
424         else
425           skip = 1;
426
427         // look ahead and see if the separator is coming up
428
//
429
// index
430
// vv <- sep ->
431
// [CR][LF][?][?][?][END] // MSIE end (incorrect)
432
// [CR][LF][?][?][?][-][-][END] // MSIE end (incorrect)
433
// [CR][LF][?][?][?][-][-][CR][LF][END] // netscape end (correct)
434
// [CR][LF][?][?][?][CR][LF] // middle (both)
435
//
436
// Remember that trailing whitespace was remove above.
437
//
438

439         // sepTry is a string we think might be the separator.
440
String JavaDoc sepTry = null;
441         sepTry = new String JavaDoc(data, index + skip, sepLen);
442
443         // if the attempt at a separator is indeed the separator...
444
if (sepTry.equals(sep))
445         {
446           // separator is the end of the data, or
447
// separator is followed by "--" and then the end.
448
if ( ((index + skip + sepLen) == endIndex) ||
449                ((index + skip + sepLen + 2) == endIndex) )
450           {
451             setIndex(v, endIndex);
452             return buffer.toByteArray();
453           }
454
455           // check if this is not the end of the entire message,
456
// but is the end of the current attachment's body
457
// (separator is followed by CRLF)
458
if ( isCR((int)data[skip + index + sepLen]) && isLF((int)data[skip + index + sepLen +1]) ||
459                isLF((int)data[skip + index + sepLen]) && !isCRLF((int)data[skip + index + sepLen +1]) )
460           {
461             // position index after the CRLF action
462
if (isLF((int)data[index + sepLen +1]))
463               setIndex(v, index + sepLen + 2);
464             else
465               setIndex(v, index + sepLen + 1);
466             return buffer.toByteArray();
467           }
468           sepTry = null;
469           buffer.write(data, index, skip);
470           index += skip;
471         }
472
473         // separator didn't match... we can fast-forward a bit.
474
// all we do here is write the CRLF to the buffer.
475
else
476         {
477           buffer.write(data, index, skip);
478           index += skip;
479         }
480       }
481       else
482       {
483         buffer.write((int)data[index++]);
484       }
485     }
486
487     // hit the end -- return null -- should not get here.
488
setIndex(v, index);
489     return buffer.toByteArray();
490   }
491
492   //
493
// read a stream fully and return it's contents as an
494
// array of bytes.
495
//
496
private static byte[] readInputStreamFully(InputStream is)
497   throws IOException
498   {
499     ByteArrayOutputStream b = new ByteArrayOutputStream();
500     {
501       int i = 0;
502       while ((i = is.read()) != -1)
503         b.write(i);
504     }
505     return b.toByteArray();
506   }
507
508   // is the given character a CR?
509
private final static boolean isCR(int i)
510   {
511     return (i == 13);
512   }
513
514   // is the given character a LF?
515
private final static boolean isLF(int i)
516   {
517     return (i == 10);
518   }
519
520   // is the given character a CR or an LF?
521
private final static boolean isCRLF(int i)
522   {
523     return ((i == 10) || (i == 13));
524   }
525
526   private final static byte[] removeWhitespace(byte[] data)
527   {
528     byte[] d = data;
529     ByteArrayOutputStream out = new ByteArrayOutputStream();
530     for (int i=0; i<d.length; i++)
531     {
532       if (!Character.isWhitespace((char)d[i]))
533         out.write(d[i]);
534     }
535     return out.toByteArray();
536   }
537
538   public static void main(String JavaDoc args[])
539   {
540     if (args.length == 0)
541     {
542       System.out.println("Usage: MIMEMessage parse filename");
543       System.out.println(" or MIMEMessage create file1..fileN");
544       System.exit(0);
545     }
546
547     try
548     {
549       String JavaDoc cmd = args[0];
550       if (cmd.equalsIgnoreCase("parse"))
551       {
552         BufferedInputStream in = new BufferedInputStream(
553           new FileInputStream(new File(args[1])));
554         long time = System.currentTimeMillis();
555         MIMEMessage m = MIMEMessage.parse(in);
556         time = System.currentTimeMillis() - time;
557         System.err.println("Parse took " + time + "ms");
558         System.err.println("");
559         Enumeration e = m.getAttachments();
560         while (e.hasMoreElements())
561         {
562           MIMEAttachment a = (MIMEAttachment)e.nextElement();
563           System.err.println("Attachment:");
564           System.err.println(" Headers:");
565           Enumeration h = a.getHeaderNames();
566           while (h.hasMoreElements())
567           {
568             String JavaDoc header = (String JavaDoc)h.nextElement();
569             System.err.println(" " + header + ": " + a.getHeader(header));
570           }
571           System.err.println(" Info:");
572           System.err.println(" Content length: " + a.getContent().length);
573           System.err.println(" Binary: " + a.isBinary());
574           System.err.println("");
575         }
576         System.out.println(m);
577       }
578       else
579       {
580         System.err.println("Creating new MIMEMessage");
581         MIMEMessage m = new MIMEMessage();
582         for (int i=1; i<args.length; i++)
583         {
584           String JavaDoc file = args[i];
585           String JavaDoc type = "unknown";
586
587           ByteArrayOutputStream bout = new ByteArrayOutputStream();
588           BufferedInputStream in = new BufferedInputStream(
589             new FileInputStream(new File(file)));
590           byte[] buffer = new byte[8192];
591           int read = 0;
592           while ((read = in.read(buffer)) != -1)
593             bout.write(buffer, 0, read);
594
595           byte[] data = bout.toByteArray();
596           boolean binary = isBinaryContent(data);
597           MIMEAttachment a = new MIMEAttachment(type, file,
598             data, binary);
599           System.err.println("binary = " + binary);
600           m.addAttachment(a);
601         }
602
603         System.out.println(m);
604       }
605     }
606     catch (Exception JavaDoc x)
607     {
608       x.printStackTrace();
609     }
610   }
611 }
612
Popular Tags