diff options
Diffstat (limited to 'extensions/pref/autoconfig')
22 files changed, 1712 insertions, 0 deletions
diff --git a/extensions/pref/autoconfig/moz.build b/extensions/pref/autoconfig/moz.build new file mode 100644 index 0000000000..0ef8aad2a9 --- /dev/null +++ b/extensions/pref/autoconfig/moz.build @@ -0,0 +1,11 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DIRS += ["src"] + +XPCSHELL_TESTS_MANIFESTS += ["test/unit/xpcshell.ini"] + +MARIONETTE_UNIT_MANIFESTS += ["test/marionette/manifest.ini"] diff --git a/extensions/pref/autoconfig/src/components.conf b/extensions/pref/autoconfig/src/components.conf new file mode 100644 index 0000000000..789a44b2e3 --- /dev/null +++ b/extensions/pref/autoconfig/src/components.conf @@ -0,0 +1,16 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +Classes = [ + { + 'cid': '{ba5bc4c6-1dd1-11b2-bb89-b844c6ec0339}', + 'contract_ids': ['@mozilla.org/readconfig;1'], + 'type': 'nsReadConfig', + 'headers': ['/extensions/pref/autoconfig/src/nsReadConfig.h'], + 'init_method': 'Init', + 'categories': {'pref-config-startup': 'ReadConfig Module'}, + }, +] diff --git a/extensions/pref/autoconfig/src/moz.build b/extensions/pref/autoconfig/src/moz.build new file mode 100644 index 0000000000..12722cf6d7 --- /dev/null +++ b/extensions/pref/autoconfig/src/moz.build @@ -0,0 +1,21 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +UNIFIED_SOURCES += [ + "nsAutoConfig.cpp", + "nsJSConfigTriggers.cpp", + "nsReadConfig.cpp", +] + +XPCOM_MANIFESTS += [ + "components.conf", +] + +FINAL_LIBRARY = "xul" + +FINAL_TARGET_FILES.defaults.autoconfig += [ + "prefcalls.js", +] diff --git a/extensions/pref/autoconfig/src/nsAutoConfig.cpp b/extensions/pref/autoconfig/src/nsAutoConfig.cpp new file mode 100644 index 0000000000..d841512c93 --- /dev/null +++ b/extensions/pref/autoconfig/src/nsAutoConfig.cpp @@ -0,0 +1,463 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ResultExtensions.h" +#include "nsAutoConfig.h" +#include "nsJSConfigTriggers.h" + +#include "nsIURI.h" +#include "nsIHttpChannel.h" +#include "nsThreadUtils.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsIObserverService.h" +#include "nsLiteralString.h" +#include "nsIPromptService.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsIPrefBranch.h" +#include "nsIPrefService.h" +#include "nsIStringBundle.h" +#include "nsContentUtils.h" +#include "nsCRT.h" +#include "nsNetCID.h" +#include "nsNetUtil.h" +#include "nspr.h" +#include <algorithm> + +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/Logging.h" +#include "mozilla/SpinEventLoopUntil.h" + +using mozilla::LogLevel; + +mozilla::LazyLogModule MCD("MCD"); + +// nsISupports Implementation + +NS_IMPL_ISUPPORTS(nsAutoConfig, nsITimerCallback, nsIStreamListener, + nsIObserver, nsIRequestObserver, nsISupportsWeakReference, + nsINamed) + +nsAutoConfig::nsAutoConfig() {} + +nsresult nsAutoConfig::Init() { + // member initializers and constructor code + + nsresult rv; + mLoaded = false; + + // Registering the object as an observer to the profile-after-change topic + nsCOMPtr<nsIObserverService> observerService = + do_GetService("@mozilla.org/observer-service;1", &rv); + if (NS_FAILED(rv)) return rv; + + rv = observerService->AddObserver(this, "profile-after-change", true); + + return rv; +} + +nsAutoConfig::~nsAutoConfig() {} + +void nsAutoConfig::SetConfigURL(const char* aConfigURL) { + mConfigURL.Assign(aConfigURL); +} + +NS_IMETHODIMP +nsAutoConfig::OnStartRequest(nsIRequest* request) { return NS_OK; } + +NS_IMETHODIMP +nsAutoConfig::OnDataAvailable(nsIRequest* request, nsIInputStream* aIStream, + uint64_t aSourceOffset, uint32_t aLength) { + uint32_t amt, size; + nsresult rv; + char buf[1024]; + + while (aLength) { + size = std::min<size_t>(aLength, sizeof(buf)); + rv = aIStream->Read(buf, size, &amt); + if (NS_FAILED(rv)) return rv; + mBuf.Append(buf, amt); + aLength -= amt; + } + return NS_OK; +} + +NS_IMETHODIMP +nsAutoConfig::OnStopRequest(nsIRequest* request, nsresult aStatus) { + nsresult rv; + + // If the request is failed, go read the failover.jsc file + if (NS_FAILED(aStatus)) { + MOZ_LOG(MCD, LogLevel::Debug, + ("mcd request failed with status %" PRIx32 "\n", + static_cast<uint32_t>(aStatus))); + return readOfflineFile(); + } + + // Checking for the http response, if failure go read the failover file. + nsCOMPtr<nsIHttpChannel> pHTTPCon(do_QueryInterface(request)); + if (pHTTPCon) { + uint32_t httpStatus; + rv = pHTTPCon->GetResponseStatus(&httpStatus); + if (NS_FAILED(rv) || httpStatus != 200) { + MOZ_LOG(MCD, LogLevel::Debug, + ("mcd http request failed with status %x\n", httpStatus)); + return readOfflineFile(); + } + } + + // Send the autoconfig.jsc to javascript engine. + + rv = EvaluateAdminConfigScript(mBuf.get(), mBuf.Length(), nullptr, false, + true, false); + if (NS_SUCCEEDED(rv)) { + // Write the autoconfig.jsc to failover.jsc (cached copy) + rv = writeFailoverFile(); + + if (NS_FAILED(rv)) NS_WARNING("Error writing failover.jsc file"); + + // Releasing the lock to allow the main thread to start execution + mLoaded = true; + + return NS_OK; + } + // there is an error in parsing of the autoconfig file. + NS_WARNING( + "Error reading autoconfig.jsc from the network, reading the offline " + "version"); + return readOfflineFile(); +} + +// Notify method as a TimerCallBack function +NS_IMETHODIMP nsAutoConfig::Notify(nsITimer* timer) { + downloadAutoConfig(); + return NS_OK; +} + +NS_IMETHODIMP +nsAutoConfig::GetName(nsACString& aName) { + aName.AssignLiteral("nsAutoConfig"); + return NS_OK; +} + +/* Observe() is called twice: once at the instantiation time and other + after the profile is set. It doesn't do anything but return NS_OK during the + creation time. Second time it calls downloadAutoConfig(). +*/ + +NS_IMETHODIMP nsAutoConfig::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* someData) { + nsresult rv = NS_OK; + if (!nsCRT::strcmp(aTopic, "profile-after-change")) { + // We will be calling downloadAutoConfig even if there is no profile + // name. Nothing will be passed as a parameter to the URL and the + // default case will be picked up by the script. + + rv = downloadAutoConfig(); + } + + return rv; +} + +nsresult nsAutoConfig::downloadAutoConfig() { + nsresult rv; + nsAutoCString emailAddr; + static bool firstTime = true; + + if (mConfigURL.IsEmpty()) { + MOZ_LOG(MCD, LogLevel::Debug, + ("global config url is empty - did you set " + "autoadmin.global_config_url?\n")); + NS_WARNING("AutoConfig called without global_config_url"); + return NS_OK; + } + + // If there is an email address appended as an argument to the ConfigURL + // in the previous read, we need to remove it when timer kicks in and + // downloads the autoconfig file again. + // If necessary, the email address will be added again as an argument. + int32_t index = mConfigURL.RFindChar((char16_t)'?'); + if (index != -1) mConfigURL.Truncate(index); + + // Clean up the previous read, the new read is going to use the same buffer + if (!mBuf.IsEmpty()) mBuf.Truncate(0); + + // Get the preferences branch and save it to the member variable + if (!mPrefBranch) { + nsCOMPtr<nsIPrefService> prefs = + do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; + + rv = prefs->GetBranch(nullptr, getter_AddRefs(mPrefBranch)); + if (NS_FAILED(rv)) return rv; + } + + // Check to see if the network is online/offline + nsCOMPtr<nsIIOService> ios = do_GetService(NS_IOSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; + + bool offline; + rv = ios->GetOffline(&offline); + if (NS_FAILED(rv)) return rv; + + if (offline) { + bool offlineFailover; + rv = mPrefBranch->GetBoolPref("autoadmin.offline_failover", + &offlineFailover); + // Read the failover.jsc if the network is offline and the pref says so + if (NS_SUCCEEDED(rv) && offlineFailover) return readOfflineFile(); + } + + /* Append user's identity at the end of the URL if the pref says so. + First we are checking for the user's email address but if it is not + available in the case where the client is used without messenger, user's + profile name will be used as an unique identifier + */ + bool appendMail; + rv = mPrefBranch->GetBoolPref("autoadmin.append_emailaddr", &appendMail); + if (NS_SUCCEEDED(rv) && appendMail) { + rv = getEmailAddr(emailAddr); + if (NS_SUCCEEDED(rv) && emailAddr.get()) { + /* Adding the unique identifier at the end of autoconfig URL. + In this case the autoconfig URL is a script and + emailAddr as passed as an argument + */ + mConfigURL.Append('?'); + mConfigURL.Append(emailAddr); + } + } + + // create a new url + nsCOMPtr<nsIURI> url; + nsCOMPtr<nsIChannel> channel; + + rv = NS_NewURI(getter_AddRefs(url), mConfigURL); + if (NS_FAILED(rv)) { + MOZ_LOG( + MCD, LogLevel::Debug, + ("failed to create URL - is autoadmin.global_config_url valid? - %s\n", + mConfigURL.get())); + return rv; + } + + MOZ_LOG(MCD, LogLevel::Debug, ("running MCD url %s\n", mConfigURL.get())); + // open a channel for the url + rv = NS_NewChannel( + getter_AddRefs(channel), url, nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + nsIContentPolicy::TYPE_OTHER, + nullptr, // nsICookieJarSettings + nullptr, // PerformanceStorage + nullptr, // loadGroup + nullptr, // aCallbacks + nsIRequest::INHIBIT_PERSISTENT_CACHING | nsIRequest::LOAD_BYPASS_CACHE); + + if (NS_FAILED(rv)) return rv; + + rv = channel->AsyncOpen(this); + if (NS_FAILED(rv)) { + readOfflineFile(); + return rv; + } + + // Set a repeating timer if the pref is set. + // This is to be done only once. + // Also We are having the event queue processing only for the startup + // It is not needed with the repeating timer. + if (firstTime) { + firstTime = false; + + /* process events until we're finished. AutoConfig.jsc reading needs + to be finished before the browser starts loading up + We are waiting for the mLoaded which will be set through + onStopRequest or readOfflineFile methods + There is a possibility of deadlock so we need to make sure + that mLoaded will be set to true in any case (success/failure) + */ + + if (!mozilla::SpinEventLoopUntil([&]() { return mLoaded; })) { + return NS_ERROR_FAILURE; + } + + int32_t minutes; + rv = mPrefBranch->GetIntPref("autoadmin.refresh_interval", &minutes); + if (NS_SUCCEEDED(rv) && minutes > 0) { + // Create a new timer and pass this nsAutoConfig + // object as a timer callback. + MOZ_TRY_VAR(mTimer, + NS_NewTimerWithCallback(this, minutes * 60 * 1000, + nsITimer::TYPE_REPEATING_SLACK)); + } + } // first_time + + return NS_OK; +} // nsPref::downloadAutoConfig() + +nsresult nsAutoConfig::readOfflineFile() { + nsresult rv; + + /* Releasing the lock to allow main thread to start + execution. At this point we do not need to stall + the thread since all network activities are done. + */ + mLoaded = true; + + bool failCache; + rv = mPrefBranch->GetBoolPref("autoadmin.failover_to_cached", &failCache); + if (NS_SUCCEEDED(rv) && !failCache) { + // disable network connections and return. + + nsCOMPtr<nsIIOService> ios = do_GetService(NS_IOSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; + + bool offline; + rv = ios->GetOffline(&offline); + if (NS_FAILED(rv)) return rv; + + if (!offline) { + rv = ios->SetOffline(true); + if (NS_FAILED(rv)) return rv; + } + + // lock the "network.online" prference so user cannot toggle back to + // online mode. + rv = mPrefBranch->SetBoolPref("network.online", false); + if (NS_FAILED(rv)) return rv; + + mPrefBranch->LockPref("network.online"); + return NS_OK; + } + + /* faiover_to_cached is set to true so + Open the file and read the content. + execute the javascript file + */ + + nsCOMPtr<nsIFile> failoverFile; + rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, + getter_AddRefs(failoverFile)); + if (NS_FAILED(rv)) return rv; + + failoverFile->AppendNative("failover.jsc"_ns); + rv = evaluateLocalFile(failoverFile); + if (NS_FAILED(rv)) + NS_WARNING("Couldn't open failover.jsc, going back to default prefs"); + return NS_OK; +} + +nsresult nsAutoConfig::evaluateLocalFile(nsIFile* file) { + nsresult rv; + nsCOMPtr<nsIInputStream> inStr; + + rv = NS_NewLocalFileInputStream(getter_AddRefs(inStr), file); + if (NS_FAILED(rv)) return rv; + + int64_t fileSize; + file->GetFileSize(&fileSize); + uint32_t fs = fileSize; // Converting 64 bit structure to unsigned int + char* buf = (char*)malloc(fs * sizeof(char)); + if (!buf) return NS_ERROR_OUT_OF_MEMORY; + + uint32_t amt = 0; + rv = inStr->Read(buf, fs, &amt); + if (NS_SUCCEEDED(rv)) { + EvaluateAdminConfigScript(buf, fs, nullptr, false, true, false); + } + inStr->Close(); + free(buf); + return rv; +} + +nsresult nsAutoConfig::writeFailoverFile() { + nsresult rv; + nsCOMPtr<nsIFile> failoverFile; + nsCOMPtr<nsIOutputStream> outStr; + uint32_t amt; + + rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, + getter_AddRefs(failoverFile)); + if (NS_FAILED(rv)) return rv; + + failoverFile->AppendNative("failover.jsc"_ns); + + rv = NS_NewLocalFileOutputStream(getter_AddRefs(outStr), failoverFile); + if (NS_FAILED(rv)) return rv; + rv = outStr->Write(mBuf.get(), mBuf.Length(), &amt); + outStr->Close(); + return rv; +} + +nsresult nsAutoConfig::getEmailAddr(nsACString& emailAddr) { + nsresult rv; + nsAutoCString prefValue; + + /* Getting an email address through set of three preferences: + First getting a default account with + "mail.accountmanager.defaultaccount" + second getting an associated id with the default account + Third getting an email address with id + */ + + rv = + mPrefBranch->GetCharPref("mail.accountmanager.defaultaccount", prefValue); + if (NS_SUCCEEDED(rv) && !prefValue.IsEmpty()) { + emailAddr = "mail.account."_ns + prefValue + ".identities"_ns; + rv = mPrefBranch->GetCharPref(PromiseFlatCString(emailAddr).get(), + prefValue); + if (NS_FAILED(rv) || prefValue.IsEmpty()) + return PromptForEMailAddress(emailAddr); + int32_t commandIndex = prefValue.FindChar(','); + if (commandIndex != kNotFound) prefValue.Truncate(commandIndex); + emailAddr = "mail.identity."_ns + prefValue + ".useremail"_ns; + rv = mPrefBranch->GetCharPref(PromiseFlatCString(emailAddr).get(), + prefValue); + if (NS_FAILED(rv) || prefValue.IsEmpty()) + return PromptForEMailAddress(emailAddr); + emailAddr = prefValue; + } else { + // look for 4.x pref in case we just migrated. + rv = mPrefBranch->GetCharPref("mail.identity.useremail", prefValue); + if (NS_SUCCEEDED(rv) && !prefValue.IsEmpty()) + emailAddr = prefValue; + else + PromptForEMailAddress(emailAddr); + } + + return NS_OK; +} + +nsresult nsAutoConfig::PromptForEMailAddress(nsACString& emailAddress) { + nsresult rv; + nsCOMPtr<nsIPromptService> promptService = + do_GetService("@mozilla.org/embedcomp/prompt-service;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIStringBundleService> bundleService = + do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIStringBundle> bundle; + rv = bundleService->CreateBundle( + "chrome://autoconfig/locale/autoconfig.properties", + getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString title; + rv = bundle->GetStringFromName("emailPromptTitle", title); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString err; + rv = bundle->GetStringFromName("emailPromptMsg", err); + NS_ENSURE_SUCCESS(rv, rv); + bool check = false; + nsString emailResult; + bool success; + rv = promptService->Prompt(nullptr, title.get(), err.get(), + getter_Copies(emailResult), nullptr, &check, + &success); + if (!success) return NS_ERROR_FAILURE; + NS_ENSURE_SUCCESS(rv, rv); + LossyCopyUTF16toASCII(emailResult, emailAddress); + return NS_OK; +} diff --git a/extensions/pref/autoconfig/src/nsAutoConfig.h b/extensions/pref/autoconfig/src/nsAutoConfig.h new file mode 100644 index 0000000000..3a78239286 --- /dev/null +++ b/extensions/pref/autoconfig/src/nsAutoConfig.h @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsAutoConfig_h +#define nsAutoConfig_h + +#include "nsITimer.h" +#include "nsIFile.h" +#include "nsINamed.h" +#include "nsIObserver.h" +#include "nsIStreamListener.h" +#include "nsWeakReference.h" +#include "nsString.h" + +class nsIPrefBranch; + +class nsAutoConfig final : public nsITimerCallback, + public nsIStreamListener, + public nsIObserver, + public nsSupportsWeakReference, + public nsINamed + +{ + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIOBSERVER + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSINAMED + + nsAutoConfig(); + nsresult Init(); + + void SetConfigURL(const char* aConfigURL); + + protected: + virtual ~nsAutoConfig(); + nsresult downloadAutoConfig(); + nsresult readOfflineFile(); + nsresult evaluateLocalFile(nsIFile* file); + nsresult writeFailoverFile(); + nsresult getEmailAddr(nsACString& emailAddr); + nsresult PromptForEMailAddress(nsACString& emailAddress); + nsCString mBuf; + nsCOMPtr<nsIPrefBranch> mPrefBranch; + bool mLoaded; + nsCOMPtr<nsITimer> mTimer; + nsCString mConfigURL; +}; + +#endif diff --git a/extensions/pref/autoconfig/src/nsJSConfigTriggers.cpp b/extensions/pref/autoconfig/src/nsJSConfigTriggers.cpp new file mode 100644 index 0000000000..4b2334ae2b --- /dev/null +++ b/extensions/pref/autoconfig/src/nsJSConfigTriggers.cpp @@ -0,0 +1,163 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsJSConfigTriggers.h" + +#include "jsapi.h" +#include "nsIXPConnect.h" +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nspr.h" +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" +#include "mozilla/NullPrincipal.h" +#include "mozilla/dom/ScriptSettings.h" +#include "nsContentUtils.h" +#include "nsJSPrincipals.h" +#include "nsIScriptError.h" +#include "js/Wrapper.h" +#include "mozilla/Utf8.h" + +extern mozilla::LazyLogModule MCD; +using mozilla::AutoSafeJSContext; +using mozilla::IsUtf8; +using mozilla::NullPrincipal; +using mozilla::dom::AutoJSAPI; + +//***************************************************************************** + +static JS::PersistentRooted<JSObject*> autoconfigSystemSb; +static JS::PersistentRooted<JSObject*> autoconfigSb; +static bool sandboxEnabled; + +nsresult CentralizedAdminPrefManagerInit(bool aSandboxEnabled) { + // If the sandbox is already created, no need to create it again. + if (autoconfigSb.initialized()) return NS_OK; + + sandboxEnabled = aSandboxEnabled; + + // Grab XPConnect. + nsCOMPtr<nsIXPConnect> xpc = nsIXPConnect::XPConnect(); + + // Grab the system principal. + nsCOMPtr<nsIPrincipal> principal; + nsContentUtils::GetSecurityManager()->GetSystemPrincipal( + getter_AddRefs(principal)); + + // Create a sandbox. + AutoSafeJSContext cx; + JS::Rooted<JSObject*> sandbox(cx); + nsresult rv = xpc->CreateSandbox(cx, principal, sandbox.address()); + NS_ENSURE_SUCCESS(rv, rv); + + // Unwrap, store and root the sandbox. + NS_ENSURE_STATE(sandbox); + autoconfigSystemSb.init(cx, js::UncheckedUnwrap(sandbox)); + + // Create an unprivileged sandbox. + principal = NullPrincipal::CreateWithoutOriginAttributes(); + rv = xpc->CreateSandbox(cx, principal, sandbox.address()); + NS_ENSURE_SUCCESS(rv, rv); + + autoconfigSb.init(cx, js::UncheckedUnwrap(sandbox)); + + // Define gSandbox on system sandbox. + JSAutoRealm ar(cx, autoconfigSystemSb); + + JS::Rooted<JS::Value> value(cx, JS::ObjectValue(*sandbox)); + + if (!JS_WrapValue(cx, &value) || + !JS_DefineProperty(cx, autoconfigSystemSb, "gSandbox", value, + JSPROP_ENUMERATE)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult CentralizedAdminPrefManagerFinish() { + if (autoconfigSb.initialized()) { + AutoSafeJSContext cx; + autoconfigSb.reset(); + autoconfigSystemSb.reset(); + JS_MaybeGC(cx); + } + return NS_OK; +} + +nsresult EvaluateAdminConfigScript(const char* js_buffer, size_t length, + const char* filename, bool globalContext, + bool callbacks, bool skipFirstLine, + bool isPrivileged) { + if (!sandboxEnabled) { + isPrivileged = true; + } + return EvaluateAdminConfigScript( + isPrivileged ? autoconfigSystemSb : autoconfigSb, js_buffer, length, + filename, globalContext, callbacks, skipFirstLine); +} + +nsresult EvaluateAdminConfigScript(JS::HandleObject sandbox, + const char* js_buffer, size_t length, + const char* filename, bool globalContext, + bool callbacks, bool skipFirstLine) { + if (skipFirstLine) { + /* In order to protect the privacy of the JavaScript preferences file + * from loading by the browser, we make the first line unparseable + * by JavaScript. We must skip that line here before executing + * the JavaScript code. + */ + unsigned int i = 0; + while (i < length) { + char c = js_buffer[i++]; + if (c == '\r') { + if (js_buffer[i] == '\n') i++; + break; + } + if (c == '\n') break; + } + + length -= i; + js_buffer += i; + } + + // Grab XPConnect. + nsCOMPtr<nsIXPConnect> xpc = nsIXPConnect::XPConnect(); + + AutoJSAPI jsapi; + if (!jsapi.Init(sandbox)) { + return NS_ERROR_UNEXPECTED; + } + JSContext* cx = jsapi.cx(); + + nsAutoCString script(js_buffer, length); + JS::RootedValue v(cx); + + nsString convertedScript; + bool isUTF8 = IsUtf8(script); + if (isUTF8) { + CopyUTF8toUTF16(script, convertedScript); + } else { + nsContentUtils::ReportToConsoleNonLocalized( + nsLiteralString( + u"Your AutoConfig file is ASCII. Please convert it to UTF-8."), + nsIScriptError::warningFlag, "autoconfig"_ns, nullptr); + /* If the length is 0, the conversion failed. Fallback to ASCII */ + convertedScript = NS_ConvertASCIItoUTF16(script); + } + { + JSAutoRealm ar(cx, autoconfigSystemSb); + JS::Rooted<JS::Value> value(cx, JS::BooleanValue(isUTF8)); + if (!JS_DefineProperty(cx, autoconfigSystemSb, "gIsUTF8", value, + JSPROP_ENUMERATE)) { + return NS_ERROR_UNEXPECTED; + } + } + nsresult rv = + xpc->EvalInSandboxObject(convertedScript, filename, cx, sandbox, &v); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} diff --git a/extensions/pref/autoconfig/src/nsJSConfigTriggers.h b/extensions/pref/autoconfig/src/nsJSConfigTriggers.h new file mode 100644 index 0000000000..7e7de38d3e --- /dev/null +++ b/extensions/pref/autoconfig/src/nsJSConfigTriggers.h @@ -0,0 +1,23 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsConfigTriggers_h +#define nsConfigTriggers_h + +#include "nscore.h" +#include "js/TypeDecls.h" + +nsresult EvaluateAdminConfigScript(const char* js_buffer, size_t length, + const char* filename, bool bGlobalContext, + bool bCallbacks, bool skipFirstLine, + bool isPrivileged = false); + +nsresult EvaluateAdminConfigScript(JS::HandleObject sandbox, + const char* js_buffer, size_t length, + const char* filename, bool bGlobalContext, + bool bCallbacks, bool skipFirstLine); + +#endif // nsConfigTriggers_h diff --git a/extensions/pref/autoconfig/src/nsReadConfig.cpp b/extensions/pref/autoconfig/src/nsReadConfig.cpp new file mode 100644 index 0000000000..e7c4db2e2b --- /dev/null +++ b/extensions/pref/autoconfig/src/nsReadConfig.cpp @@ -0,0 +1,298 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsReadConfig.h" +#include "nsJSConfigTriggers.h" + +#include "mozilla/Logging.h" +#include "mozilla/Components.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsIAppStartup.h" +#include "nsIChannel.h" +#include "nsContentUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsIFile.h" +#include "nsIInputStream.h" +#include "nsIObserverService.h" +#include "nsIPrefBranch.h" +#include "nsIPrefService.h" +#include "nsIPromptService.h" +#include "nsIStringBundle.h" +#include "nsNetUtil.h" +#include "nsString.h" +#include "nsCRT.h" +#include "nspr.h" +#include "nsXULAppAPI.h" + +using namespace mozilla; + +extern bool sandboxEnabled; + +extern mozilla::LazyLogModule MCD; + +extern nsresult CentralizedAdminPrefManagerInit(bool aSandboxEnabled); +extern nsresult CentralizedAdminPrefManagerFinish(); + +static nsresult DisplayError(void) { + nsresult rv; + + nsCOMPtr<nsIPromptService> promptService = + do_GetService("@mozilla.org/embedcomp/prompt-service;1"); + if (!promptService) return NS_ERROR_FAILURE; + + nsCOMPtr<nsIStringBundleService> bundleService = + do_GetService(NS_STRINGBUNDLE_CONTRACTID); + if (!bundleService) return NS_ERROR_FAILURE; + + nsCOMPtr<nsIStringBundle> bundle; + bundleService->CreateBundle( + "chrome://autoconfig/locale/autoconfig.properties", + getter_AddRefs(bundle)); + if (!bundle) return NS_ERROR_FAILURE; + + nsAutoString title; + rv = bundle->GetStringFromName("readConfigTitle", title); + if (NS_FAILED(rv)) return rv; + + nsAutoString err; + rv = bundle->GetStringFromName("readConfigMsg", err); + if (NS_FAILED(rv)) return rv; + + return promptService->Alert(nullptr, title.get(), err.get()); +} + +// nsISupports Implementation + +NS_IMPL_ISUPPORTS(nsReadConfig, nsIObserver) + +nsReadConfig::nsReadConfig() : mRead(false) {} + +nsresult nsReadConfig::Init() { + nsresult rv; + + nsCOMPtr<nsIObserverService> observerService = + do_GetService("@mozilla.org/observer-service;1", &rv); + + if (observerService) { + rv = + observerService->AddObserver(this, NS_PREFSERVICE_READ_TOPIC_ID, false); + } + return (rv); +} + +nsReadConfig::~nsReadConfig() { CentralizedAdminPrefManagerFinish(); } + +NS_IMETHODIMP nsReadConfig::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* someData) { + nsresult rv = NS_OK; + + if (!nsCRT::strcmp(aTopic, NS_PREFSERVICE_READ_TOPIC_ID)) { + rv = readConfigFile(); + // Don't show error alerts if the sandbox is enabled, just show + // sandbox warning. + if (NS_FAILED(rv)) { + if (sandboxEnabled) { + nsContentUtils::ReportToConsoleNonLocalized( + u"Autoconfig is sandboxed by default. See " + "https://support.mozilla.org/products/" + "firefox-enterprise for more information."_ns, + nsIScriptError::warningFlag, "autoconfig"_ns, nullptr); + } else { + rv = DisplayError(); + if (NS_FAILED(rv)) { + nsCOMPtr<nsIAppStartup> appStartup = + components::AppStartup::Service(); + if (appStartup) { + bool userAllowedQuit = true; + appStartup->Quit(nsIAppStartup::eAttemptQuit, 0, &userAllowedQuit); + } + } + } + } + } + return rv; +} + +/** + * This is the blocklist for known bad autoconfig files. + */ +static const char* gBlockedConfigs[] = {"dsengine.cfg"}; + +nsresult nsReadConfig::readConfigFile() { + nsresult rv = NS_OK; + nsAutoCString lockFileName; + nsAutoCString lockVendor; + uint32_t fileNameLen = 0; + + nsCOMPtr<nsIPrefBranch> defaultPrefBranch; + nsCOMPtr<nsIPrefService> prefService = + do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; + + rv = + prefService->GetDefaultBranch(nullptr, getter_AddRefs(defaultPrefBranch)); + if (NS_FAILED(rv)) return rv; + + constexpr auto channel = nsLiteralCString{MOZ_STRINGIFY(MOZ_UPDATE_CHANNEL)}; + + bool sandboxEnabled = + channel.EqualsLiteral("beta") || channel.EqualsLiteral("release"); + + mozilla::Unused << defaultPrefBranch->GetBoolPref( + "general.config.sandbox_enabled", &sandboxEnabled); + + rv = defaultPrefBranch->GetCharPref("general.config.filename", lockFileName); + + if (NS_FAILED(rv)) return rv; + + MOZ_LOG(MCD, LogLevel::Debug, + ("general.config.filename = %s\n", lockFileName.get())); + + for (size_t index = 0, len = mozilla::ArrayLength(gBlockedConfigs); + index < len; ++index) { + if (lockFileName == gBlockedConfigs[index]) { + // This is NS_OK because we don't want to show an error to the user + return rv; + } + } + + // This needs to be read only once. + // + if (!mRead) { + // Initiate the new JS Context for Preference management + + rv = CentralizedAdminPrefManagerInit(sandboxEnabled); + if (NS_FAILED(rv)) return rv; + + // Open and evaluate function calls to set/lock/unlock prefs + rv = openAndEvaluateJSFile("prefcalls.js", 0, false, false); + if (NS_FAILED(rv)) return rv; + + mRead = true; + } + // If the lockFileName is nullptr return ok, because no lockFile will be used + + // Once the config file is read, we should check that the vendor name + // is consistent By checking for the vendor name after reading the config + // file we allow for the preference to be set (and locked) by the creator + // of the cfg file meaning the file can not be renamed (successfully). + + nsCOMPtr<nsIPrefBranch> prefBranch; + rv = prefService->GetBranch(nullptr, getter_AddRefs(prefBranch)); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t obscureValue = 0; + (void)defaultPrefBranch->GetIntPref("general.config.obscure_value", + &obscureValue); + MOZ_LOG(MCD, LogLevel::Debug, + ("evaluating .cfg file %s with obscureValue %d\n", lockFileName.get(), + obscureValue)); + rv = openAndEvaluateJSFile(lockFileName.get(), obscureValue, true, true); + if (NS_FAILED(rv)) { + MOZ_LOG(MCD, LogLevel::Debug, + ("error evaluating .cfg file %s %" PRIx32 "\n", lockFileName.get(), + static_cast<uint32_t>(rv))); + return rv; + } + + rv = prefBranch->GetCharPref("general.config.filename", lockFileName); + if (NS_FAILED(rv)) + // There is NO REASON we should ever get here. This is POST reading + // of the config file. + return NS_ERROR_FAILURE; + + rv = prefBranch->GetCharPref("general.config.vendor", lockVendor); + // If vendor is not nullptr, do this check + if (NS_SUCCEEDED(rv)) { + fileNameLen = strlen(lockFileName.get()); + + // lockVendor and lockFileName should be the same with the addtion of + // .cfg to the filename by checking this post reading of the cfg file + // this value can be set within the cfg file adding a level of security. + + if (PL_strncmp(lockFileName.get(), lockVendor.get(), fileNameLen - 4) != 0) + return NS_ERROR_FAILURE; + } + + // get the value of the autoconfig url + nsAutoCString urlName; + rv = prefBranch->GetCharPref("autoadmin.global_config_url", urlName); + if (NS_SUCCEEDED(rv) && !urlName.IsEmpty()) { + // Instantiating nsAutoConfig object if the pref is present + mAutoConfig = new nsAutoConfig(); + + rv = mAutoConfig->Init(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mAutoConfig->SetConfigURL(urlName.get()); + } + + return NS_OK; +} // ReadConfigFile + +nsresult nsReadConfig::openAndEvaluateJSFile(const char* aFileName, + int32_t obscureValue, + bool isEncoded, bool isBinDir) { + nsresult rv; + + nsCOMPtr<nsIInputStream> inStr; + if (isBinDir) { + nsCOMPtr<nsIFile> jsFile; + rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(jsFile)); + if (NS_FAILED(rv)) return rv; + + rv = jsFile->AppendNative(nsDependentCString(aFileName)); + if (NS_FAILED(rv)) return rv; + + rv = NS_NewLocalFileInputStream(getter_AddRefs(inStr), jsFile); + if (NS_FAILED(rv)) return rv; + + } else { + nsAutoCString location("resource://gre/defaults/autoconfig/"); + location += aFileName; + + nsCOMPtr<nsIURI> uri; + rv = NS_NewURI(getter_AddRefs(uri), location); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIChannel> channel; + rv = NS_NewChannel(getter_AddRefs(channel), uri, + nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + nsIContentPolicy::TYPE_OTHER); + NS_ENSURE_SUCCESS(rv, rv); + + rv = channel->Open(getter_AddRefs(inStr)); + NS_ENSURE_SUCCESS(rv, rv); + } + + uint64_t fs64; + uint32_t amt = 0; + rv = inStr->Available(&fs64); + if (NS_FAILED(rv)) return rv; + // This used to use PR_Malloc(), which doesn't support over 4GB. + if (fs64 > UINT32_MAX) return NS_ERROR_FILE_TOO_BIG; + uint32_t fs = (uint32_t)fs64; + + char* buf = (char*)malloc(fs * sizeof(char)); + if (!buf) return NS_ERROR_OUT_OF_MEMORY; + + rv = inStr->Read(buf, (uint32_t)fs, &amt); + NS_ASSERTION((amt == fs), "failed to read the entire configuration file!!"); + if (NS_SUCCEEDED(rv)) { + if (obscureValue > 0) { + // Unobscure file by subtracting some value from every char. + for (uint32_t i = 0; i < amt; i++) buf[i] -= obscureValue; + } + rv = EvaluateAdminConfigScript(buf, amt, aFileName, false, true, isEncoded, + !isBinDir); + } + inStr->Close(); + free(buf); + + return rv; +} diff --git a/extensions/pref/autoconfig/src/nsReadConfig.h b/extensions/pref/autoconfig/src/nsReadConfig.h new file mode 100644 index 0000000000..465a517186 --- /dev/null +++ b/extensions/pref/autoconfig/src/nsReadConfig.h @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsReadConfig_h +#define nsReadConfig_h + +#include "mozilla/RefPtr.h" +#include "nsAutoConfig.h" +#include "nsIObserver.h" + +class nsReadConfig final : public nsIObserver { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIOBSERVER + + nsReadConfig(); + + nsresult Init(); + + protected: + virtual ~nsReadConfig(); + + nsresult readConfigFile(); + nsresult openAndEvaluateJSFile(const char* aFileName, int32_t obscureValue, + bool isEncoded, bool isBinDir); + bool mRead; + + private: + RefPtr<nsAutoConfig> mAutoConfig; +}; + +#endif diff --git a/extensions/pref/autoconfig/src/prefcalls.js b/extensions/pref/autoconfig/src/prefcalls.js new file mode 100644 index 0000000000..0d8e4cfdc0 --- /dev/null +++ b/extensions/pref/autoconfig/src/prefcalls.js @@ -0,0 +1,240 @@ +/* global processLDAPValues */ +/* -*- tab-width: 4; indent-tabs-mode: nil; js-indent-level: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* globals gSandbox */ + +const nsILDAPURL = Ci.nsILDAPURL; +const LDAPURLContractID = "@mozilla.org/network/ldap-url;1"; +const nsILDAPSyncQuery = Ci.nsILDAPSyncQuery; +const LDAPSyncQueryContractID = "@mozilla.org/ldapsyncquery;1"; +const nsIPrefService = Ci.nsIPrefService; +const PrefServiceContractID = "@mozilla.org/preferences-service;1"; + +// ChromeUtils isn't available here, so we can't use Services.* +/* eslint-disable mozilla/use-services */ + +var gVersion; +var gIsUTF8; + +function getPrefBranch() { + var prefService = Cc[PrefServiceContractID].getService(nsIPrefService); + return prefService.getBranch(null); +} + +function pref(prefName, value) { + try { + var prefBranch = getPrefBranch(); + + if (typeof value == "string") { + if (gIsUTF8) { + prefBranch.setStringPref(prefName, value); + return; + } + prefBranch.setCharPref(prefName, value); + } else if (typeof value == "number") { + prefBranch.setIntPref(prefName, value); + } else if (typeof value == "boolean") { + prefBranch.setBoolPref(prefName, value); + } + } catch (e) { + displayError("pref", e); + } +} + +function defaultPref(prefName, value) { + try { + var prefService = Cc[PrefServiceContractID].getService(nsIPrefService); + var prefBranch = prefService.getDefaultBranch(null); + if (typeof value == "string") { + if (gIsUTF8) { + prefBranch.setStringPref(prefName, value); + return; + } + prefBranch.setCharPref(prefName, value); + } else if (typeof value == "number") { + prefBranch.setIntPref(prefName, value); + } else if (typeof value == "boolean") { + prefBranch.setBoolPref(prefName, value); + } + } catch (e) { + displayError("defaultPref", e); + } +} + +function lockPref(prefName, value) { + try { + var prefBranch = getPrefBranch(); + + if (prefBranch.prefIsLocked(prefName)) { + prefBranch.unlockPref(prefName); + } + + defaultPref(prefName, value); + + prefBranch.lockPref(prefName); + } catch (e) { + displayError("lockPref", e); + } +} + +function unlockPref(prefName) { + try { + var prefBranch = getPrefBranch(); + prefBranch.unlockPref(prefName); + } catch (e) { + displayError("unlockPref", e); + } +} + +function getPref(prefName) { + try { + var prefBranch = getPrefBranch(); + + switch (prefBranch.getPrefType(prefName)) { + case prefBranch.PREF_STRING: + if (gIsUTF8) { + return prefBranch.getStringPref(prefName); + } + return prefBranch.getCharPref(prefName); + + case prefBranch.PREF_INT: + return prefBranch.getIntPref(prefName); + + case prefBranch.PREF_BOOL: + return prefBranch.getBoolPref(prefName); + default: + return null; + } + } catch (e) { + displayError("getPref", e); + } + return undefined; +} + +function clearPref(prefName) { + try { + var prefBranch = getPrefBranch(); + prefBranch.clearUserPref(prefName); + } catch (e) {} +} + +function setLDAPVersion(version) { + gVersion = version; +} + +function getLDAPAttributes(host, base, filter, attribs, isSecure) { + try { + var urlSpec = + "ldap" + + (isSecure ? "s" : "") + + "://" + + host + + (isSecure ? ":636" : "") + + "/" + + base + + "?" + + attribs + + "?sub?" + + filter; + + var url = Cc["@mozilla.org/network/io-service;1"] + .getService(Ci.nsIIOService) + .newURI(urlSpec) + .QueryInterface(Ci.nsILDAPURL); + + var ldapquery = Cc[LDAPSyncQueryContractID].createInstance( + nsILDAPSyncQuery + ); + // default to LDAP v3 + if (!gVersion) { + gVersion = Ci.nsILDAPConnection.VERSION3; + } + // user supplied method + if ("processLDAPValues" in gSandbox) { + gSandbox.processLDAPValues(ldapquery.getQueryResults(url, gVersion)); + } else { + processLDAPValues(ldapquery.getQueryResults(url, gVersion)); + } + } catch (e) { + displayError("getLDAPAttributes", e); + } +} + +function getLDAPValue(str, key) { + try { + if (str == null || key == null) { + return null; + } + + var search_key = "\n" + key + "="; + + var start_pos = str.indexOf(search_key); + if (start_pos == -1) { + return null; + } + + start_pos += search_key.length; + + var end_pos = str.indexOf("\n", start_pos); + if (end_pos == -1) { + end_pos = str.length; + } + + return str.substring(start_pos, end_pos); + } catch (e) { + displayError("getLDAPValue", e); + } + return undefined; +} + +function displayError(funcname, message) { + try { + var promptService = Cc[ + "@mozilla.org/embedcomp/prompt-service;1" + ].getService(Ci.nsIPromptService); + var bundle = Cc["@mozilla.org/intl/stringbundle;1"] + .getService(Ci.nsIStringBundleService) + .createBundle("chrome://autoconfig/locale/autoconfig.properties"); + + var title = bundle.GetStringFromName("autoConfigTitle"); + var msg = bundle.formatStringFromName("autoConfigMsg", [funcname]); + promptService.alert(null, title, msg + " " + message); + } catch (e) {} +} + +function getenv(name) { + try { + var environment = Cc["@mozilla.org/process/environment;1"].getService( + Ci.nsIEnvironment + ); + return environment.get(name); + } catch (e) { + displayError("getEnvironment", e); + } + return undefined; +} + +var APIs = { + pref, + defaultPref, + lockPref, + unlockPref, + getPref, + clearPref, + setLDAPVersion, + getLDAPAttributes, + getLDAPValue, + displayError, + getenv, +}; + +for (let [defineAs, func] of Object.entries(APIs)) { + Cu.exportFunction(func, gSandbox, { defineAs }); +} + +Object.defineProperty(Cu.waiveXrays(gSandbox), "gIsUTF8", { + get: Cu.exportFunction(() => gIsUTF8, gSandbox), +}); diff --git a/extensions/pref/autoconfig/test/marionette/autoconfig.cfg b/extensions/pref/autoconfig/test/marionette/autoconfig.cfg new file mode 100644 index 0000000000..1e6fde18bf --- /dev/null +++ b/extensions/pref/autoconfig/test/marionette/autoconfig.cfg @@ -0,0 +1,18 @@ +// # don't remove this comment! (the first line is ignored by Mozilla) + +// Verify this one has a user value +pref("_autoconfig_.test.userpref", "userpref"); + +// Verify this one has a default pref +defaultPref("_autoconfig_.test.defaultpref", "defaultpref"); + +// Verify this one is locked +lockPref("_autoconfig_.test.lockpref", "lockpref"); + +lockPref("_autoconfig_.test.unlockpref", "unlockpref"); +// Verify this one is unlocked +unlockPref("_autoconfig_.test.unlockpref"); + +pref("_autoconfig_.test.clearpref", "clearpref"); +// Verify this one has no value +clearPref("_autoconfig_.test.clearpref"); diff --git a/extensions/pref/autoconfig/test/marionette/autoconfig.js b/extensions/pref/autoconfig/test/marionette/autoconfig.js new file mode 100644 index 0000000000..c891c5d108 --- /dev/null +++ b/extensions/pref/autoconfig/test/marionette/autoconfig.js @@ -0,0 +1,5 @@ +/* global pref */ +pref("general.config.sandbox_enabled", true); +pref("general.config.filename", "autoconfig.cfg"); +pref("general.config.vendor", "autoconfig"); +pref("general.config.obscure_value", 0); diff --git a/extensions/pref/autoconfig/test/marionette/manifest.ini b/extensions/pref/autoconfig/test/marionette/manifest.ini new file mode 100644 index 0000000000..c10c20c2ae --- /dev/null +++ b/extensions/pref/autoconfig/test/marionette/manifest.ini @@ -0,0 +1 @@ +[test_autoconfig.py]
diff --git a/extensions/pref/autoconfig/test/marionette/test_autoconfig.py b/extensions/pref/autoconfig/test/marionette/test_autoconfig.py new file mode 100644 index 0000000000..2cf520f86a --- /dev/null +++ b/extensions/pref/autoconfig/test/marionette/test_autoconfig.py @@ -0,0 +1,103 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from __future__ import absolute_import + +import os +import shutil + +from marionette_harness import MarionetteTestCase + + +class TestAutoConfig(MarionetteTestCase): + def tearDown(self): + self.marionette.quit(clean=True) + + if hasattr(self, "pref_file"): + os.remove(self.pref_file) + if hasattr(self, "autoconfig_file"): + os.remove(self.autoconfig_file) + + super(TestAutoConfig, self).tearDown() + + def pref_has_user_value(self, pref): + with self.marionette.using_context("chrome"): + return self.marionette.execute_script( + """ + return Services.prefs.prefHasUserValue(arguments[0]); + """, + script_args=(pref,), + ) + + def pref_is_locked(self, pref): + with self.marionette.using_context("chrome"): + return self.marionette.execute_script( + """ + return Services.prefs.prefIsLocked(arguments[0]); + """, + script_args=(pref,), + ) + + def test_autoconfig(self): + with self.marionette.using_context("chrome"): + self.exe_dir = self.marionette.execute_script( + """ + return Services.dirsvc.get("GreD", Ci.nsIFile).path; + """ + ) + + self.marionette.quit() + + test_dir = os.path.dirname(__file__) + self.pref_file = os.path.join(self.exe_dir, "defaults", "pref", "autoconfig.js") + shutil.copyfile(os.path.join(test_dir, "autoconfig.js"), self.pref_file) + self.autoconfig_file = os.path.join(self.exe_dir, "autoconfig.cfg") + shutil.copyfile(os.path.join(test_dir, "autoconfig.cfg"), self.autoconfig_file) + + self.marionette.start_session() + + with self.marionette.using_context("chrome"): + self.assertTrue( + self.pref_has_user_value("_autoconfig_.test.userpref"), + "Pref should have user value", + ) + + self.assertEqual( + self.marionette.get_pref("_autoconfig_.test.userpref"), + "userpref", + "User pref should be set", + ) + + self.assertEqual( + self.marionette.get_pref("_autoconfig_.test.defaultpref", True), + "defaultpref", + "Default pref should be set", + ) + + self.assertTrue( + self.pref_is_locked("_autoconfig_.test.lockpref"), + "Pref should be locked", + ) + + self.assertEqual( + self.marionette.get_pref("_autoconfig_.test.lockpref"), + "lockpref", + "Locked pref should be set", + ) + + self.assertFalse( + self.pref_is_locked("_autoconfig_.test.unlockpref"), + "Pref should be unlocked", + ) + + self.assertEqual( + self.marionette.get_pref("_autoconfig_.test.unlockpref"), + "unlockpref", + "Unlocked pref should be set", + ) + + self.assertFalse( + self.pref_has_user_value("_autoconfig_.test.clearpref"), + "Pref should be cleared", + ) diff --git a/extensions/pref/autoconfig/test/unit/autoconfig-all.cfg b/extensions/pref/autoconfig/test/unit/autoconfig-all.cfg new file mode 100644 index 0000000000..f636c3ec22 --- /dev/null +++ b/extensions/pref/autoconfig/test/unit/autoconfig-all.cfg @@ -0,0 +1,31 @@ +// # don't remove this comment! (the first line is ignored by Mozilla) + +// Verify this one has a user value +pref("_autoconfig_.test.userpref", "userpref"); + +// Verify this one has a default pref +defaultPref("_autoconfig_.test.defaultpref", "defaultpref"); + +// Verify this one is locked +lockPref("_autoconfig_.test.lockpref", "lockpref"); + +lockPref("_autoconfig_.test.unlockpref", "unlockpref"); +// Verify this one is unlocked +unlockPref("_autoconfig_.test.unlockpref"); + +pref("_autoconfig_.test.clearpref", "clearpref"); +// Verify this one has no value +clearPref("_autoconfig_.test.clearpref"); + +// Verify this one is set to the correct value +pref("_autoconfig_.test.getpref.query", "getpref"); +pref("_autoconfig_.test.getpref", getPref("_autoconfig_.test.getpref.query")); + +// Verify this one is set to the correct value +pref("_autoconfig_.test.getenv", getenv("AUTOCONFIG_TEST_GETENV")); + +// Since we can't test displayError directly, verify that it +// exists and is a function +pref("_autoconfig_.test.displayerror", typeof(displayError)); + +// We are not getPrefBranch because it is being removed diff --git a/extensions/pref/autoconfig/test/unit/autoconfig-chromecheck.cfg b/extensions/pref/autoconfig/test/unit/autoconfig-chromecheck.cfg new file mode 100644 index 0000000000..6f04144896 --- /dev/null +++ b/extensions/pref/autoconfig/test/unit/autoconfig-chromecheck.cfg @@ -0,0 +1,3 @@ +// # don't remove this comment! (the first line is ignored by Mozilla) + +lockPref("_test.string.typeofComponents", typeof Components); diff --git a/extensions/pref/autoconfig/test/unit/autoconfig-latin1.cfg b/extensions/pref/autoconfig/test/unit/autoconfig-latin1.cfg new file mode 100644 index 0000000000..6b96c65fd0 --- /dev/null +++ b/extensions/pref/autoconfig/test/unit/autoconfig-latin1.cfg @@ -0,0 +1,6 @@ +// # don't remove this comment! (the first line is ignored by Mozilla) +// © +lockPref("_test.string.ASCII", "ASCII"); +lockPref("_test.string.non-ASCII", "日本語"); +lockPref("_test.string.getPref", getPref("_test.string.non-ASCII")); +lockPref("_test.string.gIsUTF8", String(this.gIsUTF8)); diff --git a/extensions/pref/autoconfig/test/unit/autoconfig-utf8.cfg b/extensions/pref/autoconfig/test/unit/autoconfig-utf8.cfg new file mode 100644 index 0000000000..eec7899420 --- /dev/null +++ b/extensions/pref/autoconfig/test/unit/autoconfig-utf8.cfg @@ -0,0 +1,6 @@ +// # don't remove this comment! (the first line is ignored by Mozilla) + +lockPref("_test.string.ASCII", "UTF-8"); +lockPref("_test.string.non-ASCII", "日本語"); +lockPref("_test.string.getPref", getPref("_test.string.non-ASCII")); +lockPref("_test.string.gIsUTF8", String(this.gIsUTF8)); diff --git a/extensions/pref/autoconfig/test/unit/autoconfig.js b/extensions/pref/autoconfig/test/unit/autoconfig.js new file mode 100644 index 0000000000..284c9c66c5 --- /dev/null +++ b/extensions/pref/autoconfig/test/unit/autoconfig.js @@ -0,0 +1,6 @@ +/* global pref */ +pref("general.config.sandbox_enabled", true); +pref("general.config.filename", "autoconfig.cfg"); +pref("general.config.vendor", "autoconfig"); +pref("general.config.obscure_value", 0); + diff --git a/extensions/pref/autoconfig/test/unit/test_autoconfig.js b/extensions/pref/autoconfig/test/unit/test_autoconfig.js new file mode 100644 index 0000000000..2951cadbd1 --- /dev/null +++ b/extensions/pref/autoconfig/test/unit/test_autoconfig.js @@ -0,0 +1,88 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* eslint no-unsafe-finally: "off"*/ +/* Turning off this rule to allow control flow operations in finally block + * http://eslint.org/docs/rules/no-unsafe-finally */ + +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); + +function run_test() { + let env = Cc["@mozilla.org/process/environment;1"].getService( + Ci.nsIEnvironment + ); + let prefs = Services.prefs.getBranch(null); + let defPrefs = Services.prefs.getDefaultBranch(null); + + let greD = Services.dirsvc.get("GreD", Ci.nsIFile); + let defaultPrefD = Services.dirsvc.get("PrfDef", Ci.nsIFile); + let testDir = do_get_cwd(); + + try { + let autoConfigJS = testDir.clone(); + autoConfigJS.append("autoconfig.js"); + autoConfigJS.copyTo(defaultPrefD, "autoconfig.js"); + + // Make sure nsReadConfig is initialized. + Cc["@mozilla.org/readconfig;1"].getService(Ci.nsISupports); + Services.prefs.resetPrefs(); + + let autoConfigCfg = testDir.clone(); + autoConfigCfg.append("autoconfig-all.cfg"); + autoConfigCfg.copyTo(greD, "autoconfig.cfg"); + + env.set("AUTOCONFIG_TEST_GETENV", "getenv"); + + Services.obs.notifyObservers( + Services.prefs, + "prefservice:before-read-userprefs" + ); + + ok(prefs.prefHasUserValue("_autoconfig_.test.userpref")); + equal("userpref", prefs.getStringPref("_autoconfig_.test.userpref")); + + equal( + "defaultpref", + defPrefs.getStringPref("_autoconfig_.test.defaultpref") + ); + equal("defaultpref", prefs.getStringPref("_autoconfig_.test.defaultpref")); + + ok(prefs.prefIsLocked("_autoconfig_.test.lockpref")); + equal("lockpref", prefs.getStringPref("_autoconfig_.test.lockpref")); + + ok(!prefs.prefIsLocked("_autoconfig_.test.unlockpref")); + equal("unlockpref", prefs.getStringPref("_autoconfig_.test.unlockpref")); + + ok(!prefs.prefHasUserValue("_autoconfig_.test.clearpref")); + + equal("getpref", prefs.getStringPref("_autoconfig_.test.getpref")); + + equal("getenv", prefs.getStringPref("_autoconfig_.test.getenv")); + + equal("function", prefs.getStringPref("_autoconfig_.test.displayerror")); + + Services.prefs.resetPrefs(); + } finally { + try { + let autoConfigJS = defaultPrefD.clone(); + autoConfigJS.append("autoconfig.js"); + autoConfigJS.remove(false); + } catch (e) { + if (e.result != Cr.NS_ERROR_FILE_NOT_FOUND) { + throw e; + } + } + + try { + let autoConfigCfg = greD.clone(); + autoConfigCfg.append("autoconfig.cfg"); + autoConfigCfg.remove(false); + } catch (e) { + if (e.result != Cr.NS_ERROR_FILE_NOT_FOUND) { + throw e; + } + } + + Services.prefs.resetPrefs(); + } +} diff --git a/extensions/pref/autoconfig/test/unit/test_autoconfig_nonascii.js b/extensions/pref/autoconfig/test/unit/test_autoconfig_nonascii.js new file mode 100644 index 0000000000..cda3d6188f --- /dev/null +++ b/extensions/pref/autoconfig/test/unit/test_autoconfig_nonascii.js @@ -0,0 +1,110 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* eslint no-unsafe-finally: "off"*/ +/* Turning off this rule to allow control flow operations in finally block + * http://eslint.org/docs/rules/no-unsafe-finally */ + +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); + +function run_test() { + let greD = Services.dirsvc.get("GreD", Ci.nsIFile); + let defaultPrefD = Services.dirsvc.get("PrfDef", Ci.nsIFile); + let testDir = do_get_cwd(); + + try { + let autoConfigJS = testDir.clone(); + autoConfigJS.append("autoconfig.js"); + autoConfigJS.copyTo(defaultPrefD, "autoconfig.js"); + + // Make sure nsReadConfig is initialized. + Cc["@mozilla.org/readconfig;1"].getService(Ci.nsISupports); + Services.prefs.resetPrefs(); + + var tests = [ + { + filename: "autoconfig-utf8.cfg", + prefs: { + "_test.string.ASCII": "UTF-8", + "_test.string.non-ASCII": "日本語", + "_test.string.getPref": "日本語", + "_test.string.gIsUTF8": "true", + }, + }, + { + filename: "autoconfig-latin1.cfg", + prefs: { + "_test.string.ASCII": "ASCII", + "_test.string.non-ASCII": "日本語", + "_test.string.getPref": "日本語", + "_test.string.gIsUTF8": "false", + }, + }, + { + filename: "autoconfig-chromecheck.cfg", + prefs: { + "_test.string.typeofComponents": "undefined", + }, + }, + ]; + + function testAutoConfig(test) { + // Make sure pref values are unset. + for (let prefName in test.prefs) { + Assert.equal( + Ci.nsIPrefBranch.PREF_INVALID, + Services.prefs.getPrefType(prefName) + ); + } + + let autoConfigCfg = testDir.clone(); + autoConfigCfg.append(test.filename); + autoConfigCfg.copyTo(greD, "autoconfig.cfg"); + + Services.obs.notifyObservers( + Services.prefs, + "prefservice:before-read-userprefs" + ); + + for (let prefName in test.prefs) { + Assert.equal( + test.prefs[prefName], + Services.prefs.getStringPref(prefName) + ); + } + + Services.prefs.resetPrefs(); + // Make sure pref values are reset. + for (let prefName in test.prefs) { + Assert.equal( + Ci.nsIPrefBranch.PREF_INVALID, + Services.prefs.getPrefType(prefName) + ); + } + } + + tests.forEach(testAutoConfig); + } finally { + try { + let autoConfigJS = defaultPrefD.clone(); + autoConfigJS.append("autoconfig.js"); + autoConfigJS.remove(false); + } catch (e) { + if (e.result != Cr.NS_ERROR_FILE_NOT_FOUND) { + throw e; + } + } + + try { + let autoConfigCfg = greD.clone(); + autoConfigCfg.append("autoconfig.cfg"); + autoConfigCfg.remove(false); + } catch (e) { + if (e.result != Cr.NS_ERROR_FILE_NOT_FOUND) { + throw e; + } + } + + Services.prefs.resetPrefs(); + } +} diff --git a/extensions/pref/autoconfig/test/unit/xpcshell.ini b/extensions/pref/autoconfig/test/unit/xpcshell.ini new file mode 100644 index 0000000000..3d6d17d5fb --- /dev/null +++ b/extensions/pref/autoconfig/test/unit/xpcshell.ini @@ -0,0 +1,12 @@ +[DEFAULT] +head = +skip-if = toolkit == 'android' +support-files = + autoconfig-all.cfg + autoconfig-latin1.cfg + autoconfig-utf8.cfg + autoconfig-chromecheck.cfg + autoconfig.js + +[test_autoconfig.js] +[test_autoconfig_nonascii.js] |