<commit_before>"use strict";

var EventEmitter = require('events').EventEmitter
  , inherits = require('util').inherits
  , CServer = require('mongodb-core').Server
  , Cursor = require('./cursor')
  , AggregationCursor = require('./aggregation_cursor')
  , CommandCursor = require('./command_cursor')
  , f = require('util').format
  , ServerCapabilities = require('./topology_base').ServerCapabilities
  , Store = require('./topology_base').Store
  , Define = require('./metadata')
  , MongoError = require('mongodb-core').MongoError
  , shallowClone = require('./utils').shallowClone
  , MAX_JS_INT = require('./utils').MAX_JS_INT;

/**
 * @fileOverview The **Server** class is a class that represents a single server topology and is
 * used to construct connections.
 *
 * **Server Should not be used, use MongoClient.connect**
 * @example
 * var Db = require('mongodb').Db,
 *   Server = require('mongodb').Server,
 *   test = require('assert');
 * // Connect using single Server
 * var db = new Db('test', new Server('localhost', 27017););
 * db.open(function(err, db) {
 *   // Get an additional db
 *   db.close();
 * });
 */

/**
 * Creates a new Server instance
 * @class
 * @deprecated
 * @param {string} host The host for the server, can be either an IP4, IP6 or domain socket style host.
 * @param {number} [port] The server port if IP4.
 * @param {object} [options=null] Optional settings.
 * @param {number} [options.poolSize=5] Number of connections in the connection pool for each server instance, set to 5 as default for legacy reasons.
 * @param {boolean} [options.ssl=false] Use ssl connection (needs to have a mongod server with ssl support)
 * @param {object} [options.sslValidate=true] Validate mongod server certificate against ca (needs to have a mongod server with ssl support, 2.4 or higher)
 * @param {boolean|function} [options.checkServerIdentity=true] Ensure we check server identify during SSL, set to false to disable checking. Only works for Node 0.12.x or higher. You can pass in a boolean or your own checkServerIdentity override function.
 * @param {array} [options.sslCA=null] Array of valid certificates either as Buffers or Strings (needs to have a mongod server with ssl support, 2.4 or higher)
 * @param {(Buffer|string)} [options.sslCert=null] String or buffer containing the certificate we wish to present (needs to have a mongod server with ssl support, 2.4 or higher)
 * @param {(Buffer|string)} [options.sslKey=null] String or buffer containing the certificate private key we wish to present (needs to have a mongod server with ssl support, 2.4 or higher)
 * @param {(Buffer|string)} [options.sslPass=null] String or buffer containing the certificate password (needs to have a mongod server with ssl support, 2.4 or higher)
 * @param {object} [options.socketOptions=null] Socket options
 * @param {boolean} [options.socketOptions.autoReconnect=false] Reconnect on error.
 * @param {boolean} [options.socketOptions.noDelay=true] TCP Socket NoDelay option.
 * @param {number} [options.socketOptions.keepAlive=0] TCP KeepAlive on the socket with a X ms delay before start.
 * @param {number} [options.socketOptions.connectTimeoutMS=0] TCP Connection timeout setting
 * @param {number} [options.socketOptions.socketTimeoutMS=0] TCP Socket timeout setting
 * @param {number} [options.reconnectTries=30] Server attempt to reconnect #times
 * @param {number} [options.reconnectInterval=1000] Server will wait # milliseconds between retries
 * @fires Server#connect
 * @fires Server#close
 * @fires Server#error
 * @fires Server#timeout
 * @fires Server#parseError
 * @fires Server#reconnect
 * @return {Server} a Server instance.
 */
var Server = function(host, port, options) {
  options = options || {};
  if(!(this instanceof Server)) return new Server(host, port, options);
  EventEmitter.call(this);
  var self = this;

  // Store option defaults
  var storeOptions = {
      force: false
    , bufferMaxEntries: -1
  }

  // If we have "unlimited" set to max Number
  if(storeOptions.bufferMaxEntries == -1) {
    storeOptions.bufferMaxEntries = MAX_JS_INT;
  }

  // Shared global store
  var store = options.store || new Store(self, storeOptions);

  // Detect if we have a socket connection
  if(host.indexOf('\/') != -1) {
    if(port != null && typeof port == 'object') {
      options = port;
      port = null;
    }
  } else if(port == null) {
    throw MongoError.create({message: 'port must be specified', driver:true});
  }

  // Clone options
  var clonedOptions = shallowClone(options);
  clonedOptions.host = host;
  clonedOptions.port = port;

  // Reconnect
  var reconnect = typeof options.auto_reconnect == 'boolean' ? options.auto_reconnect : true;
  reconnect = typeof options.autoReconnect == 'boolean' ? options.autoReconnect : reconnect;
  var emitError = typeof options.emitError == 'boolean' ? options.emitError : true;
  var poolSize = typeof options.poolSize == 'number' ? options.poolSize : 5;

  // Socket options passed down
  if(options.socketOptions) {
    if(options.socketOptions.connectTimeoutMS) {
      this.connectTimeoutMS = options.socketOptions.connectTimeoutMS;
      clonedOptions.connectionTimeout = options.socketOptions.connectTimeoutMS;
    }

    if(options.socketOptions.socketTimeoutMS) {
      clonedOptions.socketTimeout = options.socketOptions.socketTimeoutMS;
    }

    if(typeof options.socketOptions.keepAlive == 'number') {
      clonedOptions.keepAliveInitialDelay = options.socketOptions.keepAlive;
      clonedOptions.keepAlive = true;
    }

    if(typeof options.socketOptions.noDelay == 'boolean') {
      clonedOptions.noDelay = options.socketOptions.noDelay;
    }
  }

  // Add the cursor factory function
  clonedOptions.cursorFactory = Cursor;
  clonedOptions.reconnect = reconnect;
  clonedOptions.emitError = emitError;
  clonedOptions.size = poolSize;

  // Translate the options
  if(clonedOptions.sslCA) clonedOptions.ca = clonedOptions.sslCA;
  if(typeof clonedOptions.sslValidate == 'boolean') clonedOptions.rejectUnauthorized = clonedOptions.sslValidate;
  if(clonedOptions.sslKey) clonedOptions.key = clonedOptions.sslKey;
  if(clonedOptions.sslCert) clonedOptions.cert = clonedOptions.sslCert;
  if(clonedOptions.sslPass) clonedOptions.passphrase = clonedOptions.sslPass;

  // Add the non connection store
  clonedOptions.disconnectHandler = store;

  // Create an instance of a server instance from mongodb-core
  var server = new CServer(clonedOptions);
  // Server capabilities
  var sCapabilities = null;

  // Define the internal properties
  this.s = {
    // Create an instance of a server instance from mongodb-core
      server: server
    // Server capabilities
    , sCapabilities: null
    // Cloned options
    , clonedOptions: clonedOptions
    // Reconnect
    , reconnect: reconnect
    // Emit error
    , emitError: emitError
    // Pool size
    , poolSize: poolSize
    // Store Options
    , storeOptions: storeOptions
    // Store
    , store: store
    // Host
    , host: host
    // Port
    , port: port
    // Options
    , options: options
  }

  // BSON property
  Object.defineProperty(this, 'bson', {
    enumerable: true, get: function() {
      return self.s.server.bson;
    }
  });

  // Last ismaster
  Object.defineProperty(this, 'isMasterDoc', {
    enumerable:true, get: function() {
      return self.s.server.lastIsMaster();
    }
  });

  // Last ismaster
  Object.defineProperty(this, 'poolSize', {
    enumerable:true, get: function() { return self.s.server.connections().length; }
  });

  Object.defineProperty(this, 'autoReconnect', {
    enumerable:true, get: function() { return self.s.reconnect; }
  });

  Object.defineProperty(this, 'host', {
    enumerable:true, get: function() { return self.s.host; }
  });

  Object.defineProperty(this, 'port', {
    enumerable:true, get: function() { return self.s.port; }
  });
}

inherits(Server, EventEmitter);

var define = Server.define = new Define('Server', Server, false);

Server.prototype.parserType = function() {
  return this.s.server.parserType();
}

define.classMethod('parserType', {callback: false, promise:false, returns: [String]});

// Connect
Server.prototype.connect = function(db, _options, callback) {
  var self = this;
  if('function' === typeof _options) callback = _options, _options = {};
  if(_options == null) _options = {};
  if(!('function' === typeof callback)) callback = null;
  self.s.options = _options;

  // Update bufferMaxEntries
  self.s.storeOptions.bufferMaxEntries = db.bufferMaxEntries;

  // Error handler
  var connectErrorHandler = function(event) {
    return function(err) {
      // Remove all event handlers
      var events = ['timeout', 'error', 'close'];
      events.forEach(function(e) {
        self.s.server.removeListener(e, connectHandlers[e]);
      });

      self.s.server.removeListener('connect', connectErrorHandler);

      // Try to callback
      try {
        callback(err);
      } catch(err) {
        process.nextTick(function() { throw err; })
      }
    }
  }

  // Actual handler
  var errorHandler = function(event) {
    return function(err) {
      if(event != 'error') {
        self.emit(event, err);
      }
    }
  }

  // Error handler
  var reconnectHandler = function(err) {
    self.emit('reconnect', self);
    self.s.store.execute();
  }

  // Destroy called on topology, perform cleanup
  var destroyHandler = function() {
    self.s.store.flush();
  }

  // Connect handler
  var connectHandler = function() {
    // Clear out all the current handlers left over
    ["timeout", "error", "close"].forEach(function(e) {
      self.s.server.removeAllListeners(e);
    });

    // Set up listeners
    self.s.server.once('timeout', errorHandler('timeout'));
    self.s.server.once('error', errorHandler('error'));
    self.s.server.on('close', errorHandler('close'));
    // Only called on destroy
    self.s.server.once('destroy', destroyHandler);

    // Emit open event
    self.emit('open', null, self);

    // Return correctly
    try {
      callback(null, self);
    } catch(err) {
      console.log(err.stack)
      process.nextTick(function() { throw err; })
    }
  }

  // Set up listeners
  var connectHandlers = {
    timeout: connectErrorHandler('timeout'),
    error: connectErrorHandler('error'),
    close: connectErrorHandler('close')
  };

  // Add the event handlers
  self.s.server.once('timeout', connectHandlers.timeout);
  self.s.server.once('error', connectHandlers.error);
  self.s.server.once('close', connectHandlers.close);
  self.s.server.once('connect', connectHandler);
  // Reconnect server
  self.s.server.on('reconnect', reconnectHandler);

  // Start connection
  self.s.server.connect(_options);
}

// Server capabilities
Server.prototype.capabilities = function() {
  if(this.s.sCapabilities) return this.s.sCapabilities;
  if(this.s.server.lastIsMaster() == null) return null;
  this.s.sCapabilities = new ServerCapabilities(this.s.server.lastIsMaster());
  return this.s.sCapabilities;
}

define.classMethod('capabilities', {callback: false, promise:false, returns: [ServerCapabilities]});

// Command
Server.prototype.command = function(ns, cmd, options, callback) {
  this.s.server.command(ns, cmd, options, callback);
}

define.classMethod('command', {callback: true, promise:false});

// Insert
Server.prototype.insert = function(ns, ops, options, callback) {
  this.s.server.insert(ns, ops, options, callback);
}

define.classMethod('insert', {callback: true, promise:false});

// Update
Server.prototype.update = function(ns, ops, options, callback) {
  this.s.server.update(ns, ops, options, callback);
}

define.classMethod('update', {callback: true, promise:false});

// Remove
Server.prototype.remove = function(ns, ops, options, callback) {
  this.s.server.remove(ns, ops, options, callback);
}

define.classMethod('remove', {callback: true, promise:false});

// IsConnected
Server.prototype.isConnected = function() {
  return this.s.server.isConnected();
}

Server.prototype.isDestroyed = function() {
  return this.s.server.isDestroyed();
}

define.classMethod('isConnected', {callback: false, promise:false, returns: [Boolean]});

// Insert
Server.prototype.cursor = function(ns, cmd, options) {
  options.disconnectHandler = this.s.store;
  return this.s.server.cursor(ns, cmd, options);
}

