1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-24 15:19:26 -05:00

feat: Add "deno_net" extension (#11150)

This commits moves implementation of net related APIs available on "Deno"
namespace to "deno_net" extension.

Following APIs were moved:
- Deno.listen()
- Deno.connect()
- Deno.listenTls()
- Deno.serveHttp()
- Deno.shutdown()
- Deno.resolveDns()
- Deno.listenDatagram()
- Deno.startTls()
- Deno.Conn
- Deno.Listener
- Deno.DatagramConn
This commit is contained in:
Bartek Iwańczuk 2021-06-29 01:43:03 +02:00 committed by GitHub
parent 30cba24848
commit 38a7128cdd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 1049 additions and 679 deletions

29
Cargo.lock generated
View file

@ -536,6 +536,7 @@ dependencies = [
"deno_doc",
"deno_fetch",
"deno_lint",
"deno_net",
"deno_runtime",
"deno_timers",
"deno_url",
@ -702,17 +703,37 @@ dependencies = [
"swc_ecmascript",
]
[[package]]
name = "deno_net"
version = "0.1.0"
dependencies = [
"bytes",
"deno_core",
"http",
"hyper",
"lazy_static",
"log",
"rustls",
"serde",
"tokio",
"tokio-util",
"trust-dns-proto",
"trust-dns-resolver",
"webpki",
"webpki-roots",
]
[[package]]
name = "deno_runtime"
version = "0.18.0"
dependencies = [
"atty",
"bytes",
"deno_broadcast_channel",
"deno_console",
"deno_core",
"deno_crypto",
"deno_fetch",
"deno_net",
"deno_timers",
"deno_url",
"deno_web",
@ -735,18 +756,12 @@ dependencies = [
"percent-encoding",
"regex",
"ring",
"rustls",
"serde",
"sys-info",
"termcolor",
"test_util",
"tokio",
"tokio-util",
"trust-dns-proto",
"trust-dns-resolver",
"uuid",
"webpki",
"webpki-roots",
"winapi 0.3.9",
"winres",
]

View file

@ -12,6 +12,7 @@ members = [
"extensions/console",
"extensions/crypto",
"extensions/fetch",
"extensions/net",
"extensions/timers",
"extensions/url",
"extensions/web",

View file

@ -25,6 +25,7 @@ deno_console = { version = "0.10.0", path = "../extensions/console" }
deno_core = { version = "0.92.0", path = "../core" }
deno_crypto = { version = "0.24.0", path = "../extensions/crypto" }
deno_fetch = { version = "0.32.0", path = "../extensions/fetch" }
deno_net = { version = "0.1.0", path = "../extensions/net" }
deno_timers = { version = "0.8.0", path = "../extensions/timers" }
deno_url = { version = "0.10.0", path = "../extensions/url" }
deno_web = { version = "0.41.0", path = "../extensions/web" }

View file

@ -68,6 +68,9 @@ fn create_compiler_snapshot(
"deno.broadcast_channel",
deno_broadcast_channel::get_declaration(),
);
op_crate_libs.insert("deno.net", deno_net::get_declaration());
op_crate_libs
.insert("deno.net_unstable", deno_net::get_unstable_declaration());
// ensure we invalidate the build properly.
for (_, path) in op_crate_libs.iter() {
@ -302,6 +305,14 @@ fn main() {
"cargo:rustc-env=DENO_BROADCAST_CHANNEL_LIB_PATH={}",
deno_broadcast_channel::get_declaration().display()
);
println!(
"cargo:rustc-env=DENO_NET_LIB_PATH={}",
deno_net::get_declaration().display()
);
println!(
"cargo:rustc-env=DENO_NET_UNSTABLE_LIB_PATH={}",
deno_net::get_unstable_declaration().display()
);
println!("cargo:rustc-env=TARGET={}", env::var("TARGET").unwrap());
println!("cargo:rustc-env=PROFILE={}", env::var("PROFILE").unwrap());

View file

@ -2,6 +2,7 @@
/// <reference no-default-lib="true" />
/// <reference lib="esnext" />
/// <reference lib="deno.net" />
/** Deno provides extra properties on `import.meta`. These are included here
* to ensure that these are still available when using the Deno namespace in
@ -1784,149 +1785,6 @@ declare namespace Deno {
* Requires `allow-write` permission. */
export function truncate(name: string, len?: number): Promise<void>;
export interface NetAddr {
transport: "tcp" | "udp";
hostname: string;
port: number;
}
export interface UnixAddr {
transport: "unix" | "unixpacket";
path: string;
}
export type Addr = NetAddr | UnixAddr;
/** A generic network listener for stream-oriented protocols. */
export interface Listener extends AsyncIterable<Conn> {
/** Waits for and resolves to the next connection to the `Listener`. */
accept(): Promise<Conn>;
/** Close closes the listener. Any pending accept promises will be rejected
* with errors. */
close(): void;
/** Return the address of the `Listener`. */
readonly addr: Addr;
/** Return the rid of the `Listener`. */
readonly rid: number;
[Symbol.asyncIterator](): AsyncIterableIterator<Conn>;
}
export interface Conn extends Reader, Writer, Closer {
/** The local address of the connection. */
readonly localAddr: Addr;
/** The remote address of the connection. */
readonly remoteAddr: Addr;
/** The resource ID of the connection. */
readonly rid: number;
/** Shuts down (`shutdown(2)`) the write side of the connection. Most
* callers should just use `close()`. */
closeWrite(): Promise<void>;
}
export interface ListenOptions {
/** The port to listen on. */
port: number;
/** A literal IP address or host name that can be resolved to an IP address.
* If not specified, defaults to `0.0.0.0`. */
hostname?: string;
}
/** Listen announces on the local transport address.
*
* ```ts
* const listener1 = Deno.listen({ port: 80 })
* const listener2 = Deno.listen({ hostname: "192.0.2.1", port: 80 })
* const listener3 = Deno.listen({ hostname: "[2001:db8::1]", port: 80 });
* const listener4 = Deno.listen({ hostname: "golang.org", port: 80, transport: "tcp" });
* ```
*
* Requires `allow-net` permission. */
export function listen(
options: ListenOptions & { transport?: "tcp" },
): Listener;
export interface ListenTlsOptions extends ListenOptions {
/** Server certificate file. */
certFile: string;
/** Server public key file. */
keyFile: string;
transport?: "tcp";
}
/** Listen announces on the local transport address over TLS (transport layer
* security).
*
* ```ts
* const lstnr = Deno.listenTls({ port: 443, certFile: "./server.crt", keyFile: "./server.key" });
* ```
*
* Requires `allow-net` permission. */
export function listenTls(options: ListenTlsOptions): Listener;
export interface ConnectOptions {
/** The port to connect to. */
port: number;
/** A literal IP address or host name that can be resolved to an IP address.
* If not specified, defaults to `127.0.0.1`. */
hostname?: string;
transport?: "tcp";
}
/**
* Connects to the hostname (default is "127.0.0.1") and port on the named
* transport (default is "tcp"), and resolves to the connection (`Conn`).
*
* ```ts
* const conn1 = await Deno.connect({ port: 80 });
* const conn2 = await Deno.connect({ hostname: "192.0.2.1", port: 80 });
* const conn3 = await Deno.connect({ hostname: "[2001:db8::1]", port: 80 });
* const conn4 = await Deno.connect({ hostname: "golang.org", port: 80, transport: "tcp" });
* ```
*
* Requires `allow-net` permission for "tcp". */
export function connect(options: ConnectOptions): Promise<Conn>;
export interface ConnectTlsOptions {
/** The port to connect to. */
port: number;
/** A literal IP address or host name that can be resolved to an IP address.
* If not specified, defaults to `127.0.0.1`. */
hostname?: string;
/** Server certificate file. */
certFile?: string;
}
/** Establishes a secure connection over TLS (transport layer security) using
* an optional cert file, hostname (default is "127.0.0.1") and port. The
* cert file is optional and if not included Mozilla's root certificates will
* be used (see also https://github.com/ctz/webpki-roots for specifics)
*
* ```ts
* const conn1 = await Deno.connectTls({ port: 80 });
* const conn2 = await Deno.connectTls({ certFile: "./certs/my_custom_root_CA.pem", hostname: "192.0.2.1", port: 80 });
* const conn3 = await Deno.connectTls({ hostname: "[2001:db8::1]", port: 80 });
* const conn4 = await Deno.connectTls({ certFile: "./certs/my_custom_root_CA.pem", hostname: "golang.org", port: 80});
* ```
*
* Requires `allow-net` permission.
*/
export function connectTls(options: ConnectTlsOptions): Promise<Conn>;
/** Shutdown socket send operations.
*
* Matches behavior of POSIX shutdown(3).
*
* ```ts
* const listener = Deno.listen({ port: 80 });
* const conn = await listener.accept();
* Deno.shutdown(conn.rid);
* ```
*/
export function shutdown(rid: number): Promise<void>;
export interface Metrics {
opsDispatched: number;
opsDispatchedSync: number;

View file

@ -2,6 +2,7 @@
/// <reference no-default-lib="true" />
/// <reference lib="deno.ns" />
/// <reference lib="deno.net_unstable" />
declare namespace Deno {
/**
@ -812,232 +813,6 @@ declare namespace Deno {
mtime: number | Date,
): Promise<void>;
/** The type of the resource record.
* Only the listed types are supported currently. */
export type RecordType =
| "A"
| "AAAA"
| "ANAME"
| "CNAME"
| "MX"
| "PTR"
| "SRV"
| "TXT";
export interface ResolveDnsOptions {
/** The name server to be used for lookups.
* If not specified, defaults to the system configuration e.g. `/etc/resolv.conf` on Unix. */
nameServer?: {
/** The IP address of the name server */
ipAddr: string;
/** The port number the query will be sent to.
* If not specified, defaults to 53. */
port?: number;
};
}
/** If `resolveDns` is called with "MX" record type specified, it will return an array of this interface. */
export interface MXRecord {
preference: number;
exchange: string;
}
/** If `resolveDns` is called with "SRV" record type specified, it will return an array of this interface. */
export interface SRVRecord {
priority: number;
weight: number;
port: number;
target: string;
}
export function resolveDns(
query: string,
recordType: "A" | "AAAA" | "ANAME" | "CNAME" | "PTR",
options?: ResolveDnsOptions,
): Promise<string[]>;
export function resolveDns(
query: string,
recordType: "MX",
options?: ResolveDnsOptions,
): Promise<MXRecord[]>;
export function resolveDns(
query: string,
recordType: "SRV",
options?: ResolveDnsOptions,
): Promise<SRVRecord[]>;
export function resolveDns(
query: string,
recordType: "TXT",
options?: ResolveDnsOptions,
): Promise<string[][]>;
/** ** UNSTABLE**: new API, yet to be vetted.
*
* Performs DNS resolution against the given query, returning resolved records.
* Fails in the cases such as:
* - the query is in invalid format
* - the options have an invalid parameter, e.g. `nameServer.port` is beyond the range of 16-bit unsigned integer
* - timed out
*
* ```ts
* const a = await Deno.resolveDns("example.com", "A");
*
* const aaaa = await Deno.resolveDns("example.com", "AAAA", {
* nameServer: { ipAddr: "8.8.8.8", port: 1234 },
* });
* ```
*
* Requires `allow-net` permission.
*/
export function resolveDns(
query: string,
recordType: RecordType,
options?: ResolveDnsOptions,
): Promise<string[] | MXRecord[] | SRVRecord[] | string[][]>;
/** **UNSTABLE**: new API, yet to be vetted.
*
* A generic transport listener for message-oriented protocols. */
export interface DatagramConn extends AsyncIterable<[Uint8Array, Addr]> {
/** **UNSTABLE**: new API, yet to be vetted.
*
* Waits for and resolves to the next message to the `UDPConn`. */
receive(p?: Uint8Array): Promise<[Uint8Array, Addr]>;
/** UNSTABLE: new API, yet to be vetted.
*
* Sends a message to the target. */
send(p: Uint8Array, addr: Addr): Promise<number>;
/** UNSTABLE: new API, yet to be vetted.
*
* Close closes the socket. Any pending message promises will be rejected
* with errors. */
close(): void;
/** Return the address of the `UDPConn`. */
readonly addr: Addr;
[Symbol.asyncIterator](): AsyncIterableIterator<[Uint8Array, Addr]>;
}
export interface UnixListenOptions {
/** A Path to the Unix Socket. */
path: string;
}
/** **UNSTABLE**: new API, yet to be vetted.
*
* Listen announces on the local transport address.
*
* ```ts
* const listener = Deno.listen({ path: "/foo/bar.sock", transport: "unix" })
* ```
*
* Requires `allow-read` and `allow-write` permission. */
export function listen(
options: UnixListenOptions & { transport: "unix" },
): Listener;
/** **UNSTABLE**: new API, yet to be vetted
*
* Listen announces on the local transport address.
*
* ```ts
* const listener1 = Deno.listenDatagram({
* port: 80,
* transport: "udp"
* });
* const listener2 = Deno.listenDatagram({
* hostname: "golang.org",
* port: 80,
* transport: "udp"
* });
* ```
*
* Requires `allow-net` permission. */
export function listenDatagram(
options: ListenOptions & { transport: "udp" },
): DatagramConn;
/** **UNSTABLE**: new API, yet to be vetted
*
* Listen announces on the local transport address.
*
* ```ts
* const listener = Deno.listenDatagram({
* path: "/foo/bar.sock",
* transport: "unixpacket"
* });
* ```
*
* Requires `allow-read` and `allow-write` permission. */
export function listenDatagram(
options: UnixListenOptions & { transport: "unixpacket" },
): DatagramConn;
export interface UnixConnectOptions {
transport: "unix";
path: string;
}
/** **UNSTABLE**: The unix socket transport is unstable as a new API yet to
* be vetted. The TCP transport is considered stable.
*
* Connects to the hostname (default is "127.0.0.1") and port on the named
* transport (default is "tcp"), and resolves to the connection (`Conn`).
*
* ```ts
* const conn1 = await Deno.connect({ port: 80 });
* const conn2 = await Deno.connect({ hostname: "192.0.2.1", port: 80 });
* const conn3 = await Deno.connect({ hostname: "[2001:db8::1]", port: 80 });
* const conn4 = await Deno.connect({ hostname: "golang.org", port: 80, transport: "tcp" });
* const conn5 = await Deno.connect({ path: "/foo/bar.sock", transport: "unix" });
* ```
*
* Requires `allow-net` permission for "tcp" and `allow-read` for "unix". */
export function connect(
options: ConnectOptions | UnixConnectOptions,
): Promise<Conn>;
export interface StartTlsOptions {
/** A literal IP address or host name that can be resolved to an IP address.
* If not specified, defaults to `127.0.0.1`. */
hostname?: string;
/** Server certificate file. */
certFile?: string;
}
/** **UNSTABLE**: new API, yet to be vetted.
*
* Start TLS handshake from an existing connection using
* an optional cert file, hostname (default is "127.0.0.1"). The
* cert file is optional and if not included Mozilla's root certificates will
* be used (see also https://github.com/ctz/webpki-roots for specifics)
* Using this function requires that the other end of the connection is
* prepared for TLS handshake.
*
* ```ts
* const conn = await Deno.connect({ port: 80, hostname: "127.0.0.1" });
* const tlsConn = await Deno.startTls(conn, { certFile: "./certs/my_custom_root_CA.pem", hostname: "localhost" });
* ```
*
* Requires `allow-net` permission.
*/
export function startTls(
conn: Conn,
options?: StartTlsOptions,
): Promise<Conn>;
export interface ListenTlsOptions {
/** **UNSTABLE**: new API, yet to be vetted.
*
* Application-Layer Protocol Negotiation (ALPN) protocols to announce to
* the client. If not specified, no ALPN extension will be included in the
* TLS handshake.
*/
alpnProtocols?: string[];
}
/** **UNSTABLE**: The `signo` argument may change to require the Deno.Signal
* enum.
*
@ -1182,36 +957,6 @@ declare namespace Deno {
bytesReceived: number;
}
export interface RequestEvent {
readonly request: Request;
respondWith(r: Response | Promise<Response>): Promise<void>;
}
export interface HttpConn extends AsyncIterable<RequestEvent> {
readonly rid: number;
nextRequest(): Promise<RequestEvent | null>;
close(): void;
}
/** **UNSTABLE**: new API, yet to be vetted.
*
* Services HTTP requests given a TCP or TLS socket.
*
* ```ts
* const conn = await Deno.connect({ port: 80, hostname: "127.0.0.1" });
* const httpConn = Deno.serveHttp(conn);
* const e = await httpConn.nextRequest();
* if (e) {
* e.respondWith(new Response("Hello World"));
* }
* ```
*
* If `httpConn.nextRequest()` encounters an error or returns `null`
* then the underlying HttpConn resource is closed automatically.
*/
export function serveHttp(conn: Conn): HttpConn;
/** **UNSTABLE**: New option, yet to be vetted. */
export interface TestDefinition {
/** Specifies the permissions that should be used to run the test.

View file

@ -334,12 +334,14 @@ pub fn get_types(unstable: bool) -> String {
crate::tsc::DENO_WEBSTORAGE_LIB,
crate::tsc::DENO_CRYPTO_LIB,
crate::tsc::DENO_BROADCAST_CHANNEL_LIB,
crate::tsc::DENO_NET_LIB,
crate::tsc::SHARED_GLOBALS_LIB,
crate::tsc::WINDOW_LIB,
];
if unstable {
types.push(crate::tsc::UNSTABLE_NS_LIB);
types.push(crate::tsc::DENO_NET_UNSTABLE_LIB);
}
types.join("\n")

View file

@ -2,9 +2,9 @@
use crate::itest;
use deno_core::url;
use deno_runtime::ops::tls::rustls;
use deno_runtime::ops::tls::webpki;
use deno_runtime::ops::tls::TlsStream;
use deno_runtime::deno_net::ops_tls::rustls;
use deno_runtime::deno_net::ops_tls::webpki;
use deno_runtime::deno_net::ops_tls::TlsStream;
use std::fs;
use std::io::BufReader;
use std::io::Cursor;

View file

@ -44,6 +44,9 @@ pub static DENO_WEBSTORAGE_LIB: &str =
pub static DENO_CRYPTO_LIB: &str = include_str!(env!("DENO_CRYPTO_LIB_PATH"));
pub static DENO_BROADCAST_CHANNEL_LIB: &str =
include_str!(env!("DENO_BROADCAST_CHANNEL_LIB_PATH"));
pub static DENO_NET_LIB: &str = include_str!(env!("DENO_NET_LIB_PATH"));
pub static DENO_NET_UNSTABLE_LIB: &str =
include_str!(env!("DENO_NET_UNSTABLE_LIB_PATH"));
pub static SHARED_GLOBALS_LIB: &str =
include_str!("dts/lib.deno.shared_globals.d.ts");
pub static WINDOW_LIB: &str = include_str!("dts/lib.deno.window.d.ts");

View file

@ -132,6 +132,23 @@
opSync("op_print", str, isErr);
}
// Some "extensions" rely on "BadResource" and "Interrupted" errors in the
// JS code (eg. "deno_net") so they are provided in "Deno.core" but later
// reexported on "Deno.errors"
class BadResource extends Error {
constructor(msg) {
super(msg);
this.name = "BadResource";
}
}
class Interrupted extends Error {
constructor(msg) {
super(msg);
this.name = "Interrupted";
}
}
// Provide bootstrap namespace
window.__bootstrap = {};
// Extra Deno.core.* exports
@ -146,5 +163,7 @@
registerErrorClass,
handleAsyncMsgFromRust,
syncOpsCache,
BadResource,
Interrupted,
});
})(this);

View file

@ -3,11 +3,25 @@
((window) => {
const core = window.Deno.core;
const { errors } = window.__bootstrap.errors;
const { read, write } = window.__bootstrap.io;
const { BadResource } = core;
async function read(
rid,
buffer,
) {
if (buffer.length === 0) {
return 0;
}
const nread = await core.opAsync("op_net_read_async", rid, buffer);
return nread === 0 ? null : nread;
}
async function write(rid, data) {
return await core.opAsync("op_net_write_async", rid, data);
}
function shutdown(rid) {
return core.opAsync("op_shutdown", rid);
return core.opAsync("op_net_shutdown", rid);
}
function opAccept(rid, transport) {
@ -104,7 +118,7 @@
try {
conn = await this.accept();
} catch (error) {
if (error instanceof errors.BadResource) {
if (error instanceof BadResource) {
return { value: undefined, done: true };
}
throw error;
@ -171,7 +185,7 @@
try {
yield await this.receive();
} catch (err) {
if (err instanceof errors.BadResource) {
if (err instanceof BadResource) {
break;
}
throw err;

View file

@ -5,8 +5,8 @@
const { InnerBody } = window.__bootstrap.fetchBody;
const { Response, fromInnerRequest, toInnerResponse, newInnerRequest } =
window.__bootstrap.fetch;
const errors = window.__bootstrap.errors.errors;
const core = window.Deno.core;
const { BadResource, Interrupted } = core;
const { ReadableStream } = window.__bootstrap.streams;
const abortSignal = window.__bootstrap.abortSignal;
@ -42,9 +42,9 @@
// a generic `BadResource` error. Instead store this error and replace
// those with it.
this[connErrorSymbol] = error;
if (error instanceof errors.BadResource) {
if (error instanceof BadResource) {
return null;
} else if (error instanceof errors.Interrupted) {
} else if (error instanceof Interrupted) {
return null;
} else if (error.message.includes("connection closed")) {
return null;
@ -159,7 +159,7 @@
], respBody instanceof Uint8Array ? respBody : null);
} catch (error) {
const connError = httpConn[connErrorSymbol];
if (error instanceof errors.BadResource && connError != null) {
if (error instanceof BadResource && connError != null) {
// deno-lint-ignore no-ex-assign
error = new connError.constructor(connError.message);
}
@ -192,7 +192,7 @@
);
} catch (error) {
const connError = httpConn[connErrorSymbol];
if (error instanceof errors.BadResource && connError != null) {
if (error instanceof BadResource && connError != null) {
// deno-lint-ignore no-ex-assign
error = new connError.constructor(connError.message);
}

31
extensions/net/Cargo.toml Normal file
View file

@ -0,0 +1,31 @@
# Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
[package]
name = "deno_net"
version = "0.1.0"
edition = "2018"
description = "Networking for Deno"
authors = ["the Deno authors"]
license = "MIT"
readme = "README.md"
repository = "https://github.com/denoland/deno"
[lib]
path = "lib.rs"
[dependencies]
deno_core = { version = "0.92.0", path = "../../core" }
bytes = "1"
log = "0.4.14"
lazy_static = "1.4.0"
http = "0.2.3"
hyper = { version = "0.14.9", features = ["server", "stream", "http1", "http2", "runtime"] }
rustls = "0.19.0"
serde = { version = "1.0.125", features = ["derive"] }
tokio = { version = "1.7.1", features = ["full"] }
tokio-util = { version = "0.6", features = ["io"] }
webpki = "0.21.4"
webpki-roots = "0.21.1"
trust-dns-proto = "0.20.3"
trust-dns-resolver = { version = "0.20.3", features = ["tokio-runtime", "serde-config"] }

30
extensions/net/README.md Normal file
View file

@ -0,0 +1,30 @@
# deno_net
This crate implements networking APIs.
This crate depends on following extensions:
- "deno_web"
- "deno_fetch"
Following ops are provided:
- "op_net_read_async"
- "op_net_write_async"
- "op_net_shutdown"
- "op_accept"
- "op_connect"
- "op_listen"
- "op_datagram_receive"
- "op_datagram_send"
- "op_dns_resolve"
- "op_start_tls"
- "op_connect_tls"
- "op_listen_tls"
- "op_accept_tls"
- "op_http_start"
- "op_http_request_next"
- "op_http_request_read"
- "op_http_response"
- "op_http_response_write"
- "op_http_response_close"

232
extensions/net/io.rs Normal file
View file

@ -0,0 +1,232 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use crate::ops_tls as tls;
use deno_core::error::null_opbuf;
use deno_core::error::AnyError;
use deno_core::error::{bad_resource_id, not_supported};
use deno_core::op_async;
use deno_core::AsyncMutFuture;
use deno_core::AsyncRefCell;
use deno_core::CancelHandle;
use deno_core::CancelTryFuture;
use deno_core::OpPair;
use deno_core::OpState;
use deno_core::RcRef;
use deno_core::Resource;
use deno_core::ResourceId;
use deno_core::ZeroCopyBuf;
use std::borrow::Cow;
use std::cell::RefCell;
use std::rc::Rc;
use tokio::io::AsyncRead;
use tokio::io::AsyncReadExt;
use tokio::io::AsyncWrite;
use tokio::io::AsyncWriteExt;
use tokio::net::tcp;
#[cfg(unix)]
use tokio::net::unix;
pub fn init() -> Vec<OpPair> {
vec![
("op_net_read_async", op_async(op_read_async)),
("op_net_write_async", op_async(op_write_async)),
("op_net_shutdown", op_async(op_shutdown)),
]
}
/// A full duplex resource has a read and write ends that are completely
/// independent, like TCP/Unix sockets and TLS streams.
#[derive(Debug)]
pub struct FullDuplexResource<R, W> {
rd: AsyncRefCell<R>,
wr: AsyncRefCell<W>,
// When a full-duplex resource is closed, all pending 'read' ops are
// canceled, while 'write' ops are allowed to complete. Therefore only
// 'read' futures should be attached to this cancel handle.
cancel_handle: CancelHandle,
}
impl<R, W> FullDuplexResource<R, W>
where
R: AsyncRead + Unpin + 'static,
W: AsyncWrite + Unpin + 'static,
{
pub fn new((rd, wr): (R, W)) -> Self {
Self {
rd: rd.into(),
wr: wr.into(),
cancel_handle: Default::default(),
}
}
pub fn into_inner(self) -> (R, W) {
(self.rd.into_inner(), self.wr.into_inner())
}
pub fn rd_borrow_mut(self: &Rc<Self>) -> AsyncMutFuture<R> {
RcRef::map(self, |r| &r.rd).borrow_mut()
}
pub fn wr_borrow_mut(self: &Rc<Self>) -> AsyncMutFuture<W> {
RcRef::map(self, |r| &r.wr).borrow_mut()
}
pub fn cancel_handle(self: &Rc<Self>) -> RcRef<CancelHandle> {
RcRef::map(self, |r| &r.cancel_handle)
}
pub fn cancel_read_ops(&self) {
self.cancel_handle.cancel()
}
pub async fn read(
self: &Rc<Self>,
buf: &mut [u8],
) -> Result<usize, AnyError> {
let mut rd = self.rd_borrow_mut().await;
let nread = rd.read(buf).try_or_cancel(self.cancel_handle()).await?;
Ok(nread)
}
pub async fn write(self: &Rc<Self>, buf: &[u8]) -> Result<usize, AnyError> {
let mut wr = self.wr_borrow_mut().await;
let nwritten = wr.write(buf).await?;
Ok(nwritten)
}
pub async fn shutdown(self: &Rc<Self>) -> Result<(), AnyError> {
let mut wr = self.wr_borrow_mut().await;
wr.shutdown().await?;
Ok(())
}
}
pub type TcpStreamResource =
FullDuplexResource<tcp::OwnedReadHalf, tcp::OwnedWriteHalf>;
impl Resource for TcpStreamResource {
fn name(&self) -> Cow<str> {
"tcpStream".into()
}
fn close(self: Rc<Self>) {
self.cancel_read_ops();
}
}
pub type TlsStreamResource = FullDuplexResource<tls::ReadHalf, tls::WriteHalf>;
impl Resource for TlsStreamResource {
fn name(&self) -> Cow<str> {
"tlsStream".into()
}
fn close(self: Rc<Self>) {
self.cancel_read_ops();
}
}
#[cfg(unix)]
pub type UnixStreamResource =
FullDuplexResource<unix::OwnedReadHalf, unix::OwnedWriteHalf>;
#[cfg(not(unix))]
pub struct UnixStreamResource;
#[cfg(not(unix))]
impl UnixStreamResource {
pub async fn read(
self: &Rc<Self>,
_buf: &mut [u8],
) -> Result<usize, AnyError> {
unreachable!()
}
pub async fn write(self: &Rc<Self>, _buf: &[u8]) -> Result<usize, AnyError> {
unreachable!()
}
pub async fn shutdown(self: &Rc<Self>) -> Result<(), AnyError> {
unreachable!()
}
pub fn cancel_read_ops(&self) {
unreachable!()
}
}
impl Resource for UnixStreamResource {
fn name(&self) -> Cow<str> {
"unixStream".into()
}
fn close(self: Rc<Self>) {
self.cancel_read_ops();
}
}
async fn op_read_async(
state: Rc<RefCell<OpState>>,
rid: ResourceId,
buf: Option<ZeroCopyBuf>,
) -> Result<u32, AnyError> {
let buf = &mut buf.ok_or_else(null_opbuf)?;
let resource = state
.borrow()
.resource_table
.get_any(rid)
.ok_or_else(bad_resource_id)?;
let nread = if let Some(s) = resource.downcast_rc::<TcpStreamResource>() {
s.read(buf).await?
} else if let Some(s) = resource.downcast_rc::<TlsStreamResource>() {
s.read(buf).await?
} else if let Some(s) = resource.downcast_rc::<UnixStreamResource>() {
s.read(buf).await?
} else {
return Err(not_supported());
};
Ok(nread as u32)
}
async fn op_write_async(
state: Rc<RefCell<OpState>>,
rid: ResourceId,
buf: Option<ZeroCopyBuf>,
) -> Result<u32, AnyError> {
let buf = &buf.ok_or_else(null_opbuf)?;
let resource = state
.borrow()
.resource_table
.get_any(rid)
.ok_or_else(bad_resource_id)?;
let nwritten = if let Some(s) = resource.downcast_rc::<TcpStreamResource>() {
s.write(buf).await?
} else if let Some(s) = resource.downcast_rc::<TlsStreamResource>() {
s.write(buf).await?
} else if let Some(s) = resource.downcast_rc::<UnixStreamResource>() {
s.write(buf).await?
} else {
return Err(not_supported());
};
Ok(nwritten as u32)
}
async fn op_shutdown(
state: Rc<RefCell<OpState>>,
rid: ResourceId,
_: (),
) -> Result<(), AnyError> {
let resource = state
.borrow()
.resource_table
.get_any(rid)
.ok_or_else(bad_resource_id)?;
if let Some(s) = resource.downcast_rc::<TcpStreamResource>() {
s.shutdown().await?;
} else if let Some(s) = resource.downcast_rc::<TlsStreamResource>() {
s.shutdown().await?;
} else if let Some(s) = resource.downcast_rc::<UnixStreamResource>() {
s.shutdown().await?;
} else {
return Err(not_supported());
}
Ok(())
}

149
extensions/net/lib.deno_net.d.ts vendored Normal file
View file

@ -0,0 +1,149 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
/// <reference no-default-lib="true" />
/// <reference lib="esnext" />
declare namespace Deno {
export interface NetAddr {
transport: "tcp" | "udp";
hostname: string;
port: number;
}
export interface UnixAddr {
transport: "unix" | "unixpacket";
path: string;
}
export type Addr = NetAddr | UnixAddr;
/** A generic network listener for stream-oriented protocols. */
export interface Listener extends AsyncIterable<Conn> {
/** Waits for and resolves to the next connection to the `Listener`. */
accept(): Promise<Conn>;
/** Close closes the listener. Any pending accept promises will be rejected
* with errors. */
close(): void;
/** Return the address of the `Listener`. */
readonly addr: Addr;
/** Return the rid of the `Listener`. */
readonly rid: number;
[Symbol.asyncIterator](): AsyncIterableIterator<Conn>;
}
export interface Conn extends Reader, Writer, Closer {
/** The local address of the connection. */
readonly localAddr: Addr;
/** The remote address of the connection. */
readonly remoteAddr: Addr;
/** The resource ID of the connection. */
readonly rid: number;
/** Shuts down (`shutdown(2)`) the write side of the connection. Most
* callers should just use `close()`. */
closeWrite(): Promise<void>;
}
export interface ListenOptions {
/** The port to listen on. */
port: number;
/** A literal IP address or host name that can be resolved to an IP address.
* If not specified, defaults to `0.0.0.0`. */
hostname?: string;
}
/** Listen announces on the local transport address.
*
* ```ts
* const listener1 = Deno.listen({ port: 80 })
* const listener2 = Deno.listen({ hostname: "192.0.2.1", port: 80 })
* const listener3 = Deno.listen({ hostname: "[2001:db8::1]", port: 80 });
* const listener4 = Deno.listen({ hostname: "golang.org", port: 80, transport: "tcp" });
* ```
*
* Requires `allow-net` permission. */
export function listen(
options: ListenOptions & { transport?: "tcp" },
): Listener;
export interface ListenTlsOptions extends ListenOptions {
/** Server certificate file. */
certFile: string;
/** Server public key file. */
keyFile: string;
transport?: "tcp";
}
/** Listen announces on the local transport address over TLS (transport layer
* security).
*
* ```ts
* const lstnr = Deno.listenTls({ port: 443, certFile: "./server.crt", keyFile: "./server.key" });
* ```
*
* Requires `allow-net` permission. */
export function listenTls(options: ListenTlsOptions): Listener;
export interface ConnectOptions {
/** The port to connect to. */
port: number;
/** A literal IP address or host name that can be resolved to an IP address.
* If not specified, defaults to `127.0.0.1`. */
hostname?: string;
transport?: "tcp";
}
/**
* Connects to the hostname (default is "127.0.0.1") and port on the named
* transport (default is "tcp"), and resolves to the connection (`Conn`).
*
* ```ts
* const conn1 = await Deno.connect({ port: 80 });
* const conn2 = await Deno.connect({ hostname: "192.0.2.1", port: 80 });
* const conn3 = await Deno.connect({ hostname: "[2001:db8::1]", port: 80 });
* const conn4 = await Deno.connect({ hostname: "golang.org", port: 80, transport: "tcp" });
* ```
*
* Requires `allow-net` permission for "tcp". */
export function connect(options: ConnectOptions): Promise<Conn>;
export interface ConnectTlsOptions {
/** The port to connect to. */
port: number;
/** A literal IP address or host name that can be resolved to an IP address.
* If not specified, defaults to `127.0.0.1`. */
hostname?: string;
/** Server certificate file. */
certFile?: string;
}
/** Establishes a secure connection over TLS (transport layer security) using
* an optional cert file, hostname (default is "127.0.0.1") and port. The
* cert file is optional and if not included Mozilla's root certificates will
* be used (see also https://github.com/ctz/webpki-roots for specifics)
*
* ```ts
* const conn1 = await Deno.connectTls({ port: 80 });
* const conn2 = await Deno.connectTls({ certFile: "./certs/my_custom_root_CA.pem", hostname: "192.0.2.1", port: 80 });
* const conn3 = await Deno.connectTls({ hostname: "[2001:db8::1]", port: 80 });
* const conn4 = await Deno.connectTls({ certFile: "./certs/my_custom_root_CA.pem", hostname: "golang.org", port: 80});
* ```
*
* Requires `allow-net` permission.
*/
export function connectTls(options: ConnectTlsOptions): Promise<Conn>;
/** Shutdown socket send operations.
*
* Matches behavior of POSIX shutdown(3).
*
* ```ts
* const listener = Deno.listen({ port: 80 });
* const conn = await listener.accept();
* Deno.shutdown(conn.rid);
* ```
*/
export function shutdown(rid: number): Promise<void>;
}

View file

@ -0,0 +1,262 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
/// <reference no-default-lib="true" />
/// <reference lib="esnext" />
declare namespace Deno {
/** The type of the resource record.
* Only the listed types are supported currently. */
export type RecordType =
| "A"
| "AAAA"
| "ANAME"
| "CNAME"
| "MX"
| "PTR"
| "SRV"
| "TXT";
export interface ResolveDnsOptions {
/** The name server to be used for lookups.
* If not specified, defaults to the system configuration e.g. `/etc/resolv.conf` on Unix. */
nameServer?: {
/** The IP address of the name server */
ipAddr: string;
/** The port number the query will be sent to.
* If not specified, defaults to 53. */
port?: number;
};
}
/** If `resolveDns` is called with "MX" record type specified, it will return an array of this interface. */
export interface MXRecord {
preference: number;
exchange: string;
}
/** If `resolveDns` is called with "SRV" record type specified, it will return an array of this interface. */
export interface SRVRecord {
priority: number;
weight: number;
port: number;
target: string;
}
export function resolveDns(
query: string,
recordType: "A" | "AAAA" | "ANAME" | "CNAME" | "PTR",
options?: ResolveDnsOptions,
): Promise<string[]>;
export function resolveDns(
query: string,
recordType: "MX",
options?: ResolveDnsOptions,
): Promise<MXRecord[]>;
export function resolveDns(
query: string,
recordType: "SRV",
options?: ResolveDnsOptions,
): Promise<SRVRecord[]>;
export function resolveDns(
query: string,
recordType: "TXT",
options?: ResolveDnsOptions,
): Promise<string[][]>;
/** ** UNSTABLE**: new API, yet to be vetted.
*
* Performs DNS resolution against the given query, returning resolved records.
* Fails in the cases such as:
* - the query is in invalid format
* - the options have an invalid parameter, e.g. `nameServer.port` is beyond the range of 16-bit unsigned integer
* - timed out
*
* ```ts
* const a = await Deno.resolveDns("example.com", "A");
*
* const aaaa = await Deno.resolveDns("example.com", "AAAA", {
* nameServer: { ipAddr: "8.8.8.8", port: 1234 },
* });
* ```
*
* Requires `allow-net` permission.
*/
export function resolveDns(
query: string,
recordType: RecordType,
options?: ResolveDnsOptions,
): Promise<string[] | MXRecord[] | SRVRecord[] | string[][]>;
/** **UNSTABLE**: new API, yet to be vetted.
*
* A generic transport listener for message-oriented protocols. */
export interface DatagramConn extends AsyncIterable<[Uint8Array, Addr]> {
/** **UNSTABLE**: new API, yet to be vetted.
*
* Waits for and resolves to the next message to the `UDPConn`. */
receive(p?: Uint8Array): Promise<[Uint8Array, Addr]>;
/** UNSTABLE: new API, yet to be vetted.
*
* Sends a message to the target. */
send(p: Uint8Array, addr: Addr): Promise<number>;
/** UNSTABLE: new API, yet to be vetted.
*
* Close closes the socket. Any pending message promises will be rejected
* with errors. */
close(): void;
/** Return the address of the `UDPConn`. */
readonly addr: Addr;
[Symbol.asyncIterator](): AsyncIterableIterator<[Uint8Array, Addr]>;
}
export interface UnixListenOptions {
/** A Path to the Unix Socket. */
path: string;
}
/** **UNSTABLE**: new API, yet to be vetted.
*
* Listen announces on the local transport address.
*
* ```ts
* const listener = Deno.listen({ path: "/foo/bar.sock", transport: "unix" })
* ```
*
* Requires `allow-read` and `allow-write` permission. */
export function listen(
options: UnixListenOptions & { transport: "unix" },
): Listener;
/** **UNSTABLE**: new API, yet to be vetted
*
* Listen announces on the local transport address.
*
* ```ts
* const listener1 = Deno.listenDatagram({
* port: 80,
* transport: "udp"
* });
* const listener2 = Deno.listenDatagram({
* hostname: "golang.org",
* port: 80,
* transport: "udp"
* });
* ```
*
* Requires `allow-net` permission. */
export function listenDatagram(
options: ListenOptions & { transport: "udp" },
): DatagramConn;
/** **UNSTABLE**: new API, yet to be vetted
*
* Listen announces on the local transport address.
*
* ```ts
* const listener = Deno.listenDatagram({
* path: "/foo/bar.sock",
* transport: "unixpacket"
* });
* ```
*
* Requires `allow-read` and `allow-write` permission. */
export function listenDatagram(
options: UnixListenOptions & { transport: "unixpacket" },
): DatagramConn;
export interface UnixConnectOptions {
transport: "unix";
path: string;
}
/** **UNSTABLE**: The unix socket transport is unstable as a new API yet to
* be vetted. The TCP transport is considered stable.
*
* Connects to the hostname (default is "127.0.0.1") and port on the named
* transport (default is "tcp"), and resolves to the connection (`Conn`).
*
* ```ts
* const conn1 = await Deno.connect({ port: 80 });
* const conn2 = await Deno.connect({ hostname: "192.0.2.1", port: 80 });
* const conn3 = await Deno.connect({ hostname: "[2001:db8::1]", port: 80 });
* const conn4 = await Deno.connect({ hostname: "golang.org", port: 80, transport: "tcp" });
* const conn5 = await Deno.connect({ path: "/foo/bar.sock", transport: "unix" });
* ```
*
* Requires `allow-net` permission for "tcp" and `allow-read` for "unix". */
export function connect(
options: ConnectOptions | UnixConnectOptions,
): Promise<Conn>;
export interface StartTlsOptions {
/** A literal IP address or host name that can be resolved to an IP address.
* If not specified, defaults to `127.0.0.1`. */
hostname?: string;
/** Server certificate file. */
certFile?: string;
}
/** **UNSTABLE**: new API, yet to be vetted.
*
* Start TLS handshake from an existing connection using
* an optional cert file, hostname (default is "127.0.0.1"). The
* cert file is optional and if not included Mozilla's root certificates will
* be used (see also https://github.com/ctz/webpki-roots for specifics)
* Using this function requires that the other end of the connection is
* prepared for TLS handshake.
*
* ```ts
* const conn = await Deno.connect({ port: 80, hostname: "127.0.0.1" });
* const tlsConn = await Deno.startTls(conn, { certFile: "./certs/my_custom_root_CA.pem", hostname: "localhost" });
* ```
*
* Requires `allow-net` permission.
*/
export function startTls(
conn: Conn,
options?: StartTlsOptions,
): Promise<Conn>;
export interface ListenTlsOptions {
/** **UNSTABLE**: new API, yet to be vetted.
*
* Application-Layer Protocol Negotiation (ALPN) protocols to announce to
* the client. If not specified, no ALPN extension will be included in the
* TLS handshake.
*/
alpnProtocols?: string[];
}
export interface RequestEvent {
readonly request: Request;
respondWith(r: Response | Promise<Response>): Promise<void>;
}
export interface HttpConn extends AsyncIterable<RequestEvent> {
readonly rid: number;
nextRequest(): Promise<RequestEvent | null>;
close(): void;
}
/** **UNSTABLE**: new API, yet to be vetted.
*
* Services HTTP requests given a TCP or TLS socket.
*
* ```ts
* const conn = await Deno.connect({ port: 80, hostname: "127.0.0.1" });
* const httpConn = Deno.serveHttp(conn);
* const e = await httpConn.nextRequest();
* if (e) {
* e.respondWith(new Response("Hello World"));
* }
* ```
*
* If `httpConn.nextRequest()` encounters an error or returns `null`
* then the underlying HttpConn resource is closed automatically.
*/
export function serveHttp(conn: Conn): HttpConn;
}

113
extensions/net/lib.rs Normal file
View file

@ -0,0 +1,113 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
pub mod io;
pub mod ops;
pub mod ops_http;
pub mod ops_tls;
#[cfg(unix)]
pub mod ops_unix;
pub mod resolve_addr;
use deno_core::error::AnyError;
use deno_core::include_js_files;
use deno_core::Extension;
use deno_core::OpState;
use std::cell::RefCell;
use std::path::Path;
use std::path::PathBuf;
use std::rc::Rc;
pub trait NetPermissions {
fn check_net<T: AsRef<str>>(
&mut self,
_host: &(T, Option<u16>),
) -> Result<(), AnyError>;
fn check_read(&mut self, _p: &Path) -> Result<(), AnyError>;
fn check_write(&mut self, _p: &Path) -> Result<(), AnyError>;
}
/// For use with this crate when the user does not want permission checks.
pub struct NoNetPermissions;
impl NetPermissions for NoNetPermissions {
fn check_net<T: AsRef<str>>(
&mut self,
_host: &(T, Option<u16>),
) -> Result<(), AnyError> {
Ok(())
}
fn check_read(&mut self, _p: &Path) -> Result<(), AnyError> {
Ok(())
}
fn check_write(&mut self, _p: &Path) -> Result<(), AnyError> {
Ok(())
}
}
/// `UnstableChecker` is a struct so it can be placed inside `GothamState`;
/// using type alias for a bool could work, but there's a high chance
/// that there might be another type alias pointing to a bool, which
/// would override previously used alias.
pub struct UnstableChecker {
pub unstable: bool,
}
impl UnstableChecker {
/// Quits the process if the --unstable flag was not provided.
///
/// This is intentionally a non-recoverable check so that people cannot probe
/// for unstable APIs from stable programs.
// NOTE(bartlomieju): keep in sync with `cli/program_state.rs`
pub fn check_unstable(&self, api_name: &str) {
if !self.unstable {
eprintln!(
"Unstable API '{}'. The --unstable flag must be provided.",
api_name
);
std::process::exit(70);
}
}
}
/// Helper for checking unstable features. Used for sync ops.
pub fn check_unstable(state: &OpState, api_name: &str) {
state.borrow::<UnstableChecker>().check_unstable(api_name)
}
/// Helper for checking unstable features. Used for async ops.
pub fn check_unstable2(state: &Rc<RefCell<OpState>>, api_name: &str) {
let state = state.borrow();
state.borrow::<UnstableChecker>().check_unstable(api_name)
}
pub fn get_declaration() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("lib.deno_net.d.ts")
}
pub fn get_unstable_declaration() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("lib.deno_net.unstable.d.ts")
}
pub fn init<P: NetPermissions + 'static>(unstable: bool) -> Extension {
let mut ops_to_register = vec![];
ops_to_register.extend(io::init());
ops_to_register.extend(ops::init::<P>());
ops_to_register.extend(ops_tls::init::<P>());
ops_to_register.extend(ops_http::init());
Extension::builder()
.js(include_js_files!(
prefix "deno:extensions/net",
"01_net.js",
"02_tls.js",
"03_http.js",
"04_net_unstable.js",
))
.ops(ops_to_register)
.state(move |state| {
state.put(UnstableChecker { unstable });
Ok(())
})
.build()
}

View file

@ -1,8 +1,9 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use crate::ops::io::TcpStreamResource;
use crate::permissions::Permissions;
use crate::io::TcpStreamResource;
use crate::resolve_addr::resolve_addr;
use crate::resolve_addr::resolve_addr_sync;
use crate::NetPermissions;
use deno_core::error::bad_resource;
use deno_core::error::custom_error;
use deno_core::error::generic_error;
@ -14,7 +15,7 @@ use deno_core::op_sync;
use deno_core::AsyncRefCell;
use deno_core::CancelHandle;
use deno_core::CancelTryFuture;
use deno_core::Extension;
use deno_core::OpPair;
use deno_core::OpState;
use deno_core::RcRef;
use deno_core::Resource;
@ -39,23 +40,21 @@ use trust_dns_resolver::system_conf;
use trust_dns_resolver::AsyncResolver;
#[cfg(unix)]
use super::net_unix;
use super::ops_unix as net_unix;
#[cfg(unix)]
use crate::ops::io::UnixStreamResource;
use crate::io::UnixStreamResource;
#[cfg(unix)]
use std::path::Path;
pub fn init() -> Extension {
Extension::builder()
.ops(vec![
pub fn init<P: NetPermissions + 'static>() -> Vec<OpPair> {
vec![
("op_accept", op_async(op_accept)),
("op_connect", op_async(op_connect)),
("op_listen", op_sync(op_listen)),
("op_connect", op_async(op_connect::<P>)),
("op_listen", op_sync(op_listen::<P>)),
("op_datagram_receive", op_async(op_datagram_receive)),
("op_datagram_send", op_async(op_datagram_send)),
("op_dns_resolve", op_async(op_dns_resolve)),
])
.build()
("op_datagram_send", op_async(op_datagram_send::<P>)),
("op_dns_resolve", op_async(op_dns_resolve::<P>)),
]
}
#[derive(Serialize)]
@ -216,11 +215,14 @@ struct SendArgs {
transport_args: ArgsEnum,
}
async fn op_datagram_send(
async fn op_datagram_send<NP>(
state: Rc<RefCell<OpState>>,
args: SendArgs,
zero_copy: Option<ZeroCopyBuf>,
) -> Result<usize, AnyError> {
) -> Result<usize, AnyError>
where
NP: NetPermissions + 'static,
{
let zero_copy = zero_copy.ok_or_else(null_opbuf)?;
let zero_copy = zero_copy.clone();
@ -232,9 +234,8 @@ async fn op_datagram_send(
} if transport == "udp" => {
{
let mut s = state.borrow_mut();
s.borrow_mut::<Permissions>()
.net
.check(&(&args.hostname, Some(args.port)))?;
s.borrow_mut::<NP>()
.check_net(&(&args.hostname, Some(args.port)))?;
}
let addr = resolve_addr(&args.hostname, args.port)
.await?
@ -259,7 +260,7 @@ async fn op_datagram_send(
let address_path = Path::new(&args.path);
{
let mut s = state.borrow_mut();
s.borrow_mut::<Permissions>().write.check(&address_path)?;
s.borrow_mut::<NP>().check_write(&address_path)?;
}
let resource = state
.borrow()
@ -285,11 +286,14 @@ struct ConnectArgs {
transport_args: ArgsEnum,
}
async fn op_connect(
async fn op_connect<NP>(
state: Rc<RefCell<OpState>>,
args: ConnectArgs,
_: (),
) -> Result<OpConn, AnyError> {
) -> Result<OpConn, AnyError>
where
NP: NetPermissions + 'static,
{
match args {
ConnectArgs {
transport,
@ -298,9 +302,8 @@ async fn op_connect(
{
let mut state_ = state.borrow_mut();
state_
.borrow_mut::<Permissions>()
.net
.check(&(&args.hostname, Some(args.port)))?;
.borrow_mut::<NP>()
.check_net(&(&args.hostname, Some(args.port)))?;
}
let addr = resolve_addr(&args.hostname, args.port)
.await?
@ -335,14 +338,8 @@ async fn op_connect(
super::check_unstable2(&state, "Deno.connect");
{
let mut state_ = state.borrow_mut();
state_
.borrow_mut::<Permissions>()
.read
.check(&address_path)?;
state_
.borrow_mut::<Permissions>()
.write
.check(&address_path)?;
state_.borrow_mut::<NP>().check_read(&address_path)?;
state_.borrow_mut::<NP>().check_write(&address_path)?;
}
let path = args.path;
let unix_stream = net_unix::UnixStream::connect(Path::new(&path)).await?;
@ -451,11 +448,14 @@ fn listen_udp(
Ok((rid, local_addr))
}
fn op_listen(
fn op_listen<NP>(
state: &mut OpState,
args: ListenArgs,
_: (),
) -> Result<OpConn, AnyError> {
) -> Result<OpConn, AnyError>
where
NP: NetPermissions + 'static,
{
match args {
ListenArgs {
transport,
@ -466,9 +466,8 @@ fn op_listen(
super::check_unstable(state, "Deno.listenDatagram");
}
state
.borrow_mut::<Permissions>()
.net
.check(&(&args.hostname, Some(args.port)))?;
.borrow_mut::<NP>()
.check_net(&(&args.hostname, Some(args.port)))?;
}
let addr = resolve_addr_sync(&args.hostname, args.port)?
.next()
@ -512,9 +511,9 @@ fn op_listen(
if transport == "unixpacket" {
super::check_unstable(state, "Deno.listenDatagram");
}
let permissions = state.borrow_mut::<Permissions>();
permissions.read.check(&address_path)?;
permissions.write.check(&address_path)?;
let permissions = state.borrow_mut::<NP>();
permissions.check_read(&address_path)?;
permissions.check_write(&address_path)?;
}
let (rid, local_addr) = if transport == "unix" {
net_unix::listen_unix(state, &address_path)?
@ -592,11 +591,14 @@ pub struct NameServer {
port: u16,
}
async fn op_dns_resolve(
async fn op_dns_resolve<NP>(
state: Rc<RefCell<OpState>>,
args: ResolveAddrArgs,
_: (),
) -> Result<Vec<DnsReturnRecord>, AnyError> {
) -> Result<Vec<DnsReturnRecord>, AnyError>
where
NP: NetPermissions + 'static,
{
let ResolveAddrArgs {
query,
record_type,
@ -621,14 +623,14 @@ async fn op_dns_resolve(
{
let mut s = state.borrow_mut();
let perm = s.borrow_mut::<Permissions>();
let perm = s.borrow_mut::<NP>();
// Checks permission against the name servers which will be actually queried.
for ns in config.name_servers() {
let socker_addr = &ns.socket_addr;
let ip = socker_addr.ip().to_string();
let port = socker_addr.port();
perm.net.check(&(ip, Some(port)))?;
perm.check_net(&(ip, Some(port)))?;
}
}

View file

@ -1,8 +1,8 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use crate::ops::io::TcpStreamResource;
use crate::ops::io::TlsStreamResource;
use crate::ops::tls::TlsStream;
use crate::io::TcpStreamResource;
use crate::io::TlsStreamResource;
use crate::ops_tls::TlsStream;
use deno_core::error::bad_resource_id;
use deno_core::error::null_opbuf;
use deno_core::error::type_error;
@ -17,7 +17,7 @@ use deno_core::AsyncRefCell;
use deno_core::ByteString;
use deno_core::CancelHandle;
use deno_core::CancelTryFuture;
use deno_core::Extension;
use deno_core::OpPair;
use deno_core::OpState;
use deno_core::RcRef;
use deno_core::Resource;
@ -46,17 +46,15 @@ use tokio::net::TcpStream;
use tokio::sync::oneshot;
use tokio_util::io::StreamReader;
pub fn init() -> Extension {
Extension::builder()
.ops(vec![
pub fn init() -> Vec<OpPair> {
vec![
("op_http_start", op_sync(op_http_start)),
("op_http_request_next", op_async(op_http_request_next)),
("op_http_request_read", op_async(op_http_request_read)),
("op_http_response", op_async(op_http_response)),
("op_http_response_write", op_async(op_http_response_write)),
("op_http_response_close", op_async(op_http_response_close)),
])
.build()
]
}
struct ServiceInner {

View file

@ -3,14 +3,14 @@
pub use rustls;
pub use webpki;
use crate::ops::io::TcpStreamResource;
use crate::ops::io::TlsStreamResource;
use crate::ops::net::IpAddr;
use crate::ops::net::OpAddr;
use crate::ops::net::OpConn;
use crate::permissions::Permissions;
use crate::io::TcpStreamResource;
use crate::io::TlsStreamResource;
use crate::ops::IpAddr;
use crate::ops::OpAddr;
use crate::ops::OpConn;
use crate::resolve_addr::resolve_addr;
use crate::resolve_addr::resolve_addr_sync;
use crate::NetPermissions;
use deno_core::error::bad_resource;
use deno_core::error::bad_resource_id;
use deno_core::error::custom_error;
@ -31,7 +31,7 @@ use deno_core::op_sync;
use deno_core::AsyncRefCell;
use deno_core::CancelHandle;
use deno_core::CancelTryFuture;
use deno_core::Extension;
use deno_core::OpPair;
use deno_core::OpState;
use deno_core::RcRef;
use deno_core::Resource;
@ -662,15 +662,13 @@ impl Write for ImplementWriteTrait<'_, TcpStream> {
}
}
pub fn init() -> Extension {
Extension::builder()
.ops(vec![
("op_start_tls", op_async(op_start_tls)),
("op_connect_tls", op_async(op_connect_tls)),
("op_listen_tls", op_sync(op_listen_tls)),
pub fn init<P: NetPermissions + 'static>() -> Vec<OpPair> {
vec![
("op_start_tls", op_async(op_start_tls::<P>)),
("op_connect_tls", op_async(op_connect_tls::<P>)),
("op_listen_tls", op_sync(op_listen_tls::<P>)),
("op_accept_tls", op_async(op_accept_tls)),
])
.build()
]
}
#[derive(Deserialize)]
@ -690,11 +688,14 @@ struct StartTlsArgs {
hostname: String,
}
async fn op_start_tls(
async fn op_start_tls<NP>(
state: Rc<RefCell<OpState>>,
args: StartTlsArgs,
_: (),
) -> Result<OpConn, AnyError> {
) -> Result<OpConn, AnyError>
where
NP: NetPermissions + 'static,
{
let rid = args.rid;
let hostname = match &*args.hostname {
"" => "localhost",
@ -705,10 +706,10 @@ async fn op_start_tls(
{
super::check_unstable2(&state, "Deno.startTls");
let mut s = state.borrow_mut();
let permissions = s.borrow_mut::<Permissions>();
permissions.net.check(&(hostname, Some(0)))?;
let permissions = s.borrow_mut::<NP>();
permissions.check_net(&(hostname, Some(0)))?;
if let Some(path) = cert_file {
permissions.read.check(Path::new(path))?;
permissions.check_read(Path::new(path))?;
}
}
@ -763,11 +764,14 @@ async fn op_start_tls(
})
}
async fn op_connect_tls(
async fn op_connect_tls<NP>(
state: Rc<RefCell<OpState>>,
args: ConnectTlsArgs,
_: (),
) -> Result<OpConn, AnyError> {
) -> Result<OpConn, AnyError>
where
NP: NetPermissions + 'static,
{
assert_eq!(args.transport, "tcp");
let hostname = match &*args.hostname {
"" => "localhost",
@ -778,10 +782,10 @@ async fn op_connect_tls(
{
let mut s = state.borrow_mut();
let permissions = s.borrow_mut::<Permissions>();
permissions.net.check(&(hostname, Some(port)))?;
let permissions = s.borrow_mut::<NP>();
permissions.check_net(&(hostname, Some(port)))?;
if let Some(path) = cert_file {
permissions.read.check(Path::new(path))?;
permissions.check_read(Path::new(path))?;
}
}
@ -912,11 +916,14 @@ pub struct ListenTlsArgs {
alpn_protocols: Option<Vec<String>>,
}
fn op_listen_tls(
fn op_listen_tls<NP>(
state: &mut OpState,
args: ListenTlsArgs,
_: (),
) -> Result<OpConn, AnyError> {
) -> Result<OpConn, AnyError>
where
NP: NetPermissions + 'static,
{
assert_eq!(args.transport, "tcp");
let hostname = &*args.hostname;
let port = args.port;
@ -924,10 +931,10 @@ fn op_listen_tls(
let key_file = &*args.key_file;
{
let permissions = state.borrow_mut::<Permissions>();
permissions.net.check(&(hostname, Some(port)))?;
permissions.read.check(Path::new(cert_file))?;
permissions.read.check(Path::new(key_file))?;
let permissions = state.borrow_mut::<NP>();
permissions.check_net(&(hostname, Some(port)))?;
permissions.check_read(Path::new(cert_file))?;
permissions.check_read(Path::new(key_file))?;
}
let mut tls_config = ServerConfig::new(NoClientAuth::new());

View file

@ -1,12 +1,11 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use super::utils::into_string;
use crate::ops::io::UnixStreamResource;
use crate::ops::net::AcceptArgs;
use crate::ops::net::OpAddr;
use crate::ops::net::OpConn;
use crate::ops::net::OpPacket;
use crate::ops::net::ReceiveArgs;
use crate::io::UnixStreamResource;
use crate::ops::AcceptArgs;
use crate::ops::OpAddr;
use crate::ops::OpConn;
use crate::ops::OpPacket;
use crate::ops::ReceiveArgs;
use deno_core::error::bad_resource;
use deno_core::error::custom_error;
use deno_core::error::null_opbuf;
@ -29,6 +28,14 @@ use tokio::net::UnixDatagram;
use tokio::net::UnixListener;
pub use tokio::net::UnixStream;
/// A utility function to map OsStrings to Strings
pub fn into_string(s: std::ffi::OsString) -> Result<String, AnyError> {
s.into_string().map_err(|s| {
let message = format!("File name or path {:?} is not valid UTF-8", s);
custom_error("InvalidData", message)
})
}
struct UnixListenerResource {
listener: AsyncRefCell<UnixListener>,
cancel: CancelHandle,

View file

@ -23,6 +23,7 @@ deno_console = { version = "0.10.0", path = "../extensions/console" }
deno_core = { version = "0.92.0", path = "../core" }
deno_crypto = { version = "0.24.0", path = "../extensions/crypto" }
deno_fetch = { version = "0.32.0", path = "../extensions/fetch" }
deno_net = { version = "0.1.0", path = "../extensions/net" }
deno_timers = { version = "0.8.0", path = "../extensions/timers" }
deno_url = { version = "0.10.0", path = "../extensions/url" }
deno_web = { version = "0.41.0", path = "../extensions/web" }
@ -41,6 +42,7 @@ deno_console = { version = "0.10.0", path = "../extensions/console" }
deno_core = { version = "0.92.0", path = "../core" }
deno_crypto = { version = "0.24.0", path = "../extensions/crypto" }
deno_fetch = { version = "0.32.0", path = "../extensions/fetch" }
deno_net = { version = "0.1.0", path = "../extensions/net" }
deno_timers = { version = "0.8.0", path = "../extensions/timers" }
deno_url = { version = "0.10.0", path = "../extensions/url" }
deno_web = { version = "0.41.0", path = "../extensions/web" }
@ -50,7 +52,6 @@ deno_websocket = { version = "0.15.0", path = "../extensions/websocket" }
deno_webstorage = { version = "0.5.0", path = "../extensions/webstorage" }
atty = "0.2.14"
bytes = "1"
dlopen = "0.1.8"
encoding_rs = "0.8.28"
filetime = "0.2.14"
@ -64,17 +65,11 @@ notify = "5.0.0-pre.7"
percent-encoding = "2.1.0"
regex = "1.4.3"
ring = "0.16.20"
rustls = "0.19.0"
serde = { version = "1.0.125", features = ["derive"] }
sys-info = "0.9.0"
termcolor = "1.1.2"
tokio = { version = "1.7.1", features = ["full"] }
tokio-util = { version = "0.6", features = ["io"] }
uuid = { version = "0.8.2", features = ["v4"] }
webpki = "0.21.4"
webpki-roots = "0.21.1"
trust-dns-proto = "0.20.3"
trust-dns-resolver = { version = "0.20.3", features = ["tokio-runtime", "serde-config"] }
[target.'cfg(windows)'.dependencies]
fwdansi = "1.1.0"

View file

@ -59,6 +59,7 @@ fn create_runtime_snapshot(snapshot_path: &Path, files: Vec<PathBuf>) {
deno_broadcast_channel::InMemoryBroadcastChannel::default(),
false, // No --unstable.
),
deno_net::init::<deno_net::NoNetPermissions>(false), // No --unstable.
];
let js_runtime = JsRuntime::new(RuntimeOptions {

View file

@ -2,6 +2,9 @@
"use strict";
((window) => {
const core = window.Deno.core;
const { BadResource, Interrupted } = core;
class NotFound extends Error {
constructor(msg) {
super(msg);
@ -86,13 +89,6 @@
}
}
class Interrupted extends Error {
constructor(msg) {
super(msg);
this.name = "Interrupted";
}
}
class WriteZero extends Error {
constructor(msg) {
super(msg);
@ -107,13 +103,6 @@
}
}
class BadResource extends Error {
constructor(msg) {
super(msg);
this.name = "BadResource";
}
}
class Http extends Error {
constructor(msg) {
super(msg);

View file

@ -4,6 +4,7 @@ pub use deno_broadcast_channel;
pub use deno_console;
pub use deno_crypto;
pub use deno_fetch;
pub use deno_net;
pub use deno_timers;
pub use deno_url;
pub use deno_web;
@ -20,7 +21,6 @@ pub mod js;
pub mod metrics;
pub mod ops;
pub mod permissions;
pub mod resolve_addr;
pub mod tokio_util;
pub mod web_worker;
pub mod worker;

View file

@ -1,6 +1,5 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use crate::ops::tls;
use deno_core::error::null_opbuf;
use deno_core::error::resource_unavailable;
use deno_core::error::AnyError;
@ -17,6 +16,9 @@ use deno_core::RcRef;
use deno_core::Resource;
use deno_core::ResourceId;
use deno_core::ZeroCopyBuf;
use deno_net::io::TcpStreamResource;
use deno_net::io::TlsStreamResource;
use deno_net::io::UnixStreamResource;
use std::borrow::Cow;
use std::cell::RefCell;
use std::io::Read;
@ -26,13 +28,10 @@ use tokio::io::AsyncRead;
use tokio::io::AsyncReadExt;
use tokio::io::AsyncWrite;
use tokio::io::AsyncWriteExt;
use tokio::net::tcp;
use tokio::process;
#[cfg(unix)]
use std::os::unix::io::FromRawFd;
#[cfg(unix)]
use tokio::net::unix;
#[cfg(windows)]
use std::os::windows::io::FromRawHandle;
@ -238,70 +237,6 @@ where
}
}
/// A full duplex resource has a read and write ends that are completely
/// independent, like TCP/Unix sockets and TLS streams.
#[derive(Debug)]
pub struct FullDuplexResource<R, W> {
rd: AsyncRefCell<R>,
wr: AsyncRefCell<W>,
// When a full-duplex resource is closed, all pending 'read' ops are
// canceled, while 'write' ops are allowed to complete. Therefore only
// 'read' futures should be attached to this cancel handle.
cancel_handle: CancelHandle,
}
impl<R, W> FullDuplexResource<R, W>
where
R: AsyncRead + Unpin + 'static,
W: AsyncWrite + Unpin + 'static,
{
pub fn new((rd, wr): (R, W)) -> Self {
Self {
rd: rd.into(),
wr: wr.into(),
cancel_handle: Default::default(),
}
}
pub fn into_inner(self) -> (R, W) {
(self.rd.into_inner(), self.wr.into_inner())
}
pub fn rd_borrow_mut(self: &Rc<Self>) -> AsyncMutFuture<R> {
RcRef::map(self, |r| &r.rd).borrow_mut()
}
pub fn wr_borrow_mut(self: &Rc<Self>) -> AsyncMutFuture<W> {
RcRef::map(self, |r| &r.wr).borrow_mut()
}
pub fn cancel_handle(self: &Rc<Self>) -> RcRef<CancelHandle> {
RcRef::map(self, |r| &r.cancel_handle)
}
pub fn cancel_read_ops(&self) {
self.cancel_handle.cancel()
}
async fn read(self: &Rc<Self>, buf: &mut [u8]) -> Result<usize, AnyError> {
let mut rd = self.rd_borrow_mut().await;
let nread = rd.read(buf).try_or_cancel(self.cancel_handle()).await?;
Ok(nread)
}
async fn write(self: &Rc<Self>, buf: &[u8]) -> Result<usize, AnyError> {
let mut wr = self.wr_borrow_mut().await;
let nwritten = wr.write(buf).await?;
Ok(nwritten)
}
async fn shutdown(self: &Rc<Self>) -> Result<(), AnyError> {
let mut wr = self.wr_borrow_mut().await;
wr.shutdown().await?;
Ok(())
}
}
pub type ChildStdinResource = WriteOnlyResource<process::ChildStdin>;
impl Resource for ChildStdinResource {
@ -334,64 +269,6 @@ impl Resource for ChildStderrResource {
}
}
pub type TcpStreamResource =
FullDuplexResource<tcp::OwnedReadHalf, tcp::OwnedWriteHalf>;
impl Resource for TcpStreamResource {
fn name(&self) -> Cow<str> {
"tcpStream".into()
}
fn close(self: Rc<Self>) {
self.cancel_read_ops();
}
}
pub type TlsStreamResource = FullDuplexResource<tls::ReadHalf, tls::WriteHalf>;
impl Resource for TlsStreamResource {
fn name(&self) -> Cow<str> {
"tlsStream".into()
}
fn close(self: Rc<Self>) {
self.cancel_read_ops();
}
}
#[cfg(unix)]
pub type UnixStreamResource =
FullDuplexResource<unix::OwnedReadHalf, unix::OwnedWriteHalf>;
#[cfg(not(unix))]
struct UnixStreamResource;
#[cfg(not(unix))]
impl UnixStreamResource {
async fn read(self: &Rc<Self>, _buf: &mut [u8]) -> Result<usize, AnyError> {
unreachable!()
}
async fn write(self: &Rc<Self>, _buf: &[u8]) -> Result<usize, AnyError> {
unreachable!()
}
async fn shutdown(self: &Rc<Self>) -> Result<(), AnyError> {
unreachable!()
}
fn cancel_read_ops(&self) {
unreachable!()
}
}
impl Resource for UnixStreamResource {
fn name(&self) -> Cow<str> {
"unixStream".into()
}
fn close(self: Rc<Self>) {
self.cancel_read_ops();
}
}
#[derive(Debug, Default)]
pub struct StdFileResource {
pub fs_file:

View file

@ -2,18 +2,13 @@
pub mod fs;
pub mod fs_events;
pub mod http;
pub mod io;
pub mod net;
#[cfg(unix)]
mod net_unix;
pub mod os;
pub mod permissions;
pub mod plugin;
pub mod process;
pub mod runtime;
pub mod signal;
pub mod tls;
pub mod tty;
mod utils;
pub mod web_worker;

View file

@ -962,6 +962,23 @@ impl Permissions {
}
}
impl deno_net::NetPermissions for Permissions {
fn check_net<T: AsRef<str>>(
&mut self,
host: &(T, Option<u16>),
) -> Result<(), AnyError> {
self.net.check(host)
}
fn check_read(&mut self, path: &Path) -> Result<(), AnyError> {
self.read.check(path)
}
fn check_write(&mut self, path: &Path) -> Result<(), AnyError> {
self.write.check(path)
}
}
impl deno_fetch::FetchPermissions for Permissions {
fn check_net_url(&mut self, url: &url::Url) -> Result<(), AnyError> {
self.net.check_url(url)

View file

@ -330,14 +330,12 @@ impl WebWorker {
vec![
ops::fs_events::init(),
ops::fs::init(),
ops::net::init(),
deno_net::init::<Permissions>(options.unstable),
ops::os::init(),
ops::http::init(),
ops::permissions::init(),
ops::plugin::init(),
ops::process::init(),
ops::signal::init(),
ops::tls::init(),
ops::tty::init(),
ops::io::init_stdio(),
]

View file

@ -120,16 +120,14 @@ impl MainWorker {
ops::worker_host::init(options.create_web_worker_cb.clone()),
ops::fs_events::init(),
ops::fs::init(),
ops::http::init(),
ops::io::init(),
ops::io::init_stdio(),
ops::net::init(),
deno_net::init::<Permissions>(options.unstable),
ops::os::init(),
ops::permissions::init(),
ops::plugin::init(),
ops::process::init(),
ops::signal::init(),
ops::tls::init(),
ops::tty::init(),
// Permissions ext (worker specific state)
perm_ext,