/*
  libvinny - a collection of more or less useful classes
  Copyright (C) 1998, Vincent Partington <vincentp@xs4all.nl>

  This program is free software; you can redistribute it and/or
  modify it under the terms of the GNU General Public License
  as published by the Free Software Foundation; either version 2
  of the License, or (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

  // Patched by IDM
*/

package org.gjt.vinny.beans;

import  java.io.BufferedReader;
import  java.io.BufferedWriter;
import  java.io.InputStreamReader;
import  java.io.IOException;
import  java.io.OutputStreamWriter;
import  java.io.Serializable;
import  java.io.StringReader;
import  java.io.Writer;
import  java.net.InetAddress;
import  java.net.Socket;
import  java.net.UnknownHostException;
import  java.util.StringTokenizer;

/**
 * The <CODE>MailBean</CODE> is a simple bean to send email messages
 * from JSP pages.
 * <P>
 * Using bean introspection and the fact that JSP pages may throw
 * <CODE>IOException</CODE>s, usage can be very simple:
 * <P>
 * <CODE>&lt;BEAN name="mailer" type="nl.nmg.beans.MailBean"&gt;&lt;/BEAN&gt;<BR>
 * &lt;% mailer.send() %&gt;</CODE>
 * <P>
 * For more robust usage, the request parameter would be checked and
 * nice error message written on an <CODE>IOException</CODE>.
 *
 * @author Vincent Partington
 * @version 1.1, 1998/09/12
 */
public class MailBean implements Serializable {
  private String      server, from, to, cc, bcc,
              subject, contentType, body, encoding;

  private int port;

  private static String localHostName = "localhost";

  static {
    try {
      localHostName = InetAddress.getLocalHost().getHostName();
    } catch(UnknownHostException uhexc) {
      //
    }
  }

  /**
   * Allocates a new MailBean. The following properties are set:
   * <P>
   * <UL>
   * <LI>server: <CODE>"mail"</CODE>
   * <LI>from: <CODE>"unknown-sender@somewhere.com"</CODE>
   * <LI>to: <CODE>"unknown-recipient@somewhere.com"</CODE>
   * <LI>cc: <CODE>null</CODE>
   * <LI>subject: <CODE>"&lt;No subject specified&gt;"</CODE>
   * <LI>contentType: <CODE>null</CODE>
   * <LI>body: <CODE>""</CODE>
   * <LI>encoding: <CODE>platform default</CODE>
   * </UL>
   */
  public MailBean() {
    server = "mail";
    from = "unknown-sender@somewhere.com";
    to = "unknown-recipient@somewhere.com";
    cc = null;
    subject = "<No subject specified>";
    contentType = null;
    body = "";
    encoding = null;
    port = 25;
  }

  /**
   * Returns the SMTP server.
   *
   * @return the SMTP server
   */
  public String getServer() {
    return server;
  }

  /**
   * Sets the SMTP server
   *
   * @param server the SMTP server
   */
  public void setServer(String server) {
    if(server != null && server.length() > 0) {
      this.server = server;
    } else {
      this.server = "mail";
    }
  }

  /**
   * Returns the sender of the message.
   *
   * @return the <CODE>From:</CODE> header value
   */
  public String getFrom() {
    return from;
  }

  /**
   * Sets the sender of the message. The sender email
   * address can be specified in one of the following ways:
   * <UL>
   * <LI>john@doe.com
   * <LI>John Doe &lt;john@doe.com&gt;
   * <LI>john@doe.com (John Doe)
   * </UL>
   * <P>
   * If <CODE>from</CODE> is <CODE>null</CODE> or empty, the
   * sender is set to <CODE>"unknown-sender@somewhere.com"</CODE>.
   *
   * @param from the email address of the sender
   */
  public void setFrom(String from) {
    if(from != null && from.length() > 0) {
      this.from = from;
    } else {
      this.from = "unknown-sender@somewhere.com";
    }
  }

  /**
   * Returns the recipients of the message.
   *
   * @return the <CODE>To:</CODE> header value
   */
  public String getTo() {
    return to;
  }

