/*
 * Copyright (C) 2002, 2003, 2004  Free Software Foundation, Inc.
 * Copyright (C) 2005 Bastian Blank <bblank@thinkmo.de>
 *
 * 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.
 */

import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;

public class test extends Applet
{
  /**
   * An input stream that filters out CR/LF pairs into LFs.
   *
   * @author Chris Burdess (dog@gnu.org)
   */
  class CRLFInputStream extends FilterInputStream
  {
    /**
     * The CR octet.
     */
    public static final int CR = 13;

    /**
     * The LF octet.
     */
    public static final int LF = 10;

    /**
     * Buffer.
     */
    protected int buf = -1;

    /**
     * Buffer at time of mark.
     */
    protected int markBuf = -1;

    /**
     * Constructs a CR/LF input stream connected to the specified input
     * stream.
     */
    public CRLFInputStream(InputStream in)
    {
      super(in);
    }

    /**
     * Reads the next byte of data from this input stream.
     * Returns -1 if the end of the stream has been reached.
     * @exception IOException if an I/O error occurs
     */
    public int read()
      throws IOException
    {
      int c;
      if (buf != -1)
      {
	c = buf;
	buf = -1;
	return c;
      }
      else
      {
	c = super.read();
	if (c == CR)
	{
	  buf = super.read();
	  if (buf == LF)
	  {
	    c = buf;
	    buf = -1;
	  }
	}
      }
      return c;
    }

    /**
     * Reads up to b.length bytes of data from this input stream into
     * an array of bytes.
     * Returns -1 if the end of the stream has been reached.
     * @exception IOException if an I/O error occurs
     */
    public int read(byte[] b)
      throws IOException
    {
      return read(b, 0, b.length);
    }

    /**
     * Reads up to len bytes of data from this input stream into an
     * array of bytes, starting at the specified offset.
     * Returns -1 if the end of the stream has been reached.
     * @exception IOException if an I/O error occurs
     */
    public int read(byte[] b, int off, int len)
      throws IOException
    {
      int shift = 0;
      if (buf != -1)
      {
	// Push buf onto start of byte array
	b[off] = (byte) buf;
	off++;
	len--;
	buf = -1;
	shift++;
      }
      int l = super.read(b, off, len);
      l = removeCRLF(b, off - shift, l);
      return l;
    }

    /**
     * Indicates whether this stream supports the mark and reset methods.
     */
    public boolean markSupported()
    {
      return in.markSupported();
    }

    /**
     * Marks the current position in this stream.
     */
    public void mark(int readlimit)
    {
      in.mark(readlimit);
      markBuf = buf;
    }

    /**
     * Repositions this stream to the position at the time the mark method was
     * called.
     */
    public void reset()
      throws IOException
    {
      in.reset();
      buf = markBuf;
    }

    private int removeCRLF(byte[] b, int off, int len)
    {
      int end = off + len;
      for (int i = off; i < end; i++)
      {
	if (b[i] == CR)
	{
	  if (i + 1 == end)
	  {
	    // This is the last byte, impossible to determine whether CRLF
	    buf = CR;
	    len--;
	  }
	  else if (b[i + 1] == LF)
	  {
	    // Shift left
	    end--;
	    for (int j = i; j < end; j++)
	    {
	      b[j] = b[j + 1];
	    }
	    len--;
	    end = off + len;
	  }
	}
      }
      return len;
    }
  }

  /** 
   * An output stream that filters LFs into CR/LF pairs.
   *
   * @author Chris Burdess (dog@gnu.org)
   */
  class CRLFOutputStream
      extends FilterOutputStream
    {
      static final String US_ASCII = "US-ASCII";

      /**
       * The CR octet.
       */
      public static final int CR = 13;

      /**
       * The LF octet.
       */
      public static final int LF = 10;

      /**
       * The CR/LF pair.
       */
      public static final byte[] CRLF = { CR, LF };

      /**
       * The last byte read.
       */
      protected int last;

      /**
       * Constructs a CR/LF output stream connected to the specified output stream.
       */
      public CRLFOutputStream(OutputStream out)
      {
	super(out);
	last = -1;
      }

      /**
       * Writes a character to the underlying stream.
       * @exception IOException if an I/O error occurred
       */
      public void write(int ch) throws IOException
      {
	if (ch == CR)
	{
	  out.write(CRLF);
	}
	else if (ch == LF)
	{
	  if (last != CR)
	  {
	    out.write(CRLF);
	  }
	}
	else
	{
	  out.write(ch);
	}
	last = ch;
      }

      /**
       * Writes a byte array to the underlying stream.
       * @exception IOException if an I/O error occurred
       */
      public void write(byte[] b)
	throws IOException
      {
	write(b, 0, b.length);
      }

      /**
       * Writes a portion of a byte array to the underlying stream.
       * @exception IOException if an I/O error occurred
       */
      public void write(byte[] b, int off, int len)
	throws IOException
      {
	int d = off;
	len += off;
	for (int i = off; i < len; i++)
	{
	  switch (b[i])
	  {
	    case CR:
	      out.write (b, d, i - d);
	      out.write (CRLF, 0, 2);
	      d = i + 1;
	      break;
	    case LF:
	      if (last != CR)
	      {
		out.write (b, d, i - d);
		out.write (CRLF, 0, 2);
	      }
	      d = i + 1;
	      break;
	  }
	  last = b[i];
	}
	if (len - d > 0)
	{
	  out.write (b, d, len - d);
	}
      }

      /**
       * Writes the specified ASCII string to the underlying stream.
       * @exception IOException if an I/O error occurred
       */
      public void write(String text)
	throws IOException
      {
	try
	{
	  byte[] bytes = text.getBytes(US_ASCII);
	  write(bytes, 0, bytes.length);
	}
	catch (UnsupportedEncodingException e)
	{
	  throw new IOException("The US-ASCII encoding is not supported " +
	      "on this system");
	}
      }

      /**
       * Writes a newline to the underlying stream.
       * @exception IOException if an I/O error occurred
       */
      public void writeln()
	throws IOException
      {
	out.write(CRLF, 0, 2);
      }
    }

