Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Magic attachments #1466

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions tmail-backend/apps/distributed/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,10 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,18 @@
import org.apache.james.jmap.InjectionKeys;
import org.apache.james.jmap.JMAPListenerModule;
import org.apache.james.jmap.JMAPModule;
import org.apache.james.jmap.rfc8621.RFC8621MethodsModule;
import org.apache.james.json.DTO;
import org.apache.james.json.DTOModule;
import org.apache.james.mailbox.MailboxManager;
import org.apache.james.mailbox.MailboxSession;
import org.apache.james.mailbox.cassandra.CassandraMailboxManager;
import org.apache.james.mailbox.cassandra.mail.CassandraAttachmentMapper;
import org.apache.james.mailbox.model.MessageId;
import org.apache.james.mailbox.model.MultimailboxesSearchQuery;
import org.apache.james.mailbox.searchhighligt.SearchHighlighter;
import org.apache.james.mailbox.searchhighligt.SearchSnippet;
import org.apache.james.mailbox.store.mail.model.impl.MessageParser;
import org.apache.james.mailbox.store.search.ListeningMessageSearchIndex;
import org.apache.james.mailbox.store.search.MessageSearchIndex;
import org.apache.james.mailbox.store.search.SimpleMessageSearchIndex;
Expand Down Expand Up @@ -270,7 +273,9 @@ protected void configure() {
new ForwardGetMethodModule(),
new ForwardSetMethodModule(),
new JMAPServerModule(),
JMAPModule.INSTANCE,
new JMAPModule(),
new RFC8621MethodsModule(),
new TMailCleverBlobResolverModule(),
new JmapEventBusModule(),
new PublicAssetsModule(),
new KeystoreCassandraModule(),
Expand Down Expand Up @@ -316,7 +321,11 @@ protected void configure() {

public static final Module CASSANDRA_MAILBOX_MODULE = Modules.combine(
new CassandraConsistencyTaskSerializationModule(),
new CassandraMailboxModule(),
Modules.override(new CassandraMailboxModule())
.with(binder -> {
binder.bind(CassandraAttachmentMapper.AttachmentIdAssignationStrategy.class).to(TMailCleverAttachmentIdAssignationStrategy.class);
binder.bind(MessageParser.class).to(TMailCleverMessageParser.class);
}),
new MailboxModule(),
new TikaMailboxModule());

Expand Down Expand Up @@ -354,7 +363,7 @@ protected void configure() {
new TeamMailboxModule(),
new TMailMailboxSortOrderProviderModule(),
new TmailEventModule(),
new TmailEventDeadLettersModule(),
new TMailEventDeadLettersModule(),
new TMailIMAPModule());

public static void main(String[] args) throws Exception {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/********************************************************************
* As a subpart of Twake Mail, this file is edited by Linagora. *
* *
* https://twake-mail.com/ *
* https://linagora.com *
* *
* This file is subject to The Affero Gnu Public License *
* version 3. *
* *
* https://www.gnu.org/licenses/agpl-3.0.en.html *
* *
* 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. *
********************************************************************/
package com.linagora.tmail.james.app;

import org.apache.james.mailbox.cassandra.mail.CassandraAttachmentMapper;
import org.apache.james.mailbox.model.AttachmentId;
import org.apache.james.mailbox.model.MessageId;
import org.apache.james.mailbox.model.ParsedAttachment;
import org.apache.james.mailbox.model.StringBackedAttachmentId;

public class TMailCleverAttachmentIdAssignationStrategy implements CassandraAttachmentMapper.AttachmentIdAssignationStrategy {
@Override
public AttachmentId assign(ParsedAttachment parsedAttachment, MessageId messageId) {
if (parsedAttachment instanceof TMailCleverParsedAttachment TMailCleverParsedAttachment) {
return StringBackedAttachmentId.from(TMailCleverParsedAttachment.translate(messageId));
}
return StringBackedAttachmentId.random();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/********************************************************************
* As a subpart of Twake Mail, this file is edited by Linagora. *
* *
* https://twake-mail.com/ *
* https://linagora.com *
* *
* This file is subject to The Affero Gnu Public License *
* version 3. *
* *
* https://www.gnu.org/licenses/agpl-3.0.en.html *
* *
* 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. *
********************************************************************/

package com.linagora.tmail.james.app;

import org.apache.james.jmap.routes.BlobResolver;
import org.apache.james.jmap.routes.MessageBlobResolver;
import org.apache.james.jmap.routes.UploadResolver;

import com.google.inject.AbstractModule;
import com.google.inject.multibindings.Multibinder;

public class TMailCleverBlobResolverModule extends AbstractModule {
@Override
protected void configure() {
Multibinder<BlobResolver> blobResolverMultibinder = Multibinder.newSetBinder(binder(), BlobResolver.class);
blobResolverMultibinder.addBinding().to(MessageBlobResolver.class);
blobResolverMultibinder.addBinding().to(UploadResolver.class);
blobResolverMultibinder.addBinding().to(TMailCleverBlobResolver.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
import com.google.inject.Singleton;
import com.linagora.tmail.event.TmailEventSerializer;

public class TmailEventDeadLettersModule extends AbstractModule {
public class TMailEventDeadLettersModule extends AbstractModule {

@Provides
@Singleton
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/** ******************************************************************
* As a subpart of Twake Mail, this file is edited by Linagora. *
* *
* https://twake-mail.com/ *
* https://linagora.com *
* *
* This file is subject to The Affero Gnu Public License *
* version 3. *
* *
* https://www.gnu.org/licenses/agpl-3.0.en.html *
* *
* 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. *
* ****************************************************************** */

package com.linagora.tmail.james.app

import jakarta.inject.Inject
import org.apache.james.jmap.mail.BlobId
import org.apache.james.jmap.routes.{AttachmentBlobResolver, BlobResolutionResult, BlobResolver, MessagePartBlobResolver, NonApplicable}
import org.apache.james.mailbox.MailboxSession
import reactor.core.scala.publisher.SMono

class TMailCleverBlobResolver @Inject() (messagePartBlobResolver: MessagePartBlobResolver, attachmentBlobResolver: AttachmentBlobResolver) extends BlobResolver {
override def resolve(blobId: BlobId, mailboxSession: MailboxSession): SMono[BlobResolutionResult] =
attachmentBlobResolver.resolve(blobId, mailboxSession)
.flatMap[BlobResolutionResult] {
case NonApplicable => messagePartBlobResolver.resolve(blobId, mailboxSession)
case any => SMono.just(any)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/** ******************************************************************
* As a subpart of Twake Mail, this file is edited by Linagora. *
* *
* https://twake-mail.com/ *
* https://linagora.com *
* *
* This file is subject to The Affero Gnu Public License *
* version 3. *
* *
* https://www.gnu.org/licenses/agpl-3.0.en.html *
* *
* 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. *
* ****************************************************************** */

package com.linagora.tmail.james.app

import com.google.common.io.ByteSource
import jakarta.inject.Inject
import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream
import org.apache.james.jmap.mail.{BlobId, Disposition, EmailBodyPart}
import org.apache.james.jmap.method.ZoneIdProvider
import org.apache.james.mailbox.model.{Cid, MessageId, ParsedAttachment}
import org.apache.james.mailbox.store.mail.model.impl.{FileBufferedBodyFactory, MessageParser}
import org.apache.james.mime4j.codec.DecodeMonitor
import org.apache.james.mime4j.dom.{Message, SingleBody}
import org.apache.james.mime4j.message.{DefaultMessageBuilder, DefaultMessageWriter}
import org.apache.james.mime4j.stream.MimeConfig

import java.io.InputStream
import java.util
import scala.jdk.CollectionConverters._
import scala.jdk.OptionConverters._


case class EmailBodyPartContent(part: EmailBodyPart) extends ByteSource {
override def size: Long = part.size.value

override def openStream: InputStream = part.entity.getBody match {
case body: SingleBody => body.getInputStream
case body =>
val writer = new DefaultMessageWriter
val outputStream = new UnsynchronizedByteArrayOutputStream()
writer.writeBody(body, outputStream)
outputStream.toInputStream
}
}

case class TMailCleverParsedAttachment(parsedAttachment: ParsedAttachment, blobId: BlobId) extends ParsedAttachment(parsedAttachment.getContentType,
parsedAttachment.getContent, parsedAttachment.getName, parsedAttachment.getCid, parsedAttachment.isInline) {

def translate(messageId: MessageId): String = {
val rawValue = blobId.value.value
if (rawValue.startsWith(TMailCleverParsedAttachment.placeholder)) {
messageId.serialize() + rawValue.substring(TMailCleverParsedAttachment.placeholder.length)
}
throw new RuntimeException("unsuported")
}
}

object TMailCleverParsedAttachment {
val placeholder: String = "___PLACEHOLDER___"
}

class TMailCleverMessageParser @Inject() (zoneIdProvider: ZoneIdProvider) extends MessageParser {
override def retrieveAttachments(fullContent: InputStream): MessageParser.ParsingResult = {
val defaultMessageBuilder = new DefaultMessageBuilder
defaultMessageBuilder.setMimeEntityConfig(MimeConfig.PERMISSIVE)
defaultMessageBuilder.setDecodeMonitor(DecodeMonitor.SILENT)
val bodyFactory = new FileBufferedBodyFactory
defaultMessageBuilder.setBodyFactory(bodyFactory)
try {
val message = defaultMessageBuilder.parseMessage(fullContent)
new MessageParser.ParsingResult(retrieveAttachments(message), () => bodyFactory.dispose())
} catch {
case e: Exception =>
// Release associated temporary files
bodyFactory.dispose()
throw e
}
}

override def retrieveAttachments(message: Message): util.List[ParsedAttachment] = {
val value = BlobId.of(TMailCleverParsedAttachment.placeholder).get
val triedPart = EmailBodyPart.of(None, zoneIdProvider.get(), value, message)
triedPart.map(part => part.attachments).toOption.getOrElse(List())
.map(attachment => {
val isInline = attachment.disposition.contains(Disposition.INLINE) && attachment.cid.isDefined
TMailCleverParsedAttachment(ParsedAttachment.builder
.contentType(attachment.`type`.value)
.content(EmailBodyPartContent(attachment))
.name(attachment.name.map(_.value).toJava)
.cid(attachment.cid.map(_.getValue).map(Cid.from).toJava)
.inline(isInline), attachment.blobId.get)
.asInstanceOf[ParsedAttachment]
}).asJava
}
}