From 49d7ec86e9004b03cf5b9413969bc57395309dee Mon Sep 17 00:00:00 2001 From: hellodword <46193371+hellodword@users.noreply.github.com> Date: Wed, 9 Mar 2022 12:39:33 +0800 Subject: [PATCH] add DoH3 --- go.mod | 1 + go.sum | 1 + upstream/upstream.go | 2 + upstream/upstream_doh3.go | 200 +++++++ upstream/upstream_doh3_test.go | 30 + .../lucas-clemente/quic-go/http3/body.go | 98 +++ .../lucas-clemente/quic-go/http3/client.go | 352 +++++++++++ .../quic-go/http3/error_codes.go | 70 +++ .../lucas-clemente/quic-go/http3/frames.go | 142 +++++ .../quic-go/http3/gzip_reader.go | 39 ++ .../lucas-clemente/quic-go/http3/request.go | 92 +++ .../quic-go/http3/request_writer.go | 319 ++++++++++ .../quic-go/http3/response_writer.go | 135 +++++ .../lucas-clemente/quic-go/http3/roundtrip.go | 198 ++++++ .../lucas-clemente/quic-go/http3/server.go | 565 ++++++++++++++++++ .../marten-seemann/qpack/.codecov.yml | 7 + .../marten-seemann/qpack/.gitignore | 6 + .../marten-seemann/qpack/.gitmodules | 3 + .../marten-seemann/qpack/.golangci.yml | 27 + .../marten-seemann/qpack/LICENSE.md | 7 + .../github.com/marten-seemann/qpack/README.md | 21 + .../marten-seemann/qpack/decoder.go | 271 +++++++++ .../marten-seemann/qpack/encoder.go | 95 +++ .../marten-seemann/qpack/header_field.go | 16 + .../marten-seemann/qpack/static_table.go | 254 ++++++++ .../github.com/marten-seemann/qpack/varint.go | 66 ++ vendor/modules.txt | 4 + 27 files changed, 3021 insertions(+) create mode 100644 upstream/upstream_doh3.go create mode 100644 upstream/upstream_doh3_test.go create mode 100644 vendor/github.com/lucas-clemente/quic-go/http3/body.go create mode 100644 vendor/github.com/lucas-clemente/quic-go/http3/client.go create mode 100644 vendor/github.com/lucas-clemente/quic-go/http3/error_codes.go create mode 100644 vendor/github.com/lucas-clemente/quic-go/http3/frames.go create mode 100644 vendor/github.com/lucas-clemente/quic-go/http3/gzip_reader.go create mode 100644 vendor/github.com/lucas-clemente/quic-go/http3/request.go create mode 100644 vendor/github.com/lucas-clemente/quic-go/http3/request_writer.go create mode 100644 vendor/github.com/lucas-clemente/quic-go/http3/response_writer.go create mode 100644 vendor/github.com/lucas-clemente/quic-go/http3/roundtrip.go create mode 100644 vendor/github.com/lucas-clemente/quic-go/http3/server.go create mode 100644 vendor/github.com/marten-seemann/qpack/.codecov.yml create mode 100644 vendor/github.com/marten-seemann/qpack/.gitignore create mode 100644 vendor/github.com/marten-seemann/qpack/.gitmodules create mode 100644 vendor/github.com/marten-seemann/qpack/.golangci.yml create mode 100644 vendor/github.com/marten-seemann/qpack/LICENSE.md create mode 100644 vendor/github.com/marten-seemann/qpack/README.md create mode 100644 vendor/github.com/marten-seemann/qpack/decoder.go create mode 100644 vendor/github.com/marten-seemann/qpack/encoder.go create mode 100644 vendor/github.com/marten-seemann/qpack/header_field.go create mode 100644 vendor/github.com/marten-seemann/qpack/static_table.go create mode 100644 vendor/github.com/marten-seemann/qpack/varint.go diff --git a/go.mod b/go.mod index da91b005e..1c9a05e8f 100644 --- a/go.mod +++ b/go.mod @@ -24,6 +24,7 @@ require ( github.com/fsnotify/fsnotify v1.5.1 // indirect github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect github.com/kr/text v0.2.0 // indirect + github.com/marten-seemann/qpack v0.2.1 // indirect github.com/marten-seemann/qtls-go1-16 v0.1.4 // indirect github.com/marten-seemann/qtls-go1-17 v0.1.0 // indirect github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect diff --git a/go.sum b/go.sum index 52d82e229..161964e38 100644 --- a/go.sum +++ b/go.sum @@ -98,6 +98,7 @@ github.com/lucas-clemente/quic-go v0.24.0 h1:ToR7SIIEdrgOhgVTHvPgdVRJfgVy+N0wQAa github.com/lucas-clemente/quic-go v0.24.0/go.mod h1:paZuzjXCE5mj6sikVLMvqXk8lJV2AsqtJ6bDhjEfxx0= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/marten-seemann/qpack v0.2.1 h1:jvTsT/HpCn2UZJdP+UUB53FfUUgeOyG5K1ns0OJOGVs= github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc= github.com/marten-seemann/qtls-go1-15 v0.1.4/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= github.com/marten-seemann/qtls-go1-16 v0.1.4 h1:xbHbOGGhrenVtII6Co8akhLEdrawwB2iHl5yhJRpnco= diff --git a/upstream/upstream.go b/upstream/upstream.go index 4ac183d75..9dac3cf90 100644 --- a/upstream/upstream.go +++ b/upstream/upstream.go @@ -143,6 +143,8 @@ func urlToUpstream(uu *url.URL, opts *Options) (u Upstream, err error) { return newDoT(uu, opts) case "https": return newDoH(uu, opts) + case "h3": + return newDoH3(uu, opts) default: return nil, fmt.Errorf("unsupported url scheme: %s", sch) } diff --git a/upstream/upstream_doh3.go b/upstream/upstream_doh3.go new file mode 100644 index 000000000..6d2a1ffb3 --- /dev/null +++ b/upstream/upstream_doh3.go @@ -0,0 +1,200 @@ +package upstream + +import ( + "context" + "crypto/tls" + "encoding/base64" + "fmt" + "io" + "net" + "net/http" + "net/url" + "os" + "sync" + "time" + + "github.com/AdguardTeam/golibs/errors" + "github.com/lucas-clemente/quic-go" + "github.com/lucas-clemente/quic-go/http3" + "github.com/miekg/dns" +) + +// dnsOverHTTP3 represents DNS-over-HTTP/3 upstream. +type dnsOverHTTP3 struct { + boot *bootstrapper + + client *http.Client + clientGuard sync.Mutex +} + +// type check +var _ Upstream = &dnsOverHTTP3{} + +// newDoH3 returns the DNS-over-HTTP3 Upstream. +func newDoH3(uu *url.URL, opts *Options) (u Upstream, err error) { + addPort(uu, defaultPortDoH) + + var b *bootstrapper + b, err = urlToBoot(uu, opts) + if err != nil { + return nil, fmt.Errorf("creating https bootstrapper: %w", err) + } + + return &dnsOverHTTP3{boot: b}, nil +} + +func (p *dnsOverHTTP3) Address() string { return p.boot.URL.String() } + +func (p *dnsOverHTTP3) Exchange(m *dns.Msg) (*dns.Msg, error) { + client, err := p.getClient() + if err != nil { + return nil, fmt.Errorf("initializing http client: %w", err) + } + + logBegin(p.Address(), m) + r, err := p.exchangeHTTP3Client(m, client) + logFinish(p.Address(), err) + + return r, err +} + +// exchangeHTTP3Client sends the DNS query to a DOH3 resolver using the specified +// http.Client instance. +func (p *dnsOverHTTP3) exchangeHTTP3Client(m *dns.Msg, client *http.Client) (*dns.Msg, error) { + buf, err := m.Pack() + if err != nil { + return nil, fmt.Errorf("packing message: %w", err) + } + + requestURL := p.Address() + "?dns=" + base64.RawURLEncoding.EncodeToString(buf) + u, err := url.Parse(requestURL) + if err != nil { + return nil, fmt.Errorf("parse request URL: %w", err) + } + + u.Scheme = "https" + req, err := http.NewRequest("GET", u.String(), nil) + if err != nil { + return nil, fmt.Errorf("creating http request to %s: %w", p.boot.URL, err) + } + + req.Header.Set("Accept", "application/dns-message") + + resp, err := client.Do(req) + if resp != nil && resp.Body != nil { + defer resp.Body.Close() + } + if err != nil { + if errors.Is(err, os.ErrDeadlineExceeded) { + // If this is a timeout error, trying to forcibly re-create the HTTP + // client instance. + // + // See https://github.com/AdguardTeam/AdGuardHome/issues/3217. + p.clientGuard.Lock() + p.client = nil + p.clientGuard.Unlock() + } + + return nil, fmt.Errorf("requesting %s: %w", p.boot.URL, err) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("reading %s: %w", p.boot.URL, err) + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("got status code %d from %s", resp.StatusCode, p.boot.URL) + } + + response := dns.Msg{} + err = response.Unpack(body) + if err != nil { + return nil, fmt.Errorf("unpacking response from %s: body is %s: %w", p.boot.URL, string(body), err) + } + + if response.Id != m.Id { + err = dns.ErrId + } + + return &response, err +} + +// getClient gets or lazily initializes an HTTP client (and transport) that will +// be used for this DOH resolver. +func (p *dnsOverHTTP3) getClient() (c *http.Client, err error) { + startTime := time.Now() + + p.clientGuard.Lock() + defer p.clientGuard.Unlock() + if p.client != nil { + return p.client, nil + } + + // Timeout can be exceeded while waiting for the lock + // This happens quite often on mobile devices + elapsed := time.Since(startTime) + if p.boot.options.Timeout > 0 && elapsed > p.boot.options.Timeout { + return nil, fmt.Errorf("timeout exceeded: %s", elapsed) + } + + p.client, err = p.createClient() + + return p.client, err +} + +func (p *dnsOverHTTP3) createClient() (*http.Client, error) { + transport, err := p.createTransport() + if err != nil { + return nil, fmt.Errorf("initializing http transport: %w", err) + } + + client := &http.Client{ + Transport: transport, + Timeout: p.boot.options.Timeout, + Jar: nil, + } + + p.client = client + return p.client, nil +} + +// createTransport initializes an HTTP transport that will be used specifically +// for this DOH3 resolver. This HTTP transport ensures that the HTTP requests +// will be sent exactly to the IP address got from the bootstrap resolver. +func (p *dnsOverHTTP3) createTransport() (http.RoundTripper, error) { + tlsConfig, dialContext, err := p.boot.get() + if err != nil { + return nil, fmt.Errorf("bootstrapping %s: %w", p.boot.URL, err) + } + + quicConfig := &quic.Config{ + HandshakeIdleTimeout: handshakeTimeout, + } + + transport := &http3.RoundTripper{ + DisableCompression: true, + TLSClientConfig: tlsConfig, + QuicConfig: quicConfig, + Dial: func(network, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlySession, error) { + // we're using bootstrapped address instead of what's passed to the function + // it does not create an actual connection, but it helps us determine + // what IP is actually reachable (when there're v4/v6 addresses) + rawConn, e := dialContext(context.Background(), "udp", "") + if e != nil { + return nil, e + } + // It's never actually used + _ = rawConn.Close() + + udpConn, ok := rawConn.(*net.UDPConn) + if !ok { + return nil, fmt.Errorf("failed to open connection to %s", p.Address()) + } + + return quic.DialAddrEarly(udpConn.RemoteAddr().String(), tlsCfg, cfg) + }, + } + + return transport, nil +} diff --git a/upstream/upstream_doh3_test.go b/upstream/upstream_doh3_test.go new file mode 100644 index 000000000..670ed88aa --- /dev/null +++ b/upstream/upstream_doh3_test.go @@ -0,0 +1,30 @@ +package upstream + +import ( + "net/http" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestUpstreamDOH3(t *testing.T) { + // Create a DNS-over-HTTP3 upstream + address := "h3://dns.google/dns-query" + u, err := AddressToUpstream(address, &Options{InsecureSkipVerify: true}) + assert.Nil(t, err) + + uq := u.(*dnsOverHTTP3) + var client *http.Client + + // Test that it responds properly + for i := 0; i < 10; i++ { + checkUpstream(t, u, address) + + if client == nil { + client = uq.client + } else { + // This way we test that the client is properly reused + assert.True(t, client == uq.client) + } + } +} diff --git a/vendor/github.com/lucas-clemente/quic-go/http3/body.go b/vendor/github.com/lucas-clemente/quic-go/http3/body.go new file mode 100644 index 000000000..851eaa1f4 --- /dev/null +++ b/vendor/github.com/lucas-clemente/quic-go/http3/body.go @@ -0,0 +1,98 @@ +package http3 + +import ( + "fmt" + "io" + + "github.com/lucas-clemente/quic-go" +) + +// The body of a http.Request or http.Response. +type body struct { + str quic.Stream + + // only set for the http.Response + // The channel is closed when the user is done with this response: + // either when Read() errors, or when Close() is called. + reqDone chan<- struct{} + reqDoneClosed bool + + onFrameError func() + + bytesRemainingInFrame uint64 +} + +var _ io.ReadCloser = &body{} + +func newRequestBody(str quic.Stream, onFrameError func()) *body { + return &body{ + str: str, + onFrameError: onFrameError, + } +} + +func newResponseBody(str quic.Stream, done chan<- struct{}, onFrameError func()) *body { + return &body{ + str: str, + onFrameError: onFrameError, + reqDone: done, + } +} + +func (r *body) Read(b []byte) (int, error) { + n, err := r.readImpl(b) + if err != nil { + r.requestDone() + } + return n, err +} + +func (r *body) readImpl(b []byte) (int, error) { + if r.bytesRemainingInFrame == 0 { + parseLoop: + for { + frame, err := parseNextFrame(r.str) + if err != nil { + return 0, err + } + switch f := frame.(type) { + case *headersFrame: + // skip HEADERS frames + continue + case *dataFrame: + r.bytesRemainingInFrame = f.Length + break parseLoop + default: + r.onFrameError() + // parseNextFrame skips over unknown frame types + // Therefore, this condition is only entered when we parsed another known frame type. + return 0, fmt.Errorf("peer sent an unexpected frame: %T", f) + } + } + } + + var n int + var err error + if r.bytesRemainingInFrame < uint64(len(b)) { + n, err = r.str.Read(b[:r.bytesRemainingInFrame]) + } else { + n, err = r.str.Read(b) + } + r.bytesRemainingInFrame -= uint64(n) + return n, err +} + +func (r *body) requestDone() { + if r.reqDoneClosed || r.reqDone == nil { + return + } + close(r.reqDone) + r.reqDoneClosed = true +} + +func (r *body) Close() error { + r.requestDone() + // If the EOF was read, CancelRead() is a no-op. + r.str.CancelRead(quic.StreamErrorCode(errorRequestCanceled)) + return nil +} diff --git a/vendor/github.com/lucas-clemente/quic-go/http3/client.go b/vendor/github.com/lucas-clemente/quic-go/http3/client.go new file mode 100644 index 000000000..861eaf0ab --- /dev/null +++ b/vendor/github.com/lucas-clemente/quic-go/http3/client.go @@ -0,0 +1,352 @@ +package http3 + +import ( + "bytes" + "context" + "crypto/tls" + "errors" + "fmt" + "io" + "net/http" + "strconv" + "sync" + + "github.com/lucas-clemente/quic-go" + "github.com/lucas-clemente/quic-go/internal/protocol" + "github.com/lucas-clemente/quic-go/internal/qtls" + "github.com/lucas-clemente/quic-go/internal/utils" + "github.com/lucas-clemente/quic-go/quicvarint" + "github.com/marten-seemann/qpack" +) + +// MethodGet0RTT allows a GET request to be sent using 0-RTT. +// Note that 0-RTT data doesn't provide replay protection. +const MethodGet0RTT = "GET_0RTT" + +const ( + defaultUserAgent = "quic-go HTTP/3" + defaultMaxResponseHeaderBytes = 10 * 1 << 20 // 10 MB +) + +var defaultQuicConfig = &quic.Config{ + MaxIncomingStreams: -1, // don't allow the server to create bidirectional streams + KeepAlive: true, + Versions: []protocol.VersionNumber{protocol.VersionTLS}, +} + +var dialAddr = quic.DialAddrEarly + +type roundTripperOpts struct { + DisableCompression bool + EnableDatagram bool + MaxHeaderBytes int64 +} + +// client is a HTTP3 client doing requests +type client struct { + tlsConf *tls.Config + config *quic.Config + opts *roundTripperOpts + + dialOnce sync.Once + dialer func(network, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlySession, error) + handshakeErr error + + requestWriter *requestWriter + + decoder *qpack.Decoder + + hostname string + session quic.EarlySession + + logger utils.Logger +} + +func newClient( + hostname string, + tlsConf *tls.Config, + opts *roundTripperOpts, + quicConfig *quic.Config, + dialer func(network, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlySession, error), +) (*client, error) { + if quicConfig == nil { + quicConfig = defaultQuicConfig.Clone() + } else if len(quicConfig.Versions) == 0 { + quicConfig = quicConfig.Clone() + quicConfig.Versions = []quic.VersionNumber{defaultQuicConfig.Versions[0]} + } + if len(quicConfig.Versions) != 1 { + return nil, errors.New("can only use a single QUIC version for dialing a HTTP/3 connection") + } + quicConfig.MaxIncomingStreams = -1 // don't allow any bidirectional streams + quicConfig.EnableDatagrams = opts.EnableDatagram + logger := utils.DefaultLogger.WithPrefix("h3 client") + + if tlsConf == nil { + tlsConf = &tls.Config{} + } else { + tlsConf = tlsConf.Clone() + } + // Replace existing ALPNs by H3 + tlsConf.NextProtos = []string{versionToALPN(quicConfig.Versions[0])} + + return &client{ + hostname: authorityAddr("https", hostname), + tlsConf: tlsConf, + requestWriter: newRequestWriter(logger), + decoder: qpack.NewDecoder(func(hf qpack.HeaderField) {}), + config: quicConfig, + opts: opts, + dialer: dialer, + logger: logger, + }, nil +} + +func (c *client) dial() error { + var err error + if c.dialer != nil { + c.session, err = c.dialer("udp", c.hostname, c.tlsConf, c.config) + } else { + c.session, err = dialAddr(c.hostname, c.tlsConf, c.config) + } + if err != nil { + return err + } + + // send the SETTINGs frame, using 0-RTT data, if possible + go func() { + if err := c.setupSession(); err != nil { + c.logger.Debugf("Setting up session failed: %s", err) + c.session.CloseWithError(quic.ApplicationErrorCode(errorInternalError), "") + } + }() + + go c.handleUnidirectionalStreams() + return nil +} + +func (c *client) setupSession() error { + // open the control stream + str, err := c.session.OpenUniStream() + if err != nil { + return err + } + buf := &bytes.Buffer{} + quicvarint.Write(buf, streamTypeControlStream) + // send the SETTINGS frame + (&settingsFrame{Datagram: c.opts.EnableDatagram}).Write(buf) + _, err = str.Write(buf.Bytes()) + return err +} + +func (c *client) handleUnidirectionalStreams() { + for { + str, err := c.session.AcceptUniStream(context.Background()) + if err != nil { + c.logger.Debugf("accepting unidirectional stream failed: %s", err) + return + } + + go func() { + streamType, err := quicvarint.Read(quicvarint.NewReader(str)) + if err != nil { + c.logger.Debugf("reading stream type on stream %d failed: %s", str.StreamID(), err) + return + } + // We're only interested in the control stream here. + switch streamType { + case streamTypeControlStream: + case streamTypeQPACKEncoderStream, streamTypeQPACKDecoderStream: + // Our QPACK implementation doesn't use the dynamic table yet. + // TODO: check that only one stream of each type is opened. + return + case streamTypePushStream: + // We never increased the Push ID, so we don't expect any push streams. + c.session.CloseWithError(quic.ApplicationErrorCode(errorIDError), "") + return + default: + str.CancelRead(quic.StreamErrorCode(errorStreamCreationError)) + return + } + f, err := parseNextFrame(str) + if err != nil { + c.session.CloseWithError(quic.ApplicationErrorCode(errorFrameError), "") + return + } + sf, ok := f.(*settingsFrame) + if !ok { + c.session.CloseWithError(quic.ApplicationErrorCode(errorMissingSettings), "") + return + } + if !sf.Datagram { + return + } + // If datagram support was enabled on our side as well as on the server side, + // we can expect it to have been negotiated both on the transport and on the HTTP/3 layer. + // Note: ConnectionState() will block until the handshake is complete (relevant when using 0-RTT). + if c.opts.EnableDatagram && !c.session.ConnectionState().SupportsDatagrams { + c.session.CloseWithError(quic.ApplicationErrorCode(errorSettingsError), "missing QUIC Datagram support") + } + }() + } +} + +func (c *client) Close() error { + if c.session == nil { + return nil + } + return c.session.CloseWithError(quic.ApplicationErrorCode(errorNoError), "") +} + +func (c *client) maxHeaderBytes() uint64 { + if c.opts.MaxHeaderBytes <= 0 { + return defaultMaxResponseHeaderBytes + } + return uint64(c.opts.MaxHeaderBytes) +} + +// RoundTrip executes a request and returns a response +func (c *client) RoundTrip(req *http.Request) (*http.Response, error) { + if authorityAddr("https", hostnameFromRequest(req)) != c.hostname { + return nil, fmt.Errorf("http3 client BUG: RoundTrip called for the wrong client (expected %s, got %s)", c.hostname, req.Host) + } + + c.dialOnce.Do(func() { + c.handshakeErr = c.dial() + }) + + if c.handshakeErr != nil { + return nil, c.handshakeErr + } + + // Immediately send out this request, if this is a 0-RTT request. + if req.Method == MethodGet0RTT { + req.Method = http.MethodGet + } else { + // wait for the handshake to complete + select { + case <-c.session.HandshakeComplete().Done(): + case <-req.Context().Done(): + return nil, req.Context().Err() + } + } + + str, err := c.session.OpenStreamSync(req.Context()) + if err != nil { + return nil, err + } + + // Request Cancellation: + // This go routine keeps running even after RoundTrip() returns. + // It is shut down when the application is done processing the body. + reqDone := make(chan struct{}) + go func() { + select { + case <-req.Context().Done(): + str.CancelWrite(quic.StreamErrorCode(errorRequestCanceled)) + str.CancelRead(quic.StreamErrorCode(errorRequestCanceled)) + case <-reqDone: + } + }() + + rsp, rerr := c.doRequest(req, str, reqDone) + if rerr.err != nil { // if any error occurred + close(reqDone) + if rerr.streamErr != 0 { // if it was a stream error + str.CancelWrite(quic.StreamErrorCode(rerr.streamErr)) + } + if rerr.connErr != 0 { // if it was a connection error + var reason string + if rerr.err != nil { + reason = rerr.err.Error() + } + c.session.CloseWithError(quic.ApplicationErrorCode(rerr.connErr), reason) + } + } + return rsp, rerr.err +} + +func (c *client) doRequest( + req *http.Request, + str quic.Stream, + reqDone chan struct{}, +) (*http.Response, requestError) { + var requestGzip bool + if !c.opts.DisableCompression && req.Method != "HEAD" && req.Header.Get("Accept-Encoding") == "" && req.Header.Get("Range") == "" { + requestGzip = true + } + if err := c.requestWriter.WriteRequest(str, req, requestGzip); err != nil { + return nil, newStreamError(errorInternalError, err) + } + + frame, err := parseNextFrame(str) + if err != nil { + return nil, newStreamError(errorFrameError, err) + } + hf, ok := frame.(*headersFrame) + if !ok { + return nil, newConnError(errorFrameUnexpected, errors.New("expected first frame to be a HEADERS frame")) + } + if hf.Length > c.maxHeaderBytes() { + return nil, newStreamError(errorFrameError, fmt.Errorf("HEADERS frame too large: %d bytes (max: %d)", hf.Length, c.maxHeaderBytes())) + } + headerBlock := make([]byte, hf.Length) + if _, err := io.ReadFull(str, headerBlock); err != nil { + return nil, newStreamError(errorRequestIncomplete, err) + } + hfs, err := c.decoder.DecodeFull(headerBlock) + if err != nil { + // TODO: use the right error code + return nil, newConnError(errorGeneralProtocolError, err) + } + + connState := qtls.ToTLSConnectionState(c.session.ConnectionState().TLS) + res := &http.Response{ + Proto: "HTTP/3", + ProtoMajor: 3, + Header: http.Header{}, + TLS: &connState, + } + for _, hf := range hfs { + switch hf.Name { + case ":status": + status, err := strconv.Atoi(hf.Value) + if err != nil { + return nil, newStreamError(errorGeneralProtocolError, errors.New("malformed non-numeric status pseudo header")) + } + res.StatusCode = status + res.Status = hf.Value + " " + http.StatusText(status) + default: + res.Header.Add(hf.Name, hf.Value) + } + } + respBody := newResponseBody(str, reqDone, func() { + c.session.CloseWithError(quic.ApplicationErrorCode(errorFrameUnexpected), "") + }) + + // Rules for when to set Content-Length are defined in https://tools.ietf.org/html/rfc7230#section-3.3.2. + _, hasTransferEncoding := res.Header["Transfer-Encoding"] + isInformational := res.StatusCode >= 100 && res.StatusCode < 200 + isNoContent := res.StatusCode == 204 + isSuccessfulConnect := req.Method == http.MethodConnect && res.StatusCode >= 200 && res.StatusCode < 300 + if !hasTransferEncoding && !isInformational && !isNoContent && !isSuccessfulConnect { + res.ContentLength = -1 + if clens, ok := res.Header["Content-Length"]; ok && len(clens) == 1 { + if clen64, err := strconv.ParseInt(clens[0], 10, 64); err == nil { + res.ContentLength = clen64 + } + } + } + + if requestGzip && res.Header.Get("Content-Encoding") == "gzip" { + res.Header.Del("Content-Encoding") + res.Header.Del("Content-Length") + res.ContentLength = -1 + res.Body = newGzipReader(respBody) + res.Uncompressed = true + } else { + res.Body = respBody + } + + return res, requestError{} +} diff --git a/vendor/github.com/lucas-clemente/quic-go/http3/error_codes.go b/vendor/github.com/lucas-clemente/quic-go/http3/error_codes.go new file mode 100644 index 000000000..dfde76d43 --- /dev/null +++ b/vendor/github.com/lucas-clemente/quic-go/http3/error_codes.go @@ -0,0 +1,70 @@ +package http3 + +import ( + "fmt" + + "github.com/lucas-clemente/quic-go" +) + +type errorCode quic.ApplicationErrorCode + +const ( + errorNoError errorCode = 0x100 + errorGeneralProtocolError errorCode = 0x101 + errorInternalError errorCode = 0x102 + errorStreamCreationError errorCode = 0x103 + errorClosedCriticalStream errorCode = 0x104 + errorFrameUnexpected errorCode = 0x105 + errorFrameError errorCode = 0x106 + errorExcessiveLoad errorCode = 0x107 + errorIDError errorCode = 0x108 + errorSettingsError errorCode = 0x109 + errorMissingSettings errorCode = 0x10a + errorRequestRejected errorCode = 0x10b + errorRequestCanceled errorCode = 0x10c + errorRequestIncomplete errorCode = 0x10d + errorMessageError errorCode = 0x10e + errorConnectError errorCode = 0x10f + errorVersionFallback errorCode = 0x110 +) + +func (e errorCode) String() string { + switch e { + case errorNoError: + return "H3_NO_ERROR" + case errorGeneralProtocolError: + return "H3_GENERAL_PROTOCOL_ERROR" + case errorInternalError: + return "H3_INTERNAL_ERROR" + case errorStreamCreationError: + return "H3_STREAM_CREATION_ERROR" + case errorClosedCriticalStream: + return "H3_CLOSED_CRITICAL_STREAM" + case errorFrameUnexpected: + return "H3_FRAME_UNEXPECTED" + case errorFrameError: + return "H3_FRAME_ERROR" + case errorExcessiveLoad: + return "H3_EXCESSIVE_LOAD" + case errorIDError: + return "H3_ID_ERROR" + case errorSettingsError: + return "H3_SETTINGS_ERROR" + case errorMissingSettings: + return "H3_MISSING_SETTINGS" + case errorRequestRejected: + return "H3_REQUEST_REJECTED" + case errorRequestCanceled: + return "H3_REQUEST_CANCELLED" + case errorRequestIncomplete: + return "H3_INCOMPLETE_REQUEST" + case errorMessageError: + return "H3_MESSAGE_ERROR" + case errorConnectError: + return "H3_CONNECT_ERROR" + case errorVersionFallback: + return "H3_VERSION_FALLBACK" + default: + return fmt.Sprintf("unknown error code: %#x", uint16(e)) + } +} diff --git a/vendor/github.com/lucas-clemente/quic-go/http3/frames.go b/vendor/github.com/lucas-clemente/quic-go/http3/frames.go new file mode 100644 index 000000000..679f66c10 --- /dev/null +++ b/vendor/github.com/lucas-clemente/quic-go/http3/frames.go @@ -0,0 +1,142 @@ +package http3 + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + + "github.com/lucas-clemente/quic-go/internal/protocol" + "github.com/lucas-clemente/quic-go/quicvarint" +) + +type frame interface{} + +func parseNextFrame(r io.Reader) (frame, error) { + qr := quicvarint.NewReader(r) + t, err := quicvarint.Read(qr) + if err != nil { + return nil, err + } + l, err := quicvarint.Read(qr) + if err != nil { + return nil, err + } + + switch t { + case 0x0: + return &dataFrame{Length: l}, nil + case 0x1: + return &headersFrame{Length: l}, nil + case 0x4: + return parseSettingsFrame(r, l) + case 0x3: // CANCEL_PUSH + fallthrough + case 0x5: // PUSH_PROMISE + fallthrough + case 0x7: // GOAWAY + fallthrough + case 0xd: // MAX_PUSH_ID + fallthrough + case 0xe: // DUPLICATE_PUSH + fallthrough + default: + // skip over unknown frames + if _, err := io.CopyN(ioutil.Discard, qr, int64(l)); err != nil { + return nil, err + } + return parseNextFrame(qr) + } +} + +type dataFrame struct { + Length uint64 +} + +func (f *dataFrame) Write(b *bytes.Buffer) { + quicvarint.Write(b, 0x0) + quicvarint.Write(b, f.Length) +} + +type headersFrame struct { + Length uint64 +} + +func (f *headersFrame) Write(b *bytes.Buffer) { + quicvarint.Write(b, 0x1) + quicvarint.Write(b, f.Length) +} + +const settingDatagram = 0x276 + +type settingsFrame struct { + Datagram bool + other map[uint64]uint64 // all settings that we don't explicitly recognize +} + +func parseSettingsFrame(r io.Reader, l uint64) (*settingsFrame, error) { + if l > 8*(1<<10) { + return nil, fmt.Errorf("unexpected size for SETTINGS frame: %d", l) + } + buf := make([]byte, l) + if _, err := io.ReadFull(r, buf); err != nil { + if err == io.ErrUnexpectedEOF { + return nil, io.EOF + } + return nil, err + } + frame := &settingsFrame{} + b := bytes.NewReader(buf) + var readDatagram bool + for b.Len() > 0 { + id, err := quicvarint.Read(b) + if err != nil { // should not happen. We allocated the whole frame already. + return nil, err + } + val, err := quicvarint.Read(b) + if err != nil { // should not happen. We allocated the whole frame already. + return nil, err + } + + switch id { + case settingDatagram: + if readDatagram { + return nil, fmt.Errorf("duplicate setting: %d", id) + } + readDatagram = true + if val != 0 && val != 1 { + return nil, fmt.Errorf("invalid value for H3_DATAGRAM: %d", val) + } + frame.Datagram = val == 1 + default: + if _, ok := frame.other[id]; ok { + return nil, fmt.Errorf("duplicate setting: %d", id) + } + if frame.other == nil { + frame.other = make(map[uint64]uint64) + } + frame.other[id] = val + } + } + return frame, nil +} + +func (f *settingsFrame) Write(b *bytes.Buffer) { + quicvarint.Write(b, 0x4) + var l protocol.ByteCount + for id, val := range f.other { + l += quicvarint.Len(id) + quicvarint.Len(val) + } + if f.Datagram { + l += quicvarint.Len(settingDatagram) + quicvarint.Len(1) + } + quicvarint.Write(b, uint64(l)) + if f.Datagram { + quicvarint.Write(b, settingDatagram) + quicvarint.Write(b, 1) + } + for id, val := range f.other { + quicvarint.Write(b, id) + quicvarint.Write(b, val) + } +} diff --git a/vendor/github.com/lucas-clemente/quic-go/http3/gzip_reader.go b/vendor/github.com/lucas-clemente/quic-go/http3/gzip_reader.go new file mode 100644 index 000000000..01983ac77 --- /dev/null +++ b/vendor/github.com/lucas-clemente/quic-go/http3/gzip_reader.go @@ -0,0 +1,39 @@ +package http3 + +// copied from net/transport.go + +// gzipReader wraps a response body so it can lazily +// call gzip.NewReader on the first call to Read +import ( + "compress/gzip" + "io" +) + +// call gzip.NewReader on the first call to Read +type gzipReader struct { + body io.ReadCloser // underlying Response.Body + zr *gzip.Reader // lazily-initialized gzip reader + zerr error // sticky error +} + +func newGzipReader(body io.ReadCloser) io.ReadCloser { + return &gzipReader{body: body} +} + +func (gz *gzipReader) Read(p []byte) (n int, err error) { + if gz.zerr != nil { + return 0, gz.zerr + } + if gz.zr == nil { + gz.zr, err = gzip.NewReader(gz.body) + if err != nil { + gz.zerr = err + return 0, err + } + } + return gz.zr.Read(p) +} + +func (gz *gzipReader) Close() error { + return gz.body.Close() +} diff --git a/vendor/github.com/lucas-clemente/quic-go/http3/request.go b/vendor/github.com/lucas-clemente/quic-go/http3/request.go new file mode 100644 index 000000000..b5fc5d5ac --- /dev/null +++ b/vendor/github.com/lucas-clemente/quic-go/http3/request.go @@ -0,0 +1,92 @@ +package http3 + +import ( + "crypto/tls" + "errors" + "net/http" + "net/url" + "strconv" + "strings" + + "github.com/marten-seemann/qpack" +) + +func requestFromHeaders(headers []qpack.HeaderField) (*http.Request, error) { + var path, authority, method, contentLengthStr string + httpHeaders := http.Header{} + + for _, h := range headers { + switch h.Name { + case ":path": + path = h.Value + case ":method": + method = h.Value + case ":authority": + authority = h.Value + case "content-length": + contentLengthStr = h.Value + default: + if !h.IsPseudo() { + httpHeaders.Add(h.Name, h.Value) + } + } + } + + // concatenate cookie headers, see https://tools.ietf.org/html/rfc6265#section-5.4 + if len(httpHeaders["Cookie"]) > 0 { + httpHeaders.Set("Cookie", strings.Join(httpHeaders["Cookie"], "; ")) + } + + isConnect := method == http.MethodConnect + if isConnect { + if path != "" || authority == "" { + return nil, errors.New(":path must be empty and :authority must not be empty") + } + } else if len(path) == 0 || len(authority) == 0 || len(method) == 0 { + return nil, errors.New(":path, :authority and :method must not be empty") + } + + var u *url.URL + var requestURI string + var err error + + if isConnect { + u = &url.URL{Host: authority} + requestURI = authority + } else { + u, err = url.ParseRequestURI(path) + if err != nil { + return nil, err + } + requestURI = path + } + + var contentLength int64 + if len(contentLengthStr) > 0 { + contentLength, err = strconv.ParseInt(contentLengthStr, 10, 64) + if err != nil { + return nil, err + } + } + + return &http.Request{ + Method: method, + URL: u, + Proto: "HTTP/3", + ProtoMajor: 3, + ProtoMinor: 0, + Header: httpHeaders, + Body: nil, + ContentLength: contentLength, + Host: authority, + RequestURI: requestURI, + TLS: &tls.ConnectionState{}, + }, nil +} + +func hostnameFromRequest(req *http.Request) string { + if req.URL != nil { + return req.URL.Host + } + return "" +} diff --git a/vendor/github.com/lucas-clemente/quic-go/http3/request_writer.go b/vendor/github.com/lucas-clemente/quic-go/http3/request_writer.go new file mode 100644 index 000000000..8878c8f19 --- /dev/null +++ b/vendor/github.com/lucas-clemente/quic-go/http3/request_writer.go @@ -0,0 +1,319 @@ +package http3 + +import ( + "bytes" + "fmt" + "io" + "net" + "net/http" + "strconv" + "strings" + "sync" + + "github.com/lucas-clemente/quic-go" + "github.com/lucas-clemente/quic-go/internal/utils" + "github.com/marten-seemann/qpack" + "golang.org/x/net/http/httpguts" + "golang.org/x/net/http2/hpack" + "golang.org/x/net/idna" +) + +const bodyCopyBufferSize = 8 * 1024 + +type requestWriter struct { + mutex sync.Mutex + encoder *qpack.Encoder + headerBuf *bytes.Buffer + + logger utils.Logger +} + +func newRequestWriter(logger utils.Logger) *requestWriter { + headerBuf := &bytes.Buffer{} + encoder := qpack.NewEncoder(headerBuf) + return &requestWriter{ + encoder: encoder, + headerBuf: headerBuf, + logger: logger, + } +} + +func (w *requestWriter) WriteRequest(str quic.Stream, req *http.Request, gzip bool) error { + buf := &bytes.Buffer{} + if err := w.writeHeaders(buf, req, gzip); err != nil { + return err + } + if _, err := str.Write(buf.Bytes()); err != nil { + return err + } + // TODO: add support for trailers + if req.Body == nil { + str.Close() + return nil + } + + // send the request body asynchronously + go func() { + defer req.Body.Close() + b := make([]byte, bodyCopyBufferSize) + for { + n, rerr := req.Body.Read(b) + if n == 0 { + if rerr == nil { + continue + } else if rerr == io.EOF { + break + } + } + buf := &bytes.Buffer{} + (&dataFrame{Length: uint64(n)}).Write(buf) + if _, err := str.Write(buf.Bytes()); err != nil { + w.logger.Errorf("Error writing request: %s", err) + return + } + if _, err := str.Write(b[:n]); err != nil { + w.logger.Errorf("Error writing request: %s", err) + return + } + if rerr != nil { + if rerr == io.EOF { + break + } + str.CancelWrite(quic.StreamErrorCode(errorRequestCanceled)) + w.logger.Errorf("Error writing request: %s", rerr) + return + } + } + str.Close() + }() + + return nil +} + +func (w *requestWriter) writeHeaders(wr io.Writer, req *http.Request, gzip bool) error { + w.mutex.Lock() + defer w.mutex.Unlock() + defer w.encoder.Close() + + if err := w.encodeHeaders(req, gzip, "", actualContentLength(req)); err != nil { + return err + } + + buf := &bytes.Buffer{} + hf := headersFrame{Length: uint64(w.headerBuf.Len())} + hf.Write(buf) + if _, err := wr.Write(buf.Bytes()); err != nil { + return err + } + if _, err := wr.Write(w.headerBuf.Bytes()); err != nil { + return err + } + w.headerBuf.Reset() + return nil +} + +// copied from net/transport.go + +func (w *requestWriter) encodeHeaders(req *http.Request, addGzipHeader bool, trailers string, contentLength int64) error { + host := req.Host + if host == "" { + host = req.URL.Host + } + host, err := httpguts.PunycodeHostPort(host) + if err != nil { + return err + } + + var path string + if req.Method != "CONNECT" { + path = req.URL.RequestURI() + if !validPseudoPath(path) { + orig := path + path = strings.TrimPrefix(path, req.URL.Scheme+"://"+host) + if !validPseudoPath(path) { + if req.URL.Opaque != "" { + return fmt.Errorf("invalid request :path %q from URL.Opaque = %q", orig, req.URL.Opaque) + } else { + return fmt.Errorf("invalid request :path %q", orig) + } + } + } + } + + // Check for any invalid headers and return an error before we + // potentially pollute our hpack state. (We want to be able to + // continue to reuse the hpack encoder for future requests) + for k, vv := range req.Header { + if !httpguts.ValidHeaderFieldName(k) { + return fmt.Errorf("invalid HTTP header name %q", k) + } + for _, v := range vv { + if !httpguts.ValidHeaderFieldValue(v) { + return fmt.Errorf("invalid HTTP header value %q for header %q", v, k) + } + } + } + + enumerateHeaders := func(f func(name, value string)) { + // 8.1.2.3 Request Pseudo-Header Fields + // The :path pseudo-header field includes the path and query parts of the + // target URI (the path-absolute production and optionally a '?' character + // followed by the query production (see Sections 3.3 and 3.4 of + // [RFC3986]). + f(":authority", host) + f(":method", req.Method) + if req.Method != "CONNECT" { + f(":path", path) + f(":scheme", req.URL.Scheme) + } + if trailers != "" { + f("trailer", trailers) + } + + var didUA bool + for k, vv := range req.Header { + if strings.EqualFold(k, "host") || strings.EqualFold(k, "content-length") { + // Host is :authority, already sent. + // Content-Length is automatic, set below. + continue + } else if strings.EqualFold(k, "connection") || strings.EqualFold(k, "proxy-connection") || + strings.EqualFold(k, "transfer-encoding") || strings.EqualFold(k, "upgrade") || + strings.EqualFold(k, "keep-alive") { + // Per 8.1.2.2 Connection-Specific Header + // Fields, don't send connection-specific + // fields. We have already checked if any + // are error-worthy so just ignore the rest. + continue + } else if strings.EqualFold(k, "user-agent") { + // Match Go's http1 behavior: at most one + // User-Agent. If set to nil or empty string, + // then omit it. Otherwise if not mentioned, + // include the default (below). + didUA = true + if len(vv) < 1 { + continue + } + vv = vv[:1] + if vv[0] == "" { + continue + } + + } + + for _, v := range vv { + f(k, v) + } + } + if shouldSendReqContentLength(req.Method, contentLength) { + f("content-length", strconv.FormatInt(contentLength, 10)) + } + if addGzipHeader { + f("accept-encoding", "gzip") + } + if !didUA { + f("user-agent", defaultUserAgent) + } + } + + // Do a first pass over the headers counting bytes to ensure + // we don't exceed cc.peerMaxHeaderListSize. This is done as a + // separate pass before encoding the headers to prevent + // modifying the hpack state. + hlSize := uint64(0) + enumerateHeaders(func(name, value string) { + hf := hpack.HeaderField{Name: name, Value: value} + hlSize += uint64(hf.Size()) + }) + + // TODO: check maximum header list size + // if hlSize > cc.peerMaxHeaderListSize { + // return errRequestHeaderListSize + // } + + // trace := httptrace.ContextClientTrace(req.Context()) + // traceHeaders := traceHasWroteHeaderField(trace) + + // Header list size is ok. Write the headers. + enumerateHeaders(func(name, value string) { + name = strings.ToLower(name) + w.encoder.WriteField(qpack.HeaderField{Name: name, Value: value}) + // if traceHeaders { + // traceWroteHeaderField(trace, name, value) + // } + }) + + return nil +} + +// authorityAddr returns a given authority (a host/IP, or host:port / ip:port) +// and returns a host:port. The port 443 is added if needed. +func authorityAddr(scheme string, authority string) (addr string) { + host, port, err := net.SplitHostPort(authority) + if err != nil { // authority didn't have a port + port = "443" + if scheme == "http" { + port = "80" + } + host = authority + } + if a, err := idna.ToASCII(host); err == nil { + host = a + } + // IPv6 address literal, without a port: + if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") { + return host + ":" + port + } + return net.JoinHostPort(host, port) +} + +// validPseudoPath reports whether v is a valid :path pseudo-header +// value. It must be either: +// +// *) a non-empty string starting with '/' +// *) the string '*', for OPTIONS requests. +// +// For now this is only used a quick check for deciding when to clean +// up Opaque URLs before sending requests from the Transport. +// See golang.org/issue/16847 +// +// We used to enforce that the path also didn't start with "//", but +// Google's GFE accepts such paths and Chrome sends them, so ignore +// that part of the spec. See golang.org/issue/19103. +func validPseudoPath(v string) bool { + return (len(v) > 0 && v[0] == '/') || v == "*" +} + +// actualContentLength returns a sanitized version of +// req.ContentLength, where 0 actually means zero (not unknown) and -1 +// means unknown. +func actualContentLength(req *http.Request) int64 { + if req.Body == nil { + return 0 + } + if req.ContentLength != 0 { + return req.ContentLength + } + return -1 +} + +// shouldSendReqContentLength reports whether the http2.Transport should send +// a "content-length" request header. This logic is basically a copy of the net/http +// transferWriter.shouldSendContentLength. +// The contentLength is the corrected contentLength (so 0 means actually 0, not unknown). +// -1 means unknown. +func shouldSendReqContentLength(method string, contentLength int64) bool { + if contentLength > 0 { + return true + } + if contentLength < 0 { + return false + } + // For zero bodies, whether we send a content-length depends on the method. + // It also kinda doesn't matter for http2 either way, with END_STREAM. + switch method { + case "POST", "PUT", "PATCH": + return true + default: + return false + } +} diff --git a/vendor/github.com/lucas-clemente/quic-go/http3/response_writer.go b/vendor/github.com/lucas-clemente/quic-go/http3/response_writer.go new file mode 100644 index 000000000..0a42518cf --- /dev/null +++ b/vendor/github.com/lucas-clemente/quic-go/http3/response_writer.go @@ -0,0 +1,135 @@ +package http3 + +import ( + "bufio" + "bytes" + "net/http" + "strconv" + "strings" + + "github.com/lucas-clemente/quic-go" + "github.com/lucas-clemente/quic-go/internal/utils" + "github.com/marten-seemann/qpack" +) + +// DataStreamer lets the caller take over the stream. After a call to DataStream +// the HTTP server library will not do anything else with the connection. +// +// It becomes the caller's responsibility to manage and close the stream. +// +// After a call to DataStream, the original Request.Body must not be used. +type DataStreamer interface { + DataStream() quic.Stream +} + +type responseWriter struct { + stream quic.Stream // needed for DataStream() + bufferedStream *bufio.Writer + + header http.Header + status int // status code passed to WriteHeader + headerWritten bool + dataStreamUsed bool // set when DataSteam() is called + + logger utils.Logger +} + +var ( + _ http.ResponseWriter = &responseWriter{} + _ http.Flusher = &responseWriter{} + _ DataStreamer = &responseWriter{} +) + +func newResponseWriter(stream quic.Stream, logger utils.Logger) *responseWriter { + return &responseWriter{ + header: http.Header{}, + stream: stream, + bufferedStream: bufio.NewWriter(stream), + logger: logger, + } +} + +func (w *responseWriter) Header() http.Header { + return w.header +} + +func (w *responseWriter) WriteHeader(status int) { + if w.headerWritten { + return + } + + if status < 100 || status >= 200 { + w.headerWritten = true + } + w.status = status + + var headers bytes.Buffer + enc := qpack.NewEncoder(&headers) + enc.WriteField(qpack.HeaderField{Name: ":status", Value: strconv.Itoa(status)}) + + for k, v := range w.header { + for index := range v { + enc.WriteField(qpack.HeaderField{Name: strings.ToLower(k), Value: v[index]}) + } + } + + buf := &bytes.Buffer{} + (&headersFrame{Length: uint64(headers.Len())}).Write(buf) + w.logger.Infof("Responding with %d", status) + if _, err := w.bufferedStream.Write(buf.Bytes()); err != nil { + w.logger.Errorf("could not write headers frame: %s", err.Error()) + } + if _, err := w.bufferedStream.Write(headers.Bytes()); err != nil { + w.logger.Errorf("could not write header frame payload: %s", err.Error()) + } + if !w.headerWritten { + w.Flush() + } +} + +func (w *responseWriter) Write(p []byte) (int, error) { + if !w.headerWritten { + w.WriteHeader(200) + } + if !bodyAllowedForStatus(w.status) { + return 0, http.ErrBodyNotAllowed + } + df := &dataFrame{Length: uint64(len(p))} + buf := &bytes.Buffer{} + df.Write(buf) + if _, err := w.bufferedStream.Write(buf.Bytes()); err != nil { + return 0, err + } + return w.bufferedStream.Write(p) +} + +func (w *responseWriter) Flush() { + if err := w.bufferedStream.Flush(); err != nil { + w.logger.Errorf("could not flush to stream: %s", err.Error()) + } +} + +func (w *responseWriter) usedDataStream() bool { + return w.dataStreamUsed +} + +func (w *responseWriter) DataStream() quic.Stream { + w.dataStreamUsed = true + w.Flush() + return w.stream +} + +// copied from http2/http2.go +// bodyAllowedForStatus reports whether a given response status code +// permits a body. See RFC 2616, section 4.4. +func bodyAllowedForStatus(status int) bool { + switch { + case status >= 100 && status <= 199: + return false + case status == 204: + return false + case status == 304: + return false + } + return true +} diff --git a/vendor/github.com/lucas-clemente/quic-go/http3/roundtrip.go b/vendor/github.com/lucas-clemente/quic-go/http3/roundtrip.go new file mode 100644 index 000000000..8e6f943e9 --- /dev/null +++ b/vendor/github.com/lucas-clemente/quic-go/http3/roundtrip.go @@ -0,0 +1,198 @@ +package http3 + +import ( + "crypto/tls" + "errors" + "fmt" + "io" + "net/http" + "strings" + "sync" + + quic "github.com/lucas-clemente/quic-go" + + "golang.org/x/net/http/httpguts" +) + +type roundTripCloser interface { + http.RoundTripper + io.Closer +} + +// RoundTripper implements the http.RoundTripper interface +type RoundTripper struct { + mutex sync.Mutex + + // DisableCompression, if true, prevents the Transport from + // requesting compression with an "Accept-Encoding: gzip" + // request header when the Request contains no existing + // Accept-Encoding value. If the Transport requests gzip on + // its own and gets a gzipped response, it's transparently + // decoded in the Response.Body. However, if the user + // explicitly requested gzip it is not automatically + // uncompressed. + DisableCompression bool + + // TLSClientConfig specifies the TLS configuration to use with + // tls.Client. If nil, the default configuration is used. + TLSClientConfig *tls.Config + + // QuicConfig is the quic.Config used for dialing new connections. + // If nil, reasonable default values will be used. + QuicConfig *quic.Config + + // Enable support for HTTP/3 datagrams. + // If set to true, QuicConfig.EnableDatagram will be set. + // See https://www.ietf.org/archive/id/draft-schinazi-masque-h3-datagram-02.html. + EnableDatagrams bool + + // Dial specifies an optional dial function for creating QUIC + // connections for requests. + // If Dial is nil, quic.DialAddrEarly will be used. + Dial func(network, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlySession, error) + + // MaxResponseHeaderBytes specifies a limit on how many response bytes are + // allowed in the server's response header. + // Zero means to use a default limit. + MaxResponseHeaderBytes int64 + + clients map[string]roundTripCloser +} + +// RoundTripOpt are options for the Transport.RoundTripOpt method. +type RoundTripOpt struct { + // OnlyCachedConn controls whether the RoundTripper may create a new QUIC connection. + // If set true and no cached connection is available, RoundTrip will return ErrNoCachedConn. + OnlyCachedConn bool + // SkipSchemeCheck controls whether we check if the scheme is https. + // This allows the use of different schemes, e.g. masque://target.example.com:443/. + SkipSchemeCheck bool +} + +var _ roundTripCloser = &RoundTripper{} + +// ErrNoCachedConn is returned when RoundTripper.OnlyCachedConn is set +var ErrNoCachedConn = errors.New("http3: no cached connection was available") + +// RoundTripOpt is like RoundTrip, but takes options. +func (r *RoundTripper) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) { + if req.URL == nil { + closeRequestBody(req) + return nil, errors.New("http3: nil Request.URL") + } + if req.URL.Host == "" { + closeRequestBody(req) + return nil, errors.New("http3: no Host in request URL") + } + if req.Header == nil { + closeRequestBody(req) + return nil, errors.New("http3: nil Request.Header") + } + + if req.URL.Scheme == "https" { + for k, vv := range req.Header { + if !httpguts.ValidHeaderFieldName(k) { + return nil, fmt.Errorf("http3: invalid http header field name %q", k) + } + for _, v := range vv { + if !httpguts.ValidHeaderFieldValue(v) { + return nil, fmt.Errorf("http3: invalid http header field value %q for key %v", v, k) + } + } + } + } else if !opt.SkipSchemeCheck { + closeRequestBody(req) + return nil, fmt.Errorf("http3: unsupported protocol scheme: %s", req.URL.Scheme) + } + + if req.Method != "" && !validMethod(req.Method) { + closeRequestBody(req) + return nil, fmt.Errorf("http3: invalid method %q", req.Method) + } + + hostname := authorityAddr("https", hostnameFromRequest(req)) + cl, err := r.getClient(hostname, opt.OnlyCachedConn) + if err != nil { + return nil, err + } + return cl.RoundTrip(req) +} + +// RoundTrip does a round trip. +func (r *RoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + return r.RoundTripOpt(req, RoundTripOpt{}) +} + +func (r *RoundTripper) getClient(hostname string, onlyCached bool) (http.RoundTripper, error) { + r.mutex.Lock() + defer r.mutex.Unlock() + + if r.clients == nil { + r.clients = make(map[string]roundTripCloser) + } + + client, ok := r.clients[hostname] + if !ok { + if onlyCached { + return nil, ErrNoCachedConn + } + var err error + client, err = newClient( + hostname, + r.TLSClientConfig, + &roundTripperOpts{ + EnableDatagram: r.EnableDatagrams, + DisableCompression: r.DisableCompression, + MaxHeaderBytes: r.MaxResponseHeaderBytes, + }, + r.QuicConfig, + r.Dial, + ) + if err != nil { + return nil, err + } + r.clients[hostname] = client + } + return client, nil +} + +// Close closes the QUIC connections that this RoundTripper has used +func (r *RoundTripper) Close() error { + r.mutex.Lock() + defer r.mutex.Unlock() + for _, client := range r.clients { + if err := client.Close(); err != nil { + return err + } + } + r.clients = nil + return nil +} + +func closeRequestBody(req *http.Request) { + if req.Body != nil { + req.Body.Close() + } +} + +func validMethod(method string) bool { + /* + Method = "OPTIONS" ; Section 9.2 + | "GET" ; Section 9.3 + | "HEAD" ; Section 9.4 + | "POST" ; Section 9.5 + | "PUT" ; Section 9.6 + | "DELETE" ; Section 9.7 + | "TRACE" ; Section 9.8 + | "CONNECT" ; Section 9.9 + | extension-method + extension-method = token + token = 1* + */ + return len(method) > 0 && strings.IndexFunc(method, isNotToken) == -1 +} + +// copied from net/http/http.go +func isNotToken(r rune) bool { + return !httpguts.IsTokenRune(r) +} diff --git a/vendor/github.com/lucas-clemente/quic-go/http3/server.go b/vendor/github.com/lucas-clemente/quic-go/http3/server.go new file mode 100644 index 000000000..ce5bc909f --- /dev/null +++ b/vendor/github.com/lucas-clemente/quic-go/http3/server.go @@ -0,0 +1,565 @@ +package http3 + +import ( + "bytes" + "context" + "crypto/tls" + "errors" + "fmt" + "io" + "net" + "net/http" + "runtime" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/lucas-clemente/quic-go" + "github.com/lucas-clemente/quic-go/internal/handshake" + "github.com/lucas-clemente/quic-go/internal/protocol" + "github.com/lucas-clemente/quic-go/internal/utils" + "github.com/lucas-clemente/quic-go/quicvarint" + "github.com/marten-seemann/qpack" +) + +// allows mocking of quic.Listen and quic.ListenAddr +var ( + quicListen = quic.ListenEarly + quicListenAddr = quic.ListenAddrEarly +) + +const ( + nextProtoH3Draft29 = "h3-29" + nextProtoH3 = "h3" +) + +const ( + streamTypeControlStream = 0 + streamTypePushStream = 1 + streamTypeQPACKEncoderStream = 2 + streamTypeQPACKDecoderStream = 3 +) + +func versionToALPN(v protocol.VersionNumber) string { + if v == protocol.Version1 { + return nextProtoH3 + } + if v == protocol.VersionTLS || v == protocol.VersionDraft29 { + return nextProtoH3Draft29 + } + return "" +} + +// contextKey is a value for use with context.WithValue. It's used as +// a pointer so it fits in an interface{} without allocation. +type contextKey struct { + name string +} + +func (k *contextKey) String() string { return "quic-go/http3 context value " + k.name } + +// ServerContextKey is a context key. It can be used in HTTP +// handlers with Context.Value to access the server that +// started the handler. The associated value will be of +// type *http3.Server. +var ServerContextKey = &contextKey{"http3-server"} + +type requestError struct { + err error + streamErr errorCode + connErr errorCode +} + +func newStreamError(code errorCode, err error) requestError { + return requestError{err: err, streamErr: code} +} + +func newConnError(code errorCode, err error) requestError { + return requestError{err: err, connErr: code} +} + +// Server is a HTTP/3 server. +type Server struct { + *http.Server + + // By providing a quic.Config, it is possible to set parameters of the QUIC connection. + // If nil, it uses reasonable default values. + QuicConfig *quic.Config + + // Enable support for HTTP/3 datagrams. + // If set to true, QuicConfig.EnableDatagram will be set. + // See https://www.ietf.org/archive/id/draft-schinazi-masque-h3-datagram-02.html. + EnableDatagrams bool + + // The port to use in Alt-Svc response headers. + // If needed Port can be manually set when the Server is created. + // This is useful when a Layer 4 firewall is redirecting UDP traffic and clients must use + // a port different from the port the Server is listening on. + Port uint32 + + mutex sync.Mutex + listeners map[*quic.EarlyListener]struct{} + closed utils.AtomicBool + + loggerOnce sync.Once + logger utils.Logger +} + +// ListenAndServe listens on the UDP address s.Addr and calls s.Handler to handle HTTP/3 requests on incoming connections. +func (s *Server) ListenAndServe() error { + if s.Server == nil { + return errors.New("use of http3.Server without http.Server") + } + return s.serveImpl(s.TLSConfig, nil) +} + +// ListenAndServeTLS listens on the UDP address s.Addr and calls s.Handler to handle HTTP/3 requests on incoming connections. +func (s *Server) ListenAndServeTLS(certFile, keyFile string) error { + var err error + certs := make([]tls.Certificate, 1) + certs[0], err = tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + return err + } + // We currently only use the cert-related stuff from tls.Config, + // so we don't need to make a full copy. + config := &tls.Config{ + Certificates: certs, + } + return s.serveImpl(config, nil) +} + +// Serve an existing UDP connection. +// It is possible to reuse the same connection for outgoing connections. +// Closing the server does not close the packet conn. +func (s *Server) Serve(conn net.PacketConn) error { + return s.serveImpl(s.TLSConfig, conn) +} + +func (s *Server) serveImpl(tlsConf *tls.Config, conn net.PacketConn) error { + if s.closed.Get() { + return http.ErrServerClosed + } + if s.Server == nil { + return errors.New("use of http3.Server without http.Server") + } + s.loggerOnce.Do(func() { + s.logger = utils.DefaultLogger.WithPrefix("server") + }) + + // The tls.Config we pass to Listen needs to have the GetConfigForClient callback set. + // That way, we can get the QUIC version and set the correct ALPN value. + baseConf := &tls.Config{ + GetConfigForClient: func(ch *tls.ClientHelloInfo) (*tls.Config, error) { + // determine the ALPN from the QUIC version used + proto := nextProtoH3Draft29 + if qconn, ok := ch.Conn.(handshake.ConnWithVersion); ok { + if qconn.GetQUICVersion() == protocol.Version1 { + proto = nextProtoH3 + } + } + config := tlsConf + if tlsConf.GetConfigForClient != nil { + getConfigForClient := tlsConf.GetConfigForClient + var err error + conf, err := getConfigForClient(ch) + if err != nil { + return nil, err + } + if conf != nil { + config = conf + } + } + if config == nil { + return nil, nil + } + config = config.Clone() + config.NextProtos = []string{proto} + return config, nil + }, + } + + var ln quic.EarlyListener + var err error + quicConf := s.QuicConfig + if quicConf == nil { + quicConf = &quic.Config{} + } else { + quicConf = s.QuicConfig.Clone() + } + if s.EnableDatagrams { + quicConf.EnableDatagrams = true + } + if conn == nil { + ln, err = quicListenAddr(s.Addr, baseConf, quicConf) + } else { + ln, err = quicListen(conn, baseConf, quicConf) + } + if err != nil { + return err + } + s.addListener(&ln) + defer s.removeListener(&ln) + + for { + sess, err := ln.Accept(context.Background()) + if err != nil { + return err + } + go s.handleConn(sess) + } +} + +// We store a pointer to interface in the map set. This is safe because we only +// call trackListener via Serve and can track+defer untrack the same pointer to +// local variable there. We never need to compare a Listener from another caller. +func (s *Server) addListener(l *quic.EarlyListener) { + s.mutex.Lock() + if s.listeners == nil { + s.listeners = make(map[*quic.EarlyListener]struct{}) + } + s.listeners[l] = struct{}{} + s.mutex.Unlock() +} + +func (s *Server) removeListener(l *quic.EarlyListener) { + s.mutex.Lock() + delete(s.listeners, l) + s.mutex.Unlock() +} + +func (s *Server) handleConn(sess quic.EarlySession) { + decoder := qpack.NewDecoder(nil) + + // send a SETTINGS frame + str, err := sess.OpenUniStream() + if err != nil { + s.logger.Debugf("Opening the control stream failed.") + return + } + buf := &bytes.Buffer{} + quicvarint.Write(buf, streamTypeControlStream) // stream type + (&settingsFrame{Datagram: s.EnableDatagrams}).Write(buf) + str.Write(buf.Bytes()) + + go s.handleUnidirectionalStreams(sess) + + // Process all requests immediately. + // It's the client's responsibility to decide which requests are eligible for 0-RTT. + for { + str, err := sess.AcceptStream(context.Background()) + if err != nil { + s.logger.Debugf("Accepting stream failed: %s", err) + return + } + go func() { + rerr := s.handleRequest(sess, str, decoder, func() { + sess.CloseWithError(quic.ApplicationErrorCode(errorFrameUnexpected), "") + }) + if rerr.err != nil || rerr.streamErr != 0 || rerr.connErr != 0 { + s.logger.Debugf("Handling request failed: %s", err) + if rerr.streamErr != 0 { + str.CancelWrite(quic.StreamErrorCode(rerr.streamErr)) + } + if rerr.connErr != 0 { + var reason string + if rerr.err != nil { + reason = rerr.err.Error() + } + sess.CloseWithError(quic.ApplicationErrorCode(rerr.connErr), reason) + } + return + } + str.Close() + }() + } +} + +func (s *Server) handleUnidirectionalStreams(sess quic.EarlySession) { + for { + str, err := sess.AcceptUniStream(context.Background()) + if err != nil { + s.logger.Debugf("accepting unidirectional stream failed: %s", err) + return + } + + go func(str quic.ReceiveStream) { + streamType, err := quicvarint.Read(quicvarint.NewReader(str)) + if err != nil { + s.logger.Debugf("reading stream type on stream %d failed: %s", str.StreamID(), err) + return + } + // We're only interested in the control stream here. + switch streamType { + case streamTypeControlStream: + case streamTypeQPACKEncoderStream, streamTypeQPACKDecoderStream: + // Our QPACK implementation doesn't use the dynamic table yet. + // TODO: check that only one stream of each type is opened. + return + case streamTypePushStream: // only the server can push + sess.CloseWithError(quic.ApplicationErrorCode(errorStreamCreationError), "") + return + default: + str.CancelRead(quic.StreamErrorCode(errorStreamCreationError)) + return + } + f, err := parseNextFrame(str) + if err != nil { + sess.CloseWithError(quic.ApplicationErrorCode(errorFrameError), "") + return + } + sf, ok := f.(*settingsFrame) + if !ok { + sess.CloseWithError(quic.ApplicationErrorCode(errorMissingSettings), "") + return + } + if !sf.Datagram { + return + } + // If datagram support was enabled on our side as well as on the client side, + // we can expect it to have been negotiated both on the transport and on the HTTP/3 layer. + // Note: ConnectionState() will block until the handshake is complete (relevant when using 0-RTT). + if s.EnableDatagrams && !sess.ConnectionState().SupportsDatagrams { + sess.CloseWithError(quic.ApplicationErrorCode(errorSettingsError), "missing QUIC Datagram support") + } + }(str) + } +} + +func (s *Server) maxHeaderBytes() uint64 { + if s.Server.MaxHeaderBytes <= 0 { + return http.DefaultMaxHeaderBytes + } + return uint64(s.Server.MaxHeaderBytes) +} + +func (s *Server) handleRequest(sess quic.Session, str quic.Stream, decoder *qpack.Decoder, onFrameError func()) requestError { + frame, err := parseNextFrame(str) + if err != nil { + return newStreamError(errorRequestIncomplete, err) + } + hf, ok := frame.(*headersFrame) + if !ok { + return newConnError(errorFrameUnexpected, errors.New("expected first frame to be a HEADERS frame")) + } + if hf.Length > s.maxHeaderBytes() { + return newStreamError(errorFrameError, fmt.Errorf("HEADERS frame too large: %d bytes (max: %d)", hf.Length, s.maxHeaderBytes())) + } + headerBlock := make([]byte, hf.Length) + if _, err := io.ReadFull(str, headerBlock); err != nil { + return newStreamError(errorRequestIncomplete, err) + } + hfs, err := decoder.DecodeFull(headerBlock) + if err != nil { + // TODO: use the right error code + return newConnError(errorGeneralProtocolError, err) + } + req, err := requestFromHeaders(hfs) + if err != nil { + // TODO: use the right error code + return newStreamError(errorGeneralProtocolError, err) + } + + req.RemoteAddr = sess.RemoteAddr().String() + req.Body = newRequestBody(str, onFrameError) + + if s.logger.Debug() { + s.logger.Infof("%s %s%s, on stream %d", req.Method, req.Host, req.RequestURI, str.StreamID()) + } else { + s.logger.Infof("%s %s%s", req.Method, req.Host, req.RequestURI) + } + + ctx := str.Context() + ctx = context.WithValue(ctx, ServerContextKey, s) + ctx = context.WithValue(ctx, http.LocalAddrContextKey, sess.LocalAddr()) + req = req.WithContext(ctx) + r := newResponseWriter(str, s.logger) + defer func() { + if !r.usedDataStream() { + r.Flush() + } + }() + handler := s.Handler + if handler == nil { + handler = http.DefaultServeMux + } + + var panicked bool + func() { + defer func() { + if p := recover(); p != nil { + // Copied from net/http/server.go + const size = 64 << 10 + buf := make([]byte, size) + buf = buf[:runtime.Stack(buf, false)] + s.logger.Errorf("http: panic serving: %v\n%s", p, buf) + panicked = true + } + }() + handler.ServeHTTP(r, req) + }() + + if !r.usedDataStream() { + if panicked { + r.WriteHeader(500) + } else { + r.WriteHeader(200) + } + // If the EOF was read by the handler, CancelRead() is a no-op. + str.CancelRead(quic.StreamErrorCode(errorNoError)) + } + return requestError{} +} + +// Close the server immediately, aborting requests and sending CONNECTION_CLOSE frames to connected clients. +// Close in combination with ListenAndServe() (instead of Serve()) may race if it is called before a UDP socket is established. +func (s *Server) Close() error { + s.closed.Set(true) + + s.mutex.Lock() + defer s.mutex.Unlock() + + var err error + for ln := range s.listeners { + if cerr := (*ln).Close(); cerr != nil && err == nil { + err = cerr + } + } + return err +} + +// CloseGracefully shuts down the server gracefully. The server sends a GOAWAY frame first, then waits for either timeout to trigger, or for all running requests to complete. +// CloseGracefully in combination with ListenAndServe() (instead of Serve()) may race if it is called before a UDP socket is established. +func (s *Server) CloseGracefully(timeout time.Duration) error { + // TODO: implement + return nil +} + +// SetQuicHeaders can be used to set the proper headers that announce that this server supports QUIC. +// The values that are set depend on the port information from s.Server.Addr, and currently look like this (if Addr has port 443): +// Alt-Svc: quic=":443"; ma=2592000; v="33,32,31,30" +func (s *Server) SetQuicHeaders(hdr http.Header) error { + port := atomic.LoadUint32(&s.Port) + + if port == 0 { + // Extract port from s.Server.Addr + _, portStr, err := net.SplitHostPort(s.Server.Addr) + if err != nil { + return err + } + portInt, err := net.LookupPort("tcp", portStr) + if err != nil { + return err + } + port = uint32(portInt) + atomic.StoreUint32(&s.Port, port) + } + + // This code assumes that we will use protocol.SupportedVersions if no quic.Config is passed. + supportedVersions := protocol.SupportedVersions + if s.QuicConfig != nil && len(s.QuicConfig.Versions) > 0 { + supportedVersions = s.QuicConfig.Versions + } + altSvc := make([]string, 0, len(supportedVersions)) + for _, version := range supportedVersions { + v := versionToALPN(version) + if len(v) > 0 { + altSvc = append(altSvc, fmt.Sprintf(`%s=":%d"; ma=2592000`, v, port)) + } + } + hdr.Add("Alt-Svc", strings.Join(altSvc, ",")) + return nil +} + +// ListenAndServeQUIC listens on the UDP network address addr and calls the +// handler for HTTP/3 requests on incoming connections. http.DefaultServeMux is +// used when handler is nil. +func ListenAndServeQUIC(addr, certFile, keyFile string, handler http.Handler) error { + server := &Server{ + Server: &http.Server{ + Addr: addr, + Handler: handler, + }, + } + return server.ListenAndServeTLS(certFile, keyFile) +} + +// ListenAndServe listens on the given network address for both, TLS and QUIC +// connetions in parallel. It returns if one of the two returns an error. +// http.DefaultServeMux is used when handler is nil. +// The correct Alt-Svc headers for QUIC are set. +func ListenAndServe(addr, certFile, keyFile string, handler http.Handler) error { + // Load certs + var err error + certs := make([]tls.Certificate, 1) + certs[0], err = tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + return err + } + // We currently only use the cert-related stuff from tls.Config, + // so we don't need to make a full copy. + config := &tls.Config{ + Certificates: certs, + } + + // Open the listeners + udpAddr, err := net.ResolveUDPAddr("udp", addr) + if err != nil { + return err + } + udpConn, err := net.ListenUDP("udp", udpAddr) + if err != nil { + return err + } + defer udpConn.Close() + + tcpAddr, err := net.ResolveTCPAddr("tcp", addr) + if err != nil { + return err + } + tcpConn, err := net.ListenTCP("tcp", tcpAddr) + if err != nil { + return err + } + defer tcpConn.Close() + + tlsConn := tls.NewListener(tcpConn, config) + defer tlsConn.Close() + + // Start the servers + httpServer := &http.Server{ + Addr: addr, + TLSConfig: config, + } + + quicServer := &Server{ + Server: httpServer, + } + + if handler == nil { + handler = http.DefaultServeMux + } + httpServer.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + quicServer.SetQuicHeaders(w.Header()) + handler.ServeHTTP(w, r) + }) + + hErr := make(chan error) + qErr := make(chan error) + go func() { + hErr <- httpServer.Serve(tlsConn) + }() + go func() { + qErr <- quicServer.Serve(udpConn) + }() + + select { + case err := <-hErr: + quicServer.Close() + return err + case err := <-qErr: + // Cannot close the HTTP server or wait for requests to complete properly :/ + return err + } +} diff --git a/vendor/github.com/marten-seemann/qpack/.codecov.yml b/vendor/github.com/marten-seemann/qpack/.codecov.yml new file mode 100644 index 000000000..00064af33 --- /dev/null +++ b/vendor/github.com/marten-seemann/qpack/.codecov.yml @@ -0,0 +1,7 @@ +coverage: + round: nearest + status: + project: + default: + threshold: 1 + patch: false diff --git a/vendor/github.com/marten-seemann/qpack/.gitignore b/vendor/github.com/marten-seemann/qpack/.gitignore new file mode 100644 index 000000000..66c189a09 --- /dev/null +++ b/vendor/github.com/marten-seemann/qpack/.gitignore @@ -0,0 +1,6 @@ +fuzzing/*.zip +fuzzing/coverprofile +fuzzing/crashers +fuzzing/sonarprofile +fuzzing/suppressions +fuzzing/corpus/ diff --git a/vendor/github.com/marten-seemann/qpack/.gitmodules b/vendor/github.com/marten-seemann/qpack/.gitmodules new file mode 100644 index 000000000..5ac16f084 --- /dev/null +++ b/vendor/github.com/marten-seemann/qpack/.gitmodules @@ -0,0 +1,3 @@ +[submodule "integrationtests/interop/qifs"] + path = integrationtests/interop/qifs + url = https://github.com/qpackers/qifs.git diff --git a/vendor/github.com/marten-seemann/qpack/.golangci.yml b/vendor/github.com/marten-seemann/qpack/.golangci.yml new file mode 100644 index 000000000..4a91adc77 --- /dev/null +++ b/vendor/github.com/marten-seemann/qpack/.golangci.yml @@ -0,0 +1,27 @@ +run: +linters-settings: +linters: + disable-all: true + enable: + - asciicheck + - deadcode + - exhaustive + - exportloopref + - goconst + - gofmt # redundant, since gofmt *should* be a no-op after gofumpt + - gofumpt + - goimports + - gosimple + - ineffassign + - misspell + - prealloc + - scopelint + - staticcheck + - stylecheck + - structcheck + - unconvert + - unparam + - unused + - varcheck + - vet + diff --git a/vendor/github.com/marten-seemann/qpack/LICENSE.md b/vendor/github.com/marten-seemann/qpack/LICENSE.md new file mode 100644 index 000000000..1ac5a2d9a --- /dev/null +++ b/vendor/github.com/marten-seemann/qpack/LICENSE.md @@ -0,0 +1,7 @@ +Copyright 2019 Marten Seemann + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/marten-seemann/qpack/README.md b/vendor/github.com/marten-seemann/qpack/README.md new file mode 100644 index 000000000..a621825ac --- /dev/null +++ b/vendor/github.com/marten-seemann/qpack/README.md @@ -0,0 +1,21 @@ +# QPACK + +[![Godoc Reference](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](https://godoc.org/github.com/marten-seemann/qpack) +[![CircleCI Build Status](https://img.shields.io/circleci/project/github/marten-seemann/qpack.svg?style=flat-square&label=CircleCI+build)](https://circleci.com/gh/marten-seemann/qpack) +[![Code Coverage](https://img.shields.io/codecov/c/github/marten-seemann/qpack/master.svg?style=flat-square)](https://codecov.io/gh/marten-seemann/qpack) + +This is a minimal QPACK implementation in Go. It is minimal in the sense that it doesn't use the dynamic table at all, but just the static table and (Huffman encoded) string literals. Wherever possible, it reuses code from the [HPACK implementation in the Go standard library](https://github.com/golang/net/tree/master/http2/hpack). + +It should be able to interoperate with other QPACK implemetations (both encoders and decoders), however it won't achieve a high compression efficiency. + +## Running the interop tests + +Install the [QPACK interop files](https://github.com/qpackers/qifs/) by running +```bash +git submodule update --init --recursive +``` + +Then run the tests: +```bash +ginkgo -r integrationtests +``` diff --git a/vendor/github.com/marten-seemann/qpack/decoder.go b/vendor/github.com/marten-seemann/qpack/decoder.go new file mode 100644 index 000000000..c90019413 --- /dev/null +++ b/vendor/github.com/marten-seemann/qpack/decoder.go @@ -0,0 +1,271 @@ +package qpack + +import ( + "bytes" + "errors" + "fmt" + "sync" + + "golang.org/x/net/http2/hpack" +) + +// A decodingError is something the spec defines as a decoding error. +type decodingError struct { + err error +} + +func (de decodingError) Error() string { + return fmt.Sprintf("decoding error: %v", de.err) +} + +// An invalidIndexError is returned when an encoder references a table +// entry before the static table or after the end of the dynamic table. +type invalidIndexError int + +func (e invalidIndexError) Error() string { + return fmt.Sprintf("invalid indexed representation index %d", int(e)) +} + +var errNoDynamicTable = decodingError{errors.New("no dynamic table")} + +// errNeedMore is an internal sentinel error value that means the +// buffer is truncated and we need to read more data before we can +// continue parsing. +var errNeedMore = errors.New("need more data") + +// A Decoder is the decoding context for incremental processing of +// header blocks. +type Decoder struct { + mutex sync.Mutex + + emitFunc func(f HeaderField) + + readRequiredInsertCount bool + readDeltaBase bool + + // buf is the unparsed buffer. It's only written to + // saveBuf if it was truncated in the middle of a header + // block. Because it's usually not owned, we can only + // process it under Write. + buf []byte // not owned; only valid during Write + + // saveBuf is previous data passed to Write which we weren't able + // to fully parse before. Unlike buf, we own this data. + saveBuf bytes.Buffer +} + +// NewDecoder returns a new decoder +// The emitFunc will be called for each valid field parsed, +// in the same goroutine as calls to Write, before Write returns. +func NewDecoder(emitFunc func(f HeaderField)) *Decoder { + return &Decoder{emitFunc: emitFunc} +} + +func (d *Decoder) Write(p []byte) (int, error) { + if len(p) == 0 { + return 0, nil + } + + d.mutex.Lock() + n, err := d.writeLocked(p) + d.mutex.Unlock() + return n, err +} + +func (d *Decoder) writeLocked(p []byte) (int, error) { + // Only copy the data if we have to. Optimistically assume + // that p will contain a complete header block. + if d.saveBuf.Len() == 0 { + d.buf = p + } else { + d.saveBuf.Write(p) + d.buf = d.saveBuf.Bytes() + d.saveBuf.Reset() + } + + if err := d.decode(); err != nil { + if err != errNeedMore { + return 0, err + } + // TODO: limit the size of the buffer + d.saveBuf.Write(d.buf) + } + return len(p), nil +} + +// DecodeFull decodes an entire block. +func (d *Decoder) DecodeFull(p []byte) ([]HeaderField, error) { + if len(p) == 0 { + return []HeaderField{}, nil + } + + d.mutex.Lock() + defer d.mutex.Unlock() + + saveFunc := d.emitFunc + defer func() { d.emitFunc = saveFunc }() + + var hf []HeaderField + d.emitFunc = func(f HeaderField) { hf = append(hf, f) } + if _, err := d.writeLocked(p); err != nil { + return nil, err + } + if err := d.Close(); err != nil { + return nil, err + } + return hf, nil +} + +// Close declares that the decoding is complete and resets the Decoder +// to be reused again for a new header block. If there is any remaining +// data in the decoder's buffer, Close returns an error. +func (d *Decoder) Close() error { + if d.saveBuf.Len() > 0 { + d.saveBuf.Reset() + return decodingError{errors.New("truncated headers")} + } + d.readRequiredInsertCount = false + d.readDeltaBase = false + return nil +} + +func (d *Decoder) decode() error { + if !d.readRequiredInsertCount { + requiredInsertCount, rest, err := readVarInt(8, d.buf) + if err != nil { + return err + } + d.readRequiredInsertCount = true + if requiredInsertCount != 0 { + return decodingError{errors.New("expected Required Insert Count to be zero")} + } + d.buf = rest + } + if !d.readDeltaBase { + base, rest, err := readVarInt(7, d.buf) + if err != nil { + return err + } + d.readDeltaBase = true + if base != 0 { + return decodingError{errors.New("expected Base to be zero")} + } + d.buf = rest + } + if len(d.buf) == 0 { + return errNeedMore + } + + for len(d.buf) > 0 { + b := d.buf[0] + var err error + switch { + case b&0x80 > 0: // 1xxxxxxx + err = d.parseIndexedHeaderField() + case b&0xc0 == 0x40: // 01xxxxxx + err = d.parseLiteralHeaderField() + case b&0xe0 == 0x20: // 001xxxxx + err = d.parseLiteralHeaderFieldWithoutNameReference() + default: + err = fmt.Errorf("unexpected type byte: %#x", b) + } + if err != nil { + return err + } + } + return nil +} + +func (d *Decoder) parseIndexedHeaderField() error { + buf := d.buf + if buf[0]&0x40 == 0 { + return errNoDynamicTable + } + index, buf, err := readVarInt(6, buf) + if err != nil { + return err + } + hf, ok := d.at(index) + if !ok { + return decodingError{invalidIndexError(index)} + } + d.emitFunc(hf) + d.buf = buf + return nil +} + +func (d *Decoder) parseLiteralHeaderField() error { + buf := d.buf + if buf[0]&0x20 > 0 || buf[0]&0x10 == 0 { + return errNoDynamicTable + } + index, buf, err := readVarInt(4, buf) + if err != nil { + return err + } + hf, ok := d.at(index) + if !ok { + return decodingError{invalidIndexError(index)} + } + if len(buf) == 0 { + return errNeedMore + } + usesHuffman := buf[0]&0x80 > 0 + val, buf, err := d.readString(buf, 7, usesHuffman) + if err != nil { + return err + } + hf.Value = val + d.emitFunc(hf) + d.buf = buf + return nil +} + +func (d *Decoder) parseLiteralHeaderFieldWithoutNameReference() error { + buf := d.buf + usesHuffmanForName := buf[0]&0x8 > 0 + name, buf, err := d.readString(buf, 3, usesHuffmanForName) + if err != nil { + return err + } + if len(buf) == 0 { + return errNeedMore + } + usesHuffmanForVal := buf[0]&0x80 > 0 + val, buf, err := d.readString(buf, 7, usesHuffmanForVal) + if err != nil { + return err + } + d.emitFunc(HeaderField{Name: name, Value: val}) + d.buf = buf + return nil +} + +func (d *Decoder) readString(buf []byte, n uint8, usesHuffman bool) (string, []byte, error) { + l, buf, err := readVarInt(n, buf) + if err != nil { + return "", nil, err + } + if uint64(len(buf)) < l { + return "", nil, errNeedMore + } + var val string + if usesHuffman { + var err error + val, err = hpack.HuffmanDecodeToString(buf[:l]) + if err != nil { + return "", nil, err + } + } else { + val = string(buf[:l]) + } + buf = buf[l:] + return val, buf, nil +} + +func (d *Decoder) at(i uint64) (hf HeaderField, ok bool) { + if i >= uint64(len(staticTableEntries)) { + return + } + return staticTableEntries[i], true +} diff --git a/vendor/github.com/marten-seemann/qpack/encoder.go b/vendor/github.com/marten-seemann/qpack/encoder.go new file mode 100644 index 000000000..13e0ad2c7 --- /dev/null +++ b/vendor/github.com/marten-seemann/qpack/encoder.go @@ -0,0 +1,95 @@ +package qpack + +import ( + "io" + + "golang.org/x/net/http2/hpack" +) + +// An Encoder performs QPACK encoding. +type Encoder struct { + wrotePrefix bool + + w io.Writer + buf []byte +} + +// NewEncoder returns a new Encoder which performs QPACK encoding. An +// encoded data is written to w. +func NewEncoder(w io.Writer) *Encoder { + return &Encoder{w: w} +} + +// WriteField encodes f into a single Write to e's underlying Writer. +// This function may also produce bytes for the Header Block Prefix +// if necessary. If produced, it is done before encoding f. +func (e *Encoder) WriteField(f HeaderField) error { + // write the Header Block Prefix + if !e.wrotePrefix { + e.buf = appendVarInt(e.buf, 8, 0) + e.buf = appendVarInt(e.buf, 7, 0) + e.wrotePrefix = true + } + + idxAndVals, nameFound := encoderMap[f.Name] + if nameFound { + if idxAndVals.values == nil { + if len(f.Value) == 0 { + e.writeIndexedField(idxAndVals.idx) + } else { + e.writeLiteralFieldWithNameReference(&f, idxAndVals.idx) + } + } else { + valIdx, valueFound := idxAndVals.values[f.Value] + if valueFound { + e.writeIndexedField(valIdx) + } else { + e.writeLiteralFieldWithNameReference(&f, idxAndVals.idx) + } + } + } else { + e.writeLiteralFieldWithoutNameReference(f) + } + + e.w.Write(e.buf) + e.buf = e.buf[:0] + return nil +} + +// Close declares that the encoding is complete and resets the Encoder +// to be reused again for a new header block. +func (e *Encoder) Close() error { + e.wrotePrefix = false + return nil +} + +func (e *Encoder) writeLiteralFieldWithoutNameReference(f HeaderField) { + offset := len(e.buf) + e.buf = appendVarInt(e.buf, 3, hpack.HuffmanEncodeLength(f.Name)) + e.buf[offset] ^= 0x20 ^ 0x8 + e.buf = hpack.AppendHuffmanString(e.buf, f.Name) + offset = len(e.buf) + e.buf = appendVarInt(e.buf, 7, hpack.HuffmanEncodeLength(f.Value)) + e.buf[offset] ^= 0x80 + e.buf = hpack.AppendHuffmanString(e.buf, f.Value) +} + +// Encodes a header field whose name is present in one of the tables. +func (e *Encoder) writeLiteralFieldWithNameReference(f *HeaderField, id uint8) { + offset := len(e.buf) + e.buf = appendVarInt(e.buf, 4, uint64(id)) + // Set the 01NTxxxx pattern, forcing N to 0 and T to 1 + e.buf[offset] ^= 0x50 + offset = len(e.buf) + e.buf = appendVarInt(e.buf, 7, hpack.HuffmanEncodeLength(f.Value)) + e.buf[offset] ^= 0x80 + e.buf = hpack.AppendHuffmanString(e.buf, f.Value) +} + +// Encodes an indexed field, meaning it's entirely defined in one of the tables. +func (e *Encoder) writeIndexedField(id uint8) { + offset := len(e.buf) + e.buf = appendVarInt(e.buf, 6, uint64(id)) + // Set the 1Txxxxxx pattern, forcing T to 1 + e.buf[offset] ^= 0xc0 +} diff --git a/vendor/github.com/marten-seemann/qpack/header_field.go b/vendor/github.com/marten-seemann/qpack/header_field.go new file mode 100644 index 000000000..4c043a992 --- /dev/null +++ b/vendor/github.com/marten-seemann/qpack/header_field.go @@ -0,0 +1,16 @@ +package qpack + +// A HeaderField is a name-value pair. Both the name and value are +// treated as opaque sequences of octets. +type HeaderField struct { + Name string + Value string +} + +// IsPseudo reports whether the header field is an HTTP3 pseudo header. +// That is, it reports whether it starts with a colon. +// It is not otherwise guaranteed to be a valid pseudo header field, +// though. +func (hf HeaderField) IsPseudo() bool { + return len(hf.Name) != 0 && hf.Name[0] == ':' +} diff --git a/vendor/github.com/marten-seemann/qpack/static_table.go b/vendor/github.com/marten-seemann/qpack/static_table.go new file mode 100644 index 000000000..930e83c7c --- /dev/null +++ b/vendor/github.com/marten-seemann/qpack/static_table.go @@ -0,0 +1,254 @@ +package qpack + +var staticTableEntries = [...]HeaderField{ + {Name: ":authority"}, + {Name: ":path", Value: "/"}, + {Name: "age", Value: "0"}, + {Name: "content-disposition"}, + {Name: "content-length", Value: "0"}, + {Name: "cookie"}, + {Name: "date"}, + {Name: "etag"}, + {Name: "if-modified-since"}, + {Name: "if-none-match"}, + {Name: "last-modified"}, + {Name: "link"}, + {Name: "location"}, + {Name: "referer"}, + {Name: "set-cookie"}, + {Name: ":method", Value: "CONNECT"}, + {Name: ":method", Value: "DELETE"}, + {Name: ":method", Value: "GET"}, + {Name: ":method", Value: "HEAD"}, + {Name: ":method", Value: "OPTIONS"}, + {Name: ":method", Value: "POST"}, + {Name: ":method", Value: "PUT"}, + {Name: ":scheme", Value: "http"}, + {Name: ":scheme", Value: "https"}, + {Name: ":status", Value: "103"}, + {Name: ":status", Value: "200"}, + {Name: ":status", Value: "304"}, + {Name: ":status", Value: "404"}, + {Name: ":status", Value: "503"}, + {Name: "accept", Value: "*/*"}, + {Name: "accept", Value: "application/dns-message"}, + {Name: "accept-encoding", Value: "gzip, deflate, br"}, + {Name: "accept-ranges", Value: "bytes"}, + {Name: "access-control-allow-headers", Value: "cache-control"}, + {Name: "access-control-allow-headers", Value: "content-type"}, + {Name: "access-control-allow-origin", Value: "*"}, + {Name: "cache-control", Value: "max-age=0"}, + {Name: "cache-control", Value: "max-age=2592000"}, + {Name: "cache-control", Value: "max-age=604800"}, + {Name: "cache-control", Value: "no-cache"}, + {Name: "cache-control", Value: "no-store"}, + {Name: "cache-control", Value: "public, max-age=31536000"}, + {Name: "content-encoding", Value: "br"}, + {Name: "content-encoding", Value: "gzip"}, + {Name: "content-type", Value: "application/dns-message"}, + {Name: "content-type", Value: "application/javascript"}, + {Name: "content-type", Value: "application/json"}, + {Name: "content-type", Value: "application/x-www-form-urlencoded"}, + {Name: "content-type", Value: "image/gif"}, + {Name: "content-type", Value: "image/jpeg"}, + {Name: "content-type", Value: "image/png"}, + {Name: "content-type", Value: "text/css"}, + {Name: "content-type", Value: "text/html; charset=utf-8"}, + {Name: "content-type", Value: "text/plain"}, + {Name: "content-type", Value: "text/plain;charset=utf-8"}, + {Name: "range", Value: "bytes=0-"}, + {Name: "strict-transport-security", Value: "max-age=31536000"}, + {Name: "strict-transport-security", Value: "max-age=31536000; includesubdomains"}, + {Name: "strict-transport-security", Value: "max-age=31536000; includesubdomains; preload"}, + {Name: "vary", Value: "accept-encoding"}, + {Name: "vary", Value: "origin"}, + {Name: "x-content-type-options", Value: "nosniff"}, + {Name: "x-xss-protection", Value: "1; mode=block"}, + {Name: ":status", Value: "100"}, + {Name: ":status", Value: "204"}, + {Name: ":status", Value: "206"}, + {Name: ":status", Value: "302"}, + {Name: ":status", Value: "400"}, + {Name: ":status", Value: "403"}, + {Name: ":status", Value: "421"}, + {Name: ":status", Value: "425"}, + {Name: ":status", Value: "500"}, + {Name: "accept-language"}, + {Name: "access-control-allow-credentials", Value: "FALSE"}, + {Name: "access-control-allow-credentials", Value: "TRUE"}, + {Name: "access-control-allow-headers", Value: "*"}, + {Name: "access-control-allow-methods", Value: "get"}, + {Name: "access-control-allow-methods", Value: "get, post, options"}, + {Name: "access-control-allow-methods", Value: "options"}, + {Name: "access-control-expose-headers", Value: "content-length"}, + {Name: "access-control-request-headers", Value: "content-type"}, + {Name: "access-control-request-method", Value: "get"}, + {Name: "access-control-request-method", Value: "post"}, + {Name: "alt-svc", Value: "clear"}, + {Name: "authorization"}, + {Name: "content-security-policy", Value: "script-src 'none'; object-src 'none'; base-uri 'none'"}, + {Name: "early-data", Value: "1"}, + {Name: "expect-ct"}, + {Name: "forwarded"}, + {Name: "if-range"}, + {Name: "origin"}, + {Name: "purpose", Value: "prefetch"}, + {Name: "server"}, + {Name: "timing-allow-origin", Value: "*"}, + {Name: "upgrade-insecure-requests", Value: "1"}, + {Name: "user-agent"}, + {Name: "x-forwarded-for"}, + {Name: "x-frame-options", Value: "deny"}, + {Name: "x-frame-options", Value: "sameorigin"}, +} + +// Only needed for tests. +// use go:linkname to retrieve the static table. +//nolint:deadcode,unused +func getStaticTable() []HeaderField { + return staticTableEntries[:] +} + +type indexAndValues struct { + idx uint8 + values map[string]uint8 +} + +// A map of the header names from the static table to their index in the table. +// This is used by the encoder to quickly find if a header is in the static table +// and what value should be used to encode it. +// There's a second level of mapping for the headers that have some predefined +// values in the static table. +var encoderMap = map[string]indexAndValues{ + ":authority": {0, nil}, + ":path": {1, map[string]uint8{"/": 1}}, + "age": {2, map[string]uint8{"0": 2}}, + "content-disposition": {3, nil}, + "content-length": {4, map[string]uint8{"0": 4}}, + "cookie": {5, nil}, + "date": {6, nil}, + "etag": {7, nil}, + "if-modified-since": {8, nil}, + "if-none-match": {9, nil}, + "last-modified": {10, nil}, + "link": {11, nil}, + "location": {12, nil}, + "referer": {13, nil}, + "set-cookie": {14, nil}, + ":method": {15, map[string]uint8{ + "CONNECT": 15, + "DELETE": 16, + "GET": 17, + "HEAD": 18, + "OPTIONS": 19, + "POST": 20, + "PUT": 21, + }}, + ":scheme": {22, map[string]uint8{ + "http": 22, + "https": 23, + }}, + ":status": {24, map[string]uint8{ + "103": 24, + "200": 25, + "304": 26, + "404": 27, + "503": 28, + "100": 63, + "204": 64, + "206": 65, + "302": 66, + "400": 67, + "403": 68, + "421": 69, + "425": 70, + "500": 71, + }}, + "accept": {29, map[string]uint8{ + "*/*": 29, + "application/dns-message": 30, + }}, + "accept-encoding": {31, map[string]uint8{"gzip, deflate, br": 31}}, + "accept-ranges": {32, map[string]uint8{"bytes": 32}}, + "access-control-allow-headers": {33, map[string]uint8{ + "cache-control": 33, + "content-type": 34, + "*": 75, + }}, + "access-control-allow-origin": {35, map[string]uint8{"*": 35}}, + "cache-control": {36, map[string]uint8{ + "max-age=0": 36, + "max-age=2592000": 37, + "max-age=604800": 38, + "no-cache": 39, + "no-store": 40, + "public, max-age=31536000": 41, + }}, + "content-encoding": {42, map[string]uint8{ + "br": 42, + "gzip": 43, + }}, + "content-type": {44, map[string]uint8{ + "application/dns-message": 44, + "application/javascript": 45, + "application/json": 46, + "application/x-www-form-urlencoded": 47, + "image/gif": 48, + "image/jpeg": 49, + "image/png": 50, + "text/css": 51, + "text/html; charset=utf-8": 52, + "text/plain": 53, + "text/plain;charset=utf-8": 54, + }}, + "range": {55, map[string]uint8{"bytes=0-": 55}}, + "strict-transport-security": {56, map[string]uint8{ + "max-age=31536000": 56, + "max-age=31536000; includesubdomains": 57, + "max-age=31536000; includesubdomains; preload": 58, + }}, + "vary": {59, map[string]uint8{ + "accept-encoding": 59, + "origin": 60, + }}, + "x-content-type-options": {61, map[string]uint8{"nosniff": 61}}, + "x-xss-protection": {62, map[string]uint8{"1; mode=block": 62}}, + // ":status" is duplicated and takes index 63 to 71 + "accept-language": {72, nil}, + "access-control-allow-credentials": {73, map[string]uint8{ + "FALSE": 73, + "TRUE": 74, + }}, + // "access-control-allow-headers" is duplicated and takes index 75 + "access-control-allow-methods": {76, map[string]uint8{ + "get": 76, + "get, post, options": 77, + "options": 78, + }}, + "access-control-expose-headers": {79, map[string]uint8{"content-length": 79}}, + "access-control-request-headers": {80, map[string]uint8{"content-type": 80}}, + "access-control-request-method": {81, map[string]uint8{ + "get": 81, + "post": 82, + }}, + "alt-svc": {83, map[string]uint8{"clear": 83}}, + "authorization": {84, nil}, + "content-security-policy": {85, map[string]uint8{ + "script-src 'none'; object-src 'none'; base-uri 'none'": 85, + }}, + "early-data": {86, map[string]uint8{"1": 86}}, + "expect-ct": {87, nil}, + "forwarded": {88, nil}, + "if-range": {89, nil}, + "origin": {90, nil}, + "purpose": {91, map[string]uint8{"prefetch": 91}}, + "server": {92, nil}, + "timing-allow-origin": {93, map[string]uint8{"*": 93}}, + "upgrade-insecure-requests": {94, map[string]uint8{"1": 94}}, + "user-agent": {95, nil}, + "x-forwarded-for": {96, nil}, + "x-frame-options": {97, map[string]uint8{ + "deny": 97, + "sameorigin": 98, + }}, +} diff --git a/vendor/github.com/marten-seemann/qpack/varint.go b/vendor/github.com/marten-seemann/qpack/varint.go new file mode 100644 index 000000000..28d71122e --- /dev/null +++ b/vendor/github.com/marten-seemann/qpack/varint.go @@ -0,0 +1,66 @@ +package qpack + +// copied from the Go standard library HPACK implementation + +import "errors" + +var errVarintOverflow = errors.New("varint integer overflow") + +// appendVarInt appends i, as encoded in variable integer form using n +// bit prefix, to dst and returns the extended buffer. +// +// See +// http://http2.github.io/http2-spec/compression.html#integer.representation +func appendVarInt(dst []byte, n byte, i uint64) []byte { + k := uint64((1 << n) - 1) + if i < k { + return append(dst, byte(i)) + } + dst = append(dst, byte(k)) + i -= k + for ; i >= 128; i >>= 7 { + dst = append(dst, byte(0x80|(i&0x7f))) + } + return append(dst, byte(i)) +} + +// readVarInt reads an unsigned variable length integer off the +// beginning of p. n is the parameter as described in +// http://http2.github.io/http2-spec/compression.html#rfc.section.5.1. +// +// n must always be between 1 and 8. +// +// The returned remain buffer is either a smaller suffix of p, or err != nil. +// The error is errNeedMore if p doesn't contain a complete integer. +func readVarInt(n byte, p []byte) (i uint64, remain []byte, err error) { + if n < 1 || n > 8 { + panic("bad n") + } + if len(p) == 0 { + return 0, p, errNeedMore + } + i = uint64(p[0]) + if n < 8 { + i &= (1 << uint64(n)) - 1 + } + if i < (1< 0 { + b := p[0] + p = p[1:] + i += uint64(b&127) << m + if b&128 == 0 { + return i, p, nil + } + m += 7 + if m >= 63 { // TODO: proper overflow check. making this up. + return 0, origP, errVarintOverflow + } + } + return 0, origP, errNeedMore +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 033c8033d..cab309a72 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -45,6 +45,7 @@ github.com/jessevdk/go-flags # github.com/lucas-clemente/quic-go v0.24.0 ## explicit; go 1.16 github.com/lucas-clemente/quic-go +github.com/lucas-clemente/quic-go/http3 github.com/lucas-clemente/quic-go/internal/ackhandler github.com/lucas-clemente/quic-go/internal/congestion github.com/lucas-clemente/quic-go/internal/flowcontrol @@ -57,6 +58,9 @@ github.com/lucas-clemente/quic-go/internal/utils github.com/lucas-clemente/quic-go/internal/wire github.com/lucas-clemente/quic-go/logging github.com/lucas-clemente/quic-go/quicvarint +# github.com/marten-seemann/qpack v0.2.1 +## explicit; go 1.14 +github.com/marten-seemann/qpack # github.com/marten-seemann/qtls-go1-16 v0.1.4 ## explicit; go 1.16 github.com/marten-seemann/qtls-go1-16