1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 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/. */
7 #include "mozilla/ArrayUtils.h"
8 #include "mozilla/Printf.h"
9 #include "mozilla/UniquePtr.h"
11 #include "ManifestParser.h"
18 #elif defined(MOZ_WIDGET_COCOA)
19 # include <CoreServices/CoreServices.h>
20 # include "nsCocoaFeatures.h"
21 #elif defined(MOZ_WIDGET_GTK)
25 #ifdef MOZ_WIDGET_ANDROID
26 # include "AndroidBuild.h"
27 # include "mozilla/java/GeckoAppShellWrappers.h"
30 #ifdef MOZ_BACKGROUNDTASKS
31 # include "mozilla/BackgroundTasks.h"
34 #include "mozilla/Services.h"
37 #include "nsConsoleMessage.h"
38 #include "nsTextFormatter.h"
39 #include "nsVersionComparator.h"
40 #include "nsXPCOMCIDInternal.h"
42 #include "nsIConsoleService.h"
43 #include "nsIScriptError.h"
44 #include "nsIXULAppInfo.h"
45 #include "nsIXULRuntime.h"
47 using namespace mozilla
;
49 struct ManifestDirective
{
50 const char* directive
;
55 // The contentaccessible flags only apply to content/resource directives.
58 // Function to handle this directive. This isn't a union because C++ still
59 // hasn't learned how to initialize unions in a sane way.
60 void (nsComponentManagerImpl::*mgrfunc
)(
61 nsComponentManagerImpl::ManifestProcessingContext
& aCx
, int aLineNo
,
63 void (nsChromeRegistry::*regfunc
)(
64 nsChromeRegistry::ManifestProcessingContext
& aCx
, int aLineNo
,
65 char* const* aArgv
, int aFlags
);
67 static const ManifestDirective kParsingTable
[] = {
70 "manifest", 1, true, false,
71 &nsComponentManagerImpl::ManifestManifest
, nullptr,
74 "category", 3, false, false,
75 &nsComponentManagerImpl::ManifestCategory
, nullptr,
78 "content", 2, true, true,
79 nullptr, &nsChromeRegistry::ManifestContent
,
82 "locale", 3, true, false,
83 nullptr, &nsChromeRegistry::ManifestLocale
,
86 "skin", 3, true, false,
87 nullptr, &nsChromeRegistry::ManifestSkin
,
90 // NB: note that while skin manifests can use this, they are only allowed
91 // to use it for chrome://../skin/ URLs
92 "override", 2, true, false,
93 nullptr, &nsChromeRegistry::ManifestOverride
,
96 "resource", 2, false, true,
97 nullptr, &nsChromeRegistry::ManifestResource
,
102 static const char kWhitespace
[] = "\t ";
104 static bool IsNewline(char aChar
) { return aChar
== '\n' || aChar
== '\r'; }
106 void LogMessage(const char* aMsg
, ...) {
107 MOZ_ASSERT(nsComponentManagerImpl::gComponentManager
);
109 nsCOMPtr
<nsIConsoleService
> console
=
110 do_GetService(NS_CONSOLESERVICE_CONTRACTID
);
116 va_start(args
, aMsg
);
117 SmprintfPointer
formatted(mozilla::Vsmprintf(aMsg
, args
));
120 nsCOMPtr
<nsIConsoleMessage
> error
=
121 new nsConsoleMessage(NS_ConvertUTF8toUTF16(formatted
.get()));
122 console
->LogMessage(error
);
125 void LogMessageWithContext(FileLocation
& aFile
, uint32_t aLineNumber
,
126 const char* aMsg
, ...) {
128 va_start(args
, aMsg
);
129 SmprintfPointer
formatted(mozilla::Vsmprintf(aMsg
, args
));
135 MOZ_ASSERT(nsComponentManagerImpl::gComponentManager
);
138 aFile
.GetURIString(file
);
140 nsCOMPtr
<nsIScriptError
> error
= do_CreateInstance(NS_SCRIPTERROR_CONTRACTID
);
142 // This can happen early in component registration. Fall back to a
143 // generic console message.
144 LogMessage("Warning: in '%s', line %i: %s", file
.get(), aLineNumber
,
149 nsCOMPtr
<nsIConsoleService
> console
=
150 do_GetService(NS_CONSOLESERVICE_CONTRACTID
);
155 nsresult rv
= error
->Init(
156 NS_ConvertUTF8toUTF16(formatted
.get()), NS_ConvertUTF8toUTF16(file
),
157 u
""_ns
, aLineNumber
, 0, nsIScriptError::warningFlag
,
158 "chrome registration"_ns
, false /* from private window */,
159 true /* from chrome context */);
164 console
->LogMessage(error
);
168 * Check for a modifier flag of the following forms:
169 * "flag" (same as "true")
172 * @param aFlag The flag to compare.
173 * @param aData The tokenized data to check; this is lowercased
174 * before being passed in.
175 * @param aResult If the flag is found, the value is assigned here.
176 * @return Whether the flag was handled.
178 static bool CheckFlag(const nsAString
& aFlag
, const nsAString
& aData
,
180 if (!StringBeginsWith(aData
, aFlag
)) {
184 if (aFlag
.Length() == aData
.Length()) {
185 // the data is simply "flag", which is the same as "flag=yes"
190 if (aData
.CharAt(aFlag
.Length()) != '=') {
191 // the data is "flag2=", which is not anything we care about
195 if (aData
.Length() == aFlag
.Length() + 1) {
200 switch (aData
.CharAt(aFlag
.Length() + 1)) {
217 enum TriState
{ eUnspecified
, eBad
, eOK
};
220 * Check for a modifier flag of the following form:
223 * @param aFlag The flag to compare.
224 * @param aData The tokenized data to check; this is lowercased
225 * before being passed in.
226 * @param aValue The value that is expected.
227 * @param aResult If this is "ok" when passed in, this is left alone.
228 * Otherwise if the flag is found it is set to eBad or eOK.
229 * @return Whether the flag was handled.
231 static bool CheckStringFlag(const nsAString
& aFlag
, const nsAString
& aData
,
232 const nsAString
& aValue
, TriState
& aResult
) {
233 if (aData
.Length() < aFlag
.Length() + 1) {
237 if (!StringBeginsWith(aData
, aFlag
)) {
241 bool comparison
= true;
242 if (aData
[aFlag
.Length()] != '=') {
243 if (aData
[aFlag
.Length()] == '!' && aData
.Length() >= aFlag
.Length() + 2 &&
244 aData
[aFlag
.Length() + 1] == '=') {
251 if (aResult
!= eOK
) {
252 nsDependentSubstring testdata
=
253 Substring(aData
, aFlag
.Length() + (comparison
? 1 : 2));
254 if (testdata
.Equals(aValue
)) {
255 aResult
= comparison
? eOK
: eBad
;
257 aResult
= comparison
? eBad
: eOK
;
264 static bool CheckOsFlag(const nsAString
& aFlag
, const nsAString
& aData
,
265 const nsAString
& aValue
, TriState
& aResult
) {
266 bool result
= CheckStringFlag(aFlag
, aData
, aValue
, aResult
);
267 #if defined(XP_UNIX) && !defined(XP_DARWIN) && !defined(ANDROID)
268 if (result
&& aResult
== eBad
) {
269 result
= CheckStringFlag(aFlag
, aData
, u
"likeunix"_ns
, aResult
);
276 * Check for a modifier flag of the following form:
282 * @param aFlag The flag to compare.
283 * @param aData The tokenized data to check; this is lowercased
284 * before being passed in.
285 * @param aValue The value that is expected. If this is empty then no
286 * comparison will match.
287 * @param aResult If this is eOK when passed in, this is left alone.
288 * Otherwise if the flag is found it is set to eBad or eOK.
289 * @return Whether the flag was handled.
292 #define COMPARE_EQ 1 << 0
293 #define COMPARE_LT 1 << 1
294 #define COMPARE_GT 1 << 2
296 static bool CheckVersionFlag(const nsString
& aFlag
, const nsString
& aData
,
297 const nsString
& aValue
, TriState
& aResult
) {
298 if (aData
.Length() < aFlag
.Length() + 2) {
302 if (!StringBeginsWith(aData
, aFlag
)) {
306 if (aValue
.Length() == 0) {
307 if (aResult
!= eOK
) {
314 nsAutoString testdata
;
316 switch (aData
[aFlag
.Length()]) {
318 comparison
= COMPARE_EQ
;
319 testdata
= Substring(aData
, aFlag
.Length() + 1);
323 if (aData
[aFlag
.Length() + 1] == '=') {
324 comparison
= COMPARE_EQ
| COMPARE_LT
;
325 testdata
= Substring(aData
, aFlag
.Length() + 2);
327 comparison
= COMPARE_LT
;
328 testdata
= Substring(aData
, aFlag
.Length() + 1);
333 if (aData
[aFlag
.Length() + 1] == '=') {
334 comparison
= COMPARE_EQ
| COMPARE_GT
;
335 testdata
= Substring(aData
, aFlag
.Length() + 2);
337 comparison
= COMPARE_GT
;
338 testdata
= Substring(aData
, aFlag
.Length() + 1);
346 if (testdata
.Length() == 0) {
350 if (aResult
!= eOK
) {
351 int32_t c
= mozilla::CompareVersions(NS_ConvertUTF16toUTF8(aValue
).get(),
352 NS_ConvertUTF16toUTF8(testdata
).get());
353 if ((c
== 0 && comparison
& COMPARE_EQ
) ||
354 (c
< 0 && comparison
& COMPARE_LT
) ||
355 (c
> 0 && comparison
& COMPARE_GT
)) {
365 // In-place conversion of ascii characters to lower case
366 static void ToLowerCase(char* aToken
) {
367 for (; *aToken
; ++aToken
) {
368 *aToken
= NS_ToLower(*aToken
);
374 struct CachedDirective
{
381 void ParseManifest(NSLocationType aType
, FileLocation
& aFile
, char* aBuf
,
383 nsComponentManagerImpl::ManifestProcessingContext
mgrcx(aType
, aFile
,
385 nsChromeRegistry::ManifestProcessingContext
chromecx(aType
, aFile
);
388 constexpr auto kContentAccessible
= u
"contentaccessible"_ns
;
389 constexpr auto kRemoteEnabled
= u
"remoteenabled"_ns
;
390 constexpr auto kRemoteRequired
= u
"remoterequired"_ns
;
391 constexpr auto kApplication
= u
"application"_ns
;
392 constexpr auto kAppVersion
= u
"appversion"_ns
;
393 constexpr auto kGeckoVersion
= u
"platformversion"_ns
;
394 constexpr auto kOs
= u
"os"_ns
;
395 constexpr auto kOsVersion
= u
"osversion"_ns
;
396 constexpr auto kABI
= u
"abi"_ns
;
397 constexpr auto kProcess
= u
"process"_ns
;
398 #if defined(MOZ_WIDGET_ANDROID)
399 constexpr auto kTablet
= u
"tablet"_ns
;
401 // You might expect this to be guarded by MOZ_BACKGROUNDTASKS, but it's not
402 // possible to have conditional manifest contents, so we need to recognize and
403 // discard these tokens even when MOZ_BACKGROUNDTASKS is not set.
404 constexpr auto kBackgroundTask
= u
"backgroundtask"_ns
;
406 constexpr auto kMain
= u
"main"_ns
;
407 constexpr auto kContent
= u
"content"_ns
;
410 constexpr auto kXPCNativeWrappers
= u
"xpcnativewrappers"_ns
;
413 nsAutoString appVersion
;
414 nsAutoString geckoVersion
;
415 nsAutoString osTarget
;
417 nsAutoString process
;
419 nsCOMPtr
<nsIXULAppInfo
> xapp(do_GetService(XULAPPINFO_SERVICE_CONTRACTID
));
423 if (NS_SUCCEEDED(rv
)) {
424 CopyUTF8toUTF16(s
, appID
);
427 rv
= xapp
->GetVersion(s
);
428 if (NS_SUCCEEDED(rv
)) {
429 CopyUTF8toUTF16(s
, appVersion
);
432 rv
= xapp
->GetPlatformVersion(s
);
433 if (NS_SUCCEEDED(rv
)) {
434 CopyUTF8toUTF16(s
, geckoVersion
);
437 nsCOMPtr
<nsIXULRuntime
> xruntime(do_QueryInterface(xapp
));
439 rv
= xruntime
->GetOS(s
);
440 if (NS_SUCCEEDED(rv
)) {
442 CopyUTF8toUTF16(s
, osTarget
);
445 rv
= xruntime
->GetXPCOMABI(s
);
446 if (NS_SUCCEEDED(rv
) && osTarget
.Length()) {
448 CopyUTF8toUTF16(s
, abi
);
449 abi
.Insert(char16_t('_'), 0);
450 abi
.Insert(osTarget
, 0);
455 nsAutoString osVersion
;
457 # pragma warning(push)
458 # pragma warning(disable : 4996) // VC12+ deprecates GetVersionEx
459 OSVERSIONINFO info
= {sizeof(OSVERSIONINFO
)};
460 if (GetVersionEx(&info
)) {
461 nsTextFormatter::ssprintf(osVersion
, u
"%ld.%ld", info
.dwMajorVersion
,
462 info
.dwMinorVersion
);
464 # pragma warning(pop)
465 #elif defined(MOZ_WIDGET_COCOA)
466 SInt32 majorVersion
= nsCocoaFeatures::macOSVersionMajor();
467 SInt32 minorVersion
= nsCocoaFeatures::macOSVersionMinor();
468 nsTextFormatter::ssprintf(osVersion
, u
"%ld.%ld", majorVersion
, minorVersion
);
469 #elif defined(MOZ_WIDGET_GTK)
470 nsTextFormatter::ssprintf(osVersion
, u
"%ld.%ld", gtk_major_version
,
472 #elif defined(MOZ_WIDGET_ANDROID)
473 bool isTablet
= false;
474 if (jni::IsAvailable()) {
475 jni::String::LocalRef release
= java::sdk::Build::VERSION::RELEASE();
476 osVersion
.Assign(release
->ToString());
477 isTablet
= java::GeckoAppShell::IsTablet();
481 if (XRE_IsContentProcess()) {
488 char* newline
= aBuf
;
491 // outer loop tokenizes by newline
493 while (*newline
&& IsNewline(*newline
)) {
502 while (*newline
&& !IsNewline(*newline
)) {
512 if (*token
== '#') { // ignore lines that begin with # as comments
516 char* whitespace
= token
;
517 token
= nsCRT::strtok(whitespace
, kWhitespace
, &whitespace
);
522 const ManifestDirective
* directive
= nullptr;
523 for (const ManifestDirective
* d
= kParsingTable
;
524 d
< ArrayEnd(kParsingTable
); ++d
) {
525 if (!strcmp(d
->directive
, token
)) {
532 LogMessageWithContext(
533 aFile
, line
, "Ignoring unrecognized chrome manifest directive '%s'.",
538 if (!directive
->ischrome
&& NS_BOOTSTRAPPED_LOCATION
== aType
) {
539 LogMessageWithContext(
541 "Bootstrapped manifest not allowed to use '%s' directive.", token
);
545 NS_ASSERTION(directive
->argc
< 4, "Need to reset argv array length");
547 for (int i
= 0; i
< directive
->argc
; ++i
) {
548 argv
[i
] = nsCRT::strtok(whitespace
, kWhitespace
, &whitespace
);
551 if (!argv
[directive
->argc
- 1]) {
552 LogMessageWithContext(aFile
, line
,
553 "Not enough arguments for chrome manifest "
554 "directive '%s', expected %i.",
555 token
, directive
->argc
);
560 TriState stAppVersion
= eUnspecified
;
561 TriState stGeckoVersion
= eUnspecified
;
562 TriState stApp
= eUnspecified
;
563 TriState stOsVersion
= eUnspecified
;
564 TriState stOs
= eUnspecified
;
565 TriState stABI
= eUnspecified
;
566 TriState stProcess
= eUnspecified
;
567 #if defined(MOZ_WIDGET_ANDROID)
568 TriState stTablet
= eUnspecified
;
570 #ifdef MOZ_BACKGROUNDTASKS
571 // When in background task mode, default to not registering
572 // category directivies unless backgroundtask=1 is specified.
573 TriState stBackgroundTask
= (BackgroundTasks::IsBackgroundTaskMode() &&
574 strcmp("category", directive
->directive
) == 0)
580 while ((token
= nsCRT::strtok(whitespace
, kWhitespace
, &whitespace
)) &&
583 NS_ConvertASCIItoUTF16
wtoken(token
);
585 if (CheckStringFlag(kApplication
, wtoken
, appID
, stApp
) ||
586 CheckOsFlag(kOs
, wtoken
, osTarget
, stOs
) ||
587 CheckStringFlag(kABI
, wtoken
, abi
, stABI
) ||
588 CheckStringFlag(kProcess
, wtoken
, process
, stProcess
) ||
589 CheckVersionFlag(kOsVersion
, wtoken
, osVersion
, stOsVersion
) ||
590 CheckVersionFlag(kAppVersion
, wtoken
, appVersion
, stAppVersion
) ||
591 CheckVersionFlag(kGeckoVersion
, wtoken
, geckoVersion
,
596 #if defined(MOZ_WIDGET_ANDROID)
598 if (CheckFlag(kTablet
, wtoken
, tablet
)) {
599 stTablet
= (tablet
== isTablet
) ? eOK
: eBad
;
604 // You might expect this to be guarded by MOZ_BACKGROUNDTASKS, it's not
605 // possible to have conditional manifest contents.
607 if (CheckFlag(kBackgroundTask
, wtoken
, flag
)) {
608 #if defined(MOZ_BACKGROUNDTASKS)
609 // Background task mode is active: filter.
611 (flag
== BackgroundTasks::IsBackgroundTaskMode()) ? eOK
: eBad
;
612 #endif /* defined(MOZ_BACKGROUNDTASKS) */
616 if (directive
->contentflags
) {
618 if (CheckFlag(kContentAccessible
, wtoken
, flag
)) {
619 if (flag
) flags
|= nsChromeRegistry::CONTENT_ACCESSIBLE
;
622 if (CheckFlag(kRemoteEnabled
, wtoken
, flag
)) {
623 if (flag
) flags
|= nsChromeRegistry::REMOTE_ALLOWED
;
626 if (CheckFlag(kRemoteRequired
, wtoken
, flag
)) {
627 if (flag
) flags
|= nsChromeRegistry::REMOTE_REQUIRED
;
632 bool xpcNativeWrappers
= true; // Dummy for CheckFlag.
633 if (CheckFlag(kXPCNativeWrappers
, wtoken
, xpcNativeWrappers
)) {
634 LogMessageWithContext(
635 aFile
, line
, "Ignoring obsolete chrome registration modifier '%s'.",
640 LogMessageWithContext(
641 aFile
, line
, "Unrecognized chrome manifest modifier '%s'.", token
);
645 if (!ok
|| stApp
== eBad
|| stAppVersion
== eBad
||
646 stGeckoVersion
== eBad
|| stOs
== eBad
|| stOsVersion
== eBad
||
647 #ifdef MOZ_WIDGET_ANDROID
650 #ifdef MOZ_BACKGROUNDTASKS
651 stBackgroundTask
== eBad
||
653 stABI
== eBad
|| stProcess
== eBad
) {
657 if (directive
->regfunc
) {
658 if (GeckoProcessType_Default
!= XRE_GetProcessType()) {
662 if (!nsChromeRegistry::gChromeRegistry
) {
663 nsCOMPtr
<nsIChromeRegistry
> cr
= mozilla::services::GetChromeRegistry();
664 if (!nsChromeRegistry::gChromeRegistry
) {
665 LogMessageWithContext(aFile
, line
,
666 "Chrome registry isn't available yet.");
671 (nsChromeRegistry::gChromeRegistry
->*(directive
->regfunc
))(chromecx
, line
,
673 } else if (directive
->ischrome
|| !aChromeOnly
) {
674 (nsComponentManagerImpl::gComponentManager
->*(directive
->mgrfunc
))(