Cygwin: locales: implement own method to check locale validity

The Windows function ResolveLocaleName is next to useless to
convert a partial locale identifier into a full, supported
locale identifier.  It converts anything which vaguely resembles
a locale into some other locale it supports.

Bad examples are:
  "en-XY" gets converted to "en-US", and worse,
  "ff-BF" gets converted to "ff-Latn-SN", even though "ff-Adlm-BF"
  exists!

To check if a locale is supported, we have to enumerate all valid
Windows locales, and return the match, even if the locale in Windows
requires a script.  Implement resolve_locale_name() as replacement
function for ResolveLocaleName.

Fixes: e95a7a7955 ("Cygwin: convert Windows locale handling from LCID to ISO5646 strings")
Signed-off-by: Corinna Vinschen <corinna@vinschen.de>
This commit is contained in:
Corinna Vinschen 2023-03-24 12:43:47 +01:00
parent 15898b9588
commit b5b67a65f8
1 changed files with 61 additions and 1 deletions

View File

@ -39,6 +39,64 @@ details. */
#define has_modifier(x) ((x)[0] && !strcmp (modifier, (x)))
/* ResolveLocaleName does not what we want. It converts anything which
vaguely resembles a locale into some other locale it supports. Bad
examples are: "en-XY" gets converted to "en-US", and worse, "ff-BF" gets
converted to "ff-Latn-SN", even though "ff-Adlm-BF" exists! Useless.
To check if a locale is supported, we have to enumerate all valid
Windows locales, and return the match, even if the locale in Windows
requires a script. */
struct res_loc_t {
const wchar_t *search_iso639;
const wchar_t *search_iso3166;
wchar_t *resolved_locale;
int res_len;
};
static BOOL
resolve_locale_proc (LPWSTR win_locale, DWORD info, LPARAM param)
{
res_loc_t *loc = (res_loc_t *) param;
wchar_t *iso639, *iso639_end;
wchar_t *iso3166;
iso639 = win_locale;
iso639_end = wcschr (iso639, L'-');
if (!iso639_end)
return TRUE;
if (wcsncmp (loc->search_iso639, iso639, iso639_end - iso639) != 0)
return TRUE;
iso3166 = ++iso639_end;
/* Territory is all upper case */
while (!iswupper (iso3166[0]) || !iswupper (iso3166[1]))
{
iso3166 = wcschr (iso3166, L'-');
if (!iso3166)
return TRUE;
++iso3166;
}
if (wcsncmp (loc->search_iso3166, iso3166, wcslen (loc->search_iso3166)))
return TRUE;
wcsncat (loc->resolved_locale, win_locale, loc->res_len - 1);
return FALSE;
}
static int
resolve_locale_name (const wchar_t *search, wchar_t *result, int rlen)
{
res_loc_t loc;
loc.search_iso639 = search;
loc.search_iso3166 = wcschr (search, L'-') + 1;
loc.resolved_locale = result;
loc.res_len = rlen;
result[0] = L'\0';
EnumSystemLocalesEx (resolve_locale_proc,
LOCALE_WINDOWS | LOCALE_SUPPLEMENTAL,
(LPARAM) &loc, NULL);
return wcslen (result);
}
/* Fetch Windows RFC 5646 locale from POSIX locale specifier.
Return values:
@ -106,8 +164,10 @@ __get_rfc5646_from_locale (const char *name, wchar_t *win_locale)
break;
}
}
/* If resolve_locale_name returns with error, or if it returns a
locale other than the input locale, we don't support this locale. */
if (!wlocale[0]
&& ResolveLocaleName (locale, wlocale, ENCODING_LEN + 1) <= 1)
&& !resolve_locale_name (locale, wlocale, ENCODING_LEN + 1))
{
set_errno (ENOENT);
return -1;