<commit_before>
var inherits = require('util').inherits

var types = require('./types')
var AST   = require('./ast')

function TypeError(message) {
  Error.apply(this)
  this.name = 'TypeError'; this.message = message
}
inherits(TypeError, Error)

function Scope (parent) {
  this.parent = (parent === undefined) ? null : parent
  this.locals = {}
}
Scope.prototype.get = function (name) {
  if (this.locals[name] !== undefined) {
    return this.locals[name]
  } else if (this.parent !== null) {
    return this.parent.get(name)
  } else {
    throw new TypeError('Unknown variable: '+name)
  }
}
Scope.prototype.setLocal = function (name, type) {
  if (this.locals[name] !== undefined) {
    throw new TypeError("Can't redefine local: "+name)
  }
  this.locals[name] = type
}
Scope.prototype.findScopeForName = function (name) {
  if (this.locals[name] !== undefined) {
    return this
  }
  if (this.parent) {
    return this.parent.findScopeForName(name)
  }
  return null
}

function TypeSystem () {
  this.cache = {}
  this.setupIntrinsics()
}
TypeSystem.prototype.setupIntrinsics = function () {
  this.cache['String'] = new types.String()
  this.cache['Number'] = new types.Number()
  // Alias Integer to Number
  this.cache['Integer'] = new types.Number()
}
TypeSystem.prototype.findByName = function (name) {
  var type = this.cache[name]
  if (type === undefined) {
    throw new TypeError('Type not found: '+name)
  }
  return type
}

function assertInstanceOf(value, type, msg) {
  if (value instanceof type) { return; }
  throw new Error(msg)
}

// AST typing -----------------------------------------------------------------

TypeSystem.prototype.walk = function (rootNode) {
  assertInstanceOf(rootNode, AST.Root, "Node must be root")

  var self = this
  var topLevelScope = new Scope()
  rootNode.statements.forEach(function (stmt) {
    self.visitStatement(stmt, topLevelScope)
  })
}

TypeSystem.prototype.visitBlock = function (node, scope) {
  var self = this
  node.statements.forEach(function (stmt) {
    self.visitStatement(stmt, scope)
  })
}

TypeSystem.prototype.visitStatement = function (node, scope) {
  switch (node.constructor) {
    case AST.Assignment:
      if (node.lvalue instanceof AST.Let) {
        this.visitLet(node, scope)
      } else if (node.lvalue instanceof AST.Var) {
        this.visitVar(node, scope)
      } else if (node.lvalue instanceof AST.Path) {
        this.visitPath(node, scope)
      } else {
        throw new TypeError('Cannot visit Assignment type: '+node.lvalue.constructor.name)
      }
      break
    case AST.If:
      this.visitIf(node, scope)
      break
    case AST.For:
      this.visitFor(node, scope)
      break
    case AST.Return:
      this.visitReturn(node, scope)
      break
    default:
      throw new TypeError("Don't know how to visit: "+node.constructor.name)
      // node.print()
      break
  }
}

TypeSystem.prototype.visitFor = function (node, scope) {
  // TODO: Check that the condition (`node.cond`) resolves to a
  //       boolean-checkable type.

  this.visitStatement(node.init, scope)
  this.visitExpression(node.cond, scope)
  this.visitExpression(node.after, scope)

  var blockScope = new Scope(scope)
  this.visitBlock(node.block, blockScope)
}

TypeSystem.prototype.visitIf = function (node, scope) {
  assertInstanceOf(node.block, AST.Block, 'Expected Block in If block')

  this.visitExpression(node.cond, scope)

  var blockScope = new Scope(scope)
  this.visitBlock(node.block, blockScope)
}

TypeSystem.prototype.visitReturn = function (node, scope) {
  if (node.expr === null || node.expr === undefined) {
    throw new TypeError('Cannot handle empty Return')
  }
  var expr = node.expr
  var exprType = this.resolveExpression(expr, scope)
  node.type = exprType
}

TypeSystem.prototype.visitPath = function (node, scope) {
  var path = node.lvalue
  var foundScope = scope.findScopeForName(path.name)
  if (foundScope === null) {
    throw new TypeError('Failed to find '+path.name)
  }
  var lvalueType = foundScope.get(path.name)
  var rvalueType = this.resolveExpression(node.rvalue, scope)

  if(!lvalueType.equals(rvalueType)) {
    throw new TypeError('Unequal types in assignment: '+lexprType.inspect()+' </> '+rexprType.inspect())
  }
}

TypeSystem.prototype.visitLet = function (node, scope) {
  var lvalueType = new types.Unknown()
  var name       = node.lvalue.name

  // Create a scope inside the Let statement for recursive calls
  var letScope = new Scope(scope)
  letScope.setLocal(name, lvalueType)

  // rvalue is an expression so let's determine its type first.
  var rvalueType = this.resolveExpression(node.rvalue, letScope, function (immediateType) {
    lvalueType.known = immediateType
  })
  scope.setLocal(name, rvalueType)
  // Update the type on the lvalue as well
  node.lvalue.type = rvalueType
}
TypeSystem.prototype.visitVar = TypeSystem.prototype.visitLet

TypeSystem.prototype.resolveExpression = function (expr, scope, immediate) {
  // If we've already deduced the type of this then just return it
  if (expr.type) { return expr.type }

  this.visitExpression(expr, scope, immediate)

  if (expr.type === null || expr.type === undefined) {
    console.log(expr)
    throw new TypeError('Failed to resolve type')
  }
  return expr.type
}

TypeSystem.prototype.visitExpression = function (node, scope, immediate) {
  switch (node.constructor) {
    case AST.Function:
      this.visitFunction(node, scope, immediate)
      break
    case AST.Binary:
      this.visitBinary(node, scope)
      break
    case AST.Chain:
      this.visitChain(node, scope)
      break
    default:
      throw new Error("Can't walk: "+node.constructor.name)
  }
}

TypeSystem.prototype.visitBinary = function (node, scope) {
  var lexprType = this.resolveExpression(node.lexpr, scope)
  var rexprType = this.resolveExpression(node.rexpr, scope)

  if (lexprType.equals(rexprType)) {
    node.type = lexprType
  } else {
    throw new TypeError('Unequal types in binary operation: '+lexprType.inspect()+' </> '+rexprType.inspect())
  }
}

TypeSystem.prototype.visitFunction = function (node, parentScope, immediate) {
  if (node.type) { return node.type }
  var self = this
  var type = new types.Function()

  if (node.ret) {
    type.ret = this.findByName(node.ret)
  }

  // If we have a callback for the immediate (not-yet-fully resolved type)
  // then call it now.
  if (immediate !== undefined) {
    immediate(type)
  }

  var functionScope = new Scope(parentScope)

  node.args.forEach(function (arg) {
    var argType = self.findByName(arg.type)
    functionScope.setLocal(arg.name, argType)
  })

  // Begin by visiting our block
  this.visitBlock(node.block, functionScope)

  if (type.ret) {
    // TODO: If the type is already known from the function definition then
    //       validate the returns match up with that.
    node.type = type
    return
  }
  throw new Error('Inferred return types not supported yet')

  // Then we'll find all the `return`s and get their types
  var returns = []
}

var know = function (type) {
  if (type instanceof types.Unknown) {
    if (type.known === null) {
      throw new TypeError('Unknown type')
    }
    return type.known
  }
  return type
}

TypeSystem.prototype.visitChain = function (node, scope) {
  var type = know(scope.get(node.name))
  node.tail.forEach(function (item) {
    if (item instanceof AST.Call) {
      assertInstanceOf(type, types.Function, 'Trying to call non-Function')
      // TODO: Type-check arguments
      // console.log(item.constructor.name+': '+item.toString())
      // Replace current type with type that's going to be returned
      var returnType = type.ret
      type = returnType
    } else {
      throw new TypeError('Cannot handle Chain item of type: '+item.constructor.name)
    }
  })
  node.type = type
}

module.exports = {TypeSystem: TypeSystem}
<commit_msg>Improve checking of function return types<commit_after>
var inherits = require('util').inherits

var types = require('./types')
var AST   = require('./ast')

function TypeError(message) {
  Error.apply(this)
  this.name = 'TypeError'; this.message = message
}
inherits(TypeError, Error)

function Scope (parent) {
  this.parent = (parent === undefined) ? null : parent
  this.locals = {}
}
Scope.prototype.get = function (name) {
  if (this.locals[name] !== undefined) {
    return this.locals[name]
  } else if (this.parent !== null) {
    return this.parent.get(name)
  } else {
    throw new TypeError('Unknown variable: '+name)
  }
}
Scope.prototype.setLocal = function (name, type) {
  if (this.locals[name] !== undefined) {
    throw new TypeError("Can't redefine local: "+name)
  }
  this.locals[name] = type
}
Scope.prototype.findScopeForName = function (name) {
  if (this.locals[name] !== undefined) {
    return this
  }
  if (this.parent) {
    return this.parent.findScopeForName(name)
  }
  return null
}

function TypeSystem () {
  this.cache = {}
  this.setupIntrinsics()
}
TypeSystem.prototype.setupIntrinsics = function () {
  this.cache['String'] = new types.String()
  this.cache['Number'] = new types.Number()
  // Alias Integer to Number
  this.cache['Integer'] = new types.Number()
}
TypeSystem.prototype.findByName = function (name) {
  var type = this.cache[name]
  if (type === undefined) {
    throw new TypeError('Type not found: '+name)
  }
  return type
}

function assertInstanceOf(value, type, msg) {
  if (value instanceof type) { return; }
  throw new Error(msg)
}

// AST typing -----------------------------------------------------------------

TypeSystem.prototype.walk = function (rootNode) {
  assertInstanceOf(rootNode, AST.Root, "Node must be root")

  var self = this
  var topLevelScope = new Scope()
  rootNode.statements.forEach(function (stmt) {
    self.visitStatement(stmt, topLevelScope)
  })
}

