Support TCP port range limitation
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
import log from 'book';
|
||||
import Koa from 'koa';
|
||||
import tldjs from 'tldjs';
|
||||
import Debug from 'debug';
|
||||
@@ -10,159 +9,159 @@ import ClientManager from './lib/ClientManager';
|
||||
|
||||
const debug = Debug('localtunnel:server');
|
||||
|
||||
export default function(opt) {
|
||||
opt = opt || {};
|
||||
export default function (opt) {
|
||||
opt = opt || {};
|
||||
|
||||
const validHosts = (opt.domain) ? [opt.domain] : undefined;
|
||||
const myTldjs = tldjs.fromUserSettings({ validHosts });
|
||||
const landingPage = opt.landing || 'https://localtunnel.github.io/www/';
|
||||
const validHosts = (opt.domain) ? [opt.domain] : undefined;
|
||||
const myTldjs = tldjs.fromUserSettings({ validHosts });
|
||||
const landingPage = opt.landing || 'https://localtunnel.github.io/www/';
|
||||
|
||||
function GetClientIdFromHostname(hostname) {
|
||||
return myTldjs.getSubdomain(hostname);
|
||||
function GetClientIdFromHostname(hostname) {
|
||||
return myTldjs.getSubdomain(hostname);
|
||||
}
|
||||
|
||||
const manager = new ClientManager(opt);
|
||||
|
||||
const schema = opt.secure ? 'https' : 'http';
|
||||
|
||||
const app = new Koa();
|
||||
const router = new Router();
|
||||
|
||||
router.get('/api/status', async (ctx, next) => {
|
||||
const stats = manager.stats;
|
||||
ctx.body = {
|
||||
tunnels: stats.tunnels,
|
||||
mem: process.memoryUsage(),
|
||||
};
|
||||
});
|
||||
|
||||
router.get('/api/tunnels/:id/status', async (ctx, next) => {
|
||||
const clientId = ctx.params.id;
|
||||
const client = manager.getClient(clientId);
|
||||
if (!client) {
|
||||
ctx.throw(404);
|
||||
return;
|
||||
}
|
||||
|
||||
const manager = new ClientManager(opt);
|
||||
const stats = client.stats();
|
||||
ctx.body = {
|
||||
connected_sockets: stats.connectedSockets,
|
||||
};
|
||||
});
|
||||
|
||||
const schema = opt.secure ? 'https' : 'http';
|
||||
app.use(router.routes());
|
||||
app.use(router.allowedMethods());
|
||||
|
||||
const app = new Koa();
|
||||
const router = new Router();
|
||||
// root endpoint
|
||||
app.use(async (ctx, next) => {
|
||||
const path = ctx.request.path;
|
||||
|
||||
router.get('/api/status', async (ctx, next) => {
|
||||
const stats = manager.stats;
|
||||
ctx.body = {
|
||||
tunnels: stats.tunnels,
|
||||
mem: process.memoryUsage(),
|
||||
};
|
||||
});
|
||||
// skip anything not on the root path
|
||||
if (path !== '/') {
|
||||
await next();
|
||||
return;
|
||||
}
|
||||
|
||||
router.get('/api/tunnels/:id/status', async (ctx, next) => {
|
||||
const clientId = ctx.params.id;
|
||||
const client = manager.getClient(clientId);
|
||||
if (!client) {
|
||||
ctx.throw(404);
|
||||
return;
|
||||
}
|
||||
const isNewClientRequest = ctx.query['new'] !== undefined;
|
||||
if (isNewClientRequest) {
|
||||
const reqId = hri.random();
|
||||
debug('making new client with id %s', reqId);
|
||||
const info = await manager.newClient(reqId);
|
||||
|
||||
const stats = client.stats();
|
||||
ctx.body = {
|
||||
connected_sockets: stats.connectedSockets,
|
||||
};
|
||||
});
|
||||
const url = schema + '://' + info.id + '.' + ctx.request.host;
|
||||
info.url = url;
|
||||
ctx.body = info;
|
||||
return;
|
||||
}
|
||||
|
||||
app.use(router.routes());
|
||||
app.use(router.allowedMethods());
|
||||
// no new client request, send to landing page
|
||||
ctx.redirect(landingPage);
|
||||
});
|
||||
|
||||
// root endpoint
|
||||
app.use(async (ctx, next) => {
|
||||
const path = ctx.request.path;
|
||||
// anything after the / path is a request for a specific client name
|
||||
// This is a backwards compat feature
|
||||
app.use(async (ctx, next) => {
|
||||
const parts = ctx.request.path.split('/');
|
||||
|
||||
// skip anything not on the root path
|
||||
if (path !== '/') {
|
||||
await next();
|
||||
return;
|
||||
}
|
||||
// any request with several layers of paths is not allowed
|
||||
// rejects /foo/bar
|
||||
// allow /foo
|
||||
if (parts.length !== 2) {
|
||||
await next();
|
||||
return;
|
||||
}
|
||||
|
||||
const isNewClientRequest = ctx.query['new'] !== undefined;
|
||||
if (isNewClientRequest) {
|
||||
const reqId = hri.random();
|
||||
debug('making new client with id %s', reqId);
|
||||
const info = await manager.newClient(reqId);
|
||||
const reqId = parts[1];
|
||||
|
||||
const url = schema + '://' + info.id + '.' + ctx.request.host;
|
||||
info.url = url;
|
||||
ctx.body = info;
|
||||
return;
|
||||
}
|
||||
// limit requested hostnames to 63 characters
|
||||
if (! /^(?:[a-z0-9][a-z0-9\-]{4,63}[a-z0-9]|[a-z0-9]{4,63})$/.test(reqId)) {
|
||||
const msg = 'Invalid subdomain. Subdomains must be lowercase and between 4 and 63 alphanumeric characters.';
|
||||
ctx.status = 403;
|
||||
ctx.body = {
|
||||
message: msg,
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
// no new client request, send to landing page
|
||||
ctx.redirect(landingPage);
|
||||
});
|
||||
debug('making new client with id %s', reqId);
|
||||
const info = await manager.newClient(reqId);
|
||||
|
||||
// anything after the / path is a request for a specific client name
|
||||
// This is a backwards compat feature
|
||||
app.use(async (ctx, next) => {
|
||||
const parts = ctx.request.path.split('/');
|
||||
const url = schema + '://' + info.id + '.' + ctx.request.host;
|
||||
info.url = url;
|
||||
ctx.body = info;
|
||||
return;
|
||||
});
|
||||
|
||||
// any request with several layers of paths is not allowed
|
||||
// rejects /foo/bar
|
||||
// allow /foo
|
||||
if (parts.length !== 2) {
|
||||
await next();
|
||||
return;
|
||||
}
|
||||
const server = http.createServer();
|
||||
|
||||
const reqId = parts[1];
|
||||
const appCallback = app.callback();
|
||||
|
||||
// limit requested hostnames to 63 characters
|
||||
if (! /^(?:[a-z0-9][a-z0-9\-]{4,63}[a-z0-9]|[a-z0-9]{4,63})$/.test(reqId)) {
|
||||
const msg = 'Invalid subdomain. Subdomains must be lowercase and between 4 and 63 alphanumeric characters.';
|
||||
ctx.status = 403;
|
||||
ctx.body = {
|
||||
message: msg,
|
||||
};
|
||||
return;
|
||||
}
|
||||
server.on('request', (req, res) => {
|
||||
// without a hostname, we won't know who the request is for
|
||||
const hostname = req.headers.host;
|
||||
if (!hostname) {
|
||||
res.statusCode = 400;
|
||||
res.end('Host header is required');
|
||||
return;
|
||||
}
|
||||
|
||||
debug('making new client with id %s', reqId);
|
||||
const info = await manager.newClient(reqId);
|
||||
const clientId = GetClientIdFromHostname(hostname);
|
||||
if (!clientId) {
|
||||
appCallback(req, res);
|
||||
return;
|
||||
}
|
||||
|
||||
const url = schema + '://' + info.id + '.' + ctx.request.host;
|
||||
info.url = url;
|
||||
ctx.body = info;
|
||||
return;
|
||||
});
|
||||
const client = manager.getClient(clientId);
|
||||
if (!client) {
|
||||
res.statusCode = 404;
|
||||
res.end('404');
|
||||
return;
|
||||
}
|
||||
|
||||
const server = http.createServer();
|
||||
client.handleRequest(req, res);
|
||||
});
|
||||
|
||||
const appCallback = app.callback();
|
||||
server.on('upgrade', (req, socket, head) => {
|
||||
const hostname = req.headers.host;
|
||||
if (!hostname) {
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
server.on('request', (req, res) => {
|
||||
// without a hostname, we won't know who the request is for
|
||||
const hostname = req.headers.host;
|
||||
if (!hostname) {
|
||||
res.statusCode = 400;
|
||||
res.end('Host header is required');
|
||||
return;
|
||||
}
|
||||
const clientId = GetClientIdFromHostname(hostname);
|
||||
if (!clientId) {
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
const clientId = GetClientIdFromHostname(hostname);
|
||||
if (!clientId) {
|
||||
appCallback(req, res);
|
||||
return;
|
||||
}
|
||||
const client = manager.getClient(clientId);
|
||||
if (!client) {
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
const client = manager.getClient(clientId);
|
||||
if (!client) {
|
||||
res.statusCode = 404;
|
||||
res.end('404');
|
||||
return;
|
||||
}
|
||||
client.handleUpgrade(req, socket);
|
||||
});
|
||||
|
||||
client.handleRequest(req, res);
|
||||
});
|
||||
|
||||
server.on('upgrade', (req, socket, head) => {
|
||||
const hostname = req.headers.host;
|
||||
if (!hostname) {
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
const clientId = GetClientIdFromHostname(hostname);
|
||||
if (!clientId) {
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
const client = manager.getClient(clientId);
|
||||
if (!client) {
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
client.handleUpgrade(req, socket);
|
||||
});
|
||||
|
||||
return server;
|
||||
return server;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user