Skip to content

Commit

Permalink
connect: make URI helper functions public
Browse files Browse the repository at this point in the history
We need this functions for `tt-ee` modules to unify the behavior
across modules.

Part of tarantool/roadmap-internal#281
  • Loading branch information
oleg-jukovec authored and psergee committed Oct 30, 2023
1 parent a7eb200 commit 18716fe
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 125 deletions.
119 changes: 5 additions & 114 deletions cli/cmd/connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@ package cmd
import (
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"syscall"

"github.com/apex/log"
Expand All @@ -22,17 +19,6 @@ import (
"golang.org/x/crypto/ssh/terminal"
)

const (
// userPathRe is a regexp for a username:password pair.
userpassRe = `[^@:/]+:[^@:/]+`

// uriPathPrefixRe is a regexp for a path prefix in uri, such as `scheme://path``.
uriPathPrefixRe = `((~?/+)|((../+)*))?`

// systemPathPrefixRe is a regexp for a path prefix to use without scheme.
systemPathPrefixRe = `(([\.~]?/+)|((../+)+))`
)

var (
connectUser string
connectPassword string
Expand Down Expand Up @@ -120,101 +106,6 @@ func NewConnectCmd() *cobra.Command {
return connectCmd
}

// isBaseURI returns true if a string is a valid URI.
func isBaseURI(str string) bool {
// tcp://host:port
// host:port
tcpReStr := `(tcp://)?([\w\\.-]+:\d+)`
// unix://../path
// unix://~/path
// unix:///path
// unix://path
unixReStr := `unix://` + uriPathPrefixRe + `[^\./@]+[^@]*`
// ../path
// ~/path
// /path
// ./path
pathReStr := systemPathPrefixRe + `[^\./].*`

uriReStr := "^((" + tcpReStr + ")|(" + unixReStr + ")|(" + pathReStr + "))$"
uriRe := regexp.MustCompile(uriReStr)
return uriRe.MatchString(str)
}

// isCredentialsURI returns true if a string is a valid credentials URI.
func isCredentialsURI(str string) bool {
// tcp://user:password@host:port
// user:password@host:port
tcpReStr := `(tcp://)?` + userpassRe + `@([\w\.-]+:\d+)`
// unix://user:password@../path
// unix://user:password@~/path
// unix://user:password@/path
// unix://user:password@path
unixReStr := `unix://` + userpassRe + `@` + uriPathPrefixRe + `[^\./@]+.*`
// user:password@../path
// user:password@~/path
// user:password@/path
// user:password@./path
pathReStr := userpassRe + `@` + systemPathPrefixRe + `[^\./].*`

uriReStr := "^((" + tcpReStr + ")|(" + unixReStr + ")|(" + pathReStr + "))$"
uriRe := regexp.MustCompile(uriReStr)
return uriRe.MatchString(str)
}

// parseBaseURI parses an URI and returns:
// (network, address)
func parseBaseURI(uri string) (string, string) {
var network, address string
uriLen := len(uri)

switch {
case uriLen > 0 && (uri[0] == '.' || uri[0] == '/' || uri[0] == '~'):
network = connector.UnixNetwork
address = uri
case uriLen >= 7 && uri[0:7] == "unix://":
network = connector.UnixNetwork
address = uri[7:]
case uriLen >= 6 && uri[0:6] == "tcp://":
network = connector.TCPNetwork
address = uri[6:]
default:
network = connector.TCPNetwork
address = uri
}

// In the case of a complex uri, shell expansion does not occur, so do it manually.
if network == connector.UnixNetwork &&
strings.HasPrefix(address, "~/") {
if homeDir, err := os.UserHomeDir(); err == nil {
address = filepath.Join(homeDir, address[2:])
}
}

return network, address
}

// parseCredentialsURI parses a URI with credentials and returns:
// (URI without credentials, user, password)
func parseCredentialsURI(str string) (string, string, string) {
if !isCredentialsURI(str) {
return str, "", ""
}

re := regexp.MustCompile(userpassRe + `@`)
// Split the string into two parts by credentials to create a string
// without the credentials.
split := re.Split(str, 2)
newStr := split[0] + split[1]

// Parse credentials.
credentialsStr := re.FindString(str)
credentialsLen := len(credentialsStr) - 1 // We don't need a last '@'.
credentialsSlice := strings.Split(credentialsStr[:credentialsLen], ":")

return newStr, credentialsSlice[0], credentialsSlice[1]
}

// makeConnOpts makes and returns connect options from the arguments.
func makeConnOpts(network, address string, connCtx connect.ConnectCtx) connector.ConnectOpts {
ssl := connector.SslOpts{
Expand Down Expand Up @@ -256,27 +147,27 @@ func resolveConnectOpts(cmdCtx *cmdcontext.CmdCtx, cliOpts *config.CliOpts,
connOpts = makeConnOpts(
connector.UnixNetwork, runningCtx.Instances[0].ConsoleSocket, *connectCtx,
)
} else if isCredentialsURI(args[0]) {
} else if connect.IsCredentialsURI(args[0]) {
if connectCtx.Username != "" || connectCtx.Password != "" {
err = fmt.Errorf("username and password are specified with" +
" flags and a URI")
return
}
newURI, user, pass := parseCredentialsURI(args[0])
network, address := parseBaseURI(newURI)
newURI, user, pass := connect.ParseCredentialsURI(args[0])
network, address := connect.ParseBaseURI(newURI)
connectCtx.Username = user
connectCtx.Password = pass
connOpts = makeConnOpts(network, address, *connectCtx)
connectCtx.ConnectTarget = newURI
} else if isBaseURI(args[0]) {
} else if connect.IsBaseURI(args[0]) {
// Environment variables do not overwrite values.
if connectCtx.Username == "" {
connectCtx.Username = os.Getenv(connect.TarantoolUsernameEnv)
}
if connectCtx.Password == "" {
connectCtx.Password = os.Getenv(connect.TarantoolPasswordEnv)
}
network, address := parseBaseURI(args[0])
network, address := connect.ParseBaseURI(args[0])
connOpts = makeConnOpts(network, address, *connectCtx)
} else {
err = fillErr
Expand Down
116 changes: 116 additions & 0 deletions cli/connect/uri.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package connect

import (
"os"
"path/filepath"
"regexp"
"strings"

"github.com/tarantool/tt/cli/connector"
)

const (
// userPathRe is a regexp for a username:password pair.
userpassRe = `[^@:/]+:[^@:/]+`

// uriPathPrefixRe is a regexp for a path prefix in uri, such as `scheme://path``.
uriPathPrefixRe = `((~?/+)|((../+)*))?`

// systemPathPrefixRe is a regexp for a path prefix to use without scheme.
systemPathPrefixRe = `(([\.~]?/+)|((../+)+))`
)

// IsBaseURI returns true if a string is a valid URI.
func IsBaseURI(str string) bool {
// tcp://host:port
// host:port
tcpReStr := `(tcp://)?([\w\\.-]+:\d+)`
// unix://../path
// unix://~/path
// unix:///path
// unix://path
unixReStr := `unix://` + uriPathPrefixRe + `[^\./@]+[^@]*`
// ../path
// ~/path
// /path
// ./path
pathReStr := systemPathPrefixRe + `[^\./].*`

uriReStr := "^((" + tcpReStr + ")|(" + unixReStr + ")|(" + pathReStr + "))$"
uriRe := regexp.MustCompile(uriReStr)
return uriRe.MatchString(str)
}

// IsCredentialsURI returns true if a string is a valid credentials URI.
func IsCredentialsURI(str string) bool {
// tcp://user:password@host:port
// user:password@host:port
tcpReStr := `(tcp://)?` + userpassRe + `@([\w\.-]+:\d+)`
// unix://user:password@../path
// unix://user:password@~/path
// unix://user:password@/path
// unix://user:password@path
unixReStr := `unix://` + userpassRe + `@` + uriPathPrefixRe + `[^\./@]+.*`
// user:password@../path
// user:password@~/path
// user:password@/path
// user:password@./path
pathReStr := userpassRe + `@` + systemPathPrefixRe + `[^\./].*`

uriReStr := "^((" + tcpReStr + ")|(" + unixReStr + ")|(" + pathReStr + "))$"
uriRe := regexp.MustCompile(uriReStr)
return uriRe.MatchString(str)
}

// ParseBaseURI parses an URI and returns:
// (network, address)
func ParseBaseURI(uri string) (string, string) {
var network, address string
uriLen := len(uri)

switch {
case uriLen > 0 && (uri[0] == '.' || uri[0] == '/' || uri[0] == '~'):
network = connector.UnixNetwork
address = uri
case uriLen >= 7 && uri[0:7] == "unix://":
network = connector.UnixNetwork
address = uri[7:]
case uriLen >= 6 && uri[0:6] == "tcp://":
network = connector.TCPNetwork
address = uri[6:]
default:
network = connector.TCPNetwork
address = uri
}

// In the case of a complex uri, shell expansion does not occur, so do it manually.
if network == connector.UnixNetwork &&
strings.HasPrefix(address, "~/") {
if homeDir, err := os.UserHomeDir(); err == nil {
address = filepath.Join(homeDir, address[2:])
}
}

return network, address
}

// ParseCredentialsURI parses a URI with credentials and returns:
// (URI without credentials, user, password)
func ParseCredentialsURI(str string) (string, string, string) {
if !IsCredentialsURI(str) {
return str, "", ""
}

re := regexp.MustCompile(userpassRe + `@`)
// Split the string into two parts by credentials to create a string
// without the credentials.
split := re.Split(str, 2)
newStr := split[0] + split[1]

// Parse credentials.
credentialsStr := re.FindString(str)
credentialsLen := len(credentialsStr) - 1 // We don't need a last '@'.
credentialsSlice := strings.Split(credentialsStr[:credentialsLen], ":")

return newStr, credentialsSlice[0], credentialsSlice[1]
}
23 changes: 12 additions & 11 deletions cli/cmd/connect_test.go → cli/connect/uri_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package cmd
package connect_test

import (
"os"
"testing"

"github.com/stretchr/testify/assert"
"github.com/tarantool/tt/cli/connect"
"github.com/tarantool/tt/cli/connector"
)

Expand Down Expand Up @@ -93,7 +94,7 @@ var invalidCredentialsUris = []string{
func TestIsBaseURIValid(t *testing.T) {
for _, uri := range validBaseUris {
t.Run(uri, func(t *testing.T) {
assert.True(t, isBaseURI(uri), "URI must be valid")
assert.True(t, connect.IsBaseURI(uri), "URI must be valid")
})
}
}
Expand All @@ -106,15 +107,15 @@ func TestIsBaseURIInvalid(t *testing.T) {

for _, uri := range invalid {
t.Run(uri, func(t *testing.T) {
assert.False(t, isBaseURI(uri), "URI must be invalid")
assert.False(t, connect.IsBaseURI(uri), "URI must be invalid")
})
}
}

func TestIsCredentialsURIValid(t *testing.T) {
for _, uri := range validCredentialsUris {
t.Run(uri, func(t *testing.T) {
assert.True(t, isCredentialsURI(uri), "URI must be valid")
assert.True(t, connect.IsCredentialsURI(uri), "URI must be valid")
})
}
}
Expand All @@ -127,7 +128,7 @@ func TestIsCredentialsURIInvalid(t *testing.T) {

for _, uri := range invalid {
t.Run(uri, func(t *testing.T) {
assert.False(t, isCredentialsURI(uri), "URI must be invalid")
assert.False(t, connect.IsCredentialsURI(uri), "URI must be invalid")
})
}
}
Expand All @@ -151,7 +152,7 @@ func TestParseCredentialsURI(t *testing.T) {

for _, c := range cases {
t.Run(c.srcUri, func(t *testing.T) {
newUri, user, pass := parseCredentialsURI(c.srcUri)
newUri, user, pass := connect.ParseCredentialsURI(c.srcUri)
assert.Equal(t, c.newUri, newUri, "a unexpected new URI")
assert.Equal(t, testUser, user, "a unexpected username")
assert.Equal(t, testPass, pass, "a unexpected password")
Expand All @@ -162,7 +163,7 @@ func TestParseCredentialsURI(t *testing.T) {
func TestParseCredentialsURI_parseValid(t *testing.T) {
for _, uri := range validCredentialsUris {
t.Run(uri, func(t *testing.T) {
newUri, user, pass := parseCredentialsURI(uri)
newUri, user, pass := connect.ParseCredentialsURI(uri)
assert.NotEqual(t, uri, newUri, "URI must change")
assert.NotEqual(t, "", user, "username must not be empty")
assert.NotEqual(t, "", pass, "password must not be empty")
Expand All @@ -178,7 +179,7 @@ func TestParseCredentialsURI_notParseInvalid(t *testing.T) {

for _, uri := range invalid {
t.Run(uri, func(t *testing.T) {
newUri, user, pass := parseCredentialsURI(uri)
newUri, user, pass := connect.ParseCredentialsURI(uri)
assert.Equal(t, uri, newUri, "URI must no change")
assert.Equal(t, "", user, "username must be empty")
assert.Equal(t, "", pass, "password must be empty")
Expand All @@ -204,19 +205,19 @@ func TestParseBaseURI(t *testing.T) {

for _, tc := range cases {
t.Run(tc.URI, func(t *testing.T) {
network, address := parseBaseURI(tc.URI)
network, address := connect.ParseBaseURI(tc.URI)
assert.Equal(t, network, tc.network)
assert.Equal(t, address, tc.address)
})
}

t.Run("starts from ~", func(t *testing.T) {
homeDir, _ := os.UserHomeDir()
network, address := parseBaseURI("unix://~/a/b")
network, address := connect.ParseBaseURI("unix://~/a/b")
assert.Equal(t, connector.UnixNetwork, network)
assert.Equal(t, homeDir+"/a/b", address)

network, address = parseBaseURI("~/a/b")
network, address = connect.ParseBaseURI("~/a/b")
assert.Equal(t, connector.UnixNetwork, network)
assert.Equal(t, homeDir+"/a/b", address)
})
Expand Down

0 comments on commit 18716fe

Please sign in to comment.