ObjectChecker: Disallow Windows shortname "GIT~1"

Windows creates shortnames for all non-8.3 files (see [1]). Hence we
need to disallow all names which could potentially be a shortname for
".git". Example: in an empty directory create a folder "GIT~1". Now you
can't create another folder ".git".

The path "GIT~1" may map to ".git" on Windows. A potential victim to
such an attack first has to initialize a git repository in order to
receive any git commits. Hence the .git folder created by init will get
the shortname "GIT~1". ".git" will only get a different shortname if the
user has created a file "GIT~1" before initialization of the git
repository.

[1] http://en.wikipedia.org/wiki/8.3_filename

Change-Id: I9978ab8f2d2951c46c1b9bbde57986d64d26b9b2
Signed-off-by: Christian Halstrick <christian.halstrick@sap.com>
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java
index aa17ac3..8a782b7 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java
@@ -1404,6 +1404,40 @@ public void testInvalidTreeNameIsDotGitSpaceDot() {
 	}
 
 	@Test
+	public void testInvalidTreeNameIsGITTilde1() {
+		StringBuilder b = new StringBuilder();
+		entry(b, "100644 GIT~1");
+		byte[] data = Constants.encodeASCII(b.toString());
+		try {
+			checker.checkTree(data);
+			fail("incorrectly accepted an invalid tree");
+		} catch (CorruptObjectException e) {
+			assertEquals("invalid name 'GIT~1'", e.getMessage());
+		}
+	}
+
+	@Test
+	public void testInvalidTreeNameIsGiTTilde1() {
+		StringBuilder b = new StringBuilder();
+		entry(b, "100644 GiT~1");
+		byte[] data = Constants.encodeASCII(b.toString());
+		try {
+			checker.checkTree(data);
+			fail("incorrectly accepted an invalid tree");
+		} catch (CorruptObjectException e) {
+			assertEquals("invalid name 'GiT~1'", e.getMessage());
+		}
+	}
+
+	@Test
+	public void testValidTreeNameIsGitTilde11() throws CorruptObjectException {
+		StringBuilder b = new StringBuilder();
+		entry(b, "100644 GIT~11");
+		byte[] data = Constants.encodeASCII(b.toString());
+		checker.checkTree(data);
+	}
+
+	@Test
 	public void testInvalidTreeTruncatedInName() {
 		final StringBuilder b = new StringBuilder();
 		b.append("100644 b");
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java
index 1e4163e..4913c44 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java
@@ -464,6 +464,9 @@ private void checkPathSegment2(byte[] raw, int ptr, int end)
 							"invalid name '%s'",
 							RawParseUtils.decode(raw, ptr, end)));
 			}
+		} else if (isGitTilde1(raw, ptr, end)) {
+			throw new CorruptObjectException(String.format("invalid name '%s'",
+					RawParseUtils.decode(raw, ptr, end)));
 		}
 
 		if (windows) {
@@ -552,6 +555,14 @@ && toLower(buf[p + 1]) == 'i'
 				&& toLower(buf[p + 2]) == 't';
 	}
 
+	private static boolean isGitTilde1(byte[] buf, int p, int end) {
+		if (end - p != 5)
+			return false;
+		return toLower(buf[p]) == 'g' && toLower(buf[p + 1]) == 'i'
+				&& toLower(buf[p + 2]) == 't' && buf[p + 3] == '~'
+				&& buf[p + 4] == '1';
+	}
+
 	private static boolean isNormalizedGit(byte[] raw, int ptr, int end) {
 		if (isGit(raw, ptr)) {
 			int dots = 0;