Browse Source

Merge changes I1a225c1b,Ie4d5f082,I41f24363,I9440dd25,I170b6f64,I02e964d1,Iec6ce9e6,Ic700ea8f,Ic3042444,I90a1eaf2,I66eaee5c,Ib921dfd7

* changes:
  Allow configuration of receive pack's ObjectChecker through fsck.*
  Correct fetch to use fetch.fsckObjects and transfer.fsckObjects
  Default receive.fsckObjects to transfer.fsckObjects
  Allow ReceivePack callers to configure their own ObjectChecker
  Check for duplicate names after folding case in ObjectChecker
  Change DirCacheCheckout to verify path using ObjectChecker
  Reject mixed case .git on Mac OS in ObjectChecker
  Reject special Windows device names in ObjectChecker
  Allow an ObjectChecker to reject special characters for Windows
  Reject '.git' as a tree name in ObjectChecker
  Extract path segment check function in ObjectChecker
  Permit ObjectChecker to optionally accept leading '0' in trees
tags/v3.4.0.201405051725-m7
Shawn Pearce 10 years ago
parent
commit
088e80315b

+ 210
- 0
org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java View File

import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;


import java.io.UnsupportedEncodingException;
import java.text.MessageFormat; import java.text.MessageFormat;


import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.CorruptObjectException;
checker.checkTree(data); checker.checkTree(data);
} }


@Test
public void testValidPosixTree() throws CorruptObjectException {
checkOneName("a<b>c:d|e");
checkOneName("test ");
checkOneName("test.");
checkOneName("NUL");
}

@Test @Test
public void testValidTreeSorting1() throws CorruptObjectException { public void testValidTreeSorting1() throws CorruptObjectException {
final StringBuilder b = new StringBuilder(); final StringBuilder b = new StringBuilder();
checker.checkTree(data); checker.checkTree(data);
} }


@Test
public void testAcceptTreeModeWithZero() throws CorruptObjectException {
StringBuilder b = new StringBuilder();
entry(b, "040000 a");
checker.setAllowLeadingZeroFileMode(true);
checker.checkTree(Constants.encodeASCII(b.toString()));
}

@Test @Test
public void testInvalidTreeModeStartsWithZero1() { public void testInvalidTreeModeStartsWithZero1() {
final StringBuilder b = new StringBuilder(); final StringBuilder b = new StringBuilder();
} }
} }


@Test
public void testInvalidTreeNameIsGit() {
StringBuilder b = new StringBuilder();
entry(b, "100644 .git");
byte[] data = Constants.encodeASCII(b.toString());
try {
checker.checkTree(data);
fail("incorrectly accepted an invalid tree");
} catch (CorruptObjectException e) {
assertEquals("invalid name '.git'", e.getMessage());
}
}

@Test
public void testInvalidTreeNameIsMixedCaseGitWindows() {
StringBuilder b = new StringBuilder();
entry(b, "100644 .GiT");
byte[] data = Constants.encodeASCII(b.toString());
try {
checker.setSafeForWindows(true);
checker.checkTree(data);
fail("incorrectly accepted an invalid tree");
} catch (CorruptObjectException e) {
assertEquals("invalid name '.GiT'", e.getMessage());
}
}

@Test
public void testInvalidTreeNameIsMixedCaseGitMacOS() {
StringBuilder b = new StringBuilder();
entry(b, "100644 .GiT");
byte[] data = Constants.encodeASCII(b.toString());
try {
checker.setSafeForMacOS(true);
checker.checkTree(data);
fail("incorrectly accepted an invalid tree");
} catch (CorruptObjectException e) {
assertEquals("invalid name '.GiT'", e.getMessage());
}
}

@Test @Test
public void testInvalidTreeTruncatedInName() { public void testInvalidTreeTruncatedInName() {
final StringBuilder b = new StringBuilder(); final StringBuilder b = new StringBuilder();
} }
} }


