const NEW_LINES_REGEXP = /[\n\r]/gm; const parseInts = (a) => a.map((v) => parseInt(v, 10)); /** * HTML input element value sanitizer. */ class HTMLInputElementValueSanitizer { /** * Sanitizes a value. * * @param input Input. * @param value Value. */ static sanitize(input, value) { switch (input.type) { case 'password': case 'search': case 'tel': case 'text': return value.replace(NEW_LINES_REGEXP, ''); case 'color': // https://html.spec.whatwg.org/multipage/forms.html#color-state-(type=color):value-sanitization-algorithm return /^#[a-fA-F\d]{6}$/.test(value) ? value.toLowerCase() : '#000000'; case 'email': // https://html.spec.whatwg.org/multipage/forms.html#e-mail-state-(type=email):value-sanitization-algorithm // https://html.spec.whatwg.org/multipage/forms.html#e-mail-state-(type=email):value-sanitization-algorithm-2 if (input.multiple) { return value .split(',') .map((token) => token.trim()) .join(','); } return value.trim().replace(NEW_LINES_REGEXP, ''); case 'number': // https://html.spec.whatwg.org/multipage/input.html#number-state-(type=number):value-sanitization-algorithm return !isNaN(Number.parseFloat(value)) ? value : ''; case 'range': { // https://html.spec.whatwg.org/multipage/input.html#range-state-(type=range):value-sanitization-algorithm const number = Number.parseFloat(value); const min = parseFloat(input.min) || 0; const max = parseFloat(input.max) || 100; if (isNaN(number)) { return max < min ? String(min) : String((min + max) / 2); } else if (number < min) { return String(min); } else if (number > max) { return String(max); } return value; } case 'url': // https://html.spec.whatwg.org/multipage/forms.html#url-state-(type=url):value-sanitization-algorithm return value.trim().replace(NEW_LINES_REGEXP, ''); case 'date': // https://html.spec.whatwg.org/multipage/input.html#date-state-(type=date):value-sanitization-algorithm value = this.sanitizeDate(value); return value && this.checkBoundaries(value, input.min, input.max) ? value : ''; case 'datetime-local': { // https://html.spec.whatwg.org/multipage/input.html#local-date-and-time-state-(type=datetime-local):value-sanitization-algorithm const match = value.match(/^(\d\d\d\d)-(\d\d)-(\d\d)[T ](\d\d):(\d\d)(?::(\d\d)(?:\.(\d{1,3}))?)?$/); if (!match) { return ''; } const dateString = this.sanitizeDate(value.slice(0, 10)); let timeString = this.sanitizeTime(value.slice(11)); if (!(dateString && timeString)) { return ''; } // Has seconds so needs to remove trailing zeros if (match[6] !== undefined) { if (timeString.indexOf('.') !== -1) { // Remove unecessary zeros milliseconds timeString = timeString.replace(/(?:\.0*|(\.\d+?)0+)$/, '$1'); } timeString = timeString.replace(/(\d\d:\d\d)(:00)$/, '$1'); } return dateString + 'T' + timeString; } case 'month': // https://html.spec.whatwg.org/multipage/input.html#month-state-(type=month):value-sanitization-algorithm if (!(value.match(/^(\d\d\d\d)-(\d\d)$/) && this.parseMonthComponent(value))) { return ''; } return this.checkBoundaries(value, input.min, input.max) ? value : ''; case 'time': { // https://html.spec.whatwg.org/multipage/input.html#time-state-(type=time):value-sanitization-algorithm value = this.sanitizeTime(value); return value && this.checkBoundaries(value, input.min, input.max) ? value : ''; } case 'week': { // https://html.spec.whatwg.org/multipage/input.html#week-state-(type=week):value-sanitization-algorithm const match = value.match(/^(\d\d\d\d)-W(\d\d)$/); if (!match) { return ''; } const [intY, intW] = parseInts(match.slice(1, 3)); if (intY <= 0 || intW < 1 || intW > 53) { return ''; } // Check date is valid const lastWeek = this.lastIsoWeekOfYear(intY); if (intW < 1 || intW > 52 + lastWeek) { return ''; } if (!this.checkBoundaries(value, input.min, input.max)) { return ''; } return value; } } return value; } /** * Checks if a value is within the boundaries of min and max. * * @param value * @param min * @param max */ static checkBoundaries(value, min, max) { if (min && min > value) { return false; } else if (max && max < value) { return false; } return true; } /** * Parses the month component of a date string. * * @param value */ static parseMonthComponent(value) { const [Y, M] = value.split('-'); const [intY, intM] = parseInts([Y, M]); if (isNaN(intY) || isNaN(intM) || intY <= 0 || intM < 1 || intM > 12) { return ''; } return value; } /** * Sanitizes a date string. * * @param value */ static sanitizeDate(value) { const match = value.match(/^(\d{4})-(\d{2})-(\d{2})$/); if (!match) { return ''; } const month = this.parseMonthComponent(value.slice(0, 7)); if (!month) { return ''; } const [intY, intM, intD] = parseInts(match.slice(1, 4)); if (intD < 1 || intD > 31) { return ''; } // Check date is valid const lastDayOfMonth = new Date(intY, intM, 0).getDate(); if (intD > lastDayOfMonth) { return ''; } return value; } /** * Sanitizes a time string. * * @param value */ static sanitizeTime(value) { const match = value.match(/^(\d{2}):(\d{2})(?::(\d{2}(?:\.(\d{1,3}))?))?$/); if (!match) { return ''; } const [intH, intM] = parseInts(match.slice(1, 3)); const ms = parseFloat(match[3] || '0') * 1000; if (intH > 23 || intM > 59 || ms > 59999) { return ''; } if (ms === 0) { return `${match[1]}:${match[2]}`; } else { return `${match[1]}:${match[2]}${ms >= 10000 ? `:${ms / 1000}` : `:0${ms / 1000}`}`; } } } /** * Returns the last ISO week of a year. * * @param year */ HTMLInputElementValueSanitizer.lastIsoWeekOfYear = (year) => { const date = new Date(+year, 11, 31); const day = (date.getDay() + 6) % 7; date.setDate(date.getDate() - day + 3); const firstThursday = date.getTime(); date.setMonth(0, 1); if (date.getDay() !== 4) { date.setMonth(0, 1 + ((4 - date.getDay() + 7) % 7)); } return 1 + Math.ceil((firstThursday - date.getTime()) / 604800000); }; export default HTMLInputElementValueSanitizer; //# sourceMappingURL=HTMLInputElementValueSanitizer.js.map