github.com/core-coin/go-core/v2@v2.1.9/internal/build/archive.go (about) 1 // Copyright 2016 by the Authors 2 // This file is part of the go-core library. 3 // 4 // The go-core library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-core library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-core library. If not, see <http://www.gnu.org/licenses/>. 16 17 package build 18 19 import ( 20 "archive/tar" 21 "archive/zip" 22 "compress/gzip" 23 "fmt" 24 "io" 25 "os" 26 "path/filepath" 27 "strings" 28 ) 29 30 type Archive interface { 31 // Directory adds a new directory entry to the archive and sets the 32 // directory for subsequent calls to Header. 33 Directory(name string) error 34 35 // Header adds a new file to the archive. The file is added to the directory 36 // set by Directory. The content of the file must be written to the returned 37 // writer. 38 Header(os.FileInfo) (io.Writer, error) 39 40 // Close flushes the archive and closes the underlying file. 41 Close() error 42 } 43 44 func NewArchive(file *os.File) (Archive, string) { 45 switch { 46 case strings.HasSuffix(file.Name(), ".zip"): 47 return NewZipArchive(file), strings.TrimSuffix(file.Name(), ".zip") 48 case strings.HasSuffix(file.Name(), ".tar.gz"): 49 return NewTarballArchive(file), strings.TrimSuffix(file.Name(), ".tar.gz") 50 default: 51 return nil, "" 52 } 53 } 54 55 // AddFile appends an existing file to an archive. 56 func AddFile(a Archive, file string) error { 57 fd, err := os.Open(file) 58 if err != nil { 59 return err 60 } 61 defer fd.Close() 62 fi, err := fd.Stat() 63 if err != nil { 64 return err 65 } 66 w, err := a.Header(fi) 67 if err != nil { 68 return err 69 } 70 if _, err := io.Copy(w, fd); err != nil { 71 return err 72 } 73 return nil 74 } 75 76 // WriteArchive creates an archive containing the given files. 77 func WriteArchive(name string, files []string) (err error) { 78 archfd, err := os.Create(name) 79 if err != nil { 80 return err 81 } 82 83 defer func() { 84 archfd.Close() 85 // Remove the half-written archive on failure. 86 if err != nil { 87 os.Remove(name) 88 } 89 }() 90 archive, basename := NewArchive(archfd) 91 if archive == nil { 92 return fmt.Errorf("unknown archive extension") 93 } 94 fmt.Println(name) 95 if err := archive.Directory(basename); err != nil { 96 return err 97 } 98 for _, file := range files { 99 fmt.Println(" +", filepath.Base(file)) 100 if err := AddFile(archive, file); err != nil { 101 return err 102 } 103 } 104 return archive.Close() 105 } 106 107 type ZipArchive struct { 108 dir string 109 zipw *zip.Writer 110 file io.Closer 111 } 112 113 func NewZipArchive(w io.WriteCloser) Archive { 114 return &ZipArchive{"", zip.NewWriter(w), w} 115 } 116 117 func (a *ZipArchive) Directory(name string) error { 118 a.dir = name + "/" 119 return nil 120 } 121 122 func (a *ZipArchive) Header(fi os.FileInfo) (io.Writer, error) { 123 head, err := zip.FileInfoHeader(fi) 124 if err != nil { 125 return nil, fmt.Errorf("can't make zip header: %v", err) 126 } 127 head.Name = a.dir + head.Name 128 head.Method = zip.Deflate 129 w, err := a.zipw.CreateHeader(head) 130 if err != nil { 131 return nil, fmt.Errorf("can't add zip header: %v", err) 132 } 133 return w, nil 134 } 135 136 func (a *ZipArchive) Close() error { 137 if err := a.zipw.Close(); err != nil { 138 return err 139 } 140 return a.file.Close() 141 } 142 143 type TarballArchive struct { 144 dir string 145 tarw *tar.Writer 146 gzw *gzip.Writer 147 file io.Closer 148 } 149 150 func NewTarballArchive(w io.WriteCloser) Archive { 151 gzw := gzip.NewWriter(w) 152 tarw := tar.NewWriter(gzw) 153 return &TarballArchive{"", tarw, gzw, w} 154 } 155 156 func (a *TarballArchive) Directory(name string) error { 157 a.dir = name + "/" 158 return a.tarw.WriteHeader(&tar.Header{ 159 Name: a.dir, 160 Mode: 0755, 161 Typeflag: tar.TypeDir, 162 }) 163 } 164 165 func (a *TarballArchive) Header(fi os.FileInfo) (io.Writer, error) { 166 head, err := tar.FileInfoHeader(fi, "") 167 if err != nil { 168 return nil, fmt.Errorf("can't make tar header: %v", err) 169 } 170 head.Name = a.dir + head.Name 171 if err := a.tarw.WriteHeader(head); err != nil { 172 return nil, fmt.Errorf("can't add tar header: %v", err) 173 } 174 return a.tarw, nil 175 } 176 177 func (a *TarballArchive) Close() error { 178 if err := a.tarw.Close(); err != nil { 179 return err 180 } 181 if err := a.gzw.Close(); err != nil { 182 return err 183 } 184 return a.file.Close() 185 } 186 187 // ExtractArchive unpacks a .zip or .tar.gz archive to the destination directory. 188 func ExtractArchive(archive string, dest string) error { 189 ar, err := os.Open(archive) 190 if err != nil { 191 return err 192 } 193 defer ar.Close() 194 195 switch { 196 case strings.HasSuffix(archive, ".tar.gz"): 197 return extractTarball(ar, dest) 198 case strings.HasSuffix(archive, ".zip"): 199 return extractZip(ar, dest) 200 default: 201 return fmt.Errorf("unhandled archive type %s", archive) 202 } 203 } 204 205 // extractTarball unpacks a .tar.gz file. 206 func extractTarball(ar io.Reader, dest string) error { 207 gzr, err := gzip.NewReader(ar) 208 if err != nil { 209 return err 210 } 211 defer gzr.Close() 212 213 tr := tar.NewReader(gzr) 214 for { 215 // Move to the next file header. 216 header, err := tr.Next() 217 if err != nil { 218 if err == io.EOF { 219 return nil 220 } 221 return err 222 } 223 // We only care about regular files, directory modes 224 // and special file types are not supported. 225 if header.Typeflag == tar.TypeReg { 226 armode := header.FileInfo().Mode() 227 err := extractFile(header.Name, armode, tr, dest) 228 if err != nil { 229 return fmt.Errorf("extract %s: %v", header.Name, err) 230 } 231 } 232 } 233 } 234 235 // extractZip unpacks the given .zip file. 236 func extractZip(ar *os.File, dest string) error { 237 info, err := ar.Stat() 238 if err != nil { 239 return err 240 } 241 zr, err := zip.NewReader(ar, info.Size()) 242 if err != nil { 243 return err 244 } 245 246 for _, zf := range zr.File { 247 if !zf.Mode().IsRegular() { 248 continue 249 } 250 251 data, err := zf.Open() 252 if err != nil { 253 return err 254 } 255 err = extractFile(zf.Name, zf.Mode(), data, dest) 256 data.Close() 257 if err != nil { 258 return fmt.Errorf("extract %s: %v", zf.Name, err) 259 } 260 } 261 return nil 262 } 263 264 // extractFile extracts a single file from an archive. 265 func extractFile(arpath string, armode os.FileMode, data io.Reader, dest string) error { 266 // Check that path is inside destination directory. 267 target := filepath.Join(dest, filepath.FromSlash(arpath)) 268 if !strings.HasPrefix(target, filepath.Clean(dest)+string(os.PathSeparator)) { 269 return fmt.Errorf("path %q escapes archive destination", target) 270 } 271 272 // Ensure the destination directory exists. 273 if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil { 274 return err 275 } 276 277 // Copy file data. 278 file, err := os.OpenFile(target, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, armode) 279 if err != nil { 280 return err 281 } 282 if _, err := io.Copy(file, data); err != nil { 283 file.Close() 284 os.Remove(target) 285 return err 286 } 287 return file.Close() 288 }