Imported Upstream version 1.27.0
[platform/upstream/grpc.git] / src / core / ext / filters / client_channel / http_proxy.cc
1 /*
2  *
3  * Copyright 2016 gRPC authors.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  *
17  */
18
19 #include <grpc/support/port_platform.h>
20
21 #include "src/core/ext/filters/client_channel/http_proxy.h"
22
23 #include <stdbool.h>
24 #include <string.h>
25
26 #include <grpc/support/alloc.h>
27 #include <grpc/support/log.h>
28 #include <grpc/support/string_util.h>
29
30 #include "src/core/ext/filters/client_channel/http_connect_handshaker.h"
31 #include "src/core/ext/filters/client_channel/proxy_mapper_registry.h"
32 #include "src/core/lib/channel/channel_args.h"
33 #include "src/core/lib/gpr/env.h"
34 #include "src/core/lib/gpr/string.h"
35 #include "src/core/lib/gprpp/host_port.h"
36 #include "src/core/lib/slice/b64.h"
37 #include "src/core/lib/uri/uri_parser.h"
38
39 namespace grpc_core {
40 namespace {
41
42 /**
43  * Parses the 'https_proxy' env var (fallback on 'http_proxy') and returns the
44  * proxy hostname to resolve or nullptr on error. Also sets 'user_cred' to user
45  * credentials if present in the 'http_proxy' env var, otherwise leaves it
46  * unchanged. It is caller's responsibility to gpr_free user_cred.
47  */
48 char* GetHttpProxyServer(char** user_cred) {
49   GPR_ASSERT(user_cred != nullptr);
50   char* proxy_name = nullptr;
51   char** authority_strs = nullptr;
52   size_t authority_nstrs;
53   /* Prefer using 'grpc_proxy'. Fallback on 'http_proxy' if it is not set.
54    * Also prefer using 'https_proxy' with fallback on 'http_proxy'. The
55    * fallback behavior can be removed if there's a demand for it.
56    */
57   char* uri_str = gpr_getenv("grpc_proxy");
58   if (uri_str == nullptr) uri_str = gpr_getenv("https_proxy");
59   if (uri_str == nullptr) uri_str = gpr_getenv("http_proxy");
60   if (uri_str == nullptr) return nullptr;
61   grpc_uri* uri = grpc_uri_parse(uri_str, false /* suppress_errors */);
62   if (uri == nullptr || uri->authority == nullptr) {
63     gpr_log(GPR_ERROR, "cannot parse value of 'http_proxy' env var");
64     goto done;
65   }
66   if (strcmp(uri->scheme, "http") != 0) {
67     gpr_log(GPR_ERROR, "'%s' scheme not supported in proxy URI", uri->scheme);
68     goto done;
69   }
70   /* Split on '@' to separate user credentials from host */
71   gpr_string_split(uri->authority, "@", &authority_strs, &authority_nstrs);
72   GPR_ASSERT(authority_nstrs != 0); /* should have at least 1 string */
73   if (authority_nstrs == 1) {
74     /* User cred not present in authority */
75     proxy_name = authority_strs[0];
76   } else if (authority_nstrs == 2) {
77     /* User cred found */
78     *user_cred = authority_strs[0];
79     proxy_name = authority_strs[1];
80     gpr_log(GPR_DEBUG, "userinfo found in proxy URI");
81   } else {
82     /* Bad authority */
83     for (size_t i = 0; i < authority_nstrs; i++) {
84       gpr_free(authority_strs[i]);
85     }
86     proxy_name = nullptr;
87   }
88   gpr_free(authority_strs);
89 done:
90   gpr_free(uri_str);
91   grpc_uri_destroy(uri);
92   return proxy_name;
93 }
94
95 class HttpProxyMapper : public ProxyMapperInterface {
96  public:
97   bool MapName(const char* server_uri, const grpc_channel_args* args,
98                char** name_to_resolve, grpc_channel_args** new_args) override {
99     if (!grpc_channel_args_find_bool(args, GRPC_ARG_ENABLE_HTTP_PROXY, true)) {
100       return false;
101     }
102     char* user_cred = nullptr;
103     *name_to_resolve = GetHttpProxyServer(&user_cred);
104     if (*name_to_resolve == nullptr) return false;
105     char* no_proxy_str = nullptr;
106     grpc_uri* uri = grpc_uri_parse(server_uri, false /* suppress_errors */);
107     if (uri == nullptr || uri->path[0] == '\0') {
108       gpr_log(GPR_ERROR,
109               "'http_proxy' environment variable set, but cannot "
110               "parse server URI '%s' -- not using proxy",
111               server_uri);
112       goto no_use_proxy;
113     }
114     if (strcmp(uri->scheme, "unix") == 0) {
115       gpr_log(GPR_INFO, "not using proxy for Unix domain socket '%s'",
116               server_uri);
117       goto no_use_proxy;
118     }
119     /* Prefer using 'no_grpc_proxy'. Fallback on 'no_proxy' if it is not set. */
120     no_proxy_str = gpr_getenv("no_grpc_proxy");
121     if (no_proxy_str == nullptr) no_proxy_str = gpr_getenv("no_proxy");
122     if (no_proxy_str != nullptr) {
123       static const char* NO_PROXY_SEPARATOR = ",";
124       bool use_proxy = true;
125       grpc_core::UniquePtr<char> server_host;
126       grpc_core::UniquePtr<char> server_port;
127       if (!grpc_core::SplitHostPort(
128               uri->path[0] == '/' ? uri->path + 1 : uri->path, &server_host,
129               &server_port)) {
130         gpr_log(GPR_INFO,
131                 "unable to split host and port, not checking no_proxy list for "
132                 "host '%s'",
133                 server_uri);
134         gpr_free(no_proxy_str);
135       } else {
136         size_t uri_len = strlen(server_host.get());
137         char** no_proxy_hosts;
138         size_t num_no_proxy_hosts;
139         gpr_string_split(no_proxy_str, NO_PROXY_SEPARATOR, &no_proxy_hosts,
140                          &num_no_proxy_hosts);
141         for (size_t i = 0; i < num_no_proxy_hosts; i++) {
142           char* no_proxy_entry = no_proxy_hosts[i];
143           size_t no_proxy_len = strlen(no_proxy_entry);
144           if (no_proxy_len <= uri_len &&
145               gpr_stricmp(no_proxy_entry,
146                           &(server_host.get()[uri_len - no_proxy_len])) == 0) {
147             gpr_log(GPR_INFO, "not using proxy for host in no_proxy list '%s'",
148                     server_uri);
149             use_proxy = false;
150             break;
151           }
152         }
153         for (size_t i = 0; i < num_no_proxy_hosts; i++) {
154           gpr_free(no_proxy_hosts[i]);
155         }
156         gpr_free(no_proxy_hosts);
157         gpr_free(no_proxy_str);
158         if (!use_proxy) goto no_use_proxy;
159       }
160     }
161     grpc_arg args_to_add[2];
162     args_to_add[0] = grpc_channel_arg_string_create(
163         (char*)GRPC_ARG_HTTP_CONNECT_SERVER,
164         uri->path[0] == '/' ? uri->path + 1 : uri->path);
165     if (user_cred != nullptr) {
166       /* Use base64 encoding for user credentials as stated in RFC 7617 */
167       char* encoded_user_cred =
168           grpc_base64_encode(user_cred, strlen(user_cred), 0, 0);
169       char* header;
170       gpr_asprintf(&header, "Proxy-Authorization:Basic %s", encoded_user_cred);
171       gpr_free(encoded_user_cred);
172       args_to_add[1] = grpc_channel_arg_string_create(
173           (char*)GRPC_ARG_HTTP_CONNECT_HEADERS, header);
174       *new_args = grpc_channel_args_copy_and_add(args, args_to_add, 2);
175       gpr_free(header);
176     } else {
177       *new_args = grpc_channel_args_copy_and_add(args, args_to_add, 1);
178     }
179     grpc_uri_destroy(uri);
180     gpr_free(user_cred);
181     return true;
182   no_use_proxy:
183     if (uri != nullptr) grpc_uri_destroy(uri);
184     gpr_free(*name_to_resolve);
185     *name_to_resolve = nullptr;
186     gpr_free(user_cred);
187     return false;
188   }
189
190   bool MapAddress(const grpc_resolved_address& /*address*/,
191                   const grpc_channel_args* /*args*/,
192                   grpc_resolved_address** /*new_address*/,
193                   grpc_channel_args** /*new_args*/) override {
194     return false;
195   }
196 };
197
198 }  // namespace
199
200 void RegisterHttpProxyMapper() {
201   ProxyMapperRegistry::Register(
202       true /* at_start */,
203       std::unique_ptr<ProxyMapperInterface>(new HttpProxyMapper()));
204 }
205
206 }  // namespace grpc_core