1/*
2 * Copyright (C) 2012 Google Inc. All rights reserved.
3 * Copyright (C) 2013-2018 Apple Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
18 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
21 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "UserMediaController.h"
28
29#if ENABLE(MEDIA_STREAM)
30
31#include "CustomHeaderFields.h"
32#include "DOMWindow.h"
33#include "Document.h"
34#include "DocumentLoader.h"
35#include "Frame.h"
36#include "HTMLIFrameElement.h"
37#include "HTMLParserIdioms.h"
38#include "SchemeRegistry.h"
39#include "Settings.h"
40#include "UserMediaRequest.h"
41
42namespace WebCore {
43
44const char* UserMediaController::supplementName()
45{
46 return "UserMediaController";
47}
48
49UserMediaController::UserMediaController(UserMediaClient* client)
50 : m_client(client)
51{
52}
53
54UserMediaController::~UserMediaController()
55{
56 m_client->pageDestroyed();
57}
58
59void provideUserMediaTo(Page* page, UserMediaClient* client)
60{
61 UserMediaController::provideTo(page, UserMediaController::supplementName(), std::make_unique<UserMediaController>(client));
62}
63
64static inline bool isSecure(DocumentLoader& documentLoader)
65{
66 auto& response = documentLoader.response();
67 if (SecurityOrigin::isLocalHostOrLoopbackIPAddress(documentLoader.response().url().host()))
68 return true;
69 return SchemeRegistry::shouldTreatURLSchemeAsSecure(response.url().protocol().toStringWithoutCopying())
70 && response.certificateInfo()
71 && !response.certificateInfo()->containsNonRootSHA1SignedCertificate();
72}
73
74static inline bool isAllowedByFeaturePolicy(const FeaturePolicy& featurePolicy, const SecurityOriginData& origin, OptionSet<UserMediaController::CaptureType> types)
75{
76 if ((types & UserMediaController::CaptureType::Camera) && !featurePolicy.allows(FeaturePolicy::Type::Camera, origin))
77 return false;
78
79 if ((types & UserMediaController::CaptureType::Microphone) && !featurePolicy.allows(FeaturePolicy::Type::Microphone, origin))
80 return false;
81
82 if ((types & UserMediaController::CaptureType::Display) && !featurePolicy.allows(FeaturePolicy::Type::DisplayCapture, origin))
83 return false;
84
85 return true;
86}
87
88static UserMediaController::GetUserMediaAccess isAllowedToUse(Document& document, Document& topDocument, OptionSet<UserMediaController::CaptureType> types)
89{
90 if (&document == &topDocument)
91 return UserMediaController::GetUserMediaAccess::CanCall;
92
93 auto* parentDocument = document.parentDocument();
94 if (!parentDocument)
95 return UserMediaController::GetUserMediaAccess::BlockedByParent;
96
97 auto* element = document.ownerElement();
98 ASSERT(element);
99 if (!element || !is<HTMLIFrameElement>(*element))
100 return UserMediaController::GetUserMediaAccess::BlockedByParent;
101
102 auto& featurePolicy = downcast<HTMLIFrameElement>(*element).featurePolicy();
103 if (isAllowedByFeaturePolicy(featurePolicy, document.securityOrigin().data(), types))
104 return UserMediaController::GetUserMediaAccess::CanCall;
105
106 return UserMediaController::GetUserMediaAccess::BlockedByFeaturePolicy;
107}
108
109UserMediaController::GetUserMediaAccess UserMediaController::canCallGetUserMedia(Document& document, OptionSet<UserMediaController::CaptureType> types)
110{
111 ASSERT(!types.isEmpty());
112
113 bool requiresSecureConnection = true;
114 if (auto page = document.page())
115 requiresSecureConnection = page->settings().mediaCaptureRequiresSecureConnection();
116 auto& documentLoader = *document.loader();
117 if (requiresSecureConnection && !isSecure(documentLoader))
118 return GetUserMediaAccess::InsecureDocument;
119
120 auto& topDocument = document.topDocument();
121 if (&document != &topDocument) {
122 for (auto* ancestorDocument = &document; ancestorDocument != &topDocument; ancestorDocument = ancestorDocument->parentDocument()) {
123 if (requiresSecureConnection && !isSecure(*ancestorDocument->loader()))
124 return GetUserMediaAccess::InsecureParent;
125
126 auto status = isAllowedToUse(*ancestorDocument, topDocument, types);
127 if (status != GetUserMediaAccess::CanCall)
128 return status;
129 }
130 }
131
132 return GetUserMediaAccess::CanCall;
133}
134
135void UserMediaController::logGetUserMediaDenial(Document& document, GetUserMediaAccess access, BlockedCaller caller)
136{
137 auto& domWindow = *document.domWindow();
138 const char* callerName;
139
140 switch (caller) {
141 case BlockedCaller::GetUserMedia:
142 callerName = "getUserMedia";
143 break;
144 case BlockedCaller::GetDisplayMedia:
145 callerName = "getDisplayMedia";
146 break;
147 case BlockedCaller::EnumerateDevices:
148 callerName = "enumerateDevices";
149 break;
150 }
151
152 switch (access) {
153 case UserMediaController::GetUserMediaAccess::InsecureDocument:
154 domWindow.printErrorMessage(makeString("Trying to call ", callerName, " from an insecure document."));
155 break;
156 case UserMediaController::GetUserMediaAccess::InsecureParent:
157 domWindow.printErrorMessage(makeString("Trying to call ", callerName, " from a document with an insecure parent frame."));
158 break;
159 case UserMediaController::GetUserMediaAccess::BlockedByParent:
160 domWindow.printErrorMessage(makeString("The top-level frame has prevented a document with a different security origin from calling ", callerName, "."));
161 break;
162 case GetUserMediaAccess::BlockedByFeaturePolicy:
163 domWindow.printErrorMessage(makeString("Trying to call ", callerName, " from a frame without correct 'allow' attribute."));
164 break;
165 case UserMediaController::GetUserMediaAccess::CanCall:
166 break;
167 }
168}
169
170} // namespace WebCore
171
172#endif // ENABLE(MEDIA_STREAM)
173