From: Petr Potekhin Date: Wed, 19 Jul 2017 23:22:15 +0000 (+0300) Subject: Fix #3985 fix Invalid date for RFC 2822 with some refactoring X-Git-Tag: 2.19.0~22^2~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=77307e7b3bc76714864e83aafbab3bc25717380a;p=thirdparty%2Fmoment.git Fix #3985 fix Invalid date for RFC 2822 with some refactoring --- diff --git a/src/lib/create/from-string.js b/src/lib/create/from-string.js index 7cae76d70..6f7af0cc2 100644 --- a/src/lib/create/from-string.js +++ b/src/lib/create/from-string.js @@ -1,7 +1,10 @@ import { configFromStringAndFormat } from './from-string-and-format'; +import { configFromArray } from './from-array'; import { hooks } from '../utils/hooks'; import { deprecate } from '../utils/deprecate'; import getParsingFlags from './parsing-flags'; +import {defaultLocaleMonthsShort} from '../units/month'; +import {defaultLocaleWeekdaysShort} from '../units/day-of-week'; // iso 8601 regex // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00) @@ -94,70 +97,91 @@ export function configFromISO(config) { } // RFC 2822 regex: For details see https://tools.ietf.org/html/rfc2822#section-3.3 -var basicRfcRegex = /^((?:Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d?\d\s(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(?:\d\d)?\d\d\s)(\d\d:\d\d)(\:\d\d)?(\s(?:UT|GMT|[ECMP][SD]T|[A-IK-Za-ik-z]|[+-]\d{4}))$/; - -// date and time from ref 2822 format -export function configFromRFC2822(config) { - var string, match, dayFormat, - dateFormat, timeFormat, tzFormat; - var timezones = { - ' GMT': ' +0000', - ' EDT': ' -0400', - ' EST': ' -0500', - ' CDT': ' -0500', - ' CST': ' -0600', - ' MDT': ' -0600', - ' MST': ' -0700', - ' PDT': ' -0700', - ' PST': ' -0800' +var rfc2822 = /^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|(?:([+-]\d\d)(\d\d)))$/; + +function extractFromRFC2822Strings(yearStr, monthStr, dayStr, hourStr, minuteStr, secondStr) { + var result = { + year: yearStr.length === 2 ? untrucateYear(parse10(yearStr)) : parse10(yearStr), + month: defaultLocaleMonthsShort.indexOf(monthStr), + day: parse10(dayStr), + hour: parse10(hourStr), + minute: parse10(minuteStr) }; - var military = 'YXWVUTSRQPONZABCDEFGHIKLM'; - var timezone, timezoneIndex; - string = config._i - .replace(/\([^\)]*\)|[\n\t]/g, ' ') // Remove comments and folding whitespace - .replace(/(\s\s+)/g, ' ') // Replace multiple-spaces with a single space - .replace(/^\s|\s$/g, ''); // Remove leading and trailing spaces - match = basicRfcRegex.exec(string); + if (secondStr) { + result.second = parse10(secondStr); + } - if (match) { - dayFormat = match[1] ? 'ddd' + ((match[1].length === 5) ? ', ' : ' ') : ''; - dateFormat = 'D MMM ' + ((match[2].length > 10) ? 'YYYY ' : 'YY '); - timeFormat = 'HH:mm' + (match[4] ? ':ss' : ''); + return result; +} - // TODO: Replace the vanilla JS Date object with an indepentent day-of-week check. - if (match[1]) { // day of week given - var momentDate = new Date(match[2]); - var momentDay = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'][momentDate.getDay()]; +function untrucateYear(year) { + return year > 60 ? 1900 + year : 2000 + year; +} - if (match[1].substr(0,3) !== momentDay) { - getParsingFlags(config).weekdayMismatch = true; - config._isValid = false; - return; - } +function preprocessRFC2822(s) { + // Remove comments and folding whitespace and replace multiple-spaces with a single space + return s.replace(/\([^)]*\)|[\n\t]/g, ' ').replace(/(\s\s+)/g, ' ').trim(); +} + +function signedOffset(offHourStr, offMinuteStr) { + var offHour = parse10(offHourStr) || 0, + offMin = parse10(offMinuteStr) || 0, + offMinSigned = offHour < 0 ? -offMin : offMin; + return offHour * 60 + offMinSigned; +} + +function checkWeekday(weekdayStr, parsedInput, config) { + if (weekdayStr) { + // TODO: Replace the vanilla JS Date object with an indepentent day-of-week check. + var weekdayProvided = defaultLocaleWeekdaysShort.indexOf(weekdayStr), + weekdayActual = new Date(parsedInput.year, parsedInput.month, parsedInput.day).getDay(); + if (weekdayProvided !== weekdayActual) { + getParsingFlags(config).weekdayMismatch = true; + config._isValid = false; + return false; } + } + return true; +} - switch (match[5].length) { - case 2: // military - if (timezoneIndex === 0) { - timezone = ' +0000'; - } else { - timezoneIndex = military.indexOf(match[5][1].toUpperCase()) - 12; - timezone = ((timezoneIndex < 0) ? ' -' : ' +') + - (('' + timezoneIndex).replace(/^-?/, '0')).match(/..$/)[0] + '00'; - } - break; - case 4: // Zone - timezone = timezones[match[5]]; - break; - default: // UT or +/-9999 - timezone = timezones[' GMT']; +var obsOffsets = { + GMT: 0, + EDT: -4 * 60, + EST: -5 * 60, + CDT: 5 * 60, + CST: 6 * 60, + MDT: 6 * 60, + MST: 7 * 60, + PDT: 7 * 60, + PST: 8 * 60 +}; + +function parse10(inty) { + return parseInt(inty, 10); +} + +function calculateOffset(obsOffset, milOffset, offHourStr, offMinuteStr) { + if (obsOffset) { + return obsOffsets[obsOffset]; + } else { + return (milOffset) ? 0 : signedOffset(offHourStr, offMinuteStr); + } +} + +// date and time from ref 2822 format +export function configFromRFC2822(config) { + var match = rfc2822.exec(preprocessRFC2822(config._i)); + if (match) { + var parsedArray = extractFromRFC2822Strings(match[4], match[3], match[2], match[5], match[6], match[7]); + if (!checkWeekday(match[1], parsedArray, config)) { + return; } - match[5] = timezone; - config._i = match.splice(1).join(''); - tzFormat = ' ZZ'; - config._f = dayFormat + dateFormat + timeFormat + tzFormat; - configFromStringAndFormat(config); + + config._a = [parsedArray.year, parsedArray.month, parsedArray.day, parsedArray.hour, parsedArray.minute, parsedArray.second]; + config._tzm = calculateOffset(match[8], match[9], match[10], match[11]); + + configFromArray(config); getParsingFlags(config).rfc2822 = true; } else { config._isValid = false; diff --git a/src/test/locale/ru.js b/src/test/locale/ru.js index b7a13f691..0ea1d74ff 100644 --- a/src/test/locale/ru.js +++ b/src/test/locale/ru.js @@ -353,3 +353,35 @@ test('weeks year starting monday formatted', function (assert) { assert.equal(moment([2012, 0, 9]).format('w ww wo'), '2 02 2-я', 'Jan 9 2012 should be week 2'); }); +test('parsing RFC 2822', function (assert) { + var testCases = { + 'clean RFC2822 datetime with all options': 'Tue, 01 Nov 2016 01:23:45 UT', + 'clean RFC2822 datetime without comma': 'Tue 01 Nov 2016 02:23:45 GMT', + 'clean RFC2822 datetime without seconds': 'Tue, 01 Nov 2016 03:23 +0000', + 'clean RFC2822 datetime without century': 'Tue, 01 Nov 16 04:23:45 Z', + 'clean RFC2822 datetime without day': '01 Nov 2016 05:23:45 z', + 'clean RFC2822 datetime with single-digit day-of-month': 'Tue, 1 Nov 2016 06:23:45 GMT', + 'RFC2822 datetime with CFWSs': '(Init Comment) Tue,\n 1 Nov 2016 (Split\n Comment) 07:23:45 +0000 (GMT)' + }; + var testCase; + + for (testCase in testCases) { + var testResult = moment(testCases[testCase], moment.RFC_2822, true); + assert.ok(testResult.isValid(), testResult); + assert.ok(testResult.parsingFlags().rfc2822, testResult + ' - rfc2822 parsingFlag'); + } +}); + +test('non RFC 2822 strings', function (assert) { + var testCases = { + 'RFC2822 datetime with all options but invalid day delimiter': 'Tue. 01 Nov 2016 01:23:45 GMT', + 'RFC2822 datetime with mismatching Day (week v date)': 'Mon, 01 Nov 2016 01:23:45 GMT' + }; + var testCase; + + for (testCase in testCases) { + var testResult = moment(testCases[testCase], moment.RFC_2822, true); + assert.ok(!testResult.isValid(), testResult); + assert.ok(!testResult.parsingFlags().rfc2822, testResult + ' - rfc2822 parsingFlag'); + } +});