1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/extensions/pref/autoconfig/src/nsAutoConfig.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,520 @@ 1.4 +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#ifdef MOZ_LOGGING 1.10 +// sorry, this has to be before the pre-compiled header 1.11 +#define FORCE_PR_LOG /* Allow logging in the release build */ 1.12 +#endif 1.13 +#include "nsAutoConfig.h" 1.14 +#include "nsIURI.h" 1.15 +#include "nsIHttpChannel.h" 1.16 +#include "nsIFileStreams.h" 1.17 +#include "nsThreadUtils.h" 1.18 +#include "nsAppDirectoryServiceDefs.h" 1.19 +#include "prmem.h" 1.20 +#include "nsIObserverService.h" 1.21 +#include "nsLiteralString.h" 1.22 +#include "nsIPromptService.h" 1.23 +#include "nsIServiceManager.h" 1.24 +#include "nsIStringBundle.h" 1.25 +#include "nsCRT.h" 1.26 +#include "nspr.h" 1.27 +#include <algorithm> 1.28 + 1.29 +PRLogModuleInfo *MCD; 1.30 + 1.31 +extern nsresult EvaluateAdminConfigScript(const char *js_buffer, size_t length, 1.32 + const char *filename, 1.33 + bool bGlobalContext, 1.34 + bool bCallbacks, 1.35 + bool skipFirstLine); 1.36 + 1.37 +// nsISupports Implementation 1.38 + 1.39 +NS_IMPL_ISUPPORTS(nsAutoConfig, nsIAutoConfig, nsITimerCallback, nsIStreamListener, nsIObserver, nsIRequestObserver, nsISupportsWeakReference) 1.40 + 1.41 +nsAutoConfig::nsAutoConfig() 1.42 +{ 1.43 +} 1.44 + 1.45 +nsresult nsAutoConfig::Init() 1.46 +{ 1.47 + // member initializers and constructor code 1.48 + 1.49 + nsresult rv; 1.50 + mLoaded = false; 1.51 + 1.52 + // Registering the object as an observer to the profile-after-change topic 1.53 + nsCOMPtr<nsIObserverService> observerService = 1.54 + do_GetService("@mozilla.org/observer-service;1", &rv); 1.55 + if (NS_FAILED(rv)) 1.56 + return rv; 1.57 + 1.58 + rv = observerService->AddObserver(this,"profile-after-change", true); 1.59 + 1.60 + return rv; 1.61 +} 1.62 + 1.63 +nsAutoConfig::~nsAutoConfig() 1.64 +{ 1.65 +} 1.66 + 1.67 +// attribute string configURL 1.68 +NS_IMETHODIMP nsAutoConfig::GetConfigURL(char **aConfigURL) 1.69 +{ 1.70 + if (!aConfigURL) 1.71 + return NS_ERROR_NULL_POINTER; 1.72 + 1.73 + if (mConfigURL.IsEmpty()) { 1.74 + *aConfigURL = nullptr; 1.75 + return NS_OK; 1.76 + } 1.77 + 1.78 + *aConfigURL = ToNewCString(mConfigURL); 1.79 + if (!*aConfigURL) 1.80 + return NS_ERROR_OUT_OF_MEMORY; 1.81 + return NS_OK; 1.82 +} 1.83 +NS_IMETHODIMP nsAutoConfig::SetConfigURL(const char *aConfigURL) 1.84 +{ 1.85 + if (!aConfigURL) 1.86 + return NS_ERROR_NULL_POINTER; 1.87 + mConfigURL.Assign(aConfigURL); 1.88 + return NS_OK; 1.89 +} 1.90 + 1.91 +NS_IMETHODIMP 1.92 +nsAutoConfig::OnStartRequest(nsIRequest *request, nsISupports *context) 1.93 +{ 1.94 + return NS_OK; 1.95 +} 1.96 + 1.97 + 1.98 +NS_IMETHODIMP 1.99 +nsAutoConfig::OnDataAvailable(nsIRequest *request, 1.100 + nsISupports *context, 1.101 + nsIInputStream *aIStream, 1.102 + uint64_t aSourceOffset, 1.103 + uint32_t aLength) 1.104 +{ 1.105 + uint32_t amt, size; 1.106 + nsresult rv; 1.107 + char buf[1024]; 1.108 + 1.109 + while (aLength) { 1.110 + size = std::min<size_t>(aLength, sizeof(buf)); 1.111 + rv = aIStream->Read(buf, size, &amt); 1.112 + if (NS_FAILED(rv)) 1.113 + return rv; 1.114 + mBuf.Append(buf, amt); 1.115 + aLength -= amt; 1.116 + } 1.117 + return NS_OK; 1.118 +} 1.119 + 1.120 + 1.121 +NS_IMETHODIMP 1.122 +nsAutoConfig::OnStopRequest(nsIRequest *request, nsISupports *context, 1.123 + nsresult aStatus) 1.124 +{ 1.125 + nsresult rv; 1.126 + 1.127 + // If the request is failed, go read the failover.jsc file 1.128 + if (NS_FAILED(aStatus)) { 1.129 + PR_LOG(MCD, PR_LOG_DEBUG, ("mcd request failed with status %x\n", aStatus)); 1.130 + return readOfflineFile(); 1.131 + } 1.132 + 1.133 + // Checking for the http response, if failure go read the failover file. 1.134 + nsCOMPtr<nsIHttpChannel> pHTTPCon(do_QueryInterface(request)); 1.135 + if (pHTTPCon) { 1.136 + uint32_t httpStatus; 1.137 + pHTTPCon->GetResponseStatus(&httpStatus); 1.138 + if (httpStatus != 200) 1.139 + { 1.140 + PR_LOG(MCD, PR_LOG_DEBUG, ("mcd http request failed with status %x\n", httpStatus)); 1.141 + return readOfflineFile(); 1.142 + } 1.143 + } 1.144 + 1.145 + // Send the autoconfig.jsc to javascript engine. 1.146 + 1.147 + rv = EvaluateAdminConfigScript(mBuf.get(), mBuf.Length(), 1.148 + nullptr, false,true, false); 1.149 + if (NS_SUCCEEDED(rv)) { 1.150 + 1.151 + // Write the autoconfig.jsc to failover.jsc (cached copy) 1.152 + rv = writeFailoverFile(); 1.153 + 1.154 + if (NS_FAILED(rv)) 1.155 + NS_WARNING("Error writing failover.jsc file"); 1.156 + 1.157 + // Releasing the lock to allow the main thread to start execution 1.158 + mLoaded = true; 1.159 + 1.160 + return NS_OK; 1.161 + } 1.162 + // there is an error in parsing of the autoconfig file. 1.163 + NS_WARNING("Error reading autoconfig.jsc from the network, reading the offline version"); 1.164 + return readOfflineFile(); 1.165 +} 1.166 + 1.167 +// Notify method as a TimerCallBack function 1.168 +NS_IMETHODIMP nsAutoConfig::Notify(nsITimer *timer) 1.169 +{ 1.170 + downloadAutoConfig(); 1.171 + return NS_OK; 1.172 +} 1.173 + 1.174 +/* Observe() is called twice: once at the instantiation time and other 1.175 + after the profile is set. It doesn't do anything but return NS_OK during the 1.176 + creation time. Second time it calls downloadAutoConfig(). 1.177 +*/ 1.178 + 1.179 +NS_IMETHODIMP nsAutoConfig::Observe(nsISupports *aSubject, 1.180 + const char *aTopic, 1.181 + const char16_t *someData) 1.182 +{ 1.183 + nsresult rv = NS_OK; 1.184 + if (!nsCRT::strcmp(aTopic, "profile-after-change")) { 1.185 + 1.186 + // We will be calling downloadAutoConfig even if there is no profile 1.187 + // name. Nothing will be passed as a parameter to the URL and the 1.188 + // default case will be picked up by the script. 1.189 + 1.190 + rv = downloadAutoConfig(); 1.191 + 1.192 + } 1.193 + 1.194 + return rv; 1.195 +} 1.196 + 1.197 +nsresult nsAutoConfig::downloadAutoConfig() 1.198 +{ 1.199 + nsresult rv; 1.200 + nsAutoCString emailAddr; 1.201 + nsXPIDLCString urlName; 1.202 + static bool firstTime = true; 1.203 + 1.204 + if (mConfigURL.IsEmpty()) { 1.205 + PR_LOG(MCD, PR_LOG_DEBUG, ("global config url is empty - did you set autoadmin.global_config_url?\n")); 1.206 + NS_WARNING("AutoConfig called without global_config_url"); 1.207 + return NS_OK; 1.208 + } 1.209 + 1.210 + // If there is an email address appended as an argument to the ConfigURL 1.211 + // in the previous read, we need to remove it when timer kicks in and 1.212 + // downloads the autoconfig file again. 1.213 + // If necessary, the email address will be added again as an argument. 1.214 + int32_t index = mConfigURL.RFindChar((char16_t)'?'); 1.215 + if (index != -1) 1.216 + mConfigURL.Truncate(index); 1.217 + 1.218 + // Clean up the previous read, the new read is going to use the same buffer 1.219 + if (!mBuf.IsEmpty()) 1.220 + mBuf.Truncate(0); 1.221 + 1.222 + // Get the preferences branch and save it to the member variable 1.223 + if (!mPrefBranch) { 1.224 + nsCOMPtr<nsIPrefService> prefs = 1.225 + do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); 1.226 + if (NS_FAILED(rv)) 1.227 + return rv; 1.228 + 1.229 + rv = prefs->GetBranch(nullptr,getter_AddRefs(mPrefBranch)); 1.230 + if (NS_FAILED(rv)) 1.231 + return rv; 1.232 + } 1.233 + 1.234 + // Check to see if the network is online/offline 1.235 + nsCOMPtr<nsIIOService> ios = do_GetService(NS_IOSERVICE_CONTRACTID, &rv); 1.236 + if (NS_FAILED(rv)) 1.237 + return rv; 1.238 + 1.239 + bool offline; 1.240 + rv = ios->GetOffline(&offline); 1.241 + if (NS_FAILED(rv)) 1.242 + return rv; 1.243 + 1.244 + if (offline) { 1.245 + bool offlineFailover; 1.246 + rv = mPrefBranch->GetBoolPref("autoadmin.offline_failover", 1.247 + &offlineFailover); 1.248 + // Read the failover.jsc if the network is offline and the pref says so 1.249 + if (NS_SUCCEEDED(rv) && offlineFailover) 1.250 + return readOfflineFile(); 1.251 + } 1.252 + 1.253 + /* Append user's identity at the end of the URL if the pref says so. 1.254 + First we are checking for the user's email address but if it is not 1.255 + available in the case where the client is used without messenger, user's 1.256 + profile name will be used as an unique identifier 1.257 + */ 1.258 + bool appendMail; 1.259 + rv = mPrefBranch->GetBoolPref("autoadmin.append_emailaddr", &appendMail); 1.260 + if (NS_SUCCEEDED(rv) && appendMail) { 1.261 + rv = getEmailAddr(emailAddr); 1.262 + if (NS_SUCCEEDED(rv) && emailAddr.get()) { 1.263 + /* Adding the unique identifier at the end of autoconfig URL. 1.264 + In this case the autoconfig URL is a script and 1.265 + emailAddr as passed as an argument 1.266 + */ 1.267 + mConfigURL.Append("?"); 1.268 + mConfigURL.Append(emailAddr); 1.269 + } 1.270 + } 1.271 + 1.272 + // create a new url 1.273 + nsCOMPtr<nsIURI> url; 1.274 + nsCOMPtr<nsIChannel> channel; 1.275 + 1.276 + rv = NS_NewURI(getter_AddRefs(url), mConfigURL.get(), nullptr, nullptr); 1.277 + if (NS_FAILED(rv)) 1.278 + { 1.279 + PR_LOG(MCD, PR_LOG_DEBUG, ("failed to create URL - is autoadmin.global_config_url valid? - %s\n", mConfigURL.get())); 1.280 + return rv; 1.281 + } 1.282 + 1.283 + PR_LOG(MCD, PR_LOG_DEBUG, ("running MCD url %s\n", mConfigURL.get())); 1.284 + // open a channel for the url 1.285 + rv = NS_NewChannel(getter_AddRefs(channel),url, nullptr, nullptr, nullptr, nsIRequest::INHIBIT_PERSISTENT_CACHING | nsIRequest::LOAD_BYPASS_CACHE); 1.286 + if (NS_FAILED(rv)) 1.287 + return rv; 1.288 + 1.289 + rv = channel->AsyncOpen(this, nullptr); 1.290 + if (NS_FAILED(rv)) { 1.291 + readOfflineFile(); 1.292 + return rv; 1.293 + } 1.294 + 1.295 + // Set a repeating timer if the pref is set. 1.296 + // This is to be done only once. 1.297 + // Also We are having the event queue processing only for the startup 1.298 + // It is not needed with the repeating timer. 1.299 + if (firstTime) { 1.300 + firstTime = false; 1.301 + 1.302 + // Getting the current thread. If we start an AsyncOpen, the thread 1.303 + // needs to wait before the reading of autoconfig is done 1.304 + 1.305 + nsCOMPtr<nsIThread> thread = do_GetCurrentThread(); 1.306 + NS_ENSURE_STATE(thread); 1.307 + 1.308 + /* process events until we're finished. AutoConfig.jsc reading needs 1.309 + to be finished before the browser starts loading up 1.310 + We are waiting for the mLoaded which will be set through 1.311 + onStopRequest or readOfflineFile methods 1.312 + There is a possibility of deadlock so we need to make sure 1.313 + that mLoaded will be set to true in any case (success/failure) 1.314 + */ 1.315 + 1.316 + while (!mLoaded) 1.317 + NS_ENSURE_STATE(NS_ProcessNextEvent(thread)); 1.318 + 1.319 + int32_t minutes; 1.320 + rv = mPrefBranch->GetIntPref("autoadmin.refresh_interval", 1.321 + &minutes); 1.322 + if (NS_SUCCEEDED(rv) && minutes > 0) { 1.323 + // Create a new timer and pass this nsAutoConfig 1.324 + // object as a timer callback. 1.325 + mTimer = do_CreateInstance("@mozilla.org/timer;1",&rv); 1.326 + if (NS_FAILED(rv)) 1.327 + return rv; 1.328 + rv = mTimer->InitWithCallback(this, minutes * 60 * 1000, 1.329 + nsITimer::TYPE_REPEATING_SLACK); 1.330 + if (NS_FAILED(rv)) 1.331 + return rv; 1.332 + } 1.333 + } //first_time 1.334 + 1.335 + return NS_OK; 1.336 +} // nsPref::downloadAutoConfig() 1.337 + 1.338 + 1.339 + 1.340 +nsresult nsAutoConfig::readOfflineFile() 1.341 +{ 1.342 + nsresult rv; 1.343 + 1.344 + /* Releasing the lock to allow main thread to start 1.345 + execution. At this point we do not need to stall 1.346 + the thread since all network activities are done. 1.347 + */ 1.348 + mLoaded = true; 1.349 + 1.350 + bool failCache; 1.351 + rv = mPrefBranch->GetBoolPref("autoadmin.failover_to_cached", &failCache); 1.352 + if (NS_SUCCEEDED(rv) && !failCache) { 1.353 + // disable network connections and return. 1.354 + 1.355 + nsCOMPtr<nsIIOService> ios = 1.356 + do_GetService(NS_IOSERVICE_CONTRACTID, &rv); 1.357 + if (NS_FAILED(rv)) 1.358 + return rv; 1.359 + 1.360 + bool offline; 1.361 + rv = ios->GetOffline(&offline); 1.362 + if (NS_FAILED(rv)) 1.363 + return rv; 1.364 + 1.365 + if (!offline) { 1.366 + rv = ios->SetOffline(true); 1.367 + if (NS_FAILED(rv)) 1.368 + return rv; 1.369 + } 1.370 + 1.371 + // lock the "network.online" prference so user cannot toggle back to 1.372 + // online mode. 1.373 + rv = mPrefBranch->SetBoolPref("network.online", false); 1.374 + if (NS_FAILED(rv)) 1.375 + return rv; 1.376 + 1.377 + mPrefBranch->LockPref("network.online"); 1.378 + return NS_OK; 1.379 + } 1.380 + 1.381 + /* faiover_to_cached is set to true so 1.382 + Open the file and read the content. 1.383 + execute the javascript file 1.384 + */ 1.385 + 1.386 + nsCOMPtr<nsIFile> failoverFile; 1.387 + rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, 1.388 + getter_AddRefs(failoverFile)); 1.389 + if (NS_FAILED(rv)) 1.390 + return rv; 1.391 + 1.392 + failoverFile->AppendNative(NS_LITERAL_CSTRING("failover.jsc")); 1.393 + rv = evaluateLocalFile(failoverFile); 1.394 + if (NS_FAILED(rv)) 1.395 + NS_WARNING("Couldn't open failover.jsc, going back to default prefs"); 1.396 + return NS_OK; 1.397 +} 1.398 + 1.399 +nsresult nsAutoConfig::evaluateLocalFile(nsIFile *file) 1.400 +{ 1.401 + nsresult rv; 1.402 + nsCOMPtr<nsIInputStream> inStr; 1.403 + 1.404 + rv = NS_NewLocalFileInputStream(getter_AddRefs(inStr), file); 1.405 + if (NS_FAILED(rv)) 1.406 + return rv; 1.407 + 1.408 + int64_t fileSize; 1.409 + file->GetFileSize(&fileSize); 1.410 + uint32_t fs = fileSize; // Converting 64 bit structure to unsigned int 1.411 + char *buf = (char *)PR_Malloc(fs * sizeof(char)); 1.412 + if (!buf) 1.413 + return NS_ERROR_OUT_OF_MEMORY; 1.414 + 1.415 + uint32_t amt = 0; 1.416 + rv = inStr->Read(buf, fs, &amt); 1.417 + if (NS_SUCCEEDED(rv)) { 1.418 + EvaluateAdminConfigScript(buf, fs, nullptr, false, 1.419 + true, false); 1.420 + } 1.421 + inStr->Close(); 1.422 + PR_Free(buf); 1.423 + return rv; 1.424 +} 1.425 + 1.426 +nsresult nsAutoConfig::writeFailoverFile() 1.427 +{ 1.428 + nsresult rv; 1.429 + nsCOMPtr<nsIFile> failoverFile; 1.430 + nsCOMPtr<nsIOutputStream> outStr; 1.431 + uint32_t amt; 1.432 + 1.433 + rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, 1.434 + getter_AddRefs(failoverFile)); 1.435 + if (NS_FAILED(rv)) 1.436 + return rv; 1.437 + 1.438 + failoverFile->AppendNative(NS_LITERAL_CSTRING("failover.jsc")); 1.439 + 1.440 + rv = NS_NewLocalFileOutputStream(getter_AddRefs(outStr), failoverFile); 1.441 + if (NS_FAILED(rv)) 1.442 + return rv; 1.443 + rv = outStr->Write(mBuf.get(),mBuf.Length(),&amt); 1.444 + outStr->Close(); 1.445 + return rv; 1.446 +} 1.447 + 1.448 +nsresult nsAutoConfig::getEmailAddr(nsACString & emailAddr) 1.449 +{ 1.450 + 1.451 + nsresult rv; 1.452 + nsXPIDLCString prefValue; 1.453 + 1.454 + /* Getting an email address through set of three preferences: 1.455 + First getting a default account with 1.456 + "mail.accountmanager.defaultaccount" 1.457 + second getting an associated id with the default account 1.458 + Third getting an email address with id 1.459 + */ 1.460 + 1.461 + rv = mPrefBranch->GetCharPref("mail.accountmanager.defaultaccount", 1.462 + getter_Copies(prefValue)); 1.463 + if (NS_SUCCEEDED(rv) && !prefValue.IsEmpty()) { 1.464 + emailAddr = NS_LITERAL_CSTRING("mail.account.") + 1.465 + prefValue + NS_LITERAL_CSTRING(".identities"); 1.466 + rv = mPrefBranch->GetCharPref(PromiseFlatCString(emailAddr).get(), 1.467 + getter_Copies(prefValue)); 1.468 + if (NS_FAILED(rv) || prefValue.IsEmpty()) 1.469 + return PromptForEMailAddress(emailAddr); 1.470 + int32_t commandIndex = prefValue.FindChar(','); 1.471 + if (commandIndex != kNotFound) 1.472 + prefValue.Truncate(commandIndex); 1.473 + emailAddr = NS_LITERAL_CSTRING("mail.identity.") + 1.474 + prefValue + NS_LITERAL_CSTRING(".useremail"); 1.475 + rv = mPrefBranch->GetCharPref(PromiseFlatCString(emailAddr).get(), 1.476 + getter_Copies(prefValue)); 1.477 + if (NS_FAILED(rv) || prefValue.IsEmpty()) 1.478 + return PromptForEMailAddress(emailAddr); 1.479 + emailAddr = prefValue; 1.480 + } 1.481 + else { 1.482 + // look for 4.x pref in case we just migrated. 1.483 + rv = mPrefBranch->GetCharPref("mail.identity.useremail", 1.484 + getter_Copies(prefValue)); 1.485 + if (NS_SUCCEEDED(rv) && !prefValue.IsEmpty()) 1.486 + emailAddr = prefValue; 1.487 + else 1.488 + PromptForEMailAddress(emailAddr); 1.489 + } 1.490 + 1.491 + return NS_OK; 1.492 +} 1.493 + 1.494 +nsresult nsAutoConfig::PromptForEMailAddress(nsACString &emailAddress) 1.495 +{ 1.496 + nsresult rv; 1.497 + nsCOMPtr<nsIPromptService> promptService = do_GetService("@mozilla.org/embedcomp/prompt-service;1", &rv); 1.498 + NS_ENSURE_SUCCESS(rv, rv); 1.499 + nsCOMPtr<nsIStringBundleService> bundleService = do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv); 1.500 + NS_ENSURE_SUCCESS(rv, rv); 1.501 + 1.502 + nsCOMPtr<nsIStringBundle> bundle; 1.503 + rv = bundleService->CreateBundle("chrome://autoconfig/locale/autoconfig.properties", 1.504 + getter_AddRefs(bundle)); 1.505 + NS_ENSURE_SUCCESS(rv, rv); 1.506 + 1.507 + nsXPIDLString title; 1.508 + rv = bundle->GetStringFromName(MOZ_UTF16("emailPromptTitle"), getter_Copies(title)); 1.509 + NS_ENSURE_SUCCESS(rv, rv); 1.510 + 1.511 + nsXPIDLString err; 1.512 + rv = bundle->GetStringFromName(MOZ_UTF16("emailPromptMsg"), getter_Copies(err)); 1.513 + NS_ENSURE_SUCCESS(rv, rv); 1.514 + bool check = false; 1.515 + nsXPIDLString emailResult; 1.516 + bool success; 1.517 + rv = promptService->Prompt(nullptr, title.get(), err.get(), getter_Copies(emailResult), nullptr, &check, &success); 1.518 + if (!success) 1.519 + return NS_ERROR_FAILURE; 1.520 + NS_ENSURE_SUCCESS(rv, rv); 1.521 + LossyCopyUTF16toASCII(emailResult, emailAddress); 1.522 + return NS_OK; 1.523 +}