]> git.proxmox.com Git - libgit2.git/blobdiff - src/object.c
New upstream version 1.4.3+dfsg.1
[libgit2.git] / src / object.c
index a178e0ed38a261ca213c9643681f85f4b78b73ad..7bc256fcebd68fa0cb54688f71e0081879269e42 100644 (file)
  * This file is part of libgit2, distributed under the GNU GPL v2 with
  * a Linking Exception. For full terms see the included COPYING file.
  */
+
+#include "object.h"
+
 #include "git2/object.h"
 
-#include "common.h"
 #include "repository.h"
 
+#include "buf.h"
 #include "commit.h"
+#include "hash.h"
 #include "tree.h"
 #include "blob.h"
+#include "oid.h"
 #include "tag.h"
 
-static const int OBJECT_BASE_SIZE = 4096;
+bool git_object__strict_input_validation = true;
+
+extern int git_odb_hash(git_oid *out, const void *data, size_t len, git_object_t type);
+size_t git_object__size(git_object_t type);
 
 typedef struct {
        const char      *str;   /* type name string */
        size_t          size;   /* size in bytes of the object structure */
 
        int  (*parse)(void *self, git_odb_object *obj);
+       int  (*parse_raw)(void *self, const char *data, size_t size);
        void (*free)(void *self);
 } git_object_def;
 
 static git_object_def git_objects_table[] = {
-       /* 0 = GIT_OBJ__EXT1 */
-       { "", 0, NULL, NULL },
+       /* 0 = GIT_OBJECT__EXT1 */
+       { "", 0, NULL, NULL, NULL },
 
-       /* 1 = GIT_OBJ_COMMIT */
-       { "commit", sizeof(git_commit), git_commit__parse, git_commit__free },
+       /* 1 = GIT_OBJECT_COMMIT */
+       { "commit", sizeof(git_commit), git_commit__parse, git_commit__parse_raw, git_commit__free },
 
-       /* 2 = GIT_OBJ_TREE */
-       { "tree", sizeof(git_tree), git_tree__parse, git_tree__free },
+       /* 2 = GIT_OBJECT_TREE */
+       { "tree", sizeof(git_tree), git_tree__parse, git_tree__parse_raw, git_tree__free },
 
-       /* 3 = GIT_OBJ_BLOB */
-       { "blob", sizeof(git_blob), git_blob__parse, git_blob__free },
+       /* 3 = GIT_OBJECT_BLOB */
+       { "blob", sizeof(git_blob), git_blob__parse, git_blob__parse_raw, git_blob__free },
 
-       /* 4 = GIT_OBJ_TAG */
-       { "tag", sizeof(git_tag), git_tag__parse, git_tag__free },
+       /* 4 = GIT_OBJECT_TAG */
+       { "tag", sizeof(git_tag), git_tag__parse, git_tag__parse_raw, git_tag__free },
 
-       /* 5 = GIT_OBJ__EXT2 */
-       { "", 0, NULL, NULL },
-       /* 6 = GIT_OBJ_OFS_DELTA */
-       { "OFS_DELTA", 0, NULL, NULL },
-       /* 7 = GIT_OBJ_REF_DELTA */
-       { "REF_DELTA", 0, NULL, NULL },
+       /* 5 = GIT_OBJECT__EXT2 */
+       { "", 0, NULL, NULL, NULL },
+       /* 6 = GIT_OBJECT_OFS_DELTA */
+       { "OFS_DELTA", 0, NULL, NULL, NULL },
+       /* 7 = GIT_OBJECT_REF_DELTA */
+       { "REF_DELTA", 0, NULL, NULL, NULL },
 };
 
