Split up DownloadableURL() into it's individual components: SupportedURL(), DownloadableURL(), and ValidatedURL(). Updated all instances of DownloadableURL() to point to ValidatedURL(). Reverted the tests that are based on un-supported protocols.

This commit is contained in:
Ali Rizvi-Santiago 2018-01-09 20:08:06 -06:00
parent 3cf448f6ec
commit c17f827e1d
7 changed files with 102 additions and 46 deletions

View File

@ -121,7 +121,7 @@ func (s *StepDownloadGuestAdditions) Run(state multistep.StateBag) multistep.Ste
} }
// Convert the file/url to an actual URL for step_download to process. // Convert the file/url to an actual URL for step_download to process.
url, err = common.DownloadableURL(url) url, err = common.ValidatedURL(url)
if err != nil { if err != nil {
err := fmt.Errorf("Error preparing guest additions url: %s", err) err := fmt.Errorf("Error preparing guest additions url: %s", err)
state.Put("error", err) state.Put("error", err)

View File

@ -97,7 +97,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
if c.SourcePath == "" { if c.SourcePath == "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("source_path is required")) errs = packer.MultiErrorAppend(errs, fmt.Errorf("source_path is required"))
} else { } else {
c.SourcePath, err = common.DownloadableURL(c.SourcePath) c.SourcePath, err = common.ValidatedURL(c.SourcePath)
if err != nil { if err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("source_path is invalid: %s", err)) errs = packer.MultiErrorAppend(errs, fmt.Errorf("source_path is invalid: %s", err))
} }

View File

@ -76,6 +76,17 @@ func TestNewConfig_sourcePath(t *testing.T) {
t.Fatalf("Nonexistant file should throw a validation error!") t.Fatalf("Nonexistant file should throw a validation error!")
} }
// Bad
c = testConfig(t)
c["source_path"] = "ftp://i/dont/exist"
_, warns, err = NewConfig(c)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatalf("should error")
}
// Good // Good
tf := getTempFile(t) tf := getTempFile(t)
defer os.Remove(tf.Name()) defer os.Remove(tf.Name())

View File

@ -5,6 +5,7 @@ import (
"net/url" "net/url"
"os" "os"
"path/filepath" "path/filepath"
"regexp"
"strings" "strings"
"time" "time"
) )
@ -42,58 +43,98 @@ func ChooseString(vals ...string) string {
return "" return ""
} }
// SupportedURL verifies that the url passed is actually supported or not
// This will also validate that the protocol is one that's actually implemented.
func SupportedURL(u *url.URL) bool {
// url.Parse shouldn't return nil except on error....but it can.
if u == nil {
return false
}
// build a dummy NewDownloadClient since this is the only place that valid
// protocols are actually exposed.
cli := NewDownloadClient(&DownloadConfig{})
// Iterate through each downloader to see if a protocol was found.
ok := false
for scheme, _ := range cli.config.DownloaderMap {
if strings.ToLower(u.Scheme) == strings.ToLower(scheme) {
ok = true
}
}
return ok
}
// DownloadableURL processes a URL that may also be a file path and returns // DownloadableURL processes a URL that may also be a file path and returns
// a completely valid URL. For example, the original URL might be "local/file.iso" // a completely valid URL representing the requested file. For example,
// which isn't a valid URL. DownloadableURL will return "file:///local/file.iso" // the original URL might be "local/file.iso" which isn't a valid URL,
// and so DownloadableURL will return "file://local/file.iso"
// No other transformations are done to the path.
func DownloadableURL(original string) (string, error) { func DownloadableURL(original string) (string, error) {
var result string
// Verify that the scheme is something we support in our common downloader. // Fix the url if it's using bad characters commonly mistaken with a path.
supported := []string{"file", "http", "https", "smb"} original = filepath.ToSlash(original)
found := false
for _, s := range supported { // Check to see that this is a parseable URL with a scheme. If so, then just pass it through.
if strings.HasPrefix(strings.ToLower(original), s+"://") { if u, err := url.Parse(original); err == nil && u.Scheme != "" && u.Host != "" {
found = true return filepath.ToSlash(original), nil
break
}
} }
// If it's properly prefixed with something we support, then we don't need // Since it's not a url, this might be a path. So, check that the file exists,
// to make it a uri. // then make it an absolute path so we can make a proper uri.
if found { if _, err := os.Stat(original); err == nil {
original = filepath.ToSlash(original) result, err = filepath.Abs(filepath.FromSlash(original))
// make sure that it can be parsed though..
uri, err := url.Parse(original)
if err != nil { if err != nil {
return "", err return "", err
} }
uri.Scheme = strings.ToLower(uri.Scheme) result, err = filepath.EvalSymlinks(result)
return uri.String(), nil
}
// If the file exists, then make it an absolute path
_, err := os.Stat(original)
if err == nil {
original, err = filepath.Abs(filepath.FromSlash(original))
if err != nil { if err != nil {
return "", err return "", err
} }
original, err = filepath.EvalSymlinks(original) result = filepath.Clean(result)
if err != nil { result = filepath.ToSlash(result)
return "", err
}
original = filepath.Clean(original) // We have no idea what this might be, so we'll leave it as is.
original = filepath.ToSlash(original) } else {
result = filepath.ToSlash(original)
} }
// Since it wasn't properly prefixed, let's make it into a well-formed // We should have a path that can just turn into a file:// scheme'd url.
// file:// uri. return fmt.Sprintf("file://%s", result), nil
}
return "file://" + original, nil // Force the parameter into a url. This will transform the parameter into
// a proper url, removing slashes, adding the proper prefix, etc.
func ValidatedURL(original string) (string, error) {
// See if the user failed to give a url
if ok, _ := regexp.MatchString("(?m)^[^[:punct:]]+://", original); !ok {
// So since no magic was found, this must be a path.
result, err := DownloadableURL(original)
if err == nil {
return ValidatedURL(result)
}
return "", err
}
// Verify that the url is parseable...just in case.
u, err := url.Parse(original)
if err != nil {
return "", err
}
// We should now have a url, so verify that it's a protocol we support.
if !SupportedURL(u) {
return "", fmt.Errorf("Unsupported protocol scheme! (%#v)", u)
}
// We should now have a properly formatted and supported url
return u.String(), nil
} }
// FileExistsLocally takes the URL output from DownloadableURL, and determines // FileExistsLocally takes the URL output from DownloadableURL, and determines