TypeSystem.prototype.visitBlock = function (node, scope) {
  var self = this
  node.statements.forEach(function (stmt) {
    self.visitStatement(stmt, scope, node)
  })
}

TypeSystem.prototype.visitStatement = function (node, scope, parentNode) {
  switch (node.constructor) {
    case AST.Assignment:
      if (node.lvalue instanceof AST.Let) {
        this.visitLet(node, scope)
      } else if (node.lvalue instanceof AST.Var) {
        this.visitVar(node, scope)
      } else if (node.lvalue instanceof AST.Path) {
        this.visitPath(node, scope)
      } else {
        throw new TypeError('Cannot visit Assignment type: '+node.lvalue.constructor.name)
      }
      break
    case AST.If:
      this.visitIf(node, scope)
      break
    case AST.For:
      this.visitFor(node, scope)
      break
    case AST.Return:
      this.visitReturn(node, scope, parentNode)
      break
    default:
      throw new TypeError("Don't know how to visit: "+node.constructor.name)
      // node.print()
      break
  }
}

TypeSystem.prototype.visitFor = function (node, scope) {
  // TODO: Check that the condition (`node.cond`) resolves to a
  //       boolean-checkable type.

  this.visitStatement(node.init, scope)
  this.visitExpression(node.cond, scope)
  this.visitExpression(node.after, scope)

  var blockScope = new Scope(scope)
  this.visitBlock(node.block, blockScope)
}

TypeSystem.prototype.visitIf = function (node, scope) {
  assertInstanceOf(node.block, AST.Block, 'Expected Block in If statement')

  this.visitExpression(node.cond, scope)

  var blockScope = new Scope(scope)
  this.visitBlock(node.block, blockScope)
}

TypeSystem.prototype.visitReturn = function (node, scope, parentNode) {
  if (node.expr === null || node.expr === undefined) {
    throw new TypeError('Cannot handle empty Return')
  }
  var expr = node.expr
  var exprType = this.resolveExpression(expr, scope)
  node.type = exprType
  assertInstanceOf(parentNode, AST.Block, 'Expected Block as parent of Return')
  if (parentNode.returnType) {
    throw new TypeError('Block already has returned')
  }
  parentNode.returnType = exprType
}

TypeSystem.prototype.visitPath = function (node, scope) {
  var path = node.lvalue
  var foundScope = scope.findScopeForName(path.name)
  if (foundScope === null) {
    throw new TypeError('Failed to find '+path.name)
  }
  var lvalueType = foundScope.get(path.name)
  var rvalueType = this.resolveExpression(node.rvalue, scope)

  if(!lvalueType.equals(rvalueType)) {
    throw new TypeError('Unequal types in assignment: '+lexprType.inspect()+' </> '+rexprType.inspect())
  }
}

TypeSystem.prototype.visitLet = function (node, scope) {
  var lvalueType = new types.Unknown()
  var name       = node.lvalue.name

  // Create a scope inside the Let statement for recursive calls
  var letScope = new Scope(scope)
  letScope.setLocal(name, lvalueType)

  // rvalue is an expression so let's determine its type first.
  var rvalueType = this.resolveExpression(node.rvalue, letScope, function (immediateType) {
    lvalueType.known = immediateType
  })
  scope.setLocal(name, rvalueType)
  // Update the type on the lvalue as well
  node.lvalue.type = rvalueType
}
TypeSystem.prototype.visitVar = TypeSystem.prototype.visitLet

TypeSystem.prototype.resolveExpression = function (expr, scope, immediate) {
  // If we've already deduced the type of this then just return it
  if (expr.type) { return expr.type }

  this.visitExpression(expr, scope, immediate)

  if (expr.type === null || expr.type === undefined) {
    console.log(expr)
    throw new TypeError('Failed to resolve type')
  }
  return expr.type
}

TypeSystem.prototype.visitExpression = function (node, scope, immediate) {
  switch (node.constructor) {
    case AST.Function:
      this.visitFunction(node, scope, immediate)
      break
    case AST.Binary:
      this.visitBinary(node, scope)
      break
    case AST.Chain:
      this.visitChain(node, scope)
      break
    default:
      throw new Error("Can't walk: "+node.constructor.name)
  }
}

TypeSystem.prototype.visitBinary = function (node, scope) {
  var lexprType = this.resolveExpression(node.lexpr, scope)
  var rexprType = this.resolveExpression(node.rexpr, scope)

  if (lexprType.equals(rexprType)) {
    node.type = lexprType
  } else {
    throw new TypeError('Unequal types in binary operation: '+lexprType.inspect()+' </> '+rexprType.inspect())
  }
}

function getAllReturnTypes (block) {
  var returnTypes = []
  if (block.returnType) { returnTypes.push(block.returnType) }

  block.statements.forEach(function (stmt) {
    switch (stmt.constructor) {
      case AST.If:
      case AST.While:
      case AST.For:
        var subblockTypes = getAllReturnTypes(stmt.block)
        returnTypes = returnTypes.concat(subblockTypes)
    }
  })
  return returnTypes
}

TypeSystem.prototype.visitFunction = function (node, parentScope, immediate) {
  if (node.type) { return node.type }
  var self = this
  var type = new types.Function()

  if (node.ret) {
    type.ret = this.findByName(node.ret)
  }

  // If we have a callback for the immediate (not-yet-fully resolved type)
  // then call it now.
  if (immediate !== undefined) {
    immediate(type)
  }

  var functionScope = new Scope(parentScope)

  node.args.forEach(function (arg) {
    var argType = self.findByName(arg.type)
    functionScope.setLocal(arg.name, argType)
  })

  // Begin by visiting our block
  this.visitBlock(node.block, functionScope)

  if (type.ret) {
    // Get all possible return types of this function (recursively collects
    // returning child blocks).
    var returnTypes = getAllReturnTypes(node.block)
    returnTypes.forEach(function (returnType) {
      if (!type.ret.equals(returnType)) {
        throw new TypeError('Type returned by function does not match declared return type')
      }
    })
    node.type = type
    return
  }
  throw new Error('Inferred return types not supported yet')

  // Then we'll find all the `return`s and get their types
  var returns = []
}

var know = function (type) {
  if (type instanceof types.Unknown) {
    if (type.known === null) {
      throw new TypeError('Unknown type')
    }
    return type.known
  }
  return type
}

TypeSystem.prototype.visitChain = function (node, scope) {
  var type = know(scope.get(node.name))
  node.tail.forEach(function (item) {
    if (item instanceof AST.Call) {
      assertInstanceOf(type, types.Function, 'Trying to call non-Function')
      // TODO: Type-check arguments
      // console.log(item.constructor.name+': '+item.toString())
      // Replace current type with type that's going to be returned
      var returnType = type.ret
      type = returnType
    } else {
      throw new TypeError('Cannot handle Chain item of type: '+item.constructor.name)
    }
  })
  node.type = type
}

module.exports = {TypeSystem: TypeSystem}
<|endoftext|>


<commit_before>/**
 * node-archiver
 *
 * Copyright (c) 2012-2013 Chris Talkington, contributors.
 * Licensed under the MIT license.
 * https://github.com/ctalkington/node-archiver/blob/master/LICENSE-MIT
 */

var lookup = [
  0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
  0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
  0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
  0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
  0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
  0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
  0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
  0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
  0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
  0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
  0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,
  0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
  0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
  0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
  0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
  0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
  0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,
  0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
  0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
  0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
  0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
  0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
  0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,
  0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
  0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
  0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
  0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
  0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
  0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
  0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
  0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
  0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
  0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
  0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
  0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
  0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
  0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,
  0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
  0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
  0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
  0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,
  0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
  0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,
  0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
  0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
  0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
  0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,
  0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
  0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
  0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
  0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
  0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
  0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,
  0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
  0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
  0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
  0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
  0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
  0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
  0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
  0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,
  0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
  0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
  0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
];

function CRC32() {
  this.crc = ~0;
}

CRC32.prototype.update = function(buffer) {
  for (var i = 0; i < buffer.length; i++) {
    this.crc = (this.crc >>> 8) ^ lookup[(this.crc ^ buffer[i]) & 0xff];
  }

  return this;
};

CRC32.prototype.digest = function() {
  return this.crc ^ -1;
};

exports.createCRC32 = function() {
  return new CRC32();
};

exports.CRC32 = CRC32;<commit_msg>util: avoid the use of buffer var name in CRC32.<commit_after>/**
 * node-archiver
 *
 * Copyright (c) 2012-2013 Chris Talkington, contributors.
 * Licensed under the MIT license.
 * https://github.com/ctalkington/node-archiver/blob/master/LICENSE-MIT
 */

var lookup = [
  0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
  0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
  0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
  0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
  0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
  0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
  0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
  0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
  0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
  0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
  0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,
  0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
  0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
  0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
  0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
  0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
  0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,
  0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
  0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
  0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
  0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
  0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
  0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,
  0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
  0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
  0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
  0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
  0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
  0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
  0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
  0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
  0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
  0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
  0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
  0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
  0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
  0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,
  0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
  0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
  0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
  0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,
  0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
  0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,
  0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
  0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
  0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
  0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,
  0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
  0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
  0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
  0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
  0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
  0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,
  0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
  0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
  0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
  0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
  0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
  0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
  0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
  0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,
  0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
  0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
  0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
];

function CRC32() {
  this.crc = ~0;
}

CRC32.prototype.update = function(buf) {
  for (var i = 0; i < buf.length; i++) {
    this.crc = (this.crc >>> 8) ^ lookup[(this.crc ^ buf[i]) & 0xff];
  }

  return this;
};

CRC32.prototype.digest = function() {
  return this.crc ^ -1;
};

exports.createCRC32 = function() {
  return new CRC32();
};

exports.CRC32 = CRC32;<|endoftext|>