@Test
public void testInvalidTreeDuplicateNames5()
throws UnsupportedEncodingException {
StringBuilder b = new StringBuilder();
entry(b, "100644 a");
entry(b, "100644 A");
byte[] data = b.toString().getBytes("UTF-8");
try {
checker.setSafeForWindows(true);
checker.checkTree(data);
fail("incorrectly accepted an invalid tree");
} catch (CorruptObjectException e) {
assertEquals("duplicate entry names", e.getMessage());
}
}

@Test
public void testInvalidTreeDuplicateNames6()
throws UnsupportedEncodingException {
StringBuilder b = new StringBuilder();
entry(b, "100644 a");
entry(b, "100644 A");
byte[] data = b.toString().getBytes("UTF-8");
try {
checker.setSafeForMacOS(true);
checker.checkTree(data);
fail("incorrectly accepted an invalid tree");
} catch (CorruptObjectException e) {
assertEquals("duplicate entry names", e.getMessage());
}
}

@Test
public void testInvalidTreeDuplicateNames7()
throws UnsupportedEncodingException {
try {
Class.forName("java.text.Normalizer");
} catch (ClassNotFoundException e) {
// Ignore this test on Java 5 platform.
return;
}

StringBuilder b = new StringBuilder();
entry(b, "100644 \u00C1");
entry(b, "100644 \u004a\u0301");
byte[] data = b.toString().getBytes("UTF-8");
try {
checker.setSafeForMacOS(true);
checker.checkTree(data);
fail("incorrectly accepted an invalid tree");
} catch (CorruptObjectException e) {
assertEquals("duplicate entry names", e.getMessage());
}
}

@Test
public void testRejectNulInPathSegment() {
try {
checker.checkPathSegment(Constants.encodeASCII("a\u0000b"), 0, 3);
fail("incorrectly accepted NUL in middle of name");
} catch (CorruptObjectException e) {
assertEquals("name contains byte 0x00", e.getMessage());
}
}

@Test
public void testRejectSpaceAtEndOnWindows() {
checker.setSafeForWindows(true);
try {
checkOneName("test ");
fail("incorrectly accepted space at end");
} catch (CorruptObjectException e) {
assertEquals("invalid name ends with ' '", e.getMessage());
}
}

@Test
public void testRejectDotAtEndOnWindows() {
checker.setSafeForWindows(true);
try {
checkOneName("test.");
fail("incorrectly accepted dot at end");
} catch (CorruptObjectException e) {
assertEquals("invalid name ends with '.'", e.getMessage());
}
}

@Test
public void testRejectDevicesOnWindows() {
checker.setSafeForWindows(true);

String[] bad = { "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3",
"COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2",
"LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9" };
for (String b : bad) {
try {
checkOneName(b);
fail("incorrectly accepted " + b);
} catch (CorruptObjectException e) {
assertEquals("invalid name '" + b + "'", e.getMessage());
}
try {
checkOneName(b + ".txt");
fail("incorrectly accepted " + b + ".txt");
} catch (CorruptObjectException e) {
assertEquals("invalid name '" + b + "'", e.getMessage());
}
}
}

@Test
public void testRejectInvalidWindowsCharacters() {
checker.setSafeForWindows(true);
rejectName('<');
rejectName('>');
rejectName(':');
rejectName('"');
rejectName('/');
rejectName('\\');
rejectName('|');
rejectName('?');
rejectName('*');

for (int i = 1; i <= 31; i++)
rejectName((byte) i);
}

private void rejectName(char c) {
try {
checkOneName("te" + c + "st");
fail("incorrectly accepted with " + c);
} catch (CorruptObjectException e) {
assertEquals("name contains '" + c + "'", e.getMessage());
}
}

private void rejectName(byte c) {
String h = Integer.toHexString(c);
try {
checkOneName("te" + ((char) c) + "st");
fail("incorrectly accepted with 0x" + h);
} catch (CorruptObjectException e) {
assertEquals("name contains byte 0x" + h, e.getMessage());
}
}

private void checkOneName(String name) throws CorruptObjectException {
StringBuilder b = new StringBuilder();
entry(b, "100644 " + name);
checker.checkTree(Constants.encodeASCII(b.toString()));
}

