View Javadoc
1   /*
2    * Copyright (C) 2008-2010, Google Inc.
3    * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
4    * and other copyright owners as documented in the project's IP log.
5    *
6    * This program and the accompanying materials are made available
7    * under the terms of the Eclipse Distribution License v1.0 which
8    * accompanies this distribution, is reproduced below, and is
9    * available at http://www.eclipse.org/org/documents/edl-v10.php
10   *
11   * All rights reserved.
12   *
13   * Redistribution and use in source and binary forms, with or
14   * without modification, are permitted provided that the following
15   * conditions are met:
16   *
17   * - Redistributions of source code must retain the above copyright
18   *   notice, this list of conditions and the following disclaimer.
19   *
20   * - Redistributions in binary form must reproduce the above
21   *   copyright notice, this list of conditions and the following
22   *   disclaimer in the documentation and/or other materials provided
23   *   with the distribution.
24   *
25   * - Neither the name of the Eclipse Foundation, Inc. nor the
26   *   names of its contributors may be used to endorse or promote
27   *   products derived from this software without specific prior
28   *   written permission.
29   *
30   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
31   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
32   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
33   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
34   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
35   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
36   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
37   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
38   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
39   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
40   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
41   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
42   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
43   */
44  
45  package org.eclipse.jgit.lib;
46  
47  import static java.lang.Integer.valueOf;
48  import static java.nio.charset.StandardCharsets.UTF_8;
49  import static org.eclipse.jgit.junit.JGitTestUtil.concat;
50  import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
51  import static org.eclipse.jgit.lib.Constants.OBJ_BAD;
52  import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
53  import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT;
54  import static org.eclipse.jgit.lib.Constants.OBJ_TAG;
55  import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
56  import static org.eclipse.jgit.lib.Constants.encode;
57  import static org.eclipse.jgit.lib.Constants.encodeASCII;
58  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.DUPLICATE_ENTRIES;
59  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.EMPTY_NAME;
60  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.FULL_PATHNAME;
61  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.HAS_DOT;
62  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.HAS_DOTDOT;
63  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.HAS_DOTGIT;
64  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.NULL_SHA1;
65  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.TREE_NOT_SORTED;
66  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.ZERO_PADDED_FILEMODE;
67  import static org.eclipse.jgit.util.RawParseUtils.decode;
68  import static org.junit.Assert.assertEquals;
69  import static org.junit.Assert.assertSame;
70  import static org.junit.Assert.fail;
71  
72  import java.text.MessageFormat;
73  
74  import org.eclipse.jgit.errors.CorruptObjectException;
75  import org.eclipse.jgit.internal.JGitText;
76  import org.junit.Before;
77  import org.junit.Rule;
78  import org.junit.Test;
79  import org.junit.rules.ExpectedException;
80  
81  public class ObjectCheckerTest {
82  	private static final ObjectChecker SECRET_KEY_CHECKER = new ObjectChecker() {
83  		@Override
84  		public void checkBlob(byte[] raw) throws CorruptObjectException {
85  			String in = decode(raw);
86  			if (in.contains("secret_key")) {
87  				throw new CorruptObjectException("don't add a secret key");
88  			}
89  		}
90  	};
91  
92  	private static final ObjectChecker SECRET_KEY_BLOB_CHECKER = new ObjectChecker() {
93  		@Override
94  		public BlobObjectChecker newBlobObjectChecker() {
95  			return new BlobObjectChecker() {
96  				private boolean containSecretKey;
97  
98  				@Override
99  				public void update(byte[] in, int offset, int len) {
100 					String str = decode(in, offset, offset + len);
101 					if (str.contains("secret_key")) {
102 						containSecretKey = true;
103 					}
104 				}
105 
106 				@Override
107 				public void endBlob(AnyObjectId id)
108 						throws CorruptObjectException {
109 					if (containSecretKey) {
110 						throw new CorruptObjectException(
111 								"don't add a secret key");
112 					}
113 				}
114 			};
115 		}
116 	};
117 
118 	private ObjectChecker checker;
119 
120 	@Rule
121 	public final ExpectedException thrown = ExpectedException.none();
122 
123 	@Before
124 	public void setUp() throws Exception {
125 		checker = new ObjectChecker();
126 	}
127 
128 	@Test
129 	public void testInvalidType() {
130 		String msg = MessageFormat.format(
131 				JGitText.get().corruptObjectInvalidType2,
132 				valueOf(OBJ_BAD));
133 		assertCorrupt(msg, OBJ_BAD, new byte[0]);
134 	}
135 
136 	@Test
137 	public void testCheckBlob() throws CorruptObjectException {
138 		// Any blob should pass...
139 		checker.checkBlob(new byte[0]);
140 		checker.checkBlob(new byte[1]);
141 
142 		checker.check(OBJ_BLOB, new byte[0]);
143 		checker.check(OBJ_BLOB, new byte[1]);
144 	}
145 
146 	@Test
147 	public void testCheckBlobNotCorrupt() throws CorruptObjectException {
148 		SECRET_KEY_CHECKER.check(OBJ_BLOB, encodeASCII("key = \"public_key\""));
149 	}
150 
151 	@Test
152 	public void testCheckBlobCorrupt() throws CorruptObjectException {
153 		thrown.expect(CorruptObjectException.class);
154 		SECRET_KEY_CHECKER.check(OBJ_BLOB, encodeASCII("key = \"secret_key\""));
155 	}
156 
157 	@Test
158 	public void testCheckBlobWithBlobObjectCheckerNotCorrupt()
159 			throws CorruptObjectException {
160 		SECRET_KEY_BLOB_CHECKER.check(OBJ_BLOB,
161 				encodeASCII("key = \"public_key\""));
162 	}
163 
164 	@Test
165 	public void testCheckBlobWithBlobObjectCheckerCorrupt()
166 			throws CorruptObjectException {
167 		thrown.expect(CorruptObjectException.class);
168 		SECRET_KEY_BLOB_CHECKER.check(OBJ_BLOB,
169 				encodeASCII("key = \"secret_key\""));
170 	}
171 
172 	@Test
173 	public void testValidCommitNoParent() throws CorruptObjectException {
174 		StringBuilder b = new StringBuilder();
175 
176 		b.append("tree ");
177 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
178 		b.append('\n');
179 
180 		b.append("author A. U. Thor <author@localhost> 1 +0000\n");
181 		b.append("committer A. U. Thor <author@localhost> 1 +0000\n");
182 
183 		byte[] data = encodeASCII(b.toString());
184 		checker.checkCommit(data);
185 		checker.check(OBJ_COMMIT, data);
186 	}
187 
188 	@Test
189 	public void testValidCommitBlankAuthor() throws CorruptObjectException {
190 		StringBuilder b = new StringBuilder();
191 
192 		b.append("tree ");
193 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
194 		b.append('\n');
195 
196 		b.append("author <> 0 +0000\n");
197 		b.append("committer <> 0 +0000\n");
198 
199 		byte[] data = encodeASCII(b.toString());
200 		checker.checkCommit(data);
201 		checker.check(OBJ_COMMIT, data);
202 	}
203 
204 	@Test
205 	public void testCommitCorruptAuthor() throws CorruptObjectException {
206 		StringBuilder b = new StringBuilder();
207 		b.append("tree be9bfa841874ccc9f2ef7c48d0c76226f89b7189\n");
208 		b.append("author b <b@c> <b@c> 0 +0000\n");
209 		b.append("committer <> 0 +0000\n");
210 
211 		byte[] data = encodeASCII(b.toString());
212 		assertCorrupt("bad date", OBJ_COMMIT, data);
213 		checker.setAllowInvalidPersonIdent(true);
214 		checker.checkCommit(data);
215 
216 		checker.setAllowInvalidPersonIdent(false);
217 		assertSkipListAccepts(OBJ_COMMIT, data);
218 	}
219 
220 	@Test
221 	public void testCommitCorruptCommitter() throws CorruptObjectException {
222 		StringBuilder b = new StringBuilder();
223 		b.append("tree be9bfa841874ccc9f2ef7c48d0c76226f89b7189\n");
224 		b.append("author <> 0 +0000\n");
225 		b.append("committer b <b@c> <b@c> 0 +0000\n");
226 
227 		byte[] data = encodeASCII(b.toString());
228 		assertCorrupt("bad date", OBJ_COMMIT, data);
229 		checker.setAllowInvalidPersonIdent(true);
230 		checker.checkCommit(data);
231 
232 		checker.setAllowInvalidPersonIdent(false);
233 		assertSkipListAccepts(OBJ_COMMIT, data);
234 	}
235 
236 	@Test
237 	public void testValidCommit1Parent() throws CorruptObjectException {
238 		StringBuilder b = new StringBuilder();
239 
240 		b.append("tree ");
241 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
242 		b.append('\n');
243 
244 		b.append("parent ");
245 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
246 		b.append('\n');
247 
248 		b.append("author A. U. Thor <author@localhost> 1 +0000\n");
249 		b.append("committer A. U. Thor <author@localhost> 1 +0000\n");
250 
251 		byte[] data = encodeASCII(b.toString());
252 		checker.checkCommit(data);
253 		checker.check(OBJ_COMMIT, data);
254 	}
255 
256 	@Test
257 	public void testValidCommit2Parent() throws CorruptObjectException {
258 		StringBuilder b = new StringBuilder();
259 
260 		b.append("tree ");
261 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
262 		b.append('\n');
263 
264 		b.append("parent ");
265 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
266 		b.append('\n');
267 
268 		b.append("parent ");
269 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
270 		b.append('\n');
271 
272 		b.append("author A. U. Thor <author@localhost> 1 +0000\n");
273 		b.append("committer A. U. Thor <author@localhost> 1 +0000\n");
274 
275 		byte[] data = encodeASCII(b.toString());
276 		checker.checkCommit(data);
277 		checker.check(OBJ_COMMIT, data);
278 	}
279 
280 	@Test
281 	public void testValidCommit128Parent() throws CorruptObjectException {
282 		StringBuilder b = new StringBuilder();
283 
284 		b.append("tree ");
285 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
286 		b.append('\n');
287 
288 		for (int i = 0; i < 128; i++) {
289 			b.append("parent ");
290 			b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
291 			b.append('\n');
292 		}
293 
294 		b.append("author A. U. Thor <author@localhost> 1 +0000\n");
295 		b.append("committer A. U. Thor <author@localhost> 1 +0000\n");
296 
297 		byte[] data = encodeASCII(b.toString());
298 		checker.checkCommit(data);
299 		checker.check(OBJ_COMMIT, data);
300 	}
301 
302 	@Test
303 	public void testValidCommitNormalTime() throws CorruptObjectException {
304 		StringBuilder b = new StringBuilder();
305 		String when = "1222757360 -0730";
306 
307 		b.append("tree ");
308 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
309 		b.append('\n');
310 
311 		b.append("author A. U. Thor <author@localhost> " + when + "\n");
312 		b.append("committer A. U. Thor <author@localhost> " + when + "\n");
313 
314 		byte[] data = encodeASCII(b.toString());
315 		checker.checkCommit(data);
316 		checker.check(OBJ_COMMIT, data);
317 	}
318 
319 	@Test
320 	public void testInvalidCommitNoTree1() {
321 		StringBuilder b = new StringBuilder();
322 		b.append("parent ");
323 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
324 		b.append('\n');
325 		assertCorrupt("no tree header", OBJ_COMMIT, b);
326 	}
327 
328 	@Test
329 	public void testInvalidCommitNoTree2() {
330 		StringBuilder b = new StringBuilder();
331 		b.append("trie ");
332 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
333 		b.append('\n');
334 		assertCorrupt("no tree header", OBJ_COMMIT, b);
335 	}
336 
337 	@Test
338 	public void testInvalidCommitNoTree3() {
339 		StringBuilder b = new StringBuilder();
340 		b.append("tree");
341 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
342 		b.append('\n');
343 		assertCorrupt("no tree header", OBJ_COMMIT, b);
344 	}
345 
346 	@Test
347 	public void testInvalidCommitNoTree4() {
348 		StringBuilder b = new StringBuilder();
349 		b.append("tree\t");
350 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
351 		b.append('\n');
352 		assertCorrupt("no tree header", OBJ_COMMIT, b);
353 	}
354 
355 	@Test
356 	public void testInvalidCommitInvalidTree1() {
357 		StringBuilder b = new StringBuilder();
358 		b.append("tree ");
359 		b.append("zzzzfa841874ccc9f2ef7c48d0c76226f89b7189");
360 		b.append('\n');
361 		assertCorrupt("invalid tree", OBJ_COMMIT, b);
362 	}
363 
364 	@Test
365 	public void testInvalidCommitInvalidTree2() {
366 		StringBuilder b = new StringBuilder();
367 		b.append("tree ");
368 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
369 		b.append("z\n");
370 		assertCorrupt("invalid tree", OBJ_COMMIT, b);
371 	}
372 
373 	@Test
374 	public void testInvalidCommitInvalidTree3() {
375 		StringBuilder b = new StringBuilder();
376 		b.append("tree ");
377 		b.append("be9b");
378 		b.append("\n");
379 
380 		byte[] data = encodeASCII(b.toString());
381 		assertCorrupt("invalid tree", OBJ_COMMIT, data);
382 	}
383 
384 	@Test
385 	public void testInvalidCommitInvalidTree4() {
386 		StringBuilder b = new StringBuilder();
387 		b.append("tree  ");
388 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
389 		b.append('\n');
390 		assertCorrupt("invalid tree", OBJ_COMMIT, b);
391 	}
392 
393 	@Test
394 	public void testInvalidCommitInvalidParent1() {
395 		StringBuilder b = new StringBuilder();
396 		b.append("tree ");
397 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
398 		b.append('\n');
399 		b.append("parent ");
400 		b.append("\n");
401 		assertCorrupt("invalid parent", OBJ_COMMIT, b);
402 	}
403 
404 	@Test
405 	public void testInvalidCommitInvalidParent2() {
406 		StringBuilder b = new StringBuilder();
407 		b.append("tree ");
408 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
409 		b.append('\n');
410 		b.append("parent ");
411 		b.append("zzzzfa841874ccc9f2ef7c48d0c76226f89b7189");
412 		b.append("\n");
413 		assertCorrupt("invalid parent", OBJ_COMMIT, b);
414 	}
415 
416 	@Test
417 	public void testInvalidCommitInvalidParent3() {
418 		StringBuilder b = new StringBuilder();
419 		b.append("tree ");
420 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
421 		b.append('\n');
422 		b.append("parent  ");
423 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
424 		b.append("\n");
425 		assertCorrupt("invalid parent", OBJ_COMMIT, b);
426 	}
427 
428 	@Test
429 	public void testInvalidCommitInvalidParent4() {
430 		StringBuilder b = new StringBuilder();
431 		b.append("tree ");
432 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
433 		b.append('\n');
434 		b.append("parent  ");
435 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
436 		b.append("z\n");
437 		assertCorrupt("invalid parent", OBJ_COMMIT, b);
438 	}
439 
440 	@Test
441 	public void testInvalidCommitInvalidParent5() {
442 		StringBuilder b = new StringBuilder();
443 		b.append("tree ");
444 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
445 		b.append('\n');
446 		b.append("parent\t");
447 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
448 		b.append("\n");
449 
450 		byte[] data = encodeASCII(b.toString());
451 		// Yes, really, we complain about author not being
452 		// found as the invalid parent line wasn't consumed.
453 		assertCorrupt("no author", OBJ_COMMIT, data);
454 	}
455 
456 	@Test
457 	public void testInvalidCommitNoAuthor() throws CorruptObjectException {
458 		StringBuilder b = new StringBuilder();
459 		b.append("tree ");
460 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
461 		b.append('\n');
462 		b.append("committer A. U. Thor <author@localhost> 1 +0000\n");
463 
464 		byte[] data = encodeASCII(b.toString());
465 		assertCorrupt("no author", OBJ_COMMIT, data);
466 		assertSkipListAccepts(OBJ_COMMIT, data);
467 	}
468 
469 	@Test
470 	public void testInvalidCommitNoCommitter1() throws CorruptObjectException {
471 		StringBuilder b = new StringBuilder();
472 		b.append("tree ");
473 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
474 		b.append('\n');
475 		b.append("author A. U. Thor <author@localhost> 1 +0000\n");
476 
477 		byte[] data = encodeASCII(b.toString());
478 		assertCorrupt("no committer", OBJ_COMMIT, data);
479 		assertSkipListAccepts(OBJ_COMMIT, data);
480 	}
481 
482 	@Test
483 	public void testInvalidCommitNoCommitter2() throws CorruptObjectException {
484 		StringBuilder b = new StringBuilder();
485 		b.append("tree ");
486 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
487 		b.append('\n');
488 		b.append("author A. U. Thor <author@localhost> 1 +0000\n");
489 		b.append("\n");
490 
491 		byte[] data = encodeASCII(b.toString());
492 		assertCorrupt("no committer", OBJ_COMMIT, data);
493 		assertSkipListAccepts(OBJ_COMMIT, data);
494 	}
495 
496 	@Test
497 	public void testInvalidCommitInvalidAuthor1()
498 			throws CorruptObjectException {
499 		StringBuilder b = new StringBuilder();
500 		b.append("tree ");
501 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
502 		b.append('\n');
503 		b.append("author A. U. Thor <foo 1 +0000\n");
504 
505 		byte[] data = encodeASCII(b.toString());
506 		assertCorrupt("bad email", OBJ_COMMIT, data);
507 		assertSkipListAccepts(OBJ_COMMIT, data);
508 	}
509 
510 	@Test
511 	public void testInvalidCommitInvalidAuthor2()
512 			throws CorruptObjectException {
513 		StringBuilder b = new StringBuilder();
514 		b.append("tree ");
515 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
516 		b.append('\n');
517 		b.append("author A. U. Thor foo> 1 +0000\n");
518 
519 		byte[] data = encodeASCII(b.toString());
520 		assertCorrupt("missing email", OBJ_COMMIT, data);
521 		assertSkipListAccepts(OBJ_COMMIT, data);
522 	}
523 
524 	@Test
525 	public void testInvalidCommitInvalidAuthor3()
526 			throws CorruptObjectException {
527 		StringBuilder b = new StringBuilder();
528 		b.append("tree ");
529 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
530 		b.append('\n');
531 		b.append("author 1 +0000\n");
532 
533 		byte[] data = encodeASCII(b.toString());
534 		assertCorrupt("missing email", OBJ_COMMIT, data);
535 		assertSkipListAccepts(OBJ_COMMIT, data);
536 	}
537 
538 	@Test
539 	public void testInvalidCommitInvalidAuthor4()
540 			throws CorruptObjectException {
541 		StringBuilder b = new StringBuilder();
542 		b.append("tree ");
543 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
544 		b.append('\n');
545 		b.append("author a <b> +0000\n");
546 
547 		byte[] data = encodeASCII(b.toString());
548 		assertCorrupt("bad date", OBJ_COMMIT, data);
549 		assertSkipListAccepts(OBJ_COMMIT, data);
550 	}
551 
552 	@Test
553 	public void testInvalidCommitInvalidAuthor5()
554 			throws CorruptObjectException {
555 		StringBuilder b = new StringBuilder();
556 		b.append("tree ");
557 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
558 		b.append('\n');
559 		b.append("author a <b>\n");
560 
561 		byte[] data = encodeASCII(b.toString());
562 		assertCorrupt("bad date", OBJ_COMMIT, data);
563 		assertSkipListAccepts(OBJ_COMMIT, data);
564 	}
565 
566 	@Test
567 	public void testInvalidCommitInvalidAuthor6()
568 			throws CorruptObjectException {
569 		StringBuilder b = new StringBuilder();
570 		b.append("tree ");
571 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
572 		b.append('\n');
573 		b.append("author a <b> z");
574 
575 		byte[] data = encodeASCII(b.toString());
576 		assertCorrupt("bad date", OBJ_COMMIT, data);
577 		assertSkipListAccepts(OBJ_COMMIT, data);
578 	}
579 
580 	@Test
581 	public void testInvalidCommitInvalidAuthor7()
582 			throws CorruptObjectException {
583 		StringBuilder b = new StringBuilder();
584 		b.append("tree ");
585 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
586 		b.append('\n');
587 		b.append("author a <b> 1 z");
588 
589 		byte[] data = encodeASCII(b.toString());
590 		assertCorrupt("bad time zone", OBJ_COMMIT, data);
591 		assertSkipListAccepts(OBJ_COMMIT, data);
592 	}
593 
594 	@Test
595 	public void testInvalidCommitInvalidCommitter()
596 			throws CorruptObjectException {
597 		StringBuilder b = new StringBuilder();
598 		b.append("tree ");
599 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
600 		b.append('\n');
601 		b.append("author a <b> 1 +0000\n");
602 		b.append("committer a <");
603 
604 		byte[] data = encodeASCII(b.toString());
605 		assertCorrupt("bad email", OBJ_COMMIT, data);
606 		assertSkipListAccepts(OBJ_COMMIT, data);
607 	}
608 
609 	@Test
610 	public void testValidTag() throws CorruptObjectException {
611 		StringBuilder b = new StringBuilder();
612 		b.append("object ");
613 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
614 		b.append('\n');
615 		b.append("type commit\n");
616 		b.append("tag test-tag\n");
617 		b.append("tagger A. U. Thor <author@localhost> 1 +0000\n");
618 
619 		byte[] data = encodeASCII(b.toString());
620 		checker.checkTag(data);
621 		checker.check(OBJ_TAG, data);
622 	}
623 
624 	@Test
625 	public void testInvalidTagNoObject1() {
626 		assertCorrupt("no object header", OBJ_TAG, new byte[0]);
627 	}
628 
629 	@Test
630 	public void testInvalidTagNoObject2() {
631 		StringBuilder b = new StringBuilder();
632 		b.append("object\t");
633 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
634 		b.append('\n');
635 		assertCorrupt("no object header", OBJ_TAG, b);
636 	}
637 
638 	@Test
639 	public void testInvalidTagNoObject3() {
640 		StringBuilder b = new StringBuilder();
641 		b.append("obejct ");
642 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
643 		b.append('\n');
644 		assertCorrupt("no object header", OBJ_TAG, b);
645 	}
646 
647 	@Test
648 	public void testInvalidTagNoObject4() {
649 		StringBuilder b = new StringBuilder();
650 		b.append("object ");
651 		b.append("zz9bfa841874ccc9f2ef7c48d0c76226f89b7189");
652 		b.append('\n');
653 		assertCorrupt("invalid object", OBJ_TAG, b);
654 	}
655 
656 	@Test
657 	public void testInvalidTagNoObject5() {
658 		StringBuilder b = new StringBuilder();
659 		b.append("object ");
660 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
661 		b.append(" \n");
662 		assertCorrupt("invalid object", OBJ_TAG, b);
663 	}
664 
665 	@Test
666 	public void testInvalidTagNoObject6() {
667 		StringBuilder b = new StringBuilder();
668 		b.append("object ");
669 		b.append("be9");
670 		assertCorrupt("invalid object", OBJ_TAG, b);
671 	}
672 
673 	@Test
674 	public void testInvalidTagNoType1() {
675 		StringBuilder b = new StringBuilder();
676 		b.append("object ");
677 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
678 		b.append('\n');
679 		assertCorrupt("no type header", OBJ_TAG, b);
680 	}
681 
682 	@Test
683 	public void testInvalidTagNoType2() {
684 		StringBuilder b = new StringBuilder();
685 		b.append("object ");
686 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
687 		b.append('\n');
688 		b.append("type\tcommit\n");
689 		assertCorrupt("no type header", OBJ_TAG, b);
690 	}
691 
692 	@Test
693 	public void testInvalidTagNoType3() {
694 		StringBuilder b = new StringBuilder();
695 		b.append("object ");
696 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
697 		b.append('\n');
698 		b.append("tpye commit\n");
699 		assertCorrupt("no type header", OBJ_TAG, b);
700 	}
701 
702 	@Test
703 	public void testInvalidTagNoType4() {
704 		StringBuilder b = new StringBuilder();
705 		b.append("object ");
706 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
707 		b.append('\n');
708 		b.append("type commit");
709 		assertCorrupt("no tag header", OBJ_TAG, b);
710 	}
711 
712 	@Test
713 	public void testInvalidTagNoTagHeader1() {
714 		StringBuilder b = new StringBuilder();
715 		b.append("object ");
716 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
717 		b.append('\n');
718 		b.append("type commit\n");
719 		assertCorrupt("no tag header", OBJ_TAG, b);
720 	}
721 
722 	@Test
723 	public void testInvalidTagNoTagHeader2() {
724 		StringBuilder b = new StringBuilder();
725 		b.append("object ");
726 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
727 		b.append('\n');
728 		b.append("type commit\n");
729 		b.append("tag\tfoo\n");
730 		assertCorrupt("no tag header", OBJ_TAG, b);
731 	}
732 
733 	@Test
734 	public void testInvalidTagNoTagHeader3() {
735 		StringBuilder b = new StringBuilder();
736 		b.append("object ");
737 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
738 		b.append('\n');
739 		b.append("type commit\n");
740 		b.append("tga foo\n");
741 		assertCorrupt("no tag header", OBJ_TAG, b);
742 	}
743 
744 	@Test
745 	public void testValidTagHasNoTaggerHeader() throws CorruptObjectException {
746 		StringBuilder b = new StringBuilder();
747 		b.append("object ");
748 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
749 		b.append('\n');
750 		b.append("type commit\n");
751 		b.append("tag foo\n");
752 		checker.checkTag(encodeASCII(b.toString()));
753 	}
754 
755 	@Test
756 	public void testInvalidTagInvalidTaggerHeader1()
757 			throws CorruptObjectException {
758 		StringBuilder b = new StringBuilder();
759 		b.append("object ");
760 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
761 		b.append('\n');
762 		b.append("type commit\n");
763 		b.append("tag foo\n");
764 		b.append("tagger \n");
765 
766 		byte[] data = encodeASCII(b.toString());
767 		assertCorrupt("missing email", OBJ_TAG, data);
768 		checker.setAllowInvalidPersonIdent(true);
769 		checker.checkTag(data);
770 
771 		checker.setAllowInvalidPersonIdent(false);
772 		assertSkipListAccepts(OBJ_TAG, data);
773 	}
774 
775 	@Test
776 	public void testInvalidTagInvalidTaggerHeader3()
777 			throws CorruptObjectException {
778 		StringBuilder b = new StringBuilder();
779 		b.append("object ");
780 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
781 		b.append('\n');
782 		b.append("type commit\n");
783 		b.append("tag foo\n");
784 		b.append("tagger a < 1 +000\n");
785 
786 		byte[] data = encodeASCII(b.toString());
787 		assertCorrupt("bad email", OBJ_TAG, data);
788 		assertSkipListAccepts(OBJ_TAG, data);
789 	}
790 
791 	@Test
792 	public void testValidEmptyTree() throws CorruptObjectException {
793 		checker.checkTree(new byte[0]);
794 		checker.check(OBJ_TREE, new byte[0]);
795 	}
796 
797 	@Test
798 	public void testValidTree1() throws CorruptObjectException {
799 		StringBuilder b = new StringBuilder();
800 		entry(b, "100644 regular-file");
801 		checker.checkTree(encodeASCII(b.toString()));
802 	}
803 
804 	@Test
805 	public void testValidTree2() throws CorruptObjectException {
806 		StringBuilder b = new StringBuilder();
807 		entry(b, "100755 executable");
808 		checker.checkTree(encodeASCII(b.toString()));
809 	}
810 
811 	@Test
812 	public void testValidTree3() throws CorruptObjectException {
813 		StringBuilder b = new StringBuilder();
814 		entry(b, "40000 tree");
815 		checker.checkTree(encodeASCII(b.toString()));
816 	}
817 
818 	@Test
819 	public void testValidTree4() throws CorruptObjectException {
820 		StringBuilder b = new StringBuilder();
821 		entry(b, "120000 symlink");
822 		checker.checkTree(encodeASCII(b.toString()));
823 	}
824 
825 	@Test
826 	public void testValidTree5() throws CorruptObjectException {
827 		StringBuilder b = new StringBuilder();
828 		entry(b, "160000 git link");
829 		checker.checkTree(encodeASCII(b.toString()));
830 	}
831 
832 	@Test
833 	public void testValidTree6() throws CorruptObjectException {
834 		StringBuilder b = new StringBuilder();
835 		entry(b, "100644 .a");
836 		checker.checkTree(encodeASCII(b.toString()));
837 	}
838 
839 	@Test
840 	public void testValidTreeWithGitmodules() throws CorruptObjectException {
841 		ObjectId treeId = ObjectId
842 				.fromString("0123012301230123012301230123012301230123");
843 		StringBuilder b = new StringBuilder();
844 		ObjectId blobId = entry(b, "100644 .gitmodules");
845 
846 		byte[] data = encodeASCII(b.toString());
847 		checker.checkTree(treeId, data);
848 		assertEquals(1, checker.getGitsubmodules().size());
849 		assertEquals(treeId, checker.getGitsubmodules().get(0).getTreeId());
850 		assertEquals(blobId, checker.getGitsubmodules().get(0).getBlobId());
851 	}
852 
853 	/*
854 	 * Windows case insensitivity and long file name handling
855 	 * means that .gitmodules has many synonyms.
856 	 *
857 	 * Examples inspired by git.git's t/t0060-path-utils.sh, by
858 	 * Johannes Schindelin and Congyi Wu.
859 	 */
860 	@Test
861 	public void testNTFSGitmodules() throws CorruptObjectException {
862 		for (String gitmodules : new String[] {
863 			".GITMODULES",
864 			".gitmodules",
865 			".Gitmodules",
866 			".gitmoduleS",
867 			"gitmod~1",
868 			"GITMOD~1",
869 			"gitmod~4",
870 			"GI7EBA~1",
871 			"gi7eba~9",
872 			"GI7EB~10",
873 			"GI7E~123",
874 			"~1000000",
875 			"~9999999"
876 		}) {
877 			checker = new ObjectChecker(); // Reset the ObjectChecker state.
878 			checker.setSafeForWindows(true);
879 			ObjectId treeId = ObjectId
880 					.fromString("0123012301230123012301230123012301230123");
881 			StringBuilder b = new StringBuilder();
882 			ObjectId blobId = entry(b, "100644 " + gitmodules);
883 
884 			byte[] data = encodeASCII(b.toString());
885 			checker.checkTree(treeId, data);
886 			assertEquals(1, checker.getGitsubmodules().size());
887 			assertEquals(treeId, checker.getGitsubmodules().get(0).getTreeId());
888 			assertEquals(blobId, checker.getGitsubmodules().get(0).getBlobId());
889 		}
890 	}
891 
892 	@Test
893 	public void testNotGitmodules() throws CorruptObjectException {
894 		for (String notGitmodules : new String[] {
895 			".gitmodu",
896 			".gitmodules oh never mind",
897 		}) {
898 			checker = new ObjectChecker(); // Reset the ObjectChecker state.
899 			checker.setSafeForWindows(true);
900 			ObjectId treeId = ObjectId
901 					.fromString("0123012301230123012301230123012301230123");
902 			StringBuilder b = new StringBuilder();
903 			entry(b, "100644 " + notGitmodules);
904 
905 			byte[] data = encodeASCII(b.toString());
906 			checker.checkTree(treeId, data);
907 			assertEquals(0, checker.getGitsubmodules().size());
908 		}
909 	}
910 
911 	/*
912 	 * TODO HFS: match ".gitmodules" case-insensitively, after stripping out
913 	 * certain zero-length Unicode code points that HFS+ strips out
914 	 */
915 
916 	@Test
917 	public void testValidTreeWithGitmodulesUppercase()
918 			throws CorruptObjectException {
919 		ObjectId treeId = ObjectId
920 				.fromString("0123012301230123012301230123012301230123");
921 		StringBuilder b = new StringBuilder();
922 		ObjectId blobId = entry(b, "100644 .GITMODULES");
923 
924 		byte[] data = encodeASCII(b.toString());
925 		checker.setSafeForWindows(true);
926 		checker.checkTree(treeId, data);
927 		assertEquals(1, checker.getGitsubmodules().size());
928 		assertEquals(treeId, checker.getGitsubmodules().get(0).getTreeId());
929 		assertEquals(blobId, checker.getGitsubmodules().get(0).getBlobId());
930 	}
931 
932 	@Test
933 	public void testTreeWithInvalidGitmodules() throws CorruptObjectException {
934 		ObjectId treeId = ObjectId
935 				.fromString("0123012301230123012301230123012301230123");
936 		StringBuilder b = new StringBuilder();
937 		entry(b, "100644 .gitmodulez");
938 
939 		byte[] data = encodeASCII(b.toString());
940 		checker.checkTree(treeId, data);
941 		checker.setSafeForWindows(true);
942 		assertEquals(0, checker.getGitsubmodules().size());
943 	}
944 
945 	@Test
946 	public void testNullSha1InTreeEntry() throws CorruptObjectException {
947 		byte[] data = concat(
948 				encodeASCII("100644 A"), new byte[] { '\0' },
949 				new byte[OBJECT_ID_LENGTH]);
950 		assertCorrupt("entry points to null SHA-1", OBJ_TREE, data);
951 		assertSkipListAccepts(OBJ_TREE, data);
952 		checker.setIgnore(NULL_SHA1, true);
953 		checker.checkTree(data);
954 	}
955 
956 	@Test
957 	public void testValidPosixTree() throws CorruptObjectException {
958 		checkOneName("a<b>c:d|e");
959 		checkOneName("test ");
960 		checkOneName("test.");
961 		checkOneName("NUL");
962 	}
963 
964 	@Test
965 	public void testValidTreeSorting1() throws CorruptObjectException {
966 		StringBuilder b = new StringBuilder();
967 		entry(b, "100644 fooaaa");
968 		entry(b, "100755 foobar");
969 		checker.checkTree(encodeASCII(b.toString()));
970 	}
971 
972 	@Test
973 	public void testValidTreeSorting2() throws CorruptObjectException {
974 		StringBuilder b = new StringBuilder();
975 		entry(b, "100755 fooaaa");
976 		entry(b, "100644 foobar");
977 		checker.checkTree(encodeASCII(b.toString()));
978 	}
979 
980 	@Test
981 	public void testValidTreeSorting3() throws CorruptObjectException {
982 		StringBuilder b = new StringBuilder();
983 		entry(b, "40000 a");
984 		entry(b, "100644 b");
985 		checker.checkTree(encodeASCII(b.toString()));
986 	}
987 
988 	@Test
989 	public void testValidTreeSorting4() throws CorruptObjectException {
990 		StringBuilder b = new StringBuilder();
991 		entry(b, "100644 a");
992 		entry(b, "40000 b");
993 		checker.checkTree(encodeASCII(b.toString()));
994 	}
995 
996 	@Test
997 	public void testValidTreeSorting5() throws CorruptObjectException {
998 		StringBuilder b = new StringBuilder();
999 		entry(b, "100644 a.c");
1000 		entry(b, "40000 a");
1001 		entry(b, "100644 a0c");
1002 		checker.checkTree(encodeASCII(b.toString()));
1003 	}
1004 
1005 	@Test
1006 	public void testValidTreeSorting6() throws CorruptObjectException {
1007 		StringBuilder b = new StringBuilder();
1008 		entry(b, "40000 a");
1009 		entry(b, "100644 apple");
1010 		checker.checkTree(encodeASCII(b.toString()));
1011 	}
1012 
1013 	@Test
1014 	public void testValidTreeSorting7() throws CorruptObjectException {
1015 		StringBuilder b = new StringBuilder();
1016 		entry(b, "40000 an orang");
1017 		entry(b, "40000 an orange");
1018 		checker.checkTree(encodeASCII(b.toString()));
1019 	}
1020 
1021 	@Test
1022 	public void testValidTreeSorting8() throws CorruptObjectException {
1023 		StringBuilder b = new StringBuilder();
1024 		entry(b, "100644 a");
1025 		entry(b, "100644 a0c");
1026 		entry(b, "100644 b");
1027 		checker.checkTree(encodeASCII(b.toString()));
1028 	}
1029 
1030 	@Test
1031 	public void testAcceptTreeModeWithZero() throws CorruptObjectException {
1032 		StringBuilder b = new StringBuilder();
1033 		entry(b, "040000 a");
1034 		byte[] data = encodeASCII(b.toString());
1035 		checker.setAllowLeadingZeroFileMode(true);
1036 		checker.checkTree(data);
1037 
1038 		checker.setAllowLeadingZeroFileMode(false);
1039 		assertSkipListAccepts(OBJ_TREE, data);
1040 
1041 		checker.setIgnore(ZERO_PADDED_FILEMODE, true);
1042 		checker.checkTree(data);
1043 	}
1044 
1045 	@Test
1046 	public void testInvalidTreeModeStartsWithZero1() {
1047 		StringBuilder b = new StringBuilder();
1048 		entry(b, "0 a");
1049 		assertCorrupt("mode starts with '0'", OBJ_TREE, b);
1050 	}
1051 
1052 	@Test
1053 	public void testInvalidTreeModeStartsWithZero2() {
1054 		StringBuilder b = new StringBuilder();
1055 		entry(b, "0100644 a");
1056 		assertCorrupt("mode starts with '0'", OBJ_TREE, b);
1057 	}
1058 
1059 	@Test
1060 	public void testInvalidTreeModeStartsWithZero3() {
1061 		StringBuilder b = new StringBuilder();
1062 		entry(b, "040000 a");
1063 		assertCorrupt("mode starts with '0'", OBJ_TREE, b);
1064 	}
1065 
1066 	@Test
1067 	public void testInvalidTreeModeNotOctal1() {
1068 		StringBuilder b = new StringBuilder();
1069 		entry(b, "8 a");
1070 		assertCorrupt("invalid mode character", OBJ_TREE, b);
1071 	}
1072 
1073 	@Test
1074 	public void testInvalidTreeModeNotOctal2() {
1075 		StringBuilder b = new StringBuilder();
1076 		entry(b, "Z a");
1077 		byte[] data = encodeASCII(b.toString());
1078 		assertCorrupt("invalid mode character", OBJ_TREE, data);
1079 		assertSkipListRejects("invalid mode character", OBJ_TREE, data);
1080 	}
1081 
1082 	@Test
1083 	public void testInvalidTreeModeNotSupportedMode1() {
1084 		StringBuilder b = new StringBuilder();
1085 		entry(b, "1 a");
1086 		byte[] data = encodeASCII(b.toString());
1087 		assertCorrupt("invalid mode 1", OBJ_TREE, data);
1088 		assertSkipListRejects("invalid mode 1", OBJ_TREE, data);
1089 	}
1090 
1091 	@Test
1092 	public void testInvalidTreeModeNotSupportedMode2() {
1093 		StringBuilder b = new StringBuilder();
1094 		entry(b, "170000 a");
1095 		assertCorrupt("invalid mode " + 0170000, OBJ_TREE, b);
1096 	}
1097 
1098 	@Test
1099 	public void testInvalidTreeModeMissingName() {
1100 		StringBuilder b = new StringBuilder();
1101 		b.append("100644");
1102 		assertCorrupt("truncated in mode", OBJ_TREE, b);
1103 	}
1104 
1105 	@Test
1106 	public void testInvalidTreeNameContainsSlash()
1107 			throws CorruptObjectException {
1108 		StringBuilder b = new StringBuilder();
1109 		entry(b, "100644 a/b");
1110 		byte[] data = encodeASCII(b.toString());
1111 		assertCorrupt("name contains '/'", OBJ_TREE, data);
1112 		assertSkipListAccepts(OBJ_TREE, data);
1113 		checker.setIgnore(FULL_PATHNAME, true);
1114 		checker.checkTree(data);
1115 	}
1116 
1117 	@Test
1118 	public void testInvalidTreeNameIsEmpty() throws CorruptObjectException {
1119 		StringBuilder b = new StringBuilder();
1120 		entry(b, "100644 ");
1121 		byte[] data = encodeASCII(b.toString());
1122 		assertCorrupt("zero length name", OBJ_TREE, data);
1123 		assertSkipListAccepts(OBJ_TREE, data);
1124 		checker.setIgnore(EMPTY_NAME, true);
1125 		checker.checkTree(data);
1126 	}
1127 
1128 	@Test
1129 	public void testInvalidTreeNameIsDot() throws CorruptObjectException {
1130 		StringBuilder b = new StringBuilder();
1131 		entry(b, "100644 .");
1132 		byte[] data = encodeASCII(b.toString());
1133 		assertCorrupt("invalid name '.'", OBJ_TREE, data);
1134 		assertSkipListAccepts(OBJ_TREE, data);
1135 		checker.setIgnore(HAS_DOT, true);
1136 		checker.checkTree(data);
1137 	}
1138 
1139 	@Test
1140 	public void testInvalidTreeNameIsDotDot() throws CorruptObjectException {
1141 		StringBuilder b = new StringBuilder();
1142 		entry(b, "100644 ..");
1143 		byte[] data = encodeASCII(b.toString());
1144 		assertCorrupt("invalid name '..'", OBJ_TREE, data);
1145 		assertSkipListAccepts(OBJ_TREE, data);
1146 		checker.setIgnore(HAS_DOTDOT, true);
1147 		checker.checkTree(data);
1148 	}
1149 
1150 	@Test
1151 	public void testInvalidTreeNameIsGit() throws CorruptObjectException {
1152 		StringBuilder b = new StringBuilder();
1153 		entry(b, "100644 .git");
1154 		byte[] data = encodeASCII(b.toString());
1155 		assertCorrupt("invalid name '.git'", OBJ_TREE, data);
1156 		assertSkipListAccepts(OBJ_TREE, data);
1157 		checker.setIgnore(HAS_DOTGIT, true);
1158 		checker.checkTree(data);
1159 	}
1160 
1161 	@Test
1162 	public void testInvalidTreeNameIsMixedCaseGit()
1163 			throws CorruptObjectException {
1164 		StringBuilder b = new StringBuilder();
1165 		entry(b, "100644 .GiT");
1166 		byte[] data = encodeASCII(b.toString());
1167 		assertCorrupt("invalid name '.GiT'", OBJ_TREE, data);
1168 		assertSkipListAccepts(OBJ_TREE, data);
1169 		checker.setIgnore(HAS_DOTGIT, true);
1170 		checker.checkTree(data);
1171 	}
1172 
1173 	@Test
1174 	public void testInvalidTreeNameIsMacHFSGit() throws CorruptObjectException {
1175 		StringBuilder b = new StringBuilder();
1176 		entry(b, "100644 .gi\u200Ct");
1177 		byte[] data = encode(b.toString());
1178 
1179 		// Fine on POSIX.
1180 		checker.checkTree(data);
1181 
1182 		// Rejected on Mac OS.
1183 		checker.setSafeForMacOS(true);
1184 		assertCorrupt(
1185 				"invalid name '.gi\u200Ct' contains ignorable Unicode characters",
1186 				OBJ_TREE, data);
1187 		assertSkipListAccepts(OBJ_TREE, data);
1188 		checker.setIgnore(HAS_DOTGIT, true);
1189 		checker.checkTree(data);
1190 	}
1191 
1192 	@Test
1193 	public void testInvalidTreeNameIsMacHFSGit2()
1194 			throws CorruptObjectException {
1195 		StringBuilder b = new StringBuilder();
1196 		entry(b, "100644 \u206B.git");
1197 		byte[] data = encode(b.toString());
1198 
1199 		// Fine on POSIX.
1200 		checker.checkTree(data);
1201 
1202 		// Rejected on Mac OS.
1203 		checker.setSafeForMacOS(true);
1204 		assertCorrupt(
1205 				"invalid name '\u206B.git' contains ignorable Unicode characters",
1206 				OBJ_TREE, data);
1207 		assertSkipListAccepts(OBJ_TREE, data);
1208 		checker.setIgnore(HAS_DOTGIT, true);
1209 		checker.checkTree(data);
1210 	}
1211 
1212 	@Test
1213 	public void testInvalidTreeNameIsMacHFSGit3()
1214 			throws CorruptObjectException {
1215 		StringBuilder b = new StringBuilder();
1216 		entry(b, "100644 .git\uFEFF");
1217 		byte[] data = encode(b.toString());
1218 
1219 		// Fine on POSIX.
1220 		checker.checkTree(data);
1221 
1222 		// Rejected on Mac OS.
1223 		checker.setSafeForMacOS(true);
1224 		assertCorrupt(
1225 				"invalid name '.git\uFEFF' contains ignorable Unicode characters",
1226 				OBJ_TREE, data);
1227 		assertSkipListAccepts(OBJ_TREE, data);
1228 		checker.setIgnore(HAS_DOTGIT, true);
1229 		checker.checkTree(data);
1230 	}
1231 
1232 
1233 
1234 	@Test
1235 	public void testInvalidTreeNameIsMacHFSGitCorruptUTF8AtEnd()
1236 			throws CorruptObjectException {
1237 		byte[] data = concat(encode("100644 .git"),
1238 				new byte[] { (byte) 0xef });
1239 		StringBuilder b = new StringBuilder();
1240 		entry(b, "");
1241 		data = concat(data, encode(b.toString()));
1242 
1243 		// Fine on POSIX.
1244 		checker.checkTree(data);
1245 
1246 		// Rejected on Mac OS.
1247 		checker.setSafeForMacOS(true);
1248 		assertCorrupt(
1249 				"invalid name contains byte sequence '0xef' which is not a valid UTF-8 character",
1250 				OBJ_TREE, data);
1251 		assertSkipListAccepts(OBJ_TREE, data);
1252 	}
1253 
1254 	@Test
1255 	public void testInvalidTreeNameIsMacHFSGitCorruptUTF8AtEnd2()
1256 			throws CorruptObjectException {
1257 		byte[] data = concat(encode("100644 .git"),
1258 				new byte[] {
1259 				(byte) 0xe2, (byte) 0xab });
1260 		StringBuilder b = new StringBuilder();
1261 		entry(b, "");
1262 		data = concat(data, encode(b.toString()));
1263 
1264 		// Fine on POSIX.
1265 		checker.checkTree(data);
1266 
1267 		// Rejected on Mac OS.
1268 		checker.setSafeForMacOS(true);
1269 		assertCorrupt(
1270 				"invalid name contains byte sequence '0xe2ab' which is not a valid UTF-8 character",
1271 				OBJ_TREE, data);
1272 		assertSkipListAccepts(OBJ_TREE, data);
1273 	}
1274 
1275 	@Test
1276 	public void testInvalidTreeNameIsNotMacHFSGit()
1277 			throws CorruptObjectException {
1278 		StringBuilder b = new StringBuilder();
1279 		entry(b, "100644 .git\u200Cx");
1280 		byte[] data = encode(b.toString());
1281 		checker.setSafeForMacOS(true);
1282 		checker.checkTree(data);
1283 	}
1284 
1285 	@Test
1286 	public void testInvalidTreeNameIsNotMacHFSGit2()
1287 			throws CorruptObjectException {
1288 		StringBuilder b = new StringBuilder();
1289 		entry(b, "100644 .kit\u200C");
1290 		byte[] data = encode(b.toString());
1291 		checker.setSafeForMacOS(true);
1292 		checker.checkTree(data);
1293 	}
1294 
1295 	@Test
1296 	public void testInvalidTreeNameIsNotMacHFSGitOtherPlatform()
1297 			throws CorruptObjectException {
1298 		StringBuilder b = new StringBuilder();
1299 		entry(b, "100644 .git\u200C");
1300 		byte[] data = encode(b.toString());
1301 		checker.checkTree(data);
1302 	}
1303 
1304 	@Test
1305 	public void testInvalidTreeNameIsDotGitDot() throws CorruptObjectException {
1306 		StringBuilder b = new StringBuilder();
1307 		entry(b, "100644 .git.");
1308 		byte[] data = encodeASCII(b.toString());
1309 		assertCorrupt("invalid name '.git.'", OBJ_TREE, data);
1310 		assertSkipListAccepts(OBJ_TREE, data);
1311 		checker.setIgnore(HAS_DOTGIT, true);
1312 		checker.checkTree(data);
1313 	}
1314 
1315 	@Test
1316 	public void testValidTreeNameIsDotGitDotDot()
1317 			throws CorruptObjectException {
1318 		StringBuilder b = new StringBuilder();
1319 		entry(b, "100644 .git..");
1320 		checker.checkTree(encodeASCII(b.toString()));
1321 	}
1322 
1323 	@Test
1324 	public void testInvalidTreeNameIsDotGitSpace()
1325 			throws CorruptObjectException {
1326 		StringBuilder b = new StringBuilder();
1327 		entry(b, "100644 .git ");
1328 		byte[] data = encodeASCII(b.toString());
1329 		assertCorrupt("invalid name '.git '", OBJ_TREE, data);
1330 		assertSkipListAccepts(OBJ_TREE, data);
1331 		checker.setIgnore(HAS_DOTGIT, true);
1332 		checker.checkTree(data);
1333 	}
1334 
1335 	@Test
1336 	public void testInvalidTreeNameIsDotGitSomething()
1337 			throws CorruptObjectException {
1338 		StringBuilder b = new StringBuilder();
1339 		entry(b, "100644 .gitfoobar");
1340 		byte[] data = encodeASCII(b.toString());
1341 		checker.checkTree(data);
1342 	}
1343 
1344 	@Test
1345 	public void testInvalidTreeNameIsDotGitSomethingSpaceSomething()
1346 			throws CorruptObjectException {
1347 		StringBuilder b = new StringBuilder();
1348 		entry(b, "100644 .gitfoo bar");
1349 		byte[] data = encodeASCII(b.toString());
1350 		checker.checkTree(data);
1351 	}
1352 
1353 	@Test
1354 	public void testInvalidTreeNameIsDotGitSomethingDot()
1355 			throws CorruptObjectException {
1356 		StringBuilder b = new StringBuilder();
1357 		entry(b, "100644 .gitfoobar.");
1358 		byte[] data = encodeASCII(b.toString());
1359 		checker.checkTree(data);
1360 	}
1361 
1362 	@Test
1363 	public void testInvalidTreeNameIsDotGitSomethingDotDot()
1364 			throws CorruptObjectException {
1365 		StringBuilder b = new StringBuilder();
1366 		entry(b, "100644 .gitfoobar..");
1367 		byte[] data = encodeASCII(b.toString());
1368 		checker.checkTree(data);
1369 	}
1370 
1371 	@Test
1372 	public void testInvalidTreeNameIsDotGitDotSpace()
1373 			throws CorruptObjectException {
1374 		StringBuilder b = new StringBuilder();
1375 		entry(b, "100644 .git. ");
1376 		byte[] data = encodeASCII(b.toString());
1377 		assertCorrupt("invalid name '.git. '", OBJ_TREE, data);
1378 		assertSkipListAccepts(OBJ_TREE, data);
1379 		checker.setIgnore(HAS_DOTGIT, true);
1380 		checker.checkTree(data);
1381 	}
1382 
1383 	@Test
1384 	public void testInvalidTreeNameIsDotGitSpaceDot()
1385 			throws CorruptObjectException {
1386 		StringBuilder b = new StringBuilder();
1387 		entry(b, "100644 .git . ");
1388 		byte[] data = encodeASCII(b.toString());
1389 		assertCorrupt("invalid name '.git . '", OBJ_TREE, data);
1390 		assertSkipListAccepts(OBJ_TREE, data);
1391 		checker.setIgnore(HAS_DOTGIT, true);
1392 		checker.checkTree(data);
1393 	}
1394 
1395 	@Test
1396 	public void testInvalidTreeNameIsGITTilde1() throws CorruptObjectException {
1397 		StringBuilder b = new StringBuilder();
1398 		entry(b, "100644 GIT~1");
1399 		byte[] data = encodeASCII(b.toString());
1400 		assertCorrupt("invalid name 'GIT~1'", OBJ_TREE, data);
1401 		assertSkipListAccepts(OBJ_TREE, data);
1402 		checker.setIgnore(HAS_DOTGIT, true);
1403 		checker.checkTree(data);
1404 	}
1405 
1406 	@Test
1407 	public void testInvalidTreeNameIsGiTTilde1() throws CorruptObjectException {
1408 		StringBuilder b = new StringBuilder();
1409 		entry(b, "100644 GiT~1");
1410 		byte[] data = encodeASCII(b.toString());
1411 		assertCorrupt("invalid name 'GiT~1'", OBJ_TREE, data);
1412 		assertSkipListAccepts(OBJ_TREE, data);
1413 		checker.setIgnore(HAS_DOTGIT, true);
1414 		checker.checkTree(data);
1415 	}
1416 
1417 	@Test
1418 	public void testValidTreeNameIsGitTilde11() throws CorruptObjectException {
1419 		StringBuilder b = new StringBuilder();
1420 		entry(b, "100644 GIT~11");
1421 		byte[] data = encodeASCII(b.toString());
1422 		checker.checkTree(data);
1423 	}
1424 
1425 	@Test
1426 	public void testInvalidTreeTruncatedInName() {
1427 		StringBuilder b = new StringBuilder();
1428 		b.append("100644 b");
1429 		byte[] data = encodeASCII(b.toString());
1430 		assertCorrupt("truncated in name", OBJ_TREE, data);
1431 		assertSkipListRejects("truncated in name", OBJ_TREE, data);
1432 	}
1433 
1434 	@Test
1435 	public void testInvalidTreeTruncatedInObjectId() {
1436 		StringBuilder b = new StringBuilder();
1437 		b.append("100644 b\0\1\2");
1438 		byte[] data = encodeASCII(b.toString());
1439 		assertCorrupt("truncated in object id", OBJ_TREE, data);
1440 		assertSkipListRejects("truncated in object id", OBJ_TREE, data);
1441 	}
1442 
1443 	@Test
1444 	public void testInvalidTreeBadSorting1() throws CorruptObjectException {
1445 		StringBuilder b = new StringBuilder();
1446 		entry(b, "100644 foobar");
1447 		entry(b, "100644 fooaaa");
1448 		byte[] data = encodeASCII(b.toString());
1449 
1450 		assertCorrupt("incorrectly sorted", OBJ_TREE, data);
1451 
1452 		ObjectId id = idFor(OBJ_TREE, data);
1453 		try {
1454 			checker.check(id, OBJ_TREE, data);
1455 			fail("Did not throw CorruptObjectException");
1456 		} catch (CorruptObjectException e) {
1457 			assertSame(TREE_NOT_SORTED, e.getErrorType());
1458 			assertEquals("treeNotSorted: object " + id.name()
1459 					+ ": incorrectly sorted", e.getMessage());
1460 		}
1461 
1462 		assertSkipListAccepts(OBJ_TREE, data);
1463 		checker.setIgnore(TREE_NOT_SORTED, true);
1464 		checker.checkTree(data);
1465 	}
1466 
1467 	@Test
1468 	public void testInvalidTreeBadSorting2() throws CorruptObjectException {
1469 		StringBuilder b = new StringBuilder();
1470 		entry(b, "40000 a");
1471 		entry(b, "100644 a.c");
1472 		byte[] data = encodeASCII(b.toString());
1473 		assertCorrupt("incorrectly sorted", OBJ_TREE, data);
1474 		assertSkipListAccepts(OBJ_TREE, data);
1475 		checker.setIgnore(TREE_NOT_SORTED, true);
1476 		checker.checkTree(data);
1477 	}
1478 
1479 	@Test
1480 	public void testInvalidTreeBadSorting3() throws CorruptObjectException {
1481 		StringBuilder b = new StringBuilder();
1482 		entry(b, "100644 a0c");
1483 		entry(b, "40000 a");
1484 		byte[] data = encodeASCII(b.toString());
1485 		assertCorrupt("incorrectly sorted", OBJ_TREE, data);
1486 		assertSkipListAccepts(OBJ_TREE, data);
1487 		checker.setIgnore(TREE_NOT_SORTED, true);
1488 		checker.checkTree(data);
1489 	}
1490 
1491 	@Test
1492 	public void testInvalidTreeDuplicateNames1_File()
1493 			throws CorruptObjectException {
1494 		StringBuilder b = new StringBuilder();
1495 		entry(b, "100644 a");
1496 		entry(b, "100644 a");
1497 		byte[] data = encodeASCII(b.toString());
1498 		assertCorrupt("duplicate entry names", OBJ_TREE, data);
1499 		assertSkipListAccepts(OBJ_TREE, data);
1500 		checker.setIgnore(DUPLICATE_ENTRIES, true);
1501 		checker.checkTree(data);
1502 	}
1503 
1504 	@Test
1505 	public void testInvalidTreeDuplicateNames1_Tree()
1506 			throws CorruptObjectException {
1507 		StringBuilder b = new StringBuilder();
1508 		entry(b, "40000 a");
1509 		entry(b, "40000 a");
1510 		byte[] data = encodeASCII(b.toString());
1511 		assertCorrupt("duplicate entry names", OBJ_TREE, data);
1512 		assertSkipListAccepts(OBJ_TREE, data);
1513 		checker.setIgnore(DUPLICATE_ENTRIES, true);
1514 		checker.checkTree(data);
1515 	}
1516 
1517 	@Test
1518 	public void testInvalidTreeDuplicateNames2() throws CorruptObjectException {
1519 		StringBuilder b = new StringBuilder();
1520 		entry(b, "100644 a");
1521 		entry(b, "100755 a");
1522 		byte[] data = encodeASCII(b.toString());
1523 		assertCorrupt("duplicate entry names", OBJ_TREE, data);
1524 		assertSkipListAccepts(OBJ_TREE, data);
1525 		checker.setIgnore(DUPLICATE_ENTRIES, true);
1526 		checker.checkTree(data);
1527 	}
1528 
1529 	@Test
1530 	public void testInvalidTreeDuplicateNames3() throws CorruptObjectException {
1531 		StringBuilder b = new StringBuilder();
1532 		entry(b, "100644 a");
1533 		entry(b, "40000 a");
1534 		byte[] data = encodeASCII(b.toString());
1535 		assertCorrupt("duplicate entry names", OBJ_TREE, data);
1536 		assertSkipListAccepts(OBJ_TREE, data);
1537 		checker.setIgnore(DUPLICATE_ENTRIES, true);
1538 		checker.checkTree(data);
1539 	}
1540 
1541 	@Test
1542 	public void testInvalidTreeDuplicateNames4() throws CorruptObjectException {
1543 		StringBuilder b = new StringBuilder();
1544 		entry(b, "100644 a");
1545 		entry(b, "100644 a.c");
1546 		entry(b, "100644 a.d");
1547 		entry(b, "100644 a.e");
1548 		entry(b, "40000 a");
1549 		entry(b, "100644 zoo");
1550 		byte[] data = encodeASCII(b.toString());
1551 		assertCorrupt("duplicate entry names", OBJ_TREE, data);
1552 		assertSkipListAccepts(OBJ_TREE, data);
1553 		checker.setIgnore(DUPLICATE_ENTRIES, true);
1554 		checker.checkTree(data);
1555 	}
1556 
1557 	@Test
1558 	public void testInvalidTreeDuplicateNames5()
1559 			throws CorruptObjectException {
1560 		StringBuilder b = new StringBuilder();
1561 		entry(b, "100644 A");
1562 		entry(b, "100644 a");
1563 		byte[] data = b.toString().getBytes(UTF_8);
1564 		checker.setSafeForWindows(true);
1565 		assertCorrupt("duplicate entry names", OBJ_TREE, data);
1566 		assertSkipListAccepts(OBJ_TREE, data);
1567 		checker.setIgnore(DUPLICATE_ENTRIES, true);
1568 		checker.checkTree(data);
1569 	}
1570 
1571 	@Test
1572 	public void testInvalidTreeDuplicateNames6()
1573 			throws CorruptObjectException {
1574 		StringBuilder b = new StringBuilder();
1575 		entry(b, "100644 A");
1576 		entry(b, "100644 a");
1577 		byte[] data = b.toString().getBytes(UTF_8);
1578 		checker.setSafeForMacOS(true);
1579 		assertCorrupt("duplicate entry names", OBJ_TREE, data);
1580 		assertSkipListAccepts(OBJ_TREE, data);
1581 		checker.setIgnore(DUPLICATE_ENTRIES, true);
1582 		checker.checkTree(data);
1583 	}
1584 
1585 	@Test
1586 	public void testInvalidTreeDuplicateNames7()
1587 			throws CorruptObjectException {
1588 		StringBuilder b = new StringBuilder();
1589 		entry(b, "100644 \u0065\u0301");
1590 		entry(b, "100644 \u00e9");
1591 		byte[] data = b.toString().getBytes(UTF_8);
1592 		checker.setSafeForMacOS(true);
1593 		assertCorrupt("duplicate entry names", OBJ_TREE, data);
1594 		assertSkipListAccepts(OBJ_TREE, data);
1595 		checker.setIgnore(DUPLICATE_ENTRIES, true);
1596 		checker.checkTree(data);
1597 	}
1598 
1599 	@Test
1600 	public void testInvalidTreeDuplicateNames8()
1601 			throws CorruptObjectException {
1602 		StringBuilder b = new StringBuilder();
1603 		entry(b, "100644 A");
1604 		checker.setSafeForMacOS(true);
1605 		checker.checkTree(b.toString().getBytes(UTF_8));
1606 	}
1607 
1608 	@Test
1609 	public void testRejectNulInPathSegment() {
1610 		try {
1611 			checker.checkPathSegment(encodeASCII("a\u0000b"), 0, 3);
1612 			fail("incorrectly accepted NUL in middle of name");
1613 		} catch (CorruptObjectException e) {
1614 			assertEquals("name contains byte 0x00", e.getMessage());
1615 		}
1616 	}
1617 
1618 	@Test
1619 	public void testRejectSpaceAtEndOnWindows() {
1620 		checker.setSafeForWindows(true);
1621 		try {
1622 			checkOneName("test ");
1623 			fail("incorrectly accepted space at end");
1624 		} catch (CorruptObjectException e) {
1625 			assertEquals("invalid name ends with ' '", e.getMessage());
1626 		}
1627 	}
1628 
1629 	@Test
1630 	public void testBug477090() throws CorruptObjectException {
1631 		checker.setSafeForMacOS(true);
1632 		final byte[] bytes = {
1633 				// U+221E 0xe2889e INFINITY ∞
1634 				(byte) 0xe2, (byte) 0x88, (byte) 0x9e,
1635 				// .html
1636 				0x2e, 0x68, 0x74, 0x6d, 0x6c };
1637 		checker.checkPathSegment(bytes, 0, bytes.length);
1638 	}
1639 
1640 	@Test
1641 	public void testRejectDotAtEndOnWindows() {
1642 		checker.setSafeForWindows(true);
1643 		try {
1644 			checkOneName("test.");
1645 			fail("incorrectly accepted dot at end");
1646 		} catch (CorruptObjectException e) {
1647 			assertEquals("invalid name ends with '.'", e.getMessage());
1648 		}
1649 	}
1650 
1651 	@Test
1652 	public void testRejectDevicesOnWindows() {
1653 		checker.setSafeForWindows(true);
1654 
1655 		String[] bad = { "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3",
1656 				"COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2",
1657 				"LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9" };
1658 		for (String b : bad) {
1659 			try {
1660 				checkOneName(b);
1661 				fail("incorrectly accepted " + b);
1662 			} catch (CorruptObjectException e) {
1663 				assertEquals("invalid name '" + b + "'", e.getMessage());
1664 			}
1665 			try {
1666 				checkOneName(b + ".txt");
1667 				fail("incorrectly accepted " + b + ".txt");
1668 			} catch (CorruptObjectException e) {
1669 				assertEquals("invalid name '" + b + "'", e.getMessage());
1670 			}
1671 		}
1672 	}
1673 
1674 	@Test
1675 	public void testRejectInvalidWindowsCharacters() {
1676 		checker.setSafeForWindows(true);
1677 		rejectName('<');
1678 		rejectName('>');
1679 		rejectName(':');
1680 		rejectName('"');
1681 		rejectName('\\');
1682 		rejectName('|');
1683 		rejectName('?');
1684 		rejectName('*');
1685 
1686 		for (int i = 1; i <= 31; i++)
1687 			rejectName((byte) i);
1688 	}
1689 
1690 	private void rejectName(char c) {
1691 		try {
1692 			checkOneName("te" + c + "st");
1693 			fail("incorrectly accepted with " + c);
1694 		} catch (CorruptObjectException e) {
1695 
1696 			assertEquals("char '" + c + "' not allowed in Windows filename", e.getMessage());
1697 		}
1698 	}
1699 
1700 	private void rejectName(byte c) {
1701 		String h = Integer.toHexString(c);
1702 		try {
1703 			checkOneName("te" + ((char) c) + "st");
1704 			fail("incorrectly accepted with 0x" + h);
1705 		} catch (CorruptObjectException e) {
1706 			assertEquals("byte 0x" + h + " not allowed in Windows filename", e.getMessage());
1707 		}
1708 	}
1709 
1710 
1711 	@Test
1712 	public void testRejectInvalidCharacter() {
1713 		try {
1714 			checkOneName("te/st");
1715 			fail("incorrectly accepted with /");
1716 		} catch (CorruptObjectException e) {
1717 
1718 			assertEquals("name contains '/'", e.getMessage());
1719 		}
1720 	}
1721 
1722 	private void checkOneName(String name) throws CorruptObjectException {
1723 		StringBuilder b = new StringBuilder();
1724 		entry(b, "100644 " + name);
1725 		checker.checkTree(encodeASCII(b.toString()));
1726 	}
1727 
1728 	/*
1729 	 * Returns the id generated for the entry
1730 	 */
1731 	private static ObjectId entry(StringBuilder b, String modeName) {
1732 		byte[] id = new byte[OBJECT_ID_LENGTH];
1733 
1734 		b.append(modeName);
1735 		b.append('\0');
1736 		for (int i = 0; i < OBJECT_ID_LENGTH; i++) {
1737 			b.append((char) i);
1738 			id[i] = (byte) i;
1739 		}
1740 
1741 		return ObjectId.fromRaw(id);
1742 	}
1743 
1744 	private void assertCorrupt(String msg, int type, StringBuilder b) {
1745 		assertCorrupt(msg, type, encodeASCII(b.toString()));
1746 	}
1747 
1748 	private void assertCorrupt(String msg, int type, byte[] data) {
1749 		try {
1750 			checker.check(type, data);
1751 			fail("Did not throw CorruptObjectException");
1752 		} catch (CorruptObjectException e) {
1753 			assertEquals(msg, e.getMessage());
1754 		}
1755 	}
1756 
1757 	private void assertSkipListAccepts(int type, byte[] data)
1758 			throws CorruptObjectException {
1759 		ObjectId id = idFor(type, data);
1760 		checker.setSkipList(set(id));
1761 		checker.check(id, type, data);
1762 		checker.setSkipList(null);
1763 	}
1764 
1765 	private void assertSkipListRejects(String msg, int type, byte[] data) {
1766 		ObjectId id = idFor(type, data);
1767 		checker.setSkipList(set(id));
1768 		try {
1769 			checker.check(id, type, data);
1770 			fail("Did not throw CorruptObjectException");
1771 		} catch (CorruptObjectException e) {
1772 			assertEquals(msg, e.getMessage());
1773 		}
1774 		checker.setSkipList(null);
1775 	}
1776 
1777 	private static ObjectIdSet set(ObjectId... ids) {
1778 		return (AnyObjectId objectId) -> {
1779 			for (ObjectId id : ids) {
1780 				if (id.equals(objectId)) {
1781 					return true;
1782 				}
1783 			}
1784 			return false;
1785 		};
1786 	}
1787 
1788 	@SuppressWarnings("resource")
1789 	private static ObjectId idFor(int type, byte[] raw) {
1790 		return new ObjectInserter.Formatter().idFor(type, raw);
1791 	}
1792 }