001: /*
002: * ImapMailboxEntry.java
003: *
004: * Copyright (C) 2000-2002 Peter Graves
005: * $Id: ImapMailboxEntry.java,v 1.1.1.1 2002/09/24 16:10:10 piso Exp $
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License
009: * as published by the Free Software Foundation; either version 2
010: * of the License, or (at your option) any later version.
011: *
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015: * GNU General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with this program; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
020: */
021:
022: package org.armedbear.j.mail;
023:
024: import java.io.Serializable;
025: import java.text.SimpleDateFormat;
026: import java.util.ArrayList;
027: import java.util.Date;
028: import java.util.List;
029: import java.util.TimeZone;
030: import org.armedbear.j.FastStringBuffer;
031: import org.armedbear.j.Log;
032: import org.armedbear.j.StringPair;
033: import org.armedbear.j.Utilities;
034:
035: /*package*/final class ImapMailboxEntry extends MailboxEntry implements
036: Serializable {
037: private transient ImapMailbox mailbox;
038:
039: private int uid;
040: private RFC822Date arrival;
041:
042: private ImapMailboxEntry() {
043: }
044:
045: // For testing only!
046: /*package*/ImapMailboxEntry(int uid) {
047: this .uid = uid;
048: }
049:
050: public final ImapMailbox getMailbox() {
051: return mailbox;
052: }
053:
054: public final void setMailbox(ImapMailbox mailbox) {
055: this .mailbox = mailbox;
056: }
057:
058: public final int getUid() {
059: return uid;
060: }
061:
062: public final RFC822Date getArrival() {
063: return arrival;
064: }
065:
066: private static final String INTERNALDATE_START = "INTERNALDATE ";
067: private static final String RFC822_SIZE_START = "RFC822.SIZE ";
068: private static final String ENVELOPE_START = "ENVELOPE (";
069: private static final String REFERENCES_START = "BODY[HEADER.FIELDS (\"REFERENCES\")]";
070:
071: public static ImapMailboxEntry parseEntry(ImapMailbox mailbox,
072: final String s) {
073: ImapMailboxEntry entry = new ImapMailboxEntry();
074: entry.mailbox = mailbox;
075: entry.messageNumber = parseMessageNumber(s);
076: if (entry.messageNumber < 1) {
077: Log.error("can't parse message number");
078: return null;
079: }
080: entry.uid = parseUid(s);
081: if (entry.uid < 1) {
082: Log.error("can't parse uid");
083: return null;
084: }
085: entry.flags = parseFlags(s);
086: int index = s.indexOf(INTERNALDATE_START);
087: if (index < 0) {
088: Log.error("can't find INTERNALDATE");
089: return null;
090: }
091: StringPair p = parseQuoted(s.substring(index
092: + INTERNALDATE_START.length()));
093: if (p == null) {
094: Log.error("can't parse INTERNALDATE");
095: return null;
096: }
097: entry.arrival = entry.parseInternalDate(p.first.trim());
098: String remaining = p.second;
099: index = remaining.indexOf(RFC822_SIZE_START);
100: if (index < 0) {
101: Log.error("can't find RFC822.SIZE");
102: return null;
103: }
104: remaining = remaining.substring(index
105: + RFC822_SIZE_START.length());
106: entry.size = -1;
107: try {
108: entry.size = Utilities.parseInt(remaining);
109: } catch (NumberFormatException e) {
110: Log.error("can't parse RFC822.SIZE");
111: Log.error("|" + remaining + "|");
112: Log.error(e);
113: return null;
114: }
115: index = remaining.indexOf(ENVELOPE_START);
116: if (index < 0) {
117: Log.error("can't find ENVELOPE");
118: return null;
119: }
120: remaining = remaining
121: .substring(index + ENVELOPE_START.length());
122: // Next field is date (quoted string).
123: p = parseQuoted(remaining);
124: if (p == null) {
125: Log.error("can't parse date");
126: return null;
127: }
128: entry.date = RFC822Date.parseDate(p.first);
129: remaining = p.second;
130: // Next field is subject (quoted string).
131: p = parseQuoted(remaining);
132: if (p == null) {
133: Log.error("can't parse subject");
134: return null;
135: }
136: entry.subject = p.first == null ? "" : RFC2047.decode(p.first);
137: remaining = p.second;
138: // Next field is "From" (parenthesized list).
139: p = parseParenthesizedList(remaining);
140: if (p == null) {
141: Log.error("can't parse \"From\" list");
142: return null;
143: }
144: if (p.first != null)
145: entry.from = parseAddressList(p.first);
146: remaining = p.second;
147: do {
148: // Sender
149: p = parseParenthesizedList(remaining);
150: if (p == null) {
151: Log.error("can't parse \"Sender\" list");
152: break;
153: }
154: remaining = p.second;
155: // Reply-To
156: p = parseParenthesizedList(remaining);
157: if (p == null) {
158: Log.error("can't parse \"Reply-To\" list");
159: break;
160: }
161: if (p.first != null)
162: entry.replyTo = parseAddressList(p.first);
163: remaining = p.second;
164: // To
165: p = parseParenthesizedList(remaining);
166: if (p == null) {
167: Log.error("can't parse \"To\" list");
168: break;
169: }
170: if (p.first != null)
171: entry.to = parseAddressList(p.first);
172: remaining = p.second;
173: // Cc
174: p = parseParenthesizedList(remaining);
175: if (p == null) {
176: Log.error("can't parse \"Cc\" list");
177: break;
178: }
179: if (p.first != null)
180: entry.cc = parseAddressList(p.first);
181: remaining = p.second;
182: // Bcc
183: p = parseParenthesizedList(remaining);
184: if (p == null) {
185: Log.error("can't parse \"Bcc\" list");
186: break;
187: }
188: remaining = p.second;
189: // In-Reply-To (quoted string)
190: p = parseQuoted(remaining);
191: if (p == null) {
192: Log.error("can't parse \"In-Reply-To\"");
193: break;
194: }
195: entry.inReplyTo = parseInReplyTo(p.first);
196: remaining = p.second;
197: p = parseQuoted(remaining);
198: if (p == null) {
199: Log.error("can't parse \"Message-ID\"");
200: break;
201: }
202: entry.messageId = p.first;
203: } while (false);
204: remaining = p.second;
205: index = remaining.indexOf(REFERENCES_START);
206: if (index >= 0) {
207: remaining = remaining.substring(index
208: + REFERENCES_START.length());
209: p = parseQuoted(remaining);
210: if (p != null) {
211: String refs = p.first.trim();
212: if (refs.length() > 0)
213: entry.references = parseReferences(refs);
214: }
215: }
216: if (entry.subject != null)
217: return entry;
218: Log.error("skipping entry");
219: return null;
220: }
221:
222: private static int parseMessageNumber(String s) {
223: final int length = s.length();
224: if (length < 2)
225: return 0; // Error.
226: // String must start with "* ".
227: if (s.charAt(0) != '*' || s.charAt(1) != ' ')
228: return 0; // Error.
229: FastStringBuffer sb = new FastStringBuffer();
230: for (int i = 2; i < length; i++) {
231: char c = s.charAt(i);
232: if (c >= '0' && c <= '9')
233: sb.append(c);
234: else
235: break;
236: }
237: try {
238: return Integer.parseInt(sb.toString());
239: } catch (NumberFormatException e) {
240: Log.error(e);
241: return 0;
242: }
243: }
244:
245: public static int parseUid(String s) {
246: final int length = s.length();
247: if (length < 2)
248: return 0; // Error.
249: // String must start with "* ".
250: if (s.charAt(0) != '*' || s.charAt(1) != ' ')
251: return 0; // Error.
252: int index = s.indexOf("UID ");
253: if (index < 0)
254: return 0;
255: FastStringBuffer sb = new FastStringBuffer();
256: for (int i = index + 4; i < length; i++) {
257: char c = s.charAt(i);
258: if (c >= '0' && c <= '9')
259: sb.append(c);
260: else
261: break;
262: }
263: try {
264: return Integer.parseInt(sb.toString());
265: } catch (NumberFormatException e) {
266: Log.error(e);
267: return 0;
268: }
269: }
270:
271: private static final String FLAGS_START = "FLAGS (";
272:
273: public static int parseFlags(String s) {
274: int flags = 0;
275: final int index = s.indexOf(FLAGS_START);
276: if (index >= 0) {
277: StringPair p = parseParenthesized(s.substring(index));
278: if (p != null && p.first != null) {
279: String flagsList = p.first.toLowerCase();
280: if (flagsList.length() > 0) {
281: if (flagsList.indexOf("seen") >= 0)
282: flags |= SEEN;
283: if (flagsList.indexOf("answered") >= 0)
284: flags |= ANSWERED;
285: if (flagsList.indexOf("recent") >= 0)
286: flags |= RECENT;
287: if (flagsList.indexOf("deleted") >= 0)
288: flags |= DELETED;
289: if (flagsList.indexOf("flagged") >= 0)
290: flags |= FLAGGED;
291: }
292: }
293: }
294: return flags;
295: }
296:
297: private static StringPair parseQuoted(String s) {
298: s = s.trim();
299: if (s.length() == 0)
300: return null;
301: String quoted = null;
302: String remaining = null;
303: if (s.charAt(0) == '{') {
304: int end = s.indexOf('}', 1);
305: if (end < 0) {
306: Log.error("parseQuoted: bad literal");
307: return null;
308: }
309: int length = 0;
310: try {
311: length = Integer.parseInt(s.substring(1, end));
312: } catch (NumberFormatException e) {
313: Log.error(e);
314: }
315: if (length == 0) {
316: Log.error("parseQuoted: length of literal is zero");
317: return null;
318: }
319: int begin = s.indexOf('\n', end + 1);
320: if (begin < 0) {
321: Log.error("parseQuoted: no LF after literal");
322: return null;
323: }
324: ++begin; // Skip LF.
325: end = begin + length;
326: if (end > s.length()) {
327: Log.error("parseQuoted end > s.length()");
328: return null;
329: }
330: quoted = s.substring(begin, end);
331: remaining = s.substring(end);
332: } else if (s.startsWith("NIL")) {
333: quoted = null;
334: remaining = s.substring(3).trim();
335: } else {
336: int begin = s.indexOf('"');
337: if (begin < 0)
338: return null;
339: int end = s.indexOf('"', begin + 1);
340: if (end < 0)
341: return null;
342: quoted = s.substring(begin + 1, end);
343: remaining = s.substring(end + 1);
344: }
345: return new StringPair(quoted, remaining);
346: }
347:
348: private static StringPair parseParenthesized(String s) {
349: int begin = s.indexOf('(');
350: if (begin < 0)
351: return null;
352: int end = -1;
353: final int limit = s.length();
354: boolean inQuote = false;
355: char quoteChar = '\0';
356: for (int i = begin + 1; i < limit; i++) {
357: char c = s.charAt(i);
358: if (inQuote) {
359: if (c == quoteChar)
360: inQuote = false;
361: } else {
362: // Not in quote.
363: if (c == '"' || c == '\'') {
364: inQuote = true;
365: quoteChar = c;
366: } else if (c == ')') {
367: end = i;
368: break;
369: }
370: }
371: }
372: if (end < 0)
373: return null;
374: String parenthesized = s.substring(begin + 1, end);
375: String remaining = s.substring(end + 1);
376: return new StringPair(parenthesized, remaining);
377: }
378:
379: static private StringPair parseParenthesizedList(String s) {
380: s = s.trim();
381: if (s.startsWith("NIL"))
382: return new StringPair(null, s.substring(3).trim());
383: int begin = s.indexOf("((");
384: if (begin < 0)
385: return null;
386: int end = s.indexOf("))");
387: if (end < 0)
388: return null;
389: String list = s.substring(begin, end + 2);
390: String remaining = s.substring(end + 2);
391: return new StringPair(list, remaining);
392: }
393:
394: private static MailAddress[] parseAddressList(String list) {
395: if (list == null)
396: return null;
397: ArrayList addresses = new ArrayList();
398: String remaining = list.substring(1, list.length() - 1);
399: while (remaining.length() > 0) {
400: StringPair p = parseParenthesized(remaining);
401: if (p == null) {
402: Log.error("parseAddressList error");
403: Log.error("list = |" + list + "|");
404: Log.error("remaining = |" + remaining + "|");
405: return null;
406: }
407: String s = p.first; // The address.
408: MailAddress address = parseAddress(s);
409: if (address == null) {
410: Log.error("**** parseAddress returned null");
411: Log.error("s = |" + s + "|");
412: }
413: if (address != null)
414: addresses.add(address);
415: remaining = p.second;
416: }
417: if (addresses.size() == 0)
418: return null;
419: MailAddress[] array = new MailAddress[addresses.size()];
420: return (MailAddress[]) addresses.toArray(array);
421: }
422:
423: private static MailAddress parseAddress(String s) {
424: StringPair p = parseQuoted(s);
425: if (p == null) // Error.
426: return null;
427: String encodedPersonal = p.first;
428: String remaining = p.second;
429: p = parseQuoted(remaining);
430: if (p == null) // Error.
431: return null;
432: String sourceRoute = p.first;
433: remaining = p.second;
434: p = parseQuoted(remaining);
435: if (p == null) // Error.
436: return null;
437: String mailName = p.first;
438: remaining = p.second;
439: p = parseQuoted(remaining);
440: if (p == null) // Error.
441: return null;
442: String domainName = p.first;
443: remaining = p.second;
444: if (remaining.length() > 0)
445: Log
446: .error("**** parseAddress: unexpected string remaining ****");
447: return new MailAddress(encodedPersonal, mailName + '@'
448: + domainName);
449: }
450:
451: private static SimpleDateFormat internalDateFormat = new SimpleDateFormat(
452: "dd-MMM-yyyy HH:mm:ss");
453:
454: private static RFC822Date parseInternalDate(String internalDate) {
455: Date date = null;
456: int index = internalDate.indexOf(' ');
457: if (index >= 0) {
458: index = internalDate.indexOf(' ', index + 1);
459: if (index >= 0) {
460: String dateString = internalDate.substring(0, index);
461: String timeZone = internalDate.substring(index + 1);
462: TimeZone tz = TimeZone.getTimeZone("GMT" + timeZone);
463: if (tz != null)
464: internalDateFormat.setTimeZone(tz);
465: try {
466: date = internalDateFormat.parse(dateString);
467: } catch (Throwable t) {
468: Log.error(t);
469: }
470: }
471: }
472: return new RFC822Date(date);
473: }
474: }
|