Friday, July 24, 2009

Command and Data

Just logging in and out to an FTP server is not really interesting, isn't it? I tried the next logical thing one would do once logged in, getting the list of files from the current directory.

os.write("LIST\r\n".getBytes());
// readInputStream . . .

Didn't quite work. Kept getting connection error. I expected the file list would be returned in the input stream like the previous commands I tried. Maybe it had something to do with the active and passive modes in FTP. Googling "FTP passive mode" got me a very good link, Active FTP vs. Passive FTP, a Definitive Explanation.
It became clear after reading that there are 2 ports involved, command and data. The previous commands I tried happen not to have data. Now I understand what RFC 959 means when it says to "listen" on a port. You need to a ServerSocket in order to "listen". Of course, "listening" must be done on a separate thread.
Out of curiosity I also tried the old faithful ftp command line. Passing -d as an argument showed me the raw commands in the background. I quickly discovered that the "ls" client command actually issues PORT before LIST.

final int port = 12345; // just a random port number

Thread t = new Thread(new Runnable(
public void run() {
ServerSocket datalistener = new ServerSocket(port);
Socket data = datalistener.accept();
// readInputStream . . .
data.close();
datalistener.close();
}
)};
t.start();

String ftpclientIP = ftpclient.getLocalAddress()
.getHostAddress().replace('.', ',');
String portargs = ftpclientIP + (port/256) + (port%256);

os.write(("PORT " + portargs + "\r\n").getBytes());
os.write("LIST\r\n".getBytes());

Simple Old Technology

Yesterday I had an idea to write an FTP client in Java. I have always known that this is a simple old piece of technology. Old, but still widely used until now. I don't understand why Sun doesn't have a simple implementation in its Java Standard Edition.
Anyway, I remembered that there was an RFC on FTP. A little bit of googling got me the paper that I wanted, RFC 959. To be honest, it wasn't as simple as I would expect it to be. That didn't stop me from starting coding. The first thing that came to my mind was to create a Socket to an FTP server on port 21 and start sending FTP commands.

Socket ftpclient = new Socket("ftp.someserver.com", 21);
InputStream is = ftpclient.getInputStream();
do {
byte[] b = new byte[1024];
int len = is.read(b);
System.out.println(new String(b, 0, len));
} while (is.available() > 0);

Next, I tried logging in. I quickly found out that you had to end each command with CRLF (\r\n).

OutputStream os = ftpclient.getOutputStream();
os.write("USER anonymous\r\n".getBytes());
// readInputStream . . .
os.write("PASS guest\r\n".getBytes());
// readInputStream . . .
os.write("QUIT\r\n".getBytes());
// readInputStream . . .

Saturday, July 18, 2009

Punish the good, reward the bad

Sydney CityRail ticketing system. Some stations do not have automatic ticket readers. Where I normally catch my train to work is one of them. What's the issue? If you normally buy weekly tickets, it can happen that you forget that your ticket has expired. So there you are travelling happily on the train without realising what is going to happen when you are about to pass the exit reader in a CBD station. Talk to a station attendant and you'll likely to get a fine.
See, if they had installed a reader on every station, you would have to present your ticket on entry. If your weekly ticket has expired, not a problem, just buy a new one.