private static void entry(final StringBuilder b, final String modeName) { private static void entry(final StringBuilder b, final String modeName) {
b.append(modeName); b.append(modeName);
b.append('\0'); b.append('\0');

+ 28
- 123
org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java View File

import org.eclipse.jgit.lib.CoreConfig.AutoCRLF; import org.eclipse.jgit.lib.CoreConfig.AutoCRLF;
import org.eclipse.jgit.lib.CoreConfig.SymLinks; import org.eclipse.jgit.lib.CoreConfig.SymLinks;
import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectChecker;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.ObjectReader;
entry.setLength((int) ol.getSize()); entry.setLength((int) ol.getSize());
} }


private static byte[][] forbidden;
static {
String[] list = getSortedForbiddenFileNames();
forbidden = new byte[list.length][];
for (int i = 0; i < list.length; ++i)
forbidden[i] = Constants.encodeASCII(list[i]);
}

static String[] getSortedForbiddenFileNames() {
String[] list = new String[] { "AUX", "COM1", "COM2", "COM3", "COM4", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
"COM5", "COM6", "COM7", "COM8", "COM9", "CON", "LPT1", "LPT2", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$
"LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", "NUL", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$
"PRN" }; //$NON-NLS-1$
return list;
}

private static void checkValidPath(CanonicalTreeParser t) private static void checkValidPath(CanonicalTreeParser t)
throws InvalidPathException { throws InvalidPathException {
ObjectChecker chk = new ObjectChecker()
.setSafeForWindows(SystemReader.getInstance().isWindows())
.setSafeForMacOS(SystemReader.getInstance().isMacOS());
for (CanonicalTreeParser i = t; i != null; i = i.getParent()) for (CanonicalTreeParser i = t; i != null; i = i.getParent())
checkValidPathSegment(i);
checkValidPathSegment(chk, i);
} }


/** /**
* @since 3.3 * @since 3.3
*/ */
public static void checkValidPath(String path) throws InvalidPathException { public static void checkValidPath(String path) throws InvalidPathException {
boolean isWindows = SystemReader.getInstance().isWindows();
boolean isOSX = SystemReader.getInstance().isMacOS();
boolean ignCase = isOSX || isWindows;
ObjectChecker chk = new ObjectChecker()
.setSafeForWindows(SystemReader.getInstance().isWindows())
.setSafeForMacOS(SystemReader.getInstance().isMacOS());


byte[] bytes = Constants.encode(path); byte[] bytes = Constants.encode(path);
int segmentStart = 0; int segmentStart = 0;
for (int i = 0; i < bytes.length; i++) {
if (bytes[i] == '/') {
checkValidPathSegment(isWindows, ignCase, bytes, segmentStart,
i, path);
segmentStart = i + 1;
}
}
if (segmentStart < bytes.length)
checkValidPathSegment(isWindows, ignCase, bytes, segmentStart,
bytes.length, path);
}

private static void checkValidPathSegment(CanonicalTreeParser t)
throws InvalidPathException {
boolean isWindows = SystemReader.getInstance().isWindows();
boolean isOSX = SystemReader.getInstance().isMacOS();
boolean ignCase = isOSX || isWindows;

int ptr = t.getNameOffset();
byte[] raw = t.getEntryPathBuffer();
int end = ptr + t.getNameLength();

checkValidPathSegment(isWindows, ignCase, raw, ptr, end,
t.getEntryPathString());
}

private static void checkValidPathSegment(boolean isWindows,
boolean ignCase, byte[] raw, int ptr, int end, String path) {
// Validate path component at this level of the tree
int start = ptr;
while (ptr < end) {
if (raw[ptr] == '/')
throw new InvalidPathException(
JGitText.get().invalidPathContainsSeparator, "/", path); //$NON-NLS-1$
if (isWindows) {
if (raw[ptr] == '\\')
throw new InvalidPathException(
JGitText.get().invalidPathContainsSeparator,
"\\", path); //$NON-NLS-1$
if (raw[ptr] == ':')
throw new InvalidPathException(
JGitText.get().invalidPathContainsSeparator,
":", path); //$NON-NLS-1$
}
ptr++;
}
// '.' and '..' are invalid here
if (ptr - start == 1) {
if (raw[start] == '.')
throw new InvalidPathException(path);
} else if (ptr - start == 2) {
if (raw[start] == '.')
if (raw[start + 1] == '.')
throw new InvalidPathException(path);
} 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'))
throw new InvalidPathException(path);
}
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 (ptr > 0) {
if (raw[ptr - 1] == '.')
throw new InvalidPathException(
JGitText.get().invalidPathPeriodAtEndWindows, path);
if (raw[ptr - 1] == ' ')
throw new InvalidPathException(
JGitText.get().invalidPathSpaceAtEndWindows, path);
}

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)
throw new InvalidPathException(
JGitText.get().invalidPathReservedOnWindows,
RawParseUtils.decode(forbidden[j]), path);
}
try {
for (int i = 0; i < bytes.length; i++) {
if (bytes[i] == '/') {
chk.checkPathSegment(bytes, segmentStart, i);
segmentStart = i + 1;
} }
} }
chk.checkPathSegment(bytes, segmentStart, bytes.length);
} catch (CorruptObjectException e) {
throw new InvalidPathException(e.getMessage());
} }
} }


