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