define.classMethod('cursor', {callback: false, promise:false, returns: [Cursor, AggregationCursor, CommandCursor]});

Server.prototype.setBSONParserType = function(type) {
  return this.s.server.setBSONParserType(type);
}

Server.prototype.lastIsMaster = function() {
  return this.s.server.lastIsMaster();
}

Server.prototype.close = function(forceClosed) {
  this.s.server.destroy();
  // We need to wash out all stored processes
  if(forceClosed == true) {
    this.s.storeOptions.force = forceClosed;
    this.s.store.flush();
  }
}

define.classMethod('close', {callback: false, promise:false});

Server.prototype.auth = function() {
  var args = Array.prototype.slice.call(arguments, 0);
  this.s.server.auth.apply(this.s.server, args);
}

define.classMethod('auth', {callback: true, promise:false});

/**
 * All raw connections
 * @method
 * @return {array}
 */
Server.prototype.connections = function() {
  return this.s.server.connections();
}

define.classMethod('connections', {callback: false, promise:false, returns:[Array]});

/**
 * Server connect event
 *
 * @event Server#connect
 * @type {object}
 */

/**
 * Server close event
 *
 * @event Server#close
 * @type {object}
 */

/**
 * Server reconnect event
 *
 * @event Server#reconnect
 * @type {object}
 */

/**
 * Server error event
 *
 * @event Server#error
 * @type {MongoError}
 */

/**
 * Server timeout event
 *
 * @event Server#timeout
 * @type {object}
 */

/**
 * Server parseError event
 *
 * @event Server#parseError
 * @type {object}
 */

module.exports = Server;
<commit_msg>Forward unref to mongodb-core server class<commit_after>"use strict";

var EventEmitter = require('events').EventEmitter
  , inherits = require('util').inherits
  , CServer = require('mongodb-core').Server
  , Cursor = require('./cursor')
  , AggregationCursor = require('./aggregation_cursor')
  , CommandCursor = require('./command_cursor')
  , f = require('util').format
  , ServerCapabilities = require('./topology_base').ServerCapabilities
  , Store = require('./topology_base').Store
  , Define = require('./metadata')
  , MongoError = require('mongodb-core').MongoError
  , shallowClone = require('./utils').shallowClone
  , MAX_JS_INT = require('./utils').MAX_JS_INT;

/**
 * @fileOverview The **Server** class is a class that represents a single server topology and is
 * used to construct connections.
 *
 * **Server Should not be used, use MongoClient.connect**
 * @example
 * var Db = require('mongodb').Db,
 *   Server = require('mongodb').Server,
 *   test = require('assert');
 * // Connect using single Server
 * var db = new Db('test', new Server('localhost', 27017););
 * db.open(function(err, db) {
 *   // Get an additional db
 *   db.close();
 * });
 */

/**
 * Creates a new Server instance
 * @class
 * @deprecated
 * @param {string} host The host for the server, can be either an IP4, IP6 or domain socket style host.
 * @param {number} [port] The server port if IP4.
 * @param {object} [options=null] Optional settings.
 * @param {number} [options.poolSize=5] Number of connections in the connection pool for each server instance, set to 5 as default for legacy reasons.
 * @param {boolean} [options.ssl=false] Use ssl connection (needs to have a mongod server with ssl support)
 * @param {object} [options.sslValidate=true] Validate mongod server certificate against ca (needs to have a mongod server with ssl support, 2.4 or higher)
 * @param {boolean|function} [options.checkServerIdentity=true] Ensure we check server identify during SSL, set to false to disable checking. Only works for Node 0.12.x or higher. You can pass in a boolean or your own checkServerIdentity override function.
 * @param {array} [options.sslCA=null] Array of valid certificates either as Buffers or Strings (needs to have a mongod server with ssl support, 2.4 or higher)
 * @param {(Buffer|string)} [options.sslCert=null] String or buffer containing the certificate we wish to present (needs to have a mongod server with ssl support, 2.4 or higher)
 * @param {(Buffer|string)} [options.sslKey=null] String or buffer containing the certificate private key we wish to present (needs to have a mongod server with ssl support, 2.4 or higher)
 * @param {(Buffer|string)} [options.sslPass=null] String or buffer containing the certificate password (needs to have a mongod server with ssl support, 2.4 or higher)
 * @param {object} [options.socketOptions=null] Socket options
 * @param {boolean} [options.socketOptions.autoReconnect=false] Reconnect on error.
 * @param {boolean} [options.socketOptions.noDelay=true] TCP Socket NoDelay option.
 * @param {number} [options.socketOptions.keepAlive=0] TCP KeepAlive on the socket with a X ms delay before start.
 * @param {number} [options.socketOptions.connectTimeoutMS=0] TCP Connection timeout setting
 * @param {number} [options.socketOptions.socketTimeoutMS=0] TCP Socket timeout setting
 * @param {number} [options.reconnectTries=30] Server attempt to reconnect #times
 * @param {number} [options.reconnectInterval=1000] Server will wait # milliseconds between retries
 * @fires Server#connect
 * @fires Server#close
 * @fires Server#error
 * @fires Server#timeout
 * @fires Server#parseError
 * @fires Server#reconnect
 * @return {Server} a Server instance.
 */
var Server = function(host, port, options) {
  options = options || {};
  if(!(this instanceof Server)) return new Server(host, port, options);
  EventEmitter.call(this);
  var self = this;

  // Store option defaults
  var storeOptions = {
      force: false
    , bufferMaxEntries: -1
  }

  // If we have "unlimited" set to max Number
  if(storeOptions.bufferMaxEntries == -1) {
    storeOptions.bufferMaxEntries = MAX_JS_INT;
  }

  // Shared global store
  var store = options.store || new Store(self, storeOptions);

  // Detect if we have a socket connection
  if(host.indexOf('\/') != -1) {
    if(port != null && typeof port == 'object') {
      options = port;
      port = null;
    }
  } else if(port == null) {
    throw MongoError.create({message: 'port must be specified', driver:true});
  }

  // Clone options
  var clonedOptions = shallowClone(options);
  clonedOptions.host = host;
  clonedOptions.port = port;

  // Reconnect
  var reconnect = typeof options.auto_reconnect == 'boolean' ? options.auto_reconnect : true;
  reconnect = typeof options.autoReconnect == 'boolean' ? options.autoReconnect : reconnect;
  var emitError = typeof options.emitError == 'boolean' ? options.emitError : true;
  var poolSize = typeof options.poolSize == 'number' ? options.poolSize : 5;

  // Socket options passed down
  if(options.socketOptions) {
    if(options.socketOptions.connectTimeoutMS) {
      this.connectTimeoutMS = options.socketOptions.connectTimeoutMS;
      clonedOptions.connectionTimeout = options.socketOptions.connectTimeoutMS;
    }

    if(options.socketOptions.socketTimeoutMS) {
      clonedOptions.socketTimeout = options.socketOptions.socketTimeoutMS;
    }

    if(typeof options.socketOptions.keepAlive == 'number') {
      clonedOptions.keepAliveInitialDelay = options.socketOptions.keepAlive;
      clonedOptions.keepAlive = true;
    }

    if(typeof options.socketOptions.noDelay == 'boolean') {
      clonedOptions.noDelay = options.socketOptions.noDelay;
    }
  }

  // Add the cursor factory function
  clonedOptions.cursorFactory = Cursor;
  clonedOptions.reconnect = reconnect;
  clonedOptions.emitError = emitError;
  clonedOptions.size = poolSize;

  // Translate the options
  if(clonedOptions.sslCA) clonedOptions.ca = clonedOptions.sslCA;
  if(typeof clonedOptions.sslValidate == 'boolean') clonedOptions.rejectUnauthorized = clonedOptions.sslValidate;
  if(clonedOptions.sslKey) clonedOptions.key = clonedOptions.sslKey;
  if(clonedOptions.sslCert) clonedOptions.cert = clonedOptions.sslCert;
  if(clonedOptions.sslPass) clonedOptions.passphrase = clonedOptions.sslPass;

  // Add the non connection store
  clonedOptions.disconnectHandler = store;

  // Create an instance of a server instance from mongodb-core
  var server = new CServer(clonedOptions);
  // Server capabilities
  var sCapabilities = null;

  // Define the internal properties
  this.s = {
    // Create an instance of a server instance from mongodb-core
      server: server
    // Server capabilities
    , sCapabilities: null
    // Cloned options
    , clonedOptions: clonedOptions
    // Reconnect
    , reconnect: reconnect
    // Emit error
    , emitError: emitError
    // Pool size
    , poolSize: poolSize
    // Store Options
    , storeOptions: storeOptions
    // Store
    , store: store
    // Host
    , host: host
    // Port
    , port: port
    // Options
    , options: options
  }

  // BSON property
  Object.defineProperty(this, 'bson', {
    enumerable: true, get: function() {
      return self.s.server.bson;
    }
  });

  // Last ismaster
  Object.defineProperty(this, 'isMasterDoc', {
    enumerable:true, get: function() {
      return self.s.server.lastIsMaster();
    }
  });

  // Last ismaster
  Object.defineProperty(this, 'poolSize', {
    enumerable:true, get: function() { return self.s.server.connections().length; }
  });

  Object.defineProperty(this, 'autoReconnect', {
    enumerable:true, get: function() { return self.s.reconnect; }
  });

  Object.defineProperty(this, 'host', {
    enumerable:true, get: function() { return self.s.host; }
  });

  Object.defineProperty(this, 'port', {
    enumerable:true, get: function() { return self.s.port; }
  });
}

inherits(Server, EventEmitter);

var define = Server.define = new Define('Server', Server, false);

Server.prototype.parserType = function() {
  return this.s.server.parserType();
}

define.classMethod('parserType', {callback: false, promise:false, returns: [String]});

// Connect
Server.prototype.connect = function(db, _options, callback) {
  var self = this;
  if('function' === typeof _options) callback = _options, _options = {};
  if(_options == null) _options = {};
  if(!('function' === typeof callback)) callback = null;
  self.s.options = _options;

  // Update bufferMaxEntries
  self.s.storeOptions.bufferMaxEntries = db.bufferMaxEntries;

  // Error handler
  var connectErrorHandler = function(event) {
    return function(err) {
      // Remove all event handlers
      var events = ['timeout', 'error', 'close'];
      events.forEach(function(e) {
        self.s.server.removeListener(e, connectHandlers[e]);
      });

      self.s.server.removeListener('connect', connectErrorHandler);

      // Try to callback
      try {
        callback(err);
      } catch(err) {
        process.nextTick(function() { throw err; })
      }
    }
  }

  // Actual handler
  var errorHandler = function(event) {
    return function(err) {
      if(event != 'error') {
        self.emit(event, err);
      }
    }
  }

  // Error handler
  var reconnectHandler = function(err) {
    self.emit('reconnect', self);
    self.s.store.execute();
  }

  // Destroy called on topology, perform cleanup
  var destroyHandler = function() {
    self.s.store.flush();
  }

  // Connect handler
  var connectHandler = function() {
    // Clear out all the current handlers left over
    ["timeout", "error", "close"].forEach(function(e) {
      self.s.server.removeAllListeners(e);
    });

    // Set up listeners
    self.s.server.once('timeout', errorHandler('timeout'));
    self.s.server.once('error', errorHandler('error'));
    self.s.server.on('close', errorHandler('close'));
    // Only called on destroy
    self.s.server.once('destroy', destroyHandler);

    // Emit open event
    self.emit('open', null, self);

    // Return correctly
    try {
      callback(null, self);
    } catch(err) {
      console.log(err.stack)
      process.nextTick(function() { throw err; })
    }
  }

  // Set up listeners
  var connectHandlers = {
    timeout: connectErrorHandler('timeout'),
    error: connectErrorHandler('error'),
    close: connectErrorHandler('close')
  };

  // Add the event handlers
  self.s.server.once('timeout', connectHandlers.timeout);
  self.s.server.once('error', connectHandlers.error);
  self.s.server.once('close', connectHandlers.close);
  self.s.server.once('connect', connectHandler);
  // Reconnect server
  self.s.server.on('reconnect', reconnectHandler);

  // Start connection
  self.s.server.connect(_options);
}

