github.com/google/go-safeweb@v0.0.0-20231219055052-64d8cfc90fbb/internal/requesttesting/queryparams/query_params_test.go (about) 1 // Copyright 2020 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package queryparams_test provides tests to inspect the behaviour of query 16 // parameters parsing in HTTP requests. 17 package queryparams_test 18 19 import ( 20 "bytes" 21 "context" 22 "net/http" 23 "net/url" 24 "testing" 25 26 "github.com/google/go-cmp/cmp" 27 "github.com/google/go-safeweb/internal/requesttesting" 28 ) 29 30 const status200OK = "HTTP/1.1 200 OK\r\n" 31 const status400BadReq = "HTTP/1.1 400 Bad Request\r\n" 32 33 // Ensures Query() only returns a map of size one and verifies whether sending a 34 // request with two values for the same key returns a []string of length 2 35 // containing the correct values 36 func TestMultipleQueryParametersSameKey(t *testing.T) { 37 const req = "GET /?vegetable=potatO&vegetable=Tomato HTTP/1.1\r\n" + 38 "Host: localhost:8080\r\n" + 39 "\r\n" 40 resp, err := requesttesting.MakeRequest(context.Background(), []byte(req), func(req *http.Request) { 41 want := url.Values{"vegetable": []string{"potatO", "Tomato"}} 42 got := req.URL.Query() 43 if diff := cmp.Diff(want, got); diff != "" { 44 t.Errorf("req.URL.Query(): got %v, want %v, diff (-want +got):\n%s", got, want, diff) 45 } 46 }) 47 if err != nil { 48 t.Fatalf("MakeRequest(): got err %v, want nil", err) 49 } 50 if !bytes.HasPrefix(resp, []byte(status200OK)) { 51 t.Errorf("response status: got %s, want %s", resp, status200OK) 52 } 53 } 54 55 func TestQueryParametersSameKeyDifferentCasing(t *testing.T) { 56 req := []byte("GET /?vegetable=potato&Vegetable=tomato HTTP/1.1\r\n" + 57 "Host: localhost:8080\r\n" + 58 "\r\n") 59 resp, err := requesttesting.MakeRequest(context.Background(), req, func(req *http.Request) { 60 want := url.Values{ 61 "vegetable": []string{"potato"}, 62 "Vegetable": []string{"tomato"}, 63 } 64 got := req.URL.Query() 65 if diff := cmp.Diff(want, got); diff != "" { 66 t.Errorf("req.URL.Query(): got %v, want %v, diff (-want +got): \n%s", got, want, diff) 67 } 68 }) 69 if err != nil { 70 t.Fatalf("MakeRequest(): got err %v, want nil", err) 71 } 72 if !bytes.HasPrefix(resp, []byte(status200OK)) { 73 t.Errorf("response status: got %s, want %s", resp, status200OK) 74 } 75 } 76 77 // TestQueryParametersValidUnicode ensures keys and values that contain non-ASCII characters are parsed correctly 78 func TestQueryParametersValidUnicode(t *testing.T) { 79 req := []byte("GET /?vegetable=ăȚâȘî HTTP/1.1\r\n" + 80 "Host: localhost:8080\r\n" + 81 "\r\n") 82 resp, err := requesttesting.MakeRequest(context.Background(), req, func(req *http.Request) { 83 want := url.Values{"vegetable": []string{"ăȚâȘî"}} 84 got := req.URL.Query() 85 if diff := cmp.Diff(want, got); diff != "" { 86 t.Errorf("req.URL.Query(): got %v, want %v, diff (-want +got):\n%s", got, want, diff) 87 } 88 }) 89 if err != nil { 90 t.Fatalf("MakeRequest(): got err %v, want nil", err) 91 } 92 if !bytes.HasPrefix(resp, []byte(status200OK)) { 93 t.Errorf("response status: got %s, want %s", resp, status200OK) 94 } 95 96 req = []byte("GET /?ăȚâȘî=vegetable HTTP/1.1\r\n" + 97 "Host: localhost:8080\r\n" + 98 "\r\n") 99 resp, err = requesttesting.MakeRequest(context.Background(), req, func(req *http.Request) { 100 want := url.Values{"ăȚâȘî": []string{"vegetable"}} 101 got := req.URL.Query() 102 if diff := cmp.Diff(want, got); diff != "" { 103 t.Errorf("req.URL.Query(): got %v, want %v, diff (-want +got):\n%s", got, want, diff) 104 } 105 }) 106 if err != nil { 107 t.Fatalf("MakeRequest(): got err %v, want nil", err) 108 } 109 if !bytes.HasPrefix(resp, []byte(status200OK)) { 110 t.Errorf("response status: got %s, want %s", resp, status200OK) 111 } 112 } 113 114 // Tests whether passing invalid Unicode in both key and value will result in a 400 Bad Request response 115 func TestQueryParametersInvalidUnicodes(t *testing.T) { 116 117 tests := []struct { 118 name string 119 req []byte 120 }{ 121 { 122 name: "Invalid Key", 123 req: []byte("GET /?\x0F=tomato&Vegetable=potato HTTP/1.1\r\n" + "Host: localhost:8080\r\n" + 124 "\r\n"), 125 }, 126 { 127 name: "Invalid value", 128 req: []byte("GET /?vegetable=\x0F&Vegetable=potato HTTP/1.1\r\n" + "Host: localhost:8080\r\n" + 129 "\r\n"), 130 }, 131 } 132 133 for _, test := range tests { 134 t.Run(test.name, func(t *testing.T) { 135 resp, err := requesttesting.MakeRequest(context.Background(), test.req, func(req *http.Request) { 136 t.Error("expected handler not to be called.") 137 }) 138 if err != nil { 139 t.Fatalf("MakeRequest(): got err %v, want nil", err) 140 } 141 if !bytes.HasPrefix(resp, []byte(status400BadReq)) { 142 t.Errorf("response status: got %s, want %s", resp, status400BadReq) 143 } 144 }) 145 } 146 147 } 148 149 // Verifies behaviour of query parameter parser when passing malformed keys or 150 // values, by breaking URL percent-encoding. Percent-encoding is used to encode 151 // special characters in URL 152 func TestQueryParametersBreakUrlEncoding(t *testing.T) { 153 tests := []struct { 154 name string 155 req []byte 156 }{ 157 { 158 name: "Broken Key", 159 req: []byte("GET /?vegetable%=tomato&Vegetable=potato HTTP/1.1\r\n" + "Host: localhost:8080\r\n" + 160 "\r\n"), 161 }, 162 { 163 name: "Broken value", 164 req: []byte("GET /?vegetable=tomato%&Vegetable=potato HTTP/1.1\r\n" + "Host: localhost:8080\r\n" + 165 "\r\n"), 166 }, 167 } 168 169 for _, test := range tests { 170 t.Run(test.name, func(t *testing.T) { 171 resp, err := requesttesting.MakeRequest(context.Background(), test.req, func(req *http.Request) { 172 var want []string 173 got := req.URL.Query()["vegetable"] 174 if diff := cmp.Diff(want, got); diff != "" { 175 t.Errorf(`URL.Query()["vegetable"]:, got %v, want %v`, req.URL.Query()["vegetable"], want) 176 } 177 want = []string{"potato"} 178 got = req.URL.Query()["Vegetable"] 179 if diff := cmp.Diff(want, got); diff != "" { 180 t.Errorf(`URL.Query()["Vegetable"]:, got %v, want %v`, req.URL.Query()["vegetable"], want) 181 } 182 }) 183 if err != nil { 184 t.Fatalf("MakeRequest(): got err %v, want nil", err) 185 } 186 if !bytes.HasPrefix(resp, []byte(status200OK)) { 187 t.Errorf("response status: got %s, want %s", resp, status200OK) 188 } 189 }) 190 } 191 192 } 193 194 // Test whether both + and %20 are interpreted as space. Having a space in the 195 // actual request will not be escaped and will result in a 400 196 func TestQueryParametersSpaceBehaviour(t *testing.T) { 197 tests := []struct { 198 name string 199 req []byte 200 want string 201 }{ 202 { 203 name: "+ as encoding for space", 204 req: []byte("GET /?vegetable=potato+pear&Vegetable=tomato HTTP/1.1\r\n" + 205 "Host: localhost:8080\r\n" + 206 "\r\n"), 207 want: "potato pear", 208 }, 209 { 210 name: "%20 as encoding for space", 211 req: []byte("GET /?vegetable=potato%20pear&Vegetable=tomato HTTP/1.1\r\n" + 212 "Host: localhost:8080\r\n" + 213 "\r\n"), 214 want: "potato pear", 215 }, 216 } 217 218 for _, test := range tests { 219 t.Run(test.name, func(t *testing.T) { 220 resp, err := requesttesting.MakeRequest(context.Background(), test.req, func(req *http.Request) { 221 if want := []string{"potato pear"}; !cmp.Equal(want, req.URL.Query()["vegetable"]) { 222 t.Errorf("URL.Query():, got %v, want %v", req.URL.Query()["vegetable"], want) 223 } 224 }) 225 if err != nil { 226 t.Fatalf("MakeRequest(): got err %v, want nil", err) 227 } 228 if !bytes.HasPrefix(resp, []byte(status200OK)) { 229 t.Errorf("response status: got %s, want %s", resp, status200OK) 230 } 231 }) 232 } 233 234 req := []byte("GET /?vegetable=potato pear&Vegetable=tomato HTTP/1.1\r\n" + 235 "Host: localhost:8080\r\n" + 236 "\r\n") 237 resp, err := requesttesting.MakeRequest(context.Background(), req, func(req *http.Request) { 238 t.Fatal("expected handler not to be called with request containing invalid Unicode.") 239 }) 240 if err != nil { 241 t.Fatalf("MakeRequest(): got err %v, want nil", err) 242 } 243 if !bytes.HasPrefix(resp, []byte(status400BadReq)) { 244 t.Errorf("response status: got %s, want %s", resp, status400BadReq) 245 } 246 }