Commit Diff


commit - 11cdebc1fb6c9fb957341857e86f14bd31cc3e30
commit + a440fac05e070a11112ddfdd8b655bf07c6c6bb7
blob - 883c138847c11a22bc4a38479cc6b052ff964509
blob + bbe1fafa84266d496fa4f4aacc7acb3f45f14ae7
--- got/Makefile
+++ got/Makefile
@@ -3,8 +3,8 @@
 PROG=		got
 SRCS=		got.c blame.c commit_graph.c delta.c diff.c diffoffset.c \
 		diffreg.c error.c fileindex.c object.c object_idcache.c \
-		object_idset.c opentemp.c path.c pack.c privsep.c reference.c \
-		repository.c sha1.c worktree.c inflate.c
+		object_idset.c object_parse.c opentemp.c path.c pack.c \
+		privsep.c reference.c repository.c sha1.c worktree.c inflate.c
 
 CPPFLAGS = -I${.CURDIR}/../include -I${.CURDIR}/../lib
 LDADD = -lutil -lz
blob - 9f5b22cf0abb36a3fb3306bb8dfc1a43adec26f3
blob + fab42876b12d54898f6e2d271a7566f85276b99e
--- lib/got_lib_object.h
+++ lib/got_lib_object.h
@@ -47,6 +47,3 @@ struct got_blob_object {
 	uint8_t *read_buf;
 	struct got_object_id id;
 };
-
-struct got_commit_object *got_object_commit_alloc_partial(void);
-struct got_tree_entry *got_alloc_tree_entry_partial(void);
blob - 816a3411e6cbfada5a7262f927d19c9645040bfa
blob + faa61ff95159f2d393bc44386a2f872609309452
--- lib/object.c
+++ lib/object.c
@@ -45,6 +45,7 @@
 #include "got_lib_path.h"
 #include "got_lib_inflate.h"
 #include "got_lib_object.h"
+#include "got_lib_object_parse.h"
 #include "got_lib_privsep.h"
 #include "got_lib_repository.h"
 
@@ -52,19 +53,6 @@
 #define	MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b))
 #endif
 
-#ifndef nitems
-#define nitems(_a) (sizeof(_a) / sizeof((_a)[0]))
-#endif
-
-#define GOT_OBJ_TAG_COMMIT	"commit"
-#define GOT_OBJ_TAG_TREE	"tree"
-#define GOT_OBJ_TAG_BLOB	"blob"
-
-#define GOT_COMMIT_TAG_TREE		"tree "
-#define GOT_COMMIT_TAG_PARENT		"parent "
-#define GOT_COMMIT_TAG_AUTHOR		"author "
-#define GOT_COMMIT_TAG_COMMITTER	"committer "
-
 const struct got_error *
 got_object_id_str(char **outbuf, struct got_object_id *id)
 {
@@ -132,185 +120,6 @@ got_object_get_type(struct got_object *obj)
 }
 
 static const struct got_error *
