tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

DateTime.cpp (28280B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
      2 * vim: set ts=8 sts=2 et sw=2 tw=80:
      3 * This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "vm/DateTime.h"
      8 
      9 #if JS_HAS_INTL_API
     10 #  include "mozilla/intl/ICU4CGlue.h"
     11 #  include "mozilla/intl/TimeZone.h"
     12 #endif
     13 #include "mozilla/ScopeExit.h"
     14 #include "mozilla/Span.h"
     15 #include "mozilla/TextUtils.h"
     16 
     17 #include <algorithm>
     18 #include <cstdlib>
     19 #include <cstring>
     20 #include <string_view>
     21 #include <time.h>
     22 
     23 #if !defined(XP_WIN)
     24 #  include <limits.h>
     25 #  include <unistd.h>
     26 #endif /* !defined(XP_WIN) */
     27 
     28 #if JS_HAS_INTL_API
     29 #  include "builtin/intl/FormatBuffer.h"
     30 #endif
     31 #include "js/AllocPolicy.h"
     32 #include "js/Date.h"
     33 #include "js/GCAPI.h"
     34 #include "js/Utility.h"
     35 #include "js/Vector.h"
     36 #include "threading/ExclusiveData.h"
     37 
     38 #include "util/Text.h"
     39 #include "vm/MutexIDs.h"
     40 #include "vm/Realm.h"
     41 
     42 static bool ComputeLocalTime(time_t local, struct tm* ptm) {
     43  // Neither localtime_s nor localtime_r are required to act as if tzset has
     44  // been called, therefore we need to explicitly call it to ensure any time
     45  // zone changes are correctly picked up.
     46 
     47 #if defined(_WIN32)
     48  _tzset();
     49  return localtime_s(ptm, &local) == 0;
     50 #elif defined(HAVE_LOCALTIME_R)
     51 #  ifndef __wasi__
     52  tzset();
     53 #  endif
     54  return localtime_r(&local, ptm);
     55 #else
     56  struct tm* otm = localtime(&local);
     57  if (!otm) {
     58    return false;
     59  }
     60  *ptm = *otm;
     61  return true;
     62 #endif
     63 }
     64 
     65 static bool ComputeUTCTime(time_t t, struct tm* ptm) {
     66 #if defined(_WIN32)
     67  return gmtime_s(ptm, &t) == 0;
     68 #elif defined(HAVE_GMTIME_R)
     69  return gmtime_r(&t, ptm);
     70 #else
     71  struct tm* otm = gmtime(&t);
     72  if (!otm) {
     73    return false;
     74  }
     75  *ptm = *otm;
     76  return true;
     77 #endif
     78 }
     79 
     80 /*
     81 * Compute the offset in seconds from the current UTC time to the current local
     82 * standard time (i.e. not including any offset due to DST).
     83 *
     84 * Examples:
     85 *
     86 * Suppose we are in California, USA on January 1, 2013 at 04:00 PST (UTC-8, no
     87 * DST in effect), corresponding to 12:00 UTC.  This function would then return
     88 * -8 * SecondsPerHour, or -28800.
     89 *
     90 * Or suppose we are in Berlin, Germany on July 1, 2013 at 17:00 CEST (UTC+2,
     91 * DST in effect), corresponding to 15:00 UTC.  This function would then return
     92 * +1 * SecondsPerHour, or +3600.
     93 */
     94 static int32_t UTCToLocalStandardOffsetSeconds() {
     95  using js::SecondsPerDay;
     96  using js::SecondsPerHour;
     97  using js::SecondsPerMinute;
     98 
     99  // Get the current time.
    100  time_t currentMaybeWithDST = time(nullptr);
    101  if (currentMaybeWithDST == time_t(-1)) {
    102    return 0;
    103  }
    104 
    105  // Break down the current time into its (locally-valued, maybe with DST)
    106  // components.
    107  struct tm local;
    108  if (!ComputeLocalTime(currentMaybeWithDST, &local)) {
    109    return 0;
    110  }
    111 
    112  // Compute a |time_t| corresponding to |local| interpreted without DST.
    113  time_t currentNoDST;
    114  if (local.tm_isdst == 0) {
    115    // If |local| wasn't DST, we can use the same time.
    116    currentNoDST = currentMaybeWithDST;
    117  } else {
    118    // If |local| respected DST, we need a time broken down into components
    119    // ignoring DST.  Turn off DST in the broken-down time.  Create a fresh
    120    // copy of |local|, because mktime() will reset tm_isdst = 1 and will
    121    // adjust tm_hour and tm_hour accordingly.
    122    struct tm localNoDST = local;
    123    localNoDST.tm_isdst = 0;
    124 
    125    // Compute a |time_t t| corresponding to the broken-down time with DST
    126    // off.  This has boundary-condition issues (for about the duration of
    127    // a DST offset) near the time a location moves to a different time
    128    // zone.  But 1) errors will be transient; 2) locations rarely change
    129    // time zone; and 3) in the absence of an API that provides the time
    130    // zone offset directly, this may be the best we can do.
    131    currentNoDST = mktime(&localNoDST);
    132    if (currentNoDST == time_t(-1)) {
    133      return 0;
    134    }
    135  }
    136 
    137  // Break down the time corresponding to the no-DST |local| into UTC-based
    138  // components.
    139  struct tm utc;
    140  if (!ComputeUTCTime(currentNoDST, &utc)) {
    141    return 0;
    142  }
    143 
    144  // Finally, compare the seconds-based components of the local non-DST
    145  // representation and the UTC representation to determine the actual
    146  // difference.
    147  int utc_secs = utc.tm_hour * SecondsPerHour + utc.tm_min * SecondsPerMinute;
    148  int local_secs =
    149      local.tm_hour * SecondsPerHour + local.tm_min * SecondsPerMinute;
    150 
    151  // Same-day?  Just subtract the seconds counts.
    152  if (utc.tm_mday == local.tm_mday) {
    153    return local_secs - utc_secs;
    154  }
    155 
    156  // If we have more UTC seconds, move local seconds into the UTC seconds'
    157  // frame of reference and then subtract.
    158  if (utc_secs > local_secs) {
    159    return (SecondsPerDay + local_secs) - utc_secs;
    160  }
    161 
    162  // Otherwise we have more local seconds, so move the UTC seconds into the
    163  // local seconds' frame of reference and then subtract.
    164  return local_secs - (utc_secs + SecondsPerDay);
    165 }
    166 
    167 void js::DateTimeInfo::internalResetTimeZone(ResetTimeZoneMode mode) {
    168 #if JS_HAS_INTL_API
    169  MOZ_ASSERT(!timeZoneOverride_, "only valid for default instance");
    170 #endif
    171 
    172  // Nothing to do when an update request is already enqueued.
    173  if (timeZoneStatus_ == TimeZoneStatus::NeedsUpdate) {
    174    return;
    175  }
    176 
    177  // Mark the state as needing an update, but defer the actual update until it's
    178  // actually needed to delay any system calls to the last possible moment. This
    179  // is beneficial when this method is called during start-up, because it avoids
    180  // main-thread I/O blocking the process.
    181  if (mode == ResetTimeZoneMode::ResetEvenIfOffsetUnchanged) {
    182    timeZoneStatus_ = TimeZoneStatus::NeedsUpdate;
    183  } else {
    184    timeZoneStatus_ = TimeZoneStatus::UpdateIfChanged;
    185  }
    186 }
    187 
    188 void js::DateTimeInfo::resetState() {
    189  dstRange_.reset();
    190 
    191 #if JS_HAS_INTL_API
    192  utcRange_.reset();
    193  localRange_.reset();
    194 
    195  {
    196    // Tell the analysis the |pFree| function pointer called by uprv_free
    197    // cannot GC.
    198    JS::AutoSuppressGCAnalysis nogc;
    199 
    200    timeZone_ = nullptr;
    201  }
    202 
    203  timeZoneId_ = nullptr;
    204  standardName_ = nullptr;
    205  daylightSavingsName_ = nullptr;
    206 #endif /* JS_HAS_INTL_API */
    207 }
    208 
    209 #if JS_HAS_INTL_API
    210 void js::DateTimeInfo::updateTimeZoneOverride(
    211    RefPtr<JS::TimeZoneString> timeZone) {
    212  MOZ_RELEASE_ASSERT(timeZoneOverride_, "can't change default instance");
    213  MOZ_ASSERT(timeZone);
    214 
    215  // Reset state when time zone override changed.
    216  if (std::strcmp(timeZoneOverride_->chars(), timeZone->chars()) != 0) {
    217    timeZoneOverride_ = timeZone;
    218 
    219    // Reuse the |utcToLocalStandardOffsetSeconds_| as the cache key.
    220    utcToLocalStandardOffsetSeconds_++;
    221 
    222    resetState();
    223  }
    224 }
    225 #endif
    226 
    227 void js::DateTimeInfo::updateTimeZone() {
    228  MOZ_ASSERT(timeZoneStatus_ != TimeZoneStatus::Valid);
    229 #if JS_HAS_INTL_API
    230  MOZ_ASSERT(!timeZoneOverride_, "only valid for default instance");
    231 #endif
    232 
    233  bool updateIfChanged = timeZoneStatus_ == TimeZoneStatus::UpdateIfChanged;
    234 
    235  timeZoneStatus_ = TimeZoneStatus::Valid;
    236 
    237  /*
    238   * The difference between local standard time and UTC will never change for
    239   * a given time zone.
    240   */
    241  int32_t newOffset = UTCToLocalStandardOffsetSeconds();
    242 
    243  if (updateIfChanged && newOffset == utcToLocalStandardOffsetSeconds_) {
    244    return;
    245  }
    246 
    247  utcToLocalStandardOffsetSeconds_ = newOffset;
    248 
    249  resetState();
    250 
    251  // Propagate the time zone change to ICU, too.
    252  {
    253    // Tell the analysis calling into ICU cannot GC.
    254    JS::AutoSuppressGCAnalysis nogc;
    255 
    256    internalResyncICUDefaultTimeZone();
    257  }
    258 }
    259 
    260 js::DateTimeInfo::DateTimeInfo() {
    261  // Set the time zone status into the invalid state, so we compute the actual
    262  // defaults on first access. We don't yet want to initialize neither <ctime>
    263  // nor ICU's time zone classes, because that may cause I/O operations slowing
    264  // down the JS engine initialization, which we're currently in the middle of.
    265  timeZoneStatus_ = TimeZoneStatus::NeedsUpdate;
    266 }
    267 
    268 #if JS_HAS_INTL_API
    269 js::DateTimeInfo::DateTimeInfo(RefPtr<JS::TimeZoneString> timeZone)
    270    : utcToLocalStandardOffsetSeconds_(SecondsPerDay),
    271      timeZoneOverride_(timeZone) {
    272  MOZ_ASSERT(timeZone);
    273 
    274  // |utcToLocalStandardOffsetSeconds_| is initialized to |SecondsPerDay| to
    275  // ensure it's larger than any valid time zone offset as computed by
    276  // |UTCToLocalStandardOffsetSeconds()|.
    277 
    278  // Manually reset all internal state, because |updateTimeZone| is never called
    279  // when a time zone override is used.
    280  resetState();
    281 }
    282 #endif
    283 
    284 js::DateTimeInfo::~DateTimeInfo() = default;
    285 
    286 int64_t js::DateTimeInfo::toClampedSeconds(int64_t milliseconds) {
    287  int64_t seconds = milliseconds / msPerSecond;
    288  int64_t millis = milliseconds % msPerSecond;
    289 
    290  // Round towards the start of time.
    291  if (millis < 0) {
    292    seconds -= 1;
    293  }
    294 
    295  if (seconds > MaxTimeT) {
    296    seconds = MaxTimeT;
    297  } else if (seconds < MinTimeT) {
    298    /* Go ahead a day to make localtime work (does not work with 0). */
    299    seconds = MinTimeT + SecondsPerDay;
    300  }
    301  return seconds;
    302 }
    303 
    304 int32_t js::DateTimeInfo::computeDSTOffsetMilliseconds(int64_t utcSeconds) {
    305  MOZ_ASSERT(utcSeconds >= MinTimeT);
    306  MOZ_ASSERT(utcSeconds <= MaxTimeT);
    307 
    308 #if JS_HAS_INTL_API
    309  int64_t utcMilliseconds = utcSeconds * msPerSecond;
    310 
    311  return timeZone()->GetDSTOffsetMs(utcMilliseconds).unwrapOr(0);
    312 #else
    313  struct tm tm;
    314  if (!ComputeLocalTime(static_cast<time_t>(utcSeconds), &tm)) {
    315    return 0;
    316  }
    317 
    318  // NB: The offset isn't computed correctly when the standard local offset
    319  //     at |utcSeconds| is different from |utcToLocalStandardOffsetSeconds|.
    320  int32_t dayoff =
    321      int32_t((utcSeconds + utcToLocalStandardOffsetSeconds_) % SecondsPerDay);
    322  int32_t tmoff = tm.tm_sec + (tm.tm_min * SecondsPerMinute) +
    323                  (tm.tm_hour * SecondsPerHour);
    324 
    325  int32_t diff = tmoff - dayoff;
    326 
    327  if (diff < 0) {
    328    diff += SecondsPerDay;
    329  } else if (uint32_t(diff) >= SecondsPerDay) {
    330    diff -= SecondsPerDay;
    331  }
    332 
    333  return diff * int32_t(msPerSecond);
    334 #endif /* JS_HAS_INTL_API */
    335 }
    336 
    337 int32_t js::DateTimeInfo::internalGetDSTOffsetMilliseconds(
    338    int64_t utcMilliseconds) {
    339  int64_t utcSeconds = toClampedSeconds(utcMilliseconds);
    340  return getOrComputeValue(dstRange_, utcSeconds,
    341                           &DateTimeInfo::computeDSTOffsetMilliseconds);
    342 }
    343 
    344 int32_t js::DateTimeInfo::getOrComputeValue(RangeCache& range, int64_t seconds,
    345                                            ComputeFn compute) {
    346  range.sanityCheck();
    347 
    348  auto checkSanity =
    349      mozilla::MakeScopeExit([&range]() { range.sanityCheck(); });
    350 
    351  // NB: Be aware of the initial range values when making changes to this
    352  //     code: the first call to this method, with those initial range
    353  //     values, must result in a cache miss.
    354  MOZ_ASSERT(seconds != INT64_MIN);
    355 
    356  if (range.startSeconds <= seconds && seconds <= range.endSeconds) {
    357    return range.offsetMilliseconds;
    358  }
    359 
    360  if (range.oldStartSeconds <= seconds && seconds <= range.oldEndSeconds) {
    361    return range.oldOffsetMilliseconds;
    362  }
    363 
    364  range.oldOffsetMilliseconds = range.offsetMilliseconds;
    365  range.oldStartSeconds = range.startSeconds;
    366  range.oldEndSeconds = range.endSeconds;
    367 
    368  if (range.startSeconds <= seconds) {
    369    int64_t newEndSeconds =
    370        std::min({range.endSeconds + RangeExpansionAmount, MaxTimeT});
    371    if (newEndSeconds >= seconds) {
    372      int32_t endOffsetMilliseconds = (this->*compute)(newEndSeconds);
    373      if (endOffsetMilliseconds == range.offsetMilliseconds) {
    374        range.endSeconds = newEndSeconds;
    375        return range.offsetMilliseconds;
    376      }
    377 
    378      range.offsetMilliseconds = (this->*compute)(seconds);
    379      if (range.offsetMilliseconds == endOffsetMilliseconds) {
    380        range.startSeconds = seconds;
    381        range.endSeconds = newEndSeconds;
    382      } else {
    383        range.endSeconds = seconds;
    384      }
    385      return range.offsetMilliseconds;
    386    }
    387 
    388    range.offsetMilliseconds = (this->*compute)(seconds);
    389    range.startSeconds = range.endSeconds = seconds;
    390    return range.offsetMilliseconds;
    391  }
    392 
    393  int64_t newStartSeconds =
    394      std::max<int64_t>({range.startSeconds - RangeExpansionAmount, MinTimeT});
    395  if (newStartSeconds <= seconds) {
    396    int32_t startOffsetMilliseconds = (this->*compute)(newStartSeconds);
    397    if (startOffsetMilliseconds == range.offsetMilliseconds) {
    398      range.startSeconds = newStartSeconds;
    399      return range.offsetMilliseconds;
    400    }
    401 
    402    range.offsetMilliseconds = (this->*compute)(seconds);
    403    if (range.offsetMilliseconds == startOffsetMilliseconds) {
    404      range.startSeconds = newStartSeconds;
    405      range.endSeconds = seconds;
    406    } else {
    407      range.startSeconds = seconds;
    408    }
    409    return range.offsetMilliseconds;
    410  }
    411 
    412  range.startSeconds = range.endSeconds = seconds;
    413  range.offsetMilliseconds = (this->*compute)(seconds);
    414  return range.offsetMilliseconds;
    415 }
    416 
    417 void js::DateTimeInfo::RangeCache::reset() {
    418  // The initial range values are carefully chosen to result in a cache miss
    419  // on first use given the range of possible values. Be careful to keep
    420  // these values and the caching algorithm in sync!
    421  offsetMilliseconds = 0;
    422  startSeconds = endSeconds = INT64_MIN;
    423  oldOffsetMilliseconds = 0;
    424  oldStartSeconds = oldEndSeconds = INT64_MIN;
    425 
    426  sanityCheck();
    427 }
    428 
    429 void js::DateTimeInfo::RangeCache::sanityCheck() {
    430  auto assertRange = [](int64_t start, int64_t end) {
    431    MOZ_ASSERT(start <= end);
    432    MOZ_ASSERT_IF(start == INT64_MIN, end == INT64_MIN);
    433    MOZ_ASSERT_IF(end == INT64_MIN, start == INT64_MIN);
    434    MOZ_ASSERT_IF(start != INT64_MIN, start >= MinTimeT && end >= MinTimeT);
    435    MOZ_ASSERT_IF(start != INT64_MIN, start <= MaxTimeT && end <= MaxTimeT);
    436  };
    437 
    438  assertRange(startSeconds, endSeconds);
    439  assertRange(oldStartSeconds, oldEndSeconds);
    440 }
    441 
    442 #if JS_HAS_INTL_API
    443 int32_t js::DateTimeInfo::computeUTCOffsetMilliseconds(int64_t localSeconds) {
    444  MOZ_ASSERT(localSeconds >= MinTimeT);
    445  MOZ_ASSERT(localSeconds <= MaxTimeT);
    446 
    447  int64_t localMilliseconds = localSeconds * msPerSecond;
    448 
    449  return timeZone()->GetUTCOffsetMs(localMilliseconds).unwrapOr(0);
    450 }
    451 
    452 int32_t js::DateTimeInfo::computeLocalOffsetMilliseconds(int64_t utcSeconds) {
    453  MOZ_ASSERT(utcSeconds >= MinTimeT);
    454  MOZ_ASSERT(utcSeconds <= MaxTimeT);
    455 
    456  UDate utcMilliseconds = UDate(utcSeconds * msPerSecond);
    457 
    458  return timeZone()->GetOffsetMs(utcMilliseconds).unwrapOr(0);
    459 }
    460 
    461 int32_t js::DateTimeInfo::internalGetOffsetMilliseconds(int64_t milliseconds,
    462                                                        TimeZoneOffset offset) {
    463  int64_t seconds = toClampedSeconds(milliseconds);
    464  return offset == TimeZoneOffset::UTC
    465             ? getOrComputeValue(localRange_, seconds,
    466                                 &DateTimeInfo::computeLocalOffsetMilliseconds)
    467             : getOrComputeValue(utcRange_, seconds,
    468                                 &DateTimeInfo::computeUTCOffsetMilliseconds);
    469 }
    470 
    471 bool js::DateTimeInfo::internalTimeZoneDisplayName(
    472    TimeZoneDisplayNameVector& result, int64_t utcMilliseconds,
    473    const char* locale) {
    474  MOZ_ASSERT(locale != nullptr);
    475 
    476  // Clear any previously cached names when the default locale changed.
    477  if (!locale_ || std::strcmp(locale_.get(), locale) != 0) {
    478    locale_ = DuplicateString(locale);
    479    if (!locale_) {
    480      return false;
    481    }
    482 
    483    standardName_.reset();
    484    daylightSavingsName_.reset();
    485  }
    486 
    487  using DaylightSavings = mozilla::intl::TimeZone::DaylightSavings;
    488 
    489  auto daylightSavings = internalGetDSTOffsetMilliseconds(utcMilliseconds) != 0
    490                             ? DaylightSavings::Yes
    491                             : DaylightSavings::No;
    492 
    493  JS::UniqueTwoByteChars& cachedName = (daylightSavings == DaylightSavings::Yes)
    494                                           ? daylightSavingsName_
    495                                           : standardName_;
    496  if (!cachedName) {
    497    // Retrieve the display name for the given locale.
    498 
    499    intl::FormatBuffer<char16_t, 0, js::SystemAllocPolicy> buffer;
    500    if (timeZone()->GetDisplayName(locale, daylightSavings, buffer).isErr()) {
    501      return false;
    502    }
    503 
    504    cachedName = buffer.extractStringZ();
    505    if (!cachedName) {
    506      return false;
    507    }
    508  }
    509  return result.append(cachedName.get(), js_strlen(cachedName.get()));
    510 }
    511 
    512 static JS::UniqueChars DeflateString(mozilla::Span<const char16_t> chars) {
    513  MOZ_ASSERT(mozilla::IsAscii(chars));
    514 
    515  size_t length = chars.size();
    516  JS::UniqueChars result(js_pod_malloc<char>(length + 1));
    517  if (!result) {
    518    return nullptr;
    519  }
    520 
    521  for (size_t i = 0; i < length; i++) {
    522    result[i] = chars[i];
    523  }
    524  result[length] = '\0';
    525 
    526  return result;
    527 }
    528 
    529 bool js::DateTimeInfo::internalTimeZoneId(TimeZoneIdentifierVector& result) {
    530  if (!timeZoneId_) {
    531    intl::FormatBuffer<char16_t,
    532                       mozilla::intl::TimeZone::TimeZoneIdentifierLength,
    533                       js::SystemAllocPolicy>
    534        buffer;
    535    if (timeZone()->GetId(buffer).isErr()) {
    536      return false;
    537    }
    538 
    539    // ICU returns the time zone identifier as UTF-16, deflate to ASCII.
    540    timeZoneId_ = DeflateString(buffer);
    541    if (!timeZoneId_) {
    542      return false;
    543    }
    544  }
    545  return result.append(timeZoneId_.get(), js_strlen(timeZoneId_.get()));
    546 }
    547 
    548 mozilla::intl::TimeZone* js::DateTimeInfo::timeZone() {
    549  if (!timeZone_) {
    550    mozilla::Maybe<mozilla::Span<const char>> timeZoneOverride;
    551    if (timeZoneOverride_) {
    552      timeZoneOverride =
    553          mozilla::Some(mozilla::MakeStringSpan(timeZoneOverride_->chars()));
    554    }
    555 
    556    auto timeZone = mozilla::intl::TimeZone::TryCreate(timeZoneOverride);
    557 
    558    // If a time zone override was specified, but couldn't be resolved to a
    559    // valid time zone, then we ignore the override request and instead use the
    560    // system default time zone.
    561    if (timeZone.isErr() && timeZoneOverride_) {
    562      timeZone = mozilla::intl::TimeZone::TryCreate();
    563    }
    564 
    565    // Creating the default time zone should never fail. If it should fail
    566    // nonetheless for some reason, just crash because we don't have a way to
    567    // propagate any errors.
    568    MOZ_RELEASE_ASSERT(timeZone.isOk());
    569 
    570    timeZone_ = timeZone.unwrap();
    571    MOZ_ASSERT(timeZone_);
    572  }
    573 
    574  return timeZone_.get();
    575 }
    576 #endif /* JS_HAS_INTL_API */
    577 
    578 /* static */ js::ExclusiveData<js::DateTimeInfo>* js::DateTimeInfo::instance;
    579 
    580 bool js::InitDateTimeState() {
    581  MOZ_ASSERT(!DateTimeInfo::instance, "we should be initializing only once");
    582 
    583  DateTimeInfo::instance =
    584      js_new<ExclusiveData<DateTimeInfo>>(mutexid::DateTimeInfoMutex);
    585  return DateTimeInfo::instance;
    586 }
    587 
    588 /* static */
    589 void js::FinishDateTimeState() {
    590  js_delete(DateTimeInfo::instance);
    591  DateTimeInfo::instance = nullptr;
    592 }
    593 
    594 void js::ResetTimeZoneInternal(ResetTimeZoneMode mode) {
    595  js::DateTimeInfo::resetTimeZone(mode);
    596 }
    597 
    598 JS_PUBLIC_API void JS::ResetTimeZone() {
    599  js::ResetTimeZoneInternal(js::ResetTimeZoneMode::ResetEvenIfOffsetUnchanged);
    600 }
    601 
    602 #if JS_HAS_INTL_API
    603 #  if defined(XP_WIN)
    604 static bool IsOlsonCompatibleWindowsTimeZoneId(std::string_view tz) {
    605  // ICU ignores the TZ environment variable on Windows and instead directly
    606  // invokes Win API functions to retrieve the current time zone. But since
    607  // we're still using the POSIX-derived localtime_s() function on Windows
    608  // and localtime_s() does return a time zone adjusted value based on the
    609  // TZ environment variable, we need to manually adjust the default ICU
    610  // time zone if TZ is set.
    611  //
    612  // Windows supports the following format for TZ: tzn[+|-]hh[:mm[:ss]][dzn]
    613  // where "tzn" is the time zone name for standard time, the time zone
    614  // offset is positive for time zones west of GMT, and "dzn" is the
    615  // optional time zone name when daylight savings are observed. Daylight
    616  // savings are always based on the U.S. daylight saving rules, that means
    617  // for example it's not possible to use "TZ=CET-1CEST" to select the IANA
    618  // time zone "CET".
    619  //
    620  // When comparing this restricted format for TZ to all IANA time zone
    621  // names, the following time zones are in the intersection of what's
    622  // supported by Windows and is also a valid IANA time zone identifier.
    623  //
    624  // Even though the time zone offset is marked as mandatory on MSDN, it
    625  // appears it defaults to zero when omitted. This in turn means we can
    626  // also allow the time zone identifiers "UCT", "UTC", and "GMT".
    627 
    628  static const char* const allowedIds[] = {
    629      // From tzdata's "northamerica" file:
    630      "EST5EDT",
    631      "CST6CDT",
    632      "MST7MDT",
    633      "PST8PDT",
    634 
    635      // From tzdata's "backward" file:
    636      "GMT+0",
    637      "GMT-0",
    638      "GMT0",
    639      "UCT",
    640      "UTC",
    641 
    642      // From tzdata's "etcetera" file:
    643      "GMT",
    644  };
    645  for (const auto& allowedId : allowedIds) {
    646    if (tz == allowedId) {
    647      return true;
    648    }
    649  }
    650  return false;
    651 }
    652 #  else
    653 static std::string_view TZContainsAbsolutePath(std::string_view tzVar) {
    654  // A TZ environment variable may be an absolute path. The path
    655  // format of TZ may begin with a colon. (ICU handles relative paths.)
    656  if (tzVar.length() > 1 && tzVar[0] == ':' && tzVar[1] == '/') {
    657    return tzVar.substr(1);
    658  }
    659  if (tzVar.length() > 0 && tzVar[0] == '/') {
    660    return tzVar;
    661  }
    662  return {};
    663 }
    664 
    665 /**
    666 * Reject the input if it doesn't match the time zone id pattern or legacy time
    667 * zone names.
    668 *
    669 * See <https://github.com/eggert/tz/blob/master/theory.html>.
    670 */
    671 static bool IsTimeZoneId(std::string_view timeZone) {
    672  size_t timeZoneLen = timeZone.length();
    673 
    674  if (timeZoneLen == 0) {
    675    return false;
    676  }
    677 
    678  for (size_t i = 0; i < timeZoneLen; i++) {
    679    char c = timeZone[i];
    680 
    681    // According to theory.html, '.' is allowed in time zone ids, but the
    682    // accompanying zic.c file doesn't allow it. Assume the source file is
    683    // correct and disallow '.' here, too.
    684    if (mozilla::IsAsciiAlphanumeric(c) || c == '_' || c == '-' || c == '+') {
    685      continue;
    686    }
    687 
    688    // Reject leading, trailing, or consecutive '/' characters.
    689    if (c == '/' && i > 0 && i + 1 < timeZoneLen && timeZone[i + 1] != '/') {
    690      continue;
    691    }
    692 
    693    return false;
    694  }
    695 
    696  return true;
    697 }
    698 
    699 /**
    700 * Given a presumptive path |tz| to a zoneinfo time zone file
    701 * (e.g. /etc/localtime), attempt to compute the time zone encoded by that
    702 * path by repeatedly resolving symlinks until a path containing "/zoneinfo/"
    703 * followed by time zone looking components is found. If a symlink is broken,
    704 * symlink-following recurs too deeply, non time zone looking components are
    705 * encountered, or some other error is encountered, then the |result| buffer is
    706 * left empty.
    707 *
    708 * If |result| is set to a non-empty string, it's only guaranteed to have
    709 * certain syntactic validity. It might not actually *be* a time zone name.
    710 *
    711 * If there's an (OOM) error, |false| is returned.
    712 */
    713 static bool ReadTimeZoneLink(std::string_view tz,
    714                             js::TimeZoneIdentifierVector& result) {
    715  MOZ_ASSERT(!tz.empty());
    716  MOZ_ASSERT(result.empty());
    717 
    718  // The resolved link name can have different paths depending on the OS.
    719  // Follow ICU and only search for "/zoneinfo/"; see $ICU/common/putil.cpp.
    720  static constexpr char ZoneInfoPath[] = "/zoneinfo/";
    721  constexpr size_t ZoneInfoPathLength = js_strlen(ZoneInfoPath);
    722 
    723  // Stop following symlinks after a fixed depth, because some common time
    724  // zones are stored in files whose name doesn't match an Olson time zone
    725  // name. For example on Ubuntu, "/usr/share/zoneinfo/America/New_York" is a
    726  // symlink to "/usr/share/zoneinfo/posixrules" and "posixrules" is not an
    727  // Olson time zone name.
    728  // Four hops should be a reasonable limit for most use cases.
    729  constexpr uint32_t FollowDepthLimit = 4;
    730 
    731 #    ifdef PATH_MAX
    732  constexpr size_t PathMax = PATH_MAX;
    733 #    else
    734  constexpr size_t PathMax = 4096;
    735 #    endif
    736  static_assert(PathMax > 0, "PathMax should be larger than zero");
    737 
    738  char linkName[PathMax];
    739  constexpr size_t linkNameLen =
    740      std::size(linkName) - 1;  // -1 to null-terminate.
    741 
    742  // Return if the TZ value is too large.
    743  if (tz.length() > linkNameLen) {
    744    return true;
    745  }
    746 
    747  tz.copy(linkName, tz.length());
    748  linkName[tz.length()] = '\0';
    749 
    750  char linkTarget[PathMax];
    751  constexpr size_t linkTargetLen =
    752      std::size(linkTarget) - 1;  // -1 to null-terminate.
    753 
    754  uint32_t depth = 0;
    755 
    756  // Search until we find "/zoneinfo/" in the link name.
    757  const char* timeZoneWithZoneInfo;
    758  while (!(timeZoneWithZoneInfo = std::strstr(linkName, ZoneInfoPath))) {
    759    // Return if the symlink nesting is too deep.
    760    if (++depth > FollowDepthLimit) {
    761      return true;
    762    }
    763 
    764    // Return on error or if the result was truncated.
    765    ssize_t slen = readlink(linkName, linkTarget, linkTargetLen);
    766    if (slen < 0 || size_t(slen) >= linkTargetLen) {
    767      return true;
    768    }
    769 
    770    // Ensure linkTarget is null-terminated. (readlink may not necessarily
    771    // null-terminate the string.)
    772    size_t len = size_t(slen);
    773    linkTarget[len] = '\0';
    774 
    775    // If the target is absolute, continue with that.
    776    if (linkTarget[0] == '/') {
    777      std::strcpy(linkName, linkTarget);
    778      continue;
    779    }
    780 
    781    // If the target is relative, it must be resolved against either the
    782    // directory the link was in, or against the current working directory.
    783    char* separator = std::strrchr(linkName, '/');
    784 
    785    // If the link name is just something like "foo", resolve linkTarget
    786    // against the current working directory.
    787    if (!separator) {
    788      std::strcpy(linkName, linkTarget);
    789      continue;
    790    }
    791 
    792    // Remove everything after the final path separator in linkName.
    793    separator[1] = '\0';
    794 
    795    // Return if the concatenated path name is too large.
    796    if (std::strlen(linkName) + len > linkNameLen) {
    797      return true;
    798    }
    799 
    800    // Keep it simple and just concatenate the path names.
    801    std::strcat(linkName, linkTarget);
    802  }
    803 
    804  std::string_view timeZone(timeZoneWithZoneInfo + ZoneInfoPathLength);
    805  if (!IsTimeZoneId(timeZone)) {
    806    return true;
    807  }
    808  return result.append(timeZone.data(), timeZone.length());
    809 }
    810 #  endif /* defined(XP_WIN) */
    811 #endif   /* JS_HAS_INTL_API */
    812 
    813 void js::DateTimeInfo::internalResyncICUDefaultTimeZone() {
    814 #if JS_HAS_INTL_API
    815  if (const char* tzenv = std::getenv("TZ")) {
    816    std::string_view tz(tzenv);
    817 
    818    mozilla::Span<const char> tzid;
    819 
    820 #  if defined(XP_WIN)
    821    // If TZ is set and its value is valid under Windows' and IANA's time zone
    822    // identifier rules, update the ICU default time zone to use this value.
    823    if (IsOlsonCompatibleWindowsTimeZoneId(tz)) {
    824      tzid = mozilla::Span(tz.data(), tz.length());
    825    } else {
    826      // If |tz| isn't a supported time zone identifier, use the default Windows
    827      // time zone for ICU.
    828      // TODO: Handle invalid time zone identifiers (bug 342068).
    829    }
    830 #  else
    831    // The TZ environment variable allows both absolute and relative paths,
    832    // optionally beginning with a colon (':'). (Relative paths, without the
    833    // colon, are just Olson time zone names.)  We need to handle absolute paths
    834    // ourselves, including handling that they might be symlinks.
    835    // <https://unicode-org.atlassian.net/browse/ICU-13694>
    836    TimeZoneIdentifierVector tzidVector;
    837    std::string_view tzlink = TZContainsAbsolutePath(tz);
    838    if (!tzlink.empty()) {
    839      if (!ReadTimeZoneLink(tzlink, tzidVector)) {
    840        // Ignore OOM.
    841        return;
    842      }
    843      tzid = tzidVector;
    844    }
    845 
    846 #    ifdef ANDROID
    847    // ICU ignores the TZ environment variable on Android. If it doesn't contain
    848    // an absolute path, try to parse it as a time zone name.
    849    else if (IsTimeZoneId(tz)) {
    850      tzid = mozilla::Span(tz.data(), tz.length());
    851    }
    852 #    endif
    853 #  endif /* defined(XP_WIN) */
    854 
    855    if (!tzid.empty()) {
    856      auto result = mozilla::intl::TimeZone::SetDefaultTimeZone(tzid);
    857      if (result.isErr()) {
    858        // Intentionally ignore any errors, because we don't have a good way to
    859        // report errors from this function.
    860        return;
    861      }
    862 
    863      // Return if the default time zone was successfully updated.
    864      if (result.unwrap()) {
    865        return;
    866      }
    867 
    868      // If SetDefaultTimeZone() succeeded, but the default time zone wasn't
    869      // changed, proceed to set the default time zone from the host time zone.
    870    }
    871  }
    872 
    873  // Intentionally ignore any errors, because we don't have a good way to report
    874  // errors from this function.
    875  (void)mozilla::intl::TimeZone::SetDefaultTimeZoneFromHostTimeZone();
    876 #endif
    877 }