private static byte toUpper(byte b) {
if (b >= 'a' && b <= 'z')
return (byte) (b - ('a' - 'A'));
return b;
private static void checkValidPathSegment(ObjectChecker chk,
CanonicalTreeParser t) throws InvalidPathException {
try {
int ptr = t.getNameOffset();
int end = ptr + t.getNameLength();
chk.checkPathSegment(t.getEntryPathBuffer(), ptr, end);
} catch (CorruptObjectException err) {
String path = t.getEntryPathString();
InvalidPathException i = new InvalidPathException(path);
i.initCause(err);
throw i;
}
} }

} }

+ 282
- 23
org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java View File

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.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;


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;


/** /**
* Verifies that an object is formatted correctly. * Verifies that an object is formatted correctly.


private final MutableInteger ptrout = new MutableInteger(); private final MutableInteger ptrout = new MutableInteger();


private boolean allowZeroMode;
private boolean windows;
private boolean macosx;

/**
* Enable accepting leading zero mode in tree entries.
* <p>
* Some broken Git libraries generated leading zeros in the mode part of
* tree entries. This is technically incorrect but gracefully allowed by
* git-core. JGit rejects such trees by default, but may need to accept
* them on broken histories.
*
* @param allow allow leading zero mode.
* @return {@code this}.
* @since 3.4
*/
public ObjectChecker setAllowLeadingZeroFileMode(boolean allow) {
allowZeroMode = allow;
return this;
}

/**
* Restrict trees to only names legal on Windows platforms.
* <p>
* Also rejects any mixed case forms of reserved names ({@code .git}).
*
* @param win true if Windows name checking should be performed.
* @return {@code this}.
* @since 3.4
*/
public ObjectChecker setSafeForWindows(boolean win) {
windows = win;
return this;
}

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