-parse_object_header(struct got_object **obj, char *buf, size_t len)
-{
-	const char *obj_tags[] = {
-		GOT_OBJ_TAG_COMMIT,
-		GOT_OBJ_TAG_TREE,
-		GOT_OBJ_TAG_BLOB
-	};
-	const int obj_types[] = {
-		GOT_OBJ_TYPE_COMMIT,
-		GOT_OBJ_TYPE_TREE,
-		GOT_OBJ_TYPE_BLOB,
-	};
-	int type = 0;
-	size_t size = 0, hdrlen = 0;
-	int i;
-	char *p = strchr(buf, '\0');
-
-	if (p == NULL)
-		return got_error(GOT_ERR_BAD_OBJ_HDR);
-
-	hdrlen = strlen(buf) + 1 /* '\0' */;
-
-	for (i = 0; i < nitems(obj_tags); i++) {
-		const char *tag = obj_tags[i];
-		size_t tlen = strlen(tag);
-		const char *errstr;
-
-		if (strncmp(buf, tag, tlen) != 0)
-			continue;
-
-		type = obj_types[i];
-		if (len <= tlen)
-			return got_error(GOT_ERR_BAD_OBJ_HDR);
-		size = strtonum(buf + tlen, 0, LONG_MAX, &errstr);
-		if (errstr != NULL)
-			return got_error(GOT_ERR_BAD_OBJ_HDR);
-		break;
-	}
-
-	if (type == 0)
-		return got_error(GOT_ERR_BAD_OBJ_HDR);
-
-	*obj = calloc(1, sizeof(**obj));
-	if (*obj == NULL)
-		return got_error_from_errno();
-	(*obj)->type = type;
-	(*obj)->hdrlen = hdrlen;
-	(*obj)->size = size;
-	return NULL;
-}
-
-static const struct got_error *
-read_object_header(struct got_object **obj, int fd)
-{
-	const struct got_error *err;
-	struct got_zstream_buf zb;
-	char *buf;
-	const size_t zbsize = 64;
-	size_t outlen, totlen;
-	int nbuf = 1;
-
-	buf = malloc(zbsize);
-	if (buf == NULL)
-		return got_error_from_errno();
-
-	err = got_inflate_init(&zb, buf, zbsize);
-	if (err)
-		return err;
-
-	totlen = 0;
-	do {
-		err = got_inflate_read_fd(&zb, fd, &outlen);
-		if (err)
-			goto done;
-		if (outlen == 0)
-			break;
-		totlen += outlen;
-		if (strchr(zb.outbuf, '\0') == NULL) {
-			char *newbuf;
-			nbuf++;
-			newbuf = recallocarray(buf, nbuf - 1, nbuf, zbsize);
-			if (newbuf == NULL) {
-				err = got_error_from_errno();
-				goto done;
-			}
-			buf = newbuf;
-			zb.outbuf = newbuf + totlen;
-			zb.outlen = (nbuf * zbsize) - totlen;
-		}
-	} while (strchr(zb.outbuf, '\0') == NULL);
-
-	err = parse_object_header(obj, buf, totlen);
-done:
-	free(buf);
-	got_inflate_end(&zb);
-	return err;
-}
-
-static void
-read_object_header_privsep_child(int obj_fd, int imsg_fds[2])
-{
-	const struct got_error *err = NULL;
-	struct got_object *obj = NULL;
-	struct imsgbuf ibuf;
-	int status = 0;
-
-	setproctitle("read object header");
-	close(imsg_fds[0]);
-	imsg_init(&ibuf, imsg_fds[1]);
-
-	/* revoke access to most system calls */
-	if (pledge("stdio", NULL) == -1) {
-		err = got_error_from_errno();
-		goto done;
-	}
-
-	err = read_object_header(&obj, obj_fd);
-	if (err)
-		goto done;
-
-	err = got_privsep_send_obj(&ibuf, obj, 0);
-done:
-	if (obj)
-		got_object_close(obj);
-	if (err) {
-		got_privsep_send_error(&ibuf, err);
-		status = 1;
-	}
-	close(obj_fd);
-	imsg_clear(&ibuf);
-	close(imsg_fds[1]);
-	_exit(status);
-}
-
-static const struct got_error *
-wait_for_child(pid_t pid)
-{
-	int child_status;
-
-	waitpid(pid, &child_status, 0);
-
-	if (!WIFEXITED(child_status))
-		return got_error(GOT_ERR_PRIVSEP_DIED);
-
-	if (WEXITSTATUS(child_status) != 0)
-		return got_error(GOT_ERR_PRIVSEP_EXIT);
-
-	return NULL;
-}
-
-static const struct got_error *
-read_object_header_privsep(struct got_object **obj, int fd)
-{
-	struct imsgbuf parent_ibuf;
-	int imsg_fds[2];
-	const struct got_error *err = NULL, *err_child = NULL;
-	pid_t pid;
-
-	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, imsg_fds) == -1)
-		return got_error_from_errno();
-
-	pid = fork();
-	if (pid == -1)
-		return got_error_from_errno();
-	else if (pid == 0) {
-		read_object_header_privsep_child(fd, imsg_fds);
-		/* not reached */
-	}
-
-	close(imsg_fds[1]);
-	imsg_init(&parent_ibuf, imsg_fds[0]);
-	err = got_privsep_recv_obj(obj, &parent_ibuf);
-	imsg_clear(&parent_ibuf);
-	err_child = wait_for_child(pid);
-	close(imsg_fds[0]);
-	return err ? err : err_child;
-}
-
-static const struct got_error *
 object_path(char **path, struct got_object_id *id, struct got_repository *repo)
 {
 	const struct got_error *err = NULL;
@@ -385,7 +194,7 @@ got_object_open(struct got_object **obj, struct got_re
 		if (*obj == NULL)
 			err = got_error(GOT_ERR_NO_OBJ);
 	} else {
-		err = read_object_header_privsep(obj, fd);
+		err = got_object_read_header_privsep(obj, fd);
 		if (err)
 			goto done;
 		memcpy((*obj)->id.sha1, id->sha1, SHA1_DIGEST_LENGTH);
@@ -437,25 +246,6 @@ got_object_close(struct got_object *obj)
 	free(obj);
 }
 
-struct got_commit_object *
-got_object_commit_alloc_partial(void)
-{
-	struct got_commit_object *commit;
-
-	commit = calloc(1, sizeof(*commit));
-	if (commit == NULL)
-		return NULL;
-	commit->tree_id = calloc(1, sizeof(*commit->tree_id));
-	if (commit->tree_id == NULL) {
-		free(commit);
-		return NULL;
-	}
-
-	SIMPLEQ_INIT(&commit->parent_ids);
-
-	return commit;
-}
-
 const struct got_error *
 got_object_open_as_commit(struct got_commit_object **commit,
     struct got_repository *repo, struct got_object_id *id)
@@ -504,492 +294,8 @@ got_object_qid_free(struct got_object_qid *qid)
 {
 	free(qid->id);
 	free(qid);
-}
-
-const struct got_error *
-got_object_commit_add_parent(struct got_commit_object *commit,
-    const char *id_str)
-{
-	const struct got_error *err = NULL;
-	struct got_object_qid *qid;
-
-	qid = malloc(sizeof(*qid));
-	if (qid == NULL)
-		return got_error_from_errno();
-
-	qid->id = malloc(sizeof(*qid->id));
-	if (qid->id == NULL) {
-		err = got_error_from_errno();
-		got_object_qid_free(qid);
-		return err;
-	}
-
-	if (!got_parse_sha1_digest(qid->id->sha1, id_str)) {
-		err = got_error(GOT_ERR_BAD_OBJ_DATA);
-		free(qid->id);
-		free(qid);
-		return err;
-	}
-
-	SIMPLEQ_INSERT_TAIL(&commit->parent_ids, qid, entry);
-	commit->nparents++;
-
-	return NULL;
-}
-
-static const struct got_error *
-parse_gmtoff(time_t *gmtoff, const char *tzstr)
-{
-	int sign = 1;
-	const char *p = tzstr;
-	time_t h, m;
-
-	*gmtoff = 0;
-
-	if (*p == '-')
-		sign = -1;
-	else if (*p != '+')
-		return got_error(GOT_ERR_BAD_OBJ_DATA);
-	p++;
-	if (!isdigit(*p) && !isdigit(*(p + 1)))
-		return got_error(GOT_ERR_BAD_OBJ_DATA);
-	h = (((*p - '0') * 10) + (*(p + 1) - '0'));
-
-	p += 2;
-	if (!isdigit(*p) && !isdigit(*(p + 1)))
-		return got_error(GOT_ERR_BAD_OBJ_DATA);
-	m = ((*p - '0') * 10) + (*(p + 1) - '0');
-
-	*gmtoff = (h * 60 * 60 + m * 60) * sign;
-	return NULL;
-}
-
-static const struct got_error *
-parse_commit_time(struct tm *tm, char *committer)
-{
-	const struct got_error *err = NULL;
-	const char *errstr;
-	char *space, *tzstr;
-	time_t gmtoff;
-	time_t time;
-
-	/* Parse and strip off trailing timezone indicator string. */
-	space = strrchr(committer, ' ');
-	if (space == NULL)
-		return got_error(GOT_ERR_BAD_OBJ_DATA);
-	tzstr = strdup(space + 1);
-	if (tzstr == NULL)
-		return got_error_from_errno();
-	err = parse_gmtoff(&gmtoff, tzstr);
-	free(tzstr);
-	if (err)
-		return err;
-	*space = '\0';
-
-	/* Timestamp is separated from committer name + email by space. */
-	space = strrchr(committer, ' ');
-	if (space == NULL)
-		return got_error(GOT_ERR_BAD_OBJ_DATA);
-
-	/* Timestamp parsed here is expressed in comitter's local time. */
-	time = strtonum(space + 1, 0, INT64_MAX, &errstr);
-	if (errstr)
-		return got_error(GOT_ERR_BAD_OBJ_DATA);
-
-	/* Express the time stamp in UTC. */
-	memset(tm, 0, sizeof(*tm));
-	time -= gmtoff;
-	if (localtime_r(&time, tm) == NULL)
-		return got_error_from_errno();
-	tm->tm_gmtoff = gmtoff;
-
-	/* Strip off parsed time information, leaving just author and email. */
-	*space = '\0';
-
-	return NULL;
-}
-
-static const struct got_error *
-parse_commit_object(struct got_commit_object **commit, char *buf, size_t len)
-{
-	const struct got_error *err = NULL;
-	char *s = buf;
-	size_t tlen;
-	ssize_t remain = (ssize_t)len;
- 
-	*commit = got_object_commit_alloc_partial();
-	if (*commit == NULL)
-		return got_error_from_errno();
-
-	tlen = strlen(GOT_COMMIT_TAG_TREE);
-	if (strncmp(s, GOT_COMMIT_TAG_TREE, tlen) == 0) {
-		remain -= tlen;
-		if (remain < SHA1_DIGEST_STRING_LENGTH) {
-			err = got_error(GOT_ERR_BAD_OBJ_DATA);
-			goto done;
-		}
-		s += tlen;
-		if (!got_parse_sha1_digest((*commit)->tree_id->sha1, s)) {
-			err = got_error(GOT_ERR_BAD_OBJ_DATA);
-			goto done;
-		}
-		remain -= SHA1_DIGEST_STRING_LENGTH;
-		s += SHA1_DIGEST_STRING_LENGTH;
-	} else {
-		err = got_error(GOT_ERR_BAD_OBJ_DATA);
-		goto done;
-	}
-
-	tlen = strlen(GOT_COMMIT_TAG_PARENT);
-	while (strncmp(s, GOT_COMMIT_TAG_PARENT, tlen) == 0) {
-		remain -= tlen;
-		if (remain < SHA1_DIGEST_STRING_LENGTH) {
-			err = got_error(GOT_ERR_BAD_OBJ_DATA);
-			goto done;
-		}
-		s += tlen;
-		err = got_object_commit_add_parent(*commit, s);
-		if (err)
-			goto done;
-
-		remain -= SHA1_DIGEST_STRING_LENGTH;
-		s += SHA1_DIGEST_STRING_LENGTH;
-	}
-
-	tlen = strlen(GOT_COMMIT_TAG_AUTHOR);
-	if (strncmp(s, GOT_COMMIT_TAG_AUTHOR, tlen) == 0) {
-		char *p;
-		size_t slen;
-
-		remain -= tlen;
-		if (remain <= 0) {
-			err = got_error(GOT_ERR_BAD_OBJ_DATA);
-			goto done;
-		}
-		s += tlen;
-		p = strchr(s, '\n');
-		if (p == NULL) {
-			err = got_error(GOT_ERR_BAD_OBJ_DATA);
-			goto done;
-		}
-		*p = '\0';
-		slen = strlen(s);
-		err = parse_commit_time(&(*commit)->tm_author, s);
-		if (err)
-			goto done;
-		(*commit)->author = strdup(s);
-		if ((*commit)->author == NULL) {
-			err = got_error_from_errno();
-			goto done;
-		}
-		s += slen + 1;
-		remain -= slen + 1;
-	}
-
-	tlen = strlen(GOT_COMMIT_TAG_COMMITTER);
-	if (strncmp(s, GOT_COMMIT_TAG_COMMITTER, tlen) == 0) {
-		char *p;
-		size_t slen;
-
-		remain -= tlen;
-		if (remain <= 0) {
-			err = got_error(GOT_ERR_BAD_OBJ_DATA);
-			goto done;
-		}
-		s += tlen;
-		p = strchr(s, '\n');
-		if (p == NULL) {
-			err = got_error(GOT_ERR_BAD_OBJ_DATA);
-			goto done;
-		}
-		*p = '\0';
-		slen = strlen(s);
-		err = parse_commit_time(&(*commit)->tm_committer, s);
-		if (err)
-			goto done;
-		(*commit)->committer = strdup(s);
-		if ((*commit)->committer == NULL) {
-			err = got_error_from_errno();
-			goto done;
-		}
-		s += slen + 1;
-		remain -= slen + 1;
-	}
-
-	(*commit)->logmsg = strndup(s, remain);
-	if ((*commit)->logmsg == NULL) {
-		err = got_error_from_errno();
-		goto done;
-	}
-done:
-	if (err) {
-		got_object_commit_close(*commit);
-		*commit = NULL;
-	}
-	return err;
-}
-
-static void
-tree_entry_close(struct got_tree_entry *te)
-{
-	free(te->id);
-	free(te->name);
-	free(te);
 }
 
-struct got_tree_entry *
-got_alloc_tree_entry_partial(void)
-{
-	struct got_tree_entry *te;
-
-	te = calloc(1, sizeof(*te));
-	if (te == NULL)
-		return NULL;
-
-	te->id = calloc(1, sizeof(*te->id));
-	if (te->id == NULL) {
-		free(te);
-		te = NULL;
-	}
-	return te;
-}
-
-static const struct got_error *
-parse_tree_entry(struct got_tree_entry **te, size_t *elen, char *buf,
-    size_t maxlen)
-{
-	char *p = buf, *space;
-	const struct got_error *err = NULL;
-
-	*te = got_alloc_tree_entry_partial();
-	if (*te == NULL)
-		return got_error_from_errno();
-
-	*elen = strlen(buf) + 1;
-	if (*elen > maxlen) {
-		free(*te);
-		*te = NULL;
-		return got_error(GOT_ERR_BAD_OBJ_DATA);
-	}
-
-	space = strchr(buf, ' ');
-	if (space == NULL) {
-		err = got_error(GOT_ERR_BAD_OBJ_DATA);
-		free(*te);
-		*te = NULL;
-		return err;
-	}
-	while (*p != ' ') {
-		if (*p < '0' && *p > '7') {
-			err = got_error(GOT_ERR_BAD_OBJ_DATA);
-			goto done;
-		}
-		(*te)->mode <<= 3;
-		(*te)->mode |= *p - '0';
-		p++;
-	}
-
-	(*te)->name = strdup(space + 1);
-	if (*elen > maxlen || maxlen - *elen < SHA1_DIGEST_LENGTH) {
-		err = got_error(GOT_ERR_BAD_OBJ_DATA);
-		goto done;
-	}
-	buf += strlen(buf) + 1;
-	memcpy((*te)->id->sha1, buf, SHA1_DIGEST_LENGTH);
-	*elen += SHA1_DIGEST_LENGTH;
-done:
-	if (err) {
-		tree_entry_close(*te);
-		*te = NULL;
-	}
-	return err;
-}
-
-static const struct got_error *
-parse_tree_object(struct got_tree_object **tree, uint8_t *buf, size_t len)
-{
-	const struct got_error *err;
-	size_t remain = len;
-
-	*tree = calloc(1, sizeof(**tree));
-	if (*tree == NULL)
-		return got_error_from_errno();
-
-	SIMPLEQ_INIT(&(*tree)->entries.head);
-
-	while (remain > 0) {
-		struct got_tree_entry *te;
-		size_t elen;
-
-		err = parse_tree_entry(&te, &elen, buf, remain);
-		if (err)
-			return err;
-		(*tree)->entries.nentries++;
-		SIMPLEQ_INSERT_TAIL(&(*tree)->entries.head, te, entry);
-		buf += elen;
-		remain -= elen;
-	}
-
-	if (remain != 0) {
-		got_object_tree_close(*tree);
-		return got_error(GOT_ERR_BAD_OBJ_DATA);
-	}
-
-	return NULL;
-}
-
-static const struct got_error *
-read_to_mem(uint8_t **outbuf, size_t *outlen, FILE *f)
-{
-	const struct got_error *err = NULL;
-	static const size_t blocksize = 512;
-	size_t n, total, remain;
-	uint8_t *buf;
-
-	*outbuf = NULL;
-	*outlen = 0;
-
-	buf = malloc(blocksize);
-	if (buf == NULL)
-		return got_error_from_errno();
-
-	remain = blocksize;
-	total = 0;
-	while (1) {
-		if (remain == 0) {
-			uint8_t *newbuf;
-			newbuf = reallocarray(buf, 1, total + blocksize);
-			if (newbuf == NULL) {
-				err = got_error_from_errno();
-				goto done;
-			}
-			buf = newbuf;
-			remain += blocksize;
-		}
-		n = fread(buf + total, 1, remain, f);
-		if (n == 0) {
-			if (ferror(f)) {
-				err = got_ferror(f, GOT_ERR_IO);
-				goto done;
-			}
-			break; /* EOF */
-		}
-		remain -= n;
-		total += n;
-	};
-
-done:
-	if (err == NULL) {
-		*outbuf = buf;
-		*outlen = total;
-	} else
-		free(buf);
-	return err;
-}
-
-static const struct got_error *
-read_commit_object(struct got_commit_object **commit, struct got_object *obj,
-    FILE *f)
-{
-	const struct got_error *err = NULL;
-	size_t len;
-	uint8_t *p;
-
-	if (obj->flags & GOT_OBJ_FLAG_PACKED)
-		err = read_to_mem(&p, &len, f);
-	else
-		err = got_inflate_to_mem(&p, &len, f);
-	if (err)
-		return err;
-
-	if (len < obj->hdrlen + obj->size) {
-		err = got_error(GOT_ERR_BAD_OBJ_DATA);
-		goto done;
-	}
-
-	/* Skip object header. */
-	len -= obj->hdrlen;
-	err = parse_commit_object(commit, p + obj->hdrlen, len);
-	free(p);
-done:
-	return err;
-}
-
-static void
-read_commit_object_privsep_child(struct got_object *obj, int obj_fd,
-    int imsg_fds[2])
-{
-	const struct got_error *err = NULL;
-	struct got_commit_object *commit = NULL;
-	struct imsgbuf ibuf;
-	FILE *f = NULL;
-	int status = 0;
-
-	setproctitle("read commit object");
-	close(imsg_fds[0]);
-	imsg_init(&ibuf, imsg_fds[1]);
-
-	/* revoke access to most system calls */
-	if (pledge("stdio", NULL) == -1) {
-		err = got_error_from_errno();
-		goto done;
-	}
-
-	f = fdopen(obj_fd, "rb");
-	if (f == NULL) {
-		err = got_error_from_errno();
-		close(obj_fd);
-		goto done;
-	}
-
-	err = read_commit_object(&commit, obj, f);
-	if (err)
-		goto done;
-
-	err = got_privsep_send_commit(&ibuf, commit);
-done:
-	if (commit)
-		got_object_commit_close(commit);
-	if (err) {
-		got_privsep_send_error(&ibuf, err);
-		status = 1;
-	}
-	if (f)
-		fclose(f);
-	imsg_clear(&ibuf);
-	close(imsg_fds[1]);
-	_exit(status);
-}
-
-static const struct got_error *
-read_commit_object_privsep(struct got_commit_object **commit,
-    struct got_object *obj, int fd)
-{
-	const struct got_error *err = NULL, *err_child = NULL;
-	struct imsgbuf parent_ibuf;
-	int imsg_fds[2];
-	pid_t pid;
-
-	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, imsg_fds) == -1)
-		return got_error_from_errno();
-
-	pid = fork();
-	if (pid == -1)
-		return got_error_from_errno();
-	else if (pid == 0) {
-		read_commit_object_privsep_child(obj, fd, imsg_fds);
-		/* not reached */
-	}
-
-	close(imsg_fds[1]);
-	imsg_init(&parent_ibuf, imsg_fds[0]);
-	err = got_privsep_recv_commit(commit, &parent_ibuf);
-	imsg_clear(&parent_ibuf);
-	err_child = wait_for_child(pid);
-	close(imsg_fds[0]);
-	return err ? err : err_child;
-}
-
 const struct got_error *
 got_object_commit_open(struct got_commit_object **commit,
     struct got_repository *repo, struct got_object *obj)
