github.com/vmware/go-vcloud-director/v2@v2.24.0/util/tar.go (about) 1 /* 2 * Copyright 2018 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. 3 */ 4 5 package util 6 7 import ( 8 "archive/tar" 9 "errors" 10 "io" 11 "net/http" 12 "os" 13 "path/filepath" 14 "strings" 15 ) 16 17 const TmpDirPrefix = "govcd" 18 19 // Unpack extracts files to system tmp dir with name govcd+random number. Created folder with files isn't deleted. 20 // Returns extracted files paths in array and path where folder with files created. 21 func Unpack(tarFile string) ([]string, string, error) { 22 23 var filePaths []string 24 var dst string 25 26 reader, err := os.Open(filepath.Clean(tarFile)) 27 if err != nil { 28 return filePaths, dst, err 29 } 30 defer func() { 31 if err := reader.Close(); err != nil { 32 Logger.Printf("Error closing file: %s\n", err) 33 } 34 }() 35 36 tarReader := tar.NewReader(reader) 37 38 dst, err = os.MkdirTemp("", TmpDirPrefix) 39 if err != nil { 40 return filePaths, dst, err 41 } 42 43 var expectedFileSize int64 = -1 44 45 for { 46 header, err := tarReader.Next() 47 48 switch { 49 50 // if no more files are found return 51 case err == io.EOF: 52 return filePaths, dst, nil 53 54 // return any other error 55 case err != nil: 56 return filePaths, dst, err 57 58 // if the header is nil, just skip it (not sure how this happens) 59 case header == nil: 60 continue 61 62 case header != nil: 63 expectedFileSize = header.Size 64 } 65 66 // the target location where the dir/newFile should be created 67 target := filepath.Join(dst, sanitizedName(header.Name)) 68 Logger.Printf("[TRACE] extracting newFile: %s \n", target) 69 70 // check the newFile type 71 switch header.Typeflag { 72 73 // if its a dir and it doesn't exist create it 74 case tar.TypeDir: 75 if _, err := os.Stat(target); err != nil { 76 if err := os.MkdirAll(target, 0750); err != nil { 77 return filePaths, dst, err 78 } 79 } 80 81 case tar.TypeSymlink: 82 if header.Linkname != "" { 83 err := os.Symlink(header.Linkname, target) 84 if err != nil { 85 return filePaths, dst, err 86 } 87 } else { 88 return filePaths, dst, errors.New("file %s is a symlink, but no link information was provided") 89 } 90 91 // if it's a newFile create it 92 case tar.TypeReg: 93 newFile, err := os.OpenFile(filepath.Clean(target), os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode)) 94 if err != nil { 95 return filePaths, dst, err 96 } 97 98 // copy over contents 99 for { 100 _, err := io.CopyN(newFile, tarReader, 1024) 101 if err != nil { 102 if errors.Is(err, io.EOF) { 103 break 104 } 105 return filePaths, dst, err 106 } 107 } 108 109 filePaths = append(filePaths, newFile.Name()) 110 111 if err := isExtractedFileValid(newFile, expectedFileSize); err != nil { 112 errClose := newFile.Close() 113 if errClose != nil { 114 Logger.Printf("[DEBUG - Unpack] error closing newFile: %s", errClose) 115 } 116 return filePaths, dst, err 117 } 118 119 // manually close here after each newFile operation; deferring would cause each newFile close 120 // to wait until all operations have completed. 121 errClose := newFile.Close() 122 if errClose != nil { 123 Logger.Printf("[DEBUG - Unpack] error closing newFile: %s", errClose) 124 } 125 } 126 } 127 } 128 129 func isExtractedFileValid(file *os.File, expectedFileSize int64) error { 130 if fInfo, err := file.Stat(); err == nil { 131 Logger.Printf("[TRACE] isExtractedFileValid: created file size %#v, size from header %#v.\n", fInfo.Size(), expectedFileSize) 132 if fInfo.Size() != expectedFileSize && expectedFileSize != -1 { 133 return errors.New("extracted file didn't match defined file size") 134 } 135 } 136 return nil 137 } 138 139 func sanitizedName(filename string) string { 140 if len(filename) > 1 && filename[1] == ':' { 141 filename = filename[2:] 142 } 143 filename = strings.TrimLeft(filename, "\\/.") 144 filename = strings.TrimLeft(filename, "./") 145 filename = strings.Replace(filename, "../../", "../", -1) 146 return strings.Replace(filename, "..\\", "", -1) 147 } 148 149 // GetFileContentType returns the real file type 150 func GetFileContentType(file string) (string, error) { // Open File 151 f, err := os.Open(filepath.Clean(file)) 152 if err != nil { 153 return "", err 154 } 155 defer func() { 156 if err := f.Close(); err != nil { 157 Logger.Printf("Error closing file: %s\n", err) 158 } 159 }() 160 // Only the first 512 bytes are used to sniff the content type. 161 buffer := make([]byte, 512) 162 163 _, err = f.Read(buffer) 164 if err != nil { 165 return "", err 166 } 167 168 // Use the net/http package's handy DectectContentType function. Always returns a valid 169 // content-type by returning "application/octet-stream" if no others seemed to match. 170 contentType := http.DetectContentType(buffer) 171 172 return contentType, nil 173 }