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