/** * MailArchiver is an application that provides services for storing and managing e-mail messages through a Web Services SOAP interface. * Copyright (C) 2012 Marcio Andre Scholl Levien and Fernando Alberto Reuter Wendt and Jose Ronaldo Nogueira Fonseca Junior * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ /******************************************************************************\ * * This product was developed by * * SERVIÇO FEDERAL DE PROCESSAMENTO DE DADOS (SERPRO), * * a government company established under Brazilian law (5.615/70), * at Department of Development of Porto Alegre. * \******************************************************************************/ package serpro.mailarchiver.domain.metaarchive; import java.io.StringReader; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.regex.Pattern; import javax.jdo.annotations.NotPersistent; import javax.jdo.annotations.PersistenceCapable; import org.apache.james.mime4j.dom.datetime.DateTime; import org.apache.james.mime4j.field.datetime.parser.DateTimeParser; import org.apache.james.mime4j.io.LineNumberInputStream; import com.google.common.collect.LinkedListMultimap; import serpro.mailarchiver.domain.BaseObject; import serpro.mailarchiver.domain.metaarchive.UnstructuredField.ContentEncodingField; import serpro.mailarchiver.domain.metaarchive.UnstructuredField.ContentLanguageField; import serpro.mailarchiver.domain.metaarchive.UnstructuredField.ContentLengthField; import serpro.mailarchiver.domain.metaarchive.UnstructuredField.ContentLocationField; import serpro.mailarchiver.domain.metaarchive.UnstructuredField.ContentTransferEncodingField; import serpro.mailarchiver.domain.metaarchive.UnstructuredField.MessageIdSequenceField; import serpro.mailarchiver.domain.metaarchive.UnstructuredField.MimeVersionField; import serpro.mailarchiver.util.BodyVisitor; import serpro.mailarchiver.util.Logger; @PersistenceCapable public abstract class Entity extends BaseObject { @NotPersistent private static final Logger log = Logger.getLocalLogger(); //**** P E R S I S T E N T **** private String oid; private int startLine; private int separatorLine; private int endLine; private int size; private Body body; private ArrayList fields = new ArrayList(); //***************************** @NotPersistent private final LineNumberInputStream.Entity lnisEntity; public Entity(LineNumberInputStream.Entity lnisEntity) { this.lnisEntity = lnisEntity; } public Entity() { lnisEntity = null; } public abstract Message getRootMessage(); public void sync() { startLine = lnisEntity.getStartLine(); separatorLine = lnisEntity.getSeparatorLine(); endLine = lnisEntity.getEndLine(); if(body instanceof SingleBody) { SingleBody singleBody = (SingleBody)body; singleBody.setOffset(lnisEntity.getBodyOffset()); singleBody.setLength(lnisEntity.getBodyLength()); } else if(body instanceof MessageBody) { MessageBody messageBody = (MessageBody)body; EmbeddedMessage embeddedMessage = messageBody.getEmbeddedMessage(); embeddedMessage.sync(); } else if(body instanceof Multipart) { Multipart multipart = (Multipart)body; for(BodyPart bodyPart : multipart.getBodyParts()) { bodyPart.sync(); } } } public final String getOid() { return oid; } public final void setOid(String oid) { this.oid = oid; } public final int getStartLine() { return startLine; } public final void setStartLine(int startLine) { this.startLine = startLine; } public final int getSeparatorLine() { return separatorLine; } public final void setSeparatorLine(int separatorLine) { this.separatorLine = separatorLine; } public final int getEndLine() { return endLine; } public final void setEndLine(int endLine) { this.endLine = endLine; } public final int getSize() { return size; } public final void setSize(int size) { this.size = size; } //-------------------------------------------------------------------------- public final Body getBody() { return body; } public final void setBody(Body body) { if(this.body != null) { this.body.internal_setEntity(null); } this.body = body; if(this.body != null) { this.body.internal_setEntity(this); } } final void internal_setBody(Body body) { this.body = body; } //-------------------------------------------------------------------------- public final List getFields() { return Collections.unmodifiableList(fields); } public final int indexOf(Field field) { return fields.indexOf(field); } public final void addField(Field field) { if(field != null) { field.setEntity(this); } } final void internal_addField(Field field) { fields.add(field); dirtyCache = true; } public final void removeField(Field field) { if((field != null) && (field.getEntity() == this)) { field.setEntity(null); } } final void internal_removeField(Field field) { fields.remove(field); dirtyCache = true; } //-------------------------------------------------------------------------- public final void incSize(int size) { setSize(getSize() + size); if(this instanceof BodyEntity) { ((BodyEntity)this).getComposite().getEntity().incSize(size); } } //-------------------------------------------------------------------------- public final void visitBodies(BodyVisitor visitor) { visitBodies(visitor, "multipart/*", "message/*", "text/html", "text/plain", "text/*"); } public final void visitBodies(BodyVisitor visitor, String... alternativeMimeTypesPriority) { List patterns = new ArrayList(); for(String s : alternativeMimeTypesPriority) { patterns.add(Pattern.compile("^" + s.trim().replaceAll("\\*", "(.+)") + "$", Pattern.CASE_INSENSITIVE)); } visitBodies(visitor, patterns.toArray(new Pattern[patterns.size()])); } public final void visitBodies(BodyVisitor visitor, Pattern... alternativeMimeTypesPriority) { if(visitor.isQuitted()) { return; } if(body instanceof SingleBody) { SingleBody singleBody = (SingleBody)body; visitor.visitSingleBody(singleBody); } else if(body instanceof MessageBody) { MessageBody messageBody = (MessageBody)body; Entity embeddedMessage = messageBody.getEmbeddedMessage(); if(embeddedMessage != null) { embeddedMessage.visitBodies(visitor, alternativeMimeTypesPriority); } } else if(body instanceof Multipart) { Multipart multipart = (Multipart)body; ContentTypeField contentTypeField = getContentTypeField(); if((contentTypeField != null) && (contentTypeField.isMultipartAlternativeMimeType())) { // multipart-alternative boolean found = false; priority: for(Pattern mimeType : alternativeMimeTypesPriority) { for(Entity bodyPart : multipart.getBodyParts()) { ContentTypeField bodyPartContentTypeField = bodyPart.getContentTypeField(); if((bodyPartContentTypeField != null) && (mimeType.matcher(bodyPartContentTypeField.getMimeType()).matches())) { found = true; bodyPart.visitBodies(visitor, alternativeMimeTypesPriority); break priority; } } } if(!found) { //choose the plainest (first) alternative. see RFC2046 Entity bodyPart = multipart.getBodyParts().get(0); bodyPart.visitBodies(visitor, alternativeMimeTypesPriority); } } else { for(Entity bodyPart : multipart.getBodyParts()) { bodyPart.visitBodies(visitor, alternativeMimeTypesPriority); if(visitor.isQuitted()) { break; } } } } } //-------------------------------------------------------------------------- public final String dumpPath() { StringBuilder sb = new StringBuilder(); dumpPath(sb, true); return sb.toString(); } abstract int dumpPath(StringBuilder sb, boolean quit); public final String dumpTree() { StringBuilder sb = new StringBuilder(); dumpTree(sb, ""); return sb.toString(); } final void dumpTree(StringBuilder sb, String pad) { int c = fields.size(); sb.append(toString(pad + (((c > 0) || (body != null)) ? "| " : " "))); for(int i = 0; i < c; i++) { sb.append("\n") .append(pad).append("|\n") .append(pad).append("+---"); if((i < (c - 1)) || (body != null)) { fields.get(i).dumpTree(sb, pad + "| "); } else { fields.get(i).dumpTree(sb, pad + " "); } } if(body != null) { sb.append("\n") .append(pad).append("|\n") .append(pad).append("+---"); body.dumpTree(sb, pad + " "); } } @Override public final String toString() { return toString(" "); } abstract String toString(String pad); // @NotPersistent private LinkedListMultimap readCache = LinkedListMultimap.create(); @NotPersistent private boolean dirtyCache = true; public synchronized final Field getField(String name) { if(dirtyCache) { updateCache(); } List fs = readCache.get(name); if(fs.isEmpty()) { return null; } return fs.get(0); } public synchronized final List getFields(String name) { if(dirtyCache) { updateCache(); } return Collections.unmodifiableList(readCache.get(name)); } private void updateCache() { readCache.clear(); for(Field f : getFields()) { readCache.put(f.getName().toLowerCase(), f); } dirtyCache = false; } //---- public final ContentTypeField getContentTypeField() { return (ContentTypeField) getField("content-type"); } public final ContentDispositionField getContentDispositionField() { return (ContentDispositionField) getField("content-disposition"); } //---- public final DateTimeField getDateField() { return (DateTimeField) getField("date"); } public final List getResentDateFields() { return (List) getFields("resent-date"); } //---- public final MailboxField getSenderField() { return (MailboxField) getField("sender"); } public final List getResentSenderFields() { return (List) getFields("resent-sender"); } //---- public final MailboxListField getFromField() { return (MailboxListField) getField("from"); } public final MailboxListField getDispositionNotificationToField() { return (MailboxListField) getField("disposition-notification-to"); } public final List getResentFromFields() { return (List) getFields("resent-from"); } //---- public final AddressListField getToField() { return (AddressListField) getField("to"); } public final AddressListField getCcField() { return (AddressListField) getField("cc"); } public final AddressListField getBccField() { return (AddressListField) getField("bcc"); } public final AddressListField getReplyToField() { return (AddressListField) getField("reply-to"); } public final List getResentToFields() { return (List) getFields("resent-to"); } public final List getResentCcFields() { return (List) getFields("resent-cc"); } public final List getResentBccFields() { return (List) getFields("resent-bcc"); } //---- public final UnstructuredField getSubjectField() { return (UnstructuredField) getField("subject"); } public final UnstructuredField getContentDescriptionField() { return (UnstructuredField) getField("content-description"); } public final UnstructuredField getMessageIdField() { return (UnstructuredField) getField("message-id"); } public final UnstructuredField getContentIdField() { return (UnstructuredField) getField("content-id"); } public final UnstructuredField getContentMD5Field() { return (UnstructuredField) getField("content-md5"); } public final List getResentMsgIdFields() { return (List) getFields("resent-msg-id"); } public final List getCommentsFields() { return (List) getFields("comments"); } public final List getKeywordsFields() { return (List) getFields("keywords"); } //--- public final MessageIdSequenceField getReferencesField() { UnstructuredField field = (UnstructuredField)getField("references"); if(field != null) { return field.getDecorator(); } return null; } public final MessageIdSequenceField getInReplyToField() { UnstructuredField field = (UnstructuredField)getField("in-reply-to"); if(field != null) { return field.getDecorator(); } return null; } public final MimeVersionField getMimeVersionField() { UnstructuredField field = (UnstructuredField)getField("mime-version"); if(field != null) { return field.getDecorator(); } return null; } public final ContentLengthField getContentLengthField() { UnstructuredField field = (UnstructuredField)getField("content-length"); if(field != null) { return field.getDecorator(); } return null; } public final ContentLanguageField getContentLanguageField() { UnstructuredField field = (UnstructuredField)getField("content-language"); if(field != null) { return field.getDecorator(); } return null; } public final ContentLocationField getContentLocationField() { UnstructuredField field = (UnstructuredField)getField("content-location"); if(field != null) { return field.getDecorator(); } return null; } public final ContentEncodingField getContentEncodingField() { UnstructuredField field = (UnstructuredField)getField("content-encoding"); if(field != null) { return field.getDecorator(); } return null; } public final ContentTransferEncodingField getContentTransferEncoding() { UnstructuredField field = (UnstructuredField)getField("content-transfer-encoding"); if(field != null) { return field.getDecorator(); } return null; } //--- public final Date getLastReceivedDate() { List receivedFields = (List) getFields("received"); if(receivedFields.size() > 0) { String receivedFieldText = receivedFields.get(0).getText(); int k = receivedFieldText.lastIndexOf(";"); if(k != -1) { StringReader reader = new StringReader(receivedFieldText.substring(k + 1)); DateTimeParser dtp = new DateTimeParser(reader); try { DateTime dt = dtp.parseAll(); if(dt != null) { return dt.getDate(); } } catch(Exception ex) { log.error(ex, "received date: %s", receivedFieldText); } } else { log.error("received date: %s", receivedFieldText); } } return null; } /* RFC 5322 - Internet Message Format +----------------+--------+------------+----------------------------+ | Field | Min | Max number | Notes | | | number | | | +----------------+--------+------------+----------------------------+ | trace | 0 | unlimited | Block prepended - see | | | | | 3.6.7 | | resent-date | 0* | unlimited* | One per block, required if | | | | | other resent fields are | | | | | present - see 3.6.6 | | resent-from | 0 | unlimited* | One per block - see 3.6.6 | | resent-sender | 0* | unlimited* | One per block, MUST occur | | | | | with multi-address | | | | | resent-from - see 3.6.6 | | resent-to | 0 | unlimited* | One per block - see 3.6.6 | | resent-cc | 0 | unlimited* | One per block - see 3.6.6 | | resent-bcc | 0 | unlimited* | One per block - see 3.6.6 | | resent-msg-id | 0 | unlimited* | One per block - see 3.6.6 | | orig-date | 1 | 1 | | | from | 1 | 1 | See sender and 3.6.2 | | sender | 0* | 1 | MUST occur with | | | | | multi-address from - see | | | | | 3.6.2 | | reply-to | 0 | 1 | | | to | 0 | 1 | | | cc | 0 | 1 | | | bcc | 0 | 1 | | | message-id | 0* | 1 | SHOULD be present - see | | | | | 3.6.4 | | in-reply-to | 0* | 1 | SHOULD occur in some | | | | | replies - see 3.6.4 | | references | 0* | 1 | SHOULD occur in some | | | | | replies - see 3.6.4 | | subject | 0 | 1 | | | comments | 0 | unlimited | | | keywords | 0 | unlimited | | | optional-field | 0 | unlimited | | +----------------+--------+------------+----------------------------+ */ // }