<commit_before>var Q = require('q');
var _ = require('lodash');
var path = require('path');
var cheerio = require('cheerio');
var domSerializer = require('dom-serializer');
var request = require('request');

var links = require('./links');
var imgUtils = require('./images');
var fs = require('./fs');

// Render a cheerio dom as html
var renderDom = function($, dom, options) {
    if (!dom && $._root && $._root.children) {
        dom = $._root.children;
    }

    options = options|| dom.options || $._options;
    return domSerializer(dom, options);
};

// Map of images that have been converted
var imgConversionCache = {};

function replaceText($, el, search, replace, text_only ) {
    return $(el).each(function(){
        var node = this.firstChild,
            val,
            new_val,

            // Elements to be removed at the end.
            remove = [];

        // Only continue if firstChild exists.
        if ( node ) {

            // Loop over all childNodes.
            do {
                // Only process text nodes.
                if ( node.nodeType === 3 ) {

                    // The original node value.
                    val = node.nodeValue;

                    // The new value.
                    new_val = val.replace( search, replace );

                    // Only replace text if the new value is actually different!
                    if ( new_val !== val ) {

                        if ( !text_only && /</.test( new_val ) ) {
                            // The new value contains HTML, set it in a slower but far more
                            // robust way.
                            $(node).before( new_val );

                            // Don't remove the node yet, or the loop will lose its place.
                            remove.push( node );
                        } else {
                            // The new value contains no HTML, so it can be set in this
                            // very fast, simple way.
                            node.nodeValue = new_val;
                        }
                    }
                }

            } while ( node = node.nextSibling );
        }

        // Time to remove those elements!
        remove.length && $(remove).remove();
    });
};

function pregQuote( str ) {
    return (str+'').replace(/([\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:])/g, "\\$1");
};

// Adapt an html snippet to be relative to a base folder
function normalizeHtml(src, options) {
    var $ = cheerio.load(src, { xmlMode: false});
    var toConvert = [];
    var svgContent = {};
    var outputRoot = options.book.options.output;

    imgConversionCache[outputRoot] = imgConversionCache[outputRoot] || {};

    // Find svg images to extract and process
    if (options.convertImages) {
        $("svg").each(function() {
            var content = renderDom($, $(this));
            var svgId = _.uniqueId("svg");
            var dest = svgId+".svg";

            // Generate filename
            dest = "/"+fs.getUniqueFilename(outputRoot, dest);

            svgContent[dest] = content;
            $(this).replaceWith($("").attr("src", dest));
        });
    }

    // Find images to normalize
    $("img").each(function() {
        var origin = undefined;
        var src = $(this).attr("src");
        var isExternal = links.isExternal(src);

        // Transform as relative to the bases
        if (links.isRelative(src)) {
            src = links.toAbsolute(src, options.base, options.output);
        }

        // Convert if needed
        if (options.convertImages) {
            // If image is external and ebook, then downlaod the images
            if (isExternal) {
                origin = src;
                src = "/"+fs.getUniqueFilename(outputRoot,  path.basename(src));
                src = links.toAbsolute(src, options.base, options.output);
                isExternal = false;
            }

            var ext = path.extname(src);
            var srcAbs = path.join("/", options.base, src);

            // Test image extension
            if (_.contains(imgUtils.INVALID, ext)) {
                if (imgConversionCache[outputRoot][srcAbs]) {
                    // Already converted
                    src = imgConversionCache[outputRoot][srcAbs];
                } else {
                    // Not converted yet
                    var dest = "";

                    // Replace extension
                    dest = path.join(path.dirname(srcAbs), path.basename(srcAbs, ext)+".png");
                    dest = dest[0] == "/"? dest.slice(1) : dest;

                    // Get a name that doesn't exists
                    dest = fs.getUniqueFilename(outputRoot, dest);

                    options.book.log.debug.ln("detect invalid image (will be converted to png):", srcAbs);

                    // Add to cache
                    imgConversionCache[outputRoot][srcAbs] = "/"+dest;

                    // Push to convert
                    toConvert.push({
                        origin: origin,
                        content: svgContent[srcAbs],
                        source: isExternal? srcAbs : path.join("./", srcAbs),
                        dest: path.join("./", dest)
                    });

                    src = path.join("/", dest);
                }

                // Reset as relative to output
                src = links.toAbsolute(src, options.base, options.output);
            }

            else if (origin) {
                // Need to downlaod image
                toConvert.push({
                    origin: origin,
                    source: path.join("./", srcAbs)
                });
            }
        }

        $(this).attr("src", src);
    });

    $("a").each(function() {
        var href = $(this).attr("href");

        if (links.isRelative(href)) {
            var absolutePath = path.join(options.base, href);

            // If is in navigation relative: change extsnio nto be .html
            if (options.navigation[absolutePath]) href = links.changeExtension(href, ".html");

            // Transform as absolute
            href = links.toAbsolute(href, options.base, options.output);
        } else {
            $(this).attr("target", "_blank");
        }

        // Transform extension
        $(this).attr("href", href);
    });

    // Replace glossayr terms
    _.each(options.glossary, function(term) {
        var r =  new RegExp( "\\b(" + pregQuote(term.name.toLowerCase()) + ")\\b" , 'gi' );
        var includedInFiles = false;

        $("*").each(function() {
            replaceText($, this, r, function(match) {
                if (!includedInFiles) {
                    includedInFiles = true;
                    term.files = term.files || [];
                    term.files.push(options.navigation[options.input]);
                }
                return ""+match+"";
            });
        });
    });

    return {
        html: renderDom($),
        images: toConvert
    };
};

// Convert svg images to png
function convertImages(images, options) {
    if (!options.convertImages) return Q();

    options.book.log.debug.ln("convert ", images.length, "images to png");

    return _.reduce(images, function(prev, image) {
        var imgin = path.resolve(options.book.options.output, image.source);

        return prev

        // Write image if need to be download
        .then(function() {
            if (!image.origin) return;
            options.book.log.debug("download image", image.origin, "...");
            return options.book.log.debug.promise(fs.writeStream(imgin, request(image.origin)));
        })

        // Write svg if content
        .then(function() {
            if (!image.content) return;
            return fs.writeFile(imgin, image.content);
        })

        // Convert
        .then(function(){
            if (!image.dest) return;
            var imgout = path.resolve(options.book.options.output, image.dest);
            options.book.log.debug("convert image", image.source, "to", image.dest, "...");
            return options.book.log.debug.promise(imgUtils.convertSVG(imgin, imgout));
        });
    }, Q())
    .then(function() {
        options.book.log.debug.ok(images.length+" images converted with success");
    });
};

// Adapt page content to be relative to a base folder
function normalizePage(sections, options) {
    options = _.defaults(options || {}, {
        // Current book
        book: null,

        // Do we need to convert svg?
        convertImages: false,

        // Current file path
        input: ".",

        // Navigation to use to transform path
        navigation: {},

        // Directory parent of the file currently in rendering process
        base: "./",

        // Directory parent from the html output
        output: "./",

        // Glossary terms
        glossary: []
    });

    // List of images to convert
    var toConvert = [];

    sections = _.map(sections, function(section) {
        if (section.type != "normal") return section;

        var out = normalizeHtml(section.content, options);;

        toConvert = toConvert.concat(out.images);
        section.content = out.html;
        return section;
    });

    return Q()
    .then(function() {
        toConvert = _.uniq(toConvert, 'source');
        return convertImages(toConvert, options);
    })
    .thenResolve(sections);
};

// Extract text from sections
function extractText(sections) {
    return _.reduce(sections, function(prev, section) {
        if (section.type != "normal") return prev;

        var $ = cheerio.load(section.content);
        $("*").each(function() {
            prev = prev+" "+$(this).text();
        });

        return prev;
    }, "");
};

module.exports = {
    normalize: normalizePage,
    extractText: extractText
};
<commit_msg>Handle SVG correctly in page normalization<commit_after>var Q = require('q');
var _ = require('lodash');
var path = require('path');
var cheerio = require('cheerio');
var domSerializer = require('dom-serializer');
var request = require('request');

var links = require('./links');
var imgUtils = require('./images');
var fs = require('./fs');

// Render a cheerio dom as html
var renderDom = function($, dom, options) {
    if (!dom && $._root && $._root.children) {
        dom = $._root.children;
    }

    options = options|| dom.options || $._options;
    return domSerializer(dom, options);
};

// Map of images that have been converted
var imgConversionCache = {};

function replaceText($, el, search, replace, text_only ) {
    return $(el).each(function(){
        var node = this.firstChild,
            val,
            new_val,

            // Elements to be removed at the end.
            remove = [];

        // Only continue if firstChild exists.
        if ( node ) {

            // Loop over all childNodes.
            do {
                // Only process text nodes.
                if ( node.nodeType === 3 ) {

                    // The original node value.
                    val = node.nodeValue;

                    // The new value.
                    new_val = val.replace( search, replace );

                    // Only replace text if the new value is actually different!
                    if ( new_val !== val ) {

                        if ( !text_only && /</.test( new_val ) ) {
                            // The new value contains HTML, set it in a slower but far more
                            // robust way.
                            $(node).before( new_val );

                            // Don't remove the node yet, or the loop will lose its place.
                            remove.push( node );
                        } else {
                            // The new value contains no HTML, so it can be set in this
                            // very fast, simple way.
                            node.nodeValue = new_val;
                        }
                    }
                }

            } while ( node = node.nextSibling );
        }

        // Time to remove those elements!
        remove.length && $(remove).remove();
    });
};

function pregQuote( str ) {
    return (str+'').replace(/([\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:])/g, "\\$1");
};

