view src/nabble/model/MailMessageFormat.java @ 0:7ecd1a4ef557

add content
author Franklin Schmidt <fschmidt@gmail.com>
date Thu, 21 Mar 2019 19:15:52 -0600
parents
children
line wrap: on
line source

package nabble.model;

import fschmidt.html.Html;
import fschmidt.html.HtmlTag;
import fschmidt.util.java.HtmlUtils;
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.MailException;
import fschmidt.util.mail.MailHome;
import fschmidt.util.mail.MailParseException;
import fschmidt.util.mail.MultipartContent;
import fschmidt.util.mail.TextContent;
import nabble.view.lib.Permissions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.ListIterator;
import java.util.regex.Matcher;


/**
 * User: dv
 * Date: Dec 29, 2007
 * Time: 4:27:45 PM
 */
public final class MailMessageFormat extends Message.Format {
	private static final Logger logger = LoggerFactory.getLogger(MailMessageFormat.class);

	MailMessageFormat(char code, String name) {
		super(code, name);
	}

	protected final Html parse(String msg,Message.Source source) {
		try {
			return doParse(msg,source);
		} catch(MailParseException e) {
			logger.error(e.toString());
			return new Html();
		}
	}

	protected final Html parseForMailText(String msg,Message.Source source) {
		throw new UnsupportedOperationException();
	}

	private static Html getTextContentAsHtml(Content content) {
		String s = getTextContent(content);
		return s==null ? null : textToHtml(s);
	}

	private static Html getHtmlContentAsHtml(Content content,Message.Source source) {
		String s = getHtmlContent(content);
		if( s == null )
			return null;
		Html html = new Html();
		html.removeBadTags(true);
		html.parse(s);
		if( source != null ) {
			markManualQuotes(html);
		}
		return html;
	}

	private static Html getMailAsHtml(Mail mail,Message.Source source) {
		Content content = mail.getContent();
		Html list;
		if (preferTextContent(mail)) {
			list = getTextContentAsHtml(content);
			if( list == null )
				list = getHtmlContentAsHtml(content, source);
		} else {
			list = getHtmlContentAsHtml(content, source);
			if( list == null )
				list = getTextContentAsHtml(content);
		}
		return list;
	}

	private static Html doParse(String msg, Message.Source source) {
		Mail mail = MailHome.newMail(msg);
		Html list = getMailAsHtml(mail, source);
		if( list == null )
			list = new Html();
		List<FileAttachmentContent> attachments = getAttachments(mail);
		setImgAttachmentSrc(list, attachments, source);
		tagEmails(list,(Node)source);
		list.addAll( new Html(listAttachmentsHtml(attachments, source)) );
		return list;
	}

	boolean isOk(String message) {
		try {
			doParse(message,null);  // make sure we can
			return true;
		} catch(MailParseException e) {
			logger.warn("",e);
			return false;
		}
	}

	protected String getText(String msg, Message.Source source) {
		try {
			// basic text message
			String text = getMailText(msg,source);

			// append attachments
			Mail mail = MailHome.newMail(msg);
			List<FileAttachmentContent> attachments = getAttachments(mail);
			String attachmentText = listAttachmentsText(attachments, source);
			if (attachmentText.length() > 0) {
				text = text + "\n\n" + attachmentText;
			}
			return text;
		} catch(MailParseException e) {
			logger.error(e.toString());
			return "";
		}
	}

	String getMailText(String msg, Message.Source source) {
		Mail mail = MailHome.newMail(msg);
		Content content = mail.getContent();
		String s = getTextContent(content);
		if (s == null) {
			s = getHtmlContent(content);
			if (s == null)
				s = "";
			s = htmlToText(s);
		}
		return s;
	}

	protected final String getTextWithoutQuotes(String msg, Message.Source source) {
		return getText(msg,source);
	}

	protected final String getEmail(String msg, int i) {
		Mail mail = MailHome.newMail(msg);
		Html list = getMailAsHtml(mail, null);
		tagEmails(list);
		return MessageFormatImpls.getEmail(list, i);
	}

