From c13cff37efb5fe9359cbc46a1efabdda9a4d783f Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood <nick@craig-wood.com>
Date: Wed, 3 May 2017 16:07:40 +0100
Subject: [PATCH] ftp: replace URL parser with url.URL

---
 ftp/ftp.go               | 74 +++++++++++++++-------------------------
 ftp/ftp_internal_test.go | 36 -------------------
 2 files changed, 27 insertions(+), 83 deletions(-)
 delete mode 100644 ftp/ftp_internal_test.go

diff --git a/ftp/ftp.go b/ftp/ftp.go
index 66deadd94..d0e6b863d 100644
--- a/ftp/ftp.go
+++ b/ftp/ftp.go
@@ -2,11 +2,9 @@
 package ftp
 
 import (
-	"fmt"
 	"io"
+	"net/url"
 	"path/filepath"
-	"regexp"
-	"strconv"
 	"strings"
 	"sync"
 	"time"
@@ -24,7 +22,7 @@ var globalMux = sync.Mutex{}
 // Register with Fs
 func init() {
 	fs.Register(&fs.RegInfo{
-		Name:        "Ftp",
+		Name:        "ftp",
 		Description: "FTP interface",
 		NewFs:       NewFs,
 		Options: []fs.Option{
@@ -32,11 +30,12 @@ func init() {
 				Name: "username",
 				Help: "Username",
 			}, {
-				Name: "password",
-				Help: "Password",
+				Name:       "password",
+				Help:       "Password",
+				IsPassword: true,
 			}, {
 				Name: "url",
-				Help: "FTP url",
+				Help: "FTP URL",
 			},
 		},
 	})
@@ -48,7 +47,7 @@ type Fs struct {
 	root     string          // the path we are working on if any
 	features *fs.Features    // optional features
 	c        *ftp.ServerConn // the connection to the FTP server
-	url      URL
+	url      *url.URL
 	mu       sync.Mutex
 }
 
@@ -67,23 +66,6 @@ type FileInfo struct {
 	IsDir   bool
 }
 
-// URL represents an FTP URL
-type URL struct {
-	Scheme string
-	Host   string
-	Port   int
-	Path   string
-}
-
-// ToDial converts the URL to Dial format
-func (u *URL) ToDial() string {
-	return fmt.Sprintf("%s:%d", u.Host, u.Port)
-}
-
-func (u *URL) String() string {
-	return fmt.Sprintf("ftp://%s:%d/%s", u.Host, u.Port, u.Path)
-}
-
 // ------------------------------------------------------------
 
 // Name of this fs
@@ -98,7 +80,7 @@ func (f *Fs) Root() string {
 
 // String returns a description of the FS
 func (f *Fs) String() string {
-	return fmt.Sprintf("FTP Connection to %s", f.url.String())
+	return f.url.String()
 }
 
 // Features returns the optional features of this Fs
@@ -106,39 +88,37 @@ func (f *Fs) Features() *fs.Features {
 	return f.features
 }
 
-// Parse a URL
-func parseURL(url string) URL {
-	// This is *similar* to the RFC 3986 regexp but it matches the
-	// port independently from the host
-	r, _ := regexp.Compile("^(([^:/?#]+):)?(//([^/?#:]*))?(:([0-9]+))?([^?#]*)(\\?([^#]*))?(#(.*))?")
-
-	data := r.FindAllStringSubmatch(url, -1)
-
-	if data[0][5] == "" {
-		data[0][5] = "21"
-	}
-	port, _ := strconv.Atoi(data[0][5])
-	return URL{data[0][2], data[0][4], port, data[0][7]}
-}
-
 // Open a new connection to the FTP server.
-func ftpConnection(name, root string) (*ftp.ServerConn, URL, error) {
-	url := fs.ConfigFileGet(name, "url")
+func ftpConnection(name, root string) (*ftp.ServerConn, *url.URL, error) {
+	URL := fs.ConfigFileGet(name, "url")
 	user := fs.ConfigFileGet(name, "username")
 	pass := fs.ConfigFileGet(name, "password")
-	u := parseURL(url)
+	pass, err := fs.Reveal(pass)
+	if err != nil {
+		return nil, nil, errors.Wrap(err, "failed to decrypt password")
+	}
+	u, err := url.Parse(URL)
+	if err != nil {
+		return nil, nil, errors.Wrap(err, "open ftp connection url parse")
+	}
 	u.Path = filepath.Join(u.Path, root)
 	fs.Debugf(nil, "New ftp Connection with name %s and url %s (path %s)", name, u.String(), u.Path)
 	globalMux.Lock()
 	defer globalMux.Unlock()
-	c, err := ftp.DialTimeout(u.ToDial(), 30*time.Second)
+	dialAddr := u.Hostname()
+	if u.Port() != "" {
+		dialAddr += ":" + u.Port()
+	} else {
+		dialAddr += ":21"
+	}
+	c, err := ftp.DialTimeout(dialAddr, 30*time.Second)
 	if err != nil {
-		fs.Errorf(nil, "Error while Dialing %s: %s", u.ToDial(), err)
+		fs.Errorf(nil, "Error while Dialing %s: %s", dialAddr, err)
 		return nil, u, err
 	}
 	err = c.Login(user, pass)
 	if err != nil {
-		fs.Errorf(nil, "Error while Logging in into %s: %s", u.ToDial(), err)
+		fs.Errorf(nil, "Error while Logging in into %s: %s", dialAddr, err)
 		return nil, u, err
 	}
 	return c, u, nil
diff --git a/ftp/ftp_internal_test.go b/ftp/ftp_internal_test.go
deleted file mode 100644
index 0e28b3b9f..000000000
--- a/ftp/ftp_internal_test.go
+++ /dev/null
@@ -1,36 +0,0 @@
-package ftp
-
-import "testing"
-
-func TestParseUrlToDial(t *testing.T) {
-	for _, test := range []struct {
-		in   string
-		want string
-	}{
-		{"ftp://foo.bar", "foo.bar:21"},
-		{"http://foo.bar", "foo.bar:21"},
-		{"ftp:/foo.bar:123", "foo.bar:123"},
-	} {
-		u := parseURL(test.in)
-		got := u.ToDial()
-		if got != test.want {
-			t.Logf("%q: want %q got %q", test.in, test.want, got)
-		}
-	}
-}
-
-func TestParseUrlPath(t *testing.T) {
-	for _, test := range []struct {
-		in   string
-		want string
-	}{
-		{"ftp://foo.bar/", "/"},
-		{"ftp://foo.bar/debian", "/debian"},
-		{"ftp://foo.bar", "/"},
-	} {
-		u := parseURL(test.in)
-		if u.Path != test.want {
-			t.Logf("%q: want %q got %q", test.in, test.want, u.Path)
-		}
-	}
-}