server.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. /**
  2. * Module dependencies and cached references.
  3. */
  4. var slice = Array.prototype.slice
  5. , net = require('net');
  6. /**
  7. * The server that does the Policy File severing
  8. *
  9. * Options:
  10. * - `log` false or a function that can output log information, defaults to console.log?
  11. *
  12. * @param {Object} options Options to customize the servers functionality.
  13. * @param {Array} origins The origins that are allowed on this server, defaults to `*:*`.
  14. * @api public
  15. */
  16. function Server (options, origins) {
  17. var me = this;
  18. this.origins = origins || ['*:*'];
  19. this.port = 843;
  20. this.log = console.log;
  21. // merge `this` with the options
  22. Object.keys(options).forEach(function (key) {
  23. me[key] && (me[key] = options[key])
  24. });
  25. // create the net server
  26. this.socket = net.createServer(function createServer (socket) {
  27. socket.on('error', function socketError () {
  28. me.responder.call(me, socket);
  29. });
  30. me.responder.call(me, socket);
  31. });
  32. // Listen for errors as the port might be blocked because we do not have root priv.
  33. this.socket.on('error', function serverError (err) {
  34. // Special and common case error handling
  35. if (err.errno == 13) {
  36. me.log && me.log(
  37. 'Unable to listen to port `' + me.port + '` as your Node.js instance does not have root privileges. ' +
  38. (
  39. me.server
  40. ? 'The Flash Policy File requests will only be served inline over the supplied HTTP server. Inline serving is slower than a dedicated server instance.'
  41. : 'No fallback server supplied, we will be unable to answer Flash Policy File requests.'
  42. )
  43. );
  44. me.emit('connect_failed', err);
  45. me.socket.removeAllListeners();
  46. delete me.socket;
  47. } else {
  48. me.log && me.log('FlashPolicyFileServer received an error event:\n' + (err.message ? err.message : err));
  49. }
  50. });
  51. this.socket.on('timeout', function serverTimeout () {});
  52. this.socket.on('close', function serverClosed (err) {
  53. err && me.log && me.log('Server closing due to an error: \n' + (err.message ? err.message : err));
  54. if (me.server) {
  55. // Remove the inline policy listener if we close down
  56. // but only when the server was `online` (see listen prototype)
  57. if (me.server['@'] && me.server.online) {
  58. me.server.removeListener('connection', me.server['@']);
  59. }
  60. // not online anymore
  61. delete me.server.online;
  62. }
  63. });
  64. // Compile the initial `buffer`
  65. this.compile();
  66. }
  67. /**
  68. * Start listening for requests
  69. *
  70. * @param {Number} port The port number it should be listening to.
  71. * @param {Server} server A HTTP server instance, this will be used to listen for inline requests
  72. * @param {Function} cb The callback needs to be called once server is ready
  73. * @api public
  74. */
  75. Server.prototype.listen = function listen (port, server, cb){
  76. var me = this
  77. , args = slice.call(arguments, 0)
  78. , callback;
  79. // assign the correct vars, for flexible arguments
  80. args.forEach(function args (arg){
  81. var type = typeof arg;
  82. if (type === 'number') me.port = arg;
  83. if (type === 'function') callback = arg;
  84. if (type === 'object') me.server = arg;
  85. });
  86. if (this.server) {
  87. // no one in their right mind would ever create a `@` prototype, so Im just gonna store
  88. // my function on the server, so I can remove it later again once the server(s) closes
  89. this.server['@'] = function connection (socket) {
  90. socket.once('data', function requestData (data) {
  91. // if it's a Flash policy request, and we can write to the
  92. if (
  93. data
  94. && data[0] === 60
  95. && data.toString() === '<policy-file-request/>\0'
  96. && socket
  97. && (socket.readyState === 'open' || socket.readyState === 'writeOnly')
  98. ){
  99. // send the buffer
  100. try {
  101. socket.end(me.buffer);
  102. } catch (e) {}
  103. }
  104. });
  105. };
  106. // attach it
  107. this.server.on('connection', this.server['@']);
  108. }
  109. // We add a callback method, so we can set a flag for when the server is `enabled` or `online`.
  110. // this flag is needed because if a error occurs and the we cannot boot up the server the
  111. // fallback functionality should not be removed during the `close` event
  112. this.port >= 0 && this.socket.listen(this.port, function serverListening () {
  113. me.socket.online = true;
  114. if (callback) {
  115. callback.call(me);
  116. callback = undefined;
  117. }
  118. });
  119. return this;
  120. };
  121. /**
  122. * Responds to socket connects and writes the compile policy file.
  123. *
  124. * @param {net.Socket} socket The socket that needs to receive the message
  125. * @api private
  126. */
  127. Server.prototype.responder = function responder (socket){
  128. if (socket && socket.readyState == 'open' && socket.end) {
  129. try {
  130. socket.end(this.buffer);
  131. } catch (e) {}
  132. }
  133. };
  134. /**
  135. * Compiles the supplied origins to a Flash Policy File format and stores it in a Node.js Buffer
  136. * this way it can be send over the wire without any performance loss.
  137. *
  138. * @api private
  139. */
  140. Server.prototype.compile = function compile (){
  141. var xml = [
  142. '<?xml version="1.0"?>'
  143. , '<!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">'
  144. , '<cross-domain-policy>'
  145. ];
  146. // add the allow access element
  147. this.origins.forEach(function origin (origin){
  148. var parts = origin.split(':');
  149. xml.push('<allow-access-from domain="' + parts[0] + '" to-ports="'+ parts[1] +'"/>');
  150. });
  151. xml.push('</cross-domain-policy>');
  152. // store the result in a buffer so we don't have to re-generate it all the time
  153. this.buffer = new Buffer(xml.join(''), 'utf8');
  154. return this;
  155. };
  156. /**
  157. * Adds a new origin to the Flash Policy File.
  158. *
  159. * @param {Arguments} The origins that need to be added.
  160. * @api public
  161. */
  162. Server.prototype.add = function add(){
  163. var args = slice.call(arguments, 0)
  164. , i = args.length;
  165. // flag duplicates
  166. while (i--) {
  167. if (this.origins.indexOf(args[i]) >= 0){
  168. args[i] = null;
  169. }
  170. }
  171. // Add all the arguments to the array
  172. // but first we want to remove all `falsy` values from the args
  173. Array.prototype.push.apply(
  174. this.origins
  175. , args.filter(function filter (value) {
  176. return !!value;
  177. })
  178. );
  179. this.compile();
  180. return this;
  181. };
  182. /**
  183. * Removes a origin from the Flash Policy File.
  184. *
  185. * @param {String} origin The origin that needs to be removed from the server
  186. * @api public
  187. */
  188. Server.prototype.remove = function remove (origin){
  189. var position = this.origins.indexOf(origin);
  190. // only remove and recompile if we have a match
  191. if (position > 0) {
  192. this.origins.splice(position,1);
  193. this.compile();
  194. }
  195. return this;
  196. };
  197. /**
  198. * Closes and cleans up the server
  199. *
  200. * @api public
  201. */
  202. Server.prototype.close = function close () {
  203. this.socket.removeAllListeners();
  204. this.socket.close();
  205. return this;
  206. };
  207. /**
  208. * Proxy the event listener requests to the created Net server
  209. */
  210. Object.keys(process.EventEmitter.prototype).forEach(function proxy (key){
  211. Server.prototype[key] = Server.prototype[key] || function () {
  212. if (this.socket) {
  213. this.socket[key].apply(this.socket, arguments);
  214. }
  215. return this;
  216. };
  217. });
  218. /**
  219. * Creates a new server instance.
  220. *
  221. * @param {Object} options A options object to override the default config
  222. * @param {Array} origins The origins that should be allowed by the server
  223. * @api public
  224. */
  225. exports.createServer = function createServer(options, origins){
  226. origins = Array.isArray(origins) ? origins : (Array.isArray(options) ? options : false);
  227. options = !Array.isArray(options) && options ? options : {};
  228. return new Server(options, origins);
  229. };
  230. /**
  231. * Provide a hook to the original server, so it can be extended if needed.
  232. */
  233. exports.Server = Server;
  234. /**
  235. * Module version
  236. */
  237. exports.version = '0.0.4';