From 0aa682fc68a80704160102fc07dea5611c010746 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Wed, 12 Mar 2014 13:59:29 -0700 Subject: Check for duplicate names after folding case in ObjectChecker Mac OS X and Windows filesystems are generally case insensitive and will fold 'a' and 'A' to the same directory entry. If the checker is enforcing safe semantics for these platforms, track all names and look for duplicates after folding case and normalizing to NFC. Change-Id: I170b6f649a72d6ef322b7254943d4c604a8d25b9 --- .../src/org/eclipse/jgit/lib/ObjectChecker.java | 70 +++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) (limited to 'org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java') 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 38fd0a1eb6..1b135a924c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java @@ -48,7 +48,12 @@ import static org.eclipse.jgit.util.RawParseUtils.match; import static org.eclipse.jgit.util.RawParseUtils.nextLF; import static org.eclipse.jgit.util.RawParseUtils.parseBase10; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; 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.internal.JGitText; @@ -348,6 +353,9 @@ public class ObjectChecker { final int sz = raw.length; int ptr = 0; int lastNameB = 0, lastNameE = 0, lastMode = 0; + Set normalized = windows || macosx + ? new HashSet() + : null; while (ptr < sz) { int thisMode = 0; @@ -373,7 +381,10 @@ public class ObjectChecker { if (ptr == sz || raw[ptr] != 0) throw new CorruptObjectException("truncated in name"); checkPathSegment2(raw, thisNameB, ptr); - if (duplicateName(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"); if (lastNameB != 0) { @@ -558,4 +569,61 @@ public class ObjectChecker { public void checkBlob(final byte[] raw) throws CorruptObjectException { // 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; + } + } + } } -- cgit v1.2.3