Package com.google.code.com.sun.mail.pop3

Source Code of com.google.code.com.sun.mail.pop3.SharedByteArrayOutputStream

/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License").  You
* may not use this file except in compliance with the License.  You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt.  See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license."  If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above.  However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/

package com.google.code.com.sun.mail.pop3;

import java.util.*;
import java.net.*;
import java.io.*;
import java.security.*;
import javax.net.ssl.SSLSocket;

import com.google.code.com.sun.mail.util.LineInputStream;
import com.google.code.com.sun.mail.util.SocketFetcher;
import com.google.code.com.sun.mail.util.PropUtil;
import com.google.code.javax.mail.util.SharedByteArrayInputStream;

class Response {
    boolean ok = false;    // true if "+OK"
    String data = null;    // rest of line after "+OK" or "-ERR"
    InputStream bytes = null// all the bytes from a multi-line response
}

/**
* This class provides a POP3 connection and implements
* the POP3 protocol requests.
*
* APOP support courtesy of "chamness".
*
* @author      Bill Shannon
*/
class Protocol {
    private Socket socket;    // POP3 socket
    private String host;    // host we're connected to
    private Properties props;    // session properties
    private String prefix;    // protocol name prefix, for props
    private DataInputStream input;  // input buf
    private PrintWriter output;    // output buf
    private boolean debug = false;
    private PrintStream out;
    private String apopChallenge = null;
    private Map capabilities = null;
    private boolean pipelining;

    private static final int POP3_PORT = 110; // standard POP3 port
    private static final String CRLF = "\r\n";
    // sometimes the returned size isn't quite big enough
    private static final int SLOP = 128;

    /**
     * Open a connection to the POP3 server.
     */
    Protocol(String host, int port, boolean debug, PrintStream out,
      Properties props, String prefix, boolean isSSL)
      throws IOException {
  this.debug = debug;
  this.out = out;
  this.host = host;
  this.props = props;
  this.prefix = prefix;
  Response r;
  boolean enableAPOP = getBoolProp(props, prefix + ".apop.enable");
  boolean disableCapa = getBoolProp(props, prefix + ".disablecapa");
  try {
      if (port == -1)
    port = POP3_PORT;
      if (debug)
    out.println("DEBUG POP3: connecting to host \"" + host +
        "\", port " + port + ", isSSL " + isSSL);

      socket = SocketFetcher.getSocket(host, port, props, prefix, isSSL);
      initStreams();
      r = simpleCommand(null);
  } catch (IOException ioe) {
      try {
    socket.close();
      } finally {
    throw ioe;
      }
  }

  if (!r.ok) {
      try {
    socket.close();
      } finally {
    throw new IOException("Connect failed");
      }
  }
  if (enableAPOP) {
      int challStart = r.data.indexOf('<')// start of challenge
      int challEnd = r.data.indexOf('>', challStart); // end of challenge
      if (challStart != -1 && challEnd != -1)
    apopChallenge = r.data.substring(challStart, challEnd + 1);
      if (debug)
    out.println("DEBUG POP3: APOP challenge: " + apopChallenge);
  }

  // if server supports RFC 2449, set capabilities
  if (!disableCapa)
      setCapabilities(capa());

  pipelining = hasCapability("PIPELINING") ||
      PropUtil.getBooleanProperty(props, prefix + ".pipelining", false);
  if (pipelining && debug)
      out.println("DEBUG POP3: PIPELINING enabled");
    }

    /**
     * Get the value of a boolean property.
     * Print out the value if debug is set.
     */
    private final synchronized boolean getBoolProp(Properties props,
        String prop) {
  boolean val = PropUtil.getBooleanProperty(props, prop, false);
  if (debug)
      out.println("DEBUG POP3: " + prop + ": " + val);
  return val;
    }

    private void initStreams() throws IOException {
  input = new DataInputStream(
      new BufferedInputStream(socket.getInputStream()));
  output = new PrintWriter(
        new BufferedWriter(
      new OutputStreamWriter(socket.getOutputStream(),
          "iso-8859-1")));
          // should be US-ASCII, but not all JDK's support
    }

    protected void finalize() throws Throwable {
  super.finalize();
  if (socket != null) { // Forgot to logout ?!
      quit();
  }
    }

