2023-08-11 03:23:27 +02:00
var https = require ( "https" ) ;
var os = require ( "os" ) ;
var http = require ( "http" ) ;
var url = require ( "url" ) ;
var fs = require ( "fs" ) ;
var path = require ( "path" ) ;
var net = require ( "net" ) ;
var version = "UNKNOWN" ;
try {
version = JSON . parse ( fs . readFileSync ( _ _dirname + "/mod.info" ) ) . version ;
} catch ( ex ) {
// Can't determine version
}
2023-12-31 21:39:21 +01:00
var configJSONS = { } ;
try {
configJSONS = JSON . parse ( fs . readFileSync ( _ _dirname + "/../../../config.json" ) ) ; // Read configuration JSON
} catch ( ex ) {
//OrangeCircle will not care about configJSONS in SVR.JS 3.x and newer
//SVR.JS 2.x and older will fail to start with broken configuration file anyway...
}
2023-08-11 03:23:27 +02:00
var scgiConf = { } ;
try {
scgiConf = JSON . parse ( fs . readFileSync ( _ _dirname + "/../../../orangecircle-config.json" ) ) ;
} catch ( ex ) {
// Use defaults
}
// Load default configuration
if ( scgiConf . path === undefined ) scgiConf . path = "/scgi" ;
scgiConf . path = scgiConf . path . replace ( /([^\/])\/+$/ , "$1" ) ;
if ( scgiConf . host === undefined ) scgiConf . host = "localhost" ;
if ( scgiConf . port === undefined ) scgiConf . port = 4000 ;
2023-12-31 21:39:21 +01:00
var disableModExposeSupported = process . versions . svrjs && process . versions . svrjs . match ( /^(?:Nightly-|(?:[4-9]|[123][0-9])[0-9]*\.|3\.(?:[1-9][0-9]+\.|9\.(?:[1-9])|4\.(?:(?:[3-9]|[12][0-9])[0-9]+|29)))/i ) ;
2023-08-11 03:23:27 +02:00
function Mod ( ) { }
2024-02-07 01:33:31 +01:00
Mod . prototype . callback = function ( req , res , serverconsole , responseEnd , href , ext , uobject , search , defaultpage , users , page404 , head , foot , fd , elseCallback , configJSON , callServerError , getCustomHeaders , origHref , redirect , parsePostData , authUser ) {
2023-08-11 03:23:27 +02:00
return function ( ) {
if ( ! configJSON ) {
configJSON = configJSONS ;
}
2024-02-07 01:33:31 +01:00
function checkIfThereIsA401Rule ( ) {
var actually401 = false ;
function createRegex ( regex ) {
var regexObj = regex . split ( "/" ) ;
if ( regexObj . length == 0 ) throw new Error ( "Invalid regex!" ) ;
var modifiers = regexObj . pop ( ) ;
regexObj . shift ( ) ;
var searchString = regexObj . join ( "/" ) ;
return new RegExp ( searchString , modifiers ) ;
}
if ( configJSON . nonStandardCodes ) {
configJSON . nonStandardCodes . every ( function ( nonscode ) {
if ( nonscode . scode == 401 ) {
if ( nonscode . regex && ( req . url . match ( createRegex ( nonscode . regex ) ) || href . match ( createRegex ( nonscode . regex ) ) ) ) {
actually401 = true ;
return true ;
} else if ( nonscode . url && ( nonStandardCodes [ i ] . url == href || ( os . platform ( ) == "win32" && nonStandardCodes [ i ] . url . toLowerCase ( ) == href . toLowerCase ( ) ) ) ) {
actually401 = true ;
return true ;
}
}
return false ;
} ) ;
}
return actually401 ;
}
2023-08-11 03:23:27 +02:00
if ( ! getCustomHeaders ) {
var bheaders = JSON . parse ( JSON . stringify ( configJSON . customHeaders ) ) ;
} else {
var bheaders = getCustomHeaders ( ) ;
}
bheaders [ "Content-Type" ] = "text/html" ; // HTML output
if ( ! getCustomHeaders ) {
bheaders [ "Server" ] =
"SVR.JS/" +
configJSON . version +
" (" +
os . platform ( ) [ 0 ] . toUpperCase ( ) +
os . platform ( ) . slice ( 1 ) +
")" ; // Add Server header
}
var abheaders = JSON . parse ( JSON . stringify ( bheaders ) ) ;
2024-02-16 02:37:49 +01:00
var socket = { } ;
2023-08-11 03:23:27 +02:00
function executeSCGI ( req , res , dh , nEnv ) {
// Function to execute SCGI scripts
var env = JSON . parse ( JSON . stringify ( process . env ) ) ;
var nEnvKeys = Object . keys ( nEnv ) ;
for ( var i = 0 ; i < nEnvKeys . length ; i ++ ) {
env [ nEnvKeys [ i ] ] = nEnv [ nEnvKeys [ i ] ] ;
}
var buffer = "" ;
var headerendline = - 1 ;
var cned = false ;
var dataHandler = function ( data ) {
2024-02-16 02:37:49 +01:00
if ( ! cned ) buffer += data . toString ( "latin1" ) ;
2023-08-11 03:23:27 +02:00
var m = null ;
2023-08-26 04:35:07 +02:00
if ( ! cned ) m = buffer . match ( /(?:\r\n\r\n|\n\r\n\r|\n\n|\r\r)/ ) ;
2023-08-11 03:23:27 +02:00
if ( ! cned && m ) {
cned = true ;
eol = m [ 0 ] ;
headerendline = m . index ;
2023-08-26 04:35:07 +02:00
var bheaders = buffer . substr ( 0 , headerendline ) . split ( /(?:\r\n|\n\r|\n|\r)/ ) ;
2023-08-11 03:23:27 +02:00
var bheaderso = { } ;
if ( dh ) bheaderso = dh ;
var code = 200 ;
var msg = "OK" ;
if ( bheaders [ 0 ] . indexOf ( "HTTP/" ) == 0 ) {
var heada = bheaders . shift ( ) ;
var hso = heada . split ( " " ) ;
code = hso [ 1 ] ;
if ( hso [ 2 ] !== undefined ) msg = heada . split ( " " ) . splice ( 2 ) . join ( " " ) ;
} else if ( bheaders [ 0 ] . indexOf ( ":" ) == - 1 ) {
var heada = bheaders . shift ( ) ;
var hso = heada . split ( " " ) ;
if ( hso [ 0 ] . match ( /^[0-9]{3}$/ ) ) {
code = hso [ 0 ] ;
if ( hso [ 1 ] !== undefined ) msg = heada . split ( " " ) . splice ( 1 ) . join ( " " ) ;
}
}
2024-02-11 15:06:34 +01:00
var hasLocation = false ;
2023-08-11 03:23:27 +02:00
for ( var i = 0 ; i < bheaders . length ; i ++ ) {
var headerp = bheaders [ i ] . split ( ": " ) ;
var headern = headerp . shift ( ) ;
var headerv = headerp . join ( ": " ) ;
if ( headern . toLowerCase ( ) == "status" ) {
code = headerv . split ( " " ) [ 0 ] ;
if ( headerv . split ( " " ) [ 1 ] !== undefined ) msg = headerv . split ( " " ) . splice ( 1 ) . join ( " " ) ;
} else if ( headern . toLowerCase ( ) == "set-cookie" ) {
if ( ! bheaderso [ "Set-Cookie" ] ) bheaderso [ "Set-Cookie" ] = [ ] ;
bheaderso [ "Set-Cookie" ] . push ( headerv ) ;
} else {
2024-02-11 15:06:34 +01:00
if ( headern . toLowerCase ( ) == "location" ) hasLocation = true ;
2023-08-11 03:23:27 +02:00
bheaderso [ headern ] = headerv ;
}
}
2024-02-11 15:06:34 +01:00
if ( ( code < 300 || code > 399 ) && hasLocation ) {
2023-08-11 03:23:27 +02:00
code = 302 ;
msg = "Found" ;
}
try {
res . writeHead ( code , msg , bheaderso ) ;
2024-05-03 16:10:57 +02:00
res . write ( Buffer . from ( buffer . substr ( headerendline + eol . length ) , "latin1" ) ) ;
2023-08-11 03:23:27 +02:00
} catch ( ex ) {
2024-02-16 02:37:49 +01:00
socket . removeAllListeners ( "data" ) ;
socket . removeAllListeners ( "end" ) ;
2023-08-11 03:23:27 +02:00
if ( ! callServerError ) {
res . writeHead ( 500 ) ;
res . end ( ex . stack ) ;
} else {
callServerError ( 500 , "OrangeCircle/" + version , ex ) ;
}
return ;
}
} else {
2024-02-16 02:37:49 +01:00
if ( cned && ! res . finished ) {
res . write ( data ) ;
socket . removeListener ( "data" , dataHandler ) ;
socket . pipe ( res , { end : false } ) ;
}
2023-08-11 03:23:27 +02:00
}
} ;
2024-02-16 02:37:49 +01:00
socket = net . createConnection ( {
2023-08-11 03:23:27 +02:00
host : scgiConf . host ,
port : scgiConf . port ,
} , function ( ) {
socket . on ( "data" , dataHandler ) ;
socket . on ( "end" , function ( ) {
res . end ( ) ;
} ) ;
var envEncoded = "" ;
var envNames = Object . keys ( env ) ;
for ( var i = 0 ; i < envNames . length ; i ++ ) {
var envName = String ( envNames [ i ] ) . replace ( /\0/g , "" ) ;
var envValue = String ( env [ envNames [ i ] ] ) . replace ( /\0/g , "" ) ;
if ( envName == "CONTENT_LENGTH" ) {
// CONTENT_LENGTH environment variable must be first!!!
envEncoded = envName + "\0" + envValue + "\0" + envEncoded ;
} else {
envEncoded += envName + "\0" + envValue + "\0" ;
}
}
socket . write ( envEncoded . length + ":" + envEncoded + "," ) ;
req . pipe ( socket , { end : false } ) ;
} ) . on ( "error" , function ( error ) {
2023-08-12 12:18:52 +02:00
var errorcode = ( error . code == "ENOTFOUND" || error . code == "EHOSTUNREACH" || error . code == "ECONNREFUSED" ) ? 503 : 500 ;
2023-08-11 03:23:27 +02:00
if ( ! callServerError ) {
2023-08-12 12:18:52 +02:00
res . writeHead ( errorcode , {
2023-08-11 03:23:27 +02:00
"Content-Type" : "text/html" ,
"Server" : "OrangeCircle/" + version
} ) ;
res . end ( "<html><head></head><body><h1>OrangeCircle Error!</h1><p>Reason: " + error . message + "</p></body></html>" ) ;
} else {
2023-08-12 12:18:52 +02:00
callServerError ( errorcode , "OrangeCircle/" + version , error ) ;
2023-08-11 03:23:27 +02:00
}
} ) ;
}
2024-02-07 01:33:31 +01:00
function executeSCGIWithEnv ( a , req , res , pubip , port , software , dh , user ) {
2023-08-11 03:23:27 +02:00
// Function to set up environment variables and execute sCGI scripts
var b = href . replace ( scgiConf . path , "" )
var nEnv = { } ;
2024-02-07 01:33:31 +01:00
if ( typeof user != "undefined" ) {
if ( user !== null ) {
if ( req . headers . authorization ) nEnv [ "AUTH_TYPE" ] = req . headers . authorization . split ( " " ) [ 0 ] ;
nEnv [ "REMOTE_USER" ] = user ;
}
} else if ( req . headers . authorization && ( typeof checkIfThereIsA401Rule == "undefined" || checkIfThereIsA401Rule ( ) ) ) {
2023-08-11 03:23:27 +02:00
nEnv [ "AUTH_TYPE" ] = req . headers . authorization . split ( " " ) [ 0 ] ;
if ( nEnv [ "AUTH_TYPE" ] == "Basic" ) {
var remoteCred = req . headers . authorization . split ( " " ) [ 1 ] ;
if ( ! remoteCred ) {
nEnv [ "REMOTE_USER" ] = "orangecircle_cgi_invalid_user" ;
} else {
var remoteCredDecoded = Buffer . from ( remoteCred , "base64" ) . toString ( "utf8" ) ;
nEnv [ "REMOTE_USER" ] = remoteCredDecoded . split ( ":" ) [ 0 ] ;
}
} else {
nEnv [ "REMOTE_USER" ] = "svrjs_this_property_is_not_yet_supported_by_orangecircle_cgi" ;
}
}
nEnv [ "QUERY_STRING" ] = req . url . split ( "?" ) [ 1 ] ;
if ( nEnv [ "QUERY_STRING" ] == undefined || nEnv [ "QUERY_STRING" ] == "undefined" ) nEnv [ "QUERY_STRING" ] = "" ;
nEnv [ "SERVER_SOFTWARE" ] = software ;
nEnv [ "SERVER_PROTOCOL" ] = "HTTP/" + req . httpVersion ;
if ( pubip && ( port !== null && port !== undefined ) ) {
nEnv [ "SERVER_PORT" ] = port ;
nEnv [ "SERVER_ADDR" ] = pubip . replace ( /^::ffff:/i , "" ) ;
if ( nEnv [ "SERVER_ADDR" ] . indexOf ( ":" ) != - 1 ) nEnv [ "SERVER_ADDR" ] = "[" + nEnv [ "SERVER_ADDR" ] + "]" ;
}
2023-08-27 09:30:24 +02:00
if ( configJSON . serverAdministratorEmail && configJSON . serverAdministratorEmail != "[no contact information]" ) {
nEnv [ "SERVER_ADMIN" ] = configJSON . serverAdministratorEmail ;
}
2023-08-11 03:23:27 +02:00
nEnv [ "SERVER_NAME" ] = req . headers . host ;
nEnv [ "DOCUMENT_ROOT" ] = process . cwd ( ) ;
nEnv [ "SCRIPT_NAME" ] = scgiConf . path ;
2023-12-31 21:39:21 +01:00
nEnv [ "PATH_INFO" ] = decodeURIComponent ( b ) ;
nEnv [ "PATH_TRANSLATED" ] = b ? ( ( process . cwd ( ) + decodeURIComponent ( require ( "os" ) . platform == "win32" ? b . replace ( /\//g , "\\" ) : b ) ) . replace ( ( require ( "os" ) . platform == "win32" ? /\\\\/g : /\/\//g ) , ( require ( "os" ) . platform == "win32" ? "\\" : "/" ) ) ) : "" ;
2023-08-11 03:23:27 +02:00
nEnv [ "REQUEST_METHOD" ] = req . method ;
nEnv [ "SCGI" ] = "1" ;
2024-11-21 00:20:33 +01:00
nEnv [ "REQUEST_URI" ] = ( ! origHref || origHref == href ) ? req . url : ( origHref + ( uobject . search ? ( ( uobject . search [ 0 ] == "?" ? "" : "?" ) + uobject . search ) : "" ) ) ;
2023-08-11 03:23:27 +02:00
nEnv [ "REMOTE_ADDR" ] = ( req . socket . realRemoteAddress ? req . socket . realRemoteAddress : ( ( req . headers [ "x-forwarded-for" ] && configJSON . enableIPSpoofing ) ? req . headers [ "x-forwarded-for" ] . split ( "," ) [ 0 ] . replace ( / /g , "" ) : req . socket . remoteAddress ) ) . replace ( /^::ffff:/i , "" ) ;
2023-09-03 11:34:18 +02:00
if ( req . socket . realRemoteAddress && req . socket . realRemotePort ) {
nEnv [ "REMOTE_PORT" ] = req . socket . realRemotePort ;
} else if ( ! ( req . socket . realRemoteAddress && ! req . socket . realRemotePort ) ) {
nEnv [ "REMOTE_PORT" ] = req . socket . remotePort ;
}
2023-08-21 20:21:32 +02:00
if ( req . socket . encrypted ) nEnv [ "HTTPS" ] = "ON" ;
2023-08-11 03:23:27 +02:00
if ( req . headers [ "content-type" ] ) nEnv [ "CONTENT_TYPE" ] = req . headers [ "content-type" ] ;
nEnv [ "CONTENT_LENGTH" ] = "0" ;
if ( req . headers [ "content-length" ] ) nEnv [ "CONTENT_LENGTH" ] = req . headers [ "content-length" ] ;
var nh = JSON . parse ( JSON . stringify ( req . headers ) ) ;
delete nh [ "content-type" ] ;
delete nh [ "content-length" ] ;
var nhKeys = Object . keys ( nh ) ;
for ( var i = 0 ; i < nhKeys . length ; i ++ ) {
nEnv [ "HTTP_" + nhKeys [ i ] . replace ( /[^0-9A-Za-z]+/g , "_" ) . toUpperCase ( ) ] = req . headers [ nhKeys [ i ] ] ;
}
executeSCGI ( req , res , dh , nEnv ) ;
}
if ( href == scgiConf . path || href . indexOf ( scgiConf . path + "/" ) == 0 ) {
try {
executeSCGIWithEnv (
2023-12-31 21:39:21 +01:00
decodeURIComponent ( href ) ,
2023-08-11 03:23:27 +02:00
req ,
res ,
req . socket . localAddress ,
req . socket . localPort ,
getCustomHeaders ?
getCustomHeaders ( ) [ "Server" ] +
2023-12-31 21:39:21 +01:00
( disableModExposeSupported && ( configJSON . exposeModsInErrorPages || configJSON . exposeModsInErrorPages === undefined ) ?
2023-08-11 03:23:27 +02:00
" OrangeCircle/" +
2023-12-31 21:39:21 +01:00
version : "" ) :
2023-08-11 03:23:27 +02:00
"SVR.JS/" +
configJSON . version +
" (" +
os . platform ( ) [ 0 ] . toUpperCase ( ) +
os . platform ( ) . slice ( 1 ) +
"; Node.JS/" +
process . version +
") OrangeCircle/" +
version ,
2024-02-07 01:33:31 +01:00
bheaders ,
authUser
2023-08-11 03:23:27 +02:00
) ;
} catch ( ex ) {
if ( ! callServerError ) {
res . writeHead ( 500 , "Internal Server Error" , abheaders ) ;
res . write (
"<html><head><title>500 Internal Server Error</title></head><body><h1>500 Internal Server Error</h1><p>A server had unexpected exception. Below, the stack trace of the error is shown:</p><code>" +
ex . stack . replace ( /\r\n/g , "<br/>" ) . replace ( /\n/g , "<br/>" ) . replace ( /\r/g , "<br/>" ) . replace ( / /g , " " ) +
"</code><p>Please contact the developer/administrator of the website.</p><p style=\"font-style: italic; font-weight: normal;\">SVR.JS " +
configJSON . version +
" (" +
os . platform ( ) [ 0 ] . toUpperCase ( ) +
os . platform ( ) . slice ( 1 ) +
"; Node.JS/" +
process . version +
") OrangeCircle/" +
version +
" " +
( req . headers . host == undefined ? "" : " on " + req . headers . host ) +
"</p></body></html>"
) ;
res . end ( ) ;
} else {
callServerError ( 500 , "OrangeCircle/" + version , ex ) ;
}
}
2023-08-14 19:39:59 +02:00
} else if ( ( href == "/orangecircle-config.json" || ( os . platform ( ) == "win32" && href . toLowerCase ( ) == "/orangecircle-config.json" ) ) && path . normalize ( _ _dirname + "/../../.." ) == process . cwd ( ) ) {
2023-08-11 03:23:27 +02:00
if ( ! callServerError ) {
res . writeHead ( 200 , "OK" , {
"Content-Type" : "application/json" ,
"Server" : "OrangeCircle/" + version
} ) ;
res . end ( JSON . stringify ( exttointerpreteruser , null , 2 ) ) ;
} else {
callServerError ( 200 , "OrangeCircle/" + version , exttointerpreteruser ) ;
}
} else {
elseCallback ( ) ;
}
}
}
module . exports = Mod ;