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 try (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 }
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 try (FileInputStream fs = new FileInputStream(path(id))) {
154 ol = UnpackedObject.open(fs, path(id), id, wc);
155 }
156 }
157
158 assertNotNull("created loader", ol);
159 assertEquals(type, ol.getType());
160 assertEquals(data.length, ol.getSize());
161 assertTrue("is large", ol.isLarge());
162 try {
163 ol.getCachedBytes();
164 fail("Should have thrown LargeObjectException");
165 } catch (LargeObjectException tooBig) {
166 assertEquals(MessageFormat.format(
167 JGitText.get().largeObjectException, id.name()), tooBig
168 .getMessage());
169 }
170
171 try (ObjectStream in = ol.openStream()) {
172 assertNotNull("have stream", in);
173 assertEquals(type, in.getType());
174 assertEquals(data.length, in.getSize());
175 byte[] data2 = new byte[data.length];
176 IO.readFully(in, data2, 0, data.length);
177 assertTrue("same content", Arrays.equals(data2, data));
178 assertEquals("stream at EOF", -1, in.read());
179 }
180 }
181
182 @Test
183 public void testStandardFormat_NegativeSize() throws Exception {
184 ObjectId id = ObjectId.zeroId();
185 byte[] data = getRng().nextBytes(300);
186
187 try {
188 byte[] gz = compressStandardFormat("blob", "-1", data);
189 UnpackedObject.open(new ByteArrayInputStream(gz), path(id), id, wc);
190 fail("Did not throw CorruptObjectException");
191 } catch (CorruptObjectException coe) {
192 assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt,
193 id.name(), JGitText.get().corruptObjectNegativeSize), coe
194 .getMessage());
195 }
196 }
197
198 @Test
199 public void testStandardFormat_InvalidType() throws Exception {
200 ObjectId id = ObjectId.zeroId();
201 byte[] data = getRng().nextBytes(300);
202
203 try {
204 byte[] gz = compressStandardFormat("not.a.type", "1", data);
205 UnpackedObject.open(new ByteArrayInputStream(gz), path(id), id, wc);
206 fail("Did not throw CorruptObjectException");
207 } catch (CorruptObjectException coe) {
208 assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt,
209 id.name(), JGitText.get().corruptObjectInvalidType), coe
210 .getMessage());
211 }
212 }
213
214 @Test
215 public void testStandardFormat_NoHeader() throws Exception {
216 ObjectId id = ObjectId.zeroId();
217 byte[] data = {};
218
219 try {
220 byte[] gz = compressStandardFormat("", "", data);
221 UnpackedObject.open(new ByteArrayInputStream(gz), path(id), id, wc);
222 fail("Did not throw CorruptObjectException");
223 } catch (CorruptObjectException coe) {
224 assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt,
225 id.name(), JGitText.get().corruptObjectNoHeader), coe
226 .getMessage());
227 }
228 }
229
230 @Test
231 public void testStandardFormat_GarbageAfterSize() throws Exception {
232 ObjectId id = ObjectId.zeroId();
233 byte[] data = getRng().nextBytes(300);
234
235 try {
236 byte[] gz = compressStandardFormat("blob", "1foo", data);
237 UnpackedObject.open(new ByteArrayInputStream(gz), path(id), id, wc);
238 fail("Did not throw CorruptObjectException");
239 } catch (CorruptObjectException coe) {
240 assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt,
241 id.name(), JGitText.get().corruptObjectGarbageAfterSize),
242 coe.getMessage());
243 }
244 }
245
246 @Test
247 public void testStandardFormat_SmallObject_CorruptZLibStream()
248 throws Exception {
249 ObjectId id = ObjectId.zeroId();
250 byte[] data = getRng().nextBytes(300);
251
252 try {
253 byte[] gz = compressStandardFormat(Constants.OBJ_BLOB, data);
254 for (int i = 5; i < gz.length; i++)
255 gz[i] = 0;
256 UnpackedObject.open(new ByteArrayInputStream(gz), path(id), id, wc);
257 fail("Did not throw CorruptObjectException");
258 } catch (CorruptObjectException coe) {
259 assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt,
260 id.name(), JGitText.get().corruptObjectBadStream), coe
261 .getMessage());
262 }
263 }
264
265 @Test
266 public void testStandardFormat_SmallObject_TruncatedZLibStream()
267 throws Exception {
268 ObjectId id = ObjectId.zeroId();
269 byte[] data = getRng().nextBytes(300);
270
271 try {
272 byte[] gz = compressStandardFormat(Constants.OBJ_BLOB, data);
273 byte[] tr = new byte[gz.length - 1];
274 System.arraycopy(gz, 0, tr, 0, tr.length);
275 UnpackedObject.open(new ByteArrayInputStream(tr), path(id), id, wc);
276 fail("Did not throw CorruptObjectException");
277 } catch (CorruptObjectException coe) {
278 assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt,
279 id.name(), JGitText.get().corruptObjectBadStream), coe
280 .getMessage());
281 }
282 }
283
284 @Test
285 public void testStandardFormat_SmallObject_TrailingGarbage()
286 throws Exception {
287 ObjectId id = ObjectId.zeroId();
288 byte[] data = getRng().nextBytes(300);
289
290 try {
291 byte[] gz = compressStandardFormat(Constants.OBJ_BLOB, data);
292 byte[] tr = new byte[gz.length + 1];
293 System.arraycopy(gz, 0, tr, 0, gz.length);
294 UnpackedObject.open(new ByteArrayInputStream(tr), path(id), id, wc);
295 fail("Did not throw CorruptObjectException");
296 } catch (CorruptObjectException coe) {
297 assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt,
298 id.name(), JGitText.get().corruptObjectBadStream), coe
299 .getMessage());
300 }
301 }
302
303 @Test
304 public void testStandardFormat_LargeObject_CorruptZLibStream()
305 throws Exception {
306 final int type = Constants.OBJ_BLOB;
307 byte[] data = getRng().nextBytes(streamThreshold + 5);
308 ObjectId id = getId(type, data);
309 byte[] gz = compressStandardFormat(type, data);
310 gz[gz.length - 1] = 0;
311 gz[gz.length - 2] = 0;
312
313 write(id, gz);
314
315 ObjectLoader ol;
316 try (FileInputStream fs = new FileInputStream(path(id))) {
317 ol = UnpackedObject.open(fs, path(id), id, wc);
318 }
319
320 byte[] tmp = new byte[data.length];
321 try (InputStream in = ol.openStream()) {
322 IO.readFully(in, tmp, 0, tmp.length);
323 fail("Did not throw CorruptObjectException");
324 } catch (CorruptObjectException coe) {
325 assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt,
326 id.name(), JGitText.get().corruptObjectBadStream), coe
327 .getMessage());
328 }
329 }
330
331 @Test
332 public void testStandardFormat_LargeObject_TruncatedZLibStream()
333 throws Exception {
334 final int type = Constants.OBJ_BLOB;
335 byte[] data = getRng().nextBytes(streamThreshold + 5);
336 ObjectId id = getId(type, data);
337 byte[] gz = compressStandardFormat(type, data);
338 byte[] tr = new byte[gz.length - 1];
339 System.arraycopy(gz, 0, tr, 0, tr.length);
340
341 write(id, tr);
342
343 ObjectLoader ol;
344 try (FileInputStream fs = new FileInputStream(path(id))) {
345 ol = UnpackedObject.open(fs, path(id), id, wc);
346 }
347
348 byte[] tmp = new byte[data.length];
349 @SuppressWarnings("resource")
350 InputStream in = ol.openStream();
351 IO.readFully(in, tmp, 0, tmp.length);
352 try {
353 in.close();
354 fail("close did not throw CorruptObjectException");
355 } catch (CorruptObjectException coe) {
356 assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt,
357 id.name(), JGitText.get().corruptObjectBadStream), coe
358 .getMessage());
359 }
360 }
361
362 @Test
363 public void testStandardFormat_LargeObject_TrailingGarbage()
364 throws Exception {
365 final int type = Constants.OBJ_BLOB;
366 byte[] data = getRng().nextBytes(streamThreshold + 5);
367 ObjectId id = getId(type, data);
368 byte[] gz = compressStandardFormat(type, data);
369 byte[] tr = new byte[gz.length + 1];
370 System.arraycopy(gz, 0, tr, 0, gz.length);
371
372 write(id, tr);
373
374 ObjectLoader ol;
375 try (FileInputStream fs = new FileInputStream(path(id))) {
376 ol = UnpackedObject.open(fs, path(id), id, wc);
377 }
378
379 byte[] tmp = new byte[data.length];
380 @SuppressWarnings("resource")
381 InputStream in = ol.openStream();
382 IO.readFully(in, tmp, 0, tmp.length);
383 try {
384 in.close();
385 fail("close did not throw CorruptObjectException");
386 } catch (CorruptObjectException coe) {
387 assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt,
388 id.name(), JGitText.get().corruptObjectBadStream), coe
389 .getMessage());
390 }
391 }
392
393 @Test
394 public void testPackFormat_SmallObject() throws Exception {
395 final int type = Constants.OBJ_BLOB;
396 byte[] data = getRng().nextBytes(300);
397 byte[] gz = compressPackFormat(type, data);
398 ObjectId id = ObjectId.zeroId();
399
400 ObjectLoader ol = UnpackedObject.open(new ByteArrayInputStream(gz),
401 path(id), id, wc);
402 assertNotNull("created loader", ol);
403 assertEquals(type, ol.getType());
404 assertEquals(data.length, ol.getSize());
405 assertFalse("is not large", ol.isLarge());
406 assertTrue("same content", Arrays.equals(data, ol.getCachedBytes()));
407
408 try (ObjectStream in = ol.openStream()) {
409 assertNotNull("have stream", in);
410 assertEquals(type, in.getType());
411 assertEquals(data.length, in.getSize());
412 byte[] data2 = new byte[data.length];
413 IO.readFully(in, data2, 0, data.length);
414 assertTrue("same content",
415 Arrays.equals(data, ol.getCachedBytes()));
416 }
417 }
418
419 @Test
420 public void testPackFormat_LargeObject() throws Exception {
421 final int type = Constants.OBJ_BLOB;
422 byte[] data = getRng().nextBytes(streamThreshold + 5);
423 ObjectId id = getId(type, data);
424 write(id, compressPackFormat(type, data));
425
426 ObjectLoader ol;
427 try (FileInputStream fs = new FileInputStream(path(id))) {
428 ol = UnpackedObject.open(fs, path(id), id, wc);
429 }
430
431 assertNotNull("created loader", ol);
432 assertEquals(type, ol.getType());
433 assertEquals(data.length, ol.getSize());
434 assertTrue("is large", ol.isLarge());
435 try {
436 ol.getCachedBytes();
437 fail("Should have thrown LargeObjectException");
438 } catch (LargeObjectException tooBig) {
439 assertEquals(MessageFormat.format(
440 JGitText.get().largeObjectException, id.name()), tooBig
441 .getMessage());
442 }
443
444 try (ObjectStream in = ol.openStream()) {
445 assertNotNull("have stream", in);
446 assertEquals(type, in.getType());
447 assertEquals(data.length, in.getSize());
448 byte[] data2 = new byte[data.length];
449 IO.readFully(in, data2, 0, data.length);
450 assertTrue("same content", Arrays.equals(data2, data));
451 assertEquals("stream at EOF", -1, in.read());
452 }
453 }
454
455 @Test
456 public void testPackFormat_DeltaNotAllowed() throws Exception {
457 ObjectId id = ObjectId.zeroId();
458 byte[] data = getRng().nextBytes(300);
459
460 try {
461 byte[] gz = compressPackFormat(Constants.OBJ_OFS_DELTA, data);
462 UnpackedObject.open(new ByteArrayInputStream(gz), path(id), id, wc);
463 fail("Did not throw CorruptObjectException");
464 } catch (CorruptObjectException coe) {
465 assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt,
466 id.name(), JGitText.get().corruptObjectInvalidType), coe
467 .getMessage());
468 }
469
470 try {
471 byte[] gz = compressPackFormat(Constants.OBJ_REF_DELTA, data);
472 UnpackedObject.open(new ByteArrayInputStream(gz), path(id), id, wc);
473 fail("Did not throw CorruptObjectException");
474 } catch (CorruptObjectException coe) {
475 assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt,
476 id.name(), JGitText.get().corruptObjectInvalidType), coe
477 .getMessage());
478 }
479
480 try {
481 byte[] gz = compressPackFormat(Constants.OBJ_TYPE_5, data);
482 UnpackedObject.open(new ByteArrayInputStream(gz), path(id), id, wc);
483 fail("Did not throw CorruptObjectException");
484 } catch (CorruptObjectException coe) {
485 assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt,
486 id.name(), JGitText.get().corruptObjectInvalidType), coe
487 .getMessage());
488 }
489
490 try {
491 byte[] gz = compressPackFormat(Constants.OBJ_EXT, data);
492 UnpackedObject.open(new ByteArrayInputStream(gz), path(id), id, wc);
493 fail("Did not throw CorruptObjectException");
494 } catch (CorruptObjectException coe) {
495 assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt,
496 id.name(), JGitText.get().corruptObjectInvalidType), coe
497 .getMessage());
498 }
499 }
500
501 private static byte[] compressStandardFormat(int type, byte[] data)
502 throws IOException {
503 String typeString = Constants.typeString(type);
504 String length = String.valueOf(data.length);
505 return compressStandardFormat(typeString, length, data);
506 }
507
508 private static byte[] compressStandardFormat(String type, String length,
509 byte[] data) throws IOException {
510 ByteArrayOutputStream out = new ByteArrayOutputStream();
511 DeflaterOutputStream d = new DeflaterOutputStream(out);
512 d.write(Constants.encodeASCII(type));
513 d.write(' ');
514 d.write(Constants.encodeASCII(length));
515 d.write(0);
516 d.write(data);
517 d.finish();
518 return out.toByteArray();
519 }
520
521 private static byte[] compressPackFormat(int type, byte[] data)
522 throws IOException {
523 byte[] hdr = new byte[64];
524 int rawLength = data.length;
525 int nextLength = rawLength >>> 4;
526 hdr[0] = (byte) ((nextLength > 0 ? 0x80 : 0x00) | (type << 4) | (rawLength & 0x0F));
527 rawLength = nextLength;
528 int n = 1;
529 while (rawLength > 0) {
530 nextLength >>>= 7;
531 hdr[n++] = (byte) ((nextLength > 0 ? 0x80 : 0x00) | (rawLength & 0x7F));
532 rawLength = nextLength;
533 }
534
535 final ByteArrayOutputStream out = new ByteArrayOutputStream();
536 out.write(hdr, 0, n);
537
538 DeflaterOutputStream d = new DeflaterOutputStream(out);
539 d.write(data);
540 d.finish();
541 return out.toByteArray();
542 }
543
544 private File path(ObjectId id) {
545 return repo.getObjectDatabase().fileFor(id);
546 }
547
548 private void write(ObjectId id, byte[] data) throws IOException {
549 File path = path(id);
550 FileUtils.mkdirs(path.getParentFile());
551 try (FileOutputStream out = new FileOutputStream(path)) {
552 out.write(data);
553 }
554 }
555
556 private ObjectId getId(int type, byte[] data) {
557 try (ObjectInserter.Formatter formatter = new ObjectInserter.Formatter()) {
558 return formatter.idFor(type, data);
559 }
560 }
561 }