Возможная ошибка, появившаяся с Spring 5.0 в отношении RFC1123; asctime ANSI C

Похоже, что начиная с Spring 5.0 формат DateTime: asctime() ANSI C, как указано в RFC2616 больше не анализируется правильно, если указана одна цифра (т. е. 9 вместо 09).

При просмотре метода тестирования: HttpHeadersTest.firstZonedDateTime(), предоставленный Spring; мы можем видеть, что для «формата asctime() ANSI C» в качестве тестового ввода предоставляется двузначное число (т. е.; 02), а не однозначное (т. е. 2), как указано в RFC2616 (3.3.1). ).

Я написал тестовый метод, чтобы продемонстрировать потенциальную ошибку.

/**
 * Assumption: ANSI C's single-digit date (i.e; 0-9) should be viable syntax as specified in the RFC2616: https://tools.ietf.org/html/rfc2616#section-3.3.1
 * Expected output: assertThat(true) & assertThat(true)
 * Actual output: assertThat(true) & assertThat(false)
 *
 * throws: java.lang.IllegalArgumentException: Cannot parse date value "Fri Jun 2 02:22:00 2017" for "Date" header
 */
@Test
public void firstZonedDateTimeANSI(){
    ZonedDateTime date = ZonedDateTime.of(2017, 6, 2, 2, 22, 0, 0, ZoneId.of("GMT"));

    // ANSI C's asctime() format where single digit dates are represented as double digits (i.e; 2 as 02)
    headers.set(HttpHeaders.DATE, "Fri Jun 02 02:22:00 2017");
    assertThat(headers.getFirstZonedDateTime(HttpHeaders.DATE)                                                      // getFirstZonedDateTime parses the Date Syntax as ANSI (HttpHeaders.DATE_PARSERS[2])
            .isEqual(date))
            .isTrue();                                                                                              // expected assertThat(true) vs actual assertThat(true)
    headers.clear();

    // ANSI C's asctime() format where single digit dates are viable (i.e; 2 as 2 not 02); as
    headers.set(HttpHeaders.DATE, "Fri Jun 2 02:22:00 2017");
    assertThat(headers.getFirstZonedDateTime(HttpHeaders.DATE)                                                      // getFirstZonedDateTime throws java.time.format.DateTimeParseException: Text 'Fri Jun 2 02:22:00 2017' could not be parsed at index 8
            .isEqual(date))
            .isTrue();                                                                                              // expected assertThat(true) vs actual assertThat(false)
}

Я ожидаю, что приведенный выше тест подтвердит истинность даже для однозначного ввода. Но как видите, при запуске тест-метода вылетает ошибка:

throws: java.lang.IllegalArgumentException: Cannot parse date value "Fri Jun 2 02:22:00 2017" for "Date" header.

При ближайшем рассмотрении с помощью отладчика; ошибка может быть отслежена до:

java.time.format.DateTimeParseException: Text 'Fri Jun 2 02:22:00 2017' could not be parsed at index 8

Похоже, что с Spring 5.0 применяется новый способ разбора заголовка DateTime. См. HttpHeaders.getFirstZonedDataTime(String headerName):

/**
 * Parse the first header value for the given header name as a date,
 * return {@code null} if there is no value, or raise {@link IllegalArgumentException}
 * if the value cannot be parsed as a date.
 * @param headerName the header name
 * @return the parsed date header, or {@code null} if none
 * @since 5.0
 */
@Nullable
public ZonedDateTime getFirstZonedDateTime(String headerName) {
    return getFirstZonedDateTime(headerName, true);
}

/**
 * Parse the first header value for the given header name as a date,
 * return {@code null} if there is no value or also in case of an invalid value
 * (if {@code rejectInvalid=false}), or raise {@link IllegalArgumentException}
 * if the value cannot be parsed as a date.
 * @param headerName the header name
 * @param rejectInvalid whether to reject invalid values with an
 * {@link IllegalArgumentException} ({@code true}) or rather return {@code null}
 * in that case ({@code false})
 * @return the parsed date header, or {@code null} if none (or invalid)
 */
