/*********************************************************************

 @version 1.2
 @author James Driscoll maus@io.com
 @author Alex Chaffee

 Usage -

 Version History:
 0.8 5/15/96  - First version
 0.9 5/16/96  - fixed date code, added localhost to HELO,
 fixed Subject bug
 0.91 7/10/96  - Yet another date fix, for European TimeZones.  Man, they
 gotta fix that code...
 1.00 7/17/96  - renamed to Qsmtp, as I have plans for the SMTP code,
 and I want to get this out and announced.  Also cleaned it
 up and commented out the DEBUG code (for size, just in case
 the compiler didn't optimize it out on your machine - mine
 didn't (Symantec Cafe Lite, you get what you pay for, and
 I paid for a book)).
 1.01 9/18/96  - Fixed the call to getLocalHost local, which 1.02 JDK didn't
 like (Cafe Lite didn't mind, though).  Think I'll be using
 JDK for all compliations from now on.  Also, added a close
 method, since finalize() is not guarenteed to be called(!).
 1.1 12/26/96 -  Fixed problem with EOL, I was using the Unix EOL, not the
 network end of line.  A fragile mail server was barfing.
 I can't beleive I wrote this - that's what half a year will do.
 Also, yanked out the debug code.  It annoyed me.
 1.11 12/27/97 - Forgot to flush(), println used to do that for me...

 1.2 9/19/98 adc
 - Added headers
 - Multiple recipients possible
 - message can contain headers

 ***********************************************************************/

package com.purpletech.net;

import java.net.*;
import java.io.*;
import java.util.*;

/**
 * SMTP client. Based on code by James Driscoll maus@io.com.
 *
 *  <h1>Qsmtp Class</h1>
 *
 * by <a href="mailto:alex@purpletech.com">Alex Chaffee</a>,
 * <a href="http://www.purpletech.com">Purple Technology</a>
 *
 * <p>
 * This class provides a quick and dirty way to send email from any Java
 * program (including servlets and applets). It is based on the Qsmtp
 * class by Jim Driscoll, but I've added the ability to add multiple
 * recipients and extra headers.
 * </p>
 *
 * <p>
 * Note: You may want to remove the package statement so it will compile
 * correctly inside the root directory of your project.
 * </p>
 *
 * <h2>Files</h2>
 * <p>
 * Download the <a href="http://www.purpletech.com/code/src/com/purpletech/net/Qsmtp.java">source code</a>.
 * </p>
 *
 * <p>
 * <h2>See also:</h2>
 * <ul>
 *
 * <li> Qsmtp by Jim Driscoll - <a href="http://www.io.com/~maus/JavaPage.html">http://www.io.com/~maus/JavaPage.html</a> - and a version rewritten by Alex Chaffee to support multiple recipients and additional headers at <a href="http://www.purpletech.com/code/">http://www.purpletech.com/code/</a>
 * </li>
 *
 * <li>
 * Sun's JavaMail Home Page - <a href="http://www.javasoft.com/products/javamail/index.html">http://www.javasoft.com/products/javamail/index.html</a>
 *
 * <li>
 * JRun Magazine article Intro to JavaMail - <a
 * href="http://jrunmag.com/docs/IntroToJavaMail.html">http://jrunmag.com/docs/IntroToJavaMail.html</a>
 * </li>
 *
 * <li>
 * Servlet Central article that describes (and has code for using) the <code>sun.net.smtp</code>
 * package -
 * <a
 * href="http://www.servletcentral.com/1998-10/wonderful.dchtml">http://www.servletcentral.com/1998-10/wonderful.dchtml</a>
 * </li>
 *
 * <li>
 * IBM Alphaworks SMTP Bean -
 * <a href="http://www.alphaworks.ibm.com/foundry.nsf/system/technologies/D6D9AA48B649C5548825662E00235954?opendocument">http://www.alphaworks.ibm.com/foundry.nsf/system/technologies/D6D9AA48B649C5548825662E00235954?opendocument</a>
 *
 * </ul>
 * </p>
 *
 * @author James Driscoll maus@io.com
 * @author Alex alex@purpletech.com
 **/
public class Qsmtp
{

    static final int DEFAULT_PORT = 25;
    //    static final String EOL = "\n";
    static final String EOL = "\r\n"; // network end of line

    protected String host;

    protected DataInputStream reply = null;
    protected PrintStream send = null;
    protected Socket sock = null;

    protected Dictionary headers = new Hashtable();

    /**
     *   Create a Qsmtp object pointing to the specified host
     *   @param hostid The host to connect to.
     *   @exception UnknownHostException
     *   @exception IOException
     */
    public Qsmtp(String hostid) throws UnknownHostException, IOException
    {
        this(hostid, DEFAULT_PORT);
    }

    public Qsmtp(String hostid, int port) throws UnknownHostException, IOException
    {
        host = hostid;
        sock = new Socket(hostid, port);
        reply = new DataInputStream(sock.getInputStream());
        send = new PrintStream(sock.getOutputStream());
        String rstr = reply.readLine();
        if (!rstr.startsWith("220")) throw new ProtocolException(rstr);
        while (rstr.indexOf('-') == 3)
        {
            rstr = reply.readLine();
            if (!rstr.startsWith("220")) throw new ProtocolException(rstr);
        }

        initHeaders();
    }

    public Qsmtp(InetAddress address) throws IOException
    {
        this(address, DEFAULT_PORT);
    }

