001: // The contents of this file are subject to the Mozilla Public License Version
002: // 1.1
003: //(the "License"); you may not use this file except in compliance with the
004: //License. You may obtain a copy of the License at http://www.mozilla.org/MPL/
005: //
006: //Software distributed under the License is distributed on an "AS IS" basis,
007: //WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
008: //for the specific language governing rights and
009: //limitations under the License.
010: //
011: //The Original Code is "The Columba Project"
012: //
013: //The Initial Developers of the Original Code are Frederik Dietz and Timo
014: // Stich.
015: //Portions created by Frederik Dietz and Timo Stich are Copyright (C) 2003.
016: //
017: //All Rights Reserved.
018: package org.columba.mail.composer;
019:
020: import java.io.IOException;
021: import java.io.InputStream;
022: import java.nio.charset.Charset;
023: import java.nio.charset.UnsupportedCharsetException;
024: import java.util.ArrayList;
025: import java.util.Collections;
026: import java.util.Iterator;
027: import java.util.regex.Matcher;
028: import java.util.regex.Pattern;
029:
030: import org.columba.addressbook.facade.IContactFacade;
031: import org.columba.addressbook.facade.IContactItem;
032: import org.columba.addressbook.facade.IFolder;
033: import org.columba.addressbook.facade.IFolderFacade;
034: import org.columba.addressbook.facade.IModelFacade;
035: import org.columba.api.exception.ServiceNotFoundException;
036: import org.columba.api.exception.StoreException;
037: import org.columba.core.io.StreamUtils;
038: import org.columba.core.xml.XmlElement;
039: import org.columba.mail.config.AccountItem;
040: import org.columba.mail.config.AccountList;
041: import org.columba.mail.config.MailConfig;
042: import org.columba.mail.connector.FacadeUtil;
043: import org.columba.mail.connector.ServiceConnector;
044: import org.columba.mail.gui.composer.ComposerModel;
045: import org.columba.ristretto.coder.Base64DecoderInputStream;
046: import org.columba.ristretto.coder.CharsetDecoderInputStream;
047: import org.columba.ristretto.coder.QuotedPrintableDecoderInputStream;
048: import org.columba.ristretto.message.Address;
049: import org.columba.ristretto.message.Header;
050: import org.columba.ristretto.message.MimeHeader;
051: import org.columba.ristretto.message.MimePart;
052: import org.columba.ristretto.message.StreamableMimePart;
053:
054: /**
055: *
056: * The <code>MessageBuilderHelper</code> class is responsible for creating the
057: * information for the <code>ComposerModel</class>class.
058: * <p>
059: * It generates appropriate header-information, mimeparts and
060: * quoted bodytext, etc.
061: * <p>
062: * These helper class is primarly used by the commands in org.columba.composer.command
063: * *
064: * @author fdietz, tstich
065: */
066: public class MessageBuilderHelper {
067:
068: /**
069: *
070: * Check if the subject headerfield already starts with a pattern like "Re:"
071: * or "Fwd:"
072: *
073: * @param subject
074: * A <code>String</code> containing the subject
075: * @param pattern
076: * A <code>String</code> specifying the pattern to search for.
077: */
078: public static boolean isAlreadyReply(String subject, String pattern) {
079: if (subject == null) {
080: return false;
081: }
082:
083: if (subject.length() == 0) {
084: return false;
085: }
086:
087: String str = subject.toLowerCase();
088:
089: // for example: "Re: this is a subject"
090: if (str.startsWith(pattern) == true) {
091: return true;
092: }
093:
094: // for example: "[columba-users]Re: this is a subject"
095: int index = str.indexOf(pattern);
096:
097: if (index != -1) {
098: return true;
099: }
100:
101: return false;
102: }
103:
104: /**
105: *
106: * create subject headerfield in using the senders message subject and
107: * prepending "Re:" if not already there
108: *
109: * @param header
110: * A <code>ColumbaHeader</code> which contains the headerfields
111: * of the message we want reply/forward.
112: *
113: * FIXME (@author fdietz): we need to i18n this!
114: */
115: public static String createReplySubject(String subject) {
116: // if subject doesn't start already with "Re:" prepend it
117: if (!isAlreadyReply(subject, "re:")) {
118: subject = "Re: " + subject;
119: }
120:
121: return subject;
122: }
123:
124: /**
125: *
126: * create Subject headerfield in using the senders message subject and
127: * prepending "Fwd:" if not already there
128: *
129: * @param header
130: * A <code>ColumbaHeader</code> which contains the headerfields
131: * of the message we want reply/forward.
132: *
133: * FIXME (@author fdietz): we need to i18n this!
134: *
135: */
136: public static String createForwardSubject(String subject) {
137: // if subject doesn't start already with "Fwd:" prepend it
138: if (!isAlreadyReply(subject, "fwd:")) {
139: subject = "Fwd: " + subject;
140: }
141:
142: return subject;
143: }
144:
145: /**
146: *
147: * create a To headerfield in using the senders message Reply-To or From
148: * headerfield
149: *
150: * @param header
151: * A <code>Header</code> which contains the headerfields of the
152: * message we want reply/forward.
153: *
154: */
155: public static String createTo(Header header) {
156: String replyTo = (String) header.get("Reply-To");
157: String from = (String) header.get("From");
158:
159: if (replyTo == null) {
160: // Reply-To headerfield isn't specified, try to use From instead
161: if (from != null) {
162: return from;
163: } else {
164: return "";
165: }
166: } else {
167: return replyTo;
168: }
169: }
170:
171: /**
172: *
173: * This is for creating the "Reply To All recipients" To headerfield.
174: *
175: * It is different from the <code>createTo</code> method in that it also
176: * appends the recipients specified in the To headerfield
177: *
178: * @param header
179: * A <code>ColumbaHeader</code> which contains the headerfields
180: * of the message we want reply/forward.
181: *
182: */
183: public static String createToAll(Header header) {
184: String sender = "";
185: String replyTo = (String) header.get("Reply-To");
186: String from = (String) header.get("From");
187: String to = (String) header.get("To");
188: String cc = (String) header.get("Cc");
189:
190: // if Reply-To headerfield isn't specified, try to use from
191: if (replyTo == null) {
192: sender = from;
193: } else {
194: sender = replyTo;
195: }
196:
197: // create To headerfield
198: StringBuffer buf = new StringBuffer();
199: buf.append(sender);
200:
201: if (to != null) {
202: buf.append(", ");
203: buf.append(to);
204: }
205:
206: if (cc != null) {
207: buf.append(", ");
208: buf.append(cc);
209: }
210:
211: return buf.toString();
212: }
213:
214: /**
215: *
216: * This method creates a To headerfield for the "Reply To MailingList"
217: * action. It uses the X-BeenThere headerfield and falls back to Reply-To or
218: * From if needed
219: *
220: * @param header
221: * A <code>Header</code> which contains the headerfields of the
222: * message we want reply/forward.
223: */
224: public static String createToMailinglist(Header header) {
225: // example: X-BeenThere: columba-devel@lists.sourceforge.net
226: String sender = (String) header.get("X-BeenThere");
227:
228: if (sender == null) {
229: sender = (String) header.get("X-Beenthere");
230: }
231:
232: if (sender == null) {
233: sender = (String) header.get("Reply-To");
234: }
235:
236: if (sender == null) {
237: sender = (String) header.get("From");
238: }
239:
240: return sender;
241: }
242:
243: /**
244: *
245: * Creates In-Reply-To and References headerfields. These are useful for
246: * mailing-list threading.
247: *
248: * @param header
249: * A <code>Header</code> which contains the headerfields of the
250: * message we want reply/forward.
251: *
252: * @param model
253: * The <code>ComposerModel</code> we want to pass the
254: * information to.
255: *
256: * TODO (@author fdietz): if the References headerfield contains to many
257: * characters, we have to remove some of the first References, before
258: * appending another one. (RFC822 headerfields are not allowed to become
259: * that long)
260: *
261: */
262: public static void createMailingListHeaderItems(Header header,
263: ComposerModel model) {
264: String messageId = (String) header.get("Message-ID");
265:
266: if (messageId == null) {
267: messageId = (String) header.get("Message-Id");
268: }
269:
270: if (messageId != null) {
271: model.setHeaderField("In-Reply-To", messageId);
272:
273: String references = (String) header.get("References");
274:
275: if (references != null) {
276: references = references + " " + messageId;
277: references = removeDoubleEntries(references);
278:
279: model.setHeaderField("References", references);
280: }
281: }
282: }
283:
284: private static String removeDoubleEntries(String input) {
285: Pattern separatorPattern = Pattern
286: .compile("\\s*(<[^\\s<>]+>)\\s*");
287: ArrayList entries = new ArrayList();
288: Matcher matcher = separatorPattern.matcher(input);
289: while (matcher.find()) {
290: entries.add(matcher.group(1));
291: }
292:
293: Collections.sort(entries);
294:
295: Iterator it = entries.iterator();
296: StringBuffer result = new StringBuffer();
297:
298: String last = (String) it.next();
299: result.append(last);
300:
301: while (it.hasNext()) {
302: String next = (String) it.next();
303: if (!next.equals(last)) {
304: last = next;
305: result.append(' ');
306: result.append(last);
307: }
308: }
309:
310: return result.toString();
311: }
312:
313: /**
314: *
315: * Search the correct Identity for replying to someone
316: * <p>
317: *
318: */
319: public static AccountItem getAccountItem(Integer accountUid) {
320: if (MailConfig.getInstance() == null)
321: return null;
322:
323: AccountList list = MailConfig.getInstance().getAccountList();
324: AccountItem accountItem = null;
325: list.getDefaultAccount();
326:
327: if (accountUid != null) {
328: accountItem = list.uidGet(accountUid.intValue());
329: }
330:
331: // *20040229, karlpeder* Use default account as fall back
332: if (accountItem == null) {
333: accountItem = list.getDefaultAccount();
334: }
335:
336: // if (accountUid != null) {
337: // accountItem = list.getDefaultAccount();
338: // }
339:
340: return accountItem;
341: }
342:
343: /**
344: *
345: * create bodytext
346: *
347: * @param message
348: * A <code>Message</code> which contains the bodytext of the
349: * message we want reply/forward.
350: */
351: public static String createBodyText(MimePart mimePart)
352: throws IOException {
353: StreamableMimePart bodyPart = (StreamableMimePart) mimePart;
354: String charsetName = bodyPart.getHeader().getContentParameter(
355: "charset");
356: int encoding = bodyPart.getHeader()
357: .getContentTransferEncoding();
358:
359: InputStream body = bodyPart.getInputStream();
360:
361: switch (encoding) {
362: case MimeHeader.QUOTED_PRINTABLE: {
363: body = new QuotedPrintableDecoderInputStream(body);
364:
365: break;
366: }
367:
368: case MimeHeader.BASE64: {
369: body = new Base64DecoderInputStream(body);
370:
371: break;
372: }
373: }
374:
375: if (charsetName != null) {
376: Charset charset;
377:
378: try {
379: charset = Charset.forName(charsetName);
380: } catch (UnsupportedCharsetException e) {
381: charset = Charset.forName(System
382: .getProperty("file.encoding"));
383: }
384:
385: body = new CharsetDecoderInputStream(body, charset);
386: }
387:
388: String bodyMsg = StreamUtils.readCharacterStream(body)
389: .toString();
390: return bodyMsg;
391: }
392:
393: /**
394: *
395: * prepend "> " characters to the bodytext to specify we are quoting
396: *
397: * @param message
398: * A <code>Message</code> which contains the bodytext of the
399: * message we want reply/forward.
400: * @param html
401: * True for html messages (a different quoting is necessary)
402: *
403: * TODO (@author fdietz): we should make this configureable
404: *
405: */
406: public static String createQuotedBodyText(CharSequence bodyText,
407: boolean html) throws IOException {
408: // Quote according model type (text/html)
409: String quotedBodyText;
410:
411: if (html) {
412: // html - quoting is done by inserting a div around the
413: // message formattet with a blue line at left edge
414: // TODO (@author fdietz): Implement quoting (font color, stylesheet,
415: // blockquote???)
416:
417: /*
418: * String lcase = bodyText.toLowerCase(); StringBuffer buf = new
419: * StringBuffer(); String quoteStart = " <blockquote> "; String
420: * quoteEnd = " </blockquote> ";
421: *
422: * int pos = lcase.indexOf(" <body"); pos = lcase.indexOf("> ", pos) +
423: * 1; buf.append(bodyText.substring(0, pos));
424: * buf.append(quoteStart); int end = lcase.indexOf(" </body");
425: * buf.append(bodyText.substring(pos, end)); buf.append(quoteEnd);
426: * buf.append(bodyText.substring(end));
427: *
428: * Logging.log.info("Source:\n" + bodyText);
429: * Logging.log.info("Result:\n" + buf.toString());
430: *
431: * quotedBodyText = buf.toString();
432: */
433: quotedBodyText = bodyText.toString();
434: } else {
435: // plain text
436: quotedBodyText = bodyText.toString().replaceAll(
437: "(?m)^(.*)$", "> $1");
438: }
439:
440: return quotedBodyText;
441: }
442:
443: /**
444: * Check if HTML support should be enabled in model.
445: *
446: * @return true, if enabled. false, otherwise
447: */
448: public static boolean isHTMLEnabled() {
449: if (MailConfig.getInstance() == null)
450: return false;
451:
452: // get configuration
453: XmlElement optionsElement = MailConfig.getInstance().get(
454: "composer_options").getElement("/options");
455: XmlElement htmlElement = optionsElement.getElement("html");
456:
457: // create html element, if it doesn't exist
458: if (htmlElement == null) {
459: htmlElement = optionsElement.addSubElement("html");
460: }
461:
462: // get enable attribute
463: String enableHtml = htmlElement.getAttribute("enable", "false");
464:
465: return Boolean.valueOf(enableHtml).booleanValue();
466: }
467:
468: /** ******************** addressbook stuff ********************** */
469: /**
470: *
471: * add automatically every person we'll send a message to the "Collected
472: * Addresses" Addressbook
473: *
474: */
475: public static void addAddressesToAddressbook(Address[] addresses) {
476:
477: try {
478: IContactFacade contactFacade = ServiceConnector
479: .getContactFacade();
480: IFolderFacade folderFacade = ServiceConnector
481: .getFolderFacade();
482: IModelFacade modelFacade = ServiceConnector
483: .getModelFacade();
484: IFolder folder = folderFacade.getCollectedAddresses();
485: for (int i = 0; i < addresses.length; i++) {
486: try {
487: IContactItem contactItem = modelFacade
488: .createContactItem();
489: Address addr = addresses[i];
490: FacadeUtil.getInstance().initContactItem(
491: contactItem, addr.getDisplayName(),
492: addr.getMailAddress());
493: contactFacade.addContact(folder.getId(),
494: contactItem);
495: } catch (StoreException e) {
496: e.printStackTrace();
497: }
498:
499: }
500: } catch (ServiceNotFoundException e) {
501: e.printStackTrace();
502: }
503:
504: }
505: }
|