// admin.js // Helper functions for VWF node server that handle all /admin related requests. var helpers = require( './helpers'), servehandler = require( './serve-handler' ), fs = require( 'fs' ), libpath = require( 'path' ), url = require( 'url' ), querystring = require( 'querystring' ), serve = require( './serve' ); // ServeTimeStateJSON is a helper function for serving the current 'time' status of // an instance as a JSON object to the client. function ServeTimeStateJSON( request, response, instanceHash ) { var instanceState = { "time": global.instances[ instanceHash ].getTime( ), "rate": global.instances[ instanceHash ].rate, "playing": global.instances[ instanceHash ].isPlaying( ), "paused": global.instances[ instanceHash ].isPaused( ), "stopped": global.instances[ instanceHash ].isStopped( ) }; serve.JSON( instanceState, response, url.parse( request.url, true ) ); } // HandleAdminState is a handler for dealing with the state GET requests // of the admin interface. // It returns the current time state of the instance as a JSON object. // If a query string is included, and the query string includes a rate value // the rate will be set first. // Returns true if it properly handles a properly formed request, otherwise // returns false. function HandleAdminState( request, response, instanceHash, queries ) { if ( ( instanceHash ) && ( request.method == 'GET' ) ) { if ( queries ) { var floatRate = parseFloat( queries.rate ); if ( ( floatRate ) && ( floatRate > 0.0 ) ) { global.instances[ instanceHash ].rate = floatRate; global.instances[ instanceHash ].play( ); } } ServeTimeStateJSON( request, response, instanceHash ); return true; } return false; } // HandleAdminRate is a handler for dealing with the rate POST requests // of the admin interface. // After verifying that the post is for a valid instance (by testing if // instanceHash is set), and testing that this was an actual POST request, // then deal with the post itself. Store the posted data (break after a limited // amount of time to avoid malicious infinite POST type issues ). Once the // post ends, attempt to parse the data as a float, use said float to set // the rate. // Returns true if it properly handles a properly formed request, otherwise // returns false. function HandleAdminRate( request, response, instanceHash ) { if ( instanceHash ) { if ( request.method == 'POST' ) { var rateData = ""; request.on( 'data', function ( data ) { rateData += data; if ( rateData.length > 1000 ) { rateData = ""; response.writeHead( 413, { 'Content-Type': 'text/plain' } ).end( ); request.connection.destroy(); } } ); request.on( 'end', function( ) { var parsedRate; parsedRate = parseFloat( rateData ); if ( ( parsedRate ) && ( parsedRate > 0 ) ) { global.instances[ instanceHash ].rate = parsedRate; global.instances[ instanceHash ].play( ); ServeTimeStateJSON( request, response, instanceHash ); } } ); return true; } } return false; } // HandleAdminPlay is a handler for dealing with the play POST requests // of the admin interface. // Despite not actually needing any data posted, allow some to be sent // but cap and destroy the connection after a brief period since this // shouldn't need any posted data, and we want to avoid malicious // endless POSTs. // Update the appropriate instance's flags so that it is playing. // Returns true if it properly handles a properly formed request, otherwise // returns false. function HandleAdminPlay( request, response, instanceHash ) { if ( instanceHash ) { if ( request.method == 'POST' ) { var rateData = ""; request.on( 'data', function ( data ) { if ( rateData.length > 10 ) { rateData = ""; response.writeHead( 413, { 'Content-Type': 'text/plain' } ).end( ); request.connection.destroy(); } } ); global.instances[ instanceHash ].play( ); ServeTimeStateJSON( request, response, instanceHash ); return true; } } } // HandleAdminPause is a handler for dealing with the pause POST requests // of the admin interface. // Despite not actually needing any data posted, allow some to be sent // but cap and destroy the connection after a brief period since this // shouldn't need any posted data, and we want to avoid malicious // endless POSTs. // Update the appropriate instance's flags so that it is paused. // Returns true if it properly handles a properly formed request, otherwise // returns false. function HandleAdminPause( request, response, instanceHash ) { if ( instanceHash ) { if ( request.method == 'POST' ) { global.instances[ instanceHash ].pause( ); ServeTimeStateJSON( request, response, instanceHash ); return true; } } } // HandleAdminPlay is a handler for dealing with the stop POST requests // of the admin interface. // Despite not actually needing any data posted, allow some to be sent // but cap and destroy the connection after a brief period since this // shouldn't need any posted data, and we want to avoid malicious // endless POSTs. // Update the appropriate instance's flags and time so that it is stopped. // Returns true if it properly handles a properly formed request, otherwise // returns false. function HandleAdminStop( request, response, instanceHash ) { if ( instanceHash ) { if ( request.method == 'POST' ) { global.instances[ instanceHash ].stop( ); ServeTimeStateJSON( request, response, instanceHash ); return true; } } } // HandleAdminInstances is a handler for dealing with the 'instances' GET requests // of the admin interface. // This request returns a JSON object containing a list of the current instances of the // application it was invoked within the path of. (Each instance also contains a list of // clients attached to that instance). // Returns true if it properly handles a properly formed request, otherwise // returns false. function HandleAdminInstances( request, response, parsedRequest ) { if ( ( parsedRequest[ 'public_path' ] ) && ( parsedRequest[ 'application' ] ) && ( request.method == "GET" ) ) { var data = { }; var applicationInstanceRegexp = new RegExp("^" + helpers.JoinPath( parsedRequest[ 'public_path' ], parsedRequest[ 'application' ] ) + "/[0-9A-Za-z]{16}$"); for ( var i in global.instances ) { if ( i.match( applicationInstanceRegexp ) ) { data[ i ] = { "clients": { } }; for ( var j in global.instances[ i ].clients ) { data[ i ].clients[ j ] = null; } } } serve.JSON( data, response, url.parse( request.url, true ) ); return true; } return false; } // HandleAdminModels is a handler for dealing with the 'models' GET requests // of the admin interface. // The models request returns a JSON object containing a list of files stored in the 'public/models' directory. // Returns true if it properly handles a properly formed request, otherwise // returns false. function HandleAdminModels( request, response ) { if ( request.method == "GET" ) { var filenames = fs.readdirSync( "." + libpath.sep + "public" + libpath.sep + "demos" + libpath.sep +"models" + libpath.sep ); var data = []; for ( var i in filenames ) { var filedata = {}; var filedetails = fs.statSync( "." + libpath.sep + "public" + libpath.sep + "demos" + libpath.sep + "models" + libpath.sep + filenames[ i ] ); if ( filedetails.isFile( ) ) { filedata[ 'url' ] = "http://" + request.headers.host + "/demos/models/" + filenames[ i ]; filedata[ 'basename' ] = filenames[ i ]; filedata[ 'size' ] = filedetails.size; filedata[ 'mtime' ] = filedetails.mtime.toGMTString( ); data.push( filedata ); } } serve.JSON( data, response, url.parse( request.url, true ) ); return true; } return false; } // HandleAdminFiles is a handler for dealing with the 'files' GET requests // of the admin interface. // This request returns the list of json files found in the public_path directory of this // request. // Returns true if it properly handles a properly formed request, otherwise // returns false. function HandleAdminFiles( request, response, parsedRequest ) { if ( ( parsedRequest[ 'public_path' ] ) && ( request.method == "GET" ) ) { var filenames = fs.readdirSync( helpers.JoinPath( global.applicationRoot, parsedRequest[ 'public_path' ] ).replace( /\//g, libpath.sep ) ); var data = []; for ( var i in filenames ) { if ( filenames[ i ].match( /\.[jJ][sS][oO][nN]$/ ) ) { var filedata = {}; var filedetails = fs.statSync( helpers.JoinPath( global.applicationRoot, parsedRequest[ 'public_path' ], filenames[ i ] ).replace( /\//g, libpath.sep ) ); if ( filedetails.helpers.IsFile( ) ) { filedata[ 'url' ] = "http://" + request.headers.host + helpers.JoinPath( parsedRequest[ 'public_path' ], filenames[ i ] ); filedata[ 'basename' ] = filenames[ i ]; filedata[ 'size' ] = filedetails.size; filedata[ 'mtime' ] = filedetails.mtime.toGMTString( ); data.push( filedata ); } } } serve.JSON( data, response, url.parse( request.url, true ) ); return true; } return false; } // HandleAdminConfig is a handler for dealing with the 'config' GET requests // of the admin interface. // This request returns contents of the config file for the application, whether it is // YAML or JSON. // Returns true if it properly handles a properly formed request and the config file exists, otherwise // returns false. function HandleAdminConfig( request, response, parsedRequest ) { if ( ( parsedRequest[ 'public_path' ] ) && ( parsedRequest[ 'application' ] ) && ( request.method == "GET" ) ) { var filenameRoot = helpers.JoinPath( global.applicationRoot, parsedRequest[ 'public_path' ], parsedRequest[ 'application' ] ); if ( helpers.IsFile( filenameRoot + ".config.yaml" ) ) { serve.YAML( ( filenameRoot + ".config.yaml" ).replace( /\//g, libpath.sep ), response, url.parse( request.url, true ) ); return true; } else if ( helpers.IsFile( filenameRoot + ".config.json" ) ) { serve.JSONFile( ( filenameRoot + ".config.json" ).replace( /\//g, libpath.sep ), response, url.parse( request.url, true ) ); return true; } else { // If there is no config file, return an empty string to prevent 404 errors serve.JSON("", response); } } return false; } // HandleAdminChrome is a handler for dealing with the 'chrome' GET requests // of the admin interface. // This request returns the application's "chrome" HTML overlay or an empty string if non-existant. // Returns true if it properly handles a properly formed request, otherwise returns false. function HandleAdminChrome( request, response, parsedRequest ) { var publicPath = parsedRequest[ 'public_path' ]; var appName = parsedRequest[ 'application' ]; if ( publicPath && appName && ( request.method == "GET" ) ) { var fileBasePath = helpers.JoinPath( global.applicationRoot, publicPath, appName ); var filePath = fileBasePath + ".html"; if ( helpers.IsFile( filePath ) ) { servehandler.File( request, response, filePath ); } else { response.end( "" ); } return true; } return false; } // The Serve function in admin takes in the nodeJS request and nnodeJS response as well as // the parsedRequest information. // It tests if the request is actually an admin request, and if calls an appropriate handler // to serve the appropriate response. // If the request is succesfully served, it returns TRUE, if the request is not succesfully // served (whether due to a file not being present, the request being malformed, or simply it // not being an admin request), then the function will return FALSE. function Serve( request, response, parsedRequest ) { if ( parsedRequest[ 'private_path' ] ) { var segments = helpers.GenerateSegments( parsedRequest[ 'private_path' ] ); if ( ( segments.length > 0 ) && ( segments[ 0 ] == "admin" ) ) { if ( segments.length == 2 ) { var queries = url.parse( request.url ).query; if ( queries ) { queries = querystring.parse( queries ); } var instanceHash = undefined; if ( ( parsedRequest[ 'instance' ] ) && ( parsedRequest[ 'public_path' ] ) && ( parsedRequest[ 'application' ] ) ) { instanceHash = helpers.JoinPath( parsedRequest[ 'public_path' ], parsedRequest[ 'application' ], parsedRequest[ 'instance' ] ); } if ( instanceHash && ( global.instances[ instanceHash ] == undefined ) ) { instanceHash = undefined; } switch ( segments[ 1 ] ) { case "state": return HandleAdminState( request, response, instanceHash, queries ); case "rate": return HandleAdminRate( request, response, instanceHash ); case "play": return HandleAdminPlay( request, response, instanceHash ); case "pause": return HandleAdminPause( request, response, instanceHash ); case "stop": return HandleAdminStop( request, response, instanceHash ); case "instances": return HandleAdminInstances( request, response, parsedRequest ); case "models": return HandleAdminModels( request, response ); case "files": return HandleAdminFiles( request, response, parsedRequest ); case "config": return HandleAdminConfig( request, response, parsedRequest ); case "chrome": return HandleAdminChrome( request, response, parsedRequest ); } } } } return false; } exports.Serve = Serve;