    /**
     * Parse the capabilities from a CAPA response.
     */
    synchronized void setCapabilities(InputStream in) {
  if (in == null) {
      capabilities = null;
      return;
  }

  capabilities = new HashMap(10);
  BufferedReader r = new BufferedReader(new InputStreamReader(in));
  String s;
  try {
      while ((s = r.readLine()) != null) {
    String cap = s;
    int i = cap.indexOf(' ');
    if (i > 0)
        cap = cap.substring(0, i);
    capabilities.put(cap.toUpperCase(Locale.ENGLISH), s);
      }
  } catch (IOException ex) {
      // should never happen
  }
    }

    /**
     * Check whether the given capability is supported by
     * this server. Returns <code>true</code> if so, otherwise
     * returns false.
     */
    synchronized boolean hasCapability(String c) {
  return capabilities != null &&
    capabilities.containsKey(c.toUpperCase(Locale.ENGLISH));
    }

    /**
     * Return the map of capabilities returned by the server.
     */
    synchronized Map getCapabilities() {
  return capabilities;
    }

    /**
     * Login to the server, using the USER and PASS commands.
     */
    synchronized String login(String user, String password)
          throws IOException {
  Response r;
  // only pipeline password if connection is secure
  boolean batch = pipelining && socket instanceof SSLSocket;
  String dpw = null;
  if (apopChallenge != null)
      dpw = getDigest(password);
  if (apopChallenge != null && dpw != null) {
      r = simpleCommand("APOP " + user + " " + dpw);
  } else if (batch) {
      String cmd = "USER " + user;
      batchCommandStart(cmd);
      issueCommand(cmd);
      cmd = "PASS " + password;
      batchCommandContinue(cmd);
      issueCommand(cmd);
      r = readResponse();
      if (!r.ok) {
    String err = r.data != null ? r.data : "USER command failed";
    r = readResponse();
    batchCommandEnd();
    return err;
      }
      r = readResponse();
      batchCommandEnd();
  } else {
      r = simpleCommand("USER " + user);
      if (!r.ok)
    return r.data != null ? r.data : "USER command failed";
      r = simpleCommand("PASS " + password);
  }
  if (!r.ok)
      return r.data != null ? r.data : "login failed";
  return null;
    }

    /**
     * Gets the APOP message digest.
     * From RFC 1939:
     *
     * The 'digest' parameter is calculated by applying the MD5
     * algorithm [RFC1321] to a string consisting of the timestamp
     * (including angle-brackets) followed by a shared secret.
     * The 'digest' parameter itself is a 16-octet value which is
     * sent in hexadecimal format, using lower-case ASCII characters.
     *
     * @param  password  The APOP password
     * @return    The APOP digest or an empty string if an error occurs.
     */
    private String getDigest(String password) {
  String key = apopChallenge + password;
  byte[] digest;
  try {
      MessageDigest md = MessageDigest.getInstance("MD5");
      digest = md.digest(key.getBytes("iso-8859-1"))// XXX
  } catch (NoSuchAlgorithmException nsae) {
      return null;
  } catch (UnsupportedEncodingException uee) {
      return null;
  }
  return toHex(digest);
    }

    private static char[] digits = {
  '0', '1', '2', '3', '4', '5', '6', '7',
  '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
    };

    /**
     * Convert a byte array to a string of hex digits representing the bytes.
     */
    private static String toHex(byte[] bytes) {
  char[] result = new char[bytes.length * 2];

  for (int index = 0, i = 0; index < bytes.length; index++) {
      int temp = bytes[index] & 0xFF;
      result[i++] = digits[temp >> 4];
      result[i++] = digits[temp & 0xF];
  }
  return new String(result);
    }

    /**
     * Close down the connection, sending the QUIT command.
     */
    synchronized boolean quit() throws IOException {
  boolean ok = false;
  try {
      Response r = simpleCommand("QUIT");
      ok = r.ok;
  } finally {
      try {
    socket.close();
      } finally {
    socket = null;
    input = null;
    output = null;
      }
  }
  return ok;
    }

