diff --git a/src/java.base/share/classes/java/net/HttpCookie.java b/src/java.base/share/classes/java/net/HttpCookie.java index 3c633522bdf..2b3a5cbb6a5 100644 --- a/src/java.base/share/classes/java/net/HttpCookie.java +++ b/src/java.base/share/classes/java/net/HttpCookie.java @@ -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; } /* diff --git a/test/jdk/java/net/CookieHandler/B6791927.java b/test/jdk/java/net/CookieHandler/B6791927.java index bc5374b2a98..ceeff260665 100644 --- a/test/jdk/java/net/CookieHandler/B6791927.java +++ b/test/jdk/java/net/CookieHandler/B6791927.java @@ -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 */ diff --git a/test/jdk/java/net/HttpCookie/ExpiredCookieTest.java b/test/jdk/java/net/HttpCookie/ExpiredCookieTest.java index 5cf7208d2ca..e2769d8dd61 100644 --- a/test/jdk/java/net/HttpCookie/ExpiredCookieTest.java +++ b/test/jdk/java/net/HttpCookie/ExpiredCookieTest.java @@ -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 cookies = cookieJar.getCookies(); + Set names = new TreeSet<>(); + for (HttpCookie cookie : cookieJar.getCookies()) + names.add(cookie.getName()); + + Set 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]); } } } diff --git a/test/jdk/java/net/HttpCookie/whitebox/java.base/java/net/MaxAgeExpires.java b/test/jdk/java/net/HttpCookie/whitebox/java.base/java/net/MaxAgeExpires.java index 6704a290836..79139d69e50 100644 --- a/test/jdk/java/net/HttpCookie/whitebox/java.base/java/net/MaxAgeExpires.java +++ b/test/jdk/java/net/HttpCookie/whitebox/java.base/java/net/MaxAgeExpires.java @@ -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"); + } }