diff --git a/cli/ops/websocket.rs b/cli/ops/websocket.rs index 23dee5f858..77b2b5f11d 100644 --- a/cli/ops/websocket.rs +++ b/cli/ops/websocket.rs @@ -8,12 +8,12 @@ use deno_core::error::AnyError; use deno_core::futures::future::poll_fn; use deno_core::futures::StreamExt; use deno_core::futures::{ready, SinkExt}; -use deno_core::serde_json; use deno_core::serde_json::json; use deno_core::serde_json::Value; use deno_core::url; use deno_core::BufVec; use deno_core::OpState; +use deno_core::{serde_json, ZeroCopyBuf}; use http::{Method, Request, Uri}; use serde::Deserialize; use std::borrow::Cow; @@ -34,6 +34,7 @@ use tokio_tungstenite::{client_async, WebSocketStream}; use webpki::DNSNameRef; pub fn init(rt: &mut deno_core::JsRuntime) { + super::reg_json_sync(rt, "op_ws_check_permission", op_ws_check_permission); super::reg_json_async(rt, "op_ws_create", op_ws_create); super::reg_json_async(rt, "op_ws_send", op_ws_send); super::reg_json_async(rt, "op_ws_close", op_ws_close); @@ -45,6 +46,29 @@ type MaybeTlsStream = type WsStream = WebSocketStream; +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct CheckPermissionArgs { + url: String, +} + +// This op is needed because creating a WS instance in JavaScript is a sync +// operation and should throw error when permissions are not fullfiled, +// but actual op that connects WS is async. +pub fn op_ws_check_permission( + state: &mut OpState, + args: Value, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let args: CheckPermissionArgs = serde_json::from_value(args)?; + + state + .borrow::() + .check_net_url(&url::Url::parse(&args.url)?)?; + + Ok(json!({})) +} + #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct CreateArgs { @@ -58,11 +82,16 @@ pub async fn op_ws_create( _bufs: BufVec, ) -> Result { let args: CreateArgs = serde_json::from_value(args)?; + { let s = state.borrow(); s.borrow::() - .check_net_url(&url::Url::parse(&args.url)?)?; + .check_net_url(&url::Url::parse(&args.url)?) + .expect( + "Permission check should have been done in op_ws_check_permission", + ); } + let ca_file = { let cli_state = super::global_state2(&state); cli_state.flags.ca_file.clone() diff --git a/cli/rt/27_websocket.js b/cli/rt/27_websocket.js index 675c1e836e..60428c24d5 100644 --- a/cli/rt/27_websocket.js +++ b/cli/rt/27_websocket.js @@ -33,6 +33,10 @@ this.#url = wsURL.href; + core.jsonOpSync("op_ws_check_permission", { + url: this.#url, + }); + if (protocols && typeof protocols === "string") { protocols = [protocols]; } diff --git a/cli/tests/unit/unit_tests.ts b/cli/tests/unit/unit_tests.ts index f545e55fad..6885204a38 100644 --- a/cli/tests/unit/unit_tests.ts +++ b/cli/tests/unit/unit_tests.ts @@ -79,3 +79,4 @@ import "./write_file_test.ts"; import "./write_text_file_test.ts"; import "./performance_test.ts"; import "./version_test.ts"; +import "./websocket_test.ts"; diff --git a/cli/tests/unit/websocket_test.ts b/cli/tests/unit/websocket_test.ts new file mode 100644 index 0000000000..8e5726d447 --- /dev/null +++ b/cli/tests/unit/websocket_test.ts @@ -0,0 +1,9 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { assertThrows, unitTest } from "./test_util.ts"; + +unitTest(function websocketPermissionless() { + assertThrows( + () => new WebSocket("ws://localhost"), + Deno.errors.PermissionDenied, + ); +});