/* * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.xwiki.contrib.mailarchive.utils.internal; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import javax.mail.internet.AddressException; import javax.mail.internet.InternetAddress; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.xwiki.component.annotation.Component; import org.xwiki.contrib.mail.MailItem; import org.xwiki.contrib.mailarchive.IMAUser; import org.xwiki.contrib.mailarchive.IMailMatcher; import org.xwiki.contrib.mailarchive.IType; import org.xwiki.contrib.mailarchive.internal.data.MAUser; import org.xwiki.contrib.mailarchive.utils.DecodedMailContent; import org.xwiki.contrib.mailarchive.utils.IMailUtils; import org.xwiki.contrib.mailarchive.utils.ITextUtils; import org.xwiki.contrib.mailarchive.xwiki.IExtendedDocumentAccessBridge; import org.xwiki.query.Query; import org.xwiki.query.QueryException; import org.xwiki.query.QueryManager; /** * @version $Id$ */ @Component @Singleton public class MailUtils implements IMailUtils { @Inject private ITextUtils textUtils; @Inject private QueryManager queryManager; @Inject private Logger logger; @Inject @Named("extended") private IExtendedDocumentAccessBridge bridge; @Override public String extractAddress(final String id) { int start = id.indexOf('<'); int end = id.indexOf('>'); if (start != -1 && end != -1) { return id.substring(start + 1, end); } else { return id; } } @Override public IMAUser parseUser(final String user, final boolean isMatchLdap) { logger.debug("parseUser {}, {}", user, isMatchLdap); MAUser maUser = new MAUser(); maUser.setOriginalAddress(user); if (StringUtils.isBlank(user)) { return maUser; } String address = null; String personal = null; // Do our best to extract an address and a personal try { InternetAddress ia = null; InternetAddress[] result = InternetAddress.parse(user, true); if (result != null && result.length > 0) { ia = result[0]; if (!StringUtils.isBlank(ia.getAddress())) { address = ia.getAddress(); } if (!StringUtils.isBlank(ia.getPersonal())) { personal = ia.getPersonal(); } } } catch (AddressException e) { logger.info("Email Address does not follow standards : " + user); } if (StringUtils.isBlank(address)) { String[] substrs = StringUtils.substringsBetween(user, "<", ">"); if (substrs != null && substrs.length > 0) { address = substrs[0]; } else { // nothing matches, we suppose recipient only contains email address address = user; } } if (StringUtils.isBlank(personal)) { if (user.contains("<")) { personal = StringUtils.substringBeforeLast(user, "<"); if (StringUtils.isBlank(personal)) { personal = StringUtils.substringBefore(address, "@"); } } } maUser.setAddress(address); maUser.setDisplayName(personal); // Now to match a wiki profile logger.debug("parseUser extracted email {}", address); String parsedUser = null; if (!StringUtils.isBlank(address)) { // to match "-external" emails and old mails with '@gemplus.com'... String pattern = address.toLowerCase(); pattern = pattern.replace("-external", "").replaceAll("^(.*)@.*[.]com$", "$1%@%.com"); logger.debug("parseUser pattern applied {}", pattern); // Try to find a wiki profile with this email as parameter. // TBD : do this in the loading phase, and only try to search db if it was not found ? String xwql = "select doc.fullName from Document doc, doc.object(XWiki.XWikiUsers) as user where LOWER(user.email) like :pattern"; List<String> profiles = null; try { profiles = queryManager.createQuery(xwql, Query.XWQL).bindValue("pattern", pattern).execute(); } catch (QueryException e) { logger.warn("parseUser Query threw exception", e); profiles = null; } if (profiles == null || profiles.size() == 0) { logger.debug("parseUser found no wiki profile from db"); return maUser; } else { if (isMatchLdap) { logger.debug("parseUser Checking for LDAP authenticated profile(s) ..."); // If there exists one, we prefer the user that's been authenticated through LDAP for (String usr : profiles) { if (bridge.exists(usr, "XWiki.LDAPProfileClass")) { parsedUser = usr; logger.debug("parseUser Found LDAP authenticated profile {}", parsedUser); } } if (parsedUser != null) { maUser.setWikiProfile(parsedUser); logger.debug("parseUser return {}", maUser); return maUser; } } } // If none has authenticated from LDAP, we return the first user found maUser.setWikiProfile(profiles.get(0)); logger.debug("parseUser return {}", maUser); return maUser; } else { logger.debug("parseUser No email found to match"); return maUser; } } @Override public DecodedMailContent decodeMailContent(final String originalHtml, final String originalBody, final boolean cut) throws IOException { String html = ""; String body = ""; if (!StringUtils.isEmpty(originalHtml)) { html = textUtils.unzipString(originalHtml); } else { // body is only plain text if (!StringUtils.isBlank(originalBody)) { if (originalBody.startsWith("<html") || originalBody.startsWith("<meta") || originalBody.contains("<br>") || originalBody.contains("<br/>")) { html = originalBody; } } } if (!StringUtils.isBlank(html)) { Matcher m = Pattern.compile("<span [^>]*>From:<\\\\/span>", Pattern.MULTILINE).matcher(html); if (cut && m.find()) { html = html.substring(0, m.start() - 1); } else if (cut && html.contains("<b>From:</b>")) { html = html.substring(0, html.indexOf("<b>From:</b>") - 1); } return new DecodedMailContent(true, html); } else { body = originalBody; Matcher m = Pattern.compile("\\n[\\s]*From:", Pattern.MULTILINE).matcher(body); if (cut && m.find()) { body = body.substring(0, m.start() + 1); } return new DecodedMailContent(false, body); } } @Override public List<IType> extractTypes(final Collection<IType> types, final MailItem mailItem) { logger.debug("extractTypes(types={}, mailItem={})", types, mailItem); List<IType> result = new ArrayList<IType>(); if (types == null || mailItem == null) { throw new IllegalArgumentException("extractTypes: Types and mailitem can't be null"); } // set IType for (IType type : types) { logger.debug("Checking for type " + type); boolean matched = true; for (IMailMatcher mailMatcher : type.getMatchers()) { logger.debug(" Checking for matcher " + mailMatcher); List<String> fields = mailMatcher.getFields(); String regexp = mailMatcher.getExpression(); Pattern pattern = null; if (mailMatcher.isAdvancedMode()) { // Manage multi line and ignore case in regexp if needed if (mailMatcher.isIgnoreCase() || mailMatcher.isMultiLine()) { String prefix = "(?"; if (mailMatcher.isIgnoreCase()) { prefix += 'i'; } if (mailMatcher.isMultiLine()) { prefix += 'm'; } prefix += ')'; regexp = prefix + regexp; } try { pattern = Pattern.compile(regexp); } catch (PatternSyntaxException e) { logger.warn("Invalid Pattern " + regexp + "can't be compiled, skipping this mail type"); break; } } Matcher matcher = null; boolean fieldMatch = false; for (String field : fields) { String fieldValue = ""; if ("from".equals(field)) { fieldValue = mailItem.getFrom(); } else if ("to".equals(field)) { fieldValue = mailItem.getTo(); } else if ("cc".equals(field)) { fieldValue = mailItem.getCc(); } else if ("subject".equals(field)) { fieldValue = mailItem.getSubject(); } logger.debug(" Checking field " + field + " with value [" + fieldValue + "] against pattern [" + regexp + "] DEBUG [" + textUtils.byte2hex(regexp.getBytes()) + "]"); if (mailMatcher.isAdvancedMode()) { matcher = pattern.matcher(fieldValue); logger.debug("Matcher : " + matcher); if (matcher != null) { fieldMatch = matcher.find(); logger.debug("fieldMatch: " + fieldMatch); logger.debug("matcher.matches : " + matcher.matches()); } } else { if (mailMatcher.isIgnoreCase()) { fieldMatch = StringUtils.containsIgnoreCase(fieldValue, regexp); } else { fieldMatch = StringUtils.contains(fieldValue, regexp); } } if (fieldMatch) { logger.debug("Field " + field + " value [" + fieldValue + "] matches pattern [" + regexp + "]"); break; } } matched = matched && fieldMatch; } if (matched) { logger.info("Matched type " + type.getName()); result.add(type); matched = true; } } return result; } }