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 (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/url"
|
||||
)
|
||||
|
||||
// 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
|
||||
getSrc, subDir := getDirSubdir(getSrc)
|
||||
|
||||
u, err := urlParse(getSrc)
|
||||
u, err := url.Parse(getSrc)
|
||||
if err == nil && u.Scheme != "" {
|
||||
// Valid URL
|
||||
return src, nil
|
||||
|
@ -66,7 +68,7 @@ func Detect(src string, pwd string) (string, error) {
|
|||
}
|
||||
}
|
||||
if subDir != "" {
|
||||
u, err := urlParse(result)
|
||||
u, err := url.Parse(result)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Error parsing URL: %s", err)
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package module
|
|||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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{
|
||||
{"/foo", "/pwd", "file:///pwd/foo", false},
|
||||
{`C:\`, `/pwd`, `file:///C:/`, false},
|
||||
{`C:\?bar=baz`, `/pwd`, `file:///C:/?bar=baz`, false},
|
||||
{`C:\`, `/pwd`, `file://C:/`, false},
|
||||
{`C:\?bar=baz`, `/pwd`, `file://C:/?bar=baz`, false},
|
||||
}
|
||||
|
||||
func TestFileDetector(t *testing.T) {
|
||||
|
@ -61,7 +61,7 @@ var noPwdUnixFileTests = []fileTest{
|
|||
|
||||
var noPwdWinFileTests = []fileTest{
|
||||
{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) {
|
||||
|
|
|
@ -11,6 +11,8 @@ import (
|
|||
"regexp"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
urlhelper "github.com/hashicorp/terraform/helper/url"
|
||||
)
|
||||
|
||||
// Getter defines the interface that schemes must implement to download
|
||||
|
@ -72,7 +74,7 @@ func Get(dst, src string) error {
|
|||
dst = tmpDir
|
||||
}
|
||||
|
||||
u, err := urlParse(src)
|
||||
u, err := urlhelper.Parse(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@ import (
|
|||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
|
||||
urlhelper "github.com/hashicorp/terraform/helper/url"
|
||||
)
|
||||
|
||||
// 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")
|
||||
}
|
||||
|
||||
newURL, err := urlParse(u.String())
|
||||
newURL, err := urlhelper.Parse(u.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/config"
|
||||
urlhelper "github.com/hashicorp/terraform/helper/url"
|
||||
)
|
||||
|
||||
const fixtureDir = "./test-fixtures"
|
||||
|
@ -43,7 +44,7 @@ func testModule(n string) string {
|
|||
}
|
||||
|
||||
func testModuleURL(n string) *url.URL {
|
||||
u, err := urlParse(testModule(n))
|
||||
u, err := urlhelper.Parse(testModule(n))
|
||||
if err != nil {
|
||||
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