Accept some of the same section keys that fsck does in git-core, allowing repositories to skip over specific kinds of acceptable broken objects, e.g.: [fsck] duplicateEntries = ignore zeroPaddedFilemode = ignore The zeroPaddedFilemode = ignore is a synonym for the JGit specific allowLeadingZeroFileMode = true. Only accept the JGit key if git-core key was not specified. Change-Id: Idaed9310e2a5ce5511670ead1aaea2b30aac903ctags/v4.2.0.201601211800-r
import static org.eclipse.jgit.lib.Constants.OBJ_TREE; | import static org.eclipse.jgit.lib.Constants.OBJ_TREE; | ||||
import static org.eclipse.jgit.lib.Constants.encode; | import static org.eclipse.jgit.lib.Constants.encode; | ||||
import static org.eclipse.jgit.lib.Constants.encodeASCII; | import static org.eclipse.jgit.lib.Constants.encodeASCII; | ||||
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.NULL_SHA1; | |||||
import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.TREE_NOT_SORTED; | |||||
import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.ZERO_PADDED_FILEMODE; | |||||
import static org.junit.Assert.assertEquals; | import static org.junit.Assert.assertEquals; | ||||
import static org.junit.Assert.assertSame; | |||||
import static org.junit.Assert.fail; | import static org.junit.Assert.fail; | ||||
import java.io.UnsupportedEncodingException; | import java.io.UnsupportedEncodingException; | ||||
b.append("committer <> 0 +0000\n"); | b.append("committer <> 0 +0000\n"); | ||||
byte[] data = encodeASCII(b.toString()); | byte[] data = encodeASCII(b.toString()); | ||||
assertCorrupt("invalid author", OBJ_COMMIT, data); | |||||
assertCorrupt("bad date", OBJ_COMMIT, data); | |||||
checker.setAllowInvalidPersonIdent(true); | checker.setAllowInvalidPersonIdent(true); | ||||
checker.checkCommit(data); | checker.checkCommit(data); | ||||
b.append("committer b <b@c> <b@c> 0 +0000\n"); | b.append("committer b <b@c> <b@c> 0 +0000\n"); | ||||
byte[] data = encodeASCII(b.toString()); | byte[] data = encodeASCII(b.toString()); | ||||
assertCorrupt("invalid committer", OBJ_COMMIT, data); | |||||
assertCorrupt("bad date", OBJ_COMMIT, data); | |||||
checker.setAllowInvalidPersonIdent(true); | checker.setAllowInvalidPersonIdent(true); | ||||
checker.checkCommit(data); | checker.checkCommit(data); | ||||
byte[] data = encodeASCII(b.toString()); | byte[] data = encodeASCII(b.toString()); | ||||
assertCorrupt("invalid tree", OBJ_COMMIT, data); | assertCorrupt("invalid tree", OBJ_COMMIT, data); | ||||
assertSkipListRejects("invalid tree", OBJ_COMMIT, data); | |||||
} | } | ||||
@Test | @Test | ||||
b.append("author A. U. Thor <foo 1 +0000\n"); | b.append("author A. U. Thor <foo 1 +0000\n"); | ||||
byte[] data = encodeASCII(b.toString()); | byte[] data = encodeASCII(b.toString()); | ||||
assertCorrupt("invalid author", OBJ_COMMIT, data); | |||||
assertCorrupt("bad email", OBJ_COMMIT, data); | |||||
assertSkipListAccepts(OBJ_COMMIT, data); | assertSkipListAccepts(OBJ_COMMIT, data); | ||||
} | } | ||||
b.append("author A. U. Thor foo> 1 +0000\n"); | b.append("author A. U. Thor foo> 1 +0000\n"); | ||||
byte[] data = encodeASCII(b.toString()); | byte[] data = encodeASCII(b.toString()); | ||||
assertCorrupt("invalid author", OBJ_COMMIT, data); | |||||
assertCorrupt("missing email", OBJ_COMMIT, data); | |||||
assertSkipListAccepts(OBJ_COMMIT, data); | assertSkipListAccepts(OBJ_COMMIT, data); | ||||
} | } | ||||
b.append("author 1 +0000\n"); | b.append("author 1 +0000\n"); | ||||
byte[] data = encodeASCII(b.toString()); | byte[] data = encodeASCII(b.toString()); | ||||
assertCorrupt("invalid author", OBJ_COMMIT, data); | |||||
assertCorrupt("missing email", OBJ_COMMIT, data); | |||||
assertSkipListAccepts(OBJ_COMMIT, data); | assertSkipListAccepts(OBJ_COMMIT, data); | ||||
} | } | ||||
b.append("author a <b> +0000\n"); | b.append("author a <b> +0000\n"); | ||||
byte[] data = encodeASCII(b.toString()); | byte[] data = encodeASCII(b.toString()); | ||||
assertCorrupt("invalid author", OBJ_COMMIT, data); | |||||
assertCorrupt("bad date", OBJ_COMMIT, data); | |||||
assertSkipListAccepts(OBJ_COMMIT, data); | assertSkipListAccepts(OBJ_COMMIT, data); | ||||
} | } | ||||
b.append("author a <b>\n"); | b.append("author a <b>\n"); | ||||
byte[] data = encodeASCII(b.toString()); | byte[] data = encodeASCII(b.toString()); | ||||
assertCorrupt("invalid author", OBJ_COMMIT, data); | |||||
assertCorrupt("bad date", OBJ_COMMIT, data); | |||||
assertSkipListAccepts(OBJ_COMMIT, data); | assertSkipListAccepts(OBJ_COMMIT, data); | ||||
} | } | ||||
b.append("author a <b> z"); | b.append("author a <b> z"); | ||||
byte[] data = encodeASCII(b.toString()); | byte[] data = encodeASCII(b.toString()); | ||||
assertCorrupt("invalid author", OBJ_COMMIT, data); | |||||
assertCorrupt("bad date", OBJ_COMMIT, data); | |||||
assertSkipListAccepts(OBJ_COMMIT, data); | assertSkipListAccepts(OBJ_COMMIT, data); | ||||
} | } | ||||
b.append("author a <b> 1 z"); | b.append("author a <b> 1 z"); | ||||
byte[] data = encodeASCII(b.toString()); | byte[] data = encodeASCII(b.toString()); | ||||
assertCorrupt("invalid author", OBJ_COMMIT, data); | |||||
assertCorrupt("bad time zone", OBJ_COMMIT, data); | |||||
assertSkipListAccepts(OBJ_COMMIT, data); | assertSkipListAccepts(OBJ_COMMIT, data); | ||||
} | } | ||||
b.append("committer a <"); | b.append("committer a <"); | ||||
byte[] data = encodeASCII(b.toString()); | byte[] data = encodeASCII(b.toString()); | ||||
assertCorrupt("invalid committer", OBJ_COMMIT, data); | |||||
assertCorrupt("bad email", OBJ_COMMIT, data); | |||||
assertSkipListAccepts(OBJ_COMMIT, data); | assertSkipListAccepts(OBJ_COMMIT, data); | ||||
} | } | ||||
b.append("tagger \n"); | b.append("tagger \n"); | ||||
byte[] data = encodeASCII(b.toString()); | byte[] data = encodeASCII(b.toString()); | ||||
assertCorrupt("invalid tagger", OBJ_TAG, data); | |||||
assertCorrupt("missing email", OBJ_TAG, data); | |||||
checker.setAllowInvalidPersonIdent(true); | checker.setAllowInvalidPersonIdent(true); | ||||
checker.checkTag(data); | checker.checkTag(data); | ||||
b.append("tagger a < 1 +000\n"); | b.append("tagger a < 1 +000\n"); | ||||
byte[] data = encodeASCII(b.toString()); | byte[] data = encodeASCII(b.toString()); | ||||
assertCorrupt("invalid tagger", OBJ_TAG, data); | |||||
assertCorrupt("bad email", OBJ_TAG, data); | |||||
assertSkipListAccepts(OBJ_TAG, data); | assertSkipListAccepts(OBJ_TAG, data); | ||||
} | } | ||||
checker.checkTree(encodeASCII(b.toString())); | checker.checkTree(encodeASCII(b.toString())); | ||||
} | } | ||||
@Test | |||||
public void testNullSha1InTreeEntry() throws CorruptObjectException { | |||||
byte[] data = concat( | |||||
encodeASCII("100644 A"), new byte[] { '\0' }, | |||||
new byte[OBJECT_ID_LENGTH]); | |||||
assertCorrupt("entry points to null SHA-1", OBJ_TREE, data); | |||||
assertSkipListAccepts(OBJ_TREE, data); | |||||
checker.setIgnore(NULL_SHA1, true); | |||||
checker.checkTree(data); | |||||
} | |||||
@Test | @Test | ||||
public void testValidPosixTree() throws CorruptObjectException { | public void testValidPosixTree() throws CorruptObjectException { | ||||
checkOneName("a<b>c:d|e"); | checkOneName("a<b>c:d|e"); | ||||
checker.setAllowLeadingZeroFileMode(false); | checker.setAllowLeadingZeroFileMode(false); | ||||
assertSkipListAccepts(OBJ_TREE, data); | assertSkipListAccepts(OBJ_TREE, data); | ||||
checker.setIgnore(ZERO_PADDED_FILEMODE, true); | |||||
checker.checkTree(data); | |||||
} | } | ||||
@Test | @Test | ||||
} | } | ||||
@Test | @Test | ||||
public void testInvalidTreeNameContainsSlash() { | |||||
public void testInvalidTreeNameContainsSlash() | |||||
throws CorruptObjectException { | |||||
StringBuilder b = new StringBuilder(); | StringBuilder b = new StringBuilder(); | ||||
entry(b, "100644 a/b"); | entry(b, "100644 a/b"); | ||||
byte[] data = encodeASCII(b.toString()); | byte[] data = encodeASCII(b.toString()); | ||||
assertCorrupt("name contains '/'", OBJ_TREE, data); | assertCorrupt("name contains '/'", OBJ_TREE, data); | ||||
assertSkipListRejects("name contains '/'", OBJ_TREE, data); | |||||
assertSkipListAccepts(OBJ_TREE, data); | |||||
checker.setIgnore(FULL_PATHNAME, true); | |||||
checker.checkTree(data); | |||||
} | } | ||||
@Test | @Test | ||||
public void testInvalidTreeNameIsEmpty() { | |||||
public void testInvalidTreeNameIsEmpty() throws CorruptObjectException { | |||||
StringBuilder b = new StringBuilder(); | StringBuilder b = new StringBuilder(); | ||||
entry(b, "100644 "); | entry(b, "100644 "); | ||||
byte[] data = encodeASCII(b.toString()); | byte[] data = encodeASCII(b.toString()); | ||||
assertCorrupt("zero length name", OBJ_TREE, data); | assertCorrupt("zero length name", OBJ_TREE, data); | ||||
assertSkipListRejects("zero length name", OBJ_TREE, data); | |||||
assertSkipListAccepts(OBJ_TREE, data); | |||||
checker.setIgnore(EMPTY_NAME, true); | |||||
checker.checkTree(data); | |||||
} | } | ||||
@Test | @Test | ||||
public void testInvalidTreeNameIsDot() { | |||||
public void testInvalidTreeNameIsDot() throws CorruptObjectException { | |||||
StringBuilder b = new StringBuilder(); | StringBuilder b = new StringBuilder(); | ||||
entry(b, "100644 ."); | entry(b, "100644 ."); | ||||
byte[] data = encodeASCII(b.toString()); | byte[] data = encodeASCII(b.toString()); | ||||
assertCorrupt("invalid name '.'", OBJ_TREE, data); | assertCorrupt("invalid name '.'", OBJ_TREE, data); | ||||
assertSkipListRejects("invalid name '.'", OBJ_TREE, data); | |||||
assertSkipListAccepts(OBJ_TREE, data); | |||||
checker.setIgnore(HAS_DOT, true); | |||||
checker.checkTree(data); | |||||
} | } | ||||
@Test | @Test | ||||
public void testInvalidTreeNameIsDotDot() { | |||||
public void testInvalidTreeNameIsDotDot() throws CorruptObjectException { | |||||
StringBuilder b = new StringBuilder(); | StringBuilder b = new StringBuilder(); | ||||
entry(b, "100644 .."); | entry(b, "100644 .."); | ||||
byte[] data = encodeASCII(b.toString()); | byte[] data = encodeASCII(b.toString()); | ||||
assertCorrupt("invalid name '..'", OBJ_TREE, data); | assertCorrupt("invalid name '..'", OBJ_TREE, data); | ||||
assertSkipListRejects("invalid name '..'", OBJ_TREE, data); | |||||
assertSkipListAccepts(OBJ_TREE, data); | |||||
checker.setIgnore(HAS_DOTDOT, true); | |||||
checker.checkTree(data); | |||||
} | } | ||||
@Test | @Test | ||||
byte[] data = encodeASCII(b.toString()); | byte[] data = encodeASCII(b.toString()); | ||||
assertCorrupt("invalid name '.git'", OBJ_TREE, data); | assertCorrupt("invalid name '.git'", OBJ_TREE, data); | ||||
assertSkipListAccepts(OBJ_TREE, data); | assertSkipListAccepts(OBJ_TREE, data); | ||||
checker.setIgnore(HAS_DOTGIT, true); | |||||
checker.checkTree(data); | |||||
} | } | ||||
@Test | @Test | ||||
byte[] data = encodeASCII(b.toString()); | byte[] data = encodeASCII(b.toString()); | ||||
assertCorrupt("invalid name '.GiT'", OBJ_TREE, data); | assertCorrupt("invalid name '.GiT'", OBJ_TREE, data); | ||||
assertSkipListAccepts(OBJ_TREE, data); | assertSkipListAccepts(OBJ_TREE, data); | ||||
checker.setIgnore(HAS_DOTGIT, true); | |||||
checker.checkTree(data); | |||||
} | } | ||||
@Test | @Test | ||||
"invalid name '.gi\u200Ct' contains ignorable Unicode characters", | "invalid name '.gi\u200Ct' contains ignorable Unicode characters", | ||||
OBJ_TREE, data); | OBJ_TREE, data); | ||||
assertSkipListAccepts(OBJ_TREE, data); | assertSkipListAccepts(OBJ_TREE, data); | ||||
checker.setIgnore(HAS_DOTGIT, true); | |||||
checker.checkTree(data); | |||||
} | } | ||||
@Test | @Test | ||||
"invalid name '\u206B.git' contains ignorable Unicode characters", | "invalid name '\u206B.git' contains ignorable Unicode characters", | ||||
OBJ_TREE, data); | OBJ_TREE, data); | ||||
assertSkipListAccepts(OBJ_TREE, data); | assertSkipListAccepts(OBJ_TREE, data); | ||||
checker.setIgnore(HAS_DOTGIT, true); | |||||
checker.checkTree(data); | |||||
} | } | ||||
@Test | @Test | ||||
"invalid name '.git\uFEFF' contains ignorable Unicode characters", | "invalid name '.git\uFEFF' contains ignorable Unicode characters", | ||||
OBJ_TREE, data); | OBJ_TREE, data); | ||||
assertSkipListAccepts(OBJ_TREE, data); | assertSkipListAccepts(OBJ_TREE, data); | ||||
checker.setIgnore(HAS_DOTGIT, true); | |||||
checker.checkTree(data); | |||||
} | } | ||||
private static byte[] concat(byte[] b1, byte[] b2) { | |||||
byte[] data = new byte[b1.length + b2.length]; | |||||
System.arraycopy(b1, 0, data, 0, b1.length); | |||||
System.arraycopy(b2, 0, data, b1.length, b2.length); | |||||
private static byte[] concat(byte[]... b) { | |||||
int n = 0; | |||||
for (byte[] a : b) { | |||||
n += a.length; | |||||
} | |||||
byte[] data = new byte[n]; | |||||
n = 0; | |||||
for (byte[] a : b) { | |||||
System.arraycopy(a, 0, data, n, a.length); | |||||
n += a.length; | |||||
} | |||||
return data; | return data; | ||||
} | } | ||||
byte[] data = encodeASCII(b.toString()); | byte[] data = encodeASCII(b.toString()); | ||||
assertCorrupt("invalid name '.git.'", OBJ_TREE, data); | assertCorrupt("invalid name '.git.'", OBJ_TREE, data); | ||||
assertSkipListAccepts(OBJ_TREE, data); | assertSkipListAccepts(OBJ_TREE, data); | ||||
checker.setIgnore(HAS_DOTGIT, true); | |||||
checker.checkTree(data); | |||||
} | } | ||||
@Test | @Test | ||||
byte[] data = encodeASCII(b.toString()); | byte[] data = encodeASCII(b.toString()); | ||||
assertCorrupt("invalid name '.git '", OBJ_TREE, data); | assertCorrupt("invalid name '.git '", OBJ_TREE, data); | ||||
assertSkipListAccepts(OBJ_TREE, data); | assertSkipListAccepts(OBJ_TREE, data); | ||||
checker.setIgnore(HAS_DOTGIT, true); | |||||
checker.checkTree(data); | |||||
} | } | ||||
@Test | @Test | ||||
byte[] data = encodeASCII(b.toString()); | byte[] data = encodeASCII(b.toString()); | ||||
assertCorrupt("invalid name '.git. '", OBJ_TREE, data); | assertCorrupt("invalid name '.git. '", OBJ_TREE, data); | ||||
assertSkipListAccepts(OBJ_TREE, data); | assertSkipListAccepts(OBJ_TREE, data); | ||||
checker.setIgnore(HAS_DOTGIT, true); | |||||
checker.checkTree(data); | |||||
} | } | ||||
@Test | @Test | ||||
byte[] data = encodeASCII(b.toString()); | byte[] data = encodeASCII(b.toString()); | ||||
assertCorrupt("invalid name '.git . '", OBJ_TREE, data); | assertCorrupt("invalid name '.git . '", OBJ_TREE, data); | ||||
assertSkipListAccepts(OBJ_TREE, data); | assertSkipListAccepts(OBJ_TREE, data); | ||||
checker.setIgnore(HAS_DOTGIT, true); | |||||
checker.checkTree(data); | |||||
} | } | ||||
@Test | @Test | ||||
byte[] data = encodeASCII(b.toString()); | byte[] data = encodeASCII(b.toString()); | ||||
assertCorrupt("invalid name 'GIT~1'", OBJ_TREE, data); | assertCorrupt("invalid name 'GIT~1'", OBJ_TREE, data); | ||||
assertSkipListAccepts(OBJ_TREE, data); | assertSkipListAccepts(OBJ_TREE, data); | ||||
checker.setIgnore(HAS_DOTGIT, true); | |||||
checker.checkTree(data); | |||||
} | } | ||||
@Test | @Test | ||||
byte[] data = encodeASCII(b.toString()); | byte[] data = encodeASCII(b.toString()); | ||||
assertCorrupt("invalid name 'GiT~1'", OBJ_TREE, data); | assertCorrupt("invalid name 'GiT~1'", OBJ_TREE, data); | ||||
assertSkipListAccepts(OBJ_TREE, data); | assertSkipListAccepts(OBJ_TREE, data); | ||||
checker.setIgnore(HAS_DOTGIT, true); | |||||
checker.checkTree(data); | |||||
} | } | ||||
@Test | @Test | ||||
entry(b, "100644 foobar"); | entry(b, "100644 foobar"); | ||||
entry(b, "100644 fooaaa"); | entry(b, "100644 fooaaa"); | ||||
byte[] data = encodeASCII(b.toString()); | byte[] data = encodeASCII(b.toString()); | ||||
assertCorrupt("incorrectly sorted", OBJ_TREE, data); | assertCorrupt("incorrectly sorted", OBJ_TREE, data); | ||||
ObjectId id = idFor(OBJ_TREE, data); | |||||
try { | |||||
checker.check(id, OBJ_TREE, data); | |||||
fail("Did not throw CorruptObjectException"); | |||||
} catch (CorruptObjectException e) { | |||||
assertSame(TREE_NOT_SORTED, e.getErrorType()); | |||||
assertEquals("treeNotSorted: object " + id.name() | |||||
+ ": incorrectly sorted", e.getMessage()); | |||||
} | |||||
assertSkipListAccepts(OBJ_TREE, data); | assertSkipListAccepts(OBJ_TREE, data); | ||||
checker.setIgnore(TREE_NOT_SORTED, true); | |||||
checker.checkTree(data); | |||||
} | } | ||||
@Test | @Test | ||||
byte[] data = encodeASCII(b.toString()); | byte[] data = encodeASCII(b.toString()); | ||||
assertCorrupt("incorrectly sorted", OBJ_TREE, data); | assertCorrupt("incorrectly sorted", OBJ_TREE, data); | ||||
assertSkipListAccepts(OBJ_TREE, data); | assertSkipListAccepts(OBJ_TREE, data); | ||||
checker.setIgnore(TREE_NOT_SORTED, true); | |||||
checker.checkTree(data); | |||||
} | } | ||||
@Test | @Test | ||||
byte[] data = encodeASCII(b.toString()); | byte[] data = encodeASCII(b.toString()); | ||||
assertCorrupt("incorrectly sorted", OBJ_TREE, data); | assertCorrupt("incorrectly sorted", OBJ_TREE, data); | ||||
assertSkipListAccepts(OBJ_TREE, data); | assertSkipListAccepts(OBJ_TREE, data); | ||||
checker.setIgnore(TREE_NOT_SORTED, true); | |||||
checker.checkTree(data); | |||||
} | } | ||||
@Test | @Test | ||||
byte[] data = encodeASCII(b.toString()); | byte[] data = encodeASCII(b.toString()); | ||||
assertCorrupt("duplicate entry names", OBJ_TREE, data); | assertCorrupt("duplicate entry names", OBJ_TREE, data); | ||||
assertSkipListAccepts(OBJ_TREE, data); | assertSkipListAccepts(OBJ_TREE, data); | ||||
checker.setIgnore(DUPLICATE_ENTRIES, true); | |||||
checker.checkTree(data); | |||||
} | } | ||||
@Test | @Test | ||||
byte[] data = encodeASCII(b.toString()); | byte[] data = encodeASCII(b.toString()); | ||||
assertCorrupt("duplicate entry names", OBJ_TREE, data); | assertCorrupt("duplicate entry names", OBJ_TREE, data); | ||||
assertSkipListAccepts(OBJ_TREE, data); | assertSkipListAccepts(OBJ_TREE, data); | ||||
checker.setIgnore(DUPLICATE_ENTRIES, true); | |||||
checker.checkTree(data); | |||||
} | } | ||||
@Test | @Test | ||||
byte[] data = encodeASCII(b.toString()); | byte[] data = encodeASCII(b.toString()); | ||||
assertCorrupt("duplicate entry names", OBJ_TREE, data); | assertCorrupt("duplicate entry names", OBJ_TREE, data); | ||||
assertSkipListAccepts(OBJ_TREE, data); | assertSkipListAccepts(OBJ_TREE, data); | ||||
checker.setIgnore(DUPLICATE_ENTRIES, true); | |||||
checker.checkTree(data); | |||||
} | } | ||||
@Test | @Test | ||||
byte[] data = encodeASCII(b.toString()); | byte[] data = encodeASCII(b.toString()); | ||||
assertCorrupt("duplicate entry names", OBJ_TREE, data); | assertCorrupt("duplicate entry names", OBJ_TREE, data); | ||||
assertSkipListAccepts(OBJ_TREE, data); | assertSkipListAccepts(OBJ_TREE, data); | ||||
checker.setIgnore(DUPLICATE_ENTRIES, true); | |||||
checker.checkTree(data); | |||||
} | } | ||||
@Test | @Test | ||||
public void testInvalidTreeDuplicateNames5() | public void testInvalidTreeDuplicateNames5() | ||||
throws UnsupportedEncodingException, CorruptObjectException { | throws UnsupportedEncodingException, CorruptObjectException { | ||||
StringBuilder b = new StringBuilder(); | StringBuilder b = new StringBuilder(); | ||||
entry(b, "100644 a"); | |||||
entry(b, "100644 A"); | entry(b, "100644 A"); | ||||
entry(b, "100644 a"); | |||||
byte[] data = b.toString().getBytes("UTF-8"); | byte[] data = b.toString().getBytes("UTF-8"); | ||||
checker.setSafeForWindows(true); | checker.setSafeForWindows(true); | ||||
assertCorrupt("duplicate entry names", OBJ_TREE, data); | assertCorrupt("duplicate entry names", OBJ_TREE, data); | ||||
assertSkipListAccepts(OBJ_TREE, data); | assertSkipListAccepts(OBJ_TREE, data); | ||||
checker.setIgnore(DUPLICATE_ENTRIES, true); | |||||
checker.checkTree(data); | |||||
} | } | ||||
@Test | @Test | ||||
public void testInvalidTreeDuplicateNames6() | public void testInvalidTreeDuplicateNames6() | ||||
throws UnsupportedEncodingException, CorruptObjectException { | throws UnsupportedEncodingException, CorruptObjectException { | ||||
StringBuilder b = new StringBuilder(); | StringBuilder b = new StringBuilder(); | ||||
entry(b, "100644 a"); | |||||
entry(b, "100644 A"); | entry(b, "100644 A"); | ||||
entry(b, "100644 a"); | |||||
byte[] data = b.toString().getBytes("UTF-8"); | byte[] data = b.toString().getBytes("UTF-8"); | ||||
checker.setSafeForMacOS(true); | checker.setSafeForMacOS(true); | ||||
assertCorrupt("duplicate entry names", OBJ_TREE, data); | assertCorrupt("duplicate entry names", OBJ_TREE, data); | ||||
assertSkipListAccepts(OBJ_TREE, data); | assertSkipListAccepts(OBJ_TREE, data); | ||||
checker.setIgnore(DUPLICATE_ENTRIES, true); | |||||
checker.checkTree(data); | |||||
} | } | ||||
@Test | @Test | ||||
checker.setSafeForMacOS(true); | checker.setSafeForMacOS(true); | ||||
assertCorrupt("duplicate entry names", OBJ_TREE, data); | assertCorrupt("duplicate entry names", OBJ_TREE, data); | ||||
assertSkipListAccepts(OBJ_TREE, data); | assertSkipListAccepts(OBJ_TREE, data); | ||||
checker.setIgnore(DUPLICATE_ENTRIES, true); | |||||
checker.checkTree(data); | |||||
} | } | ||||
@Test | @Test |
connectionTimeOut=Connection time out: {0} | connectionTimeOut=Connection time out: {0} | ||||
contextMustBeNonNegative=context must be >= 0 | contextMustBeNonNegative=context must be >= 0 | ||||
corruptionDetectedReReadingAt=Corruption detected re-reading at {0} | corruptionDetectedReReadingAt=Corruption detected re-reading at {0} | ||||
corruptObjectBadDate=bad date | |||||
corruptObjectBadEmail=bad email | |||||
corruptObjectBadStream=bad stream | corruptObjectBadStream=bad stream | ||||
corruptObjectBadStreamCorruptHeader=bad stream, corrupt header | corruptObjectBadStreamCorruptHeader=bad stream, corrupt header | ||||
corruptObjectBadTimezone=bad time zone | |||||
corruptObjectDuplicateEntryNames=duplicate entry names | corruptObjectDuplicateEntryNames=duplicate entry names | ||||
corruptObjectGarbageAfterSize=garbage after size | corruptObjectGarbageAfterSize=garbage after size | ||||
corruptObjectIncorrectLength=incorrect length | corruptObjectIncorrectLength=incorrect length | ||||
corruptObjectIncorrectSorting=incorrectly sorted | corruptObjectIncorrectSorting=incorrectly sorted | ||||
corruptObjectInvalidAuthor=invalid author | |||||
corruptObjectInvalidCommitter=invalid committer | |||||
corruptObjectInvalidEntryMode=invalid entry mode | corruptObjectInvalidEntryMode=invalid entry mode | ||||
corruptObjectInvalidMode=invalid mode | corruptObjectInvalidMode=invalid mode | ||||
corruptObjectInvalidModeChar=invalid mode character | corruptObjectInvalidModeChar=invalid mode character | ||||
corruptObjectInvalidNamePrn=invalid name 'PRN' | corruptObjectInvalidNamePrn=invalid name 'PRN' | ||||
corruptObjectInvalidObject=invalid object | corruptObjectInvalidObject=invalid object | ||||
corruptObjectInvalidParent=invalid parent | corruptObjectInvalidParent=invalid parent | ||||
corruptObjectInvalidTagger=invalid tagger | |||||
corruptObjectInvalidTree=invalid tree | corruptObjectInvalidTree=invalid tree | ||||
corruptObjectInvalidType=invalid type | corruptObjectInvalidType=invalid type | ||||
corruptObjectInvalidType2=invalid type {0} | corruptObjectInvalidType2=invalid type {0} | ||||
corruptObjectMalformedHeader=malformed header: {0} | corruptObjectMalformedHeader=malformed header: {0} | ||||
corruptObjectMissingEmail=missing email | |||||
corruptObjectNameContainsByte=name contains byte 0x%x | corruptObjectNameContainsByte=name contains byte 0x%x | ||||
corruptObjectNameContainsChar=name contains '%c' | corruptObjectNameContainsChar=name contains '%c' | ||||
corruptObjectNameContainsNullByte=name contains byte 0x00 | corruptObjectNameContainsNullByte=name contains byte 0x00 | ||||
corruptObjectTruncatedInMode=truncated in mode | corruptObjectTruncatedInMode=truncated in mode | ||||
corruptObjectTruncatedInName=truncated in name | corruptObjectTruncatedInName=truncated in name | ||||
corruptObjectTruncatedInObjectId=truncated in object id | corruptObjectTruncatedInObjectId=truncated in object id | ||||
corruptObjectZeroId=entry points to null SHA-1 | |||||
couldNotCheckOutBecauseOfConflicts=Could not check out because of conflicts | couldNotCheckOutBecauseOfConflicts=Could not check out because of conflicts | ||||
couldNotDeleteLockFileShouldNotHappen=Could not delete lock file. Should not happen | couldNotDeleteLockFileShouldNotHappen=Could not delete lock file. Should not happen | ||||
couldNotDeleteTemporaryIndexFileShouldNotHappen=Could not delete temporary index file. Should not happen | couldNotDeleteTemporaryIndexFileShouldNotHappen=Could not delete temporary index file. Should not happen | ||||
objectAtHasBadZlibStream=Object at {0} in {1} has bad zlib stream | objectAtHasBadZlibStream=Object at {0} in {1} has bad zlib stream | ||||
objectAtPathDoesNotHaveId=Object at path "{0}" does not have an id assigned. All object ids must be assigned prior to writing a tree. | objectAtPathDoesNotHaveId=Object at path "{0}" does not have an id assigned. All object ids must be assigned prior to writing a tree. | ||||
objectIsCorrupt=Object {0} is corrupt: {1} | objectIsCorrupt=Object {0} is corrupt: {1} | ||||
objectIsCorrupt3={0}: object {1}: {2} | |||||
objectIsNotA=Object {0} is not a {1}. | objectIsNotA=Object {0} is not a {1}. | ||||
objectNotFound=Object {0} not found. | objectNotFound=Object {0} not found. | ||||
objectNotFoundIn=Object {0} not found in {1}. | objectNotFoundIn=Object {0} not found in {1}. |
import java.io.IOException; | import java.io.IOException; | ||||
import java.text.MessageFormat; | import java.text.MessageFormat; | ||||
import org.eclipse.jgit.annotations.Nullable; | |||||
import org.eclipse.jgit.internal.JGitText; | import org.eclipse.jgit.internal.JGitText; | ||||
import org.eclipse.jgit.lib.AnyObjectId; | import org.eclipse.jgit.lib.AnyObjectId; | ||||
import org.eclipse.jgit.lib.ObjectChecker; | |||||
import org.eclipse.jgit.lib.ObjectId; | import org.eclipse.jgit.lib.ObjectId; | ||||
/** | /** | ||||
public class CorruptObjectException extends IOException { | public class CorruptObjectException extends IOException { | ||||
private static final long serialVersionUID = 1L; | private static final long serialVersionUID = 1L; | ||||
private ObjectChecker.ErrorType errorType; | |||||
/** | |||||
* Report a specific error condition discovered in an object. | |||||
* | |||||
* @param type | |||||
* type of error | |||||
* @param id | |||||
* identity of the bad object | |||||
* @param why | |||||
* description of the error. | |||||
* @since 4.2 | |||||
*/ | |||||
public CorruptObjectException(ObjectChecker.ErrorType type, AnyObjectId id, | |||||
String why) { | |||||
super(MessageFormat.format(JGitText.get().objectIsCorrupt3, | |||||
type.getMessageId(), id.name(), why)); | |||||
this.errorType = type; | |||||
} | |||||
/** | /** | ||||
* Construct a CorruptObjectException for reporting a problem specified | * Construct a CorruptObjectException for reporting a problem specified | ||||
* object id | * object id | ||||
* @param id | * @param id | ||||
* @param why | * @param why | ||||
*/ | */ | ||||
public CorruptObjectException(final AnyObjectId id, final String why) { | |||||
this(id.toObjectId(), why); | |||||
public CorruptObjectException(AnyObjectId id, String why) { | |||||
super(MessageFormat.format(JGitText.get().objectIsCorrupt, id.name(), why)); | |||||
} | } | ||||
/** | /** | ||||
* @param id | * @param id | ||||
* @param why | * @param why | ||||
*/ | */ | ||||
public CorruptObjectException(final ObjectId id, final String why) { | |||||
public CorruptObjectException(ObjectId id, String why) { | |||||
super(MessageFormat.format(JGitText.get().objectIsCorrupt, id.name(), why)); | super(MessageFormat.format(JGitText.get().objectIsCorrupt, id.name(), why)); | ||||
} | } | ||||
* | * | ||||
* @param why | * @param why | ||||
*/ | */ | ||||
public CorruptObjectException(final String why) { | |||||
public CorruptObjectException(String why) { | |||||
super(why); | super(why); | ||||
} | } | ||||
super(why); | super(why); | ||||
initCause(cause); | initCause(cause); | ||||
} | } | ||||
/** | |||||
* Specific error condition identified by {@link ObjectChecker}. | |||||
* | |||||
* @return error condition or null. | |||||
* @since 4.2 | |||||
*/ | |||||
@Nullable | |||||
public ObjectChecker.ErrorType getErrorType() { | |||||
return errorType; | |||||
} | |||||
} | } |
/***/ public String connectionTimeOut; | /***/ public String connectionTimeOut; | ||||
/***/ public String contextMustBeNonNegative; | /***/ public String contextMustBeNonNegative; | ||||
/***/ public String corruptionDetectedReReadingAt; | /***/ public String corruptionDetectedReReadingAt; | ||||
/***/ public String corruptObjectBadDate; | |||||
/***/ public String corruptObjectBadEmail; | |||||
/***/ public String corruptObjectBadStream; | /***/ public String corruptObjectBadStream; | ||||
/***/ public String corruptObjectBadStreamCorruptHeader; | /***/ public String corruptObjectBadStreamCorruptHeader; | ||||
/***/ public String corruptObjectBadTimezone; | |||||
/***/ public String corruptObjectDuplicateEntryNames; | /***/ public String corruptObjectDuplicateEntryNames; | ||||
/***/ public String corruptObjectGarbageAfterSize; | /***/ public String corruptObjectGarbageAfterSize; | ||||
/***/ public String corruptObjectIncorrectLength; | /***/ public String corruptObjectIncorrectLength; | ||||
/***/ public String corruptObjectIncorrectSorting; | /***/ public String corruptObjectIncorrectSorting; | ||||
/***/ public String corruptObjectInvalidAuthor; | |||||
/***/ public String corruptObjectInvalidCommitter; | |||||
/***/ public String corruptObjectInvalidEntryMode; | /***/ public String corruptObjectInvalidEntryMode; | ||||
/***/ public String corruptObjectInvalidMode; | /***/ public String corruptObjectInvalidMode; | ||||
/***/ public String corruptObjectInvalidModeChar; | /***/ public String corruptObjectInvalidModeChar; | ||||
/***/ public String corruptObjectInvalidNamePrn; | /***/ public String corruptObjectInvalidNamePrn; | ||||
/***/ public String corruptObjectInvalidObject; | /***/ public String corruptObjectInvalidObject; | ||||
/***/ public String corruptObjectInvalidParent; | /***/ public String corruptObjectInvalidParent; | ||||
/***/ public String corruptObjectInvalidTagger; | |||||
/***/ public String corruptObjectInvalidTree; | /***/ public String corruptObjectInvalidTree; | ||||
/***/ public String corruptObjectInvalidType; | /***/ public String corruptObjectInvalidType; | ||||
/***/ public String corruptObjectInvalidType2; | /***/ public String corruptObjectInvalidType2; | ||||
/***/ public String corruptObjectMalformedHeader; | /***/ public String corruptObjectMalformedHeader; | ||||
/***/ public String corruptObjectMissingEmail; | |||||
/***/ public String corruptObjectNameContainsByte; | /***/ public String corruptObjectNameContainsByte; | ||||
/***/ public String corruptObjectNameContainsChar; | /***/ public String corruptObjectNameContainsChar; | ||||
/***/ public String corruptObjectNameContainsNullByte; | /***/ public String corruptObjectNameContainsNullByte; | ||||
/***/ public String corruptObjectTruncatedInMode; | /***/ public String corruptObjectTruncatedInMode; | ||||
/***/ public String corruptObjectTruncatedInName; | /***/ public String corruptObjectTruncatedInName; | ||||
/***/ public String corruptObjectTruncatedInObjectId; | /***/ public String corruptObjectTruncatedInObjectId; | ||||
/***/ public String corruptObjectZeroId; | |||||
/***/ public String corruptPack; | /***/ public String corruptPack; | ||||
/***/ public String couldNotCheckOutBecauseOfConflicts; | /***/ public String couldNotCheckOutBecauseOfConflicts; | ||||
/***/ public String couldNotDeleteLockFileShouldNotHappen; | /***/ public String couldNotDeleteLockFileShouldNotHappen; | ||||
/***/ public String objectAtHasBadZlibStream; | /***/ public String objectAtHasBadZlibStream; | ||||
/***/ public String objectAtPathDoesNotHaveId; | /***/ public String objectAtPathDoesNotHaveId; | ||||
/***/ public String objectIsCorrupt; | /***/ public String objectIsCorrupt; | ||||
/***/ public String objectIsCorrupt3; | |||||
/***/ public String objectIsNotA; | /***/ public String objectIsNotA; | ||||
/***/ public String objectNotFound; | /***/ public String objectNotFound; | ||||
/***/ public String objectNotFoundIn; | /***/ public String objectNotFoundIn; |
package org.eclipse.jgit.lib; | package org.eclipse.jgit.lib; | ||||
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_BLOB; | ||||
import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT; | 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_TAG; | ||||
import static org.eclipse.jgit.lib.Constants.OBJ_TREE; | import static org.eclipse.jgit.lib.Constants.OBJ_TREE; | ||||
import static org.eclipse.jgit.util.RawParseUtils.match; | |||||
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.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. | ||||
/** 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$ | ||||
/** | |||||
* Potential issues identified by the checker. | |||||
* | |||||
* @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 final MutableObjectId tempId = new MutableObjectId(); | private final MutableObjectId tempId = new MutableObjectId(); | ||||
private final MutableInteger ptrout = new MutableInteger(); | |||||
private final MutableInteger bufPtr = new MutableInteger(); | |||||
private EnumSet<ErrorType> errors = EnumSet.allOf(ErrorType.class); | |||||
private ObjectIdSet skipList; | private ObjectIdSet skipList; | ||||
private boolean allowZeroMode; | |||||
private boolean allowInvalidPersonIdent; | private boolean allowInvalidPersonIdent; | ||||
private boolean windows; | private boolean windows; | ||||
private boolean macosx; | private boolean macosx; | ||||
return this; | 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> | ||||
* 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 this; | |||||
return setIgnore(ZERO_PADDED_FILEMODE, allow); | |||||
} | } | ||||
/** | /** | ||||
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); | |||||
return ptr + Constants.OBJECT_ID_STRING_LENGTH; | |||||
tempId.fromString(raw, p); | |||||
} 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(byte[] raw, int ptr, @Nullable AnyObjectId id) { | |||||
if (allowInvalidPersonIdent || skip(id)) | |||||
return nextLF(raw, ptr); | |||||
private void checkPersonIdent(byte[] raw, @Nullable AnyObjectId id) | |||||
throws CorruptObjectException { | |||||
if (allowInvalidPersonIdent) { | |||||
bufPtr.value = nextLF(raw, bufPtr.value); | |||||
return; | |||||
} | |||||
final int emailB = nextLF(raw, ptr, '<'); | |||||
if (emailB == ptr || raw[emailB - 1] != '<') | |||||
return -1; | |||||
final int emailB = nextLF(raw, bufPtr.value, '<'); | |||||
if (emailB == bufPtr.value || raw[emailB - 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] != '>') | |||||
return -1; | |||||
if (emailE == raw.length || raw[emailE] != ' ') | |||||
return -1; | |||||
if (emailE == emailB || raw[emailE - 1] != '>') { | |||||
report(BAD_EMAIL, id, JGitText.get().corruptObjectBadEmail); | |||||
bufPtr.value = nextLF(raw, bufPtr.value); | |||||
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 | |||||
ptr = ptrout.value; | |||||
if (emailE + 1 == ptr) | |||||
return -1; | |||||
if (ptr == raw.length || raw[ptr] != ' ') | |||||
return -1; | |||||
parseBase10(raw, emailE + 1, bufPtr); // when | |||||
if (emailE + 1 == bufPtr.value || bufPtr.value == raw.length | |||||
|| raw[bufPtr.value] != ' ') { | |||||
report(BAD_DATE, id, JGitText.get().corruptObjectBadDate); | |||||
bufPtr.value = nextLF(raw, bufPtr.value); | |||||
return; | |||||
} | |||||
parseBase10(raw, ptr + 1, ptrout); // tz offset | |||||
if (ptr + 1 == ptrout.value) | |||||
return -1; | |||||
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; | |||||
} | |||||
ptr = ptrout.value; | |||||
if (raw[ptr++] == '\n') | |||||
return ptr; | |||||
return -1; | |||||
p = bufPtr.value; | |||||
if (raw[p] == '\n') { | |||||
bufPtr.value = p + 1; | |||||
} else { | |||||
report(BAD_TIMEZONE, id, JGitText.get().corruptObjectBadTimezone); | |||||
bufPtr.value = nextLF(raw, p); | |||||
} | |||||
} | } | ||||
/** | /** | ||||
*/ | */ | ||||
public void checkCommit(@Nullable AnyObjectId id, byte[] raw) | public void checkCommit(@Nullable AnyObjectId id, byte[] raw) | ||||
throws CorruptObjectException { | throws CorruptObjectException { | ||||
int ptr = 0; | |||||
bufPtr.value = 0; | |||||
if ((ptr = match(raw, ptr, tree)) < 0) | |||||
throw new CorruptObjectException( | |||||
JGitText.get().corruptObjectNotreeHeader); | |||||
if ((ptr = id(raw, ptr)) < 0 || raw[ptr++] != '\n') | |||||
throw new CorruptObjectException( | |||||
JGitText.get().corruptObjectInvalidTree); | |||||
if (!match(raw, tree)) { | |||||
report(MISSING_TREE, id, JGitText.get().corruptObjectNotreeHeader); | |||||
} else if (!checkId(raw)) { | |||||
report(BAD_TREE_SHA1, id, JGitText.get().corruptObjectInvalidTree); | |||||
} | |||||
while (match(raw, ptr, parent) >= 0) { | |||||
ptr += parent.length; | |||||
if ((ptr = id(raw, ptr)) < 0 || raw[ptr++] != '\n') | |||||
throw new CorruptObjectException( | |||||
while (match(raw, parent)) { | |||||
if (!checkId(raw)) { | |||||
report(BAD_PARENT_SHA1, id, | |||||
JGitText.get().corruptObjectInvalidParent); | JGitText.get().corruptObjectInvalidParent); | ||||
} | |||||
} | } | ||||
int p = match(raw, ptr, author); | |||||
if (p > ptr) { | |||||
if ((ptr = personIdent(raw, p, id)) < 0) { | |||||
throw new CorruptObjectException( | |||||
JGitText.get().corruptObjectInvalidAuthor); | |||||
} | |||||
} else if (!skip(id)) { | |||||
throw new CorruptObjectException( | |||||
JGitText.get().corruptObjectNoAuthor); | |||||
if (match(raw, author)) { | |||||
checkPersonIdent(raw, id); | |||||
} else { | |||||
report(MISSING_AUTHOR, id, JGitText.get().corruptObjectNoAuthor); | |||||
} | } | ||||
p = match(raw, ptr, committer); | |||||
if (p > ptr) { | |||||
if ((ptr = personIdent(raw, p, id)) < 0) { | |||||
throw new CorruptObjectException( | |||||
JGitText.get().corruptObjectInvalidCommitter); | |||||
} | |||||
} else if (!skip(id)) { | |||||
throw new CorruptObjectException( | |||||
if (match(raw, committer)) { | |||||
checkPersonIdent(raw, id); | |||||
} else { | |||||
report(MISSING_COMMITTER, id, | |||||
JGitText.get().corruptObjectNoCommitter); | JGitText.get().corruptObjectNoCommitter); | ||||
} | } | ||||
} | } | ||||
*/ | */ | ||||
public void checkTag(@Nullable AnyObjectId id, byte[] raw) | public void checkTag(@Nullable AnyObjectId id, byte[] raw) | ||||
throws CorruptObjectException { | throws CorruptObjectException { | ||||
int ptr = 0; | |||||
if ((ptr = match(raw, ptr, object)) < 0) | |||||
throw new 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') | |||||
throw new CorruptObjectException( | |||||
} else if (!checkId(raw)) { | |||||
report(BAD_OBJECT_SHA1, id, | |||||
JGitText.get().corruptObjectInvalidObject); | JGitText.get().corruptObjectInvalidObject); | ||||
} | |||||
if ((ptr = match(raw, ptr, type)) < 0) | |||||
throw new CorruptObjectException( | |||||
if (!match(raw, type)) { | |||||
report(MISSING_TYPE_ENTRY, id, | |||||
JGitText.get().corruptObjectNoTypeHeader); | JGitText.get().corruptObjectNoTypeHeader); | ||||
ptr = nextLF(raw, ptr); | |||||
} | |||||
bufPtr.value = nextLF(raw, bufPtr.value); | |||||
if (match(raw, ptr, tag) < 0 && !skip(id)) | |||||
throw new CorruptObjectException( | |||||
if (!match(raw, tag)) { | |||||
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 ((ptr = personIdent(raw, ptr, id)) < 0) { | |||||
throw new CorruptObjectException( | |||||
JGitText.get().corruptObjectInvalidTagger); | |||||
} | |||||
if (match(raw, tagger)) { | |||||
checkPersonIdent(raw, id); | |||||
} | } | ||||
} | } | ||||
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; | ||||
boolean skip = skip(id); | |||||
Set<String> normalized = !skip && (windows || macosx) | |||||
Set<String> normalized = windows || macosx | |||||
? new HashSet<String>() | ? new HashSet<String>() | ||||
: null; | : null; | ||||
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 && !skip) | |||||
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); | |||||
if (ptr == sz || raw[ptr] != 0) | |||||
ptr = scanPathSegment(raw, ptr, sz, id); | |||||
if (ptr == sz || raw[ptr] != 0) { | |||||
throw new CorruptObjectException( | throw new CorruptObjectException( | ||||
JGitText.get().corruptObjectTruncatedInName); | JGitText.get().corruptObjectTruncatedInName); | ||||
checkPathSegment2(raw, thisNameB, ptr, skip); | |||||
} | |||||
checkPathSegment2(raw, thisNameB, ptr, id); | |||||
if (normalized != null) { | if (normalized != null) { | ||||
if (!normalized.add(normalize(raw, thisNameB, ptr))) | |||||
throw new CorruptObjectException( | |||||
if (!normalized.add(normalize(raw, thisNameB, ptr))) { | |||||
report(DUPLICATE_ENTRIES, id, | |||||
JGitText.get().corruptObjectDuplicateEntryNames); | JGitText.get().corruptObjectDuplicateEntryNames); | ||||
} else if (!skip && duplicateName(raw, thisNameB, ptr)) | |||||
throw new CorruptObjectException( | |||||
} | |||||
} else if (duplicateName(raw, thisNameB, ptr)) { | |||||
report(DUPLICATE_ENTRIES, id, | |||||
JGitText.get().corruptObjectDuplicateEntryNames); | JGitText.get().corruptObjectDuplicateEntryNames); | ||||
} | |||||
if (!skip && 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) | |||||
throw new CorruptObjectException( | |||||
if (cmp > 0) { | |||||
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; | |||||
if (ptr > sz) | |||||
ptr += 1 + OBJECT_ID_LENGTH; | |||||
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) | |||||
throws CorruptObjectException { | |||||
private int scanPathSegment(byte[] raw, int ptr, int end, | |||||
@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))); | ||||
return null; | return null; | ||||
} | } | ||||
private boolean skip(@Nullable AnyObjectId id) { | |||||
return skipList != null && id != null && skipList.contains(id); | |||||
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); | |||||
} | |||||
} | } | ||||
/** | /** | ||||
*/ | */ | ||||
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, false); | |||||
checkPathSegment2(raw, ptr, end, null); | |||||
} | } | ||||
private void checkPathSegment2(byte[] raw, int ptr, int end, boolean skip) | |||||
throws CorruptObjectException { | |||||
if (ptr == end) | |||||
throw new CorruptObjectException( | |||||
JGitText.get().corruptObjectNameZeroLength); | |||||
private void checkPathSegment2(byte[] raw, int ptr, int end, | |||||
@Nullable AnyObjectId id) throws CorruptObjectException { | |||||
if (ptr == end) { | |||||
report(EMPTY_NAME, id, JGitText.get().corruptObjectNameZeroLength); | |||||
return; | |||||
} | |||||
if (raw[ptr] == '.') { | if (raw[ptr] == '.') { | ||||
switch (end - ptr) { | switch (end - ptr) { | ||||
case 1: | case 1: | ||||
throw new CorruptObjectException( | |||||
JGitText.get().corruptObjectNameDot); | |||||
report(HAS_DOT, id, JGitText.get().corruptObjectNameDot); | |||||
break; | |||||
case 2: | case 2: | ||||
if (raw[ptr + 1] == '.') | |||||
throw new CorruptObjectException( | |||||
if (raw[ptr + 1] == '.') { | |||||
report(HAS_DOTDOT, id, | |||||
JGitText.get().corruptObjectNameDotDot); | JGitText.get().corruptObjectNameDotDot); | ||||
} | |||||
break; | break; | ||||
case 4: | case 4: | ||||
if (!skip && isGit(raw, ptr + 1)) | |||||
throw new CorruptObjectException(String.format( | |||||
if (isGit(raw, ptr + 1)) { | |||||
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 (!skip && end - ptr > 4 | |||||
&& isNormalizedGit(raw, ptr + 1, end)) | |||||
throw new CorruptObjectException(String.format( | |||||
if (end - ptr > 4 && isNormalizedGit(raw, ptr + 1, end)) { | |||||
report(HAS_DOTGIT, id, String.format( | |||||
JGitText.get().corruptObjectInvalidName, | JGitText.get().corruptObjectInvalidName, | ||||
RawParseUtils.decode(raw, ptr, end))); | RawParseUtils.decode(raw, ptr, end))); | ||||
} | |||||
} | } | ||||
} else if (!skip && isGitTilde1(raw, ptr, end)) { | |||||
throw new CorruptObjectException(String.format( | |||||
} else if (isGitTilde1(raw, ptr, end)) { | |||||
report(HAS_DOTGIT, id, String.format( | |||||
JGitText.get().corruptObjectInvalidName, | JGitText.get().corruptObjectInvalidName, | ||||
RawParseUtils.decode(raw, ptr, end))); | RawParseUtils.decode(raw, ptr, end))); | ||||
} | } | ||||
if (!skip) { | |||||
if (macosx && isMacHFSGit(raw, ptr, end)) | |||||
throw new CorruptObjectException(String.format( | |||||
JGitText.get().corruptObjectInvalidNameIgnorableUnicode, | |||||
RawParseUtils.decode(raw, ptr, end))); | |||||
if (macosx && isMacHFSGit(raw, ptr, end, id)) { | |||||
report(HAS_DOTGIT, id, String.format( | |||||
JGitText.get().corruptObjectInvalidNameIgnorableUnicode, | |||||
RawParseUtils.decode(raw, ptr, end))); | |||||
} | |||||
if (windows) { | |||||
// Windows ignores space and dot at end of file name. | |||||
if (raw[end - 1] == ' ' || raw[end - 1] == '.') | |||||
throw new CorruptObjectException(String.format( | |||||
JGitText.get().corruptObjectInvalidNameEnd, | |||||
Character.valueOf(((char) raw[end - 1])))); | |||||
if (end - ptr >= 3) | |||||
checkNotWindowsDevice(raw, ptr, end); | |||||
if (windows) { | |||||
// Windows ignores space and dot at end of file name. | |||||
if (raw[end - 1] == ' ' || raw[end - 1] == '.') { | |||||
report(WIN32_BAD_NAME, id, String.format( | |||||
JGitText.get().corruptObjectInvalidNameEnd, | |||||
Character.valueOf(((char) raw[end - 1])))); | |||||
} | |||||
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) | |||||
throws CorruptObjectException { | |||||
private boolean isMacHFSGit(byte[] raw, int ptr, int end, | |||||
@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]) { | ||||
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)) { | ||||
return false; | return false; | ||||
} | } | ||||
private static void checkTruncatedIgnorableUTF8(byte[] raw, int ptr, int end) | |||||
throws CorruptObjectException { | |||||
if ((ptr + 2) >= end) | |||||
throw new CorruptObjectException(MessageFormat.format( | |||||
private boolean checkTruncatedIgnorableUTF8(byte[] raw, int ptr, int end, | |||||
@Nullable AnyObjectId id) throws CorruptObjectException { | |||||
if ((ptr + 2) >= end) { | |||||
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) { | ||||
return b.toString(); | return b.toString(); | ||||
} | } | ||||
private static void checkNotWindowsDevice(byte[] raw, int ptr, int end) | |||||
throws CorruptObjectException { | |||||
private void checkNotWindowsDevice(byte[] raw, int ptr, int end, | |||||
@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] == '.')) | |||||
throw new CorruptObjectException( | |||||
&& (end - ptr == 3 || raw[ptr + 3] == '.')) { | |||||
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] == '.')) | |||||
throw new CorruptObjectException( | |||||
&& (end - ptr == 3 || raw[ptr + 3] == '.')) { | |||||
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] == '.')) | |||||
throw new CorruptObjectException(String.format( | |||||
&& (end - ptr == 4 || raw[ptr + 4] == '.')) { | |||||
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] | ||||
&& 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] == '.')) | |||||
throw new CorruptObjectException(String.format( | |||||
&& (end - ptr == 4 || raw[ptr + 4] == '.')) { | |||||
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] == '.')) | |||||
throw new CorruptObjectException( | |||||
&& (end - ptr == 3 || raw[ptr + 3] == '.')) { | |||||
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] == '.')) | |||||
throw new CorruptObjectException( | |||||
&& (end - ptr == 3 || raw[ptr + 3] == '.')) { | |||||
report(WIN32_BAD_NAME, id, | |||||
JGitText.get().corruptObjectInvalidNamePrn); | JGitText.get().corruptObjectInvalidNamePrn); | ||||
} | |||||
break; | break; | ||||
} | } | ||||
} | } | ||||
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')); |
try { | try { | ||||
objCheck.check(id, type, data); | objCheck.check(id, type, data); | ||||
} catch (CorruptObjectException e) { | } catch (CorruptObjectException e) { | ||||
if (e.getErrorType() != null) { | |||||
throw e; | |||||
} | |||||
throw new CorruptObjectException(MessageFormat.format( | throw new CorruptObjectException(MessageFormat.format( | ||||
JGitText.get().invalidObject, | JGitText.get().invalidObject, | ||||
Constants.typeString(type), | Constants.typeString(type), |
package org.eclipse.jgit.transport; | package org.eclipse.jgit.transport; | ||||
import static org.eclipse.jgit.util.StringUtils.equalsIgnoreCase; | |||||
import static org.eclipse.jgit.util.StringUtils.toLowerCase; | |||||
import java.io.File; | import java.io.File; | ||||
import java.util.EnumSet; | |||||
import java.util.HashMap; | import java.util.HashMap; | ||||
import java.util.Map; | import java.util.Map; | ||||
* parameters. | * parameters. | ||||
*/ | */ | ||||
public class TransferConfig { | public class TransferConfig { | ||||
private static final String FSCK = "fsck"; //$NON-NLS-1$ | |||||
/** Key for {@link Config#get(SectionParser)}. */ | /** Key for {@link Config#get(SectionParser)}. */ | ||||
public static final Config.SectionParser<TransferConfig> KEY = new SectionParser<TransferConfig>() { | public static final Config.SectionParser<TransferConfig> KEY = new SectionParser<TransferConfig>() { | ||||
public TransferConfig parse(final Config cfg) { | public TransferConfig parse(final Config cfg) { | ||||
} | } | ||||
}; | }; | ||||
enum FsckMode { | |||||
ERROR, WARN, IGNORE; | |||||
} | |||||
private final boolean fetchFsck; | private final boolean fetchFsck; | ||||
private final boolean receiveFsck; | private final boolean receiveFsck; | ||||
private final String fsckSkipList; | private final String fsckSkipList; | ||||
private final boolean allowLeadingZeroFileMode; | |||||
private final EnumSet<ObjectChecker.ErrorType> ignore; | |||||
private final boolean allowInvalidPersonIdent; | private final boolean allowInvalidPersonIdent; | ||||
private final boolean safeForWindows; | private final boolean safeForWindows; | ||||
private final boolean safeForMacOS; | private final boolean safeForMacOS; | ||||
boolean fsck = rc.getBoolean("transfer", "fsckobjects", false); //$NON-NLS-1$ //$NON-NLS-2$ | boolean fsck = rc.getBoolean("transfer", "fsckobjects", false); //$NON-NLS-1$ //$NON-NLS-2$ | ||||
fetchFsck = rc.getBoolean("fetch", "fsckobjects", fsck); //$NON-NLS-1$ //$NON-NLS-2$ | fetchFsck = rc.getBoolean("fetch", "fsckobjects", fsck); //$NON-NLS-1$ //$NON-NLS-2$ | ||||
receiveFsck = rc.getBoolean("receive", "fsckobjects", fsck); //$NON-NLS-1$ //$NON-NLS-2$ | receiveFsck = rc.getBoolean("receive", "fsckobjects", fsck); //$NON-NLS-1$ //$NON-NLS-2$ | ||||
fsckSkipList = rc.getString("fsck", null, "skipList"); //$NON-NLS-1$ //$NON-NLS-2$ | |||||
allowLeadingZeroFileMode = rc.getBoolean("fsck", "allowLeadingZeroFileMode", false); //$NON-NLS-1$ //$NON-NLS-2$ | |||||
allowInvalidPersonIdent = rc.getBoolean("fsck", "allowInvalidPersonIdent", false); //$NON-NLS-1$ //$NON-NLS-2$ | |||||
safeForWindows = rc.getBoolean("fsck", "safeForWindows", //$NON-NLS-1$ //$NON-NLS-2$ | |||||
fsckSkipList = rc.getString(FSCK, null, "skipList"); //$NON-NLS-1$ | |||||
allowInvalidPersonIdent = rc.getBoolean(FSCK, "allowInvalidPersonIdent", false); //$NON-NLS-1$ | |||||
safeForWindows = rc.getBoolean(FSCK, "safeForWindows", //$NON-NLS-1$ | |||||
SystemReader.getInstance().isWindows()); | SystemReader.getInstance().isWindows()); | ||||
safeForMacOS = rc.getBoolean("fsck", "safeForMacOS", //$NON-NLS-1$ //$NON-NLS-2$ | |||||
safeForMacOS = rc.getBoolean(FSCK, "safeForMacOS", //$NON-NLS-1$ | |||||
SystemReader.getInstance().isMacOS()); | SystemReader.getInstance().isMacOS()); | ||||
ignore = EnumSet.noneOf(ObjectChecker.ErrorType.class); | |||||
EnumSet<ObjectChecker.ErrorType> set = EnumSet | |||||
.noneOf(ObjectChecker.ErrorType.class); | |||||
for (String key : rc.getNames(FSCK)) { | |||||
if (equalsIgnoreCase(key, "skipList") //$NON-NLS-1$ | |||||
|| equalsIgnoreCase(key, "allowLeadingZeroFileMode") //$NON-NLS-1$ | |||||
|| equalsIgnoreCase(key, "allowInvalidPersonIdent") //$NON-NLS-1$ | |||||
|| equalsIgnoreCase(key, "safeForWindows") //$NON-NLS-1$ | |||||
|| equalsIgnoreCase(key, "safeForMacOS")) { //$NON-NLS-1$ | |||||
continue; | |||||
} | |||||
ObjectChecker.ErrorType id = FsckKeyNameHolder.parse(key); | |||||
if (id != null) { | |||||
switch (rc.getEnum(FSCK, null, key, FsckMode.ERROR)) { | |||||
case ERROR: | |||||
ignore.remove(id); | |||||
break; | |||||
case WARN: | |||||
case IGNORE: | |||||
ignore.add(id); | |||||
break; | |||||
} | |||||
set.add(id); | |||||
} | |||||
} | |||||
if (!set.contains(ObjectChecker.ErrorType.ZERO_PADDED_FILEMODE) | |||||
&& rc.getBoolean(FSCK, "allowLeadingZeroFileMode", false)) { //$NON-NLS-1$ | |||||
ignore.add(ObjectChecker.ErrorType.ZERO_PADDED_FILEMODE); | |||||
} | |||||
allowTipSha1InWant = rc.getBoolean( | allowTipSha1InWant = rc.getBoolean( | ||||
"uploadpack", "allowtipsha1inwant", false); //$NON-NLS-1$ //$NON-NLS-2$ | "uploadpack", "allowtipsha1inwant", false); //$NON-NLS-1$ //$NON-NLS-2$ | ||||
allowReachableSha1InWant = rc.getBoolean( | allowReachableSha1InWant = rc.getBoolean( | ||||
return null; | return null; | ||||
} | } | ||||
return new ObjectChecker() | return new ObjectChecker() | ||||
.setAllowLeadingZeroFileMode(allowLeadingZeroFileMode) | |||||
.setIgnore(ignore) | |||||
.setAllowInvalidPersonIdent(allowInvalidPersonIdent) | .setAllowInvalidPersonIdent(allowInvalidPersonIdent) | ||||
.setSafeForWindows(safeForWindows) | .setSafeForWindows(safeForWindows) | ||||
.setSafeForMacOS(safeForMacOS) | .setSafeForMacOS(safeForMacOS) | ||||
} | } | ||||
}; | }; | ||||
} | } | ||||
static class FsckKeyNameHolder { | |||||
private static final Map<String, ObjectChecker.ErrorType> errors; | |||||
static { | |||||
errors = new HashMap<>(); | |||||
for (ObjectChecker.ErrorType m : ObjectChecker.ErrorType.values()) { | |||||
errors.put(keyNameFor(m.name()), m); | |||||
} | |||||
} | |||||
@Nullable | |||||
static ObjectChecker.ErrorType parse(String key) { | |||||
return errors.get(toLowerCase(key)); | |||||
} | |||||
private static String keyNameFor(String name) { | |||||
StringBuilder r = new StringBuilder(name.length()); | |||||
for (int i = 0; i < name.length(); i++) { | |||||
char c = name.charAt(i); | |||||
if (c != '_') { | |||||
r.append(c); | |||||
} | |||||
} | |||||
return toLowerCase(r.toString()); | |||||
} | |||||
private FsckKeyNameHolder() { | |||||
} | |||||
} | |||||
} | } |