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 "AddonManagerStartup.h"
7 #include "AddonManagerStartup-inlines.h"
8 
9 #include "jsapi.h"
10 #include "jsfriendapi.h"
11 #include "js/Array.h"  // JS::IsArrayObject
12 #include "js/ArrayBuffer.h"
13 #include "js/JSON.h"
14 #include "js/TracingAPI.h"
15 #include "xpcpublic.h"
16 
17 #include "mozilla/ClearOnShutdown.h"
18 #include "mozilla/EndianUtils.h"
19 #include "mozilla/Components.h"
20 #include "mozilla/Compression.h"
21 #include "mozilla/LinkedList.h"
22 #include "mozilla/Preferences.h"
23 #include "mozilla/ResultExtensions.h"
24 #include "mozilla/URLPreloader.h"
25 #include "mozilla/Unused.h"
26 #include "mozilla/ErrorResult.h"
27 #include "mozilla/dom/ipc/StructuredCloneData.h"
28 
29 #include "nsAppDirectoryServiceDefs.h"
30 #include "nsAppRunner.h"
31 #include "nsContentUtils.h"
32 #include "nsChromeRegistry.h"
33 #include "nsIAppStartup.h"
34 #include "nsIDOMWindowUtils.h"  // for nsIJSRAIIHelper
35 #include "nsIFileURL.h"
36 #include "nsIIOService.h"
37 #include "nsIJARURI.h"
38 #include "nsIStringEnumerator.h"
39 #include "nsIZipReader.h"
40 #include "nsJARProtocolHandler.h"
41 #include "nsJSUtils.h"
42 #include "nsReadableUtils.h"
43 #include "nsXULAppAPI.h"
44 
45 #include <stdlib.h>
46 
47 namespace mozilla {
48 
49 using Compression::LZ4;
50 using dom::ipc::StructuredCloneData;
51 
GetSingleton()52 AddonManagerStartup& AddonManagerStartup::GetSingleton() {
53   static RefPtr<AddonManagerStartup> singleton;
54   if (!singleton) {
55     singleton = new AddonManagerStartup();
56     ClearOnShutdown(&singleton);
57   }
58   return *singleton;
59 }
60 
61 AddonManagerStartup::AddonManagerStartup() = default;
62 
ProfileDir()63 nsIFile* AddonManagerStartup::ProfileDir() {
64   if (!mProfileDir) {
65     nsresult rv;
66 
67     rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
68                                 getter_AddRefs(mProfileDir));
69     MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
70   }
71 
72   return mProfileDir;
73 }
74 
NS_IMPL_ISUPPORTS(AddonManagerStartup,amIAddonManagerStartup,nsIObserver)75 NS_IMPL_ISUPPORTS(AddonManagerStartup, amIAddonManagerStartup, nsIObserver)
76 
77 /*****************************************************************************
78  * URI utils
79  *****************************************************************************/
80 
81 static nsresult ParseJARURI(nsIJARURI* uri, nsIURI** jarFile,
82                             nsCString& entry) {
83   MOZ_TRY(uri->GetJARFile(jarFile));
84   MOZ_TRY(uri->GetJAREntry(entry));
85 
86   // The entry portion of a jar: URI is required to begin with a '/', but for
87   // nested JAR URIs, the leading / of the outer entry is currently stripped.
88   // This is a bug which should be fixed in the JAR URI code, but...
89   if (entry.IsEmpty() || entry[0] != '/') {
90     entry.Insert('/', 0);
91   }
92   return NS_OK;
93 }
94 
ParseJARURI(nsIURI * uri,nsIURI ** jarFile,nsCString & entry)95 static nsresult ParseJARURI(nsIURI* uri, nsIURI** jarFile, nsCString& entry) {
96   nsresult rv;
97   nsCOMPtr<nsIJARURI> jarURI = do_QueryInterface(uri, &rv);
98   MOZ_TRY(rv);
99 
100   return ParseJARURI(jarURI, jarFile, entry);
101 }
102 
GetFile(nsIURI * uri)103 static Result<nsCOMPtr<nsIFile>, nsresult> GetFile(nsIURI* uri) {
104   nsresult rv;
105   nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(uri, &rv);
106   MOZ_TRY(rv);
107 
108   nsCOMPtr<nsIFile> file;
109   MOZ_TRY(fileURL->GetFile(getter_AddRefs(file)));
110   MOZ_ASSERT(file);
111 
112   return std::move(file);
113 }
114 
115 /*****************************************************************************
116  * File utils
117  *****************************************************************************/
118 
CloneAndAppend(nsIFile * aFile,const char * name)119 static already_AddRefed<nsIFile> CloneAndAppend(nsIFile* aFile,
120                                                 const char* name) {
121   nsCOMPtr<nsIFile> file;
122   aFile->Clone(getter_AddRefs(file));
123   file->AppendNative(nsDependentCString(name));
124   return file.forget();
125 }
126 
IsNormalFile(nsIFile * file)127 static bool IsNormalFile(nsIFile* file) {
128   bool result;
129   return NS_SUCCEEDED(file->IsFile(&result)) && result;
130 }
131 
132 static const char STRUCTURED_CLONE_MAGIC[] = "mozJSSCLz40v001";
133 
134 template <typename T>
DecodeLZ4(const nsACString & lz4,const T & magicNumber)135 static Result<nsCString, nsresult> DecodeLZ4(const nsACString& lz4,
136                                              const T& magicNumber) {
137   constexpr auto HEADER_SIZE = sizeof(magicNumber) + 4;
138 
139   // Note: We want to include the null terminator here.
140   nsDependentCSubstring magic(magicNumber, sizeof(magicNumber));
141 
142   if (lz4.Length() < HEADER_SIZE || StringHead(lz4, magic.Length()) != magic) {
143     return Err(NS_ERROR_UNEXPECTED);
144   }
145 
146   auto data = lz4.BeginReading() + magic.Length();
147   auto size = LittleEndian::readUint32(data);
148   data += 4;
149 
150   size_t dataLen = lz4.EndReading() - data;
151   size_t outputSize;
152 
153   nsCString result;
154   if (!result.SetLength(size, fallible) ||
155       !LZ4::decompress(data, dataLen, result.BeginWriting(), size,
156                        &outputSize)) {
157     return Err(NS_ERROR_UNEXPECTED);
158   }
159 
160   MOZ_DIAGNOSTIC_ASSERT(size == outputSize);
161 
162   return std::move(result);
163 }
164 
165 // Our zlib headers redefine this to MOZ_Z_compress, which breaks LZ4::compress
166 #undef compress
167 
168 template <typename T>
EncodeLZ4(const nsACString & data,const T & magicNumber)169 static Result<nsCString, nsresult> EncodeLZ4(const nsACString& data,
170                                              const T& magicNumber) {
171   // Note: We want to include the null terminator here.
172   nsDependentCSubstring magic(magicNumber, sizeof(magicNumber));
173 
174   nsAutoCString result;
175   result.Append(magic);
176 
177   auto off = result.Length();
178   if (!result.SetLength(off + 4, fallible)) {
179     return Err(NS_ERROR_OUT_OF_MEMORY);
180   }
181 
182   LittleEndian::writeUint32(result.BeginWriting() + off, data.Length());
183   off += 4;
184 
185   auto size = LZ4::maxCompressedSize(data.Length());
186   if (!result.SetLength(off + size, fallible)) {
187     return Err(NS_ERROR_OUT_OF_MEMORY);
188   }
189 
190   size = LZ4::compress(data.BeginReading(), data.Length(),
191                        result.BeginWriting() + off);
192 
193   if (!result.SetLength(off + size, fallible)) {
194     return Err(NS_ERROR_OUT_OF_MEMORY);
195   }
196   return std::move(result);
197 }
198 
199 static_assert(sizeof STRUCTURED_CLONE_MAGIC % 8 == 0,
200               "Magic number should be an array of uint64_t");
201 
202 /**
203  * Reads the contents of a LZ4-compressed file, as stored by the OS.File
204  * module, and returns the decompressed contents on success.
205  */
ReadFileLZ4(nsIFile * file)206 static Result<nsCString, nsresult> ReadFileLZ4(nsIFile* file) {
207   static const char MAGIC_NUMBER[] = "mozLz40";
208 
209   nsCString lz4;
210   MOZ_TRY_VAR(lz4, URLPreloader::ReadFile(file));
211 
212   if (lz4.IsEmpty()) {
213     return lz4;
214   }
215 
216   return DecodeLZ4(lz4, MAGIC_NUMBER);
217 }
218 
ParseJSON(JSContext * cx,nsACString & jsonData,JS::MutableHandleValue result)219 static bool ParseJSON(JSContext* cx, nsACString& jsonData,
220                       JS::MutableHandleValue result) {
221   NS_ConvertUTF8toUTF16 str(jsonData);
222   jsonData.Truncate();
223 
224   return JS_ParseJSON(cx, str.Data(), str.Length(), result);
225 }
226 
GetJarCache()227 static Result<nsCOMPtr<nsIZipReaderCache>, nsresult> GetJarCache() {
228   nsCOMPtr<nsIIOService> ios = components::IO::Service();
229   NS_ENSURE_TRUE(ios, Err(NS_ERROR_FAILURE));
230 
231   nsCOMPtr<nsIProtocolHandler> jarProto;
232   MOZ_TRY(ios->GetProtocolHandler("jar", getter_AddRefs(jarProto)));
233 
234   auto jar = static_cast<nsJARProtocolHandler*>(jarProto.get());
235   MOZ_ASSERT(jar);
236 
237   nsCOMPtr<nsIZipReaderCache> zipCache = jar->JarCache();
238   return std::move(zipCache);
239 }
240 
GetFileLocation(nsIURI * uri)241 static Result<FileLocation, nsresult> GetFileLocation(nsIURI* uri) {
242   FileLocation location;
243 
244   nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(uri);
245   nsCOMPtr<nsIFile> file;
246   if (fileURL) {
247     MOZ_TRY(fileURL->GetFile(getter_AddRefs(file)));
248     location.Init(file);
249   } else {
250     nsCOMPtr<nsIURI> fileURI;
251     nsCString entry;
252     MOZ_TRY(ParseJARURI(uri, getter_AddRefs(fileURI), entry));
253 
254     MOZ_TRY_VAR(file, GetFile(fileURI));
255 
256     location.Init(file, entry.get());
257   }
258 
259   return std::move(location);
260 }
261 
262 /*****************************************************************************
263  * JSON data handling
264  *****************************************************************************/
265 
266 class MOZ_STACK_CLASS WrapperBase {
267  protected:
WrapperBase(JSContext * cx,JSObject * object)268   WrapperBase(JSContext* cx, JSObject* object) : mCx(cx), mObject(cx, object) {}
269 
WrapperBase(JSContext * cx,const JS::Value & value)270   WrapperBase(JSContext* cx, const JS::Value& value) : mCx(cx), mObject(cx) {
271     if (value.isObject()) {
272       mObject = &value.toObject();
273     } else {
274       mObject = JS_NewPlainObject(cx);
275     }
276   }
277 
278  protected:
279   JSContext* mCx;
280   JS::RootedObject mObject;
281 
282   bool GetBool(const char* name, bool defVal = false);
283 
284   double GetNumber(const char* name, double defVal = 0);
285 
286   nsString GetString(const char* name, const char* defVal = "");
287 
288   JSObject* GetObject(const char* name);
289 };
290 
GetBool(const char * name,bool defVal)291 bool WrapperBase::GetBool(const char* name, bool defVal) {
292   JS::RootedObject obj(mCx, mObject);
293 
294   JS::RootedValue val(mCx, JS::UndefinedValue());
295   if (!JS_GetProperty(mCx, obj, name, &val)) {
296     JS_ClearPendingException(mCx);
297   }
298 
299   if (val.isBoolean()) {
300     return val.toBoolean();
301   }
302   return defVal;
303 }
304 
GetNumber(const char * name,double defVal)305 double WrapperBase::GetNumber(const char* name, double defVal) {
306   JS::RootedObject obj(mCx, mObject);
307 
308   JS::RootedValue val(mCx, JS::UndefinedValue());
309   if (!JS_GetProperty(mCx, obj, name, &val)) {
310     JS_ClearPendingException(mCx);
311   }
312 
313   if (val.isNumber()) {
314     return val.toNumber();
315   }
316   return defVal;
317 }
318 
GetString(const char * name,const char * defVal)319 nsString WrapperBase::GetString(const char* name, const char* defVal) {
320   JS::RootedObject obj(mCx, mObject);
321 
322   JS::RootedValue val(mCx, JS::UndefinedValue());
323   if (!JS_GetProperty(mCx, obj, name, &val)) {
324     JS_ClearPendingException(mCx);
325   }
326 
327   nsString res;
328   if (val.isString()) {
329     AssignJSString(mCx, res, val.toString());
330   } else {
331     res.AppendASCII(defVal);
332   }
333   return res;
334 }
335 
GetObject(const char * name)336 JSObject* WrapperBase::GetObject(const char* name) {
337   JS::RootedObject obj(mCx, mObject);
338 
339   JS::RootedValue val(mCx, JS::UndefinedValue());
340   if (!JS_GetProperty(mCx, obj, name, &val)) {
341     JS_ClearPendingException(mCx);
342   }
343 
344   if (val.isObject()) {
345     return &val.toObject();
346   }
347   return nullptr;
348 }
349 
350 class MOZ_STACK_CLASS InstallLocation : public WrapperBase {
351  public:
352   InstallLocation(JSContext* cx, const JS::Value& value);
353 
InstallLocation(PropertyIterElem & iter)354   MOZ_IMPLICIT InstallLocation(PropertyIterElem& iter)
355       : InstallLocation(iter.Cx(), iter.Value()) {}
356 
InstallLocation(const InstallLocation & other)357   InstallLocation(const InstallLocation& other)
358       : InstallLocation(other.mCx, JS::ObjectValue(*other.mObject)) {}
359 
SetChanged(bool changed)360   void SetChanged(bool changed) {
361     JS::RootedObject obj(mCx, mObject);
362 
363     JS::RootedValue val(mCx, JS::BooleanValue(changed));
364     if (!JS_SetProperty(mCx, obj, "changed", val)) {
365       JS_ClearPendingException(mCx);
366     }
367   }
368 
Addons()369   PropertyIter& Addons() { return mAddonsIter.ref(); }
370 
Path()371   nsString Path() { return GetString("path"); }
372 
ShouldCheckStartupModifications()373   bool ShouldCheckStartupModifications() {
374     return GetBool("checkStartupModifications");
375   }
376 
377  private:
378   JS::RootedObject mAddonsObj;
379   Maybe<PropertyIter> mAddonsIter;
380 };
381 
382 class MOZ_STACK_CLASS Addon : public WrapperBase {
383  public:
Addon(JSContext * cx,InstallLocation & location,const nsAString & id,JSObject * object)384   Addon(JSContext* cx, InstallLocation& location, const nsAString& id,
385         JSObject* object)
386       : WrapperBase(cx, object), mId(id), mLocation(location) {}
387 
Addon(PropertyIterElem & iter)388   MOZ_IMPLICIT Addon(PropertyIterElem& iter)
389       : WrapperBase(iter.Cx(), iter.Value()),
390         mId(iter.Name()),
391         mLocation(*static_cast<InstallLocation*>(iter.Context())) {}
392 
Addon(const Addon & other)393   Addon(const Addon& other)
394       : WrapperBase(other.mCx, other.mObject),
395         mId(other.mId),
396         mLocation(other.mLocation) {}
397 
Id()398   const nsString& Id() { return mId; }
399 
Path()400   nsString Path() { return GetString("path"); }
401 
Type()402   nsString Type() { return GetString("type", "extension"); }
403 
Enabled()404   bool Enabled() { return GetBool("enabled"); }
405 
LastModifiedTime()406   double LastModifiedTime() { return GetNumber("lastModifiedTime"); }
407 
ShouldCheckStartupModifications()408   bool ShouldCheckStartupModifications() {
409     return Type().EqualsLiteral("locale");
410   }
411 
412   Result<nsCOMPtr<nsIFile>, nsresult> FullPath();
413 
414   Result<bool, nsresult> UpdateLastModifiedTime();
415 
416  private:
417   nsString mId;
418   InstallLocation& mLocation;
419 };
420 
FullPath()421 Result<nsCOMPtr<nsIFile>, nsresult> Addon::FullPath() {
422   nsString path = Path();
423 
424   // First check for an absolute path, in case we have a proxy file.
425   nsCOMPtr<nsIFile> file;
426   if (NS_SUCCEEDED(NS_NewLocalFile(path, false, getter_AddRefs(file)))) {
427     return std::move(file);
428   }
429 
430   // If not an absolute path, fall back to a relative path from the location.
431   MOZ_TRY(NS_NewLocalFile(mLocation.Path(), false, getter_AddRefs(file)));
432 
433   MOZ_TRY(file->AppendRelativePath(path));
434   return std::move(file);
435 }
436 
UpdateLastModifiedTime()437 Result<bool, nsresult> Addon::UpdateLastModifiedTime() {
438   nsCOMPtr<nsIFile> file;
439   MOZ_TRY_VAR(file, FullPath());
440 
441   JS::RootedObject obj(mCx, mObject);
442 
443   bool result;
444   if (NS_FAILED(file->Exists(&result)) || !result) {
445     JS::RootedValue value(mCx, JS::NullValue());
446     if (!JS_SetProperty(mCx, obj, "currentModifiedTime", value)) {
447       JS_ClearPendingException(mCx);
448     }
449 
450     return true;
451   }
452 
453   PRTime time;
454 
455   nsCOMPtr<nsIFile> manifest = file;
456   if (!IsNormalFile(manifest)) {
457     manifest = CloneAndAppend(file, "manifest.json");
458     if (!IsNormalFile(manifest)) {
459       return true;
460     }
461   }
462 
463   if (NS_FAILED(manifest->GetLastModifiedTime(&time))) {
464     return true;
465   }
466 
467   double lastModified = time;
468   JS::RootedValue value(mCx, JS::NumberValue(lastModified));
469   if (!JS_SetProperty(mCx, obj, "currentModifiedTime", value)) {
470     JS_ClearPendingException(mCx);
471   }
472 
473   return lastModified != LastModifiedTime();
474 }
475 
InstallLocation(JSContext * cx,const JS::Value & value)476 InstallLocation::InstallLocation(JSContext* cx, const JS::Value& value)
477     : WrapperBase(cx, value), mAddonsObj(cx), mAddonsIter() {
478   mAddonsObj = GetObject("addons");
479   if (!mAddonsObj) {
480     mAddonsObj = JS_NewPlainObject(cx);
481   }
482   mAddonsIter.emplace(cx, mAddonsObj, this);
483 }
484 
485 /*****************************************************************************
486  * XPC interfacing
487  *****************************************************************************/
488 
ReadStartupData(JSContext * cx,JS::MutableHandleValue locations)489 nsresult AddonManagerStartup::ReadStartupData(
490     JSContext* cx, JS::MutableHandleValue locations) {
491   locations.set(JS::UndefinedValue());
492 
493   nsCOMPtr<nsIFile> file =
494       CloneAndAppend(ProfileDir(), "addonStartup.json.lz4");
495 
496   nsCString data;
497   auto res = ReadFileLZ4(file);
498   if (res.isOk()) {
499     data = res.unwrap();
500   } else if (res.inspectErr() != NS_ERROR_FILE_NOT_FOUND) {
501     return res.unwrapErr();
502   }
503 
504   if (data.IsEmpty() || !ParseJSON(cx, data, locations)) {
505     return NS_OK;
506   }
507 
508   if (!locations.isObject()) {
509     return NS_ERROR_UNEXPECTED;
510   }
511 
512   JS::RootedObject locs(cx, &locations.toObject());
513   for (auto e1 : PropertyIter(cx, locs)) {
514     InstallLocation loc(e1);
515 
516     bool shouldCheck = loc.ShouldCheckStartupModifications();
517 
518     for (auto e2 : loc.Addons()) {
519       Addon addon(e2);
520 
521       if (addon.Enabled() &&
522           (shouldCheck || addon.ShouldCheckStartupModifications())) {
523         bool changed;
524         MOZ_TRY_VAR(changed, addon.UpdateLastModifiedTime());
525         if (changed) {
526           loc.SetChanged(true);
527         }
528       }
529     }
530   }
531 
532   return NS_OK;
533 }
534 
EncodeBlob(JS::HandleValue value,JSContext * cx,JS::MutableHandleValue result)535 nsresult AddonManagerStartup::EncodeBlob(JS::HandleValue value, JSContext* cx,
536                                          JS::MutableHandleValue result) {
537   StructuredCloneData holder;
538 
539   ErrorResult rv;
540   holder.Write(cx, value, rv);
541   if (rv.Failed()) {
542     return rv.StealNSResult();
543   }
544 
545   nsAutoCString scData;
546 
547   holder.Data().ForEachDataChunk([&](const char* aData, size_t aSize) {
548     scData.Append(nsDependentCSubstring(aData, aSize));
549     return true;
550   });
551 
552   nsCString lz4;
553   MOZ_TRY_VAR(lz4, EncodeLZ4(scData, STRUCTURED_CLONE_MAGIC));
554 
555   JS::RootedObject obj(cx);
556   MOZ_TRY(nsContentUtils::CreateArrayBuffer(cx, lz4, &obj.get()));
557 
558   result.set(JS::ObjectValue(*obj));
559   return NS_OK;
560 }
561 
DecodeBlob(JS::HandleValue value,JSContext * cx,JS::MutableHandleValue result)562 nsresult AddonManagerStartup::DecodeBlob(JS::HandleValue value, JSContext* cx,
563                                          JS::MutableHandleValue result) {
564   NS_ENSURE_TRUE(value.isObject() &&
565                      JS::IsArrayBufferObject(&value.toObject()) &&
566                      JS::ArrayBufferHasData(&value.toObject()),
567                  NS_ERROR_INVALID_ARG);
568 
569   StructuredCloneData holder;
570 
571   nsCString data;
572   {
573     JS::AutoCheckCannotGC nogc;
574 
575     auto obj = &value.toObject();
576     bool isShared;
577 
578     size_t len = JS::GetArrayBufferByteLength(obj);
579     NS_ENSURE_TRUE(len <= INT32_MAX, NS_ERROR_INVALID_ARG);
580     nsDependentCSubstring lz4(
581         reinterpret_cast<char*>(JS::GetArrayBufferData(obj, &isShared, nogc)),
582         uint32_t(len));
583 
584     MOZ_TRY_VAR(data, DecodeLZ4(lz4, STRUCTURED_CLONE_MAGIC));
585   }
586 
587   bool ok = holder.CopyExternalData(data.get(), data.Length());
588   NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
589 
590   ErrorResult rv;
591   holder.Read(cx, result, rv);
592   return rv.StealNSResult();
593   ;
594 }
595 
EnumerateZip(nsIZipReader * zip,const nsACString & pattern,nsTArray<nsString> & results)596 static nsresult EnumerateZip(nsIZipReader* zip, const nsACString& pattern,
597                              nsTArray<nsString>& results) {
598   nsCOMPtr<nsIUTF8StringEnumerator> entries;
599   MOZ_TRY(zip->FindEntries(pattern, getter_AddRefs(entries)));
600 
601   bool hasMore;
602   while (NS_SUCCEEDED(entries->HasMore(&hasMore)) && hasMore) {
603     nsAutoCString name;
604     MOZ_TRY(entries->GetNext(name));
605 
606     results.AppendElement(NS_ConvertUTF8toUTF16(name));
607   }
608 
609   return NS_OK;
610 }
611 
EnumerateJAR(nsIURI * uri,const nsACString & pattern,nsTArray<nsString> & results)612 nsresult AddonManagerStartup::EnumerateJAR(nsIURI* uri,
613                                            const nsACString& pattern,
614                                            nsTArray<nsString>& results) {
615   nsCOMPtr<nsIZipReaderCache> zipCache;
616   MOZ_TRY_VAR(zipCache, GetJarCache());
617 
618   nsCOMPtr<nsIZipReader> zip;
619   nsCOMPtr<nsIFile> file;
620   if (nsCOMPtr<nsIJARURI> jarURI = do_QueryInterface(uri)) {
621     nsCOMPtr<nsIURI> fileURI;
622     nsCString entry;
623     MOZ_TRY(ParseJARURI(jarURI, getter_AddRefs(fileURI), entry));
624 
625     MOZ_TRY_VAR(file, GetFile(fileURI));
626     MOZ_TRY(
627         zipCache->GetInnerZip(file, Substring(entry, 1), getter_AddRefs(zip)));
628   } else {
629     MOZ_TRY_VAR(file, GetFile(uri));
630     MOZ_TRY(zipCache->GetZip(file, getter_AddRefs(zip)));
631   }
632   MOZ_ASSERT(zip);
633 
634   return EnumerateZip(zip, pattern, results);
635 }
636 
EnumerateJARSubtree(nsIURI * uri,nsTArray<nsString> & results)637 nsresult AddonManagerStartup::EnumerateJARSubtree(nsIURI* uri,
638                                                   nsTArray<nsString>& results) {
639   nsCOMPtr<nsIURI> fileURI;
640   nsCString entry;
641   MOZ_TRY(ParseJARURI(uri, getter_AddRefs(fileURI), entry));
642 
643   // Mangle the path into a pattern to match all child entries by escaping any
644   // existing pattern matching metacharacters it contains and appending "/*".
645   constexpr auto metaChars = "[]()?*~|$\\"_ns;
646 
647   nsCString pattern;
648   pattern.SetCapacity(entry.Length());
649 
650   // The first character of the entry name is "/", which we want to skip.
651   for (auto chr : Span(Substring(entry, 1))) {
652     if (metaChars.FindChar(chr) >= 0) {
653       pattern.Append('\\');
654     }
655     pattern.Append(chr);
656   }
657   if (!pattern.IsEmpty() && !StringEndsWith(pattern, "/"_ns)) {
658     pattern.Append('/');
659   }
660   pattern.Append('*');
661 
662   return EnumerateJAR(fileURI, pattern, results);
663 }
664 
InitializeURLPreloader()665 nsresult AddonManagerStartup::InitializeURLPreloader() {
666   MOZ_RELEASE_ASSERT(xpc::IsInAutomation());
667 
668   URLPreloader::ReInitialize();
669 
670   return NS_OK;
671 }
672 
673 /******************************************************************************
674  * RegisterChrome
675  ******************************************************************************/
676 
677 namespace {
678 static bool sObserverRegistered;
679 
680 struct ContentEntry final {
ContentEntrymozilla::__anona25dda5d0211::ContentEntry681   explicit ContentEntry(nsTArray<nsCString>&& aArgs, uint8_t aFlags = 0)
682       : mArgs(std::move(aArgs)), mFlags(aFlags) {}
683 
684   AutoTArray<nsCString, 2> mArgs;
685   uint8_t mFlags;
686 };
687 
688 };  // anonymous namespace
689 };  // namespace mozilla
690 
691 MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(mozilla::ContentEntry);
692 
693 namespace mozilla {
694 namespace {
695 
696 class RegistryEntries final : public nsIJSRAIIHelper,
697                               public LinkedListElement<RegistryEntries> {
698  public:
699   NS_DECL_ISUPPORTS
700   NS_DECL_NSIJSRAIIHELPER
701 
702   using Override = AutoTArray<nsCString, 2>;
703   using Locale = AutoTArray<nsCString, 3>;
704 
RegistryEntries(FileLocation & location,nsTArray<Override> && overrides,nsTArray<ContentEntry> && content,nsTArray<Locale> && locales)705   RegistryEntries(FileLocation& location, nsTArray<Override>&& overrides,
706                   nsTArray<ContentEntry>&& content, nsTArray<Locale>&& locales)
707       : mLocation(location),
708         mOverrides(std::move(overrides)),
709         mContent(std::move(content)),
710         mLocales(std::move(locales)) {}
711 
712   void Register();
713 
714  protected:
~RegistryEntries()715   virtual ~RegistryEntries() { Unused << Destruct(); }
716 
717  private:
718   FileLocation mLocation;
719   const nsTArray<Override> mOverrides;
720   const nsTArray<ContentEntry> mContent;
721   const nsTArray<Locale> mLocales;
722 };
723 
NS_IMPL_ISUPPORTS(RegistryEntries,nsIJSRAIIHelper)724 NS_IMPL_ISUPPORTS(RegistryEntries, nsIJSRAIIHelper)
725 
726 void RegistryEntries::Register() {
727   RefPtr<nsChromeRegistry> cr = nsChromeRegistry::GetSingleton();
728 
729   nsChromeRegistry::ManifestProcessingContext context(NS_EXTENSION_LOCATION,
730                                                       mLocation);
731 
732   for (auto& override : mOverrides) {
733     const char* args[] = {override[0].get(), override[1].get()};
734     cr->ManifestOverride(context, 0, const_cast<char**>(args), 0);
735   }
736 
737   for (auto& content : mContent) {
738     const char* args[] = {content.mArgs[0].get(), content.mArgs[1].get()};
739     cr->ManifestContent(context, 0, const_cast<char**>(args), content.mFlags);
740   }
741 
742   for (auto& locale : mLocales) {
743     const char* args[] = {locale[0].get(), locale[1].get(), locale[2].get()};
744     cr->ManifestLocale(context, 0, const_cast<char**>(args), 0);
745   }
746 }
747 
748 NS_IMETHODIMP
Destruct()749 RegistryEntries::Destruct() {
750   if (isInList()) {
751     remove();
752 
753     // No point in doing I/O to check for new chrome during shutdown, return
754     // early in that case.
755     nsCOMPtr<nsIAppStartup> appStartup = components::AppStartup::Service();
756     if (!appStartup || appStartup->GetShuttingDown()) {
757       return NS_OK;
758     }
759 
760     // When we remove dynamic entries from the registry, we need to rebuild it
761     // in order to ensure a consistent state. See comments in Observe().
762     RefPtr<nsChromeRegistry> cr = nsChromeRegistry::GetSingleton();
763     return cr->CheckForNewChrome();
764   }
765   return NS_OK;
766 }
767 
GetRegistryEntries()768 static LinkedList<RegistryEntries>& GetRegistryEntries() {
769   static LinkedList<RegistryEntries> sEntries;
770   return sEntries;
771 }
772 };  // anonymous namespace
773 
774 NS_IMETHODIMP
RegisterChrome(nsIURI * manifestURI,JS::HandleValue locations,JSContext * cx,nsIJSRAIIHelper ** result)775 AddonManagerStartup::RegisterChrome(nsIURI* manifestURI,
776                                     JS::HandleValue locations, JSContext* cx,
777                                     nsIJSRAIIHelper** result) {
778   auto IsArray = [cx](JS::HandleValue val) -> bool {
779     bool isArray;
780     return JS::IsArrayObject(cx, val, &isArray) && isArray;
781   };
782 
783   NS_ENSURE_ARG_POINTER(manifestURI);
784   NS_ENSURE_TRUE(IsArray(locations), NS_ERROR_INVALID_ARG);
785 
786   FileLocation location;
787   MOZ_TRY_VAR(location, GetFileLocation(manifestURI));
788 
789   nsTArray<RegistryEntries::Locale> locales;
790   nsTArray<ContentEntry> content;
791   nsTArray<RegistryEntries::Override> overrides;
792 
793   JS::RootedObject locs(cx, &locations.toObject());
794   JS::RootedValue arrayVal(cx);
795   JS::RootedObject array(cx);
796 
797   for (auto elem : ArrayIter(cx, locs)) {
798     arrayVal = elem.Value();
799     NS_ENSURE_TRUE(IsArray(arrayVal), NS_ERROR_INVALID_ARG);
800 
801     array = &arrayVal.toObject();
802 
803     AutoTArray<nsCString, 4> vals;
804     for (auto val : ArrayIter(cx, array)) {
805       nsAutoJSString str;
806       NS_ENSURE_TRUE(str.init(cx, val.Value()), NS_ERROR_OUT_OF_MEMORY);
807 
808       vals.AppendElement(NS_ConvertUTF16toUTF8(str));
809     }
810     NS_ENSURE_TRUE(vals.Length() > 0, NS_ERROR_INVALID_ARG);
811 
812     nsCString type = vals[0];
813     vals.RemoveElementAt(0);
814 
815     if (type.EqualsLiteral("override")) {
816       NS_ENSURE_TRUE(vals.Length() == 2, NS_ERROR_INVALID_ARG);
817       overrides.AppendElement(std::move(vals));
818     } else if (type.EqualsLiteral("content")) {
819       if (vals.Length() == 3 &&
820           vals[2].EqualsLiteral("contentaccessible=yes")) {
821         NS_ENSURE_TRUE(xpc::IsInAutomation(), NS_ERROR_INVALID_ARG);
822         vals.RemoveElementAt(2);
823         content.AppendElement(ContentEntry(
824             std::move(vals), nsChromeRegistry::CONTENT_ACCESSIBLE));
825       } else {
826         NS_ENSURE_TRUE(vals.Length() == 2, NS_ERROR_INVALID_ARG);
827         content.AppendElement(ContentEntry(std::move(vals)));
828       }
829     } else if (type.EqualsLiteral("locale")) {
830       NS_ENSURE_TRUE(vals.Length() == 3, NS_ERROR_INVALID_ARG);
831       locales.AppendElement(std::move(vals));
832     } else {
833       return NS_ERROR_INVALID_ARG;
834     }
835   }
836 
837   if (!sObserverRegistered) {
838     nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
839     NS_ENSURE_TRUE(obs, NS_ERROR_UNEXPECTED);
840     obs->AddObserver(this, "chrome-manifests-loaded", false);
841 
842     sObserverRegistered = true;
843   }
844 
845   auto entry = MakeRefPtr<RegistryEntries>(
846       location, std::move(overrides), std::move(content), std::move(locales));
847 
848   entry->Register();
849   GetRegistryEntries().insertBack(entry);
850 
851   entry.forget(result);
852   return NS_OK;
853 }
854 
855 NS_IMETHODIMP
Observe(nsISupports * subject,const char * topic,const char16_t * data)856 AddonManagerStartup::Observe(nsISupports* subject, const char* topic,
857                              const char16_t* data) {
858   // The chrome registry is maintained as a set of global resource mappings
859   // generated mainly from manifest files, on-the-fly, as they're parsed.
860   // Entries added later override entries added earlier, and no record is kept
861   // of the former state.
862   //
863   // As a result, if we remove a dynamically-added manifest file, or a set of
864   // dynamic entries, the registry needs to be rebuilt from scratch, from the
865   // manifests and dynamic entries that remain. The chrome registry itself
866   // takes care of re-parsing manifes files. This observer notification lets
867   // us know when we need to re-register our dynamic entries.
868   if (!strcmp(topic, "chrome-manifests-loaded")) {
869     for (auto entry : GetRegistryEntries()) {
870       entry->Register();
871     }
872   }
873 
874   return NS_OK;
875 }
876 
877 }  // namespace mozilla
878