// Server capabilities
Server.prototype.capabilities = function() {
  if(this.s.sCapabilities) return this.s.sCapabilities;
  if(this.s.server.lastIsMaster() == null) return null;
  this.s.sCapabilities = new ServerCapabilities(this.s.server.lastIsMaster());
  return this.s.sCapabilities;
}

define.classMethod('capabilities', {callback: false, promise:false, returns: [ServerCapabilities]});

// Command
Server.prototype.command = function(ns, cmd, options, callback) {
  this.s.server.command(ns, cmd, options, callback);
}

define.classMethod('command', {callback: true, promise:false});

// Insert
Server.prototype.insert = function(ns, ops, options, callback) {
  this.s.server.insert(ns, ops, options, callback);
}

define.classMethod('insert', {callback: true, promise:false});

// Update
Server.prototype.update = function(ns, ops, options, callback) {
  this.s.server.update(ns, ops, options, callback);
}

define.classMethod('update', {callback: true, promise:false});

// Remove
Server.prototype.remove = function(ns, ops, options, callback) {
  this.s.server.remove(ns, ops, options, callback);
}

define.classMethod('remove', {callback: true, promise:false});

// IsConnected
Server.prototype.isConnected = function() {
  return this.s.server.isConnected();
}

Server.prototype.isDestroyed = function() {
  return this.s.server.isDestroyed();
}

define.classMethod('isConnected', {callback: false, promise:false, returns: [Boolean]});

// Insert
Server.prototype.cursor = function(ns, cmd, options) {
  options.disconnectHandler = this.s.store;
  return this.s.server.cursor(ns, cmd, options);
}

define.classMethod('cursor', {callback: false, promise:false, returns: [Cursor, AggregationCursor, CommandCursor]});

Server.prototype.setBSONParserType = function(type) {
  return this.s.server.setBSONParserType(type);
}

Server.prototype.lastIsMaster = function() {
  return this.s.server.lastIsMaster();
}

/**
 * Unref all sockets
 * @method
 */
Server.prototype.unref = function() {
  this.s.server.unref();
}

Server.prototype.close = function(forceClosed) {
  this.s.server.destroy();
  // We need to wash out all stored processes
  if(forceClosed == true) {
    this.s.storeOptions.force = forceClosed;
    this.s.store.flush();
  }
}

define.classMethod('close', {callback: false, promise:false});

Server.prototype.auth = function() {
  var args = Array.prototype.slice.call(arguments, 0);
  this.s.server.auth.apply(this.s.server, args);
}

define.classMethod('auth', {callback: true, promise:false});

/**
 * All raw connections
 * @method
 * @return {array}
 */
Server.prototype.connections = function() {
  return this.s.server.connections();
}

define.classMethod('connections', {callback: false, promise:false, returns:[Array]});

/**
 * Server connect event
 *
 * @event Server#connect
 * @type {object}
 */

/**
 * Server close event
 *
 * @event Server#close
 * @type {object}
 */

/**
 * Server reconnect event
 *
 * @event Server#reconnect
 * @type {object}
 */

/**
 * Server error event
 *
 * @event Server#error
 * @type {MongoError}
 */

/**
 * Server timeout event
 *
 * @event Server#timeout
 * @type {object}
 */

/**
 * Server parseError event
 *
 * @event Server#parseError
 * @type {object}
 */

module.exports = Server;
<|endoftext|>


<commit_before>//jshint esversion: 6
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
import CallLog from './call_log';

const streams = {};
const Services = {
    initializeCallSession(_id, meteorUser) {
        CallLog.update({
            $or: [{
                status: {
                    $ne: "FINISHED"
                },
                caller: meteorUser._id
            }, {
                status: {
                    $ne: "FINISHED"
                },
                target: meteorUser._id
            }]

        }, {
            $set: {
                status: "FINISHED"
            }
        });
        const logId = CallLog.insert({
            status: "NEW",
            target: _id,
            caller: meteorUser._id,
            callerConnectionId: this.connection.id
        });
        streams[logId] = new Meteor.Streamer(logId);
        streams[logId].allowRead('all');
        streams[logId].allowWrite('all');
        return logId;
    }
    /**
     * Call allows you to call a remote user using their userId
     * @param _id {string}
     */
    call(_id) {
        check(_id, String);
        const meteorUser = Meteor.user();
        if (!meteorUser) {
            const err = new Meteor.Error(403, "USER_NOT_LOGGED_IN", {
                caller: meteorUser._id,
                target: _id
            });
            Meteor.VideoCallServices.onError(err);
            throw err;
        }
        if (Services.checkConnect(meteorUser._id, _id)) {
            const inCall = CallLog.findOne({
                status: "CONNECTED",
                target: _id
            });
            if (inCall) {
                const err = new Meteor.Error(500, "TARGET_IN_CALL", inCall);
                Meteor.VideoCallServices.onError(err, inCall, Meteor.userId());
                throw err;
            }
            else {
                return Services.initializeCallSession(_id, meteorUser);
            }
        }
        else {
            Services.connectionNotAllowed(_id, meteorUser);
        }

    },
    connectionNotAllowed(_id, meteorUser) {
        const err = new Meteor.Error(403, "CONNECTION_NOT_ALLOWED", {
            target: meteorUser._id,
            caller: _id
        });
        Meteor.VideoCallServices.onError(err, _id, meteorUser);
        throw err;
    }
    /**
     * Check if call connection should be permitted
     * @param _id {caller}
     * @param _id {target}
     * @returns boolean
     */
    checkConnect(caller, target) {
        return true;
    },
    /**
     * Answer current phone call
     */
    answer() {
        const user = Meteor.user();
        if (!user) {
            const err = new Meteor.Error(403, "USER_NOT_LOGGED_IN");
            Meteor.VideoCallServices.onError(err);
            throw err;
        }
        const session = CallLog.findOne({
            target: user._id,
            status: 'NEW'
        });
        if (!session) {
            const err = new Meteor.Error(500, 'SESSION_NOT_FOUND', {
                target: user._id
            });
            Meteor.VideoCallServices.onError(err, undefined, user);
            throw err;
        }

        else {
            CallLog.update({
                _id: session._id
            }, {
                $set: {
                    targetConnectionId: this.connection.id,
                    status: 'ACCEPTED'
                }
            });
        }
    },
    /**
     * End current phone call
     */
    end() {
        const _id = Meteor.userId();
        CallLog.find({
            $or: [{
                status: {
                    $ne: 'FINISHED'
                },
                target: _id
            }, {
                status: {
                    $ne: 'FINISHED'
                },
                caller: _id
            }]
        }).forEach(call =>
            CallLog.update({
                _id: call._id
            }, {
                $set: {
                    status: 'FINISHED'
                }
            }));
    },
    ackReject(id) {
        CallLog.update({
            _id: id,
            caller: Meteor.userId()
        }, {
            $set: {
                status: "FINISHED"
            }
        });
    },
    reject() {
        const user = Meteor.user();
        if (user) {
            CallLog.update({
                target: user._id,
                status: 'NEW'
            }, {
                $set: {
                    status: "REJECTED"
                }
            });
        }
        else {
            throw new Meteor.Error(403, "Could not find user");
        }
    }
};

export {
    Services
};
<commit_msg>remove comma<commit_after>//jshint esversion: 6
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
import CallLog from './call_log';

const streams = {};
const Services = {
    initializeCallSession(_id, meteorUser) {
        CallLog.update({
            $or: [{
                status: {
                    $ne: "FINISHED"
                },
                caller: meteorUser._id
            }, {
                status: {
                    $ne: "FINISHED"
                },
                target: meteorUser._id
            }]

        }, {
            $set: {
                status: "FINISHED"
            }
        });
        const logId = CallLog.insert({
            status: "NEW",
            target: _id,
            caller: meteorUser._id,
            callerConnectionId: this.connection.id
        });
        streams[logId] = new Meteor.Streamer(logId);
        streams[logId].allowRead('all');
        streams[logId].allowWrite('all');
        return logId;
    },
    /**
     * Call allows you to call a remote user using their userId
     * @param _id {string}
     */
    call(_id) {
        check(_id, String);
        const meteorUser = Meteor.user();
        if (!meteorUser) {
            const err = new Meteor.Error(403, "USER_NOT_LOGGED_IN", {
                caller: meteorUser._id,
                target: _id
            });
            Meteor.VideoCallServices.onError(err);
            throw err;
        }
        if (Services.checkConnect(meteorUser._id, _id)) {
            const inCall = CallLog.findOne({
                status: "CONNECTED",
                target: _id
            });
            if (inCall) {
                const err = new Meteor.Error(500, "TARGET_IN_CALL", inCall);
                Meteor.VideoCallServices.onError(err, inCall, Meteor.userId());
                throw err;
            }
            else {
                return Services.initializeCallSession(_id, meteorUser);
            }
        }
        else {
            Services.connectionNotAllowed(_id, meteorUser);
        }

    },
    connectionNotAllowed(_id, meteorUser) {
        const err = new Meteor.Error(403, "CONNECTION_NOT_ALLOWED", {
            target: meteorUser._id,
            caller: _id
        });
        Meteor.VideoCallServices.onError(err, _id, meteorUser);
        throw err;
    },
    /**
     * Check if call connection should be permitted
     * @param _id {caller}
     * @param _id {target}
     * @returns boolean
     */
    checkConnect(caller, target) {
        return true;
    },
    /**
     * Answer current phone call
     */
    answer() {
        const user = Meteor.user();
        if (!user) {
            const err = new Meteor.Error(403, "USER_NOT_LOGGED_IN");
            Meteor.VideoCallServices.onError(err);
            throw err;
        }
        const session = CallLog.findOne({
            target: user._id,
            status: 'NEW'
        });
        if (!session) {
            const err = new Meteor.Error(500, 'SESSION_NOT_FOUND', {
                target: user._id
            });
            Meteor.VideoCallServices.onError(err, undefined, user);
            throw err;
        }

        else {
            CallLog.update({
                _id: session._id
            }, {
                $set: {
                    targetConnectionId: this.connection.id,
                    status: 'ACCEPTED'
                }
            });
        }
    },
    /**
     * End current phone call
     */
    end() {
        const _id = Meteor.userId();
        CallLog.find({
            $or: [{
                status: {
                    $ne: 'FINISHED'
                },
                target: _id
            }, {
                status: {
                    $ne: 'FINISHED'
                },
                caller: _id
            }]
        }).forEach(call =>
            CallLog.update({
                _id: call._id
            }, {
                $set: {
                    status: 'FINISHED'
                }
            }));
    },
    ackReject(id) {
        CallLog.update({
            _id: id,
            caller: Meteor.userId()
        }, {
            $set: {
                status: "FINISHED"
            }
        });
    },
    reject() {
        const user = Meteor.user();
        if (user) {
            CallLog.update({
                target: user._id,
                status: 'NEW'
            }, {
                $set: {
                    status: "REJECTED"
                }
            });
        }
        else {
            throw new Meteor.Error(403, "Could not find user");
        }
    }
};

export {
    Services
};
<|endoftext|>


<commit_before>
/**
 * Module dependencies.
 */

var qs = require('querystring')
  , parse = require('url').parse
  , readFileSync = require('fs').readFileSync
  , crypto = require('crypto')
  , base64id = require('base64id')
  , transports = require('./transports')
  , EventEmitter = require('events').EventEmitter
  , Socket = require('./socket')
  , WebSocketServer = require('ws').Server
  , debug = require('debug')('engine');

/**
 * Module exports.
 */

module.exports = Server;

/**
 * Server constructor.
 *
 * @param {Object} options
 * @api public
 */

function Server(opts){
  this.clients = {};
  this.clientsCount = 0;

  opts = opts || {};
  this.pingTimeout = opts.pingTimeout || 60000;
  this.pingInterval = opts.pingInterval || 25000;
  this.upgradeTimeout = opts.upgradeTimeout || 10000;
  this.maxHttpBufferSize = opts.maxHttpBufferSize || 10E7;
  this.transports = opts.transports || Object.keys(transports);
  this.allowUpgrades = false !== opts.allowUpgrades;
  this.cookie = false !== opts.cookie ? (opts.cookie || 'io') : false;

  // initialize websocket server
  if (~this.transports.indexOf('websocket')) {
    this.ws = new WebSocketServer({ noServer: true, clientTracking: false });
  }
};

