1
2
3
4
5
6
7
8
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.text.MessageFormat;
27 import java.time.Instant;
28 import java.util.List;
29 import java.util.Set;
30
31 import javax.servlet.http.HttpServletRequest;
32
33 import org.eclipse.jetty.servlet.DefaultServlet;
34 import org.eclipse.jetty.servlet.ServletContextHandler;
35 import org.eclipse.jetty.servlet.ServletHolder;
36 import org.eclipse.jgit.api.Git;
37 import org.eclipse.jgit.errors.NoRemoteRepositoryException;
38 import org.eclipse.jgit.errors.RepositoryNotFoundException;
39 import org.eclipse.jgit.errors.TransportException;
40 import org.eclipse.jgit.http.server.GitServlet;
41 import org.eclipse.jgit.internal.JGitText;
42 import org.eclipse.jgit.junit.TestRepository;
43 import org.eclipse.jgit.junit.http.AccessEvent;
44 import org.eclipse.jgit.junit.http.AppServer;
45 import org.eclipse.jgit.lib.Constants;
46 import org.eclipse.jgit.lib.Ref;
47 import org.eclipse.jgit.lib.RefUpdate;
48 import org.eclipse.jgit.lib.Repository;
49 import org.eclipse.jgit.lib.StoredConfig;
50 import org.eclipse.jgit.revwalk.RevCommit;
51 import org.eclipse.jgit.transport.FetchConnection;
52 import org.eclipse.jgit.transport.HttpTransport;
53 import org.eclipse.jgit.transport.PacketLineIn;
54 import org.eclipse.jgit.transport.PacketLineOut;
55 import org.eclipse.jgit.transport.Transport;
56 import org.eclipse.jgit.transport.URIish;
57 import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
58 import org.eclipse.jgit.transport.http.HttpConnection;
59 import org.eclipse.jgit.transport.http.HttpConnectionFactory;
60 import org.junit.Before;
61 import org.junit.Test;
62
63 public class HttpClientTests extends AllFactoriesHttpTestCase {
64
65 private TestRepository<Repository> remoteRepository;
66
67 private URIish dumbAuthNoneURI;
68
69 private URIish dumbAuthBasicURI;
70
71 private URIish smartAuthNoneURI;
72
73 private URIish smartAuthBasicURI;
74
75 public HttpClientTests(HttpConnectionFactory cf) {
76 super(cf);
77 }
78
79 @Override
80 @Before
81 public void setUp() throws Exception {
82 super.setUp();
83
84 remoteRepository = createTestRepository();
85 remoteRepository.update(master, remoteRepository.commit().create());
86
87 ServletContextHandler dNone = dumb("/dnone");
88 ServletContextHandler dBasic = server.authBasic(dumb("/dbasic"));
89
90 ServletContextHandler sNone = smart("/snone");
91 ServletContextHandler sBasic = server.authBasic(smart("/sbasic"));
92
93 server.setUp();
94
95 final String srcName = nameOf(remoteRepository.getRepository());
96 dumbAuthNoneURI = toURIish(dNone, srcName);
97 dumbAuthBasicURI = toURIish(dBasic, srcName);
98
99 smartAuthNoneURI = toURIish(sNone, srcName);
100 smartAuthBasicURI = toURIish(sBasic, srcName);
101 }
102
103 private ServletContextHandler dumb(String path) {
104 final File srcGit = remoteRepository.getRepository().getDirectory();
105 final URI base = srcGit.getParentFile().toURI();
106
107 ServletContextHandler ctx = server.addContext(path);
108 ctx.setResourceBase(base.toString());
109 ServletHolder holder = ctx.addServlet(DefaultServlet.class, "/");
110
111 holder.setInitParameter("aliases", "true");
112 return ctx;
113 }
114
115 private ServletContextHandler smart(String path) {
116 GitServlet gs = new GitServlet();
117 gs.setRepositoryResolver((HttpServletRequest req, String name) -> {
118 final Repository db = remoteRepository.getRepository();
119 if (!name.equals(nameOf(db))) {
120 throw new RepositoryNotFoundException(name);
121 }
122 db.incrementOpen();
123 return db;
124 });
125
126 ServletContextHandler ctx = server.addContext(path);
127 ctx.addServlet(new ServletHolder(gs), "/*");
128 return ctx;
129 }
130
131 private static String nameOf(Repository db) {
132 return db.getDirectory().getName();
133 }
134
135 @Test
136 public void testRepositoryNotFound_Dumb() throws Exception {
137 URIish uri = toURIish("/dumb.none/not-found");
138 Repository dst = createBareRepository();
139 try (Transport t = Transport.open(dst, uri)) {
140 try {
141 t.openFetch();
142 fail("connection opened to not found repository");
143 } catch (NoRemoteRepositoryException err) {
144 String exp = uri + ": " + uri
145 + "/info/refs?service=git-upload-pack not found";
146 assertNotNull(err.getMessage());
147 assertTrue("Unexpected error message",
148 err.getMessage().startsWith(exp));
149 }
150 }
151 }
152
153 @Test
154 public void testRepositoryNotFound_Smart() throws Exception {
155 URIish uri = toURIish("/smart.none/not-found");
156 Repository dst = createBareRepository();
157 try (Transport t = Transport.open(dst, uri)) {
158 try {
159 t.openFetch();
160 fail("connection opened to not found repository");
161 } catch (NoRemoteRepositoryException err) {
162 String exp = uri + ": " + uri
163 + "/info/refs?service=git-upload-pack not found";
164 assertNotNull(err.getMessage());
165 assertTrue("Unexpected error message",
166 err.getMessage().startsWith(exp));
167 }
168 }
169 }
170
171 @Test
172 public void testListRemote_Dumb_DetachedHEAD() throws Exception {
173 Repository src = remoteRepository.getRepository();
174 RefUpdate u = src.updateRef(Constants.HEAD, true);
175 RevCommit Q = remoteRepository.commit().message("Q").create();
176 u.setNewObjectId(Q);
177 assertEquals(RefUpdate.Result.FORCED, u.forceUpdate());
178
179 Repository dst = createBareRepository();
180 Ref head;
181 try (Transport t = Transport.open(dst, dumbAuthNoneURI);
182 FetchConnection c = t.openFetch()) {
183 head = c.getRef(Constants.HEAD);
184 }
185 assertNotNull("has " + Constants.HEAD, head);
186 assertEquals(Q, head.getObjectId());
187 }
188
189 @Test
190 public void testListRemote_Dumb_NoHEAD() throws Exception {
191 Repository src = remoteRepository.getRepository();
192 File headref = new File(src.getDirectory(), Constants.HEAD);
193 assertTrue("HEAD used to be present", headref.delete());
194 assertFalse("HEAD is gone", headref.exists());
195
196 Repository dst = createBareRepository();
197 Ref head;
198 try (Transport t = Transport.open(dst, dumbAuthNoneURI);
199 FetchConnection c = t.openFetch()) {
200 head = c.getRef(Constants.HEAD);
201 }
202 assertNull("has no " + Constants.HEAD, head);
203 }
204
205 @Test
206 public void testListRemote_Smart_DetachedHEAD() throws Exception {
207 Repository src = remoteRepository.getRepository();
208 RefUpdate u = src.updateRef(Constants.HEAD, true);
209 RevCommit Q = remoteRepository.commit().message("Q").create();
210 u.setNewObjectId(Q);
211 assertEquals(RefUpdate.Result.FORCED, u.forceUpdate());
212
213 Repository dst = createBareRepository();
214 Ref head;
215 try (Transport t = Transport.open(dst, smartAuthNoneURI);
216 FetchConnection c = t.openFetch()) {
217 head = c.getRef(Constants.HEAD);
218 }
219 assertNotNull("has " + Constants.HEAD, head);
220 assertEquals(Q, head.getObjectId());
221 }
222
223 @Test
224 public void testListRemote_Smart_WithQueryParameters() throws Exception {
225 URIish myURI = toURIish("/snone/do?r=1&p=test.git");
226 Repository dst = createBareRepository();
227 try (Transport t = Transport.open(dst, myURI)) {
228 try {
229 t.openFetch();
230 fail("test did not fail to find repository as expected");
231 } catch (NoRemoteRepositoryException err) {
232
233 }
234 }
235
236 List<AccessEvent> requests = getRequests();
237 assertEquals(1, requests.size());
238
239 AccessEvent info = requests.get(0);
240 assertEquals("GET", info.getMethod());
241 assertEquals("/snone/do", info.getPath());
242 assertEquals(3, info.getParameters().size());
243 assertEquals("1", info.getParameter("r"));
244 assertEquals("test.git/info/refs", info.getParameter("p"));
245 assertEquals("git-upload-pack", info.getParameter("service"));
246 assertEquals(404, info.getStatus());
247 }
248
249 @Test
250 public void testListRemote_Dumb_NeedsAuth() throws Exception {
251 Repository dst = createBareRepository();
252 try (Transport t = Transport.open(dst, dumbAuthBasicURI)) {
253 try {
254 t.openFetch();
255 fail("connection opened even info/refs needs auth basic");
256 } catch (TransportException err) {
257 String exp = dumbAuthBasicURI + ": "
258 + JGitText.get().noCredentialsProvider;
259 assertEquals(exp, err.getMessage());
260 }
261 }
262 }
263
264 @Test
265 public void testListRemote_Dumb_Auth() throws Exception {
266 Repository dst = createBareRepository();
267 try (Transport t = Transport.open(dst, dumbAuthBasicURI)) {
268 t.setCredentialsProvider(new UsernamePasswordCredentialsProvider(
269 AppServer.username, AppServer.password));
270 t.openFetch().close();
271 }
272 try (Transport t = Transport.open(dst, dumbAuthBasicURI)) {
273 t.setCredentialsProvider(new UsernamePasswordCredentialsProvider(
274 AppServer.username, ""));
275 try {
276 t.openFetch();
277 fail("connection opened even info/refs needs auth basic and we provide wrong password");
278 } catch (TransportException err) {
279 String exp = dumbAuthBasicURI + ": "
280 + JGitText.get().notAuthorized;
281 assertEquals(exp, err.getMessage());
282 }
283 }
284 }
285
286 @Test
287 public void testListRemote_Smart_UploadPackNeedsAuth() throws Exception {
288 Repository dst = createBareRepository();
289 try (Transport t = Transport.open(dst, smartAuthBasicURI)) {
290 try {
291 t.openFetch();
292 fail("connection opened even though service disabled");
293 } catch (TransportException err) {
294 String exp = smartAuthBasicURI + ": "
295 + JGitText.get().noCredentialsProvider;
296 assertEquals(exp, err.getMessage());
297 }
298 }
299 }
300
301 @Test
302 public void testListRemote_Smart_UploadPackDisabled() throws Exception {
303 Repository src = remoteRepository.getRepository();
304 final StoredConfig cfg = src.getConfig();
305 cfg.setBoolean("http", null, "uploadpack", false);
306 cfg.save();
307
308 Repository dst = createBareRepository();
309 try (Transport t = Transport.open(dst, smartAuthNoneURI)) {
310 try {
311 t.openFetch();
312 fail("connection opened even though service disabled");
313 } catch (TransportException err) {
314 String exp = smartAuthNoneURI + ": "
315 + MessageFormat.format(
316 JGitText.get().serviceNotPermitted,
317 smartAuthNoneURI.toString() + "/",
318 "git-upload-pack");
319 assertEquals(exp, err.getMessage());
320 }
321 }
322 }
323
324 @Test
325 public void testListRemoteWithoutLocalRepository() throws Exception {
326 try (Transport t = Transport.open(smartAuthNoneURI);
327 FetchConnection c = t.openFetch()) {
328 Ref head = c.getRef(Constants.HEAD);
329 assertNotNull(head);
330 }
331 }
332
333 @Test
334 public void testHttpClientWantsV2AndServerNotConfigured() throws Exception {
335 String url = smartAuthNoneURI.toString() + "/info/refs?service=git-upload-pack";
336 HttpConnection c = HttpTransport.getConnectionFactory()
337 .create(new URL(url));
338 c.setRequestMethod("GET");
339 c.setRequestProperty("Git-Protocol", "version=2");
340 assertEquals(200, c.getResponseCode());
341
342 PacketLineIn pckIn = new PacketLineIn(c.getInputStream());
343 assertThat(pckIn.readString(), is("version 2"));
344 }
345
346 @Test
347 public void testHttpServerConfiguredToV0() throws Exception {
348 remoteRepository.getRepository().getConfig().setInt(
349 "protocol", null, "version", 0);
350 String url = smartAuthNoneURI.toString() + "/info/refs?service=git-upload-pack";
351 HttpConnection c = HttpTransport.getConnectionFactory()
352 .create(new URL(url));
353 c.setRequestMethod("GET");
354 c.setRequestProperty("Git-Protocol", "version=2");
355 assertEquals(200, c.getResponseCode());
356
357 PacketLineIn pckIn = new PacketLineIn(c.getInputStream());
358
359
360 assertThat(pckIn.readString(), is("# service=git-upload-pack"));
361 assertTrue(PacketLineIn.isEnd(pckIn.readString()));
362 assertTrue(pckIn.readString().matches("[0-9a-f]{40} HEAD.*"));
363 }
364
365 @Test
366 public void testV2HttpFirstResponse() throws Exception {
367 String url = smartAuthNoneURI.toString() + "/info/refs?service=git-upload-pack";
368 HttpConnection c = HttpTransport.getConnectionFactory()
369 .create(new URL(url));
370 c.setRequestMethod("GET");
371 c.setRequestProperty("Git-Protocol", "version=2");
372 assertEquals(200, c.getResponseCode());
373
374 PacketLineIn pckIn = new PacketLineIn(c.getInputStream());
375 assertThat(pckIn.readString(), is("version 2"));
376
377
378
379 for (String s : pckIn.readStrings()) {
380 assertTrue(!s.isEmpty());
381 }
382 }
383
384 @Test
385 public void testV2HttpSubsequentResponse() throws Exception {
386 String url = smartAuthNoneURI.toString() + "/git-upload-pack";
387 HttpConnection c = HttpTransport.getConnectionFactory()
388 .create(new URL(url));
389 c.setRequestMethod("POST");
390 c.setRequestProperty("Content-Type", "application/x-git-upload-pack-request");
391 c.setRequestProperty("Git-Protocol", "version=2");
392 c.setDoOutput(true);
393
394
395
396
397
398 try (OutputStream os = c.getOutputStream()) {
399 PacketLineOut pckOut = new PacketLineOut(os);
400 pckOut.writeString("command=ls-refs");
401 pckOut.writeDelim();
402 pckOut.end();
403 }
404
405 PacketLineIn pckIn = new PacketLineIn(c.getInputStream());
406
407
408 for (String s : pckIn.readStrings()) {
409 assertTrue(s.matches("[0-9a-f]{40} [A-Za-z/]*"));
410 }
411
412 assertEquals(200, c.getResponseCode());
413 }
414
415 @Test
416 public void testCloneWithDepth() throws Exception {
417 remoteRepository.getRepository().getConfig().setInt(
418 "protocol", null, "version", 0);
419 File directory = createTempDirectory("testCloneWithDepth");
420 Git git = Git.cloneRepository()
421 .setDirectory(directory)
422 .setDepth(1)
423 .setURI(smartAuthNoneURI.toString())
424 .call();
425
426 assertEquals(Set.of(git.getRepository().resolve(Constants.HEAD)), git.getRepository().getObjectDatabase().getShallowCommits());
427 }
428
429 @Test
430 public void testCloneWithDeepenSince() throws Exception {
431 remoteRepository.getRepository().getConfig().setInt(
432 "protocol", null, "version", 0);
433 RevCommit commit = remoteRepository.commit()
434 .parent(remoteRepository.git().log().call().iterator().next())
435 .message("Test")
436 .add("test.txt", "Hello world")
437 .create();
438 remoteRepository.update(master, commit);
439
440 File directory = createTempDirectory("testCloneWithDeepenSince");
441 Git git = Git.cloneRepository()
442 .setDirectory(directory)
443 .setShallowSince(Instant.ofEpochSecond(commit.getCommitTime()))
444 .setURI(smartAuthNoneURI.toString())
445 .call();
446
447 assertEquals(Set.of(git.getRepository().resolve(Constants.HEAD)), git.getRepository().getObjectDatabase().getShallowCommits());
448 }
449
450 @Test
451 public void testCloneWithDeepenNot() throws Exception {
452 remoteRepository.getRepository().getConfig().setInt(
453 "protocol", null, "version", 0);
454 RevCommit commit = remoteRepository.git().log().call().iterator().next();
455 remoteRepository.update(master, remoteRepository.commit()
456 .parent(commit)
457 .message("Test")
458 .add("test.txt", "Hello world")
459 .create());
460
461 File directory = createTempDirectory("testCloneWithDeepenNot");
462 Git git = Git.cloneRepository()
463 .setDirectory(directory)
464 .addShallowExclude(commit.getId())
465 .setURI(smartAuthNoneURI.toString())
466 .call();
467
468 assertEquals(Set.of(git.getRepository().resolve(Constants.HEAD)), git.getRepository().getObjectDatabase().getShallowCommits());
469 }
470
471 @Test
472 public void testV2CloneWithDepth() throws Exception {
473 File directory = createTempDirectory("testV2CloneWithDepth");
474 Git git = Git.cloneRepository()
475 .setDirectory(directory)
476 .setDepth(1)
477 .setURI(smartAuthNoneURI.toString())
478 .call();
479
480 assertEquals(Set.of(git.getRepository().resolve(Constants.HEAD)), git.getRepository().getObjectDatabase().getShallowCommits());
481 }
482
483 @Test
484 public void testV2CloneWithDeepenSince() throws Exception {
485 RevCommit commit = remoteRepository.commit()
486 .parent(remoteRepository.git().log().call().iterator().next())
487 .message("Test")
488 .add("test.txt", "Hello world")
489 .create();
490 remoteRepository.update(master, commit);
491
492 File directory = createTempDirectory("testV2CloneWithDeepenSince");
493 Git git = Git.cloneRepository()
494 .setDirectory(directory)
495 .setShallowSince(Instant.ofEpochSecond(commit.getCommitTime()))
496 .setURI(smartAuthNoneURI.toString())
497 .call();
498
499 assertEquals(Set.of(git.getRepository().resolve(Constants.HEAD)), git.getRepository().getObjectDatabase().getShallowCommits());
500 }
501
502 @Test
503 public void testV2CloneWithDeepenNot() throws Exception {
504 RevCommit commit = remoteRepository.git().log().call().iterator().next();
505 remoteRepository.update(master, remoteRepository.commit()
506 .parent(commit)
507 .message("Test")
508 .add("test.txt", "Hello world")
509 .create());
510
511 File directory = createTempDirectory("testV2CloneWithDeepenNot");
512 Git git = Git.cloneRepository()
513 .setDirectory(directory)
514 .addShallowExclude(commit.getId())
515 .setURI(smartAuthNoneURI.toString())
516 .call();
517
518 assertEquals(Set.of(git.getRepository().resolve(Constants.HEAD)), git.getRepository().getObjectDatabase().getShallowCommits());
519 }
520 }