github.com/weiwenhao/getter@v1.30.1/client.go (about) 1 package getter 2 3 import ( 4 "context" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "strconv" 10 "strings" 11 12 urlhelper "github.com/weiwenhao/getter/helper/url" 13 safetemp "github.com/hashicorp/go-safetemp" 14 ) 15 16 // Client is a client for downloading things. 17 // 18 // Top-level functions such as Get are shortcuts for interacting with a client. 19 // Using a client directly allows more fine-grained control over how downloading 20 // is done, as well as customizing the protocols supported. 21 type Client struct { 22 // Ctx for cancellation 23 Ctx context.Context 24 25 // Src is the source URL to get. 26 // 27 // Dst is the path to save the downloaded thing as. If Dir is set to 28 // true, then this should be a directory. If the directory doesn't exist, 29 // it will be created for you. 30 // 31 // Pwd is the working directory for detection. If this isn't set, some 32 // detection may fail. Client will not default pwd to the current 33 // working directory for security reasons. 34 Src string 35 Dst string 36 Pwd string 37 38 // Mode is the method of download the client will use. See ClientMode 39 // for documentation. 40 Mode ClientMode 41 42 // Umask is used to mask file permissions when storing local files or decompressing 43 // an archive 44 Umask os.FileMode 45 46 // Detectors is the list of detectors that are tried on the source. 47 // If this is nil, then the default Detectors will be used. 48 Detectors []Detector 49 50 // Decompressors is the map of decompressors supported by this client. 51 // If this is nil, then the default value is the Decompressors global. 52 Decompressors map[string]Decompressor 53 54 // Getters is the map of protocols supported by this client. If this 55 // is nil, then the default Getters variable will be used. 56 Getters map[string]Getter 57 58 // Dir, if true, tells the Client it is downloading a directory (versus 59 // a single file). This distinction is necessary since filenames and 60 // directory names follow the same format so disambiguating is impossible 61 // without knowing ahead of time. 62 // 63 // WARNING: deprecated. If Mode is set, that will take precedence. 64 Dir bool 65 66 // ProgressListener allows to track file downloads. 67 // By default a no op progress listener is used. 68 ProgressListener ProgressTracker 69 70 // Insecure controls whether a client verifies the server's 71 // certificate chain and host name. If Insecure is true, crypto/tls 72 // accepts any certificate presented by the server and any host name in that 73 // certificate. In this mode, TLS is susceptible to machine-in-the-middle 74 // attacks unless custom verification is used. This should be used only for 75 // testing or in combination with VerifyConnection or VerifyPeerCertificate. 76 // This is identical to tls.Config.InsecureSkipVerify. 77 Insecure bool 78 79 Options []ClientOption 80 } 81 82 // umask returns the effective umask for the Client, defaulting to the process umask 83 func (c *Client) umask() os.FileMode { 84 if c == nil { 85 return 0 86 } 87 return c.Umask 88 } 89 90 // mode returns file mode umasked by the Client umask 91 func (c *Client) mode(mode os.FileMode) os.FileMode { 92 m := mode & ^c.umask() 93 return m 94 } 95 96 // Get downloads the configured source to the destination. 97 func (c *Client) Get() error { 98 if err := c.Configure(c.Options...); err != nil { 99 return err 100 } 101 102 // Store this locally since there are cases we swap this 103 mode := c.Mode 104 if mode == ClientModeInvalid { 105 if c.Dir { 106 mode = ClientModeDir 107 } else { 108 mode = ClientModeFile 109 } 110 } 111 112 src, err := Detect(c.Src, c.Pwd, c.Detectors) 113 if err != nil { 114 return err 115 } 116 117 // Determine if we have a forced protocol, i.e. "git::http://..." 118 force, src := getForcedGetter(src) 119 120 // If there is a subdir component, then we download the root separately 121 // and then copy over the proper subdir. 122 var realDst string 123 dst := c.Dst 124 src, subDir := SourceDirSubdir(src) 125 if subDir != "" { 126 td, tdcloser, err := safetemp.Dir("", "getter") 127 if err != nil { 128 return err 129 } 130 defer tdcloser.Close() 131 132 realDst = dst 133 dst = td 134 } 135 136 u, err := urlhelper.Parse(src) 137 if err != nil { 138 return err 139 } 140 if force == "" { 141 force = u.Scheme 142 } 143 144 g, ok := c.Getters[force] 145 if !ok { 146 return fmt.Errorf( 147 "download not supported for scheme '%s'", force) 148 } 149 150 // We have magic query parameters that we use to signal different features 151 q := u.Query() 152 153 // Determine if we have an archive type 154 archiveV := q.Get("archive") 155 if archiveV != "" { 156 // Delete the paramter since it is a magic parameter we don't 157 // want to pass on to the Getter 158 q.Del("archive") 159 u.RawQuery = q.Encode() 160 161 // If we can parse the value as a bool and it is false, then 162 // set the archive to "-" which should never map to a decompressor 163 if b, err := strconv.ParseBool(archiveV); err == nil && !b { 164 archiveV = "-" 165 } 166 } 167 if archiveV == "" { 168 // We don't appear to... but is it part of the filename? 169 matchingLen := 0 170 for k := range c.Decompressors { 171 if strings.HasSuffix(u.Path, "."+k) && len(k) > matchingLen { 172 archiveV = k 173 matchingLen = len(k) 174 } 175 } 176 } 177 178 // If we have a decompressor, then we need to change the destination 179 // to download to a temporary path. We unarchive this into the final, 180 // real path. 181 var decompressDst string 182 var decompressDir bool 183 decompressor := c.Decompressors[archiveV] 184 if decompressor != nil { 185 // Create a temporary directory to store our archive. We delete 186 // this at the end of everything. 187 td, err := ioutil.TempDir("", "getter") 188 if err != nil { 189 return fmt.Errorf( 190 "Error creating temporary directory for archive: %s", err) 191 } 192 defer os.RemoveAll(td) 193 194 // Swap the download directory to be our temporary path and 195 // store the old values. 196 decompressDst = dst 197 decompressDir = mode != ClientModeFile 198 dst = filepath.Join(td, "archive") 199 mode = ClientModeFile 200 } 201 202 // Determine checksum if we have one 203 checksum, err := c.extractChecksum(u) 204 if err != nil { 205 return fmt.Errorf("invalid checksum: %s", err) 206 } 207 208 // Delete the query parameter if we have it. 209 q.Del("checksum") 210 u.RawQuery = q.Encode() 211 212 if mode == ClientModeAny { 213 // Ask the getter which client mode to use 214 mode, err = g.ClientMode(u) 215 if err != nil { 216 return err 217 } 218 219 // Destination is the base name of the URL path in "any" mode when 220 // a file source is detected. 221 if mode == ClientModeFile { 222 filename := filepath.Base(u.Path) 223 224 // Determine if we have a custom file name 225 if v := q.Get("filename"); v != "" { 226 // Delete the query parameter if we have it. 227 q.Del("filename") 228 u.RawQuery = q.Encode() 229 230 filename = v 231 } 232 233 dst = filepath.Join(dst, filename) 234 } 235 } 236 237 // If we're not downloading a directory, then just download the file 238 // and return. 239 if mode == ClientModeFile { 240 getFile := true 241 if checksum != nil { 242 if err := checksum.checksum(dst); err == nil { 243 // don't get the file if the checksum of dst is correct 244 getFile = false 245 } 246 } 247 if getFile { 248 err := g.GetFile(dst, u) 249 if err != nil { 250 return err 251 } 252 253 if checksum != nil { 254 if err := checksum.checksum(dst); err != nil { 255 return err 256 } 257 } 258 } 259 260 if decompressor != nil { 261 // We have a decompressor, so decompress the current destination 262 // into the final destination with the proper mode. 263 err := decompressor.Decompress(decompressDst, dst, decompressDir, c.umask()) 264 if err != nil { 265 return err 266 } 267 268 // Swap the information back 269 dst = decompressDst 270 if decompressDir { 271 mode = ClientModeAny 272 } else { 273 mode = ClientModeFile 274 } 275 } 276 277 // We check the dir value again because it can be switched back 278 // if we were unarchiving. If we're still only Get-ing a file, then 279 // we're done. 280 if mode == ClientModeFile { 281 return nil 282 } 283 } 284 285 // If we're at this point we're either downloading a directory or we've 286 // downloaded and unarchived a directory and we're just checking subdir. 287 // In the case we have a decompressor we don't Get because it was Get 288 // above. 289 if decompressor == nil { 290 // If we're getting a directory, then this is an error. You cannot 291 // checksum a directory. TODO: test 292 if checksum != nil { 293 return fmt.Errorf( 294 "checksum cannot be specified for directory download") 295 } 296 297 // We're downloading a directory, which might require a bit more work 298 // if we're specifying a subdir. 299 err := g.Get(dst, u) 300 if err != nil { 301 err = fmt.Errorf("error downloading '%s': %s", RedactURL(u), err) 302 return err 303 } 304 } 305 306 // If we have a subdir, copy that over 307 if subDir != "" { 308 if err := os.RemoveAll(realDst); err != nil { 309 return err 310 } 311 if err := os.MkdirAll(realDst, c.mode(0755)); err != nil { 312 return err 313 } 314 315 // Process any globs 316 subDir, err := SubdirGlob(dst, subDir) 317 if err != nil { 318 return err 319 } 320 321 return copyDir(c.Ctx, realDst, subDir, false, c.umask()) 322 } 323 324 return nil 325 }