From 9216d49ded190b018bd63a8dd55e71d64ef29389 Mon Sep 17 00:00:00 2001 From: Dengke Tang Date: Wed, 25 Jun 2025 10:31:04 -0700 Subject: [PATCH 1/8] no proxy --- include/aws/http/private/no_proxy.h | 26 ++ source/no_proxy.c | 368 +++++++++++++++++++++ source/proxy_connection.c | 46 ++- tests/CMakeLists.txt | 14 + tests/test_no_proxy.c | 484 ++++++++++++++++++++++++++++ 5 files changed, 912 insertions(+), 26 deletions(-) create mode 100644 include/aws/http/private/no_proxy.h create mode 100644 source/no_proxy.c create mode 100644 tests/test_no_proxy.c diff --git a/include/aws/http/private/no_proxy.h b/include/aws/http/private/no_proxy.h new file mode 100644 index 000000000..7ee30d7a4 --- /dev/null +++ b/include/aws/http/private/no_proxy.h @@ -0,0 +1,26 @@ +#ifndef AWS_NO_PROXY_H +#define AWS_NO_PROXY_H + +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ +#include +AWS_PUSH_SANE_WARNING_LEVEL +AWS_EXTERN_C_BEGIN + +/* + * Check if a host should bypass the proxy based on the NO_PROXY environment variable. + * Since NO_PROXY has no standard yet. Follows the curl implementation from noproxy.c. + * + * NO_PROXY is a comma-separated list of domain names, hostnames, or IP addresses that + * should bypass the proxy. + * + * Returns true if the host should bypass the proxy. + */ +AWS_HTTP_API bool aws_check_no_proxy(struct aws_allocator *allocator, struct aws_byte_cursor host); + +AWS_EXTERN_C_END +AWS_POP_SANE_WARNING_LEVEL + +#endif /* AWS_NO_PROXY_H */ diff --git a/source/no_proxy.c b/source/no_proxy.c new file mode 100644 index 000000000..bd28ee9e8 --- /dev/null +++ b/source/no_proxy.c @@ -0,0 +1,368 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ +#include +#include + +#ifdef _WIN32 +# include +#else +# include +#endif + +static const char *s_no_proxy_env_var = "NO_PROXY"; +static const char *s_no_proxy_env_var_low = "no_proxy"; + +enum hostname_type { + HOSTNAME_TYPE_IPV4, + HOSTNAME_TYPE_IPV6, + HOSTNAME_TYPE_REGULAR, +}; + +/** + * Determines whether a host string is an IPv4 address and stores the binary representation. + * Checks if the string follows IPv4 format + * + * @param host The host string to check + * @param addr_out Optional pointer to store the parsed binary address. Must be at least 4 bytes. + * @return true if the host is an IPv4 address, false otherwise + */ +static bool s_is_ipv4_address(const struct aws_byte_cursor *host, void *addr_out) { + if (!host || host->len == 0 || !addr_out) { + return false; + } + + char ip_buffer[128] = {'\0'}; + if (host->len >= sizeof(ip_buffer)) { + /* Too long to be valid IPv4 */ + return false; + } + + memcpy(ip_buffer, host->ptr, host->len); + ip_buffer[host->len] = '\0'; /* Null-terminate for inet_pton */ + + int result = inet_pton(AF_INET, ip_buffer, addr_out); + return result == 1; +} + +/** + * Determines whether a host string is an IPv6 address and stores the binary representation. + * If it contains `[]`, remove them + * + * @param host The host string to check + * @param addr_out Optional pointer to store the parsed binary address. Must be at least 16 bytes. + * @return true if the host is an IPv6 address, false otherwise + */ +static bool s_is_ipv6_address_and_update_host(struct aws_byte_cursor *host, void *addr_out) { + if (!host || host->len < 2 || !addr_out) { + return false; + } + + /* Check if the address is enclosed in brackets and strip them for validation */ + if (host->ptr[0] == '[' && host->ptr[host->len - 1] == ']') { + aws_byte_cursor_advance(host, 1); + host->len--; + } + + char ip_buffer[128] = {'\0'}; + if (host->len >= sizeof(ip_buffer)) { + /* Too long to be valid IPv6 */ + return false; + } + + memcpy(ip_buffer, host->ptr, host->len); + ip_buffer[host->len] = '\0'; /* Null-terminate for inet_pton */ + + int result = inet_pton(AF_INET6, ip_buffer, addr_out); + return result == 1; +} + +/** + * s_cidr4_match() returns true if the given IPv4 address is within the + * specified CIDR address range. + * Based on the curl implementation Curl_cidr4_match(). + * + * @param bits The number of network bits in the CIDR notation + * @param network_part The network pattern to match against (e.g., "192.168.0.0")\ + * @param host_addr Pre-parsed binary representation of the host IP, or NULL to parse from host + * @return true if the IP address matches the CIDR pattern, false otherwise + */ +static bool s_cidr4_match(uint16_t bits, struct aws_byte_cursor network_part, const void *host_addr) { + + uint32_t address = 0; + uint32_t check = 0; + + /* Check for valid bits parameter */ + if (bits > 32) { + /* Invalid netmask bits */ + return false; + } + + /* Parse the host address if not provided */ + AWS_ASSERT(host_addr != NULL); + /* Use the pre-parsed host address */ + memcpy(&address, host_addr, sizeof(address)); + + /* Parse the network pattern */ + char pattern_buffer[128] = {'\0'}; + + /* Check buffer size */ + if (network_part.len >= sizeof(pattern_buffer)) { + /* Too long to be valid IPv4 */ + return false; + } + + memcpy(pattern_buffer, network_part.ptr, network_part.len); + pattern_buffer[network_part.len] = '\0'; + + /* Convert network pattern to binary */ + if (inet_pton(AF_INET, pattern_buffer, &check) != 1) { + return false; + } + + if (bits > 0 && bits < 32) { + /* Apply the network mask for CIDR comparison */ + uint32_t mask = 0xffffffff << (32 - bits); + uint32_t host_network = ntohl(address); + uint32_t check_network = ntohl(check); + + /* Compare the masked addresses */ + return (host_network & mask) == (check_network & mask); + } + + /* For /32 or no bits specified, use exact match */ + return address == check; +} + +/** + * s_cidr6_match() returns true if the given IPv6 address is within the + * specified CIDR address range. + * Based on the curl implementation Curl_cidr6_match(). + * + * @param bits The number of network bits in the CIDR notation + * @param network_part The network pattern to match against (e.g., "2001:db8::") + * @param host_addr Pre-parsed binary representation of the host IP, or NULL to parse from host + * @return true if the IP address matches the CIDR pattern, false otherwise + */ +static bool s_cidr6_match(uint16_t bits, struct aws_byte_cursor network_part, const void *host_addr) { + + unsigned char address[16] = {0}; + unsigned char check[16] = {0}; + + /* If no bits specified, use full 128 bits for IPv6 */ + if (!bits) { + bits = 128; + } + + /* Check for valid bits parameter */ + if (bits > 128) { + return false; + } + + AWS_ASSERT(host_addr != NULL); + /* Copy pre-parsed host address if provided */ + memcpy(address, host_addr, sizeof(address)); + + /* Parse the network pattern */ + char pattern_buffer[128] = {'\0'}; + + /* Check buffer size */ + if (network_part.len >= sizeof(pattern_buffer)) { + /* Too long to be valid IPv6 */ + return false; + } + + memcpy(pattern_buffer, network_part.ptr, network_part.len); + pattern_buffer[network_part.len] = '\0'; + + /* Convert network pattern to binary */ + if (inet_pton(AF_INET6, pattern_buffer, check) != 1) { + return false; + } + + /* Calculate full bytes and remaining bits in the netmask */ + unsigned int bytes = bits / 8; + unsigned int rest = bits % 8; + + /* Compare full bytes of the network part */ + if (bytes > 0 && memcmp(address, check, bytes) != 0) { + return false; + } + + /* If we have remaining bits, compare the partial byte */ + if (rest > 0 && bytes < 16) { + /* Create a mask for the remaining bits */ + unsigned char mask = (unsigned char)(0xff << (8 - rest)); + + /* Check if the masked bits match */ + if ((address[bytes] & mask) != (check[bytes] & mask)) { + return false; + } + } + + /* All checks passed, addresses match within the CIDR range */ + return true; +} + +static bool s_is_dot(uint8_t c) { + return c == '.'; +} + +/* The host is expected to be the result from */ +bool aws_check_no_proxy(struct aws_allocator *allocator, struct aws_byte_cursor host) { + if (host.len == 0) { + return false; + } + + struct aws_string *no_proxy_str = aws_get_env_nonempty(allocator, s_no_proxy_env_var_low); + if (no_proxy_str == NULL) { + no_proxy_str = aws_get_env_nonempty(allocator, s_no_proxy_env_var); + } + + if (no_proxy_str == NULL) { + aws_string_destroy(no_proxy_str); + return false; + } + + /* Single "*" wildcard matches all hosts */ + if (aws_string_eq_c_str(no_proxy_str, "*")) { + AWS_LOGF_DEBUG(AWS_LS_HTTP_CONNECTION, "wildcard no_proxy found, bypassing any proxy"); + aws_string_destroy(no_proxy_str); + return true; + } + bool bypass = false; + struct aws_byte_cursor no_proxy_cur = aws_byte_cursor_from_string(no_proxy_str); + struct aws_array_list no_proxy_list; + if (aws_array_list_init_dynamic(&no_proxy_list, allocator, 10, sizeof(struct aws_byte_cursor))) { + goto cleanup; + } + /* Split the NO_PROXY string by commas */ + if (aws_byte_cursor_split_on_char(&no_proxy_cur, ',', &no_proxy_list)) { + goto cleanup; + } + + /* Store parsed binary addresses for reuse */ + unsigned char ipv4_addr[4] = {0}; + unsigned char ipv6_addr[16] = {0}; + void *host_addr_ptr = NULL; + + /* Determine host type and parse address if applicable */ + enum hostname_type type = HOSTNAME_TYPE_REGULAR; + if (s_is_ipv4_address(&host, ipv4_addr)) { + type = HOSTNAME_TYPE_IPV4; + host_addr_ptr = ipv4_addr; + } else { + struct aws_byte_cursor host_copy = host; + if (s_is_ipv6_address_and_update_host(&host_copy, ipv6_addr)) { + type = HOSTNAME_TYPE_IPV6; + host_addr_ptr = ipv6_addr; + /* Update the host */ + host = host_copy; + } else { + /* Not an IP address, so it's a regular hostname */ + type = HOSTNAME_TYPE_REGULAR; + /* Ignore the trailing dot in the hostname */ + host = aws_byte_cursor_right_trim_pred(&host, s_is_dot); + } + } + + char bits_buffer[8] = {'\0'}; + for (size_t i = 0; i < aws_array_list_length(&no_proxy_list); i++) { + struct aws_byte_cursor pattern; + if (aws_array_list_get_at(&no_proxy_list, &pattern, i)) { + continue; + } + + /* Trim whitespace from both ends for the pattern */ + pattern = aws_byte_cursor_trim_pred(&pattern, aws_isspace); + if (pattern.len == 0) { + /* If pattern is empty, ignore it. */ + continue; + } + switch (type) { + case HOSTNAME_TYPE_REGULAR: { + /** + * A: example.com matches 'example.com' + * B: www.example.com matches 'example.com' + * C: nonexample.com DOES NOT match 'example.com' + */ + /* Trim dot from both ends for the pattern */ + pattern = aws_byte_cursor_trim_pred(&pattern, s_is_dot); + if (pattern.len == 0) { + /* If pattern is empty, ignore it. */ + continue; + } + if (pattern.len == host.len) { + if (aws_byte_cursor_eq_ignore_case(&pattern, &host)) { + bypass = true; + goto cleanup; + } else { + continue; + } + } else if (pattern.len < host.len) { + /* Check if the pattern is a suffix of the host. All the math is safe since pattern.len < host.len + */ + struct aws_byte_cursor tail_with_extra_byte = host; + /* 1. the byte before the tail should be `.` */ + aws_byte_cursor_advance(&tail_with_extra_byte, host.len - pattern.len - 1); + uint8_t var = 0; + /* tail_with_extra_byte will be updated to move over the `.` */ + aws_byte_cursor_read_u8(&tail_with_extra_byte, &var); + if (var != '.') { + continue; + } + /* 2. the tail of the host should match the pattern */ + if (aws_byte_cursor_eq_ignore_case(&pattern, &tail_with_extra_byte)) { + bypass = true; + goto cleanup; + } else { + continue; + } + } + } break; + case HOSTNAME_TYPE_IPV4: + case HOSTNAME_TYPE_IPV6: { + /* Extract network part and bits from CIDR notation */ + struct aws_byte_cursor substr = {0}; + struct aws_byte_cursor network_part = {0}; + /* CIDR found. parse the bits */ + uint16_t network_bits = 0; + if (aws_byte_cursor_next_split(&pattern, '/', &substr)) { + network_part = substr; + } + if (aws_byte_cursor_next_split(&pattern, '/', &substr)) { + /* This substr will be number of bits. */ + if (substr.len > 8) { + /* Invalid, ignore it. */ + continue; + } + memcpy(bits_buffer, substr.ptr, substr.len); + bits_buffer[substr.len] = '\0'; /* Null-terminate for atoi */ + network_bits = (uint16_t)atoi(bits_buffer); + } + if (type == HOSTNAME_TYPE_IPV4) { + if (s_cidr4_match(network_bits, network_part, host_addr_ptr)) { + bypass = true; + goto cleanup; + } + } else { + if (s_cidr6_match(network_bits, network_part, host_addr_ptr)) { + bypass = true; + goto cleanup; + } + } + } break; + + default: + /* Invalid stage */ + AWS_FATAL_ASSERT(false); + break; + } + } + +cleanup: + aws_array_list_clean_up(&no_proxy_list); + aws_string_destroy(no_proxy_str); + return bypass; +} diff --git a/source/proxy_connection.c b/source/proxy_connection.c index 3706c2fd5..57beea177 100644 --- a/source/proxy_connection.c +++ b/source/proxy_connection.c @@ -3,6 +3,7 @@ * SPDX-License-Identifier: Apache-2.0. */ +#include #include #include @@ -11,7 +12,6 @@ #include #include #include -#include #include #include #include @@ -29,10 +29,10 @@ AWS_STATIC_STRING_FROM_LITERAL(s_proxy_connection_header_value, "Keep-Alive"); AWS_STATIC_STRING_FROM_LITERAL(s_options_method, "OPTIONS"); AWS_STATIC_STRING_FROM_LITERAL(s_star_path, "*"); -AWS_STATIC_STRING_FROM_LITERAL(s_http_proxy_env_var, "HTTP_PROXY"); -AWS_STATIC_STRING_FROM_LITERAL(s_http_proxy_env_var_low, "http_proxy"); -AWS_STATIC_STRING_FROM_LITERAL(s_https_proxy_env_var, "HTTPS_PROXY"); -AWS_STATIC_STRING_FROM_LITERAL(s_https_proxy_env_var_low, "https_proxy"); +static const char *s_http_proxy_env_var = "HTTP_PROXY"; +static const char *s_http_proxy_env_var_low = "http_proxy"; +static const char *s_https_proxy_env_var = "HTTPS_PROXY"; +static const char *s_https_proxy_env_var_low = "https_proxy"; #ifndef BYO_CRYPTO AWS_STATIC_STRING_FROM_LITERAL(s_proxy_no_verify_peer_env_var, "AWS_PROXY_NO_VERIFY_PEER"); @@ -1140,23 +1140,6 @@ static enum aws_http_proxy_connection_type s_determine_proxy_connection_type( } } -static struct aws_string *s_get_proxy_environment_value( - struct aws_allocator *allocator, - const struct aws_string *env_name) { - struct aws_string *out_string = NULL; - if (aws_get_environment_value(allocator, env_name, &out_string) == AWS_OP_SUCCESS && out_string != NULL && - out_string->len > 0) { - AWS_LOGF_DEBUG( - AWS_LS_HTTP_CONNECTION, - "%s environment found, %s", - aws_string_c_str(env_name), - aws_string_c_str(out_string)); - return out_string; - } - aws_string_destroy(out_string); - return NULL; -} - static int s_proxy_uri_init_from_env_variable( struct aws_allocator *allocator, const struct aws_http_client_connection_options *options, @@ -1164,18 +1147,29 @@ static int s_proxy_uri_init_from_env_variable( bool *found) { struct aws_string *proxy_uri_string = NULL; *found = false; + + /* First check if this host should bypass proxy using NO_PROXY */ + struct aws_byte_cursor host_cursor = options->host_name; + if (aws_check_no_proxy(allocator, host_cursor)) { + AWS_LOGF_DEBUG( + AWS_LS_HTTP_CONNECTION, + "Host \"" PRInSTR "\" found in NO_PROXY, bypassing proxy", + AWS_BYTE_CURSOR_PRI(host_cursor)); + return AWS_OP_SUCCESS; + } + if (options->tls_options) { - proxy_uri_string = s_get_proxy_environment_value(allocator, s_https_proxy_env_var_low); + proxy_uri_string = aws_get_env_nonempty(allocator, s_https_proxy_env_var_low); if (proxy_uri_string == NULL) { - proxy_uri_string = s_get_proxy_environment_value(allocator, s_https_proxy_env_var); + proxy_uri_string = aws_get_env_nonempty(allocator, s_https_proxy_env_var); } if (proxy_uri_string == NULL) { return AWS_OP_SUCCESS; } } else { - proxy_uri_string = s_get_proxy_environment_value(allocator, s_http_proxy_env_var_low); + proxy_uri_string = aws_get_env_nonempty(allocator, s_http_proxy_env_var_low); if (proxy_uri_string == NULL) { - proxy_uri_string = s_get_proxy_environment_value(allocator, s_http_proxy_env_var); + proxy_uri_string = aws_get_env_nonempty(allocator, s_http_proxy_env_var); } if (proxy_uri_string == NULL) { return AWS_OP_SUCCESS; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 01160d28c..80bb3821f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -636,6 +636,20 @@ add_test_case(http_proxy_adaptive_ntlm_success) add_test_case(http_proxy_adaptive_failure) add_test_case(http_forwarding_proxy_uri_rewrite) add_test_case(http_forwarding_proxy_uri_rewrite_options_star) + +# NO_PROXY tests +add_test_case(test_no_proxy_subdomain_matching) +add_test_case(test_no_proxy_wildcard_patterns) +add_test_case(test_no_proxy_case_insensitivity) +add_test_case(test_no_proxy_multiple_patterns) +add_test_case(test_no_proxy_ipv6_address) +add_test_case(test_no_proxy_whitespace_handling) +add_test_case(test_no_proxy_cidr_notation) +add_test_case(test_no_proxy_ipv6_cidr_notation) +add_test_case(test_no_proxy_invalid_patterns) +add_test_case(test_no_proxy_invalid_host_inputs) +add_test_case(test_no_proxy_environment_unset) + add_test_case(http_tunnel_proxy_connection_success) add_test_case(https_tunnel_proxy_connection_success) add_test_case(http_tunnel_proxy_connection_failure_connect) diff --git a/tests/test_no_proxy.c b/tests/test_no_proxy.c new file mode 100644 index 000000000..c9fd2a8dd --- /dev/null +++ b/tests/test_no_proxy.c @@ -0,0 +1,484 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#include +#include + +#include +#include +#include + +#include +#include +#include + +AWS_STATIC_STRING_FROM_LITERAL(s_http_proxy_env_var, "http_proxy"); +AWS_STATIC_STRING_FROM_LITERAL(s_no_proxy_env_var, "no_proxy"); + +static int s_init_no_proxy_test(struct aws_allocator *allocator, const char *no_proxy_value_str) { + + aws_http_library_init(allocator); + + /* Set up test variables */ + struct aws_string *proxy_value = aws_string_new_from_c_str(allocator, "http://proxy.example.org:8888"); + struct aws_string *no_proxy_value = aws_string_new_from_c_str(allocator, no_proxy_value_str); + + /* Set environment variables for testing */ + ASSERT_SUCCESS(aws_set_environment_value(s_http_proxy_env_var, proxy_value)); + ASSERT_SUCCESS(aws_set_environment_value(s_no_proxy_env_var, no_proxy_value)); + + aws_string_destroy(proxy_value); + aws_string_destroy(no_proxy_value); + return AWS_OP_SUCCESS; +} + +static int s_cleanup_no_proxy_test(void) { + ASSERT_SUCCESS(aws_unset_environment_value(s_http_proxy_env_var)); + ASSERT_SUCCESS(aws_unset_environment_value(s_no_proxy_env_var)); + aws_http_library_clean_up(); + return AWS_OP_SUCCESS; +} + +static int s_test_no_proxy_helper(struct aws_allocator *allocator, const char *host, bool expected_bypass) { + /* Call the function that checks NO_PROXY */ + ASSERT_UINT_EQUALS(aws_check_no_proxy(allocator, aws_byte_cursor_from_c_str(host)), expected_bypass); + return AWS_OP_SUCCESS; +} + +/** + * Test subdomain matching with NO_PROXY + */ +static int s_test_no_proxy_subdomain_matching(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + const char *no_proxy_value = ".example.com"; + + /* Initialize test with NO_PROXY value */ + ASSERT_SUCCESS(s_init_no_proxy_test(allocator, no_proxy_value)); + + /* Test that a subdomain matches when NO_PROXY contains a domain with leading dot */ + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "sub.example.com", true)); + /* cannot match */ + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "sub.subexample.com", false)); + + /* Clean up the test environment */ + return s_cleanup_no_proxy_test(); +} +AWS_TEST_CASE(test_no_proxy_subdomain_matching, s_test_no_proxy_subdomain_matching); + +/** + * Test wildcard patterns in NO_PROXY + */ +static int s_test_no_proxy_wildcard_patterns(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + const char *no_proxy_value = "*"; + + /* Initialize test with NO_PROXY value */ + ASSERT_SUCCESS(s_init_no_proxy_test(allocator, no_proxy_value)); + + /* Test that a wildcard pattern matches all hosts */ + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "any.example.com", true)); + + /* Clean up the test environment */ + return s_cleanup_no_proxy_test(); +} +AWS_TEST_CASE(test_no_proxy_wildcard_patterns, s_test_no_proxy_wildcard_patterns); + +/** + * Test case insensitivity in NO_PROXY + */ +static int s_test_no_proxy_case_insensitivity(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + const char *no_proxy_value = "example.COM"; + + /* Initialize test with NO_PROXY value */ + ASSERT_SUCCESS(s_init_no_proxy_test(allocator, no_proxy_value)); + + /* Test that case insensitive matching works for both host and NO_PROXY entries */ + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "EXAMPLE.com", true)); + + /* Clean up the test environment */ + return s_cleanup_no_proxy_test(); +} +AWS_TEST_CASE(test_no_proxy_case_insensitivity, s_test_no_proxy_case_insensitivity); + +/** + * Test IPv6 addresses + */ +static int s_test_no_proxy_ipv6_address(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + /* Pattern don't allow `[]`, just follows what curl does. */ + const char *no_proxy_value = "2001:db8::1, ::1, [2001:db8::2]"; + + /* Initialize test with NO_PROXY value */ + ASSERT_SUCCESS(s_init_no_proxy_test(allocator, no_proxy_value)); + + /* Test that an IPv6 address in brackets matches */ + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "[2001:db8::1]", true)); + + /* Test another IPv6 address format (localhost) */ + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "::1", true)); + + /* Test a non-matching IPv6 address */ + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "[2001:db8::2]", false)); + + /* Clean up the test environment */ + return s_cleanup_no_proxy_test(); +} +AWS_TEST_CASE(test_no_proxy_ipv6_address, s_test_no_proxy_ipv6_address); + +/** + * Test multiple patterns in NO_PROXY + */ +static int s_test_no_proxy_multiple_patterns(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + /* Only wildcard support is a single `*`, if it's in the list, it will be ignored. */ + const char *no_proxy_value = "foo.bar,example.com,other.net,*"; + + /* Initialize test with NO_PROXY value */ + ASSERT_SUCCESS(s_init_no_proxy_test(allocator, no_proxy_value)); + + /* Test that a host matches when it's in the middle of a comma-separated list */ + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "example.com", true)); + + /* Test that another host in the list also matches */ + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "foo.bar", true)); + + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "foo.", false)); + + /* Clean up the test environment */ + return s_cleanup_no_proxy_test(); +} +AWS_TEST_CASE(test_no_proxy_multiple_patterns, s_test_no_proxy_multiple_patterns); + +/** + * Test whitespace handling in NO_PROXY + */ +static int s_test_no_proxy_whitespace_handling(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + const char *no_proxy_value = " example.com , foo.bar "; + + /* Initialize test with NO_PROXY value */ + ASSERT_SUCCESS(s_init_no_proxy_test(allocator, no_proxy_value)); + + /* Test that whitespace is properly handled in NO_PROXY entries */ + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "example.com", true)); + + /* Test that another host with whitespace also matches */ + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "foo.bar", true)); + + /* Clean up the test environment */ + return s_cleanup_no_proxy_test(); +} +AWS_TEST_CASE(test_no_proxy_whitespace_handling, s_test_no_proxy_whitespace_handling); + +/** + * Test IP addresses in NO_PROXY + */ +static int s_test_no_proxy_ip_address(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + const char *no_proxy_value = "192.168.1.1,10.0.0.0"; + + /* Initialize test with NO_PROXY value */ + ASSERT_SUCCESS(s_init_no_proxy_test(allocator, no_proxy_value)); + + /* Test that IP address matching works */ + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "192.168.1.1", true)); + + /* Test that a different IP doesn't match */ + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "192.168.2.1", false)); + + /* Clean up the test environment */ + return s_cleanup_no_proxy_test(); +} +AWS_TEST_CASE(test_no_proxy_ip_address, s_test_no_proxy_ip_address); + +/** + * Test port-specific exclusions in NO_PROXY + * + * NOTE: This tests a curl-specific feature where entries like "example.com:8080" + * can be used to bypass the proxy only for specific ports. The current implementation + * doesn't support this feature, so this test documents that behavior. + */ +static int s_test_no_proxy_port_specific(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + const char *no_proxy_value = "example.com:8080"; + + /* Initialize test with NO_PROXY value */ + ASSERT_SUCCESS(s_init_no_proxy_test(allocator, no_proxy_value)); + + /* Our implementation only does hostname matching and ignores port information. + * In curl, this would bypass only on port 8080, but in our implementation it + * bypasses for all ports. */ + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "example.com", true)); + + /* Clean up the test environment */ + return s_cleanup_no_proxy_test(); +} +AWS_TEST_CASE(test_no_proxy_port_specific, s_test_no_proxy_port_specific); + +/** + * Test CIDR notation in NO_PROXY + * + * Tests the CIDR notation support (similar to curl 7.86.0) where "192.168.0.0/16" + * would match all addresses starting with "192.168". + */ +static int s_test_no_proxy_cidr_notation(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + const char *no_proxy_value_16 = "192.168.0.0/16"; + + /* Initialize test with first NO_PROXY value */ + ASSERT_SUCCESS(s_init_no_proxy_test(allocator, no_proxy_value_16)); + + /* Test that an IP address in a CIDR range matches */ + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "192.168.5.10", true)); + + /* Test that an IP address outside the CIDR range doesn't match */ + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "10.0.5.10", false)); + + /* Clean up first test */ + ASSERT_SUCCESS(s_cleanup_no_proxy_test()); + + /* Test with a more specific subnet mask */ + const char *no_proxy_value_24 = "192.168.5.0/24"; + + /* Initialize test with second NO_PROXY value */ + ASSERT_SUCCESS(s_init_no_proxy_test(allocator, no_proxy_value_24)); + + /* Test that an IP address in a more specific CIDR range matches */ + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "192.168.5.10", true)); + + /* Test that an IP address outside the specific CIDR range doesn't match */ + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "192.168.6.10", false)); + + /* Clean up second test */ + return s_cleanup_no_proxy_test(); +} +AWS_TEST_CASE(test_no_proxy_cidr_notation, s_test_no_proxy_cidr_notation); + +/** + * Test IPv6 CIDR notation in NO_PROXY + * + * Tests the CIDR notation support for IPv6 addresses where "2001:db8::/32" + * would match all addresses starting with "2001:db8". + */ +static int s_test_no_proxy_ipv6_cidr_notation(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + const char *no_proxy_value_32 = "2001:db8::/32"; + + /* Initialize test with first NO_PROXY value */ + ASSERT_SUCCESS(s_init_no_proxy_test(allocator, no_proxy_value_32)); + + /* Test that an IPv6 address in a CIDR range matches */ + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "[2001:db8:1:2::3]", true)); + + /* Test that an IPv6 address outside the CIDR range doesn't match */ + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "[2001:db9:1:2::3]", false)); + + /* Clean up first test */ + ASSERT_SUCCESS(s_cleanup_no_proxy_test()); + + /* Test with a more specific prefix length */ + const char *no_proxy_value_64 = "2001:db8:1:2::/64"; + + /* Initialize test with second NO_PROXY value */ + ASSERT_SUCCESS(s_init_no_proxy_test(allocator, no_proxy_value_64)); + + /* Test that an IPv6 address in a more specific CIDR range matches */ + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "[2001:db8:1:2::3]", true)); + + /* Test that an IPv6 address outside the specific CIDR range doesn't match */ + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "[2001:db8:1:3::3]", false)); + + /* Clean up second test */ + return s_cleanup_no_proxy_test(); +} +AWS_TEST_CASE(test_no_proxy_ipv6_cidr_notation, s_test_no_proxy_ipv6_cidr_notation); + +/** + * Test invalid IP addresses and CIDR blocks in NO_PROXY + * + * Verifies that the NO_PROXY implementation safely handles and ignores invalid + * IP addresses and CIDR blocks without crashing. + */ +static int s_test_no_proxy_invalid_patterns(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + /* Test with invalid IP addresses and CIDR notations mixed with valid entries */ + const char *no_proxy_value = "example.com,999.999.999.999,192.168.1.3/33,192.168.b.c," + "2001:xyz::bad:ipv6,2001:db8::/129,not:a:valid:ip/64," + "[malformed],192.168.1.2," + "192.168.1.1/99999999999999999," /* Invalid network bits */ + "2001:db8::/999999"; /* Invalid IPv6 prefix */ + + /* Initialize test with NO_PROXY value containing invalid patterns */ + ASSERT_SUCCESS(s_init_no_proxy_test(allocator, no_proxy_value)); + + /* Test that invalid IP addresses and CIDR blocks are safely ignored */ + /* The last valid entry (192.168.1.2) should still match */ + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "192.168.1.2", true)); + + /* Test that the valid hostname entry still works */ + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "example.com", true)); + + /* Test with an invalid host address parameter */ + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "192.168.1.1", false)); + + /* An invalid IP address will be treated as regular hostname and match as regular hostname. */ + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "999.999.999.999", true)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "999.999.999.999.999", true)); + + /* Clean up the test environment */ + ASSERT_SUCCESS(s_cleanup_no_proxy_test()); + + /* Test with only invalid entries */ + const char *invalid_only = "999.999.999.999,192.168.1.1/33,not:an:ip:addr,2001:xyz::bad"; + + /* Initialize test with NO_PROXY value containing only invalid patterns */ + ASSERT_SUCCESS(s_init_no_proxy_test(allocator, invalid_only)); + + /* Test that a valid IP doesn't match when NO_PROXY contains only invalid entries */ + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "192.168.1.1", false)); + + /* Test with empty host parameter */ + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "", false)); + + /* Test with a very malformed CIDR input that could cause parsing issues */ + const char *malformed_cidr = "192.168.1.1/abcdef,2001:db8::/xyz"; + + /* Initialize test with NO_PROXY value containing malformed CIDR notation */ + ASSERT_SUCCESS(s_init_no_proxy_test(allocator, malformed_cidr)); + /* Malformed CIDR will be taken as the entr */ + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "2001:db8::", true)); + + /* Clean up the test environment */ + ASSERT_SUCCESS(s_cleanup_no_proxy_test()); + + /* Test with very large CIDR parts that would be rejected in the buffer size check */ + char large_value[200]; + memset(large_value, 'x', sizeof(large_value) - 1); + large_value[sizeof(large_value) - 1] = '\0'; + + char large_cidr[256]; + snprintf(large_cidr, sizeof(large_cidr), "192.168.1.1/%s", large_value); + + /* Initialize test with NO_PROXY value containing oversized CIDR notation */ + ASSERT_SUCCESS(s_init_no_proxy_test(allocator, large_cidr)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "192.168.1.1", false)); + + /* Clean up the test environment */ + ASSERT_SUCCESS(s_cleanup_no_proxy_test()); + + /* Clean up and return */ + return s_cleanup_no_proxy_test(); +} +AWS_TEST_CASE(test_no_proxy_invalid_patterns, s_test_no_proxy_invalid_patterns); + +/** + * Test invalid host inputs to aws_check_no_proxy + * + * Verifies that the aws_check_no_proxy function handles malformed host inputs + * gracefully without crashing. These tests specifically check the host parameter + * rather than the NO_PROXY environment variable content. + */ +static int s_test_no_proxy_invalid_host_inputs(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + /* Set up a valid NO_PROXY value for testing */ + const char *no_proxy_value = "example.com,192.168.1.0/24,2001:db8::/32"; + + /* Initialize test with valid NO_PROXY value */ + ASSERT_SUCCESS(s_init_no_proxy_test(allocator, no_proxy_value)); + + /* Test with invalid IPv4 address */ + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "999.999.999.999", false)); + + /* Test with malformed IPv4 address formats */ + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "192.168.1", false)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "192.168..1", false)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "192.168.1.", false)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, ".192.168.1", false)); + + /* Test with invalid IPv6 address variants */ + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "[2001:db8::xyz]", false)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "[2001:db8::]:", false)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "2001:db8:::", false)); + + /* Test with malformed IPv6 address brackets */ + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "[2001:db8::1", false)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "2001:db8::1]", false)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "[[2001:db8::1]]", false)); + + /* Test with empty host */ + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "", false)); + + /* Test with extremely long host that exceeds buffer sizes */ + char long_host[1024]; + memset(long_host, 'a', sizeof(long_host) - 1); + long_host[sizeof(long_host) - 1] = '\0'; + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, long_host, false)); + + /* Test with extremely long IPv4 address that would hit buffer checks */ + char long_ipv4[150] = "192.168.1.1"; + for (int i = 0; i < 130; i++) { + long_ipv4[11 + i] = '9'; /* Padding with extra digits */ + } + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, long_ipv4, false)); + + /* Test with extremely long IPv6 address that would hit buffer checks */ + char long_ipv6[150] = "[2001:db8::1"; + for (int i = 0; i < 130; i++) { + long_ipv6[11 + i] = '1'; /* Padding with extra digits */ + } + long_ipv6[141] = ']'; + long_ipv6[142] = '\0'; + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, long_ipv6, false)); + + /* Clean up the test environment */ + return s_cleanup_no_proxy_test(); +} +AWS_TEST_CASE(test_no_proxy_invalid_host_inputs, s_test_no_proxy_invalid_host_inputs); + +/** + * Test behavior when NO_PROXY environment variable is unset or empty + * + * Verifies that the aws_check_no_proxy function correctly handles cases where + * the NO_PROXY environment variable is not set or empty. + */ +static int s_test_no_proxy_environment_unset(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_http_library_init(allocator); + + /* Test with NO_PROXY unset (only http_proxy set) */ + struct aws_string *proxy_value = aws_string_new_from_c_str(allocator, "http://proxy.example.org:8888"); + ASSERT_SUCCESS(aws_set_environment_value(s_http_proxy_env_var, proxy_value)); + + /* Make sure NO_PROXY environment variables are unset */ + ASSERT_SUCCESS(aws_unset_environment_value(s_no_proxy_env_var)); + struct aws_string *uppercase_no_proxy = aws_string_new_from_c_str(allocator, "NO_PROXY"); + ASSERT_SUCCESS(aws_unset_environment_value(uppercase_no_proxy)); /* Upper case version */ + aws_string_destroy(uppercase_no_proxy); + + /* With NO_PROXY unset, aws_check_no_proxy should return false for any host */ + ASSERT_FALSE(aws_check_no_proxy(allocator, aws_byte_cursor_from_c_str("example.com"))); + ASSERT_FALSE(aws_check_no_proxy(allocator, aws_byte_cursor_from_c_str("192.168.1.1"))); + ASSERT_FALSE(aws_check_no_proxy(allocator, aws_byte_cursor_from_c_str("[2001:db8::1]"))); + + /* Now test with empty NO_PROXY */ + struct aws_string *empty_no_proxy = aws_string_new_from_c_str(allocator, ""); + ASSERT_SUCCESS(aws_set_environment_value(s_no_proxy_env_var, empty_no_proxy)); + + /* With empty NO_PROXY, aws_check_no_proxy should still return false */ + ASSERT_FALSE(aws_check_no_proxy(allocator, aws_byte_cursor_from_c_str("example.com"))); + + /* Clean up */ + ASSERT_SUCCESS(aws_unset_environment_value(s_http_proxy_env_var)); + ASSERT_SUCCESS(aws_unset_environment_value(s_no_proxy_env_var)); + aws_string_destroy(proxy_value); + aws_string_destroy(empty_no_proxy); + aws_http_library_clean_up(); + return AWS_OP_SUCCESS; +} +AWS_TEST_CASE(test_no_proxy_environment_unset, s_test_no_proxy_environment_unset); From d49343d4ce63f36385f722d4c16763f4ee5a8e31 Mon Sep 17 00:00:00 2001 From: Dengke Tang Date: Wed, 25 Jun 2025 10:42:37 -0700 Subject: [PATCH 2/8] add the exact version number and link the doc --- include/aws/http/private/no_proxy.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/aws/http/private/no_proxy.h b/include/aws/http/private/no_proxy.h index 7ee30d7a4..7145eb0f0 100644 --- a/include/aws/http/private/no_proxy.h +++ b/include/aws/http/private/no_proxy.h @@ -11,7 +11,8 @@ AWS_EXTERN_C_BEGIN /* * Check if a host should bypass the proxy based on the NO_PROXY environment variable. - * Since NO_PROXY has no standard yet. Follows the curl implementation from noproxy.c. + * Since NO_PROXY has no standard yet. Follows the curl implementation from noproxy.c at 8.14.1. + * https://github.com/curl/curl/blob/curl-8_14_1/lib/noproxy.c * * NO_PROXY is a comma-separated list of domain names, hostnames, or IP addresses that * should bypass the proxy. From 6c6091a1f3369f9462830e330fe0bdfacbf405fe Mon Sep 17 00:00:00 2001 From: Dengke Tang Date: Wed, 25 Jun 2025 11:16:23 -0700 Subject: [PATCH 3/8] add documentation --- include/aws/http/proxy.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/aws/http/proxy.h b/include/aws/http/proxy.h index 4e23d30db..d5855cb70 100644 --- a/include/aws/http/proxy.h +++ b/include/aws/http/proxy.h @@ -43,6 +43,8 @@ enum aws_http_proxy_env_var_type { * Enable get proxy URL from environment variable, when the manual proxy options of connection manager is not set. * env HTTPS_PROXY/https_proxy will be checked when the main connection use tls. * env HTTP_PROXY/http_proxy will be checked when the main connection NOT use tls. + * env NO_PROXY/no_proxy will be checked to bypass proxy if the host match the pattern. + * Check `aws_check_no_proxy` for detail. * The lower case version has precedence. */ AWS_HPEV_ENABLE, From fe1ce7a875a76a8b2eb177d504cfa0466e97627a Mon Sep 17 00:00:00 2001 From: Dengke Tang Date: Thu, 10 Jul 2025 12:08:19 -0700 Subject: [PATCH 4/8] address comments --- include/aws/http/private/no_proxy.h | 10 +- include/aws/http/proxy.h | 4 +- source/no_proxy.c | 183 +++++---------- source/proxy_connection.c | 25 +- tests/CMakeLists.txt | 1 - tests/test_no_proxy.c | 340 ++++++++++++---------------- 6 files changed, 235 insertions(+), 328 deletions(-) diff --git a/include/aws/http/private/no_proxy.h b/include/aws/http/private/no_proxy.h index 7145eb0f0..1daf6efb8 100644 --- a/include/aws/http/private/no_proxy.h +++ b/include/aws/http/private/no_proxy.h @@ -10,16 +10,22 @@ AWS_PUSH_SANE_WARNING_LEVEL AWS_EXTERN_C_BEGIN /* - * Check if a host should bypass the proxy based on the NO_PROXY environment variable. + * Check if a host should bypass the proxy based on the NO_PROXY environment variable or provided no_proxy value. * Since NO_PROXY has no standard yet. Follows the curl implementation from noproxy.c at 8.14.1. * https://github.com/curl/curl/blob/curl-8_14_1/lib/noproxy.c * * NO_PROXY is a comma-separated list of domain names, hostnames, or IP addresses that * should bypass the proxy. * + * If no_proxy is NULL, the function will read the NO_PROXY environment variable. + * If no_proxy is provided, it will be used instead of the environment variable. + * * Returns true if the host should bypass the proxy. */ -AWS_HTTP_API bool aws_check_no_proxy(struct aws_allocator *allocator, struct aws_byte_cursor host); +AWS_HTTP_API bool aws_http_host_matches_no_proxy( + struct aws_allocator *allocator, + struct aws_byte_cursor host, + struct aws_string *no_proxy_str); AWS_EXTERN_C_END AWS_POP_SANE_WARNING_LEVEL diff --git a/include/aws/http/proxy.h b/include/aws/http/proxy.h index d5855cb70..621018e34 100644 --- a/include/aws/http/proxy.h +++ b/include/aws/http/proxy.h @@ -44,8 +44,8 @@ enum aws_http_proxy_env_var_type { * env HTTPS_PROXY/https_proxy will be checked when the main connection use tls. * env HTTP_PROXY/http_proxy will be checked when the main connection NOT use tls. * env NO_PROXY/no_proxy will be checked to bypass proxy if the host match the pattern. - * Check `aws_check_no_proxy` for detail. - * The lower case version has precedence. + * Check `aws_http_host_matches_no_proxy` for detail. This function can also be used with a direct no_proxy + * parameter. The lower case version has precedence. */ AWS_HPEV_ENABLE, }; diff --git a/source/no_proxy.c b/source/no_proxy.c index bd28ee9e8..f37adf69f 100644 --- a/source/no_proxy.c +++ b/source/no_proxy.c @@ -11,9 +11,6 @@ # include #endif -static const char *s_no_proxy_env_var = "NO_PROXY"; -static const char *s_no_proxy_env_var_low = "no_proxy"; - enum hostname_type { HOSTNAME_TYPE_IPV4, HOSTNAME_TYPE_IPV6, @@ -21,63 +18,40 @@ enum hostname_type { }; /** - * Determines whether a host string is an IPv4 address and stores the binary representation. - * Checks if the string follows IPv4 format - * - * @param host The host string to check - * @param addr_out Optional pointer to store the parsed binary address. Must be at least 4 bytes. - * @return true if the host is an IPv4 address, false otherwise - */ -static bool s_is_ipv4_address(const struct aws_byte_cursor *host, void *addr_out) { - if (!host || host->len == 0 || !addr_out) { - return false; - } - - char ip_buffer[128] = {'\0'}; - if (host->len >= sizeof(ip_buffer)) { - /* Too long to be valid IPv4 */ - return false; - } - - memcpy(ip_buffer, host->ptr, host->len); - ip_buffer[host->len] = '\0'; /* Null-terminate for inet_pton */ - - int result = inet_pton(AF_INET, ip_buffer, addr_out); - return result == 1; -} - -/** - * Determines whether a host string is an IPv6 address and stores the binary representation. - * If it contains `[]`, remove them + * Prase a host string as an IPv6 address and stores the binary representation. + * Return False is the host is not a valid IPv6 address. + * If host contains `[]`, remove them * * @param host The host string to check * @param addr_out Optional pointer to store the parsed binary address. Must be at least 16 bytes. * @return true if the host is an IPv6 address, false otherwise */ -static bool s_is_ipv6_address_and_update_host(struct aws_byte_cursor *host, void *addr_out) { - if (!host || host->len < 2 || !addr_out) { +static bool s_parse_ipv6_address_and_update_host( + struct aws_allocator *allocator, + struct aws_string *host_str, + void *addr_out) { + if (!host_str || host_str->len < 2 || !addr_out) { return false; } - - /* Check if the address is enclosed in brackets and strip them for validation */ - if (host->ptr[0] == '[' && host->ptr[host->len - 1] == ']') { - aws_byte_cursor_advance(host, 1); - host->len--; + struct aws_string *host_str_copy = host_str; + struct aws_byte_cursor host = aws_byte_cursor_from_string(host_str); + if (host.ptr[0] == '[' && host.ptr[host.len - 1] == ']') { + /* Check if the address is enclosed in brackets and strip them for validation */ + aws_byte_cursor_advance(&host, 1); + host.len--; + host_str_copy = aws_string_new_from_cursor(allocator, &host); } - char ip_buffer[128] = {'\0'}; - if (host->len >= sizeof(ip_buffer)) { - /* Too long to be valid IPv6 */ - return false; + if (inet_pton(AF_INET6, aws_string_c_str(host_str_copy), addr_out) == 1) { + /* Update the host str */ + if (host_str != host_str_copy) { + aws_string_destroy(host_str); + host_str = host_str_copy; + } + return true; } - - memcpy(ip_buffer, host->ptr, host->len); - ip_buffer[host->len] = '\0'; /* Null-terminate for inet_pton */ - - int result = inet_pton(AF_INET6, ip_buffer, addr_out); - return result == 1; + return false; } - /** * s_cidr4_match() returns true if the given IPv4 address is within the * specified CIDR address range. @@ -88,9 +62,8 @@ static bool s_is_ipv6_address_and_update_host(struct aws_byte_cursor *host, void * @param host_addr Pre-parsed binary representation of the host IP, or NULL to parse from host * @return true if the IP address matches the CIDR pattern, false otherwise */ -static bool s_cidr4_match(uint16_t bits, struct aws_byte_cursor network_part, const void *host_addr) { +static bool s_cidr4_match(uint16_t bits, struct aws_string *network_part, uint32_t address) { - uint32_t address = 0; uint32_t check = 0; /* Check for valid bits parameter */ @@ -99,25 +72,8 @@ static bool s_cidr4_match(uint16_t bits, struct aws_byte_cursor network_part, co return false; } - /* Parse the host address if not provided */ - AWS_ASSERT(host_addr != NULL); - /* Use the pre-parsed host address */ - memcpy(&address, host_addr, sizeof(address)); - - /* Parse the network pattern */ - char pattern_buffer[128] = {'\0'}; - - /* Check buffer size */ - if (network_part.len >= sizeof(pattern_buffer)) { - /* Too long to be valid IPv4 */ - return false; - } - - memcpy(pattern_buffer, network_part.ptr, network_part.len); - pattern_buffer[network_part.len] = '\0'; - /* Convert network pattern to binary */ - if (inet_pton(AF_INET, pattern_buffer, &check) != 1) { + if (inet_pton(AF_INET, aws_string_c_str(network_part), &check) != 1) { return false; } @@ -145,10 +101,8 @@ static bool s_cidr4_match(uint16_t bits, struct aws_byte_cursor network_part, co * @param host_addr Pre-parsed binary representation of the host IP, or NULL to parse from host * @return true if the IP address matches the CIDR pattern, false otherwise */ -static bool s_cidr6_match(uint16_t bits, struct aws_byte_cursor network_part, const void *host_addr) { - - unsigned char address[16] = {0}; - unsigned char check[16] = {0}; +static bool s_cidr6_match(uint16_t bits, struct aws_string *network_part, uint8_t *address) { + uint8_t check[16] = {0}; /* If no bits specified, use full 128 bits for IPv6 */ if (!bits) { @@ -159,25 +113,8 @@ static bool s_cidr6_match(uint16_t bits, struct aws_byte_cursor network_part, co if (bits > 128) { return false; } - - AWS_ASSERT(host_addr != NULL); - /* Copy pre-parsed host address if provided */ - memcpy(address, host_addr, sizeof(address)); - - /* Parse the network pattern */ - char pattern_buffer[128] = {'\0'}; - - /* Check buffer size */ - if (network_part.len >= sizeof(pattern_buffer)) { - /* Too long to be valid IPv6 */ - return false; - } - - memcpy(pattern_buffer, network_part.ptr, network_part.len); - pattern_buffer[network_part.len] = '\0'; - /* Convert network pattern to binary */ - if (inet_pton(AF_INET6, pattern_buffer, check) != 1) { + if (inet_pton(AF_INET6, aws_string_c_str(network_part), check) != 1) { return false; } @@ -209,31 +146,24 @@ static bool s_is_dot(uint8_t c) { return c == '.'; } -/* The host is expected to be the result from */ -bool aws_check_no_proxy(struct aws_allocator *allocator, struct aws_byte_cursor host) { - if (host.len == 0) { - return false; - } - - struct aws_string *no_proxy_str = aws_get_env_nonempty(allocator, s_no_proxy_env_var_low); - if (no_proxy_str == NULL) { - no_proxy_str = aws_get_env_nonempty(allocator, s_no_proxy_env_var); - } - - if (no_proxy_str == NULL) { - aws_string_destroy(no_proxy_str); +/* The host is expected to be the host result from URL parser. */ +bool aws_http_host_matches_no_proxy( + struct aws_allocator *allocator, + struct aws_byte_cursor host, + struct aws_string *no_proxy_str) { + if (host.len == 0 || no_proxy_str == NULL) { return false; } - /* Single "*" wildcard matches all hosts */ if (aws_string_eq_c_str(no_proxy_str, "*")) { AWS_LOGF_DEBUG(AWS_LS_HTTP_CONNECTION, "wildcard no_proxy found, bypassing any proxy"); - aws_string_destroy(no_proxy_str); return true; } bool bypass = false; struct aws_byte_cursor no_proxy_cur = aws_byte_cursor_from_string(no_proxy_str); struct aws_array_list no_proxy_list; + struct aws_string *host_str = aws_string_new_from_cursor(allocator, &host); + if (aws_array_list_init_dynamic(&no_proxy_list, allocator, 10, sizeof(struct aws_byte_cursor))) { goto cleanup; } @@ -243,22 +173,17 @@ bool aws_check_no_proxy(struct aws_allocator *allocator, struct aws_byte_cursor } /* Store parsed binary addresses for reuse */ - unsigned char ipv4_addr[4] = {0}; - unsigned char ipv6_addr[16] = {0}; - void *host_addr_ptr = NULL; + uint32_t ipv4_addr = 0; + uint8_t ipv6_addr[16] = {0}; /* Determine host type and parse address if applicable */ enum hostname_type type = HOSTNAME_TYPE_REGULAR; - if (s_is_ipv4_address(&host, ipv4_addr)) { + if (inet_pton(AF_INET, aws_string_c_str(host_str), &ipv4_addr) == 1) { type = HOSTNAME_TYPE_IPV4; - host_addr_ptr = ipv4_addr; } else { - struct aws_byte_cursor host_copy = host; - if (s_is_ipv6_address_and_update_host(&host_copy, ipv6_addr)) { + if (s_parse_ipv6_address_and_update_host(allocator, host_str, ipv6_addr)) { + /* the host is valid IPv6 */ type = HOSTNAME_TYPE_IPV6; - host_addr_ptr = ipv6_addr; - /* Update the host */ - host = host_copy; } else { /* Not an IP address, so it's a regular hostname */ type = HOSTNAME_TYPE_REGULAR; @@ -267,7 +192,6 @@ bool aws_check_no_proxy(struct aws_allocator *allocator, struct aws_byte_cursor } } - char bits_buffer[8] = {'\0'}; for (size_t i = 0; i < aws_array_list_length(&no_proxy_list); i++) { struct aws_byte_cursor pattern; if (aws_array_list_get_at(&no_proxy_list, &pattern, i)) { @@ -301,7 +225,8 @@ bool aws_check_no_proxy(struct aws_allocator *allocator, struct aws_byte_cursor continue; } } else if (pattern.len < host.len) { - /* Check if the pattern is a suffix of the host. All the math is safe since pattern.len < host.len + /* Check if the pattern is a suffix of the host. All the math is safe since pattern.len < + * host.len */ struct aws_byte_cursor tail_with_extra_byte = host; /* 1. the byte before the tail should be `.` */ @@ -327,31 +252,33 @@ bool aws_check_no_proxy(struct aws_allocator *allocator, struct aws_byte_cursor struct aws_byte_cursor substr = {0}; struct aws_byte_cursor network_part = {0}; /* CIDR found. parse the bits */ - uint16_t network_bits = 0; + uint64_t network_bits = 0; if (aws_byte_cursor_next_split(&pattern, '/', &substr)) { network_part = substr; } if (aws_byte_cursor_next_split(&pattern, '/', &substr)) { - /* This substr will be number of bits. */ - if (substr.len > 8) { - /* Invalid, ignore it. */ + /* There is a second part of the pattern after `/`. */ + /* Now, take the rest of the pattern after `/` as the bits */ + aws_byte_cursor_advance(&pattern, network_part.len + 1); + if (aws_byte_cursor_utf8_parse_u64(pattern, &network_bits)) { continue; } - memcpy(bits_buffer, substr.ptr, substr.len); - bits_buffer[substr.len] = '\0'; /* Null-terminate for atoi */ - network_bits = (uint16_t)atoi(bits_buffer); } + struct aws_string *network_part_str = aws_string_new_from_cursor(allocator, &network_part); if (type == HOSTNAME_TYPE_IPV4) { - if (s_cidr4_match(network_bits, network_part, host_addr_ptr)) { + if (s_cidr4_match(network_bits, network_part_str, ipv4_addr)) { bypass = true; + aws_string_destroy(network_part_str); goto cleanup; } } else { - if (s_cidr6_match(network_bits, network_part, host_addr_ptr)) { + if (s_cidr6_match(network_bits, network_part_str, ipv6_addr)) { bypass = true; + aws_string_destroy(network_part_str); goto cleanup; } } + aws_string_destroy(network_part_str); } break; default: @@ -362,7 +289,7 @@ bool aws_check_no_proxy(struct aws_allocator *allocator, struct aws_byte_cursor } cleanup: + aws_string_destroy(host_str); aws_array_list_clean_up(&no_proxy_list); - aws_string_destroy(no_proxy_str); return bypass; } diff --git a/source/proxy_connection.c b/source/proxy_connection.c index 57beea177..bb96705c6 100644 --- a/source/proxy_connection.c +++ b/source/proxy_connection.c @@ -33,6 +33,8 @@ static const char *s_http_proxy_env_var = "HTTP_PROXY"; static const char *s_http_proxy_env_var_low = "http_proxy"; static const char *s_https_proxy_env_var = "HTTPS_PROXY"; static const char *s_https_proxy_env_var_low = "https_proxy"; +static const char *s_no_proxy_env_var = "NO_PROXY"; +static const char *s_no_proxy_env_var_low = "no_proxy"; #ifndef BYO_CRYPTO AWS_STATIC_STRING_FROM_LITERAL(s_proxy_no_verify_peer_env_var, "AWS_PROXY_NO_VERIFY_PEER"); @@ -1150,12 +1152,23 @@ static int s_proxy_uri_init_from_env_variable( /* First check if this host should bypass proxy using NO_PROXY */ struct aws_byte_cursor host_cursor = options->host_name; - if (aws_check_no_proxy(allocator, host_cursor)) { - AWS_LOGF_DEBUG( - AWS_LS_HTTP_CONNECTION, - "Host \"" PRInSTR "\" found in NO_PROXY, bypassing proxy", - AWS_BYTE_CURSOR_PRI(host_cursor)); - return AWS_OP_SUCCESS; + + /* Get the NO_PROXY environment variable */ + struct aws_string *no_proxy_str = aws_get_env_nonempty(allocator, s_no_proxy_env_var_low); + if (no_proxy_str == NULL) { + no_proxy_str = aws_get_env_nonempty(allocator, s_no_proxy_env_var); + } + + if (no_proxy_str != NULL) { + if (aws_http_host_matches_no_proxy(allocator, host_cursor, no_proxy_str)) { + AWS_LOGF_DEBUG( + AWS_LS_HTTP_CONNECTION, + "Host \"" PRInSTR "\" found in NO_PROXY, bypassing proxy", + AWS_BYTE_CURSOR_PRI(host_cursor)); + aws_string_destroy(no_proxy_str); + return AWS_OP_SUCCESS; + } + aws_string_destroy(no_proxy_str); } if (options->tls_options) { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 80bb3821f..e51eed53e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -648,7 +648,6 @@ add_test_case(test_no_proxy_cidr_notation) add_test_case(test_no_proxy_ipv6_cidr_notation) add_test_case(test_no_proxy_invalid_patterns) add_test_case(test_no_proxy_invalid_host_inputs) -add_test_case(test_no_proxy_environment_unset) add_test_case(http_tunnel_proxy_connection_success) add_test_case(https_tunnel_proxy_connection_success) diff --git a/tests/test_no_proxy.c b/tests/test_no_proxy.c index c9fd2a8dd..dd021fac6 100644 --- a/tests/test_no_proxy.c +++ b/tests/test_no_proxy.c @@ -14,36 +14,21 @@ #include #include -AWS_STATIC_STRING_FROM_LITERAL(s_http_proxy_env_var, "http_proxy"); -AWS_STATIC_STRING_FROM_LITERAL(s_no_proxy_env_var, "no_proxy"); +static int s_test_no_proxy_helper( + struct aws_allocator *allocator, + const char *host, + const char *no_proxy_value, + bool expected_bypass) { + /* Create a no_proxy string for testing */ + struct aws_string *no_proxy_str = aws_string_new_from_c_str(allocator, no_proxy_value); -static int s_init_no_proxy_test(struct aws_allocator *allocator, const char *no_proxy_value_str) { - - aws_http_library_init(allocator); - - /* Set up test variables */ - struct aws_string *proxy_value = aws_string_new_from_c_str(allocator, "http://proxy.example.org:8888"); - struct aws_string *no_proxy_value = aws_string_new_from_c_str(allocator, no_proxy_value_str); - - /* Set environment variables for testing */ - ASSERT_SUCCESS(aws_set_environment_value(s_http_proxy_env_var, proxy_value)); - ASSERT_SUCCESS(aws_set_environment_value(s_no_proxy_env_var, no_proxy_value)); - - aws_string_destroy(proxy_value); - aws_string_destroy(no_proxy_value); - return AWS_OP_SUCCESS; -} + /* Call the function that checks NO_PROXY */ + ASSERT_UINT_EQUALS( + aws_http_host_matches_no_proxy(allocator, aws_byte_cursor_from_c_str(host), no_proxy_str), expected_bypass); -static int s_cleanup_no_proxy_test(void) { - ASSERT_SUCCESS(aws_unset_environment_value(s_http_proxy_env_var)); - ASSERT_SUCCESS(aws_unset_environment_value(s_no_proxy_env_var)); - aws_http_library_clean_up(); - return AWS_OP_SUCCESS; -} + /* Clean up */ + aws_string_destroy(no_proxy_str); -static int s_test_no_proxy_helper(struct aws_allocator *allocator, const char *host, bool expected_bypass) { - /* Call the function that checks NO_PROXY */ - ASSERT_UINT_EQUALS(aws_check_no_proxy(allocator, aws_byte_cursor_from_c_str(host)), expected_bypass); return AWS_OP_SUCCESS; } @@ -54,16 +39,15 @@ static int s_test_no_proxy_subdomain_matching(struct aws_allocator *allocator, v (void)ctx; const char *no_proxy_value = ".example.com"; - /* Initialize test with NO_PROXY value */ - ASSERT_SUCCESS(s_init_no_proxy_test(allocator, no_proxy_value)); + aws_http_library_init(allocator); /* Test that a subdomain matches when NO_PROXY contains a domain with leading dot */ - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "sub.example.com", true)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "sub.example.com", no_proxy_value, true)); /* cannot match */ - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "sub.subexample.com", false)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "sub.subexample.com", no_proxy_value, false)); - /* Clean up the test environment */ - return s_cleanup_no_proxy_test(); + aws_http_library_clean_up(); + return AWS_OP_SUCCESS; } AWS_TEST_CASE(test_no_proxy_subdomain_matching, s_test_no_proxy_subdomain_matching); @@ -74,14 +58,13 @@ static int s_test_no_proxy_wildcard_patterns(struct aws_allocator *allocator, vo (void)ctx; const char *no_proxy_value = "*"; - /* Initialize test with NO_PROXY value */ - ASSERT_SUCCESS(s_init_no_proxy_test(allocator, no_proxy_value)); + aws_http_library_init(allocator); /* Test that a wildcard pattern matches all hosts */ - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "any.example.com", true)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "any.example.com", no_proxy_value, true)); - /* Clean up the test environment */ - return s_cleanup_no_proxy_test(); + aws_http_library_clean_up(); + return AWS_OP_SUCCESS; } AWS_TEST_CASE(test_no_proxy_wildcard_patterns, s_test_no_proxy_wildcard_patterns); @@ -92,14 +75,13 @@ static int s_test_no_proxy_case_insensitivity(struct aws_allocator *allocator, v (void)ctx; const char *no_proxy_value = "example.COM"; - /* Initialize test with NO_PROXY value */ - ASSERT_SUCCESS(s_init_no_proxy_test(allocator, no_proxy_value)); + aws_http_library_init(allocator); /* Test that case insensitive matching works for both host and NO_PROXY entries */ - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "EXAMPLE.com", true)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "EXAMPLE.com", no_proxy_value, true)); - /* Clean up the test environment */ - return s_cleanup_no_proxy_test(); + aws_http_library_clean_up(); + return AWS_OP_SUCCESS; } AWS_TEST_CASE(test_no_proxy_case_insensitivity, s_test_no_proxy_case_insensitivity); @@ -111,20 +93,19 @@ static int s_test_no_proxy_ipv6_address(struct aws_allocator *allocator, void *c /* Pattern don't allow `[]`, just follows what curl does. */ const char *no_proxy_value = "2001:db8::1, ::1, [2001:db8::2]"; - /* Initialize test with NO_PROXY value */ - ASSERT_SUCCESS(s_init_no_proxy_test(allocator, no_proxy_value)); + aws_http_library_init(allocator); /* Test that an IPv6 address in brackets matches */ - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "[2001:db8::1]", true)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "[2001:db8::1]", no_proxy_value, true)); /* Test another IPv6 address format (localhost) */ - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "::1", true)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "::1", no_proxy_value, true)); /* Test a non-matching IPv6 address */ - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "[2001:db8::2]", false)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "[2001:db8::2]", no_proxy_value, false)); - /* Clean up the test environment */ - return s_cleanup_no_proxy_test(); + aws_http_library_clean_up(); + return AWS_OP_SUCCESS; } AWS_TEST_CASE(test_no_proxy_ipv6_address, s_test_no_proxy_ipv6_address); @@ -134,21 +115,21 @@ AWS_TEST_CASE(test_no_proxy_ipv6_address, s_test_no_proxy_ipv6_address); static int s_test_no_proxy_multiple_patterns(struct aws_allocator *allocator, void *ctx) { (void)ctx; /* Only wildcard support is a single `*`, if it's in the list, it will be ignored. */ - const char *no_proxy_value = "foo.bar,example.com,other.net,*"; + /* commas at start or end won't affect it. */ + const char *no_proxy_value = ",,foo.bar,example.com,other.net,*,"; - /* Initialize test with NO_PROXY value */ - ASSERT_SUCCESS(s_init_no_proxy_test(allocator, no_proxy_value)); + aws_http_library_init(allocator); /* Test that a host matches when it's in the middle of a comma-separated list */ - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "example.com", true)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "example.com", no_proxy_value, true)); /* Test that another host in the list also matches */ - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "foo.bar", true)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "foo.bar", no_proxy_value, true)); - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "foo.", false)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "foo.", no_proxy_value, false)); - /* Clean up the test environment */ - return s_cleanup_no_proxy_test(); + aws_http_library_clean_up(); + return AWS_OP_SUCCESS; } AWS_TEST_CASE(test_no_proxy_multiple_patterns, s_test_no_proxy_multiple_patterns); @@ -159,17 +140,16 @@ static int s_test_no_proxy_whitespace_handling(struct aws_allocator *allocator, (void)ctx; const char *no_proxy_value = " example.com , foo.bar "; - /* Initialize test with NO_PROXY value */ - ASSERT_SUCCESS(s_init_no_proxy_test(allocator, no_proxy_value)); + aws_http_library_init(allocator); /* Test that whitespace is properly handled in NO_PROXY entries */ - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "example.com", true)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "example.com", no_proxy_value, true)); /* Test that another host with whitespace also matches */ - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "foo.bar", true)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "foo.bar", no_proxy_value, true)); - /* Clean up the test environment */ - return s_cleanup_no_proxy_test(); + aws_http_library_clean_up(); + return AWS_OP_SUCCESS; } AWS_TEST_CASE(test_no_proxy_whitespace_handling, s_test_no_proxy_whitespace_handling); @@ -180,17 +160,16 @@ static int s_test_no_proxy_ip_address(struct aws_allocator *allocator, void *ctx (void)ctx; const char *no_proxy_value = "192.168.1.1,10.0.0.0"; - /* Initialize test with NO_PROXY value */ - ASSERT_SUCCESS(s_init_no_proxy_test(allocator, no_proxy_value)); + aws_http_library_init(allocator); /* Test that IP address matching works */ - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "192.168.1.1", true)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "192.168.1.1", no_proxy_value, true)); /* Test that a different IP doesn't match */ - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "192.168.2.1", false)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "192.168.2.1", no_proxy_value, false)); - /* Clean up the test environment */ - return s_cleanup_no_proxy_test(); + aws_http_library_clean_up(); + return AWS_OP_SUCCESS; } AWS_TEST_CASE(test_no_proxy_ip_address, s_test_no_proxy_ip_address); @@ -205,16 +184,15 @@ static int s_test_no_proxy_port_specific(struct aws_allocator *allocator, void * (void)ctx; const char *no_proxy_value = "example.com:8080"; - /* Initialize test with NO_PROXY value */ - ASSERT_SUCCESS(s_init_no_proxy_test(allocator, no_proxy_value)); + aws_http_library_init(allocator); /* Our implementation only does hostname matching and ignores port information. * In curl, this would bypass only on port 8080, but in our implementation it * bypasses for all ports. */ - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "example.com", true)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "example.com", no_proxy_value, true)); - /* Clean up the test environment */ - return s_cleanup_no_proxy_test(); + aws_http_library_clean_up(); + return AWS_OP_SUCCESS; } AWS_TEST_CASE(test_no_proxy_port_specific, s_test_no_proxy_port_specific); @@ -227,33 +205,24 @@ AWS_TEST_CASE(test_no_proxy_port_specific, s_test_no_proxy_port_specific); static int s_test_no_proxy_cidr_notation(struct aws_allocator *allocator, void *ctx) { (void)ctx; const char *no_proxy_value_16 = "192.168.0.0/16"; + const char *no_proxy_value_24 = "192.168.5.0/24"; - /* Initialize test with first NO_PROXY value */ - ASSERT_SUCCESS(s_init_no_proxy_test(allocator, no_proxy_value_16)); + aws_http_library_init(allocator); /* Test that an IP address in a CIDR range matches */ - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "192.168.5.10", true)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "192.168.5.10", no_proxy_value_16, true)); /* Test that an IP address outside the CIDR range doesn't match */ - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "10.0.5.10", false)); - - /* Clean up first test */ - ASSERT_SUCCESS(s_cleanup_no_proxy_test()); - - /* Test with a more specific subnet mask */ - const char *no_proxy_value_24 = "192.168.5.0/24"; - - /* Initialize test with second NO_PROXY value */ - ASSERT_SUCCESS(s_init_no_proxy_test(allocator, no_proxy_value_24)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "10.0.5.10", no_proxy_value_16, false)); /* Test that an IP address in a more specific CIDR range matches */ - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "192.168.5.10", true)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "192.168.5.10", no_proxy_value_24, true)); /* Test that an IP address outside the specific CIDR range doesn't match */ - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "192.168.6.10", false)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "192.168.6.10", no_proxy_value_24, false)); - /* Clean up second test */ - return s_cleanup_no_proxy_test(); + aws_http_library_clean_up(); + return AWS_OP_SUCCESS; } AWS_TEST_CASE(test_no_proxy_cidr_notation, s_test_no_proxy_cidr_notation); @@ -266,33 +235,24 @@ AWS_TEST_CASE(test_no_proxy_cidr_notation, s_test_no_proxy_cidr_notation); static int s_test_no_proxy_ipv6_cidr_notation(struct aws_allocator *allocator, void *ctx) { (void)ctx; const char *no_proxy_value_32 = "2001:db8::/32"; + const char *no_proxy_value_64 = "2001:db8:1:2::/64"; - /* Initialize test with first NO_PROXY value */ - ASSERT_SUCCESS(s_init_no_proxy_test(allocator, no_proxy_value_32)); + aws_http_library_init(allocator); /* Test that an IPv6 address in a CIDR range matches */ - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "[2001:db8:1:2::3]", true)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "[2001:db8:1:2::3]", no_proxy_value_32, true)); /* Test that an IPv6 address outside the CIDR range doesn't match */ - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "[2001:db9:1:2::3]", false)); - - /* Clean up first test */ - ASSERT_SUCCESS(s_cleanup_no_proxy_test()); - - /* Test with a more specific prefix length */ - const char *no_proxy_value_64 = "2001:db8:1:2::/64"; - - /* Initialize test with second NO_PROXY value */ - ASSERT_SUCCESS(s_init_no_proxy_test(allocator, no_proxy_value_64)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "[2001:db9:1:2::3]", no_proxy_value_32, false)); /* Test that an IPv6 address in a more specific CIDR range matches */ - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "[2001:db8:1:2::3]", true)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "[2001:db8:1:2::3]", no_proxy_value_64, true)); /* Test that an IPv6 address outside the specific CIDR range doesn't match */ - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "[2001:db8:1:3::3]", false)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "[2001:db8:1:3::3]", no_proxy_value_64, false)); - /* Clean up second test */ - return s_cleanup_no_proxy_test(); + aws_http_library_clean_up(); + return AWS_OP_SUCCESS; } AWS_TEST_CASE(test_no_proxy_ipv6_cidr_notation, s_test_no_proxy_ipv6_cidr_notation); @@ -305,55 +265,45 @@ AWS_TEST_CASE(test_no_proxy_ipv6_cidr_notation, s_test_no_proxy_ipv6_cidr_notati static int s_test_no_proxy_invalid_patterns(struct aws_allocator *allocator, void *ctx) { (void)ctx; + aws_http_library_init(allocator); + /* Test with invalid IP addresses and CIDR notations mixed with valid entries */ const char *no_proxy_value = "example.com,999.999.999.999,192.168.1.3/33,192.168.b.c," "2001:xyz::bad:ipv6,2001:db8::/129,not:a:valid:ip/64," "[malformed],192.168.1.2," "192.168.1.1/99999999999999999," /* Invalid network bits */ + "192.168.1.1/9/9/9," /* Invalid network bits */ "2001:db8::/999999"; /* Invalid IPv6 prefix */ - - /* Initialize test with NO_PROXY value containing invalid patterns */ - ASSERT_SUCCESS(s_init_no_proxy_test(allocator, no_proxy_value)); + // const char *no_proxy_value = "192.168.1.1/9/9/9"; /* Test that invalid IP addresses and CIDR blocks are safely ignored */ /* The last valid entry (192.168.1.2) should still match */ - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "192.168.1.2", true)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "192.168.1.2", no_proxy_value, true)); /* Test that the valid hostname entry still works */ - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "example.com", true)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "example.com", no_proxy_value, true)); /* Test with an invalid host address parameter */ - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "192.168.1.1", false)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "192.168.1.1", no_proxy_value, false)); /* An invalid IP address will be treated as regular hostname and match as regular hostname. */ - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "999.999.999.999", true)); - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "999.999.999.999.999", true)); - - /* Clean up the test environment */ - ASSERT_SUCCESS(s_cleanup_no_proxy_test()); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "999.999.999.999", no_proxy_value, true)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "999.999.999.999.999", no_proxy_value, true)); /* Test with only invalid entries */ const char *invalid_only = "999.999.999.999,192.168.1.1/33,not:an:ip:addr,2001:xyz::bad"; - /* Initialize test with NO_PROXY value containing only invalid patterns */ - ASSERT_SUCCESS(s_init_no_proxy_test(allocator, invalid_only)); - /* Test that a valid IP doesn't match when NO_PROXY contains only invalid entries */ - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "192.168.1.1", false)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "192.168.1.1", invalid_only, false)); /* Test with empty host parameter */ - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "", false)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "", invalid_only, false)); /* Test with a very malformed CIDR input that could cause parsing issues */ const char *malformed_cidr = "192.168.1.1/abcdef,2001:db8::/xyz"; - /* Initialize test with NO_PROXY value containing malformed CIDR notation */ - ASSERT_SUCCESS(s_init_no_proxy_test(allocator, malformed_cidr)); - /* Malformed CIDR will be taken as the entr */ - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "2001:db8::", true)); - - /* Clean up the test environment */ - ASSERT_SUCCESS(s_cleanup_no_proxy_test()); + /* Malformed CIDR will be taken as the entry */ + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "2001:db8::", malformed_cidr, false)); /* Test with very large CIDR parts that would be rejected in the buffer size check */ char large_value[200]; @@ -363,68 +313,63 @@ static int s_test_no_proxy_invalid_patterns(struct aws_allocator *allocator, voi char large_cidr[256]; snprintf(large_cidr, sizeof(large_cidr), "192.168.1.1/%s", large_value); - /* Initialize test with NO_PROXY value containing oversized CIDR notation */ - ASSERT_SUCCESS(s_init_no_proxy_test(allocator, large_cidr)); - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "192.168.1.1", false)); - - /* Clean up the test environment */ - ASSERT_SUCCESS(s_cleanup_no_proxy_test()); + /* Test with oversized CIDR notation */ + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "192.168.1.1", large_cidr, false)); - /* Clean up and return */ - return s_cleanup_no_proxy_test(); + aws_http_library_clean_up(); + return AWS_OP_SUCCESS; } AWS_TEST_CASE(test_no_proxy_invalid_patterns, s_test_no_proxy_invalid_patterns); /** - * Test invalid host inputs to aws_check_no_proxy + * Test invalid host inputs to aws_http_host_matches_no_proxy * - * Verifies that the aws_check_no_proxy function handles malformed host inputs + * Verifies that the aws_http_host_matches_no_proxy function handles malformed host inputs * gracefully without crashing. These tests specifically check the host parameter * rather than the NO_PROXY environment variable content. */ static int s_test_no_proxy_invalid_host_inputs(struct aws_allocator *allocator, void *ctx) { (void)ctx; + aws_http_library_init(allocator); + /* Set up a valid NO_PROXY value for testing */ const char *no_proxy_value = "example.com,192.168.1.0/24,2001:db8::/32"; - /* Initialize test with valid NO_PROXY value */ - ASSERT_SUCCESS(s_init_no_proxy_test(allocator, no_proxy_value)); - /* Test with invalid IPv4 address */ - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "999.999.999.999", false)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "999.999.999.999", no_proxy_value, false)); /* Test with malformed IPv4 address formats */ - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "192.168.1", false)); - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "192.168..1", false)); - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "192.168.1.", false)); - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, ".192.168.1", false)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "192.168.1", no_proxy_value, false)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "192.168..1", no_proxy_value, false)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "192.168.1.", no_proxy_value, false)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, ".192.168.1", no_proxy_value, false)); /* Test with invalid IPv6 address variants */ - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "[2001:db8::xyz]", false)); - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "[2001:db8::]:", false)); - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "2001:db8:::", false)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "[2001:db8::xyz]", no_proxy_value, false)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "[2001:db8::]:", no_proxy_value, false)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "2001:db8:::", no_proxy_value, false)); /* Test with malformed IPv6 address brackets */ - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "[2001:db8::1", false)); - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "2001:db8::1]", false)); - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "[[2001:db8::1]]", false)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "[2001:db8::1", no_proxy_value, false)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "2001:db8::1]", no_proxy_value, false)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "[[2001:db8::1]]", no_proxy_value, false)); /* Test with empty host */ - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "", false)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "", no_proxy_value, false)); /* Test with extremely long host that exceeds buffer sizes */ char long_host[1024]; memset(long_host, 'a', sizeof(long_host) - 1); long_host[sizeof(long_host) - 1] = '\0'; - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, long_host, false)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, long_host, no_proxy_value, false)); /* Test with extremely long IPv4 address that would hit buffer checks */ char long_ipv4[150] = "192.168.1.1"; for (int i = 0; i < 130; i++) { long_ipv4[11 + i] = '9'; /* Padding with extra digits */ } - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, long_ipv4, false)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, long_ipv4, no_proxy_value, false)); /* Test with extremely long IPv6 address that would hit buffer checks */ char long_ipv6[150] = "[2001:db8::1"; @@ -433,52 +378,69 @@ static int s_test_no_proxy_invalid_host_inputs(struct aws_allocator *allocator, } long_ipv6[141] = ']'; long_ipv6[142] = '\0'; - ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, long_ipv6, false)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, long_ipv6, no_proxy_value, false)); - /* Clean up the test environment */ - return s_cleanup_no_proxy_test(); + aws_http_library_clean_up(); + return AWS_OP_SUCCESS; } AWS_TEST_CASE(test_no_proxy_invalid_host_inputs, s_test_no_proxy_invalid_host_inputs); /** - * Test behavior when NO_PROXY environment variable is unset or empty + * Test behavior when NO_PROXY is empty * - * Verifies that the aws_check_no_proxy function correctly handles cases where - * the NO_PROXY environment variable is not set or empty. + * Verifies that the aws_http_host_matches_no_proxy function correctly handles cases where + * the NO_PROXY value is empty. */ -static int s_test_no_proxy_environment_unset(struct aws_allocator *allocator, void *ctx) { +static int s_test_no_proxy_empty(struct aws_allocator *allocator, void *ctx) { (void)ctx; aws_http_library_init(allocator); - /* Test with NO_PROXY unset (only http_proxy set) */ - struct aws_string *proxy_value = aws_string_new_from_c_str(allocator, "http://proxy.example.org:8888"); - ASSERT_SUCCESS(aws_set_environment_value(s_http_proxy_env_var, proxy_value)); + /* Test with empty NO_PROXY */ + const char *empty_no_proxy_value = ""; + + /* With empty NO_PROXY, aws_http_host_matches_no_proxy should return false for any host */ + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "example.com", empty_no_proxy_value, false)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "192.168.1.1", empty_no_proxy_value, false)); + ASSERT_SUCCESS(s_test_no_proxy_helper(allocator, "[2001:db8::1]", empty_no_proxy_value, false)); + + aws_http_library_clean_up(); + return AWS_OP_SUCCESS; +} +AWS_TEST_CASE(test_no_proxy_empty, s_test_no_proxy_empty); + +/** + * Test using the direct no_proxy parameter + * + * Verifies that the aws_http_host_matches_no_proxy function correctly uses the provided no_proxy parameter. + */ +static int s_test_no_proxy_direct_parameter(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_http_library_init(allocator); - /* Make sure NO_PROXY environment variables are unset */ - ASSERT_SUCCESS(aws_unset_environment_value(s_no_proxy_env_var)); - struct aws_string *uppercase_no_proxy = aws_string_new_from_c_str(allocator, "NO_PROXY"); - ASSERT_SUCCESS(aws_unset_environment_value(uppercase_no_proxy)); /* Upper case version */ - aws_string_destroy(uppercase_no_proxy); + /* Create different no_proxy values to test with */ + const char *direct_no_proxy_value = "direct.example.com"; + const char *wildcard_no_proxy_value = "*"; - /* With NO_PROXY unset, aws_check_no_proxy should return false for any host */ - ASSERT_FALSE(aws_check_no_proxy(allocator, aws_byte_cursor_from_c_str("example.com"))); - ASSERT_FALSE(aws_check_no_proxy(allocator, aws_byte_cursor_from_c_str("192.168.1.1"))); - ASSERT_FALSE(aws_check_no_proxy(allocator, aws_byte_cursor_from_c_str("[2001:db8::1]"))); + /* Test with specific host */ + ASSERT_TRUE(aws_http_host_matches_no_proxy( + allocator, + aws_byte_cursor_from_c_str("direct.example.com"), + aws_string_new_from_c_str(allocator, direct_no_proxy_value))); - /* Now test with empty NO_PROXY */ - struct aws_string *empty_no_proxy = aws_string_new_from_c_str(allocator, ""); - ASSERT_SUCCESS(aws_set_environment_value(s_no_proxy_env_var, empty_no_proxy)); + ASSERT_FALSE(aws_http_host_matches_no_proxy( + allocator, + aws_byte_cursor_from_c_str("other.example.com"), + aws_string_new_from_c_str(allocator, direct_no_proxy_value))); - /* With empty NO_PROXY, aws_check_no_proxy should still return false */ - ASSERT_FALSE(aws_check_no_proxy(allocator, aws_byte_cursor_from_c_str("example.com"))); + /* Test with wildcard */ + ASSERT_TRUE(aws_http_host_matches_no_proxy( + allocator, + aws_byte_cursor_from_c_str("any.host.com"), + aws_string_new_from_c_str(allocator, wildcard_no_proxy_value))); - /* Clean up */ - ASSERT_SUCCESS(aws_unset_environment_value(s_http_proxy_env_var)); - ASSERT_SUCCESS(aws_unset_environment_value(s_no_proxy_env_var)); - aws_string_destroy(proxy_value); - aws_string_destroy(empty_no_proxy); aws_http_library_clean_up(); return AWS_OP_SUCCESS; } -AWS_TEST_CASE(test_no_proxy_environment_unset, s_test_no_proxy_environment_unset); +AWS_TEST_CASE(test_no_proxy_direct_parameter, s_test_no_proxy_direct_parameter); From cca109a47999e4617ed50c3b29eae1fff07a64a9 Mon Sep 17 00:00:00 2001 From: Dengke Tang Date: Thu, 10 Jul 2025 12:19:42 -0700 Subject: [PATCH 5/8] avoid the pointer update --- source/no_proxy.c | 56 ++++++++++++++++------------------------------- 1 file changed, 19 insertions(+), 37 deletions(-) diff --git a/source/no_proxy.c b/source/no_proxy.c index f37adf69f..83da6bb82 100644 --- a/source/no_proxy.c +++ b/source/no_proxy.c @@ -17,41 +17,6 @@ enum hostname_type { HOSTNAME_TYPE_REGULAR, }; -/** - * Prase a host string as an IPv6 address and stores the binary representation. - * Return False is the host is not a valid IPv6 address. - * If host contains `[]`, remove them - * - * @param host The host string to check - * @param addr_out Optional pointer to store the parsed binary address. Must be at least 16 bytes. - * @return true if the host is an IPv6 address, false otherwise - */ -static bool s_parse_ipv6_address_and_update_host( - struct aws_allocator *allocator, - struct aws_string *host_str, - void *addr_out) { - if (!host_str || host_str->len < 2 || !addr_out) { - return false; - } - struct aws_string *host_str_copy = host_str; - struct aws_byte_cursor host = aws_byte_cursor_from_string(host_str); - if (host.ptr[0] == '[' && host.ptr[host.len - 1] == ']') { - /* Check if the address is enclosed in brackets and strip them for validation */ - aws_byte_cursor_advance(&host, 1); - host.len--; - host_str_copy = aws_string_new_from_cursor(allocator, &host); - } - - if (inet_pton(AF_INET6, aws_string_c_str(host_str_copy), addr_out) == 1) { - /* Update the host str */ - if (host_str != host_str_copy) { - aws_string_destroy(host_str); - host_str = host_str_copy; - } - return true; - } - return false; -} /** * s_cidr4_match() returns true if the given IPv4 address is within the * specified CIDR address range. @@ -181,8 +146,21 @@ bool aws_http_host_matches_no_proxy( if (inet_pton(AF_INET, aws_string_c_str(host_str), &ipv4_addr) == 1) { type = HOSTNAME_TYPE_IPV4; } else { - if (s_parse_ipv6_address_and_update_host(allocator, host_str, ipv6_addr)) { - /* the host is valid IPv6 */ + struct aws_string *host_str_copy = host_str; + struct aws_byte_cursor host = aws_byte_cursor_from_string(host_str); + if (host.ptr[0] == '[' && host.ptr[host.len - 1] == ']') { + /* Check if the address is enclosed in brackets and strip them for validation */ + aws_byte_cursor_advance(&host, 1); + host.len--; + host_str_copy = aws_string_new_from_cursor(allocator, &host); + } + + if (inet_pton(AF_INET6, aws_string_c_str(host_str_copy), ipv6_addr) == 1) { + /* Update the host str */ + if (host_str != host_str_copy) { + aws_string_destroy(host_str); + host_str = host_str_copy; + } type = HOSTNAME_TYPE_IPV6; } else { /* Not an IP address, so it's a regular hostname */ @@ -190,6 +168,10 @@ bool aws_http_host_matches_no_proxy( /* Ignore the trailing dot in the hostname */ host = aws_byte_cursor_right_trim_pred(&host, s_is_dot); } + if (host_str != host_str_copy) { + /* clean up the copy, but don't update the str. */ + aws_string_destroy(host_str_copy); + } } for (size_t i = 0; i < aws_array_list_length(&no_proxy_list); i++) { From 664d7ebb93e52aed4541d74cd8fbb6cd5b2c7eed Mon Sep 17 00:00:00 2001 From: Dengke Tang Date: Thu, 10 Jul 2025 13:12:37 -0700 Subject: [PATCH 6/8] compile warnings. --- source/no_proxy.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/source/no_proxy.c b/source/no_proxy.c index 83da6bb82..e66aa3409 100644 --- a/source/no_proxy.c +++ b/source/no_proxy.c @@ -27,7 +27,7 @@ enum hostname_type { * @param host_addr Pre-parsed binary representation of the host IP, or NULL to parse from host * @return true if the IP address matches the CIDR pattern, false otherwise */ -static bool s_cidr4_match(uint16_t bits, struct aws_string *network_part, uint32_t address) { +static bool s_cidr4_match(uint64_t bits, struct aws_string *network_part, uint32_t address) { uint32_t check = 0; @@ -66,7 +66,7 @@ static bool s_cidr4_match(uint16_t bits, struct aws_string *network_part, uint32 * @param host_addr Pre-parsed binary representation of the host IP, or NULL to parse from host * @return true if the IP address matches the CIDR pattern, false otherwise */ -static bool s_cidr6_match(uint16_t bits, struct aws_string *network_part, uint8_t *address) { +static bool s_cidr6_match(uint64_t bits, struct aws_string *network_part, uint8_t *address) { uint8_t check[16] = {0}; /* If no bits specified, use full 128 bits for IPv6 */ @@ -147,12 +147,12 @@ bool aws_http_host_matches_no_proxy( type = HOSTNAME_TYPE_IPV4; } else { struct aws_string *host_str_copy = host_str; - struct aws_byte_cursor host = aws_byte_cursor_from_string(host_str); - if (host.ptr[0] == '[' && host.ptr[host.len - 1] == ']') { + struct aws_byte_cursor host_copy = host; + if (host_copy.ptr[0] == '[' && host_copy.ptr[host_copy.len - 1] == ']') { /* Check if the address is enclosed in brackets and strip them for validation */ - aws_byte_cursor_advance(&host, 1); - host.len--; - host_str_copy = aws_string_new_from_cursor(allocator, &host); + aws_byte_cursor_advance(&host_copy, 1); + host_copy.len--; + host_str_copy = aws_string_new_from_cursor(allocator, &host_copy); } if (inet_pton(AF_INET6, aws_string_c_str(host_str_copy), ipv6_addr) == 1) { From 43bec0df1d76990d39af7fed0cde6fa6ada1d2e2 Mon Sep 17 00:00:00 2001 From: Dengke Tang Date: Thu, 10 Jul 2025 13:28:46 -0700 Subject: [PATCH 7/8] more warnings --- source/no_proxy.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/no_proxy.c b/source/no_proxy.c index e66aa3409..ad699df10 100644 --- a/source/no_proxy.c +++ b/source/no_proxy.c @@ -84,8 +84,8 @@ static bool s_cidr6_match(uint64_t bits, struct aws_string *network_part, uint8_ } /* Calculate full bytes and remaining bits in the netmask */ - unsigned int bytes = bits / 8; - unsigned int rest = bits % 8; + uint64_t bytes = bits / 8; + uint64_t rest = bits % 8; /* Compare full bytes of the network part */ if (bytes > 0 && memcmp(address, check, bytes) != 0) { From 4e66b53133bde843ef519df8c7641de995605102 Mon Sep 17 00:00:00 2001 From: Dengke Tang Date: Thu, 10 Jul 2025 13:37:29 -0700 Subject: [PATCH 8/8] type cast for 32bit windows --- source/no_proxy.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/no_proxy.c b/source/no_proxy.c index ad699df10..fd3777a37 100644 --- a/source/no_proxy.c +++ b/source/no_proxy.c @@ -88,7 +88,7 @@ static bool s_cidr6_match(uint64_t bits, struct aws_string *network_part, uint8_ uint64_t rest = bits % 8; /* Compare full bytes of the network part */ - if (bytes > 0 && memcmp(address, check, bytes) != 0) { + if (bytes > 0 && memcmp(address, check, (size_t)bytes) != 0) { return false; }