1 package SnowMailClient.model; 2 3 import snow.utils.storage.*; 4 import SnowMailClient.utils.*; 5 import snow.utils.gui.*; 6 import snow.concurrent.*; 7 import SnowMailClient.*; 8 import SnowMailClient.crypto.*; 9 import SnowMailClient.model.multipart.*; 10 import SnowMailClient.Language.Language; 11 import SnowMailClient.SpamFilter.WordStatistic; 12 import SnowMailClient.model.events.MailMessageChangeListener; 13 import SnowMailClient.gnupg.GnuPGLink; 14 import SnowMailClient.gnupg.model.*; 15 16 import java.util.*; 17 import java.text.*; 18 import java.io.*; 19 import javax.swing.event.ChangeListener ; 20 import javax.swing.event.ChangeEvent ; 21 22 31 public final class MailMessage implements Vectorizable 32 { 33 public boolean debug = false; 34 35 38 private String completeContent; 39 40 private AppProperties properties = new AppProperties(); 42 43 47 private String messageBody = ""; 48 49 private Header header = new Header(); 51 52 private MimeTreeModel mimeTreeModel = new MimeTreeModel(new MimePart()); 54 55 56 59 private Vector<MailMessageChangeListener> changeListeners = new Vector<MailMessageChangeListener>(); 60 61 private final SimpleDateFormat dateFormat = new SimpleDateFormat("dd MMM yyyy HH':'mm", Language.getInstance().getLocale()); 62 63 public boolean selectedInView = false; 65 66 68 public void parse(String completeContent) 69 { 70 if(debug) System.out.println("\nMailMessage Parse debug:"); 71 header.removeAllEntries(); 72 73 this.completeContent = completeContent; 74 this.parse(completeContent, true); 75 } 76 77 80 public Header getHeader() { return header; } 81 82 public Address getFromAddress() { return new Address(header.getEntryValue("from","?")); } 83 public Vector<Address> getToAddresses() { return Address.parseAddressList(header.getEntryValue("to","?")); } 84 public String getToAddressesText() { return header.getEntryValue("to","?"); } 85 86 public String getSubject() { return header.getEntryValue("subject","?"); } 87 public String getMessageID() { return header.getEntryValue("message-id","?"); } 88 public String getDateFieldFormHeader() { return header.getEntryValue("date","?"); } 89 public String getContentType() { return header.getEntryValue("content-type", ""); } 90 public int getSize() { return completeContent.length(); } 91 92 96 public boolean lookIfContentIsHTML() 97 { 98 String ct = header.getEntryValue("content-type", "").toUpperCase(); 99 if(ct.indexOf("TEXT/HTML")!=-1) return true; 100 101 if(StringUtils.startsWithIgnoresCaseAndBlanks( this.getMessageBody(), "<HTML")) return true; 103 if(StringUtils.startsWithIgnoresCaseAndBlanks( this.getMessageBody(), "<x-HTML")) return true; 104 if(StringUtils.startsWithIgnoresCaseAndBlanks( this.getMessageBody(), "<!")) return true; 105 106 return false; 108 } 109 110 113 public boolean lookIfContentIsPGPEncrypted() 114 { 115 if( messageBody.length()<100) return false; 116 117 if( this.messageBody.substring(0,80).toUpperCase().indexOf("-----BEGIN PGP MESSAGE-----")==-1) return false; 118 if( this.messageBody.substring(messageBody.length()-80).toUpperCase().indexOf("-----END PGP MESSAGE-----")==-1) return false; 119 120 return true; 121 } 122 123 public boolean lookIfContentIsPGPSigned() 124 { 125 if( messageBody.length()<100) return false; 126 127 if( this.messageBody.substring(0,80).toUpperCase().indexOf("-----BEGIN PGP SIGNED MESSAGE-----")==-1) return false; 128 if( this.messageBody.substring(messageBody.length()-80).toUpperCase().indexOf("-----END PGP SIGNATURE-----")==-1) return false; 129 130 return true; 131 } 132 133 136 public boolean getIsNew() { return properties.getBoolean("isNew", true); } 137 138 public long getParsedMessageTime() { return properties.getLong( "parsedMessageTime", 0L); } 139 140 public boolean getHasBeenSent() { return properties.getBoolean( "hasBeenSent", false); } 141 public void setHasBeenSent(boolean b) 142 { 143 properties.setBoolean( "hasBeenSent", b); 144 this.notifyChangeListeners(MailMessageChangeListener.MailMessageChangeType.PROPERTY, "has been sent="+b); 145 } 146 public boolean getHasBeenReceived() { return properties.getBoolean( "HasBeenReceived", false); } 147 public void setHasBeenReceived(boolean b) 148 { 149 properties.setBoolean( "HasBeenReceived", b); 150 this.notifyChangeListeners(MailMessageChangeListener.MailMessageChangeType.PROPERTY, "has been received="+b); 151 } 152 153 public void setSPAM(boolean is) 154 { 155 properties.setBoolean( "isSpam", is); 156 if(is) properties.setBoolean( "isHam", false); 157 this.notifyChangeListeners(MailMessageChangeListener.MailMessageChangeType.PROPERTY, "is spam="+is); 158 } 159 public boolean getIsSPAM() { return properties.getBoolean("isSpam", false); } 160 161 public void setHAM(boolean is) 162 { 163 properties.setBoolean( "isHam", is); 164 if(is) properties.setBoolean( "isSpam", false); 165 this.notifyChangeListeners(MailMessageChangeListener.MailMessageChangeType.PROPERTY, "is ham="+is); 166 } 167 public boolean getIsHAM() 168 { 169 if( properties.getBoolean("isHam", false) ) return true; 170 if(getIsSPAM()) return false; 171 if( SnowMailClientApp.getInstance().getAddressBook().hasAddress(getFromAddress().getMailAddress()) ) 172 { 173 return true; 174 } 175 return false; 176 } 177 178 181 public boolean getIsFalsePositive() 182 { 183 double prob = this.getSPAMProbability(); 184 return WordStatistic.isSpam(prob) && getIsHAM(); 185 } 186 187 public void setSPAMProbability(double p) 188 { 189 properties.setDouble( "SPAMProbability", p); 190 this.notifyChangeListeners(MailMessageChangeListener.MailMessageChangeType.PROPERTY, "spam prob="+p); 191 } 192 193 public double getSPAMProbability() 194 { 195 return properties.getDouble("SPAMProbability", -1); 196 } 197 198 199 200 203 public void setIsNoMoreNew() 204 { 205 properties.setBoolean("isNew", false); 206 this.notifyChangeListeners(MailMessageChangeListener.MailMessageChangeType.PROPERTY, "is no more new"); 207 } 208 209 public void setMustBeSigned(boolean must) 210 { 211 System.out.println("Message must be signed"); 212 properties.setBoolean("mustBeSigned", must); 213 this.notifyChangeListeners(MailMessageChangeListener.MailMessageChangeType.PROPERTY, "must be signed="+must); 214 } 215 216 public boolean getMustBeSigned() 217 { 218 return properties.getBoolean("mustBeSigned", false); 219 } 220 221 public void setMustBeEncrypted(boolean must) 222 { 223 properties.setBoolean("mustBeEncrypted", must); 224 this.notifyChangeListeners(MailMessageChangeListener.MailMessageChangeType.PROPERTY, "must be enctypted="+must); 225 } 226 227 public boolean getMustBeEncrypted() { return properties.getBoolean("mustBeEncrypted", false); } 228 229 public void setHasBeenEncrypted(boolean was) 230 { 231 properties.setBoolean("hasBeenEncrypted", was); 232 this.notifyChangeListeners(MailMessageChangeListener.MailMessageChangeType.PROPERTY, "was enctypted="+was); 233 } 234 235 private void setHasBeenSigned(boolean was) 236 { 237 properties.setBoolean("hasBeenSigned", was); 238 this.notifyChangeListeners(MailMessageChangeListener.MailMessageChangeType.PROPERTY, "was signed="+was); 239 } 240 241 242 public boolean getHasBeenEncrypted() { return properties.getBoolean("hasBeenEncrypted", false); } 243 public boolean getHasBeenSigned( ) { return properties.getBoolean("hasBeenSigned", false); } 244 245 248 public String getStatusRemark() 249 { 250 StringBuffer rem = new StringBuffer (); 251 252 if(getHasBeenSent()) 254 { 255 256 if(this.getHasBeenEncrypted() && this.getHasBeenSigned()) 257 { 258 rem.append( Language.translate("Message has been sent signed and encrypted")); 259 } 260 else if(this.getHasBeenEncrypted()) 261 { 262 rem.append( Language.translate("Message has been sent encrypted")); 263 } 264 else if(this.getHasBeenSigned()) 265 { 266 rem.append( Language.translate("Message has been sent signed")); 267 } 268 else 269 { 270 rem.append("\n"+ Language.translate("Message has been sent")); 271 } 273 } 274 else if(getHasBeenReceived()) 275 { 276 if( this.getHasBeenDecrypted() ) 277 { 278 rem.append( Language.translate("Message has been decrypted") ); 279 } 280 } 283 else 284 { 285 rem.append( Language.translate("Message has not been sent yet") ); 286 } 287 288 String signatureVerifications = properties.getStringLCK("signatureVerifications",""); 289 if(signatureVerifications.length()>0) 290 { 291 rem.append("\n"+signatureVerifications); 292 } 293 294 return rem.toString().trim(); 295 } 296 297 301 public void setActualDate() 302 { 303 header.setEntryOverwrite("date", MailMessageUtils.msgDateFormat(new Date())); 304 recomposeMessageFromParts(); 305 this.notifyChangeListeners(MailMessageChangeListener.MailMessageChangeType.HEADER, "Date changed"); 306 } 307 308 310 public void setRequestNotification_(Address address) 311 { 312 header.setEntryOverwrite("Disposition-Notification-To", address.toString()); 314 315 header.setEntryOverwrite("Return-Receipt-To", address.toString()); 317 recomposeMessageFromParts(); 318 this.notifyChangeListeners(MailMessageChangeListener.MailMessageChangeType.HEADER, "return address set to "+address); 319 } 320 321 322 public void setIsReplyMessage(String id_original) 323 { 324 header.setEntryOverwrite("In-Reply-To", id_original); 325 header.setEntryOverwrite("References", id_original); 326 this.notifyChangeListeners(MailMessageChangeListener.MailMessageChangeType.HEADER, "Reply to message id set = "+id_original); 327 } 328 329 330 334 public void setMessageHeader(Address from, Vector<Address> tos, String subject) 335 { 336 String fromString = ""; 337 if(from!=null) 338 { 339 fromString = from.toString(); 340 } 341 342 header.setEntryOverwrite("From", fromString); 343 StringBuffer toBuffer = new StringBuffer (); 344 if(tos!=null) 345 { 346 for(int i=0; i<tos.size(); i++) 347 { 348 toBuffer.append(tos.elementAt(i).getMailAddress()); 349 if(i<tos.size()-1) toBuffer.append("; "); 350 } 351 } 352 header.setEntryOverwrite("To", toBuffer.toString()); 353 header.setEntryOverwrite("Subject", subject); 354 header.setEntryOverwrite("Date", MailMessageUtils.msgDateFormat(new Date())); 355 properties.setLong("parsedMessageTime", new Date().getTime()); 356 357 if(from!=null) 358 { 359 header.setEntryOverwrite("Message-ID", MailMessageUtils.createMessageID(from)); 360 } 361 header.setEntryOverwrite("Reply-To", fromString); 362 header.setEntryOverwrite("Return-Path", fromString); 363 364 this.notifyChangeListeners(MailMessageChangeListener.MailMessageChangeType.HEADER, "header set"); 365 } 366 367 370 public void setMessage(Address from, Vector<Address> tos, String subject, String body) 371 { 372 if(this.mimeTreeModel.isMimeMessageFormat()) 373 { 374 mimeTreeModel.setMessagePlainText(from, tos, subject, body); 375 completeContent = mimeTreeModel.createMessageStringContent(); 378 this.parse(completeContent); 379 } 380 else 381 { 382 header.setEntryOverwrite("From", from.toString()); 383 StringBuffer toBuffer = new StringBuffer (); 384 for(int i=0; i<tos.size(); i++) 385 { 386 toBuffer.append(tos.elementAt(i).getMailAddress()); 387 if(i<tos.size()-1) toBuffer.append("; "); 388 } 389 header.setEntryOverwrite("To", toBuffer.toString()); 390 header.setEntryOverwrite("Subject", subject); 391 header.setEntryOverwrite("Date", MailMessageUtils.msgDateFormat(new Date())); 392 393 properties.setLong("parsedMessageTime", new Date().getTime()); 394 395 header.setEntryOverwrite("Message-ID", MailMessageUtils.createMessageID(from)); 396 header.setEntryOverwrite("MIME-Version", "1.0"); 397 398 399 400 header.setEntryOverwrite("X-Mailer", "SnowMail 1.0"); 401 header.setEntryOverwrite("Reply-To", from.toString()); 402 header.setEntryOverwrite("Return-Path", from.toString()); 403 404 410 411 412 setRequestNotification_(from); 415 416 418 422 this.messageBody = body; 423 424 recomposeMessageFromParts(); 425 } 426 427 this.notifyChangeListeners(MailMessageChangeListener.MailMessageChangeType.MESSAGE, "message set"); 428 } 429 430 431 433 private void recomposeMessageFromParts() 434 { 435 this.completeContent = 436 header.toString() 437 + "\r\n" 438 + messageBody; 439 } 440 441 public boolean isEditable(){ return properties.getBoolean("isEditable", false); } 442 public void setEditable(boolean is) 443 { 444 properties.setBoolean("isEditable", is); 445 this.notifyChangeListeners(MailMessageChangeListener.MailMessageChangeType.PROPERTY, "is editable="+is); 446 } 447 448 449 451 public boolean searchText(String textUP) 452 { 453 MimePart[] partsFound = this.mimeTreeModel.searchInTextParts(textUP); 454 if(partsFound.length>0) 455 { 456 return true; 457 } 458 459 if(this.messageBody.toUpperCase().indexOf(textUP)>=0) 460 { 461 return true; 462 } 463 464 return false; 466 } 467 468 474 475 476 478 public String getMessageBody() 479 { 480 return messageBody; 481 } 482 483 486 public String getCompleteContentAsString() 487 { 488 return completeContent; 489 } 490 491 492 493 498 public byte[] getCompleteContentToSend(GnuPGLink gpg, GnuPGKeyID signingKid, byte[] signingPass, Interrupter interrupter) throws Exception 499 { 500 ByteArrayOutputStream buffer = new ByteArrayOutputStream(1024); 501 if(this.getMimeTree().isMimeMessageFormat()) 502 { 503 if(signingKid!=null) 504 { 505 throw new Exception ("Signing is currently not supported for MIME messages"); 506 } 507 508 buffer.write( mimeTreeModel.getContent_For_Sending() ); 509 510 516 517 } 518 else 519 { 520 521 String encodingCharset = CharsetUtils.getMinimalEncodingCharset(messageBody); 522 byte[] messageBodyBytes = messageBody.getBytes(encodingCharset); 523 header.setEntryOverwrite("Content-Type", "text/plain;\r\n\tcharset=\""+encodingCharset+"\""); 524 525 if(signingKid!=null) 526 { 527 ByteArrayInputStream bin = new ByteArrayInputStream( messageBodyBytes.clone()); 529 messageBodyBytes = gpg.sign(bin, signingKid, signingPass, interrupter); 530 setHasBeenSigned(true); 531 } 532 533 boolean useQuotedPrintable = !MailMessageUtils.is7Bits(messageBodyBytes); 534 535 if(useQuotedPrintable) 536 { 537 header.setEntryOverwrite("Content-Transfer-Encoding", "quoted-printable"); messageBodyBytes = MailMessageUtils.encodeMessageQuotedPrintable(messageBodyBytes); 539 } 540 else 541 { 542 header.setEntryOverwrite("Content-Transfer-Encoding", "8bit"); } 544 545 buffer.write(this.header.to_ASCII_String().getBytes("us-ascii")); 546 buffer.write("\r\n".getBytes("us-ascii")); 547 buffer.write(messageBodyBytes); 548 } 549 return buffer.toByteArray(); 550 } 551 552 554 public MimeTreeModel getMimeTree() 555 { 556 return mimeTreeModel; 557 } 558 559 563 private void parse(String mess, boolean parseBody) 564 { 565 NumberedLineReader reader = new NumberedLineReader( mess ); 566 567 try 568 { 569 Header.parseHeader(reader, header); 570 571 if(parseBody) 572 { 573 StringBuffer sb = new StringBuffer (); 575 String fromLine = reader.readLine(); 576 while(fromLine!=null) 577 { 578 sb.append(fromLine); 579 sb.append("\n"); 580 fromLine = reader.readLine(); 581 } 582 583 messageBody = sb.toString(); 584 585 messageBody = decodeContent(messageBody); 588 589 if(debug) 590 { 591 System.out.println("================ Body part ("+messageBody.length()+") chars"); 592 } 593 594 if(MimeUtils.isMultipart(this)) 595 { 596 mimeTreeModel.clearMimeTree(); 598 MimePart rootPart = mimeTreeModel.getRootPart(); 599 rootPart.debug = debug; 600 rootPart.parseMimeTree( 601 new NumberedLineReader(mess), 602 "#no#boundary%&/?", false, 0); 605 } 606 607 } 608 reader.close(); 609 } 610 catch(Exception e) 611 { 612 613 messageBody = mess; 614 error = e.getMessage(); 615 System.out.println("Mail Parse Error: "+e.getMessage()); 616 System.out.println(" from = "+this.getFromAddress()); 617 System.out.println(" to = "+this.getToAddresses()); 618 System.out.println(" subject = "+this.getSubject()); 619 620 if(e.getMessage().length()==0) 621 { 622 e.printStackTrace(); 624 error = "Mail parse error"; 625 } 627 else 628 { 629 e.printStackTrace(); 631 } 632 } 633 634 String dateString = this.getDateFieldFormHeader(); 636 if(dateString.equals("?")) 637 { 638 header.setEntryOverwrite("date", MailMessageUtils.msgDateFormat(new Date())); 640 recomposeMessageFromParts(); 641 } 642 643 try 644 { 645 properties.setLong("parsedMessageTime", 646 MailMessageUtils.parseDateFromString(dateString).getTime()); 647 } 648 catch(Exception e) 649 { 650 properties.setLong("parsedMessageTime", 0L); 651 if(!dateString.equals("?")) 652 { 653 System.out.println("Cannot parse date from '"+dateString+"'"); 654 } 655 } 656 } 657 658 String error = null; 659 public String getErrorMessage() 660 { 661 return error; 662 } 663 664 public boolean hasParseError() { return error!=null; } 665 666 667 668 669 671 private String decodeContent(String text) throws Exception 672 { 673 String contentTransferEncoding = header.getEntryValue("content-transfer-encoding", "").toUpperCase(); 674 String cs = HeaderUtils.getHeader_Charset_entry(header); 675 if(debug) 676 { 677 System.out.println("MailMessage decode content ct="+contentTransferEncoding+", cs="+cs); 678 } 679 680 if(contentTransferEncoding.indexOf("QUOTED-PRINTABLE")>=0) 681 { 682 return MailMessageUtils.decodeQuotedPrintable(text, cs); 683 } 684 else if(contentTransferEncoding.indexOf("8BIT")>=0) 685 { 686 try 687 { 688 691 if(!CharsetUtils.isEncodingSupported(text,"iso-8859-1")) 692 { 693 694 if(debug) 695 { 696 System.out.print("===== Bad format iso-8859-1 for text ====="); 697 int pos = CharsetUtils.getFirstEncodingError(text, "iso-8859-1"); 698 if(pos>=0) 699 { 700 char errorChar = text.charAt(pos); 701 System.out.print(" char= ' "+errorChar+" ', code="+((int) errorChar)); 702 } 703 System.out.println(", from= " + this.getFromAddress()+", subj= "+this.getSubject()); 704 705 System.out.println(""+ text + "===== end ====="); 706 } 707 708 return text; 710 } 711 712 byte[] bytes = text.getBytes("iso-8859-1"); String dec = new String (bytes, cs); 714 715 return dec; 716 717 } 718 catch(Exception e) 719 { 720 return text; 721 } 722 } 723 else if(contentTransferEncoding.indexOf("BASE64")>=0) 724 { 725 try 726 { 727 return new String (Utilities.decodeBase64(text), cs); 728 } 729 catch(Exception e) 730 { 731 throw new Exception ("Error decoding part base 64 content:\n "+e.getMessage()); 732 } 733 } 734 else 735 { 736 return text; 737 } 738 } 739 740 741 742 744 public String getTextRepresentationForPrinting() 745 { 746 StringBuffer sb = new StringBuffer (); 747 sb.append("From: \t"+this.getFromAddress().toString()); 748 sb.append("\nTo: \t"+this.getToAddressesText()); 749 sb.append("\nSubject:\t"+this.getSubject()); 750 sb.append("\nDate: \t"+this.getDateFieldFormHeader()); 751 sb.append("\n\n"); 752 753 if(this.getMimeTree().isMimeMessageFormat()) 754 { 755 MimePart mp = getMimeTree().getFirstPlainTextPart(); 756 if(mp!=null) 757 { 758 sb.append(""+mp.getBodyAsText()); 759 } 760 else 761 { 762 sb.append(this.getMessageBody()); 763 } 764 } 765 else 766 { 767 sb.append(this.getMessageBody()); 768 } 769 770 return sb.toString(); 771 } 772 773 777 public void addChangeListener(MailMessageChangeListener mcl) 778 { 779 this.changeListeners.add(mcl); 780 } 781 782 public void removeChangeListener(MailMessageChangeListener mcl) 783 { 784 this.changeListeners.remove(mcl); 785 } 786 787 private void notifyChangeListeners(MailMessageChangeListener.MailMessageChangeType type, String detail) 788 { 789 MailMessageChangeListener[] mcls = null; 793 synchronized(changeListeners) 794 { 795 mcls = changeListeners.toArray(new MailMessageChangeListener[changeListeners.size()]); 796 } 797 for(MailMessageChangeListener mcl : mcls) 798 { 799 mcl.mailMessageChanged(this, type, detail); 800 } 801 } 802 803 809 public void notifyThatThisMessageIsBeingEditedNow(String detail) 810 { 811 MailMessageChangeListener[] mcls = null; 815 synchronized(changeListeners) 816 { 817 mcls = changeListeners.toArray(new MailMessageChangeListener[changeListeners.size()]); 818 } 819 for(MailMessageChangeListener mcl : mcls) 820 { 821 mcl.mailMessageChanged(this, MailMessageChangeListener.MailMessageChangeType.IN_EDITION, detail); 822 } 823 } 824 825 828 829 831 public void setDecryptedMessage(String decryptedMessage) 832 { 833 this.parse(decryptedMessage); 834 properties.setBoolean( "HasBeenDecrypted", true); 835 this.notifyChangeListeners(MailMessageChangeListener.MailMessageChangeType.DECRYPTED, "decrypted message"); this.notifyChangeListeners(MailMessageChangeListener.MailMessageChangeType.HEADER, "decrypted message"); } 838 839 841 public boolean getHasBeenDecrypted() 842 { 843 return properties.getBoolean( "HasBeenDecrypted", false); 844 } 845 846 847 public void setSignatureVerification(Vector<SignatureVerificationResult> res) 848 { 849 StringBuffer signaturesText = new StringBuffer (); 850 if(res.size()==0) 851 { 852 signaturesText.append(Language.translate("NO SIGNATURES FOUND")); 853 } 854 855 for(SignatureVerificationResult sign: res) 856 { 857 signaturesText.append("\n"+sign.toString()); 858 } 859 properties.setStringLCK("signatureVerifications", signaturesText.toString().trim()); 860 this.notifyChangeListeners(MailMessageChangeListener.MailMessageChangeType.DECRYPTED, "signature verification"); } 862 863 864 868 public MailMessage() 869 { 870 } 871 872 public Vector<Object > getVectorRepresentation() throws VectorizeException 873 { 874 875 Vector<Object > rep = new Vector<Object >(); 876 rep.addElement(2); 877 rep.addElement(completeContent); 878 rep.addElement(properties.getVectorRepresentation()); 879 880 return rep; 881 } 882 883 885 @SuppressWarnings ("unchecked") 886 public void createFromVectorRepresentation(Vector<Object > v) throws VectorizeException 887 { 888 int version = (Integer ) v.get(0); 889 if(version>=1) 890 { 891 completeContent = (String ) v.get(1); 892 properties.createFromVectorRepresentation((Vector) v.get(2)); 893 this.parse(completeContent, true); 894 895 if(version>=2) 896 { 897 } 899 } 900 else 901 { 902 throw new VectorizeException(Language.translate("bad version %",""+version)); 903 } 904 } 905 906 907 908 } | Popular Tags |