Skip to content

Commit

Permalink
#404, #405: Added support for Content-Transfer-Encoding and Content-D…
Browse files Browse the repository at this point in the history
…escription for attachments
  • Loading branch information
bbottema committed Jul 15, 2022
1 parent 4d75911 commit c8b32ed
Show file tree
Hide file tree
Showing 17 changed files with 439 additions and 90 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,27 +26,58 @@ public class AttachmentResource implements Serializable {
private static final long serialVersionUID = 1234567L;

/**
* @see #AttachmentResource(String, DataSource)
* @see #AttachmentResource(String, DataSource, String)
*/
private final String name;

/**
* @see #AttachmentResource(String, DataSource)
* @see #AttachmentResource(String, DataSource, String)
*/
// data source is not serializable, so transient (Kryo can do it though, see SerializationUtil in the OutlookModule)
private transient final DataSource dataSource;

/**
* @see #AttachmentResource(String, DataSource, String)
*/
@Nullable
private final String description;

/**
* @see #AttachmentResource(String, DataSource, String)
*/
@Nullable
private final ContentTransferEncoding contentTransferEncoding;

/**
* Delegates to {@link AttachmentResource#AttachmentResource(String, DataSource, String, ContentTransferEncoding)} with null-description and no forced content transfer encoding
*/
public AttachmentResource(@Nullable final String name, @NotNull final DataSource dataSource) {
this(name, dataSource, null, null);
}

/**
* Delegates to {@link AttachmentResource#AttachmentResource(String, DataSource, String, ContentTransferEncoding)} with no forced content transfer encoding
*/
public AttachmentResource(@Nullable final String name, @NotNull final DataSource dataSource, @Nullable final String description) {
this(name, dataSource, description, null);
}

/**
* Constructor; initializes the attachment resource with a name and data.
*
* @param name The name of the attachment which can be a simple name, a filename or a named embedded image (eg. <cid:footer>). Leave
* <code>null</code> to fall back on {@link DataSource#getName()}.
* @param dataSource The attachment data. If no name was provided, the name of this datasource is used if provided.
* @param name The name of the attachment which can be a simple name, a filename or a named embedded image (eg. &lt;cid:footer&gt;). Leave
* <code>null</code> to fall back on {@link DataSource#getName()}.
* @param dataSource The attachment data. If no name was provided, the name of this datasource is used if provided.
* @param description An optional description that will find its way in the MimeMEssage with the Content-Description header. This is rarely needed.
* @param contentTransferEncoding An optional encoder option to force the data encoding while in MimeMessage/EML format.
*
* @see DataSource
*/
public AttachmentResource(@Nullable final String name, @NotNull final DataSource dataSource) {
public AttachmentResource(@Nullable final String name, @NotNull final DataSource dataSource, @Nullable final String description, @Nullable final ContentTransferEncoding contentTransferEncoding) {
this.name = name;
this.dataSource = checkNonEmptyArgument(dataSource, "dataSource");
this.description = description;
this.contentTransferEncoding = contentTransferEncoding;
}

/**
Expand Down Expand Up @@ -79,14 +110,6 @@ public String readAllData(@SuppressWarnings("SameParameterValue") @NotNull final
return MiscUtil.readInputStreamToString(getDataSourceInputStream(), charset);
}

/**
* @return {@link #dataSource}
*/
@NotNull
public DataSource getDataSource() {
return dataSource;
}

/**
* Delegates to {@link DataSource#getInputStream}
*/
Expand All @@ -100,13 +123,37 @@ public InputStream getDataSourceInputStream() {
}

/**
* @return {@link #name}
* @see #AttachmentResource(String, DataSource, String)
*/
@NotNull
public DataSource getDataSource() {
return dataSource;
}

/**
* @see #AttachmentResource(String, DataSource, String)
*/
@Nullable
public String getName() {
return name;
}

/**
* @see #AttachmentResource(String, DataSource, String)
*/
@Nullable
public String getDescription() {
return description;
}

/**
* @see #AttachmentResource(String, DataSource, String)
*/
@Nullable
public ContentTransferEncoding getContentTransferEncoding() {
return contentTransferEncoding;
}

@SuppressWarnings("SameReturnValue")
@Override
public int hashCode() {
Expand All @@ -119,16 +166,20 @@ public boolean equals(@Nullable Object o) {
if (o == null || getClass() != o.getClass()) return false;
AttachmentResource that = (AttachmentResource) o;
return Objects.equals(name, that.name) &&
EqualsHelper.isEqualDataSource(dataSource, that.dataSource);
EqualsHelper.isEqualDataSource(dataSource, that.dataSource) &&
Objects.equals(description, that.description) &&
Objects.equals(contentTransferEncoding, that.contentTransferEncoding);
}

@Override
@NotNull
public String toString() {
return "AttachmentResource{" +
"\n\t\tname='" + name + '\'' +
"\n\t\tname='" + name + "'" +
",\n\t\tdataSource.name=" + dataSource.getName() +
",\n\t\tdataSource.getContentType=" + dataSource.getContentType() +
",\n\t\tdescription=" + (description != null ? "'" + description + "'" : "null") +
",\n\t\tcontentTransferEncoding=" + (contentTransferEncoding != null ? "'" + contentTransferEncoding + "'" : "null") +
"\n\t}";
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import org.jetbrains.annotations.NotNull;

import java.util.Arrays;

Expand All @@ -28,4 +29,16 @@ public enum ContentTransferEncoding {
X_UUE("x-uue");

private final String encoder;

public static ContentTransferEncoding byEncoder(@NotNull final String encoder) {
return Arrays.stream(values())
.filter(c -> c.encoder.equals(encoder))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("unknown content transfer encoder: " + encoder));
}

@Override
public String toString() {
return encoder;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1059,36 +1059,59 @@ public interface EmailPopulatingBuilder {
* @see #withHeaders(Map)
*/
EmailPopulatingBuilder withHeader(@NotNull String name, @Nullable Object value);

/**
* Delegates to {@link #withAttachment(String, DataSource)}, with a named {@link ByteArrayDataSource} created using the provided name, data and
* mimetype.
* Delegates to {@link #withAttachment(String, byte[], String, String, ContentTransferEncoding)} with null-description and no forced content transfer encoding.
*/
EmailPopulatingBuilder withAttachment(@Nullable String name, @NotNull byte[] data, @NotNull String mimetype);

/**
* Delegates to {@link #withAttachment(String, byte[], String, String, ContentTransferEncoding)} with no forced content transfer encoding.
*/
EmailPopulatingBuilder withAttachment(@Nullable String name, @NotNull byte[] data, @NotNull String mimetype, @Nullable String description);

/**
* Delegates to {@link #withAttachment(String, DataSource)}, with a named {@link ByteArrayDataSource} created using the provided name, data and mimetype.
*
* @param name Optional name of the attachment (e.g. 'filename.ext'). If omitted, the internal name of the datasource is used. If that too is
* empty, a name will be generated using {@link java.util.UUID}.
* @param data The binary data of the attachment.
* @param mimetype The content type of the given data (e.g. "plain/text", "image/gif" or "application/pdf").
* @param name Optional name of the attachment (e.g. 'filename.ext'). If omitted, the internal name of the datasource is used. If that too is empty, a name will be generated
* using {@link java.util.UUID}.
* @param data The binary data of the attachment.
* @param mimetype The content type of the given data (e.g. "plain/text", "image/gif" or "application/pdf").
* @param description An optional description that will find its way in the MimeMEssage with the Content-Description header. This is rarely needed.
* @param contentTransferEncoding An optional encoder option to force the data encoding while in MimeMessage/EML format.
*
* @see #withAttachment(String, DataSource)
* @see #withAttachments(List)
*/
EmailPopulatingBuilder withAttachment(@Nullable String name, @NotNull byte[] data, @NotNull String mimetype);
EmailPopulatingBuilder withAttachment(@Nullable String name, @NotNull byte[] data, @NotNull String mimetype, @Nullable String description, @Nullable ContentTransferEncoding contentTransferEncoding);

/**
* Adds an attachment to the email message, which will be shown in the email client as seperate files available for download or inline display if
* the client supports it (for example, most browsers these days display PDF's in a popup).
* Delegates to {@link #withAttachment(String, DataSource, String, ContentTransferEncoding)} with null-description and no forced content transfer encoding.
*/
EmailPopulatingBuilder withAttachment(@Nullable String name, @NotNull DataSource filedata);

/**
* Delegates to {@link #withAttachment(String, DataSource, String, ContentTransferEncoding)} with no forced content transfer encoding.
*/
EmailPopulatingBuilder withAttachment(@Nullable String name, @NotNull DataSource filedata, @Nullable final String description);

/**
* Adds an attachment to the email message, which will be shown in the email client as seperate files available for download or inline display if the client supports it (for example, most browsers
* these days display PDF's in a popup).
* <p>
* <strong>Note</strong>: for embedding images instead of attaching them for download, refer to {@link #withEmbeddedImage(String, DataSource)} instead.
*
* @param name Optional name of the attachment (e.g. 'filename.ext'). If omitted, the internal name of the datasource is used. If that too is
* empty, a name will be generated using {@link java.util.UUID}.
* @param filedata The attachment data.
* @param name Optional name of the attachment (e.g. 'filename.ext'). If omitted, the internal name of the datasource is used. If that too is empty, a name will be generated
* using {@link java.util.UUID}.
* @param filedata The attachment data.
* @param description An optional description that will find its way in the MimeMEssage with the Content-Description header. This is rarely needed.
* @param contentTransferEncoding An optional encoder option to force the data encoding while in MimeMessage/EML format.
*
* @see #withAttachment(String, byte[], String)
* @see #withAttachments(List)
*/
EmailPopulatingBuilder withAttachment(@Nullable String name, @NotNull DataSource filedata);
EmailPopulatingBuilder withAttachment(@Nullable String name, @NotNull DataSource filedata, @Nullable final String description, @Nullable final ContentTransferEncoding contentTransferEncoding);

/**
* Delegates to {@link #withAttachment(String, DataSource)} for each attachment.
*/
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.simplejavamail.api.email.CalendarMethod;
import org.simplejavamail.api.email.ContentTransferEncoding;
import org.simplejavamail.api.email.Email;
import org.simplejavamail.api.email.EmailPopulatingBuilder;
import org.simplejavamail.api.email.OriginalSmimeDetails;
Expand All @@ -17,6 +18,7 @@
import org.simplejavamail.api.internal.smimesupport.builder.SmimeParseResult;
import org.simplejavamail.api.mailer.config.Pkcs12Config;
import org.simplejavamail.converter.internal.InternalEmailConverterImpl;
import org.simplejavamail.converter.internal.mimemessage.MimeDataSource;
import org.simplejavamail.converter.internal.mimemessage.MimeMessageParser;
import org.simplejavamail.converter.internal.mimemessage.MimeMessageParser.ParsedMimeMessageComponents;
import org.simplejavamail.converter.internal.mimemessage.MimeMessageProducerHelper;
Expand All @@ -26,6 +28,7 @@
import org.simplejavamail.email.internal.InternalEmailPopulatingBuilder;
import org.simplejavamail.internal.moduleloader.ModuleLoader;
import org.simplejavamail.internal.smimesupport.model.OriginalSmimeDetailsImpl;
import org.simplejavamail.internal.util.MiscUtil;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
Expand All @@ -45,6 +48,7 @@
import static org.simplejavamail.internal.moduleloader.ModuleLoader.loadSmimeModule;
import static org.simplejavamail.internal.util.MiscUtil.extractCID;
import static org.simplejavamail.internal.util.MiscUtil.readInputStreamToString;
import static org.simplejavamail.internal.util.MiscUtil.valueNullOrEmpty;
import static org.simplejavamail.internal.util.Preconditions.checkNonEmptyArgument;
import static org.simplejavamail.internal.util.Preconditions.verifyNonnullOrEmpty;

Expand Down Expand Up @@ -636,17 +640,17 @@ private static EmailPopulatingBuilder buildEmailFromMimeMessage(@NotNull final E
builder.withReplyTo(parsed.getReplyToAddresses().getPersonal(), parsed.getReplyToAddresses().getAddress());
}
builder.withHeaders(parsed.getHeaders());
final InternetAddress dnTo = parsed.getDispositionNotificationTo();
if (dnTo != null) {
builder.withDispositionNotificationTo(dnTo);
if (parsed.getDispositionNotificationTo() != null) {
builder.withDispositionNotificationTo(parsed.getDispositionNotificationTo());
}
final InternetAddress rrTo = parsed.getReturnReceiptTo();
if (rrTo != null) {
builder.withReturnReceiptTo(rrTo);
if (parsed.getReturnReceiptTo() != null) {
builder.withReturnReceiptTo(parsed.getReturnReceiptTo());
}
final InternetAddress bTo = parsed.getBounceToAddress();
if (bTo != null) {
builder.withBounceTo(bTo);
if (parsed.getBounceToAddress() != null) {
builder.withBounceTo(parsed.getBounceToAddress());
}
if (parsed.getContentTransferEncoding() != null) {
builder.withContentTransferEncoding(ContentTransferEncoding.byEncoder(parsed.getContentTransferEncoding()));
}
builder.fixingMessageId(parsed.getMessageId());
for (final InternetAddress to : parsed.getToAddresses()) {
Expand All @@ -671,8 +675,10 @@ private static EmailPopulatingBuilder buildEmailFromMimeMessage(@NotNull final E
final String cidName = checkNonEmptyArgument(cid.getKey(), "cid.key");
builder.withEmbeddedImage(extractCID(cidName), cid.getValue());
}
for (final Map.Entry<String, DataSource> attachment : parsed.getAttachmentList()) {
builder.withAttachment(extractCID(attachment.getKey()), attachment.getValue());
for (final MimeDataSource attachment : parsed.getAttachmentList()) {
final ContentTransferEncoding encoding = !valueNullOrEmpty(attachment.getContentTransferEncoding())
? ContentTransferEncoding.byEncoder(attachment.getContentTransferEncoding()) : null;
builder.withAttachment(extractCID(attachment.getName()), attachment.getDataSource(), attachment.getContentDescription(), encoding);
}
return builder;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.simplejavamail.converter.internal.mimemessage;

import jakarta.activation.DataSource;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Objects;

@Getter
@Builder
public class MimeDataSource implements Comparable<MimeDataSource> {
private final String name;
private final DataSource dataSource;
@Nullable private final String contentDescription;
@Nullable private final String contentTransferEncoding;

@Override
public int compareTo(@NotNull final MimeDataSource o) {
int keyComparison = getName().compareTo(o.getName());
if (keyComparison != 0) {
return keyComparison;
}
return Integer.compare(getDataSource().hashCode(), o.getDataSource().hashCode());
}

@Override
public boolean equals(final Object o) {
return this == o ||
(o instanceof MimeDataSource && compareTo((MimeDataSource) o) == 0);
}

@Override
public int hashCode() {
return Objects.hash(name, dataSource);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,12 @@ private static BodyPart getBodyPartFromDatasource(final AttachmentResource attac
pl.set("name", fileName);
attachmentPart.setHeader("Content-Type", contentType + pl);
attachmentPart.setHeader("Content-ID", format("<%s>", resourceName));
// if (!valueNullOrEmpty(attachmentResource.getDescription())) { // FIXME clean up?
attachmentPart.setHeader("Content-Description", attachmentResource.getDescription());
// }
if (!valueNullOrEmpty(attachmentResource.getContentTransferEncoding())) {
attachmentPart.setHeader("Content-Transfer-Encoding", attachmentResource.getContentTransferEncoding().getEncoder());
}
attachmentPart.setDisposition(dispositionType);
return attachmentPart;
}
Expand Down
Loading

0 comments on commit c8b32ed

Please sign in to comment.