/* * Copyright (C) the libgit2 contributors. All rights reserved. * * 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 "common.h" #include "git2/odb.h" #include "delta-apply.h" /* * This file was heavily cribbed from BinaryDelta.java in JGit, which * itself was heavily cribbed from patch-delta.c in the * GIT project. The original delta patching code was written by * Nicolas Pitre . */ static int hdr_sz( size_t *size, const unsigned char **delta, const unsigned char *end) { const unsigned char *d = *delta; size_t r = 0; unsigned int c, shift = 0; do { if (d == end) return -1; c = *d++; r |= (c & 0x7f) << shift; shift += 7; } while (c & 0x80); *delta = d; *size = r; return 0; } int git__delta_read_header( const unsigned char *delta, size_t delta_len, size_t *base_sz, size_t *res_sz) { const unsigned char *delta_end = delta + delta_len; if ((hdr_sz(base_sz, &delta, delta_end) < 0) || (hdr_sz(res_sz, &delta, delta_end) < 0)) return -1; return 0; } #define DELTA_HEADER_BUFFER_LEN 16 int git__delta_read_header_fromstream(size_t *base_sz, size_t *res_sz, git_packfile_stream *stream) { static const size_t buffer_len = DELTA_HEADER_BUFFER_LEN; unsigned char buffer[DELTA_HEADER_BUFFER_LEN]; const unsigned char *delta, *delta_end; size_t len; ssize_t read; len = read = 0; while (len < buffer_len) { read = git_packfile_stream_read(stream, &buffer[len], buffer_len - len); if (read == 0) break; if (read == GIT_EBUFS) continue; len += read; } delta = buffer; delta_end = delta + len; if ((hdr_sz(base_sz, &delta, delta_end) < 0) || (hdr_sz(res_sz, &delta, delta_end) < 0)) return -1; return 0; } int git__delta_apply( git_rawobj *out, const unsigned char *base, size_t base_len, const unsigned char *delta, size_t delta_len) { const unsigned char *delta_end = delta + delta_len; size_t base_sz, res_sz, alloc_sz; unsigned char *res_dp; /* Check that the base size matches the data we were given; * if not we would underflow while accessing data from the * base object, resulting in data corruption or segfault. */ if ((hdr_sz(&base_sz, &delta, delta_end) < 0) || (base_sz != base_len)) { giterr_set(GITERR_INVALID, "Failed to apply delta. Base size does not match given data"); return -1; } if (hdr_sz(&res_sz, &delta, delta_end) < 0) { giterr_set(GITERR_INVALID, "Failed to apply delta. Base size does not match given data"); return -1; } GITERR_CHECK_ALLOC_ADD(&alloc_sz, res_sz, 1); res_dp = git__malloc(alloc_sz); GITERR_CHECK_ALLOC(res_dp); res_dp[res_sz] = '\0'; out->data = res_dp; out->len = res_sz; while (delta < delta_end) { unsigned char cmd = *delta++; if (cmd & 0x80) { /* cmd is a copy instruction; copy from the base. */ size_t off = 0, len = 0; if (cmd & 0x01) off = *delta++; if (cmd & 0x02) off |= *delta++ << 8UL; if (cmd & 0x04) off |= *delta++ << 16UL; if (cmd & 0x08) off |= *delta++ << 24UL; if (cmd & 0x10) len = *delta++; if (cmd & 0x20) len |= *delta++ << 8UL; if (cmd & 0x40) len |= *delta++ << 16UL; if (!len) len = 0x10000; if (base_len < off + len || res_sz < len) goto fail; memcpy(res_dp, base + off, len); res_dp += len; res_sz -= len; } else if (cmd) { /* cmd is a literal insert instruction; copy from * the delta stream itself. */ if (delta_end - delta < cmd || res_sz < cmd) goto fail; memcpy(res_dp, delta, cmd); delta += cmd; res_dp += cmd; res_sz -= cmd; } else { /* cmd == 0 is reserved for future encodings. */ goto fail; } } if (delta != delta_end || res_sz) goto fail; return 0; fail: git__free(out->data); out->data = NULL; giterr_set(GITERR_INVALID, "Failed to apply delta"); return -1; }