root/trunk/LogicMail/src/org/logicprobe/LogicMail/util/MailMessageParser.java

Revision 336, 9.7 kB (checked in by octorian, 6 weeks ago)
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1/*-
2 * Copyright (c) 2008, Derek Konigsberg
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 * 3. Neither the name of the project nor the names of its
15 *    contributors may be used to endorse or promote products derived
16 *    from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
27 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
29 * OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32package org.logicprobe.LogicMail.util;
33
34import java.io.IOException;
35import java.io.InputStream;
36import java.util.Calendar;
37import java.util.Hashtable;
38
39import net.rim.device.api.io.SharedInputStream;
40import net.rim.device.api.mime.MIMEInputStream;
41import net.rim.device.api.mime.MIMEParsingException;
42
43import org.logicprobe.LogicMail.AppInfo;
44import org.logicprobe.LogicMail.message.MessageEnvelope;
45import org.logicprobe.LogicMail.message.MessagePart;
46import org.logicprobe.LogicMail.message.MessagePartFactory;
47import org.logicprobe.LogicMail.message.MultiPart;
48
49/**
50 * This class contains static parser functions used for
51 * parsing raw message source text.
52 */
53public class MailMessageParser {
54    private static String strCRLF = "\r\n";
55
56    private MailMessageParser() { }
57
58    /**
59     * Parses the message envelope from the message headers.
60     *
61     * @param rawHeaders The raw header text, separated into lines.
62     * @return The message envelope.
63     */
64    public static MessageEnvelope parseMessageEnvelope(String[] rawHeaders) {
65        Hashtable headers = StringParser.parseMailHeaders(rawHeaders);
66        MessageEnvelope env = new MessageEnvelope();
67       
68        // Populate the common header field bits of the envelope
69        env.subject = StringParser.parseEncodedHeader((String)headers.get("subject"));
70        if(env.subject == null) {
71            env.subject = "<subject>";
72        }
73        env.from = parseAddressList((String)headers.get("from"));
74        env.sender = parseAddressList((String)headers.get("sender"));
75        env.to = parseAddressList((String)headers.get("to"));
76        env.cc = parseAddressList((String)headers.get("cc"));
77        env.bcc = parseAddressList((String)headers.get("bcc"));
78        try {
79            env.date = StringParser.parseDateString((String)headers.get("date"));
80        } catch (Exception e) {
81            env.date = Calendar.getInstance().getTime();
82        }
83        env.replyTo = parseAddressList((String)headers.get("reply-to"));
84        env.messageId = (String)headers.get("message-id");
85        env.inReplyTo = (String)headers.get("in-reply-to");
86        return env;
87    }
88
89    /**
90     * Generates the message headers corresponding to the provided envelope.
91     *
92     * @param envelope The message envelope.
93     * @param includeUserAgent True to include the User-Agent line.
94     * @return The headers, one per line, with CRLF line separators.
95     */
96    public static String generateMessageHeaders(MessageEnvelope envelope, boolean includeUserAgent) {
97        StringBuffer buffer = new StringBuffer();
98
99        // Create the message headers
100        buffer.append("From: ");
101        buffer.append(StringParser.makeCsvString(envelope.from));
102        buffer.append(strCRLF);
103       
104        buffer.append("To: ");
105        buffer.append(StringParser.makeCsvString(envelope.to));
106        buffer.append(strCRLF);
107
108        if ((envelope.cc != null) && (envelope.cc.length > 0)) {
109            buffer.append("Cc: ");
110            buffer.append(StringParser.makeCsvString(envelope.cc));
111            buffer.append(strCRLF);
112        }
113
114        if ((envelope.replyTo != null) && (envelope.replyTo.length > 0)) {
115            buffer.append("Reply-To: ");
116            buffer.append(StringParser.makeCsvString(envelope.replyTo));
117            buffer.append(strCRLF);
118        }
119
120        buffer.append("Date: ");
121        buffer.append(StringParser.createDateString(envelope.date));
122        buffer.append(strCRLF);
123
124        if(includeUserAgent) {
125                buffer.append("User-Agent: ");
126                buffer.append(AppInfo.getName());
127                buffer.append('/');
128                buffer.append(AppInfo.getVersion());
129                buffer.append(strCRLF);
130        }
131       
132        buffer.append("Subject: ");
133        buffer.append(envelope.subject);
134        buffer.append(strCRLF);
135
136        if (envelope.inReplyTo != null) {
137            buffer.append("In-Reply-To: ");
138            buffer.append(envelope.inReplyTo);
139            buffer.append(strCRLF);
140        }
141        return buffer.toString();
142    }
143   
144    /**
145     * Separates a list of addresses contained within a message header.
146     * This is slightly more complicated than a string tokenizer, as it
147     * has to deal with quoting and escaping.
148     *
149     * @param text The header line containing the addresses.
150     * @return The separated addresses.
151     */
152    private static String[] parseAddressList(String text) {
153        String[] addresses = StringParser.parseCsvString(text);
154        for(int i=0; i<addresses.length; i++) {
155            addresses[i] = StringParser.parseEncodedHeader(addresses[i]);
156            if(addresses[i].length() > 0 && addresses[i].charAt(0) == '"') {
157                int p = addresses[i].indexOf('<');
158                while(p > 0 && addresses[i].charAt(p) != '"') p--;
159                if(p > 0 && p+1 < addresses[i].length()) {
160                    addresses[i] = addresses[i].substring(1, p) + addresses[i].substring(p+1);
161                }
162            }
163        }
164        return addresses;
165    }
166   
167    /**
168     * Parses the raw message body.
169     *
170     * @param inputStream The stream to read the raw message from
171     * @return The root message part.
172     * @throws IOException Signals that an I/O exception has occurred.
173     */
174    public static MessagePart parseRawMessage(InputStream inputStream) throws IOException {
175        MIMEInputStream mimeInputStream = null;
176        try {
177            mimeInputStream = new MIMEInputStream(inputStream);
178        } catch (MIMEParsingException e) {
179            return null;
180        }
181        MessagePart rootPart = getMessagePart(mimeInputStream);
182        return rootPart;
183    }
184   
185    /**
186     * Recursively walk the provided MIMEInputStream, building a message
187     * tree in the process.
188     *
189     * @param mimeInputStream MIMEInputStream of the downloaded message data
190     * @return Root MessagePart element for this portion of the message tree
191     */
192    private static MessagePart getMessagePart(MIMEInputStream mimeInputStream) throws IOException {
193        // Parse out the MIME type and relevant header fields
194        String mimeType = mimeInputStream.getContentType();
195        String type = mimeType.substring(0, mimeType.indexOf('/'));
196        String subtype = mimeType.substring(mimeType.indexOf('/') + 1);
197        String encoding = mimeInputStream.getHeader("Content-Transfer-Encoding");
198        String charset = mimeInputStream.getContentTypeParameter("charset");
199       
200        // Default parameters used when headers are missing
201        if(encoding == null) {
202            encoding = "7bit";
203        }
204       
205        // Handle the multi-part case
206        if(mimeInputStream.isMultiPart() && type.equalsIgnoreCase("multipart")) {
207            MessagePart part = MessagePartFactory.createMessagePart(type, subtype, null, null, null);
208            MIMEInputStream[] mimeSubparts = mimeInputStream.getParts();
209            for(int i=0;i<mimeSubparts.length;i++) {
210                MessagePart subPart = getMessagePart(mimeSubparts[i]);
211                if(subPart != null) {
212                    ((MultiPart)part).addPart(subPart);
213                }
214            }
215            return part;
216        }
217        // Handle the single-part case
218        else {
219            byte[] buffer;
220            // Handle encoded binary data (should be more encoding-agnostic)
221            if(encoding.equalsIgnoreCase("base64") && mimeInputStream.isPartComplete()!=0) {
222                SharedInputStream sis = mimeInputStream.getRawMIMEInputStream();
223                buffer = StringParser.readWholeStream(sis);
224
225                int offset = 0;
226                while((offset+3 < buffer.length) &&
227                        !(buffer[offset]=='\r' && buffer[offset+1]=='\n' &&
228                        buffer[offset+2]=='\r' && buffer[offset+3]=='\n')) {
229                    offset++;
230                }
231                int size = buffer.length - offset;
232                return MessagePartFactory.createMessagePart(type, subtype, encoding, charset, new String(buffer, offset, size));
233            }
234            else {
235                buffer = StringParser.readWholeStream(mimeInputStream);
236                return MessagePartFactory.createMessagePart(type, subtype, encoding, charset, new String(buffer));
237            }
238        }
239    }
240}
Note: See TracBrowser for help on using the browser.