/**
 * Protocol errors mappings.
 */

Server.errors = {
  UNKNOWN_TRANSPORT: 0,
  UNKNOWN_SID: 1,
  BAD_HANDSHAKE_METHOD: 2,
  BAD_REQUEST: 3
};

Server.errorMessages = {
  0: 'Transport unknown',
  1: 'Session ID unknown',
  2: 'Bad handshake method',
  3: 'Bad request'
};

/**
 * Inherits from EventEmitter.
 */

Server.prototype.__proto__ = EventEmitter.prototype;

/**
 * Hash of open clients.
 *
 * @api public
 */

Server.prototype.clients;

/**
 * Returns a list of available transports for upgrade given a certain transport.
 *
 * @return {Array}
 * @api public
 */

Server.prototype.upgrades = function(transport){
  if (!this.allowUpgrades) return [];
  return transports[transport].upgradesTo || [];
};

/**
 * Verifies a request.
 *
 * @param {http.ServerRequest}
 * @return {Boolean} whether the request is valid
 * @api private
 */

Server.prototype.verify = function(req, upgrade){
  // transport check
  var transport = req._query.transport;
  if (!~this.transports.indexOf(transport)) {
    debug('unknown transport "%s"', transport);
    return Server.errors.UNKNOWN_TRANSPORT;
  }

  // sid check
  var sid = req._query.sid;
  if (sid) {
    if (!this.clients.hasOwnProperty(sid))
      return Server.errors.UNKNOWN_SID;
    if (!upgrade && this.clients[sid].transport.name !== transport) {
      debug('bad request: unexpected transport without upgrade');
      return Server.errors.BAD_REQUEST;
    }
  } else {
    // handshake is GET only
    return 'GET' == req.method ||
           Server.errors.BAD_HANDSHAKE_METHOD;
  }

  return true;
};

/**
 * Prepares a request by processing the query string.
 *
 * @api private
 */

Server.prototype.prepare = function(req){
  // try to leverage pre-existing `req._query` (e.g: from connect)
  if (!req._query) {
    req._query = ~req.url.indexOf('?') ? qs.parse(parse(req.url).query) : {};
  }
};

/**
 * Closes all clients.
 *
 * @api public
 */

Server.prototype.close = function(){
  debug('closing all open clients');
  for (var i in this.clients) {
    this.clients[i].close();
  }
  return this;
};

/**
 * Handles an Engine.IO HTTP request.
 *
 * @param {http.ServerRequest} request
 * @param {http.ServerResponse|http.OutgoingMessage} response
 * @api public
 */

Server.prototype.handleRequest = function(req, res){
  debug('handling "%s" http request "%s"', req.method, req.url);
  this.prepare(req);
  req.res = res;

  var code = this.verify(req, false);
  if (code !== true) {
    sendErrorMessage(res, code);
    return this;
  }

  if (req._query.sid) {
    debug('setting new request for existing client');
    this.clients[req._query.sid].transport.onRequest(req);
  } else {
    this.handshake(req._query.transport, req);
  }

  return this;
};

/**
 * Sends an Engine.IO Error Message
 *
 * @param {http.ServerResponse} response
 * @param {code} error code
 * @api private
 */

 function sendErrorMessage(res, code) {
    res.writeHead(400, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({
      code: code,
      message: Server.errorMessages[code]
    }));
 }

/**
 * Handshakes a new client.
 *
 * @param {String} transport name
 * @param {Object} request object
 * @api private
 */

Server.prototype.handshake = function(transport, req){
  var id = base64id.generateId();

  debug('handshaking client "%s"', id);

  var transportName = transport;
  try {
    var transport = new transports[transport](req);
    if ('polling' == transportName) {
      transport.maxHttpBufferSize = this.maxHttpBufferSize;
    }

    if (req._query && req._query.b64) {
      transport.supportsBinary = false;
    } else {
      transport.supportsBinary = true;
    }
  }
  catch (e) {
    sendErrorMessage(req.res, Server.errors.BAD_REQUEST);
    return;
  }
  var socket = new Socket(id, this, transport, req);
  var self = this;

  if (false !== this.cookie) {
    transport.on('headers', function(headers){
      headers['Set-Cookie'] = self.cookie + '=' + id;
    });
  }

  transport.onRequest(req);

  this.clients[id] = socket;
  this.clientsCount++;

  socket.once('close', function(){
    delete self.clients[id];
    self.clientsCount--;
  });

  this.emit('connection', socket);
};

/**
 * Handles an Engine.IO HTTP Upgrade.
 *
 * @api public
 */

Server.prototype.handleUpgrade = function(req, socket, head){
  this.prepare(req);

  if (this.verify(req, true) !== true) {
    socket.end();
    return;
  }

  // delegate to ws
  var self = this;
  this.ws.handleUpgrade(req, socket, head, function(conn){
    self.onWebSocket(req, conn);
  });
};

/**
 * Called upon a ws.io connection.
 *
 * @param {ws.Socket} websocket
 * @api private
 */

Server.prototype.onWebSocket = function(req, socket){
  if (!transports[req._query.transport].prototype.handlesUpgrades) {
    debug('transport doesnt handle upgraded requests');
    socket.close();
    return;
  }

  // get client id
  var id = req._query.sid;

  // keep a reference to the ws.Socket
  req.websocket = socket;

  if (id) {
    if (!this.clients[id]) {
      debug('upgrade attempt for closed client');
      socket.close();
    } else if (this.clients[id].upgraded) {
      debug('transport had already been upgraded');
      socket.close();
    } else {
      debug('upgrading existing transport');
      var transport = new transports[req._query.transport](req);
      if (req._query && req._query.b64) {
        transport.supportsBinary = false;
      } else {
        transport.supportsBinary = true;
      }
      this.clients[id].maybeUpgrade(transport);
    }
  } else {
    this.handshake(req._query.transport, req);
  }
};

/**
 * Handles a regular connection to watch for flash policy requests.
 *
 * @param {net.Stream} socket
 * @api private
 */

var policy = readFileSync(__dirname + '/transports/flashsocket.xml');

Server.prototype.handleSocket = function(socket){
  socket.on('data', function onData(data){
    // no need for buffering as node will discard subsequent packets
    // since they constitute a malformed HTTP request
    if (60 == data[0] && 23 == data.length) {
      var str = data.slice(0, 23).toString();
      if ('<policy-file-request/>\0' == str) {
        socket.end(policy);
      }
    }
    socket.removeListener('data', onData);
  });
};
<commit_msg>Added support for giving a function to engine.io to decide whether a request should be allowed or not when verifying. This is used by socket.io to check for allowed origins.<commit_after>
/**
 * Module dependencies.
 */

var qs = require('querystring')
  , parse = require('url').parse
  , readFileSync = require('fs').readFileSync
  , crypto = require('crypto')
  , base64id = require('base64id')
  , transports = require('./transports')
  , EventEmitter = require('events').EventEmitter
  , Socket = require('./socket')
  , WebSocketServer = require('ws').Server
  , debug = require('debug')('engine');

/**
 * Module exports.
 */

module.exports = Server;

/**
 * Server constructor.
 *
 * @param {Object} options
 * @api public
 */

function Server(opts){
  this.clients = {};
  this.clientsCount = 0;

  opts = opts || {};
  this.pingTimeout = opts.pingTimeout || 60000;
  this.pingInterval = opts.pingInterval || 25000;
  this.upgradeTimeout = opts.upgradeTimeout || 10000;
  this.maxHttpBufferSize = opts.maxHttpBufferSize || 10E7;
  this.transports = opts.transports || Object.keys(transports);
  this.allowUpgrades = false !== opts.allowUpgrades;
  this.allowRequest = opts.allowRequest;
  this.cookie = false !== opts.cookie ? (opts.cookie || 'io') : false;

  // initialize websocket server
  if (~this.transports.indexOf('websocket')) {
    this.ws = new WebSocketServer({ noServer: true, clientTracking: false });
  }
};

/**
 * Protocol errors mappings.
 */

Server.errors = {
  UNKNOWN_TRANSPORT: 0,
  UNKNOWN_SID: 1,
  BAD_HANDSHAKE_METHOD: 2,
  BAD_REQUEST: 3
};

Server.errorMessages = {
  0: 'Transport unknown',
  1: 'Session ID unknown',
  2: 'Bad handshake method',
  3: 'Bad request'
};

/**
 * Inherits from EventEmitter.
 */

Server.prototype.__proto__ = EventEmitter.prototype;

/**
 * Hash of open clients.
 *
 * @api public
 */

Server.prototype.clients;

/**
 * Returns a list of available transports for upgrade given a certain transport.
 *
 * @return {Array}
 * @api public
 */

Server.prototype.upgrades = function(transport){
  if (!this.allowUpgrades) return [];
  return transports[transport].upgradesTo || [];
};

/**
 * Verifies a request.
 *
 * @param {http.ServerRequest}
 * @return {Boolean} whether the request is valid
 * @api private
 */

Server.prototype.verify = function(req, upgrade){
  // transport check
  var transport = req._query.transport;
  if (!~this.transports.indexOf(transport)) {
    debug('unknown transport "%s"', transport);
    return Server.errors.UNKNOWN_TRANSPORT;
  }

  // sid check
  var sid = req._query.sid;
  if (sid) {
    if (!this.clients.hasOwnProperty(sid))
      return Server.errors.UNKNOWN_SID;
    if (!upgrade && this.clients[sid].transport.name !== transport) {
      debug('bad request: unexpected transport without upgrade');
      return Server.errors.BAD_REQUEST;
    }
  } else {
    // handshake is GET only
    if ('GET' != req.method) return Server.errors.BAD_HANDSHAKE_METHOD;
    return !this.allowRequest || this.allowRequest(req);
  }

  return true;
};

/**
 * Prepares a request by processing the query string.
 *
 * @api private
 */

Server.prototype.prepare = function(req){
  // try to leverage pre-existing `req._query` (e.g: from connect)
  if (!req._query) {
    req._query = ~req.url.indexOf('?') ? qs.parse(parse(req.url).query) : {};
  }
};

/**
 * Closes all clients.
 *
 * @api public
 */

Server.prototype.close = function(){
  debug('closing all open clients');
  for (var i in this.clients) {
    this.clients[i].close();
  }
  return this;
};

/**
 * Handles an Engine.IO HTTP request.
 *
 * @param {http.ServerRequest} request
 * @param {http.ServerResponse|http.OutgoingMessage} response
 * @api public
 */

Server.prototype.handleRequest = function(req, res){
  debug('handling "%s" http request "%s"', req.method, req.url);
  this.prepare(req);
  req.res = res;

  var code = this.verify(req, false);
  if (code !== true) {
    sendErrorMessage(res, code);
    return this;
  }

  if (req._query.sid) {
    debug('setting new request for existing client');
    this.clients[req._query.sid].transport.onRequest(req);
  } else {
    this.handshake(req._query.transport, req);
  }

  return this;
};

/**
 * Sends an Engine.IO Error Message
 *
 * @param {http.ServerResponse} response
 * @param {code} error code
 * @api private
 */

 function sendErrorMessage(res, code) {
    res.writeHead(400, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({
      code: code,
      message: Server.errorMessages[code]
    }));
 }

/**
 * Handshakes a new client.
 *
 * @param {String} transport name
 * @param {Object} request object
 * @api private
 */

