github.com/remind101/go-getter@v0.0.0-20180809191950-4bda8fa99001/decompress_tar.go (about) 1 package getter 2 3 import ( 4 "archive/tar" 5 "fmt" 6 "io" 7 "os" 8 "path/filepath" 9 "time" 10 ) 11 12 // untar is a shared helper for untarring an archive. The reader should provide 13 // an uncompressed view of the tar archive. 14 func untar(input io.Reader, dst, src string, dir bool) error { 15 tarR := tar.NewReader(input) 16 done := false 17 dirHdrs := []*tar.Header{} 18 now := time.Now() 19 for { 20 hdr, err := tarR.Next() 21 if err == io.EOF { 22 if !done { 23 // Empty archive 24 return fmt.Errorf("empty archive: %s", src) 25 } 26 27 break 28 } 29 if err != nil { 30 return err 31 } 32 33 if hdr.Typeflag == tar.TypeXGlobalHeader || hdr.Typeflag == tar.TypeXHeader { 34 // don't unpack extended headers as files 35 continue 36 } 37 38 path := dst 39 if dir { 40 // Disallow parent traversal 41 if containsDotDot(hdr.Name) { 42 return fmt.Errorf("entry contains '..': %s", hdr.Name) 43 } 44 45 path = filepath.Join(path, hdr.Name) 46 } 47 48 if hdr.FileInfo().IsDir() { 49 if !dir { 50 return fmt.Errorf("expected a single file: %s", src) 51 } 52 53 // A directory, just make the directory and continue unarchiving... 54 if err := os.MkdirAll(path, 0755); err != nil { 55 return err 56 } 57 58 // Record the directory information so that we may set its attributes 59 // after all files have been extracted 60 dirHdrs = append(dirHdrs, hdr) 61 62 continue 63 } else { 64 // There is no ordering guarantee that a file in a directory is 65 // listed before the directory 66 dstPath := filepath.Dir(path) 67 68 // Check that the directory exists, otherwise create it 69 if _, err := os.Stat(dstPath); os.IsNotExist(err) { 70 if err := os.MkdirAll(dstPath, 0755); err != nil { 71 return err 72 } 73 } 74 } 75 76 // We have a file. If we already decoded, then it is an error 77 if !dir && done { 78 return fmt.Errorf("expected a single file, got multiple: %s", src) 79 } 80 81 // Mark that we're done so future in single file mode errors 82 done = true 83 84 // Open the file for writing 85 dstF, err := os.Create(path) 86 if err != nil { 87 return err 88 } 89 _, err = io.Copy(dstF, tarR) 90 dstF.Close() 91 if err != nil { 92 return err 93 } 94 95 // Chmod the file 96 if err := os.Chmod(path, hdr.FileInfo().Mode()); err != nil { 97 return err 98 } 99 100 // Set the access and modification time if valid, otherwise default to current time 101 aTime := now 102 mTime := now 103 if hdr.AccessTime.Unix() > 0 { 104 aTime = hdr.AccessTime 105 } 106 if hdr.ModTime.Unix() > 0 { 107 mTime = hdr.ModTime 108 } 109 if err := os.Chtimes(path, aTime, mTime); err != nil { 110 return err 111 } 112 } 113 114 // Perform a final pass over extracted directories to update metadata 115 for _, dirHdr := range dirHdrs { 116 path := filepath.Join(dst, dirHdr.Name) 117 // Chmod the directory since they might be created before we know the mode flags 118 if err := os.Chmod(path, dirHdr.FileInfo().Mode()); err != nil { 119 return err 120 } 121 // Set the mtime/atime attributes since they would have been changed during extraction 122 aTime := now 123 mTime := now 124 if dirHdr.AccessTime.Unix() > 0 { 125 aTime = dirHdr.AccessTime 126 } 127 if dirHdr.ModTime.Unix() > 0 { 128 mTime = dirHdr.ModTime 129 } 130 if err := os.Chtimes(path, aTime, mTime); err != nil { 131 return err 132 } 133 } 134 135 return nil 136 } 137 138 // tarDecompressor is an implementation of Decompressor that can 139 // unpack tar files. 140 type tarDecompressor struct{} 141 142 func (d *tarDecompressor) Decompress(dst, src string, dir bool) error { 143 // If we're going into a directory we should make that first 144 mkdir := dst 145 if !dir { 146 mkdir = filepath.Dir(dst) 147 } 148 if err := os.MkdirAll(mkdir, 0755); err != nil { 149 return err 150 } 151 152 // File first 153 f, err := os.Open(src) 154 if err != nil { 155 return err 156 } 157 defer f.Close() 158 159 return untar(f, dst, src, dir) 160 }