Path: blob/master/src/java.base/share/classes/sun/net/ftp/impl/FtpClient.java
67848 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;25262728import java.io.BufferedInputStream;29import java.io.BufferedOutputStream;30import java.io.BufferedReader;31import java.io.Closeable;32import java.io.FileNotFoundException;33import java.io.IOException;34import java.io.InputStream;35import java.io.InputStreamReader;36import java.io.OutputStream;37import java.io.PrintStream;38import java.io.UnsupportedEncodingException;39import java.net.Inet6Address;40import java.net.InetAddress;41import java.net.InetSocketAddress;42import java.net.Proxy;43import java.net.ServerSocket;44import java.net.Socket;45import java.net.SocketAddress;46import java.security.AccessController;47import java.security.PrivilegedAction;48import java.security.PrivilegedExceptionAction;49import java.text.DateFormat;50import java.time.ZoneOffset;51import java.time.ZonedDateTime;52import java.time.format.DateTimeFormatter;53import java.time.format.DateTimeParseException;54import java.util.ArrayList;55import java.util.Arrays;56import java.util.Base64;57import java.util.Calendar;58import java.util.Date;59import java.util.Iterator;60import java.util.List;61import java.util.Vector;62import java.util.regex.Matcher;63import java.util.regex.Pattern;64import javax.net.ssl.SSLSocket;65import javax.net.ssl.SSLSocketFactory;66import sun.net.ftp.FtpDirEntry;67import sun.net.ftp.FtpDirParser;68import sun.net.ftp.FtpProtocolException;69import sun.net.ftp.FtpReplyCode;70import sun.net.util.IPAddressUtil;71import sun.util.logging.PlatformLogger;727374public class FtpClient extends sun.net.ftp.FtpClient {7576private static int defaultSoTimeout;77private static int defaultConnectTimeout;78private static final PlatformLogger logger =79PlatformLogger.getLogger("sun.net.ftp.FtpClient");80private Proxy proxy;81private Socket server;82private PrintStream out;83private InputStream in;84private int readTimeout = -1;85private int connectTimeout = -1;8687/* Name of encoding to use for output */88private static String encoding = "ISO8859_1";89/** remember the ftp server name because we may need it */90private InetSocketAddress serverAddr;91private boolean replyPending = false;92private boolean loggedIn = false;93private boolean useCrypto = false;94private SSLSocketFactory sslFact;95private Socket oldSocket;96/** Array of strings (usually 1 entry) for the last reply from the server. */97private Vector<String> serverResponse = new Vector<String>(1);98/** The last reply code from the ftp daemon. */99private FtpReplyCode lastReplyCode = null;100/** Welcome message from the server, if any. */101private String welcomeMsg;102/**103* Only passive mode used in JDK. See Bug 8010784.104*/105private final boolean passiveMode = true;106private TransferType type = TransferType.BINARY;107private long restartOffset = 0;108private long lastTransSize = -1; // -1 means 'unknown size'109private String lastFileName;110/**111* Static members used by the parser112*/113private static String[] patStrings = {114// drwxr-xr-x 1 user01 ftp 512 Jan 29 23:32 prog115"([\\-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}*)",116// drwxr-xr-x 1 user01 ftp 512 Jan 29 1997 prog117"([\\-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}*)",118// 04/28/2006 09:12a 3,563 genBuffer.sh119"(\\d{2}/\\d{2}/\\d{4})\\s*(\\d{2}:\\d{2}[ap])\\s*((?:[0-9,]+)|(?:<DIR>))\\s*(\\p{Graph}*)",120// 01-29-97 11:32PM <DIR> prog121"(\\d{2}-\\d{2}-\\d{2})\\s*(\\d{2}:\\d{2}[AP]M)\\s*((?:[0-9,]+)|(?:<DIR>))\\s*(\\p{Graph}*)"122};123private static int[][] patternGroups = {124// 0 - file, 1 - size, 2 - date, 3 - time, 4 - year, 5 - permissions,125// 6 - user, 7 - group126{7, 4, 5, 6, 0, 1, 2, 3},127{7, 4, 5, 0, 6, 1, 2, 3},128{4, 3, 1, 2, 0, 0, 0, 0},129{4, 3, 1, 2, 0, 0, 0, 0}};130private static Pattern[] patterns;131private static Pattern linkp = Pattern.compile("(\\p{Print}+) \\-\\> (\\p{Print}+)$");132private DateFormat df = DateFormat.getDateInstance(DateFormat.MEDIUM, java.util.Locale.US);133private static final boolean acceptPasvAddressVal;134static {135final int vals[] = {0, 0};136final String acceptPasvAddress[] = {null};137@SuppressWarnings("removal")138final String enc = AccessController.doPrivileged(139new PrivilegedAction<String>() {140public String run() {141acceptPasvAddress[0] = System.getProperty("jdk.net.ftp.trustPasvAddress", "false");142vals[0] = Integer.getInteger("sun.net.client.defaultReadTimeout", 300_000).intValue();143vals[1] = Integer.getInteger("sun.net.client.defaultConnectTimeout", 300_000).intValue();144return System.getProperty("file.encoding", "ISO8859_1");145}146});147if (vals[0] == 0) {148defaultSoTimeout = -1;149} else {150defaultSoTimeout = vals[0];151}152153if (vals[1] == 0) {154defaultConnectTimeout = -1;155} else {156defaultConnectTimeout = vals[1];157}158159encoding = enc;160try {161if (!isASCIISuperset(encoding)) {162encoding = "ISO8859_1";163}164} catch (Exception e) {165encoding = "ISO8859_1";166}167168patterns = new Pattern[patStrings.length];169for (int i = 0; i < patStrings.length; i++) {170patterns[i] = Pattern.compile(patStrings[i]);171}172173acceptPasvAddressVal = Boolean.parseBoolean(acceptPasvAddress[0]);174}175176/**177* Test the named character encoding to verify that it converts ASCII178* characters correctly. We have to use an ASCII based encoding, or else179* the NetworkClients will not work correctly in EBCDIC based systems.180* However, we cannot just use ASCII or ISO8859_1 universally, because in181* Asian locales, non-ASCII characters may be embedded in otherwise182* ASCII based protocols (e.g. HTTP). The specifications (RFC2616, 2398)183* are a little ambiguous in this matter. For instance, RFC2398 [part 2.1]184* says that the HTTP request URI should be escaped using a defined185* mechanism, but there is no way to specify in the escaped string what186* the original character set is. It is not correct to assume that187* UTF-8 is always used (as in URLs in HTML 4.0). For this reason,188* until the specifications are updated to deal with this issue more189* comprehensively, and more importantly, HTTP servers are known to190* support these mechanisms, we will maintain the current behavior191* where it is possible to send non-ASCII characters in their original192* unescaped form.193*/194private static boolean isASCIISuperset(String encoding) throws Exception {195String chkS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" +196"abcdefghijklmnopqrstuvwxyz-_.!~*'();/?:@&=+$,";197198// Expected byte sequence for string above199byte[] chkB = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 71, 72,20073, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99,201100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114,202115, 116, 117, 118, 119, 120, 121, 122, 45, 95, 46, 33, 126, 42, 39, 40, 41, 59,20347, 63, 58, 64, 38, 61, 43, 36, 44};204205byte[] b = chkS.getBytes(encoding);206return java.util.Arrays.equals(b, chkB);207}208209private class DefaultParser implements FtpDirParser {210211/**212* Possible patterns:213*214* drwxr-xr-x 1 user01 ftp 512 Jan 29 23:32 prog215* drwxr-xr-x 1 user01 ftp 512 Jan 29 1997 prog216* drwxr-xr-x 1 1 1 512 Jan 29 23:32 prog217* lrwxr-xr-x 1 user01 ftp 512 Jan 29 23:32 prog -> prog2000218* drwxr-xr-x 1 username ftp 512 Jan 29 23:32 prog219* -rw-r--r-- 1 jcc staff 105009 Feb 3 15:05 test.1220*221* 01-29-97 11:32PM <DIR> prog222* 04/28/2006 09:12a 3,563 genBuffer.sh223*224* drwxr-xr-x folder 0 Jan 29 23:32 prog225*226* 0 DIR 01-29-97 23:32 PROG227*/228private DefaultParser() {229}230231public FtpDirEntry parseLine(String line) {232String fdate = null;233String fsize = null;234String time = null;235String filename = null;236String permstring = null;237String username = null;238String groupname = null;239boolean dir = false;240Calendar now = Calendar.getInstance();241int year = now.get(Calendar.YEAR);242243Matcher m = null;244for (int j = 0; j < patterns.length; j++) {245m = patterns[j].matcher(line);246if (m.find()) {247// 0 - file, 1 - size, 2 - date, 3 - time, 4 - year,248// 5 - permissions, 6 - user, 7 - group249filename = m.group(patternGroups[j][0]);250fsize = m.group(patternGroups[j][1]);251fdate = m.group(patternGroups[j][2]);252if (patternGroups[j][4] > 0) {253fdate += (", " + m.group(patternGroups[j][4]));254} else if (patternGroups[j][3] > 0) {255fdate += (", " + String.valueOf(year));256}257if (patternGroups[j][3] > 0) {258time = m.group(patternGroups[j][3]);259}260if (patternGroups[j][5] > 0) {261permstring = m.group(patternGroups[j][5]);262dir = permstring.startsWith("d");263}264if (patternGroups[j][6] > 0) {265username = m.group(patternGroups[j][6]);266}267if (patternGroups[j][7] > 0) {268groupname = m.group(patternGroups[j][7]);269}270// Old DOS format271if ("<DIR>".equals(fsize)) {272dir = true;273fsize = null;274}275}276}277278if (filename != null) {279Date d;280try {281d = df.parse(fdate);282} catch (Exception e) {283d = null;284}285if (d != null && time != null) {286int c = time.indexOf(':');287now.setTime(d);288now.set(Calendar.HOUR, Integer.parseInt(time, 0, c, 10));289now.set(Calendar.MINUTE, Integer.parseInt(time, c + 1, time.length(), 10));290d = now.getTime();291}292// see if it's a symbolic link, i.e. the name if followed293// by a -> and a path294Matcher m2 = linkp.matcher(filename);295if (m2.find()) {296// Keep only the name then297filename = m2.group(1);298}299boolean[][] perms = new boolean[3][3];300for (int i = 0; i < 3; i++) {301for (int j = 0; j < 3; j++) {302perms[i][j] = (permstring.charAt((i * 3) + j) != '-');303}304}305FtpDirEntry file = new FtpDirEntry(filename);306file.setUser(username).setGroup(groupname);307file.setSize(Long.parseLong(fsize)).setLastModified(d);308file.setPermissions(perms);309file.setType(dir ? FtpDirEntry.Type.DIR : (line.charAt(0) == 'l' ? FtpDirEntry.Type.LINK : FtpDirEntry.Type.FILE));310return file;311}312return null;313}314}315316private static class MLSxParser implements FtpDirParser {317public FtpDirEntry parseLine(String line) {318String name = null;319int i = line.lastIndexOf(';');320if (i > 0) {321name = line.substring(i + 1).trim();322line = line.substring(0, i);323} else {324name = line.trim();325line = "";326}327FtpDirEntry file = new FtpDirEntry(name);328while (!line.isEmpty()) {329String s;330i = line.indexOf(';');331if (i > 0) {332s = line.substring(0, i);333line = line.substring(i + 1);334} else {335s = line;336line = "";337}338i = s.indexOf('=');339if (i > 0) {340String fact = s.substring(0, i);341String value = s.substring(i + 1);342file.addFact(fact, value);343}344}345String s = file.getFact("Size");346if (s != null) {347file.setSize(Long.parseLong(s));348}349s = file.getFact("Modify");350if (s != null) {351Date d = parseRfc3659TimeValue(s);352if (d != null) {353file.setLastModified(d);354}355}356s = file.getFact("Create");357if (s != null) {358Date d = parseRfc3659TimeValue(s);359if (d != null) {360file.setCreated(d);361}362}363s = file.getFact("Type");364if (s != null) {365if (s.equalsIgnoreCase("file")) {366file.setType(FtpDirEntry.Type.FILE);367}368if (s.equalsIgnoreCase("dir")) {369file.setType(FtpDirEntry.Type.DIR);370}371if (s.equalsIgnoreCase("cdir")) {372file.setType(FtpDirEntry.Type.CDIR);373}374if (s.equalsIgnoreCase("pdir")) {375file.setType(FtpDirEntry.Type.PDIR);376}377}378return file;379}380};381private FtpDirParser parser = new DefaultParser();382private FtpDirParser mlsxParser = new MLSxParser();383private static Pattern transPat = null;384385private void getTransferSize() {386lastTransSize = -1;387/**388* If it's a start of data transfer response, let's try to extract389* the size from the response string. Usually it looks like that:390*391* 150 Opening BINARY mode data connection for foo (6701 bytes).392*/393String response = getLastResponseString();394if (transPat == null) {395transPat = Pattern.compile("150 Opening .*\\((\\d+) bytes\\).");396}397Matcher m = transPat.matcher(response);398if (m.find()) {399String s = m.group(1);400lastTransSize = Long.parseLong(s);401}402}403404/**405* extract the created file name from the response string:406* 226 Transfer complete (unique file name:toto.txt.1).407* Usually happens when a STOU (store unique) command had been issued.408*/409private void getTransferName() {410lastFileName = null;411String response = getLastResponseString();412int i = response.indexOf("unique file name:");413int e = response.lastIndexOf(')');414if (i >= 0) {415i += 17; // Length of "unique file name:"416lastFileName = response.substring(i, e);417}418}419420/**421* Pulls the response from the server and returns the code as a422* number. Returns -1 on failure.423*/424private int readServerResponse() throws IOException {425StringBuilder replyBuf = new StringBuilder(32);426int c;427int continuingCode = -1;428int code;429String response;430431serverResponse.setSize(0);432while (true) {433while ((c = in.read()) != -1) {434if (c == '\r') {435if ((c = in.read()) != '\n') {436replyBuf.append('\r');437}438}439replyBuf.append((char) c);440if (c == '\n') {441break;442}443}444response = replyBuf.toString();445replyBuf.setLength(0);446if (logger.isLoggable(PlatformLogger.Level.FINEST)) {447logger.finest("Server [" + serverAddr + "] --> " + response);448}449450if (response.isEmpty()) {451code = -1;452} else {453try {454code = Integer.parseInt(response, 0, 3, 10);455} catch (NumberFormatException e) {456code = -1;457} catch (IndexOutOfBoundsException e) {458/* this line doesn't contain a response code, so459we just completely ignore it */460continue;461}462}463serverResponse.addElement(response);464if (continuingCode != -1) {465/* we've seen a ###- sequence */466if (code != continuingCode ||467(response.length() >= 4 && response.charAt(3) == '-')) {468continue;469} else {470/* seen the end of code sequence */471continuingCode = -1;472break;473}474} else if (response.length() >= 4 && response.charAt(3) == '-') {475continuingCode = code;476continue;477} else {478break;479}480}481482return code;483}484485/** Sends command <i>cmd</i> to the server. */486private void sendServer(String cmd) {487out.print(cmd);488if (logger.isLoggable(PlatformLogger.Level.FINEST)) {489logger.finest("Server [" + serverAddr + "] <-- " + cmd);490}491}492493/** converts the server response into a string. */494private String getResponseString() {495return serverResponse.elementAt(0);496}497498/** Returns all server response strings. */499private Vector<String> getResponseStrings() {500return serverResponse;501}502503/**504* Read the reply from the FTP server.505*506* @return <code>true</code> if the command was successful507* @throws IOException if an error occurred508*/509private boolean readReply() throws IOException {510lastReplyCode = FtpReplyCode.find(readServerResponse());511512if (lastReplyCode.isPositivePreliminary()) {513replyPending = true;514return true;515}516if (lastReplyCode.isPositiveCompletion() || lastReplyCode.isPositiveIntermediate()) {517if (lastReplyCode == FtpReplyCode.CLOSING_DATA_CONNECTION) {518getTransferName();519}520return true;521}522return false;523}524525/**526* Sends a command to the FTP server and returns the error code527* (which can be a "success") sent by the server.528*529* @param cmd530* @return <code>true</code> if the command was successful531* @throws IOException532*/533private boolean issueCommand(String cmd) throws IOException,534sun.net.ftp.FtpProtocolException {535if (!isConnected()) {536throw new IllegalStateException("Not connected");537}538if (replyPending) {539try {540completePending();541} catch (sun.net.ftp.FtpProtocolException e) {542// ignore...543}544}545if (cmd.indexOf('\n') != -1) {546sun.net.ftp.FtpProtocolException ex547= new sun.net.ftp.FtpProtocolException("Illegal FTP command");548ex.initCause(new IllegalArgumentException("Illegal carriage return"));549throw ex;550}551sendServer(cmd + "\r\n");552return readReply();553}554555/**556* Send a command to the FTP server and check for success.557*558* @param cmd String containing the command559*560* @throws FtpProtocolException if an error occurred561*/562private void issueCommandCheck(String cmd) throws sun.net.ftp.FtpProtocolException, IOException {563if (!issueCommand(cmd)) {564throw new sun.net.ftp.FtpProtocolException(cmd + ":" + getResponseString(), getLastReplyCode());565}566}567private static Pattern epsvPat = null;568private static Pattern pasvPat = null;569570/**571* Opens a "PASSIVE" connection with the server and returns the connected572* <code>Socket</code>.573*574* @return the connected <code>Socket</code>575* @throws IOException if the connection was unsuccessful.576*/577private Socket openPassiveDataConnection(String cmd) throws sun.net.ftp.FtpProtocolException, IOException {578String serverAnswer;579int port;580InetSocketAddress dest = null;581582/**583* Here is the idea:584*585* - First we want to try the new (and IPv6 compatible) EPSV command586* But since we want to be nice with NAT software, we'll issue the587* EPSV ALL command first.588* EPSV is documented in RFC2428589* - If EPSV fails, then we fall back to the older, yet ok, PASV590* - If PASV fails as well, then we throw an exception and the calling591* method will have to try the EPRT or PORT command592*/593if (issueCommand("EPSV ALL")) {594// We can safely use EPSV commands595issueCommandCheck("EPSV");596serverAnswer = getResponseString();597598// The response string from a EPSV command will contain the port number599// the format will be :600// 229 Entering Extended PASSIVE Mode (|||58210|)601//602// So we'll use the regular expresions package to parse the output.603604if (epsvPat == null) {605epsvPat = Pattern.compile("^229 .* \\(\\|\\|\\|(\\d+)\\|\\)");606}607Matcher m = epsvPat.matcher(serverAnswer);608if (!m.find()) {609throw new sun.net.ftp.FtpProtocolException("EPSV failed : " + serverAnswer);610}611// Yay! Let's extract the port number612String s = m.group(1);613port = Integer.parseInt(s);614InetAddress add = server.getInetAddress();615if (add != null) {616dest = new InetSocketAddress(add, port);617} else {618// This means we used an Unresolved address to connect in619// the first place. Most likely because the proxy is doing620// the name resolution for us, so let's keep using unresolved621// address.622dest = InetSocketAddress.createUnresolved(serverAddr.getHostName(), port);623}624} else {625// EPSV ALL failed, so Let's try the regular PASV cmd626issueCommandCheck("PASV");627serverAnswer = getResponseString();628629// Let's parse the response String to get the IP & port to connect630// to. The String should be in the following format :631//632// 227 Entering PASSIVE Mode (A1,A2,A3,A4,p1,p2)633//634// Note that the two parenthesis are optional635//636// The IP address is A1.A2.A3.A4 and the port is p1 * 256 + p2637//638// The regular expression is a bit more complex this time, because639// the parenthesis are optionals and we have to use 3 groups.640if (pasvPat == null) {641pasvPat = Pattern.compile("227 .* \\(?(\\d{1,3},\\d{1,3},\\d{1,3},\\d{1,3}),(\\d{1,3}),(\\d{1,3})\\)?");642}643Matcher m = pasvPat.matcher(serverAnswer);644if (!m.find()) {645throw new sun.net.ftp.FtpProtocolException("PASV failed : " + serverAnswer);646}647// Get port number out of group 2 & 3648port = Integer.parseInt(m.group(3)) + (Integer.parseInt(m.group(2)) << 8);649// IP address is simple650String s = m.group(1).replace(',', '.');651if (!IPAddressUtil.isIPv4LiteralAddress(s))652throw new FtpProtocolException("PASV failed : " + serverAnswer);653if (acceptPasvAddressVal) {654dest = new InetSocketAddress(s, port);655} else {656dest = validatePasvAddress(port, s, server.getInetAddress());657}658}659660// Got everything, let's open the socket!661Socket s;662if (proxy != null) {663if (proxy.type() == Proxy.Type.SOCKS) {664PrivilegedAction<Socket> pa = () -> new Socket(proxy);665@SuppressWarnings("removal")666var tmp = AccessController.doPrivileged(pa);667s = tmp;668} else {669s = new Socket(Proxy.NO_PROXY);670}671} else {672s = new Socket();673}674675PrivilegedAction<InetAddress> pa = () -> server.getLocalAddress();676@SuppressWarnings("removal")677InetAddress serverAddress = AccessController.doPrivileged(pa);678679// Bind the socket to the same address as the control channel. This680// is needed in case of multi-homed systems.681s.bind(new InetSocketAddress(serverAddress, 0));682if (connectTimeout >= 0) {683s.connect(dest, connectTimeout);684} else {685if (defaultConnectTimeout > 0) {686s.connect(dest, defaultConnectTimeout);687} else {688s.connect(dest);689}690}691if (readTimeout >= 0) {692s.setSoTimeout(readTimeout);693} else if (defaultSoTimeout > 0) {694s.setSoTimeout(defaultSoTimeout);695}696if (useCrypto) {697try {698s = sslFact.createSocket(s, dest.getHostName(), dest.getPort(), true);699} catch (Exception e) {700throw new sun.net.ftp.FtpProtocolException("Can't open secure data channel: " + e);701}702}703if (!issueCommand(cmd)) {704s.close();705if (getLastReplyCode() == FtpReplyCode.FILE_UNAVAILABLE) {706// Ensure backward compatibility707throw new FileNotFoundException(cmd);708}709throw new sun.net.ftp.FtpProtocolException(cmd + ":" + getResponseString(), getLastReplyCode());710}711return s;712}713714static final String ERROR_MSG = "Address should be the same as originating server";715716/**717* Returns an InetSocketAddress, based on value of acceptPasvAddressVal718* and other conditions such as the server address returned by pasv719* is not a hostname, is a socks proxy, or the loopback. An exception720* is thrown if none of the valid conditions are met.721*/722private InetSocketAddress validatePasvAddress(int port, String s, InetAddress address)723throws FtpProtocolException724{725if (address == null) {726return InetSocketAddress.createUnresolved(serverAddr.getHostName(), port);727}728String serverAddress = address.getHostAddress();729if (serverAddress.equals(s)) {730return new InetSocketAddress(s, port);731} else if (address.isLoopbackAddress() && s.startsWith("127.")) { // can be 127.0732return new InetSocketAddress(s, port);733} else if (address.isLoopbackAddress()) {734if (privilegedLocalHost().getHostAddress().equals(s)) {735return new InetSocketAddress(s, port);736} else {737throw new FtpProtocolException(ERROR_MSG);738}739} else if (s.startsWith("127.")) {740if (privilegedLocalHost().equals(address)) {741return new InetSocketAddress(s, port);742} else {743throw new FtpProtocolException(ERROR_MSG);744}745}746String hostName = address.getHostName();747if (!(IPAddressUtil.isIPv4LiteralAddress(hostName) || IPAddressUtil.isIPv6LiteralAddress(hostName))) {748InetAddress[] names = privilegedGetAllByName(hostName);749String resAddress = Arrays750.stream(names)751.map(InetAddress::getHostAddress)752.filter(s::equalsIgnoreCase)753.findFirst()754.orElse(null);755if (resAddress != null) {756return new InetSocketAddress(s, port);757}758}759throw new FtpProtocolException(ERROR_MSG);760}761762private static InetAddress privilegedLocalHost() throws FtpProtocolException {763PrivilegedExceptionAction<InetAddress> action = InetAddress::getLocalHost;764try {765@SuppressWarnings("removal")766var tmp = AccessController.doPrivileged(action);767return tmp;768} catch (Exception e) {769var ftpEx = new FtpProtocolException(ERROR_MSG);770ftpEx.initCause(e);771throw ftpEx;772}773}774775private static InetAddress[] privilegedGetAllByName(String hostName) throws FtpProtocolException {776PrivilegedExceptionAction<InetAddress[]> pAction = () -> InetAddress.getAllByName(hostName);777try {778@SuppressWarnings("removal")779var tmp =AccessController.doPrivileged(pAction);780return tmp;781} catch (Exception e) {782var ftpEx = new FtpProtocolException(ERROR_MSG);783ftpEx.initCause(e);784throw ftpEx;785}786}787788/**789* Opens a data connection with the server according to the set mode790* (ACTIVE or PASSIVE) then send the command passed as an argument.791*792* @param cmd the <code>String</code> containing the command to execute793* @return the connected <code>Socket</code>794* @throws IOException if the connection or command failed795*/796private Socket openDataConnection(String cmd) throws sun.net.ftp.FtpProtocolException, IOException {797Socket clientSocket;798if (passiveMode) {799try {800return openPassiveDataConnection(cmd);801} catch (sun.net.ftp.FtpProtocolException e) {802// If Passive mode failed, fall back on PORT803// Otherwise throw exception804String errmsg = e.getMessage();805if (!errmsg.startsWith("PASV") && !errmsg.startsWith("EPSV")) {806throw e;807}808}809}810ServerSocket portSocket;811InetAddress myAddress;812String portCmd;813814if (proxy != null && proxy.type() == Proxy.Type.SOCKS) {815// We're behind a firewall and the passive mode fail,816// since we can't accept a connection through SOCKS (yet)817// throw an exception818throw new sun.net.ftp.FtpProtocolException("Passive mode failed");819}820// Bind the ServerSocket to the same address as the control channel821// This is needed for multi-homed systems822portSocket = new ServerSocket(0, 1, server.getLocalAddress());823try {824myAddress = portSocket.getInetAddress();825if (myAddress.isAnyLocalAddress()) {826myAddress = server.getLocalAddress();827}828// Let's try the new, IPv6 compatible EPRT command829// See RFC2428 for specifics830// Some FTP servers (like the one on Solaris) are bugged, they831// will accept the EPRT command but then, the subsequent command832// (e.g. RETR) will fail, so we have to check BOTH results (the833// EPRT cmd then the actual command) to decide whether we should834// fall back on the older PORT command.835portCmd = "EPRT |" + ((myAddress instanceof Inet6Address) ? "2" : "1") + "|" +836myAddress.getHostAddress() + "|" + portSocket.getLocalPort() + "|";837if (!issueCommand(portCmd) || !issueCommand(cmd)) {838// The EPRT command failed, let's fall back to good old PORT839portCmd = "PORT ";840byte[] addr = myAddress.getAddress();841842/* append host addr */843for (int i = 0; i < addr.length; i++) {844portCmd = portCmd + (addr[i] & 0xFF) + ",";845}846847/* append port number */848portCmd = portCmd + ((portSocket.getLocalPort() >>> 8) & 0xff) + "," + (portSocket.getLocalPort() & 0xff);849issueCommandCheck(portCmd);850issueCommandCheck(cmd);851}852// Either the EPRT or the PORT command was successful853// Let's create the client socket854if (connectTimeout >= 0) {855portSocket.setSoTimeout(connectTimeout);856} else {857if (defaultConnectTimeout > 0) {858portSocket.setSoTimeout(defaultConnectTimeout);859}860}861clientSocket = portSocket.accept();862if (readTimeout >= 0) {863clientSocket.setSoTimeout(readTimeout);864} else {865if (defaultSoTimeout > 0) {866clientSocket.setSoTimeout(defaultSoTimeout);867}868}869} finally {870portSocket.close();871}872if (useCrypto) {873try {874clientSocket = sslFact.createSocket(clientSocket, serverAddr.getHostName(), serverAddr.getPort(), true);875} catch (Exception ex) {876throw new IOException(ex.getLocalizedMessage());877}878}879return clientSocket;880}881882private InputStream createInputStream(InputStream in) {883if (type == TransferType.ASCII) {884return new sun.net.TelnetInputStream(in, false);885}886return in;887}888889private OutputStream createOutputStream(OutputStream out) {890if (type == TransferType.ASCII) {891return new sun.net.TelnetOutputStream(out, false);892}893return out;894}895896/**897* Creates an instance of FtpClient. The client is not connected to any898* server yet.899*900*/901protected FtpClient() {902}903904/**905* Creates an instance of FtpClient. The client is not connected to any906* server yet.907*908*/909public static sun.net.ftp.FtpClient create() {910return new FtpClient();911}912913/**914* Set the transfer mode to <I>passive</I>. In that mode, data connections915* are established by having the client connect to the server.916* This is the recommended default mode as it will work best through917* firewalls and NATs.918*919* @return This FtpClient920* @see #setActiveMode()921*/922public sun.net.ftp.FtpClient enablePassiveMode(boolean passive) {923924// Only passive mode used in JDK. See Bug 8010784.925// passiveMode = passive;926return this;927}928929/**930* Gets the current transfer mode.931*932* @return the current <code>FtpTransferMode</code>933*/934public boolean isPassiveModeEnabled() {935return passiveMode;936}937938/**939* Sets the timeout value to use when connecting to the server,940*941* @param timeout the timeout value, in milliseconds, to use for the connect942* operation. A value of zero or less, means use the default timeout.943*944* @return This FtpClient945*/946public sun.net.ftp.FtpClient setConnectTimeout(int timeout) {947connectTimeout = timeout;948return this;949}950951/**952* Returns the current connection timeout value.953*954* @return the value, in milliseconds, of the current connect timeout.955* @see #setConnectTimeout(int)956*/957public int getConnectTimeout() {958return connectTimeout;959}960961/**962* Sets the timeout value to use when reading from the server,963*964* @param timeout the timeout value, in milliseconds, to use for the read965* operation. A value of zero or less, means use the default timeout.966* @return This FtpClient967*/968public sun.net.ftp.FtpClient setReadTimeout(int timeout) {969readTimeout = timeout;970return this;971}972973/**974* Returns the current read timeout value.975*976* @return the value, in milliseconds, of the current read timeout.977* @see #setReadTimeout(int)978*/979public int getReadTimeout() {980return readTimeout;981}982983public sun.net.ftp.FtpClient setProxy(Proxy p) {984proxy = p;985return this;986}987988/**989* Get the proxy of this FtpClient990*991* @return the <code>Proxy</code>, this client is using, or <code>null</code>992* if none is used.993* @see #setProxy(Proxy)994*/995public Proxy getProxy() {996return proxy;997}998999/**1000* Connects to the specified destination.1001*1002* @param dest the <code>InetSocketAddress</code> to connect to.1003* @throws IOException if the connection fails.1004*/1005private void tryConnect(InetSocketAddress dest, int timeout) throws IOException {1006if (isConnected()) {1007disconnect();1008}1009server = doConnect(dest, timeout);1010try {1011out = new PrintStream(new BufferedOutputStream(server.getOutputStream()),1012true, encoding);1013} catch (UnsupportedEncodingException e) {1014throw new InternalError(encoding + "encoding not found", e);1015}1016in = new BufferedInputStream(server.getInputStream());1017}10181019private Socket doConnect(InetSocketAddress dest, int timeout) throws IOException {1020Socket s;1021if (proxy != null) {1022if (proxy.type() == Proxy.Type.SOCKS) {1023PrivilegedAction<Socket> pa = () -> new Socket(proxy);1024@SuppressWarnings("removal")1025var tmp = AccessController.doPrivileged(pa);1026s = tmp;1027} else {1028s = new Socket(Proxy.NO_PROXY);1029}1030} else {1031s = new Socket();1032}1033// Instance specific timeouts do have priority, that means1034// connectTimeout & readTimeout (-1 means not set)1035// Then global default timeouts1036// Then no timeout.1037if (timeout >= 0) {1038s.connect(dest, timeout);1039} else {1040if (connectTimeout >= 0) {1041s.connect(dest, connectTimeout);1042} else {1043if (defaultConnectTimeout > 0) {1044s.connect(dest, defaultConnectTimeout);1045} else {1046s.connect(dest);1047}1048}1049}1050if (readTimeout >= 0) {1051s.setSoTimeout(readTimeout);1052} else if (defaultSoTimeout > 0) {1053s.setSoTimeout(defaultSoTimeout);1054}1055return s;1056}10571058private void disconnect() throws IOException {1059if (isConnected()) {1060server.close();1061}1062server = null;1063in = null;1064out = null;1065lastTransSize = -1;1066lastFileName = null;1067restartOffset = 0;1068welcomeMsg = null;1069lastReplyCode = null;1070serverResponse.setSize(0);1071}10721073/**1074* Tests whether this client is connected or not to a server.1075*1076* @return <code>true</code> if the client is connected.1077*/1078public boolean isConnected() {1079return server != null;1080}10811082public SocketAddress getServerAddress() {1083return server == null ? null : server.getRemoteSocketAddress();1084}10851086public sun.net.ftp.FtpClient connect(SocketAddress dest) throws sun.net.ftp.FtpProtocolException, IOException {1087return connect(dest, -1);1088}10891090/**1091* Connects the FtpClient to the specified destination.1092*1093* @param dest the address of the destination server1094* @throws IOException if connection failed.1095*/1096public sun.net.ftp.FtpClient connect(SocketAddress dest, int timeout) throws sun.net.ftp.FtpProtocolException, IOException {1097if (!(dest instanceof InetSocketAddress)) {1098throw new IllegalArgumentException("Wrong address type");1099}1100serverAddr = (InetSocketAddress) dest;1101tryConnect(serverAddr, timeout);1102if (!readReply()) {1103throw new sun.net.ftp.FtpProtocolException("Welcome message: " +1104getResponseString(), lastReplyCode);1105}1106welcomeMsg = getResponseString().substring(4);1107return this;1108}11091110private void tryLogin(String user, char[] password) throws sun.net.ftp.FtpProtocolException, IOException {1111issueCommandCheck("USER " + user);11121113/*1114* Checks for "331 User name okay, need password." answer1115*/1116if (lastReplyCode == FtpReplyCode.NEED_PASSWORD) {1117if ((password != null) && (password.length > 0)) {1118issueCommandCheck("PASS " + String.valueOf(password));1119}1120}1121}11221123/**1124* Attempts to log on the server with the specified user name and password.1125*1126* @param user The user name1127* @param password The password for that user1128* @return <code>true</code> if the login was successful.1129* @throws IOException if an error occurred during the transmission1130*/1131public sun.net.ftp.FtpClient login(String user, char[] password) throws sun.net.ftp.FtpProtocolException, IOException {1132if (!isConnected()) {1133throw new sun.net.ftp.FtpProtocolException("Not connected yet", FtpReplyCode.BAD_SEQUENCE);1134}1135if (user == null || user.isEmpty()) {1136throw new IllegalArgumentException("User name can't be null or empty");1137}1138tryLogin(user, password);11391140// keep the welcome message around so we can1141// put it in the resulting HTML page.1142String l;1143StringBuilder sb = new StringBuilder();1144for (int i = 0; i < serverResponse.size(); i++) {1145l = serverResponse.elementAt(i);1146if (l != null) {1147if (l.length() >= 4 && l.startsWith("230")) {1148// get rid of the "230-" prefix1149l = l.substring(4);1150}1151sb.append(l);1152}1153}1154welcomeMsg = sb.toString();1155loggedIn = true;1156return this;1157}11581159/**1160* Attempts to log on the server with the specified user name, password and1161* account name.1162*1163* @param user The user name1164* @param password The password for that user.1165* @param account The account name for that user.1166* @return <code>true</code> if the login was successful.1167* @throws IOException if an error occurs during the transmission.1168*/1169public sun.net.ftp.FtpClient login(String user, char[] password, String account) throws sun.net.ftp.FtpProtocolException, IOException {11701171if (!isConnected()) {1172throw new sun.net.ftp.FtpProtocolException("Not connected yet", FtpReplyCode.BAD_SEQUENCE);1173}1174if (user == null || user.isEmpty()) {1175throw new IllegalArgumentException("User name can't be null or empty");1176}1177tryLogin(user, password);11781179/*1180* Checks for "332 Need account for login." answer1181*/1182if (lastReplyCode == FtpReplyCode.NEED_ACCOUNT) {1183issueCommandCheck("ACCT " + account);1184}11851186// keep the welcome message around so we can1187// put it in the resulting HTML page.1188StringBuilder sb = new StringBuilder();1189if (serverResponse != null) {1190for (String l : serverResponse) {1191if (l != null) {1192if (l.length() >= 4 && l.startsWith("230")) {1193// get rid of the "230-" prefix1194l = l.substring(4);1195}1196sb.append(l);1197}1198}1199}1200welcomeMsg = sb.toString();1201loggedIn = true;1202return this;1203}12041205/**1206* Logs out the current user. This is in effect terminates the current1207* session and the connection to the server will be closed.1208*1209*/1210public void close() throws IOException {1211if (isConnected()) {1212try {1213issueCommand("QUIT");1214} catch (FtpProtocolException e) {1215}1216loggedIn = false;1217}1218disconnect();1219}12201221/**1222* Checks whether the client is logged in to the server or not.1223*1224* @return <code>true</code> if the client has already completed a login.1225*/1226public boolean isLoggedIn() {1227return loggedIn;1228}12291230/**1231* Changes to a specific directory on a remote FTP server1232*1233* @param remoteDirectory path of the directory to CD to.1234* @return <code>true</code> if the operation was successful.1235* @exception <code>FtpProtocolException</code>1236*/1237public sun.net.ftp.FtpClient changeDirectory(String remoteDirectory) throws sun.net.ftp.FtpProtocolException, IOException {1238if (remoteDirectory == null || remoteDirectory.isEmpty()) {1239throw new IllegalArgumentException("directory can't be null or empty");1240}12411242issueCommandCheck("CWD " + remoteDirectory);1243return this;1244}12451246/**1247* Changes to the parent directory, sending the CDUP command to the server.1248*1249* @return <code>true</code> if the command was successful.1250* @throws IOException1251*/1252public sun.net.ftp.FtpClient changeToParentDirectory() throws sun.net.ftp.FtpProtocolException, IOException {1253issueCommandCheck("CDUP");1254return this;1255}12561257/**1258* Returns the server current working directory, or <code>null</code> if1259* the PWD command failed.1260*1261* @return a <code>String</code> containing the current working directory,1262* or <code>null</code>1263* @throws IOException1264*/1265public String getWorkingDirectory() throws sun.net.ftp.FtpProtocolException, IOException {1266issueCommandCheck("PWD");1267/*1268* answer will be of the following format :1269*1270* 257 "/" is current directory.1271*/1272String answ = getResponseString();1273if (!answ.startsWith("257")) {1274return null;1275}1276return answ.substring(5, answ.lastIndexOf('"'));1277}12781279/**1280* Sets the restart offset to the specified value. That value will be1281* sent through a <code>REST</code> command to server before a file1282* transfer and has the effect of resuming a file transfer from the1283* specified point. After a transfer the restart offset is set back to1284* zero.1285*1286* @param offset the offset in the remote file at which to start the next1287* transfer. This must be a value greater than or equal to zero.1288* @throws IllegalArgumentException if the offset is negative.1289*/1290public sun.net.ftp.FtpClient setRestartOffset(long offset) {1291if (offset < 0) {1292throw new IllegalArgumentException("offset can't be negative");1293}1294restartOffset = offset;1295return this;1296}12971298/**1299* Retrieves a file from the ftp server and writes it to the specified1300* <code>OutputStream</code>.1301* If the restart offset was set, then a <code>REST</code> command will be1302* sent before the RETR in order to restart the tranfer from the specified1303* offset.1304* The <code>OutputStream</code> is not closed by this method at the end1305* of the transfer.1306*1307* @param name a {@code String} containing the name of the file to1308* retreive from the server.1309* @param local the <code>OutputStream</code> the file should be written to.1310* @throws IOException if the transfer fails.1311*/1312public sun.net.ftp.FtpClient getFile(String name, OutputStream local) throws sun.net.ftp.FtpProtocolException, IOException {1313if (restartOffset > 0) {1314Socket s;1315try {1316s = openDataConnection("REST " + restartOffset);1317} finally {1318restartOffset = 0;1319}1320issueCommandCheck("RETR " + name);1321getTransferSize();1322try (InputStream remote = createInputStream(s.getInputStream())) {1323remote.transferTo(local);1324}1325} else {1326Socket s = openDataConnection("RETR " + name);1327getTransferSize();1328try (InputStream remote = createInputStream(s.getInputStream())) {1329remote.transferTo(local);1330}1331}1332return completePending();1333}13341335/**1336* Retrieves a file from the ftp server, using the RETR command, and1337* returns the InputStream from* the established data connection.1338* {@link #completePending()} <b>has</b> to be called once the application1339* is done reading from the returned stream.1340*1341* @param name the name of the remote file1342* @return the {@link java.io.InputStream} from the data connection, or1343* <code>null</code> if the command was unsuccessful.1344* @throws IOException if an error occurred during the transmission.1345*/1346public InputStream getFileStream(String name) throws sun.net.ftp.FtpProtocolException, IOException {1347Socket s;1348if (restartOffset > 0) {1349try {1350s = openDataConnection("REST " + restartOffset);1351} finally {1352restartOffset = 0;1353}1354if (s == null) {1355return null;1356}1357issueCommandCheck("RETR " + name);1358getTransferSize();1359return createInputStream(s.getInputStream());1360}13611362s = openDataConnection("RETR " + name);1363if (s == null) {1364return null;1365}1366getTransferSize();1367return createInputStream(s.getInputStream());1368}13691370/**1371* Transfers a file from the client to the server (aka a <I>put</I>)1372* by sending the STOR or STOU command, depending on the1373* <code>unique</code> argument, and returns the <code>OutputStream</code>1374* from the established data connection.1375* {@link #completePending()} <b>has</b> to be called once the application1376* is finished writing to the stream.1377*1378* A new file is created at the server site if the file specified does1379* not already exist.1380*1381* If <code>unique</code> is set to <code>true</code>, the resultant file1382* is to be created under a name unique to that directory, meaning1383* it will not overwrite an existing file, instead the server will1384* generate a new, unique, file name.1385* The name of the remote file can be retrieved, after completion of the1386* transfer, by calling {@link #getLastFileName()}.1387*1388* @param name the name of the remote file to write.1389* @param unique <code>true</code> if the remote files should be unique,1390* in which case the STOU command will be used.1391* @return the {@link java.io.OutputStream} from the data connection or1392* <code>null</code> if the command was unsuccessful.1393* @throws IOException if an error occurred during the transmission.1394*/1395public OutputStream putFileStream(String name, boolean unique)1396throws sun.net.ftp.FtpProtocolException, IOException1397{1398String cmd = unique ? "STOU " : "STOR ";1399Socket s = openDataConnection(cmd + name);1400if (s == null) {1401return null;1402}1403boolean bm = (type == TransferType.BINARY);1404return new sun.net.TelnetOutputStream(s.getOutputStream(), bm);1405}14061407/**1408* Transfers a file from the client to the server (aka a <I>put</I>)1409* by sending the STOR command. The content of the <code>InputStream</code>1410* passed in argument is written into the remote file, overwriting any1411* existing data.1412*1413* A new file is created at the server site if the file specified does1414* not already exist.1415*1416* @param name the name of the remote file to write.1417* @param local the <code>InputStream</code> that points to the data to1418* transfer.1419* @param unique <code>true</code> if the remote file should be unique1420* (i.e. not already existing), <code>false</code> otherwise.1421* @return <code>true</code> if the transfer was successful.1422* @throws IOException if an error occurred during the transmission.1423* @see #getLastFileName()1424*/1425public sun.net.ftp.FtpClient putFile(String name, InputStream local, boolean unique) throws sun.net.ftp.FtpProtocolException, IOException {1426String cmd = unique ? "STOU " : "STOR ";1427if (type == TransferType.BINARY) {1428Socket s = openDataConnection(cmd + name);1429try (OutputStream remote = createOutputStream(s.getOutputStream())) {1430local.transferTo(remote);1431}1432}1433return completePending();1434}14351436/**1437* Sends the APPE command to the server in order to transfer a data stream1438* passed in argument and append it to the content of the specified remote1439* file.1440*1441* @param name A <code>String</code> containing the name of the remote file1442* to append to.1443* @param local The <code>InputStream</code> providing access to the data1444* to be appended.1445* @return <code>true</code> if the transfer was successful.1446* @throws IOException if an error occurred during the transmission.1447*/1448public sun.net.ftp.FtpClient appendFile(String name, InputStream local) throws sun.net.ftp.FtpProtocolException, IOException {1449Socket s = openDataConnection("APPE " + name);1450try (OutputStream remote = createOutputStream(s.getOutputStream())) {1451local.transferTo(remote);1452}1453return completePending();1454}14551456/**1457* Renames a file on the server.1458*1459* @param from the name of the file being renamed1460* @param to the new name for the file1461* @throws IOException if the command fails1462*/1463public sun.net.ftp.FtpClient rename(String from, String to) throws sun.net.ftp.FtpProtocolException, IOException {1464issueCommandCheck("RNFR " + from);1465issueCommandCheck("RNTO " + to);1466return this;1467}14681469/**1470* Deletes a file on the server.1471*1472* @param name a <code>String</code> containing the name of the file1473* to delete.1474* @return <code>true</code> if the command was successful1475* @throws IOException if an error occurred during the exchange1476*/1477public sun.net.ftp.FtpClient deleteFile(String name) throws sun.net.ftp.FtpProtocolException, IOException {1478issueCommandCheck("DELE " + name);1479return this;1480}14811482/**1483* Creates a new directory on the server.1484*1485* @param name a <code>String</code> containing the name of the directory1486* to create.1487* @return <code>true</code> if the operation was successful.1488* @throws IOException if an error occurred during the exchange1489*/1490public sun.net.ftp.FtpClient makeDirectory(String name) throws sun.net.ftp.FtpProtocolException, IOException {1491issueCommandCheck("MKD " + name);1492return this;1493}14941495/**1496* Removes a directory on the server.1497*1498* @param name a <code>String</code> containing the name of the directory1499* to remove.1500*1501* @return <code>true</code> if the operation was successful.1502* @throws IOException if an error occurred during the exchange.1503*/1504public sun.net.ftp.FtpClient removeDirectory(String name) throws sun.net.ftp.FtpProtocolException, IOException {1505issueCommandCheck("RMD " + name);1506return this;1507}15081509/**1510* Sends a No-operation command. It's useful for testing the connection1511* status or as a <I>keep alive</I> mechanism.1512*1513* @throws FtpProtocolException if the command fails1514*/1515public sun.net.ftp.FtpClient noop() throws sun.net.ftp.FtpProtocolException, IOException {1516issueCommandCheck("NOOP");1517return this;1518}15191520/**1521* Sends the STAT command to the server.1522* This can be used while a data connection is open to get a status1523* on the current transfer, in that case the parameter should be1524* <code>null</code>.1525* If used between file transfers, it may have a pathname as argument1526* in which case it will work as the LIST command except no data1527* connection will be created.1528*1529* @param name an optional <code>String</code> containing the pathname1530* the STAT command should apply to.1531* @return the response from the server or <code>null</code> if the1532* command failed.1533* @throws IOException if an error occurred during the transmission.1534*/1535public String getStatus(String name) throws sun.net.ftp.FtpProtocolException, IOException {1536issueCommandCheck((name == null ? "STAT" : "STAT " + name));1537/*1538* A typical response will be:1539* 213-status of t32.gif:1540* -rw-r--r-- 1 jcc staff 247445 Feb 17 1998 t32.gif1541* 213 End of Status1542*1543* or1544*1545* 211-jsn FTP server status:1546* Version wu-2.6.2+Sun1547* Connected to localhost (::1)1548* Logged in as jccollet1549* TYPE: ASCII, FORM: Nonprint; STRUcture: File; transfer MODE: Stream1550* No data connection1551* 0 data bytes received in 0 files1552* 0 data bytes transmitted in 0 files1553* 0 data bytes total in 0 files1554* 53 traffic bytes received in 0 transfers1555* 485 traffic bytes transmitted in 0 transfers1556* 587 traffic bytes total in 0 transfers1557* 211 End of status1558*1559* So we need to remove the 1st and last line1560*/1561Vector<String> resp = getResponseStrings();1562StringBuilder sb = new StringBuilder();1563for (int i = 1; i < resp.size() - 1; i++) {1564sb.append(resp.get(i));1565}1566return sb.toString();1567}15681569/**1570* Sends the FEAT command to the server and returns the list of supported1571* features in the form of strings.1572*1573* The features are the supported commands, like AUTH TLS, PROT or PASV.1574* See the RFCs for a complete list.1575*1576* Note that not all FTP servers support that command, in which case1577* the method will return <code>null</code>1578*1579* @return a <code>List</code> of <code>Strings</code> describing the1580* supported additional features, or <code>null</code>1581* if the command is not supported.1582* @throws IOException if an error occurs during the transmission.1583*/1584public List<String> getFeatures() throws sun.net.ftp.FtpProtocolException, IOException {1585/*1586* The FEAT command, when implemented will return something like:1587*1588* 211-Features:1589* AUTH TLS1590* PBSZ1591* PROT1592* EPSV1593* EPRT1594* PASV1595* REST STREAM1596* 211 END1597*/1598ArrayList<String> features = new ArrayList<String>();1599issueCommandCheck("FEAT");1600Vector<String> resp = getResponseStrings();1601// Note that we start at index 1 to skip the 1st line (211-...)1602// and we stop before the last line.1603for (int i = 1; i < resp.size() - 1; i++) {1604String s = resp.get(i);1605// Get rid of leading space and trailing newline1606features.add(s.substring(1, s.length() - 1));1607}1608return features;1609}16101611/**1612* sends the ABOR command to the server.1613* It tells the server to stop the previous command or transfer.1614*1615* @return <code>true</code> if the command was successful.1616* @throws IOException if an error occurred during the transmission.1617*/1618public sun.net.ftp.FtpClient abort() throws sun.net.ftp.FtpProtocolException, IOException {1619issueCommandCheck("ABOR");1620// TODO: Must check the ReplyCode:1621/*1622* From the RFC:1623* There are two cases for the server upon receipt of this1624* command: (1) the FTP service command was already completed,1625* or (2) the FTP service command is still in progress.1626* In the first case, the server closes the data connection1627* (if it is open) and responds with a 226 reply, indicating1628* that the abort command was successfully processed.1629* In the second case, the server aborts the FTP service in1630* progress and closes the data connection, returning a 4261631* reply to indicate that the service request terminated1632* abnormally. The server then sends a 226 reply,1633* indicating that the abort command was successfully1634* processed.1635*/163616371638return this;1639}16401641/**1642* Some methods do not wait until completion before returning, so this1643* method can be called to wait until completion. This is typically the case1644* with commands that trigger a transfer like {@link #getFileStream(String)}.1645* So this method should be called before accessing information related to1646* such a command.1647* <p>This method will actually block reading on the command channel for a1648* notification from the server that the command is finished. Such a1649* notification often carries extra information concerning the completion1650* of the pending action (e.g. number of bytes transfered).</p>1651* <p>Note that this will return true immediately if no command or action1652* is pending</p>1653* <p>It should be also noted that most methods issuing commands to the ftp1654* server will call this method if a previous command is pending.1655* <p>Example of use:1656* <pre>1657* InputStream in = cl.getFileStream("file");1658* ...1659* cl.completePending();1660* long size = cl.getLastTransferSize();1661* </pre>1662* On the other hand, it's not necessary in a case like:1663* <pre>1664* InputStream in = cl.getFileStream("file");1665* // read content1666* ...1667* cl.logout();1668* </pre>1669* <p>Since {@link #logout()} will call completePending() if necessary.</p>1670* @return <code>true</code> if the completion was successful or if no1671* action was pending.1672* @throws IOException1673*/1674public sun.net.ftp.FtpClient completePending() throws sun.net.ftp.FtpProtocolException, IOException {1675while (replyPending) {1676replyPending = false;1677if (!readReply()) {1678throw new sun.net.ftp.FtpProtocolException(getLastResponseString(), lastReplyCode);1679}1680}1681return this;1682}16831684/**1685* Reinitializes the USER parameters on the FTP server1686*1687* @throws FtpProtocolException if the command fails1688*/1689public sun.net.ftp.FtpClient reInit() throws sun.net.ftp.FtpProtocolException, IOException {1690issueCommandCheck("REIN");1691loggedIn = false;1692if (useCrypto) {1693if (server instanceof SSLSocket) {1694javax.net.ssl.SSLSession session = ((SSLSocket) server).getSession();1695session.invalidate();1696// Restore previous socket and streams1697server = oldSocket;1698oldSocket = null;1699try {1700out = new PrintStream(new BufferedOutputStream(server.getOutputStream()),1701true, encoding);1702} catch (UnsupportedEncodingException e) {1703throw new InternalError(encoding + "encoding not found", e);1704}1705in = new BufferedInputStream(server.getInputStream());1706}1707}1708useCrypto = false;1709return this;1710}17111712/**1713* Changes the transfer type (binary, ascii, ebcdic) and issue the1714* proper command (e.g. TYPE A) to the server.1715*1716* @param type the <code>FtpTransferType</code> to use.1717* @return This FtpClient1718* @throws IOException if an error occurs during transmission.1719*/1720public sun.net.ftp.FtpClient setType(TransferType type) throws sun.net.ftp.FtpProtocolException, IOException {1721String cmd = "NOOP";17221723this.type = type;1724if (type == TransferType.ASCII) {1725cmd = "TYPE A";1726}1727if (type == TransferType.BINARY) {1728cmd = "TYPE I";1729}1730if (type == TransferType.EBCDIC) {1731cmd = "TYPE E";1732}1733issueCommandCheck(cmd);1734return this;1735}17361737/**1738* Issues a LIST command to the server to get the current directory1739* listing, and returns the InputStream from the data connection.1740* {@link #completePending()} <b>has</b> to be called once the application1741* is finished writing to the stream.1742*1743* @param path the pathname of the directory to list, or <code>null</code>1744* for the current working directory.1745* @return the <code>InputStream</code> from the resulting data connection1746* @throws IOException if an error occurs during the transmission.1747* @see #changeDirectory(String)1748* @see #listFiles(String)1749*/1750public InputStream list(String path) throws sun.net.ftp.FtpProtocolException, IOException {1751Socket s;1752s = openDataConnection(path == null ? "LIST" : "LIST " + path);1753if (s != null) {1754return createInputStream(s.getInputStream());1755}1756return null;1757}17581759/**1760* Issues a NLST path command to server to get the specified directory1761* content. It differs from {@link #list(String)} method by the fact that1762* it will only list the file names which would make the parsing of the1763* somewhat easier.1764*1765* {@link #completePending()} <b>has</b> to be called once the application1766* is finished writing to the stream.1767*1768* @param path a <code>String</code> containing the pathname of the1769* directory to list or <code>null</code> for the current working1770* directory.1771* @return the <code>InputStream</code> from the resulting data connection1772* @throws IOException if an error occurs during the transmission.1773*/1774public InputStream nameList(String path) throws sun.net.ftp.FtpProtocolException, IOException {1775Socket s;1776s = openDataConnection(path == null ? "NLST" : "NLST " + path);1777if (s != null) {1778return createInputStream(s.getInputStream());1779}1780return null;1781}17821783/**1784* Issues the SIZE [path] command to the server to get the size of a1785* specific file on the server.1786* Note that this command may not be supported by the server. In which1787* case -1 will be returned.1788*1789* @param path a <code>String</code> containing the pathname of the1790* file.1791* @return a <code>long</code> containing the size of the file or -1 if1792* the server returned an error, which can be checked with1793* {@link #getLastReplyCode()}.1794* @throws IOException if an error occurs during the transmission.1795*/1796public long getSize(String path) throws sun.net.ftp.FtpProtocolException, IOException {1797if (path == null || path.isEmpty()) {1798throw new IllegalArgumentException("path can't be null or empty");1799}1800issueCommandCheck("SIZE " + path);1801if (lastReplyCode == FtpReplyCode.FILE_STATUS) {1802String s = getResponseString();1803s = s.substring(4, s.length() - 1);1804return Long.parseLong(s);1805}1806return -1;1807}18081809private static final DateTimeFormatter RFC3659_DATETIME_FORMAT = DateTimeFormatter.ofPattern("yyyyMMddHHmmss[.SSS]")1810.withZone(ZoneOffset.UTC);18111812/**1813* Issues the MDTM [path] command to the server to get the modification1814* time of a specific file on the server.1815* Note that this command may not be supported by the server, in which1816* case <code>null</code> will be returned.1817*1818* @param path a <code>String</code> containing the pathname of the file.1819* @return a <code>Date</code> representing the last modification time1820* or <code>null</code> if the server returned an error, which1821* can be checked with {@link #getLastReplyCode()}.1822* @throws IOException if an error occurs during the transmission.1823*/1824public Date getLastModified(String path) throws sun.net.ftp.FtpProtocolException, IOException {1825issueCommandCheck("MDTM " + path);1826if (lastReplyCode == FtpReplyCode.FILE_STATUS) {1827String s = getResponseString();1828return parseRfc3659TimeValue(s.substring(4, s.length() - 1));1829}1830return null;1831}18321833private static Date parseRfc3659TimeValue(String s) {1834Date result = null;1835try {1836var d = ZonedDateTime.parse(s, RFC3659_DATETIME_FORMAT);1837result = Date.from(d.toInstant());1838} catch (DateTimeParseException ex) {1839}1840return result;1841}18421843/**1844* Sets the parser used to handle the directory output to the specified1845* one. By default the parser is set to one that can handle most FTP1846* servers output (Unix base mostly). However it may be necessary for1847* and application to provide its own parser due to some uncommon1848* output format.1849*1850* @param p The <code>FtpDirParser</code> to use.1851* @see #listFiles(String)1852*/1853public sun.net.ftp.FtpClient setDirParser(FtpDirParser p) {1854parser = p;1855return this;1856}18571858private static class FtpFileIterator implements Iterator<FtpDirEntry>, Closeable {18591860private BufferedReader in = null;1861private FtpDirEntry nextFile = null;1862private FtpDirParser fparser = null;1863private boolean eof = false;18641865public FtpFileIterator(FtpDirParser p, BufferedReader in) {1866this.in = in;1867this.fparser = p;1868readNext();1869}18701871private void readNext() {1872nextFile = null;1873if (eof) {1874return;1875}1876String line = null;1877try {1878do {1879line = in.readLine();1880if (line != null) {1881nextFile = fparser.parseLine(line);1882if (nextFile != null) {1883return;1884}1885}1886} while (line != null);1887in.close();1888} catch (IOException iOException) {1889}1890eof = true;1891}18921893public boolean hasNext() {1894return nextFile != null;1895}18961897public FtpDirEntry next() {1898FtpDirEntry ret = nextFile;1899readNext();1900return ret;1901}19021903public void remove() {1904throw new UnsupportedOperationException("Not supported yet.");1905}19061907public void close() throws IOException {1908if (in != null && !eof) {1909in.close();1910}1911eof = true;1912nextFile = null;1913}1914}19151916/**1917* Issues a MLSD command to the server to get the specified directory1918* listing and applies the current parser to create an Iterator of1919* {@link java.net.ftp.FtpDirEntry}. Note that the Iterator returned is also a1920* {@link java.io.Closeable}.1921* If the server doesn't support the MLSD command, the LIST command is used1922* instead.1923*1924* {@link #completePending()} <b>has</b> to be called once the application1925* is finished iterating through the files.1926*1927* @param path the pathname of the directory to list or <code>null</code>1928* for the current working directoty.1929* @return a <code>Iterator</code> of files or <code>null</code> if the1930* command failed.1931* @throws IOException if an error occurred during the transmission1932* @see #setDirParser(FtpDirParser)1933* @see #changeDirectory(String)1934*/1935public Iterator<FtpDirEntry> listFiles(String path) throws sun.net.ftp.FtpProtocolException, IOException {1936Socket s = null;1937BufferedReader sin = null;1938try {1939s = openDataConnection(path == null ? "MLSD" : "MLSD " + path);1940} catch (sun.net.ftp.FtpProtocolException FtpException) {1941// The server doesn't understand new MLSD command, ignore and fall1942// back to LIST1943}19441945if (s != null) {1946sin = new BufferedReader(new InputStreamReader(s.getInputStream()));1947return new FtpFileIterator(mlsxParser, sin);1948} else {1949s = openDataConnection(path == null ? "LIST" : "LIST " + path);1950if (s != null) {1951sin = new BufferedReader(new InputStreamReader(s.getInputStream()));1952return new FtpFileIterator(parser, sin);1953}1954}1955return null;1956}19571958private boolean sendSecurityData(byte[] buf) throws IOException,1959sun.net.ftp.FtpProtocolException {1960String s = Base64.getMimeEncoder().encodeToString(buf);1961return issueCommand("ADAT " + s);1962}19631964private byte[] getSecurityData() {1965String s = getLastResponseString();1966if (s.substring(4, 9).equalsIgnoreCase("ADAT=")) {1967// Need to get rid of the leading '315 ADAT='1968// and the trailing newline1969return Base64.getMimeDecoder().decode(s.substring(9, s.length() - 1));1970}1971return null;1972}19731974/**1975* Attempts to use Kerberos GSSAPI as an authentication mechanism with the1976* ftp server. This will issue an <code>AUTH GSSAPI</code> command, and if1977* it is accepted by the server, will followup with <code>ADAT</code>1978* command to exchange the various tokens until authentification is1979* successful. This conforms to Appendix I of RFC 2228.1980*1981* @return <code>true</code> if authentication was successful.1982* @throws IOException if an error occurs during the transmission.1983*/1984public sun.net.ftp.FtpClient useKerberos() throws sun.net.ftp.FtpProtocolException, IOException {1985/*1986* Comment out for the moment since it's not in use and would create1987* needless cross-package links.1988*1989issueCommandCheck("AUTH GSSAPI");1990if (lastReplyCode != FtpReplyCode.NEED_ADAT)1991throw new sun.net.ftp.FtpProtocolException("Unexpected reply from server");1992try {1993GSSManager manager = GSSManager.getInstance();1994GSSName name = manager.createName("SERVICE:ftp@"+1995serverAddr.getHostName(), null);1996GSSContext context = manager.createContext(name, null, null,1997GSSContext.DEFAULT_LIFETIME);1998context.requestMutualAuth(true);1999context.requestReplayDet(true);2000context.requestSequenceDet(true);2001context.requestCredDeleg(true);2002byte []inToken = new byte[0];2003while (!context.isEstablished()) {2004byte[] outToken2005= context.initSecContext(inToken, 0, inToken.length);2006// send the output token if generated2007if (outToken != null) {2008if (sendSecurityData(outToken)) {2009inToken = getSecurityData();2010}2011}2012}2013loggedIn = true;2014} catch (GSSException e) {20152016}2017*/2018return this;2019}20202021/**2022* Returns the Welcome string the server sent during initial connection.2023*2024* @return a <code>String</code> containing the message the server2025* returned during connection or <code>null</code>.2026*/2027public String getWelcomeMsg() {2028return welcomeMsg;2029}20302031/**2032* Returns the last reply code sent by the server.2033*2034* @return the lastReplyCode2035*/2036public FtpReplyCode getLastReplyCode() {2037return lastReplyCode;2038}20392040/**2041* Returns the last response string sent by the server.2042*2043* @return the message string, which can be quite long, last returned2044* by the server.2045*/2046public String getLastResponseString() {2047StringBuilder sb = new StringBuilder();2048if (serverResponse != null) {2049for (String l : serverResponse) {2050if (l != null) {2051sb.append(l);2052}2053}2054}2055return sb.toString();2056}20572058/**2059* Returns, when available, the size of the latest started transfer.2060* This is retreived by parsing the response string received as an initial2061* response to a RETR or similar request.2062*2063* @return the size of the latest transfer or -1 if either there was no2064* transfer or the information was unavailable.2065*/2066public long getLastTransferSize() {2067return lastTransSize;2068}20692070/**2071* Returns, when available, the remote name of the last transfered file.2072* This is mainly useful for "put" operation when the unique flag was2073* set since it allows to recover the unique file name created on the2074* server which may be different from the one submitted with the command.2075*2076* @return the name the latest transfered file remote name, or2077* <code>null</code> if that information is unavailable.2078*/2079public String getLastFileName() {2080return lastFileName;2081}20822083/**2084* Attempts to switch to a secure, encrypted connection. This is done by2085* sending the "AUTH TLS" command.2086* <p>See <a href="http://www.ietf.org/rfc/rfc4217.txt">RFC 4217</a></p>2087* If successful this will establish a secure command channel with the2088* server, it will also make it so that all other transfers (e.g. a RETR2089* command) will be done over an encrypted channel as well unless a2090* {@link #reInit()} command or a {@link #endSecureSession()} command is issued.2091*2092* @return <code>true</code> if the operation was successful.2093* @throws IOException if an error occurred during the transmission.2094* @see #endSecureSession()2095*/2096public sun.net.ftp.FtpClient startSecureSession() throws sun.net.ftp.FtpProtocolException, IOException {2097if (!isConnected()) {2098throw new sun.net.ftp.FtpProtocolException("Not connected yet", FtpReplyCode.BAD_SEQUENCE);2099}2100if (sslFact == null) {2101try {2102sslFact = (SSLSocketFactory) SSLSocketFactory.getDefault();2103} catch (Exception e) {2104throw new IOException(e.getLocalizedMessage());2105}2106}2107issueCommandCheck("AUTH TLS");2108Socket s = null;2109try {2110s = sslFact.createSocket(server, serverAddr.getHostName(), serverAddr.getPort(), true);2111} catch (javax.net.ssl.SSLException ssle) {2112try {2113disconnect();2114} catch (Exception e) {2115}2116throw ssle;2117}2118// Remember underlying socket so we can restore it later2119oldSocket = server;2120server = s;2121try {2122out = new PrintStream(new BufferedOutputStream(server.getOutputStream()),2123true, encoding);2124} catch (UnsupportedEncodingException e) {2125throw new InternalError(encoding + "encoding not found", e);2126}2127in = new BufferedInputStream(server.getInputStream());21282129issueCommandCheck("PBSZ 0");2130issueCommandCheck("PROT P");2131useCrypto = true;2132return this;2133}21342135/**2136* Sends a <code>CCC</code> command followed by a <code>PROT C</code>2137* command to the server terminating an encrypted session and reverting2138* back to a non crypted transmission.2139*2140* @return <code>true</code> if the operation was successful.2141* @throws IOException if an error occurred during transmission.2142* @see #startSecureSession()2143*/2144public sun.net.ftp.FtpClient endSecureSession() throws sun.net.ftp.FtpProtocolException, IOException {2145if (!useCrypto) {2146return this;2147}21482149issueCommandCheck("CCC");2150issueCommandCheck("PROT C");2151useCrypto = false;2152// Restore previous socket and streams2153server = oldSocket;2154oldSocket = null;2155try {2156out = new PrintStream(new BufferedOutputStream(server.getOutputStream()),2157true, encoding);2158} catch (UnsupportedEncodingException e) {2159throw new InternalError(encoding + "encoding not found", e);2160}2161in = new BufferedInputStream(server.getInputStream());21622163return this;2164}21652166/**2167* Sends the "Allocate" (ALLO) command to the server telling it to2168* pre-allocate the specified number of bytes for the next transfer.2169*2170* @param size The number of bytes to allocate.2171* @return <code>true</code> if the operation was successful.2172* @throws IOException if an error occurred during the transmission.2173*/2174public sun.net.ftp.FtpClient allocate(long size) throws sun.net.ftp.FtpProtocolException, IOException {2175issueCommandCheck("ALLO " + size);2176return this;2177}21782179/**2180* Sends the "Structure Mount" (SMNT) command to the server. This let the2181* user mount a different file system data structure without altering his2182* login or accounting information.2183*2184* @param struct a <code>String</code> containing the name of the2185* structure to mount.2186* @return <code>true</code> if the operation was successful.2187* @throws IOException if an error occurred during the transmission.2188*/2189public sun.net.ftp.FtpClient structureMount(String struct) throws sun.net.ftp.FtpProtocolException, IOException {2190issueCommandCheck("SMNT " + struct);2191return this;2192}21932194/**2195* Sends a SYST (System) command to the server and returns the String2196* sent back by the server describing the operating system at the2197* server.2198*2199* @return a <code>String</code> describing the OS, or <code>null</code>2200* if the operation was not successful.2201* @throws IOException if an error occurred during the transmission.2202*/2203public String getSystem() throws sun.net.ftp.FtpProtocolException, IOException {2204issueCommandCheck("SYST");2205/*2206* 215 UNIX Type: L8 Version: SUNOS2207*/2208String resp = getResponseString();2209// Get rid of the leading code and blank2210return resp.substring(4);2211}22122213/**2214* Sends the HELP command to the server, with an optional command, like2215* SITE, and returns the text sent back by the server.2216*2217* @param cmd the command for which the help is requested or2218* <code>null</code> for the general help2219* @return a <code>String</code> containing the text sent back by the2220* server, or <code>null</code> if the command failed.2221* @throws IOException if an error occurred during transmission2222*/2223public String getHelp(String cmd) throws sun.net.ftp.FtpProtocolException, IOException {2224issueCommandCheck("HELP " + cmd);2225/**2226*2227* HELP2228* 214-The following commands are implemented.2229* USER EPRT STRU ALLO DELE SYST RMD MDTM ADAT2230* PASS EPSV MODE REST CWD STAT PWD PROT2231* QUIT LPRT RETR RNFR LIST HELP CDUP PBSZ2232* PORT LPSV STOR RNTO NLST NOOP STOU AUTH2233* PASV TYPE APPE ABOR SITE MKD SIZE CCC2234* 214 Direct comments to ftp-bugs@jsn.2235*2236* HELP SITE2237* 214-The following SITE commands are implemented.2238* UMASK HELP GROUPS2239* IDLE ALIAS CHECKMETHOD2240* CHMOD CDPATH CHECKSUM2241* 214 Direct comments to ftp-bugs@jsn.2242*/2243Vector<String> resp = getResponseStrings();2244if (resp.size() == 1) {2245// Single line response2246return resp.get(0).substring(4);2247}2248// on multiple lines answers, like the ones above, remove 1st and last2249// line, concat the others.2250StringBuilder sb = new StringBuilder();2251for (int i = 1; i < resp.size() - 1; i++) {2252sb.append(resp.get(i).substring(3));2253}2254return sb.toString();2255}22562257/**2258* Sends the SITE command to the server. This is used by the server2259* to provide services specific to his system that are essential2260* to file transfer.2261*2262* @param cmd the command to be sent.2263* @return <code>true</code> if the command was successful.2264* @throws IOException if an error occurred during transmission2265*/2266public sun.net.ftp.FtpClient siteCmd(String cmd) throws sun.net.ftp.FtpProtocolException, IOException {2267issueCommandCheck("SITE " + cmd);2268return this;2269}2270}227122722273