  /**
   * An input stream that can read lines of input.
   *
   * @author Chris Burdess (dog@gnu.org)
   */
  class LineInputStream
      extends FilterInputStream
    {
      /*
       * Line buffer.
       */
      private ByteArrayOutputStream buf;

      /*
       * Encoding to use when translating bytes to characters.
       */
      private String encoding;

      /*
       * End-of-stream flag.
       */
      private boolean eof;

      /**
       * Whether we can use block reads.
       */
      private final boolean blockReads;

      /**
       * Constructor using the US-ASCII character encoding.
       * @param in the underlying input stream
       */
      public LineInputStream(InputStream in)
      {
	this(in, "US-ASCII");
      }

      /**
       * Constructor.
       * @param in the underlying input stream
       * @param encoding the character encoding to use
       */
      public LineInputStream(InputStream in, String encoding)
      {
	super(in);
	buf = new ByteArrayOutputStream();
	this.encoding = encoding;
	eof = false;
	blockReads = in.markSupported();
      }

      /**
       * Read a line of input.
       */
      public String readLine()
	throws IOException
      {
	if (eof)
	{
	  return null;
	}
	do
	{
	  if (blockReads)
	  {
	    // Use mark and reset to read chunks of bytes
	    final int MIN_LENGTH = 1024;
	    int len, pos;

	    len = in.available();
	    len = (len < MIN_LENGTH) ? MIN_LENGTH : len;
	    byte[] b = new byte[len];
	    in.mark(len);
	    // Read into buffer b
	    len = in.read(b, 0, len);
	    // Handle EOF
	    if (len == -1)
	    {
	      eof = true;
	      if (buf.size() == 0)
	      {
		return null;
	      }
	      else
	      {
		// We don't care about resetting buf
		return buf.toString(encoding);
	      }
	    }
	    // Get index of LF in b
	    pos = indexOf(b, len, (byte) 0x0a);
	    if (pos != -1)
	    {
	      // Write pos bytes to buf
	      buf.write(b, 0, pos);
	      // Reset stream, and read pos + 1 bytes
	      in.reset();
	      pos += 1;
	      while (pos > 0)
	      {
		len = in.read(b, 0, pos);
		pos = (len == -1) ? -1 : pos - len;
	      }
	      // Return line
	      String ret = buf.toString(encoding);
	      buf.reset();
	      return ret;
	    }
	    else
	    {
	      // Append everything to buf and fall through to re-read.
	      buf.write(b, 0, len);
	    }
	  }
	  else
	  {
	    // We must use character reads in order not to read too much
	    // from the underlying stream.
	    int c = in.read();
	    switch (c)
	    {
	      case -1:
		eof = true;
		if (buf.size() == 0)
		{
		  return null;
		}
		// Fall through and return contents of buffer.
	      case 0x0a:                // LF
		String ret = buf.toString(encoding);
		buf.reset();
		return ret;
	      default:
		buf.write(c);
	    }
	  }
	}
	while (true);
      }

      private int indexOf(byte[] b, int len, byte c)
      {
	for (int pos = 0; pos < len; pos++)
	{
	  if (b[pos] == c)
	  {
	    return pos;
	  }
	}
	return -1;
      }
    }

  Socket socket;
  TextField field;

  public void init ()
  {
    setLayout (new java.awt.GridLayout (2, 0));

    field = new TextField ();
    Button button = new Button ("Do");

    ActionListener actionListener = new ActionListener ()
    {
      public void actionPerformed (ActionEvent actionEvent)
      {
	int open_port = Integer.parseInt (field.getText ());
	do_ftp_port (getCodeBase ().getHost (), open_port);
      }
    };

    button.addActionListener(actionListener);
    add (field);
    add (button);
    validate ();
  }

  protected void send (CRLFOutputStream w, String cmd) throws IOException
  {
    w.write (cmd);
    w.writeln ();
    w.flush ();
  }

  public void do_ftp_port (String host, int open_port)
  {
    try
    {
      socket = new Socket (host, 21);
      CRLFOutputStream w = new CRLFOutputStream (new BufferedOutputStream (socket.getOutputStream ()));
      LineInputStream r = new LineInputStream (new CRLFInputStream (new BufferedInputStream (socket.getInputStream ())));
      r.readLine ();
      String ip = ((InetSocketAddress) socket.getLocalSocketAddress ()).getAddress ().getHostAddress ().replace ('.', ',');
      String port = Integer.toString ((open_port >> 8) & 0xff) + "," + Integer.toString (open_port & 0xff);
      send (w, "USER anonymous");
      r.readLine ();
      send (w, "PASS anonymous");
      r.readLine ();
      send (w, "PORT " + ip + "," + port);
      r.readLine ();
      send (w, "LIST");
      r.readLine ();
    }
    catch (UnknownHostException e) { return; }
    catch (IOException e) { return; }
  }

  public static void main (String args[])
  {
    test t = new test ();
    t.do_ftp_port (args[0], Integer.parseInt (args[1]));
  }
}
