Skip to content

Commit 6545e19

Browse files
committed
8255078: sun/net/ftp/imp/FtpClient$MLSxParser uses wrong datetime format
Reviewed-by: dfuchs
1 parent 3f6abd2 commit 6545e19

File tree

2 files changed

+219
-31
lines changed

2 files changed

+219
-31
lines changed

src/java.base/share/classes/sun/net/ftp/impl/FtpClient.java

Lines changed: 27 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2009, 2017, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2009, 2020, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -289,9 +289,6 @@ public FtpDirEntry parseLine(String line) {
289289
}
290290

291291
private class MLSxParser implements FtpDirParser {
292-
293-
private SimpleDateFormat df = new SimpleDateFormat("yyyyMMddhhmmss");
294-
295292
public FtpDirEntry parseLine(String line) {
296293
String name = null;
297294
int i = line.lastIndexOf(';');
@@ -326,22 +323,14 @@ public FtpDirEntry parseLine(String line) {
326323
}
327324
s = file.getFact("Modify");
328325
if (s != null) {
329-
Date d = null;
330-
try {
331-
d = df.parse(s);
332-
} catch (ParseException ex) {
333-
}
326+
Date d = parseRfc3659TimeValue(s);
334327
if (d != null) {
335328
file.setLastModified(d);
336329
}
337330
}
338331
s = file.getFact("Create");
339332
if (s != null) {
340-
Date d = null;
341-
try {
342-
d = df.parse(s);
343-
} catch (ParseException ex) {
344-
}
333+
Date d = parseRfc3659TimeValue(s);
345334
if (d != null) {
346335
file.setCreated(d);
347336
}
@@ -1749,15 +1738,17 @@ public long getSize(String path) throws sun.net.ftp.FtpProtocolException, IOExce
17491738
}
17501739
return -1;
17511740
}
1752-
private static String[] MDTMformats = {
1753-
"yyyyMMddHHmmss.SSS",
1754-
"yyyyMMddHHmmss"
1755-
};
1756-
private static SimpleDateFormat[] dateFormats = new SimpleDateFormat[MDTMformats.length];
1741+
1742+
private static final SimpleDateFormat[] dateFormats;
17571743

17581744
static {
1759-
for (int i = 0; i < MDTMformats.length; i++) {
1760-
dateFormats[i] = new SimpleDateFormat(MDTMformats[i]);
1745+
String[] formats = {
1746+
"yyyyMMddHHmmss.SSS",
1747+
"yyyyMMddHHmmss"
1748+
};
1749+
dateFormats = new SimpleDateFormat[formats.length];
1750+
for (int i = 0; i < formats.length; ++i) {
1751+
dateFormats[i] = new SimpleDateFormat(formats[i]);
17611752
dateFormats[i].setTimeZone(TimeZone.getTimeZone("GMT"));
17621753
}
17631754
}
@@ -1778,20 +1769,25 @@ public Date getLastModified(String path) throws sun.net.ftp.FtpProtocolException
17781769
issueCommandCheck("MDTM " + path);
17791770
if (lastReplyCode == FtpReplyCode.FILE_STATUS) {
17801771
String s = getResponseString().substring(4);
1781-
Date d = null;
1782-
for (SimpleDateFormat dateFormat : dateFormats) {
1783-
try {
1784-
d = dateFormat.parse(s);
1785-
} catch (ParseException ex) {
1786-
}
1787-
if (d != null) {
1788-
return d;
1789-
}
1790-
}
1772+
return parseRfc3659TimeValue(s);
17911773
}
17921774
return null;
17931775
}
17941776

1777+
private static Date parseRfc3659TimeValue(String s) {
1778+
Date d = null;
1779+
for (SimpleDateFormat dateFormat : dateFormats) {
1780+
try {
1781+
d = dateFormat.parse(s);
1782+
} catch (ParseException ex) {
1783+
}
1784+
if (d != null) {
1785+
return d;
1786+
}
1787+
}
1788+
return d;
1789+
}
1790+
17951791
/**
17961792
* Sets the parser used to handle the directory output to the specified
17971793
* one. By default the parser is set to one that can handle most FTP
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
/*
2+
* Copyright (c) 2020, 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.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
/*
25+
* @test
26+
* @bug 8255078
27+
* @summary verify that datetime in MDTM and MLSD responses are properly parsed
28+
* @library /test/lib
29+
* @modules java.base/sun.net.ftp
30+
* @build jdk.test.lib.Asserts
31+
* @run main TestFtpTimeValue
32+
*/
33+
34+
import jdk.test.lib.Asserts;
35+
import sun.net.ftp.FtpClient;
36+
37+
import java.io.*;
38+
import java.net.*;
39+
import java.util.Calendar;
40+
import java.util.Date;
41+
import java.util.GregorianCalendar;
42+
import java.util.TimeZone;
43+
44+
45+
public class TestFtpTimeValue {
46+
private enum TestCase {
47+
AmTimeNoMilli(2019, 4, 20, 10, 57, 13, 0),
48+
AmTimeWithMilli(2019, 4, 20, 10, 57, 13, 50),
49+
PmTimeNoMilli(2019, 4, 20, 22, 57, 13, 0),
50+
PmTimeWithMilli(2019, 4, 20, 22, 57, 13, 50),
51+
;
52+
53+
public final Date expectedCreated;
54+
public final Date expectedModified;
55+
public final String create;
56+
public final String modify;
57+
58+
TestCase(int year, int month, int day, int hrs, int min, int sec, int milliseconds) {
59+
var calendar = GregorianCalendar.getInstance(TimeZone.getTimeZone("GMT"));
60+
// month is 0-based in Calendar
61+
calendar.set(year, month - 1, day, hrs, min, sec);
62+
calendar.set(Calendar.MILLISECOND, milliseconds);
63+
expectedCreated = calendar.getTime();
64+
var s = String.format("%4d%2d%2d%2d%2d%2d", year, month, day, hrs, min, sec);
65+
if (milliseconds != 0) {
66+
s += "." + String.format("%3d", milliseconds);
67+
}
68+
create = s;
69+
70+
calendar.add(GregorianCalendar.SECOND, 1);
71+
expectedModified = calendar.getTime();
72+
s = String.format("%4d%2d%2d%2d%2d%2d", year, month, day, hrs, min, sec + 1);
73+
if (milliseconds != 0) {
74+
s += "." + String.format("%3d", milliseconds);
75+
}
76+
modify = s;
77+
}
78+
}
79+
80+
public static void main(String[] args) throws Exception {
81+
try (FtpServer server = new FtpServer();
82+
FtpClient client = FtpClient.create()) {
83+
(new Thread(server)).start();
84+
int port = server.getPort();
85+
var loopback = InetAddress.getLoopbackAddress();
86+
client.connect(new InetSocketAddress(loopback, port));
87+
client.enablePassiveMode(true);
88+
for (var testCase : TestCase.values()) {
89+
Asserts.assertEQ(testCase.expectedModified, client.getLastModified(testCase.name()),
90+
"wrong modified date from MDTM for " + testCase);
91+
}
92+
for (var it = client.listFiles(null); it.hasNext(); ) {
93+
var e = it.next();
94+
Asserts.assertEQ(TestCase.valueOf(e.getName()).expectedCreated, e.getCreated(),
95+
"wrong created date from MLSD for " + e.getName());
96+
Asserts.assertEQ(TestCase.valueOf(e.getName()).expectedModified, e.getLastModified(),
97+
"wrong modified date from MLSD for " + e.getName());
98+
}
99+
}
100+
}
101+
102+
private static class FtpServer implements AutoCloseable, Runnable {
103+
private final ServerSocket serverSocket;
104+
private final ServerSocket pasv;
105+
106+
FtpServer() throws IOException {
107+
var loopback = InetAddress.getLoopbackAddress();
108+
serverSocket = new ServerSocket();
109+
serverSocket.bind(new InetSocketAddress(loopback, 0));
110+
pasv = new ServerSocket();
111+
pasv.bind(new InetSocketAddress(loopback, 0));
112+
}
113+
114+
public void handleClient(Socket client) throws IOException {
115+
String str;
116+
117+
client.setSoTimeout(2000);
118+
BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
119+
PrintWriter out = new PrintWriter(client.getOutputStream(), true);
120+
out.println("220 FTP serverSocket is ready.");
121+
boolean done = false;
122+
while (!done) {
123+
try {
124+
str = in.readLine();
125+
} catch (SocketException e) {
126+
done = true;
127+
continue;
128+
}
129+
String cmd = str.substring(0, str.indexOf(" ") > 0 ? str.indexOf(" ") : str.length());
130+
String args = (cmd.equals(str)) ? "" : str.substring(str.indexOf(" ") + 1);
131+
System.err.println("C> " + str);
132+
switch (cmd) {
133+
case "QUIT":
134+
out.println("221 Goodbye.");
135+
System.err.println("S> 221");
136+
done = true;
137+
break;
138+
case "MDTM": {
139+
var testCase = TestCase.valueOf(args);
140+
out.println("213 " + testCase.modify);
141+
System.err.println("S> 213");
142+
break;
143+
}
144+
case "MLSD":
145+
try (var socket = pasv.accept();
146+
var dout = new PrintWriter(socket.getOutputStream(), true)) {
147+
out.println("150 MLSD start");
148+
System.err.println("S> 150");
149+
for (var testCase : TestCase.values()) {
150+
dout.printf("modify=%s;create=%s; %s%n",
151+
testCase.modify, testCase.create, testCase.name());
152+
}
153+
}
154+
out.println("226 MLSD done.");
155+
System.err.println("S> 226");
156+
break;
157+
case "EPSV":
158+
if ("all".equalsIgnoreCase(args)) {
159+
out.println("200 EPSV ALL command successful.");
160+
System.err.println("S> 200");
161+
continue;
162+
}
163+
out.println("229 Entering Extended Passive Mode (|||" + pasv.getLocalPort() + "|)");
164+
System.err.println("S> 229");
165+
break;
166+
default:
167+
System.err.println("S> 500");
168+
out.println("500 unsupported command: " + str);
169+
}
170+
}
171+
}
172+
173+
public int getPort() {
174+
return serverSocket.getLocalPort();
175+
}
176+
177+
public void close() throws IOException {
178+
serverSocket.close();
179+
pasv.close();
180+
}
181+
182+
@Override
183+
public void run() {
184+
try (Socket client = serverSocket.accept()) {
185+
handleClient(client);
186+
} catch (Throwable t) {
187+
t.printStackTrace();
188+
throw new RuntimeException("Problem in test execution", t);
189+
}
190+
}
191+
}
192+
}

0 commit comments

Comments
 (0)