View Javadoc
1   /*
2    * Copyright (C) 2018, Konrad Windszus <konrad_w@gmx.de> and others
3    *
4    * This program and the accompanying materials are made available under the
5    * terms of the Eclipse Distribution License v. 1.0 which is available at
6    * https://www.eclipse.org/org/documents/edl-v10.php.
7    *
8    * SPDX-License-Identifier: BSD-3-Clause
9    */
10  package org.eclipse.jgit.internal.transport.http;
11  
12  import static org.hamcrest.MatcherAssert.assertThat;
13  import static org.junit.Assert.assertEquals;
14  import static org.junit.Assert.assertTrue;
15  
16  import java.io.IOException;
17  import java.io.InputStream;
18  import java.io.Writer;
19  import java.net.HttpCookie;
20  import java.net.URL;
21  import java.nio.charset.StandardCharsets;
22  import java.nio.file.Files;
23  import java.nio.file.Path;
24  import java.nio.file.StandardCopyOption;
25  import java.time.Duration;
26  import java.time.Instant;
27  import java.time.temporal.ChronoUnit;
28  import java.util.Arrays;
29  import java.util.LinkedHashSet;
30  import java.util.List;
31  import java.util.Set;
32  
33  import org.eclipse.jgit.internal.storage.file.LockFile;
34  import org.eclipse.jgit.util.http.HttpCookiesMatcher;
35  import org.hamcrest.CoreMatchers;
36  import org.junit.Before;
37  import org.junit.Rule;
38  import org.junit.Test;
39  import org.junit.rules.TemporaryFolder;
40  
41  public class NetscapeCookieFileTest {
42  
43  	@Rule
44  	public TemporaryFolder folder = new TemporaryFolder();
45  
46  	private Path tmpFile;
47  
48  	private URL baseUrl;
49  
50  	/**
51  	 * This is the expiration date that is used in the test cookie files.
52  	 */
53  	private static final Instant TEST_EXPIRY_DATE = Instant
54  			.parse("2030-01-01T12:00:00.000Z");
55  
56  	/** Earlier than TEST_EXPIRY_DATE. */
57  	private static final Instant TEST_DATE = TEST_EXPIRY_DATE.minus(180,
58  			ChronoUnit.DAYS);
59  
60  	@Before
61  	public void setUp() throws IOException {
62  		// this will not only return a new file name but also create new empty
63  		// file!
64  		tmpFile = folder.newFile().toPath();
65  		baseUrl = new URL("http://domain.com/my/path");
66  	}
67  
68  	@Test
69  	public void testMergeCookies() {
70  		Set<HttpCookie> cookieSet1 = new LinkedHashSet<>();
71  		HttpCookie cookie = new HttpCookie("key1", "valueFromSet1");
72  		cookieSet1.add(cookie);
73  		cookie = new HttpCookie("key2", "valueFromSet1");
74  		cookieSet1.add(cookie);
75  
76  		Set<HttpCookie> cookieSet2 = new LinkedHashSet<>();
77  		cookie = new HttpCookie("key1", "valueFromSet2");
78  		cookieSet2.add(cookie);
79  		cookie = new HttpCookie("key3", "valueFromSet2");
80  		cookieSet2.add(cookie);
81  
82  		Set<HttpCookie> cookiesExpectedMergedSet = new LinkedHashSet<>();
83  		cookie = new HttpCookie("key1", "valueFromSet1");
84  		cookiesExpectedMergedSet.add(cookie);
85  		cookie = new HttpCookie("key2", "valueFromSet1");
86  		cookiesExpectedMergedSet.add(cookie);
87  		cookie = new HttpCookie("key3", "valueFromSet2");
88  		cookiesExpectedMergedSet.add(cookie);
89  
90  		assertThat(NetscapeCookieFile.mergeCookies(cookieSet1, cookieSet2),
91  				HttpCookiesMatcher.containsInOrder(cookiesExpectedMergedSet));
92  
93  		assertThat(NetscapeCookieFile.mergeCookies(cookieSet1, null),
94  				HttpCookiesMatcher.containsInOrder(cookieSet1));
95  	}
96  
97  	@Test
98  	public void testWriteToNewFile() throws IOException {
99  		Set<HttpCookie> cookies = new LinkedHashSet<>();
100 		cookies.add(new HttpCookie("key1", "value"));
101 		// first cookie is a session cookie (and should be ignored)
102 
103 		HttpCookie cookie = new HttpCookie("key2", "value");
104 		cookie.setSecure(true);
105 		cookie.setDomain("mydomain.com");
106 		cookie.setPath("/");
107 		cookie.setMaxAge(1000);
108 		cookies.add(cookie);
109 		try (Writer writer = Files.newBufferedWriter(tmpFile,
110 				StandardCharsets.US_ASCII)) {
111 			NetscapeCookieFile.write(writer, cookies, baseUrl, TEST_DATE);
112 		}
113 
114 		String expectedExpiration = String
115 				.valueOf(TEST_DATE.getEpochSecond() + cookie.getMaxAge());
116 
117 		assertThat(Files.readAllLines(tmpFile, StandardCharsets.US_ASCII),
118 				CoreMatchers
119 						.equalTo(Arrays.asList("mydomain.com\tTRUE\t/\tTRUE\t"
120 								+ expectedExpiration + "\tkey2\tvalue")));
121 	}
122 
123 	@Test
124 	public void testWriteToExistingFile() throws IOException {
125 		try (InputStream input = this.getClass()
126 				.getResourceAsStream("cookies-simple1.txt")) {
127 			Files.copy(input, tmpFile, StandardCopyOption.REPLACE_EXISTING);
128 		}
129 
130 		Set<HttpCookie> cookies = new LinkedHashSet<>();
131 		HttpCookie cookie = new HttpCookie("key2", "value2");
132 		cookie.setMaxAge(1000);
133 		cookies.add(cookie);
134 		try (Writer writer = Files.newBufferedWriter(tmpFile,
135 				StandardCharsets.US_ASCII)) {
136 			NetscapeCookieFile.write(writer, cookies, baseUrl, TEST_DATE);
137 		}
138 		String expectedExpiration = String
139 				.valueOf(TEST_DATE.getEpochSecond() + cookie.getMaxAge());
140 
141 		assertThat(Files.readAllLines(tmpFile, StandardCharsets.US_ASCII),
142 				CoreMatchers.equalTo(
143 						Arrays.asList("domain.com\tTRUE\t/my/path\tFALSE\t"
144 								+ expectedExpiration + "\tkey2\tvalue2")));
145 	}
146 
147 	@Test(expected = IOException.class)
148 	public void testWriteWhileSomeoneIsHoldingTheLock()
149 			throws IllegalArgumentException, IOException, InterruptedException {
150 		try (InputStream input = this.getClass()
151 				.getResourceAsStream("cookies-simple1.txt")) {
152 			Files.copy(input, tmpFile, StandardCopyOption.REPLACE_EXISTING);
153 		}
154 		NetscapeCookieFile cookieFile = new NetscapeCookieFile(tmpFile);
155 		// now imitate another process/thread holding the lock file
156 		LockFile lockFile = new LockFile(tmpFile.toFile());
157 		try {
158 			assertTrue("Could not acquire lock", lockFile.lock());
159 			cookieFile.write(baseUrl);
160 		} finally {
161 			lockFile.unlock();
162 		}
163 	}
164 
165 	@Test
166 	public void testReadCookieFileWithMilliseconds() throws IOException {
167 		try (InputStream input = this.getClass()
168 				.getResourceAsStream("cookies-with-milliseconds.txt")) {
169 			Files.copy(input, tmpFile, StandardCopyOption.REPLACE_EXISTING);
170 		}
171 		NetscapeCookieFile cookieFile = new NetscapeCookieFile(tmpFile,
172 				TEST_DATE);
173 		long expectedMaxAge = Duration.between(TEST_DATE, TEST_EXPIRY_DATE)
174 				.getSeconds();
175 		for (HttpCookie cookie : cookieFile.getCookies(true)) {
176 			assertEquals(expectedMaxAge, cookie.getMaxAge());
177 		}
178 	}
179 
180 	@Test
181 	public void testWriteAfterAnotherJgitProcessModifiedTheFile()
182 			throws IOException, InterruptedException {
183 		try (InputStream input = this.getClass()
184 				.getResourceAsStream("cookies-simple1.txt")) {
185 			Files.copy(input, tmpFile, StandardCopyOption.REPLACE_EXISTING);
186 		}
187 		NetscapeCookieFile cookieFile = new NetscapeCookieFile(tmpFile,
188 				TEST_DATE);
189 		cookieFile.getCookies(true);
190 		// now modify file externally
191 		try (InputStream input = this.getClass()
192 				.getResourceAsStream("cookies-simple2.txt")) {
193 			Files.copy(input, tmpFile, StandardCopyOption.REPLACE_EXISTING);
194 		}
195 		// now try to write
196 		cookieFile.write(baseUrl);
197 
198 		List<String> lines = Files.readAllLines(tmpFile,
199 				StandardCharsets.US_ASCII);
200 
201 		assertEquals("Expected 3 lines", 3, lines.size());
202 		assertEquals(
203 				"some-domain1\tTRUE\t/some/path1\tFALSE\t1893499200\tkey1\tvalueFromSimple2",
204 				lines.get(0));
205 		assertEquals(
206 				"some-domain1\tTRUE\t/some/path1\tFALSE\t1893499200\tkey3\tvalueFromSimple2",
207 				lines.get(1));
208 		assertEquals(
209 				"some-domain1\tTRUE\t/some/path1\tFALSE\t1893499200\tkey2\tvalueFromSimple1",
210 				lines.get(2));
211 	}
212 
213 	@Test
214 	public void testWriteAndReadCycle() throws IOException {
215 		Set<HttpCookie> cookies = new LinkedHashSet<>();
216 
217 		HttpCookie cookie = new HttpCookie("key1", "value1");
218 		cookie.setPath("/some/path1");
219 		cookie.setDomain("some-domain1");
220 		cookie.setMaxAge(1000);
221 		cookies.add(cookie);
222 		cookie = new HttpCookie("key2", "value2");
223 		cookie.setSecure(true);
224 		cookie.setPath("/some/path2");
225 		cookie.setDomain("some-domain2");
226 		cookie.setMaxAge(1000);
227 		cookie.setHttpOnly(true);
228 		cookies.add(cookie);
229 
230 		try (Writer writer = Files.newBufferedWriter(tmpFile,
231 				StandardCharsets.US_ASCII)) {
232 			NetscapeCookieFile.write(writer, cookies, baseUrl, TEST_DATE);
233 		}
234 		Set<HttpCookie> actualCookies = new NetscapeCookieFile(tmpFile,
235 				TEST_DATE)
236 				.getCookies(true);
237 		assertThat(actualCookies, HttpCookiesMatcher.containsInOrder(cookies));
238 	}
239 
240 	@Test
241 	public void testReadAndWriteCycle() throws IOException {
242 		try (InputStream input = this.getClass()
243 				.getResourceAsStream("cookies-simple1.txt")) {
244 			Files.copy(input, tmpFile, StandardCopyOption.REPLACE_EXISTING);
245 		}
246 		Set<HttpCookie> cookies = new NetscapeCookieFile(tmpFile, TEST_DATE)
247 				.getCookies(true);
248 		Path tmpFile2 = folder.newFile().toPath();
249 		try (Writer writer = Files.newBufferedWriter(tmpFile2,
250 				StandardCharsets.US_ASCII)) {
251 			NetscapeCookieFile.write(writer, cookies, baseUrl, TEST_DATE);
252 		}
253 		// compare original file with newly written one, they should not differ
254 		assertEquals(Files.readAllLines(tmpFile), Files.readAllLines(tmpFile2));
255 	}
256 
257 	@Test
258 	public void testReadWithEmptyAndCommentLines() throws IOException {
259 		try (InputStream input = this.getClass().getResourceAsStream(
260 				"cookies-with-empty-and-comment-lines.txt")) {
261 			Files.copy(input, tmpFile, StandardCopyOption.REPLACE_EXISTING);
262 		}
263 
264 		Set<HttpCookie> cookies = new LinkedHashSet<>();
265 
266 		HttpCookie cookie = new HttpCookie("key2", "value2");
267 		cookie.setDomain("some-domain2");
268 		cookie.setPath("/some/path2");
269 		cookie.setMaxAge(
270 				Duration.between(TEST_DATE, TEST_EXPIRY_DATE).getSeconds());
271 		cookie.setSecure(true);
272 		cookie.setHttpOnly(true);
273 		cookies.add(cookie);
274 
275 		cookie = new HttpCookie("key3", "value3");
276 		cookie.setDomain("some-domain3");
277 		cookie.setPath("/some/path3");
278 		cookie.setMaxAge(
279 				Duration.between(TEST_DATE, TEST_EXPIRY_DATE).getSeconds());
280 		cookies.add(cookie);
281 
282 		Set<HttpCookie> actualCookies = new NetscapeCookieFile(tmpFile,
283 				TEST_DATE).getCookies(true);
284 		assertThat(actualCookies, HttpCookiesMatcher.containsInOrder(cookies));
285 	}
286 
287 	@Test
288 	public void testReadInvalidFile() throws IOException {
289 		try (InputStream input = this.getClass()
290 				.getResourceAsStream("cookies-invalid.txt")) {
291 			Files.copy(input, tmpFile, StandardCopyOption.REPLACE_EXISTING);
292 		}
293 
294 		assertTrue(new NetscapeCookieFile(tmpFile, TEST_DATE).getCookies(true)
295 				.isEmpty());
296 	}
297 }