Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/jdk17u
Path: blob/master/src/java.base/share/classes/sun/net/ftp/impl/FtpClient.java
67848 views
1
/*
2
* Copyright (c) 2009, 2021, Oracle and/or its affiliates. All rights reserved.
3
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4
*
5
* This code is free software; you can redistribute it and/or modify it
6
* under the terms of the GNU General Public License version 2 only, as
7
* published by the Free Software Foundation. Oracle designates this
8
* particular file as subject to the "Classpath" exception as provided
9
* by Oracle in the LICENSE file that accompanied this code.
10
*
11
* This code is distributed in the hope that it will be useful, but WITHOUT
12
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14
* version 2 for more details (a copy is included in the LICENSE file that
15
* accompanied this code).
16
*
17
* You should have received a copy of the GNU General Public License version
18
* 2 along with this work; if not, write to the Free Software Foundation,
19
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20
*
21
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22
* or visit www.oracle.com if you need additional information or have any
23
* questions.
24
*/
25
package sun.net.ftp.impl;
26
27
28
29
import java.io.BufferedInputStream;
30
import java.io.BufferedOutputStream;
31
import java.io.BufferedReader;
32
import java.io.Closeable;
33
import java.io.FileNotFoundException;
34
import java.io.IOException;
35
import java.io.InputStream;
36
import java.io.InputStreamReader;
37
import java.io.OutputStream;
38
import java.io.PrintStream;
39
import java.io.UnsupportedEncodingException;
40
import java.net.Inet6Address;
41
import java.net.InetAddress;
42
import java.net.InetSocketAddress;
43
import java.net.Proxy;
44
import java.net.ServerSocket;
45
import java.net.Socket;
46
import java.net.SocketAddress;
47
import java.security.AccessController;
48
import java.security.PrivilegedAction;
49
import java.security.PrivilegedExceptionAction;
50
import java.text.DateFormat;
51
import java.time.ZoneOffset;
52
import java.time.ZonedDateTime;
53
import java.time.format.DateTimeFormatter;
54
import java.time.format.DateTimeParseException;
55
import java.util.ArrayList;
56
import java.util.Arrays;
57
import java.util.Base64;
58
import java.util.Calendar;
59
import java.util.Date;
60
import java.util.Iterator;
61
import java.util.List;
62
import java.util.Vector;
63
import java.util.regex.Matcher;
64
import java.util.regex.Pattern;
65
import javax.net.ssl.SSLSocket;
66
import javax.net.ssl.SSLSocketFactory;
67
import sun.net.ftp.FtpDirEntry;
68
import sun.net.ftp.FtpDirParser;
69
import sun.net.ftp.FtpProtocolException;
70
import sun.net.ftp.FtpReplyCode;
71
import sun.net.util.IPAddressUtil;
72
import sun.util.logging.PlatformLogger;
73
74
75
public class FtpClient extends sun.net.ftp.FtpClient {
76
77
private static int defaultSoTimeout;
78
private static int defaultConnectTimeout;
79
private static final PlatformLogger logger =
80
PlatformLogger.getLogger("sun.net.ftp.FtpClient");
81
private Proxy proxy;
82
private Socket server;
83
private PrintStream out;
84
private InputStream in;
85
private int readTimeout = -1;
86
private int connectTimeout = -1;
87
88
/* Name of encoding to use for output */
89
private static String encoding = "ISO8859_1";
90
/** remember the ftp server name because we may need it */
91
private InetSocketAddress serverAddr;
92
private boolean replyPending = false;
93
private boolean loggedIn = false;
94
private boolean useCrypto = false;
95
private SSLSocketFactory sslFact;
96
private Socket oldSocket;
97
/** Array of strings (usually 1 entry) for the last reply from the server. */
98
private Vector<String> serverResponse = new Vector<String>(1);
99
/** The last reply code from the ftp daemon. */
100
private FtpReplyCode lastReplyCode = null;
101
/** Welcome message from the server, if any. */
102
private String welcomeMsg;
103
/**
104
* Only passive mode used in JDK. See Bug 8010784.
105
*/
106
private final boolean passiveMode = true;
107
private TransferType type = TransferType.BINARY;
108
private long restartOffset = 0;
109
private long lastTransSize = -1; // -1 means 'unknown size'
110
private String lastFileName;
111
/**
112
* Static members used by the parser
113
*/
114
private static String[] patStrings = {
115
// drwxr-xr-x 1 user01 ftp 512 Jan 29 23:32 prog
116
"([\\-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}*)",
117
// drwxr-xr-x 1 user01 ftp 512 Jan 29 1997 prog
118
"([\\-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}*)",
119
// 04/28/2006 09:12a 3,563 genBuffer.sh
120
"(\\d{2}/\\d{2}/\\d{4})\\s*(\\d{2}:\\d{2}[ap])\\s*((?:[0-9,]+)|(?:<DIR>))\\s*(\\p{Graph}*)",
121
// 01-29-97 11:32PM <DIR> prog
122
"(\\d{2}-\\d{2}-\\d{2})\\s*(\\d{2}:\\d{2}[AP]M)\\s*((?:[0-9,]+)|(?:<DIR>))\\s*(\\p{Graph}*)"
123
};
124
private static int[][] patternGroups = {
125
// 0 - file, 1 - size, 2 - date, 3 - time, 4 - year, 5 - permissions,
126
// 6 - user, 7 - group
127
{7, 4, 5, 6, 0, 1, 2, 3},
128
{7, 4, 5, 0, 6, 1, 2, 3},
129
{4, 3, 1, 2, 0, 0, 0, 0},
130
{4, 3, 1, 2, 0, 0, 0, 0}};
131
private static Pattern[] patterns;
132
private static Pattern linkp = Pattern.compile("(\\p{Print}+) \\-\\> (\\p{Print}+)$");
133
private DateFormat df = DateFormat.getDateInstance(DateFormat.MEDIUM, java.util.Locale.US);
134
private static final boolean acceptPasvAddressVal;
135
static {
136
final int vals[] = {0, 0};
137
final String acceptPasvAddress[] = {null};
138
@SuppressWarnings("removal")
139
final String enc = AccessController.doPrivileged(
140
new PrivilegedAction<String>() {
141
public String run() {
142
acceptPasvAddress[0] = System.getProperty("jdk.net.ftp.trustPasvAddress", "false");
143
vals[0] = Integer.getInteger("sun.net.client.defaultReadTimeout", 300_000).intValue();
144
vals[1] = Integer.getInteger("sun.net.client.defaultConnectTimeout", 300_000).intValue();
145
return System.getProperty("file.encoding", "ISO8859_1");
146
}
147
});
148
if (vals[0] == 0) {
149
defaultSoTimeout = -1;
150
} else {
151
defaultSoTimeout = vals[0];
152
}
153
154
if (vals[1] == 0) {
155
defaultConnectTimeout = -1;
156
} else {
157
defaultConnectTimeout = vals[1];
158
}
159
160
encoding = enc;
161
try {
162
if (!isASCIISuperset(encoding)) {
163
encoding = "ISO8859_1";
164
}
165
} catch (Exception e) {
166
encoding = "ISO8859_1";
167
}
168
169
patterns = new Pattern[patStrings.length];
170
for (int i = 0; i < patStrings.length; i++) {
171
patterns[i] = Pattern.compile(patStrings[i]);
172
}
173
174
acceptPasvAddressVal = Boolean.parseBoolean(acceptPasvAddress[0]);
175
}
176
177
/**
178
* Test the named character encoding to verify that it converts ASCII
179
* characters correctly. We have to use an ASCII based encoding, or else
180
* the NetworkClients will not work correctly in EBCDIC based systems.
181
* However, we cannot just use ASCII or ISO8859_1 universally, because in
182
* Asian locales, non-ASCII characters may be embedded in otherwise
183
* ASCII based protocols (e.g. HTTP). The specifications (RFC2616, 2398)
184
* are a little ambiguous in this matter. For instance, RFC2398 [part 2.1]
185
* says that the HTTP request URI should be escaped using a defined
186
* mechanism, but there is no way to specify in the escaped string what
187
* the original character set is. It is not correct to assume that
188
* UTF-8 is always used (as in URLs in HTML 4.0). For this reason,
189
* until the specifications are updated to deal with this issue more
190
* comprehensively, and more importantly, HTTP servers are known to
191
* support these mechanisms, we will maintain the current behavior
192
* where it is possible to send non-ASCII characters in their original
193
* unescaped form.
194
*/
195
private static boolean isASCIISuperset(String encoding) throws Exception {
196
String chkS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
197
"abcdefghijklmnopqrstuvwxyz-_.!~*'();/?:@&=+$,";
198
199
// Expected byte sequence for string above
200
byte[] chkB = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 71, 72,
201
73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99,
202
100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114,
203
115, 116, 117, 118, 119, 120, 121, 122, 45, 95, 46, 33, 126, 42, 39, 40, 41, 59,
204
47, 63, 58, 64, 38, 61, 43, 36, 44};
205
206
byte[] b = chkS.getBytes(encoding);
207
return java.util.Arrays.equals(b, chkB);
208
}
209
210
private class DefaultParser implements FtpDirParser {
211
212
/**
213
* Possible patterns:
214
*
215
* drwxr-xr-x 1 user01 ftp 512 Jan 29 23:32 prog
216
* drwxr-xr-x 1 user01 ftp 512 Jan 29 1997 prog
217
* drwxr-xr-x 1 1 1 512 Jan 29 23:32 prog
218
* lrwxr-xr-x 1 user01 ftp 512 Jan 29 23:32 prog -> prog2000
219
* drwxr-xr-x 1 username ftp 512 Jan 29 23:32 prog
220
* -rw-r--r-- 1 jcc staff 105009 Feb 3 15:05 test.1
221
*
222
* 01-29-97 11:32PM <DIR> prog
223
* 04/28/2006 09:12a 3,563 genBuffer.sh
224
*
225
* drwxr-xr-x folder 0 Jan 29 23:32 prog
226
*
227
* 0 DIR 01-29-97 23:32 PROG
228
*/
229
private DefaultParser() {
230
}
231
232
public FtpDirEntry parseLine(String line) {
233
String fdate = null;
234
String fsize = null;
235
String time = null;
236
String filename = null;
237
String permstring = null;
238
String username = null;
239
String groupname = null;
240
boolean dir = false;
241
Calendar now = Calendar.getInstance();
242
int year = now.get(Calendar.YEAR);
243
244
Matcher m = null;
245
for (int j = 0; j < patterns.length; j++) {
246
m = patterns[j].matcher(line);
247
if (m.find()) {
248
// 0 - file, 1 - size, 2 - date, 3 - time, 4 - year,
249
// 5 - permissions, 6 - user, 7 - group
250
filename = m.group(patternGroups[j][0]);
251
fsize = m.group(patternGroups[j][1]);
252
fdate = m.group(patternGroups[j][2]);
253
if (patternGroups[j][4] > 0) {
254
fdate += (", " + m.group(patternGroups[j][4]));
255
} else if (patternGroups[j][3] > 0) {
256
fdate += (", " + String.valueOf(year));
257
}
258
if (patternGroups[j][3] > 0) {
259
time = m.group(patternGroups[j][3]);
260
}
261
if (patternGroups[j][5] > 0) {
262
permstring = m.group(patternGroups[j][5]);
263
dir = permstring.startsWith("d");
264
}
265
if (patternGroups[j][6] > 0) {
266
username = m.group(patternGroups[j][6]);
267
}
268
if (patternGroups[j][7] > 0) {
269
groupname = m.group(patternGroups[j][7]);
270
}
271
// Old DOS format
272
if ("<DIR>".equals(fsize)) {
273
dir = true;
274
fsize = null;
275
}
276
}
277
}
278
279
if (filename != null) {
280
Date d;
281
try {
282
d = df.parse(fdate);
283
} catch (Exception e) {
284
d = null;
285
}
286
if (d != null && time != null) {
287
int c = time.indexOf(':');
288
now.setTime(d);
289
now.set(Calendar.HOUR, Integer.parseInt(time, 0, c, 10));
290
now.set(Calendar.MINUTE, Integer.parseInt(time, c + 1, time.length(), 10));
291
d = now.getTime();
292
}
293
// see if it's a symbolic link, i.e. the name if followed
294
// by a -> and a path
295
Matcher m2 = linkp.matcher(filename);
296
if (m2.find()) {
297
// Keep only the name then
298
filename = m2.group(1);
299
}
300
boolean[][] perms = new boolean[3][3];
301
for (int i = 0; i < 3; i++) {
302
for (int j = 0; j < 3; j++) {
303
perms[i][j] = (permstring.charAt((i * 3) + j) != '-');
304
}
305
}
306
FtpDirEntry file = new FtpDirEntry(filename);
307
file.setUser(username).setGroup(groupname);
308
file.setSize(Long.parseLong(fsize)).setLastModified(d);
309
file.setPermissions(perms);
310
file.setType(dir ? FtpDirEntry.Type.DIR : (line.charAt(0) == 'l' ? FtpDirEntry.Type.LINK : FtpDirEntry.Type.FILE));
311
return file;
312
}
313
return null;
314
}
315
}
316
317
private static class MLSxParser implements FtpDirParser {
318
public FtpDirEntry parseLine(String line) {
319
String name = null;
320
int i = line.lastIndexOf(';');
321
if (i > 0) {
322
name = line.substring(i + 1).trim();
323
line = line.substring(0, i);
324
} else {
325
name = line.trim();
326
line = "";
327
}
328
FtpDirEntry file = new FtpDirEntry(name);
329
while (!line.isEmpty()) {
330
String s;
331
i = line.indexOf(';');
332
if (i > 0) {
333
s = line.substring(0, i);
334
line = line.substring(i + 1);
335
} else {
336
s = line;
337
line = "";
338
}
339
i = s.indexOf('=');
340
if (i > 0) {
341
String fact = s.substring(0, i);
342
String value = s.substring(i + 1);
343
file.addFact(fact, value);
344
}
345
}
346
String s = file.getFact("Size");
347
if (s != null) {
348
file.setSize(Long.parseLong(s));
349
}
350
s = file.getFact("Modify");
351
if (s != null) {
352
Date d = parseRfc3659TimeValue(s);
353
if (d != null) {
354
file.setLastModified(d);
355
}
356
}
357
s = file.getFact("Create");
358
if (s != null) {
359
Date d = parseRfc3659TimeValue(s);
360
if (d != null) {
361
file.setCreated(d);
362
}
363
}
364
s = file.getFact("Type");
365
if (s != null) {
366
if (s.equalsIgnoreCase("file")) {
367
file.setType(FtpDirEntry.Type.FILE);
368
}
369
if (s.equalsIgnoreCase("dir")) {
370
file.setType(FtpDirEntry.Type.DIR);
371
}
372
if (s.equalsIgnoreCase("cdir")) {
373
file.setType(FtpDirEntry.Type.CDIR);
374
}
375
if (s.equalsIgnoreCase("pdir")) {
376
file.setType(FtpDirEntry.Type.PDIR);
377
}
378
}
379
return file;
380
}
381
};
382
private FtpDirParser parser = new DefaultParser();
383
private FtpDirParser mlsxParser = new MLSxParser();
384
private static Pattern transPat = null;
385
386
private void getTransferSize() {
387
lastTransSize = -1;
388
/**
389
* If it's a start of data transfer response, let's try to extract
390
* the size from the response string. Usually it looks like that:
391
*
392
* 150 Opening BINARY mode data connection for foo (6701 bytes).
393
*/
394
String response = getLastResponseString();
395
if (transPat == null) {
396
transPat = Pattern.compile("150 Opening .*\\((\\d+) bytes\\).");
397
}
398
Matcher m = transPat.matcher(response);
399
if (m.find()) {
400
String s = m.group(1);
401
lastTransSize = Long.parseLong(s);
402
}
403
}
404
405
/**
406
* extract the created file name from the response string:
407
* 226 Transfer complete (unique file name:toto.txt.1).
408
* Usually happens when a STOU (store unique) command had been issued.
409
*/
410
private void getTransferName() {
411
lastFileName = null;
412
String response = getLastResponseString();
413
int i = response.indexOf("unique file name:");
414
int e = response.lastIndexOf(')');
415
if (i >= 0) {
416
i += 17; // Length of "unique file name:"
417
lastFileName = response.substring(i, e);
418
}
419
}
420
421
/**
422
* Pulls the response from the server and returns the code as a
423
* number. Returns -1 on failure.
424
*/
425
private int readServerResponse() throws IOException {
426
StringBuilder replyBuf = new StringBuilder(32);
427
int c;
428
int continuingCode = -1;
429
int code;
430
String response;
431
432
serverResponse.setSize(0);
433
while (true) {
434
while ((c = in.read()) != -1) {
435
if (c == '\r') {
436
if ((c = in.read()) != '\n') {
437
replyBuf.append('\r');
438
}
439
}
440
replyBuf.append((char) c);
441
if (c == '\n') {
442
break;
443
}
444
}
445
response = replyBuf.toString();
446
replyBuf.setLength(0);
447
if (logger.isLoggable(PlatformLogger.Level.FINEST)) {
448
logger.finest("Server [" + serverAddr + "] --> " + response);
449
}
450
451
if (response.isEmpty()) {
452
code = -1;
453
} else {
454
try {
455
code = Integer.parseInt(response, 0, 3, 10);
456
} catch (NumberFormatException e) {
457
code = -1;
458
} catch (IndexOutOfBoundsException e) {
459
/* this line doesn't contain a response code, so
460
we just completely ignore it */
461
continue;
462
}
463
}
464
serverResponse.addElement(response);
465
if (continuingCode != -1) {
466
/* we've seen a ###- sequence */
467
if (code != continuingCode ||
468
(response.length() >= 4 && response.charAt(3) == '-')) {
469
continue;
470
} else {
471
/* seen the end of code sequence */
472
continuingCode = -1;
473
break;
474
}
475
} else if (response.length() >= 4 && response.charAt(3) == '-') {
476
continuingCode = code;
477
continue;
478
} else {
479
break;
480
}
481
}
482
483
return code;
484
}
485
486
/** Sends command <i>cmd</i> to the server. */
487
private void sendServer(String cmd) {
488
out.print(cmd);
489
if (logger.isLoggable(PlatformLogger.Level.FINEST)) {
490
logger.finest("Server [" + serverAddr + "] <-- " + cmd);
491
}
492
}
493
494
/** converts the server response into a string. */
495
private String getResponseString() {
496
return serverResponse.elementAt(0);
497
}
498
499
/** Returns all server response strings. */
500
private Vector<String> getResponseStrings() {
501
return serverResponse;
502
}
503
504
/**
505
* Read the reply from the FTP server.
506
*
507
* @return <code>true</code> if the command was successful
508
* @throws IOException if an error occurred
509
*/
510
private boolean readReply() throws IOException {
511
lastReplyCode = FtpReplyCode.find(readServerResponse());
512
513
if (lastReplyCode.isPositivePreliminary()) {
514
replyPending = true;
515
return true;
516
}
517
if (lastReplyCode.isPositiveCompletion() || lastReplyCode.isPositiveIntermediate()) {
518
if (lastReplyCode == FtpReplyCode.CLOSING_DATA_CONNECTION) {
519
getTransferName();
520
}
521
return true;
522
}
523
return false;
524
}
525
526
/**
527
* Sends a command to the FTP server and returns the error code
528
* (which can be a "success") sent by the server.
529
*
530
* @param cmd
531
* @return <code>true</code> if the command was successful
532
* @throws IOException
533
*/
534
private boolean issueCommand(String cmd) throws IOException,
535
sun.net.ftp.FtpProtocolException {
536
if (!isConnected()) {
537
throw new IllegalStateException("Not connected");
538
}
539
if (replyPending) {
540
try {
541
completePending();
542
} catch (sun.net.ftp.FtpProtocolException e) {
543
// ignore...
544
}
545
}
546
if (cmd.indexOf('\n') != -1) {
547
sun.net.ftp.FtpProtocolException ex
548
= new sun.net.ftp.FtpProtocolException("Illegal FTP command");
549
ex.initCause(new IllegalArgumentException("Illegal carriage return"));
550
throw ex;
551
}
552
sendServer(cmd + "\r\n");
553
return readReply();
554
}
555
556
/**
557
* Send a command to the FTP server and check for success.
558
*
559
* @param cmd String containing the command
560
*
561
* @throws FtpProtocolException if an error occurred
562
*/
563
private void issueCommandCheck(String cmd) throws sun.net.ftp.FtpProtocolException, IOException {
564
if (!issueCommand(cmd)) {
565
throw new sun.net.ftp.FtpProtocolException(cmd + ":" + getResponseString(), getLastReplyCode());
566
}
567
}
568
private static Pattern epsvPat = null;
569
private static Pattern pasvPat = null;
570
571
/**
572
* Opens a "PASSIVE" connection with the server and returns the connected
573
* <code>Socket</code>.
574
*
575
* @return the connected <code>Socket</code>
576
* @throws IOException if the connection was unsuccessful.
577
*/
578
private Socket openPassiveDataConnection(String cmd) throws sun.net.ftp.FtpProtocolException, IOException {
579
String serverAnswer;
580
int port;
581
InetSocketAddress dest = null;
582
583
/**
584
* Here is the idea:
585
*
586
* - First we want to try the new (and IPv6 compatible) EPSV command
587
* But since we want to be nice with NAT software, we'll issue the
588
* EPSV ALL command first.
589
* EPSV is documented in RFC2428
590
* - If EPSV fails, then we fall back to the older, yet ok, PASV
591
* - If PASV fails as well, then we throw an exception and the calling
592
* method will have to try the EPRT or PORT command
593
*/
594
if (issueCommand("EPSV ALL")) {
595
// We can safely use EPSV commands
596
issueCommandCheck("EPSV");
597
serverAnswer = getResponseString();
598
599
// The response string from a EPSV command will contain the port number
600
// the format will be :
601
// 229 Entering Extended PASSIVE Mode (|||58210|)
602
//
603
// So we'll use the regular expresions package to parse the output.
604
605
if (epsvPat == null) {
606
epsvPat = Pattern.compile("^229 .* \\(\\|\\|\\|(\\d+)\\|\\)");
607
}
608
Matcher m = epsvPat.matcher(serverAnswer);
609
if (!m.find()) {
610
throw new sun.net.ftp.FtpProtocolException("EPSV failed : " + serverAnswer);
611
}
612
// Yay! Let's extract the port number
613
String s = m.group(1);
614
port = Integer.parseInt(s);
615
InetAddress add = server.getInetAddress();
616
if (add != null) {
617
dest = new InetSocketAddress(add, port);
618
} else {
619
// This means we used an Unresolved address to connect in
620
// the first place. Most likely because the proxy is doing
621
// the name resolution for us, so let's keep using unresolved
622
// address.
623
dest = InetSocketAddress.createUnresolved(serverAddr.getHostName(), port);
624
}
625
} else {
626
// EPSV ALL failed, so Let's try the regular PASV cmd
627
issueCommandCheck("PASV");
628
serverAnswer = getResponseString();
629
630
// Let's parse the response String to get the IP & port to connect
631
// to. The String should be in the following format :
632
//
633
// 227 Entering PASSIVE Mode (A1,A2,A3,A4,p1,p2)
634
//
635
// Note that the two parenthesis are optional
636
//
637
// The IP address is A1.A2.A3.A4 and the port is p1 * 256 + p2
638
//
639
// The regular expression is a bit more complex this time, because
640
// the parenthesis are optionals and we have to use 3 groups.
641
if (pasvPat == null) {
642
pasvPat = Pattern.compile("227 .* \\(?(\\d{1,3},\\d{1,3},\\d{1,3},\\d{1,3}),(\\d{1,3}),(\\d{1,3})\\)?");
643
}
644
Matcher m = pasvPat.matcher(serverAnswer);
645
if (!m.find()) {
646
throw new sun.net.ftp.FtpProtocolException("PASV failed : " + serverAnswer);
647
}
648
// Get port number out of group 2 & 3
649
port = Integer.parseInt(m.group(3)) + (Integer.parseInt(m.group(2)) << 8);
650
// IP address is simple
651
String s = m.group(1).replace(',', '.');
652
if (!IPAddressUtil.isIPv4LiteralAddress(s))
653
throw new FtpProtocolException("PASV failed : " + serverAnswer);
654
if (acceptPasvAddressVal) {
655
dest = new InetSocketAddress(s, port);
656
} else {
657
dest = validatePasvAddress(port, s, server.getInetAddress());
658
}
659
}
660
661
// Got everything, let's open the socket!
662
Socket s;
663
if (proxy != null) {
664
if (proxy.type() == Proxy.Type.SOCKS) {
665
PrivilegedAction<Socket> pa = () -> new Socket(proxy);
666
@SuppressWarnings("removal")
667
var tmp = AccessController.doPrivileged(pa);
668
s = tmp;
669
} else {
670
s = new Socket(Proxy.NO_PROXY);
671
}
672
} else {
673
s = new Socket();
674
}
675
676
PrivilegedAction<InetAddress> pa = () -> server.getLocalAddress();
677
@SuppressWarnings("removal")
678
InetAddress serverAddress = AccessController.doPrivileged(pa);
679
680
// Bind the socket to the same address as the control channel. This
681
// is needed in case of multi-homed systems.
682
s.bind(new InetSocketAddress(serverAddress, 0));
683
if (connectTimeout >= 0) {
684
s.connect(dest, connectTimeout);
685
} else {
686
if (defaultConnectTimeout > 0) {
687
s.connect(dest, defaultConnectTimeout);
688
} else {
689
s.connect(dest);
690
}
691
}
692
if (readTimeout >= 0) {
693
s.setSoTimeout(readTimeout);
694
} else if (defaultSoTimeout > 0) {
695
s.setSoTimeout(defaultSoTimeout);
696
}
697
if (useCrypto) {
698
try {
699
s = sslFact.createSocket(s, dest.getHostName(), dest.getPort(), true);
700
} catch (Exception e) {
701
throw new sun.net.ftp.FtpProtocolException("Can't open secure data channel: " + e);
702
}
703
}
704
if (!issueCommand(cmd)) {
705
s.close();
706
if (getLastReplyCode() == FtpReplyCode.FILE_UNAVAILABLE) {
707
// Ensure backward compatibility
708
throw new FileNotFoundException(cmd);
709
}
710
throw new sun.net.ftp.FtpProtocolException(cmd + ":" + getResponseString(), getLastReplyCode());
711
}
712
return s;
713
}
714
715
static final String ERROR_MSG = "Address should be the same as originating server";
716
717
/**
718
* Returns an InetSocketAddress, based on value of acceptPasvAddressVal
719
* and other conditions such as the server address returned by pasv
720
* is not a hostname, is a socks proxy, or the loopback. An exception
721
* is thrown if none of the valid conditions are met.
722
*/
723
private InetSocketAddress validatePasvAddress(int port, String s, InetAddress address)
724
throws FtpProtocolException
725
{
726
if (address == null) {
727
return InetSocketAddress.createUnresolved(serverAddr.getHostName(), port);
728
}
729
String serverAddress = address.getHostAddress();
730
if (serverAddress.equals(s)) {
731
return new InetSocketAddress(s, port);
732
} else if (address.isLoopbackAddress() && s.startsWith("127.")) { // can be 127.0
733
return new InetSocketAddress(s, port);
734
} else if (address.isLoopbackAddress()) {
735
if (privilegedLocalHost().getHostAddress().equals(s)) {
736
return new InetSocketAddress(s, port);
737
} else {
738
throw new FtpProtocolException(ERROR_MSG);
739
}
740
} else if (s.startsWith("127.")) {
741
if (privilegedLocalHost().equals(address)) {
742
return new InetSocketAddress(s, port);
743
} else {
744
throw new FtpProtocolException(ERROR_MSG);
745
}
746
}
747
String hostName = address.getHostName();
748
if (!(IPAddressUtil.isIPv4LiteralAddress(hostName) || IPAddressUtil.isIPv6LiteralAddress(hostName))) {
749
InetAddress[] names = privilegedGetAllByName(hostName);
750
String resAddress = Arrays
751
.stream(names)
752
.map(InetAddress::getHostAddress)
753
.filter(s::equalsIgnoreCase)
754
.findFirst()
755
.orElse(null);
756
if (resAddress != null) {
757
return new InetSocketAddress(s, port);
758
}
759
}
760
throw new FtpProtocolException(ERROR_MSG);
761
}
762
763
private static InetAddress privilegedLocalHost() throws FtpProtocolException {
764
PrivilegedExceptionAction<InetAddress> action = InetAddress::getLocalHost;
765
try {
766
@SuppressWarnings("removal")
767
var tmp = AccessController.doPrivileged(action);
768
return tmp;
769
} catch (Exception e) {
770
var ftpEx = new FtpProtocolException(ERROR_MSG);
771
ftpEx.initCause(e);
772
throw ftpEx;
773
}
774
}
775
776
private static InetAddress[] privilegedGetAllByName(String hostName) throws FtpProtocolException {
777
PrivilegedExceptionAction<InetAddress[]> pAction = () -> InetAddress.getAllByName(hostName);
778
try {
779
@SuppressWarnings("removal")
780
var tmp =AccessController.doPrivileged(pAction);
781
return tmp;
782
} catch (Exception e) {
783
var ftpEx = new FtpProtocolException(ERROR_MSG);
784
ftpEx.initCause(e);
785
throw ftpEx;
786
}
787
}
788
789
/**
790
* Opens a data connection with the server according to the set mode
791
* (ACTIVE or PASSIVE) then send the command passed as an argument.
792
*
793
* @param cmd the <code>String</code> containing the command to execute
794
* @return the connected <code>Socket</code>
795
* @throws IOException if the connection or command failed
796
*/
797
private Socket openDataConnection(String cmd) throws sun.net.ftp.FtpProtocolException, IOException {
798
Socket clientSocket;
799
if (passiveMode) {
800
try {
801
return openPassiveDataConnection(cmd);
802
} catch (sun.net.ftp.FtpProtocolException e) {
803
// If Passive mode failed, fall back on PORT
804
// Otherwise throw exception
805
String errmsg = e.getMessage();
806
if (!errmsg.startsWith("PASV") && !errmsg.startsWith("EPSV")) {
807
throw e;
808
}
809
}
810
}
811
ServerSocket portSocket;
812
InetAddress myAddress;
813
String portCmd;
814
815
if (proxy != null && proxy.type() == Proxy.Type.SOCKS) {
816
// We're behind a firewall and the passive mode fail,
817
// since we can't accept a connection through SOCKS (yet)
818
// throw an exception
819
throw new sun.net.ftp.FtpProtocolException("Passive mode failed");
820
}
821
// Bind the ServerSocket to the same address as the control channel
822
// This is needed for multi-homed systems
823
portSocket = new ServerSocket(0, 1, server.getLocalAddress());
824
try {
825
myAddress = portSocket.getInetAddress();
826
if (myAddress.isAnyLocalAddress()) {
827
myAddress = server.getLocalAddress();
828
}
829
// Let's try the new, IPv6 compatible EPRT command
830
// See RFC2428 for specifics
831
// Some FTP servers (like the one on Solaris) are bugged, they
832
// will accept the EPRT command but then, the subsequent command
833
// (e.g. RETR) will fail, so we have to check BOTH results (the
834
// EPRT cmd then the actual command) to decide whether we should
835
// fall back on the older PORT command.
836
portCmd = "EPRT |" + ((myAddress instanceof Inet6Address) ? "2" : "1") + "|" +
837
myAddress.getHostAddress() + "|" + portSocket.getLocalPort() + "|";
838
if (!issueCommand(portCmd) || !issueCommand(cmd)) {
839
// The EPRT command failed, let's fall back to good old PORT
840
portCmd = "PORT ";
841
byte[] addr = myAddress.getAddress();
842
843
/* append host addr */
844
for (int i = 0; i < addr.length; i++) {
845
portCmd = portCmd + (addr[i] & 0xFF) + ",";
846
}
847
848
/* append port number */
849
portCmd = portCmd + ((portSocket.getLocalPort() >>> 8) & 0xff) + "," + (portSocket.getLocalPort() & 0xff);
850
issueCommandCheck(portCmd);
851
issueCommandCheck(cmd);
852
}
853
// Either the EPRT or the PORT command was successful
854
// Let's create the client socket
855
if (connectTimeout >= 0) {
856
portSocket.setSoTimeout(connectTimeout);
857
} else {
858
if (defaultConnectTimeout > 0) {
859
portSocket.setSoTimeout(defaultConnectTimeout);
860
}
861
}
862
clientSocket = portSocket.accept();
863
if (readTimeout >= 0) {
864
clientSocket.setSoTimeout(readTimeout);
865
} else {
866
if (defaultSoTimeout > 0) {
867
clientSocket.setSoTimeout(defaultSoTimeout);
868
}
869
}
870
} finally {
871
portSocket.close();
872
}
873
if (useCrypto) {
874
try {
875
clientSocket = sslFact.createSocket(clientSocket, serverAddr.getHostName(), serverAddr.getPort(), true);
876
} catch (Exception ex) {
877
throw new IOException(ex.getLocalizedMessage());
878
}
879
}
880
return clientSocket;
881
}
882
883
private InputStream createInputStream(InputStream in) {
884
if (type == TransferType.ASCII) {
885
return new sun.net.TelnetInputStream(in, false);
886
}
887
return in;
888
}
889
890
private OutputStream createOutputStream(OutputStream out) {
891
if (type == TransferType.ASCII) {
892
return new sun.net.TelnetOutputStream(out, false);
893
}
894
return out;
895
}
896
897
/**
898
* Creates an instance of FtpClient. The client is not connected to any
899
* server yet.
900
*
901
*/
902
protected FtpClient() {
903
}
904
905
/**
906
* Creates an instance of FtpClient. The client is not connected to any
907
* server yet.
908
*
909
*/
910
public static sun.net.ftp.FtpClient create() {
911
return new FtpClient();
912
}
913
914
/**
915
* Set the transfer mode to <I>passive</I>. In that mode, data connections
916
* are established by having the client connect to the server.
917
* This is the recommended default mode as it will work best through
918
* firewalls and NATs.
919
*
920
* @return This FtpClient
921
* @see #setActiveMode()
922
*/
923
public sun.net.ftp.FtpClient enablePassiveMode(boolean passive) {
924
925
// Only passive mode used in JDK. See Bug 8010784.
926
// passiveMode = passive;
927
return this;
928
}
929
930
/**
931
* Gets the current transfer mode.
932
*
933
* @return the current <code>FtpTransferMode</code>
934
*/
935
public boolean isPassiveModeEnabled() {
936
return passiveMode;
937
}
938
939
/**
940
* Sets the timeout value to use when connecting to the server,
941
*
942
* @param timeout the timeout value, in milliseconds, to use for the connect
943
* operation. A value of zero or less, means use the default timeout.
944
*
945
* @return This FtpClient
946
*/
947
public sun.net.ftp.FtpClient setConnectTimeout(int timeout) {
948
connectTimeout = timeout;
949
return this;
950
}
951
952
/**
953
* Returns the current connection timeout value.
954
*
955
* @return the value, in milliseconds, of the current connect timeout.
956
* @see #setConnectTimeout(int)
957
*/
958
public int getConnectTimeout() {
959
return connectTimeout;
960
}
961
962
/**
963
* Sets the timeout value to use when reading from the server,
964
*
965
* @param timeout the timeout value, in milliseconds, to use for the read
966
* operation. A value of zero or less, means use the default timeout.
967
* @return This FtpClient
968
*/
969
public sun.net.ftp.FtpClient setReadTimeout(int timeout) {
970
readTimeout = timeout;
971
return this;
972
}
973
974
/**
975
* Returns the current read timeout value.
976
*
977
* @return the value, in milliseconds, of the current read timeout.
978
* @see #setReadTimeout(int)
979
*/
980
public int getReadTimeout() {
981
return readTimeout;
982
}
983
984
public sun.net.ftp.FtpClient setProxy(Proxy p) {
985
proxy = p;
986
return this;
987
}
988
989
/**
990
* Get the proxy of this FtpClient
991
*
992
* @return the <code>Proxy</code>, this client is using, or <code>null</code>
993
* if none is used.
994
* @see #setProxy(Proxy)
995
*/
996
public Proxy getProxy() {
997
return proxy;
998
}
999
1000
/**
1001
* Connects to the specified destination.
1002
*
1003
* @param dest the <code>InetSocketAddress</code> to connect to.
1004
* @throws IOException if the connection fails.
1005
*/
1006
private void tryConnect(InetSocketAddress dest, int timeout) throws IOException {
1007
if (isConnected()) {
1008
disconnect();
1009
}
1010
server = doConnect(dest, timeout);
1011
try {
1012
out = new PrintStream(new BufferedOutputStream(server.getOutputStream()),
1013
true, encoding);
1014
} catch (UnsupportedEncodingException e) {
1015
throw new InternalError(encoding + "encoding not found", e);
1016
}
1017
in = new BufferedInputStream(server.getInputStream());
1018
}
1019
1020
private Socket doConnect(InetSocketAddress dest, int timeout) throws IOException {
1021
Socket s;
1022
if (proxy != null) {
1023
if (proxy.type() == Proxy.Type.SOCKS) {
1024
PrivilegedAction<Socket> pa = () -> new Socket(proxy);
1025
@SuppressWarnings("removal")
1026
var tmp = AccessController.doPrivileged(pa);
1027
s = tmp;
1028
} else {
1029
s = new Socket(Proxy.NO_PROXY);
1030
}
1031
} else {
1032
s = new Socket();
1033
}
1034
// Instance specific timeouts do have priority, that means
1035
// connectTimeout & readTimeout (-1 means not set)
1036
// Then global default timeouts
1037
// Then no timeout.
1038
if (timeout >= 0) {
1039
s.connect(dest, timeout);
1040
} else {
1041
if (connectTimeout >= 0) {
1042
s.connect(dest, connectTimeout);
1043
} else {
1044
if (defaultConnectTimeout > 0) {
1045
s.connect(dest, defaultConnectTimeout);
1046
} else {
1047
s.connect(dest);
1048
}
1049
}
1050
}
1051
if (readTimeout >= 0) {
1052
s.setSoTimeout(readTimeout);
1053
} else if (defaultSoTimeout > 0) {
1054
s.setSoTimeout(defaultSoTimeout);
1055
}
1056
return s;
1057
}
1058
1059
private void disconnect() throws IOException {
1060
if (isConnected()) {
1061
server.close();
1062
}
1063
server = null;
1064
in = null;
1065
out = null;
1066
lastTransSize = -1;
1067
lastFileName = null;
1068
restartOffset = 0;
1069
welcomeMsg = null;
1070
lastReplyCode = null;
1071
serverResponse.setSize(0);
1072
}
1073
1074
/**
1075
* Tests whether this client is connected or not to a server.
1076
*
1077
* @return <code>true</code> if the client is connected.
1078
*/
1079
public boolean isConnected() {
1080
return server != null;
1081
}
1082
1083
public SocketAddress getServerAddress() {
1084
return server == null ? null : server.getRemoteSocketAddress();
1085
}
1086
1087
public sun.net.ftp.FtpClient connect(SocketAddress dest) throws sun.net.ftp.FtpProtocolException, IOException {
1088
return connect(dest, -1);
1089
}
1090
1091
/**
1092
* Connects the FtpClient to the specified destination.
1093
*
1094
* @param dest the address of the destination server
1095
* @throws IOException if connection failed.
1096
*/
1097
public sun.net.ftp.FtpClient connect(SocketAddress dest, int timeout) throws sun.net.ftp.FtpProtocolException, IOException {
1098
if (!(dest instanceof InetSocketAddress)) {
1099
throw new IllegalArgumentException("Wrong address type");
1100
}
1101
serverAddr = (InetSocketAddress) dest;
1102
tryConnect(serverAddr, timeout);
1103
if (!readReply()) {
1104
throw new sun.net.ftp.FtpProtocolException("Welcome message: " +
1105
getResponseString(), lastReplyCode);
1106
}
1107
welcomeMsg = getResponseString().substring(4);
1108
return this;
1109
}
1110
1111
private void tryLogin(String user, char[] password) throws sun.net.ftp.FtpProtocolException, IOException {
1112
issueCommandCheck("USER " + user);
1113
1114
/*
1115
* Checks for "331 User name okay, need password." answer
1116
*/
1117
if (lastReplyCode == FtpReplyCode.NEED_PASSWORD) {
1118
if ((password != null) && (password.length > 0)) {
1119
issueCommandCheck("PASS " + String.valueOf(password));
1120
}
1121
}
1122
}
1123
1124
/**
1125
* Attempts to log on the server with the specified user name and password.
1126
*
1127
* @param user The user name
1128
* @param password The password for that user
1129
* @return <code>true</code> if the login was successful.
1130
* @throws IOException if an error occurred during the transmission
1131
*/
1132
public sun.net.ftp.FtpClient login(String user, char[] password) throws sun.net.ftp.FtpProtocolException, IOException {
1133
if (!isConnected()) {
1134
throw new sun.net.ftp.FtpProtocolException("Not connected yet", FtpReplyCode.BAD_SEQUENCE);
1135
}
1136
if (user == null || user.isEmpty()) {
1137
throw new IllegalArgumentException("User name can't be null or empty");
1138
}
1139
tryLogin(user, password);
1140
1141
// keep the welcome message around so we can
1142
// put it in the resulting HTML page.
1143
String l;
1144
StringBuilder sb = new StringBuilder();
1145
for (int i = 0; i < serverResponse.size(); i++) {
1146
l = serverResponse.elementAt(i);
1147
if (l != null) {
1148
if (l.length() >= 4 && l.startsWith("230")) {
1149
// get rid of the "230-" prefix
1150
l = l.substring(4);
1151
}
1152
sb.append(l);
1153
}
1154
}
1155
welcomeMsg = sb.toString();
1156
loggedIn = true;
1157
return this;
1158
}
1159
1160
/**
1161
* Attempts to log on the server with the specified user name, password and
1162
* account name.
1163
*
1164
* @param user The user name
1165
* @param password The password for that user.
1166
* @param account The account name for that user.
1167
* @return <code>true</code> if the login was successful.
1168
* @throws IOException if an error occurs during the transmission.
1169
*/
1170
public sun.net.ftp.FtpClient login(String user, char[] password, String account) throws sun.net.ftp.FtpProtocolException, IOException {
1171
1172
if (!isConnected()) {
1173
throw new sun.net.ftp.FtpProtocolException("Not connected yet", FtpReplyCode.BAD_SEQUENCE);
1174
}
1175
if (user == null || user.isEmpty()) {
1176
throw new IllegalArgumentException("User name can't be null or empty");
1177
}
1178
tryLogin(user, password);
1179
1180
/*
1181
* Checks for "332 Need account for login." answer
1182
*/
1183
if (lastReplyCode == FtpReplyCode.NEED_ACCOUNT) {
1184
issueCommandCheck("ACCT " + account);
1185
}
1186
1187
// keep the welcome message around so we can
1188
// put it in the resulting HTML page.
1189
StringBuilder sb = new StringBuilder();
1190
if (serverResponse != null) {
1191
for (String l : serverResponse) {
1192
if (l != null) {
1193
if (l.length() >= 4 && l.startsWith("230")) {
1194
// get rid of the "230-" prefix
1195
l = l.substring(4);
1196
}
1197
sb.append(l);
1198
}
1199
}
1200
}
1201
welcomeMsg = sb.toString();
1202
loggedIn = true;
1203
return this;
1204
}
1205
1206
/**
1207
* Logs out the current user. This is in effect terminates the current
1208
* session and the connection to the server will be closed.
1209
*
1210
*/
1211
public void close() throws IOException {
1212
if (isConnected()) {
1213
try {
1214
issueCommand("QUIT");
1215
} catch (FtpProtocolException e) {
1216
}
1217
loggedIn = false;
1218
}
1219
disconnect();
1220
}
1221
1222
/**
1223
* Checks whether the client is logged in to the server or not.
1224
*
1225
* @return <code>true</code> if the client has already completed a login.
1226
*/
1227
public boolean isLoggedIn() {
1228
return loggedIn;
1229
}
1230
1231
/**
1232
* Changes to a specific directory on a remote FTP server
1233
*
1234
* @param remoteDirectory path of the directory to CD to.
1235
* @return <code>true</code> if the operation was successful.
1236
* @exception <code>FtpProtocolException</code>
1237
*/
1238
public sun.net.ftp.FtpClient changeDirectory(String remoteDirectory) throws sun.net.ftp.FtpProtocolException, IOException {
1239
if (remoteDirectory == null || remoteDirectory.isEmpty()) {
1240
throw new IllegalArgumentException("directory can't be null or empty");
1241
}
1242
1243
issueCommandCheck("CWD " + remoteDirectory);
1244
return this;
1245
}
1246
1247
/**
1248
* Changes to the parent directory, sending the CDUP command to the server.
1249
*
1250
* @return <code>true</code> if the command was successful.
1251
* @throws IOException
1252
*/
1253
public sun.net.ftp.FtpClient changeToParentDirectory() throws sun.net.ftp.FtpProtocolException, IOException {
1254
issueCommandCheck("CDUP");
1255
return this;
1256
}
1257
1258
/**
1259
* Returns the server current working directory, or <code>null</code> if
1260
* the PWD command failed.
1261
*
1262
* @return a <code>String</code> containing the current working directory,
1263
* or <code>null</code>
1264
* @throws IOException
1265
*/
1266
public String getWorkingDirectory() throws sun.net.ftp.FtpProtocolException, IOException {
1267
issueCommandCheck("PWD");
1268
/*
1269
* answer will be of the following format :
1270
*
1271
* 257 "/" is current directory.
1272
*/
1273
String answ = getResponseString();
1274
if (!answ.startsWith("257")) {
1275
return null;
1276
}
1277
return answ.substring(5, answ.lastIndexOf('"'));
1278
}
1279
1280
/**
1281
* Sets the restart offset to the specified value. That value will be
1282
* sent through a <code>REST</code> command to server before a file
1283
* transfer and has the effect of resuming a file transfer from the
1284
* specified point. After a transfer the restart offset is set back to
1285
* zero.
1286
*
1287
* @param offset the offset in the remote file at which to start the next
1288
* transfer. This must be a value greater than or equal to zero.
1289
* @throws IllegalArgumentException if the offset is negative.
1290
*/
1291
public sun.net.ftp.FtpClient setRestartOffset(long offset) {
1292
if (offset < 0) {
1293
throw new IllegalArgumentException("offset can't be negative");
1294
}
1295
restartOffset = offset;
1296
return this;
1297
}
1298
1299
/**
1300
* Retrieves a file from the ftp server and writes it to the specified
1301
* <code>OutputStream</code>.
1302
* If the restart offset was set, then a <code>REST</code> command will be
1303
* sent before the RETR in order to restart the tranfer from the specified
1304
* offset.
1305
* The <code>OutputStream</code> is not closed by this method at the end
1306
* of the transfer.
1307
*
1308
* @param name a {@code String} containing the name of the file to
1309
* retreive from the server.
1310
* @param local the <code>OutputStream</code> the file should be written to.
1311
* @throws IOException if the transfer fails.
1312
*/
1313
public sun.net.ftp.FtpClient getFile(String name, OutputStream local) throws sun.net.ftp.FtpProtocolException, IOException {
1314
if (restartOffset > 0) {
1315
Socket s;
1316
try {
1317
s = openDataConnection("REST " + restartOffset);
1318
} finally {
1319
restartOffset = 0;
1320
}
1321
issueCommandCheck("RETR " + name);
1322
getTransferSize();
1323
try (InputStream remote = createInputStream(s.getInputStream())) {
1324
remote.transferTo(local);
1325
}
1326
} else {
1327
Socket s = openDataConnection("RETR " + name);
1328
getTransferSize();
1329
try (InputStream remote = createInputStream(s.getInputStream())) {
1330
remote.transferTo(local);
1331
}
1332
}
1333
return completePending();
1334
}
1335
1336
/**
1337
* Retrieves a file from the ftp server, using the RETR command, and
1338
* returns the InputStream from* the established data connection.
1339
* {@link #completePending()} <b>has</b> to be called once the application
1340
* is done reading from the returned stream.
1341
*
1342
* @param name the name of the remote file
1343
* @return the {@link java.io.InputStream} from the data connection, or
1344
* <code>null</code> if the command was unsuccessful.
1345
* @throws IOException if an error occurred during the transmission.
1346
*/
1347
public InputStream getFileStream(String name) throws sun.net.ftp.FtpProtocolException, IOException {
1348
Socket s;
1349
if (restartOffset > 0) {
1350
try {
1351
s = openDataConnection("REST " + restartOffset);
1352
} finally {
1353
restartOffset = 0;
1354
}
1355
if (s == null) {
1356
return null;
1357
}
1358
issueCommandCheck("RETR " + name);
1359
getTransferSize();
1360
return createInputStream(s.getInputStream());
1361
}
1362
1363
s = openDataConnection("RETR " + name);
1364
if (s == null) {
1365
return null;
1366
}
1367
getTransferSize();
1368
return createInputStream(s.getInputStream());
1369
}
1370
1371
/**
1372
* Transfers a file from the client to the server (aka a <I>put</I>)
1373
* by sending the STOR or STOU command, depending on the
1374
* <code>unique</code> argument, and returns the <code>OutputStream</code>
1375
* from the established data connection.
1376
* {@link #completePending()} <b>has</b> to be called once the application
1377
* is finished writing to the stream.
1378
*
1379
* A new file is created at the server site if the file specified does
1380
* not already exist.
1381
*
1382
* If <code>unique</code> is set to <code>true</code>, the resultant file
1383
* is to be created under a name unique to that directory, meaning
1384
* it will not overwrite an existing file, instead the server will
1385
* generate a new, unique, file name.
1386
* The name of the remote file can be retrieved, after completion of the
1387
* transfer, by calling {@link #getLastFileName()}.
1388
*
1389
* @param name the name of the remote file to write.
1390
* @param unique <code>true</code> if the remote files should be unique,
1391
* in which case the STOU command will be used.
1392
* @return the {@link java.io.OutputStream} from the data connection or
1393
* <code>null</code> if the command was unsuccessful.
1394
* @throws IOException if an error occurred during the transmission.
1395
*/
1396
public OutputStream putFileStream(String name, boolean unique)
1397
throws sun.net.ftp.FtpProtocolException, IOException
1398
{
1399
String cmd = unique ? "STOU " : "STOR ";
1400
Socket s = openDataConnection(cmd + name);
1401
if (s == null) {
1402
return null;
1403
}
1404
boolean bm = (type == TransferType.BINARY);
1405
return new sun.net.TelnetOutputStream(s.getOutputStream(), bm);
1406
}
1407
1408
/**
1409
* Transfers a file from the client to the server (aka a <I>put</I>)
1410
* by sending the STOR command. The content of the <code>InputStream</code>
1411
* passed in argument is written into the remote file, overwriting any
1412
* existing data.
1413
*
1414
* A new file is created at the server site if the file specified does
1415
* not already exist.
1416
*
1417
* @param name the name of the remote file to write.
1418
* @param local the <code>InputStream</code> that points to the data to
1419
* transfer.
1420
* @param unique <code>true</code> if the remote file should be unique
1421
* (i.e. not already existing), <code>false</code> otherwise.
1422
* @return <code>true</code> if the transfer was successful.
1423
* @throws IOException if an error occurred during the transmission.
1424
* @see #getLastFileName()
1425
*/
1426
public sun.net.ftp.FtpClient putFile(String name, InputStream local, boolean unique) throws sun.net.ftp.FtpProtocolException, IOException {
1427
String cmd = unique ? "STOU " : "STOR ";
1428
if (type == TransferType.BINARY) {
1429
Socket s = openDataConnection(cmd + name);
1430
try (OutputStream remote = createOutputStream(s.getOutputStream())) {
1431
local.transferTo(remote);
1432
}
1433
}
1434
return completePending();
1435
}
1436
1437
/**
1438
* Sends the APPE command to the server in order to transfer a data stream
1439
* passed in argument and append it to the content of the specified remote
1440
* file.
1441
*
1442
* @param name A <code>String</code> containing the name of the remote file
1443
* to append to.
1444
* @param local The <code>InputStream</code> providing access to the data
1445
* to be appended.
1446
* @return <code>true</code> if the transfer was successful.
1447
* @throws IOException if an error occurred during the transmission.
1448
*/
1449
public sun.net.ftp.FtpClient appendFile(String name, InputStream local) throws sun.net.ftp.FtpProtocolException, IOException {
1450
Socket s = openDataConnection("APPE " + name);
1451
try (OutputStream remote = createOutputStream(s.getOutputStream())) {
1452
local.transferTo(remote);
1453
}
1454
return completePending();
1455
}
1456
1457
/**
1458
* Renames a file on the server.
1459
*
1460
* @param from the name of the file being renamed
1461
* @param to the new name for the file
1462
* @throws IOException if the command fails
1463
*/
1464
public sun.net.ftp.FtpClient rename(String from, String to) throws sun.net.ftp.FtpProtocolException, IOException {
1465
issueCommandCheck("RNFR " + from);
1466
issueCommandCheck("RNTO " + to);
1467
return this;
1468
}
1469
1470
/**
1471
* Deletes a file on the server.
1472
*
1473
* @param name a <code>String</code> containing the name of the file
1474
* to delete.
1475
* @return <code>true</code> if the command was successful
1476
* @throws IOException if an error occurred during the exchange
1477
*/
1478
public sun.net.ftp.FtpClient deleteFile(String name) throws sun.net.ftp.FtpProtocolException, IOException {
1479
issueCommandCheck("DELE " + name);
1480
return this;
1481
}
1482
1483
/**
1484
* Creates a new directory on the server.
1485
*
1486
* @param name a <code>String</code> containing the name of the directory
1487
* to create.
1488
* @return <code>true</code> if the operation was successful.
1489
* @throws IOException if an error occurred during the exchange
1490
*/
1491
public sun.net.ftp.FtpClient makeDirectory(String name) throws sun.net.ftp.FtpProtocolException, IOException {
1492
issueCommandCheck("MKD " + name);
1493
return this;
1494
}
1495
1496
/**
1497
* Removes a directory on the server.
1498
*
1499
* @param name a <code>String</code> containing the name of the directory
1500
* to remove.
1501
*
1502
* @return <code>true</code> if the operation was successful.
1503
* @throws IOException if an error occurred during the exchange.
1504
*/
1505
public sun.net.ftp.FtpClient removeDirectory(String name) throws sun.net.ftp.FtpProtocolException, IOException {
1506
issueCommandCheck("RMD " + name);
1507
return this;
1508
}
1509
1510
/**
1511
* Sends a No-operation command. It's useful for testing the connection
1512
* status or as a <I>keep alive</I> mechanism.
1513
*
1514
* @throws FtpProtocolException if the command fails
1515
*/
1516
public sun.net.ftp.FtpClient noop() throws sun.net.ftp.FtpProtocolException, IOException {
1517
issueCommandCheck("NOOP");
1518
return this;
1519
}
1520
1521
/**
1522
* Sends the STAT command to the server.
1523
* This can be used while a data connection is open to get a status
1524
* on the current transfer, in that case the parameter should be
1525
* <code>null</code>.
1526
* If used between file transfers, it may have a pathname as argument
1527
* in which case it will work as the LIST command except no data
1528
* connection will be created.
1529
*
1530
* @param name an optional <code>String</code> containing the pathname
1531
* the STAT command should apply to.
1532
* @return the response from the server or <code>null</code> if the
1533
* command failed.
1534
* @throws IOException if an error occurred during the transmission.
1535
*/
1536
public String getStatus(String name) throws sun.net.ftp.FtpProtocolException, IOException {
1537
issueCommandCheck((name == null ? "STAT" : "STAT " + name));
1538
/*
1539
* A typical response will be:
1540
* 213-status of t32.gif:
1541
* -rw-r--r-- 1 jcc staff 247445 Feb 17 1998 t32.gif
1542
* 213 End of Status
1543
*
1544
* or
1545
*
1546
* 211-jsn FTP server status:
1547
* Version wu-2.6.2+Sun
1548
* Connected to localhost (::1)
1549
* Logged in as jccollet
1550
* TYPE: ASCII, FORM: Nonprint; STRUcture: File; transfer MODE: Stream
1551
* No data connection
1552
* 0 data bytes received in 0 files
1553
* 0 data bytes transmitted in 0 files
1554
* 0 data bytes total in 0 files
1555
* 53 traffic bytes received in 0 transfers
1556
* 485 traffic bytes transmitted in 0 transfers
1557
* 587 traffic bytes total in 0 transfers
1558
* 211 End of status
1559
*
1560
* So we need to remove the 1st and last line
1561
*/
1562
Vector<String> resp = getResponseStrings();
1563
StringBuilder sb = new StringBuilder();
1564
for (int i = 1; i < resp.size() - 1; i++) {
1565
sb.append(resp.get(i));
1566
}
1567
return sb.toString();
1568
}
1569
1570
/**
1571
* Sends the FEAT command to the server and returns the list of supported
1572
* features in the form of strings.
1573
*
1574
* The features are the supported commands, like AUTH TLS, PROT or PASV.
1575
* See the RFCs for a complete list.
1576
*
1577
* Note that not all FTP servers support that command, in which case
1578
* the method will return <code>null</code>
1579
*
1580
* @return a <code>List</code> of <code>Strings</code> describing the
1581
* supported additional features, or <code>null</code>
1582
* if the command is not supported.
1583
* @throws IOException if an error occurs during the transmission.
1584
*/
1585
public List<String> getFeatures() throws sun.net.ftp.FtpProtocolException, IOException {
1586
/*
1587
* The FEAT command, when implemented will return something like:
1588
*
1589
* 211-Features:
1590
* AUTH TLS
1591
* PBSZ
1592
* PROT
1593
* EPSV
1594
* EPRT
1595
* PASV
1596
* REST STREAM
1597
* 211 END
1598
*/
1599
ArrayList<String> features = new ArrayList<String>();
1600
issueCommandCheck("FEAT");
1601
Vector<String> resp = getResponseStrings();
1602
// Note that we start at index 1 to skip the 1st line (211-...)
1603
// and we stop before the last line.
1604
for (int i = 1; i < resp.size() - 1; i++) {
1605
String s = resp.get(i);
1606
// Get rid of leading space and trailing newline
1607
features.add(s.substring(1, s.length() - 1));
1608
}
1609
return features;
1610
}
1611
1612
/**
1613
* sends the ABOR command to the server.
1614
* It tells the server to stop the previous command or transfer.
1615
*
1616
* @return <code>true</code> if the command was successful.
1617
* @throws IOException if an error occurred during the transmission.
1618
*/
1619
public sun.net.ftp.FtpClient abort() throws sun.net.ftp.FtpProtocolException, IOException {
1620
issueCommandCheck("ABOR");
1621
// TODO: Must check the ReplyCode:
1622
/*
1623
* From the RFC:
1624
* There are two cases for the server upon receipt of this
1625
* command: (1) the FTP service command was already completed,
1626
* or (2) the FTP service command is still in progress.
1627
* In the first case, the server closes the data connection
1628
* (if it is open) and responds with a 226 reply, indicating
1629
* that the abort command was successfully processed.
1630
* In the second case, the server aborts the FTP service in
1631
* progress and closes the data connection, returning a 426
1632
* reply to indicate that the service request terminated
1633
* abnormally. The server then sends a 226 reply,
1634
* indicating that the abort command was successfully
1635
* processed.
1636
*/
1637
1638
1639
return this;
1640
}
1641
1642
/**
1643
* Some methods do not wait until completion before returning, so this
1644
* method can be called to wait until completion. This is typically the case
1645
* with commands that trigger a transfer like {@link #getFileStream(String)}.
1646
* So this method should be called before accessing information related to
1647
* such a command.
1648
* <p>This method will actually block reading on the command channel for a
1649
* notification from the server that the command is finished. Such a
1650
* notification often carries extra information concerning the completion
1651
* of the pending action (e.g. number of bytes transfered).</p>
1652
* <p>Note that this will return true immediately if no command or action
1653
* is pending</p>
1654
* <p>It should be also noted that most methods issuing commands to the ftp
1655
* server will call this method if a previous command is pending.
1656
* <p>Example of use:
1657
* <pre>
1658
* InputStream in = cl.getFileStream("file");
1659
* ...
1660
* cl.completePending();
1661
* long size = cl.getLastTransferSize();
1662
* </pre>
1663
* On the other hand, it's not necessary in a case like:
1664
* <pre>
1665
* InputStream in = cl.getFileStream("file");
1666
* // read content
1667
* ...
1668
* cl.logout();
1669
* </pre>
1670
* <p>Since {@link #logout()} will call completePending() if necessary.</p>
1671
* @return <code>true</code> if the completion was successful or if no
1672
* action was pending.
1673
* @throws IOException
1674
*/
1675
public sun.net.ftp.FtpClient completePending() throws sun.net.ftp.FtpProtocolException, IOException {
1676
while (replyPending) {
1677
replyPending = false;
1678
if (!readReply()) {
1679
throw new sun.net.ftp.FtpProtocolException(getLastResponseString(), lastReplyCode);
1680
}
1681
}
1682
return this;
1683
}
1684
1685
/**
1686
* Reinitializes the USER parameters on the FTP server
1687
*
1688
* @throws FtpProtocolException if the command fails
1689
*/
1690
public sun.net.ftp.FtpClient reInit() throws sun.net.ftp.FtpProtocolException, IOException {
1691
issueCommandCheck("REIN");
1692
loggedIn = false;
1693
if (useCrypto) {
1694
if (server instanceof SSLSocket) {
1695
javax.net.ssl.SSLSession session = ((SSLSocket) server).getSession();
1696
session.invalidate();
1697
// Restore previous socket and streams
1698
server = oldSocket;
1699
oldSocket = null;
1700
try {
1701
out = new PrintStream(new BufferedOutputStream(server.getOutputStream()),
1702
true, encoding);
1703
} catch (UnsupportedEncodingException e) {
1704
throw new InternalError(encoding + "encoding not found", e);
1705
}
1706
in = new BufferedInputStream(server.getInputStream());
1707
}
1708
}
1709
useCrypto = false;
1710
return this;
1711
}
1712
1713
/**
1714
* Changes the transfer type (binary, ascii, ebcdic) and issue the
1715
* proper command (e.g. TYPE A) to the server.
1716
*
1717
* @param type the <code>FtpTransferType</code> to use.
1718
* @return This FtpClient
1719
* @throws IOException if an error occurs during transmission.
1720
*/
1721
public sun.net.ftp.FtpClient setType(TransferType type) throws sun.net.ftp.FtpProtocolException, IOException {
1722
String cmd = "NOOP";
1723
1724
this.type = type;
1725
if (type == TransferType.ASCII) {
1726
cmd = "TYPE A";
1727
}
1728
if (type == TransferType.BINARY) {
1729
cmd = "TYPE I";
1730
}
1731
if (type == TransferType.EBCDIC) {
1732
cmd = "TYPE E";
1733
}
1734
issueCommandCheck(cmd);
1735
return this;
1736
}
1737
1738
/**
1739
* Issues a LIST command to the server to get the current directory
1740
* listing, and returns the InputStream from the data connection.
1741
* {@link #completePending()} <b>has</b> to be called once the application
1742
* is finished writing to the stream.
1743
*
1744
* @param path the pathname of the directory to list, or <code>null</code>
1745
* for the current working directory.
1746
* @return the <code>InputStream</code> from the resulting data connection
1747
* @throws IOException if an error occurs during the transmission.
1748
* @see #changeDirectory(String)
1749
* @see #listFiles(String)
1750
*/
1751
public InputStream list(String path) throws sun.net.ftp.FtpProtocolException, IOException {
1752
Socket s;
1753
s = openDataConnection(path == null ? "LIST" : "LIST " + path);
1754
if (s != null) {
1755
return createInputStream(s.getInputStream());
1756
}
1757
return null;
1758
}
1759
1760
/**
1761
* Issues a NLST path command to server to get the specified directory
1762
* content. It differs from {@link #list(String)} method by the fact that
1763
* it will only list the file names which would make the parsing of the
1764
* somewhat easier.
1765
*
1766
* {@link #completePending()} <b>has</b> to be called once the application
1767
* is finished writing to the stream.
1768
*
1769
* @param path a <code>String</code> containing the pathname of the
1770
* directory to list or <code>null</code> for the current working
1771
* directory.
1772
* @return the <code>InputStream</code> from the resulting data connection
1773
* @throws IOException if an error occurs during the transmission.
1774
*/
1775
public InputStream nameList(String path) throws sun.net.ftp.FtpProtocolException, IOException {
1776
Socket s;
1777
s = openDataConnection(path == null ? "NLST" : "NLST " + path);
1778
if (s != null) {
1779
return createInputStream(s.getInputStream());
1780
}
1781
return null;
1782
}
1783
1784
/**
1785
* Issues the SIZE [path] command to the server to get the size of a
1786
* specific file on the server.
1787
* Note that this command may not be supported by the server. In which
1788
* case -1 will be returned.
1789
*
1790
* @param path a <code>String</code> containing the pathname of the
1791
* file.
1792
* @return a <code>long</code> containing the size of the file or -1 if
1793
* the server returned an error, which can be checked with
1794
* {@link #getLastReplyCode()}.
1795
* @throws IOException if an error occurs during the transmission.
1796
*/
1797
public long getSize(String path) throws sun.net.ftp.FtpProtocolException, IOException {
1798
if (path == null || path.isEmpty()) {
1799
throw new IllegalArgumentException("path can't be null or empty");
1800
}
1801
issueCommandCheck("SIZE " + path);
1802
if (lastReplyCode == FtpReplyCode.FILE_STATUS) {
1803
String s = getResponseString();
1804
s = s.substring(4, s.length() - 1);
1805
return Long.parseLong(s);
1806
}
1807
return -1;
1808
}
1809
1810
private static final DateTimeFormatter RFC3659_DATETIME_FORMAT = DateTimeFormatter.ofPattern("yyyyMMddHHmmss[.SSS]")
1811
.withZone(ZoneOffset.UTC);
1812
1813
/**
1814
* Issues the MDTM [path] command to the server to get the modification
1815
* time of a specific file on the server.
1816
* Note that this command may not be supported by the server, in which
1817
* case <code>null</code> will be returned.
1818
*
1819
* @param path a <code>String</code> containing the pathname of the file.
1820
* @return a <code>Date</code> representing the last modification time
1821
* or <code>null</code> if the server returned an error, which
1822
* can be checked with {@link #getLastReplyCode()}.
1823
* @throws IOException if an error occurs during the transmission.
1824
*/
1825
public Date getLastModified(String path) throws sun.net.ftp.FtpProtocolException, IOException {
1826
issueCommandCheck("MDTM " + path);
1827
if (lastReplyCode == FtpReplyCode.FILE_STATUS) {
1828
String s = getResponseString();
1829
return parseRfc3659TimeValue(s.substring(4, s.length() - 1));
1830
}
1831
return null;
1832
}
1833
1834
private static Date parseRfc3659TimeValue(String s) {
1835
Date result = null;
1836
try {
1837
var d = ZonedDateTime.parse(s, RFC3659_DATETIME_FORMAT);
1838
result = Date.from(d.toInstant());
1839
} catch (DateTimeParseException ex) {
1840
}
1841
return result;
1842
}
1843
1844
/**
1845
* Sets the parser used to handle the directory output to the specified
1846
* one. By default the parser is set to one that can handle most FTP
1847
* servers output (Unix base mostly). However it may be necessary for
1848
* and application to provide its own parser due to some uncommon
1849
* output format.
1850
*
1851
* @param p The <code>FtpDirParser</code> to use.
1852
* @see #listFiles(String)
1853
*/
1854
public sun.net.ftp.FtpClient setDirParser(FtpDirParser p) {
1855
parser = p;
1856
return this;
1857
}
1858
1859
private static class FtpFileIterator implements Iterator<FtpDirEntry>, Closeable {
1860
1861
private BufferedReader in = null;
1862
private FtpDirEntry nextFile = null;
1863
private FtpDirParser fparser = null;
1864
private boolean eof = false;
1865
1866
public FtpFileIterator(FtpDirParser p, BufferedReader in) {
1867
this.in = in;
1868
this.fparser = p;
1869
readNext();
1870
}
1871
1872
private void readNext() {
1873
nextFile = null;
1874
if (eof) {
1875
return;
1876
}
1877
String line = null;
1878
try {
1879
do {
1880
line = in.readLine();
1881
if (line != null) {
1882
nextFile = fparser.parseLine(line);
1883
if (nextFile != null) {
1884
return;
1885
}
1886
}
1887
} while (line != null);
1888
in.close();
1889
} catch (IOException iOException) {
1890
}
1891
eof = true;
1892
}
1893
1894
public boolean hasNext() {
1895
return nextFile != null;
1896
}
1897
1898
public FtpDirEntry next() {
1899
FtpDirEntry ret = nextFile;
1900
readNext();
1901
return ret;
1902
}
1903
1904
public void remove() {
1905
throw new UnsupportedOperationException("Not supported yet.");
1906
}
1907
1908
public void close() throws IOException {
1909
if (in != null && !eof) {
1910
in.close();
1911
}
1912
eof = true;
1913
nextFile = null;
1914
}
1915
}
1916
1917
/**
1918
* Issues a MLSD command to the server to get the specified directory
1919
* listing and applies the current parser to create an Iterator of
1920
* {@link java.net.ftp.FtpDirEntry}. Note that the Iterator returned is also a
1921
* {@link java.io.Closeable}.
1922
* If the server doesn't support the MLSD command, the LIST command is used
1923
* instead.
1924
*
1925
* {@link #completePending()} <b>has</b> to be called once the application
1926
* is finished iterating through the files.
1927
*
1928
* @param path the pathname of the directory to list or <code>null</code>
1929
* for the current working directoty.
1930
* @return a <code>Iterator</code> of files or <code>null</code> if the
1931
* command failed.
1932
* @throws IOException if an error occurred during the transmission
1933
* @see #setDirParser(FtpDirParser)
1934
* @see #changeDirectory(String)
1935
*/
1936
public Iterator<FtpDirEntry> listFiles(String path) throws sun.net.ftp.FtpProtocolException, IOException {
1937
Socket s = null;
1938
BufferedReader sin = null;
1939
try {
1940
s = openDataConnection(path == null ? "MLSD" : "MLSD " + path);
1941
} catch (sun.net.ftp.FtpProtocolException FtpException) {
1942
// The server doesn't understand new MLSD command, ignore and fall
1943
// back to LIST
1944
}
1945
1946
if (s != null) {
1947
sin = new BufferedReader(new InputStreamReader(s.getInputStream()));
1948
return new FtpFileIterator(mlsxParser, sin);
1949
} else {
1950
s = openDataConnection(path == null ? "LIST" : "LIST " + path);
1951
if (s != null) {
1952
sin = new BufferedReader(new InputStreamReader(s.getInputStream()));
1953
return new FtpFileIterator(parser, sin);
1954
}
1955
}
1956
return null;
1957
}
1958
1959
private boolean sendSecurityData(byte[] buf) throws IOException,
1960
sun.net.ftp.FtpProtocolException {
1961
String s = Base64.getMimeEncoder().encodeToString(buf);
1962
return issueCommand("ADAT " + s);
1963
}
1964
1965
private byte[] getSecurityData() {
1966
String s = getLastResponseString();
1967
if (s.substring(4, 9).equalsIgnoreCase("ADAT=")) {
1968
// Need to get rid of the leading '315 ADAT='
1969
// and the trailing newline
1970
return Base64.getMimeDecoder().decode(s.substring(9, s.length() - 1));
1971
}
1972
return null;
1973
}
1974
1975
/**
1976
* Attempts to use Kerberos GSSAPI as an authentication mechanism with the
1977
* ftp server. This will issue an <code>AUTH GSSAPI</code> command, and if
1978
* it is accepted by the server, will followup with <code>ADAT</code>
1979
* command to exchange the various tokens until authentification is
1980
* successful. This conforms to Appendix I of RFC 2228.
1981
*
1982
* @return <code>true</code> if authentication was successful.
1983
* @throws IOException if an error occurs during the transmission.
1984
*/
1985
public sun.net.ftp.FtpClient useKerberos() throws sun.net.ftp.FtpProtocolException, IOException {
1986
/*
1987
* Comment out for the moment since it's not in use and would create
1988
* needless cross-package links.
1989
*
1990
issueCommandCheck("AUTH GSSAPI");
1991
if (lastReplyCode != FtpReplyCode.NEED_ADAT)
1992
throw new sun.net.ftp.FtpProtocolException("Unexpected reply from server");
1993
try {
1994
GSSManager manager = GSSManager.getInstance();
1995
GSSName name = manager.createName("SERVICE:ftp@"+
1996
serverAddr.getHostName(), null);
1997
GSSContext context = manager.createContext(name, null, null,
1998
GSSContext.DEFAULT_LIFETIME);
1999
context.requestMutualAuth(true);
2000
context.requestReplayDet(true);
2001
context.requestSequenceDet(true);
2002
context.requestCredDeleg(true);
2003
byte []inToken = new byte[0];
2004
while (!context.isEstablished()) {
2005
byte[] outToken
2006
= context.initSecContext(inToken, 0, inToken.length);
2007
// send the output token if generated
2008
if (outToken != null) {
2009
if (sendSecurityData(outToken)) {
2010
inToken = getSecurityData();
2011
}
2012
}
2013
}
2014
loggedIn = true;
2015
} catch (GSSException e) {
2016
2017
}
2018
*/
2019
return this;
2020
}
2021
2022
/**
2023
* Returns the Welcome string the server sent during initial connection.
2024
*
2025
* @return a <code>String</code> containing the message the server
2026
* returned during connection or <code>null</code>.
2027
*/
2028
public String getWelcomeMsg() {
2029
return welcomeMsg;
2030
}
2031
2032
/**
2033
* Returns the last reply code sent by the server.
2034
*
2035
* @return the lastReplyCode
2036
*/
2037
public FtpReplyCode getLastReplyCode() {
2038
return lastReplyCode;
2039
}
2040
2041
/**
2042
* Returns the last response string sent by the server.
2043
*
2044
* @return the message string, which can be quite long, last returned
2045
* by the server.
2046
*/
2047
public String getLastResponseString() {
2048
StringBuilder sb = new StringBuilder();
2049
if (serverResponse != null) {
2050
for (String l : serverResponse) {
2051
if (l != null) {
2052
sb.append(l);
2053
}
2054
}
2055
}
2056
return sb.toString();
2057
}
2058
2059
/**
2060
* Returns, when available, the size of the latest started transfer.
2061
* This is retreived by parsing the response string received as an initial
2062
* response to a RETR or similar request.
2063
*
2064
* @return the size of the latest transfer or -1 if either there was no
2065
* transfer or the information was unavailable.
2066
*/
2067
public long getLastTransferSize() {
2068
return lastTransSize;
2069
}
2070
2071
/**
2072
* Returns, when available, the remote name of the last transfered file.
2073
* This is mainly useful for "put" operation when the unique flag was
2074
* set since it allows to recover the unique file name created on the
2075
* server which may be different from the one submitted with the command.
2076
*
2077
* @return the name the latest transfered file remote name, or
2078
* <code>null</code> if that information is unavailable.
2079
*/
2080
public String getLastFileName() {
2081
return lastFileName;
2082
}
2083
2084
/**
2085
* Attempts to switch to a secure, encrypted connection. This is done by
2086
* sending the "AUTH TLS" command.
2087
* <p>See <a href="http://www.ietf.org/rfc/rfc4217.txt">RFC 4217</a></p>
2088
* If successful this will establish a secure command channel with the
2089
* server, it will also make it so that all other transfers (e.g. a RETR
2090
* command) will be done over an encrypted channel as well unless a
2091
* {@link #reInit()} command or a {@link #endSecureSession()} command is issued.
2092
*
2093
* @return <code>true</code> if the operation was successful.
2094
* @throws IOException if an error occurred during the transmission.
2095
* @see #endSecureSession()
2096
*/
2097
public sun.net.ftp.FtpClient startSecureSession() throws sun.net.ftp.FtpProtocolException, IOException {
2098
if (!isConnected()) {
2099
throw new sun.net.ftp.FtpProtocolException("Not connected yet", FtpReplyCode.BAD_SEQUENCE);
2100
}
2101
if (sslFact == null) {
2102
try {
2103
sslFact = (SSLSocketFactory) SSLSocketFactory.getDefault();
2104
} catch (Exception e) {
2105
throw new IOException(e.getLocalizedMessage());
2106
}
2107
}
2108
issueCommandCheck("AUTH TLS");
2109
Socket s = null;
2110
try {
2111
s = sslFact.createSocket(server, serverAddr.getHostName(), serverAddr.getPort(), true);
2112
} catch (javax.net.ssl.SSLException ssle) {
2113
try {
2114
disconnect();
2115
} catch (Exception e) {
2116
}
2117
throw ssle;
2118
}
2119
// Remember underlying socket so we can restore it later
2120
oldSocket = server;
2121
server = s;
2122
try {
2123
out = new PrintStream(new BufferedOutputStream(server.getOutputStream()),
2124
true, encoding);
2125
} catch (UnsupportedEncodingException e) {
2126
throw new InternalError(encoding + "encoding not found", e);
2127
}
2128
in = new BufferedInputStream(server.getInputStream());
2129
2130
issueCommandCheck("PBSZ 0");
2131
issueCommandCheck("PROT P");
2132
useCrypto = true;
2133
return this;
2134
}
2135
2136
/**
2137
* Sends a <code>CCC</code> command followed by a <code>PROT C</code>
2138
* command to the server terminating an encrypted session and reverting
2139
* back to a non crypted transmission.
2140
*
2141
* @return <code>true</code> if the operation was successful.
2142
* @throws IOException if an error occurred during transmission.
2143
* @see #startSecureSession()
2144
*/
2145
public sun.net.ftp.FtpClient endSecureSession() throws sun.net.ftp.FtpProtocolException, IOException {
2146
if (!useCrypto) {
2147
return this;
2148
}
2149
2150
issueCommandCheck("CCC");
2151
issueCommandCheck("PROT C");
2152
useCrypto = false;
2153
// Restore previous socket and streams
2154
server = oldSocket;
2155
oldSocket = null;
2156
try {
2157
out = new PrintStream(new BufferedOutputStream(server.getOutputStream()),
2158
true, encoding);
2159
} catch (UnsupportedEncodingException e) {
2160
throw new InternalError(encoding + "encoding not found", e);
2161
}
2162
in = new BufferedInputStream(server.getInputStream());
2163
2164
return this;
2165
}
2166
2167
/**
2168
* Sends the "Allocate" (ALLO) command to the server telling it to
2169
* pre-allocate the specified number of bytes for the next transfer.
2170
*
2171
* @param size The number of bytes to allocate.
2172
* @return <code>true</code> if the operation was successful.
2173
* @throws IOException if an error occurred during the transmission.
2174
*/
2175
public sun.net.ftp.FtpClient allocate(long size) throws sun.net.ftp.FtpProtocolException, IOException {
2176
issueCommandCheck("ALLO " + size);
2177
return this;
2178
}
2179
2180
/**
2181
* Sends the "Structure Mount" (SMNT) command to the server. This let the
2182
* user mount a different file system data structure without altering his
2183
* login or accounting information.
2184
*
2185
* @param struct a <code>String</code> containing the name of the
2186
* structure to mount.
2187
* @return <code>true</code> if the operation was successful.
2188
* @throws IOException if an error occurred during the transmission.
2189
*/
2190
public sun.net.ftp.FtpClient structureMount(String struct) throws sun.net.ftp.FtpProtocolException, IOException {
2191
issueCommandCheck("SMNT " + struct);
2192
return this;
2193
}
2194
2195
/**
2196
* Sends a SYST (System) command to the server and returns the String
2197
* sent back by the server describing the operating system at the
2198
* server.
2199
*
2200
* @return a <code>String</code> describing the OS, or <code>null</code>
2201
* if the operation was not successful.
2202
* @throws IOException if an error occurred during the transmission.
2203
*/
2204
public String getSystem() throws sun.net.ftp.FtpProtocolException, IOException {
2205
issueCommandCheck("SYST");
2206
/*
2207
* 215 UNIX Type: L8 Version: SUNOS
2208
*/
2209
String resp = getResponseString();
2210
// Get rid of the leading code and blank
2211
return resp.substring(4);
2212
}
2213
2214
/**
2215
* Sends the HELP command to the server, with an optional command, like
2216
* SITE, and returns the text sent back by the server.
2217
*
2218
* @param cmd the command for which the help is requested or
2219
* <code>null</code> for the general help
2220
* @return a <code>String</code> containing the text sent back by the
2221
* server, or <code>null</code> if the command failed.
2222
* @throws IOException if an error occurred during transmission
2223
*/
2224
public String getHelp(String cmd) throws sun.net.ftp.FtpProtocolException, IOException {
2225
issueCommandCheck("HELP " + cmd);
2226
/**
2227
*
2228
* HELP
2229
* 214-The following commands are implemented.
2230
* USER EPRT STRU ALLO DELE SYST RMD MDTM ADAT
2231
* PASS EPSV MODE REST CWD STAT PWD PROT
2232
* QUIT LPRT RETR RNFR LIST HELP CDUP PBSZ
2233
* PORT LPSV STOR RNTO NLST NOOP STOU AUTH
2234
* PASV TYPE APPE ABOR SITE MKD SIZE CCC
2235
* 214 Direct comments to ftp-bugs@jsn.
2236
*
2237
* HELP SITE
2238
* 214-The following SITE commands are implemented.
2239
* UMASK HELP GROUPS
2240
* IDLE ALIAS CHECKMETHOD
2241
* CHMOD CDPATH CHECKSUM
2242
* 214 Direct comments to ftp-bugs@jsn.
2243
*/
2244
Vector<String> resp = getResponseStrings();
2245
if (resp.size() == 1) {
2246
// Single line response
2247
return resp.get(0).substring(4);
2248
}
2249
// on multiple lines answers, like the ones above, remove 1st and last
2250
// line, concat the others.
2251
StringBuilder sb = new StringBuilder();
2252
for (int i = 1; i < resp.size() - 1; i++) {
2253
sb.append(resp.get(i).substring(3));
2254
}
2255
return sb.toString();
2256
}
2257
2258
/**
2259
* Sends the SITE command to the server. This is used by the server
2260
* to provide services specific to his system that are essential
2261
* to file transfer.
2262
*
2263
* @param cmd the command to be sent.
2264
* @return <code>true</code> if the command was successful.
2265
* @throws IOException if an error occurred during transmission
2266
*/
2267
public sun.net.ftp.FtpClient siteCmd(String cmd) throws sun.net.ftp.FtpProtocolException, IOException {
2268
issueCommandCheck("SITE " + cmd);
2269
return this;
2270
}
2271
}
2272
2273