Bug 1842773 - Part 32: Allow constructing growable SharedArrayBuffers. r=sfink
[gecko.git] / js / src / vm / DateTime.cpp
blob21ecc2e9f50a16357ace3320335d31a4929fc146
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/. */
7 #include "vm/DateTime.h"
9 #if JS_HAS_INTL_API
10 # include "mozilla/intl/TimeZone.h"
11 #endif
12 #include "mozilla/ScopeExit.h"
13 #include "mozilla/TextUtils.h"
15 #include <algorithm>
16 #include <cstdlib>
17 #include <cstring>
18 #include <iterator>
19 #include <string_view>
20 #include <time.h>
22 #if !defined(XP_WIN)
23 # include <limits.h>
24 # include <unistd.h>
25 #endif /* !defined(XP_WIN) */
27 #if JS_HAS_INTL_API
28 # include "builtin/intl/FormatBuffer.h"
29 #endif
30 #include "js/AllocPolicy.h"
31 #include "js/Date.h"
32 #include "js/GCAPI.h"
33 #include "js/Vector.h"
34 #include "threading/ExclusiveData.h"
36 #include "util/Text.h"
37 #include "vm/MutexIDs.h"
38 #include "vm/Realm.h"
40 /* static */
41 js::DateTimeInfo::ForceUTC js::DateTimeInfo::forceUTC(JS::Realm* realm) {
42 return realm->creationOptions().forceUTC() ? DateTimeInfo::ForceUTC::Yes
43 : DateTimeInfo::ForceUTC::No;
46 static bool ComputeLocalTime(time_t local, struct tm* ptm) {
47 // Neither localtime_s nor localtime_r are required to act as if tzset has
48 // been called, therefore we need to explicitly call it to ensure any time
49 // zone changes are correctly picked up.
51 #if defined(_WIN32)
52 _tzset();
53 return localtime_s(ptm, &local) == 0;
54 #elif defined(HAVE_LOCALTIME_R)
55 # ifndef __wasi__
56 tzset();
57 # endif
58 return localtime_r(&local, ptm);
59 #else
60 struct tm* otm = localtime(&local);
61 if (!otm) {
62 return false;
64 *ptm = *otm;
65 return true;
66 #endif
69 static bool ComputeUTCTime(time_t t, struct tm* ptm) {
70 #if defined(_WIN32)
71 return gmtime_s(ptm, &t) == 0;
72 #elif defined(HAVE_GMTIME_R)
73 return gmtime_r(&t, ptm);
74 #else
75 struct tm* otm = gmtime(&t);
76 if (!otm) {
77 return false;
79 *ptm = *otm;
80 return true;
81 #endif
85 * Compute the offset in seconds from the current UTC time to the current local
86 * standard time (i.e. not including any offset due to DST).
88 * Examples:
90 * Suppose we are in California, USA on January 1, 2013 at 04:00 PST (UTC-8, no
91 * DST in effect), corresponding to 12:00 UTC. This function would then return
92 * -8 * SecondsPerHour, or -28800.
94 * Or suppose we are in Berlin, Germany on July 1, 2013 at 17:00 CEST (UTC+2,
95 * DST in effect), corresponding to 15:00 UTC. This function would then return
96 * +1 * SecondsPerHour, or +3600.
98 static int32_t UTCToLocalStandardOffsetSeconds() {
99 using js::SecondsPerDay;
100 using js::SecondsPerHour;
101 using js::SecondsPerMinute;
103 // Get the current time.
104 time_t currentMaybeWithDST = time(nullptr);
105 if (currentMaybeWithDST == time_t(-1)) {
106 return 0;
109 // Break down the current time into its (locally-valued, maybe with DST)
110 // components.
111 struct tm local;
112 if (!ComputeLocalTime(currentMaybeWithDST, &local)) {
113 return 0;
116 // Compute a |time_t| corresponding to |local| interpreted without DST.
117 time_t currentNoDST;
118 if (local.tm_isdst == 0) {
119 // If |local| wasn't DST, we can use the same time.
120 currentNoDST = currentMaybeWithDST;
121 } else {
122 // If |local| respected DST, we need a time broken down into components
123 // ignoring DST. Turn off DST in the broken-down time. Create a fresh
124 // copy of |local|, because mktime() will reset tm_isdst = 1 and will
125 // adjust tm_hour and tm_hour accordingly.
126 struct tm localNoDST = local;
127 localNoDST.tm_isdst = 0;
129 // Compute a |time_t t| corresponding to the broken-down time with DST
130 // off. This has boundary-condition issues (for about the duration of
131 // a DST offset) near the time a location moves to a different time
132 // zone. But 1) errors will be transient; 2) locations rarely change
133 // time zone; and 3) in the absence of an API that provides the time
134 // zone offset directly, this may be the best we can do.
135 currentNoDST = mktime(&localNoDST);
136 if (currentNoDST == time_t(-1)) {
137 return 0;
141 // Break down the time corresponding to the no-DST |local| into UTC-based
142 // components.
143 struct tm utc;
144 if (!ComputeUTCTime(currentNoDST, &utc)) {
145 return 0;
148 // Finally, compare the seconds-based components of the local non-DST
149 // representation and the UTC representation to determine the actual
150 // difference.
151 int utc_secs =
152 utc.tm_hour * SecondsPerHour + utc.tm_min * int(SecondsPerMinute);
153 int local_secs =
154 local.tm_hour * SecondsPerHour + local.tm_min * int(SecondsPerMinute);
156 // Same-day? Just subtract the seconds counts.
157 if (utc.tm_mday == local.tm_mday) {
158 return local_secs - utc_secs;
161 // If we have more UTC seconds, move local seconds into the UTC seconds'
162 // frame of reference and then subtract.
163 if (utc_secs > local_secs) {
164 return (SecondsPerDay + local_secs) - utc_secs;
167 // Otherwise we have more local seconds, so move the UTC seconds into the
168 // local seconds' frame of reference and then subtract.
169 return local_secs - (utc_secs + SecondsPerDay);
172 void js::DateTimeInfo::internalResetTimeZone(ResetTimeZoneMode mode) {
173 // Nothing to do when an update request is already enqueued.
174 if (timeZoneStatus_ == TimeZoneStatus::NeedsUpdate) {
175 return;
178 // Mark the state as needing an update, but defer the actual update until it's
179 // actually needed to delay any system calls to the last possible moment. This
180 // is beneficial when this method is called during start-up, because it avoids
181 // main-thread I/O blocking the process.
182 if (mode == ResetTimeZoneMode::ResetEvenIfOffsetUnchanged) {
183 timeZoneStatus_ = TimeZoneStatus::NeedsUpdate;
184 } else {
185 timeZoneStatus_ = TimeZoneStatus::UpdateIfChanged;
189 void js::DateTimeInfo::updateTimeZone() {
190 MOZ_ASSERT(timeZoneStatus_ != TimeZoneStatus::Valid);
192 bool updateIfChanged = timeZoneStatus_ == TimeZoneStatus::UpdateIfChanged;
194 timeZoneStatus_ = TimeZoneStatus::Valid;
197 * The difference between local standard time and UTC will never change for
198 * a given time zone.
200 int32_t newOffset = UTCToLocalStandardOffsetSeconds();
202 if (updateIfChanged && newOffset == utcToLocalStandardOffsetSeconds_) {
203 return;
206 utcToLocalStandardOffsetSeconds_ = newOffset;
208 dstRange_.reset();
210 #if JS_HAS_INTL_API
211 utcRange_.reset();
212 localRange_.reset();
215 // Tell the analysis the |pFree| function pointer called by uprv_free
216 // cannot GC.
217 JS::AutoSuppressGCAnalysis nogc;
219 timeZone_ = nullptr;
222 standardName_ = nullptr;
223 daylightSavingsName_ = nullptr;
224 #endif /* JS_HAS_INTL_API */
226 // Propagate the time zone change to ICU, too.
228 // Tell the analysis calling into ICU cannot GC.
229 JS::AutoSuppressGCAnalysis nogc;
231 internalResyncICUDefaultTimeZone();
235 js::DateTimeInfo::DateTimeInfo(bool forceUTC) : forceUTC_(forceUTC) {
236 // Set the time zone status into the invalid state, so we compute the actual
237 // defaults on first access. We don't yet want to initialize neither <ctime>
238 // nor ICU's time zone classes, because that may cause I/O operations slowing
239 // down the JS engine initialization, which we're currently in the middle of.
240 timeZoneStatus_ = TimeZoneStatus::NeedsUpdate;
243 js::DateTimeInfo::~DateTimeInfo() = default;
245 int64_t js::DateTimeInfo::toClampedSeconds(int64_t milliseconds) {
246 int64_t seconds = milliseconds / int64_t(msPerSecond);
247 int64_t millis = milliseconds % int64_t(msPerSecond);
249 // Round towards the start of time.
250 if (millis < 0) {
251 seconds -= 1;
254 if (seconds > MaxTimeT) {
255 seconds = MaxTimeT;
256 } else if (seconds < MinTimeT) {
257 /* Go ahead a day to make localtime work (does not work with 0). */
258 seconds = SecondsPerDay;
260 return seconds;
263 int32_t js::DateTimeInfo::computeDSTOffsetMilliseconds(int64_t utcSeconds) {
264 MOZ_ASSERT(utcSeconds >= MinTimeT);
265 MOZ_ASSERT(utcSeconds <= MaxTimeT);
267 #if JS_HAS_INTL_API
268 int64_t utcMilliseconds = utcSeconds * int64_t(msPerSecond);
270 return timeZone()->GetDSTOffsetMs(utcMilliseconds).unwrapOr(0);
271 #else
272 struct tm tm;
273 if (!ComputeLocalTime(static_cast<time_t>(utcSeconds), &tm)) {
274 return 0;
277 // NB: The offset isn't computed correctly when the standard local offset
278 // at |utcSeconds| is different from |utcToLocalStandardOffsetSeconds|.
279 int32_t dayoff =
280 int32_t((utcSeconds + utcToLocalStandardOffsetSeconds_) % SecondsPerDay);
281 int32_t tmoff = tm.tm_sec + (tm.tm_min * SecondsPerMinute) +
282 (tm.tm_hour * SecondsPerHour);
284 int32_t diff = tmoff - dayoff;
286 if (diff < 0) {
287 diff += SecondsPerDay;
288 } else if (uint32_t(diff) >= SecondsPerDay) {
289 diff -= SecondsPerDay;
292 return diff * int32_t(msPerSecond);
293 #endif /* JS_HAS_INTL_API */
296 int32_t js::DateTimeInfo::internalGetDSTOffsetMilliseconds(
297 int64_t utcMilliseconds) {
298 int64_t utcSeconds = toClampedSeconds(utcMilliseconds);
299 return getOrComputeValue(dstRange_, utcSeconds,
300 &DateTimeInfo::computeDSTOffsetMilliseconds);
303 int32_t js::DateTimeInfo::getOrComputeValue(RangeCache& range, int64_t seconds,
304 ComputeFn compute) {
305 range.sanityCheck();
307 auto checkSanity =
308 mozilla::MakeScopeExit([&range]() { range.sanityCheck(); });
310 // NB: Be aware of the initial range values when making changes to this
311 // code: the first call to this method, with those initial range
312 // values, must result in a cache miss.
313 MOZ_ASSERT(seconds != INT64_MIN);
315 if (range.startSeconds <= seconds && seconds <= range.endSeconds) {
316 return range.offsetMilliseconds;
319 if (range.oldStartSeconds <= seconds && seconds <= range.oldEndSeconds) {
320 return range.oldOffsetMilliseconds;
323 range.oldOffsetMilliseconds = range.offsetMilliseconds;
324 range.oldStartSeconds = range.startSeconds;
325 range.oldEndSeconds = range.endSeconds;
327 if (range.startSeconds <= seconds) {
328 int64_t newEndSeconds =
329 std::min({range.endSeconds + RangeExpansionAmount, MaxTimeT});
330 if (newEndSeconds >= seconds) {
331 int32_t endOffsetMilliseconds = (this->*compute)(newEndSeconds);
332 if (endOffsetMilliseconds == range.offsetMilliseconds) {
333 range.endSeconds = newEndSeconds;
334 return range.offsetMilliseconds;
337 range.offsetMilliseconds = (this->*compute)(seconds);
338 if (range.offsetMilliseconds == endOffsetMilliseconds) {
339 range.startSeconds = seconds;
340 range.endSeconds = newEndSeconds;
341 } else {
342 range.endSeconds = seconds;
344 return range.offsetMilliseconds;
347 range.offsetMilliseconds = (this->*compute)(seconds);
348 range.startSeconds = range.endSeconds = seconds;
349 return range.offsetMilliseconds;
352 int64_t newStartSeconds =
353 std::max<int64_t>({range.startSeconds - RangeExpansionAmount, MinTimeT});
354 if (newStartSeconds <= seconds) {
355 int32_t startOffsetMilliseconds = (this->*compute)(newStartSeconds);
356 if (startOffsetMilliseconds == range.offsetMilliseconds) {
357 range.startSeconds = newStartSeconds;
358 return range.offsetMilliseconds;
361 range.offsetMilliseconds = (this->*compute)(seconds);
362 if (range.offsetMilliseconds == startOffsetMilliseconds) {
363 range.startSeconds = newStartSeconds;
364 range.endSeconds = seconds;
365 } else {
366 range.startSeconds = seconds;
368 return range.offsetMilliseconds;
371 range.startSeconds = range.endSeconds = seconds;
372 range.offsetMilliseconds = (this->*compute)(seconds);
373 return range.offsetMilliseconds;
376 void js::DateTimeInfo::RangeCache::reset() {
377 // The initial range values are carefully chosen to result in a cache miss
378 // on first use given the range of possible values. Be careful to keep
379 // these values and the caching algorithm in sync!
380 offsetMilliseconds = 0;
381 startSeconds = endSeconds = INT64_MIN;
382 oldOffsetMilliseconds = 0;
383 oldStartSeconds = oldEndSeconds = INT64_MIN;
385 sanityCheck();
388 void js::DateTimeInfo::RangeCache::sanityCheck() {
389 auto assertRange = [](int64_t start, int64_t end) {
390 MOZ_ASSERT(start <= end);
391 MOZ_ASSERT_IF(start == INT64_MIN, end == INT64_MIN);
392 MOZ_ASSERT_IF(end == INT64_MIN, start == INT64_MIN);
393 MOZ_ASSERT_IF(start != INT64_MIN, start >= MinTimeT && end >= MinTimeT);
394 MOZ_ASSERT_IF(start != INT64_MIN, start <= MaxTimeT && end <= MaxTimeT);
397 assertRange(startSeconds, endSeconds);
398 assertRange(oldStartSeconds, oldEndSeconds);
401 #if JS_HAS_INTL_API
402 int32_t js::DateTimeInfo::computeUTCOffsetMilliseconds(int64_t localSeconds) {
403 MOZ_ASSERT(localSeconds >= MinTimeT);
404 MOZ_ASSERT(localSeconds <= MaxTimeT);
406 int64_t localMilliseconds = localSeconds * int64_t(msPerSecond);
408 return timeZone()->GetUTCOffsetMs(localMilliseconds).unwrapOr(0);
411 int32_t js::DateTimeInfo::computeLocalOffsetMilliseconds(int64_t utcSeconds) {
412 MOZ_ASSERT(utcSeconds >= MinTimeT);
413 MOZ_ASSERT(utcSeconds <= MaxTimeT);
415 UDate utcMilliseconds = UDate(utcSeconds * int64_t(msPerSecond));
417 return timeZone()->GetOffsetMs(utcMilliseconds).unwrapOr(0);
420 int32_t js::DateTimeInfo::internalGetOffsetMilliseconds(int64_t milliseconds,
421 TimeZoneOffset offset) {
422 int64_t seconds = toClampedSeconds(milliseconds);
423 return offset == TimeZoneOffset::UTC
424 ? getOrComputeValue(localRange_, seconds,
425 &DateTimeInfo::computeLocalOffsetMilliseconds)
426 : getOrComputeValue(utcRange_, seconds,
427 &DateTimeInfo::computeUTCOffsetMilliseconds);
430 bool js::DateTimeInfo::internalTimeZoneDisplayName(char16_t* buf, size_t buflen,
431 int64_t utcMilliseconds,
432 const char* locale) {
433 MOZ_ASSERT(buf != nullptr);
434 MOZ_ASSERT(buflen > 0);
435 MOZ_ASSERT(locale != nullptr);
437 // Clear any previously cached names when the default locale changed.
438 if (!locale_ || std::strcmp(locale_.get(), locale) != 0) {
439 locale_ = DuplicateString(locale);
440 if (!locale_) {
441 return false;
444 standardName_.reset();
445 daylightSavingsName_.reset();
448 using DaylightSavings = mozilla::intl::TimeZone::DaylightSavings;
450 auto daylightSavings = internalGetDSTOffsetMilliseconds(utcMilliseconds) != 0
451 ? DaylightSavings::Yes
452 : DaylightSavings::No;
454 JS::UniqueTwoByteChars& cachedName = (daylightSavings == DaylightSavings::Yes)
455 ? daylightSavingsName_
456 : standardName_;
457 if (!cachedName) {
458 // Retrieve the display name for the given locale.
460 intl::FormatBuffer<char16_t, 0, js::SystemAllocPolicy> buffer;
461 if (timeZone()->GetDisplayName(locale, daylightSavings, buffer).isErr()) {
462 return false;
465 cachedName = buffer.extractStringZ();
466 if (!cachedName) {
467 return false;
471 // Return an empty string if the display name doesn't fit into the buffer.
472 size_t length = js_strlen(cachedName.get());
473 if (length < buflen) {
474 std::copy(cachedName.get(), cachedName.get() + length, buf);
475 } else {
476 length = 0;
479 buf[length] = '\0';
480 return true;
483 mozilla::intl::TimeZone* js::DateTimeInfo::timeZone() {
484 if (!timeZone_) {
485 // For resist finger printing mode we always use the UTC time zone.
486 mozilla::Maybe<mozilla::Span<const char16_t>> timeZoneOverride;
487 if (forceUTC_) {
488 timeZoneOverride = mozilla::Some(mozilla::MakeStringSpan(u"UTC"));
491 auto timeZone = mozilla::intl::TimeZone::TryCreate(timeZoneOverride);
493 // Creating the default or UTC time zone should never fail. If it should
494 // fail nonetheless for some reason, just crash because we don't have a way
495 // to propagate any errors.
496 MOZ_RELEASE_ASSERT(timeZone.isOk());
498 timeZone_ = timeZone.unwrap();
499 MOZ_ASSERT(timeZone_);
502 return timeZone_.get();
504 #endif /* JS_HAS_INTL_API */
506 /* static */ js::ExclusiveData<js::DateTimeInfo>* js::DateTimeInfo::instance;
507 /* static */ js::ExclusiveData<js::DateTimeInfo>* js::DateTimeInfo::instanceUTC;
509 bool js::InitDateTimeState() {
510 MOZ_ASSERT(!DateTimeInfo::instance && !DateTimeInfo::instanceUTC,
511 "we should be initializing only once");
513 DateTimeInfo::instance =
514 js_new<ExclusiveData<DateTimeInfo>>(mutexid::DateTimeInfoMutex, false);
515 DateTimeInfo::instanceUTC =
516 js_new<ExclusiveData<DateTimeInfo>>(mutexid::DateTimeInfoMutex, true);
517 return DateTimeInfo::instance && DateTimeInfo::instanceUTC;
520 /* static */
521 void js::FinishDateTimeState() {
522 js_delete(DateTimeInfo::instance);
523 DateTimeInfo::instance = nullptr;
526 void js::ResetTimeZoneInternal(ResetTimeZoneMode mode) {
527 js::DateTimeInfo::resetTimeZone(mode);
530 JS_PUBLIC_API void JS::ResetTimeZone() {
531 js::ResetTimeZoneInternal(js::ResetTimeZoneMode::ResetEvenIfOffsetUnchanged);
534 #if JS_HAS_INTL_API
535 # if defined(XP_WIN)
536 static bool IsOlsonCompatibleWindowsTimeZoneId(std::string_view tz) {
537 // ICU ignores the TZ environment variable on Windows and instead directly
538 // invokes Win API functions to retrieve the current time zone. But since
539 // we're still using the POSIX-derived localtime_s() function on Windows
540 // and localtime_s() does return a time zone adjusted value based on the
541 // TZ environment variable, we need to manually adjust the default ICU
542 // time zone if TZ is set.
544 // Windows supports the following format for TZ: tzn[+|-]hh[:mm[:ss]][dzn]
545 // where "tzn" is the time zone name for standard time, the time zone
546 // offset is positive for time zones west of GMT, and "dzn" is the
547 // optional time zone name when daylight savings are observed. Daylight
548 // savings are always based on the U.S. daylight saving rules, that means
549 // for example it's not possible to use "TZ=CET-1CEST" to select the IANA
550 // time zone "CET".
552 // When comparing this restricted format for TZ to all IANA time zone
553 // names, the following time zones are in the intersection of what's
554 // supported by Windows and is also a valid IANA time zone identifier.
556 // Even though the time zone offset is marked as mandatory on MSDN, it
557 // appears it defaults to zero when omitted. This in turn means we can
558 // also allow the time zone identifiers "UCT", "UTC", and "GMT".
560 static const char* const allowedIds[] = {
561 // From tzdata's "northamerica" file:
562 "EST5EDT",
563 "CST6CDT",
564 "MST7MDT",
565 "PST8PDT",
567 // From tzdata's "backward" file:
568 "GMT+0",
569 "GMT-0",
570 "GMT0",
571 "UCT",
572 "UTC",
574 // From tzdata's "etcetera" file:
575 "GMT",
577 for (const auto& allowedId : allowedIds) {
578 if (tz == allowedId) {
579 return true;
582 return false;
584 # else
585 static std::string_view TZContainsAbsolutePath(std::string_view tzVar) {
586 // A TZ environment variable may be an absolute path. The path
587 // format of TZ may begin with a colon. (ICU handles relative paths.)
588 if (tzVar.length() > 1 && tzVar[0] == ':' && tzVar[1] == '/') {
589 return tzVar.substr(1);
591 if (tzVar.length() > 0 && tzVar[0] == '/') {
592 return tzVar;
594 return {};
598 * Reject the input if it doesn't match the time zone id pattern or legacy time
599 * zone names.
601 * See <https://github.com/eggert/tz/blob/master/theory.html>.
603 static bool IsTimeZoneId(std::string_view timeZone) {
604 size_t timeZoneLen = timeZone.length();
606 if (timeZoneLen == 0) {
607 return false;
610 for (size_t i = 0; i < timeZoneLen; i++) {
611 char c = timeZone[i];
613 // According to theory.html, '.' is allowed in time zone ids, but the
614 // accompanying zic.c file doesn't allow it. Assume the source file is
615 // correct and disallow '.' here, too.
616 if (mozilla::IsAsciiAlphanumeric(c) || c == '_' || c == '-' || c == '+') {
617 continue;
620 // Reject leading, trailing, or consecutive '/' characters.
621 if (c == '/' && i > 0 && i + 1 < timeZoneLen && timeZone[i + 1] != '/') {
622 continue;
625 return false;
628 return true;
631 using TimeZoneIdentifierVector =
632 js::Vector<char, mozilla::intl::TimeZone::TimeZoneIdentifierLength,
633 js::SystemAllocPolicy>;
636 * Given a presumptive path |tz| to a zoneinfo time zone file
637 * (e.g. /etc/localtime), attempt to compute the time zone encoded by that
638 * path by repeatedly resolving symlinks until a path containing "/zoneinfo/"
639 * followed by time zone looking components is found. If a symlink is broken,
640 * symlink-following recurs too deeply, non time zone looking components are
641 * encountered, or some other error is encountered, then the |result| buffer is
642 * left empty.
644 * If |result| is set to a non-empty string, it's only guaranteed to have
645 * certain syntactic validity. It might not actually *be* a time zone name.
647 * If there's an (OOM) error, |false| is returned.
649 static bool ReadTimeZoneLink(std::string_view tz,
650 TimeZoneIdentifierVector& result) {
651 MOZ_ASSERT(!tz.empty());
652 MOZ_ASSERT(result.empty());
654 // The resolved link name can have different paths depending on the OS.
655 // Follow ICU and only search for "/zoneinfo/"; see $ICU/common/putil.cpp.
656 static constexpr char ZoneInfoPath[] = "/zoneinfo/";
657 constexpr size_t ZoneInfoPathLength = js_strlen(ZoneInfoPath);
659 // Stop following symlinks after a fixed depth, because some common time
660 // zones are stored in files whose name doesn't match an Olson time zone
661 // name. For example on Ubuntu, "/usr/share/zoneinfo/America/New_York" is a
662 // symlink to "/usr/share/zoneinfo/posixrules" and "posixrules" is not an
663 // Olson time zone name.
664 // Four hops should be a reasonable limit for most use cases.
665 constexpr uint32_t FollowDepthLimit = 4;
667 # ifdef PATH_MAX
668 constexpr size_t PathMax = PATH_MAX;
669 # else
670 constexpr size_t PathMax = 4096;
671 # endif
672 static_assert(PathMax > 0, "PathMax should be larger than zero");
674 char linkName[PathMax];
675 constexpr size_t linkNameLen =
676 std::size(linkName) - 1; // -1 to null-terminate.
678 // Return if the TZ value is too large.
679 if (tz.length() > linkNameLen) {
680 return true;
683 tz.copy(linkName, tz.length());
684 linkName[tz.length()] = '\0';
686 char linkTarget[PathMax];
687 constexpr size_t linkTargetLen =
688 std::size(linkTarget) - 1; // -1 to null-terminate.
690 uint32_t depth = 0;
692 // Search until we find "/zoneinfo/" in the link name.
693 const char* timeZoneWithZoneInfo;
694 while (!(timeZoneWithZoneInfo = std::strstr(linkName, ZoneInfoPath))) {
695 // Return if the symlink nesting is too deep.
696 if (++depth > FollowDepthLimit) {
697 return true;
700 // Return on error or if the result was truncated.
701 ssize_t slen = readlink(linkName, linkTarget, linkTargetLen);
702 if (slen < 0 || size_t(slen) >= linkTargetLen) {
703 return true;
706 // Ensure linkTarget is null-terminated. (readlink may not necessarily
707 // null-terminate the string.)
708 size_t len = size_t(slen);
709 linkTarget[len] = '\0';
711 // If the target is absolute, continue with that.
712 if (linkTarget[0] == '/') {
713 std::strcpy(linkName, linkTarget);
714 continue;
717 // If the target is relative, it must be resolved against either the
718 // directory the link was in, or against the current working directory.
719 char* separator = std::strrchr(linkName, '/');
721 // If the link name is just something like "foo", resolve linkTarget
722 // against the current working directory.
723 if (!separator) {
724 std::strcpy(linkName, linkTarget);
725 continue;
728 // Remove everything after the final path separator in linkName.
729 separator[1] = '\0';
731 // Return if the concatenated path name is too large.
732 if (std::strlen(linkName) + len > linkNameLen) {
733 return true;
736 // Keep it simple and just concatenate the path names.
737 std::strcat(linkName, linkTarget);
740 std::string_view timeZone(timeZoneWithZoneInfo + ZoneInfoPathLength);
741 if (!IsTimeZoneId(timeZone)) {
742 return true;
744 return result.append(timeZone.data(), timeZone.length());
746 # endif /* defined(XP_WIN) */
747 #endif /* JS_HAS_INTL_API */
749 void js::DateTimeInfo::internalResyncICUDefaultTimeZone() {
750 #if JS_HAS_INTL_API
751 // In the future we should not be setting a default ICU time zone at all,
752 // instead all accesses should go through the appropriate DateTimeInfo
753 // instance depending on the resist fingerprinting status. For now we return
754 // early to prevent overwriting the default time zone with the UTC time zone
755 // used by RFP.
756 if (forceUTC_) {
757 return;
760 if (const char* tzenv = std::getenv("TZ")) {
761 std::string_view tz(tzenv);
763 mozilla::Span<const char> tzid;
765 # if defined(XP_WIN)
766 // If TZ is set and its value is valid under Windows' and IANA's time zone
767 // identifier rules, update the ICU default time zone to use this value.
768 if (IsOlsonCompatibleWindowsTimeZoneId(tz)) {
769 tzid = mozilla::Span(tz.data(), tz.length());
770 } else {
771 // If |tz| isn't a supported time zone identifier, use the default Windows
772 // time zone for ICU.
773 // TODO: Handle invalid time zone identifiers (bug 342068).
775 # else
776 // The TZ environment variable allows both absolute and relative paths,
777 // optionally beginning with a colon (':'). (Relative paths, without the
778 // colon, are just Olson time zone names.) We need to handle absolute paths
779 // ourselves, including handling that they might be symlinks.
780 // <https://unicode-org.atlassian.net/browse/ICU-13694>
781 TimeZoneIdentifierVector tzidVector;
782 std::string_view tzlink = TZContainsAbsolutePath(tz);
783 if (!tzlink.empty()) {
784 if (!ReadTimeZoneLink(tzlink, tzidVector)) {
785 // Ignore OOM.
786 return;
788 tzid = tzidVector;
791 # ifdef ANDROID
792 // ICU ignores the TZ environment variable on Android. If it doesn't contain
793 // an absolute path, try to parse it as a time zone name.
794 else if (IsTimeZoneId(tz)) {
795 tzid = mozilla::Span(tz.data(), tz.length());
797 # endif
798 # endif /* defined(XP_WIN) */
800 if (!tzid.empty()) {
801 auto result = mozilla::intl::TimeZone::SetDefaultTimeZone(tzid);
802 if (result.isErr()) {
803 // Intentionally ignore any errors, because we don't have a good way to
804 // report errors from this function.
805 return;
808 // Return if the default time zone was successfully updated.
809 if (result.unwrap()) {
810 return;
813 // If SetDefaultTimeZone() succeeded, but the default time zone wasn't
814 // changed, proceed to set the default time zone from the host time zone.
818 // Intentionally ignore any errors, because we don't have a good way to report
819 // errors from this function.
820 (void)mozilla::intl::TimeZone::SetDefaultTimeZoneFromHostTimeZone();
821 #endif