Path: blob/master/src/java.base/share/classes/sun/net/ftp/impl/FtpClient.java
41161 views
/*1* Copyright (c) 2009, 2021, 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.time.ZoneOffset;32import java.time.ZonedDateTime;33import java.time.format.DateTimeFormatter;34import java.time.format.DateTimeParseException;35import java.util.ArrayList;36import java.util.Base64;37import java.util.Calendar;38import java.util.Date;39import java.util.Iterator;40import java.util.List;41import java.util.Vector;42import java.util.regex.Matcher;43import java.util.regex.Pattern;44import javax.net.ssl.SSLSocket;45import javax.net.ssl.SSLSocketFactory;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};112@SuppressWarnings("removal")113final String enc = AccessController.doPrivileged(114new PrivilegedAction<String>() {115public String run() {116vals[0] = Integer.getInteger("sun.net.client.defaultReadTimeout", 300_000).intValue();117vals[1] = Integer.getInteger("sun.net.client.defaultConnectTimeout", 300_000).intValue();118return System.getProperty("file.encoding", "ISO8859_1");119}120});121if (vals[0] == 0) {122defaultSoTimeout = -1;123} else {124defaultSoTimeout = vals[0];125}126127if (vals[1] == 0) {128defaultConnectTimeout = -1;129} else {130defaultConnectTimeout = vals[1];131}132133encoding = enc;134try {135if (!isASCIISuperset(encoding)) {136encoding = "ISO8859_1";137}138} catch (Exception e) {139encoding = "ISO8859_1";140}141142patterns = new Pattern[patStrings.length];143for (int i = 0; i < patStrings.length; i++) {144patterns[i] = Pattern.compile(patStrings[i]);145}146}147148/**149* Test the named character encoding to verify that it converts ASCII150* characters correctly. We have to use an ASCII based encoding, or else151* the NetworkClients will not work correctly in EBCDIC based systems.152* However, we cannot just use ASCII or ISO8859_1 universally, because in153* Asian locales, non-ASCII characters may be embedded in otherwise154* ASCII based protocols (e.g. HTTP). The specifications (RFC2616, 2398)155* are a little ambiguous in this matter. For instance, RFC2398 [part 2.1]156* says that the HTTP request URI should be escaped using a defined157* mechanism, but there is no way to specify in the escaped string what158* the original character set is. It is not correct to assume that159* UTF-8 is always used (as in URLs in HTML 4.0). For this reason,160* until the specifications are updated to deal with this issue more161* comprehensively, and more importantly, HTTP servers are known to162* support these mechanisms, we will maintain the current behavior163* where it is possible to send non-ASCII characters in their original164* unescaped form.165*/166private static boolean isASCIISuperset(String encoding) throws Exception {167String chkS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" +168"abcdefghijklmnopqrstuvwxyz-_.!~*'();/?:@&=+$,";169170// Expected byte sequence for string above171byte[] chkB = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 71, 72,17273, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99,173100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114,174115, 116, 117, 118, 119, 120, 121, 122, 45, 95, 46, 33, 126, 42, 39, 40, 41, 59,17547, 63, 58, 64, 38, 61, 43, 36, 44};176177byte[] b = chkS.getBytes(encoding);178return java.util.Arrays.equals(b, chkB);179}180181private class DefaultParser implements FtpDirParser {182183/**184* Possible patterns:185*186* drwxr-xr-x 1 user01 ftp 512 Jan 29 23:32 prog187* drwxr-xr-x 1 user01 ftp 512 Jan 29 1997 prog188* drwxr-xr-x 1 1 1 512 Jan 29 23:32 prog189* lrwxr-xr-x 1 user01 ftp 512 Jan 29 23:32 prog -> prog2000190* drwxr-xr-x 1 username ftp 512 Jan 29 23:32 prog191* -rw-r--r-- 1 jcc staff 105009 Feb 3 15:05 test.1192*193* 01-29-97 11:32PM <DIR> prog194* 04/28/2006 09:12a 3,563 genBuffer.sh195*196* drwxr-xr-x folder 0 Jan 29 23:32 prog197*198* 0 DIR 01-29-97 23:32 PROG199*/200private DefaultParser() {201}202203public FtpDirEntry parseLine(String line) {204String fdate = null;205String fsize = null;206String time = null;207String filename = null;208String permstring = null;209String username = null;210String groupname = null;211boolean dir = false;212Calendar now = Calendar.getInstance();213int year = now.get(Calendar.YEAR);214215Matcher m = null;216for (int j = 0; j < patterns.length; j++) {217m = patterns[j].matcher(line);218if (m.find()) {219// 0 - file, 1 - size, 2 - date, 3 - time, 4 - year,220// 5 - permissions, 6 - user, 7 - group221filename = m.group(patternGroups[j][0]);222fsize = m.group(patternGroups[j][1]);223fdate = m.group(patternGroups[j][2]);224if (patternGroups[j][4] > 0) {225fdate += (", " + m.group(patternGroups[j][4]));226} else if (patternGroups[j][3] > 0) {227fdate += (", " + String.valueOf(year));228}229if (patternGroups[j][3] > 0) {230time = m.group(patternGroups[j][3]);231}232if (patternGroups[j][5] > 0) {233permstring = m.group(patternGroups[j][5]);234dir = permstring.startsWith("d");235}236if (patternGroups[j][6] > 0) {237username = m.group(patternGroups[j][6]);238}239if (patternGroups[j][7] > 0) {240groupname = m.group(patternGroups[j][7]);241}242// Old DOS format243if ("<DIR>".equals(fsize)) {244dir = true;245fsize = null;246}247}248}249250if (filename != null) {251Date d;252try {253d = df.parse(fdate);254} catch (Exception e) {255d = null;256}257if (d != null && time != null) {258int c = time.indexOf(':');259now.setTime(d);260now.set(Calendar.HOUR, Integer.parseInt(time, 0, c, 10));261now.set(Calendar.MINUTE, Integer.parseInt(time, c + 1, time.length(), 10));262d = now.getTime();263}264// see if it's a symbolic link, i.e. the name if followed265// by a -> and a path266Matcher m2 = linkp.matcher(filename);267if (m2.find()) {268// Keep only the name then269filename = m2.group(1);270}271boolean[][] perms = new boolean[3][3];272for (int i = 0; i < 3; i++) {273for (int j = 0; j < 3; j++) {274perms[i][j] = (permstring.charAt((i * 3) + j) != '-');275}276}277FtpDirEntry file = new FtpDirEntry(filename);278file.setUser(username).setGroup(groupname);279file.setSize(Long.parseLong(fsize)).setLastModified(d);280file.setPermissions(perms);281file.setType(dir ? FtpDirEntry.Type.DIR : (line.charAt(0) == 'l' ? FtpDirEntry.Type.LINK : FtpDirEntry.Type.FILE));282return file;283}284return null;285}286}287288private static class MLSxParser implements FtpDirParser {289public FtpDirEntry parseLine(String line) {290String name = null;291int i = line.lastIndexOf(';');292if (i > 0) {293name = line.substring(i + 1).trim();294line = line.substring(0, i);295} else {296name = line.trim();297line = "";298}299FtpDirEntry file = new FtpDirEntry(name);300while (!line.isEmpty()) {301String s;302i = line.indexOf(';');303if (i > 0) {304s = line.substring(0, i);305line = line.substring(i + 1);306} else {307s = line;308line = "";309}310i = s.indexOf('=');311if (i > 0) {312String fact = s.substring(0, i);313String value = s.substring(i + 1);314file.addFact(fact, value);315}316}317String s = file.getFact("Size");318if (s != null) {319file.setSize(Long.parseLong(s));320}321s = file.getFact("Modify");322if (s != null) {323Date d = parseRfc3659TimeValue(s);324if (d != null) {325file.setLastModified(d);326}327}328s = file.getFact("Create");329if (s != null) {330Date d = parseRfc3659TimeValue(s);331if (d != null) {332file.setCreated(d);333}334}335s = file.getFact("Type");336if (s != null) {337if (s.equalsIgnoreCase("file")) {338file.setType(FtpDirEntry.Type.FILE);339}340if (s.equalsIgnoreCase("dir")) {341file.setType(FtpDirEntry.Type.DIR);342}343if (s.equalsIgnoreCase("cdir")) {344file.setType(FtpDirEntry.Type.CDIR);345}346if (s.equalsIgnoreCase("pdir")) {347file.setType(FtpDirEntry.Type.PDIR);348}349}350return file;351}352};353private FtpDirParser parser = new DefaultParser();354private FtpDirParser mlsxParser = new MLSxParser();355private static Pattern transPat = null;356357private void getTransferSize() {358lastTransSize = -1;359/**360* If it's a start of data transfer response, let's try to extract361* the size from the response string. Usually it looks like that:362*363* 150 Opening BINARY mode data connection for foo (6701 bytes).364*/365String response = getLastResponseString();366if (transPat == null) {367transPat = Pattern.compile("150 Opening .*\\((\\d+) bytes\\).");368}369Matcher m = transPat.matcher(response);370if (m.find()) {371String s = m.group(1);372lastTransSize = Long.parseLong(s);373}374}375376/**377* extract the created file name from the response string:378* 226 Transfer complete (unique file name:toto.txt.1).379* Usually happens when a STOU (store unique) command had been issued.380*/381private void getTransferName() {382lastFileName = null;383String response = getLastResponseString();384int i = response.indexOf("unique file name:");385int e = response.lastIndexOf(')');386if (i >= 0) {387i += 17; // Length of "unique file name:"388lastFileName = response.substring(i, e);389}390}391392/**393* Pulls the response from the server and returns the code as a394* number. Returns -1 on failure.395*/396private int readServerResponse() throws IOException {397StringBuilder replyBuf = new StringBuilder(32);398int c;399int continuingCode = -1;400int code;401String response;402403serverResponse.setSize(0);404while (true) {405while ((c = in.read()) != -1) {406if (c == '\r') {407if ((c = in.read()) != '\n') {408replyBuf.append('\r');409}410}411replyBuf.append((char) c);412if (c == '\n') {413break;414}415}416response = replyBuf.toString();417replyBuf.setLength(0);418if (logger.isLoggable(PlatformLogger.Level.FINEST)) {419logger.finest("Server [" + serverAddr + "] --> " + response);420}421422if (response.isEmpty()) {423code = -1;424} else {425try {426code = Integer.parseInt(response, 0, 3, 10);427} catch (NumberFormatException e) {428code = -1;429} catch (IndexOutOfBoundsException e) {430/* this line doesn't contain a response code, so431we just completely ignore it */432continue;433}434}435serverResponse.addElement(response);436if (continuingCode != -1) {437/* we've seen a ###- sequence */438if (code != continuingCode ||439(response.length() >= 4 && response.charAt(3) == '-')) {440continue;441} else {442/* seen the end of code sequence */443continuingCode = -1;444break;445}446} else if (response.length() >= 4 && response.charAt(3) == '-') {447continuingCode = code;448continue;449} else {450break;451}452}453454return code;455}456457/** Sends command <i>cmd</i> to the server. */458private void sendServer(String cmd) {459out.print(cmd);460if (logger.isLoggable(PlatformLogger.Level.FINEST)) {461logger.finest("Server [" + serverAddr + "] <-- " + cmd);462}463}464465/** converts the server response into a string. */466private String getResponseString() {467return serverResponse.elementAt(0);468}469470/** Returns all server response strings. */471private Vector<String> getResponseStrings() {472return serverResponse;473}474475/**476* Read the reply from the FTP server.477*478* @return <code>true</code> if the command was successful479* @throws IOException if an error occurred480*/481private boolean readReply() throws IOException {482lastReplyCode = FtpReplyCode.find(readServerResponse());483484if (lastReplyCode.isPositivePreliminary()) {485replyPending = true;486return true;487}488if (lastReplyCode.isPositiveCompletion() || lastReplyCode.isPositiveIntermediate()) {489if (lastReplyCode == FtpReplyCode.CLOSING_DATA_CONNECTION) {490getTransferName();491}492return true;493}494return false;495}496497/**498* Sends a command to the FTP server and returns the error code499* (which can be a "success") sent by the server.500*501* @param cmd502* @return <code>true</code> if the command was successful503* @throws IOException504*/505private boolean issueCommand(String cmd) throws IOException,506sun.net.ftp.FtpProtocolException {507if (!isConnected()) {508throw new IllegalStateException("Not connected");509}510if (replyPending) {511try {512completePending();513} catch (sun.net.ftp.FtpProtocolException e) {514// ignore...515}516}517if (cmd.indexOf('\n') != -1) {518sun.net.ftp.FtpProtocolException ex519= new sun.net.ftp.FtpProtocolException("Illegal FTP command");520ex.initCause(new IllegalArgumentException("Illegal carriage return"));521throw ex;522}523sendServer(cmd + "\r\n");524return readReply();525}526527/**528* Send a command to the FTP server and check for success.529*530* @param cmd String containing the command531*532* @throws FtpProtocolException if an error occurred533*/534private void issueCommandCheck(String cmd) throws sun.net.ftp.FtpProtocolException, IOException {535if (!issueCommand(cmd)) {536throw new sun.net.ftp.FtpProtocolException(cmd + ":" + getResponseString(), getLastReplyCode());537}538}539private static Pattern epsvPat = null;540private static Pattern pasvPat = null;541542/**543* Opens a "PASSIVE" connection with the server and returns the connected544* <code>Socket</code>.545*546* @return the connected <code>Socket</code>547* @throws IOException if the connection was unsuccessful.548*/549private Socket openPassiveDataConnection(String cmd) throws sun.net.ftp.FtpProtocolException, IOException {550String serverAnswer;551int port;552InetSocketAddress dest = null;553554/**555* Here is the idea:556*557* - First we want to try the new (and IPv6 compatible) EPSV command558* But since we want to be nice with NAT software, we'll issue the559* EPSV ALL command first.560* EPSV is documented in RFC2428561* - If EPSV fails, then we fall back to the older, yet ok, PASV562* - If PASV fails as well, then we throw an exception and the calling563* method will have to try the EPRT or PORT command564*/565if (issueCommand("EPSV ALL")) {566// We can safely use EPSV commands567issueCommandCheck("EPSV");568serverAnswer = getResponseString();569570// The response string from a EPSV command will contain the port number571// the format will be :572// 229 Entering Extended PASSIVE Mode (|||58210|)573//574// So we'll use the regular expresions package to parse the output.575576if (epsvPat == null) {577epsvPat = Pattern.compile("^229 .* \\(\\|\\|\\|(\\d+)\\|\\)");578}579Matcher m = epsvPat.matcher(serverAnswer);580if (!m.find()) {581throw new sun.net.ftp.FtpProtocolException("EPSV failed : " + serverAnswer);582}583// Yay! Let's extract the port number584String s = m.group(1);585port = Integer.parseInt(s);586InetAddress add = server.getInetAddress();587if (add != null) {588dest = new InetSocketAddress(add, port);589} else {590// This means we used an Unresolved address to connect in591// the first place. Most likely because the proxy is doing592// the name resolution for us, so let's keep using unresolved593// address.594dest = InetSocketAddress.createUnresolved(serverAddr.getHostName(), port);595}596} else {597// EPSV ALL failed, so Let's try the regular PASV cmd598issueCommandCheck("PASV");599serverAnswer = getResponseString();600601// Let's parse the response String to get the IP & port to connect602// to. The String should be in the following format :603//604// 227 Entering PASSIVE Mode (A1,A2,A3,A4,p1,p2)605//606// Note that the two parenthesis are optional607//608// The IP address is A1.A2.A3.A4 and the port is p1 * 256 + p2609//610// The regular expression is a bit more complex this time, because611// the parenthesis are optionals and we have to use 3 groups.612613if (pasvPat == null) {614pasvPat = Pattern.compile("227 .* \\(?(\\d{1,3},\\d{1,3},\\d{1,3},\\d{1,3}),(\\d{1,3}),(\\d{1,3})\\)?");615}616Matcher m = pasvPat.matcher(serverAnswer);617if (!m.find()) {618throw new sun.net.ftp.FtpProtocolException("PASV failed : " + serverAnswer);619}620// Get port number out of group 2 & 3621port = Integer.parseInt(m.group(3)) + (Integer.parseInt(m.group(2)) << 8);622// IP address is simple623String s = m.group(1).replace(',', '.');624dest = new InetSocketAddress(s, port);625}626// Got everything, let's open the socket!627Socket s;628if (proxy != null) {629if (proxy.type() == Proxy.Type.SOCKS) {630PrivilegedAction<Socket> pa = () -> new Socket(proxy);631@SuppressWarnings("removal")632var tmp = AccessController.doPrivileged(pa);633s = tmp;634} else {635s = new Socket(Proxy.NO_PROXY);636}637} else {638s = new Socket();639}640641PrivilegedAction<InetAddress> pa = () -> server.getLocalAddress();642@SuppressWarnings("removal")643InetAddress serverAddress = AccessController.doPrivileged(pa);644645// Bind the socket to the same address as the control channel. This646// is needed in case of multi-homed systems.647s.bind(new InetSocketAddress(serverAddress, 0));648if (connectTimeout >= 0) {649s.connect(dest, connectTimeout);650} else {651if (defaultConnectTimeout > 0) {652s.connect(dest, defaultConnectTimeout);653} else {654s.connect(dest);655}656}657if (readTimeout >= 0) {658s.setSoTimeout(readTimeout);659} else if (defaultSoTimeout > 0) {660s.setSoTimeout(defaultSoTimeout);661}662if (useCrypto) {663try {664s = sslFact.createSocket(s, dest.getHostName(), dest.getPort(), true);665} catch (Exception e) {666throw new sun.net.ftp.FtpProtocolException("Can't open secure data channel: " + e);667}668}669if (!issueCommand(cmd)) {670s.close();671if (getLastReplyCode() == FtpReplyCode.FILE_UNAVAILABLE) {672// Ensure backward compatibility673throw new FileNotFoundException(cmd);674}675throw new sun.net.ftp.FtpProtocolException(cmd + ":" + getResponseString(), getLastReplyCode());676}677return s;678}679680/**681* Opens a data connection with the server according to the set mode682* (ACTIVE or PASSIVE) then send the command passed as an argument.683*684* @param cmd the <code>String</code> containing the command to execute685* @return the connected <code>Socket</code>686* @throws IOException if the connection or command failed687*/688private Socket openDataConnection(String cmd) throws sun.net.ftp.FtpProtocolException, IOException {689Socket clientSocket;690691if (passiveMode) {692try {693return openPassiveDataConnection(cmd);694} catch (sun.net.ftp.FtpProtocolException e) {695// If Passive mode failed, fall back on PORT696// Otherwise throw exception697String errmsg = e.getMessage();698if (!errmsg.startsWith("PASV") && !errmsg.startsWith("EPSV")) {699throw e;700}701}702}703ServerSocket portSocket;704InetAddress myAddress;705String portCmd;706707if (proxy != null && proxy.type() == Proxy.Type.SOCKS) {708// We're behind a firewall and the passive mode fail,709// since we can't accept a connection through SOCKS (yet)710// throw an exception711throw new sun.net.ftp.FtpProtocolException("Passive mode failed");712}713// Bind the ServerSocket to the same address as the control channel714// This is needed for multi-homed systems715portSocket = new ServerSocket(0, 1, server.getLocalAddress());716try {717myAddress = portSocket.getInetAddress();718if (myAddress.isAnyLocalAddress()) {719myAddress = server.getLocalAddress();720}721// Let's try the new, IPv6 compatible EPRT command722// See RFC2428 for specifics723// Some FTP servers (like the one on Solaris) are bugged, they724// will accept the EPRT command but then, the subsequent command725// (e.g. RETR) will fail, so we have to check BOTH results (the726// EPRT cmd then the actual command) to decide whether we should727// fall back on the older PORT command.728portCmd = "EPRT |" + ((myAddress instanceof Inet6Address) ? "2" : "1") + "|" +729myAddress.getHostAddress() + "|" + portSocket.getLocalPort() + "|";730if (!issueCommand(portCmd) || !issueCommand(cmd)) {731// The EPRT command failed, let's fall back to good old PORT732portCmd = "PORT ";733byte[] addr = myAddress.getAddress();734735/* append host addr */736for (int i = 0; i < addr.length; i++) {737portCmd = portCmd + (addr[i] & 0xFF) + ",";738}739740/* append port number */741portCmd = portCmd + ((portSocket.getLocalPort() >>> 8) & 0xff) + "," + (portSocket.getLocalPort() & 0xff);742issueCommandCheck(portCmd);743issueCommandCheck(cmd);744}745// Either the EPRT or the PORT command was successful746// Let's create the client socket747if (connectTimeout >= 0) {748portSocket.setSoTimeout(connectTimeout);749} else {750if (defaultConnectTimeout > 0) {751portSocket.setSoTimeout(defaultConnectTimeout);752}753}754clientSocket = portSocket.accept();755if (readTimeout >= 0) {756clientSocket.setSoTimeout(readTimeout);757} else {758if (defaultSoTimeout > 0) {759clientSocket.setSoTimeout(defaultSoTimeout);760}761}762} finally {763portSocket.close();764}765if (useCrypto) {766try {767clientSocket = sslFact.createSocket(clientSocket, serverAddr.getHostName(), serverAddr.getPort(), true);768} catch (Exception ex) {769throw new IOException(ex.getLocalizedMessage());770}771}772return clientSocket;773}774775private InputStream createInputStream(InputStream in) {776if (type == TransferType.ASCII) {777return new sun.net.TelnetInputStream(in, false);778}779return in;780}781782private OutputStream createOutputStream(OutputStream out) {783if (type == TransferType.ASCII) {784return new sun.net.TelnetOutputStream(out, false);785}786return out;787}788789/**790* Creates an instance of FtpClient. The client is not connected to any791* server yet.792*793*/794protected FtpClient() {795}796797/**798* Creates an instance of FtpClient. The client is not connected to any799* server yet.800*801*/802public static sun.net.ftp.FtpClient create() {803return new FtpClient();804}805806/**807* Set the transfer mode to <I>passive</I>. In that mode, data connections808* are established by having the client connect to the server.809* This is the recommended default mode as it will work best through810* firewalls and NATs.811*812* @return This FtpClient813* @see #setActiveMode()814*/815public sun.net.ftp.FtpClient enablePassiveMode(boolean passive) {816817// Only passive mode used in JDK. See Bug 8010784.818// passiveMode = passive;819return this;820}821822/**823* Gets the current transfer mode.824*825* @return the current <code>FtpTransferMode</code>826*/827public boolean isPassiveModeEnabled() {828return passiveMode;829}830831/**832* Sets the timeout value to use when connecting to the server,833*834* @param timeout the timeout value, in milliseconds, to use for the connect835* operation. A value of zero or less, means use the default timeout.836*837* @return This FtpClient838*/839public sun.net.ftp.FtpClient setConnectTimeout(int timeout) {840connectTimeout = timeout;841return this;842}843844/**845* Returns the current connection timeout value.846*847* @return the value, in milliseconds, of the current connect timeout.848* @see #setConnectTimeout(int)849*/850public int getConnectTimeout() {851return connectTimeout;852}853854/**855* Sets the timeout value to use when reading from the server,856*857* @param timeout the timeout value, in milliseconds, to use for the read858* operation. A value of zero or less, means use the default timeout.859* @return This FtpClient860*/861public sun.net.ftp.FtpClient setReadTimeout(int timeout) {862readTimeout = timeout;863return this;864}865866/**867* Returns the current read timeout value.868*869* @return the value, in milliseconds, of the current read timeout.870* @see #setReadTimeout(int)871*/872public int getReadTimeout() {873return readTimeout;874}875876public sun.net.ftp.FtpClient setProxy(Proxy p) {877proxy = p;878return this;879}880881/**882* Get the proxy of this FtpClient883*884* @return the <code>Proxy</code>, this client is using, or <code>null</code>885* if none is used.886* @see #setProxy(Proxy)887*/888public Proxy getProxy() {889return proxy;890}891892/**893* Connects to the specified destination.894*895* @param dest the <code>InetSocketAddress</code> to connect to.896* @throws IOException if the connection fails.897*/898private void tryConnect(InetSocketAddress dest, int timeout) throws IOException {899if (isConnected()) {900disconnect();901}902server = doConnect(dest, timeout);903try {904out = new PrintStream(new BufferedOutputStream(server.getOutputStream()),905true, encoding);906} catch (UnsupportedEncodingException e) {907throw new InternalError(encoding + "encoding not found", e);908}909in = new BufferedInputStream(server.getInputStream());910}911912private Socket doConnect(InetSocketAddress dest, int timeout) throws IOException {913Socket s;914if (proxy != null) {915if (proxy.type() == Proxy.Type.SOCKS) {916PrivilegedAction<Socket> pa = () -> new Socket(proxy);917@SuppressWarnings("removal")918var tmp = AccessController.doPrivileged(pa);919s = tmp;920} else {921s = new Socket(Proxy.NO_PROXY);922}923} else {924s = new Socket();925}926// Instance specific timeouts do have priority, that means927// connectTimeout & readTimeout (-1 means not set)928// Then global default timeouts929// Then no timeout.930if (timeout >= 0) {931s.connect(dest, timeout);932} else {933if (connectTimeout >= 0) {934s.connect(dest, connectTimeout);935} else {936if (defaultConnectTimeout > 0) {937s.connect(dest, defaultConnectTimeout);938} else {939s.connect(dest);940}941}942}943if (readTimeout >= 0) {944s.setSoTimeout(readTimeout);945} else if (defaultSoTimeout > 0) {946s.setSoTimeout(defaultSoTimeout);947}948return s;949}950951private void disconnect() throws IOException {952if (isConnected()) {953server.close();954}955server = null;956in = null;957out = null;958lastTransSize = -1;959lastFileName = null;960restartOffset = 0;961welcomeMsg = null;962lastReplyCode = null;963serverResponse.setSize(0);964}965966/**967* Tests whether this client is connected or not to a server.968*969* @return <code>true</code> if the client is connected.970*/971public boolean isConnected() {972return server != null;973}974975public SocketAddress getServerAddress() {976return server == null ? null : server.getRemoteSocketAddress();977}978979public sun.net.ftp.FtpClient connect(SocketAddress dest) throws sun.net.ftp.FtpProtocolException, IOException {980return connect(dest, -1);981}982983/**984* Connects the FtpClient to the specified destination.985*986* @param dest the address of the destination server987* @throws IOException if connection failed.988*/989public sun.net.ftp.FtpClient connect(SocketAddress dest, int timeout) throws sun.net.ftp.FtpProtocolException, IOException {990if (!(dest instanceof InetSocketAddress)) {991throw new IllegalArgumentException("Wrong address type");992}993serverAddr = (InetSocketAddress) dest;994tryConnect(serverAddr, timeout);995if (!readReply()) {996throw new sun.net.ftp.FtpProtocolException("Welcome message: " +997getResponseString(), lastReplyCode);998}999welcomeMsg = getResponseString().substring(4);1000return this;1001}10021003private void tryLogin(String user, char[] password) throws sun.net.ftp.FtpProtocolException, IOException {1004issueCommandCheck("USER " + user);10051006/*1007* Checks for "331 User name okay, need password." answer1008*/1009if (lastReplyCode == FtpReplyCode.NEED_PASSWORD) {1010if ((password != null) && (password.length > 0)) {1011issueCommandCheck("PASS " + String.valueOf(password));1012}1013}1014}10151016/**1017* Attempts to log on the server with the specified user name and password.1018*1019* @param user The user name1020* @param password The password for that user1021* @return <code>true</code> if the login was successful.1022* @throws IOException if an error occurred during the transmission1023*/1024public sun.net.ftp.FtpClient login(String user, char[] password) throws sun.net.ftp.FtpProtocolException, IOException {1025if (!isConnected()) {1026throw new sun.net.ftp.FtpProtocolException("Not connected yet", FtpReplyCode.BAD_SEQUENCE);1027}1028if (user == null || user.isEmpty()) {1029throw new IllegalArgumentException("User name can't be null or empty");1030}1031tryLogin(user, password);10321033// keep the welcome message around so we can1034// put it in the resulting HTML page.1035String l;1036StringBuilder sb = new StringBuilder();1037for (int i = 0; i < serverResponse.size(); i++) {1038l = serverResponse.elementAt(i);1039if (l != null) {1040if (l.length() >= 4 && l.startsWith("230")) {1041// get rid of the "230-" prefix1042l = l.substring(4);1043}1044sb.append(l);1045}1046}1047welcomeMsg = sb.toString();1048loggedIn = true;1049return this;1050}10511052/**1053* Attempts to log on the server with the specified user name, password and1054* account name.1055*1056* @param user The user name1057* @param password The password for that user.1058* @param account The account name for that user.1059* @return <code>true</code> if the login was successful.1060* @throws IOException if an error occurs during the transmission.1061*/1062public sun.net.ftp.FtpClient login(String user, char[] password, String account) throws sun.net.ftp.FtpProtocolException, IOException {10631064if (!isConnected()) {1065throw new sun.net.ftp.FtpProtocolException("Not connected yet", FtpReplyCode.BAD_SEQUENCE);1066}1067if (user == null || user.isEmpty()) {1068throw new IllegalArgumentException("User name can't be null or empty");1069}1070tryLogin(user, password);10711072/*1073* Checks for "332 Need account for login." answer1074*/1075if (lastReplyCode == FtpReplyCode.NEED_ACCOUNT) {1076issueCommandCheck("ACCT " + account);1077}10781079// keep the welcome message around so we can1080// put it in the resulting HTML page.1081StringBuilder sb = new StringBuilder();1082if (serverResponse != null) {1083for (String l : serverResponse) {1084if (l != null) {1085if (l.length() >= 4 && l.startsWith("230")) {1086// get rid of the "230-" prefix1087l = l.substring(4);1088}1089sb.append(l);1090}1091}1092}1093welcomeMsg = sb.toString();1094loggedIn = true;1095return this;1096}10971098/**1099* Logs out the current user. This is in effect terminates the current1100* session and the connection to the server will be closed.1101*1102*/1103public void close() throws IOException {1104if (isConnected()) {1105try {1106issueCommand("QUIT");1107} catch (FtpProtocolException e) {1108}1109loggedIn = false;1110}1111disconnect();1112}11131114/**1115* Checks whether the client is logged in to the server or not.1116*1117* @return <code>true</code> if the client has already completed a login.1118*/1119public boolean isLoggedIn() {1120return loggedIn;1121}11221123/**1124* Changes to a specific directory on a remote FTP server1125*1126* @param remoteDirectory path of the directory to CD to.1127* @return <code>true</code> if the operation was successful.1128* @exception <code>FtpProtocolException</code>1129*/1130public sun.net.ftp.FtpClient changeDirectory(String remoteDirectory) throws sun.net.ftp.FtpProtocolException, IOException {1131if (remoteDirectory == null || remoteDirectory.isEmpty()) {1132throw new IllegalArgumentException("directory can't be null or empty");1133}11341135issueCommandCheck("CWD " + remoteDirectory);1136return this;1137}11381139/**1140* Changes to the parent directory, sending the CDUP command to the server.1141*1142* @return <code>true</code> if the command was successful.1143* @throws IOException1144*/1145public sun.net.ftp.FtpClient changeToParentDirectory() throws sun.net.ftp.FtpProtocolException, IOException {1146issueCommandCheck("CDUP");1147return this;1148}11491150/**1151* Returns the server current working directory, or <code>null</code> if1152* the PWD command failed.1153*1154* @return a <code>String</code> containing the current working directory,1155* or <code>null</code>1156* @throws IOException1157*/1158public String getWorkingDirectory() throws sun.net.ftp.FtpProtocolException, IOException {1159issueCommandCheck("PWD");1160/*1161* answer will be of the following format :1162*1163* 257 "/" is current directory.1164*/1165String answ = getResponseString();1166if (!answ.startsWith("257")) {1167return null;1168}1169return answ.substring(5, answ.lastIndexOf('"'));1170}11711172/**1173* Sets the restart offset to the specified value. That value will be1174* sent through a <code>REST</code> command to server before a file1175* transfer and has the effect of resuming a file transfer from the1176* specified point. After a transfer the restart offset is set back to1177* zero.1178*1179* @param offset the offset in the remote file at which to start the next1180* transfer. This must be a value greater than or equal to zero.1181* @throws IllegalArgumentException if the offset is negative.1182*/1183public sun.net.ftp.FtpClient setRestartOffset(long offset) {1184if (offset < 0) {1185throw new IllegalArgumentException("offset can't be negative");1186}1187restartOffset = offset;1188return this;1189}11901191/**1192* Retrieves a file from the ftp server and writes it to the specified1193* <code>OutputStream</code>.1194* If the restart offset was set, then a <code>REST</code> command will be1195* sent before the RETR in order to restart the tranfer from the specified1196* offset.1197* The <code>OutputStream</code> is not closed by this method at the end1198* of the transfer.1199*1200* @param name a {@code String} containing the name of the file to1201* retreive from the server.1202* @param local the <code>OutputStream</code> the file should be written to.1203* @throws IOException if the transfer fails.1204*/1205public sun.net.ftp.FtpClient getFile(String name, OutputStream local) throws sun.net.ftp.FtpProtocolException, IOException {1206if (restartOffset > 0) {1207Socket s;1208try {1209s = openDataConnection("REST " + restartOffset);1210} finally {1211restartOffset = 0;1212}1213issueCommandCheck("RETR " + name);1214getTransferSize();1215try (InputStream remote = createInputStream(s.getInputStream())) {1216remote.transferTo(local);1217}1218} else {1219Socket s = openDataConnection("RETR " + name);1220getTransferSize();1221try (InputStream remote = createInputStream(s.getInputStream())) {1222remote.transferTo(local);1223}1224}1225return completePending();1226}12271228/**1229* Retrieves a file from the ftp server, using the RETR command, and1230* returns the InputStream from* the established data connection.1231* {@link #completePending()} <b>has</b> to be called once the application1232* is done reading from the returned stream.1233*1234* @param name the name of the remote file1235* @return the {@link java.io.InputStream} from the data connection, or1236* <code>null</code> if the command was unsuccessful.1237* @throws IOException if an error occurred during the transmission.1238*/1239public InputStream getFileStream(String name) throws sun.net.ftp.FtpProtocolException, IOException {1240Socket s;1241if (restartOffset > 0) {1242try {1243s = openDataConnection("REST " + restartOffset);1244} finally {1245restartOffset = 0;1246}1247if (s == null) {1248return null;1249}1250issueCommandCheck("RETR " + name);1251getTransferSize();1252return createInputStream(s.getInputStream());1253}12541255s = openDataConnection("RETR " + name);1256if (s == null) {1257return null;1258}1259getTransferSize();1260return createInputStream(s.getInputStream());1261}12621263/**1264* Transfers a file from the client to the server (aka a <I>put</I>)1265* by sending the STOR or STOU command, depending on the1266* <code>unique</code> argument, and returns the <code>OutputStream</code>1267* from the established data connection.1268* {@link #completePending()} <b>has</b> to be called once the application1269* is finished writing to the stream.1270*1271* A new file is created at the server site if the file specified does1272* not already exist.1273*1274* If <code>unique</code> is set to <code>true</code>, the resultant file1275* is to be created under a name unique to that directory, meaning1276* it will not overwrite an existing file, instead the server will1277* generate a new, unique, file name.1278* The name of the remote file can be retrieved, after completion of the1279* transfer, by calling {@link #getLastFileName()}.1280*1281* @param name the name of the remote file to write.1282* @param unique <code>true</code> if the remote files should be unique,1283* in which case the STOU command will be used.1284* @return the {@link java.io.OutputStream} from the data connection or1285* <code>null</code> if the command was unsuccessful.1286* @throws IOException if an error occurred during the transmission.1287*/1288public OutputStream putFileStream(String name, boolean unique)1289throws sun.net.ftp.FtpProtocolException, IOException1290{1291String cmd = unique ? "STOU " : "STOR ";1292Socket s = openDataConnection(cmd + name);1293if (s == null) {1294return null;1295}1296boolean bm = (type == TransferType.BINARY);1297return new sun.net.TelnetOutputStream(s.getOutputStream(), bm);1298}12991300/**1301* Transfers a file from the client to the server (aka a <I>put</I>)1302* by sending the STOR command. The content of the <code>InputStream</code>1303* passed in argument is written into the remote file, overwriting any1304* existing data.1305*1306* A new file is created at the server site if the file specified does1307* not already exist.1308*1309* @param name the name of the remote file to write.1310* @param local the <code>InputStream</code> that points to the data to1311* transfer.1312* @param unique <code>true</code> if the remote file should be unique1313* (i.e. not already existing), <code>false</code> otherwise.1314* @return <code>true</code> if the transfer was successful.1315* @throws IOException if an error occurred during the transmission.1316* @see #getLastFileName()1317*/1318public sun.net.ftp.FtpClient putFile(String name, InputStream local, boolean unique) throws sun.net.ftp.FtpProtocolException, IOException {1319String cmd = unique ? "STOU " : "STOR ";1320if (type == TransferType.BINARY) {1321Socket s = openDataConnection(cmd + name);1322try (OutputStream remote = createOutputStream(s.getOutputStream())) {1323local.transferTo(remote);1324}1325}1326return completePending();1327}13281329/**1330* Sends the APPE command to the server in order to transfer a data stream1331* passed in argument and append it to the content of the specified remote1332* file.1333*1334* @param name A <code>String</code> containing the name of the remote file1335* to append to.1336* @param local The <code>InputStream</code> providing access to the data1337* to be appended.1338* @return <code>true</code> if the transfer was successful.1339* @throws IOException if an error occurred during the transmission.1340*/1341public sun.net.ftp.FtpClient appendFile(String name, InputStream local) throws sun.net.ftp.FtpProtocolException, IOException {1342Socket s = openDataConnection("APPE " + name);1343try (OutputStream remote = createOutputStream(s.getOutputStream())) {1344local.transferTo(remote);1345}1346return completePending();1347}13481349/**1350* Renames a file on the server.1351*1352* @param from the name of the file being renamed1353* @param to the new name for the file1354* @throws IOException if the command fails1355*/1356public sun.net.ftp.FtpClient rename(String from, String to) throws sun.net.ftp.FtpProtocolException, IOException {1357issueCommandCheck("RNFR " + from);1358issueCommandCheck("RNTO " + to);1359return this;1360}13611362/**1363* Deletes a file on the server.1364*1365* @param name a <code>String</code> containing the name of the file1366* to delete.1367* @return <code>true</code> if the command was successful1368* @throws IOException if an error occurred during the exchange1369*/1370public sun.net.ftp.FtpClient deleteFile(String name) throws sun.net.ftp.FtpProtocolException, IOException {1371issueCommandCheck("DELE " + name);1372return this;1373}13741375/**1376* Creates a new directory on the server.1377*1378* @param name a <code>String</code> containing the name of the directory1379* to create.1380* @return <code>true</code> if the operation was successful.1381* @throws IOException if an error occurred during the exchange1382*/1383public sun.net.ftp.FtpClient makeDirectory(String name) throws sun.net.ftp.FtpProtocolException, IOException {1384issueCommandCheck("MKD " + name);1385return this;1386}13871388/**1389* Removes a directory on the server.1390*1391* @param name a <code>String</code> containing the name of the directory1392* to remove.1393*1394* @return <code>true</code> if the operation was successful.1395* @throws IOException if an error occurred during the exchange.1396*/1397public sun.net.ftp.FtpClient removeDirectory(String name) throws sun.net.ftp.FtpProtocolException, IOException {1398issueCommandCheck("RMD " + name);1399return this;1400}14011402/**1403* Sends a No-operation command. It's useful for testing the connection1404* status or as a <I>keep alive</I> mechanism.1405*1406* @throws FtpProtocolException if the command fails1407*/1408public sun.net.ftp.FtpClient noop() throws sun.net.ftp.FtpProtocolException, IOException {1409issueCommandCheck("NOOP");1410return this;1411}14121413/**1414* Sends the STAT command to the server.1415* This can be used while a data connection is open to get a status1416* on the current transfer, in that case the parameter should be1417* <code>null</code>.1418* If used between file transfers, it may have a pathname as argument1419* in which case it will work as the LIST command except no data1420* connection will be created.1421*1422* @param name an optional <code>String</code> containing the pathname1423* the STAT command should apply to.1424* @return the response from the server or <code>null</code> if the1425* command failed.1426* @throws IOException if an error occurred during the transmission.1427*/1428public String getStatus(String name) throws sun.net.ftp.FtpProtocolException, IOException {1429issueCommandCheck((name == null ? "STAT" : "STAT " + name));1430/*1431* A typical response will be:1432* 213-status of t32.gif:1433* -rw-r--r-- 1 jcc staff 247445 Feb 17 1998 t32.gif1434* 213 End of Status1435*1436* or1437*1438* 211-jsn FTP server status:1439* Version wu-2.6.2+Sun1440* Connected to localhost (::1)1441* Logged in as jccollet1442* TYPE: ASCII, FORM: Nonprint; STRUcture: File; transfer MODE: Stream1443* No data connection1444* 0 data bytes received in 0 files1445* 0 data bytes transmitted in 0 files1446* 0 data bytes total in 0 files1447* 53 traffic bytes received in 0 transfers1448* 485 traffic bytes transmitted in 0 transfers1449* 587 traffic bytes total in 0 transfers1450* 211 End of status1451*1452* So we need to remove the 1st and last line1453*/1454Vector<String> resp = getResponseStrings();1455StringBuilder sb = new StringBuilder();1456for (int i = 1; i < resp.size() - 1; i++) {1457sb.append(resp.get(i));1458}1459return sb.toString();1460}14611462/**1463* Sends the FEAT command to the server and returns the list of supported1464* features in the form of strings.1465*1466* The features are the supported commands, like AUTH TLS, PROT or PASV.1467* See the RFCs for a complete list.1468*1469* Note that not all FTP servers support that command, in which case1470* the method will return <code>null</code>1471*1472* @return a <code>List</code> of <code>Strings</code> describing the1473* supported additional features, or <code>null</code>1474* if the command is not supported.1475* @throws IOException if an error occurs during the transmission.1476*/1477public List<String> getFeatures() throws sun.net.ftp.FtpProtocolException, IOException {1478/*1479* The FEAT command, when implemented will return something like:1480*1481* 211-Features:1482* AUTH TLS1483* PBSZ1484* PROT1485* EPSV1486* EPRT1487* PASV1488* REST STREAM1489* 211 END1490*/1491ArrayList<String> features = new ArrayList<String>();1492issueCommandCheck("FEAT");1493Vector<String> resp = getResponseStrings();1494// Note that we start at index 1 to skip the 1st line (211-...)1495// and we stop before the last line.1496for (int i = 1; i < resp.size() - 1; i++) {1497String s = resp.get(i);1498// Get rid of leading space and trailing newline1499features.add(s.substring(1, s.length() - 1));1500}1501return features;1502}15031504/**1505* sends the ABOR command to the server.1506* It tells the server to stop the previous command or transfer.1507*1508* @return <code>true</code> if the command was successful.1509* @throws IOException if an error occurred during the transmission.1510*/1511public sun.net.ftp.FtpClient abort() throws sun.net.ftp.FtpProtocolException, IOException {1512issueCommandCheck("ABOR");1513// TODO: Must check the ReplyCode:1514/*1515* From the RFC:1516* There are two cases for the server upon receipt of this1517* command: (1) the FTP service command was already completed,1518* or (2) the FTP service command is still in progress.1519* In the first case, the server closes the data connection1520* (if it is open) and responds with a 226 reply, indicating1521* that the abort command was successfully processed.1522* In the second case, the server aborts the FTP service in1523* progress and closes the data connection, returning a 4261524* reply to indicate that the service request terminated1525* abnormally. The server then sends a 226 reply,1526* indicating that the abort command was successfully1527* processed.1528*/152915301531return this;1532}15331534/**1535* Some methods do not wait until completion before returning, so this1536* method can be called to wait until completion. This is typically the case1537* with commands that trigger a transfer like {@link #getFileStream(String)}.1538* So this method should be called before accessing information related to1539* such a command.1540* <p>This method will actually block reading on the command channel for a1541* notification from the server that the command is finished. Such a1542* notification often carries extra information concerning the completion1543* of the pending action (e.g. number of bytes transfered).</p>1544* <p>Note that this will return true immediately if no command or action1545* is pending</p>1546* <p>It should be also noted that most methods issuing commands to the ftp1547* server will call this method if a previous command is pending.1548* <p>Example of use:1549* <pre>1550* InputStream in = cl.getFileStream("file");1551* ...1552* cl.completePending();1553* long size = cl.getLastTransferSize();1554* </pre>1555* On the other hand, it's not necessary in a case like:1556* <pre>1557* InputStream in = cl.getFileStream("file");1558* // read content1559* ...1560* cl.logout();1561* </pre>1562* <p>Since {@link #logout()} will call completePending() if necessary.</p>1563* @return <code>true</code> if the completion was successful or if no1564* action was pending.1565* @throws IOException1566*/1567public sun.net.ftp.FtpClient completePending() throws sun.net.ftp.FtpProtocolException, IOException {1568while (replyPending) {1569replyPending = false;1570if (!readReply()) {1571throw new sun.net.ftp.FtpProtocolException(getLastResponseString(), lastReplyCode);1572}1573}1574return this;1575}15761577/**1578* Reinitializes the USER parameters on the FTP server1579*1580* @throws FtpProtocolException if the command fails1581*/1582public sun.net.ftp.FtpClient reInit() throws sun.net.ftp.FtpProtocolException, IOException {1583issueCommandCheck("REIN");1584loggedIn = false;1585if (useCrypto) {1586if (server instanceof SSLSocket) {1587javax.net.ssl.SSLSession session = ((SSLSocket) server).getSession();1588session.invalidate();1589// Restore previous socket and streams1590server = oldSocket;1591oldSocket = null;1592try {1593out = new PrintStream(new BufferedOutputStream(server.getOutputStream()),1594true, encoding);1595} catch (UnsupportedEncodingException e) {1596throw new InternalError(encoding + "encoding not found", e);1597}1598in = new BufferedInputStream(server.getInputStream());1599}1600}1601useCrypto = false;1602return this;1603}16041605/**1606* Changes the transfer type (binary, ascii, ebcdic) and issue the1607* proper command (e.g. TYPE A) to the server.1608*1609* @param type the <code>FtpTransferType</code> to use.1610* @return This FtpClient1611* @throws IOException if an error occurs during transmission.1612*/1613public sun.net.ftp.FtpClient setType(TransferType type) throws sun.net.ftp.FtpProtocolException, IOException {1614String cmd = "NOOP";16151616this.type = type;1617if (type == TransferType.ASCII) {1618cmd = "TYPE A";1619}1620if (type == TransferType.BINARY) {1621cmd = "TYPE I";1622}1623if (type == TransferType.EBCDIC) {1624cmd = "TYPE E";1625}1626issueCommandCheck(cmd);1627return this;1628}16291630/**1631* Issues a LIST command to the server to get the current directory1632* listing, and returns the InputStream from the data connection.1633* {@link #completePending()} <b>has</b> to be called once the application1634* is finished writing to the stream.1635*1636* @param path the pathname of the directory to list, or <code>null</code>1637* for the current working directory.1638* @return the <code>InputStream</code> from the resulting data connection1639* @throws IOException if an error occurs during the transmission.1640* @see #changeDirectory(String)1641* @see #listFiles(String)1642*/1643public InputStream list(String path) throws sun.net.ftp.FtpProtocolException, IOException {1644Socket s;1645s = openDataConnection(path == null ? "LIST" : "LIST " + path);1646if (s != null) {1647return createInputStream(s.getInputStream());1648}1649return null;1650}16511652/**1653* Issues a NLST path command to server to get the specified directory1654* content. It differs from {@link #list(String)} method by the fact that1655* it will only list the file names which would make the parsing of the1656* somewhat easier.1657*1658* {@link #completePending()} <b>has</b> to be called once the application1659* is finished writing to the stream.1660*1661* @param path a <code>String</code> containing the pathname of the1662* directory to list or <code>null</code> for the current working1663* directory.1664* @return the <code>InputStream</code> from the resulting data connection1665* @throws IOException if an error occurs during the transmission.1666*/1667public InputStream nameList(String path) throws sun.net.ftp.FtpProtocolException, IOException {1668Socket s;1669s = openDataConnection(path == null ? "NLST" : "NLST " + path);1670if (s != null) {1671return createInputStream(s.getInputStream());1672}1673return null;1674}16751676/**1677* Issues the SIZE [path] command to the server to get the size of a1678* specific file on the server.1679* Note that this command may not be supported by the server. In which1680* case -1 will be returned.1681*1682* @param path a <code>String</code> containing the pathname of the1683* file.1684* @return a <code>long</code> containing the size of the file or -1 if1685* the server returned an error, which can be checked with1686* {@link #getLastReplyCode()}.1687* @throws IOException if an error occurs during the transmission.1688*/1689public long getSize(String path) throws sun.net.ftp.FtpProtocolException, IOException {1690if (path == null || path.isEmpty()) {1691throw new IllegalArgumentException("path can't be null or empty");1692}1693issueCommandCheck("SIZE " + path);1694if (lastReplyCode == FtpReplyCode.FILE_STATUS) {1695String s = getResponseString();1696s = s.substring(4, s.length() - 1);1697return Long.parseLong(s);1698}1699return -1;1700}17011702private static final DateTimeFormatter RFC3659_DATETIME_FORMAT = DateTimeFormatter.ofPattern("yyyyMMddHHmmss[.SSS]")1703.withZone(ZoneOffset.UTC);17041705/**1706* Issues the MDTM [path] command to the server to get the modification1707* time of a specific file on the server.1708* Note that this command may not be supported by the server, in which1709* case <code>null</code> will be returned.1710*1711* @param path a <code>String</code> containing the pathname of the file.1712* @return a <code>Date</code> representing the last modification time1713* or <code>null</code> if the server returned an error, which1714* can be checked with {@link #getLastReplyCode()}.1715* @throws IOException if an error occurs during the transmission.1716*/1717public Date getLastModified(String path) throws sun.net.ftp.FtpProtocolException, IOException {1718issueCommandCheck("MDTM " + path);1719if (lastReplyCode == FtpReplyCode.FILE_STATUS) {1720String s = getResponseString();1721return parseRfc3659TimeValue(s.substring(4, s.length() - 1));1722}1723return null;1724}17251726private static Date parseRfc3659TimeValue(String s) {1727Date result = null;1728try {1729var d = ZonedDateTime.parse(s, RFC3659_DATETIME_FORMAT);1730result = Date.from(d.toInstant());1731} catch (DateTimeParseException ex) {1732}1733return result;1734}17351736/**1737* Sets the parser used to handle the directory output to the specified1738* one. By default the parser is set to one that can handle most FTP1739* servers output (Unix base mostly). However it may be necessary for1740* and application to provide its own parser due to some uncommon1741* output format.1742*1743* @param p The <code>FtpDirParser</code> to use.1744* @see #listFiles(String)1745*/1746public sun.net.ftp.FtpClient setDirParser(FtpDirParser p) {1747parser = p;1748return this;1749}17501751private static class FtpFileIterator implements Iterator<FtpDirEntry>, Closeable {17521753private BufferedReader in = null;1754private FtpDirEntry nextFile = null;1755private FtpDirParser fparser = null;1756private boolean eof = false;17571758public FtpFileIterator(FtpDirParser p, BufferedReader in) {1759this.in = in;1760this.fparser = p;1761readNext();1762}17631764private void readNext() {1765nextFile = null;1766if (eof) {1767return;1768}1769String line = null;1770try {1771do {1772line = in.readLine();1773if (line != null) {1774nextFile = fparser.parseLine(line);1775if (nextFile != null) {1776return;1777}1778}1779} while (line != null);1780in.close();1781} catch (IOException iOException) {1782}1783eof = true;1784}17851786public boolean hasNext() {1787return nextFile != null;1788}17891790public FtpDirEntry next() {1791FtpDirEntry ret = nextFile;1792readNext();1793return ret;1794}17951796public void remove() {1797throw new UnsupportedOperationException("Not supported yet.");1798}17991800public void close() throws IOException {1801if (in != null && !eof) {1802in.close();1803}1804eof = true;1805nextFile = null;1806}1807}18081809/**1810* Issues a MLSD command to the server to get the specified directory1811* listing and applies the current parser to create an Iterator of1812* {@link java.net.ftp.FtpDirEntry}. Note that the Iterator returned is also a1813* {@link java.io.Closeable}.1814* If the server doesn't support the MLSD command, the LIST command is used1815* instead.1816*1817* {@link #completePending()} <b>has</b> to be called once the application1818* is finished iterating through the files.1819*1820* @param path the pathname of the directory to list or <code>null</code>1821* for the current working directoty.1822* @return a <code>Iterator</code> of files or <code>null</code> if the1823* command failed.1824* @throws IOException if an error occurred during the transmission1825* @see #setDirParser(FtpDirParser)1826* @see #changeDirectory(String)1827*/1828public Iterator<FtpDirEntry> listFiles(String path) throws sun.net.ftp.FtpProtocolException, IOException {1829Socket s = null;1830BufferedReader sin = null;1831try {1832s = openDataConnection(path == null ? "MLSD" : "MLSD " + path);1833} catch (sun.net.ftp.FtpProtocolException FtpException) {1834// The server doesn't understand new MLSD command, ignore and fall1835// back to LIST1836}18371838if (s != null) {1839sin = new BufferedReader(new InputStreamReader(s.getInputStream()));1840return new FtpFileIterator(mlsxParser, sin);1841} else {1842s = openDataConnection(path == null ? "LIST" : "LIST " + path);1843if (s != null) {1844sin = new BufferedReader(new InputStreamReader(s.getInputStream()));1845return new FtpFileIterator(parser, sin);1846}1847}1848return null;1849}18501851private boolean sendSecurityData(byte[] buf) throws IOException,1852sun.net.ftp.FtpProtocolException {1853String s = Base64.getMimeEncoder().encodeToString(buf);1854return issueCommand("ADAT " + s);1855}18561857private byte[] getSecurityData() {1858String s = getLastResponseString();1859if (s.substring(4, 9).equalsIgnoreCase("ADAT=")) {1860// Need to get rid of the leading '315 ADAT='1861// and the trailing newline1862return Base64.getMimeDecoder().decode(s.substring(9, s.length() - 1));1863}1864return null;1865}18661867/**1868* Attempts to use Kerberos GSSAPI as an authentication mechanism with the1869* ftp server. This will issue an <code>AUTH GSSAPI</code> command, and if1870* it is accepted by the server, will followup with <code>ADAT</code>1871* command to exchange the various tokens until authentification is1872* successful. This conforms to Appendix I of RFC 2228.1873*1874* @return <code>true</code> if authentication was successful.1875* @throws IOException if an error occurs during the transmission.1876*/1877public sun.net.ftp.FtpClient useKerberos() throws sun.net.ftp.FtpProtocolException, IOException {1878/*1879* Comment out for the moment since it's not in use and would create1880* needless cross-package links.1881*1882issueCommandCheck("AUTH GSSAPI");1883if (lastReplyCode != FtpReplyCode.NEED_ADAT)1884throw new sun.net.ftp.FtpProtocolException("Unexpected reply from server");1885try {1886GSSManager manager = GSSManager.getInstance();1887GSSName name = manager.createName("SERVICE:ftp@"+1888serverAddr.getHostName(), null);1889GSSContext context = manager.createContext(name, null, null,1890GSSContext.DEFAULT_LIFETIME);1891context.requestMutualAuth(true);1892context.requestReplayDet(true);1893context.requestSequenceDet(true);1894context.requestCredDeleg(true);1895byte []inToken = new byte[0];1896while (!context.isEstablished()) {1897byte[] outToken1898= context.initSecContext(inToken, 0, inToken.length);1899// send the output token if generated1900if (outToken != null) {1901if (sendSecurityData(outToken)) {1902inToken = getSecurityData();1903}1904}1905}1906loggedIn = true;1907} catch (GSSException e) {19081909}1910*/1911return this;1912}19131914/**1915* Returns the Welcome string the server sent during initial connection.1916*1917* @return a <code>String</code> containing the message the server1918* returned during connection or <code>null</code>.1919*/1920public String getWelcomeMsg() {1921return welcomeMsg;1922}19231924/**1925* Returns the last reply code sent by the server.1926*1927* @return the lastReplyCode1928*/1929public FtpReplyCode getLastReplyCode() {1930return lastReplyCode;1931}19321933/**1934* Returns the last response string sent by the server.1935*1936* @return the message string, which can be quite long, last returned1937* by the server.1938*/1939public String getLastResponseString() {1940StringBuilder sb = new StringBuilder();1941if (serverResponse != null) {1942for (String l : serverResponse) {1943if (l != null) {1944sb.append(l);1945}1946}1947}1948return sb.toString();1949}19501951/**1952* Returns, when available, the size of the latest started transfer.1953* This is retreived by parsing the response string received as an initial1954* response to a RETR or similar request.1955*1956* @return the size of the latest transfer or -1 if either there was no1957* transfer or the information was unavailable.1958*/1959public long getLastTransferSize() {1960return lastTransSize;1961}19621963/**1964* Returns, when available, the remote name of the last transfered file.1965* This is mainly useful for "put" operation when the unique flag was1966* set since it allows to recover the unique file name created on the1967* server which may be different from the one submitted with the command.1968*1969* @return the name the latest transfered file remote name, or1970* <code>null</code> if that information is unavailable.1971*/1972public String getLastFileName() {1973return lastFileName;1974}19751976/**1977* Attempts to switch to a secure, encrypted connection. This is done by1978* sending the "AUTH TLS" command.1979* <p>See <a href="http://www.ietf.org/rfc/rfc4217.txt">RFC 4217</a></p>1980* If successful this will establish a secure command channel with the1981* server, it will also make it so that all other transfers (e.g. a RETR1982* command) will be done over an encrypted channel as well unless a1983* {@link #reInit()} command or a {@link #endSecureSession()} command is issued.1984*1985* @return <code>true</code> if the operation was successful.1986* @throws IOException if an error occurred during the transmission.1987* @see #endSecureSession()1988*/1989public sun.net.ftp.FtpClient startSecureSession() throws sun.net.ftp.FtpProtocolException, IOException {1990if (!isConnected()) {1991throw new sun.net.ftp.FtpProtocolException("Not connected yet", FtpReplyCode.BAD_SEQUENCE);1992}1993if (sslFact == null) {1994try {1995sslFact = (SSLSocketFactory) SSLSocketFactory.getDefault();1996} catch (Exception e) {1997throw new IOException(e.getLocalizedMessage());1998}1999}2000issueCommandCheck("AUTH TLS");2001Socket s = null;2002try {2003s = sslFact.createSocket(server, serverAddr.getHostName(), serverAddr.getPort(), true);2004} catch (javax.net.ssl.SSLException ssle) {2005try {2006disconnect();2007} catch (Exception e) {2008}2009throw ssle;2010}2011// Remember underlying socket so we can restore it later2012oldSocket = server;2013server = s;2014try {2015out = new PrintStream(new BufferedOutputStream(server.getOutputStream()),2016true, encoding);2017} catch (UnsupportedEncodingException e) {2018throw new InternalError(encoding + "encoding not found", e);2019}2020in = new BufferedInputStream(server.getInputStream());20212022issueCommandCheck("PBSZ 0");2023issueCommandCheck("PROT P");2024useCrypto = true;2025return this;2026}20272028/**2029* Sends a <code>CCC</code> command followed by a <code>PROT C</code>2030* command to the server terminating an encrypted session and reverting2031* back to a non crypted transmission.2032*2033* @return <code>true</code> if the operation was successful.2034* @throws IOException if an error occurred during transmission.2035* @see #startSecureSession()2036*/2037public sun.net.ftp.FtpClient endSecureSession() throws sun.net.ftp.FtpProtocolException, IOException {2038if (!useCrypto) {2039return this;2040}20412042issueCommandCheck("CCC");2043issueCommandCheck("PROT C");2044useCrypto = false;2045// Restore previous socket and streams2046server = oldSocket;2047oldSocket = null;2048try {2049out = new PrintStream(new BufferedOutputStream(server.getOutputStream()),2050true, encoding);2051} catch (UnsupportedEncodingException e) {2052throw new InternalError(encoding + "encoding not found", e);2053}2054in = new BufferedInputStream(server.getInputStream());20552056return this;2057}20582059/**2060* Sends the "Allocate" (ALLO) command to the server telling it to2061* pre-allocate the specified number of bytes for the next transfer.2062*2063* @param size The number of bytes to allocate.2064* @return <code>true</code> if the operation was successful.2065* @throws IOException if an error occurred during the transmission.2066*/2067public sun.net.ftp.FtpClient allocate(long size) throws sun.net.ftp.FtpProtocolException, IOException {2068issueCommandCheck("ALLO " + size);2069return this;2070}20712072/**2073* Sends the "Structure Mount" (SMNT) command to the server. This let the2074* user mount a different file system data structure without altering his2075* login or accounting information.2076*2077* @param struct a <code>String</code> containing the name of the2078* structure to mount.2079* @return <code>true</code> if the operation was successful.2080* @throws IOException if an error occurred during the transmission.2081*/2082public sun.net.ftp.FtpClient structureMount(String struct) throws sun.net.ftp.FtpProtocolException, IOException {2083issueCommandCheck("SMNT " + struct);2084return this;2085}20862087/**2088* Sends a SYST (System) command to the server and returns the String2089* sent back by the server describing the operating system at the2090* server.2091*2092* @return a <code>String</code> describing the OS, or <code>null</code>2093* if the operation was not successful.2094* @throws IOException if an error occurred during the transmission.2095*/2096public String getSystem() throws sun.net.ftp.FtpProtocolException, IOException {2097issueCommandCheck("SYST");2098/*2099* 215 UNIX Type: L8 Version: SUNOS2100*/2101String resp = getResponseString();2102// Get rid of the leading code and blank2103return resp.substring(4);2104}21052106/**2107* Sends the HELP command to the server, with an optional command, like2108* SITE, and returns the text sent back by the server.2109*2110* @param cmd the command for which the help is requested or2111* <code>null</code> for the general help2112* @return a <code>String</code> containing the text sent back by the2113* server, or <code>null</code> if the command failed.2114* @throws IOException if an error occurred during transmission2115*/2116public String getHelp(String cmd) throws sun.net.ftp.FtpProtocolException, IOException {2117issueCommandCheck("HELP " + cmd);2118/**2119*2120* HELP2121* 214-The following commands are implemented.2122* USER EPRT STRU ALLO DELE SYST RMD MDTM ADAT2123* PASS EPSV MODE REST CWD STAT PWD PROT2124* QUIT LPRT RETR RNFR LIST HELP CDUP PBSZ2125* PORT LPSV STOR RNTO NLST NOOP STOU AUTH2126* PASV TYPE APPE ABOR SITE MKD SIZE CCC2127* 214 Direct comments to ftp-bugs@jsn.2128*2129* HELP SITE2130* 214-The following SITE commands are implemented.2131* UMASK HELP GROUPS2132* IDLE ALIAS CHECKMETHOD2133* CHMOD CDPATH CHECKSUM2134* 214 Direct comments to ftp-bugs@jsn.2135*/2136Vector<String> resp = getResponseStrings();2137if (resp.size() == 1) {2138// Single line response2139return resp.get(0).substring(4);2140}2141// on multiple lines answers, like the ones above, remove 1st and last2142// line, concat the others.2143StringBuilder sb = new StringBuilder();2144for (int i = 1; i < resp.size() - 1; i++) {2145sb.append(resp.get(i).substring(3));2146}2147return sb.toString();2148}21492150/**2151* Sends the SITE command to the server. This is used by the server2152* to provide services specific to his system that are essential2153* to file transfer.2154*2155* @param cmd the command to be sent.2156* @return <code>true</code> if the command was successful.2157* @throws IOException if an error occurred during transmission2158*/2159public sun.net.ftp.FtpClient siteCmd(String cmd) throws sun.net.ftp.FtpProtocolException, IOException {2160issueCommandCheck("SITE " + cmd);2161return this;2162}2163}216421652166