Skip to content

Commit 5b3c595

Browse files
committed
Tighten error checks in datetime input, and remove bogus "ISO" format.
DecodeDateTime and DecodeTimeOnly had support for date input in the style "Y2023M03D16", which the comments claimed to be an "ISO" format. However, so far as I can find there is no such format in ISO 8601; they write units before numbers in intervals, but not in datetimes. Furthermore, the lesser-known ISO 8601-2 spec actually defines an incompatible format "2023Y03M16D". None of our documentation mentions such a format either. So let's just drop it. That leaves us with only two cases for a prefix unit specifier in datetimes: Julian dates written as Jnnnn, and the "T" separator defined by ISO 8601. Add checks to catch misuse of these specifiers, that is consecutive specifiers or a dangling specifier at the end of the string. We do not however disallow a specifier that is separated from the field that it disambiguates (by noise words or unrelated fields). That being the case, remove some overly-aggressive error checks from the ISOTIME cases. Joseph Koshakow, editorialized a bit by me; thanks also to Peter Eisentraut for some standards-reading. Discussion: https://postgr.es/m/CAAvxfHf2Q1gKLiHGnuPOiyf0ASvKUM4BnMfsXuwgtYEb_Gx0Zw@mail.gmail.com
1 parent 2b7259f commit 5b3c595

File tree

3 files changed

+92
-220
lines changed

3 files changed

+92
-220
lines changed

src/backend/utils/adt/datetime.c