Server.prototype.handshake = function(transport, req){
  var id = base64id.generateId();

  debug('handshaking client "%s"', id);

  var transportName = transport;
  try {
    var transport = new transports[transport](req);
    if ('polling' == transportName) {
      transport.maxHttpBufferSize = this.maxHttpBufferSize;
    }

    if (req._query && req._query.b64) {
      transport.supportsBinary = false;
    } else {
      transport.supportsBinary = true;
    }
  }
  catch (e) {
    sendErrorMessage(req.res, Server.errors.BAD_REQUEST);
    return;
  }
  var socket = new Socket(id, this, transport, req);
  var self = this;

  if (false !== this.cookie) {
    transport.on('headers', function(headers){
      headers['Set-Cookie'] = self.cookie + '=' + id;
    });
  }

  transport.onRequest(req);

  this.clients[id] = socket;
  this.clientsCount++;

  socket.once('close', function(){
    delete self.clients[id];
    self.clientsCount--;
  });

  this.emit('connection', socket);
};

/**
 * Handles an Engine.IO HTTP Upgrade.
 *
 * @api public
 */

Server.prototype.handleUpgrade = function(req, socket, head){
  this.prepare(req);

  if (this.verify(req, true) !== true) {
    socket.end();
    return;
  }

  // delegate to ws
  var self = this;
  this.ws.handleUpgrade(req, socket, head, function(conn){
    self.onWebSocket(req, conn);
  });
};

/**
 * Called upon a ws.io connection.
 *
 * @param {ws.Socket} websocket
 * @api private
 */

Server.prototype.onWebSocket = function(req, socket){
  if (!transports[req._query.transport].prototype.handlesUpgrades) {
    debug('transport doesnt handle upgraded requests');
    socket.close();
    return;
  }

  // get client id
  var id = req._query.sid;

  // keep a reference to the ws.Socket
  req.websocket = socket;

  if (id) {
    if (!this.clients[id]) {
      debug('upgrade attempt for closed client');
      socket.close();
    } else if (this.clients[id].upgraded) {
      debug('transport had already been upgraded');
      socket.close();
    } else {
      debug('upgrading existing transport');
      var transport = new transports[req._query.transport](req);
      if (req._query && req._query.b64) {
        transport.supportsBinary = false;
      } else {
        transport.supportsBinary = true;
      }
      this.clients[id].maybeUpgrade(transport);
    }
  } else {
    this.handshake(req._query.transport, req);
  }
};

/**
 * Handles a regular connection to watch for flash policy requests.
 *
 * @param {net.Stream} socket
 * @api private
 */

var policy = readFileSync(__dirname + '/transports/flashsocket.xml');

Server.prototype.handleSocket = function(socket){
  socket.on('data', function onData(data){
    // no need for buffering as node will discard subsequent packets
    // since they constitute a malformed HTTP request
    if (60 == data[0] && 23 == data.length) {
      var str = data.slice(0, 23).toString();
      if ('<policy-file-request/>\0' == str) {
        socket.end(policy);
      }
    }
    socket.removeListener('data', onData);
  });
};
<|endoftext|>


<commit_before>const fs = require("fs");

// https://stackoverflow.com/a/1584377
Array.prototype.unique = function() {
    var a = this.concat();
    for(var i=0; i<a.length; ++i) {
        for(var j=i+1; j<a.length; ++j) {
            if(a[i] === a[j])
                a.splice(j--, 1);
        }
    }

    return a;
};

module.exports = class Auth {
    constructor(config) {
        fs.readFile("./db/helper_Auth.json", (err, data) => {
            if (err) {
                this.db = {
                    auth: {},
                    _globalAdmins: config.globalAdmins
                };
            } else {
                this.db = JSON.parse(data);
                this.db._globalAdmins = this.db._globalAdmins.concat(config.globalAdmins).unique();
            }
        });
    }

    synchronize() {
        fs.writeFile(
            "./helper_Auth.json",
            JSON.stringify(this.db),
            err => {
                if (err) throw err;
            }
        );
    }

    isMod(userId, chatId) {
        if (this.isAdmin(userId, chatId)) {
            return true;
        }
        return this.getMods(chatId).includes(userId);
    }

    isAdmin(userId, chatId) {
        if (this.isGlobalAdmin(userId)) {
            return true;
        }
        return this.getAdmins(chatId).includes(userId);
    }

    isGlobalAdmin(userId) {
        return this.getGlobalAdmins().includes(userId);
    }

    addAdmin(userId, chatId) {
        if (!this.db.auth[chatId])
            this.db.auth[chatId] = {};

        if (!this.db.auth[chatId].admins)
            this.db.auth[chatId].admins = [];

        this.db.auth[chatId].admins.push(userId);
        this.synchronize();
    }

    removeAdmin(userId, chatId) {
        if (!this.db.auth[chatId])
            this.db.auth[chatId] = {};

        if (!this.db.auth[chatId].admins)
            this.db.auth[chatId].admins = [];

        this.db.auth[chatId].admins = this.db.auth[chatId].admins.filter(admin => admin !== userId);
        this.synchronize();
    }

    addMod(userId, chatId) {
        if (!this.db.auth[chatId])
            this.db.auth[chatId] = {};

        if (!this.db.auth[chatId].mods)
            this.db.auth[chatId].mods = [];

        this.db.auth[chatId].mods.push(userId);
        this.synchronize();
    }

    removeMod(userId, chatId) {
        if (!this.db.auth[chatId])
            this.db.auth[chatId] = {};

        if (!this.db.auth[chatId].mods)
            this.db.auth[chatId].mods = [];

        this.db.auth[chatId].mods = this.db.auth[chatId].mods.filter(mod => mod !== userId);
        this.synchronize();
    }

    addGlobalAdmin(userId) {
        if (!this.db._globalAdmins)
            this.db._globalAdmins = [];

        this.db._globalAdmins.push(userId);
        this.synchronize();
    }

    getMods(chatId) {
        if (this.db.auth[chatId] && this.db.auth[chatId].mods) {
            return this.db.auth[chatId].mods;
        }
        return [];
    }

    getAdmins(chatId) {
        if (this.db.auth[chatId] && this.db.auth[chatId].admins) {
            return this.db.auth[chatId].admins;
        }
        return [];
    }

    getGlobalAdmins() {
        if (this.db._globalAdmins) {
            return this.db._globalAdmins;
        }
        return [];
    }
};<commit_msg>Fix auth db path<commit_after>const fs = require("fs");

// https://stackoverflow.com/a/1584377
Array.prototype.unique = function() {
    var a = this.concat();
    for(var i=0; i<a.length; ++i) {
        for(var j=i+1; j<a.length; ++j) {
            if(a[i] === a[j])
                a.splice(j--, 1);
        }
    }

    return a;
};

module.exports = class Auth {
    constructor(config) {
        fs.readFile("./db/helper_Auth.json", (err, data) => {
            if (err) {
                this.db = {
                    auth: {},
                    _globalAdmins: config.globalAdmins
                };
            } else {
                this.db = JSON.parse(data);
                this.db._globalAdmins = this.db._globalAdmins.concat(config.globalAdmins).unique();
            }
        });
    }

    synchronize() {
        fs.writeFile(
            "./db/helper_Auth.json",
            JSON.stringify(this.db),
            err => {
                if (err) throw err;
            }
        );
    }

    isMod(userId, chatId) {
        if (this.isAdmin(userId, chatId)) {
            return true;
        }
        return this.getMods(chatId).includes(userId);
    }

    isAdmin(userId, chatId) {
        if (this.isGlobalAdmin(userId)) {
            return true;
        }
        return this.getAdmins(chatId).includes(userId);
    }

    isGlobalAdmin(userId) {
        return this.getGlobalAdmins().includes(userId);
    }

    addAdmin(userId, chatId) {
        if (!this.db.auth[chatId])
            this.db.auth[chatId] = {};

        if (!this.db.auth[chatId].admins)
            this.db.auth[chatId].admins = [];

        this.db.auth[chatId].admins.push(userId);
        this.synchronize();
    }

    removeAdmin(userId, chatId) {
        if (!this.db.auth[chatId])
            this.db.auth[chatId] = {};

        if (!this.db.auth[chatId].admins)
            this.db.auth[chatId].admins = [];

        this.db.auth[chatId].admins = this.db.auth[chatId].admins.filter(admin => admin !== userId);
        this.synchronize();
    }

    addMod(userId, chatId) {
        if (!this.db.auth[chatId])
            this.db.auth[chatId] = {};

        if (!this.db.auth[chatId].mods)
            this.db.auth[chatId].mods = [];

        this.db.auth[chatId].mods.push(userId);
        this.synchronize();
    }

    removeMod(userId, chatId) {
        if (!this.db.auth[chatId])
            this.db.auth[chatId] = {};

        if (!this.db.auth[chatId].mods)
            this.db.auth[chatId].mods = [];

        this.db.auth[chatId].mods = this.db.auth[chatId].mods.filter(mod => mod !== userId);
        this.synchronize();
    }

    addGlobalAdmin(userId) {
        if (!this.db._globalAdmins)
            this.db._globalAdmins = [];

        this.db._globalAdmins.push(userId);
        this.synchronize();
    }

    getMods(chatId) {
        if (this.db.auth[chatId] && this.db.auth[chatId].mods) {
            return this.db.auth[chatId].mods;
        }
        return [];
    }

    getAdmins(chatId) {
        if (this.db.auth[chatId] && this.db.auth[chatId].admins) {
            return this.db.auth[chatId].admins;
        }
        return [];
    }

    getGlobalAdmins() {
        if (this.db._globalAdmins) {
            return this.db._globalAdmins;
        }
        return [];
    }
};<|endoftext|>


<commit_before>'use strict';

const events = require('events');
const net    = require('net');
const util   = require('util');

const sansa  = require('./sansa');

const reservedEventNames = ['on'];

function createServer() {

  const sansaServer = new events.EventEmitter();
  const _outstandingCallbacksByClient = {};

  sansaServer.setMaxListeners(0);

  const respondToMessage = function(client, message, _err, body) {
    let err = _err;
    if (err && Object.prototype.toString.call(err) === '[object Error]') {
      err = err.toString();
    }
    client.write(sansa.build({
      kind: 'reply',
      id: message.id,
      err,
      body,
    }));
  };

  const tell = function(client, fn, body) {

    if (reservedEventNames.indexOf(fn) >= 0) {
      throw Error('tell(' + fn + ') is a reserved event name');
    }

    if (!client.isConnected) {
      throw Error('tell(' + fn + ') on client that has disconnected');
    }

    client.write(sansa.build({
      kind: 'tell',
      body,
      function: fn,
    }));
  };

  const ask = function(client, fn, body, callback) {
    if (typeof callback !== 'function') {
      throw Error('ask(' + fn + ') with no callback');
    }

    if (reservedEventNames.indexOf(fn) >= 0) {
      throw Error('ask(' + fn + ') is a reserved event name');
    }

    if (!client.isConnected) {
      throw Error('ask(' + fn + ') on client that has disconnected');
    }

    const id = client.sessionId + '-' + Math.random().toString().substr(2, 5);
    _outstandingCallbacksByClient[client.sessionId][id] = callback;

    client.write(sansa.build({
      kind: 'ask',
      id,
      body,
      function: fn,
    }));
  };

  const serverConnected = function(client) {

    client.sessionId = Math.random().toString().substr(2, 5);
    _outstandingCallbacksByClient[client.sessionId] = {};

    const nukeCallbacks = function(err) {
      const callbacks = _outstandingCallbacksByClient[client.sessionId];

      delete _outstandingCallbacksByClient[client.sessionId];

      for (const id in callbacks) {
        if (!callbacks.hasOwnProperty(id)) {
          continue;
        }
        callbacks[id](Error(err));
      }
    };

    client.isConnected = true;
    client.ask = ask.bind(null, client);
    client.tell = tell.bind(null, client);

    client.on('error', err => {
      client.isConnected = false;
      nukeCallbacks('Client error: ' + (err.message || err));
      return sansaServer.emit('client error', client, err);
    });

    client.on('close', () => {
      client.isConnected = false;
      nukeCallbacks('Client disconnected');
      return sansaServer.emit('disconnected', client);
    });

    const parser = sansa.createParser();

    parser.on('message', message => {
      if (message.kind === 'tell') {
        client.emit(message['function'], message.body);
        sansaServer.emit(message['function'], client, message.body);

      } else if (message.kind === 'ask') {
        client.emit(message['function'], message.body, (err, response) => {
          respondToMessage(client, message, err, response);
        });
        sansaServer.emit(message['function'], client, message.body, (err, response) => {
          respondToMessage(client, message, err, response);
        });

      } else if (message.kind === 'reply') {
        if (typeof _outstandingCallbacksByClient[client.sessionId][message.id] === 'function') {
          _outstandingCallbacksByClient[client.sessionId][message.id](message.err, message.body);
          delete _outstandingCallbacksByClient[client.sessionId][message.id];
        }

      } else if (message.kind === 'heartbeat') {
        client.write(sansa.build({
          kind: 'heartbeat',
        }));
      }
    });

    parser.on('error', error => {
      client.isConnected = false;
      client.emit('client error', error);
      client.destroy();
      sansaServer.emit('client error', client, error);
    });

    sansaServer.emit('connected', client);
    client.pipe(parser);
  };

  const server = net.createServer(client => {
    return serverConnected(client);
  });

  // Public API
  sansaServer.listen = server.listen.bind(server);
  sansaServer.address = server.address.bind(server);
  sansaServer.close = server.close.bind(server);

  return sansaServer;
}