    /**
     * Return the total number of messages and mailbox size,
     * using the STAT command.
     */
    synchronized Status stat() throws IOException {
  Response r = simpleCommand("STAT");
  Status s = new Status();

  /*
   * Normally the STAT command shouldn't fail but apparently it
   * does when accessing Hotmail too often, returning:
   * -ERR login allowed only every 15 minutes
   * (Why it doesn't just fail the login, I don't know.)
   * This is a serious failure that we don't want to hide
   * from the user.
   */
  if (!r.ok)
      throw new IOException("STAT command failed: " + r.data);

  if (r.data != null) {
      try {
    StringTokenizer st = new StringTokenizer(r.data);
    s.total = Integer.parseInt(st.nextToken());
    s.size = Integer.parseInt(st.nextToken());
      } catch (Exception e) {
      }
  }
  return s;
    }

    /**
     * Return the size of the message using the LIST command.
     */
    synchronized int list(int msg) throws IOException {
  Response r = simpleCommand("LIST " + msg);
  int size = -1;
  if (r.ok && r.data != null) {
      try {
    StringTokenizer st = new StringTokenizer(r.data);
    st.nextToken();    // skip message number
    size = Integer.parseInt(st.nextToken());
      } catch (Exception e) {
      }
  }
  return size;
    }

    /**
     * Return the size of all messages using the LIST command.
     */
    synchronized InputStream list() throws IOException {
  Response r = multilineCommand("LIST", 128); // 128 == output size est
  return r.bytes;
    }

    /**
     * Retrieve the specified message.
     * Given an estimate of the message's size we can be more efficient,
     * preallocating the array and returning a SharedInputStream to allow
     * us to share the array.
     */
    synchronized InputStream retr(int msg, int size) throws IOException {
  Response r;
  String cmd;
  boolean batch = size == 0 && pipelining;
  if (batch) {
      cmd = "LIST " + msg;
      batchCommandStart(cmd);
      issueCommand(cmd);
      cmd = "RETR " + msg;
      batchCommandContinue(cmd);
      issueCommand(cmd);
      r = readResponse();
      if (r.ok && r.data != null) {
    // parse the LIST response to get the message size
    try {
        StringTokenizer st = new StringTokenizer(r.data);
        st.nextToken();    // skip message number
        size = Integer.parseInt(st.nextToken());
        // don't allow ridiculous sizes
        if (size > 1024*1024*1024 || size < 0)
      size = 0;
        else {
      if (debug)
          out.println(
        "DEBUG POP3: pipeline message size " + size);
      size += SLOP;
        }
    } catch (Exception e) {
    }
      }
      r = readResponse();
      if (r.ok)
    r.bytes = readMultilineResponse(size + SLOP);
      batchCommandEnd();
  } else {
      cmd = "RETR " + msg;
      multilineCommandStart(cmd);
      issueCommand(cmd);
      r = readResponse();
      if (!r.ok) {
    multilineCommandEnd();
    return null;
      }

      /*
       * Many servers return a response to the RETR command of the form:
       * +OK 832 octets
       * If we don't have a size guess already, try to parse the response
       * for data in that format and use it if found.  It's only a guess,
       * but it might be a good guess.
       */
      if (size <= 0 && r.data != null) {
    try {
        StringTokenizer st = new StringTokenizer(r.data);
        String s = st.nextToken();
        String octets = st.nextToken();
        if (octets != null && octets.equals("octets")) {
      size = Integer.parseInt(s);
      // don't allow ridiculous sizes
      if (size > 1024*1024*1024 || size < 0)
          size = 0;
      else {
          if (debug)
        out.println(
            "DEBUG POP3: guessing message size: " +
          size);
          size += SLOP;
      }
        }
    } catch (Exception e) {
    }
      }
      r.bytes = readMultilineResponse(size);
      multilineCommandEnd();
  }
  if (r.ok) {
      if (debug && size > 0)
    out.println(
        "DEBUG POP3: got message size " + r.bytes.available());
  }
  return r.bytes;
    }

