/** * Module dependencies and cached references. */ var slice = Array.prototype.slice , net = require('net'); /** * The server that does the Policy File severing * * Options: * - `log` false or a function that can output log information, defaults to console.log? * * @param {Object} options Options to customize the servers functionality. * @param {Array} origins The origins that are allowed on this server, defaults to `*:*`. * @api public */ function Server (options, origins) { var me = this; this.origins = origins || ['*:*']; this.port = 843; this.log = console.log; // merge `this` with the options Object.keys(options).forEach(function (key) { me[key] && (me[key] = options[key]) }); // create the net server this.socket = net.createServer(function createServer (socket) { socket.on('error', function socketError () { me.responder.call(me, socket); }); me.responder.call(me, socket); }); // Listen for errors as the port might be blocked because we do not have root priv. this.socket.on('error', function serverError (err) { // Special and common case error handling if (err.errno == 13) { me.log && me.log( 'Unable to listen to port `' + me.port + '` as your Node.js instance does not have root privileges. ' + ( me.server ? 'The Flash Policy File requests will only be served inline over the supplied HTTP server. Inline serving is slower than a dedicated server instance.' : 'No fallback server supplied, we will be unable to answer Flash Policy File requests.' ) ); me.emit('connect_failed', err); me.socket.removeAllListeners(); delete me.socket; } else { me.log && me.log('FlashPolicyFileServer received an error event:\n' + (err.message ? err.message : err)); } }); this.socket.on('timeout', function serverTimeout () {}); this.socket.on('close', function serverClosed (err) { err && me.log && me.log('Server closing due to an error: \n' + (err.message ? err.message : err)); if (me.server) { // Remove the inline policy listener if we close down // but only when the server was `online` (see listen prototype) if (me.server['@'] && me.server.online) { me.server.removeListener('connection', me.server['@']); } // not online anymore delete me.server.online; } }); // Compile the initial `buffer` this.compile(); } /** * Start listening for requests * * @param {Number} port The port number it should be listening to. * @param {Server} server A HTTP server instance, this will be used to listen for inline requests * @param {Function} cb The callback needs to be called once server is ready * @api public */ Server.prototype.listen = function listen (port, server, cb){ var me = this , args = slice.call(arguments, 0) , callback; // assign the correct vars, for flexible arguments args.forEach(function args (arg){ var type = typeof arg; if (type === 'number') me.port = arg; if (type === 'function') callback = arg; if (type === 'object') me.server = arg; }); if (this.server) { // no one in their right mind would ever create a `@` prototype, so Im just gonna store // my function on the server, so I can remove it later again once the server(s) closes this.server['@'] = function connection (socket) { socket.once('data', function requestData (data) { // if it's a Flash policy request, and we can write to the if ( data && data[0] === 60 && data.toString() === '\0' && socket && (socket.readyState === 'open' || socket.readyState === 'writeOnly') ){ // send the buffer try { socket.end(me.buffer); } catch (e) {} } }); }; // attach it this.server.on('connection', this.server['@']); } // We add a callback method, so we can set a flag for when the server is `enabled` or `online`. // this flag is needed because if a error occurs and the we cannot boot up the server the // fallback functionality should not be removed during the `close` event this.port >= 0 && this.socket.listen(this.port, function serverListening () { me.socket.online = true; if (callback) { callback.call(me); callback = undefined; } }); return this; }; /** * Responds to socket connects and writes the compile policy file. * * @param {net.Socket} socket The socket that needs to receive the message * @api private */ Server.prototype.responder = function responder (socket){ if (socket && socket.readyState == 'open' && socket.end) { try { socket.end(this.buffer); } catch (e) {} } }; /** * Compiles the supplied origins to a Flash Policy File format and stores it in a Node.js Buffer * this way it can be send over the wire without any performance loss. * * @api private */ Server.prototype.compile = function compile (){ var xml = [ '' , '' , '' ]; // add the allow access element this.origins.forEach(function origin (origin){ var parts = origin.split(':'); xml.push(''); }); xml.push(''); // store the result in a buffer so we don't have to re-generate it all the time this.buffer = new Buffer(xml.join(''), 'utf8'); return this; }; /** * Adds a new origin to the Flash Policy File. * * @param {Arguments} The origins that need to be added. * @api public */ Server.prototype.add = function add(){ var args = slice.call(arguments, 0) , i = args.length; // flag duplicates while (i--) { if (this.origins.indexOf(args[i]) >= 0){ args[i] = null; } } // Add all the arguments to the array // but first we want to remove all `falsy` values from the args Array.prototype.push.apply( this.origins , args.filter(function filter (value) { return !!value; }) ); this.compile(); return this; }; /** * Removes a origin from the Flash Policy File. * * @param {String} origin The origin that needs to be removed from the server * @api public */ Server.prototype.remove = function remove (origin){ var position = this.origins.indexOf(origin); // only remove and recompile if we have a match if (position > 0) { this.origins.splice(position,1); this.compile(); } return this; }; /** * Closes and cleans up the server * * @api public */ Server.prototype.close = function close () { this.socket.removeAllListeners(); this.socket.close(); return this; }; /** * Proxy the event listener requests to the created Net server */ Object.keys(process.EventEmitter.prototype).forEach(function proxy (key){ Server.prototype[key] = Server.prototype[key] || function () { if (this.socket) { this.socket[key].apply(this.socket, arguments); } return this; }; }); /** * Creates a new server instance. * * @param {Object} options A options object to override the default config * @param {Array} origins The origins that should be allowed by the server * @api public */ exports.createServer = function createServer(options, origins){ origins = Array.isArray(origins) ? origins : (Array.isArray(options) ? options : false); options = !Array.isArray(options) && options ? options : {}; return new Server(options, origins); }; /** * Provide a hook to the original server, so it can be extended if needed. */ exports.Server = Server; /** * Module version */ exports.version = '0.0.4';