Initial commit

This commit is contained in:
abrendan
2023-11-30 14:15:19 +00:00
commit e4599df811
5457 changed files with 500139 additions and 0 deletions

16
node_modules/server/plugins/compress/index.js generated vendored Normal file
View File

@@ -0,0 +1,16 @@
const modern = require('../../src/modern');
const compress = require('compression');
module.exports = {
name: 'compress',
options: {
__root: 'compress',
compress: {
default: {},
type: Object
}
},
// The whole plugin won't be loaded if the option is false
before: ctx => modern(compress(ctx.options.compress))(ctx)
};

View File

@@ -0,0 +1,18 @@
const run = require('server/test/run');
describe('compress', () => {
it('works with the defaults', async () => {
const res = await run(() => 'Hello world').get('/');
expect(res.body).toBe('Hello world');
});
it('works with an empty option object', async () => {
const res = await run({ compress: {} }, () => 'Hello world').get('/');
expect(res.body).toBe('Hello world');
});
it('works without compress', async () => {
const res = await run({ compress: false }, () => 'Hello world').get('/');
expect(res.body).toBe('Hello world');
});
});

68
node_modules/server/plugins/express/index.js generated vendored Normal file
View File

@@ -0,0 +1,68 @@
const express = require('express');
module.exports = {
name: 'express',
options: {
// See these in-depth in https://expressjs.com/en/api.html#app.set
'case sensitive routing': {},
'env': {
inherit: 'env'
},
'etag': {},
'jsonp callback name': {},
'json replacer': {},
'json spaces': {},
'query parser': {},
'strict routing': {},
'subdomain offset': {},
'trust proxy': {},
'views': {
default: 'views',
inherit: true,
type: String,
folder: true
},
'view cache': {},
'view engine': {
inherit: 'engine'
},
'x-powered-by': {}
},
init: ctx => {
ctx.express = express;
ctx.app = ctx.express();
// Go through all of the options and set the right ones
for (let key in ctx.options.express) {
let value = ctx.options.express[key];
if (typeof value !== 'undefined') {
ctx.app.set(key, value);
}
}
// Accept HTML as a render extension
ctx.app.engine('html', require('hbs').__express);
if (ctx.options.engine) {
// If it's an object, expect a { engine: { engineName: engineFN } }
if (typeof ctx.options.engine === 'object') {
for (let name in ctx.options.engine) {
ctx.app.engine(name, ctx.options.engine[name]);
ctx.app.set('view engine', name);
}
} else { // Simple case like { engine: 'pug' }
ctx.app.set('view engine', ctx.options.engine);
}
}
},
listen: ctx => new Promise((resolve, reject) => {
ctx.server = ctx.app.listen(ctx.options.port, () => {
ctx.log.debug(`Server started on http://localhost:${ctx.options.port}/`);
resolve();
});
ctx.close = () => new Promise((res, rej) => {
ctx.server.close(err => err ? rej(err) : res());
});
ctx.server.on('error', err => reject(err));
})
};

View File

@@ -0,0 +1,56 @@
const server = require('../../server');
const { status } = server.reply;
// Test runner:
const run = require('server/test/run');
describe('express', () => {
it('is defined', () => {
server(parseInt(1000 + Math.random() * 10000)).then(ctx => {
expect(ctx.app).toBeDefined();
ctx.close();
});
});
it('accepts the options', async () => {
const options = {
'case sensitive routing': true,
'etag': 'strong',
'jsonp callback name': 'abc',
'subdomain offset': 1,
'trust proxy': true,
'view cache': true,
'x-powered-by': false
};
const res = await run({ express: options }, ctx => {
for (let key in options) {
expect(ctx.app.get(key)).toBe(options[key]);
}
return status(200);
}).get('/');
expect(res.status).toBe(200);
expect(res.body).toBe('');
});
it('ignores the view engine (use .engine instead)', async () => {
const res = await run({ express: { 'view engine': 'abc' } }, ctx => {
expect(ctx.app.get('env')).toBe('test');
expect(ctx.app.get('view engine')).toBe('pug');
return status(200);
}).get('/');
expect(res.status).toBe(200);
expect(res.body).toBe('');
});
it.skip('uses an engine', async () => {
const res = run({
express: { engine: {
blabla: 'I do not know how to make an engine yet'
}}
}).get('/');
expect(res.status).toBe(200);
expect(res.body).toBe('');
});
});

