/* | 
|
 * Copyright (c) 2009, 2017, Oracle and/or its affiliates. All rights reserved. | 
|
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | 
|
 * | 
|
 * This code is free software; you can redistribute it and/or modify it | 
|
 * under the terms of the GNU General Public License version 2 only, as | 
|
 * published by the Free Software Foundation.  Oracle designates this | 
|
 * particular file as subject to the "Classpath" exception as provided | 
|
 * by Oracle in the LICENSE file that accompanied this code. | 
|
 * | 
|
 * This code is distributed in the hope that it will be useful, but WITHOUT | 
|
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | 
|
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License | 
|
 * version 2 for more details (a copy is included in the LICENSE file that | 
|
 * accompanied this code). | 
|
 * | 
|
 * You should have received a copy of the GNU General Public License version | 
|
 * 2 along with this work; if not, write to the Free Software Foundation, | 
|
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | 
|
 * | 
|
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | 
|
 * or visit www.oracle.com if you need additional information or have any | 
|
 * questions. | 
|
*/  | 
|
package sun.net.ftp.impl;  | 
|
import java.net.*;  | 
|
import java.io.*;  | 
|
import java.security.AccessController;  | 
|
import java.security.PrivilegedAction;  | 
|
import java.text.DateFormat;  | 
|
import java.text.ParseException;  | 
|
import java.text.SimpleDateFormat;  | 
|
import java.util.ArrayList;  | 
|
import java.util.Calendar;  | 
|
import java.util.Date;  | 
|
import java.util.Iterator;  | 
|
import java.util.List;  | 
|
import java.util.TimeZone;  | 
|
import java.util.Vector;  | 
|
import java.util.regex.Matcher;  | 
|
import java.util.regex.Pattern;  | 
|
import javax.net.ssl.SSLSocket;  | 
|
import javax.net.ssl.SSLSocketFactory;  | 
|
import sun.misc.BASE64Decoder;  | 
|
import sun.misc.BASE64Encoder;  | 
|
import sun.net.ftp.*;  | 
|
import sun.util.logging.PlatformLogger;  | 
|
public class FtpClient extends sun.net.ftp.FtpClient {  | 
|
private static int defaultSoTimeout;  | 
|
private static int defaultConnectTimeout;  | 
|
private static final PlatformLogger logger =  | 
|
PlatformLogger.getLogger("sun.net.ftp.FtpClient");  | 
|
private Proxy proxy;  | 
|
private Socket server;  | 
|
private PrintStream out;  | 
|
private InputStream in;  | 
|
private int readTimeout = -1;  | 
|
private int connectTimeout = -1;  | 
|
    /* Name of encoding to use for output */ | 
|
private static String encoding = "ISO8859_1";  | 
|
    /** remember the ftp server name because we may need it */ | 
|
private InetSocketAddress serverAddr;  | 
|
private boolean replyPending = false;  | 
|
private boolean loggedIn = false;  | 
|
private boolean useCrypto = false;  | 
|
private SSLSocketFactory sslFact;  | 
|
private Socket oldSocket;  | 
|
    /** Array of strings (usually 1 entry) for the last reply from the server. */ | 
|
private Vector<String> serverResponse = new Vector<String>(1);  | 
|
    /** The last reply code from the ftp daemon. */ | 
|
private FtpReplyCode lastReplyCode = null;  | 
|
    /** Welcome message from the server, if any. */ | 
|
private String welcomeMsg;  | 
|
    /** | 
|
     * Only passive mode used in JDK. See Bug 8010784. | 
|
*/  | 
|
private final boolean passiveMode = true;  | 
|
private TransferType type = TransferType.BINARY;  | 
|
private long restartOffset = 0;  | 
|
    private long lastTransSize = -1; // -1 means 'unknown size' | 
|
private String lastFileName;  | 
|
    /** | 
|
     * Static members used by the parser | 
|
*/  | 
|
private static String[] patStrings = {  | 
|
        // drwxr-xr-x  1 user01        ftp   512 Jan 29 23:32 prog | 
|
        "([\\-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}*)", | 
|
        // drwxr-xr-x  1 user01        ftp   512 Jan 29 1997 prog | 
|
        "([\\-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}*)", | 
|
        // 04/28/2006  09:12a               3,563 genBuffer.sh | 
|
        "(\\d{2}/\\d{2}/\\d{4})\\s*(\\d{2}:\\d{2}[ap])\\s*((?:[0-9,]+)|(?:<DIR>))\\s*(\\p{Graph}*)", | 
|
        // 01-29-97    11:32PM <DIR> prog | 
|
        "(\\d{2}-\\d{2}-\\d{2})\\s*(\\d{2}:\\d{2}[AP]M)\\s*((?:[0-9,]+)|(?:<DIR>))\\s*(\\p{Graph}*)" | 
|
};  | 
|
    private static int[][] patternGroups = { | 
|
// 0 - file, 1 - size, 2 - date, 3 - time, 4 - year, 5 - permissions,  | 
|
        // 6 - user, 7 - group | 
|
        {7, 4, 5, 6, 0, 1, 2, 3}, | 
|
        {7, 4, 5, 0, 6, 1, 2, 3}, | 
|
        {4, 3, 1, 2, 0, 0, 0, 0}, | 
|
        {4, 3, 1, 2, 0, 0, 0, 0}}; | 
|
private static Pattern[] patterns;  | 
|
private static Pattern linkp = Pattern.compile("(\\p{Print}+) \\-\\> (\\p{Print}+)$");  | 
|
private DateFormat df = DateFormat.getDateInstance(DateFormat.MEDIUM, java.util.Locale.US);  | 
|
    static { | 
|
        final int vals[] = {0, 0}; | 
|
final String encs[] = {null};  | 
|
AccessController.doPrivileged(  | 
|
new PrivilegedAction<Object>() {  | 
|
public Object run() {  | 
|
vals[0] = Integer.getInteger("sun.net.client.defaultReadTimeout", 300_000).intValue();  | 
|
vals[1] = Integer.getInteger("sun.net.client.defaultConnectTimeout", 300_000).intValue();  | 
|
encs[0] = System.getProperty("file.encoding", "ISO8859_1");  | 
|
return null;  | 
|
}  | 
|
});  | 
|
if (vals[0] == 0) {  | 
|
defaultSoTimeout = -1;  | 
|
        } else { | 
|
defaultSoTimeout = vals[0];  | 
|
}  | 
|
if (vals[1] == 0) {  | 
|
defaultConnectTimeout = -1;  | 
|
        } else { | 
|
defaultConnectTimeout = vals[1];  | 
|
}  | 
|
encoding = encs[0];  | 
|
        try { | 
|
if (!isASCIISuperset(encoding)) {  | 
|
encoding = "ISO8859_1";  | 
|
}  | 
|
} catch (Exception e) {  | 
|
encoding = "ISO8859_1";  | 
|
}  | 
|
patterns = new Pattern[patStrings.length];  | 
|
for (int i = 0; i < patStrings.length; i++) {  | 
|
patterns[i] = Pattern.compile(patStrings[i]);  | 
|
}  | 
|
}  | 
|
    /** | 
|
     * Test the named character encoding to verify that it converts ASCII | 
|
     * characters correctly. We have to use an ASCII based encoding, or else | 
|
     * the NetworkClients will not work correctly in EBCDIC based systems. | 
|
     * However, we cannot just use ASCII or ISO8859_1 universally, because in | 
|
     * Asian locales, non-ASCII characters may be embedded in otherwise | 
|
     * ASCII based protocols (eg. HTTP). The specifications (RFC2616, 2398) | 
|
     * are a little ambiguous in this matter. For instance, RFC2398 [part 2.1] | 
|
     * says that the HTTP request URI should be escaped using a defined | 
|
     * mechanism, but there is no way to specify in the escaped string what | 
|
     * the original character set is. It is not correct to assume that | 
|
     * UTF-8 is always used (as in URLs in HTML 4.0).  For this reason, | 
|
     * until the specifications are updated to deal with this issue more | 
|
     * comprehensively, and more importantly, HTTP servers are known to | 
|
     * support these mechanisms, we will maintain the current behavior | 
|
     * where it is possible to send non-ASCII characters in their original | 
|
     * unescaped form. | 
|
*/  | 
|
private static boolean isASCIISuperset(String encoding) throws Exception {  | 
|
String chkS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" +  | 
|
                "abcdefghijklmnopqrstuvwxyz-_.!~*'();/?:@&=+$,"; | 
|
        // Expected byte sequence for string above | 
|
        byte[] chkB = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 71, 72, | 
|
73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99,  | 
|
100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114,  | 
|
115, 116, 117, 118, 119, 120, 121, 122, 45, 95, 46, 33, 126, 42, 39, 40, 41, 59,  | 
|
47, 63, 58, 64, 38, 61, 43, 36, 44};  | 
|
byte[] b = chkS.getBytes(encoding);  | 
|
return java.util.Arrays.equals(b, chkB);  | 
|
}  | 
|
private class DefaultParser implements FtpDirParser {  | 
|
        /** | 
|
         * Possible patterns: | 
|
         * | 
|
         *  drwxr-xr-x  1 user01        ftp   512 Jan 29 23:32 prog | 
|
         *  drwxr-xr-x  1 user01        ftp   512 Jan 29 1997 prog | 
|
         *  drwxr-xr-x  1 1             1     512 Jan 29 23:32 prog | 
|
         *  lrwxr-xr-x  1 user01        ftp   512 Jan 29 23:32 prog -> prog2000 | 
|
         *  drwxr-xr-x  1 username      ftp   512 Jan 29 23:32 prog | 
|
         *  -rw-r--r--  1 jcc      staff     105009 Feb  3 15:05 test.1 | 
|
         * | 
|
         *  01-29-97    11:32PM <DIR> prog | 
|
         *  04/28/2006  09:12a               3,563 genBuffer.sh | 
|
         * | 
|
         *  drwxr-xr-x  folder   0       Jan 29 23:32 prog | 
|
         * | 
|
         *  0 DIR 01-29-97 23:32 PROG | 
|
*/  | 
|
        private DefaultParser() { | 
|
}  | 
|
public FtpDirEntry parseLine(String line) {  | 
|
String fdate = null;  | 
|
String fsize = null;  | 
|
String time = null;  | 
|
String filename = null;  | 
|
String permstring = null;  | 
|
String username = null;  | 
|
String groupname = null;  | 
|
boolean dir = false;  | 
|
Calendar now = Calendar.getInstance();  | 
|
int year = now.get(Calendar.YEAR);  | 
|
Matcher m = null;  | 
|
for (int j = 0; j < patterns.length; j++) {  | 
|
m = patterns[j].matcher(line);  | 
|
if (m.find()) {  | 
|
// 0 - file, 1 - size, 2 - date, 3 - time, 4 - year,  | 
|
                    // 5 - permissions, 6 - user, 7 - group | 
|
filename = m.group(patternGroups[j][0]);  | 
|
fsize = m.group(patternGroups[j][1]);  | 
|
fdate = m.group(patternGroups[j][2]);  | 
|
if (patternGroups[j][4] > 0) {  | 
|
fdate += (", " + m.group(patternGroups[j][4]));  | 
|
} else if (patternGroups[j][3] > 0) {  | 
|
fdate += (", " + String.valueOf(year));  | 
|
}  | 
|
if (patternGroups[j][3] > 0) {  | 
|
time = m.group(patternGroups[j][3]);  | 
|
}  | 
|
if (patternGroups[j][5] > 0) {  | 
|
permstring = m.group(patternGroups[j][5]);  | 
|
dir = permstring.startsWith("d");  | 
|
}  | 
|
if (patternGroups[j][6] > 0) {  | 
|
username = m.group(patternGroups[j][6]);  | 
|
}  | 
|
if (patternGroups[j][7] > 0) {  | 
|
groupname = m.group(patternGroups[j][7]);  | 
|
}  | 
|
                    // Old DOS format | 
|
if ("<DIR>".equals(fsize)) {  | 
|
dir = true;  | 
|
fsize = null;  | 
|
}  | 
|
}  | 
|
}  | 
|
if (filename != null) {  | 
|
Date d;  | 
|
                try { | 
|
d = df.parse(fdate);  | 
|
} catch (Exception e) {  | 
|
d = null;  | 
|
}  | 
|
if (d != null && time != null) {  | 
|
int c = time.indexOf(":");  | 
|
now.setTime(d);  | 
|
now.set(Calendar.HOUR, Integer.parseInt(time.substring(0, c)));  | 
|
now.set(Calendar.MINUTE, Integer.parseInt(time.substring(c + 1)));  | 
|
d = now.getTime();  | 
|
}  | 
|
// see if it's a symbolic link, i.e. the name if followed  | 
|
                // by a -> and a path | 
|
Matcher m2 = linkp.matcher(filename);  | 
|
if (m2.find()) {  | 
|
                    // Keep only the name then | 
|
filename = m2.group(1);  | 
|
}  | 
|
boolean[][] perms = new boolean[3][3];  | 
|
for (int i = 0; i < 3; i++) {  | 
|
for (int j = 0; j < 3; j++) {  | 
|
perms[i][j] = (permstring.charAt((i * 3) + j) != '-');  | 
|
}  | 
|
}  | 
|
FtpDirEntry file = new FtpDirEntry(filename);  | 
|
file.setUser(username).setGroup(groupname);  | 
|
file.setSize(Long.parseLong(fsize)).setLastModified(d);  | 
|
file.setPermissions(perms);  | 
|
file.setType(dir ? FtpDirEntry.Type.DIR : (line.charAt(0) == 'l' ? FtpDirEntry.Type.LINK : FtpDirEntry.Type.FILE));  | 
|
return file;  | 
|
}  | 
|
return null;  | 
|
}  | 
|
}  | 
|
private class MLSxParser implements FtpDirParser {  | 
|
private SimpleDateFormat df = new SimpleDateFormat("yyyyMMddhhmmss");  | 
|
public FtpDirEntry parseLine(String line) {  | 
|
String name = null;  | 
|
int i = line.lastIndexOf(";");  | 
|
if (i > 0) {  | 
|
name = line.substring(i + 1).trim();  | 
|
line = line.substring(0, i);  | 
|
            } else { | 
|
name = line.trim();  | 
|
line = "";  | 
|
}  | 
|
FtpDirEntry file = new FtpDirEntry(name);  | 
|
while (!line.isEmpty()) {  | 
|
String s;  | 
|
i = line.indexOf(";");  | 
|
if (i > 0) {  | 
|
s = line.substring(0, i);  | 
|
line = line.substring(i + 1);  | 
|
                } else { | 
|
s = line;  | 
|
line = "";  | 
|
}  | 
|
i = s.indexOf("=");  | 
|
if (i > 0) {  | 
|
String fact = s.substring(0, i);  | 
|
String value = s.substring(i + 1);  | 
|
file.addFact(fact, value);  | 
|
}  | 
|
}  | 
|
String s = file.getFact("Size");  | 
|
if (s != null) {  | 
|
file.setSize(Long.parseLong(s));  | 
|
}  | 
|
s = file.getFact("Modify");  | 
|
if (s != null) {  | 
|
Date d = null;  | 
|
                try { | 
|
d = df.parse(s);  | 
|
} catch (ParseException ex) {  | 
|
}  | 
|
if (d != null) {  | 
|
file.setLastModified(d);  | 
|
}  | 
|
}  | 
|
s = file.getFact("Create");  | 
|
if (s != null) {  | 
|
Date d = null;  | 
|
                try { | 
|
d = df.parse(s);  | 
|
} catch (ParseException ex) {  | 
|
}  | 
|
if (d != null) {  | 
|
file.setCreated(d);  | 
|
}  | 
|
}  | 
|
s = file.getFact("Type");  | 
|
if (s != null) {  | 
|
if (s.equalsIgnoreCase("file")) {  | 
|
file.setType(FtpDirEntry.Type.FILE);  | 
|
}  | 
|
if (s.equalsIgnoreCase("dir")) {  | 
|
file.setType(FtpDirEntry.Type.DIR);  | 
|
}  | 
|
if (s.equalsIgnoreCase("cdir")) {  | 
|
file.setType(FtpDirEntry.Type.CDIR);  | 
|
}  | 
|
if (s.equalsIgnoreCase("pdir")) {  | 
|
file.setType(FtpDirEntry.Type.PDIR);  | 
|
}  | 
|
}  | 
|
return file;  | 
|
}  | 
|
};  | 
|
private FtpDirParser parser = new DefaultParser();  | 
|
private FtpDirParser mlsxParser = new MLSxParser();  | 
|
private static Pattern transPat = null;  | 
|
    private void getTransferSize() { | 
|
lastTransSize = -1;  | 
|
        /** | 
|
         * If it's a start of data transfer response, let's try to extract | 
|
         * the size from the response string. Usually it looks like that: | 
|
         * | 
|
         * 150 Opening BINARY mode data connection for foo (6701 bytes). | 
|
*/  | 
|
String response = getLastResponseString();  | 
|
if (transPat == null) {  | 
|
transPat = Pattern.compile("150 Opening .*\\((\\d+) bytes\\).");  | 
|
}  | 
|
Matcher m = transPat.matcher(response);  | 
|
if (m.find()) {  | 
|
String s = m.group(1);  | 
|
lastTransSize = Long.parseLong(s);  | 
|
}  | 
|
}  | 
|
    /** | 
|
     * extract the created file name from the response string: | 
|
     * 226 Transfer complete (unique file name:toto.txt.1). | 
|
     * Usually happens when a STOU (store unique) command had been issued. | 
|
*/  | 
|
    private void getTransferName() { | 
|
lastFileName = null;  | 
|
String response = getLastResponseString();  | 
|
int i = response.indexOf("unique file name:");  | 
|
int e = response.lastIndexOf(')');  | 
|
if (i >= 0) {  | 
|
i += 17; // Length of "unique file name:"  | 
|
lastFileName = response.substring(i, e);  | 
|
}  | 
|
}  | 
|
    /** | 
|
     * Pulls the response from the server and returns the code as a | 
|
     * number. Returns -1 on failure. | 
|
*/  | 
|
private int readServerResponse() throws IOException {  | 
|
StringBuffer replyBuf = new StringBuffer(32);  | 
|
int c;  | 
|
int continuingCode = -1;  | 
|
int code;  | 
|
String response;  | 
|
serverResponse.setSize(0);  | 
|
        while (true) { | 
|
while ((c = in.read()) != -1) {  | 
|
if (c == '\r') {  | 
|
if ((c = in.read()) != '\n') {  | 
|
replyBuf.append('\r');  | 
|
}  | 
|
}  | 
|
replyBuf.append((char) c);  | 
|
if (c == '\n') {  | 
|
break;  | 
|
}  | 
|
}  | 
|
response = replyBuf.toString();  | 
|
replyBuf.setLength(0);  | 
|
if (logger.isLoggable(PlatformLogger.Level.FINEST)) {  | 
|
logger.finest("Server [" + serverAddr + "] --> " + response);  | 
|
}  | 
|
if (response.length() == 0) {  | 
|
code = -1;  | 
|
            } else { | 
|
                try { | 
|
code = Integer.parseInt(response.substring(0, 3));  | 
|
} catch (NumberFormatException e) {  | 
|
code = -1;  | 
|
} catch (StringIndexOutOfBoundsException e) {  | 
|
                    /* this line doesn't contain a response code, so | 
|
we just completely ignore it */  | 
|
continue;  | 
|
}  | 
|
}  | 
|
serverResponse.addElement(response);  | 
|
if (continuingCode != -1) {  | 
|
                /* we've seen a ###- sequence */ | 
|
if (code != continuingCode ||  | 
|
(response.length() >= 4 && response.charAt(3) == '-')) {  | 
|
continue;  | 
|
                } else { | 
|
                    /* seen the end of code sequence */ | 
|
continuingCode = -1;  | 
|
break;  | 
|
}  | 
|
} else if (response.length() >= 4 && response.charAt(3) == '-') {  | 
|
continuingCode = code;  | 
|
continue;  | 
|
            } else { | 
|
break;  | 
|
}  | 
|
}  | 
|
return code;  | 
|
}  | 
|
    /** Sends command <i>cmd</i> to the server. */ | 
