1 19 package org.columba.mail.composer; 20 21 import java.io.BufferedReader ; 22 import java.io.File ; 23 import java.io.FileReader ; 24 import java.io.IOException ; 25 import java.io.InputStream ; 26 import java.nio.charset.Charset ; 27 import java.util.ArrayList ; 28 import java.util.Date ; 29 import java.util.Iterator ; 30 import java.util.List ; 31 import java.util.logging.Logger ; 32 33 import org.columba.api.command.IWorkerStatusController; 34 import org.columba.core.logging.Logging; 35 import org.columba.core.versioninfo.VersionInfo; 36 import org.columba.core.xml.XmlElement; 37 import org.columba.mail.config.AccountItem; 38 import org.columba.mail.config.Identity; 39 import org.columba.mail.config.MailConfig; 40 import org.columba.mail.config.SecurityItem; 41 import org.columba.mail.gui.composer.ComposerModel; 42 import org.columba.mail.message.PGPMimePart; 43 import org.columba.mail.message.SendableHeader; 44 import org.columba.mail.parser.ListBuilder; 45 import org.columba.mail.parser.ListParser; 46 import org.columba.mail.parser.text.HtmlParser; 47 import org.columba.ristretto.coder.EncodedWord; 48 import org.columba.ristretto.composer.MimeTreeRenderer; 49 import org.columba.ristretto.io.CharSequenceSource; 50 import org.columba.ristretto.message.Address; 51 import org.columba.ristretto.message.LocalMimePart; 52 import org.columba.ristretto.message.MessageDate; 53 import org.columba.ristretto.message.MessageIDGenerator; 54 import org.columba.ristretto.message.MimeHeader; 55 import org.columba.ristretto.message.MimePart; 56 import org.columba.ristretto.message.StreamableMimePart; 57 import org.columba.ristretto.parser.ParserException; 58 59 public class MessageComposer { 60 61 private static final Logger LOG = Logger 62 .getLogger("org.columba.mail.composer"); 63 64 private static final Charset headerCharset = Charset.forName("UTF-8"); 65 66 private ComposerModel model; 67 68 private int accountUid; 69 70 public MessageComposer(ComposerModel model) { 71 this.model = model; 72 } 73 74 protected SendableHeader initHeader() { 75 SendableHeader header = new SendableHeader(); 76 77 if (model.getToList() != null) { 79 String s = ListParser.createStringFromList(ListBuilder 80 .createFlatList(model.getToList()), ","); 81 if ( s.length() != 0 ) 82 header.set("To",EncodedWord.encode(s, 83 headerCharset, EncodedWord.QUOTED_PRINTABLE).toString() ); 84 } 85 86 if (model.getCcList() != null) { 87 String s = ListParser.createStringFromList(ListBuilder 88 .createFlatList(model.getCcList()), ","); 89 if ( s.length() != 0 ) 90 header.set("Cc",EncodedWord.encode(s, 91 headerCharset, EncodedWord.QUOTED_PRINTABLE).toString() ); 92 } 93 94 95 header.getAttributes().put("columba.subject", model.getSubject()); 96 97 header.set("Subject", EncodedWord.encode(model.getSubject(), 102 headerCharset, EncodedWord.QUOTED_PRINTABLE).toString()); 103 104 AccountItem item = model.getAccountItem(); 105 Identity identity = item.getIdentity(); 106 107 if (model.getMessage().getHeader().getHeader().get("From") != null) { 110 header.set("From", EncodedWord.encode(model.getMessage().getHeader().getHeader().get( 111 "From"), headerCharset, EncodedWord.QUOTED_PRINTABLE).toString()); 112 } else { 113 header.set("From", EncodedWord.encode(identity.getAddress().toString(), 114 headerCharset, EncodedWord.QUOTED_PRINTABLE).toString()); 115 } 116 117 118 119 header.set("X-Priority", model.getPriority()); 120 121 128 header.set("Mime-Version", "1.0"); 129 130 String organisation = identity.getOrganisation(); 131 132 if (organisation != null && organisation.length() > 0) { 133 header.set("Organisation", organisation); 134 } 135 136 Address replyAddress = identity.getReplyToAddress(); 138 139 if (replyAddress != null) { 140 header.set("Reply-To", EncodedWord.encode(replyAddress.getMailAddress(), 141 headerCharset, EncodedWord.QUOTED_PRINTABLE).toString()); 142 } 143 144 String messageID = MessageIDGenerator.generate(); 145 header.set("Message-ID", messageID); 146 147 String inreply = model.getHeaderField("In-Reply-To"); 148 149 if (inreply != null) { 150 header.set("In-Reply-To", EncodedWord.encode(inreply, 151 headerCharset, EncodedWord.QUOTED_PRINTABLE).toString()); 152 } 153 154 String references = model.getHeaderField("References"); 155 156 if (references != null) { 157 header.set("References", references); 158 } 159 160 header.set("X-Mailer", "Columba (" 161 + VersionInfo.getVersion() + ")"); 162 163 header.getAttributes().put("columba.from", identity.getAddress()); 164 165 Date date = new Date (); 167 header.getAttributes().put("columba.date", date); 168 header.set("Date", MessageDate.toString(date)); 169 170 header.getAttributes().put("columba.attachment", new Boolean (model.getAttachments().size() > 0)); 172 173 header.setFlags(model.getMessage().getHeader().getFlags()); 175 176 return header; 177 } 178 179 private boolean needQPEncoding(String input) { 180 for (int i = 0; i < input.length(); i++) { 181 if (input.charAt(i) > 127) { 182 return true; 183 } 184 } 185 186 return false; 187 } 188 189 198 protected String getSignature(File file) { 199 StringBuffer strbuf = new StringBuffer (); 200 201 try { 202 BufferedReader in = new BufferedReader (new FileReader (file)); 203 204 208 String str; 209 210 while ((str = in.readLine()) != null) { 211 strbuf.append(str + "\n"); 212 } 213 214 in.close(); 215 216 return strbuf.toString(); 217 } catch (IOException ex) { 218 ex.printStackTrace(); 219 220 return ""; 221 } 222 } 223 224 235 private StreamableMimePart composeMultipartAlternativeMimePart(boolean appendSignature) { 236 StreamableMimePart textPart = composeTextMimePart(appendSignature); 238 239 StreamableMimePart htmlPart = composeHtmlMimePart(appendSignature); 241 242 LocalMimePart bodyPart = new LocalMimePart(new MimeHeader("multipart", 244 "alternative")); 245 bodyPart.addChild(textPart); 246 bodyPart.addChild(htmlPart); 247 248 return bodyPart; 249 } 250 251 261 private StreamableMimePart composeHtmlMimePart(boolean appendSignature) { 262 LocalMimePart bodyPart = new LocalMimePart(new MimeHeader("text", 264 "html")); 265 266 String charsetName = model.getCharset().name(); 268 269 270 StringBuffer buf = new StringBuffer (); 271 String body = model.getBodyText(); 272 273 body = HtmlParser.substituteURL(body, false); 275 body = HtmlParser.substituteEmailAddress(body, false); 276 277 String lcase = body.toLowerCase(); 279 if (lcase.indexOf("<!doctype") == -1) { 281 buf.append("<!DOCTYPE HTML PUBLIC " 283 + "\"-//W3C//DTD HTML 3.2//EN\">\r\n"); 284 } 285 286 String meta = "<meta " + "http-equiv=\"Content-Type\" " 288 + "content=\"text/html; charset=" + charsetName + "\">"; 289 int pos = lcase.indexOf("<head"); 290 int bodyStart; 291 292 if (pos == -1) { 293 pos = lcase.indexOf("<html") + 6; 295 buf.append(body.substring(0, pos)); 296 buf.append("<head>"); 297 buf.append(meta); 298 buf.append("</head>"); 299 300 bodyStart = pos; 301 } else { 302 pos = lcase.indexOf('>', pos) + 1; 304 buf.append(body.substring(0, pos)); 305 buf.append(meta); 306 307 bodyStart = lcase.indexOf("</head"); 310 } 311 312 int bodyEnd = lcase.indexOf("</body"); 314 buf.append(body.substring(bodyStart, bodyEnd)); 315 316 AccountItem item = model.getAccountItem(); 318 Identity identity = item.getIdentity(); 319 File signatureFile = identity.getSignature(); 320 321 if (signatureFile != null) { 322 String signature = getSignature(signatureFile); 323 324 if (signature != null) { 325 buf.append("\r\n\r\n"); 326 327 buf.append(signature); 330 } 331 } 332 333 buf.append(body.substring(bodyEnd)); 335 body = buf.toString(); 336 337 if (needQPEncoding(body)) { 339 bodyPart.getHeader().setContentTransferEncoding("quoted-printable"); 340 341 if( charsetName.equalsIgnoreCase("us-ascii")){ 344 charsetName = "UTF-8"; 345 LOG.info("Charset was US-ASCII but text has 8-bit chars -> switched to UTF-8"); 346 } 347 } 348 349 bodyPart.getHeader().putContentParameter("charset", charsetName); 350 351 if (body.length() == 0) { 353 body = " "; 354 } 355 356 bodyPart.setBody(new CharSequenceSource(body)); 357 358 return bodyPart; 359 } 360 361 372 private StreamableMimePart composeTextMimePart(boolean appendSignature) { 373 LocalMimePart bodyPart = new LocalMimePart(new MimeHeader("text", 375 "plain")); 376 377 String charsetName = model.getCharset().name(); 379 380 381 String body = model.getBodyText(); 382 383 387 if (model.isHtml()) { 388 body = HtmlParser.htmlToText(body); 389 } 390 391 AccountItem item = model.getAccountItem(); 392 Identity identity = item.getIdentity(); 393 File signatureFile = identity.getSignature(); 394 395 if (appendSignature && signatureFile != null) { 396 String signature = getSignature(signatureFile); 397 398 if (signature != null) { 399 body = body + "\r\n\r\n" + signature; 400 } 401 } 402 403 if (needQPEncoding(body)) { 404 bodyPart.getHeader().setContentTransferEncoding("quoted-printable"); 405 406 if( charsetName.equalsIgnoreCase("us-ascii")){ 409 charsetName = "UTF-8"; 410 LOG.info("Charset was US-ASCII but text has 8-bit chars -> switched to UTF-8"); 411 } 412 } 413 414 bodyPart.getHeader().putContentParameter("charset", charsetName); 416 417 if (body.length() == 0) { 419 body = " "; 420 } 421 422 bodyPart.setBody(new CharSequenceSource(body)); 423 424 return bodyPart; 425 } 426 427 428 429 public SendableMessage compose(IWorkerStatusController workerStatusController, boolean appendSignature) 430 throws Exception { 431 this.accountUid = model.getAccountItem().getUid(); 432 433 workerStatusController.setDisplayText("Composing Message..."); 434 435 MimeTreeRenderer renderer = MimeTreeRenderer.getInstance(); 436 SendableMessage message = new SendableMessage(); 437 SendableHeader header = initHeader(); 438 MimePart root = null; 439 440 445 446 List attachments = model.getAttachments(); 448 List mimeParts = new ArrayList (); 449 Iterator ite = attachments.iterator(); 450 451 while (ite.hasNext()) { 452 mimeParts.add(ite.next()); 453 } 454 455 StreamableMimePart body; 457 458 if (model.isHtml()) { 459 XmlElement composerOptions = MailConfig.getInstance() 461 .getComposerOptionsConfig().getRoot() 462 .getElement("/options"); 463 XmlElement html = composerOptions.getElement("html"); 464 465 if (html == null) { 466 html = composerOptions.addSubElement("html"); 467 } 468 469 String multipart = html.getAttribute("send_as_multipart", "true"); 470 471 if (multipart.equals("true")) { 472 body = composeMultipartAlternativeMimePart(appendSignature); 474 } else { 475 body = composeHtmlMimePart(appendSignature); 477 } 478 } else { 479 body = composeTextMimePart(appendSignature); 481 } 482 483 if (body != null) { 484 mimeParts.add(0, body); 485 } 486 487 if (mimeParts.size() > 1) { 489 root = new MimePart(new MimeHeader("multipart", "mixed")); 490 491 for (int i = 0; i < mimeParts.size(); i++) { 492 root.addChild((StreamableMimePart) mimeParts.get(i)); 493 } 494 } else { 495 root = (MimePart) mimeParts.get(0); 496 } 497 498 if (model.isSignMessage()) { 499 SecurityItem item = model.getAccountItem().getPGPItem(); 500 String idStr = item.get("id"); 501 502 if ((idStr == null) || (idStr.length() == 0)) { 505 item.setString("id", model.getAccountItem().getIdentity() 507 .getAddress().getMailAddress()); 508 } 509 510 PGPMimePart signPart = new PGPMimePart(new MimeHeader("multipart", 511 "signed"), item); 512 513 signPart.addChild(root); 514 root = signPart; 515 } 516 517 if (model.isEncryptMessage()) { 518 SecurityItem item = model.getAccountItem().getPGPItem(); 519 520 List recipientList = model.getRCPTVector(); 522 StringBuffer recipientBuf = new StringBuffer (); 523 524 for (Iterator it = recipientList.iterator(); it.hasNext();) { 525 recipientBuf.append((String ) it.next()); 526 } 527 528 item.setString("recipients", recipientBuf.toString()); 529 530 PGPMimePart signPart = new PGPMimePart(new MimeHeader("multipart", 531 "encrypted"), item); 532 533 signPart.addChild(root); 534 root = signPart; 535 } 536 537 header.setRecipients(model.getRCPTVector()); 538 539 List headerItemList; 540 541 headerItemList = model.getToList(); 542 543 544 if ( ( headerItemList != null ) && (headerItemList.size() > 0) ){ 545 Address adr = null; 546 try { 547 adr = Address.parse((String ) headerItemList.get(0)); 548 header.getAttributes().put("columba.to", adr); 549 } catch (ParserException e) { 550 if (Logging.DEBUG) 551 e.printStackTrace(); 552 } 553 } 554 555 headerItemList = model.getCcList(); 556 557 if ( ( headerItemList != null ) && (headerItemList.size() > 0) ) { 558 Address adr = null; 559 try { 560 adr = Address.parse((String ) headerItemList.get(0)); 561 header.getAttributes().put("columba.cc", adr); 562 } catch (ParserException e) { 563 if (Logging.DEBUG) 564 e.printStackTrace(); 565 } 566 567 } 568 569 root.getHeader().getHeader().merge(header.getHeader()); 570 571 InputStream in = renderer.renderMimePart(root); 572 573 int size = in.available() / 1024; 575 header.getAttributes().put("columba.size", new Integer (size)); 576 577 message.setHeader(header); 578 579 message.setAccountUid(accountUid); 580 581 message.setSourceStream(in); 583 584 return message; 585 } 586 } | Popular Tags |