0001: /*
0002: * SendMail.java
0003: *
0004: * Copyright (C) 2000-2003 Peter Graves
0005: * $Id: SendMail.java,v 1.9 2003/08/09 17:42:30 piso Exp $
0006: *
0007: * This program is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU General Public License
0009: * as published by the Free Software Foundation; either version 2
0010: * of the License, or (at your option) any later version.
0011: *
0012: * This program is distributed in the hope that it will be useful,
0013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0015: * GNU General Public License for more details.
0016: *
0017: * You should have received a copy of the GNU General Public License
0018: * along with this program; if not, write to the Free Software
0019: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
0020: */
0021:
0022: package org.armedbear.j.mail;
0023:
0024: import gnu.regexp.RE;
0025: import gnu.regexp.REMatch;
0026: import gnu.regexp.UncheckedRE;
0027: import java.io.BufferedInputStream;
0028: import java.io.BufferedReader;
0029: import java.io.BufferedWriter;
0030: import java.io.FileInputStream;
0031: import java.io.FileWriter;
0032: import java.io.IOException;
0033: import java.io.InputStreamReader;
0034: import java.io.OutputStreamWriter;
0035: import java.io.Writer;
0036: import java.text.SimpleDateFormat;
0037: import java.util.ArrayList;
0038: import java.util.Calendar;
0039: import java.util.List;
0040: import java.util.Random;
0041: import javax.swing.Icon;
0042: import javax.swing.SwingUtilities;
0043: import javax.swing.undo.CompoundEdit;
0044: import org.armedbear.j.Buffer;
0045: import org.armedbear.j.BufferIterator;
0046: import org.armedbear.j.Debug;
0047: import org.armedbear.j.Directories;
0048: import org.armedbear.j.Editor;
0049: import org.armedbear.j.EditorIterator;
0050: import org.armedbear.j.Expansion;
0051: import org.armedbear.j.File;
0052: import org.armedbear.j.FastStringBuffer;
0053: import org.armedbear.j.Headers;
0054: import org.armedbear.j.Line;
0055: import org.armedbear.j.Log;
0056: import org.armedbear.j.MessageDialog;
0057: import org.armedbear.j.OpenFileDialog;
0058: import org.armedbear.j.Platform;
0059: import org.armedbear.j.Position;
0060: import org.armedbear.j.Preferences;
0061: import org.armedbear.j.Property;
0062: import org.armedbear.j.Region;
0063: import org.armedbear.j.Sidebar;
0064: import org.armedbear.j.SimpleEdit;
0065: import org.armedbear.j.Utilities;
0066: import org.armedbear.j.Version;
0067:
0068: public final class SendMail extends Buffer {
0069: private final static String HEADER_SEPARATOR = "--text follows this line--";
0070: private final static String DEFAULT_TITLE = "Compose";
0071:
0072: private static Preferences preferences;
0073:
0074: private boolean reply;
0075: private List group;
0076: private Mailbox mailbox;
0077: private MailboxEntry entryRepliedTo;
0078: private String boundary;
0079: private String smtp;
0080: private SmtpSession session;
0081: private boolean hasBeenSent;
0082:
0083: public SendMail() {
0084: super ();
0085: init();
0086: try {
0087: lockWrite();
0088: } catch (InterruptedException e) {
0089: Log.debug(e);
0090: return;
0091: }
0092: try {
0093: appendFrom();
0094: appendLine("To: ");
0095: appendLine("Subject: ");
0096: appendDefaultHeaders();
0097: appendLine(HEADER_SEPARATOR);
0098: appendLine("");
0099: appendSignature();
0100: renumber();
0101: formatter.parseBuffer();
0102: setLoaded(true);
0103: } finally {
0104: unlockWrite();
0105: }
0106: }
0107:
0108: // Re-opening an unsent message.
0109: public SendMail(File file) {
0110: super ();
0111: setFile(file);
0112: init();
0113: }
0114:
0115: // Forward.
0116: public SendMail(MessageBuffer messageBuffer) {
0117: super ();
0118: init();
0119: mailbox = messageBuffer.getMailbox();
0120: entryRepliedTo = messageBuffer.getMailboxEntry();
0121: try {
0122: lockWrite();
0123: } catch (InterruptedException e) {
0124: Log.debug(e);
0125: return;
0126: }
0127: try {
0128: appendFrom();
0129: appendLine("To: ");
0130: appendLine("Subject: [Fwd: " + entryRepliedTo.getSubject()
0131: + "]");
0132: appendDefaultHeaders();
0133: appendLine(HEADER_SEPARATOR);
0134: appendLine("");
0135: Position pos = new Position(getLastLine(), 0);
0136: insertString(pos, "----- Forwarded message -----\n\n");
0137: insertString(pos, messageBuffer.getText());
0138: insertString(pos,
0139: "\n\n----- End of forwarded message -----");
0140: unmodified();
0141: renumber();
0142: formatter.parseBuffer();
0143: setLoaded(true);
0144: } finally {
0145: unlockWrite();
0146: }
0147: }
0148:
0149: // Reply.
0150: public SendMail(MessageBuffer messageBuffer, boolean replyToGroup) {
0151: super ();
0152: init();
0153: mailbox = messageBuffer.getMailbox();
0154: entryRepliedTo = messageBuffer.getMailboxEntry();
0155: reply = true;
0156: MailAddress[] replyTo = entryRepliedTo.getReplyTo();
0157: MailAddress[] from = entryRepliedTo.getFrom();
0158: MailAddress[] to = entryRepliedTo.getTo();
0159: MailAddress[] cc = entryRepliedTo.getCc();
0160: boolean skipTo = false;
0161: try {
0162: lockWrite();
0163: } catch (InterruptedException e) {
0164: Log.debug(e);
0165: return;
0166: }
0167: try {
0168: appendFrom();
0169: // Get senders.
0170: List senders = null;
0171: if (replyTo != null && replyTo.length > 0) {
0172: senders = new ArrayList();
0173: for (int i = 0; i < replyTo.length; i++)
0174: senders.add(replyTo[i]);
0175: } else if (from != null && from.length > 0) {
0176: senders = new ArrayList();
0177: for (int i = 0; i < from.length; i++)
0178: senders.add(from[i]);
0179: }
0180: if (senders != null) {
0181: Debug.assertTrue(senders.size() > 0);
0182: // Remove user's address from list of senders.
0183: for (int i = senders.size(); i-- > 0;) {
0184: MailAddress a = (MailAddress) senders.get(i);
0185: if (a.addressMatches(Mail.getUserMailAddress()))
0186: senders.remove(i);
0187: }
0188: if (senders.size() == 0) {
0189: // User was the only sender of the original message.
0190: // Use "To" addresses instead.
0191: for (int i = 0; i < to.length; i++)
0192: senders.add(to[i]);
0193: // Don't add "To" addresses to group.
0194: skipTo = true;
0195: }
0196: removeDuplicateAddresses(senders);
0197: appendAddressHeader("To: ", senders);
0198: }
0199: // Gather addresses for reply to group.
0200: group = new ArrayList();
0201: if (!skipTo && to != null) {
0202: for (int i = 0; i < to.length; i++) {
0203: MailAddress a = to[i];
0204: if (!a.addressMatches(Mail.getUserMailAddress()))
0205: group.add(a);
0206: }
0207: }
0208: if (cc != null) {
0209: for (int i = 0; i < cc.length; i++) {
0210: MailAddress a = cc[i];
0211: if (!a.addressMatches(Mail.getUserMailAddress()))
0212: group.add(a);
0213: }
0214: }
0215: removeDuplicateAddresses(group);
0216: if (senders != null) {
0217: // Make sure we don't duplicate entries in the "To:" header.
0218: for (int i = senders.size(); i-- > 0;) {
0219: MailAddress toAddress = (MailAddress) senders
0220: .get(i);
0221: for (int j = group.size(); j-- > 0;) {
0222: MailAddress a = (MailAddress) group.get(j);
0223: if (a.equals(toAddress)) {
0224: // It's a duplicate. Remove it from the group.
0225: group.remove(j);
0226: break;
0227: }
0228: }
0229: }
0230: }
0231: // Add group to recipients if applicable.
0232: if (replyToGroup && group.size() > 0)
0233: appendAddressHeader("Cc: ", group);
0234: String subject = entryRepliedTo.getSubject();
0235: if (!subject.toLowerCase().startsWith("re:"))
0236: subject = "Re: ".concat(subject);
0237: appendLine("Subject: ".concat(subject));
0238: appendLine("In-Reply-To: " + entryRepliedTo.getMessageId());
0239: appendReferences();
0240: appendDefaultHeaders();
0241: appendLine(HEADER_SEPARATOR);
0242: String attribution = getAttribution(messageBuffer);
0243: if (attribution != null)
0244: appendLine(attribution);
0245: String s = messageBuffer
0246: .quoteBody(getIntegerProperty(Property.WRAP_COL));
0247: if (s != null)
0248: append(s);
0249: appendLine("");
0250: appendSignature();
0251: unmodified();
0252: renumber();
0253: formatter.parseBuffer();
0254: setLoaded(true);
0255: } finally {
0256: unlockWrite();
0257: }
0258: }
0259:
0260: private void init() {
0261: if (preferences == null)
0262: preferences = Editor.preferences();
0263: initializeUndo();
0264: type = TYPE_NORMAL;
0265: title = DEFAULT_TITLE;
0266: if (getFile() == null) {
0267: final File dir = Directories.getDraftsFolder();
0268: if (!dir.isDirectory())
0269: dir.mkdirs();
0270: if (dir.isDirectory()) {
0271: setFile(Utilities.getTempFile(dir));
0272: } else {
0273: // Use temp directory as fallback. (Shouldn't happen.)
0274: setFile(Utilities.getTempFile());
0275: }
0276: }
0277: autosaveEnabled = true;
0278: mode = SendMailMode.getMode();
0279: formatter = mode.getFormatter(this );
0280: if (!getFile().isFile())
0281: lineSeparator = "\n";
0282: setInitialized(true);
0283: }
0284:
0285: public int load() {
0286: super .load();
0287: title = getSubject();
0288: if (title == null || title.length() == 0)
0289: title = DEFAULT_TITLE;
0290: return LOAD_COMPLETED;
0291: }
0292:
0293: public boolean save() {
0294: boolean result = super .save();
0295: for (BufferIterator it = new BufferIterator(); it.hasNext();) {
0296: Buffer buf = it.nextBuffer();
0297: if (buf instanceof Drafts) {
0298: Drafts drafts = (Drafts) buf;
0299: drafts.reload();
0300: break;
0301: }
0302: }
0303: return result;
0304: }
0305:
0306: public boolean hasBeenSent() {
0307: return hasBeenSent;
0308: }
0309:
0310: private static void removeDuplicateAddresses(List list) {
0311: // Remove duplicate entries from list.
0312: for (int i = list.size(); i-- > 0;) {
0313: MailAddress ma = (MailAddress) list.get(i);
0314: String addr = ma.getAddress();
0315: for (int j = i - 1; j >= 0; j--) {
0316: MailAddress ma2 = (MailAddress) list.get(j);
0317: if (ma.equals(ma2)) {
0318: list.remove(i);
0319: break;
0320: }
0321: // Not strictly equal. Check for same address.
0322: String addr2 = ma2.getAddress();
0323: if (addr.equals(addr2)) {
0324: list.remove(i);
0325: // We've removed ma from the list.
0326: // Don't lose any extra information it may have.
0327: if (ma2.getPersonal() == null
0328: || ma2.getPersonal().length() == 0) {
0329: if (ma.getPersonal() != null
0330: && ma.getPersonal().length() > 0)
0331: list.set(j, ma);
0332: }
0333: }
0334: }
0335: }
0336: }
0337:
0338: private void appendAddressHeader(String prefix, List list) {
0339: if (list == null)
0340: return;
0341: if (list.size() == 0)
0342: return;
0343: append(MailUtilities.constructAddressHeader(prefix, list));
0344: }
0345:
0346: private void appendFrom() {
0347: if (!preferences.getBooleanProperty(Property.CONFIRM_SEND)) {
0348: MailAddress ma = Mail.getUserMailAddress();
0349: if (ma != null)
0350: appendLine("From: ".concat(ma.toString()));
0351: }
0352: }
0353:
0354: private void replaceFrom(String from) {
0355: final Editor editor = Editor.currentEditor();
0356: final Position savedDot = editor.getDotCopy();
0357: try {
0358: lockWrite();
0359: } catch (InterruptedException e) {
0360: Log.error(e);
0361: return;
0362: }
0363: try {
0364: CompoundEdit compoundEdit = editor.beginCompoundEdit();
0365: // Remove existing "From:" header(s).
0366: removeHeaders(editor, "from:");
0367: // Move dot to beginning of buffer.
0368: editor.addUndo(SimpleEdit.MOVE);
0369: editor.getDot().moveTo(getFirstLine(), 0);
0370: // Insert new "From:" header.
0371: editor.addUndo(SimpleEdit.INSERT_STRING);
0372: insertString(editor.getDot(), "From: ".concat(from).concat(
0373: "\n"));
0374: // Restore dot to saved position if possible.
0375: Line dotLine = savedDot.getLine();
0376: if (contains(dotLine)) {
0377: int dotOffset = savedDot.getOffset();
0378: if (dotOffset > dotLine.length())
0379: dotOffset = dotLine.length();
0380: editor.addUndo(SimpleEdit.MOVE);
0381: editor.getDot().moveTo(dotLine, dotOffset);
0382: }
0383: editor.addUndo(SimpleEdit.MOVE);
0384: editor.moveCaretToDotCol();
0385: editor.endCompoundEdit(compoundEdit);
0386: getFormatter().parseBuffer();
0387: } finally {
0388: unlockWrite();
0389: }
0390: repaint();
0391: }
0392:
0393: private void replaceBcc(List bccList) {
0394: final Editor editor = Editor.currentEditor();
0395: final Position savedDot = editor.getDotCopy();
0396: try {
0397: lockWrite();
0398: } catch (InterruptedException e) {
0399: Log.error(e);
0400: return;
0401: }
0402: try {
0403: CompoundEdit compoundEdit = editor.beginCompoundEdit();
0404: // Remove existing "Bcc:" header(s).
0405: removeHeaders(editor, "bcc:");
0406: // Move dot to end of header area and insert new "Bcc:" header.
0407: for (Line line = getFirstLine(); line != null; line = line
0408: .next()) {
0409: if (line.getText().equals(HEADER_SEPARATOR)) {
0410: editor.addUndo(SimpleEdit.MOVE);
0411: editor.getDot().moveTo(line, 0);
0412: editor.addUndo(SimpleEdit.INSERT_STRING);
0413: insertString(editor.getDot(), MailUtilities
0414: .constructAddressHeader("Bcc: ", bccList)
0415: .concat("\n"));
0416: break;
0417: }
0418: }
0419: // Restore dot to saved position.
0420: Line dotLine = savedDot.getLine();
0421: if (contains(dotLine)) {
0422: int dotOffset = savedDot.getOffset();
0423: if (dotOffset > dotLine.length())
0424: dotOffset = dotLine.length();
0425: editor.addUndo(SimpleEdit.MOVE);
0426: editor.getDot().moveTo(dotLine, dotOffset);
0427: }
0428: editor.addUndo(SimpleEdit.MOVE);
0429: editor.moveCaretToDotCol();
0430: editor.endCompoundEdit(compoundEdit);
0431: getFormatter().parseBuffer();
0432: } finally {
0433: unlockWrite();
0434: }
0435: repaint();
0436: }
0437:
0438: private void appendReferences() {
0439: if (entryRepliedTo != null) {
0440: final String prefix = "References: ";
0441: FastStringBuffer sb = new FastStringBuffer(prefix);
0442: int length = prefix.length();
0443: String[] oldReferences = entryRepliedTo.getReferences();
0444: final String[] references;
0445: if (oldReferences != null) {
0446: references = new String[oldReferences.length + 1];
0447: System.arraycopy(oldReferences, 0, references, 0,
0448: oldReferences.length);
0449: } else
0450: references = new String[1];
0451: references[references.length - 1] = entryRepliedTo
0452: .getMessageId();
0453: for (int i = 0; i < references.length; i++) {
0454: String s = references[i];
0455: if (i > 0 && length + s.length() > 990) {
0456: // Won't fit on current line.
0457: sb.append(lineSeparator);
0458: final String indent = " ";
0459: sb.append(indent); // Continuation.
0460: sb.append(s);
0461: length = indent.length() + s.length();
0462: } else {
0463: if (i > 0) {
0464: sb.append(" ");
0465: length++;
0466: }
0467: sb.append(s);
0468: length += s.length();
0469: }
0470: }
0471: append(sb.toString());
0472: }
0473: }
0474:
0475: private void appendDefaultHeaders() {
0476: String bcc = preferences.getStringProperty("bcc");
0477: if (bcc != null)
0478: appendLine("Bcc: ".concat(bcc));
0479: }
0480:
0481: private void appendSignature() {
0482: File file = null;
0483: String fileName = preferences
0484: .getStringProperty(Property.SIGNATURE);
0485: if (fileName != null)
0486: file = File.getInstance(fileName);
0487: else if (Platform.isPlatformUnix())
0488: file = File.getInstance(Directories.getUserHomeDirectory(),
0489: ".signature");
0490: if (file == null || !file.isFile())
0491: return;
0492: FastStringBuffer sb = new FastStringBuffer();
0493: try {
0494: BufferedReader reader = new BufferedReader(
0495: new InputStreamReader(file.getInputStream()));
0496: while (true) {
0497: String s = reader.readLine();
0498: if (s == null)
0499: break;
0500: if (sb.length() > 0)
0501: sb.append('\n');
0502: sb.append(s);
0503: }
0504: reader.close();
0505: } catch (IOException e) {
0506: Log.error(e);
0507: }
0508: if (sb.length() > 0)
0509: append(sb.toString());
0510: }
0511:
0512: public static final String getHeaderSeparator() {
0513: return HEADER_SEPARATOR;
0514: }
0515:
0516: public void modified() {
0517: super .modified();
0518: setTitle();
0519: }
0520:
0521: private void setTitle() {
0522: String s = getSubject();
0523: if (s == null || s.length() == 0)
0524: s = DEFAULT_TITLE;
0525: if (title == null || !title.equals(s)) {
0526: title = s;
0527: Sidebar
0528: .setUpdateFlagInAllFrames(SIDEBAR_BUFFER_LIST_CHANGED);
0529: }
0530: }
0531:
0532: public void attachFile() {
0533: Editor editor = Editor.currentEditor();
0534: File file = OpenFileDialog.getLocalFile(editor, "Attach File");
0535: if (file != null && file.isFile()) {
0536: for (Line line = getFirstLine(); line != null; line = line
0537: .next()) {
0538: if (line.getText().equals(HEADER_SEPARATOR)) {
0539: Position pos = new Position(line, 0);
0540: insertString(pos, "Attachment: "
0541: + file.canonicalPath() + "\n");
0542: break;
0543: }
0544: }
0545: }
0546: }
0547:
0548: public void send() {
0549: if (!checkHeader())
0550: return;
0551: checkRecipients();
0552: checkEmpty();
0553: if (preferences.getBooleanProperty(Property.CONFIRM_SEND)) {
0554: if (!confirmSend())
0555: return;
0556: }
0557: Runnable sendRunnable = new Runnable() {
0558: public void run() {
0559: boolean succeeded = false;
0560: if (smtp != null)
0561: session = SmtpSession.getSession(smtp);
0562: else
0563: session = SmtpSession.getDefaultSession();
0564: if (session != null) {
0565: File messageFile = Utilities.getTempFile();
0566: try {
0567: OutputStreamWriter writer = new OutputStreamWriter(
0568: messageFile.getOutputStream());
0569: writeMessageText(writer);
0570: writer.flush();
0571: writer.close();
0572: succeeded = session.sendMessage(SendMail.this ,
0573: messageFile);
0574: if (succeeded)
0575: writeFcc(messageFile);
0576: messageFile.delete();
0577: } catch (IOException e) {
0578: Log.error(e);
0579: }
0580: }
0581: hasBeenSent = succeeded;
0582: SwingUtilities
0583: .invokeLater(succeeded ? succeededRunnable
0584: : errorRunnable);
0585: }
0586: };
0587: setBusy(true);
0588: new Thread(sendRunnable).start();
0589: }
0590:
0591: private boolean confirmSend() {
0592: final Editor editor = Editor.currentEditor();
0593: ConfirmSendDialog d = new ConfirmSendDialog(editor, this );
0594: editor.centerDialog(d);
0595: d.show();
0596: if (d.cancelled())
0597: return false;
0598: String from = d.getFrom();
0599: if (from != null)
0600: replaceFrom(from);
0601: if (d.bccAddSender() || d.bccAddOther()) {
0602: List bccList = new ArrayList();
0603: MailAddress[] bcc = MailAddress.parseAddresses(getBcc());
0604: if (bcc != null) {
0605: for (int i = 0; i < bcc.length; i++)
0606: bccList.add(bcc[i]);
0607: }
0608: if (d.bccAddSender() && from != null)
0609: bccList.add(MailAddress.parseAddress(from));
0610: if (d.bccAddOther()) {
0611: String bccOther = d.getBccOther();
0612: if (bccOther != null && bccOther.length() > 0)
0613: bccList.add(MailAddress.parseAddress(bccOther));
0614: }
0615: if (bccList.size() > 0) {
0616: removeDuplicateAddresses(bccList);
0617: replaceBcc(bccList);
0618: }
0619: }
0620: smtp = d.getSmtp();
0621: return true;
0622: }
0623:
0624: private boolean checkHeader() {
0625: for (Line line = getFirstLine(); line != null; line = line
0626: .next()) {
0627: if (line.getText().equals(HEADER_SEPARATOR))
0628: return true;
0629: }
0630: MessageDialog.showMessageDialog(Editor.currentEditor(),
0631: "Message separator line is missing", "Error");
0632: return false;
0633: // BUG!! Should confirm here that we have a valid "From" address!
0634: }
0635:
0636: // Make sure all continued address header lines end with commas.
0637: private void checkRecipients() {
0638: for (Line line = getFirstLine(); line != null; line = line
0639: .next()) {
0640: String text = line.getText();
0641: if (text.equals(HEADER_SEPARATOR))
0642: return;
0643: String lower = text.toLowerCase();
0644: if (lower.startsWith("to:") || lower.startsWith("cc:")
0645: || lower.startsWith("bcc:")) {
0646: Line continuation = line.next();
0647: while (continuation != null
0648: && continuation.length() > 0) {
0649: char c = continuation.charAt(0);
0650: if (c == ' ' || c == '\t') {
0651: // It's really a continuation line. Make sure the
0652: // preceding line ends with a comma.
0653: String s = trimTrailing(line.getText());
0654: int length = s.length();
0655: if (length > 0 && s.charAt(length - 1) != ',')
0656: line.setText(s.concat(","));
0657: line = continuation;
0658: continuation = continuation.next();
0659: } else
0660: break;
0661: }
0662: }
0663: }
0664: }
0665:
0666: // Trims trailing whitespace.
0667: private static String trimTrailing(String s) {
0668: int length = s.length();
0669: if (length == 0
0670: || !Character.isWhitespace(s.charAt(length - 1)))
0671: return s;
0672: do {
0673: --length;
0674: } while (length > 0
0675: && Character.isWhitespace(s.charAt(length - 1)));
0676: return s.substring(0, length);
0677: }
0678:
0679: private void checkEmpty() {
0680: String eom = getStringProperty(Property.EOM);
0681: if (eom == null || eom.length() == 0)
0682: return;
0683: Line subjectLine = null;
0684: Line line;
0685: for (line = getFirstLine(); line != null; line = line.next()) {
0686: String text = line.getText();
0687: if (text.toLowerCase().startsWith("subject:"))
0688: subjectLine = line;
0689: else if (text.toLowerCase().startsWith("attachment:"))
0690: return; // Not empty.
0691: else if (text.equals(HEADER_SEPARATOR))
0692: break;
0693: }
0694: if (subjectLine == null)
0695: return;
0696: if (line != null) {
0697: line = line.next();
0698: // Now we're on the first line of the message body.
0699: while (line != null) {
0700: if (!line.isBlank())
0701: return; // Not empty.
0702: line = line.next();
0703: }
0704: }
0705: // Empty.
0706: String text = subjectLine.getText();
0707: if (!text.endsWith(eom))
0708: subjectLine.setText(text + eom);
0709: }
0710:
0711: private Runnable succeededRunnable = new Runnable() {
0712: public void run() {
0713: unmodified();
0714: if (reply && mailbox != null && entryRepliedTo != null)
0715: mailbox.setAnsweredFlag(entryRepliedTo);
0716: File file = getFile();
0717: if (file.isFile()) {
0718: Log.debug("deleting draft " + file);
0719: file.delete();
0720: for (BufferIterator it = new BufferIterator(); it
0721: .hasNext();) {
0722: Buffer buf = it.nextBuffer();
0723: if (buf instanceof Drafts) {
0724: Drafts drafts = (Drafts) buf;
0725: drafts.reload();
0726: break;
0727: }
0728: }
0729: }
0730: setBusy(false);
0731: kill();
0732: EditorIterator iter = new EditorIterator();
0733: while (iter.hasNext())
0734: iter.nextEditor().updateDisplay();
0735: }
0736: };
0737:
0738: private Runnable errorRunnable = new Runnable() {
0739: public void run() {
0740: setBusy(false);
0741: final Editor editor = Editor.currentEditor();
0742: editor.updateDisplay();
0743: MessageDialog.showMessageDialog(editor,
0744: session != null ? session.getErrorText()
0745: : "Unable to send message", "Send Mail");
0746: }
0747: };
0748:
0749: private void writeMessageText(Writer writer) {
0750: final String separator = "\n";
0751: try {
0752: writer.write("Date: " + RFC822Date.getDateTimeString()
0753: + separator);
0754: if (getFrom() == null)
0755: writer.write("From: " + getDefaultFromAddress()
0756: + separator);
0757: Line line;
0758: // Headers.
0759: boolean inBcc = false;
0760: List attachments = null;
0761: for (line = getFirstLine(); line != null; line = line
0762: .next()) {
0763: String text = line.getText();
0764: if (text.length() == 0) {
0765: // Found empty line before reaching end of headers.
0766: // Discard it.
0767: continue;
0768: }
0769: if (text.equals(HEADER_SEPARATOR)) {
0770: // Reached end of headers.
0771: writer.write("Message-ID: "
0772: + Mail.generateMessageId() + separator);
0773: writer.write("User-Agent: "
0774: + Version.getLongVersionString()
0775: + separator);
0776: attachments = parseAttachments();
0777: if (attachments != null)
0778: writer.write(generateMimeHeaders(separator));
0779: // Skip header separator line.
0780: line = line.next();
0781: break;
0782: }
0783: if (inBcc) {
0784: char c = text.charAt(0);
0785: if (c == ' ' || c == '\t') {
0786: // It's a continuation line. Skip it.
0787: continue;
0788: } else {
0789: // Start of next header.
0790: inBcc = false;
0791: }
0792: }
0793: if (text.toLowerCase().startsWith("bcc:")) {
0794: inBcc = true;
0795: continue;
0796: }
0797: if (text.toLowerCase().startsWith("attachment:"))
0798: continue;
0799: writer.write(line.getText());
0800: writer.write(separator);
0801: }
0802: final Line startOfBody = line;
0803: // Scan body of message.
0804: boolean qp = false;
0805: for (line = startOfBody; line != null; line = line.next()) {
0806: if (requiresEncoding(line)) {
0807: qp = true;
0808: break;
0809: }
0810: }
0811: String transferEncoding = qp ? "quoted-printable" : "7bit";
0812: String characterEncoding = getStringProperty(Property.DEFAULT_ENCODING);
0813: if (attachments != null) {
0814: // Append extra separator at end of headers.
0815: writer.write(separator);
0816: writer
0817: .write("This is a multi-part message in MIME format.");
0818: writer.write(separator);
0819: writer.write("--");
0820: writer.write(getBoundary());
0821: writer.write(separator);
0822: writer.write("Content-Type: text/plain");
0823: if (qp) {
0824: writer.write("; charset=");
0825: writer.write(getCharSetName(characterEncoding));
0826: }
0827: writer.write(separator);
0828: writer.write("Content-Transfer-Encoding: ");
0829: writer.write(transferEncoding);
0830: writer.write(separator);
0831: writer.write(separator);
0832: } else {
0833: // No attachments.
0834: if (qp) {
0835: writer.write("Content-Type: text/plain");
0836: if (qp) {
0837: writer.write("; charset=");
0838: writer.write(getCharSetName(characterEncoding));
0839: }
0840: writer.write(separator);
0841: writer.write("Content-Transfer-Encoding: ");
0842: writer.write(transferEncoding);
0843: writer.write(separator);
0844: }
0845: // Append extra separator at end of headers.
0846: writer.write(separator);
0847: }
0848: // Body.
0849: for (line = startOfBody; line != null; line = line.next()) {
0850: String s = line.getText();
0851: if (qp) {
0852: writer.write(QuotedPrintableEncoder.encode(s,
0853: characterEncoding, separator));
0854: } else {
0855: // Dot stuffing.
0856: if (s.length() > 0 && s.charAt(0) == '.')
0857: writer.write('.');
0858: writer.write(s);
0859: }
0860: writer.write(separator);
0861: }
0862: // Attachments.
0863: if (attachments != null) {
0864: for (int i = 0; i < attachments.size(); i++) {
0865: String fullPath = (String) attachments.get(i);
0866: File file = File.getInstance(fullPath);
0867: String contentType = getContentTypeForFile(file);
0868: Log.debug("contentType = " + contentType);
0869: writer.write("--");
0870: writer.write(getBoundary());
0871: writer.write(separator);
0872: writer.write("Content-Type: ");
0873: writer.write(contentType);
0874: writer.write(separator);
0875: writer.write("Content-Transfer-Encoding: base64");
0876: writer.write(separator);
0877: writer
0878: .write("Content-Disposition: attachment; filename=\"");
0879: writer.write(file.getName());
0880: writer.write('"');
0881: writer.write(separator);
0882: writer.write(separator);
0883: writeEncodedFile(file, writer, separator);
0884: writer.write(separator);
0885: }
0886: writer.write("--");
0887: writer.write(getBoundary());
0888: writer.write("--");
0889: writer.write(separator);
0890: }
0891: } catch (IOException e) {
0892: Log.error(e);
0893: }
0894: }
0895:
0896: private void writeEncodedFile(File file, Writer writer,
0897: String separator) {
0898: if (file == null || !file.isFile() || !file.canRead())
0899: return;
0900: try {
0901: FileInputStream inputStream = file.getInputStream();
0902: Base64Encoder encoder = new Base64Encoder(inputStream);
0903: String s;
0904: while ((s = encoder.encodeLine()) != null) {
0905: writer.write(s);
0906: writer.write(separator);
0907: }
0908: writer.flush();
0909: inputStream.close();
0910: } catch (IOException e) {
0911: Log.error(e);
0912: }
0913: }
0914:
0915: public String getFrom() {
0916: return getHeaderValue("from");
0917: }
0918:
0919: public String getFromAddress() {
0920: String from = getFrom();
0921: if (from != null)
0922: return getAddress(from);
0923: // No "From:" header.
0924: return Mail.getUserMailAddress().getAddress();
0925: }
0926:
0927: private final String getDefaultFromAddress() {
0928: return Mail.getUserMailAddress().toString();
0929: }
0930:
0931: // Given "Piso Mojado <piso@armedbear.yi.org>", returns
0932: // "piso@armedbear.yi.org".
0933: public static String getAddress(String s) {
0934: if (s == null)
0935: return null;
0936: int index = s.indexOf('@');
0937: if (index < 0)
0938: return null;
0939: int begin = 0;
0940: int end = s.length();
0941: for (int i = index; i-- > 0;) {
0942: char c = s.charAt(i);
0943: if (c == ',' || c == '"' || c == '<'
0944: || Character.isWhitespace(c)) {
0945: begin = i + 1;
0946: break;
0947: }
0948: }
0949: for (int i = index + 1; i < end; i++) {
0950: char c = s.charAt(i);
0951: if (c == ',' || c == '"' || c == '>'
0952: || Character.isWhitespace(c)) {
0953: end = i;
0954: break;
0955: }
0956: }
0957: return s.substring(begin, end);
0958: }
0959:
0960: public String getTo() {
0961: Log.debug("getTo to = |" + getHeaderValue("to") + "|");
0962: return getHeaderValue("to");
0963: }
0964:
0965: public String getCc() {
0966: Log.debug("getCc cc = |" + getHeaderValue("cc") + "|");
0967: return getHeaderValue("cc");
0968: }
0969:
0970: public String getBcc() {
0971: Log.debug("getBcc bcc = |" + getHeaderValue("bcc") + "|");
0972: return getHeaderValue("bcc");
0973: }
0974:
0975: public void ccGroup() {
0976: if (group == null)
0977: return;
0978: // Entries from the original group will come first in the new list.
0979: List newList = new ArrayList(group);
0980: // Add the entries from the existing "Cc:" header (if any) back in.
0981: MailAddress[] cc = MailAddress.parseAddresses(getCc());
0982: if (cc != null) {
0983: for (int i = 0; i < cc.length; i++) {
0984: MailAddress oldAddress = cc[i];
0985: // Skip entries that are already in the list.
0986: boolean isDuplicate = false;
0987: for (int j = newList.size() - 1; j >= 0; j--) {
0988: MailAddress a = (MailAddress) newList.get(j);
0989: if (oldAddress.equals(a)) {
0990: isDuplicate = true;
0991: break;
0992: }
0993: }
0994: if (!isDuplicate)
0995: newList.add(oldAddress);
0996: }
0997: }
0998: // Make sure we don't duplicate entries in the "To:" header.
0999: MailAddress[] to = MailAddress.parseAddresses(getTo());
1000: if (to != null) {
1001: for (int i = to.length - 1; i >= 0; i--) {
1002: MailAddress toAddress = to[i];
1003: for (int j = newList.size() - 1; j >= 0; j--) {
1004: MailAddress a = (MailAddress) newList.get(j);
1005: if (a.equals(toAddress)) {
1006: // It's a duplicate. Remove it from the new list.
1007: Log.debug("removing "
1008: + (MailAddress) newList.get(j));
1009: newList.remove(j);
1010: break;
1011: }
1012: }
1013: }
1014: }
1015: final Editor editor = Editor.currentEditor();
1016: final Position savedDot = editor.getDotCopy();
1017: try {
1018: lockWrite();
1019: } catch (InterruptedException e) {
1020: Log.error(e);
1021: return;
1022: }
1023: try {
1024: CompoundEdit compoundEdit = editor.beginCompoundEdit();
1025: // Remove all existing "Cc:" headers (there can be more than one).
1026: removeHeaders(editor, "cc:");
1027: // Move dot to line after "To:" header.
1028: for (Line line = getFirstLine(); line != null; line = line
1029: .next()) {
1030: if (line.getText().toLowerCase().startsWith("to:")) {
1031: // Found first line of "To:" header.
1032: for (Line next = line.next(); next != null; next = next
1033: .next()) {
1034: if (next.length() == 0 || next.charAt(0) == ' '
1035: || next.charAt(0) == '\t') {
1036: // Continuation line.
1037: continue;
1038: } else {
1039: // Found next header. Put dot here.
1040: editor.addUndo(SimpleEdit.MOVE);
1041: editor.getDot().moveTo(next, 0);
1042: break;
1043: }
1044: }
1045: break;
1046: }
1047: }
1048: // Insert new "Cc:" header.
1049: editor.addUndo(SimpleEdit.INSERT_STRING);
1050: FastStringBuffer sb = new FastStringBuffer();
1051: sb.append(MailUtilities.constructAddressHeader("Cc: ",
1052: newList));
1053: sb.append(lineSeparator);
1054: insertString(editor.getDot(), sb.toString());
1055: // Restore dot to saved position if possible.
1056: Line dotLine = savedDot.getLine();
1057: if (contains(dotLine)) {
1058: int dotOffset = savedDot.getOffset();
1059: if (dotOffset > dotLine.length())
1060: dotOffset = dotLine.length();
1061: editor.addUndo(SimpleEdit.MOVE);
1062: editor.getDot().moveTo(dotLine, dotOffset);
1063: }
1064: editor.addUndo(SimpleEdit.MOVE);
1065: editor.moveCaretToDotCol();
1066: editor.endCompoundEdit(compoundEdit);
1067: getFormatter().parseBuffer();
1068: } finally {
1069: unlockWrite();
1070: }
1071: repaint();
1072: }
1073:
1074: // Remove all header lines for hdr (e.g "from:", "cc:", "bcc:"). We assume
1075: // the buffer is write-locked and beginCompoundEdit has been called. The
1076: // current editor gets passed in to manage undo.
1077: private void removeHeaders(Editor editor, String hdr) {
1078: // Make sure hdr is all lower case and ends with a colon.
1079: hdr = hdr.toLowerCase();
1080: if (!hdr.endsWith(":"))
1081: hdr = hdr.concat(":");
1082: while (true) {
1083: // Find appropriate line in message headers.
1084: Line beginLine = null;
1085: // Always start at top of buffer.
1086: for (Line line = getFirstLine(); line != null; line = line
1087: .next()) {
1088: String text = line.getText();
1089: if (text.equals(HEADER_SEPARATOR))
1090: return;
1091: if (text.toLowerCase().startsWith(hdr)) {
1092: beginLine = line;
1093: break;
1094: }
1095: }
1096: // Not found.
1097: if (beginLine == null)
1098: return;
1099: // We want to delete the region up to the start of the next header
1100: // line.
1101: Line endLine = null;
1102: for (Line line = beginLine.next(); line != null; line = line
1103: .next()) {
1104: String text = line.getText();
1105: if (text.length() == 0)
1106: continue;
1107: if (text.equals(HEADER_SEPARATOR)) {
1108: endLine = line;
1109: break;
1110: }
1111: char c = line.getText().charAt(0);
1112: if (c != ' ' && c != '\t') {
1113: endLine = line;
1114: break;
1115: }
1116: }
1117: // endLine should never be null here, since at worst we should
1118: // always hit the header separator line. But the user might have
1119: // deleted the header separator...
1120: if (endLine == null)
1121: return;
1122: // Delete the region from beginLine to endLine.
1123: Region r = new Region(this , new Position(beginLine, 0),
1124: new Position(endLine, 0));
1125: editor.addUndo(SimpleEdit.MOVE);
1126: editor.getDot().moveTo(r.getBegin());
1127: editor.addUndoDeleteRegion(r);
1128: r.delete();
1129: }
1130: }
1131:
1132: public List getAddressees() {
1133: ArrayList list = new ArrayList();
1134: appendAddressesFromString(list, getTo());
1135: appendAddressesFromString(list, getCc());
1136: appendAddressesFromString(list, getBcc());
1137: for (int i = 0; i < list.size(); i++)
1138: Log.debug("|" + (String) list.get(i) + "|");
1139: return list;
1140: }
1141:
1142: private static void appendAddressesFromString(List list, String s) {
1143: if (s == null)
1144: return;
1145: s = s.trim();
1146: int length = s.length();
1147: if (length == 0)
1148: return;
1149: FastStringBuffer sb = new FastStringBuffer(64);
1150: boolean inQuote = false;
1151: for (int i = 0; i < length; i++) {
1152: char c = s.charAt(i);
1153: switch (c) {
1154: case '"':
1155: inQuote = !inQuote;
1156: sb.append(c);
1157: break;
1158: case ',':
1159: if (inQuote) {
1160: // A comma inside a quoted string is just an ordinary
1161: // character.
1162: sb.append(c);
1163: } else {
1164: // Otherwise a comma marks the end of the address.
1165: String address = sb.toString().trim();
1166: if (address.length() > 0)
1167: list.add(address);
1168: sb.setLength(0);
1169: }
1170: break;
1171: default:
1172: sb.append(c);
1173: break;
1174: }
1175: }
1176: if (sb.length() > 0) {
1177: String address = sb.toString().trim();
1178: if (address.length() > 0)
1179: list.add(address);
1180: }
1181: }
1182:
1183: public String getSubject() {
1184: return getHeaderValue("subject");
1185: }
1186:
1187: // Combines multiple occurrences of "To:", "Cc:", "Bcc:".
1188: private String getHeaderValue(String headerName) {
1189: boolean combine = false;
1190: String key = headerName.toLowerCase() + ':';
1191: if (key.equals("to:") || key.equals("cc:")
1192: || key.equals("bcc:"))
1193: combine = true;
1194: FastStringBuffer sb = null;
1195: for (Line line = getFirstLine(); line != null; line = line
1196: .next()) {
1197: String text = line.getText();
1198: if (text.equals(HEADER_SEPARATOR))
1199: break;
1200: if (text.toLowerCase().startsWith(key)) {
1201: if (sb == null) {
1202: sb = new FastStringBuffer();
1203: } else {
1204: // Make sure there's a proper separator when we're
1205: // combining address headers.
1206: sb.append(", ");
1207: }
1208: sb.append(text.substring(key.length()).trim());
1209: Line continuation = line.next();
1210: while (continuation != null
1211: && continuation.length() > 0) {
1212: char c = continuation.charAt(0);
1213: if (c == ' ' || c == '\t') {
1214: // This is in fact a continuation line.
1215: sb.append(continuation.getText());
1216: line = continuation;
1217: continuation = continuation.next();
1218: continue;
1219: } else
1220: break;
1221: }
1222: if (!combine)
1223: break;
1224: }
1225: }
1226: return sb != null ? sb.toString() : null;
1227: }
1228:
1229: public Position getInitialDotPos() {
1230: if (reply) {
1231: for (Line line = getFirstLine(); line != null; line = line
1232: .next()) {
1233: if (line.getText().equals(HEADER_SEPARATOR)) {
1234: line = line.next();
1235: if (line != null)
1236: return new Position(line, 0);
1237: }
1238: }
1239: } else {
1240: for (Line line = getFirstLine(); line != null; line = line
1241: .next()) {
1242: if (line.getText().startsWith("To: "))
1243: return new Position(line, line.length());
1244: }
1245: }
1246: // Under normal circumstances we shouldn't get here, but anything can
1247: // happen with a postponed message...
1248: return new Position(getFirstLine(), 0);
1249: }
1250:
1251: private List parseAttachments() {
1252: ArrayList attachments = null;
1253: for (Line line = getFirstLine(); line != null; line = line
1254: .next()) {
1255: if (line.getText().equals(HEADER_SEPARATOR))
1256: break;
1257: if (line.getText().toLowerCase().startsWith("attachment:")) {
1258: String filename = line.getText().substring(11).trim();
1259: if (filename.length() > 0) {
1260: if (attachments == null)
1261: attachments = new ArrayList();
1262: attachments.add(filename);
1263: }
1264: }
1265: }
1266: return attachments;
1267: }
1268:
1269: private String generateMimeHeaders(String separator) {
1270: FastStringBuffer sb = new FastStringBuffer();
1271: sb.append("MIME-Version: 1.0");
1272: sb.append(separator);
1273: sb.append("Content-Type: multipart/mixed; boundary=\"");
1274: sb.append(getBoundary());
1275: sb.append('"');
1276: sb.append(separator);
1277: return sb.toString();
1278: }
1279:
1280: private String getBoundary() {
1281: if (boundary == null) {
1282: FastStringBuffer sb = new FastStringBuffer(16);
1283: Random random = new Random();
1284: char[] chars = getBoundaryChars();
1285: for (int i = 0; i < 16; i++) {
1286: int index = random.nextInt(chars.length);
1287: sb.append(chars[index]);
1288: }
1289: boundary = sb.toString();
1290: }
1291: return boundary;
1292: }
1293:
1294: private char[] getBoundaryChars() {
1295: return Base64Encoder.getBase64Chars();
1296: }
1297:
1298: private String getContentTypeForFile(File file) {
1299: boolean isBinary = false;
1300: try {
1301: BufferedInputStream inputStream = new BufferedInputStream(
1302: file.getInputStream());
1303: int c;
1304: while ((c = inputStream.read()) >= 0) {
1305: if (c >= ' ' && c < 127)
1306: continue;
1307: if (c == '\r')
1308: continue;
1309: if (c == '\n')
1310: continue;
1311: if (c == '\t')
1312: continue;
1313: if (c == '\f')
1314: continue;
1315: // If none of the above, it's not text.
1316: isBinary = true;
1317: break;
1318: }
1319: inputStream.close();
1320: } catch (IOException e) {
1321: Log.error(e);
1322: isBinary = true;
1323: }
1324: String extension = Utilities.getExtension(file);
1325: if (extension != null)
1326: extension = extension.toLowerCase();
1327: if (isBinary) {
1328: if (extension != null) {
1329: // Check for known image types.
1330: extension = extension.toLowerCase();
1331: if (extension.equals(".jpeg")
1332: || extension.equals(".jpg"))
1333: return "image/jpeg";
1334: if (extension.equals(".gif"))
1335: return "image/gif";
1336: if (extension.equals(".png"))
1337: return "image/png";
1338: }
1339: // No extension or not a known type.
1340: return "application/octet-stream";
1341: } else {
1342: if (extension != null) {
1343: if (extension.equals(".html")
1344: || extension.equals(".htm"))
1345: return "text/html";
1346: if (extension.equals(".xml"))
1347: return "text/xml";
1348: }
1349: // No extension or not a known type.
1350: return "text/plain";
1351: }
1352: }
1353:
1354: public File getCurrentDirectory() {
1355: return Directories.getUserHomeDirectory();
1356: }
1357:
1358: public File getCompletionDirectory() {
1359: return Directories.getUserHomeDirectory();
1360: }
1361:
1362: // For the buffer list.
1363: public Icon getIcon() {
1364: if (isModified())
1365: return Utilities.getIconFromFile("compose_modified.png");
1366: return Utilities.getIconFromFile("compose.png");
1367: }
1368:
1369: public String getFileNameForDisplay() {
1370: return "";
1371: }
1372:
1373: private static boolean requiresEncoding(Line line) {
1374: final String text = line.getText();
1375: if (text.length() > 990)
1376: return true;
1377: if (text.length() == 0)
1378: return false;
1379: for (int i = text.length() - 1; i >= 0; i--) {
1380: char c = text.charAt(i);
1381: if (c < ' ' && c != '\t')
1382: return true;
1383: if (c >= 127)
1384: return true;
1385: }
1386: if (text.startsWith("From "))
1387: return true;
1388: if (text.charAt(0) == '.')
1389: return true;
1390: return false;
1391: }
1392:
1393: private static final String getCharSetName(String characterEncoding) {
1394: if (characterEncoding.equals("ASCII"))
1395: return "us-ascii";
1396: if (characterEncoding.startsWith("ISO8859_"))
1397: return "iso-8859-" + characterEncoding.substring(8);
1398: // BUG! There are more cases!
1399: return characterEncoding;
1400: }
1401:
1402: public boolean isHeaderLine(Line maybe) {
1403: for (Line line = getFirstLine(); line != null; line = line
1404: .next()) {
1405: if (line == maybe)
1406: return true;
1407: if (line.getText().equals(HEADER_SEPARATOR))
1408: return false;
1409: }
1410: return false;
1411: }
1412:
1413: public void tab(Editor editor) {
1414: Line dotLine = editor.getDotLine();
1415: if (dotLine.getText().equals(HEADER_SEPARATOR)) {
1416: Line line = dotLine.next();
1417: if (line != null)
1418: editor.moveDotTo(line, 0);
1419: return;
1420: }
1421: for (Line line = getFirstLine(); line != null; line = line
1422: .next()) {
1423: if (line == dotLine) {
1424: break;
1425: } else if (line.getText().equals(HEADER_SEPARATOR)) {
1426: // We're in the body of the message. Just do the normal thing.
1427: editor.insertTab();
1428: return;
1429: }
1430: }
1431: // Reaching here, dot is in the header area. Advance to the end of the
1432: // next header.
1433: Line line = null;
1434: for (line = dotLine.next(); line != null; line = line.next()) {
1435: if (!isContinuationLine(line))
1436: break;
1437: }
1438: if (line == null)
1439: return;
1440: // Have we reached the header separator line?
1441: if (line.getText().equals(HEADER_SEPARATOR)) {
1442: line = line.next();
1443: if (line != null)
1444: editor.moveDotTo(line, 0);
1445: return;
1446: }
1447: // We're at start of next header. Put dot at end of it.
1448: while (true) {
1449: Line next = line.next();
1450: if (next == null)
1451: return;
1452: if (!isContinuationLine(next)) {
1453: // Next line is not a continuation line of current header.
1454: // Move dot to end of current line.
1455: editor.moveDotTo(line, line.length());
1456: return;
1457: }
1458: line = next;
1459: }
1460: }
1461:
1462: public void backTab(Editor editor) {
1463: Line dotLine = editor.getDotLine();
1464: // Check to see whether dot is in the header area or in the body of
1465: // the message.
1466: for (Line line = getFirstLine(); line != null; line = line
1467: .next()) {
1468: if (line == dotLine) {
1469: break;
1470: } else if (line.getText().equals(HEADER_SEPARATOR)) {
1471: // Dot is in the body of the message. Go back to the end of
1472: // the last header line.
1473: line = line.previous();
1474: if (line != null)
1475: editor.moveDotTo(line, line.length());
1476: return;
1477: }
1478: }
1479: // Reaching here, dot is in the header area. Find the first line of
1480: // the header dot is in.
1481: Line line = dotLine;
1482: while (isContinuationLine(line)) {
1483: line = line.previous();
1484: if (line == null)
1485: return; // Shouldn't happen.
1486: }
1487: // Now we're on the first line of the header dot was in. Put dot at
1488: // the end of the previous line.
1489: line = line.previous();
1490: if (line != null)
1491: editor.moveDotTo(line, line.length());
1492: }
1493:
1494: private static final boolean isContinuationLine(Line line) {
1495: if (line.length() == 0)
1496: return false;
1497: char c = line.charAt(0);
1498: return c == ' ' || c == '\t';
1499: }
1500:
1501: // Append message to sent messages file (if so configured).
1502: private void writeFcc(File messageFile) {
1503: final File sentMessagesFile = Mail.getSentMessagesFile();
1504: if (sentMessagesFile == null)
1505: return;
1506: try {
1507: BufferedReader reader = new BufferedReader(
1508: new InputStreamReader(messageFile.getInputStream()));
1509: BufferedWriter writer = new BufferedWriter(new FileWriter(
1510: sentMessagesFile.canonicalPath(), true));
1511: writer.write("From - ");
1512: SimpleDateFormat dateFormatter = new SimpleDateFormat(
1513: "EEE MMM d HH:mm:ss yyyy");
1514: Calendar cal = Calendar.getInstance();
1515: String dateString = dateFormatter.format(cal.getTime());
1516: writer.write(dateString);
1517: writer.write('\n');
1518: String s;
1519: while ((s = reader.readLine()) != null) {
1520: writer.write(s);
1521: writer.write('\n');
1522: }
1523: writer.write('\n');
1524: writer.flush();
1525: writer.close();
1526: reader.close();
1527: } catch (IOException e) {
1528: Log.error(e);
1529: }
1530: }
1531:
1532: public Expansion getExpansion(Position dot) {
1533: int endOfHeaders = -1;
1534: for (Line line = getFirstLine(); line != null; line = line
1535: .next()) {
1536: if (line.getText().equals(HEADER_SEPARATOR)) {
1537: endOfHeaders = line.lineNumber();
1538: break;
1539: }
1540: }
1541: if (dot.lineNumber() < endOfHeaders)
1542: return new MailAddressExpansion(dot);
1543: else
1544: return super .getExpansion(dot);
1545: }
1546:
1547: private static final RE dateRE = new UncheckedRE(
1548: "[A-Za-z]+, [0-9][0-9]? [A-Za-z]+ [0-9][0-9][0-9][0-9]");
1549: private static final RE timeRE = new UncheckedRE(
1550: "[0-9:]+ [+-][0-9][0-9][0-9][0-9]");
1551:
1552: private static String getAttribution(MessageBuffer messageBuffer) {
1553: if (messageBuffer == null)
1554: return null;
1555: Mailbox mailbox = messageBuffer.getMailbox();
1556: if (mailbox == null)
1557: return null;
1558: MailboxEntry entry = messageBuffer.getMailboxEntry();
1559: if (entry == null)
1560: return null;
1561: String template = preferences
1562: .getStringProperty(Property.ATTRIBUTION);
1563: if (template == null || template.length() == 0)
1564: return null;
1565: FastStringBuffer sb = new FastStringBuffer();
1566: final int limit = template.length();
1567: for (int i = 0; i < limit; i++) {
1568: char c = template.charAt(i);
1569: if (c == '%' && ++i < limit) {
1570: c = template.charAt(i);
1571: switch (c) {
1572: case 'd': {
1573: // Date/time in sender's time zone.
1574: Message message = messageBuffer.getMessage();
1575: if (message == null)
1576: return null;
1577: String dateTime = message
1578: .getHeaderValue(Headers.DATE);
1579: REMatch m1 = dateRE.getMatch(dateTime);
1580: if (m1 != null) {
1581: REMatch m2 = timeRE.getMatch(dateTime
1582: .substring(m1.getEndIndex()));
1583: if (m2 != null) {
1584: sb.append(m1.toString());
1585: sb.append(" at ");
1586: sb.append(m2.toString());
1587: }
1588: }
1589: break;
1590: }
1591: case 'n': {
1592: // Author's real name (or address if missing).
1593: MailAddress[] from = entry.getFrom();
1594: if (from == null || from.length == 0)
1595: return null;
1596: String personal = from[0].getPersonal();
1597: if (personal != null && personal.length() > 0)
1598: sb.append(personal);
1599: else {
1600: String addr = from[0].getAddress();
1601: if (addr != null && addr.length() > 0)
1602: sb.append(addr);
1603: else
1604: return null;
1605: }
1606: break;
1607: }
1608: default:
1609: Log.error("invalid format sequence \"%" + c + '"'
1610: + " in attribution");
1611: return null;
1612: }
1613: } else
1614: sb.append(c);
1615: }
1616: return sb.toString();
1617: }
1618: }
|