Document::securityOrigin() should return a reference.
[WebKit-https.git] / Source / WebCore / Modules / mediastream / UserMediaRequest.cpp
1 /*
2  * Copyright (C) 2011 Ericsson AB. All rights reserved.
3  * Copyright (C) 2012 Google Inc. All rights reserved.
4  * Copyright (C) 2013-2016 Apple Inc. All rights reserved.
5  * Copyright (C) 2013 Nokia Corporation and/or its subsidiary(-ies).
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer
15  *    in the documentation and/or other materials provided with the
16  *    distribution.
17  * 3. Neither the name of Ericsson nor the names of its contributors
18  *    may be used to endorse or promote products derived from this
19  *    software without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32  */
33
34 #include "config.h"
35 #include "UserMediaRequest.h"
36
37 #if ENABLE(MEDIA_STREAM)
38
39 #include "Document.h"
40 #include "DocumentLoader.h"
41 #include "ExceptionCode.h"
42 #include "JSMediaStream.h"
43 #include "JSOverconstrainedError.h"
44 #include "MainFrame.h"
45 #include "MediaConstraintsImpl.h"
46 #include "RealtimeMediaSourceCenter.h"
47 #include "Settings.h"
48 #include "UserMediaController.h"
49
50 namespace WebCore {
51
52 ExceptionOr<void> UserMediaRequest::start(Document& document, Ref<MediaConstraintsImpl>&& audioConstraints, Ref<MediaConstraintsImpl>&& videoConstraints, DOMPromise<IDLInterface<MediaStream>>&& promise)
53 {
54     auto* userMedia = UserMediaController::from(document.page());
55     if (!userMedia)
56         return Exception { NOT_SUPPORTED_ERR }; // FIXME: Why is it better to return an exception here instead of rejecting the promise as we do just below?
57
58     if (!audioConstraints->isValid() && !videoConstraints->isValid()) {
59         promise.reject(TypeError);
60         return { };
61     }
62
63     adoptRef(*new UserMediaRequest(document, *userMedia, WTFMove(audioConstraints), WTFMove(videoConstraints), WTFMove(promise)))->start();
64     return { };
65 }
66
67 UserMediaRequest::UserMediaRequest(Document& document, UserMediaController& controller, Ref<MediaConstraintsImpl>&& audioConstraints, Ref<MediaConstraintsImpl>&& videoConstraints, DOMPromise<IDLInterface<MediaStream>>&& promise)
68     : ContextDestructionObserver(&document)
69     , m_audioConstraints(WTFMove(audioConstraints))
70     , m_videoConstraints(WTFMove(videoConstraints))
71     , m_controller(&controller)
72     , m_promise(WTFMove(promise))
73 {
74 }
75
76 UserMediaRequest::~UserMediaRequest()
77 {
78 }
79
80 SecurityOrigin* UserMediaRequest::userMediaDocumentOrigin() const
81 {
82     if (!m_scriptExecutionContext)
83         return nullptr;
84     return m_scriptExecutionContext->securityOrigin();
85 }
86
87 SecurityOrigin* UserMediaRequest::topLevelDocumentOrigin() const
88 {
89     if (!m_scriptExecutionContext)
90         return nullptr;
91     return m_scriptExecutionContext->topOrigin();
92 }
93
94 static bool isSecure(DocumentLoader& documentLoader)
95 {
96     auto& response = documentLoader.response();
97     return response.url().protocolIs("https")
98         && response.certificateInfo()
99         && !response.certificateInfo()->containsNonRootSHA1SignedCertificate();
100 }
101
102 static bool canCallGetUserMedia(Document& document, String& errorMessage)
103 {
104     bool requiresSecureConnection = document.frame()->settings().mediaCaptureRequiresSecureConnection();
105     if (requiresSecureConnection && !isSecure(*document.loader())) {
106         errorMessage = "Trying to call getUserMedia from an insecure document.";
107         return false;
108     }
109
110     auto& topDocument = document.topDocument();
111     if (&document != &topDocument) {
112         auto& topOrigin = *topDocument.topOrigin();
113
114         if (!document.securityOrigin().isSameSchemeHostPort(topOrigin)) {
115             errorMessage = "Trying to call getUserMedia from a document with a different security origin than its top-level frame.";
116             return false;
117         }
118
119         for (auto* ancestorDocument = document.parentDocument(); ancestorDocument != &topDocument; ancestorDocument = ancestorDocument->parentDocument()) {
120             if (requiresSecureConnection && !isSecure(*ancestorDocument->loader())) {
121                 errorMessage = "Trying to call getUserMedia from a document with an insecure parent frame.";
122                 return false;
123             }
124
125             if (!ancestorDocument->securityOrigin().isSameSchemeHostPort(topOrigin)) {
126                 errorMessage = "Trying to call getUserMedia from a document with a different security origin than its top-level frame.";
127                 return false;
128             }
129         }
130     }
131     
132     return true;
133 }
134
135 void UserMediaRequest::start()
136 {
137     if (!m_scriptExecutionContext || !m_controller) {
138         deny(MediaAccessDenialReason::OtherFailure, emptyString());
139         return;
140     }
141
142     Document& document = downcast<Document>(*m_scriptExecutionContext);
143
144     // 10.2 - 6.3 Optionally, e.g., based on a previously-established user preference, for security reasons,
145     // or due to platform limitations, jump to the step labeled Permission Failure below.
146     String errorMessage;
147     if (!canCallGetUserMedia(document, errorMessage)) {
148         deny(MediaAccessDenialReason::PermissionDenied, emptyString());
149         document.domWindow()->printErrorMessage(errorMessage);
150         return;
151     }
152
153     m_controller->requestUserMediaAccess(*this);
154 }
155
156 void UserMediaRequest::allow(const String& audioDeviceUID, const String& videoDeviceUID)
157 {
158     m_allowedAudioDeviceUID = audioDeviceUID;
159     m_allowedVideoDeviceUID = videoDeviceUID;
160
161     RefPtr<UserMediaRequest> protectedThis = this;
162     RealtimeMediaSourceCenter::NewMediaStreamHandler callback = [this, protectedThis = WTFMove(protectedThis)](RefPtr<MediaStreamPrivate>&& privateStream) mutable {
163         if (!m_scriptExecutionContext)
164             return;
165
166         if (!privateStream) {
167             deny(MediaAccessDenialReason::HardwareError, emptyString());
168             return;
169         }
170
171         auto stream = MediaStream::create(*m_scriptExecutionContext, WTFMove(privateStream));
172         if (stream->getTracks().isEmpty()) {
173             deny(MediaAccessDenialReason::HardwareError, emptyString());
174             return;
175         }
176
177         for (auto& track : stream->getAudioTracks())
178             track->source().startProducingData();
179
180         for (auto& track : stream->getVideoTracks())
181             track->source().startProducingData();
182         
183         m_promise.resolve(stream);
184     };
185
186     RealtimeMediaSourceCenter::singleton().createMediaStream(WTFMove(callback), m_allowedAudioDeviceUID, m_allowedVideoDeviceUID, &m_audioConstraints.get(), &m_videoConstraints.get());
187 }
188
189 void UserMediaRequest::deny(MediaAccessDenialReason reason, const String& invalidConstraint)
190 {
191     if (!m_scriptExecutionContext)
192         return;
193
194     switch (reason) {
195     case MediaAccessDenialReason::NoConstraints:
196         m_promise.reject(TypeError);
197         break;
198     case MediaAccessDenialReason::UserMediaDisabled:
199         m_promise.reject(SECURITY_ERR);
200         break;
201     case MediaAccessDenialReason::NoCaptureDevices:
202         m_promise.reject(NOT_FOUND_ERR);
203         break;
204     case MediaAccessDenialReason::InvalidConstraint:
205         m_promise.rejectType<IDLInterface<OverconstrainedError>>(OverconstrainedError::create(invalidConstraint, ASCIILiteral("Invalid constraint")).get());
206         break;
207     case MediaAccessDenialReason::HardwareError:
208         m_promise.reject(NotReadableError);
209         break;
210     case MediaAccessDenialReason::OtherFailure:
211         m_promise.reject(ABORT_ERR);
212         break;
213     case MediaAccessDenialReason::PermissionDenied:
214         m_promise.reject(NotAllowedError);
215         break;
216     }
217 }
218
219 void UserMediaRequest::contextDestroyed()
220 {
221     ContextDestructionObserver::contextDestroyed();
222     Ref<UserMediaRequest> protectedThis(*this);
223     if (m_controller) {
224         m_controller->cancelUserMediaAccessRequest(*this);
225         m_controller = nullptr;
226     }
227 }
228
229 Document* UserMediaRequest::document() const
230 {
231     return downcast<Document>(m_scriptExecutionContext);
232 }
233
234 } // namespace WebCore
235
236 #endif // ENABLE(MEDIA_STREAM)