1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44 package org.eclipse.jgit.transport;
45
46 import static org.junit.Assert.assertEquals;
47 import static org.junit.Assert.assertFalse;
48 import static org.junit.Assert.assertNotNull;
49 import static org.junit.Assert.assertNull;
50 import static org.junit.Assert.assertSame;
51 import static org.junit.Assert.assertTrue;
52 import static org.junit.Assert.fail;
53
54 import java.io.ByteArrayInputStream;
55 import java.io.IOException;
56 import java.net.URISyntaxException;
57 import java.security.MessageDigest;
58 import java.util.Collections;
59 import java.util.HashMap;
60 import java.util.Map;
61 import java.util.Set;
62 import java.util.concurrent.atomic.AtomicReference;
63 import java.util.zip.Deflater;
64
65 import org.eclipse.jgit.errors.MissingObjectException;
66 import org.eclipse.jgit.errors.UnpackException;
67 import org.eclipse.jgit.internal.storage.file.ObjectDirectory;
68 import org.eclipse.jgit.internal.storage.pack.BinaryDelta;
69 import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
70 import org.eclipse.jgit.junit.TestRepository;
71 import org.eclipse.jgit.lib.Constants;
72 import org.eclipse.jgit.lib.NullProgressMonitor;
73 import org.eclipse.jgit.lib.ObjectId;
74 import org.eclipse.jgit.lib.ObjectInserter;
75 import org.eclipse.jgit.lib.ObjectLoader;
76 import org.eclipse.jgit.lib.Ref;
77 import org.eclipse.jgit.lib.Repository;
78 import org.eclipse.jgit.revwalk.RevBlob;
79 import org.eclipse.jgit.revwalk.RevCommit;
80 import org.eclipse.jgit.revwalk.RevTree;
81 import org.eclipse.jgit.revwalk.RevWalk;
82 import org.eclipse.jgit.util.NB;
83 import org.eclipse.jgit.util.TemporaryBuffer;
84 import org.junit.After;
85 import org.junit.Before;
86 import org.junit.Test;
87
88 public class ReceivePackAdvertiseRefsHookTest extends LocalDiskRepositoryTestCase {
89 private static final NullProgressMonitor PM = NullProgressMonitor.INSTANCE;
90
91 private static final String R_MASTER = Constants.R_HEADS + Constants.MASTER;
92
93 private static final String R_PRIVATE = Constants.R_HEADS + "private";
94
95 private Repository src;
96
97 private Repository dst;
98
99 private RevCommit A, B, P;
100
101 private RevBlob a, b;
102
103 @Override
104 @Before
105 public void setUp() throws Exception {
106 super.setUp();
107
108 src = createBareRepository();
109 dst = createBareRepository();
110
111
112
113 TestRepository<Repository> d = new TestRepository<>(dst);
114 a = d.blob("a");
115 A = d.commit(d.tree(d.file("a", a)));
116 B = d.commit().parent(A).create();
117 d.update(R_MASTER, B);
118
119
120
121 try (Transport t = Transport.open(src, uriOf(dst))) {
122 t.fetch(PM, Collections.singleton(new RefSpec("+refs/*:refs/*")));
123 assertEquals(B, src.resolve(R_MASTER));
124 }
125
126
127
128 b = d.blob("b");
129 P = d.commit(d.tree(d.file("b", b)), A);
130 d.update(R_PRIVATE, P);
131 }
132
133 @Test
134 public void testFilterHidesPrivate() throws Exception {
135 Map<String, Ref> refs;
136 try (TransportLocal t = new TransportLocal(src, uriOf(dst),
137 dst.getDirectory()) {
138 @Override
139 ReceivePack createReceivePack(Repository db) {
140 db.close();
141 dst.incrementOpen();
142
143 final ReceivePack rp = super.createReceivePack(dst);
144 rp.setAdvertiseRefsHook(new HidePrivateHook());
145 return rp;
146 }
147 }) {
148 try (PushConnection c = t.openPush()) {
149 refs = c.getRefsMap();
150 }
151 }
152
153 assertNotNull(refs);
154 assertNull("no private", refs.get(R_PRIVATE));
155 assertNull("no HEAD", refs.get(Constants.HEAD));
156 assertEquals(1, refs.size());
157
158 Ref master = refs.get(R_MASTER);
159 assertNotNull("has master", master);
160 assertEquals(B, master.getObjectId());
161 }
162
163 @Test
164 public void resetsHaves() throws Exception {
165 AtomicReference<Set<ObjectId>> haves = new AtomicReference<>();
166 try (TransportLocal t = new TransportLocal(src, uriOf(dst),
167 dst.getDirectory()) {
168 @Override
169 ReceivePack createReceivePack(Repository db) {
170 dst.incrementOpen();
171
172 ReceivePack rp = super.createReceivePack(dst);
173 rp.setAdvertiseRefsHook(new AdvertiseRefsHook() {
174 @Override
175 public void advertiseRefs(BaseReceivePack rp2)
176 throws ServiceMayNotContinueException {
177 rp.setAdvertisedRefs(rp.getRepository().getAllRefs(),
178 null);
179 new HidePrivateHook().advertiseRefs(rp);
180 haves.set(rp.getAdvertisedObjects());
181 }
182
183 @Override
184 public void advertiseRefs(UploadPack uploadPack)
185 throws ServiceMayNotContinueException {
186 throw new UnsupportedOperationException();
187 }
188 });
189 return rp;
190 }
191 }) {
192 try (PushConnection c = t.openPush()) {
193
194 }
195 }
196
197 assertEquals(1, haves.get().size());
198 assertTrue(haves.get().contains(B));
199 assertFalse(haves.get().contains(P));
200 }
201
202 private TransportLocal newTransportLocalWithStrictValidation()
203 throws Exception {
204 return new TransportLocal(src, uriOf(dst), dst.getDirectory()) {
205 @Override
206 ReceivePack createReceivePack(Repository db) {
207 db.close();
208 dst.incrementOpen();
209
210 final ReceivePack rp = super.createReceivePack(dst);
211 rp.setCheckReceivedObjects(true);
212 rp.setCheckReferencedObjectsAreReachable(true);
213 rp.setAdvertiseRefsHook(new HidePrivateHook());
214 return rp;
215 }
216 };
217 }
218
219 @Test
220 public void testSuccess() throws Exception {
221
222
223 TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024);
224
225 packHeader(pack, 2);
226 pack.write((Constants.OBJ_BLOB) << 4 | 1);
227 deflate(pack, new byte[] { 'a' });
228
229 pack.write((Constants.OBJ_REF_DELTA) << 4 | 4);
230 a.copyRawTo(pack);
231 deflate(pack, new byte[] { 0x1, 0x1, 0x1, 'b' });
232
233 digest(pack);
234 openPack(pack);
235
236
237
238 ObjectDirectory od = (ObjectDirectory) src.getObjectDatabase();
239 assertTrue("has b", src.hasObject(b));
240 assertFalse("b not loose", od.fileFor(b).exists());
241
242
243
244 TestRepository<Repository> s = new TestRepository<>(src);
245 RevCommit N = s.commit().parent(B).add("q", b).create();
246 s.update(R_MASTER, N);
247
248
249
250 PushResult r;
251 RemoteRefUpdate u = new RemoteRefUpdate(
252 src,
253 R_MASTER,
254 R_MASTER,
255 false,
256 null,
257 null
258 );
259 try (TransportLocal t = newTransportLocalWithStrictValidation()) {
260 t.setPushThin(true);
261 r = t.push(PM, Collections.singleton(u));
262 }
263
264 assertNotNull("have result", r);
265 assertNull("private not advertised", r.getAdvertisedRef(R_PRIVATE));
266 assertSame("master updated", RemoteRefUpdate.Status.OK, u.getStatus());
267 assertEquals(N, dst.resolve(R_MASTER));
268 }
269
270 @Test
271 public void testCreateBranchAtHiddenCommitFails() throws Exception {
272 final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(64);
273 packHeader(pack, 0);
274 digest(pack);
275
276 final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(256);
277 final PacketLineOut inPckLine = new PacketLineOut(inBuf);
278 inPckLine.writeString(ObjectId.zeroId().name() + ' ' + P.name() + ' '
279 + "refs/heads/s" + '\0'
280 + BasePackPushConnection.CAPABILITY_REPORT_STATUS);
281 inPckLine.end();
282 pack.writeTo(inBuf, PM);
283
284 final TemporaryBuffer.Heap outBuf = new TemporaryBuffer.Heap(1024);
285 final ReceivePack rp = new ReceivePack(dst);
286 rp.setCheckReceivedObjects(true);
287 rp.setCheckReferencedObjectsAreReachable(true);
288 rp.setAdvertiseRefsHook(new HidePrivateHook());
289 try {
290 receive(rp, inBuf, outBuf);
291 fail("Expected UnpackException");
292 } catch (UnpackException failed) {
293 Throwable err = failed.getCause();
294 assertTrue(err instanceof MissingObjectException);
295 MissingObjectException moe = (MissingObjectException) err;
296 assertEquals(P, moe.getObjectId());
297 }
298
299 final PacketLineIn r = asPacketLineIn(outBuf);
300 String master = r.readString();
301 int nul = master.indexOf('\0');
302 assertTrue("has capability list", nul > 0);
303 assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul));
304 assertSame(PacketLineIn.END, r.readString());
305
306 assertEquals("unpack error Missing commit " + P.name(), r.readString());
307 assertEquals("ng refs/heads/s n/a (unpacker error)", r.readString());
308 assertSame(PacketLineIn.END, r.readString());
309 }
310
311 private static void receive(final ReceivePack rp,
312 final TemporaryBuffer.Heap inBuf, final TemporaryBuffer.Heap outBuf)
313 throws IOException {
314 rp.receive(new ByteArrayInputStream(inBuf.toByteArray()), outBuf, null);
315 }
316
317 @Test
318 public void testUsingHiddenDeltaBaseFails() throws Exception {
319 byte[] delta = { 0x1, 0x1, 0x1, 'c' };
320 TestRepository<Repository> s = new TestRepository<>(src);
321 RevCommit N = s.commit().parent(B).add("q",
322 s.blob(BinaryDelta.apply(dst.open(b).getCachedBytes(), delta)))
323 .create();
324
325 final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024);
326 packHeader(pack, 3);
327 copy(pack, src.open(N));
328 copy(pack, src.open(s.parseBody(N).getTree()));
329 pack.write((Constants.OBJ_REF_DELTA) << 4 | 4);
330 b.copyRawTo(pack);
331 deflate(pack, delta);
332 digest(pack);
333
334 final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(1024);
335 final PacketLineOut inPckLine = new PacketLineOut(inBuf);
336 inPckLine.writeString(ObjectId.zeroId().name() + ' ' + N.name() + ' '
337 + "refs/heads/s" + '\0'
338 + BasePackPushConnection.CAPABILITY_REPORT_STATUS);
339 inPckLine.end();
340 pack.writeTo(inBuf, PM);
341
342 final TemporaryBuffer.Heap outBuf = new TemporaryBuffer.Heap(1024);
343 final ReceivePack rp = new ReceivePack(dst);
344 rp.setCheckReceivedObjects(true);
345 rp.setCheckReferencedObjectsAreReachable(true);
346 rp.setAdvertiseRefsHook(new HidePrivateHook());
347 try {
348 receive(rp, inBuf, outBuf);
349 fail("Expected UnpackException");
350 } catch (UnpackException failed) {
351 Throwable err = failed.getCause();
352 assertTrue(err instanceof MissingObjectException);
353 MissingObjectException moe = (MissingObjectException) err;
354 assertEquals(b, moe.getObjectId());
355 }
356
357 final PacketLineIn r = asPacketLineIn(outBuf);
358 String master = r.readString();
359 int nul = master.indexOf('\0');
360 assertTrue("has capability list", nul > 0);
361 assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul));
362 assertSame(PacketLineIn.END, r.readString());
363
364 assertEquals("unpack error Missing blob " + b.name(), r.readString());
365 assertEquals("ng refs/heads/s n/a (unpacker error)", r.readString());
366 assertSame(PacketLineIn.END, r.readString());
367 }
368
369 @Test
370 public void testUsingHiddenCommonBlobFails() throws Exception {
371
372
373 TestRepository<Repository> s = new TestRepository<>(src);
374 RevCommit N = s.commit().parent(B).add("q", s.blob("b")).create();
375
376
377
378 final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024);
379 packHeader(pack, 2);
380 copy(pack, src.open(N));
381 copy(pack,src.open(s.parseBody(N).getTree()));
382 digest(pack);
383
384 final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(1024);
385 final PacketLineOut inPckLine = new PacketLineOut(inBuf);
386 inPckLine.writeString(ObjectId.zeroId().name() + ' ' + N.name() + ' '
387 + "refs/heads/s" + '\0'
388 + BasePackPushConnection.CAPABILITY_REPORT_STATUS);
389 inPckLine.end();
390 pack.writeTo(inBuf, PM);
391
392 final TemporaryBuffer.Heap outBuf = new TemporaryBuffer.Heap(1024);
393 final ReceivePack rp = new ReceivePack(dst);
394 rp.setCheckReceivedObjects(true);
395 rp.setCheckReferencedObjectsAreReachable(true);
396 rp.setAdvertiseRefsHook(new HidePrivateHook());
397 try {
398 receive(rp, inBuf, outBuf);
399 fail("Expected UnpackException");
400 } catch (UnpackException failed) {
401 Throwable err = failed.getCause();
402 assertTrue(err instanceof MissingObjectException);
403 MissingObjectException moe = (MissingObjectException) err;
404 assertEquals(b, moe.getObjectId());
405 }
406
407 final PacketLineIn r = asPacketLineIn(outBuf);
408 String master = r.readString();
409 int nul = master.indexOf('\0');
410 assertTrue("has capability list", nul > 0);
411 assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul));
412 assertSame(PacketLineIn.END, r.readString());
413
414 assertEquals("unpack error Missing blob " + b.name(), r.readString());
415 assertEquals("ng refs/heads/s n/a (unpacker error)", r.readString());
416 assertSame(PacketLineIn.END, r.readString());
417 }
418
419 @Test
420 public void testUsingUnknownBlobFails() throws Exception {
421
422
423 TestRepository<Repository> s = new TestRepository<>(src);
424 RevBlob n = s.blob("n");
425 RevCommit N = s.commit().parent(B).add("q", n).create();
426
427
428
429 final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024);
430 packHeader(pack, 2);
431 copy(pack, src.open(N));
432 copy(pack,src.open(s.parseBody(N).getTree()));
433 digest(pack);
434
435 final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(1024);
436 final PacketLineOut inPckLine = new PacketLineOut(inBuf);
437 inPckLine.writeString(ObjectId.zeroId().name() + ' ' + N.name() + ' '
438 + "refs/heads/s" + '\0'
439 + BasePackPushConnection.CAPABILITY_REPORT_STATUS);
440 inPckLine.end();
441 pack.writeTo(inBuf, PM);
442
443 final TemporaryBuffer.Heap outBuf = new TemporaryBuffer.Heap(1024);
444 final ReceivePack rp = new ReceivePack(dst);
445 rp.setCheckReceivedObjects(true);
446 rp.setCheckReferencedObjectsAreReachable(true);
447 rp.setAdvertiseRefsHook(new HidePrivateHook());
448 try {
449 receive(rp, inBuf, outBuf);
450 fail("Expected UnpackException");
451 } catch (UnpackException failed) {
452 Throwable err = failed.getCause();
453 assertTrue(err instanceof MissingObjectException);
454 MissingObjectException moe = (MissingObjectException) err;
455 assertEquals(n, moe.getObjectId());
456 }
457
458 final PacketLineIn r = asPacketLineIn(outBuf);
459 String master = r.readString();
460 int nul = master.indexOf('\0');
461 assertTrue("has capability list", nul > 0);
462 assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul));
463 assertSame(PacketLineIn.END, r.readString());
464
465 assertEquals("unpack error Missing blob " + n.name(), r.readString());
466 assertEquals("ng refs/heads/s n/a (unpacker error)", r.readString());
467 assertSame(PacketLineIn.END, r.readString());
468 }
469
470 @Test
471 public void testUsingUnknownTreeFails() throws Exception {
472 TestRepository<Repository> s = new TestRepository<>(src);
473 RevCommit N = s.commit().parent(B).add("q", s.blob("a")).create();
474 RevTree t = s.parseBody(N).getTree();
475
476
477
478 final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024);
479 packHeader(pack, 1);
480 copy(pack, src.open(N));
481 digest(pack);
482
483 final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(1024);
484 final PacketLineOut inPckLine = new PacketLineOut(inBuf);
485 inPckLine.writeString(ObjectId.zeroId().name() + ' ' + N.name() + ' '
486 + "refs/heads/s" + '\0'
487 + BasePackPushConnection.CAPABILITY_REPORT_STATUS);
488 inPckLine.end();
489 pack.writeTo(inBuf, PM);
490
491 final TemporaryBuffer.Heap outBuf = new TemporaryBuffer.Heap(1024);
492 final ReceivePack rp = new ReceivePack(dst);
493 rp.setCheckReceivedObjects(true);
494 rp.setCheckReferencedObjectsAreReachable(true);
495 rp.setAdvertiseRefsHook(new HidePrivateHook());
496 try {
497 receive(rp, inBuf, outBuf);
498 fail("Expected UnpackException");
499 } catch (UnpackException failed) {
500 Throwable err = failed.getCause();
501 assertTrue(err instanceof MissingObjectException);
502 MissingObjectException moe = (MissingObjectException) err;
503 assertEquals(t, moe.getObjectId());
504 }
505
506 final PacketLineIn r = asPacketLineIn(outBuf);
507 String master = r.readString();
508 int nul = master.indexOf('\0');
509 assertTrue("has capability list", nul > 0);
510 assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul));
511 assertSame(PacketLineIn.END, r.readString());
512
513 assertEquals("unpack error Missing tree " + t.name(), r.readString());
514 assertEquals("ng refs/heads/s n/a (unpacker error)", r.readString());
515 assertSame(PacketLineIn.END, r.readString());
516 }
517
518 private static void packHeader(TemporaryBuffer.Heap tinyPack, int cnt)
519 throws IOException {
520 final byte[] hdr = new byte[8];
521 NB.encodeInt32(hdr, 0, 2);
522 NB.encodeInt32(hdr, 4, cnt);
523
524 tinyPack.write(Constants.PACK_SIGNATURE);
525 tinyPack.write(hdr, 0, 8);
526 }
527
528 private static void copy(TemporaryBuffer.Heap tinyPack, ObjectLoader ldr)
529 throws IOException {
530 final byte[] buf = new byte[64];
531 final byte[] content = ldr.getCachedBytes();
532 int dataLength = content.length;
533 int nextLength = dataLength >>> 4;
534 int size = 0;
535 buf[size++] = (byte) ((nextLength > 0 ? 0x80 : 0x00)
536 | (ldr.getType() << 4) | (dataLength & 0x0F));
537 dataLength = nextLength;
538 while (dataLength > 0) {
539 nextLength >>>= 7;
540 buf[size++] = (byte) ((nextLength > 0 ? 0x80 : 0x00) | (dataLength & 0x7F));
541 dataLength = nextLength;
542 }
543 tinyPack.write(buf, 0, size);
544 deflate(tinyPack, content);
545 }
546
547 private static void deflate(TemporaryBuffer.Heap tinyPack,
548 final byte[] content)
549 throws IOException {
550 final Deflater deflater = new Deflater();
551 final byte[] buf = new byte[128];
552 deflater.setInput(content, 0, content.length);
553 deflater.finish();
554 do {
555 final int n = deflater.deflate(buf, 0, buf.length);
556 if (n > 0)
557 tinyPack.write(buf, 0, n);
558 } while (!deflater.finished());
559 }
560
561 private static void digest(TemporaryBuffer.Heap buf) throws IOException {
562 MessageDigest md = Constants.newMessageDigest();
563 md.update(buf.toByteArray());
564 buf.write(md.digest());
565 }
566
567 private ObjectInserter inserter;
568
569 @After
570 public void release() {
571 if (inserter != null) {
572 inserter.close();
573 }
574 }
575
576 private void openPack(TemporaryBuffer.Heap buf) throws IOException {
577 if (inserter == null)
578 inserter = src.newObjectInserter();
579
580 final byte[] raw = buf.toByteArray();
581 PackParser p = inserter.newPackParser(new ByteArrayInputStream(raw));
582 p.setAllowThin(true);
583 p.parse(PM);
584 }
585
586 private static PacketLineIn asPacketLineIn(TemporaryBuffer.Heap buf)
587 throws IOException {
588 return new PacketLineIn(new ByteArrayInputStream(buf.toByteArray()));
589 }
590
591 private static final class HidePrivateHook extends AbstractAdvertiseRefsHook {
592 @Override
593 public Map<String, Ref> getAdvertisedRefs(Repository r, RevWalk revWalk) {
594 Map<String, Ref> refs = new HashMap<>(r.getAllRefs());
595 assertNotNull(refs.remove(R_PRIVATE));
596 return refs;
597 }
598 }
599
600 private static URIish uriOf(Repository r) throws URISyntaxException {
601 return new URIish(r.getDirectory().getAbsolutePath());
602 }
603 }