+29-192
Original file line numberDiff line numberDiff line change
@@ -983,7 +983,7 @@ DecodeDateTime(char **field, int *ftype, int nf,
983983
int fmask = 0,
984984
tmask,
985985
type;
986-
int ptype = 0; /* "prefix type" for ISO y2001m02d04 format */
986+
int ptype = 0; /* "prefix type" for ISO and Julian formats */
987987
int i;
988988
int val;
989989
int dterr;
@@ -1071,6 +1071,9 @@ DecodeDateTime(char **field, int *ftype, int nf,
10711071
{
10721072
char *cp;
10731073

1074+
/*
1075+
* Allow a preceding "t" field, but no other units.
1076+
*/
10741077
if (ptype != 0)
10751078
{
10761079
/* Sanity check; should not fail this test */
@@ -1175,8 +1178,7 @@ DecodeDateTime(char **field, int *ftype, int nf,
11751178
case DTK_NUMBER:
11761179

11771180
/*
1178-
* Was this an "ISO date" with embedded field labels? An
1179-
* example is "y2001m02d04" - thomas 2001-02-04
1181+
* Deal with cases where previous field labeled this one
11801182
*/
11811183
if (ptype != 0)
11821184
{
@@ -1187,85 +1189,11 @@ DecodeDateTime(char **field, int *ftype, int nf,
11871189
value = strtoint(field[i], &cp, 10);
11881190
if (errno == ERANGE)
11891191
return DTERR_FIELD_OVERFLOW;
1190-
1191-
/*
1192-
* only a few kinds are allowed to have an embedded
1193-
* decimal
1194-
*/
1195-
if (*cp == '.')
1196-
switch (ptype)
1197-
{
1198-
case DTK_JULIAN:
1199-
case DTK_TIME:
1200-
case DTK_SECOND:
1201-
break;
1202-
default:
1203-
return DTERR_BAD_FORMAT;
1204-
break;
1205-
}
1206-
else if (*cp != '\0')
1192+
if (*cp != '.' && *cp != '\0')
12071193
return DTERR_BAD_FORMAT;
12081194

12091195
switch (ptype)
12101196
{
1211-
case DTK_YEAR:
1212-
tm->tm_year = value;
1213-
tmask = DTK_M(YEAR);
1214-
break;
1215-
1216-
case DTK_MONTH:
1217-
1218-
/*
1219-
* already have a month and hour? then assume
1220-
* minutes
1221-
*/
1222-
if ((fmask & DTK_M(MONTH)) != 0 &&
1223-
(fmask & DTK_M(HOUR)) != 0)
1224-
{
1225-
tm->tm_min = value;
1226-
tmask = DTK_M(MINUTE);
1227-
}
1228-
else
1229-
{
1230-
tm->tm_mon = value;
1231-
tmask = DTK_M(MONTH);
1232-
}
1233-
break;
1234-
1235-
case DTK_DAY:
1236-
tm->tm_mday = value;
1237-
tmask = DTK_M(DAY);
1238-
break;
1239-
1240-
case DTK_HOUR:
1241-
tm->tm_hour = value;
1242-
tmask = DTK_M(HOUR);
1243-
break;
1244-
1245-
case DTK_MINUTE:
1246-
tm->tm_min = value;
1247-
tmask = DTK_M(MINUTE);
1248-
break;
1249-
1250-
case DTK_SECOND:
1251-
tm->tm_sec = value;
1252-
tmask = DTK_M(SECOND);
1253-
if (*cp == '.')
1254-
{
1255-
dterr = ParseFractionalSecond(cp, fsec);
1256-
if (dterr)
1257-
return dterr;
1258-
tmask = DTK_ALL_SECS_M;
1259-
}
1260-
break;
1261-
1262-
case DTK_TZ:
1263-
tmask = DTK_M(TZ);
1264-
dterr = DecodeTimezone(field[i], tzp);
1265-
if (dterr)
1266-
return dterr;
1267-
break;
1268-
12691197
case DTK_JULIAN:
12701198
/* previous field was a label for "julian date" */
12711199
if (value < 0)
@@ -1519,6 +1447,9 @@ DecodeDateTime(char **field, int *ftype, int nf,
15191447

15201448
case UNITS:
15211449
tmask = 0;
1450+
/* reject consecutive unhandled units */
1451+
if (ptype != 0)
1452+
return DTERR_BAD_FORMAT;
15221453
ptype = val;
15231454
break;
15241455

@@ -1534,18 +1465,9 @@ DecodeDateTime(char **field, int *ftype, int nf,
15341465
if ((fmask & DTK_DATE_M) != DTK_DATE_M)
15351466
return DTERR_BAD_FORMAT;
15361467

1537-
/***
1538-
* We will need one of the following fields:
1539-
* DTK_NUMBER should be hhmmss.fff
1540-
* DTK_TIME should be hh:mm:ss.fff
1541-
* DTK_DATE should be hhmmss-zz
1542-
***/
1543-
if (i >= nf - 1 ||
1544-
(ftype[i + 1] != DTK_NUMBER &&
1545-
ftype[i + 1] != DTK_TIME &&
1546-
ftype[i + 1] != DTK_DATE))
1468+
/* reject consecutive unhandled units */
1469+
if (ptype != 0)
15471470
return DTERR_BAD_FORMAT;
1548-
15491471
ptype = val;
15501472
break;
15511473

@@ -1576,6 +1498,10 @@ DecodeDateTime(char **field, int *ftype, int nf,
15761498
fmask |= tmask;
15771499
} /* end loop over fields */
15781500

1501+
/* reject if prefix type appeared and was never handled */
1502+
if (ptype != 0)
1503+
return DTERR_BAD_FORMAT;
1504+
15791505
/* do additional checking for normal date specs (but not "infinity" etc) */
15801506
if (*dtype == DTK_DATE)
15811507
{
@@ -1943,7 +1869,7 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
19431869
int fmask = 0,
19441870
tmask,
19451871
type;
1946-
int ptype = 0; /* "prefix type" for ISO h04mm05s06 format */
1872+
int ptype = 0; /* "prefix type" for ISO and Julian formats */
19471873
int i;
19481874
int val;
19491875
int dterr;
@@ -2070,112 +1996,26 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
20701996
case DTK_NUMBER:
20711997

20721998
/*
2073-
* Was this an "ISO time" with embedded field labels? An
2074-
* example is "h04mm05s06" - thomas 2001-02-04
1999+
* Deal with cases where previous field labeled this one
20752000
*/
20762001
if (ptype != 0)
20772002
{
20782003
char *cp;
20792004
int value;
20802005

2081-
/* Only accept a date under limited circumstances */
2082-
switch (ptype)
2083-
{
2084-
case DTK_JULIAN:
2085-
case DTK_YEAR:
2086-
case DTK_MONTH:
2087-
case DTK_DAY:
2088-
if (tzp == NULL)
2089-
return DTERR_BAD_FORMAT;
2090-
default:
2091-
break;
2092-
}
2093-
20942006
errno = 0;
20952007
value = strtoint(field[i], &cp, 10);
20962008
if (errno == ERANGE)
20972009
return DTERR_FIELD_OVERFLOW;
2098-
2099-
/*
2100-
* only a few kinds are allowed to have an embedded
2101-
* decimal
2102-
*/
2103-
if (*cp == '.')
2104-
switch (ptype)
2105-
{
2106-
case DTK_JULIAN:
2107-
case DTK_TIME:
2108-
case DTK_SECOND:
2109-
break;
2110-
default:
2111-
return DTERR_BAD_FORMAT;
2112-
break;
2113-
}
2114-
else if (*cp != '\0')
2010+
if (*cp != '.' && *cp != '\0')
21152011
return DTERR_BAD_FORMAT;
21162012

21172013
switch (ptype)
21182014
{
2119-
case DTK_YEAR:
2120-
tm->tm_year = value;
2121-
tmask = DTK_M(YEAR);
2122-
break;
2123-
2124-
case DTK_MONTH:
2125-
2126-
/*
2127-
* already have a month and hour? then assume
2128-
* minutes
2129-
*/
2130-
if ((fmask & DTK_M(MONTH)) != 0 &&
2131-
(fmask & DTK_M(HOUR)) != 0)
2132-
{
2133-
tm->tm_min = value;
2134-
tmask = DTK_M(MINUTE);
2135-
}
2136-
else
2137-
{
2138-
tm->tm_mon = value;
2139-
tmask = DTK_M(MONTH);
2140-
}
2141-
break;
2142-
2143-
case DTK_DAY:
2144-
tm->tm_mday = value;
2145-
tmask = DTK_M(DAY);
2146-
break;
2147-
2148-
case DTK_HOUR:
2149-
tm->tm_hour = value;
2150-
tmask = DTK_M(HOUR);
2151-
break;
2152-
2153-
case DTK_MINUTE:
2154-
tm->tm_min = value;
2155-
tmask = DTK_M(MINUTE);
2156-
break;
2157-
2158-
case DTK_SECOND:
2159-
tm->tm_sec = value;
2160-
tmask = DTK_M(SECOND);
2161-
if (*cp == '.')
2162-
{
2163-
dterr = ParseFractionalSecond(cp, fsec);
2164-
if (dterr)
2165-
return dterr;
2166-
tmask = DTK_ALL_SECS_M;
2167-
}
2168-
break;
2169-
2170-
case DTK_TZ:
2171-
tmask = DTK_M(TZ);
2172-
dterr = DecodeTimezone(field[i], tzp);
2173-
if (dterr)
2174-
return dterr;
2175-
break;
2176-
21772015
case DTK_JULIAN:
21782016
/* previous field was a label for "julian date" */
2017+
if (tzp == NULL)
2018+
return DTERR_BAD_FORMAT;
21792019
if (value < 0)
21802020
return DTERR_FIELD_OVERFLOW;
21812021
tmask = DTK_DATE_M;
@@ -2378,24 +2218,17 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
23782218

23792219
case UNITS:
23802220
tmask = 0;
2221+
/* reject consecutive unhandled units */
2222+
if (ptype != 0)
2223+
return DTERR_BAD_FORMAT;
23812224
ptype = val;
23822225
break;
23832226

23842227
case ISOTIME:
23852228
tmask = 0;
2386-
2387-
/***
2388-
* We will need one of the following fields:
2389-
* DTK_NUMBER should be hhmmss.fff
2390-
* DTK_TIME should be hh:mm:ss.fff
2391-
* DTK_DATE should be hhmmss-zz
2392-
***/
2393-
if (i >= nf - 1 ||
2394-
(ftype[i + 1] != DTK_NUMBER &&
2395-
ftype[i + 1] != DTK_TIME &&
2396-
ftype[i + 1] != DTK_DATE))
2229+
/* reject consecutive unhandled units */
2230+
if (ptype != 0)
23972231
return DTERR_BAD_FORMAT;
2398-
23992232
ptype = val;
24002233
break;
24012234

@@ -2426,6 +2259,10 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
24262259
fmask |= tmask;
24272260
} /* end loop over fields */
24282261

2262+
/* reject if prefix type appeared and was never handled */
2263+
if (ptype != 0)
2264+
return DTERR_BAD_FORMAT;
2265+
24292266
/* do final checking/adjustment of Y/M/D fields */
24302267
dterr = ValidateDate(fmask, isjulian, is2digits, bc, tm);
24312268
if (dterr)

0 commit comments

Comments
 (0)