]> granicus.if.org Git - postgresql/blob - src/port/win32setlocale.c
Sync regex code with Tcl 8.6.4.
[postgresql] / src / port / win32setlocale.c
1 /*-------------------------------------------------------------------------
2  *
3  * win32setlocale.c
4  *              Wrapper to work around bugs in Windows setlocale() implementation
5  *
6  * Copyright (c) 2011-2015, 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
35 #include "c.h"
36
37 #undef setlocale
38
39 struct locale_map
40 {
41         /*
42          * String in locale name to replace. Can be a single string (end is NULL),
43          * or separate start and end strings. If two strings are given, the locale
44          * name must contain both of them, and everything between them is
45          * replaced. This is used for a poor-man's regexp search, allowing
46          * replacement of "start.*end".
47          */
48         const char *locale_name_start;
49         const char *locale_name_end;
50
51         const char *replacement;        /* string to replace the match with */
52 };
53
54 /*
55  * Mappings applied before calling setlocale(), to the argument.
56  */
57 static const struct locale_map locale_map_argument[] = {
58         /*
59          * "HKG" is listed here:
60          * http://msdn.microsoft.com/en-us/library/cdax410z%28v=vs.71%29.aspx
61          * (Country/Region Strings).
62          *
63          * "ARE" is the ISO-3166 three-letter code for U.A.E. It is not on the
64          * above list, but seems to work anyway.
65          */
66         {"Hong Kong S.A.R.", NULL, "HKG"},
67         {"U.A.E.", NULL, "ARE"},
68
69         /*
70          * The ISO-3166 country code for Macau S.A.R. is MAC, but Windows doesn't
71          * seem to recognize that. And Macau isn't listed in the table of accepted
72          * abbreviations linked above. Fortunately, "ZHM" seems to be accepted as
73          * an alias for "Chinese (Traditional)_Macau S.A.R..950". I'm not sure
74          * where "ZHM" comes from, must be some legacy naming scheme. But hey, it
75          * works.
76          *
77          * Note that unlike HKG and ARE, ZHM is an alias for the *whole* locale
78          * name, not just the country part.
79          *
80          * Some versions of Windows spell it "Macau", others "Macao".
81          */
82         {"Chinese (Traditional)_Macau S.A.R..950", NULL, "ZHM"},
83         {"Chinese_Macau S.A.R..950", NULL, "ZHM"},
84         {"Chinese (Traditional)_Macao S.A.R..950", NULL, "ZHM"},
85         {"Chinese_Macao S.A.R..950", NULL, "ZHM"},
86         {NULL, NULL, NULL}
87 };
88
89 /*
90  * Mappings applied after calling setlocale(), to its return value.
91  */
92 static const struct locale_map locale_map_result[] = {
93         /*
94          * "Norwegian (Bokmål)" locale name contains the a-ring character.
95          * Map it to a pure-ASCII alias.
96          *
97          * It's not clear what encoding setlocale() uses when it returns the
98          * locale name, so to play it safe, we search for "Norwegian (Bok*l)".
99          */
100         {"Norwegian (Bokm", "l)_Norway", "Norwegian_Norway"},
101         {NULL, NULL, NULL}
102 };
103
104 #define MAX_LOCALE_NAME_LEN             100
105
106 static const char *
107 map_locale(const struct locale_map * map, const char *locale)
108 {
109         static char aliasbuf[MAX_LOCALE_NAME_LEN];
110         int                     i;
111
112         /* Check if the locale name matches any of the problematic ones. */
113         for (i = 0; map[i].locale_name_start != NULL; i++)
114         {
115                 const char *needle_start = map[i].locale_name_start;
116                 const char *needle_end = map[i].locale_name_end;
117                 const char *replacement = map[i].replacement;
118                 char       *match;
119                 char       *match_start = NULL;
120                 char       *match_end = NULL;
121
122                 match = strstr(locale, needle_start);
123                 if (match)
124                 {
125                         /*
126                          * Found a match for the first part. If this was a two-part
127                          * replacement, find the second part.
128                          */
129                         match_start = match;
130                         if (needle_end)
131                         {
132                                 match = strstr(match_start + strlen(needle_start), needle_end);
133                                 if (match)
134                                         match_end = match + strlen(needle_end);
135                                 else
136                                         match_start = NULL;
137                         }
138                         else
139                                 match_end = match_start + strlen(needle_start);
140                 }
141
142                 if (match_start)
143                 {
144                         /* Found a match. Replace the matched string. */
145                         int                     matchpos = match_start - locale;
146                         int                     replacementlen = strlen(replacement);
147                         char       *rest = match_end;
148                         int                     restlen = strlen(rest);
149
150                         /* check that the result fits in the static buffer */
151                         if (matchpos + replacementlen + restlen + 1 > MAX_LOCALE_NAME_LEN)
152                                 return NULL;
153
154                         memcpy(&aliasbuf[0], &locale[0], matchpos);
155                         memcpy(&aliasbuf[matchpos], replacement, replacementlen);
156                         /* includes null terminator */
157                         memcpy(&aliasbuf[matchpos + replacementlen], rest, restlen + 1);
158
159                         return aliasbuf;
160                 }
161         }
162
163         /* no match, just return the original string */
164         return locale;
165 }
166
167 char *
168 pgwin32_setlocale(int category, const char *locale)
169 {
170         const char *argument;
171         char       *result;
172
173         if (locale == NULL)
174                 argument = NULL;
175         else
176                 argument = map_locale(locale_map_argument, locale);
177
178         /* Call the real setlocale() function */
179         result = setlocale(category, argument);
180
181         /*
182          * setlocale() is specified to return a "char *" that the caller is
183          * forbidden to modify, so casting away the "const" is innocuous.
184          */
185         if (result)
186                 result = (char *) map_locale(locale_map_result, result);
187
188         return result;
189 }