Mercurial Hosting > nabble
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(">") ) { | |
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(">") ) { | |
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 } |