@@ -1012,14 +318,14 @@ got_object_commit_open(struct got_commit_object **comm
 		if (err)
 			return err;
 		obj->size = len;
-		err = parse_commit_object(commit, buf, len);
+		err = got_object_parse_commit(commit, buf, len);
 		free(buf);
 	} else {
 		int fd;
 		err = open_loose_object(&fd, obj, repo);
 		if (err)
 			return err;
-		err = read_commit_object_privsep(commit, obj, fd);
+		err = got_object_read_commit_privsep(commit, obj, fd);
 		close(fd);
 	}
 
@@ -1053,110 +359,8 @@ got_object_commit_close(struct got_commit_object *comm
 	free(commit->committer);
 	free(commit->logmsg);
 	free(commit);
-}
-
-static const struct got_error *
-read_tree_object(struct got_tree_object **tree, struct got_object *obj, FILE *f)
-{
-	const struct got_error *err = NULL;
-	size_t len;
-	uint8_t *p;
-
-	if (obj->flags & GOT_OBJ_FLAG_PACKED)
-		err = read_to_mem(&p, &len, f);
-	else
-		err = got_inflate_to_mem(&p, &len, f);
-	if (err)
-		return err;
-
-	if (len < obj->hdrlen + obj->size) {
-		err = got_error(GOT_ERR_BAD_OBJ_DATA);
-		goto done;
-	}
-
-	/* Skip object header. */
-	len -= obj->hdrlen;
-	err = parse_tree_object(tree, p + obj->hdrlen, len);
-	free(p);
-done:
-	return err;
-}
-
-static void
-read_tree_object_privsep_child(struct got_object *obj, int obj_fd,
-    int imsg_fds[2])
-{
-	const struct got_error *err = NULL;
-	struct got_tree_object *tree = NULL;
-	struct imsgbuf ibuf;
-	FILE *f = NULL;
-	int status = 0;
-
-	setproctitle("read tree object");
-	close(imsg_fds[0]);
-	imsg_init(&ibuf, imsg_fds[1]);
-
-	/* revoke access to most system calls */
-	if (pledge("stdio", NULL) == -1) {
-		err = got_error_from_errno();
-		goto done;
-	}
-
-	f = fdopen(obj_fd, "rb");
-	if (f == NULL) {
-		err = got_error_from_errno();
-		close(obj_fd);
-		goto done;
-	}
-
-	err = read_tree_object(&tree, obj, f);
-	if (err)
-		goto done;
-
-	err = got_privsep_send_tree(&ibuf, tree);
-done:
-	if (tree)
-		got_object_tree_close(tree);
-	if (err) {
-		got_privsep_send_error(&ibuf, err);
-		status = 1;
-	}
-	if (f)
-		fclose(f);
-	imsg_clear(&ibuf);
-	close(imsg_fds[1]);
-	_exit(status);
 }
 
