0
|
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 }
|