comparison 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
comparison
equal deleted inserted replaced
-1:000000000000 0:7ecd1a4ef557
1 package nabble.model;
2
3 import fschmidt.html.Html;
4 import fschmidt.html.HtmlTag;
5 import fschmidt.util.java.HtmlUtils;
6 import fschmidt.util.mail.AlternativeMultipartContent;
7 import fschmidt.util.mail.Content;
8 import fschmidt.util.mail.FileAttachmentContent;
9 import fschmidt.util.mail.HtmlTextContent;
10 import fschmidt.util.mail.Mail;
11 import fschmidt.util.mail.MailAddress;
12 import fschmidt.util.mail.MailException;
13 import fschmidt.util.mail.MailHome;
14 import fschmidt.util.mail.MailParseException;
15 import fschmidt.util.mail.MultipartContent;
16 import fschmidt.util.mail.TextContent;
17 import nabble.view.lib.Permissions;
18 import org.slf4j.Logger;
19 import org.slf4j.LoggerFactory;
20
21 import java.io.InputStream;
22 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.List;
25 import java.util.ListIterator;
26 import java.util.regex.Matcher;
27
28
29 /**
30 * User: dv
31 * Date: Dec 29, 2007
32 * Time: 4:27:45 PM
33 */
34 public final class MailMessageFormat extends Message.Format {
35 private static final Logger logger = LoggerFactory.getLogger(MailMessageFormat.class);
36
37 MailMessageFormat(char code, String name) {
38 super(code, name);
39 }
40
41 protected final Html parse(String msg,Message.Source source) {
42 try {
43 return doParse(msg,source);
44 } catch(MailParseException e) {
45 logger.error(e.toString());
46 return new Html();
47 }
48 }
49
50 protected final Html parseForMailText(String msg,Message.Source source) {
51 throw new UnsupportedOperationException();
52 }
53
54 private static Html getTextContentAsHtml(Content content) {
55 String s = getTextContent(content);
56 return s==null ? null : textToHtml(s);
57 }
58
59 private static Html getHtmlContentAsHtml(Content content,Message.Source source) {
60 String s = getHtmlContent(content);
61 if( s == null )
62 return null;
63 Html html = new Html();
64 html.removeBadTags(true);
65 html.parse(s);
66 if( source != null ) {
67 markManualQuotes(html);
68 }
69 return html;
70 }
71
72 private static Html getMailAsHtml(Mail mail,Message.Source source) {
73 Content content = mail.getContent();
74 Html list;
75 if (preferTextContent(mail)) {
76 list = getTextContentAsHtml(content);
77 if( list == null )
78 list = getHtmlContentAsHtml(content, source);
79 } else {
80 list = getHtmlContentAsHtml(content, source);
81 if( list == null )
82 list = getTextContentAsHtml(content);
83 }
84 return list;
85 }
86
87 private static Html doParse(String msg, Message.Source source) {
88 Mail mail = MailHome.newMail(msg);
89 Html list = getMailAsHtml(mail, source);
90 if( list == null )
91 list = new Html();
92 List<FileAttachmentContent> attachments = getAttachments(mail);
93 setImgAttachmentSrc(list, attachments, source);
94 tagEmails(list,(Node)source);
95 list.addAll( new Html(listAttachmentsHtml(attachments, source)) );
96 return list;
97 }
98
99 boolean isOk(String message) {
100 try {
101 doParse(message,null); // make sure we can
102 return true;
103 } catch(MailParseException e) {
104 logger.warn("",e);
105 return false;
106 }
107 }
108
109 protected String getText(String msg, Message.Source source) {
110 try {
111 // basic text message
112 String text = getMailText(msg,source);
113
114 // append attachments
115 Mail mail = MailHome.newMail(msg);
116 List<FileAttachmentContent> attachments = getAttachments(mail);
117 String attachmentText = listAttachmentsText(attachments, source);
118 if (attachmentText.length() > 0) {
119 text = text + "\n\n" + attachmentText;
120 }
121 return text;
122 } catch(MailParseException e) {
123 logger.error(e.toString());
124 return "";
125 }
126 }
127
128 String getMailText(String msg, Message.Source source) {
129 Mail mail = MailHome.newMail(msg);
130 Content content = mail.getContent();
131 String s = getTextContent(content);
132 if (s == null) {
133 s = getHtmlContent(content);
134 if (s == null)
135 s = "";
136 s = htmlToText(s);
137 }
138 return s;
139 }
140
141 protected final String getTextWithoutQuotes(String msg, Message.Source source) {
142 return getText(msg,source);
143 }
144
145 protected final String getEmail(String msg, int i) {
146 Mail mail = MailHome.newMail(msg);
147 Html list = getMailAsHtml(mail, null);
148 tagEmails(list);
149 return MessageFormatImpls.getEmail(list, i);
150 }
151
152 /**
153 * Process html and replace source url in img tags with a new Nabble-specific attachment url
154 *
155 * @param list html to process
156 * @param attachments list of attachments
157 * @param source source
158 */
159 private static void setImgAttachmentSrc(Html list, List<FileAttachmentContent> attachments, Message.Source source) {
160 if (source instanceof NodeImpl) {
161 FileAttachmentContent[] fa = attachments.toArray(new FileAttachmentContent[attachments.size()]);
162 for (ListIterator i = list.listIterator(); i.hasNext();) {
163 Object o = i.next();
164 if (o instanceof HtmlTag) {
165 HtmlTag tag = (HtmlTag) o;
166 if (tag.getName().toLowerCase().equals("img")) {
167 String src = HtmlTag.unquote(tag.getAttributeValue("src"));
168 if (src == null || !src.startsWith("cid:")) continue;
169 src = src.substring(4);
170 int n = 0;
171 FileAttachmentContent attachment = null;
172 for (; n < fa.length; n++) {
173 String contentId = fa[n].getContentID();
174 if (contentId!=null && src.equals(MailSubsystem.stripBrackets(contentId))) {
175 attachment = fa[n];
176 attachments.set(n, null);
177 break;
178 }
179 }
180 if (attachment != null) {
181 String fileName = getFileName(attachment, n);
182 StringBuilder buf = new StringBuilder();
183 buf.append("\"");
184 buf.append(source.getSite().getBaseUrl());
185 buf.append("/attachment/");
186 buf.append(source.getSourceId());
187 buf.append("/");
188 buf.append(n);
189 buf.append("/");
190 buf.append(HtmlUtils.urlEncode(fileName).replaceAll("\\+", "%20"));
191 buf.append("\"");
192 tag.setAttribute("src", buf.toString());
193 } else {
194 i.remove();
195 }
196 }
197 }
198 }
199 }
200 }
201
202 /**
203 * Create string representation to an attachment list
204 *
205 * @param attachments list of attachments
206 * @param source source
207 * @return s with attachment list
208 */
209 private static String listAttachmentsHtml(List<FileAttachmentContent> attachments, Message.Source source) {
210 if (attachments.size() == 0 || !(source instanceof NodeImpl)) return "";
211 StringBuilder buf = new StringBuilder();
212 for (int i = 0; i < attachments.size(); i++) {
213 if (attachments.get(i) == null) continue;
214 String fileName = getFileName(attachments.get(i), i);
215 String fileSize = getFileSize(attachments.get(i));
216 buf.append("<br/><img src=\"");
217 buf.append(source.getSite().getBaseUrl());
218 buf.append("/images/icon_attachment.gif\" > <strong>");
219 buf.append(fileName);
220 buf.append("</strong>");
221 if (fileSize != null) {
222 buf.append(" (");
223 buf.append(fileSize);
224 buf.append(")");
225 }
226 buf.append(" <a href=\"");
227 buf.append(source.getSite().getBaseUrl());
228 buf.append("/attachment/");
229 buf.append(source.getSourceId());
230 buf.append("/");
231 buf.append(i);
232 buf.append("/");
233 buf.append(HtmlUtils.urlEncode(fileName).replaceAll("\\+", "%20"));
234 buf.append("\" target=\"_top\">");
235 buf.append("Download Attachment");
236 buf.append("</a>");
237 }
238 if (buf.length() == 0) return "";
239 buf.insert(0, "<!--start-attachments--><div class=\"small\">");
240 buf.append("</div><!--end-attachments-->");
241 return buf.toString();
242 }
243
244 /**
245 * Create string representation to an attachment list
246 *
247 * @param attachments list of attachments
248 * @param source source
249 * @return s with attachment list
250 */
251 private static String listAttachmentsText(List<FileAttachmentContent> attachments, Message.Source source) {
252 if (attachments.size() == 0 || !(source instanceof NodeImpl)) return "";
253 StringBuilder buf = new StringBuilder();
254 for (int i = 0; i < attachments.size(); i++) {
255 if (attachments.get(i) == null) continue;
256 String fileName = getFileName(attachments.get(i), i);
257 String fileSize = getFileSize(attachments.get(i));
258 buf.append(fileName);
259 if (fileSize != null) {
260 buf.append(" (");
261 buf.append(fileSize);
262 buf.append(")");
263 }
264 buf.append(" <");
265 buf.append(source.getSite().getBaseUrl());
266 buf.append("/attachment/");
267 buf.append(source.getSourceId());
268 buf.append("/");
269 buf.append(i);
270 buf.append("/");
271 buf.append(HtmlUtils.urlEncode(fileName).replaceAll("\\+", "%20"));
272 buf.append(">\n");
273 }
274 if (buf.length() == 0) return "";
275 return buf.toString();
276 }
277
278 /**
279 * Get a name of a file in the attachment
280 *
281 * @param attachment attachment with file
282 * @param n suffix for name
283 * @return the name of the file in the attachment
284 */
285 private static String getFileName(FileAttachmentContent attachment, int n) {
286 try {
287 if (attachment.getFileName() != null)
288 return attachment.getFileName();
289 } catch (MailException e) {
290 }
291 try {
292 if (attachment.getContentID() != null)
293 return MailSubsystem.stripBrackets(attachment.getContentID());
294 } catch (MailException e) {
295 }
296 return "attachment" + n;
297 }
298
299 /**
300 * Get a size of a file in the attachment
301 *
302 * @param attachment attachment with file
303 * @return symbolic file size presentation or null
304 */
305 private static String getFileSize(FileAttachmentContent attachment) {
306 int size = attachment.getSize();
307 if( size == -1 )
308 return null;
309 if (size > 1024 * 1024)
310 return size / (1024 * 1024) + "M";
311 else if (size > 1024)
312 return size / 1024 + "K";
313 else
314 return size + " bytes";
315 }
316
317 /**
318 * Get attachment as a stream
319 *
320 * @param post attachment source
321 * @param i index of the attachment
322 * @return a stream with attachment's content
323 */
324 public InputStream getAttachment(Node post, int i) {
325 String msg = post.getMessage().getRaw();
326 Mail mail = MailHome.newMail(msg);
327 List<FileAttachmentContent> attachments = getAttachments(mail);
328 FileAttachmentContent attachment = attachments.get(i);
329 return attachment.getInputStream();
330 }
331
332 /**
333 * Extract html content from content object
334 *
335 * @param content content to analyze
336 * @return string representation of content
337 */
338 private static String getHtmlContent(Content content) {
339 if (content instanceof HtmlTextContent) {
340 HtmlTextContent html = (HtmlTextContent) content;
341 return html.getText();
342 } else if (content instanceof AlternativeMultipartContent) {
343 AlternativeMultipartContent amp = (AlternativeMultipartContent) content;
344 Content[] amparts = amp.getParts();
345 for (Content ampart : amparts) {
346 String ams = getHtmlContent(ampart);
347 if (ams != null) return ams;
348 }
349 return null;
350 } else if (content instanceof MultipartContent) {
351 MultipartContent mmp = (MultipartContent) content;
352 Content[] mmparts = mmp.getParts();
353 StringBuilder buf = new StringBuilder();
354 boolean hasContent = false;
355 for (Content mmpart : mmparts) {
356 String mms = getHtmlContent(mmpart);
357 if (mms == null) {
358 mms = getTextContent(mmpart);
359 if (mms != null) mms = textToHtml(mms).toString();
360 }
361 if (mms != null) {
362 hasContent = true;
363 if (buf.length() > 0)
364 buf.append("<br />");
365 buf.append(mms);
366 }
367 }
368 return hasContent ? buf.toString() : null;
369 } else if (content instanceof Mail) {
370 return getHtmlContent(((Mail) content).getContent());
371 } else {
372 return null;
373 }
374
375 }
376
377 /**
378 * Does a source prefer text content
379 * @return true if text content is prefered
380 */
381 private static boolean preferTextContent(Mail mail) {
382 String returnPath = MailSubsystem.getReturnPath(mail);
383 return returnPath!=null && returnPath.endsWith(".groups.yahoo.com");
384 }
385
386 /**
387 * Extract text content from content object
388 *
389 * @param content content to analyze
390 * @return string representation of content
391 */
392 private static String getTextContent(Content content) {
393 if (content instanceof AlternativeMultipartContent) {
394 AlternativeMultipartContent amp = (AlternativeMultipartContent) content;
395 Content[] amparts = amp.getParts();
396 for (Content ampart : amparts) {
397 String ams = getTextContent(ampart);
398 if (ams != null) return ams;
399 }
400 return null;
401 } else if (content instanceof MultipartContent) {
402 MultipartContent mmp = (MultipartContent) content;
403 Content[] mmparts = mmp.getParts();
404 StringBuilder buf = new StringBuilder();
405 boolean hasContent = false;
406 for (Content mmpart : mmparts) {
407 String mms = getTextContent(mmpart);
408 if (mms == null) {
409 mms = getHtmlContent(mmpart);
410 if (mms != null) mms = htmlToText(mms);
411 }
412 if (mms != null) {
413 hasContent = true;
414 if (buf.length() > 0)
415 buf.append('\n');
416 buf.append(mms);
417 }
418 }
419 return hasContent ? buf.toString() : null;
420 } else if (content instanceof TextContent && !(content instanceof HtmlTextContent)) {
421 TextContent textContent = (TextContent) content;
422 return textContent.getText();
423 } else if (content instanceof Mail) {
424 return getTextContent(((Mail) content).getContent());
425 } else {
426 return null;
427 }
428 }
429
430 private static List<FileAttachmentContent> getAttachments(Content content) {
431 if (content instanceof FileAttachmentContent) {
432 return Collections.singletonList( (FileAttachmentContent)content );
433 }
434 if (content instanceof MultipartContent) {
435 List<FileAttachmentContent> attachments = new ArrayList<FileAttachmentContent>();
436 MultipartContent mmp = (MultipartContent) content;
437 for (Content mmpart : mmp.getParts()) {
438 attachments.addAll( getAttachments(mmpart) );
439 }
440 return attachments;
441 }
442 if (content instanceof Mail) {
443 return getAttachments( ((Mail)content).getContent() );
444 }
445 return Collections.emptyList();
446 }
447
448
449 private static Html textToHtml(String text) {
450 Html list = MessageFormatImpls.convertLinks(text);
451 for( ListIterator<Object> iter = list.listIterator(); iter.hasNext(); ) {
452 Object obj = iter.next();
453 if( obj instanceof String ) {
454 String s = (String)obj;
455 s = HtmlUtils.htmlEncode(s);
456 iter.set(s);
457 }
458 }
459 MessageFormatImpls.textToHtml(list);
460 markManualQuotes(list);
461 return list;
462 }
463
464
465 private static final HtmlTag emailTag = new HtmlTag("email");
466 private static final HtmlTag _emailTag = new HtmlTag("/email");
467
468 /**
469 * Use <email>me@host.com</email> to replace
470 * email. MessageFormatImpls.processEmail should be called to touch up the final result
471 */
472 public static void tagEmails(Html list,Node node) {
473 if( node!=null && !Permissions.isPrivate(node) )
474 tagEmails(list);
475 }
476
477 private static void tagEmails(Html list) {
478 boolean insideA = false;
479 boolean insideEmail = false;
480 for( ListIterator<Object> i=list.listIterator(); i.hasNext(); ) {
481 Object o = i.next();
482 if( o instanceof HtmlTag ) {
483 HtmlTag tag = (HtmlTag)o;
484 String tagName = tag.getName().toLowerCase();
485 if( insideEmail ) {
486 if( tagName.equals("/email") )
487 insideEmail = false;
488 continue;
489 }
490 if( tagName.equals("email") ) {
491 insideEmail = true;
492 } else if( tagName.equals("a") ) {
493 String href = tag.getAttributeValue("href");
494 if (href != null) {
495 if (href.startsWith("\"mailto:")) {
496 i.remove();
497 boolean foundCloseTag = false;
498 while (i.hasNext() && !foundCloseTag) {
499 Object o1 = i.next();
500 if ( o1 instanceof HtmlTag ) {
501 HtmlTag tag1 = (HtmlTag)o1;
502 if (tag1.getName().toLowerCase().equals("/a")) {
503 foundCloseTag = true;
504 }
505 }
506 i.remove();
507 }
508 i.add(emailTag);
509 i.add(href.substring(8, href.length() - 1));
510 i.add(_emailTag);
511 } else {
512 insideA = true;
513 }
514 }
515 } else if (tagName.equals("/a")) {
516 insideA = false;
517 }
518 } else if (o instanceof String && !insideEmail) {
519 String s = (String)o;
520 Matcher m = MailAddress.EMAIL_PATTERN.matcher(s);
521 if( m.find() ) {
522 StringBuffer buf = new StringBuffer();
523 do {
524 m.appendReplacement(buf, insideA?"$1@...":"<email>$1@$2</email>");
525 } while (m.find());
526 m.appendTail(buf);
527 Html hlist = new Html(buf.toString());
528 i.remove();
529 for(ListIterator j=hlist.listIterator();j.hasNext();) {
530 i.add(j.next());
531 }
532 }
533 }
534 }
535 }
536
537
538
539 private static final HtmlTag divHiddenQuote = new HtmlTag("div class='shrinkable-quote'");
540 private static final HtmlTag _div = new HtmlTag("/div");
541
542 private static void markManualQuotes(Html list) {
543 int n = list.size() - 1;
544 if( n==-1 ) return;
545 int nLines = 0;
546 int iStart = -1;
547 Object o = list.get(0);
548 if( o instanceof String ) {
549 String s = (String)o;
550 if( s.startsWith("&gt;") ) {
551 nLines++;
552 }
553 }
554 for( int i=0; i<n; i++ ) {
555 o = list.get(i);
556 if( !(o instanceof HtmlTag) )
557 continue;
558 HtmlTag tag = (HtmlTag)o;
559 if( !tag.getName().toLowerCase().equals("br") )
560 continue;
561 o = list.get(i+1);
562 if( o instanceof String ) {
563 String s = (String)o;
564 if( s.startsWith("&gt;") ) {
565 if( nLines == 0 )
566 iStart = i;
567 nLines++;
568 continue;
569 }
570 }
571 if( nLines >= Init.quotedLinesToHide ) {
572 if( iStart == -1 ) {
573 list.add(0,divHiddenQuote);
574 } else {
575 list.add(iStart,divHiddenQuote);
576 }
577 i++;
578 n++;
579 list.set(i,_div);
580 }
581 nLines = 0;
582 }
583 if( nLines >= Init.quotedLinesToHide ) {
584 if( iStart == -1 )
585 iStart = 0;
586 list.add(iStart,divHiddenQuote);
587 list.add(_div);
588 }
589 }
590
591 private static String htmlToText(String msg) {
592 return MessageUtils.htmlToText(new Html(msg));
593 }
594
595 }