Skip to content

Commit 3bc91c0

Browse files
fix(smithy-client): remove capturing groups from date regex (#3008)
1 parent 720891d commit 3bc91c0

File tree

1 file changed

+59
-43
lines changed

1 file changed

+59
-43
lines changed

packages/smithy-client/src/date-utils.ts

Lines changed: 59 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,7 @@ export function dateToUtcString(date: Date): string {
3737
return `${DAYS[dayOfWeek]}, ${dayOfMonthString} ${MONTHS[month]} ${year} ${hoursString}:${minutesString}:${secondsString} GMT`;
3838
}
3939

40-
const RFC3339 = new RegExp(
41-
/^(?<Y>\d{4})-(?<M>\d{2})-(?<D>\d{2})[tT](?<H>\d{2}):(?<m>\d{2}):(?<s>\d{2})(?:\.(?<frac>\d+))?[zZ]$/
42-
);
40+
const RFC3339 = new RegExp(/^(\d{4})-(\d{2})-(\d{2})[tT](\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?[zZ]$/);
4341

4442
/**
4543
* Parses a value into a Date. Returns undefined if the input is null or
@@ -62,24 +60,27 @@ export const parseRfc3339DateTime = (value: unknown): Date | undefined => {
6260
throw new TypeError("RFC-3339 date-times must be expressed as strings");
6361
}
6462
const match = RFC3339.exec(value);
65-
if (!match || !match.groups) {
63+
if (!match) {
6664
throw new TypeError("Invalid RFC-3339 date-time value");
6765
}
68-
const year = strictParseShort(stripLeadingZeroes(match.groups["Y"]))!;
69-
const month = parseDateValue(match.groups["M"], "month", 1, 12);
70-
const day = parseDateValue(match.groups["D"], "day", 1, 31);
7166

72-
return buildDate(year, month, day, match);
67+
const [_, yearStr, monthStr, dayStr, hours, minutes, seconds, fractionalMilliseconds] = match;
68+
69+
const year = strictParseShort(stripLeadingZeroes(yearStr))!;
70+
const month = parseDateValue(monthStr, "month", 1, 12);
71+
const day = parseDateValue(dayStr, "day", 1, 31);
72+
73+
return buildDate(year, month, day, { hours, minutes, seconds, fractionalMilliseconds });
7374
};
7475

7576
const IMF_FIXDATE = new RegExp(
76-
/^(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun), (?<D>\d{2}) (?<M>Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) (?<Y>\d{4}) (?<H>\d{2}):(?<m>\d{2}):(?<s>\d{2})(?:\.(?<frac>\d+))? GMT$/
77+
/^(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun), (\d{2}) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) (\d{4}) (\d{2}):(\d{2}):(\d{2})(?:\.(\d+))? GMT$/
7778
);
7879
const RFC_850_DATE = new RegExp(
79-
/^(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), (?<D>\d{2})-(?<M>Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-(?<Y>\d{2}) (?<H>\d{2}):(?<m>\d{2}):(?<s>\d{2})(?:\.(?<frac>\d+))? GMT$/
80+
/^(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), (\d{2})-(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-(\d{2}) (\d{2}):(\d{2}):(\d{2})(?:\.(\d+))? GMT$/
8081
);
8182
const ASC_TIME = new RegExp(
82-
/^(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun) (?<M>Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) (?<D> [1-9]|\d{2}) (?<H>\d{2}):(?<m>\d{2}):(?<s>\d{2})(?:\.(?<frac>\d+))? (?<Y>\d{4})$/
83+
/^(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) ( [1-9]|\d{2}) (\d{2}):(\d{2}):(\d{2})(?:\.(\d+))? (\d{4})$/
8384
);
8485

8586
/**
@@ -102,37 +103,45 @@ export const parseRfc7231DateTime = (value: unknown): Date | undefined => {
102103
throw new TypeError("RFC-7231 date-times must be expressed as strings");
103104
}
104105

105-
//allow customization of day parsing for asctime days, which can be left-padded with spaces
106-
let dayFn: (value: string) => number = (value) => parseDateValue(value, "day", 1, 31);
106+
let match = IMF_FIXDATE.exec(value);
107+
if (match) {
108+
const [_, dayStr, monthStr, yearStr, hours, minutes, seconds, fractionalMilliseconds] = match;
109+
return buildDate(
110+
strictParseShort(stripLeadingZeroes(yearStr))!,
111+
parseMonthByShortName(monthStr),
112+
parseDateValue(dayStr, "day", 1, 31),
113+
{ hours, minutes, seconds, fractionalMilliseconds }
114+
);
115+
}
107116

108-
//all formats other than RFC 850 use a four-digit year
109-
let yearFn: (value: string) => number = (value: string) => strictParseShort(stripLeadingZeroes(value))!;
110-
//RFC 850 dates need post-processing to adjust year values if they are too far in the future
111-
let dateAdjustmentFn: (value: Date) => Date = (value) => value;
117+
match = RFC_850_DATE.exec(value);
118+
if (match) {
119+
const [_, dayStr, monthStr, yearStr, hours, minutes, seconds, fractionalMilliseconds] = match;
120+
// RFC 850 dates use 2-digit years. So we parse the year specifically,
121+
// and then once we've constructed the entire date, we adjust it if the resultant date
122+
// is too far in the future.
123+
return adjustRfc850Year(
124+
buildDate(parseTwoDigitYear(yearStr), parseMonthByShortName(monthStr), parseDateValue(dayStr, "day", 1, 31), {
125+
hours,
126+
minutes,
127+
seconds,
128+
fractionalMilliseconds,
129+
})
130+
);
131+
}
112132

113-
let match = IMF_FIXDATE.exec(value);
114-
if (!match || !match.groups) {
115-
match = RFC_850_DATE.exec(value);
116-
if (match && match.groups) {
117-
// RFC 850 dates use 2-digit years. So we parse the year specifically,
118-
// and then once we've constructed the entire date, we adjust it if the resultant date
119-
// is too far in the future.
120-
yearFn = parseTwoDigitYear;
121-
dateAdjustmentFn = adjustRfc850Year;
122-
} else {
123-
match = ASC_TIME.exec(value);
124-
if (match && match.groups) {
125-
dayFn = (value) => parseDateValue(value.trimLeft(), "day", 1, 31);
126-
} else {
127-
throw new TypeError("Invalid RFC-7231 date-time value");
128-
}
129-
}
133+
match = ASC_TIME.exec(value);
134+
if (match) {
135+
const [_, monthStr, dayStr, hours, minutes, seconds, fractionalMilliseconds, yearStr] = match;
136+
return buildDate(
137+
strictParseShort(stripLeadingZeroes(yearStr))!,
138+
parseMonthByShortName(monthStr),
139+
parseDateValue(dayStr.trimLeft(), "day", 1, 31),
140+
{ hours, minutes, seconds, fractionalMilliseconds }
141+
);
130142
}
131143

132-
const year = yearFn(match.groups["Y"]);
133-
const month = parseMonthByShortName(match.groups["M"]);
134-
const day = dayFn(match.groups["D"]);
135-
return dateAdjustmentFn(buildDate(year, month, day, match));
144+
throw new TypeError("Invalid RFC-7231 date-time value");
136145
};
137146

138147
/**
@@ -164,6 +173,13 @@ export const parseEpochTimestamp = (value: unknown): Date | undefined => {
164173
return new Date(Math.round(valueAsDouble * 1000));
165174
};
166175

176+
interface RawTime {
177+
hours: string;
178+
minutes: string;
179+
seconds: string;
180+
fractionalMilliseconds: string | undefined;
181+
}
182+
167183
/**
168184
* Build a date from a numeric year, month, date, and an match with named groups
169185
* "H", "m", s", and "frac", representing hours, minutes, seconds, and optional fractional seconds.
@@ -172,7 +188,7 @@ export const parseEpochTimestamp = (value: unknown): Date | undefined => {
172188
* @param day numeric year
173189
* @param match match with groups "H", "m", s", and "frac"
174190
*/
175-
const buildDate = (year: number, month: number, day: number, match: RegExpMatchArray): Date => {
191+
const buildDate = (year: number, month: number, day: number, time: RawTime): Date => {
176192
const adjustedMonth = month - 1; // JavaScript, and our internal data structures, expect 0-indexed months
177193
validateDayOfMonth(year, adjustedMonth, day);
178194
// Adjust month down by 1
@@ -181,11 +197,11 @@ const buildDate = (year: number, month: number, day: number, match: RegExpMatchA
181197
year,
182198
adjustedMonth,
183199
day,
184-
parseDateValue(match.groups!["H"]!, "hour", 0, 23),
185-
parseDateValue(match.groups!["m"]!, "minute", 0, 59),
200+
parseDateValue(time.hours, "hour", 0, 23),
201+
parseDateValue(time.minutes, "minute", 0, 59),
186202
// seconds can go up to 60 for leap seconds
187-
parseDateValue(match.groups!["s"]!, "seconds", 0, 60),
188-
parseMilliseconds(match.groups!["frac"])
203+
parseDateValue(time.seconds, "seconds", 0, 60),
204+
parseMilliseconds(time.fractionalMilliseconds)
189205
)
190206
);
191207
};

0 commit comments

Comments
 (0)