blob: 5caf41a395a2dbea0ec4fc2a06162552797e42e9 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/modules/direct_sockets/socket.h"
#include <utility>
#include "base/notreached.h"
#include "net/base/net_errors.h"
#include "services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom-shared.h"
#include "third_party/blink/public/mojom/frame/lifecycle.mojom-shared.h"
#include "third_party/blink/public/platform/browser_interface_broker_proxy.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
#include "third_party/blink/renderer/core/dom/dom_exception.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/platform/bindings/exception_code.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/scheduler/public/scheduling_policy.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
namespace blink {
namespace {
std::pair<DOMExceptionCode, String>
CreateDOMExceptionCodeAndMessageFromNetErrorCode(int32_t net_error) {
switch (net_error) {
case net::ERR_NAME_NOT_RESOLVED:
return {DOMExceptionCode::kNetworkError,
"Hostname couldn't be resolved."};
case net::ERR_INVALID_URL:
return {DOMExceptionCode::kDataError, "Supplied url is not valid."};
case net::ERR_UNEXPECTED:
return {DOMExceptionCode::kUnknownError, "Unexpected error occured."};
case net::ERR_ACCESS_DENIED:
return {DOMExceptionCode::kInvalidAccessError,
"Access to the requested host or port is blocked."};
case net::ERR_NETWORK_ACCESS_DENIED:
return {DOMExceptionCode::kInvalidAccessError, "Firewall error."};
case net::ERR_BLOCKED_BY_PRIVATE_NETWORK_ACCESS_CHECKS:
return {DOMExceptionCode::kInvalidAccessError,
"Access to private network is blocked."};
default:
return {DOMExceptionCode::kNetworkError, "Network Error."};
}
}
} // namespace
ScriptPromise<IDLUndefined> Socket::closed(ScriptState* script_state) const {
return closed_->Promise(script_state->World());
}
Socket::Socket(ScriptState* script_state)
: ExecutionContextLifecycleStateObserver(
ExecutionContext::From(script_state)),
script_state_(script_state),
service_(GetExecutionContext()),
feature_handle_for_scheduler_(
GetExecutionContext()->GetScheduler()->RegisterFeature(
SchedulingPolicy::Feature::kOutstandingNetworkRequestDirectSocket,
{SchedulingPolicy::DisableBackForwardCache()})),
closed_(MakeGarbageCollected<ScriptPromiseProperty<IDLUndefined, IDLAny>>(
GetExecutionContext())) {
UpdateStateIfNeeded();
GetExecutionContext()->GetBrowserInterfaceBroker().GetInterface(
service_.BindNewPipeAndPassReceiver(
GetExecutionContext()->GetTaskRunner(TaskType::kNetworking)));
service_.set_disconnect_handler(
BindOnce(&Socket::OnServiceConnectionError, WrapPersistent(this)));
// |closed| promise is just one of the ways to learn that the socket state has
// changed. Therefore it's not necessary to force developers to handle
// rejections.
closed_->MarkAsHandled();
}
Socket::~Socket() = default;
// static
bool Socket::CheckContextAndPermissions(ScriptState* script_state,
ExceptionState& exception_state) {
if (!script_state->ContextIsValid()) {
exception_state.ThrowDOMException(DOMExceptionCode::kNotSupportedError,
"Current context is detached.");
return false;
}
ExecutionContext* execution_context = ExecutionContext::From(script_state);
if (execution_context->IsWindow()) {
// TODO(crbug.com/407883159): Replace IsFeatureEnabled() with
// CrossOriginIsolatedCapability() once Chrome Apps are deprecated.
if (!execution_context->IsIsolatedContext() ||
!execution_context->IsFeatureEnabled(
network::mojom::PermissionsPolicyFeature::kCrossOriginIsolated)) {
exception_state.ThrowDOMException(
DOMExceptionCode::kNotAllowedError,
"Frame is not sufficiently isolated to use Direct Sockets.");
return false;
}
if (!execution_context->IsFeatureEnabled(
network::mojom::PermissionsPolicyFeature::kDirectSockets)) {
exception_state.ThrowDOMException(
DOMExceptionCode::kNotAllowedError,
"Permissions-Policy: direct-sockets are disabled.");
return false;
}
return true;
} else if (execution_context->IsWorkerGlobalScope()) {
// TODO(crbug.com/407883159): Replace IsFeatureEnabled() with
// CrossOriginIsolatedCapability() once Chrome Apps are deprecated.
if (!execution_context->IsIsolatedContext() ||
!execution_context->IsFeatureEnabled(
network::mojom::PermissionsPolicyFeature::kCrossOriginIsolated)) {
exception_state.ThrowDOMException(
DOMExceptionCode::kNotAllowedError,
"Frame is not sufficiently isolated to use Direct Sockets.");
return false;
}
return true;
} else {
NOTREACHED();
}
}
// static
DOMException* Socket::CreateDOMExceptionFromNetErrorCode(int32_t net_error) {
auto [code, message] =
CreateDOMExceptionCodeAndMessageFromNetErrorCode(net_error);
return MakeGarbageCollected<DOMException>(code, std::move(message));
}
void Socket::Trace(Visitor* visitor) const {
visitor->Trace(script_state_);
visitor->Trace(service_);
visitor->Trace(closed_);
ExecutionContextLifecycleStateObserver::Trace(visitor);
}
void Socket::ResetServiceAndFeatureHandle() {
feature_handle_for_scheduler_.reset();
service_.reset();
}
// static
protocol::Network::DirectSocketDnsQueryType Socket::MapProbeDnsQueryType(
V8SocketDnsQueryType dns_query_type) {
switch (dns_query_type.AsEnum()) {
case V8SocketDnsQueryType::Enum::kIpv4:
return protocol::Network::DirectSocketDnsQueryTypeEnum::Ipv4;
case V8SocketDnsQueryType::Enum::kIpv6:
return protocol::Network::DirectSocketDnsQueryTypeEnum::Ipv6;
}
}
} // namespace blink