]> Sergey Matveev's repositories - meta4ra.git/blob - cmd/meta4-check/main.go
Check size before hashing
[meta4ra.git] / cmd / meta4-check / main.go
1 // meta4ra -- Metalink 4.0 utilities
2 // Copyright (C) 2021-2024 Sergey Matveev <stargrave@stargrave.org>
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, version 3 of the License.
7 //
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 // GNU General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
15
16 // Metalink 4.0 checker
17 package main
18
19 import (
20         "bufio"
21         "encoding/xml"
22         "flag"
23         "fmt"
24         "io"
25         "io/fs"
26         "log"
27         "os"
28         "path"
29         "strings"
30
31         "go.stargrave.org/meta4ra"
32 )
33
34 func main() {
35         hashes := flag.String("hashes",
36                 strings.Join(meta4ra.HashesDefault, ","), "hash-name:command-s")
37         extractSig := flag.Bool("extract-sig", false, "Extract signature files")
38         metaPath := flag.String("meta4", "file.meta4", "Metalink file")
39         flag.Usage = func() {
40                 fmt.Fprintf(flag.CommandLine.Output(),
41                         "Usage: %s [options] [FILE ...]\n", os.Args[0])
42                 flag.PrintDefaults()
43                 fmt.Fprint(flag.CommandLine.Output(), `
44 If no FILEs are specified, then all <file>s from metalink are searched and
45 verified if they exist. Otherwise only specified FILEs are checked. If you
46 want to skip any <file> verification (for example only to validate the format
47 and -extract-sig, then you can just specify empty ("") FILE.
48 `)
49         }
50         flag.Parse()
51         log.SetFlags(log.Lshortfile)
52
53         data, err := os.ReadFile(*metaPath)
54         if err != nil {
55                 log.Fatalln(err)
56         }
57         var meta meta4ra.Metalink
58         err = xml.Unmarshal(data, &meta)
59         if err != nil {
60                 log.Fatalln(err)
61         }
62
63         toCheck := make(map[string]string)
64         for _, fn := range flag.Args() {
65                 toCheck[path.Base(fn)] = fn
66         }
67
68         bad := false
69         for _, f := range meta.Files {
70                 for _, sig := range f.Signature {
71                         if !*extractSig {
72                                 continue
73                         }
74                         var fn string
75                         switch sig.MediaType {
76                         case meta4ra.SigMediaTypePGP:
77                                 fn = f.Name + ".asc"
78                         case meta4ra.SigMediaTypeSSH:
79                                 fn = f.Name + ".sig"
80                         }
81                         if fn == "" {
82                                 continue
83                         }
84                         if err = os.WriteFile(
85                                 fn,
86                                 []byte(strings.TrimPrefix(sig.Signature, "\n")),
87                                 fs.FileMode(0666),
88                         ); err != nil {
89                                 fmt.Println("Error:", f.Name, "can not save signature:", err)
90                                 bad = true
91                         }
92                 }
93
94                 fullPath := toCheck[f.Name]
95                 if !(len(toCheck) == 0 || fullPath != "") {
96                         continue
97                 }
98                 if fullPath == "" {
99                         fullPath = f.Name
100                 }
101                 s, err := os.Stat(fullPath)
102                 if err != nil {
103                         fmt.Println(err)
104                         bad = true
105                         continue
106                 }
107                 if uint64(s.Size()) != f.Size {
108                         fmt.Println("size mismatch",
109                                 f.Name, "our:", s.Size(), "their:", f.Size)
110                         bad = true
111                         continue
112                 }
113
114                 hasher := meta4ra.NewHasher(*hashes)
115                 var hashTheir string
116                 var hashName string
117                 for i, name := range hasher.Names {
118                         for _, h := range f.Hashes {
119                                 if h.Type == name {
120                                         hasher.Names = []string{name}
121                                         hasher.Cmds = append(hasher.Cmds[:0], hasher.Cmds[i])
122                                         hasher.Ins = append(hasher.Ins[:0], hasher.Ins[i])
123                                         hasher.Outs = append(hasher.Outs[:0], hasher.Outs[i])
124                                         hashName = name
125                                         hashTheir = h.Hash
126                                         goto HashFound
127                                 }
128                         }
129                 }
130                 fmt.Println("no common hashes found for:", f.Name)
131                 bad = true
132                 continue
133
134         HashFound:
135                 fd, err := os.Open(fullPath)
136                 if err != nil {
137                         fmt.Println("Error:", f.Name, err)
138                         bad = true
139                         continue
140                 }
141                 hasher.Start()
142                 _, err = io.Copy(hasher, bufio.NewReaderSize(fd, 1<<20))
143                 fd.Close()
144                 sums := hasher.Sums()
145                 if err != nil {
146                         fmt.Println("Error:", f.Name, err)
147                         bad = true
148                         continue
149                 }
150                 hashOur := sums[0].Hash
151                 if hashOur == hashTheir {
152                         fmt.Println(f.Name, hashName, "good")
153                 } else {
154                         fmt.Println(
155                                 "hash mismatch:", f.Name, hashName,
156                                 "our:", hashOur,
157                                 "their:", hashTheir,
158                         )
159                         bad = true
160                         continue
161                 }
162         }
163         if bad {
164                 os.Exit(1)
165         }
166 }