| // Copyright 2014 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package http2 | |
| import ( | |
| "bytes" | |
| "encoding/binary" | |
| "errors" | |
| "fmt" | |
| "io" | |
| "log" | |
| "slices" | |
| "strings" | |
| "sync" | |
| "golang.org/x/net/http/httpguts" | |
| "golang.org/x/net/http2/hpack" | |
| "golang.org/x/net/internal/httpsfv" | |
| ) | |
| const frameHeaderLen = 9 | |
| var padZeros = make([]byte, 255) // zeros for padding | |
| // A FrameType is a registered frame type as defined in | |
| // https://httpwg.org/specs/rfc7540.html#rfc.section.11.2 and other future | |
| // RFCs. | |
| type FrameType uint8 | |
| const ( | |
| FrameData FrameType = 0x0 | |
| FrameHeaders FrameType = 0x1 | |
| FramePriority FrameType = 0x2 | |
| FrameRSTStream FrameType = 0x3 | |
| FrameSettings FrameType = 0x4 | |
| FramePushPromise FrameType = 0x5 | |
| FramePing FrameType = 0x6 | |
| FrameGoAway FrameType = 0x7 | |
| FrameWindowUpdate FrameType = 0x8 | |
| FrameContinuation FrameType = 0x9 | |
| FramePriorityUpdate FrameType = 0x10 | |
| ) | |
| var frameNames = [...]string{ | |
| FrameData: "DATA", | |
| FrameHeaders: "HEADERS", | |
| FramePriority: "PRIORITY", | |
| FrameRSTStream: "RST_STREAM", | |
| FrameSettings: "SETTINGS", | |
| FramePushPromise: "PUSH_PROMISE", | |
| FramePing: "PING", | |
| FrameGoAway: "GOAWAY", | |
| FrameWindowUpdate: "WINDOW_UPDATE", | |
| FrameContinuation: "CONTINUATION", | |
| FramePriorityUpdate: "PRIORITY_UPDATE", | |
| } | |
| func (t FrameType) String() string { | |
| if int(t) < len(frameNames) { | |
| return frameNames[t] | |
| } | |
| return fmt.Sprintf("UNKNOWN_FRAME_TYPE_%d", t) | |
| } | |
| // Flags is a bitmask of HTTP/2 flags. | |
| // The meaning of flags varies depending on the frame type. | |
| type Flags uint8 | |
| // Has reports whether f contains all (0 or more) flags in v. | |
| func (f Flags) Has(v Flags) bool { | |
| return (f & v) == v | |
| } | |
| // Frame-specific FrameHeader flag bits. | |
| const ( | |
| // Data Frame | |
| FlagDataEndStream Flags = 0x1 | |
| FlagDataPadded Flags = 0x8 | |
| // Headers Frame | |
| FlagHeadersEndStream Flags = 0x1 | |
| FlagHeadersEndHeaders Flags = 0x4 | |
| FlagHeadersPadded Flags = 0x8 | |
| FlagHeadersPriority Flags = 0x20 | |
| // Settings Frame | |
| FlagSettingsAck Flags = 0x1 | |
| // Ping Frame | |
| FlagPingAck Flags = 0x1 | |
| // Continuation Frame | |
| FlagContinuationEndHeaders Flags = 0x4 | |
| FlagPushPromiseEndHeaders Flags = 0x4 | |
| FlagPushPromisePadded Flags = 0x8 | |
| ) | |
| var flagName = map[FrameType]map[Flags]string{ | |
| FrameData: { | |
| FlagDataEndStream: "END_STREAM", | |
| FlagDataPadded: "PADDED", | |
| }, | |
| FrameHeaders: { | |
| FlagHeadersEndStream: "END_STREAM", | |
| FlagHeadersEndHeaders: "END_HEADERS", | |
| FlagHeadersPadded: "PADDED", | |
| FlagHeadersPriority: "PRIORITY", | |
| }, | |
| FrameSettings: { | |
| FlagSettingsAck: "ACK", | |
| }, | |
| FramePing: { | |
| FlagPingAck: "ACK", | |
| }, | |
| FrameContinuation: { | |
| FlagContinuationEndHeaders: "END_HEADERS", | |
| }, | |
| FramePushPromise: { | |
| FlagPushPromiseEndHeaders: "END_HEADERS", | |
| FlagPushPromisePadded: "PADDED", | |
| }, | |
| } | |
| // a frameParser parses a frame given its FrameHeader and payload | |
| // bytes. The length of payload will always equal fh.Length (which | |
| // might be 0). | |
| type frameParser func(fc *frameCache, fh FrameHeader, countError func(string), payload []byte) (Frame, error) | |
| var frameParsers = [...]frameParser{ | |
| FrameData: parseDataFrame, | |
| FrameHeaders: parseHeadersFrame, | |
| FramePriority: parsePriorityFrame, | |
| FrameRSTStream: parseRSTStreamFrame, | |
| FrameSettings: parseSettingsFrame, | |
| FramePushPromise: parsePushPromise, | |
| FramePing: parsePingFrame, | |
| FrameGoAway: parseGoAwayFrame, | |
| FrameWindowUpdate: parseWindowUpdateFrame, | |
| FrameContinuation: parseContinuationFrame, | |
| FramePriorityUpdate: parsePriorityUpdateFrame, | |
| } | |
| func typeFrameParser(t FrameType) frameParser { | |
| if int(t) < len(frameParsers) { | |
| if f := frameParsers[t]; f != nil { | |
| return f | |
| } | |
| } | |
| return parseUnknownFrame | |
| } | |
| // A FrameHeader is the 9 byte header of all HTTP/2 frames. | |
| // | |
| // See https://httpwg.org/specs/rfc7540.html#FrameHeader | |
| type FrameHeader struct { | |
| valid bool // caller can access []byte fields in the Frame | |
| // Type is the 1 byte frame type. There are ten standard frame | |
| // types, but extension frame types may be written by WriteRawFrame | |
| // and will be returned by ReadFrame (as UnknownFrame). | |
| Type FrameType | |
| // Flags are the 1 byte of 8 potential bit flags per frame. | |
| // They are specific to the frame type. | |
| Flags Flags | |
| // Length is the length of the frame, not including the 9 byte header. | |
| // The maximum size is one byte less than 16MB (uint24), but only | |
| // frames up to 16KB are allowed without peer agreement. | |
| Length uint32 | |
| // StreamID is which stream this frame is for. Certain frames | |
| // are not stream-specific, in which case this field is 0. | |
| StreamID uint32 | |
| } | |
| // Header returns h. It exists so FrameHeaders can be embedded in other | |
| // specific frame types and implement the Frame interface. | |
| func (h FrameHeader) Header() FrameHeader { return h } | |
| func (h FrameHeader) String() string { | |
| var buf bytes.Buffer | |
| buf.WriteString("[FrameHeader ") | |
| h.writeDebug(&buf) | |
| buf.WriteByte(']') | |
| return buf.String() | |
| } | |
| func (h FrameHeader) writeDebug(buf *bytes.Buffer) { | |
| buf.WriteString(h.Type.String()) | |
| if h.Flags != 0 { | |
| buf.WriteString(" flags=") | |
| set := 0 | |
| for i := uint8(0); i < 8; i++ { | |
| if h.Flags&(1<<i) == 0 { | |
| continue | |
| } | |
| set++ | |
| if set > 1 { | |
| buf.WriteByte('|') | |
| } | |
| name := flagName[h.Type][Flags(1<<i)] | |
| if name != "" { | |
| buf.WriteString(name) | |
| } else { | |
| fmt.Fprintf(buf, "0x%x", 1<<i) | |
| } | |
| } | |
| } | |
| if h.StreamID != 0 { | |
| fmt.Fprintf(buf, " stream=%d", h.StreamID) | |
| } | |
| fmt.Fprintf(buf, " len=%d", h.Length) | |
| } | |
| func (h *FrameHeader) checkValid() { | |
| if !h.valid { | |
| panic("Frame accessor called on non-owned Frame") | |
| } | |
| } | |
| func (h *FrameHeader) invalidate() { h.valid = false } | |
| // frame header bytes. | |
| // Used only by ReadFrameHeader. | |
| var fhBytes = sync.Pool{ | |
| New: func() interface{} { | |
| buf := make([]byte, frameHeaderLen) | |
| return &buf | |
| }, | |
| } | |
| func invalidHTTP1LookingFrameHeader() FrameHeader { | |
| fh, _ := readFrameHeader(make([]byte, frameHeaderLen), strings.NewReader("HTTP/1.1 ")) | |
| return fh | |
| } | |
| // ReadFrameHeader reads 9 bytes from r and returns a FrameHeader. | |
| // Most users should use Framer.ReadFrame instead. | |
| func ReadFrameHeader(r io.Reader) (FrameHeader, error) { | |
| bufp := fhBytes.Get().(*[]byte) | |
| defer fhBytes.Put(bufp) | |
| return readFrameHeader(*bufp, r) | |
| } | |
| func readFrameHeader(buf []byte, r io.Reader) (FrameHeader, error) { | |
| _, err := io.ReadFull(r, buf[:frameHeaderLen]) | |
| if err != nil { | |
| return FrameHeader{}, err | |
| } | |
| return FrameHeader{ | |
| Length: (uint32(buf[0])<<16 | uint32(buf[1])<<8 | uint32(buf[2])), | |
| Type: FrameType(buf[3]), | |
| Flags: Flags(buf[4]), | |
| StreamID: binary.BigEndian.Uint32(buf[5:]) & (1<<31 - 1), | |
| valid: true, | |
| }, nil | |
| } | |
| // A Frame is the base interface implemented by all frame types. | |
| // Callers will generally type-assert the specific frame type: | |
| // *HeadersFrame, *SettingsFrame, *WindowUpdateFrame, etc. | |
| // | |
| // Frames are only valid until the next call to Framer.ReadFrame. | |
| type Frame interface { | |
| Header() FrameHeader | |
| // invalidate is called by Framer.ReadFrame to make this | |
| // frame's buffers as being invalid, since the subsequent | |
| // frame will reuse them. | |
| invalidate() | |
| } | |
| // A Framer reads and writes Frames. | |
| type Framer struct { | |
| r io.Reader | |
| lastFrame Frame | |
| errDetail error | |
| // countError is a non-nil func that's called on a frame parse | |
| // error with some unique error path token. It's initialized | |
| // from Transport.CountError or Server.CountError. | |
| countError func(errToken string) | |
| // lastHeaderStream is non-zero if the last frame was an | |
| // unfinished HEADERS/CONTINUATION. | |
| lastHeaderStream uint32 | |
| // lastFrameType holds the type of the last frame for verifying frame order. | |
| lastFrameType FrameType | |
| maxReadSize uint32 | |
| headerBuf [frameHeaderLen]byte | |
| // TODO: let getReadBuf be configurable, and use a less memory-pinning | |
| // allocator in server.go to minimize memory pinned for many idle conns. | |
| // Will probably also need to make frame invalidation have a hook too. | |
| getReadBuf func(size uint32) []byte | |
| readBuf []byte // cache for default getReadBuf | |
| maxWriteSize uint32 // zero means unlimited; TODO: implement | |
| w io.Writer | |
| wbuf []byte | |
| // AllowIllegalWrites permits the Framer's Write methods to | |
| // write frames that do not conform to the HTTP/2 spec. This | |
| // permits using the Framer to test other HTTP/2 | |
| // implementations' conformance to the spec. | |
| // If false, the Write methods will prefer to return an error | |
| // rather than comply. | |
| AllowIllegalWrites bool | |
| // AllowIllegalReads permits the Framer's ReadFrame method | |
| // to return non-compliant frames or frame orders. | |
| // This is for testing and permits using the Framer to test | |
| // other HTTP/2 implementations' conformance to the spec. | |
| // It is not compatible with ReadMetaHeaders. | |
| AllowIllegalReads bool | |
| // ReadMetaHeaders if non-nil causes ReadFrame to merge | |
| // HEADERS and CONTINUATION frames together and return | |
| // MetaHeadersFrame instead. | |
| ReadMetaHeaders *hpack.Decoder | |
| // MaxHeaderListSize is the http2 MAX_HEADER_LIST_SIZE. | |
| // It's used only if ReadMetaHeaders is set; 0 means a sane default | |
| // (currently 16MB) | |
| // If the limit is hit, MetaHeadersFrame.Truncated is set true. | |
| MaxHeaderListSize uint32 | |
| // TODO: track which type of frame & with which flags was sent | |
| // last. Then return an error (unless AllowIllegalWrites) if | |
| // we're in the middle of a header block and a | |
| // non-Continuation or Continuation on a different stream is | |
| // attempted to be written. | |
| logReads, logWrites bool | |
| debugFramer *Framer // only use for logging written writes | |
| debugFramerBuf *bytes.Buffer | |
| debugReadLoggerf func(string, ...interface{}) | |
| debugWriteLoggerf func(string, ...interface{}) | |
| frameCache *frameCache // nil if frames aren't reused (default) | |
| } | |
| func (fr *Framer) maxHeaderListSize() uint32 { | |
| if fr.MaxHeaderListSize == 0 { | |
| return 16 << 20 // sane default, per docs | |
| } | |
| return fr.MaxHeaderListSize | |
| } | |
| func (f *Framer) startWrite(ftype FrameType, flags Flags, streamID uint32) { | |
| // Write the FrameHeader. | |
| f.wbuf = append(f.wbuf[:0], | |
| 0, // 3 bytes of length, filled in endWrite | |
| 0, | |
| 0, | |
| byte(ftype), | |
| byte(flags), | |
| byte(streamID>>24), | |
| byte(streamID>>16), | |
| byte(streamID>>8), | |
| byte(streamID)) | |
| } | |
| func (f *Framer) endWrite() error { | |
| // Now that we know the final size, fill in the FrameHeader in | |
| // the space previously reserved for it. Abuse append. | |
| length := len(f.wbuf) - frameHeaderLen | |
| if length >= (1 << 24) { | |
| return ErrFrameTooLarge | |
| } | |
| _ = append(f.wbuf[:0], | |
| byte(length>>16), | |
| byte(length>>8), | |
| byte(length)) | |
| if f.logWrites { | |
| f.logWrite() | |
| } | |
| n, err := f.w.Write(f.wbuf) | |
| if err == nil && n != len(f.wbuf) { | |
| err = io.ErrShortWrite | |
| } | |
| return err | |
| } | |
| func (f *Framer) logWrite() { | |
| if f.debugFramer == nil { | |
| f.debugFramerBuf = new(bytes.Buffer) | |
| f.debugFramer = NewFramer(nil, f.debugFramerBuf) | |
| f.debugFramer.logReads = false // we log it ourselves, saying "wrote" below | |
| // Let us read anything, even if we accidentally wrote it | |
| // in the wrong order: | |
| f.debugFramer.AllowIllegalReads = true | |
| } | |
| f.debugFramerBuf.Write(f.wbuf) | |
| fr, err := f.debugFramer.ReadFrame() | |
| if err != nil { | |
| f.debugWriteLoggerf("http2: Framer %p: failed to decode just-written frame", f) | |
| return | |
| } | |
| f.debugWriteLoggerf("http2: Framer %p: wrote %v", f, summarizeFrame(fr)) | |
| } | |
| func (f *Framer) writeByte(v byte) { f.wbuf = append(f.wbuf, v) } | |
| func (f *Framer) writeBytes(v []byte) { f.wbuf = append(f.wbuf, v...) } | |
| func (f *Framer) writeUint16(v uint16) { f.wbuf = append(f.wbuf, byte(v>>8), byte(v)) } | |
| func (f *Framer) writeUint32(v uint32) { | |
| f.wbuf = append(f.wbuf, byte(v>>24), byte(v>>16), byte(v>>8), byte(v)) | |
| } | |
| const ( | |
| minMaxFrameSize = 1 << 14 | |
| maxFrameSize = 1<<24 - 1 | |
| ) | |
| // SetReuseFrames allows the Framer to reuse Frames. | |
| // If called on a Framer, Frames returned by calls to ReadFrame are only | |
| // valid until the next call to ReadFrame. | |
| func (fr *Framer) SetReuseFrames() { | |
| if fr.frameCache != nil { | |
| return | |
| } | |
| fr.frameCache = &frameCache{} | |
| } | |
| type frameCache struct { | |
| dataFrame DataFrame | |
| } | |
| func (fc *frameCache) getDataFrame() *DataFrame { | |
| if fc == nil { | |
| return &DataFrame{} | |
| } | |
| return &fc.dataFrame | |
| } | |
| // NewFramer returns a Framer that writes frames to w and reads them from r. | |
| func NewFramer(w io.Writer, r io.Reader) *Framer { | |
| fr := &Framer{ | |
| w: w, | |
| r: r, | |
| countError: func(string) {}, | |
| logReads: logFrameReads, | |
| logWrites: logFrameWrites, | |
| debugReadLoggerf: log.Printf, | |
| debugWriteLoggerf: log.Printf, | |
| } | |
| fr.getReadBuf = func(size uint32) []byte { | |
| if cap(fr.readBuf) >= int(size) { | |
| return fr.readBuf[:size] | |
| } | |
| fr.readBuf = make([]byte, size) | |
| return fr.readBuf | |
| } | |
| fr.SetMaxReadFrameSize(maxFrameSize) | |
| return fr | |
| } | |
| // SetMaxReadFrameSize sets the maximum size of a frame | |
| // that will be read by a subsequent call to ReadFrame. | |
| // It is the caller's responsibility to advertise this | |
| // limit with a SETTINGS frame. | |
| func (fr *Framer) SetMaxReadFrameSize(v uint32) { | |
| if v > maxFrameSize { | |
| v = maxFrameSize | |
| } | |
| fr.maxReadSize = v | |
| } | |
| // ErrorDetail returns a more detailed error of the last error | |
| // returned by Framer.ReadFrame. For instance, if ReadFrame | |
| // returns a StreamError with code PROTOCOL_ERROR, ErrorDetail | |
| // will say exactly what was invalid. ErrorDetail is not guaranteed | |
| // to return a non-nil value and like the rest of the http2 package, | |
| // its return value is not protected by an API compatibility promise. | |
| // ErrorDetail is reset after the next call to ReadFrame. | |
| func (fr *Framer) ErrorDetail() error { | |
| return fr.errDetail | |
| } | |
| // ErrFrameTooLarge is returned from Framer.ReadFrame when the peer | |
| // sends a frame that is larger than declared with SetMaxReadFrameSize. | |
| var ErrFrameTooLarge = errors.New("http2: frame too large") | |
| // terminalReadFrameError reports whether err is an unrecoverable | |
| // error from ReadFrame and no other frames should be read. | |
| func terminalReadFrameError(err error) bool { | |
| if _, ok := err.(StreamError); ok { | |
| return false | |
| } | |
| return err != nil | |
| } | |
| // ReadFrameHeader reads the header of the next frame. | |
| // It reads the 9-byte fixed frame header, and does not read any portion of the | |
| // frame payload. The caller is responsible for consuming the payload, either | |
| // with ReadFrameForHeader or directly from the Framer's io.Reader. | |
| // | |
| // If the frame is larger than previously set with SetMaxReadFrameSize, it | |
| // returns the frame header and ErrFrameTooLarge. | |
| // | |
| // If the returned FrameHeader.StreamID is non-zero, it indicates the stream | |
| // responsible for the error. | |
| func (fr *Framer) ReadFrameHeader() (FrameHeader, error) { | |
| fr.errDetail = nil | |
| fh, err := readFrameHeader(fr.headerBuf[:], fr.r) | |
| if err != nil { | |
| return fh, err | |
| } | |
| if fh.Length > fr.maxReadSize { | |
| if fh == invalidHTTP1LookingFrameHeader() { | |
| return fh, fmt.Errorf("http2: failed reading the frame payload: %w, note that the frame header looked like an HTTP/1.1 header", ErrFrameTooLarge) | |
| } | |
| return fh, ErrFrameTooLarge | |
| } | |
| if err := fr.checkFrameOrder(fh); err != nil { | |
| return fh, err | |
| } | |
| return fh, nil | |
| } | |
| // ReadFrameForHeader reads the payload for the frame with the given FrameHeader. | |
| // | |
| // It behaves identically to ReadFrame, other than not checking the maximum | |
| // frame size. | |
| func (fr *Framer) ReadFrameForHeader(fh FrameHeader) (Frame, error) { | |
| if fr.lastFrame != nil { | |
| fr.lastFrame.invalidate() | |
| } | |
| payload := fr.getReadBuf(fh.Length) | |
| if _, err := io.ReadFull(fr.r, payload); err != nil { | |
| if fh == invalidHTTP1LookingFrameHeader() { | |
| return nil, fmt.Errorf("http2: failed reading the frame payload: %w, note that the frame header looked like an HTTP/1.1 header", err) | |
| } | |
| return nil, err | |
| } | |
| f, err := typeFrameParser(fh.Type)(fr.frameCache, fh, fr.countError, payload) | |
| if err != nil { | |
| if ce, ok := err.(connError); ok { | |
| return nil, fr.connError(ce.Code, ce.Reason) | |
| } | |
| return nil, err | |
| } | |
| fr.lastFrame = f | |
| if fr.logReads { | |
| fr.debugReadLoggerf("http2: Framer %p: read %v", fr, summarizeFrame(f)) | |
| } | |
| if fh.Type == FrameHeaders && fr.ReadMetaHeaders != nil { | |
| return fr.readMetaFrame(f.(*HeadersFrame)) | |
| } | |
| return f, nil | |
| } | |
| // ReadFrame reads a single frame. The returned Frame is only valid | |
| // until the next call to ReadFrame or ReadFrameBodyForHeader. | |
| // | |
| // If the frame is larger than previously set with SetMaxReadFrameSize, the | |
| // returned error is ErrFrameTooLarge. Other errors may be of type | |
| // ConnectionError, StreamError, or anything else from the underlying | |
| // reader. | |
| // | |
| // If ReadFrame returns an error and a non-nil Frame, the Frame's StreamID | |
| // indicates the stream responsible for the error. | |
| func (fr *Framer) ReadFrame() (Frame, error) { | |
| fh, err := fr.ReadFrameHeader() | |
| if err != nil { | |
| return nil, err | |
| } | |
| return fr.ReadFrameForHeader(fh) | |
| } | |
| // connError returns ConnectionError(code) but first | |
| // stashes away a public reason to the caller can optionally relay it | |
| // to the peer before hanging up on them. This might help others debug | |
| // their implementations. | |
| func (fr *Framer) connError(code ErrCode, reason string) error { | |
| fr.errDetail = errors.New(reason) | |
| return ConnectionError(code) | |
| } | |
| // checkFrameOrder reports an error if f is an invalid frame to return | |
| // next from ReadFrame. Mostly it checks whether HEADERS and | |
| // CONTINUATION frames are contiguous. | |
| func (fr *Framer) checkFrameOrder(fh FrameHeader) error { | |
| lastType := fr.lastFrameType | |
| fr.lastFrameType = fh.Type | |
| if fr.AllowIllegalReads { | |
| return nil | |
| } | |
| if fr.lastHeaderStream != 0 { | |
| if fh.Type != FrameContinuation { | |
| return fr.connError(ErrCodeProtocol, | |
| fmt.Sprintf("got %s for stream %d; expected CONTINUATION following %s for stream %d", | |
| fh.Type, fh.StreamID, | |
| lastType, fr.lastHeaderStream)) | |
| } | |
| if fh.StreamID != fr.lastHeaderStream { | |
| return fr.connError(ErrCodeProtocol, | |
| fmt.Sprintf("got CONTINUATION for stream %d; expected stream %d", | |
| fh.StreamID, fr.lastHeaderStream)) | |
| } | |
| } else if fh.Type == FrameContinuation { | |
| return fr.connError(ErrCodeProtocol, fmt.Sprintf("unexpected CONTINUATION for stream %d", fh.StreamID)) | |
| } | |
| switch fh.Type { | |
| case FrameHeaders, FrameContinuation: | |
| if fh.Flags.Has(FlagHeadersEndHeaders) { | |
| fr.lastHeaderStream = 0 | |
| } else { | |
| fr.lastHeaderStream = fh.StreamID | |
| } | |
| } | |
| return nil | |
| } | |
| // A DataFrame conveys arbitrary, variable-length sequences of octets | |
| // associated with a stream. | |
| // See https://httpwg.org/specs/rfc7540.html#rfc.section.6.1 | |
| type DataFrame struct { | |
| FrameHeader | |
| data []byte | |
| } | |
| func (f *DataFrame) StreamEnded() bool { | |
| return f.FrameHeader.Flags.Has(FlagDataEndStream) | |
| } | |
| // Data returns the frame's data octets, not including any padding | |
| // size byte or padding suffix bytes. | |
| // The caller must not retain the returned memory past the next | |
| // call to ReadFrame. | |
| func (f *DataFrame) Data() []byte { | |
| f.checkValid() | |
| return f.data | |
| } | |
| func parseDataFrame(fc *frameCache, fh FrameHeader, countError func(string), payload []byte) (Frame, error) { | |
| if fh.StreamID == 0 { | |
| // DATA frames MUST be associated with a stream. If a | |
| // DATA frame is received whose stream identifier | |
| // field is 0x0, the recipient MUST respond with a | |
| // connection error (Section 5.4.1) of type | |
| // PROTOCOL_ERROR. | |
| countError("frame_data_stream_0") | |
| return nil, connError{ErrCodeProtocol, "DATA frame with stream ID 0"} | |
| } | |
| f := fc.getDataFrame() | |
| f.FrameHeader = fh | |
| var padSize byte | |
| if fh.Flags.Has(FlagDataPadded) { | |
| var err error | |
| payload, padSize, err = readByte(payload) | |
| if err != nil { | |
| countError("frame_data_pad_byte_short") | |
| return nil, err | |
| } | |
| } | |
| if int(padSize) > len(payload) { | |
| // If the length of the padding is greater than the | |
| // length of the frame payload, the recipient MUST | |
| // treat this as a connection error. | |
| // Filed: https://github.com/http2/http2-spec/issues/610 | |
| countError("frame_data_pad_too_big") | |
| return nil, connError{ErrCodeProtocol, "pad size larger than data payload"} | |
| } | |
| f.data = payload[:len(payload)-int(padSize)] | |
| return f, nil | |
| } | |
| var ( | |
| errStreamID = errors.New("invalid stream ID") | |
| errDepStreamID = errors.New("invalid dependent stream ID") | |
| errPadLength = errors.New("pad length too large") | |
| errPadBytes = errors.New("padding bytes must all be zeros unless AllowIllegalWrites is enabled") | |
| ) | |
| func validStreamIDOrZero(streamID uint32) bool { | |
| return streamID&(1<<31) == 0 | |
| } | |
| func validStreamID(streamID uint32) bool { | |
| return streamID != 0 && streamID&(1<<31) == 0 | |
| } | |
| // WriteData writes a DATA frame. | |
| // | |
| // It will perform exactly one Write to the underlying Writer. | |
| // It is the caller's responsibility not to violate the maximum frame size | |
| // and to not call other Write methods concurrently. | |
| func (f *Framer) WriteData(streamID uint32, endStream bool, data []byte) error { | |
| return f.WriteDataPadded(streamID, endStream, data, nil) | |
| } | |
| // WriteDataPadded writes a DATA frame with optional padding. | |
| // | |
| // If pad is nil, the padding bit is not sent. | |
| // The length of pad must not exceed 255 bytes. | |
| // The bytes of pad must all be zero, unless f.AllowIllegalWrites is set. | |
| // | |
| // It will perform exactly one Write to the underlying Writer. | |
| // It is the caller's responsibility not to violate the maximum frame size | |
| // and to not call other Write methods concurrently. | |
| func (f *Framer) WriteDataPadded(streamID uint32, endStream bool, data, pad []byte) error { | |
| if err := f.startWriteDataPadded(streamID, endStream, data, pad); err != nil { | |
| return err | |
| } | |
| return f.endWrite() | |
| } | |
| // startWriteDataPadded is WriteDataPadded, but only writes the frame to the Framer's internal buffer. | |
| // The caller should call endWrite to flush the frame to the underlying writer. | |
| func (f *Framer) startWriteDataPadded(streamID uint32, endStream bool, data, pad []byte) error { | |
| if !validStreamID(streamID) && !f.AllowIllegalWrites { | |
| return errStreamID | |
| } | |
| if len(pad) > 0 { | |
| if len(pad) > 255 { | |
| return errPadLength | |
| } | |
| if !f.AllowIllegalWrites { | |
| for _, b := range pad { | |
| if b != 0 { | |
| // "Padding octets MUST be set to zero when sending." | |
| return errPadBytes | |
| } | |
| } | |
| } | |
| } | |
| var flags Flags | |
| if endStream { | |
| flags |= FlagDataEndStream | |
| } | |
| if pad != nil { | |
| flags |= FlagDataPadded | |
| } | |
| f.startWrite(FrameData, flags, streamID) | |
| if pad != nil { | |
| f.wbuf = append(f.wbuf, byte(len(pad))) | |
| } | |
| f.wbuf = append(f.wbuf, data...) | |
| f.wbuf = append(f.wbuf, pad...) | |
| return nil | |
| } | |
| // A SettingsFrame conveys configuration parameters that affect how | |
| // endpoints communicate, such as preferences and constraints on peer | |
| // behavior. | |
| // | |
| // See https://httpwg.org/specs/rfc7540.html#SETTINGS | |
| type SettingsFrame struct { | |
| FrameHeader | |
| p []byte | |
| } | |
| func parseSettingsFrame(_ *frameCache, fh FrameHeader, countError func(string), p []byte) (Frame, error) { | |
| if fh.Flags.Has(FlagSettingsAck) && fh.Length > 0 { | |
| // When this (ACK 0x1) bit is set, the payload of the | |
| // SETTINGS frame MUST be empty. Receipt of a | |
| // SETTINGS frame with the ACK flag set and a length | |
| // field value other than 0 MUST be treated as a | |
| // connection error (Section 5.4.1) of type | |
| // FRAME_SIZE_ERROR. | |
| countError("frame_settings_ack_with_length") | |
| return nil, ConnectionError(ErrCodeFrameSize) | |
| } | |
| if fh.StreamID != 0 { | |
| // SETTINGS frames always apply to a connection, | |
| // never a single stream. The stream identifier for a | |
| // SETTINGS frame MUST be zero (0x0). If an endpoint | |
| // receives a SETTINGS frame whose stream identifier | |
| // field is anything other than 0x0, the endpoint MUST | |
| // respond with a connection error (Section 5.4.1) of | |
| // type PROTOCOL_ERROR. | |
| countError("frame_settings_has_stream") | |
| return nil, ConnectionError(ErrCodeProtocol) | |
| } | |
| if len(p)%6 != 0 { | |
| countError("frame_settings_mod_6") | |
| // Expecting even number of 6 byte settings. | |
| return nil, ConnectionError(ErrCodeFrameSize) | |
| } | |
| f := &SettingsFrame{FrameHeader: fh, p: p} | |
| if v, ok := f.Value(SettingInitialWindowSize); ok && v > (1<<31)-1 { | |
| countError("frame_settings_window_size_too_big") | |
| // Values above the maximum flow control window size of 2^31 - 1 MUST | |
| // be treated as a connection error (Section 5.4.1) of type | |
| // FLOW_CONTROL_ERROR. | |
| return nil, ConnectionError(ErrCodeFlowControl) | |
| } | |
| return f, nil | |
| } | |
| func (f *SettingsFrame) IsAck() bool { | |
| return f.FrameHeader.Flags.Has(FlagSettingsAck) | |
| } | |
| func (f *SettingsFrame) Value(id SettingID) (v uint32, ok bool) { | |
| f.checkValid() | |
| for i := 0; i < f.NumSettings(); i++ { | |
| if s := f.Setting(i); s.ID == id { | |
| return s.Val, true | |
| } | |
| } | |
| return 0, false | |
| } | |
| // Setting returns the setting from the frame at the given 0-based index. | |
| // The index must be >= 0 and less than f.NumSettings(). | |
| func (f *SettingsFrame) Setting(i int) Setting { | |
| buf := f.p | |
| return Setting{ | |
| ID: SettingID(binary.BigEndian.Uint16(buf[i*6 : i*6+2])), | |
| Val: binary.BigEndian.Uint32(buf[i*6+2 : i*6+6]), | |
| } | |
| } | |
| func (f *SettingsFrame) NumSettings() int { return len(f.p) / 6 } | |
| // HasDuplicates reports whether f contains any duplicate setting IDs. | |
| func (f *SettingsFrame) HasDuplicates() bool { | |
| num := f.NumSettings() | |
| if num == 0 { | |
| return false | |
| } | |
| // If it's small enough (the common case), just do the n^2 | |
| // thing and avoid a map allocation. | |
| if num < 10 { | |
| for i := 0; i < num; i++ { | |
| idi := f.Setting(i).ID | |
| for j := i + 1; j < num; j++ { | |
| idj := f.Setting(j).ID | |
| if idi == idj { | |
| return true | |
| } | |
| } | |
| } | |
| return false | |
| } | |
| seen := map[SettingID]bool{} | |
| for i := 0; i < num; i++ { | |
| id := f.Setting(i).ID | |
| if seen[id] { | |
| return true | |
| } | |
| seen[id] = true | |
| } | |
| return false | |
| } | |
| // ForeachSetting runs fn for each setting. | |
| // It stops and returns the first error. | |
| func (f *SettingsFrame) ForeachSetting(fn func(Setting) error) error { | |
| f.checkValid() | |
| for i := 0; i < f.NumSettings(); i++ { | |
| if err := fn(f.Setting(i)); err != nil { | |
| return err | |
| } | |
| } | |
| return nil | |
| } | |
| // WriteSettings writes a SETTINGS frame with zero or more settings | |
| // specified and the ACK bit not set. | |
| // | |
| // It will perform exactly one Write to the underlying Writer. | |
| // It is the caller's responsibility to not call other Write methods concurrently. | |
| func (f *Framer) WriteSettings(settings ...Setting) error { | |
| f.startWrite(FrameSettings, 0, 0) | |
| for _, s := range settings { | |
| f.writeUint16(uint16(s.ID)) | |
| f.writeUint32(s.Val) | |
| } | |
| return f.endWrite() | |
| } | |
| // WriteSettingsAck writes an empty SETTINGS frame with the ACK bit set. | |
| // | |
| // It will perform exactly one Write to the underlying Writer. | |
| // It is the caller's responsibility to not call other Write methods concurrently. | |
| func (f *Framer) WriteSettingsAck() error { | |
| f.startWrite(FrameSettings, FlagSettingsAck, 0) | |
| return f.endWrite() | |
| } | |
| // A PingFrame is a mechanism for measuring a minimal round trip time | |
| // from the sender, as well as determining whether an idle connection | |
| // is still functional. | |
| // See https://httpwg.org/specs/rfc7540.html#rfc.section.6.7 | |
| type PingFrame struct { | |
| FrameHeader | |
| Data [8]byte | |
| } | |
| func (f *PingFrame) IsAck() bool { return f.Flags.Has(FlagPingAck) } | |
| func parsePingFrame(_ *frameCache, fh FrameHeader, countError func(string), payload []byte) (Frame, error) { | |
| if len(payload) != 8 { | |
| countError("frame_ping_length") | |
| return nil, ConnectionError(ErrCodeFrameSize) | |
| } | |
| if fh.StreamID != 0 { | |
| countError("frame_ping_has_stream") | |
| return nil, ConnectionError(ErrCodeProtocol) | |
| } | |
| f := &PingFrame{FrameHeader: fh} | |
| copy(f.Data[:], payload) | |
| return f, nil | |
| } | |
| func (f *Framer) WritePing(ack bool, data [8]byte) error { | |
| var flags Flags | |
| if ack { | |
| flags = FlagPingAck | |
| } | |
| f.startWrite(FramePing, flags, 0) | |
| f.writeBytes(data[:]) | |
| return f.endWrite() | |
| } | |
| // A GoAwayFrame informs the remote peer to stop creating streams on this connection. | |
| // See https://httpwg.org/specs/rfc7540.html#rfc.section.6.8 | |
| type GoAwayFrame struct { | |
| FrameHeader | |
| LastStreamID uint32 | |
| ErrCode ErrCode | |
| debugData []byte | |
| } | |
| // DebugData returns any debug data in the GOAWAY frame. Its contents | |
| // are not defined. | |
| // The caller must not retain the returned memory past the next | |
| // call to ReadFrame. | |
| func (f *GoAwayFrame) DebugData() []byte { | |
| f.checkValid() | |
| return f.debugData | |
| } | |
| func parseGoAwayFrame(_ *frameCache, fh FrameHeader, countError func(string), p []byte) (Frame, error) { | |
| if fh.StreamID != 0 { | |
| countError("frame_goaway_has_stream") | |
| return nil, ConnectionError(ErrCodeProtocol) | |
| } | |
| if len(p) < 8 { | |
| countError("frame_goaway_short") | |
| return nil, ConnectionError(ErrCodeFrameSize) | |
| } | |
| return &GoAwayFrame{ | |
| FrameHeader: fh, | |
| LastStreamID: binary.BigEndian.Uint32(p[:4]) & (1<<31 - 1), | |
| ErrCode: ErrCode(binary.BigEndian.Uint32(p[4:8])), | |
| debugData: p[8:], | |
| }, nil | |
| } | |
| func (f *Framer) WriteGoAway(maxStreamID uint32, code ErrCode, debugData []byte) error { | |
| f.startWrite(FrameGoAway, 0, 0) | |
| f.writeUint32(maxStreamID & (1<<31 - 1)) | |
| f.writeUint32(uint32(code)) | |
| f.writeBytes(debugData) | |
| return f.endWrite() | |
| } | |
| // An UnknownFrame is the frame type returned when the frame type is unknown | |
| // or no specific frame type parser exists. | |
| type UnknownFrame struct { | |
| FrameHeader | |
| p []byte | |
| } | |
| // Payload returns the frame's payload (after the header). It is not | |
| // valid to call this method after a subsequent call to | |
| // Framer.ReadFrame, nor is it valid to retain the returned slice. | |
| // The memory is owned by the Framer and is invalidated when the next | |
| // frame is read. | |
| func (f *UnknownFrame) Payload() []byte { | |
| f.checkValid() | |
| return f.p | |
| } | |
| func parseUnknownFrame(_ *frameCache, fh FrameHeader, countError func(string), p []byte) (Frame, error) { | |
| return &UnknownFrame{fh, p}, nil | |
| } | |
| // A WindowUpdateFrame is used to implement flow control. | |
| // See https://httpwg.org/specs/rfc7540.html#rfc.section.6.9 | |
| type WindowUpdateFrame struct { | |
| FrameHeader | |
| Increment uint32 // never read with high bit set | |
| } | |
| func parseWindowUpdateFrame(_ *frameCache, fh FrameHeader, countError func(string), p []byte) (Frame, error) { | |
| if len(p) != 4 { | |
| countError("frame_windowupdate_bad_len") | |
| return nil, ConnectionError(ErrCodeFrameSize) | |
| } | |
| inc := binary.BigEndian.Uint32(p[:4]) & 0x7fffffff // mask off high reserved bit | |
| if inc == 0 { | |
| // A receiver MUST treat the receipt of a | |
| // WINDOW_UPDATE frame with an flow control window | |
| // increment of 0 as a stream error (Section 5.4.2) of | |
| // type PROTOCOL_ERROR; errors on the connection flow | |
| // control window MUST be treated as a connection | |
| // error (Section 5.4.1). | |
| if fh.StreamID == 0 { | |
| countError("frame_windowupdate_zero_inc_conn") | |
| return nil, ConnectionError(ErrCodeProtocol) | |
| } | |
| countError("frame_windowupdate_zero_inc_stream") | |
| return nil, streamError(fh.StreamID, ErrCodeProtocol) | |
| } | |
| return &WindowUpdateFrame{ | |
| FrameHeader: fh, | |
| Increment: inc, | |
| }, nil | |
| } | |
| // WriteWindowUpdate writes a WINDOW_UPDATE frame. | |
| // The increment value must be between 1 and 2,147,483,647, inclusive. | |
| // If the Stream ID is zero, the window update applies to the | |
| // connection as a whole. | |
| func (f *Framer) WriteWindowUpdate(streamID, incr uint32) error { | |
| // "The legal range for the increment to the flow control window is 1 to 2^31-1 (2,147,483,647) octets." | |
| if (incr < 1 || incr > 2147483647) && !f.AllowIllegalWrites { | |
| return errors.New("illegal window increment value") | |
| } | |
| f.startWrite(FrameWindowUpdate, 0, streamID) | |
| f.writeUint32(incr) | |
| return f.endWrite() | |
| } | |
| // A HeadersFrame is used to open a stream and additionally carries a | |
| // header block fragment. | |
| type HeadersFrame struct { | |
| FrameHeader | |
| // Priority is set if FlagHeadersPriority is set in the FrameHeader. | |
| Priority PriorityParam | |
| headerFragBuf []byte // not owned | |
| } | |
| func (f *HeadersFrame) HeaderBlockFragment() []byte { | |
| f.checkValid() | |
| return f.headerFragBuf | |
| } | |
| func (f *HeadersFrame) HeadersEnded() bool { | |
| return f.FrameHeader.Flags.Has(FlagHeadersEndHeaders) | |
| } | |
| func (f *HeadersFrame) StreamEnded() bool { | |
| return f.FrameHeader.Flags.Has(FlagHeadersEndStream) | |
| } | |
| func (f *HeadersFrame) HasPriority() bool { | |
| return f.FrameHeader.Flags.Has(FlagHeadersPriority) | |
| } | |
| func parseHeadersFrame(_ *frameCache, fh FrameHeader, countError func(string), p []byte) (_ Frame, err error) { | |
| hf := &HeadersFrame{ | |
| FrameHeader: fh, | |
| } | |
| if fh.StreamID == 0 { | |
| // HEADERS frames MUST be associated with a stream. If a HEADERS frame | |
| // is received whose stream identifier field is 0x0, the recipient MUST | |
| // respond with a connection error (Section 5.4.1) of type | |
| // PROTOCOL_ERROR. | |
| countError("frame_headers_zero_stream") | |
| return nil, connError{ErrCodeProtocol, "HEADERS frame with stream ID 0"} | |
| } | |
| var padLength uint8 | |
| if fh.Flags.Has(FlagHeadersPadded) { | |
| if p, padLength, err = readByte(p); err != nil { | |
| countError("frame_headers_pad_short") | |
| return | |
| } | |
| } | |
| if fh.Flags.Has(FlagHeadersPriority) { | |
| var v uint32 | |
| p, v, err = readUint32(p) | |
| if err != nil { | |
| countError("frame_headers_prio_short") | |
| return nil, err | |
| } | |
| hf.Priority.StreamDep = v & 0x7fffffff | |
| hf.Priority.Exclusive = (v != hf.Priority.StreamDep) // high bit was set | |
| p, hf.Priority.Weight, err = readByte(p) | |
| if err != nil { | |
| countError("frame_headers_prio_weight_short") | |
| return nil, err | |
| } | |
| } | |
| if len(p)-int(padLength) < 0 { | |
| countError("frame_headers_pad_too_big") | |
| return nil, streamError(fh.StreamID, ErrCodeProtocol) | |
| } | |
| hf.headerFragBuf = p[:len(p)-int(padLength)] | |
| return hf, nil | |
| } | |
| // HeadersFrameParam are the parameters for writing a HEADERS frame. | |
| type HeadersFrameParam struct { | |
| // StreamID is the required Stream ID to initiate. | |
| StreamID uint32 | |
| // BlockFragment is part (or all) of a Header Block. | |
| BlockFragment []byte | |
| // EndStream indicates that the header block is the last that | |
| // the endpoint will send for the identified stream. Setting | |
| // this flag causes the stream to enter one of "half closed" | |
| // states. | |
| EndStream bool | |
| // EndHeaders indicates that this frame contains an entire | |
| // header block and is not followed by any | |
| // CONTINUATION frames. | |
| EndHeaders bool | |
| // PadLength is the optional number of bytes of zeros to add | |
| // to this frame. | |
| PadLength uint8 | |
| // Priority, if non-zero, includes stream priority information | |
| // in the HEADER frame. | |
| Priority PriorityParam | |
| } | |
| // WriteHeaders writes a single HEADERS frame. | |
| // | |
| // This is a low-level header writing method. Encoding headers and | |
| // splitting them into any necessary CONTINUATION frames is handled | |
| // elsewhere. | |
| // | |
| // It will perform exactly one Write to the underlying Writer. | |
| // It is the caller's responsibility to not call other Write methods concurrently. | |
| func (f *Framer) WriteHeaders(p HeadersFrameParam) error { | |
| if !validStreamID(p.StreamID) && !f.AllowIllegalWrites { | |
| return errStreamID | |
| } | |
| var flags Flags | |
| if p.PadLength != 0 { | |
| flags |= FlagHeadersPadded | |
| } | |
| if p.EndStream { | |
| flags |= FlagHeadersEndStream | |
| } | |
| if p.EndHeaders { | |
| flags |= FlagHeadersEndHeaders | |
| } | |
| if !p.Priority.IsZero() { | |
| flags |= FlagHeadersPriority | |
| } | |
| f.startWrite(FrameHeaders, flags, p.StreamID) | |
| if p.PadLength != 0 { | |
| f.writeByte(p.PadLength) | |
| } | |
| if !p.Priority.IsZero() { | |
| v := p.Priority.StreamDep | |
| if !validStreamIDOrZero(v) && !f.AllowIllegalWrites { | |
| return errDepStreamID | |
| } | |
| if p.Priority.Exclusive { | |
| v |= 1 << 31 | |
| } | |
| f.writeUint32(v) | |
| f.writeByte(p.Priority.Weight) | |
| } | |
| f.wbuf = append(f.wbuf, p.BlockFragment...) | |
| f.wbuf = append(f.wbuf, padZeros[:p.PadLength]...) | |
| return f.endWrite() | |
| } | |
| // A PriorityFrame specifies the sender-advised priority of a stream. | |
| // See https://httpwg.org/specs/rfc7540.html#rfc.section.6.3 | |
| type PriorityFrame struct { | |
| FrameHeader | |
| PriorityParam | |
| } | |
| // defaultRFC9218Priority determines what priority we should use as the default | |
| // value. | |
| // | |
| // According to RFC 9218, by default, streams should be given an urgency of 3 | |
| // and should be non-incremental. However, making streams non-incremental by | |
| // default would be a huge change to our historical behavior where we would | |
| // round-robin writes across streams. When streams are non-incremental, we | |
| // would process streams of the same urgency one-by-one to completion instead. | |
| // | |
| // To avoid such a sudden change which might break some HTTP/2 users, this | |
| // function allows the caller to specify whether they can actually use the | |
| // default value as specified in RFC 9218. If not, this function will return a | |
| // priority value where streams are incremental by default instead: effectively | |
| // a round-robin between stream of the same urgency. | |
| // | |
| // As an example, a server might not be able to use the RFC 9218 default value | |
| // when it's not sure that the client it is serving is aware of RFC 9218. | |
| func defaultRFC9218Priority(canUseDefault bool) PriorityParam { | |
| if canUseDefault { | |
| return PriorityParam{ | |
| urgency: 3, | |
| incremental: 0, | |
| } | |
| } | |
| return PriorityParam{ | |
| urgency: 3, | |
| incremental: 1, | |
| } | |
| } | |
| // Note that HTTP/2 has had two different prioritization schemes, and | |
| // PriorityParam struct below is a superset of both schemes. The exported | |
| // symbols are from RFC 7540 and the non-exported ones are from RFC 9218. | |
| // PriorityParam are the stream prioritization parameters. | |
| type PriorityParam struct { | |
| // StreamDep is a 31-bit stream identifier for the | |
| // stream that this stream depends on. Zero means no | |
| // dependency. | |
| StreamDep uint32 | |
| // Exclusive is whether the dependency is exclusive. | |
| Exclusive bool | |
| // Weight is the stream's zero-indexed weight. It should be | |
| // set together with StreamDep, or neither should be set. Per | |
| // the spec, "Add one to the value to obtain a weight between | |
| // 1 and 256." | |
| Weight uint8 | |
| // "The urgency (u) parameter value is Integer (see Section 3.3.1 of | |
| // [STRUCTURED-FIELDS]), between 0 and 7 inclusive, in descending order of | |
| // priority. The default is 3." | |
| urgency uint8 | |
| // "The incremental (i) parameter value is Boolean (see Section 3.3.6 of | |
| // [STRUCTURED-FIELDS]). It indicates if an HTTP response can be processed | |
| // incrementally, i.e., provide some meaningful output as chunks of the | |
| // response arrive." | |
| // | |
| // We use uint8 (i.e. 0 is false, 1 is true) instead of bool so we can | |
| // avoid unnecessary type conversions and because either type takes 1 byte. | |
| incremental uint8 | |
| } | |
| func (p PriorityParam) IsZero() bool { | |
| return p == PriorityParam{} | |
| } | |
| func parsePriorityFrame(_ *frameCache, fh FrameHeader, countError func(string), payload []byte) (Frame, error) { | |
| if fh.StreamID == 0 { | |
| countError("frame_priority_zero_stream") | |
| return nil, connError{ErrCodeProtocol, "PRIORITY frame with stream ID 0"} | |
| } | |
| if len(payload) != 5 { | |
| countError("frame_priority_bad_length") | |
| return nil, connError{ErrCodeFrameSize, fmt.Sprintf("PRIORITY frame payload size was %d; want 5", len(payload))} | |
| } | |
| v := binary.BigEndian.Uint32(payload[:4]) | |
| streamID := v & 0x7fffffff // mask off high bit | |
| return &PriorityFrame{ | |
| FrameHeader: fh, | |
| PriorityParam: PriorityParam{ | |
| Weight: payload[4], | |
| StreamDep: streamID, | |
| Exclusive: streamID != v, // was high bit set? | |
| }, | |
| }, nil | |
| } | |
| // WritePriority writes a PRIORITY frame. | |
| // | |
| // It will perform exactly one Write to the underlying Writer. | |
| // It is the caller's responsibility to not call other Write methods concurrently. | |
| func (f *Framer) WritePriority(streamID uint32, p PriorityParam) error { | |
| if !validStreamID(streamID) && !f.AllowIllegalWrites { | |
| return errStreamID | |
| } | |
| if !validStreamIDOrZero(p.StreamDep) { | |
| return errDepStreamID | |
| } | |
| f.startWrite(FramePriority, 0, streamID) | |
| v := p.StreamDep | |
| if p.Exclusive { | |
| v |= 1 << 31 | |
| } | |
| f.writeUint32(v) | |
| f.writeByte(p.Weight) | |
| return f.endWrite() | |
| } | |
| // PriorityUpdateFrame is a PRIORITY_UPDATE frame as described in | |
| // https://www.rfc-editor.org/rfc/rfc9218.html#name-the-priority_update-frame. | |
| type PriorityUpdateFrame struct { | |
| FrameHeader | |
| Priority string | |
| PrioritizedStreamID uint32 | |
| } | |
| func parseRFC9218Priority(s string, canUseDefault bool) (p PriorityParam, ok bool) { | |
| p = defaultRFC9218Priority(canUseDefault) | |
| ok = httpsfv.ParseDictionary(s, func(key, val, _ string) { | |
| switch key { | |
| case "u": | |
| if u, ok := httpsfv.ParseInteger(val); ok && u >= 0 && u <= 7 { | |
| p.urgency = uint8(u) | |
| } | |
| case "i": | |
| if i, ok := httpsfv.ParseBoolean(val); ok { | |
| if i { | |
| p.incremental = 1 | |
| } else { | |
| p.incremental = 0 | |
| } | |
| } | |
| } | |
| }) | |
| if !ok { | |
| return defaultRFC9218Priority(canUseDefault), ok | |
| } | |
| return p, true | |
| } | |
| func parsePriorityUpdateFrame(_ *frameCache, fh FrameHeader, countError func(string), payload []byte) (Frame, error) { | |
| if fh.StreamID != 0 { | |
| countError("frame_priority_update_non_zero_stream") | |
| return nil, connError{ErrCodeProtocol, "PRIORITY_UPDATE frame with non-zero stream ID"} | |
| } | |
| if len(payload) < 4 { | |
| countError("frame_priority_update_bad_length") | |
| return nil, connError{ErrCodeFrameSize, fmt.Sprintf("PRIORITY_UPDATE frame payload size was %d; want at least 4", len(payload))} | |
| } | |
| v := binary.BigEndian.Uint32(payload[:4]) | |
| streamID := v & 0x7fffffff // mask off high bit | |
| if streamID == 0 { | |
| countError("frame_priority_update_prioritizing_zero_stream") | |
| return nil, connError{ErrCodeProtocol, "PRIORITY_UPDATE frame with prioritized stream ID of zero"} | |
| } | |
| return &PriorityUpdateFrame{ | |
| FrameHeader: fh, | |
| PrioritizedStreamID: streamID, | |
| Priority: string(payload[4:]), | |
| }, nil | |
| } | |
| // WritePriorityUpdate writes a PRIORITY_UPDATE frame. | |
| // | |
| // It will perform exactly one Write to the underlying Writer. | |
| // It is the caller's responsibility to not call other Write methods concurrently. | |
| func (f *Framer) WritePriorityUpdate(streamID uint32, priority string) error { | |
| if !validStreamID(streamID) && !f.AllowIllegalWrites { | |
| return errStreamID | |
| } | |
| f.startWrite(FramePriorityUpdate, 0, 0) | |
| f.writeUint32(streamID) | |
| f.writeBytes([]byte(priority)) | |
| return f.endWrite() | |
| } | |
| // A RSTStreamFrame allows for abnormal termination of a stream. | |
| // See https://httpwg.org/specs/rfc7540.html#rfc.section.6.4 | |
| type RSTStreamFrame struct { | |
| FrameHeader | |
| ErrCode ErrCode | |
| } | |
| func parseRSTStreamFrame(_ *frameCache, fh FrameHeader, countError func(string), p []byte) (Frame, error) { | |
| if len(p) != 4 { | |
| countError("frame_rststream_bad_len") | |
| return nil, ConnectionError(ErrCodeFrameSize) | |
| } | |
| if fh.StreamID == 0 { | |
| countError("frame_rststream_zero_stream") | |
| return nil, ConnectionError(ErrCodeProtocol) | |
| } | |
| return &RSTStreamFrame{fh, ErrCode(binary.BigEndian.Uint32(p[:4]))}, nil | |
| } | |
| // WriteRSTStream writes a RST_STREAM frame. | |
| // | |
| // It will perform exactly one Write to the underlying Writer. | |
| // It is the caller's responsibility to not call other Write methods concurrently. | |
| func (f *Framer) WriteRSTStream(streamID uint32, code ErrCode) error { | |
| if !validStreamID(streamID) && !f.AllowIllegalWrites { | |
| return errStreamID | |
| } | |
| f.startWrite(FrameRSTStream, 0, streamID) | |
| f.writeUint32(uint32(code)) | |
| return f.endWrite() | |
| } | |
| // A ContinuationFrame is used to continue a sequence of header block fragments. | |
| // See https://httpwg.org/specs/rfc7540.html#rfc.section.6.10 | |
| type ContinuationFrame struct { | |
| FrameHeader | |
| headerFragBuf []byte | |
| } | |
| func parseContinuationFrame(_ *frameCache, fh FrameHeader, countError func(string), p []byte) (Frame, error) { | |
| if fh.StreamID == 0 { | |
| countError("frame_continuation_zero_stream") | |
| return nil, connError{ErrCodeProtocol, "CONTINUATION frame with stream ID 0"} | |
| } | |
| return &ContinuationFrame{fh, p}, nil | |
| } | |
| func (f *ContinuationFrame) HeaderBlockFragment() []byte { | |
| f.checkValid() | |
| return f.headerFragBuf | |
| } | |
| func (f *ContinuationFrame) HeadersEnded() bool { | |
| return f.FrameHeader.Flags.Has(FlagContinuationEndHeaders) | |
| } | |
| // WriteContinuation writes a CONTINUATION frame. | |
| // | |
| // It will perform exactly one Write to the underlying Writer. | |
| // It is the caller's responsibility to not call other Write methods concurrently. | |
| func (f *Framer) WriteContinuation(streamID uint32, endHeaders bool, headerBlockFragment []byte) error { | |
| if !validStreamID(streamID) && !f.AllowIllegalWrites { | |
| return errStreamID | |
| } | |
| var flags Flags | |
| if endHeaders { | |
| flags |= FlagContinuationEndHeaders | |
| } | |
| f.startWrite(FrameContinuation, flags, streamID) | |
| f.wbuf = append(f.wbuf, headerBlockFragment...) | |
| return f.endWrite() | |
| } | |
| // A PushPromiseFrame is used to initiate a server stream. | |
| // See https://httpwg.org/specs/rfc7540.html#rfc.section.6.6 | |
| type PushPromiseFrame struct { | |
| FrameHeader | |
| PromiseID uint32 | |
| headerFragBuf []byte // not owned | |
| } | |
| func (f *PushPromiseFrame) HeaderBlockFragment() []byte { | |
| f.checkValid() | |
| return f.headerFragBuf | |
| } | |
| func (f *PushPromiseFrame) HeadersEnded() bool { | |
| return f.FrameHeader.Flags.Has(FlagPushPromiseEndHeaders) | |
| } | |
| func parsePushPromise(_ *frameCache, fh FrameHeader, countError func(string), p []byte) (_ Frame, err error) { | |
| pp := &PushPromiseFrame{ | |
| FrameHeader: fh, | |
| } | |
| if pp.StreamID == 0 { | |
| // PUSH_PROMISE frames MUST be associated with an existing, | |
| // peer-initiated stream. The stream identifier of a | |
| // PUSH_PROMISE frame indicates the stream it is associated | |
| // with. If the stream identifier field specifies the value | |
| // 0x0, a recipient MUST respond with a connection error | |
| // (Section 5.4.1) of type PROTOCOL_ERROR. | |
| countError("frame_pushpromise_zero_stream") | |
| return nil, ConnectionError(ErrCodeProtocol) | |
| } | |
| // The PUSH_PROMISE frame includes optional padding. | |
| // Padding fields and flags are identical to those defined for DATA frames | |
| var padLength uint8 | |
| if fh.Flags.Has(FlagPushPromisePadded) { | |
| if p, padLength, err = readByte(p); err != nil { | |
| countError("frame_pushpromise_pad_short") | |
| return | |
| } | |
| } | |
| p, pp.PromiseID, err = readUint32(p) | |
| if err != nil { | |
| countError("frame_pushpromise_promiseid_short") | |
| return | |
| } | |
| pp.PromiseID = pp.PromiseID & (1<<31 - 1) | |
| if int(padLength) > len(p) { | |
| // like the DATA frame, error out if padding is longer than the body. | |
| countError("frame_pushpromise_pad_too_big") | |
| return nil, ConnectionError(ErrCodeProtocol) | |
| } | |
| pp.headerFragBuf = p[:len(p)-int(padLength)] | |
| return pp, nil | |
| } | |
| // PushPromiseParam are the parameters for writing a PUSH_PROMISE frame. | |
| type PushPromiseParam struct { | |
| // StreamID is the required Stream ID to initiate. | |
| StreamID uint32 | |
| // PromiseID is the required Stream ID which this | |
| // Push Promises | |
| PromiseID uint32 | |
| // BlockFragment is part (or all) of a Header Block. | |
| BlockFragment []byte | |
| // EndHeaders indicates that this frame contains an entire | |
| // header block and is not followed by any | |
| // CONTINUATION frames. | |
| EndHeaders bool | |
| // PadLength is the optional number of bytes of zeros to add | |
| // to this frame. | |
| PadLength uint8 | |
| } | |
| // WritePushPromise writes a single PushPromise Frame. | |
| // | |
| // As with Header Frames, This is the low level call for writing | |
| // individual frames. Continuation frames are handled elsewhere. | |
| // | |
| // It will perform exactly one Write to the underlying Writer. | |
| // It is the caller's responsibility to not call other Write methods concurrently. | |
| func (f *Framer) WritePushPromise(p PushPromiseParam) error { | |
| if !validStreamID(p.StreamID) && !f.AllowIllegalWrites { | |
| return errStreamID | |
| } | |
| var flags Flags | |
| if p.PadLength != 0 { | |
| flags |= FlagPushPromisePadded | |
| } | |
| if p.EndHeaders { | |
| flags |= FlagPushPromiseEndHeaders | |
| } | |
| f.startWrite(FramePushPromise, flags, p.StreamID) | |
| if p.PadLength != 0 { | |
| f.writeByte(p.PadLength) | |
| } | |
| if !validStreamID(p.PromiseID) && !f.AllowIllegalWrites { | |
| return errStreamID | |
| } | |
| f.writeUint32(p.PromiseID) | |
| f.wbuf = append(f.wbuf, p.BlockFragment...) | |
| f.wbuf = append(f.wbuf, padZeros[:p.PadLength]...) | |
| return f.endWrite() | |
| } | |
| // WriteRawFrame writes a raw frame. This can be used to write | |
| // extension frames unknown to this package. | |
| func (f *Framer) WriteRawFrame(t FrameType, flags Flags, streamID uint32, payload []byte) error { | |
| f.startWrite(t, flags, streamID) | |
| f.writeBytes(payload) | |
| return f.endWrite() | |
| } | |
| func readByte(p []byte) (remain []byte, b byte, err error) { | |
| if len(p) == 0 { | |
| return nil, 0, io.ErrUnexpectedEOF | |
| } | |
| return p[1:], p[0], nil | |
| } | |
| func readUint32(p []byte) (remain []byte, v uint32, err error) { | |
| if len(p) < 4 { | |
| return nil, 0, io.ErrUnexpectedEOF | |
| } | |
| return p[4:], binary.BigEndian.Uint32(p[:4]), nil | |
| } | |
| type streamEnder interface { | |
| StreamEnded() bool | |
| } | |
| type headersEnder interface { | |
| HeadersEnded() bool | |
| } | |
| type headersOrContinuation interface { | |
| headersEnder | |
| HeaderBlockFragment() []byte | |
| } | |
| // A MetaHeadersFrame is the representation of one HEADERS frame and | |
| // zero or more contiguous CONTINUATION frames and the decoding of | |
| // their HPACK-encoded contents. | |
| // | |
| // This type of frame does not appear on the wire and is only returned | |
| // by the Framer when Framer.ReadMetaHeaders is set. | |
| type MetaHeadersFrame struct { | |
| *HeadersFrame | |
| // Fields are the fields contained in the HEADERS and | |
| // CONTINUATION frames. The underlying slice is owned by the | |
| // Framer and must not be retained after the next call to | |
| // ReadFrame. | |
| // | |
| // Fields are guaranteed to be in the correct http2 order and | |
| // not have unknown pseudo header fields or invalid header | |
| // field names or values. Required pseudo header fields may be | |
| // missing, however. Use the MetaHeadersFrame.Pseudo accessor | |
| // method access pseudo headers. | |
| Fields []hpack.HeaderField | |
| // Truncated is whether the max header list size limit was hit | |
| // and Fields is incomplete. The hpack decoder state is still | |
| // valid, however. | |
| Truncated bool | |
| } | |
| // PseudoValue returns the given pseudo header field's value. | |
| // The provided pseudo field should not contain the leading colon. | |
| func (mh *MetaHeadersFrame) PseudoValue(pseudo string) string { | |
| for _, hf := range mh.Fields { | |
| if !hf.IsPseudo() { | |
| return "" | |
| } | |
| if hf.Name[1:] == pseudo { | |
| return hf.Value | |
| } | |
| } | |
| return "" | |
| } | |
| // RegularFields returns the regular (non-pseudo) header fields of mh. | |
| // The caller does not own the returned slice. | |
| func (mh *MetaHeadersFrame) RegularFields() []hpack.HeaderField { | |
| for i, hf := range mh.Fields { | |
| if !hf.IsPseudo() { | |
| return mh.Fields[i:] | |
| } | |
| } | |
| return nil | |
| } | |
| // PseudoFields returns the pseudo header fields of mh. | |
| // The caller does not own the returned slice. | |
| func (mh *MetaHeadersFrame) PseudoFields() []hpack.HeaderField { | |
| for i, hf := range mh.Fields { | |
| if !hf.IsPseudo() { | |
| return mh.Fields[:i] | |
| } | |
| } | |
| return mh.Fields | |
| } | |
| func (mh *MetaHeadersFrame) rfc9218Priority(priorityAware bool) (p PriorityParam, priorityAwareAfter, hasIntermediary bool) { | |
| var s string | |
| for _, field := range mh.Fields { | |
| if field.Name == "priority" { | |
| s = field.Value | |
| priorityAware = true | |
| } | |
| if slices.Contains([]string{"via", "forwarded", "x-forwarded-for"}, field.Name) { | |
| hasIntermediary = true | |
| } | |
| } | |
| // No need to check for ok. parseRFC9218Priority will return a default | |
| // value if there is no priority field or if the field cannot be parsed. | |
| p, _ = parseRFC9218Priority(s, priorityAware && !hasIntermediary) | |
| return p, priorityAware, hasIntermediary | |
| } | |
| func (mh *MetaHeadersFrame) checkPseudos() error { | |
| var isRequest, isResponse bool | |
| pf := mh.PseudoFields() | |
| for i, hf := range pf { | |
| switch hf.Name { | |
| case ":method", ":path", ":scheme", ":authority", ":protocol": | |
| isRequest = true | |
| case ":status": | |
| isResponse = true | |
| default: | |
| return pseudoHeaderError(hf.Name) | |
| } | |
| // Check for duplicates. | |
| // This would be a bad algorithm, but N is 5. | |
| // And this doesn't allocate. | |
| for _, hf2 := range pf[:i] { | |
| if hf.Name == hf2.Name { | |
| return duplicatePseudoHeaderError(hf.Name) | |
| } | |
| } | |
| } | |
| if isRequest && isResponse { | |
| return errMixPseudoHeaderTypes | |
| } | |
| return nil | |
| } | |
| func (fr *Framer) maxHeaderStringLen() int { | |
| v := int(fr.maxHeaderListSize()) | |
| if v < 0 { | |
| // If maxHeaderListSize overflows an int, use no limit (0). | |
| return 0 | |
| } | |
| return v | |
| } | |
| // readMetaFrame returns 0 or more CONTINUATION frames from fr and | |
| // merge them into the provided hf and returns a MetaHeadersFrame | |
| // with the decoded hpack values. | |
| func (fr *Framer) readMetaFrame(hf *HeadersFrame) (Frame, error) { | |
| if fr.AllowIllegalReads { | |
| return nil, errors.New("illegal use of AllowIllegalReads with ReadMetaHeaders") | |
| } | |
| mh := &MetaHeadersFrame{ | |
| HeadersFrame: hf, | |
| } | |
| var remainSize = fr.maxHeaderListSize() | |
| var sawRegular bool | |
| var invalid error // pseudo header field errors | |
| hdec := fr.ReadMetaHeaders | |
| hdec.SetEmitEnabled(true) | |
| hdec.SetMaxStringLength(fr.maxHeaderStringLen()) | |
| hdec.SetEmitFunc(func(hf hpack.HeaderField) { | |
| if VerboseLogs && fr.logReads { | |
| fr.debugReadLoggerf("http2: decoded hpack field %+v", hf) | |
| } | |
| if !httpguts.ValidHeaderFieldValue(hf.Value) { | |
| // Don't include the value in the error, because it may be sensitive. | |
| invalid = headerFieldValueError(hf.Name) | |
| } | |
| isPseudo := strings.HasPrefix(hf.Name, ":") | |
| if isPseudo { | |
| if sawRegular { | |
| invalid = errPseudoAfterRegular | |
| } | |
| } else { | |
| sawRegular = true | |
| if !validWireHeaderFieldName(hf.Name) { | |
| invalid = headerFieldNameError(hf.Name) | |
| } | |
| } | |
| if invalid != nil { | |
| hdec.SetEmitEnabled(false) | |
| return | |
| } | |
| size := hf.Size() | |
| if size > remainSize { | |
| hdec.SetEmitEnabled(false) | |
| mh.Truncated = true | |
| remainSize = 0 | |
| return | |
| } | |
| remainSize -= size | |
| mh.Fields = append(mh.Fields, hf) | |
| }) | |
| // Lose reference to MetaHeadersFrame: | |
| defer hdec.SetEmitFunc(func(hf hpack.HeaderField) {}) | |
| var hc headersOrContinuation = hf | |
| for { | |
| frag := hc.HeaderBlockFragment() | |
| // Avoid parsing large amounts of headers that we will then discard. | |
| // If the sender exceeds the max header list size by too much, | |
| // skip parsing the fragment and close the connection. | |
| // | |
| // "Too much" is either any CONTINUATION frame after we've already | |
| // exceeded the max header list size (in which case remainSize is 0), | |
| // or a frame whose encoded size is more than twice the remaining | |
| // header list bytes we're willing to accept. | |
| if int64(len(frag)) > int64(2*remainSize) { | |
| if VerboseLogs { | |
| log.Printf("http2: header list too large") | |
| } | |
| // It would be nice to send a RST_STREAM before sending the GOAWAY, | |
| // but the structure of the server's frame writer makes this difficult. | |
| return mh, ConnectionError(ErrCodeProtocol) | |
| } | |
| // Also close the connection after any CONTINUATION frame following an | |
| // invalid header, since we stop tracking the size of the headers after | |
| // an invalid one. | |
| if invalid != nil { | |
| if VerboseLogs { | |
| log.Printf("http2: invalid header: %v", invalid) | |
| } | |
| // It would be nice to send a RST_STREAM before sending the GOAWAY, | |
| // but the structure of the server's frame writer makes this difficult. | |
| return mh, ConnectionError(ErrCodeProtocol) | |
| } | |
| if _, err := hdec.Write(frag); err != nil { | |
| return mh, ConnectionError(ErrCodeCompression) | |
| } | |
| if hc.HeadersEnded() { | |
| break | |
| } | |
| if f, err := fr.ReadFrame(); err != nil { | |
| return nil, err | |
| } else { | |
| hc = f.(*ContinuationFrame) // guaranteed by checkFrameOrder | |
| } | |
| } | |
| mh.HeadersFrame.headerFragBuf = nil | |
| mh.HeadersFrame.invalidate() | |
| if err := hdec.Close(); err != nil { | |
| return mh, ConnectionError(ErrCodeCompression) | |
| } | |
| if invalid != nil { | |
| fr.errDetail = invalid | |
| if VerboseLogs { | |
| log.Printf("http2: invalid header: %v", invalid) | |
| } | |
| return nil, StreamError{mh.StreamID, ErrCodeProtocol, invalid} | |
| } | |
| if err := mh.checkPseudos(); err != nil { | |
| fr.errDetail = err | |
| if VerboseLogs { | |
| log.Printf("http2: invalid pseudo headers: %v", err) | |
| } | |
| return nil, StreamError{mh.StreamID, ErrCodeProtocol, err} | |
| } | |
| return mh, nil | |
| } | |
| func summarizeFrame(f Frame) string { | |
| var buf bytes.Buffer | |
| f.Header().writeDebug(&buf) | |
| switch f := f.(type) { | |
| case *SettingsFrame: | |
| n := 0 | |
| f.ForeachSetting(func(s Setting) error { | |
| n++ | |
| if n == 1 { | |
| buf.WriteString(", settings:") | |
| } | |
| fmt.Fprintf(&buf, " %v=%v,", s.ID, s.Val) | |
| return nil | |
| }) | |
| if n > 0 { | |
| buf.Truncate(buf.Len() - 1) // remove trailing comma | |
| } | |
| case *DataFrame: | |
| data := f.Data() | |
| const max = 256 | |
| if len(data) > max { | |
| data = data[:max] | |
| } | |
| fmt.Fprintf(&buf, " data=%q", data) | |
| if len(f.Data()) > max { | |
| fmt.Fprintf(&buf, " (%d bytes omitted)", len(f.Data())-max) | |
| } | |
| case *WindowUpdateFrame: | |
| if f.StreamID == 0 { | |
| buf.WriteString(" (conn)") | |
| } | |
| fmt.Fprintf(&buf, " incr=%v", f.Increment) | |
| case *PingFrame: | |
| fmt.Fprintf(&buf, " ping=%q", f.Data[:]) | |
| case *GoAwayFrame: | |
| fmt.Fprintf(&buf, " LastStreamID=%v ErrCode=%v Debug=%q", | |
| f.LastStreamID, f.ErrCode, f.debugData) | |
| case *RSTStreamFrame: | |
| fmt.Fprintf(&buf, " ErrCode=%v", f.ErrCode) | |
| } | |
| return buf.String() | |
| } |