github.com/scraniel/migrate@v0.0.0-20230320185700-339088f36cee/database/parse_test.go (about) 1 package database_test 2 3 import ( 4 "encoding/hex" 5 "net/url" 6 "strings" 7 "testing" 8 ) 9 10 const reservedChars = "!#$%&'()*+,/:;=?@[]" 11 12 const baseUsername = "username" 13 14 // TestUserUnencodedReservedURLChars documents the behavior of using unencoded reserved characters in usernames with 15 // net/url Parse() 16 func TestUserUnencodedReservedURLChars(t *testing.T) { 17 scheme := "database://" 18 urlSuffix := "password@localhost:12345/myDB?someParam=true" 19 urlSuffixAndSep := ":" + urlSuffix 20 21 testcases := []struct { 22 char string 23 parses bool 24 expectedUsername string // empty string means that the username failed to parse 25 encodedURL string 26 }{ 27 {char: "!", parses: true, expectedUsername: baseUsername + "!", 28 encodedURL: scheme + baseUsername + "%21" + urlSuffixAndSep}, 29 {char: "#", parses: true, expectedUsername: "", 30 encodedURL: scheme + baseUsername + "#" + urlSuffixAndSep}, 31 {char: "$", parses: true, expectedUsername: baseUsername + "$", 32 encodedURL: scheme + baseUsername + "$" + urlSuffixAndSep}, 33 {char: "%", parses: false}, 34 {char: "&", parses: true, expectedUsername: baseUsername + "&", 35 encodedURL: scheme + baseUsername + "&" + urlSuffixAndSep}, 36 {char: "'", parses: true, expectedUsername: "username'", 37 encodedURL: scheme + baseUsername + "%27" + urlSuffixAndSep}, 38 {char: "(", parses: true, expectedUsername: "username(", 39 encodedURL: scheme + baseUsername + "%28" + urlSuffixAndSep}, 40 {char: ")", parses: true, expectedUsername: "username)", 41 encodedURL: scheme + baseUsername + "%29" + urlSuffixAndSep}, 42 {char: "*", parses: true, expectedUsername: "username*", 43 encodedURL: scheme + baseUsername + "%2A" + urlSuffixAndSep}, 44 {char: "+", parses: true, expectedUsername: "username+", 45 encodedURL: scheme + baseUsername + "+" + urlSuffixAndSep}, 46 {char: ",", parses: true, expectedUsername: "username,", 47 encodedURL: scheme + baseUsername + "," + urlSuffixAndSep}, 48 {char: "/", parses: true, expectedUsername: "", 49 encodedURL: scheme + baseUsername + "/" + urlSuffixAndSep}, 50 {char: ":", parses: true, expectedUsername: baseUsername, 51 encodedURL: scheme + baseUsername + ":%3A" + urlSuffix}, 52 {char: ";", parses: true, expectedUsername: "username;", 53 encodedURL: scheme + baseUsername + ";" + urlSuffixAndSep}, 54 {char: "=", parses: true, expectedUsername: "username=", 55 encodedURL: scheme + baseUsername + "=" + urlSuffixAndSep}, 56 {char: "?", parses: true, expectedUsername: "", 57 encodedURL: scheme + baseUsername + "?" + urlSuffixAndSep}, 58 {char: "@", parses: true, expectedUsername: "username@", 59 encodedURL: scheme + baseUsername + "%40" + urlSuffixAndSep}, 60 {char: "[", parses: false}, 61 {char: "]", parses: false}, 62 } 63 64 testedChars := make([]string, 0, len(reservedChars)) 65 for _, tc := range testcases { 66 testedChars = append(testedChars, tc.char) 67 t.Run("reserved char "+tc.char, func(t *testing.T) { 68 s := scheme + baseUsername + tc.char + urlSuffixAndSep 69 u, err := url.Parse(s) 70 if err == nil { 71 if !tc.parses { 72 t.Error("Unexpectedly parsed reserved character. url:", s) 73 return 74 } 75 var username string 76 if u.User != nil { 77 username = u.User.Username() 78 } 79 if username != tc.expectedUsername { 80 t.Error("Got unexpected username:", username, "!=", tc.expectedUsername) 81 } 82 if s := u.String(); s != tc.encodedURL { 83 t.Error("Got unexpected encoded URL:", s, "!=", tc.encodedURL) 84 } 85 } else { 86 if tc.parses { 87 t.Error("Failed to parse reserved character. url:", s) 88 } 89 } 90 }) 91 } 92 93 t.Run("All reserved chars tested", func(t *testing.T) { 94 if s := strings.Join(testedChars, ""); s != reservedChars { 95 t.Error("Not all reserved URL characters were tested:", s, "!=", reservedChars) 96 } 97 }) 98 } 99 100 func TestUserEncodedReservedURLChars(t *testing.T) { 101 scheme := "database://" 102 urlSuffix := "password@localhost:12345/myDB?someParam=true" 103 urlSuffixAndSep := ":" + urlSuffix 104 105 for _, c := range reservedChars { 106 c := string(c) 107 t.Run("reserved char "+c, func(t *testing.T) { 108 encodedChar := "%" + hex.EncodeToString([]byte(c)) 109 s := scheme + baseUsername + encodedChar + urlSuffixAndSep 110 expectedUsername := baseUsername + c 111 u, err := url.Parse(s) 112 if err != nil { 113 t.Fatal("Failed to parse url with encoded reserved character. url:", s) 114 } 115 if u.User == nil { 116 t.Fatal("Failed to parse userinfo with encoded reserve character. url:", s) 117 } 118 if username := u.User.Username(); username != expectedUsername { 119 t.Fatal("Got unexpected username:", username, "!=", expectedUsername) 120 } 121 }) 122 } 123 } 124 125 // TestPasswordUnencodedReservedURLChars documents the behavior of using unencoded reserved characters in passwords 126 // with net/url Parse() 127 func TestPasswordUnencodedReservedURLChars(t *testing.T) { 128 username := baseUsername 129 schemeAndUsernameAndSep := "database://" + username + ":" 130 basePassword := "password" 131 urlSuffixAndSep := "@localhost:12345/myDB?someParam=true" 132 133 testcases := []struct { 134 char string 135 parses bool 136 expectedUsername string // empty string means that the username failed to parse 137 expectedPassword string // empty string means that the password failed to parse 138 encodedURL string 139 }{ 140 {char: "!", parses: true, expectedUsername: username, expectedPassword: basePassword + "!", 141 encodedURL: schemeAndUsernameAndSep + basePassword + "%21" + urlSuffixAndSep}, 142 {char: "#", parses: false}, 143 {char: "$", parses: true, expectedUsername: username, expectedPassword: basePassword + "$", 144 encodedURL: schemeAndUsernameAndSep + basePassword + "$" + urlSuffixAndSep}, 145 {char: "%", parses: false}, 146 {char: "&", parses: true, expectedUsername: username, expectedPassword: basePassword + "&", 147 encodedURL: schemeAndUsernameAndSep + basePassword + "&" + urlSuffixAndSep}, 148 {char: "'", parses: true, expectedUsername: username, expectedPassword: "password'", 149 encodedURL: schemeAndUsernameAndSep + basePassword + "%27" + urlSuffixAndSep}, 150 {char: "(", parses: true, expectedUsername: username, expectedPassword: "password(", 151 encodedURL: schemeAndUsernameAndSep + basePassword + "%28" + urlSuffixAndSep}, 152 {char: ")", parses: true, expectedUsername: username, expectedPassword: "password)", 153 encodedURL: schemeAndUsernameAndSep + basePassword + "%29" + urlSuffixAndSep}, 154 {char: "*", parses: true, expectedUsername: username, expectedPassword: "password*", 155 encodedURL: schemeAndUsernameAndSep + basePassword + "%2A" + urlSuffixAndSep}, 156 {char: "+", parses: true, expectedUsername: username, expectedPassword: "password+", 157 encodedURL: schemeAndUsernameAndSep + basePassword + "+" + urlSuffixAndSep}, 158 {char: ",", parses: true, expectedUsername: username, expectedPassword: "password,", 159 encodedURL: schemeAndUsernameAndSep + basePassword + "," + urlSuffixAndSep}, 160 {char: "/", parses: false}, 161 {char: ":", parses: true, expectedUsername: username, expectedPassword: "password:", 162 encodedURL: schemeAndUsernameAndSep + basePassword + "%3A" + urlSuffixAndSep}, 163 {char: ";", parses: true, expectedUsername: username, expectedPassword: "password;", 164 encodedURL: schemeAndUsernameAndSep + basePassword + ";" + urlSuffixAndSep}, 165 {char: "=", parses: true, expectedUsername: username, expectedPassword: "password=", 166 encodedURL: schemeAndUsernameAndSep + basePassword + "=" + urlSuffixAndSep}, 167 {char: "?", parses: false}, 168 {char: "@", parses: true, expectedUsername: username, expectedPassword: "password@", 169 encodedURL: schemeAndUsernameAndSep + basePassword + "%40" + urlSuffixAndSep}, 170 {char: "[", parses: false}, 171 {char: "]", parses: false}, 172 } 173 174 testedChars := make([]string, 0, len(reservedChars)) 175 for _, tc := range testcases { 176 testedChars = append(testedChars, tc.char) 177 t.Run("reserved char "+tc.char, func(t *testing.T) { 178 s := schemeAndUsernameAndSep + basePassword + tc.char + urlSuffixAndSep 179 u, err := url.Parse(s) 180 if err == nil { 181 if !tc.parses { 182 t.Error("Unexpectedly parsed reserved character. url:", s) 183 return 184 } 185 var username, password string 186 if u.User != nil { 187 username = u.User.Username() 188 password, _ = u.User.Password() 189 } 190 if username != tc.expectedUsername { 191 t.Error("Got unexpected username:", username, "!=", tc.expectedUsername) 192 } 193 if password != tc.expectedPassword { 194 t.Error("Got unexpected password:", password, "!=", tc.expectedPassword) 195 } 196 if s := u.String(); s != tc.encodedURL { 197 t.Error("Got unexpected encoded URL:", s, "!=", tc.encodedURL) 198 } 199 } else { 200 if tc.parses { 201 t.Error("Failed to parse reserved character. url:", s) 202 } 203 } 204 }) 205 } 206 207 t.Run("All reserved chars tested", func(t *testing.T) { 208 if s := strings.Join(testedChars, ""); s != reservedChars { 209 t.Error("Not all reserved URL characters were tested:", s, "!=", reservedChars) 210 } 211 }) 212 } 213 214 func TestPasswordEncodedReservedURLChars(t *testing.T) { 215 username := baseUsername 216 schemeAndUsernameAndSep := "database://" + username + ":" 217 basePassword := "password" 218 urlSuffixAndSep := "@localhost:12345/myDB?someParam=true" 219 220 for _, c := range reservedChars { 221 c := string(c) 222 t.Run("reserved char "+c, func(t *testing.T) { 223 encodedChar := "%" + hex.EncodeToString([]byte(c)) 224 s := schemeAndUsernameAndSep + basePassword + encodedChar + urlSuffixAndSep 225 expectedPassword := basePassword + c 226 u, err := url.Parse(s) 227 if err != nil { 228 t.Fatal("Failed to parse url with encoded reserved character. url:", s) 229 } 230 if u.User == nil { 231 t.Fatal("Failed to parse userinfo with encoded reserve character. url:", s) 232 } 233 if n := u.User.Username(); n != username { 234 t.Fatal("Got unexpected username:", n, "!=", username) 235 } 236 if p, _ := u.User.Password(); p != expectedPassword { 237 t.Fatal("Got unexpected password:", p, "!=", expectedPassword) 238 } 239 }) 240 } 241 }