github.com/jfrog/jfrog-cli-core/v2@v2.51.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 }