8380549: HttpCookie.expiryDate2DeltaSeconds returns 0 on parse failure, causing immediate cookie expiration

Reviewed-by: vyazici, michaelm
This commit is contained in:
EunHyunsu 2026-07-15 07:16:10 +00:00 committed by Volkan Yazici
parent 2b05a136cb
commit f146847ca1
4 changed files with 60 additions and 21 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -1005,12 +1005,13 @@ public final class HttpCookie implements Cloneable {
}
} catch (NumberFormatException ignored) {}
try {
if (expiresValue != null) {
long delta = cookie.expiryDate2DeltaSeconds(expiresValue);
if (expiresValue != null) {
Calendar cal = parseExpires(expiresValue);
if (cal != null) {
long delta = (cal.getTimeInMillis() - cookie.whenCreated) / 1000;
cookie.maxAge = (delta > 0 ? delta : 0);
}
} catch (NumberFormatException ignored) {}
}
}
private static void assignAttribute(HttpCookie cookie,
@ -1082,10 +1083,10 @@ public final class HttpCookie implements Cloneable {
* @param dateString
* a date string in one of the formats defined in Netscape cookie spec
*
* @return delta seconds between this cookie's creation time and the time
* specified by dateString
* @return the parsed date as a Calendar, or null if none of the
* formats could parse the given date string
*/
private long expiryDate2DeltaSeconds(String dateString) {
private static Calendar parseExpires(String dateString) {
Calendar cal = new GregorianCalendar(GMT);
for (int i = 0; i < COOKIE_DATE_FORMATS.length; i++) {
SimpleDateFormat df = new SimpleDateFormat(COOKIE_DATE_FORMATS[i],
@ -1108,12 +1109,12 @@ public final class HttpCookie implements Cloneable {
}
cal.set(Calendar.YEAR, year);
}
return (cal.getTimeInMillis() - whenCreated) / 1000;
return cal;
} catch (Exception e) {
// Ignore, try the next date format
}
}
return 0;
return null;
}
/*

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2009, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2009, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -24,7 +24,7 @@
/**
* @test
* @bug 6791927 8233886
* @summary Wrong Locale in HttpCookie::expiryDate2DeltaSeconds
* @summary Wrong Locale in HttpCookie::parseExpires
* @run main/othervm B6791927
*/

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -23,7 +23,7 @@
/*
* @test
* @bug 8000525
* @bug 8000525 8380549
* @library /test/lib
*/
@ -33,6 +33,8 @@ import java.io.*;
import java.text.*;
import jdk.test.lib.net.URIBuilder;
import static jdk.test.lib.Asserts.assertEquals;
public class ExpiredCookieTest {
// lifted from HttpCookie.java
private final static String[] COOKIE_DATE_FORMATS = {
@ -92,15 +94,28 @@ public class ExpiredCookieTest {
cm.put(uri, header);
CookieStore cookieJar = cm.getCookieStore();
List <HttpCookie> cookies = cookieJar.getCookies();
Set<String> names = new TreeSet<>();
for (HttpCookie cookie : cookieJar.getCookies())
names.add(cookie.getName());
Set<String> expected;
if (COOKIE_DATE_FORMATS[i].contains("yyyy")) {
if (cookies.size() != 2)
throw new RuntimeException(
"Incorrectly parsing a bad date");
} else if (cookies.size() != 1) {
throw new RuntimeException(
"Incorrectly parsing a bad date");
// Four-digit years parse unambiguously: TEST1 and TEST2 are
// in the past and expire, while TEST3 and TEST4 remain.
expected = new TreeSet<>(List.of("TEST3", "TEST4"));
} else {
// Two-digit years make TEST2 and TEST3 resolve to a mismatched
// day-of-week, so strict parsing rejects the Expires value; per
// RFC 6265 section 5.2.1 an unparseable Expires is ignored, so
// they remain as session cookies. TEST1 parses cleanly but is
// already expired, so it is dropped. TEST4's two-digit year
// round-trips to itself (69 -> 2069), so it parses and remains
// because its expiry is still in the future.
expected = new TreeSet<>(List.of("TEST2", "TEST3", "TEST4"));
}
assertEquals(expected, names,
"Incorrectly parsing a bad date, format: "
+ COOKIE_DATE_FORMATS[i]);
}
}
}

View File

@ -33,6 +33,7 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
public class MaxAgeExpires {
@ -138,4 +139,26 @@ public class MaxAgeExpires {
cookie.setMaxAge(-2);
assertEquals(-2, cookie.getMaxAge());
}
public static Object[][] unparseableDates() {
return new Object[][] {
{ "GARBAGE" },
{ "2024-01-01T00:00:00Z" }, // format not supported by RFC-6265
{ "January 1, 2099 00:00:00 GMT" } // format not supported by RFC-6265
};
}
@ParameterizedTest
@MethodSource("unparseableDates")
public void testUnparseableExpires(String badDate) {
// RFC 6265 section 5.2.1: if the expires value fails to parse,
// the cookie-av should be ignored.
// That results in the HttpCookie implementation to have maxAge value of -1.
HttpCookie cookie = HttpCookie.parse(
"Set-Cookie: name=value; expires=" + badDate).get(0);
assertEquals(-1, cookie.getMaxAge(),
"Unparseable expires=\"" + badDate + "\" should be ignored");
assertFalse(cookie.hasExpired(),
"Cookie with ignored expires should not be expired");
}
}