diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 5766b18363..a3958820c9 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -239,7 +239,7 @@ jobs: - uses: actions/checkout@v4 - - run: cmake -B build -DCMAKE_UNITY_BUILD=ON -DCURL_WERROR=ON ${{ matrix.build.generate }} + - run: cmake -B build -DCMAKE_UNITY_BUILD=ON -DCURL_WERROR=ON -DUSE_APPLE_IDN=ON ${{ matrix.build.generate }} name: 'cmake generate' - run: cmake --build build --parallel 3 diff --git a/CMakeLists.txt b/CMakeLists.txt index 4cc8a9d2e2..51b4aa3654 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -899,6 +899,21 @@ if(WIN32) endif() endif() +if(APPLE) + option(USE_APPLE_IDN "Use Apple built-in IDN support" OFF) + if(USE_APPLE_IDN) + cmake_push_check_state() + set(CMAKE_REQUIRED_LIBRARIES "icucore") + check_symbol_exists("uidna_openUTS46" "unicode/uidna.h" HAVE_APPLE_IDN) + cmake_pop_check_state() + if(HAVE_APPLE_IDN) + list(APPEND CURL_LIBS "icucore") + else() + set(USE_APPLE_IDN OFF) + endif() + endif() +endif() + #libpsl option(CURL_USE_LIBPSL "Use libPSL" ON) mark_as_advanced(CURL_USE_LIBPSL) @@ -1600,7 +1615,7 @@ if(NOT CURL_DISABLE_INSTALL) _add_if("brotli" HAVE_BROTLI) _add_if("zstd" HAVE_ZSTD) _add_if("AsynchDNS" USE_ARES OR USE_THREADS_POSIX OR USE_THREADS_WIN32) - _add_if("IDN" HAVE_LIBIDN2 OR USE_WIN32_IDN) + _add_if("IDN" HAVE_LIBIDN2 OR USE_WIN32_IDN OR USE_APPLE_IDN) _add_if("Largefile" (SIZEOF_CURL_OFF_T GREATER 4) AND ((SIZEOF_OFF_T GREATER 4) OR USE_WIN32_LARGE_FILES)) _add_if("SSPI" USE_WINDOWS_SSPI) diff --git a/docs/TODO b/docs/TODO index 159e2e8753..e5bf092433 100644 --- a/docs/TODO +++ b/docs/TODO @@ -22,7 +22,6 @@ 1.3 struct lifreq 1.4 Better and more sharing 1.5 get rid of PATH_MAX - 1.6 native IDN support on macOS 1.8 CURLOPT_RESOLVE for any port number 1.9 Cache negative name resolves 1.10 auto-detect proxy @@ -251,16 +250,6 @@ there we need libssh2 to properly tell us when we pass in a too small buffer and its current API (as of libssh2 1.2.7) does not. -1.6 native IDN support on macOS - - On recent macOS versions, the getaddrinfo() function itself has built-in IDN - support. By setting the AI_CANONNAME flag, the function will return the - encoded name in the ai_canonname struct field in the returned information. - This could be used by curl on macOS when built without a separate IDN library - and an IDN host name is used in a URL. - - See initial work in https://github.com/curl/curl/pull/5371 - 1.8 CURLOPT_RESOLVE for any port number This option allows applications to set a replacement IP address for a given diff --git a/lib/curl_config.h.cmake b/lib/curl_config.h.cmake index 11b0cb54fd..5d394675d2 100644 --- a/lib/curl_config.h.cmake +++ b/lib/curl_config.h.cmake @@ -794,6 +794,9 @@ ${SIZEOF_TIME_T_CODE} /* to enable Windows IDN */ #cmakedefine USE_WIN32_IDN 1 +/* to enable Apple IDN */ +#cmakedefine USE_APPLE_IDN 1 + /* Define to 1 to enable websocket support. */ #cmakedefine USE_WEBSOCKETS 1 diff --git a/lib/curl_setup.h b/lib/curl_setup.h index 620680b54f..5dd94b3c79 100644 --- a/lib/curl_setup.h +++ b/lib/curl_setup.h @@ -649,13 +649,14 @@ /* ---------------------------------------------------------------- */ -#if defined(HAVE_LIBIDN2) && defined(HAVE_IDN2_H) && !defined(USE_WIN32_IDN) +#if defined(HAVE_LIBIDN2) && defined(HAVE_IDN2_H) && \ + !defined(USE_WIN32_IDN) && !defined(USE_APPLE_IDN) /* The lib and header are present */ #define USE_LIBIDN2 #endif -#if defined(USE_LIBIDN2) && defined(USE_WIN32_IDN) -#error "Both libidn2 and WinIDN are enabled, choose one." +#if defined(USE_LIBIDN2) && (defined(USE_WIN32_IDN) || defined(USE_APPLE_IDN)) +#error "libidn2 cannot be enabled with WinIDN or AppleIDN, choose one." #endif #define LIBIDN_REQUIRED_VERSION "0.4.1" diff --git a/lib/idn.c b/lib/idn.c index 3890b0b06b..c795672544 100644 --- a/lib/idn.c +++ b/lib/idn.c @@ -50,6 +50,63 @@ #include "curl_memory.h" #include "memdebug.h" +/* for macOS and iOS targets */ +#if defined(USE_APPLE_IDN) +#include + +static CURLcode mac_idn_to_ascii(const char *in, char **out) +{ + UErrorCode err = U_ZERO_ERROR; + UIDNA* idna = uidna_openUTS46(UIDNA_CHECK_BIDI, &err); + if(U_FAILURE(err)) { + return CURLE_OUT_OF_MEMORY; + } + else { + UIDNAInfo info = UIDNA_INFO_INITIALIZER; + char buffer[256] = {0}; + (void)uidna_nameToASCII_UTF8(idna, in, -1, buffer, + sizeof(buffer), &info, &err); + uidna_close(idna); + if(U_FAILURE(err)) { + return CURLE_URL_MALFORMAT; + } + else { + *out = strdup(buffer); + if(*out) + return CURLE_OK; + else + return CURLE_OUT_OF_MEMORY; + } + } +} + +static CURLcode mac_ascii_to_idn(const char *in, char **out) +{ + UErrorCode err = U_ZERO_ERROR; + UIDNA* idna = uidna_openUTS46(UIDNA_CHECK_BIDI, &err); + if(U_FAILURE(err)) { + return CURLE_OUT_OF_MEMORY; + } + else { + UIDNAInfo info = UIDNA_INFO_INITIALIZER; + char buffer[256] = {0}; + (void)uidna_nameToUnicodeUTF8(idna, in, -1, buffer, + sizeof(buffer), &info, &err); + uidna_close(idna); + if(U_FAILURE(err)) { + return CURLE_URL_MALFORMAT; + } + else { + *out = strdup(buffer); + if(*out) + return CURLE_OK; + else + return CURLE_OUT_OF_MEMORY; + } + } +} +#endif + #ifdef USE_WIN32_IDN /* using Windows kernel32 and normaliz libraries. */ @@ -181,6 +238,8 @@ static CURLcode idn_decode(const char *input, char **output) result = CURLE_NOT_BUILT_IN; #elif defined(USE_WIN32_IDN) result = win32_idn_to_ascii(input, &decoded); +#elif defined(USE_APPLE_IDN) + result = mac_idn_to_ascii(input, &decoded); #endif if(!result) *output = decoded; @@ -198,6 +257,10 @@ static CURLcode idn_encode(const char *puny, char **output) CURLcode result = win32_ascii_to_idn(puny, &enc); if(result) return result; +#elif defined(USE_APPLE_IDN) + CURLcode result = mac_ascii_to_idn(puny, &enc); + if(result) + return result; #endif *output = enc; return CURLE_OK; diff --git a/lib/idn.h b/lib/idn.h index e75124ef9f..2bdce8927f 100644 --- a/lib/idn.h +++ b/lib/idn.h @@ -26,7 +26,7 @@ bool Curl_is_ASCII_name(const char *hostname); CURLcode Curl_idnconvert_hostname(struct hostname *host); -#if defined(USE_LIBIDN2) || defined(USE_WIN32_IDN) +#if defined(USE_LIBIDN2) || defined(USE_WIN32_IDN) || defined(USE_APPLE_IDN) #define USE_IDN void Curl_free_idnconverted_hostname(struct hostname *host); CURLcode Curl_idn_decode(const char *input, char **output); diff --git a/lib/version.c b/lib/version.c index 257c1fedbf..66371923a7 100644 --- a/lib/version.c +++ b/lib/version.c @@ -209,6 +209,8 @@ char *curl_version(void) src[i++] = idn_version; #elif defined(USE_WIN32_IDN) src[i++] = (char *)"WinIDN"; +#elif defined(USE_APPLE_IDN) + src[i++] = (char *)"AppleIDN"; #endif #ifdef USE_LIBPSL @@ -475,7 +477,7 @@ static const struct feat features_table[] = { !defined(CURL_DISABLE_HTTP) FEATURE("HTTPS-proxy", https_proxy_present, CURL_VERSION_HTTPS_PROXY), #endif -#if defined(USE_LIBIDN2) || defined(USE_WIN32_IDN) +#if defined(USE_LIBIDN2) || defined(USE_WIN32_IDN) || defined(USE_APPLE_IDN) FEATURE("IDN", idn_present, CURL_VERSION_IDN), #endif #ifdef USE_IPV6 diff --git a/tests/libtest/lib1560.c b/tests/libtest/lib1560.c index 1509c76a7f..2f7c76a433 100644 --- a/tests/libtest/lib1560.c +++ b/tests/libtest/lib1560.c @@ -31,7 +31,7 @@ */ #include "test.h" -#if defined(USE_LIBIDN2) || defined(USE_WIN32_IDN) +#if defined(USE_LIBIDN2) || defined(USE_WIN32_IDN) || defined(USE_APPLE_IDN) #define USE_IDN #endif