aboutsummaryrefslogtreecommitdiff
path: root/object/signed/tag/parse.go
blob: b2061d3ff952177e5b6b64958ab2f9ea07fd8a92 (about) (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package signedtag

import (
	"bytes"
	"slices"

	objectid "codeberg.org/lindenii/furgit/object/id"
)

var signatureBeginLines = [][]byte{ //nolint:gochecknoglobals
	[]byte("-----BEGIN PGP SIGNATURE-----"),
	[]byte("-----BEGIN PGP MESSAGE-----"),
	[]byte("-----BEGIN SSH SIGNATURE-----"),
	[]byte("-----BEGIN SIGNED MESSAGE-----"),
}

// Parse parses one raw tag object body for signature extraction.
//
// Git stores the signature for storageAlgo as an in-body ASCII-armored
// trailer, and may store additional signatures for other algorithms in
// gpgsig* headers.
//
// The returned Tag remains valid only while body remains unchanged.
//
// Labels: Deps-Borrowed, Life-Parent.
func Parse(body []byte, storageAlgo objectid.Algorithm) (*Tag, error) {
	tag := &Tag{
		body:       body,
		signatures: make(map[objectid.Algorithm][]byteRange),
	}

	signatureStart := len(body)
	for i := 0; i < len(body); {
		lineStart := i
		rel := bytes.IndexByte(body[i:], '\n')
		next := len(body)

		lineEnd := len(body)
		if rel >= 0 {
			lineEnd = i + rel
			next = lineEnd + 1
		}

		line := body[lineStart:lineEnd]
		if slices.ContainsFunc(signatureBeginLines, func(begin []byte) bool {
			return bytes.HasPrefix(line, begin)
		}) {
			signatureStart = lineStart
		}

		i = next
	}

	payloadStart := 0

	payloadEnd := signatureStart
	if signatureStart == len(body) {
		payloadEnd = len(body)
	}

	for i := 0; i < payloadEnd; {
		lineStart := i
		rel := bytes.IndexByte(body[i:payloadEnd], '\n')
		next := payloadEnd

		lineEnd := payloadEnd
		if rel >= 0 {
			lineEnd = i + rel
			next = lineEnd + 1
		}

		line := body[lineStart:lineEnd]
		i = next

		if len(line) == 0 {
			break
		}

		if line[0] == ' ' {
			continue
		}

		key, valueStart, found := bytes.Cut(line, []byte{' '})
		if !found {
			continue
		}

		algo, ok := objectid.ParseSignatureHeaderName(string(key))
		if !ok {
			continue
		}

		tag.appendPayloadRange(payloadStart, lineStart)
		tag.signatures[algo] = append(tag.signatures[algo], byteRange{
			start: lineEnd - len(valueStart),
			end:   next,
		})

		for i < payloadEnd {
			rel := bytes.IndexByte(body[i:payloadEnd], '\n')
			next = payloadEnd

			lineEnd = payloadEnd
			if rel >= 0 {
				lineEnd = i + rel
				next = lineEnd + 1
			}

			cont := body[i:lineEnd]
			if len(cont) == 0 || cont[0] != ' ' {
				break
			}

			tag.signatures[algo] = append(tag.signatures[algo], byteRange{
				start: i + 1,
				end:   next,
			})

			i = next
		}

		payloadStart = i
	}

	tag.appendPayloadRange(payloadStart, payloadEnd)

	if signatureStart != len(body) {
		tag.signatures[storageAlgo] = append(tag.signatures[storageAlgo], byteRange{
			start: signatureStart,
			end:   len(body),
		})
	}

	return tag, nil
}

func (tag *Tag) appendPayloadRange(start, end int) {
	if start >= end {
		return
	}

	tag.payload = append(tag.payload, byteRange{start: start, end: end})
}