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()), file
, aLineNumber
, 0,
157 nsIScriptError::warningFlag
, "chrome registration"_ns
,
158 false /* from private window */, true /* from chrome context */);
163 console
->LogMessage(error
);
167 * Check for a modifier flag of the following forms:
168 * "flag" (same as "true")
171 * @param aFlag The flag to compare.
172 * @param aData The tokenized data to check; this is lowercased
173 * before being passed in.
174 * @param aResult If the flag is found, the value is assigned here.
175 * @return Whether the flag was handled.
177 static bool CheckFlag(const nsAString
& aFlag
, const nsAString
& aData
,
179 if (!StringBeginsWith(aData
, aFlag
)) {
183 if (aFlag
.Length() == aData
.Length()) {
184 // the data is simply "flag", which is the same as "flag=yes"
189 if (aData
.CharAt(aFlag
.Length()) != '=') {
190 // the data is "flag2=", which is not anything we care about
194 if (aData
.Length() == aFlag
.Length() + 1) {
199 switch (aData
.CharAt(aFlag
.Length() + 1)) {
216 enum TriState
{ eUnspecified
, eBad
, eOK
};
219 * Check for a modifier flag of the following form:
222 * @param aFlag The flag to compare.
223 * @param aData The tokenized data to check; this is lowercased
224 * before being passed in.
225 * @param aValue The value that is expected.
226 * @param aResult If this is "ok" when passed in, this is left alone.
227 * Otherwise if the flag is found it is set to eBad or eOK.
228 * @return Whether the flag was handled.
230 static bool CheckStringFlag(const nsAString
& aFlag
, const nsAString
& aData
,
231 const nsAString
& aValue
, TriState
& aResult
) {
232 if (aData
.Length() < aFlag
.Length() + 1) {
236 if (!StringBeginsWith(aData
, aFlag
)) {
240 bool comparison
= true;
241 if (aData
[aFlag
.Length()] != '=') {
242 if (aData
[aFlag
.Length()] == '!' && aData
.Length() >= aFlag
.Length() + 2 &&
243 aData
[aFlag
.Length() + 1] == '=') {
250 if (aResult
!= eOK
) {
251 nsDependentSubstring testdata
=
252 Substring(aData
, aFlag
.Length() + (comparison
? 1 : 2));
253 if (testdata
.Equals(aValue
)) {
254 aResult
= comparison
? eOK
: eBad
;
256 aResult
= comparison
? eBad
: eOK
;
263 static bool CheckOsFlag(const nsAString
& aFlag
, const nsAString
& aData
,
264 const nsAString
& aValue
, TriState
& aResult
) {
265 bool result
= CheckStringFlag(aFlag
, aData
, aValue
, aResult
);
266 #if defined(XP_UNIX) && !defined(XP_DARWIN) && !defined(ANDROID)
267 if (result
&& aResult
== eBad
) {
268 result
= CheckStringFlag(aFlag
, aData
, u
"likeunix"_ns
, aResult
);
275 * Check for a modifier flag of the following form:
281 * @param aFlag The flag to compare.
282 * @param aData The tokenized data to check; this is lowercased
283 * before being passed in.
284 * @param aValue The value that is expected. If this is empty then no
285 * comparison will match.
286 * @param aResult If this is eOK when passed in, this is left alone.
287 * Otherwise if the flag is found it is set to eBad or eOK.
288 * @return Whether the flag was handled.
291 #define COMPARE_EQ 1 << 0
292 #define COMPARE_LT 1 << 1
293 #define COMPARE_GT 1 << 2
295 static bool CheckVersionFlag(const nsString
& aFlag
, const nsString
& aData
,
296 const nsString
& aValue
, TriState
& aResult
) {
297 if (aData
.Length() < aFlag
.Length() + 2) {
301 if (!StringBeginsWith(aData
, aFlag
)) {
305 if (aValue
.Length() == 0) {
306 if (aResult
!= eOK
) {
313 nsAutoString testdata
;
315 switch (aData
[aFlag
.Length()]) {
317 comparison
= COMPARE_EQ
;
318 testdata
= Substring(aData
, aFlag
.Length() + 1);
322 if (aData
[aFlag
.Length() + 1] == '=') {
323 comparison
= COMPARE_EQ
| COMPARE_LT
;
324 testdata
= Substring(aData
, aFlag
.Length() + 2);
326 comparison
= COMPARE_LT
;
327 testdata
= Substring(aData
, aFlag
.Length() + 1);
332 if (aData
[aFlag
.Length() + 1] == '=') {
333 comparison
= COMPARE_EQ
| COMPARE_GT
;
334 testdata
= Substring(aData
, aFlag
.Length() + 2);
336 comparison
= COMPARE_GT
;
337 testdata
= Substring(aData
, aFlag
.Length() + 1);
345 if (testdata
.Length() == 0) {
349 if (aResult
!= eOK
) {
350 int32_t c
= mozilla::CompareVersions(NS_ConvertUTF16toUTF8(aValue
).get(),
351 NS_ConvertUTF16toUTF8(testdata
).get());
352 if ((c
== 0 && comparison
& COMPARE_EQ
) ||
353 (c
< 0 && comparison
& COMPARE_LT
) ||
354 (c
> 0 && comparison
& COMPARE_GT
)) {
364 // In-place conversion of ascii characters to lower case
365 static void ToLowerCase(char* aToken
) {
366 for (; *aToken
; ++aToken
) {
367 *aToken
= NS_ToLower(*aToken
);
373 struct CachedDirective
{
380 void ParseManifest(NSLocationType aType
, FileLocation
& aFile
, char* aBuf
,
382 nsComponentManagerImpl::ManifestProcessingContext
mgrcx(aType
, aFile
,
384 nsChromeRegistry::ManifestProcessingContext
chromecx(aType
, aFile
);
387 constexpr auto kContentAccessible
= u
"contentaccessible"_ns
;
388 constexpr auto kRemoteEnabled
= u
"remoteenabled"_ns
;
389 constexpr auto kRemoteRequired
= u
"remoterequired"_ns
;
390 constexpr auto kApplication
= u
"application"_ns
;
391 constexpr auto kAppVersion
= u
"appversion"_ns
;
392 constexpr auto kGeckoVersion
= u
"platformversion"_ns
;
393 constexpr auto kOs
= u
"os"_ns
;
394 constexpr auto kOsVersion
= u
"osversion"_ns
;
395 constexpr auto kABI
= u
"abi"_ns
;
396 constexpr auto kProcess
= u
"process"_ns
;
397 #if defined(MOZ_WIDGET_ANDROID)
398 constexpr auto kTablet
= u
"tablet"_ns
;
400 // You might expect this to be guarded by MOZ_BACKGROUNDTASKS, but it's not
401 // possible to have conditional manifest contents, so we need to recognize and
402 // discard these tokens even when MOZ_BACKGROUNDTASKS is not set.
403 constexpr auto kBackgroundTask
= u
"backgroundtask"_ns
;
405 constexpr auto kMain
= u
"main"_ns
;
406 constexpr auto kContent
= u
"content"_ns
;
409 constexpr auto kXPCNativeWrappers
= u
"xpcnativewrappers"_ns
;
412 nsAutoString appVersion
;
413 nsAutoString geckoVersion
;
414 nsAutoString osTarget
;
416 nsAutoString process
;
418 nsCOMPtr
<nsIXULAppInfo
> xapp(do_GetService(XULAPPINFO_SERVICE_CONTRACTID
));
422 if (NS_SUCCEEDED(rv
)) {
423 CopyUTF8toUTF16(s
, appID
);
426 rv
= xapp
->GetVersion(s
);
427 if (NS_SUCCEEDED(rv
)) {
428 CopyUTF8toUTF16(s
, appVersion
);
431 rv
= xapp
->GetPlatformVersion(s
);
432 if (NS_SUCCEEDED(rv
)) {
433 CopyUTF8toUTF16(s
, geckoVersion
);
436 nsCOMPtr
<nsIXULRuntime
> xruntime(do_QueryInterface(xapp
));
438 rv
= xruntime
->GetOS(s
);
439 if (NS_SUCCEEDED(rv
)) {
441 CopyUTF8toUTF16(s
, osTarget
);
444 rv
= xruntime
->GetXPCOMABI(s
);
445 if (NS_SUCCEEDED(rv
) && osTarget
.Length()) {
447 CopyUTF8toUTF16(s
, abi
);
448 abi
.Insert(char16_t('_'), 0);
449 abi
.Insert(osTarget
, 0);
454 nsAutoString osVersion
;
456 # pragma warning(push)
457 # pragma warning(disable : 4996) // VC12+ deprecates GetVersionEx
458 OSVERSIONINFO info
= {sizeof(OSVERSIONINFO
)};
459 if (GetVersionEx(&info
)) {
460 nsTextFormatter::ssprintf(osVersion
, u
"%ld.%ld", info
.dwMajorVersion
,
461 info
.dwMinorVersion
);
463 # pragma warning(pop)
464 #elif defined(MOZ_WIDGET_COCOA)
465 SInt32 majorVersion
= nsCocoaFeatures::macOSVersionMajor();
466 SInt32 minorVersion
= nsCocoaFeatures::macOSVersionMinor();
467 nsTextFormatter::ssprintf(osVersion
, u
"%ld.%ld", majorVersion
, minorVersion
);
468 #elif defined(MOZ_WIDGET_GTK)
469 nsTextFormatter::ssprintf(osVersion
, u
"%ld.%ld", gtk_major_version
,
471 #elif defined(MOZ_WIDGET_ANDROID)
472 bool isTablet
= false;
473 if (jni::IsAvailable()) {
474 jni::String::LocalRef release
= java::sdk::Build::VERSION::RELEASE();
475 osVersion
.Assign(release
->ToString());
476 isTablet
= java::GeckoAppShell::IsTablet();
480 if (XRE_IsContentProcess()) {
487 char* newline
= aBuf
;
490 // outer loop tokenizes by newline
492 while (*newline
&& IsNewline(*newline
)) {
501 while (*newline
&& !IsNewline(*newline
)) {
511 if (*token
== '#') { // ignore lines that begin with # as comments
515 char* whitespace
= token
;
516 token
= nsCRT::strtok(whitespace
, kWhitespace
, &whitespace
);
521 const ManifestDirective
* directive
= nullptr;
522 for (const ManifestDirective
* d
= kParsingTable
;
523 d
< std::end(kParsingTable
); ++d
) {
524 if (!strcmp(d
->directive
, token
)) {
531 LogMessageWithContext(
532 aFile
, line
, "Ignoring unrecognized chrome manifest directive '%s'.",
537 if (!directive
->ischrome
&& NS_BOOTSTRAPPED_LOCATION
== aType
) {
538 LogMessageWithContext(
540 "Bootstrapped manifest not allowed to use '%s' directive.", token
);
544 NS_ASSERTION(directive
->argc
< 4, "Need to reset argv array length");
546 for (int i
= 0; i
< directive
->argc
; ++i
) {
547 argv
[i
] = nsCRT::strtok(whitespace
, kWhitespace
, &whitespace
);
550 if (!argv
[directive
->argc
- 1]) {
551 LogMessageWithContext(aFile
, line
,
552 "Not enough arguments for chrome manifest "
553 "directive '%s', expected %i.",
554 token
, directive
->argc
);
559 TriState stAppVersion
= eUnspecified
;
560 TriState stGeckoVersion
= eUnspecified
;
561 TriState stApp
= eUnspecified
;
562 TriState stOsVersion
= eUnspecified
;
563 TriState stOs
= eUnspecified
;
564 TriState stABI
= eUnspecified
;
565 TriState stProcess
= eUnspecified
;
566 #if defined(MOZ_WIDGET_ANDROID)
567 TriState stTablet
= eUnspecified
;
569 #ifdef MOZ_BACKGROUNDTASKS
570 // When in background task mode, default to not registering
571 // category directivies unless backgroundtask=1 is specified.
572 TriState stBackgroundTask
= (BackgroundTasks::IsBackgroundTaskMode() &&
573 strcmp("category", directive
->directive
) == 0)
579 while ((token
= nsCRT::strtok(whitespace
, kWhitespace
, &whitespace
)) &&
582 NS_ConvertASCIItoUTF16
wtoken(token
);
584 if (CheckStringFlag(kApplication
, wtoken
, appID
, stApp
) ||
585 CheckOsFlag(kOs
, wtoken
, osTarget
, stOs
) ||
586 CheckStringFlag(kABI
, wtoken
, abi
, stABI
) ||
587 CheckStringFlag(kProcess
, wtoken
, process
, stProcess
) ||
588 CheckVersionFlag(kOsVersion
, wtoken
, osVersion
, stOsVersion
) ||
589 CheckVersionFlag(kAppVersion
, wtoken
, appVersion
, stAppVersion
) ||
590 CheckVersionFlag(kGeckoVersion
, wtoken
, geckoVersion
,
595 #if defined(MOZ_WIDGET_ANDROID)
597 if (CheckFlag(kTablet
, wtoken
, tablet
)) {
598 stTablet
= (tablet
== isTablet
) ? eOK
: eBad
;
603 // You might expect this to be guarded by MOZ_BACKGROUNDTASKS, it's not
604 // possible to have conditional manifest contents.
606 if (CheckFlag(kBackgroundTask
, wtoken
, flag
)) {
607 #if defined(MOZ_BACKGROUNDTASKS)
608 // Background task mode is active: filter.
610 (flag
== BackgroundTasks::IsBackgroundTaskMode()) ? eOK
: eBad
;
611 #endif /* defined(MOZ_BACKGROUNDTASKS) */
615 if (directive
->contentflags
) {
617 if (CheckFlag(kContentAccessible
, wtoken
, flag
)) {
618 if (flag
) flags
|= nsChromeRegistry::CONTENT_ACCESSIBLE
;
621 if (CheckFlag(kRemoteEnabled
, wtoken
, flag
)) {
622 if (flag
) flags
|= nsChromeRegistry::REMOTE_ALLOWED
;
625 if (CheckFlag(kRemoteRequired
, wtoken
, flag
)) {
626 if (flag
) flags
|= nsChromeRegistry::REMOTE_REQUIRED
;
631 bool xpcNativeWrappers
= true; // Dummy for CheckFlag.
632 if (CheckFlag(kXPCNativeWrappers
, wtoken
, xpcNativeWrappers
)) {
633 LogMessageWithContext(
634 aFile
, line
, "Ignoring obsolete chrome registration modifier '%s'.",
639 LogMessageWithContext(
640 aFile
, line
, "Unrecognized chrome manifest modifier '%s'.", token
);
644 if (!ok
|| stApp
== eBad
|| stAppVersion
== eBad
||
645 stGeckoVersion
== eBad
|| stOs
== eBad
|| stOsVersion
== eBad
||
646 #ifdef MOZ_WIDGET_ANDROID
649 #ifdef MOZ_BACKGROUNDTASKS
650 stBackgroundTask
== eBad
||
652 stABI
== eBad
|| stProcess
== eBad
) {
656 if (directive
->regfunc
) {
657 if (GeckoProcessType_Default
!= XRE_GetProcessType()) {
661 if (!nsChromeRegistry::gChromeRegistry
) {
662 nsCOMPtr
<nsIChromeRegistry
> cr
= mozilla::services::GetChromeRegistry();
663 if (!nsChromeRegistry::gChromeRegistry
) {
664 LogMessageWithContext(aFile
, line
,
665 "Chrome registry isn't available yet.");
670 (nsChromeRegistry::gChromeRegistry
->*(directive
->regfunc
))(chromecx
, line
,
672 } else if (directive
->ischrome
|| !aChromeOnly
) {
673 (nsComponentManagerImpl::gComponentManager
->*(directive
->mgrfunc
))(