-/*
-meta4a -- Metalink 4.0 checker
-Copyright (C) 2021-2023 Sergey Matveev <stargrave@stargrave.org>
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, version 3 of the License.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program. If not, see <http://www.gnu.org/licenses/>.
-*/
+// meta4ra -- Metalink 4.0 utilities
+// Copyright (C) 2021-2024 Sergey Matveev <stargrave@stargrave.org>
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, version 3 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
// Metalink 4.0 checker
package main
"io/fs"
"log"
"os"
+ "path"
"strings"
"go.stargrave.org/meta4ra"
)
func main() {
- hashes := flag.String("hashes", strings.Join(meta4ra.HashesDefault, ","), "hash-name:command-s")
+ hashes := flag.String("hashes",
+ strings.Join(meta4ra.HashesDefault, ","), "hash-name:command-s")
extractSig := flag.Bool("extract-sig", false, "Extract signature files")
- log.SetFlags(log.Lshortfile)
+ metaPath := flag.String("meta4", "file.meta4", "Metalink file")
+ flag.Usage = func() {
+ fmt.Fprintf(flag.CommandLine.Output(),
+ "Usage: %s [options] [FILE ...]\n", os.Args[0])
+ flag.PrintDefaults()
+ fmt.Fprint(flag.CommandLine.Output(), `
+If no FILEs are specified, then all <file>s from metalink are searched and
+verified if they exist. Otherwise only specified FILEs are checked. If you
+want to skip any <file> verification (for example only to validate the format
+and -extract-sig, then you can just specify empty ("") FILE.
+`)
+ }
flag.Parse()
+ log.SetFlags(log.Lshortfile)
+
+ data, err := os.ReadFile(*metaPath)
+ if err != nil {
+ log.Fatalln(err)
+ }
+ var meta meta4ra.Metalink
+ err = xml.Unmarshal(data, &meta)
+ if err != nil {
+ log.Fatalln(err)
+ }
+
+ toCheck := make(map[string]string)
+ for _, fn := range flag.Args() {
+ toCheck[path.Base(fn)] = fn
+ }
+
bad := false
- for _, metaPath := range flag.Args() {
- data, err := os.ReadFile(metaPath)
- if err != nil {
- log.Fatalln(err)
- }
- var meta meta4ra.Metalink
- err = xml.Unmarshal(data, &meta)
- if err != nil {
- log.Fatalln(err)
- }
- for _, f := range meta.Files {
- for _, sig := range f.Signature {
- if !*extractSig {
- continue
- }
- var fn string
- switch sig.MediaType {
- case meta4ra.SigMediaTypePGP:
- fn = f.Name + ".asc"
- case meta4ra.SigMediaTypeSSH:
- fn = f.Name + ".sig"
- }
- if fn == "" {
- continue
- }
- if err = os.WriteFile(
- fn,
- []byte(strings.TrimPrefix(sig.Signature, "\n")),
- fs.FileMode(0666),
- ); err != nil {
- fmt.Println("Error:", f.Name, "can not save signature:", err)
- bad = true
- }
+ for _, f := range meta.Files {
+ for _, sig := range f.Signature {
+ if !*extractSig {
+ continue
}
- hasher := meta4ra.NewHasher(*hashes)
- var hashTheir string
- var hashName string
- for i, name := range hasher.Names {
- for _, h := range f.Hashes {
- if h.Type == name {
- hasher.Names = []string{name}
- hasher.Cmds = append(hasher.Cmds[:0], hasher.Cmds[i])
- hasher.Ins = append(hasher.Ins[:0], hasher.Ins[i])
- hasher.Outs = append(hasher.Outs[:0], hasher.Outs[i])
- hashName = name
- hashTheir = h.Hash
- goto HashFound
- }
- }
+ var fn string
+ switch sig.MediaType {
+ case meta4ra.SigMediaTypePGP:
+ fn = f.Name + ".asc"
+ case meta4ra.SigMediaTypeSSH:
+ fn = f.Name + ".sig"
}
- log.Fatalln("no common hashes found for:", f.Name)
- HashFound:
- fd, err := os.Open(f.Name)
- if err != nil {
+ if fn == "" {
continue
}
- hasher.Start()
- _, err = io.Copy(hasher, bufio.NewReaderSize(fd, 1<<20))
- fd.Close()
- sums := hasher.Sums()
- if err != nil {
- fmt.Println("Error:", f.Name, err)
+ if err = os.WriteFile(
+ fn,
+ []byte(strings.TrimPrefix(sig.Signature, "\n")),
+ fs.FileMode(0666),
+ ); err != nil {
+ fmt.Println("Error:", f.Name, "can not save signature:", err)
bad = true
- continue
}
- hashOur := sums[0].Hash
- if hashOur == hashTheir {
- fmt.Println(f.Name, hashName, "good")
- } else {
- fmt.Println(
- "Hash does not match:", f.Name, hashName,
- "our:", hashOur,
- "their:", hashTheir,
- )
- bad = true
- continue
+ }
+
+ fullPath := toCheck[f.Name]
+ if !(len(toCheck) == 0 || fullPath != "") {
+ continue
+ }
+ if fullPath == "" {
+ fullPath = f.Name
+ }
+ s, err := os.Stat(fullPath)
+ if err != nil {
+ fmt.Println(err)
+ bad = true
+ continue
+ }
+ if uint64(s.Size()) != f.Size {
+ fmt.Println("size mismatch",
+ f.Name, "our:", s.Size(), "their:", f.Size)
+ bad = true
+ continue
+ }
+
+ hasher := meta4ra.NewHasher(*hashes)
+ var hashTheir string
+ var hashName string
+ for i, name := range hasher.Names {
+ for _, h := range f.Hashes {
+ if h.Type == name {
+ hasher.Names = []string{name}
+ hasher.Cmds = append(hasher.Cmds[:0], hasher.Cmds[i])
+ hasher.Ins = append(hasher.Ins[:0], hasher.Ins[i])
+ hasher.Outs = append(hasher.Outs[:0], hasher.Outs[i])
+ hashName = name
+ hashTheir = h.Hash
+ goto HashFound
+ }
}
}
+ fmt.Println("no common hashes found for:", f.Name)
+ bad = true
+ continue
+
+ HashFound:
+ fd, err := os.Open(fullPath)
+ if err != nil {
+ fmt.Println("Error:", f.Name, err)
+ bad = true
+ continue
+ }
+ hasher.Start()
+ _, err = io.Copy(hasher, bufio.NewReaderSize(fd, 1<<20))
+ fd.Close()
+ sums := hasher.Sums()
+ if err != nil {
+ fmt.Println("Error:", f.Name, err)
+ bad = true
+ continue
+ }
+ hashOur := sums[0].Hash
+ if hashOur == hashTheir {
+ fmt.Println(f.Name, hashName, "good")
+ } else {
+ fmt.Println(
+ "hash mismatch:", f.Name, hashName,
+ "our:", hashOur,
+ "their:", hashTheir,
+ )
+ bad = true
+ continue
+ }
}
if bad {
os.Exit(1)