Code refactoring for bpa operator
[icn.git] / cmd / bpa-operator / vendor / github.com / grpc-ecosystem / grpc-gateway / runtime / context.go
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         "time"
13
14         "google.golang.org/grpc/codes"
15         "google.golang.org/grpc/grpclog"
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 func decodeBinHeader(v string) ([]byte, error) {
45         if len(v)%4 == 0 {
46                 // Input was padded, or padding was not necessary.
47                 return base64.StdEncoding.DecodeString(v)
48         }
49         return base64.RawStdEncoding.DecodeString(v)
50 }
51
52 /*
53 AnnotateContext adds context information such as metadata from the request.
54
55 At a minimum, the RemoteAddr is included in the fashion of "X-Forwarded-For",
56 except that the forwarded destination is not another HTTP service but rather
57 a gRPC service.
58 */
59 func AnnotateContext(ctx context.Context, mux *ServeMux, req *http.Request) (context.Context, error) {
60         var pairs []string
61         timeout := DefaultContextTimeout
62         if tm := req.Header.Get(metadataGrpcTimeout); tm != "" {
63                 var err error
64                 timeout, err = timeoutDecode(tm)
65                 if err != nil {
66                         return nil, status.Errorf(codes.InvalidArgument, "invalid grpc-timeout: %s", tm)
67                 }
68         }
69
70         for key, vals := range req.Header {
71                 for _, val := range vals {
72                         key = textproto.CanonicalMIMEHeaderKey(key)
73                         // For backwards-compatibility, pass through 'authorization' header with no prefix.
74                         if key == "Authorization" {
75                                 pairs = append(pairs, "authorization", val)
76                         }
77                         if h, ok := mux.incomingHeaderMatcher(key); ok {
78                                 // Handles "-bin" metadata in grpc, since grpc will do another base64
79                                 // encode before sending to server, we need to decode it first.
80                                 if strings.HasSuffix(key, metadataHeaderBinarySuffix) {
81                                         b, err := decodeBinHeader(val)
82                                         if err != nil {
83                                                 return nil, status.Errorf(codes.InvalidArgument, "invalid binary header %s: %s", key, err)
84                                         }
85
86                                         val = string(b)
87                                 }
88                                 pairs = append(pairs, h, val)
89                         }
90                 }
91         }
92         if host := req.Header.Get(xForwardedHost); host != "" {
93                 pairs = append(pairs, strings.ToLower(xForwardedHost), host)
94         } else if req.Host != "" {
95                 pairs = append(pairs, strings.ToLower(xForwardedHost), req.Host)
96         }
97
98         if addr := req.RemoteAddr; addr != "" {
99                 if remoteIP, _, err := net.SplitHostPort(addr); err == nil {
100                         if fwd := req.Header.Get(xForwardedFor); fwd == "" {
101                                 pairs = append(pairs, strings.ToLower(xForwardedFor), remoteIP)
102                         } else {
103                                 pairs = append(pairs, strings.ToLower(xForwardedFor), fmt.Sprintf("%s, %s", fwd, remoteIP))
104                         }
105                 } else {
106                         grpclog.Infof("invalid remote addr: %s", addr)
107                 }
108         }
109
110         if timeout != 0 {
111                 ctx, _ = context.WithTimeout(ctx, timeout)
112         }
113         if len(pairs) == 0 {
114                 return ctx, nil
115         }
116         md := metadata.Pairs(pairs...)
117         for _, mda := range mux.metadataAnnotators {
118                 md = metadata.Join(md, mda(ctx, req))
119         }
120         return metadata.NewOutgoingContext(ctx, md), nil
121 }
122
123 // ServerMetadata consists of metadata sent from gRPC server.
124 type ServerMetadata struct {
125         HeaderMD  metadata.MD
126         TrailerMD metadata.MD
127 }
128
129 type serverMetadataKey struct{}
130
131 // NewServerMetadataContext creates a new context with ServerMetadata
132 func NewServerMetadataContext(ctx context.Context, md ServerMetadata) context.Context {
133         return context.WithValue(ctx, serverMetadataKey{}, md)
134 }
135
136 // ServerMetadataFromContext returns the ServerMetadata in ctx
137 func ServerMetadataFromContext(ctx context.Context) (md ServerMetadata, ok bool) {
138         md, ok = ctx.Value(serverMetadataKey{}).(ServerMetadata)
139         return
140 }
141
142 func timeoutDecode(s string) (time.Duration, error) {
143         size := len(s)
144         if size < 2 {
145                 return 0, fmt.Errorf("timeout string is too short: %q", s)
146         }
147         d, ok := timeoutUnitToDuration(s[size-1])
148         if !ok {
149                 return 0, fmt.Errorf("timeout unit is not recognized: %q", s)
150         }
151         t, err := strconv.ParseInt(s[:size-1], 10, 64)
152         if err != nil {
153                 return 0, err
154         }
155         return d * time.Duration(t), nil
156 }
157
158 func timeoutUnitToDuration(u uint8) (d time.Duration, ok bool) {
159         switch u {
160         case 'H':
161                 return time.Hour, true
162         case 'M':
163                 return time.Minute, true
164         case 'S':
165                 return time.Second, true
166         case 'm':
167                 return time.Millisecond, true
168         case 'u':
169                 return time.Microsecond, true
170         case 'n':
171                 return time.Nanosecond, true
172         default:
173         }
174         return
175 }
176
177 // isPermanentHTTPHeader checks whether hdr belongs to the list of
178 // permenant request headers maintained by IANA.
179 // http://www.iana.org/assignments/message-headers/message-headers.xml
180 func isPermanentHTTPHeader(hdr string) bool {
181         switch hdr {
182         case
183                 "Accept",
184                 "Accept-Charset",
185                 "Accept-Language",
186                 "Accept-Ranges",
187                 "Authorization",
188                 "Cache-Control",
189                 "Content-Type",
190                 "Cookie",
191                 "Date",
192                 "Expect",
193                 "From",
194                 "Host",
195                 "If-Match",
196                 "If-Modified-Since",
197                 "If-None-Match",
198                 "If-Schedule-Tag-Match",
199                 "If-Unmodified-Since",
200                 "Max-Forwards",
201                 "Origin",
202                 "Pragma",
203                 "Referer",
204                 "User-Agent",
205                 "Via",
206                 "Warning":
207                 return true
208         }
209         return false
210 }