    /**
     * Retrieve the specified message and stream the content to the
     * specified OutputStream.  Return true on success.
     */
    synchronized boolean retr(int msg, OutputStream os) throws IOException {
  String cmd = "RETR " + msg;
  multilineCommandStart(cmd);
  issueCommand(cmd);
  Response r = readResponse();
  if (!r.ok) {
      multilineCommandEnd();
      return false;
  }

  Throwable terr = null;
  int b, lastb = '\n';
  try {
      while ((b = input.read()) >= 0) {
    if (lastb == '\n' && b == '.') {
        if (debug)
      out.write(b);
        b = input.read();
        if (b == '\r') {
      if (debug)
          out.write(b);
      // end of response, consume LF as well
      b = input.read();
      if (debug)
          out.write(b);
      break;
        }
    }

    /*
     * Keep writing unless we get an error while writing,
     * which we defer until all of the data has been read.
     */
    if (terr == null) {
        try {
      os.write(b);
        } catch (IOException ex) {
      if (debug)
          out.println(
        "DEBUG POP3: exception while streaming: " + ex);
      terr = ex;
        } catch (RuntimeException ex) {
      if (debug)
          out.println(
        "DEBUG POP3: exception while streaming: " + ex);
      terr = ex;
        }
    }
    if (debug)
        out.write(b);
    lastb = b;
      }
  } catch (InterruptedIOException iioex) {
      /*
       * As above in simpleCommand, close the socket to recover.
       */
      try {
    socket.close();
      } catch (IOException cex) { }
      throw iioex;
  }
  if (b < 0)
      throw new EOFException("EOF on socket");

  // was there a deferred error?
  if (terr != null) {
      if (terr instanceof IOException)
    throw (IOException)terr;
      if (terr instanceof RuntimeException)
    throw (RuntimeException)terr;
      assert false// can't get here
  }
  multilineCommandEnd();
  return true;
    }

    /**
     * Return the message header and the first n lines of the message.
     */
    synchronized InputStream top(int msg, int n) throws IOException {
  Response r = multilineCommand("TOP " + msg + " " + n, 0);
  return r.bytes;
    }

    /**
     * Delete (permanently) the specified message.
     */
    synchronized boolean dele(int msg) throws IOException {
  Response r = simpleCommand("DELE " + msg);
  return r.ok;
    }

    /**
     * Return the UIDL string for the message.
     */
    synchronized String uidl(int msg) throws IOException {
  Response r = simpleCommand("UIDL " + msg);
  if (!r.ok)
      return null;
  int i = r.data.indexOf(' ');
  if (i > 0)
      return r.data.substring(i + 1);
  else
      return null;
    }

    /**
     * Return the UIDL strings for all messages.
     * The UID for msg #N is returned in uids[N-1].
     */
    synchronized boolean uidl(String[] uids) throws IOException {
  Response r = multilineCommand("UIDL", 15 * uids.length);
  if (!r.ok)
      return false;
  LineInputStream lis = new LineInputStream(r.bytes);
  String line = null;
  while ((line = lis.readLine()) != null) {
      int i = line.indexOf(' ');
      if (i < 1 || i >= line.length())
    continue;
      int n = Integer.parseInt(line.substring(0, i));
      if (n > 0 && n <= uids.length)
    uids[n - 1] = line.substring(i + 1);
  }
  return true;
    }

    /**
     * Do a NOOP.
     */
    synchronized boolean noop() throws IOException {
  Response r = simpleCommand("NOOP");
  return r.ok;
    }

    /**
     * Do an RSET.
     */
    synchronized boolean rset() throws IOException {
  Response r = simpleCommand("RSET");
  return r.ok;
    }

    /**
     * Start TLS using STLS command specified by RFC 2595.
     */
    synchronized boolean stls() throws IOException {
  Response r = simpleCommand("STLS");
  if (r.ok) {
      // it worked, now switch the socket into TLS mode
      try {
    socket = SocketFetcher.startTLS(socket, host, props, prefix);
    initStreams();
      } catch (IOException ioex) {
    try {
        socket.close();
    } finally {
        socket = null;
        input = null;
        output = null;
    }
    IOException sioex =
        new IOException("Could not convert socket to TLS");
    sioex.initCause(ioex);
    throw sioex;
      }
  }
  return r.ok;
    }

    /**
     * Get server capabilities using CAPA command specified by RFC 2449.
     * Returns null if not supported.
     */
    synchronized InputStream capa() throws IOException {
  Response r = multilineCommand("CAPA", 128); // 128 == output size est
  if (!r.ok)
      return null;
  return r.bytes;
    }