/** /**
* Check an object for parsing errors. * Check an object for parsing errors.
* *
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;
Set<String> normalized = windows || macosx
? new HashSet<String>()
: null;


while (ptr < sz) { while (ptr < sz) {
int thisMode = 0; int thisMode = 0;
break; break;
if (c < '0' || c > '7') if (c < '0' || c > '7')
throw new CorruptObjectException("invalid mode character"); throw new CorruptObjectException("invalid mode character");
if (thisMode == 0 && c == '0')
if (thisMode == 0 && c == '0' && !allowZeroMode)
throw new CorruptObjectException("mode starts with '0'"); throw new CorruptObjectException("mode starts with '0'");
thisMode <<= 3; thisMode <<= 3;
thisMode += c - '0'; thisMode += c - '0';
throw new CorruptObjectException("invalid mode " + thisMode); throw new CorruptObjectException("invalid mode " + thisMode);


final int thisNameB = ptr; final int thisNameB = ptr;
for (;;) {
if (ptr == sz)
throw new CorruptObjectException("truncated in name");
final byte c = raw[ptr++];
if (c == 0)
break;
if (c == '/')
throw new CorruptObjectException("name contains '/'");
}
if (thisNameB + 1 == ptr)
throw new CorruptObjectException("zero length name");
if (raw[thisNameB] == '.') {
final int nameLen = (ptr - 1) - thisNameB;
if (nameLen == 1)
throw new CorruptObjectException("invalid name '.'");
if (nameLen == 2 && raw[thisNameB + 1] == '.')
throw new CorruptObjectException("invalid name '..'");
}
if (duplicateName(raw, thisNameB, ptr - 1))
ptr = scanPathSegment(raw, ptr, sz);
if (ptr == sz || raw[ptr] != 0)
throw new CorruptObjectException("truncated in name");
checkPathSegment2(raw, thisNameB, ptr);
if (normalized != null) {
if (normalized.add(normalize(raw, thisNameB, ptr)))
throw new CorruptObjectException("duplicate entry names");
} else if (duplicateName(raw, thisNameB, ptr))
throw new CorruptObjectException("duplicate entry names"); throw new CorruptObjectException("duplicate entry names");


if (lastNameB != 0) { if (lastNameB != 0) {
final int cmp = pathCompare(raw, lastNameB, lastNameE, final int cmp = pathCompare(raw, lastNameB, lastNameE,
lastMode, thisNameB, ptr - 1, thisMode);
lastMode, thisNameB, ptr, thisMode);
if (cmp > 0) if (cmp > 0)
throw new CorruptObjectException("incorrectly sorted"); throw new CorruptObjectException("incorrectly sorted");
} }


lastNameB = thisNameB; lastNameB = thisNameB;
lastNameE = ptr - 1;
lastNameE = ptr;
lastMode = thisMode; lastMode = thisMode;


ptr += Constants.OBJECT_ID_LENGTH;
ptr += 1 + Constants.OBJECT_ID_LENGTH;
if (ptr > sz) if (ptr > sz)
throw new CorruptObjectException("truncated in object id"); throw new CorruptObjectException("truncated in object id");
} }
} }


private int scanPathSegment(byte[] raw, int ptr, int end)
throws CorruptObjectException {
for (; ptr < end; ptr++) {
byte c = raw[ptr];
if (c == 0)
return ptr;
if (c == '/')
throw new CorruptObjectException("name contains '/'");
if (windows && isInvalidOnWindows(c)) {
if (c > 31)
throw new CorruptObjectException(String.format(
"name contains '%c'", c));
throw new CorruptObjectException(String.format(
"name contains byte 0x%x", c & 0xff));
}
}
return ptr;
}

/**
* Check tree path entry for validity.
*
* @param raw buffer to scan.
* @param ptr offset to first byte of the name.
* @param end offset to one past last byte of name.
* @throws CorruptObjectException name is invalid.
* @since 3.4
*/
public void checkPathSegment(byte[] raw, int ptr, int end)
throws CorruptObjectException {
int e = scanPathSegment(raw, ptr, end);
if (e < end && raw[e] == 0)
throw new CorruptObjectException("name contains byte 0x00");
checkPathSegment2(raw, ptr, end);
}

