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 }