21ea1b574b481a89c968bdc80db8394a27ffbd55
[WebKit-https.git] / Source / WebCore / Modules / mediastream / UserMediaController.cpp
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
41 namespace WebCore {
42
43 const char* UserMediaController::supplementName()
44 {
45     return "UserMediaController";
46 }
47
48 UserMediaController::UserMediaController(UserMediaClient* client)
49     : m_client(client)
50 {
51 }
52
53 UserMediaController::~UserMediaController()
54 {
55     m_client->pageDestroyed();
56 }
57
58 void provideUserMediaTo(Page* page, UserMediaClient* client)
59 {
60     UserMediaController::provideTo(page, UserMediaController::supplementName(), std::make_unique<UserMediaController>(client));
61 }
62
63 static 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
73 static 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
112 UserMediaController::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
138 void 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)