Use express-validator, refactor
This commit is contained in:
parent
a0693fb3a2
commit
e80bb0372b
|
@ -10,6 +10,7 @@
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "^4.17.1"
|
"express": "^4.17.1",
|
||||||
|
"express-validator": "^6.9.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
};
|
165
api/src/index.js
165
api/src/index.js
|
@ -1,110 +1,63 @@
|
||||||
const { writeFile } = require('fs/promises');
|
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const app = express();
|
const { execute } = require('./execute');
|
||||||
const languages = require('./languages');
|
const { languages } = require('./languages');
|
||||||
const { spawn } = require('child_process');
|
const { checkSchema, validationResult } = require('express-validator');
|
||||||
|
|
||||||
{
|
|
||||||
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 PORT = 2000;
|
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}`));
|
app.listen(PORT, () => console.log(`Listening on port ${PORT}`));
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
module.exports = [
|
const { spawn } = require('child_process');
|
||||||
|
|
||||||
|
const languages = [
|
||||||
{
|
{
|
||||||
name: 'nasm',
|
name: 'nasm',
|
||||||
aliases: ['asm', 'nasm'],
|
aliases: ['asm', 'nasm'],
|
||||||
|
@ -112,3 +114,40 @@ module.exports = [
|
||||||
aliases: ['ts', 'typescript'],
|
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,
|
||||||
|
};
|
||||||
|
|
|
@ -4,11 +4,7 @@ dir="$( cd "$( dirname "$0" )" && pwd )"
|
||||||
|
|
||||||
touch $dir/lockfile
|
touch $dir/lockfile
|
||||||
|
|
||||||
if [ -z "$1" ]; then
|
if [ -z "$1" ] || [ -z "$2" ]; then
|
||||||
echo "invalid args"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
if [ -z "$2" ]; then
|
|
||||||
echo "invalid args"
|
echo "invalid args"
|
||||||
exit
|
exit
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -167,7 +167,7 @@ Content-Type: application/json
|
||||||
- typescript
|
- typescript
|
||||||
|
|
||||||
#### Principle of Operation
|
#### 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
|
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.
|
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.).
|
The source file is either ran or compiled and ran (in the case of languages like c, c++, c#, go, etc.).
|
||||||
|
|
Loading…
Reference in New Issue