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  }