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