| author | Fabian Huch <huch@in.tum.de> | 
| Fri, 15 Mar 2024 17:57:03 +0100 | |
| changeset 79899 | c73a36081b1c | 
| parent 79444 | 71fde9e76ca9 | 
| child 79979 | a4100b7ab951 | 
| permissions | -rw-r--r-- | 
| 78829 | 1 | /* Title: Pure/General/mail.scala | 
| 2 | Author: Fabian Huch, TU Muenchen | |
| 3 | ||
| 4 | Support for sending text mails via SMTP. | |
| 5 | */ | |
| 6 | ||
| 7 | package isabelle | |
| 8 | ||
| 9 | ||
| 78855 | 10 | import java.util.{Properties => JProperties}
 | 
| 79443 
0d7c7fe65638
update javamail component with current jakarta mail APIs and eclipse angus implementation;
 Fabian Huch <huch@in.tum.de> parents: 
78855diff
changeset | 11 | import jakarta.mail.internet.{InternetAddress, MimeMessage}
 | 
| 
0d7c7fe65638
update javamail component with current jakarta mail APIs and eclipse angus implementation;
 Fabian Huch <huch@in.tum.de> parents: 
78855diff
changeset | 12 | import jakarta.mail.{AuthenticationFailedException, Authenticator, Message, MessagingException,
 | 
| 78855 | 13 | PasswordAuthentication, Transport as JTransport, Session => JSession} | 
| 78829 | 14 | |
| 15 | ||
| 16 | object Mail {
 | |
| 17 | /* validated addresses */ | |
| 18 | ||
| 19 |   final class Address private[Mail](rep: String) {
 | |
| 20 | override def toString: String = rep | |
| 21 | } | |
| 22 | ||
| 23 |   val default_address: Address = address("user@localhost")
 | |
| 24 | def address(s: String): Address = | |
| 25 |     Exn.capture(new InternetAddress(s).validate()) match {
 | |
| 26 | case Exn.Res(_) => new Address(s) | |
| 27 |       case _ => error("Invalid mail address: " + quote(s))
 | |
| 28 | } | |
| 29 | ||
| 30 | ||
| 31 | /* smtp server */ | |
| 32 | ||
| 33 |   enum Transport {
 | |
| 34 | case Plaintext extends Transport | |
| 35 | case SSL(protocol: String = "TLSv1.2") extends Transport | |
| 36 | case STARTTLS extends Transport | |
| 37 | } | |
| 38 | ||
| 39 | class Server ( | |
| 40 | sender: Address, | |
| 41 | smtp_host: String, | |
| 42 | smtp_port: Int = 587, | |
| 43 | user: String = "", | |
| 44 | password: String = "", | |
| 45 | transport: Transport = Transport.SSL() | |
| 46 |   ) {
 | |
| 47 | def use_auth: Boolean = user.nonEmpty && password.nonEmpty | |
| 48 | ||
| 49 |     private def mail_session: JSession = {
 | |
| 50 | val props = new JProperties() | |
| 79444 
71fde9e76ca9
proper SMTP session: set envelope sender address correctly;
 Fabian Huch <huch@in.tum.de> parents: 
79443diff
changeset | 51 |       props.setProperty("mail.smtp.from", sender.toString)
 | 
| 78829 | 52 |       props.setProperty("mail.smtp.host", smtp_host)
 | 
| 53 |       props.setProperty("mail.smtp.port", smtp_port.toString)
 | |
| 54 |       props.setProperty("mail.smtp.auth", use_auth.toString)
 | |
| 55 | ||
| 56 |       transport match {
 | |
| 57 | case Transport.SSL(protocol) => | |
| 58 |           props.setProperty("mail.smtp.ssl.enable", "true")
 | |
| 59 |          props.setProperty("mail.smtp.ssl.protocols", protocol)
 | |
| 60 | case Transport.STARTTLS => | |
| 61 |           props.setProperty("mail.smtp.starttls.enable", "true")
 | |
| 62 | case Transport.Plaintext => | |
| 63 | } | |
| 64 | ||
| 65 |       val authenticator = new Authenticator() {
 | |
| 66 | override def getPasswordAuthentication = new PasswordAuthentication(user, password) | |
| 67 | } | |
| 79444 
71fde9e76ca9
proper SMTP session: set envelope sender address correctly;
 Fabian Huch <huch@in.tum.de> parents: 
79443diff
changeset | 68 | JSession.getInstance(props, authenticator) | 
| 78829 | 69 | } | 
| 70 | ||
| 71 | def defined: Boolean = smtp_host.nonEmpty | |
| 72 |     def check(): Unit = {
 | |
| 73 |       val transport = mail_session.getTransport("smtp")
 | |
| 74 |       try {
 | |
| 75 | transport.connect(smtp_host, smtp_port, user, password) | |
| 76 | transport.close() | |
| 77 | } | |
| 78 |       catch {
 | |
| 79 |         case exn: Throwable => error("Could not connect to SMTP server: " + exn.getMessage)
 | |
| 80 | } | |
| 81 | } | |
| 82 | ||
| 83 |     def send(mail: Mail): Unit = {
 | |
| 84 | val from_address = mail.from_address.getOrElse(sender) | |
| 85 | val from = | |
| 86 | if (mail.from_name.isEmpty) new InternetAddress(from_address.toString) | |
| 87 | else new InternetAddress(from_address.toString, mail.from_name) | |
| 88 | ||
| 89 | val message = new MimeMessage(mail_session) | |
| 90 | message.setFrom(from) | |
| 91 | message.setSender(new InternetAddress(sender.toString)) | |
| 92 | message.setSubject(mail.subject) | |
| 93 | message.setText(mail.content, "UTF-8") | |
| 94 | message.setSentDate(new java.util.Date()) | |
| 95 | ||
| 96 |       for (recipient <- mail.recipients) {
 | |
| 97 | message.addRecipient(Message.RecipientType.TO, new InternetAddress(recipient.toString)) | |
| 98 | } | |
| 99 | ||
| 100 |       try { JTransport.send(message) }
 | |
| 101 |       catch { case exn: Throwable => error("Sending mail failed: " + exn.getMessage) }
 | |
| 102 | } | |
| 103 | } | |
| 104 | } | |
| 105 | ||
| 106 | case class Mail( | |
| 107 | subject: String, | |
| 108 | recipients: List[Mail.Address], | |
| 109 | content: String, | |
| 110 | from_address: Option[Mail.Address] = None, | |
| 111 | from_name: String = "") |