Copyright 2019 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 fetch provides a way to fetch modules from a proxy.
package fetch

import (
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	

	
	
	
	
	
	
	
	
)
BadPackageError represents an error loading a package because its contents do not make up a valid package. This can happen, for example, if the .go files fail to parse or declare different package names.
type BadPackageError struct {
	Err error // Not nil.
}

func ( *BadPackageError) () string { return .Err.Error() }
loadPackage loads a Go package by calling loadPackageWithBuildContext, trying several build contexts in turn. It returns a goPackage with documentation information for each build context that results in a valid package, in the same order that the build contexts are listed. If none of them result in a package, then loadPackage returns nil, nil. If a package is fine except that its documentation is too large, loadPackage returns a goPackage whose err field is a non-nil error with godoc.ErrTooLarge in its chain.
func ( context.Context,  []*zip.File,  string,
	 *source.Info,  *godoc.ModuleInfo) ( *goPackage,  error) {
	defer derrors.Wrap(&, "loadPackage(ctx, zipGoFiles, %q, sourceInfo, modInfo)", )
	,  := trace.StartSpan(, "fetch.loadPackage")
	defer .End()
Make a map with all the zip file contents.
	 := make(map[string][]byte)
	for ,  := range  {
		,  := path.Split(.Name)
		,  := readZipFile(, MaxFileSize)
		if  != nil {
			return nil, 
		}
		[] = 
	}

	 := .ModulePath
	 := path.Join(, )
	if  == stdlib.ModulePath {
		 = 
	}
	 := internal.V1Path(, )

Parse the package for each build context. The documentation is determined by the set of matching files, so keep track of those to avoid duplication.
	 := map[string]*internal.Documentation{}
	for ,  := range internal.BuildContexts {
		,  := matchingFiles(.GOOS, .GOARCH, )
		if  != nil {
			return nil, 
		}
		 := mapKeyForFiles()
We have seen this set of files before. loadPackageWithBuildContext will produce the same outputs, so don't bother calling it. Just copy the doc.
			 := *
			.GOOS = .GOOS
			.GOARCH = .GOARCH
			.API = nil
			for ,  := range .API {
				 := *
				.Children = nil
				.GOOS = .GOOS
				.GOARCH = .GOARCH
				.Children = append(.Children, .Children...)
				.API = append(.API, &)
			}
			.docs = append(.docs, &)
			continue
		}
		, , , , ,  := loadPackageForBuildContext(,
			, , , )
		for ,  := range  {
			.GOOS = .GOOS
			.GOARCH = .GOARCH
		}

		switch {
No package for this build context.
			continue
This build context was bad, but maybe others aren't.
			continue
The doc for this build context is too large. To keep things simple, return a single package with this error that will be used for all build contexts, and ignore the others.
			return &goPackage{
				err:     ,
				path:    ,
				v1path:  ,
				name:    ,
				imports: ,
				docs: []*internal.Documentation{{
					GOOS:     internal.All,
					GOARCH:   internal.All,
					Synopsis: ,
					Source:   ,
					API:      ,
				}},
			}, nil
Serious error. Fail.
			return nil, 
No error.
			if  == nil {
				 = &goPackage{
					path:    ,
					v1path:  ,
					name:    ,
					imports: , // Use the imports from the first successful build context.
				}
All the build contexts should use the same package name. Although it's technically legal for different build tags to result in different package names, it's not something we support.
			if  != .name {
				return nil, &BadPackageError{
					Err: fmt.Errorf("more than one package name (%q and %q)", .name, ),
				}
			}
			 := &internal.Documentation{
				GOOS:     .GOOS,
				GOARCH:   .GOARCH,
				Synopsis: ,
				Source:   ,
				API:      ,
			}
			[] = 
			.docs = append(.docs, )
		}
If all the build contexts succeeded and had the same set of files, then assume that the package doc is valid for all build contexts. Represent this with a single Documentation whose GOOS and GOARCH are both "all".
	if len() == 1 && len(.docs) == len(internal.BuildContexts) {
		.docs = .docs[:1]
		.docs[0].GOOS = internal.All
		.docs[0].GOARCH = internal.All
		for ,  := range .docs[0].API {
			.GOOS = internal.All
			.GOARCH = internal.All
		}
	}
	return , nil
}
mapKeyForFiles generates a value that corresponds to the given set of file names and can be used as a map key. It assumes the filenames do not contain spaces.
func ( map[string][]byte) string {
	var  []string
	for  := range  {
		 = append(, )
	}
	sort.Strings()
	return strings.Join(, " ")
}
httpPost allows package fetch tests to stub out playground URL fetches.
loadPackageForBuildContext loads a Go package made of .go files in files, which should match some build context. modulePath is stdlib.ModulePath for the Go standard library and the module path for all other modules. innerPath is the path of the Go package directory relative to the module root. The files argument must contain only .go files that have been verified to be of reasonable size and that match the build context. It returns the package name, list of imports, the package synopsis, and the serialized source (AST) for the package. It returns an error with NotFound in its chain if the directory doesn't contain a Go package or all .go files have been excluded by constraints. A *BadPackageError error is returned if the directory contains .go files but do not make up a valid package. If it returns an error with ErrTooLarge in its chain, the other return values are still valid.
func ( context.Context,  map[string][]byte,  string,  *source.Info,  *godoc.ModuleInfo) (
	 string,  []string,  string,  []byte,  []*internal.Symbol,  error) {
	 := .ModulePath
	defer derrors.Wrap(&, "loadPackageWithBuildContext(files, %q, %q, %+v)", , , )

	, , ,  := loadFilesWithBuildContext(, )
	if  != nil {
		return "", nil, "", nil, nil, 
	}
	 := godoc.NewPackage(, .ModulePackages)
	for ,  := range  {
Don't strip the seemingly unexported functions from the builtin package; they are actually Go builtins like make, new, etc.
		if  == stdlib.ModulePath &&  == "builtin" {
			 = false
		}
		.AddFile(, )
	}
Encode first, because Render messes with the AST.
	,  := .Encode()
	if  != nil {
		return "", nil, "", nil, nil, 
	}

	, , ,  = .DocInfo(, , , )
	if  != nil {
		return "", nil, "", nil, nil, 
	}
	return , , , , , 
}
loadFilesWithBuildContext loads all the given Go files at innerPath. It returns the package name as it occurs in the source, a map of the ASTs of all the Go files, and the token.FileSet used for parsing. If there are no non-test Go files, it returns a NotFound error.
Parse .go files and add them to the goFiles slice.
	var (
		            = token.NewFileSet()
		         = make(map[string]*ast.File)
		 int
		     string
		 string // Name of file where packageName came from.
	)
	for ,  := range  {
		,  := parser.ParseFile(, , , parser.ParseComments)
		if  != nil {
			if  == nil {
				return "", nil, nil, fmt.Errorf("internal error: the source couldn't be read: %v", )
			}
			return "", nil, nil, &BadPackageError{Err: }
Remember all files, including test files for their examples.
		[] = 
		if strings.HasSuffix(, "_test.go") {
			continue
Keep track of the number of non-test files to check that the package name is the same. and also because a directory with only test files doesn't count as a Go package.
		++
		if  == 1 {
			 = .Name.Name
			 = 
		} else if .Name.Name !=  {
			return "", nil, nil, &BadPackageError{Err: &build.MultiplePackageError{
				Dir:      ,
				Packages: []string{, .Name.Name},
				Files:    []string{, },
			}}
		}
	}
This directory doesn't contain a package, or at least not one that matches this build context.
		return "", nil, nil, derrors.NotFound
	}
	return , , , nil
}
matchingFiles returns a map from file names to their contents, read from zipGoFiles. It includes only those files that match the build context determined by goos and goarch.
func (,  string,  map[string][]byte) ( map[string][]byte,  error) {
	defer derrors.Wrap(&, "matchingFiles(%q, %q, zipGoFiles)", , )
bctx is used to make decisions about which of the .go files are included by build constraints.
If left nil, the default implementations of these read from disk, which we do not want. None of these functions should be used inside this function; it would be an internal error if they are. Set them to non-nil values to catch if that happens.
		SplitPathList: func(string) []string { panic("internal error: unexpected call to SplitPathList") },
		IsAbsPath:     func(string) bool { panic("internal error: unexpected call to IsAbsPath") },
		IsDir:         func(string) bool { panic("internal error: unexpected call to IsDir") },
		HasSubdir:     func(string, string) (string, bool) { panic("internal error: unexpected call to HasSubdir") },
		ReadDir:       func(string) ([]os.FileInfo, error) { panic("internal error: unexpected call to ReadDir") },
	}
Copy the input map so we don't modify it.
	 = map[string][]byte{}
	for ,  := range  {
		[] = 
	}
	for  := range  {
		,  := .MatchFile(".", ) // This will access the file we just added to files map above.
		if  != nil {
			return nil, &BadPackageError{Err: fmt.Errorf(`bctx.MatchFile(".", %q): %w`, , )}
		}
		if ! {
			delete(, )
		}
	}
	return , nil
}
readZipFile decompresses zip file f and returns its uncompressed contents. The caller can check f.UncompressedSize64 before calling readZipFile to get the expected uncompressed size of f. limit is the maximum number of bytes to read.
func ( *zip.File,  int64) ( []byte,  error) {
	defer derrors.Add(&, "readZipFile(%q)", .Name)

	,  := .Open()
	if  != nil {
		return nil, fmt.Errorf("f.Open(): %v", )
	}
	,  := ioutil.ReadAll(io.LimitReader(, ))
	if  != nil {
		.Close()
		return nil, fmt.Errorf("ioutil.ReadAll(r): %v", )
	}
	if  := .Close();  != nil {
		return nil, fmt.Errorf("closing: %v", )
	}
	return , nil
}
mib is the number of bytes in a mebibyte (Mi).
const mib = 1024 * 1024
The largest module zip size we can comfortably process. We probably will OOM if we process a module whose zip is larger.
var maxModuleZipSize int64 = math.MaxInt64

func () {
	 := config.GetEnvInt("GO_DISCOVERY_MAX_MODULE_ZIP_MI", -1)
	if  > 0 {
		maxModuleZipSize = int64() * mib
	}
}

var zipLoadShedder *loadShedder

func () {
	 := context.Background()
	 := config.GetEnvInt("GO_DISCOVERY_MAX_IN_FLIGHT_ZIP_MI", -1)
	if  > 0 {
		log.Infof(, "shedding load over %dMi", )
		zipLoadShedder = &loadShedder{maxSizeInFlight: uint64() * mib}
	}
}
ZipLoadShedStats returns a snapshot of the current LoadShedStats for zip files.