private void checkPathSegment2(byte[] raw, int ptr, int end)
throws CorruptObjectException {
if (ptr == end)
throw new CorruptObjectException("zero length name");
if (raw[ptr] == '.') {
switch (end - ptr) {
case 1:
throw new CorruptObjectException("invalid name '.'");
case 2:
if (raw[ptr + 1] == '.')
throw new CorruptObjectException("invalid name '..'");
break;
case 4:
if (isDotGit(raw, ptr + 1))
throw new CorruptObjectException(String.format(
"invalid name '%s'",
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("invalid name ends with '"
+ ((char) raw[end - 1]) + "'");
if (end - ptr >= 3)
checkNotWindowsDevice(raw, ptr, end);
}
}

private static void checkNotWindowsDevice(byte[] raw, int ptr, int end)
throws CorruptObjectException {
switch (toLower(raw[ptr])) {
case 'a': // AUX
if (end - ptr >= 3
&& toLower(raw[ptr + 1]) == 'u'
&& toLower(raw[ptr + 2]) == 'x'
&& (end - ptr == 3 || raw[ptr + 3] == '.'))
throw new CorruptObjectException("invalid name 'AUX'");
break;

case 'c': // CON, COM[1-9]
if (end - ptr >= 3
&& toLower(raw[ptr + 2]) == 'n'
&& toLower(raw[ptr + 1]) == 'o'
&& (end - ptr == 3 || raw[ptr + 3] == '.'))
throw new CorruptObjectException("invalid name 'CON'");
if (end - ptr >= 4
&& toLower(raw[ptr + 2]) == 'm'
&& toLower(raw[ptr + 1]) == 'o'
&& isPositiveDigit(raw[ptr + 3])
&& (end - ptr == 4 || raw[ptr + 4] == '.'))
throw new CorruptObjectException("invalid name 'COM"
+ ((char) raw[ptr + 3]) + "'");
break;

case 'l': // LPT[1-9]
if (end - ptr >= 4
&& toLower(raw[ptr + 1]) == 'p'
&& toLower(raw[ptr + 2]) == 't'
&& isPositiveDigit(raw[ptr + 3])
&& (end - ptr == 4 || raw[ptr + 4] == '.'))
throw new CorruptObjectException("invalid name 'LPT"
+ ((char) raw[ptr + 3]) + "'");
break;

case 'n': // NUL
if (end - ptr >= 3
&& toLower(raw[ptr + 1]) == 'u'
&& toLower(raw[ptr + 2]) == 'l'
&& (end - ptr == 3 || raw[ptr + 3] == '.'))
throw new CorruptObjectException("invalid name 'NUL'");
break;

case 'p': // PRN
if (end - ptr >= 3
&& toLower(raw[ptr + 1]) == 'r'
&& toLower(raw[ptr + 2]) == 'n'
&& (end - ptr == 3 || raw[ptr + 3] == '.'))
throw new CorruptObjectException("invalid name 'PRN'");
break;
}
}

private static boolean isInvalidOnWindows(byte c) {
// Windows disallows "special" characters in a path component.
switch (c) {
case '"':
case '*':
case ':':
case '<':
case '>':
case '?':
case '\\':
case '|':
return true;
}
return 1 <= c && c <= 31;
}

private boolean isDotGit(byte[] buf, int p) {
if (windows || macosx)
return toLower(buf[p]) == 'g'
&& toLower(buf[p + 1]) == 'i'
&& toLower(buf[p + 2]) == 't';
return buf[p] == 'g' && buf[p + 1] == 'i' && buf[p + 2] == 't';
}

private static char toLower(byte b) {
if ('A' <= b && b <= 'Z')
return (char) (b + ('a' - 'A'));
return (char) b;
}

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

/** /**
* Check a blob for errors. * Check a blob for errors.
* *
public void checkBlob(final byte[] raw) throws CorruptObjectException { public void checkBlob(final byte[] raw) throws CorruptObjectException {
// We can always assume the blob is valid. // We can always assume the blob is valid.
} }

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

private static class Normalizer {
// TODO Simplify invocation to Normalizer after dropping Java 5.
private static final Method normalize;
private static final Object nfc;
static {
Method method;
Object formNfc;
try {
Class<?> formClazz = Class.forName("java.text.Normalizer$Form"); //$NON-NLS-1$
formNfc = formClazz.getField("NFC").get(null); //$NON-NLS-1$
method = Class.forName("java.text.Normalizer") //$NON-NLS-1$
.getMethod("normalize", CharSequence.class, formClazz); //$NON-NLS-1$
} catch (ClassNotFoundException e) {
method = null;
formNfc = null;
} catch (NoSuchFieldException e) {
method = null;
formNfc = null;
} catch (NoSuchMethodException e) {
method = null;
formNfc = null;
} catch (SecurityException e) {
method = null;
formNfc = null;
} catch (IllegalArgumentException e) {
method = null;
formNfc = null;
} catch (IllegalAccessException e) {
method = null;
formNfc = null;
}
normalize = method;
nfc = formNfc;
}

static String normalize(String in) {
if (normalize == null)
return in;
try {
return (String) normalize.invoke(null, in, nfc);
} catch (IllegalAccessException e) {
return in;
} catch (InvocationTargetException e) {
if (e.getCause() instanceof RuntimeException)
throw (RuntimeException) e.getCause();
if (e.getCause() instanceof Error)
throw (Error) e.getCause();
return in;
}
}
}
} }

+ 41
- 10
org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java View File

import org.eclipse.jgit.lib.Config.SectionParser; import org.eclipse.jgit.lib.Config.SectionParser;
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectChecker;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectIdSubclassMap; import org.eclipse.jgit.lib.ObjectIdSubclassMap;
import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.ObjectInserter;
private boolean expectDataAfterPackFooter; private boolean expectDataAfterPackFooter;


/** Should an incoming transfer validate objects? */ /** Should an incoming transfer validate objects? */
private boolean checkReceivedObjects;
private ObjectChecker objectChecker;


/** Should an incoming transfer permit create requests? */ /** Should an incoming transfer permit create requests? */
private boolean allowCreates; private boolean allowCreates;
walk = new RevWalk(db); walk = new RevWalk(db);


final ReceiveConfig cfg = db.getConfig().get(ReceiveConfig.KEY); final ReceiveConfig cfg = db.getConfig().get(ReceiveConfig.KEY);
checkReceivedObjects = cfg.checkReceivedObjects;
objectChecker = cfg.newObjectChecker();
allowCreates = cfg.allowCreates; allowCreates = cfg.allowCreates;
allowDeletes = cfg.allowDeletes; allowDeletes = cfg.allowDeletes;
allowNonFastForwards = cfg.allowNonFastForwards; allowNonFastForwards = cfg.allowNonFastForwards;
}; };


final boolean checkReceivedObjects; final boolean checkReceivedObjects;
final boolean allowLeadingZeroFileMode;
final boolean safeForWindows;
final boolean safeForMacOS;


final boolean allowCreates; final boolean allowCreates;

final boolean allowDeletes; final boolean allowDeletes;

final boolean allowNonFastForwards; final boolean allowNonFastForwards;

final boolean allowOfsDelta; final boolean allowOfsDelta;


ReceiveConfig(final Config config) { ReceiveConfig(final Config config) {
checkReceivedObjects = config.getBoolean("receive", "fsckobjects", //$NON-NLS-1$ //$NON-NLS-2$
false);
checkReceivedObjects = config.getBoolean(
"receive", "fsckobjects", //$NON-NLS-1$ //$NON-NLS-2$
config.getBoolean("transfer", "fsckobjects", false)); //$NON-NLS-1$ //$NON-NLS-2$
allowLeadingZeroFileMode = checkReceivedObjects
&& config.getBoolean("fsck", "allowLeadingZeroFileMode", false); //$NON-NLS-1$ //$NON-NLS-2$
safeForWindows = checkReceivedObjects
&& config.getBoolean("fsck", "safeForWindows", false); //$NON-NLS-1$ //$NON-NLS-2$
safeForMacOS = checkReceivedObjects
&& config.getBoolean("fsck", "safeForMacOS", false); //$NON-NLS-1$ //$NON-NLS-2$

allowCreates = true; allowCreates = true;
allowDeletes = !config.getBoolean("receive", "denydeletes", false); //$NON-NLS-1$ //$NON-NLS-2$ allowDeletes = !config.getBoolean("receive", "denydeletes", false); //$NON-NLS-1$ //$NON-NLS-2$
allowNonFastForwards = !config.getBoolean("receive", //$NON-NLS-1$ allowNonFastForwards = !config.getBoolean("receive", //$NON-NLS-1$
allowOfsDelta = config.getBoolean("repack", "usedeltabaseoffset", //$NON-NLS-1$ //$NON-NLS-2$ allowOfsDelta = config.getBoolean("repack", "usedeltabaseoffset", //$NON-NLS-1$ //$NON-NLS-2$
true); true);
} }

ObjectChecker newObjectChecker() {
if (!checkReceivedObjects)
return null;
return new ObjectChecker()
.setAllowLeadingZeroFileMode(allowLeadingZeroFileMode)
.setSafeForWindows(safeForWindows)
.setSafeForMacOS(safeForMacOS);
}
} }


