1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
|
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview A utility for decoding timestamps strings into JS Date objects.
* This is needed for showing the SAML password expiry notifications.
* Timestamps are allowed to be sent to us in a variety of formats, since SAML
* administrators may not have the ability to convert between formats at their
* end. This class doesn't need to be informed which format the timestamp is in,
* since the different allowed formats don't tend to overlap in practice.
*
* The supported formats are NTFS filetimes, Unix time (in seconds or ms),
* and ISO 8601.
*/
/** @const @private {number} Maximum length of a valid timestamp. */
const MAX_SANE_LENGTH = 30;
/** @const @private {!Date} The earliest date considered sane. */
const MIN_SANE_DATE = new Date('1980-01-01 UTC');
/** @const @private {!Date} The latest date considered sane. */
const MAX_SANE_DATE = new Date('10000-01-01 UTC');
/** @const @private {!Date} Epoch for Windows NTFS FILETIME timestamps. */
const NTFS_EPOCH = new Date('1601-01-01 UTC');
/** @const @private {!RegExp} Pattern to match integers. */
const INTEGER_PATTERN = /^-?\d+$/;
/**
* Pattern to match ISO 8601 dates / times. Rejects other text-based timestamp
* formats (eg '01-02-03') since they cannot be parsed in a consistent way.
* @const @private {!RegExp}
*/
const ISO_8601_PATTERN = /^\d\d\d\d-\d\d-\d\d(T|$)/;
/**
* Decode a timestamp string that is in one of the supported formats.
* @param {string} str A timestamp formatted as a string.
* @return {?Date} A valid decoded timestamp, or null.
*/
export function decodeTimestamp(str) {
str = str.trim();
if (str.length === 0 || str.length > MAX_SANE_LENGTH) {
return null;
}
if (INTEGER_PATTERN.test(str)) {
return decodeIntegerTimestamp(parseInt(str, 10));
} else if (ISO_8601_PATTERN.test(str)) {
return decodeIso8601(str);
}
return null;
}
/**
* Decode a timestamp that is in one of the supported integer formats:
* NTFS filetime, Unix time (s), or Unix time (ms).
* @param {number} num An integer timestamp.
* @return {?Date} A valid decoded timestamp, or null.
*/
function decodeIntegerTimestamp(num) {
// We don't ask which format integer timestamps are in, because we can guess
// confidently by choosing the decode function that gives a sane result.
let result;
for (const decodeFunc
of [decodeNtfsFiletime, decodeUnixMilliseconds,
decodeUnixSeconds]) {
result = decodeFunc(num);
if (result && result >= MIN_SANE_DATE && result <= MAX_SANE_DATE) {
return result;
}
}
// For dates that fall outside the sane range, we cannot guess which format
// was used, but at least we can tell if the result should be in the far
// past or the far future, and return a result that is roughly equivalent.
return result && result < MIN_SANE_DATE ?
new Date(MIN_SANE_DATE) // Copy-before-return protects these two
:
new Date(MAX_SANE_DATE); // constants (since Date is mutable).
}
/**
* Decode a NTFS filetime timestamp with an epoch of year 1601.
* @param {number} hundredsOfNs Each tick measures 100 nanoseconds.
* @return {?Date}
*/
function decodeNtfsFiletime(hundredsOfNs) {
return createValidDate(NTFS_EPOCH.valueOf() + (hundredsOfNs / 10000));
}
/**
* Decode a Unix timestamp which is counting milliseconds since 1970.
* @param {number} milliseconds
* @return {?Date}
*/
function decodeUnixMilliseconds(milliseconds) {
return createValidDate(milliseconds);
}
/**
* Decode a Unix timestamp which is counting seconds since 1970.
* @param {number} seconds
* @return {?Date}
*/
function decodeUnixSeconds(seconds) {
return createValidDate(seconds * 1000);
}
/**
* Decodes a timestamp string that is in ISO 8601 format.
* @param {string} str
* @return {?Date}
*/
function decodeIso8601(str) {
// If no timezone is specified, appending 'Z' means we will parse as UTC.
// (If a timezone is already specified, appending 'Z' is simply invalid.)
// Using UTC as a default is predictable, using local time is unpredictable.
return createValidDate(str + 'Z') || createValidDate(str);
}
/**
* Constructs a date and returns it if it is valid, otherwise returns null.
* @param {*} arg Argument for constructing the date.
* @return {?Date} A valid date object, or null.
*/
function createValidDate(arg) {
const date = new Date(arg);
return isNaN(date) ? null : date;
}
|