| 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}
 | 
| 78829 |     11 | import javax.mail.internet.{InternetAddress, MimeMessage}
 | 
| 78855 |     12 | import javax.mail.{AuthenticationFailedException, Authenticator, Message, MessagingException,
 | 
|  |     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()
 | 
|  |     51 |       props.setProperty("mail.smtp.host", smtp_host)
 | 
|  |     52 |       props.setProperty("mail.smtp.port", smtp_port.toString)
 | 
|  |     53 |       props.setProperty("mail.smtp.auth", use_auth.toString)
 | 
|  |     54 | 
 | 
|  |     55 |       transport match {
 | 
|  |     56 |         case Transport.SSL(protocol) =>
 | 
|  |     57 |           props.setProperty("mail.smtp.ssl.enable", "true")
 | 
|  |     58 |          props.setProperty("mail.smtp.ssl.protocols", protocol)
 | 
|  |     59 |         case Transport.STARTTLS =>
 | 
|  |     60 |           props.setProperty("mail.smtp.starttls.enable", "true")
 | 
|  |     61 |         case Transport.Plaintext =>
 | 
|  |     62 |       }
 | 
|  |     63 | 
 | 
|  |     64 |       val authenticator = new Authenticator() {
 | 
|  |     65 |         override def getPasswordAuthentication = new PasswordAuthentication(user, password)
 | 
|  |     66 |       }
 | 
|  |     67 |       JSession.getDefaultInstance(props, authenticator)
 | 
|  |     68 |     }
 | 
|  |     69 | 
 | 
|  |     70 |     def defined: Boolean = smtp_host.nonEmpty
 | 
|  |     71 |     def check(): Unit = {
 | 
|  |     72 |       val transport = mail_session.getTransport("smtp")
 | 
|  |     73 |       try {
 | 
|  |     74 |         transport.connect(smtp_host, smtp_port, user, password)
 | 
|  |     75 |         transport.close()
 | 
|  |     76 |       }
 | 
|  |     77 |       catch {
 | 
|  |     78 |         case exn: Throwable => error("Could not connect to SMTP server: " + exn.getMessage)
 | 
|  |     79 |       }
 | 
|  |     80 |     }
 | 
|  |     81 | 
 | 
|  |     82 |     def send(mail: Mail): Unit = {
 | 
|  |     83 |       val from_address = mail.from_address.getOrElse(sender)
 | 
|  |     84 |       val from =
 | 
|  |     85 |         if (mail.from_name.isEmpty) new InternetAddress(from_address.toString)
 | 
|  |     86 |         else new InternetAddress(from_address.toString, mail.from_name)
 | 
|  |     87 | 
 | 
|  |     88 |       val message = new MimeMessage(mail_session)
 | 
|  |     89 |       message.setFrom(from)
 | 
|  |     90 |       message.setSender(new InternetAddress(sender.toString))
 | 
|  |     91 |       message.setSubject(mail.subject)
 | 
|  |     92 |       message.setText(mail.content, "UTF-8")
 | 
|  |     93 |       message.setSentDate(new java.util.Date())
 | 
|  |     94 | 
 | 
|  |     95 |       for (recipient <- mail.recipients) {
 | 
|  |     96 |         message.addRecipient(Message.RecipientType.TO, new InternetAddress(recipient.toString))
 | 
|  |     97 |       }
 | 
|  |     98 | 
 | 
|  |     99 |       try { JTransport.send(message) }
 | 
|  |    100 |       catch { case exn: Throwable => error("Sending mail failed: " + exn.getMessage) }
 | 
|  |    101 |     }
 | 
|  |    102 |   }
 | 
|  |    103 | }
 | 
|  |    104 | 
 | 
|  |    105 | case class Mail(
 | 
|  |    106 |   subject: String,
 | 
|  |    107 |   recipients: List[Mail.Address],
 | 
|  |    108 |   content: String,
 | 
|  |    109 |   from_address: Option[Mail.Address] = None,
 | 
|  |    110 |   from_name: String = "")
 |