helper/url: add Windows 'safe' URL Parse wrapper
Pull out the urlParse function, which was introduced in config/module, into a helper package.
This commit is contained in:
parent
26156981d7
commit
e7bbbfb098
|
@ -3,6 +3,8 @@ package module
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/url"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Detector defines the interface that an invalid URL or a URL with a blank
|
// Detector defines the interface that an invalid URL or a URL with a blank
|
||||||
|
@ -37,7 +39,7 @@ func Detect(src string, pwd string) (string, error) {
|
||||||
// Separate out the subdir if there is one, we don't pass that to detect
|
// Separate out the subdir if there is one, we don't pass that to detect
|
||||||
getSrc, subDir := getDirSubdir(getSrc)
|
getSrc, subDir := getDirSubdir(getSrc)
|
||||||
|
|
||||||
u, err := urlParse(getSrc)
|
u, err := url.Parse(getSrc)
|
||||||
if err == nil && u.Scheme != "" {
|
if err == nil && u.Scheme != "" {
|
||||||
// Valid URL
|
// Valid URL
|
||||||
return src, nil
|
return src, nil
|
||||||
|
@ -66,7 +68,7 @@ func Detect(src string, pwd string) (string, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if subDir != "" {
|
if subDir != "" {
|
||||||
u, err := urlParse(result)
|
u, err := url.Parse(result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("Error parsing URL: %s", err)
|
return "", fmt.Errorf("Error parsing URL: %s", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package module
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FileDetector implements Detector to detect file paths.
|
// FileDetector implements Detector to detect file paths.
|
||||||
|
@ -23,3 +24,17 @@ func (d *FileDetector) Detect(src, pwd string) (string, bool, error) {
|
||||||
}
|
}
|
||||||
return fmtFileURL(src), true, nil
|
return fmtFileURL(src), true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fmtFileURL(path string) string {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
// Make sure we're using "/" on Windows. URLs are "/"-based.
|
||||||
|
path = filepath.ToSlash(path)
|
||||||
|
return fmt.Sprintf("file://%s", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure that we don't start with "/" since we add that below.
|
||||||
|
if path[0] == '/' {
|
||||||
|
path = path[1:]
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("file:///%s", path)
|
||||||
|
}
|
||||||
|
|
|
@ -23,8 +23,8 @@ var unixFileTests = []fileTest{
|
||||||
|
|
||||||
var winFileTests = []fileTest{
|
var winFileTests = []fileTest{
|
||||||
{"/foo", "/pwd", "file:///pwd/foo", false},
|
{"/foo", "/pwd", "file:///pwd/foo", false},
|
||||||
{`C:\`, `/pwd`, `file:///C:/`, false},
|
{`C:\`, `/pwd`, `file://C:/`, false},
|
||||||
{`C:\?bar=baz`, `/pwd`, `file:///C:/?bar=baz`, false},
|
{`C:\?bar=baz`, `/pwd`, `file://C:/?bar=baz`, false},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFileDetector(t *testing.T) {
|
func TestFileDetector(t *testing.T) {
|
||||||
|
@ -61,7 +61,7 @@ var noPwdUnixFileTests = []fileTest{
|
||||||
|
|
||||||
var noPwdWinFileTests = []fileTest{
|
var noPwdWinFileTests = []fileTest{
|
||||||
{in: "/foo", pwd: "", out: "", err: true},
|
{in: "/foo", pwd: "", out: "", err: true},
|
||||||
{in: `C:\`, pwd: ``, out: `file:///C:/`, err: false},
|
{in: `C:\`, pwd: ``, out: `file://C:/`, err: false},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFileDetector_noPwd(t *testing.T) {
|
func TestFileDetector_noPwd(t *testing.T) {
|
||||||
|
|
|
@ -11,6 +11,8 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
urlhelper "github.com/hashicorp/terraform/helper/url"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Getter defines the interface that schemes must implement to download
|
// Getter defines the interface that schemes must implement to download
|
||||||
|
@ -72,7 +74,7 @@ func Get(dst, src string) error {
|
||||||
dst = tmpDir
|
dst = tmpDir
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := urlParse(src)
|
u, err := urlhelper.Parse(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,8 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
|
urlhelper "github.com/hashicorp/terraform/helper/url"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HgGetter is a Getter implementation that will download a module from
|
// HgGetter is a Getter implementation that will download a module from
|
||||||
|
@ -17,7 +19,7 @@ func (g *HgGetter) Get(dst string, u *url.URL) error {
|
||||||
return fmt.Errorf("hg must be available and on the PATH")
|
return fmt.Errorf("hg must be available and on the PATH")
|
||||||
}
|
}
|
||||||
|
|
||||||
newURL, err := urlParse(u.String())
|
newURL, err := urlhelper.Parse(u.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/config"
|
"github.com/hashicorp/terraform/config"
|
||||||
|
urlhelper "github.com/hashicorp/terraform/helper/url"
|
||||||
)
|
)
|
||||||
|
|
||||||
const fixtureDir = "./test-fixtures"
|
const fixtureDir = "./test-fixtures"
|
||||||
|
@ -43,7 +44,7 @@ func testModule(n string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testModuleURL(n string) *url.URL {
|
func testModuleURL(n string) *url.URL {
|
||||||
u, err := urlParse(testModule(n))
|
u, err := urlhelper.Parse(testModule(n))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,63 +0,0 @@
|
||||||
package module
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func urlParse(rawURL string) (*url.URL, error) {
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
// Make sure we're using "/" on Windows. URLs are "/"-based.
|
|
||||||
rawURL = filepath.ToSlash(rawURL)
|
|
||||||
}
|
|
||||||
u, err := url.Parse(rawURL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if runtime.GOOS != "windows" {
|
|
||||||
return u, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(rawURL) > 1 && rawURL[1] == ':' {
|
|
||||||
// Assume we're dealing with a drive letter file path on Windows.
|
|
||||||
// We need to adjust the URL Path for drive letter file paths
|
|
||||||
// because url.Parse("c:/users/user") yields URL Scheme = "c"
|
|
||||||
// and URL path = "/users/user".
|
|
||||||
u.Path = fmt.Sprintf("%s:%s", u.Scheme, u.Path)
|
|
||||||
u.Scheme = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(u.Host) > 1 && u.Host[1] == ':' && strings.HasPrefix(rawURL, "file://") {
|
|
||||||
// Assume we're dealing with a drive letter file path on Windows
|
|
||||||
// where the drive letter has been parsed into the URL Host.
|
|
||||||
u.Path = fmt.Sprintf("%s%s", u.Host, u.Path)
|
|
||||||
u.Host = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove leading slash for absolute file paths on Windows.
|
|
||||||
// For example, url.Parse yields u.Path = "/C:/Users/user" for
|
|
||||||
// rawURL = "file:///C:/Users/user", which is an incorrect syntax.
|
|
||||||
if len(u.Path) > 2 && u.Path[0] == '/' && u.Path[2] == ':' {
|
|
||||||
u.Path = u.Path[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
return u, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func fmtFileURL(path string) string {
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
// Make sure we're using "/" on Windows. URLs are "/"-based.
|
|
||||||
path = filepath.ToSlash(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure that we don't start with "/" since we add that below.
|
|
||||||
if path[0] == '/' {
|
|
||||||
path = path[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("file:///%s", path)
|
|
||||||
}
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
package url
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Parse parses rawURL into a URL structure.
|
||||||
|
// The rawURL may be relative or absolute.
|
||||||
|
//
|
||||||
|
// Parse is a wrapper for the Go stdlib net/url Parse function, but returns
|
||||||
|
// Windows "safe" URLs on Windows platforms.
|
||||||
|
func Parse(rawURL string) (*url.URL, error) {
|
||||||
|
return parse(rawURL)
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
package url
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type parseTest struct {
|
||||||
|
rawURL string
|
||||||
|
scheme string
|
||||||
|
host string
|
||||||
|
path string
|
||||||
|
str string
|
||||||
|
err bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var parseTests = []parseTest{
|
||||||
|
{
|
||||||
|
rawURL: "/foo/bar",
|
||||||
|
scheme: "",
|
||||||
|
host: "",
|
||||||
|
path: "/foo/bar",
|
||||||
|
str: "/foo/bar",
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rawURL: "file:///dir/",
|
||||||
|
scheme: "file",
|
||||||
|
host: "",
|
||||||
|
path: "/dir/",
|
||||||
|
str: "file:///dir/",
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var winParseTests = []parseTest{
|
||||||
|
{
|
||||||
|
rawURL: `C:\`,
|
||||||
|
scheme: ``,
|
||||||
|
host: ``,
|
||||||
|
path: `C:/`,
|
||||||
|
str: `C:/`,
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rawURL: `file://C:\`,
|
||||||
|
scheme: `file`,
|
||||||
|
host: ``,
|
||||||
|
path: `C:/`,
|
||||||
|
str: `file://C:/`,
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rawURL: `file:///C:\`,
|
||||||
|
scheme: `file`,
|
||||||
|
host: ``,
|
||||||
|
path: `C:/`,
|
||||||
|
str: `file://C:/`,
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParse(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
parseTests = append(parseTests, winParseTests...)
|
||||||
|
}
|
||||||
|
for i, pt := range parseTests {
|
||||||
|
url, err := Parse(pt.rawURL)
|
||||||
|
if err != nil && !pt.err {
|
||||||
|
t.Errorf("test %d: unexpected error: %s", i, err)
|
||||||
|
}
|
||||||
|
if err == nil && pt.err {
|
||||||
|
t.Errorf("test %d: expected an error", i)
|
||||||
|
}
|
||||||
|
if url.Scheme != pt.scheme {
|
||||||
|
t.Errorf("test %d: expected Scheme = %q, got %q", i, pt.scheme, url.Scheme)
|
||||||
|
}
|
||||||
|
if url.Host != pt.host {
|
||||||
|
t.Errorf("test %d: expected Host = %q, got %q", i, pt.host, url.Host)
|
||||||
|
}
|
||||||
|
if url.Path != pt.path {
|
||||||
|
t.Errorf("test %d: expected Path = %q, got %q", i, pt.path, url.Path)
|
||||||
|
}
|
||||||
|
if url.String() != pt.str {
|
||||||
|
t.Errorf("test %d: expected url.String() = %q, got %q", i, pt.str, url.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package url
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
func parse(rawURL string) (*url.URL, error) {
|
||||||
|
return url.Parse(rawURL)
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package url
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func parse(rawURL string) (*url.URL, error) {
|
||||||
|
// Make sure we're using "/" since URLs are "/"-based.
|
||||||
|
rawURL = filepath.ToSlash(rawURL)
|
||||||
|
|
||||||
|
u, err := url.Parse(rawURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(rawURL) > 1 && rawURL[1] == ':' {
|
||||||
|
// Assume we're dealing with a drive letter file path where the drive
|
||||||
|
// letter has been parsed into the URL Scheme, and the rest of the path
|
||||||
|
// has been parsed into the URL Path without the leading ':' character.
|
||||||
|
u.Path = fmt.Sprintf("%s:%s", string(rawURL[0]), u.Path)
|
||||||
|
u.Scheme = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(u.Host) > 1 && u.Host[1] == ':' && strings.HasPrefix(rawURL, "file://") {
|
||||||
|
// Assume we're dealing with a drive letter file path where the drive
|
||||||
|
// letter has been parsed into the URL Host.
|
||||||
|
u.Path = fmt.Sprintf("%s%s", u.Host, u.Path)
|
||||||
|
u.Host = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove leading slash for absolute file paths.
|
||||||
|
if len(u.Path) > 2 && u.Path[0] == '/' && u.Path[2] == ':' {
|
||||||
|
u.Path = u.Path[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
return u, err
|
||||||
|
}
|
Loading…
Reference in New Issue