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/. */
6
7 #include "nsPluginArray.h"
8
9 #include "mozilla/dom/PluginArrayBinding.h"
10 #include "mozilla/dom/PluginBinding.h"
11 #include "mozilla/dom/HiddenPluginEvent.h"
12
13 #include "nsMimeTypeArray.h"
14 #include "Navigator.h"
15 #include "nsIDocShell.h"
16 #include "nsIWebNavigation.h"
17 #include "nsPluginHost.h"
18 #include "nsPluginTags.h"
19 #include "nsIObserverService.h"
20 #include "nsIWeakReference.h"
21 #include "mozilla/Services.h"
22 #include "nsIInterfaceRequestorUtils.h"
23 #include "nsContentUtils.h"
24 #include "nsIPermissionManager.h"
25 #include "nsIDocument.h"
26 #include "nsIBlocklistService.h"
27
28 using namespace mozilla;
29 using namespace mozilla::dom;
30
nsPluginArray(nsPIDOMWindowInner * aWindow)31 nsPluginArray::nsPluginArray(nsPIDOMWindowInner* aWindow)
32 : mWindow(aWindow)
33 {
34 }
35
36 void
Init()37 nsPluginArray::Init()
38 {
39 nsCOMPtr<nsIObserverService> obsService =
40 mozilla::services::GetObserverService();
41 if (obsService) {
42 obsService->AddObserver(this, "plugin-info-updated", true);
43 }
44 }
45
~nsPluginArray()46 nsPluginArray::~nsPluginArray()
47 {
48 }
49
50 static bool
ResistFingerprinting()51 ResistFingerprinting() {
52 return !nsContentUtils::ThreadsafeIsCallerChrome() &&
53 nsContentUtils::ResistFingerprinting();
54 }
55
56 nsPIDOMWindowInner*
GetParentObject() const57 nsPluginArray::GetParentObject() const
58 {
59 MOZ_ASSERT(mWindow);
60 return mWindow;
61 }
62
63 JSObject*
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)64 nsPluginArray::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
65 {
66 return PluginArrayBinding::Wrap(aCx, this, aGivenProto);
67 }
68
69 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsPluginArray)
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsPluginArray)70 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsPluginArray)
71 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsPluginArray)
72 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
73 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
74 NS_INTERFACE_MAP_ENTRY(nsIObserver)
75 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
76 NS_INTERFACE_MAP_END
77
78 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsPluginArray,
79 mWindow,
80 mPlugins,
81 mCTPPlugins)
82
83 static void
84 GetPluginMimeTypes(const nsTArray<RefPtr<nsPluginElement> >& aPlugins,
85 nsTArray<RefPtr<nsMimeType> >& aMimeTypes)
86 {
87 for (uint32_t i = 0; i < aPlugins.Length(); ++i) {
88 nsPluginElement *plugin = aPlugins[i];
89 aMimeTypes.AppendElements(plugin->MimeTypes());
90 }
91 }
92
93 static bool
operator <(const RefPtr<nsMimeType> & lhs,const RefPtr<nsMimeType> & rhs)94 operator<(const RefPtr<nsMimeType>& lhs, const RefPtr<nsMimeType>& rhs)
95 {
96 // Sort MIME types alphabetically by type name.
97 return lhs->Type() < rhs->Type();
98 }
99
100 void
GetMimeTypes(nsTArray<RefPtr<nsMimeType>> & aMimeTypes)101 nsPluginArray::GetMimeTypes(nsTArray<RefPtr<nsMimeType>>& aMimeTypes)
102 {
103 aMimeTypes.Clear();
104
105 if (!AllowPlugins()) {
106 return;
107 }
108
109 EnsurePlugins();
110
111 GetPluginMimeTypes(mPlugins, aMimeTypes);
112
113 // Alphabetize the enumeration order of non-hidden MIME types to reduce
114 // fingerprintable entropy based on plugins' installation file times.
115 aMimeTypes.Sort();
116 }
117
118 void
GetCTPMimeTypes(nsTArray<RefPtr<nsMimeType>> & aMimeTypes)119 nsPluginArray::GetCTPMimeTypes(nsTArray<RefPtr<nsMimeType>>& aMimeTypes)
120 {
121 aMimeTypes.Clear();
122
123 if (!AllowPlugins()) {
124 return;
125 }
126
127 EnsurePlugins();
128
129 GetPluginMimeTypes(mCTPPlugins, aMimeTypes);
130
131 // Alphabetize the enumeration order of non-hidden MIME types to reduce
132 // fingerprintable entropy based on plugins' installation file times.
133 aMimeTypes.Sort();
134 }
135
136 nsPluginElement*
Item(uint32_t aIndex)137 nsPluginArray::Item(uint32_t aIndex)
138 {
139 bool unused;
140 return IndexedGetter(aIndex, unused);
141 }
142
143 nsPluginElement*
NamedItem(const nsAString & aName)144 nsPluginArray::NamedItem(const nsAString& aName)
145 {
146 bool unused;
147 return NamedGetter(aName, unused);
148 }
149
150 void
Refresh(bool aReloadDocuments)151 nsPluginArray::Refresh(bool aReloadDocuments)
152 {
153 RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
154
155 if(!AllowPlugins() || !pluginHost) {
156 return;
157 }
158
159 // NS_ERROR_PLUGINS_PLUGINSNOTCHANGED on reloading plugins indicates
160 // that plugins did not change and was not reloaded
161 if (pluginHost->ReloadPlugins() ==
162 NS_ERROR_PLUGINS_PLUGINSNOTCHANGED) {
163 nsTArray<nsCOMPtr<nsIInternalPluginTag> > newPluginTags;
164 pluginHost->GetPlugins(newPluginTags);
165
166 // Check if the number of plugins we know about are different from
167 // the number of plugin tags the plugin host knows about. If the
168 // lengths are different, we refresh. This is safe because we're
169 // notified for every plugin enabling/disabling event that
170 // happens, and therefore the lengths will be in sync only when
171 // the both arrays contain the same plugin tags (though as
172 // different types).
173 if (newPluginTags.Length() == mPlugins.Length()) {
174 return;
175 }
176 }
177
178 mPlugins.Clear();
179 mCTPPlugins.Clear();
180
181 nsCOMPtr<nsIDOMNavigator> navigator = mWindow->GetNavigator();
182
183 if (!navigator) {
184 return;
185 }
186
187 static_cast<mozilla::dom::Navigator*>(navigator.get())->RefreshMIMEArray();
188
189 nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(mWindow);
190 if (aReloadDocuments && webNav) {
191 webNav->Reload(nsIWebNavigation::LOAD_FLAGS_NONE);
192 }
193 }
194
195 nsPluginElement*
IndexedGetter(uint32_t aIndex,bool & aFound)196 nsPluginArray::IndexedGetter(uint32_t aIndex, bool &aFound)
197 {
198 aFound = false;
199
200 if (!AllowPlugins() || ResistFingerprinting()) {
201 return nullptr;
202 }
203
204 EnsurePlugins();
205
206 aFound = aIndex < mPlugins.Length();
207
208 if (!aFound) {
209 return nullptr;
210 }
211
212 return mPlugins[aIndex];
213 }
214
215 void
Invalidate()216 nsPluginArray::Invalidate()
217 {
218 nsCOMPtr<nsIObserverService> obsService =
219 mozilla::services::GetObserverService();
220 if (obsService) {
221 obsService->RemoveObserver(this, "plugin-info-updated");
222 }
223 }
224
225 static nsPluginElement*
FindPlugin(const nsTArray<RefPtr<nsPluginElement>> & aPlugins,const nsAString & aName)226 FindPlugin(const nsTArray<RefPtr<nsPluginElement> >& aPlugins,
227 const nsAString& aName)
228 {
229 for (uint32_t i = 0; i < aPlugins.Length(); ++i) {
230 nsAutoString pluginName;
231 nsPluginElement* plugin = aPlugins[i];
232 plugin->GetName(pluginName);
233
234 if (pluginName.Equals(aName)) {
235 return plugin;
236 }
237 }
238
239 return nullptr;
240 }
241
242 nsPluginElement*
NamedGetter(const nsAString & aName,bool & aFound)243 nsPluginArray::NamedGetter(const nsAString& aName, bool &aFound)
244 {
245 aFound = false;
246
247 if (!AllowPlugins() || ResistFingerprinting()) {
248 return nullptr;
249 }
250
251 EnsurePlugins();
252
253 nsPluginElement* plugin = FindPlugin(mPlugins, aName);
254 aFound = (plugin != nullptr);
255 if (!aFound) {
256 nsPluginElement* hiddenPlugin = FindPlugin(mCTPPlugins, aName);
257 if (hiddenPlugin) {
258 NotifyHiddenPluginTouched(hiddenPlugin);
259 }
260 }
261 return plugin;
262 }
263
NotifyHiddenPluginTouched(nsPluginElement * aHiddenElement)264 void nsPluginArray::NotifyHiddenPluginTouched(nsPluginElement* aHiddenElement)
265 {
266 HiddenPluginEventInit init;
267 init.mTag = aHiddenElement->PluginTag();
268 nsCOMPtr<nsIDocument> doc = aHiddenElement->GetParentObject()->GetDoc();
269 RefPtr<HiddenPluginEvent> event =
270 HiddenPluginEvent::Constructor(doc, NS_LITERAL_STRING("HiddenPlugin"), init);
271 event->SetTarget(doc);
272 event->SetTrusted(true);
273 event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
274 bool dummy;
275 doc->DispatchEvent(event, &dummy);
276 }
277
278 uint32_t
Length()279 nsPluginArray::Length()
280 {
281 if (!AllowPlugins() || ResistFingerprinting()) {
282 return 0;
283 }
284
285 EnsurePlugins();
286
287 return mPlugins.Length();
288 }
289
290 void
GetSupportedNames(nsTArray<nsString> & aRetval)291 nsPluginArray::GetSupportedNames(nsTArray<nsString>& aRetval)
292 {
293 aRetval.Clear();
294
295 if (!AllowPlugins()) {
296 return;
297 }
298
299 for (uint32_t i = 0; i < mPlugins.Length(); ++i) {
300 nsAutoString pluginName;
301 mPlugins[i]->GetName(pluginName);
302
303 aRetval.AppendElement(pluginName);
304 }
305 }
306
307 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)308 nsPluginArray::Observe(nsISupports *aSubject, const char *aTopic,
309 const char16_t *aData) {
310 if (!nsCRT::strcmp(aTopic, "plugin-info-updated")) {
311 Refresh(false);
312 }
313
314 return NS_OK;
315 }
316
317 bool
AllowPlugins() const318 nsPluginArray::AllowPlugins() const
319 {
320 nsCOMPtr<nsIDocShell> docShell = mWindow ? mWindow->GetDocShell() : nullptr;
321
322 return docShell && docShell->PluginsAllowedInCurrentDoc();
323 }
324
325 static bool
operator <(const RefPtr<nsPluginElement> & lhs,const RefPtr<nsPluginElement> & rhs)326 operator<(const RefPtr<nsPluginElement>& lhs,
327 const RefPtr<nsPluginElement>& rhs)
328 {
329 // Sort plugins alphabetically by name.
330 return lhs->PluginTag()->Name() < rhs->PluginTag()->Name();
331 }
332
333 static bool
PluginShouldBeHidden(nsCString aName)334 PluginShouldBeHidden(nsCString aName) {
335 // This only supports one hidden plugin
336 return Preferences::GetCString("plugins.navigator.hidden_ctp_plugin").Equals(aName);
337 }
338
339 void
EnsurePlugins()340 nsPluginArray::EnsurePlugins()
341 {
342 if (!mPlugins.IsEmpty() || !mCTPPlugins.IsEmpty()) {
343 // We already have an array of plugin elements.
344 return;
345 }
346
347 RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
348 if (!pluginHost) {
349 // We have no plugin host.
350 return;
351 }
352
353 nsTArray<nsCOMPtr<nsIInternalPluginTag> > pluginTags;
354 pluginHost->GetPlugins(pluginTags);
355
356 // need to wrap each of these with a nsPluginElement, which is
357 // scriptable.
358 for (uint32_t i = 0; i < pluginTags.Length(); ++i) {
359 nsCOMPtr<nsPluginTag> pluginTag = do_QueryInterface(pluginTags[i]);
360 if (!pluginTag) {
361 mPlugins.AppendElement(new nsPluginElement(mWindow, pluginTags[i]));
362 } else if (pluginTag->IsActive()) {
363 uint32_t permission = nsIPermissionManager::ALLOW_ACTION;
364 uint32_t blocklistState;
365 if (pluginTag->IsClicktoplay() &&
366 NS_SUCCEEDED(pluginTag->GetBlocklistState(&blocklistState)) &&
367 blocklistState == nsIBlocklistService::STATE_NOT_BLOCKED) {
368 nsCString name;
369 pluginTag->GetName(name);
370 if (PluginShouldBeHidden(name)) {
371 RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
372 nsCString permString;
373 nsresult rv = pluginHost->GetPermissionStringForTag(pluginTag, 0, permString);
374 if (rv == NS_OK) {
375 nsIPrincipal* principal = mWindow->GetExtantDoc()->NodePrincipal();
376 nsCOMPtr<nsIPermissionManager> permMgr = services::GetPermissionManager();
377 permMgr->TestPermissionFromPrincipal(principal, permString.get(), &permission);
378 }
379 }
380 }
381 if (permission == nsIPermissionManager::ALLOW_ACTION) {
382 mPlugins.AppendElement(new nsPluginElement(mWindow, pluginTags[i]));
383 } else {
384 mCTPPlugins.AppendElement(new nsPluginElement(mWindow, pluginTags[i]));
385 }
386 }
387 }
388
389 if (mPlugins.Length() == 0 && mCTPPlugins.Length() != 0) {
390 nsCOMPtr<nsPluginTag> hiddenTag = new nsPluginTag("Hidden Plugin", NULL, "dummy.plugin", NULL, NULL,
391 NULL, NULL, NULL, 0, 0, false);
392 mPlugins.AppendElement(new nsPluginElement(mWindow, hiddenTag));
393 }
394
395 // Alphabetize the enumeration order of non-hidden plugins to reduce
396 // fingerprintable entropy based on plugins' installation file times.
397 mPlugins.Sort();
398 }
399
400 // nsPluginElement implementation.
401
402 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsPluginElement)
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsPluginElement)403 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsPluginElement)
404 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsPluginElement)
405 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
406 NS_INTERFACE_MAP_ENTRY(nsISupports)
407 NS_INTERFACE_MAP_END
408
409 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsPluginElement, mWindow, mMimeTypes)
410
411 nsPluginElement::nsPluginElement(nsPIDOMWindowInner* aWindow,
412 nsIInternalPluginTag* aPluginTag)
413 : mWindow(aWindow),
414 mPluginTag(aPluginTag)
415 {
416 }
417
~nsPluginElement()418 nsPluginElement::~nsPluginElement()
419 {
420 }
421
422 nsPIDOMWindowInner*
GetParentObject() const423 nsPluginElement::GetParentObject() const
424 {
425 MOZ_ASSERT(mWindow);
426 return mWindow;
427 }
428
429 JSObject*
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)430 nsPluginElement::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
431 {
432 return PluginBinding::Wrap(aCx, this, aGivenProto);
433 }
434
435 void
GetDescription(nsString & retval) const436 nsPluginElement::GetDescription(nsString& retval) const
437 {
438 CopyUTF8toUTF16(mPluginTag->Description(), retval);
439 }
440
441 void
GetFilename(nsString & retval) const442 nsPluginElement::GetFilename(nsString& retval) const
443 {
444 CopyUTF8toUTF16(mPluginTag->FileName(), retval);
445 }
446
447 void
GetVersion(nsString & retval) const448 nsPluginElement::GetVersion(nsString& retval) const
449 {
450 CopyUTF8toUTF16(mPluginTag->Version(), retval);
451 }
452
453 void
GetName(nsString & retval) const454 nsPluginElement::GetName(nsString& retval) const
455 {
456 CopyUTF8toUTF16(mPluginTag->Name(), retval);
457 }
458
459 nsMimeType*
Item(uint32_t aIndex)460 nsPluginElement::Item(uint32_t aIndex)
461 {
462 EnsurePluginMimeTypes();
463
464 return mMimeTypes.SafeElementAt(aIndex);
465 }
466
467 nsMimeType*
NamedItem(const nsAString & aName)468 nsPluginElement::NamedItem(const nsAString& aName)
469 {
470 bool unused;
471 return NamedGetter(aName, unused);
472 }
473
474 nsMimeType*
IndexedGetter(uint32_t aIndex,bool & aFound)475 nsPluginElement::IndexedGetter(uint32_t aIndex, bool &aFound)
476 {
477 EnsurePluginMimeTypes();
478
479 aFound = aIndex < mMimeTypes.Length();
480
481 if (!aFound) {
482 return nullptr;
483 }
484
485 return mMimeTypes[aIndex];
486 }
487
488 nsMimeType*
NamedGetter(const nsAString & aName,bool & aFound)489 nsPluginElement::NamedGetter(const nsAString& aName, bool &aFound)
490 {
491 EnsurePluginMimeTypes();
492
493 aFound = false;
494
495 for (uint32_t i = 0; i < mMimeTypes.Length(); ++i) {
496 if (mMimeTypes[i]->Type().Equals(aName)) {
497 aFound = true;
498
499 return mMimeTypes[i];
500 }
501 }
502
503 return nullptr;
504 }
505
506 uint32_t
Length()507 nsPluginElement::Length()
508 {
509 EnsurePluginMimeTypes();
510
511 return mMimeTypes.Length();
512 }
513
514 void
GetSupportedNames(nsTArray<nsString> & retval)515 nsPluginElement::GetSupportedNames(nsTArray<nsString>& retval)
516 {
517 EnsurePluginMimeTypes();
518
519 for (uint32_t i = 0; i < mMimeTypes.Length(); ++i) {
520 retval.AppendElement(mMimeTypes[i]->Type());
521 }
522 }
523
524 nsTArray<RefPtr<nsMimeType> >&
MimeTypes()525 nsPluginElement::MimeTypes()
526 {
527 EnsurePluginMimeTypes();
528
529 return mMimeTypes;
530 }
531
532 void
EnsurePluginMimeTypes()533 nsPluginElement::EnsurePluginMimeTypes()
534 {
535 if (!mMimeTypes.IsEmpty()) {
536 return;
537 }
538
539 if (mPluginTag->MimeTypes().Length() != mPluginTag->MimeDescriptions().Length() ||
540 mPluginTag->MimeTypes().Length() != mPluginTag->Extensions().Length()) {
541 MOZ_ASSERT(false, "mime type arrays expected to be the same length");
542 return;
543 }
544
545 for (uint32_t i = 0; i < mPluginTag->MimeTypes().Length(); ++i) {
546 NS_ConvertUTF8toUTF16 type(mPluginTag->MimeTypes()[i]);
547 NS_ConvertUTF8toUTF16 description(mPluginTag->MimeDescriptions()[i]);
548 NS_ConvertUTF8toUTF16 extension(mPluginTag->Extensions()[i]);
549
550 mMimeTypes.AppendElement(new nsMimeType(mWindow, this, type, description,
551 extension));
552 }
553 }
554