CVE-2026-74950
Overview
Background
- Mark-of-the-Web (MotW)
- the Windows
Zone.Identifieralternate data stream (ADS) attached to a downloaded file, recording aZoneIdplusHostUrl/ReferrerUrlso the OS can warn before opening internet-sourced files. - `WinUtils::MaybeWriteFileZoneId`
- the Gecko routine (widget/windows) that writes that ADS for a completed download, given the source URL and referrer.
- Security zones
ZONE_MY_COMPUTER(0)/ZONE_INTRANET(1)/ZONE_TRUSTED(2)/ZONE_INTERNET(3)/ZONE_RESTRICTED(4); the writer persists the URL/referrer only forZONE_INTERNETand above.- Userpass in a URL
- embedded
user:password@hostcredentials that must never be persisted to disk metadata.
Root Cause Analysis
MaybeWriteFileZoneId records the originating HostUrl and ReferrerUrl into the file’s persistent Zone.Identifier ADS. Several inputs are sensitive and must be normalized before they are written to disk: a source or referrer URL can carry embedded user:pw@ credentials, a referrer can use a non-web scheme that should not be disclosed, and a private-browsing download must not persist its originating URL or referrer at all. The defect is that these cases were not consistently sanitized, so credentials or private-session origin URLs could be written into a file’s Mark-of-the-Web metadata that survives on disk and is readable by any later process — an information-disclosure.
The fix normalizes the values before writing: user:pw@ is stripped from both the HostUrl and ReferrerUrl, a referrer whose scheme is not permitted is omitted, non-HTTP sources fall back to about:internet, sub-Internet zones skip the write entirely, and private browsing writes neither URL nor referrer. This commit lands the gtest regression suite that pins every one of those behaviors; the corresponding source change to MaybeWriteFileZoneIdSync shipped in a sibling commit.
Zone.Identifier ADS; the fix sanitizes it first — stripping embedded credentials, dropping disallowed referrer schemes, and writing nothing in private browsing.Attack Path
- Craft a credentialed source
an attacker gets the victim to download a file whose source or referrer URL embeds
user:pw@credentials (or the download occurs in a private-browsing session). - ADS persists the secret
pre-fix,
MaybeWriteFileZoneIdwrites the rawHostUrl/ReferrerUrl(credentials, or the private-session origin) into the file’s persistentZone.Identifierstream. - Read it back any local process, or an exfiltration path that reads the ADS, recovers the embedded credentials or the private-browsing origin from the saved file’s metadata.
Impact Assessment
Zone.Identifier ADS, readable by other local processes. Rated moderate: it requires the victim to download a file under the relevant conditions, and it leaks metadata rather than granting code execution. Windows only.Changed Functions
| Function | Change | Notes |
|---|---|---|
ifwidget/windows/tests/gtest/TestWinUtils.cpp |
modified | |
SpinEventLoopUntilwidget/windows/tests/gtest/TestWinUtils.cpp |
modified | |
forwidget/windows/tests/gtest/TestWinUtils.cpp |
modified |
Files Changed
widget/windows/tests/gtest/TestWinUtils.cpp
Audit Directions
- Secrets into persistent metadataaudit every path that writes a URL or referrer into on-disk metadata (ADS, xattrs, sidecar files) for
user:pw@stripping and private-browsing suppression. - Zone/scheme gatingverify writers that record provenance only do so for the intended zones and permitted schemes, matching the sub-Internet-skip and referrer-scheme rules pinned here.
- Correlation follow-upthis CVE’s stored fix commit contains only the regression test; locate the sibling source commit that changed
MaybeWriteFileZoneIdSyncto confirm the exact sanitization logic.
Patch
diff --git a/widget/windows/tests/gtest/TestWinUtils.cpp b/widget/windows/tests/gtest/TestWinUtils.cpp
index 849ff8bd8ba..144fc1bace5 100644
--- a/widget/windows/tests/gtest/TestWinUtils.cpp
+++ b/widget/windows/tests/gtest/TestWinUtils.cpp
@@ -5,6 +5,8 @@
#include "gtest/gtest.h"
#include "WinUtils.h"
+#include "nsStreamUtils.h"
+
using namespace mozilla;
using namespace mozilla::widget;
@@ -23,3 +25,253 @@ TEST(WinUtils, Regions)
ASSERT_EQ(region, WinUtils::ConvertHRGNToRegion(rgn))
<< "Region should round-trip";
}
+
+/*****************************************************************************/
+
+// https://learn.microsoft.com/en-us/dotnet/api/system.security.securityzone?view=netframework-4.8.1
+constexpr uint32_t ZONE_MY_COMPUTER = 0;
+constexpr uint32_t ZONE_INTRANET = 1;
+constexpr uint32_t ZONE_TRUSTED = 2;
+constexpr uint32_t ZONE_INTERNET = 3;
+constexpr uint32_t ZONE_RESTRICTED = 4;
+
+// ZoneIdKey overrides applied by MaybeWriteFileZoneId.
+constexpr auto kFallbackUrl = "about:internet"_ns;
+constexpr auto kFallbackReferrer = ""_ns;
+
+struct FileTestData {
+ // Set to Nothing() to indicate no URL written. Used in private browsing.
+ Maybe<nsCString> mSourceUrl;
+ // Set to Nothing() to indicate no referrer written (private browsing) or
+ // just no referrer. It is not valid to set the referrer but no URL.
+ Maybe<nsCString> mReferrerSpec;
+ // Set to Nothing() to check the case for file-does-not-exist.
+ Maybe<uint32_t> mExpectedZone;
+ // Overrides for the HostUrl / ReferrerUrl values written to the ADS.
+ // Nothing() -> expected equals the input
+ // Some("") -> the line is expected to be omitted
+ // Some(s) -> the line is expected to be exactly "<key>=<s>\r\n"
+ Maybe<nsCString> mExpectedHostUrl = Nothing();
+ Maybe<nsCString> mExpectedReferrer = Nothing();
+};
+
+#define TestSuccess(_operation) \
+ { \
+ nsresult rv = _operation; \
+ if (NS_FAILED(rv)) { \
+ ADD_FAILURE() << #_operation << " | Failed with result " \
+ << static_cast<uint32_t>(rv) << std::endl; \
+ return rv; \
+ } \
+ }
+
+// Reads the file's Zone.Identifier ADS and asserts it matches what
+// MaybeWriteFileZoneIdSync would have written for the given test data.
+static nsresult VerifyZoneIdentifier(nsIFile* aSaveFile,
+ const FileTestData& aData) {
+ nsString savePath;
+ TestSuccess(aSaveFile->GetPath(savePath));
+ auto isSlash = [](char aCh) { return aCh == '/' || aCh == '\\'; };
+ bool isUNC =
+ savePath.Length() >= 2 && isSlash(savePath[0]) && isSlash(savePath[1]);
+ nsString adsPath = isUNC ? u"\\\\?\\UNC\\"_ns + Substring(savePath, 2)
+ : u"\\\\?\\"_ns + savePath;
+ adsPath += u":Zone.Identifier"_ns;
+
+ nsCOMPtr<nsIFile> adsFile;
+ TestSuccess(NS_NewLocalFile(adsPath, getter_AddRefs(adsFile)));
+
+ bool expectWritten =
+ aData.mExpectedZone && *aData.mExpectedZone >= ZONE_INTERNET;
+
+ nsCOMPtr<nsIInputStream> stream;
+ nsresult openRv = NS_NewLocalFileInputStream(getter_AddRefs(stream), adsFile);
+
+ if (!expectWritten) {
+ // ADS should not have been written.
+ EXPECT_EQ(openRv, NS_ERROR_FILE_NOT_FOUND)
+ << "Expected no Zone.Identifier ADS";
+ return NS_OK;
+ }
+
+ TestSuccess(openRv);
+
+ nsAutoCString actual;
+ TestSuccess(NS_ConsumeStream(stream, UINT32_MAX, actual));
+
+ nsCString expectedHost = aData.mExpectedHostUrl.valueOr(aData.mSourceUrl.valueOr(""_ns));
+ nsCString expectedReferrer =
+ aData.mExpectedReferrer.valueOr(aData.mReferrerSpec.valueOr(""_ns));
+
+ nsAutoCString expected;
+ expected.AppendLiteral("[ZoneTransfer]\r\n");
+ expected.AppendPrintf("ZoneId=%u\r\n", *aData.mExpectedZone);
+ if (!expectedReferrer.IsEmpty()) {
+ expected.AppendLiteral("ReferrerUrl=");
+ expected.Append(expectedReferrer);
+ expected.AppendLiteral("\r\n");
+ }
+ if (!expectedHost.IsEmpty()) {
+ expected.AppendLiteral("HostUrl=");
+ expected.Append(expectedHost);
+ expected.AppendLiteral("\r\n");
+ }
+
+ EXPECT_STREQ(expected.get(), actual.get())
+ << "Zone.Identifier contents did not match expected";
+ return NS_OK;
+}
+
+static nsresult SetAndTestFileZone(const FileTestData& aData) {
+ SCOPED_TRACE(testing::Message()
+ << "source="
+ << (aData.mSourceUrl ? aData.mSourceUrl->get() : "(none)")
+ << " referrer="
+ << (aData.mReferrerSpec ? aData.mReferrerSpec->get() : "(none)")
+ << " expectedZone="
+ << (aData.mExpectedZone ? std::to_string(*aData.mExpectedZone)
+ : std::string("(none)")));
+
+ nsCOMPtr<nsIFile> tmp;
+ TestSuccess(NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmp)));
+ MOZ_ASSERT(tmp);
+
+ nsID id = nsID::GenerateUUID();
+ TestSuccess(tmp->AppendNative("TestZones-"_ns +
+ nsCString(id.ToString().get()) + ".txt"_ns));
+ if (aData.mExpectedZone) {
+ TestSuccess(tmp->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0666));
+ } else {
+ bool exists;
+ TestSuccess(tmp->Exists(&exists));
+ EXPECT_FALSE(exists);
+ }
+
+ bool done = false;
+ WinUtils::MaybeWriteFileZoneId(tmp, aData.mSourceUrl, aData.mReferrerSpec)
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [&done, &aData, tmp](bool aResult) {
+ if (!aData.mExpectedZone) {
+ // File-doesn't-exist case: MaybeWriteFileZoneIdSync returns
+ // Ok(false) (it does not consider that an error).
+ EXPECT_FALSE(aResult);
+ } else if (*aData.mExpectedZone >= ZONE_INTERNET) {
+ EXPECT_TRUE(aResult);
+ } else {
+ // Below-Internet zones (My Computer, Intranet, Trusted) skip the
+ // write per WinUtils::MaybeWriteFileZoneIdSync.
+ EXPECT_FALSE(aResult);
+ }
+ VerifyZoneIdentifier(tmp, aData);
+ done = true;
+ },
+ [&done](nsresult aErr) {
+ ADD_FAILURE() << "Unexpected promise rejection: "
+ << static_cast<uint32_t>(aErr);
+ done = true;
+ });
+
+ SpinEventLoopUntil("write zone test"_ns, [&done]() { return done; });
+
+ // Cleanup. Skip the file-doesn't-exist rows -- we never created the file.
+ if (aData.mExpectedZone) {
+ uint32_t dummy;
+ TestSuccess(tmp->Remove(false, &dummy));
+ }
+ return NS_OK;
+}
+
+TEST(WinUtils, MaybeWriteFileZoneId)
+{
+ const FileTestData fileTestDatas[] = {
+ // Valid URL, empty referrer (matches the JS code's "" when no referrer).
+ {Some("http://www.example.org/"_ns), Some(""_ns), Some(ZONE_INTERNET)},
+ {Some("about:blank"_ns), Some(""_ns), Some(ZONE_INTERNET),
+ Some(kFallbackUrl)},
+ {Some("about:srcdoc"_ns), Some(""_ns), Some(ZONE_INTERNET),
+ Some(kFallbackUrl)},
+ {Some("data:text/html,foo"_ns), Some(""_ns), Some(ZONE_INTERNET),
+ Some(kFallbackUrl)},
+ {Some("http://localhost:8080/"_ns), Some(""_ns), Some(ZONE_INTRANET)},
+ {Some("http://127.0.0.1/"_ns), Some(""_ns), Some(ZONE_INTERNET)},
+ {Some("file://C:/foo"_ns), Some(""_ns), Some(ZONE_MY_COMPUTER),
+ Some(kFallbackUrl)},
+ {Some("file:///C:/foo"_ns), Some(""_ns), Some(ZONE_MY_COMPUTER),
+ Some(kFallbackUrl)},
+ {Some("file://server/share/foo"_ns), Some(""_ns), Some(ZONE_INTRANET),
+ Some(kFallbackUrl)},
+ {Some("file:////server/share/foo"_ns), Some(""_ns), Some(ZONE_INTRANET),
+ Some(kFallbackUrl)},
+
+ // Valid URL, Nothing referrer. (Edge case, not actually used in Gecko.)
+ {Some("http://www.example.org/"_ns), Nothing(), Some(ZONE_INTERNET)},
+ {Some("about:blank"_ns), Nothing(), Some(ZONE_INTERNET),
+ Some(kFallbackUrl)},
+ {Some("about:srcdoc"_ns), Nothing(), Some(ZONE_INTERNET),
+ Some(kFallbackUrl)},
+ {Some("data:text/html,foo"_ns), Nothing(), Some(ZONE_INTERNET),
+ Some(kFallbackUrl)},
+ {Some("http://localhost:8080/"_ns), Nothing(), Some(ZONE_INTRANET)},
+ {Some("http://127.0.0.1/"_ns), Nothing(), Some(ZONE_INTERNET)},
+ {Some("file://C:/foo"_ns), Nothing(), Some(ZONE_MY_COMPUTER),
+ Some(kFallbackUrl)},
+ {Some("file:///C:/foo"_ns), Nothing(), Some(ZONE_MY_COMPUTER),
+ Some(kFallbackUrl)},
+ {Some("file://server/share/foo"_ns), Nothing(), Some(ZONE_INTRANET),
+ Some(kFallbackUrl)},
+ {Some("file:////server/share/foo"_ns), Nothing(), Some(ZONE_INTRANET),
+ Some(kFallbackUrl)},
+
+ // Valid URL and referrer.
+ {Some("http://www.example.org/"_ns), Some("http://www.example.com/"_ns),
+ Some(ZONE_INTERNET)},
+ {Some("about:blank"_ns), Some("http://www.example.com/"_ns),
+ Some(ZONE_INTERNET), Some(kFallbackUrl)},
+ {Some("about:srcdoc"_ns), Some("http://www.example.com/"_ns),
+ Some(ZONE_INTERNET), Some(kFallbackUrl)},
+ {Some("data:text/html,foo"_ns), Some("http://www.example.com/"_ns),
+ Some(ZONE_INTERNET), Some(kFallbackUrl)},
+ {Some("http://localhost:8080/"_ns), Some("http://www.example.com/"_ns),
+ Some(ZONE_INTRANET)},
+ {Some("http://127.0.0.1/"_ns), Some("http://www.example.com/"_ns),
+ Some(ZONE_INTERNET)},
+ {Some("file://C:/foo"_ns), Some("http://www.example.com/"_ns),
+ Some(ZONE_MY_COMPUTER), Some(kFallbackUrl)},
+ {Some("file:///C:/foo"_ns), Some("http://www.example.com/"_ns),
+ Some(ZONE_MY_COMPUTER), Some(kFallbackUrl)},
+ {Some("file://server/share/foo"_ns), Some("http://www.example.com/"_ns),
+ Some(ZONE_INTRANET), Some(kFallbackUrl)},
+ {Some("file:////server/share/foo"_ns), Some("http://www.example.com/"_ns),
+ Some(ZONE_INTRANET), Some(kFallbackUrl)},
+
+ // Userpass on the source URL is stripped from URL and referrer.
+ {Some("http://user:pw@www.example.org/"_ns), Some(""_ns),
+ Some(ZONE_INTERNET), Some("http://www.example.org/"_ns)},
+ {Some("http://www.example.org/"_ns),
+ Some("http://user:pw@www.example.com/"_ns), Some(ZONE_INTERNET),
+ Nothing() /* mExpectedHostUrl */,
+ Some("http://www.example.com/"_ns) /* mExpectedReferrer */},
+
+ // Non-permitted scheme on the referrer => ReferrerUrl line omitted.
+ {Some("http://www.example.org/"_ns), Some("about:blank"_ns),
+ Some(ZONE_INTERNET), Nothing() /* mExpectedHostUrl */,
+ Some(kFallbackReferrer) /* mExpectedReferrer */},
+
+ // No URL or referrer (private browsing -- zone=internet).
+ {Nothing(), Nothing(), Some(ZONE_INTERNET)},
+
+ // File to set zone for does not exist.
+ {Some("http://www.example.org/"_ns), Some("http://www.example.com/"_ns),
+ Nothing()},
+ {Nothing(), Nothing(), Nothing()},
+
+ // Not expected from nsIFile but included for completeness.
+ {Some("\\\\server\\share\\foo"_ns), Some(""_ns), Some(ZONE_INTRANET),
+ Some(kFallbackUrl)},
+ };
+
+ for (auto& data : fileTestDatas) {
+ SetAndTestFileZone(data);
+ }
+}
Regression Test / PoC
diff --git a/widget/windows/tests/gtest/TestWinUtils.cpp b/widget/windows/tests/gtest/TestWinUtils.cpp
index 849ff8bd8ba..144fc1bace5 100644
--- a/widget/windows/tests/gtest/TestWinUtils.cpp
+++ b/widget/windows/tests/gtest/TestWinUtils.cpp
@@ -5,6 +5,8 @@
#include "gtest/gtest.h"
#include "WinUtils.h"
+#include "nsStreamUtils.h"
+
using namespace mozilla;
using namespace mozilla::widget;
@@ -23,3 +25,253 @@ TEST(WinUtils, Regions)
ASSERT_EQ(region, WinUtils::ConvertHRGNToRegion(rgn))
<< "Region should round-trip";
}
+
+/*****************************************************************************/
+
+// https://learn.microsoft.com/en-us/dotnet/api/system.security.securityzone?view=netframework-4.8.1
+constexpr uint32_t ZONE_MY_COMPUTER = 0;
+constexpr uint32_t ZONE_INTRANET = 1;
+constexpr uint32_t ZONE_TRUSTED = 2;
+constexpr uint32_t ZONE_INTERNET = 3;
+constexpr uint32_t ZONE_RESTRICTED = 4;
+
+// ZoneIdKey overrides applied by MaybeWriteFileZoneId.
+constexpr auto kFallbackUrl = "about:internet"_ns;
+constexpr auto kFallbackReferrer = ""_ns;
+
+struct FileTestData {
+ // Set to Nothing() to indicate no URL written. Used in private browsing.
+ Maybe<nsCString> mSourceUrl;
+ // Set to Nothing() to indicate no referrer written (private browsing) or
+ // just no referrer. It is not valid to set the referrer but no URL.
+ Maybe<nsCString> mReferrerSpec;
+ // Set to Nothing() to check the case for file-does-not-exist.
+ Maybe<uint32_t> mExpectedZone;
+ // Overrides for the HostUrl / ReferrerUrl values written to the ADS.
+ // Nothing() -> expected equals the input
+ // Some("") -> the line is expected to be omitted
+ // Some(s) -> the line is expected to be exactly "<key>=<s>\r\n"
+ Maybe<nsCString> mExpectedHostUrl = Nothing();
+ Maybe<nsCString> mExpectedReferrer = Nothing();
+};
+
+#define TestSuccess(_operation) \
+ { \
+ nsresult rv = _operation; \
+ if (NS_FAILED(rv)) { \
+ ADD_FAILURE() << #_operation << " | Failed with result " \
+ << static_cast<uint32_t>(rv) << std::endl; \
+ return rv; \
+ } \
+ }
+
+// Reads the file's Zone.Identifier ADS and asserts it matches what
+// MaybeWriteFileZoneIdSync would have written for the given test data.
+static nsresult VerifyZoneIdentifier(nsIFile* aSaveFile,
+ const FileTestData& aData) {
+ nsString savePath;
+ TestSuccess(aSaveFile->GetPath(savePath));
+ auto isSlash = [](char aCh) { return aCh == '/' || aCh == '\\'; };
+ bool isUNC =
+ savePath.Length() >= 2 && isSlash(savePath[0]) && isSlash(savePath[1]);
+ nsString adsPath = isUNC ? u"\\\\?\\UNC\\"_ns + Substring(savePath, 2)
+ : u"\\\\?\\"_ns + savePath;
+ adsPath += u":Zone.Identifier"_ns;
+
+ nsCOMPtr<nsIFile> adsFile;
+ TestSuccess(NS_NewLocalFile(adsPath, getter_AddRefs(adsFile)));
+
+ bool expectWritten =
+ aData.mExpectedZone && *aData.mExpectedZone >= ZONE_INTERNET;
+
+ nsCOMPtr<nsIInputStream> stream;
+ nsresult openRv = NS_NewLocalFileInputStream(getter_AddRefs(stream), adsFile);
+
+ if (!expectWritten) {
+ // ADS should not have been written.
+ EXPECT_EQ(openRv, NS_ERROR_FILE_NOT_FOUND)
+ << "Expected no Zone.Identifier ADS";
+ return NS_OK;
+ }
+
+ TestSuccess(openRv);
+
+ nsAutoCString actual;
+ TestSuccess(NS_ConsumeStream(stream, UINT32_MAX, actual));
+
+ nsCString expectedHost = aData.mExpectedHostUrl.valueOr(aData.mSourceUrl.valueOr(""_ns));
+ nsCString expectedReferrer =
+ aData.mExpectedReferrer.valueOr(aData.mReferrerSpec.valueOr(""_ns));
+
+ nsAutoCString expected;
+ expected.AppendLiteral("[ZoneTransfer]\r\n");
+ expected.AppendPrintf("ZoneId=%u\r\n", *aData.mExpectedZone);
+ if (!expectedReferrer.IsEmpty()) {
+ expected.AppendLiteral("ReferrerUrl=");
+ expected.Append(expectedReferrer);
+ expected.AppendLiteral("\r\n");
+ }
+ if (!expectedHost.IsEmpty()) {
+ expected.AppendLiteral("HostUrl=");
+ expected.Append(expectedHost);
+ expected.AppendLiteral("\r\n");
+ }
+
+ EXPECT_STREQ(expected.get(), actual.get())
+ << "Zone.Identifier contents did not match expected";
+ return NS_OK;
+}
+
+static nsresult SetAndTestFileZone(const FileTestData& aData) {
+ SCOPED_TRACE(testing::Message()
+ << "source="
+ << (aData.mSourceUrl ? aData.mSourceUrl->get() : "(none)")
+ << " referrer="
+ << (aData.mReferrerSpec ? aData.mReferrerSpec->get() : "(none)")
+ << " expectedZone="
+ << (aData.mExpectedZone ? std::to_string(*aData.mExpectedZone)
+ : std::string("(none)")));
+
+ nsCOMPtr<nsIFile> tmp;
+ TestSuccess(NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmp)));
+ MOZ_ASSERT(tmp);
+
+ nsID id = nsID::GenerateUUID();
+ TestSuccess(tmp->AppendNative("TestZones-"_ns +
+ nsCString(id.ToString().get()) + ".txt"_ns));
+ if (aData.mExpectedZone) {
+ TestSuccess(tmp->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0666));
+ } else {
+ bool exists;
+ TestSuccess(tmp->Exists(&exists));
+ EXPECT_FALSE(exists);
+ }
+
+ bool done = false;
+ WinUtils::MaybeWriteFileZoneId(tmp, aData.mSourceUrl, aData.mReferrerSpec)
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [&done, &aData, tmp](bool aResult) {
+ if (!aData.mExpectedZone) {
+ // File-doesn't-exist case: MaybeWriteFileZoneIdSync returns
+ // Ok(false) (it does not consider that an error).
+ EXPECT_FALSE(aResult);
+ } else if (*aData.mExpectedZone >= ZONE_INTERNET) {
+ EXPECT_TRUE(aResult);
+ } else {
+ // Below-Internet zones (My Computer, Intranet, Trusted) skip the
+ // write per WinUtils::MaybeWriteFileZoneIdSync.
+ EXPECT_FALSE(aResult);
+ }
+ VerifyZoneIdentifier(tmp, aData);
+ done = true;
+ },
+ [&done](nsresult aErr) {
+ ADD_FAILURE() << "Unexpected promise rejection: "
+ << static_cast<uint32_t>(aErr);
+ done = true;
+ });
+
+ SpinEventLoopUntil("write zone test"_ns, [&done]() { return done; });
+
+ // Cleanup. Skip the file-doesn't-exist rows -- we never created the file.
+ if (aData.mExpectedZone) {
+ uint32_t dummy;
+ TestSuccess(tmp->Remove(false, &dummy));
+ }
+ return NS_OK;
+}
+
+TEST(WinUtils, MaybeWriteFileZoneId)
+{
+ const FileTestData fileTestDatas[] = {
+ // Valid URL, empty referrer (matches the JS code's "" when no referrer).
+ {Some("http://www.example.org/"_ns), Some(""_ns), Some(ZONE_INTERNET)},
+ {Some("about:blank"_ns), Some(""_ns), Some(ZONE_INTERNET),
+ Some(kFallbackUrl)},
+ {Some("about:srcdoc"_ns), Some(""_ns), Some(ZONE_INTERNET),
+ Some(kFallbackUrl)},
+ {Some("data:text/html,foo"_ns), Some(""_ns), Some(ZONE_INTERNET),
+ Some(kFallbackUrl)},
+ {Some("http://localhost:8080/"_ns), Some(""_ns), Some(ZONE_INTRANET)},
+ {Some("http://127.0.0.1/"_ns), Some(""_ns), Some(ZONE_INTERNET)},
+ {Some("file://C:/foo"_ns), Some(""_ns), Some(ZONE_MY_COMPUTER),
+ Some(kFallbackUrl)},
+ {Some("file:///C:/foo"_ns), Some(""_ns), Some(ZONE_MY_COMPUTER),
+ Some(kFallbackUrl)},
+ {Some("file://server/share/foo"_ns), Some(""_ns), Some(ZONE_INTRANET),
+ Some(kFallbackUrl)},
+ {Some("file:////server/share/foo"_ns), Some(""_ns), Some(ZONE_INTRANET),
+ Some(kFallbackUrl)},
+
+ // Valid URL, Nothing referrer. (Edge case, not actually used in Gecko.)
+ {Some("http://www.example.org/"_ns), Nothing(), Some(ZONE_INTERNET)},
+ {Some("about:blank"_ns), Nothing(), Some(ZONE_INTERNET),
+ Some(kFallbackUrl)},
+ {Some("about:srcdoc"_ns), Nothing(), Some(ZONE_INTERNET),
+ Some(kFallbackUrl)},
+ {Some("data:text/html,foo"_ns), Nothing(), Some(ZONE_INTERNET),
+ Some(kFallbackUrl)},
+ {Some("http://localhost:8080/"_ns), Nothing(), Some(ZONE_INTRANET)},
+ {Some("http://127.0.0.1/"_ns), Nothing(), Some(ZONE_INTERNET)},
+ {Some("file://C:/foo"_ns), Nothing(), Some(ZONE_MY_COMPUTER),
+ Some(kFallbackUrl)},
+ {Some("file:///C:/foo"_ns), Nothing(), Some(ZONE_MY_COMPUTER),
+ Some(kFallbackUrl)},
+ {Some("file://server/share/foo"_ns), Nothing(), Some(ZONE_INTRANET),
+ Some(kFallbackUrl)},
+ {Some("file:////server/share/foo"_ns), Nothing(), Some(ZONE_INTRANET),
+ Some(kFallbackUrl)},
+
+ // Valid URL and referrer.
+ {Some("http://www.example.org/"_ns), Some("http://www.example.com/"_ns),
+ Some(ZONE_INTERNET)},
+ {Some("about:blank"_ns), Some("http://www.example.com/"_ns),
+ Some(ZONE_INTERNET), Some(kFallbackUrl)},
+ {Some("about:srcdoc"_ns), Some("http://www.example.com/"_ns),
+ Some(ZONE_INTERNET), Some(kFallbackUrl)},
+ {Some("data:text/html,foo"_ns), Some("http://www.example.com/"_ns),
+ Some(ZONE_INTERNET), Some(kFallbackUrl)},
+ {Some("http://localhost:8080/"_ns), Some("http://www.example.com/"_ns),
+ Some(ZONE_INTRANET)},
+ {Some("http://127.0.0.1/"_ns), Some("http://www.example.com/"_ns),
+ Some(ZONE_INTERNET)},
+ {Some("file://C:/foo"_ns), Some("http://www.example.com/"_ns),
+ Some(ZONE_MY_COMPUTER), Some(kFallbackUrl)},
+ {Some("file:///C:/foo"_ns), Some("http://www.example.com/"_ns),
+ Some(ZONE_MY_COMPUTER), Some(kFallbackUrl)},
+ {Some("file://server/share/foo"_ns), Some("http://www.example.com/"_ns),
+ Some(ZONE_INTRANET), Some(kFallbackUrl)},
+ {Some("file:////server/share/foo"_ns), Some("http://www.example.com/"_ns),
+ Some(ZONE_INTRANET), Some(kFallbackUrl)},
+
+ // Userpass on the source URL is stripped from URL and referrer.
+ {Some("http://user:pw@www.example.org/"_ns), Some(""_ns),
+ Some(ZONE_INTERNET), Some("http://www.example.org/"_ns)},
+ {Some("http://www.example.org/"_ns),
+ Some("http://user:pw@www.example.com/"_ns), Some(ZONE_INTERNET),
+ Nothing() /* mExpectedHostUrl */,
+ Some("http://www.example.com/"_ns) /* mExpectedReferrer */},
+
+ // Non-permitted scheme on the referrer => ReferrerUrl line omitted.
+ {Some("http://www.example.org/"_ns), Some("about:blank"_ns),
+ Some(ZONE_INTERNET), Nothing() /* mExpectedHostUrl */,
+ Some(kFallbackReferrer) /* mExpectedReferrer */},
+
+ // No URL or referrer (private browsing -- zone=internet).
+ {Nothing(), Nothing(), Some(ZONE_INTERNET)},
+
+ // File to set zone for does not exist.
+ {Some("http://www.example.org/"_ns), Some("http://www.example.com/"_ns),
+ Nothing()},
+ {Nothing(), Nothing(), Nothing()},
+
+ // Not expected from nsIFile but included for completeness.
+ {Some("\\\\server\\share\\foo"_ns), Some(""_ns), Some(ZONE_INTRANET),
+ Some(kFallbackUrl)},
+ };
+
+ for (auto& data : fileTestDatas) {
+ SetAndTestFileZone(data);
+ }
+}