| /* |
| * |
| * Copyright 2016 gRPC authors. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| |
| #include <grpc/support/port_platform.h> |
| |
| #include "src/core/ext/filters/client_channel/http_proxy.h" |
| |
| #include <stdbool.h> |
| #include <string.h> |
| |
| #include <grpc/support/alloc.h> |
| #include <grpc/support/log.h> |
| #include <grpc/support/string_util.h> |
| |
| #include "src/core/ext/filters/client_channel/http_connect_handshaker.h" |
| #include "src/core/ext/filters/client_channel/proxy_mapper_registry.h" |
| #include "src/core/lib/channel/channel_args.h" |
| #include "src/core/lib/gpr/env.h" |
| #include "src/core/lib/gpr/host_port.h" |
| #include "src/core/lib/gpr/string.h" |
| #include "src/core/lib/slice/b64.h" |
| #include "src/core/lib/uri/uri_parser.h" |
| |
| /** |
| * Parses the 'https_proxy' env var (fallback on 'http_proxy') and returns the |
| * proxy hostname to resolve or nullptr on error. Also sets 'user_cred' to user |
| * credentials if present in the 'http_proxy' env var, otherwise leaves it |
| * unchanged. It is caller's responsibility to gpr_free user_cred. |
| */ |
| static char* get_http_proxy_server(char** user_cred) { |
| GPR_ASSERT(user_cred != nullptr); |
| char* proxy_name = nullptr; |
| char** authority_strs = nullptr; |
| size_t authority_nstrs; |
| /* Prefer using 'https_proxy'. Fallback on 'http_proxy' if it is not set. The |
| * fallback behavior can be removed if there's a demand for it. |
| */ |
| char* uri_str = gpr_getenv("https_proxy"); |
| if (uri_str == nullptr) uri_str = gpr_getenv("http_proxy"); |
| if (uri_str == nullptr) return nullptr; |
| grpc_uri* uri = grpc_uri_parse(uri_str, false /* suppress_errors */); |
| if (uri == nullptr || uri->authority == nullptr) { |
| gpr_log(GPR_ERROR, "cannot parse value of 'http_proxy' env var"); |
| goto done; |
| } |
| if (strcmp(uri->scheme, "http") != 0) { |
| gpr_log(GPR_ERROR, "'%s' scheme not supported in proxy URI", uri->scheme); |
| goto done; |
| } |
| /* Split on '@' to separate user credentials from host */ |
| gpr_string_split(uri->authority, "@", &authority_strs, &authority_nstrs); |
| GPR_ASSERT(authority_nstrs != 0); /* should have at least 1 string */ |
| if (authority_nstrs == 1) { |
| /* User cred not present in authority */ |
| proxy_name = authority_strs[0]; |
| } else if (authority_nstrs == 2) { |
| /* User cred found */ |
| *user_cred = authority_strs[0]; |
| proxy_name = authority_strs[1]; |
| gpr_log(GPR_DEBUG, "userinfo found in proxy URI"); |
| } else { |
| /* Bad authority */ |
| for (size_t i = 0; i < authority_nstrs; i++) { |
| gpr_free(authority_strs[i]); |
| } |
| proxy_name = nullptr; |
| } |
| gpr_free(authority_strs); |
| done: |
| gpr_free(uri_str); |
| grpc_uri_destroy(uri); |
| return proxy_name; |
| } |
| |
| /** |
| * Checks the value of GRPC_ARG_ENABLE_HTTP_PROXY to determine if http_proxy |
| * should be used. |
| */ |
| bool http_proxy_enabled(const grpc_channel_args* args) { |
| const grpc_arg* arg = |
| grpc_channel_args_find(args, GRPC_ARG_ENABLE_HTTP_PROXY); |
| return grpc_channel_arg_get_bool(arg, true); |
| } |
| |
| static bool proxy_mapper_map_name(grpc_proxy_mapper* mapper, |
| const char* server_uri, |
| const grpc_channel_args* args, |
| char** name_to_resolve, |
| grpc_channel_args** new_args) { |
| if (!http_proxy_enabled(args)) { |
| return false; |
| } |
| char* user_cred = nullptr; |
| *name_to_resolve = get_http_proxy_server(&user_cred); |
| if (*name_to_resolve == nullptr) return false; |
| char* no_proxy_str = nullptr; |
| grpc_uri* uri = grpc_uri_parse(server_uri, false /* suppress_errors */); |
| if (uri == nullptr || uri->path[0] == '\0') { |
| gpr_log(GPR_ERROR, |
| "'http_proxy' environment variable set, but cannot " |
| "parse server URI '%s' -- not using proxy", |
| server_uri); |
| goto no_use_proxy; |
| } |
| if (strcmp(uri->scheme, "unix") == 0) { |
| gpr_log(GPR_INFO, "not using proxy for Unix domain socket '%s'", |
| server_uri); |
| goto no_use_proxy; |
| } |
| no_proxy_str = gpr_getenv("no_proxy"); |
| if (no_proxy_str != nullptr) { |
| static const char* NO_PROXY_SEPARATOR = ","; |
| bool use_proxy = true; |
| char* server_host; |
| char* server_port; |
| if (!gpr_split_host_port(uri->path[0] == '/' ? uri->path + 1 : uri->path, |
| &server_host, &server_port)) { |
| gpr_log(GPR_INFO, |
| "unable to split host and port, not checking no_proxy list for " |
| "host '%s'", |
| server_uri); |
| gpr_free(no_proxy_str); |
| } else { |
| size_t uri_len = strlen(server_host); |
| char** no_proxy_hosts; |
| size_t num_no_proxy_hosts; |
| gpr_string_split(no_proxy_str, NO_PROXY_SEPARATOR, &no_proxy_hosts, |
| &num_no_proxy_hosts); |
| for (size_t i = 0; i < num_no_proxy_hosts; i++) { |
| char* no_proxy_entry = no_proxy_hosts[i]; |
| size_t no_proxy_len = strlen(no_proxy_entry); |
| if (no_proxy_len <= uri_len && |
| gpr_stricmp(no_proxy_entry, &server_host[uri_len - no_proxy_len]) == |
| 0) { |
| gpr_log(GPR_INFO, "not using proxy for host in no_proxy list '%s'", |
| server_uri); |
| use_proxy = false; |
| break; |
| } |
| } |
| for (size_t i = 0; i < num_no_proxy_hosts; i++) { |
| gpr_free(no_proxy_hosts[i]); |
| } |
| gpr_free(no_proxy_hosts); |
| gpr_free(server_host); |
| gpr_free(server_port); |
| gpr_free(no_proxy_str); |
| if (!use_proxy) goto no_use_proxy; |
| } |
| } |
| grpc_arg args_to_add[2]; |
| args_to_add[0] = grpc_channel_arg_string_create( |
| (char*)GRPC_ARG_HTTP_CONNECT_SERVER, |
| uri->path[0] == '/' ? uri->path + 1 : uri->path); |
| if (user_cred != nullptr) { |
| /* Use base64 encoding for user credentials as stated in RFC 7617 */ |
| char* encoded_user_cred = |
| grpc_base64_encode(user_cred, strlen(user_cred), 0, 0); |
| char* header; |
| gpr_asprintf(&header, "Proxy-Authorization:Basic %s", encoded_user_cred); |
| gpr_free(encoded_user_cred); |
| args_to_add[1] = grpc_channel_arg_string_create( |
| (char*)GRPC_ARG_HTTP_CONNECT_HEADERS, header); |
| *new_args = grpc_channel_args_copy_and_add(args, args_to_add, 2); |
| gpr_free(header); |
| } else { |
| *new_args = grpc_channel_args_copy_and_add(args, args_to_add, 1); |
| } |
| grpc_uri_destroy(uri); |
| gpr_free(user_cred); |
| return true; |
| no_use_proxy: |
| if (uri != nullptr) grpc_uri_destroy(uri); |
| gpr_free(*name_to_resolve); |
| *name_to_resolve = nullptr; |
| gpr_free(user_cred); |
| return false; |
| } |
| |
| static bool proxy_mapper_map_address(grpc_proxy_mapper* mapper, |
| const grpc_resolved_address* address, |
| const grpc_channel_args* args, |
| grpc_resolved_address** new_address, |
| grpc_channel_args** new_args) { |
| return false; |
| } |
| |
| static void proxy_mapper_destroy(grpc_proxy_mapper* mapper) {} |
| |
| static const grpc_proxy_mapper_vtable proxy_mapper_vtable = { |
| proxy_mapper_map_name, proxy_mapper_map_address, proxy_mapper_destroy}; |
| |
| static grpc_proxy_mapper proxy_mapper = {&proxy_mapper_vtable}; |
| |
| void grpc_register_http_proxy_mapper() { |
| grpc_proxy_mapper_register(true /* at_start */, &proxy_mapper); |
| } |