diff options
Diffstat (limited to 'internal/slackware_com/mirror.go')
-rw-r--r-- | internal/slackware_com/mirror.go | 202 |
1 files changed, 202 insertions, 0 deletions
diff --git a/internal/slackware_com/mirror.go b/internal/slackware_com/mirror.go new file mode 100644 index 0000000..f3bb4a8 --- /dev/null +++ b/internal/slackware_com/mirror.go @@ -0,0 +1,202 @@ +package slackware_com + +import ( + "bytes" + "context" + "fmt" + "hash" + "io" + "net" + "net/http" + "net/url" + "path" + "strings" +) + +type Mirror struct { + client *http.Client + url *url.URL + + ChangeLog *ChangeLog + Checksums *Checksums +} + +func OpenMirror(mirrorURL *url.URL) (*Mirror, error) { + var zeroDialer net.Dialer + transport := http.DefaultTransport.(*http.Transport).Clone() + transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { + return zeroDialer.DialContext(ctx, "tcp4", addr) + } + http.DefaultClient.Transport = transport + + mirror := Mirror{ + client: http.DefaultClient, + url: mirrorURL, + } + + err := mirror.Sync() + if err != nil { + return nil, err + } + + return &mirror, nil +} + +func signaturePath(filePath string) string { + return fmt.Sprintf("%s.asc", filePath) +} + +func isSignature(filePath string) bool { + return strings.HasSuffix(filePath, ".asc") +} + +func (m *Mirror) hasSignature(filePath string) bool { + if isSignature(filePath) { + return false + } + if path.Base(filePath) == ChecksumsMD5 { + return true + } + return m.Checksums.HasFilePath(signaturePath(filePath)) +} + +func (m *Mirror) DownloadAndVerify(wr io.Writer, filePath string) error { + fileUrl := m.url.JoinPath(filePath).String() + resp, err := m.client.Get(fileUrl) + if err != nil { + return err + } + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("bad HTTP response: %s", http.StatusText(resp.StatusCode)) + } + + var r io.Reader = resp.Body + defer resp.Body.Close() + + var hash *hash.Hash + if filePath != ChecksumsMD5 && filePath != signaturePath(ChecksumsMD5) { + h := m.Checksums.Hash() + r = io.TeeReader(r, h) + + hash = &h + } + + var signatureVerifier *GnuPGVerifier + if m.hasSignature(filePath) { + var signature bytes.Buffer + err := m.DownloadAndVerify(&signature, signaturePath(filePath)) + if err != nil { + return err + } + + signatureVerifier = RunSignatureVerifier(signature.String()) + r = io.TeeReader(r, signatureVerifier) + } + + _, err = io.Copy(wr, r) + if err != nil { + return err + } + + if hash != nil { + ok, err := m.Checksums.Verify(filePath, (*hash).Sum(nil)) + if err != nil { + return err + } + if !ok { + return fmt.Errorf("MD5 verification failed") + } + } + + if signatureVerifier != nil { + ok, _ := signatureVerifier.IsValid() + if !ok { + return fmt.Errorf("signature verification failed") + } + } + + return nil +} + +func (m *Mirror) downloadChangeLog() error { + var buf bytes.Buffer + err := m.DownloadAndVerify(&buf, ChangeLogTxt) + if err != nil { + return fmt.Errorf("downloading %s: %w", ChangeLogTxt, err) + } + + var changeLog ChangeLog + err = changeLog.UnmarshalText(buf.Bytes()) + if err != nil { + return fmt.Errorf("%s: %w", ChangeLogTxt, err) + } + m.ChangeLog = &changeLog + + return nil +} + +func (m *Mirror) downloadChecksums() error { + var buf bytes.Buffer + err := m.DownloadAndVerify(&buf, ChecksumsMD5) + if err != nil { + return fmt.Errorf("downloading %s: %w", ChecksumsMD5, err) + } + + var checksums Checksums + err = checksums.UnmarshalText(buf.Bytes()) + if err != nil { + return fmt.Errorf("%s: %w", ChecksumsMD5, err) + } + m.Checksums = &checksums + + return nil +} + +// FindPackage returns the file path for the most appropriate package. +func (m *Mirror) FindPackage(pattern PackageNamePattern) (string, bool) { + found := "" + priority := 0 + + pathPriority := 0 + for _, file := range m.Checksums.Files() { + if !IsPackage(file) { + continue + } + file = path.Clean(file) + + if strings.HasPrefix(file, "patches/packages") { + pathPriority = 5 + } else if strings.HasPrefix(file, "slackware") { + pathPriority = 4 + } else if strings.HasPrefix(file, "extra") { + pathPriority = 3 + } else if strings.HasPrefix(file, "testing/packages") { + pathPriority = 2 + } else if strings.HasPrefix(file, "pasture") { + pathPriority = 1 + } else { + continue + } + + pkg, _ := NewPackageNameFromPath(file) + if pattern.IsMatch(pkg) && pathPriority > priority { + found = file + priority = pathPriority + } + } + + return found, found != "" +} + +func (m *Mirror) Sync() error { + err := m.downloadChecksums() + if err != nil { + return err + } + err = m.downloadChangeLog() + if err != nil { + return err + } + return nil +} |