forked from svrjs/svrjs
Add virtual host support
This commit is contained in:
parent
e490f8341b
commit
4a138f73d8
3 changed files with 594 additions and 339 deletions
323
svr.js
323
svr.js
|
@ -196,7 +196,7 @@ if (!singlethreaded) {
|
|||
// Close IPC server
|
||||
process.send = function () {};
|
||||
fakeIPCServer.close();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -227,14 +227,14 @@ if (!singlethreaded) {
|
|||
sendImplemented = false;
|
||||
}
|
||||
|
||||
oldLog = console.log;
|
||||
var oldLog = console.log;
|
||||
console.log = function (a,b,c,d,e,f) {
|
||||
if(a == "ChildProcess.prototype.send() - Sorry! Not implemented yet") {
|
||||
throw new Error("NOT IMPLEMENTED");
|
||||
} else {
|
||||
oldLog(a,b,c,d,e,f);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
worker.send(undefined);
|
||||
|
@ -292,7 +292,7 @@ if (!singlethreaded) {
|
|||
cluster._workersCounter++;
|
||||
return newWorker;
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
if (process.isBun && (cluster.isMaster === undefined || (cluster.isMaster && process.env.NODE_UNIQUE_ID))) {
|
||||
cluster.bunShim();
|
||||
|
@ -542,6 +542,36 @@ function createRegex(regex, isPath) {
|
|||
return new RegExp(searchString, modifiers);
|
||||
}
|
||||
|
||||
function checkForEnabledDirectoryListing(hostname) {
|
||||
function matchHostname(hostnameM) {
|
||||
if (typeof hostnameM == "undefined" || hostnameM == "*") {
|
||||
return true;
|
||||
} else if (hostname && hostnameM.indexOf("*.") == 0 && hostnameM != "*.") {
|
||||
var hostnamesRoot = hostnameM.substr(2);
|
||||
if (hostname == hostnamesRoot || hostname.indexOf("." + hostnamesRoot) == hostname.length - hostnamesRoot.length - 1) {
|
||||
return true;
|
||||
}
|
||||
} else if (hostname && hostname == hostnameM) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
var main = (configJSON.enableDirectoryListing || configJSON.enableDirectoryListing === undefined);
|
||||
if(!configJSON.enableDirectoryListingVHost) return main;
|
||||
var vhostP = null;
|
||||
configJSON.enableDirectoryListingVHost.every(function(vhost) {
|
||||
if(matchHostname(vhost.host)) {
|
||||
vhostP = vhost;
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
if(!vhostP || vhostP.enabled === undefined) return main;
|
||||
else return vhostP.enabled;
|
||||
}
|
||||
|
||||
//IP Block list object
|
||||
function ipBlockList(rawBlockList) {
|
||||
|
||||
|
@ -847,7 +877,6 @@ if (ips.length == 0) {
|
|||
}
|
||||
var host = ips[(ips.length) - 1];
|
||||
if (!host) host = "[offline]";
|
||||
var hp = host + ":" + port.toString();
|
||||
|
||||
var ipRequestCompleted = false;
|
||||
var ipRequestGotError = false;
|
||||
|
@ -1023,6 +1052,7 @@ var disableToHTTPSRedirect = false;
|
|||
var nonStandardCodesRaw = [];
|
||||
var disableUnusedWorkerTermination = false;
|
||||
var rewriteDirtyURLs = false;
|
||||
var errorPages = [];
|
||||
|
||||
//Get properties from config.json
|
||||
if (configJSON.blacklist != undefined) rawBlackList = configJSON.blacklist;
|
||||
|
@ -1049,6 +1079,7 @@ if (configJSON.disableNonEncryptedServer != undefined) disableNonEncryptedServer
|
|||
if (configJSON.disableToHTTPSRedirect != undefined) disableToHTTPSRedirect = configJSON.disableToHTTPSRedirect;
|
||||
if (configJSON.disableUnusedWorkerTermination != undefined) disableUnusedWorkerTermination = configJSON.disableUnusedWorkerTermination;
|
||||
if (configJSON.rewriteDirtyURLs != undefined) rewriteDirtyURLs = configJSON.rewriteDirtyURLs;
|
||||
if (configJSON.errorPages != undefined) errorPages = configJSON.errorPages;
|
||||
if (configJSON.wwwroot != undefined) {
|
||||
var wwwroot = configJSON.wwwroot;
|
||||
if (cluster.isPrimary || cluster.isPrimary === undefined) process.chdir(wwwroot);
|
||||
|
@ -2087,8 +2118,39 @@ if (!cluster.isPrimary) {
|
|||
}
|
||||
};
|
||||
|
||||
function matchHostname(hostname) {
|
||||
if (typeof hostname == "undefined" || hostname == "*") {
|
||||
return true;
|
||||
} else if (req.headers.host && hostname.indexOf("*.") == 0 && hostname != "*.") {
|
||||
var hostnamesRoot = hostname.substr(2);
|
||||
if (req.headers.host == hostnamesRoot || req.headers.host.indexOf("." + hostnamesRoot) == req.headers.host.length - hostnamesRoot.length - 1) {
|
||||
return true;
|
||||
}
|
||||
} else if (req.headers.host && req.headers.host == hostname) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function getCustomHeaders() {
|
||||
var ph = JSON.parse(JSON.stringify(customHeaders));
|
||||
if(configJSON.customHeadersVHost) {
|
||||
var vhostP = null;
|
||||
configJSON.customHeadersVHost.every(function(vhost) {
|
||||
if(matchHostname(vhost.host)) {
|
||||
vhostP = vhost;
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
if(vhostP && vhostP.headers) {
|
||||
var phNu = JSON.parse(JSON.stringify(vhostP.headers));
|
||||
Object.keys(phNu).forEach(function (phNuK) {
|
||||
ph[phNuK] = phNu[phNuK];
|
||||
});
|
||||
}
|
||||
}
|
||||
Object.keys(ph).forEach(function (phk) {
|
||||
if (typeof ph[phk] == "string") ph[phk] = ph[phk].replace(/\{path\}/g, req.url);
|
||||
});
|
||||
|
@ -2158,10 +2220,55 @@ if (!cluster.isPrimary) {
|
|||
|
||||
//Server error calling method
|
||||
function callServerError(errorCode, extName, stack, ch) {
|
||||
var errorFile = errorCode.toString() + ".html";
|
||||
var errorFile2 = "." + errorCode.toString();
|
||||
if (fs.existsSync(errorFile2)) errorFile = errorFile2;
|
||||
if (errorCode == 404 && fs.existsSync(page404)) errorFile = page404;
|
||||
function getErrorFileName(list, callback, _i) {
|
||||
function medCallback(p) {
|
||||
if(p) callback(p);
|
||||
else {
|
||||
fs.access(page404, fs.constants.F_OK, function(err) {
|
||||
if(err) {
|
||||
fs.access("." + errorCode.toString(), fs.constants.F_OK, function(err) {
|
||||
try {
|
||||
if(err) {
|
||||
callback(errorCode.toString() + ".html");
|
||||
} else {
|
||||
callback("." + errorCode.toString());
|
||||
}
|
||||
} catch(err2) {
|
||||
callServerError(500, undefined, generateErrorStack(err2));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
callback(page404);
|
||||
} catch(err2) {
|
||||
callServerError(500, undefined, generateErrorStack(err2));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if(!_i) _i = 0;
|
||||
if(_i >= list.length) {
|
||||
medCallback(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if(list[_i].scode != errorCode || !matchHostname(list[_i].host)) {
|
||||
getErrorFileName(list, callback, _i+1);
|
||||
return;
|
||||
} else {
|
||||
fs.access(list[_i].path, fs.constants.F_OK, function(err) {
|
||||
if(err) {
|
||||
getErrorFileName(list, callback, _i+1);
|
||||
} else {
|
||||
medCallback(list[_i].path);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getErrorFileName(errorPages, function(errorFile) {
|
||||
if (Object.prototype.toString.call(stack) === "[object Error]") stack = generateErrorStack(stack);
|
||||
if (stack === undefined) stack = generateErrorStack(new Error("Unknown error"));
|
||||
if (errorCode == 500 || errorCode == 502) {
|
||||
|
@ -2216,6 +2323,7 @@ if (!cluster.isPrimary) {
|
|||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var reqport = "";
|
||||
|
@ -2553,10 +2661,60 @@ if (!cluster.isPrimary) {
|
|||
|
||||
//Server error calling method
|
||||
function callServerError(errorCode, extName, stack, ch) {
|
||||
var errorFile = errorCode.toString() + ".html";
|
||||
var errorFile2 = "." + errorCode.toString();
|
||||
if (fs.existsSync(errorFile2)) errorFile = errorFile2;
|
||||
if (errorCode == 404 && fs.existsSync(page404)) errorFile = page404;
|
||||
function getErrorFileName(list, callback, _i) {
|
||||
if(err.code == "ERR_SSL_HTTP_REQUEST" && process.version && parseInt(process.version.split(".")[0].substr(1)) >= 16) {
|
||||
//Disable custom error page for HTTP SSL error
|
||||
callback(errorCode.toString() + ".html");
|
||||
return;
|
||||
}
|
||||
function medCallback(p) {
|
||||
if(p) callback(p);
|
||||
else {
|
||||
fs.access(page404, fs.constants.F_OK, function(err) {
|
||||
if(err) {
|
||||
fs.access("." + errorCode.toString(), fs.constants.F_OK, function(err) {
|
||||
try {
|
||||
if(err) {
|
||||
callback(errorCode.toString() + ".html");
|
||||
} else {
|
||||
callback("." + errorCode.toString());
|
||||
}
|
||||
} catch(err2) {
|
||||
callServerError(500, undefined, generateErrorStack(err2));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
callback(page404);
|
||||
} catch(err2) {
|
||||
callServerError(500, undefined, generateErrorStack(err2));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if(!_i) _i = 0;
|
||||
if(_i >= list.length) {
|
||||
medCallback(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if(list[_i].scode != errorCode) {
|
||||
getErrorFileName(list, callback, _i+1);
|
||||
return;
|
||||
} else {
|
||||
fs.access(list[_i].path, fs.constants.F_OK, function(err) {
|
||||
if(err) {
|
||||
getErrorFileName(list, callback, _i+1);
|
||||
} else {
|
||||
medCallback(list[_i].path);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getErrorFileName(errorPages, function(errorFile) {
|
||||
if (Object.prototype.toString.call(stack) === "[object Error]") stack = generateErrorStack(stack);
|
||||
if (stack === undefined) stack = generateErrorStack(new Error("Unknown error"));
|
||||
if (errorCode == 500 || errorCode == 502) {
|
||||
|
@ -2618,6 +2776,7 @@ if (!cluster.isPrimary) {
|
|||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
var reqip = socket.remoteAddress;
|
||||
var reqport = socket.remotePort;
|
||||
|
@ -2917,8 +3076,39 @@ if (!cluster.isPrimary) {
|
|||
}
|
||||
};
|
||||
|
||||
function matchHostname(hostname) {
|
||||
if (typeof hostname == "undefined" || hostname == "*") {
|
||||
return true;
|
||||
} else if (request.headers.host && hostname.indexOf("*.") == 0 && hostname != "*.") {
|
||||
var hostnamesRoot = hostname.substr(2);
|
||||
if (request.headers.host == hostnamesRoot || request.headers.host.indexOf("." + hostnamesRoot) == request.headers.host.length - hostnamesRoot.length - 1) {
|
||||
return true;
|
||||
}
|
||||
} else if (request.headers.host && request.headers.host == hostname) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function getCustomHeaders() {
|
||||
var ph = JSON.parse(JSON.stringify(customHeaders));
|
||||
if(configJSON.customHeadersVHost) {
|
||||
var vhostP = null;
|
||||
configJSON.customHeadersVHost.every(function(vhost) {
|
||||
if(matchHostname(vhost.host)) {
|
||||
vhostP = vhost;
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
if(vhostP && vhostP.headers) {
|
||||
var phNu = JSON.parse(JSON.stringify(vhostP.headers));
|
||||
Object.keys(phNu).forEach(function (phNuK) {
|
||||
ph[phNuK] = phNu[phNuK];
|
||||
});
|
||||
}
|
||||
}
|
||||
Object.keys(ph).forEach(function (phk) {
|
||||
if (typeof ph[phk] == "string") ph[phk] = ph[phk].replace(/\{path\}/g, request.url);
|
||||
});
|
||||
|
@ -3218,10 +3408,57 @@ if (!cluster.isPrimary) {
|
|||
throw new TypeError("Error stack parameter needs to be either a string or an instance of Error object.");
|
||||
}
|
||||
|
||||
var errorFile = errorCode.toString() + ".html";
|
||||
var errorFile2 = "." + errorCode.toString();
|
||||
if (fs.existsSync(errorFile2)) errorFile = errorFile2;
|
||||
if (errorCode == 404 && fs.existsSync(page404)) errorFile = page404;
|
||||
// Determine error file
|
||||
|
||||
function getErrorFileName(list, callback, _i) {
|
||||
function medCallback(p) {
|
||||
if(p) callback(p);
|
||||
else {
|
||||
fs.access(page404, fs.constants.F_OK, function(err) {
|
||||
if(err) {
|
||||
fs.access("." + errorCode.toString(), fs.constants.F_OK, function(err) {
|
||||
try {
|
||||
if(err) {
|
||||
callback(errorCode.toString() + ".html");
|
||||
} else {
|
||||
callback("." + errorCode.toString());
|
||||
}
|
||||
} catch(err2) {
|
||||
callServerError(500, undefined, generateErrorStack(err2));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
callback(page404);
|
||||
} catch(err2) {
|
||||
callServerError(500, undefined, generateErrorStack(err2));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if(!_i) _i = 0;
|
||||
if(_i >= list.length) {
|
||||
medCallback(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if(list[_i].scode != errorCode || !matchHostname(list[_i].host)) {
|
||||
getErrorFileName(list, callback, _i+1);
|
||||
return;
|
||||
} else {
|
||||
fs.access(list[_i].path, fs.constants.F_OK, function(err) {
|
||||
if(err) {
|
||||
getErrorFileName(list, callback, _i+1);
|
||||
} else {
|
||||
medCallback(list[_i].path);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getErrorFileName(errorPages, function(errorFile) {
|
||||
|
||||
// Generate error stack if not provided
|
||||
if (Object.prototype.toString.call(stack) === "[object Error]") stack = generateErrorStack(stack);
|
||||
|
@ -3292,6 +3529,7 @@ if (!cluster.isPrimary) {
|
|||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
@ -3704,7 +3942,7 @@ if (!cluster.isPrimary) {
|
|||
function properServe() {
|
||||
if (stats.isDirectory()) {
|
||||
// Check if directory listing is enabled in the configuration
|
||||
if (configJSON.enableDirectoryListing || configJSON.enableDirectoryListing === undefined) {
|
||||
if (checkForEnabledDirectoryListing(req.headers.host)) {
|
||||
var customHeaders = getCustomHeaders();
|
||||
customHeaders["Content-Type"] = "text/html; charset=utf-8";
|
||||
res.writeHead(200, http.STATUS_CODES[200], customHeaders);
|
||||
|
@ -4272,15 +4510,17 @@ if (!cluster.isPrimary) {
|
|||
//URL REWRITING
|
||||
function rewriteURL(address, map) {
|
||||
var rewrittenAddress = address;
|
||||
for (var i = 0; i < map.length; i++) {
|
||||
if (createRegex(map[i].definingRegex).test(address)) {
|
||||
for (var j = 0; j < map[i].replacements.length; j++) {
|
||||
rewrittenAddress = rewrittenAddress.replace(createRegex(map[i].replacements[j].regex), map[i].replacements[j].replacement);
|
||||
}
|
||||
if (map[i].append) rewrittenAddress += map[i].append;
|
||||
break;
|
||||
}
|
||||
map.every(function(mapEntry) {
|
||||
if (matchHostname(mapEntry.host) && createRegex(mapEntry.definingRegex).test(address)) {
|
||||
mapEntry.replacements.forEach(function (replacement) {
|
||||
rewrittenAddress = rewrittenAddress.replace(createRegex(replacement.regex), replacement.replacement);
|
||||
});
|
||||
if (mapEntry.append) rewrittenAddress += mapEntry.append;
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
return rewrittenAddress;
|
||||
}
|
||||
var origHref = href;
|
||||
|
@ -4342,14 +4582,13 @@ if (!cluster.isPrimary) {
|
|||
//Set response headers
|
||||
if (!isProxy) {
|
||||
var hkh = getCustomHeaders();
|
||||
var hk = Object.keys(hkh);
|
||||
for (var i = 0; i < hk.length; i++) {
|
||||
Object.keys(hkh).forEach(function(hkS) {
|
||||
try {
|
||||
response.setHeader(hk[i], hkh[hk[i]]);
|
||||
response.setHeader(hkS, hkh[hkS]);
|
||||
} catch (err) {
|
||||
//Headers will not be set.
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//Check if path is forbidden
|
||||
|
@ -4375,6 +4614,7 @@ if (!cluster.isPrimary) {
|
|||
var regexI = [];
|
||||
if (!isProxy && nonStandardCodes != undefined) {
|
||||
for (var i = 0; i < nonStandardCodes.length; i++) {
|
||||
if(matchHostname(nonStandardCodes[i].host)) {
|
||||
var isMatch = false;
|
||||
if (nonStandardCodes[i].regex) {
|
||||
var createdRegex = createRegex(nonStandardCodes[i].regex, true);
|
||||
|
@ -4405,6 +4645,7 @@ if (!cluster.isPrimary) {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle non-standard codes
|
||||
if (nonscodeIndex > -1) {
|
||||
|
@ -4454,7 +4695,7 @@ if (!cluster.isPrimary) {
|
|||
} else {
|
||||
checkIfPasswordMatches(list, password, callback, _i+1);
|
||||
}
|
||||
}
|
||||
};
|
||||
var hashedPassword = sha256(password + list[_i].salt);
|
||||
if(list[_i].scrypt) {
|
||||
if(!crypto.scrypt) {
|
||||
|
@ -4462,7 +4703,7 @@ if (!cluster.isPrimary) {
|
|||
return;
|
||||
} else {
|
||||
var cacheEntry = scryptCache.find(function (entry) {
|
||||
return (entry.password == hashedPassword && entry.salt == list[_i].salt)
|
||||
return (entry.password == hashedPassword && entry.salt == list[_i].salt);
|
||||
});
|
||||
if(cacheEntry) {
|
||||
cb(cacheEntry.hash);
|
||||
|
@ -4484,7 +4725,7 @@ if (!cluster.isPrimary) {
|
|||
return;
|
||||
} else {
|
||||
var cacheEntry = pbkdf2Cache.find(function (entry) {
|
||||
return (entry.password == hashedPassword && entry.salt == list[_i].salt)
|
||||
return (entry.password == hashedPassword && entry.salt == list[_i].salt);
|
||||
});
|
||||
if(cacheEntry) {
|
||||
cb(cacheEntry.hash);
|
||||
|
@ -4506,6 +4747,7 @@ if (!cluster.isPrimary) {
|
|||
}
|
||||
|
||||
function authorizedCallback(bruteProtection) {
|
||||
try {
|
||||
var ha = getCustomHeaders();
|
||||
ha["WWW-Authenticate"] = "Basic realm=\"" + (authcode.realm ? authcode.realm.replace(/(\\|")/g, "\\$1") : "SVR.JS HTTP Basic Authorization") + "\", charset=\"UTF-8\"";
|
||||
var credentials = req.headers["authorization"];
|
||||
|
@ -4529,13 +4771,17 @@ if (!cluster.isPrimary) {
|
|||
}
|
||||
var username = decodedCredentialsMatch[1];
|
||||
var password = decodedCredentialsMatch[2];
|
||||
var usernameMatch = users.filter(function (entry) {
|
||||
var usernameMatch = [];
|
||||
if(!authcode.userList || authcode.userList.indexOf(username) > -1) {
|
||||
usernameMatch = users.filter(function (entry) {
|
||||
return entry.name == username;
|
||||
});
|
||||
}
|
||||
if(usernameMatch.length == 0) {
|
||||
usernameMatch.push({name: username, pass: "FAKEPASS", salt: "FAKESALT"}); //Fake credentials
|
||||
}
|
||||
checkIfPasswordMatches(usernameMatch, password, function (authorized) {
|
||||
try {
|
||||
if (!authorized) {
|
||||
if (bruteProtection) {
|
||||
if (process.send) {
|
||||
|
@ -4564,7 +4810,15 @@ if (!cluster.isPrimary) {
|
|||
}
|
||||
modExecute(mods, vres(req, res, serverconsole, responseEnd, href, ext, uobject, search, "index.html", users, page404, head, foot, fd, callServerError, getCustomHeaders, origHref, redirect, parsePostData));
|
||||
}
|
||||
} catch(err) {
|
||||
callServerError(500, undefined, generateErrorStack(err));
|
||||
return;
|
||||
}
|
||||
});
|
||||
} catch(err) {
|
||||
callServerError(500, undefined, generateErrorStack(err));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (authcode.disableBruteProtection) {
|
||||
authorizedCallback(false);
|
||||
|
@ -5507,6 +5761,7 @@ function saveConfig() {
|
|||
if (configJSONobj.enableETag === undefined) configJSONobj.enableETag = true;
|
||||
if (configJSONobj.disableUnusedWorkerTermination === undefined) configJSONobj.disableUnusedWorkerTermination = false;
|
||||
if (configJSONobj.rewriteDirtyURLs === undefined) configJSONobj.rewriteDirtyURLs = false;
|
||||
if (configJSONobj.errorPages === undefined) configJSONobj.errorPages = [];
|
||||
|
||||
var configString = JSON.stringify(configJSONobj, null, 2);
|
||||
fs.writeFileSync(__dirname + "/config.json", configString);
|
||||
|
|
|
@ -1 +1 @@
|
|||
7
|
||||
31
|
Reference in a new issue