admin.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. // admin.js
  2. // Helper functions for VWF node server that handle all /admin related requests.
  3. var helpers = require( './helpers'),
  4. servehandler = require( './serve-handler' ),
  5. fs = require( 'fs' ),
  6. libpath = require( 'path' ),
  7. url = require( 'url' ),
  8. querystring = require( 'querystring' ),
  9. serve = require( './serve' );
  10. // ServeTimeStateJSON is a helper function for serving the current 'time' status of
  11. // an instance as a JSON object to the client.
  12. function ServeTimeStateJSON( request, response, instanceHash ) {
  13. 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( ) };
  14. serve.JSON( instanceState, response, url.parse( request.url, true ) );
  15. }
  16. // HandleAdminState is a handler for dealing with the state GET requests
  17. // of the admin interface.
  18. // It returns the current time state of the instance as a JSON object.
  19. // If a query string is included, and the query string includes a rate value
  20. // the rate will be set first.
  21. // Returns true if it properly handles a properly formed request, otherwise
  22. // returns false.
  23. function HandleAdminState( request, response, instanceHash, queries ) {
  24. if ( ( instanceHash ) && ( request.method == 'GET' ) ) {
  25. if ( queries ) {
  26. var floatRate = parseFloat( queries.rate );
  27. if ( ( floatRate ) && ( floatRate > 0.0 ) ) {
  28. global.instances[ instanceHash ].rate = floatRate;
  29. global.instances[ instanceHash ].play( );
  30. }
  31. }
  32. ServeTimeStateJSON( request, response, instanceHash );
  33. return true;
  34. }
  35. return false;
  36. }
  37. // HandleAdminRate is a handler for dealing with the rate POST requests
  38. // of the admin interface.
  39. // After verifying that the post is for a valid instance (by testing if
  40. // instanceHash is set), and testing that this was an actual POST request,
  41. // then deal with the post itself. Store the posted data (break after a limited
  42. // amount of time to avoid malicious infinite POST type issues ). Once the
  43. // post ends, attempt to parse the data as a float, use said float to set
  44. // the rate.
  45. // Returns true if it properly handles a properly formed request, otherwise
  46. // returns false.
  47. function HandleAdminRate( request, response, instanceHash ) {
  48. if ( instanceHash ) {
  49. if ( request.method == 'POST' ) {
  50. var rateData = "";
  51. request.on( 'data', function ( data ) {
  52. rateData += data;
  53. if ( rateData.length > 1000 ) {
  54. rateData = "";
  55. response.writeHead( 413, { 'Content-Type': 'text/plain' } ).end( );
  56. request.connection.destroy();
  57. }
  58. } );
  59. request.on( 'end', function( ) {
  60. var parsedRate;
  61. parsedRate = parseFloat( rateData );
  62. if ( ( parsedRate ) && ( parsedRate > 0 ) ) {
  63. global.instances[ instanceHash ].rate = parsedRate;
  64. global.instances[ instanceHash ].play( );
  65. ServeTimeStateJSON( request, response, instanceHash );
  66. }
  67. } );
  68. return true;
  69. }
  70. }
  71. return false;
  72. }
  73. // HandleAdminPlay is a handler for dealing with the play POST requests
  74. // of the admin interface.
  75. // Despite not actually needing any data posted, allow some to be sent
  76. // but cap and destroy the connection after a brief period since this
  77. // shouldn't need any posted data, and we want to avoid malicious
  78. // endless POSTs.
  79. // Update the appropriate instance's flags so that it is playing.
  80. // Returns true if it properly handles a properly formed request, otherwise
  81. // returns false.
  82. function HandleAdminPlay( request, response, instanceHash ) {
  83. if ( instanceHash ) {
  84. if ( request.method == 'POST' ) {
  85. var rateData = "";
  86. request.on( 'data', function ( data ) {
  87. if ( rateData.length > 10 ) {
  88. rateData = "";
  89. response.writeHead( 413, { 'Content-Type': 'text/plain' } ).end( );
  90. request.connection.destroy();
  91. }
  92. } );
  93. global.instances[ instanceHash ].play( );
  94. ServeTimeStateJSON( request, response, instanceHash );
  95. return true;
  96. }
  97. }
  98. }
  99. // HandleAdminPause is a handler for dealing with the pause POST requests
  100. // of the admin interface.
  101. // Despite not actually needing any data posted, allow some to be sent
  102. // but cap and destroy the connection after a brief period since this
  103. // shouldn't need any posted data, and we want to avoid malicious
  104. // endless POSTs.
  105. // Update the appropriate instance's flags so that it is paused.
  106. // Returns true if it properly handles a properly formed request, otherwise
  107. // returns false.
  108. function HandleAdminPause( request, response, instanceHash ) {
  109. if ( instanceHash ) {
  110. if ( request.method == 'POST' ) {
  111. global.instances[ instanceHash ].pause( );
  112. ServeTimeStateJSON( request, response, instanceHash );
  113. return true;
  114. }
  115. }
  116. }
  117. // HandleAdminPlay is a handler for dealing with the stop POST requests
  118. // of the admin interface.
  119. // Despite not actually needing any data posted, allow some to be sent
  120. // but cap and destroy the connection after a brief period since this
  121. // shouldn't need any posted data, and we want to avoid malicious
  122. // endless POSTs.
  123. // Update the appropriate instance's flags and time so that it is stopped.
  124. // Returns true if it properly handles a properly formed request, otherwise
  125. // returns false.
  126. function HandleAdminStop( request, response, instanceHash ) {
  127. if ( instanceHash ) {
  128. if ( request.method == 'POST' ) {
  129. global.instances[ instanceHash ].stop( );
  130. ServeTimeStateJSON( request, response, instanceHash );
  131. return true;
  132. }
  133. }
  134. }
  135. // HandleAdminInstances is a handler for dealing with the 'instances' GET requests
  136. // of the admin interface.
  137. // This request returns a JSON object containing a list of the current instances of the
  138. // application it was invoked within the path of. (Each instance also contains a list of
  139. // clients attached to that instance).
  140. // Returns true if it properly handles a properly formed request, otherwise
  141. // returns false.
  142. function HandleAdminInstances( request, response, parsedRequest ) {
  143. if ( ( parsedRequest[ 'public_path' ] ) && ( parsedRequest[ 'application' ] ) && ( request.method == "GET" ) ) {
  144. var data = { };
  145. var applicationInstanceRegexp = new RegExp("^" + helpers.JoinPath( parsedRequest[ 'public_path' ], parsedRequest[ 'application' ] ) + "/[0-9A-Za-z]{16}$");
  146. for ( var i in global.instances ) {
  147. if ( i.match( applicationInstanceRegexp ) ) {
  148. data[ i ] = { "clients": { } };
  149. for ( var j in global.instances[ i ].clients ) {
  150. data[ i ].clients[ j ] = null;
  151. }
  152. }
  153. }
  154. serve.JSON( data, response, url.parse( request.url, true ) );
  155. return true;
  156. }
  157. return false;
  158. }
  159. // HandleAdminModels is a handler for dealing with the 'models' GET requests
  160. // of the admin interface.
  161. // The models request returns a JSON object containing a list of files stored in the 'public/models' directory.
  162. // Returns true if it properly handles a properly formed request, otherwise
  163. // returns false.
  164. function HandleAdminModels( request, response ) {
  165. if ( request.method == "GET" ) {
  166. var filenames = fs.readdirSync( "." + libpath.sep + "public" + libpath.sep + "demos" + libpath.sep +"models" + libpath.sep );
  167. var data = [];
  168. for ( var i in filenames ) {
  169. var filedata = {};
  170. var filedetails = fs.statSync( "." + libpath.sep + "public" + libpath.sep + "demos" + libpath.sep + "models" + libpath.sep + filenames[ i ] );
  171. if ( filedetails.isFile( ) ) {
  172. filedata[ 'url' ] = "http://" + request.headers.host + "/demos/models/" + filenames[ i ];
  173. filedata[ 'basename' ] = filenames[ i ];
  174. filedata[ 'size' ] = filedetails.size;
  175. filedata[ 'mtime' ] = filedetails.mtime.toGMTString( );
  176. data.push( filedata );
  177. }
  178. }
  179. serve.JSON( data, response, url.parse( request.url, true ) );
  180. return true;
  181. }
  182. return false;
  183. }
  184. // HandleAdminFiles is a handler for dealing with the 'files' GET requests
  185. // of the admin interface.
  186. // This request returns the list of json files found in the public_path directory of this
  187. // request.
  188. // Returns true if it properly handles a properly formed request, otherwise
  189. // returns false.
  190. function HandleAdminFiles( request, response, parsedRequest ) {
  191. if ( ( parsedRequest[ 'public_path' ] ) && ( request.method == "GET" ) ) {
  192. var filenames = fs.readdirSync( helpers.JoinPath( global.applicationRoot, parsedRequest[ 'public_path' ] ).replace( /\//g, libpath.sep ) );
  193. var data = [];
  194. for ( var i in filenames ) {
  195. if ( filenames[ i ].match( /\.[jJ][sS][oO][nN]$/ ) ) {
  196. var filedata = {};
  197. var filedetails = fs.statSync( helpers.JoinPath( global.applicationRoot, parsedRequest[ 'public_path' ], filenames[ i ] ).replace( /\//g, libpath.sep ) );
  198. if ( filedetails.helpers.IsFile( ) ) {
  199. filedata[ 'url' ] = "http://" + request.headers.host + helpers.JoinPath( parsedRequest[ 'public_path' ], filenames[ i ] );
  200. filedata[ 'basename' ] = filenames[ i ];
  201. filedata[ 'size' ] = filedetails.size;
  202. filedata[ 'mtime' ] = filedetails.mtime.toGMTString( );
  203. data.push( filedata );
  204. }
  205. }
  206. }
  207. serve.JSON( data, response, url.parse( request.url, true ) );
  208. return true;
  209. }
  210. return false;
  211. }
  212. // HandleAdminConfig is a handler for dealing with the 'config' GET requests
  213. // of the admin interface.
  214. // This request returns contents of the config file for the application, whether it is
  215. // YAML or JSON.
  216. // Returns true if it properly handles a properly formed request and the config file exists, otherwise
  217. // returns false.
  218. function HandleAdminConfig( request, response, parsedRequest ) {
  219. if ( ( parsedRequest[ 'public_path' ] ) && ( parsedRequest[ 'application' ] ) && ( request.method == "GET" ) ) {
  220. var filenameRoot = helpers.JoinPath( global.applicationRoot, parsedRequest[ 'public_path' ], parsedRequest[ 'application' ] );
  221. if ( helpers.IsFile( filenameRoot + ".config.yaml" ) ) {
  222. serve.YAML( ( filenameRoot + ".config.yaml" ).replace( /\//g, libpath.sep ), response, url.parse( request.url, true ) );
  223. return true;
  224. }
  225. else if ( helpers.IsFile( filenameRoot + ".config.json" ) ) {
  226. serve.JSONFile( ( filenameRoot + ".config.json" ).replace( /\//g, libpath.sep ), response, url.parse( request.url, true ) );
  227. return true;
  228. }
  229. else {
  230. // If there is no config file, return an empty string to prevent 404 errors
  231. serve.JSON("", response);
  232. }
  233. }
  234. return false;
  235. }
  236. // HandleAdminChrome is a handler for dealing with the 'chrome' GET requests
  237. // of the admin interface.
  238. // This request returns the application's "chrome" HTML overlay or an empty string if non-existant.
  239. // Returns true if it properly handles a properly formed request, otherwise returns false.
  240. function HandleAdminChrome( request, response, parsedRequest ) {
  241. var publicPath = parsedRequest[ 'public_path' ];
  242. var appName = parsedRequest[ 'application' ];
  243. if ( publicPath && appName && ( request.method == "GET" ) ) {
  244. var fileBasePath = helpers.JoinPath( global.applicationRoot, publicPath, appName );
  245. var filePath = fileBasePath + ".html";
  246. if ( helpers.IsFile( filePath ) ) {
  247. servehandler.File( request, response, filePath );
  248. } else {
  249. response.end( "" );
  250. }
  251. return true;
  252. }
  253. return false;
  254. }
  255. // The Serve function in admin takes in the nodeJS request and nnodeJS response as well as
  256. // the parsedRequest information.
  257. // It tests if the request is actually an admin request, and if calls an appropriate handler
  258. // to serve the appropriate response.
  259. // If the request is succesfully served, it returns TRUE, if the request is not succesfully
  260. // served (whether due to a file not being present, the request being malformed, or simply it
  261. // not being an admin request), then the function will return FALSE.
  262. function Serve( request, response, parsedRequest ) {
  263. if ( parsedRequest[ 'private_path' ] ) {
  264. var segments = helpers.GenerateSegments( parsedRequest[ 'private_path' ] );
  265. if ( ( segments.length > 0 ) && ( segments[ 0 ] == "admin" ) ) {
  266. if ( segments.length == 2 ) {
  267. var queries = url.parse( request.url ).query;
  268. if ( queries ) {
  269. queries = querystring.parse( queries );
  270. }
  271. var instanceHash = undefined;
  272. if ( ( parsedRequest[ 'instance' ] ) && ( parsedRequest[ 'public_path' ] ) && ( parsedRequest[ 'application' ] ) ) {
  273. instanceHash = helpers.JoinPath( parsedRequest[ 'public_path' ], parsedRequest[ 'application' ], parsedRequest[ 'instance' ] );
  274. }
  275. if ( instanceHash && ( global.instances[ instanceHash ] == undefined ) ) {
  276. instanceHash = undefined;
  277. }
  278. switch ( segments[ 1 ] ) {
  279. case "state":
  280. return HandleAdminState( request, response, instanceHash, queries );
  281. case "rate":
  282. return HandleAdminRate( request, response, instanceHash );
  283. case "play":
  284. return HandleAdminPlay( request, response, instanceHash );
  285. case "pause":
  286. return HandleAdminPause( request, response, instanceHash );
  287. case "stop":
  288. return HandleAdminStop( request, response, instanceHash );
  289. case "instances":
  290. return HandleAdminInstances( request, response, parsedRequest );
  291. case "models":
  292. return HandleAdminModels( request, response );
  293. case "files":
  294. return HandleAdminFiles( request, response, parsedRequest );
  295. case "config":
  296. return HandleAdminConfig( request, response, parsedRequest );
  297. case "chrome":
  298. return HandleAdminChrome( request, response, parsedRequest );
  299. }
  300. }
  301. }
  302. }
  303. return false;
  304. }
  305. exports.Serve = Serve;