Skip to content

Commit

Permalink
feat: convert fs to use core
Browse files Browse the repository at this point in the history
Convert the fs module and its example(s) to use the recently-introduced core
module.
  • Loading branch information
denizenging committed Sep 14, 2023
1 parent d8c7c6d commit 99cc59d
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 39 deletions.
3 changes: 2 additions & 1 deletion pkg/fs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"dependencies": {
"@slangroom/deps": "workspace:*",
"@slangroom/shared": "workspace:*",
"@slangroom/ignored": "workspace:*"
"@slangroom/ignored": "workspace:*",
"@slangroom/core": "workspace:*"
},
"repository": "https://github.com/dyne/slangroom",
"license": "AGPL-3.0-only",
Expand Down
39 changes: 39 additions & 0 deletions pkg/fs/src/plugins.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { visit } from '@slangroom/fs/visitor';
import { AfterPlugin } from '@slangroom/core/plugin';
import * as path from 'node:path';
import * as fs from 'node:fs/promises';

/**
* A directory that is safe to write to and read from.
*
* Care must be taken to not allow writes and reads outside of this directory.
*/
export const SandboxDir = '/tmp/slangroom';

export const ThenISaveStringIntoTheFile = new AfterPlugin(async ({ statement, result }) => {
const ast = visit(statement);
// TODO: if `visit()` fails, exit (return)

const filename = result[ast.filename] as string;
const content = result[ast.content] as string;
const normalized = path.normalize(filename);
// "/" and ".." prevents directory traversal
const does_directory_traversal = normalized.startsWith('/') || normalized.startsWith('..');
// "." ensures that "foo/bar" or "./foo" is valid, while "." isn't
// (that is, no "real" filepath is provided)
const doesnt_provide_file = normalized.startsWith('.');
if (does_directory_traversal || doesnt_provide_file) return; // TODO: instead of ignoring, do we wanna error out?
// here onward, we're sure `filepath` is under `SandboxDir`
const filepath = path.join(SandboxDir, normalized);

// these following two lines allow subdirs to be created if
// `normalized` contains any (or `SandboxDir` to be created if it
// doesn't exist already)
const dirname = path.dirname(filepath);
await fs.mkdir(dirname, { recursive: true });

const fhandle = await fs.open(filepath, 'wx');
await fhandle.write(content);
});

export const allPlugins = new Set([ThenISaveStringIntoTheFile]);
53 changes: 53 additions & 0 deletions pkg/fs/test/plugins.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import test from 'ava';
import { allPlugins, SandboxDir } from '@slangroom/fs/plugins';
import { Slangroom } from '@slangroom/core';
import * as fs from 'node:fs/promises';
import * as path from 'node:path';

test('requires a real path', async (t) => {
const slang = new Slangroom(allPlugins);
const contract = `Rule unknown ignore
Given I have a 'string' named 'nameOfTheFile'
When I create the random object of '64' bits
When I rename the 'random_object' to 'stringToWrite'
Then I save the 'stringToWrite' into the file 'nameOfTheFile'
Then I print the 'stringToWrite'
Then I print the 'nameOfTheFile'
`;
{
const params = { data: { nameOfTheFile: '../trying/to/h4ck' } };
const zout = await slang.execute(contract, params);
const nameOfTheFile = zout.result['nameOfTheFile'] as string;
await t.throwsAsync(fs.readFile(path.resolve(SandboxDir, nameOfTheFile)));
}

{
const params = { data: { nameOfTheFile: '/trying/to/h4ck' } };
const zout = await slang.execute(contract, params);
const nameOfTheFile = zout.result['nameOfTheFile'] as string;
await t.throwsAsync(fs.readFile(path.resolve(SandboxDir, nameOfTheFile)));
}
});

test('keyholder works', async (t) => {
const slang = new Slangroom(allPlugins);
const contract = `Rule unknown ignore
Given I have a 'string' named 'nameOfTheFile'
When I create the random object of '64' bits
When I rename the 'random_object' to 'stringToWrite'
Then I save the 'stringToWrite' into the file 'nameOfTheFile'
Then I print the 'stringToWrite'
Then I print the 'nameOfTheFile'
`;
const randomStr = (Math.random() + 1).toString(36).substring(7);
const params = { data: { nameOfTheFile: randomStr } };
const zout = await slang.execute(contract, params);
const nameOfTheFile = zout.result['nameOfTheFile'] as string;
const stringToWrite = zout.result['stringToWrite'] as string;
const buf = await fs.readFile(path.resolve(SandboxDir, nameOfTheFile));
t.is(buf.toString(), stringToWrite);
});
38 changes: 0 additions & 38 deletions pkg/fs/test/visitor.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import test from 'ava';
import { visit } from '@slangroom/fs/visitor';
import { getIgnoredStatements } from '@slangroom/ignored';
import { zencodeExec } from '@slangroom/shared';
import * as fs from 'node:fs/promises';
import * as path from 'node:path';

test('ast is correct with one statement', async (t) => {
// Given I have a contract with one filesystems statement in it
Expand Down Expand Up @@ -99,38 +96,3 @@ Then I save the 'stringToWrite2' into the file 'nameOfTheFile2'
// And the value indexed by its filename in data must be data's nameOfTheFile2
t.is(data[third?.filename as 'nameOfTheFile2'], data.nameOfTheFile2);
});

test('keyholder works', async (t) => {
// Given I have a contract with one filesystems statement in it
const contract = `Rule unknown ignore
Given I have a 'string' named 'nameOfTheFile'
When I create the random object of '64' bits
When I rename the 'random_object' to 'stringToWrite'
Then I save the 'stringToWrite' into the file 'nameOfTheFile'
Then I print the 'stringToWrite'
Then I print the 'nameOfTheFile'
`;
// And the params used in the contract
const params = { data: { nameOfTheFile: 'hello-world.txt' } };
// When I get the ignored statement of it
const ignoreds = await getIgnoredStatements(contract, params);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const ast = visit(ignoreds[0]!);
// And execute the contract
const zout = await zencodeExec(contract, params);
// And get the values of the identifiers pointed by filename and content of
// the ignored statement
const nameOfTheFile = zout.result[ast.filename] as string;
const stringToWrite = zout.result[ast.content] as string;
// And create /tmp/slangroom
await fs.mkdir('/tmp/slangroom/', { recursive: true });
// And open file named after nameOfTheFile for writing (and create if it
// doesn't already exists
const fh = await fs.open(path.resolve('/tmp/slangroom/', nameOfTheFile), 'w');
// And write the value of stringToWrite to the file
const { buffer } = await fh.write(stringToWrite);
// Then the content of the file must be stringToWrite
t.is(buffer, stringToWrite);
});
12 changes: 12 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 99cc59d

Please sign in to comment.