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 "DOMWindow.h"
32#include "Document.h"
33#include "DocumentLoader.h"
34#include "Frame.h"
35#include "HTMLIFrameElement.h"
36#include "HTMLParserIdioms.h"
37#include "SchemeRegistry.h"
38#include "Settings.h"
39#include "UserMediaRequest.h"
40
41namespace WebCore {
42
43const char* UserMediaController::supplementName()
44{
45 return "UserMediaController";
46}
47
48UserMediaController::UserMediaController(UserMediaClient* client)
49 : m_client(client)
50{
51}
52
53UserMediaController::~UserMediaController()
54{
55 m_client->pageDestroyed();
56}
57
58void provideUserMediaTo(Page* page, UserMediaClient* client)
59{
60 UserMediaController::provideTo(page, UserMediaController::supplementName(), std::make_unique<UserMediaController>(client));
61}
62
63static bool isSecure(DocumentLoader& documentLoader)
64{
65 auto& response = documentLoader.response();
66 if (SecurityOrigin::isLocalHostOrLoopbackIPAddress(documentLoader.response().url().host()))
67 return true;
68 return SchemeRegistry::shouldTreatURLSchemeAsSecure(response.url().protocol().toStringWithoutCopying())
69 && response.certificateInfo()
70 && !response.certificateInfo()->containsNonRootSHA1SignedCertificate();
71}
72
73static UserMediaController::GetUserMediaAccess isAllowedToUse(Document& document, Document& topDocument, OptionSet<UserMediaController::CaptureType> types)
74{
75 if (&document == &topDocument)
76 return UserMediaController::GetUserMediaAccess::CanCall;
77
78 auto* parentDocument = document.parentDocument();
79 if (!parentDocument)
80 return UserMediaController::GetUserMediaAccess::BlockedByParent;
81
82 if (document.securityOrigin().isSameSchemeHostPort(parentDocument->securityOrigin()))
83 return UserMediaController::GetUserMediaAccess::CanCall;
84
85 auto* element = document.ownerElement();
86 ASSERT(element);
87 if (!element)
88 return UserMediaController::GetUserMediaAccess::BlockedByParent;
89
90 if (!is<HTMLIFrameElement>(*element))
91 return UserMediaController::GetUserMediaAccess::BlockedByParent;
92 auto& allow = downcast<HTMLIFrameElement>(*element).allow();
93
94 bool allowCameraAccess = false;
95 bool allowMicrophoneAccess = false;
96 bool allowDisplay = false;
97 for (auto allowItem : StringView { allow }.split(';')) {
98 auto item = allowItem.stripLeadingAndTrailingMatchedCharacters(isHTMLSpace<UChar>);
99 if (!allowCameraAccess && item == "camera")
100 allowCameraAccess = true;
101 else if (!allowMicrophoneAccess && item == "microphone")
102 allowMicrophoneAccess = true;
103 else if (!allowDisplay && item == "display")
104 allowDisplay = true;
105 }
106 if ((allowCameraAccess || !(types & UserMediaController::CaptureType::Camera)) && (allowMicrophoneAccess || !(types & UserMediaController::CaptureType::Microphone)) && (allowDisplay || !(types & UserMediaController::CaptureType::Display)))
107 return UserMediaController::GetUserMediaAccess::CanCall;
108
109 return UserMediaController::GetUserMediaAccess::BlockedByFeaturePolicy;
110}
111
112UserMediaController::GetUserMediaAccess UserMediaController::canCallGetUserMedia(Document& document, OptionSet<UserMediaController::CaptureType> types)
113{
114 ASSERT(!types.isEmpty());
115
116 bool requiresSecureConnection = true;
117 if (auto page = document.page())
118 requiresSecureConnection = page->settings().mediaCaptureRequiresSecureConnection();
119 auto& documentLoader = *document.loader();
120 if (requiresSecureConnection && !isSecure(documentLoader))
121 return GetUserMediaAccess::InsecureDocument;
122
123 auto& topDocument = document.topDocument();
124 if (&document != &topDocument) {
125 for (auto* ancestorDocument = &document; ancestorDocument != &topDocument; ancestorDocument = ancestorDocument->parentDocument()) {
126 if (requiresSecureConnection && !isSecure(*ancestorDocument->loader()))
127 return GetUserMediaAccess::InsecureParent;
128
129 auto status = isAllowedToUse(*ancestorDocument, topDocument, types);
130 if (status != GetUserMediaAccess::CanCall)
131 return status;
132 }
133 }
134
135 return GetUserMediaAccess::CanCall;
136}
137
138void UserMediaController::logGetUserMediaDenial(Document& document, GetUserMediaAccess access, BlockedCaller caller)
139{
140 auto& domWindow = *document.domWindow();
141 const char* callerName;
142
143 switch (caller) {
144 case BlockedCaller::GetUserMedia:
145 callerName = "getUserMedia";
146 break;
147 case BlockedCaller::GetDisplayMedia:
148 callerName = "getDisplayMedia";
149 break;
150 case BlockedCaller::EnumerateDevices:
151 callerName = "enumerateDevices";
152 break;
153 }
154
155 switch (access) {
156 case UserMediaController::GetUserMediaAccess::InsecureDocument:
157 domWindow.printErrorMessage(makeString("Trying to call ", callerName, " from an insecure document."));
158 break;
159 case UserMediaController::GetUserMediaAccess::InsecureParent:
160 domWindow.printErrorMessage(makeString("Trying to call ", callerName, " from a document with an insecure parent frame."));
161 break;
162 case UserMediaController::GetUserMediaAccess::BlockedByParent:
163 domWindow.printErrorMessage(makeString("The top-level frame has prevented a document with a different security origin from calling ", callerName, "."));
164 break;
165 case GetUserMediaAccess::BlockedByFeaturePolicy:
166 domWindow.printErrorMessage(makeString("Trying to call ", callerName, " from a frame without correct 'allow' attribute."));
167 break;
168 case UserMediaController::GetUserMediaAccess::CanCall:
169 break;
170 }
171}
172
173} // namespace WebCore
174
175#endif // ENABLE(MEDIA_STREAM)
176