1 package SnowMailClient.model.multipart; 2 3 import javax.swing.tree.DefaultMutableTreeNode ; 4 import SnowMailClient.utils.*; 5 import snow.utils.storage.*; 6 import SnowMailClient.crypto.Utilities; 7 import SnowMailClient.utils.MailMessageUtils; 8 import SnowMailClient.model.*; 9 10 import java.util.*; 11 import java.text.*; 12 import java.io.*; 13 import java.nio.charset.*; 14 import java.util.regex.*; 15 16 20 public final class MimePart extends DefaultMutableTreeNode 21 { 22 public boolean debug = true; 23 24 private final Header header = new Header(); 26 private byte[] byteContent = new byte[0]; 27 28 29 public enum ContentType { 31 NO_TYPE, 32 MULTIPART, TEXT, IMAGE, VIDEO, AUDIO, APPLICATION, 33 MESSAGE }; 35 36 private boolean isAttachment = false; 38 39 private ContentType contentType = ContentType.TEXT; private String contentSubType = ""; 42 private String boundary; 43 private String name = "?"; 44 45 private String charset = ""; 47 48 49 private boolean isRoot = false; 50 51 54 public MimePart() 55 { 56 super("???"); 57 } 58 59 public int getPartsCount() 60 { 61 return this.getChildCount(); 62 } 63 64 public MimePart getPartAt(int pos) 65 { 66 return (MimePart) this.getChildAt(pos); 67 } 68 69 70 71 73 public void addPart_if_valid(MimePart part) 74 { 75 if(debug) 76 { 77 System.out.println("===> add part of size "+part.getContentTypeString().length()+"\n"); 78 } 79 80 81 if(!part.isMultipart() && (part.byteContent==null || part.byteContent.length==0)) 82 { 83 return; 85 } 86 87 88 this.add(part); 89 } 90 91 92 public byte[] getByteContent() 93 { 94 return byteContent; 95 } 96 97 101 public String getBodyAsText() 102 { 103 String cont = null; 104 try 105 { 106 if(charset.equals("")) 107 { 108 cont = new String (byteContent, "iso-8859-1"); } 111 else 112 { 113 cont =new String (byteContent, charset); 114 } 115 } 116 catch(Exception e) 117 { 118 System.out.println("MimePart: Bad charset decoding, error= "+e.getMessage()); 119 System.out.println("charset = "+charset); 120 System.out.println("content length = "+byteContent.length); 121 122 cont = new String (byteContent); 123 } 124 125 131 132 return cont; 133 } 134 135 public boolean containText(String txtUP) 136 { 137 if(this.contentType==ContentType.TEXT) 138 { 139 if(this.getBodyAsText().toUpperCase().indexOf(txtUP)>=0) 140 { 141 return true; 142 } 143 } 144 return false; 145 } 146 147 148 public String getContentTypeString() { return header.getEntryValue("Content-Type", ""); } 149 150 151 153 public String getID() 154 { 155 return header.getEntryValue("Content-Id", "?"); 156 } 157 158 public boolean isMultipart() 159 { 160 return contentType == ContentType.MULTIPART; 161 } 162 163 public boolean isAttachment() 164 { 165 return isAttachment; 166 } 167 168 170 public ContentType getContentTYPE() 171 { 172 return contentType; 173 } 174 175 177 public String getContentSubType() { return contentSubType; } 178 179 180 184 public boolean lookIfContentIsHTML() 185 { 186 if(contentType!=ContentType.TEXT) return false; 187 if(contentSubType.toLowerCase().indexOf("html")>=0) return true; 188 if(this.byteContent == null) return false; 189 190 String cont = this.getBodyAsText(); 191 if(StringUtils.startsWithIgnoresCaseAndBlanks( cont, "<HTML>")) return true; 193 if(StringUtils.startsWithIgnoresCaseAndBlanks( cont, "<!")) return true; 194 195 return false; 197 } 198 199 200 204 public boolean lookIfContentIsAnImage() 205 { 206 if(contentType==ContentType.IMAGE) return true; 207 String name = this.getName().toLowerCase(); 208 if( name.endsWith(".bmp") || name.endsWith(".png") || name.endsWith(".jpg") 209 ||name.endsWith("jpeg") || name.endsWith("wbmp") || name.endsWith(".gif")) return true; 210 211 return false; 212 } 213 214 218 public boolean lookIfContentIsAnAttachment() 219 { 220 if(isAttachment) return true; 221 if(contentType==ContentType.APPLICATION) return true; 222 if(contentType==ContentType.MESSAGE) return true; 223 224 return false; 225 } 226 227 228 229 231 public void setContentAsMIME_ROOT(Address from, Vector<Address> tos, String subject) 232 { 233 header.removeAllEntries(); 234 isAttachment = false; 235 isRoot = true; 236 setContentType(ContentType.MULTIPART, "Mixed"); 237 238 header.setEntryOverwrite("From", from.toString()); 239 StringBuffer toBuffer = new StringBuffer (); 240 for(int i=0; i<tos.size(); i++) 241 { 242 toBuffer.append(tos.get(i).getMailAddress()); 243 if(i<tos.size()-1) toBuffer.append("; "); 244 } 245 246 header.setEntryOverwrite("To", toBuffer.toString()); 247 header.setEntryOverwrite("Subject", subject); 248 header.setEntryOverwrite("Date", MailMessageUtils.msgDateFormat(new Date())); 249 250 251 header.setEntryOverwrite("Message-ID", MailMessageUtils.createMessageID(from)); 252 header.setEntryOverwrite("MIME-Version", "1.0"); 253 254 { 258 } 260 264 265 266 header.setEntryOverwrite("X-Priority", "3 (Normal)"); 267 header.setEntryOverwrite("X-Mailer", "SnowMail 2.0"); 268 header.setEntryOverwrite("Importance", "Normal"); 269 } 270 271 272 273 276 public void setContentAsPlainTextMailMessage(String text) 277 { 278 282 283 header.removeAllEntries(); 284 isAttachment = false; 285 isRoot = false; 286 setContentType(ContentType.TEXT, "PLAIN"); 287 288 isAttachment = false; 289 this.charset = CharsetUtils.getMinimalEncodingCharset(text);; 290 291 292 try 293 { 294 this.byteContent = text.getBytes(charset); 295 } 296 catch(Exception e) 297 { 298 this.byteContent = text.getBytes(); e.printStackTrace(); 300 } 301 302 308 309 } 310 311 313 public void setContentAsAttachment(ContentType type, String subType, 314 byte[] cont, String name) 315 { 316 setContentType(type, subType); 317 this.isAttachment = true; this.byteContent = cont; 319 this.name = name; 320 } 321 322 public void setContentType(ContentType type, String subType) 323 { 324 contentType = type; 325 this.contentSubType = subType; 326 if(this.isMultipart()) 327 { 328 name = "Multipart"; 329 } 330 } 331 332 335 private void writeMIMEFlagsInHeader(boolean multipart, boolean needsQuotedPrintableFormat) 336 { 337 if(multipart) 338 { 339 342 String mpt = "Multipart"; 343 if(!this.contentSubType.equals("")) 344 { 345 mpt += "/"+contentSubType; 346 } 347 header.setEntryOverwrite("Content-Type", ""+mpt+";\n\tboundary=\""+boundary+"\""); 348 349 header.remove("Content-Transfer-Encoding"); 351 } 352 else 353 { 354 357 String ctt = ""; 358 if( this.contentType==ContentType.TEXT) ctt+="Text"; 359 else if(this.contentType==ContentType.VIDEO) ctt+="Video"; 360 else if(this.contentType==ContentType.IMAGE) ctt+="Image"; 361 else if(this.contentType==ContentType.AUDIO) ctt+="Audio"; 362 else if(this.contentType==ContentType.APPLICATION) ctt+="Attachment"; 363 else if(this.contentType==ContentType.MESSAGE) ctt+="Message"; 364 365 366 if(!this.contentSubType.equals("")) 367 { 368 ctt += "/"+contentSubType; 369 } 370 371 if(this.contentType==ContentType.TEXT) 372 { 373 if(!charset.equals("")) 374 { 375 ctt += ";\n\tcharset="+charset; 376 } 377 } 378 379 if(isAttachment && !name.equals("?")) 380 { 381 ctt += ";\n\tname=\""+name+"\""; 382 } 383 384 header.setEntryOverwrite("Content-Type", ctt); 385 if(this.contentType==ContentType.MULTIPART) 386 { 387 389 header.remove("Content-Transfer-Encoding"); 391 } 392 else if(this.contentType==ContentType.TEXT) 393 { 394 if(needsQuotedPrintableFormat) 395 { 396 header.setEntryOverwrite("Content-Transfer-Encoding", "quoted-printable"); 397 } 398 else 399 { 400 header.setEntryOverwrite("Content-Transfer-Encoding", "7bit"); 401 } 402 } 403 else 404 { 405 header.setEntryOverwrite("Content-Transfer-Encoding", "base64"); 406 } 407 408 if(isAttachment) 409 { 410 header.setEntryOverwrite("Content-Disposition", "attachment;\n\tfilename=\""+name+"\""); 412 } 413 } 414 415 if(isRoot) 416 { 417 header.setEntryOverwrite("Mime-Version", "1.0 (SnowMmail admin@www.snowraver.org)"); 418 } 419 } 420 421 422 424 public byte[] getContent_For_Sending(int level, int rand1, int rand2) throws Exception 425 { 426 ByteArrayOutputStream bos = new ByteArrayOutputStream(); 427 if(this.isMultipart()) 428 { 429 this.boundary = MimeUtils.createUniqueBoundary(level,rand1,rand2); 431 } 432 433 boolean needsQuotedPrintableFormat = !MailMessageUtils.is7Bits(this.byteContent); 434 writeMIMEFlagsInHeader(this.isMultipart(), needsQuotedPrintableFormat); 435 436 bos.write((header.to_ASCII_String()+"\r\n").getBytes("us-ascii")); 437 438 if(this.isMultipart()) 439 { 440 if(level==1) 441 { 442 bos.write(("\r\nThis is a multi-part message in MIME format.\r\n").getBytes("us-ascii")); 443 } 444 445 for(int i=0; i<this.getChildCount(); i++) 446 { 447 bos.write(("\r\n--"+boundary+"\r\n").getBytes("us-ascii")); 448 MimePart mp = this.getPartAt(i); 449 bos.write(mp.getContent_For_Sending(level+1, rand1, rand2)); 450 } 451 452 bos.write(("\r\n--"+boundary+"--\r\n").getBytes("us-ascii")); 453 } 454 else 455 { 456 if(contentType==ContentType.TEXT) 457 { 458 if(needsQuotedPrintableFormat) 460 { 461 bos.write( MailMessageUtils.encodeMessageQuotedPrintable(byteContent) ); 463 } 464 else 465 { 466 bos.write( byteContent ); 467 } 468 } 469 else 470 { 471 bos.write( Utilities.encodeBase64(byteContent).getBytes("us-ascii") ); 472 bos.write("\n".getBytes("us-ascii")); 473 } 474 } 475 476 477 return bos.toByteArray(); 478 } 479 480 481 484 public String createStringRepresentation(int level, int rand1, int rand2) 485 { 486 if(this.isMultipart()) 487 { 488 this.boundary = MimeUtils.createUniqueBoundary(level,rand1,rand2); 490 } 491 492 writeMIMEFlagsInHeader(this.isMultipart(), 493 false); 495 StringBuffer sb = new StringBuffer (); 496 sb.append(getHeaderText()+"\n"); 497 498 if(this.isMultipart()) 499 { 500 if(level==1) 501 { 502 sb.append("\nThis is a multi-part message in MIME format.\n"); 503 } 504 505 for(int i=0; i<this.getChildCount(); i++) 506 { 507 sb.append("\n--"+boundary+"\n"); 508 509 MimePart mp = this.getPartAt(i); 510 sb.append(mp.createStringRepresentation(level+1, rand1, rand2)); 511 512 } 513 sb.append("\n--"+boundary+"--\n"); 514 } 515 else 516 { 517 if(contentType==ContentType.TEXT) 518 { 519 if(charset.equals("")) 520 { 521 sb.append(new String (byteContent)+""); 522 } 523 else 524 { 525 try 526 { 527 sb.append(new String (byteContent, charset)+""); 528 } 529 catch(Exception e) 530 { 531 e.printStackTrace(); 532 sb.append(new String (byteContent)+""); 533 } 534 } 535 } 536 else 537 { 538 sb.append( Utilities.encodeBase64(byteContent) ); 539 sb.append("\n"); 540 } 541 } 542 543 return sb.toString(); 544 } 545 546 548 public String getHeaderText() 549 { 550 return header.toString(); 551 } 552 553 554 556 boolean parse_boundary_end_reached = false; 557 558 571 582 public void parseMimeTree(NumberedLineReader lineReader, 583 String boundaryToParse, 584 boolean jumpToNextBoundary, 585 int level) throws Exception 586 { 587 if(debug) 588 { 589 System.out.println("\nParsing "+boundaryToParse+", jump="+jumpToNextBoundary+", level="+level+", line="+lineReader.getLineNumber()); 590 } 591 592 if(jumpToNextBoundary) 594 { 595 String deadLine = null; 596 while((deadLine=lineReader.readLine())!=null) 597 { 598 int pos = deadLine.indexOf(boundaryToParse); 604 if(pos>=0 && pos<10) break; } 606 if(deadLine==null) 607 { 608 throw new BoundaryNotFoundException("No start found for boundary "+boundaryToParse); 609 } 610 } 611 612 try 615 { 616 Header.parseHeader(lineReader, header); 617 if(debug) 618 { 619 System.out.println(" Header read "+header.getEntriesCount()+"\n line="+lineReader.getLineNumber()); 620 } 621 } 622 catch(Exception e) 623 { 624 String ll = lineReader.getLastLineCached(); 626 contentType = ContentType.NO_TYPE; 627 if(ll==null) 628 { 629 this.parse_boundary_end_reached = true; 630 return ; 631 } 632 633 if(ll.trim().endsWith("--")) 634 { 635 this.parse_boundary_end_reached = true; 636 return ; 637 } 638 639 System.out.println("MimePart parse error at level="+level+", Jump "+jumpToNextBoundary+", bound="+boundaryToParse 641 +", line="+lineReader.getLineNumber()); 642 throw new Exception ("Cannot read mime part header", e); 643 } 644 645 String contentTypeStringUP = getContentTypeString().trim().toUpperCase(); 648 649 if(contentTypeStringUP.equals("")) 650 { 651 this.contentType = ContentType.NO_TYPE; 652 name = "NO_type"; 653 } 654 else if(contentTypeStringUP.indexOf("MULTIPART")>=0) 655 { 656 this.contentType = ContentType.MULTIPART; 657 name = "Multipart"; 658 } 659 else if(contentTypeStringUP.indexOf("MESSAGE")>=0) 660 { 661 this.contentType =ContentType.MESSAGE; 662 name = "Message"; 663 } 664 else if(contentTypeStringUP.indexOf("TEXT")>=0) 665 { 666 this.contentType = ContentType.TEXT; 667 name = "Text"; 668 } 669 else if(contentTypeStringUP.indexOf("IMAGE")>=0) 670 { 671 this.contentType = ContentType.IMAGE; 672 name = "Image"; 673 } 674 else if(contentTypeStringUP.indexOf("VIDEO")>=0) 675 { 676 this.contentType = ContentType.VIDEO; 677 name = "Video"; 678 } 679 else if(contentTypeStringUP.indexOf("AUDIO")>=0) 680 { 681 this.contentType = ContentType.AUDIO; 682 name = "Audio"; 683 } 684 else if(contentTypeStringUP.indexOf("APPLICATION")>=0) 685 { 686 this.contentType = ContentType.APPLICATION; 687 name = "Application"; 688 } 689 else if(contentTypeStringUP.indexOf("ATTACHMENT")>=0) { 691 this.contentType = ContentType.APPLICATION; 692 name = "Application"; 693 } 694 else 695 { 696 throw new RuntimeException ("Unknown mime content type: "+contentTypeStringUP); 697 } 698 699 int posSlash = contentTypeStringUP.indexOf("/"); 701 if(posSlash==-1) 702 { 703 this.contentSubType = ""; 704 } 705 else 706 { 707 this.contentSubType = contentTypeStringUP.substring(posSlash+1); 708 int posSp = contentSubType.indexOf(";"); 709 if(posSp!=-1) contentSubType = contentSubType.substring(0, posSp); 710 } 712 713 714 charset = HeaderUtils.getHeader_Charset_entry(header); 716 717 String cd = header.getEntryValue("Content-Disposition","").toUpperCase(); 719 if(cd.indexOf("ATTACHMENT")!=-1) 720 { 721 this.isAttachment = true; 722 } 723 724 726 int posName = contentTypeStringUP.indexOf("NAME=\""); 729 if(posName!=-1) 730 { 731 int endPos = contentTypeStringUP.indexOf("\"", posName+6); 732 if(endPos>=0) 733 { 734 String ct = getContentTypeString().trim(); 735 name = ct.substring(posName+6, endPos); 736 } 738 } 739 740 name = MailMessageUtils.decodeCharsetCodedArgumentFully(name); 742 743 744 if(debug) 745 { 746 System.out.println(" MimePart ct="+this.contentType+", "+contentSubType); 747 System.out.println(" cs="+charset+" att="+isAttachment+" name="+name+" multi="+isMultipart()); 748 } 749 750 if(this.isMultipart()) 753 { 754 797 798 String boundary = MimePart.extractBoundary(getContentTypeString().trim()); 799 if(boundary==null) 800 { 801 System.out.println("No boundary found for multipart="+contentTypeStringUP); 802 throw new Exception ("No boundary"); 803 } 804 805 boolean first = true; while(true) 808 { 809 MimePart mp = new MimePart(); 810 mp.debug = debug; 811 812 mp.parseMimeTree(lineReader, boundary, first, level+1); 813 if(mp.parse_boundary_end_reached) 814 { 815 if(debug) 816 { 817 System.out.println("end reached !"); 818 } 819 820 if(contentType != ContentType.NO_TYPE) 822 { 823 this.addPart_if_valid(mp); 824 } 825 826 break; } 828 829 first = false; 830 831 if(contentType != ContentType.NO_TYPE) 832 { 833 this.addPart_if_valid(mp); 834 } 835 } 836 837 this.parse_boundary_end_reached = false; 839 return; 840 841 } 842 else 843 { 844 StringBuffer content = new StringBuffer (); 847 String line = null; 848 if(debug) 849 { 850 System.out.println("Parsing mimePart content from line "+lineReader.getLineNumber()); 851 } 852 853 while((line=lineReader.readLine())!=null) 854 { 855 if(line.indexOf(boundaryToParse)!=-1) 856 { 857 break; 859 } 860 content.append(line+"\n"); 861 } 862 863 if(debug) 864 { 865 System.out.println(" ===== read content size = "+content.length()); 866 System.out.println(" ========== START "); 867 System.out.println(content.toString()); 868 System.out.println(" *========== END "); 869 870 } 871 872 873 if(line==null) 874 { 875 878 this.parse_boundary_end_reached = true; 882 883 if(content.length()>0) 884 { 885 setByteContentFromParsed(content.substring(0,content.length()-1)); } 887 else 888 { 889 setByteContentFromParsed(content.toString()); 890 } 891 892 return ; 893 } 894 895 this.parse_boundary_end_reached = line.trim().endsWith("--"); 897 898 if(content.length()>0) 899 { 900 setByteContentFromParsed(content.substring(0,content.length()-1)); } 902 else 903 { 904 setByteContentFromParsed(content.toString()); 905 } 906 907 return ; 908 } 909 } 910 911 912 913 915 private void setByteContentFromParsed(String content) throws Exception 916 { 917 919 930 if(debug) 931 { 932 System.out.println("setByteContentFromParsed false"); 933 } 934 935 setByteContentFromParsed(content, debug); 936 937 } 938 939 940 public String getTextForTreeRepresentation() 941 { 942 if(byteContent!=null && byteContent.length>0) 943 { 944 return getName()+" ("+MailMessageUtils.formatSize(byteContent.length)+")"; 945 } 946 else 947 { 948 return getName(); 949 } 950 } 951 952 public String getName() 953 { 954 if(this.lookIfContentIsHTML()) return "HTML"; 956 return name; 957 } 958 959 962 private void setByteContentFromParsed(String scont, boolean debug) throws Exception 963 { 964 String contentTransferEncoding = header.getEntryValue("content-transfer-encoding", "").toUpperCase(); 967 968 if(debug) 969 { 970 System.out.println("setByteContentFromParsed enc = "+contentTransferEncoding); 971 System.out.println(""+scont+"\n============="); 972 } 973 974 String charsetToDecode = "iso-8859-1"; 975 if(charset.length()>0) 976 { 977 charsetToDecode = charset; 978 } 979 980 if(charsetToDecode.equalsIgnoreCase("us-ascii")) 981 { 982 charsetToDecode = "iso-8859-1"; 986 charset = "iso-8859-1"; 987 } 988 989 try 992 { 993 Charset cs = Charset.forName(charsetToDecode); 994 "hello".getBytes(charsetToDecode); 995 } 996 catch(Exception e) 997 { 998 if(debug) 999 { 1000 System.out.println("MimePart unknown charset "+charsetToDecode+" using iso-8859-1"); 1001 } 1002 charsetToDecode = "iso-8859-1"; 1003 charset = "iso-8859-1"; 1004 } 1005 1006 if(contentTransferEncoding.equals("")) 1007 { 1008 byteContent = scont.getBytes(charsetToDecode); 1010 } 1011 else if(contentTransferEncoding.indexOf("8BIT")!=-1) 1012 { 1013 byteContent = scont.getBytes(charsetToDecode); 1014 } 1015 else if(contentTransferEncoding.indexOf("7BIT")!=-1) 1016 { 1017 byteContent = scont.getBytes(charsetToDecode); 1018 } 1019 else if(contentTransferEncoding.indexOf("QUOTED-PRINTABLE")!=-1) 1020 { 1021 try 1022 { 1023 byteContent = MailMessageUtils.decodeQuotedPrintable(scont, charsetToDecode).getBytes(charsetToDecode); 1024 } 1025 catch(Exception e) 1026 { 1027 System.out.println("Cannot decode charset '"+charsetToDecode+"'"); 1028 charset = "iso-8859-1"; 1029 byteContent = MailMessageUtils.decodeQuotedPrintable(scont, "iso-8859-1").getBytes("iso-8859-1"); 1030 } 1031 } 1032 else if(contentTransferEncoding.indexOf("BASE64")!=-1) 1033 { 1034 try 1035 { 1036 byteContent = Utilities.decodeBase64( scont ); 1037 } 1038 catch(Exception e) 1039 { 1040 throw new Exception ("MimePart: error decoding base 64 content:\n "+e.getMessage()); 1041 } 1042 } 1043 else 1044 { 1045 System.out.println("MimePart: unknown content-transfer-encoding: "+contentTransferEncoding); 1046 byteContent = scont.getBytes(charsetToDecode); 1048 } 1049 1050 } 1051 1052 public static void main(String [] arguments) 1053 { 1054 System.out.println(extractBoundary("BOUNDARY=\"12a3\"")); 1055 } 1056 1057 public static String extractBoundary(String txt) 1058 { 1059 Pattern p = Pattern.compile("boundary\\s*=\\s*(.*)", Pattern.CASE_INSENSITIVE); 1060 Matcher m = p.matcher(txt); 1061 if(m.find()) 1062 { 1063 String found = m.group(1).trim(); 1064 if(found.startsWith("\"")) found = found.substring(1); 1065 if(found.endsWith(";")) found = found.substring(0,found.length()-1); 1066 if(found.endsWith("\"")) found = found.substring(0,found.length()-1); 1067 return found; 1068 } 1069 else 1070 { 1071 return null; 1072 } 1073 1074 } 1075 1076 1077 1078} | Popular Tags |