/** /**
* of the connection. * of the connection.
*/ */
public boolean isCheckReceivedObjects() { public boolean isCheckReceivedObjects() {
return checkReceivedObjects;
return objectChecker != null;
} }


/** /**
* @param check * @param check
* true to enable checking received objects; false to assume all * true to enable checking received objects; false to assume all
* received objects are valid. * received objects are valid.
* @see #setObjectChecker(ObjectChecker)
*/ */
public void setCheckReceivedObjects(final boolean check) { public void setCheckReceivedObjects(final boolean check) {
checkReceivedObjects = check;
if (check && objectChecker == null)
setObjectChecker(new ObjectChecker());
else if (!check && objectChecker != null)
setObjectChecker(null);
}

/**
* @param impl if non-null the object checking instance to verify each
* received object with; null to disable object checking.
* @since 3.4
*/
public void setObjectChecker(ObjectChecker impl) {
objectChecker = impl;
} }


/** @return true if the client can request refs to be created. */ /** @return true if the client can request refs to be created. */
parser.setCheckEofAfterPackFooter(!biDirectionalPipe parser.setCheckEofAfterPackFooter(!biDirectionalPipe
&& !isExpectDataAfterPackFooter()); && !isExpectDataAfterPackFooter());
parser.setExpectDataAfterPackFooter(isExpectDataAfterPackFooter()); parser.setExpectDataAfterPackFooter(isExpectDataAfterPackFooter());
parser.setObjectChecking(isCheckReceivedObjects());
parser.setObjectChecker(objectChecker);
parser.setLockMessage(lockMsg); parser.setLockMessage(lockMsg);
parser.setMaxObjectSizeLimit(maxObjectSizeLimit); parser.setMaxObjectSizeLimit(maxObjectSizeLimit);
packLock = parser.parse(receiving, resolving); packLock = parser.parse(receiving, resolving);

