/* * Copyright 2011 Future Systems * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.krakenapps.ftp; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintWriter; import java.net.BindException; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.UnknownHostException; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; import java.util.Vector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class FtpClient { private Logger logger = LoggerFactory.getLogger(FtpClient.class.getName()); private final int BUF_SIZE = 512; private final String CHARSET = "UTF-8"; private Socket socket; private BufferedReader reader; private PrintWriter writer; private Vector<FtpServerMessage> messageLog; private TransferMode transferMode; private ServerSocket servSock; private int minActvPort = 1024; private int maxActvPort = 65535; private int activeTimeout = 5000; public enum TransferMode { Active, Passive; }; public FtpClient(String host) throws UnknownHostException, IOException { this(host, 21); } public FtpClient(String host, int port) throws UnknownHostException, IOException { this.socket = new Socket(host, port); this.reader = new BufferedReader(new InputStreamReader(this.socket.getInputStream(), Charset.forName(CHARSET))); this.writer = new PrintWriter(socket.getOutputStream()); this.messageLog = new Vector<FtpServerMessage>(); if (!getMessage().isCode(220)) throw new IOException("ftp connect failed"); this.transferMode = TransferMode.Passive; } public boolean isConnected() { return socket.isConnected(); } public FtpServerMessage getLastMessage() { if (messageLog.size() == 0) return null; return messageLog.lastElement(); } public boolean login(String user, String password) throws IOException { if (!send("USER " + user).isCode(331)) return false; if (!send("PASS " + password).isCode(230)) return false; return setBinary(); } public String system() throws IOException { return send("SYST").getMessages()[0]; } public boolean setAscii() throws IOException { return send("TYPE A").isCode(200); } public boolean setBinary() throws IOException { return send("TYPE I").isCode(200); } public void setTransferMode(TransferMode transferMode) { this.transferMode = transferMode; } public void setActiveTimeout(int activeTimeout) { this.activeTimeout = activeTimeout; } public void setActvPortRange(int min, int max) { if (min < 1 || min > 65535 || max < 1 || max > 65535) throw new IllegalArgumentException("port out of range"); this.minActvPort = min; this.maxActvPort = max; } public String printWorkingDirectory() throws IOException { FtpServerMessage msg = send("PWD"); if (!msg.isCode(257)) return null; return extract(msg.getMessages()[0], "\""); } public boolean changeWorkingDirectory(String dir) throws IOException { return send("CWD " + dir).isCode(250); } public boolean changeToParentDirectory() throws IOException { return send("CDUP").isCode(200); } public String[] list() throws IOException { Socket data = openDataPort(); List<String> list = new ArrayList<String>(); if (!send("LIST").isCode(150)) return null; if (transferMode.equals(TransferMode.Active)) data = servSock.accept(); BufferedReader reader = new BufferedReader(new InputStreamReader(data.getInputStream(), Charset.forName(CHARSET))); while (reader.ready()) list.add(reader.readLine()); data.close(); if (servSock != null && !servSock.isClosed()) servSock.close(); if (!getMessage().isCode(226)) return null; return list.toArray(new String[list.size()]); } public ListEntry[] mlsd() throws IOException { Socket data = openDataPort(); List<ListEntry> entries = new ArrayList<ListEntry>(); if (!send("MLSD").isCode(150)) return null; if (transferMode.equals(TransferMode.Active)) data = servSock.accept(); BufferedReader reader = new BufferedReader(new InputStreamReader(data.getInputStream(), Charset.forName("UTF-8"))); while (reader.ready()) entries.add(new ListEntry(reader.readLine())); data.close(); if (servSock != null && !servSock.isClosed()) servSock.close(); if (!getMessage().isCode(226)) return null; return entries.toArray(new ListEntry[entries.size()]); } public boolean retrieve(String filename) throws IOException { return retrieve(filename, new File(".")); } public boolean retrieve(String filename, File dir) throws IOException { if (!dir.isDirectory()) return false; Socket data = openDataPort(); if (!send("RETR " + filename).isCode(150)) return false; if (transferMode.equals(TransferMode.Active)) data = servSock.accept(); File file = new File(dir, filename); copy(data.getInputStream(), new FileOutputStream(file)); data.close(); if (servSock != null && !servSock.isClosed()) servSock.close(); if (!getMessage().isCode(226)) return false; return true; } public boolean store(File file) throws IOException { if (!file.exists()) return false; Socket data = openDataPort(); if (!send("STOR " + file.getName()).isCode(150)) return false; if (transferMode.equals(TransferMode.Active)) data = servSock.accept(); copy(new FileInputStream(file), data.getOutputStream()); data.close(); if (servSock != null && !servSock.isClosed()) servSock.close(); if (!getMessage().isCode(226)) return false; return true; } public boolean rename(String from, String to) throws IOException { if (!send("RNFR " + from).isCode(350)) return false; if (!send("RNTO " + to).isCode(250)) return false; return true; } public boolean delete(String file) throws IOException { return send("DELE " + file).isCode(250); } public boolean makeDirectory(String dir) throws IOException { return send("MKD " + dir).isCode(257); } public boolean removeDirectory(String dir) throws IOException { return send("RMD " + dir).isCode(250); } private FtpServerMessage getMessage() throws IOException { if (!socket.isConnected()) throw new IOException("already closed"); FtpServerMessage msg = new FtpServerMessage(reader); messageLog.add(msg); if (msg.getCode() == null || msg.isCode(421)) throw new IOException(msg.toString()); return msg; } private FtpServerMessage send(String msg) throws IOException { if (!socket.isConnected()) throw new IOException("already closed"); if (messageLog.lastElement().isCode(421)) throw new IOException(messageLog.lastElement().toString()); writer.println(msg); writer.flush(); return getMessage(); } private Socket openDataPort() throws IOException { if (transferMode.equals(TransferMode.Active)) return active(); else return passive(); } private Socket active() throws IOException { for (int port = maxActvPort; port > minActvPort; port--) { try { servSock = new ServerSocket(port); servSock.setSoTimeout(activeTimeout); InetAddress local = InetAddress.getLocalHost(); if (socket.getInetAddress().equals(InetAddress.getByName("localhost"))) local = InetAddress.getByName("localhost"); byte[] addr = local.getAddress(); String cmd = String.format("PORT %d,%d,%d,%d,%d,%d", addr[0], addr[1], addr[2], addr[3], port >> 8, port % 256); logger.info(cmd); FtpServerMessage msg = send(cmd); if (!msg.isCode(200)) servSock.close(); return null; } catch (BindException e) { } } return null; } private Socket passive() throws IOException { FtpServerMessage msg = send("PASV"); if (!msg.isCode(227)) return null; String[] addr = extract(msg.getMessages()[0], "(", ")").split(","); InetAddress host = InetAddress.getByAddress(new byte[] { (byte) Integer.parseInt(addr[0]), (byte) Integer.parseInt(addr[1]), (byte) Integer.parseInt(addr[2]), (byte) Integer.parseInt(addr[3]) }); int port = (Integer.parseInt(addr[4]) << 8) + Integer.parseInt(addr[5]); Socket pasv = new Socket(host, port); return pasv; } public void close() { try { send("QUIT"); socket.close(); } catch (IOException e) { } } private String extract(String str, String bracket) { return extract(str, bracket, bracket); } private String extract(String str, String startBracket, String endBracket) { int begin = str.indexOf(startBracket) + 1; int end = str.indexOf(endBracket, begin); return str.substring(begin, end); } private void copy(InputStream is, OutputStream os) { try { byte[] buf = new byte[BUF_SIZE]; while (true) { int len = is.read(buf, 0, buf.length); if (len == -1) break; os.write(buf, 0, len); } } catch (IOException e) { logger.error("kraken-ftp: " + e.getMessage()); } finally { try { os.close(); } catch (IOException e) { logger.error("kraken-ftp: close failed", e); } try { is.close(); } catch (IOException e) { logger.error("kraken-ftp: close failed", e); } } } }