Mercurial Hosting > nabble
diff src/fschmidt/util/mail/javamail/MailImpl.java @ 68:00520880ad02
add fschmidt source
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Sun, 05 Oct 2025 17:24:15 -0600 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/mail/javamail/MailImpl.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,618 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.mail.javamail; + +import fschmidt.util.java.IoUtils; +import fschmidt.util.mail.AlternativeMultipartContent; +import fschmidt.util.mail.Content; +import fschmidt.util.mail.FileAttachmentContent; +import fschmidt.util.mail.HtmlTextContent; +import fschmidt.util.mail.Mail; +import fschmidt.util.mail.MailAddress; +import fschmidt.util.mail.MailAddressException; +import fschmidt.util.mail.MailEncodingException; +import fschmidt.util.mail.MailException; +import fschmidt.util.mail.MailParseException; +import fschmidt.util.mail.MixedMultipartContent; +import fschmidt.util.mail.MultipartContent; +import fschmidt.util.mail.PlainTextContent; +import fschmidt.util.mail.TextAttachmentContent; +import fschmidt.util.mail.TextContent; +import fschmidt.util.mail.UnsupportedContent; + +import javax.activation.CommandMap; +import javax.activation.MailcapCommandMap; +import javax.mail.Address; +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.Part; +import javax.mail.Session; +import javax.mail.internet.AddressException; +import javax.mail.internet.ContentType; +import javax.mail.internet.HeaderTokenizer; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeBodyPart; +import javax.mail.internet.MimeMessage; +import javax.mail.internet.MimeMultipart; +import javax.mail.internet.MimePart; +import javax.mail.internet.MimeUtility; +import javax.mail.internet.ParseException; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.util.Date; +import java.util.Enumeration; + + +public class MailImpl implements Mail { + private static final Session nullSession = null; + + class MyMimeMessage extends MimeMessage { + MyMimeMessage() { + super(nullSession); + } + + MyMimeMessage(InputStream is) throws MessagingException { + super(nullSession,is); + } + + MyMimeMessage(MimeMessage msg) throws MessagingException { + super(msg); + } + + void setSession(Session session) { + this.session = session; + } + + protected void updateHeaders() + throws MessagingException + { + super.updateHeaders(); + if( messageID != null ) + setHeader( "Message-ID", messageID ); + } + } + + final MyMimeMessage msg; + private String messageID = null; + + public MailImpl() { + this.msg = new MyMimeMessage(); + } + + public MailImpl(String rawInput) { + try { + this.msg = new MyMimeMessage(new ByteArrayInputStream(rawInput.getBytes("ISO-8859-1"))); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } catch(MessagingException e) { + throw MailImpl.e(e); + } + } + + MailImpl(MimeMessage msg) throws MessagingException { + this.msg = new MyMimeMessage(msg); + } + + public String getType() { + return "message"; + } + + public String getSubtype() { + return "rfc822"; + } + + public Content getContent() throws MailException { + try { + return getPart(msg); + } catch(MessagingException e) { + throw MailImpl.e(e); + } catch(IOException e) { + throw MailImpl.e(e); + } + } + + private static Content getPart(Part part) throws MessagingException, IOException { + String ct = part.getContentType().toLowerCase(); + int end = ct.indexOf(';'); + if( end != -1 ) + ct = ct.substring(0,end); + String[] a = ct.split("/"); + if (a.length==0) + return new UnsupportedContent(part.getInputStream(), null, null); + String type = a[0]; + String subtype = a.length>1 ? a[1]:""; + if( type.equals("text") ) { + String text = null; + Object obj; + try { + obj = part.getContent(); + } catch (UnsupportedEncodingException e) { + obj = IoUtils.readAll(new InputStreamReader(part.getInputStream(), "ISO-8859-1")); + if (!isAllAscii((String)obj)) + return new UnsupportedContent(obj, type, subtype); + } + if( Part.ATTACHMENT.equalsIgnoreCase(part.getDisposition()) ) + return new FileAttachmentContentImpl((MimePart)part, type, subtype); + if( obj instanceof String ) { + text = (String)obj; + } else if( obj instanceof InputStream ) { + try { + String charset = null; + String filename = null; + try { + ContentType contentType = new ContentType(part.getContentType()); + charset = MimeUtility.javaCharset(contentType.getParameter("charset")); + filename = contentType.getParameter("name"); + } catch (ParseException e) {} + text = IoUtils.readAll(new InputStreamReader((InputStream)obj,charset!=null?charset:"ISO-8859-1")); + } catch (IOException e) { + return new UnsupportedContent(part.getInputStream(), type, subtype); + } catch (MessagingException e) { + return new UnsupportedContent(part.getInputStream(), type, subtype); + } + } else { + return new FileAttachmentContentImpl((MimePart)part, type, subtype); + } + if( subtype.equals("plain") || subtype.equals("")) + return new PlainTextContent(text); + if( subtype.equals("html") ) + return new HtmlTextContent(text); + return new TextContent(subtype,text); + } + if( type.equals("multipart") && part.getContent() instanceof MimeMultipart) { + Content[] parts = getParts( (MimeMultipart)part.getContent() ); + if( subtype.equals("alternative") ) + return new AlternativeMultipartContent(parts); + if( subtype.equals("mixed") ) + return new MixedMultipartContent(parts); + if( subtype.equals("signed") ) + return new MixedMultipartContent(parts); + return new MultipartContent(subtype,parts); + } + if( type.equals("message") && subtype.equals("rfc822") && part.getContent() instanceof MimeMessage) { + MimeMessage mm = (MimeMessage)part.getContent(); + return new MailImpl(mm); + } + /* + if( type.equals("application") ) { + if( subtype.equals("pgp-signature") ) + return new PlainTextContent((String)obj); + } + */ + return new FileAttachmentContentImpl((MimePart)part, type, subtype); + } + + private static Content[] getParts(MimeMultipart mp) throws MessagingException, IOException { + Content[] parts = new Content[mp.getCount()]; + for( int i=0; i<parts.length; i++ ) { + parts[i] = getPart(mp.getBodyPart(i)); + } + return parts; + } + + private static boolean isAllAscii(String s) { + for (int i=0;i<s.length();i++) + if (nonascii((int)s.charAt(i))) return false; + return true; + } + + // copied from javamail + private static boolean nonascii(int b) { + return b >= 0177 || (b < 040 && b != '\r' && b != '\n' && b != '\t'); + } + + public void setContent(Content content) throws MailException { + try { + setPart(msg,content); + } catch(MessagingException e) { + throw MailImpl.e(e); + } + } + + private final static String defaultCharset = MimeUtility.quote( + MimeUtility.mimeCharset(MimeUtility.getDefaultJavaCharset()), + HeaderTokenizer.MIME); + + private static void setPart(Part part,Content content) throws MessagingException { + if( content instanceof PlainTextContent ) { + TextContent textContent = (TextContent)content; + // Using part.setContent breaks the encoding of non-ascii email + // because com.sun.mail.handlers.text_plain assumes us-ascii charset + // if charset is not specified. Part.setText handles this correctly. + part.setText( textContent.getText() ); + return; + } + if( content instanceof TextAttachmentContent ) { + TextAttachmentContent textContent = (TextAttachmentContent)content; + // Use same logic as javamail MimeBodyPart.setText() to determine charset + String charset = isAllAscii(textContent.getText())?"us-ascii":defaultCharset; + part.setContent( textContent.getText(), content.getType()+'/'+content.getSubtype()+"; charset="+charset); + part.setFileName( textContent.getFilename() ); + return; + } + if( content instanceof FileAttachmentContent ) { + FileAttachmentContent fileContent = (FileAttachmentContent)content; + byte[] contents = new byte[0]; + InputStream is = fileContent.getInputStream(); + try { + contents = IoUtils.readAll(is); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + try { + is.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + part.setContent( contents, content.getType()+'/'+content.getSubtype()); + part.setFileName( fileContent.getFileName() ); + return; + } + if( content instanceof TextContent ) { + TextContent textContent = (TextContent)content; + // Use same logic as javamail MimeBodyPart.setText() to determine charset + String charset = isAllAscii(textContent.getText())?"us-ascii":defaultCharset; + part.setContent( textContent.getText(), content.getType()+'/'+content.getSubtype()+"; charset="+charset); + return; + } + if( content instanceof MultipartContent ) { + part.setContent( makeMimeMultipart((MultipartContent)content) ); + return; + } + if( content instanceof MailImpl ) { + MailImpl mail = (MailImpl)content; + part.setContent( mail.msg, "message/rfc822" ); + part.setDisposition(Part.ATTACHMENT); + return; + } + throw new UnsupportedOperationException("content class "+content.getClass()); + } + + private static MimeMultipart makeMimeMultipart(MultipartContent mc) throws MessagingException { + MimeMultipart mp = new MimeMultipart(mc.getSubtype()); + Content[] parts = mc.getParts(); + for( int i=0; i<parts.length; i++ ) { + MimeBodyPart mbp = new MimeBodyPart(); + setPart(mbp,parts[i]); + mp.addBodyPart(mbp); + } + return mp; + } + + public String getSubject() throws MailException { + try { + return msg.getSubject(); + } catch(MessagingException e) { + throw MailImpl.e(e); + } + } + + public void setSubject(String subject) throws MailException { + try { + msg.setSubject(subject); + } catch(MessagingException e) { + throw MailImpl.e(e); + } + } + + static InternetAddress addr(MailAddress addr) + throws MessagingException, UnsupportedEncodingException + { + return addr.getDisplayName() == null + ? new InternetAddress(addr.getAddrSpec()) + : new InternetAddress(addr.getAddrSpec(),addr.getDisplayName()) + ; + } + + static InternetAddress[] addr(MailAddress[] addrs) + throws MessagingException, UnsupportedEncodingException + { + InternetAddress[] a = new InternetAddress[addrs.length]; + for( int i=0; i<addrs.length; i++ ) { + a[i] = addr(addrs[i]); + } + return a; + } + + static MailAddress addr(Address a) { + if( a==null ) + return null; + InternetAddress addr = (InternetAddress)a; + return addr.getPersonal() == null + ? new MailAddress(addr.getAddress()) + : new MailAddress(addr.getAddress(),addr.getPersonal()) + ; + } + + private static MailAddress[] addr(Address[] a) { + if( a==null ) + return new MailAddress[0]; + MailAddress[] ma = new MailAddress[a.length]; + for( int i=0; i<a.length; i++ ) { + ma[i] = addr(a[i]); + } + return ma; + } + + public MailAddress getFrom() throws MailException { + try { + Address[] addrs = msg.getFrom(); + if( addrs==null || addrs.length==0 ) + throw new MailAddressException("number of from addresses is 0"); + //if( addrs.length > 1 ) + // log.warn("number of from addresses is "+addrs.length); + return addr(addrs[0]); + } catch(MessagingException e) { + throw MailImpl.e(e); + } + } + + public void setFrom(MailAddress address) throws MailException { + try { + msg.setFrom(addr(address)); + } catch(MessagingException e) { + throw MailImpl.e(e); + } catch(UnsupportedEncodingException e) { + throw new MailException(e); + } + } + + public MailAddress getSender() throws MailException { + try { + return addr(msg.getSender()); + } catch(MessagingException e) { + throw MailImpl.e(e); + } + } + + public void setSender(MailAddress address) throws MailException { + try { + msg.setSender(addr(address)); + } catch(MessagingException e) { + throw MailImpl.e(e); + } catch(UnsupportedEncodingException e) { + throw new MailException(e); + } + } + + public MailAddress[] getReplyTo() throws MailException { + try { + String replyTo = msg.getHeader("Reply-To", ","); + return (replyTo == null) ? null : addr(InternetAddress.parse(replyTo)); + } catch(MessagingException e) { + throw MailImpl.e(e); + } + } + + public void setReplyTo(MailAddress... addresses) throws MailException { + try { + msg.setReplyTo(addr(addresses)); + } catch(MessagingException e) { + throw MailImpl.e(e); + } catch(UnsupportedEncodingException e) { + throw new MailException(e); + } + } + + public MailAddress[] getTo() throws MailException { + try { + return addr(msg.getRecipients(Message.RecipientType.TO)); + } catch(MessagingException e) { + throw MailImpl.e(e); + } + } + + public MailAddress[] getCc() throws MailException { + try { + return addr(msg.getRecipients(Message.RecipientType.CC)); + } catch(MessagingException e) { + throw MailImpl.e(e); + } + } + + public MailAddress[] getBcc() throws MailException { + try { + return addr(msg.getRecipients(Message.RecipientType.BCC)); + } catch(MessagingException e) { + throw MailImpl.e(e); + } + } + + public void setTo(MailAddress... addresses) throws MailException { + try { + msg.setRecipients(Message.RecipientType.TO,addr(addresses)); + } catch(MessagingException e) { + throw MailImpl.e(e); + } catch(UnsupportedEncodingException e) { + throw new MailException(e); + } + } + + public void setCc(MailAddress... addresses) throws MailException { + try { + msg.setRecipients(Message.RecipientType.CC,addr(addresses)); + } catch(MessagingException e) { + throw MailImpl.e(e); + } catch(UnsupportedEncodingException e) { + throw new MailException(e); + } + } + + public void setBcc(MailAddress... addresses) throws MailException { + try { + msg.setRecipients(Message.RecipientType.BCC,addr(addresses)); + } catch(MessagingException e) { + throw MailImpl.e(e); + } catch(UnsupportedEncodingException e) { + throw new MailException(e); + } + } + + public String[] getHeader(String name) throws MailException { + try { + return msg.getHeader(name); + } catch(MessagingException e) { + throw MailImpl.e(e); + } + } + + public void setHeader(String name,String... values) throws MailException { + try { + if( values==null ) { + msg.removeHeader(name); + } else { + msg.setHeader(name,values[0]); + for( int i=1; i<values.length; i++ ) { + msg.addHeader(name,values[i]); + } + } + } catch(MessagingException e) { + throw MailImpl.e(e); + } + } + + public void setHeader(String name, MailAddress address) throws MailException { + try { + msg.setHeader(name, addr(address).toString()); + } catch(MessagingException e) { + throw MailImpl.e(e); + } catch(UnsupportedEncodingException e) { + throw new MailException(e); + } + } + + public String getMessageID() throws MailException { + try { + if( messageID != null ) + return messageID; + return msg.getMessageID(); + } catch(MessagingException e) { + throw MailImpl.e(e); + } + } + + public void setMessageID(String messageID) throws MailException { + this.messageID = messageID; + } + + public Date getSentDate() throws MailException { + try { + return msg.getSentDate(); + } catch(MessagingException e) { + throw MailImpl.e(e); + } + } + + public void setSentDate(Date sentDate) throws MailException { + try { + msg.setSentDate(sentDate); + } catch(MessagingException e) { + throw MailImpl.e(e); + } + } + + public String getRawInput() throws MailException { + try { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + msg.writeTo(out); + return out.toString("ISO-8859-1"); + } catch(MessagingException e) { + throw MailImpl.e(e); + } catch(IOException e) { + throw new MailException(e); + } + } + + public String toString() { + try { + StringBuilder buf = new StringBuilder(); + for( Enumeration en=msg.getAllHeaderLines(); en.hasMoreElements(); ) { + String header = (String)en.nextElement(); + buf.append(header).append("\n"); + } + buf.append("\n"); + buf.append(getContent()); + return buf.toString(); + } catch(MessagingException e) { + throw new RuntimeException(e); + } + } + + + // exception handling + + static MailException e(MessagingException e) { + return e(e.getMessage(),e); + } + + static MailException e(String msg,AddressException e) { + return new MailAddressException(msg,e); + } + + static MailException e(String msg,MessagingException e) { + if( e instanceof AddressException ) + return e(msg,(AddressException)e); + else if(e instanceof ParseException ) + return e(msg,(ParseException)e); + if (msg!=null && msg.startsWith("Missing start boundary")) + return new MailParseException(msg,e); + Exception e2 = e.getNextException(); + return e2==null ? new MailException(msg,e) : e(msg,e2); + } + + static MailException e(String msg,Exception e) { + if( e instanceof MessagingException ) + return e(msg,(MessagingException)e); + return new MailException(msg,e); + } + + static MailException e(String msg,ParseException e) { + return new MailParseException(msg,e); + } + + static MailException e(IOException e) { + return e(e.getMessage(),e); + } + + static MailException e(String msg,IOException e) { + if ( e instanceof UnsupportedEncodingException ) + return e(msg,(UnsupportedEncodingException)e); + if (msg!=null && msg.startsWith("Unknown encoding")) + return new MailEncodingException(msg,e); + return new MailException(msg,e); + } + + static MailException e(String msg,UnsupportedEncodingException e) { + return new MailEncodingException(msg,e); + } + + static { + MailcapCommandMap mcm = (MailcapCommandMap)CommandMap.getDefaultCommandMap(); + mcm.addMailcap("text/x-aol; ; x-java-content-handler=com.sun.mail.handlers.text_plain"); + } +}