Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,11 @@ Supported protocols include Echo, Finger, FTP, NNTP, NTP, POP3(S), SMTP(S), Teln
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit-pioneer</groupId>
<artifactId>junit-pioneer</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.ftpserver</groupId>
<artifactId>ftpserver-core</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package org.apache.commons.net.ftp.parser;

import java.util.Calendar;
import java.util.GregorianCalendar;

import org.apache.commons.net.ftp.FTPFile;

Expand Down Expand Up @@ -105,7 +106,7 @@ public FTPFile parseFTPEntry(final String entry) {
// intentionally do nothing
}

final Calendar cal = Calendar.getInstance();
final Calendar cal = new GregorianCalendar();
cal.set(Calendar.MILLISECOND, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MINUTE, 0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;

import org.apache.commons.net.ftp.Configurable;
Expand Down Expand Up @@ -291,9 +292,14 @@ public Calendar parseTimestamp(final String timestampStr, final Calendar serverT
// all instances of short dates which are +- 6 months from current date.
// TODO this won't always work for systems that use short dates +0/-12months
// e.g. if today is Jan 1 2001 and the short date is Feb 29
final String year = Integer.toString(now.get(Calendar.YEAR));
// now is a clone of the caller's serverTime, which under some default locales (e.g. a Thai Buddhist calendar) is not Gregorian, so read the year
// through a Gregorian calendar at the same instant to keep the appended year Gregorian rather than 543 years out.
final GregorianCalendar gregorianNow = new GregorianCalendar(now.getTimeZone());
gregorianNow.setTimeInMillis(now.getTimeInMillis());
final String year = Integer.toString(gregorianNow.get(Calendar.YEAR));
final String timeStampStrPlusYear = timestampStr + " " + year;
final SimpleDateFormat hackFormatter = new SimpleDateFormat(recentDateFormat.toPattern() + " yyyy", recentDateFormat.getDateFormatSymbols());
hackFormatter.setCalendar(new GregorianCalendar());
hackFormatter.setLenient(false);
hackFormatter.setTimeZone(recentDateFormat.getTimeZone());
final ParsePosition pp = new ParsePosition(0);
Expand Down Expand Up @@ -338,6 +344,7 @@ private void setDefaultDateFormat(final String format, final DateFormatSymbols d
} else {
defaultDateFormat = new SimpleDateFormat(format);
}
defaultDateFormat.setCalendar(new GregorianCalendar());
defaultDateFormat.setLenient(false);
} else {
defaultDateFormat = null;
Expand All @@ -363,6 +370,7 @@ private void setRecentDateFormat(final String format, final DateFormatSymbols df
} else {
recentDateFormat = new SimpleDateFormat(format);
}
recentDateFormat.setCalendar(new GregorianCalendar());
recentDateFormat.setLenient(false);
} else {
recentDateFormat = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,10 @@ public static Calendar parseGMTdateTime(final String timestamp) {
final SimpleDateFormat dateFormat;
final boolean hasMillis;
if (timestamp.contains(".")) {
dateFormat = new SimpleDateFormat("yyyyMMddHHmmss.SSS");
dateFormat = new SimpleDateFormat("yyyyMMddHHmmss.SSS", Locale.ROOT);
hasMillis = true;
} else {
dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
dateFormat = new SimpleDateFormat("yyyyMMddHHmmss", Locale.ROOT);
hasMillis = false;
}
final TimeZone gmtTimeZone = TimeZone.getTimeZone("GMT");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPFileEntryParser;
import org.junit.jupiter.api.Test;
import org.junitpioneer.jupiter.DefaultLocale;

/**
* Tests the EnterpriseUnixFTPEntryParser
Expand Down Expand Up @@ -156,6 +157,21 @@ void testParseFieldsOnFile() throws Exception {
assertEquals(0, zDateTime.getSecond());
}

/**
* A numeric year in a listing must be read as a Gregorian year regardless of the JVM default locale. Under a Thai locale the base Calendar is a Buddhist
* calendar, which would otherwise store the timestamp 543 years out.
*/
@Test
@DefaultLocale(language = "th", country = "TH")
void testAbsoluteYearWithNonGregorianDefaultLocale() {
final FTPFile ftpFile = getParser().parseFTPEntry("-C--E-----FTP A QUA1I1 18128 41 Apr 1 2014 QUADTEST3");
final TimeZone timeZone = TimeZone.getDefault();
final ZonedDateTime zDateTime = ZonedDateTime.ofInstant(ftpFile.getTimestampInstant(), ZoneId.of(timeZone.getID()));
assertEquals(2014, zDateTime.getYear());
assertEquals(Month.APRIL, zDateTime.getMonth());
assertEquals(1, zDateTime.getDayOfMonth());
}

@Override
@Test
void testRecentPrecision() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.apache.commons.net.ftp.FTPClientConfig;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junitpioneer.jupiter.DefaultLocale;

/**
* Test the FTPTimestampParser class.
Expand Down Expand Up @@ -271,6 +272,55 @@ void testParser() throws ParseException {
}
}

/**
* A numeric year from a server listing must be read as a Gregorian year regardless of the JVM default locale. Under a Thai locale the default
* SimpleDateFormat calendar is a Buddhist calendar, which would otherwise shift the parsed instant by 543 years.
*/
@Test
@DefaultLocale(language = "th", country = "TH")
void testParseTimestampWithNonGregorianDefaultLocale() throws ParseException {
final FTPTimestampParserImpl parser = new FTPTimestampParserImpl();
final FTPClientConfig config = new FTPClientConfig(FTPClientConfig.SYST_UNIX);
config.setDefaultDateFormatStr("yyyy-MM-dd HH:mm");
config.setRecentDateFormatStr("MMM d HH:mm");
config.setServerLanguageCode("en");
config.setServerTimeZoneId("GMT");
parser.configure(config);
final Calendar parsed = parser.parseTimestamp("2010-03-13 22:45", new GregorianCalendar());
final GregorianCalendar expected = new GregorianCalendar(TimeZone.getTimeZone("GMT"), Locale.ROOT);
expected.clear();
expected.set(2010, Calendar.MARCH, 13, 22, 45, 0);
assertEquals(expected.getTimeInMillis(), parsed.getTimeInMillis());
}

/**
* A recent (short) date carries no year, so the parser appends the year taken from the supplied server time. When a caller builds that server time via
* {@link Calendar#getInstance()} under a Thai default locale it is a Buddhist calendar, so the appended year must be read through a Gregorian calendar or
* the parsed instant lands 543 years out.
*/
@Test
@DefaultLocale(language = "th", country = "TH")
void testParseRecentTimestampWithNonGregorianDefaultLocale() throws ParseException {
final FTPTimestampParserImpl parser = new FTPTimestampParserImpl();
final FTPClientConfig config = new FTPClientConfig(FTPClientConfig.SYST_UNIX);
config.setDefaultDateFormatStr("yyyy-MM-dd HH:mm");
config.setRecentDateFormatStr("MMM d HH:mm");
config.setServerLanguageCode("en");
config.setServerTimeZoneId("GMT");
parser.configure(config);
// Calendar.getInstance() under a Thai default locale is a Buddhist calendar, mirroring how a caller builds the server time.
final Calendar serverTime = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
final GregorianCalendar serverInstant = new GregorianCalendar(TimeZone.getTimeZone("GMT"), Locale.ROOT);
serverInstant.clear();
serverInstant.set(2010, Calendar.JUNE, 1, 0, 0, 0);
serverTime.setTimeInMillis(serverInstant.getTimeInMillis());
final Calendar parsed = parser.parseTimestamp("Mar 13 22:45", serverTime);
final GregorianCalendar expected = new GregorianCalendar(TimeZone.getTimeZone("GMT"), Locale.ROOT);
expected.clear();
expected.set(2010, Calendar.MARCH, 13, 22, 45, 0);
assertEquals(expected.getTimeInMillis(), parsed.getTimeInMillis());
}

@Test
void testParseShortFutureDates1() throws Exception {
final GregorianCalendar now = new GregorianCalendar(2001, Calendar.MAY, 30, 12, 0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,17 @@
*/
package org.apache.commons.net.ftp.parser;

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;

import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPFileEntryParser;
import org.junit.jupiter.api.Test;
import org.junitpioneer.jupiter.DefaultLocale;

/**
*/
Expand Down Expand Up @@ -68,6 +76,20 @@ protected FTPFile nullFileOrNullDate(final FTPFile f) {
return f;
}

/**
* The RFC 3659 time stamp is a numeric Gregorian date. Parsing it must not depend on the JVM default locale's calendar, which, for example,
* a Thai locale is a Buddhist calendar that would read the year 543 years out.
*/
@Test
@DefaultLocale(language = "th", country = "TH")
void testParseGMTdateTimeWithNonGregorianDefaultLocale() {
final Calendar parsed = MLSxEntryParser.parseGMTdateTime("20100313224553");
final GregorianCalendar expected = new GregorianCalendar(TimeZone.getTimeZone("GMT"), Locale.ROOT);
expected.clear();
expected.set(2010, Calendar.MARCH, 13, 22, 45, 53);
assertEquals(expected.getTimeInMillis(), parsed.getTimeInMillis());
}

@Override
@Test
void testDefaultPrecision() {
Expand Down
Loading