Use express-validator, refactor

This commit is contained in:
Vrganj 2021-01-16 00:53:51 +01:00
parent a0693fb3a2
commit e80bb0372b
6 changed files with 261 additions and 226 deletions

View File

@ -10,6 +10,7 @@
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.17.1"
"express": "^4.17.1",
"express-validator": "^6.9.2"
}
}

46
api/src/execute.js Normal file
View File

@ -0,0 +1,46 @@
const { writeFile } = require('fs/promises');
const { spawn } = require('child_process');
async function execute(res, language, body) {
const stamp = new Date().getTime();
const sourceFile = `/tmp/${stamp}.code`;
await writeFile(sourceFile, body.source);
const process = spawn(__dirname + '/../../lxc/execute', [
language.name,
sourceFile,
body.args?.join('\n') ?? '',
]);
const result = {
ran: true,
language: language.name,
stderr: '',
stdout: '',
output: '',
};
if (language.version)
result.version = language.version;
process.stderr.on('data', chunk => {
result.stderr += chunk;
result.output += chunk;
});
process.stdout.on('data', chunk => {
result.stdout += chunk;
result.output += chunk;
});
result.stderr = result.stderr.trim().substring(0, 65535);
result.stdout = result.stdout.trim().substring(0, 65535);
result.output = result.output.trim().substring(0, 65535);
process.on('exit', () => res.json(result));
}
module.exports = {
execute,
};

View File

@ -1,110 +1,63 @@
const { writeFile } = require('fs/promises');
const express = require('express');
const app = express();
const languages = require('./languages');
const { spawn } = require('child_process');
{
const process = spawn(__dirname + '/../../lxc/versions');
let output = '';
process.stderr.on('data', chunk => output += chunk);
process.stdout.on('data', chunk => output += chunk);
process.on('exit', () => {
const sections = output.toLowerCase().split('---');
const versions = {};
for (const section of sections) {
const lines = section.trim().split('\n');
if (lines.length >= 2) {
const language = lines[0];
if (language === 'java') {
versions[language] = /\d+/.exec(lines[1])?.[0];
} else if (language === 'emacs') {
versions[language] = /\d+\.\d+/.exec(lines[1])?.[0];
} else {
versions[language] = /\d+\.\d+\.\d+/.exec(section)?.[0];
}
}
}
for (const language of languages) {
language.version = versions[language.name];
}
});
}
app.use(express.json());
app.post('/execute', (req, res) => {
const body = req.body;
const language = languages.find(language => {
return language.aliases.includes(body.language?.toString()?.toLowerCase());
});
if (!language) {
return res.status(400).json({
code: 'unsupported_language',
message: `${body.language} is not supported by Piston`,
});
} else if (typeof body.source !== 'string') {
return res.status(400).json({
code: 'missing_source',
message: 'source field is invalid',
});
} else if (body.args && !Array.isArray(body.args)) {
return res.status(400).json({
code: 'invalid_args',
message: 'args field is not an array',
});
}
launch(res, language, body);
});
async function launch(res, language, body) {
const stamp = new Date().getTime();
const sourceFile = `/tmp/${stamp}.code`;
await writeFile(sourceFile, body.source);
const process = spawn(__dirname + '/../../lxc/execute', [language.name, sourceFile, (body.args ?? []).join('\n')]);
const result = {
ran: true,
language: language.name,
stderr: '',
stdout: '',
output: '',
};
if (language.version)
result.version = language.version;
process.stderr.addListener('data', chunk => {
result.stderr += chunk;
result.output += chunk;
});
process.stdout.addListener('data', chunk => {
result.stdout += chunk;
result.output += chunk;
});
result.stderr = result.stderr.trim().substring(0, 65535);
result.stdout = result.stdout.trim().substring(0, 65535);
result.output = result.output.trim().substring(0, 65535);
process.on('exit', () => res.json(result));
}
app.get('/versions', (req, res) => {
res.json(languages);
});
const { execute } = require('./execute');
const { languages } = require('./languages');
const { checkSchema, validationResult } = require('express-validator');
const PORT = 2000;
const app = express();
app.use(express.json());
app.post(
'/execute',
checkSchema({
language: {
in: 'body',
notEmpty: {
errorMessage: 'Supply a language field',
},
isString: {
errorMessage: 'Supplied language is not a string',
},
custom: {
options: value => languages.find(language => language.name === value?.toLowerCase()),
errorMessage: 'Supplied language is not supported by Piston',
},
},
source: {
in: 'body',
notEmpty: {
errorMessage: 'Supply a source field',
},
isString: {
errorMessage: 'Supplied source is not a string',
},
},
args: {
in: 'body',
optional: true,
isArray: {
errorMessage: 'Supplied args is not an array',
},
}
}),
(req, res) => {
const errors = validationResult(req).array();
if (errors.length === 0) {
const language = languages.find(language =>
language.aliases.includes(req.body.language.toLowerCase())
);
execute(res, language, req.body);
} else {
res.status(400).json({
message: errors[0].msg,
});
}
},
);
app.get('/versions', (_, res) => res.json(languages));
app.listen(PORT, () => console.log(`Listening on port ${PORT}`));

View File

@ -1,4 +1,6 @@
module.exports = [
const { spawn } = require('child_process');
const languages = [
{
name: 'nasm',
aliases: ['asm', 'nasm'],
@ -112,3 +114,40 @@ module.exports = [
aliases: ['ts', 'typescript'],
},
];
{
const process = spawn(__dirname + '/../../lxc/versions');
let output = '';
process.stderr.on('data', chunk => output += chunk);
process.stdout.on('data', chunk => output += chunk);
process.on('exit', () => {
const sections = output.toLowerCase().split('---');
const versions = {};
for (const section of sections) {
const lines = section.trim().split('\n');
if (lines.length >= 2) {
const language = lines[0];
if (language === 'java') {
versions[language] = /\d+/.exec(lines[1])?.[0];
} else if (language === 'emacs') {
versions[language] = /\d+\.\d+/.exec(lines[1])?.[0];
} else {
versions[language] = /\d+\.\d+\.\d+/.exec(section)?.[0];
}
}
}
for (const language of languages) {
language.version = versions[language.name];
}
});
}
module.exports = {
languages,
};

View File

@ -4,11 +4,7 @@ dir="$( cd "$( dirname "$0" )" && pwd )"
touch $dir/lockfile
if [ -z "$1" ]; then
echo "invalid args"
exit
fi
if [ -z "$2" ]; then
if [ -z "$1" ] || [ -z "$2" ]; then
echo "invalid args"
exit
fi

View File

@ -167,7 +167,7 @@ Content-Type: application/json
- typescript
#### Principle of Operation
Piston utilizes LXC as the primary mechanism for sandboxing. There is a small API written in Go which takes
Piston utilizes LXC as the primary mechanism for sandboxing. There is a small API written in Node which takes
in execution requests and executes them in the container. High level, the API writes
a temporary source and args file to `/tmp` and that gets mounted read-only along with the execution scripts into the container.
The source file is either ran or compiled and ran (in the case of languages like c, c++, c#, go, etc.).