	/**
	 * Process html and replace source url in img tags with a new Nabble-specific attachment url
	 *
	 * @param list        html to process
	 * @param attachments list of attachments
	 * @param source      source
	 */
	private static void setImgAttachmentSrc(Html list, List<FileAttachmentContent> attachments, Message.Source source) {
		if (source instanceof NodeImpl) {
			FileAttachmentContent[] fa = attachments.toArray(new FileAttachmentContent[attachments.size()]);
			for (ListIterator i = list.listIterator(); i.hasNext();) {
				Object o = i.next();
				if (o instanceof HtmlTag) {
					HtmlTag tag = (HtmlTag) o;
					if (tag.getName().toLowerCase().equals("img")) {
						String src = HtmlTag.unquote(tag.getAttributeValue("src"));
						if (src == null || !src.startsWith("cid:")) continue;
						src = src.substring(4);
						int n = 0;
						FileAttachmentContent attachment = null;
						for (; n < fa.length; n++) {
							String contentId = fa[n].getContentID();
							if (contentId!=null && src.equals(MailSubsystem.stripBrackets(contentId))) {
								attachment = fa[n];
								attachments.set(n, null);
								break;
							}
						}
						if (attachment != null) {
							String fileName = getFileName(attachment, n);
							StringBuilder buf = new StringBuilder();
							buf.append("\"");
							buf.append(source.getSite().getBaseUrl());
							buf.append("/attachment/");
							buf.append(source.getSourceId());
							buf.append("/");
							buf.append(n);
							buf.append("/");
							buf.append(HtmlUtils.urlEncode(fileName).replaceAll("\\+", "%20"));
							buf.append("\"");
							tag.setAttribute("src", buf.toString());
						} else {
							i.remove();
						}
					}
				}
			}
		}
	}

	/**
	 * Create string representation to an attachment list
	 *
	 * @param attachments list of attachments
	 * @param source      source
	 * @return s with attachment list
	 */
	private static String listAttachmentsHtml(List<FileAttachmentContent> attachments, Message.Source source) {
		if (attachments.size() == 0 || !(source instanceof NodeImpl)) return "";
		StringBuilder buf = new StringBuilder();
		for (int i = 0; i < attachments.size(); i++) {
			if (attachments.get(i) == null) continue;
			String fileName = getFileName(attachments.get(i), i);
			String fileSize = getFileSize(attachments.get(i));
			buf.append("<br/><img src=\"");
			buf.append(source.getSite().getBaseUrl());
			buf.append("/images/icon_attachment.gif\" > <strong>");
			buf.append(fileName);
			buf.append("</strong>");
			if (fileSize != null) {
				buf.append(" (");
				buf.append(fileSize);
				buf.append(")");
			}
			buf.append(" <a href=\"");
			buf.append(source.getSite().getBaseUrl());
			buf.append("/attachment/");
			buf.append(source.getSourceId());
			buf.append("/");
			buf.append(i);
			buf.append("/");
			buf.append(HtmlUtils.urlEncode(fileName).replaceAll("\\+", "%20"));
			buf.append("\" target=\"_top\">");
			buf.append("Download Attachment");
			buf.append("</a>");
		}
		if (buf.length() == 0) return "";
		buf.insert(0, "<!--start-attachments--><div class=\"small\">");
		buf.append("</div><!--end-attachments-->");
		return buf.toString();
	}

	/**
	 * Create string representation to an attachment list
	 *
	 * @param attachments list of attachments
	 * @param source      source
	 * @return s with attachment list
	 */
	private static String listAttachmentsText(List<FileAttachmentContent> attachments, Message.Source source) {
		if (attachments.size() == 0 || !(source instanceof NodeImpl)) return "";
		StringBuilder buf = new StringBuilder();
		for (int i = 0; i < attachments.size(); i++) {
			if (attachments.get(i) == null) continue;
			String fileName = getFileName(attachments.get(i), i);
			String fileSize = getFileSize(attachments.get(i));
			buf.append(fileName);
			if (fileSize != null) {
				buf.append(" (");
				buf.append(fileSize);
				buf.append(")");
			}
			buf.append(" <");
			buf.append(source.getSite().getBaseUrl());
			buf.append("/attachment/");
			buf.append(source.getSourceId());
			buf.append("/");
			buf.append(i);
			buf.append("/");
			buf.append(HtmlUtils.urlEncode(fileName).replaceAll("\\+", "%20"));
			buf.append(">\n");
		}
		if (buf.length() == 0) return "";
		return buf.toString();
	}

	/**
	 * Get a name of a file in the attachment
	 *
	 * @param attachment attachment with file
	 * @param n          suffix for name
	 * @return the name of the file in the attachment
	 */
	private static String getFileName(FileAttachmentContent attachment, int n) {
		try {
			if (attachment.getFileName() != null)
				return attachment.getFileName();
		} catch (MailException e) {
		}
		try {
			if (attachment.getContentID() != null)
				return MailSubsystem.stripBrackets(attachment.getContentID());
		} catch (MailException e) {
		}
		return "attachment" + n;
	}

