View Javadoc
1   /*
2    * Copyright (C) 2010, Google Inc.
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available
6    * under the terms of the Eclipse Distribution License v1.0 which
7    * accompanies this distribution, is reproduced below, and is
8    * available at http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or
13   * without modification, are permitted provided that the following
14   * conditions are met:
15   *
16   * - Redistributions of source code must retain the above copyright
17   *   notice, this list of conditions and the following disclaimer.
18   *
19   * - Redistributions in binary form must reproduce the above
20   *   copyright notice, this list of conditions and the following
21   *   disclaimer in the documentation and/or other materials provided
22   *   with the distribution.
23   *
24   * - Neither the name of the Eclipse Foundation, Inc. nor the
25   *   names of its contributors may be used to endorse or promote
26   *   products derived from this software without specific prior
27   *   written permission.
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
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 }