summaryrefslogtreecommitdiff
path: root/src/backend/utils
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2013-03-05 13:02:43 -0500
committerTom Lane <tgl@sss.pgh.pa.us>2013-03-05 13:02:43 -0500
commita382997ee07a8550f4ea23b5a542b046268a273c (patch)
tree240e0c50edf1257f7370babc3709cc41eb89695f /src/backend/utils
parentda5f032a8350a77aeed4f9d459633055f0adb645 (diff)
Fix to_char() to use ASCII-only case-folding rules where appropriate.
formatting.c used locale-dependent case folding rules in some code paths where the result isn't supposed to be locale-dependent, for example to_char(timestamp, 'DAY'). Since the source data is always just ASCII in these cases, that usually didn't matter ... but it does matter in Turkish locales, which have unusual treatment of "i" and "I". To confuse matters even more, the misbehavior was only visible in UTF8 encoding, because in single-byte encodings we used pg_toupper/pg_tolower which don't have locale-specific behavior for ASCII characters. Fix by providing intentionally ASCII-only case-folding functions and using these where appropriate. Per bug #7913 from Adnan Dursun. Back-patch to all active branches, since it's been like this for a long time.
Diffstat (limited to 'src/backend/utils')
-rw-r--r--src/backend/utils/adt/formatting.c138
1 files changed, 113 insertions, 25 deletions
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 5b210ebeaeb..71980899cc3 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -1644,6 +1644,87 @@ str_initcap(const char *buff, size_t nbytes)
return result;
}
+/*
+ * ASCII-only lower function
+ *
+ * We pass the number of bytes so we can pass varlena and char*
+ * to this function. The result is a palloc'd, null-terminated string.
+ */
+char *
+asc_tolower(const char *buff, size_t nbytes)
+{
+ char *result;
+ char *p;
+
+ if (!buff)
+ return NULL;
+
+ result = pnstrdup(buff, nbytes);
+
+ for (p = result; *p; p++)
+ *p = pg_tolower((unsigned char) *p);
+
+ return result;
+}
+
+/*
+ * ASCII-only upper function
+ *
+ * We pass the number of bytes so we can pass varlena and char*
+ * to this function. The result is a palloc'd, null-terminated string.
+ */
+char *
+asc_toupper(const char *buff, size_t nbytes)
+{
+ char *result;
+ char *p;
+
+ if (!buff)
+ return NULL;
+
+ result = pnstrdup(buff, nbytes);
+
+ for (p = result; *p; p++)
+ *p = pg_toupper((unsigned char) *p);
+
+ return result;
+}
+
+/*
+ * ASCII-only initcap function
+ *
+ * We pass the number of bytes so we can pass varlena and char*
+ * to this function. The result is a palloc'd, null-terminated string.
+ */
+char *
+asc_initcap(const char *buff, size_t nbytes)
+{
+ char *result;
+ char *p;
+ int wasalnum = false;
+
+ if (!buff)
+ return NULL;
+
+ result = pnstrdup(buff, nbytes);
+
+ for (p = result; *p; p++)
+ {
+ char c;
+
+ if (wasalnum)
+ *p = c = pg_tolower((unsigned char) *p);
+ else
+ *p = c = pg_toupper((unsigned char) *p);
+ /* we don't trust isalnum() here */
+ wasalnum = ((c >= 'A' && c <= 'Z') ||
+ (c >= 'a' && c <= 'z') ||
+ (c >= '0' && c <= '9'));
+ }
+
+ return result;
+}
+
/* convenience routines for when the input is null-terminated */
static char *
@@ -1664,6 +1745,20 @@ str_initcap_z(const char *buff)
return str_initcap(buff, strlen(buff));
}
+static char *
+asc_tolower_z(const char *buff)
+{
+ return asc_tolower(buff, strlen(buff));
+}
+
+static char *
+asc_toupper_z(const char *buff)
+{
+ return asc_toupper(buff, strlen(buff));
+}
+
+/* asc_initcap_z is not currently needed */
+
/* ----------
* Skip TM / th in FROM_CHAR
@@ -2151,7 +2246,8 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out)
INVALID_FOR_INTERVAL;
if (tmtcTzn(in))
{
- char *p = str_tolower_z(tmtcTzn(in));
+ /* We assume here that timezone names aren't localized */
+ char *p = asc_tolower_z(tmtcTzn(in));
strcpy(s, p);
pfree(p);
@@ -2198,7 +2294,7 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out)
strcpy(s, str_toupper_z(localized_full_months[tm->tm_mon - 1]));
else
sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9,
- str_toupper_z(months_full[tm->tm_mon - 1]));
+ asc_toupper_z(months_full[tm->tm_mon - 1]));
s += strlen(s);
break;
case DCH_Month:
@@ -2208,7 +2304,8 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out)
if (S_TM(n->suffix))
strcpy(s, str_initcap_z(localized_full_months[tm->tm_mon - 1]));
else
- sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9, months_full[tm->tm_mon - 1]);
+ sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9,
+ months_full[tm->tm_mon - 1]);
s += strlen(s);
break;
case DCH_month:
@@ -2218,10 +2315,8 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out)
if (S_TM(n->suffix))
strcpy(s, str_tolower_z(localized_full_months[tm->tm_mon - 1]));
else
- {
- sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9, months_full[tm->tm_mon - 1]);
- *s = pg_tolower((unsigned char) *s);
- }
+ sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9,
+ asc_tolower_z(months_full[tm->tm_mon - 1]));
s += strlen(s);
break;
case DCH_MON:
@@ -2231,7 +2326,7 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out)
if (S_TM(n->suffix))
strcpy(s, str_toupper_z(localized_abbrev_months[tm->tm_mon - 1]));
else
- strcpy(s, str_toupper_z(months[tm->tm_mon - 1]));
+ strcpy(s, asc_toupper_z(months[tm->tm_mon - 1]));
s += strlen(s);
break;
case DCH_Mon:
@@ -2251,10 +2346,7 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out)
if (S_TM(n->suffix))
strcpy(s, str_tolower_z(localized_abbrev_months[tm->tm_mon - 1]));
else
- {
- strcpy(s, months[tm->tm_mon - 1]);
- *s = pg_tolower((unsigned char) *s);
- }
+ strcpy(s, asc_tolower_z(months[tm->tm_mon - 1]));
s += strlen(s);
break;
case DCH_MM:
@@ -2269,7 +2361,7 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out)
strcpy(s, str_toupper_z(localized_full_days[tm->tm_wday]));
else
sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9,
- str_toupper_z(days[tm->tm_wday]));
+ asc_toupper_z(days[tm->tm_wday]));
s += strlen(s);
break;
case DCH_Day:
@@ -2277,7 +2369,8 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out)
if (S_TM(n->suffix))
strcpy(s, str_initcap_z(localized_full_days[tm->tm_wday]));
else
- sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9, days[tm->tm_wday]);
+ sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9,
+ days[tm->tm_wday]);
s += strlen(s);
break;
case DCH_day:
@@ -2285,10 +2378,8 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out)
if (S_TM(n->suffix))
strcpy(s, str_tolower_z(localized_full_days[tm->tm_wday]));
else
- {
- sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9, days[tm->tm_wday]);
- *s = pg_tolower((unsigned char) *s);
- }
+ sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9,
+ asc_tolower_z(days[tm->tm_wday]));
s += strlen(s);
break;
case DCH_DY:
@@ -2296,7 +2387,7 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out)
if (S_TM(n->suffix))
strcpy(s, str_toupper_z(localized_abbrev_days[tm->tm_wday]));
else
- strcpy(s, str_toupper_z(days_short[tm->tm_wday]));
+ strcpy(s, asc_toupper_z(days_short[tm->tm_wday]));
s += strlen(s);
break;
case DCH_Dy:
@@ -2312,10 +2403,7 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out)
if (S_TM(n->suffix))
strcpy(s, str_tolower_z(localized_abbrev_days[tm->tm_wday]));
else
- {
- strcpy(s, days_short[tm->tm_wday]);
- *s = pg_tolower((unsigned char) *s);
- }
+ strcpy(s, asc_tolower_z(days_short[tm->tm_wday]));
s += strlen(s);
break;
case DCH_DDD:
@@ -4422,12 +4510,12 @@ NUM_processor(FormatNode *node, NUMDesc *Num, char *inout, char *number,
case NUM_rn:
if (IS_FILLMODE(Np->Num))
{
- strcpy(Np->inout_p, str_tolower_z(Np->number_p));
+ strcpy(Np->inout_p, asc_tolower_z(Np->number_p));
Np->inout_p += strlen(Np->inout_p) - 1;
}
else
{
- sprintf(Np->inout_p, "%15s", str_tolower_z(Np->number_p));
+ sprintf(Np->inout_p, "%15s", asc_tolower_z(Np->number_p));
Np->inout_p += strlen(Np->inout_p) - 1;
}
break;