1 // Copyright 2018, Google Inc.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
8 // * Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above
11 // copyright notice, this list of conditions and the following disclaimer
12 // in the documentation and/or other materials provided with the
13 // distribution.
14 // * Neither the name of Google Inc. nor the names of its
15 // contributors may be used to endorse or promote products derived from
16 // this software without specific prior written permission.
17 //
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 30 package gax
31 32 import (
33 "bytes"
34 "context"
35 "fmt"
36 "net/http"
37 "runtime"
38 "strings"
39 "unicode"
40 41 "github.com/googleapis/gax-go/v2/callctx"
42 "google.golang.org/grpc/metadata"
43 )
44 45 var (
46 // GoVersion is a header-safe representation of the current runtime
47 // environment's Go version. This is for GAX consumers that need to
48 // report the Go runtime version in API calls.
49 GoVersion string
50 // version is a package internal global variable for testing purposes.
51 version = runtime.Version
52 )
53 54 // versionUnknown is only used when the runtime version cannot be determined.
55 const versionUnknown = "UNKNOWN"
56 57 func init() {
58 GoVersion = goVersion()
59 }
60 61 // goVersion returns a Go runtime version derived from the runtime environment
62 // that is modified to be suitable for reporting in a header, meaning it has no
63 // whitespace. If it is unable to determine the Go runtime version, it returns
64 // versionUnknown.
65 func goVersion() string {
66 const develPrefix = "devel +"
67 68 s := version()
69 if strings.HasPrefix(s, develPrefix) {
70 s = s[len(develPrefix):]
71 if p := strings.IndexFunc(s, unicode.IsSpace); p >= 0 {
72 s = s[:p]
73 }
74 return s
75 } else if p := strings.IndexFunc(s, unicode.IsSpace); p >= 0 {
76 s = s[:p]
77 }
78 79 notSemverRune := func(r rune) bool {
80 return !strings.ContainsRune("0123456789.", r)
81 }
82 83 if strings.HasPrefix(s, "go1") {
84 s = s[2:]
85 var prerelease string
86 if p := strings.IndexFunc(s, notSemverRune); p >= 0 {
87 s, prerelease = s[:p], s[p:]
88 }
89 if strings.HasSuffix(s, ".") {
90 s += "0"
91 } else if strings.Count(s, ".") < 2 {
92 s += ".0"
93 }
94 if prerelease != "" {
95 // Some release candidates already have a dash in them.
96 if !strings.HasPrefix(prerelease, "-") {
97 prerelease = "-" + prerelease
98 }
99 s += prerelease
100 }
101 return s
102 }
103 return "UNKNOWN"
104 }
105 106 // XGoogHeader is for use by the Google Cloud Libraries only. See package
107 // [github.com/googleapis/gax-go/v2/callctx] for help setting/retrieving
108 // request/response headers.
109 //
110 // XGoogHeader formats key-value pairs.
111 // The resulting string is suitable for x-goog-api-client header.
112 func XGoogHeader(keyval ...string) string {
113 if len(keyval) == 0 {
114 return ""
115 }
116 if len(keyval)%2 != 0 {
117 panic("gax.Header: odd argument count")
118 }
119 var buf bytes.Buffer
120 for i := 0; i < len(keyval); i += 2 {
121 buf.WriteByte(' ')
122 buf.WriteString(keyval[i])
123 buf.WriteByte('/')
124 buf.WriteString(keyval[i+1])
125 }
126 return buf.String()[1:]
127 }
128 129 // InsertMetadataIntoOutgoingContext is for use by the Google Cloud Libraries
130 // only. See package [github.com/googleapis/gax-go/v2/callctx] for help
131 // setting/retrieving request/response headers.
132 //
133 // InsertMetadataIntoOutgoingContext returns a new context that merges the
134 // provided keyvals metadata pairs with any existing metadata/headers in the
135 // provided context. keyvals should have a corresponding value for every key
136 // provided. If there is an odd number of keyvals this method will panic.
137 // Existing values for keys will not be overwritten, instead provided values
138 // will be appended to the list of existing values.
139 func InsertMetadataIntoOutgoingContext(ctx context.Context, keyvals ...string) context.Context {
140 return metadata.NewOutgoingContext(ctx, insertMetadata(ctx, keyvals...))
141 }
142 143 // BuildHeaders is for use by the Google Cloud Libraries only. See package
144 // [github.com/googleapis/gax-go/v2/callctx] for help setting/retrieving
145 // request/response headers.
146 //
147 // BuildHeaders returns a new http.Header that merges the provided
148 // keyvals header pairs with any existing metadata/headers in the provided
149 // context. keyvals should have a corresponding value for every key provided.
150 // If there is an odd number of keyvals this method will panic.
151 // Existing values for keys will not be overwritten, instead provided values
152 // will be appended to the list of existing values.
153 func BuildHeaders(ctx context.Context, keyvals ...string) http.Header {
154 return http.Header(insertMetadata(ctx, keyvals...))
155 }
156 157 func insertMetadata(ctx context.Context, keyvals ...string) metadata.MD {
158 if len(keyvals)%2 != 0 {
159 panic(fmt.Sprintf("gax: an even number of key value pairs must be provided, got %d", len(keyvals)))
160 }
161 out, ok := metadata.FromOutgoingContext(ctx)
162 if !ok {
163 out = metadata.MD(make(map[string][]string))
164 }
165 headers := callctx.HeadersFromContext(ctx)
166 167 // x-goog-api-client is a special case that we want to make sure gets merged
168 // into a single header.
169 const xGoogHeader = "x-goog-api-client"
170 var mergedXgoogHeader strings.Builder
171 172 for k, vals := range headers {
173 if k == xGoogHeader {
174 // Merge all values for the x-goog-api-client header set on the ctx.
175 for _, v := range vals {
176 mergedXgoogHeader.WriteString(v)
177 mergedXgoogHeader.WriteRune(' ')
178 }
179 continue
180 }
181 out[k] = append(out[k], vals...)
182 }
183 for i := 0; i < len(keyvals); i = i + 2 {
184 out[keyvals[i]] = append(out[keyvals[i]], keyvals[i+1])
185 186 if keyvals[i] == xGoogHeader {
187 // Merge the x-goog-api-client header values set on the ctx with any
188 // values passed in for it from the client.
189 mergedXgoogHeader.WriteString(keyvals[i+1])
190 mergedXgoogHeader.WriteRune(' ')
191 }
192 }
193 194 // Add the x goog header back in, replacing the separate values that were set.
195 if mergedXgoogHeader.Len() > 0 {
196 out[xGoogHeader] = []string{mergedXgoogHeader.String()[:mergedXgoogHeader.Len()-1]}
197 }
198 199 return out
200 }
201