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 PatternThe regular expression pattern to extract the boundary from the Content-Type header of a MIME message.private @NonNull jakarta.mail.internet.InternetAddressThe student's e-mail address at the university.static final StringThe 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 intprivate char[]The password for the university's e-mail server.private @NonNull PropertiesThe 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.SessionThe e-mail session that is used to send the e-mail notifications.static final StringThe domain name of the university's SMTP server that will be used to send the e-mail notifications.static final intprivate static final @NonNull StringThe user agent header will be specified whensendinge-mails: Thedefaultuser agent for the Friedolyn application. -
Constructor Summary
ConstructorsConstructorDescriptionEMailSender(@NonNull jakarta.mail.internet.InternetAddress email, char[] password) Constructs a newEMailSenderwith the given SMTP credentials for the university's e-mail server. -
Method Summary
Modifier and TypeMethodDescriptionprivate @NonNull jakarta.mail.internet.MimeMessageconstructEmail(@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 newMimeMessagefrom the specified content and metadata.private @NonNull jakarta.mail.internet.MimeMultipartconstructMultipart(String message, File... attachments) Constructs a MIME multipart from the given e-mail message and files attached.@NonNull EMailSendercopy()private static @NonNull FileencryptMessage(@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 FileencryptMessage(@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.booleanCompares thisEMailSenderwith the givenObject.private voidInitialises thepropertieswith the default settings for sending e-mails via the university's SMTP server.parseBoundary(@NonNull String contentTypeHeader) private voidsaveEmailToSentFolder(@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 thesenderusing thepasswordspecified in theconstructorto log in at the university'sSMTP server.@NonNull EMailSender.SentEmailsend(@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 thesenderusing thepasswordspecified in theconstructorto log in at the university'sSMTP server.@NonNull EMailSender.SentEmailsend(@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 thesenderusing thepasswordspecified in theconstructorto log in at the university'sSMTP server.@NonNull StringtoString()@NonNull Stringprivate 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 whensendinge-mails: Thedefaultuser agent for the Friedolyn application.
-
-
Constructor Details
-
EMailSender
public EMailSender(@NonNull @NonNull jakarta.mail.internet.InternetAddress email, char[] password) Constructs a newEMailSenderwith 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 thepropertieswith 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 thesenderusing thepasswordspecified in theconstructorto 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
.emlfile. - 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 thesenderusing thepasswordspecified in theconstructorto 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 toencryptthe 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
.emlfile.-
unencryptedEmail: A copy of the e-mail before encryption or (if sent unencrypted) the exact e-mail as it was sent. Empty iffallBackToUnencryptedisfalse. -
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 thesenderusing thepasswordspecified in theconstructorto 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 toencryptthe 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
.emlfile.-
unencryptedEmail: A copy of the e-mail before encryption or (if sent unencrypted) the exact e-mail as it was sent. Empty iffallBackToUnencryptedisfalse. -
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 namesare 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 newMimeMessagefrom 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
Filewith the nameencrypted.ascin 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-Typeheader of the MIME message.- Returns:
- The encrypted message as a
Filewith the nameencrypted.ascin 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
writtenout -
The message could not be written to the
encrypted.ascPGP/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
MimeMessages but only theirMimeMultipartparts and have to construct the beginning of theMimeMultipartourselves. 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:
toStringin interfaceRedactable- Overrides:
toStringin classObject- Returns:
- A string representation of this object including all fields, even sensitive ones.
-
toStringRedacted
- Specified by:
toStringRedactedin 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:
copyin 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 thisEMailSenderwith the givenObject.- Overrides:
equalsin classObject- Parameters:
object- TheObjectto compare thisEMailSenderwith.- Returns:
-
trueif the given object is anEMailSenderwhose fields' values match their equivalents in thisEMailSender, especially (but not exclusively) if the givenEMailSenderis the exact same reference as thisEMailSender. -
falseif the given object is not aEMailSenderor 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-Typeheader, 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.
-