View Javadoc
1   /*
2    * Copyright (C) 2009-2010, Google Inc. 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  
11  package org.eclipse.jgit.http.test;
12  
13  import static org.hamcrest.MatcherAssert.assertThat;
14  import static org.hamcrest.Matchers.is;
15  import static org.junit.Assert.assertEquals;
16  import static org.junit.Assert.assertFalse;
17  import static org.junit.Assert.assertNotNull;
18  import static org.junit.Assert.assertNull;
19  import static org.junit.Assert.assertTrue;
20  import static org.junit.Assert.fail;
21  
22  import java.io.File;
23  import java.io.OutputStream;
24  import java.net.URI;
25  import java.net.URL;
26  import java.util.List;
27  
28  import javax.servlet.http.HttpServletRequest;
29  
30  import org.eclipse.jetty.servlet.DefaultServlet;
31  import org.eclipse.jetty.servlet.ServletContextHandler;
32  import org.eclipse.jetty.servlet.ServletHolder;
33  import org.eclipse.jgit.errors.NoRemoteRepositoryException;
34  import org.eclipse.jgit.errors.RepositoryNotFoundException;
35  import org.eclipse.jgit.errors.TransportException;
36  import org.eclipse.jgit.http.server.GitServlet;
37  import org.eclipse.jgit.internal.JGitText;
38  import org.eclipse.jgit.junit.TestRepository;
39  import org.eclipse.jgit.junit.http.AccessEvent;
40  import org.eclipse.jgit.junit.http.AppServer;
41  import org.eclipse.jgit.lib.Constants;
42  import org.eclipse.jgit.lib.Ref;
43  import org.eclipse.jgit.lib.RefUpdate;
44  import org.eclipse.jgit.lib.Repository;
45  import org.eclipse.jgit.lib.StoredConfig;
46  import org.eclipse.jgit.revwalk.RevCommit;
47  import org.eclipse.jgit.transport.FetchConnection;
48  import org.eclipse.jgit.transport.HttpTransport;
49  import org.eclipse.jgit.transport.PacketLineIn;
50  import org.eclipse.jgit.transport.PacketLineOut;
51  import org.eclipse.jgit.transport.Transport;
52  import org.eclipse.jgit.transport.URIish;
53  import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
54  import org.eclipse.jgit.transport.http.HttpConnection;
55  import org.eclipse.jgit.transport.http.HttpConnectionFactory;
56  import org.junit.Before;
57  import org.junit.Test;
58  
59  public class HttpClientTests extends AllFactoriesHttpTestCase {
60  
61  	private TestRepository<Repository> remoteRepository;
62  
63  	private URIish dumbAuthNoneURI;
64  
65  	private URIish dumbAuthBasicURI;
66  
67  	private URIish smartAuthNoneURI;
68  
69  	private URIish smartAuthBasicURI;
70  
71  	public HttpClientTests(HttpConnectionFactory cf) {
72  		super(cf);
73  	}
74  
75  	@Override
76  	@Before
77  	public void setUp() throws Exception {
78  		super.setUp();
79  
80  		remoteRepository = createTestRepository();
81  		remoteRepository.update(master, remoteRepository.commit().create());
82  
83  		ServletContextHandler dNone = dumb("/dnone");
84  		ServletContextHandler dBasic = server.authBasic(dumb("/dbasic"));
85  
86  		ServletContextHandler sNone = smart("/snone");
87  		ServletContextHandler sBasic = server.authBasic(smart("/sbasic"));
88  
89  		server.setUp();
90  
91  		final String srcName = nameOf(remoteRepository.getRepository());
92  		dumbAuthNoneURI = toURIish(dNone, srcName);
93  		dumbAuthBasicURI = toURIish(dBasic, srcName);
94  
95  		smartAuthNoneURI = toURIish(sNone, srcName);
96  		smartAuthBasicURI = toURIish(sBasic, srcName);
97  	}
98  
99  	private ServletContextHandler dumb(String path) {
100 		final File srcGit = remoteRepository.getRepository().getDirectory();
101 		final URI base = srcGit.getParentFile().toURI();
102 
103 		ServletContextHandler ctx = server.addContext(path);
104 		ctx.setResourceBase(base.toString());
105 		ServletHolder holder = ctx.addServlet(DefaultServlet.class, "/");
106 		// The tmp directory is symlinked on OS X
107 		holder.setInitParameter("aliases", "true");
108 		return ctx;
109 	}
110 
111 	private ServletContextHandler smart(String path) {
112 		GitServlet gs = new GitServlet();
113 		gs.setRepositoryResolver((HttpServletRequest req, String name) -> {
114 			final Repository db = remoteRepository.getRepository();
115 			if (!name.equals(nameOf(db))) {
116 				throw new RepositoryNotFoundException(name);
117 			}
118 			db.incrementOpen();
119 			return db;
120 		});
121 
122 		ServletContextHandler ctx = server.addContext(path);
123 		ctx.addServlet(new ServletHolder(gs), "/*");
124 		return ctx;
125 	}
126 
127 	private static String nameOf(Repository db) {
128 		return db.getDirectory().getName();
129 	}
130 
131 	@Test
132 	public void testRepositoryNotFound_Dumb() throws Exception {
133 		URIish uri = toURIish("/dumb.none/not-found");
134 		Repository dst = createBareRepository();
135 		try (Transport t = Transport.open(dst, uri)) {
136 			try {
137 				t.openFetch();
138 				fail("connection opened to not found repository");
139 			} catch (NoRemoteRepositoryException err) {
140 				String exp = uri + ": " + uri
141 						+ "/info/refs?service=git-upload-pack not found";
142 				assertNotNull(err.getMessage());
143 				assertTrue("Unexpected error message",
144 						err.getMessage().startsWith(exp));
145 			}
146 		}
147 	}
148 
149 	@Test
150 	public void testRepositoryNotFound_Smart() throws Exception {
151 		URIish uri = toURIish("/smart.none/not-found");
152 		Repository dst = createBareRepository();
153 		try (Transport t = Transport.open(dst, uri)) {
154 			try {
155 				t.openFetch();
156 				fail("connection opened to not found repository");
157 			} catch (NoRemoteRepositoryException err) {
158 				String exp = uri + ": " + uri
159 						+ "/info/refs?service=git-upload-pack not found";
160 				assertNotNull(err.getMessage());
161 				assertTrue("Unexpected error message",
162 						err.getMessage().startsWith(exp));
163 			}
164 		}
165 	}
166 
167 	@Test
168 	public void testListRemote_Dumb_DetachedHEAD() throws Exception {
169 		Repository src = remoteRepository.getRepository();
170 		RefUpdate u = src.updateRef(Constants.HEAD, true);
171 		RevCommit Q = remoteRepository.commit().message("Q").create();
172 		u.setNewObjectId(Q);
173 		assertEquals(RefUpdate.Result.FORCED, u.forceUpdate());
174 
175 		Repository dst = createBareRepository();
176 		Ref head;
177 		try (Transport t = Transport.open(dst, dumbAuthNoneURI);
178 				FetchConnection c = t.openFetch()) {
179 			head = c.getRef(Constants.HEAD);
180 		}
181 		assertNotNull("has " + Constants.HEAD, head);
182 		assertEquals(Q, head.getObjectId());
183 	}
184 
185 	@Test
186 	public void testListRemote_Dumb_NoHEAD() throws Exception {
187 		Repository src = remoteRepository.getRepository();
188 		File headref = new File(src.getDirectory(), Constants.HEAD);
189 		assertTrue("HEAD used to be present", headref.delete());
190 		assertFalse("HEAD is gone", headref.exists());
191 
192 		Repository dst = createBareRepository();
193 		Ref head;
194 		try (Transport t = Transport.open(dst, dumbAuthNoneURI);
195 				FetchConnection c = t.openFetch()) {
196 			head = c.getRef(Constants.HEAD);
197 		}
198 		assertNull("has no " + Constants.HEAD, head);
199 	}
200 
201 	@Test
202 	public void testListRemote_Smart_DetachedHEAD() throws Exception {
203 		Repository src = remoteRepository.getRepository();
204 		RefUpdate u = src.updateRef(Constants.HEAD, true);
205 		RevCommit Q = remoteRepository.commit().message("Q").create();
206 		u.setNewObjectId(Q);
207 		assertEquals(RefUpdate.Result.FORCED, u.forceUpdate());
208 
209 		Repository dst = createBareRepository();
210 		Ref head;
211 		try (Transport t = Transport.open(dst, smartAuthNoneURI);
212 				FetchConnection c = t.openFetch()) {
213 			head = c.getRef(Constants.HEAD);
214 		}
215 		assertNotNull("has " + Constants.HEAD, head);
216 		assertEquals(Q, head.getObjectId());
217 	}
218 
219 	@Test
220 	public void testListRemote_Smart_WithQueryParameters() throws Exception {
221 		URIish myURI = toURIish("/snone/do?r=1&p=test.git");
222 		Repository dst = createBareRepository();
223 		try (Transport t = Transport.open(dst, myURI)) {
224 			try {
225 				t.openFetch();
226 				fail("test did not fail to find repository as expected");
227 			} catch (NoRemoteRepositoryException err) {
228 				// expected
229 			}
230 		}
231 
232 		List<AccessEvent> requests = getRequests();
233 		assertEquals(1, requests.size());
234 
235 		AccessEvent info = requests.get(0);
236 		assertEquals("GET", info.getMethod());
237 		assertEquals("/snone/do", info.getPath());
238 		assertEquals(3, info.getParameters().size());
239 		assertEquals("1", info.getParameter("r"));
240 		assertEquals("test.git/info/refs", info.getParameter("p"));
241 		assertEquals("git-upload-pack", info.getParameter("service"));
242 		assertEquals(404, info.getStatus());
243 	}
244 
245 	@Test
246 	public void testListRemote_Dumb_NeedsAuth() throws Exception {
247 		Repository dst = createBareRepository();
248 		try (Transport t = Transport.open(dst, dumbAuthBasicURI)) {
249 			try {
250 				t.openFetch();
251 				fail("connection opened even info/refs needs auth basic");
252 			} catch (TransportException err) {
253 				String exp = dumbAuthBasicURI + ": "
254 						+ JGitText.get().noCredentialsProvider;
255 				assertEquals(exp, err.getMessage());
256 			}
257 		}
258 	}
259 
260 	@Test
261 	public void testListRemote_Dumb_Auth() throws Exception {
262 		Repository dst = createBareRepository();
263 		try (Transport t = Transport.open(dst, dumbAuthBasicURI)) {
264 			t.setCredentialsProvider(new UsernamePasswordCredentialsProvider(
265 					AppServer.username, AppServer.password));
266 			t.openFetch().close();
267 		}
268 		try (Transport t = Transport.open(dst, dumbAuthBasicURI)) {
269 			t.setCredentialsProvider(new UsernamePasswordCredentialsProvider(
270 					AppServer.username, ""));
271 			try {
272 				t.openFetch();
273 				fail("connection opened even info/refs needs auth basic and we provide wrong password");
274 			} catch (TransportException err) {
275 				String exp = dumbAuthBasicURI + ": "
276 						+ JGitText.get().notAuthorized;
277 				assertEquals(exp, err.getMessage());
278 			}
279 		}
280 	}
281 
282 	@Test
283 	public void testListRemote_Smart_UploadPackNeedsAuth() throws Exception {
284 		Repository dst = createBareRepository();
285 		try (Transport t = Transport.open(dst, smartAuthBasicURI)) {
286 			try {
287 				t.openFetch();
288 				fail("connection opened even though service disabled");
289 			} catch (TransportException err) {
290 				String exp = smartAuthBasicURI + ": "
291 						+ JGitText.get().noCredentialsProvider;
292 				assertEquals(exp, err.getMessage());
293 			}
294 		}
295 	}
296 
297 	@Test
298 	public void testListRemote_Smart_UploadPackDisabled() throws Exception {
299 		Repository src = remoteRepository.getRepository();
300 		final StoredConfig cfg = src.getConfig();
301 		cfg.setBoolean("http", null, "uploadpack", false);
302 		cfg.save();
303 
304 		Repository dst = createBareRepository();
305 		try (Transport t = Transport.open(dst, smartAuthNoneURI)) {
306 			try {
307 				t.openFetch();
308 				fail("connection opened even though service disabled");
309 			} catch (TransportException err) {
310 				String exp = smartAuthNoneURI + ": "
311 						+ JGitText.get().serviceNotEnabledNoName;
312 				assertEquals(exp, err.getMessage());
313 			}
314 		}
315 	}
316 
317 	@Test
318 	public void testListRemoteWithoutLocalRepository() throws Exception {
319 		try (Transport t = Transport.open(smartAuthNoneURI);
320 				FetchConnection c = t.openFetch()) {
321 			Ref head = c.getRef(Constants.HEAD);
322 			assertNotNull(head);
323 		}
324 	}
325 
326 	@Test
327 	public void testHttpClientWantsV2AndServerNotConfigured() throws Exception {
328 		String url = smartAuthNoneURI.toString() + "/info/refs?service=git-upload-pack";
329 		HttpConnection c = HttpTransport.getConnectionFactory()
330 				.create(new URL(url));
331 		c.setRequestMethod("GET");
332 		c.setRequestProperty("Git-Protocol", "version=2");
333 		assertEquals(200, c.getResponseCode());
334 
335 		PacketLineIn pckIn = new PacketLineIn(c.getInputStream());
336 		assertThat(pckIn.readString(), is("version 2"));
337 	}
338 
339 	@Test
340 	public void testHttpServerConfiguredToV0() throws Exception {
341 		remoteRepository.getRepository().getConfig().setInt(
342 			"protocol", null, "version", 0);
343 		String url = smartAuthNoneURI.toString() + "/info/refs?service=git-upload-pack";
344 		HttpConnection c = HttpTransport.getConnectionFactory()
345 				.create(new URL(url));
346 		c.setRequestMethod("GET");
347 		c.setRequestProperty("Git-Protocol", "version=2");
348 		assertEquals(200, c.getResponseCode());
349 
350 		PacketLineIn pckIn = new PacketLineIn(c.getInputStream());
351 
352 		// Check that we get a v0 response.
353 		assertThat(pckIn.readString(), is("# service=git-upload-pack"));
354 		assertTrue(PacketLineIn.isEnd(pckIn.readString()));
355 		assertTrue(pckIn.readString().matches("[0-9a-f]{40} HEAD.*"));
356 	}
357 
358 	@Test
359 	public void testV2HttpFirstResponse() throws Exception {
360 		String url = smartAuthNoneURI.toString() + "/info/refs?service=git-upload-pack";
361 		HttpConnection c = HttpTransport.getConnectionFactory()
362 				.create(new URL(url));
363 		c.setRequestMethod("GET");
364 		c.setRequestProperty("Git-Protocol", "version=2");
365 		assertEquals(200, c.getResponseCode());
366 
367 		PacketLineIn pckIn = new PacketLineIn(c.getInputStream());
368 		assertThat(pckIn.readString(), is("version 2"));
369 
370 		// What remains are capabilities - ensure that all of them are
371 		// non-empty strings, and that we see END at the end.
372 		for (String s : pckIn.readStrings()) {
373 			assertTrue(!s.isEmpty());
374 		}
375 	}
376 
377 	@Test
378 	public void testV2HttpSubsequentResponse() throws Exception {
379 		String url = smartAuthNoneURI.toString() + "/git-upload-pack";
380 		HttpConnection c = HttpTransport.getConnectionFactory()
381 				.create(new URL(url));
382 		c.setRequestMethod("POST");
383 		c.setRequestProperty("Content-Type", "application/x-git-upload-pack-request");
384 		c.setRequestProperty("Git-Protocol", "version=2");
385 		c.setDoOutput(true);
386 
387 		// Test ls-refs to verify that everything is connected
388 		// properly. Tests for other commands go in
389 		// UploadPackTest.java.
390 
391 		try (OutputStream os = c.getOutputStream()) {
392 			PacketLineOut pckOut = new PacketLineOut(os);
393 			pckOut.writeString("command=ls-refs");
394 			pckOut.writeDelim();
395 			pckOut.end();
396 		}
397 
398 		PacketLineIn pckIn = new PacketLineIn(c.getInputStream());
399 
400 		// Just check that we get what looks like a ref advertisement.
401 		for (String s : pckIn.readStrings()) {
402 			assertTrue(s.matches("[0-9a-f]{40} [A-Za-z/]*"));
403 		}
404 
405 		assertEquals(200, c.getResponseCode());
406 	}
407 }