forked from quic-go/quic-go
The length limit is a property of the (request and response) body. As such, it's better implemented there than by wrapping the HTTP stream.
237 lines
7.1 KiB
Go
237 lines
7.1 KiB
Go
package http3
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/quic-go/quic-go"
|
|
"github.com/quic-go/quic-go/internal/protocol"
|
|
|
|
"github.com/quic-go/qpack"
|
|
)
|
|
|
|
// A Stream is an HTTP/3 request stream.
|
|
// When writing to and reading from the stream, data is framed in HTTP/3 DATA frames.
|
|
type Stream = quic.Stream
|
|
|
|
// A RequestStream is an HTTP/3 request stream.
|
|
// When writing to and reading from the stream, data is framed in HTTP/3 DATA frames.
|
|
type RequestStream interface {
|
|
quic.Stream
|
|
|
|
// SendRequestHeader sends the HTTP request.
|
|
// It is invalid to call it more than once.
|
|
// It is invalid to call it after Write has been called.
|
|
SendRequestHeader(req *http.Request) error
|
|
|
|
// ReadResponse reads the HTTP response from the stream.
|
|
// It is invalid to call it more than once.
|
|
// It doesn't set Response.Request and Response.TLS.
|
|
// It is invalid to call it after Read has been called.
|
|
ReadResponse() (*http.Response, error)
|
|
}
|
|
|
|
type stream struct {
|
|
quic.Stream
|
|
conn quic.Connection
|
|
|
|
buf []byte // used as a temporary buffer when writing the HTTP/3 frame headers
|
|
|
|
bytesRemainingInFrame uint64
|
|
}
|
|
|
|
var _ Stream = &stream{}
|
|
|
|
func newStream(str quic.Stream, conn quic.Connection) *stream {
|
|
return &stream{
|
|
Stream: str,
|
|
conn: conn,
|
|
buf: make([]byte, 0, 16),
|
|
}
|
|
}
|
|
|
|
func (s *stream) Read(b []byte) (int, error) {
|
|
if s.bytesRemainingInFrame == 0 {
|
|
parseLoop:
|
|
for {
|
|
frame, err := parseNextFrame(s.Stream, nil)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
switch f := frame.(type) {
|
|
case *headersFrame:
|
|
// skip HEADERS frames
|
|
continue
|
|
case *dataFrame:
|
|
s.bytesRemainingInFrame = f.Length
|
|
break parseLoop
|
|
default:
|
|
s.conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameUnexpected), "")
|
|
// 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 s.bytesRemainingInFrame < uint64(len(b)) {
|
|
n, err = s.Stream.Read(b[:s.bytesRemainingInFrame])
|
|
} else {
|
|
n, err = s.Stream.Read(b)
|
|
}
|
|
s.bytesRemainingInFrame -= uint64(n)
|
|
return n, err
|
|
}
|
|
|
|
func (s *stream) hasMoreData() bool {
|
|
return s.bytesRemainingInFrame > 0
|
|
}
|
|
|
|
func (s *stream) Write(b []byte) (int, error) {
|
|
s.buf = s.buf[:0]
|
|
s.buf = (&dataFrame{Length: uint64(len(b))}).Append(s.buf)
|
|
if _, err := s.Stream.Write(s.buf); err != nil {
|
|
return 0, err
|
|
}
|
|
return s.Stream.Write(b)
|
|
}
|
|
|
|
func (s *stream) StreamID() protocol.StreamID {
|
|
return s.Stream.StreamID()
|
|
}
|
|
|
|
// The stream conforms to the quic.Stream interface, but instead of writing to and reading directly
|
|
// from the QUIC stream, it writes to and reads from the HTTP stream.
|
|
type requestStream struct {
|
|
*stream
|
|
|
|
responseBody io.ReadCloser // set by ReadResponse
|
|
|
|
decoder *qpack.Decoder
|
|
requestWriter *requestWriter
|
|
maxHeaderBytes uint64
|
|
reqDone chan<- struct{}
|
|
disableCompression bool
|
|
|
|
sentRequest bool
|
|
requestedGzip bool
|
|
isConnect bool
|
|
}
|
|
|
|
var _ RequestStream = &requestStream{}
|
|
|
|
func newRequestStream(
|
|
str *stream,
|
|
requestWriter *requestWriter,
|
|
reqDone chan<- struct{},
|
|
decoder *qpack.Decoder,
|
|
disableCompression bool,
|
|
maxHeaderBytes uint64,
|
|
) *requestStream {
|
|
return &requestStream{
|
|
stream: str,
|
|
requestWriter: requestWriter,
|
|
reqDone: reqDone,
|
|
decoder: decoder,
|
|
disableCompression: disableCompression,
|
|
maxHeaderBytes: maxHeaderBytes,
|
|
}
|
|
}
|
|
|
|
func (s *requestStream) Read(b []byte) (int, error) {
|
|
if s.responseBody == nil {
|
|
return 0, errors.New("http3: invalid use of RequestStream.Read: need to call ReadResponse first")
|
|
}
|
|
return s.responseBody.Read(b)
|
|
}
|
|
|
|
func (s *requestStream) SendRequestHeader(req *http.Request) error {
|
|
if s.sentRequest {
|
|
return errors.New("http3: invalid duplicate use of SendRequestHeader")
|
|
}
|
|
if !s.disableCompression && req.Method != http.MethodHead &&
|
|
req.Header.Get("Accept-Encoding") == "" && req.Header.Get("Range") == "" {
|
|
s.requestedGzip = true
|
|
}
|
|
s.isConnect = req.Method == http.MethodConnect
|
|
s.sentRequest = true
|
|
return s.requestWriter.WriteRequestHeader(s.Stream, req, s.requestedGzip)
|
|
}
|
|
|
|
func (s *requestStream) ReadResponse() (*http.Response, error) {
|
|
frame, err := parseNextFrame(s.Stream, nil)
|
|
if err != nil {
|
|
s.Stream.CancelRead(quic.StreamErrorCode(ErrCodeFrameError))
|
|
s.Stream.CancelWrite(quic.StreamErrorCode(ErrCodeFrameError))
|
|
return nil, fmt.Errorf("http3: parsing frame failed: %w", err)
|
|
}
|
|
hf, ok := frame.(*headersFrame)
|
|
if !ok {
|
|
s.conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameUnexpected), "expected first frame to be a HEADERS frame")
|
|
return nil, errors.New("http3: expected first frame to be a HEADERS frame")
|
|
}
|
|
if hf.Length > s.maxHeaderBytes {
|
|
s.Stream.CancelRead(quic.StreamErrorCode(ErrCodeFrameError))
|
|
s.Stream.CancelWrite(quic.StreamErrorCode(ErrCodeFrameError))
|
|
return nil, fmt.Errorf("http3: HEADERS frame too large: %d bytes (max: %d)", hf.Length, s.maxHeaderBytes)
|
|
}
|
|
headerBlock := make([]byte, hf.Length)
|
|
if _, err := io.ReadFull(s.Stream, headerBlock); err != nil {
|
|
s.Stream.CancelRead(quic.StreamErrorCode(ErrCodeRequestIncomplete))
|
|
s.Stream.CancelWrite(quic.StreamErrorCode(ErrCodeRequestIncomplete))
|
|
return nil, fmt.Errorf("http3: failed to read response headers: %w", err)
|
|
}
|
|
hfs, err := s.decoder.DecodeFull(headerBlock)
|
|
if err != nil {
|
|
// TODO: use the right error code
|
|
s.conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeGeneralProtocolError), "")
|
|
return nil, fmt.Errorf("http3: failed to decode response headers: %w", err)
|
|
}
|
|
|
|
res, err := responseFromHeaders(hfs)
|
|
if err != nil {
|
|
s.Stream.CancelRead(quic.StreamErrorCode(ErrCodeMessageError))
|
|
s.Stream.CancelWrite(quic.StreamErrorCode(ErrCodeMessageError))
|
|
return nil, fmt.Errorf("http3: invalid response: %w", err)
|
|
}
|
|
|
|
// Check that the server doesn't send more data in DATA frames than indicated by the Content-Length header (if set).
|
|
// See section 4.1.2 of RFC 9114.
|
|
contentLength := int64(-1)
|
|
if _, ok := res.Header["Content-Length"]; ok && res.ContentLength >= 0 {
|
|
contentLength = res.ContentLength
|
|
}
|
|
respBody := newResponseBody(s.stream, contentLength, s.reqDone)
|
|
|
|
// 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 == http.StatusNoContent
|
|
isSuccessfulConnect := s.isConnect && 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 s.requestedGzip && res.Header.Get("Content-Encoding") == "gzip" {
|
|
res.Header.Del("Content-Encoding")
|
|
res.Header.Del("Content-Length")
|
|
res.ContentLength = -1
|
|
s.responseBody = newGzipReader(respBody)
|
|
res.Uncompressed = true
|
|
} else {
|
|
s.responseBody = respBody
|
|
}
|
|
res.Body = s.responseBody
|
|
return res, nil
|
|
}
|