cmd/validators.go (153 lines of code) (raw):

// Copyright © 2017 Microsoft <wastore@microsoft.com> // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. package cmd import ( "fmt" "net/url" "reflect" "regexp" "strings" "github.com/JeffreyRichter/enum/enum" "github.com/Azure/azure-storage-azcopy/v10/common" ) func ValidateFromTo(src, dst string, userSpecifiedFromTo string) (common.FromTo, error) { if userSpecifiedFromTo == "" { inferredFromTo := inferFromTo(src, dst) // If user didn't explicitly specify FromTo, use what was inferred (if possible) if inferredFromTo == common.EFromTo.Unknown() { return common.EFromTo.Unknown(), fmt.Errorf("the inferred source/destination combination could not be identified, or is currently not supported") } return inferredFromTo, nil } // User explicitly specified FromTo, therefore, we should respect what they specified. var userFromTo common.FromTo err := userFromTo.Parse(userSpecifiedFromTo) if err != nil { return common.EFromTo.Unknown(), fmt.Errorf("invalid --from-to value specified: %q. "+fromToHelpText, userSpecifiedFromTo) } return userFromTo, nil } const fromToHelpFormat = "Specified to nudge AzCopy when resource detection may not work (e.g. piping/emulator/azure stack); Valid FromTo are pairs of Source-Destination words (e.g. BlobLocal, BlobBlob) that specify the source and destination resource types. All valid FromTos are: %s" var fromToHelp = func() string { validFromTos := "" isSafeToOutput := func(loc common.Location) bool { switch loc { case common.ELocation.Benchmark(), common.ELocation.None(), common.ELocation.Unknown(): return false default: return true } } enum.GetSymbols(reflect.TypeOf(common.EFromTo), func(enumSymbolName string, enumSymbolValue interface{}) (stop bool) { fromTo := enumSymbolValue.(common.FromTo) if isSafeToOutput(fromTo.From()) && isSafeToOutput(fromTo.To()) { validFromTos += fromTo.String() + ", " } return false }) return fmt.Sprintf(fromToHelpFormat, strings.TrimSuffix(validFromTos, ", ")) }() var fromToHelpText = fromToHelp const locationHelpFormat = "Specified to nudge AzCopy when resource detection may not work (e.g. emulator/azure stack); Valid Location are Source words (e.g. Blob, File) that specify the source resource type. All valid Locations are: %s" var locationHelp = func() string { validLocations := "" isSafeToOutput := func(loc common.Location) bool { switch loc { case common.ELocation.Benchmark(), common.ELocation.None(), common.ELocation.Unknown(): return false default: return true } } enum.GetSymbols(reflect.TypeOf(common.ELocation), func(enumSymbolName string, enumSymbolValue interface{}) (stop bool) { location := enumSymbolValue.(common.Location) if isSafeToOutput(location) { validLocations += location.String() + ", " } return false }) return fmt.Sprintf(locationHelpFormat, strings.TrimSuffix(validLocations, ", ")) }() var locationHelpText = locationHelp func inferFromTo(src, dst string) common.FromTo { // Try to infer the 1st argument srcLocation := InferArgumentLocation(src) if srcLocation == srcLocation.Unknown() { glcm.Info("Cannot infer source location of " + common.URLStringExtension(src).RedactSecretQueryParamForLogging() + ". Please specify the --from-to switch. " + fromToHelpText) return common.EFromTo.Unknown() } dstLocation := InferArgumentLocation(dst) if dstLocation == dstLocation.Unknown() { glcm.Info("Cannot infer destination location of " + common.URLStringExtension(dst).RedactSecretQueryParamForLogging() + ". Please specify the --from-to switch. " + fromToHelpText) return common.EFromTo.Unknown() } out := common.EFromTo.Unknown() // Check that the intended FromTo is in the list of valid FromTos; if it's not, return Unknown as usual and warn the user. intent := (common.FromTo(srcLocation) << 8) | common.FromTo(dstLocation) enum.GetSymbols(reflect.TypeOf(common.EFromTo), func(enumSymbolName string, enumSymbolValue interface{}) (stop bool) { // find if our fromto is a valid option fromTo := enumSymbolValue.(common.FromTo) // none/unknown will never appear as valid outputs of the above functions // If it's our intended fromto, we're good. if fromTo == intent { out = intent return true } return false }) if out != common.EFromTo.Unknown() { return out } glcm.Info("The parameters you supplied were " + "Source: '" + common.URLStringExtension(src).RedactSecretQueryParamForLogging() + "' of type " + srcLocation.String() + ", and Destination: '" + common.URLStringExtension(dst).RedactSecretQueryParamForLogging() + "' of type " + dstLocation.String()) glcm.Info("Based on the parameters supplied, a valid source-destination combination could not " + "automatically be found. Please check the parameters you supplied. If they are correct, please " + "specify an exact source and destination type using the --from-to switch. " + fromToHelpText) return out } var IPv4Regex = regexp.MustCompile(`\d+\.\d+\.\d+\.\d+`) // simple regex func ValidateArgumentLocation(src string, userSpecifiedLocation string) (common.Location, error) { if userSpecifiedLocation == "" { inferredLocation := InferArgumentLocation(src) // If user didn't explicitly specify Location, use what was inferred (if possible) if inferredLocation == common.ELocation.Unknown() { return common.ELocation.Unknown(), fmt.Errorf("the inferred location could not be identified, or is currently not supported") } return inferredLocation, nil } // User explicitly specified Location, therefore, we should respect what they specified. var userLocation common.Location err := userLocation.Parse(userSpecifiedLocation) if err != nil { return common.ELocation.Unknown(), fmt.Errorf("invalid --location value specified: %q. "+locationHelpText, userSpecifiedLocation) } return userLocation, nil } func InferArgumentLocation(arg string) common.Location { if arg == pipeLocation { return common.ELocation.Pipe() } if startsWith(arg, "http") { // Let's try to parse the argument as a URL u, err := url.Parse(arg) // NOTE: sometimes, a local path can also be parsed as a url. To avoid thinking it's a URL, check Scheme, Host, and Path if err == nil && u.Scheme != "" && u.Host != "" { // Is the argument a URL to blob storage? switch host := strings.ToLower(u.Host); true { // Azure Stack does not have the core.windows.net case strings.Contains(host, ".blob"): return common.ELocation.Blob() case strings.Contains(host, ".file"): return common.ELocation.File() case strings.Contains(host, ".dfs"): return common.ELocation.BlobFS() case strings.Contains(host, benchmarkSourceHost): return common.ELocation.Benchmark() // enable targeting an emulator/stack case IPv4Regex.MatchString(host): return common.ELocation.Unknown() } if common.IsS3URL(*u) { return common.ELocation.S3() } if common.IsGCPURL(*u) { return common.ELocation.GCP() } // If none of the above conditions match, return Unknown return common.ELocation.Unknown() } } return common.ELocation.Local() }