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.transport;
11  
12  import static org.hamcrest.MatcherAssert.assertThat;
13  
14  import java.io.File;
15  import java.io.IOException;
16  import java.net.HttpCookie;
17  import java.time.Instant;
18  import java.util.Arrays;
19  import java.util.Collections;
20  import java.util.Date;
21  import java.util.LinkedHashMap;
22  import java.util.LinkedHashSet;
23  import java.util.Map;
24  import java.util.Set;
25  
26  import org.eclipse.jgit.internal.transport.http.NetscapeCookieFile;
27  import org.eclipse.jgit.lib.Config;
28  import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
29  import org.eclipse.jgit.transport.http.HttpConnection;
30  import org.eclipse.jgit.util.http.HttpCookiesMatcher;
31  import org.junit.Assert;
32  import org.junit.Before;
33  import org.junit.Test;
34  import org.mockito.ArgumentMatchers;
35  import org.mockito.Mockito;
36  
37  public class TransportHttpTest extends SampleDataRepositoryTestCase {
38  	private URIish uri;
39  	private File cookieFile;
40  
41  	@Override
42  	@Before
43  	public void setUp() throws Exception {
44  		super.setUp();
45  		uri = new URIish("https://everyones.loves.git/u/2");
46  
47  		final Config config = db.getConfig();
48  		config.setBoolean("http", null, "saveCookies", true);
49  		cookieFile = createTempFile();
50  		config.setString("http", null, "cookieFile",
51  				cookieFile.getAbsolutePath());
52  	}
53  
54  	@Test
55  	public void testMatchesCookieDomain() {
56  		Assert.assertTrue(TransportHttp.matchesCookieDomain("example.com",
57  				"example.com"));
58  		Assert.assertTrue(TransportHttp.matchesCookieDomain("Example.Com",
59  				"example.cOM"));
60  		Assert.assertTrue(TransportHttp.matchesCookieDomain(
61  				"some.subdomain.example.com", "example.com"));
62  		Assert.assertFalse(TransportHttp
63  				.matchesCookieDomain("someotherexample.com", "example.com"));
64  		Assert.assertFalse(TransportHttp.matchesCookieDomain("example.com",
65  				"example1.com"));
66  		Assert.assertFalse(TransportHttp
67  				.matchesCookieDomain("sub.sub.example.com", ".example.com"));
68  		Assert.assertTrue(TransportHttp.matchesCookieDomain("host.example.com",
69  				"example.com"));
70  		Assert.assertTrue(TransportHttp.matchesCookieDomain(
71  				"something.example.com", "something.example.com"));
72  		Assert.assertTrue(TransportHttp.matchesCookieDomain(
73  				"host.something.example.com", "something.example.com"));
74  	}
75  
76  	@Test
77  	public void testMatchesCookiePath() {
78  		Assert.assertTrue(
79  				TransportHttp.matchesCookiePath("/some/path", "/some/path"));
80  		Assert.assertTrue(TransportHttp.matchesCookiePath("/some/path/child",
81  				"/some/path"));
82  		Assert.assertTrue(TransportHttp.matchesCookiePath("/some/path/child",
83  				"/some/path/"));
84  		Assert.assertFalse(TransportHttp.matchesCookiePath("/some/pathother",
85  				"/some/path"));
86  		Assert.assertFalse(
87  				TransportHttp.matchesCookiePath("otherpath", "/some/path"));
88  	}
89  
90  	@Test
91  	public void testProcessResponseCookies() throws IOException {
92  		HttpConnection connection = Mockito.mock(HttpConnection.class);
93  		Mockito.when(
94  				connection.getHeaderFields(ArgumentMatchers.eq("Set-Cookie")))
95  				.thenReturn(Arrays.asList(
96  						"id=a3fWa; Expires=Fri, 01 Jan 2100 11:00:00 GMT; Secure; HttpOnly",
97  						"sessionid=38afes7a8; HttpOnly; Path=/"));
98  		Mockito.when(
99  				connection.getHeaderFields(ArgumentMatchers.eq("Set-Cookie2")))
100 				.thenReturn(Collections
101 						.singletonList("cookie2=some value; Max-Age=1234; Path=/"));
102 
103 		try (TransportHttp transportHttp = new TransportHttp(db, uri)) {
104 			Date creationDate = new Date();
105 			transportHttp.processResponseCookies(connection);
106 
107 			// evaluate written cookie file
108 			Set<HttpCookie> expectedCookies = new LinkedHashSet<>();
109 
110 			HttpCookie cookie = new HttpCookie("id", "a3fWa");
111 			cookie.setDomain("everyones.loves.git");
112 			cookie.setPath("/u/2/");
113 
114 			cookie.setMaxAge(
115 					(Instant.parse("2100-01-01T11:00:00.000Z").toEpochMilli()
116 							- creationDate.getTime()) / 1000);
117 			cookie.setSecure(true);
118 			cookie.setHttpOnly(true);
119 			expectedCookies.add(cookie);
120 
121 			cookie = new HttpCookie("cookie2", "some value");
122 			cookie.setDomain("everyones.loves.git");
123 			cookie.setPath("/");
124 			cookie.setMaxAge(1234);
125 			expectedCookies.add(cookie);
126 
127 			assertThat(
128 					new NetscapeCookieFile(cookieFile.toPath())
129 							.getCookies(true),
130 					HttpCookiesMatcher.containsInOrder(expectedCookies, 5));
131 		}
132 	}
133 
134 	@Test
135 	public void testProcessResponseCookiesNotPersistingWithSaveCookiesFalse()
136 			throws IOException {
137 		HttpConnection connection = Mockito.mock(HttpConnection.class);
138 		Mockito.when(
139 				connection.getHeaderFields(ArgumentMatchers.eq("Set-Cookie")))
140 				.thenReturn(Arrays.asList(
141 						"id=a3fWa; Expires=Thu, 21 Oct 2100 11:00:00 GMT; Secure; HttpOnly",
142 						"sessionid=38afes7a8; HttpOnly; Path=/"));
143 		Mockito.when(
144 				connection.getHeaderFields(ArgumentMatchers.eq("Set-Cookie2")))
145 				.thenReturn(Collections.singletonList(
146 						"cookie2=some value; Max-Age=1234; Path=/"));
147 
148 		// tweak config
149 		final Config config = db.getConfig();
150 		config.setBoolean("http", null, "saveCookies", false);
151 
152 		try (TransportHttp transportHttp = new TransportHttp(db, uri)) {
153 			transportHttp.processResponseCookies(connection);
154 
155 			// evaluate written cookie file
156 			Assert.assertFalse("Cookie file was not supposed to be written!",
157 					cookieFile.exists());
158 		}
159 	}
160 
161 	private void assertHeaders(String expected, String... headersToAdd) {
162 		HttpConnection fake = Mockito.mock(HttpConnection.class);
163 		Map<String, String> headers = new LinkedHashMap<>();
164 		Mockito.doAnswer(invocation -> {
165 			Object[] args = invocation.getArguments();
166 			headers.put(args[0].toString(), args[1].toString());
167 			return null;
168 		}).when(fake).setRequestProperty(ArgumentMatchers.anyString(),
169 				ArgumentMatchers.anyString());
170 		TransportHttp.addHeaders(fake, Arrays.asList(headersToAdd));
171 		Assert.assertEquals(expected, headers.toString());
172 	}
173 
174 	@Test
175 	public void testAddHeaders() {
176 		assertHeaders("{a=b, c=d}", "a: b", "c :d");
177 	}
178 
179 	@Test
180 	public void testAddHeaderEmptyValue() {
181 		assertHeaders("{a-x=b, c=, d=e}", "a-x: b", "c:", "d:e");
182 	}
183 
184 	@Test
185 	public void testSkipHeaderWithEmptyKey() {
186 		assertHeaders("{a=b, c=d}", "a: b", " : x", "c :d");
187 		assertHeaders("{a=b, c=d}", "a: b", ": x", "c :d");
188 	}
189 
190 	@Test
191 	public void testSkipHeaderWithoutKey() {
192 		assertHeaders("{a=b, c=d}", "a: b", "x", "c :d");
193 	}
194 
195 	@Test
196 	public void testSkipHeaderWithInvalidKey() {
197 		assertHeaders("{a=b, c=d}", "a: b", "q/p: x", "c :d");
198 		assertHeaders("{a=b, c=d}", "a: b", "ä: x", "c :d");
199 	}
200 
201 	@Test
202 	public void testSkipHeaderWithNonAsciiValue() {
203 		assertHeaders("{a=b, c=d}", "a: b", "q/p: x", "c :d");
204 		assertHeaders("{a=b, c=d}", "a: b", "x: ä", "c :d");
205 	}
206 }