21
node_modules/server/plugins/favicon/index.js generated vendored Normal file
View File

@@ -0,0 +1,21 @@
const modern = require('../../src/modern');
const favicon = require('serve-favicon');
module.exports = {
name: 'favicon',
options: {
__root: 'location',
location: {
type: String,
file: true,
env: 'FAVICON'
}
},
before: [
ctx => {
if (!ctx.options.favicon.location) return false;
return modern(favicon(ctx.options.favicon.location))(ctx);
}
]
};

View File

@@ -0,0 +1,16 @@
const run = require('server/test/run');
const favicon = 'test/logo.png';
describe('Default modules', () => {
it('favicon', async () => {
const res = await run({ favicon }).get('/favicon.ico');
expect(res.headers['content-type']).toBe('image/x-icon');
});
// TODO: test for non-existing
// TODO: test different locations
// TODO: test for env
});

40
node_modules/server/plugins/final/errors.js generated vendored Normal file
View File

@@ -0,0 +1,40 @@
const error = require('../../error')('/plugin/final/');
error.noreturn = ({ method, url }) => `
Your middleware did not return anything for this request:
${method} ${url}
This normally happens when no route was matched or if the router did not reply with anything. Make sure to return something, even if it's a catch-all error.
Documentation for reply: https://serverjs.io/documentation/reply/
Relevant issue: https://github.com/franciscop/server/issues/118
`;
error.unhandled = `
Some middleware threw an error that was not handled properly. This can happen when you do this:
~~~
// BAD:
server(ctx => { throw new Error('I am an error!'); });
~~~
To catch and handle these types of errors, add a route to the end of your middlewares to handle errors like this:
~~~
// GOOD:
const { error } = server.router;
const { status } = server.reply;
server(
ctx => { throw new Error('I am an error!'); },
// ...
error(ctx => status(500).send(ctx.error.message))
);
~~~
Please feel free to open an issue in Github asking for more info:
https://github.com/franciscop/server
`;
module.exports = error;

68
node_modules/server/plugins/final/final.test.js generated vendored Normal file
View File

@@ -0,0 +1,68 @@
const run = require('server/test/run');
// Note: the `raw` option only works for tests
const storeLog = out => ({ report: { write: log => { out.log = log; } } });
describe('final', () => {
it('gets called with an unhandled error', async () => {
const simple = () => { throw new Error('Hello Error'); };
const out = {};
const res = await run({ raw: true, log: storeLog(out) }, simple).get('/');
expect(res.statusCode).toBe(500);
expect(res.body).toBe('Internal Server Error');
expect(out.log).toMatch('Hello Error');
});
it('just logs it if the headers were already sent', async () => {
const simple = () => { throw new Error('Hello Error'); };
const out = {};
const res = await run({ raw: true, log: storeLog(out) }, () => 'Hello world', simple).get('/');
expect(res.statusCode).toBe(200);
expect(res.body).toBe('Hello world');
expect(out.log).toMatch('Hello Error');
});
it('displays the appropriate error to the public', async () => {
const simple = () => {
const err = new Error('Hello Error: display to the public');
err.public = true;
throw err;
};
const out = {};
const res = await run({ raw: true, log: storeLog(out) }, simple).get('/');
expect(res.statusCode).toBe(500);
expect(res.body).toBe('Hello Error: display to the public');
expect(out.log).toMatch('Hello Error');
});
it('makes the status 500 if it is invalid', async () => {
const simple = () => {
const err = new Error('Hello Error');
err.status = 'pepito';
throw err;
};
const out = {};
const res = await run({ raw: true, log: storeLog(out) }, simple).get('/');
expect(res.statusCode).toBe(500);
expect(res.body).toBe('Internal Server Error');
expect(out.log).toMatch('Hello Error');
});
it('does not reply if the headers are already sent', async () => {
const simple = ctx => {
ctx.res.send('Error 世界');
throw new Error('Hello');
};
const res = await run(simple).get('/');
expect(res.body).toBe('Error 世界');
});
it('handles non-existing requests to a 404', async () => {
const out = {};
const res = await run({ log: storeLog(out) }).get('/non-existing');
expect(res.statusCode).toBe(404);
expect(out.log).toMatch(/did not return anything/);
});
});

