michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #ifdef MOZ_LOGGING michael@0: // sorry, this has to be before the pre-compiled header michael@0: #define FORCE_PR_LOG /* Allow logging in the release build */ michael@0: #endif michael@0: #include "nsAutoConfig.h" michael@0: #include "nsIURI.h" michael@0: #include "nsIHttpChannel.h" michael@0: #include "nsIFileStreams.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsAppDirectoryServiceDefs.h" michael@0: #include "prmem.h" michael@0: #include "nsIObserverService.h" michael@0: #include "nsLiteralString.h" michael@0: #include "nsIPromptService.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "nsIStringBundle.h" michael@0: #include "nsCRT.h" michael@0: #include "nspr.h" michael@0: #include michael@0: michael@0: PRLogModuleInfo *MCD; michael@0: michael@0: extern nsresult EvaluateAdminConfigScript(const char *js_buffer, size_t length, michael@0: const char *filename, michael@0: bool bGlobalContext, michael@0: bool bCallbacks, michael@0: bool skipFirstLine); michael@0: michael@0: // nsISupports Implementation michael@0: michael@0: NS_IMPL_ISUPPORTS(nsAutoConfig, nsIAutoConfig, nsITimerCallback, nsIStreamListener, nsIObserver, nsIRequestObserver, nsISupportsWeakReference) michael@0: michael@0: nsAutoConfig::nsAutoConfig() michael@0: { michael@0: } michael@0: michael@0: nsresult nsAutoConfig::Init() michael@0: { michael@0: // member initializers and constructor code michael@0: michael@0: nsresult rv; michael@0: mLoaded = false; michael@0: michael@0: // Registering the object as an observer to the profile-after-change topic michael@0: nsCOMPtr observerService = michael@0: do_GetService("@mozilla.org/observer-service;1", &rv); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: rv = observerService->AddObserver(this,"profile-after-change", true); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsAutoConfig::~nsAutoConfig() michael@0: { michael@0: } michael@0: michael@0: // attribute string configURL michael@0: NS_IMETHODIMP nsAutoConfig::GetConfigURL(char **aConfigURL) michael@0: { michael@0: if (!aConfigURL) michael@0: return NS_ERROR_NULL_POINTER; michael@0: michael@0: if (mConfigURL.IsEmpty()) { michael@0: *aConfigURL = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: *aConfigURL = ToNewCString(mConfigURL); michael@0: if (!*aConfigURL) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: return NS_OK; michael@0: } michael@0: NS_IMETHODIMP nsAutoConfig::SetConfigURL(const char *aConfigURL) michael@0: { michael@0: if (!aConfigURL) michael@0: return NS_ERROR_NULL_POINTER; michael@0: mConfigURL.Assign(aConfigURL); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsAutoConfig::OnStartRequest(nsIRequest *request, nsISupports *context) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsAutoConfig::OnDataAvailable(nsIRequest *request, michael@0: nsISupports *context, michael@0: nsIInputStream *aIStream, michael@0: uint64_t aSourceOffset, michael@0: uint32_t aLength) michael@0: { michael@0: uint32_t amt, size; michael@0: nsresult rv; michael@0: char buf[1024]; michael@0: michael@0: while (aLength) { michael@0: size = std::min(aLength, sizeof(buf)); michael@0: rv = aIStream->Read(buf, size, &amt); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: mBuf.Append(buf, amt); michael@0: aLength -= amt; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsAutoConfig::OnStopRequest(nsIRequest *request, nsISupports *context, michael@0: nsresult aStatus) michael@0: { michael@0: nsresult rv; michael@0: michael@0: // If the request is failed, go read the failover.jsc file michael@0: if (NS_FAILED(aStatus)) { michael@0: PR_LOG(MCD, PR_LOG_DEBUG, ("mcd request failed with status %x\n", aStatus)); michael@0: return readOfflineFile(); michael@0: } michael@0: michael@0: // Checking for the http response, if failure go read the failover file. michael@0: nsCOMPtr pHTTPCon(do_QueryInterface(request)); michael@0: if (pHTTPCon) { michael@0: uint32_t httpStatus; michael@0: pHTTPCon->GetResponseStatus(&httpStatus); michael@0: if (httpStatus != 200) michael@0: { michael@0: PR_LOG(MCD, PR_LOG_DEBUG, ("mcd http request failed with status %x\n", httpStatus)); michael@0: return readOfflineFile(); michael@0: } michael@0: } michael@0: michael@0: // Send the autoconfig.jsc to javascript engine. michael@0: michael@0: rv = EvaluateAdminConfigScript(mBuf.get(), mBuf.Length(), michael@0: nullptr, false,true, false); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: michael@0: // Write the autoconfig.jsc to failover.jsc (cached copy) michael@0: rv = writeFailoverFile(); michael@0: michael@0: if (NS_FAILED(rv)) michael@0: NS_WARNING("Error writing failover.jsc file"); michael@0: michael@0: // Releasing the lock to allow the main thread to start execution michael@0: mLoaded = true; michael@0: michael@0: return NS_OK; michael@0: } michael@0: // there is an error in parsing of the autoconfig file. michael@0: NS_WARNING("Error reading autoconfig.jsc from the network, reading the offline version"); michael@0: return readOfflineFile(); michael@0: } michael@0: michael@0: // Notify method as a TimerCallBack function michael@0: NS_IMETHODIMP nsAutoConfig::Notify(nsITimer *timer) michael@0: { michael@0: downloadAutoConfig(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* Observe() is called twice: once at the instantiation time and other michael@0: after the profile is set. It doesn't do anything but return NS_OK during the michael@0: creation time. Second time it calls downloadAutoConfig(). michael@0: */ michael@0: michael@0: NS_IMETHODIMP nsAutoConfig::Observe(nsISupports *aSubject, michael@0: const char *aTopic, michael@0: const char16_t *someData) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: if (!nsCRT::strcmp(aTopic, "profile-after-change")) { michael@0: michael@0: // We will be calling downloadAutoConfig even if there is no profile michael@0: // name. Nothing will be passed as a parameter to the URL and the michael@0: // default case will be picked up by the script. michael@0: michael@0: rv = downloadAutoConfig(); michael@0: michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult nsAutoConfig::downloadAutoConfig() michael@0: { michael@0: nsresult rv; michael@0: nsAutoCString emailAddr; michael@0: nsXPIDLCString urlName; michael@0: static bool firstTime = true; michael@0: michael@0: if (mConfigURL.IsEmpty()) { michael@0: PR_LOG(MCD, PR_LOG_DEBUG, ("global config url is empty - did you set autoadmin.global_config_url?\n")); michael@0: NS_WARNING("AutoConfig called without global_config_url"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // If there is an email address appended as an argument to the ConfigURL michael@0: // in the previous read, we need to remove it when timer kicks in and michael@0: // downloads the autoconfig file again. michael@0: // If necessary, the email address will be added again as an argument. michael@0: int32_t index = mConfigURL.RFindChar((char16_t)'?'); michael@0: if (index != -1) michael@0: mConfigURL.Truncate(index); michael@0: michael@0: // Clean up the previous read, the new read is going to use the same buffer michael@0: if (!mBuf.IsEmpty()) michael@0: mBuf.Truncate(0); michael@0: michael@0: // Get the preferences branch and save it to the member variable michael@0: if (!mPrefBranch) { michael@0: nsCOMPtr prefs = michael@0: do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: rv = prefs->GetBranch(nullptr,getter_AddRefs(mPrefBranch)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: } michael@0: michael@0: // Check to see if the network is online/offline michael@0: nsCOMPtr ios = do_GetService(NS_IOSERVICE_CONTRACTID, &rv); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: bool offline; michael@0: rv = ios->GetOffline(&offline); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: if (offline) { michael@0: bool offlineFailover; michael@0: rv = mPrefBranch->GetBoolPref("autoadmin.offline_failover", michael@0: &offlineFailover); michael@0: // Read the failover.jsc if the network is offline and the pref says so michael@0: if (NS_SUCCEEDED(rv) && offlineFailover) michael@0: return readOfflineFile(); michael@0: } michael@0: michael@0: /* Append user's identity at the end of the URL if the pref says so. michael@0: First we are checking for the user's email address but if it is not michael@0: available in the case where the client is used without messenger, user's michael@0: profile name will be used as an unique identifier michael@0: */ michael@0: bool appendMail; michael@0: rv = mPrefBranch->GetBoolPref("autoadmin.append_emailaddr", &appendMail); michael@0: if (NS_SUCCEEDED(rv) && appendMail) { michael@0: rv = getEmailAddr(emailAddr); michael@0: if (NS_SUCCEEDED(rv) && emailAddr.get()) { michael@0: /* Adding the unique identifier at the end of autoconfig URL. michael@0: In this case the autoconfig URL is a script and michael@0: emailAddr as passed as an argument michael@0: */ michael@0: mConfigURL.Append("?"); michael@0: mConfigURL.Append(emailAddr); michael@0: } michael@0: } michael@0: michael@0: // create a new url michael@0: nsCOMPtr url; michael@0: nsCOMPtr channel; michael@0: michael@0: rv = NS_NewURI(getter_AddRefs(url), mConfigURL.get(), nullptr, nullptr); michael@0: if (NS_FAILED(rv)) michael@0: { michael@0: PR_LOG(MCD, PR_LOG_DEBUG, ("failed to create URL - is autoadmin.global_config_url valid? - %s\n", mConfigURL.get())); michael@0: return rv; michael@0: } michael@0: michael@0: PR_LOG(MCD, PR_LOG_DEBUG, ("running MCD url %s\n", mConfigURL.get())); michael@0: // open a channel for the url michael@0: rv = NS_NewChannel(getter_AddRefs(channel),url, nullptr, nullptr, nullptr, nsIRequest::INHIBIT_PERSISTENT_CACHING | nsIRequest::LOAD_BYPASS_CACHE); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: rv = channel->AsyncOpen(this, nullptr); michael@0: if (NS_FAILED(rv)) { michael@0: readOfflineFile(); michael@0: return rv; michael@0: } michael@0: michael@0: // Set a repeating timer if the pref is set. michael@0: // This is to be done only once. michael@0: // Also We are having the event queue processing only for the startup michael@0: // It is not needed with the repeating timer. michael@0: if (firstTime) { michael@0: firstTime = false; michael@0: michael@0: // Getting the current thread. If we start an AsyncOpen, the thread michael@0: // needs to wait before the reading of autoconfig is done michael@0: michael@0: nsCOMPtr thread = do_GetCurrentThread(); michael@0: NS_ENSURE_STATE(thread); michael@0: michael@0: /* process events until we're finished. AutoConfig.jsc reading needs michael@0: to be finished before the browser starts loading up michael@0: We are waiting for the mLoaded which will be set through michael@0: onStopRequest or readOfflineFile methods michael@0: There is a possibility of deadlock so we need to make sure michael@0: that mLoaded will be set to true in any case (success/failure) michael@0: */ michael@0: michael@0: while (!mLoaded) michael@0: NS_ENSURE_STATE(NS_ProcessNextEvent(thread)); michael@0: michael@0: int32_t minutes; michael@0: rv = mPrefBranch->GetIntPref("autoadmin.refresh_interval", michael@0: &minutes); michael@0: if (NS_SUCCEEDED(rv) && minutes > 0) { michael@0: // Create a new timer and pass this nsAutoConfig michael@0: // object as a timer callback. michael@0: mTimer = do_CreateInstance("@mozilla.org/timer;1",&rv); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: rv = mTimer->InitWithCallback(this, minutes * 60 * 1000, michael@0: nsITimer::TYPE_REPEATING_SLACK); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: } michael@0: } //first_time michael@0: michael@0: return NS_OK; michael@0: } // nsPref::downloadAutoConfig() michael@0: michael@0: michael@0: michael@0: nsresult nsAutoConfig::readOfflineFile() michael@0: { michael@0: nsresult rv; michael@0: michael@0: /* Releasing the lock to allow main thread to start michael@0: execution. At this point we do not need to stall michael@0: the thread since all network activities are done. michael@0: */ michael@0: mLoaded = true; michael@0: michael@0: bool failCache; michael@0: rv = mPrefBranch->GetBoolPref("autoadmin.failover_to_cached", &failCache); michael@0: if (NS_SUCCEEDED(rv) && !failCache) { michael@0: // disable network connections and return. michael@0: michael@0: nsCOMPtr ios = michael@0: do_GetService(NS_IOSERVICE_CONTRACTID, &rv); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: bool offline; michael@0: rv = ios->GetOffline(&offline); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: if (!offline) { michael@0: rv = ios->SetOffline(true); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: } michael@0: michael@0: // lock the "network.online" prference so user cannot toggle back to michael@0: // online mode. michael@0: rv = mPrefBranch->SetBoolPref("network.online", false); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: mPrefBranch->LockPref("network.online"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* faiover_to_cached is set to true so michael@0: Open the file and read the content. michael@0: execute the javascript file michael@0: */ michael@0: michael@0: nsCOMPtr failoverFile; michael@0: rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, michael@0: getter_AddRefs(failoverFile)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: failoverFile->AppendNative(NS_LITERAL_CSTRING("failover.jsc")); michael@0: rv = evaluateLocalFile(failoverFile); michael@0: if (NS_FAILED(rv)) michael@0: NS_WARNING("Couldn't open failover.jsc, going back to default prefs"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult nsAutoConfig::evaluateLocalFile(nsIFile *file) michael@0: { michael@0: nsresult rv; michael@0: nsCOMPtr inStr; michael@0: michael@0: rv = NS_NewLocalFileInputStream(getter_AddRefs(inStr), file); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: int64_t fileSize; michael@0: file->GetFileSize(&fileSize); michael@0: uint32_t fs = fileSize; // Converting 64 bit structure to unsigned int michael@0: char *buf = (char *)PR_Malloc(fs * sizeof(char)); michael@0: if (!buf) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: uint32_t amt = 0; michael@0: rv = inStr->Read(buf, fs, &amt); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: EvaluateAdminConfigScript(buf, fs, nullptr, false, michael@0: true, false); michael@0: } michael@0: inStr->Close(); michael@0: PR_Free(buf); michael@0: return rv; michael@0: } michael@0: michael@0: nsresult nsAutoConfig::writeFailoverFile() michael@0: { michael@0: nsresult rv; michael@0: nsCOMPtr failoverFile; michael@0: nsCOMPtr outStr; michael@0: uint32_t amt; michael@0: michael@0: rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, michael@0: getter_AddRefs(failoverFile)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: failoverFile->AppendNative(NS_LITERAL_CSTRING("failover.jsc")); michael@0: michael@0: rv = NS_NewLocalFileOutputStream(getter_AddRefs(outStr), failoverFile); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: rv = outStr->Write(mBuf.get(),mBuf.Length(),&amt); michael@0: outStr->Close(); michael@0: return rv; michael@0: } michael@0: michael@0: nsresult nsAutoConfig::getEmailAddr(nsACString & emailAddr) michael@0: { michael@0: michael@0: nsresult rv; michael@0: nsXPIDLCString prefValue; michael@0: michael@0: /* Getting an email address through set of three preferences: michael@0: First getting a default account with michael@0: "mail.accountmanager.defaultaccount" michael@0: second getting an associated id with the default account michael@0: Third getting an email address with id michael@0: */ michael@0: michael@0: rv = mPrefBranch->GetCharPref("mail.accountmanager.defaultaccount", michael@0: getter_Copies(prefValue)); michael@0: if (NS_SUCCEEDED(rv) && !prefValue.IsEmpty()) { michael@0: emailAddr = NS_LITERAL_CSTRING("mail.account.") + michael@0: prefValue + NS_LITERAL_CSTRING(".identities"); michael@0: rv = mPrefBranch->GetCharPref(PromiseFlatCString(emailAddr).get(), michael@0: getter_Copies(prefValue)); michael@0: if (NS_FAILED(rv) || prefValue.IsEmpty()) michael@0: return PromptForEMailAddress(emailAddr); michael@0: int32_t commandIndex = prefValue.FindChar(','); michael@0: if (commandIndex != kNotFound) michael@0: prefValue.Truncate(commandIndex); michael@0: emailAddr = NS_LITERAL_CSTRING("mail.identity.") + michael@0: prefValue + NS_LITERAL_CSTRING(".useremail"); michael@0: rv = mPrefBranch->GetCharPref(PromiseFlatCString(emailAddr).get(), michael@0: getter_Copies(prefValue)); michael@0: if (NS_FAILED(rv) || prefValue.IsEmpty()) michael@0: return PromptForEMailAddress(emailAddr); michael@0: emailAddr = prefValue; michael@0: } michael@0: else { michael@0: // look for 4.x pref in case we just migrated. michael@0: rv = mPrefBranch->GetCharPref("mail.identity.useremail", michael@0: getter_Copies(prefValue)); michael@0: if (NS_SUCCEEDED(rv) && !prefValue.IsEmpty()) michael@0: emailAddr = prefValue; michael@0: else michael@0: PromptForEMailAddress(emailAddr); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult nsAutoConfig::PromptForEMailAddress(nsACString &emailAddress) michael@0: { michael@0: nsresult rv; michael@0: nsCOMPtr promptService = do_GetService("@mozilla.org/embedcomp/prompt-service;1", &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: nsCOMPtr bundleService = do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr bundle; michael@0: rv = bundleService->CreateBundle("chrome://autoconfig/locale/autoconfig.properties", michael@0: getter_AddRefs(bundle)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsXPIDLString title; michael@0: rv = bundle->GetStringFromName(MOZ_UTF16("emailPromptTitle"), getter_Copies(title)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsXPIDLString err; michael@0: rv = bundle->GetStringFromName(MOZ_UTF16("emailPromptMsg"), getter_Copies(err)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: bool check = false; michael@0: nsXPIDLString emailResult; michael@0: bool success; michael@0: rv = promptService->Prompt(nullptr, title.get(), err.get(), getter_Copies(emailResult), nullptr, &check, &success); michael@0: if (!success) michael@0: return NS_ERROR_FAILURE; michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: LossyCopyUTF16toASCII(emailResult, emailAddress); michael@0: return NS_OK; michael@0: }