--- a/etc/build.props Thu Oct 26 11:29:00 2023 +0200
+++ b/etc/build.props Tue Oct 24 19:17:46 2023 +0200
@@ -87,6 +87,7 @@
src/Pure/General/linear_set.scala \
src/Pure/General/logger.scala \
src/Pure/General/long_name.scala \
+ src/Pure/General/mail.scala \
src/Pure/General/mailman.scala \
src/Pure/General/mercurial.scala \
src/Pure/General/multi_map.scala \
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/Pure/General/mail.scala Tue Oct 24 19:17:46 2023 +0200
@@ -0,0 +1,109 @@
+/* Title: Pure/General/mail.scala
+ Author: Fabian Huch, TU Muenchen
+
+Support for sending text mails via SMTP.
+*/
+
+package isabelle
+
+
+import java.util.Properties as JProperties
+import javax.mail.internet.{InternetAddress, MimeMessage}
+import javax.mail.{AuthenticationFailedException, Authenticator, Message, MessagingException, PasswordAuthentication, Transport as JTransport, Session as JSession}
+
+
+object Mail {
+ /* validated addresses */
+
+ final class Address private[Mail](rep: String) {
+ override def toString: String = rep
+ }
+
+ val default_address: Address = address("user@localhost")
+ def address(s: String): Address =
+ Exn.capture(new InternetAddress(s).validate()) match {
+ case Exn.Res(_) => new Address(s)
+ case _ => error("Invalid mail address: " + quote(s))
+ }
+
+
+ /* smtp server */
+
+ enum Transport {
+ case Plaintext extends Transport
+ case SSL(protocol: String = "TLSv1.2") extends Transport
+ case STARTTLS extends Transport
+ }
+
+ class Server (
+ sender: Address,
+ smtp_host: String,
+ smtp_port: Int = 587,
+ user: String = "",
+ password: String = "",
+ transport: Transport = Transport.SSL()
+ ) {
+ def use_auth: Boolean = user.nonEmpty && password.nonEmpty
+
+ private def mail_session: JSession = {
+ val props = new JProperties()
+ props.setProperty("mail.smtp.host", smtp_host)
+ props.setProperty("mail.smtp.port", smtp_port.toString)
+ props.setProperty("mail.smtp.auth", use_auth.toString)
+
+ transport match {
+ case Transport.SSL(protocol) =>
+ props.setProperty("mail.smtp.ssl.enable", "true")
+ props.setProperty("mail.smtp.ssl.protocols", protocol)
+ case Transport.STARTTLS =>
+ props.setProperty("mail.smtp.starttls.enable", "true")
+ case Transport.Plaintext =>
+ }
+
+ val authenticator = new Authenticator() {
+ override def getPasswordAuthentication = new PasswordAuthentication(user, password)
+ }
+ JSession.getDefaultInstance(props, authenticator)
+ }
+
+ def defined: Boolean = smtp_host.nonEmpty
+ def check(): Unit = {
+ val transport = mail_session.getTransport("smtp")
+ try {
+ transport.connect(smtp_host, smtp_port, user, password)
+ transport.close()
+ }
+ catch {
+ case exn: Throwable => error("Could not connect to SMTP server: " + exn.getMessage)
+ }
+ }
+
+ def send(mail: Mail): Unit = {
+ val from_address = mail.from_address.getOrElse(sender)
+ val from =
+ if (mail.from_name.isEmpty) new InternetAddress(from_address.toString)
+ else new InternetAddress(from_address.toString, mail.from_name)
+
+ val message = new MimeMessage(mail_session)
+ message.setFrom(from)
+ message.setSender(new InternetAddress(sender.toString))
+ message.setSubject(mail.subject)
+ message.setText(mail.content, "UTF-8")
+ message.setSentDate(new java.util.Date())
+
+ for (recipient <- mail.recipients) {
+ message.addRecipient(Message.RecipientType.TO, new InternetAddress(recipient.toString))
+ }
+
+ try { JTransport.send(message) }
+ catch { case exn: Throwable => error("Sending mail failed: " + exn.getMessage) }
+ }
+ }
+}
+
+case class Mail(
+ subject: String,
+ recipients: List[Mail.Address],
+ content: String,
+ from_address: Option[Mail.Address] = None,
+ from_name: String = "")