Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/sun/net/ftp/impl/FtpClient.java
38923 views
/*1* Copyright (c) 2009, 2017, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/24package sun.net.ftp.impl;2526import java.net.*;27import java.io.*;28import java.security.AccessController;29import java.security.PrivilegedAction;30import java.text.DateFormat;31import java.text.ParseException;32import java.text.SimpleDateFormat;33import java.util.ArrayList;34import java.util.Calendar;35import java.util.Date;36import java.util.Iterator;37import java.util.List;38import java.util.TimeZone;39import java.util.Vector;40import java.util.regex.Matcher;41import java.util.regex.Pattern;42import javax.net.ssl.SSLSocket;43import javax.net.ssl.SSLSocketFactory;44import sun.misc.BASE64Decoder;45import sun.misc.BASE64Encoder;46import sun.net.ftp.*;47import sun.util.logging.PlatformLogger;484950public class FtpClient extends sun.net.ftp.FtpClient {5152private static int defaultSoTimeout;53private static int defaultConnectTimeout;54private static final PlatformLogger logger =55PlatformLogger.getLogger("sun.net.ftp.FtpClient");56private Proxy proxy;57private Socket server;58private PrintStream out;59private InputStream in;60private int readTimeout = -1;61private int connectTimeout = -1;6263/* Name of encoding to use for output */64private static String encoding = "ISO8859_1";65/** remember the ftp server name because we may need it */66private InetSocketAddress serverAddr;67private boolean replyPending = false;68private boolean loggedIn = false;69private boolean useCrypto = false;70private SSLSocketFactory sslFact;71private Socket oldSocket;72/** Array of strings (usually 1 entry) for the last reply from the server. */73private Vector<String> serverResponse = new Vector<String>(1);74/** The last reply code from the ftp daemon. */75private FtpReplyCode lastReplyCode = null;76/** Welcome message from the server, if any. */77private String welcomeMsg;78/**79* Only passive mode used in JDK. See Bug 8010784.80*/81private final boolean passiveMode = true;82private TransferType type = TransferType.BINARY;83private long restartOffset = 0;84private long lastTransSize = -1; // -1 means 'unknown size'85private String lastFileName;86/**87* Static members used by the parser88*/89private static String[] patStrings = {90// drwxr-xr-x 1 user01 ftp 512 Jan 29 23:32 prog91"([\\-ld](?:[r\\-][w\\-][x\\-]){3})\\s*\\d+ (\\w+)\\s*(\\w+)\\s*(\\d+)\\s*([A-Z][a-z][a-z]\\s*\\d+)\\s*(\\d\\d:\\d\\d)\\s*(\\p{Print}*)",92// drwxr-xr-x 1 user01 ftp 512 Jan 29 1997 prog93"([\\-ld](?:[r\\-][w\\-][x\\-]){3})\\s*\\d+ (\\w+)\\s*(\\w+)\\s*(\\d+)\\s*([A-Z][a-z][a-z]\\s*\\d+)\\s*(\\d{4})\\s*(\\p{Print}*)",94// 04/28/2006 09:12a 3,563 genBuffer.sh95"(\\d{2}/\\d{2}/\\d{4})\\s*(\\d{2}:\\d{2}[ap])\\s*((?:[0-9,]+)|(?:<DIR>))\\s*(\\p{Graph}*)",96// 01-29-97 11:32PM <DIR> prog97"(\\d{2}-\\d{2}-\\d{2})\\s*(\\d{2}:\\d{2}[AP]M)\\s*((?:[0-9,]+)|(?:<DIR>))\\s*(\\p{Graph}*)"98};99private static int[][] patternGroups = {100// 0 - file, 1 - size, 2 - date, 3 - time, 4 - year, 5 - permissions,101// 6 - user, 7 - group102{7, 4, 5, 6, 0, 1, 2, 3},103{7, 4, 5, 0, 6, 1, 2, 3},104{4, 3, 1, 2, 0, 0, 0, 0},105{4, 3, 1, 2, 0, 0, 0, 0}};106private static Pattern[] patterns;107private static Pattern linkp = Pattern.compile("(\\p{Print}+) \\-\\> (\\p{Print}+)$");108private DateFormat df = DateFormat.getDateInstance(DateFormat.MEDIUM, java.util.Locale.US);109110static {111final int vals[] = {0, 0};112final String encs[] = {null};113114AccessController.doPrivileged(115new PrivilegedAction<Object>() {116117public Object run() {118vals[0] = Integer.getInteger("sun.net.client.defaultReadTimeout", 300_000).intValue();119vals[1] = Integer.getInteger("sun.net.client.defaultConnectTimeout", 300_000).intValue();120encs[0] = System.getProperty("file.encoding", "ISO8859_1");121return null;122}123});124if (vals[0] == 0) {125defaultSoTimeout = -1;126} else {127defaultSoTimeout = vals[0];128}129130if (vals[1] == 0) {131defaultConnectTimeout = -1;132} else {133defaultConnectTimeout = vals[1];134}135136encoding = encs[0];137try {138if (!isASCIISuperset(encoding)) {139encoding = "ISO8859_1";140}141} catch (Exception e) {142encoding = "ISO8859_1";143}144145patterns = new Pattern[patStrings.length];146for (int i = 0; i < patStrings.length; i++) {147patterns[i] = Pattern.compile(patStrings[i]);148}149}150151/**152* Test the named character encoding to verify that it converts ASCII153* characters correctly. We have to use an ASCII based encoding, or else154* the NetworkClients will not work correctly in EBCDIC based systems.155* However, we cannot just use ASCII or ISO8859_1 universally, because in156* Asian locales, non-ASCII characters may be embedded in otherwise157* ASCII based protocols (eg. HTTP). The specifications (RFC2616, 2398)158* are a little ambiguous in this matter. For instance, RFC2398 [part 2.1]159* says that the HTTP request URI should be escaped using a defined160* mechanism, but there is no way to specify in the escaped string what161* the original character set is. It is not correct to assume that162* UTF-8 is always used (as in URLs in HTML 4.0). For this reason,163* until the specifications are updated to deal with this issue more164* comprehensively, and more importantly, HTTP servers are known to165* support these mechanisms, we will maintain the current behavior166* where it is possible to send non-ASCII characters in their original167* unescaped form.168*/169private static boolean isASCIISuperset(String encoding) throws Exception {170String chkS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" +171"abcdefghijklmnopqrstuvwxyz-_.!~*'();/?:@&=+$,";172173// Expected byte sequence for string above174byte[] chkB = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 71, 72,17573, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99,176100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114,177115, 116, 117, 118, 119, 120, 121, 122, 45, 95, 46, 33, 126, 42, 39, 40, 41, 59,17847, 63, 58, 64, 38, 61, 43, 36, 44};179180byte[] b = chkS.getBytes(encoding);181return java.util.Arrays.equals(b, chkB);182}183184private class DefaultParser implements FtpDirParser {185186/**187* Possible patterns:188*189* drwxr-xr-x 1 user01 ftp 512 Jan 29 23:32 prog190* drwxr-xr-x 1 user01 ftp 512 Jan 29 1997 prog191* drwxr-xr-x 1 1 1 512 Jan 29 23:32 prog192* lrwxr-xr-x 1 user01 ftp 512 Jan 29 23:32 prog -> prog2000193* drwxr-xr-x 1 username ftp 512 Jan 29 23:32 prog194* -rw-r--r-- 1 jcc staff 105009 Feb 3 15:05 test.1195*196* 01-29-97 11:32PM <DIR> prog197* 04/28/2006 09:12a 3,563 genBuffer.sh198*199* drwxr-xr-x folder 0 Jan 29 23:32 prog200*201* 0 DIR 01-29-97 23:32 PROG202*/203private DefaultParser() {204}205206public FtpDirEntry parseLine(String line) {207String fdate = null;208String fsize = null;209String time = null;210String filename = null;211String permstring = null;212String username = null;213String groupname = null;214boolean dir = false;215Calendar now = Calendar.getInstance();216int year = now.get(Calendar.YEAR);217218Matcher m = null;219for (int j = 0; j < patterns.length; j++) {220m = patterns[j].matcher(line);221if (m.find()) {222// 0 - file, 1 - size, 2 - date, 3 - time, 4 - year,223// 5 - permissions, 6 - user, 7 - group224filename = m.group(patternGroups[j][0]);225fsize = m.group(patternGroups[j][1]);226fdate = m.group(patternGroups[j][2]);227if (patternGroups[j][4] > 0) {228fdate += (", " + m.group(patternGroups[j][4]));229} else if (patternGroups[j][3] > 0) {230fdate += (", " + String.valueOf(year));231}232if (patternGroups[j][3] > 0) {233time = m.group(patternGroups[j][3]);234}235if (patternGroups[j][5] > 0) {236permstring = m.group(patternGroups[j][5]);237dir = permstring.startsWith("d");238}239if (patternGroups[j][6] > 0) {240username = m.group(patternGroups[j][6]);241}242if (patternGroups[j][7] > 0) {243groupname = m.group(patternGroups[j][7]);244}245// Old DOS format246if ("<DIR>".equals(fsize)) {247dir = true;248fsize = null;249}250}251}252253if (filename != null) {254Date d;255try {256d = df.parse(fdate);257} catch (Exception e) {258d = null;259}260if (d != null && time != null) {261int c = time.indexOf(":");262now.setTime(d);263now.set(Calendar.HOUR, Integer.parseInt(time.substring(0, c)));264now.set(Calendar.MINUTE, Integer.parseInt(time.substring(c + 1)));265d = now.getTime();266}267// see if it's a symbolic link, i.e. the name if followed268// by a -> and a path269Matcher m2 = linkp.matcher(filename);270if (m2.find()) {271// Keep only the name then272filename = m2.group(1);273}274boolean[][] perms = new boolean[3][3];275for (int i = 0; i < 3; i++) {276for (int j = 0; j < 3; j++) {277perms[i][j] = (permstring.charAt((i * 3) + j) != '-');278}279}280FtpDirEntry file = new FtpDirEntry(filename);281file.setUser(username).setGroup(groupname);282file.setSize(Long.parseLong(fsize)).setLastModified(d);283file.setPermissions(perms);284file.setType(dir ? FtpDirEntry.Type.DIR : (line.charAt(0) == 'l' ? FtpDirEntry.Type.LINK : FtpDirEntry.Type.FILE));285return file;286}287return null;288}289}290291private class MLSxParser implements FtpDirParser {292293private SimpleDateFormat df = new SimpleDateFormat("yyyyMMddhhmmss");294295public FtpDirEntry parseLine(String line) {296String name = null;297int i = line.lastIndexOf(";");298if (i > 0) {299name = line.substring(i + 1).trim();300line = line.substring(0, i);301} else {302name = line.trim();303line = "";304}305FtpDirEntry file = new FtpDirEntry(name);306while (!line.isEmpty()) {307String s;308i = line.indexOf(";");309if (i > 0) {310s = line.substring(0, i);311line = line.substring(i + 1);312} else {313s = line;314line = "";315}316i = s.indexOf("=");317if (i > 0) {318String fact = s.substring(0, i);319String value = s.substring(i + 1);320file.addFact(fact, value);321}322}323String s = file.getFact("Size");324if (s != null) {325file.setSize(Long.parseLong(s));326}327s = file.getFact("Modify");328if (s != null) {329Date d = null;330try {331d = df.parse(s);332} catch (ParseException ex) {333}334if (d != null) {335file.setLastModified(d);336}337}338s = file.getFact("Create");339if (s != null) {340Date d = null;341try {342d = df.parse(s);343} catch (ParseException ex) {344}345if (d != null) {346file.setCreated(d);347}348}349s = file.getFact("Type");350if (s != null) {351if (s.equalsIgnoreCase("file")) {352file.setType(FtpDirEntry.Type.FILE);353}354if (s.equalsIgnoreCase("dir")) {355file.setType(FtpDirEntry.Type.DIR);356}357if (s.equalsIgnoreCase("cdir")) {358file.setType(FtpDirEntry.Type.CDIR);359}360if (s.equalsIgnoreCase("pdir")) {361file.setType(FtpDirEntry.Type.PDIR);362}363}364return file;365}366};367private FtpDirParser parser = new DefaultParser();368private FtpDirParser mlsxParser = new MLSxParser();369private static Pattern transPat = null;370371private void getTransferSize() {372lastTransSize = -1;373/**374* If it's a start of data transfer response, let's try to extract375* the size from the response string. Usually it looks like that:376*377* 150 Opening BINARY mode data connection for foo (6701 bytes).378*/379String response = getLastResponseString();380if (transPat == null) {381transPat = Pattern.compile("150 Opening .*\\((\\d+) bytes\\).");382}383Matcher m = transPat.matcher(response);384if (m.find()) {385String s = m.group(1);386lastTransSize = Long.parseLong(s);387}388}389390/**391* extract the created file name from the response string:392* 226 Transfer complete (unique file name:toto.txt.1).393* Usually happens when a STOU (store unique) command had been issued.394*/395private void getTransferName() {396lastFileName = null;397String response = getLastResponseString();398int i = response.indexOf("unique file name:");399int e = response.lastIndexOf(')');400if (i >= 0) {401i += 17; // Length of "unique file name:"402lastFileName = response.substring(i, e);403}404}405406/**407* Pulls the response from the server and returns the code as a408* number. Returns -1 on failure.409*/410private int readServerResponse() throws IOException {411StringBuffer replyBuf = new StringBuffer(32);412int c;413int continuingCode = -1;414int code;415String response;416417serverResponse.setSize(0);418while (true) {419while ((c = in.read()) != -1) {420if (c == '\r') {421if ((c = in.read()) != '\n') {422replyBuf.append('\r');423}424}425replyBuf.append((char) c);426if (c == '\n') {427break;428}429}430response = replyBuf.toString();431replyBuf.setLength(0);432if (logger.isLoggable(PlatformLogger.Level.FINEST)) {433logger.finest("Server [" + serverAddr + "] --> " + response);434}435436if (response.length() == 0) {437code = -1;438} else {439try {440code = Integer.parseInt(response.substring(0, 3));441} catch (NumberFormatException e) {442code = -1;443} catch (StringIndexOutOfBoundsException e) {444/* this line doesn't contain a response code, so445we just completely ignore it */446continue;447}448}449serverResponse.addElement(response);450if (continuingCode != -1) {451/* we've seen a ###- sequence */452if (code != continuingCode ||453(response.length() >= 4 && response.charAt(3) == '-')) {454continue;455} else {456/* seen the end of code sequence */457continuingCode = -1;458break;459}460} else if (response.length() >= 4 && response.charAt(3) == '-') {461continuingCode = code;462continue;463} else {464break;465}466}467468return code;469}470471/** Sends command <i>cmd</i> to the server. */472private void sendServer(String cmd) {473out.print(cmd);474if (logger.isLoggable(PlatformLogger.Level.FINEST)) {475logger.finest("Server [" + serverAddr + "] <-- " + cmd);476}477}478479/** converts the server response into a string. */480private String getResponseString() {481return serverResponse.elementAt(0);482}483484/** Returns all server response strings. */485private Vector<String> getResponseStrings() {486return serverResponse;487}488489/**490* Read the reply from the FTP server.491*492* @return <code>true</code> if the command was successful493* @throws IOException if an error occurred494*/495private boolean readReply() throws IOException {496lastReplyCode = FtpReplyCode.find(readServerResponse());497498if (lastReplyCode.isPositivePreliminary()) {499replyPending = true;500return true;501}502if (lastReplyCode.isPositiveCompletion() || lastReplyCode.isPositiveIntermediate()) {503if (lastReplyCode == FtpReplyCode.CLOSING_DATA_CONNECTION) {504getTransferName();505}506return true;507}508return false;509}510511/**512* Sends a command to the FTP server and returns the error code513* (which can be a "success") sent by the server.514*515* @param cmd516* @return <code>true</code> if the command was successful517* @throws IOException518*/519private boolean issueCommand(String cmd) throws IOException,520sun.net.ftp.FtpProtocolException {521if (!isConnected()) {522throw new IllegalStateException("Not connected");523}524if (replyPending) {525try {526completePending();527} catch (sun.net.ftp.FtpProtocolException e) {528// ignore...529}530}531if (cmd.indexOf('\n') != -1) {532sun.net.ftp.FtpProtocolException ex533= new sun.net.ftp.FtpProtocolException("Illegal FTP command");534ex.initCause(new IllegalArgumentException("Illegal carriage return"));535throw ex;536}537sendServer(cmd + "\r\n");538return readReply();539}540541/**542* Send a command to the FTP server and check for success.543*544* @param cmd String containing the command545*546* @throws FtpProtocolException if an error occurred547*/548private void issueCommandCheck(String cmd) throws sun.net.ftp.FtpProtocolException, IOException {549if (!issueCommand(cmd)) {550throw new sun.net.ftp.FtpProtocolException(cmd + ":" + getResponseString(), getLastReplyCode());551}552}553private static Pattern epsvPat = null;554private static Pattern pasvPat = null;555556/**557* Opens a "PASSIVE" connection with the server and returns the connected558* <code>Socket</code>.559*560* @return the connected <code>Socket</code>561* @throws IOException if the connection was unsuccessful.562*/563private Socket openPassiveDataConnection(String cmd) throws sun.net.ftp.FtpProtocolException, IOException {564String serverAnswer;565int port;566InetSocketAddress dest = null;567568/**569* Here is the idea:570*571* - First we want to try the new (and IPv6 compatible) EPSV command572* But since we want to be nice with NAT software, we'll issue the573* EPSV ALL command first.574* EPSV is documented in RFC2428575* - If EPSV fails, then we fall back to the older, yet ok, PASV576* - If PASV fails as well, then we throw an exception and the calling577* method will have to try the EPRT or PORT command578*/579if (issueCommand("EPSV ALL")) {580// We can safely use EPSV commands581issueCommandCheck("EPSV");582serverAnswer = getResponseString();583584// The response string from a EPSV command will contain the port number585// the format will be :586// 229 Entering Extended PASSIVE Mode (|||58210|)587//588// So we'll use the regular expresions package to parse the output.589590if (epsvPat == null) {591epsvPat = Pattern.compile("^229 .* \\(\\|\\|\\|(\\d+)\\|\\)");592}593Matcher m = epsvPat.matcher(serverAnswer);594if (!m.find()) {595throw new sun.net.ftp.FtpProtocolException("EPSV failed : " + serverAnswer);596}597// Yay! Let's extract the port number598String s = m.group(1);599port = Integer.parseInt(s);600InetAddress add = server.getInetAddress();601if (add != null) {602dest = new InetSocketAddress(add, port);603} else {604// This means we used an Unresolved address to connect in605// the first place. Most likely because the proxy is doing606// the name resolution for us, so let's keep using unresolved607// address.608dest = InetSocketAddress.createUnresolved(serverAddr.getHostName(), port);609}610} else {611// EPSV ALL failed, so Let's try the regular PASV cmd612issueCommandCheck("PASV");613serverAnswer = getResponseString();614615// Let's parse the response String to get the IP & port to connect616// to. The String should be in the following format :617//618// 227 Entering PASSIVE Mode (A1,A2,A3,A4,p1,p2)619//620// Note that the two parenthesis are optional621//622// The IP address is A1.A2.A3.A4 and the port is p1 * 256 + p2623//624// The regular expression is a bit more complex this time, because625// the parenthesis are optionals and we have to use 3 groups.626627if (pasvPat == null) {628pasvPat = Pattern.compile("227 .* \\(?(\\d{1,3},\\d{1,3},\\d{1,3},\\d{1,3}),(\\d{1,3}),(\\d{1,3})\\)?");629}630Matcher m = pasvPat.matcher(serverAnswer);631if (!m.find()) {632throw new sun.net.ftp.FtpProtocolException("PASV failed : " + serverAnswer);633}634// Get port number out of group 2 & 3635port = Integer.parseInt(m.group(3)) + (Integer.parseInt(m.group(2)) << 8);636// IP address is simple637String s = m.group(1).replace(',', '.');638dest = new InetSocketAddress(s, port);639}640// Got everything, let's open the socket!641Socket s;642if (proxy != null) {643if (proxy.type() == Proxy.Type.SOCKS) {644s = AccessController.doPrivileged(645new PrivilegedAction<Socket>() {646647public Socket run() {648return new Socket(proxy);649}650});651} else {652s = new Socket(Proxy.NO_PROXY);653}654} else {655s = new Socket();656}657658InetAddress serverAddress = AccessController.doPrivileged(659new PrivilegedAction<InetAddress>() {660@Override661public InetAddress run() {662return server.getLocalAddress();663}664});665666// Bind the socket to the same address as the control channel. This667// is needed in case of multi-homed systems.668s.bind(new InetSocketAddress(serverAddress, 0));669if (connectTimeout >= 0) {670s.connect(dest, connectTimeout);671} else {672if (defaultConnectTimeout > 0) {673s.connect(dest, defaultConnectTimeout);674} else {675s.connect(dest);676}677}678if (readTimeout >= 0) {679s.setSoTimeout(readTimeout);680} else if (defaultSoTimeout > 0) {681s.setSoTimeout(defaultSoTimeout);682}683if (useCrypto) {684try {685s = sslFact.createSocket(s, dest.getHostName(), dest.getPort(), true);686} catch (Exception e) {687throw new sun.net.ftp.FtpProtocolException("Can't open secure data channel: " + e);688}689}690if (!issueCommand(cmd)) {691s.close();692if (getLastReplyCode() == FtpReplyCode.FILE_UNAVAILABLE) {693// Ensure backward compatibility694throw new FileNotFoundException(cmd);695}696throw new sun.net.ftp.FtpProtocolException(cmd + ":" + getResponseString(), getLastReplyCode());697}698return s;699}700701/**702* Opens a data connection with the server according to the set mode703* (ACTIVE or PASSIVE) then send the command passed as an argument.704*705* @param cmd the <code>String</code> containing the command to execute706* @return the connected <code>Socket</code>707* @throws IOException if the connection or command failed708*/709private Socket openDataConnection(String cmd) throws sun.net.ftp.FtpProtocolException, IOException {710Socket clientSocket;711712if (passiveMode) {713try {714return openPassiveDataConnection(cmd);715} catch (sun.net.ftp.FtpProtocolException e) {716// If Passive mode failed, fall back on PORT717// Otherwise throw exception718String errmsg = e.getMessage();719if (!errmsg.startsWith("PASV") && !errmsg.startsWith("EPSV")) {720throw e;721}722}723}724ServerSocket portSocket;725InetAddress myAddress;726String portCmd;727728if (proxy != null && proxy.type() == Proxy.Type.SOCKS) {729// We're behind a firewall and the passive mode fail,730// since we can't accept a connection through SOCKS (yet)731// throw an exception732throw new sun.net.ftp.FtpProtocolException("Passive mode failed");733}734// Bind the ServerSocket to the same address as the control channel735// This is needed for multi-homed systems736portSocket = new ServerSocket(0, 1, server.getLocalAddress());737try {738myAddress = portSocket.getInetAddress();739if (myAddress.isAnyLocalAddress()) {740myAddress = server.getLocalAddress();741}742// Let's try the new, IPv6 compatible EPRT command743// See RFC2428 for specifics744// Some FTP servers (like the one on Solaris) are bugged, they745// will accept the EPRT command but then, the subsequent command746// (e.g. RETR) will fail, so we have to check BOTH results (the747// EPRT cmd then the actual command) to decide whether we should748// fall back on the older PORT command.749portCmd = "EPRT |" + ((myAddress instanceof Inet6Address) ? "2" : "1") + "|" +750myAddress.getHostAddress() + "|" + portSocket.getLocalPort() + "|";751if (!issueCommand(portCmd) || !issueCommand(cmd)) {752// The EPRT command failed, let's fall back to good old PORT753portCmd = "PORT ";754byte[] addr = myAddress.getAddress();755756/* append host addr */757for (int i = 0; i < addr.length; i++) {758portCmd = portCmd + (addr[i] & 0xFF) + ",";759}760761/* append port number */762portCmd = portCmd + ((portSocket.getLocalPort() >>> 8) & 0xff) + "," + (portSocket.getLocalPort() & 0xff);763issueCommandCheck(portCmd);764issueCommandCheck(cmd);765}766// Either the EPRT or the PORT command was successful767// Let's create the client socket768if (connectTimeout >= 0) {769portSocket.setSoTimeout(connectTimeout);770} else {771if (defaultConnectTimeout > 0) {772portSocket.setSoTimeout(defaultConnectTimeout);773}774}775clientSocket = portSocket.accept();776if (readTimeout >= 0) {777clientSocket.setSoTimeout(readTimeout);778} else {779if (defaultSoTimeout > 0) {780clientSocket.setSoTimeout(defaultSoTimeout);781}782}783} finally {784portSocket.close();785}786if (useCrypto) {787try {788clientSocket = sslFact.createSocket(clientSocket, serverAddr.getHostName(), serverAddr.getPort(), true);789} catch (Exception ex) {790throw new IOException(ex.getLocalizedMessage());791}792}793return clientSocket;794}795796private InputStream createInputStream(InputStream in) {797if (type == TransferType.ASCII) {798return new sun.net.TelnetInputStream(in, false);799}800return in;801}802803private OutputStream createOutputStream(OutputStream out) {804if (type == TransferType.ASCII) {805return new sun.net.TelnetOutputStream(out, false);806}807return out;808}809810/**811* Creates an instance of FtpClient. The client is not connected to any812* server yet.813*814*/815protected FtpClient() {816}817818/**819* Creates an instance of FtpClient. The client is not connected to any820* server yet.821*822*/823public static sun.net.ftp.FtpClient create() {824return new FtpClient();825}826827/**828* Set the transfer mode to <I>passive</I>. In that mode, data connections829* are established by having the client connect to the server.830* This is the recommended default mode as it will work best through831* firewalls and NATs.832*833* @return This FtpClient834* @see #setActiveMode()835*/836public sun.net.ftp.FtpClient enablePassiveMode(boolean passive) {837838// Only passive mode used in JDK. See Bug 8010784.839// passiveMode = passive;840return this;841}842843/**844* Gets the current transfer mode.845*846* @return the current <code>FtpTransferMode</code>847*/848public boolean isPassiveModeEnabled() {849return passiveMode;850}851852/**853* Sets the timeout value to use when connecting to the server,854*855* @param timeout the timeout value, in milliseconds, to use for the connect856* operation. A value of zero or less, means use the default timeout.857*858* @return This FtpClient859*/860public sun.net.ftp.FtpClient setConnectTimeout(int timeout) {861connectTimeout = timeout;862return this;863}864865/**866* Returns the current connection timeout value.867*868* @return the value, in milliseconds, of the current connect timeout.869* @see #setConnectTimeout(int)870*/871public int getConnectTimeout() {872return connectTimeout;873}874875/**876* Sets the timeout value to use when reading from the server,877*878* @param timeout the timeout value, in milliseconds, to use for the read879* operation. A value of zero or less, means use the default timeout.880* @return This FtpClient881*/882public sun.net.ftp.FtpClient setReadTimeout(int timeout) {883readTimeout = timeout;884return this;885}886887/**888* Returns the current read timeout value.889*890* @return the value, in milliseconds, of the current read timeout.891* @see #setReadTimeout(int)892*/893public int getReadTimeout() {894return readTimeout;895}896897public sun.net.ftp.FtpClient setProxy(Proxy p) {898proxy = p;899return this;900}901902/**903* Get the proxy of this FtpClient904*905* @return the <code>Proxy</code>, this client is using, or <code>null</code>906* if none is used.907* @see #setProxy(Proxy)908*/909public Proxy getProxy() {910return proxy;911}912913/**914* Connects to the specified destination.915*916* @param dest the <code>InetSocketAddress</code> to connect to.917* @throws IOException if the connection fails.918*/919private void tryConnect(InetSocketAddress dest, int timeout) throws IOException {920if (isConnected()) {921disconnect();922}923server = doConnect(dest, timeout);924try {925out = new PrintStream(new BufferedOutputStream(server.getOutputStream()),926true, encoding);927} catch (UnsupportedEncodingException e) {928throw new InternalError(encoding + "encoding not found", e);929}930in = new BufferedInputStream(server.getInputStream());931}932933private Socket doConnect(InetSocketAddress dest, int timeout) throws IOException {934Socket s;935if (proxy != null) {936if (proxy.type() == Proxy.Type.SOCKS) {937s = AccessController.doPrivileged(938new PrivilegedAction<Socket>() {939940public Socket run() {941return new Socket(proxy);942}943});944} else {945s = new Socket(Proxy.NO_PROXY);946}947} else {948s = new Socket();949}950// Instance specific timeouts do have priority, that means951// connectTimeout & readTimeout (-1 means not set)952// Then global default timeouts953// Then no timeout.954if (timeout >= 0) {955s.connect(dest, timeout);956} else {957if (connectTimeout >= 0) {958s.connect(dest, connectTimeout);959} else {960if (defaultConnectTimeout > 0) {961s.connect(dest, defaultConnectTimeout);962} else {963s.connect(dest);964}965}966}967if (readTimeout >= 0) {968s.setSoTimeout(readTimeout);969} else if (defaultSoTimeout > 0) {970s.setSoTimeout(defaultSoTimeout);971}972return s;973}974975private void disconnect() throws IOException {976if (isConnected()) {977server.close();978}979server = null;980in = null;981out = null;982lastTransSize = -1;983lastFileName = null;984restartOffset = 0;985welcomeMsg = null;986lastReplyCode = null;987serverResponse.setSize(0);988}989990/**991* Tests whether this client is connected or not to a server.992*993* @return <code>true</code> if the client is connected.994*/995public boolean isConnected() {996return server != null;997}998999public SocketAddress getServerAddress() {1000return server == null ? null : server.getRemoteSocketAddress();1001}10021003public sun.net.ftp.FtpClient connect(SocketAddress dest) throws sun.net.ftp.FtpProtocolException, IOException {1004return connect(dest, -1);1005}10061007/**1008* Connects the FtpClient to the specified destination.1009*1010* @param dest the address of the destination server1011* @throws IOException if connection failed.1012*/1013public sun.net.ftp.FtpClient connect(SocketAddress dest, int timeout) throws sun.net.ftp.FtpProtocolException, IOException {1014if (!(dest instanceof InetSocketAddress)) {1015throw new IllegalArgumentException("Wrong address type");1016}1017serverAddr = (InetSocketAddress) dest;1018tryConnect(serverAddr, timeout);1019if (!readReply()) {1020throw new sun.net.ftp.FtpProtocolException("Welcome message: " +1021getResponseString(), lastReplyCode);1022}1023welcomeMsg = getResponseString().substring(4);1024return this;1025}10261027private void tryLogin(String user, char[] password) throws sun.net.ftp.FtpProtocolException, IOException {1028issueCommandCheck("USER " + user);10291030/*1031* Checks for "331 User name okay, need password." answer1032*/1033if (lastReplyCode == FtpReplyCode.NEED_PASSWORD) {1034if ((password != null) && (password.length > 0)) {1035issueCommandCheck("PASS " + String.valueOf(password));1036}1037}1038}10391040/**1041* Attempts to log on the server with the specified user name and password.1042*1043* @param user The user name1044* @param password The password for that user1045* @return <code>true</code> if the login was successful.1046* @throws IOException if an error occurred during the transmission1047*/1048public sun.net.ftp.FtpClient login(String user, char[] password) throws sun.net.ftp.FtpProtocolException, IOException {1049if (!isConnected()) {1050throw new sun.net.ftp.FtpProtocolException("Not connected yet", FtpReplyCode.BAD_SEQUENCE);1051}1052if (user == null || user.length() == 0) {1053throw new IllegalArgumentException("User name can't be null or empty");1054}1055tryLogin(user, password);10561057// keep the welcome message around so we can1058// put it in the resulting HTML page.1059String l;1060StringBuffer sb = new StringBuffer();1061for (int i = 0; i < serverResponse.size(); i++) {1062l = serverResponse.elementAt(i);1063if (l != null) {1064if (l.length() >= 4 && l.startsWith("230")) {1065// get rid of the "230-" prefix1066l = l.substring(4);1067}1068sb.append(l);1069}1070}1071welcomeMsg = sb.toString();1072loggedIn = true;1073return this;1074}10751076/**1077* Attempts to log on the server with the specified user name, password and1078* account name.1079*1080* @param user The user name1081* @param password The password for that user.1082* @param account The account name for that user.1083* @return <code>true</code> if the login was successful.1084* @throws IOException if an error occurs during the transmission.1085*/1086public sun.net.ftp.FtpClient login(String user, char[] password, String account) throws sun.net.ftp.FtpProtocolException, IOException {10871088if (!isConnected()) {1089throw new sun.net.ftp.FtpProtocolException("Not connected yet", FtpReplyCode.BAD_SEQUENCE);1090}1091if (user == null || user.length() == 0) {1092throw new IllegalArgumentException("User name can't be null or empty");1093}1094tryLogin(user, password);10951096/*1097* Checks for "332 Need account for login." answer1098*/1099if (lastReplyCode == FtpReplyCode.NEED_ACCOUNT) {1100issueCommandCheck("ACCT " + account);1101}11021103// keep the welcome message around so we can1104// put it in the resulting HTML page.1105StringBuffer sb = new StringBuffer();1106if (serverResponse != null) {1107for (String l : serverResponse) {1108if (l != null) {1109if (l.length() >= 4 && l.startsWith("230")) {1110// get rid of the "230-" prefix1111l = l.substring(4);1112}1113sb.append(l);1114}1115}1116}1117welcomeMsg = sb.toString();1118loggedIn = true;1119return this;1120}11211122/**1123* Logs out the current user. This is in effect terminates the current1124* session and the connection to the server will be closed.1125*1126*/1127public void close() throws IOException {1128if (isConnected()) {1129try {1130issueCommand("QUIT");1131} catch (FtpProtocolException e) {1132}1133loggedIn = false;1134}1135disconnect();1136}11371138/**1139* Checks whether the client is logged in to the server or not.1140*1141* @return <code>true</code> if the client has already completed a login.1142*/1143public boolean isLoggedIn() {1144return loggedIn;1145}11461147/**1148* Changes to a specific directory on a remote FTP server1149*1150* @param remoteDirectory path of the directory to CD to.1151* @return <code>true</code> if the operation was successful.1152* @exception <code>FtpProtocolException</code>1153*/1154public sun.net.ftp.FtpClient changeDirectory(String remoteDirectory) throws sun.net.ftp.FtpProtocolException, IOException {1155if (remoteDirectory == null || "".equals(remoteDirectory)) {1156throw new IllegalArgumentException("directory can't be null or empty");1157}11581159issueCommandCheck("CWD " + remoteDirectory);1160return this;1161}11621163/**1164* Changes to the parent directory, sending the CDUP command to the server.1165*1166* @return <code>true</code> if the command was successful.1167* @throws IOException1168*/1169public sun.net.ftp.FtpClient changeToParentDirectory() throws sun.net.ftp.FtpProtocolException, IOException {1170issueCommandCheck("CDUP");1171return this;1172}11731174/**1175* Returns the server current working directory, or <code>null</code> if1176* the PWD command failed.1177*1178* @return a <code>String</code> containing the current working directory,1179* or <code>null</code>1180* @throws IOException1181*/1182public String getWorkingDirectory() throws sun.net.ftp.FtpProtocolException, IOException {1183issueCommandCheck("PWD");1184/*1185* answer will be of the following format :1186*1187* 257 "/" is current directory.1188*/1189String answ = getResponseString();1190if (!answ.startsWith("257")) {1191return null;1192}1193return answ.substring(5, answ.lastIndexOf('"'));1194}11951196/**1197* Sets the restart offset to the specified value. That value will be1198* sent through a <code>REST</code> command to server before a file1199* transfer and has the effect of resuming a file transfer from the1200* specified point. After a transfer the restart offset is set back to1201* zero.1202*1203* @param offset the offset in the remote file at which to start the next1204* transfer. This must be a value greater than or equal to zero.1205* @throws IllegalArgumentException if the offset is negative.1206*/1207public sun.net.ftp.FtpClient setRestartOffset(long offset) {1208if (offset < 0) {1209throw new IllegalArgumentException("offset can't be negative");1210}1211restartOffset = offset;1212return this;1213}12141215/**1216* Retrieves a file from the ftp server and writes it to the specified1217* <code>OutputStream</code>.1218* If the restart offset was set, then a <code>REST</code> command will be1219* sent before the RETR in order to restart the tranfer from the specified1220* offset.1221* The <code>OutputStream</code> is not closed by this method at the end1222* of the transfer.1223*1224* @param name a <code>String<code> containing the name of the file to1225* retreive from the server.1226* @param local the <code>OutputStream</code> the file should be written to.1227* @throws IOException if the transfer fails.1228*/1229public sun.net.ftp.FtpClient getFile(String name, OutputStream local) throws sun.net.ftp.FtpProtocolException, IOException {1230int mtu = 1500;1231if (restartOffset > 0) {1232Socket s;1233try {1234s = openDataConnection("REST " + restartOffset);1235} finally {1236restartOffset = 0;1237}1238issueCommandCheck("RETR " + name);1239getTransferSize();1240InputStream remote = createInputStream(s.getInputStream());1241byte[] buf = new byte[mtu * 10];1242int l;1243while ((l = remote.read(buf)) >= 0) {1244if (l > 0) {1245local.write(buf, 0, l);1246}1247}1248remote.close();1249} else {1250Socket s = openDataConnection("RETR " + name);1251getTransferSize();1252InputStream remote = createInputStream(s.getInputStream());1253byte[] buf = new byte[mtu * 10];1254int l;1255while ((l = remote.read(buf)) >= 0) {1256if (l > 0) {1257local.write(buf, 0, l);1258}1259}1260remote.close();1261}1262return completePending();1263}12641265/**1266* Retrieves a file from the ftp server, using the RETR command, and1267* returns the InputStream from* the established data connection.1268* {@link #completePending()} <b>has</b> to be called once the application1269* is done reading from the returned stream.1270*1271* @param name the name of the remote file1272* @return the {@link java.io.InputStream} from the data connection, or1273* <code>null</code> if the command was unsuccessful.1274* @throws IOException if an error occurred during the transmission.1275*/1276public InputStream getFileStream(String name) throws sun.net.ftp.FtpProtocolException, IOException {1277Socket s;1278if (restartOffset > 0) {1279try {1280s = openDataConnection("REST " + restartOffset);1281} finally {1282restartOffset = 0;1283}1284if (s == null) {1285return null;1286}1287issueCommandCheck("RETR " + name);1288getTransferSize();1289return createInputStream(s.getInputStream());1290}12911292s = openDataConnection("RETR " + name);1293if (s == null) {1294return null;1295}1296getTransferSize();1297return createInputStream(s.getInputStream());1298}12991300/**1301* Transfers a file from the client to the server (aka a <I>put</I>)1302* by sending the STOR or STOU command, depending on the1303* <code>unique</code> argument, and returns the <code>OutputStream</code>1304* from the established data connection.1305* {@link #completePending()} <b>has</b> to be called once the application1306* is finished writing to the stream.1307*1308* A new file is created at the server site if the file specified does1309* not already exist.1310*1311* If <code>unique</code> is set to <code>true</code>, the resultant file1312* is to be created under a name unique to that directory, meaning1313* it will not overwrite an existing file, instead the server will1314* generate a new, unique, file name.1315* The name of the remote file can be retrieved, after completion of the1316* transfer, by calling {@link #getLastFileName()}.1317*1318* @param name the name of the remote file to write.1319* @param unique <code>true</code> if the remote files should be unique,1320* in which case the STOU command will be used.1321* @return the {@link java.io.OutputStream} from the data connection or1322* <code>null</code> if the command was unsuccessful.1323* @throws IOException if an error occurred during the transmission.1324*/1325public OutputStream putFileStream(String name, boolean unique)1326throws sun.net.ftp.FtpProtocolException, IOException1327{1328String cmd = unique ? "STOU " : "STOR ";1329Socket s = openDataConnection(cmd + name);1330if (s == null) {1331return null;1332}1333boolean bm = (type == TransferType.BINARY);1334return new sun.net.TelnetOutputStream(s.getOutputStream(), bm);1335}13361337/**1338* Transfers a file from the client to the server (aka a <I>put</I>)1339* by sending the STOR command. The content of the <code>InputStream</code>1340* passed in argument is written into the remote file, overwriting any1341* existing data.1342*1343* A new file is created at the server site if the file specified does1344* not already exist.1345*1346* @param name the name of the remote file to write.1347* @param local the <code>InputStream</code> that points to the data to1348* transfer.1349* @param unique <code>true</code> if the remote file should be unique1350* (i.e. not already existing), <code>false</code> otherwise.1351* @return <code>true</code> if the transfer was successful.1352* @throws IOException if an error occurred during the transmission.1353* @see #getLastFileName()1354*/1355public sun.net.ftp.FtpClient putFile(String name, InputStream local, boolean unique) throws sun.net.ftp.FtpProtocolException, IOException {1356String cmd = unique ? "STOU " : "STOR ";1357int mtu = 1500;1358if (type == TransferType.BINARY) {1359Socket s = openDataConnection(cmd + name);1360OutputStream remote = createOutputStream(s.getOutputStream());1361byte[] buf = new byte[mtu * 10];1362int l;1363while ((l = local.read(buf)) >= 0) {1364if (l > 0) {1365remote.write(buf, 0, l);1366}1367}1368remote.close();1369}1370return completePending();1371}13721373/**1374* Sends the APPE command to the server in order to transfer a data stream1375* passed in argument and append it to the content of the specified remote1376* file.1377*1378* @param name A <code>String</code> containing the name of the remote file1379* to append to.1380* @param local The <code>InputStream</code> providing access to the data1381* to be appended.1382* @return <code>true</code> if the transfer was successful.1383* @throws IOException if an error occurred during the transmission.1384*/1385public sun.net.ftp.FtpClient appendFile(String name, InputStream local) throws sun.net.ftp.FtpProtocolException, IOException {1386int mtu = 1500;1387Socket s = openDataConnection("APPE " + name);1388OutputStream remote = createOutputStream(s.getOutputStream());1389byte[] buf = new byte[mtu * 10];1390int l;1391while ((l = local.read(buf)) >= 0) {1392if (l > 0) {1393remote.write(buf, 0, l);1394}1395}1396remote.close();1397return completePending();1398}13991400/**1401* Renames a file on the server.1402*1403* @param from the name of the file being renamed1404* @param to the new name for the file1405* @throws IOException if the command fails1406*/1407public sun.net.ftp.FtpClient rename(String from, String to) throws sun.net.ftp.FtpProtocolException, IOException {1408issueCommandCheck("RNFR " + from);1409issueCommandCheck("RNTO " + to);1410return this;1411}14121413/**1414* Deletes a file on the server.1415*1416* @param name a <code>String</code> containing the name of the file1417* to delete.1418* @return <code>true</code> if the command was successful1419* @throws IOException if an error occurred during the exchange1420*/1421public sun.net.ftp.FtpClient deleteFile(String name) throws sun.net.ftp.FtpProtocolException, IOException {1422issueCommandCheck("DELE " + name);1423return this;1424}14251426/**1427* Creates a new directory on the server.1428*1429* @param name a <code>String</code> containing the name of the directory1430* to create.1431* @return <code>true</code> if the operation was successful.1432* @throws IOException if an error occurred during the exchange1433*/1434public sun.net.ftp.FtpClient makeDirectory(String name) throws sun.net.ftp.FtpProtocolException, IOException {1435issueCommandCheck("MKD " + name);1436return this;1437}14381439/**1440* Removes a directory on the server.1441*1442* @param name a <code>String</code> containing the name of the directory1443* to remove.1444*1445* @return <code>true</code> if the operation was successful.1446* @throws IOException if an error occurred during the exchange.1447*/1448public sun.net.ftp.FtpClient removeDirectory(String name) throws sun.net.ftp.FtpProtocolException, IOException {1449issueCommandCheck("RMD " + name);1450return this;1451}14521453/**1454* Sends a No-operation command. It's useful for testing the connection1455* status or as a <I>keep alive</I> mechanism.1456*1457* @throws FtpProtocolException if the command fails1458*/1459public sun.net.ftp.FtpClient noop() throws sun.net.ftp.FtpProtocolException, IOException {1460issueCommandCheck("NOOP");1461return this;1462}14631464/**1465* Sends the STAT command to the server.1466* This can be used while a data connection is open to get a status1467* on the current transfer, in that case the parameter should be1468* <code>null</code>.1469* If used between file transfers, it may have a pathname as argument1470* in which case it will work as the LIST command except no data1471* connection will be created.1472*1473* @param name an optional <code>String</code> containing the pathname1474* the STAT command should apply to.1475* @return the response from the server or <code>null</code> if the1476* command failed.1477* @throws IOException if an error occurred during the transmission.1478*/1479public String getStatus(String name) throws sun.net.ftp.FtpProtocolException, IOException {1480issueCommandCheck((name == null ? "STAT" : "STAT " + name));1481/*1482* A typical response will be:1483* 213-status of t32.gif:1484* -rw-r--r-- 1 jcc staff 247445 Feb 17 1998 t32.gif1485* 213 End of Status1486*1487* or1488*1489* 211-jsn FTP server status:1490* Version wu-2.6.2+Sun1491* Connected to localhost (::1)1492* Logged in as jccollet1493* TYPE: ASCII, FORM: Nonprint; STRUcture: File; transfer MODE: Stream1494* No data connection1495* 0 data bytes received in 0 files1496* 0 data bytes transmitted in 0 files1497* 0 data bytes total in 0 files1498* 53 traffic bytes received in 0 transfers1499* 485 traffic bytes transmitted in 0 transfers1500* 587 traffic bytes total in 0 transfers1501* 211 End of status1502*1503* So we need to remove the 1st and last line1504*/1505Vector<String> resp = getResponseStrings();1506StringBuffer sb = new StringBuffer();1507for (int i = 1; i < resp.size() - 1; i++) {1508sb.append(resp.get(i));1509}1510return sb.toString();1511}15121513/**1514* Sends the FEAT command to the server and returns the list of supported1515* features in the form of strings.1516*1517* The features are the supported commands, like AUTH TLS, PROT or PASV.1518* See the RFCs for a complete list.1519*1520* Note that not all FTP servers support that command, in which case1521* the method will return <code>null</code>1522*1523* @return a <code>List</code> of <code>Strings</code> describing the1524* supported additional features, or <code>null</code>1525* if the command is not supported.1526* @throws IOException if an error occurs during the transmission.1527*/1528public List<String> getFeatures() throws sun.net.ftp.FtpProtocolException, IOException {1529/*1530* The FEAT command, when implemented will return something like:1531*1532* 211-Features:1533* AUTH TLS1534* PBSZ1535* PROT1536* EPSV1537* EPRT1538* PASV1539* REST STREAM1540* 211 END1541*/1542ArrayList<String> features = new ArrayList<String>();1543issueCommandCheck("FEAT");1544Vector<String> resp = getResponseStrings();1545// Note that we start at index 1 to skip the 1st line (211-...)1546// and we stop before the last line.1547for (int i = 1; i < resp.size() - 1; i++) {1548String s = resp.get(i);1549// Get rid of leading space and trailing newline1550features.add(s.substring(1, s.length() - 1));1551}1552return features;1553}15541555/**1556* sends the ABOR command to the server.1557* It tells the server to stop the previous command or transfer.1558*1559* @return <code>true</code> if the command was successful.1560* @throws IOException if an error occurred during the transmission.1561*/1562public sun.net.ftp.FtpClient abort() throws sun.net.ftp.FtpProtocolException, IOException {1563issueCommandCheck("ABOR");1564// TODO: Must check the ReplyCode:1565/*1566* From the RFC:1567* There are two cases for the server upon receipt of this1568* command: (1) the FTP service command was already completed,1569* or (2) the FTP service command is still in progress.1570* In the first case, the server closes the data connection1571* (if it is open) and responds with a 226 reply, indicating1572* that the abort command was successfully processed.1573* In the second case, the server aborts the FTP service in1574* progress and closes the data connection, returning a 4261575* reply to indicate that the service request terminated1576* abnormally. The server then sends a 226 reply,1577* indicating that the abort command was successfully1578* processed.1579*/158015811582return this;1583}15841585/**1586* Some methods do not wait until completion before returning, so this1587* method can be called to wait until completion. This is typically the case1588* with commands that trigger a transfer like {@link #getFileStream(String)}.1589* So this method should be called before accessing information related to1590* such a command.1591* <p>This method will actually block reading on the command channel for a1592* notification from the server that the command is finished. Such a1593* notification often carries extra information concerning the completion1594* of the pending action (e.g. number of bytes transfered).</p>1595* <p>Note that this will return true immediately if no command or action1596* is pending</p>1597* <p>It should be also noted that most methods issuing commands to the ftp1598* server will call this method if a previous command is pending.1599* <p>Example of use:1600* <pre>1601* InputStream in = cl.getFileStream("file");1602* ...1603* cl.completePending();1604* long size = cl.getLastTransferSize();1605* </pre>1606* On the other hand, it's not necessary in a case like:1607* <pre>1608* InputStream in = cl.getFileStream("file");1609* // read content1610* ...1611* cl.logout();1612* </pre>1613* <p>Since {@link #logout()} will call completePending() if necessary.</p>1614* @return <code>true</code> if the completion was successful or if no1615* action was pending.1616* @throws IOException1617*/1618public sun.net.ftp.FtpClient completePending() throws sun.net.ftp.FtpProtocolException, IOException {1619while (replyPending) {1620replyPending = false;1621if (!readReply()) {1622throw new sun.net.ftp.FtpProtocolException(getLastResponseString(), lastReplyCode);1623}1624}1625return this;1626}16271628/**1629* Reinitializes the USER parameters on the FTP server1630*1631* @throws FtpProtocolException if the command fails1632*/1633public sun.net.ftp.FtpClient reInit() throws sun.net.ftp.FtpProtocolException, IOException {1634issueCommandCheck("REIN");1635loggedIn = false;1636if (useCrypto) {1637if (server instanceof SSLSocket) {1638javax.net.ssl.SSLSession session = ((SSLSocket) server).getSession();1639session.invalidate();1640// Restore previous socket and streams1641server = oldSocket;1642oldSocket = null;1643try {1644out = new PrintStream(new BufferedOutputStream(server.getOutputStream()),1645true, encoding);1646} catch (UnsupportedEncodingException e) {1647throw new InternalError(encoding + "encoding not found", e);1648}1649in = new BufferedInputStream(server.getInputStream());1650}1651}1652useCrypto = false;1653return this;1654}16551656/**1657* Changes the transfer type (binary, ascii, ebcdic) and issue the1658* proper command (e.g. TYPE A) to the server.1659*1660* @param type the <code>FtpTransferType</code> to use.1661* @return This FtpClient1662* @throws IOException if an error occurs during transmission.1663*/1664public sun.net.ftp.FtpClient setType(TransferType type) throws sun.net.ftp.FtpProtocolException, IOException {1665String cmd = "NOOP";16661667this.type = type;1668if (type == TransferType.ASCII) {1669cmd = "TYPE A";1670}1671if (type == TransferType.BINARY) {1672cmd = "TYPE I";1673}1674if (type == TransferType.EBCDIC) {1675cmd = "TYPE E";1676}1677issueCommandCheck(cmd);1678return this;1679}16801681/**1682* Issues a LIST command to the server to get the current directory1683* listing, and returns the InputStream from the data connection.1684* {@link #completePending()} <b>has</b> to be called once the application1685* is finished writing to the stream.1686*1687* @param path the pathname of the directory to list, or <code>null</code>1688* for the current working directory.1689* @return the <code>InputStream</code> from the resulting data connection1690* @throws IOException if an error occurs during the transmission.1691* @see #changeDirectory(String)1692* @see #listFiles(String)1693*/1694public InputStream list(String path) throws sun.net.ftp.FtpProtocolException, IOException {1695Socket s;1696s = openDataConnection(path == null ? "LIST" : "LIST " + path);1697if (s != null) {1698return createInputStream(s.getInputStream());1699}1700return null;1701}17021703/**1704* Issues a NLST path command to server to get the specified directory1705* content. It differs from {@link #list(String)} method by the fact that1706* it will only list the file names which would make the parsing of the1707* somewhat easier.1708*1709* {@link #completePending()} <b>has</b> to be called once the application1710* is finished writing to the stream.1711*1712* @param path a <code>String</code> containing the pathname of the1713* directory to list or <code>null</code> for the current working1714* directory.1715* @return the <code>InputStream</code> from the resulting data connection1716* @throws IOException if an error occurs during the transmission.1717*/1718public InputStream nameList(String path) throws sun.net.ftp.FtpProtocolException, IOException {1719Socket s;1720s = openDataConnection(path == null ? "NLST" : "NLST " + path);1721if (s != null) {1722return createInputStream(s.getInputStream());1723}1724return null;1725}17261727/**1728* Issues the SIZE [path] command to the server to get the size of a1729* specific file on the server.1730* Note that this command may not be supported by the server. In which1731* case -1 will be returned.1732*1733* @param path a <code>String</code> containing the pathname of the1734* file.1735* @return a <code>long</code> containing the size of the file or -1 if1736* the server returned an error, which can be checked with1737* {@link #getLastReplyCode()}.1738* @throws IOException if an error occurs during the transmission.1739*/1740public long getSize(String path) throws sun.net.ftp.FtpProtocolException, IOException {1741if (path == null || path.length() == 0) {1742throw new IllegalArgumentException("path can't be null or empty");1743}1744issueCommandCheck("SIZE " + path);1745if (lastReplyCode == FtpReplyCode.FILE_STATUS) {1746String s = getResponseString();1747s = s.substring(4, s.length() - 1);1748return Long.parseLong(s);1749}1750return -1;1751}1752private static String[] MDTMformats = {1753"yyyyMMddHHmmss.SSS",1754"yyyyMMddHHmmss"1755};1756private static SimpleDateFormat[] dateFormats = new SimpleDateFormat[MDTMformats.length];17571758static {1759for (int i = 0; i < MDTMformats.length; i++) {1760dateFormats[i] = new SimpleDateFormat(MDTMformats[i]);1761dateFormats[i].setTimeZone(TimeZone.getTimeZone("GMT"));1762}1763}17641765/**1766* Issues the MDTM [path] command to the server to get the modification1767* time of a specific file on the server.1768* Note that this command may not be supported by the server, in which1769* case <code>null</code> will be returned.1770*1771* @param path a <code>String</code> containing the pathname of the file.1772* @return a <code>Date</code> representing the last modification time1773* or <code>null</code> if the server returned an error, which1774* can be checked with {@link #getLastReplyCode()}.1775* @throws IOException if an error occurs during the transmission.1776*/1777public Date getLastModified(String path) throws sun.net.ftp.FtpProtocolException, IOException {1778issueCommandCheck("MDTM " + path);1779if (lastReplyCode == FtpReplyCode.FILE_STATUS) {1780String s = getResponseString().substring(4);1781Date d = null;1782for (SimpleDateFormat dateFormat : dateFormats) {1783try {1784d = dateFormat.parse(s);1785} catch (ParseException ex) {1786}1787if (d != null) {1788return d;1789}1790}1791}1792return null;1793}17941795/**1796* Sets the parser used to handle the directory output to the specified1797* one. By default the parser is set to one that can handle most FTP1798* servers output (Unix base mostly). However it may be necessary for1799* and application to provide its own parser due to some uncommon1800* output format.1801*1802* @param p The <code>FtpDirParser</code> to use.1803* @see #listFiles(String)1804*/1805public sun.net.ftp.FtpClient setDirParser(FtpDirParser p) {1806parser = p;1807return this;1808}18091810private class FtpFileIterator implements Iterator<FtpDirEntry>, Closeable {18111812private BufferedReader in = null;1813private FtpDirEntry nextFile = null;1814private FtpDirParser fparser = null;1815private boolean eof = false;18161817public FtpFileIterator(FtpDirParser p, BufferedReader in) {1818this.in = in;1819this.fparser = p;1820readNext();1821}18221823private void readNext() {1824nextFile = null;1825if (eof) {1826return;1827}1828String line = null;1829try {1830do {1831line = in.readLine();1832if (line != null) {1833nextFile = fparser.parseLine(line);1834if (nextFile != null) {1835return;1836}1837}1838} while (line != null);1839in.close();1840} catch (IOException iOException) {1841}1842eof = true;1843}18441845public boolean hasNext() {1846return nextFile != null;1847}18481849public FtpDirEntry next() {1850FtpDirEntry ret = nextFile;1851readNext();1852return ret;1853}18541855public void remove() {1856throw new UnsupportedOperationException("Not supported yet.");1857}18581859public void close() throws IOException {1860if (in != null && !eof) {1861in.close();1862}1863eof = true;1864nextFile = null;1865}1866}18671868/**1869* Issues a MLSD command to the server to get the specified directory1870* listing and applies the current parser to create an Iterator of1871* {@link java.net.ftp.FtpDirEntry}. Note that the Iterator returned is also a1872* {@link java.io.Closeable}.1873* If the server doesn't support the MLSD command, the LIST command is used1874* instead.1875*1876* {@link #completePending()} <b>has</b> to be called once the application1877* is finished iterating through the files.1878*1879* @param path the pathname of the directory to list or <code>null</code>1880* for the current working directoty.1881* @return a <code>Iterator</code> of files or <code>null</code> if the1882* command failed.1883* @throws IOException if an error occurred during the transmission1884* @see #setDirParser(FtpDirParser)1885* @see #changeDirectory(String)1886*/1887public Iterator<FtpDirEntry> listFiles(String path) throws sun.net.ftp.FtpProtocolException, IOException {1888Socket s = null;1889BufferedReader sin = null;1890try {1891s = openDataConnection(path == null ? "MLSD" : "MLSD " + path);1892} catch (sun.net.ftp.FtpProtocolException FtpException) {1893// The server doesn't understand new MLSD command, ignore and fall1894// back to LIST1895}18961897if (s != null) {1898sin = new BufferedReader(new InputStreamReader(s.getInputStream()));1899return new FtpFileIterator(mlsxParser, sin);1900} else {1901s = openDataConnection(path == null ? "LIST" : "LIST " + path);1902if (s != null) {1903sin = new BufferedReader(new InputStreamReader(s.getInputStream()));1904return new FtpFileIterator(parser, sin);1905}1906}1907return null;1908}19091910private boolean sendSecurityData(byte[] buf) throws IOException,1911sun.net.ftp.FtpProtocolException {1912BASE64Encoder encoder = new BASE64Encoder();1913String s = encoder.encode(buf);1914return issueCommand("ADAT " + s);1915}19161917private byte[] getSecurityData() {1918String s = getLastResponseString();1919if (s.substring(4, 9).equalsIgnoreCase("ADAT=")) {1920BASE64Decoder decoder = new BASE64Decoder();1921try {1922// Need to get rid of the leading '315 ADAT='1923// and the trailing newline1924return decoder.decodeBuffer(s.substring(9, s.length() - 1));1925} catch (IOException e) {1926//1927}1928}1929return null;1930}19311932/**1933* Attempts to use Kerberos GSSAPI as an authentication mechanism with the1934* ftp server. This will issue an <code>AUTH GSSAPI</code> command, and if1935* it is accepted by the server, will followup with <code>ADAT</code>1936* command to exchange the various tokens until authentification is1937* successful. This conforms to Appendix I of RFC 2228.1938*1939* @return <code>true</code> if authentication was successful.1940* @throws IOException if an error occurs during the transmission.1941*/1942public sun.net.ftp.FtpClient useKerberos() throws sun.net.ftp.FtpProtocolException, IOException {1943/*1944* Comment out for the moment since it's not in use and would create1945* needless cross-package links.1946*1947issueCommandCheck("AUTH GSSAPI");1948if (lastReplyCode != FtpReplyCode.NEED_ADAT)1949throw new sun.net.ftp.FtpProtocolException("Unexpected reply from server");1950try {1951GSSManager manager = GSSManager.getInstance();1952GSSName name = manager.createName("SERVICE:ftp@"+1953serverAddr.getHostName(), null);1954GSSContext context = manager.createContext(name, null, null,1955GSSContext.DEFAULT_LIFETIME);1956context.requestMutualAuth(true);1957context.requestReplayDet(true);1958context.requestSequenceDet(true);1959context.requestCredDeleg(true);1960byte []inToken = new byte[0];1961while (!context.isEstablished()) {1962byte[] outToken1963= context.initSecContext(inToken, 0, inToken.length);1964// send the output token if generated1965if (outToken != null) {1966if (sendSecurityData(outToken)) {1967inToken = getSecurityData();1968}1969}1970}1971loggedIn = true;1972} catch (GSSException e) {19731974}1975*/1976return this;1977}19781979/**1980* Returns the Welcome string the server sent during initial connection.1981*1982* @return a <code>String</code> containing the message the server1983* returned during connection or <code>null</code>.1984*/1985public String getWelcomeMsg() {1986return welcomeMsg;1987}19881989/**1990* Returns the last reply code sent by the server.1991*1992* @return the lastReplyCode1993*/1994public FtpReplyCode getLastReplyCode() {1995return lastReplyCode;1996}19971998/**1999* Returns the last response string sent by the server.2000*2001* @return the message string, which can be quite long, last returned2002* by the server.2003*/2004public String getLastResponseString() {2005StringBuffer sb = new StringBuffer();2006if (serverResponse != null) {2007for (String l : serverResponse) {2008if (l != null) {2009sb.append(l);2010}2011}2012}2013return sb.toString();2014}20152016/**2017* Returns, when available, the size of the latest started transfer.2018* This is retreived by parsing the response string received as an initial2019* response to a RETR or similar request.2020*2021* @return the size of the latest transfer or -1 if either there was no2022* transfer or the information was unavailable.2023*/2024public long getLastTransferSize() {2025return lastTransSize;2026}20272028/**2029* Returns, when available, the remote name of the last transfered file.2030* This is mainly useful for "put" operation when the unique flag was2031* set since it allows to recover the unique file name created on the2032* server which may be different from the one submitted with the command.2033*2034* @return the name the latest transfered file remote name, or2035* <code>null</code> if that information is unavailable.2036*/2037public String getLastFileName() {2038return lastFileName;2039}20402041/**2042* Attempts to switch to a secure, encrypted connection. This is done by2043* sending the "AUTH TLS" command.2044* <p>See <a href="http://www.ietf.org/rfc/rfc4217.txt">RFC 4217</a></p>2045* If successful this will establish a secure command channel with the2046* server, it will also make it so that all other transfers (e.g. a RETR2047* command) will be done over an encrypted channel as well unless a2048* {@link #reInit()} command or a {@link #endSecureSession()} command is issued.2049*2050* @return <code>true</code> if the operation was successful.2051* @throws IOException if an error occurred during the transmission.2052* @see #endSecureSession()2053*/2054public sun.net.ftp.FtpClient startSecureSession() throws sun.net.ftp.FtpProtocolException, IOException {2055if (!isConnected()) {2056throw new sun.net.ftp.FtpProtocolException("Not connected yet", FtpReplyCode.BAD_SEQUENCE);2057}2058if (sslFact == null) {2059try {2060sslFact = (SSLSocketFactory) SSLSocketFactory.getDefault();2061} catch (Exception e) {2062throw new IOException(e.getLocalizedMessage());2063}2064}2065issueCommandCheck("AUTH TLS");2066Socket s = null;2067try {2068s = sslFact.createSocket(server, serverAddr.getHostName(), serverAddr.getPort(), true);2069} catch (javax.net.ssl.SSLException ssle) {2070try {2071disconnect();2072} catch (Exception e) {2073}2074throw ssle;2075}2076// Remember underlying socket so we can restore it later2077oldSocket = server;2078server = s;2079try {2080out = new PrintStream(new BufferedOutputStream(server.getOutputStream()),2081true, encoding);2082} catch (UnsupportedEncodingException e) {2083throw new InternalError(encoding + "encoding not found", e);2084}2085in = new BufferedInputStream(server.getInputStream());20862087issueCommandCheck("PBSZ 0");2088issueCommandCheck("PROT P");2089useCrypto = true;2090return this;2091}20922093/**2094* Sends a <code>CCC</code> command followed by a <code>PROT C</code>2095* command to the server terminating an encrypted session and reverting2096* back to a non crypted transmission.2097*2098* @return <code>true</code> if the operation was successful.2099* @throws IOException if an error occurred during transmission.2100* @see #startSecureSession()2101*/2102public sun.net.ftp.FtpClient endSecureSession() throws sun.net.ftp.FtpProtocolException, IOException {2103if (!useCrypto) {2104return this;2105}21062107issueCommandCheck("CCC");2108issueCommandCheck("PROT C");2109useCrypto = false;2110// Restore previous socket and streams2111server = oldSocket;2112oldSocket = null;2113try {2114out = new PrintStream(new BufferedOutputStream(server.getOutputStream()),2115true, encoding);2116} catch (UnsupportedEncodingException e) {2117throw new InternalError(encoding + "encoding not found", e);2118}2119in = new BufferedInputStream(server.getInputStream());21202121return this;2122}21232124/**2125* Sends the "Allocate" (ALLO) command to the server telling it to2126* pre-allocate the specified number of bytes for the next transfer.2127*2128* @param size The number of bytes to allocate.2129* @return <code>true</code> if the operation was successful.2130* @throws IOException if an error occurred during the transmission.2131*/2132public sun.net.ftp.FtpClient allocate(long size) throws sun.net.ftp.FtpProtocolException, IOException {2133issueCommandCheck("ALLO " + size);2134return this;2135}21362137/**2138* Sends the "Structure Mount" (SMNT) command to the server. This let the2139* user mount a different file system data structure without altering his2140* login or accounting information.2141*2142* @param struct a <code>String</code> containing the name of the2143* structure to mount.2144* @return <code>true</code> if the operation was successful.2145* @throws IOException if an error occurred during the transmission.2146*/2147public sun.net.ftp.FtpClient structureMount(String struct) throws sun.net.ftp.FtpProtocolException, IOException {2148issueCommandCheck("SMNT " + struct);2149return this;2150}21512152/**2153* Sends a SYST (System) command to the server and returns the String2154* sent back by the server describing the operating system at the2155* server.2156*2157* @return a <code>String</code> describing the OS, or <code>null</code>2158* if the operation was not successful.2159* @throws IOException if an error occurred during the transmission.2160*/2161public String getSystem() throws sun.net.ftp.FtpProtocolException, IOException {2162issueCommandCheck("SYST");2163/*2164* 215 UNIX Type: L8 Version: SUNOS2165*/2166String resp = getResponseString();2167// Get rid of the leading code and blank2168return resp.substring(4);2169}21702171/**2172* Sends the HELP command to the server, with an optional command, like2173* SITE, and returns the text sent back by the server.2174*2175* @param cmd the command for which the help is requested or2176* <code>null</code> for the general help2177* @return a <code>String</code> containing the text sent back by the2178* server, or <code>null</code> if the command failed.2179* @throws IOException if an error occurred during transmission2180*/2181public String getHelp(String cmd) throws sun.net.ftp.FtpProtocolException, IOException {2182issueCommandCheck("HELP " + cmd);2183/**2184*2185* HELP2186* 214-The following commands are implemented.2187* USER EPRT STRU ALLO DELE SYST RMD MDTM ADAT2188* PASS EPSV MODE REST CWD STAT PWD PROT2189* QUIT LPRT RETR RNFR LIST HELP CDUP PBSZ2190* PORT LPSV STOR RNTO NLST NOOP STOU AUTH2191* PASV TYPE APPE ABOR SITE MKD SIZE CCC2192* 214 Direct comments to ftp-bugs@jsn.2193*2194* HELP SITE2195* 214-The following SITE commands are implemented.2196* UMASK HELP GROUPS2197* IDLE ALIAS CHECKMETHOD2198* CHMOD CDPATH CHECKSUM2199* 214 Direct comments to ftp-bugs@jsn.2200*/2201Vector<String> resp = getResponseStrings();2202if (resp.size() == 1) {2203// Single line response2204return resp.get(0).substring(4);2205}2206// on multiple lines answers, like the ones above, remove 1st and last2207// line, concat the the others.2208StringBuffer sb = new StringBuffer();2209for (int i = 1; i < resp.size() - 1; i++) {2210sb.append(resp.get(i).substring(3));2211}2212return sb.toString();2213}22142215/**2216* Sends the SITE command to the server. This is used by the server2217* to provide services specific to his system that are essential2218* to file transfer.2219*2220* @param cmd the command to be sent.2221* @return <code>true</code> if the command was successful.2222* @throws IOException if an error occurred during transmission2223*/2224public sun.net.ftp.FtpClient siteCmd(String cmd) throws sun.net.ftp.FtpProtocolException, IOException {2225issueCommandCheck("SITE " + cmd);2226return this;2227}2228}222922302231