// Adapt an html snippet to be relative to a base folder
function normalizeHtml(src, options) {
    var $ = cheerio.load(src, {
        // We should parse html without trying to normalize too much
        xmlMode: false,

        // SVG need some attributes to use uppercases
        lowerCaseAttributeNames: false,
        lowerCaseTags: false
    });
    var toConvert = [];
    var svgContent = {};
    var outputRoot = options.book.options.output;

    imgConversionCache[outputRoot] = imgConversionCache[outputRoot] || {};

    // Find svg images to extract and process
    if (options.convertImages) {
        $("svg").each(function() {
            var content = renderDom($, $(this));
            var svgId = _.uniqueId("svg");
            var dest = svgId+".svg";

            // Generate filename
            dest = "/"+fs.getUniqueFilename(outputRoot, dest);

            svgContent[dest] = content;
            $(this).replaceWith($("").attr("src", dest));
        });
    }

    // Find images to normalize
    $("img").each(function() {
        var origin = undefined;
        var src = $(this).attr("src");
        var isExternal = links.isExternal(src);

        // Transform as relative to the bases
        if (links.isRelative(src)) {
            src = links.toAbsolute(src, options.base, options.output);
        }

        // Convert if needed
        if (options.convertImages) {
            // If image is external and ebook, then downlaod the images
            if (isExternal) {
                origin = src;
                src = "/"+fs.getUniqueFilename(outputRoot,  path.basename(src));
                src = links.toAbsolute(src, options.base, options.output);
                isExternal = false;
            }

            var ext = path.extname(src);
            var srcAbs = path.join("/", options.base, src);

            // Test image extension
            if (_.contains(imgUtils.INVALID, ext)) {
                if (imgConversionCache[outputRoot][srcAbs]) {
                    // Already converted
                    src = imgConversionCache[outputRoot][srcAbs];
                } else {
                    // Not converted yet
                    var dest = "";

                    // Replace extension
                    dest = path.join(path.dirname(srcAbs), path.basename(srcAbs, ext)+".png");
                    dest = dest[0] == "/"? dest.slice(1) : dest;

                    // Get a name that doesn't exists
                    dest = fs.getUniqueFilename(outputRoot, dest);

                    options.book.log.debug.ln("detect invalid image (will be converted to png):", srcAbs);

                    // Add to cache
                    imgConversionCache[outputRoot][srcAbs] = "/"+dest;

                    // Push to convert
                    toConvert.push({
                        origin: origin,
                        content: svgContent[srcAbs],
                        source: isExternal? srcAbs : path.join("./", srcAbs),
                        dest: path.join("./", dest)
                    });

                    src = path.join("/", dest);
                }

                // Reset as relative to output
                src = links.toAbsolute(src, options.base, options.output);
            }

            else if (origin) {
                // Need to downlaod image
                toConvert.push({
                    origin: origin,
                    source: path.join("./", srcAbs)
                });
            }
        }

        $(this).attr("src", src);
    });

    $("a").each(function() {
        var href = $(this).attr("href");

        if (links.isRelative(href)) {
            var absolutePath = path.join(options.base, href);

            // If is in navigation relative: change extsnio nto be .html
            if (options.navigation[absolutePath]) href = links.changeExtension(href, ".html");

            // Transform as absolute
            href = links.toAbsolute(href, options.base, options.output);
        } else {
            $(this).attr("target", "_blank");
        }

        // Transform extension
        $(this).attr("href", href);
    });

    // Replace glossayr terms
    _.each(options.glossary, function(term) {
        var r =  new RegExp( "\\b(" + pregQuote(term.name.toLowerCase()) + ")\\b" , 'gi' );
        var includedInFiles = false;

        $("*").each(function() {
            replaceText($, this, r, function(match) {
                if (!includedInFiles) {
                    includedInFiles = true;
                    term.files = term.files || [];
                    term.files.push(options.navigation[options.input]);
                }
                return ""+match+"";
            });
        });
    });

    return {
        html: renderDom($),
        images: toConvert
    };
};

// Convert svg images to png
function convertImages(images, options) {
    if (!options.convertImages) return Q();

    options.book.log.debug.ln("convert ", images.length, "images to png");

    return _.reduce(images, function(prev, image) {
        var imgin = path.resolve(options.book.options.output, image.source);

        return prev

        // Write image if need to be download
        .then(function() {
            if (!image.origin) return;
            options.book.log.debug("download image", image.origin, "...");
            return options.book.log.debug.promise(fs.writeStream(imgin, request(image.origin)));
        })

        // Write svg if content
        .then(function() {
            if (!image.content) return;
            return fs.writeFile(imgin, image.content);
        })

        // Convert
        .then(function(){
            if (!image.dest) return;
            var imgout = path.resolve(options.book.options.output, image.dest);
            options.book.log.debug("convert image", image.source, "to", image.dest, "...");
            return options.book.log.debug.promise(imgUtils.convertSVG(imgin, imgout));
        });
    }, Q())
    .then(function() {
        options.book.log.debug.ok(images.length+" images converted with success");
    });
};

// Adapt page content to be relative to a base folder
function normalizePage(sections, options) {
    options = _.defaults(options || {}, {
        // Current book
        book: null,

        // Do we need to convert svg?
        convertImages: false,

        // Current file path
        input: ".",

        // Navigation to use to transform path
        navigation: {},

        // Directory parent of the file currently in rendering process
        base: "./",

        // Directory parent from the html output
        output: "./",

        // Glossary terms
        glossary: []
    });

    // List of images to convert
    var toConvert = [];

    sections = _.map(sections, function(section) {
        if (section.type != "normal") return section;

        var out = normalizeHtml(section.content, options);;

        toConvert = toConvert.concat(out.images);
        section.content = out.html;
        return section;
    });

    return Q()
    .then(function() {
        toConvert = _.uniq(toConvert, 'source');
        return convertImages(toConvert, options);
    })
    .thenResolve(sections);
};

// Extract text from sections
function extractText(sections) {
    return _.reduce(sections, function(prev, section) {
        if (section.type != "normal") return prev;

        var $ = cheerio.load(section.content);
        $("*").each(function() {
            prev = prev+" "+$(this).text();
        });

        return prev;
    }, "");
};

module.exports = {
    normalize: normalizePage,
    extractText: extractText
};
<|endoftext|>


<commit_before>var path = require('path')
var http = require('http')
var jDad = require('jdad')
var merge = require('lodash.merge')
var debug = require('debug')('micromono:router')
var assign = require('lodash.assign')
var isArray = require('lodash.isarray')
var argsNames = require('js-args-names')
var httpProxy = require('http-proxy')

/**
 * Create route definition object from route name.
 *
 * @param  {String}    routeName Path of the route
 * @return {Object}    Route definition object
 */
exports.generateRouteByName = function(routeName, defaultMethod) {
  if ('string' === typeof routeName) {
    var _path = routeName.split('::')
    var method = defaultMethod || 'get'
    if (_path.length === 2) {
      method = _path[0]
      _path = _path[1]
    } else {
      _path = routeName
    }

    var route = {
      name: routeName,
      method: method,
      path: _path
    }

    return route
  } else {
    throw new Error('Route name should be string.')
  }
}

/**
 * Normalize route definition to a portable format which could be easily used
 * by different web frameworks.
 *
 * ```javascript
 * route: {
 *   'get::/user/:name': function(req, res) {...}
 * }
 * ```
 *
 * will be formatted into:
 *
 * ```javascript
 * {
 *   name: 'get::/user/:name',
 *   method: 'get',
 *   path: '/user/:name',
 *   handler: [Function],
 *   args: ['req', 'res'],
 *   middleware: null
 * }
 * ```
 *
 * Example with route middleware:
 *
 * ```javascript
 * route: {
 *   'get::/user/:name': [function(req, res, next) {...}, function(req, res) {...}]
 * }
 * ```
 *
 * will be formatted into:
 *
 * ```javascript
 * {
 *   name: 'get::/user/:name',
 *   method: 'get',
 *   path: '/user/:name',
 *   handler: Function,
 *   args: ['req', 'res'],
 *   middleware: [Function]
 * }
 * ```
 *
 * @param {Object}  route   Route definition object.
 * @param {Service} service Instance of service.
 * @return {Object}         Formatted routes object.
 */
exports.normalizeRoutes = function(route, page, pageApiBaseUrl) {
  var _routes = {}

  Object.keys(route).forEach(function(routePath) {
    var middleware
    var routeHandler = route[routePath]
    var _route = exports.generateRouteByName(routePath)

    if (page && page.hasOwnProperty(routePath)) {
      _route.page = page[routePath]
    }

    if (isArray(routeHandler)) {
      middleware = routeHandler
      routeHandler = middleware.pop()
    }

    _route.args = argsNames(routeHandler)
    _route.handler = routeHandler
    _route.middleware = middleware || null

    if (_route.page) {
      // Add the page api route
      var apiRoutePath = path.join(pageApiBaseUrl, _route.path)
      if (!_routes[apiRoutePath]) {
        _route.api = apiRoutePath
        _routes[apiRoutePath] = {
          name: apiRoutePath,
          method: _route.method,
          path: apiRoutePath,
          args: _route.args,
          handler: _route.handler,
          middleware: _route.middleware
        }
      }
    }

    _routes[routePath] = _route
  })

  return _routes
}

exports.normalizeUses = function(use) {
  var _uses = {}

  Object.keys(use).forEach(function(name) {
    var _use = use[name]
    if (!Array.isArray(_use)) {
      _use = [_use]
    }
    _use = _use.map(function(url) {
      if ('string' === typeof url) {
        url = exports.generateRouteByName(url, 'default')
      }
      return url
    })
    _uses[name] = _use
  })

  return _uses
}

exports.normalizeMiddlewares = function(middleware, middlewareBaseUrl) {
  var _middlewares = {}

  Object.keys(middleware).forEach(function(name) {
    _middlewares[name] = {
      name: name,
      path: path.join(middlewareBaseUrl, name),
      handler: middleware[name]
    }
  })

  return _middlewares
}