View File

@ -36,15 +36,21 @@ func TestChooseString(t *testing.T) {
} }
} }
func TestDownloadableURL(t *testing.T) { func TestValidatedURL(t *testing.T) {
// Invalid URL: has hex code in host // Invalid URL: has hex code in host
_, err := DownloadableURL("http://what%20.com") _, err := ValidatedURL("http://what%20.com")
if err == nil {
t.Fatalf("expected err : %s", err)
}
// Invalid: unsupported scheme
_, err = ValidatedURL("ftp://host.com/path")
if err == nil { if err == nil {
t.Fatalf("expected err : %s", err) t.Fatalf("expected err : %s", err)
} }
// Valid: http // Valid: http
u, err := DownloadableURL("HTTP://packer.io/path") u, err := ValidatedURL("HTTP://packer.io/path")
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
@ -69,7 +75,7 @@ func TestDownloadableURL(t *testing.T) {
} }
for _, tc := range cases { for _, tc := range cases {
u, err := DownloadableURL(tc.InputString) u, err := ValidatedURL(tc.InputString)
if u != tc.OutputURL { if u != tc.OutputURL {
t.Fatal(fmt.Sprintf("Error with URL %s: got %s but expected %s", t.Fatal(fmt.Sprintf("Error with URL %s: got %s but expected %s",
tc.InputString, tc.OutputURL, u)) tc.InputString, tc.OutputURL, u))
@ -77,11 +83,11 @@ func TestDownloadableURL(t *testing.T) {
if (err != nil) != tc.ErrExpected { if (err != nil) != tc.ErrExpected {
if tc.ErrExpected == true { if tc.ErrExpected == true {
t.Fatal(fmt.Sprintf("Error with URL %s: we expected "+ t.Fatal(fmt.Sprintf("Error with URL %s: we expected "+
"DownloadableURL to return an error but didn't get one.", "ValidatedURL to return an error but didn't get one.",
tc.InputString)) tc.InputString))
} else { } else {
t.Fatal(fmt.Sprintf("Error with URL %s: we did not expect an "+ t.Fatal(fmt.Sprintf("Error with URL %s: we did not expect an "+
" error from DownloadableURL but we got: %s", " error from ValidatedURL but we got: %s",
tc.InputString, err)) tc.InputString, err))
} }
} }

View File

@ -86,8 +86,6 @@ func NewDownloadClient(c *DownloadConfig) *DownloadClient {
// Create downloader map if it hasn't been specified already. // Create downloader map if it hasn't been specified already.
if c.DownloaderMap == nil { if c.DownloaderMap == nil {
// XXX: Make sure you add any new protocols you implement
// to the DownloadableURL implementation in config.go!
c.DownloaderMap = map[string]Downloader{ c.DownloaderMap = map[string]Downloader{
"file": &FileDownloader{bufferSize: nil}, "file": &FileDownloader{bufferSize: nil},
"http": &HTTPDownloader{userAgent: c.UserAgent}, "http": &HTTPDownloader{userAgent: c.UserAgent},

View File

@ -111,7 +111,7 @@ func (c *ISOConfig) Prepare(ctx *interpolate.Context) (warnings []string, errs [
c.ISOChecksum = strings.ToLower(c.ISOChecksum) c.ISOChecksum = strings.ToLower(c.ISOChecksum)
for i, url := range c.ISOUrls { for i, url := range c.ISOUrls {
url, err := DownloadableURL(url) url, err := ValidatedURL(url)
if err != nil { if err != nil {
errs = append( errs = append(
errs, fmt.Errorf("Failed to parse iso_url %d: %s", i+1, err)) errs, fmt.Errorf("Failed to parse iso_url %d: %s", i+1, err))