From 9ab03389f047e5520c184b9fce4cd5fb2e4804bd Mon Sep 17 00:00:00 2001 From: Dmitry Sharshakov Date: Fri, 8 Feb 2019 23:59:38 +0300 Subject: [PATCH] Add --allow-read (#1689) Co-authored-by: Greg Altman --- js/chmod_test.ts | 8 ++-- js/copy_file_test.ts | 72 +++++++++++++++++++++++------------ js/files_test.ts | 40 ++++++++++++++++--- js/mkdir_test.ts | 10 ++--- js/read_dir_test.ts | 34 ++++++++++++++--- js/read_file_test.ts | 32 ++++++++++++++-- js/read_link_test.ts | 32 ++++++++++++++-- js/rename_test.ts | 6 +-- js/resources_test.ts | 2 +- js/stat_test.ts | 66 +++++++++++++++++++++++++++----- js/symlink_test.ts | 8 ++-- js/test_util.ts | 28 +++++++++----- js/truncate_test.ts | 4 +- js/write_file_test.ts | 20 +++++----- src/flags.rs | 22 +++++++++++ src/isolate.rs | 5 +++ src/main.rs | 1 + src/ops.rs | 60 +++++++++++++++++++++++------ src/permissions.rs | 17 +++++++++ tests/016_double_await.test | 2 +- tools/fmt_test.py | 5 ++- tools/test_format.py | 2 +- tools/throughput_benchmark.py | 2 +- tools/unit_tests.py | 14 ++++--- tools/util_test.ts | 21 ++++++++-- 25 files changed, 395 insertions(+), 118 deletions(-) diff --git a/js/chmod_test.ts b/js/chmod_test.ts index ceee5b0656..e3676b5472 100644 --- a/js/chmod_test.ts +++ b/js/chmod_test.ts @@ -4,7 +4,7 @@ import * as deno from "deno"; const isNotWindows = deno.platform.os !== "win"; -testPerm({ write: true }, function chmodSyncSuccess() { +testPerm({ read: true, write: true }, function chmodSyncSuccess() { const enc = new TextEncoder(); const data = enc.encode("Hello"); const tempDir = deno.makeTempDirSync(); @@ -23,7 +23,7 @@ testPerm({ write: true }, function chmodSyncSuccess() { // Check symlink when not on windows if (isNotWindows) { - testPerm({ write: true }, function chmodSyncSymlinkSuccess() { + testPerm({ read: true, write: true }, function chmodSyncSymlinkSuccess() { const enc = new TextEncoder(); const data = enc.encode("Hello"); const tempDir = deno.makeTempDirSync(); @@ -69,7 +69,7 @@ testPerm({ write: false }, function chmodSyncPerm() { assertEqual(err.name, "PermissionDenied"); }); -testPerm({ write: true }, async function chmodSuccess() { +testPerm({ read: true, write: true }, async function chmodSuccess() { const enc = new TextEncoder(); const data = enc.encode("Hello"); const tempDir = deno.makeTempDirSync(); @@ -88,7 +88,7 @@ testPerm({ write: true }, async function chmodSuccess() { // Check symlink when not on windows if (isNotWindows) { - testPerm({ write: true }, async function chmodSymlinkSuccess() { + testPerm({ read: true, write: true }, async function chmodSymlinkSuccess() { const enc = new TextEncoder(); const data = enc.encode("Hello"); const tempDir = deno.makeTempDirSync(); diff --git a/js/copy_file_test.ts b/js/copy_file_test.ts index bdc455f45e..3bd1f72296 100644 --- a/js/copy_file_test.ts +++ b/js/copy_file_test.ts @@ -20,7 +20,7 @@ function assertSameContent(filename1: string, filename2: string) { assertEqual(data1, data2); } -testPerm({ write: true }, function copyFileSyncSuccess() { +testPerm({ read: true, write: true }, function copyFileSyncSuccess() { const tempDir = deno.makeTempDirSync(); const fromFilename = tempDir + "/from.txt"; const toFilename = tempDir + "/to.txt"; @@ -32,7 +32,7 @@ testPerm({ write: true }, function copyFileSyncSuccess() { assertSameContent(fromFilename, toFilename); }); -testPerm({ write: true }, function copyFileSyncFailure() { +testPerm({ write: true, read: true }, function copyFileSyncFailure() { const tempDir = deno.makeTempDirSync(); const fromFilename = tempDir + "/from.txt"; const toFilename = tempDir + "/to.txt"; @@ -48,7 +48,31 @@ testPerm({ write: true }, function copyFileSyncFailure() { assertEqual(err.name, "NotFound"); }); -testPerm({ write: true }, function copyFileSyncOverwrite() { +testPerm({ write: true, read: false }, function copyFileSyncPerm1() { + let caughtError = false; + try { + deno.copyFileSync("/from.txt", "/to.txt"); + } catch (e) { + caughtError = true; + assertEqual(e.kind, deno.ErrorKind.PermissionDenied); + assertEqual(e.name, "PermissionDenied"); + } + assert(caughtError); +}); + +testPerm({ write: false, read: true }, function copyFileSyncPerm2() { + let caughtError = false; + try { + deno.copyFileSync("/from.txt", "/to.txt"); + } catch (e) { + caughtError = true; + assertEqual(e.kind, deno.ErrorKind.PermissionDenied); + assertEqual(e.name, "PermissionDenied"); + } + assert(caughtError); +}); + +testPerm({ read: true, write: true }, function copyFileSyncOverwrite() { const tempDir = deno.makeTempDirSync(); const fromFilename = tempDir + "/from.txt"; const toFilename = tempDir + "/to.txt"; @@ -62,19 +86,7 @@ testPerm({ write: true }, function copyFileSyncOverwrite() { assertSameContent(fromFilename, toFilename); }); -testPerm({ write: false }, function copyFileSyncPerm() { - let err; - try { - deno.copyFileSync("/from.txt", "/to.txt"); - } catch (e) { - err = e; - } - assert(!!err); - assertEqual(err.kind, deno.ErrorKind.PermissionDenied); - assertEqual(err.name, "PermissionDenied"); -}); - -testPerm({ write: true }, async function copyFileSuccess() { +testPerm({ read: true, write: true }, async function copyFileSuccess() { const tempDir = deno.makeTempDirSync(); const fromFilename = tempDir + "/from.txt"; const toFilename = tempDir + "/to.txt"; @@ -86,7 +98,7 @@ testPerm({ write: true }, async function copyFileSuccess() { assertSameContent(fromFilename, toFilename); }); -testPerm({ write: true }, async function copyFileFailure() { +testPerm({ read: true, write: true }, async function copyFileFailure() { const tempDir = deno.makeTempDirSync(); const fromFilename = tempDir + "/from.txt"; const toFilename = tempDir + "/to.txt"; @@ -102,7 +114,7 @@ testPerm({ write: true }, async function copyFileFailure() { assertEqual(err.name, "NotFound"); }); -testPerm({ write: true }, async function copyFileOverwrite() { +testPerm({ read: true, write: true }, async function copyFileOverwrite() { const tempDir = deno.makeTempDirSync(); const fromFilename = tempDir + "/from.txt"; const toFilename = tempDir + "/to.txt"; @@ -116,14 +128,26 @@ testPerm({ write: true }, async function copyFileOverwrite() { assertSameContent(fromFilename, toFilename); }); -testPerm({ write: false }, async function copyFilePerm() { - let err; +testPerm({ read: false, write: true }, async function copyFilePerm1() { + let caughtError = false; try { await deno.copyFile("/from.txt", "/to.txt"); } catch (e) { - err = e; + caughtError = true; + assertEqual(e.kind, deno.ErrorKind.PermissionDenied); + assertEqual(e.name, "PermissionDenied"); } - assert(!!err); - assertEqual(err.kind, deno.ErrorKind.PermissionDenied); - assertEqual(err.name, "PermissionDenied"); + assert(caughtError); +}); + +testPerm({ read: true, write: false }, async function copyFilePerm2() { + let caughtError = false; + try { + await deno.copyFile("/from.txt", "/to.txt"); + } catch (e) { + caughtError = true; + assertEqual(e.kind, deno.ErrorKind.PermissionDenied); + assertEqual(e.name, "PermissionDenied"); + } + assert(caughtError); }); diff --git a/js/files_test.ts b/js/files_test.ts index 9014e7b831..a4d8a064a0 100644 --- a/js/files_test.ts +++ b/js/files_test.ts @@ -8,7 +8,7 @@ test(function filesStdioFileDescriptors() { assertEqual(deno.stderr.rid, 2); }); -test(async function filesCopyToStdout() { +testPerm({ read: true }, async function filesCopyToStdout() { const filename = "package.json"; const file = await deno.open(filename); assert(file.rid > 2); @@ -18,7 +18,7 @@ test(async function filesCopyToStdout() { console.log("bytes written", bytesWritten); }); -test(async function filesToAsyncIterator() { +testPerm({ read: true }, async function filesToAsyncIterator() { const filename = "tests/hello.txt"; const file = await deno.open(filename); @@ -32,7 +32,7 @@ test(async function filesToAsyncIterator() { testPerm({ write: false }, async function writePermFailure() { const filename = "tests/hello.txt"; - const writeModes: deno.OpenMode[] = ["r+", "w", "w+", "a", "a+", "x", "x+"]; + const writeModes: deno.OpenMode[] = ["w", "a", "x"]; for (const mode of writeModes) { let err; try { @@ -46,7 +46,35 @@ testPerm({ write: false }, async function writePermFailure() { } }); -testPerm({ write: true }, async function createFile() { +testPerm({ read: false }, async function readPermFailure() { + let caughtError = false; + try { + await deno.open("package.json", "r"); + } catch (e) { + caughtError = true; + assertEqual(e.kind, deno.ErrorKind.PermissionDenied); + assertEqual(e.name, "PermissionDenied"); + } + assert(caughtError); +}); + +testPerm({ write: false, read: false }, async function readWritePermFailure() { + const filename = "tests/hello.txt"; + const writeModes: deno.OpenMode[] = ["r+", "w+", "a+", "x+"]; + for (const mode of writeModes) { + let err; + try { + await deno.open(filename, mode); + } catch (e) { + err = e; + } + assert(!!err); + assertEqual(err.kind, deno.ErrorKind.PermissionDenied); + assertEqual(err.name, "PermissionDenied"); + } +}); + +testPerm({ read: true, write: true }, async function createFile() { const tempDir = await deno.makeTempDir(); const filename = tempDir + "/test.txt"; const f = await deno.open(filename, "w"); @@ -64,7 +92,7 @@ testPerm({ write: true }, async function createFile() { await deno.remove(tempDir, { recursive: true }); }); -testPerm({ write: true }, async function openModeWrite() { +testPerm({ read: true, write: true }, async function openModeWrite() { const tempDir = deno.makeTempDirSync(); const encoder = new TextEncoder(); const filename = tempDir + "hello.txt"; @@ -98,7 +126,7 @@ testPerm({ write: true }, async function openModeWrite() { await deno.remove(tempDir, { recursive: true }); }); -testPerm({ write: true }, async function openModeWriteRead() { +testPerm({ read: true, write: true }, async function openModeWriteRead() { const tempDir = deno.makeTempDirSync(); const encoder = new TextEncoder(); const filename = tempDir + "hello.txt"; diff --git a/js/mkdir_test.ts b/js/mkdir_test.ts index 8b36c18f98..e88181485c 100644 --- a/js/mkdir_test.ts +++ b/js/mkdir_test.ts @@ -2,14 +2,14 @@ import { testPerm, assert, assertEqual } from "./test_util.ts"; import * as deno from "deno"; -testPerm({ write: true }, function mkdirSyncSuccess() { +testPerm({ read: true, write: true }, function mkdirSyncSuccess() { const path = deno.makeTempDirSync() + "/dir"; deno.mkdirSync(path); const pathInfo = deno.statSync(path); assert(pathInfo.isDirectory()); }); -testPerm({ write: true }, function mkdirSyncMode() { +testPerm({ read: true, write: true }, function mkdirSyncMode() { const path = deno.makeTempDirSync() + "/dir"; deno.mkdirSync(path, false, 0o755); // no perm for x const pathInfo = deno.statSync(path); @@ -30,7 +30,7 @@ testPerm({ write: false }, function mkdirSyncPerm() { assertEqual(err.name, "PermissionDenied"); }); -testPerm({ write: true }, async function mkdirSuccess() { +testPerm({ read: true, write: true }, async function mkdirSuccess() { const path = deno.makeTempDirSync() + "/dir"; await deno.mkdir(path); const pathInfo = deno.statSync(path); @@ -48,14 +48,14 @@ testPerm({ write: true }, function mkdirErrIfExists() { assertEqual(err.name, "AlreadyExists"); }); -testPerm({ write: true }, function mkdirSyncRecursive() { +testPerm({ read: true, write: true }, function mkdirSyncRecursive() { const path = deno.makeTempDirSync() + "/nested/directory"; deno.mkdirSync(path, true); const pathInfo = deno.statSync(path); assert(pathInfo.isDirectory()); }); -testPerm({ write: true }, async function mkdirRecursive() { +testPerm({ read: true, write: true }, async function mkdirRecursive() { const path = deno.makeTempDirSync() + "/nested/directory"; await deno.mkdir(path, true); const pathInfo = deno.statSync(path); diff --git a/js/read_dir_test.ts b/js/read_dir_test.ts index d025c6ec57..77f33f7140 100644 --- a/js/read_dir_test.ts +++ b/js/read_dir_test.ts @@ -1,5 +1,5 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -import { test, testPerm, assert, assertEqual } from "./test_util.ts"; +import { testPerm, assert, assertEqual } from "./test_util.ts"; import * as deno from "deno"; import { FileInfo } from "deno"; @@ -22,12 +22,24 @@ function assertSameContent(files: FileInfo[]) { assertEqual(counter, 2); } -test(function readDirSyncSuccess() { +testPerm({ read: true }, function readDirSyncSuccess() { const files = deno.readDirSync("tests/"); assertSameContent(files); }); -test(function readDirSyncNotDir() { +testPerm({ read: false }, function readDirSyncPerm() { + let caughtError = false; + try { + const files = deno.readDirSync("tests/"); + } catch (e) { + caughtError = true; + assertEqual(e.kind, deno.ErrorKind.PermissionDenied); + assertEqual(e.name, "PermissionDenied"); + } + assert(caughtError); +}); + +testPerm({ read: true }, function readDirSyncNotDir() { let caughtError = false; let src; @@ -41,7 +53,7 @@ test(function readDirSyncNotDir() { assertEqual(src, undefined); }); -test(function readDirSyncNotFound() { +testPerm({ read: true }, function readDirSyncNotFound() { let caughtError = false; let src; @@ -55,7 +67,19 @@ test(function readDirSyncNotFound() { assertEqual(src, undefined); }); -test(async function readDirSuccess() { +testPerm({ read: true }, async function readDirSuccess() { const files = await deno.readDir("tests/"); assertSameContent(files); }); + +testPerm({ read: false }, async function readDirPerm() { + let caughtError = false; + try { + const files = await deno.readDir("tests/"); + } catch (e) { + caughtError = true; + assertEqual(e.kind, deno.ErrorKind.PermissionDenied); + assertEqual(e.name, "PermissionDenied"); + } + assert(caughtError); +}); diff --git a/js/read_file_test.ts b/js/read_file_test.ts index db35dd7020..34b3913453 100644 --- a/js/read_file_test.ts +++ b/js/read_file_test.ts @@ -1,8 +1,8 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -import { test, assert, assertEqual } from "./test_util.ts"; +import { testPerm, assert, assertEqual } from "./test_util.ts"; import * as deno from "deno"; -test(function readFileSyncSuccess() { +testPerm({ read: true }, function readFileSyncSuccess() { const data = deno.readFileSync("package.json"); assert(data.byteLength > 0); const decoder = new TextDecoder("utf-8"); @@ -11,7 +11,19 @@ test(function readFileSyncSuccess() { assertEqual(pkg.name, "deno"); }); -test(function readFileSyncNotFound() { +testPerm({ read: false }, function readFileSyncPerm() { + let caughtError = false; + try { + const data = deno.readFileSync("package.json"); + } catch (e) { + caughtError = true; + assertEqual(e.kind, deno.ErrorKind.PermissionDenied); + assertEqual(e.name, "PermissionDenied"); + } + assert(caughtError); +}); + +testPerm({ read: true }, function readFileSyncNotFound() { let caughtError = false; let data; try { @@ -24,7 +36,7 @@ test(function readFileSyncNotFound() { assert(data === undefined); }); -test(async function readFileSuccess() { +testPerm({ read: true }, async function readFileSuccess() { const data = await deno.readFile("package.json"); assert(data.byteLength > 0); const decoder = new TextDecoder("utf-8"); @@ -32,3 +44,15 @@ test(async function readFileSuccess() { const pkg = JSON.parse(json); assertEqual(pkg.name, "deno"); }); + +testPerm({ read: false }, async function readFilePerm() { + let caughtError = false; + try { + await deno.readFile("package.json"); + } catch (e) { + caughtError = true; + assertEqual(e.kind, deno.ErrorKind.PermissionDenied); + assertEqual(e.name, "PermissionDenied"); + } + assert(caughtError); +}); diff --git a/js/read_link_test.ts b/js/read_link_test.ts index d4a2666b74..760ed1ea6e 100644 --- a/js/read_link_test.ts +++ b/js/read_link_test.ts @@ -1,8 +1,8 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -import { test, testPerm, assert, assertEqual } from "./test_util.ts"; +import { testPerm, assert, assertEqual } from "./test_util.ts"; import * as deno from "deno"; -testPerm({ write: true }, function readlinkSyncSuccess() { +testPerm({ write: true, read: true }, function readlinkSyncSuccess() { const testDir = deno.makeTempDirSync(); const target = testDir + "/target"; const symlink = testDir + "/symln"; @@ -16,7 +16,19 @@ testPerm({ write: true }, function readlinkSyncSuccess() { } }); -test(function readlinkSyncNotFound() { +testPerm({ read: false }, async function readlinkSyncPerm() { + let caughtError = false; + try { + deno.readlinkSync("/symlink"); + } catch (e) { + caughtError = true; + assertEqual(e.kind, deno.ErrorKind.PermissionDenied); + assertEqual(e.name, "PermissionDenied"); + } + assert(caughtError); +}); + +testPerm({ read: true }, function readlinkSyncNotFound() { let caughtError = false; let data; try { @@ -29,7 +41,7 @@ test(function readlinkSyncNotFound() { assertEqual(data, undefined); }); -testPerm({ write: true }, async function readlinkSuccess() { +testPerm({ write: true, read: true }, async function readlinkSuccess() { const testDir = deno.makeTempDirSync(); const target = testDir + "/target"; const symlink = testDir + "/symln"; @@ -42,3 +54,15 @@ testPerm({ write: true }, async function readlinkSuccess() { assertEqual(targetPath, target); } }); + +testPerm({ read: false }, async function readlinkPerm() { + let caughtError = false; + try { + await deno.readlink("/symlink"); + } catch (e) { + caughtError = true; + assertEqual(e.kind, deno.ErrorKind.PermissionDenied); + assertEqual(e.name, "PermissionDenied"); + } + assert(caughtError); +}); diff --git a/js/rename_test.ts b/js/rename_test.ts index bc08f006b2..33c7d06efd 100644 --- a/js/rename_test.ts +++ b/js/rename_test.ts @@ -2,7 +2,7 @@ import { testPerm, assert, assertEqual } from "./test_util.ts"; import * as deno from "deno"; -testPerm({ write: true }, function renameSyncSuccess() { +testPerm({ read: true, write: true }, function renameSyncSuccess() { const testDir = deno.makeTempDirSync(); const oldpath = testDir + "/oldpath"; const newpath = testDir + "/newpath"; @@ -24,7 +24,7 @@ testPerm({ write: true }, function renameSyncSuccess() { assertEqual(oldPathInfo, undefined); }); -testPerm({ write: false }, function renameSyncPerm() { +testPerm({ read: true, write: false }, function renameSyncPerm() { let err; try { const oldpath = "/oldbaddir"; @@ -37,7 +37,7 @@ testPerm({ write: false }, function renameSyncPerm() { assertEqual(err.name, "PermissionDenied"); }); -testPerm({ write: true }, async function renameSuccess() { +testPerm({ read: true, write: true }, async function renameSuccess() { const testDir = deno.makeTempDirSync(); const oldpath = testDir + "/oldpath"; const newpath = testDir + "/newpath"; diff --git a/js/resources_test.ts b/js/resources_test.ts index 7cfcbf1f8f..722602c986 100644 --- a/js/resources_test.ts +++ b/js/resources_test.ts @@ -26,7 +26,7 @@ testPerm({ net: true }, async function resourcesNet() { listener.close(); }); -test(async function resourcesFile() { +testPerm({ read: true }, async function resourcesFile() { const resourcesBefore = deno.resources(); await deno.open("tests/hello.txt"); const resourcesAfter = deno.resources(); diff --git a/js/stat_test.ts b/js/stat_test.ts index 19db81d378..21962f1b4d 100644 --- a/js/stat_test.ts +++ b/js/stat_test.ts @@ -1,10 +1,10 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -import { test, assert, assertEqual } from "./test_util.ts"; +import { testPerm, assert, assertEqual } from "./test_util.ts"; import * as deno from "deno"; // TODO Add tests for modified, accessed, and created fields once there is a way // to create temp files. -test(async function statSyncSuccess() { +testPerm({ read: true }, async function statSyncSuccess() { const packageInfo = deno.statSync("package.json"); assert(packageInfo.isFile()); assert(!packageInfo.isSymlink()); @@ -18,7 +18,19 @@ test(async function statSyncSuccess() { assert(!srcInfo.isSymlink()); }); -test(async function statSyncNotFound() { +testPerm({ read: false }, async function statSyncPerm() { + let caughtError = false; + try { + deno.statSync("package.json"); + } catch (e) { + caughtError = true; + assertEqual(e.kind, deno.ErrorKind.PermissionDenied); + assertEqual(e.name, "PermissionDenied"); + } + assert(caughtError); +}); + +testPerm({ read: true }, async function statSyncNotFound() { let caughtError = false; let badInfo; @@ -34,7 +46,7 @@ test(async function statSyncNotFound() { assertEqual(badInfo, undefined); }); -test(async function lstatSyncSuccess() { +testPerm({ read: true }, async function lstatSyncSuccess() { const packageInfo = deno.lstatSync("package.json"); assert(packageInfo.isFile()); assert(!packageInfo.isSymlink()); @@ -48,7 +60,19 @@ test(async function lstatSyncSuccess() { assert(!srcInfo.isSymlink()); }); -test(async function lstatSyncNotFound() { +testPerm({ read: false }, async function lstatSyncPerm() { + let caughtError = false; + try { + deno.lstatSync("package.json"); + } catch (e) { + caughtError = true; + assertEqual(e.kind, deno.ErrorKind.PermissionDenied); + assertEqual(e.name, "PermissionDenied"); + } + assert(caughtError); +}); + +testPerm({ read: true }, async function lstatSyncNotFound() { let caughtError = false; let badInfo; @@ -64,7 +88,7 @@ test(async function lstatSyncNotFound() { assertEqual(badInfo, undefined); }); -test(async function statSuccess() { +testPerm({ read: true }, async function statSuccess() { const packageInfo = await deno.stat("package.json"); assert(packageInfo.isFile()); assert(!packageInfo.isSymlink()); @@ -78,7 +102,19 @@ test(async function statSuccess() { assert(!srcInfo.isSymlink()); }); -test(async function statNotFound() { +testPerm({ read: false }, async function statPerm() { + let caughtError = false; + try { + await deno.stat("package.json"); + } catch (e) { + caughtError = true; + assertEqual(e.kind, deno.ErrorKind.PermissionDenied); + assertEqual(e.name, "PermissionDenied"); + } + assert(caughtError); +}); + +testPerm({ read: true }, async function statNotFound() { let caughtError = false; let badInfo; @@ -94,7 +130,7 @@ test(async function statNotFound() { assertEqual(badInfo, undefined); }); -test(async function lstatSuccess() { +testPerm({ read: true }, async function lstatSuccess() { const packageInfo = await deno.lstat("package.json"); assert(packageInfo.isFile()); assert(!packageInfo.isSymlink()); @@ -108,7 +144,19 @@ test(async function lstatSuccess() { assert(!srcInfo.isSymlink()); }); -test(async function lstatNotFound() { +testPerm({ read: false }, async function lstatPerm() { + let caughtError = false; + try { + await deno.lstat("package.json"); + } catch (e) { + caughtError = true; + assertEqual(e.kind, deno.ErrorKind.PermissionDenied); + assertEqual(e.name, "PermissionDenied"); + } + assert(caughtError); +}); + +testPerm({ read: true }, async function lstatNotFound() { let caughtError = false; let badInfo; diff --git a/js/symlink_test.ts b/js/symlink_test.ts index b552499f4f..ab84638780 100644 --- a/js/symlink_test.ts +++ b/js/symlink_test.ts @@ -1,8 +1,8 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -import { testPerm, assert, assertEqual } from "./test_util.ts"; +import { test, testPerm, assert, assertEqual } from "./test_util.ts"; import * as deno from "deno"; -testPerm({ write: true }, function symlinkSyncSuccess() { +testPerm({ read: true, write: true }, function symlinkSyncSuccess() { const testDir = deno.makeTempDirSync(); const oldname = testDir + "/oldname"; const newname = testDir + "/newname"; @@ -25,7 +25,7 @@ testPerm({ write: true }, function symlinkSyncSuccess() { } }); -testPerm({ write: false }, function symlinkSyncPerm() { +test(function symlinkSyncPerm() { let err; try { deno.symlinkSync("oldbaddir", "newbaddir"); @@ -47,7 +47,7 @@ testPerm({ write: true }, function symlinkSyncNotImplemented() { assertEqual(err.message, "Not implemented"); }); -testPerm({ write: true }, async function symlinkSuccess() { +testPerm({ read: true, write: true }, async function symlinkSuccess() { const testDir = deno.makeTempDirSync(); const oldname = testDir + "/oldname"; const newname = testDir + "/newname"; diff --git a/js/test_util.ts b/js/test_util.ts index d3af0f12e4..6791a5f3be 100644 --- a/js/test_util.ts +++ b/js/test_util.ts @@ -18,6 +18,7 @@ export { testing.setFilter(deno.args[1]); interface DenoPermissions { + read?: boolean; write?: boolean; net?: boolean; env?: boolean; @@ -25,24 +26,26 @@ interface DenoPermissions { } function permToString(perms: DenoPermissions): string { + const r = perms.read ? 1 : 0; const w = perms.write ? 1 : 0; const n = perms.net ? 1 : 0; const e = perms.env ? 1 : 0; - const r = perms.run ? 1 : 0; - return `permW${w}N${n}E${e}R${r}`; + const u = perms.run ? 1 : 0; + return `permR${r}W${w}N${n}E${e}U${u}`; } function permFromString(s: string): DenoPermissions { - const re = /^permW([01])N([01])E([01])R([01])$/; + const re = /^permR([01])W([01])N([01])E([01])U([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])), - env: Boolean(Number(found[3])), - run: Boolean(Number(found[4])) + read: Boolean(Number(found[1])), + write: Boolean(Number(found[2])), + net: Boolean(Number(found[3])), + env: Boolean(Number(found[4])), + run: Boolean(Number(found[5])) }; } @@ -52,7 +55,10 @@ export function testPerm(perms: DenoPermissions, fn: testing.TestFunction) { } export function test(fn: testing.TestFunction) { - testPerm({ write: false, net: false, env: false, run: false }, fn); + testPerm( + { read: false, write: false, net: false, env: false, run: false }, + fn + ); } test(function permSerialization() { @@ -60,8 +66,10 @@ test(function permSerialization() { for (const net of [true, false]) { for (const env of [true, false]) { for (const run of [true, false]) { - const perms: DenoPermissions = { write, net, env, run }; - testing.assertEqual(perms, permFromString(permToString(perms))); + for (const read of [true, false]) { + const perms: DenoPermissions = { write, net, env, run, read }; + testing.assertEqual(perms, permFromString(permToString(perms))); + } } } } diff --git a/js/truncate_test.ts b/js/truncate_test.ts index c0e1b163b7..2556dc76a8 100644 --- a/js/truncate_test.ts +++ b/js/truncate_test.ts @@ -16,7 +16,7 @@ async function readData(name: string): Promise { return text; } -testPerm({ write: true }, function truncateSyncSuccess() { +testPerm({ read: true, write: true }, function truncateSyncSuccess() { const enc = new TextEncoder(); const d = enc.encode("Hello"); const filename = deno.makeTempDirSync() + "/test_truncateSync.txt"; @@ -33,7 +33,7 @@ testPerm({ write: true }, function truncateSyncSuccess() { deno.removeSync(filename); }); -testPerm({ write: true }, async function truncateSuccess() { +testPerm({ read: true, write: true }, async function truncateSuccess() { const enc = new TextEncoder(); const d = enc.encode("Hello"); const filename = deno.makeTempDirSync() + "/test_truncate.txt"; diff --git a/js/write_file_test.ts b/js/write_file_test.ts index 7a43dec627..39f191842f 100644 --- a/js/write_file_test.ts +++ b/js/write_file_test.ts @@ -2,7 +2,7 @@ import { testPerm, assert, assertEqual } from "./test_util.ts"; import * as deno from "deno"; -testPerm({ write: true }, function writeFileSyncSuccess() { +testPerm({ read: true, write: true }, function writeFileSyncSuccess() { const enc = new TextEncoder(); const data = enc.encode("Hello"); const filename = deno.makeTempDirSync() + "/test.txt"; @@ -45,7 +45,7 @@ testPerm({ write: false }, function writeFileSyncPerm() { assert(caughtError); }); -testPerm({ write: true }, function writeFileSyncUpdatePerm() { +testPerm({ read: true, write: true }, function writeFileSyncUpdatePerm() { if (deno.platform.os !== "win") { const enc = new TextEncoder(); const data = enc.encode("Hello"); @@ -57,7 +57,7 @@ testPerm({ write: true }, function writeFileSyncUpdatePerm() { } }); -testPerm({ write: true }, function writeFileSyncCreate() { +testPerm({ read: true, write: true }, function writeFileSyncCreate() { const enc = new TextEncoder(); const data = enc.encode("Hello"); const filename = deno.makeTempDirSync() + "/test.txt"; @@ -81,7 +81,7 @@ testPerm({ write: true }, function writeFileSyncCreate() { assertEqual("Hello", actual); }); -testPerm({ write: true }, function writeFileSyncAppend() { +testPerm({ read: true, write: true }, function writeFileSyncAppend() { const enc = new TextEncoder(); const data = enc.encode("Hello"); const filename = deno.makeTempDirSync() + "/test.txt"; @@ -103,7 +103,7 @@ testPerm({ write: true }, function writeFileSyncAppend() { assertEqual("Hello", actual); }); -testPerm({ write: true }, async function writeFileSuccess() { +testPerm({ read: true, write: true }, async function writeFileSuccess() { const enc = new TextEncoder(); const data = enc.encode("Hello"); const filename = deno.makeTempDirSync() + "/test.txt"; @@ -114,7 +114,7 @@ testPerm({ write: true }, async function writeFileSuccess() { assertEqual("Hello", actual); }); -testPerm({ write: true }, async function writeFileNotFound() { +testPerm({ read: true, write: true }, async function writeFileNotFound() { const enc = new TextEncoder(); const data = enc.encode("Hello"); const filename = "/baddir/test.txt"; @@ -130,7 +130,7 @@ testPerm({ write: true }, async function writeFileNotFound() { assert(caughtError); }); -testPerm({ write: false }, async function writeFilePerm() { +testPerm({ read: true, write: false }, async function writeFilePerm() { const enc = new TextEncoder(); const data = enc.encode("Hello"); const filename = "/baddir/test.txt"; @@ -146,7 +146,7 @@ testPerm({ write: false }, async function writeFilePerm() { assert(caughtError); }); -testPerm({ write: true }, async function writeFileUpdatePerm() { +testPerm({ read: true, write: true }, async function writeFileUpdatePerm() { if (deno.platform.os !== "win") { const enc = new TextEncoder(); const data = enc.encode("Hello"); @@ -158,7 +158,7 @@ testPerm({ write: true }, async function writeFileUpdatePerm() { } }); -testPerm({ write: true }, async function writeFileCreate() { +testPerm({ read: true, write: true }, async function writeFileCreate() { const enc = new TextEncoder(); const data = enc.encode("Hello"); const filename = deno.makeTempDirSync() + "/test.txt"; @@ -182,7 +182,7 @@ testPerm({ write: true }, async function writeFileCreate() { assertEqual("Hello", actual); }); -testPerm({ write: true }, async function writeFileAppend() { +testPerm({ read: true, write: true }, async function writeFileAppend() { const enc = new TextEncoder(); const data = enc.encode("Hello"); const filename = deno.makeTempDirSync() + "/test.txt"; diff --git a/src/flags.rs b/src/flags.rs index d68bb5e09c..a680c8aa1d 100644 --- a/src/flags.rs +++ b/src/flags.rs @@ -23,6 +23,7 @@ pub struct DenoFlags { pub version: bool, pub reload: bool, pub recompile: bool, + pub allow_read: bool, pub allow_write: bool, pub allow_net: bool, pub allow_env: bool, @@ -89,6 +90,9 @@ fn set_recognized_flags( if matches.opt_present("recompile") { flags.recompile = true; } + if matches.opt_present("allow-read") { + flags.allow_read = true; + } if matches.opt_present("allow-write") { flags.allow_write = true; } @@ -102,9 +106,11 @@ fn set_recognized_flags( flags.allow_run = true; } if matches.opt_present("allow-all") { + flags.allow_read = true; flags.allow_env = true; flags.allow_net = true; flags.allow_run = true; + flags.allow_read = true; flags.allow_write = true; } if matches.opt_present("types") { @@ -142,6 +148,7 @@ pub fn set_flags( // TODO(kevinkassimo): v8_set_flags intercepts '-help' with single '-' // Resolve that and then uncomment line below (enabling Go style -long-flag) // opts.long_only(true); + opts.optflag("", "allow-read", "Allow file system read access."); opts.optflag("", "allow-write", "Allow file system write access."); opts.optflag("", "allow-net", "Allow network access."); opts.optflag("", "allow-env", "Allow environment access."); @@ -262,12 +269,27 @@ fn test_set_flags_7() { allow_net: true, allow_env: true, allow_run: true, + allow_read: true, allow_write: true, ..DenoFlags::default() } ) } +#[test] +fn test_set_flags_8() { + let (flags, rest, _) = + set_flags(svec!["deno", "gist.ts", "--allow-read"]).unwrap(); + assert_eq!(rest, svec!["deno", "gist.ts"]); + assert_eq!( + flags, + DenoFlags { + allow_read: true, + ..DenoFlags::default() + } + ) +} + // Returns args passed to V8, followed by args passed to JS fn v8_set_flags_preprocess(args: Vec) -> (Vec, Vec) { let (rest, mut v8_args) = diff --git a/src/isolate.rs b/src/isolate.rs index 3c9cec797d..fbe65a54e5 100644 --- a/src/isolate.rs +++ b/src/isolate.rs @@ -113,6 +113,11 @@ impl IsolateState { Arc::new(IsolateState::new(flags, rest_argv, None)) } + #[inline] + pub fn check_read(&self, filename: &str) -> DenoResult<()> { + self.permissions.check_read(filename) + } + #[inline] pub fn check_write(&self, filename: &str) -> DenoResult<()> { self.permissions.check_write(filename) diff --git a/src/main.rs b/src/main.rs index 3daea0d31f..25e68e3443 100644 --- a/src/main.rs +++ b/src/main.rs @@ -86,6 +86,7 @@ fn main() { if flags.fmt { rest_argv.insert(1, "https://deno.land/x/std/prettier/main.ts".to_string()); + flags.allow_read = true; flags.allow_write = true; } diff --git a/src/ops.rs b/src/ops.rs index 82d79fc2f2..86bc6efad3 100644 --- a/src/ops.rs +++ b/src/ops.rs @@ -685,10 +685,24 @@ fn op_open( } } - if mode != "r" { - // Write permission is needed except "r" mode - if let Err(e) = state.check_write(&filename_str) { - return odd_future(e); + match mode { + "r" => { + if let Err(e) = state.check_read(&filename_str) { + return odd_future(e); + } + } + "w" | "a" | "x" => { + if let Err(e) = state.check_write(&filename_str) { + return odd_future(e); + } + } + &_ => { + if let Err(e) = state.check_read(&filename_str) { + return odd_future(e); + } + if let Err(e) = state.check_write(&filename_str) { + return odd_future(e); + } } } @@ -862,15 +876,19 @@ fn op_remove( // Prototype https://github.com/denoland/deno/blob/golang/os.go#L171-L184 fn op_read_file( - _state: &Arc, + state: &Arc, base: &msg::Base<'_>, data: libdeno::deno_buf, ) -> Box { assert_eq!(data.len(), 0); let inner = base.inner_as_read_file().unwrap(); let cmd_id = base.cmd_id(); - let filename = PathBuf::from(inner.filename().unwrap()); + let filename_ = inner.filename().unwrap(); + let filename = PathBuf::from(filename_); debug!("op_read_file {}", filename.display()); + if let Err(e) = state.check_read(&filename_) { + return odd_future(e); + } blocking(base.sync(), move || { let vec = fs::read(&filename)?; // Build the response message. memcpy data into inner. @@ -902,10 +920,14 @@ fn op_copy_file( ) -> Box { assert_eq!(data.len(), 0); let inner = base.inner_as_copy_file().unwrap(); - let from = PathBuf::from(inner.from().unwrap()); + let from_ = inner.from().unwrap(); + let from = PathBuf::from(from_); let to_ = inner.to().unwrap(); let to = PathBuf::from(to_); + if let Err(e) = state.check_read(&from_) { + return odd_future(e); + } if let Err(e) = state.check_write(&to_) { return odd_future(e); } @@ -974,16 +996,21 @@ fn op_cwd( } fn op_stat( - _state: &Arc, + state: &Arc, base: &msg::Base<'_>, data: libdeno::deno_buf, ) -> Box { assert_eq!(data.len(), 0); let inner = base.inner_as_stat().unwrap(); let cmd_id = base.cmd_id(); - let filename = PathBuf::from(inner.filename().unwrap()); + let filename_ = inner.filename().unwrap(); + let filename = PathBuf::from(filename_); let lstat = inner.lstat(); + if let Err(e) = state.check_read(&filename_) { + return odd_future(e); + } + blocking(base.sync(), move || { let builder = &mut FlatBufferBuilder::new(); debug!("op_stat {} {}", filename.display(), lstat); @@ -1021,7 +1048,7 @@ fn op_stat( } fn op_read_dir( - _state: &Arc, + state: &Arc, base: &msg::Base<'_>, data: libdeno::deno_buf, ) -> Box { @@ -1030,6 +1057,10 @@ fn op_read_dir( let cmd_id = base.cmd_id(); let path = String::from(inner.path().unwrap()); + if let Err(e) = state.check_read(&path) { + return odd_future(e); + } + blocking(base.sync(), move || -> OpResult { debug!("op_read_dir {}", path); let builder = &mut FlatBufferBuilder::new(); @@ -1157,14 +1188,19 @@ fn op_symlink( } fn op_read_link( - _state: &Arc, + state: &Arc, base: &msg::Base<'_>, data: libdeno::deno_buf, ) -> Box { assert_eq!(data.len(), 0); let inner = base.inner_as_readlink().unwrap(); let cmd_id = base.cmd_id(); - let name = PathBuf::from(inner.name().unwrap()); + let name_ = inner.name().unwrap(); + let name = PathBuf::from(name_); + + if let Err(e) = state.check_read(&name_) { + return odd_future(e); + } blocking(base.sync(), move || -> OpResult { debug!("op_read_link {}", name.display()); diff --git a/src/permissions.rs b/src/permissions.rs index c05ea4ee50..8546e4ee94 100644 --- a/src/permissions.rs +++ b/src/permissions.rs @@ -12,6 +12,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; #[cfg_attr(feature = "cargo-clippy", allow(stutter))] #[derive(Debug, Default)] pub struct DenoPermissions { + pub allow_read: AtomicBool, pub allow_write: AtomicBool, pub allow_net: AtomicBool, pub allow_env: AtomicBool, @@ -21,6 +22,7 @@ pub struct DenoPermissions { impl DenoPermissions { pub fn new(flags: &DenoFlags) -> Self { Self { + allow_read: AtomicBool::new(flags.allow_read), allow_write: AtomicBool::new(flags.allow_write), allow_env: AtomicBool::new(flags.allow_env), allow_net: AtomicBool::new(flags.allow_net), @@ -40,6 +42,21 @@ impl DenoPermissions { r } + pub fn check_read(&self, filename: &str) -> DenoResult<()> { + if self.allow_read.load(Ordering::SeqCst) { + return Ok(()); + }; + // TODO get location (where access occurred) + let r = permission_prompt(&format!( + "Deno requests read access to \"{}\".", + filename + ));; + if r.is_ok() { + self.allow_read.store(true, Ordering::SeqCst); + } + r + } + pub fn check_write(&self, filename: &str) -> DenoResult<()> { if self.allow_write.load(Ordering::SeqCst) { return Ok(()); diff --git a/tests/016_double_await.test b/tests/016_double_await.test index 0e5c1babce..35c0288100 100644 --- a/tests/016_double_await.test +++ b/tests/016_double_await.test @@ -1,2 +1,2 @@ -args: tests/016_double_await.ts --reload +args: tests/016_double_await.ts --allow-read --reload output: tests/016_double_await.ts.out diff --git a/tools/fmt_test.py b/tools/fmt_test.py index 95733dc209..c8eb83399f 100755 --- a/tools/fmt_test.py +++ b/tools/fmt_test.py @@ -18,7 +18,9 @@ def fmt_test(deno_exe): # Set DENO_DIR to //js/ so we don't have to rely on an intenet # connection to download https://deno.land/x/std/prettier/main.ts deno_dir = os.path.join(root_path, "js") - run([deno_exe, dst, "--fmt"], merge_env={"DENO_DIR": deno_dir}) + run( + [deno_exe, dst, "--fmt", "--allow-read"], + merge_env={"DENO_DIR": deno_dir}) with open(fixed_filename) as f: expected = f.read() with open(dst) as f: @@ -31,4 +33,3 @@ def fmt_test(deno_exe): if __name__ == "__main__": fmt_test(sys.argv[1]) - diff --git a/tools/test_format.py b/tools/test_format.py index 69c0505018..2e26f21990 100755 --- a/tools/test_format.py +++ b/tools/test_format.py @@ -30,7 +30,7 @@ def main(): print "No available deno executable." sys.exit(1) - util.run([deno_path, "--allow-run", "tools/format.ts"]) + util.run([deno_path, "--allow-read", "--allow-run", "tools/format.ts"]) output = util.run_output( ["git", "status", "-uno", "--porcelain", "--ignore-submodules"]) if len(output) > 0: diff --git a/tools/throughput_benchmark.py b/tools/throughput_benchmark.py index 4f1c02449a..9048d3582d 100755 --- a/tools/throughput_benchmark.py +++ b/tools/throughput_benchmark.py @@ -19,7 +19,7 @@ ADDR = "127.0.0.1:4544" def cat(deno_exe, megs): size = megs * MB start = time.time() - cmd = deno_exe + " tests/cat.ts /dev/zero | head -c %s " % size + cmd = deno_exe + " tests/cat.ts --allow-read /dev/zero | head -c %s " % size print cmd subprocess.check_output(cmd, shell=True) end = time.time() diff --git a/tools/unit_tests.py b/tools/unit_tests.py index 3f9956d435..542db46425 100755 --- a/tools/unit_tests.py +++ b/tools/unit_tests.py @@ -43,12 +43,14 @@ def run_unit_test(deno_exe, permStr, flags=None): # 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_unit_test(deno_exe, "permW0N0E0R0") - run_unit_test(deno_exe, "permW1N0E0R0", ["--allow-write"]) - run_unit_test(deno_exe, "permW0N1E0R0", ["--allow-net"]) - run_unit_test(deno_exe, "permW0N0E1R0", ["--allow-env"]) - run_unit_test(deno_exe, "permW0N0E0R1", ["--allow-run"]) - run_unit_test(deno_exe, "permW1N0E0R1", ["--allow-run", "--allow-write"]) + run_unit_test(deno_exe, "permR0W0N0E0U0") + run_unit_test(deno_exe, "permR1W0N0E0U0", ["--allow-read"]) + run_unit_test(deno_exe, "permR0W1N0E0U0", ["--allow-write"]) + run_unit_test(deno_exe, "permR1W1N0E0U0", ["--allow-read", "--allow-write"]) + run_unit_test(deno_exe, "permR1W0N1E0U0", ["--allow-read", "--allow-net"]) + run_unit_test(deno_exe, "permR0W0N0E1U0", ["--allow-env"]) + run_unit_test(deno_exe, "permR0W0N0E0U1", ["--allow-run"]) + run_unit_test(deno_exe, "permR0W1N0E0U1", ["--allow-run", "--allow-write"]) # TODO We might accidentally miss some. We should be smarter about which we # run. Maybe we can use the "filtered out" number to check this. diff --git a/tools/util_test.ts b/tools/util_test.ts index 79868b4401..5a1d336172 100644 --- a/tools/util_test.ts +++ b/tools/util_test.ts @@ -1,5 +1,6 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -import { test, assertEqual } from "../js/test_util.ts"; +import * as deno from "deno"; +import { assert, testPerm, assertEqual } from "../js/test_util.ts"; import { findFiles } from "./util.ts"; const testDir = "tools/testdata/find_files_testdata"; @@ -7,7 +8,7 @@ const testDir = "tools/testdata/find_files_testdata"; // Sorts and replace backslashes with slashes. const normalize = files => files.map(f => f.replace(/\\/g, "/")).sort(); -test(function testFindFiles() { +testPerm({ read: true }, function testFindFiles() { const files = findFiles([testDir], [".ts", ".md"]); assertEqual(normalize(files), [ `${testDir}/bar.md`, @@ -23,7 +24,7 @@ test(function testFindFiles() { ]); }); -test(function testFindFilesDepth() { +testPerm({ read: true }, function testFindFilesDepth() { const files = findFiles([testDir], [".ts", ".md"], { depth: 1 }); assertEqual(normalize(files), [ `${testDir}/bar.md`, @@ -33,7 +34,7 @@ test(function testFindFilesDepth() { ]); }); -test(function testFindFilesSkip() { +testPerm({ read: true }, function testFindFilesSkip() { const files = findFiles([testDir], [".ts", ".md"], { skip: ["foo.md", "subdir1"] }); @@ -47,3 +48,15 @@ test(function testFindFilesSkip() { `${testDir}/subdir0/subdir0/foo.ts` ]); }); + +testPerm({ read: false }, function testFindFilesPerm() { + let caughtError = false; + try { + const files = findFiles([testDir], [".ts", ".md"]); + } catch (e) { + caughtError = true; + assertEqual(e.kind, deno.ErrorKind.PermissionDenied); + assertEqual(e.name, "PermissionDenied"); + } + assert(caughtError); +});