|  | // Copyright 2019 The Chromium Authors. All rights reserved. | 
|  | // 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. | 
|  | */ | 
|  |  | 
|  | cr.define('samlTimestamps', function() { | 
|  | 'use strict'; | 
|  |  | 
|  | /** @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; | 
|  | } | 
|  |  | 
|  | // #cr_define_end | 
|  | // Public functions: | 
|  | return {decodeTimestamp: decodeTimestamp}; | 
|  | }); |