You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

194 lines
6.0KB

  1. /*-------------------------------------------------------------------------
  2. *
  3. * win32setlocale.c
  4. * Wrapper to work around bugs in Windows setlocale() implementation
  5. *
  6. * Copyright (c) 2011-2019, PostgreSQL Global Development Group
  7. *
  8. * IDENTIFICATION
  9. * src/port/win32setlocale.c
  10. *
  11. *
  12. * The setlocale() function in Windows is broken in two ways. First, it
  13. * has a problem with locale names that have a dot in the country name. For
  14. * example:
  15. *
  16. * "Chinese (Traditional)_Hong Kong S.A.R..950"
  17. *
  18. * For some reason, setlocale() doesn't accept that as argument, even though
  19. * setlocale(LC_ALL, NULL) returns exactly that. Fortunately, it accepts
  20. * various alternative names for such countries, so to work around the broken
  21. * setlocale() function, we map the troublemaking locale names to accepted
  22. * aliases, before calling setlocale().
  23. *
  24. * The second problem is that the locale name for "Norwegian (Bokmål)"
  25. * contains a non-ASCII character. That's problematic, because it's not clear
  26. * what encoding the locale name itself is supposed to be in, when you
  27. * haven't yet set a locale. Also, it causes problems when the cluster
  28. * contains databases with different encodings, as the locale name is stored
  29. * in the pg_database system catalog. To work around that, when setlocale()
  30. * returns that locale name, map it to a pure-ASCII alias for the same
  31. * locale.
  32. *-------------------------------------------------------------------------
  33. */
  34. #include "c.h"
  35. #undef setlocale
  36. struct locale_map
  37. {
  38. /*
  39. * String in locale name to replace. Can be a single string (end is NULL),
  40. * or separate start and end strings. If two strings are given, the locale
  41. * name must contain both of them, and everything between them is
  42. * replaced. This is used for a poor-man's regexp search, allowing
  43. * replacement of "start.*end".
  44. */
  45. const char *locale_name_start;
  46. const char *locale_name_end;
  47. const char *replacement; /* string to replace the match with */
  48. };
  49. /*
  50. * Mappings applied before calling setlocale(), to the argument.
  51. */
  52. static const struct locale_map locale_map_argument[] = {
  53. /*
  54. * "HKG" is listed here:
  55. * http://msdn.microsoft.com/en-us/library/cdax410z%28v=vs.71%29.aspx
  56. * (Country/Region Strings).
  57. *
  58. * "ARE" is the ISO-3166 three-letter code for U.A.E. It is not on the
  59. * above list, but seems to work anyway.
  60. */
  61. {"Hong Kong S.A.R.", NULL, "HKG"},
  62. {"U.A.E.", NULL, "ARE"},
  63. /*
  64. * The ISO-3166 country code for Macau S.A.R. is MAC, but Windows doesn't
  65. * seem to recognize that. And Macau isn't listed in the table of accepted
  66. * abbreviations linked above. Fortunately, "ZHM" seems to be accepted as
  67. * an alias for "Chinese (Traditional)_Macau S.A.R..950". I'm not sure
  68. * where "ZHM" comes from, must be some legacy naming scheme. But hey, it
  69. * works.
  70. *
  71. * Note that unlike HKG and ARE, ZHM is an alias for the *whole* locale
  72. * name, not just the country part.
  73. *
  74. * Some versions of Windows spell it "Macau", others "Macao".
  75. */
  76. {"Chinese (Traditional)_Macau S.A.R..950", NULL, "ZHM"},
  77. {"Chinese_Macau S.A.R..950", NULL, "ZHM"},
  78. {"Chinese (Traditional)_Macao S.A.R..950", NULL, "ZHM"},
  79. {"Chinese_Macao S.A.R..950", NULL, "ZHM"},
  80. {NULL, NULL, NULL}
  81. };
  82. /*
  83. * Mappings applied after calling setlocale(), to its return value.
  84. */
  85. static const struct locale_map locale_map_result[] = {
  86. /*
  87. * "Norwegian (Bokmål)" locale name contains the a-ring character.
  88. * Map it to a pure-ASCII alias.
  89. *
  90. * It's not clear what encoding setlocale() uses when it returns the
  91. * locale name, so to play it safe, we search for "Norwegian (Bok*l)".
  92. *
  93. * Just to make life even more complicated, some versions of Windows spell
  94. * the locale name without parentheses. Translate that too.
  95. */
  96. {"Norwegian (Bokm", "l)_Norway", "Norwegian_Norway"},
  97. {"Norwegian Bokm", "l_Norway", "Norwegian_Norway"},
  98. {NULL, NULL, NULL}
  99. };
  100. #define MAX_LOCALE_NAME_LEN 100
  101. static const char *
  102. map_locale(const struct locale_map *map, const char *locale)
  103. {
  104. static char aliasbuf[MAX_LOCALE_NAME_LEN];
  105. int i;
  106. /* Check if the locale name matches any of the problematic ones. */
  107. for (i = 0; map[i].locale_name_start != NULL; i++)
  108. {
  109. const char *needle_start = map[i].locale_name_start;
  110. const char *needle_end = map[i].locale_name_end;
  111. const char *replacement = map[i].replacement;
  112. char *match;
  113. char *match_start = NULL;
  114. char *match_end = NULL;
  115. match = strstr(locale, needle_start);
  116. if (match)
  117. {
  118. /*
  119. * Found a match for the first part. If this was a two-part
  120. * replacement, find the second part.
  121. */
  122. match_start = match;
  123. if (needle_end)
  124. {
  125. match = strstr(match_start + strlen(needle_start), needle_end);
  126. if (match)
  127. match_end = match + strlen(needle_end);
  128. else
  129. match_start = NULL;
  130. }
  131. else
  132. match_end = match_start + strlen(needle_start);
  133. }
  134. if (match_start)
  135. {
  136. /* Found a match. Replace the matched string. */
  137. int matchpos = match_start - locale;
  138. int replacementlen = strlen(replacement);
  139. char *rest = match_end;
  140. int restlen = strlen(rest);
  141. /* check that the result fits in the static buffer */
  142. if (matchpos + replacementlen + restlen + 1 > MAX_LOCALE_NAME_LEN)
  143. return NULL;
  144. memcpy(&aliasbuf[0], &locale[0], matchpos);
  145. memcpy(&aliasbuf[matchpos], replacement, replacementlen);
  146. /* includes null terminator */
  147. memcpy(&aliasbuf[matchpos + replacementlen], rest, restlen + 1);
  148. return aliasbuf;
  149. }
  150. }
  151. /* no match, just return the original string */
  152. return locale;
  153. }
  154. char *
  155. pgwin32_setlocale(int category, const char *locale)
  156. {
  157. const char *argument;
  158. char *result;
  159. if (locale == NULL)
  160. argument = NULL;
  161. else
  162. argument = map_locale(locale_map_argument, locale);
  163. /* Call the real setlocale() function */
  164. result = setlocale(category, argument);
  165. /*
  166. * setlocale() is specified to return a "char *" that the caller is
  167. * forbidden to modify, so casting away the "const" is innocuous.
  168. */
  169. if (result)
  170. result = unconstify(char *, map_locale(locale_map_result, result));
  171. return result;
  172. }