// Static function for processing remote middleware. It rebuilds the remote
// middleware handler so it could be used locally.
exports.rebuildRemoteMiddlewares = function(middlewares, service) {
  Object.keys(middlewares).forEach(function(mName) {
    var middleware = middlewares[mName]
    var _path = middleware.path
    debug('[%s] rebuild remote middleware handler "%s" url "%s"', service.name, middleware.name, _path)

    var handler = function() {
      return function(req, res, next) {
        var provider = service.scheduler.get()
        if (!provider) {
          exports.noProviderAvailable(req, res, next)
          return
        }

        var headers = assign({}, req.headers)
        delete headers['content-length']
        var proxyReq = http.request({
          host: provider.address,
          port: provider.webPort,
          path: _path,
          method: req.method,
          headers: headers
        }, function(proxyRes) {
          if (proxyRes.statusCode === 103) {
            var reqMerge = proxyRes.headers['x-micromono-req']
            if (reqMerge) {
              try {
                reqMerge = jDad.parse(reqMerge, {
                  decycle: true
                })
                merge(req, reqMerge)
              } catch (e) {
                debug('[%s] Failed to merge request from remote middleware', service.name, e)
              }
            }
            next()
          } else {
            res.set(proxyRes.headers)
            res.statusCode = proxyRes.statusCode
            proxyRes.pipe(res)
          }
        })

        proxyReq.on('error', function(err, req, res) {
          if (res) {
            res.writeHead(500, {
              'Content-Type': 'text/plain'
            })
            res.end('Service error')
          }
          debug('[%s] middleware "%s" proxy error', service.name, mName, err.stack)
        })

        proxyReq.end()
      }
    }

    service.middleware[middleware.name] = handler
  })
}

/**
 * Get a function which proxy the requests to the real services.
 *
 * @param  {String} baseUrl      The base url for the target endpoint of the service.
 * @param  {String} allowUpgrade The url for upgrade request (websockets).
 * @return {Function}            The proxy handler function.
 */
exports.getProxyHandler = function(scheduler, webServer, allowUpgrade) {
  var proxy = httpProxy.createProxyServer()

  proxy.on('error', function(err, req, res) {
    if (res) {
      res.writeHead(500, {
        'Content-Type': 'text/plain'
      })
      res.end('Service error')
    }
    debug('Proxy error', err.stack)
  })

  if (allowUpgrade) {
    webServer.on('upgrade', function(req, socket, head) {
      var provider = scheduler.get()
      if (!provider) {
        exports.noProviderAvailable(req, socket)
      } else {
        var target = 'http://' + provider.host + ':' + provider.web.port
        proxy.ws(req, socket, head, {
          target: target
        })
      }
    })
  }

  return function(req, res) {
    var provider = scheduler.get()
    if (!provider) {
      exports.noProviderAvailable(req, res)
    } else {
      var target = 'http://' + provider.host + ':' + provider.web.port
      proxy.web(req, res, {
        target: target
      })
    }
  }
}

exports.noProviderAvailable = function(req, res) {
  res.writeHead(500)
  res.end('Service Unavailable')
}
<commit_msg>Update for new announcement format.<commit_after>var path = require('path')
var http = require('http')
var jDad = require('jdad')
var merge = require('lodash.merge')
var debug = require('debug')('micromono:router')
var assign = require('lodash.assign')
var isArray = require('lodash.isarray')
var argsNames = require('js-args-names')
var httpProxy = require('http-proxy')

/**
 * Create route definition object from route name.
 *
 * @param  {String}    routeName Path of the route
 * @return {Object}    Route definition object
 */
exports.generateRouteByName = function(routeName, defaultMethod) {
  if ('string' === typeof routeName) {
    var _path = routeName.split('::')
    var method = defaultMethod || 'get'
    if (_path.length === 2) {
      method = _path[0]
      _path = _path[1]
    } else {
      _path = routeName
    }

    var route = {
      name: routeName,
      method: method,
      path: _path
    }

    return route
  } else {
    throw new Error('Route name should be string.')
  }
}

/**
 * Normalize route definition to a portable format which could be easily used
 * by different web frameworks.
 *
 * ```javascript
 * route: {
 *   'get::/user/:name': function(req, res) {...}
 * }
 * ```
 *
 * will be formatted into:
 *
 * ```javascript
 * {
 *   name: 'get::/user/:name',
 *   method: 'get',
 *   path: '/user/:name',
 *   handler: [Function],
 *   args: ['req', 'res'],
 *   middleware: null
 * }
 * ```
 *
 * Example with route middleware:
 *
 * ```javascript
 * route: {
 *   'get::/user/:name': [function(req, res, next) {...}, function(req, res) {...}]
 * }
 * ```
 *
 * will be formatted into:
 *
 * ```javascript
 * {
 *   name: 'get::/user/:name',
 *   method: 'get',
 *   path: '/user/:name',
 *   handler: Function,
 *   args: ['req', 'res'],
 *   middleware: [Function]
 * }
 * ```
 *
 * @param {Object}  route   Route definition object.
 * @param {Service} service Instance of service.
 * @return {Object}         Formatted routes object.
 */
exports.normalizeRoutes = function(route, page, pageApiBaseUrl) {
  var _routes = {}

  Object.keys(route).forEach(function(routePath) {
    var middleware
    var routeHandler = route[routePath]
    var _route = exports.generateRouteByName(routePath)

    if (page && page.hasOwnProperty(routePath)) {
      _route.page = page[routePath]
    }

    if (isArray(routeHandler)) {
      middleware = routeHandler
      routeHandler = middleware.pop()
    }

    _route.args = argsNames(routeHandler)
    _route.handler = routeHandler
    _route.middleware = middleware || null

    if (_route.page) {
      // Add the page api route
      var apiRoutePath = path.join(pageApiBaseUrl, _route.path)
      if (!_routes[apiRoutePath]) {
        _route.api = apiRoutePath
        _routes[apiRoutePath] = {
          name: apiRoutePath,
          method: _route.method,
          path: apiRoutePath,
          args: _route.args,
          handler: _route.handler,
          middleware: _route.middleware
        }
      }
    }

    _routes[routePath] = _route
  })

  return _routes
}

exports.normalizeUses = function(use) {
  var _uses = {}

  Object.keys(use).forEach(function(name) {
    var _use = use[name]
    if (!Array.isArray(_use)) {
      _use = [_use]
    }
    _use = _use.map(function(url) {
      if ('string' === typeof url) {
        url = exports.generateRouteByName(url, 'default')
      }
      return url
    })
    _uses[name] = _use
  })

  return _uses
}

exports.normalizeMiddlewares = function(middleware, middlewareBaseUrl) {
  var _middlewares = {}

  Object.keys(middleware).forEach(function(name) {
    _middlewares[name] = {
      name: name,
      path: path.join(middlewareBaseUrl, name),
      handler: middleware[name]
    }
  })

  return _middlewares
}

// Static function for processing remote middleware. It rebuilds the remote
// middleware handler so it could be used locally.
exports.rebuildRemoteMiddlewares = function(middlewares, service) {
  Object.keys(middlewares).forEach(function(mName) {
    var middleware = middlewares[mName]
    var _path = middleware.path
    debug('[%s] rebuild remote middleware handler "%s" url "%s"', service.name, middleware.name, _path)

    var handler = function() {
      return function(req, res, next) {
        var provider = service.scheduler.get()
        if (!provider) {
          exports.noProviderAvailable(req, res, next)
          return
        }

        var headers = assign({}, req.headers)
        delete headers['content-length']
        var proxyReq = http.request({
          host: provider.host,
          port: provider.web.port,
          path: _path,
          method: req.method,
          headers: headers
        }, function(proxyRes) {
          if (proxyRes.statusCode === 103) {
            var reqMerge = proxyRes.headers['x-micromono-req']
            if (reqMerge) {
              try {
                reqMerge = jDad.parse(reqMerge, {
                  decycle: true
                })
                merge(req, reqMerge)
              } catch (e) {
                debug('[%s] Failed to merge request from remote middleware', service.name, e)
              }
            }
            next()
          } else {
            res.set(proxyRes.headers)
            res.statusCode = proxyRes.statusCode
            proxyRes.pipe(res)
          }
        })

        proxyReq.on('error', function(err, req, res) {
          if (res) {
            res.writeHead(500, {
              'Content-Type': 'text/plain'
            })
            res.end('Service error')
          }
          debug('[%s] middleware "%s" proxy error', service.name, mName, err.stack)
        })

        proxyReq.end()
      }
    }

    service.middleware[middleware.name] = handler
  })
}

/**
 * Get a function which proxy the requests to the real services.
 *
 * @param  {String} baseUrl      The base url for the target endpoint of the service.
 * @param  {String} allowUpgrade The url for upgrade request (websockets).
 * @return {Function}            The proxy handler function.
 */
exports.getProxyHandler = function(scheduler, webServer, allowUpgrade) {
  var proxy = httpProxy.createProxyServer()

  proxy.on('error', function(err, req, res) {
    if (res) {
      res.writeHead(500, {
        'Content-Type': 'text/plain'
      })
      res.end('Service error')
    }
    debug('Proxy error', err.stack)
  })

  if (allowUpgrade) {
    webServer.on('upgrade', function(req, socket, head) {
      var provider = scheduler.get()
      if (!provider) {
        exports.noProviderAvailable(req, socket)
      } else {
        var target = 'http://' + provider.host + ':' + provider.web.port
        proxy.ws(req, socket, head, {
          target: target
        })
      }
    })
  }

  return function(req, res) {
    var provider = scheduler.get()
    if (!provider) {
      exports.noProviderAvailable(req, res)
    } else {
      var target = 'http://' + provider.host + ':' + provider.web.port
      proxy.web(req, res, {
        target: target
      })
    }
  }
}

exports.noProviderAvailable = function(req, res) {
  res.writeHead(500)
  res.end('Service Unavailable')
}
<|endoftext|>


<commit_before>/*!
 * ride-over <https://github.com/nknapp/ride-over>
 *
 * Copyright (c) 2015 Nils Knappmeier.
 * Released under the MIT license.
 */

