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  }