-static const struct got_error *
-read_tree_object_privsep(struct got_tree_object **tree, struct got_object *obj,
-    int fd)
-{
-	const struct got_error *err = NULL, *err_child = NULL;
-	struct imsgbuf parent_ibuf;
-	int imsg_fds[2];
-	pid_t pid;
-
-	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, imsg_fds) == -1)
-		return got_error_from_errno();
-
-	pid = fork();
-	if (pid == -1)
-		return got_error_from_errno();
-	else if (pid == 0) {
-		read_tree_object_privsep_child(obj, fd, imsg_fds);
-		/* not reached */
-	}
-
-	close(imsg_fds[1]);
-	imsg_init(&parent_ibuf, imsg_fds[0]);
-	err = got_privsep_recv_tree(tree, &parent_ibuf);
-	imsg_clear(&parent_ibuf);
-	err_child = wait_for_child(pid);
-	close(imsg_fds[0]);
-	return err ? err : err_child;
-}
-
 const struct got_error *
 got_object_tree_open(struct got_tree_object **tree,
     struct got_repository *repo, struct got_object *obj)
@@ -1179,14 +383,14 @@ got_object_tree_open(struct got_tree_object **tree,
 		if (err)
 			return err;
 		obj->size = len;
-		err = parse_tree_object(tree, buf, len);
+		err = got_object_parse_tree(tree, buf, len);
 		free(buf);
 	} else {
 		int fd;
 		err = open_loose_object(&fd, obj, repo);
 		if (err)
 			return err;
-		err = read_tree_object_privsep(tree, obj, fd);
+		err = got_object_read_tree_privsep(tree, obj, fd);
 		close(fd);
 	}
 
@@ -1235,7 +439,7 @@ got_object_tree_close(struct got_tree_object *tree)
 	while (!SIMPLEQ_EMPTY(&tree->entries.head)) {
 		te = SIMPLEQ_FIRST(&tree->entries.head);
 		SIMPLEQ_REMOVE_HEAD(&tree->entries.head, entry);
-		tree_entry_close(te);
+		got_object_tree_entry_close(te);
 	}
 
 	free(tree);
@@ -1247,78 +451,6 @@ got_object_tree_get_entries(struct got_tree_object *tr
 	return &tree->entries;
 }
 
-static const struct got_error *
-read_blob_object_privsep_child(int outfd, int infd, int imsg_fds[2])
-{
-	const struct got_error *err = NULL;
-	struct imsgbuf ibuf;
-	int status = 0;
-	size_t size;
-	FILE *infile = NULL;
-
-	setproctitle("read blob object");
-	close(imsg_fds[0]);
-	imsg_init(&ibuf, imsg_fds[1]);
-
-	/* revoke access to most system calls */
-	if (pledge("stdio", NULL) == -1) {
-		err = got_error_from_errno();
-		goto done;
-	}
-
-	infile = fdopen(infd, "rb");
-	if (infile == NULL) {
-		err = got_error_from_errno();
-		close(infd);
-		goto done;
-	}
-	err = got_inflate_to_fd(&size, infile, outfd);
-	fclose(infile);
-	if (err)
-		goto done;
-
-	err = got_privsep_send_blob(&ibuf, size);
-done:
-	if (err) {
-		got_privsep_send_error(&ibuf, err);
-		status = 1;
-	}
-	close(outfd);
-	imsg_clear(&ibuf);
-	close(imsg_fds[1]);
-	_exit(status);
-}
-
-static const struct got_error *
-read_blob_object_privsep(size_t *size, int outfd, int infd)
-{
-	struct imsgbuf parent_ibuf;
-	int imsg_fds[2];
-	const struct got_error *err = NULL, *err_child = NULL;
-	pid_t pid;
-
-	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, imsg_fds) == -1)
-		return got_error_from_errno();
-
-	pid = fork();
-	if (pid == -1)
-		return got_error_from_errno();
-	else if (pid == 0) {
-		read_blob_object_privsep_child(outfd, infd, imsg_fds);
-		/* not reached */
-	}
-
-	close(imsg_fds[1]);
-	imsg_init(&parent_ibuf, imsg_fds[0]);
-	err = got_privsep_recv_blob(size, &parent_ibuf);
-	imsg_clear(&parent_ibuf);
-	err_child = wait_for_child(pid);
-	close(imsg_fds[0]);
-	if (lseek(outfd, SEEK_SET, 0) == -1)
-		err = got_error_from_errno();
-	return err ? err : err_child;
-}
-
 const struct got_error *
 got_object_blob_open(struct got_blob_object **blob,
     struct got_repository *repo, struct got_object *obj, size_t blocksize)
