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  }