@Nullable
private ZonedDateTime getFirstZonedDateTime(String headerName, boolean rejectInvalid) {
    String headerValue = getFirst(headerName);
    if (headerValue == null) {
        // No header value sent at all
        return null;
    }
    if (headerValue.length() >= 3) {
        // Short "0" or "-1" like values are never valid HTTP date headers...
        // Let's only bother with DateTimeFormatter parsing for long enough values.

        // See https://stackoverflow.com/questions/12626699/if-modified-since-http-header-passed-by-ie9-includes-length
        int parametersIndex = headerValue.indexOf(';');
        if (parametersIndex != -1) {
            headerValue = headerValue.substring(0, parametersIndex);
        }

        for (DateTimeFormatter dateFormatter : DATE_PARSERS) {
            try {
                return ZonedDateTime.parse(headerValue, dateFormatter);
            }
            catch (DateTimeParseException ex) {
                // ignore
            }
        }

    }
    if (rejectInvalid) {
        throw new IllegalArgumentException("Cannot parse date value \"" + headerValue +
                "\" for \"" + headerName + "\" header");
    }
    return null;
}

Я считаю, что ошибка была введена в Spring 5.0, а точнее в этом цикле по адресу private ZonedDateTime getFirstZonedDateTime(String headerName, boolean rejectInvalid):

for (DateTimeFormatter dateFormatter : DATE_PARSERS) {
        try {
            return ZonedDateTime.parse(headerValue, dateFormatter);
        }
        catch (DateTimeParseException ex) {
            // ignore
        }
    }

При просмотре последней функциональной сборки: Spring 4.3 использовался аналогичный цикл: private long getFirstDate(String headerName, boolean rejectInvalid)

        for (String dateFormat : DATE_FORMATS) {
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat(dateFormat, Locale.US);
            simpleDateFormat.setTimeZone(GMT);
            try {
                return simpleDateFormat.parse(headerValue).getTime();
            }
            catch (ParseException ex) {
                // ignore
            }
        }

Но в то время как Spring 4.3 по-прежнему использовал java.text.SimpleDateFormat для синтаксического анализа, начиная с Spring 5.0 для синтаксического анализа используется Java.time.format.ZonedDateTime.

И Spring 4.3, и Spring 5.0 обращаются к одному и тому же частному статическому массиву для итерации:

/**
 * Date formats with time zone as specified in the HTTP RFC to use for parsing.
 * @see <a href="https://tools.ietf.org/html/rfc7231#section-7.1.1.1">Section 7.1.1.1 of RFC 7231</a>
 */
private static final DateTimeFormatter[] DATE_PARSERS = new DateTimeFormatter[] {
        DateTimeFormatter.RFC_1123_DATE_TIME,
        DateTimeFormatter.ofPattern("EEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US),
        DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss yyyy", Locale.US).withZone(GMT)
};

Заключить:

Я полагаю, что в Spring 5.0 была введена ошибка, из-за которой формат asctime() ANSI C, определенный в RFC2616, больше не анализируется правильно при анализе одной цифры;

Я считаю, что причиной ошибки является изменение с simpleDateFormat на ZonedDateTime для синтаксического анализа.

Я хотел бы, чтобы кто-нибудь воспроизвел эту ошибку, прежде чем я отправлю ее в Spring через Github; чтобы убедиться, что я не сделал ошибок в тестовом примере или предположениях.

Это мой первый пост; прощать любые ошибки; (структурированные) отзывы приветствуются.


person Kevin Stoffers    schedule 15.08.2019    source источник
comment
Если вы считаете, что это ошибка, вы должны сообщить об этом в весенние проблемы github. Если в тестовых примерах есть какие-либо ошибки, вы получите комментарии. SO не подходит для этого.   -  person Karthikeyan Vaithilingam    schedule 15.08.2019
comment
С командой из 10 авторов; у команды Spring есть 804 открытых заявки, которые они в настоящее время должны решить. Я не согласен с вашим мнением о том, что С.О. не будет подходящим местом для этого. Тем более, что я поясняю в своем посте, что я хотел бы, чтобы кто-то фальсифицировал или подтвердил мои выводы, прежде чем я сделаю счет 805. Ключевое слово «до», поскольку, если ошибка действительно подтверждена, Github действительно является местом, где я отправлю отчет об ошибке. ; это не то, что я делаю сейчас   -  person Kevin Stoffers    schedule 15.08.2019