blob: 0a3f89e238e7608baf10ea4e9d0ae5b633e341ab [file] [log] [blame]
/*
* Copyright (C) 2018, Konrad Windszus <konrad_w@gmx.de>
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
* available at http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.eclipse.jgit.internal.transport.http;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.net.HttpCookie;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.time.Instant;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import org.eclipse.jgit.internal.storage.file.LockFile;
import org.eclipse.jgit.internal.transport.http.NetscapeCookieFile;
import org.eclipse.jgit.util.http.HttpCookiesMatcher;
import org.hamcrest.CoreMatchers;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
public class NetscapeCookieFileTest {
@Rule
public TemporaryFolder folder = new TemporaryFolder();
private Path tmpFile;
private URL baseUrl;
/**
* This is the expiration date that is used in the test cookie files
*/
private static long JAN_01_2030_NOON = Instant
.parse("2030-01-01T12:00:00.000Z").toEpochMilli();
@Before
public void setUp() throws IOException {
// this will not only return a new file name but also create new empty
// file!
tmpFile = folder.newFile().toPath();
baseUrl = new URL("http://domain.com/my/path");
}
@Test
public void testMergeCookies() {
Set<HttpCookie> cookieSet1 = new LinkedHashSet<>();
HttpCookie cookie = new HttpCookie("key1", "valueFromSet1");
cookieSet1.add(cookie);
cookie = new HttpCookie("key2", "valueFromSet1");
cookieSet1.add(cookie);
Set<HttpCookie> cookieSet2 = new LinkedHashSet<>();
cookie = new HttpCookie("key1", "valueFromSet2");
cookieSet2.add(cookie);
cookie = new HttpCookie("key3", "valueFromSet2");
cookieSet2.add(cookie);
Set<HttpCookie> cookiesExpectedMergedSet = new LinkedHashSet<>();
cookie = new HttpCookie("key1", "valueFromSet1");
cookiesExpectedMergedSet.add(cookie);
cookie = new HttpCookie("key2", "valueFromSet1");
cookiesExpectedMergedSet.add(cookie);
cookie = new HttpCookie("key3", "valueFromSet2");
cookiesExpectedMergedSet.add(cookie);
Assert.assertThat(
NetscapeCookieFile.mergeCookies(cookieSet1, cookieSet2),
HttpCookiesMatcher.containsInOrder(cookiesExpectedMergedSet));
Assert.assertThat(NetscapeCookieFile.mergeCookies(cookieSet1, null),
HttpCookiesMatcher.containsInOrder(cookieSet1));
}
@Test
public void testWriteToNewFile() throws IOException {
Set<HttpCookie> cookies = new LinkedHashSet<>();
cookies.add(new HttpCookie("key1", "value"));
// first cookie is a session cookie (and should be ignored)
HttpCookie cookie = new HttpCookie("key2", "value");
cookie.setSecure(true);
cookie.setDomain("mydomain.com");
cookie.setPath("/");
cookie.setMaxAge(1000);
cookies.add(cookie);
Date creationDate = new Date();
try (Writer writer = Files.newBufferedWriter(tmpFile,
StandardCharsets.US_ASCII)) {
NetscapeCookieFile.write(writer, cookies, baseUrl, creationDate);
}
String expectedExpiration = String
.valueOf(creationDate.getTime() + (cookie.getMaxAge() * 1000));
Assert.assertThat(
Files.readAllLines(tmpFile, StandardCharsets.US_ASCII),
CoreMatchers
.equalTo(Arrays.asList("mydomain.com\tTRUE\t/\tTRUE\t"
+ expectedExpiration + "\tkey2\tvalue")));
}
@Test
public void testWriteToExistingFile() throws IOException {
try (InputStream input = this.getClass()
.getResourceAsStream("cookies-simple1.txt")) {
Files.copy(input, tmpFile, StandardCopyOption.REPLACE_EXISTING);
}
Set<HttpCookie> cookies = new LinkedHashSet<>();
HttpCookie cookie = new HttpCookie("key2", "value2");
cookie.setMaxAge(1000);
cookies.add(cookie);
Date creationDate = new Date();
try (Writer writer = Files.newBufferedWriter(tmpFile,
StandardCharsets.US_ASCII)) {
NetscapeCookieFile.write(writer, cookies, baseUrl, creationDate);
}
String expectedExpiration = String
.valueOf(creationDate.getTime() + (cookie.getMaxAge() * 1000));
Assert.assertThat(
Files.readAllLines(tmpFile, StandardCharsets.US_ASCII),
CoreMatchers.equalTo(
Arrays.asList("domain.com\tTRUE\t/my/path\tFALSE\t"
+ expectedExpiration + "\tkey2\tvalue2")));
}
@Test(expected = IOException.class)
public void testWriteWhileSomeoneIsHoldingTheLock()
throws IllegalArgumentException, IOException, InterruptedException {
try (InputStream input = this.getClass()
.getResourceAsStream("cookies-simple1.txt")) {
Files.copy(input, tmpFile, StandardCopyOption.REPLACE_EXISTING);
}
NetscapeCookieFile cookieFile = new NetscapeCookieFile(tmpFile);
// now imitate another process/thread holding the lock file
LockFile lockFile = new LockFile(tmpFile.toFile());
try {
Assert.assertTrue("Could not acquire lock", lockFile.lock());
cookieFile.write(baseUrl);
} finally {
lockFile.unlock();
}
}
@Test
public void testWriteAfterAnotherJgitProcessModifiedTheFile()
throws IOException, InterruptedException {
try (InputStream input = this.getClass()
.getResourceAsStream("cookies-simple1.txt")) {
Files.copy(input, tmpFile, StandardCopyOption.REPLACE_EXISTING);
}
NetscapeCookieFile cookieFile = new NetscapeCookieFile(tmpFile);
cookieFile.getCookies(true);
// now modify file externally
try (InputStream input = this.getClass()
.getResourceAsStream("cookies-simple2.txt")) {
Files.copy(input, tmpFile, StandardCopyOption.REPLACE_EXISTING);
}
// now try to write
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,
StandardCharsets.US_ASCII);
Assert.assertEquals("Expected 3 lines", 3, lines.size());
assertStringMatchesPatternWithInexactNumber(lines.get(0),
"some-domain1\tTRUE\t/some/path1\tFALSE\t(\\d*)\tkey1\tvalueFromSimple2",
JAN_01_2030_NOON, 1000);
assertStringMatchesPatternWithInexactNumber(lines.get(1),
"some-domain1\tTRUE\t/some/path1\tFALSE\t(\\d*)\tkey3\tvalueFromSimple2",
JAN_01_2030_NOON, 1000);
assertStringMatchesPatternWithInexactNumber(lines.get(2),
"some-domain1\tTRUE\t/some/path1\tFALSE\t(\\d*)\tkey2\tvalueFromSimple1",
JAN_01_2030_NOON, 1000);
}
@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);
Assert.assertTrue("Given string '" + string + "' does not match '"
+ pattern + "'", matcher.matches());
// extract numeric value
Long actualNumericValue = Long.decode(matcher.group(1));
Assert.assertTrue(
"Value is supposed to be close to " + expectedNumericValue
+ " but is " + actualNumericValue + ".",
Math.abs(expectedNumericValue - actualNumericValue) <= delta);
}
@Test
public void testWriteAndReadCycle() throws IOException {
Set<HttpCookie> cookies = new LinkedHashSet<>();
HttpCookie cookie = new HttpCookie("key1", "value1");
cookie.setPath("/some/path1");
cookie.setDomain("some-domain1");
cookie.setMaxAge(1000);
cookies.add(cookie);
cookie = new HttpCookie("key2", "value2");
cookie.setSecure(true);
cookie.setPath("/some/path2");
cookie.setDomain("some-domain2");
cookie.setMaxAge(1000);
cookie.setHttpOnly(true);
cookies.add(cookie);
Date creationDate = new Date();
try (Writer writer = Files.newBufferedWriter(tmpFile,
StandardCharsets.US_ASCII)) {
NetscapeCookieFile.write(writer, cookies, baseUrl, creationDate);
}
Set<HttpCookie> actualCookies = new NetscapeCookieFile(tmpFile,
creationDate).getCookies(true);
Assert.assertThat(actualCookies,
HttpCookiesMatcher.containsInOrder(cookies));
}
@Test
public void testReadAndWriteCycle() throws IOException {
try (InputStream input = this.getClass()
.getResourceAsStream("cookies-simple1.txt")) {
Files.copy(input, tmpFile, StandardCopyOption.REPLACE_EXISTING);
}
// round up to the next second (to prevent rounding errors)
Date creationDate = new Date(
(System.currentTimeMillis() / 1000) * 1000);
Set<HttpCookie> cookies = new NetscapeCookieFile(tmpFile, creationDate)
.getCookies(true);
Path tmpFile2 = folder.newFile().toPath();
try (Writer writer = Files.newBufferedWriter(tmpFile2,
StandardCharsets.US_ASCII)) {
NetscapeCookieFile.write(writer, cookies, baseUrl, creationDate);
}
// compare original file with newly written one, they should not differ
Assert.assertEquals(Files.readAllLines(tmpFile),
Files.readAllLines(tmpFile2));
}
@Test
public void testReadWithEmptyAndCommentLines() throws IOException {
try (InputStream input = this.getClass().getResourceAsStream(
"cookies-with-empty-and-comment-lines.txt")) {
Files.copy(input, tmpFile, StandardCopyOption.REPLACE_EXISTING);
}
Date creationDate = new Date();
Set<HttpCookie> cookies = new LinkedHashSet<>();
HttpCookie cookie = new HttpCookie("key2", "value2");
cookie.setDomain("some-domain2");
cookie.setPath("/some/path2");
cookie.setMaxAge((JAN_01_2030_NOON - creationDate.getTime()) / 1000);
cookie.setSecure(true);
cookie.setHttpOnly(true);
cookies.add(cookie);
cookie = new HttpCookie("key3", "value3");
cookie.setDomain("some-domain3");
cookie.setPath("/some/path3");
cookie.setMaxAge((JAN_01_2030_NOON - creationDate.getTime()) / 1000);
cookies.add(cookie);
Set<HttpCookie> actualCookies = new NetscapeCookieFile(tmpFile, creationDate)
.getCookies(true);
Assert.assertThat(actualCookies,
HttpCookiesMatcher.containsInOrder(cookies));
}
@Test
public void testReadInvalidFile() throws IOException {
try (InputStream input = this.getClass()
.getResourceAsStream("cookies-invalid.txt")) {
Files.copy(input, tmpFile, StandardCopyOption.REPLACE_EXISTING);
}
new NetscapeCookieFile(tmpFile)
.getCookies(true);
}
}