summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRobin Rosenberg <robin.rosenberg@dewire.com>2012-04-23 22:37:50 +0200
committerMatthias Sohn <matthias.sohn@sap.com>2012-04-23 22:37:50 +0200
commit42d7565ba9b97effdee3f737e32541b26b7341ab (patch)
tree397809a3509520df69cb068791585cf1a77637d9
parent9c5b31703f278c510bec64fa7a822713feaca6f2 (diff)
downloadjgit-42d7565ba9b97effdee3f737e32541b26b7341ab.tar.gz
jgit-42d7565ba9b97effdee3f737e32541b26b7341ab.zip
Validate paths during DirCheckout
DirCacheCheckout and CanonicalTreeParser cooperate. CanonicalTreeParser can detect malformed, potentially malicious tree entries and sets a flag, while DirCacheCheckout refuses to work with such paths. Malicious tree entries are ".", "..", ".git" (case insensitive), any name containing '/' and (on Windows '\') and also (on Windows) any paths ending in a combination of '.' or space or containing a ':'. We also forbid all special names like "con" etc on Windows. Some of the test can execute on any platform by enabling partial platform emulation. A new runtime exception, InvalidPathException, is introduced. For backwards compatibility it extends InvalidArgumentException. Change-Id: I86199105814b63d4340e5de0e471d0da6b579ead Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
-rw-r--r--org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java1
-rw-r--r--org.eclipse.jgit.test/META-INF/MANIFEST.MF1
-rw-r--r--org.eclipse.jgit.test/pom.xml7
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutMaliciousPathTest.java420
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java110
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java13
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/dircache/InvalidPathException.java63
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java20
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java8
9 files changed, 635 insertions, 8 deletions
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java
index 23bf5632cc..deca341067 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java
@@ -92,6 +92,7 @@ public class MockSystemReader extends SystemReader {
init(Constants.GIT_COMMITTER_EMAIL_KEY);
userGitConfig = new MockConfig(null, null);
systemGitConfig = new MockConfig(null, null);
+ setCurrentPlatform();
}
private void init(final String n) {
diff --git a/org.eclipse.jgit.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.test/META-INF/MANIFEST.MF
index 05b524d381..5db483b1fa 100644
--- a/org.eclipse.jgit.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.test/META-INF/MANIFEST.MF
@@ -40,6 +40,7 @@ Import-Package: org.eclipse.jgit.api;version="[2.0.0,2.1.0)",
org.eclipse.jgit.util;version="[2.0.0,2.1.0)",
org.eclipse.jgit.util.io;version="[2.0.0,2.1.0)",
org.hamcrest;version="[1.1.0,2.0.0)",
+ org.hamcrest.text.pattern;version="[1.1.0,2.0.0)",
org.junit;version="[4.4.0,5.0.0)",
org.junit.experimental.theories;version="[4.4.0,5.0.0)",
org.junit.runner;version="[4.4.0,5.0.0)"
diff --git a/org.eclipse.jgit.test/pom.xml b/org.eclipse.jgit.test/pom.xml
index 6d5f38acb7..969e205b0b 100644
--- a/org.eclipse.jgit.test/pom.xml
+++ b/org.eclipse.jgit.test/pom.xml
@@ -70,6 +70,13 @@
</dependency>
<dependency>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest-library</artifactId>
+ <scope>test</scope>
+ <version>[1.1.0,2.0.0)</version>
+ </dependency>
+
+ <dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit</artifactId>
<version>${project.version}</version>
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutMaliciousPathTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutMaliciousPathTest.java
new file mode 100644
index 0000000000..65d0418b33
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutMaliciousPathTest.java
@@ -0,0 +1,420 @@
+/*
+ * Copyright (C) 2011, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v1.0 which accompanies this
+ * distribution, is reproduced below, and is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.lib;
+
+import static org.hamcrest.Matchers.startsWith;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.CheckoutConflictException;
+import org.eclipse.jgit.api.errors.InvalidRefNameException;
+import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.api.errors.RefAlreadyExistsException;
+import org.eclipse.jgit.api.errors.RefNotFoundException;
+import org.eclipse.jgit.dircache.InvalidPathException;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.junit.MockSystemReader;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.util.SystemReader;
+import org.junit.Test;
+
+public class DirCacheCheckoutMaliciousPathTest extends RepositoryTestCase {
+ protected ObjectId theHead;
+ protected ObjectId theMerge;
+
+ @Test
+ public void testMaliciousAbsolutePathIsOk() throws Exception {
+ testMaliciousPathGoodFirstCheckout("ok");
+ }
+
+ @Test
+ public void testMaliciousAbsolutePathIsOkSecondCheckout() throws Exception {
+ testMaliciousPathGoodSecondCheckout("ok");
+ }
+
+ @Test
+ public void testMaliciousAbsolutePathIsOkTwoLevels() throws Exception {
+ testMaliciousPathGoodSecondCheckout("a", "ok");
+ }
+
+ @Test
+ public void testMaliciousAbsolutePath() throws Exception {
+ testMaliciousPathBadFirstCheckout("/tmp/x");
+ }
+
+ @Test
+ public void testMaliciousAbsolutePathSecondCheckout() throws Exception {
+ testMaliciousPathBadSecondCheckout("/tmp/x");
+ }
+
+ @Test
+ public void testMaliciousAbsolutePathTwoLevelsFirstBad() throws Exception {
+ testMaliciousPathBadFirstCheckout("/tmp/x", "y");
+ }
+
+ @Test
+ public void testMaliciousAbsolutePathTwoLevelsSecondBad() throws Exception {
+ testMaliciousPathBadFirstCheckout("y", "/tmp/x");
+ }
+
+ @Test
+ public void testMaliciousAbsoluteCurDrivePathWindows() throws Exception {
+ ((MockSystemReader) SystemReader.getInstance()).setWindows();
+ testMaliciousPathBadFirstCheckout("\\somepath");
+ }
+
+ @Test
+ public void testMaliciousAbsoluteCurDrivePathWindowsOnUnix()
+ throws Exception {
+ ((MockSystemReader) SystemReader.getInstance()).setUnix();
+ testMaliciousPathGoodFirstCheckout("\\somepath");
+ }
+
+ @Test
+ public void testMaliciousAbsoluteUNCPathWindows1() throws Exception {
+ ((MockSystemReader) SystemReader.getInstance()).setWindows();
+ testMaliciousPathBadFirstCheckout("\\\\somepath");
+ }
+
+ @Test
+ public void testMaliciousAbsoluteUNCPathWindows1OnUnix() throws Exception {
+ ((MockSystemReader) SystemReader.getInstance()).setUnix();
+ testMaliciousPathGoodFirstCheckout("\\\\somepath");
+ }
+
+ @Test
+ public void testMaliciousAbsoluteUNCPathWindows2() throws Exception {
+ ((MockSystemReader) SystemReader.getInstance()).setWindows();
+ testMaliciousPathBadFirstCheckout("\\/somepath");
+ }
+
+ @Test
+ public void testMaliciousAbsoluteUNCPathWindows2OnUnix() throws Exception {
+ ((MockSystemReader) SystemReader.getInstance()).setUnix();
+ testMaliciousPathBadFirstCheckout("\\/somepath");
+ }
+
+ @Test
+ public void testMaliciousAbsoluteWindowsPath1() throws Exception {
+ ((MockSystemReader) SystemReader.getInstance()).setWindows();
+ testMaliciousPathBadFirstCheckout("c:\\temp\\x");
+ }
+
+ @Test
+ public void testMaliciousAbsoluteWindowsPath1OnUnix() throws Exception {
+ if (File.separatorChar == '\\')
+ return; // cannot emulate Unix on Windows for this test
+ ((MockSystemReader) SystemReader.getInstance()).setUnix();
+ testMaliciousPathGoodFirstCheckout("c:\\temp\\x");
+ }
+
+ @Test
+ public void testMaliciousAbsoluteWindowsPath2() throws Exception {
+ ((MockSystemReader) SystemReader.getInstance()).setCurrentPlatform();
+ testMaliciousPathBadFirstCheckout("c:/temp/x");
+ }
+
+ @Test
+ public void testMaliciousGitPath1() throws Exception {
+ testMaliciousPathBadFirstCheckout(".git/konfig");
+ }
+
+ @Test
+ public void testMaliciousGitPath2() throws Exception {
+ testMaliciousPathBadFirstCheckout(".git", "konfig");
+ }
+
+ @Test
+ public void testMaliciousGitPath1Case() throws Exception {
+ ((MockSystemReader) SystemReader.getInstance()).setWindows(); // or OS X
+ testMaliciousPathBadFirstCheckout(".Git/konfig");
+ }
+
+ @Test
+ public void testMaliciousGitPath2Case() throws Exception {
+ ((MockSystemReader) SystemReader.getInstance()).setWindows(); // or OS X
+ testMaliciousPathBadFirstCheckout(".gIt", "konfig");
+ }
+
+ @Test
+ public void testMaliciousGitPath3Case() throws Exception {
+ ((MockSystemReader) SystemReader.getInstance()).setWindows(); // or OS X
+ testMaliciousPathBadFirstCheckout(".giT", "konfig");
+ }
+
+ @Test
+ public void testMaliciousGitPathEndSpaceWindows() throws Exception {
+ ((MockSystemReader) SystemReader.getInstance()).setWindows();
+ testMaliciousPathBadFirstCheckout(".git ", "konfig");
+ }
+
+ @Test
+ public void testMaliciousGitPathEndSpaceUnixOk() throws Exception {
+ if (File.separatorChar == '\\')
+ return; // cannot emulate Unix on Windows for this test
+ ((MockSystemReader) SystemReader.getInstance()).setUnix();
+ testMaliciousPathGoodFirstCheckout(".git ", "konfig");
+ }
+
+ @Test
+ public void testMaliciousGitPathEndDotWindows1() throws Exception {
+ ((MockSystemReader) SystemReader.getInstance()).setWindows();
+ testMaliciousPathBadFirstCheckout(".git.", "konfig");
+ }
+
+ @Test
+ public void testMaliciousGitPathEndDotWindows2() throws Exception {
+ ((MockSystemReader) SystemReader.getInstance()).setWindows();
+ testMaliciousPathBadFirstCheckout(".f.");
+ }
+
+ @Test
+ public void testMaliciousGitPathEndDotWindows3() throws Exception {
+ ((MockSystemReader) SystemReader.getInstance()).setWindows();
+ testMaliciousPathGoodFirstCheckout(".f");
+ }
+
+ @Test
+ public void testMaliciousGitPathEndDotUnixOk() throws Exception {
+ if (File.separatorChar == '\\')
+ return; // cannot emulate Unix on Windows for this test
+ ((MockSystemReader) SystemReader.getInstance()).setUnix();
+ testMaliciousPathGoodFirstCheckout(".git.", "konfig");
+ }
+
+ @Test
+ public void testMaliciousPathDotDot() throws Exception {
+ ((MockSystemReader) SystemReader.getInstance()).setCurrentPlatform();
+ testMaliciousPathBadFirstCheckout("..", "no");
+ }
+
+ @Test
+ public void testMaliciousPathDot() throws Exception {
+ ((MockSystemReader) SystemReader.getInstance()).setCurrentPlatform();
+ testMaliciousPathBadFirstCheckout(".", "no");
+ }
+
+ @Test
+ public void testMaliciousPathEmpty() throws Exception {
+ ((MockSystemReader) SystemReader.getInstance()).setCurrentPlatform();
+ testMaliciousPathBadFirstCheckout("", "no");
+ }
+
+ @Test
+ public void testMaliciousWindowsADS() throws Exception {
+ ((MockSystemReader) SystemReader.getInstance()).setWindows();
+ testMaliciousPathBadFirstCheckout("some:path");
+ }
+
+ @Test
+ public void testMaliciousWindowsADSOnUnix() throws Exception {
+ if (File.separatorChar == '\\')
+ return; // cannot emulate Unix on Windows for this test
+ ((MockSystemReader) SystemReader.getInstance()).setUnix();
+ testMaliciousPathGoodFirstCheckout("some:path");
+ }
+
+ @Test
+ public void testForbiddenNamesOnWindowsEgCon() throws Exception {
+ ((MockSystemReader) SystemReader.getInstance()).setWindows();
+ testMaliciousPathBadFirstCheckout("con");
+ }
+
+ @Test
+ public void testForbiddenNamesOnWindowsEgConDotSuffix() throws Exception {
+ ((MockSystemReader) SystemReader.getInstance()).setWindows();
+ testMaliciousPathBadFirstCheckout("con.txt");
+ }
+
+ @Test
+ public void testForbiddenNamesOnWindowsEgLpt1() throws Exception {
+ ((MockSystemReader) SystemReader.getInstance()).setWindows();
+ testMaliciousPathBadFirstCheckout("lpt1");
+ }
+
+ @Test
+ public void testForbiddenNamesOnWindowsEgLpt1DotSuffix() throws Exception {
+ ((MockSystemReader) SystemReader.getInstance()).setWindows();
+ testMaliciousPathBadFirstCheckout("lpt1.txt");
+ }
+
+ @Test
+ public void testForbiddenNamesOnWindowsEgDotCon() throws Exception {
+ ((MockSystemReader) SystemReader.getInstance()).setWindows();
+ testMaliciousPathGoodFirstCheckout(".con");
+ }
+
+ @Test
+ public void testForbiddenNamesOnWindowsEgLpr() throws Exception {
+ ((MockSystemReader) SystemReader.getInstance()).setWindows();
+ testMaliciousPathGoodFirstCheckout("lpt"); // good name
+ }
+
+ @Test
+ public void testForbiddenNamesOnWindowsEgCon1() throws Exception {
+ ((MockSystemReader) SystemReader.getInstance()).setWindows();
+ testMaliciousPathGoodFirstCheckout("con1"); // good name
+ }
+
+ @Test
+ public void testForbiddenWindowsNamesOnUnixEgCon() throws Exception {
+ if (File.separatorChar == '\\')
+ return; // cannot emulate Unix on Windows for this test
+ testMaliciousPathGoodFirstCheckout("con");
+ }
+
+ @Test
+ public void testForbiddenWindowsNamesOnUnixEgLpt1() throws Exception {
+ if (File.separatorChar == '\\')
+ return; // cannot emulate Unix on Windows for this test
+ testMaliciousPathGoodFirstCheckout("lpt1");
+ }
+
+ private void testMaliciousPathBadFirstCheckout(String... paths)
+ throws Exception {
+ testMaliciousPath(false, false, paths);
+ }
+
+ private void testMaliciousPathBadSecondCheckout(String... paths) throws Exception {
+ testMaliciousPath(false, true, paths);
+ }
+
+ private void testMaliciousPathGoodFirstCheckout(String... paths)
+ throws Exception {
+ testMaliciousPath(true, false, paths);
+ }
+
+ private void testMaliciousPathGoodSecondCheckout(String... paths) throws Exception {
+ testMaliciousPath(true, true, paths);
+ }
+
+ /**
+ * Create a bad tree and tries to check it out
+ *
+ * @param good
+ * true if we expect this to pass
+ * @param secondCheckout
+ * perform the actual test on the second checkout
+ * @param path
+ * to the blob, one or more levels
+ * @throws IOException
+ * @throws RefAlreadyExistsException
+ * @throws RefNotFoundException
+ * @throws InvalidRefNameException
+ * @throws MissingObjectException
+ * @throws IncorrectObjectTypeException
+ * @throws CheckoutConflictException
+ * @throws JGitInternalException
+ */
+ private void testMaliciousPath(boolean good, boolean secondCheckout, String... path)
+ throws IOException, RefAlreadyExistsException,
+ RefNotFoundException, InvalidRefNameException,
+ MissingObjectException, IncorrectObjectTypeException,
+ JGitInternalException, CheckoutConflictException {
+ Git git = new Git(db);
+ ObjectInserter newObjectInserter;
+ newObjectInserter = git.getRepository().newObjectInserter();
+ ObjectId blobId = newObjectInserter.insert(Constants.OBJ_BLOB,
+ "data".getBytes());
+ newObjectInserter = git.getRepository().newObjectInserter();
+ FileMode mode = FileMode.REGULAR_FILE;
+ ObjectId insertId = blobId;
+ for (int i = path.length - 1; i >= 0; --i) {
+ TreeFormatter treeFormatter = new TreeFormatter();
+ treeFormatter.append("goodpath", mode, insertId);
+ insertId = newObjectInserter.insert(treeFormatter);
+ mode = FileMode.TREE;
+ }
+ newObjectInserter = git.getRepository().newObjectInserter();
+ CommitBuilder commitBuilder = new CommitBuilder();
+ commitBuilder.setAuthor(author);
+ commitBuilder.setCommitter(committer);
+ commitBuilder.setMessage("foo#1");
+ commitBuilder.setTreeId(insertId);
+ ObjectId firstCommitId = newObjectInserter.insert(commitBuilder);
+
+ newObjectInserter = git.getRepository().newObjectInserter();
+ mode = FileMode.REGULAR_FILE;
+ insertId = blobId;
+ for (int i = path.length - 1; i >= 0; --i) {
+ TreeFormatter treeFormatter = new TreeFormatter();
+ treeFormatter.append(path[i], mode, insertId);
+ insertId = newObjectInserter.insert(treeFormatter);
+ mode = FileMode.TREE;
+ }
+
+ // Create another commit
+ commitBuilder = new CommitBuilder();
+ commitBuilder.setAuthor(author);
+ commitBuilder.setCommitter(committer);
+ commitBuilder.setMessage("foo#2");
+ commitBuilder.setTreeId(insertId);
+ commitBuilder.setParentId(firstCommitId);
+ ObjectId commitId = newObjectInserter.insert(commitBuilder);
+
+ RevWalk revWalk = new RevWalk(git.getRepository());
+ if (!secondCheckout)
+ git.checkout().setStartPoint(revWalk.parseCommit(firstCommitId))
+ .setName("refs/heads/master").setCreateBranch(true).call();
+ try {
+ if (secondCheckout) {
+ git.checkout().setStartPoint(revWalk.parseCommit(commitId))
+ .setName("refs/heads/master").setCreateBranch(true)
+ .call();
+ } else {
+ git.branchCreate().setName("refs/heads/next")
+ .setStartPoint(commitId.name()).call();
+ git.checkout().setName("refs/heads/next")
+ .call();
+ }
+ if (!good)
+ fail("Checkout of Tree " + Arrays.asList(path) + " should fail");
+ } catch (InvalidPathException e) {
+ if (good)
+ throw e;
+ assertThat(e.getMessage(), startsWith("Invalid path: "));
+ }
+ }
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
index 859f611737..22a8558328 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
@@ -58,12 +58,13 @@ import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.IndexWriteException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.CoreConfig.AutoCRLF;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.lib.CoreConfig.AutoCRLF;
import org.eclipse.jgit.treewalk.AbstractTreeIterator;
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.eclipse.jgit.treewalk.EmptyTreeIterator;
@@ -75,6 +76,7 @@ import org.eclipse.jgit.treewalk.WorkingTreeOptions;
import org.eclipse.jgit.treewalk.filter.PathFilter;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FileUtils;
+import org.eclipse.jgit.util.SystemReader;
import org.eclipse.jgit.util.io.AutoCRLFOutputStream;
/**
@@ -304,6 +306,8 @@ public class DirCacheCheckout {
void processEntry(CanonicalTreeParser m, DirCacheBuildIterator i,
WorkingTreeIterator f) throws IOException {
if (m != null) {
+ if (!isValidPath(m))
+ throw new InvalidPathException(m.getEntryPathString());
// There is an entry in the merge commit. Means: we want to update
// what's currently in the index and working-tree to that one
if (i == null) {
@@ -506,12 +510,15 @@ public class DirCacheCheckout {
* @throws IOException
*/
- void processEntry(AbstractTreeIterator h, AbstractTreeIterator m,
+ void processEntry(CanonicalTreeParser h, CanonicalTreeParser m,
DirCacheBuildIterator i, WorkingTreeIterator f) throws IOException {
DirCacheEntry dce = i != null ? i.getDirCacheEntry() : null;
String name = walk.getPathString();
+ if (m != null && !isValidPath(m))
+ throw new InvalidPathException(m.getEntryPathString());
+
if (i == null && m == null && h == null) {
// File/Directory conflict case #20
if (walk.isDirectoryFileConflict())
@@ -988,4 +995,103 @@ public class DirCacheCheckout {
else
entry.setLength((int) ol.getSize());
}
+
+ private static byte[][] forbidden;
+ static {
+ String[] list = new String[] { "AUX", "COM1", "COM2", "COM3", "COM4",
+ "COM5", "COM6", "COM7", "COM8", "COM9", "CON", "LPT1", "LPT2",
+ "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", "NUL",
+ "PRN" };
+ forbidden = new byte[list.length][];
+ for (int i = 0; i < list.length; ++i)
+ forbidden[i] = Constants.encodeASCII(list[i]);
+ }
+
+ private static boolean isValidPath(CanonicalTreeParser t) {
+ for (CanonicalTreeParser i = t; i != null; i = i.getParent())
+ if (!isValidPathSegment(i))
+ return false;
+ return true;
+ }
+
+ private static boolean isValidPathSegment(CanonicalTreeParser t) {
+ boolean isWindows = "Windows".equals(SystemReader.getInstance()
+ .getProperty("os.name"));
+ boolean isOSX = "Mac OS X".equals(SystemReader.getInstance()
+ .getProperty("os.name"));
+ boolean ignCase = isOSX || isWindows;
+
+ int ptr = t.getNameOffset();
+ byte[] raw = t.getEntryPathBuffer();
+ int end = ptr + t.getNameLength();
+
+ // Validate path component at this level of the tree
+ int start = ptr;
+ while (ptr < end) {
+ if (raw[ptr] == '/')
+ return false;
+ if (isWindows) {
+ if (raw[ptr] == '\\')
+ return false;
+ if (raw[ptr] == ':')
+ return false;
+ }
+ ptr++;
+ }
+ // '.' and '.'' are invalid here
+ if (ptr - start == 1) {
+ if (raw[start] == '.')
+ return false;
+ } else if (ptr - start == 2) {
+ if (raw[start] == '.')
+ if (raw[start + 1] == '.')
+ return false;
+ } else if (ptr - start == 4) {
+ // .git (possibly case insensitive) is disallowed
+ if (raw[start] == '.')
+ if (raw[start + 1] == 'g' || (ignCase && raw[start + 1] == 'G'))
+ if (raw[start + 2] == 'i'
+ || (ignCase && raw[start + 2] == 'I'))
+ if (raw[start + 3] == 't'
+ || (ignCase && raw[start + 3] == 'T'))
+ return false;
+ }
+ if (isWindows) {
+ // Space or period at end of file name is ignored by Windows.
+ // Treat this as a bad path for now. We may want to handle
+ // this as case insensitivity in the future.
+ if (raw[ptr - 1] == '.' || raw[ptr - 1] == ' ')
+ return false;
+ int i;
+ // Bad names, eliminate suffix first
+ for (i = start; i < ptr; ++i)
+ if (raw[i] == '.')
+ break;
+ int len = i - start;
+ if (len == 3 || len == 4) {
+ for (int j = 0; j < forbidden.length; ++j) {
+ if (forbidden[j].length == len) {
+ if (toUpper(raw[start]) < forbidden[j][0])
+ break;
+ int k;
+ for (k = 0; k < len; ++k) {
+ if (toUpper(raw[start + k]) != forbidden[j][k])
+ break;
+ }
+ if (k == len)
+ return false;
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+ private static byte toUpper(byte b) {
+ if (b >= 'a' && b <= 'z')
+ return (byte) (b - ('a' - 'A'));
+ return b;
+ }
+
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java
index 08cc9a8d52..d16f7d4f2f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java
@@ -64,6 +64,7 @@ import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.util.IO;
import org.eclipse.jgit.util.MutableInteger;
import org.eclipse.jgit.util.NB;
+import org.eclipse.jgit.util.SystemReader;
/**
* A single file (or stage of a file) in a {@link DirCache}.
@@ -261,8 +262,7 @@ public class DirCacheEntry {
@SuppressWarnings("boxing")
public DirCacheEntry(final byte[] newPath, final int stage) {
if (!isValidPath(newPath))
- throw new IllegalArgumentException(MessageFormat.format(JGitText.get().invalidPath
- , toString(newPath)));
+ throw new InvalidPathException(toString(newPath));
if (stage < 0 || 3 < stage)
throw new IllegalArgumentException(MessageFormat.format(JGitText.get().invalidStageForPath
, stage, toString(newPath)));
@@ -716,7 +716,14 @@ public class DirCacheEntry {
else
return false;
break;
-
+ case '\\':
+ case ':':
+ // Tree's never have a backslash in them, not even on Windows
+ // but even there we regard it as an invalid path
+ if ("Windows".equals(SystemReader.getInstance().getProperty(
+ "os.name")))
+ return false;
+ //$FALL-THROUGH$
default:
componentHasChars = true;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/InvalidPathException.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/InvalidPathException.java
new file mode 100644
index 0000000000..155519f28b
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/InvalidPathException.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2011, Robin Rosenberg
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.dircache;
+
+import java.text.MessageFormat;
+
+import org.eclipse.jgit.internal.JGitText;
+
+/**
+ * Thrown when JGit detects and refuses to use an invalid path
+ */
+public class InvalidPathException extends IllegalArgumentException {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * @param path
+ */
+ public InvalidPathException(String path) {
+ super(MessageFormat.format(JGitText.get().invalidPath, path));
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java
index df3dac3919..ca5fec2a9a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java
@@ -49,6 +49,7 @@ import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
+import org.eclipse.jgit.dircache.DirCacheCheckout;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.lib.Constants;
@@ -647,10 +648,23 @@ public abstract class AbstractTreeIterator {
}
/**
- * Get the name component of the current entry path into the provided buffer.
+ * JGit internal API for use by {@link DirCacheCheckout}
*
- * @param buffer the buffer to get the name into, it is assumed that buffer can hold the name
- * @param offset the offset of the name in the buffer
+ * @return start of name component part within {@link #getEntryPathBuffer()}
+ */
+ public int getNameOffset() {
+ return pathOffset;
+ }
+
+ /**
+ * Get the name component of the current entry path into the provided
+ * buffer.
+ *
+ * @param buffer
+ * the buffer to get the name into, it is assumed that buffer can
+ * hold the name
+ * @param offset
+ * the offset of the name in the buffer
* @see #getNameLength()
*/
public void getName(byte[] buffer, int offset) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java
index 1e49d380a8..57bbd64ba5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java
@@ -109,6 +109,14 @@ public class CanonicalTreeParser extends AbstractTreeIterator {
}
/**
+ * @return the parent of this tree parser
+ * @internal
+ */
+ public CanonicalTreeParser getParent() {
+ return (CanonicalTreeParser) parent;
+ }
+
+ /**
* Reset this parser to walk through the given tree data.
*
* @param treeData