nsAutoConfig.cpp (14788B)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include "nsAutoConfig.h" 7 #include "nsJSConfigTriggers.h" 8 9 #include "nsIURI.h" 10 #include "nsIHttpChannel.h" 11 #include "nsThreadUtils.h" 12 #include "nsAppDirectoryServiceDefs.h" 13 #include "nsIObserverService.h" 14 #include "nsLiteralString.h" 15 #include "nsIPromptService.h" 16 #include "nsIInputStream.h" 17 #include "nsIOutputStream.h" 18 #include "nsIPrefBranch.h" 19 #include "nsIPrefService.h" 20 #include "nsIStringBundle.h" 21 #include "nsContentUtils.h" 22 #include "nsCRT.h" 23 #include "nsNetCID.h" 24 #include "nsNetUtil.h" 25 #include "nspr.h" 26 #include <algorithm> 27 28 #include "mozilla/IntegerPrintfMacros.h" 29 #include "mozilla/Logging.h" 30 #include "mozilla/SpinEventLoopUntil.h" 31 #include "mozilla/Try.h" 32 33 using mozilla::LogLevel; 34 35 mozilla::LazyLogModule MCD("MCD"); 36 37 // nsISupports Implementation 38 39 NS_IMPL_ISUPPORTS(nsAutoConfig, nsITimerCallback, nsIStreamListener, 40 nsIObserver, nsIRequestObserver, nsISupportsWeakReference, 41 nsINamed) 42 43 nsAutoConfig::nsAutoConfig() {} 44 45 nsresult nsAutoConfig::Init() { 46 // member initializers and constructor code 47 48 nsresult rv; 49 mLoaded = false; 50 51 // Registering the object as an observer to the profile-after-change topic 52 nsCOMPtr<nsIObserverService> observerService = 53 do_GetService("@mozilla.org/observer-service;1", &rv); 54 if (NS_FAILED(rv)) return rv; 55 56 rv = observerService->AddObserver(this, "profile-after-change", true); 57 58 return rv; 59 } 60 61 nsAutoConfig::~nsAutoConfig() {} 62 63 void nsAutoConfig::SetConfigURL(const char* aConfigURL) { 64 mConfigURL.Assign(aConfigURL); 65 } 66 67 NS_IMETHODIMP 68 nsAutoConfig::OnStartRequest(nsIRequest* request) { return NS_OK; } 69 70 NS_IMETHODIMP 71 nsAutoConfig::OnDataAvailable(nsIRequest* request, nsIInputStream* aIStream, 72 uint64_t aSourceOffset, uint32_t aLength) { 73 uint32_t amt, size; 74 nsresult rv; 75 char buf[1024]; 76 77 while (aLength) { 78 size = std::min<size_t>(aLength, sizeof(buf)); 79 rv = aIStream->Read(buf, size, &amt); 80 if (NS_FAILED(rv)) return rv; 81 mBuf.Append(buf, amt); 82 aLength -= amt; 83 } 84 return NS_OK; 85 } 86 87 NS_IMETHODIMP 88 nsAutoConfig::OnStopRequest(nsIRequest* request, nsresult aStatus) { 89 nsresult rv; 90 91 // If the request is failed, go read the failover.jsc file 92 if (NS_FAILED(aStatus)) { 93 MOZ_LOG(MCD, LogLevel::Debug, 94 ("mcd request failed with status %" PRIx32 "\n", 95 static_cast<uint32_t>(aStatus))); 96 return readOfflineFile(); 97 } 98 99 // Checking for the http response, if failure go read the failover file. 100 nsCOMPtr<nsIHttpChannel> pHTTPCon(do_QueryInterface(request)); 101 if (pHTTPCon) { 102 uint32_t httpStatus; 103 rv = pHTTPCon->GetResponseStatus(&httpStatus); 104 if (NS_FAILED(rv) || httpStatus != 200) { 105 MOZ_LOG(MCD, LogLevel::Debug, 106 ("mcd http request failed with status %x\n", httpStatus)); 107 return readOfflineFile(); 108 } 109 } 110 111 // Send the autoconfig.jsc to javascript engine. 112 113 rv = EvaluateAdminConfigScript(mBuf.get(), mBuf.Length(), nullptr, false, 114 true, false); 115 if (NS_SUCCEEDED(rv)) { 116 // Write the autoconfig.jsc to failover.jsc (cached copy) 117 rv = writeFailoverFile(); 118 119 if (NS_FAILED(rv)) NS_WARNING("Error writing failover.jsc file"); 120 121 // Releasing the lock to allow the main thread to start execution 122 mLoaded = true; 123 124 return NS_OK; 125 } 126 // there is an error in parsing of the autoconfig file. 127 NS_WARNING( 128 "Error reading autoconfig.jsc from the network, reading the offline " 129 "version"); 130 return readOfflineFile(); 131 } 132 133 // Notify method as a TimerCallBack function 134 NS_IMETHODIMP nsAutoConfig::Notify(nsITimer* timer) { 135 downloadAutoConfig(); 136 return NS_OK; 137 } 138 139 NS_IMETHODIMP 140 nsAutoConfig::GetName(nsACString& aName) { 141 aName.AssignLiteral("nsAutoConfig"); 142 return NS_OK; 143 } 144 145 /* Observe() is called twice: once at the instantiation time and other 146 after the profile is set. It doesn't do anything but return NS_OK during the 147 creation time. Second time it calls downloadAutoConfig(). 148 */ 149 150 NS_IMETHODIMP nsAutoConfig::Observe(nsISupports* aSubject, const char* aTopic, 151 const char16_t* someData) { 152 nsresult rv = NS_OK; 153 if (!nsCRT::strcmp(aTopic, "profile-after-change")) { 154 // We will be calling downloadAutoConfig even if there is no profile 155 // name. Nothing will be passed as a parameter to the URL and the 156 // default case will be picked up by the script. 157 158 rv = downloadAutoConfig(); 159 } 160 161 return rv; 162 } 163 164 nsresult nsAutoConfig::downloadAutoConfig() { 165 nsresult rv; 166 nsAutoCString emailAddr; 167 static bool firstTime = true; 168 169 if (mConfigURL.IsEmpty()) { 170 MOZ_LOG(MCD, LogLevel::Debug, 171 ("global config url is empty - did you set " 172 "autoadmin.global_config_url?\n")); 173 NS_WARNING("AutoConfig called without global_config_url"); 174 return NS_OK; 175 } 176 177 // If there is an email address appended as an argument to the ConfigURL 178 // in the previous read, we need to remove it when timer kicks in and 179 // downloads the autoconfig file again. 180 // If necessary, the email address will be added again as an argument. 181 int32_t index = mConfigURL.RFindChar((char16_t)'?'); 182 if (index != -1) mConfigURL.Truncate(index); 183 184 // Clean up the previous read, the new read is going to use the same buffer 185 if (!mBuf.IsEmpty()) mBuf.Truncate(0); 186 187 // Get the preferences branch and save it to the member variable 188 if (!mPrefBranch) { 189 nsCOMPtr<nsIPrefService> prefs = 190 do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); 191 if (NS_FAILED(rv)) return rv; 192 193 rv = prefs->GetBranch(nullptr, getter_AddRefs(mPrefBranch)); 194 if (NS_FAILED(rv)) return rv; 195 } 196 197 // Check to see if the network is online/offline 198 nsCOMPtr<nsIIOService> ios = do_GetService(NS_IOSERVICE_CONTRACTID, &rv); 199 if (NS_FAILED(rv)) return rv; 200 201 bool offline; 202 rv = ios->GetOffline(&offline); 203 if (NS_FAILED(rv)) return rv; 204 205 if (offline) { 206 bool offlineFailover; 207 rv = mPrefBranch->GetBoolPref("autoadmin.offline_failover", 208 &offlineFailover); 209 // Read the failover.jsc if the network is offline and the pref says so 210 if (NS_SUCCEEDED(rv) && offlineFailover) return readOfflineFile(); 211 } 212 213 /* Append user's identity at the end of the URL if the pref says so. 214 First we are checking for the user's email address but if it is not 215 available in the case where the client is used without messenger, user's 216 profile name will be used as an unique identifier 217 */ 218 bool appendMail; 219 rv = mPrefBranch->GetBoolPref("autoadmin.append_emailaddr", &appendMail); 220 if (NS_SUCCEEDED(rv) && appendMail) { 221 rv = getEmailAddr(emailAddr); 222 if (NS_SUCCEEDED(rv) && emailAddr.get()) { 223 /* Adding the unique identifier at the end of autoconfig URL. 224 In this case the autoconfig URL is a script and 225 emailAddr as passed as an argument 226 */ 227 mConfigURL.Append('?'); 228 mConfigURL.Append(emailAddr); 229 } 230 } 231 232 // create a new url 233 nsCOMPtr<nsIURI> url; 234 nsCOMPtr<nsIChannel> channel; 235 236 rv = NS_NewURI(getter_AddRefs(url), mConfigURL); 237 if (NS_FAILED(rv)) { 238 MOZ_LOG( 239 MCD, LogLevel::Debug, 240 ("failed to create URL - is autoadmin.global_config_url valid? - %s\n", 241 mConfigURL.get())); 242 return rv; 243 } 244 245 MOZ_LOG(MCD, LogLevel::Debug, ("running MCD url %s\n", mConfigURL.get())); 246 // open a channel for the url 247 rv = NS_NewChannel( 248 getter_AddRefs(channel), url, nsContentUtils::GetSystemPrincipal(), 249 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, 250 nsIContentPolicy::TYPE_OTHER, 251 nullptr, // nsICookieJarSettings 252 nullptr, // PerformanceStorage 253 nullptr, // loadGroup 254 nullptr, // aCallbacks 255 nsIRequest::INHIBIT_PERSISTENT_CACHING | nsIRequest::LOAD_BYPASS_CACHE); 256 257 if (NS_FAILED(rv)) return rv; 258 259 rv = channel->AsyncOpen(this); 260 if (NS_FAILED(rv)) { 261 readOfflineFile(); 262 return rv; 263 } 264 265 // Set a repeating timer if the pref is set. 266 // This is to be done only once. 267 // Also We are having the event queue processing only for the startup 268 // It is not needed with the repeating timer. 269 if (firstTime) { 270 firstTime = false; 271 272 /* process events until we're finished. AutoConfig.jsc reading needs 273 to be finished before the browser starts loading up 274 We are waiting for the mLoaded which will be set through 275 onStopRequest or readOfflineFile methods 276 There is a possibility of deadlock so we need to make sure 277 that mLoaded will be set to true in any case (success/failure) 278 */ 279 280 if (!mozilla::SpinEventLoopUntil("nsAutoConfig::downloadAutoConfig"_ns, 281 [&]() { return mLoaded; })) { 282 return NS_ERROR_FAILURE; 283 } 284 285 int32_t minutes; 286 rv = mPrefBranch->GetIntPref("autoadmin.refresh_interval", &minutes); 287 if (NS_SUCCEEDED(rv) && minutes > 0) { 288 // Create a new timer and pass this nsAutoConfig 289 // object as a timer callback. 290 mTimer = MOZ_TRY(NS_NewTimerWithCallback(this, minutes * 60 * 1000, 291 nsITimer::TYPE_REPEATING_SLACK)); 292 } 293 } // first_time 294 295 return NS_OK; 296 } // nsPref::downloadAutoConfig() 297 298 nsresult nsAutoConfig::readOfflineFile() { 299 nsresult rv; 300 301 /* Releasing the lock to allow main thread to start 302 execution. At this point we do not need to stall 303 the thread since all network activities are done. 304 */ 305 mLoaded = true; 306 307 bool failCache; 308 rv = mPrefBranch->GetBoolPref("autoadmin.failover_to_cached", &failCache); 309 if (NS_SUCCEEDED(rv) && !failCache) { 310 // disable network connections and return. 311 312 nsCOMPtr<nsIIOService> ios = do_GetService(NS_IOSERVICE_CONTRACTID, &rv); 313 if (NS_FAILED(rv)) return rv; 314 315 bool offline; 316 rv = ios->GetOffline(&offline); 317 if (NS_FAILED(rv)) return rv; 318 319 if (!offline) { 320 rv = ios->SetOffline(true); 321 if (NS_FAILED(rv)) return rv; 322 } 323 324 // lock the "network.online" prference so user cannot toggle back to 325 // online mode. 326 rv = mPrefBranch->SetBoolPref("network.online", false); 327 if (NS_FAILED(rv)) return rv; 328 329 mPrefBranch->LockPref("network.online"); 330 return NS_OK; 331 } 332 333 /* faiover_to_cached is set to true so 334 Open the file and read the content. 335 execute the javascript file 336 */ 337 338 nsCOMPtr<nsIFile> failoverFile; 339 rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, 340 getter_AddRefs(failoverFile)); 341 if (NS_FAILED(rv)) return rv; 342 343 failoverFile->AppendNative("failover.jsc"_ns); 344 rv = evaluateLocalFile(failoverFile); 345 if (NS_FAILED(rv)) 346 NS_WARNING("Couldn't open failover.jsc, going back to default prefs"); 347 return NS_OK; 348 } 349 350 nsresult nsAutoConfig::evaluateLocalFile(nsIFile* file) { 351 nsresult rv; 352 nsCOMPtr<nsIInputStream> inStr; 353 354 rv = NS_NewLocalFileInputStream(getter_AddRefs(inStr), file); 355 if (NS_FAILED(rv)) return rv; 356 357 int64_t fileSize; 358 file->GetFileSize(&fileSize); 359 uint32_t fs = fileSize; // Converting 64 bit structure to unsigned int 360 char* buf = (char*)malloc(fs * sizeof(char)); 361 if (!buf) return NS_ERROR_OUT_OF_MEMORY; 362 363 uint32_t amt = 0; 364 rv = inStr->Read(buf, fs, &amt); 365 if (NS_SUCCEEDED(rv)) { 366 EvaluateAdminConfigScript(buf, fs, nullptr, false, true, false); 367 } 368 inStr->Close(); 369 free(buf); 370 return rv; 371 } 372 373 nsresult nsAutoConfig::writeFailoverFile() { 374 nsresult rv; 375 nsCOMPtr<nsIFile> failoverFile; 376 nsCOMPtr<nsIOutputStream> outStr; 377 uint32_t amt; 378 379 rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, 380 getter_AddRefs(failoverFile)); 381 if (NS_FAILED(rv)) return rv; 382 383 failoverFile->AppendNative("failover.jsc"_ns); 384 385 rv = NS_NewLocalFileOutputStream(getter_AddRefs(outStr), failoverFile); 386 if (NS_FAILED(rv)) return rv; 387 rv = outStr->Write(mBuf.get(), mBuf.Length(), &amt); 388 outStr->Close(); 389 return rv; 390 } 391 392 nsresult nsAutoConfig::getEmailAddr(nsACString& emailAddr) { 393 nsresult rv; 394 nsAutoCString prefValue; 395 396 /* Getting an email address through set of three preferences: 397 First getting a default account with 398 "mail.accountmanager.defaultaccount" 399 second getting an associated id with the default account 400 Third getting an email address with id 401 */ 402 403 rv = 404 mPrefBranch->GetCharPref("mail.accountmanager.defaultaccount", prefValue); 405 if (NS_SUCCEEDED(rv) && !prefValue.IsEmpty()) { 406 emailAddr = "mail.account."_ns + prefValue + ".identities"_ns; 407 rv = mPrefBranch->GetCharPref(PromiseFlatCString(emailAddr).get(), 408 prefValue); 409 if (NS_FAILED(rv) || prefValue.IsEmpty()) 410 return PromptForEMailAddress(emailAddr); 411 int32_t commandIndex = prefValue.FindChar(','); 412 if (commandIndex != kNotFound) prefValue.Truncate(commandIndex); 413 emailAddr = "mail.identity."_ns + prefValue + ".useremail"_ns; 414 rv = mPrefBranch->GetCharPref(PromiseFlatCString(emailAddr).get(), 415 prefValue); 416 if (NS_FAILED(rv) || prefValue.IsEmpty()) 417 return PromptForEMailAddress(emailAddr); 418 emailAddr = prefValue; 419 } else { 420 // look for 4.x pref in case we just migrated. 421 rv = mPrefBranch->GetCharPref("mail.identity.useremail", prefValue); 422 if (NS_SUCCEEDED(rv) && !prefValue.IsEmpty()) 423 emailAddr = prefValue; 424 else 425 PromptForEMailAddress(emailAddr); 426 } 427 428 return NS_OK; 429 } 430 431 nsresult nsAutoConfig::PromptForEMailAddress(nsACString& emailAddress) { 432 nsresult rv; 433 nsCOMPtr<nsIPromptService> promptService = 434 do_GetService("@mozilla.org/prompter;1", &rv); 435 NS_ENSURE_SUCCESS(rv, rv); 436 nsCOMPtr<nsIStringBundleService> bundleService = 437 do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv); 438 NS_ENSURE_SUCCESS(rv, rv); 439 440 nsCOMPtr<nsIStringBundle> bundle; 441 rv = bundleService->CreateBundle( 442 "chrome://autoconfig/locale/autoconfig.properties", 443 getter_AddRefs(bundle)); 444 NS_ENSURE_SUCCESS(rv, rv); 445 446 nsAutoString title; 447 rv = bundle->GetStringFromName("emailPromptTitle", title); 448 NS_ENSURE_SUCCESS(rv, rv); 449 450 nsAutoString err; 451 rv = bundle->GetStringFromName("emailPromptMsg", err); 452 NS_ENSURE_SUCCESS(rv, rv); 453 bool check = false; 454 nsString emailResult; 455 bool success; 456 rv = promptService->Prompt(nullptr, title.get(), err.get(), 457 getter_Copies(emailResult), nullptr, &check, 458 &success); 459 if (!success) return NS_ERROR_FAILURE; 460 NS_ENSURE_SUCCESS(rv, rv); 461 LossyCopyUTF16toASCII(emailResult, emailAddress); 462 return NS_OK; 463 }