|
private void sendServer(String cmd) {  | 
|
out.print(cmd);  | 
|
if (logger.isLoggable(PlatformLogger.Level.FINEST)) {  | 
|
logger.finest("Server [" + serverAddr + "] <-- " + cmd);  | 
|
}  | 
|
}  | 
|
    /** converts the server response into a string. */ | 
|
private String getResponseString() {  | 
|
return serverResponse.elementAt(0);  | 
|
}  | 
|
    /** Returns all server response strings. */ | 
|
private Vector<String> getResponseStrings() {  | 
|
return serverResponse;  | 
|
}  | 
|
    /** | 
|
     * Read the reply from the FTP server. | 
|
     * | 
|
     * @return <code>true</code> if the command was successful | 
|
     * @throws IOException if an error occurred | 
|
*/  | 
|
private boolean readReply() throws IOException {  | 
|
lastReplyCode = FtpReplyCode.find(readServerResponse());  | 
|
if (lastReplyCode.isPositivePreliminary()) {  | 
|
replyPending = true;  | 
|
return true;  | 
|
}  | 
|
if (lastReplyCode.isPositiveCompletion() || lastReplyCode.isPositiveIntermediate()) {  | 
|
if (lastReplyCode == FtpReplyCode.CLOSING_DATA_CONNECTION) {  | 
|
getTransferName();  | 
|
}  | 
|
return true;  | 
|
}  | 
|
return false;  | 
|
}  | 
|
    /** | 
|
     * Sends a command to the FTP server and returns the error code | 
|
     * (which can be a "success") sent by the server. | 
|
     * | 
|
     * @param cmd | 
|
     * @return <code>true</code> if the command was successful | 
|
     * @throws IOException | 
|
*/  | 
|
private boolean issueCommand(String cmd) throws IOException,  | 
|
sun.net.ftp.FtpProtocolException {  | 
|
if (!isConnected()) {  | 
|
throw new IllegalStateException("Not connected");  | 
|
}  | 
|
if (replyPending) {  | 
|
            try { | 
|
completePending();  | 
|
} catch (sun.net.ftp.FtpProtocolException e) {  | 
|
// ignore...  | 
|
}  | 
|
}  | 
|
if (cmd.indexOf('\n') != -1) {  | 
|
sun.net.ftp.FtpProtocolException ex  | 
|
= new sun.net.ftp.FtpProtocolException("Illegal FTP command");  | 
|
ex.initCause(new IllegalArgumentException("Illegal carriage return"));  | 
|
throw ex;  | 
|
}  | 
|
sendServer(cmd + "\r\n");  | 
|
return readReply();  | 
|
}  | 
|
    /** | 
|
     * Send a command to the FTP server and check for success. | 
|
     * | 
|
     * @param cmd String containing the command | 
|
     * | 
|
     * @throws FtpProtocolException if an error occurred | 
|
*/  | 
|
private void issueCommandCheck(String cmd) throws sun.net.ftp.FtpProtocolException, IOException {  | 
|
if (!issueCommand(cmd)) {  | 
|
throw new sun.net.ftp.FtpProtocolException(cmd + ":" + getResponseString(), getLastReplyCode());  | 
|
}  | 
|
}  | 
|
private static Pattern epsvPat = null;  | 
|
private static Pattern pasvPat = null;  | 
|
    /** | 
|
     * Opens a "PASSIVE" connection with the server and returns the connected | 
|
     * <code>Socket</code>. | 
|
     * | 
|
     * @return the connected <code>Socket</code> | 
|
     * @throws IOException if the connection was unsuccessful. | 
|
*/  | 
|
private Socket openPassiveDataConnection(String cmd) throws sun.net.ftp.FtpProtocolException, IOException {  | 
|
String serverAnswer;  | 
|
int port;  | 
|
InetSocketAddress dest = null;  | 
|
        /** | 
|
         * Here is the idea: | 
|
         * | 
|
         * - First we want to try the new (and IPv6 compatible) EPSV command | 
|
         *   But since we want to be nice with NAT software, we'll issue the | 
|
         *   EPSV ALL command first. | 
|
         *   EPSV is documented in RFC2428 | 
|
         * - If EPSV fails, then we fall back to the older, yet ok, PASV | 
|
         * - If PASV fails as well, then we throw an exception and the calling | 
|
         *   method will have to try the EPRT or PORT command | 
|
*/  | 
|
if (issueCommand("EPSV ALL")) {  | 
|
            // We can safely use EPSV commands | 
|
issueCommandCheck("EPSV");  | 
|
serverAnswer = getResponseString();  | 
|
// The response string from a EPSV command will contain the port number  | 
|
// the format will be :  | 
|
// 229 Entering Extended PASSIVE Mode (|||58210|)  | 
|
//  | 
|
// So we'll use the regular expresions package to parse the output.  | 
|
if (epsvPat == null) {  | 
|
epsvPat = Pattern.compile("^229 .* \\(\\|\\|\\|(\\d+)\\|\\)");  | 
|
}  | 
|
Matcher m = epsvPat.matcher(serverAnswer);  | 
|
if (!m.find()) {  | 
|
throw new sun.net.ftp.FtpProtocolException("EPSV failed : " + serverAnswer);  | 
|
}  | 
|
            // Yay! Let's extract the port number | 
|
String s = m.group(1);  | 
|
port = Integer.parseInt(s);  | 
|
InetAddress add = server.getInetAddress();  | 
|
if (add != null) {  | 
|
dest = new InetSocketAddress(add, port);  | 
|
            } else { | 
|
// This means we used an Unresolved address to connect in  | 
|
// the first place. Most likely because the proxy is doing  | 
|
// the name resolution for us, so let's keep using unresolved  | 
|
                // address. | 
|
dest = InetSocketAddress.createUnresolved(serverAddr.getHostName(), port);  | 
|
}  | 
|
        } else { | 
|
            // EPSV ALL failed, so Let's try the regular PASV cmd | 
|
issueCommandCheck("PASV");  | 
|
serverAnswer = getResponseString();  | 
|
// Let's parse the response String to get the IP & port to connect  | 
|
// to. The String should be in the following format :  | 
|
//  | 
|
// 227 Entering PASSIVE Mode (A1,A2,A3,A4,p1,p2)  | 
|
//  | 
|
// Note that the two parenthesis are optional  | 
|
//  | 
|
// The IP address is A1.A2.A3.A4 and the port is p1 * 256 + p2  | 
|
//  | 
|
// The regular expression is a bit more complex this time, because  | 
|
// the parenthesis are optionals and we have to use 3 groups.  | 
|
if (pasvPat == null) {  | 
|
pasvPat = Pattern.compile("227 .* \\(?(\\d{1,3},\\d{1,3},\\d{1,3},\\d{1,3}),(\\d{1,3}),(\\d{1,3})\\)?");  | 
|
}  | 
|
Matcher m = pasvPat.matcher(serverAnswer);  | 
|
if (!m.find()) {  | 
|
throw new sun.net.ftp.FtpProtocolException("PASV failed : " + serverAnswer);  | 
|
}  | 
|
            // Get port number out of group 2 & 3 | 
|
port = Integer.parseInt(m.group(3)) + (Integer.parseInt(m.group(2)) << 8);  | 
|
            // IP address is simple | 
|
String s = m.group(1).replace(',', '.');  | 
|
dest = new InetSocketAddress(s, port);  | 
|
}  | 
|
        // Got everything, let's open the socket! | 
|
Socket s;  | 
|
if (proxy != null) {  | 
|
if (proxy.type() == Proxy.Type.SOCKS) {  | 
|
s = AccessController.doPrivileged(  | 
|
new PrivilegedAction<Socket>() {  | 
|
public Socket run() {  | 
|
return new Socket(proxy);  | 
|
}  | 
|
});  | 
|
            } else { | 
|
s = new Socket(Proxy.NO_PROXY);  | 
|
}  | 
|
        } else { | 
|
s = new Socket();  | 
|
}  | 
|
InetAddress serverAddress = AccessController.doPrivileged(  | 
|
new PrivilegedAction<InetAddress>() {  | 
|
@Override  | 
|
public InetAddress run() {  | 
|
return server.getLocalAddress();  | 
|
}  | 
|
});  | 
|
// Bind the socket to the same address as the control channel. This  | 
|
        // is needed in case of multi-homed systems. | 
|
s.bind(new InetSocketAddress(serverAddress, 0));  | 
|
if (connectTimeout >= 0) {  | 
|
s.connect(dest, connectTimeout);  | 
|
        } else { | 
|
if (defaultConnectTimeout > 0) {  | 
|
s.connect(dest, defaultConnectTimeout);  | 
|
            } else { | 
|
s.connect(dest);  | 
|
}  | 
|
}  | 
|
if (readTimeout >= 0) {  | 
|
s.setSoTimeout(readTimeout);  | 
|
} else if (defaultSoTimeout > 0) {  | 
|
s.setSoTimeout(defaultSoTimeout);  | 
|
}  | 
|
if (useCrypto) {  | 
|
            try { | 
|
s = sslFact.createSocket(s, dest.getHostName(), dest.getPort(), true);  | 
|
} catch (Exception e) {  | 
|
throw new sun.net.ftp.FtpProtocolException("Can't open secure data channel: " + e);  | 
|
}  | 
|
}  | 
|
if (!issueCommand(cmd)) {  | 
|
s.close();  | 
|
if (getLastReplyCode() == FtpReplyCode.FILE_UNAVAILABLE) {  | 
|
                // Ensure backward compatibility | 
|
throw new FileNotFoundException(cmd);  | 
|
}  | 
|
throw new sun.net.ftp.FtpProtocolException(cmd + ":" + getResponseString(), getLastReplyCode());  | 
|
}  | 
|
return s;  | 
|
}  | 
|
    /** | 
|
     * Opens a data connection with the server according to the set mode | 
|
     * (ACTIVE or PASSIVE) then send the command passed as an argument. | 
|
     * | 
|
     * @param cmd the <code>String</code> containing the command to execute | 
|
     * @return the connected <code>Socket</code> | 
|
     * @throws IOException if the connection or command failed | 
|
*/  | 
|
private Socket openDataConnection(String cmd) throws sun.net.ftp.FtpProtocolException, IOException {  | 
|
Socket clientSocket;  | 
|
if (passiveMode) {  | 
|
            try { | 
|
return openPassiveDataConnection(cmd);  | 
|
} catch (sun.net.ftp.FtpProtocolException e) {  | 
|
// If Passive mode failed, fall back on PORT  | 
|
                // Otherwise throw exception | 
|
String errmsg = e.getMessage();  | 
|
if (!errmsg.startsWith("PASV") && !errmsg.startsWith("EPSV")) {  | 
|
throw e;  | 
|
}  | 
|
}  | 
|
}  | 
|
ServerSocket portSocket;  | 
|
InetAddress myAddress;  | 
|
String portCmd;  | 
|
if (proxy != null && proxy.type() == Proxy.Type.SOCKS) {  | 
|
// We're behind a firewall and the passive mode fail,  | 
|
// since we can't accept a connection through SOCKS (yet)  | 
|
            // throw an exception | 
|
throw new sun.net.ftp.FtpProtocolException("Passive mode failed");  | 
|
}  | 
|
// Bind the ServerSocket to the same address as the control channel  | 
|
        // This is needed for multi-homed systems | 
|
portSocket = new ServerSocket(0, 1, server.getLocalAddress());  | 
|
        try { | 
|
myAddress = portSocket.getInetAddress();  | 
|
if (myAddress.isAnyLocalAddress()) {  | 
|
myAddress = server.getLocalAddress();  | 
|
}  | 
|
// Let's try the new, IPv6 compatible EPRT command  | 
|
// See RFC2428 for specifics  | 
|
// Some FTP servers (like the one on Solaris) are bugged, they  | 
|
// will accept the EPRT command but then, the subsequent command  | 
|
// (e.g. RETR) will fail, so we have to check BOTH results (the  | 
|
// EPRT cmd then the actual command) to decide whether we should  | 
|
            // fall back on the older PORT command. | 
|
portCmd = "EPRT |" + ((myAddress instanceof Inet6Address) ? "2" : "1") + "|" +  | 
|
myAddress.getHostAddress() + "|" + portSocket.getLocalPort() + "|";  | 
|
if (!issueCommand(portCmd) || !issueCommand(cmd)) {  | 
|
                // The EPRT command failed, let's fall back to good old PORT | 
|
portCmd = "PORT ";  | 
|
byte[] addr = myAddress.getAddress();  | 
|
                /* append host addr */ | 
|
for (int i = 0; i < addr.length; i++) {  | 
|
portCmd = portCmd + (addr[i] & 0xFF) + ",";  | 
|
}  | 
|
                /* append port number */ | 
|
portCmd = portCmd + ((portSocket.getLocalPort() >>> 8) & 0xff) + "," + (portSocket.getLocalPort() & 0xff);  | 
|
issueCommandCheck(portCmd);  | 
|
issueCommandCheck(cmd);  | 
|
}  | 
|
// Either the EPRT or the PORT command was successful  | 
|
            // Let's create the client socket | 
|
if (connectTimeout >= 0) {  | 
|
portSocket.setSoTimeout(connectTimeout);  | 
|
            } else { | 
|
if (defaultConnectTimeout > 0) {  | 
|
portSocket.setSoTimeout(defaultConnectTimeout);  | 
|
}  | 
|
}  | 
|
clientSocket = portSocket.accept();  | 
|
if (readTimeout >= 0) {  | 
|
clientSocket.setSoTimeout(readTimeout);  | 
|
            } else { | 
|
if (defaultSoTimeout > 0) {  | 
|
clientSocket.setSoTimeout(defaultSoTimeout);  | 
|
}  | 
|
}  | 
|
        } finally { | 
|
portSocket.close();  | 
|
}  | 
|
if (useCrypto) {  | 
|
            try { | 
|
clientSocket = sslFact.createSocket(clientSocket, serverAddr.getHostName(), serverAddr.getPort(), true);  | 
|
} catch (Exception ex) {  | 
|
throw new IOException(ex.getLocalizedMessage());  | 
|
}  | 
|
}  | 
|
return clientSocket;  | 
|
}  | 
|
private InputStream createInputStream(InputStream in) {  | 
|
if (type == TransferType.ASCII) {  | 
|
return new sun.net.TelnetInputStream(in, false);  | 
|
}  | 
|
return in;  | 
|
}  | 
|
private OutputStream createOutputStream(OutputStream out) {  | 
|
if (type == TransferType.ASCII) {  | 
|
return new sun.net.TelnetOutputStream(out, false);  | 
|
}  | 
|
return out;  | 
|
}  | 
|
    /** | 
|
     * Creates an instance of FtpClient. The client is not connected to any | 
|
     * server yet. | 
|
     * | 
|
*/  | 
|
    protected FtpClient() { | 
|
}  | 
|
    /** | 
|
     * Creates an instance of FtpClient. The client is not connected to any | 
|
     * server yet. | 
|
     * | 
|
*/  | 
|
public static sun.net.ftp.FtpClient create() {  | 
|
return new FtpClient();  | 
|
}  | 
|
    /** | 
|
     * Set the transfer mode to <I>passive</I>. In that mode, data connections | 
|
     * are established by having the client connect to the server. | 
|
     * This is the recommended default mode as it will work best through | 
|
     * firewalls and NATs. | 
|
     * | 
|
     * @return This FtpClient | 
|
     * @see #setActiveMode() | 
|
*/  | 
|
public sun.net.ftp.FtpClient enablePassiveMode(boolean passive) {  | 
|
// Only passive mode used in JDK. See Bug 8010784.  | 
|
        // passiveMode = passive; | 
|
return this;  | 
|
}  | 
|
    /** | 
|
     * Gets the current transfer mode. | 
|
     * | 
|
     * @return the current <code>FtpTransferMode</code> | 
|
*/  | 
|
    public boolean isPassiveModeEnabled() { | 
|
return passiveMode;  | 
|
}  | 
|
    /** | 
|
     * Sets the timeout value to use when connecting to the server, | 
|
     * | 
|
     * @param timeout the timeout value, in milliseconds, to use for the connect | 
|
     *        operation. A value of zero or less, means use the default timeout. | 
|
     * | 
|
     * @return This FtpClient | 
|
*/  | 
|
public sun.net.ftp.FtpClient setConnectTimeout(int timeout) {  | 
|
connectTimeout = timeout;  | 
|
return this;  | 
|
}  | 
|
    /** | 
|
     * Returns the current connection timeout value. | 
|
     * | 
|
     * @return the value, in milliseconds, of the current connect timeout. | 
|
     * @see #setConnectTimeout(int) | 
|
*/  | 
|
    public int getConnectTimeout() { | 
|
return connectTimeout;  | 
|
}  | 
|
    /** | 
|
     * Sets the timeout value to use when reading from the server, | 
|
     * | 
|
     * @param timeout the timeout value, in milliseconds, to use for the read | 
|
     *        operation. A value of zero or less, means use the default timeout. | 
|
     * @return This FtpClient | 
|
*/  | 
|
public sun.net.ftp.FtpClient setReadTimeout(int timeout) {  | 
|
readTimeout = timeout;  | 
|
return this;  | 
|
}  | 
|
    /** | 
|
     * Returns the current read timeout value. | 
|
     * | 
|
     * @return the value, in milliseconds, of the current read timeout. | 
|
     * @see #setReadTimeout(int) | 
|
*/  | 
|
    public int getReadTimeout() { | 
|
return readTimeout;  | 
|
}  | 
|
public sun.net.ftp.FtpClient setProxy(Proxy p) {  | 
|
proxy = p;  | 
|
return this;  | 
|
}  | 
|
    /** | 
|
     * Get the proxy of this FtpClient | 
|
     * | 
|
     * @return the <code>Proxy</code>, this client is using, or <code>null</code> | 
|
     *         if none is used. | 
|
     * @see #setProxy(Proxy) | 
|
*/  | 
|
public Proxy getProxy() {  | 
|
return proxy;  | 
|
}  | 
|
    /** | 
|
     * Connects to the specified destination. | 
|
     * | 
|
     * @param dest the <code>InetSocketAddress</code> to connect to. | 
|
     * @throws IOException if the connection fails. | 
|
*/  | 
|
private void tryConnect(InetSocketAddress dest, int timeout) throws IOException {  | 
|
if (isConnected()) {  | 
|
disconnect();  | 
|
}  | 
|
server = doConnect(dest, timeout);  | 
|
        try { | 
|
out = new PrintStream(new BufferedOutputStream(server.getOutputStream()),  | 
|
true, encoding);  | 
|
} catch (UnsupportedEncodingException e) {  | 
|
throw new InternalError(encoding + "encoding not found", e);  | 
|
}  | 
|
in = new BufferedInputStream(server.getInputStream());  | 
|
}  | 
|
private Socket doConnect(InetSocketAddress dest, int timeout) throws IOException {  | 
|
Socket s;  | 
|
if (proxy != null) {  | 
|
if (proxy.type() == Proxy.Type.SOCKS) {  | 
|
s = AccessController.doPrivileged(  | 
|
new PrivilegedAction<Socket>() {  | 
|
public Socket run() {  | 
|
return new Socket(proxy);  | 
|
}  | 
|
});  | 
|
            } else { | 
|
s = new Socket(Proxy.NO_PROXY);  | 
|
}  | 
|
        } else { | 
|
s = new Socket();  | 
|
}  | 
|
// Instance specific timeouts do have priority, that means  | 
|
// connectTimeout & readTimeout (-1 means not set)  | 
|
// Then global default timeouts  | 
|
        // Then no timeout. | 
|
if (timeout >= 0) {  | 
|
s.connect(dest, timeout);  | 
|
        } else { | 
|
if (connectTimeout >= 0) {  | 
|
s.connect(dest, connectTimeout);  | 
|
            } else { | 
|
if (defaultConnectTimeout > 0) {  | 
|
s.connect(dest, defaultConnectTimeout);  | 
|
                } else { | 
|
s.connect(dest);  | 
|
}  | 
|
}  | 
|
}  | 
|
if (readTimeout >= 0) {  | 
|
s.setSoTimeout(readTimeout);  | 
|
} else if (defaultSoTimeout > 0) {  | 
|
s.setSoTimeout(defaultSoTimeout);  | 
|
}  | 
|
return s;  | 
|
}  | 
|
private void disconnect() throws IOException {  | 
|
if (isConnected()) {  | 
|
server.close();  | 
|
}  | 
|
server = null;  | 
|
in = null;  | 
|
out = null;  | 
|
lastTransSize = -1;  | 
|
lastFileName = null;  | 
|
restartOffset = 0;  | 
|
welcomeMsg = null;  | 
|
lastReplyCode = null;  | 
|
serverResponse.setSize(0);  | 
|
}  | 
|
    /** | 
|
     * Tests whether this client is connected or not to a server. | 
|
     * | 
|
     * @return <code>true</code> if the client is connected. | 
|
*/  | 
|
    public boolean isConnected() { | 
|
return server != null;  | 
|
}  | 
|
public SocketAddress getServerAddress() {  | 
|
return server == null ? null : server.getRemoteSocketAddress();  | 
|
}  | 
|
public sun.net.ftp.FtpClient connect(SocketAddress dest) throws sun.net.ftp.FtpProtocolException, IOException {  | 
|
return connect(dest, -1);  | 
|
}  | 
|
    /** | 
|
     * Connects the FtpClient to the specified destination. | 
|
     * | 
|
     * @param dest the address of the destination server | 
|
     * @throws IOException if connection failed. | 
|
*/  | 
|
public sun.net.ftp.FtpClient connect(SocketAddress dest, int timeout) throws sun.net.ftp.FtpProtocolException, IOException {  | 
|
if (!(dest instanceof InetSocketAddress)) {  | 
|
throw new IllegalArgumentException("Wrong address type");  | 
|
}  | 
|
serverAddr = (InetSocketAddress) dest;  | 
|
tryConnect(serverAddr, timeout);  | 
|
if (!readReply()) {  | 
|
throw new sun.net.ftp.FtpProtocolException("Welcome message: " +  | 
|
getResponseString(), lastReplyCode);  | 
|
}  | 
|
welcomeMsg = getResponseString().substring(4);  | 
|
return this;  | 
|
}  | 
|
private void tryLogin(String user, char[] password) throws sun.net.ftp.FtpProtocolException, IOException {  | 
|
issueCommandCheck("USER " + user);  | 
|
        /* | 
|
         * Checks for "331 User name okay, need password." answer | 
|
*/  | 
|
if (lastReplyCode == FtpReplyCode.NEED_PASSWORD) {  | 
|
if ((password != null) && (password.length > 0)) {  | 
|
issueCommandCheck("PASS " + String.valueOf(password));  | 
|
}  | 
|
}  | 
|
}  | 
|
    /** | 
|
     * Attempts to log on the server with the specified user name and password. | 
|
     * | 
|
     * @param user The user name | 
|
     * @param password The password for that user | 
|
     * @return <code>true</code> if the login was successful. | 
|
     * @throws IOException if an error occurred during the transmission | 
|
*/  | 
|
public sun.net.ftp.FtpClient login(String user, char[] password) throws sun.net.ftp.FtpProtocolException, IOException {  | 
|
if (!isConnected()) {  | 
|
throw new sun.net.ftp.FtpProtocolException("Not connected yet", FtpReplyCode.BAD_SEQUENCE);  | 
|
}  | 
|
if (user == null || user.length() == 0) {  | 
|
throw new IllegalArgumentException("User name can't be null or empty");  | 
|
}  | 
|
tryLogin(user, password);  | 
|
// keep the welcome message around so we can  | 
|
        // put it in the resulting HTML page. | 
|
String l;  | 
|
StringBuffer sb = new StringBuffer();  | 
|
for (int i = 0; i < serverResponse.size(); i++) {  | 
|
l = serverResponse.elementAt(i);  | 
|
if (l != null) {  | 
|
if (l.length() >= 4 && l.startsWith("230")) {  | 
|
                    // get rid of the "230-" prefix | 
|
l = l.substring(4);  | 
|
}  | 
|
sb.append(l);  | 
|
}  | 
|
}  | 
|
welcomeMsg = sb.toString();  | 
|
loggedIn = true;  | 
|
return this;  | 
|
}  | 
|
    /** | 
|
     * Attempts to log on the server with the specified user name, password and | 
|
     * account name. | 
|
     * | 
|
     * @param user The user name | 
|
     * @param password The password for that user. | 
|
     * @param account The account name for that user. | 
|
     * @return <code>true</code> if the login was successful. | 
|
     * @throws IOException if an error occurs during the transmission. | 
|
*/  | 
|
public sun.net.ftp.FtpClient login(String user, char[] password, String account) throws sun.net.ftp.FtpProtocolException, IOException {  | 
|
if (!isConnected()) {  | 
|
throw new sun.net.ftp.FtpProtocolException("Not connected yet", FtpReplyCode.BAD_SEQUENCE);  | 
|
}  | 
|
if (user == null || user.length() == 0) {  | 
|
throw new IllegalArgumentException("User name can't be null or empty");  | 
|
}  | 
|
tryLogin(user, password);  | 
|
        /* | 
|
         * Checks for "332 Need account for login." answer | 
|
*/  | 
|
if (lastReplyCode == FtpReplyCode.NEED_ACCOUNT) {  | 
|
issueCommandCheck("ACCT " + account);  | 
|
}  | 
|
// keep the welcome message around so we can  | 
|
        // put it in the resulting HTML page. | 
|
StringBuffer sb = new StringBuffer();  | 
|
if (serverResponse != null) {  | 
|
for (String l : serverResponse) {  | 
|
if (l != null) {  | 
|
if (l.length() >= 4 && l.startsWith("230")) {  | 
|
                        // get rid of the "230-" prefix | 
|
l = l.substring(4);  | 
|
}  | 
|
sb.append(l);  | 
|
}  | 
|
}  | 
|
}  | 
|
welcomeMsg = sb.toString();  | 
|
loggedIn = true;  | 
|
return this;  | 
|
}  | 
|
    /** | 
|
     * Logs out the current user. This is in effect terminates the current | 
|
     * session and the connection to the server will be closed. | 
|
     * | 
|
*/  | 
|
public void close() throws IOException {  | 
|
if (isConnected()) {  | 
|
            try { | 
|
issueCommand("QUIT");  | 
|
} catch (FtpProtocolException e) {  | 
|
}  | 
|
loggedIn = false;  | 
|
}  | 
|
disconnect();  | 
|
}  | 
|
    /** | 
|
     * Checks whether the client is logged in to the server or not. | 
|
     * | 
|
     * @return <code>true</code> if the client has already completed a login. | 
|
*/  | 
|
    public boolean isLoggedIn() { | 
|
return loggedIn;  | 
|
}  | 
|
    /** | 
|
     * Changes to a specific directory on a remote FTP server | 
|
     * | 
|
     * @param remoteDirectory path of the directory to CD to. | 
|
     * @return <code>true</code> if the operation was successful. | 
|
     * @exception <code>FtpProtocolException</code> | 
|
*/  | 
|
public sun.net.ftp.FtpClient changeDirectory(String remoteDirectory) throws sun.net.ftp.FtpProtocolException, IOException {  | 
|
if (remoteDirectory == null || "".equals(remoteDirectory)) {  | 
|
throw new IllegalArgumentException("directory can't be null or empty");  | 
|
}  | 
|
issueCommandCheck("CWD " + remoteDirectory);  | 
|
return this;  | 
|
}  | 
|
    /** | 
|
     * Changes to the parent directory, sending the CDUP command to the server. | 
|
     * | 
|
     * @return <code>true</code> if the command was successful. | 
|
     * @throws IOException | 
|
*/  | 
|
public sun.net.ftp.FtpClient changeToParentDirectory() throws sun.net.ftp.FtpProtocolException, IOException {  | 
|
issueCommandCheck("CDUP");  | 
|
return this;  | 
|
}  | 
|
    /** | 
|
     * Returns the server current working directory, or <code>null</code> if | 
|
     * the PWD command failed. | 
|
     * | 
|
     * @return a <code>String</code> containing the current working directory, | 
|
     *         or <code>null</code> | 
|
     * @throws IOException | 
|
*/  | 
|
public String getWorkingDirectory() throws sun.net.ftp.FtpProtocolException, IOException {  | 
|
issueCommandCheck("PWD");  | 
|
        /* | 
|
         * answer will be of the following format : | 
|
         * | 
|
         * 257 "/" is current directory. | 
|
*/  | 
|
String answ = getResponseString();  | 
|
if (!answ.startsWith("257")) {  | 
|
return null;  | 
|
}  | 
|
return answ.substring(5, answ.lastIndexOf('"'));  | 
|
}  | 
|
    /** | 
|
     * Sets the restart offset to the specified value.  That value will be | 
|
     * sent through a <code>REST</code> command to server before a file | 
|
     * transfer and has the effect of resuming a file transfer from the | 
|
     * specified point. After a transfer the restart offset is set back to | 
|
     * zero. | 
|
     * | 
|
     * @param offset the offset in the remote file at which to start the next | 
|
     *        transfer. This must be a value greater than or equal to zero. | 
|
     * @throws IllegalArgumentException if the offset is negative. | 
|
*/  | 
|
public sun.net.ftp.FtpClient setRestartOffset(long offset) {  | 
|
if (offset < 0) {  | 
|
throw new IllegalArgumentException("offset can't be negative");  | 
|
}  | 
|
restartOffset = offset;  | 
|
return this;  | 
|
}  | 
|
    /** | 
|
     * Retrieves a file from the ftp server and writes it to the specified | 
|
     * <code>OutputStream</code>. | 
|
     * If the restart offset was set, then a <code>REST</code> command will be | 
|
     * sent before the RETR in order to restart the tranfer from the specified | 
|
     * offset. | 
|
     * The <code>OutputStream</code> is not closed by this method at the end | 
|
     * of the transfer. | 
|
     * | 
|
     * @param name a <code>String<code> containing the name of the file to | 
|
     *        retreive from the server. | 
|
     * @param local the <code>OutputStream</code> the file should be written to. | 
|
     * @throws IOException if the transfer fails. | 
|
*/  | 
|
public sun.net.ftp.FtpClient getFile(String name, OutputStream local) throws sun.net.ftp.FtpProtocolException, IOException {  | 
|
int mtu = 1500;  | 
|
if (restartOffset > 0) {  | 
|
Socket s;  | 
|
            try { | 
|
s = openDataConnection("REST " + restartOffset);  | 
|
            } finally { | 
|
restartOffset = 0;  | 
|
}  | 
|
issueCommandCheck("RETR " + name);  | 
|
getTransferSize();  | 
|
InputStream remote = createInputStream(s.getInputStream());  | 
|
byte[] buf = new byte[mtu * 10];  | 
|
int l;  | 
|
while ((l = remote.read(buf)) >= 0) {  | 
|
if (l > 0) {  | 
|
local.write(buf, 0, l);  | 
|
}  | 
|
}  | 
|
remote.close();  | 
|
        } else { | 
|
Socket s = openDataConnection("RETR " + name);  | 
|
getTransferSize();  | 
|
InputStream remote = createInputStream(s.getInputStream());  | 
|
byte[] buf = new byte[mtu * 10];  | 
|
int l;  | 
|
while ((l = remote.read(buf)) >= 0) {  | 
|
if (l > 0) {  | 
|
local.write(buf, 0, l);  | 
|
}  | 
|
}  | 
|
remote.close();  | 
|
}  | 
|
return completePending();  | 
|
}  | 
|
    /** | 
|
     * Retrieves a file from the ftp server, using the RETR command, and | 
|
     * returns the InputStream from* the established data connection. | 
|
     * {@link #completePending()} <b>has</b> to be called once the application | 
|
     * is done reading from the returned stream. | 
|
     * | 
|
     * @param name the name of the remote file | 
|
     * @return the {@link java.io.InputStream} from the data connection, or | 
|
     *         <code>null</code> if the command was unsuccessful. | 
|
     * @throws IOException if an error occurred during the transmission. | 
|
*/  | 
|
public InputStream getFileStream(String name) throws sun.net.ftp.FtpProtocolException, IOException {  | 
|
Socket s;  | 
|
if (restartOffset > 0) {  | 
|
            try { | 
|
s = openDataConnection("REST " + restartOffset);  | 
|
            } finally { | 
|
restartOffset = 0;  | 
|
}  | 
|
if (s == null) {  | 
|
return null;  | 
|
}  | 
|
issueCommandCheck("RETR " + name);  | 
|
getTransferSize();  | 
|
return createInputStream(s.getInputStream());  | 
|
}  | 
|
s = openDataConnection("RETR " + name);  | 
|
if (s == null) {  | 
|
return null;  | 
|
}  | 
|
getTransferSize();  | 
|
return createInputStream(s.getInputStream());  | 
|
}  | 
|
    /** | 
|
     * Transfers a file from the client to the server (aka a <I>put</I>) | 
|
     * by sending the STOR or STOU command, depending on the | 
|
     * <code>unique</code> argument, and returns the <code>OutputStream</code> | 
|
     * from the established data connection. | 
|
     * {@link #completePending()} <b>has</b> to be called once the application | 
|
     * is finished writing to the stream. | 
|
     * | 
|
     * A new file is created at the server site if the file specified does | 
|
     * not already exist. | 
|
     * | 
|
     * If <code>unique</code> is set to <code>true</code>, the resultant file | 
|
     * is to be created under a name unique to that directory, meaning | 
|
     * it will not overwrite an existing file, instead the server will | 
|
     * generate a new, unique, file name. | 
|
     * The name of the remote file can be retrieved, after completion of the | 
|
     * transfer, by calling {@link #getLastFileName()}. | 
|
     * | 
|
     * @param name the name of the remote file to write. | 
|
     * @param unique <code>true</code> if the remote files should be unique, | 
|
     *        in which case the STOU command will be used. | 
|
     * @return the {@link java.io.OutputStream} from the data connection or | 
|
     *         <code>null</code> if the command was unsuccessful. | 
|
     * @throws IOException if an error occurred during the transmission. | 
|
*/  | 
|
public OutputStream putFileStream(String name, boolean unique)  | 
|
throws sun.net.ftp.FtpProtocolException, IOException  | 
|
    { | 
|
String cmd = unique ? "STOU " : "STOR ";  | 
|
Socket s = openDataConnection(cmd + name);  | 
|
if (s == null) {  | 
|
return null;  | 
|
}  | 
|
boolean bm = (type == TransferType.BINARY);  | 
|
return new sun.net.TelnetOutputStream(s.getOutputStream(), bm);  | 
|
}  | 
|
    /** | 
|
     * Transfers a file from the client to the server (aka a <I>put</I>) | 
|
     * by sending the STOR command. The content of the <code>InputStream</code> | 
|
     * passed in argument is written into the remote file, overwriting any | 
|
     * existing data. | 
|
     * | 
|
     * A new file is created at the server site if the file specified does | 
|
     * not already exist. | 
|
     * | 
|
     * @param name the name of the remote file to write. | 
|
     * @param local the <code>InputStream</code> that points to the data to | 
|
     *        transfer. | 
|
     * @param unique <code>true</code> if the remote file should be unique | 
|
     *        (i.e. not already existing), <code>false</code> otherwise. | 
|
     * @return <code>true</code> if the transfer was successful. | 
|
     * @throws IOException if an error occurred during the transmission. | 
|
     * @see #getLastFileName() | 
|
*/  | 
|
public sun.net.ftp.FtpClient putFile(String name, InputStream local, boolean unique) throws sun.net.ftp.FtpProtocolException, IOException {  | 
|
String cmd = unique ? "STOU " : "STOR ";  | 
|
int mtu = 1500;  | 
|
if (type == TransferType.BINARY) {  | 
|
Socket s = openDataConnection(cmd + name);  | 
|
OutputStream remote = createOutputStream(s.getOutputStream());  | 
|
byte[] buf = new byte[mtu * 10];  | 
|
int l;  | 
|
while ((l = local.read(buf)) >= 0) {  | 
|
if (l > 0) {  | 
|
remote.write(buf, 0, l);  | 
|
}  | 
|
}  | 
|
remote.close();  | 
|
}  | 
|
return completePending();  | 
|
}  | 
|
    /** | 
|
     * Sends the APPE command to the server in order to transfer a data stream | 
|
     * passed in argument and append it to the content of the specified remote | 
|
     * file. | 
|
     * | 
|
     * @param name A <code>String</code> containing the name of the remote file | 
|
     *        to append to. | 
|
     * @param local The <code>InputStream</code> providing access to the data | 
|
     *        to be appended. | 
|
     * @return <code>true</code> if the transfer was successful. | 
|
     * @throws IOException if an error occurred during the transmission. | 
|
*/  | 
|
public sun.net.ftp.FtpClient appendFile(String name, InputStream local) throws sun.net.ftp.FtpProtocolException, IOException {  | 
|
int mtu = 1500;  | 
|
Socket s = openDataConnection("APPE " + name);  | 
|
OutputStream remote = createOutputStream(s.getOutputStream());  | 
|
byte[] buf = new byte[mtu * 10];  | 
|
int l;  | 
|
while ((l = local.read(buf)) >= 0) {  | 
|
if (l > 0) {  | 
|
remote.write(buf, 0, l);  | 
|
}  | 
|
}  | 
|
remote.close();  | 
|
return completePending();  | 
|
}  | 
|
    /** | 
|
     * Renames a file on the server. | 
|
     * | 
|
     * @param from the name of the file being renamed | 
|
     * @param to the new name for the file | 
|
     * @throws IOException if the command fails | 
|
*/  | 
|
public sun.net.ftp.FtpClient rename(String from, String to) throws sun.net.ftp.FtpProtocolException, IOException {  | 
|
issueCommandCheck("RNFR " + from);  | 
|
issueCommandCheck("RNTO " + to);  | 
|
return this;  | 
|
}  | 
|
    /** | 
|
     * Deletes a file on the server. | 
|
     * | 
|
     * @param name a <code>String</code> containing the name of the file | 
|
     *        to delete. | 
|
     * @return <code>true</code> if the command was successful | 
|
     * @throws IOException if an error occurred during the exchange | 
|
*/  | 
|
public sun.net.ftp.FtpClient deleteFile(String name) throws sun.net.ftp.FtpProtocolException, IOException {  | 
|
issueCommandCheck("DELE " + name);  | 
|
return this;  | 
|
}  | 
|
    /** | 
|
     * Creates a new directory on the server. | 
|
     * | 
|
     * @param name a <code>String</code> containing the name of the directory | 
|
     *        to create. | 
|
     * @return <code>true</code> if the operation was successful. | 
|
     * @throws IOException if an error occurred during the exchange | 
|
*/  | 
|
public sun.net.ftp.FtpClient makeDirectory(String name) throws sun.net.ftp.FtpProtocolException, IOException {  | 
|
issueCommandCheck("MKD " + name);  | 
|
return this;  | 
|
}  | 
|
    /** | 
|
     * Removes a directory on the server. | 
|
     * | 
|
     * @param name a <code>String</code> containing the name of the directory | 
|
     *        to remove. | 
|
     * | 
|
     * @return <code>true</code> if the operation was successful. | 
|
     * @throws IOException if an error occurred during the exchange. | 
|
*/  | 
|
public sun.net.ftp.FtpClient removeDirectory(String name) throws sun.net.ftp.FtpProtocolException, IOException {  | 
|
issueCommandCheck("RMD " + name);  | 
|
return this;  | 
|
}  | 
|
    /** | 
|
     * Sends a No-operation command. It's useful for testing the connection | 
|
     * status or as a <I>keep alive</I> mechanism. | 
|
     * | 
|
     * @throws FtpProtocolException if the command fails | 
|
*/  | 
|
public sun.net.ftp.FtpClient noop() throws sun.net.ftp.FtpProtocolException, IOException {  | 
|
issueCommandCheck("NOOP");  | 
|
return this;  | 
|
}  | 
|
    /** | 
|
     * Sends the STAT command to the server. | 
|
     * This can be used while a data connection is open to get a status | 
|
     * on the current transfer, in that case the parameter should be | 
|
     * <code>null</code>. | 
|
     * If used between file transfers, it may have a pathname as argument | 
|
     * in which case it will work as the LIST command except no data | 
|
     * connection will be created. | 
|
     * | 
|
     * @param name an optional <code>String</code> containing the pathname | 
|
     *        the STAT command should apply to. | 
|
     * @return the response from the server or <code>null</code> if the | 
|
     *         command failed. | 
|
     * @throws IOException if an error occurred during the transmission. | 
|
*/  | 
|
public String getStatus(String name) throws sun.net.ftp.FtpProtocolException, IOException {  | 
|
issueCommandCheck((name == null ? "STAT" : "STAT " + name));  | 
|
        /* | 
|
         * A typical response will be: | 
|
         *  213-status of t32.gif: | 
|
         * -rw-r--r--   1 jcc      staff     247445 Feb 17  1998 t32.gif | 
|
         * 213 End of Status | 
|
         * | 
|
         * or | 
|
         * | 
|
         * 211-jsn FTP server status: | 
|
         *     Version wu-2.6.2+Sun | 
|
         *     Connected to localhost (::1) | 
|
         *     Logged in as jccollet | 
|
         *     TYPE: ASCII, FORM: Nonprint; STRUcture: File; transfer MODE: Stream | 
|
         *      No data connection | 
|
         *     0 data bytes received in 0 files | 
|
         *     0 data bytes transmitted in 0 files | 
|
         *     0 data bytes total in 0 files | 
|
         *     53 traffic bytes received in 0 transfers | 
|
         *     485 traffic bytes transmitted in 0 transfers | 
|
         *     587 traffic bytes total in 0 transfers | 
|
         * 211 End of status | 
|
         * | 
|
         * So we need to remove the 1st and last line | 
|
*/  | 
|
Vector<String> resp = getResponseStrings();  | 
|
StringBuffer sb = new StringBuffer();  | 
|
for (int i = 1; i < resp.size() - 1; i++) {  | 
|
sb.append(resp.get(i));  | 
|
}  | 
|
return sb.toString();  | 
|
}  | 
|
    /** | 
|
     * Sends the FEAT command to the server and returns the list of supported | 
|
     * features in the form of strings. | 
|
     * | 
|
     * The features are the supported commands, like AUTH TLS, PROT or PASV. | 
|
     * See the RFCs for a complete list. | 
|
     * | 
|
     * Note that not all FTP servers support that command, in which case | 
|
     * the method will return <code>null</code> | 
|
     * | 
|
     * @return a <code>List</code> of <code>Strings</code> describing the | 
|
     *         supported additional features, or <code>null</code> | 
|
     *         if the command is not supported. | 
|
     * @throws IOException if an error occurs during the transmission. | 
|
*/  | 
|
public List<String> getFeatures() throws sun.net.ftp.FtpProtocolException, IOException {  | 
|
        /* | 
|
         * The FEAT command, when implemented will return something like: | 
|
         * | 
|
         * 211-Features: | 
|
         *   AUTH TLS | 
|
         *   PBSZ | 
|
         *   PROT | 
|
         *   EPSV | 
|
         *   EPRT | 
|
         *   PASV | 
|
         *   REST STREAM | 
|
         *  211 END | 
|
*/  | 
|
ArrayList<String> features = new ArrayList<String>();  | 
|
issueCommandCheck("FEAT");  | 
|
Vector<String> resp = getResponseStrings();  | 
|
// Note that we start at index 1 to skip the 1st line (211-...)  | 
|
        // and we stop before the last line. | 
|
for (int i = 1; i < resp.size() - 1; i++) {  | 
|
String s = resp.get(i);  | 
|
            // Get rid of leading space and trailing newline | 
|
features.add(s.substring(1, s.length() - 1));  | 
|
}  | 
|
return features;  | 
|
}  | 
|
    /** | 
|
     * sends the ABOR command to the server. | 
|
     * It tells the server to stop the previous command or transfer. | 
|
     * | 
|
     * @return <code>true</code> if the command was successful. | 
|
     * @throws IOException if an error occurred during the transmission. | 
|
*/  | 
|
public sun.net.ftp.FtpClient abort() throws sun.net.ftp.FtpProtocolException, IOException {  | 
|
issueCommandCheck("ABOR");  | 
|
// TODO: Must check the ReplyCode:  | 
|
/*  | 
|
* From the RFC:  | 
|
* There are two cases for the server upon receipt of this  | 
|
* command: (1) the FTP service command was already completed,  | 
|
* or (2) the FTP service command is still in progress.  | 
|
* In the first case, the server closes the data connection  | 
|
* (if it is open) and responds with a 226 reply, indicating  | 
|
* that the abort command was successfully processed.  | 
|
* In the second case, the server aborts the FTP service in  | 
|
* progress and closes the data connection, returning a 426  | 
|
* reply to indicate that the service request terminated  | 
|
* abnormally. The server then sends a 226 reply,  | 
|
* indicating that the abort command was successfully  | 
|
* processed.  | 
|
*/  | 
|
return this;  | 
|
}  | 
|
    /** | 
|
     * Some methods do not wait until completion before returning, so this | 
|
     * method can be called to wait until completion. This is typically the case | 
|
     * with commands that trigger a transfer like {@link #getFileStream(String)}. | 
|
     * So this method should be called before accessing information related to | 
|
     * such a command. | 
|
     * <p>This method will actually block reading on the command channel for a | 
|
     * notification from the server that the command is finished. Such a | 
|
     * notification often carries extra information concerning the completion | 
|
     * of the pending action (e.g. number of bytes transfered).</p> | 
|
     * <p>Note that this will return true immediately if no command or action | 
|
     * is pending</p> | 
|
     * <p>It should be also noted that most methods issuing commands to the ftp | 
|
     * server will call this method if a previous command is pending. | 
|
     * <p>Example of use: | 
|
     * <pre> | 
|
     * InputStream in = cl.getFileStream("file"); | 
|
     * ... | 
|
     * cl.completePending(); | 
|
     * long size = cl.getLastTransferSize(); | 
|
     * </pre> | 
|
     * On the other hand, it's not necessary in a case like: | 
|
     * <pre> | 
|
     * InputStream in = cl.getFileStream("file"); | 
|
     * // read content | 
|
     * ... | 
|
     * cl.logout(); | 
|
     * </pre> | 
|
     * <p>Since {@link #logout()} will call completePending() if necessary.</p> | 
|
     * @return <code>true</code> if the completion was successful or if no | 
|
     *         action was pending. | 
|
     * @throws IOException | 
|
*/  | 
|
public sun.net.ftp.FtpClient completePending() throws sun.net.ftp.FtpProtocolException, IOException {  | 
|
while (replyPending) {  | 
|
replyPending = false;  | 
|
if (!readReply()) {  | 
|
throw new sun.net.ftp.FtpProtocolException(getLastResponseString(), lastReplyCode);  | 
|
}  | 
|
}  | 
|
return this;  | 
|
}  | 
|
    /** | 
|
     * Reinitializes the USER parameters on the FTP server | 
|
     * | 
|
     * @throws FtpProtocolException if the command fails | 
|
*/  | 
|
public sun.net.ftp.FtpClient reInit() throws sun.net.ftp.FtpProtocolException, IOException {  | 
|
issueCommandCheck("REIN");  | 
|
loggedIn = false;  | 
|
if (useCrypto) {  | 
|
if (server instanceof SSLSocket) {  | 
|
javax.net.ssl.SSLSession session = ((SSLSocket) server).getSession();  | 
|
session.invalidate();  | 
|
                // Restore previous socket and streams | 
|
server = oldSocket;  | 
|
oldSocket = null;  | 
|
                try { | 
|
out = new PrintStream(new BufferedOutputStream(server.getOutputStream()),  | 
|
true, encoding);  | 
|
} catch (UnsupportedEncodingException e) {  | 
|
throw new InternalError(encoding + "encoding not found", e);  | 
|
}  | 
|
in = new BufferedInputStream(server.getInputStream());  | 
|
}  | 
|
}  | 
|
useCrypto = false;  | 
|
return this;  | 
|
}  | 
|
    /** | 
|
     * Changes the transfer type (binary, ascii, ebcdic) and issue the | 
|
     * proper command (e.g. TYPE A) to the server. | 
|
     * | 
|
     * @param type the <code>FtpTransferType</code> to use. | 
|
     * @return This FtpClient | 
|
     * @throws IOException if an error occurs during transmission. | 
|
*/  | 
|
public sun.net.ftp.FtpClient setType(TransferType type) throws sun.net.ftp.FtpProtocolException, IOException {  | 
|
String cmd = "NOOP";  | 
|
this.type = type;  | 
|
if (type == TransferType.ASCII) {  | 
|
cmd = "TYPE A";  | 
|
}  | 
|
if (type == TransferType.BINARY) {  | 
|
cmd = "TYPE I";  | 
|
}  | 
|
if (type == TransferType.EBCDIC) {  | 
|
cmd = "TYPE E";  | 
|
}  | 
|
issueCommandCheck(cmd);  | 
|
return this;  | 
|
}  | 
|
    /** | 
|
     * Issues a LIST command to the server to get the current directory | 
|
     * listing, and returns the InputStream from the data connection. | 
|
     * {@link #completePending()} <b>has</b> to be called once the application | 
|
     * is finished writing to the stream. | 
|
     * | 
|
     * @param path the pathname of the directory to list, or <code>null</code> | 
|
     *        for the current working directory. | 
|
     * @return the <code>InputStream</code> from the resulting data connection | 
|
     * @throws IOException if an error occurs during the transmission. | 
|
     * @see #changeDirectory(String) | 
|
     * @see #listFiles(String) | 
|
*/  | 
|
public InputStream list(String path) throws sun.net.ftp.FtpProtocolException, IOException {  | 
|
Socket s;  | 
|
s = openDataConnection(path == null ? "LIST" : "LIST " + path);  | 
|
if (s != null) {  | 
|
return createInputStream(s.getInputStream());  | 
|
}  | 
|
return null;  | 
|
}  | 
|
    /** | 
|
     * Issues a NLST path command to server to get the specified directory | 
|
     * content. It differs from {@link #list(String)} method by the fact that | 
|
     * it will only list the file names which would make the parsing of the | 
|
     * somewhat easier. | 
|
     * | 
|
     * {@link #completePending()} <b>has</b> to be called once the application | 
|
     * is finished writing to the stream. | 
|
     * | 
|
     * @param path a <code>String</code> containing the pathname of the | 
|
     *        directory to list or <code>null</code> for the current working | 
|
     *        directory. | 
|
     * @return the <code>InputStream</code> from the resulting data connection | 
|
     * @throws IOException if an error occurs during the transmission. | 
|
*/  | 
|
public InputStream nameList(String path) throws sun.net.ftp.FtpProtocolException, IOException {  | 
|
Socket s;  | 
|
s = openDataConnection(path == null ? "NLST" : "NLST " + path);  | 
|
if (s != null) {  | 
|
return createInputStream(s.getInputStream());  | 
|
}  | 
|
return null;  | 
|
}  | 
|
    /** | 
|
     * Issues the SIZE [path] command to the server to get the size of a | 
|
     * specific file on the server. | 
|
     * Note that this command may not be supported by the server. In which | 
|
     * case -1 will be returned. | 
|
     * | 
|
     * @param path a <code>String</code> containing the pathname of the | 
|
     *        file. | 
|
     * @return a <code>long</code> containing the size of the file or -1 if | 
|
     *         the server returned an error, which can be checked with | 
|
     *         {@link #getLastReplyCode()}. | 
|
     * @throws IOException if an error occurs during the transmission. | 
|
*/  | 
|
public long getSize(String path) throws sun.net.ftp.FtpProtocolException, IOException {  | 
|
if (path == null || path.length() == 0) {  | 
|
throw new IllegalArgumentException("path can't be null or empty");  | 
|
}  | 
|
issueCommandCheck("SIZE " + path);  | 
|
if (lastReplyCode == FtpReplyCode.FILE_STATUS) {  | 
|
String s = getResponseString();  | 
|
s = s.substring(4, s.length() - 1);  | 
|
return Long.parseLong(s);  | 
|
}  | 
|
return -1;  | 
|
}  | 
|
private static String[] MDTMformats = {  | 
|
        "yyyyMMddHHmmss.SSS", | 
|
        "yyyyMMddHHmmss" | 
|
};  | 
|
private static SimpleDateFormat[] dateFormats = new SimpleDateFormat[MDTMformats.length];  | 
|
    static { | 
|
for (int i = 0; i < MDTMformats.length; i++) {  | 
|
dateFormats[i] = new SimpleDateFormat(MDTMformats[i]);  | 
|
dateFormats[i].setTimeZone(TimeZone.getTimeZone("GMT"));  | 
|
}  | 
|
}  | 
|
    /** | 
|
     * Issues the MDTM [path] command to the server to get the modification | 
|
     * time of a specific file on the server. | 
|
     * Note that this command may not be supported by the server, in which | 
|
     * case <code>null</code> will be returned. | 
|
     * | 
|
     * @param path a <code>String</code> containing the pathname of the file. | 
|
     * @return a <code>Date</code> representing the last modification time | 
|
     *         or <code>null</code> if the server returned an error, which | 
|
     *         can be checked with {@link #getLastReplyCode()}. | 
|
     * @throws IOException if an error occurs during the transmission. | 
|
*/  | 
|
public Date getLastModified(String path) throws sun.net.ftp.FtpProtocolException, IOException {  | 
|
issueCommandCheck("MDTM " + path);  | 
|
if (lastReplyCode == FtpReplyCode.FILE_STATUS) {  | 
|
String s = getResponseString().substring(4);  | 
|
Date d = null;  | 
|
for (SimpleDateFormat dateFormat : dateFormats) {  | 
|
                try { | 
|
d = dateFormat.parse(s);  | 
|
} catch (ParseException ex) {  | 
|
}  | 
|
if (d != null) {  | 
|
return d;  | 
|
}  | 
|
}  | 
|
}  | 
|
return null;  | 
|
}  | 
|
    /** | 
|
     * Sets the parser used to handle the directory output to the specified | 
|
     * one. By default the parser is set to one that can handle most FTP | 
|
     * servers output (Unix base mostly). However it may be necessary for | 
|
     * and application to provide its own parser due to some uncommon | 
|
     * output format. | 
|
     * | 
|
     * @param p The <code>FtpDirParser</code> to use. | 
|
     * @see #listFiles(String) | 
|
*/  | 
|
public sun.net.ftp.FtpClient setDirParser(FtpDirParser p) {  | 
|
parser = p;  | 
|
return this;  | 
|
}  | 
|
private class FtpFileIterator implements Iterator<FtpDirEntry>, Closeable {  | 
|
private BufferedReader in = null;  | 
|
private FtpDirEntry nextFile = null;  | 
|
private FtpDirParser fparser = null;  | 
|
private boolean eof = false;  | 
|
public FtpFileIterator(FtpDirParser p, BufferedReader in) {  | 
|
this.in = in;  | 
|
this.fparser = p;  | 
|
readNext();  | 
|
}  | 
|
        private void readNext() { | 
|
nextFile = null;  | 
|
if (eof) {  | 
|
return;  | 
|
}  | 
|
String line = null;  | 
|
            try { | 
|
                do { | 
|
line = in.readLine();  | 
|
if (line != null) {  | 
|
nextFile = fparser.parseLine(line);  | 
|
if (nextFile != null) {  | 
|
return;  | 
|
}  | 
|
}  | 
|
} while (line != null);  | 
|
in.close();  | 
|
} catch (IOException iOException) {  | 
|
}  | 
|
eof = true;  | 
|
}  | 
|
        public boolean hasNext() { | 
|
return nextFile != null;  | 
|
}  | 
|
public FtpDirEntry next() {  | 
|
FtpDirEntry ret = nextFile;  | 
|
readNext();  | 
|
return ret;  | 
|
}  | 
|
        public void remove() { | 
|
throw new UnsupportedOperationException("Not supported yet.");  | 
|
}  | 
|
public void close() throws IOException {  | 
|
if (in != null && !eof) {  | 
|
in.close();  | 
|
}  | 
|
eof = true;  | 
|
nextFile = null;  | 
|
}  | 
|
}  | 
|
    /** | 
|
     * Issues a MLSD command to the server to get the specified directory | 
|
     * listing and applies the current parser to create an Iterator of | 
|
     * {@link java.net.ftp.FtpDirEntry}. Note that the Iterator returned is also a | 
|
     * {@link java.io.Closeable}. | 
|
     * If the server doesn't support the MLSD command, the LIST command is used | 
|
     * instead. | 
|
     * | 
|
     * {@link #completePending()} <b>has</b> to be called once the application | 
|
     * is finished iterating through the files. | 
|
     * | 
|
     * @param path the pathname of the directory to list or <code>null</code> | 
|
     *        for the current working directoty. | 
|
     * @return a <code>Iterator</code> of files or <code>null</code> if the | 
|
     *         command failed. | 
|
     * @throws IOException if an error occurred during the transmission | 
|
     * @see #setDirParser(FtpDirParser) | 
|
     * @see #changeDirectory(String) | 
|
*/  | 
|
public Iterator<FtpDirEntry> listFiles(String path) throws sun.net.ftp.FtpProtocolException, IOException {  | 
|
Socket s = null;  | 
|
BufferedReader sin = null;  | 
|
        try { | 
|
s = openDataConnection(path == null ? "MLSD" : "MLSD " + path);  | 
|
} catch (sun.net.ftp.FtpProtocolException FtpException) {  | 
|
// The server doesn't understand new MLSD command, ignore and fall  | 
|
// back to LIST  | 
|
}  | 
|
if (s != null) {  | 
|
sin = new BufferedReader(new InputStreamReader(s.getInputStream()));  | 
|
return new FtpFileIterator(mlsxParser, sin);  | 
|
        } else { | 
|
s = openDataConnection(path == null ? "LIST" : "LIST " + path);  | 
|
if (s != null) {  | 
|
sin = new BufferedReader(new InputStreamReader(s.getInputStream()));  | 
|
return new FtpFileIterator(parser, sin);  | 
|
}  | 
|
}  | 
|
return null;  | 
|
}  | 
|
private boolean sendSecurityData(byte[] buf) throws IOException,  | 
|
sun.net.ftp.FtpProtocolException {  | 
|
BASE64Encoder encoder = new BASE64Encoder();  | 
|
String s = encoder.encode(buf);  | 
|
return issueCommand("ADAT " + s);  | 
|
}  | 
|
    private byte[] getSecurityData() { | 
|
String s = getLastResponseString();  | 
|
if (s.substring(4, 9).equalsIgnoreCase("ADAT=")) {  | 
|
BASE64Decoder decoder = new BASE64Decoder();  | 
|
            try { | 
|
// Need to get rid of the leading '315 ADAT='  | 
|
                // and the trailing newline | 
|
return decoder.decodeBuffer(s.substring(9, s.length() - 1));  | 
|
} catch (IOException e) {  | 
|
//  | 
|
}  | 
|
}  | 
|
return null;  | 
|
}  | 
|
    /** | 
|
     * Attempts to use Kerberos GSSAPI as an authentication mechanism with the | 
|
     * ftp server. This will issue an <code>AUTH GSSAPI</code> command, and if | 
|
     * it is accepted by the server, will followup with <code>ADAT</code> | 
|
     * command to exchange the various tokens until authentification is | 
|
     * successful. This conforms to Appendix I of RFC 2228. | 
|
     * | 
|
     * @return <code>true</code> if authentication was successful. | 
|
     * @throws IOException if an error occurs during the transmission. | 
|
*/  | 
|
public sun.net.ftp.FtpClient useKerberos() throws sun.net.ftp.FtpProtocolException, IOException {  | 
|
        /* | 
|
         * Comment out for the moment since it's not in use and would create | 
|
         * needless cross-package links. | 
|
         * | 
|
        issueCommandCheck("AUTH GSSAPI"); | 
|
        if (lastReplyCode != FtpReplyCode.NEED_ADAT) | 
|
        throw new sun.net.ftp.FtpProtocolException("Unexpected reply from server"); | 
|
        try { | 
|
        GSSManager manager = GSSManager.getInstance(); | 
|
        GSSName name = manager.createName("SERVICE:ftp@"+ | 
|
        serverAddr.getHostName(), null); | 
|
        GSSContext context = manager.createContext(name, null, null, | 
|
        GSSContext.DEFAULT_LIFETIME); | 
|
        context.requestMutualAuth(true); | 
|
        context.requestReplayDet(true); | 
|
        context.requestSequenceDet(true); | 
|
        context.requestCredDeleg(true); | 
|
        byte []inToken = new byte[0]; | 
|
        while (!context.isEstablished()) { | 
|
        byte[] outToken | 
|
        = context.initSecContext(inToken, 0, inToken.length); | 
|
        // send the output token if generated | 
|
        if (outToken != null) { | 
|
        if (sendSecurityData(outToken)) { | 
|
        inToken = getSecurityData(); | 
|
        } | 
|
        } | 
|
        } | 
|
        loggedIn = true; | 
|
        } catch (GSSException e) { | 
|
 | 
|
        } | 
|
*/  | 
|
return this;  | 
|
}  | 
|
    /** | 
|
     * Returns the Welcome string the server sent during initial connection. | 
|
     * | 
|
     * @return a <code>String</code> containing the message the server | 
|
     *         returned during connection or <code>null</code>. | 
|
*/  | 
|
public String getWelcomeMsg() {  | 
|
return welcomeMsg;  | 
|
}  | 
|
    /** | 
|
     * Returns the last reply code sent by the server. | 
|
     * | 
|
     * @return the lastReplyCode | 
|
*/  | 
|
public FtpReplyCode getLastReplyCode() {  | 
|
return lastReplyCode;  | 
|
}  | 
|
    /** | 
|
     * Returns the last response string sent by the server. | 
|
     * | 
|
     * @return the message string, which can be quite long, last returned | 
|
     *         by the server. | 
|
*/  | 
|
public String getLastResponseString() {  | 
|
StringBuffer sb = new StringBuffer();  | 
|
if (serverResponse != null) {  | 
|
for (String l : serverResponse) {  | 
|
if (l != null) {  | 
|
sb.append(l);  | 
|
}  | 
|
}  | 
|
}  | 
|
return sb.toString();  | 
|
}  | 
|
    /** | 
|
     * Returns, when available, the size of the latest started transfer. | 
|
     * This is retreived by parsing the response string received as an initial | 
|
     * response to a RETR or similar request. | 
|
     * | 
|
     * @return the size of the latest transfer or -1 if either there was no | 
|
     *         transfer or the information was unavailable. | 
|
*/  | 
|
    public long getLastTransferSize() { | 
|
return lastTransSize;  | 
|
}  | 
|
    /** | 
|
     * Returns, when available, the remote name of the last transfered file. | 
|
     * This is mainly useful for "put" operation when the unique flag was | 
|
     * set since it allows to recover the unique file name created on the | 
|
     * server which may be different from the one submitted with the command. | 
|
     * | 
|
     * @return the name the latest transfered file remote name, or | 
|
     *         <code>null</code> if that information is unavailable. | 
|
*/  | 
|
public String getLastFileName() {  | 
|
return lastFileName;  | 
|
}  | 
|
    /** | 
|
     * Attempts to switch to a secure, encrypted connection. This is done by | 
|
     * sending the "AUTH TLS" command. | 
|
     * <p>See <a href="http://www.ietf.org/rfc/rfc4217.txt">RFC 4217</a></p> | 
|
     * If successful this will establish a secure command channel with the | 
|
     * server, it will also make it so that all other transfers (e.g. a RETR | 
|
     * command) will be done over an encrypted channel as well unless a | 
|
     * {@link #reInit()} command or a {@link #endSecureSession()} command is issued. | 
|
     * | 
|
     * @return <code>true</code> if the operation was successful. | 
|
     * @throws IOException if an error occurred during the transmission. | 
|
     * @see #endSecureSession() | 
|
*/  | 
|
public sun.net.ftp.FtpClient startSecureSession() throws sun.net.ftp.FtpProtocolException, IOException {  | 
|
if (!isConnected()) {  | 
|
throw new sun.net.ftp.FtpProtocolException("Not connected yet", FtpReplyCode.BAD_SEQUENCE);  | 
|
}  | 
|
if (sslFact == null) {  | 
|
            try { | 
|
sslFact = (SSLSocketFactory) SSLSocketFactory.getDefault();  | 
|
} catch (Exception e) {  | 
|
throw new IOException(e.getLocalizedMessage());  | 
|
}  | 
|
}  | 
|
issueCommandCheck("AUTH TLS");  | 
|
Socket s = null;  | 
|
        try { | 
|
s = sslFact.createSocket(server, serverAddr.getHostName(), serverAddr.getPort(), true);  | 
|
        } catch (javax.net.ssl.SSLException ssle) { | 
|
            try { | 
|
disconnect();  | 
|
} catch (Exception e) {  | 
|
}  | 
|
throw ssle;  | 
|
}  | 
|
        // Remember underlying socket so we can restore it later | 
|
oldSocket = server;  | 
|
server = s;  | 
|
        try { | 
|
out = new PrintStream(new BufferedOutputStream(server.getOutputStream()),  | 
|
true, encoding);  | 
|
} catch (UnsupportedEncodingException e) {  | 
|
throw new InternalError(encoding + "encoding not found", e);  | 
|
}  | 
|
in = new BufferedInputStream(server.getInputStream());  | 
|
issueCommandCheck("PBSZ 0");  | 
|
issueCommandCheck("PROT P");  | 
|
useCrypto = true;  | 
|
return this;  | 
|
}  | 
|
    /** | 
|
     * Sends a <code>CCC</code> command followed by a <code>PROT C</code> | 
|
     * command to the server terminating an encrypted session and reverting | 
|
     * back to a non crypted transmission. | 
|
     * | 
|
     * @return <code>true</code> if the operation was successful. | 
|
     * @throws IOException if an error occurred during transmission. | 
|
     * @see #startSecureSession() | 
|
*/  | 
|
public sun.net.ftp.FtpClient endSecureSession() throws sun.net.ftp.FtpProtocolException, IOException {  | 
|
if (!useCrypto) {  | 
|
return this;  | 
|
}  | 
|
issueCommandCheck("CCC");  | 
|
issueCommandCheck("PROT C");  | 
|
useCrypto = false;  | 
|
        // Restore previous socket and streams | 
|
server = oldSocket;  | 
|
oldSocket = null;  | 
|
        try { | 
|
out = new PrintStream(new BufferedOutputStream(server.getOutputStream()),  | 
|
true, encoding);  | 
|
} catch (UnsupportedEncodingException e) {  | 
|
throw new InternalError(encoding + "encoding not found", e);  | 
|
}  | 
|
in = new BufferedInputStream(server.getInputStream());  | 
|
return this;  | 
|
}  | 
|
    /** | 
|
     * Sends the "Allocate" (ALLO) command to the server telling it to | 
|
     * pre-allocate the specified number of bytes for the next transfer. | 
|
     * | 
|
     * @param size The number of bytes to allocate. | 
|
     * @return <code>true</code> if the operation was successful. | 
|
     * @throws IOException if an error occurred during the transmission. | 
|
*/  | 
|
public sun.net.ftp.FtpClient allocate(long size) throws sun.net.ftp.FtpProtocolException, IOException {  | 
|
issueCommandCheck("ALLO " + size);  | 
|
return this;  | 
|
}  | 
|
    /** | 
|
     * Sends the "Structure Mount" (SMNT) command to the server. This let the | 
|
     * user mount a different file system data structure without altering his | 
|
     * login or accounting information. | 
|
     * | 
|
     * @param struct a <code>String</code> containing the name of the | 
|
     *        structure to mount. | 
|
     * @return <code>true</code> if the operation was successful. | 
|
     * @throws IOException if an error occurred during the transmission. | 
|
*/  | 
|
public sun.net.ftp.FtpClient structureMount(String struct) throws sun.net.ftp.FtpProtocolException, IOException {  | 
|
issueCommandCheck("SMNT " + struct);  | 
|
return this;  | 
|
}  | 
|
    /** | 
|
     * Sends a SYST (System) command to the server and returns the String | 
|
     * sent back by the server describing the operating system at the | 
|
     * server. | 
|
     * | 
|
     * @return a <code>String</code> describing the OS, or <code>null</code> | 
|
     *         if the operation was not successful. | 
|
     * @throws IOException if an error occurred during the transmission. | 
|
*/  | 
|
public String getSystem() throws sun.net.ftp.FtpProtocolException, IOException {  | 
|
issueCommandCheck("SYST");  | 
|
        /* | 
|
         * 215 UNIX Type: L8 Version: SUNOS | 
|
*/  | 
|
String resp = getResponseString();  | 
|
        // Get rid of the leading code and blank | 
|
return resp.substring(4);  | 
|
}  | 
|
    /** | 
|
     * Sends the HELP command to the server, with an optional command, like | 
|
     * SITE, and returns the text sent back by the server. | 
|
     * | 
|
     * @param cmd the command for which the help is requested or | 
|
     *        <code>null</code> for the general help | 
|
     * @return a <code>String</code> containing the text sent back by the | 
|
     *         server, or <code>null</code> if the command failed. | 
|
     * @throws IOException if an error occurred during transmission | 
|
*/  | 
|
public String getHelp(String cmd) throws sun.net.ftp.FtpProtocolException, IOException {  | 
|
issueCommandCheck("HELP " + cmd);  | 
|
        /** | 
|
         * | 
|
         * HELP | 
|
         * 214-The following commands are implemented. | 
|
         *   USER    EPRT    STRU    ALLO    DELE    SYST    RMD     MDTM    ADAT | 
|
         *   PASS    EPSV    MODE    REST    CWD     STAT    PWD     PROT | 
|
         *   QUIT    LPRT    RETR    RNFR    LIST    HELP    CDUP    PBSZ | 
|
         *   PORT    LPSV    STOR    RNTO    NLST    NOOP    STOU    AUTH | 
|
         *   PASV    TYPE    APPE    ABOR    SITE    MKD     SIZE    CCC | 
|
         * 214 Direct comments to ftp-bugs@jsn. | 
|
         * | 
|
         * HELP SITE | 
|
         * 214-The following SITE commands are implemented. | 
|
         *   UMASK           HELP            GROUPS | 
|
         *   IDLE            ALIAS           CHECKMETHOD | 
|
         *   CHMOD           CDPATH          CHECKSUM | 
|
         * 214 Direct comments to ftp-bugs@jsn. | 
|
*/  | 
|
Vector<String> resp = getResponseStrings();  | 
|
if (resp.size() == 1) {  | 
|
            // Single line response | 
|
return resp.get(0).substring(4);  | 
|
}  | 
|
// on multiple lines answers, like the ones above, remove 1st and last  | 
|
        // line, concat the the others. | 
|
StringBuffer sb = new StringBuffer();  | 
|
for (int i = 1; i < resp.size() - 1; i++) {  | 
|
sb.append(resp.get(i).substring(3));  | 
|
}  | 
|
return sb.toString();  | 
|
}  | 
|
    /** | 
|
     * Sends the SITE command to the server. This is used by the server | 
|
     * to provide services specific to his system that are essential | 
|
     * to file transfer. | 
|
     * | 
|
     * @param cmd the command to be sent. | 
|
     * @return <code>true</code> if the command was successful. | 
|
     * @throws IOException if an error occurred during transmission | 
|
*/  | 
|
public sun.net.ftp.FtpClient siteCmd(String cmd) throws sun.net.ftp.FtpProtocolException, IOException {  | 
|
issueCommandCheck("SITE " + cmd);  | 
|
return this;  | 
|
}  | 
|
}  |