@@ -1361,7 +493,7 @@ got_object_blob_open(struct got_blob_object **blob,
 			goto done;
 		}
 
-		err = read_blob_object_privsep(&size, outfd, infd);
+		err = got_object_read_blob_privsep(&size, outfd, infd);
 		close(infd);
 		if (err)
 			goto done;
blob - /dev/null
blob + cf6560f02d84d3a42d5c5c293e1e1724f768c530 (mode 644)
--- /dev/null
+++ lib/object_parse.c
@@ -0,0 +1,915 @@
+/*
+ * Copyright (c) 2018 Stefan Sperling <stsp@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/queue.h>
+#include <sys/uio.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <sha1.h>
+#include <zlib.h>
+#include <ctype.h>
+#include <limits.h>
+#include <imsg.h>
+#include <time.h>
+
+#include "got_error.h"
+#include "got_object.h"
+#include "got_repository.h"
+#include "got_opentemp.h"
+
+#include "got_lib_sha1.h"
+#include "got_lib_delta.h"
+#include "got_lib_pack.h"
+#include "got_lib_inflate.h"
+#include "got_lib_object.h"
+#include "got_lib_privsep.h"
+
+#ifndef nitems
+#define nitems(_a) (sizeof(_a) / sizeof((_a)[0]))
+#endif
+
+#define GOT_OBJ_TAG_COMMIT	"commit"
+#define GOT_OBJ_TAG_TREE	"tree"
+#define GOT_OBJ_TAG_BLOB	"blob"
+
+#define GOT_COMMIT_TAG_TREE		"tree "
+#define GOT_COMMIT_TAG_PARENT		"parent "
+#define GOT_COMMIT_TAG_AUTHOR		"author "
+#define GOT_COMMIT_TAG_COMMITTER	"committer "
+
+static const struct got_error *
+parse_object_header(struct got_object **obj, char *buf, size_t len)
+{
+	const char *obj_tags[] = {
+		GOT_OBJ_TAG_COMMIT,
+		GOT_OBJ_TAG_TREE,
+		GOT_OBJ_TAG_BLOB
+	};
+	const int obj_types[] = {
+		GOT_OBJ_TYPE_COMMIT,
+		GOT_OBJ_TYPE_TREE,
+		GOT_OBJ_TYPE_BLOB,
+	};
+	int type = 0;
+	size_t size = 0, hdrlen = 0;
+	int i;
+	char *p = strchr(buf, '\0');
+
+	if (p == NULL)
+		return got_error(GOT_ERR_BAD_OBJ_HDR);
+
+	hdrlen = strlen(buf) + 1 /* '\0' */;
+
+	for (i = 0; i < nitems(obj_tags); i++) {
+		const char *tag = obj_tags[i];
+		size_t tlen = strlen(tag);
+		const char *errstr;
+
+		if (strncmp(buf, tag, tlen) != 0)
+			continue;
+
+		type = obj_types[i];
+		if (len <= tlen)
+			return got_error(GOT_ERR_BAD_OBJ_HDR);
+		size = strtonum(buf + tlen, 0, LONG_MAX, &errstr);
+		if (errstr != NULL)
+			return got_error(GOT_ERR_BAD_OBJ_HDR);
+		break;
+	}
+
+	if (type == 0)
+		return got_error(GOT_ERR_BAD_OBJ_HDR);
+
+	*obj = calloc(1, sizeof(**obj));
+	if (*obj == NULL)
+		return got_error_from_errno();
+	(*obj)->type = type;
+	(*obj)->hdrlen = hdrlen;
+	(*obj)->size = size;
+	return NULL;
+}
+
+static const struct got_error *
+read_object_header(struct got_object **obj, int fd)
+{
+	const struct got_error *err;
+	struct got_zstream_buf zb;
+	char *buf;
+	const size_t zbsize = 64;
+	size_t outlen, totlen;
+	int nbuf = 1;
+
+	buf = malloc(zbsize);
+	if (buf == NULL)
+		return got_error_from_errno();
+
+	err = got_inflate_init(&zb, buf, zbsize);
+	if (err)
+		return err;
+
+	totlen = 0;
+	do {
+		err = got_inflate_read_fd(&zb, fd, &outlen);
+		if (err)
+			goto done;
+		if (outlen == 0)
+			break;
+		totlen += outlen;
+		if (strchr(zb.outbuf, '\0') == NULL) {
+			char *newbuf;
+			nbuf++;
+			newbuf = recallocarray(buf, nbuf - 1, nbuf, zbsize);
+			if (newbuf == NULL) {
+				err = got_error_from_errno();
+				goto done;
+			}
+			buf = newbuf;
+			zb.outbuf = newbuf + totlen;
+			zb.outlen = (nbuf * zbsize) - totlen;
+		}
+	} while (strchr(zb.outbuf, '\0') == NULL);
+
+	err = parse_object_header(obj, buf, totlen);
+done:
+	free(buf);
+	got_inflate_end(&zb);
+	return err;
+}
+
+static void
+read_object_header_privsep_child(int obj_fd, int imsg_fds[2])
+{
+	const struct got_error *err = NULL;
+	struct got_object *obj = NULL;
+	struct imsgbuf ibuf;
+	int status = 0;
+
+	setproctitle("read object header");
+	close(imsg_fds[0]);
+	imsg_init(&ibuf, imsg_fds[1]);
+
+	/* revoke access to most system calls */
+	if (pledge("stdio", NULL) == -1) {
+		err = got_error_from_errno();
+		goto done;
+	}
+
+	err = read_object_header(&obj, obj_fd);
+	if (err)
+		goto done;
+
+	err = got_privsep_send_obj(&ibuf, obj, 0);
+done:
+	if (obj)
+		got_object_close(obj);
+	if (err) {
+		got_privsep_send_error(&ibuf, err);
+		status = 1;
+	}
+	close(obj_fd);
+	imsg_clear(&ibuf);
+	close(imsg_fds[1]);
+	_exit(status);
+}
+
+static const struct got_error *
+wait_for_child(pid_t pid)
+{
+	int child_status;
+
+	waitpid(pid, &child_status, 0);
+
+	if (!WIFEXITED(child_status))
+		return got_error(GOT_ERR_PRIVSEP_DIED);
+
+	if (WEXITSTATUS(child_status) != 0)
+		return got_error(GOT_ERR_PRIVSEP_EXIT);
+
+	return NULL;
+}
+
+const struct got_error *
+got_object_read_header_privsep(struct got_object **obj, int fd)
+{
+	struct imsgbuf parent_ibuf;
+	int imsg_fds[2];
+	const struct got_error *err = NULL, *err_child = NULL;
+	pid_t pid;
+
+	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, imsg_fds) == -1)
+		return got_error_from_errno();
+
+	pid = fork();
+	if (pid == -1)
+		return got_error_from_errno();
+	else if (pid == 0) {
+		read_object_header_privsep_child(fd, imsg_fds);
+		/* not reached */
+	}
+
+	close(imsg_fds[1]);
+	imsg_init(&parent_ibuf, imsg_fds[0]);
+	err = got_privsep_recv_obj(obj, &parent_ibuf);
+	imsg_clear(&parent_ibuf);
+	err_child = wait_for_child(pid);
+	close(imsg_fds[0]);
+	return err ? err : err_child;
+}
+
+struct got_commit_object *
+got_object_commit_alloc_partial(void)
+{
+	struct got_commit_object *commit;
+
+	commit = calloc(1, sizeof(*commit));
+	if (commit == NULL)
+		return NULL;
+	commit->tree_id = calloc(1, sizeof(*commit->tree_id));
+	if (commit->tree_id == NULL) {
+		free(commit);
+		return NULL;
+	}
+
+	SIMPLEQ_INIT(&commit->parent_ids);
+
+	return commit;
+}
+
+const struct got_error *
+got_object_commit_add_parent(struct got_commit_object *commit,
+    const char *id_str)
+{
+	const struct got_error *err = NULL;
+	struct got_object_qid *qid;
+
+	qid = malloc(sizeof(*qid));
+	if (qid == NULL)
+		return got_error_from_errno();
+
+	qid->id = malloc(sizeof(*qid->id));
+	if (qid->id == NULL) {
+		err = got_error_from_errno();
+		got_object_qid_free(qid);
+		return err;
+	}
+
+	if (!got_parse_sha1_digest(qid->id->sha1, id_str)) {
+		err = got_error(GOT_ERR_BAD_OBJ_DATA);
+		free(qid->id);
+		free(qid);
+		return err;
+	}
+
+	SIMPLEQ_INSERT_TAIL(&commit->parent_ids, qid, entry);
+	commit->nparents++;
+
+	return NULL;
+}
+
+static const struct got_error *
+parse_gmtoff(time_t *gmtoff, const char *tzstr)
+{
+	int sign = 1;
+	const char *p = tzstr;
+	time_t h, m;
+
+	*gmtoff = 0;
+
+	if (*p == '-')
+		sign = -1;
+	else if (*p != '+')
+		return got_error(GOT_ERR_BAD_OBJ_DATA);
+	p++;
+	if (!isdigit(*p) && !isdigit(*(p + 1)))
+		return got_error(GOT_ERR_BAD_OBJ_DATA);
+	h = (((*p - '0') * 10) + (*(p + 1) - '0'));
+
+	p += 2;
+	if (!isdigit(*p) && !isdigit(*(p + 1)))
+		return got_error(GOT_ERR_BAD_OBJ_DATA);
+	m = ((*p - '0') * 10) + (*(p + 1) - '0');
+
+	*gmtoff = (h * 60 * 60 + m * 60) * sign;
+	return NULL;
+}
+
+static const struct got_error *
+parse_commit_time(struct tm *tm, char *committer)
+{
+	const struct got_error *err = NULL;
+	const char *errstr;
+	char *space, *tzstr;
+	time_t gmtoff;
+	time_t time;
+
+	/* Parse and strip off trailing timezone indicator string. */
+	space = strrchr(committer, ' ');
+	if (space == NULL)
+		return got_error(GOT_ERR_BAD_OBJ_DATA);
+	tzstr = strdup(space + 1);
+	if (tzstr == NULL)
+		return got_error_from_errno();
+	err = parse_gmtoff(&gmtoff, tzstr);
+	free(tzstr);
+	if (err)
+		return err;
+	*space = '\0';
+
+	/* Timestamp is separated from committer name + email by space. */
+	space = strrchr(committer, ' ');
+	if (space == NULL)
+		return got_error(GOT_ERR_BAD_OBJ_DATA);
+
+	/* Timestamp parsed here is expressed in comitter's local time. */
+	time = strtonum(space + 1, 0, INT64_MAX, &errstr);
+	if (errstr)
+		return got_error(GOT_ERR_BAD_OBJ_DATA);
+
+	/* Express the time stamp in UTC. */
+	memset(tm, 0, sizeof(*tm));
+	time -= gmtoff;
+	if (localtime_r(&time, tm) == NULL)
+		return got_error_from_errno();
+	tm->tm_gmtoff = gmtoff;
+
+	/* Strip off parsed time information, leaving just author and email. */
+	*space = '\0';
+
+	return NULL;
+}
+
+const struct got_error *
+got_object_parse_commit(struct got_commit_object **commit, char *buf, size_t len)
+{
+	const struct got_error *err = NULL;
+	char *s = buf;
+	size_t tlen;
+	ssize_t remain = (ssize_t)len;
+ 
+	*commit = got_object_commit_alloc_partial();
+	if (*commit == NULL)
+		return got_error_from_errno();
+
+	tlen = strlen(GOT_COMMIT_TAG_TREE);
+	if (strncmp(s, GOT_COMMIT_TAG_TREE, tlen) == 0) {
+		remain -= tlen;
+		if (remain < SHA1_DIGEST_STRING_LENGTH) {
+			err = got_error(GOT_ERR_BAD_OBJ_DATA);
+			goto done;
+		}
+		s += tlen;
+		if (!got_parse_sha1_digest((*commit)->tree_id->sha1, s)) {
+			err = got_error(GOT_ERR_BAD_OBJ_DATA);
+			goto done;
+		}
+		remain -= SHA1_DIGEST_STRING_LENGTH;
+		s += SHA1_DIGEST_STRING_LENGTH;
+	} else {
+		err = got_error(GOT_ERR_BAD_OBJ_DATA);
+		goto done;
+	}
+
+	tlen = strlen(GOT_COMMIT_TAG_PARENT);
+	while (strncmp(s, GOT_COMMIT_TAG_PARENT, tlen) == 0) {
+		remain -= tlen;
+		if (remain < SHA1_DIGEST_STRING_LENGTH) {
+			err = got_error(GOT_ERR_BAD_OBJ_DATA);
+			goto done;
+		}
+		s += tlen;
+		err = got_object_commit_add_parent(*commit, s);
+		if (err)
+			goto done;
+
+		remain -= SHA1_DIGEST_STRING_LENGTH;
+		s += SHA1_DIGEST_STRING_LENGTH;
+	}
+
+	tlen = strlen(GOT_COMMIT_TAG_AUTHOR);
+	if (strncmp(s, GOT_COMMIT_TAG_AUTHOR, tlen) == 0) {
+		char *p;
+		size_t slen;
+
+		remain -= tlen;
+		if (remain <= 0) {
+			err = got_error(GOT_ERR_BAD_OBJ_DATA);
+			goto done;
+		}
+		s += tlen;
+		p = strchr(s, '\n');
+		if (p == NULL) {
+			err = got_error(GOT_ERR_BAD_OBJ_DATA);
+			goto done;
+		}
+		*p = '\0';
+		slen = strlen(s);
+		err = parse_commit_time(&(*commit)->tm_author, s);
+		if (err)
+			goto done;
+		(*commit)->author = strdup(s);
+		if ((*commit)->author == NULL) {
+			err = got_error_from_errno();
+			goto done;
+		}
+		s += slen + 1;
+		remain -= slen + 1;
+	}
+
+	tlen = strlen(GOT_COMMIT_TAG_COMMITTER);
+	if (strncmp(s, GOT_COMMIT_TAG_COMMITTER, tlen) == 0) {
+		char *p;
+		size_t slen;
+
+		remain -= tlen;
+		if (remain <= 0) {
+			err = got_error(GOT_ERR_BAD_OBJ_DATA);
+			goto done;
+		}
+		s += tlen;
+		p = strchr(s, '\n');
+		if (p == NULL) {
+			err = got_error(GOT_ERR_BAD_OBJ_DATA);
+			goto done;
+		}
+		*p = '\0';
+		slen = strlen(s);
+		err = parse_commit_time(&(*commit)->tm_committer, s);
+		if (err)
+			goto done;
+		(*commit)->committer = strdup(s);
+		if ((*commit)->committer == NULL) {
+			err = got_error_from_errno();
+			goto done;
+		}
+		s += slen + 1;
+		remain -= slen + 1;
+	}
+
+	(*commit)->logmsg = strndup(s, remain);
+	if ((*commit)->logmsg == NULL) {
+		err = got_error_from_errno();
+		goto done;
+	}
+done:
+	if (err) {
+		got_object_commit_close(*commit);
+		*commit = NULL;
+	}
+	return err;
+}
+
+void
+got_object_tree_entry_close(struct got_tree_entry *te)
+{
+	free(te->id);
+	free(te->name);
+	free(te);
+}
+
+struct got_tree_entry *
+got_alloc_tree_entry_partial(void)
+{
+	struct got_tree_entry *te;
+
+	te = calloc(1, sizeof(*te));
+	if (te == NULL)
+		return NULL;
+
+	te->id = calloc(1, sizeof(*te->id));
+	if (te->id == NULL) {
+		free(te);
+		te = NULL;
+	}
+	return te;
+}
+
+static const struct got_error *
+parse_tree_entry(struct got_tree_entry **te, size_t *elen, char *buf,
+    size_t maxlen)
+{
+	char *p = buf, *space;
+	const struct got_error *err = NULL;
+
+	*te = got_alloc_tree_entry_partial();
+	if (*te == NULL)
+		return got_error_from_errno();
+
+	*elen = strlen(buf) + 1;
+	if (*elen > maxlen) {
+		free(*te);
+		*te = NULL;
+		return got_error(GOT_ERR_BAD_OBJ_DATA);
+	}
+
+	space = strchr(buf, ' ');
+	if (space == NULL) {
+		err = got_error(GOT_ERR_BAD_OBJ_DATA);
+		free(*te);
+		*te = NULL;
+		return err;
+	}
+	while (*p != ' ') {
+		if (*p < '0' && *p > '7') {
+			err = got_error(GOT_ERR_BAD_OBJ_DATA);
+			goto done;
+		}
+		(*te)->mode <<= 3;
+		(*te)->mode |= *p - '0';
+		p++;
+	}
+
+	(*te)->name = strdup(space + 1);
+	if (*elen > maxlen || maxlen - *elen < SHA1_DIGEST_LENGTH) {
+		err = got_error(GOT_ERR_BAD_OBJ_DATA);
+		goto done;
+	}
+	buf += strlen(buf) + 1;
+	memcpy((*te)->id->sha1, buf, SHA1_DIGEST_LENGTH);
+	*elen += SHA1_DIGEST_LENGTH;
+done:
+	if (err) {
+		got_object_tree_entry_close(*te);
+		*te = NULL;
+	}
+	return err;
+}
+
+const struct got_error *
+got_object_parse_tree(struct got_tree_object **tree, uint8_t *buf, size_t len)
+{
+	const struct got_error *err;
+	size_t remain = len;
+
+	*tree = calloc(1, sizeof(**tree));
+	if (*tree == NULL)
+		return got_error_from_errno();
+
+	SIMPLEQ_INIT(&(*tree)->entries.head);
+
+	while (remain > 0) {
+		struct got_tree_entry *te;
+		size_t elen;
+
+		err = parse_tree_entry(&te, &elen, buf, remain);
+		if (err)
+			return err;
+		(*tree)->entries.nentries++;
+		SIMPLEQ_INSERT_TAIL(&(*tree)->entries.head, te, entry);
+		buf += elen;
+		remain -= elen;
+	}
+
+	if (remain != 0) {
+		got_object_tree_close(*tree);
+		return got_error(GOT_ERR_BAD_OBJ_DATA);
+	}
+
+	return NULL;
+}
+
+static const struct got_error *
+read_to_mem(uint8_t **outbuf, size_t *outlen, FILE *f)
+{
+	const struct got_error *err = NULL;
+	static const size_t blocksize = 512;
+	size_t n, total, remain;
+	uint8_t *buf;
+
+	*outbuf = NULL;
+	*outlen = 0;
+
+	buf = malloc(blocksize);
+	if (buf == NULL)
+		return got_error_from_errno();
+
+	remain = blocksize;
+	total = 0;
+	while (1) {
+		if (remain == 0) {
+			uint8_t *newbuf;
+			newbuf = reallocarray(buf, 1, total + blocksize);
+			if (newbuf == NULL) {
+				err = got_error_from_errno();
+				goto done;
+			}
+			buf = newbuf;
+			remain += blocksize;
+		}
+		n = fread(buf + total, 1, remain, f);
+		if (n == 0) {
+			if (ferror(f)) {
+				err = got_ferror(f, GOT_ERR_IO);
+				goto done;
+			}
+			break; /* EOF */
+		}
+		remain -= n;
+		total += n;
+	};
+
+done:
+	if (err == NULL) {
+		*outbuf = buf;
+		*outlen = total;
+	} else
+		free(buf);
+	return err;
+}
+
+static const struct got_error *
+read_commit_object(struct got_commit_object **commit, struct got_object *obj,
+    FILE *f)
+{
+	const struct got_error *err = NULL;
+	size_t len;
+	uint8_t *p;
+
+	if (obj->flags & GOT_OBJ_FLAG_PACKED)
+		err = read_to_mem(&p, &len, f);
+	else
+		err = got_inflate_to_mem(&p, &len, f);
+	if (err)
+		return err;
+
+	if (len < obj->hdrlen + obj->size) {
+		err = got_error(GOT_ERR_BAD_OBJ_DATA);
+		goto done;
+	}
+
+	/* Skip object header. */
+	len -= obj->hdrlen;
+	err = got_object_parse_commit(commit, p + obj->hdrlen, len);
+	free(p);
+done:
+	return err;
+}
+
+static void
+read_commit_object_privsep_child(struct got_object *obj, int obj_fd,
+    int imsg_fds[2])
+{
+	const struct got_error *err = NULL;
+	struct got_commit_object *commit = NULL;
+	struct imsgbuf ibuf;
+	FILE *f = NULL;
+	int status = 0;
+
+	setproctitle("read commit object");
+	close(imsg_fds[0]);
+	imsg_init(&ibuf, imsg_fds[1]);
+
+	/* revoke access to most system calls */
+	if (pledge("stdio", NULL) == -1) {
+		err = got_error_from_errno();
+		goto done;
+	}
+
+	f = fdopen(obj_fd, "rb");
+	if (f == NULL) {
+		err = got_error_from_errno();
+		close(obj_fd);
+		goto done;
+	}
+
+	err = read_commit_object(&commit, obj, f);
+	if (err)
+		goto done;
+
+	err = got_privsep_send_commit(&ibuf, commit);
+done:
+	if (commit)
+		got_object_commit_close(commit);
+	if (err) {
+		got_privsep_send_error(&ibuf, err);
+		status = 1;
+	}
+	if (f)
+		fclose(f);
+	imsg_clear(&ibuf);
+	close(imsg_fds[1]);
+	_exit(status);
+}
+
+const struct got_error *
+got_object_read_commit_privsep(struct got_commit_object **commit,
+    struct got_object *obj, int fd)
+{
+	const struct got_error *err = NULL, *err_child = NULL;
+	struct imsgbuf parent_ibuf;
+	int imsg_fds[2];
+	pid_t pid;
+
+	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, imsg_fds) == -1)
+		return got_error_from_errno();
+
+	pid = fork();
+	if (pid == -1)
+		return got_error_from_errno();
+	else if (pid == 0) {
+		read_commit_object_privsep_child(obj, fd, imsg_fds);
+		/* not reached */
+	}
+
+	close(imsg_fds[1]);
+	imsg_init(&parent_ibuf, imsg_fds[0]);
+	err = got_privsep_recv_commit(commit, &parent_ibuf);
+	imsg_clear(&parent_ibuf);
+	err_child = wait_for_child(pid);
+	close(imsg_fds[0]);
+	return err ? err : err_child;
+}
+
+static const struct got_error *
+read_tree_object(struct got_tree_object **tree, struct got_object *obj, FILE *f)
+{
+	const struct got_error *err = NULL;
+	size_t len;
+	uint8_t *p;
+
+	if (obj->flags & GOT_OBJ_FLAG_PACKED)
+		err = read_to_mem(&p, &len, f);
+	else
+		err = got_inflate_to_mem(&p, &len, f);
+	if (err)
+		return err;
+
+	if (len < obj->hdrlen + obj->size) {
+		err = got_error(GOT_ERR_BAD_OBJ_DATA);
+		goto done;
+	}
+
+	/* Skip object header. */
+	len -= obj->hdrlen;
+	err = got_object_parse_tree(tree, p + obj->hdrlen, len);
+	free(p);
+done:
+	return err;
+}
+
+static void
+read_tree_object_privsep_child(struct got_object *obj, int obj_fd,
+    int imsg_fds[2])
+{
+	const struct got_error *err = NULL;
+	struct got_tree_object *tree = NULL;
+	struct imsgbuf ibuf;
+	FILE *f = NULL;
+	int status = 0;
+
+	setproctitle("read tree object");
+	close(imsg_fds[0]);
+	imsg_init(&ibuf, imsg_fds[1]);
+
+	/* revoke access to most system calls */
+	if (pledge("stdio", NULL) == -1) {
+		err = got_error_from_errno();
+		goto done;
+	}
+
+	f = fdopen(obj_fd, "rb");
+	if (f == NULL) {
+		err = got_error_from_errno();
+		close(obj_fd);
+		goto done;
+	}
+
+	err = read_tree_object(&tree, obj, f);
+	if (err)
+		goto done;
+
+	err = got_privsep_send_tree(&ibuf, tree);
+done:
+	if (tree)
+		got_object_tree_close(tree);
+	if (err) {
+		got_privsep_send_error(&ibuf, err);
+		status = 1;
+	}
+	if (f)
+		fclose(f);
+	imsg_clear(&ibuf);
+	close(imsg_fds[1]);
+	_exit(status);
+}
+
+const struct got_error *
+got_object_read_tree_privsep(struct got_tree_object **tree,
+    struct got_object *obj, int fd)
+{
+	const struct got_error *err = NULL, *err_child = NULL;
+	struct imsgbuf parent_ibuf;
+	int imsg_fds[2];
+	pid_t pid;
+
+	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, imsg_fds) == -1)
+		return got_error_from_errno();
+
+	pid = fork();
+	if (pid == -1)
+		return got_error_from_errno();
+	else if (pid == 0) {
+		read_tree_object_privsep_child(obj, fd, imsg_fds);
+		/* not reached */
+	}
+
+	close(imsg_fds[1]);
+	imsg_init(&parent_ibuf, imsg_fds[0]);
+	err = got_privsep_recv_tree(tree, &parent_ibuf);
+	imsg_clear(&parent_ibuf);
+	err_child = wait_for_child(pid);
+	close(imsg_fds[0]);
+	return err ? err : err_child;
+}
+
+static const struct got_error *
+read_blob_object_privsep_child(int outfd, int infd, int imsg_fds[2])
+{
+	const struct got_error *err = NULL;
+	struct imsgbuf ibuf;
+	int status = 0;
+	size_t size;
+	FILE *infile = NULL;
+
+	setproctitle("read blob object");
+	close(imsg_fds[0]);
+	imsg_init(&ibuf, imsg_fds[1]);
+
+	/* revoke access to most system calls */
+	if (pledge("stdio", NULL) == -1) {
+		err = got_error_from_errno();
+		goto done;
+	}
+
+	infile = fdopen(infd, "rb");
+	if (infile == NULL) {
+		err = got_error_from_errno();
+		close(infd);
+		goto done;
+	}
+	err = got_inflate_to_fd(&size, infile, outfd);
+	fclose(infile);
+	if (err)
+		goto done;
+
+	err = got_privsep_send_blob(&ibuf, size);
+done:
+	if (err) {
+		got_privsep_send_error(&ibuf, err);
+		status = 1;
+	}
+	close(outfd);
+	imsg_clear(&ibuf);
+	close(imsg_fds[1]);
+	_exit(status);
+}
+
+const struct got_error *
+got_object_read_blob_privsep(size_t *size, int outfd, int infd)
+{
+	struct imsgbuf parent_ibuf;
+	int imsg_fds[2];
+	const struct got_error *err = NULL, *err_child = NULL;
+	pid_t pid;
+
+	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, imsg_fds) == -1)
+		return got_error_from_errno();
+
+	pid = fork();
+	if (pid == -1)
+		return got_error_from_errno();
+	else if (pid == 0) {
+		read_blob_object_privsep_child(outfd, infd, imsg_fds);
+		/* not reached */
+	}
+
+	close(imsg_fds[1]);
+	imsg_init(&parent_ibuf, imsg_fds[0]);
+	err = got_privsep_recv_blob(size, &parent_ibuf);
+	imsg_clear(&parent_ibuf);
+	err_child = wait_for_child(pid);
+	close(imsg_fds[0]);
+	if (lseek(outfd, SEEK_SET, 0) == -1)
+		err = got_error_from_errno();
+	return err ? err : err_child;
+}
blob - 1a17fecb11d62ea191129249f2495e89a2182902
blob + 7b2c957fd762f6d53cbcf3927a6b89c82d207f12
--- lib/privsep.c
+++ lib/privsep.c
@@ -36,6 +36,7 @@
 #include "got_lib_delta.h"
 #include "got_lib_inflate.h"
 #include "got_lib_object.h"
