2023-08-11 19:53:57 +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 stream = require ( "stream" ) ;
var version = "UNKNOWN" ;
try {
version = JSON . parse ( fs . readFileSync ( _ _dirname + "/mod.info" ) ) . version ;
} catch ( ex ) {
// Can't determine version
}
2023-12-31 21:42:00 +01:00
var configJSONS = { } ;
try {
configJSONS = JSON . parse ( fs . readFileSync ( _ _dirname + "/../../../config.json" ) ) ; // Read configuration JSON
} catch ( ex ) {
//YellowSquare 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 19:53:57 +02:00
class RequestBodyStream extends stream . Readable {
constructor ( sourceStream ) {
super ( ) ;
this . sourceStream = sourceStream ;
// When the source stream emits data, push it to the wrapper stream
this . sourceStream . on ( 'data' , ( chunk ) => {
this . push ( chunk ) ;
} ) ;
// When the source stream ends, push null to indicate the end of the wrapper stream
this . sourceStream . on ( 'end' , ( ) => {
this . push ( null ) ;
} ) ;
}
// Implement the _read method to handle the read operation
_read ( ) { }
}
class ErrorStream extends stream . Writable {
constructor ( ) {
super ( ) ;
this . buffer = Buffer . alloc ( 0 ) ;
}
_write ( chunk , encoding , callback ) {
// Concatenate the incoming chunk to the buffer
this . buffer = Buffer . concat ( [ this . buffer , chunk ] ) ;
// Call the callback to indicate that the write operation is complete
callback ( ) ;
}
}
2024-12-09 15:44:46 +01:00
var normalizedWebrootSupported = process . versions . svrjs && process . versions . svrjs . match ( /^(?:Nightly-|(?:[5-9]|[1234][0-9])[0-9]*\.|4\.(?:(?:[1][0-9]|[2-9])+\.))/i ) ;
function normalizeWebroot ( currentWebroot ) {
if ( currentWebroot === undefined ) {
return process . cwd ( ) ;
} else if ( ! path . isAbsolute ( currentWebroot ) ) {
return (
process . cwd ( ) + ( os . platform ( ) == "win32" ? "\\" : "/" ) + currentWebroot
) ;
} else {
return currentWebroot ;
}
}
2023-08-11 19:53:57 +02:00
function Mod ( ) { }
2024-02-07 01:25:32 +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 19:53:57 +02:00
return function ( ) {
if ( ! configJSON ) {
configJSON = configJSONS ;
}
2024-12-09 15:44:46 +01:00
var detectedWwwroot = normalizedWebrootSupported ? normalizeWebroot ( configJSON . wwwroot ) : process . cwd ( ) ;
2024-02-07 01:25:32 +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 19:53:57 +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 ) ) ;
function executeJSGI ( fname , req , res , dh , jsgiRequestObject ) {
// Function to execute JSGI scripts
if ( ! fname . match ( /\.jsgi(?:\.js)?$/ ) ) {
elseCallback ( ) ;
return ;
}
try {
2024-12-09 15:44:46 +01:00
var JSGIApp = require ( detectedWwwroot + "/" + fname ) ;
2023-08-11 19:53:57 +02:00
var jsgiResponseObject = { } ;
if ( typeof JSGIApp === "object" && JSGIApp . app ) {
jsgiResponseObject = JSGIApp . app ( jsgiRequestObject ) ;
} else {
throw new Error ( "JSGI app must have app key in exports objects!!!" ) ;
}
if ( jsgiRequestObject . jsgi . errors . readable ) jsgiRequestObject . jsgi . errors . end ( ) ;
var errors = jsgiRequestObject . jsgi . errors . buffer . toString ( ) ;
if ( errors . trim ( ) . length > 0 ) {
serverconsole . errmessage ( "There were JSGI application errors:" ) ;
serverconsole . errmessage ( errors ) ;
}
if ( ! jsgiResponseObject . status ) jsgiResponseObject . status = 200 ;
if ( ! getCustomHeaders ) {
var aheaders = JSON . parse ( JSON . stringify ( configJSON . customHeaders ) ) ;
} else {
var aheaders = getCustomHeaders ( ) ;
}
if ( jsgiResponseObject . headers ) {
var hKeys = Object . keys ( jsgiResponseObject . headers ) ;
for ( var i = 0 ; i < hKeys . length ; i ++ ) {
aheaders [ hKeys [ i ] ] = jsgiResponseObject . headers [ hKeys [ i ] ] ;
}
}
if ( ! jsgiResponseObject . body ) jsgiResponseObject . body = [ "" ] ;
if ( typeof jsgiResponseObject . body === "string" ) {
res . writeHead ( jsgiResponseObject . status , http . STATUS _CODES [ jsgiResponseObject . status ] , aheaders ) ;
res . write ( jsgiResponseObject . body ) ;
res . end ( ) ;
} else if ( typeof jsgiResponseObject . body . forEach !== "function" ) {
throw new Error ( "JSGI app must return body, which has forEach function." ) ;
} else {
res . writeHead ( jsgiResponseObject . status , http . STATUS _CODES [ jsgiResponseObject . status ] , aheaders ) ;
jsgiResponseObject . body . forEach ( function ( chunk ) {
res . write ( chunk ) ;
} ) ;
res . end ( ) ;
}
} catch ( error ) {
if ( ! callServerError ) {
res . writeHead ( 500 , {
"Content-Type" : "text/html" ,
"Server" : "YellowSquare/" + version
} ) ;
res . end ( "<html><head></head><body><h1>YellowSquare Error!</h1><p>Reason: " + error . message + "</p></body></html>" ) ;
} else {
callServerError ( 500 , "YellowSquare/" + version , error ) ;
}
}
}
2024-02-07 01:25:32 +01:00
function executeJSGIWithReqObj ( a , b , req , res , pubip , port , software , dh , user ) {
2023-08-11 19:53:57 +02:00
// Function to set up request object and execute JSGI scripts
var inputStream = new RequestBodyStream ( req ) ;
var errorStream = new ErrorStream ( ) ;
var jsgiRequestObject = {
version : req . httpVersion . split ( "." ) ,
method : req . method ,
headers : req . headers ,
input : inputStream ,
scriptName : a ,
2024-01-29 20:03:20 +01:00
pathInfo : decodeURIComponent ( b ) ,
2024-12-09 15:44:46 +01:00
pathTranslated : b ? ( ( detectedWwwroot + decodeURIComponent ( require ( "os" ) . platform == "win32" ? b . replace ( /\//g , "\\" ) : b ) ) . replace ( ( require ( "os" ) . platform == "win32" ? /\\\\/g : /\/\//g ) , ( require ( "os" ) . platform == "win32" ? "\\" : "/" ) ) ) : "" ,
2023-08-11 19:53:57 +02:00
scheme : req . socket . encrypted ? "https" : "http" ,
env : { } ,
jsgi : {
version : [ 0 , 3 ] ,
errors : errorStream ,
multithread : false ,
multiprocess : true ,
runOnce : false ,
async : false ,
cgi : false ,
ext : { }
} ,
serverSoftware : software ,
2023-09-03 11:26:34 +02:00
remoteAddr : ( 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-08-11 19:53:57 +02:00
} ;
2023-09-03 11:26:34 +02:00
if ( req . socket . realRemoteAddress && req . socket . realRemotePort ) {
jsgiRequestObject . remotePort = req . socket . realRemotePort ;
} else if ( ! ( req . socket . realRemoteAddress && ! req . socket . realRemotePort ) ) {
jsgiRequestObject . remotePort = req . socket . remotePort ;
}
2024-02-07 01:25:32 +01:00
if ( typeof user != "undefined" ) {
if ( user !== null ) {
if ( req . headers . authorization ) jsgiRequestObject . authType = req . headers . authorization . split ( " " ) [ 0 ] ;
jsgiRequestObject . remoteUser = user ;
}
} else if ( req . headers . authorization && ( typeof checkIfThereIsA401Rule == "undefined" || checkIfThereIsA401Rule ( ) ) ) {
2023-08-11 19:53:57 +02:00
jsgiRequestObject . authType = req . headers . authorization . split ( " " ) [ 0 ] ;
if ( jsgiRequestObject . authType == "Basic" ) {
var remoteCred = req . headers . authorization . split ( " " ) [ 1 ] ;
if ( ! remoteCred ) {
jsgiRequestObject . remoteUser = "yellowsquare_jsgi_invalid_user" ;
} else {
var remoteCredDecoded = Buffer . from ( remoteCred , "base64" ) . toString ( "utf8" ) ;
jsgiRequestObject . remoteUser = remoteCredDecoded . split ( ":" ) [ 0 ] ;
}
} else {
jsgiRequestObject . remoteUser = "svrjs_this_property_is_not_yet_supported_by_yellowsquare_jsgi" ;
}
}
jsgiRequestObject . queryString = req . url . split ( "?" ) [ 1 ] ;
if ( jsgiRequestObject . queryString == undefined || jsgiRequestObject . queryString == "undefined" ) jsgiRequestObject . queryString = "" ;
if ( pubip && ( port !== null && port !== undefined ) ) {
jsgiRequestObject . port = port ;
jsgiRequestObject . host = pubip . replace ( /^::ffff:/i , "" ) ;
if ( jsgiRequestObject . host . indexOf ( ":" ) != - 1 ) jsgiRequestObject . host = "[" + jsgiRequestObject . host + "]" ;
}
2024-12-09 15:44:46 +01:00
executeJSGI ( detectedWwwroot + a , req , res , dh , jsgiRequestObject ) ;
2023-08-11 19:53:57 +02:00
}
2024-02-15 20:09:10 +01:00
if ( href . match ( new RegExp ( "^/jsgi-bin(?:$|[?#/])" , os . platform ( ) == "win32" ? "i" : "" ) ) ) {
2024-12-09 15:44:46 +01:00
fs . stat ( detectedWwwroot + decodeURIComponent ( href ) , function ( err , stats ) {
2023-08-11 19:53:57 +02:00
if ( ! err ) {
if ( ! stats . isFile ( ) ) {
elseCallback ( ) ;
} else {
try {
executeJSGIWithReqObj (
2024-01-29 20:03:20 +01:00
decodeURIComponent ( href ) ,
2023-08-11 19:53:57 +02:00
"" ,
req ,
res ,
req . socket . localAddress ,
req . socket . localPort ,
getCustomHeaders ?
getCustomHeaders ( ) [ "Server" ] +
" YellowSquare/" +
version :
"SVR.JS/" +
configJSON . version +
" (" +
os . platform ( ) [ 0 ] . toUpperCase ( ) +
os . platform ( ) . slice ( 1 ) +
"; Node.JS/" +
process . version +
") YellowSquare/" +
version ,
2024-02-07 01:25:32 +01:00
bheaders ,
authUser
2023-08-11 19:53:57 +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 +
") YellowSquare/" +
version +
" " +
( req . headers . host == undefined ? "" : " on " + req . headers . host ) +
"</p></body></html>"
) ;
res . end ( ) ;
} else {
callServerError ( 500 , "YellowSquare/" + version , ex ) ;
}
}
}
} else if ( err && err . code == "ENOTDIR" ) {
function checkPath ( pth , cb , a ) {
// Function to check the path of the file and execute CGI script
var cpth = pth . split ( "/" ) ;
if ( cpth . length < 3 ) {
cb ( false ) ;
return ;
}
if ( ! a ) b = [ ] ;
else var b = a . split ( "/" ) ;
var isFile = false ;
2024-12-09 15:44:46 +01:00
fs . stat ( detectedWwwroot + "/" + pth , function ( err , stats ) {
2023-08-11 19:53:57 +02:00
if ( ! err && stats . isFile ( ) ) {
cb ( {
fpth : pth ,
rpth : ( a !== undefined ? "/" + a : "" )
} )
} else {
b . unshift ( cpth . pop ( ) ) ;
return checkPath ( cpth . join ( "/" ) , cb , b . join ( "/" ) ) ;
}
} ) ;
}
2024-01-29 20:03:20 +01:00
checkPath ( "." + decodeURIComponent ( href ) , function ( pathp ) {
2023-08-11 19:53:57 +02:00
if ( ! pathp ) {
elseCallback ( ) ;
} else {
try {
executeJSGIWithReqObj (
pathp . fpth . substr ( 1 ) ,
pathp . rpth ,
req ,
res ,
req . socket . localAddress ,
req . socket . localPort ,
getCustomHeaders ?
getCustomHeaders ( ) [ "Server" ] +
" YellowSquare/" +
version :
"SVR.JS/" +
configJSON . version +
" (" +
os . platform ( ) [ 0 ] . toUpperCase ( ) +
os . platform ( ) . slice ( 1 ) +
"; Node.JS/" +
process . version +
") YellowSquare/" +
version ,
2024-02-07 01:25:32 +01:00
bheaders ,
authUser
2023-08-11 19:53:57 +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 +
") YellowSquare/" +
version +
" " +
( req . headers . host == undefined ? "" : " on " + req . headers . host ) +
"</p></body></html>"
) ;
res . end ( ) ;
} else {
callServerError ( 500 , "YellowSquare/" + version , ex ) ;
}
}
}
} ) ;
} else {
2023-12-31 21:43:26 +01:00
elseCallback ( ) ; //Invoke default error handler
2023-08-11 19:53:57 +02:00
}
} ) ;
} else {
elseCallback ( ) ;
}
}
}
module . exports = Mod ;