    /**
     * Issue a simple POP3 command and return the response.
     */
    private Response simpleCommand(String cmd) throws IOException {
  simpleCommandStart(cmd);
  issueCommand(cmd);
  Response r = readResponse();
  simpleCommandEnd();
  return r;
    }

    /**
     * Send the specified command.
     */
    private void issueCommand(String cmd) throws IOException {
  if (socket == null)
      throw new IOException("Folder is closed")// XXX

  if (cmd != null) {
      if (debug)
    out.println("C: " + cmd);
      cmd += CRLF;
      output.print(cmd)// do it in one write
      output.flush();
  }
    }

    /**
     * Read the response to a command.
     */
    private Response readResponse() throws IOException {
  String line = null;
  try {
      line = input.readLine()// XXX - readLine is deprecated
  } catch (InterruptedIOException iioex) {
      /*
       * If we get a timeout while using the socket, we have no idea
       * what state the connection is in.  The server could still be
       * alive, but slow, and could still be sending data.  The only
       * safe way to recover is to drop the connection.  Later use
       * of the socket should get an EOFException.
       */
      try {
    socket.close();
      } catch (IOException cex) { }
      throw iioex;
  }

  if (line == null) {
      if (debug)
    out.println("S: EOF");
      throw new EOFException("EOF on socket");
  }
  if (debug)
      out.println("S: " + line);
  Response r = new Response();
  if (line.startsWith("+OK"))
      r.ok = true;
  else if (line.startsWith("-ERR"))
      r.ok = false;
  else
      throw new IOException("Unexpected response: " + line);
  int i;
  if ((i = line.indexOf(' ')) >= 0)
      r.data = line.substring(i + 1);
  return r;
    }

    /**
     * Issue a POP3 command that expects a multi-line response.
     * <code>size</code> is an estimate of the response size.
     */
    private Response multilineCommand(String cmd, int size) throws IOException {
  multilineCommandStart(cmd);
  issueCommand(cmd);
  Response r = readResponse();
  if (!r.ok) {
      multilineCommandEnd();
      return r;
  }
  r.bytes = readMultilineResponse(size);
  multilineCommandEnd();
  return r;
    }

    /**
     * Read the response to a multiline command after the command response.
     * The size parameter indicates the expected size of the response;
     * the actual size can be different.  Returns an InputStream to the
     * response bytes.
     */
    private InputStream readMultilineResponse(int size) throws IOException {
  SharedByteArrayOutputStream buf = new SharedByteArrayOutputStream(size);
  int b, lastb = '\n';
  try {
      while ((b = input.read()) >= 0) {
    if (lastb == '\n' && b == '.') {
        if (debug)
      out.write(b);
        b = input.read();
        if (b == '\r') {
      if (debug)
          out.write(b);
      // end of response, consume LF as well
      b = input.read();
      if (debug)
          out.write(b);
      break;
        }
    }
    buf.write(b);
    if (debug)
        out.write(b);
    lastb = b;
      }
  } catch (InterruptedIOException iioex) {
      /*
       * As above in readResponse, close the socket to recover.
       */
      try {
    socket.close();
      } catch (IOException cex) { }
      throw iioex;
  }
  if (b < 0)
      throw new EOFException("EOF on socket");
  return buf.toStream();
    }

    /*
     * Probe points for GlassFish monitoring.
     */
    private void simpleCommandStart(String command) { }
    private void simpleCommandEnd() { }
    private void multilineCommandStart(String command) { }
    private void multilineCommandEnd() { }
    private void batchCommandStart(String command) { }
    private void batchCommandContinue(String command) { }
    private void batchCommandEnd() { }
}

/**
* A ByteArrayOutputStream that allows us to share the byte array
* rather than copy it.  Eventually could replace this with something
* that doesn't require a single contiguous byte array.
*/
class SharedByteArrayOutputStream extends ByteArrayOutputStream {
    public SharedByteArrayOutputStream(int size) {
  super(size);
    }

    public InputStream toStream() {
  return new SharedByteArrayInputStream(buf, 0, count);
    }
}
TOP

Related Classes of com.google.code.com.sun.mail.pop3.SharedByteArrayOutputStream

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.