Bug 1826564 [wpt PR 39394] - Update mypy, a=testonly
[gecko.git] / xpcom / components / ManifestParser.cpp
blob88ee06d78db60a84343fd3d23c16e163aead37c3
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"
13 #include <string.h>
15 #include "prio.h"
16 #if defined(XP_WIN)
17 # include <windows.h>
18 #elif defined(MOZ_WIDGET_COCOA)
19 # include <CoreServices/CoreServices.h>
20 # include "nsCocoaFeatures.h"
21 #elif defined(MOZ_WIDGET_GTK)
22 # include <gtk/gtk.h>
23 #endif
25 #ifdef MOZ_WIDGET_ANDROID
26 # include "AndroidBuild.h"
27 # include "mozilla/java/GeckoAppShellWrappers.h"
28 #endif
30 #ifdef MOZ_BACKGROUNDTASKS
31 # include "mozilla/BackgroundTasks.h"
32 #endif
34 #include "mozilla/Services.h"
36 #include "nsCRT.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;
51 int argc;
53 bool ischrome;
55 // The contentaccessible flags only apply to content/resource directives.
56 bool contentflags;
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,
62 char* const* aArgv);
63 void (nsChromeRegistry::*regfunc)(
64 nsChromeRegistry::ManifestProcessingContext& aCx, int aLineNo,
65 char* const* aArgv, int aFlags);
67 static const ManifestDirective kParsingTable[] = {
68 // clang-format off
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,
99 // clang-format on
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);
111 if (!console) {
112 return;
115 va_list args;
116 va_start(args, aMsg);
117 SmprintfPointer formatted(mozilla::Vsmprintf(aMsg, args));
118 va_end(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, ...) {
127 va_list args;
128 va_start(args, aMsg);
129 SmprintfPointer formatted(mozilla::Vsmprintf(aMsg, args));
130 va_end(args);
131 if (!formatted) {
132 return;
135 MOZ_ASSERT(nsComponentManagerImpl::gComponentManager);
137 nsCString file;
138 aFile.GetURIString(file);
140 nsCOMPtr<nsIScriptError> error = do_CreateInstance(NS_SCRIPTERROR_CONTRACTID);
141 if (!error) {
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,
145 formatted.get());
146 return;
149 nsCOMPtr<nsIConsoleService> console =
150 do_GetService(NS_CONSOLESERVICE_CONTRACTID);
151 if (!console) {
152 return;
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 */);
160 if (NS_FAILED(rv)) {
161 return;
164 console->LogMessage(error);
168 * Check for a modifier flag of the following forms:
169 * "flag" (same as "true")
170 * "flag=yes|true|1"
171 * "flag="no|false|0"
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,
179 bool& aResult) {
180 if (!StringBeginsWith(aData, aFlag)) {
181 return false;
184 if (aFlag.Length() == aData.Length()) {
185 // the data is simply "flag", which is the same as "flag=yes"
186 aResult = true;
187 return true;
190 if (aData.CharAt(aFlag.Length()) != '=') {
191 // the data is "flag2=", which is not anything we care about
192 return false;
195 if (aData.Length() == aFlag.Length() + 1) {
196 aResult = false;
197 return true;
200 switch (aData.CharAt(aFlag.Length() + 1)) {
201 case '1':
202 case 't': // true
203 case 'y': // yes
204 aResult = true;
205 return true;
207 case '0':
208 case 'f': // false
209 case 'n': // no
210 aResult = false;
211 return true;
214 return false;
217 enum TriState { eUnspecified, eBad, eOK };
220 * Check for a modifier flag of the following form:
221 * "flag=string"
222 * "flag!=string"
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) {
234 return false;
237 if (!StringBeginsWith(aData, aFlag)) {
238 return false;
241 bool comparison = true;
242 if (aData[aFlag.Length()] != '=') {
243 if (aData[aFlag.Length()] == '!' && aData.Length() >= aFlag.Length() + 2 &&
244 aData[aFlag.Length() + 1] == '=') {
245 comparison = false;
246 } else {
247 return false;
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;
256 } else {
257 aResult = comparison ? eBad : eOK;
261 return true;
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);
271 #endif
272 return result;
276 * Check for a modifier flag of the following form:
277 * "flag=version"
278 * "flag<=version"
279 * "flag<version"
280 * "flag>=version"
281 * "flag>version"
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) {
299 return false;
302 if (!StringBeginsWith(aData, aFlag)) {
303 return false;
306 if (aValue.Length() == 0) {
307 if (aResult != eOK) {
308 aResult = eBad;
310 return true;
313 uint32_t comparison;
314 nsAutoString testdata;
316 switch (aData[aFlag.Length()]) {
317 case '=':
318 comparison = COMPARE_EQ;
319 testdata = Substring(aData, aFlag.Length() + 1);
320 break;
322 case '<':
323 if (aData[aFlag.Length() + 1] == '=') {
324 comparison = COMPARE_EQ | COMPARE_LT;
325 testdata = Substring(aData, aFlag.Length() + 2);
326 } else {
327 comparison = COMPARE_LT;
328 testdata = Substring(aData, aFlag.Length() + 1);
330 break;
332 case '>':
333 if (aData[aFlag.Length() + 1] == '=') {
334 comparison = COMPARE_EQ | COMPARE_GT;
335 testdata = Substring(aData, aFlag.Length() + 2);
336 } else {
337 comparison = COMPARE_GT;
338 testdata = Substring(aData, aFlag.Length() + 1);
340 break;
342 default:
343 return false;
346 if (testdata.Length() == 0) {
347 return false;
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)) {
356 aResult = eOK;
357 } else {
358 aResult = eBad;
362 return true;
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);
372 namespace {
374 struct CachedDirective {
375 int lineno;
376 char* argv[4];
379 } // namespace
381 void ParseManifest(NSLocationType aType, FileLocation& aFile, char* aBuf,
382 bool aChromeOnly) {
383 nsComponentManagerImpl::ManifestProcessingContext mgrcx(aType, aFile,
384 aChromeOnly);
385 nsChromeRegistry::ManifestProcessingContext chromecx(aType, aFile);
386 nsresult rv;
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;
400 #endif
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;
409 // Obsolete
410 constexpr auto kXPCNativeWrappers = u"xpcnativewrappers"_ns;
412 nsAutoString appID;
413 nsAutoString appVersion;
414 nsAutoString geckoVersion;
415 nsAutoString osTarget;
416 nsAutoString abi;
417 nsAutoString process;
419 nsCOMPtr<nsIXULAppInfo> xapp(do_GetService(XULAPPINFO_SERVICE_CONTRACTID));
420 if (xapp) {
421 nsAutoCString s;
422 rv = xapp->GetID(s);
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));
438 if (xruntime) {
439 rv = xruntime->GetOS(s);
440 if (NS_SUCCEEDED(rv)) {
441 ToLowerCase(s);
442 CopyUTF8toUTF16(s, osTarget);
445 rv = xruntime->GetXPCOMABI(s);
446 if (NS_SUCCEEDED(rv) && osTarget.Length()) {
447 ToLowerCase(s);
448 CopyUTF8toUTF16(s, abi);
449 abi.Insert(char16_t('_'), 0);
450 abi.Insert(osTarget, 0);
455 nsAutoString osVersion;
456 #if defined(XP_WIN)
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,
471 gtk_minor_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();
479 #endif
481 if (XRE_IsContentProcess()) {
482 process = kContent;
483 } else {
484 process = kMain;
487 char* token;
488 char* newline = aBuf;
489 uint32_t line = 0;
491 // outer loop tokenizes by newline
492 while (*newline) {
493 while (*newline && IsNewline(*newline)) {
494 ++newline;
495 ++line;
497 if (!*newline) {
498 break;
501 token = newline;
502 while (*newline && !IsNewline(*newline)) {
503 ++newline;
506 if (*newline) {
507 *newline = '\0';
508 ++newline;
510 ++line;
512 if (*token == '#') { // ignore lines that begin with # as comments
513 continue;
516 char* whitespace = token;
517 token = nsCRT::strtok(whitespace, kWhitespace, &whitespace);
518 if (!token) {
519 continue;
522 const ManifestDirective* directive = nullptr;
523 for (const ManifestDirective* d = kParsingTable;
524 d < ArrayEnd(kParsingTable); ++d) {
525 if (!strcmp(d->directive, token)) {
526 directive = d;
527 break;
531 if (!directive) {
532 LogMessageWithContext(
533 aFile, line, "Ignoring unrecognized chrome manifest directive '%s'.",
534 token);
535 continue;
538 if (!directive->ischrome && NS_BOOTSTRAPPED_LOCATION == aType) {
539 LogMessageWithContext(
540 aFile, line,
541 "Bootstrapped manifest not allowed to use '%s' directive.", token);
542 continue;
545 NS_ASSERTION(directive->argc < 4, "Need to reset argv array length");
546 char* argv[4];
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);
556 continue;
559 bool ok = true;
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;
569 #endif
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)
575 ? eBad
576 : eUnspecified;
577 #endif
578 int flags = 0;
580 while ((token = nsCRT::strtok(whitespace, kWhitespace, &whitespace)) &&
581 ok) {
582 ToLowerCase(token);
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,
592 stGeckoVersion)) {
593 continue;
596 #if defined(MOZ_WIDGET_ANDROID)
597 bool tablet = false;
598 if (CheckFlag(kTablet, wtoken, tablet)) {
599 stTablet = (tablet == isTablet) ? eOK : eBad;
600 continue;
602 #endif
604 // You might expect this to be guarded by MOZ_BACKGROUNDTASKS, it's not
605 // possible to have conditional manifest contents.
606 bool flag;
607 if (CheckFlag(kBackgroundTask, wtoken, flag)) {
608 #if defined(MOZ_BACKGROUNDTASKS)
609 // Background task mode is active: filter.
610 stBackgroundTask =
611 (flag == BackgroundTasks::IsBackgroundTaskMode()) ? eOK : eBad;
612 #endif /* defined(MOZ_BACKGROUNDTASKS) */
613 continue;
616 if (directive->contentflags) {
617 bool flag;
618 if (CheckFlag(kContentAccessible, wtoken, flag)) {
619 if (flag) flags |= nsChromeRegistry::CONTENT_ACCESSIBLE;
620 continue;
622 if (CheckFlag(kRemoteEnabled, wtoken, flag)) {
623 if (flag) flags |= nsChromeRegistry::REMOTE_ALLOWED;
624 continue;
626 if (CheckFlag(kRemoteRequired, wtoken, flag)) {
627 if (flag) flags |= nsChromeRegistry::REMOTE_REQUIRED;
628 continue;
632 bool xpcNativeWrappers = true; // Dummy for CheckFlag.
633 if (CheckFlag(kXPCNativeWrappers, wtoken, xpcNativeWrappers)) {
634 LogMessageWithContext(
635 aFile, line, "Ignoring obsolete chrome registration modifier '%s'.",
636 token);
637 continue;
640 LogMessageWithContext(
641 aFile, line, "Unrecognized chrome manifest modifier '%s'.", token);
642 ok = false;
645 if (!ok || stApp == eBad || stAppVersion == eBad ||
646 stGeckoVersion == eBad || stOs == eBad || stOsVersion == eBad ||
647 #ifdef MOZ_WIDGET_ANDROID
648 stTablet == eBad ||
649 #endif
650 #ifdef MOZ_BACKGROUNDTASKS
651 stBackgroundTask == eBad ||
652 #endif
653 stABI == eBad || stProcess == eBad) {
654 continue;
657 if (directive->regfunc) {
658 if (GeckoProcessType_Default != XRE_GetProcessType()) {
659 continue;
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.");
667 continue;
671 (nsChromeRegistry::gChromeRegistry->*(directive->regfunc))(chromecx, line,
672 argv, flags);
673 } else if (directive->ischrome || !aChromeOnly) {
674 (nsComponentManagerImpl::gComponentManager->*(directive->mgrfunc))(
675 mgrcx, line, argv);