From 4a4febb7103edaf3734a5271a4617f19de9c76dc Mon Sep 17 00:00:00 2001 From: Nathan Moinvaziri Date: Tue, 12 Dec 2023 10:54:31 -0800 Subject: [PATCH] Fixed local ip lookup on macOS GitHub instances. myIpAddress() and myIpAddressEx should be enumerating the network interfaces and returning the local addresses with getifaddrs/GetAdapterAddresses. Where as, dnsResolve() and dnsResolveEx() enumerates resolved host names using getaddrinfo. --- net_util.c | 168 ++++++++++---- net_util.h | 48 ++-- test/CMakeLists.txt | 226 +++++++++--------- test/test_execute.cc | 302 ++++++++++++------------- test/{test_dns.cc => test_net_util.cc} | 119 +++++----- 5 files changed, 466 insertions(+), 397 deletions(-) rename test/{test_dns.cc => test_net_util.cc} (51%) diff --git a/net_util.c b/net_util.c index 19ec49f..7a89dab 100644 --- a/net_util.c +++ b/net_util.c @@ -4,6 +4,7 @@ #include #include #include +#include #ifdef _WIN32 # include @@ -16,104 +17,171 @@ # include #endif +#include "net_adapter.h" #include "util.h" +typedef struct address_list { + int32_t family; + int32_t max_addrs; + char *string; + size_t string_len; + size_t max_string; +} address_list; + +// Calculate the max length of the address string +static bool my_ip_address_list_length(void *user_data, net_adapter_s *adapter) { + address_list *list = (address_list *)user_data; + // Use different length depending on the address type + list->max_string += ((list->family == AF_INET) ? INET_ADDRSTRLEN : INET6_ADDRSTRLEN); + // Add room for semi-colon separators + list->max_string += 2; + return true; +} + +// Copy each localhost address into a string buffer seperated by semi-colons +static bool my_ip_address_list_populate(void *user_data, net_adapter_s *adapter) { + address_list *list = (address_list *)user_data; + + if (!list->max_addrs || !adapter->is_connected || !*adapter->ip) + return true; + +#ifdef _WIN32 + if (!adapter->is_dhcp_v4) + return true; +#endif + + if (list->family == AF_INET || list->family == AF_UNSPEC) { + char ip_str[INET_ADDRSTRLEN] = {0}; + inet_ntop(AF_INET, adapter->ip, ip_str, sizeof(ip_str)); + strncat(list->string + list->string_len, ip_str, list->max_string - list->string_len - 1); + list->string_len += strlen(ip_str); + } + + if (adapter->is_ipv6 && (list->family == AF_INET6 || list->family == AF_UNSPEC)) { + // Append semi-colon separator + if (list->max_string - list->string_len > 1) { + list->string[list->string_len++] = ';'; + list->string[list->string_len] = 0; + } + + char ipv6_str[INET6_ADDRSTRLEN] = {0}; + inet_ntop(AF_INET6, adapter->ipv6, ipv6_str, sizeof(ipv6_str)); + strncat(list->string + list->string_len, ipv6_str, list->max_string - list->string_len - 1); + list->string_len += strlen(ipv6_str); + } + + list->max_addrs--; + + // Append semi-colon separator + if (list->max_addrs && list->max_string - list->string_len > 1) { + list->string[list->string_len++] = ';'; + list->string[list->string_len] = 0; + } + return true; +} + +// Enumerate network adapters and get localhost addresses with a filter +static char *my_ip_address_filter(int32_t family, int32_t max_addrs) { + address_list list = {family, max_addrs, NULL, 0, 1}; + int32_t err = 0; + + if (!net_adapter_enum(&list, my_ip_address_list_length)) + return NULL; + + // Allocate buffer for the return string + list.string = (char *)calloc(1, list.max_string); + if (list.string) { + if (!net_adapter_enum(&list, my_ip_address_list_populate)) { + free(list.string); + list.string = NULL; + } + } + + return list.string; +} + +// Get local IPv4 address for localhost +char *my_ip_address(void) { + return my_ip_address_filter(AF_INET, 1); +} + +// Get local IPv6 and IPv6 addresses for localhost +char *my_ip_address_ex(void) { + return my_ip_address_filter(AF_UNSPEC, UINT8_MAX); +} + // Resolve a host name to its addresses with a filter and custom separator static char *dns_resolve_filter(const char *host, int32_t family, uint8_t max_addrs, int32_t *error) { - char name[HOST_MAX] = {0}; + address_list list = {family, max_addrs, NULL, 0, 1}; struct addrinfo hints = {0}; struct addrinfo *address_info = NULL; struct addrinfo *address = NULL; - char *ai_string = NULL; - size_t ai_string_len = 0; int32_t err = 0; - // If no host supplied, then use local machine name if (!host) { - err = gethostname(name, sizeof(name)); - if (err != 0) - goto dns_resolve_error; - } else { - // Otherwise copy the host provided - strncat(name, host, sizeof(name) - 1); + *error = EINVAL; + return NULL; } - hints.ai_flags = AI_NUMERICHOST; - hints.ai_family = PF_UNSPEC; + hints.ai_family = family; hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; - - err = getaddrinfo(name, NULL, &hints, &address_info); - if (err != EAI_NONAME) { - if (err != 0) - goto dns_resolve_error; - // Name is already an IP address - freeaddrinfo(address_info); - return strdup(name); - } - - hints.ai_flags = 0; - err = getaddrinfo(name, NULL, &hints, &address_info); + err = getaddrinfo(host, NULL, &hints, &address_info); if (err != 0) goto dns_resolve_error; // Calculate the length of the return string - size_t max_ai_string = 1; address = address_info; while (address) { // Use different length depending on the address type - if (address->ai_family == AF_INET) - max_ai_string += INET_ADDRSTRLEN; - else - max_ai_string += INET6_ADDRSTRLEN; - + list.max_string += (address->ai_family == AF_INET) ? INET_ADDRSTRLEN : INET6_ADDRSTRLEN; // Add room for semi-colon separator - max_ai_string++; + list.max_string++; address = address->ai_next; } // Allocate buffer for the return string - ai_string = (char *)calloc(1, max_ai_string); - if (!ai_string) + list.string = (char *)calloc(1, list.max_string); + if (!list.string) goto dns_resolve_error; // Enumerate each address address = address_info; - while (address && max_addrs) { + while (address && list.max_addrs) { // Only copy addresses that match the family filter - if (family == AF_UNSPEC || address->ai_family == family) { + if (list.family == AF_UNSPEC || address->ai_family == list.family) { // Ensure there is room to copy something into return string buffer - if (ai_string_len >= max_ai_string) + if (list.string_len >= list.max_string) break; - // Copy address name into return string - err = getnameinfo(address->ai_addr, (socklen_t)address->ai_addrlen, ai_string + ai_string_len, - (uint32_t)(max_ai_string - ai_string_len), NULL, 0, NI_NUMERICHOST); - if (err != 0) - goto dns_resolve_error; + // Convert address name into a numeric host string + err = getnameinfo(address->ai_addr, (socklen_t)address->ai_addrlen, list.string + list.string_len, + (uint32_t)(list.max_string - list.string_len), NULL, 0, NI_NUMERICHOST); + if (err != 0) { + continue; + } - max_addrs--; + list.max_addrs--; // Append semi-colon separator - ai_string_len = strlen(ai_string); - if (max_addrs && address->ai_next && ai_string_len + 1 < max_ai_string) { - ai_string[ai_string_len++] = ';'; - ai_string[ai_string_len] = 0; + list.string_len = strlen(list.string); + if (list.max_addrs && address->ai_next && list.string_len + 1 < list.max_string) { + list.string[list.string_len++] = ';'; + list.string[list.string_len] = 0; } } address = address->ai_next; } - if (err != 0) + if (err != 0 && list.string_len == 0) goto dns_resolve_error; - return ai_string; + return list.string; dns_resolve_error: - free(ai_string); + free(list.string); if (address_info) freeaddrinfo(address_info); diff --git a/net_util.h b/net_util.h index 4d20eb8..08caa8b 100644 --- a/net_util.h +++ b/net_util.h @@ -1,21 +1,27 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -// Resolve a host name to it an IPv4 address -char *dns_resolve(const char *host, int32_t *error); - -// Resolve a host name to its addresses -char *dns_resolve_ex(const char *host, int32_t *error); - -// Check if the ipv4 address matches the cidr notation range -bool is_ipv4_in_cidr_range(const char *ip, const char *cidr); - -// Check if the ipv6 address matches the cidr notation range -bool is_ipv6_in_cidr_range(const char *ip, const char *cidr); - -#ifdef __cplusplus -} -#endif +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +// Get local IPv4 address for localhost +char *my_ip_address(void); + +// Get local IPv6 and IPv6 addresses for localhost +char *my_ip_address_ex(void); + +// Resolve a host name to it an IPv4 address +char *dns_resolve(const char *host, int32_t *error); + +// Resolve a host name to its IPv6 and IPv6 addresses +char *dns_resolve_ex(const char *host, int32_t *error); + +// Check if the ipv4 address matches the cidr notation range +bool is_ipv4_in_cidr_range(const char *ip, const char *cidr); + +// Check if the ipv6 address matches the cidr notation range +bool is_ipv6_in_cidr_range(const char *ip, const char *cidr); + +#ifdef __cplusplus +} +#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 9d964d6..c825c9b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,113 +1,113 @@ -if(PROXYRES_BUILD_CLI) - set(PROXYCLI_SRCS proxycli.c) - - if(CMAKE_SYSTEM_NAME STREQUAL "WindowsStore") - set(PROXYCLI_ASSETS - uwp/Square44x44Logo.png - uwp/Square150x150Logo.png - uwp/StoreLogo.png - uwp/Package.appxmanifest) - - set_source_files_properties(${PROXYCLI_ASSETS} PROPERTIES - VS_DEPLOYMENT_CONTENT 1 - VS_DEPLOYMENT_LOCATION "Assets") - endif() - - add_executable(proxycli ${PROXYCLI_SRCS} ${PROXYCLI_ASSETS}) - target_link_libraries(proxycli PRIVATE proxyres) - - if(TARGET CURL::libcurl) - add_executable(curl_proxyres curl_proxyres.c) - target_link_libraries(curl_proxyres PRIVATE proxyres CURL::libcurl) - - add_test(NAME curl_proxyres-help - COMMAND curl_proxyres --help) - add_test(NAME curl_proxyres-google - COMMAND curl_proxyres http://google.com/) - endif() - - if(CMAKE_SYSTEM_NAME STREQUAL "WindowsStore") - return() - endif() - - add_test(NAME proxycli-help - COMMAND proxycli --help) - add_test(NAME proxycli-config - COMMAND proxycli config) - add_test(NAME proxycli-resolve-google - COMMAND proxycli resolve https://simple.com/ https://google.com/) - if(PROXYRES_EXECUTE OR (UNIX AND NOT APPLE)) - add_test(NAME proxycli-execute-google - COMMAND proxycli execute ${CMAKE_SOURCE_DIR}/test/pac.js https://simple.com/ https://google.com/) - endif() -endif() - -if(PROXYRES_BUILD_TESTS) - if(NOT TARGET GTest::GTest) - find_package(GTest QUIET) - endif() - - if(NOT TARGET GTest::GTest) - include(FetchContent) - - # Prevent overriding the parent project's compiler/linker settings for Windows - set(gtest_force_shared_crt ON CACHE BOOL - "Use shared (DLL) run-time lib even when Google Test is built as static lib." FORCE) - - # Allow specifying alternative Google test repository - if(NOT DEFINED GTEST_REPOSITORY) - set(GTEST_REPOSITORY https://github.com/google/googletest.git) - endif() - if(NOT DEFINED GTEST_TAG) - # Use older version of Google test to support older versions of GCC - if (CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS_EQUAL 5.3) - set(GTEST_TAG release-1.10.0) - else() - set(GTEST_TAG release-1.11.0) - endif() - endif() - - # Fetch Google test source code from official repository - FetchContent_Declare(googletest - GIT_REPOSITORY ${GTEST_REPOSITORY} - GIT_TAG ${GTEST_TAG}) - - FetchContent_GetProperties(googletest) - if(NOT googletest_POPULATED) - FetchContent_Populate(googletest) - add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR} EXCLUDE_FROM_ALL) - endif() - - add_library(GTest::GTest ALIAS gtest) - add_library(GTest::Main ALIAS gtest_main) - endif() - - set(TEST_SRCS - test_config.cc - test_main.cc - test_threadpool.cc - test_util.cc) - if(WIN32) - list(APPEND TEST_SRCS - test_util_win.cc) - elseif(UNIX AND NOT APPLE) - list(APPEND TEST_SRCS - test_util_linux.cc) - endif() - if(PROXYRES_EXECUTE) - list(APPEND TEST_SRCS - test_dns.cc - test_execute.cc - test_fetch.cc - test_net_adapter.cc - test_wpad.cc) - endif() - - add_executable(gtest_proxyres ${TEST_SRCS}) - target_link_libraries(gtest_proxyres PRIVATE proxyres GTest::GTest) - target_include_directories(gtest_proxyres PRIVATE - ${CMAKE_SOURCE_DIR} - ${CMAKE_SOURCE_DIR}/include/proxyres) - - add_test(NAME gtest_proxyres COMMAND gtest_proxyres) -endif() +if(PROXYRES_BUILD_CLI) + set(PROXYCLI_SRCS proxycli.c) + + if(CMAKE_SYSTEM_NAME STREQUAL "WindowsStore") + set(PROXYCLI_ASSETS + uwp/Square44x44Logo.png + uwp/Square150x150Logo.png + uwp/StoreLogo.png + uwp/Package.appxmanifest) + + set_source_files_properties(${PROXYCLI_ASSETS} PROPERTIES + VS_DEPLOYMENT_CONTENT 1 + VS_DEPLOYMENT_LOCATION "Assets") + endif() + + add_executable(proxycli ${PROXYCLI_SRCS} ${PROXYCLI_ASSETS}) + target_link_libraries(proxycli PRIVATE proxyres) + + if(TARGET CURL::libcurl) + add_executable(curl_proxyres curl_proxyres.c) + target_link_libraries(curl_proxyres PRIVATE proxyres CURL::libcurl) + + add_test(NAME curl_proxyres-help + COMMAND curl_proxyres --help) + add_test(NAME curl_proxyres-google + COMMAND curl_proxyres http://google.com/) + endif() + + if(CMAKE_SYSTEM_NAME STREQUAL "WindowsStore") + return() + endif() + + add_test(NAME proxycli-help + COMMAND proxycli --help) + add_test(NAME proxycli-config + COMMAND proxycli config) + add_test(NAME proxycli-resolve-google + COMMAND proxycli resolve https://simple.com/ https://google.com/) + if(PROXYRES_EXECUTE OR (UNIX AND NOT APPLE)) + add_test(NAME proxycli-execute-google + COMMAND proxycli execute ${CMAKE_SOURCE_DIR}/test/pac.js https://simple.com/ https://google.com/) + endif() +endif() + +if(PROXYRES_BUILD_TESTS) + if(NOT TARGET GTest::GTest) + find_package(GTest QUIET) + endif() + + if(NOT TARGET GTest::GTest) + include(FetchContent) + + # Prevent overriding the parent project's compiler/linker settings for Windows + set(gtest_force_shared_crt ON CACHE BOOL + "Use shared (DLL) run-time lib even when Google Test is built as static lib." FORCE) + + # Allow specifying alternative Google test repository + if(NOT DEFINED GTEST_REPOSITORY) + set(GTEST_REPOSITORY https://github.com/google/googletest.git) + endif() + if(NOT DEFINED GTEST_TAG) + # Use older version of Google test to support older versions of GCC + if (CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS_EQUAL 5.3) + set(GTEST_TAG release-1.10.0) + else() + set(GTEST_TAG release-1.11.0) + endif() + endif() + + # Fetch Google test source code from official repository + FetchContent_Declare(googletest + GIT_REPOSITORY ${GTEST_REPOSITORY} + GIT_TAG ${GTEST_TAG}) + + FetchContent_GetProperties(googletest) + if(NOT googletest_POPULATED) + FetchContent_Populate(googletest) + add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR} EXCLUDE_FROM_ALL) + endif() + + add_library(GTest::GTest ALIAS gtest) + add_library(GTest::Main ALIAS gtest_main) + endif() + + set(TEST_SRCS + test_config.cc + test_main.cc + test_net_util.cc + test_net_adapter.cc + test_threadpool.cc + test_util.cc) + if(WIN32) + list(APPEND TEST_SRCS + test_util_win.cc) + elseif(UNIX AND NOT APPLE) + list(APPEND TEST_SRCS + test_util_linux.cc) + endif() + if(PROXYRES_EXECUTE) + list(APPEND TEST_SRCS + test_execute.cc + test_fetch.cc + test_wpad.cc) + endif() + + add_executable(gtest_proxyres ${TEST_SRCS}) + target_link_libraries(gtest_proxyres PRIVATE proxyres GTest::GTest) + target_include_directories(gtest_proxyres PRIVATE + ${CMAKE_SOURCE_DIR} + ${CMAKE_SOURCE_DIR}/include/proxyres) + + add_test(NAME gtest_proxyres COMMAND gtest_proxyres) +endif() diff --git a/test/test_execute.cc b/test/test_execute.cc index 43af4da..99c28be 100644 --- a/test/test_execute.cc +++ b/test/test_execute.cc @@ -1,153 +1,149 @@ -#include -#include -#include -#include - -#include - -#include "execute.h" -#include "net_util.h" -#include "util.h" - -struct execute_param { - const char *url; - const char *expected; - - friend std::ostream &operator<<(std::ostream &os, const execute_param ¶m) { - return os << "url: " << param.url; - } -}; - -static const char *script = R"( -function FindProxyForURL(url, host) { - if (host == "myip") { - return "PROXY " + myIpAddress() + ":80"; - } - if (host == "myipex") { - var addresses = myIpAddressEx().split(';'); - var proxies = ""; - for (var i = 0; i < addresses.length; i++) { - proxies += "PROXY " + addresses[i] + ":80"; - if (i != addresses.length - 1) { - proxies += ";"; - } - } - return proxies; - } - if (host == "localhost") { - return "PROXY " + dnsResolve(host) + ":80"; - } - if (host == "::1") { - return "PROXY " + dnsResolveEx(host) + ":80"; - } - if (host == "127.0.0.1") { - return "PROXY localhost:30"; - } - if (isPlainHostName(host)) { - return "HTTP plain"; - } - if (host == "simple.com") { - return "PROXY no-such-proxy:80"; - } - if (shExpMatch(url, '*microsoft.com/*')) { - return "PROXY microsoft.com:80"; - } - return "DIRECT"; -})"; - -constexpr execute_param execute_tests[] = {{"your-pc", "HTTP plain"}, - {"127.0.0.1", "PROXY localhost:30"}, - {"localhost", "PROXY 127.0.0.1:80"}, - {"::1", "PROXY ::1:80"}, - {"myip", NULL}, - {"myipex", NULL}, - {"http://127.0.0.1/", "PROXY localhost:30"}, - {"http://simple.com/", "PROXY no-such-proxy:80"}, - {"http://example2.com/", "DIRECT"}, - {"http://microsoft.com/test", "PROXY microsoft.com:80"}, - {"file:///c:/test", NULL}, - {"file:////home/test", NULL}}; - -class execute : public ::testing::TestWithParam {}; - -INSTANTIATE_TEST_SUITE_P(execute, execute, testing::ValuesIn(execute_tests)); - -// Construct expected output for myip test case -static char *expected_my_ip(void) { - int32_t error = 0; - char *address = dns_resolve(NULL, &error); - EXPECT_EQ(error, 0); - if (!address) - return NULL; - - size_t max_expected = strlen(address) + 16; - char *expected = (char *)calloc(max_expected, sizeof(char)); - if (!expected) - return NULL; - - snprintf(expected, max_expected, "PROXY %s:80", address); - free(address); - return expected; -} - -// Construct expected output for myipex test case -static char *expected_my_ip_ex(void) { - int32_t error = 0; - char *addresses = dns_resolve_ex(NULL, &error); - EXPECT_EQ(error, 0); - if (!addresses) - return NULL; - - int32_t address_count = str_count_chr(addresses, ';') + 1; - size_t max_expected = (address_count + 16) * HOST_MAX; - char *expected = (char *)calloc(max_expected, sizeof(char)); - if (!expected) - return NULL; - - // Enumerate each address and append expected proxy to string - const char *addressesp = addresses; - while (addressesp) { - char *address = str_sep_dup(&addressesp, ";"); - size_t expected_len = strlen(expected); - if (expected_len) { - strncat(expected, ";", max_expected - expected_len); - expected_len++; - } - snprintf(expected + expected_len, max_expected - expected_len, "PROXY %s:80", address); - free(address); - } - return expected; -} - -TEST_P(execute, get_proxies_for_url) { - const auto ¶m = GetParam(); - void *proxy_execute = proxy_execute_create(); - EXPECT_NE(proxy_execute, nullptr); - if (proxy_execute) { - EXPECT_TRUE(proxy_execute_get_proxies_for_url(proxy_execute, script, param.url)); - EXPECT_EQ(proxy_execute_get_error(proxy_execute), 0); - const char *list = proxy_execute_get_list(proxy_execute); - EXPECT_NE(list, nullptr); - if (list) { - if (!param.expected) { - // Test expected result from myIpAddress() and myIpAddressEx() - if (strcmp(param.url, "myip") == 0) { - char *expected = expected_my_ip(); - EXPECT_NE(expected, nullptr); - if (expected) - EXPECT_STREQ(list, expected); - free(expected); - } else if (strcmp(param.url, "myipex") == 0) { - char *expected = expected_my_ip_ex(); - EXPECT_NE(expected, nullptr); - if (expected) - EXPECT_STREQ(list, expected); - free(expected); - } - } else { - EXPECT_STREQ(list, param.expected); - } - } - proxy_execute_delete(&proxy_execute); - } -} +#include +#include +#include +#include + +#include + +#include "execute.h" +#include "net_util.h" +#include "util.h" + +struct execute_param { + const char *url; + const char *expected; + + friend std::ostream &operator<<(std::ostream &os, const execute_param ¶m) { + return os << "url: " << param.url; + } +}; + +static const char *script = R"( +function FindProxyForURL(url, host) { + if (host == "myip") { + return "PROXY " + myIpAddress() + ":80"; + } + if (host == "myipex") { + var addresses = myIpAddressEx().split(';'); + var proxies = ""; + for (var i = 0; i < addresses.length; i++) { + proxies += "PROXY " + addresses[i] + ":80"; + if (i != addresses.length - 1) { + proxies += ";"; + } + } + return proxies; + } + if (host == "localhost") { + return "PROXY " + dnsResolve(host) + ":80"; + } + if (host == "::1") { + return "PROXY " + dnsResolveEx(host) + ":80"; + } + if (host == "127.0.0.1") { + return "PROXY localhost:30"; + } + if (isPlainHostName(host)) { + return "HTTP plain"; + } + if (host == "simple.com") { + return "PROXY no-such-proxy:80"; + } + if (shExpMatch(url, '*microsoft.com/*')) { + return "PROXY microsoft.com:80"; + } + return "DIRECT"; +})"; + +constexpr execute_param execute_tests[] = {{"your-pc", "HTTP plain"}, + {"127.0.0.1", "PROXY localhost:30"}, + {"localhost", "PROXY 127.0.0.1:80"}, + {"::1", "PROXY ::1:80"}, + {"myip", NULL}, + {"myipex", NULL}, + {"http://127.0.0.1/", "PROXY localhost:30"}, + {"http://simple.com/", "PROXY no-such-proxy:80"}, + {"http://example2.com/", "DIRECT"}, + {"http://microsoft.com/test", "PROXY microsoft.com:80"}, + {"file:///c:/test", NULL}, + {"file:////home/test", NULL}}; + +class execute : public ::testing::TestWithParam {}; + +INSTANTIATE_TEST_SUITE_P(execute, execute, testing::ValuesIn(execute_tests)); + +// Construct expected output for myip test case +static char *expected_my_ip(void) { + char *address = my_ip_address(); + if (!address) + return NULL; + + size_t max_expected = strlen(address) + 16; + char *expected = (char *)calloc(max_expected, sizeof(char)); + if (!expected) + return NULL; + + snprintf(expected, max_expected, "PROXY %s:80", address); + free(address); + return expected; +} + +// Construct expected output for myipex test case +static char *expected_my_ip_ex(void) { + char *addresses = my_ip_address_ex(); + if (!addresses) + return NULL; + + int32_t address_count = str_count_chr(addresses, ';') + 1; + size_t max_expected = (address_count + 16) * HOST_MAX; + char *expected = (char *)calloc(max_expected, sizeof(char)); + if (!expected) + return NULL; + + // Enumerate each address and append expected proxy to string + const char *addressesp = addresses; + while (addressesp) { + char *address = str_sep_dup(&addressesp, ";"); + size_t expected_len = strlen(expected); + if (expected_len) { + strncat(expected, ";", max_expected - expected_len); + expected_len++; + } + snprintf(expected + expected_len, max_expected - expected_len, "PROXY %s:80", address); + free(address); + } + return expected; +} + +TEST_P(execute, get_proxies_for_url) { + const auto ¶m = GetParam(); + void *proxy_execute = proxy_execute_create(); + EXPECT_NE(proxy_execute, nullptr); + if (proxy_execute) { + EXPECT_TRUE(proxy_execute_get_proxies_for_url(proxy_execute, script, param.url)); + EXPECT_EQ(proxy_execute_get_error(proxy_execute), 0); + const char *list = proxy_execute_get_list(proxy_execute); + EXPECT_NE(list, nullptr); + if (list) { + if (!param.expected) { + // Test expected result from myIpAddress() and myIpAddressEx() + if (strcmp(param.url, "myip") == 0) { + char *expected = expected_my_ip(); + EXPECT_NE(expected, nullptr); + if (expected) + EXPECT_STREQ(list, expected); + free(expected); + } else if (strcmp(param.url, "myipex") == 0) { + char *expected = expected_my_ip_ex(); + EXPECT_NE(expected, nullptr); + if (expected) + EXPECT_STREQ(list, expected); + free(expected); + } + } else { + EXPECT_STREQ(list, param.expected); + } + } + proxy_execute_delete(&proxy_execute); + } +} diff --git a/test/test_dns.cc b/test/test_net_util.cc similarity index 51% rename from test/test_dns.cc rename to test/test_net_util.cc index 34cd0a2..997c7d6 100644 --- a/test/test_dns.cc +++ b/test/test_net_util.cc @@ -1,60 +1,59 @@ -#include -#include -#include -#include - -#include - -#include "net_util.h" - -TEST(dns, resolve_local) { - int32_t error = 0; - char *ip = dns_resolve(NULL, &error); - EXPECT_EQ(error, 0); - EXPECT_NE(ip, nullptr); - if (ip) { - EXPECT_TRUE(strstr(ip, ".") != NULL); - free(ip); - } -} - -TEST(dns, resolve_google) { - int32_t error = 0; - char *ip = dns_resolve("google.com", &error); - EXPECT_EQ(error, 0); - EXPECT_TRUE(ip != NULL); - if (ip) { - EXPECT_TRUE(strstr(ip, ".") != NULL); - EXPECT_TRUE(strstr(ip, ";") == NULL); - free(ip); - } -} - -TEST(dns, resolve_bad) { - int32_t error = 0; - char *ip = dns_resolve("hopefully-doesnt-exist.com", &error); - EXPECT_EQ(ip, nullptr); - EXPECT_NE(error, 0); -} - -TEST(dns_ex, resolve_local) { - int32_t error = 0; - char *ips = dns_resolve_ex(NULL, &error); - EXPECT_EQ(error, 0); - EXPECT_TRUE(ips != NULL); - if (ips) { - EXPECT_TRUE(strstr(ips, ".") != NULL || strstr(ips, ":") != NULL); - free(ips); - } -} - -TEST(dns_ex, resolve_google) { - int32_t error = 0; - char *ips = dns_resolve_ex("google.com", &error); - EXPECT_EQ(error, 0); - EXPECT_TRUE(ips != NULL); - if (ips) { - EXPECT_TRUE(strstr(ips, ".") != NULL || strstr(ips, ":") != NULL); - free(ips); - } -} +#include +#include +#include +#include + +#include + +#include "net_util.h" + +TEST(net_util, my_ip_address) { + char *address = my_ip_address(); + EXPECT_TRUE(address != NULL); + if (address) { + EXPECT_TRUE(strstr(address, ".") != NULL); + EXPECT_TRUE(strstr(address, ";") == NULL); + EXPECT_TRUE(strstr(address, ":") == NULL); + free(address); + } +} + +TEST(net_util, my_ip_address_ex) { + char *addresses = my_ip_address_ex(); + EXPECT_TRUE(addresses != NULL); + if (addresses) { + EXPECT_TRUE(strstr(addresses, ".") != NULL || strstr(addresses, ":") != NULL); + free(addresses); + } +} + +TEST(net_util, dns_resolve_google) { + int32_t error = 0; + char *ip = dns_resolve("google.com", &error); + EXPECT_EQ(error, 0); + EXPECT_TRUE(ip != NULL); + if (ip) { + EXPECT_TRUE(strstr(ip, ".") != NULL); + EXPECT_TRUE(strstr(ip, ";") == NULL); + EXPECT_TRUE(strstr(ip, ":") == NULL); + free(ip); + } +} + +TEST(net_util, dns_resolve_bad) { + int32_t error = 0; + char *ip = dns_resolve("hopefully-doesnt-exist.com", &error); + EXPECT_EQ(ip, nullptr); + EXPECT_NE(error, 0); +} + +TEST(net_util, dns_ex_resolve_google) { + int32_t error = 0; + char *ips = dns_resolve_ex("google.com", &error); + EXPECT_EQ(error, 0); + EXPECT_TRUE(ips != NULL); + if (ips) { + EXPECT_TRUE(strstr(ips, ".") != NULL || strstr(ips, ":") != NULL); + free(ips); + } +}