    public Qsmtp(InetAddress address, int port) throws IOException
    {
        host = address.getHostAddress();
        sock = new Socket(address, port);
        reply = new DataInputStream(sock.getInputStream());
        send = new PrintStream(sock.getOutputStream());
        String rstr = reply.readLine();
        if (!rstr.startsWith("220")) throw new ProtocolException(rstr);
        while (rstr.indexOf('-') == 3)
        {
            rstr = reply.readLine();
            if (!rstr.startsWith("220")) throw new ProtocolException(rstr);
        }
    }

    public String getHost()
    {
        return host;
    }

    public void initHeaders()
    {
        // Warn the world that we are on the loose - with the comments header:
        headers.put("Comment", "Unauthenticated sender");
        headers.put("X-Mailer", "Java Qsmtp");
    }

    public void setHeader(String name, String value)
    {
        headers.put(name, value);
    }

    public void addTo(String address)
    {
        String to;
        to = (String) headers.get("To");
        if (to == null)
            to = address;
        else
            to = to + ", " + address;
        headers.put("To", to);
    }

    public void sendmsg(String from, String to,
                        String subject, String message)
            throws IOException, ProtocolException
    {
        setHeader("From", from);
        setHeader("To", to);
        setHeader("Subject", subject);
        sendmsg(message);
    }

    public void sendmsg(String message)
            throws IOException, ProtocolException
    {
        String rstr;
        String sstr;

        // Create Date - we'll cheat by assuming that local clock is right
        headers.put("Date", msgDateFormat(new Date()));

        // If the message starts with header lines, parse them
        message = addHeaders(message);

        InetAddress local;
        String host;
        try
        {
            local = InetAddress.getLocalHost();
            host = local.getHostName();
        }
        catch (UnknownHostException ioe)
        {
            System.err.println("No local IP address found - is your network up?");
            host = "localhost";
        }

        sendLine("HELO " + host, "250");

        sendLine("MAIL FROM: <" + headers.get("From") + ">", "250");

        String to = (String) headers.get("To");
        if (to == null || to.equals(""))
            throw new ProtocolException("No recipients! You must specify a 'To' header.");

        StringTokenizer tok = new StringTokenizer(to, ",");
        while (tok.hasMoreTokens())
        {
            String next = (String) tok.nextToken();
            sendLine("RCPT TO: <" + next + ">", "250");
        }

        sendLine("DATA", "354");

        // print the headers
        printHeaders();

        // Sending a blank line ends the header part.
        send.print(EOL);

        // Now send the message proper
        send.print(message);
        if (!message.endsWith("\n"))
            send.print(EOL);

        sendLine(".", "250");
    }

    private void printHeaders()
    {
        Enumeration e = headers.keys();
        while (e.hasMoreElements())
        {
            String header = (String) e.nextElement();
            String value = (String) headers.get(header);
            send.print(header);
            send.print(": ");
            send.print(value);
            send.print(EOL);
        }
    }

    void sendLine(String line, String success)
            throws IOException, ProtocolException
    {
        send.print(line);
        send.print(EOL);
        send.flush();
        String rstr = reply.readLine();
        if (!rstr.startsWith(success)) throw new ProtocolException(rstr);
    }

    String addHeaders(String message)
    {
        try
        {
            BufferedReader in = new BufferedReader
                    (new StringReader(message));
            String line = in.readLine();
            int colon = line.indexOf(':');
            if ((colon != -1) &&
                    (line.charAt(colon + 1) == ' ') &&
                    (line.substring(0, colon).indexOf(' ') == -1))
            {
                // we're in header land

                while (!line.equals(""))
                {
                    colon = line.indexOf(':');
                    if (colon == -1)
                        break;
                    String header = line.substring(0, colon);
                    String value = line.substring(colon + 2);
                    headers.put(header, value);
                    line = in.readLine();
                }
                StringWriter s = new StringWriter();
                PrintWriter out = new PrintWriter(s);
                while ((line = in.readLine()) != null)
                    out.println(line);
                return s.toString();
            }
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        return message;
    }

    public void close()
    {
        try
        {
            send.print("QUIT");
            send.print(EOL);
            send.flush();
            sock.close();
        }
        catch (IOException ioe)
        {
            // As though there's anything I can do about it now...
        }
    }

    protected void finalize() throws Throwable
    {
        this.close();
        super.finalize();
    }

    private String msgDateFormat(Date senddate)
    {
        String formatted = "hold";

        String Day[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
        String Month[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};

        formatted = Day[senddate.getDay()] + ", ";
        formatted = formatted + String.valueOf(senddate.getDate()) + " ";
        formatted = formatted + Month[senddate.getMonth()] + " ";
        if (senddate.getYear() > 99)
            formatted = formatted + String.valueOf(senddate.getYear() + 1900) + " ";
        else
            formatted = formatted + String.valueOf(senddate.getYear()) + " ";
        if (senddate.getHours() < 10) formatted = formatted + "0";
        formatted = formatted + String.valueOf(senddate.getHours()) + ":";
        if (senddate.getMinutes() < 10) formatted = formatted + "0";
        formatted = formatted + String.valueOf(senddate.getMinutes()) + ":";
        if (senddate.getSeconds() < 10) formatted = formatted + "0";
        formatted = formatted + String.valueOf(senddate.getSeconds()) + " ";
        if (senddate.getTimezoneOffset() < 0)
            formatted = formatted + "+";
        else
            formatted = formatted + "-";
        if (Math.abs(senddate.getTimezoneOffset()) / 60 < 10) formatted = formatted + "0";
        formatted = formatted + String.valueOf(Math.abs(senddate.getTimezoneOffset()) / 60);
        if (Math.abs(senddate.getTimezoneOffset()) % 60 < 10) formatted = formatted + "0";
        formatted = formatted + String.valueOf(Math.abs(senddate.getTimezoneOffset()) % 60);

        return formatted;
    }
}
