blob: 82a8c382577731046bdf15c80a9ff660c2cc302f [file] [log] [blame]
/*
* Copyright (C) 2008, Google Inc.
* Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
* 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.eclipse.jgit.util.RawParseUtils.match;
import static org.eclipse.jgit.util.RawParseUtils.nextLF;
import static org.eclipse.jgit.util.RawParseUtils.parseBase10;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.util.MutableInteger;
/**
* Verifies that an object is formatted correctly.
* <p>
* Verifications made by this class only check that the fields of an object are
* formatted correctly. The ObjectId checksum of the object is not verified, and
* connectivity links between objects are also not verified. Its assumed that
* the caller can provide both of these validations on its own.
* <p>
* Instances of this class are not thread safe, but they may be reused to
* perform multiple object validations.
*/
public class ObjectChecker {
/** Header "tree " */
public static final byte[] tree = Constants.encodeASCII("tree ");
/** Header "parent " */
public static final byte[] parent = Constants.encodeASCII("parent ");
/** Header "author " */
public static final byte[] author = Constants.encodeASCII("author ");
/** Header "committer " */
public static final byte[] committer = Constants.encodeASCII("committer ");
/** Header "encoding " */
public static final byte[] encoding = Constants.encodeASCII("encoding ");
/** Header "object " */
public static final byte[] object = Constants.encodeASCII("object ");
/** Header "type " */
public static final byte[] type = Constants.encodeASCII("type ");
/** Header "tag " */
public static final byte[] tag = Constants.encodeASCII("tag ");
/** Header "tagger " */
public static final byte[] tagger = Constants.encodeASCII("tagger ");
private final MutableObjectId tempId = new MutableObjectId();
private final MutableInteger ptrout = new MutableInteger();
/**
* Check an object for parsing errors.
*
* @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.
*/
public void check(final int objType, final byte[] raw)
throws CorruptObjectException {
switch (objType) {
case Constants.OBJ_COMMIT:
checkCommit(raw);
break;
case Constants.OBJ_TAG:
checkTag(raw);
break;
case Constants.OBJ_TREE:
checkTree(raw);
break;
case Constants.OBJ_BLOB:
checkBlob(raw);
break;
default:
throw new CorruptObjectException("Invalid object type: " + objType);
}
}
private int id(final byte[] raw, final int ptr) {
try {
tempId.fromString(raw, ptr);
return ptr + Constants.OBJECT_ID_STRING_LENGTH;
} catch (IllegalArgumentException e) {
return -1;
}
}
private int personIdent(final byte[] raw, int ptr) {
final int emailB = nextLF(raw, ptr, '<');
if (emailB == ptr || raw[emailB - 1] != '<')
return -1;
final int emailE = nextLF(raw, emailB, '>');
if (emailE == emailB || raw[emailE - 1] != '>')
return -1;
if (emailE == raw.length || raw[emailE] != ' ')
return -1;
parseBase10(raw, emailE + 1, ptrout); // when
ptr = ptrout.value;
if (emailE + 1 == ptr)
return -1;
if (ptr == raw.length || raw[ptr] != ' ')
return -1;
parseBase10(raw, ptr + 1, ptrout); // tz offset
if (ptr + 1 == ptrout.value)
return -1;
return ptrout.value;
}
/**
* 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(final byte[] raw) throws CorruptObjectException {
int ptr = 0;
if ((ptr = match(raw, ptr, tree)) < 0)
throw new CorruptObjectException("no tree header");
if ((ptr = id(raw, ptr)) < 0 || raw[ptr++] != '\n')
throw new CorruptObjectException("invalid tree");
while (match(raw, ptr, parent) >= 0) {
ptr += parent.length;
if ((ptr = id(raw, ptr)) < 0 || raw[ptr++] != '\n')
throw new CorruptObjectException("invalid parent");
}
if ((ptr = match(raw, ptr, author)) < 0)
throw new CorruptObjectException("no author");
if ((ptr = personIdent(raw, ptr)) < 0 || raw[ptr++] != '\n')
throw new CorruptObjectException("invalid author");
if ((ptr = match(raw, ptr, committer)) < 0)
throw new CorruptObjectException("no committer");
if ((ptr = personIdent(raw, ptr)) < 0 || raw[ptr++] != '\n')
throw new CorruptObjectException("invalid committer");
}
/**
* 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(final byte[] raw) throws CorruptObjectException {
int ptr = 0;
if ((ptr = match(raw, ptr, object)) < 0)
throw new CorruptObjectException("no object header");
if ((ptr = id(raw, ptr)) < 0 || raw[ptr++] != '\n')
throw new CorruptObjectException("invalid object");
if ((ptr = match(raw, ptr, type)) < 0)
throw new CorruptObjectException("no type header");
ptr = nextLF(raw, ptr);
if ((ptr = match(raw, ptr, tag)) < 0)
throw new CorruptObjectException("no tag header");
ptr = nextLF(raw, ptr);
if ((ptr = match(raw, ptr, tagger)) < 0)
throw new CorruptObjectException("no tagger header");
if ((ptr = personIdent(raw, ptr)) < 0 || raw[ptr++] != '\n')
throw new CorruptObjectException("invalid tagger");
}
private static int lastPathChar(final int mode) {
return FileMode.TREE.equals(mode) ? '/' : '\0';
}
private static int pathCompare(final byte[] raw, int aPos, final int aEnd,
final int aMode, int bPos, final int bEnd, final int bMode) {
while (aPos < aEnd && bPos < bEnd) {
final int cmp = (raw[aPos++] & 0xff) - (raw[bPos++] & 0xff);
if (cmp != 0)
return cmp;
}
if (aPos < aEnd)
return (raw[aPos] & 0xff) - lastPathChar(bMode);
if (bPos < bEnd)
return lastPathChar(aMode) - (raw[bPos] & 0xff);
return 0;
}
private static boolean duplicateName(final byte[] raw,
final int thisNamePos, final int thisNameEnd) {
final int sz = raw.length;
int nextPtr = thisNameEnd + 1 + Constants.OBJECT_ID_LENGTH;
for (;;) {
int nextMode = 0;
for (;;) {
if (nextPtr >= sz)
return false;
final byte c = raw[nextPtr++];
if (' ' == c)
break;
nextMode <<= 3;
nextMode += c - '0';
}
final int nextNamePos = nextPtr;
for (;;) {
if (nextPtr == sz)
return false;
final byte c = raw[nextPtr++];
if (c == 0)
break;
}
if (nextNamePos + 1 == nextPtr)
return false;
final int cmp = pathCompare(raw, thisNamePos, thisNameEnd,
FileMode.TREE.getBits(), nextNamePos, nextPtr - 1, nextMode);
if (cmp < 0)
return false;
else if (cmp == 0)
return true;
nextPtr += Constants.OBJECT_ID_LENGTH;
}
}
/**
* Check a canonical formatted tree for errors.
*
* @param raw
* the raw tree data. The array is never modified.
* @throws CorruptObjectException
* if any error was detected.
*/
public void checkTree(final byte[] raw) throws CorruptObjectException {
final int sz = raw.length;
int ptr = 0;
int lastNameB = 0, lastNameE = 0, lastMode = 0;
while (ptr < sz) {
int thisMode = 0;
for (;;) {
if (ptr == sz)
throw new CorruptObjectException("truncated in mode");
final byte c = raw[ptr++];
if (' ' == c)
break;
if (c < '0' || c > '7')
throw new CorruptObjectException("invalid mode character");
if (thisMode == 0 && c == '0')
throw new CorruptObjectException("mode starts with '0'");
thisMode <<= 3;
thisMode += c - '0';
}
if (FileMode.fromBits(thisMode).getObjectType() == Constants.OBJ_BAD)
throw new CorruptObjectException("invalid mode " + thisMode);
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))
throw new CorruptObjectException("duplicate entry names");
if (lastNameB != 0) {
final int cmp = pathCompare(raw, lastNameB, lastNameE,
lastMode, thisNameB, ptr - 1, thisMode);
if (cmp > 0)
throw new CorruptObjectException("incorrectly sorted");
}
lastNameB = thisNameB;
lastNameE = ptr - 1;
lastMode = thisMode;
ptr += Constants.OBJECT_ID_LENGTH;
if (ptr > sz)
throw new CorruptObjectException("truncated in object id");
}
}
/**
* Check a blob for errors.
*
* @param raw
* the blob data. The array is never modified.
* @throws CorruptObjectException
* if any error was detected.
*/
public void checkBlob(final byte[] raw) throws CorruptObjectException {
// We can always assume the blob is valid.
}
}