Bug 1843841 - Make navigator.appName constant. r=dom-worker-reviewers,webidl,smaug,timhuang

This is supposed to constant per https://html.spec.whatwg.org/#client-identification.

Differential Revision: https://phabricator.services.mozilla.com/D183723
This commit is contained in:
Tom Schuster 2023-07-19 17:00:53 +00:00
parent 584ea48db5
commit 7906e7f340
18 changed files with 39 additions and 144 deletions

View file

@ -114,6 +114,7 @@ const CONST_PRODUCT = "Gecko";
const CONST_PRODUCTSUB = "20100101"; const CONST_PRODUCTSUB = "20100101";
const CONST_VENDOR = ""; const CONST_VENDOR = "";
const CONST_VENDORSUB = ""; const CONST_VENDORSUB = "";
const CONST_LEGACY_BUILD_ID = "20181001000000";
const appVersion = parseInt(Services.appinfo.version); const appVersion = parseInt(Services.appinfo.version);
const rvVersion = const rvVersion =
@ -228,6 +229,11 @@ async function testNavigator() {
CONST_APPNAME, CONST_APPNAME,
"Navigator.appName reports correct constant value." "Navigator.appName reports correct constant value."
); );
is(
result.buildID,
CONST_LEGACY_BUILD_ID,
"Navigator.buildID reports correct constant value."
);
is( is(
result.product, result.product,
CONST_PRODUCT, CONST_PRODUCT,
@ -438,11 +444,11 @@ add_task(async function setupResistFingerprinting() {
add_task(async function runOverrideTest() { add_task(async function runOverrideTest() {
await SpecialPowers.pushPrefEnv({ await SpecialPowers.pushPrefEnv({
set: [ set: [
["general.appname.override", "appName overridden"],
["general.appversion.override", "appVersion overridden"], ["general.appversion.override", "appVersion overridden"],
["general.platform.override", "platform overridden"], ["general.platform.override", "platform overridden"],
["general.useragent.override", "userAgent overridden"], ["general.useragent.override", "userAgent overridden"],
["general.oscpu.override", "oscpu overridden"], ["general.oscpu.override", "oscpu overridden"],
["general.buildID.override", "buildID overridden"],
], ],
}); });
@ -452,7 +458,7 @@ add_task(async function runOverrideTest() {
await testUserAgentHeader(); await testUserAgentHeader();
// Pop general.appname.override etc // Pop general.appversion.override etc
await SpecialPowers.popPrefEnv(); await SpecialPowers.popPrefEnv();
// Pop privacy.resistFingerprinting // Pop privacy.resistFingerprinting

View file

@ -11,6 +11,7 @@
result.appCodeName = navigator.appCodeName; result.appCodeName = navigator.appCodeName;
result.appName = navigator.appName; result.appName = navigator.appName;
result.appVersion = navigator.appVersion; result.appVersion = navigator.appVersion;
result.buildID = navigator.buildID;
result.platform = navigator.platform; result.platform = navigator.platform;
result.userAgent = navigator.userAgent; result.userAgent = navigator.userAgent;
result.product = navigator.product; result.product = navigator.product;

View file

@ -307,11 +307,8 @@ void Navigator::GetAppVersion(nsAString& aAppVersion, CallerType aCallerType,
} }
} }
void Navigator::GetAppName(nsAString& aAppName, CallerType aCallerType) const { void Navigator::GetAppName(nsAString& aAppName) const {
nsCOMPtr<Document> doc = mWindow->GetExtantDoc(); aAppName.AssignLiteral("Netscape");
AppName(aAppName, doc,
/* aUsePrefOverriddenValue = */ aCallerType != CallerType::System);
} }
/** /**
@ -2028,32 +2025,6 @@ nsresult Navigator::GetAppVersion(nsAString& aAppVersion, Document* aCallerDoc,
return rv; return rv;
} }
/* static */
void Navigator::AppName(nsAString& aAppName, Document* aCallerDoc,
bool aUsePrefOverriddenValue) {
MOZ_ASSERT(NS_IsMainThread());
if (aUsePrefOverriddenValue) {
// If fingerprinting resistance is on, we will spoof this value. See
// nsRFPService.h for details about spoofed values.
if (ShouldResistFingerprinting(aCallerDoc, RFPTarget::NavigatorAppName)) {
aAppName.AssignLiteral(SPOOFED_APPNAME);
return;
}
nsAutoString override;
nsresult rv =
mozilla::Preferences::GetString("general.appname.override", override);
if (NS_SUCCEEDED(rv)) {
aAppName = override;
return;
}
}
aAppName.AssignLiteral("Netscape");
}
void Navigator::ClearUserAgentCache() { void Navigator::ClearUserAgentCache() {
Navigator_Binding::ClearCachedUserAgentValue(this); Navigator_Binding::ClearCachedUserAgentValue(this);
} }

View file

@ -107,7 +107,7 @@ class Navigator final : public nsISupports, public nsWrapperCache {
void GetProduct(nsAString& aProduct); void GetProduct(nsAString& aProduct);
void GetLanguage(nsAString& aLanguage); void GetLanguage(nsAString& aLanguage);
void GetAppName(nsAString& aAppName, CallerType aCallerType) const; void GetAppName(nsAString& aAppName) const;
void GetAppVersion(nsAString& aAppName, CallerType aCallerType, void GetAppVersion(nsAString& aAppName, CallerType aCallerType,
ErrorResult& aRv) const; ErrorResult& aRv) const;
void GetPlatform(nsAString& aPlatform, CallerType aCallerType, void GetPlatform(nsAString& aPlatform, CallerType aCallerType,
@ -132,9 +132,6 @@ class Navigator final : public nsISupports, public nsWrapperCache {
bool CanShare(const ShareData& aData); bool CanShare(const ShareData& aData);
already_AddRefed<Promise> Share(const ShareData& aData, ErrorResult& aRv); already_AddRefed<Promise> Share(const ShareData& aData, ErrorResult& aRv);
static void AppName(nsAString& aAppName, Document* aCallerDoc,
bool aUsePrefOverriddenValue);
static nsresult GetPlatform(nsAString& aPlatform, Document* aCallerDoc, static nsresult GetPlatform(nsAString& aPlatform, Document* aCallerDoc,
bool aUsePrefOverriddenValue); bool aUsePrefOverriddenValue);

View file

@ -268,7 +268,6 @@ skip-if = headless # Bug 1405867
[test_anonymousContent_insert.html] [test_anonymousContent_insert.html]
[test_anonymousContent_manipulate_content.html] [test_anonymousContent_manipulate_content.html]
[test_anonymousContent_style_csp.html] [test_anonymousContent_style_csp.html]
[test_appname_override.html]
[test_async_setTimeout_stack.html] [test_async_setTimeout_stack.html]
[test_async_setTimeout_stack_across_globals.html] [test_async_setTimeout_stack_across_globals.html]
[test_base.xhtml] [test_base.xhtml]

View file

@ -1,26 +0,0 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=939445
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 939445 - general.appname.override</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=939445">Mozilla Bug 939445</a>
<script type="application/javascript">
function runTest() {
is(navigator.appName, "hello", "general.appname.override not working");
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({"set": [["general.appname.override", "hello"]]}, runTest);
</script>
</body>
</html>

View file

@ -15,19 +15,16 @@
<pre id="test"></pre> <pre id="test"></pre>
<script class="testbody" type="text/javascript"> <script class="testbody" type="text/javascript">
ok(navigator.appName, "This is used just to populate the cache");
ok(navigator.appVersion, "This is used just to populate the cache"); ok(navigator.appVersion, "This is used just to populate the cache");
ok(navigator.platform, "This is used just to populate the cache");
// B2G could have an empty platform.
info(navigator.platform);
ok(navigator.userAgent, "This is used just to populate the cache"); ok(navigator.userAgent, "This is used just to populate the cache");
ok(navigator.buildID, "This is used just to populate the cache");
SpecialPowers.pushPrefEnv({"set": [ SpecialPowers.pushPrefEnv({"set": [
["general.appname.override", "appName overridden"],
["general.appversion.override", "appVersion overridden"], ["general.appversion.override", "appVersion overridden"],
["general.platform.override", "platform overridden"], ["general.platform.override", "platform overridden"],
["general.useragent.override", "userAgent overridden"], ["general.useragent.override", "userAgent overridden"],
["general.buildID.override", "buildID overridden"],
]}, ]},
function() { function() {
var ifr = document.createElement('IFRAME'); var ifr = document.createElement('IFRAME');
@ -35,10 +32,10 @@
ifr.addEventListener('load', function() { ifr.addEventListener('load', function() {
var nav = ifr.contentWindow.navigator; var nav = ifr.contentWindow.navigator;
isnot(navigator.appName, nav.appName, "appName should match");
isnot(navigator.appVersion, nav.appVersion, "appVersion should match"); isnot(navigator.appVersion, nav.appVersion, "appVersion should match");
isnot(navigator.platform, nav.platform, "platform should match"); isnot(navigator.platform, nav.platform, "platform should match");
isnot(navigator.userAgent, nav.userAgent, "userAgent should match"); isnot(navigator.userAgent, nav.userAgent, "userAgent should match");
isnot(navigator.buildID, nav.buildID, "buildID should match");
SimpleTest.finish(); SimpleTest.finish();
}); });

View file

@ -4,7 +4,7 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. * You can obtain one at http://mozilla.org/MPL/2.0/.
* *
* The origin of this IDL file is * The origin of this IDL file is
* http://www.whatwg.org/specs/web-apps/current-work/#the-navigator-object * https://html.spec.whatwg.org/#the-navigator-object
* http://www.w3.org/TR/tracking-dnt/ * http://www.w3.org/TR/tracking-dnt/
* http://www.w3.org/TR/geolocation-API/#geolocation_interface * http://www.w3.org/TR/geolocation-API/#geolocation_interface
* http://www.w3.org/TR/battery-status/#navigatorbattery-interface * http://www.w3.org/TR/battery-status/#navigatorbattery-interface
@ -26,7 +26,7 @@
interface URI; interface URI;
// http://www.whatwg.org/specs/web-apps/current-work/#the-navigator-object // https://html.spec.whatwg.org/#the-navigator-object
[HeaderFile="Navigator.h", [HeaderFile="Navigator.h",
Exposed=Window] Exposed=Window]
interface Navigator { interface Navigator {
@ -47,8 +47,8 @@ interface mixin NavigatorID {
// WebKit/Blink/Trident/Presto support this (hardcoded "Mozilla"). // WebKit/Blink/Trident/Presto support this (hardcoded "Mozilla").
[Constant, Cached, Throws] [Constant, Cached, Throws]
readonly attribute DOMString appCodeName; // constant "Mozilla" readonly attribute DOMString appCodeName; // constant "Mozilla"
[Constant, Cached, NeedsCallerType] [Constant, Cached]
readonly attribute DOMString appName; readonly attribute DOMString appName; // constant "Netscape"
[Constant, Cached, Throws, NeedsCallerType] [Constant, Cached, Throws, NeedsCallerType]
readonly attribute DOMString appVersion; readonly attribute DOMString appVersion;
[Pure, Cached, Throws, NeedsCallerType] [Pure, Cached, Throws, NeedsCallerType]

View file

@ -992,18 +992,6 @@ void PrefLanguagesChanged(const char* /* aPrefName */, void* /* aClosure */) {
} }
} }
void AppNameOverrideChanged(const char* /* aPrefName */, void* /* aClosure */) {
AssertIsOnMainThread();
nsAutoString override;
Preferences::GetString("general.appname.override", override);
RuntimeService* runtime = RuntimeService::GetService();
if (runtime) {
runtime->UpdateAppNameOverridePreference(override);
}
}
void AppVersionOverrideChanged(const char* /* aPrefName */, void AppVersionOverrideChanged(const char* /* aPrefName */,
void* /* aClosure */) { void* /* aClosure */) {
AssertIsOnMainThread(); AssertIsOnMainThread();
@ -1172,9 +1160,6 @@ bool RuntimeService::RegisterWorker(WorkerPrivate& aWorkerPrivate) {
} }
} else { } else {
if (!mNavigatorPropertiesLoaded) { if (!mNavigatorPropertiesLoaded) {
Navigator::AppName(mNavigatorProperties.mAppName,
aWorkerPrivate.GetDocument(),
false /* aUsePrefOverriddenValue */);
if (NS_FAILED(Navigator::GetAppVersion( if (NS_FAILED(Navigator::GetAppVersion(
mNavigatorProperties.mAppVersion, aWorkerPrivate.GetDocument(), mNavigatorProperties.mAppVersion, aWorkerPrivate.GetDocument(),
false /* aUsePrefOverriddenValue */)) || false /* aUsePrefOverriddenValue */)) ||
@ -1407,7 +1392,6 @@ nsresult RuntimeService::Init() {
LoadGCZealOptions, PREF_JS_OPTIONS_PREFIX PREF_GCZEAL)) || LoadGCZealOptions, PREF_JS_OPTIONS_PREFIX PREF_GCZEAL)) ||
#endif #endif
WORKER_PREF("intl.accept_languages", PrefLanguagesChanged) || WORKER_PREF("intl.accept_languages", PrefLanguagesChanged) ||
WORKER_PREF("general.appname.override", AppNameOverrideChanged) ||
WORKER_PREF("general.appversion.override", AppVersionOverrideChanged) || WORKER_PREF("general.appversion.override", AppVersionOverrideChanged) ||
WORKER_PREF("general.platform.override", PlatformOverrideChanged) || WORKER_PREF("general.platform.override", PlatformOverrideChanged) ||
NS_FAILED(Preferences::RegisterPrefixCallbackAndCall( NS_FAILED(Preferences::RegisterPrefixCallbackAndCall(
@ -1657,7 +1641,6 @@ void RuntimeService::Cleanup() {
if (NS_FAILED(Preferences::UnregisterPrefixCallback( if (NS_FAILED(Preferences::UnregisterPrefixCallback(
LoadContextOptions, PREF_JS_OPTIONS_PREFIX)) || LoadContextOptions, PREF_JS_OPTIONS_PREFIX)) ||
WORKER_PREF("intl.accept_languages", PrefLanguagesChanged) || WORKER_PREF("intl.accept_languages", PrefLanguagesChanged) ||
WORKER_PREF("general.appname.override", AppNameOverrideChanged) ||
WORKER_PREF("general.appversion.override", AppVersionOverrideChanged) || WORKER_PREF("general.appversion.override", AppVersionOverrideChanged) ||
WORKER_PREF("general.platform.override", PlatformOverrideChanged) || WORKER_PREF("general.platform.override", PlatformOverrideChanged) ||
#ifdef JS_GC_ZEAL #ifdef JS_GC_ZEAL
@ -1815,11 +1798,6 @@ void RuntimeService::UpdateAllWorkerContextOptions() {
}); });
} }
void RuntimeService::UpdateAppNameOverridePreference(const nsAString& aValue) {
AssertIsOnMainThread();
mNavigatorProperties.mAppNameOverridden = aValue;
}
void RuntimeService::UpdateAppVersionOverridePreference( void RuntimeService::UpdateAppVersionOverridePreference(
const nsAString& aValue) { const nsAString& aValue) {
AssertIsOnMainThread(); AssertIsOnMainThread();

View file

@ -68,8 +68,6 @@ class RuntimeService final : public nsIObserver {
public: public:
struct NavigatorProperties { struct NavigatorProperties {
nsString mAppName;
nsString mAppNameOverridden;
nsString mAppVersion; nsString mAppVersion;
nsString mAppVersionOverridden; nsString mAppVersionOverridden;
nsString mPlatform; nsString mPlatform;
@ -125,8 +123,6 @@ class RuntimeService final : public nsIObserver {
sDefaultJSSettings->contextOptions = aContextOptions; sDefaultJSSettings->contextOptions = aContextOptions;
} }
void UpdateAppNameOverridePreference(const nsAString& aValue);
void UpdateAppVersionOverridePreference(const nsAString& aValue); void UpdateAppVersionOverridePreference(const nsAString& aValue);
void UpdatePlatformOverridePreference(const nsAString& aValue); void UpdatePlatformOverridePreference(const nsAString& aValue);

View file

@ -96,28 +96,6 @@ void WorkerNavigator::SetLanguages(const nsTArray<nsString>& aLanguages) {
mProperties.mLanguages = aLanguages.Clone(); mProperties.mLanguages = aLanguages.Clone();
} }
void WorkerNavigator::GetAppName(nsString& aAppName,
CallerType aCallerType) const {
WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
MOZ_ASSERT(workerPrivate);
if (aCallerType != CallerType::System) {
if (workerPrivate->ShouldResistFingerprinting(
RFPTarget::NavigatorAppName)) {
// See nsRFPService.h for spoofed value.
aAppName.AssignLiteral(SPOOFED_APPNAME);
return;
}
if (!mProperties.mAppNameOverridden.IsEmpty()) {
aAppName = mProperties.mAppNameOverridden;
return;
}
}
aAppName = mProperties.mAppName;
}
void WorkerNavigator::GetAppVersion(nsString& aAppVersion, void WorkerNavigator::GetAppVersion(nsString& aAppVersion,
CallerType aCallerType, CallerType aCallerType,
ErrorResult& aRv) const { ErrorResult& aRv) const {

View file

@ -67,7 +67,9 @@ class WorkerNavigator final : public nsWrapperCache {
void GetAppCodeName(nsString& aAppCodeName, ErrorResult& /* unused */) const { void GetAppCodeName(nsString& aAppCodeName, ErrorResult& /* unused */) const {
aAppCodeName.AssignLiteral("Mozilla"); aAppCodeName.AssignLiteral("Mozilla");
} }
void GetAppName(nsString& aAppName, CallerType aCallerType) const; void GetAppName(nsString& aAppName) const {
aAppName.AssignLiteral("Netscape");
}
void GetAppVersion(nsString& aAppVersion, CallerType aCallerType, void GetAppVersion(nsString& aAppVersion, CallerType aCallerType,
ErrorResult& aRv) const; ErrorResult& aRv) const;

View file

@ -39,7 +39,6 @@
function replaceAndCheckValues() { function replaceAndCheckValues() {
SpecialPowers.pushPrefEnv({"set": [ SpecialPowers.pushPrefEnv({"set": [
["general.appname.override", "appName overridden"],
["general.appversion.override", "appVersion overridden"], ["general.appversion.override", "appVersion overridden"],
["general.platform.override", "platform overridden"], ["general.platform.override", "platform overridden"],
["general.useragent.override", "userAgent overridden"] ["general.useragent.override", "userAgent overridden"]

View file

@ -17,8 +17,6 @@
worker.onmessage = function(event) { worker.onmessage = function(event) {
is(event.data.appCodeName, navigator.appCodeName, "appCodeName should match"); is(event.data.appCodeName, navigator.appCodeName, "appCodeName should match");
is(event.data.appName, navigator.appName, "appName should match");
isnot(event.data.appName, "appName overridden", "appName is not overridden");
is(event.data.appVersion, navigator.appVersion, "appVersion should match"); is(event.data.appVersion, navigator.appVersion, "appVersion should match");
isnot(event.data.appVersion, "appVersion overridden", "appVersion is not overridden"); isnot(event.data.appVersion, "appVersion overridden", "appVersion is not overridden");
is(event.data.platform, navigator.platform, "platform should match"); is(event.data.platform, navigator.platform, "platform should match");
@ -31,7 +29,6 @@
function replaceAndCheckValues() { function replaceAndCheckValues() {
SpecialPowers.pushPrefEnv({"set": [ SpecialPowers.pushPrefEnv({"set": [
["general.appname.override", "appName overridden"],
["general.appversion.override", "appVersion overridden"], ["general.appversion.override", "appVersion overridden"],
["general.platform.override", "platform overridden"], ["general.platform.override", "platform overridden"],
["general.useragent.override", "userAgent overridden"] ["general.useragent.override", "userAgent overridden"]

View file

@ -6110,7 +6110,6 @@ static const PrefListEntry sDynamicPrefOverrideList[]{
PREF_LIST_ENTRY("extensions.foobaz"), PREF_LIST_ENTRY("extensions.foobaz"),
PREF_LIST_ENTRY( PREF_LIST_ENTRY(
"extensions.formautofill.creditCards.heuristics.testConfidence"), "extensions.formautofill.creditCards.heuristics.testConfidence"),
PREF_LIST_ENTRY("general.appname.override"),
PREF_LIST_ENTRY("general.appversion.override"), PREF_LIST_ENTRY("general.appversion.override"),
PREF_LIST_ENTRY("general.buildID.override"), PREF_LIST_ENTRY("general.buildID.override"),
PREF_LIST_ENTRY("general.oscpu.override"), PREF_LIST_ENTRY("general.oscpu.override"),

View file

@ -2,39 +2,39 @@
/* vim: set sts=2 sw=2 et tw=80: */ /* vim: set sts=2 sw=2 et tw=80: */
"use strict"; "use strict";
async function queryAppName() { async function queryBuildID() {
let extension = ExtensionTestUtils.loadExtension({ let extension = ExtensionTestUtils.loadExtension({
background() { background() {
browser.test.sendMessage("result", { appName: navigator.appName }); browser.test.sendMessage("result", { buildID: navigator.buildID });
}, },
}); });
await extension.startup(); await extension.startup();
let result = await extension.awaitMessage("result"); let { buildID } = await extension.awaitMessage("result");
await extension.unload(); await extension.unload();
return result.appName; return buildID;
} }
const APPNAME_OVERRIDE = "MyTestAppName"; const BUILDID_OVERRIDE = "Overridden buildID";
add_task( add_task(
{ {
pref_set: [["general.appname.override", APPNAME_OVERRIDE]], pref_set: [["general.buildID.override", BUILDID_OVERRIDE]],
}, },
async function test_appName_normal() { async function test_buildID_normal() {
let appName = await queryAppName(); let buildID = await queryBuildID();
Assert.equal(appName, APPNAME_OVERRIDE); Assert.equal(buildID, BUILDID_OVERRIDE);
} }
); );
add_task( add_task(
{ {
pref_set: [ pref_set: [
["general.appname.override", APPNAME_OVERRIDE], ["general.buildID.override", BUILDID_OVERRIDE],
["privacy.resistFingerprinting", true], ["privacy.resistFingerprinting", true],
], ],
}, },
async function test_appName_resistFingerprinting() { async function test_buildID_resistFingerprinting() {
let appName = await queryAppName(); let buildID = await queryBuildID();
Assert.equal(appName, APPNAME_OVERRIDE); Assert.equal(buildID, BUILDID_OVERRIDE);
} }
); );

View file

@ -24,8 +24,10 @@ ITEM_VALUE(CanvasRandomization, 1llu << 8)
ITEM_VALUE(CanvasImageExtractionPrompt, 1llu << 9) ITEM_VALUE(CanvasImageExtractionPrompt, 1llu << 9)
ITEM_VALUE(CanvasExtractionFromThirdPartiesIsBlocked, 1llu << 10) ITEM_VALUE(CanvasExtractionFromThirdPartiesIsBlocked, 1llu << 10)
ITEM_VALUE(CanvasExtractionBeforeUserInputIsBlocked, 1llu << 11) ITEM_VALUE(CanvasExtractionBeforeUserInputIsBlocked, 1llu << 11)
// UNUSED: 1llu << 12
// Various "client identification" values of the navigator object // Various "client identification" values of the navigator object
ITEM_VALUE(NavigatorAppName, 1llu << 12)
ITEM_VALUE(NavigatorAppVersion, 1llu << 13) ITEM_VALUE(NavigatorAppVersion, 1llu << 13)
ITEM_VALUE(NavigatorBuildID, 1llu << 14) ITEM_VALUE(NavigatorBuildID, 1llu << 14)
ITEM_VALUE(NavigatorHWConcurrency, 1llu << 15) ITEM_VALUE(NavigatorHWConcurrency, 1llu << 15)

View file

@ -47,7 +47,6 @@
# define SPOOFED_PLATFORM "Linux x86_64" # define SPOOFED_PLATFORM "Linux x86_64"
#endif #endif
#define SPOOFED_APPNAME "Netscape"
#define LEGACY_BUILD_ID "20181001000000" #define LEGACY_BUILD_ID "20181001000000"
#define LEGACY_UA_GECKO_TRAIL "20100101" #define LEGACY_UA_GECKO_TRAIL "20100101"