|
|
@ -44,20 +44,56 @@ |
|
|
|
|
|
|
|
|
|
|
|
package org.eclipse.jgit.lib; |
|
|
|
package org.eclipse.jgit.lib; |
|
|
|
|
|
|
|
|
|
|
|
import static org.eclipse.jgit.util.RawParseUtils.match; |
|
|
|
import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH; |
|
|
|
|
|
|
|
import static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH; |
|
|
|
|
|
|
|
import static org.eclipse.jgit.lib.Constants.OBJ_BAD; |
|
|
|
|
|
|
|
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; |
|
|
|
|
|
|
|
import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT; |
|
|
|
|
|
|
|
import static org.eclipse.jgit.lib.Constants.OBJ_TAG; |
|
|
|
|
|
|
|
import static org.eclipse.jgit.lib.Constants.OBJ_TREE; |
|
|
|
|
|
|
|
import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_DATE; |
|
|
|
|
|
|
|
import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_EMAIL; |
|
|
|
|
|
|
|
import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_OBJECT_SHA1; |
|
|
|
|
|
|
|
import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_PARENT_SHA1; |
|
|
|
|
|
|
|
import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_TIMEZONE; |
|
|
|
|
|
|
|
import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_TREE_SHA1; |
|
|
|
|
|
|
|
import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_UTF8; |
|
|
|
|
|
|
|
import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.DUPLICATE_ENTRIES; |
|
|
|
|
|
|
|
import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.EMPTY_NAME; |
|
|
|
|
|
|
|
import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.FULL_PATHNAME; |
|
|
|
|
|
|
|
import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.HAS_DOT; |
|
|
|
|
|
|
|
import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.HAS_DOTDOT; |
|
|
|
|
|
|
|
import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.HAS_DOTGIT; |
|
|
|
|
|
|
|
import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_AUTHOR; |
|
|
|
|
|
|
|
import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_COMMITTER; |
|
|
|
|
|
|
|
import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_EMAIL; |
|
|
|
|
|
|
|
import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_OBJECT; |
|
|
|
|
|
|
|
import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_SPACE_BEFORE_DATE; |
|
|
|
|
|
|
|
import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_TAG_ENTRY; |
|
|
|
|
|
|
|
import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_TREE; |
|
|
|
|
|
|
|
import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_TYPE_ENTRY; |
|
|
|
|
|
|
|
import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.NULL_SHA1; |
|
|
|
|
|
|
|
import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.TREE_NOT_SORTED; |
|
|
|
|
|
|
|
import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.UNKNOWN_TYPE; |
|
|
|
|
|
|
|
import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.WIN32_BAD_NAME; |
|
|
|
|
|
|
|
import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.ZERO_PADDED_FILEMODE; |
|
|
|
import static org.eclipse.jgit.util.RawParseUtils.nextLF; |
|
|
|
import static org.eclipse.jgit.util.RawParseUtils.nextLF; |
|
|
|
import static org.eclipse.jgit.util.RawParseUtils.parseBase10; |
|
|
|
import static org.eclipse.jgit.util.RawParseUtils.parseBase10; |
|
|
|
|
|
|
|
|
|
|
|
import java.text.MessageFormat; |
|
|
|
import java.text.MessageFormat; |
|
|
|
import java.text.Normalizer; |
|
|
|
import java.text.Normalizer; |
|
|
|
|
|
|
|
import java.util.EnumSet; |
|
|
|
import java.util.HashSet; |
|
|
|
import java.util.HashSet; |
|
|
|
import java.util.Locale; |
|
|
|
import java.util.Locale; |
|
|
|
import java.util.Set; |
|
|
|
import java.util.Set; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import org.eclipse.jgit.annotations.NonNull; |
|
|
|
|
|
|
|
import org.eclipse.jgit.annotations.Nullable; |
|
|
|
import org.eclipse.jgit.errors.CorruptObjectException; |
|
|
|
import org.eclipse.jgit.errors.CorruptObjectException; |
|
|
|
import org.eclipse.jgit.internal.JGitText; |
|
|
|
import org.eclipse.jgit.internal.JGitText; |
|
|
|
import org.eclipse.jgit.util.MutableInteger; |
|
|
|
import org.eclipse.jgit.util.MutableInteger; |
|
|
|
import org.eclipse.jgit.util.RawParseUtils; |
|
|
|
import org.eclipse.jgit.util.RawParseUtils; |
|
|
|
|
|
|
|
import org.eclipse.jgit.util.StringUtils; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Verifies that an object is formatted correctly. |
|
|
|
* Verifies that an object is formatted correctly. |
|
|
@ -98,16 +134,119 @@ public class ObjectChecker { |
|
|
|
/** Header "tagger " */ |
|
|
|
/** Header "tagger " */ |
|
|
|
public static final byte[] tagger = Constants.encodeASCII("tagger "); //$NON-NLS-1$
|
|
|
|
public static final byte[] tagger = Constants.encodeASCII("tagger "); //$NON-NLS-1$
|
|
|
|
|
|
|
|
|
|
|
|
private final MutableObjectId tempId = new MutableObjectId(); |
|
|
|
/** |
|
|
|
|
|
|
|
* Potential issues identified by the checker. |
|
|
|
private final MutableInteger ptrout = new MutableInteger(); |
|
|
|
* |
|
|
|
|
|
|
|
* @since 4.2 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
public enum ErrorType { |
|
|
|
|
|
|
|
// @formatter:off
|
|
|
|
|
|
|
|
// These names match git-core so that fsck section keys also match.
|
|
|
|
|
|
|
|
/***/ NULL_SHA1, |
|
|
|
|
|
|
|
/***/ DUPLICATE_ENTRIES, |
|
|
|
|
|
|
|
/***/ TREE_NOT_SORTED, |
|
|
|
|
|
|
|
/***/ ZERO_PADDED_FILEMODE, |
|
|
|
|
|
|
|
/***/ EMPTY_NAME, |
|
|
|
|
|
|
|
/***/ FULL_PATHNAME, |
|
|
|
|
|
|
|
/***/ HAS_DOT, |
|
|
|
|
|
|
|
/***/ HAS_DOTDOT, |
|
|
|
|
|
|
|
/***/ HAS_DOTGIT, |
|
|
|
|
|
|
|
/***/ BAD_OBJECT_SHA1, |
|
|
|
|
|
|
|
/***/ BAD_PARENT_SHA1, |
|
|
|
|
|
|
|
/***/ BAD_TREE_SHA1, |
|
|
|
|
|
|
|
/***/ MISSING_AUTHOR, |
|
|
|
|
|
|
|
/***/ MISSING_COMMITTER, |
|
|
|
|
|
|
|
/***/ MISSING_OBJECT, |
|
|
|
|
|
|
|
/***/ MISSING_TREE, |
|
|
|
|
|
|
|
/***/ MISSING_TYPE_ENTRY, |
|
|
|
|
|
|
|
/***/ MISSING_TAG_ENTRY, |
|
|
|
|
|
|
|
/***/ BAD_DATE, |
|
|
|
|
|
|
|
/***/ BAD_EMAIL, |
|
|
|
|
|
|
|
/***/ BAD_TIMEZONE, |
|
|
|
|
|
|
|
/***/ MISSING_EMAIL, |
|
|
|
|
|
|
|
/***/ MISSING_SPACE_BEFORE_DATE, |
|
|
|
|
|
|
|
/***/ UNKNOWN_TYPE, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// These are unique to JGit.
|
|
|
|
|
|
|
|
/***/ WIN32_BAD_NAME, |
|
|
|
|
|
|
|
/***/ BAD_UTF8; |
|
|
|
|
|
|
|
// @formatter:on
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** @return camelCaseVersion of the name. */ |
|
|
|
|
|
|
|
public String getMessageId() { |
|
|
|
|
|
|
|
String n = name(); |
|
|
|
|
|
|
|
StringBuilder r = new StringBuilder(n.length()); |
|
|
|
|
|
|
|
for (int i = 0; i < n.length(); i++) { |
|
|
|
|
|
|
|
char c = n.charAt(i); |
|
|
|
|
|
|
|
if (c != '_') { |
|
|
|
|
|
|
|
r.append(StringUtils.toLowerCase(c)); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
r.append(n.charAt(++i)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return r.toString(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private boolean allowZeroMode; |
|
|
|
private final MutableObjectId tempId = new MutableObjectId(); |
|
|
|
|
|
|
|
private final MutableInteger bufPtr = new MutableInteger(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private EnumSet<ErrorType> errors = EnumSet.allOf(ErrorType.class); |
|
|
|
|
|
|
|
private ObjectIdSet skipList; |
|
|
|
private boolean allowInvalidPersonIdent; |
|
|
|
private boolean allowInvalidPersonIdent; |
|
|
|
private boolean windows; |
|
|
|
private boolean windows; |
|
|
|
private boolean macosx; |
|
|
|
private boolean macosx; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* Enable accepting specific malformed (but not horribly broken) objects. |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @param objects |
|
|
|
|
|
|
|
* collection of object names known to be broken in a non-fatal |
|
|
|
|
|
|
|
* way that should be ignored by the checker. |
|
|
|
|
|
|
|
* @return {@code this} |
|
|
|
|
|
|
|
* @since 4.2 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
public ObjectChecker setSkipList(@Nullable ObjectIdSet objects) { |
|
|
|
|
|
|
|
skipList = objects; |
|
|
|
|
|
|
|
return this; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* Configure error types to be ignored across all objects. |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @param ids |
|
|
|
|
|
|
|
* error types to ignore. The caller's set is copied. |
|
|
|
|
|
|
|
* @return {@code this} |
|
|
|
|
|
|
|
* @since 4.2 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
public ObjectChecker setIgnore(@Nullable Set<ErrorType> ids) { |
|
|
|
|
|
|
|
errors = EnumSet.allOf(ErrorType.class); |
|
|
|
|
|
|
|
if (ids != null) { |
|
|
|
|
|
|
|
errors.removeAll(ids); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return this; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* Add message type to be ignored across all objects. |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @param id |
|
|
|
|
|
|
|
* error type to ignore. |
|
|
|
|
|
|
|
* @param ignore |
|
|
|
|
|
|
|
* true to ignore this error; false to treat the error as an |
|
|
|
|
|
|
|
* error and throw. |
|
|
|
|
|
|
|
* @return {@code this} |
|
|
|
|
|
|
|
* @since 4.2 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
public ObjectChecker setIgnore(ErrorType id, boolean ignore) { |
|
|
|
|
|
|
|
if (ignore) { |
|
|
|
|
|
|
|
errors.remove(id); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
errors.add(id); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return this; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Enable accepting leading zero mode in tree entries. |
|
|
|
* Enable accepting leading zero mode in tree entries. |
|
|
|
* <p> |
|
|
|
* <p> |
|
|
@ -115,14 +254,15 @@ public class ObjectChecker { |
|
|
|
* tree entries. This is technically incorrect but gracefully allowed by |
|
|
|
* tree entries. This is technically incorrect but gracefully allowed by |
|
|
|
* git-core. JGit rejects such trees by default, but may need to accept |
|
|
|
* git-core. JGit rejects such trees by default, but may need to accept |
|
|
|
* them on broken histories. |
|
|
|
* them on broken histories. |
|
|
|
|
|
|
|
* <p> |
|
|
|
|
|
|
|
* Same as {@code setIgnore(ZERO_PADDED_FILEMODE, allow)}. |
|
|
|
* |
|
|
|
* |
|
|
|
* @param allow allow leading zero mode. |
|
|
|
* @param allow allow leading zero mode. |
|
|
|
* @return {@code this}. |
|
|
|
* @return {@code this}. |
|
|
|
* @since 3.4 |
|
|
|
* @since 3.4 |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public ObjectChecker setAllowLeadingZeroFileMode(boolean allow) { |
|
|
|
public ObjectChecker setAllowLeadingZeroFileMode(boolean allow) { |
|
|
|
allowZeroMode = allow; |
|
|
|
return setIgnore(ZERO_PADDED_FILEMODE, allow); |
|
|
|
return this; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
@ -183,62 +323,117 @@ public class ObjectChecker { |
|
|
|
* @throws CorruptObjectException |
|
|
|
* @throws CorruptObjectException |
|
|
|
* if an error is identified. |
|
|
|
* if an error is identified. |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public void check(final int objType, final byte[] raw) |
|
|
|
public void check(int objType, byte[] raw) |
|
|
|
|
|
|
|
throws CorruptObjectException { |
|
|
|
|
|
|
|
check(idFor(objType, raw), objType, raw); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* Check an object for parsing errors. |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @param id |
|
|
|
|
|
|
|
* identify of the object being checked. |
|
|
|
|
|
|
|
* @param objType |
|
|
|
|
|
|
|
* type of the object. Must be a valid object type code in |
|
|
|
|
|
|
|
* {@link Constants}. |
|
|
|
|
|
|
|
* @param raw |
|
|
|
|
|
|
|
* the raw data which comprises the object. This should be in the |
|
|
|
|
|
|
|
* canonical format (that is the format used to generate the |
|
|
|
|
|
|
|
* ObjectId of the object). The array is never modified. |
|
|
|
|
|
|
|
* @throws CorruptObjectException |
|
|
|
|
|
|
|
* if an error is identified. |
|
|
|
|
|
|
|
* @since 4.2 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
public void check(@Nullable AnyObjectId id, int objType, byte[] raw) |
|
|
|
throws CorruptObjectException { |
|
|
|
throws CorruptObjectException { |
|
|
|
switch (objType) { |
|
|
|
switch (objType) { |
|
|
|
case Constants.OBJ_COMMIT: |
|
|
|
case OBJ_COMMIT: |
|
|
|
checkCommit(raw); |
|
|
|
checkCommit(id, raw); |
|
|
|
break; |
|
|
|
break; |
|
|
|
case Constants.OBJ_TAG: |
|
|
|
case OBJ_TAG: |
|
|
|
checkTag(raw); |
|
|
|
checkTag(id, raw); |
|
|
|
break; |
|
|
|
break; |
|
|
|
case Constants.OBJ_TREE: |
|
|
|
case OBJ_TREE: |
|
|
|
checkTree(raw); |
|
|
|
checkTree(id, raw); |
|
|
|
break; |
|
|
|
break; |
|
|
|
case Constants.OBJ_BLOB: |
|
|
|
case OBJ_BLOB: |
|
|
|
checkBlob(raw); |
|
|
|
checkBlob(raw); |
|
|
|
break; |
|
|
|
break; |
|
|
|
default: |
|
|
|
default: |
|
|
|
throw new CorruptObjectException(MessageFormat.format( |
|
|
|
report(UNKNOWN_TYPE, id, MessageFormat.format( |
|
|
|
JGitText.get().corruptObjectInvalidType2, |
|
|
|
JGitText.get().corruptObjectInvalidType2, |
|
|
|
Integer.valueOf(objType))); |
|
|
|
Integer.valueOf(objType))); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private int id(final byte[] raw, final int ptr) { |
|
|
|
private boolean checkId(byte[] raw) { |
|
|
|
|
|
|
|
int p = bufPtr.value; |
|
|
|
try { |
|
|
|
try { |
|
|
|
tempId.fromString(raw, ptr); |
|
|
|
tempId.fromString(raw, p); |
|
|
|
return ptr + Constants.OBJECT_ID_STRING_LENGTH; |
|
|
|
|
|
|
|
} catch (IllegalArgumentException e) { |
|
|
|
} catch (IllegalArgumentException e) { |
|
|
|
return -1; |
|
|
|
bufPtr.value = nextLF(raw, p); |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
p += OBJECT_ID_STRING_LENGTH; |
|
|
|
|
|
|
|
if (raw[p] == '\n') { |
|
|
|
|
|
|
|
bufPtr.value = p + 1; |
|
|
|
|
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
bufPtr.value = nextLF(raw, p); |
|
|
|
|
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private int personIdent(final byte[] raw, int ptr) { |
|
|
|
private void checkPersonIdent(byte[] raw, @Nullable AnyObjectId id) |
|
|
|
if (allowInvalidPersonIdent) |
|
|
|
throws CorruptObjectException { |
|
|
|
return nextLF(raw, ptr) - 1; |
|
|
|
if (allowInvalidPersonIdent) { |
|
|
|
|
|
|
|
bufPtr.value = nextLF(raw, bufPtr.value); |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
final int emailB = nextLF(raw, ptr, '<'); |
|
|
|
final int emailB = nextLF(raw, bufPtr.value, '<'); |
|
|
|
if (emailB == ptr || raw[emailB - 1] != '<') |
|
|
|
if (emailB == bufPtr.value || raw[emailB - 1] != '<') { |
|
|
|
return -1; |
|
|
|
report(MISSING_EMAIL, id, JGitText.get().corruptObjectMissingEmail); |
|
|
|
|
|
|
|
bufPtr.value = nextLF(raw, bufPtr.value); |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
final int emailE = nextLF(raw, emailB, '>'); |
|
|
|
final int emailE = nextLF(raw, emailB, '>'); |
|
|
|
if (emailE == emailB || raw[emailE - 1] != '>') |
|
|
|
if (emailE == emailB || raw[emailE - 1] != '>') { |
|
|
|
return -1; |
|
|
|
report(BAD_EMAIL, id, JGitText.get().corruptObjectBadEmail); |
|
|
|
if (emailE == raw.length || raw[emailE] != ' ') |
|
|
|
bufPtr.value = nextLF(raw, bufPtr.value); |
|
|
|
return -1; |
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (emailE == raw.length || raw[emailE] != ' ') { |
|
|
|
|
|
|
|
report(MISSING_SPACE_BEFORE_DATE, id, |
|
|
|
|
|
|
|
JGitText.get().corruptObjectBadDate); |
|
|
|
|
|
|
|
bufPtr.value = nextLF(raw, bufPtr.value); |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
parseBase10(raw, emailE + 1, ptrout); // when
|
|
|
|
parseBase10(raw, emailE + 1, bufPtr); // when
|
|
|
|
ptr = ptrout.value; |
|
|
|
if (emailE + 1 == bufPtr.value || bufPtr.value == raw.length |
|
|
|
if (emailE + 1 == ptr) |
|
|
|
|| raw[bufPtr.value] != ' ') { |
|
|
|
return -1; |
|
|
|
report(BAD_DATE, id, JGitText.get().corruptObjectBadDate); |
|
|
|
if (ptr == raw.length || raw[ptr] != ' ') |
|
|
|
bufPtr.value = nextLF(raw, bufPtr.value); |
|
|
|
return -1; |
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int p = bufPtr.value + 1; |
|
|
|
|
|
|
|
parseBase10(raw, p, bufPtr); // tz offset
|
|
|
|
|
|
|
|
if (p == bufPtr.value) { |
|
|
|
|
|
|
|
report(BAD_TIMEZONE, id, JGitText.get().corruptObjectBadTimezone); |
|
|
|
|
|
|
|
bufPtr.value = nextLF(raw, bufPtr.value); |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
parseBase10(raw, ptr + 1, ptrout); // tz offset
|
|
|
|
p = bufPtr.value; |
|
|
|
if (ptr + 1 == ptrout.value) |
|
|
|
if (raw[p] == '\n') { |
|
|
|
return -1; |
|
|
|
bufPtr.value = p + 1; |
|
|
|
return ptrout.value; |
|
|
|
} else { |
|
|
|
|
|
|
|
report(BAD_TIMEZONE, id, JGitText.get().corruptObjectBadTimezone); |
|
|
|
|
|
|
|
bufPtr.value = nextLF(raw, p); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
@ -249,36 +444,50 @@ public class ObjectChecker { |
|
|
|
* @throws CorruptObjectException |
|
|
|
* @throws CorruptObjectException |
|
|
|
* if any error was detected. |
|
|
|
* if any error was detected. |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public void checkCommit(final byte[] raw) throws CorruptObjectException { |
|
|
|
public void checkCommit(byte[] raw) throws CorruptObjectException { |
|
|
|
int ptr = 0; |
|
|
|
checkCommit(idFor(OBJ_COMMIT, raw), raw); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if ((ptr = match(raw, ptr, tree)) < 0) |
|
|
|
/** |
|
|
|
throw new CorruptObjectException( |
|
|
|
* Check a commit for errors. |
|
|
|
JGitText.get().corruptObjectNotreeHeader); |
|
|
|
* |
|
|
|
if ((ptr = id(raw, ptr)) < 0 || raw[ptr++] != '\n') |
|
|
|
* @param id |
|
|
|
throw new CorruptObjectException( |
|
|
|
* identity of the object being checked. |
|
|
|
JGitText.get().corruptObjectInvalidTree); |
|
|
|
* @param raw |
|
|
|
|
|
|
|
* the commit data. The array is never modified. |
|
|
|
|
|
|
|
* @throws CorruptObjectException |
|
|
|
|
|
|
|
* if any error was detected. |
|
|
|
|
|
|
|
* @since 4.2 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
public void checkCommit(@Nullable AnyObjectId id, byte[] raw) |
|
|
|
|
|
|
|
throws CorruptObjectException { |
|
|
|
|
|
|
|
bufPtr.value = 0; |
|
|
|
|
|
|
|
|
|
|
|
while (match(raw, ptr, parent) >= 0) { |
|
|
|
if (!match(raw, tree)) { |
|
|
|
ptr += parent.length; |
|
|
|
report(MISSING_TREE, id, JGitText.get().corruptObjectNotreeHeader); |
|
|
|
if ((ptr = id(raw, ptr)) < 0 || raw[ptr++] != '\n') |
|
|
|
} else if (!checkId(raw)) { |
|
|
|
throw new CorruptObjectException( |
|
|
|
report(BAD_TREE_SHA1, id, JGitText.get().corruptObjectInvalidTree); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
while (match(raw, parent)) { |
|
|
|
|
|
|
|
if (!checkId(raw)) { |
|
|
|
|
|
|
|
report(BAD_PARENT_SHA1, id, |
|
|
|
JGitText.get().corruptObjectInvalidParent); |
|
|
|
JGitText.get().corruptObjectInvalidParent); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if ((ptr = match(raw, ptr, author)) < 0) |
|
|
|
if (match(raw, author)) { |
|
|
|
throw new CorruptObjectException( |
|
|
|
checkPersonIdent(raw, id); |
|
|
|
JGitText.get().corruptObjectNoAuthor); |
|
|
|
} else { |
|
|
|
if ((ptr = personIdent(raw, ptr)) < 0 || raw[ptr++] != '\n') |
|
|
|
report(MISSING_AUTHOR, id, JGitText.get().corruptObjectNoAuthor); |
|
|
|
throw new CorruptObjectException( |
|
|
|
} |
|
|
|
JGitText.get().corruptObjectInvalidAuthor); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if ((ptr = match(raw, ptr, committer)) < 0) |
|
|
|
if (match(raw, committer)) { |
|
|
|
throw new CorruptObjectException( |
|
|
|
checkPersonIdent(raw, id); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
report(MISSING_COMMITTER, id, |
|
|
|
JGitText.get().corruptObjectNoCommitter); |
|
|
|
JGitText.get().corruptObjectNoCommitter); |
|
|
|
if ((ptr = personIdent(raw, ptr)) < 0 || raw[ptr++] != '\n') |
|
|
|
} |
|
|
|
throw new CorruptObjectException( |
|
|
|
|
|
|
|
JGitText.get().corruptObjectInvalidCommitter); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
@ -289,30 +498,46 @@ public class ObjectChecker { |
|
|
|
* @throws CorruptObjectException |
|
|
|
* @throws CorruptObjectException |
|
|
|
* if any error was detected. |
|
|
|
* if any error was detected. |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public void checkTag(final byte[] raw) throws CorruptObjectException { |
|
|
|
public void checkTag(byte[] raw) throws CorruptObjectException { |
|
|
|
int ptr = 0; |
|
|
|
checkTag(idFor(OBJ_TAG, raw), raw); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if ((ptr = match(raw, ptr, object)) < 0) |
|
|
|
/** |
|
|
|
throw new CorruptObjectException( |
|
|
|
* Check an annotated tag for errors. |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @param id |
|
|
|
|
|
|
|
* identity of the object being checked. |
|
|
|
|
|
|
|
* @param raw |
|
|
|
|
|
|
|
* the tag data. The array is never modified. |
|
|
|
|
|
|
|
* @throws CorruptObjectException |
|
|
|
|
|
|
|
* if any error was detected. |
|
|
|
|
|
|
|
* @since 4.2 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
public void checkTag(@Nullable AnyObjectId id, byte[] raw) |
|
|
|
|
|
|
|
throws CorruptObjectException { |
|
|
|
|
|
|
|
bufPtr.value = 0; |
|
|
|
|
|
|
|
if (!match(raw, object)) { |
|
|
|
|
|
|
|
report(MISSING_OBJECT, id, |
|
|
|
JGitText.get().corruptObjectNoObjectHeader); |
|
|
|
JGitText.get().corruptObjectNoObjectHeader); |
|
|
|
if ((ptr = id(raw, ptr)) < 0 || raw[ptr++] != '\n') |
|
|
|
} else if (!checkId(raw)) { |
|
|
|
throw new CorruptObjectException( |
|
|
|
report(BAD_OBJECT_SHA1, id, |
|
|
|
JGitText.get().corruptObjectInvalidObject); |
|
|
|
JGitText.get().corruptObjectInvalidObject); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if ((ptr = match(raw, ptr, type)) < 0) |
|
|
|
if (!match(raw, type)) { |
|
|
|
throw new CorruptObjectException( |
|
|
|
report(MISSING_TYPE_ENTRY, id, |
|
|
|
JGitText.get().corruptObjectNoTypeHeader); |
|
|
|
JGitText.get().corruptObjectNoTypeHeader); |
|
|
|
ptr = nextLF(raw, ptr); |
|
|
|
} |
|
|
|
|
|
|
|
bufPtr.value = nextLF(raw, bufPtr.value); |
|
|
|
|
|
|
|
|
|
|
|
if ((ptr = match(raw, ptr, tag)) < 0) |
|
|
|
if (!match(raw, tag)) { |
|
|
|
throw new CorruptObjectException( |
|
|
|
report(MISSING_TAG_ENTRY, id, |
|
|
|
JGitText.get().corruptObjectNoTagHeader); |
|
|
|
JGitText.get().corruptObjectNoTagHeader); |
|
|
|
ptr = nextLF(raw, ptr); |
|
|
|
} |
|
|
|
|
|
|
|
bufPtr.value = nextLF(raw, bufPtr.value); |
|
|
|
|
|
|
|
|
|
|
|
if ((ptr = match(raw, ptr, tagger)) > 0) { |
|
|
|
if (match(raw, tagger)) { |
|
|
|
if ((ptr = personIdent(raw, ptr)) < 0 || raw[ptr++] != '\n') |
|
|
|
checkPersonIdent(raw, id); |
|
|
|
throw new CorruptObjectException( |
|
|
|
|
|
|
|
JGitText.get().corruptObjectInvalidTagger); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -381,7 +606,23 @@ public class ObjectChecker { |
|
|
|
* @throws CorruptObjectException |
|
|
|
* @throws CorruptObjectException |
|
|
|
* if any error was detected. |
|
|
|
* if any error was detected. |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public void checkTree(final byte[] raw) throws CorruptObjectException { |
|
|
|
public void checkTree(byte[] raw) throws CorruptObjectException { |
|
|
|
|
|
|
|
checkTree(idFor(OBJ_TREE, raw), raw); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* Check a canonical formatted tree for errors. |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @param id |
|
|
|
|
|
|
|
* identity of the object being checked. |
|
|
|
|
|
|
|
* @param raw |
|
|
|
|
|
|
|
* the raw tree data. The array is never modified. |
|
|
|
|
|
|
|
* @throws CorruptObjectException |
|
|
|
|
|
|
|
* if any error was detected. |
|
|
|
|
|
|
|
* @since 4.2 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
public void checkTree(@Nullable AnyObjectId id, byte[] raw) |
|
|
|
|
|
|
|
throws CorruptObjectException { |
|
|
|
final int sz = raw.length; |
|
|
|
final int sz = raw.length; |
|
|
|
int ptr = 0; |
|
|
|
int ptr = 0; |
|
|
|
int lastNameB = 0, lastNameE = 0, lastMode = 0; |
|
|
|
int lastNameB = 0, lastNameE = 0, lastMode = 0; |
|
|
@ -392,74 +633,89 @@ public class ObjectChecker { |
|
|
|
while (ptr < sz) { |
|
|
|
while (ptr < sz) { |
|
|
|
int thisMode = 0; |
|
|
|
int thisMode = 0; |
|
|
|
for (;;) { |
|
|
|
for (;;) { |
|
|
|
if (ptr == sz) |
|
|
|
if (ptr == sz) { |
|
|
|
throw new CorruptObjectException( |
|
|
|
throw new CorruptObjectException( |
|
|
|
JGitText.get().corruptObjectTruncatedInMode); |
|
|
|
JGitText.get().corruptObjectTruncatedInMode); |
|
|
|
|
|
|
|
} |
|
|
|
final byte c = raw[ptr++]; |
|
|
|
final byte c = raw[ptr++]; |
|
|
|
if (' ' == c) |
|
|
|
if (' ' == c) |
|
|
|
break; |
|
|
|
break; |
|
|
|
if (c < '0' || c > '7') |
|
|
|
if (c < '0' || c > '7') { |
|
|
|
throw new CorruptObjectException( |
|
|
|
throw new CorruptObjectException( |
|
|
|
JGitText.get().corruptObjectInvalidModeChar); |
|
|
|
JGitText.get().corruptObjectInvalidModeChar); |
|
|
|
if (thisMode == 0 && c == '0' && !allowZeroMode) |
|
|
|
} |
|
|
|
throw new CorruptObjectException( |
|
|
|
if (thisMode == 0 && c == '0') { |
|
|
|
|
|
|
|
report(ZERO_PADDED_FILEMODE, id, |
|
|
|
JGitText.get().corruptObjectInvalidModeStartsZero); |
|
|
|
JGitText.get().corruptObjectInvalidModeStartsZero); |
|
|
|
|
|
|
|
} |
|
|
|
thisMode <<= 3; |
|
|
|
thisMode <<= 3; |
|
|
|
thisMode += c - '0'; |
|
|
|
thisMode += c - '0'; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (FileMode.fromBits(thisMode).getObjectType() == Constants.OBJ_BAD) |
|
|
|
if (FileMode.fromBits(thisMode).getObjectType() == OBJ_BAD) { |
|
|
|
throw new CorruptObjectException(MessageFormat.format( |
|
|
|
throw new CorruptObjectException(MessageFormat.format( |
|
|
|
JGitText.get().corruptObjectInvalidMode2, |
|
|
|
JGitText.get().corruptObjectInvalidMode2, |
|
|
|
Integer.valueOf(thisMode))); |
|
|
|
Integer.valueOf(thisMode))); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
final int thisNameB = ptr; |
|
|
|
final int thisNameB = ptr; |
|
|
|
ptr = scanPathSegment(raw, ptr, sz); |
|
|
|
ptr = scanPathSegment(raw, ptr, sz, id); |
|
|
|
if (ptr == sz || raw[ptr] != 0) |
|
|
|
if (ptr == sz || raw[ptr] != 0) { |
|
|
|
throw new CorruptObjectException( |
|
|
|
throw new CorruptObjectException( |
|
|
|
JGitText.get().corruptObjectTruncatedInName); |
|
|
|
JGitText.get().corruptObjectTruncatedInName); |
|
|
|
checkPathSegment2(raw, thisNameB, ptr); |
|
|
|
} |
|
|
|
|
|
|
|
checkPathSegment2(raw, thisNameB, ptr, id); |
|
|
|
if (normalized != null) { |
|
|
|
if (normalized != null) { |
|
|
|
if (!normalized.add(normalize(raw, thisNameB, ptr))) |
|
|
|
if (!normalized.add(normalize(raw, thisNameB, ptr))) { |
|
|
|
throw new CorruptObjectException( |
|
|
|
report(DUPLICATE_ENTRIES, id, |
|
|
|
JGitText.get().corruptObjectDuplicateEntryNames); |
|
|
|
JGitText.get().corruptObjectDuplicateEntryNames); |
|
|
|
} else if (duplicateName(raw, thisNameB, ptr)) |
|
|
|
} |
|
|
|
throw new CorruptObjectException( |
|
|
|
} else if (duplicateName(raw, thisNameB, ptr)) { |
|
|
|
|
|
|
|
report(DUPLICATE_ENTRIES, id, |
|
|
|
JGitText.get().corruptObjectDuplicateEntryNames); |
|
|
|
JGitText.get().corruptObjectDuplicateEntryNames); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (lastNameB != 0) { |
|
|
|
if (lastNameB != 0) { |
|
|
|
final int cmp = pathCompare(raw, lastNameB, lastNameE, |
|
|
|
final int cmp = pathCompare(raw, lastNameB, lastNameE, |
|
|
|
lastMode, thisNameB, ptr, thisMode); |
|
|
|
lastMode, thisNameB, ptr, thisMode); |
|
|
|
if (cmp > 0) |
|
|
|
if (cmp > 0) { |
|
|
|
throw new CorruptObjectException( |
|
|
|
report(TREE_NOT_SORTED, id, |
|
|
|
JGitText.get().corruptObjectIncorrectSorting); |
|
|
|
JGitText.get().corruptObjectIncorrectSorting); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
lastNameB = thisNameB; |
|
|
|
lastNameB = thisNameB; |
|
|
|
lastNameE = ptr; |
|
|
|
lastNameE = ptr; |
|
|
|
lastMode = thisMode; |
|
|
|
lastMode = thisMode; |
|
|
|
|
|
|
|
|
|
|
|
ptr += 1 + Constants.OBJECT_ID_LENGTH; |
|
|
|
ptr += 1 + OBJECT_ID_LENGTH; |
|
|
|
if (ptr > sz) |
|
|
|
if (ptr > sz) { |
|
|
|
throw new CorruptObjectException( |
|
|
|
throw new CorruptObjectException( |
|
|
|
JGitText.get().corruptObjectTruncatedInObjectId); |
|
|
|
JGitText.get().corruptObjectTruncatedInObjectId); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (ObjectId.zeroId().compareTo(raw, ptr - OBJECT_ID_LENGTH) == 0) { |
|
|
|
|
|
|
|
report(NULL_SHA1, id, JGitText.get().corruptObjectZeroId); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private int scanPathSegment(byte[] raw, int ptr, int end) |
|
|
|
private int scanPathSegment(byte[] raw, int ptr, int end, |
|
|
|
throws CorruptObjectException { |
|
|
|
@Nullable AnyObjectId id) throws CorruptObjectException { |
|
|
|
for (; ptr < end; ptr++) { |
|
|
|
for (; ptr < end; ptr++) { |
|
|
|
byte c = raw[ptr]; |
|
|
|
byte c = raw[ptr]; |
|
|
|
if (c == 0) |
|
|
|
if (c == 0) { |
|
|
|
return ptr; |
|
|
|
return ptr; |
|
|
|
if (c == '/') |
|
|
|
} |
|
|
|
throw new CorruptObjectException( |
|
|
|
if (c == '/') { |
|
|
|
|
|
|
|
report(FULL_PATHNAME, id, |
|
|
|
JGitText.get().corruptObjectNameContainsSlash); |
|
|
|
JGitText.get().corruptObjectNameContainsSlash); |
|
|
|
|
|
|
|
} |
|
|
|
if (windows && isInvalidOnWindows(c)) { |
|
|
|
if (windows && isInvalidOnWindows(c)) { |
|
|
|
if (c > 31) |
|
|
|
if (c > 31) { |
|
|
|
throw new CorruptObjectException(String.format( |
|
|
|
throw new CorruptObjectException(String.format( |
|
|
|
JGitText.get().corruptObjectNameContainsChar, |
|
|
|
JGitText.get().corruptObjectNameContainsChar, |
|
|
|
Byte.valueOf(c))); |
|
|
|
Byte.valueOf(c))); |
|
|
|
|
|
|
|
} |
|
|
|
throw new CorruptObjectException(String.format( |
|
|
|
throw new CorruptObjectException(String.format( |
|
|
|
JGitText.get().corruptObjectNameContainsByte, |
|
|
|
JGitText.get().corruptObjectNameContainsByte, |
|
|
|
Integer.valueOf(c & 0xff))); |
|
|
|
Integer.valueOf(c & 0xff))); |
|
|
@ -468,6 +724,26 @@ public class ObjectChecker { |
|
|
|
return ptr; |
|
|
|
return ptr; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@SuppressWarnings("resource") |
|
|
|
|
|
|
|
@Nullable |
|
|
|
|
|
|
|
private ObjectId idFor(int objType, byte[] raw) { |
|
|
|
|
|
|
|
if (skipList != null) { |
|
|
|
|
|
|
|
return new ObjectInserter.Formatter().idFor(objType, raw); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return null; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private void report(@NonNull ErrorType err, @Nullable AnyObjectId id, |
|
|
|
|
|
|
|
String why) throws CorruptObjectException { |
|
|
|
|
|
|
|
if (errors.contains(err) |
|
|
|
|
|
|
|
&& (id == null || skipList == null || !skipList.contains(id))) { |
|
|
|
|
|
|
|
if (id != null) { |
|
|
|
|
|
|
|
throw new CorruptObjectException(err, id, why); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
throw new CorruptObjectException(why); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Check tree path entry for validity. |
|
|
|
* Check tree path entry for validity. |
|
|
|
* <p> |
|
|
|
* <p> |
|
|
@ -518,73 +794,82 @@ public class ObjectChecker { |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public void checkPathSegment(byte[] raw, int ptr, int end) |
|
|
|
public void checkPathSegment(byte[] raw, int ptr, int end) |
|
|
|
throws CorruptObjectException { |
|
|
|
throws CorruptObjectException { |
|
|
|
int e = scanPathSegment(raw, ptr, end); |
|
|
|
int e = scanPathSegment(raw, ptr, end, null); |
|
|
|
if (e < end && raw[e] == 0) |
|
|
|
if (e < end && raw[e] == 0) |
|
|
|
throw new CorruptObjectException( |
|
|
|
throw new CorruptObjectException( |
|
|
|
JGitText.get().corruptObjectNameContainsNullByte); |
|
|
|
JGitText.get().corruptObjectNameContainsNullByte); |
|
|
|
checkPathSegment2(raw, ptr, end); |
|
|
|
checkPathSegment2(raw, ptr, end, null); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private void checkPathSegment2(byte[] raw, int ptr, int end) |
|
|
|
private void checkPathSegment2(byte[] raw, int ptr, int end, |
|
|
|
throws CorruptObjectException { |
|
|
|
@Nullable AnyObjectId id) throws CorruptObjectException { |
|
|
|
if (ptr == end) |
|
|
|
if (ptr == end) { |
|
|
|
throw new CorruptObjectException( |
|
|
|
report(EMPTY_NAME, id, JGitText.get().corruptObjectNameZeroLength); |
|
|
|
JGitText.get().corruptObjectNameZeroLength); |
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (raw[ptr] == '.') { |
|
|
|
if (raw[ptr] == '.') { |
|
|
|
switch (end - ptr) { |
|
|
|
switch (end - ptr) { |
|
|
|
case 1: |
|
|
|
case 1: |
|
|
|
throw new CorruptObjectException( |
|
|
|
report(HAS_DOT, id, JGitText.get().corruptObjectNameDot); |
|
|
|
JGitText.get().corruptObjectNameDot); |
|
|
|
break; |
|
|
|
case 2: |
|
|
|
case 2: |
|
|
|
if (raw[ptr + 1] == '.') |
|
|
|
if (raw[ptr + 1] == '.') { |
|
|
|
throw new CorruptObjectException( |
|
|
|
report(HAS_DOTDOT, id, |
|
|
|
JGitText.get().corruptObjectNameDotDot); |
|
|
|
JGitText.get().corruptObjectNameDotDot); |
|
|
|
|
|
|
|
} |
|
|
|
break; |
|
|
|
break; |
|
|
|
case 4: |
|
|
|
case 4: |
|
|
|
if (isGit(raw, ptr + 1)) |
|
|
|
if (isGit(raw, ptr + 1)) { |
|
|
|
throw new CorruptObjectException(String.format( |
|
|
|
report(HAS_DOTGIT, id, String.format( |
|
|
|
JGitText.get().corruptObjectInvalidName, |
|
|
|
JGitText.get().corruptObjectInvalidName, |
|
|
|
RawParseUtils.decode(raw, ptr, end))); |
|
|
|
RawParseUtils.decode(raw, ptr, end))); |
|
|
|
|
|
|
|
} |
|
|
|
break; |
|
|
|
break; |
|
|
|
default: |
|
|
|
default: |
|
|
|
if (end - ptr > 4 && isNormalizedGit(raw, ptr + 1, end)) |
|
|
|
if (end - ptr > 4 && isNormalizedGit(raw, ptr + 1, end)) { |
|
|
|
throw new CorruptObjectException(String.format( |
|
|
|
report(HAS_DOTGIT, id, String.format( |
|
|
|
JGitText.get().corruptObjectInvalidName, |
|
|
|
JGitText.get().corruptObjectInvalidName, |
|
|
|
RawParseUtils.decode(raw, ptr, end))); |
|
|
|
RawParseUtils.decode(raw, ptr, end))); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} else if (isGitTilde1(raw, ptr, end)) { |
|
|
|
} else if (isGitTilde1(raw, ptr, end)) { |
|
|
|
throw new CorruptObjectException(String.format( |
|
|
|
report(HAS_DOTGIT, id, String.format( |
|
|
|
JGitText.get().corruptObjectInvalidName, |
|
|
|
JGitText.get().corruptObjectInvalidName, |
|
|
|
RawParseUtils.decode(raw, ptr, end))); |
|
|
|
RawParseUtils.decode(raw, ptr, end))); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (macosx && isMacHFSGit(raw, ptr, end, id)) { |
|
|
|
if (macosx && isMacHFSGit(raw, ptr, end)) |
|
|
|
report(HAS_DOTGIT, id, String.format( |
|
|
|
throw new CorruptObjectException(String.format( |
|
|
|
|
|
|
|
JGitText.get().corruptObjectInvalidNameIgnorableUnicode, |
|
|
|
JGitText.get().corruptObjectInvalidNameIgnorableUnicode, |
|
|
|
RawParseUtils.decode(raw, ptr, end))); |
|
|
|
RawParseUtils.decode(raw, ptr, end))); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (windows) { |
|
|
|
if (windows) { |
|
|
|
// Windows ignores space and dot at end of file name.
|
|
|
|
// Windows ignores space and dot at end of file name.
|
|
|
|
if (raw[end - 1] == ' ' || raw[end - 1] == '.') |
|
|
|
if (raw[end - 1] == ' ' || raw[end - 1] == '.') { |
|
|
|
throw new CorruptObjectException(String.format( |
|
|
|
report(WIN32_BAD_NAME, id, String.format( |
|
|
|
JGitText.get().corruptObjectInvalidNameEnd, |
|
|
|
JGitText.get().corruptObjectInvalidNameEnd, |
|
|
|
Character.valueOf(((char) raw[end - 1])))); |
|
|
|
Character.valueOf(((char) raw[end - 1])))); |
|
|
|
if (end - ptr >= 3) |
|
|
|
} |
|
|
|
checkNotWindowsDevice(raw, ptr, end); |
|
|
|
if (end - ptr >= 3) { |
|
|
|
|
|
|
|
checkNotWindowsDevice(raw, ptr, end, id); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Mac's HFS+ folds permutations of ".git" and Unicode ignorable characters
|
|
|
|
// Mac's HFS+ folds permutations of ".git" and Unicode ignorable characters
|
|
|
|
// to ".git" therefore we should prevent such names
|
|
|
|
// to ".git" therefore we should prevent such names
|
|
|
|
private static boolean isMacHFSGit(byte[] raw, int ptr, int end) |
|
|
|
private boolean isMacHFSGit(byte[] raw, int ptr, int end, |
|
|
|
throws CorruptObjectException { |
|
|
|
@Nullable AnyObjectId id) throws CorruptObjectException { |
|
|
|
boolean ignorable = false; |
|
|
|
boolean ignorable = false; |
|
|
|
byte[] git = new byte[] { '.', 'g', 'i', 't' }; |
|
|
|
byte[] git = new byte[] { '.', 'g', 'i', 't' }; |
|
|
|
int g = 0; |
|
|
|
int g = 0; |
|
|
|
while (ptr < end) { |
|
|
|
while (ptr < end) { |
|
|
|
switch (raw[ptr]) { |
|
|
|
switch (raw[ptr]) { |
|
|
|
case (byte) 0xe2: // http://www.utf8-chartable.de/unicode-utf8-table.pl?start=8192
|
|
|
|
case (byte) 0xe2: // http://www.utf8-chartable.de/unicode-utf8-table.pl?start=8192
|
|
|
|
checkTruncatedIgnorableUTF8(raw, ptr, end); |
|
|
|
if (!checkTruncatedIgnorableUTF8(raw, ptr, end, id)) { |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
} |
|
|
|
switch (raw[ptr + 1]) { |
|
|
|
switch (raw[ptr + 1]) { |
|
|
|
case (byte) 0x80: |
|
|
|
case (byte) 0x80: |
|
|
|
switch (raw[ptr + 2]) { |
|
|
|
switch (raw[ptr + 2]) { |
|
|
@ -621,7 +906,9 @@ public class ObjectChecker { |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
case (byte) 0xef: // http://www.utf8-chartable.de/unicode-utf8-table.pl?start=65024
|
|
|
|
case (byte) 0xef: // http://www.utf8-chartable.de/unicode-utf8-table.pl?start=65024
|
|
|
|
checkTruncatedIgnorableUTF8(raw, ptr, end); |
|
|
|
if (!checkTruncatedIgnorableUTF8(raw, ptr, end, id)) { |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
} |
|
|
|
// U+FEFF 0xefbbbf ZERO WIDTH NO-BREAK SPACE
|
|
|
|
// U+FEFF 0xefbbbf ZERO WIDTH NO-BREAK SPACE
|
|
|
|
if ((raw[ptr + 1] == (byte) 0xbb) |
|
|
|
if ((raw[ptr + 1] == (byte) 0xbb) |
|
|
|
&& (raw[ptr + 2] == (byte) 0xbf)) { |
|
|
|
&& (raw[ptr + 2] == (byte) 0xbf)) { |
|
|
@ -642,12 +929,15 @@ public class ObjectChecker { |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private static void checkTruncatedIgnorableUTF8(byte[] raw, int ptr, int end) |
|
|
|
private boolean checkTruncatedIgnorableUTF8(byte[] raw, int ptr, int end, |
|
|
|
throws CorruptObjectException { |
|
|
|
@Nullable AnyObjectId id) throws CorruptObjectException { |
|
|
|
if ((ptr + 2) >= end) |
|
|
|
if ((ptr + 2) >= end) { |
|
|
|
throw new CorruptObjectException(MessageFormat.format( |
|
|
|
report(BAD_UTF8, id, MessageFormat.format( |
|
|
|
JGitText.get().corruptObjectInvalidNameInvalidUtf8, |
|
|
|
JGitText.get().corruptObjectInvalidNameInvalidUtf8, |
|
|
|
toHexString(raw, ptr, end))); |
|
|
|
toHexString(raw, ptr, end))); |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private static String toHexString(byte[] raw, int ptr, int end) { |
|
|
|
private static String toHexString(byte[] raw, int ptr, int end) { |
|
|
@ -657,33 +947,36 @@ public class ObjectChecker { |
|
|
|
return b.toString(); |
|
|
|
return b.toString(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private static void checkNotWindowsDevice(byte[] raw, int ptr, int end) |
|
|
|
private void checkNotWindowsDevice(byte[] raw, int ptr, int end, |
|
|
|
throws CorruptObjectException { |
|
|
|
@Nullable AnyObjectId id) throws CorruptObjectException { |
|
|
|
switch (toLower(raw[ptr])) { |
|
|
|
switch (toLower(raw[ptr])) { |
|
|
|
case 'a': // AUX
|
|
|
|
case 'a': // AUX
|
|
|
|
if (end - ptr >= 3 |
|
|
|
if (end - ptr >= 3 |
|
|
|
&& toLower(raw[ptr + 1]) == 'u' |
|
|
|
&& toLower(raw[ptr + 1]) == 'u' |
|
|
|
&& toLower(raw[ptr + 2]) == 'x' |
|
|
|
&& toLower(raw[ptr + 2]) == 'x' |
|
|
|
&& (end - ptr == 3 || raw[ptr + 3] == '.')) |
|
|
|
&& (end - ptr == 3 || raw[ptr + 3] == '.')) { |
|
|
|
throw new CorruptObjectException( |
|
|
|
report(WIN32_BAD_NAME, id, |
|
|
|
JGitText.get().corruptObjectInvalidNameAux); |
|
|
|
JGitText.get().corruptObjectInvalidNameAux); |
|
|
|
|
|
|
|
} |
|
|
|
break; |
|
|
|
break; |
|
|
|
|
|
|
|
|
|
|
|
case 'c': // CON, COM[1-9]
|
|
|
|
case 'c': // CON, COM[1-9]
|
|
|
|
if (end - ptr >= 3 |
|
|
|
if (end - ptr >= 3 |
|
|
|
&& toLower(raw[ptr + 2]) == 'n' |
|
|
|
&& toLower(raw[ptr + 2]) == 'n' |
|
|
|
&& toLower(raw[ptr + 1]) == 'o' |
|
|
|
&& toLower(raw[ptr + 1]) == 'o' |
|
|
|
&& (end - ptr == 3 || raw[ptr + 3] == '.')) |
|
|
|
&& (end - ptr == 3 || raw[ptr + 3] == '.')) { |
|
|
|
throw new CorruptObjectException( |
|
|
|
report(WIN32_BAD_NAME, id, |
|
|
|
JGitText.get().corruptObjectInvalidNameCon); |
|
|
|
JGitText.get().corruptObjectInvalidNameCon); |
|
|
|
|
|
|
|
} |
|
|
|
if (end - ptr >= 4 |
|
|
|
if (end - ptr >= 4 |
|
|
|
&& toLower(raw[ptr + 2]) == 'm' |
|
|
|
&& toLower(raw[ptr + 2]) == 'm' |
|
|
|
&& toLower(raw[ptr + 1]) == 'o' |
|
|
|
&& toLower(raw[ptr + 1]) == 'o' |
|
|
|
&& isPositiveDigit(raw[ptr + 3]) |
|
|
|
&& isPositiveDigit(raw[ptr + 3]) |
|
|
|
&& (end - ptr == 4 || raw[ptr + 4] == '.')) |
|
|
|
&& (end - ptr == 4 || raw[ptr + 4] == '.')) { |
|
|
|
throw new CorruptObjectException(String.format( |
|
|
|
report(WIN32_BAD_NAME, id, String.format( |
|
|
|
JGitText.get().corruptObjectInvalidNameCom, |
|
|
|
JGitText.get().corruptObjectInvalidNameCom, |
|
|
|
Character.valueOf(((char) raw[ptr + 3])))); |
|
|
|
Character.valueOf(((char) raw[ptr + 3])))); |
|
|
|
|
|
|
|
} |
|
|
|
break; |
|
|
|
break; |
|
|
|
|
|
|
|
|
|
|
|
case 'l': // LPT[1-9]
|
|
|
|
case 'l': // LPT[1-9]
|
|
|
@ -691,28 +984,31 @@ public class ObjectChecker { |
|
|
|
&& toLower(raw[ptr + 1]) == 'p' |
|
|
|
&& toLower(raw[ptr + 1]) == 'p' |
|
|
|
&& toLower(raw[ptr + 2]) == 't' |
|
|
|
&& toLower(raw[ptr + 2]) == 't' |
|
|
|
&& isPositiveDigit(raw[ptr + 3]) |
|
|
|
&& isPositiveDigit(raw[ptr + 3]) |
|
|
|
&& (end - ptr == 4 || raw[ptr + 4] == '.')) |
|
|
|
&& (end - ptr == 4 || raw[ptr + 4] == '.')) { |
|
|
|
throw new CorruptObjectException(String.format( |
|
|
|
report(WIN32_BAD_NAME, id, String.format( |
|
|
|
JGitText.get().corruptObjectInvalidNameLpt, |
|
|
|
JGitText.get().corruptObjectInvalidNameLpt, |
|
|
|
Character.valueOf(((char) raw[ptr + 3])))); |
|
|
|
Character.valueOf(((char) raw[ptr + 3])))); |
|
|
|
|
|
|
|
} |
|
|
|
break; |
|
|
|
break; |
|
|
|
|
|
|
|
|
|
|
|
case 'n': // NUL
|
|
|
|
case 'n': // NUL
|
|
|
|
if (end - ptr >= 3 |
|
|
|
if (end - ptr >= 3 |
|
|
|
&& toLower(raw[ptr + 1]) == 'u' |
|
|
|
&& toLower(raw[ptr + 1]) == 'u' |
|
|
|
&& toLower(raw[ptr + 2]) == 'l' |
|
|
|
&& toLower(raw[ptr + 2]) == 'l' |
|
|
|
&& (end - ptr == 3 || raw[ptr + 3] == '.')) |
|
|
|
&& (end - ptr == 3 || raw[ptr + 3] == '.')) { |
|
|
|
throw new CorruptObjectException( |
|
|
|
report(WIN32_BAD_NAME, id, |
|
|
|
JGitText.get().corruptObjectInvalidNameNul); |
|
|
|
JGitText.get().corruptObjectInvalidNameNul); |
|
|
|
|
|
|
|
} |
|
|
|
break; |
|
|
|
break; |
|
|
|
|
|
|
|
|
|
|
|
case 'p': // PRN
|
|
|
|
case 'p': // PRN
|
|
|
|
if (end - ptr >= 3 |
|
|
|
if (end - ptr >= 3 |
|
|
|
&& toLower(raw[ptr + 1]) == 'r' |
|
|
|
&& toLower(raw[ptr + 1]) == 'r' |
|
|
|
&& toLower(raw[ptr + 2]) == 'n' |
|
|
|
&& toLower(raw[ptr + 2]) == 'n' |
|
|
|
&& (end - ptr == 3 || raw[ptr + 3] == '.')) |
|
|
|
&& (end - ptr == 3 || raw[ptr + 3] == '.')) { |
|
|
|
throw new CorruptObjectException( |
|
|
|
report(WIN32_BAD_NAME, id, |
|
|
|
JGitText.get().corruptObjectInvalidNamePrn); |
|
|
|
JGitText.get().corruptObjectInvalidNamePrn); |
|
|
|
|
|
|
|
} |
|
|
|
break; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
@ -765,6 +1061,15 @@ public class ObjectChecker { |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private boolean match(byte[] b, byte[] src) { |
|
|
|
|
|
|
|
int r = RawParseUtils.match(b, bufPtr.value, src); |
|
|
|
|
|
|
|
if (r < 0) { |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
bufPtr.value = r; |
|
|
|
|
|
|
|
return true; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private static char toLower(byte b) { |
|
|
|
private static char toLower(byte b) { |
|
|
|
if ('A' <= b && b <= 'Z') |
|
|
|
if ('A' <= b && b <= 'Z') |
|
|
|
return (char) (b + ('a' - 'A')); |
|
|
|
return (char) (b + ('a' - 'A')); |
|
|
|