	/**
	 * Get a size of a file in the attachment
	 *
	 * @param attachment attachment with file
	 * @return symbolic file size presentation or null
	 */
	private static String getFileSize(FileAttachmentContent attachment) {
		int size = attachment.getSize();
		if( size == -1 )
			return null;
		if (size > 1024 * 1024)
			return size / (1024 * 1024) + "M";
		else if (size > 1024)
			return size / 1024 + "K";
		else
			return size + " bytes";
	}

	/**
	 * Get attachment as a stream
	 *
	 * @param post attachment source
	 * @param i    index of the attachment
	 * @return a stream with attachment's content
	 */
	public InputStream getAttachment(Node post, int i) {
		String msg = post.getMessage().getRaw();
		Mail mail = MailHome.newMail(msg);
		List<FileAttachmentContent> attachments = getAttachments(mail);
		FileAttachmentContent attachment = attachments.get(i);
		return attachment.getInputStream();
	}

	/**
	 * Extract html content from content object
	 *
	 * @param content     content to analyze
	 * @return string representation of content
	 */
	private static String getHtmlContent(Content content) {
		if (content instanceof HtmlTextContent) {
			HtmlTextContent html = (HtmlTextContent) content;
			return html.getText();
		} else if (content instanceof AlternativeMultipartContent) {
			AlternativeMultipartContent amp = (AlternativeMultipartContent) content;
			Content[] amparts = amp.getParts();
			for (Content ampart : amparts) {
				String ams = getHtmlContent(ampart);
				if (ams != null) return ams;
			}
			return null;
		} else if (content instanceof MultipartContent) {
			MultipartContent mmp = (MultipartContent) content;
			Content[] mmparts = mmp.getParts();
			StringBuilder buf = new StringBuilder();
			boolean hasContent = false;
			for (Content mmpart : mmparts) {
				String mms = getHtmlContent(mmpart);
				if (mms == null) {
					mms = getTextContent(mmpart);
					if (mms != null) mms = textToHtml(mms).toString();
				}
				if (mms != null) {
					hasContent = true;
					if (buf.length() > 0)
						buf.append("<br />");
					buf.append(mms);
				}
			}
			return hasContent ? buf.toString() : null;
		} else if (content instanceof Mail) {
			return getHtmlContent(((Mail) content).getContent());
		} else {
			return null;
		}

	}

	/**
	 * Does a source prefer text content
	 * @return true if text content is prefered
	 */
	private static boolean preferTextContent(Mail mail) {
		String returnPath = MailSubsystem.getReturnPath(mail);
		return returnPath!=null && returnPath.endsWith(".groups.yahoo.com");
	}

	/**
	 * Extract text content from content object
	 *
	 * @param content     content to analyze
	 * @return string representation of content
	 */
	private static String getTextContent(Content content) {
		if (content instanceof AlternativeMultipartContent) {
			AlternativeMultipartContent amp = (AlternativeMultipartContent) content;
			Content[] amparts = amp.getParts();
			for (Content ampart : amparts) {
				String ams = getTextContent(ampart);
				if (ams != null) return ams;
			}
			return null;
		} else if (content instanceof MultipartContent) {
			MultipartContent mmp = (MultipartContent) content;
			Content[] mmparts = mmp.getParts();
			StringBuilder buf = new StringBuilder();
			boolean hasContent = false;
			for (Content mmpart : mmparts) {
				String mms = getTextContent(mmpart);
				if (mms == null) {
					mms = getHtmlContent(mmpart);
					if (mms != null) mms = htmlToText(mms);
				}
				if (mms != null) {
					hasContent = true;
					if (buf.length() > 0)
						buf.append('\n');
					buf.append(mms);
				}
			}
			return hasContent ? buf.toString() : null;
		} else if (content instanceof TextContent && !(content instanceof HtmlTextContent)) {
			TextContent textContent = (TextContent) content;
			return textContent.getText();
		} else if (content instanceof Mail) {
			return getTextContent(((Mail) content).getContent());
		} else {
			return null;
		}
	}

	private static List<FileAttachmentContent> getAttachments(Content content) {
		if (content instanceof FileAttachmentContent) {
			return Collections.singletonList( (FileAttachmentContent)content );
		}
		if (content instanceof MultipartContent) {
			List<FileAttachmentContent> attachments = new ArrayList<FileAttachmentContent>();
			MultipartContent mmp = (MultipartContent) content;
			for (Content mmpart : mmp.getParts()) {
				attachments.addAll( getAttachments(mmpart) );
			}
			return attachments;
		}
		if (content instanceof Mail) {
			return getAttachments( ((Mail)content).getContent() );
		}
		return Collections.emptyList();
	}