/**
 * Wrap a function so that if it overrides another function, that function will
 * be available as `this.parent`
 * @param fn
 */
module.exports = function withParent (fn) {
  if (fn == null) {
    return fn
  }
  var result = cloneFunction(fn)
  // Misuse of `_.partial` so that we have dedicated function object on which we can set properties
  result._customize_custom_overrider = function (a, b) {
    return b.bind({
      parent: a
    })
  }
  return result
}

// https://gist.github.com/Sykkro/7490193#file-clone-js-L1
function cloneFunction (fn) {
  var temp = function temporary () {
    return fn.apply(this, arguments)
  }
  for (var key in fn) {
    temp[key] = fn[key]
  }
  return temp
}
<commit_msg>Remove obsolete comment<commit_after>/*!
 * ride-over <https://github.com/nknapp/ride-over>
 *
 * Copyright (c) 2015 Nils Knappmeier.
 * Released under the MIT license.
 */

/**
 * Wrap a function so that if it overrides another function, that function will
 * be available as `this.parent`
 * @param fn
 */
module.exports = function withParent (fn) {
  if (fn == null) {
    return fn
  }
  var result = cloneFunction(fn)
  result._customize_custom_overrider = function (a, b) {
    return b.bind({
      parent: a
    })
  }
  return result
}

// https://gist.github.com/Sykkro/7490193#file-clone-js-L1
function cloneFunction (fn) {
  var temp = function temporary () {
    return fn.apply(this, arguments)
  }
  for (var key in fn) {
    temp[key] = fn[key]
  }
  return temp
}
<|endoftext|>


<commit_before>/**
 * Created by apizzimenti on 6/8/16.
 */

"use strict";

/**
 * @author Anthony Pizzimenti
 *
 * @desc Abstract parent class for Inventory items.
 *
 * @param game {object} Current Phaser game instance.
 * @param key {string} Cached Phaser texture or image.
 * @param inventory {Inventory} Game's Inventory.
 * @param [name=Item.key] {string} Item's name to be displayed in the Inventory.
 *
 * @property game {object} Phaser game instance.
 * @property key {string} The key that belongs to this Item.
 * @property keys {string[]} Dummy property that can be overwritten.
 * @property map {Map} Game's Map object.
 * @property inventory {Inventory} This game's inventory.
 * @property text {object} When the item is loaded into the inventory, this prop is populated with a Phaser.Text object.
 * @property inventorySprite {sprite} On initialization, this will contain a regular Phaser sprite.
 * @property sprite {sprite} On selected, this will contain an isometric Sprite.
 * @property sprite.tile {object} Contains location and directional information for the sprite.
 * @property direction {number} This sprite's default direction.
 *
 * @class {object} Item
 * @this Item
 */

function Item (game, key, inventory, name) {

    this.keys = [key, key, key, key];

    this.game = game;
    this.key = key;

    if (name) {
        this.name = name;
    } else {
        this.name = key;
    }

    this.inventory = inventory;
    this.text = {};

    this.inventorySprite = {};
    this.sprite = {};
    this.sprite.tile = {};
    this.sprite.direction = 0;
    this.map = this.inventory.map;
    this.auto = true;

}

/**
 * @author Anthony Pizzimenti
 *
 * @desc Default action method. Can and should be overwritten on a per-item basis.
 *
 * @this Item
 */

Item.prototype.action = function () {
    console.warn(`${this.key} is using the builtin action method`);
};

/**
 * @author Anthony Pizzimenti
 *
 * @desc Initializes objects for the isometric physics system. Sprites are immovable.
 *
 * @this Item
 */

Item.prototype.threeDInitialize = function () {

    this.sprite.anchor.set(...Globals.anchor);
    this.sprite.body.collideWorldBounds = true;
    this.game.physics.isoArcade.enable(this.sprite);
    this.sprite.enableBody = true;

    this.sprite.tile = {};
    this.sprite.direction = 0;

    this.sprite.body.immovable = true;

    this.setting = true;
    direction(this);
};
<commit_msg>make items movable<commit_after>/**
 * Created by apizzimenti on 6/8/16.
 */

"use strict";

/**
 * @author Anthony Pizzimenti
 *
 * @desc Abstract parent class for Inventory items.
 *
 * @param game {object} Current Phaser game instance.
 * @param key {string} Cached Phaser texture or image.
 * @param inventory {Inventory} Game's Inventory.
 * @param [name=Item.key] {string} Item's name to be displayed in the Inventory.
 *
 * @property game {object} Phaser game instance.
 * @property key {string} The key that belongs to this Item.
 * @property keys {string[]} Dummy property that can be overwritten.
 * @property map {Map} Game's Map object.
 * @property inventory {Inventory} This game's inventory.
 * @property text {object} When the item is loaded into the inventory, this prop is populated with a Phaser.Text object.
 * @property inventorySprite {sprite} On initialization, this will contain a regular Phaser sprite.
 * @property sprite {sprite} On selected, this will contain an isometric Sprite.
 * @property sprite.tile {object} Contains location and directional information for the sprite.
 * @property direction {number} This sprite's default direction.
 *
 * @class {object} Item
 * @this Item
 */

function Item (game, key, inventory, name) {

    this.keys = [key, key, key, key];

    this.game = game;
    this.key = key;

    if (name) {
        this.name = name;
    } else {
        this.name = key;
    }

    this.inventory = inventory;
    this.text = {};

    this.inventorySprite = {};
    this.sprite = {};
    this.sprite.tile = {};
    this.sprite.direction = 0;
    this.map = this.inventory.map;
    this.auto = true;

}

/**
 * @author Anthony Pizzimenti
 *
 * @desc Default action method. Can and should be overwritten on a per-item basis.
 *
 * @this Item
 */

Item.prototype.action = function () {
    console.warn(`${this.key} is using the builtin action method`);
};

/**
 * @author Anthony Pizzimenti
 *
 * @desc Initializes objects for the isometric physics system.
 *
 * @this Item
 */

Item.prototype.threeDInitialize = function () {

    this.sprite.anchor.set(...Globals.anchor);
    this.sprite.body.collideWorldBounds = true;
    this.game.physics.isoArcade.enable(this.sprite);
    this.sprite.enableBody = true;

    this.sprite.tile = {};
    this.sprite.direction = 0;

    this.setting = true;
    direction(this);
};
<|endoftext|>


<commit_before>var path = require('canonical-path');

function UsingContainer(c, prefix) {
  this._c = c;
  this._prefix = prefix;
}

UsingContainer.prototype.register = function(id) {
  id = path.join(this._prefix, id);

  var spec = this._c._specs[id];
  // FIXME: This should always load, so later used sources correctly override earlier ones.
  if (spec) { return; } // already registered
  this._c._loadSpec(id);
}

module.exports = UsingContainer;
<commit_msg>Always load specs on register.<commit_after>var path = require('canonical-path');

function UsingContainer(c, prefix) {
  this._c = c;
  this._prefix = prefix;
}

UsingContainer.prototype.register = function(id) {
  id = path.join(this._prefix, id);
  this._c._loadSpec(id);
}

module.exports = UsingContainer;
<|endoftext|>


<commit_before>/**
 * Copyright 2015 Netsend.
 *
 * This file is part of PersDB.
 *
 * PersDB is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Affero General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 *
 * PersDB is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License along
 * with PersDB. If not, see <https://www.gnu.org/licenses/>.
 */

/* jshint -W116 */

'use strict';

var programName = require('path').basename(__filename, '.js');

if (process.getuid() !== 0) {
  console.error('%s: execute as root', programName);
  process.exit(1);
}

if (typeof process.send !== 'function') {
  console.error('%s: use child_process.spawn to invoke this module', programName);
  process.exit(2);
}

var net = require('net');
var tls = require('tls');
var fs = require('fs');

var chroot = require('chroot');
var keyFilter = require('object-key-filter');
var LDJSONStream = require('ld-jsonstream');
var posix = require('posix');
var ws = require('nodejs-websocket');
var c = require('constants');

var logger = require('./logger');

// filter password out request
function debugReq(req) {
  return keyFilter(req, ['password'], true);
}

/**
 * Instantiate a WebSocket server that listens to incoming auth requests.
 *
 * Full FSM: init --> listen
 *
 * The first message emitted is "init" which signals that this process is ready to
 * receive the configuration data. File desciptors for the log config should be
 * sent in subsequent messages.
 *
 * {
 *   log:            {Object}      // log configuration
 *   cert:           {Object}      // X.509 certificate for the TLS server
 *   key:            {Object}      // RSA private key for the certificate
 *   dhparam:        {Object}      // file that contains a Diffie-Hellman group
 *   [host]:         {String}      // websocket host, defaults to 127.0.0.1
 *   [port]:         {Number}      // websocket port, defaults to 3344
 *   [proxyPort]:    {Number}      // tcp port of preauth, defaults to 2344
 *   [chroot]:       {String}      // defaults to /var/empty
 *   [user]:         {String}      // defaults to "nobody"
 *   [group]:        {String}      // defaults to "nobody"
 * }
 *
 * No other messages should be sent to this process.
 *
 * As soon as the server is listening, a "listen" message is emitted. After this
 * only new incoming auth requests are sent from this process to it's parent.
 */

var log; // used after receiving the log configuration
var proxyPort; // used after config is recieved

// return a string identifier of the connection if connected over ip
function createConnId(conn) {
  var connId;
  if (conn.remoteAddress) {
    connId = conn.remoteAddress + '-' + conn.remotePort + '-' + conn.localAddress + '-' + conn.localPort;
  } else {
    connId = 'UNIX domain socket';
  }
  return connId;
}

