-
Notifications
You must be signed in to change notification settings - Fork 52
Expand file tree
/
Copy pathcontrol_file_parse.go
More file actions
237 lines (218 loc) · 6.26 KB
/
Copy pathcontrol_file_parse.go
File metadata and controls
237 lines (218 loc) · 6.26 KB
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
package zsync
/*
* SPDX-FileCopyrightText: 2004,2005,2007,2009,2025,2026 Colin Phipps <[email protected]>
*
* SPDX-License-Identifier: Artistic-2.0
*/
import (
"bufio"
"crypto"
"crypto/sha1"
_ "crypto/sha256"
"encoding/binary"
"errors"
"fmt"
"io"
"strconv"
"strings"
"time"
"github.com/cph6/zsync/internal/rcksum"
)
var (
ErrNotZsyncFile = errors.New("not a zsync file")
ErrMissingRequiredFields = errors.New("required fields missing from zsync control file")
ErrNotSupportedCompressed = errors.New("zsync file only offers compressed download options - not supported in this version")
ErrNotSupported004 = errors.New("zsync 0.0.4 control files are not supported")
)
const (
sizeofStructGzblock = 4 // from zsync-0.6.x
)
func (zs *Syncer) parseControlFile(f io.Reader) error {
checksumBytes := 16
rsumBytes := 4
seqMatches := 1
// Set of headers that we can ignore safely, and counts of how often they
// were seen.
safelines := make(map[string]int)
safelines["Z-URL"] = 0
safelines["Min-Version"] = 0
strongHash := crypto.MD4 // Default from older versions of zsync.
seenHeader := false
reader := bufio.NewReader(f)
for {
line, err := reader.ReadString('\n')
if err != nil {
return err
}
line = strings.TrimSuffix(line, "\n")
if line == "" {
break
}
parts := strings.SplitN(line, ": ", 2)
if len(parts) != 2 {
return fmt.Errorf("bad line: %s", line)
}
key, value := parts[0], parts[1]
// Require the "zsync:" header to come first.
if !seenHeader {
if key != "zsync" {
return ErrNotZsyncFile
}
if value == "0.0.4" {
return ErrNotSupported004
}
seenHeader = true
continue
}
switch key {
case "Length":
var err error
zs.filelen, err = strconv.ParseInt(value, 10, 64)
if err != nil {
return fmt.Errorf("bad length: %s", value)
}
case "Filename":
zs.targetFilename = value
case "URL":
zs.urls = append(zs.urls, value)
case "Blocksize":
blocksize, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return fmt.Errorf("bad blocksize: %s", value)
}
if blocksize&(blocksize-1) != 0 {
return fmt.Errorf("nonsensical blocksize %d", blocksize)
}
zs.blocksize = blocksize
case "Strong-Hash-Algorithm":
switch value {
case "MD4":
strongHash = crypto.MD4
case "MD5":
strongHash = crypto.MD5
case "SHA-224":
strongHash = crypto.SHA224
default:
return fmt.Errorf("unsupported strong checksum algorithm: %s", value)
}
case "Hash-Lengths":
parts := strings.Split(value, ",")
if len(parts) != 3 {
return fmt.Errorf("bad hash lengths: %s", value)
}
var err error
seqMatches, err = strconv.Atoi(parts[0])
if err != nil {
return fmt.Errorf("bad seqMatches: %s", parts[0])
}
rsumBytes, err = strconv.Atoi(parts[1])
if err != nil {
return fmt.Errorf("bad rsumBytes: %s", parts[1])
}
checksumBytes, err = strconv.Atoi(parts[2])
if err != nil {
return fmt.Errorf("bad checksumBytes: %s", parts[2])
}
if rsumBytes < 1 || rsumBytes > 4 || seqMatches < 1 || seqMatches > 2 {
return fmt.Errorf("nonsensical hash lengths: %s", value)
}
case "Safe":
for _, s := range strings.Split(value, ",") {
safelines[s] = 0
}
case "File-Hash":
vs := strings.SplitN(value, ":", 2)
alg := vs[0]
if len(vs) == 2 {
switch alg {
case "SHA-1":
zs.checksumMethod = crypto.SHA1
zs.checksum = vs[1]
case "SHA-256":
zs.checksumMethod = crypto.SHA256
zs.checksum = vs[1]
}
}
// Else, proceed without verification.
case "SHA-1":
if len(value) != sha1.Size*2 {
return fmt.Errorf("SHA-1 digest wrong length")
}
zs.checksum = value
zs.checksumMethod = crypto.SHA1
case "MTime":
t, err := time.Parse(time.RFC1123Z, value)
if err != nil {
return fmt.Errorf("bad mtime: %s", value)
}
zs.mtime = t
case "Z-Map2":
// zsync-0.7.0 and onwards does not support downloading required data
// from inside a compressed version of that data. (It supports
// downloading gzip --rsyncable compressed data where the compressed data
// is the target.)
// But a zsync-0.6.x control file can support uncompressed and compressed
// downloads, so the client library here is capable of *skipping* a Z-Map2
// header.
numEntries, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return fmt.Errorf("bad Z-Map2 size: %s", value)
}
buf := make([]byte, numEntries*sizeofStructGzblock)
if _, err := io.ReadFull(reader, buf); err != nil {
return err
}
_ = buf
default:
// Ignore headers if they were included in "Safe".
if _, ok := safelines[key]; ok {
safelines[key]++
} else {
return fmt.Errorf("unknown header: %s", key)
}
}
}
if zs.filelen == 0 || zs.blocksize == 0 {
return ErrMissingRequiredFields
}
zs.blocks = (zs.filelen + zs.blocksize - 1) / zs.blocksize
if checksumBytes < 3 || checksumBytes > strongHash.New().Size() {
return fmt.Errorf("nonsensical strong hash size specified: %d", checksumBytes)
}
// The current client does not support any file without a "URL" provided; but
// someone using the library might provide alternative download mechanisms so
// we do not treat all files without a URL line as unsupported here. However
// any zsync-0.6.x control file with a compressed URL is clearly intended to
// be used with HTTP downloading, which is not supported by zsync-0.7.0 and
// above unless a non-compressed alternative URL was given.
if len(zs.urls) == 0 && safelines["Z-URL"] > 0 {
return ErrNotSupportedCompressed
}
rs, err := rcksum.New(rcksum.BlockID(zs.blocks), zs.blocksize, strongHash, int(rsumBytes), uint(checksumBytes), seqMatches)
if err != nil {
return err
}
zs.rs = rs
// Read block checksums
for i := int64(0); i < zs.blocks; i++ {
var rsum rcksum.RSum
rsumByte := make([]byte, 4)
checksum := make([]byte, checksumBytes)
// Read rsum.
if _, err := io.ReadFull(reader, rsumByte[4-rsumBytes:]); err != nil {
return err
}
_, err = binary.Decode(rsumByte, binary.BigEndian, &rsum)
if err != nil {
return err
}
// Read checksum
if _, err := io.ReadFull(reader, checksum); err != nil {
return err
}
var cksum rcksum.StrongChecksum
copy(cksum[:], checksum)
zs.rs.AddTargetBlock(rcksum.BlockID(i), rsum, cksum)
}
return nil
}