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 		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") // We are testing that the close() method throws
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") // We are testing that the close() method throws
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 }