44
node_modules/server/plugins/final/index.js generated vendored Normal file
View File

@@ -0,0 +1,44 @@
// This file makes sure to clean up things in case there was something missing
// There are two reasons normally for this to happen: no reply was set or an
// unhandled error was thrown
const FinalError = require('./errors');
// Make sure that a (404) reply is sent if there was no user reply
const handler = async ctx => {
if (!ctx.res.headersSent) {
// Send the user-set status
ctx.res.status(ctx.res.explicitStatus ? ctx.res.statusCode : 404).send();
// Show it only if there was no status set in a return
if (!ctx.res.explicitStatus) {
ctx.log.error(
new FinalError('noreturn', { url: ctx.url, method: ctx.method })
);
}
}
};
// Make sure there is a (500) reply if there was an unhandled error thrown
handler.error = ctx => {
const error = ctx.error;
ctx.log.warning(FinalError('unhandled'));
ctx.log.error(error);
if (!ctx.res.headersSent) {
let status = error.status || error.code || 500;
if (typeof status !== 'number') status = 500;
// Display the error message if this error is marked as public
if (error.public) {
return ctx.res.status(status).send(error.message);
}
// Otherwise just display the default error for that code
ctx.res.sendStatus(status);
}
};
module.exports = {
name: 'final',
after: handler
};
// module.exports = handler;

33
node_modules/server/plugins/log/index.js generated vendored Normal file
View File

@@ -0,0 +1,33 @@
const Log = require('log');
const valid = [
'emergency',
'alert',
'critical',
'error',
'warning',
'notice',
'info',
'debug'
];
// Log plugin
const plugin = {
name: 'log',
options: {
__root: 'level',
level: {
default: 'info',
type: String,
enum: valid
},
report: {
default: process.stdout
}
},
init: ctx => {
ctx.log = new Log(ctx.options.log.level, ctx.options.log.report);
}
};
module.exports = plugin;

47
node_modules/server/plugins/log/integration.test.js generated vendored Normal file
View File

@@ -0,0 +1,47 @@
const server = require('../../server');
const { status } = server.reply;
const ConfigError = require('server/src/config/errors');
// Test runner:
const run = require('server/test/run');
describe('log()', () => {
it('is defined', () => {
server(parseInt(1000 + Math.random() * 10000)).then(ctx => {
expect(ctx.log).toBeDefined();
ctx.close();
});
});
it('is inside the middleware', async () => {
const res = await run(ctx => status(ctx.log ? 200 : 500)).get('/');
expect(res.statusCode).toBe(200);
});
it('has the right methods', async () => {
const res = await run(ctx => {
expect(ctx.log.emergency).toBeDefined();
expect(ctx.log.alert).toBeDefined();
expect(ctx.log.critical).toBeDefined();
expect(ctx.log.error).toBeDefined();
expect(ctx.log.warning).toBeDefined();
expect(ctx.log.notice).toBeDefined();
expect(ctx.log.info).toBeDefined();
expect(ctx.log.debug).toBeDefined();
return status(200);
}).get('/');
expect(res.statusCode).toBe(200);
});
it('rejects invalid log levels', async () => {
const res = run({ log: 'abc' }).get('/');
// Now errors must be fully qualified with Jest
expect(res).rejects.toMatchObject(new ConfigError('enum', {
name: 'level',
value: 'abc',
possible: ['emergency','alert','critical','error','warning','notice','info','debug'],
status: 500
}));
});
});

97
node_modules/server/plugins/parser/index.js generated vendored Normal file
View File

