1 21 22 package org.armedbear.j.mail; 23 24 import java.io.Serializable ; 25 import java.text.SimpleDateFormat ; 26 import java.util.ArrayList ; 27 import java.util.Date ; 28 import java.util.List ; 29 import java.util.TimeZone ; 30 import org.armedbear.j.FastStringBuffer; 31 import org.armedbear.j.Log; 32 import org.armedbear.j.StringPair; 33 import org.armedbear.j.Utilities; 34 35 final class ImapMailboxEntry extends MailboxEntry implements Serializable 36 { 37 private transient ImapMailbox mailbox; 38 39 private int uid; 40 private RFC822Date arrival; 41 42 private ImapMailboxEntry() 43 { 44 } 45 46 ImapMailboxEntry(int uid) 48 { 49 this.uid = uid; 50 } 51 52 public final ImapMailbox getMailbox() 53 { 54 return mailbox; 55 } 56 57 public final void setMailbox(ImapMailbox mailbox) 58 { 59 this.mailbox = mailbox; 60 } 61 62 public final int getUid() 63 { 64 return uid; 65 } 66 67 public final RFC822Date getArrival() 68 { 69 return arrival; 70 } 71 72 private static final String INTERNALDATE_START = "INTERNALDATE "; 73 private static final String RFC822_SIZE_START = "RFC822.SIZE "; 74 private static final String ENVELOPE_START = "ENVELOPE ("; 75 private static final String REFERENCES_START = 76 "BODY[HEADER.FIELDS (\"REFERENCES\")]"; 77 78 public static ImapMailboxEntry parseEntry(ImapMailbox mailbox, final String s) 79 { 80 ImapMailboxEntry entry = new ImapMailboxEntry(); 81 entry.mailbox = mailbox; 82 entry.messageNumber = parseMessageNumber(s); 83 if (entry.messageNumber < 1) { 84 Log.error("can't parse message number"); 85 return null; 86 } 87 entry.uid = parseUid(s); 88 if (entry.uid < 1) { 89 Log.error("can't parse uid"); 90 return null; 91 } 92 entry.flags = parseFlags(s); 93 int index = s.indexOf(INTERNALDATE_START); 94 if (index < 0) { 95 Log.error("can't find INTERNALDATE"); 96 return null; 97 } 98 StringPair p = 99 parseQuoted(s.substring(index + INTERNALDATE_START.length())); 100 if (p == null) { 101 Log.error("can't parse INTERNALDATE"); 102 return null; 103 } 104 entry.arrival = entry.parseInternalDate(p.first.trim()); 105 String remaining = p.second; 106 index = remaining.indexOf(RFC822_SIZE_START); 107 if (index < 0) { 108 Log.error("can't find RFC822.SIZE"); 109 return null; 110 } 111 remaining = remaining.substring(index + RFC822_SIZE_START.length()); 112 entry.size = -1; 113 try { 114 entry.size = Utilities.parseInt(remaining); 115 } 116 catch(NumberFormatException e) { 117 Log.error("can't parse RFC822.SIZE"); 118 Log.error("|" + remaining + "|"); 119 Log.error(e); 120 return null; 121 } 122 index = remaining.indexOf(ENVELOPE_START); 123 if (index < 0) { 124 Log.error("can't find ENVELOPE"); 125 return null; 126 } 127 remaining = remaining.substring(index + ENVELOPE_START.length()); 128 p = parseQuoted(remaining); 130 if (p == null) { 131 Log.error("can't parse date"); 132 return null; 133 } 134 entry.date = RFC822Date.parseDate(p.first); 135 remaining = p.second; 136 p = parseQuoted(remaining); 138 if (p == null) { 139 Log.error("can't parse subject"); 140 return null; 141 } 142 entry.subject = p.first == null ? "" : RFC2047.decode(p.first); 143 remaining = p.second; 144 p = parseParenthesizedList(remaining); 146 if (p == null) { 147 Log.error("can't parse \"From\" list"); 148 return null; 149 } 150 if (p.first != null) 151 entry.from = parseAddressList(p.first); 152 remaining = p.second; 153 do { 154 p = parseParenthesizedList(remaining); 156 if (p == null) { 157 Log.error("can't parse \"Sender\" list"); 158 break; 159 } 160 remaining = p.second; 161 p = parseParenthesizedList(remaining); 163 if (p == null) { 164 Log.error("can't parse \"Reply-To\" list"); 165 break; 166 } 167 if (p.first != null) 168 entry.replyTo = parseAddressList(p.first); 169 remaining = p.second; 170 p = parseParenthesizedList(remaining); 172 if (p == null) { 173 Log.error("can't parse \"To\" list"); 174 break; 175 } 176 if (p.first != null) 177 entry.to = parseAddressList(p.first); 178 remaining = p.second; 179 p = parseParenthesizedList(remaining); 181 if (p == null) { 182 Log.error("can't parse \"Cc\" list"); 183 break; 184 } 185 if (p.first != null) 186 entry.cc = parseAddressList(p.first); 187 remaining = p.second; 188 p = parseParenthesizedList(remaining); 190 if (p == null) { 191 Log.error("can't parse \"Bcc\" list"); 192 break; 193 } 194 remaining = p.second; 195 p = parseQuoted(remaining); 197 if (p == null) { 198 Log.error("can't parse \"In-Reply-To\""); 199 break; 200 } 201 entry.inReplyTo = parseInReplyTo(p.first); 202 remaining = p.second; 203 p = parseQuoted(remaining); 204 if (p == null) { 205 Log.error("can't parse \"Message-ID\""); 206 break; 207 } 208 entry.messageId = p.first; 209 } while (false); 210 remaining = p.second; 211 index = remaining.indexOf(REFERENCES_START); 212 if (index >= 0) { 213 remaining = remaining.substring(index + REFERENCES_START.length()); 214 p = parseQuoted(remaining); 215 if (p != null) { 216 String refs = p.first.trim(); 217 if (refs.length() > 0) 218 entry.references = parseReferences(refs); 219 } 220 } 221 if (entry.subject != null) 222 return entry; 223 Log.error("skipping entry"); 224 return null; 225 } 226 227 private static int parseMessageNumber(String s) 228 { 229 final int length = s.length(); 230 if (length < 2) 231 return 0; if (s.charAt(0) != '*' || s.charAt(1) != ' ') 234 return 0; FastStringBuffer sb = new FastStringBuffer(); 236 for (int i = 2; i < length; i++) { 237 char c = s.charAt(i); 238 if (c >= '0' && c <= '9') 239 sb.append(c); 240 else 241 break; 242 } 243 try { 244 return Integer.parseInt(sb.toString()); 245 } 246 catch (NumberFormatException e) { 247 Log.error(e); 248 return 0; 249 } 250 } 251 252 public static int parseUid(String s) 253 { 254 final int length = s.length(); 255 if (length < 2) 256 return 0; if (s.charAt(0) != '*' || s.charAt(1) != ' ') 259 return 0; int index = s.indexOf("UID "); 261 if (index < 0) 262 return 0; 263 FastStringBuffer sb = new FastStringBuffer(); 264 for (int i = index+4; i < length; i++) { 265 char c = s.charAt(i); 266 if (c >= '0' && c <= '9') 267 sb.append(c); 268 else 269 break; 270 } 271 try { 272 return Integer.parseInt(sb.toString()); 273 } 274 catch (NumberFormatException e) { 275 Log.error(e); 276 return 0; 277 } 278 } 279 280 private static final String FLAGS_START = "FLAGS ("; 281 282 public static int parseFlags(String s) 283 { 284 int flags = 0; 285 final int index = s.indexOf(FLAGS_START); 286 if (index >= 0) { 287 StringPair p = parseParenthesized(s.substring(index)); 288 if (p != null && p.first != null) { 289 String flagsList = p.first.toLowerCase(); 290 if (flagsList.length() > 0) { 291 if (flagsList.indexOf("seen") >= 0) 292 flags |= SEEN; 293 if (flagsList.indexOf("answered") >= 0) 294 flags |= ANSWERED; 295 if (flagsList.indexOf("recent") >= 0) 296 flags |= RECENT; 297 if (flagsList.indexOf("deleted") >= 0) 298 flags |= DELETED; 299 if (flagsList.indexOf("flagged") >= 0) 300 flags |= FLAGGED; 301 } 302 } 303 } 304 return flags; 305 } 306 307 private static StringPair parseQuoted(String s) 308 { 309 s = s.trim(); 310 if (s.length() == 0) 311 return null; 312 String quoted = null; 313 String remaining = null; 314 if (s.charAt(0) == '{') { 315 int end = s.indexOf('}', 1); 316 if (end < 0) { 317 Log.error("parseQuoted: bad literal"); 318 return null; 319 } 320 int length = 0; 321 try { 322 length = Integer.parseInt(s.substring(1, end)); 323 } 324 catch (NumberFormatException e) { 325 Log.error(e); 326 } 327 if (length == 0) { 328 Log.error("parseQuoted: length of literal is zero"); 329 return null; 330 } 331 int begin = s.indexOf('\n', end+1); 332 if (begin < 0) { 333 Log.error("parseQuoted: no LF after literal"); 334 return null; 335 } 336 ++begin; end = begin + length; 338 if (end > s.length()) { 339 Log.error("parseQuoted end > s.length()"); 340 return null; 341 } 342 quoted = s.substring(begin, end); 343 remaining = s.substring(end); 344 } else if (s.startsWith("NIL")) { 345 quoted = null; 346 remaining = s.substring(3).trim(); 347 } else { 348 int begin = s.indexOf('"'); 349 if (begin < 0) 350 return null; 351 int end = s.indexOf('"', begin + 1); 352 if (end < 0) 353 return null; 354 quoted = s.substring(begin + 1, end); 355 remaining = s.substring(end + 1); 356 } 357 return new StringPair(quoted, remaining); 358 } 359 360 private static StringPair parseParenthesized(String s) 361 { 362 int begin = s.indexOf('('); 363 if (begin < 0) 364 return null; 365 int end = -1; 366 final int limit = s.length(); 367 boolean inQuote = false; 368 char quoteChar = '\0'; 369 for (int i = begin + 1; i < limit; i++) { 370 char c = s.charAt(i); 371 if (inQuote) { 372 if (c == quoteChar) 373 inQuote = false; 374 } else { 375 if (c == '"' || c == '\'') { 377 inQuote = true; 378 quoteChar = c; 379 } else if (c == ')') { 380 end = i; 381 break; 382 } 383 } 384 } 385 if (end < 0) 386 return null; 387 String parenthesized = s.substring(begin+1, end); 388 String remaining = s.substring(end+1); 389 return new StringPair(parenthesized, remaining); 390 } 391 392 static private StringPair parseParenthesizedList(String s) 393 { 394 s = s.trim(); 395 if (s.startsWith("NIL")) 396 return new StringPair(null, s.substring(3).trim()); 397 int begin = s.indexOf("(("); 398 if (begin < 0) 399 return null; 400 int end = s.indexOf("))"); 401 if (end < 0) 402 return null; 403 String list = s.substring(begin, end + 2); 404 String remaining = s.substring(end + 2); 405 return new StringPair(list, remaining); 406 } 407 408 private static MailAddress[] parseAddressList(String list) 409 { 410 if (list == null) 411 return null; 412 ArrayList addresses = new ArrayList (); 413 String remaining = list.substring(1, list.length()-1); 414 while (remaining.length() > 0) { 415 StringPair p = parseParenthesized(remaining); 416 if (p == null) { 417 Log.error("parseAddressList error"); 418 Log.error("list = |" + list + "|"); 419 Log.error("remaining = |" + remaining + "|"); 420 return null; 421 } 422 String s = p.first; MailAddress address = parseAddress(s); 424 if (address == null) { 425 Log.error("**** parseAddress returned null"); 426 Log.error("s = |" + s + "|"); 427 } 428 if (address != null) 429 addresses.add(address); 430 remaining = p.second; 431 } 432 if (addresses.size() == 0) 433 return null; 434 MailAddress[] array = new MailAddress[addresses.size()]; 435 return (MailAddress[]) addresses.toArray(array); 436 } 437 438 private static MailAddress parseAddress(String s) 439 { 440 StringPair p = parseQuoted(s); 441 if (p == null) return null; 443 String encodedPersonal = p.first; 444 String remaining = p.second; 445 p = parseQuoted(remaining); 446 if (p == null) return null; 448 String sourceRoute = p.first; 449 remaining = p.second; 450 p = parseQuoted(remaining); 451 if (p == null) return null; 453 String mailName = p.first; 454 remaining = p.second; 455 p = parseQuoted(remaining); 456 if (p == null) return null; 458 String domainName = p.first; 459 remaining = p.second; 460 if (remaining.length() > 0) 461 Log.error("**** parseAddress: unexpected string remaining ****"); 462 return new MailAddress(encodedPersonal, mailName + '@' + domainName); 463 } 464 465 private static SimpleDateFormat internalDateFormat = new SimpleDateFormat ("dd-MMM-yyyy HH:mm:ss"); 466 467 private static RFC822Date parseInternalDate(String internalDate) 468 { 469 Date date = null; 470 int index = internalDate.indexOf(' '); 471 if (index >= 0) { 472 index = internalDate.indexOf(' ', index+1); 473 if (index >= 0) { 474 String dateString = internalDate.substring(0, index); 475 String timeZone = internalDate.substring(index + 1); 476 TimeZone tz = TimeZone.getTimeZone("GMT" + timeZone); 477 if (tz != null) 478 internalDateFormat.setTimeZone(tz); 479 try { 480 date = internalDateFormat.parse(dateString); 481 } 482 catch (Throwable t) { 483 Log.error(t); 484 } 485 } 486 } 487 return new RFC822Date(date); 488 } 489 } 490 | Popular Tags |