]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blobdiff - vendor/google.golang.org/grpc/internal/transport/http_util.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / google.golang.org / grpc / internal / transport / http_util.go
similarity index 58%
rename from vendor/google.golang.org/grpc/transport/http_util.go
rename to vendor/google.golang.org/grpc/internal/transport/http_util.go
index 685c6fbf96782e93e1cdca4ebfbea9bbe6f23d9f..77a2cfaaef336ab2de35e506884a0949341b3332 100644 (file)
@@ -24,12 +24,13 @@ import (
        "encoding/base64"
        "fmt"
        "io"
+       "math"
        "net"
        "net/http"
        "strconv"
        "strings"
-       "sync/atomic"
        "time"
+       "unicode/utf8"
 
        "github.com/golang/protobuf/proto"
        "golang.org/x/net/http2"
@@ -44,8 +45,12 @@ const (
        http2MaxFrameLen = 16384 // 16KB frame
        // http://http2.github.io/http2-spec/#SettingValues
        http2InitHeaderTableSize = 4096
-       // http2IOBufSize specifies the buffer size for sending frames.
-       http2IOBufSize = 32 * 1024
+       // baseContentType is the base content-type for gRPC.  This is a valid
+       // content-type on it's own, but can also include a content-subtype such as
+       // "proto" as a suffix after "+" or ";".  See
+       // https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests
+       // for more details.
+       baseContentType = "application/grpc"
 )
 
 var (
@@ -64,7 +69,7 @@ var (
                http2.ErrCodeConnect:            codes.Internal,
                http2.ErrCodeEnhanceYourCalm:    codes.ResourceExhausted,
                http2.ErrCodeInadequateSecurity: codes.PermissionDenied,
-               http2.ErrCodeHTTP11Required:     codes.FailedPrecondition,
+               http2.ErrCodeHTTP11Required:     codes.Internal,
        }
        statusCodeConvTab = map[codes.Code]http2.ErrCode{
                codes.Internal:          http2.ErrCodeInternal,
@@ -111,7 +116,12 @@ type decodeState struct {
        timeout    time.Duration
        method     string
        // key-value metadata map from the peer.
-       mdata map[string][]string
+       mdata          map[string][]string
+       statsTags      []byte
+       statsTrace     []byte
+       contentSubtype string
+       // whether decoding on server side or not
+       serverSide bool
 }
 
 // isReservedHeader checks whether hdr belongs to HTTP2 headers
@@ -123,12 +133,16 @@ func isReservedHeader(hdr string) bool {
        }
        switch hdr {
        case "content-type",
+               "user-agent",
                "grpc-message-type",
                "grpc-encoding",
                "grpc-message",
                "grpc-status",
                "grpc-timeout",
                "grpc-status-details-bin",
+               // Intentionally exclude grpc-previous-rpc-attempts and
+               // grpc-retry-pushback-ms, which are "reserved", but their API
+               // intentionally works via metadata.
                "te":
                return true
        default:
@@ -136,28 +150,55 @@ func isReservedHeader(hdr string) bool {
        }
 }
 
-// isWhitelistedPseudoHeader checks whether hdr belongs to HTTP2 pseudoheaders
-// that should be propagated into metadata visible to users.
-func isWhitelistedPseudoHeader(hdr string) bool {
+// isWhitelistedHeader checks whether hdr should be propagated into metadata
+// visible to users, even though it is classified as "reserved", above.
+func isWhitelistedHeader(hdr string) bool {
        switch hdr {
-       case ":authority":
+       case ":authority", "user-agent":
                return true
        default:
                return false
        }
 }
 
-func validContentType(t string) bool {
-       e := "application/grpc"
-       if !strings.HasPrefix(t, e) {
-               return false
+// contentSubtype returns the content-subtype for the given content-type.  The
+// given content-type must be a valid content-type that starts with
+// "application/grpc". A content-subtype will follow "application/grpc" after a
+// "+" or ";". See
+// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests for
+// more details.
+//
+// If contentType is not a valid content-type for gRPC, the boolean
+// will be false, otherwise true. If content-type == "application/grpc",
+// "application/grpc+", or "application/grpc;", the boolean will be true,
+// but no content-subtype will be returned.
+//
+// contentType is assumed to be lowercase already.
+func contentSubtype(contentType string) (string, bool) {
+       if contentType == baseContentType {
+               return "", true
+       }
+       if !strings.HasPrefix(contentType, baseContentType) {
+               return "", false
+       }
+       // guaranteed since != baseContentType and has baseContentType prefix
+       switch contentType[len(baseContentType)] {
+       case '+', ';':
+               // this will return true for "application/grpc+" or "application/grpc;"
+               // which the previous validContentType function tested to be valid, so we
+               // just say that no content-subtype is specified in this case
+               return contentType[len(baseContentType)+1:], true
+       default:
+               return "", false
        }
-       // Support variations on the content-type
-       // (e.g. "application/grpc+blah", "application/grpc;blah").
-       if len(t) > len(e) && t[len(e)] != '+' && t[len(e)] != ';' {
-               return false
+}
+
+// contentSubtype is assumed to be lowercase
+func contentType(contentSubtype string) string {
+       if contentSubtype == "" {
+               return baseContentType
        }
-       return true
+       return baseContentType + "+" + contentSubtype
 }
 
 func (d *decodeState) status() *status.Status {
@@ -197,13 +238,22 @@ func decodeMetadataHeader(k, v string) (string, error) {
        return v, nil
 }
 
-func (d *decodeState) decodeResponseHeader(frame *http2.MetaHeadersFrame) error {
+func (d *decodeState) decodeHeader(frame *http2.MetaHeadersFrame) error {
+       // frame.Truncated is set to true when framer detects that the current header
+       // list size hits MaxHeaderListSize limit.
+       if frame.Truncated {
+               return status.Error(codes.Internal, "peer header list size exceeded limit")
+       }
        for _, hf := range frame.Fields {
                if err := d.processHeaderField(hf); err != nil {
                        return err
                }
        }
 
+       if d.serverSide {
+               return nil
+       }
+
        // If grpc status exists, no need to check further.
        if d.rawStatusCode != nil || d.statusGen != nil {
                return nil
@@ -212,7 +262,7 @@ func (d *decodeState) decodeResponseHeader(frame *http2.MetaHeadersFrame) error
        // If grpc status doesn't exist and http status doesn't exist,
        // then it's a malformed header.
        if d.httpStatus == nil {
-               return streamErrorf(codes.Internal, "malformed header: doesn't contain status(gRPC or HTTP)")
+               return status.Error(codes.Internal, "malformed header: doesn't contain status(gRPC or HTTP)")
        }
 
        if *(d.httpStatus) != http.StatusOK {
@@ -220,33 +270,46 @@ func (d *decodeState) decodeResponseHeader(frame *http2.MetaHeadersFrame) error
                if !ok {
                        code = codes.Unknown
                }
-               return streamErrorf(code, http.StatusText(*(d.httpStatus)))
+               return status.Error(code, http.StatusText(*(d.httpStatus)))
        }
 
        // gRPC status doesn't exist and http status is OK.
        // Set rawStatusCode to be unknown and return nil error.
        // So that, if the stream has ended this Unknown status
-       // will be propogated to the user.
+       // will be propagated to the user.
        // Otherwise, it will be ignored. In which case, status from
-       // a later trailer, that has StreamEnded flag set, is propogated.
+       // a later trailer, that has StreamEnded flag set, is propagated.
        code := int(codes.Unknown)
        d.rawStatusCode = &code
        return nil
+}
 
+func (d *decodeState) addMetadata(k, v string) {
+       if d.mdata == nil {
+               d.mdata = make(map[string][]string)
+       }
+       d.mdata[k] = append(d.mdata[k], v)
 }
 
 func (d *decodeState) processHeaderField(f hpack.HeaderField) error {
        switch f.Name {
        case "content-type":
-               if !validContentType(f.Value) {
-                       return streamErrorf(codes.FailedPrecondition, "transport: received the unexpected content-type %q", f.Value)
+               contentSubtype, validContentType := contentSubtype(f.Value)
+               if !validContentType {
+                       return status.Errorf(codes.Internal, "transport: received the unexpected content-type %q", f.Value)
                }
+               d.contentSubtype = contentSubtype
+               // TODO: do we want to propagate the whole content-type in the metadata,
+               // or come up with a way to just propagate the content-subtype if it was set?
+               // ie {"content-type": "application/grpc+proto"} or {"content-subtype": "proto"}
+               // in the metadata?
+               d.addMetadata(f.Name, f.Value)
        case "grpc-encoding":
                d.encoding = f.Value
        case "grpc-status":
                code, err := strconv.Atoi(f.Value)
                if err != nil {
-                       return streamErrorf(codes.Internal, "transport: malformed grpc-status: %v", err)
+                       return status.Errorf(codes.Internal, "transport: malformed grpc-status: %v", err)
                }
                d.rawStatusCode = &code
        case "grpc-message":
@@ -254,39 +317,51 @@ func (d *decodeState) processHeaderField(f hpack.HeaderField) error {
        case "grpc-status-details-bin":
                v, err := decodeBinHeader(f.Value)
                if err != nil {
-                       return streamErrorf(codes.Internal, "transport: malformed grpc-status-details-bin: %v", err)
+                       return status.Errorf(codes.Internal, "transport: malformed grpc-status-details-bin: %v", err)
                }
                s := &spb.Status{}
                if err := proto.Unmarshal(v, s); err != nil {
-                       return streamErrorf(codes.Internal, "transport: malformed grpc-status-details-bin: %v", err)
+                       return status.Errorf(codes.Internal, "transport: malformed grpc-status-details-bin: %v", err)
                }
                d.statusGen = status.FromProto(s)
        case "grpc-timeout":
                d.timeoutSet = true
                var err error
                if d.timeout, err = decodeTimeout(f.Value); err != nil {
-                       return streamErrorf(codes.Internal, "transport: malformed time-out: %v", err)
+                       return status.Errorf(codes.Internal, "transport: malformed time-out: %v", err)
                }
        case ":path":
                d.method = f.Value
        case ":status":
                code, err := strconv.Atoi(f.Value)
                if err != nil {
-                       return streamErrorf(codes.Internal, "transport: malformed http-status: %v", err)
+                       return status.Errorf(codes.Internal, "transport: malformed http-status: %v", err)
                }
                d.httpStatus = &code
+       case "grpc-tags-bin":
+               v, err := decodeBinHeader(f.Value)
+               if err != nil {
+                       return status.Errorf(codes.Internal, "transport: malformed grpc-tags-bin: %v", err)
+               }
+               d.statsTags = v
+               d.addMetadata(f.Name, string(v))
+       case "grpc-trace-bin":
+               v, err := decodeBinHeader(f.Value)
+               if err != nil {
+                       return status.Errorf(codes.Internal, "transport: malformed grpc-trace-bin: %v", err)
+               }
+               d.statsTrace = v
+               d.addMetadata(f.Name, string(v))
        default:
-               if !isReservedHeader(f.Name) || isWhitelistedPseudoHeader(f.Name) {
-                       if d.mdata == nil {
-                               d.mdata = make(map[string][]string)
-                       }
-                       v, err := decodeMetadataHeader(f.Name, f.Value)
-                       if err != nil {
-                               errorf("Failed to decode metadata header (%q, %q): %v", f.Name, f.Value, err)
-                               return nil
-                       }
-                       d.mdata[f.Name] = append(d.mdata[f.Name], v)
+               if isReservedHeader(f.Name) && !isWhitelistedHeader(f.Name) {
+                       break
+               }
+               v, err := decodeMetadataHeader(f.Name, f.Value)
+               if err != nil {
+                       errorf("Failed to decode metadata header (%q, %q): %v", f.Name, f.Value, err)
+                       return nil
                }
+               d.addMetadata(f.Name, v)
        }
        return nil
 }
@@ -361,6 +436,10 @@ func decodeTimeout(s string) (time.Duration, error) {
        if size < 2 {
                return 0, fmt.Errorf("transport: timeout string is too short: %q", s)
        }
+       if size > 9 {
+               // Spec allows for 8 digits plus the unit.
+               return 0, fmt.Errorf("transport: timeout string is too long: %q", s)
+       }
        unit := timeoutUnit(s[size-1])
        d, ok := timeoutUnitToDuration(unit)
        if !ok {
@@ -370,21 +449,27 @@ func decodeTimeout(s string) (time.Duration, error) {
        if err != nil {
                return 0, err
        }
+       const maxHours = math.MaxInt64 / int64(time.Hour)
+       if d == time.Hour && t > maxHours {
+               // This timeout would overflow math.MaxInt64; clamp it.
+               return time.Duration(math.MaxInt64), nil
+       }
        return d * time.Duration(t), nil
 }
 
 const (
        spaceByte   = ' '
-       tildaByte   = '~'
+       tildeByte   = '~'
        percentByte = '%'
 )
 
 // encodeGrpcMessage is used to encode status code in header field
-// "grpc-message".
-// It checks to see if each individual byte in msg is an
-// allowable byte, and then either percent encoding or passing it through.
-// When percent encoding, the byte is converted into hexadecimal notation
-// with a '%' prepended.
+// "grpc-message". It does percent encoding and also replaces invalid utf-8
+// characters with Unicode replacement character.
+//
+// It checks to see if each individual byte in msg is an allowable byte, and
+// then either percent encoding or passing it through. When percent encoding,
+// the byte is converted into hexadecimal notation with a '%' prepended.
 func encodeGrpcMessage(msg string) string {
        if msg == "" {
                return ""
@@ -392,7 +477,7 @@ func encodeGrpcMessage(msg string) string {
        lenMsg := len(msg)
        for i := 0; i < lenMsg; i++ {
                c := msg[i]
-               if !(c >= spaceByte && c < tildaByte && c != percentByte) {
+               if !(c >= spaceByte && c <= tildeByte && c != percentByte) {
                        return encodeGrpcMessageUnchecked(msg)
                }
        }
@@ -401,14 +486,26 @@ func encodeGrpcMessage(msg string) string {
 
 func encodeGrpcMessageUnchecked(msg string) string {
        var buf bytes.Buffer
-       lenMsg := len(msg)
-       for i := 0; i < lenMsg; i++ {
-               c := msg[i]
-               if c >= spaceByte && c < tildaByte && c != percentByte {
-                       buf.WriteByte(c)
-               } else {
-                       buf.WriteString(fmt.Sprintf("%%%02X", c))
+       for len(msg) > 0 {
+               r, size := utf8.DecodeRuneInString(msg)
+               for _, b := range []byte(string(r)) {
+                       if size > 1 {
+                               // If size > 1, r is not ascii. Always do percent encoding.
+                               buf.WriteString(fmt.Sprintf("%%%02X", b))
+                               continue
+                       }
+
+                       // The for loop is necessary even if size == 1. r could be
+                       // utf8.RuneError.
+                       //
+                       // fmt.Sprintf("%%%02X", utf8.RuneError) gives "%FFFD".
+                       if b >= spaceByte && b <= tildeByte && b != percentByte {
+                               buf.WriteByte(b)
+                       } else {
+                               buf.WriteString(fmt.Sprintf("%%%02X", b))
+                       }
                }
+               msg = msg[size:]
        }
        return buf.String()
 }
@@ -447,151 +544,80 @@ func decodeGrpcMessageUnchecked(msg string) string {
        return buf.String()
 }
 
-type framer struct {
-       numWriters int32
-       reader     io.Reader
-       writer     *bufio.Writer
-       fr         *http2.Framer
-}
-
-func newFramer(conn net.Conn) *framer {
-       f := &framer{
-               reader: bufio.NewReaderSize(conn, http2IOBufSize),
-               writer: bufio.NewWriterSize(conn, http2IOBufSize),
-       }
-       f.fr = http2.NewFramer(f.writer, f.reader)
-       // Opt-in to Frame reuse API on framer to reduce garbage.
-       // Frames aren't safe to read from after a subsequent call to ReadFrame.
-       f.fr.SetReuseFrames()
-       f.fr.ReadMetaHeaders = hpack.NewDecoder(http2InitHeaderTableSize, nil)
-       return f
-}
+type bufWriter struct {
+       buf       []byte
+       offset    int
+       batchSize int
+       conn      net.Conn
+       err       error
 
-func (f *framer) adjustNumWriters(i int32) int32 {
-       return atomic.AddInt32(&f.numWriters, i)
+       onFlush func()
 }
 
-// The following writeXXX functions can only be called when the caller gets
-// unblocked from writableChan channel (i.e., owns the privilege to write).
-
-func (f *framer) writeContinuation(forceFlush bool, streamID uint32, endHeaders bool, headerBlockFragment []byte) error {
-       if err := f.fr.WriteContinuation(streamID, endHeaders, headerBlockFragment); err != nil {
-               return err
+func newBufWriter(conn net.Conn, batchSize int) *bufWriter {
+       return &bufWriter{
+               buf:       make([]byte, batchSize*2),
+               batchSize: batchSize,
+               conn:      conn,
        }
-       if forceFlush {
-               return f.writer.Flush()
-       }
-       return nil
 }
 
-func (f *framer) writeData(forceFlush bool, streamID uint32, endStream bool, data []byte) error {
-       if err := f.fr.WriteData(streamID, endStream, data); err != nil {
-               return err
+func (w *bufWriter) Write(b []byte) (n int, err error) {
+       if w.err != nil {
+               return 0, w.err
        }
-       if forceFlush {
-               return f.writer.Flush()
+       if w.batchSize == 0 { // Buffer has been disabled.
+               return w.conn.Write(b)
        }
-       return nil
-}
-
-func (f *framer) writeGoAway(forceFlush bool, maxStreamID uint32, code http2.ErrCode, debugData []byte) error {
-       if err := f.fr.WriteGoAway(maxStreamID, code, debugData); err != nil {
-               return err
-       }
-       if forceFlush {
-               return f.writer.Flush()
-       }
-       return nil
-}
-
-func (f *framer) writeHeaders(forceFlush bool, p http2.HeadersFrameParam) error {
-       if err := f.fr.WriteHeaders(p); err != nil {
-               return err
-       }
-       if forceFlush {
-               return f.writer.Flush()
-       }
-       return nil
-}
-
-func (f *framer) writePing(forceFlush, ack bool, data [8]byte) error {
-       if err := f.fr.WritePing(ack, data); err != nil {
-               return err
-       }
-       if forceFlush {
-               return f.writer.Flush()
-       }
-       return nil
-}
-
-func (f *framer) writePriority(forceFlush bool, streamID uint32, p http2.PriorityParam) error {
-       if err := f.fr.WritePriority(streamID, p); err != nil {
-               return err
-       }
-       if forceFlush {
-               return f.writer.Flush()
+       for len(b) > 0 {
+               nn := copy(w.buf[w.offset:], b)
+               b = b[nn:]
+               w.offset += nn
+               n += nn
+               if w.offset >= w.batchSize {
+                       err = w.Flush()
+               }
        }
-       return nil
+       return n, err
 }
 
-func (f *framer) writePushPromise(forceFlush bool, p http2.PushPromiseParam) error {
-       if err := f.fr.WritePushPromise(p); err != nil {
-               return err
-       }
-       if forceFlush {
-               return f.writer.Flush()
+func (w *bufWriter) Flush() error {
+       if w.err != nil {
+               return w.err
        }
-       return nil
-}
-
-func (f *framer) writeRSTStream(forceFlush bool, streamID uint32, code http2.ErrCode) error {
-       if err := f.fr.WriteRSTStream(streamID, code); err != nil {
-               return err
+       if w.offset == 0 {
+               return nil
        }
-       if forceFlush {
-               return f.writer.Flush()
+       if w.onFlush != nil {
+               w.onFlush()
        }
-       return nil
+       _, w.err = w.conn.Write(w.buf[:w.offset])
+       w.offset = 0
+       return w.err
 }
 
-func (f *framer) writeSettings(forceFlush bool, settings ...http2.Setting) error {
-       if err := f.fr.WriteSettings(settings...); err != nil {
-               return err
-       }
-       if forceFlush {
-               return f.writer.Flush()
-       }
-       return nil
+type framer struct {
+       writer *bufWriter
+       fr     *http2.Framer
 }
 
-func (f *framer) writeSettingsAck(forceFlush bool) error {
-       if err := f.fr.WriteSettingsAck(); err != nil {
-               return err
+func newFramer(conn net.Conn, writeBufferSize, readBufferSize int, maxHeaderListSize uint32) *framer {
+       if writeBufferSize < 0 {
+               writeBufferSize = 0
        }
-       if forceFlush {
-               return f.writer.Flush()
+       var r io.Reader = conn
+       if readBufferSize > 0 {
+               r = bufio.NewReaderSize(r, readBufferSize)
        }
-       return nil
-}
-
-func (f *framer) writeWindowUpdate(forceFlush bool, streamID, incr uint32) error {
-       if err := f.fr.WriteWindowUpdate(streamID, incr); err != nil {
-               return err
-       }
-       if forceFlush {
-               return f.writer.Flush()
+       w := newBufWriter(conn, writeBufferSize)
+       f := &framer{
+               writer: w,
+               fr:     http2.NewFramer(w, r),
        }
-       return nil
-}
-
-func (f *framer) flushWrite() error {
-       return f.writer.Flush()
-}
-
-func (f *framer) readFrame() (http2.Frame, error) {
-       return f.fr.ReadFrame()
-}
-
-func (f *framer) errorDetail() error {
-       return f.fr.ErrorDetail()
+       // Opt-in to Frame reuse API on framer to reduce garbage.
+       // Frames aren't safe to read from after a subsequent call to ReadFrame.
+       f.fr.SetReuseFrames()
+       f.fr.MaxHeaderListSize = maxHeaderListSize
+       f.fr.ReadMetaHeaders = hpack.NewDecoder(http2InitHeaderTableSize, nil)
+       return f
 }