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