  /**
   * Sets the recipients of the message. The recipient email
   * addresses are separated by comma's and may be specified
   * in one of the following ways:
   * <UL>
   * <LI>john@doe.com
   * <LI>John Doe &lt;john@doe.com&gt;
   * <LI>john@doe.com (John Doe)
   * </UL>
   * <P>
   * If <CODE>to</CODE> is <CODE>null</CODE> or empty, the
   * sender is set to <CODE>"unknown-recipient@somewhere.com"</CODE>.
   *
   * @param the email address of the recipient
   */
  public void setTo(String to) {
    if(to != null && to.length() > 0) {
      this.to = to;
    } else {
      this.to = "unknown-recipient@somewhere.com";
    }
  }

  /**
   * Sets the message body encoding.
   * null means platform default encoding
   */
  public void setEncoding(String enc)
  {
    encoding = enc;
  }

  /**
   * Returns current message body encoding, null if using default
   */
  public String getEncoding()
  {
    return encoding;
  }

  /**
   * Sets the mail server port. 25 is default value
   */
  public void setPort(int port)
  {
    this.port = port;
  }

  /** 
   * Returns the mail server communication port
   */
  public int getPort()
  {
    return port;
  }

  /**
   * Returns the additional recipients of the message.
   *
   * @return the <CODE>Cc:</CODE> header value
   */
  public String getCc() {
    return cc;
  }

  /**
   * Sets the additional recipients of the message. The recipient
   * email addresses are separated by comma's and may be specified
   * in one of the following ways:
   * <UL>
   * <LI>john@doe.com
   * <LI>John Doe &lt;john@doe.com&gt;
   * <LI>john@doe.com (John Doe)
   * </UL>
   *
   * @param the email addresses of the additional recipients
   */
  public void setCc(String cc) {
    if(cc != null && cc.length() > 0) {
      this.cc = cc;
    } else {
      this.cc = null;
    }
  }

  /**
   * Returns the invisible recipients of the message.
   *
   * @return the <CODE>Bcc:</CODE> header value
   *         (which is not actually sent)
   */
  public String getBcc() {
    return bcc;
  }

  /**
   * Sets the invisible recipients of the message. The recipient
   * email addresses are separated by comma's and may be specified
   * in one of the following ways:
   * <UL>
   * <LI>john@doe.com
   * <LI>John Doe &lt;john@doe.com&gt;
   * <LI>john@doe.com (John Doe)
   * </UL>
   *
   * @param the email addresses of the additional recipients
   */
  public void setBcc(String bcc) {
    if(bcc != null && bcc.length() > 0) {
      this.bcc = bcc;
    } else {
      this.bcc = null;
    }
  }

  /**
   * Returns the subject of the message.
   *
   * @return the <CODE>Subject:</CODE> header value
   */
  public String getSubject() {
    return subject;
  }

  /**
   * Sets the subject of the message.
   * <P>
   * If <CODE>subject</CODE> is <CODE>null</CODE> or empty,
   * the subject is set to <CODE>"&lt;No subject specified&gt;"</CODE>.
   *
   * @param subject the subject to set
   */
  public void setSubject(String subject) {
    if(subject != null && subject.length() > 0) {
      this.subject = subject;
    } else {
      this.subject = "<No subject specified>";
    }
  }

  /**
   * Returns the content-type of the message.
   *
   * @return the <CODE>Content-Type:</CODE> header value
   */
  public String getContentType() {
    return contentType;
  }

  /**
   * Sets the content-type of the message.
   *
   * @param contentType the content-type to set
   */
  public void setContentType(String contentType) {
    if(contentType != null && contentType.length() > 0) {
      this.contentType = contentType;
    } else {
      this.contentType = null;
    }
  }

  /**
   * Returns the body of the message.
   *
   * @return the body
   */
  public String getBody() {
    return body;
  }

  /**
   * Sets the body of the message.
   * <P>
   * If <CODE>body</CODE> is <CODE>null</CODE>,
   * the body is set to the empty string.
   *
   * @param body the body to set
   */
  public void setBody(String body) {
    if(body != null) {
      this.body = body;
    } else {
      this.body = "";
    }
  }

