xpcom/components/ManifestParser.cpp

branch
TOR_BUG_9701
changeset 15
b8a032363ba2
equal deleted inserted replaced
-1:000000000000 0:9b0549ff89c8
1 /* -*- Mode: C++; tab-width: 8; 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 "mozilla/ArrayUtils.h"
7
8 #include "ManifestParser.h"
9
10 #include <string.h>
11
12 #include "prio.h"
13 #include "prprf.h"
14 #if defined(XP_WIN)
15 #include <windows.h>
16 #elif defined(MOZ_WIDGET_COCOA)
17 #include <CoreServices/CoreServices.h>
18 #include "nsCocoaFeatures.h"
19 #elif defined(MOZ_WIDGET_GTK)
20 #include <gtk/gtk.h>
21 #endif
22
23 #ifdef MOZ_WIDGET_ANDROID
24 #include "AndroidBridge.h"
25 #endif
26
27 #include "mozilla/Services.h"
28
29 #include "nsCRT.h"
30 #include "nsConsoleMessage.h"
31 #include "nsTextFormatter.h"
32 #include "nsVersionComparator.h"
33 #include "nsXPCOMCIDInternal.h"
34
35 #include "nsIConsoleService.h"
36 #include "nsIScriptError.h"
37 #include "nsIXULAppInfo.h"
38 #include "nsIXULRuntime.h"
39
40 using namespace mozilla;
41
42 struct ManifestDirective
43 {
44 const char* directive;
45 int argc;
46
47 // Some directives should only be delivered for NS_COMPONENT_LOCATION
48 // manifests.
49 bool componentonly;
50
51 bool ischrome;
52
53 bool allowbootstrap;
54
55 // The platform/contentaccessible flags only apply to content directives.
56 bool contentflags;
57
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& cx,
62 int lineno, char *const * argv);
63 void (nsChromeRegistry::*regfunc)
64 (nsChromeRegistry::ManifestProcessingContext& cx,
65 int lineno, char *const *argv,
66 bool platform, bool contentaccessible);
67
68 bool isContract;
69 };
70 static const ManifestDirective kParsingTable[] = {
71 { "manifest", 1, false, true, true, false,
72 &nsComponentManagerImpl::ManifestManifest, nullptr },
73 { "binary-component", 1, true, false, false, false,
74 &nsComponentManagerImpl::ManifestBinaryComponent, nullptr },
75 { "interfaces", 1, true, false, false, false,
76 &nsComponentManagerImpl::ManifestXPT, nullptr },
77 { "component", 2, true, false, false, false,
78 &nsComponentManagerImpl::ManifestComponent, nullptr },
79 { "contract", 2, true, false, false, false,
80 &nsComponentManagerImpl::ManifestContract, nullptr, true},
81 { "category", 3, true, false, false, false,
82 &nsComponentManagerImpl::ManifestCategory, nullptr },
83 { "content", 2, true, true, true, true,
84 nullptr, &nsChromeRegistry::ManifestContent },
85 { "locale", 3, true, true, true, false,
86 nullptr, &nsChromeRegistry::ManifestLocale },
87 { "skin", 3, false, true, true, false,
88 nullptr, &nsChromeRegistry::ManifestSkin },
89 { "overlay", 2, true, true, false, false,
90 nullptr, &nsChromeRegistry::ManifestOverlay },
91 { "style", 2, false, true, false, false,
92 nullptr, &nsChromeRegistry::ManifestStyle },
93 { "override", 2, true, true, true, false,
94 nullptr, &nsChromeRegistry::ManifestOverride },
95 { "resource", 2, true, true, false, false,
96 nullptr, &nsChromeRegistry::ManifestResource }
97 };
98
99 static const char kWhitespace[] = "\t ";
100
101 static bool IsNewline(char c)
102 {
103 return c == '\n' || c == '\r';
104 }
105
106 namespace {
107 struct AutoPR_smprintf_free
108 {
109 AutoPR_smprintf_free(char* buf)
110 : mBuf(buf)
111 {
112 }
113
114 ~AutoPR_smprintf_free()
115 {
116 if (mBuf)
117 PR_smprintf_free(mBuf);
118 }
119
120 operator char*() const {
121 return mBuf;
122 }
123
124 char* mBuf;
125 };
126
127 } // anonymous namespace
128
129 void LogMessage(const char* aMsg, ...)
130 {
131 nsCOMPtr<nsIConsoleService> console =
132 do_GetService(NS_CONSOLESERVICE_CONTRACTID);
133 if (!console)
134 return;
135
136 va_list args;
137 va_start(args, aMsg);
138 AutoPR_smprintf_free formatted(PR_vsmprintf(aMsg, args));
139 va_end(args);
140
141 nsCOMPtr<nsIConsoleMessage> error =
142 new nsConsoleMessage(NS_ConvertUTF8toUTF16(formatted).get());
143 console->LogMessage(error);
144 }
145
146 void LogMessageWithContext(FileLocation &aFile,
147 uint32_t aLineNumber, const char* aMsg, ...)
148 {
149 va_list args;
150 va_start(args, aMsg);
151 AutoPR_smprintf_free formatted(PR_vsmprintf(aMsg, args));
152 va_end(args);
153 if (!formatted)
154 return;
155
156 nsCString file;
157 aFile.GetURIString(file);
158
159 nsCOMPtr<nsIScriptError> error =
160 do_CreateInstance(NS_SCRIPTERROR_CONTRACTID);
161 if (!error) {
162 // This can happen early in component registration. Fall back to a
163 // generic console message.
164 LogMessage("Warning: in '%s', line %i: %s", file.get(),
165 aLineNumber, (char*) formatted);
166 return;
167 }
168
169 nsCOMPtr<nsIConsoleService> console =
170 do_GetService(NS_CONSOLESERVICE_CONTRACTID);
171 if (!console)
172 return;
173
174 nsresult rv = error->Init(NS_ConvertUTF8toUTF16(formatted),
175 NS_ConvertUTF8toUTF16(file), EmptyString(),
176 aLineNumber, 0, nsIScriptError::warningFlag,
177 "chrome registration");
178 if (NS_FAILED(rv))
179 return;
180
181 console->LogMessage(error);
182 }
183
184 /**
185 * Check for a modifier flag of the following forms:
186 * "flag" (same as "true")
187 * "flag=yes|true|1"
188 * "flag="no|false|0"
189 * @param aFlag The flag to compare.
190 * @param aData The tokenized data to check; this is lowercased
191 * before being passed in.
192 * @param aResult If the flag is found, the value is assigned here.
193 * @return Whether the flag was handled.
194 */
195 static bool
196 CheckFlag(const nsSubstring& aFlag, const nsSubstring& aData, bool& aResult)
197 {
198 if (!StringBeginsWith(aData, aFlag))
199 return false;
200
201 if (aFlag.Length() == aData.Length()) {
202 // the data is simply "flag", which is the same as "flag=yes"
203 aResult = true;
204 return true;
205 }
206
207 if (aData.CharAt(aFlag.Length()) != '=') {
208 // the data is "flag2=", which is not anything we care about
209 return false;
210 }
211
212 if (aData.Length() == aFlag.Length() + 1) {
213 aResult = false;
214 return true;
215 }
216
217 switch (aData.CharAt(aFlag.Length() + 1)) {
218 case '1':
219 case 't': //true
220 case 'y': //yes
221 aResult = true;
222 return true;
223
224 case '0':
225 case 'f': //false
226 case 'n': //no
227 aResult = false;
228 return true;
229 }
230
231 return false;
232 }
233
234 enum TriState {
235 eUnspecified,
236 eBad,
237 eOK
238 };
239
240 /**
241 * Check for a modifier flag of the following form:
242 * "flag=string"
243 * "flag!=string"
244 * @param aFlag The flag to compare.
245 * @param aData The tokenized data to check; this is lowercased
246 * before being passed in.
247 * @param aValue The value that is expected.
248 * @param aResult If this is "ok" when passed in, this is left alone.
249 * Otherwise if the flag is found it is set to eBad or eOK.
250 * @return Whether the flag was handled.
251 */
252 static bool
253 CheckStringFlag(const nsSubstring& aFlag, const nsSubstring& aData,
254 const nsSubstring& aValue, TriState& aResult)
255 {
256 if (aData.Length() < aFlag.Length() + 1)
257 return false;
258
259 if (!StringBeginsWith(aData, aFlag))
260 return false;
261
262 bool comparison = true;
263 if (aData[aFlag.Length()] != '=') {
264 if (aData[aFlag.Length()] == '!' &&
265 aData.Length() >= aFlag.Length() + 2 &&
266 aData[aFlag.Length() + 1] == '=')
267 comparison = false;
268 else
269 return false;
270 }
271
272 if (aResult != eOK) {
273 nsDependentSubstring testdata = Substring(aData, aFlag.Length() + (comparison ? 1 : 2));
274 if (testdata.Equals(aValue))
275 aResult = comparison ? eOK : eBad;
276 else
277 aResult = comparison ? eBad : eOK;
278 }
279
280 return true;
281 }
282
283 /**
284 * Check for a modifier flag of the following form:
285 * "flag=version"
286 * "flag<=version"
287 * "flag<version"
288 * "flag>=version"
289 * "flag>version"
290 * @param aFlag The flag to compare.
291 * @param aData The tokenized data to check; this is lowercased
292 * before being passed in.
293 * @param aValue The value that is expected. If this is empty then no
294 * comparison will match.
295 * @param aResult If this is eOK when passed in, this is left alone.
296 * Otherwise if the flag is found it is set to eBad or eOK.
297 * @return Whether the flag was handled.
298 */
299
300 #define COMPARE_EQ 1 << 0
301 #define COMPARE_LT 1 << 1
302 #define COMPARE_GT 1 << 2
303
304 static bool
305 CheckVersionFlag(const nsString& aFlag, const nsString& aData,
306 const nsString& aValue, TriState& aResult)
307 {
308 if (aData.Length() < aFlag.Length() + 2)
309 return false;
310
311 if (!StringBeginsWith(aData, aFlag))
312 return false;
313
314 if (aValue.Length() == 0) {
315 if (aResult != eOK)
316 aResult = eBad;
317 return true;
318 }
319
320 uint32_t comparison;
321 nsAutoString testdata;
322
323 switch (aData[aFlag.Length()]) {
324 case '=':
325 comparison = COMPARE_EQ;
326 testdata = Substring(aData, aFlag.Length() + 1);
327 break;
328
329 case '<':
330 if (aData[aFlag.Length() + 1] == '=') {
331 comparison = COMPARE_EQ | COMPARE_LT;
332 testdata = Substring(aData, aFlag.Length() + 2);
333 }
334 else {
335 comparison = COMPARE_LT;
336 testdata = Substring(aData, aFlag.Length() + 1);
337 }
338 break;
339
340 case '>':
341 if (aData[aFlag.Length() + 1] == '=') {
342 comparison = COMPARE_EQ | COMPARE_GT;
343 testdata = Substring(aData, aFlag.Length() + 2);
344 }
345 else {
346 comparison = COMPARE_GT;
347 testdata = Substring(aData, aFlag.Length() + 1);
348 }
349 break;
350
351 default:
352 return false;
353 }
354
355 if (testdata.Length() == 0)
356 return false;
357
358 if (aResult != eOK) {
359 int32_t c = mozilla::CompareVersions(NS_ConvertUTF16toUTF8(aValue).get(),
360 NS_ConvertUTF16toUTF8(testdata).get());
361 if ((c == 0 && comparison & COMPARE_EQ) ||
362 (c < 0 && comparison & COMPARE_LT) ||
363 (c > 0 && comparison & COMPARE_GT))
364 aResult = eOK;
365 else
366 aResult = eBad;
367 }
368
369 return true;
370 }
371
372 // In-place conversion of ascii characters to lower case
373 static void
374 ToLowerCase(char* token)
375 {
376 for (; *token; ++token)
377 *token = NS_ToLower(*token);
378 }
379
380 namespace {
381
382 struct CachedDirective
383 {
384 int lineno;
385 char* argv[4];
386 };
387
388 } // anonymous namespace
389
390
391 void
392 ParseManifest(NSLocationType type, FileLocation &file, char* buf, bool aChromeOnly)
393 {
394 nsComponentManagerImpl::ManifestProcessingContext mgrcx(type, file, aChromeOnly);
395 nsChromeRegistry::ManifestProcessingContext chromecx(type, file);
396 nsresult rv;
397
398 NS_NAMED_LITERAL_STRING(kPlatform, "platform");
399 NS_NAMED_LITERAL_STRING(kContentAccessible, "contentaccessible");
400 NS_NAMED_LITERAL_STRING(kApplication, "application");
401 NS_NAMED_LITERAL_STRING(kAppVersion, "appversion");
402 NS_NAMED_LITERAL_STRING(kGeckoVersion, "platformversion");
403 NS_NAMED_LITERAL_STRING(kOs, "os");
404 NS_NAMED_LITERAL_STRING(kOsVersion, "osversion");
405 NS_NAMED_LITERAL_STRING(kABI, "abi");
406 #if defined(MOZ_WIDGET_ANDROID)
407 NS_NAMED_LITERAL_STRING(kTablet, "tablet");
408 #endif
409
410 // Obsolete
411 NS_NAMED_LITERAL_STRING(kXPCNativeWrappers, "xpcnativewrappers");
412
413 nsAutoString appID;
414 nsAutoString appVersion;
415 nsAutoString geckoVersion;
416 nsAutoString osTarget;
417 nsAutoString abi;
418
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);
425
426 rv = xapp->GetVersion(s);
427 if (NS_SUCCEEDED(rv))
428 CopyUTF8toUTF16(s, appVersion);
429
430 rv = xapp->GetPlatformVersion(s);
431 if (NS_SUCCEEDED(rv))
432 CopyUTF8toUTF16(s, geckoVersion);
433
434 nsCOMPtr<nsIXULRuntime> xruntime (do_QueryInterface(xapp));
435 if (xruntime) {
436 rv = xruntime->GetOS(s);
437 if (NS_SUCCEEDED(rv)) {
438 ToLowerCase(s);
439 CopyUTF8toUTF16(s, osTarget);
440 }
441
442 rv = xruntime->GetXPCOMABI(s);
443 if (NS_SUCCEEDED(rv) && osTarget.Length()) {
444 ToLowerCase(s);
445 CopyUTF8toUTF16(s, abi);
446 abi.Insert(char16_t('_'), 0);
447 abi.Insert(osTarget, 0);
448 }
449 }
450 }
451
452 nsAutoString osVersion;
453 #if defined(XP_WIN)
454 #pragma warning(push)
455 #pragma warning(disable:4996) // VC12+ deprecates GetVersionEx
456 OSVERSIONINFO info = { sizeof(OSVERSIONINFO) };
457 if (GetVersionEx(&info)) {
458 nsTextFormatter::ssprintf(osVersion, MOZ_UTF16("%ld.%ld"),
459 info.dwMajorVersion,
460 info.dwMinorVersion);
461 }
462 #pragma warning(pop)
463 #elif defined(MOZ_WIDGET_COCOA)
464 SInt32 majorVersion = nsCocoaFeatures::OSXVersionMajor();
465 SInt32 minorVersion = nsCocoaFeatures::OSXVersionMinor();
466 nsTextFormatter::ssprintf(osVersion, NS_LITERAL_STRING("%ld.%ld").get(),
467 majorVersion,
468 minorVersion);
469 #elif defined(MOZ_WIDGET_GTK)
470 nsTextFormatter::ssprintf(osVersion, MOZ_UTF16("%ld.%ld"),
471 gtk_major_version,
472 gtk_minor_version);
473 #elif defined(MOZ_WIDGET_ANDROID)
474 bool isTablet = false;
475 if (mozilla::AndroidBridge::Bridge()) {
476 mozilla::AndroidBridge::Bridge()->GetStaticStringField("android/os/Build$VERSION", "RELEASE", osVersion);
477 isTablet = mozilla::widget::android::GeckoAppShell::IsTablet();
478 }
479 #endif
480
481 // Because contracts must be registered after CIDs, we save and process them
482 // at the end.
483 nsTArray<CachedDirective> contracts;
484
485 char *token;
486 char *newline = buf;
487 uint32_t line = 0;
488
489 // outer loop tokenizes by newline
490 while (*newline) {
491 while (*newline && IsNewline(*newline)) {
492 ++newline;
493 ++line;
494 }
495 if (!*newline)
496 break;
497
498 token = newline;
499 while (*newline && !IsNewline(*newline))
500 ++newline;
501
502 if (*newline) {
503 *newline = '\0';
504 ++newline;
505 }
506 ++line;
507
508 if (*token == '#') // ignore lines that begin with # as comments
509 continue;
510
511 char *whitespace = token;
512 token = nsCRT::strtok(whitespace, kWhitespace, &whitespace);
513 if (!token) continue;
514
515 const ManifestDirective* directive = nullptr;
516 for (const ManifestDirective* d = kParsingTable;
517 d < ArrayEnd(kParsingTable);
518 ++d) {
519 if (!strcmp(d->directive, token)) {
520 directive = d;
521 break;
522 }
523 }
524
525 if (!directive) {
526 LogMessageWithContext(file, line,
527 "Ignoring unrecognized chrome manifest directive '%s'.",
528 token);
529 continue;
530 }
531
532 if (!directive->allowbootstrap && NS_BOOTSTRAPPED_LOCATION == type) {
533 LogMessageWithContext(file, line,
534 "Bootstrapped manifest not allowed to use '%s' directive.",
535 token);
536 continue;
537 }
538
539 if (directive->componentonly && NS_SKIN_LOCATION == type) {
540 LogMessageWithContext(file, line,
541 "Skin manifest not allowed to use '%s' directive.",
542 token);
543 continue;
544 }
545
546 NS_ASSERTION(directive->argc < 4, "Need to reset argv array length");
547 char* argv[4];
548 for (int i = 0; i < directive->argc; ++i)
549 argv[i] = nsCRT::strtok(whitespace, kWhitespace, &whitespace);
550
551 if (!argv[directive->argc - 1]) {
552 LogMessageWithContext(file, line,
553 "Not enough arguments for chrome manifest directive '%s', expected %i.",
554 token, directive->argc);
555 continue;
556 }
557
558 bool ok = true;
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 #if defined(MOZ_WIDGET_ANDROID)
566 TriState stTablet = eUnspecified;
567 #endif
568 bool platform = false;
569 bool contentAccessible = false;
570
571 while (nullptr != (token = nsCRT::strtok(whitespace, kWhitespace, &whitespace)) && ok) {
572 ToLowerCase(token);
573 NS_ConvertASCIItoUTF16 wtoken(token);
574
575 if (CheckStringFlag(kApplication, wtoken, appID, stApp) ||
576 CheckStringFlag(kOs, wtoken, osTarget, stOs) ||
577 CheckStringFlag(kABI, wtoken, abi, stABI) ||
578 CheckVersionFlag(kOsVersion, wtoken, osVersion, stOsVersion) ||
579 CheckVersionFlag(kAppVersion, wtoken, appVersion, stAppVersion) ||
580 CheckVersionFlag(kGeckoVersion, wtoken, geckoVersion, stGeckoVersion))
581 continue;
582
583 #if defined(MOZ_WIDGET_ANDROID)
584 bool tablet = false;
585 if (CheckFlag(kTablet, wtoken, tablet)) {
586 stTablet = (tablet == isTablet) ? eOK : eBad;
587 continue;
588 }
589 #endif
590
591 if (directive->contentflags &&
592 (CheckFlag(kPlatform, wtoken, platform) ||
593 CheckFlag(kContentAccessible, wtoken, contentAccessible)))
594 continue;
595
596 bool xpcNativeWrappers = true; // Dummy for CheckFlag.
597 if (CheckFlag(kXPCNativeWrappers, wtoken, xpcNativeWrappers)) {
598 LogMessageWithContext(file, line,
599 "Ignoring obsolete chrome registration modifier '%s'.",
600 token);
601 continue;
602 }
603
604 LogMessageWithContext(file, line,
605 "Unrecognized chrome manifest modifier '%s'.",
606 token);
607 ok = false;
608 }
609
610 if (!ok ||
611 stApp == eBad ||
612 stAppVersion == eBad ||
613 stGeckoVersion == eBad ||
614 stOs == eBad ||
615 stOsVersion == eBad ||
616 #ifdef MOZ_WIDGET_ANDROID
617 stTablet == eBad ||
618 #endif
619 stABI == eBad)
620 continue;
621
622 if (directive->regfunc) {
623 if (GeckoProcessType_Default != XRE_GetProcessType())
624 continue;
625
626 if (!nsChromeRegistry::gChromeRegistry) {
627 nsCOMPtr<nsIChromeRegistry> cr =
628 mozilla::services::GetChromeRegistryService();
629 if (!nsChromeRegistry::gChromeRegistry) {
630 LogMessageWithContext(file, line,
631 "Chrome registry isn't available yet.");
632 continue;
633 }
634 }
635
636 (nsChromeRegistry::gChromeRegistry->*(directive->regfunc))
637 (chromecx, line, argv, platform, contentAccessible);
638 }
639 else if (directive->ischrome || !aChromeOnly) {
640 if (directive->isContract) {
641 CachedDirective* cd = contracts.AppendElement();
642 cd->lineno = line;
643 cd->argv[0] = argv[0];
644 cd->argv[1] = argv[1];
645 }
646 else
647 (nsComponentManagerImpl::gComponentManager->*(directive->mgrfunc))
648 (mgrcx, line, argv);
649 }
650 }
651
652 for (uint32_t i = 0; i < contracts.Length(); ++i) {
653 CachedDirective& d = contracts[i];
654 nsComponentManagerImpl::gComponentManager->ManifestContract
655 (mgrcx, d.lineno, d.argv);
656 }
657 }

mercurial