blob: 7c1693d0f8e959785f46432317c6deb50a0f729a [file] [log] [blame]
// Copyright 2022 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.
// Package modules assists in working with modules, e.g.,
// downloading a module via a Go proxy client.
package modules
import (
"archive/zip"
"context"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"golang.org/x/pkgsite-metrics/internal/derrors"
"golang.org/x/pkgsite-metrics/internal/log"
"golang.org/x/pkgsite-metrics/internal/proxy"
)
// Download fetches module at version via proxyClient and writes the modules
// down to disk at dir.
func Download(ctx context.Context, module, version, dir string, proxyClient *proxy.Client, stripModulePrefix bool) error {
zipr, err := proxyClient.Zip(ctx, module, version)
if err != nil {
return fmt.Errorf("%v: %w", err, derrors.ProxyError)
}
log.Debugf(ctx, "writing module zip: %s@%s", module, version)
stripPrefix := ""
if stripModulePrefix {
stripPrefix = module + "@" + version + "/"
}
if err := writeZip(zipr, dir, stripPrefix); err != nil {
return fmt.Errorf("%v: %w", err, derrors.ScanModuleOSError)
}
return nil
}
func writeZip(r *zip.Reader, destination, stripPrefix string) error {
for _, f := range r.File {
name := strings.TrimPrefix(f.Name, stripPrefix)
fpath := filepath.Join(destination, name)
if !strings.HasPrefix(fpath, filepath.Clean(destination)+string(os.PathSeparator)) {
return fmt.Errorf("%s is an illegal filepath", fpath)
}
// Do not include vendor directory. They currently contain only modules.txt,
// not the dependencies. This makes package loading fail. Starting with go1.24,
// there likely won't be any vendor directories at all.
if vendored(name) {
continue
}
if f.FileInfo().IsDir() {
if err := os.MkdirAll(fpath, os.ModePerm); err != nil {
return err
}
continue
}
if err := os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
return err
}
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return err
}
rc, err := f.Open()
if err != nil {
return err
}
if _, err := io.Copy(outFile, rc); err != nil {
return err
}
if err := outFile.Close(); err != nil {
return err
}
if err := rc.Close(); err != nil {
return err
}
}
return nil
}
func vendored(path string) bool {
return path == "vendor" || strings.HasPrefix(path, "vendor"+string(os.PathSeparator))
}