  /**
   * Sends the email message.
   *
   * @exception IOException if there was an I/O or SMTP error
   */
  public void send() throws IOException {
    Socket        socket;
    BufferedReader    sockIn;
    Writer        sockOut;
    BufferedReader    bodyIn;
    String        line;

    // make connection to mail server
    socket = new Socket(server, port);
    sockIn = new BufferedReader(new InputStreamReader(socket.getInputStream()));
    try {
      OutputStreamWriter osw = (encoding == null) ? new OutputStreamWriter(socket.getOutputStream()) : new OutputStreamWriter(socket.getOutputStream(),encoding);
      sockOut = new BufferedWriter(osw);
      try {
        checkReply(sockIn);
        sendCommand(sockOut, "HELO " + localHostName);
        checkReply(sockIn);

        // send envelope
        sendCommand(sockOut, "MAIL FROM: " + getRealAddress(from));
        checkReply(sockIn);
        sendRecipients(sockIn, sockOut, to);
        sendRecipients(sockIn, sockOut, cc);
        sendRecipients(sockIn, sockOut, bcc);
        sendCommand(sockOut, "DATA");
        checkReply(sockIn);

        // send message
        writeHeader(sockOut, "From", from);
        writeHeader(sockOut, "To", to);
        if(cc != null) {
          writeHeader(sockOut, "Cc", cc);
        }
        writeHeader(sockOut, "Subject", subject);
        if(contentType != null) {
          writeHeader(sockOut, "MIME-Version", "1.0");
          writeHeader(sockOut, "Content-Type", contentType);
        }
        sockOut.write("\015\012");

        bodyIn = new BufferedReader(new StringReader(body));
        while((line = bodyIn.readLine()) != null) {
          if(line.equals(".")) {
            sockOut.write("..\015\012");
          } else {
            sockOut.write(line);
            sockOut.write("\015\012");
          }
        }
        sockOut.write(".\015\012");
        sockOut.flush();
        checkReply(sockIn);

        sendCommand(sockOut, "QUIT");
      } finally {
        sockOut.close();
      }
    } finally {
      sockIn.close();
    }
  }

  private String getRealAddress(String addr) {
    int     i, j;

    i = addr.indexOf('(');
    if(i != -1) {
      return addr.substring(0, i).trim();
    }

    i = addr.indexOf('<');
    if(i != -1) {
      j = addr.indexOf('>', i);
      if(j != -1) {
        return addr.substring(i+1, j).trim();
      }
    }

    return addr.trim();
  }

  private void sendRecipients(BufferedReader sockIn,
              Writer sockOut, String addrs) throws IOException
  {
    StringTokenizer   toker;
    String        addr;

    if(addrs != null) {
      toker = new StringTokenizer(addrs, ",");
      while(toker.hasMoreTokens()) {
        addr = toker.nextToken().trim();
        if(addr.length() > 0) {
          sendCommand(sockOut, "RCPT TO: " + getRealAddress(addr));
          checkReply(sockIn);
        }
      }
    }
  }

  private void writeHeader(Writer sockOut, String key, String value) throws IOException {
    BufferedReader  valueIn;
    String      line;
    boolean     first = true;

    valueIn = new BufferedReader(new StringReader(value));
    try {
      while((line = valueIn.readLine()) != null) {
        if(first) {
          sockOut.write(key);
          sockOut.write(": ");
          first = false;
        } else {
          sockOut.write("\t");
        }
        sockOut.write(line);
        sockOut.write("\015\012");
      }
    } finally {
      valueIn.close();
    }
  }

  /*
   * These two methods were written by Thornton Rose.  There was no copyright
   * notice on the source, and it was on www.developer.com, so I hope it's
   * cool to use them here.
   */
  private void sendCommand(Writer sockOut, String command) throws IOException {
    sockOut.write(command);
    sockOut.write("\015\012");
    sockOut.flush();
  }

  private void checkReply(BufferedReader sockIn) throws IOException {
    String  reply;
    char  statusCode;

    reply = sockIn.readLine();
    statusCode = reply.charAt(0);

    if(statusCode == '4' || statusCode == '5') {
      throw new IOException("SMTP Error: " + reply);
    }
  }
}
