github.com/weiwenhao/getter@v1.30.1/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, umask os.FileMode) 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, mode(0755, umask)); 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, mode(0755, umask)); 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 err = copyReader(path, tarR, hdr.FileInfo().Mode(), umask) 86 if err != nil { 87 return err 88 } 89 90 // Set the access and modification time if valid, otherwise default to current time 91 aTime := now 92 mTime := now 93 if hdr.AccessTime.Unix() > 0 { 94 aTime = hdr.AccessTime 95 } 96 if hdr.ModTime.Unix() > 0 { 97 mTime = hdr.ModTime 98 } 99 if err := os.Chtimes(path, aTime, mTime); err != nil { 100 return err 101 } 102 } 103 104 // Perform a final pass over extracted directories to update metadata 105 for _, dirHdr := range dirHdrs { 106 path := filepath.Join(dst, dirHdr.Name) 107 // Chmod the directory since they might be created before we know the mode flags 108 if err := os.Chmod(path, mode(dirHdr.FileInfo().Mode(), umask)); err != nil { 109 return err 110 } 111 // Set the mtime/atime attributes since they would have been changed during extraction 112 aTime := now 113 mTime := now 114 if dirHdr.AccessTime.Unix() > 0 { 115 aTime = dirHdr.AccessTime 116 } 117 if dirHdr.ModTime.Unix() > 0 { 118 mTime = dirHdr.ModTime 119 } 120 if err := os.Chtimes(path, aTime, mTime); err != nil { 121 return err 122 } 123 } 124 125 return nil 126 } 127 128 // TarDecompressor is an implementation of Decompressor that can 129 // unpack tar files. 130 type TarDecompressor struct{} 131 132 func (d *TarDecompressor) Decompress(dst, src string, dir bool, umask os.FileMode) error { 133 // If we're going into a directory we should make that first 134 mkdir := dst 135 if !dir { 136 mkdir = filepath.Dir(dst) 137 } 138 if err := os.MkdirAll(mkdir, mode(0755, umask)); err != nil { 139 return err 140 } 141 142 // File first 143 f, err := os.Open(src) 144 if err != nil { 145 return err 146 } 147 defer f.Close() 148 149 return untar(f, dst, src, dir, umask) 150 }