function connHandler(conn) {
  var connId = createConnId(conn.socket);
  log.info('%s: connected %j', connId, conn.socket.address());

  var connected, received = [];
  conn.once('text', function(authReq) {
    log.info('%s: received %d bytes "%j"', connId, authReq.length, debugReq(JSON.parse(authReq)));
    received.push(authReq);
    if (connected) {
      log.info('%s: proxy auth request to server "%j"', connId, debugReq(JSON.parse(authReq)));
      preauthSock.write(received.shift());
    }
    conn.once('text', function(dataReq) {
      log.info('%s: received %d bytes "%s"', connId, dataReq.length, dataReq);
      received.push(dataReq);
      if (connected) {
        log.info('%s: proxy auth/data request(s) to server', connId);
        while (received.length) {
          preauthSock.write(received.shift());
        }
      }
      conn.on('text', function() {
        log.err('%s: %s', connId, 'more than two text events emitted');
        preauthSock.end();
        conn.close();
      });
    });
  });

  var connClose, preauthClose;
  var preauthSock = net.createConnection({ port: proxyPort, host: '127.0.0.1' }, function() {
    log.info('%s: connected to proxy', connId);
    connected = true;
    if (received.length) {
      log.info('%s: proxy auth/data requests to server', connId);
      while (received.length) {
        preauthSock.write(received.shift());
      }
    }
    var binRecMode;
    conn.on('binary', function(rs) {
      log.debug('%s: setup binary reader', connId);
      rs.on('readable', function() {
        var chunk;
        while (null !== (chunk = rs.read())) {
          log.debug('%s: write chunk from client', connId);
          preauthSock.write(chunk);
        }
      });
      rs.on('end', function() {
        log.debug('%s: end binary reader', connId);
      });
      binRecMode = true;
    });
    conn.on('error', function(err) {
      log.err('%s: %s', connId, err);
      preauthSock.end();
    });
    conn.on('close', function() {
      log.info('%s: close', connId);
      connClose = true;
      // only end if not in binRecMode yet.
      if (!binRecMode && !preauthClose) {
        log.debug('%s: end (no binary data received)', connId);
        preauthSock.end();
        preauthClose = true;
      }
    });

    // proxy data from preauth back to connection
    var ls = new LDJSONStream({ flush: false, maxDocs: 1, maxBytes: 512, debug: false }); // accept one data req
    preauthSock.pipe(ls).once('readable', function() {
      var data = ls.read();
      if (data == null) { return; }

      var dataReq = JSON.stringify(data);
      log.info('%s: proxy data request to client "%s"', connId, dataReq);
      conn.sendText(dataReq + '\n');

      // setup a direct pipe for binary data (only BSON is expected after data request)
      preauthSock.unpipe(ls);
      // push back any data in ls
      if (ls.buffer.length) {
        log.info('push back %d bytes', ls.buffer.length);
        preauthSock.unshift(ls.buffer);
      }

      preauthSock.on('readable', function() {
        var chunk;
        while (null !== (chunk = preauthSock.read())) {
          conn.sendBinary(chunk);
        }
      });
    });
    preauthSock.on('end', function() {
      log.info('%s: preauth end', connId);
      preauthClose = true;
      if (!connClose) {
        conn.close();
        connClose = true;
      }
    });
  });
}

/**
 * Expect one init request (request that contains listen config)
 * {
 *   log:            {Object}      // log configuration
 *   cert:           {String}      // X.509 certificate for the TLS server
 *   key:            {String}      // RSA private key for the certificate
 *   dhparam:        {String}      // file that contains a Diffie-Hellman group
 *   [host]:         {String}      // websocket host, defaults to 127.0.0.1
 *   [port]:         {Number}      // websocket port, defaults to 3344
 *   [proxyPort]:    {Number}      // tcp port of preauth, defaults to 2344
 *   [chroot]:       {String}      // defaults to /var/empty
 *   [user]:         {String}      // defaults to "nobody"
 *   [group]:        {String}      // defaults to "nobody"
 * }
 */
process.once('message', function(msg) {
  if (typeof msg !== 'object') { throw new TypeError('msg must be an object'); }

  if (typeof msg.log !== 'object') { throw new TypeError('msg.log must be an object'); }
  if (!msg.cert || typeof msg.cert !== 'string') { throw new TypeError('msg.cert must be a non-empty string'); }
  if (!msg.key || typeof msg.key !== 'string') { throw new TypeError('msg.key must be a non-empty string'); }
  if (!msg.dhparam || typeof msg.dhparam !== 'string') { throw new TypeError('msg.dhparam must be a non-empty string'); }

  if (msg.host != null && typeof msg.host !== 'string') { throw new TypeError('msg.host must be a string'); }
  if (msg.port != null && typeof msg.port !== 'number') { throw new TypeError('msg.port must be a number'); }
  if (msg.proxyPort != null && typeof msg.proxyPort !== 'number') { throw new TypeError('msg.proxyPort must be a number'); }
  if (msg.chroot != null && typeof msg.chroot !== 'string') { throw new TypeError('msg.chroot must be a string'); }
  if (msg.user != null && typeof msg.user !== 'string') { throw new TypeError('msg.user must be a string'); }
  if (msg.group != null && typeof msg.group !== 'string') { throw new TypeError('msg.group must be a string'); }

  var host = msg.host || '127.0.0.1';
  var port = msg.port || 3344;
  proxyPort = msg.proxyPort || 2344;

  programName = 'wss ' + host + ':' + port;

  process.title = 'pdb/' + programName;

  var user = msg.user || 'nobody';
  var group = msg.group || 'nobody';

  var newRoot = msg.chroot || '/var/empty';

  msg.log.ident = programName;

  // open log
  logger(msg.log, function(err, l) {
    if (err) { l.err(err); throw err; }

    log = l; // use this logger in the mt's as well

    // make sure the key is not world readable/writable
    var stats = fs.statSync(msg.key);
    if ((stats.mode & 0o066) !== 0) {
      log.err('msg.key should not be group or world readable or writable');
      process.exit(12);
    }

    var cert = fs.readFileSync(msg.cert);
    var key = fs.readFileSync(msg.key);
    var dhparam = fs.readFileSync(msg.dhparam);

    // determine ciphers
    var ciphers = [];
    if (~tls.getCiphers().indexOf('ecdhe-rsa-chacha20-poly1305')) {
      ciphers.push('ECDHE-RSA-CHACHA20-POLY1305');
    }
    if (~tls.getCiphers().indexOf('dhe-rsa-chacha20-poly1305')) {
      ciphers.push('DHE-RSA-CHACHA20-POLY1305');
    }
    if (~tls.getCiphers().indexOf('ecdhe-rsa-aes128-gcm-sha256')) {
      ciphers.push('ECDHE-RSA-AES128-GCM-SHA256');
    }
    if (~tls.getCiphers().indexOf('dhe-rsa-aes128-gcm-sha256')) {
      ciphers.push('DHE-RSA-AES128-GCM-SHA256');
    }

    if (!ciphers.length) {
      log.err('no supported ciphers found');
      process.exit(11);
    }

    var uid, gid;
    try {
      uid = posix.getpwnam(user).uid;
      gid = posix.getgrnam(group).gid;
    } catch(err) {
      log.err('wss %s %s:%s', err, user, group);
      process.exit(3);
    }

    // chroot or exit
    try {
      chroot(newRoot, user, gid);
      log.info('wss changed root to %s and user:group to %s:%s', newRoot, user, group);
    } catch(err) {
      log.err('wss changing root or user failed %j %s', msg, err);
      process.exit(8);
    }

    // open server or exit
    function openServerAndProceed() {
      // create start and stop tasks, ensure at least one async function

      var opts = {
        secure: true,
        cert: cert,
        key: key,
        dhparam: dhparam,
        secureProtocol: 'SSLv23_server_method',
        secureOptions: c.SSL_OP_NO_SSLv2|c.SSL_OP_NO_SSLv3|c.SSL_OP_NO_TLSv1|c.SSL_OP_NO_TLSv1_1,
        ciphers: ciphers.join(':'),
        handshakeTimeout: 20
      };

      var wsServer = ws.createServer(opts, connHandler);

      wsServer.on('error', function(err) {
        log.err(err);
      });

      wsServer.on('close', function() {
        log.notice('closed');
      });

      // start WebSocket server
      wsServer.listen(port, host, function() {
        log.notice('WebSocket bound %s:%s', host, port);
        process.send('listen');
      });

      // handle shutdown
      function shutdown() {
        log.info('closing...');
        wsServer.socket.close();
        wsServer.connections.forEach(function(conn) {
          conn.close();
        });
      }

      // listen to kill signals
      process.once('SIGTERM', shutdown);
      process.once('SIGINT', shutdown);
    }

    openServerAndProceed();
  });
});

process.send('init');
<commit_msg>wss_server: remove debugReq<commit_after>/**
 * Copyright 2015 Netsend.
 *
 * This file is part of PersDB.
 *
 * PersDB is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Affero General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 *
 * PersDB is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License along
 * with PersDB. If not, see <https://www.gnu.org/licenses/>.
 */

/* jshint -W116 */

'use strict';

var programName = require('path').basename(__filename, '.js');

if (process.getuid() !== 0) {
  console.error('%s: execute as root', programName);
  process.exit(1);
}

if (typeof process.send !== 'function') {
  console.error('%s: use child_process.spawn to invoke this module', programName);
  process.exit(2);
}

var net = require('net');
var tls = require('tls');
var fs = require('fs');

var chroot = require('chroot');
var LDJSONStream = require('ld-jsonstream');
var posix = require('posix');
var ws = require('nodejs-websocket');
var c = require('constants');

var logger = require('./logger');

