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 org.eclipse.jgit.lib.Constants.DOT_GIT_MODULES;
48  import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
49  import static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH;
50  import static org.eclipse.jgit.lib.Constants.OBJ_BAD;
51  import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
52  import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT;
53  import static org.eclipse.jgit.lib.Constants.OBJ_TAG;
54  import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
55  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_DATE;
56  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_EMAIL;
57  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_OBJECT_SHA1;
58  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_PARENT_SHA1;
59  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_TIMEZONE;
60  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_TREE_SHA1;
61  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_UTF8;
62  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.DUPLICATE_ENTRIES;
63  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.EMPTY_NAME;
64  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.FULL_PATHNAME;
65  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.HAS_DOT;
66  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.HAS_DOTDOT;
67  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.HAS_DOTGIT;
68  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_AUTHOR;
69  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_COMMITTER;
70  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_EMAIL;
71  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_OBJECT;
72  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_SPACE_BEFORE_DATE;
73  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_TAG_ENTRY;
74  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_TREE;
75  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_TYPE_ENTRY;
76  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.NULL_SHA1;
77  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.TREE_NOT_SORTED;
78  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.UNKNOWN_TYPE;
79  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.WIN32_BAD_NAME;
80  import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.ZERO_PADDED_FILEMODE;
81  import static org.eclipse.jgit.util.Paths.compare;
82  import static org.eclipse.jgit.util.Paths.compareSameName;
83  import static org.eclipse.jgit.util.RawParseUtils.nextLF;
84  import static org.eclipse.jgit.util.RawParseUtils.parseBase10;
85  
86  import java.text.MessageFormat;
87  import java.text.Normalizer;
88  import java.util.ArrayList;
89  import java.util.EnumSet;
90  import java.util.HashSet;
91  import java.util.List;
92  import java.util.Locale;
93  import java.util.Set;
94  
95  import org.eclipse.jgit.annotations.NonNull;
96  import org.eclipse.jgit.annotations.Nullable;
97  import org.eclipse.jgit.errors.CorruptObjectException;
98  import org.eclipse.jgit.internal.JGitText;
99  import org.eclipse.jgit.util.MutableInteger;
100 import org.eclipse.jgit.util.RawParseUtils;
101 import org.eclipse.jgit.util.StringUtils;
102 
103 /**
104  * Verifies that an object is formatted correctly.
105  * <p>
106  * Verifications made by this class only check that the fields of an object are
107  * formatted correctly. The ObjectId checksum of the object is not verified, and
108  * connectivity links between objects are also not verified. Its assumed that
109  * the caller can provide both of these validations on its own.
110  * <p>
111  * Instances of this class are not thread safe, but they may be reused to
112  * perform multiple object validations.
113  */
114 public class ObjectChecker {
115 	/** Header "tree " */
116 	public static final byte[] tree = Constants.encodeASCII("tree "); //$NON-NLS-1$
117 
118 	/** Header "parent " */
119 	public static final byte[] parent = Constants.encodeASCII("parent "); //$NON-NLS-1$
120 
121 	/** Header "author " */
122 	public static final byte[] author = Constants.encodeASCII("author "); //$NON-NLS-1$
123 
124 	/** Header "committer " */
125 	public static final byte[] committer = Constants.encodeASCII("committer "); //$NON-NLS-1$
126 
127 	/** Header "encoding " */
128 	public static final byte[] encoding = Constants.encodeASCII("encoding "); //$NON-NLS-1$
129 
130 	/** Header "object " */
131 	public static final byte[] object = Constants.encodeASCII("object "); //$NON-NLS-1$
132 
133 	/** Header "type " */
134 	public static final byte[] type = Constants.encodeASCII("type "); //$NON-NLS-1$
135 
136 	/** Header "tag " */
137 	public static final byte[] tag = Constants.encodeASCII("tag "); //$NON-NLS-1$
138 
139 	/** Header "tagger " */
140 	public static final byte[] tagger = Constants.encodeASCII("tagger "); //$NON-NLS-1$
141 
142 	/** Path ".gitmodules" */
143 	private static final byte[] dotGitmodules = Constants.encodeASCII(DOT_GIT_MODULES);
144 
145 	/**
146 	 * Potential issues identified by the checker.
147 	 *
148 	 * @since 4.2
149 	 */
150 	public enum ErrorType {
151 		// @formatter:off
152 		// These names match git-core so that fsck section keys also match.
153 		/***/ NULL_SHA1,
154 		/***/ DUPLICATE_ENTRIES,
155 		/***/ TREE_NOT_SORTED,
156 		/***/ ZERO_PADDED_FILEMODE,
157 		/***/ EMPTY_NAME,
158 		/***/ FULL_PATHNAME,
159 		/***/ HAS_DOT,
160 		/***/ HAS_DOTDOT,
161 		/***/ HAS_DOTGIT,
162 		/***/ BAD_OBJECT_SHA1,
163 		/***/ BAD_PARENT_SHA1,
164 		/***/ BAD_TREE_SHA1,
165 		/***/ MISSING_AUTHOR,
166 		/***/ MISSING_COMMITTER,
167 		/***/ MISSING_OBJECT,
168 		/***/ MISSING_TREE,
169 		/***/ MISSING_TYPE_ENTRY,
170 		/***/ MISSING_TAG_ENTRY,
171 		/***/ BAD_DATE,
172 		/***/ BAD_EMAIL,
173 		/***/ BAD_TIMEZONE,
174 		/***/ MISSING_EMAIL,
175 		/***/ MISSING_SPACE_BEFORE_DATE,
176 		/***/ UNKNOWN_TYPE,
177 
178 		// These are unique to JGit.
179 		/***/ WIN32_BAD_NAME,
180 		/***/ BAD_UTF8;
181 		// @formatter:on
182 
183 		/** @return camelCaseVersion of the name. */
184 		public String getMessageId() {
185 			String n = name();
186 			StringBuilder r = new StringBuilder(n.length());
187 			for (int i = 0; i < n.length(); i++) {
188 				char c = n.charAt(i);
189 				if (c != '_') {
190 					r.append(StringUtils.toLowerCase(c));
191 				} else {
192 					r.append(n.charAt(++i));
193 				}
194 			}
195 			return r.toString();
196 		}
197 	}
198 
199 	private final MutableObjectId tempId = new MutableObjectId();
200 	private final MutableInteger bufPtr = new MutableInteger();
201 
202 	private EnumSet<ErrorType> errors = EnumSet.allOf(ErrorType.class);
203 	private ObjectIdSet skipList;
204 	private boolean allowInvalidPersonIdent;
205 	private boolean windows;
206 	private boolean macosx;
207 
208 	private final List<GitmoduleEntry> gitsubmodules = new ArrayList<>();
209 
210 	/**
211 	 * Enable accepting specific malformed (but not horribly broken) objects.
212 	 *
213 	 * @param objects
214 	 *            collection of object names known to be broken in a non-fatal
215 	 *            way that should be ignored by the checker.
216 	 * @return {@code this}
217 	 * @since 4.2
218 	 */
219 	public ObjectChecker setSkipList(@Nullable ObjectIdSet objects) {
220 		skipList = objects;
221 		return this;
222 	}
223 
224 	/**
225 	 * Configure error types to be ignored across all objects.
226 	 *
227 	 * @param ids
228 	 *            error types to ignore. The caller's set is copied.
229 	 * @return {@code this}
230 	 * @since 4.2
231 	 */
232 	public ObjectChecker setIgnore(@Nullable Set<ErrorType> ids) {
233 		errors = EnumSet.allOf(ErrorType.class);
234 		if (ids != null) {
235 			errors.removeAll(ids);
236 		}
237 		return this;
238 	}
239 
240 	/**
241 	 * Add message type to be ignored across all objects.
242 	 *
243 	 * @param id
244 	 *            error type to ignore.
245 	 * @param ignore
246 	 *            true to ignore this error; false to treat the error as an
247 	 *            error and throw.
248 	 * @return {@code this}
249 	 * @since 4.2
250 	 */
251 	public ObjectChecker setIgnore(ErrorType id, boolean ignore) {
252 		if (ignore) {
253 			errors.remove(id);
254 		} else {
255 			errors.add(id);
256 		}
257 		return this;
258 	}
259 
260 	/**
261 	 * Enable accepting leading zero mode in tree entries.
262 	 * <p>
263 	 * Some broken Git libraries generated leading zeros in the mode part of
264 	 * tree entries. This is technically incorrect but gracefully allowed by
265 	 * git-core. JGit rejects such trees by default, but may need to accept
266 	 * them on broken histories.
267 	 * <p>
268 	 * Same as {@code setIgnore(ZERO_PADDED_FILEMODE, allow)}.
269 	 *
270 	 * @param allow allow leading zero mode.
271 	 * @return {@code this}.
272 	 * @since 3.4
273 	 */
274 	public ObjectChecker setAllowLeadingZeroFileMode(boolean allow) {
275 		return setIgnore(ZERO_PADDED_FILEMODE, allow);
276 	}
277 
278 	/**
279 	 * Enable accepting invalid author, committer and tagger identities.
280 	 * <p>
281 	 * Some broken Git versions/libraries allowed users to create commits and
282 	 * tags with invalid formatting between the name, email and timestamp.
283 	 *
284 	 * @param allow
285 	 *            if true accept invalid person identity strings.
286 	 * @return {@code this}.
287 	 * @since 4.0
288 	 */
289 	public ObjectChecker setAllowInvalidPersonIdent(boolean allow) {
290 		allowInvalidPersonIdent = allow;
291 		return this;
292 	}
293 
294 	/**
295 	 * Restrict trees to only names legal on Windows platforms.
296 	 * <p>
297 	 * Also rejects any mixed case forms of reserved names ({@code .git}).
298 	 *
299 	 * @param win true if Windows name checking should be performed.
300 	 * @return {@code this}.
301 	 * @since 3.4
302 	 */
303 	public ObjectChecker setSafeForWindows(boolean win) {
304 		windows = win;
305 		return this;
306 	}
307 
308 	/**
309 	 * Restrict trees to only names legal on Mac OS X platforms.
310 	 * <p>
311 	 * Rejects any mixed case forms of reserved names ({@code .git})
312 	 * for users working on HFS+ in case-insensitive (default) mode.
313 	 *
314 	 * @param mac true if Mac OS X name checking should be performed.
315 	 * @return {@code this}.
316 	 * @since 3.4
317 	 */
318 	public ObjectChecker setSafeForMacOS(boolean mac) {
319 		macosx = mac;
320 		return this;
321 	}
322 
323 	/**
324 	 * Check an object for parsing errors.
325 	 *
326 	 * @param objType
327 	 *            type of the object. Must be a valid object type code in
328 	 *            {@link org.eclipse.jgit.lib.Constants}.
329 	 * @param raw
330 	 *            the raw data which comprises the object. This should be in the
331 	 *            canonical format (that is the format used to generate the
332 	 *            ObjectId of the object). The array is never modified.
333 	 * @throws org.eclipse.jgit.errors.CorruptObjectException
334 	 *             if an error is identified.
335 	 */
336 	public void check(int objType, byte[] raw)
337 			throws CorruptObjectException {
338 		check(idFor(objType, raw), objType, raw);
339 	}
340 
341 	/**
342 	 * Check an object for parsing errors.
343 	 *
344 	 * @param id
345 	 *            identify of the object being checked.
346 	 * @param objType
347 	 *            type of the object. Must be a valid object type code in
348 	 *            {@link org.eclipse.jgit.lib.Constants}.
349 	 * @param raw
350 	 *            the raw data which comprises the object. This should be in the
351 	 *            canonical format (that is the format used to generate the
352 	 *            ObjectId of the object). The array is never modified.
353 	 * @throws org.eclipse.jgit.errors.CorruptObjectException
354 	 *             if an error is identified.
355 	 * @since 4.2
356 	 */
357 	public void check(@Nullable AnyObjectId id, int objType, byte[] raw)
358 			throws CorruptObjectException {
359 		switch (objType) {
360 		case OBJ_COMMIT:
361 			checkCommit(id, raw);
362 			break;
363 		case OBJ_TAG:
364 			checkTag(id, raw);
365 			break;
366 		case OBJ_TREE:
367 			checkTree(id, raw);
368 			break;
369 		case OBJ_BLOB:
370 			BlobObjectChecker checker = newBlobObjectChecker();
371 			if (checker == null) {
372 				checkBlob(raw);
373 			} else {
374 				checker.update(raw, 0, raw.length);
375 				checker.endBlob(id);
376 			}
377 			break;
378 		default:
379 			report(UNKNOWN_TYPE, id, MessageFormat.format(
380 					JGitText.get().corruptObjectInvalidType2,
381 					Integer.valueOf(objType)));
382 		}
383 	}
384 
385 	private boolean checkId(byte[] raw) {
386 		int p = bufPtr.value;
387 		try {
388 			tempId.fromString(raw, p);
389 		} catch (IllegalArgumentException e) {
390 			bufPtr.value = nextLF(raw, p);
391 			return false;
392 		}
393 
394 		p += OBJECT_ID_STRING_LENGTH;
395 		if (raw[p] == '\n') {
396 			bufPtr.value = p + 1;
397 			return true;
398 		}
399 		bufPtr.value = nextLF(raw, p);
400 		return false;
401 	}
402 
403 	private void checkPersonIdent(byte[] raw, @Nullable AnyObjectId id)
404 			throws CorruptObjectException {
405 		if (allowInvalidPersonIdent) {
406 			bufPtr.value = nextLF(raw, bufPtr.value);
407 			return;
408 		}
409 
410 		final int emailB = nextLF(raw, bufPtr.value, '<');
411 		if (emailB == bufPtr.value || raw[emailB - 1] != '<') {
412 			report(MISSING_EMAIL, id, JGitText.get().corruptObjectMissingEmail);
413 			bufPtr.value = nextLF(raw, bufPtr.value);
414 			return;
415 		}
416 
417 		final int emailE = nextLF(raw, emailB, '>');
418 		if (emailE == emailB || raw[emailE - 1] != '>') {
419 			report(BAD_EMAIL, id, JGitText.get().corruptObjectBadEmail);
420 			bufPtr.value = nextLF(raw, bufPtr.value);
421 			return;
422 		}
423 		if (emailE == raw.length || raw[emailE] != ' ') {
424 			report(MISSING_SPACE_BEFORE_DATE, id,
425 					JGitText.get().corruptObjectBadDate);
426 			bufPtr.value = nextLF(raw, bufPtr.value);
427 			return;
428 		}
429 
430 		parseBase10(raw, emailE + 1, bufPtr); // when
431 		if (emailE + 1 == bufPtr.value || bufPtr.value == raw.length
432 				|| raw[bufPtr.value] != ' ') {
433 			report(BAD_DATE, id, JGitText.get().corruptObjectBadDate);
434 			bufPtr.value = nextLF(raw, bufPtr.value);
435 			return;
436 		}
437 
438 		int p = bufPtr.value + 1;
439 		parseBase10(raw, p, bufPtr); // tz offset
440 		if (p == bufPtr.value) {
441 			report(BAD_TIMEZONE, id, JGitText.get().corruptObjectBadTimezone);
442 			bufPtr.value = nextLF(raw, bufPtr.value);
443 			return;
444 		}
445 
446 		p = bufPtr.value;
447 		if (raw[p] == '\n') {
448 			bufPtr.value = p + 1;
449 		} else {
450 			report(BAD_TIMEZONE, id, JGitText.get().corruptObjectBadTimezone);
451 			bufPtr.value = nextLF(raw, p);
452 		}
453 	}
454 
455 	/**
456 	 * Check a commit for errors.
457 	 *
458 	 * @param raw
459 	 *            the commit data. The array is never modified.
460 	 * @throws org.eclipse.jgit.errors.CorruptObjectException
461 	 *             if any error was detected.
462 	 */
463 	public void checkCommit(byte[] raw) throws CorruptObjectException {
464 		checkCommit(idFor(OBJ_COMMIT, raw), raw);
465 	}
466 
467 	/**
468 	 * Check a commit for errors.
469 	 *
470 	 * @param id
471 	 *            identity of the object being checked.
472 	 * @param raw
473 	 *            the commit data. The array is never modified.
474 	 * @throws org.eclipse.jgit.errors.CorruptObjectException
475 	 *             if any error was detected.
476 	 * @since 4.2
477 	 */
478 	public void checkCommit(@Nullable AnyObjectId id, byte[] raw)
479 			throws CorruptObjectException {
480 		bufPtr.value = 0;
481 
482 		if (!match(raw, tree)) {
483 			report(MISSING_TREE, id, JGitText.get().corruptObjectNotreeHeader);
484 		} else if (!checkId(raw)) {
485 			report(BAD_TREE_SHA1, id, JGitText.get().corruptObjectInvalidTree);
486 		}
487 
488 		while (match(raw, parent)) {
489 			if (!checkId(raw)) {
490 				report(BAD_PARENT_SHA1, id,
491 						JGitText.get().corruptObjectInvalidParent);
492 			}
493 		}
494 
495 		if (match(raw, author)) {
496 			checkPersonIdent(raw, id);
497 		} else {
498 			report(MISSING_AUTHOR, id, JGitText.get().corruptObjectNoAuthor);
499 		}
500 
501 		if (match(raw, committer)) {
502 			checkPersonIdent(raw, id);
503 		} else {
504 			report(MISSING_COMMITTER, id,
505 					JGitText.get().corruptObjectNoCommitter);
506 		}
507 	}
508 
509 	/**
510 	 * Check an annotated tag for errors.
511 	 *
512 	 * @param raw
513 	 *            the tag data. The array is never modified.
514 	 * @throws org.eclipse.jgit.errors.CorruptObjectException
515 	 *             if any error was detected.
516 	 */
517 	public void checkTag(byte[] raw) throws CorruptObjectException {
518 		checkTag(idFor(OBJ_TAG, raw), raw);
519 	}
520 
521 	/**
522 	 * Check an annotated tag for errors.
523 	 *
524 	 * @param id
525 	 *            identity of the object being checked.
526 	 * @param raw
527 	 *            the tag data. The array is never modified.
528 	 * @throws org.eclipse.jgit.errors.CorruptObjectException
529 	 *             if any error was detected.
530 	 * @since 4.2
531 	 */
532 	public void checkTag(@Nullable AnyObjectId id, byte[] raw)
533 			throws CorruptObjectException {
534 		bufPtr.value = 0;
535 		if (!match(raw, object)) {
536 			report(MISSING_OBJECT, id,
537 					JGitText.get().corruptObjectNoObjectHeader);
538 		} else if (!checkId(raw)) {
539 			report(BAD_OBJECT_SHA1, id,
540 					JGitText.get().corruptObjectInvalidObject);
541 		}
542 
543 		if (!match(raw, type)) {
544 			report(MISSING_TYPE_ENTRY, id,
545 					JGitText.get().corruptObjectNoTypeHeader);
546 		}
547 		bufPtr.value = nextLF(raw, bufPtr.value);
548 
549 		if (!match(raw, tag)) {
550 			report(MISSING_TAG_ENTRY, id,
551 					JGitText.get().corruptObjectNoTagHeader);
552 		}
553 		bufPtr.value = nextLF(raw, bufPtr.value);
554 
555 		if (match(raw, tagger)) {
556 			checkPersonIdent(raw, id);
557 		}
558 	}
559 
560 	private static boolean duplicateName(final byte[] raw,
561 			final int thisNamePos, final int thisNameEnd) {
562 		final int sz = raw.length;
563 		int nextPtr = thisNameEnd + 1 + Constants.OBJECT_ID_LENGTH;
564 		for (;;) {
565 			int nextMode = 0;
566 			for (;;) {
567 				if (nextPtr >= sz)
568 					return false;
569 				final byte c = raw[nextPtr++];
570 				if (' ' == c)
571 					break;
572 				nextMode <<= 3;
573 				nextMode += c - '0';
574 			}
575 
576 			final int nextNamePos = nextPtr;
577 			for (;;) {
578 				if (nextPtr == sz)
579 					return false;
580 				final byte c = raw[nextPtr++];
581 				if (c == 0)
582 					break;
583 			}
584 			if (nextNamePos + 1 == nextPtr)
585 				return false;
586 
587 			int cmp = compareSameName(
588 					raw, thisNamePos, thisNameEnd,
589 					raw, nextNamePos, nextPtr - 1, nextMode);
590 			if (cmp < 0)
591 				return false;
592 			else if (cmp == 0)
593 				return true;
594 
595 			nextPtr += Constants.OBJECT_ID_LENGTH;
596 		}
597 	}
598 
599 	/**
600 	 * Check a canonical formatted tree for errors.
601 	 *
602 	 * @param raw
603 	 *            the raw tree data. The array is never modified.
604 	 * @throws org.eclipse.jgit.errors.CorruptObjectException
605 	 *             if any error was detected.
606 	 */
607 	public void checkTree(byte[] raw) throws CorruptObjectException {
608 		checkTree(idFor(OBJ_TREE, raw), raw);
609 	}
610 
611 	/**
612 	 * Check a canonical formatted tree for errors.
613 	 *
614 	 * @param id
615 	 *            identity of the object being checked.
616 	 * @param raw
617 	 *            the raw tree data. The array is never modified.
618 	 * @throws org.eclipse.jgit.errors.CorruptObjectException
619 	 *             if any error was detected.
620 	 * @since 4.2
621 	 */
622 	public void checkTree(@Nullable AnyObjectId id, byte[] raw)
623 			throws CorruptObjectException {
624 		final int sz = raw.length;
625 		int ptr = 0;
626 		int lastNameB = 0, lastNameE = 0, lastMode = 0;
627 		Set<String> normalized = windows || macosx
628 				? new HashSet<>()
629 				: null;
630 
631 		while (ptr < sz) {
632 			int thisMode = 0;
633 			for (;;) {
634 				if (ptr == sz) {
635 					throw new CorruptObjectException(
636 							JGitText.get().corruptObjectTruncatedInMode);
637 				}
638 				final byte c = raw[ptr++];
639 				if (' ' == c)
640 					break;
641 				if (c < '0' || c > '7') {
642 					throw new CorruptObjectException(
643 							JGitText.get().corruptObjectInvalidModeChar);
644 				}
645 				if (thisMode == 0 && c == '0') {
646 					report(ZERO_PADDED_FILEMODE, id,
647 							JGitText.get().corruptObjectInvalidModeStartsZero);
648 				}
649 				thisMode <<= 3;
650 				thisMode += c - '0';
651 			}
652 
653 			if (FileMode.fromBits(thisMode).getObjectType() == OBJ_BAD) {
654 				throw new CorruptObjectException(MessageFormat.format(
655 						JGitText.get().corruptObjectInvalidMode2,
656 						Integer.valueOf(thisMode)));
657 			}
658 
659 			final int thisNameB = ptr;
660 			ptr = scanPathSegment(raw, ptr, sz, id);
661 			if (ptr == sz || raw[ptr] != 0) {
662 				throw new CorruptObjectException(
663 						JGitText.get().corruptObjectTruncatedInName);
664 			}
665 			checkPathSegment2(raw, thisNameB, ptr, id);
666 			if (normalized != null) {
667 				if (!normalized.add(normalize(raw, thisNameB, ptr))) {
668 					report(DUPLICATE_ENTRIES, id,
669 							JGitText.get().corruptObjectDuplicateEntryNames);
670 				}
671 			} else if (duplicateName(raw, thisNameB, ptr)) {
672 				report(DUPLICATE_ENTRIES, id,
673 						JGitText.get().corruptObjectDuplicateEntryNames);
674 			}
675 
676 			if (lastNameB != 0) {
677 				int cmp = compare(
678 						raw, lastNameB, lastNameE, lastMode,
679 						raw, thisNameB, ptr, thisMode);
680 				if (cmp > 0) {
681 					report(TREE_NOT_SORTED, id,
682 							JGitText.get().corruptObjectIncorrectSorting);
683 				}
684 			}
685 
686 			lastNameB = thisNameB;
687 			lastNameE = ptr;
688 			lastMode = thisMode;
689 
690 			ptr += 1 + OBJECT_ID_LENGTH;
691 			if (ptr > sz) {
692 				throw new CorruptObjectException(
693 						JGitText.get().corruptObjectTruncatedInObjectId);
694 			}
695 
696 			if (ObjectId.zeroId().compareTo(raw, ptr - OBJECT_ID_LENGTH) == 0) {
697 				report(NULL_SHA1, id, JGitText.get().corruptObjectZeroId);
698 			}
699 
700 			if (id != null && isGitmodules(raw, lastNameB, lastNameE, id)) {
701 				ObjectId blob = ObjectId.fromRaw(raw, ptr - OBJECT_ID_LENGTH);
702 				gitsubmodules.add(new GitmoduleEntry(id, blob));
703 			}
704 		}
705 	}
706 
707 	private int scanPathSegment(byte[] raw, int ptr, int end,
708 			@Nullable AnyObjectId id) throws CorruptObjectException {
709 		for (; ptr < end; ptr++) {
710 			byte c = raw[ptr];
711 			if (c == 0) {
712 				return ptr;
713 			}
714 			if (c == '/') {
715 				report(FULL_PATHNAME, id,
716 						JGitText.get().corruptObjectNameContainsSlash);
717 			}
718 			if (windows && isInvalidOnWindows(c)) {
719 				if (c > 31) {
720 					throw new CorruptObjectException(String.format(
721 							JGitText.get().corruptObjectNameContainsChar,
722 							Byte.valueOf(c)));
723 				}
724 				throw new CorruptObjectException(String.format(
725 						JGitText.get().corruptObjectNameContainsByte,
726 						Integer.valueOf(c & 0xff)));
727 			}
728 		}
729 		return ptr;
730 	}
731 
732 	@Nullable
733 	private ObjectId idFor(int objType, byte[] raw) {
734 		if (skipList != null) {
735 			try (ObjectInserter.Formatter fmt = new ObjectInserter.Formatter()) {
736 				return fmt.idFor(objType, raw);
737 			}
738 		}
739 		return null;
740 	}
741 
742 	private void report(@NonNull ErrorType err, @Nullable AnyObjectId id,
743 			String why) throws CorruptObjectException {
744 		if (errors.contains(err)
745 				&& (id == null || skipList == null || !skipList.contains(id))) {
746 			if (id != null) {
747 				throw new CorruptObjectException(err, id, why);
748 			}
749 			throw new CorruptObjectException(why);
750 		}
751 	}
752 
753 	/**
754 	 * Check tree path entry for validity.
755 	 * <p>
756 	 * Unlike {@link #checkPathSegment(byte[], int, int)}, this version scans a
757 	 * multi-directory path string such as {@code "src/main.c"}.
758 	 *
759 	 * @param path
760 	 *            path string to scan.
761 	 * @throws org.eclipse.jgit.errors.CorruptObjectException
762 	 *             path is invalid.
763 	 * @since 3.6
764 	 */
765 	public void checkPath(String path) throws CorruptObjectException {
766 		byte[] buf = Constants.encode(path);
767 		checkPath(buf, 0, buf.length);
768 	}
769 
770 	/**
771 	 * Check tree path entry for validity.
772 	 * <p>
773 	 * Unlike {@link #checkPathSegment(byte[], int, int)}, this version scans a
774 	 * multi-directory path string such as {@code "src/main.c"}.
775 	 *
776 	 * @param raw
777 	 *            buffer to scan.
778 	 * @param ptr
779 	 *            offset to first byte of the name.
780 	 * @param end
781 	 *            offset to one past last byte of name.
782 	 * @throws org.eclipse.jgit.errors.CorruptObjectException
783 	 *             path is invalid.
784 	 * @since 3.6
785 	 */
786 	public void checkPath(byte[] raw, int ptr, int end)
787 			throws CorruptObjectException {
788 		int start = ptr;
789 		for (; ptr < end; ptr++) {
790 			if (raw[ptr] == '/') {
791 				checkPathSegment(raw, start, ptr);
792 				start = ptr + 1;
793 			}
794 		}
795 		checkPathSegment(raw, start, end);
796 	}
797 
798 	/**
799 	 * Check tree path entry for validity.
800 	 *
801 	 * @param raw
802 	 *            buffer to scan.
803 	 * @param ptr
804 	 *            offset to first byte of the name.
805 	 * @param end
806 	 *            offset to one past last byte of name.
807 	 * @throws org.eclipse.jgit.errors.CorruptObjectException
808 	 *             name is invalid.
809 	 * @since 3.4
810 	 */
811 	public void checkPathSegment(byte[] raw, int ptr, int end)
812 			throws CorruptObjectException {
813 		int e = scanPathSegment(raw, ptr, end, null);
814 		if (e < end && raw[e] == 0)
815 			throw new CorruptObjectException(
816 					JGitText.get().corruptObjectNameContainsNullByte);
817 		checkPathSegment2(raw, ptr, end, null);
818 	}
819 
820 	private void checkPathSegment2(byte[] raw, int ptr, int end,
821 			@Nullable AnyObjectId id) throws CorruptObjectException {
822 		if (ptr == end) {
823 			report(EMPTY_NAME, id, JGitText.get().corruptObjectNameZeroLength);
824 			return;
825 		}
826 
827 		if (raw[ptr] == '.') {
828 			switch (end - ptr) {
829 			case 1:
830 				report(HAS_DOT, id, JGitText.get().corruptObjectNameDot);
831 				break;
832 			case 2:
833 				if (raw[ptr + 1] == '.') {
834 					report(HAS_DOTDOT, id,
835 							JGitText.get().corruptObjectNameDotDot);
836 				}
837 				break;
838 			case 4:
839 				if (isGit(raw, ptr + 1)) {
840 					report(HAS_DOTGIT, id, String.format(
841 							JGitText.get().corruptObjectInvalidName,
842 							RawParseUtils.decode(raw, ptr, end)));
843 				}
844 				break;
845 			default:
846 				if (end - ptr > 4 && isNormalizedGit(raw, ptr + 1, end)) {
847 					report(HAS_DOTGIT, id, String.format(
848 							JGitText.get().corruptObjectInvalidName,
849 							RawParseUtils.decode(raw, ptr, end)));
850 				}
851 			}
852 		} else if (isGitTilde1(raw, ptr, end)) {
853 			report(HAS_DOTGIT, id, String.format(
854 					JGitText.get().corruptObjectInvalidName,
855 					RawParseUtils.decode(raw, ptr, end)));
856 		}
857 		if (macosx && isMacHFSGit(raw, ptr, end, id)) {
858 			report(HAS_DOTGIT, id, String.format(
859 					JGitText.get().corruptObjectInvalidNameIgnorableUnicode,
860 					RawParseUtils.decode(raw, ptr, end)));
861 		}
862 
863 		if (windows) {
864 			// Windows ignores space and dot at end of file name.
865 			if (raw[end - 1] == ' ' || raw[end - 1] == '.') {
866 				report(WIN32_BAD_NAME, id, String.format(
867 						JGitText.get().corruptObjectInvalidNameEnd,
868 						Character.valueOf(((char) raw[end - 1]))));
869 			}
870 			if (end - ptr >= 3) {
871 				checkNotWindowsDevice(raw, ptr, end, id);
872 			}
873 		}
874 	}
875 
876 	// Mac's HFS+ folds permutations of ".git" and Unicode ignorable characters
877 	// to ".git" therefore we should prevent such names
878 	private boolean isMacHFSPath(byte[] raw, int ptr, int end, byte[] path,
879 			@Nullable AnyObjectId id) throws CorruptObjectException {
880 		boolean ignorable = false;
881 		int g = 0;
882 		while (ptr < end) {
883 			switch (raw[ptr]) {
884 			case (byte) 0xe2: // http://www.utf8-chartable.de/unicode-utf8-table.pl?start=8192
885 				if (!checkTruncatedIgnorableUTF8(raw, ptr, end, id)) {
886 					return false;
887 				}
888 				switch (raw[ptr + 1]) {
889 				case (byte) 0x80:
890 					switch (raw[ptr + 2]) {
891 					case (byte) 0x8c:	// U+200C 0xe2808c ZERO WIDTH NON-JOINER
892 					case (byte) 0x8d:	// U+200D 0xe2808d ZERO WIDTH JOINER
893 					case (byte) 0x8e:	// U+200E 0xe2808e LEFT-TO-RIGHT MARK
894 					case (byte) 0x8f:	// U+200F 0xe2808f RIGHT-TO-LEFT MARK
895 					case (byte) 0xaa:	// U+202A 0xe280aa LEFT-TO-RIGHT EMBEDDING
896 					case (byte) 0xab:	// U+202B 0xe280ab RIGHT-TO-LEFT EMBEDDING
897 					case (byte) 0xac:	// U+202C 0xe280ac POP DIRECTIONAL FORMATTING
898 					case (byte) 0xad:	// U+202D 0xe280ad LEFT-TO-RIGHT OVERRIDE
899 					case (byte) 0xae:	// U+202E 0xe280ae RIGHT-TO-LEFT OVERRIDE
900 						ignorable = true;
901 						ptr += 3;
902 						continue;
903 					default:
904 						return false;
905 					}
906 				case (byte) 0x81:
907 					switch (raw[ptr + 2]) {
908 					case (byte) 0xaa:	// U+206A 0xe281aa INHIBIT SYMMETRIC SWAPPING
909 					case (byte) 0xab:	// U+206B 0xe281ab ACTIVATE SYMMETRIC SWAPPING
910 					case (byte) 0xac:	// U+206C 0xe281ac INHIBIT ARABIC FORM SHAPING
911 					case (byte) 0xad:	// U+206D 0xe281ad ACTIVATE ARABIC FORM SHAPING
912 					case (byte) 0xae:	// U+206E 0xe281ae NATIONAL DIGIT SHAPES
913 					case (byte) 0xaf:	// U+206F 0xe281af NOMINAL DIGIT SHAPES
914 						ignorable = true;
915 						ptr += 3;
916 						continue;
917 					default:
918 						return false;
919 					}
920 				default:
921 					return false;
922 				}
923 			case (byte) 0xef: // http://www.utf8-chartable.de/unicode-utf8-table.pl?start=65024
924 				if (!checkTruncatedIgnorableUTF8(raw, ptr, end, id)) {
925 					return false;
926 				}
927 				// U+FEFF 0xefbbbf ZERO WIDTH NO-BREAK SPACE
928 				if ((raw[ptr + 1] == (byte) 0xbb)
929 						&& (raw[ptr + 2] == (byte) 0xbf)) {
930 					ignorable = true;
931 					ptr += 3;
932 					continue;
933 				}
934 				return false;
935 			default:
936 				if (g == path.length) {
937 					return false;
938 				}
939 				if (toLower(raw[ptr++]) != path[g++]) {
940 					return false;
941 				}
942 			}
943 		}
944 		if (g == path.length && ignorable) {
945 			return true;
946 		}
947 		return false;
948 	}
949 
950 	private boolean isMacHFSGit(byte[] raw, int ptr, int end,
951 			@Nullable AnyObjectId id) throws CorruptObjectException {
952 		byte[] git = new byte[] { '.', 'g', 'i', 't' };
953 		return isMacHFSPath(raw, ptr, end, git, id);
954 	}
955 
956 	private boolean isMacHFSGitmodules(byte[] raw, int ptr, int end,
957 			@Nullable AnyObjectId id) throws CorruptObjectException {
958 		return isMacHFSPath(raw, ptr, end, dotGitmodules, id);
959 	}
960 
961 	private boolean checkTruncatedIgnorableUTF8(byte[] raw, int ptr, int end,
962 			@Nullable AnyObjectId id) throws CorruptObjectException {
963 		if ((ptr + 2) >= end) {
964 			report(BAD_UTF8, id, MessageFormat.format(
965 					JGitText.get().corruptObjectInvalidNameInvalidUtf8,
966 					toHexString(raw, ptr, end)));
967 			return false;
968 		}
969 		return true;
970 	}
971 
972 	private static String toHexString(byte[] raw, int ptr, int end) {
973 		StringBuilder b = new StringBuilder("0x"); //$NON-NLS-1$
974 		for (int i = ptr; i < end; i++)
975 			b.append(String.format("%02x", Byte.valueOf(raw[i]))); //$NON-NLS-1$
976 		return b.toString();
977 	}
978 
979 	private void checkNotWindowsDevice(byte[] raw, int ptr, int end,
980 			@Nullable AnyObjectId id) throws CorruptObjectException {
981 		switch (toLower(raw[ptr])) {
982 		case 'a': // AUX
983 			if (end - ptr >= 3
984 					&& toLower(raw[ptr + 1]) == 'u'
985 					&& toLower(raw[ptr + 2]) == 'x'
986 					&& (end - ptr == 3 || raw[ptr + 3] == '.')) {
987 				report(WIN32_BAD_NAME, id,
988 						JGitText.get().corruptObjectInvalidNameAux);
989 			}
990 			break;
991 
992 		case 'c': // CON, COM[1-9]
993 			if (end - ptr >= 3
994 					&& toLower(raw[ptr + 2]) == 'n'
995 					&& toLower(raw[ptr + 1]) == 'o'
996 					&& (end - ptr == 3 || raw[ptr + 3] == '.')) {
997 				report(WIN32_BAD_NAME, id,
998 						JGitText.get().corruptObjectInvalidNameCon);
999 			}
1000 			if (end - ptr >= 4
1001 					&& toLower(raw[ptr + 2]) == 'm'
1002 					&& toLower(raw[ptr + 1]) == 'o'
1003 					&& isPositiveDigit(raw[ptr + 3])
1004 					&& (end - ptr == 4 || raw[ptr + 4] == '.')) {
1005 				report(WIN32_BAD_NAME, id, String.format(
1006 						JGitText.get().corruptObjectInvalidNameCom,
1007 						Character.valueOf(((char) raw[ptr + 3]))));
1008 			}
1009 			break;
1010 
1011 		case 'l': // LPT[1-9]
1012 			if (end - ptr >= 4
1013 					&& toLower(raw[ptr + 1]) == 'p'
1014 					&& toLower(raw[ptr + 2]) == 't'
1015 					&& isPositiveDigit(raw[ptr + 3])
1016 					&& (end - ptr == 4 || raw[ptr + 4] == '.')) {
1017 				report(WIN32_BAD_NAME, id, String.format(
1018 						JGitText.get().corruptObjectInvalidNameLpt,
1019 						Character.valueOf(((char) raw[ptr + 3]))));
1020 			}
1021 			break;
1022 
1023 		case 'n': // NUL
1024 			if (end - ptr >= 3
1025 					&& toLower(raw[ptr + 1]) == 'u'
1026 					&& toLower(raw[ptr + 2]) == 'l'
1027 					&& (end - ptr == 3 || raw[ptr + 3] == '.')) {
1028 				report(WIN32_BAD_NAME, id,
1029 						JGitText.get().corruptObjectInvalidNameNul);
1030 			}
1031 			break;
1032 
1033 		case 'p': // PRN
1034 			if (end - ptr >= 3
1035 					&& toLower(raw[ptr + 1]) == 'r'
1036 					&& toLower(raw[ptr + 2]) == 'n'
1037 					&& (end - ptr == 3 || raw[ptr + 3] == '.')) {
1038 				report(WIN32_BAD_NAME, id,
1039 						JGitText.get().corruptObjectInvalidNamePrn);
1040 			}
1041 			break;
1042 		}
1043 	}
1044 
1045 	private static boolean isInvalidOnWindows(byte c) {
1046 		// Windows disallows "special" characters in a path component.
1047 		switch (c) {
1048 		case '"':
1049 		case '*':
1050 		case ':':
1051 		case '<':
1052 		case '>':
1053 		case '?':
1054 		case '\\':
1055 		case '|':
1056 			return true;
1057 		}
1058 		return 1 <= c && c <= 31;
1059 	}
1060 
1061 	private static boolean isGit(byte[] buf, int p) {
1062 		return toLower(buf[p]) == 'g'
1063 				&& toLower(buf[p + 1]) == 'i'
1064 				&& toLower(buf[p + 2]) == 't';
1065 	}
1066 
1067 	/**
1068 	 * Check if the filename contained in buf[start:end] could be read as a
1069 	 * .gitmodules file when checked out to the working directory.
1070 	 *
1071 	 * This ought to be a simple comparison, but some filesystems have peculiar
1072 	 * rules for normalizing filenames:
1073 	 *
1074 	 * NTFS has backward-compatibility support for 8.3 synonyms of long file
1075 	 * names (see
1076 	 * https://web.archive.org/web/20160318181041/https://usn.pw/blog/gen/2015/06/09/filenames/
1077 	 * for details). NTFS is also case-insensitive.
1078 	 *
1079 	 * MacOS's HFS+ folds away ignorable Unicode characters in addition to case
1080 	 * folding.
1081 	 *
1082 	 * @param buf
1083 	 *            byte array to decode
1084 	 * @param start
1085 	 *            position where a supposed filename is starting
1086 	 * @param end
1087 	 *            position where a supposed filename is ending
1088 	 * @param id
1089 	 *            object id for error reporting
1090 	 *
1091 	 * @return true if the filename in buf could be a ".gitmodules" file
1092 	 * @throws CorruptObjectException
1093 	 */
1094 	private boolean isGitmodules(byte[] buf, int start, int end, @Nullable AnyObjectId id)
1095 			throws CorruptObjectException {
1096 		// Simple cases first.
1097 		if (end - start < 8) {
1098 			return false;
1099 		}
1100 		return (end - start == dotGitmodules.length
1101 				&& RawParseUtils.match(buf, start, dotGitmodules) != -1)
1102 			|| (macosx && isMacHFSGitmodules(buf, start, end, id))
1103 			|| (windows && isNTFSGitmodules(buf, start, end));
1104 	}
1105 
1106 	private boolean matchLowerCase(byte[] b, int ptr, byte[] src) {
1107 		if (ptr + src.length > b.length) {
1108 			return false;
1109 		}
1110 		for (int i = 0; i < src.length; i++, ptr++) {
1111 			if (toLower(b[ptr]) != src[i]) {
1112 				return false;
1113 			}
1114 		}
1115 		return true;
1116 	}
1117 
1118 	// .gitmodules, case-insensitive, or an 8.3 abbreviation of the same.
1119 	private boolean isNTFSGitmodules(byte[] buf, int start, int end) {
1120 		if (end - start == 11) {
1121 			return matchLowerCase(buf, start, dotGitmodules);
1122 		}
1123 
1124 		if (end - start != 8) {
1125 			return false;
1126 		}
1127 
1128 		// "gitmod" or a prefix of "gi7eba", followed by...
1129 		byte[] gitmod = new byte[]{'g', 'i', 't', 'm', 'o', 'd', '~'};
1130 		if (matchLowerCase(buf, start, gitmod)) {
1131 			start += 6;
1132 		} else {
1133 			byte[] gi7eba = new byte[]{'g', 'i', '7', 'e', 'b', 'a'};
1134 			for (int i = 0; i < gi7eba.length; i++, start++) {
1135 				byte c = (byte) toLower(buf[start]);
1136 				if (c == '~') {
1137 					break;
1138 				}
1139 				if (c != gi7eba[i]) {
1140 					return false;
1141 				}
1142 			}
1143 		}
1144 
1145 		// ... ~ and a number
1146 		if (end - start < 2) {
1147 			return false;
1148 		}
1149 		if (buf[start] != '~') {
1150 			return false;
1151 		}
1152 		start++;
1153 		if (buf[start] < '1' || buf[start] > '9') {
1154 			return false;
1155 		}
1156 		start++;
1157 		for (; start != end; start++) {
1158 			if (buf[start] < '0' || buf[start] > '9') {
1159 				return false;
1160 			}
1161 		}
1162 		return true;
1163 	}
1164 
1165 	private static boolean isGitTilde1(byte[] buf, int p, int end) {
1166 		if (end - p != 5)
1167 			return false;
1168 		return toLower(buf[p]) == 'g' && toLower(buf[p + 1]) == 'i'
1169 				&& toLower(buf[p + 2]) == 't' && buf[p + 3] == '~'
1170 				&& buf[p + 4] == '1';
1171 	}
1172 
1173 	private static boolean isNormalizedGit(byte[] raw, int ptr, int end) {
1174 		if (isGit(raw, ptr)) {
1175 			int dots = 0;
1176 			boolean space = false;
1177 			int p = end - 1;
1178 			for (; (ptr + 2) < p; p--) {
1179 				if (raw[p] == '.')
1180 					dots++;
1181 				else if (raw[p] == ' ')
1182 					space = true;
1183 				else
1184 					break;
1185 			}
1186 			return p == ptr + 2 && (dots == 1 || space);
1187 		}
1188 		return false;
1189 	}
1190 
1191 	private boolean match(byte[] b, byte[] src) {
1192 		int r = RawParseUtils.match(b, bufPtr.value, src);
1193 		if (r < 0) {
1194 			return false;
1195 		}
1196 		bufPtr.value = r;
1197 		return true;
1198 	}
1199 
1200 	private static char toLower(byte b) {
1201 		if ('A' <= b && b <= 'Z')
1202 			return (char) (b + ('a' - 'A'));
1203 		return (char) b;
1204 	}
1205 
1206 	private static boolean isPositiveDigit(byte b) {
1207 		return '1' <= b && b <= '9';
1208 	}
1209 
1210 	/**
1211 	 * Create a new {@link org.eclipse.jgit.lib.BlobObjectChecker}.
1212 	 *
1213 	 * @return new BlobObjectChecker or null if it's not provided.
1214 	 * @since 4.9
1215 	 */
1216 	@Nullable
1217 	public BlobObjectChecker newBlobObjectChecker() {
1218 		return null;
1219 	}
1220 
1221 	/**
1222 	 * Check a blob for errors.
1223 	 *
1224 	 * <p>
1225 	 * This may not be called from PackParser in some cases. Use
1226 	 * {@link #newBlobObjectChecker} instead.
1227 	 *
1228 	 * @param raw
1229 	 *            the blob data. The array is never modified.
1230 	 * @throws org.eclipse.jgit.errors.CorruptObjectException
1231 	 *             if any error was detected.
1232 	 */
1233 	public void checkBlob(byte[] raw) throws CorruptObjectException {
1234 		// We can always assume the blob is valid.
1235 	}
1236 
1237 	private String normalize(byte[] raw, int ptr, int end) {
1238 		String n = RawParseUtils.decode(raw, ptr, end).toLowerCase(Locale.US);
1239 		return macosx ? Normalizer.normalize(n, Normalizer.Form.NFC) : n;
1240 	}
1241 
1242 	/**
1243 	 * Get the list of ".gitmodules" files found in the pack. For each, report
1244 	 * its blob id (e.g. to validate its contents) and the tree where it was
1245 	 * found (e.g. to check if it is in the root)
1246 	 *
1247 	 * @return List of pairs of ids {@literal <tree, blob>}.
1248 	 *
1249 	 * @since 4.7.5
1250 	 */
1251 	public List<GitmoduleEntry> getGitsubmodules() {
1252 		return gitsubmodules;
1253 	}
1254 }