| 1 31 package org.blojsom.plugin.moblog; 32 33 import org.apache.commons.logging.Log; 34 import org.apache.commons.logging.LogFactory; 35 import org.blojsom.blog.Blog; 36 import org.blojsom.blog.Entry; 37 import org.blojsom.event.EventBroadcaster; 38 import org.blojsom.fetcher.Fetcher; 39 import org.blojsom.fetcher.FetcherException; 40 import org.blojsom.plugin.PluginException; 41 import org.blojsom.plugin.admin.event.EntryAddedEvent; 42 import org.blojsom.plugin.email.EmailConstants; 43 import org.blojsom.plugin.email.SimpleAuthenticator; 44 import org.blojsom.plugin.velocity.StandaloneVelocityPlugin; 45 import org.blojsom.util.BlojsomConstants; 46 import org.blojsom.util.BlojsomMetaDataConstants; 47 import org.blojsom.util.BlojsomUtils; 48 49 import javax.mail.*; 50 import javax.mail.internet.InternetAddress ; 51 import javax.mail.internet.MimeMultipart ; 52 import javax.naming.Context ; 53 import javax.naming.InitialContext ; 54 import javax.naming.NamingException ; 55 import javax.servlet.http.HttpServletRequest ; 56 import javax.servlet.http.HttpServletResponse ; 57 import java.io.*; 58 import java.net.ConnectException ; 59 import java.util.*; 60 import java.util.regex.Matcher ; 61 import java.util.regex.Pattern ; 62 63 71 public class MoblogPlugin extends StandaloneVelocityPlugin { 72 73 private Log _logger = LogFactory.getLog(MoblogPlugin.class); 74 75 private static final String MOBLOG_ENTRY_TEMPLATE = "org/blojsom/plugin/moblog/moblog-plugin-template.vm"; 76 77 private static final String MOBLOG_SUBJECT = "MOBLOG_SUBJECT"; 78 private static final String MOBLOG_BODY_TEXT = "MOBLOG_BODY_TEXT"; 79 private static final String MOBLOG_IMAGES = "MOBLOG_IMAGES"; 80 private static final String MOBLOG_ATTACHMENTS = "MOBLOG_ATTACHMENTS"; 81 private static final String MOBLOG_ATTACHMENT = "MOBLOG_ATTACHMENT"; 82 private static final String MOBLOG_ATTACHMENT_URL = "MOBLOG_ATTACHMENT_URL"; 83 private static final String MOBLOG_IMAGE = "MOBLOG_IMAGE"; 84 private static final String MOBLOG_IMAGE_URL = "MOBLOG_IMAGE_URL"; 85 86 89 private static final String MULTIPART_ALTERNATIVE_MIME_TYPE = "multipart/alternative"; 90 91 94 private static final String TEXT_HTML_MIME_TYPE = "text/html"; 95 96 99 public static final String DEFAULT_TEXT_MIME_TYPES = "text/plain, text/html"; 100 101 104 public static final String DEFAULT_IMAGE_MIME_TYPES = "image/jpg, image/jpeg, image/gif, image/png"; 105 106 109 private static final String MULTIPART_TYPE = "multipart/*"; 110 111 114 private static final String DEFAULT_MESSAGE_STORE = "pop3"; 115 116 119 private static final int DEFAULT_POLL_TIME = 720; 120 121 124 public static final String PLUGIN_MOBLOG_CONFIGURATION_IP = "plugin-moblog"; 125 126 129 public static final String PLUGIN_MOBLOG_POLL_TIME = "plugin-moblog-poll-time"; 130 131 134 public static final String PLUGIN_MOBLOG_STORE_PROVIDER = "plugin-moblog-store-provider"; 135 136 139 public static final String DEFAULT_MOBLOG_AUTHORIZATION_FILE = "moblog-authorization.properties"; 140 141 144 public static final String PROPERTY_AUTHORIZATION = "moblog-authorization"; 145 146 149 public static final String PROPERTY_HOSTNAME = "moblog-hostname"; 150 151 154 public static final String PROPERTY_USERID = "moblog-userid"; 155 156 159 public static final String PROPERTY_PASSWORD = "moblog-password"; 160 161 164 public static final String PROPERTY_CATEGORY = "moblog-category"; 165 166 169 public static final String PROPERTY_ENABLED = "moblog-enabled"; 170 171 174 public static final String PLUGIN_MOBLOG_SECRET_WORD = "moblog-secret-word"; 175 176 179 public static final String PLUGIN_MOBLOG_IMAGE_MIME_TYPES = "moblog-image-mime-types"; 180 181 184 public static final String PLUGIN_MOBLOG_ATTACHMENT_MIME_TYPES = "moblog-attachment-mime-types"; 185 186 189 public static final String PLUGIN_MOBLOG_TEXT_MIME_TYPES = "moblog-text-mime-types"; 190 191 194 public static final String PLUGIN_MOBLOG_IGNORE_EXPRESSION = "moblog-ignore-expression"; 195 196 public static final String PLUGIN_MOBLOG_AUTHORIZED_ADDRESSES = "moblog-authorized-addresses"; 197 198 private int _pollTime; 199 200 private Session _storeSession; 201 private boolean _finished = false; 202 private MailboxChecker _checker; 203 private String _storeProvider; 204 205 private EventBroadcaster _eventBroadcaster; 206 private Fetcher _fetcher; 207 208 213 public void setFetcher(Fetcher fetcher) { 214 _fetcher = fetcher; 215 } 216 217 222 public void setEventBroadcaster(EventBroadcaster eventBroadcaster) { 223 _eventBroadcaster = eventBroadcaster; 224 } 225 226 232 public void init() throws PluginException { 233 super.init(); 234 235 String moblogPollTime = _servletConfig.getInitParameter(PLUGIN_MOBLOG_POLL_TIME); 236 if (BlojsomUtils.checkNullOrBlank(moblogPollTime)) { 237 _pollTime = DEFAULT_POLL_TIME; 238 } else { 239 try { 240 _pollTime = Integer.parseInt(moblogPollTime); 241 } catch (NumberFormatException e) { 242 if (_logger.isErrorEnabled()) { 243 if (_logger.isErrorEnabled()) { 244 _logger.error("Invalid time specified for: " + PLUGIN_MOBLOG_POLL_TIME); 245 } 246 } 247 _pollTime = DEFAULT_POLL_TIME; 248 } 249 } 250 251 _storeProvider = _servletConfig.getInitParameter(PLUGIN_MOBLOG_STORE_PROVIDER); 252 if (BlojsomUtils.checkNullOrBlank(_storeProvider)) { 253 _storeProvider = DEFAULT_MESSAGE_STORE; 254 } 255 256 _checker = new MailboxChecker(); 257 _checker.setDaemon(true); 258 259 String hostname = _servletConfig.getInitParameter(EmailConstants.SMTPSERVER_IP); 260 if (hostname != null) { 261 if (hostname.startsWith("java:comp/env")) { 262 try { 263 Context context = new InitialContext (); 264 _storeSession = (Session) context.lookup(hostname); 265 } catch (NamingException e) { 266 if (_logger.isErrorEnabled()) { 267 _logger.error(e); 268 } 269 270 throw new PluginException(e); 271 } 272 } else { 273 String username = _servletConfig.getInitParameter(EmailConstants.SMTPSERVER_USERNAME_IP); 274 String password = _servletConfig.getInitParameter(EmailConstants.SMTPSERVER_PASSWORD_IP); 275 276 Properties props = new Properties(); 277 props.put(EmailConstants.SESSION_NAME, hostname); 278 if (BlojsomUtils.checkNullOrBlank(username) || BlojsomUtils.checkNullOrBlank(password)) { 279 _storeSession = Session.getInstance(props, null); 280 } else { 281 _storeSession = Session.getInstance(props, new SimpleAuthenticator(username, password)); 282 } 283 } 284 } 285 286 _checker.start(); 287 } 288 289 300 public Entry[] process(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Blog blog, Map context, Entry[] entries) throws PluginException { 301 return entries; 302 } 303 304 309 public void cleanup() throws PluginException { 310 } 311 312 317 public void destroy() throws PluginException { 318 _finished = true; 319 } 320 321 324 private class MailboxChecker extends Thread { 325 326 336 public MailboxChecker() { 337 super(); 338 } 339 340 345 private void processMailbox(Mailbox mailbox) { 346 Folder folder = null; 347 Store store = null; 348 String subject; 349 350 try { 351 store = _storeSession.getStore(_storeProvider); 352 store.connect(mailbox.getHostName(), mailbox.getUserId(), mailbox.getPassword()); 353 354 folder = store.getDefaultFolder(); 356 if (folder == null) { 357 if (_logger.isErrorEnabled()) { 358 _logger.error("Default folder is null."); 359 } 360 _finished = true; 361 } 362 363 folder = folder.getFolder(mailbox.getFolder()); 365 if (folder == null) { 366 if (_logger.isErrorEnabled()) { 367 _logger.error("No POP3 folder called " + mailbox.getFolder()); 368 } 369 _finished = true; 370 } 371 372 folder.open(Folder.READ_WRITE); 374 375 Message[] msgs = folder.getMessages(); 377 378 if (_logger.isDebugEnabled()) { 379 _logger.debug("Found [" + msgs.length + "] messages"); 380 } 381 382 for (int msgNum = 0; msgNum < msgs.length; msgNum++) { 383 String from = ((InternetAddress ) msgs[msgNum].getFrom()[0]).getAddress(); 384 if (_logger.isDebugEnabled()) { 385 _logger.debug("Processing message: " + msgNum); 386 } 387 388 if (!checkSender(mailbox, from)) { 389 if (_logger.isDebugEnabled()) { 390 _logger.debug("Unauthorized sender address: " + from); 391 _logger.debug("Deleting message: " + msgNum); 392 } 393 394 msgs[msgNum].setFlag(Flags.Flag.DELETED, true); 395 } else { 396 Message email = msgs[msgNum]; 397 subject = email.getSubject(); 398 399 StringBuffer description = new StringBuffer (); 400 Part messagePart; 401 messagePart = email; 402 Pattern pattern = null; 403 List moblogImages = new ArrayList(); 404 List moblogAttachments = new ArrayList(); 405 Map moblogContext = new HashMap(); 406 407 if (mailbox.getIgnoreExpression() != null) { 408 pattern = Pattern.compile(mailbox.getIgnoreExpression(), Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.UNICODE_CASE | Pattern.DOTALL); 409 } 410 411 if (subject == null) { 412 subject = ""; 413 } else { 414 subject = subject.trim(); 415 } 416 417 String secretWord = mailbox.getSecretWord(); 418 if (secretWord != null) { 419 if (!subject.startsWith(secretWord)) { 420 if (_logger.isErrorEnabled()) { 421 _logger.error("Message does not begin with secret word for user id: " + mailbox.getUserId()); 422 } 423 msgs[msgNum].setFlag(Flags.Flag.DELETED, true); 424 425 continue; 426 } else { 427 subject = subject.substring(secretWord.length()); 428 } 429 } 430 431 if (email.isMimeType(MULTIPART_TYPE)) { 432 String overallType = email.getContentType(); 434 overallType = sanitizeContentType(overallType); 435 436 boolean isMultipartAlternative = false; 437 if (MULTIPART_ALTERNATIVE_MIME_TYPE.equals(overallType)) { 438 isMultipartAlternative = true; 439 } 440 441 Multipart mp = (Multipart ) messagePart.getContent(); 442 int count = mp.getCount(); 443 444 for (int i = 0; i < count; i++) { 445 BodyPart bp = mp.getBodyPart(i); 446 String type = bp.getContentType(); 447 if (type != null) { 448 type = sanitizeContentType(type); 449 Map imageMimeTypes = mailbox.getImageMimeTypes(); 450 Map attachmentMimeTypes = mailbox.getAttachmentMimeTypes(); 451 Map textMimeTypes = mailbox.getTextMimeTypes(); 452 453 if (MULTIPART_ALTERNATIVE_MIME_TYPE.equals(type)) { 455 Object mimeMultipartContent = bp.getContent(); 456 if (mimeMultipartContent instanceof MimeMultipart ) { 457 MimeMultipart mimeMultipart = (MimeMultipart ) mimeMultipartContent; 458 int mimeMultipartCount = mimeMultipart.getCount(); 459 for (int j = 0; j < mimeMultipartCount; j++) { 460 BodyPart mimeMultipartBodyPart = mimeMultipart.getBodyPart(j); 461 String mmpbpType = mimeMultipartBodyPart.getContentType(); 462 if (mmpbpType != null) { 463 mmpbpType = sanitizeContentType(mmpbpType); 464 if (TEXT_HTML_MIME_TYPE.equals(mmpbpType)) { 465 if (_logger.isDebugEnabled()) { 466 _logger.debug("Using HTML part of multipart/alternative: " + type); 467 } 468 InputStream is = bp.getInputStream(); 469 470 BufferedReader reader = new BufferedReader(new InputStreamReader(is, BlojsomConstants.UTF8)); 471 String thisLine; 472 473 while ((thisLine = reader.readLine()) != null) { 474 description.append(thisLine); 475 description.append(BlojsomConstants.LINE_SEPARATOR); 476 } 477 478 reader.close(); 479 if (pattern != null) { 480 Matcher matcher = pattern.matcher(description); 481 if (!matcher.find() && !matcher.matches()) { 482 moblogContext.put(MOBLOG_BODY_TEXT, description.toString()); 484 } 485 } else { 486 moblogContext.put(MOBLOG_BODY_TEXT, description.toString()); 488 } 489 } else { 490 if (_logger.isDebugEnabled()) { 491 _logger.debug("Skipping non-HTML part of multipart/alternative block"); 492 } 493 } 494 } else { 495 if (_logger.isInfoEnabled()) { 496 _logger.info("Unknown mimetype for multipart/alternative block"); 497 } 498 } 499 } 500 } else { 501 if (_logger.isDebugEnabled()) { 502 _logger.debug("Multipart alternative block not instance of MimeMultipart"); 503 } 504 } 505 } else { 506 if (imageMimeTypes.containsKey(type)) { 507 if (_logger.isDebugEnabled()) { 508 _logger.debug("Creating image of type: " + type); 509 } 510 String outputFilename = BlojsomUtils.digestString(bp.getFileName() + "-" + new Date().getTime()); 511 String extension = BlojsomUtils.getFileExtension(bp.getFileName()); 512 if (BlojsomUtils.checkNullOrBlank(extension)) { 513 extension = ""; 514 } 515 516 if (_logger.isDebugEnabled()) { 517 _logger.debug("Writing to: " + mailbox.getOutputDirectory() + File.separator + outputFilename + "." + extension); 518 } 519 MoblogPluginUtils.saveFile(mailbox.getOutputDirectory() + File.separator + outputFilename, "." + extension, bp.getInputStream()); 520 521 String baseurl = mailbox.getBlogBaseURL(); 522 Map moblogImageInformation = new HashMap(); 523 moblogImageInformation.put(MOBLOG_IMAGE, outputFilename + "." + extension); 524 moblogImageInformation.put(MOBLOG_IMAGE_URL, baseurl + mailbox.getUrlPrefix() + outputFilename + "." + extension); 525 moblogImages.add(moblogImageInformation); 526 } else if (attachmentMimeTypes.containsKey(type)) { 527 if (_logger.isDebugEnabled()) { 528 _logger.debug("Creating attachment of type: " + type); 529 } 530 String outputFilename = BlojsomUtils.digestString(bp.getFileName() + "-" + new Date().getTime()); 531 String extension = BlojsomUtils.getFileExtension(bp.getFileName()); 532 if (BlojsomUtils.checkNullOrBlank(extension)) { 533 extension = ""; 534 } 535 536 if (_logger.isDebugEnabled()) { 537 _logger.debug("Writing to: " + mailbox.getOutputDirectory() + File.separator + outputFilename + "." + extension); 538 } 539 MoblogPluginUtils.saveFile(mailbox.getOutputDirectory() + File.separator + outputFilename, "." + extension, bp.getInputStream()); 540 541 String baseurl = mailbox.getBlogBaseURL(); 542 Map moblogAttachmentInformation = new HashMap(); 543 moblogAttachmentInformation.put(MOBLOG_ATTACHMENT, bp.getFileName()); 544 moblogAttachmentInformation.put(MOBLOG_ATTACHMENT_URL, baseurl + mailbox.getUrlPrefix() + outputFilename + "." + extension); 545 moblogAttachments.add(moblogAttachmentInformation); 546 } else if (textMimeTypes.containsKey(type)) { 547 if ((isMultipartAlternative && (TEXT_HTML_MIME_TYPE.equals(type))) || !isMultipartAlternative) 548 { 549 if (_logger.isDebugEnabled()) { 550 _logger.debug("Using text part of type: " + type); 551 } 552 InputStream is = bp.getInputStream(); 553 554 BufferedReader reader = new BufferedReader(new InputStreamReader(is, BlojsomConstants.UTF8)); 555 String thisLine; 556 557 while ((thisLine = reader.readLine()) != null) { 558 description.append(thisLine); 559 description.append(BlojsomConstants.LINE_SEPARATOR); 560 } 561 562 reader.close(); 563 if (pattern != null) { 564 Matcher matcher = pattern.matcher(description); 565 if (!matcher.find() && !matcher.matches()) { 566 moblogContext.put(MOBLOG_BODY_TEXT, description.toString()); 567 } 568 } else { 569 moblogContext.put(MOBLOG_BODY_TEXT, description.toString()); 570 } 571 } 572 } else { 573 if (_logger.isInfoEnabled()) { 574 _logger.info("Unknown mimetype for multipart: " + type); 575 } 576 } 577 } 578 } else { 579 if (_logger.isDebugEnabled()) { 580 _logger.debug("Body part has no defined mime type. Skipping."); 581 } 582 } 583 } 584 } else { 585 Map textMimeTypes = mailbox.getTextMimeTypes(); 587 String mimeType = email.getContentType(); 588 if (mimeType != null) { 589 mimeType = sanitizeContentType(mimeType); 590 } 591 592 if ((mimeType != null) && (textMimeTypes.containsKey(mimeType))) { 593 InputStream is = email.getInputStream(); 594 595 BufferedReader reader = new BufferedReader(new InputStreamReader(is, BlojsomConstants.UTF8)); 596 String thisLine; 597 598 while ((thisLine = reader.readLine()) != null) { 599 description.append(thisLine); 600 description.append(BlojsomConstants.LINE_SEPARATOR); 601 } 602 603 reader.close(); 604 if (pattern != null) { 605 Matcher matcher = pattern.matcher(description); 606 if (!matcher.find() && !matcher.matches()) { 607 moblogContext.put(MOBLOG_BODY_TEXT, description.toString()); 608 } 609 } else { 610 moblogContext.put(MOBLOG_BODY_TEXT, description.toString()); 611 } 612 } else { 613 if (_logger.isInfoEnabled()) { 614 _logger.info("Unknown mimetype: " + mimeType); 615 } 616 } 617 } 618 619 boolean categoryInSubject = false; 621 String categoryFromSubject = null; 622 if (subject.startsWith("[")) { 623 int startIndex = subject.indexOf("["); 624 if (startIndex != -1) { 625 int closingIndex = subject.indexOf("]", startIndex); 626 if (closingIndex != -1) { 627 categoryFromSubject = subject.substring(startIndex + 1, closingIndex); 628 subject = subject.substring(closingIndex + 1); 629 categoryFromSubject = BlojsomUtils.normalize(categoryFromSubject); 630 if (!categoryFromSubject.startsWith("/")) { 631 categoryFromSubject = "/" + categoryFromSubject; 632 } 633 if (!categoryFromSubject.endsWith("/")) { 634 categoryFromSubject += "/"; 635 } 636 categoryInSubject = true; 637 if (_logger.isInfoEnabled()) { 638 _logger.info("Using category [" + categoryFromSubject + "] for entry: " + subject); 639 } 640 } 641 } 642 } 643 644 String categoryID = categoryInSubject ? categoryFromSubject : mailbox.getCategoryId(); 645 646 moblogContext.put(MOBLOG_SUBJECT, subject); 647 moblogContext.put(MOBLOG_IMAGES, moblogImages); 648 moblogContext.put(MOBLOG_ATTACHMENTS, moblogAttachments); 649 650 try { 651 Blog blog = _fetcher.loadBlog(mailbox.getBlogId()); 652 String moblogText = mergeTemplate(MOBLOG_ENTRY_TEMPLATE, blog, moblogContext); 653 654 Entry entry; 655 entry = _fetcher.newEntry(); 656 657 entry.setBlogCategoryId(Integer.valueOf(categoryID)); 658 entry.setBlogId(mailbox.getId()); 659 entry.setDate(new Date()); 660 entry.setDescription(moblogText); 661 entry.setTitle(subject); 662 entry.setStatus(BlojsomMetaDataConstants.PUBLISHED_STATUS); 663 664 Map entryMetaData = new HashMap(); 665 entryMetaData.put(BlojsomMetaDataConstants.BLOG_ENTRY_METADATA_AUTHOR_EXT, from); 666 entry.setMetaData(entryMetaData); 667 668 _fetcher.saveEntry(blog, entry); 669 670 msgs[msgNum].setFlag(Flags.Flag.DELETED, true); 671 672 _eventBroadcaster.broadcastEvent(new EntryAddedEvent(this, new Date(), entry, blog)); 673 } catch (FetcherException e) { 674 if (_logger.isErrorEnabled()) { 675 _logger.error(e); 676 } 677 } 678 } 679 } 680 681 try { 683 if (folder != null) { 684 folder.close(true); 685 } 686 687 if (store != null) { 688 store.close(); 689 } 690 } catch (MessagingException e) { 691 if (_logger.isErrorEnabled()) { 692 _logger.error(e); 693 } 694 } 695 } catch (ConnectException e) { 696 if (_logger.isErrorEnabled()) { 697 _logger.error(e); 698 } 699 } catch (NoSuchProviderException e) { 700 if (_logger.isErrorEnabled()) { 701 _logger.error(e); 702 } 703 } catch (MessagingException e) { 704 if (_logger.isErrorEnabled()) { 705 _logger.error(e); 706 } 707 } catch (IOException e) { 708 if (_logger.isErrorEnabled()) { 709 _logger.error(e); 710 } 711 } finally { 712 try { 713 if (folder != null && folder.isOpen()) { 714 folder.close(true); 715 } 716 717 if (store != null) { 718 store.close(); 719 } 720 } catch (MessagingException e) { 721 if (_logger.isErrorEnabled()) { 722 _logger.error(e); 723 } 724 } 725 } 726 } 727 728 731 public void run() { 732 try { 733 while (!_finished) { 734 if (_logger.isDebugEnabled()) { 735 _logger.debug("Moblog plugin waking up and looking for new messages"); 736 } 737 738 String [] blogIDs = _fetcher.loadBlogIDs(); 739 for (int i = 0; i < blogIDs.length; i++) { 740 String blogID = blogIDs[i]; 741 Blog blog = _fetcher.loadBlog(blogID); 742 743 Mailbox mailbox = MoblogPluginUtils.readMailboxSettingsForBlog( _servletConfig, blog); 744 if (mailbox != null) { 745 if (mailbox.isEnabled()) { 746 if (_logger.isDebugEnabled()) { 747 _logger.debug("Checking mailbox: " + mailbox.getUserId() + " for blog: " + mailbox.getBlogId()); 748 } 749 processMailbox(mailbox); 750 } 751 } 752 } 753 754 if (_logger.isDebugEnabled()) { 755 _logger.debug("Moblog plugin off to take a nap"); 756 } 757 sleep(_pollTime * 1000); 758 } 759 } catch (InterruptedException e) { 760 if (_logger.isErrorEnabled()) { 761 _logger.error(e); 762 } 763 } catch (FetcherException e) { 764 if (_logger.isErrorEnabled()) { 765 _logger.error(e); 766 } 767 } 768 } 769 770 778 private boolean checkSender(Mailbox mailbox, String fromAddress) { 779 boolean result = false; 780 Map authorizedAddresses = mailbox.getAuthorizedAddresses(); 781 782 if (authorizedAddresses.containsKey(fromAddress)) { 783 result = true; 784 } 785 786 return result; 787 } 788 } 789 790 796 protected String sanitizeContentType(String contentType) { 797 int semicolonIndex = contentType.indexOf(";"); 798 if (semicolonIndex != -1) { 799 contentType = contentType.substring(0, semicolonIndex); 800 } 801 802 return contentType.toLowerCase(); 803 } 804 } 805 | Popular Tags |