github.com/mikelsr/quic-go@v0.36.1-0.20230701132136-1d9415b66898/http3/request_test.go (about)

     1  package http3
     2  
     3  import (
     4  	"net/http"
     5  	"net/url"
     6  
     7  	. "github.com/onsi/ginkgo/v2"
     8  	. "github.com/onsi/gomega"
     9  	"github.com/quic-go/qpack"
    10  )
    11  
    12  var _ = Describe("Request", func() {
    13  	It("populates request", func() {
    14  		headers := []qpack.HeaderField{
    15  			{Name: ":path", Value: "/foo"},
    16  			{Name: ":authority", Value: "quic.clemente.io"},
    17  			{Name: ":method", Value: "GET"},
    18  			{Name: "content-length", Value: "42"},
    19  		}
    20  		req, err := requestFromHeaders(headers)
    21  		Expect(err).NotTo(HaveOccurred())
    22  		Expect(req.Method).To(Equal("GET"))
    23  		Expect(req.URL.Path).To(Equal("/foo"))
    24  		Expect(req.URL.Host).To(BeEmpty())
    25  		Expect(req.Proto).To(Equal("HTTP/3.0"))
    26  		Expect(req.ProtoMajor).To(Equal(3))
    27  		Expect(req.ProtoMinor).To(BeZero())
    28  		Expect(req.ContentLength).To(Equal(int64(42)))
    29  		Expect(req.Header).To(BeEmpty())
    30  		Expect(req.Body).To(BeNil())
    31  		Expect(req.Host).To(Equal("quic.clemente.io"))
    32  		Expect(req.RequestURI).To(Equal("/foo"))
    33  	})
    34  
    35  	It("parses path with leading double slashes", func() {
    36  		headers := []qpack.HeaderField{
    37  			{Name: ":path", Value: "//foo"},
    38  			{Name: ":authority", Value: "quic.clemente.io"},
    39  			{Name: ":method", Value: "GET"},
    40  		}
    41  		req, err := requestFromHeaders(headers)
    42  		Expect(err).NotTo(HaveOccurred())
    43  		Expect(req.Header).To(BeEmpty())
    44  		Expect(req.Body).To(BeNil())
    45  		Expect(req.URL.Path).To(Equal("//foo"))
    46  		Expect(req.URL.Host).To(BeEmpty())
    47  		Expect(req.Host).To(Equal("quic.clemente.io"))
    48  		Expect(req.RequestURI).To(Equal("//foo"))
    49  	})
    50  
    51  	It("concatenates the cookie headers", func() {
    52  		headers := []qpack.HeaderField{
    53  			{Name: ":path", Value: "/foo"},
    54  			{Name: ":authority", Value: "quic.clemente.io"},
    55  			{Name: ":method", Value: "GET"},
    56  			{Name: "cookie", Value: "cookie1=foobar1"},
    57  			{Name: "cookie", Value: "cookie2=foobar2"},
    58  		}
    59  		req, err := requestFromHeaders(headers)
    60  		Expect(err).NotTo(HaveOccurred())
    61  		Expect(req.Header).To(Equal(http.Header{
    62  			"Cookie": []string{"cookie1=foobar1; cookie2=foobar2"},
    63  		}))
    64  	})
    65  
    66  	It("handles Other headers", func() {
    67  		headers := []qpack.HeaderField{
    68  			{Name: ":path", Value: "/foo"},
    69  			{Name: ":authority", Value: "quic.clemente.io"},
    70  			{Name: ":method", Value: "GET"},
    71  			{Name: "cache-control", Value: "max-age=0"},
    72  			{Name: "duplicate-header", Value: "1"},
    73  			{Name: "duplicate-header", Value: "2"},
    74  		}
    75  		req, err := requestFromHeaders(headers)
    76  		Expect(err).NotTo(HaveOccurred())
    77  		Expect(req.Header).To(Equal(http.Header{
    78  			"Cache-Control":    []string{"max-age=0"},
    79  			"Duplicate-Header": []string{"1", "2"},
    80  		}))
    81  	})
    82  
    83  	It("errors with missing path", func() {
    84  		headers := []qpack.HeaderField{
    85  			{Name: ":authority", Value: "quic.clemente.io"},
    86  			{Name: ":method", Value: "GET"},
    87  		}
    88  		_, err := requestFromHeaders(headers)
    89  		Expect(err).To(MatchError(":path, :authority and :method must not be empty"))
    90  	})
    91  
    92  	It("errors with missing method", func() {
    93  		headers := []qpack.HeaderField{
    94  			{Name: ":path", Value: "/foo"},
    95  			{Name: ":authority", Value: "quic.clemente.io"},
    96  		}
    97  		_, err := requestFromHeaders(headers)
    98  		Expect(err).To(MatchError(":path, :authority and :method must not be empty"))
    99  	})
   100  
   101  	It("errors with missing authority", func() {
   102  		headers := []qpack.HeaderField{
   103  			{Name: ":path", Value: "/foo"},
   104  			{Name: ":method", Value: "GET"},
   105  		}
   106  		_, err := requestFromHeaders(headers)
   107  		Expect(err).To(MatchError(":path, :authority and :method must not be empty"))
   108  	})
   109  
   110  	Context("regular HTTP CONNECT", func() {
   111  		It("handles CONNECT method", func() {
   112  			headers := []qpack.HeaderField{
   113  				{Name: ":authority", Value: "quic.clemente.io"},
   114  				{Name: ":method", Value: http.MethodConnect},
   115  			}
   116  			req, err := requestFromHeaders(headers)
   117  			Expect(err).NotTo(HaveOccurred())
   118  			Expect(req.Method).To(Equal(http.MethodConnect))
   119  			Expect(req.RequestURI).To(Equal("quic.clemente.io"))
   120  		})
   121  
   122  		It("errors with missing authority in CONNECT method", func() {
   123  			headers := []qpack.HeaderField{
   124  				{Name: ":method", Value: http.MethodConnect},
   125  			}
   126  			_, err := requestFromHeaders(headers)
   127  			Expect(err).To(MatchError(":path must be empty and :authority must not be empty"))
   128  		})
   129  
   130  		It("errors with extra path in CONNECT method", func() {
   131  			headers := []qpack.HeaderField{
   132  				{Name: ":path", Value: "/foo"},
   133  				{Name: ":authority", Value: "quic.clemente.io"},
   134  				{Name: ":method", Value: http.MethodConnect},
   135  			}
   136  			_, err := requestFromHeaders(headers)
   137  			Expect(err).To(MatchError(":path must be empty and :authority must not be empty"))
   138  		})
   139  	})
   140  
   141  	Context("Extended CONNECT", func() {
   142  		It("handles Extended CONNECT method", func() {
   143  			headers := []qpack.HeaderField{
   144  				{Name: ":protocol", Value: "webtransport"},
   145  				{Name: ":scheme", Value: "ftp"},
   146  				{Name: ":method", Value: http.MethodConnect},
   147  				{Name: ":authority", Value: "quic.clemente.io"},
   148  				{Name: ":path", Value: "/foo?val=1337"},
   149  			}
   150  			req, err := requestFromHeaders(headers)
   151  			Expect(err).NotTo(HaveOccurred())
   152  			Expect(req.Method).To(Equal(http.MethodConnect))
   153  			Expect(req.Proto).To(Equal("webtransport"))
   154  			Expect(req.URL.String()).To(Equal("ftp://quic.clemente.io/foo?val=1337"))
   155  			Expect(req.URL.Query().Get("val")).To(Equal("1337"))
   156  		})
   157  
   158  		It("errors with missing scheme", func() {
   159  			headers := []qpack.HeaderField{
   160  				{Name: ":protocol", Value: "webtransport"},
   161  				{Name: ":method", Value: http.MethodConnect},
   162  				{Name: ":authority", Value: "quic.clemente.io"},
   163  				{Name: ":path", Value: "/foo"},
   164  			}
   165  			_, err := requestFromHeaders(headers)
   166  			Expect(err).To(MatchError("extended CONNECT: :scheme, :path and :authority must not be empty"))
   167  		})
   168  	})
   169  
   170  	Context("extracting the hostname from a request", func() {
   171  		var url *url.URL
   172  
   173  		BeforeEach(func() {
   174  			var err error
   175  			url, err = url.Parse("https://quic.clemente.io:1337")
   176  			Expect(err).ToNot(HaveOccurred())
   177  		})
   178  
   179  		It("uses req.URL.Host", func() {
   180  			req := &http.Request{URL: url}
   181  			Expect(hostnameFromRequest(req)).To(Equal("quic.clemente.io:1337"))
   182  		})
   183  
   184  		It("uses req.URL.Host even if req.Host is available", func() {
   185  			req := &http.Request{
   186  				Host: "www.example.org",
   187  				URL:  url,
   188  			}
   189  			Expect(hostnameFromRequest(req)).To(Equal("quic.clemente.io:1337"))
   190  		})
   191  
   192  		It("returns an empty hostname if nothing is set", func() {
   193  			Expect(hostnameFromRequest(&http.Request{})).To(BeEmpty())
   194  		})
   195  	})
   196  })