| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "core/frame/DOMWindow.h" |
| |
| #include "core/dom/Document.h" |
| #include "core/dom/ExceptionCode.h" |
| #include "core/dom/ExecutionContext.h" |
| #include "core/dom/SecurityContext.h" |
| #include "core/events/MessageEvent.h" |
| #include "core/frame/External.h" |
| #include "core/frame/Frame.h" |
| #include "core/frame/FrameClient.h" |
| #include "core/frame/FrameConsole.h" |
| #include "core/frame/LocalDOMWindow.h" |
| #include "core/frame/Location.h" |
| #include "core/frame/RemoteDOMWindow.h" |
| #include "core/frame/RemoteFrame.h" |
| #include "core/frame/Settings.h" |
| #include "core/frame/UseCounter.h" |
| #include "core/input/EventHandler.h" |
| #include "core/inspector/ConsoleMessage.h" |
| #include "core/inspector/InspectorInstrumentation.h" |
| #include "core/loader/FrameLoaderClient.h" |
| #include "core/loader/MixedContentChecker.h" |
| #include "core/page/ChromeClient.h" |
| #include "core/page/FocusController.h" |
| #include "core/page/Page.h" |
| #include "platform/weborigin/KURL.h" |
| #include "platform/weborigin/SecurityOrigin.h" |
| #include "platform/weborigin/Suborigin.h" |
| #include <memory> |
| |
| namespace blink { |
| |
| DOMWindow::DOMWindow() : m_windowIsClosing(false) {} |
| |
| DOMWindow::~DOMWindow() {} |
| |
| v8::Local<v8::Object> DOMWindow::wrap(v8::Isolate*, |
| v8::Local<v8::Object> creationContext) { |
| LOG(FATAL) << "DOMWindow must never be wrapped with wrap method. The " |
| "wrappers must be created at WindowProxy::createContext() and " |
| "setupWindowPrototypeChain()."; |
| return v8::Local<v8::Object>(); |
| } |
| |
| v8::Local<v8::Object> DOMWindow::associateWithWrapper( |
| v8::Isolate*, |
| const WrapperTypeInfo*, |
| v8::Local<v8::Object> wrapper) { |
| LOG(FATAL) << "DOMWindow must never be wrapped with wrap method. The " |
| "wrappers must be created at WindowProxy::createContext() and " |
| "setupWindowPrototypeChain()."; |
| return v8::Local<v8::Object>(); |
| } |
| |
| const AtomicString& DOMWindow::interfaceName() const { |
| return EventTargetNames::DOMWindow; |
| } |
| |
| const DOMWindow* DOMWindow::toDOMWindow() const { |
| return this; |
| } |
| |
| Location* DOMWindow::location() const { |
| if (!m_location) |
| m_location = Location::create(frame()); |
| return m_location.get(); |
| } |
| |
| bool DOMWindow::closed() const { |
| return m_windowIsClosing || !frame() || !frame()->host(); |
| } |
| |
| unsigned DOMWindow::length() const { |
| return frame() ? frame()->tree().scopedChildCount() : 0; |
| } |
| |
| v8::Local<v8::Object> DOMWindow::self(ScriptState* scriptState) const { |
| return scriptState->context()->Global(); |
| } |
| |
| DOMWindow* DOMWindow::opener() const { |
| // FIXME: Use FrameTree to get opener as well, to simplify logic here. |
| if (!frame() || !frame()->client()) |
| return nullptr; |
| |
| Frame* opener = frame()->client()->opener(); |
| return opener ? opener->domWindow() : nullptr; |
| } |
| |
| DOMWindow* DOMWindow::parent() const { |
| if (!frame()) |
| return nullptr; |
| |
| Frame* parent = frame()->tree().parent(); |
| return parent ? parent->domWindow() : frame()->domWindow(); |
| } |
| |
| DOMWindow* DOMWindow::top() const { |
| if (!frame()) |
| return nullptr; |
| |
| return frame()->tree().top()->domWindow(); |
| } |
| |
| External* DOMWindow::external() const { |
| DEFINE_STATIC_LOCAL(Persistent<External>, external, (new External)); |
| return external; |
| } |
| |
| DOMWindow* DOMWindow::anonymousIndexedGetter(uint32_t index) const { |
| if (!frame()) |
| return nullptr; |
| |
| Frame* child = frame()->tree().scopedChild(index); |
| return child ? child->domWindow() : nullptr; |
| } |
| |
| bool DOMWindow::isCurrentlyDisplayedInFrame() const { |
| if (frame()) |
| SECURITY_CHECK(frame()->domWindow() == this); |
| return frame() && frame()->host(); |
| } |
| |
| bool DOMWindow::isInsecureScriptAccess(LocalDOMWindow& callingWindow, |
| const String& urlString) { |
| if (!protocolIsJavaScript(urlString)) |
| return false; |
| |
| // If this DOMWindow isn't currently active in the Frame, then there's no |
| // way we should allow the access. |
| if (isCurrentlyDisplayedInFrame()) { |
| // FIXME: Is there some way to eliminate the need for a separate |
| // "callingWindow == this" check? |
| if (&callingWindow == this) |
| return false; |
| |
| // FIXME: The name canAccess seems to be a roundabout way to ask "can |
| // execute script". Can we name the SecurityOrigin function better to make |
| // this more clear? |
| if (callingWindow.document()->getSecurityOrigin()->canAccessCheckSuborigins( |
| frame()->securityContext()->getSecurityOrigin())) |
| return false; |
| } |
| |
| callingWindow.printErrorMessage( |
| crossDomainAccessErrorMessage(&callingWindow)); |
| return true; |
| } |
| |
| void DOMWindow::resetLocation() { |
| // Location needs to be reset manually because it doesn't inherit from |
| // DOMWindowProperty. DOMWindowProperty is local-only, and Location needs to |
| // support remote windows, too. |
| if (m_location) { |
| m_location->reset(); |
| m_location = nullptr; |
| } |
| } |
| |
| bool DOMWindow::isSecureContext() const { |
| if (!frame()) |
| return false; |
| |
| return document()->isSecureContext( |
| ExecutionContext::StandardSecureContextCheck); |
| } |
| |
| void DOMWindow::postMessage(PassRefPtr<SerializedScriptValue> message, |
| const MessagePortArray& ports, |
| const String& targetOrigin, |
| LocalDOMWindow* source, |
| ExceptionState& exceptionState) { |
| if (!isCurrentlyDisplayedInFrame()) |
| return; |
| |
| Document* sourceDocument = source->document(); |
| |
| // Compute the target origin. We need to do this synchronously in order |
| // to generate the SyntaxError exception correctly. |
| RefPtr<SecurityOrigin> target; |
| if (targetOrigin == "/") { |
| if (!sourceDocument) |
| return; |
| target = sourceDocument->getSecurityOrigin(); |
| } else if (targetOrigin != "*") { |
| target = SecurityOrigin::createFromString(targetOrigin); |
| // It doesn't make sense target a postMessage at a unique origin |
| // because there's no way to represent a unique origin in a string. |
| if (target->isUnique()) { |
| exceptionState.throwDOMException( |
| SyntaxError, "Invalid target origin '" + targetOrigin + |
| "' in a call to 'postMessage'."); |
| return; |
| } |
| } |
| |
| std::unique_ptr<MessagePortChannelArray> channels = |
| MessagePort::disentanglePorts(getExecutionContext(), ports, |
| exceptionState); |
| if (exceptionState.hadException()) |
| return; |
| |
| // Capture the source of the message. We need to do this synchronously |
| // in order to capture the source of the message correctly. |
| if (!sourceDocument) |
| return; |
| |
| const SecurityOrigin* securityOrigin = sourceDocument->getSecurityOrigin(); |
| bool hasSuborigin = sourceDocument->getSecurityOrigin()->hasSuborigin(); |
| Suborigin::SuboriginPolicyOptions unsafeSendOpt = |
| Suborigin::SuboriginPolicyOptions::UnsafePostMessageSend; |
| |
| String sourceOrigin = |
| (hasSuborigin && |
| securityOrigin->suborigin()->policyContains(unsafeSendOpt)) |
| ? securityOrigin->toPhysicalOriginString() |
| : securityOrigin->toString(); |
| String sourceSuborigin = |
| hasSuborigin ? securityOrigin->suborigin()->name() : String(); |
| |
| KURL targetUrl = |
| isLocalDOMWindow() |
| ? document()->url() |
| : KURL(KURL(), |
| frame()->securityContext()->getSecurityOrigin()->toString()); |
| if (MixedContentChecker::isMixedContent(sourceDocument->getSecurityOrigin(), |
| targetUrl)) |
| UseCounter::count(frame(), UseCounter::PostMessageFromSecureToInsecure); |
| else if (MixedContentChecker::isMixedContent( |
| frame()->securityContext()->getSecurityOrigin(), |
| sourceDocument->url())) |
| UseCounter::count(frame(), UseCounter::PostMessageFromInsecureToSecure); |
| |
| MessageEvent* event = |
| MessageEvent::create(std::move(channels), std::move(message), |
| sourceOrigin, String(), source, sourceSuborigin); |
| |
| schedulePostMessage(event, std::move(target), sourceDocument); |
| } |
| |
| // FIXME: Once we're throwing exceptions for cross-origin access violations, we |
| // will always sanitize the target frame details, so we can safely combine |
| // 'crossDomainAccessErrorMessage' with this method after considering exactly |
| // which details may be exposed to JavaScript. |
| // |
| // http://crbug.com/17325 |
| String DOMWindow::sanitizedCrossDomainAccessErrorMessage( |
| const LocalDOMWindow* callingWindow) const { |
| if (!callingWindow || !callingWindow->document() || !frame()) |
| return String(); |
| |
| const KURL& callingWindowURL = callingWindow->document()->url(); |
| if (callingWindowURL.isNull()) |
| return String(); |
| |
| const SecurityOrigin* activeOrigin = |
| callingWindow->document()->getSecurityOrigin(); |
| String message = "Blocked a frame with origin \"" + activeOrigin->toString() + |
| "\" from accessing a cross-origin frame."; |
| |
| // FIXME: Evaluate which details from 'crossDomainAccessErrorMessage' may |
| // safely be reported to JavaScript. |
| |
| return message; |
| } |
| |
| String DOMWindow::crossDomainAccessErrorMessage( |
| const LocalDOMWindow* callingWindow) const { |
| if (!callingWindow || !callingWindow->document() || !frame()) |
| return String(); |
| |
| const KURL& callingWindowURL = callingWindow->document()->url(); |
| if (callingWindowURL.isNull()) |
| return String(); |
| |
| // FIXME: This message, and other console messages, have extra newlines. |
| // Should remove them. |
| const SecurityOrigin* activeOrigin = |
| callingWindow->document()->getSecurityOrigin(); |
| const SecurityOrigin* targetOrigin = |
| frame()->securityContext()->getSecurityOrigin(); |
| // It's possible for a remote frame to be same origin with respect to a |
| // local frame, but it must still be treated as a disallowed cross-domain |
| // access. See https://crbug.com/601629. |
| ASSERT(frame()->isRemoteFrame() || |
| !activeOrigin->canAccessCheckSuborigins(targetOrigin)); |
| |
| String message = "Blocked a frame with origin \"" + activeOrigin->toString() + |
| "\" from accessing a frame with origin \"" + |
| targetOrigin->toString() + "\". "; |
| |
| // Sandbox errors: Use the origin of the frames' location, rather than their |
| // actual origin (since we know that at least one will be "null"). |
| KURL activeURL = callingWindow->document()->url(); |
| // TODO(alexmos): RemoteFrames do not have a document, and their URLs |
| // aren't replicated. For now, construct the URL using the replicated |
| // origin for RemoteFrames. If the target frame is remote and sandboxed, |
| // there isn't anything else to show other than "null" for its origin. |
| KURL targetURL = isLocalDOMWindow() ? document()->url() |
| : KURL(KURL(), targetOrigin->toString()); |
| if (frame()->securityContext()->isSandboxed(SandboxOrigin) || |
| callingWindow->document()->isSandboxed(SandboxOrigin)) { |
| message = "Blocked a frame at \"" + |
| SecurityOrigin::create(activeURL)->toString() + |
| "\" from accessing a frame at \"" + |
| SecurityOrigin::create(targetURL)->toString() + "\". "; |
| if (frame()->securityContext()->isSandboxed(SandboxOrigin) && |
| callingWindow->document()->isSandboxed(SandboxOrigin)) |
| return "Sandbox access violation: " + message + |
| " Both frames are sandboxed and lack the \"allow-same-origin\" " |
| "flag."; |
| if (frame()->securityContext()->isSandboxed(SandboxOrigin)) |
| return "Sandbox access violation: " + message + |
| " The frame being accessed is sandboxed and lacks the " |
| "\"allow-same-origin\" flag."; |
| return "Sandbox access violation: " + message + |
| " The frame requesting access is sandboxed and lacks the " |
| "\"allow-same-origin\" flag."; |
| } |
| |
| // Protocol errors: Use the URL's protocol rather than the origin's protocol |
| // so that we get a useful message for non-heirarchal URLs like 'data:'. |
| if (targetOrigin->protocol() != activeOrigin->protocol()) |
| return message + " The frame requesting access has a protocol of \"" + |
| activeURL.protocol() + |
| "\", the frame being accessed has a protocol of \"" + |
| targetURL.protocol() + "\". Protocols must match.\n"; |
| |
| // 'document.domain' errors. |
| if (targetOrigin->domainWasSetInDOM() && activeOrigin->domainWasSetInDOM()) |
| return message + |
| "The frame requesting access set \"document.domain\" to \"" + |
| activeOrigin->domain() + |
| "\", the frame being accessed set it to \"" + |
| targetOrigin->domain() + |
| "\". Both must set \"document.domain\" to the same value to allow " |
| "access."; |
| if (activeOrigin->domainWasSetInDOM()) |
| return message + |
| "The frame requesting access set \"document.domain\" to \"" + |
| activeOrigin->domain() + |
| "\", but the frame being accessed did not. Both must set " |
| "\"document.domain\" to the same value to allow access."; |
| if (targetOrigin->domainWasSetInDOM()) |
| return message + "The frame being accessed set \"document.domain\" to \"" + |
| targetOrigin->domain() + |
| "\", but the frame requesting access did not. Both must set " |
| "\"document.domain\" to the same value to allow access."; |
| |
| // Default. |
| return message + "Protocols, domains, and ports must match."; |
| } |
| |
| void DOMWindow::close(ExecutionContext* context) { |
| if (!frame() || !frame()->isMainFrame()) |
| return; |
| |
| Page* page = frame()->page(); |
| if (!page) |
| return; |
| |
| Document* activeDocument = nullptr; |
| if (context) { |
| ASSERT(isMainThread()); |
| activeDocument = toDocument(context); |
| if (!activeDocument) |
| return; |
| |
| if (!activeDocument->frame() || |
| !activeDocument->frame()->canNavigate(*frame())) |
| return; |
| } |
| |
| Settings* settings = frame()->settings(); |
| bool allowScriptsToCloseWindows = |
| settings && settings->allowScriptsToCloseWindows(); |
| |
| if (!page->openedByDOM() && frame()->client()->backForwardLength() > 1 && |
| !allowScriptsToCloseWindows) { |
| if (activeDocument) { |
| activeDocument->domWindow()->frameConsole()->addMessage( |
| ConsoleMessage::create( |
| JSMessageSource, WarningMessageLevel, |
| "Scripts may close only the windows that were opened by it.")); |
| } |
| return; |
| } |
| |
| if (!frame()->shouldClose()) |
| return; |
| |
| InspectorInstrumentation::NativeBreakpoint nativeBreakpoint(context, "close", |
| true); |
| |
| page->closeSoon(); |
| |
| // So as to make window.closed return the expected result |
| // after window.close(), separately record the to-be-closed |
| // state of this window. Scripts may access window.closed |
| // before the deferred close operation has gone ahead. |
| m_windowIsClosing = true; |
| } |
| |
| void DOMWindow::focus(ExecutionContext* context) { |
| if (!frame()) |
| return; |
| |
| Page* page = frame()->page(); |
| if (!page) |
| return; |
| |
| ASSERT(context); |
| |
| bool allowFocus = context->isWindowInteractionAllowed(); |
| if (allowFocus) { |
| context->consumeWindowInteraction(); |
| } else { |
| ASSERT(isMainThread()); |
| allowFocus = opener() && (opener() != this) && |
| (toDocument(context)->domWindow() == opener()); |
| } |
| |
| // If we're a top level window, bring the window to the front. |
| if (frame()->isMainFrame() && allowFocus) |
| page->chromeClient().focus(); |
| |
| page->focusController().focusDocumentView(frame(), true /* notifyEmbedder */); |
| } |
| |
| DEFINE_TRACE(DOMWindow) { |
| visitor->trace(m_location); |
| EventTargetWithInlineData::trace(visitor); |
| } |
| |
| } // namespace blink |