HTTP: cookie file stores expiration in seconds
A cookie file stores the expiration in seconds since the Linux Epoch, not in milliseconds. Correct reading and writing cookie files; with a backwards-compatibility hack to read files that contain a millisecond timestamp. Add a test, and fix tests not to rely on the actual current time so that they will also run successfully after 2030-01-01 noon. Bug: 571574 Change-Id: If3ba68391e574520701cdee119544eedc42a1ff2 Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
This commit is contained in:
parent
535f446a2e
commit
40d6eda3f1
|
@ -1,2 +1,2 @@
|
||||||
some-domain1 TRUE /some/path1 FALSE 1893499200000 key1 valueFromSimple1
|
some-domain1 TRUE /some/path1 FALSE 1893499200 key1 valueFromSimple1
|
||||||
some-domain1 TRUE /some/path1 FALSE 1893499200000 key2 valueFromSimple1
|
some-domain1 TRUE /some/path1 FALSE 1893499200 key2 valueFromSimple1
|
|
@ -1,2 +1,2 @@
|
||||||
some-domain1 TRUE /some/path1 FALSE 1893499200000 key1 valueFromSimple2
|
some-domain1 TRUE /some/path1 FALSE 1893499200 key1 valueFromSimple2
|
||||||
some-domain1 TRUE /some/path1 FALSE 1893499200000 key3 valueFromSimple2
|
some-domain1 TRUE /some/path1 FALSE 1893499200 key3 valueFromSimple2
|
|
@ -3,6 +3,6 @@
|
||||||
some-domain1 TRUE /some/path1 FALSE 0 key1 value1
|
some-domain1 TRUE /some/path1 FALSE 0 key1 value1
|
||||||
|
|
||||||
# expires date is 01/01/2030 @ 12:00am (UTC)
|
# expires date is 01/01/2030 @ 12:00am (UTC)
|
||||||
#HttpOnly_.some-domain2 TRUE /some/path2 TRUE 1893499200000 key2 value2
|
#HttpOnly_.some-domain2 TRUE /some/path2 TRUE 1893499200 key2 value2
|
||||||
|
|
||||||
some-domain3 TRUE /some/path3 FALSE 1893499200000 key3 value3
|
some-domain3 TRUE /some/path3 FALSE 1893499200 key3 value3
|
|
@ -0,0 +1,2 @@
|
||||||
|
some-domain1 TRUE /some/path1 FALSE 1893499200000 key1 valueFromSimple1
|
||||||
|
some-domain1 TRUE /some/path1 FALSE 1893499200 key2 valueFromSimple1
|
|
@ -22,13 +22,13 @@
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.StandardCopyOption;
|
import java.nio.file.StandardCopyOption;
|
||||||
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Date;
|
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import org.eclipse.jgit.internal.storage.file.LockFile;
|
import org.eclipse.jgit.internal.storage.file.LockFile;
|
||||||
import org.eclipse.jgit.util.http.HttpCookiesMatcher;
|
import org.eclipse.jgit.util.http.HttpCookiesMatcher;
|
||||||
|
@ -48,10 +48,14 @@ public class NetscapeCookieFileTest {
|
||||||
private URL baseUrl;
|
private URL baseUrl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the expiration date that is used in the test cookie files
|
* This is the expiration date that is used in the test cookie files.
|
||||||
*/
|
*/
|
||||||
private static long JAN_01_2030_NOON = Instant
|
private static final Instant TEST_EXPIRY_DATE = Instant
|
||||||
.parse("2030-01-01T12:00:00.000Z").toEpochMilli();
|
.parse("2030-01-01T12:00:00.000Z");
|
||||||
|
|
||||||
|
/** Earlier than TEST_EXPIRY_DATE. */
|
||||||
|
private static final Instant TEST_DATE = TEST_EXPIRY_DATE.minus(180,
|
||||||
|
ChronoUnit.DAYS);
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws IOException {
|
public void setUp() throws IOException {
|
||||||
|
@ -102,14 +106,13 @@ public void testWriteToNewFile() throws IOException {
|
||||||
cookie.setPath("/");
|
cookie.setPath("/");
|
||||||
cookie.setMaxAge(1000);
|
cookie.setMaxAge(1000);
|
||||||
cookies.add(cookie);
|
cookies.add(cookie);
|
||||||
Date creationDate = new Date();
|
|
||||||
try (Writer writer = Files.newBufferedWriter(tmpFile,
|
try (Writer writer = Files.newBufferedWriter(tmpFile,
|
||||||
StandardCharsets.US_ASCII)) {
|
StandardCharsets.US_ASCII)) {
|
||||||
NetscapeCookieFile.write(writer, cookies, baseUrl, creationDate);
|
NetscapeCookieFile.write(writer, cookies, baseUrl, TEST_DATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
String expectedExpiration = String
|
String expectedExpiration = String
|
||||||
.valueOf(creationDate.getTime() + (cookie.getMaxAge() * 1000));
|
.valueOf(TEST_DATE.getEpochSecond() + cookie.getMaxAge());
|
||||||
|
|
||||||
assertThat(Files.readAllLines(tmpFile, StandardCharsets.US_ASCII),
|
assertThat(Files.readAllLines(tmpFile, StandardCharsets.US_ASCII),
|
||||||
CoreMatchers
|
CoreMatchers
|
||||||
|
@ -128,13 +131,12 @@ public void testWriteToExistingFile() throws IOException {
|
||||||
HttpCookie cookie = new HttpCookie("key2", "value2");
|
HttpCookie cookie = new HttpCookie("key2", "value2");
|
||||||
cookie.setMaxAge(1000);
|
cookie.setMaxAge(1000);
|
||||||
cookies.add(cookie);
|
cookies.add(cookie);
|
||||||
Date creationDate = new Date();
|
|
||||||
try (Writer writer = Files.newBufferedWriter(tmpFile,
|
try (Writer writer = Files.newBufferedWriter(tmpFile,
|
||||||
StandardCharsets.US_ASCII)) {
|
StandardCharsets.US_ASCII)) {
|
||||||
NetscapeCookieFile.write(writer, cookies, baseUrl, creationDate);
|
NetscapeCookieFile.write(writer, cookies, baseUrl, TEST_DATE);
|
||||||
}
|
}
|
||||||
String expectedExpiration = String
|
String expectedExpiration = String
|
||||||
.valueOf(creationDate.getTime() + (cookie.getMaxAge() * 1000));
|
.valueOf(TEST_DATE.getEpochSecond() + cookie.getMaxAge());
|
||||||
|
|
||||||
assertThat(Files.readAllLines(tmpFile, StandardCharsets.US_ASCII),
|
assertThat(Files.readAllLines(tmpFile, StandardCharsets.US_ASCII),
|
||||||
CoreMatchers.equalTo(
|
CoreMatchers.equalTo(
|
||||||
|
@ -160,6 +162,21 @@ public void testWriteWhileSomeoneIsHoldingTheLock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadCookieFileWithMilliseconds() throws IOException {
|
||||||
|
try (InputStream input = this.getClass()
|
||||||
|
.getResourceAsStream("cookies-with-milliseconds.txt")) {
|
||||||
|
Files.copy(input, tmpFile, StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
}
|
||||||
|
NetscapeCookieFile cookieFile = new NetscapeCookieFile(tmpFile,
|
||||||
|
TEST_DATE);
|
||||||
|
long expectedMaxAge = Duration.between(TEST_DATE, TEST_EXPIRY_DATE)
|
||||||
|
.getSeconds();
|
||||||
|
for (HttpCookie cookie : cookieFile.getCookies(true)) {
|
||||||
|
assertEquals(expectedMaxAge, cookie.getMaxAge());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testWriteAfterAnotherJgitProcessModifiedTheFile()
|
public void testWriteAfterAnotherJgitProcessModifiedTheFile()
|
||||||
throws IOException, InterruptedException {
|
throws IOException, InterruptedException {
|
||||||
|
@ -167,7 +184,8 @@ public void testWriteAfterAnotherJgitProcessModifiedTheFile()
|
||||||
.getResourceAsStream("cookies-simple1.txt")) {
|
.getResourceAsStream("cookies-simple1.txt")) {
|
||||||
Files.copy(input, tmpFile, StandardCopyOption.REPLACE_EXISTING);
|
Files.copy(input, tmpFile, StandardCopyOption.REPLACE_EXISTING);
|
||||||
}
|
}
|
||||||
NetscapeCookieFile cookieFile = new NetscapeCookieFile(tmpFile);
|
NetscapeCookieFile cookieFile = new NetscapeCookieFile(tmpFile,
|
||||||
|
TEST_DATE);
|
||||||
cookieFile.getCookies(true);
|
cookieFile.getCookies(true);
|
||||||
// now modify file externally
|
// now modify file externally
|
||||||
try (InputStream input = this.getClass()
|
try (InputStream input = this.getClass()
|
||||||
|
@ -177,39 +195,19 @@ public void testWriteAfterAnotherJgitProcessModifiedTheFile()
|
||||||
// now try to write
|
// now try to write
|
||||||
cookieFile.write(baseUrl);
|
cookieFile.write(baseUrl);
|
||||||
|
|
||||||
// validate that the external changes are there as well
|
|
||||||
// due to rounding errors (conversion from ms to sec to ms)
|
|
||||||
// the expiration date might not be exact
|
|
||||||
List<String> lines = Files.readAllLines(tmpFile,
|
List<String> lines = Files.readAllLines(tmpFile,
|
||||||
StandardCharsets.US_ASCII);
|
StandardCharsets.US_ASCII);
|
||||||
|
|
||||||
assertEquals("Expected 3 lines", 3, lines.size());
|
assertEquals("Expected 3 lines", 3, lines.size());
|
||||||
assertStringMatchesPatternWithInexactNumber(lines.get(0),
|
assertEquals(
|
||||||
"some-domain1\tTRUE\t/some/path1\tFALSE\t(\\d*)\tkey1\tvalueFromSimple2",
|
"some-domain1\tTRUE\t/some/path1\tFALSE\t1893499200\tkey1\tvalueFromSimple2",
|
||||||
JAN_01_2030_NOON, 1000);
|
lines.get(0));
|
||||||
assertStringMatchesPatternWithInexactNumber(lines.get(1),
|
assertEquals(
|
||||||
"some-domain1\tTRUE\t/some/path1\tFALSE\t(\\d*)\tkey3\tvalueFromSimple2",
|
"some-domain1\tTRUE\t/some/path1\tFALSE\t1893499200\tkey3\tvalueFromSimple2",
|
||||||
JAN_01_2030_NOON, 1000);
|
lines.get(1));
|
||||||
assertStringMatchesPatternWithInexactNumber(lines.get(2),
|
assertEquals(
|
||||||
"some-domain1\tTRUE\t/some/path1\tFALSE\t(\\d*)\tkey2\tvalueFromSimple1",
|
"some-domain1\tTRUE\t/some/path1\tFALSE\t1893499200\tkey2\tvalueFromSimple1",
|
||||||
JAN_01_2030_NOON, 1000);
|
lines.get(2));
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("boxing")
|
|
||||||
private static final void assertStringMatchesPatternWithInexactNumber(
|
|
||||||
String string, String pattern, long expectedNumericValue,
|
|
||||||
long delta) {
|
|
||||||
java.util.regex.Matcher matcher = Pattern.compile(pattern)
|
|
||||||
.matcher(string);
|
|
||||||
assertTrue("Given string '" + string + "' does not match '" + pattern
|
|
||||||
+ "'", matcher.matches());
|
|
||||||
// extract numeric value
|
|
||||||
Long actualNumericValue = Long.decode(matcher.group(1));
|
|
||||||
|
|
||||||
assertTrue(
|
|
||||||
"Value is supposed to be close to " + expectedNumericValue
|
|
||||||
+ " but is " + actualNumericValue + ".",
|
|
||||||
Math.abs(expectedNumericValue - actualNumericValue) <= delta);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -229,14 +227,13 @@ public void testWriteAndReadCycle() throws IOException {
|
||||||
cookie.setHttpOnly(true);
|
cookie.setHttpOnly(true);
|
||||||
cookies.add(cookie);
|
cookies.add(cookie);
|
||||||
|
|
||||||
Date creationDate = new Date();
|
|
||||||
|
|
||||||
try (Writer writer = Files.newBufferedWriter(tmpFile,
|
try (Writer writer = Files.newBufferedWriter(tmpFile,
|
||||||
StandardCharsets.US_ASCII)) {
|
StandardCharsets.US_ASCII)) {
|
||||||
NetscapeCookieFile.write(writer, cookies, baseUrl, creationDate);
|
NetscapeCookieFile.write(writer, cookies, baseUrl, TEST_DATE);
|
||||||
}
|
}
|
||||||
Set<HttpCookie> actualCookies = new NetscapeCookieFile(tmpFile,
|
Set<HttpCookie> actualCookies = new NetscapeCookieFile(tmpFile,
|
||||||
creationDate).getCookies(true);
|
TEST_DATE)
|
||||||
|
.getCookies(true);
|
||||||
assertThat(actualCookies, HttpCookiesMatcher.containsInOrder(cookies));
|
assertThat(actualCookies, HttpCookiesMatcher.containsInOrder(cookies));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,15 +243,12 @@ public void testReadAndWriteCycle() throws IOException {
|
||||||
.getResourceAsStream("cookies-simple1.txt")) {
|
.getResourceAsStream("cookies-simple1.txt")) {
|
||||||
Files.copy(input, tmpFile, StandardCopyOption.REPLACE_EXISTING);
|
Files.copy(input, tmpFile, StandardCopyOption.REPLACE_EXISTING);
|
||||||
}
|
}
|
||||||
// round up to the next second (to prevent rounding errors)
|
Set<HttpCookie> cookies = new NetscapeCookieFile(tmpFile, TEST_DATE)
|
||||||
Date creationDate = new Date(
|
|
||||||
(System.currentTimeMillis() / 1000) * 1000);
|
|
||||||
Set<HttpCookie> cookies = new NetscapeCookieFile(tmpFile, creationDate)
|
|
||||||
.getCookies(true);
|
.getCookies(true);
|
||||||
Path tmpFile2 = folder.newFile().toPath();
|
Path tmpFile2 = folder.newFile().toPath();
|
||||||
try (Writer writer = Files.newBufferedWriter(tmpFile2,
|
try (Writer writer = Files.newBufferedWriter(tmpFile2,
|
||||||
StandardCharsets.US_ASCII)) {
|
StandardCharsets.US_ASCII)) {
|
||||||
NetscapeCookieFile.write(writer, cookies, baseUrl, creationDate);
|
NetscapeCookieFile.write(writer, cookies, baseUrl, TEST_DATE);
|
||||||
}
|
}
|
||||||
// compare original file with newly written one, they should not differ
|
// compare original file with newly written one, they should not differ
|
||||||
assertEquals(Files.readAllLines(tmpFile), Files.readAllLines(tmpFile2));
|
assertEquals(Files.readAllLines(tmpFile), Files.readAllLines(tmpFile2));
|
||||||
|
@ -267,13 +261,13 @@ public void testReadWithEmptyAndCommentLines() throws IOException {
|
||||||
Files.copy(input, tmpFile, StandardCopyOption.REPLACE_EXISTING);
|
Files.copy(input, tmpFile, StandardCopyOption.REPLACE_EXISTING);
|
||||||
}
|
}
|
||||||
|
|
||||||
Date creationDate = new Date();
|
|
||||||
Set<HttpCookie> cookies = new LinkedHashSet<>();
|
Set<HttpCookie> cookies = new LinkedHashSet<>();
|
||||||
|
|
||||||
HttpCookie cookie = new HttpCookie("key2", "value2");
|
HttpCookie cookie = new HttpCookie("key2", "value2");
|
||||||
cookie.setDomain("some-domain2");
|
cookie.setDomain("some-domain2");
|
||||||
cookie.setPath("/some/path2");
|
cookie.setPath("/some/path2");
|
||||||
cookie.setMaxAge((JAN_01_2030_NOON - creationDate.getTime()) / 1000);
|
cookie.setMaxAge(
|
||||||
|
Duration.between(TEST_DATE, TEST_EXPIRY_DATE).getSeconds());
|
||||||
cookie.setSecure(true);
|
cookie.setSecure(true);
|
||||||
cookie.setHttpOnly(true);
|
cookie.setHttpOnly(true);
|
||||||
cookies.add(cookie);
|
cookies.add(cookie);
|
||||||
|
@ -281,11 +275,12 @@ public void testReadWithEmptyAndCommentLines() throws IOException {
|
||||||
cookie = new HttpCookie("key3", "value3");
|
cookie = new HttpCookie("key3", "value3");
|
||||||
cookie.setDomain("some-domain3");
|
cookie.setDomain("some-domain3");
|
||||||
cookie.setPath("/some/path3");
|
cookie.setPath("/some/path3");
|
||||||
cookie.setMaxAge((JAN_01_2030_NOON - creationDate.getTime()) / 1000);
|
cookie.setMaxAge(
|
||||||
|
Duration.between(TEST_DATE, TEST_EXPIRY_DATE).getSeconds());
|
||||||
cookies.add(cookie);
|
cookies.add(cookie);
|
||||||
|
|
||||||
Set<HttpCookie> actualCookies = new NetscapeCookieFile(tmpFile, creationDate)
|
Set<HttpCookie> actualCookies = new NetscapeCookieFile(tmpFile,
|
||||||
.getCookies(true);
|
TEST_DATE).getCookies(true);
|
||||||
assertThat(actualCookies, HttpCookiesMatcher.containsInOrder(cookies));
|
assertThat(actualCookies, HttpCookiesMatcher.containsInOrder(cookies));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -296,7 +291,7 @@ public void testReadInvalidFile() throws IOException {
|
||||||
Files.copy(input, tmpFile, StandardCopyOption.REPLACE_EXISTING);
|
Files.copy(input, tmpFile, StandardCopyOption.REPLACE_EXISTING);
|
||||||
}
|
}
|
||||||
|
|
||||||
new NetscapeCookieFile(tmpFile)
|
assertTrue(new NetscapeCookieFile(tmpFile, TEST_DATE).getCookies(true)
|
||||||
.getCookies(true);
|
.isEmpty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,11 +22,12 @@
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.text.MessageFormat;
|
import java.text.MessageFormat;
|
||||||
|
import java.time.Instant;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Date;
|
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.eclipse.jgit.annotations.NonNull;
|
import org.eclipse.jgit.annotations.NonNull;
|
||||||
import org.eclipse.jgit.annotations.Nullable;
|
import org.eclipse.jgit.annotations.Nullable;
|
||||||
|
@ -53,6 +54,7 @@
|
||||||
* In general this class is not thread-safe. So any consumer needs to take care
|
* In general this class is not thread-safe. So any consumer needs to take care
|
||||||
* of synchronization!
|
* of synchronization!
|
||||||
*
|
*
|
||||||
|
* @see <a href="https://curl.se/docs/http-cookies.html">Cookie file format</a>
|
||||||
* @see <a href="http://www.cookiecentral.com/faq/#3.5">Netscape Cookie File
|
* @see <a href="http://www.cookiecentral.com/faq/#3.5">Netscape Cookie File
|
||||||
* Format</a>
|
* Format</a>
|
||||||
* @see <a href=
|
* @see <a href=
|
||||||
|
@ -92,7 +94,7 @@ public final class NetscapeCookieFile {
|
||||||
|
|
||||||
private byte[] hash;
|
private byte[] hash;
|
||||||
|
|
||||||
final Date creationDate;
|
private final Instant createdAt;
|
||||||
|
|
||||||
private Set<HttpCookie> cookies = null;
|
private Set<HttpCookie> cookies = null;
|
||||||
|
|
||||||
|
@ -104,13 +106,13 @@ public final class NetscapeCookieFile {
|
||||||
* where to find the cookie file
|
* where to find the cookie file
|
||||||
*/
|
*/
|
||||||
public NetscapeCookieFile(Path path) {
|
public NetscapeCookieFile(Path path) {
|
||||||
this(path, new Date());
|
this(path, Instant.now());
|
||||||
}
|
}
|
||||||
|
|
||||||
NetscapeCookieFile(Path path, Date creationDate) {
|
NetscapeCookieFile(Path path, Instant createdAt) {
|
||||||
this.path = path;
|
this.path = path;
|
||||||
this.snapshot = FileSnapshot.DIRTY;
|
this.snapshot = FileSnapshot.DIRTY;
|
||||||
this.creationDate = creationDate;
|
this.createdAt = createdAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -142,7 +144,7 @@ public Set<HttpCookie> getCookies(boolean refresh) {
|
||||||
if (cookies == null || refresh) {
|
if (cookies == null || refresh) {
|
||||||
try {
|
try {
|
||||||
byte[] in = getFileContentIfModified();
|
byte[] in = getFileContentIfModified();
|
||||||
Set<HttpCookie> newCookies = parseCookieFile(in, creationDate);
|
Set<HttpCookie> newCookies = parseCookieFile(in, createdAt);
|
||||||
if (cookies != null) {
|
if (cookies != null) {
|
||||||
cookies = mergeCookies(newCookies, cookies);
|
cookies = mergeCookies(newCookies, cookies);
|
||||||
} else {
|
} else {
|
||||||
|
@ -168,9 +170,9 @@ public Set<HttpCookie> getCookies(boolean refresh) {
|
||||||
*
|
*
|
||||||
* @param input
|
* @param input
|
||||||
* the file content to parse
|
* the file content to parse
|
||||||
* @param creationDate
|
* @param createdAt
|
||||||
* the date for the creation of the cookies (used to calculate
|
* cookie creation time; used to calculate the maxAge based on
|
||||||
* the maxAge based on the expiration date given within the file)
|
* the expiration date given within the file
|
||||||
* @return the set of parsed cookies from the given file (even expired
|
* @return the set of parsed cookies from the given file (even expired
|
||||||
* ones). If there is more than one cookie with the same name in
|
* ones). If there is more than one cookie with the same name in
|
||||||
* this file the last one overwrites the first one!
|
* this file the last one overwrites the first one!
|
||||||
|
@ -180,7 +182,7 @@ public Set<HttpCookie> getCookies(boolean refresh) {
|
||||||
* if the given file does not have a proper format
|
* if the given file does not have a proper format
|
||||||
*/
|
*/
|
||||||
private static Set<HttpCookie> parseCookieFile(@NonNull byte[] input,
|
private static Set<HttpCookie> parseCookieFile(@NonNull byte[] input,
|
||||||
@NonNull Date creationDate)
|
@NonNull Instant createdAt)
|
||||||
throws IOException, IllegalArgumentException {
|
throws IOException, IllegalArgumentException {
|
||||||
|
|
||||||
String decoded = RawParseUtils.decode(StandardCharsets.US_ASCII, input);
|
String decoded = RawParseUtils.decode(StandardCharsets.US_ASCII, input);
|
||||||
|
@ -190,7 +192,7 @@ private static Set<HttpCookie> parseCookieFile(@NonNull byte[] input,
|
||||||
new StringReader(decoded))) {
|
new StringReader(decoded))) {
|
||||||
String line;
|
String line;
|
||||||
while ((line = reader.readLine()) != null) {
|
while ((line = reader.readLine()) != null) {
|
||||||
HttpCookie cookie = parseLine(line, creationDate);
|
HttpCookie cookie = parseLine(line, createdAt);
|
||||||
if (cookie != null) {
|
if (cookie != null) {
|
||||||
cookies.add(cookie);
|
cookies.add(cookie);
|
||||||
}
|
}
|
||||||
|
@ -200,7 +202,7 @@ private static Set<HttpCookie> parseCookieFile(@NonNull byte[] input,
|
||||||
}
|
}
|
||||||
|
|
||||||
private static HttpCookie parseLine(@NonNull String line,
|
private static HttpCookie parseLine(@NonNull String line,
|
||||||
@NonNull Date creationDate) {
|
@NonNull Instant createdAt) {
|
||||||
if (line.isEmpty() || (line.startsWith("#") //$NON-NLS-1$
|
if (line.isEmpty() || (line.startsWith("#") //$NON-NLS-1$
|
||||||
&& !line.startsWith(HTTP_ONLY_PREAMBLE))) {
|
&& !line.startsWith(HTTP_ONLY_PREAMBLE))) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -236,7 +238,12 @@ private static HttpCookie parseLine(@NonNull String line,
|
||||||
cookie.setSecure(Boolean.parseBoolean(cookieLineParts[3]));
|
cookie.setSecure(Boolean.parseBoolean(cookieLineParts[3]));
|
||||||
|
|
||||||
long expires = Long.parseLong(cookieLineParts[4]);
|
long expires = Long.parseLong(cookieLineParts[4]);
|
||||||
long maxAge = (expires - creationDate.getTime()) / 1000;
|
// Older versions stored milliseconds. This heuristic to detect that
|
||||||
|
// will cause trouble in the year 33658. :-)
|
||||||
|
if (cookieLineParts[4].length() == 13) {
|
||||||
|
expires = TimeUnit.MILLISECONDS.toSeconds(expires);
|
||||||
|
}
|
||||||
|
long maxAge = expires - createdAt.getEpochSecond();
|
||||||
if (maxAge <= 0) {
|
if (maxAge <= 0) {
|
||||||
return null; // skip expired cookies
|
return null; // skip expired cookies
|
||||||
}
|
}
|
||||||
|
@ -245,7 +252,7 @@ private static HttpCookie parseLine(@NonNull String line,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read the underying file and return its content but only in case it has
|
* Read the underlying file and return its content but only in case it has
|
||||||
* been modified since the last access.
|
* been modified since the last access.
|
||||||
* <p>
|
* <p>
|
||||||
* Internally calculates the hash and maintains {@link FileSnapshot}s to
|
* Internally calculates the hash and maintains {@link FileSnapshot}s to
|
||||||
|
@ -333,7 +340,7 @@ public void write(URL url) throws IOException, InterruptedException {
|
||||||
path);
|
path);
|
||||||
// reread new changes if necessary
|
// reread new changes if necessary
|
||||||
Set<HttpCookie> cookiesFromFile = NetscapeCookieFile
|
Set<HttpCookie> cookiesFromFile = NetscapeCookieFile
|
||||||
.parseCookieFile(cookieFileContent, creationDate);
|
.parseCookieFile(cookieFileContent, createdAt);
|
||||||
this.cookies = mergeCookies(cookiesFromFile, cookies);
|
this.cookies = mergeCookies(cookiesFromFile, cookies);
|
||||||
}
|
}
|
||||||
} catch (FileNotFoundException e) {
|
} catch (FileNotFoundException e) {
|
||||||
|
@ -343,7 +350,7 @@ public void write(URL url) throws IOException, InterruptedException {
|
||||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||||
try (Writer writer = new OutputStreamWriter(output,
|
try (Writer writer = new OutputStreamWriter(output,
|
||||||
StandardCharsets.US_ASCII)) {
|
StandardCharsets.US_ASCII)) {
|
||||||
write(writer, cookies, url, creationDate);
|
write(writer, cookies, url, createdAt);
|
||||||
}
|
}
|
||||||
LockFile lockFile = new LockFile(path.toFile());
|
LockFile lockFile = new LockFile(path.toFile());
|
||||||
for (int retryCount = 0; retryCount < LOCK_ACQUIRE_MAX_RETRY_COUNT; retryCount++) {
|
for (int retryCount = 0; retryCount < LOCK_ACQUIRE_MAX_RETRY_COUNT; retryCount++) {
|
||||||
|
@ -377,24 +384,23 @@ public void write(URL url) throws IOException, InterruptedException {
|
||||||
* @param url
|
* @param url
|
||||||
* the url for which to write the cookie (to derive the default
|
* the url for which to write the cookie (to derive the default
|
||||||
* values for certain cookie attributes)
|
* values for certain cookie attributes)
|
||||||
* @param creationDate
|
* @param createdAt
|
||||||
* the date when the cookie has been created. Important for
|
* cookie creation time; used to calculate a cookie's expiration
|
||||||
* calculation the cookie expiration time (calculated from
|
* time
|
||||||
* cookie's maxAge and this creation time)
|
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
* if an I/O error occurs
|
* if an I/O error occurs
|
||||||
*/
|
*/
|
||||||
static void write(@NonNull Writer writer,
|
static void write(@NonNull Writer writer,
|
||||||
@NonNull Collection<HttpCookie> cookies, @NonNull URL url,
|
@NonNull Collection<HttpCookie> cookies, @NonNull URL url,
|
||||||
@NonNull Date creationDate) throws IOException {
|
@NonNull Instant createdAt) throws IOException {
|
||||||
for (HttpCookie cookie : cookies) {
|
for (HttpCookie cookie : cookies) {
|
||||||
writeCookie(writer, cookie, url, creationDate);
|
writeCookie(writer, cookie, url, createdAt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void writeCookie(@NonNull Writer writer,
|
private static void writeCookie(@NonNull Writer writer,
|
||||||
@NonNull HttpCookie cookie, @NonNull URL url,
|
@NonNull HttpCookie cookie, @NonNull URL url,
|
||||||
@NonNull Date creationDate) throws IOException {
|
@NonNull Instant createdAt) throws IOException {
|
||||||
if (cookie.getMaxAge() <= 0) {
|
if (cookie.getMaxAge() <= 0) {
|
||||||
return; // skip expired cookies
|
return; // skip expired cookies
|
||||||
}
|
}
|
||||||
|
@ -422,7 +428,7 @@ private static void writeCookie(@NonNull Writer writer,
|
||||||
final String expirationDate;
|
final String expirationDate;
|
||||||
// whenCreated field is not accessible in HttpCookie
|
// whenCreated field is not accessible in HttpCookie
|
||||||
expirationDate = String
|
expirationDate = String
|
||||||
.valueOf(creationDate.getTime() + (cookie.getMaxAge() * 1000));
|
.valueOf(createdAt.getEpochSecond() + cookie.getMaxAge());
|
||||||
writer.write(expirationDate);
|
writer.write(expirationDate);
|
||||||
writer.write(COLUMN_SEPARATOR);
|
writer.write(COLUMN_SEPARATOR);
|
||||||
writer.write(cookie.getName());
|
writer.write(cookie.getName());
|
||||||
|
|
Loading…
Reference in New Issue