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  }