nsChromeRegistryChrome.cpp (20314B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=2 sts=2 sw=2 et tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "mozilla/dom/ContentParent.h" 8 #include "RegistryMessageUtils.h" 9 #include "nsResProtocolHandler.h" 10 11 #include "nsChromeRegistryChrome.h" 12 13 #if defined(XP_WIN) 14 # include <windows.h> 15 #elif defined(XP_MACOSX) 16 # include <CoreServices/CoreServices.h> 17 #endif 18 19 #include "nsArrayEnumerator.h" 20 #include "nsComponentManager.h" 21 #include "nsEnumeratorUtils.h" 22 #include "nsNetUtil.h" 23 #include "nsStringEnumerator.h" 24 #include "nsTextFormatter.h" 25 #include "nsXPCOMCIDInternal.h" 26 27 #include "mozilla/LookAndFeel.h" 28 29 #include "nsIObserverService.h" 30 #include "mozilla/AppShutdown.h" 31 #include "mozilla/Components.h" 32 #include "mozilla/Preferences.h" 33 #include "nsIResProtocolHandler.h" 34 #include "nsIScriptError.h" 35 #include "nsIXULRuntime.h" 36 37 #define PACKAGE_OVERRIDE_BRANCH "chrome.override_package." 38 #define SKIN "classic/1.0"_ns 39 40 using namespace mozilla; 41 using mozilla::dom::ContentParent; 42 using mozilla::dom::PContentParent; 43 using mozilla::intl::LocaleService; 44 45 // We use a "best-fit" algorithm for matching locales and themes. 46 // 1) the exact selected locale/theme 47 // 2) (locales only) same language, different country 48 // e.g. en-GB is the selected locale, only en-US is available 49 // 3) any available locale/theme 50 51 /** 52 * Match the language-part of two lang-COUNTRY codes, hopefully but 53 * not guaranteed to be in the form ab-CD or abz-CD. "ab" should also 54 * work, any other garbage-in will produce undefined results as long 55 * as it does not crash. 56 */ 57 static bool LanguagesMatch(const nsACString& a, const nsACString& b) { 58 if (a.Length() < 2 || b.Length() < 2) return false; 59 60 nsACString::const_iterator as, ae, bs, be; 61 a.BeginReading(as); 62 a.EndReading(ae); 63 b.BeginReading(bs); 64 b.EndReading(be); 65 66 while (*as == *bs) { 67 if (*as == '-') return true; 68 69 ++as; 70 ++bs; 71 72 // reached the end 73 if (as == ae && bs == be) return true; 74 75 // "a" is short 76 if (as == ae) return (*bs == '-'); 77 78 // "b" is short 79 if (bs == be) return (*as == '-'); 80 } 81 82 return false; 83 } 84 85 nsChromeRegistryChrome::nsChromeRegistryChrome() 86 : mProfileLoaded(false), mDynamicRegistration(true) {} 87 88 nsChromeRegistryChrome::~nsChromeRegistryChrome() {} 89 90 nsresult nsChromeRegistryChrome::Init() { 91 nsresult rv = nsChromeRegistry::Init(); 92 if (NS_FAILED(rv)) return rv; 93 94 bool safeMode = false; 95 nsCOMPtr<nsIXULRuntime> xulrun(do_GetService(XULAPPINFO_SERVICE_CONTRACTID)); 96 if (xulrun) xulrun->GetInSafeMode(&safeMode); 97 98 nsCOMPtr<nsIObserverService> obsService = 99 mozilla::services::GetObserverService(); 100 if (obsService) { 101 obsService->AddObserver(this, "profile-initial-state", true); 102 obsService->AddObserver(this, "intl:app-locales-changed", true); 103 } 104 105 return NS_OK; 106 } 107 108 NS_IMETHODIMP 109 nsChromeRegistryChrome::GetLocalesForPackage( 110 const nsACString& aPackage, nsIUTF8StringEnumerator** aResult) { 111 nsCString realpackage; 112 nsresult rv = OverrideLocalePackage(aPackage, realpackage); 113 if (NS_FAILED(rv)) return rv; 114 115 nsTArray<nsCString>* a = new nsTArray<nsCString>; 116 if (!a) return NS_ERROR_OUT_OF_MEMORY; 117 118 PackageEntry* entry; 119 if (mPackagesHash.Get(realpackage, &entry)) { 120 entry->locales.EnumerateToArray(a); 121 } 122 123 rv = NS_NewAdoptingUTF8StringEnumerator(aResult, a); 124 if (NS_FAILED(rv)) delete a; 125 126 return rv; 127 } 128 129 NS_IMETHODIMP 130 nsChromeRegistryChrome::IsLocaleRTL(const nsACString& package, bool* aResult) { 131 *aResult = false; 132 133 nsAutoCString locale; 134 GetSelectedLocale(package, locale); 135 if (locale.Length() < 2) return NS_OK; 136 137 *aResult = LocaleService::IsLocaleRTL(locale); 138 return NS_OK; 139 } 140 141 /** 142 * This method negotiates only between the app locale and the available 143 * chrome packages. 144 * 145 * If you want to get the current application's UI locale, please use 146 * LocaleService::GetAppLocaleAsBCP47. 147 */ 148 nsresult nsChromeRegistryChrome::GetSelectedLocale(const nsACString& aPackage, 149 nsACString& aLocale) { 150 nsAutoCString reqLocale; 151 if (aPackage.EqualsLiteral("global")) { 152 LocaleService::GetInstance()->GetAppLocaleAsBCP47(reqLocale); 153 } else { 154 AutoTArray<nsCString, 10> requestedLocales; 155 LocaleService::GetInstance()->GetRequestedLocales(requestedLocales); 156 reqLocale.Assign(requestedLocales[0]); 157 } 158 159 nsCString realpackage; 160 nsresult rv = OverrideLocalePackage(aPackage, realpackage); 161 if (NS_FAILED(rv)) return rv; 162 PackageEntry* entry; 163 if (!mPackagesHash.Get(realpackage, &entry)) return NS_ERROR_FILE_NOT_FOUND; 164 165 aLocale = entry->locales.GetSelected(reqLocale, nsProviderArray::LOCALE); 166 if (aLocale.IsEmpty()) return NS_ERROR_FAILURE; 167 168 return NS_OK; 169 } 170 171 nsresult nsChromeRegistryChrome::OverrideLocalePackage( 172 const nsACString& aPackage, nsACString& aOverride) { 173 const nsACString& pref = nsLiteralCString(PACKAGE_OVERRIDE_BRANCH) + aPackage; 174 nsAutoCString override; 175 nsresult rv = mozilla::Preferences::GetCString(PromiseFlatCString(pref).get(), 176 override); 177 if (NS_SUCCEEDED(rv)) { 178 aOverride = override; 179 } else { 180 aOverride = aPackage; 181 } 182 return NS_OK; 183 } 184 185 NS_IMETHODIMP 186 nsChromeRegistryChrome::Observe(nsISupports* aSubject, const char* aTopic, 187 const char16_t* someData) { 188 nsresult rv = NS_OK; 189 190 if (!strcmp("profile-initial-state", aTopic)) { 191 mProfileLoaded = true; 192 } else if (!strcmp("intl:app-locales-changed", aTopic)) { 193 if (mProfileLoaded) { 194 FlushAllCaches(); 195 } 196 } else { 197 NS_ERROR("Unexpected observer topic!"); 198 } 199 200 return rv; 201 } 202 203 NS_IMETHODIMP 204 nsChromeRegistryChrome::CheckForNewChrome() { 205 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { 206 MOZ_ASSERT(false, "checking for new chrome during shutdown"); 207 return NS_ERROR_UNEXPECTED; 208 } 209 210 mPackagesHash.Clear(); 211 mOverrideTable.Clear(); 212 213 mDynamicRegistration = false; 214 215 nsComponentManagerImpl::gComponentManager->RereadChromeManifests(); 216 217 mDynamicRegistration = true; 218 219 SendRegisteredChrome(nullptr); 220 return NS_OK; 221 } 222 223 static void SerializeURI(nsIURI* aURI, SerializedURI& aSerializedURI) { 224 if (!aURI) return; 225 226 aURI->GetSpec(aSerializedURI.spec); 227 } 228 229 void nsChromeRegistryChrome::SendRegisteredChrome( 230 mozilla::dom::PContentParent* aParent) { 231 nsTArray<ChromePackage> packages; 232 nsTArray<SubstitutionMapping> resources; 233 nsTArray<OverrideMapping> overrides; 234 235 for (const auto& entry : mPackagesHash) { 236 ChromePackage chromePackage; 237 ChromePackageFromPackageEntry(entry.GetKey(), entry.GetWeak(), 238 &chromePackage, SKIN); 239 packages.AppendElement(chromePackage); 240 } 241 242 // If we were passed a parent then a new child process has been created and 243 // has requested all of the chrome so send it the resources too. Otherwise 244 // resource mappings are sent by the resource protocol handler dynamically. 245 if (aParent) { 246 nsCOMPtr<nsIIOService> io(do_GetIOService()); 247 NS_ENSURE_TRUE_VOID(io); 248 249 nsCOMPtr<nsIProtocolHandler> ph; 250 nsresult rv = io->GetProtocolHandler("resource", getter_AddRefs(ph)); 251 NS_ENSURE_SUCCESS_VOID(rv); 252 253 nsCOMPtr<nsIResProtocolHandler> irph(do_QueryInterface(ph)); 254 nsResProtocolHandler* rph = static_cast<nsResProtocolHandler*>(irph.get()); 255 rv = rph->CollectSubstitutions(resources); 256 NS_ENSURE_SUCCESS_VOID(rv); 257 } 258 259 for (const auto& entry : mOverrideTable) { 260 SerializedURI chromeURI, overrideURI; 261 262 SerializeURI(entry.GetKey(), chromeURI); 263 SerializeURI(entry.GetWeak(), overrideURI); 264 265 overrides.AppendElement( 266 OverrideMapping{std::move(chromeURI), std::move(overrideURI)}); 267 } 268 269 nsAutoCString appLocale; 270 LocaleService::GetInstance()->GetAppLocaleAsBCP47(appLocale); 271 272 if (aParent) { 273 bool success = aParent->SendRegisterChrome(packages, resources, overrides, 274 appLocale, false); 275 NS_ENSURE_TRUE_VOID(success); 276 } else { 277 nsTArray<ContentParent*> parents; 278 ContentParent::GetAll(parents); 279 if (!parents.Length()) return; 280 281 for (uint32_t i = 0; i < parents.Length(); i++) { 282 DebugOnly<bool> success = parents[i]->SendRegisterChrome( 283 packages, resources, overrides, appLocale, true); 284 NS_WARNING_ASSERTION(success, 285 "couldn't reset a child's registered chrome"); 286 } 287 } 288 } 289 290 /* static */ 291 void nsChromeRegistryChrome::ChromePackageFromPackageEntry( 292 const nsACString& aPackageName, PackageEntry* aPackage, 293 ChromePackage* aChromePackage, const nsCString& aSelectedSkin) { 294 nsAutoCString appLocale; 295 LocaleService::GetInstance()->GetAppLocaleAsBCP47(appLocale); 296 297 SerializeURI(aPackage->baseURI, aChromePackage->contentBaseURI); 298 SerializeURI(aPackage->locales.GetBase(appLocale, nsProviderArray::LOCALE), 299 aChromePackage->localeBaseURI); 300 SerializeURI(aPackage->skins.GetBase(aSelectedSkin, nsProviderArray::ANY), 301 aChromePackage->skinBaseURI); 302 aChromePackage->package = aPackageName; 303 aChromePackage->flags = aPackage->flags; 304 } 305 306 static bool CanLoadResource(nsIURI* aResourceURI) { 307 bool isLocalResource = false; 308 (void)NS_URIChainHasFlags(aResourceURI, 309 nsIProtocolHandler::URI_IS_LOCAL_RESOURCE, 310 &isLocalResource); 311 return isLocalResource; 312 } 313 314 nsIURI* nsChromeRegistryChrome::GetBaseURIFromPackage( 315 const nsCString& aPackage, const nsCString& aProvider, 316 const nsCString& aPath) { 317 PackageEntry* entry; 318 if (!mPackagesHash.Get(aPackage, &entry)) { 319 if (!mInitialized) return nullptr; 320 321 LogMessage("No chrome package registered for chrome://%s/%s/%s", 322 aPackage.get(), aProvider.get(), aPath.get()); 323 324 return nullptr; 325 } 326 327 if (aProvider.EqualsLiteral("locale")) { 328 nsAutoCString appLocale; 329 LocaleService::GetInstance()->GetAppLocaleAsLangTag(appLocale); 330 return entry->locales.GetBase(appLocale, nsProviderArray::LOCALE); 331 } 332 333 if (aProvider.EqualsLiteral("skin")) { 334 return entry->skins.GetBase(SKIN, nsProviderArray::ANY); 335 } 336 337 if (aProvider.EqualsLiteral("content")) { 338 return entry->baseURI; 339 } 340 return nullptr; 341 } 342 343 nsresult nsChromeRegistryChrome::GetFlagsFromPackage(const nsCString& aPackage, 344 uint32_t* aFlags) { 345 PackageEntry* entry; 346 if (!mPackagesHash.Get(aPackage, &entry)) return NS_ERROR_FILE_NOT_FOUND; 347 348 *aFlags = entry->flags; 349 return NS_OK; 350 } 351 352 nsChromeRegistryChrome::ProviderEntry* 353 nsChromeRegistryChrome::nsProviderArray::GetProvider( 354 const nsACString& aPreferred, MatchType aType) { 355 size_t i = mArray.Length(); 356 if (!i) return nullptr; 357 358 ProviderEntry* found = nullptr; // Only set if we find a partial-match locale 359 ProviderEntry* entry = nullptr; 360 361 while (i--) { 362 entry = &mArray[i]; 363 if (aPreferred.Equals(entry->provider)) return entry; 364 365 if (aType != LOCALE) continue; 366 367 if (LanguagesMatch(aPreferred, entry->provider)) { 368 found = entry; 369 continue; 370 } 371 372 if (!found && entry->provider.EqualsLiteral("en-US")) found = entry; 373 } 374 375 if (!found && aType != EXACT) return entry; 376 377 return found; 378 } 379 380 nsIURI* nsChromeRegistryChrome::nsProviderArray::GetBase( 381 const nsACString& aPreferred, MatchType aType) { 382 ProviderEntry* provider = GetProvider(aPreferred, aType); 383 384 if (!provider) return nullptr; 385 386 return provider->baseURI; 387 } 388 389 const nsACString& nsChromeRegistryChrome::nsProviderArray::GetSelected( 390 const nsACString& aPreferred, MatchType aType) { 391 ProviderEntry* entry = GetProvider(aPreferred, aType); 392 393 if (entry) return entry->provider; 394 395 return EmptyCString(); 396 } 397 398 void nsChromeRegistryChrome::nsProviderArray::SetBase( 399 const nsACString& aProvider, nsIURI* aBaseURL) { 400 ProviderEntry* provider = GetProvider(aProvider, EXACT); 401 402 if (provider) { 403 provider->baseURI = aBaseURL; 404 return; 405 } 406 407 // no existing entries, add a new one 408 mArray.AppendElement(ProviderEntry(aProvider, aBaseURL)); 409 } 410 411 void nsChromeRegistryChrome::nsProviderArray::EnumerateToArray( 412 nsTArray<nsCString>* a) { 413 int32_t i = mArray.Length(); 414 while (i--) { 415 a->AppendElement(mArray[i].provider); 416 } 417 } 418 419 nsIURI* nsChromeRegistry::ManifestProcessingContext::GetManifestURI() { 420 if (!mManifestURI) { 421 nsCString uri; 422 mFile.GetURIString(uri); 423 NS_NewURI(getter_AddRefs(mManifestURI), uri); 424 } 425 return mManifestURI; 426 } 427 428 already_AddRefed<nsIURI> 429 nsChromeRegistry::ManifestProcessingContext::ResolveURI(const char* uri) { 430 nsIURI* baseuri = GetManifestURI(); 431 if (!baseuri) return nullptr; 432 433 nsCOMPtr<nsIURI> resolved; 434 nsresult rv = NS_NewURI(getter_AddRefs(resolved), uri, baseuri); 435 if (NS_FAILED(rv)) return nullptr; 436 437 return resolved.forget(); 438 } 439 440 static void EnsureLowerCase(char* aBuf) { 441 for (; *aBuf; ++aBuf) { 442 char ch = *aBuf; 443 if (ch >= 'A' && ch <= 'Z') *aBuf = ch + 'a' - 'A'; 444 } 445 } 446 447 static void SendManifestEntry(const ChromeRegistryItem& aItem) { 448 nsTArray<ContentParent*> parents; 449 ContentParent::GetAll(parents); 450 if (!parents.Length()) return; 451 452 for (uint32_t i = 0; i < parents.Length(); i++) { 453 (void)parents[i]->SendRegisterChromeItem(aItem); 454 } 455 } 456 457 void nsChromeRegistryChrome::ManifestContent(ManifestProcessingContext& cx, 458 int lineno, char* const* argv, 459 int flags) { 460 char* package = argv[0]; 461 char* uri = argv[1]; 462 463 EnsureLowerCase(package); 464 465 nsCOMPtr<nsIURI> resolved = cx.ResolveURI(uri); 466 if (!resolved) { 467 LogMessageWithContext( 468 cx.GetManifestURI(), lineno, nsIScriptError::warningFlag, 469 "During chrome registration, unable to create URI '%s'.", uri); 470 return; 471 } 472 473 if (!CanLoadResource(resolved)) { 474 LogMessageWithContext(resolved, lineno, nsIScriptError::warningFlag, 475 "During chrome registration, cannot register " 476 "non-local URI '%s' as content.", 477 uri); 478 return; 479 } 480 481 nsDependentCString packageName(package); 482 PackageEntry* entry = mPackagesHash.GetOrInsertNew(packageName); 483 entry->baseURI = resolved; 484 entry->flags = flags; 485 486 if (mDynamicRegistration) { 487 ChromePackage chromePackage; 488 ChromePackageFromPackageEntry(packageName, entry, &chromePackage, SKIN); 489 SendManifestEntry(chromePackage); 490 } 491 } 492 493 void nsChromeRegistryChrome::ManifestLocale(ManifestProcessingContext& cx, 494 int lineno, char* const* argv, 495 int flags) { 496 char* package = argv[0]; 497 char* provider = argv[1]; 498 char* uri = argv[2]; 499 500 EnsureLowerCase(package); 501 502 nsCOMPtr<nsIURI> resolved = cx.ResolveURI(uri); 503 if (!resolved) { 504 LogMessageWithContext( 505 cx.GetManifestURI(), lineno, nsIScriptError::warningFlag, 506 "During chrome registration, unable to create URI '%s'.", uri); 507 return; 508 } 509 510 if (!CanLoadResource(resolved)) { 511 LogMessageWithContext(resolved, lineno, nsIScriptError::warningFlag, 512 "During chrome registration, cannot register " 513 "non-local URI '%s' as content.", 514 uri); 515 return; 516 } 517 518 nsDependentCString packageName(package); 519 PackageEntry* entry = mPackagesHash.GetOrInsertNew(packageName); 520 entry->locales.SetBase(nsDependentCString(provider), resolved); 521 522 if (mDynamicRegistration) { 523 ChromePackage chromePackage; 524 ChromePackageFromPackageEntry(packageName, entry, &chromePackage, SKIN); 525 SendManifestEntry(chromePackage); 526 } 527 528 // We use mainPackage as the package we track for reporting new locales being 529 // registered. For most cases it will be "global", but for Fennec it will be 530 // "browser". 531 nsAutoCString mainPackage; 532 nsresult rv = OverrideLocalePackage("global"_ns, mainPackage); 533 if (NS_FAILED(rv)) { 534 return; 535 } 536 } 537 538 void nsChromeRegistryChrome::ManifestSkin(ManifestProcessingContext& cx, 539 int lineno, char* const* argv, 540 int flags) { 541 char* package = argv[0]; 542 char* provider = argv[1]; 543 char* uri = argv[2]; 544 545 EnsureLowerCase(package); 546 547 nsCOMPtr<nsIURI> resolved = cx.ResolveURI(uri); 548 if (!resolved) { 549 LogMessageWithContext( 550 cx.GetManifestURI(), lineno, nsIScriptError::warningFlag, 551 "During chrome registration, unable to create URI '%s'.", uri); 552 return; 553 } 554 555 if (!CanLoadResource(resolved)) { 556 LogMessageWithContext(resolved, lineno, nsIScriptError::warningFlag, 557 "During chrome registration, cannot register " 558 "non-local URI '%s' as content.", 559 uri); 560 return; 561 } 562 563 nsDependentCString packageName(package); 564 PackageEntry* entry = mPackagesHash.GetOrInsertNew(packageName); 565 entry->skins.SetBase(nsDependentCString(provider), resolved); 566 567 if (mDynamicRegistration) { 568 ChromePackage chromePackage; 569 ChromePackageFromPackageEntry(packageName, entry, &chromePackage, SKIN); 570 SendManifestEntry(chromePackage); 571 } 572 } 573 574 void nsChromeRegistryChrome::ManifestOverride(ManifestProcessingContext& cx, 575 int lineno, char* const* argv, 576 int flags) { 577 char* chrome = argv[0]; 578 char* resolved = argv[1]; 579 580 nsCOMPtr<nsIURI> chromeuri = cx.ResolveURI(chrome); 581 nsCOMPtr<nsIURI> resolveduri = cx.ResolveURI(resolved); 582 if (!chromeuri || !resolveduri) { 583 LogMessageWithContext(cx.GetManifestURI(), lineno, 584 nsIScriptError::warningFlag, 585 "During chrome registration, unable to create URI."); 586 return; 587 } 588 589 if (!CanLoadResource(resolveduri)) { 590 LogMessageWithContext( 591 cx.GetManifestURI(), lineno, nsIScriptError::warningFlag, 592 "Cannot register non-local URI '%s' for an override.", resolved); 593 return; 594 } 595 mOverrideTable.InsertOrUpdate(chromeuri, resolveduri); 596 597 if (mDynamicRegistration) { 598 SerializedURI serializedChrome; 599 SerializedURI serializedOverride; 600 601 SerializeURI(chromeuri, serializedChrome); 602 SerializeURI(resolveduri, serializedOverride); 603 604 OverrideMapping override = {serializedChrome, serializedOverride}; 605 SendManifestEntry(override); 606 } 607 } 608 609 void nsChromeRegistryChrome::ManifestResource(ManifestProcessingContext& cx, 610 int lineno, char* const* argv, 611 int flags) { 612 char* package = argv[0]; 613 char* uri = argv[1]; 614 615 EnsureLowerCase(package); 616 nsDependentCString host(package); 617 618 nsCOMPtr<nsIIOService> io = mozilla::components::IO::Service(); 619 if (!io) { 620 NS_WARNING("No IO service trying to process chrome manifests"); 621 return; 622 } 623 624 nsCOMPtr<nsIProtocolHandler> ph; 625 nsresult rv = io->GetProtocolHandler("resource", getter_AddRefs(ph)); 626 if (NS_FAILED(rv)) return; 627 628 nsCOMPtr<nsIResProtocolHandler> rph = do_QueryInterface(ph); 629 630 nsCOMPtr<nsIURI> resolved = cx.ResolveURI(uri); 631 if (!resolved) { 632 LogMessageWithContext( 633 cx.GetManifestURI(), lineno, nsIScriptError::warningFlag, 634 "During chrome registration, unable to create URI '%s'.", uri); 635 return; 636 } 637 638 if (!CanLoadResource(resolved)) { 639 LogMessageWithContext( 640 cx.GetManifestURI(), lineno, nsIScriptError::warningFlag, 641 "Warning: cannot register non-local URI '%s' as a resource.", uri); 642 return; 643 } 644 645 // By default, Firefox resources are not content-accessible unless the 646 // manifests opts in. 647 bool contentAccessible = (flags & nsChromeRegistry::CONTENT_ACCESSIBLE); 648 649 uint32_t substitutionFlags = 0; 650 if (contentAccessible) { 651 substitutionFlags |= nsIResProtocolHandler::ALLOW_CONTENT_ACCESS; 652 } 653 rv = rph->SetSubstitutionWithFlags(host, resolved, substitutionFlags); 654 if (NS_FAILED(rv)) { 655 LogMessageWithContext(cx.GetManifestURI(), lineno, 656 nsIScriptError::warningFlag, 657 "Warning: cannot set substitution for '%s'.", uri); 658 } 659 }