@ -44,6 +44,10 @@
package org.eclipse.jgit.lib ;
package org.eclipse.jgit.lib ;
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_TAG ;
import static org.eclipse.jgit.lib.Constants.OBJ_TREE ;
import static org.eclipse.jgit.util.RawParseUtils.match ;
import static org.eclipse.jgit.util.RawParseUtils.match ;
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 ;
@ -54,6 +58,7 @@ 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.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 ;
@ -99,15 +104,28 @@ public class ObjectChecker {
public static final byte [ ] tagger = Constants . encodeASCII ( "tagger " ) ; //$NON-NLS-1$
public static final byte [ ] tagger = Constants . encodeASCII ( "tagger " ) ; //$NON-NLS-1$
private final MutableObjectId tempId = new MutableObjectId ( ) ;
private final MutableObjectId tempId = new MutableObjectId ( ) ;
private final MutableInteger ptrout = new MutableInteger ( ) ;
private final MutableInteger ptrout = new MutableInteger ( ) ;
private ObjectIdSet skipList ;
private boolean allowZeroMode ;
private boolean allowZeroMode ;
private boolean allowInvalidPersonIdent ;
private boolean allowInvalidPersonIdent ;
private boolean windows ;
private boolean windows ;
private boolean macosx ;
private boolean macosx ;
/ * *
* Enable accepting specific malformed ( but not horribly broken ) objects .
*
* @param objects
* collection of object names known to be broken in a non - fatal
* way that should be ignored by the checker .
* @return { @code this }
* @since 4 . 2
* /
public ObjectChecker setSkipList ( @Nullable ObjectIdSet objects ) {
skipList = objects ;
return this ;
}
/ * *
/ * *
* Enable accepting leading zero mode in tree entries .
* Enable accepting leading zero mode in tree entries .
* < p >
* < p >
@ -183,19 +201,40 @@ public class ObjectChecker {
* @throws CorruptObjectException
* @throws CorruptObjectException
* if an error is identified .
* if an error is identified .
* /
* /
public void check ( final int objType , final byte [ ] raw )
public void check ( int objType , byte [ ] raw )
throws CorruptObjectException {
check ( idFor ( objType , raw ) , objType , raw ) ;
}
/ * *
* Check an object for parsing errors .
*
* @param id
* identify of the object being checked .
* @param objType
* type of the object . Must be a valid object type code in
* { @link Constants } .
* @param raw
* the raw data which comprises the object . This should be in the
* canonical format ( that is the format used to generate the
* ObjectId of the object ) . The array is never modified .
* @throws CorruptObjectException
* if an error is identified .
* @since 4 . 2
* /
public void check ( @Nullable AnyObjectId id , int objType , byte [ ] raw )
throws CorruptObjectException {
throws CorruptObjectException {
switch ( objType ) {
switch ( objType ) {
case Constants . OBJ_COMMIT :
case OBJ_COMMIT :
checkCommit ( raw ) ;
checkCommit ( id , raw ) ;
break ;
break ;
case Constants . OBJ_TAG :
case OBJ_TAG :
checkTag ( raw ) ;
checkTag ( id , raw ) ;
break ;
break ;
case Constants . OBJ_TREE :
case OBJ_TREE :
checkTree ( raw ) ;
checkTree ( id , raw ) ;
break ;
break ;
case Constants . OBJ_BLOB :
case OBJ_BLOB :
checkBlob ( raw ) ;
checkBlob ( raw ) ;
break ;
break ;
default :
default :
@ -214,9 +253,9 @@ public class ObjectChecker {
}
}
}
}
private int personIdent ( final byte [ ] raw , int ptr ) {
private int personIdent ( byte [ ] raw , int ptr , @Nullable AnyObjectId id ) {
if ( allowInvalidPersonIdent )
if ( allowInvalidPersonIdent | | skip ( id ) )
return nextLF ( raw , ptr ) - 1 ;
return nextLF ( raw , ptr ) ;
final int emailB = nextLF ( raw , ptr , '<' ) ;
final int emailB = nextLF ( raw , ptr , '<' ) ;
if ( emailB = = ptr | | raw [ emailB - 1 ] ! = '<' )
if ( emailB = = ptr | | raw [ emailB - 1 ] ! = '<' )
@ -238,18 +277,38 @@ public class ObjectChecker {
parseBase10 ( raw , ptr + 1 , ptrout ) ; // tz offset
parseBase10 ( raw , ptr + 1 , ptrout ) ; // tz offset
if ( ptr + 1 = = ptrout . value )
if ( ptr + 1 = = ptrout . value )
return - 1 ;
return - 1 ;
return ptrout . value ;
ptr = ptrout . value ;
if ( raw [ ptr + + ] = = '\n' )
return ptr ;
return - 1 ;
}
/ * *
* Check a commit for errors .
*
* @param raw
* the commit data . The array is never modified .
* @throws CorruptObjectException
* if any error was detected .
* /
public void checkCommit ( byte [ ] raw ) throws CorruptObjectException {
checkCommit ( idFor ( OBJ_COMMIT , raw ) , raw ) ;
}
}
/ * *
/ * *
* Check a commit for errors .
* Check a commit for errors .
*
*
* @param id
* identity of the object being checked .
* @param raw
* @param raw
* the commit data . The array is never modified .
* the commit data . The array is never modified .
* @throws CorruptObjectException
* @throws CorruptObjectException
* if any error was detected .
* if any error was detected .
* @since 4 . 2
* /
* /
public void checkCommit ( final byte [ ] raw ) throws CorruptObjectException {
public void checkCommit ( @Nullable AnyObjectId id , byte [ ] raw )
throws CorruptObjectException {
int ptr = 0 ;
int ptr = 0 ;
if ( ( ptr = match ( raw , ptr , tree ) ) < 0 )
if ( ( ptr = match ( raw , ptr , tree ) ) < 0 )
@ -266,30 +325,54 @@ public class ObjectChecker {
JGitText . get ( ) . corruptObjectInvalidParent ) ;
JGitText . get ( ) . corruptObjectInvalidParent ) ;
}
}
if ( ( ptr = match ( raw , ptr , author ) ) < 0 )
int p = match ( raw , ptr , author ) ;
throw new CorruptObjectException (
if ( p > ptr ) {
JGitText . get ( ) . corruptObjectNoAuthor ) ;
if ( ( ptr = personIdent ( raw , p , id ) ) < 0 ) {
if ( ( ptr = personIdent ( raw , ptr ) ) < 0 | | raw [ ptr + + ] ! = '\n' )
throw new CorruptObjectException (
throw new CorruptObjectException (
JGitText . get ( ) . corruptObjectInvalidAuthor ) ;
JGitText . get ( ) . corruptObjectInvalidAuthor ) ;
}
if ( ( ptr = match ( raw , ptr , committer ) ) < 0 )
} else if ( ! skip ( id ) ) {
throw new CorruptObjectException (
throw new CorruptObjectException (
JGitText . get ( ) . corruptObjectNoCommitter ) ;
JGitText . get ( ) . corruptObjectNoAuthor ) ;
if ( ( ptr = personIdent ( raw , ptr ) ) < 0 | | raw [ ptr + + ] ! = '\n' )
}
p = match ( raw , ptr , committer ) ;
if ( p > ptr ) {
if ( ( ptr = personIdent ( raw , p , id ) ) < 0 ) {
throw new CorruptObjectException (
throw new CorruptObjectException (
JGitText . get ( ) . corruptObjectInvalidCommitter ) ;
JGitText . get ( ) . corruptObjectInvalidCommitter ) ;
}
}
} else if ( ! skip ( id ) ) {
throw new CorruptObjectException (
JGitText . get ( ) . corruptObjectNoCommitter ) ;
}
}
/ * *
* Check an annotated tag for errors .
*
* @param raw
* the tag data . The array is never modified .
* @throws CorruptObjectException
* if any error was detected .
* /
public void checkTag ( byte [ ] raw ) throws CorruptObjectException {
checkTag ( idFor ( OBJ_TAG , raw ) , raw ) ;
}
/ * *
/ * *
* Check an annotated tag for errors .
* Check an annotated tag for errors .
*
*
* @param id
* identity of the object being checked .
* @param raw
* @param raw
* the tag data . The array is never modified .
* the tag data . The array is never modified .
* @throws CorruptObjectException
* @throws CorruptObjectException
* if any error was detected .
* if any error was detected .
* @since 4 . 2
* /
* /
public void checkTag ( final byte [ ] raw ) throws CorruptObjectException {
public void checkTag ( @Nullable AnyObjectId id , byte [ ] raw )
throws CorruptObjectException {
int ptr = 0 ;
int ptr = 0 ;
if ( ( ptr = match ( raw , ptr , object ) ) < 0 )
if ( ( ptr = match ( raw , ptr , object ) ) < 0 )
@ -304,17 +387,18 @@ public class ObjectChecker {
JGitText . get ( ) . corruptObjectNoTypeHeader ) ;
JGitText . get ( ) . corruptObjectNoTypeHeader ) ;
ptr = nextLF ( raw , ptr ) ;
ptr = nextLF ( raw , ptr ) ;
if ( ( ptr = match ( raw , ptr , tag ) ) < 0 )
if ( match ( raw , ptr , tag ) < 0 & & ! skip ( id ) )
throw new CorruptObjectException (
throw new CorruptObjectException (
JGitText . get ( ) . corruptObjectNoTagHeader ) ;
JGitText . get ( ) . corruptObjectNoTagHeader ) ;
ptr = nextLF ( raw , ptr ) ;
ptr = nextLF ( raw , ptr ) ;
if ( ( ptr = match ( raw , ptr , tagger ) ) > 0 ) {
if ( ( ptr = match ( raw , ptr , tagger ) ) > 0 ) {
if ( ( ptr = personIdent ( raw , ptr ) ) < 0 | | raw [ ptr + + ] ! = '\n' )
if ( ( ptr = personIdent ( raw , ptr , id ) ) < 0 ) {
throw new CorruptObjectException (
throw new CorruptObjectException (
JGitText . get ( ) . corruptObjectInvalidTagger ) ;
JGitText . get ( ) . corruptObjectInvalidTagger ) ;
}
}
}
}
}
private static int lastPathChar ( final int mode ) {
private static int lastPathChar ( final int mode ) {
return FileMode . TREE . equals ( mode ) ? '/' : '\0' ;
return FileMode . TREE . equals ( mode ) ? '/' : '\0' ;
@ -381,11 +465,28 @@ public class ObjectChecker {
* @throws CorruptObjectException
* @throws CorruptObjectException
* if any error was detected .
* if any error was detected .
* /
* /
public void checkTree ( final byte [ ] raw ) throws CorruptObjectException {
public void checkTree ( byte [ ] raw ) throws CorruptObjectException {
checkTree ( idFor ( OBJ_TREE , raw ) , raw ) ;
}
/ * *
* Check a canonical formatted tree for errors .
*
* @param id
* identity of the object being checked .
* @param raw
* the raw tree data . The array is never modified .
* @throws CorruptObjectException
* if any error was detected .
* @since 4 . 2
* /
public void checkTree ( @Nullable AnyObjectId id , byte [ ] raw )
throws CorruptObjectException {
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
boolean skip = skip ( id ) ;
Set < String > normalized = ! skip & & ( windows | | macosx )
? new HashSet < String > ( )
? new HashSet < String > ( )
: null ;
: null ;
@ -401,7 +502,7 @@ public class ObjectChecker {
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 )
if ( thisMode = = 0 & & c = = '0' & & ! allowZeroMode & & ! skip )
throw new CorruptObjectException (
throw new CorruptObjectException (
JGitText . get ( ) . corruptObjectInvalidModeStartsZero ) ;
JGitText . get ( ) . corruptObjectInvalidModeStartsZero ) ;
thisMode < < = 3 ;
thisMode < < = 3 ;
@ -418,16 +519,16 @@ public class ObjectChecker {
if ( ptr = = sz | | raw [ ptr ] ! = 0 )
if ( ptr = = sz | | raw [ ptr ] ! = 0 )
throw new CorruptObjectException (
throw new CorruptObjectException (
JGitText . get ( ) . corruptObjectTruncatedInName ) ;
JGitText . get ( ) . corruptObjectTruncatedInName ) ;
checkPathSegment2 ( raw , thisNameB , ptr ) ;
checkPathSegment2 ( raw , thisNameB , ptr , skip ) ;
if ( normalized ! = null ) {
if ( normalized ! = null ) {
if ( ! normalized . add ( normalize ( raw , thisNameB , ptr ) ) )
if ( ! normalized . add ( normalize ( raw , thisNameB , ptr ) ) )
throw new CorruptObjectException (
throw new CorruptObjectException (
JGitText . get ( ) . corruptObjectDuplicateEntryNames ) ;
JGitText . get ( ) . corruptObjectDuplicateEntryNames ) ;
} else if ( duplicateName ( raw , thisNameB , ptr ) )
} else if ( ! skip & & duplicateName ( raw , thisNameB , ptr ) )
throw new CorruptObjectException (
throw new CorruptObjectException (
JGitText . get ( ) . corruptObjectDuplicateEntryNames ) ;
JGitText . get ( ) . corruptObjectDuplicateEntryNames ) ;
if ( lastNameB ! = 0 ) {
if ( ! skip & & 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 )
if ( cmp > 0 )
@ -468,6 +569,19 @@ public class ObjectChecker {
return ptr ;
return ptr ;
}
}
@SuppressWarnings ( "resource" )
@Nullable
private ObjectId idFor ( int objType , byte [ ] raw ) {
if ( skipList ! = null ) {
return new ObjectInserter . Formatter ( ) . idFor ( objType , raw ) ;
}
return null ;
}
private boolean skip ( @Nullable AnyObjectId id ) {
return skipList ! = null & & id ! = null & & skipList . contains ( id ) ;
}
/ * *
/ * *
* Check tree path entry for validity .
* Check tree path entry for validity .
* < p >
* < p >
@ -522,10 +636,10 @@ public class ObjectChecker {
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 ) ;
checkPathSegment2 ( raw , ptr , end , false ) ;
}
}
private void checkPathSegment2 ( byte [ ] raw , int ptr , int end )
private void checkPathSegment2 ( byte [ ] raw , int ptr , int end , boolean skip )
throws CorruptObjectException {
throws CorruptObjectException {
if ( ptr = = end )
if ( ptr = = end )
throw new CorruptObjectException (
throw new CorruptObjectException (
@ -541,23 +655,24 @@ public class ObjectChecker {
JGitText . get ( ) . corruptObjectNameDotDot ) ;
JGitText . get ( ) . corruptObjectNameDotDot ) ;
break ;
break ;
case 4 :
case 4 :
if ( isGit ( raw , ptr + 1 ) )
if ( ! skip & & isGit ( raw , ptr + 1 ) )
throw new CorruptObjectException ( String . format (
throw new CorruptObjectException ( String . format (
JGitText . get ( ) . corruptObjectInvalidName ,
JGitText . get ( ) . corruptObjectInvalidName ,
RawParseUtils . decode ( raw , ptr , end ) ) ) ;
RawParseUtils . decode ( raw , ptr , end ) ) ) ;
break ;
break ;
default :
default :
if ( end - ptr > 4 & & isNormalizedGit ( raw , ptr + 1 , end ) )
if ( ! skip & & end - ptr > 4
& & isNormalizedGit ( raw , ptr + 1 , end ) )
throw new CorruptObjectException ( String . format (
throw new CorruptObjectException ( String . format (
JGitText . get ( ) . corruptObjectInvalidName ,
JGitText . get ( ) . corruptObjectInvalidName ,
RawParseUtils . decode ( raw , ptr , end ) ) ) ;
RawParseUtils . decode ( raw , ptr , end ) ) ) ;
}
}
} else if ( isGitTilde1 ( raw , ptr , end ) ) {
} else if ( ! skip & & isGitTilde1 ( raw , ptr , end ) ) {
throw new CorruptObjectException ( String . format (
throw new CorruptObjectException ( 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 ) )
if ( macosx & & isMacHFSGit ( raw , ptr , end ) )
throw new CorruptObjectException ( String . format (
throw new CorruptObjectException ( String . format (
JGitText . get ( ) . corruptObjectInvalidNameIgnorableUnicode ,
JGitText . get ( ) . corruptObjectInvalidNameIgnorableUnicode ,
@ -573,6 +688,7 @@ public class ObjectChecker {
checkNotWindowsDevice ( raw , ptr , end ) ;
checkNotWindowsDevice ( raw , ptr , end ) ;
}
}
}
}
}
// 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