ObjectChecker.java

  1. /*
  2.  * Copyright (C) 2008-2010, Google Inc.
  3.  * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others
  4.  *
  5.  * This program and the accompanying materials are made available under the
  6.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  7.  * https://www.eclipse.org/org/documents/edl-v10.php.
  8.  *
  9.  * SPDX-License-Identifier: BSD-3-Clause
  10.  */

  11. package org.eclipse.jgit.lib;

  12. import static org.eclipse.jgit.lib.Constants.DOT_GIT_MODULES;
  13. import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
  14. import static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH;
  15. import static org.eclipse.jgit.lib.Constants.OBJ_BAD;
  16. import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
  17. import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT;
  18. import static org.eclipse.jgit.lib.Constants.OBJ_TAG;
  19. import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
  20. import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_DATE;
  21. import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_EMAIL;
  22. import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_OBJECT_SHA1;
  23. import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_PARENT_SHA1;
  24. import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_TIMEZONE;
  25. import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_TREE_SHA1;
  26. import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_UTF8;
  27. import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.DUPLICATE_ENTRIES;
  28. import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.EMPTY_NAME;
  29. import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.FULL_PATHNAME;
  30. import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.HAS_DOT;
  31. import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.HAS_DOTDOT;
  32. import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.HAS_DOTGIT;
  33. import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_AUTHOR;
  34. import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_COMMITTER;
  35. import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_EMAIL;
  36. import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_OBJECT;
  37. import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_SPACE_BEFORE_DATE;
  38. import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_TAG_ENTRY;
  39. import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_TREE;
  40. import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_TYPE_ENTRY;
  41. import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.NULL_SHA1;
  42. import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.TREE_NOT_SORTED;
  43. import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.UNKNOWN_TYPE;
  44. import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.WIN32_BAD_NAME;
  45. import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.ZERO_PADDED_FILEMODE;
  46. import static org.eclipse.jgit.util.Paths.compare;
  47. import static org.eclipse.jgit.util.Paths.compareSameName;
  48. import static org.eclipse.jgit.util.RawParseUtils.nextLF;
  49. import static org.eclipse.jgit.util.RawParseUtils.parseBase10;

  50. import java.text.MessageFormat;
  51. import java.text.Normalizer;
  52. import java.util.ArrayList;
  53. import java.util.EnumSet;
  54. import java.util.HashSet;
  55. import java.util.List;
  56. import java.util.Locale;
  57. import java.util.Set;

  58. import org.eclipse.jgit.annotations.NonNull;
  59. import org.eclipse.jgit.annotations.Nullable;
  60. import org.eclipse.jgit.errors.CorruptObjectException;
  61. import org.eclipse.jgit.internal.JGitText;
  62. import org.eclipse.jgit.util.MutableInteger;
  63. import org.eclipse.jgit.util.RawParseUtils;
  64. import org.eclipse.jgit.util.StringUtils;

  65. /**
  66.  * Verifies that an object is formatted correctly.
  67.  * <p>
  68.  * Verifications made by this class only check that the fields of an object are
  69.  * formatted correctly. The ObjectId checksum of the object is not verified, and
  70.  * connectivity links between objects are also not verified. Its assumed that
  71.  * the caller can provide both of these validations on its own.
  72.  * <p>
  73.  * Instances of this class are not thread safe, but they may be reused to
  74.  * perform multiple object validations, calling {@link #reset()} between them to
  75.  * clear the internal state (e.g. {@link #getGitsubmodules()})
  76.  */
  77. public class ObjectChecker {
  78.     /** Header "tree " */
  79.     public static final byte[] tree = Constants.encodeASCII("tree "); //$NON-NLS-1$

  80.     /** Header "parent " */
  81.     public static final byte[] parent = Constants.encodeASCII("parent "); //$NON-NLS-1$

  82.     /** Header "author " */
  83.     public static final byte[] author = Constants.encodeASCII("author "); //$NON-NLS-1$

  84.     /** Header "committer " */
  85.     public static final byte[] committer = Constants.encodeASCII("committer "); //$NON-NLS-1$

  86.     /** Header "encoding " */
  87.     public static final byte[] encoding = Constants.encodeASCII("encoding "); //$NON-NLS-1$

  88.     /** Header "object " */
  89.     public static final byte[] object = Constants.encodeASCII("object "); //$NON-NLS-1$

  90.     /** Header "type " */
  91.     public static final byte[] type = Constants.encodeASCII("type "); //$NON-NLS-1$

  92.     /** Header "tag " */
  93.     public static final byte[] tag = Constants.encodeASCII("tag "); //$NON-NLS-1$

  94.     /** Header "tagger " */
  95.     public static final byte[] tagger = Constants.encodeASCII("tagger "); //$NON-NLS-1$

  96.     /** Path ".gitmodules" */
  97.     private static final byte[] dotGitmodules = Constants.encodeASCII(DOT_GIT_MODULES);

  98.     /**
  99.      * Potential issues identified by the checker.
  100.      *
  101.      * @since 4.2
  102.      */
  103.     public enum ErrorType {
  104.         // @formatter:off
  105.         // These names match git-core so that fsck section keys also match.
  106.         /***/ NULL_SHA1,
  107.         /***/ DUPLICATE_ENTRIES,
  108.         /***/ TREE_NOT_SORTED,
  109.         /***/ ZERO_PADDED_FILEMODE,
  110.         /***/ EMPTY_NAME,
  111.         /***/ FULL_PATHNAME,
  112.         /***/ HAS_DOT,
  113.         /***/ HAS_DOTDOT,
  114.         /***/ HAS_DOTGIT,
  115.         /***/ BAD_OBJECT_SHA1,
  116.         /***/ BAD_PARENT_SHA1,
  117.         /***/ BAD_TREE_SHA1,
  118.         /***/ MISSING_AUTHOR,
  119.         /***/ MISSING_COMMITTER,
  120.         /***/ MISSING_OBJECT,
  121.         /***/ MISSING_TREE,
  122.         /***/ MISSING_TYPE_ENTRY,
  123.         /***/ MISSING_TAG_ENTRY,
  124.         /***/ BAD_DATE,
  125.         /***/ BAD_EMAIL,
  126.         /***/ BAD_TIMEZONE,
  127.         /***/ MISSING_EMAIL,
  128.         /***/ MISSING_SPACE_BEFORE_DATE,
  129.         /** @since 5.2 */ GITMODULES_BLOB,
  130.         /** @since 5.2 */ GITMODULES_LARGE,
  131.         /** @since 5.2 */ GITMODULES_NAME,
  132.         /** @since 5.2 */ GITMODULES_PARSE,
  133.         /** @since 5.2 */ GITMODULES_PATH,
  134.         /** @since 5.2 */ GITMODULES_SYMLINK,
  135.         /** @since 5.2 */ GITMODULES_URL,
  136.         /***/ UNKNOWN_TYPE,

  137.         // These are unique to JGit.
  138.         /***/ WIN32_BAD_NAME,
  139.         /***/ BAD_UTF8;
  140.         // @formatter:on

  141.         /** @return camelCaseVersion of the name. */
  142.         public String getMessageId() {
  143.             String n = name();
  144.             StringBuilder r = new StringBuilder(n.length());
  145.             for (int i = 0; i < n.length(); i++) {
  146.                 char c = n.charAt(i);
  147.                 if (c != '_') {
  148.                     r.append(StringUtils.toLowerCase(c));
  149.                 } else {
  150.                     r.append(n.charAt(++i));
  151.                 }
  152.             }
  153.             return r.toString();
  154.         }
  155.     }

  156.     private final MutableObjectId tempId = new MutableObjectId();
  157.     private final MutableInteger bufPtr = new MutableInteger();

  158.     private EnumSet<ErrorType> errors = EnumSet.allOf(ErrorType.class);
  159.     private ObjectIdSet skipList;
  160.     private boolean allowInvalidPersonIdent;
  161.     private boolean windows;
  162.     private boolean macosx;

  163.     private final List<GitmoduleEntry> gitsubmodules = new ArrayList<>();

  164.     /**
  165.      * Enable accepting specific malformed (but not horribly broken) objects.
  166.      *
  167.      * @param objects
  168.      *            collection of object names known to be broken in a non-fatal
  169.      *            way that should be ignored by the checker.
  170.      * @return {@code this}
  171.      * @since 4.2
  172.      */
  173.     public ObjectChecker setSkipList(@Nullable ObjectIdSet objects) {
  174.         skipList = objects;
  175.         return this;
  176.     }

  177.     /**
  178.      * Configure error types to be ignored across all objects.
  179.      *
  180.      * @param ids
  181.      *            error types to ignore. The caller's set is copied.
  182.      * @return {@code this}
  183.      * @since 4.2
  184.      */
  185.     public ObjectChecker setIgnore(@Nullable Set<ErrorType> ids) {
  186.         errors = EnumSet.allOf(ErrorType.class);
  187.         if (ids != null) {
  188.             errors.removeAll(ids);
  189.         }
  190.         return this;
  191.     }

  192.     /**
  193.      * Add message type to be ignored across all objects.
  194.      *
  195.      * @param id
  196.      *            error type to ignore.
  197.      * @param ignore
  198.      *            true to ignore this error; false to treat the error as an
  199.      *            error and throw.
  200.      * @return {@code this}
  201.      * @since 4.2
  202.      */
  203.     public ObjectChecker setIgnore(ErrorType id, boolean ignore) {
  204.         if (ignore) {
  205.             errors.remove(id);
  206.         } else {
  207.             errors.add(id);
  208.         }
  209.         return this;
  210.     }

  211.     /**
  212.      * Enable accepting leading zero mode in tree entries.
  213.      * <p>
  214.      * Some broken Git libraries generated leading zeros in the mode part of
  215.      * tree entries. This is technically incorrect but gracefully allowed by
  216.      * git-core. JGit rejects such trees by default, but may need to accept
  217.      * them on broken histories.
  218.      * <p>
  219.      * Same as {@code setIgnore(ZERO_PADDED_FILEMODE, allow)}.
  220.      *
  221.      * @param allow allow leading zero mode.
  222.      * @return {@code this}.
  223.      * @since 3.4
  224.      */
  225.     public ObjectChecker setAllowLeadingZeroFileMode(boolean allow) {
  226.         return setIgnore(ZERO_PADDED_FILEMODE, allow);
  227.     }

  228.     /**
  229.      * Enable accepting invalid author, committer and tagger identities.
  230.      * <p>
  231.      * Some broken Git versions/libraries allowed users to create commits and
  232.      * tags with invalid formatting between the name, email and timestamp.
  233.      *
  234.      * @param allow
  235.      *            if true accept invalid person identity strings.
  236.      * @return {@code this}.
  237.      * @since 4.0
  238.      */
  239.     public ObjectChecker setAllowInvalidPersonIdent(boolean allow) {
  240.         allowInvalidPersonIdent = allow;
  241.         return this;
  242.     }

  243.     /**
  244.      * Restrict trees to only names legal on Windows platforms.
  245.      * <p>
  246.      * Also rejects any mixed case forms of reserved names ({@code .git}).
  247.      *
  248.      * @param win true if Windows name checking should be performed.
  249.      * @return {@code this}.
  250.      * @since 3.4
  251.      */
  252.     public ObjectChecker setSafeForWindows(boolean win) {
  253.         windows = win;
  254.         return this;
  255.     }

  256.     /**
  257.      * Restrict trees to only names legal on Mac OS X platforms.
  258.      * <p>
  259.      * Rejects any mixed case forms of reserved names ({@code .git})
  260.      * for users working on HFS+ in case-insensitive (default) mode.
  261.      *
  262.      * @param mac true if Mac OS X name checking should be performed.
  263.      * @return {@code this}.
  264.      * @since 3.4
  265.      */
  266.     public ObjectChecker setSafeForMacOS(boolean mac) {
  267.         macosx = mac;
  268.         return this;
  269.     }

  270.     /**
  271.      * Check an object for parsing errors.
  272.      *
  273.      * @param objType
  274.      *            type of the object. Must be a valid object type code in
  275.      *            {@link org.eclipse.jgit.lib.Constants}.
  276.      * @param raw
  277.      *            the raw data which comprises the object. This should be in the
  278.      *            canonical format (that is the format used to generate the
  279.      *            ObjectId of the object). The array is never modified.
  280.      * @throws org.eclipse.jgit.errors.CorruptObjectException
  281.      *             if an error is identified.
  282.      */
  283.     public void check(int objType, byte[] raw)
  284.             throws CorruptObjectException {
  285.         check(idFor(objType, raw), objType, raw);
  286.     }

  287.     /**
  288.      * Check an object for parsing errors.
  289.      *
  290.      * @param id
  291.      *            identify of the object being checked.
  292.      * @param objType
  293.      *            type of the object. Must be a valid object type code in
  294.      *            {@link org.eclipse.jgit.lib.Constants}.
  295.      * @param raw
  296.      *            the raw data which comprises the object. This should be in the
  297.      *            canonical format (that is the format used to generate the
  298.      *            ObjectId of the object). The array is never modified.
  299.      * @throws org.eclipse.jgit.errors.CorruptObjectException
  300.      *             if an error is identified.
  301.      * @since 4.2
  302.      */
  303.     public void check(@Nullable AnyObjectId id, int objType, byte[] raw)
  304.             throws CorruptObjectException {
  305.         switch (objType) {
  306.         case OBJ_COMMIT:
  307.             checkCommit(id, raw);
  308.             break;
  309.         case OBJ_TAG:
  310.             checkTag(id, raw);
  311.             break;
  312.         case OBJ_TREE:
  313.             checkTree(id, raw);
  314.             break;
  315.         case OBJ_BLOB:
  316.             BlobObjectChecker checker = newBlobObjectChecker();
  317.             if (checker == null) {
  318.                 checkBlob(raw);
  319.             } else {
  320.                 checker.update(raw, 0, raw.length);
  321.                 checker.endBlob(id);
  322.             }
  323.             break;
  324.         default:
  325.             report(UNKNOWN_TYPE, id, MessageFormat.format(
  326.                     JGitText.get().corruptObjectInvalidType2,
  327.                     Integer.valueOf(objType)));
  328.         }
  329.     }

  330.     private boolean checkId(byte[] raw) {
  331.         int p = bufPtr.value;
  332.         try {
  333.             tempId.fromString(raw, p);
  334.         } catch (IllegalArgumentException e) {
  335.             bufPtr.value = nextLF(raw, p);
  336.             return false;
  337.         }

  338.         p += OBJECT_ID_STRING_LENGTH;
  339.         if (raw[p] == '\n') {
  340.             bufPtr.value = p + 1;
  341.             return true;
  342.         }
  343.         bufPtr.value = nextLF(raw, p);
  344.         return false;
  345.     }

  346.     private void checkPersonIdent(byte[] raw, @Nullable AnyObjectId id)
  347.             throws CorruptObjectException {
  348.         if (allowInvalidPersonIdent) {
  349.             bufPtr.value = nextLF(raw, bufPtr.value);
  350.             return;
  351.         }

  352.         final int emailB = nextLF(raw, bufPtr.value, '<');
  353.         if (emailB == bufPtr.value || raw[emailB - 1] != '<') {
  354.             report(MISSING_EMAIL, id, JGitText.get().corruptObjectMissingEmail);
  355.             bufPtr.value = nextLF(raw, bufPtr.value);
  356.             return;
  357.         }

  358.         final int emailE = nextLF(raw, emailB, '>');
  359.         if (emailE == emailB || raw[emailE - 1] != '>') {
  360.             report(BAD_EMAIL, id, JGitText.get().corruptObjectBadEmail);
  361.             bufPtr.value = nextLF(raw, bufPtr.value);
  362.             return;
  363.         }
  364.         if (emailE == raw.length || raw[emailE] != ' ') {
  365.             report(MISSING_SPACE_BEFORE_DATE, id,
  366.                     JGitText.get().corruptObjectBadDate);
  367.             bufPtr.value = nextLF(raw, bufPtr.value);
  368.             return;
  369.         }

  370.         parseBase10(raw, emailE + 1, bufPtr); // when
  371.         if (emailE + 1 == bufPtr.value || bufPtr.value == raw.length
  372.                 || raw[bufPtr.value] != ' ') {
  373.             report(BAD_DATE, id, JGitText.get().corruptObjectBadDate);
  374.             bufPtr.value = nextLF(raw, bufPtr.value);
  375.             return;
  376.         }

  377.         int p = bufPtr.value + 1;
  378.         parseBase10(raw, p, bufPtr); // tz offset
  379.         if (p == bufPtr.value) {
  380.             report(BAD_TIMEZONE, id, JGitText.get().corruptObjectBadTimezone);
  381.             bufPtr.value = nextLF(raw, bufPtr.value);
  382.             return;
  383.         }

  384.         p = bufPtr.value;
  385.         if (raw[p] == '\n') {
  386.             bufPtr.value = p + 1;
  387.         } else {
  388.             report(BAD_TIMEZONE, id, JGitText.get().corruptObjectBadTimezone);
  389.             bufPtr.value = nextLF(raw, p);
  390.         }
  391.     }

  392.     /**
  393.      * Check a commit for errors.
  394.      *
  395.      * @param raw
  396.      *            the commit data. The array is never modified.
  397.      * @throws org.eclipse.jgit.errors.CorruptObjectException
  398.      *             if any error was detected.
  399.      */
  400.     public void checkCommit(byte[] raw) throws CorruptObjectException {
  401.         checkCommit(idFor(OBJ_COMMIT, raw), raw);
  402.     }

  403.     /**
  404.      * Check a commit for errors.
  405.      *
  406.      * @param id
  407.      *            identity of the object being checked.
  408.      * @param raw
  409.      *            the commit data. The array is never modified.
  410.      * @throws org.eclipse.jgit.errors.CorruptObjectException
  411.      *             if any error was detected.
  412.      * @since 4.2
  413.      */
  414.     public void checkCommit(@Nullable AnyObjectId id, byte[] raw)
  415.             throws CorruptObjectException {
  416.         bufPtr.value = 0;

  417.         if (!match(raw, tree)) {
  418.             report(MISSING_TREE, id, JGitText.get().corruptObjectNotreeHeader);
  419.         } else if (!checkId(raw)) {
  420.             report(BAD_TREE_SHA1, id, JGitText.get().corruptObjectInvalidTree);
  421.         }

  422.         while (match(raw, parent)) {
  423.             if (!checkId(raw)) {
  424.                 report(BAD_PARENT_SHA1, id,
  425.                         JGitText.get().corruptObjectInvalidParent);
  426.             }
  427.         }

  428.         if (match(raw, author)) {
  429.             checkPersonIdent(raw, id);
  430.         } else {
  431.             report(MISSING_AUTHOR, id, JGitText.get().corruptObjectNoAuthor);
  432.         }

  433.         if (match(raw, committer)) {
  434.             checkPersonIdent(raw, id);
  435.         } else {
  436.             report(MISSING_COMMITTER, id,
  437.                     JGitText.get().corruptObjectNoCommitter);
  438.         }
  439.     }

  440.     /**
  441.      * Check an annotated tag for errors.
  442.      *
  443.      * @param raw
  444.      *            the tag data. The array is never modified.
  445.      * @throws org.eclipse.jgit.errors.CorruptObjectException
  446.      *             if any error was detected.
  447.      */
  448.     public void checkTag(byte[] raw) throws CorruptObjectException {
  449.         checkTag(idFor(OBJ_TAG, raw), raw);
  450.     }

  451.     /**
  452.      * Check an annotated tag for errors.
  453.      *
  454.      * @param id
  455.      *            identity of the object being checked.
  456.      * @param raw
  457.      *            the tag data. The array is never modified.
  458.      * @throws org.eclipse.jgit.errors.CorruptObjectException
  459.      *             if any error was detected.
  460.      * @since 4.2
  461.      */
  462.     public void checkTag(@Nullable AnyObjectId id, byte[] raw)
  463.             throws CorruptObjectException {
  464.         bufPtr.value = 0;
  465.         if (!match(raw, object)) {
  466.             report(MISSING_OBJECT, id,
  467.                     JGitText.get().corruptObjectNoObjectHeader);
  468.         } else if (!checkId(raw)) {
  469.             report(BAD_OBJECT_SHA1, id,
  470.                     JGitText.get().corruptObjectInvalidObject);
  471.         }

  472.         if (!match(raw, type)) {
  473.             report(MISSING_TYPE_ENTRY, id,
  474.                     JGitText.get().corruptObjectNoTypeHeader);
  475.         }
  476.         bufPtr.value = nextLF(raw, bufPtr.value);

  477.         if (!match(raw, tag)) {
  478.             report(MISSING_TAG_ENTRY, id,
  479.                     JGitText.get().corruptObjectNoTagHeader);
  480.         }
  481.         bufPtr.value = nextLF(raw, bufPtr.value);

  482.         if (match(raw, tagger)) {
  483.             checkPersonIdent(raw, id);
  484.         }
  485.     }

  486.     private static boolean duplicateName(final byte[] raw,
  487.             final int thisNamePos, final int thisNameEnd) {
  488.         final int sz = raw.length;
  489.         int nextPtr = thisNameEnd + 1 + Constants.OBJECT_ID_LENGTH;
  490.         for (;;) {
  491.             int nextMode = 0;
  492.             for (;;) {
  493.                 if (nextPtr >= sz)
  494.                     return false;
  495.                 final byte c = raw[nextPtr++];
  496.                 if (' ' == c)
  497.                     break;
  498.                 nextMode <<= 3;
  499.                 nextMode += c - '0';
  500.             }

  501.             final int nextNamePos = nextPtr;
  502.             for (;;) {
  503.                 if (nextPtr == sz)
  504.                     return false;
  505.                 final byte c = raw[nextPtr++];
  506.                 if (c == 0)
  507.                     break;
  508.             }
  509.             if (nextNamePos + 1 == nextPtr)
  510.                 return false;

  511.             int cmp = compareSameName(
  512.                     raw, thisNamePos, thisNameEnd,
  513.                     raw, nextNamePos, nextPtr - 1, nextMode);
  514.             if (cmp < 0)
  515.                 return false;
  516.             else if (cmp == 0)
  517.                 return true;

  518.             nextPtr += Constants.OBJECT_ID_LENGTH;
  519.         }
  520.     }

  521.     /**
  522.      * Check a canonical formatted tree for errors.
  523.      *
  524.      * @param raw
  525.      *            the raw tree data. The array is never modified.
  526.      * @throws org.eclipse.jgit.errors.CorruptObjectException
  527.      *             if any error was detected.
  528.      */
  529.     public void checkTree(byte[] raw) throws CorruptObjectException {
  530.         checkTree(idFor(OBJ_TREE, raw), raw);
  531.     }

  532.     /**
  533.      * Check a canonical formatted tree for errors.
  534.      *
  535.      * @param id
  536.      *            identity of the object being checked.
  537.      * @param raw
  538.      *            the raw tree data. The array is never modified.
  539.      * @throws org.eclipse.jgit.errors.CorruptObjectException
  540.      *             if any error was detected.
  541.      * @since 4.2
  542.      */
  543.     public void checkTree(@Nullable AnyObjectId id, byte[] raw)
  544.             throws CorruptObjectException {
  545.         final int sz = raw.length;
  546.         int ptr = 0;
  547.         int lastNameB = 0, lastNameE = 0, lastMode = 0;
  548.         Set<String> normalized = windows || macosx
  549.                 ? new HashSet<>()
  550.                 : null;

  551.         while (ptr < sz) {
  552.             int thisMode = 0;
  553.             for (;;) {
  554.                 if (ptr == sz) {
  555.                     throw new CorruptObjectException(
  556.                             JGitText.get().corruptObjectTruncatedInMode);
  557.                 }
  558.                 final byte c = raw[ptr++];
  559.                 if (' ' == c)
  560.                     break;
  561.                 if (c < '0' || c > '7') {
  562.                     throw new CorruptObjectException(
  563.                             JGitText.get().corruptObjectInvalidModeChar);
  564.                 }
  565.                 if (thisMode == 0 && c == '0') {
  566.                     report(ZERO_PADDED_FILEMODE, id,
  567.                             JGitText.get().corruptObjectInvalidModeStartsZero);
  568.                 }
  569.                 thisMode <<= 3;
  570.                 thisMode += c - '0';
  571.             }

  572.             if (FileMode.fromBits(thisMode).getObjectType() == OBJ_BAD) {
  573.                 throw new CorruptObjectException(MessageFormat.format(
  574.                         JGitText.get().corruptObjectInvalidMode2,
  575.                         Integer.valueOf(thisMode)));
  576.             }

  577.             final int thisNameB = ptr;
  578.             ptr = scanPathSegment(raw, ptr, sz, id);
  579.             if (ptr == sz || raw[ptr] != 0) {
  580.                 throw new CorruptObjectException(
  581.                         JGitText.get().corruptObjectTruncatedInName);
  582.             }
  583.             checkPathSegment2(raw, thisNameB, ptr, id);
  584.             if (normalized != null) {
  585.                 if (!normalized.add(normalize(raw, thisNameB, ptr))) {
  586.                     report(DUPLICATE_ENTRIES, id,
  587.                             JGitText.get().corruptObjectDuplicateEntryNames);
  588.                 }
  589.             } else if (duplicateName(raw, thisNameB, ptr)) {
  590.                 report(DUPLICATE_ENTRIES, id,
  591.                         JGitText.get().corruptObjectDuplicateEntryNames);
  592.             }

  593.             if (lastNameB != 0) {
  594.                 int cmp = compare(
  595.                         raw, lastNameB, lastNameE, lastMode,
  596.                         raw, thisNameB, ptr, thisMode);
  597.                 if (cmp > 0) {
  598.                     report(TREE_NOT_SORTED, id,
  599.                             JGitText.get().corruptObjectIncorrectSorting);
  600.                 }
  601.             }

  602.             lastNameB = thisNameB;
  603.             lastNameE = ptr;
  604.             lastMode = thisMode;

  605.             ptr += 1 + OBJECT_ID_LENGTH;
  606.             if (ptr > sz) {
  607.                 throw new CorruptObjectException(
  608.                         JGitText.get().corruptObjectTruncatedInObjectId);
  609.             }

  610.             if (ObjectId.zeroId().compareTo(raw, ptr - OBJECT_ID_LENGTH) == 0) {
  611.                 report(NULL_SHA1, id, JGitText.get().corruptObjectZeroId);
  612.             }

  613.             if (id != null && isGitmodules(raw, lastNameB, lastNameE, id)) {
  614.                 ObjectId blob = ObjectId.fromRaw(raw, ptr - OBJECT_ID_LENGTH);
  615.                 gitsubmodules.add(new GitmoduleEntry(id, blob));
  616.             }
  617.         }
  618.     }

  619.     private int scanPathSegment(byte[] raw, int ptr, int end,
  620.             @Nullable AnyObjectId id) throws CorruptObjectException {
  621.         for (; ptr < end; ptr++) {
  622.             byte c = raw[ptr];
  623.             if (c == 0) {
  624.                 return ptr;
  625.             }
  626.             if (c == '/') {
  627.                 report(FULL_PATHNAME, id,
  628.                         JGitText.get().corruptObjectNameContainsSlash);
  629.             }
  630.             if (windows && isInvalidOnWindows(c)) {
  631.                 if (c > 31) {
  632.                     throw new CorruptObjectException(String.format(
  633.                             JGitText.get().corruptObjectNameContainsChar,
  634.                             Byte.valueOf(c)));
  635.                 }
  636.                 throw new CorruptObjectException(String.format(
  637.                         JGitText.get().corruptObjectNameContainsByte,
  638.                         Integer.valueOf(c & 0xff)));
  639.             }
  640.         }
  641.         return ptr;
  642.     }

  643.     @Nullable
  644.     private ObjectId idFor(int objType, byte[] raw) {
  645.         if (skipList != null) {
  646.             try (ObjectInserter.Formatter fmt = new ObjectInserter.Formatter()) {
  647.                 return fmt.idFor(objType, raw);
  648.             }
  649.         }
  650.         return null;
  651.     }

  652.     private void report(@NonNull ErrorType err, @Nullable AnyObjectId id,
  653.             String why) throws CorruptObjectException {
  654.         if (errors.contains(err)
  655.                 && (id == null || skipList == null || !skipList.contains(id))) {
  656.             if (id != null) {
  657.                 throw new CorruptObjectException(err, id, why);
  658.             }
  659.             throw new CorruptObjectException(why);
  660.         }
  661.     }

  662.     /**
  663.      * Check tree path entry for validity.
  664.      * <p>
  665.      * Unlike {@link #checkPathSegment(byte[], int, int)}, this version scans a
  666.      * multi-directory path string such as {@code "src/main.c"}.
  667.      *
  668.      * @param path
  669.      *            path string to scan.
  670.      * @throws org.eclipse.jgit.errors.CorruptObjectException
  671.      *             path is invalid.
  672.      * @since 3.6
  673.      */
  674.     public void checkPath(String path) throws CorruptObjectException {
  675.         byte[] buf = Constants.encode(path);
  676.         checkPath(buf, 0, buf.length);
  677.     }

  678.     /**
  679.      * Check tree path entry for validity.
  680.      * <p>
  681.      * Unlike {@link #checkPathSegment(byte[], int, int)}, this version scans a
  682.      * multi-directory path string such as {@code "src/main.c"}.
  683.      *
  684.      * @param raw
  685.      *            buffer to scan.
  686.      * @param ptr
  687.      *            offset to first byte of the name.
  688.      * @param end
  689.      *            offset to one past last byte of name.
  690.      * @throws org.eclipse.jgit.errors.CorruptObjectException
  691.      *             path is invalid.
  692.      * @since 3.6
  693.      */
  694.     public void checkPath(byte[] raw, int ptr, int end)
  695.             throws CorruptObjectException {
  696.         int start = ptr;
  697.         for (; ptr < end; ptr++) {
  698.             if (raw[ptr] == '/') {
  699.                 checkPathSegment(raw, start, ptr);
  700.                 start = ptr + 1;
  701.             }
  702.         }
  703.         checkPathSegment(raw, start, end);
  704.     }

  705.     /**
  706.      * Check tree path entry for validity.
  707.      *
  708.      * @param raw
  709.      *            buffer to scan.
  710.      * @param ptr
  711.      *            offset to first byte of the name.
  712.      * @param end
  713.      *            offset to one past last byte of name.
  714.      * @throws org.eclipse.jgit.errors.CorruptObjectException
  715.      *             name is invalid.
  716.      * @since 3.4
  717.      */
  718.     public void checkPathSegment(byte[] raw, int ptr, int end)
  719.             throws CorruptObjectException {
  720.         int e = scanPathSegment(raw, ptr, end, null);
  721.         if (e < end && raw[e] == 0)
  722.             throw new CorruptObjectException(
  723.                     JGitText.get().corruptObjectNameContainsNullByte);
  724.         checkPathSegment2(raw, ptr, end, null);
  725.     }

  726.     private void checkPathSegment2(byte[] raw, int ptr, int end,
  727.             @Nullable AnyObjectId id) throws CorruptObjectException {
  728.         if (ptr == end) {
  729.             report(EMPTY_NAME, id, JGitText.get().corruptObjectNameZeroLength);
  730.             return;
  731.         }

  732.         if (raw[ptr] == '.') {
  733.             switch (end - ptr) {
  734.             case 1:
  735.                 report(HAS_DOT, id, JGitText.get().corruptObjectNameDot);
  736.                 break;
  737.             case 2:
  738.                 if (raw[ptr + 1] == '.') {
  739.                     report(HAS_DOTDOT, id,
  740.                             JGitText.get().corruptObjectNameDotDot);
  741.                 }
  742.                 break;
  743.             case 4:
  744.                 if (isGit(raw, ptr + 1)) {
  745.                     report(HAS_DOTGIT, id, String.format(
  746.                             JGitText.get().corruptObjectInvalidName,
  747.                             RawParseUtils.decode(raw, ptr, end)));
  748.                 }
  749.                 break;
  750.             default:
  751.                 if (end - ptr > 4 && isNormalizedGit(raw, ptr + 1, end)) {
  752.                     report(HAS_DOTGIT, id, String.format(
  753.                             JGitText.get().corruptObjectInvalidName,
  754.                             RawParseUtils.decode(raw, ptr, end)));
  755.                 }
  756.             }
  757.         } else if (isGitTilde1(raw, ptr, end)) {
  758.             report(HAS_DOTGIT, id, String.format(
  759.                     JGitText.get().corruptObjectInvalidName,
  760.                     RawParseUtils.decode(raw, ptr, end)));
  761.         }
  762.         if (macosx && isMacHFSGit(raw, ptr, end, id)) {
  763.             report(HAS_DOTGIT, id, String.format(
  764.                     JGitText.get().corruptObjectInvalidNameIgnorableUnicode,
  765.                     RawParseUtils.decode(raw, ptr, end)));
  766.         }

  767.         if (windows) {
  768.             // Windows ignores space and dot at end of file name.
  769.             if (raw[end - 1] == ' ' || raw[end - 1] == '.') {
  770.                 report(WIN32_BAD_NAME, id, String.format(
  771.                         JGitText.get().corruptObjectInvalidNameEnd,
  772.                         Character.valueOf(((char) raw[end - 1]))));
  773.             }
  774.             if (end - ptr >= 3) {
  775.                 checkNotWindowsDevice(raw, ptr, end, id);
  776.             }
  777.         }
  778.     }

  779.     // Mac's HFS+ folds permutations of ".git" and Unicode ignorable characters
  780.     // to ".git" therefore we should prevent such names
  781.     private boolean isMacHFSPath(byte[] raw, int ptr, int end, byte[] path,
  782.             @Nullable AnyObjectId id) throws CorruptObjectException {
  783.         boolean ignorable = false;
  784.         int g = 0;
  785.         while (ptr < end) {
  786.             switch (raw[ptr]) {
  787.             case (byte) 0xe2: // http://www.utf8-chartable.de/unicode-utf8-table.pl?start=8192
  788.                 if (!checkTruncatedIgnorableUTF8(raw, ptr, end, id)) {
  789.                     return false;
  790.                 }
  791.                 switch (raw[ptr + 1]) {
  792.                 case (byte) 0x80:
  793.                     switch (raw[ptr + 2]) {
  794.                     case (byte) 0x8c:   // U+200C 0xe2808c ZERO WIDTH NON-JOINER
  795.                     case (byte) 0x8d:   // U+200D 0xe2808d ZERO WIDTH JOINER
  796.                     case (byte) 0x8e:   // U+200E 0xe2808e LEFT-TO-RIGHT MARK
  797.                     case (byte) 0x8f:   // U+200F 0xe2808f RIGHT-TO-LEFT MARK
  798.                     case (byte) 0xaa:   // U+202A 0xe280aa LEFT-TO-RIGHT EMBEDDING
  799.                     case (byte) 0xab:   // U+202B 0xe280ab RIGHT-TO-LEFT EMBEDDING
  800.                     case (byte) 0xac:   // U+202C 0xe280ac POP DIRECTIONAL FORMATTING
  801.                     case (byte) 0xad:   // U+202D 0xe280ad LEFT-TO-RIGHT OVERRIDE
  802.                     case (byte) 0xae:   // U+202E 0xe280ae RIGHT-TO-LEFT OVERRIDE
  803.                         ignorable = true;
  804.                         ptr += 3;
  805.                         continue;
  806.                     default:
  807.                         return false;
  808.                     }
  809.                 case (byte) 0x81:
  810.                     switch (raw[ptr + 2]) {
  811.                     case (byte) 0xaa:   // U+206A 0xe281aa INHIBIT SYMMETRIC SWAPPING
  812.                     case (byte) 0xab:   // U+206B 0xe281ab ACTIVATE SYMMETRIC SWAPPING
  813.                     case (byte) 0xac:   // U+206C 0xe281ac INHIBIT ARABIC FORM SHAPING
  814.                     case (byte) 0xad:   // U+206D 0xe281ad ACTIVATE ARABIC FORM SHAPING
  815.                     case (byte) 0xae:   // U+206E 0xe281ae NATIONAL DIGIT SHAPES
  816.                     case (byte) 0xaf:   // U+206F 0xe281af NOMINAL DIGIT SHAPES
  817.                         ignorable = true;
  818.                         ptr += 3;
  819.                         continue;
  820.                     default:
  821.                         return false;
  822.                     }
  823.                 default:
  824.                     return false;
  825.                 }
  826.             case (byte) 0xef: // http://www.utf8-chartable.de/unicode-utf8-table.pl?start=65024
  827.                 if (!checkTruncatedIgnorableUTF8(raw, ptr, end, id)) {
  828.                     return false;
  829.                 }
  830.                 // U+FEFF 0xefbbbf ZERO WIDTH NO-BREAK SPACE
  831.                 if ((raw[ptr + 1] == (byte) 0xbb)
  832.                         && (raw[ptr + 2] == (byte) 0xbf)) {
  833.                     ignorable = true;
  834.                     ptr += 3;
  835.                     continue;
  836.                 }
  837.                 return false;
  838.             default:
  839.                 if (g == path.length) {
  840.                     return false;
  841.                 }
  842.                 if (toLower(raw[ptr++]) != path[g++]) {
  843.                     return false;
  844.                 }
  845.             }
  846.         }
  847.         if (g == path.length && ignorable) {
  848.             return true;
  849.         }
  850.         return false;
  851.     }

  852.     private boolean isMacHFSGit(byte[] raw, int ptr, int end,
  853.             @Nullable AnyObjectId id) throws CorruptObjectException {
  854.         byte[] git = new byte[] { '.', 'g', 'i', 't' };
  855.         return isMacHFSPath(raw, ptr, end, git, id);
  856.     }

  857.     private boolean isMacHFSGitmodules(byte[] raw, int ptr, int end,
  858.             @Nullable AnyObjectId id) throws CorruptObjectException {
  859.         return isMacHFSPath(raw, ptr, end, dotGitmodules, id);
  860.     }

  861.     private boolean checkTruncatedIgnorableUTF8(byte[] raw, int ptr, int end,
  862.             @Nullable AnyObjectId id) throws CorruptObjectException {
  863.         if ((ptr + 2) >= end) {
  864.             report(BAD_UTF8, id, MessageFormat.format(
  865.                     JGitText.get().corruptObjectInvalidNameInvalidUtf8,
  866.                     toHexString(raw, ptr, end)));
  867.             return false;
  868.         }
  869.         return true;
  870.     }

  871.     private static String toHexString(byte[] raw, int ptr, int end) {
  872.         StringBuilder b = new StringBuilder("0x"); //$NON-NLS-1$
  873.         for (int i = ptr; i < end; i++)
  874.             b.append(String.format("%02x", Byte.valueOf(raw[i]))); //$NON-NLS-1$
  875.         return b.toString();
  876.     }

  877.     private void checkNotWindowsDevice(byte[] raw, int ptr, int end,
  878.             @Nullable AnyObjectId id) throws CorruptObjectException {
  879.         switch (toLower(raw[ptr])) {
  880.         case 'a': // AUX
  881.             if (end - ptr >= 3
  882.                     && toLower(raw[ptr + 1]) == 'u'
  883.                     && toLower(raw[ptr + 2]) == 'x'
  884.                     && (end - ptr == 3 || raw[ptr + 3] == '.')) {
  885.                 report(WIN32_BAD_NAME, id,
  886.                         JGitText.get().corruptObjectInvalidNameAux);
  887.             }
  888.             break;

  889.         case 'c': // CON, COM[1-9]
  890.             if (end - ptr >= 3
  891.                     && toLower(raw[ptr + 2]) == 'n'
  892.                     && toLower(raw[ptr + 1]) == 'o'
  893.                     && (end - ptr == 3 || raw[ptr + 3] == '.')) {
  894.                 report(WIN32_BAD_NAME, id,
  895.                         JGitText.get().corruptObjectInvalidNameCon);
  896.             }
  897.             if (end - ptr >= 4
  898.                     && toLower(raw[ptr + 2]) == 'm'
  899.                     && toLower(raw[ptr + 1]) == 'o'
  900.                     && isPositiveDigit(raw[ptr + 3])
  901.                     && (end - ptr == 4 || raw[ptr + 4] == '.')) {
  902.                 report(WIN32_BAD_NAME, id, String.format(
  903.                         JGitText.get().corruptObjectInvalidNameCom,
  904.                         Character.valueOf(((char) raw[ptr + 3]))));
  905.             }
  906.             break;

  907.         case 'l': // LPT[1-9]
  908.             if (end - ptr >= 4
  909.                     && toLower(raw[ptr + 1]) == 'p'
  910.                     && toLower(raw[ptr + 2]) == 't'
  911.                     && isPositiveDigit(raw[ptr + 3])
  912.                     && (end - ptr == 4 || raw[ptr + 4] == '.')) {
  913.                 report(WIN32_BAD_NAME, id, String.format(
  914.                         JGitText.get().corruptObjectInvalidNameLpt,
  915.                         Character.valueOf(((char) raw[ptr + 3]))));
  916.             }
  917.             break;

  918.         case 'n': // NUL
  919.             if (end - ptr >= 3
  920.                     && toLower(raw[ptr + 1]) == 'u'
  921.                     && toLower(raw[ptr + 2]) == 'l'
  922.                     && (end - ptr == 3 || raw[ptr + 3] == '.')) {
  923.                 report(WIN32_BAD_NAME, id,
  924.                         JGitText.get().corruptObjectInvalidNameNul);
  925.             }
  926.             break;

  927.         case 'p': // PRN
  928.             if (end - ptr >= 3
  929.                     && toLower(raw[ptr + 1]) == 'r'
  930.                     && toLower(raw[ptr + 2]) == 'n'
  931.                     && (end - ptr == 3 || raw[ptr + 3] == '.')) {
  932.                 report(WIN32_BAD_NAME, id,
  933.                         JGitText.get().corruptObjectInvalidNamePrn);
  934.             }
  935.             break;
  936.         }
  937.     }

  938.     private static boolean isInvalidOnWindows(byte c) {
  939.         // Windows disallows "special" characters in a path component.
  940.         switch (c) {
  941.         case '"':
  942.         case '*':
  943.         case ':':
  944.         case '<':
  945.         case '>':
  946.         case '?':
  947.         case '\\':
  948.         case '|':
  949.             return true;
  950.         }
  951.         return 1 <= c && c <= 31;
  952.     }

  953.     private static boolean isGit(byte[] buf, int p) {
  954.         return toLower(buf[p]) == 'g'
  955.                 && toLower(buf[p + 1]) == 'i'
  956.                 && toLower(buf[p + 2]) == 't';
  957.     }

  958.     /**
  959.      * Check if the filename contained in buf[start:end] could be read as a
  960.      * .gitmodules file when checked out to the working directory.
  961.      *
  962.      * This ought to be a simple comparison, but some filesystems have peculiar
  963.      * rules for normalizing filenames:
  964.      *
  965.      * NTFS has backward-compatibility support for 8.3 synonyms of long file
  966.      * names (see
  967.      * https://web.archive.org/web/20160318181041/https://usn.pw/blog/gen/2015/06/09/filenames/
  968.      * for details). NTFS is also case-insensitive.
  969.      *
  970.      * MacOS's HFS+ folds away ignorable Unicode characters in addition to case
  971.      * folding.
  972.      *
  973.      * @param buf
  974.      *            byte array to decode
  975.      * @param start
  976.      *            position where a supposed filename is starting
  977.      * @param end
  978.      *            position where a supposed filename is ending
  979.      * @param id
  980.      *            object id for error reporting
  981.      *
  982.      * @return true if the filename in buf could be a ".gitmodules" file
  983.      * @throws CorruptObjectException
  984.      */
  985.     private boolean isGitmodules(byte[] buf, int start, int end, @Nullable AnyObjectId id)
  986.             throws CorruptObjectException {
  987.         // Simple cases first.
  988.         if (end - start < 8) {
  989.             return false;
  990.         }
  991.         return (end - start == dotGitmodules.length
  992.                 && RawParseUtils.match(buf, start, dotGitmodules) != -1)
  993.             || (macosx && isMacHFSGitmodules(buf, start, end, id))
  994.             || (windows && isNTFSGitmodules(buf, start, end));
  995.     }

  996.     private boolean matchLowerCase(byte[] b, int ptr, byte[] src) {
  997.         if (ptr + src.length > b.length) {
  998.             return false;
  999.         }
  1000.         for (int i = 0; i < src.length; i++, ptr++) {
  1001.             if (toLower(b[ptr]) != src[i]) {
  1002.                 return false;
  1003.             }
  1004.         }
  1005.         return true;
  1006.     }

  1007.     // .gitmodules, case-insensitive, or an 8.3 abbreviation of the same.
  1008.     private boolean isNTFSGitmodules(byte[] buf, int start, int end) {
  1009.         if (end - start == 11) {
  1010.             return matchLowerCase(buf, start, dotGitmodules);
  1011.         }

  1012.         if (end - start != 8) {
  1013.             return false;
  1014.         }

  1015.         // "gitmod" or a prefix of "gi7eba", followed by...
  1016.         byte[] gitmod = new byte[]{'g', 'i', 't', 'm', 'o', 'd', '~'};
  1017.         if (matchLowerCase(buf, start, gitmod)) {
  1018.             start += 6;
  1019.         } else {
  1020.             byte[] gi7eba = new byte[]{'g', 'i', '7', 'e', 'b', 'a'};
  1021.             for (int i = 0; i < gi7eba.length; i++, start++) {
  1022.                 byte c = (byte) toLower(buf[start]);
  1023.                 if (c == '~') {
  1024.                     break;
  1025.                 }
  1026.                 if (c != gi7eba[i]) {
  1027.                     return false;
  1028.                 }
  1029.             }
  1030.         }

  1031.         // ... ~ and a number
  1032.         if (end - start < 2) {
  1033.             return false;
  1034.         }
  1035.         if (buf[start] != '~') {
  1036.             return false;
  1037.         }
  1038.         start++;
  1039.         if (buf[start] < '1' || buf[start] > '9') {
  1040.             return false;
  1041.         }
  1042.         start++;
  1043.         for (; start != end; start++) {
  1044.             if (buf[start] < '0' || buf[start] > '9') {
  1045.                 return false;
  1046.             }
  1047.         }
  1048.         return true;
  1049.     }

  1050.     private static boolean isGitTilde1(byte[] buf, int p, int end) {
  1051.         if (end - p != 5)
  1052.             return false;
  1053.         return toLower(buf[p]) == 'g' && toLower(buf[p + 1]) == 'i'
  1054.                 && toLower(buf[p + 2]) == 't' && buf[p + 3] == '~'
  1055.                 && buf[p + 4] == '1';
  1056.     }

  1057.     private static boolean isNormalizedGit(byte[] raw, int ptr, int end) {
  1058.         if (isGit(raw, ptr)) {
  1059.             int dots = 0;
  1060.             boolean space = false;
  1061.             int p = end - 1;
  1062.             for (; (ptr + 2) < p; p--) {
  1063.                 if (raw[p] == '.')
  1064.                     dots++;
  1065.                 else if (raw[p] == ' ')
  1066.                     space = true;
  1067.                 else
  1068.                     break;
  1069.             }
  1070.             return p == ptr + 2 && (dots == 1 || space);
  1071.         }
  1072.         return false;
  1073.     }

  1074.     private boolean match(byte[] b, byte[] src) {
  1075.         int r = RawParseUtils.match(b, bufPtr.value, src);
  1076.         if (r < 0) {
  1077.             return false;
  1078.         }
  1079.         bufPtr.value = r;
  1080.         return true;
  1081.     }

  1082.     private static char toLower(byte b) {
  1083.         if ('A' <= b && b <= 'Z')
  1084.             return (char) (b + ('a' - 'A'));
  1085.         return (char) b;
  1086.     }

  1087.     private static boolean isPositiveDigit(byte b) {
  1088.         return '1' <= b && b <= '9';
  1089.     }

  1090.     /**
  1091.      * Create a new {@link org.eclipse.jgit.lib.BlobObjectChecker}.
  1092.      *
  1093.      * @return new BlobObjectChecker or null if it's not provided.
  1094.      * @since 4.9
  1095.      */
  1096.     @Nullable
  1097.     public BlobObjectChecker newBlobObjectChecker() {
  1098.         return null;
  1099.     }

  1100.     /**
  1101.      * Check a blob for errors.
  1102.      *
  1103.      * <p>
  1104.      * This may not be called from PackParser in some cases. Use
  1105.      * {@link #newBlobObjectChecker} instead.
  1106.      *
  1107.      * @param raw
  1108.      *            the blob data. The array is never modified.
  1109.      * @throws org.eclipse.jgit.errors.CorruptObjectException
  1110.      *             if any error was detected.
  1111.      */
  1112.     public void checkBlob(byte[] raw) throws CorruptObjectException {
  1113.         // We can always assume the blob is valid.
  1114.     }

  1115.     private String normalize(byte[] raw, int ptr, int end) {
  1116.         String n = RawParseUtils.decode(raw, ptr, end).toLowerCase(Locale.US);
  1117.         return macosx ? Normalizer.normalize(n, Normalizer.Form.NFC) : n;
  1118.     }

  1119.     /**
  1120.      * Get the list of ".gitmodules" files found in the pack. For each, report
  1121.      * its blob id (e.g. to validate its contents) and the tree where it was
  1122.      * found (e.g. to check if it is in the root)
  1123.      *
  1124.      * @return List of pairs of ids {@literal <tree, blob>}.
  1125.      *
  1126.      * @since 4.7.5
  1127.      */
  1128.     public List<GitmoduleEntry> getGitsubmodules() {
  1129.         return gitsubmodules;
  1130.     }

  1131.     /**
  1132.      * Reset the invocation-specific state from this instance. Specifically this
  1133.      * clears the list of .gitmodules files encountered (see
  1134.      * {@link #getGitsubmodules()})
  1135.      *
  1136.      * Configurations like errors to filter, skip lists or the specified O.S.
  1137.      * (set via {@link #setSafeForMacOS(boolean)} or
  1138.      * {@link #setSafeForWindows(boolean)}) are NOT cleared.
  1139.      *
  1140.      * @since 5.2
  1141.      */
  1142.     public void reset() {
  1143.         gitsubmodules.clear();
  1144.     }
  1145. }