@@ -0,0 +1,97 @@
// Parser plugin
// Get the raw request and transform it into something usable
// Examples: ctx.body, ctx.files, etc
const join = require('../../src/join');
const modern = require('../../src/modern');
const plugin = {
name: 'parser',
options: {
body: {
type: [Object, Boolean],
default: { extended: true },
extend: true
},
json: {
type: [Object, Boolean],
default: {}
},
text: {
type: Object,
default: {}
},
data: {
type: Object,
default: {}
},
cookie: {
type: Object,
default: {}
},
method: {
type: [Object, String, Boolean],
default: [
'X-HTTP-Method',
'X-HTTP-Method-Override',
'X-Method-Override',
'_method'
],
// Coerce it into an Array if it is not already
clean: value => typeof value === 'string' ? [value] : value
}
},
// It is populated in "init()" right now:
before: [
ctx => {
if (!ctx.options.parser.method) return;
return join(ctx.options.parser.method.map(one => {
return modern(require('method-override')(one));
}))(ctx);
},
ctx => {
if (!ctx.options.parser.body) return;
const body = require('body-parser').urlencoded(ctx.options.parser.body);
return modern(body)(ctx);
},
// JSON parser
ctx => {
if (!ctx.options.parser.json) return;
const json = require('body-parser').json(ctx.options.parser.json);
return modern(json)(ctx);
},
// Text parser
ctx => {
if (!ctx.options.parser.text) return;
const text = require('body-parser').text(ctx.options.parser.text);
return modern(text)(ctx);
},
// Data parser
ctx => {
if (!ctx.options.parser.data) return;
const data = require('express-data-parser')(ctx.options.parser.data);
return modern(data)(ctx);
},
// Cookie parser
ctx => {
if (!ctx.options.parser.cookie) return;
const cookie = require('cookie-parser')(
ctx.options.secret,
ctx.options.parser.cookie
);
return modern(cookie)(ctx);
},
// Add a reference from ctx.req.body to the ctx.data and an alias
ctx => {
ctx.data = ctx.body;
},
]
};
module.exports = plugin;

121
node_modules/server/plugins/parser/integration.test.js generated vendored Normal file
View File

@@ -0,0 +1,121 @@
// External libraries used
const { cookie } = require('server/reply');
const run = require('server/test/run');
const fs = require('fs');
run.options = { security: false };
// Local helpers and data
const logo = fs.createReadStream(__dirname + '/../../test/logo.png');
const content = ctx => ctx.headers['content-type'];
describe('Default modules', () => {
it('bodyParser', async () => {
const mid = ctx => {
expect(ctx.data).toEqual(ctx.req.body);
expect(ctx.data).toBeDefined();
expect(ctx.data.hello).toBe('世界');
expect(content(ctx)).toBe('application/x-www-form-urlencoded');
return 'Hello 世界';
};
const res = await run(mid).post('/', { form: 'hello=世界' });
expect(res.body).toBe('Hello 世界');
});
it('dataParser', async () => {
const mid = ctx => ctx.files.logo;
const res = await run(mid).post('/', { formData: { logo } });
expect(res.body.name).toBe('logo.png');
expect(res.body.type).toBe('image/png');
expect(res.body.size).toBe(30587);
});
// It can *set* cookies from the server()
// TODO: it can *get* cookies from the server()
it('cookieParser', async () => {
const mid = () => cookie('place', '世界').send('Hello 世界');
const res = await run(mid).post('/', { body: { place: '世界' } });
const cookies = res.headers['set-cookie'].join();
expect(cookies).toMatch('place=%E4%B8%96%E7%95%8C');
});
// Change the method to the specified one
it('method-override through header', async () => {
const mid = ctx => {
expect(ctx.method).toBe('PUT');
expect(ctx.originalMethod).toBe('POST');
return 'Hello 世界';
};
const headers = { 'X-HTTP-Method-Override': 'PUT' };
const res = await run(mid).post('/', { headers });
expect(res.body).toBe('Hello 世界');
});
// Change the method to the specified one
it('override-method works with a string', async () => {
const mid = ctx => {
expect(ctx.method).toBe('PUT');
expect(ctx.originalMethod).toBe('POST');
return 'Hello 世界';
};
const headers = { 'X-HTTP-Method-Override': 'PUT' };
const res = await run({ parser: {
method: 'X-HTTP-Method-Override'
} }, mid).post('/', { headers });
expect(res.body).toBe('Hello 世界');
});
// Change the method to the specified one
it('override-method works with an array', async () => {
const mid = ctx => {
expect(ctx.method).toBe('PUT');
expect(ctx.originalMethod).toBe('POST');
return 'Hello 世界';
};
const headers = { 'X-HTTP-Method-Override': 'PUT' };
const res = await run({ parser: {
method: ['X-HTTP-Method-Override']
} }, mid).post('/', { headers });
expect(res.body).toBe('Hello 世界');
});
// TODO: check more options
});
describe('Cancel parts through options', () => {
it('can cancel bodyParser', async () => {
const options = { parser: { body: false } };
const mid = ctx => {
expect(ctx.body).toEqual({});
expect(ctx.headers['content-type']).toBe('application/x-www-form-urlencoded');
return 'Hello 世界';
};
const res = await run(options, mid).post('/', { form: 'hello=世界' });
expect(res.body).toBe('Hello 世界');
});
it('can cancel jsonParser', async () => {
const mid = ctx => {
expect(ctx.data).toEqual(ctx.req.body);
expect(ctx.data).toEqual({});
expect(content(ctx)).toBe('application/json');
return 'Hello 世界';
};
const res = await run({ parser: { json: false }}, mid).post('/', { body: { hello: '世界' }});
expect(res.body).toBe('Hello 世界');
});
// TODO: check all others can be cancelled
});