module.exports = createServer;
<commit_msg>Remove unused require<commit_after>'use strict';

const events = require('events');
const net    = require('net');

const sansa  = require('./sansa');

const reservedEventNames = ['on'];

function createServer() {

  const sansaServer = new events.EventEmitter();
  const _outstandingCallbacksByClient = {};

  sansaServer.setMaxListeners(0);

  const respondToMessage = function(client, message, _err, body) {
    let err = _err;
    if (err && Object.prototype.toString.call(err) === '[object Error]') {
      err = err.toString();
    }
    client.write(sansa.build({
      kind: 'reply',
      id: message.id,
      err,
      body,
    }));
  };

  const tell = function(client, fn, body) {

    if (reservedEventNames.indexOf(fn) >= 0) {
      throw Error('tell(' + fn + ') is a reserved event name');
    }

    if (!client.isConnected) {
      throw Error('tell(' + fn + ') on client that has disconnected');
    }

    client.write(sansa.build({
      kind: 'tell',
      body,
      function: fn,
    }));
  };

  const ask = function(client, fn, body, callback) {
    if (typeof callback !== 'function') {
      throw Error('ask(' + fn + ') with no callback');
    }

    if (reservedEventNames.indexOf(fn) >= 0) {
      throw Error('ask(' + fn + ') is a reserved event name');
    }

    if (!client.isConnected) {
      throw Error('ask(' + fn + ') on client that has disconnected');
    }

    const id = client.sessionId + '-' + Math.random().toString().substr(2, 5);
    _outstandingCallbacksByClient[client.sessionId][id] = callback;

    client.write(sansa.build({
      kind: 'ask',
      id,
      body,
      function: fn,
    }));
  };

  const serverConnected = function(client) {

    client.sessionId = Math.random().toString().substr(2, 5);
    _outstandingCallbacksByClient[client.sessionId] = {};

    const nukeCallbacks = function(err) {
      const callbacks = _outstandingCallbacksByClient[client.sessionId];

      delete _outstandingCallbacksByClient[client.sessionId];

      for (const id in callbacks) {
        if (!callbacks.hasOwnProperty(id)) {
          continue;
        }
        callbacks[id](Error(err));
      }
    };

    client.isConnected = true;
    client.ask = ask.bind(null, client);
    client.tell = tell.bind(null, client);

    client.on('error', err => {
      client.isConnected = false;
      nukeCallbacks('Client error: ' + (err.message || err));
      return sansaServer.emit('client error', client, err);
    });

    client.on('close', () => {
      client.isConnected = false;
      nukeCallbacks('Client disconnected');
      return sansaServer.emit('disconnected', client);
    });

    const parser = sansa.createParser();

    parser.on('message', message => {
      if (message.kind === 'tell') {
        client.emit(message['function'], message.body);
        sansaServer.emit(message['function'], client, message.body);

      } else if (message.kind === 'ask') {
        client.emit(message['function'], message.body, (err, response) => {
          respondToMessage(client, message, err, response);
        });
        sansaServer.emit(message['function'], client, message.body, (err, response) => {
          respondToMessage(client, message, err, response);
        });

      } else if (message.kind === 'reply') {
        if (typeof _outstandingCallbacksByClient[client.sessionId][message.id] === 'function') {
          _outstandingCallbacksByClient[client.sessionId][message.id](message.err, message.body);
          delete _outstandingCallbacksByClient[client.sessionId][message.id];
        }

      } else if (message.kind === 'heartbeat') {
        client.write(sansa.build({
          kind: 'heartbeat',
        }));
      }
    });

    parser.on('error', error => {
      client.isConnected = false;
      client.emit('client error', error);
      client.destroy();
      sansaServer.emit('client error', client, error);
    });

    sansaServer.emit('connected', client);
    client.pipe(parser);
  };

  const server = net.createServer(client => {
    return serverConnected(client);
  });

  // Public API
  sansaServer.listen = server.listen.bind(server);
  sansaServer.address = server.address.bind(server);
  sansaServer.close = server.close.bind(server);

  return sansaServer;
}

module.exports = createServer;
<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react';
import ReactDOM from 'react-dom/server';
import serialize from 'serialize-javascript';
import Helmet from 'react-helmet';

/**
 * Wrapper component containing HTML metadata and boilerplate tags.
 * Used in server-side code only to wrap the string output of the
 * rendered route component.
 *
 * The only thing this component doesn't (and can't) include is the
 * HTML doctype declaration, which is added to the rendered output
 * by the server.js file.
 */
export default class Html extends Component {
  static propTypes = {
    assets: PropTypes.object,
    component: PropTypes.node,
    store: PropTypes.object
  };

