зеркало из https://github.com/golang/vulndb.git
117 строки
2.9 KiB
Go
117 строки
2.9 KiB
Go
// Copyright 2021 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
//go:build go1.21
|
|
|
|
// Command forks determines if Go modules are similar.
|
|
package main
|
|
|
|
import (
|
|
"cmp"
|
|
"context"
|
|
"encoding/gob"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"os/signal"
|
|
"slices"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
func main() {
|
|
out := flag.CommandLine.Output()
|
|
flag.Usage = func() {
|
|
fmt.Fprintf(out, "usage: forks [ PATH | PATH@VERSION ] ...\n")
|
|
fmt.Fprintf(out, "Print potential forks for each module path or module path @ version.\n")
|
|
fmt.Fprintf(out, "Each fork is preceded by its score. Scores range from 0 to 10, with 10 meaning most\n")
|
|
fmt.Fprintf(out, "similar. Only matches with scores of at least 6 are printed.\n")
|
|
fmt.Fprintf(out, "Scores are approximations that are based on partial data (not the full module content),\n")
|
|
fmt.Fprintf(out, "so even a score of 10 does not mean that the modules are identical.\n")
|
|
flag.PrintDefaults()
|
|
}
|
|
|
|
flag.Parse()
|
|
ctx, _ := signal.NotifyContext(context.Background(), os.Interrupt)
|
|
if err := run(ctx); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
type Forks struct {
|
|
Comment string
|
|
Timestamp time.Time
|
|
MinScore int // smallest score that is stored
|
|
Modules []string // mapping from int to module@version
|
|
Matches map[int][]Score // from module ID to matches and their scores
|
|
|
|
}
|
|
|
|
type Score struct {
|
|
Module int // index into Forks.Modules
|
|
Score int // 0 - 10
|
|
}
|
|
|
|
func run(_ context.Context) error {
|
|
if flag.NArg() == 0 {
|
|
flag.Usage()
|
|
return nil
|
|
}
|
|
dbFilename := os.Getenv("FORKSDB")
|
|
if dbFilename == "" {
|
|
return errors.New("must set FORKSDB to file")
|
|
}
|
|
f, err := os.Open(dbFilename)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
dec := gob.NewDecoder(f)
|
|
var db Forks
|
|
if err := dec.Decode(&db); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Build a map from path@version and path to ID.
|
|
modsToIDs := buildIndex(db.Modules)
|
|
|
|
// Print the forks for each arg.
|
|
for _, arg := range flag.Args() {
|
|
if ids, ok := modsToIDs[arg]; ok {
|
|
for _, id := range ids {
|
|
fmt.Printf("%s\n", db.Modules[id])
|
|
matches := db.Matches[id]
|
|
slices.SortFunc(matches, func(s1, s2 Score) int {
|
|
if c := cmp.Compare(s1.Score, s2.Score); c != 0 {
|
|
return c
|
|
}
|
|
return cmp.Compare(db.Modules[s1.Module], db.Modules[s2.Module])
|
|
})
|
|
for _, m := range matches {
|
|
fmt.Printf(" %2d %s\n", m.Score, db.Modules[m.Module])
|
|
}
|
|
}
|
|
} else {
|
|
fmt.Printf("%s: no forks\n", arg)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// buildIndex builds a map from "path@version" and "path" to IDs.
|
|
func buildIndex(mods []string) map[string][]int {
|
|
modsToIDs := map[string][]int{}
|
|
for i, mv := range mods {
|
|
path, _, found := strings.Cut(mv, "@")
|
|
if !found {
|
|
panic(fmt.Errorf("no '@' in %s", mv))
|
|
}
|
|
modsToIDs[mv] = append(modsToIDs[mv], i)
|
|
modsToIDs[path] = append(modsToIDs[path], i)
|
|
}
|
|
return modsToIDs
|
|
}
|