63
node_modules/server/plugins/security/index.js generated vendored Normal file
View File

@@ -0,0 +1,63 @@
const modern = require('../../src/modern');
const csurf = require('csurf');
const helmet = require('helmet');
module.exports = {
name: 'security',
options: {
csrf: {
env: 'SECURITY_CSRF',
default: {},
type: Object
},
contentSecurityPolicy: {
env: 'SECURITY_CONTENTSECURITYPOLICY'
},
expectCt: {
env: 'SECURITY_EXPECTCT'
},
dnsPrefetchControl: {
env: 'SECURITY_DNSPREFETCHCONTROL'
},
frameguard: {
env: 'SECURITY_FRAMEGUARD'
},
hidePoweredBy: {
env: 'SECURITY_HIDEPOWEREDBY'
},
hpkp: {
env: 'SECURITY_HPKP'
},
hsts: {
env: 'SECURITY_HSTS'
},
ieNoOpen: {
env: 'SECURITY_IENOOPEN'
},
noCache: {
env: 'SECURITY_NOCACHE'
},
noSniff: {
env: 'SECURITY_NOSNIFF'
},
referrerPolicy: {
env: 'SECURITY_REFERRERPOLICY'
},
xssFilter: {
env: 'SECURITY_XSSFILTER'
}
},
before: [
ctx => ctx.options.security && ctx.options.security.csrf
? modern(csurf(ctx.options.security.csrf))(ctx)
: false,
ctx => {
// Set the csrf for render(): https://expressjs.com/en/api.html#res.locals
if (ctx.req.csrfToken) {
ctx.csrf = ctx.req.csrfToken();
ctx.res.locals.csrf = ctx.csrf;
}
},
ctx => ctx.options.security ? modern(helmet(ctx.options.security))(ctx) : false
]
};

16
node_modules/server/plugins/security/unit.test.js generated vendored Normal file
View File

@@ -0,0 +1,16 @@
const run = require('server/test/run');
const { get, post } = require('server/router');
describe('static plugin', () => {
it('csurf', async () => {
return await run({ public: 'test' }, [
get('/', ctx => ctx.res.locals.csrf),
post('/', () => '世界')
]).alive(async api => {
const csrf = (await api.get('/')).body;
expect(csrf).toBeDefined();
const res = await api.post('/', { body: { _csrf: csrf }});
expect(res.statusCode).toBe(200);
});
});
});

49
node_modules/server/plugins/session/index.js generated vendored Normal file
View File

@@ -0,0 +1,49 @@
const modern = require('../../src/modern');
const server = require('../../server');
const session = require('express-session');
server.session = session;
const RedisStore = require('connect-redis')(server.session);
let sessionMiddleware;
module.exports = {
name: 'session',
options: {
__root: 'secret',
resave: {
default: false
},
saveUninitialized: {
default: true
},
cookie: {
default: {}
},
secret: {
type: String,
inherit: 'secret',
env: 'SESSION_SECRET'
},
store: {
env: false
},
redis: {
type: String,
inherit: true,
env: 'REDIS_URL'
}
},
init: ctx => {
if (!ctx.options.session.store && ctx.options.session.redis) {
ctx.options.session.store = new RedisStore({
url: ctx.options.session.redis
});
}
sessionMiddleware = session(ctx.options.session);
},
before: ctx => modern(sessionMiddleware)(ctx),
launch: ctx => {
ctx.io.use(function(socket, next) {
sessionMiddleware(socket.request, socket.request.res || {}, next);
});
}
};

