gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

context.go (9325B)


      1 package runtime
      2 
      3 import (
      4 	"context"
      5 	"encoding/base64"
      6 	"fmt"
      7 	"net"
      8 	"net/http"
      9 	"net/textproto"
     10 	"strconv"
     11 	"strings"
     12 	"sync"
     13 	"time"
     14 
     15 	"google.golang.org/grpc/codes"
     16 	"google.golang.org/grpc/metadata"
     17 	"google.golang.org/grpc/status"
     18 )
     19 
     20 // MetadataHeaderPrefix is the http prefix that represents custom metadata
     21 // parameters to or from a gRPC call.
     22 const MetadataHeaderPrefix = "Grpc-Metadata-"
     23 
     24 // MetadataPrefix is prepended to permanent HTTP header keys (as specified
     25 // by the IANA) when added to the gRPC context.
     26 const MetadataPrefix = "grpcgateway-"
     27 
     28 // MetadataTrailerPrefix is prepended to gRPC metadata as it is converted to
     29 // HTTP headers in a response handled by grpc-gateway
     30 const MetadataTrailerPrefix = "Grpc-Trailer-"
     31 
     32 const metadataGrpcTimeout = "Grpc-Timeout"
     33 const metadataHeaderBinarySuffix = "-Bin"
     34 
     35 const xForwardedFor = "X-Forwarded-For"
     36 const xForwardedHost = "X-Forwarded-Host"
     37 
     38 var (
     39 	// DefaultContextTimeout is used for gRPC call context.WithTimeout whenever a Grpc-Timeout inbound
     40 	// header isn't present. If the value is 0 the sent `context` will not have a timeout.
     41 	DefaultContextTimeout = 0 * time.Second
     42 )
     43 
     44 type (
     45 	rpcMethodKey       struct{}
     46 	httpPathPatternKey struct{}
     47 
     48 	AnnotateContextOption func(ctx context.Context) context.Context
     49 )
     50 
     51 func WithHTTPPathPattern(pattern string) AnnotateContextOption {
     52 	return func(ctx context.Context) context.Context {
     53 		return withHTTPPathPattern(ctx, pattern)
     54 	}
     55 }
     56 
     57 func decodeBinHeader(v string) ([]byte, error) {
     58 	if len(v)%4 == 0 {
     59 		// Input was padded, or padding was not necessary.
     60 		return base64.StdEncoding.DecodeString(v)
     61 	}
     62 	return base64.RawStdEncoding.DecodeString(v)
     63 }
     64 
     65 /*
     66 AnnotateContext adds context information such as metadata from the request.
     67 
     68 At a minimum, the RemoteAddr is included in the fashion of "X-Forwarded-For",
     69 except that the forwarded destination is not another HTTP service but rather
     70 a gRPC service.
     71 */
     72 func AnnotateContext(ctx context.Context, mux *ServeMux, req *http.Request, rpcMethodName string, options ...AnnotateContextOption) (context.Context, error) {
     73 	ctx, md, err := annotateContext(ctx, mux, req, rpcMethodName, options...)
     74 	if err != nil {
     75 		return nil, err
     76 	}
     77 	if md == nil {
     78 		return ctx, nil
     79 	}
     80 
     81 	return metadata.NewOutgoingContext(ctx, md), nil
     82 }
     83 
     84 // AnnotateIncomingContext adds context information such as metadata from the request.
     85 // Attach metadata as incoming context.
     86 func AnnotateIncomingContext(ctx context.Context, mux *ServeMux, req *http.Request, rpcMethodName string, options ...AnnotateContextOption) (context.Context, error) {
     87 	ctx, md, err := annotateContext(ctx, mux, req, rpcMethodName, options...)
     88 	if err != nil {
     89 		return nil, err
     90 	}
     91 	if md == nil {
     92 		return ctx, nil
     93 	}
     94 
     95 	return metadata.NewIncomingContext(ctx, md), nil
     96 }
     97 
     98 func annotateContext(ctx context.Context, mux *ServeMux, req *http.Request, rpcMethodName string, options ...AnnotateContextOption) (context.Context, metadata.MD, error) {
     99 	ctx = withRPCMethod(ctx, rpcMethodName)
    100 	for _, o := range options {
    101 		ctx = o(ctx)
    102 	}
    103 	var pairs []string
    104 	timeout := DefaultContextTimeout
    105 	if tm := req.Header.Get(metadataGrpcTimeout); tm != "" {
    106 		var err error
    107 		timeout, err = timeoutDecode(tm)
    108 		if err != nil {
    109 			return nil, nil, status.Errorf(codes.InvalidArgument, "invalid grpc-timeout: %s", tm)
    110 		}
    111 	}
    112 
    113 	for key, vals := range req.Header {
    114 		key = textproto.CanonicalMIMEHeaderKey(key)
    115 		for _, val := range vals {
    116 			// For backwards-compatibility, pass through 'authorization' header with no prefix.
    117 			if key == "Authorization" {
    118 				pairs = append(pairs, "authorization", val)
    119 			}
    120 			if h, ok := mux.incomingHeaderMatcher(key); ok {
    121 				// Handles "-bin" metadata in grpc, since grpc will do another base64
    122 				// encode before sending to server, we need to decode it first.
    123 				if strings.HasSuffix(key, metadataHeaderBinarySuffix) {
    124 					b, err := decodeBinHeader(val)
    125 					if err != nil {
    126 						return nil, nil, status.Errorf(codes.InvalidArgument, "invalid binary header %s: %s", key, err)
    127 					}
    128 
    129 					val = string(b)
    130 				}
    131 				pairs = append(pairs, h, val)
    132 			}
    133 		}
    134 	}
    135 	if host := req.Header.Get(xForwardedHost); host != "" {
    136 		pairs = append(pairs, strings.ToLower(xForwardedHost), host)
    137 	} else if req.Host != "" {
    138 		pairs = append(pairs, strings.ToLower(xForwardedHost), req.Host)
    139 	}
    140 
    141 	if addr := req.RemoteAddr; addr != "" {
    142 		if remoteIP, _, err := net.SplitHostPort(addr); err == nil {
    143 			if fwd := req.Header.Get(xForwardedFor); fwd == "" {
    144 				pairs = append(pairs, strings.ToLower(xForwardedFor), remoteIP)
    145 			} else {
    146 				pairs = append(pairs, strings.ToLower(xForwardedFor), fmt.Sprintf("%s, %s", fwd, remoteIP))
    147 			}
    148 		}
    149 	}
    150 
    151 	if timeout != 0 {
    152 		//nolint:govet  // The context outlives this function
    153 		ctx, _ = context.WithTimeout(ctx, timeout)
    154 	}
    155 	if len(pairs) == 0 {
    156 		return ctx, nil, nil
    157 	}
    158 	md := metadata.Pairs(pairs...)
    159 	for _, mda := range mux.metadataAnnotators {
    160 		md = metadata.Join(md, mda(ctx, req))
    161 	}
    162 	return ctx, md, nil
    163 }
    164 
    165 // ServerMetadata consists of metadata sent from gRPC server.
    166 type ServerMetadata struct {
    167 	HeaderMD  metadata.MD
    168 	TrailerMD metadata.MD
    169 }
    170 
    171 type serverMetadataKey struct{}
    172 
    173 // NewServerMetadataContext creates a new context with ServerMetadata
    174 func NewServerMetadataContext(ctx context.Context, md ServerMetadata) context.Context {
    175 	return context.WithValue(ctx, serverMetadataKey{}, md)
    176 }
    177 
    178 // ServerMetadataFromContext returns the ServerMetadata in ctx
    179 func ServerMetadataFromContext(ctx context.Context) (md ServerMetadata, ok bool) {
    180 	md, ok = ctx.Value(serverMetadataKey{}).(ServerMetadata)
    181 	return
    182 }
    183 
    184 // ServerTransportStream implements grpc.ServerTransportStream.
    185 // It should only be used by the generated files to support grpc.SendHeader
    186 // outside of gRPC server use.
    187 type ServerTransportStream struct {
    188 	mu      sync.Mutex
    189 	header  metadata.MD
    190 	trailer metadata.MD
    191 }
    192 
    193 // Method returns the method for the stream.
    194 func (s *ServerTransportStream) Method() string {
    195 	return ""
    196 }
    197 
    198 // Header returns the header metadata of the stream.
    199 func (s *ServerTransportStream) Header() metadata.MD {
    200 	s.mu.Lock()
    201 	defer s.mu.Unlock()
    202 	return s.header.Copy()
    203 }
    204 
    205 // SetHeader sets the header metadata.
    206 func (s *ServerTransportStream) SetHeader(md metadata.MD) error {
    207 	if md.Len() == 0 {
    208 		return nil
    209 	}
    210 
    211 	s.mu.Lock()
    212 	s.header = metadata.Join(s.header, md)
    213 	s.mu.Unlock()
    214 	return nil
    215 }
    216 
    217 // SendHeader sets the header metadata.
    218 func (s *ServerTransportStream) SendHeader(md metadata.MD) error {
    219 	return s.SetHeader(md)
    220 }
    221 
    222 // Trailer returns the cached trailer metadata.
    223 func (s *ServerTransportStream) Trailer() metadata.MD {
    224 	s.mu.Lock()
    225 	defer s.mu.Unlock()
    226 	return s.trailer.Copy()
    227 }
    228 
    229 // SetTrailer sets the trailer metadata.
    230 func (s *ServerTransportStream) SetTrailer(md metadata.MD) error {
    231 	if md.Len() == 0 {
    232 		return nil
    233 	}
    234 
    235 	s.mu.Lock()
    236 	s.trailer = metadata.Join(s.trailer, md)
    237 	s.mu.Unlock()
    238 	return nil
    239 }
    240 
    241 func timeoutDecode(s string) (time.Duration, error) {
    242 	size := len(s)
    243 	if size < 2 {
    244 		return 0, fmt.Errorf("timeout string is too short: %q", s)
    245 	}
    246 	d, ok := timeoutUnitToDuration(s[size-1])
    247 	if !ok {
    248 		return 0, fmt.Errorf("timeout unit is not recognized: %q", s)
    249 	}
    250 	t, err := strconv.ParseInt(s[:size-1], 10, 64)
    251 	if err != nil {
    252 		return 0, err
    253 	}
    254 	return d * time.Duration(t), nil
    255 }
    256 
    257 func timeoutUnitToDuration(u uint8) (d time.Duration, ok bool) {
    258 	switch u {
    259 	case 'H':
    260 		return time.Hour, true
    261 	case 'M':
    262 		return time.Minute, true
    263 	case 'S':
    264 		return time.Second, true
    265 	case 'm':
    266 		return time.Millisecond, true
    267 	case 'u':
    268 		return time.Microsecond, true
    269 	case 'n':
    270 		return time.Nanosecond, true
    271 	default:
    272 	}
    273 	return
    274 }
    275 
    276 // isPermanentHTTPHeader checks whether hdr belongs to the list of
    277 // permanent request headers maintained by IANA.
    278 // http://www.iana.org/assignments/message-headers/message-headers.xml
    279 func isPermanentHTTPHeader(hdr string) bool {
    280 	switch hdr {
    281 	case
    282 		"Accept",
    283 		"Accept-Charset",
    284 		"Accept-Language",
    285 		"Accept-Ranges",
    286 		"Authorization",
    287 		"Cache-Control",
    288 		"Content-Type",
    289 		"Cookie",
    290 		"Date",
    291 		"Expect",
    292 		"From",
    293 		"Host",
    294 		"If-Match",
    295 		"If-Modified-Since",
    296 		"If-None-Match",
    297 		"If-Schedule-Tag-Match",
    298 		"If-Unmodified-Since",
    299 		"Max-Forwards",
    300 		"Origin",
    301 		"Pragma",
    302 		"Referer",
    303 		"User-Agent",
    304 		"Via",
    305 		"Warning":
    306 		return true
    307 	}
    308 	return false
    309 }
    310 
    311 // RPCMethod returns the method string for the server context. The returned
    312 // string is in the format of "/package.service/method".
    313 func RPCMethod(ctx context.Context) (string, bool) {
    314 	m := ctx.Value(rpcMethodKey{})
    315 	if m == nil {
    316 		return "", false
    317 	}
    318 	ms, ok := m.(string)
    319 	if !ok {
    320 		return "", false
    321 	}
    322 	return ms, true
    323 }
    324 
    325 func withRPCMethod(ctx context.Context, rpcMethodName string) context.Context {
    326 	return context.WithValue(ctx, rpcMethodKey{}, rpcMethodName)
    327 }
    328 
    329 // HTTPPathPattern returns the HTTP path pattern string relating to the HTTP handler, if one exists.
    330 // The format of the returned string is defined by the google.api.http path template type.
    331 func HTTPPathPattern(ctx context.Context) (string, bool) {
    332 	m := ctx.Value(httpPathPatternKey{})
    333 	if m == nil {
    334 		return "", false
    335 	}
    336 	ms, ok := m.(string)
    337 	if !ok {
    338 		return "", false
    339 	}
    340 	return ms, true
    341 }
    342 
    343 func withHTTPPathPattern(ctx context.Context, httpPathPattern string) context.Context {
    344 	return context.WithValue(ctx, httpPathPatternKey{}, httpPathPattern)
    345 }