  render() {
    const { assets, component, store } = this.props;
    const content = component ? ReactDOM.renderToString(component) : '';
    const head = Helmet.rewind();

    return (

          {head.base.toComponent()}
          {head.title.toComponent()}
          {head.meta.toComponent()}
          {head.link.toComponent()}
          {head.script.toComponent()}

          <link rel="shortcut icon" href="/favicon.ico" />
          <meta httpEquiv="X-UA-Compatible" content="IE=edge" />
          <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=0" />
          <link href="https://fonts.googleapis.com/css?family=Roboto:400,300,500" rel="stylesheet" type="text/css" />
          {/* styles (will be present only in production with webpack extract text plugin) */}
          {Object.keys(assets.styles).map((style, key) =>
            <link href={assets.styles[style]} key={key} media="screen, projection" rel="stylesheet" type="text/css" charSet="UTF-8" />
          )}


<commit_before>module.exports = Server

var arrayRemove = require('unordered-array-remove')
var http = require('http')
var mime = require('mime')
var pump = require('pump')
var rangeParser = require('range-parser')
var url = require('url')

function Server (torrent, opts) {
  var server = http.createServer()
  if (!opts) opts = {}
  if (!opts.origin) opts.origin = '*' // allow all origins by default

  var sockets = []
  var pendingReady = []
  var closed = false

  server.on('connection', onConnection)
  server.on('request', onRequest)

  var _close = server.close
  server.close = function (cb) {
    closed = true
    server.removeListener('connection', onConnection)
    server.removeListener('request', onRequest)
    while (pendingReady.length) {
      var onReady = pendingReady.pop()
      torrent.removeListener('ready', onReady)
    }
    torrent = null
    _close.call(server, cb)
  }

  server.destroy = function (cb) {
    sockets.forEach(function (socket) {
      socket.destroy()
    })

    // Only call `server.close` if user has not called it already
    if (!cb) cb = function () {}
    if (closed) process.nextTick(cb)
    else server.close(cb)
  }

  function isOriginAllowed (req) {
    // When `origin` option is `false`, deny all cross-origin requests
    if (opts.origin === false) return false

    // Requests without an 'Origin' header are not actually cross-origin, so just
    // deny them
    if (req.headers.origin == null) return false

    // If a 'hostname' string is specified, deny requests with a 'Host'
    // header that does not match the origin of the torrent server to prevent
    // DNS rebinding attacks.
    if (opts.hostname && req.headers.host !== `${opts.hostname}:${server.address().port}`) {
      return false
    }

    // The user allowed all origins
    if (opts.origin === '*') return true

    // Allow requests where the 'Origin' header matches the `opts.origin` setting
    return req.headers.origin === opts.origin
  }

  function onConnection (socket) {
    socket.setTimeout(36000000)
    sockets.push(socket)
    socket.once('close', function () {
      arrayRemove(sockets, sockets.indexOf(socket))
    })
  }

  function onRequest (req, res) {
    var pathname = url.parse(req.url).pathname

    if (pathname === '/favicon.ico') {
      return serve404Page()
    }

    // Allow cross-origin requests (CORS)
    if (isOriginAllowed(req)) {
      res.setHeader('Access-Control-Allow-Origin', req.headers.origin)
    }

    // Prevent browser mime-type sniffing
    res.setHeader('X-Content-Type-Options', 'nosniff')

    // Allow CORS requests to specify arbitrary headers, e.g. 'Range',
    // by responding to the OPTIONS preflight request with the specified
    // origin and requested headers.
    if (req.method === 'OPTIONS') {
      if (isOriginAllowed(req)) return serveOptionsRequest()
      else return serveMethodNotAllowed()
    }

    if (req.method === 'GET' || req.method === 'HEAD') {
      if (torrent.ready) {
        handleRequest()
      } else {
        pendingReady.push(onReady)
        torrent.once('ready', onReady)
      }
      return
    }

    return serveMethodNotAllowed()

    function serveOptionsRequest () {
      res.statusCode = 204 // no content
      res.setHeader('Access-Control-Max-Age', '600')
      res.setHeader('Access-Control-Allow-Methods', 'GET,HEAD')

      if (req.headers['access-control-request-headers']) {
        res.setHeader(
          'Access-Control-Allow-Headers',
          req.headers['access-control-request-headers']
        )
      }
      res.end()
    }

    function onReady () {
      arrayRemove(pendingReady, pendingReady.indexOf(onReady))
      handleRequest()
    }

    function handleRequest () {
      if (pathname === '/') {
        return serveIndexPage()
      }

      var index = Number(pathname.split('/')[1])
      if (Number.isNaN(index) || index >= torrent.files.length) {
        return serve404Page()
      }

      var file = torrent.files[index]
      serveFile(file)
    }

    function serveIndexPage () {
      res.statusCode = 200
      res.setHeader('Content-Type', 'text/html')

      var listHtml = torrent.files.map(function (file, i) {
        return '
' + file.path + ' ' +
          '(' + file.length + ' bytes)
'
      }).join('
')

      var html = getPageHTML(
        torrent.name + ' - WebTorrent',
        '
' + torrent.name + '

' + listHtml + '
'
      )
      res.end(html)
    }

    function serve404Page () {
      res.statusCode = 404
      res.setHeader('Content-Type', 'text/html')

      var html = getPageHTML('404 - Not Found', '
404 - Not Found
')
      res.end(html)
    }

    function serveFile (file) {
      res.statusCode = 200
      res.setHeader('Content-Type', mime.getType(file.name))

      // Support range-requests
      res.setHeader('Accept-Ranges', 'bytes')

      // Set name of file (for "Save Page As..." dialog)
      res.setHeader(
        'Content-Disposition',
        'inline; filename*=UTF-8\'\'' + encodeRFC5987(file.name)
      )

      // Support DLNA streaming
      res.setHeader('transferMode.dlna.org', 'Streaming')
      res.setHeader(
        'contentFeatures.dlna.org',
        'DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=01700000000000000000000000000000'
      )

      // `rangeParser` returns an array of ranges, or an error code (number) if
      // there was an error parsing the range.
      var range = rangeParser(file.length, req.headers.range || '')

      if (Array.isArray(range)) {
        res.statusCode = 206 // indicates that range-request was understood

        // no support for multi-range request, just use the first range
        range = range[0]

        res.setHeader(
          'Content-Range',
          'bytes ' + range.start + '-' + range.end + '/' + file.length
        )
        res.setHeader('Content-Length', range.end - range.start + 1)
      } else {
        range = null
        res.setHeader('Content-Length', file.length)
      }

      if (req.method === 'HEAD') {
        return res.end()
      }

      pump(file.createReadStream(range), res)
    }

    function serveMethodNotAllowed () {
      res.statusCode = 405
      res.setHeader('Content-Type', 'text/html')
      var html = getPageHTML('405 - Method Not Allowed', '
405 - Method Not Allowed
')
      res.end(html)
    }
  }

  return server
}

function getPageHTML (title, pageHtml) {
  return '<!DOCTYPE html>' +
    '<meta charset="utf-8">' +
    '<title>' + title + '</title>' +
    '' + pageHtml + ''
}

// From https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
function encodeRFC5987 (str) {
  return encodeURIComponent(str)
    // Note that although RFC3986 reserves "!", RFC5987 does not,
    // so we do not need to escape it
    .replace(/['()]/g, escape) // i.e., %27 %28 %29
    .replace(/\*/g, '%2A')
    // The following are not required for percent-encoding per RFC5987,
    // so we can allow for a little better readability over the wire: |`^
    .replace(/%(?:7C|60|5E)/g, unescape)
}
<commit_msg>Modernize lib/server.js<commit_after>const arrayRemove = require('unordered-array-remove')
const http = require('http')
const mime = require('mime')
const pump = require('pump')
const rangeParser = require('range-parser')
const url = require('url')

function Server (torrent, opts = {}) {
  const server = http.createServer()
  if (!opts.origin) opts.origin = '*' // allow all origins by default

  const sockets = []
  const pendingReady = []
  let closed = false

  server.on('connection', onConnection)
  server.on('request', onRequest)

  const _close = server.close
  server.close = cb => {
    closed = true
    server.removeListener('connection', onConnection)
    server.removeListener('request', onRequest)
    while (pendingReady.length) {
      const onReady = pendingReady.pop()
      torrent.removeListener('ready', onReady)
    }
    torrent = null
    _close.call(server, cb)
  }

  server.destroy = cb => {
    sockets.forEach(socket => {
      socket.destroy()
    })

    // Only call `server.close` if user has not called it already
    if (!cb) cb = () => {}
    if (closed) process.nextTick(cb)
    else server.close(cb)
  }

  function isOriginAllowed (req) {
    // When `origin` option is `false`, deny all cross-origin requests
    if (opts.origin === false) return false

    // Requests without an 'Origin' header are not actually cross-origin, so just
    // deny them
    if (req.headers.origin == null) return false

    // If a 'hostname' string is specified, deny requests with a 'Host'
    // header that does not match the origin of the torrent server to prevent
    // DNS rebinding attacks.
    if (opts.hostname && req.headers.host !== `${opts.hostname}:${server.address().port}`) {
      return false
    }

    // The user allowed all origins
    if (opts.origin === '*') return true

    // Allow requests where the 'Origin' header matches the `opts.origin` setting
    return req.headers.origin === opts.origin
  }

  function onConnection (socket) {
    socket.setTimeout(36000000)
    sockets.push(socket)
    socket.once('close', () => {
      arrayRemove(sockets, sockets.indexOf(socket))
    })
  }

  function onRequest (req, res) {
    const pathname = url.parse(req.url).pathname

    if (pathname === '/favicon.ico') {
      return serve404Page()
    }

    // Allow cross-origin requests (CORS)
    if (isOriginAllowed(req)) {
      res.setHeader('Access-Control-Allow-Origin', req.headers.origin)
    }

    // Prevent browser mime-type sniffing
    res.setHeader('X-Content-Type-Options', 'nosniff')

    // Allow CORS requests to specify arbitrary headers, e.g. 'Range',
    // by responding to the OPTIONS preflight request with the specified
    // origin and requested headers.
    if (req.method === 'OPTIONS') {
      if (isOriginAllowed(req)) return serveOptionsRequest()
      else return serveMethodNotAllowed()
    }

    if (req.method === 'GET' || req.method === 'HEAD') {
      if (torrent.ready) {
        handleRequest()
      } else {
        pendingReady.push(onReady)
        torrent.once('ready', onReady)
      }
      return
    }

    return serveMethodNotAllowed()

    function serveOptionsRequest () {
      res.statusCode = 204 // no content
      res.setHeader('Access-Control-Max-Age', '600')
      res.setHeader('Access-Control-Allow-Methods', 'GET,HEAD')

      if (req.headers['access-control-request-headers']) {
        res.setHeader(
          'Access-Control-Allow-Headers',
          req.headers['access-control-request-headers']
        )
      }
      res.end()
    }

    function onReady () {
      arrayRemove(pendingReady, pendingReady.indexOf(onReady))
      handleRequest()
    }

    function handleRequest () {
      if (pathname === '/') {
        return serveIndexPage()
      }

      const index = Number(pathname.split('/')[1])
      if (Number.isNaN(index) || index >= torrent.files.length) {
        return serve404Page()
      }

      const file = torrent.files[index]
      serveFile(file)
    }

    function serveIndexPage () {
      res.statusCode = 200
      res.setHeader('Content-Type', 'text/html')

      const listHtml = torrent.files.map((file, i) => `<li><a download="${file.name}" href="/${i}/${file.name}">${file.path}</a> (${file.length} bytes)</li>`).join('
')

      const html = getPageHTML(
        `${torrent.name} - WebTorrent`,
        `<h1>${torrent.name}</h1><ol>${listHtml}</ol>`
      )
      res.end(html)
    }

    function serve404Page () {
      res.statusCode = 404
      res.setHeader('Content-Type', 'text/html')

      const html = getPageHTML('404 - Not Found', '
404 - Not Found
')
      res.end(html)
    }

    function serveFile (file) {
      res.statusCode = 200
      res.setHeader('Content-Type', mime.getType(file.name))

      // Support range-requests
      res.setHeader('Accept-Ranges', 'bytes')

      // Set name of file (for "Save Page As..." dialog)
      res.setHeader(
        'Content-Disposition',
        `inline; filename*=UTF-8''${encodeRFC5987(file.name)}`
      )

      // Support DLNA streaming
      res.setHeader('transferMode.dlna.org', 'Streaming')
      res.setHeader(
        'contentFeatures.dlna.org',
        'DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=01700000000000000000000000000000'
      )

      // `rangeParser` returns an array of ranges, or an error code (number) if
      // there was an error parsing the range.
      let range = rangeParser(file.length, req.headers.range || '')

      if (Array.isArray(range)) {
        res.statusCode = 206 // indicates that range-request was understood

        // no support for multi-range request, just use the first range
        range = range[0]

        res.setHeader(
          'Content-Range',
          `bytes ${range.start}-${range.end}/${file.length}`
        )
        res.setHeader('Content-Length', range.end - range.start + 1)
      } else {
        range = null
        res.setHeader('Content-Length', file.length)
      }

      if (req.method === 'HEAD') {
        return res.end()
      }

      pump(file.createReadStream(range), res)
    }

    function serveMethodNotAllowed () {
      res.statusCode = 405
      res.setHeader('Content-Type', 'text/html')
      const html = getPageHTML('405 - Method Not Allowed', '
405 - Method Not Allowed
')
      res.end(html)
    }
  }

  return server
}

function getPageHTML (title, pageHtml) {
  return `<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><title>${title}</title></head><body>${pageHtml}</body></html>`
}

// From https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
function encodeRFC5987 (str) {
  return encodeURIComponent(str)
    // Note that although RFC3986 reserves "!", RFC5987 does not,
    // so we do not need to escape it
    .replace(/['()]/g, escape) // i.e., %27 %28 %29
    .replace(/\*/g, '%2A')
    // The following are not required for percent-encoding per RFC5987,
    // so we can allow for a little better readability over the wire: |`^
    .replace(/%(?:7C|60|5E)/g, unescape)
}

module.exports = Server
<|endoftext|>


<commit_before>export const randomIndex = (arr) => {
  return Math.floor(Math.random() * arr.length);
}

export const randomItem = (arr) => {
  return arr[randomIndex(arr)];
}

export const lastItem = (arr) => {
  return arr[arr.length - 1];
}<commit_msg>style(helpers/util): fixes according to airbnb-javascript guide<commit_after>export const randomIndex = arr => Math.floor(Math.random() * arr.length);

export const randomItem = arr => arr[randomIndex(arr)];

export const lastItem = arr => arr[arr.length - 1];
<|endoftext|>


<commit_before>
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);

var sockets = [];

var tempRes = null;
var tempSocket = null;

var __dirnameBack = __dirname.split('/');
__dirnameBack.splice(__dirnameBack.length - 1);
__dirnameBack = __dirnameBack.join('/');

app.get('/file/*', function (req, res) {
  var reqSplit = req.url.split('/');
  tempRes = res;
  tempSocket.emit('acceptRequest', reqSplit[2], reqSplit[3]);
});

app.get('/', function (req, res) {
  res.sendFile(__dirnameBack + '/client/index.html');
});

app.get('/angular.min.js', function (req, res) {
  res.sendFile(__dirnameBack + '/node_modules/angular/angular.min.js');
});

app.get('/angular.min.js.map', function (req, res) {
  res.send('null');
});

app.get('/socket.io-client.js', function (req, res) {
  res.sendFile(__dirnameBack + '/node_modules/socket.io-client/socket.io.js');
});

app.get('/node-uuid.js', function (req, res) {
  res.sendFile(__dirnameBack + '/node_modules/node-uuid/uuid.js');
});

app.get('/minimist.js', function (req, res) {
  res.sendFile(__dirnameBack + '/tempMinimist.js');
});

app.get('/app.js', function (req, res) {
  res.sendFile(__dirnameBack + '/client/app.js');
});

app.get('/client.js', function (req, res) {
  res.sendFile(__dirnameBack + '/client/client.js');
});

app.get('/users.js', function (req, res) {
  res.sendFile(__dirnameBack + '/client/users.js');
});

app.get('/user.js', function (req, res) {
  res.sendFile(__dirnameBack + '/client/user.js');
});

app.get('/rooms.js', function (req, res) {
  res.sendFile(__dirnameBack + '/client/rooms.js');
});

app.get('/messages.js', function (req, res) {
  res.sendFile(__dirnameBack + '/client/messages.js');
});

app.get('/sendFiles.js', function (req, res) {
  res.sendFile(__dirnameBack + '/client/sendFiles.js');
});

app.get('/acceptFiles.js', function (req, res) {
  res.sendFile(__dirnameBack + '/client/acceptFiles.js');
});

app.get('/index.css', function (req, res) {
  res.sendFile(__dirnameBack + '/client/index.css');
});

app.get('/rightSomethingDirective.js', function (req, res) {
  res.sendFile(__dirnameBack + '/client/directives/rightSomethingDirective.js');
});

app.get('/fileUploadDirective.js', function (req, res) {
  res.sendFile(__dirnameBack + '/client/directives/fileUploadDirective.js');
});

app.get('/roomDirective.js', function (req, res) {
  res.sendFile(__dirnameBack + '/client/directives/roomDirective.js');
});

app.get('/acceptFileDirective.js', function (req, res) {
  res.sendFile(__dirnameBack + '/client/directives/acceptFileDirective.js');
});

app.get('/usersDirective.js', function (req, res) {
  res.sendFile(__dirnameBack + '/client/directives/usersDirective.js');
});

app.get('/scrollMessagesDirective.js', function (req, res) {
  res.sendFile(__dirnameBack + '/client/directives/scrollMessagesDirective.js');
});

app.get('/test', function (req, res) {
  res.sendFile(__dirnameBack + '/test/client/test.html');
});

app.get('/mocha.js', function (req, res) {
  res.sendFile(__dirnameBack + '/node_modules/mocha/mocha.js')
});

app.get('/mocha.css', function (req, res) {
  res.sendFile(__dirnameBack + '/node_modules/mocha/mocha.css')
});

app.get('/angular-mocks.js', function (req, res) {
  res.sendFile(__dirnameBack + '/node_modules/angular-mocks/angular-mocks.js')
});

app.get('/expect.js', function (req, res) {
  res.sendFile(__dirnameBack + '/node_modules/expect.js/index.js')
});

app.get('/test.js', function (res, res) {
  res.sendFile(__dirnameBack + '/test/client/test.js')
});

app.get('/testUsers.js', function (res, res) {
  res.sendFile(__dirnameBack + '/test/client/testUsers.js')
});

app.get('/testRooms.js', function (res, res) {
  res.sendFile(__dirnameBack + '/test/client/testRooms.js')
});

app.get('/testMessages.js', function (res, res) {
  res.sendFile(__dirnameBack + '/test/client/testMessages.js')
});

app.get('/testUser.js', function (res, res) {
  res.sendFile(__dirnameBack + '/test/client/testUser.js')
});

app.get('/testSendFiles.js', function (res, res) {
  res.sendFile(__dirnameBack + '/test/client/testSendFiles.js')
});

app.get('/testAcceptFiles.js', function (res, res) {
  res.sendFile(__dirnameBack + '/test/client/testAcceptFiles.js')
});

io.on('connect', function (socket) {

  tempSocket = socket;

  var temp;

  socket.on('something', function(json) {

    temp = {
      socket: socket,
      json: json
    };

    sockets.push(temp);

    sockets.forEach(function (e) {
      if (socket.id !== e.socket.id) {
        e.socket.emit('something', json);
      }
    });

    console.log('connected: ' + json.name);
  });

  socket.on('disconnect', function () {

    sockets = sockets.filter(function (e) {
      if (socket.id !== e.socket.id) return e;
    });

    sockets.forEach(function (e) {
      e.socket.emit('didDisconnect', socket.id);
    });

    console.log('disconnected: ' + temp.json.name);

  });

  socket.on('userJson', function (id, json) {
    sockets.forEach(function (e) {
      if (e.socket.id === id) {
        e.socket.emit('userJson', json);
      }
    });
  });

  socket.on('jsonChanged', function (json) {
    sockets.forEach(function (e) {
      if (e.socket.id === socket.id) {
        e.json = json;
      }
      else {
        e.socket.emit('jsonChanged', json);
      }
    });
  });

  socket.on('createRoom', function (roomId, name) {
    sockets.forEach(function (e) {
      if (socket.id !== e.socket.id) {
        e.socket.emit('createRoom', roomId, socket.id, name);
      }
    });
  });

  socket.on('removeRoom', function (roomId) {
    sockets.forEach(function (e) {
      if (socket.id !== e.socket.id) {
        e.socket.emit('removeRoom', roomId);
      }
    });
  });

  socket.on('joinRoom', function (roomId) {
    sockets.forEach(function (e) {
      if (socket.id !== e.socket.id) {
        e.socket.emit('joinRoom', roomId, socket.id);
      }
    });
  });

  socket.on('leaveRoom', function (roomId) {
    sockets.forEach(function (e) {
      if (socket.id !== e.socket.id) {
        e.socket.emit('leaveRoom', roomId, socket.id);
      }
    });
  });

  socket.on('message', function (roomId, to, message) {
    sockets.forEach(function (e) {
      if (socket.id !== e.socket.id) {
        if (to === null || to === e.socket.id) {
          e.socket.emit('message', roomId, socket.id, message);
        }
      }
    });
  });

  socket.on('users', function() {
    var users = sockets.map(function (e) {
      return e.json.name;
    });
    socket.emit('users', users);
  });

  socket.on('fileData', function (filename, data) {
    tempRes.set({
      'Content-Disposition': 'attachment; filename=' + filename,
      'Content-Type': 'text/plain',
    });
    tempRes.end(data);
  });

  socket.on('hasRequest', function (roomId, id, fileId, filename, stats) {

    sockets.forEach(function (e) {
      if (socket.id !== e.socket.id) {
        if (id === null || id === e.socket.id) {
          e.socket.emit('hasRequest', roomId, socket.id, fileId, filename, stats);
        }
      }
    });

  });

  socket.on('rmsend', function (toId, fileId) {

    sockets.forEach(function (e) {
      if (socket.id !== e.socket.id) {
        if (toId === null || toId === e.socket.id) {
          e.socket.emit('rmsend', fileId);
        }
      }
    });

  });

});

function connect(port, fn) {

  http.listen(port, function (err) {

    fn(err);

  });

}

module.exports.connect = connect;
<commit_msg>Fix file download socket<commit_after>
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);

var sockets = [];

var tempRes = null;

var __dirnameBack = __dirname.split('/');
__dirnameBack.splice(__dirnameBack.length - 1);
__dirnameBack = __dirnameBack.join('/');

app.get('/file/*', function (req, res) {
  tempRes = res;
  var reqSplit = req.url.split('/');
  var userId = reqSplit[2];
  var fileId = reqSplit[3];
  sockets.forEach(function (e) {
    if (userId == e.socket.id) {
      e.socket.emit('acceptRequest', userId, fileId);
    }
  });
});

app.get('/', function (req, res) {
  res.sendFile(__dirnameBack + '/client/index.html');
});

app.get('/angular.min.js', function (req, res) {
  res.sendFile(__dirnameBack + '/node_modules/angular/angular.min.js');
});

app.get('/angular.min.js.map', function (req, res) {
  res.send('null');
});

app.get('/socket.io-client.js', function (req, res) {
  res.sendFile(__dirnameBack + '/node_modules/socket.io-client/socket.io.js');
});

app.get('/node-uuid.js', function (req, res) {
  res.sendFile(__dirnameBack + '/node_modules/node-uuid/uuid.js');
});

app.get('/minimist.js', function (req, res) {
  res.sendFile(__dirnameBack + '/tempMinimist.js');
});

app.get('/app.js', function (req, res) {
  res.sendFile(__dirnameBack + '/client/app.js');
});

app.get('/client.js', function (req, res) {
  res.sendFile(__dirnameBack + '/client/client.js');
});

app.get('/users.js', function (req, res) {
  res.sendFile(__dirnameBack + '/client/users.js');
});

app.get('/user.js', function (req, res) {
  res.sendFile(__dirnameBack + '/client/user.js');
});

app.get('/rooms.js', function (req, res) {
  res.sendFile(__dirnameBack + '/client/rooms.js');
});

app.get('/messages.js', function (req, res) {
  res.sendFile(__dirnameBack + '/client/messages.js');
});

app.get('/sendFiles.js', function (req, res) {
  res.sendFile(__dirnameBack + '/client/sendFiles.js');
});

app.get('/acceptFiles.js', function (req, res) {
  res.sendFile(__dirnameBack + '/client/acceptFiles.js');
});

app.get('/index.css', function (req, res) {
  res.sendFile(__dirnameBack + '/client/index.css');
});

app.get('/rightSomethingDirective.js', function (req, res) {
  res.sendFile(__dirnameBack + '/client/directives/rightSomethingDirective.js');
});

app.get('/fileUploadDirective.js', function (req, res) {
  res.sendFile(__dirnameBack + '/client/directives/fileUploadDirective.js');
});

app.get('/roomDirective.js', function (req, res) {
  res.sendFile(__dirnameBack + '/client/directives/roomDirective.js');
});

app.get('/acceptFileDirective.js', function (req, res) {
  res.sendFile(__dirnameBack + '/client/directives/acceptFileDirective.js');
});

app.get('/usersDirective.js', function (req, res) {
  res.sendFile(__dirnameBack + '/client/directives/usersDirective.js');
});

app.get('/scrollMessagesDirective.js', function (req, res) {
  res.sendFile(__dirnameBack + '/client/directives/scrollMessagesDirective.js');
});

app.get('/test', function (req, res) {
  res.sendFile(__dirnameBack + '/test/client/test.html');
});

app.get('/mocha.js', function (req, res) {
  res.sendFile(__dirnameBack + '/node_modules/mocha/mocha.js')
});

app.get('/mocha.css', function (req, res) {
  res.sendFile(__dirnameBack + '/node_modules/mocha/mocha.css')
});

app.get('/angular-mocks.js', function (req, res) {
  res.sendFile(__dirnameBack + '/node_modules/angular-mocks/angular-mocks.js')
});

app.get('/expect.js', function (req, res) {
  res.sendFile(__dirnameBack + '/node_modules/expect.js/index.js')
});

app.get('/test.js', function (res, res) {
  res.sendFile(__dirnameBack + '/test/client/test.js')
});

app.get('/testUsers.js', function (res, res) {
  res.sendFile(__dirnameBack + '/test/client/testUsers.js')
});

app.get('/testRooms.js', function (res, res) {
  res.sendFile(__dirnameBack + '/test/client/testRooms.js')
});

app.get('/testMessages.js', function (res, res) {
  res.sendFile(__dirnameBack + '/test/client/testMessages.js')
});

app.get('/testUser.js', function (res, res) {
  res.sendFile(__dirnameBack + '/test/client/testUser.js')
});

app.get('/testSendFiles.js', function (res, res) {
  res.sendFile(__dirnameBack + '/test/client/testSendFiles.js')
});

app.get('/testAcceptFiles.js', function (res, res) {
  res.sendFile(__dirnameBack + '/test/client/testAcceptFiles.js')
});

io.on('connect', function (socket) {

  var temp;

  socket.on('something', function(json) {

    temp = {
      socket: socket,
      json: json
    };

    sockets.push(temp);

    sockets.forEach(function (e) {
      if (socket.id !== e.socket.id) {
        e.socket.emit('something', json);
      }
    });

    console.log('connected: ' + json.name);
  });

  socket.on('disconnect', function () {

    sockets = sockets.filter(function (e) {
      if (socket.id !== e.socket.id) return e;
    });

    sockets.forEach(function (e) {
      e.socket.emit('didDisconnect', socket.id);
    });

    console.log('disconnected: ' + temp.json.name);

  });

  socket.on('userJson', function (id, json) {
    sockets.forEach(function (e) {
      if (e.socket.id === id) {
        e.socket.emit('userJson', json);
      }
    });
  });

  socket.on('jsonChanged', function (json) {
    sockets.forEach(function (e) {
      if (e.socket.id === socket.id) {
        e.json = json;
      }
      else {
        e.socket.emit('jsonChanged', json);
      }
    });
  });

  socket.on('createRoom', function (roomId, name) {
    sockets.forEach(function (e) {
      if (socket.id !== e.socket.id) {
        e.socket.emit('createRoom', roomId, socket.id, name);
      }
    });
  });

  socket.on('removeRoom', function (roomId) {
    sockets.forEach(function (e) {
      if (socket.id !== e.socket.id) {
        e.socket.emit('removeRoom', roomId);
      }
    });
  });

  socket.on('joinRoom', function (roomId) {
    sockets.forEach(function (e) {
      if (socket.id !== e.socket.id) {
        e.socket.emit('joinRoom', roomId, socket.id);
      }
    });
  });

  socket.on('leaveRoom', function (roomId) {
    sockets.forEach(function (e) {
      if (socket.id !== e.socket.id) {
        e.socket.emit('leaveRoom', roomId, socket.id);
      }
    });
  });

  socket.on('message', function (roomId, to, message) {
    sockets.forEach(function (e) {
      if (socket.id !== e.socket.id) {
        if (to === null || to === e.socket.id) {
          e.socket.emit('message', roomId, socket.id, message);
        }
      }
    });
  });

  socket.on('users', function() {
    var users = sockets.map(function (e) {
      return e.json.name;
    });
    socket.emit('users', users);
  });

  socket.on('fileData', function (filename, data) {
    tempRes.set({
      'Content-Disposition': 'attachment; filename=' + filename,
      'Content-Type': 'text/plain',
    });
    tempRes.end(data);
  });

  socket.on('hasRequest', function (roomId, id, fileId, filename, stats) {

    sockets.forEach(function (e) {
      if (socket.id !== e.socket.id) {
        if (id === null || id === e.socket.id) {
          e.socket.emit('hasRequest', roomId, socket.id, fileId, filename, stats);
        }
      }
    });

  });

  socket.on('rmsend', function (toId, fileId) {

    sockets.forEach(function (e) {
      if (socket.id !== e.socket.id) {
        if (toId === null || toId === e.socket.id) {
          e.socket.emit('rmsend', fileId);
        }
      }
    });

  });

});

function connect(port, fn) {

  http.listen(port, function (err) {

    fn(err);

  });

}

module.exports.connect = connect;
<|endoftext|>