From 46992b0f02f74066bcdfd9b03e33bc03abd10dc7 Mon Sep 17 00:00:00 2001 From: Steven Hartland Date: Thu, 10 Jun 2021 10:17:14 +0100 Subject: [PATCH] fix: DialURL compatibility with redis-cli (#566) Fix compatibility of DialURL with respect for single component user-info records. This enables URLs such as redis://mypass@localhost/1 as supported by redis-cli to be used. --- redis/conn.go | 13 ++++++++++++- redis/conn_test.go | 29 +++++++++++++++++++---------- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/redis/conn.go b/redis/conn.go index 1398b4d1..7d757dce 100644 --- a/redis/conn.go +++ b/redis/conn.go @@ -168,6 +168,7 @@ func DialPassword(password string) DialOption { // DialUsername specifies the username to use when connecting to // the Redis server when Redis ACLs are used. +// A DialPassword must also be passed otherwise this option will have no effect. func DialUsername(username string) DialOption { return DialOption{func(do *dialOptions) { do.username = username @@ -347,8 +348,18 @@ func DialURL(rawurl string, options ...DialOption) (Conn, error) { if u.User != nil { password, isSet := u.User.Password() + username := u.User.Username() if isSet { - options = append(options, DialUsername(u.User.Username()), DialPassword(password)) + if username != "" { + // ACL + options = append(options, DialUsername(username), DialPassword(password)) + } else { + // requirepass - user-info username:password with blank username + options = append(options, DialPassword(password)) + } + } else if username != "" { + // requirepass - redis-cli compatibility which treats as single arg in user-info as a password + options = append(options, DialPassword(username)) } } diff --git a/redis/conn_test.go b/redis/conn_test.go index 425c4f09..b33cc41a 100644 --- a/redis/conn_test.go +++ b/redis/conn_test.go @@ -640,6 +640,13 @@ var dialURLTests = []struct { w string }{ {"password", "redis://:abc123@localhost", "+OK\r\n", "*2\r\n$4\r\nAUTH\r\n$6\r\nabc123\r\n"}, + {"password redis-cli compat", "redis://abc123@localhost", "+OK\r\n", "*2\r\n$4\r\nAUTH\r\n$6\r\nabc123\r\n"}, + {"password db1", "redis://:abc123@localhost/1", "+OK\r\n+OK\r\n", "*2\r\n$4\r\nAUTH\r\n$6\r\nabc123\r\n*2\r\n$6\r\nSELECT\r\n$1\r\n1\r\n"}, + {"password db1 redis-cli compat", "redis://abc123@localhost/1", "+OK\r\n+OK\r\n", "*2\r\n$4\r\nAUTH\r\n$6\r\nabc123\r\n*2\r\n$6\r\nSELECT\r\n$1\r\n1\r\n"}, + {"password no host db0", "redis://:abc123@/0", "+OK\r\n+OK\r\n", "*2\r\n$4\r\nAUTH\r\n$6\r\nabc123\r\n"}, + {"password no host db0 redis-cli compat", "redis://abc123@/0", "+OK\r\n+OK\r\n", "*2\r\n$4\r\nAUTH\r\n$6\r\nabc123\r\n"}, + {"password no host db1", "redis://:abc123@/1", "+OK\r\n+OK\r\n", "*2\r\n$4\r\nAUTH\r\n$6\r\nabc123\r\n*2\r\n$6\r\nSELECT\r\n$1\r\n1\r\n"}, + {"password no host db1 redis-cli compat", "redis://abc123@/1", "+OK\r\n+OK\r\n", "*2\r\n$4\r\nAUTH\r\n$6\r\nabc123\r\n*2\r\n$6\r\nSELECT\r\n$1\r\n1\r\n"}, {"username and password", "redis://user:password@localhost", "+OK\r\n", "*3\r\n$4\r\nAUTH\r\n$4\r\nuser\r\n$8\r\npassword\r\n"}, {"username", "redis://x:@localhost", "+OK\r\n", ""}, {"database 3", "redis://localhost/3", "+OK\r\n", "*2\r\n$6\r\nSELECT\r\n$1\r\n3\r\n"}, @@ -649,16 +656,18 @@ var dialURLTests = []struct { func TestDialURL(t *testing.T) { for _, tt := range dialURLTests { - var buf bytes.Buffer - // UseTLS should be ignored in all of these tests. - _, err := redis.DialURL(tt.url, dialTestConn(tt.r, &buf), redis.DialUseTLS(true)) - if err != nil { - t.Errorf("%s dial error: %v", tt.description, err) - continue - } - if w := buf.String(); w != tt.w { - t.Errorf("%s commands = %q, want %q", tt.description, w, tt.w) - } + t.Run(tt.description, func(t *testing.T) { + var buf bytes.Buffer + // UseTLS should be ignored in all of these tests. + _, err := redis.DialURL(tt.url, dialTestConn(tt.r, &buf), redis.DialUseTLS(true)) + if err != nil { + t.Errorf("%s dial error: %v, buf: %v", tt.description, err, buf.String()) + return + } + if w := buf.String(); w != tt.w { + t.Errorf("%s commands = %q, want %q", tt.description, w, tt.w) + } + }) } }