master
  1package gitdiff
  2
  3import (
  4	"bytes"
  5	"compress/zlib"
  6	"fmt"
  7	"io"
  8	"io/ioutil"
  9	"strconv"
 10	"strings"
 11)
 12
 13func (p *parser) ParseBinaryFragments(f *File) (n int, err error) {
 14	isBinary, hasData, err := p.ParseBinaryMarker()
 15	if err != nil || !isBinary {
 16		return 0, err
 17	}
 18
 19	f.IsBinary = true
 20	if !hasData {
 21		return 0, nil
 22	}
 23
 24	forward, err := p.ParseBinaryFragmentHeader()
 25	if err != nil {
 26		return 0, err
 27	}
 28	if forward == nil {
 29		return 0, p.Errorf(0, "missing data for binary patch")
 30	}
 31	if err := p.ParseBinaryChunk(forward); err != nil {
 32		return 0, err
 33	}
 34	f.BinaryFragment = forward
 35
 36	// valid for reverse to not exist, but it must be valid if present
 37	reverse, err := p.ParseBinaryFragmentHeader()
 38	if err != nil {
 39		return 1, err
 40	}
 41	if reverse == nil {
 42		return 1, nil
 43	}
 44	if err := p.ParseBinaryChunk(reverse); err != nil {
 45		return 1, err
 46	}
 47	f.ReverseBinaryFragment = reverse
 48
 49	return 1, nil
 50}
 51
 52func (p *parser) ParseBinaryMarker() (isBinary bool, hasData bool, err error) {
 53	line := p.Line(0)
 54	switch {
 55	case line == "GIT binary patch\n":
 56		hasData = true
 57	case isBinaryNoDataMarker(line):
 58	default:
 59		return false, false, nil
 60	}
 61
 62	if err = p.Next(); err != nil && err != io.EOF {
 63		return false, false, err
 64	}
 65	return true, hasData, nil
 66}
 67
 68func isBinaryNoDataMarker(line string) bool {
 69	if strings.HasSuffix(line, " differ\n") {
 70		return strings.HasPrefix(line, "Binary files ") || strings.HasPrefix(line, "Files ")
 71	}
 72	return false
 73}
 74
 75func (p *parser) ParseBinaryFragmentHeader() (*BinaryFragment, error) {
 76	parts := strings.SplitN(strings.TrimSuffix(p.Line(0), "\n"), " ", 2)
 77	if len(parts) < 2 {
 78		return nil, nil
 79	}
 80
 81	frag := &BinaryFragment{}
 82	switch parts[0] {
 83	case "delta":
 84		frag.Method = BinaryPatchDelta
 85	case "literal":
 86		frag.Method = BinaryPatchLiteral
 87	default:
 88		return nil, nil
 89	}
 90
 91	var err error
 92	if frag.Size, err = strconv.ParseInt(parts[1], 10, 64); err != nil {
 93		nerr := err.(*strconv.NumError)
 94		return nil, p.Errorf(0, "binary patch: invalid size: %v", nerr.Err)
 95	}
 96
 97	if err := p.Next(); err != nil && err != io.EOF {
 98		return nil, err
 99	}
100	return frag, nil
101}
102
103func (p *parser) ParseBinaryChunk(frag *BinaryFragment) error {
104	// Binary fragments are encoded as a series of base85 encoded lines. Each
105	// line starts with a character in [A-Za-z] giving the number of bytes on
106	// the line, where A = 1 and z = 52, and ends with a newline character.
107	//
108	// The base85 encoding means each line is a multiple of 5 characters + 2
109	// additional characters for the length byte and the newline. The fragment
110	// ends with a blank line.
111	const (
112		shortestValidLine = "A00000\n"
113		maxBytesPerLine   = 52
114	)
115
116	var data bytes.Buffer
117	buf := make([]byte, maxBytesPerLine)
118	for {
119		line := p.Line(0)
120		if line == "\n" {
121			break
122		}
123		if len(line) < len(shortestValidLine) || (len(line)-2)%5 != 0 {
124			return p.Errorf(0, "binary patch: corrupt data line")
125		}
126
127		byteCount, seq := int(line[0]), line[1:len(line)-1]
128		switch {
129		case 'A' <= byteCount && byteCount <= 'Z':
130			byteCount = byteCount - 'A' + 1
131		case 'a' <= byteCount && byteCount <= 'z':
132			byteCount = byteCount - 'a' + 27
133		default:
134			return p.Errorf(0, "binary patch: invalid length byte")
135		}
136
137		// base85 encodes every 4 bytes into 5 characters, with up to 3 bytes of end padding
138		maxByteCount := len(seq) / 5 * 4
139		if byteCount > maxByteCount || byteCount < maxByteCount-3 {
140			return p.Errorf(0, "binary patch: incorrect byte count")
141		}
142
143		if err := base85Decode(buf[:byteCount], []byte(seq)); err != nil {
144			return p.Errorf(0, "binary patch: %v", err)
145		}
146		data.Write(buf[:byteCount])
147
148		if err := p.Next(); err != nil {
149			if err == io.EOF {
150				return p.Errorf(0, "binary patch: unexpected EOF")
151			}
152			return err
153		}
154	}
155
156	if err := inflateBinaryChunk(frag, &data); err != nil {
157		return p.Errorf(0, "binary patch: %v", err)
158	}
159
160	// consume the empty line that ended the fragment
161	if err := p.Next(); err != nil && err != io.EOF {
162		return err
163	}
164	return nil
165}
166
167func inflateBinaryChunk(frag *BinaryFragment, r io.Reader) error {
168	zr, err := zlib.NewReader(r)
169	if err != nil {
170		return err
171	}
172
173	data, err := ioutil.ReadAll(zr)
174	if err != nil {
175		return err
176	}
177	if err := zr.Close(); err != nil {
178		return err
179	}
180
181	if int64(len(data)) != frag.Size {
182		return fmt.Errorf("%d byte fragment inflated to %d", frag.Size, len(data))
183	}
184	frag.Data = data
185	return nil
186}