+ 6
- 3
org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java View File

} }
}; };


private final boolean fsckObjects;
private final boolean fetchFsck;
private final boolean allowTipSha1InWant; private final boolean allowTipSha1InWant;
private final String[] hideRefs; private final String[] hideRefs;


} }


private TransferConfig(final Config rc) { private TransferConfig(final Config rc) {
fsckObjects = rc.getBoolean("receive", "fsckobjects", false); //$NON-NLS-1$ //$NON-NLS-2$
fetchFsck = rc.getBoolean(
"fetch", "fsckobjects", //$NON-NLS-1$ //$NON-NLS-2$
rc.getBoolean("transfer", "fsckobjects", false)); //$NON-NLS-1$ //$NON-NLS-2$

allowTipSha1InWant = rc.getBoolean( allowTipSha1InWant = rc.getBoolean(
"uploadpack", "allowtipsha1inwant", false); //$NON-NLS-1$ //$NON-NLS-2$ "uploadpack", "allowtipsha1inwant", false); //$NON-NLS-1$ //$NON-NLS-2$
hideRefs = rc.getStringList("uploadpack", null, "hiderefs"); //$NON-NLS-1$ //$NON-NLS-2$ hideRefs = rc.getStringList("uploadpack", null, "hiderefs"); //$NON-NLS-1$ //$NON-NLS-2$
* @return strictly verify received objects? * @return strictly verify received objects?
*/ */
public boolean isFsckObjects() { public boolean isFsckObjects() {
return fsckObjects;
return fetchFsck;
} }


/** /**

Loading…
Cancel
Save