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