36
node_modules/server/plugins/session/unit.test.js generated vendored Normal file
View File

@@ -0,0 +1,36 @@
const server = require('server');
const run = require('server/test/run');
const { get } = require('server/router');
const send = require('server/reply/send');
describe('static plugin', () => {
it('can handle sessions', async () => {
return run({ public: 'test' }, [
get('/a', ctx => {
ctx.session.page = 'pageA';
return send('');
}),
get('/b', ctx => send(ctx.session.page))
]).alive(async api => {
expect((await api.get('/a')).body).toEqual('');
expect((await api.get('/b')).body).toEqual('pageA');
});
});
it('persists the session', async () => {
const mid = ctx => {
ctx.session.counter = (ctx.session.counter || 0) + 1;
return 'n' + ctx.session.counter;
};
return run(mid).alive(async api => {
for (let i = 0; i < 3; i++) {
const res = await api.get('/');
expect(res.body).toBe('n' + (i + 1));
}
});
});
it('has the session for creating stores', () => {
expect(server.session).toHaveProperty('Store', jasmine.any(Function));
});
});

56
node_modules/server/plugins/socket/index.js generated vendored Normal file
View File

@@ -0,0 +1,56 @@
// Create a socket plugin
const socketIO = require('socket.io');
const extend = require('extend');
const listeners = {};
module.exports = {
name: 'socket',
options: {
path: {
env: 'SOCKET_PATH'
},
serveClient: {},
adapter: {},
origins: {},
parser: {},
pingTimeout: {},
pingInterval: {},
upgradeTimeout: {},
maxHttpBufferSize: {},
allowRequest: {},
transports: {},
allowUpgrades: {},
perMessageDeflate: {},
httpCompression: {},
cookie: {},
cookiePath: {},
cookieHttpOnly: {},
wsEngine: {}
},
router: (path, middle) => {
listeners[path] = listeners[path] || [];
listeners[path].push(middle);
},
launch: ctx => {
if (!ctx.options.socket) return;
ctx.io = socketIO(ctx.server, ctx.options.socket);
ctx.io.on('connect', socket => {
// console.log(socket.client.request.session);
for (let path in listeners) {
if (path !== 'connect') {
listeners[path].forEach(cb => {
socket.on(path, data => {
cb(extend(socket.client.request, ctx, { path, socket, data }));
});
});
}
}
if (listeners['connect']) {
listeners['connect'].forEach(cb => {
cb(extend(socket.client.request, ctx, { path: 'connect', socket }));
});
}
});
}
};

19
node_modules/server/plugins/static/index.js generated vendored Normal file
View File

@@ -0,0 +1,19 @@
const modern = require('../../src/modern');
module.exports = {
name: 'static',
options: {
__root: 'public',
public: {
type: String,
inherit: 'public',
env: false
}
},
init: ctx => {
if (!ctx.options.static.public) return;
module.exports.before = [
modern(ctx.express.static(ctx.options.static.public))
];
}
};

45
node_modules/server/plugins/static/unit.test.js generated vendored Normal file
View File

@@ -0,0 +1,45 @@
const run = require('server/test/run');
const stat = require('./');
const storeLog = out => ({ report: { write: log => { out.log = log; } } });
describe('static plugin', () => {
it('exists', () => {
expect(stat).toBeDefined();
expect(stat.name).toBe('static');
expect(stat.options).toBeDefined();
});
it('static', async () => {
const res = await run({ public: 'test' }).get('/logo.png');
expect(res.statusCode).toBe(200);
expect(res.headers['content-type']).toBe('image/png');
});
it('non-existing static', async () => {
let out = {};
const log = storeLog(out);
const res = await run({ public: 'xxxx', log }).get('/non-existing.png');
expect(res.statusCode).toBe(404);
expect(out.log).toMatch(/did not return anything/);
});
it('does not serve if set to false', async () => {
let out = {};
const log = storeLog(out);
const res = await run({ public: false, log }).get('/logo.png');
expect(res.statusCode).toBe(404);
expect(out.log).toMatch(/did not return anything/);
});
it('does not serve if set to empty string', async () => {
let out = {};
const log = storeLog(out);
const res = await run({ public: '', log }).get('/logo.png');
expect(res.statusCode).toBe(404);
expect(out.log).toMatch(/did not return anything/);
});
});