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