1 /* -*- Mode: C++; tab-width: 2; 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 "nsStringBundle.h"
7 #include "nsID.h"
8 #include "nsString.h"
9 #include "nsIStringBundle.h"
10 #include "nsStringBundleService.h"
11 #include "nsStringBundleTextOverride.h"
12 #include "nsISupportsPrimitives.h"
13 #include "nsIMutableArray.h"
14 #include "nsArrayEnumerator.h"
15 #include "nscore.h"
16 #include "nsMemory.h"
17 #include "nsNetUtil.h"
18 #include "nsComponentManagerUtils.h"
19 #include "nsServiceManagerUtils.h"
20 #include "nsIInputStream.h"
21 #include "nsIURI.h"
22 #include "nsIObserverService.h"
23 #include "nsCOMArray.h"
24 #include "nsTextFormatter.h"
25 #include "nsIErrorService.h"
26 #include "nsICategoryManager.h"
27 #include "nsContentUtils.h"
28 #include "nsStringStream.h"
29 #include "mozilla/ResultExtensions.h"
30 #include "mozilla/URLPreloader.h"
31 
32 // for async loading
33 #ifdef ASYNC_LOADING
34 #include "nsIBinaryInputStream.h"
35 #include "nsIStringStream.h"
36 #endif
37 
38 using namespace mozilla;
39 
40 static NS_DEFINE_CID(kErrorServiceCID, NS_ERRORSERVICE_CID);
41 
NS_IMPL_ISUPPORTS(nsStringBundle,nsIStringBundle)42 NS_IMPL_ISUPPORTS(nsStringBundle, nsIStringBundle)
43 
44 nsStringBundle::nsStringBundle(const char* aURLSpec,
45                                nsIStringBundleOverride* aOverrideStrings)
46     : mPropertiesURL(aURLSpec),
47       mOverrideStrings(aOverrideStrings),
48       mReentrantMonitor("nsStringBundle.mReentrantMonitor"),
49       mAttemptedLoad(false),
50       mLoaded(false) {}
51 
~nsStringBundle()52 nsStringBundle::~nsStringBundle() {}
53 
54 NS_IMETHODIMP
AsyncPreload()55 nsStringBundle::AsyncPreload() {
56   return NS_IdleDispatchToCurrentThread(NewIdleRunnableMethod(
57       "nsStringBundle::LoadProperties", this, &nsStringBundle::LoadProperties));
58 }
59 
LoadProperties()60 nsresult nsStringBundle::LoadProperties() {
61   // this is different than mLoaded, because we only want to attempt
62   // to load once
63   // we only want to load once, but if we've tried once and failed,
64   // continue to throw an error!
65   if (mAttemptedLoad) {
66     if (mLoaded) return NS_OK;
67 
68     return NS_ERROR_UNEXPECTED;
69   }
70 
71   mAttemptedLoad = true;
72 
73   nsresult rv;
74 
75   // do it synchronously
76   nsCOMPtr<nsIURI> uri;
77   rv = NS_NewURI(getter_AddRefs(uri), mPropertiesURL);
78   if (NS_FAILED(rv)) return rv;
79 
80   // whitelist check for local schemes
81   nsCString scheme;
82   uri->GetScheme(scheme);
83   if (!scheme.EqualsLiteral("chrome") && !scheme.EqualsLiteral("jar") &&
84       !scheme.EqualsLiteral("resource") && !scheme.EqualsLiteral("file") &&
85       !scheme.EqualsLiteral("data")) {
86     return NS_ERROR_ABORT;
87   }
88 
89   nsCOMPtr<nsIInputStream> in;
90 
91   auto result = URLPreloader::ReadURI(uri);
92   if (result.isOk()) {
93     MOZ_TRY(NS_NewCStringInputStream(getter_AddRefs(in), result.unwrap()));
94   } else {
95     nsCOMPtr<nsIChannel> channel;
96     rv = NS_NewChannel(getter_AddRefs(channel), uri,
97                        nsContentUtils::GetSystemPrincipal(),
98                        nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
99                        nsIContentPolicy::TYPE_OTHER);
100 
101     if (NS_FAILED(rv)) return rv;
102 
103     // It's a string bundle.  We expect a text/plain type, so set that as hint
104     channel->SetContentType(NS_LITERAL_CSTRING("text/plain"));
105 
106     rv = channel->Open2(getter_AddRefs(in));
107     if (NS_FAILED(rv)) return rv;
108   }
109 
110   NS_ASSERTION(NS_SUCCEEDED(rv) && in, "Error in OpenBlockingStream");
111   NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && in, NS_ERROR_FAILURE);
112 
113   static NS_DEFINE_CID(kPersistentPropertiesCID, NS_IPERSISTENTPROPERTIES_CID);
114   mProps = do_CreateInstance(kPersistentPropertiesCID, &rv);
115   NS_ENSURE_SUCCESS(rv, rv);
116 
117   mAttemptedLoad = mLoaded = true;
118   rv = mProps->Load(in);
119 
120   mLoaded = NS_SUCCEEDED(rv);
121 
122   return rv;
123 }
124 
125 NS_IMETHODIMP
GetStringFromID(int32_t aID,nsAString & aResult)126 nsStringBundle::GetStringFromID(int32_t aID, nsAString& aResult) {
127   nsAutoCString idStr;
128   idStr.AppendInt(aID, 10);
129   return GetStringFromName(idStr.get(), aResult);
130 }
131 
132 NS_IMETHODIMP
GetStringFromAUTF8Name(const nsACString & aName,nsAString & aResult)133 nsStringBundle::GetStringFromAUTF8Name(const nsACString& aName,
134                                        nsAString& aResult) {
135   return GetStringFromName(PromiseFlatCString(aName).get(), aResult);
136 }
137 
138 NS_IMETHODIMP
GetStringFromName(const char * aName,nsAString & aResult)139 nsStringBundle::GetStringFromName(const char* aName, nsAString& aResult) {
140   NS_ENSURE_ARG_POINTER(aName);
141 
142   nsresult rv = LoadProperties();
143   if (NS_FAILED(rv)) return rv;
144 
145   ReentrantMonitorAutoEnter automon(mReentrantMonitor);
146 
147   // try override first
148   if (mOverrideStrings) {
149     rv = mOverrideStrings->GetStringFromName(
150         mPropertiesURL, nsDependentCString(aName), aResult);
151     if (NS_SUCCEEDED(rv)) return rv;
152   }
153 
154   return mProps->GetStringProperty(nsDependentCString(aName), aResult);
155 }
156 
157 NS_IMETHODIMP
FormatStringFromID(int32_t aID,const char16_t ** aParams,uint32_t aLength,nsAString & aResult)158 nsStringBundle::FormatStringFromID(int32_t aID, const char16_t** aParams,
159                                    uint32_t aLength, nsAString& aResult) {
160   nsAutoCString idStr;
161   idStr.AppendInt(aID, 10);
162   return FormatStringFromName(idStr.get(), aParams, aLength, aResult);
163 }
164 
165 // this function supports at most 10 parameters.. see below for why
166 NS_IMETHODIMP
FormatStringFromAUTF8Name(const nsACString & aName,const char16_t ** aParams,uint32_t aLength,nsAString & aResult)167 nsStringBundle::FormatStringFromAUTF8Name(const nsACString& aName,
168                                           const char16_t** aParams,
169                                           uint32_t aLength,
170                                           nsAString& aResult) {
171   return FormatStringFromName(PromiseFlatCString(aName).get(), aParams, aLength,
172                               aResult);
173 }
174 
175 // this function supports at most 10 parameters.. see below for why
176 NS_IMETHODIMP
FormatStringFromName(const char * aName,const char16_t ** aParams,uint32_t aLength,nsAString & aResult)177 nsStringBundle::FormatStringFromName(const char* aName,
178                                      const char16_t** aParams, uint32_t aLength,
179                                      nsAString& aResult) {
180   NS_ASSERTION(aParams && aLength,
181                "FormatStringFromName() without format parameters: use "
182                "GetStringFromName() instead");
183 
184   nsAutoString formatStr;
185   nsresult rv = GetStringFromName(aName, formatStr);
186   if (NS_FAILED(rv)) return rv;
187 
188   return FormatString(formatStr.get(), aParams, aLength, aResult);
189 }
190 
GetCombinedEnumeration(nsIStringBundleOverride * aOverrideStrings,nsISimpleEnumerator ** aResult)191 nsresult nsStringBundle::GetCombinedEnumeration(
192     nsIStringBundleOverride* aOverrideStrings, nsISimpleEnumerator** aResult) {
193   nsCOMPtr<nsISupports> supports;
194   nsCOMPtr<nsIPropertyElement> propElement;
195 
196   nsresult rv;
197 
198   nsCOMPtr<nsIMutableArray> resultArray =
199       do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
200   NS_ENSURE_SUCCESS(rv, rv);
201 
202   // first, append the override elements
203   nsCOMPtr<nsISimpleEnumerator> overrideEnumerator;
204   rv = aOverrideStrings->EnumerateKeysInBundle(
205       mPropertiesURL, getter_AddRefs(overrideEnumerator));
206 
207   bool hasMore;
208   rv = overrideEnumerator->HasMoreElements(&hasMore);
209   NS_ENSURE_SUCCESS(rv, rv);
210   while (hasMore) {
211     rv = overrideEnumerator->GetNext(getter_AddRefs(supports));
212     if (NS_SUCCEEDED(rv)) resultArray->AppendElement(supports);
213 
214     rv = overrideEnumerator->HasMoreElements(&hasMore);
215     NS_ENSURE_SUCCESS(rv, rv);
216   }
217 
218   // ok, now we have the override elements in resultArray
219   nsCOMPtr<nsISimpleEnumerator> propEnumerator;
220   rv = mProps->Enumerate(getter_AddRefs(propEnumerator));
221   if (NS_FAILED(rv)) {
222     // no elements in mProps anyway, just return what we have
223     return NS_NewArrayEnumerator(aResult, resultArray);
224   }
225 
226   // second, append all the elements that are in mProps
227   do {
228     rv = propEnumerator->GetNext(getter_AddRefs(supports));
229     if (NS_SUCCEEDED(rv) && (propElement = do_QueryInterface(supports, &rv))) {
230       // now check if its in the override bundle
231       nsAutoCString key;
232       propElement->GetKey(key);
233 
234       nsAutoString value;
235       rv = aOverrideStrings->GetStringFromName(mPropertiesURL, key, value);
236 
237       // if it isn't there, then it is safe to append
238       if (NS_FAILED(rv)) resultArray->AppendElement(propElement);
239     }
240 
241     rv = propEnumerator->HasMoreElements(&hasMore);
242     NS_ENSURE_SUCCESS(rv, rv);
243   } while (hasMore);
244 
245   return resultArray->Enumerate(aResult);
246 }
247 
248 NS_IMETHODIMP
GetSimpleEnumeration(nsISimpleEnumerator ** elements)249 nsStringBundle::GetSimpleEnumeration(nsISimpleEnumerator** elements) {
250   if (!elements) return NS_ERROR_INVALID_POINTER;
251 
252   nsresult rv;
253   rv = LoadProperties();
254   if (NS_FAILED(rv)) return rv;
255 
256   if (mOverrideStrings)
257     return GetCombinedEnumeration(mOverrideStrings, elements);
258 
259   return mProps->Enumerate(elements);
260 }
261 
FormatString(const char16_t * aFormatStr,const char16_t ** aParams,uint32_t aLength,nsAString & aResult)262 nsresult nsStringBundle::FormatString(const char16_t* aFormatStr,
263                                       const char16_t** aParams,
264                                       uint32_t aLength, nsAString& aResult) {
265   NS_ENSURE_ARG(aLength <= 10);  // enforce 10-parameter limit
266 
267   // implementation note: you would think you could use vsmprintf
268   // to build up an arbitrary length array.. except that there
269   // is no way to build up a va_list at runtime!
270   // Don't believe me? See:
271   //   http://www.eskimo.com/~scs/C-faq/q15.13.html
272   // -alecf
273   nsTextFormatter::ssprintf(
274       aResult, aFormatStr, aLength >= 1 ? aParams[0] : nullptr,
275       aLength >= 2 ? aParams[1] : nullptr, aLength >= 3 ? aParams[2] : nullptr,
276       aLength >= 4 ? aParams[3] : nullptr, aLength >= 5 ? aParams[4] : nullptr,
277       aLength >= 6 ? aParams[5] : nullptr, aLength >= 7 ? aParams[6] : nullptr,
278       aLength >= 8 ? aParams[7] : nullptr, aLength >= 9 ? aParams[8] : nullptr,
279       aLength >= 10 ? aParams[9] : nullptr);
280 
281   return NS_OK;
282 }
283 
NS_IMPL_ISUPPORTS(nsExtensibleStringBundle,nsIStringBundle)284 NS_IMPL_ISUPPORTS(nsExtensibleStringBundle, nsIStringBundle)
285 
286 nsExtensibleStringBundle::nsExtensibleStringBundle() { mLoaded = false; }
287 
Init(const char * aCategory,nsIStringBundleService * aBundleService)288 nsresult nsExtensibleStringBundle::Init(
289     const char* aCategory, nsIStringBundleService* aBundleService) {
290   nsresult rv;
291   nsCOMPtr<nsICategoryManager> catman =
292       do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv);
293   if (NS_FAILED(rv)) return rv;
294 
295   nsCOMPtr<nsISimpleEnumerator> enumerator;
296   rv = catman->EnumerateCategory(aCategory, getter_AddRefs(enumerator));
297   if (NS_FAILED(rv)) return rv;
298 
299   bool hasMore;
300   while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) {
301     nsCOMPtr<nsISupports> supports;
302     rv = enumerator->GetNext(getter_AddRefs(supports));
303     if (NS_FAILED(rv)) continue;
304 
305     nsCOMPtr<nsISupportsCString> supStr = do_QueryInterface(supports, &rv);
306     if (NS_FAILED(rv)) continue;
307 
308     nsAutoCString name;
309     rv = supStr->GetData(name);
310     if (NS_FAILED(rv)) continue;
311 
312     nsCOMPtr<nsIStringBundle> bundle;
313     rv = aBundleService->CreateBundle(name.get(), getter_AddRefs(bundle));
314     if (NS_FAILED(rv)) continue;
315 
316     mBundles.AppendObject(bundle);
317   }
318 
319   return rv;
320 }
321 
322 NS_IMETHODIMP
AsyncPreload()323 nsExtensibleStringBundle::AsyncPreload() {
324   nsresult rv = NS_OK;
325   const uint32_t size = mBundles.Count();
326   for (uint32_t i = 0; i < size; ++i) {
327     nsIStringBundle* bundle = mBundles[i];
328     if (bundle) {
329       nsresult rv2 = bundle->AsyncPreload();
330       rv = NS_FAILED(rv) ? rv : rv2;
331     }
332   }
333   return rv;
334 }
335 
~nsExtensibleStringBundle()336 nsExtensibleStringBundle::~nsExtensibleStringBundle() {}
337 
GetStringFromID(int32_t aID,nsAString & aResult)338 nsresult nsExtensibleStringBundle::GetStringFromID(int32_t aID,
339                                                    nsAString& aResult) {
340   nsAutoCString idStr;
341   idStr.AppendInt(aID, 10);
342   return GetStringFromName(idStr.get(), aResult);
343 }
344 
GetStringFromAUTF8Name(const nsACString & aName,nsAString & aResult)345 nsresult nsExtensibleStringBundle::GetStringFromAUTF8Name(
346     const nsACString& aName, nsAString& aResult) {
347   return GetStringFromName(PromiseFlatCString(aName).get(), aResult);
348 }
349 
GetStringFromName(const char * aName,nsAString & aResult)350 nsresult nsExtensibleStringBundle::GetStringFromName(const char* aName,
351                                                      nsAString& aResult) {
352   nsresult rv;
353   const uint32_t size = mBundles.Count();
354   for (uint32_t i = 0; i < size; ++i) {
355     nsIStringBundle* bundle = mBundles[i];
356     if (bundle) {
357       rv = bundle->GetStringFromName(aName, aResult);
358       if (NS_SUCCEEDED(rv)) return NS_OK;
359     }
360   }
361 
362   return NS_ERROR_FAILURE;
363 }
364 
365 NS_IMETHODIMP
FormatStringFromID(int32_t aID,const char16_t ** aParams,uint32_t aLength,nsAString & aResult)366 nsExtensibleStringBundle::FormatStringFromID(int32_t aID,
367                                              const char16_t** aParams,
368                                              uint32_t aLength,
369                                              nsAString& aResult) {
370   nsAutoCString idStr;
371   idStr.AppendInt(aID, 10);
372   return FormatStringFromName(idStr.get(), aParams, aLength, aResult);
373 }
374 
375 NS_IMETHODIMP
FormatStringFromAUTF8Name(const nsACString & aName,const char16_t ** aParams,uint32_t aLength,nsAString & aResult)376 nsExtensibleStringBundle::FormatStringFromAUTF8Name(const nsACString& aName,
377                                                     const char16_t** aParams,
378                                                     uint32_t aLength,
379                                                     nsAString& aResult) {
380   return FormatStringFromName(PromiseFlatCString(aName).get(), aParams, aLength,
381                               aResult);
382 }
383 
384 NS_IMETHODIMP
FormatStringFromName(const char * aName,const char16_t ** aParams,uint32_t aLength,nsAString & aResult)385 nsExtensibleStringBundle::FormatStringFromName(const char* aName,
386                                                const char16_t** aParams,
387                                                uint32_t aLength,
388                                                nsAString& aResult) {
389   nsAutoString formatStr;
390   nsresult rv;
391   rv = GetStringFromName(aName, formatStr);
392   if (NS_FAILED(rv)) return rv;
393 
394   return nsStringBundle::FormatString(formatStr.get(), aParams, aLength,
395                                       aResult);
396 }
397 
GetSimpleEnumeration(nsISimpleEnumerator ** aResult)398 nsresult nsExtensibleStringBundle::GetSimpleEnumeration(
399     nsISimpleEnumerator** aResult) {
400   // XXX write me
401   *aResult = nullptr;
402   return NS_ERROR_NOT_IMPLEMENTED;
403 }
404 
405   /////////////////////////////////////////////////////////////////////////////////////////
406 
407 #define MAX_CACHED_BUNDLES 16
408 
409 struct bundleCacheEntry_t final : public LinkedListElement<bundleCacheEntry_t> {
410   nsCString mHashKey;
411   nsCOMPtr<nsIStringBundle> mBundle;
412 
bundleCacheEntry_tbundleCacheEntry_t413   bundleCacheEntry_t() { MOZ_COUNT_CTOR(bundleCacheEntry_t); }
414 
~bundleCacheEntry_tbundleCacheEntry_t415   ~bundleCacheEntry_t() { MOZ_COUNT_DTOR(bundleCacheEntry_t); }
416 };
417 
nsStringBundleService()418 nsStringBundleService::nsStringBundleService()
419     : mBundleMap(MAX_CACHED_BUNDLES) {
420   mErrorService = do_GetService(kErrorServiceCID);
421   NS_ASSERTION(mErrorService, "Couldn't get error service");
422 }
423 
NS_IMPL_ISUPPORTS(nsStringBundleService,nsIStringBundleService,nsIObserver,nsISupportsWeakReference)424 NS_IMPL_ISUPPORTS(nsStringBundleService, nsIStringBundleService, nsIObserver,
425                   nsISupportsWeakReference)
426 
427 nsStringBundleService::~nsStringBundleService() { flushBundleCache(); }
428 
Init()429 nsresult nsStringBundleService::Init() {
430   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
431   if (os) {
432     os->AddObserver(this, "memory-pressure", true);
433     os->AddObserver(this, "profile-do-change", true);
434     os->AddObserver(this, "chrome-flush-caches", true);
435     os->AddObserver(this, "xpcom-category-entry-added", true);
436     os->AddObserver(this, "intl:app-locales-changed", true);
437   }
438 
439   // instantiate the override service, if there is any.
440   // at some point we probably want to make this a category, and
441   // support multiple overrides
442   mOverrideStrings = do_GetService(NS_STRINGBUNDLETEXTOVERRIDE_CONTRACTID);
443 
444   return NS_OK;
445 }
446 
447 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aSomeData)448 nsStringBundleService::Observe(nsISupports* aSubject, const char* aTopic,
449                                const char16_t* aSomeData) {
450   if (strcmp("memory-pressure", aTopic) == 0 ||
451       strcmp("profile-do-change", aTopic) == 0 ||
452       strcmp("chrome-flush-caches", aTopic) == 0 ||
453       strcmp("intl:app-locales-changed", aTopic) == 0) {
454     flushBundleCache();
455   } else if (strcmp("xpcom-category-entry-added", aTopic) == 0 &&
456              NS_LITERAL_STRING("xpcom-autoregistration").Equals(aSomeData)) {
457     mOverrideStrings = do_GetService(NS_STRINGBUNDLETEXTOVERRIDE_CONTRACTID);
458   }
459 
460   return NS_OK;
461 }
462 
flushBundleCache()463 void nsStringBundleService::flushBundleCache() {
464   // release all bundles in the cache
465   mBundleMap.Clear();
466 
467   while (!mBundleCache.isEmpty()) {
468     delete mBundleCache.popFirst();
469   }
470 }
471 
472 NS_IMETHODIMP
FlushBundles()473 nsStringBundleService::FlushBundles() {
474   flushBundleCache();
475   return NS_OK;
476 }
477 
getStringBundle(const char * aURLSpec,nsIStringBundle ** aResult)478 void nsStringBundleService::getStringBundle(const char* aURLSpec,
479                                             nsIStringBundle** aResult) {
480   nsDependentCString key(aURLSpec);
481   bundleCacheEntry_t* cacheEntry = mBundleMap.Get(key);
482 
483   if (cacheEntry) {
484     // cache hit!
485     // remove it from the list, it will later be reinserted
486     // at the head of the list
487     cacheEntry->remove();
488 
489   } else {
490     // hasn't been cached, so insert it into the hash table
491     RefPtr<nsStringBundle> bundle =
492         new nsStringBundle(aURLSpec, mOverrideStrings);
493     cacheEntry = insertIntoCache(bundle.forget(), key);
494   }
495 
496   // at this point the cacheEntry should exist in the hashtable,
497   // but is not in the LRU cache.
498   // put the cache entry at the front of the list
499   mBundleCache.insertFront(cacheEntry);
500 
501   // finally, return the value
502   *aResult = cacheEntry->mBundle;
503   NS_ADDREF(*aResult);
504 }
505 
insertIntoCache(already_AddRefed<nsIStringBundle> aBundle,nsCString & aHashKey)506 bundleCacheEntry_t* nsStringBundleService::insertIntoCache(
507     already_AddRefed<nsIStringBundle> aBundle, nsCString& aHashKey) {
508   bundleCacheEntry_t* cacheEntry;
509 
510   if (mBundleMap.Count() < MAX_CACHED_BUNDLES) {
511     // cache not full - create a new entry
512     cacheEntry = new bundleCacheEntry_t();
513   } else {
514     // cache is full
515     // take the last entry in the list, and recycle it.
516     cacheEntry = mBundleCache.getLast();
517 
518     // remove it from the hash table and linked list
519     NS_ASSERTION(mBundleMap.Contains(cacheEntry->mHashKey),
520                  "Element will not be removed!");
521     mBundleMap.Remove(cacheEntry->mHashKey);
522     cacheEntry->remove();
523   }
524 
525   // at this point we have a new cacheEntry that doesn't exist
526   // in the hashtable, so set up the cacheEntry
527   cacheEntry->mHashKey = aHashKey;
528   cacheEntry->mBundle = aBundle;
529 
530   // insert the entry into the cache and map, make it the MRU
531   mBundleMap.Put(cacheEntry->mHashKey, cacheEntry);
532 
533   return cacheEntry;
534 }
535 
536 NS_IMETHODIMP
CreateBundle(const char * aURLSpec,nsIStringBundle ** aResult)537 nsStringBundleService::CreateBundle(const char* aURLSpec,
538                                     nsIStringBundle** aResult) {
539   getStringBundle(aURLSpec, aResult);
540   return NS_OK;
541 }
542 
543 NS_IMETHODIMP
CreateExtensibleBundle(const char * aCategory,nsIStringBundle ** aResult)544 nsStringBundleService::CreateExtensibleBundle(const char* aCategory,
545                                               nsIStringBundle** aResult) {
546   NS_ENSURE_ARG_POINTER(aResult);
547   *aResult = nullptr;
548 
549   RefPtr<nsExtensibleStringBundle> bundle = new nsExtensibleStringBundle();
550 
551   nsresult res = bundle->Init(aCategory, this);
552   if (NS_FAILED(res)) {
553     return res;
554   }
555 
556   bundle.forget(aResult);
557   return NS_OK;
558 }
559 
560 #define GLOBAL_PROPERTIES "chrome://global/locale/global-strres.properties"
561 
FormatWithBundle(nsIStringBundle * bundle,nsresult aStatus,uint32_t argCount,char16_t ** argArray,nsAString & result)562 nsresult nsStringBundleService::FormatWithBundle(nsIStringBundle* bundle,
563                                                  nsresult aStatus,
564                                                  uint32_t argCount,
565                                                  char16_t** argArray,
566                                                  nsAString& result) {
567   nsresult rv;
568 
569   // try looking up the error message with the int key:
570   uint16_t code = NS_ERROR_GET_CODE(aStatus);
571   rv = bundle->FormatStringFromID(code, (const char16_t**)argArray, argCount,
572                                   result);
573 
574   // If the int key fails, try looking up the default error message. E.g. print:
575   //   An unknown error has occurred (0x804B0003).
576   if (NS_FAILED(rv)) {
577     nsAutoString statusStr;
578     statusStr.AppendInt(static_cast<uint32_t>(aStatus), 16);
579     const char16_t* otherArgArray[1];
580     otherArgArray[0] = statusStr.get();
581     uint16_t code = NS_ERROR_GET_CODE(NS_ERROR_FAILURE);
582     rv = bundle->FormatStringFromID(code, otherArgArray, 1, result);
583   }
584 
585   return rv;
586 }
587 
588 NS_IMETHODIMP
FormatStatusMessage(nsresult aStatus,const char16_t * aStatusArg,nsAString & result)589 nsStringBundleService::FormatStatusMessage(nsresult aStatus,
590                                            const char16_t* aStatusArg,
591                                            nsAString& result) {
592   nsresult rv;
593   uint32_t i, argCount = 0;
594   nsCOMPtr<nsIStringBundle> bundle;
595   nsCString stringBundleURL;
596 
597   // XXX hack for mailnews who has already formatted their messages:
598   if (aStatus == NS_OK && aStatusArg) {
599     result.Assign(aStatusArg);
600     return NS_OK;
601   }
602 
603   if (aStatus == NS_OK) {
604     return NS_ERROR_FAILURE;  // no message to format
605   }
606 
607   // format the arguments:
608   const nsDependentString args(aStatusArg);
609   argCount = args.CountChar(char16_t('\n')) + 1;
610   NS_ENSURE_ARG(argCount <= 10);  // enforce 10-parameter limit
611   char16_t* argArray[10];
612 
613   // convert the aStatusArg into a char16_t array
614   if (argCount == 1) {
615     // avoid construction for the simple case:
616     argArray[0] = (char16_t*)aStatusArg;
617   } else if (argCount > 1) {
618     int32_t offset = 0;
619     for (i = 0; i < argCount; i++) {
620       int32_t pos = args.FindChar('\n', offset);
621       if (pos == -1) pos = args.Length();
622       argArray[i] = ToNewUnicode(Substring(args, offset, pos - offset));
623       if (argArray[i] == nullptr) {
624         rv = NS_ERROR_OUT_OF_MEMORY;
625         argCount = i - 1;  // don't try to free uninitialized memory
626         goto done;
627       }
628       offset = pos + 1;
629     }
630   }
631 
632   // find the string bundle for the error's module:
633   rv = mErrorService->GetErrorStringBundle(NS_ERROR_GET_MODULE(aStatus),
634                                            getter_Copies(stringBundleURL));
635   if (NS_SUCCEEDED(rv)) {
636     getStringBundle(stringBundleURL.get(), getter_AddRefs(bundle));
637     rv = FormatWithBundle(bundle, aStatus, argCount, argArray, result);
638   }
639   if (NS_FAILED(rv)) {
640     getStringBundle(GLOBAL_PROPERTIES, getter_AddRefs(bundle));
641     rv = FormatWithBundle(bundle, aStatus, argCount, argArray, result);
642   }
643 
644 done:
645   if (argCount > 1) {
646     for (i = 0; i < argCount; i++) {
647       if (argArray[i]) free(argArray[i]);
648     }
649   }
650   return rv;
651 }
652