From f131445a46555f1634aecae0fc1d4979b4cefa6d Mon Sep 17 00:00:00 2001 From: Aaron Power Date: Fri, 31 Aug 2018 12:51:12 +0100 Subject: [PATCH] Implemented deno.env and refactored flags.rs --- js/deno.ts | 1 + js/os.ts | 58 ++++++++++++++++++++++++++++++++++++ js/os_test.ts | 21 ++++++++++++++ js/test_util.ts | 21 +++++++++----- src/flags.rs | 49 ++++++++++--------------------- src/handlers.rs | 71 +++++++++++++++++++++++++++++++++++++++++++++ src/msg.fbs | 19 ++++++++++++ tools/unit_tests.py | 12 ++++---- 8 files changed, 206 insertions(+), 46 deletions(-) diff --git a/js/deno.ts b/js/deno.ts index e24345ccde..c9611d1169 100644 --- a/js/deno.ts +++ b/js/deno.ts @@ -1,6 +1,7 @@ // Copyright 2018 the Deno authors. All rights reserved. MIT license. // Public deno module. export { + env, exit, FileInfo, makeTempDirSync, diff --git a/js/os.ts b/js/os.ts index 8730a6ddfe..e98f1557de 100644 --- a/js/os.ts +++ b/js/os.ts @@ -128,6 +128,64 @@ export function readFileSync(filename: string): Uint8Array { return new Uint8Array(dataArray!); } +function createEnv(_msg: fbs.EnvironRes): { [index:string]: string } { + const env: { [index:string]: string } = {}; + + for (let i = 0; i < _msg.mapLength(); i++) { + const item = _msg.map(i)!; + + env[item.key()!] = item.value()!; + } + + return new Proxy(env, { + set(obj, prop: string, value: string | number) { + setEnv(prop, value.toString()); + return Reflect.set(obj, prop, value); + } + }); +} + +function setEnv(key: string, value: string): void { + const builder = new flatbuffers.Builder(); + const _key = builder.createString(key); + const _value = builder.createString(value); + fbs.SetEnv.startSetEnv(builder); + fbs.SetEnv.addKey(builder, _key); + fbs.SetEnv.addValue(builder, _value); + const msg = fbs.SetEnv.endSetEnv(builder); + send(builder, fbs.Any.SetEnv, msg); +} + +/** + * Returns a snapshot of the environment variables at invocation. Mutating a + * property in the object will set that variable in the environment for + * the process. The environment object will only accept `string`s or `number`s + * as values. + * import { env } from "deno"; + * const env = deno.env(); + * console.log(env.SHELL) + * env.TEST_VAR = "HELLO"; + * + * const newEnv = deno.env(); + * console.log(env.TEST_VAR == newEnv.TEST_VAR); + */ +export function env(): { [index:string]: string } { + /* Ideally we could write + const res = send({ + command: fbs.Command.ENV, + }); + */ + const builder = new flatbuffers.Builder(); + fbs.Environ.startEnviron(builder); + const msg = fbs.Environ.endEnviron(builder); + const baseRes = send(builder, fbs.Any.Environ, msg)!; + assert(fbs.Any.EnvironRes === baseRes.msgType()); + const res = new fbs.EnvironRes(); + assert(baseRes.msg(res) != null); + // TypeScript cannot track assertion above, therefore not null assertion + return createEnv(res); +} + export class FileInfo { private _isFile: boolean; private _isSymlink: boolean; diff --git a/js/os_test.ts b/js/os_test.ts index 7bf1d86a57..cd5ede221d 100644 --- a/js/os_test.ts +++ b/js/os_test.ts @@ -2,6 +2,27 @@ import { test, testPerm, assert, assertEqual } from "./test_util.ts"; import * as deno from "deno"; +testPerm({ env: true }, async function envSuccess() { + const env = deno.env(); + assert(env !== null); + env.test_var = "Hello World"; + const newEnv = deno.env(); + assertEqual(env.test_var, newEnv.test_var); +}); + +test(async function envFailure() { + let caughtError = false; + try { + const env = deno.env(); + } catch (err) { + caughtError = true; + // TODO assert(err instanceof deno.PermissionDenied). + assertEqual(err.name, "deno.PermissionDenied"); + } + + assert(caughtError); +}); + // TODO Add tests for modified, accessed, and created fields once there is a way // to create temp files. test(async function statSyncSuccess() { diff --git a/js/test_util.ts b/js/test_util.ts index 116b96ff2e..433bbf11ba 100644 --- a/js/test_util.ts +++ b/js/test_util.ts @@ -17,23 +17,26 @@ testing.setFilter(deno.argv[1]); interface DenoPermissions { write?: boolean; net?: boolean; + env?: boolean; } function permToString(perms: DenoPermissions): string { const w = perms.write ? 1 : 0; const n = perms.net ? 1 : 0; - return `permW${w}N${n}`; + const e = perms.env ? 1 : 0; + return `permW${w}N${n}E${e}`; } function permFromString(s: string): DenoPermissions { - const re = /^permW([01])N([01])$/; + const re = /^permW([01])N([01])E([01])$/; const found = s.match(re); if (!found) { throw Error("Not a permission string"); } return { write: Boolean(Number(found[1])), - net: Boolean(Number(found[2])) + net: Boolean(Number(found[2])), + env: Boolean(Number(found[3])), }; } @@ -43,14 +46,16 @@ export function testPerm(perms: DenoPermissions, fn: testing.TestFunction) { } export function test(fn: testing.TestFunction) { - testPerm({ write: false, net: false }, fn); + testPerm({ write: false, net: false, env: false }, fn); } test(function permSerialization() { - for (let write of [true, false]) { - for (let net of [true, false]) { - let perms: DenoPermissions = { write, net }; - testing.assertEqual(perms, permFromString(permToString(perms))); + for (const write of [true, false]) { + for (const net of [true, false]) { + for (const env of [true, false]) { + const perms: DenoPermissions = { write, net, env }; + testing.assertEqual(perms, permFromString(permToString(perms))); + } } } }); diff --git a/src/flags.rs b/src/flags.rs index 7149ab19f8..851bacb6bc 100644 --- a/src/flags.rs +++ b/src/flags.rs @@ -11,7 +11,7 @@ macro_rules! svec { ($($x:expr),*) => (vec![$($x.to_string()),*]); } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Default)] pub struct DenoFlags { pub help: bool, pub log_debug: bool, @@ -19,6 +19,7 @@ pub struct DenoFlags { pub reload: bool, pub allow_write: bool, pub allow_net: bool, + pub allow_env: bool, } pub fn print_usage() { @@ -27,6 +28,7 @@ pub fn print_usage() { --allow-write Allow file system write access. --allow-net Allow network access. +--allow-env Allow environment access. -v or --version Print the version. -r or --reload Reload cached remote resources. -D or --log-debug Log debug output. @@ -37,14 +39,7 @@ pub fn print_usage() { // Parses flags for deno. This does not do v8_set_flags() - call that separately. pub fn set_flags(args: Vec) -> (DenoFlags, Vec) { - let mut flags = DenoFlags { - help: false, - version: false, - reload: false, - log_debug: false, - allow_write: false, - allow_net: false, - }; + let mut flags = DenoFlags::default(); let mut rest = Vec::new(); for a in &args { match a.as_str() { @@ -52,6 +47,7 @@ pub fn set_flags(args: Vec) -> (DenoFlags, Vec) { "-D" | "--log-debug" => flags.log_debug = true, "-v" | "--version" => flags.version = true, "-r" | "--reload" => flags.reload = true, + "--allow-env" => flags.allow_env = true, "--allow-write" => flags.allow_write = true, "--allow-net" => flags.allow_net = true, _ => rest.push(a.clone()), @@ -64,15 +60,10 @@ pub fn set_flags(args: Vec) -> (DenoFlags, Vec) { #[test] fn test_set_flags_1() { let (flags, rest) = set_flags(svec!["deno", "--version"]); - assert!(rest == svec!["deno"]); - assert!( - flags == DenoFlags { - help: false, - log_debug: false, + assert_eq!(rest, svec!["deno"]); + assert_eq!(flags, DenoFlags { version: true, - reload: false, - allow_write: false, - allow_net: false, + ..DenoFlags::default() } ); } @@ -80,15 +71,11 @@ fn test_set_flags_1() { #[test] fn test_set_flags_2() { let (flags, rest) = set_flags(svec!["deno", "-r", "-D", "script.ts"]); - assert!(rest == svec!["deno", "script.ts"]); - assert!( - flags == DenoFlags { - help: false, + assert_eq!(rest, svec!["deno", "script.ts"]); + assert_eq!(flags, DenoFlags { log_debug: true, - version: false, reload: true, - allow_write: false, - allow_net: false, + ..DenoFlags::default() } ); } @@ -97,15 +84,11 @@ fn test_set_flags_2() { fn test_set_flags_3() { let (flags, rest) = set_flags(svec!["deno", "-r", "script.ts", "--allow-write"]); - assert!(rest == svec!["deno", "script.ts"]); - assert!( - flags == DenoFlags { - help: false, - log_debug: false, - version: false, + assert_eq!(rest, svec!["deno", "script.ts"]); + assert_eq!(flags, DenoFlags { reload: true, allow_write: true, - allow_net: false, + ..DenoFlags::default() } ); } @@ -142,13 +125,13 @@ fn parse_core_args(args: Vec) -> (Vec, Vec) { fn test_parse_core_args_1() { let js_args = parse_core_args(vec!["deno".to_string(), "--v8-options".to_string()]); - assert!(js_args == (vec!["deno".to_string(), "--help".to_string()], vec![])); + assert_eq!(js_args, (vec!["deno".to_string(), "--help".to_string()], vec![])); } #[test] fn test_parse_core_args_2() { let js_args = parse_core_args(vec!["deno".to_string(), "--help".to_string()]); - assert!(js_args == (vec!["deno".to_string()], vec!["--help".to_string()])); + assert_eq!(js_args, (vec!["deno".to_string()], vec!["--help".to_string()])); } // Pass the command line arguments to v8. diff --git a/src/handlers.rs b/src/handlers.rs index 8d321c46f9..407ecf5c8a 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -45,6 +45,7 @@ pub extern "C" fn msg_from_js(d: *const DenoC, buf: deno_buf) { let output_code = msg.output_code().unwrap(); handle_code_cache(d, &mut builder, filename, source_code, output_code) } + msg::Any::Environ => handle_env(d, &mut builder), msg::Any::FetchReq => { // TODO base.msg_as_FetchReq(); let msg = msg::FetchReq::init_from_table(base.msg().unwrap()); @@ -79,6 +80,13 @@ pub extern "C" fn msg_from_js(d: *const DenoC, buf: deno_buf) { let filename = msg.filename().unwrap(); handle_read_file_sync(d, &mut builder, filename) } + msg::Any::SetEnv => { + // TODO base.msg_as_SetEnv(); + let msg = msg::SetEnv::init_from_table(base.msg().unwrap()); + let key = msg.key().unwrap(); + let value = msg.value().unwrap(); + handle_set_env(d, &mut builder, key, value) + } msg::Any::StatSync => { // TODO base.msg_as_StatSync(); let msg = msg::StatSync::init_from_table(base.msg().unwrap()); @@ -244,6 +252,69 @@ fn handle_code_cache( Ok(null_buf()) // null response indicates success. } +fn handle_set_env( + d: *const DenoC, + _builder: &mut FlatBufferBuilder, + key: &str, + value: &str, +) -> HandlerResult { + let deno = from_c(d); + if !deno.flags.allow_env { + let err = std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "allow_env is off.", + ); + return Err(err.into()); + } + + std::env::set_var(key, value); + Ok(null_buf()) +} + +fn handle_env( + d: *const DenoC, + builder: &mut FlatBufferBuilder, +) -> HandlerResult { + let deno = from_c(d); + if !deno.flags.allow_env { + let err = std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "allow_env is off.", + ); + return Err(err.into()); + } + + let vars: Vec<_> = std::env::vars().map(|(key, value)| { + let key = builder.create_string(&key); + let value = builder.create_string(&value); + + msg::EnvPair::create(builder, &msg::EnvPairArgs { + key: Some(key), + value: Some(value), + ..Default::default() + }) + }).collect(); + + let tables = builder.create_vector_of_reverse_offsets(&vars); + + let msg = msg::EnvironRes::create( + builder, + &msg::EnvironResArgs { + map: Some(tables), + ..Default::default() + }, + ); + + Ok(create_msg( + builder, + &msg::BaseArgs { + msg: Some(flatbuffers::Offset::new(msg.value())), + msg_type: msg::Any::EnvironRes, + ..Default::default() + }, + )) +} + fn handle_fetch_req( d: *const DenoC, _builder: &mut FlatBufferBuilder, diff --git a/src/msg.fbs b/src/msg.fbs index eb3753d754..ca2e16af7a 100644 --- a/src/msg.fbs +++ b/src/msg.fbs @@ -10,6 +10,8 @@ union Any { TimerStart, TimerReady, TimerClear, + Environ, + EnvironRes, FetchReq, FetchRes, MakeTempDir, @@ -18,6 +20,7 @@ union Any { ReadFileSyncRes, StatSync, StatSyncRes, + SetEnv, WriteFileSync, } @@ -124,6 +127,22 @@ table TimerClear { id: uint; } +table Environ {} + +table SetEnv { + key: string; + value: string; +} + +table EnvironRes { + map: [EnvPair]; +} + +table EnvPair { + key: string; + value: string; +} + table FetchReq { id: uint; url: string; diff --git a/tools/unit_tests.py b/tools/unit_tests.py index a2cfa33b6d..460d32bc2f 100755 --- a/tools/unit_tests.py +++ b/tools/unit_tests.py @@ -10,12 +10,14 @@ import sys # tests by the special string. permW0N0 means allow-write but not allow-net. # See js/test_util.ts for more details. def unit_tests(deno_exe): - run([deno_exe, "js/unit_tests.ts", "permW0N0"]) - run([deno_exe, "js/unit_tests.ts", "permW1N0", "--allow-write"]) - run([deno_exe, "js/unit_tests.ts", "permW0N1", "--allow-net"]) + run([deno_exe, "js/unit_tests.ts", "permW0N0E0"]) + run([deno_exe, "js/unit_tests.ts", "permW1N0E0", "--allow-write"]) + run([deno_exe, "js/unit_tests.ts", "permW0N1E0", "--allow-net"]) + run([deno_exe, "js/unit_tests.ts", "permW0N0E1", "--allow-env"]) run([ - deno_exe, "js/unit_tests.ts", "permW1N1", "--allow-write", - "--allow-net" + deno_exe, "js/unit_tests.ts", "permW1N1E1", "--allow-write", + "--allow-net", + "--allow-env", ])