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");
+	}
+}