java.lang.Object
page.codeberg.friedolyn.util.EMailSender
- All Implemented Interfaces:
Copyable<EMailSender>
,Redactable
Helper tool to send e-mails via the
SMTP server of the Friedrich Schiller
University Jena. The e-mails can be encrypted with
PGP in
PGP/MIME format.
- See Also:
-
Nested Class Summary
Nested Classes -
Field Summary
FieldsModifier and TypeFieldDescriptionprivate static final Pattern
The regular expression pattern to extract the boundary from the Content-Type header of a MIME message.private @NonNull jakarta.mail.internet.InternetAddress
The student's e-mail address at the university.static final String
The domain name of the university's IMAP server that will be used to store the sent e-mails in the sent-folder of the student's inbox.static final int
private char[]
The password for the university's e-mail server.private @NonNull Properties
The configuration for sending the e-mail notifications.private static final String[]
Common names for the sent folder in various IMAP servers.private @NonNull jakarta.mail.Session
The e-mail session that is used to send the e-mail notifications.static final String
The domain name of the university's SMTP server that will be used to send the e-mail notifications.static final int
private static final @NonNull String
The user agent header will be specified whensending
e-mails: Thedefault
user agent for the Friedolyn application. -
Constructor Summary
ConstructorsConstructorDescriptionEMailSender
(@NonNull jakarta.mail.internet.InternetAddress email, char[] password) Constructs a newEMailSender
with the given SMTP credentials for the university's e-mail server. -
Method Summary
Modifier and TypeMethodDescriptionprivate @NonNull jakarta.mail.internet.MimeMessage
constructEmail
(@NonNull String subject, @NonNull String message, @NonNull Set<jakarta.mail.internet.InternetAddress> recipients, @NonNull Set<jakarta.mail.internet.InternetAddress> ccRecipients, @NonNull Set<jakarta.mail.internet.InternetAddress> bccRecipients, @NonNull File... attachments) Constructs a newMimeMessage
from the specified content and metadata.private @NonNull jakarta.mail.internet.MimeMultipart
constructMultipart
(String message, File... attachments) Constructs a MIME multipart from the given e-mail message and files attached.@NonNull EMailSender
copy()
private static @NonNull File
encryptMessage
(@NonNull jakarta.mail.internet.MimeMultipart message, @NonNull Set<String> pgpPublicKeys) Encrypts the given MIME message for the given PGP public keys, following the PGP/MIME standard.private static @NonNull File
encryptMessage
(@NonNull jakarta.mail.internet.MimeMultipart message, @NonNull Set<String> pgpPublicKeys, @NonNull String boundary) Encrypts the given MIME message for the given PGP public keys, following the PGP/MIME standard.boolean
Compares thisEMailSender
with the givenObject
.private void
Initialises theproperties
with the default settings for sending e-mails via the university's SMTP server.parseBoundary
(@NonNull String contentTypeHeader) private void
saveEmailToSentFolder
(@NonNull jakarta.mail.internet.MimeMessage email) Tries to store the given e-mail in the sent folder of the user's university e-mail account, using the IMAP protocol.@NonNull Optional
<ByteArrayOutputStream> send
(@NonNull String subject, @NonNull String message, @NonNull Set<jakarta.mail.internet.InternetAddress> recipients, boolean saveToSentFolder, @NonNull File... attachments) Sends an unencrypted e-mail from thesender
using thepassword
specified in theconstructor
to log in at the university'sSMTP server
.@NonNull EMailSender.SentEmail
send
(@NonNull String subject, @NonNull String message, @NonNull Set<jakarta.mail.internet.InternetAddress> recipients, boolean saveToSentFolder, @NonNull Set<String> pgpPublicKeys, boolean fallBackToUnencrypted, @NonNull File... attachments) Sends an encrypted e-mail from thesender
using thepassword
specified in theconstructor
to log in at the university'sSMTP server
.@NonNull EMailSender.SentEmail
send
(@NonNull String subject, @NonNull String message, @NonNull Set<jakarta.mail.internet.InternetAddress> recipients, @NonNull Set<jakarta.mail.internet.InternetAddress> ccRecipients, @NonNull Set<jakarta.mail.internet.InternetAddress> bccRecipients, boolean saveToSentFolder, @NonNull Set<String> pgpPublicKeys, boolean fallBackToUnencrypted, @NonNull File... attachments) Sends an e-mail from thesender
using thepassword
specified in theconstructor
to log in at the university'sSMTP server
.@NonNull String
toString()
@NonNull String
private static @NonNull Optional
<ByteArrayOutputStream> writeEmail
(@NonNull jakarta.mail.Message email) Converts the given e-mail to its binary representation (in MIME format).
-
Field Details
-
SMTP_HOST
-
IMAP_HOST
-
SMTP_PORT
-
IMAP_PORT
-
BOUNDARY_PATTERN
The regular expression pattern to extract the boundary from the Content-Type header of a MIME message.- See Also:
-
SENT_FOLDER_NAMES
Common names for the sent folder in various IMAP servers. -
email
@NonNull private @NonNull jakarta.mail.internet.InternetAddress emailThe student's e-mail address at the university. Will be used as the sender of the e-mail. -
password
private char[] passwordThe password for the university's e-mail server. -
properties
The configuration for sending the e-mail notifications. -
session
@NonNull private @NonNull jakarta.mail.Session sessionThe e-mail session that is used to send the e-mail notifications. -
USER_AGENT
The user agent header will be specified whensending
e-mails: Thedefault
user agent for the Friedolyn application.
-
-
Constructor Details
-
EMailSender
public EMailSender(@NonNull @NonNull jakarta.mail.internet.InternetAddress email, char[] password) Constructs a newEMailSender
with the given SMTP credentials for the university's e-mail server.- Parameters:
email
- The student's e-mail address at the university. Will be used as the sender of the e-mail.password
- The student's password for the university's e-mail server.
-
-
Method Details
-
initialiseProperties
private void initialiseProperties()Initialises theproperties
with the default settings for sending e-mails via the university's SMTP server. -
send
@NonNull public @NonNull Optional<ByteArrayOutputStream> send(@NonNull @NonNull String subject, @NonNull @NonNull String message, @NonNull @NonNull Set<jakarta.mail.internet.InternetAddress> recipients, boolean saveToSentFolder, @NonNull @NonNull File... attachments) throws RuntimeException Sends an unencrypted e-mail from thesender
using thepassword
specified in theconstructor
to log in at the university'sSMTP server
. The e-mail will not be encrypted.- Parameters:
subject
- What the e-mail is about.message
- The actual content of the e-mail.recipients
- Whom the e-mail shall be sent to.saveToSentFolder
- Whether the e-mail should be stored in the “sent” folder of the user's inbox.attachments
- Any number of files to send along with the message.- Returns:
- The entire e-mail as it was sent, ready to be written to an
.eml
file. - Throws:
RuntimeException
- Seesend(String, String, Set, Set, Set, boolean, Set, boolean, File...)
.- See Also:
-
send
@NonNull public @NonNull EMailSender.SentEmail send(@NonNull @NonNull String subject, @NonNull @NonNull String message, @NonNull @NonNull Set<jakarta.mail.internet.InternetAddress> recipients, boolean saveToSentFolder, @NonNull @NonNull Set<String> pgpPublicKeys, boolean fallBackToUnencrypted, @NonNull @NonNull File... attachments) throws RuntimeException Sends an encrypted e-mail from thesender
using thepassword
specified in theconstructor
to log in at the university'sSMTP server
. The message will be encrypted with the given PGP public keys.- Parameters:
subject
- What the e-mail is about.message
- The actual content of the e-mail.recipients
- Whom the e-mail shall be sent to.saveToSentFolder
- Whether the e-mail should be stored in the “sent” folder of the user's inbox.pgpPublicKeys
- Any number of PGP public keys toencrypt
the message with. It does not matter if the keys are of RSA or ECC type, as long as they support encryption.fallBackToUnencrypted
- If the e-mail could not be encrypted, should it be sent unencrypted?attachments
- Any number of files to send along with the message.- Returns:
- The entire e-mail as it was sent, ready to be written to an
.eml
file.-
unencryptedEmail
: A copy of the e-mail before encryption or (if sent unencrypted) the exact e-mail as it was sent. Empty iffallBackToUnencrypted
isfalse
. -
encryptedEmail
: If the e-mail was sent encrypted, the e-mail as it was sent. Otherwise, empty.
OutputStream
. If empty, this does NOT imply that the e-mail was not sent; if the e-mail could not be sent, an exception will be thrown instead. -
- Throws:
RuntimeException
- Seesend(String, String, Set, Set, Set, boolean, Set, boolean, File...)
.- See Also:
-
send
@NonNull public @NonNull EMailSender.SentEmail send(@NonNull @NonNull String subject, @NonNull @NonNull String message, @NonNull @NonNull Set<jakarta.mail.internet.InternetAddress> recipients, @NonNull @NonNull Set<jakarta.mail.internet.InternetAddress> ccRecipients, @NonNull @NonNull Set<jakarta.mail.internet.InternetAddress> bccRecipients, boolean saveToSentFolder, @NonNull @NonNull Set<String> pgpPublicKeys, boolean fallBackToUnencrypted, @NonNull @NonNull File... attachments) throws RuntimeException Sends an e-mail from thesender
using thepassword
specified in theconstructor
to log in at the university'sSMTP server
.- Parameters:
subject
- What the e-mail is about.message
- The actual content of the e-mail.recipients
- Whom the e-mail shall be sent to.ccRecipients
- Whom the e-mail shall be carbon-copied to.bccRecipients
- Whom the e-mail shall be blind-carbon-copied to.saveToSentFolder
- Whether the e-mail should be stored in the “sent” folder of the user's inbox.pgpPublicKeys
- Any number of PGP public keys toencrypt
the message with. It does not matter if the keys are of RSA or ECC type, as long as they support encryption.fallBackToUnencrypted
- If the e-mail could not be encrypted, should it be sent unencrypted?attachments
- Any number of files to send along with the message.- Returns:
- The entire e-mail as it was sent, ready to be written to an
.eml
file.-
unencryptedEmail
: A copy of the e-mail before encryption or (if sent unencrypted) the exact e-mail as it was sent. Empty iffallBackToUnencrypted
isfalse
. -
encryptedEmail
: If the e-mail was sent encrypted, the e-mail as it was sent. Otherwise, empty.
OutputStream
. If empty, this does NOT imply that the e-mail was not sent; if the e-mail could not be sent, an exception will be thrown instead. -
- Throws:
RuntimeException
- If:- the recipients are not valid
- the message is not valid
- the files could not be attached to the e-mail
-
the e-mail could not be encrypted and falling back to unencrypted is disabled
(see
encryptMessage(MimeMultipart, Set, String)
) - the e-mail could not be sent
- See Also:
-
saveEmailToSentFolder
private void saveEmailToSentFolder(@NonNull @NonNull jakarta.mail.internet.MimeMessage email) throws RuntimeException Tries to store the given e-mail in the sent folder of the user's university e-mail account, using the IMAP protocol.- Parameters:
email
- The message to store in the sent folder.- Throws:
RuntimeException
- If:- the sent-folder could not be found
- the e-mail could not be saved to the sent folder
- Implementation Note:
- Since the sent-folder is not standardised,
several names
are tried to find the correct folder.
-
constructEmail
@NonNull private @NonNull jakarta.mail.internet.MimeMessage constructEmail(@NonNull @NonNull String subject, @NonNull @NonNull String message, @NonNull @NonNull Set<jakarta.mail.internet.InternetAddress> recipients, @NonNull @NonNull Set<jakarta.mail.internet.InternetAddress> ccRecipients, @NonNull @NonNull Set<jakarta.mail.internet.InternetAddress> bccRecipients, @NonNull @NonNull File... attachments) throws IllegalStateException, jakarta.mail.MessagingException, IOException Constructs a newMimeMessage
from the specified content and metadata.- Parameters:
subject
- What the e-mail is about.message
- The actual content of the e-mail.recipients
- Whom the e-mail shall be sent to.ccRecipients
- Whom the e-mail shall be carbon-copied to.bccRecipients
- Whom the e-mail shall be blind-carbon-copied to.attachments
- Any number of files to send along with the message.- Returns:
- The constructed e-mail.
- Throws:
IllegalStateException
- If the e-mail is from a read-only folder.jakarta.mail.MessagingException
- If the e-mail is from a read-only folder.IOException
- If the files attached could not be read.
-
constructMultipart
@NonNull private @NonNull jakarta.mail.internet.MimeMultipart constructMultipart(String message, File... attachments) throws jakarta.mail.MessagingException, IOException Constructs a MIME multipart from the given e-mail message and files attached.- Parameters:
message
- The actual content of the e-mail.attachments
- Any number of files to send along with the message.- Returns:
- The e-mail message as a MIME multipart.
- Throws:
jakarta.mail.MessagingException
- No context specified by the underlying JavaMail API. Sorry.IOException
- If the files attached could not be read.
-
writeEmail
@NonNull private static @NonNull Optional<ByteArrayOutputStream> writeEmail(@NonNull @NonNull jakarta.mail.Message email) Converts the given e-mail to its binary representation (in MIME format).- Parameters:
email
- The e-mail to convert.- Returns:
- The e-mail as a binary representation in MIME format.
-
encryptMessage
@NonNull private static @NonNull File encryptMessage(@NonNull @NonNull jakarta.mail.internet.MimeMultipart message, @NonNull @NonNull Set<String> pgpPublicKeys) throws IllegalArgumentException Encrypts the given MIME message for the given PGP public keys, following the PGP/MIME standard. Does not specify a boundary for the MIME message.- Parameters:
message
- The already MIME-formatted message to encrypt. May contain text and several attachments of any file type.pgpPublicKeys
- Any number of PGP public keys to encrypt the message with. It does not matter if the keys are of RSA or ECC type, as long as they support encryption.- Returns:
- The encrypted message as a
File
with the nameencrypted.asc
in the system's temporary directory. - Throws:
IllegalArgumentException
- SeeencryptMessage(MimeMultipart, Set, String)
.
-
encryptMessage
@NonNull private static @NonNull File encryptMessage(@NonNull @NonNull jakarta.mail.internet.MimeMultipart message, @NonNull @NonNull Set<String> pgpPublicKeys, @NonNull @NonNull String boundary) throws IllegalArgumentException Encrypts the given MIME message for the given PGP public keys, following the PGP/MIME standard.- Parameters:
message
- The already MIME-formatted message to encrypt. May contain text and several attachments of any file type.pgpPublicKeys
- Any number of PGP public keys to encrypt the message with. It does not matter if the keys are of RSA or ECC type, as long as they support encryption.boundary
- The boundary used in the MIME message provided. If there is no boundary present, this parameter should be an emptyString
(if so, no boundary will be specified). Will be added to theContent-Type
header of the MIME message.- Returns:
- The encrypted message as a
File
with the nameencrypted.asc
in the system's temporary directory. - Throws:
IllegalArgumentException
- If:- no PGP public keys were provided
- at least one of the given PGP public keys is invalid
-
If the message could not be
written
out -
The message could not be written to the
encrypted.asc
PGP/MIME file, including if the system's temporary directory could not be accessed.
- Implementation Note:
- The boundary is needed due to limitations of JavaMail: It does not natively support
PGP/MIME encryption and – by default – places a boundary at the top of the MIME message, which
does not match the PGP/MIME format. Therefore, we can't encrypt entire
MimeMessage
s but only theirMimeMultipart
parts and have to construct the beginning of theMimeMultipart
ourselves. This is done by specifying the Content-Type header at the very top, as required for PGP/MIME, including the specified boundary.Incorrect (JavaMail default)
--=-SiYyK6Oi9/3KVlFTZwJf Content-Type: multipart/mixed; boundary="=-SiYyK6Oi9/3KVlFTZwJf" Content-Type: text/plain Content-Transfer-Encoding: 7bit To whom it may concern, I am writing that you should not read this e-mail. --=-SiYyK6Oi9/3KVlFTZwJf Content-Disposition: attachment; filename="attachment.txt" Content-Type: text/plain; name="attachment.txt"; charset="UTF-8" Content-Transfer-Encoding: base64 4oCcVWx0aW1hdGVseSwgYXJndWluZyB0aGF0IHlvdSBkb24ndCBjYXJlIGFib3V0IHRoZSByaWdo dCB0byBwcml2YWN5IGJlY2F1c2UgeW91IGhhdmUgbm90aGluZyB0byBoaWRlIGlzIG5vCmRpZmZl cmVudCB0aGFuIHNheWluZyB5b3UgZG9uJ3QgY2FyZSBhYm91dCBmcmVlIHNwZWVjaCBiZWNhdXNl IHlvdSBoYXZlIG5vdGhpbmcgdG8gc2F5LuKAnQoK4oCTIEVkd2FyZCBTbm93ZGVu --=-SiYyK6Oi9/3KVlFTZwJf--
Correct (manually constructed)
Content-Type: multipart/mixed; boundary="=-SiYyK6Oi9/3KVlFTZwJf" --=-SiYyK6Oi9/3KVlFTZwJf Content-Type: text/plain Content-Transfer-Encoding: 7bit To whom it may concern, I am writing that you should not read this e-mail. --=-SiYyK6Oi9/3KVlFTZwJf Content-Disposition: attachment; filename="attachment.txt" Content-Type: text/plain; name="attachment.txt"; charset="UTF-8" Content-Transfer-Encoding: base64 4oCcVWx0aW1hdGVseSwgYXJndWluZyB0aGF0IHlvdSBkb24ndCBjYXJlIGFib3V0IHRoZSByaWdo dCB0byBwcml2YWN5IGJlY2F1c2UgeW91IGhhdmUgbm90aGluZyB0byBoaWRlIGlzIG5vCmRpZmZl cmVudCB0aGFuIHNheWluZyB5b3UgZG9uJ3QgY2FyZSBhYm91dCBmcmVlIHNwZWVjaCBiZWNhdXNl IHlvdSBoYXZlIG5vdGhpbmcgdG8gc2F5LuKAnQoK4oCTIEVkd2FyZCBTbm93ZGVu --=-SiYyK6Oi9/3KVlFTZwJf--
-
toString
- Specified by:
toString
in interfaceRedactable
- Overrides:
toString
in classObject
- Returns:
- A string representation of this object including all fields, even sensitive ones.
-
toStringRedacted
- Specified by:
toStringRedacted
in interfaceRedactable
- Returns:
- A string representation of this object including all fields. However, sensitive fields will be removed or replaced with a placeholder. The exact behaviour is implementation-specific and may depend on the debug level currently in effect.
-
copy
- Specified by:
copy
in interfaceCopyable<EMailSender>
- Returns:
- A deep copy of the object, i.e. a clone that has the exact same values as the original object, but is a different instance (new reference).
-
equals
Compares thisEMailSender
with the givenObject
.- Overrides:
equals
in classObject
- Parameters:
object
- TheObject
to compare thisEMailSender
with.- Returns:
-
true
if the given object is anEMailSender
whose fields' values match their equivalents in thisEMailSender
, especially (but not exclusively) if the givenEMailSender
is the exact same reference as thisEMailSender
. -
false
if the given object is not aEMailSender
or if any of the fields' values differ from their equivalents in thisEMailSender
.
-
-
parseBoundary
- Parameters:
contentTypeHeader
- The value of a MIME message's Content-Type header.- Returns:
- The boundary specified in the
given
Content-Type
header, if present. Empty if no boundary was found. - Implementation Note:
- This method is necessary because JavaMail does not support PGP/MIME encryption natively and does not provide a way to manually specify a boundary for the MIME message. Therefore, the boundary must be extracted from the Content-Type header of the JavaMail-generated MIME message.
-