github.com/jfrog/jfrog-cli-core/v2@v2.52.0/artifactory/commands/golang/archive.go (about)

     1  package golang
     2  
     3  // The code in this file was copied from https://github.com/golang/go
     4  // which is under this license https://github.com/golang/go/blob/master/LICENSE
     5  // Copyright (c) 2009 The Go Authors. All rights reserved.
     6  
     7  // Redistribution and use in source and binary forms, with or without
     8  // modification, are permitted provided that the following conditions are
     9  // met:
    10  
    11  //    * Redistributions of source code must retain the above copyright
    12  // notice, this list of conditions and the following disclaimer.
    13  //    * Redistributions in binary form must reproduce the above
    14  // copyright notice, this list of conditions and the following disclaimer
    15  // in the documentation and/or other materials provided with the
    16  // distribution.
    17  //    * Neither the name of Google Inc. nor the names of its
    18  // contributors may be used to endorse or promote products derived from
    19  // this software without specific prior written permission.
    20  
    21  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    22  // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    23  // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    24  // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    25  // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    26  // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    27  // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    28  // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    29  // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    30  // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    31  // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    32  import (
    33  	"github.com/jfrog/jfrog-client-go/artifactory/services/fspatterns"
    34  	"github.com/jfrog/jfrog-client-go/utils"
    35  	"golang.org/x/mod/module"
    36  	gozip "golang.org/x/mod/zip"
    37  	"io"
    38  	"os"
    39  	"path/filepath"
    40  	"regexp"
    41  	"strings"
    42  )
    43  
    44  // Package zip provides functions for creating and extracting module zip files.
    45  //
    46  // Module zip files have several restrictions listed below. These are necessary
    47  // to ensure that module zip files can be extracted consistently on supported
    48  // platforms and file systems.
    49  //
    50  // • No two file paths may be equal under Unicode case-folding (see
    51  // strings.EqualFold).
    52  //
    53  // • A go.mod file may or may not appear in the top-level directory. If present,
    54  // it must be named "go.mod", not any other case. Files named "go.mod"
    55  // are not allowed in any other directory.
    56  //
    57  // • The total size in bytes of a module zip file may be at most MaxZipFile
    58  // bytes (500 MiB). The total uncompressed size of the files within the
    59  // zip may also be at most MaxZipFile bytes.
    60  //
    61  // • Each file's uncompressed size must match its declared 64-bit uncompressed
    62  // size in the zip file header.
    63  //
    64  // • If the zip contains files named "<module>@<version>/go.mod" or
    65  // "<module>@<version>/LICENSE", their sizes in bytes may be at most
    66  // MaxGoMod or MaxLICENSE, respectively (both are 16 MiB).
    67  //
    68  // • Empty directories are ignored. File permissions and timestamps are also
    69  // ignored.
    70  //
    71  // • Symbolic links and other irregular files are not allowed.
    72  //
    73  // Note that this package does not provide hashing functionality. See
    74  // golang.org/x/mod/sumdb/dirhash.
    75  
    76  // Archive project files according to the go project standard
    77  func archiveProject(writer io.Writer, dir, mod, version string, excludedPatterns []string) error {
    78  	m := module.Version{Version: version, Path: mod}
    79  	excludedPatterns, err := getAbsolutePaths(excludedPatterns)
    80  	if err != nil {
    81  		return err
    82  	}
    83  	excludePatternsStr := fspatterns.PrepareExcludePathPattern(excludedPatterns, utils.GetPatternType(utils.PatternTypes{RegExp: false, Ant: false}), true)
    84  	var files []gozip.File
    85  	err = filepath.Walk(dir, func(filePath string, info os.FileInfo, err error) error {
    86  		if err != nil {
    87  			return err
    88  		}
    89  		relPath, err := filepath.Rel(dir, filePath)
    90  		if err != nil {
    91  			return err
    92  		}
    93  		slashPath := filepath.ToSlash(relPath)
    94  		if info.IsDir() {
    95  			if filePath == dir {
    96  				// Don't skip the top-level directory.
    97  				return nil
    98  			}
    99  
   100  			// Skip VCS directories.
   101  			// fossil repos are regular files with arbitrary names, so we don't try
   102  			// to exclude them.
   103  			switch filepath.Base(filePath) {
   104  			case ".bzr", ".git", ".hg", ".svn":
   105  				return filepath.SkipDir
   106  			}
   107  
   108  			// Skip some subdirectories inside vendor, but maintain bug
   109  			// golang.org/issue/31562, described in isVendoredPackage.
   110  			// We would like Create and CreateFromDir to produce the same result
   111  			// for a set of files, whether expressed as a directory tree or zip.
   112  
   113  			if isVendoredPackage(slashPath) {
   114  				return filepath.SkipDir
   115  			}
   116  
   117  			// Skip submodules (directories containing go.mod files).
   118  			if goModInfo, err := os.Lstat(filepath.Join(filePath, "go.mod")); err == nil && !goModInfo.IsDir() {
   119  				return filepath.SkipDir
   120  			}
   121  		}
   122  		if info.Mode().IsRegular() {
   123  			if !isVendoredPackage(slashPath) {
   124  				excluded, err := isPathExcluded(filePath, excludePatternsStr)
   125  				if err != nil {
   126  					return err
   127  				}
   128  				if !excluded {
   129  					files = append(files, dirFile{
   130  						filePath:  filePath,
   131  						slashPath: slashPath,
   132  						info:      info,
   133  					})
   134  				}
   135  			}
   136  			return nil
   137  		}
   138  		// Not a regular file or a directory. Probably a symbolic link.
   139  		// Irregular files are ignored, so skip it.
   140  		return nil
   141  	})
   142  	if err != nil {
   143  		return err
   144  	}
   145  
   146  	return gozip.Create(writer, m, files)
   147  }
   148  
   149  func getAbsolutePaths(exclusionPatterns []string) ([]string, error) {
   150  	var absolutedPaths []string
   151  	for _, singleExclusion := range exclusionPatterns {
   152  		singleExclusion, err := filepath.Abs(singleExclusion)
   153  		if err != nil {
   154  			return nil, err
   155  		}
   156  		absolutedPaths = append(absolutedPaths, singleExclusion)
   157  	}
   158  	return absolutedPaths, nil
   159  }
   160  
   161  // This function receives a path and a regexp.
   162  // It returns trUe is the path received matches the regexp.
   163  // Before the match, thw path is turned into an absolute.
   164  func isPathExcluded(path string, excludePatternsRegexp string) (excluded bool, err error) {
   165  	var fullPath string
   166  	if len(excludePatternsRegexp) > 0 {
   167  		fullPath, err = filepath.Abs(path)
   168  		if err != nil {
   169  			return
   170  		}
   171  		excluded, err = regexp.MatchString(excludePatternsRegexp, fullPath)
   172  	}
   173  	return
   174  }
   175  
   176  func isVendoredPackage(name string) bool {
   177  	var i int
   178  	if strings.HasPrefix(name, "vendor/") {
   179  		i += len("vendor/")
   180  	} else if j := strings.Index(name, "/vendor/"); j >= 0 {
   181  		// This offset looks incorrect; this should probably be
   182  		//
   183  		// 	i = j + len("/vendor/")
   184  		//
   185  		// Unfortunately, we can't fix it without invalidating checksums.
   186  		// Fortunately, the error appears to be strictly conservative: we'll retain
   187  		// vendored packages that we should have pruned, but we won't prune
   188  		// non-vendored packages that we should have retained.
   189  		//
   190  		// Since this defect doesn't seem to break anything, it's not worth fixing
   191  		// for now.
   192  		i += len("/vendor/")
   193  	} else {
   194  		return false
   195  	}
   196  	return strings.Contains(name[i:], "/")
   197  }
   198  
   199  type dirFile struct {
   200  	filePath, slashPath string
   201  	info                os.FileInfo
   202  }
   203  
   204  func (f dirFile) Path() string                 { return f.slashPath }
   205  func (f dirFile) Lstat() (os.FileInfo, error)  { return f.info, nil }
   206  func (f dirFile) Open() (io.ReadCloser, error) { return os.Open(f.filePath) }
   207  
   208  // File provides an abstraction for a file in a directory, zip, or anything
   209  // else that looks like a file.
   210  type File interface {
   211  	// Path returns a clean slash-separated relative path from the module root
   212  	// directory to the file.
   213  	Path() string
   214  
   215  	// Lstat returns information about the file. If the file is a symbolic link,
   216  	// Lstat returns information about the link itself, not the file it points to.
   217  	Lstat() (os.FileInfo, error)
   218  
   219  	// Open provides access to the data within a regular file. Open may return
   220  	// an error if called on a directory or symbolic link.
   221  	Open() (io.ReadCloser, error)
   222  }