/**
 * Instantiate a WebSocket server that listens to incoming auth requests.
 *
 * Full FSM: init --> listen
 *
 * The first message emitted is "init" which signals that this process is ready to
 * receive the configuration data. File desciptors for the log config should be
 * sent in subsequent messages.
 *
 * {
 *   log:            {Object}      // log configuration
 *   cert:           {Object}      // X.509 certificate for the TLS server
 *   key:            {Object}      // RSA private key for the certificate
 *   dhparam:        {Object}      // file that contains a Diffie-Hellman group
 *   [host]:         {String}      // websocket host, defaults to 127.0.0.1
 *   [port]:         {Number}      // websocket port, defaults to 3344
 *   [proxyPort]:    {Number}      // tcp port of preauth, defaults to 2344
 *   [chroot]:       {String}      // defaults to /var/empty
 *   [user]:         {String}      // defaults to "nobody"
 *   [group]:        {String}      // defaults to "nobody"
 * }
 *
 * No other messages should be sent to this process.
 *
 * As soon as the server is listening, a "listen" message is emitted. After this
 * only new incoming auth requests are sent from this process to it's parent.
 */

var log; // used after receiving the log configuration
var proxyPort; // used after config is recieved

// return a string identifier of the connection if connected over ip
function createConnId(conn) {
  var connId;
  if (conn.remoteAddress) {
    connId = conn.remoteAddress + '-' + conn.remotePort + '-' + conn.localAddress + '-' + conn.localPort;
  } else {
    connId = 'UNIX domain socket';
  }
  return connId;
}

function connHandler(conn) {
  var connId = createConnId(conn.socket);
  log.info('%s: connected %j', connId, conn.socket.address());

  var connected, received = [];
  conn.once('text', function(authReq) {
    log.info('%s: received %d bytes', connId, authReq.length);
    received.push(authReq);
    if (connected) {
      log.info('%s: proxy auth request to server', connId);
      preauthSock.write(received.shift());
    }
    conn.once('text', function(dataReq) {
      log.info('%s: received %d bytes "%s"', connId, dataReq.length, dataReq);
      received.push(dataReq);
      if (connected) {
        log.info('%s: proxy auth/data request(s) to server', connId);
        while (received.length) {
          preauthSock.write(received.shift());
        }
      }
      conn.on('text', function() {
        log.err('%s: %s', connId, 'more than two text events emitted');
        preauthSock.end();
        conn.close();
      });
    });
  });

  var connClose, preauthClose;
  var preauthSock = net.createConnection({ port: proxyPort, host: '127.0.0.1' }, function() {
    log.info('%s: connected to proxy', connId);
    connected = true;
    if (received.length) {
      log.info('%s: proxy auth/data requests to server', connId);
      while (received.length) {
        preauthSock.write(received.shift());
      }
    }
    var binRecMode;
    conn.on('binary', function(rs) {
      log.debug('%s: setup binary reader', connId);
      rs.on('readable', function() {
        var chunk;
        while (null !== (chunk = rs.read())) {
          log.debug('%s: write chunk from client', connId);
          preauthSock.write(chunk);
        }
      });
      rs.on('end', function() {
        log.debug('%s: end binary reader', connId);
      });
      binRecMode = true;
    });
    conn.on('error', function(err) {
      log.err('%s: %s', connId, err);
      preauthSock.end();
    });
    conn.on('close', function() {
      log.info('%s: close', connId);
      connClose = true;
      // only end if not in binRecMode yet.
      if (!binRecMode && !preauthClose) {
        log.debug('%s: end (no binary data received)', connId);
        preauthSock.end();
        preauthClose = true;
      }
    });

    // proxy data from preauth back to connection
    var ls = new LDJSONStream({ flush: false, maxDocs: 1, maxBytes: 512, debug: false }); // accept one data req
    preauthSock.pipe(ls).once('readable', function() {
      var data = ls.read();
      if (data == null) { return; }

      var dataReq = JSON.stringify(data);
      log.info('%s: proxy data request to client "%s"', connId, dataReq);
      conn.sendText(dataReq + '\n');

      // setup a direct pipe for binary data (only BSON is expected after data request)
      preauthSock.unpipe(ls);
      // push back any data in ls
      if (ls.buffer.length) {
        log.info('push back %d bytes', ls.buffer.length);
        preauthSock.unshift(ls.buffer);
      }

      preauthSock.on('readable', function() {
        var chunk;
        while (null !== (chunk = preauthSock.read())) {
          conn.sendBinary(chunk);
        }
      });
    });
    preauthSock.on('end', function() {
      log.info('%s: preauth end', connId);
      preauthClose = true;
      if (!connClose) {
        conn.close();
        connClose = true;
      }
    });
  });
}

/**
 * Expect one init request (request that contains listen config)
 * {
 *   log:            {Object}      // log configuration
 *   cert:           {String}      // X.509 certificate for the TLS server
 *   key:            {String}      // RSA private key for the certificate
 *   dhparam:        {String}      // file that contains a Diffie-Hellman group
 *   [host]:         {String}      // websocket host, defaults to 127.0.0.1
 *   [port]:         {Number}      // websocket port, defaults to 3344
 *   [proxyPort]:    {Number}      // tcp port of preauth, defaults to 2344
 *   [chroot]:       {String}      // defaults to /var/empty
 *   [user]:         {String}      // defaults to "nobody"
 *   [group]:        {String}      // defaults to "nobody"
 * }
 */
process.once('message', function(msg) {
  if (typeof msg !== 'object') { throw new TypeError('msg must be an object'); }

  if (typeof msg.log !== 'object') { throw new TypeError('msg.log must be an object'); }
  if (!msg.cert || typeof msg.cert !== 'string') { throw new TypeError('msg.cert must be a non-empty string'); }
  if (!msg.key || typeof msg.key !== 'string') { throw new TypeError('msg.key must be a non-empty string'); }
  if (!msg.dhparam || typeof msg.dhparam !== 'string') { throw new TypeError('msg.dhparam must be a non-empty string'); }

  if (msg.host != null && typeof msg.host !== 'string') { throw new TypeError('msg.host must be a string'); }
  if (msg.port != null && typeof msg.port !== 'number') { throw new TypeError('msg.port must be a number'); }
  if (msg.proxyPort != null && typeof msg.proxyPort !== 'number') { throw new TypeError('msg.proxyPort must be a number'); }
  if (msg.chroot != null && typeof msg.chroot !== 'string') { throw new TypeError('msg.chroot must be a string'); }
  if (msg.user != null && typeof msg.user !== 'string') { throw new TypeError('msg.user must be a string'); }
  if (msg.group != null && typeof msg.group !== 'string') { throw new TypeError('msg.group must be a string'); }

  var host = msg.host || '127.0.0.1';
  var port = msg.port || 3344;
  proxyPort = msg.proxyPort || 2344;

  programName = 'wss ' + host + ':' + port;

  process.title = 'pdb/' + programName;

  var user = msg.user || 'nobody';
  var group = msg.group || 'nobody';

  var newRoot = msg.chroot || '/var/empty';

  msg.log.ident = programName;

  // open log
  logger(msg.log, function(err, l) {
    if (err) { l.err(err); throw err; }

    log = l; // use this logger in the mt's as well

    // make sure the key is not world readable/writable
    var stats = fs.statSync(msg.key);
    if ((stats.mode & 0o066) !== 0) {
      log.err('msg.key should not be group or world readable or writable');
      process.exit(12);
    }

    var cert = fs.readFileSync(msg.cert);
    var key = fs.readFileSync(msg.key);
    var dhparam = fs.readFileSync(msg.dhparam);

    // determine ciphers
    var ciphers = [];
    if (~tls.getCiphers().indexOf('ecdhe-rsa-chacha20-poly1305')) {
      ciphers.push('ECDHE-RSA-CHACHA20-POLY1305');
    }
    if (~tls.getCiphers().indexOf('dhe-rsa-chacha20-poly1305')) {
      ciphers.push('DHE-RSA-CHACHA20-POLY1305');
    }
    if (~tls.getCiphers().indexOf('ecdhe-rsa-aes128-gcm-sha256')) {
      ciphers.push('ECDHE-RSA-AES128-GCM-SHA256');
    }
    if (~tls.getCiphers().indexOf('dhe-rsa-aes128-gcm-sha256')) {
      ciphers.push('DHE-RSA-AES128-GCM-SHA256');
    }

    if (!ciphers.length) {
      log.err('no supported ciphers found');
      process.exit(11);
    }

    var uid, gid;
    try {
      uid = posix.getpwnam(user).uid;
      gid = posix.getgrnam(group).gid;
    } catch(err) {
      log.err('wss %s %s:%s', err, user, group);
      process.exit(3);
    }

    // chroot or exit
    try {
      chroot(newRoot, user, gid);
      log.info('wss changed root to %s and user:group to %s:%s', newRoot, user, group);
    } catch(err) {
      log.err('wss changing root or user failed %j %s', msg, err);
      process.exit(8);
    }

    // open server or exit
    function openServerAndProceed() {
      // create start and stop tasks, ensure at least one async function

      var opts = {
        secure: true,
        cert: cert,
        key: key,
        dhparam: dhparam,
        secureProtocol: 'SSLv23_server_method',
        secureOptions: c.SSL_OP_NO_SSLv2|c.SSL_OP_NO_SSLv3|c.SSL_OP_NO_TLSv1|c.SSL_OP_NO_TLSv1_1,
        ciphers: ciphers.join(':'),
        handshakeTimeout: 20
      };

      var wsServer = ws.createServer(opts, connHandler);

      wsServer.on('error', function(err) {
        log.err(err);
      });

      wsServer.on('close', function() {
        log.notice('closed');
      });

      // start WebSocket server
      wsServer.listen(port, host, function() {
        log.notice('WebSocket bound %s:%s', host, port);
        process.send('listen');
      });

      // handle shutdown
      function shutdown() {
        log.info('closing...');
        wsServer.socket.close();
        wsServer.connections.forEach(function(conn) {
          conn.close();
        });
      }

      // listen to kill signals
      process.once('SIGTERM', shutdown);
      process.once('SIGINT', shutdown);
    }

    openServerAndProceed();
  });
});

process.send('init');
<|endoftext|>