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