1 21 22 package org.armedbear.j.mail; 23 24 import gnu.regexp.RE; 25 import gnu.regexp.REMatch; 26 import gnu.regexp.UncheckedRE; 27 import java.io.BufferedInputStream ; 28 import java.io.BufferedReader ; 29 import java.io.BufferedWriter ; 30 import java.io.FileInputStream ; 31 import java.io.FileWriter ; 32 import java.io.IOException ; 33 import java.io.InputStreamReader ; 34 import java.io.OutputStreamWriter ; 35 import java.io.Writer ; 36 import java.text.SimpleDateFormat ; 37 import java.util.ArrayList ; 38 import java.util.Calendar ; 39 import java.util.List ; 40 import java.util.Random ; 41 import javax.swing.Icon ; 42 import javax.swing.SwingUtilities ; 43 import javax.swing.undo.CompoundEdit ; 44 import org.armedbear.j.Buffer; 45 import org.armedbear.j.BufferIterator; 46 import org.armedbear.j.Debug; 47 import org.armedbear.j.Directories; 48 import org.armedbear.j.Editor; 49 import org.armedbear.j.EditorIterator; 50 import org.armedbear.j.Expansion; 51 import org.armedbear.j.File; 52 import org.armedbear.j.FastStringBuffer; 53 import org.armedbear.j.Headers; 54 import org.armedbear.j.Line; 55 import org.armedbear.j.Log; 56 import org.armedbear.j.MessageDialog; 57 import org.armedbear.j.OpenFileDialog; 58 import org.armedbear.j.Platform; 59 import org.armedbear.j.Position; 60 import org.armedbear.j.Preferences; 61 import org.armedbear.j.Property; 62 import org.armedbear.j.Region; 63 import org.armedbear.j.Sidebar; 64 import org.armedbear.j.SimpleEdit; 65 import org.armedbear.j.Utilities; 66 import org.armedbear.j.Version; 67 68 public final class SendMail extends Buffer 69 { 70 private final static String HEADER_SEPARATOR = "--text follows this line--"; 71 private final static String DEFAULT_TITLE = "Compose"; 72 73 private static Preferences preferences; 74 75 private boolean reply; 76 private List group; 77 private Mailbox mailbox; 78 private MailboxEntry entryRepliedTo; 79 private String boundary; 80 private String smtp; 81 private SmtpSession session; 82 private boolean hasBeenSent; 83 84 public SendMail() 85 { 86 super(); 87 init(); 88 try { 89 lockWrite(); 90 } 91 catch (InterruptedException e) { 92 Log.debug(e); 93 return; 94 } 95 try { 96 appendFrom(); 97 appendLine("To: "); 98 appendLine("Subject: "); 99 appendDefaultHeaders(); 100 appendLine(HEADER_SEPARATOR); 101 appendLine(""); 102 appendSignature(); 103 renumber(); 104 formatter.parseBuffer(); 105 setLoaded(true); 106 } 107 finally { 108 unlockWrite(); 109 } 110 } 111 112 public SendMail(File file) 114 { 115 super(); 116 setFile(file); 117 init(); 118 } 119 120 public SendMail(MessageBuffer messageBuffer) 122 { 123 super(); 124 init(); 125 mailbox = messageBuffer.getMailbox(); 126 entryRepliedTo = messageBuffer.getMailboxEntry(); 127 try { 128 lockWrite(); 129 } 130 catch (InterruptedException e) { 131 Log.debug(e); 132 return; 133 } 134 try { 135 appendFrom(); 136 appendLine("To: "); 137 appendLine("Subject: [Fwd: " + entryRepliedTo.getSubject() + "]"); 138 appendDefaultHeaders(); 139 appendLine(HEADER_SEPARATOR); 140 appendLine(""); 141 Position pos = new Position(getLastLine(), 0); 142 insertString(pos, "----- Forwarded message -----\n\n"); 143 insertString(pos, messageBuffer.getText()); 144 insertString(pos, "\n\n----- End of forwarded message -----"); 145 unmodified(); 146 renumber(); 147 formatter.parseBuffer(); 148 setLoaded(true); 149 } 150 finally { 151 unlockWrite(); 152 } 153 } 154 155 public SendMail(MessageBuffer messageBuffer, boolean replyToGroup) 157 { 158 super(); 159 init(); 160 mailbox = messageBuffer.getMailbox(); 161 entryRepliedTo = messageBuffer.getMailboxEntry(); 162 reply = true; 163 MailAddress[] replyTo = entryRepliedTo.getReplyTo(); 164 MailAddress[] from = entryRepliedTo.getFrom(); 165 MailAddress[] to = entryRepliedTo.getTo(); 166 MailAddress[] cc = entryRepliedTo.getCc(); 167 boolean skipTo = false; 168 try { 169 lockWrite(); 170 } 171 catch (InterruptedException e) { 172 Log.debug(e); 173 return; 174 } 175 try { 176 appendFrom(); 177 List senders = null; 179 if (replyTo != null && replyTo.length > 0) { 180 senders = new ArrayList (); 181 for (int i = 0; i < replyTo.length; i++) 182 senders.add(replyTo[i]); 183 } else if (from != null && from.length > 0) { 184 senders = new ArrayList (); 185 for (int i = 0; i < from.length; i++) 186 senders.add(from[i]); 187 } 188 if (senders != null) { 189 Debug.assertTrue(senders.size() > 0); 190 for (int i = senders.size(); i-- > 0;) { 192 MailAddress a = (MailAddress) senders.get(i); 193 if (a.addressMatches(Mail.getUserMailAddress())) 194 senders.remove(i); 195 } 196 if (senders.size() == 0) { 197 for (int i = 0; i < to.length; i++) 200 senders.add(to[i]); 201 skipTo = true; 203 } 204 removeDuplicateAddresses(senders); 205 appendAddressHeader("To: ", senders); 206 } 207 group = new ArrayList (); 209 if (!skipTo && to != null) { 210 for (int i = 0; i < to.length; i++) { 211 MailAddress a = to[i]; 212 if (!a.addressMatches(Mail.getUserMailAddress())) 213 group.add(a); 214 } 215 } 216 if (cc != null) { 217 for (int i = 0; i < cc.length; i++) { 218 MailAddress a = cc[i]; 219 if (!a.addressMatches(Mail.getUserMailAddress())) 220 group.add(a); 221 } 222 } 223 removeDuplicateAddresses(group); 224 if (senders != null) { 225 for (int i = senders.size(); i-- > 0;) { 227 MailAddress toAddress = (MailAddress) senders.get(i); 228 for (int j = group.size(); j-- > 0;) { 229 MailAddress a = (MailAddress) group.get(j); 230 if (a.equals(toAddress)) { 231 group.remove(j); 233 break; 234 } 235 } 236 } 237 } 238 if (replyToGroup && group.size() > 0) 240 appendAddressHeader("Cc: ", group); 241 String subject = entryRepliedTo.getSubject(); 242 if (!subject.toLowerCase().startsWith("re:")) 243 subject = "Re: ".concat(subject); 244 appendLine("Subject: ".concat(subject)); 245 appendLine("In-Reply-To: " + entryRepliedTo.getMessageId()); 246 appendReferences(); 247 appendDefaultHeaders(); 248 appendLine(HEADER_SEPARATOR); 249 String attribution = getAttribution(messageBuffer); 250 if (attribution != null) 251 appendLine(attribution); 252 String s = 253 messageBuffer.quoteBody(getIntegerProperty(Property.WRAP_COL)); 254 if (s != null) 255 append(s); 256 appendLine(""); 257 appendSignature(); 258 unmodified(); 259 renumber(); 260 formatter.parseBuffer(); 261 setLoaded(true); 262 } 263 finally { 264 unlockWrite(); 265 } 266 } 267 268 private void init() 269 { 270 if (preferences == null) 271 preferences = Editor.preferences(); 272 initializeUndo(); 273 type = TYPE_NORMAL; 274 title = DEFAULT_TITLE; 275 if (getFile() == null) { 276 final File dir = Directories.getDraftsFolder(); 277 if (!dir.isDirectory()) 278 dir.mkdirs(); 279 if (dir.isDirectory()) { 280 setFile(Utilities.getTempFile(dir)); 281 } else { 282 setFile(Utilities.getTempFile()); 284 } 285 } 286 autosaveEnabled = true; 287 mode = SendMailMode.getMode(); 288 formatter = mode.getFormatter(this); 289 if (!getFile().isFile()) 290 lineSeparator = "\n"; 291 setInitialized(true); 292 } 293 294 public int load() 295 { 296 super.load(); 297 title = getSubject(); 298 if (title == null || title.length() == 0) 299 title = DEFAULT_TITLE; 300 return LOAD_COMPLETED; 301 } 302 303 public boolean save() 304 { 305 boolean result = super.save(); 306 for (BufferIterator it = new BufferIterator(); it.hasNext();) { 307 Buffer buf = it.nextBuffer(); 308 if (buf instanceof Drafts) { 309 Drafts drafts = (Drafts) buf; 310 drafts.reload(); 311 break; 312 } 313 } 314 return result; 315 } 316 317 public boolean hasBeenSent() 318 { 319 return hasBeenSent; 320 } 321 322 private static void removeDuplicateAddresses(List list) 323 { 324 for (int i = list.size(); i-- > 0;) { 326 MailAddress ma = (MailAddress) list.get(i); 327 String addr = ma.getAddress(); 328 for (int j = i-1; j >= 0; j--) { 329 MailAddress ma2 = (MailAddress) list.get(j); 330 if (ma.equals(ma2)) { 331 list.remove(i); 332 break; 333 } 334 String addr2 = ma2.getAddress(); 336 if (addr.equals(addr2)) { 337 list.remove(i); 338 if (ma2.getPersonal() == null || ma2.getPersonal().length() == 0) { 341 if (ma.getPersonal() != null && ma.getPersonal().length() > 0) 342 list.set(j, ma); 343 } 344 } 345 } 346 } 347 } 348 349 private void appendAddressHeader(String prefix, List list) 350 { 351 if (list == null) 352 return; 353 if (list.size() == 0) 354 return; 355 append(MailUtilities.constructAddressHeader(prefix, list)); 356 } 357 358 private void appendFrom() 359 { 360 if (!preferences.getBooleanProperty(Property.CONFIRM_SEND)) { 361 MailAddress ma = Mail.getUserMailAddress(); 362 if (ma != null) 363 appendLine("From: ".concat(ma.toString())); 364 } 365 } 366 367 private void replaceFrom(String from) 368 { 369 final Editor editor = Editor.currentEditor(); 370 final Position savedDot = editor.getDotCopy(); 371 try { 372 lockWrite(); 373 } 374 catch (InterruptedException e) { 375 Log.error(e); 376 return; 377 } 378 try { 379 CompoundEdit compoundEdit = editor.beginCompoundEdit(); 380 removeHeaders(editor, "from:"); 382 editor.addUndo(SimpleEdit.MOVE); 384 editor.getDot().moveTo(getFirstLine(), 0); 385 editor.addUndo(SimpleEdit.INSERT_STRING); 387 insertString(editor.getDot(), "From: ".concat(from).concat("\n")); 388 Line dotLine = savedDot.getLine(); 390 if (contains(dotLine)) { 391 int dotOffset = savedDot.getOffset(); 392 if (dotOffset > dotLine.length()) 393 dotOffset = dotLine.length(); 394 editor.addUndo(SimpleEdit.MOVE); 395 editor.getDot().moveTo(dotLine, dotOffset); 396 } 397 editor.addUndo(SimpleEdit.MOVE); 398 editor.moveCaretToDotCol(); 399 editor.endCompoundEdit(compoundEdit); 400 getFormatter().parseBuffer(); 401 } 402 finally { 403 unlockWrite(); 404 } 405 repaint(); 406 } 407 408 private void replaceBcc(List bccList) 409 { 410 final Editor editor = Editor.currentEditor(); 411 final Position savedDot = editor.getDotCopy(); 412 try { 413 lockWrite(); 414 } 415 catch (InterruptedException e) { 416 Log.error(e); 417 return; 418 } 419 try { 420 CompoundEdit compoundEdit = editor.beginCompoundEdit(); 421 removeHeaders(editor, "bcc:"); 423 for (Line line = getFirstLine(); line != null; line = line.next()) { 425 if (line.getText().equals(HEADER_SEPARATOR)) { 426 editor.addUndo(SimpleEdit.MOVE); 427 editor.getDot().moveTo(line, 0); 428 editor.addUndo(SimpleEdit.INSERT_STRING); 429 insertString(editor.getDot(), 430 MailUtilities.constructAddressHeader("Bcc: ", bccList).concat("\n")); 431 break; 432 } 433 } 434 Line dotLine = savedDot.getLine(); 436 if (contains(dotLine)) { 437 int dotOffset = savedDot.getOffset(); 438 if (dotOffset > dotLine.length()) 439 dotOffset = dotLine.length(); 440 editor.addUndo(SimpleEdit.MOVE); 441 editor.getDot().moveTo(dotLine, dotOffset); 442 } 443 editor.addUndo(SimpleEdit.MOVE); 444 editor.moveCaretToDotCol(); 445 editor.endCompoundEdit(compoundEdit); 446 getFormatter().parseBuffer(); 447 } 448 finally { 449 unlockWrite(); 450 } 451 repaint(); 452 } 453 454 private void appendReferences() 455 { 456 if (entryRepliedTo != null) { 457 final String prefix = "References: "; 458 FastStringBuffer sb = new FastStringBuffer(prefix); 459 int length = prefix.length(); 460 String [] oldReferences = entryRepliedTo.getReferences(); 461 final String [] references; 462 if (oldReferences != null) { 463 references = new String [oldReferences.length+1]; 464 System.arraycopy(oldReferences, 0, references, 0, oldReferences.length); 465 } else 466 references = new String [1]; 467 references[references.length-1] = entryRepliedTo.getMessageId(); 468 for (int i = 0; i < references.length; i++) { 469 String s = references[i]; 470 if (i > 0 && length + s.length() > 990) { 471 sb.append(lineSeparator); 473 final String indent = " "; 474 sb.append(indent); sb.append(s); 476 length = indent.length() + s.length(); 477 } else { 478 if (i > 0) { 479 sb.append(" "); 480 length++; 481 } 482 sb.append(s); 483 length += s.length(); 484 } 485 } 486 append(sb.toString()); 487 } 488 } 489 490 private void appendDefaultHeaders() 491 { 492 String bcc = preferences.getStringProperty("bcc"); 493 if (bcc != null) 494 appendLine("Bcc: ".concat(bcc)); 495 } 496 497 private void appendSignature() 498 { 499 File file = null; 500 String fileName = preferences.getStringProperty(Property.SIGNATURE); 501 if (fileName != null) 502 file = File.getInstance(fileName); 503 else if (Platform.isPlatformUnix()) 504 file = File.getInstance(Directories.getUserHomeDirectory(), 505 ".signature"); 506 if (file == null || !file.isFile()) 507 return; 508 FastStringBuffer sb = new FastStringBuffer(); 509 try { 510 BufferedReader reader = 511 new BufferedReader (new InputStreamReader (file.getInputStream())); 512 while (true) { 513 String s = reader.readLine(); 514 if (s == null) 515 break; 516 if (sb.length() > 0) 517 sb.append('\n'); 518 sb.append(s); 519 } 520 reader.close(); 521 } 522 catch (IOException e) { 523 Log.error(e); 524 } 525 if (sb.length() > 0) 526 append(sb.toString()); 527 } 528 529 public static final String getHeaderSeparator() 530 { 531 return HEADER_SEPARATOR; 532 } 533 534 public void modified() 535 { 536 super.modified(); 537 setTitle(); 538 } 539 540 private void setTitle() 541 { 542 String s = getSubject(); 543 if (s == null || s.length() == 0) 544 s = DEFAULT_TITLE; 545 if (title == null || !title.equals(s)) { 546 title = s; 547 Sidebar.setUpdateFlagInAllFrames(SIDEBAR_BUFFER_LIST_CHANGED); 548 } 549 } 550 551 public void attachFile() 552 { 553 Editor editor = Editor.currentEditor(); 554 File file = OpenFileDialog.getLocalFile(editor, "Attach File"); 555 if (file != null && file.isFile()) { 556 for (Line line = getFirstLine(); line != null; line = line.next()) { 557 if (line.getText().equals(HEADER_SEPARATOR)) { 558 Position pos = new Position(line, 0); 559 insertString(pos, 560 "Attachment: " + file.canonicalPath() + "\n"); 561 break; 562 } 563 } 564 } 565 } 566 567 public void send() 568 { 569 if (!checkHeader()) 570 return; 571 checkRecipients(); 572 checkEmpty(); 573 if (preferences.getBooleanProperty(Property.CONFIRM_SEND)) { 574 if (!confirmSend()) 575 return; 576 } 577 Runnable sendRunnable = new Runnable () { 578 public void run() 579 { 580 boolean succeeded = false; 581 if (smtp != null) 582 session = SmtpSession.getSession(smtp); 583 else 584 session = SmtpSession.getDefaultSession(); 585 if (session != null) { 586 File messageFile = Utilities.getTempFile(); 587 try { 588 OutputStreamWriter writer = 589 new OutputStreamWriter (messageFile.getOutputStream()); 590 writeMessageText(writer); 591 writer.flush(); 592 writer.close(); 593 succeeded = session.sendMessage(SendMail.this, 594 messageFile); 595 if (succeeded) 596 writeFcc(messageFile); 597 messageFile.delete(); 598 } 599 catch (IOException e) { 600 Log.error(e); 601 } 602 } 603 hasBeenSent = succeeded; 604 SwingUtilities.invokeLater(succeeded ? succeededRunnable : 605 errorRunnable); 606 } 607 }; 608 setBusy(true); 609 new Thread (sendRunnable).start(); 610 } 611 612 private boolean confirmSend() 613 { 614 final Editor editor = Editor.currentEditor(); 615 ConfirmSendDialog d = new ConfirmSendDialog(editor, this); 616 editor.centerDialog(d); 617 d.show(); 618 if (d.cancelled()) 619 return false; 620 String from = d.getFrom(); 621 if (from != null) 622 replaceFrom(from); 623 if (d.bccAddSender() || d.bccAddOther()) { 624 List bccList = new ArrayList (); 625 MailAddress[] bcc = MailAddress.parseAddresses(getBcc()); 626 if (bcc != null) { 627 for (int i = 0; i < bcc.length; i++) 628 bccList.add(bcc[i]); 629 } 630 if (d.bccAddSender() && from != null) 631 bccList.add(MailAddress.parseAddress(from)); 632 if (d.bccAddOther()) { 633 String bccOther = d.getBccOther(); 634 if (bccOther != null && bccOther.length() > 0) 635 bccList.add(MailAddress.parseAddress(bccOther)); 636 } 637 if (bccList.size() > 0) { 638 removeDuplicateAddresses(bccList); 639 replaceBcc(bccList); 640 } 641 } 642 smtp = d.getSmtp(); 643 return true; 644 } 645 646 private boolean checkHeader() 647 { 648 for (Line line = getFirstLine(); line != null; line = line.next()) { 649 if (line.getText().equals(HEADER_SEPARATOR)) 650 return true; 651 } 652 MessageDialog.showMessageDialog(Editor.currentEditor(), 653 "Message separator line is missing", "Error"); 654 return false; 655 } 657 658 private void checkRecipients() 660 { 661 for (Line line = getFirstLine(); line != null; line = line.next()) { 662 String text = line.getText(); 663 if (text.equals(HEADER_SEPARATOR)) 664 return; 665 String lower = text.toLowerCase(); 666 if (lower.startsWith("to:") || lower.startsWith("cc:") || 667 lower.startsWith("bcc:")) { 668 Line continuation = line.next(); 669 while (continuation != null && continuation.length() > 0) { 670 char c = continuation.charAt(0); 671 if (c == ' ' || c == '\t') { 672 String s = trimTrailing(line.getText()); 675 int length = s.length(); 676 if (length > 0 && s.charAt(length - 1) != ',') 677 line.setText(s.concat(",")); 678 line = continuation; 679 continuation = continuation.next(); 680 } else 681 break; 682 } 683 } 684 } 685 } 686 687 private static String trimTrailing(String s) 689 { 690 int length = s.length(); 691 if (length == 0 || !Character.isWhitespace(s.charAt(length - 1))) 692 return s; 693 do { 694 --length; 695 } while (length > 0 && Character.isWhitespace(s.charAt(length - 1))); 696 return s.substring(0, length); 697 } 698 699 private void checkEmpty() 700 { 701 String eom = getStringProperty(Property.EOM); 702 if (eom == null || eom.length() == 0) 703 return; 704 Line subjectLine = null; 705 Line line; 706 for (line = getFirstLine(); line != null; line = line.next()) { 707 String text = line.getText(); 708 if (text.toLowerCase().startsWith("subject:")) 709 subjectLine = line; 710 else if (text.toLowerCase().startsWith("attachment:")) 711 return; else if (text.equals(HEADER_SEPARATOR)) 713 break; 714 } 715 if (subjectLine == null) 716 return; 717 if (line != null) { 718 line = line.next(); 719 while (line != null) { 721 if (!line.isBlank()) 722 return; line = line.next(); 724 } 725 } 726 String text = subjectLine.getText(); 728 if (!text.endsWith(eom)) 729 subjectLine.setText(text + eom); 730 } 731 732 private Runnable succeededRunnable = new Runnable () { 733 public void run() 734 { 735 unmodified(); 736 if (reply && mailbox != null && entryRepliedTo != null) 737 mailbox.setAnsweredFlag(entryRepliedTo); 738 File file = getFile(); 739 if (file.isFile()) { 740 Log.debug("deleting draft " + file); 741 file.delete(); 742 for (BufferIterator it = new BufferIterator(); it.hasNext();) { 743 Buffer buf = it.nextBuffer(); 744 if (buf instanceof Drafts) { 745 Drafts drafts = (Drafts) buf; 746 drafts.reload(); 747 break; 748 } 749 } 750 } 751 setBusy(false); 752 kill(); 753 EditorIterator iter = new EditorIterator(); 754 while (iter.hasNext()) 755 iter.nextEditor().updateDisplay(); 756 } 757 }; 758 759 private Runnable errorRunnable = new Runnable () { 760 public void run() 761 { 762 setBusy(false); 763 final Editor editor = Editor.currentEditor(); 764 editor.updateDisplay(); 765 MessageDialog.showMessageDialog(editor, 766 session != null ? session.getErrorText() : "Unable to send message", 767 "Send Mail"); 768 } 769 }; 770 771 private void writeMessageText(Writer writer) 772 { 773 final String separator = "\n"; 774 try { 775 writer.write("Date: " + RFC822Date.getDateTimeString() + separator); 776 if (getFrom() == null) 777 writer.write("From: " + getDefaultFromAddress() + separator); 778 Line line; 779 boolean inBcc = false; 781 List attachments = null; 782 for (line = getFirstLine(); line != null; line = line.next()) { 783 String text = line.getText(); 784 if (text.length() == 0) { 785 continue; 788 } 789 if (text.equals(HEADER_SEPARATOR)) { 790 writer.write("Message-ID: " + Mail.generateMessageId() + separator); 792 writer.write("User-Agent: " + Version.getLongVersionString() + separator); 793 attachments = parseAttachments(); 794 if (attachments != null) 795 writer.write(generateMimeHeaders(separator)); 796 line = line.next(); 798 break; 799 } 800 if (inBcc) { 801 char c = text.charAt(0); 802 if (c == ' ' || c == '\t') { 803 continue; 805 } else { 806 inBcc = false; 808 } 809 } 810 if (text.toLowerCase().startsWith("bcc:")) { 811 inBcc = true; 812 continue; 813 } 814 if (text.toLowerCase().startsWith("attachment:")) 815 continue; 816 writer.write(line.getText()); 817 writer.write(separator); 818 } 819 final Line startOfBody = line; 820 boolean qp = false; 822 for (line = startOfBody; line != null; line = line.next()) { 823 if (requiresEncoding(line)) { 824 qp = true; 825 break; 826 } 827 } 828 String transferEncoding = qp ? "quoted-printable" : "7bit"; 829 String characterEncoding = 830 getStringProperty(Property.DEFAULT_ENCODING); 831 if (attachments != null) { 832 writer.write(separator); 834 writer.write("This is a multi-part message in MIME format."); 835 writer.write(separator); 836 writer.write("--"); 837 writer.write(getBoundary()); 838 writer.write(separator); 839 writer.write("Content-Type: text/plain"); 840 if (qp) { 841 writer.write("; charset="); 842 writer.write(getCharSetName(characterEncoding)); 843 } 844 writer.write(separator); 845 writer.write("Content-Transfer-Encoding: "); 846 writer.write(transferEncoding); 847 writer.write(separator); 848 writer.write(separator); 849 } else { 850 if (qp) { 852 writer.write("Content-Type: text/plain"); 853 if (qp) { 854 writer.write("; charset="); 855 writer.write(getCharSetName(characterEncoding)); 856 } 857 writer.write(separator); 858 writer.write("Content-Transfer-Encoding: "); 859 writer.write(transferEncoding); 860 writer.write(separator); 861 } 862 writer.write(separator); 864 } 865 for (line = startOfBody; line != null; line = line.next()) { 867 String s = line.getText(); 868 if (qp) { 869 writer.write(QuotedPrintableEncoder.encode(s, 870 characterEncoding, separator)); 871 } else { 872 if (s.length() > 0 && s.charAt(0) == '.') 874 writer.write('.'); 875 writer.write(s); 876 } 877 writer.write(separator); 878 } 879 if (attachments != null) { 881 for (int i = 0; i < attachments.size(); i++) { 882 String fullPath = (String ) attachments.get(i); 883 File file = File.getInstance(fullPath); 884 String contentType = getContentTypeForFile(file); 885 Log.debug("contentType = " + contentType); 886 writer.write("--"); 887 writer.write(getBoundary()); 888 writer.write(separator); 889 writer.write("Content-Type: "); 890 writer.write(contentType); 891 writer.write(separator); 892 writer.write("Content-Transfer-Encoding: base64"); 893 writer.write(separator); 894 writer.write("Content-Disposition: attachment; filename=\""); 895 writer.write(file.getName()); 896 writer.write('"'); 897 writer.write(separator); 898 writer.write(separator); 899 writeEncodedFile(file, writer, separator); 900 writer.write(separator); 901 } 902 writer.write("--"); 903 writer.write(getBoundary()); 904 writer.write("--"); 905 writer.write(separator); 906 } 907 } 908 catch (IOException e) { 909 Log.error(e); 910 } 911 } 912 913 private void writeEncodedFile(File file, Writer writer, String separator) 914 { 915 if (file == null || !file.isFile() || !file.canRead()) 916 return; 917 try { 918 FileInputStream inputStream = file.getInputStream(); 919 Base64Encoder encoder = new Base64Encoder(inputStream); 920 String s; 921 while ((s = encoder.encodeLine()) != null) { 922 writer.write(s); 923 writer.write(separator); 924 } 925 writer.flush(); 926 inputStream.close(); 927 } 928 catch (IOException e) { 929 Log.error(e); 930 } 931 } 932 933 public String getFrom() 934 { 935 return getHeaderValue("from"); 936 } 937 938 public String getFromAddress() 939 { 940 String from = getFrom(); 941 if (from != null) 942 return getAddress(from); 943 return Mail.getUserMailAddress().getAddress(); 945 } 946 947 private final String getDefaultFromAddress() 948 { 949 return Mail.getUserMailAddress().toString(); 950 } 951 952 public static String getAddress(String s) 955 { 956 if (s == null) 957 return null; 958 int index = s.indexOf('@'); 959 if (index < 0) 960 return null; 961 int begin = 0; 962 int end = s.length(); 963 for (int i = index; i-- > 0;) { 964 char c = s.charAt(i); 965 if (c == ',' || c == '"' || c == '<' || Character.isWhitespace(c)) { 966 begin = i + 1; 967 break; 968 } 969 } 970 for (int i = index + 1; i < end; i++) { 971 char c = s.charAt(i); 972 if (c == ',' || c == '"' || c == '>' || Character.isWhitespace(c)) { 973 end = i; 974 break; 975 } 976 } 977 return s.substring(begin, end); 978 } 979 980 public String getTo() 981 { 982 Log.debug("getTo to = |" + getHeaderValue("to") + "|"); 983 return getHeaderValue("to"); 984 } 985 986 public String getCc() 987 { 988 Log.debug("getCc cc = |" + getHeaderValue("cc") + "|"); 989 return getHeaderValue("cc"); 990 } 991 992 public String getBcc() 993 { 994 Log.debug("getBcc bcc = |" + getHeaderValue("bcc") + "|"); 995 return getHeaderValue("bcc"); 996 } 997 998 public void ccGroup() 999 { 1000 if (group == null) 1001 return; 1002 List newList = new ArrayList (group); 1004 MailAddress[] cc = MailAddress.parseAddresses(getCc()); 1006 if (cc != null) { 1007 for (int i = 0; i < cc.length; i++) { 1008 MailAddress oldAddress = cc[i]; 1009 boolean isDuplicate = false; 1011 for (int j = newList.size()-1; j >= 0; j--) { 1012 MailAddress a = (MailAddress) newList.get(j); 1013 if (oldAddress.equals(a)) { 1014 isDuplicate = true; 1015 break; 1016 } 1017 } 1018 if (!isDuplicate) 1019 newList.add(oldAddress); 1020 } 1021 } 1022 MailAddress[] to = MailAddress.parseAddresses(getTo()); 1024 if (to != null) { 1025 for (int i = to.length-1; i >= 0; i--) { 1026 MailAddress toAddress = to[i]; 1027 for (int j = newList.size()-1; j >= 0; j--) { 1028 MailAddress a = (MailAddress) newList.get(j); 1029 if (a.equals(toAddress)) { 1030 Log.debug("removing " + (MailAddress)newList.get(j)); 1032 newList.remove(j); 1033 break; 1034 } 1035 } 1036 } 1037 } 1038 final Editor editor = Editor.currentEditor(); 1039 final Position savedDot = editor.getDotCopy(); 1040 try { 1041 lockWrite(); 1042 } 1043 catch (InterruptedException e) { 1044 Log.error(e); 1045 return; 1046 } 1047 try { 1048 CompoundEdit compoundEdit = editor.beginCompoundEdit(); 1049 removeHeaders(editor, "cc:"); 1051 for (Line line = getFirstLine(); line != null; line = line.next()) { 1053 if (line.getText().toLowerCase().startsWith("to:")) { 1054 for (Line next = line.next(); next != null; next = next.next()) { 1056 if (next.length() == 0 || next.charAt(0) == ' ' || 1057 next.charAt(0) == '\t') { 1058 continue; 1060 } else { 1061 editor.addUndo(SimpleEdit.MOVE); 1063 editor.getDot().moveTo(next, 0); 1064 break; 1065 } 1066 } 1067 break; 1068 } 1069 } 1070 editor.addUndo(SimpleEdit.INSERT_STRING); 1072 FastStringBuffer sb = new FastStringBuffer(); 1073 sb.append(MailUtilities.constructAddressHeader("Cc: ", newList)); 1074 sb.append(lineSeparator); 1075 insertString(editor.getDot(), sb.toString()); 1076 Line dotLine = savedDot.getLine(); 1078 if (contains(dotLine)) { 1079 int dotOffset = savedDot.getOffset(); 1080 if (dotOffset > dotLine.length()) 1081 dotOffset = dotLine.length(); 1082 editor.addUndo(SimpleEdit.MOVE); 1083 editor.getDot().moveTo(dotLine, dotOffset); 1084 } 1085 editor.addUndo(SimpleEdit.MOVE); 1086 editor.moveCaretToDotCol(); 1087 editor.endCompoundEdit(compoundEdit); 1088 getFormatter().parseBuffer(); 1089 } 1090 finally { 1091 unlockWrite(); 1092 } 1093 repaint(); 1094 } 1095 1096 private void removeHeaders(Editor editor, String hdr) 1100 { 1101 hdr = hdr.toLowerCase(); 1103 if (!hdr.endsWith(":")) 1104 hdr = hdr.concat(":"); 1105 while (true) { 1106 Line beginLine = null; 1108 for (Line line = getFirstLine(); line != null; line = line.next()) { 1110 String text = line.getText(); 1111 if (text.equals(HEADER_SEPARATOR)) 1112 return; 1113 if (text.toLowerCase().startsWith(hdr)) { 1114 beginLine = line; 1115 break; 1116 } 1117 } 1118 if (beginLine == null) 1120 return; 1121 Line endLine = null; 1124 for (Line line = beginLine.next(); line != null; line = line.next()) { 1125 String text = line.getText(); 1126 if (text.length() == 0) 1127 continue; 1128 if (text.equals(HEADER_SEPARATOR)) { 1129 endLine = line; 1130 break; 1131 } 1132 char c = line.getText().charAt(0); 1133 if (c != ' ' && c != '\t') { 1134 endLine = line; 1135 break; 1136 } 1137 } 1138 if (endLine == null) 1142 return; 1143 Region r = new Region(this, new Position(beginLine, 0), 1145 new Position(endLine, 0)); 1146 editor.addUndo(SimpleEdit.MOVE); 1147 editor.getDot().moveTo(r.getBegin()); 1148 editor.addUndoDeleteRegion(r); 1149 r.delete(); 1150 } 1151 } 1152 1153 public List getAddressees() 1154 { 1155 ArrayList list = new ArrayList (); 1156 appendAddressesFromString(list, getTo()); 1157 appendAddressesFromString(list, getCc()); 1158 appendAddressesFromString(list, getBcc()); 1159 for (int i = 0; i < list.size(); i++) 1160 Log.debug("|" + (String ) list.get(i) + "|"); 1161 return list; 1162 } 1163 1164 private static void appendAddressesFromString(List list, String s) 1165 { 1166 if (s == null) 1167 return; 1168 s = s.trim(); 1169 int length = s.length(); 1170 if (length == 0) 1171 return; 1172 FastStringBuffer sb = new FastStringBuffer(64); 1173 boolean inQuote = false; 1174 for (int i = 0; i < length; i++) { 1175 char c = s.charAt(i); 1176 switch (c) { 1177 case '"': 1178 inQuote = !inQuote; 1179 sb.append(c); 1180 break; 1181 case ',': 1182 if (inQuote) { 1183 sb.append(c); 1186 } else { 1187 String address = sb.toString().trim(); 1189 if (address.length() > 0) 1190 list.add(address); 1191 sb.setLength(0); 1192 } 1193 break; 1194 default: 1195 sb.append(c); 1196 break; 1197 } 1198 } 1199 if (sb.length() > 0) { 1200 String address = sb.toString().trim(); 1201 if (address.length() > 0) 1202 list.add(address); 1203 } 1204 } 1205 1206 public String getSubject() 1207 { 1208 return getHeaderValue("subject"); 1209 } 1210 1211 private String getHeaderValue(String headerName) 1213 { 1214 boolean combine = false; 1215 String key = headerName.toLowerCase() + ':'; 1216 if (key.equals("to:") || key.equals("cc:") || key.equals("bcc:")) 1217 combine = true; 1218 FastStringBuffer sb = null; 1219 for (Line line = getFirstLine(); line != null; line = line.next()) { 1220 String text = line.getText(); 1221 if (text.equals(HEADER_SEPARATOR)) 1222 break; 1223 if (text.toLowerCase().startsWith(key)) { 1224 if (sb == null) { 1225 sb = new FastStringBuffer(); 1226 } else { 1227 sb.append(", "); 1230 } 1231 sb.append(text.substring(key.length()).trim()); 1232 Line continuation = line.next(); 1233 while (continuation != null && continuation.length() > 0) { 1234 char c = continuation.charAt(0); 1235 if (c == ' ' || c == '\t') { 1236 sb.append(continuation.getText()); 1238 line = continuation; 1239 continuation = continuation.next(); 1240 continue; 1241 } else 1242 break; 1243 } 1244 if (!combine) 1245 break; 1246 } 1247 } 1248 return sb != null ? sb.toString() : null; 1249 } 1250 1251 public Position getInitialDotPos() 1252 { 1253 if (reply) { 1254 for (Line line = getFirstLine(); line != null; line = line.next()) { 1255 if (line.getText().equals(HEADER_SEPARATOR)) { 1256 line = line.next(); 1257 if (line != null) 1258 return new Position(line, 0); 1259 } 1260 } 1261 } else { 1262 for (Line line = getFirstLine(); line != null; line = line.next()) { 1263 if (line.getText().startsWith("To: ")) 1264 return new Position(line, line.length()); 1265 } 1266 } 1267 return new Position(getFirstLine(), 0); 1270 } 1271 1272 private List parseAttachments() 1273 { 1274 ArrayList attachments = null; 1275 for (Line line = getFirstLine(); line != null; line = line.next()) { 1276 if (line.getText().equals(HEADER_SEPARATOR)) 1277 break; 1278 if (line.getText().toLowerCase().startsWith("attachment:")) { 1279 String filename = line.getText().substring(11).trim(); 1280 if (filename.length() > 0) { 1281 if (attachments == null) 1282 attachments = new ArrayList (); 1283 attachments.add(filename); 1284 } 1285 } 1286 } 1287 return attachments; 1288 } 1289 1290 private String generateMimeHeaders(String separator) 1291 { 1292 FastStringBuffer sb = new FastStringBuffer(); 1293 sb.append("MIME-Version: 1.0"); 1294 sb.append(separator); 1295 sb.append("Content-Type: multipart/mixed; boundary=\""); 1296 sb.append(getBoundary()); 1297 sb.append('"'); 1298 sb.append(separator); 1299 return sb.toString(); 1300 } 1301 1302 private String getBoundary() 1303 { 1304 if (boundary == null) { 1305 FastStringBuffer sb = new FastStringBuffer(16); 1306 Random random = new Random (); 1307 char[] chars = getBoundaryChars(); 1308 for (int i = 0; i < 16; i++) { 1309 int index = random.nextInt(chars.length); 1310 sb.append(chars[index]); 1311 } 1312 boundary = sb.toString(); 1313 } 1314 return boundary; 1315 } 1316 1317 private char[] getBoundaryChars() 1318 { 1319 return Base64Encoder.getBase64Chars(); 1320 } 1321 1322 private String getContentTypeForFile(File file) 1323 { 1324 boolean isBinary = false; 1325 try { 1326 BufferedInputStream inputStream = 1327 new BufferedInputStream (file.getInputStream()); 1328 int c; 1329 while ((c = inputStream.read()) >= 0) { 1330 if (c >= ' ' && c < 127) 1331 continue; 1332 if (c == '\r') 1333 continue; 1334 if (c == '\n') 1335 continue; 1336 if (c == '\t') 1337 continue; 1338 if (c == '\f') 1339 continue; 1340 isBinary = true; 1342 break; 1343 } 1344 inputStream.close(); 1345 } 1346 catch (IOException e) { 1347 Log.error(e); 1348 isBinary = true; 1349 } 1350 String extension = Utilities.getExtension(file); 1351 if (extension != null) 1352 extension = extension.toLowerCase(); 1353 if (isBinary) { 1354 if (extension != null) { 1355 extension = extension.toLowerCase(); 1357 if (extension.equals(".jpeg") || extension.equals(".jpg")) 1358 return "image/jpeg"; 1359 if (extension.equals(".gif")) 1360 return "image/gif"; 1361 if (extension.equals(".png")) 1362 return "image/png"; 1363 } 1364 return "application/octet-stream"; 1366 } else { 1367 if (extension != null) { 1368 if (extension.equals(".html") || extension.equals(".htm")) 1369 return "text/html"; 1370 if (extension.equals(".xml")) 1371 return "text/xml"; 1372 } 1373 return "text/plain"; 1375 } 1376 } 1377 1378 public File getCurrentDirectory() 1379 { 1380 return Directories.getUserHomeDirectory(); 1381 } 1382 1383 public File getCompletionDirectory() 1384 { 1385 return Directories.getUserHomeDirectory(); 1386 } 1387 1388 public Icon getIcon() 1390 { 1391 if (isModified()) 1392 return Utilities.getIconFromFile("compose_modified.png"); 1393 return Utilities.getIconFromFile("compose.png"); 1394 } 1395 1396 public String getFileNameForDisplay() 1397 { 1398 return ""; 1399 } 1400 1401 private static boolean requiresEncoding(Line line) 1402 { 1403 final String text = line.getText(); 1404 if (text.length() > 990) 1405 return true; 1406 if (text.length() == 0) 1407 return false; 1408 for (int i = text.length()-1; i >= 0; i--) { 1409 char c = text.charAt(i); 1410 if (c < ' ' && c != '\t') 1411 return true; 1412 if (c >= 127) 1413 return true; 1414 } 1415 if (text.startsWith("From ")) 1416 return true; 1417 if (text.charAt(0) == '.') 1418 return true; 1419 return false; 1420 } 1421 1422 private static final String getCharSetName(String characterEncoding) 1423 { 1424 if (characterEncoding.equals("ASCII")) 1425 return "us-ascii"; 1426 if (characterEncoding.startsWith("ISO8859_")) 1427 return "iso-8859-" + characterEncoding.substring(8); 1428 return characterEncoding; 1430 } 1431 1432 public boolean isHeaderLine(Line maybe) 1433 { 1434 for (Line line = getFirstLine(); line != null; line = line.next()) { 1435 if (line == maybe) 1436 return true; 1437 if (line.getText().equals(HEADER_SEPARATOR)) 1438 return false; 1439 } 1440 return false; 1441 } 1442 1443 public void tab(Editor editor) 1444 { 1445 Line dotLine = editor.getDotLine(); 1446 if (dotLine.getText().equals(HEADER_SEPARATOR)) { 1447 Line line = dotLine.next(); 1448 if (line != null) 1449 editor.moveDotTo(line, 0); 1450 return; 1451 } 1452 for (Line line = getFirstLine(); line != null; line = line.next()) { 1453 if (line == dotLine) { 1454 break; 1455 } else if (line.getText().equals(HEADER_SEPARATOR)) { 1456 editor.insertTab(); 1458 return; 1459 } 1460 } 1461 Line line = null; 1464 for (line = dotLine.next(); line != null; line = line.next()) { 1465 if (!isContinuationLine(line)) 1466 break; 1467 } 1468 if (line == null) 1469 return; 1470 if (line.getText().equals(HEADER_SEPARATOR)) { 1472 line = line.next(); 1473 if (line != null) 1474 editor.moveDotTo(line, 0); 1475 return; 1476 } 1477 while (true) { 1479 Line next = line.next(); 1480 if (next == null) 1481 return; 1482 if (!isContinuationLine(next)) { 1483 editor.moveDotTo(line, line.length()); 1486 return; 1487 } 1488 line = next; 1489 } 1490 } 1491 1492 public void backTab(Editor editor) 1493 { 1494 Line dotLine = editor.getDotLine(); 1495 for (Line line = getFirstLine(); line != null; line = line.next()) { 1498 if (line == dotLine) { 1499 break; 1500 } else if (line.getText().equals(HEADER_SEPARATOR)) { 1501 line = line.previous(); 1504 if (line != null) 1505 editor.moveDotTo(line, line.length()); 1506 return; 1507 } 1508 } 1509 Line line = dotLine; 1512 while (isContinuationLine(line)) { 1513 line = line.previous(); 1514 if (line == null) 1515 return; } 1517 line = line.previous(); 1520 if (line != null) 1521 editor.moveDotTo(line, line.length()); 1522 } 1523 1524 private static final boolean isContinuationLine(Line line) 1525 { 1526 if (line.length() == 0) 1527 return false; 1528 char c = line.charAt(0); 1529 return c == ' ' || c == '\t'; 1530 } 1531 1532 private void writeFcc(File messageFile) 1534 { 1535 final File sentMessagesFile = Mail.getSentMessagesFile(); 1536 if (sentMessagesFile == null) 1537 return; 1538 try { 1539 BufferedReader reader = 1540 new BufferedReader (new InputStreamReader (messageFile.getInputStream())); 1541 BufferedWriter writer = 1542 new BufferedWriter (new FileWriter (sentMessagesFile.canonicalPath(), true)); 1543 writer.write("From - "); 1544 SimpleDateFormat dateFormatter = 1545 new SimpleDateFormat ("EEE MMM d HH:mm:ss yyyy"); 1546 Calendar cal = Calendar.getInstance(); 1547 String dateString = dateFormatter.format(cal.getTime()); 1548 writer.write(dateString); 1549 writer.write('\n'); 1550 String s; 1551 while((s = reader.readLine()) != null) { 1552 writer.write(s); 1553 writer.write('\n'); 1554 } 1555 writer.write('\n'); 1556 writer.flush(); 1557 writer.close(); 1558 reader.close(); 1559 } 1560 catch (IOException e) { 1561 Log.error(e); 1562 } 1563 } 1564 1565 public Expansion getExpansion(Position dot) 1566 { 1567 int endOfHeaders = -1; 1568 for (Line line = getFirstLine(); line != null; line = line.next()) { 1569 if (line.getText().equals(HEADER_SEPARATOR)) { 1570 endOfHeaders = line.lineNumber(); 1571 break; 1572 } 1573 } 1574 if (dot.lineNumber() < endOfHeaders) 1575 return new MailAddressExpansion(dot); 1576 else 1577 return super.getExpansion(dot); 1578 } 1579 1580 private static final RE dateRE = 1581 new UncheckedRE("[A-Za-z]+, [0-9][0-9]? [A-Za-z]+ [0-9][0-9][0-9][0-9]"); 1582 private static final RE timeRE = 1583 new UncheckedRE("[0-9:]+ [+-][0-9][0-9][0-9][0-9]"); 1584 1585 private static String getAttribution(MessageBuffer messageBuffer) 1586 { 1587 if (messageBuffer == null) 1588 return null; 1589 Mailbox mailbox = messageBuffer.getMailbox(); 1590 if (mailbox == null) 1591 return null; 1592 MailboxEntry entry = messageBuffer.getMailboxEntry(); 1593 if (entry == null) 1594 return null; 1595 String template = preferences.getStringProperty(Property.ATTRIBUTION); 1596 if (template == null || template.length() == 0) 1597 return null; 1598 FastStringBuffer sb = new FastStringBuffer(); 1599 final int limit = template.length(); 1600 for (int i = 0; i < limit; i++) { 1601 char c = template.charAt(i); 1602 if (c == '%' && ++i < limit) { 1603 c = template.charAt(i); 1604 switch (c) { 1605 case 'd': { 1606 Message message = messageBuffer.getMessage(); 1608 if (message == null) 1609 return null; 1610 String dateTime = message.getHeaderValue(Headers.DATE); 1611 REMatch m1 = dateRE.getMatch(dateTime); 1612 if (m1 != null) { 1613 REMatch m2 = 1614 timeRE.getMatch(dateTime.substring(m1.getEndIndex())); 1615 if (m2 != null) { 1616 sb.append(m1.toString()); 1617 sb.append(" at "); 1618 sb.append(m2.toString()); 1619 } 1620 } 1621 break; 1622 } 1623 case 'n': { 1624 MailAddress[] from = entry.getFrom(); 1626 if (from == null || from.length == 0) 1627 return null; 1628 String personal = from[0].getPersonal(); 1629 if (personal != null && personal.length() > 0) 1630 sb.append(personal); 1631 else { 1632 String addr = from[0].getAddress(); 1633 if (addr != null && addr.length() > 0) 1634 sb.append(addr); 1635 else 1636 return null; 1637 } 1638 break; 1639 } 1640 default: 1641 Log.error("invalid format sequence \"%" + c + '"' + 1642 " in attribution"); 1643 return null; 1644 } 1645 } else 1646 sb.append(c); 1647 } 1648 return sb.toString(); 1649 } 1650} 1651 | Popular Tags |