+#include "got_lib_object_parse.h"
 #include "got_lib_privsep.h"
 
 #ifndef MIN
blob - 38e4d55fd16735f3347afa277906a156fcf932ce
blob + b4eade29e86f3947e2c77fb07aa7ee63085e46fd
--- regress/idset/Makefile
+++ regress/idset/Makefile
@@ -3,7 +3,7 @@
 PROG = idset_test
 SRCS = error.c object.c privsep.c sha1.c pack.c inflate.c path.c opentemp.c \
 	delta.c repository.c reference.c worktree.c fileindex.c \
-	object_idcache.c object_idset.c idset_test.c
+	object_idcache.c object_idset.c object_parse.c idset_test.c
 
 CPPFLAGS = -I${.CURDIR}/../../include -I${.CURDIR}/../../lib
 LDADD = -lutil -lz
blob - 42b06ff5a56203d601f0b4921e63d9137a6e6a5a
blob + ee47b5d17c6579fcc0d5a562dbb73b3a98a3da3b
--- regress/repository/Makefile
+++ regress/repository/Makefile
@@ -2,8 +2,9 @@
 
 PROG = repository_test
 SRCS = path.c repository.c error.c reference.c object.c object_idcache.c \
-	object_idset.c opentemp.c sha1.c diff.c diffreg.c pack.c privsep.c \
-	delta.c fileindex.c worktree.c inflate.c repository_test.c
+	object_idset.c object_parse.c opentemp.c sha1.c diff.c diffreg.c \
+	pack.c privsep.c delta.c fileindex.c worktree.c inflate.c \
+	repository_test.c
 
 CPPFLAGS = -I${.CURDIR}/../../include -I${.CURDIR}/../../lib
 LDADD = -lutil -lz
