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.internal.storage.file;
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.assertTrue;
50 import static org.junit.Assert.fail;
51
52 import java.io.ByteArrayInputStream;
53 import java.io.ByteArrayOutputStream;
54 import java.io.File;
55 import java.io.FileInputStream;
56 import java.io.FileOutputStream;
57 import java.io.IOException;
58 import java.io.InputStream;
59 import java.text.MessageFormat;
60 import java.util.Arrays;
61 import java.util.zip.DeflaterOutputStream;
62
63 import org.eclipse.jgit.errors.CorruptObjectException;
64 import org.eclipse.jgit.errors.LargeObjectException;
65 import org.eclipse.jgit.internal.JGitText;
66 import org.eclipse.jgit.junit.JGitTestUtil;
67 import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
68 import org.eclipse.jgit.junit.TestRng;
69 import org.eclipse.jgit.lib.Constants;
70 import org.eclipse.jgit.lib.ObjectId;
71 import org.eclipse.jgit.lib.ObjectInserter;
72 import org.eclipse.jgit.lib.ObjectLoader;
73 import org.eclipse.jgit.lib.ObjectStream;
74 import org.eclipse.jgit.storage.file.WindowCacheConfig;
75 import org.eclipse.jgit.util.FileUtils;
76 import org.eclipse.jgit.util.IO;
77 import org.junit.After;
78 import org.junit.Before;
79 import org.junit.Test;
80
81 public class UnpackedObjectTest extends LocalDiskRepositoryTestCase {
82 private int streamThreshold = 16 * 1024;
83
84 private TestRng rng;
85
86 private FileRepository repo;
87
88 private WindowCursor wc;
89
90 private TestRng getRng() {
91 if (rng == null)
92 rng = new TestRng(JGitTestUtil.getName());
93 return rng;
94 }
95
96 @Override
97 @Before
98 public void setUp() throws Exception {
99 super.setUp();
100
101 WindowCacheConfig cfg = new WindowCacheConfig();
102 cfg.setStreamFileThreshold(streamThreshold);
103 cfg.install();
104
105 repo = createBareRepository();
106 wc = (WindowCursor) repo.newObjectReader();
107 }
108
109 @Override
110 @After
111 public void tearDown() throws Exception {
112 if (wc != null)
113 wc.close();
114 new WindowCacheConfig().install();
115 super.tearDown();
116 }
117
118 @Test
119 public void testStandardFormat_SmallObject() throws Exception {
120 final int type = Constants.OBJ_BLOB;
121 byte[] data = getRng().nextBytes(300);
122 byte[] gz = compressStandardFormat(type, data);
123 ObjectId id = ObjectId.zeroId();
124
125 ObjectLoader ol = UnpackedObject.open(new ByteArrayInputStream(gz),
126 path(id), id, wc);
127 assertNotNull("created loader", ol);
128 assertEquals(type, ol.getType());
129 assertEquals(data.length, ol.getSize());
130 assertFalse("is not large", ol.isLarge());
131 assertTrue("same content", Arrays.equals(data, ol.getCachedBytes()));
132
133 ObjectStream in = ol.openStream();
134 assertNotNull("have stream", in);
135 assertEquals(type, in.getType());
136 assertEquals(data.length, in.getSize());
137 byte[] data2 = new byte[data.length];
138 IO.readFully(in, data2, 0, data.length);
139 assertTrue("same content", Arrays.equals(data2, data));
140 assertEquals("stream at EOF", -1, in.read());
141 in.close();
142 }
143
144 @Test
145 public void testStandardFormat_LargeObject() throws Exception {
146 final int type = Constants.OBJ_BLOB;
147 byte[] data = getRng().nextBytes(streamThreshold + 5);
148 ObjectId id = getId(type, data);
149 write(id, compressStandardFormat(type, data));
150
151 ObjectLoader ol;
152 {
153 FileInputStream fs = new FileInputStream(path(id));
154 try {
155 ol = UnpackedObject.open(fs, path(id), id, wc);
156 } finally {
157 fs.close();
158 }
159 }
160
161 assertNotNull("created loader", ol);
162 assertEquals(type, ol.getType());
163 assertEquals(data.length, ol.getSize());
164 assertTrue("is large", ol.isLarge());
165 try {
166 ol.getCachedBytes();
167 fail("Should have thrown LargeObjectException");
168 } catch (LargeObjectException tooBig) {
169 assertEquals(MessageFormat.format(
170 JGitText.get().largeObjectException, id.name()), tooBig
171 .getMessage());
172 }
173
174 ObjectStream in = ol.openStream();
175 assertNotNull("have stream", in);
176 assertEquals(type, in.getType());
177 assertEquals(data.length, in.getSize());
178 byte[] data2 = new byte[data.length];
179 IO.readFully(in, data2, 0, data.length);
180 assertTrue("same content", Arrays.equals(data2, data));
181 assertEquals("stream at EOF", -1, in.read());
182 in.close();
183 }
184
185 @Test
186 public void testStandardFormat_NegativeSize() throws Exception {
187 ObjectId id = ObjectId.zeroId();
188 byte[] data = getRng().nextBytes(300);
189
190 try {
191 byte[] gz = compressStandardFormat("blob", "-1", data);
192 UnpackedObject.open(new ByteArrayInputStream(gz), path(id), id, wc);
193 fail("Did not throw CorruptObjectException");
194 } catch (CorruptObjectException coe) {
195 assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt,
196 id.name(), JGitText.get().corruptObjectNegativeSize), coe
197 .getMessage());
198 }
199 }
200
201 @Test
202 public void testStandardFormat_InvalidType() throws Exception {
203 ObjectId id = ObjectId.zeroId();
204 byte[] data = getRng().nextBytes(300);
205
206 try {
207 byte[] gz = compressStandardFormat("not.a.type", "1", data);
208 UnpackedObject.open(new ByteArrayInputStream(gz), path(id), id, wc);
209 fail("Did not throw CorruptObjectException");
210 } catch (CorruptObjectException coe) {
211 assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt,
212 id.name(), JGitText.get().corruptObjectInvalidType), coe
213 .getMessage());
214 }
215 }
216
217 @Test
218 public void testStandardFormat_NoHeader() throws Exception {
219 ObjectId id = ObjectId.zeroId();
220 byte[] data = {};
221
222 try {
223 byte[] gz = compressStandardFormat("", "", data);
224 UnpackedObject.open(new ByteArrayInputStream(gz), path(id), id, wc);
225 fail("Did not throw CorruptObjectException");
226 } catch (CorruptObjectException coe) {
227 assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt,
228 id.name(), JGitText.get().corruptObjectNoHeader), coe
229 .getMessage());
230 }
231 }
232
233 @Test
234 public void testStandardFormat_GarbageAfterSize() throws Exception {
235 ObjectId id = ObjectId.zeroId();
236 byte[] data = getRng().nextBytes(300);
237
238 try {
239 byte[] gz = compressStandardFormat("blob", "1foo", data);
240 UnpackedObject.open(new ByteArrayInputStream(gz), path(id), id, wc);
241 fail("Did not throw CorruptObjectException");
242 } catch (CorruptObjectException coe) {
243 assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt,
244 id.name(), JGitText.get().corruptObjectGarbageAfterSize),
245 coe.getMessage());
246 }
247 }
248
249 @Test
250 public void testStandardFormat_SmallObject_CorruptZLibStream()
251 throws Exception {
252 ObjectId id = ObjectId.zeroId();
253 byte[] data = getRng().nextBytes(300);
254
255 try {
256 byte[] gz = compressStandardFormat(Constants.OBJ_BLOB, data);
257 for (int i = 5; i < gz.length; i++)
258 gz[i] = 0;
259 UnpackedObject.open(new ByteArrayInputStream(gz), path(id), id, wc);
260 fail("Did not throw CorruptObjectException");
261 } catch (CorruptObjectException coe) {
262 assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt,
263 id.name(), JGitText.get().corruptObjectBadStream), coe
264 .getMessage());
265 }
266 }
267
268 @Test
269 public void testStandardFormat_SmallObject_TruncatedZLibStream()
270 throws Exception {
271 ObjectId id = ObjectId.zeroId();
272 byte[] data = getRng().nextBytes(300);
273
274 try {
275 byte[] gz = compressStandardFormat(Constants.OBJ_BLOB, data);
276 byte[] tr = new byte[gz.length - 1];
277 System.arraycopy(gz, 0, tr, 0, tr.length);
278 UnpackedObject.open(new ByteArrayInputStream(tr), path(id), id, wc);
279 fail("Did not throw CorruptObjectException");
280 } catch (CorruptObjectException coe) {
281 assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt,
282 id.name(), JGitText.get().corruptObjectBadStream), coe
283 .getMessage());
284 }
285 }
286
287 @Test
288 public void testStandardFormat_SmallObject_TrailingGarbage()
289 throws Exception {
290 ObjectId id = ObjectId.zeroId();
291 byte[] data = getRng().nextBytes(300);
292
293 try {
294 byte[] gz = compressStandardFormat(Constants.OBJ_BLOB, data);
295 byte[] tr = new byte[gz.length + 1];
296 System.arraycopy(gz, 0, tr, 0, gz.length);
297 UnpackedObject.open(new ByteArrayInputStream(tr), path(id), id, wc);
298 fail("Did not throw CorruptObjectException");
299 } catch (CorruptObjectException coe) {
300 assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt,
301 id.name(), JGitText.get().corruptObjectBadStream), coe
302 .getMessage());
303 }
304 }
305
306 @Test
307 public void testStandardFormat_LargeObject_CorruptZLibStream()
308 throws Exception {
309 final int type = Constants.OBJ_BLOB;
310 byte[] data = getRng().nextBytes(streamThreshold + 5);
311 ObjectId id = getId(type, data);
312 byte[] gz = compressStandardFormat(type, data);
313 gz[gz.length - 1] = 0;
314 gz[gz.length - 2] = 0;
315
316 write(id, gz);
317
318 ObjectLoader ol;
319 {
320 FileInputStream fs = new FileInputStream(path(id));
321 try {
322 ol = UnpackedObject.open(fs, path(id), id, wc);
323 } finally {
324 fs.close();
325 }
326 }
327
328 try {
329 byte[] tmp = new byte[data.length];
330 InputStream in = ol.openStream();
331 try {
332 IO.readFully(in, tmp, 0, tmp.length);
333 } finally {
334 in.close();
335 }
336 fail("Did not throw CorruptObjectException");
337 } catch (CorruptObjectException coe) {
338 assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt,
339 id.name(), JGitText.get().corruptObjectBadStream), coe
340 .getMessage());
341 }
342 }
343
344 @Test
345 public void testStandardFormat_LargeObject_TruncatedZLibStream()
346 throws Exception {
347 final int type = Constants.OBJ_BLOB;
348 byte[] data = getRng().nextBytes(streamThreshold + 5);
349 ObjectId id = getId(type, data);
350 byte[] gz = compressStandardFormat(type, data);
351 byte[] tr = new byte[gz.length - 1];
352 System.arraycopy(gz, 0, tr, 0, tr.length);
353
354 write(id, tr);
355
356 ObjectLoader ol;
357 {
358 FileInputStream fs = new FileInputStream(path(id));
359 try {
360 ol = UnpackedObject.open(fs, path(id), id, wc);
361 } finally {
362 fs.close();
363 }
364 }
365
366 byte[] tmp = new byte[data.length];
367 InputStream in = ol.openStream();
368 IO.readFully(in, tmp, 0, tmp.length);
369 try {
370 in.close();
371 fail("close did not throw CorruptObjectException");
372 } catch (CorruptObjectException coe) {
373 assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt,
374 id.name(), JGitText.get().corruptObjectBadStream), coe
375 .getMessage());
376 }
377 }
378
379 @Test
380 public void testStandardFormat_LargeObject_TrailingGarbage()
381 throws Exception {
382 final int type = Constants.OBJ_BLOB;
383 byte[] data = getRng().nextBytes(streamThreshold + 5);
384 ObjectId id = getId(type, data);
385 byte[] gz = compressStandardFormat(type, data);
386 byte[] tr = new byte[gz.length + 1];
387 System.arraycopy(gz, 0, tr, 0, gz.length);
388
389 write(id, tr);
390
391 ObjectLoader ol;
392 {
393 FileInputStream fs = new FileInputStream(path(id));
394 try {
395 ol = UnpackedObject.open(fs, path(id), id, wc);
396 } finally {
397 fs.close();
398 }
399 }
400
401 byte[] tmp = new byte[data.length];
402 InputStream in = ol.openStream();
403 IO.readFully(in, tmp, 0, tmp.length);
404 try {
405 in.close();
406 fail("close did not throw CorruptObjectException");
407 } catch (CorruptObjectException coe) {
408 assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt,
409 id.name(), JGitText.get().corruptObjectBadStream), coe
410 .getMessage());
411 }
412 }
413
414 @Test
415 public void testPackFormat_SmallObject() throws Exception {
416 final int type = Constants.OBJ_BLOB;
417 byte[] data = getRng().nextBytes(300);
418 byte[] gz = compressPackFormat(type, data);
419 ObjectId id = ObjectId.zeroId();
420
421 ObjectLoader ol = UnpackedObject.open(new ByteArrayInputStream(gz),
422 path(id), id, wc);
423 assertNotNull("created loader", ol);
424 assertEquals(type, ol.getType());
425 assertEquals(data.length, ol.getSize());
426 assertFalse("is not large", ol.isLarge());
427 assertTrue("same content", Arrays.equals(data, ol.getCachedBytes()));
428
429 ObjectStream in = ol.openStream();
430 assertNotNull("have stream", in);
431 assertEquals(type, in.getType());
432 assertEquals(data.length, in.getSize());
433 byte[] data2 = new byte[data.length];
434 IO.readFully(in, data2, 0, data.length);
435 assertTrue("same content", Arrays.equals(data, ol.getCachedBytes()));
436 in.close();
437 }
438
439 @Test
440 public void testPackFormat_LargeObject() throws Exception {
441 final int type = Constants.OBJ_BLOB;
442 byte[] data = getRng().nextBytes(streamThreshold + 5);
443 ObjectId id = getId(type, data);
444 write(id, compressPackFormat(type, data));
445
446 ObjectLoader ol;
447 {
448 FileInputStream fs = new FileInputStream(path(id));
449 try {
450 ol = UnpackedObject.open(fs, path(id), id, wc);
451 } finally {
452 fs.close();
453 }
454 }
455
456 assertNotNull("created loader", ol);
457 assertEquals(type, ol.getType());
458 assertEquals(data.length, ol.getSize());
459 assertTrue("is large", ol.isLarge());
460 try {
461 ol.getCachedBytes();
462 fail("Should have thrown LargeObjectException");
463 } catch (LargeObjectException tooBig) {
464 assertEquals(MessageFormat.format(
465 JGitText.get().largeObjectException, id.name()), tooBig
466 .getMessage());
467 }
468
469 ObjectStream in = ol.openStream();
470 assertNotNull("have stream", in);
471 assertEquals(type, in.getType());
472 assertEquals(data.length, in.getSize());
473 byte[] data2 = new byte[data.length];
474 IO.readFully(in, data2, 0, data.length);
475 assertTrue("same content", Arrays.equals(data2, data));
476 assertEquals("stream at EOF", -1, in.read());
477 in.close();
478 }
479
480 @Test
481 public void testPackFormat_DeltaNotAllowed() throws Exception {
482 ObjectId id = ObjectId.zeroId();
483 byte[] data = getRng().nextBytes(300);
484
485 try {
486 byte[] gz = compressPackFormat(Constants.OBJ_OFS_DELTA, data);
487 UnpackedObject.open(new ByteArrayInputStream(gz), path(id), id, wc);
488 fail("Did not throw CorruptObjectException");
489 } catch (CorruptObjectException coe) {
490 assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt,
491 id.name(), JGitText.get().corruptObjectInvalidType), coe
492 .getMessage());
493 }
494
495 try {
496 byte[] gz = compressPackFormat(Constants.OBJ_REF_DELTA, data);
497 UnpackedObject.open(new ByteArrayInputStream(gz), path(id), id, wc);
498 fail("Did not throw CorruptObjectException");
499 } catch (CorruptObjectException coe) {
500 assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt,
501 id.name(), JGitText.get().corruptObjectInvalidType), coe
502 .getMessage());
503 }
504
505 try {
506 byte[] gz = compressPackFormat(Constants.OBJ_TYPE_5, data);
507 UnpackedObject.open(new ByteArrayInputStream(gz), path(id), id, wc);
508 fail("Did not throw CorruptObjectException");
509 } catch (CorruptObjectException coe) {
510 assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt,
511 id.name(), JGitText.get().corruptObjectInvalidType), coe
512 .getMessage());
513 }
514
515 try {
516 byte[] gz = compressPackFormat(Constants.OBJ_EXT, data);
517 UnpackedObject.open(new ByteArrayInputStream(gz), path(id), id, wc);
518 fail("Did not throw CorruptObjectException");
519 } catch (CorruptObjectException coe) {
520 assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt,
521 id.name(), JGitText.get().corruptObjectInvalidType), coe
522 .getMessage());
523 }
524 }
525
526 private static byte[] compressStandardFormat(int type, byte[] data)
527 throws IOException {
528 String typeString = Constants.typeString(type);
529 String length = String.valueOf(data.length);
530 return compressStandardFormat(typeString, length, data);
531 }
532
533 private static byte[] compressStandardFormat(String type, String length,
534 byte[] data) throws IOException {
535 ByteArrayOutputStream out = new ByteArrayOutputStream();
536 DeflaterOutputStream d = new DeflaterOutputStream(out);
537 d.write(Constants.encodeASCII(type));
538 d.write(' ');
539 d.write(Constants.encodeASCII(length));
540 d.write(0);
541 d.write(data);
542 d.finish();
543 return out.toByteArray();
544 }
545
546 private static byte[] compressPackFormat(int type, byte[] data)
547 throws IOException {
548 byte[] hdr = new byte[64];
549 int rawLength = data.length;
550 int nextLength = rawLength >>> 4;
551 hdr[0] = (byte) ((nextLength > 0 ? 0x80 : 0x00) | (type << 4) | (rawLength & 0x0F));
552 rawLength = nextLength;
553 int n = 1;
554 while (rawLength > 0) {
555 nextLength >>>= 7;
556 hdr[n++] = (byte) ((nextLength > 0 ? 0x80 : 0x00) | (rawLength & 0x7F));
557 rawLength = nextLength;
558 }
559
560 final ByteArrayOutputStream out = new ByteArrayOutputStream();
561 out.write(hdr, 0, n);
562
563 DeflaterOutputStream d = new DeflaterOutputStream(out);
564 d.write(data);
565 d.finish();
566 return out.toByteArray();
567 }
568
569 private File path(ObjectId id) {
570 return repo.getObjectDatabase().fileFor(id);
571 }
572
573 private void write(ObjectId id, byte[] data) throws IOException {
574 File path = path(id);
575 FileUtils.mkdirs(path.getParentFile());
576 FileOutputStream out = new FileOutputStream(path);
577 try {
578 out.write(data);
579 } finally {
580 out.close();
581 }
582 }
583
584 private ObjectId getId(int type, byte[] data) {
585 try (ObjectInserter.Formatter formatter = new ObjectInserter.Formatter()) {
586 return formatter.idFor(type, data);
587 }
588 }
589 }