	private static Html textToHtml(String text) {
		Html list = MessageFormatImpls.convertLinks(text);
		for( ListIterator<Object> iter = list.listIterator(); iter.hasNext(); ) {
			Object obj = iter.next();
			if( obj instanceof String ) {
				String s = (String)obj;
				s = HtmlUtils.htmlEncode(s);
				iter.set(s);
			}
		}
		MessageFormatImpls.textToHtml(list);
		markManualQuotes(list);
		return list;
	}


	private static final HtmlTag emailTag = new HtmlTag("email");
	private static final HtmlTag _emailTag = new HtmlTag("/email");

	/**
	 * Use <email>me@host.com</email> to replace
	 * email. MessageFormatImpls.processEmail should be called to touch up the final result
	 */
	public static void tagEmails(Html list,Node node) {
		if( node!=null && !Permissions.isPrivate(node) )
			tagEmails(list);
	}

	private static void tagEmails(Html list) {
		boolean insideA = false;
		boolean insideEmail = false;
		for( ListIterator<Object> i=list.listIterator(); i.hasNext(); ) {
			Object o = i.next();
			if( o instanceof HtmlTag ) {
				HtmlTag tag = (HtmlTag)o;
				String tagName = tag.getName().toLowerCase();
				if( insideEmail ) {
					if( tagName.equals("/email") )
						insideEmail = false;
					continue;
				}
				if( tagName.equals("email") ) {
					insideEmail = true;
				} else if( tagName.equals("a") ) {
					String href = tag.getAttributeValue("href");
					if (href != null) {
						if (href.startsWith("\"mailto:")) {
							i.remove();
							boolean foundCloseTag = false;
							while (i.hasNext() && !foundCloseTag) {
								Object o1 = i.next();
								if ( o1 instanceof HtmlTag ) {
									HtmlTag tag1 = (HtmlTag)o1;
									if (tag1.getName().toLowerCase().equals("/a")) {
										foundCloseTag = true;
									}
								}
								i.remove();
							}
							i.add(emailTag);
							i.add(href.substring(8, href.length() - 1));
							i.add(_emailTag);
						} else {
							insideA = true;
						}
					}
				} else if (tagName.equals("/a")) {
					insideA = false;
				}
			} else if (o instanceof String && !insideEmail) {
				String s = (String)o;
				Matcher m = MailAddress.EMAIL_PATTERN.matcher(s);
				if( m.find() ) {
					StringBuffer buf = new StringBuffer();
					do {
						m.appendReplacement(buf, insideA?"$1@...":"<email>$1@$2</email>");
					} while (m.find());
					m.appendTail(buf);
					Html hlist = new Html(buf.toString());
					i.remove();
					for(ListIterator j=hlist.listIterator();j.hasNext();) {
						i.add(j.next());
					}
				}
			}
		}
	}



	private static final HtmlTag divHiddenQuote = new HtmlTag("div class='shrinkable-quote'");
	private static final HtmlTag _div = new HtmlTag("/div");

	private static void markManualQuotes(Html list) {
		int n = list.size() - 1;
		if( n==-1 ) return;
		int nLines = 0;
		int iStart = -1;
		Object o = list.get(0);
		if( o instanceof String ) {
			String s = (String)o;
			if( s.startsWith("&gt;") ) {
				nLines++;
			}
		}
		for( int i=0; i<n; i++ ) {
			o = list.get(i);
			if( !(o instanceof HtmlTag) )
				continue;
			HtmlTag tag = (HtmlTag)o;
			if( !tag.getName().toLowerCase().equals("br") )
				continue;
			o = list.get(i+1);
			if( o instanceof String ) {
				String s = (String)o;
				if( s.startsWith("&gt;") ) {
					if( nLines == 0 )
						iStart = i;
					nLines++;
					continue;
				}
			}
			if( nLines >= Init.quotedLinesToHide ) {
				if( iStart == -1 ) {
					list.add(0,divHiddenQuote);
				} else {
					list.add(iStart,divHiddenQuote);
				}
				i++;
				n++;
				list.set(i,_div);
			}
			nLines = 0;
		}
		if( nLines >= Init.quotedLinesToHide ) {
			if( iStart == -1 )
				iStart = 0;
			list.add(iStart,divHiddenQuote);
			list.add(_div);
		}
	}

	private static String htmlToText(String msg) {
		return MessageUtils.htmlToText(new Html(msg));
	}

}