Package org.subethamail.smtp.client

Source Code of org.subethamail.smtp.client.SMTPClient$Response

package org.subethamail.smtp.client;

import java.io.BufferedReader;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.UnknownHostException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.subethamail.smtp.io.DotTerminatedOutputStream;
import org.subethamail.smtp.io.ExtraDotOutputStream;


/**
* A very low level abstraction of the STMP stream which knows how to handle
* the raw protocol for lines, whitespace, etc.
*
* @author Jeff Schnitzer
*/
public class SMTPClient
{
  /** 5 minutes */
  private static final int CONNECT_TIMEOUT = 300 * 1000;

  /** 10 minutes */
  private static final int REPLY_TIMEOUT = 600 * 1000;

  /** */
  private static Logger log = LoggerFactory.getLogger(SMTPClient.class);

  /** the local socket address */
  private SocketAddress bindpoint;

  /** Just for display purposes */
  String hostPort;

  /** The raw socket */
  Socket socket;

  /** */
  BufferedReader reader;

  /** Output streams used for data */
  OutputStream rawOutput;
  /**
   * A stream which wraps {@link #rawOutput} and is used to write out the DOT
   * CR LF terminating sequence in the DATA command, if necessary
   * complementing the message content with a closing CR LF.
   */
  DotTerminatedOutputStream dotTerminatedOutput;
  /**
   * This stream wraps {@link #dotTerminatedOutput} and it does the dot
   * stuffing for the SMTP DATA command.
   */
  ExtraDotOutputStream dataOutput;

  /** Note we bypass this during DATA */
  PrintWriter writer;

  /**
   * Result of an SMTP exchange.
   */
  public static class Response
  {
    int code;
    String message;

    public Response(int code, String text)
    {
      this.code = code;
      this.message = text;
    }

    public int getCode() { return this.code; }
    public String getMessage() { return this.message; }

    public boolean isSuccess()
    {
      return this.code >= 100 && this.code < 400;
    }

    @Override
    public String toString() { return this.code + " " + this.message; }
  }

  /**
   * Creates an unconnected client.
   */
  public SMTPClient()
  {
    // nothing to do
  }

  /**
   * Establishes a connection to host and port.
   *
   * @throws UnknownHostException
   *             if the hostname cannot be resolved
   * @throws IOException
   *             if there is a problem connecting to the port
   */
  public SMTPClient(String host, int port) throws UnknownHostException, IOException
  {
    this(host, port, null);
  }

  /**
   * Establishes a connection to host and port from the specified local socket
   * address.
   *
   * @param bindpoint
   *            the local socket address. If null, the system will pick up an
   *            ephemeral port and a valid local address.
   * @throws UnknownHostException
   *             if the hostname cannot be resolved
   * @throws IOException
   *             if there is a problem connecting to the port
   */
  public SMTPClient(String host, int port, SocketAddress bindpoint) throws UnknownHostException, IOException
  {
    this.bindpoint = bindpoint;
    connect(host, port);
  }

  /**
   * Establishes a connection to host and port.
   *
   * @throws IOException
   *             if there is a problem connecting to the port
   */
  public void connect(String host, int port) throws IOException
  {
    if (socket != null)
      throw new IllegalStateException("Already connected");

    this.hostPort = host + ":" + port;

    if (log.isDebugEnabled())
      log.debug("Connecting to " + this.hostPort);

    this.socket = createSocket();
    this.socket.bind(this.bindpoint);
    this.socket.setSoTimeout(REPLY_TIMEOUT);
    this.socket.connect(new InetSocketAddress(host, port), CONNECT_TIMEOUT);

    try
    {
      this.bindpoint = this.socket.getLocalSocketAddress();

      this.reader = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));

      this.rawOutput = this.socket.getOutputStream();
      this.dotTerminatedOutput = new DotTerminatedOutputStream(this.rawOutput);
      this.dataOutput = new ExtraDotOutputStream(this.dotTerminatedOutput);
      this.writer = new PrintWriter(this.rawOutput, true);
    }
    catch (IOException e)
    {
      close();
      throw e;
    }
  }

  /**
   * Returns a new unconnected socket.
   * <p>
   * Implementation notice for subclasses: This function is called by the
   * constructors which open the connection immediately. In these cases the
   * subclass is not yet initialized, therefore subclasses overriding this
   * function shouldn't use those constructors.
   */
  protected Socket createSocket()
  {
    return new Socket();
  }

  /**
   * @return a nice pretty description of who we are connected to
   */
  public String getHostPort()
  {
    return this.hostPort;
  }

  /**
   * Sends a message to the server, ie "HELO foo.example.com". A newline will
   * be appended to the message.
   *
   * @param msg should not have any newlines
   */
  protected void send(String msg) throws IOException
  {
    if (log.isDebugEnabled())
      log.debug("Client: " + msg);

    // Force \r\n since println() behaves differently on different platforms
    this.writer.print(msg + "\r\n");
    this.writer.flush();
  }

  /**
   * Note that the response text comes back without trailing newlines.
   */
  protected Response receive() throws IOException
  {
    StringBuilder builder = new StringBuilder();
    String line = null;

    boolean done = false;
    do
    {
      line = this.reader.readLine();
      if (line == null)
      {
        if (builder.length() == 0)
          throw new EOFException("Server disconnected unexpectedly, no reply received");
        else
          throw new IOException("Malformed SMTP reply: " + builder);
      }

      if (log.isDebugEnabled())
        log.debug("Server: " + line);

      if (line.length() < 4)
        throw new IOException("Malformed SMTP reply: " + line);
      builder.append(line.substring(4));

      if (line.charAt(3) == '-')
        builder.append('\n');
      else
        done = true;
    }
    while (!done);

    int code;
    String codeString = line.substring(0, 3);
    try
    {
      code = Integer.parseInt(codeString);
    }
    catch (NumberFormatException e)
    {
      throw new IOException("Malformed SMTP reply: " + line, e);
    }

    return new Response(code, builder.toString());
  }

  /**
   * Sends a message to the server, ie "HELO foo.example.com". A newline will
   * be appended to the message.
   *
   * @param msg should not have any newlines
   * @return the response from the server
   */
  public Response sendReceive(String msg) throws IOException
  {
    this.send(msg);
    return this.receive();
  }

  /** If response is not success, throw an exception */
  public void receiveAndCheck() throws IOException, SMTPException
  {
    Response resp = this.receive();
    if (!resp.isSuccess())
      throw new SMTPException(resp);
  }

  /** If response is not success, throw an exception */
  public void sendAndCheck(String msg) throws IOException, SMTPException
  {
    this.send(msg);
    this.receiveAndCheck();
  }

  /** Logs but otherwise ignores errors */
  public void close()
  {
    if (this.socket != null && !this.socket.isClosed())
    {
      try
      {
        this.socket.close();

        if (log.isDebugEnabled())
          log.debug("Closed connection to " + this.hostPort);
      }
      catch (IOException ex)
      {
        log.error("Problem closing connection to " + this.hostPort, ex);
      }
    }
  }

  /** */
  @Override
  public String toString()
  {
    return this.getClass().getSimpleName() + " { " + this.hostPort + "}";
  }

  /**
   * Sets the local socket address. If null, the system will pick up an
   * ephemeral port and a valid local address. Default is null.
   */
  public void setBindpoint(SocketAddress bindpoint)
  {
    this.bindpoint = bindpoint;
  }

  /**
   * Returns the local socket address.
   */
  public SocketAddress getBindpoint()
  {
    return bindpoint;
  }
}
TOP

Related Classes of org.subethamail.smtp.client.SMTPClient$Response

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.