+int git_object__from_raw(
+       git_object **object_out,
+       const char *data,
+       size_t size,
+       git_object_t type)
+{
+       git_object_def *def;
+       git_object *object;
+       size_t object_size;
+       int error;
+
+       GIT_ASSERT_ARG(object_out);
+       *object_out = NULL;
+
+       /* Validate type match */
+       if (type != GIT_OBJECT_BLOB && type != GIT_OBJECT_TREE && type != GIT_OBJECT_COMMIT && type != GIT_OBJECT_TAG) {
+               git_error_set(GIT_ERROR_INVALID, "the requested type is invalid");
+               return GIT_ENOTFOUND;
+       }
+
+       if ((object_size = git_object__size(type)) == 0) {
+               git_error_set(GIT_ERROR_INVALID, "the requested type is invalid");
+               return GIT_ENOTFOUND;
+       }
+
+       /* Allocate and initialize base object */
+       object = git__calloc(1, object_size);
+       GIT_ERROR_CHECK_ALLOC(object);
+       object->cached.flags = GIT_CACHE_STORE_PARSED;
+       object->cached.type = type;
+       if ((error = git_odb_hash(&object->cached.oid, data, size, type)) < 0)
+               return error;
+
+       /* Parse raw object data */
+       def = &git_objects_table[type];
+       GIT_ASSERT(def->free && def->parse_raw);
+
+       if ((error = def->parse_raw(object, data, size)) < 0) {
+               def->free(object);
+               return error;
+       }
+
+       git_cached_obj_incref(object);
+       *object_out = object;
+
+       return 0;
+}
+
 int git_object__from_odb_object(
        git_object **object_out,
        git_repository *repo,
        git_odb_object *odb_obj,
-       git_otype type)
+       git_object_t type)
 {
        int error;
        size_t object_size;
        git_object_def *def;
        git_object *object = NULL;
 
-       assert(object_out);
+       GIT_ASSERT_ARG(object_out);
        *object_out = NULL;
 
        /* Validate type match */
-       if (type != GIT_OBJ_ANY && type != odb_obj->cached.type) {
-               giterr_set(GITERR_INVALID,
-                       "The requested type does not match the type in the ODB");
+       if (type != GIT_OBJECT_ANY && type != odb_obj->cached.type) {
+               git_error_set(GIT_ERROR_INVALID,
+                       "the requested type does not match the type in the ODB");
                return GIT_ENOTFOUND;
        }
 
        if ((object_size = git_object__size(odb_obj->cached.type)) == 0) {
-               giterr_set(GITERR_INVALID, "The requested type is invalid");
+               git_error_set(GIT_ERROR_INVALID, "the requested type is invalid");
                return GIT_ENOTFOUND;
        }
 
        /* Allocate and initialize base object */
        object = git__calloc(1, object_size);
-       GITERR_CHECK_ALLOC(object);
+       GIT_ERROR_CHECK_ALLOC(object);
 
        git_oid_cpy(&object->cached.oid, &odb_obj->cached.oid);
        object->cached.type = odb_obj->cached.type;
@@ -85,19 +142,24 @@ int git_object__from_odb_object(
 
        /* Parse raw object data */
        def = &git_objects_table[odb_obj->cached.type];
-       assert(def->free && def->parse);
+       GIT_ASSERT(def->free && def->parse);
 
-       if ((error = def->parse(object, odb_obj)) < 0)
+       if ((error = def->parse(object, odb_obj)) < 0) {
+               /*
+                * parse returns EINVALID on invalid data; downgrade
+                * that to a normal -1 error code.
+                */
                def->free(object);
-       else
-               *object_out = git_cache_store_parsed(&repo->objects, object);
+               return -1;
+       }
 
-       return error;
+       *object_out = git_cache_store_parsed(&repo->objects, object);
+       return 0;
 }
 
 void git_object__free(void *obj)
 {
-       git_otype type = ((git_object *)obj)->cached.type;
+       git_object_t type = ((git_object *)obj)->cached.type;
 
        if (type < 0 || ((size_t)type) >= ARRAY_SIZE(git_objects_table) ||
                !git_objects_table[type].free)
@@ -111,17 +173,19 @@ int git_object_lookup_prefix(
        git_repository *repo,
        const git_oid *id,
        size_t len,
-       git_otype type)
+       git_object_t type)
 {
        git_object *object = NULL;
        git_odb *odb = NULL;
        git_odb_object *odb_obj = NULL;
        int error = 0;
 
-       assert(repo && object_out && id);
+       GIT_ASSERT_ARG(repo);
+       GIT_ASSERT_ARG(object_out);
+       GIT_ASSERT_ARG(id);
 
        if (len < GIT_OID_MINPREFIXLEN) {
-               giterr_set(GITERR_OBJECT, "Ambiguous lookup - OID prefix is too short");
+               git_error_set(GIT_ERROR_OBJECT, "ambiguous lookup - OID prefix is too short");
                return GIT_EAMBIGUOUS;
        }
 
@@ -129,10 +193,10 @@ int git_object_lookup_prefix(
        if (error < 0)
                return error;
 
-       if (len > GIT_OID_RAWSZ)
-               len = GIT_OID_RAWSZ;
+       if (len > GIT_OID_HEXSZ)
+               len = GIT_OID_HEXSZ;
 
-       if (len == GIT_OID_RAWSZ) {
+       if (len == GIT_OID_HEXSZ) {
                git_cached_obj *cached = NULL;
 
                /* We want to match the full id : we can first look up in the cache,
@@ -143,10 +207,10 @@ int git_object_lookup_prefix(
                        if (cached->flags == GIT_CACHE_STORE_PARSED) {
                                object = (git_object *)cached;
 
-                               if (type != GIT_OBJ_ANY && type != object->cached.type) {
+                               if (type != GIT_OBJECT_ANY && type != object->cached.type) {
                                        git_object_free(object);
-                                       giterr_set(GITERR_INVALID,
-                                               "The requested type does not match the type in ODB");
+                                       git_error_set(GIT_ERROR_INVALID,
+                                               "the requested type does not match the type in the ODB");
                                        return GIT_ENOTFOUND;
                                }
 
@@ -155,7 +219,7 @@ int git_object_lookup_prefix(
                        } else if (cached->flags == GIT_CACHE_STORE_RAW) {
                                odb_obj = (git_odb_object *)cached;
                        } else {
-                               assert(!"Wrong caching type in the global object cache");
+                               GIT_ASSERT(!"Wrong caching type in the global object cache");
                        }
                } else {
                        /* Object was not found in the cache, let's explore the backends.
@@ -166,15 +230,11 @@ int git_object_lookup_prefix(
                        error = git_odb_read(&odb_obj, odb, id);
                }
        } else {
-               git_oid short_oid;
+               git_oid short_oid = {{ 0 }};
 
-               /* We copy the first len*4 bits from id and fill the remaining with 0s */
-               memcpy(short_oid.id, id->id, (len + 1) / 2);
-               if (len % 2)
-                       short_oid.id[len / 2] &= 0xF0;
-               memset(short_oid.id + (len + 1) / 2, 0, (GIT_OID_RAWSZ - len) / 2);
+               git_oid__cpy_prefix(&short_oid, id, len);
 
-               /* If len < GIT_OID_RAWSZ (a strict short oid was given), we have
+               /* If len < GIT_OID_HEXSZ (a strict short oid was given), we have
                 * 2 options :
                 * - We always search in the cache first. If we find that short oid is
                 *      ambiguous, we can stop. But in all the other cases, we must then
@@ -197,7 +257,7 @@ int git_object_lookup_prefix(
        return error;
 }
 
-int git_object_lookup(git_object **object_out, git_repository *repo, const git_oid *id, git_otype type) {
+int git_object_lookup(git_object **object_out, git_repository *repo, const git_oid *id, git_object_t type) {
        return git_object_lookup_prefix(object_out, repo, id, GIT_OID_HEXSZ, type);
 }
 
@@ -211,23 +271,23 @@ void git_object_free(git_object *object)
 
 const git_oid *git_object_id(const git_object *obj)
 {
-       assert(obj);
+       GIT_ASSERT_ARG_WITH_RETVAL(obj, NULL);
        return &obj->cached.oid;
 }
 
-git_otype git_object_type(const git_object *obj)
+git_object_t git_object_type(const git_object *obj)
 {
-       assert(obj);
+       GIT_ASSERT_ARG_WITH_RETVAL(obj, GIT_OBJECT_INVALID);
        return obj->cached.type;
 }
 
 git_repository *git_object_owner(const git_object *obj)
 {
-       assert(obj);
+       GIT_ASSERT_ARG_WITH_RETVAL(obj, NULL);
        return obj->repo;
 }
 
-const char *git_object_type2string(git_otype type)
+const char *git_object_type2string(git_object_t type)
 {
        if (type < 0 || ((size_t) type) >= ARRAY_SIZE(git_objects_table))
                return "";
@@ -235,21 +295,30 @@ const char *git_object_type2string(git_otype type)
        return git_objects_table[type].str;
 }
 
-git_otype git_object_string2type(const char *str)
+git_object_t git_object_string2type(const char *str)
+{
+       if (!str)
+               return GIT_OBJECT_INVALID;
+
+       return git_object_stringn2type(str, strlen(str));
+}
+
+git_object_t git_object_stringn2type(const char *str, size_t len)
 {
        size_t i;
 
-       if (!str || !*str)
-               return GIT_OBJ_BAD;
+       if (!str || !len || !*str)
+               return GIT_OBJECT_INVALID;
 
        for (i = 0; i < ARRAY_SIZE(git_objects_table); i++)
-               if (!strcmp(str, git_objects_table[i].str))
-                       return (git_otype)i;
+               if (*git_objects_table[i].str &&
+                       !git__prefixncmp(str, len, git_objects_table[i].str))
+                       return (git_object_t)i;
 
-       return GIT_OBJ_BAD;
+       return GIT_OBJECT_INVALID;
 }
 
-int git_object_typeisloose(git_otype type)
+int git_object_typeisloose(git_object_t type)
 {
        if (type < 0 || ((size_t) type) >= ARRAY_SIZE(git_objects_table))
                return 0;
@@ -257,7 +326,7 @@ int git_object_typeisloose(git_otype type)
        return (git_objects_table[type].size > 0) ? 1 : 0;
 }
 
-size_t git_object__size(git_otype type)
+size_t git_object__size(git_object_t type)
 {
        if (type < 0 || ((size_t) type) >= ARRAY_SIZE(git_objects_table))
                return 0;
@@ -267,17 +336,17 @@ size_t git_object__size(git_otype type)
 
 static int dereference_object(git_object **dereferenced, git_object *obj)
 {
-       git_otype type = git_object_type(obj);
+       git_object_t type = git_object_type(obj);
 
        switch (type) {
-       case GIT_OBJ_COMMIT:
+       case GIT_OBJECT_COMMIT:
                return git_commit_tree((git_tree **)dereferenced, (git_commit*)obj);
 
-       case GIT_OBJ_TAG:
+       case GIT_OBJECT_TAG:
                return git_tag_target(dereferenced, (git_tag*)obj);
 
-       case GIT_OBJ_BLOB:
-       case GIT_OBJ_TREE:
+       case GIT_OBJECT_BLOB:
+       case GIT_OBJECT_TREE:
                return GIT_EPEEL;
 
        default:
@@ -285,7 +354,7 @@ static int dereference_object(git_object **dereferenced, git_object *obj)
        }
 }
 
-static int peel_error(int error, const git_oid *oid, git_otype type)
+static int peel_error(int error, const git_oid *oid, git_object_t type)
 {
        const char *type_name;
        char hex_oid[GIT_OID_HEXSZ + 1];
@@ -295,29 +364,29 @@ static int peel_error(int error, const git_oid *oid, git_otype type)
        git_oid_fmt(hex_oid, oid);
        hex_oid[GIT_OID_HEXSZ] = '\0';
 
-       giterr_set(GITERR_OBJECT, "The git_object of id '%s' can not be "
-               "successfully peeled into a %s (git_otype=%i).", hex_oid, type_name, type);
+       git_error_set(GIT_ERROR_OBJECT, "the git_object of id '%s' can not be "
+               "successfully peeled into a %s (git_object_t=%i).", hex_oid, type_name, type);
 
        return error;
 }
 
-static int check_type_combination(git_otype type, git_otype target)
+static int check_type_combination(git_object_t type, git_object_t target)
 {
        if (type == target)
                return 0;
 
        switch (type) {
-       case GIT_OBJ_BLOB:
-       case GIT_OBJ_TREE:
+       case GIT_OBJECT_BLOB:
+       case GIT_OBJECT_TREE:
                /* a blob or tree can never be peeled to anything but themselves */
                return GIT_EINVALIDSPEC;
                break;
-       case GIT_OBJ_COMMIT:
+       case GIT_OBJECT_COMMIT:
                /* a commit can only be peeled to a tree */
-               if (target != GIT_OBJ_TREE && target != GIT_OBJ_ANY)
+               if (target != GIT_OBJECT_TREE && target != GIT_OBJECT_ANY)
                        return GIT_EINVALIDSPEC;
                break;
-       case GIT_OBJ_TAG:
+       case GIT_OBJECT_TAG:
                /* a tag may point to anything, so we let anything through */
                break;
        default:
@@ -330,18 +399,19 @@ static int check_type_combination(git_otype type, git_otype target)
 int git_object_peel(
        git_object **peeled,
        const git_object *object,
-       git_otype target_type)
+       git_object_t target_type)
 {
        git_object *source, *deref = NULL;
        int error;
 
-       assert(object && peeled);
+       GIT_ASSERT_ARG(object);
+       GIT_ASSERT_ARG(peeled);
 
-       assert(target_type == GIT_OBJ_TAG ||
-               target_type == GIT_OBJ_COMMIT ||
-               target_type == GIT_OBJ_TREE ||
-               target_type == GIT_OBJ_BLOB ||
-               target_type == GIT_OBJ_ANY);
+       GIT_ASSERT_ARG(target_type == GIT_OBJECT_TAG ||
+               target_type == GIT_OBJECT_COMMIT ||
+               target_type == GIT_OBJECT_TREE ||
+               target_type == GIT_OBJECT_BLOB ||
+               target_type == GIT_OBJECT_ANY);
 
        if ((error = check_type_combination(git_object_type(object), target_type)) < 0)
                return peel_error(error, git_object_id(object), target_type);
@@ -361,7 +431,7 @@ int git_object_peel(
                        return 0;
                }
 
-               if (target_type == GIT_OBJ_ANY &&
+               if (target_type == GIT_OBJECT_ANY &&
                        git_object_type(deref) != git_object_type(object))
                {
                        *peeled = deref;
@@ -394,23 +464,25 @@ int git_object_lookup_bypath(
                git_object **out,
                const git_object *treeish,
                const char *path,
-               git_otype type)
+               git_object_t type)
 {
        int error = -1;
        git_tree *tree = NULL;
        git_tree_entry *entry = NULL;
 
-       assert(out && treeish && path);
+       GIT_ASSERT_ARG(out);
+       GIT_ASSERT_ARG(treeish);
+       GIT_ASSERT_ARG(path);
 
-       if ((error = git_object_peel((git_object**)&tree, treeish, GIT_OBJ_TREE)) < 0 ||
+       if ((error = git_object_peel((git_object**)&tree, treeish, GIT_OBJECT_TREE)) < 0 ||
                 (error = git_tree_entry_bypath(&entry, tree, path)) < 0)
        {
                goto cleanup;
        }
 
-       if (type != GIT_OBJ_ANY && git_tree_entry_type(entry) != type)
+       if (type != GIT_OBJECT_ANY && git_tree_entry_type(entry) != type)
        {
-               giterr_set(GITERR_OBJECT,
+               git_error_set(GIT_ERROR_OBJECT,
                                "object at path '%s' is not of the asked-for type %d",
                                path, type);
                error = GIT_EINVALIDSPEC;
@@ -425,19 +497,19 @@ cleanup:
        return error;
 }
 
-int git_object_short_id(git_buf *out, const git_object *obj)
+static int git_object__short_id(git_str *out, const git_object *obj)
 {
        git_repository *repo;
        int len = GIT_ABBREV_DEFAULT, error;
        git_oid id = {{0}};
        git_odb *odb;
 
-       assert(out && obj);
+       GIT_ASSERT_ARG(out);
+       GIT_ASSERT_ARG(obj);
 
-       git_buf_sanitize(out);
        repo = git_object_owner(obj);
 
-       if ((error = git_repository__cvar(&len, repo, GIT_CVAR_ABBREV)) < 0)
+       if ((error = git_repository__configmap_lookup(&len, repo, GIT_CONFIGMAP_ABBREV)) < 0)
                return error;
 
        if ((error = git_repository_odb(&odb, repo)) < 0)
@@ -453,11 +525,11 @@ int git_object_short_id(git_buf *out, const git_object *obj)
                if (error != GIT_EAMBIGUOUS)
                        break;
 
-               giterr_clear();
+               git_error_clear();
                len++;
        }
 
-       if (!error && !(error = git_buf_grow(out, len + 1))) {
+       if (!error && !(error = git_str_grow(out, len + 1))) {
                git_oid_tostr(out->ptr, len + 1, &id);
                out->size = len;
        }
@@ -467,3 +539,63 @@ int git_object_short_id(git_buf *out, const git_object *obj)
        return error;
 }
 
+int git_object_short_id(git_buf *out, const git_object *obj)
+{
+       GIT_BUF_WRAP_PRIVATE(out, git_object__short_id, obj);
+}
+
+bool git_object__is_valid(
+       git_repository *repo, const git_oid *id, git_object_t expected_type)
+{
+       git_odb *odb;
+       git_object_t actual_type;
+       size_t len;
+       int error;
+
+       if (!git_object__strict_input_validation)
+               return true;
+
+       if ((error = git_repository_odb__weakptr(&odb, repo)) < 0 ||
+               (error = git_odb_read_header(&len, &actual_type, odb, id)) < 0)
+               return false;
+
+       if (expected_type != GIT_OBJECT_ANY && expected_type != actual_type) {
+               git_error_set(GIT_ERROR_INVALID,
+                       "the requested type does not match the type in the ODB");
+               return false;
+       }
+
+       return true;
+}
+
+int git_object_rawcontent_is_valid(
+       int *valid,
+       const char *buf,
+       size_t len,
+       git_object_t type)
+{
+       git_object *obj = NULL;
+       int error;
+
+       GIT_ASSERT_ARG(valid);
+       GIT_ASSERT_ARG(buf);
+
+       /* Blobs are always valid; don't bother parsing. */
+       if (type == GIT_OBJECT_BLOB) {
+               *valid = 1;
+               return 0;
+       }
+
+       error = git_object__from_raw(&obj, buf, len, type);
+       git_object_free(obj);
+
+       if (error == 0) {
+               *valid = 1;
+               return 0;
+       } else if (error == GIT_EINVALID) {
+               *valid = 0;
+               return 0;
+       }
+
+       return error;
+}