/*
* Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code 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
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.io.*;
import java.net.*;
/*
* @test
* @bug 4398880
* @summary FTP URL processing modified to conform to RFC 1738
*/
public class FtpURL {
/**
* A class that simulates, on a separate, an FTP server.
*/
private class FtpServer extends Thread {
private ServerSocket server;
private int port;
private boolean done = false;
private boolean portEnabled = true;
private boolean pasvEnabled = true;
private String username;
private String password;
private String cwd;
private String filename;
private String type;
private boolean list = false;
/**
* This Inner class will handle ONE client at a time.
* That's where 99% of the protocol handling is done.
*/
private class FtpServerHandler {
BufferedReader in;
PrintWriter out;
Socket client;
private final int ERROR = 0;
private final int USER = 1;
private final int PASS = 2;
private final int CWD = 3;
private final int CDUP = 4;
private final int PWD = 5;
private final int TYPE = 6;
private final int NOOP = 7;
private final int RETR = 8;
private final int PASV = 9;
private final int PORT = 10;
private final int LIST = 11;
private final int REIN = 12;
private final int QUIT = 13;
private final int STOR = 14;
private final int NLST = 15;
private final int RNFR = 16;
private final int RNTO = 17;
String[] cmds = { "USER", "PASS", "CWD", "CDUP", "PWD", "TYPE",
"NOOP", "RETR", "PASV", "PORT", "LIST", "REIN",
"QUIT", "STOR", "NLST", "RNFR", "RNTO" };
private String arg = null;
private ServerSocket pasv = null;
private int data_port = 0;
private InetAddress data_addr = null;
/**
* Parses a line to match it with one of the supported FTP commands.
* Returns the command number.
*/
private int parseCmd(String cmd) {
if (cmd == null || cmd.length() < 3)
return ERROR;
int blank = cmd.indexOf(' ');
if (blank < 0)
blank = cmd.length();
if (blank < 3)
return ERROR;
String s = cmd.substring(0, blank);
if (cmd.length() > blank+1)
arg = cmd.substring(blank+1, cmd.length());
else
arg = null;
for (int i = 0; i < cmds.length; i++) {
if (s.equalsIgnoreCase(cmds[i]))
return i+1;
}
return ERROR;
}
public FtpServerHandler(Socket cl) {
client = cl;
}
protected boolean isPasvSet() {
if (pasv != null && !pasvEnabled) {
try {
pasv.close();
} catch (IOException ex) {
}
pasv = null;
}
if (pasvEnabled && pasv != null)
return true;
return false;
}
/**
* Open the data socket with the client. This can be the
* result of a "PASV" or "PORT" command.
*/
protected OutputStream getOutDataStream() {
try {
if (isPasvSet()) {
Socket s = pasv.accept();
return s.getOutputStream();
}
if (data_addr != null) {
Socket s = new Socket(data_addr, data_port);
data_addr = null;
data_port = 0;
return s.getOutputStream();
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
protected InputStream getInDataStream() {
try {
if (isPasvSet()) {
Socket s = pasv.accept();
return s.getInputStream();
}
if (data_addr != null) {
Socket s = new Socket(data_addr, data_port);
data_addr = null;
data_port = 0;
return s.getInputStream();
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* Handles the protocol exchange with the client.
*/
public void run() {
boolean done = false;
String str;
int res;
boolean logged = false;
boolean waitpass = false;
try {
in = new BufferedReader(new InputStreamReader(client.getInputStream()));
out = new PrintWriter(client.getOutputStream(), true);
out.println("220 tatooine FTP server (SunOS 5.8) ready.");
} catch (Exception ex) {
return;
}
synchronized (FtpServer.this) {
while (!done) {
try {
str = in.readLine();
res = parseCmd(str);
if ((res > PASS && res != QUIT) && !logged) {
out.println("530 Not logged in.");
continue;
}
switch (res) {
case ERROR:
out.println("500 '" + str + "': command not understood.");
break;
case USER:
if (!logged && !waitpass) {
username = str.substring(5);
password = null;
cwd = null;
if ("user2".equals(username)) {
out.println("230 Guest login ok, access restrictions apply.");
logged = true;
} else {
out.println("331 Password required for " + arg);
waitpass = true;
}
} else {
out.println("503 Bad sequence of commands.");
}
break;
case PASS:
if (!logged && waitpass) {
out.println("230 Guest login ok, access restrictions apply.");
password = str.substring(5);
logged = true;
waitpass = false;
} else
out.println("503 Bad sequence of commands.");
break;
case QUIT:
out.println("221 Goodbye.");
out.flush();
out.close();
if (pasv != null)
pasv.close();
done = true;
break;
case TYPE:
out.println("200 Type set to " + arg + ".");
type = arg;
break;
case CWD:
out.println("250 CWD command successful.");
if (cwd == null)
cwd = str.substring(4);
else
cwd = cwd + "/" + str.substring(4);
break;
case CDUP:
out.println("250 CWD command successful.");
break;
case PWD:
out.println("257 \"" + cwd + "\" is current directory");
break;
case PASV:
if (!pasvEnabled) {
out.println("500 PASV is disabled, use PORT instead.");
continue;
}
try {
if (pasv == null)
pasv = new ServerSocket(0);
int port = pasv.getLocalPort();
out.println("227 Entering Passive Mode (127,0,0,1," +
(port >> 8) + "," + (port & 0xff) +")");
} catch (IOException ssex) {
out.println("425 Can't build data connection: Connection refused.");
}
break;
case PORT:
if (!portEnabled) {
out.println("500 PORT is disabled, use PASV instead");
continue;
}
StringBuffer host;
int i=0, j=4;
while (j>0) {
i = arg.indexOf(',', i+1);
if (i < 0)
break;
j--;
}
if (j != 0) {
out.println("500 '" + arg + "': command not understood.");
continue;
}
try {
host = new StringBuffer(arg.substring(0,i));
for (j=0; j < host.length(); j++)
if (host.charAt(j) == ',')
host.setCharAt(j, '.');
String ports = arg.substring(i+1);
i = ports.indexOf(',');
data_port = Integer.parseInt(ports.substring(0,i)) << 8;
data_port += (Integer.parseInt(ports.substring(i+1)));
data_addr = InetAddress.getByName(host.toString());
out.println("200 Command okay.");
} catch (Exception ex3) {
data_port = 0;
data_addr = null;
out.println("500 '" + arg + "': command not understood.");
}
break;
case RETR:
{
filename = str.substring(5);
OutputStream dout = getOutDataStream();
if (dout != null) {
out.println("200 Command okay.");
PrintWriter pout = new PrintWriter(new BufferedOutputStream(dout));
pout.println("Hello World!");
pout.flush();
pout.close();
list = false;
} else
out.println("425 Can't build data connection: Connection refused.");
}
break;
case NLST:
filename = arg;
case LIST:
{
OutputStream dout = getOutDataStream();
if (dout != null) {
out.println("200 Command okay.");
PrintWriter pout = new PrintWriter(new BufferedOutputStream(dout));
pout.println("total 130");
pout.println("drwxrwxrwt 7 sys sys 577 May 12 03:30 .");
pout.println("drwxr-xr-x 39 root root 1024 Mar 27 12:55 ..");
pout.println("drwxrwxr-x 2 root root 176 Apr 10 12:02 .X11-pipe");
pout.println("drwxrwxr-x 2 root root 176 Apr 10 12:02 .X11-unix");
pout.println("drwxrwxrwx 2 root root 179 Mar 30 15:09 .pcmcia");
pout.println("drwxrwxrwx 2 jladen staff 117 Mar 30 18:18 .removable");
pout.println("drwxrwxrwt 2 root root 327 Mar 30 15:08 .rpc_door");
pout.println("-rw-r--r-- 1 root other 21 May 5 16:59 hello2.txt");
pout.println("-rw-rw-r-- 1 root sys 5968 Mar 30 15:08 ps_data");
pout.flush();
pout.close();
list = true;
try {
FtpServer.this.wait ();
} catch (Exception e) {}
} else
out.println("425 Can't build data connection: Connection refused.");
}
break;
case STOR:
{
InputStream is = getInDataStream();
if (is != null) {
out.println("200 Command okay.");
BufferedInputStream din = new BufferedInputStream(is);
int val;
do {
val = din.read();
} while (val != -1);
din.close();
} else
out.println("425 Can't build data connection: Connection refused.");
}
break;
}
} catch (IOException ioe) {
ioe.printStackTrace();
try {
out.close();
} catch (Exception ex2) {
}
done = true;
}
}
}
}
}
public FtpServer(int port) {
this.port = port;
try {
server = new ServerSocket(port);
} catch (IOException e) {
}
}
public FtpServer() {
this(21);
}
public int getPort() {
if (server != null)
return server.getLocalPort();
return 0;
}
/**
* A way to tell the server that it can stop.
*/
synchronized public void terminate() {
done = true;
}
synchronized public void setPortEnabled(boolean ok) {
portEnabled = ok;
}
synchronized public void setPasvEnabled(boolean ok) {
pasvEnabled = ok;
}
String getUsername() {
return username;
}
String getPassword() {
return password;
}
String pwd() {
return cwd;
}
String getFilename() {
return filename;
}
String getType() {
return type;
}
synchronized boolean getList() {
notify ();
return list;
}
/*
* All we got to do here is create a ServerSocket and wait for connections.
* When a connection happens, we just have to create a thread that will
* handle it.
*/
public void run() {
try {
Socket client;
for (int i=0; i<2; i++) {
client = server.accept();
(new FtpServerHandler(client)).run();
}
} catch(Exception e) {
} finally {
try { server.close(); } catch (IOException unused) {}
}
}
}
public static void main(String[] args) throws Exception {
FtpURL test = new FtpURL();
}
public FtpURL() throws Exception {
FtpServer server = new FtpServer(0);
BufferedReader in = null;
try {
server.start();
int port = server.getPort();
// Now let's check the URL handler
URL url = new URL("ftp://user:password@localhost:" + port + "/%2Fetc/motd;type=a");
URLConnection con = url.openConnection();
in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String s;
do {
s = in.readLine();
} while (s != null);
if (!("user".equals(server.getUsername())))
throw new RuntimeException("Inccorect username received");
if (!("password".equals(server.getPassword())))
throw new RuntimeException("Inccorect password received");
if (!("/etc".equals(server.pwd())))
throw new RuntimeException("Inccorect directory received");
if (!("motd".equals(server.getFilename())))
throw new RuntimeException("Inccorect username received");
if (!("A".equals(server.getType())))
throw new RuntimeException("Incorrect type received");
in.close();
// We're done!
// Second URL test
port = server.getPort();
// Now let's check the URL handler
url = new URL("ftp://user2:@localhost:" + port + "/%2Fusr/bin;type=d");
con = url.openConnection();
in = new BufferedReader(new InputStreamReader(con.getInputStream()));
do {
s = in.readLine();
} while (s != null);
if (!server.getList())
throw new RuntimeException(";type=d didn't generate a NLST");
if (server.getPassword() != null)
throw new RuntimeException("password should be null!");
if (! "bin".equals(server.getFilename()))
throw new RuntimeException("Incorrect filename received");
if (! "/usr".equals(server.pwd()))
throw new RuntimeException("Incorrect pwd received");
// We're done!
} catch (Exception e) {
throw new RuntimeException("FTP support error: " + e.getMessage());
} finally {
try { in.close(); } catch (IOException unused) {}
server.terminate();
server.server.close();
}
}
}