blob - 9d0e62fd9a44e3ffd0196775424715a6dddb143d
blob + 4a2eb3aad111c9c4ebda609be120828e2da69773
--- regress/worktree/Makefile
+++ regress/worktree/Makefile
@@ -2,8 +2,8 @@
 
 PROG = worktree_test
 SRCS = worktree.c repository.c object.c object_idcache.c object_idset.c \
-	opentemp.c path.c error.c reference.c sha1.c pack.c privsep.c delta.c \
-	inflate.c fileindex.c worktree_test.c
+	object_parse.c opentemp.c path.c error.c reference.c sha1.c pack.c \
+	privsep.c delta.c inflate.c fileindex.c worktree_test.c
 
 CPPFLAGS = -I${.CURDIR}/../../include -I${.CURDIR}/../../lib
 LDADD = -lutil -lz
blob - 922dbcd707b874d378087d240fc186b754332b02
blob + ab8d78df0d3be8f6a5d681827f8c9ee32971fd07
--- tog/Makefile
+++ tog/Makefile
@@ -3,8 +3,9 @@
 PROG=		tog
 SRCS=		tog.c blame.c commit_graph.c delta.c diff.c diffoffset.c \
 		diffreg.c error.c fileindex.c object.c object_idcache.c \
-		object_idset.c opentemp.c path.c pack.c privsep.c \
-		reference.c repository.c sha1.c worktree.c utf8.c inflate.c
+		object_idset.c object_parse.c opentemp.c path.c pack.c \
+		privsep.c reference.c repository.c sha1.c worktree.c \
+		utf8.c inflate.c
 
 CPPFLAGS = -I${.CURDIR}/../include -I${.CURDIR}/../lib
 LDADD = -lpanel -lncursesw -lutil -lz -lpthread