<commit_before>/*
 *   Copyright 2014-2015 Guy Bedford (http://guybedford.com)
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */
var ui = require('./ui');
var path = require('path');
var config = require('./config');
var systemBuilder = require('systemjs-builder');
var fs = require('fs');
var asp = require('rsvp').denodeify;

exports.depCache = function(moduleName) {
  return config.load()
  .then(function() {
    moduleName = moduleName || config.loader.main;
  })
  .then(function() {
    ui.log('info', 'Injecting the traced dependency tree for `' + moduleName + '`...');

    // trace the starting module
    var cfg = config.loader.getConfig();
    cfg.baseURL = path.relative(process.cwd(), config.pjson.baseURL);

    return systemBuilder.trace(moduleName, cfg);
  })
  .then(function(output) {
    var traceTree = output.tree;
    moduleName = output.moduleName;
    var depCache = config.loader.depCache = config.loader.depCache || {};
    for (var d in traceTree) {
      var deps = traceTree[d].deps.map(function(dep) {
        return traceTree[d].depMap[dep];
      });

      if (deps.length)
        depCache[d] = deps;
    }
  })
  .then(config.save)
  .then(function() {
    ui.log('ok', 'Depenency tree injected');
  })
  .catch(function(e) {
    ui.log('err', e.stack || e);
  });
}

function extractOperations(args) {
  var operations = [];

  for (var i = 1; i < args.length - 1; i = i + 2) {
    var operator = args[i];
    var moduleName = args[i + 1];

    operations.push({
      operator: operator,
      moduleName: moduleName
    });
  }

  return operations;
}

function extractBundleName(fileName){
  return path.relative(config.pjson.baseURL, fileName.replace(/\.js$/, ''));
}

// options.inject, options.sourceMaps, options.minify
exports.bundle = function(moduleExpression, fileName, opts) {
  var args = moduleExpression.split(' ');

  var firstModule = args[0];

  var operations = extractOperations(args);

  systemBuilder.reset();

  return config.load()
  .then(function() {
    fileName = fileName || path.resolve(config.pjson.baseURL, 'build.js');

    if (!opts.sourceMaps)
      return removeExistingSourceMap(fileName);
  })
  .then(function() {
    ui.log('info', 'Building the bundle tree for `' + moduleExpression + '`...');

    // trace the starting module
    var cfg = config.loader.getConfig();
    cfg.baseURL = path.relative(process.cwd(), config.pjson.baseURL);

    return systemBuilder.trace(firstModule, cfg);
  })
  .then(function(trace) {

    // if there are no other operations, then we have the final tree
    if (!operations.length)
      return trace.tree;

    // chain the operations, applying them with the trace of the next module
    var operationPromise = Promise.resolve(trace.tree);
    operations.forEach(function(op) {
      operationPromise = operationPromise
      .then(function(curTree) {
        return systemBuilder.trace(op.moduleName)
        .then(function(nextTrace) {

          var operatorFunction;

          if (op.operator == '+')
            operatorFunction = systemBuilder.addTrees;
          else if (op.operator == '-')
            operatorFunction = systemBuilder.subtractTrees;
          else
            throw 'Unknown operator ' + op.operator;

          return operatorFunction(curTree, nextTrace.tree);
        });
      });
    });

    return operationPromise;
  })
  .then(function(buildTree) {
    if (opts.inject) {
      // Add the bundle to config if the inject flag was given.
      var bundleName = extractBundleName(fileName);
      if (!config.loader.bundles) {
        config.loader.bundles = {};
      }
      config.loader.bundles[bundleName] = Object.keys(buildTree).filter(function(moduleName) {
        return buildTree[moduleName].metadata.build !== false;
      });
      ui.log('ok', '`' + bundleName + '` added to config bundles.');
    }
    if (!('lowResSourceMaps' in opts))
      opts.lowResSourceMaps = true;
    return systemBuilder.buildTree(buildTree, fileName, opts);
  })
  .then(function() {
    delete config.loader.depCache;
  })
  .then(config.save)
  .then(function() {
    var buildPath = path.relative(process.cwd(), fileName);
    var resolution = opts.lowResSourceMaps ? 'low-res ' : '';
    ui.log('ok', 'Built into `' + buildPath + '`' +
      (opts.sourceMaps ? ' with ' + resolution + 'source maps' : '') + ', ' +
      (opts.minify ? '' : 'un') + 'minified' +
      (opts.minify ? (opts.mangle ? ', ' : ', un') + 'mangled.' : '.'));
  })
  .catch(function(e) {
    ui.log('err', e.stack || e);
    throw e;
  });
}

exports.unbundle = function() {
  return config.load()
  .then(function() {
    config.loader.bundles = {};
    config.loader.depCache = {};
    return config.save();
  })
  .then(function() {
    ui.log('ok', 'Bundle configuration removed.');
  });
}

// options.minify, options.sourceMaps
exports.bundleSFX = function(moduleName, fileName, opts) {
  return config.load()
  .then(function() {
    fileName = fileName || path.resolve(config.pjson.baseURL, 'build.js');

    if (!opts.sourceMaps)
      return removeExistingSourceMap(fileName);
  })
  .then(function() {
    ui.log('info', 'Building the single-file sfx bundle for `' + moduleName + '`...');

    // trace the starting module
    var cfg = config.loader.getConfig();
    cfg.baseURL = path.relative(process.cwd(), config.pjson.baseURL);

    opts.config = cfg;
    if (!('lowResSourceMaps' in opts))
      opts.lowResSourceMaps = true;

    return systemBuilder.buildSFX(moduleName, fileName, opts);
  })
  .then(config.save)
  .then(function() {
    ui.log('ok', 'Built into `' + path.relative(process.cwd(), fileName) + '`' + (opts.sourceMaps ? ' with source maps' : '') + ', ' + (opts.minify ? '' : 'un') + 'minified' + ', ' + (opts.mangle ? '' : 'un') + 'mangled.');
  })
  .catch(function(e) {
    ui.log('err', e.stack || e);
    throw e;
  });
}

function removeExistingSourceMap(fileName) {
  return asp(fs.unlink)(fileName + '.map')
  .catch(function(e) {
    if (e.code == 'ENOENT')
      return;
    throw e;
  });
}
<commit_msg>ensure bundle name is converted from windows paths<commit_after>/*
 *   Copyright 2014-2015 Guy Bedford (http://guybedford.com)
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */
var ui = require('./ui');
var path = require('path');
var config = require('./config');
var systemBuilder = require('systemjs-builder');
var fs = require('fs');
var asp = require('rsvp').denodeify;

exports.depCache = function(moduleName) {
  return config.load()
  .then(function() {
    moduleName = moduleName || config.loader.main;
  })
  .then(function() {
    ui.log('info', 'Injecting the traced dependency tree for `' + moduleName + '`...');

    // trace the starting module
    var cfg = config.loader.getConfig();
    cfg.baseURL = path.relative(process.cwd(), config.pjson.baseURL);

    return systemBuilder.trace(moduleName, cfg);
  })
  .then(function(output) {
    var traceTree = output.tree;
    moduleName = output.moduleName;
    var depCache = config.loader.depCache = config.loader.depCache || {};
    for (var d in traceTree) {
      var deps = traceTree[d].deps.map(function(dep) {
        return traceTree[d].depMap[dep];
      });

      if (deps.length)
        depCache[d] = deps;
    }
  })
  .then(config.save)
  .then(function() {
    ui.log('ok', 'Depenency tree injected');
  })
  .catch(function(e) {
    ui.log('err', e.stack || e);
  });
}

function extractOperations(args) {
  var operations = [];

  for (var i = 1; i < args.length - 1; i = i + 2) {
    var operator = args[i];
    var moduleName = args[i + 1];

    operations.push({
      operator: operator,
      moduleName: moduleName
    });
  }

  return operations;
}

function extractBundleName(fileName) {
  return path.relative(config.pjson.baseURL, fileName.replace(/\.js$/, '')).replace(/\\/g, '/');
}

// options.inject, options.sourceMaps, options.minify
exports.bundle = function(moduleExpression, fileName, opts) {
  var args = moduleExpression.split(' ');

  var firstModule = args[0];

  var operations = extractOperations(args);

  systemBuilder.reset();

  return config.load()
  .then(function() {
    fileName = fileName || path.resolve(config.pjson.baseURL, 'build.js');

    if (!opts.sourceMaps)
      return removeExistingSourceMap(fileName);
  })
  .then(function() {
    ui.log('info', 'Building the bundle tree for `' + moduleExpression + '`...');

    // trace the starting module
    var cfg = config.loader.getConfig();
    cfg.baseURL = path.relative(process.cwd(), config.pjson.baseURL);

    return systemBuilder.trace(firstModule, cfg);
  })
  .then(function(trace) {

    // if there are no other operations, then we have the final tree
    if (!operations.length)
      return trace.tree;

    // chain the operations, applying them with the trace of the next module
    var operationPromise = Promise.resolve(trace.tree);
    operations.forEach(function(op) {
      operationPromise = operationPromise
      .then(function(curTree) {
        return systemBuilder.trace(op.moduleName)
        .then(function(nextTrace) {

          var operatorFunction;

          if (op.operator == '+')
            operatorFunction = systemBuilder.addTrees;
          else if (op.operator == '-')
            operatorFunction = systemBuilder.subtractTrees;
          else
            throw 'Unknown operator ' + op.operator;

          return operatorFunction(curTree, nextTrace.tree);
        });
      });
    });

    return operationPromise;
  })
  .then(function(buildTree) {
    if (opts.inject) {
      // Add the bundle to config if the inject flag was given.
      var bundleName = extractBundleName(fileName);
      if (!config.loader.bundles) {
        config.loader.bundles = {};
      }
      config.loader.bundles[bundleName] = Object.keys(buildTree).filter(function(moduleName) {
        return buildTree[moduleName].metadata.build !== false;
      });
      ui.log('ok', '`' + bundleName + '` added to config bundles.');
    }
    if (!('lowResSourceMaps' in opts))
      opts.lowResSourceMaps = true;
    return systemBuilder.buildTree(buildTree, fileName, opts);
  })
  .then(function() {
    delete config.loader.depCache;
  })
  .then(config.save)
  .then(function() {
    var buildPath = path.relative(process.cwd(), fileName);
    var resolution = opts.lowResSourceMaps ? 'low-res ' : '';
    ui.log('ok', 'Built into `' + buildPath + '`' +
      (opts.sourceMaps ? ' with ' + resolution + 'source maps' : '') + ', ' +
      (opts.minify ? '' : 'un') + 'minified' +
      (opts.minify ? (opts.mangle ? ', ' : ', un') + 'mangled.' : '.'));
  })
  .catch(function(e) {
    ui.log('err', e.stack || e);
    throw e;
  });
}

exports.unbundle = function() {
  return config.load()
  .then(function() {
    config.loader.bundles = {};
    config.loader.depCache = {};
    return config.save();
  })
  .then(function() {
    ui.log('ok', 'Bundle configuration removed.');
  });
}

// options.minify, options.sourceMaps
exports.bundleSFX = function(moduleName, fileName, opts) {
  return config.load()
  .then(function() {
    fileName = fileName || path.resolve(config.pjson.baseURL, 'build.js');

    if (!opts.sourceMaps)
      return removeExistingSourceMap(fileName);
  })
  .then(function() {
    ui.log('info', 'Building the single-file sfx bundle for `' + moduleName + '`...');

    // trace the starting module
    var cfg = config.loader.getConfig();
    cfg.baseURL = path.relative(process.cwd(), config.pjson.baseURL);

    opts.config = cfg;
    if (!('lowResSourceMaps' in opts))
      opts.lowResSourceMaps = true;

    return systemBuilder.buildSFX(moduleName, fileName, opts);
  })
  .then(config.save)
  .then(function() {
    ui.log('ok', 'Built into `' + path.relative(process.cwd(), fileName) + '`' + (opts.sourceMaps ? ' with source maps' : '') + ', ' + (opts.minify ? '' : 'un') + 'minified' + ', ' + (opts.mangle ? '' : 'un') + 'mangled.');
  })
  .catch(function(e) {
    ui.log('err', e.stack || e);
    throw e;
  });
}

function removeExistingSourceMap(fileName) {
  return asp(fs.unlink)(fileName + '.map')
  .catch(function(e) {
    if (e.code == 'ENOENT')
      return;
    throw e;
  });
}
<|endoftext|>


<commit_before>var assert = require('assert'), crypto = require('crypto');
var DataReader = require('seccamp2015-data-reader').DataReader;

function incSeq(buf){
  var i;
  for(i=buf.length-1; i >= 0;i--){
    if(buf[i] < 0xff){
      buf[i]++;
      break;
    }
    buf[i] = 0x00;
  }
}

var type = {
  changecipherspec: 0x14,
  alert: 0x15,
  handshake: 0x16,
  application: 0x17
};

var handshake_type = {
  clienthello: 0x01,
  clientkeyexchange: 0x10,
  finished: 0x14
};

function writeVector(data, floor, ceiling) {
  assert(data.length >= floor);
  assert(ceiling >= data.length);
  var vector_length = Math.ceil(ceiling.toString(2).length/8);
  var length = new Buffer(vector_length);
  length.writeUIntBE(data.length, 0, vector_length);
  return Buffer.concat([length, data]);
}

exports.checkRecordHeader = checkRecordHeader;
function checkRecordHeader(reader) {
  if (5 > reader.bytesRemaining())
    return null;

  var length = reader.peekBytes(0, 5).readUIntBE(3, 2);
  if (length > reader.bytesRemaining())
    return null;

  return true;
}

function createRecord(type, data) {
  var header = new Buffer(5);
  header[0] = type;
  header[1] = 0x03;
  header[2] = 0x03;
  header.writeUIntBE(data.length, 3, 2);
  return Buffer.concat([header, data]);
}

function parseRecordHeader(reader) {
  assert(reader.bytesRemaining() >= 5);
  var type = reader.readBytes(1).readUInt8(0);
  var version = reader.readBytes(2);
  var length = reader.readBytes(2).readUIntBE(0, 2);
  return {type: type, version: version, length: length};
}

function createHandshake(type, data) {
  var header = new Buffer(4);
  header[0] = type;
  header.writeUIntBE(data.length, 1, 3);
  return Buffer.concat([header, data]);
}

exports.createClientHello = createClientHello;
function createClientHello(json, state) {
  state.handshake.clienthello_json = json;
  var version = json.version;
  var random = json.random;
  var session_id = writeVector(json.session_id, 0, 32);
  var cipher_suites = writeVector(Buffer.concat(json.cipher_suites), 2, 1 << 16 - 2);
  var compression = writeVector(json.compression, 0, 1 << 8 -1);
  var handshake = createHandshake(handshake_type.clienthello, Buffer.concat([version, random, session_id, cipher_suites, compression]));
  return createRecord(type.handshake, handshake);
}

exports.parseServerHello = parseServerHello;
function parseServerHello(reader, state) {
  if (!checkRecordHeader(reader))
    return null;

  var record_header = parseRecordHeader(reader);
  var handshake_msg_buf = reader.peekBytes(0, record_header.length);
  state.handshake_message_list.push(handshake_msg_buf);

  var type = reader.readBytes(1).readUInt8(0);
  var length = reader.readBytes(3).readUIntBE(0, 3);
  var version = reader.readBytes(2);
  var random = reader.readBytes(32);
  var session_id = reader.readVector(0, 32);
  var cipher = reader.readBytes(2);
  var compression = reader.readBytes(1);

  state.handshake.serverhello_json = {
    record_header: record_header,
    type: type,
    length: length,
    version: version,
    random: random,
    session_id: session_id,
    cipher: cipher,
    compression: compression
  };
  return true;
}

exports.parseCertificate = parseCertificate;
function parseCertificate(reader, state) {
  if (!checkRecordHeader(reader))
    return null;

  var record_header = parseRecordHeader(reader);
  var handshake_msg_buf = reader.peekBytes(0, record_header.length);
  state.handshake_message_list.push(handshake_msg_buf);

  var type = reader.readBytes(1).readUInt8(0);
  var length = reader.readBytes(3).readUIntBE(0, 3);
  var cert_reader = new DataReader(reader.readBytes(length));
  var certlist = [];
  while(cert_reader.bytesRemaining() > 0) {
    var cert = cert_reader.readVector(0, 1 << 24 - 1);
    certlist.push(cert);
  }

  state.handshake.certificate_json = {
    record_header: record_header,
    type: type,
    length: length,
    certlist: certlist
  };
  return true;
}

exports.parseServerHelloDone = parseServerHelloDone;
function parseServerHelloDone(reader, state) {
  if (!checkRecordHeader(reader))
    return null;

  var record_header = parseRecordHeader(reader);
  var handshake_msg_buf = reader.peekBytes(0, record_header.length);
  state.handshake_message_list.push(handshake_msg_buf);

  var type = reader.readBytes(1).readUInt8(0);
  var length = reader.readBytes(3).readUIntBE(0, 3);

  state.handshake.serverhellodone_json = {
    record_header: record_header,
    type: type,
    length: length,
    buf: handshake_msg_buf
  };
  return true;
}

exports.parseServerFinished = parseServerFinished;
function parseServerFinished(reader, state) {
  if (!checkRecordHeader(reader))
    return null;

  var record_header = parseRecordHeader(reader);
  var handshake_msg_buf = reader.peekBytes(0, record_header.length);
  var type = reader.readBytes(1).readUInt8(0);
  var length = reader.readBytes(3).readUIntBE(0, 3);
  var verify_data = reader.readBytes(length);

  var shasum = crypto.createHash('sha256');
  shasum.update(Buffer.concat(state.handshake_message_list));
  var message_hash = shasum.digest();

  state.handshake_message_list.push(handshake_msg_buf);

  var master_secret = state.keyblock_json.master_secret;
  var r = PRF12(master_secret, "server finished", message_hash, 12);

  assert(r.equals(verify_data));

  state.handshake.serverfinished_json = {
    record_header: record_header,
    type: type,
    length: length,
    verify_data: verify_data
  };
  return true;
}

exports.parseApplicationData = parseApplicationData;
function parseApplicationData(reader) {
  if (!checkRecordHeader(reader))
    return null;

  var record_header = parseRecordHeader(reader);
  var data = reader.readBytes(record_header.length);
  return {
    record_header: record_header,
    data: data
  };
}

exports.P_hash = P_hash;
function P_hash(algo, secret, seed, size) {
  var result = (new Buffer(size)).fill(0);
  var hmac = crypto.createHmac(algo, secret);
  hmac.update(seed);
  var a = hmac.digest(); // A(1)
  var j = 0;
  while(j < size) {
    hmac = crypto.createHmac(algo, secret);
    hmac.update(a);
    hmac.update(seed);
    var b = hmac.digest();
    var todo = b.length;
    if (j + todo > size) {
      todo = size -j;
    }
    b.copy(result, j, 0, todo);
    j += todo;

    hmac = crypto.createHmac(algo, secret);
    hmac.update(a);
    a = hmac.digest(); // A(i+1)
  }

  return result;
}

exports.PRF12 = PRF12;
function PRF12(secret, label, seed, size) {
  var newSeed = Buffer.concat([new Buffer(label), seed]);
  return P_hash('sha256', secret, newSeed, size);
}

exports.KDF = KDF;
function KDF(pre_master_secret, clienthello_json, serverhello_json) {
  var client_random = clienthello_json.random;
  var server_random = serverhello_json.random;
  var master_secret = PRF12(pre_master_secret, "master secret", Buffer.concat([client_random, server_random]), 48);
  var key_block_reader = new DataReader(   // 40bytes key_block for AES-128-GCM
    PRF12(master_secret, "key expansion", Buffer.concat([server_random, client_random]), 40));

  return {
    master_secret: master_secret,
    client_write_MAC_key: null,
    server_write_MAC_key: null,
    client_write_key: key_block_reader.readBytes(16),
    server_write_key: key_block_reader.readBytes(16),
    client_write_IV: key_block_reader.readBytes(4),
    server_write_IV: key_block_reader.readBytes(4)
  };
}

exports.createClientKeyExchange = createClientKeyExchange;
function createClientKeyExchange(json, state) {
  state.handshake.clientkeyexchange_json = json;
  var public_key = json.pubkey;
  var pre_master_secret = json.pre_master_secret;
  var encrypted = crypto.publicEncrypt({
    key: public_key,
    padding: require('constants').RSA_PKCS1_PADDING
  }, pre_master_secret);
  var encrypted_pre_master_secret = writeVector(encrypted, 0, 1 << 16 - 1);
  var handshake = createHandshake(handshake_type.clientkeyexchange, encrypted_pre_master_secret);
  return createRecord(type.handshake, handshake);
};

exports.createChangeCipherSpec = createChangeCipherSpec;
function createChangeCipherSpec() {
  return new Buffer('140303000101', 'hex');
};

exports.createClientFinished = createClientFinished;
function createClientFinished(json, state) {
  state.handshake.clientfinished_json = json;
  var shasum = crypto.createHash('sha256');
  shasum.update(Buffer.concat(json.handshake_message_list));
  var message_hash = shasum.digest();
  var r = PRF12(json.master_secret, "client finished", message_hash, 12);
  var handshake = createHandshake(handshake_type.finished, r);
  return createRecord(type.handshake, handshake);
}

exports.createApplicationData = createApplicationData;
function createApplicationData(data) {
  return createRecord(type.application, data);
}

exports.DecryptAEAD = DecryptAEAD;
function DecryptAEAD(reader, state) {
  if (!checkRecordHeader(reader))
    return null;

  var write_key = state.keyblock_json.server_write_key;
  var write_iv = state.keyblock_json.server_write_IV;

  var record_header_type_version = reader.peekBytes(0, 3);
  var record_header = parseRecordHeader(reader);
  var frame = reader.readBytes(record_header.length);
  var nonce_explicit = frame.slice(0, 8);
  var enc_data = frame.slice(8, frame.length - 16);
  var record_header_length = new Buffer(2);
  record_header_length.writeUInt16BE(enc_data.length);
  var record_header_buf = Buffer.concat([record_header_type_version, record_header_length]);
  var tag = frame.slice(-16);
  var iv = Buffer.concat([write_iv.slice(0,4), nonce_explicit]);
  var bob = crypto.createDecipheriv('aes-128-gcm', write_key, iv);
  bob.setAuthTag(tag);
  var aad = Buffer.concat([state.read_seq, record_header_buf]);
  bob.setAAD(aad);
  var clear = bob.update(enc_data);
  var length = new Buffer(2);
  length.writeUIntBE(clear.length, 0, 2);
  bob.final();
  incSeq(state.read_seq);
  var buf = reader.readBytes(reader.bytesRemaining());
  return new DataReader(Buffer.concat([record_header_type_version, length, clear, buf]));
}

exports.createPreMasterSecretRSAKeyExchange = createPreMasterSecretRSAKeyExchange;
function createPreMasterSecretRSAKeyExchange(state) {
  var pre_master_secret = Buffer.concat([state.handshake.clienthello_json.version, crypto.randomBytes(46)]);
  return pre_master_secret;
}

exports.createKeyBlock = createKeyBlock;
function createKeyBlock(pre_master_secret, state) {
  var keyblock_json = KDF(pre_master_secret, state.handshake.clienthello_json, state.handshake.serverhello_json);
  return keyblock_json;
}

exports.sendClientKeyExchange = sendClientKeyExchange;
function sendClientKeyExchange(state) {
  var pre_master_secret = createPreMasterSecretRSAKeyExchange(state);
  state.keyblock_json = createKeyBlock(pre_master_secret, state);
  var clientkeyexchange_json = {
    pre_master_secret: pre_master_secret,
    pubkey: require('fs').readFileSync(__dirname + '/pubkey.pem')
  };
  var clientkeyexchange = createClientKeyExchange(clientkeyexchange_json, state);
  sendTLSFrame(clientkeyexchange, state);
}

exports.sendChangeCipherSpec = sendChangeCipherSpec;
function sendChangeCipherSpec(state) {
  var changecipherspec = createChangeCipherSpec();
  sendTLSFrame(changecipherspec, state);
  state.send_encrypted = true;
}

exports.sendClientFinished = sendClientFinished;
function sendClientFinished(state) {
  var clientfinished_json = {
    master_secret: state.keyblock_json.master_secret,
    handshake_message_list: state.handshake_message_list
  };
  var clientfinished = createClientFinished(clientfinished_json, state);
  sendTLSFrame(clientfinished, state);
}

exports.sendClientFrame = sendClientFrame;
function sendClientFrame(state) {
  sendClientKeyExchange(state);
  sendChangeCipherSpec(state);
  state.send_encrypted = true;
  sendClientFinished(state);
}

exports.sendTLSFrame = sendTLSFrame;
function sendTLSFrame(frame, state) {
  if (frame[0] === type.handshake)
    state.handshake_message_list.push(frame.slice(5));

  if (state.send_encrypted)
    frame = EncryptAEAD(frame, state);

  state.socket.write(frame);
}

exports.EncryptAEAD = EncryptAEAD;
function EncryptAEAD(frame, state) {
  var iv = Buffer.concat([state.keyblock_json.client_write_IV.slice(0,4), state.nonce_explicit]);
  var bob = crypto.createCipheriv('aes-128-gcm', state.keyblock_json.client_write_key, iv);
  var record_header = frame.slice(0, 5);
  var aad = Buffer.concat([state.write_seq, record_header]);
  bob.setAAD(aad);
  var encrypted1 = bob.update(frame.slice(5));
  var encrypted2 = bob.final();
  var encrypted = Buffer.concat([encrypted1, encrypted2]);
  var tag = bob.getAuthTag(tag);
  incSeq(state.write_seq);
  record_header.writeUIntBE(state.nonce_explicit.length + encrypted.length + tag.length, 3, 2);
  return Buffer.concat([record_header, state.nonce_explicit, encrypted, tag]);
}
<commit_msg>add comments and refactoring<commit_after>var assert = require('assert'), crypto = require('crypto');
var DataReader = require('seccamp2015-data-reader').DataReader;

// Increment Number of Big Endian Bufffer
function incSeq(buf){
  var i;
  for(i=buf.length-1; i >= 0;i--){
    if(buf[i] < 0xff){
      buf[i]++;
      break;
    }
    buf[i] = 0x00;
  }
}

// TLS Content Type List
var type = {
  changecipherspec: 0x14,
  alert: 0x15,
  handshake: 0x16,
  application: 0x17
};

// TLS HandshakeType List
var handshake_type = {
  clienthello: 0x01,
  serverhello: 0x02,
  certificate: 0x0b,
  serverkeyexchange: 0x0c,
  serverhellodone: 0x0e,
  clientkeyexchange: 0x10,
  finished: 0x14
};

// write data to variable vector format with length field
function writeVector(data, floor, ceiling) {
  assert(data.length >= floor);
  assert(ceiling >= data.length);
  var vector_length = Math.ceil(ceiling.toString(2).length/8);
  var length = new Buffer(vector_length);
  length.writeUIntBE(data.length, 0, vector_length);
  return Buffer.concat([length, data]);
}

// check if reader has enough length to parse
exports.checkRecordHeader = checkRecordHeader;
function checkRecordHeader(reader) {
  if (5 > reader.bytesRemaining())
    return null;

  var length = reader.peekBytes(0, 5).readUIntBE(3, 2);
  if (length > reader.bytesRemaining())
    return null;

  return true;
}

// Create record buffer with adding header of TLS1.2
function createRecord(type, data) {
  var header = new Buffer(5);
  header[0] = type;
  header[1] = 0x03;
  header[2] = 0x03; // Explicitly used TLS1.2
  header.writeUIntBE(data.length, 3, 2);
  return Buffer.concat([header, data]);
}

// return json format of record header
function parseRecordHeader(reader) {
  assert(reader.bytesRemaining() >= 5);
  var type = reader.readBytes(1).readUInt8(0);
  var version = reader.readBytes(2);
  var length = reader.readBytes(2).readUIntBE(0, 2);
  return {type: type, version: version, length: length};
}

// add Handshake header in front of data
function createHandshake(type, data) {
  var header = new Buffer(4);
  header[0] = type;
  header.writeUIntBE(data.length, 1, 3);
  return Buffer.concat([header, data]);
}

exports.createClientHello = createClientHello;
function createClientHello(json, state) {
  state.handshake.clienthello_json = json;
  var version = json.version;
  var random = json.random;
  var session_id = writeVector(json.session_id, 0, 32);
  var cipher_suites = writeVector(Buffer.concat(json.cipher_suites), 2, 1 << 16 - 2);
  var compression = writeVector(json.compression, 0, 1 << 8 -1);
  var handshake = createHandshake(
    handshake_type.clienthello,
    Buffer.concat([version, random, session_id, cipher_suites, compression])
  );
  return createRecord(type.handshake, handshake);
}

function storeHandshakeData(reader, length, state) {
  // store handshake buffer data for session hash in Finished
  var handshake_msg_buf = reader.peekBytes(0, length);
  state.handshake_message_list.push(handshake_msg_buf);
}

exports.parseServerHello = parseServerHello;
function parseServerHello(reader, state) {
  if (!checkRecordHeader(reader))
    return null;

  var record_header = parseRecordHeader(reader);
  storeHandshakeData(reader, record_header.length, state);
  var type = reader.readBytes(1).readUInt8(0);
  var length = reader.readBytes(3).readUIntBE(0, 3);
  var version = reader.readBytes(2);
  var random = reader.readBytes(32);
  var session_id = reader.readVector(0, 32);
  var cipher = reader.readBytes(2);
  var compression = reader.readBytes(1);

  state.handshake.serverhello_json = {
    record_header: record_header,
    type: type,
    length: length,
    version: version,
    random: random,
    session_id: session_id,
    cipher: cipher,
    compression: compression
  };
  return true;
}

exports.parseCertificate = parseCertificate;
function parseCertificate(reader, state) {
  if (!checkRecordHeader(reader))
    return null;

  var record_header = parseRecordHeader(reader);
  storeHandshakeData(reader, record_header.length, state);

  var type = reader.readBytes(1).readUInt8(0);
  var length = reader.readBytes(3).readUIntBE(0, 3);
  var cert_reader = new DataReader(reader.readBytes(length));
  var certlist = [];
  while(cert_reader.bytesRemaining() > 0) {
    var cert = cert_reader.readVector(0, 1 << 24 - 1);
    certlist.push(cert);
  }

  state.handshake.certificate_json = {
    record_header: record_header,
    type: type,
    length: length,
    certlist: certlist
  };
  return true;
}

exports.parseServerHelloDone = parseServerHelloDone;
function parseServerHelloDone(reader, state) {
  if (!checkRecordHeader(reader))
    return null;

  var record_header = parseRecordHeader(reader);
  storeHandshakeData(reader, record_header.length, state);

  var type = reader.readBytes(1).readUInt8(0);
  var length = reader.readBytes(3).readUIntBE(0, 3);

  state.handshake.serverhellodone_json = {
    record_header: record_header,
    type: type,
    length: length
  };
  return true;
}

exports.parseServerFinished = parseServerFinished;
function parseServerFinished(reader, state) {
  if (!checkRecordHeader(reader))
    return null;

  var record_header = parseRecordHeader(reader);
  var handshake_msg_buf = reader.peekBytes(0, record_header.length);
  var type = reader.readBytes(1).readUInt8(0);
  var length = reader.readBytes(3).readUIntBE(0, 3);
  var verify_data = reader.readBytes(length);

  // Calculate session hash
  var shasum = crypto.createHash('sha256');
  shasum.update(Buffer.concat(state.handshake_message_list));
  var message_hash = shasum.digest();

  // Store Handshake data after hash caluclated
  state.handshake_message_list.push(handshake_msg_buf);

  var master_secret = state.keyblock_json.master_secret;

  // obtain session hash
  var r = PRF12(master_secret, "server finished", message_hash, 12);

  // Handshake integrity check
  assert(r.equals(verify_data));

  state.handshake.serverfinished_json = {
    record_header: record_header,
    type: type,
    length: length,
    verify_data: verify_data
  };
  return true;
}

exports.parseApplicationData = parseApplicationData;
function parseApplicationData(reader) {
  if (!checkRecordHeader(reader))
    return null;

  var record_header = parseRecordHeader(reader);
  var data = reader.readBytes(record_header.length);
  return {
    record_header: record_header,
    data: data
  };
}

// P_hash is defined in TLS1.2(RFC5246)
exports.P_hash = P_hash;
function P_hash(algo, secret, seed, size) {
  var result = (new Buffer(size)).fill(0);
  var hmac = crypto.createHmac(algo, secret);
  hmac.update(seed);
  var a = hmac.digest(); // A(1)
  var j = 0;
  while(j < size) {
    hmac = crypto.createHmac(algo, secret);
    hmac.update(a);
    hmac.update(seed);
    var b = hmac.digest();
    var todo = b.length;
    if (j + todo > size) {
      todo = size -j;
    }
    b.copy(result, j, 0, todo);
    j += todo;

    hmac = crypto.createHmac(algo, secret);
    hmac.update(a);
    a = hmac.digest(); // A(i+1)
  }

  return result;
}

// Pesuedo Random Function in TLS1.2 is sha256
exports.PRF12 = PRF12;
function PRF12(secret, label, seed, size) {
  var newSeed = Buffer.concat([new Buffer(label), seed]);
  return P_hash('sha256', secret, newSeed, size);
}

// Key Deviation Function for AES-128-GCM
exports.KDF = KDF;
function KDF(pre_master_secret, client_random, server_random) {
  var master_secret = PRF12(pre_master_secret, "master secret", Buffer.concat([client_random, server_random]), 48);
  // 40 bytes key_block for AES-128-GCM
  var key_block_reader = new DataReader(
    PRF12(master_secret, "key expansion", Buffer.concat([server_random, client_random]), 40));

  return {
    master_secret: master_secret,
    client_write_MAC_key: null,
    server_write_MAC_key: null,
    client_write_key: key_block_reader.readBytes(16),
    server_write_key: key_block_reader.readBytes(16),
    client_write_IV: key_block_reader.readBytes(4),
    server_write_IV: key_block_reader.readBytes(4)
  };
}

exports.createClientKeyExchange = createClientKeyExchange;
function createClientKeyExchange(json, state) {
  state.handshake.clientkeyexchange_json = json;
  var public_key = json.pubkey;
  var pre_master_secret = json.pre_master_secret;
  var encrypted = crypto.publicEncrypt({
    key: public_key,
    padding: require('constants').RSA_PKCS1_PADDING
  }, pre_master_secret);
  var encrypted_pre_master_secret = writeVector(encrypted, 0, 1 << 16 - 1);
  var handshake = createHandshake(handshake_type.clientkeyexchange, encrypted_pre_master_secret);
  return createRecord(type.handshake, handshake);
};

exports.createChangeCipherSpec = createChangeCipherSpec;
function createChangeCipherSpec() {
  return new Buffer('140303000101', 'hex');
};

exports.createClientFinished = createClientFinished;
function createClientFinished(json, state) {
  state.handshake.clientfinished_json = json;
  // create session hash
  var shasum = crypto.createHash('sha256');
  shasum.update(Buffer.concat(json.handshake_message_list));
  var message_hash = shasum.digest();
  var r = PRF12(json.master_secret, "client finished", message_hash, 12);
  var handshake = createHandshake(handshake_type.finished, r);
  return createRecord(type.handshake, handshake);
}

exports.createApplicationData = createApplicationData;
function createApplicationData(data) {
  return createRecord(type.application, data);
}

// pre_master_secret used only in RSA KeyExchange
exports.createPreMasterSecretRSAKeyExchange = createPreMasterSecretRSAKeyExchange;
function createPreMasterSecretRSAKeyExchange(version) {
  var pre_master_secret = Buffer.concat([version, crypto.randomBytes(46)]);
  return pre_master_secret;
}

exports.createKeyBlock = createKeyBlock;
function createKeyBlock(pre_master_secret, client_random, server_random) {
  var keyblock_json = KDF(pre_master_secret, client_random, server_random);
  return keyblock_json;
}

exports.sendClientKeyExchange = sendClientKeyExchange;
function sendClientKeyExchange(state) {
  var version = state.handshake.clienthello_json.version;
  var client_random = state.handshake.clienthello_json.random;
  var server_random = state.handshake.serverhello_json.random;
  var pre_master_secret = createPreMasterSecretRSAKeyExchange(version);
  state.keyblock_json = createKeyBlock(pre_master_secret, client_random, server_random);
  var clientkeyexchange_json = {
    pre_master_secret: pre_master_secret,
    pubkey: require('fs').readFileSync(__dirname + '/pubkey.pem')
  };
  var clientkeyexchange = createClientKeyExchange(clientkeyexchange_json, state);
  sendTLSFrame(clientkeyexchange, state);
}

exports.sendChangeCipherSpec = sendChangeCipherSpec;
function sendChangeCipherSpec(state) {
  var changecipherspec = createChangeCipherSpec();
  sendTLSFrame(changecipherspec, state);
  // After sending ChangeCipherSpec, sending frame is always encrypted.
  state.send_encrypted = true;
}

exports.sendClientFinished = sendClientFinished;
function sendClientFinished(state) {
  var clientfinished_json = {
    master_secret: state.keyblock_json.master_secret,
    handshake_message_list: state.handshake_message_list
  };
  var clientfinished = createClientFinished(clientfinished_json, state);
  sendTLSFrame(clientfinished, state);
}

// DeCipher that supports only aes-128-gcm
exports.Decrypt = Decrypt;
function Decrypt(encrypted, aad, key, iv, tag) {
  var decipher = crypto.createDecipheriv('aes-128-gcm', key, iv);
  decipher.setAuthTag(tag);
  decipher.setAAD(aad);
  var clear = decipher.update(encrypted);
  decipher.final();
  return clear;
}

exports.DecryptAEAD = DecryptAEAD;
function DecryptAEAD(reader, state) {
  if (!checkRecordHeader(reader))
    return null;

  var record_header = reader.readBytes(5);
  var length = record_header.readUIntBE(3, 2);
  var frame = reader.readBytes(length);
  var nonce_explicit = frame.slice(0, 8);
  var encrypted = frame.slice(8, frame.length - 16);
  var tag = frame.slice(-16);

  record_header.writeUIntBE(encrypted.length, 3, 2);

  var write_key = state.keyblock_json.server_write_key;
  var write_iv = state.keyblock_json.server_write_IV;
  var iv = Buffer.concat([write_iv.slice(0,4), nonce_explicit]);
  var aad = Buffer.concat([state.read_seq, record_header]);
  var clear = Decrypt(encrypted, aad, write_key, iv, tag);

  // re-calculate length with removing nonce_explict and tag length
  record_header.writeUIntBE(clear.length, 3, 2);

  // increment read sequence number
  incSeq(state.read_seq);

  return new DataReader(Buffer.concat(
    [record_header, clear, reader.readBytes(reader.bytesRemaining())]
  ));
}

// Cipher that supports only aes-128-gcm
exports.Encrypt = Encrypt;
function Encrypt(plain, key, iv, aad) {
  var cipher = crypto.createCipheriv('aes-128-gcm', key, iv);
  cipher.setAAD(aad);
  var encrypted1 = cipher.update(plain);
  var encrypted2 = cipher.final();
  var encrypted = Buffer.concat([encrypted1, encrypted2]);
  var tag = cipher.getAuthTag(tag);
  return {encrypted: encrypted, tag: tag};
}

exports.EncryptAEAD = EncryptAEAD;
function EncryptAEAD(frame, state) {
  var key = state.keyblock_json.client_write_key;
  var iv = Buffer.concat([state.keyblock_json.client_write_IV.slice(0,4), state.nonce_explicit]);
  var record_header = frame.slice(0, 5);
  var aad = Buffer.concat([state.write_seq, record_header]);

  var ret = Encrypt(frame.slice(5), key, iv, aad);
  var encrypted = ret.encrypted;
  var tag = ret.tag;

  // re-calcuate length with adding nonce_explit and tag length
  var length = state.nonce_explicit.length + encrypted.length + tag.length;
  record_header.writeUIntBE(length, 3, 2);

  // increment write sequence number
  incSeq(state.write_seq);
  return Buffer.concat([record_header, state.nonce_explicit, encrypted, tag]);
}

exports.sendTLSFrame = sendTLSFrame;
function sendTLSFrame(frame, state) {
  if (frame[0] === type.handshake)
    state.handshake_message_list.push(frame.slice(5));

  if (state.send_encrypted)
    frame = EncryptAEAD(frame, state);

  state.socket.write(frame);
}

// send all client's Handshake after receiving Certificate
exports.sendClientFrame = sendClientFrame;
function sendClientFrame(state) {
  sendClientKeyExchange(state);
  sendChangeCipherSpec(state);
  sendClientFinished(state);
}
<|endoftext|>


<commit_before>'use strict';

const fs = require('fs');

/**
 * Bundles the given Swagger API
 *
 * @param {string} api - The path of the Swagger API file
 * @param {{outfile: string, dereference: boolean, format: number}} options
 * @param {function} [cb]
 * @returns {Promise}
 */
module.exports = function bundle (api, options, cb) {
  // NOTE: lazy-load SwaggerParser, so the CLI can set the env.DEBUG variable first
  var SwaggerParser = require('swagger-parser');  // eslint-disable-line

  // Determine which Swagger Parser method to use
  let method = options.dereference ? 'dereference' : 'bundle';

  // If the API contains circular $refs, then ignore them.
  // Otherwise, JSON serialization will fail
  let opts = { $refs: { circular: 'ignore' }};

  // Normalize the callback function
  if (typeof cb !== 'function') {
    cb = function () {};
  }

  return SwaggerParser[method](api, opts)
    .then(api => {
      // Serialize the bundled/dereferenced API as JSON
      let json = toJSON(api, options.format);

      if (options.outfile) {
        // Write the JSON to the output file
        fs.writeFileSync(options.outfile, json);
      }

      cb(null, json);
      return json;
    })
    .catch((err) => {
      cb(err, null);
      throw err;
    });
};

/**
 * Serializes the given API as JSON, using the given spaces for formatting.
 *
 * @param {object} api
 * @param {string|number} spaces
 */
function toJSON (api, spaces) {
  let strSpaces = spaces;
  let numSpaces = parseInt(spaces);
  if (isNaN(numSpaces)) {
    spaces = strSpaces || 2;
  }
  else {
    spaces = numSpaces;
  }

  return JSON.stringify(api, null, spaces) + '\n';
}
<commit_msg>create the output directory, if necessary<commit_after>'use strict';

const fs = require('fs');
const path = require('path');
const mkdirp = require('mkdirp');

/**
 * Bundles the given Swagger API
 *
 * @param {string} api - The path of the Swagger API file
 * @param {{outfile: string, dereference: boolean, format: number}} options
 * @param {function} [cb]
 * @returns {Promise}
 */
module.exports = function bundle (api, options, cb) {
  // NOTE: lazy-load SwaggerParser, so the CLI can set the env.DEBUG variable first
  var SwaggerParser = require('swagger-parser');  // eslint-disable-line

  // Determine which Swagger Parser method to use
  let method = options.dereference ? 'dereference' : 'bundle';

  // If the API contains circular $refs, then ignore them.
  // Otherwise, JSON serialization will fail
  let opts = { $refs: { circular: 'ignore' }};

  // Normalize the callback function
  if (typeof cb !== 'function') {
    cb = function () {};
  }

  return SwaggerParser[method](api, opts)
    .then(bundled => {
      // Serialize the bundled/dereferenced API as JSON
      let json = toJSON(bundled, options.format);

      if (options.outfile) {
        // Create the output directory, if necessary
        mkdirp.sync(path.dirname(options.outfile));

        // Write the JSON to the output file
        fs.writeFileSync(options.outfile, json);
      }

      cb(null, json);
      return json;
    })
    .catch((err) => {
      cb(err, null);
      throw err;
    });
};

/**
 * Serializes the given API as JSON, using the given spaces for formatting.
 *
 * @param {object} api
 * @param {string|number} spaces
 */
function toJSON (api, spaces) {
  let strSpaces = spaces;
  let numSpaces = parseInt(spaces);
  if (isNaN(numSpaces)) {
    spaces = strSpaces || 2;
  }
  else {
    spaces = numSpaces;
  }

  return JSON.stringify(api, null, spaces) + '\n';
}
<|endoftext|>


<commit_before>'use strict'

const extend = require('deep-extend')
const Connection = require('./lib/connection.js')
const EventEmitter = require('event-emitter-es6')

class Blitz extends EventEmitter {

    /**
     * Merge default options with client options
     */
    constructor(options) {
        super()

        this.options = extend({

            // Resource Config
            api_url: "http://localhost:3010/",
            auth_url: "http://localhost:3030/",

            // Connection Config
            use_socket: true,
            namespace: '',

            // Authorization Config
            user_key: null,
            user_secret: null,
            ignore_limiter: false
        }, options)

        // Add "/" to url if not existing
        if (this.options.api_url.slice(-1) !== "/") this.options.api_url += "/"
        if (this.options.auth_url.slice(-1) !== "/") this.options.auth_url += "/"

        // Establish connection to resource server w/ options
        this.connection = new Connection()
        this.connection.setup(this.options)

            // Open up client to higher level
            .then(() => {
                if (this.options.use_socket) this.client = this.connection.client.socket
            })
            .then(() => this.emit('ready'))
    }

    /**
     * RESTful methods for manual interaction
     */
    query(verb, query) {
        return new Promise((resolve, reject) => {
            if (query[0] === "/") query = query.slice(1, query.length)

            this.connection.request(verb, this.options.api_url + query)
                .then(res => resolve(res))
                .catch(err => {
                    throw (new Error(err))
                })
        })
    }

    get(query) {
        return new Promise((resolve, reject) => {
            this.query("GET", query).then((res) => resolve(res))
        })
    }

    post(query) {
        return new Promise((resolve, reject) => {
            this.query("POST", query).then((res) => resolve(res))
        })
    }

    put(query) {
        return new Promise((resolve, reject) => {
            this.query("PUT", query).then((res) => resolve(res))
        })
    }

    delete(query) {
        return new Promise((resolve, reject) => {
            this.query("DELETE", query).then((res) => resolve(res))
        })
    }
}

module.exports = Blitz
<commit_msg>fixed new url format for post/put<commit_after>'use strict'

const extend = require('deep-extend')
const Connection = require('./lib/connection.js')
const EventEmitter = require('event-emitter-es6')

class Blitz extends EventEmitter {

    /**
     * Merge default options with client options
     */
    constructor(options) {
        super()

        this.options = extend({

            // Resource Config
            api_url: "http://localhost:3010/",
            auth_url: "http://localhost:3030/",

            // Connection Config
            use_socket: true,
            namespace: '',

            // Authorization Config
            user_key: null,
            user_secret: null,
            ignore_limiter: false
        }, options)

        // Add "/" to url if not existing
        if (this.options.api_url.slice(-1) !== "/") this.options.api_url += "/"
        if (this.options.auth_url.slice(-1) !== "/") this.options.auth_url += "/"

        // Establish connection to resource server w/ options
        this.connection = new Connection()
        this.connection.setup(this.options)

            // Open up client to higher level
            .then(() => {
                if (this.options.use_socket) this.client = this.connection.client.socket
            })
            .then(() => this.emit('ready'))
    }

    /**
     * RESTful methods for manual interaction
     */
    query(verb, query) {
        return new Promise((resolve, reject) => {

            // Get proper URL from strings & objects (see post requests)
            if (typeof query === "string") {
                if(query[0] === "/") query = query.slice(1, query.length)
                query = this.options.api_url + query
            }

            // Object as query
            else {
                if(query.url[0] === "/") {
                   query.url = query.url.slice(1, query.url.length)
                }
                query.url = this.options.api_url + query.url
            }

            // Let connection handle request
            this.connection.request(verb, query)
                .then(res => resolve(res))
                .catch(err => {
                    throw (new Error(err))
                })
        })
    }

    get(query) {
        return new Promise((resolve, reject) => {
            this.query("GET", query).then((res) => resolve(res))
        })
    }

    post(query) {
        return new Promise((resolve, reject) => {
            this.query("POST", query).then((res) => resolve(res))
        })
    }

    put(query) {
        return new Promise((resolve, reject) => {
            this.query("PUT", query).then((res) => resolve(res))
        })
    }

    delete(query) {
        return new Promise((resolve, reject) => {
            this.query("DELETE", query).then((res) => resolve(res))
        })
    }
}

module.exports = Blitz
<|endoftext|>


<commit_before>function Camera(mainCanvas)
{
	var view_scale = {x:1, y:1};
	var view_bias = {x:0, y:0};
	var view_position = {x:0, y:0};
	var view_unitScale = 1;

	var activeLayer;
	var activeCanvas;
	var activeGraphics;
	var canvas = [undefined, mainCanvas, undefined];
	var graphics = [undefined, undefined, undefined];
	var canvasW = mainCanvas.width;
	var canvasH = mainCanvas.height;
	var canvasAspect = canvasW / canvasH;
	var clientRect;

	var lines = [];

	function init()
	{
		canvas[0] = document.createElement('canvas');
		canvas[0].width = mainCanvas.width;
		canvas[0].height = mainCanvas.height;
		canvas[0].style.position = mainCanvas.style.position;
		canvas[0].style.left = mainCanvas.style.left;
		canvas[0].style.top = mainCanvas.style.top;
		canvas[0].style.zIndex = mainCanvas.style.zIndex-2;
		canvas[0].style.cursor = "none";
		canvas[0].tabIndex = -1;
		if (mainCanvas.parentNode != undefined)
		{
			mainCanvas.parentNode.insertBefore(canvas[0], mainCanvas);
		}

		canvas[1] = document.createElement('canvas');
		canvas[1].width = mainCanvas.width;
		canvas[1].height = mainCanvas.height;
		canvas[1].style.position = mainCanvas.style.position;
		canvas[1].style.left = mainCanvas.style.left;
		canvas[1].style.top = mainCanvas.style.top;
		canvas[1].style.zIndex = mainCanvas.style.zIndex-1;
		canvas[1].style.cursor = "none";
		canvas[1].tabIndex = -1;
		if (mainCanvas.parentNode != undefined)
		{
			mainCanvas.parentNode.insertBefore(canvas[1], mainCanvas);
		}

		canvas[2] = mainCanvas;

		graphics = [new Graphics(canvas[0]), new Graphics(canvas[1]), new Graphics(canvas[2])];

		setLayer(1);
		setViewPosition(0, 0);
		setUnitScale(96/2.54 * 1.25 * 0.5);
	}

	function setLayer(index)
	{
		if (activeLayer != index)
		{
			activeLayer = index;
			activeCanvas = canvas[index];
			activeGraphics = graphics[index];
			clientRect = activeCanvas.getBoundingClientRect();
		}
	}

	this.onResize = function()
	{
		canvasW = activeCanvas.width;
		canvasH = activeCanvas.height;
		canvasAspect = canvasW / canvasH;

		activeGraphics.onResize();
	}

	function saveAsJavascript()
	{
		var str = "";

		str += "camera.setViewPosition(" + view_position.x + ", " + view_position.y + ");"
		str += "camera.setUnitScale(" + view_unitScale + ");"

		return str;
	}

	function updateProjection()
	{
		view_scale.x = view_unitScale;
		view_scale.y = -view_unitScale;
		view_bias.x = -view_position.x * view_scale.x + canvasW/2;
		view_bias.y = canvasH/2 - view_position.y * view_scale.y;
	}

	function setViewPosition(x,y)
	{
		view_position.x = x;
		view_position.y = y;
		updateProjection();
	}

	function setUnitScale(s)
	{
		view_unitScale = Math.max(1,s);
		updateProjection();
	}

	function transformP(v)
	{
		return mad(v, view_scale, view_bias);
	}

	function transformN(v)
	{
		return mul(v, view_scale);
	}

	function invTransformP(v)
	{
		return div(sub(v, view_bias), view_scale);
	}

	function scale(v)
	{
		return mul(v,view_unitScale)
	}

	function invScale(v)
	{
		return div(v,view_unitScale)
	}

	this.clear = function(color)
	{
		activeGraphics.clear(color);

		if (activeLayer == 1)
		{
			lines = [];
		}
	}

	function drawLine(a,b,color,width,dash,ownerObject)
	{
		if (ownerObject !== undefined)
		{
			lines.push({a:a, b:b, object:ownerObject});
		}

		return activeGraphics.drawLine(transformP(a), transformP(b), color, width, dash);
	}

	function drawBezierCurve(a,ca,cb,b,color,width,dash,ownerObject)
	{
		//if (ownerObject !== undefined)
		//{
		//	lines.push({a:a, b:b, object:ownerObject});
		//}

		return activeGraphics.drawBezierCurve(transformP(a), transformP(ca), transformP(cb), transformP(b), color, width, dash);
	}

	function drawLineStrip(points, closed, color, width, dash, fillColor, ownerObject)
	{
		if (ownerObject !== undefined)
		{
			for (var i = 0; i < points.length - 1; ++i)
			{
				lines.push({a:points[i], b:points[i+1], object:ownerObject});
			}
		}

		var pixelPoints = [];

		for (var i = 0; i < points.length; ++i)
		{
			pixelPoints.push(transformP(points[i]));
		}

		return activeGraphics.drawLineStrip(pixelPoints, closed, color, width, dash, fillColor);
	}

	function drawLines(points, color, width, dash, fillColor, ownerObject)
	{
		if (ownerObject !== undefined)
		{
			for (var i = 0; i < points.length - 1; i+=2)
			{
				lines.push({a:points[i], b:points[i+1], object:ownerObject});
			}
		}

		var pixelPoints = [];

		for (var i = 0; i < points.length; ++i)
		{
			pixelPoints.push(transformP(points[i]));
		}

		return activeGraphics.drawLines(pixelPoints, color, width, dash, fillColor);
	}

	function drawCross(p, size, rotation, color, width, dash)
	{
		return activeGraphics.drawCross(transformP(p), scale(size), rotation, color, width, dash);
	}

	function drawRectangle(a, b, color, width, dash, fillColor)
	{
		if ( (typeof(b) !== "object") )
			return activeGraphics.drawRectangle(transformP(a), scale(b), color, width, dash, fillColor);
		else
			return activeGraphics.drawRectangle(transformP(a), transformP(b), color, width, dash, fillColor);
	}

	function drawArrow(pStart,pEnd,color,width,dash,ownerObject)
	{
		if (ownerObject !== undefined)
		{
			lines.push({a:pStart, b:pEnd, object:ownerObject});
		}

		return activeGraphics.drawArrow(transformP(pStart), transformP(pEnd), color, width, dash);
	}

	function drawText(O,text,color,align, angle, font)
	{
		return activeGraphics.drawText(transformP(O),text,color,align,angle,font);
	}

	function drawHemisphere(O, N, radius, lineColor, lineWidth, fillColor, lineDash)
	{
		return activeGraphics.drawHemisphere(transformP(O), transformN(N), scale(radius), lineColor, lineWidth, fillColor, lineDash);
	}

	function drawArc(O, radius, startAngle, endAngle, lineColor, lineWidth, fillColor, lineDash)
	{
		return activeGraphics.drawArc(transformP(O), scale(radius), -startAngle, -endAngle, lineColor, lineWidth, fillColor, lineDash);
	}

	function drawBRDFGraph(BRDF, V, N, F0, roughness, O, radius, lineColor, lineWidth, fillColor, lineDash)
	{
		if (typeof(radius) === "undefined")
			radius = 1;

		return activeGraphics.drawBRDFGraph(BRDF, transformN(V), transformN(N), F0, roughness, transformP(O), scale(radius), lineColor, lineWidth, fillColor, lineDash);
	}

	function drawStar(O, pointCount, outerRadius, innerRadiusFactor, rotationAngle, lineColor, lineWidth, fillColor)
	{
		return activeGraphics.drawStar(transformP(O), pointCount, scale(outerRadius), innerRadiusFactor, rotationAngle, lineColor, lineWidth, fillColor);
	}

	function drawLight(O,radius)
	{
		return activeGraphics.drawLight(transformP(O), scale(radius));
	}

	function getMousePos(evt)
	{
		var p= 	{
					x: (evt.clientX - clientRect.left),
					y: (evt.clientY - clientRect.top)
				};

		return invTransformP(p);
	}

	function getSnapPoints(mousePoint, previousMousePos, mouseDir)
	{
		var points = [];

		for (var i=0; i!=lines.length; ++i)
		{
			var A = lines[i].a;
			var B = lines[i].b;

			var threshold = invScale(10);

			if (	mousePoint.x > Math.max(A.x, B.x)+threshold ||
					mousePoint.x < Math.min(A.x, B.x)-threshold ||
					mousePoint.y > Math.max(A.y, B.y)+threshold ||
					mousePoint.y < Math.min(A.y, B.y)-threshold)
			{
				continue;
			}

			var AB = sub(B, A);
			var tan = transpose(AB).unit();

			if (mousePoint !== undefined)
			{
				var AP = sub(mousePoint, A);

				var d = dot(AP, AB.unit());
				var t = dot(AP, tan);

				if (d>=0 && d<=AB.length())
				{
					var I = mad(AB.unit(), d, A);
					points.push({ type: "coincident", p: I, N: tan.neg(), objects: [lines[i].object] });
				}

				if (previousMousePos !== undefined && Math.abs(t)<=this.invScale(30))
				{
					var AP0 = sub(previousMousePos, A);
					var d0 = dot(AP0, AB.unit());
					var t0 = dot(AP0, tan);
					var I = mad(AB.unit(), d0, A);

					var testI = mad(AB.unit(), d, A);

					var perp = { type: "perpendicular", p: I, testP: testI, objects: [lines[i].object] };

					if (t0>=0)		perp.N = tan;
					else			perp.N = tan.neg();

							if (d < 0)			perp.p0 = A;
					else	if (d>AB.length())	perp.p0 = B;
					else						perp.p0 = I;

					points.push(perp);
				}

				if (	previousMousePos !== undefined &&
						mouseDir !== undefined &&
						d >= 0 && d <= AB.length() &&
						//dot(mouseDir, tan)<0 &&
						Math.abs(t) <= this.invScale(30))
				{
					var intersection = intersectRayRay(previousMousePos, mouseDir, A, AB.unit());

					if (intersection.hit)
					{
						var testI = mad(AB.unit(), d, A);

						var extendPoint = { type: "extendTo", p: intersection.P.copy(), testP: testI, objects: [lines[i].object] };

								if (intersection.tLine <= 0)		extendPoint.p0 = A;
						else	if (intersection.tLine >= 1)		extendPoint.p0 = B;
						else										extendPoint.p0 = testI;

						points.push(extendPoint);
					}
				}
			}

			for (var j = i + 1; j < lines.length; ++j)
			{
				if (lines[i].object == lines[j].object)
					continue;

				var C = lines[j].a;
				var D = lines[j].b;

				var AC = sub(C, A);
				var CD = sub(D, C);
				var d = -dot(AC, tan);
				var CD_along_tan = dot(CD.unit(), tan);
				var CD_factor = d / CD_along_tan;

				var I = add(C, mul(CD.unit(), CD_factor));

				var dAB = dot(sub(I, A), AB.unit());
				var dCD = dot(sub(I, C), CD.unit());

				if (dAB < 0 || dCD < 0 || dAB > distance(A, B) || dCD > distance(C, D))
					continue;

				points.push({type:"intersection", p:I, objects:[lines[i].object, lines[j].object] });
			}
		}

		return points;
	}

	function measureText(text, font)
	{
		var p = activeGraphics.measureText(text, font);

		return div(p, view_unitScale);
	}

	this.getUnitScale	 	= function() { return view_unitScale; 	}
	this.getViewBias		= function() { return view_bias; 	 	}
	this.getViewPosition	= function() { return view_position; 	}
	this.getViewSize		= function() { return new Vector(canvasW/view_unitScale, canvasH/view_unitScale); }
	this.getCanvas			= function() { return activeCanvas;		}
	this.getGraphics		= function() { return activeGraphics;	}

	this.setViewPosition	= setViewPosition;
	this.setUnitScale		= setUnitScale;
	this.setLayer			= setLayer;

	this.saveAsJavascript	= saveAsJavascript;
	this.transformP			= transformP;
	this.transformN			= transformN;
	this.scale				= scale;
	this.invScale			= invScale;
	this.invTransformP		= invTransformP;
	this.drawText 			= drawText;
	this.drawArrow 			= drawArrow;
	this.drawLine			= drawLine;
	this.drawBezierCurve	= drawBezierCurve;
	this.drawLines			= drawLines;
	this.drawLineStrip		= drawLineStrip;
	this.drawCross 			= drawCross;
	this.drawRectangle		= drawRectangle;
	this.drawHemisphere 	= drawHemisphere;
	this.drawArc			= drawArc;
	this.drawBRDFGraph		= drawBRDFGraph;
	this.drawLight			= drawLight;
	this.drawStar			= drawStar;
	this.getMousePos		= getMousePos;
	this.getSnapPoints		= getSnapPoints;
	this.measureText		= measureText;

	init();
}
<commit_msg>Fixed zoom position being wrong when canvas is only partially visible<commit_after>function Camera(mainCanvas)
{
	var view_scale = {x:1, y:1};
	var view_bias = {x:0, y:0};
	var view_position = {x:0, y:0};
	var view_unitScale = 1;

	var activeLayer;
	var activeCanvas;
	var activeGraphics;
	var canvas = [undefined, mainCanvas, undefined];
	var graphics = [undefined, undefined, undefined];
	var canvasW = mainCanvas.width;
	var canvasH = mainCanvas.height;
	var canvasAspect = canvasW / canvasH;

	var lines = [];

	function init()
	{
		canvas[0] = document.createElement('canvas');
		canvas[0].width = mainCanvas.width;
		canvas[0].height = mainCanvas.height;
		canvas[0].style.position = mainCanvas.style.position;
		canvas[0].style.left = mainCanvas.style.left;
		canvas[0].style.top = mainCanvas.style.top;
		canvas[0].style.zIndex = mainCanvas.style.zIndex-2;
		canvas[0].style.cursor = "none";
		canvas[0].tabIndex = -1;
		if (mainCanvas.parentNode != undefined)
		{
			mainCanvas.parentNode.insertBefore(canvas[0], mainCanvas);
		}

		canvas[1] = document.createElement('canvas');
		canvas[1].width = mainCanvas.width;
		canvas[1].height = mainCanvas.height;
		canvas[1].style.position = mainCanvas.style.position;
		canvas[1].style.left = mainCanvas.style.left;
		canvas[1].style.top = mainCanvas.style.top;
		canvas[1].style.zIndex = mainCanvas.style.zIndex-1;
		canvas[1].style.cursor = "none";
		canvas[1].tabIndex = -1;
		if (mainCanvas.parentNode != undefined)
		{
			mainCanvas.parentNode.insertBefore(canvas[1], mainCanvas);
		}

		canvas[2] = mainCanvas;

		graphics = [new Graphics(canvas[0]), new Graphics(canvas[1]), new Graphics(canvas[2])];

		setLayer(1);
		setViewPosition(0, 0);
		setUnitScale(96/2.54 * 1.25 * 0.5);
	}

	function setLayer(index)
	{
		if (activeLayer != index)
		{
			activeLayer = index;
			activeCanvas = canvas[index];
			activeGraphics = graphics[index];
		}
	}

	this.onResize = function()
	{
		canvasW = activeCanvas.width;
		canvasH = activeCanvas.height;
		canvasAspect = canvasW / canvasH;

		activeGraphics.onResize();
	}

	function saveAsJavascript()
	{
		var str = "";

		str += "camera.setViewPosition(" + view_position.x + ", " + view_position.y + ");"
		str += "camera.setUnitScale(" + view_unitScale + ");"

		return str;
	}

	function updateProjection()
	{
		view_scale.x = view_unitScale;
		view_scale.y = -view_unitScale;
		view_bias.x = -view_position.x * view_scale.x + canvasW/2;
		view_bias.y = canvasH/2 - view_position.y * view_scale.y;
	}

	function setViewPosition(x,y)
	{
		view_position.x = x;
		view_position.y = y;
		updateProjection();
	}

	function setUnitScale(s)
	{
		view_unitScale = Math.max(1,s);
		updateProjection();
	}

	function transformP(v)
	{
		return mad(v, view_scale, view_bias);
	}

	function transformN(v)
	{
		return mul(v, view_scale);
	}

	function invTransformP(v)
	{
		return div(sub(v, view_bias), view_scale);
	}

	function scale(v)
	{
		return mul(v,view_unitScale)
	}

	function invScale(v)
	{
		return div(v,view_unitScale)
	}

	this.clear = function(color)
	{
		activeGraphics.clear(color);

		if (activeLayer == 1)
		{
			lines = [];
		}
	}

	function drawLine(a,b,color,width,dash,ownerObject)
	{
		if (ownerObject !== undefined)
		{
			lines.push({a:a, b:b, object:ownerObject});
		}

		return activeGraphics.drawLine(transformP(a), transformP(b), color, width, dash);
	}

	function drawBezierCurve(a,ca,cb,b,color,width,dash,ownerObject)
	{
		//if (ownerObject !== undefined)
		//{
		//	lines.push({a:a, b:b, object:ownerObject});
		//}

		return activeGraphics.drawBezierCurve(transformP(a), transformP(ca), transformP(cb), transformP(b), color, width, dash);
	}

	function drawLineStrip(points, closed, color, width, dash, fillColor, ownerObject)
	{
		if (ownerObject !== undefined)
		{
			for (var i = 0; i < points.length - 1; ++i)
			{
				lines.push({a:points[i], b:points[i+1], object:ownerObject});
			}
		}

		var pixelPoints = [];

		for (var i = 0; i < points.length; ++i)
		{
			pixelPoints.push(transformP(points[i]));
		}

		return activeGraphics.drawLineStrip(pixelPoints, closed, color, width, dash, fillColor);
	}

	function drawLines(points, color, width, dash, fillColor, ownerObject)
	{
		if (ownerObject !== undefined)
		{
			for (var i = 0; i < points.length - 1; i+=2)
			{
				lines.push({a:points[i], b:points[i+1], object:ownerObject});
			}
		}

		var pixelPoints = [];

		for (var i = 0; i < points.length; ++i)
		{
			pixelPoints.push(transformP(points[i]));
		}

		return activeGraphics.drawLines(pixelPoints, color, width, dash, fillColor);
	}

	function drawCross(p, size, rotation, color, width, dash)
	{
		return activeGraphics.drawCross(transformP(p), scale(size), rotation, color, width, dash);
	}

	function drawRectangle(a, b, color, width, dash, fillColor)
	{
		if ( (typeof(b) !== "object") )
			return activeGraphics.drawRectangle(transformP(a), scale(b), color, width, dash, fillColor);
		else
			return activeGraphics.drawRectangle(transformP(a), transformP(b), color, width, dash, fillColor);
	}

	function drawArrow(pStart,pEnd,color,width,dash,ownerObject)
	{
		if (ownerObject !== undefined)
		{
			lines.push({a:pStart, b:pEnd, object:ownerObject});
		}

		return activeGraphics.drawArrow(transformP(pStart), transformP(pEnd), color, width, dash);
	}

	function drawText(O,text,color,align, angle, font)
	{
		return activeGraphics.drawText(transformP(O),text,color,align,angle,font);
	}

	function drawHemisphere(O, N, radius, lineColor, lineWidth, fillColor, lineDash)
	{
		return activeGraphics.drawHemisphere(transformP(O), transformN(N), scale(radius), lineColor, lineWidth, fillColor, lineDash);
	}

	function drawArc(O, radius, startAngle, endAngle, lineColor, lineWidth, fillColor, lineDash)
	{
		return activeGraphics.drawArc(transformP(O), scale(radius), -startAngle, -endAngle, lineColor, lineWidth, fillColor, lineDash);
	}

	function drawBRDFGraph(BRDF, V, N, F0, roughness, O, radius, lineColor, lineWidth, fillColor, lineDash)
	{
		if (typeof(radius) === "undefined")
			radius = 1;

		return activeGraphics.drawBRDFGraph(BRDF, transformN(V), transformN(N), F0, roughness, transformP(O), scale(radius), lineColor, lineWidth, fillColor, lineDash);
	}

	function drawStar(O, pointCount, outerRadius, innerRadiusFactor, rotationAngle, lineColor, lineWidth, fillColor)
	{
		return activeGraphics.drawStar(transformP(O), pointCount, scale(outerRadius), innerRadiusFactor, rotationAngle, lineColor, lineWidth, fillColor);
	}

	function drawLight(O,radius)
	{
		return activeGraphics.drawLight(transformP(O), scale(radius));
	}

	function getMousePos(evt)
	{
		var clientRect = activeCanvas.getBoundingClientRect();

		var p= 	{
					x: (evt.clientX - clientRect.left),
					y: (evt.clientY - clientRect.top)
				};

		return invTransformP(p);
	}

	function getSnapPoints(mousePoint, previousMousePos, mouseDir)
	{
		var points = [];

		for (var i=0; i!=lines.length; ++i)
		{
			var A = lines[i].a;
			var B = lines[i].b;

			var threshold = invScale(10);

			if (	mousePoint.x > Math.max(A.x, B.x)+threshold ||
					mousePoint.x < Math.min(A.x, B.x)-threshold ||
					mousePoint.y > Math.max(A.y, B.y)+threshold ||
					mousePoint.y < Math.min(A.y, B.y)-threshold)
			{
				continue;
			}

			var AB = sub(B, A);
			var tan = transpose(AB).unit();

			if (mousePoint !== undefined)
			{
				var AP = sub(mousePoint, A);

				var d = dot(AP, AB.unit());
				var t = dot(AP, tan);

				if (d>=0 && d<=AB.length())
				{
					var I = mad(AB.unit(), d, A);
					points.push({ type: "coincident", p: I, N: tan.neg(), objects: [lines[i].object] });
				}

				if (previousMousePos !== undefined && Math.abs(t)<=this.invScale(30))
				{
					var AP0 = sub(previousMousePos, A);
					var d0 = dot(AP0, AB.unit());
					var t0 = dot(AP0, tan);
					var I = mad(AB.unit(), d0, A);

					var testI = mad(AB.unit(), d, A);

					var perp = { type: "perpendicular", p: I, testP: testI, objects: [lines[i].object] };

					if (t0>=0)		perp.N = tan;
					else			perp.N = tan.neg();

							if (d < 0)			perp.p0 = A;
					else	if (d>AB.length())	perp.p0 = B;
					else						perp.p0 = I;

					points.push(perp);
				}

				if (	previousMousePos !== undefined &&
						mouseDir !== undefined &&
						d >= 0 && d <= AB.length() &&
						//dot(mouseDir, tan)<0 &&
						Math.abs(t) <= this.invScale(30))
				{
					var intersection = intersectRayRay(previousMousePos, mouseDir, A, AB.unit());

					if (intersection.hit)
					{
						var testI = mad(AB.unit(), d, A);

						var extendPoint = { type: "extendTo", p: intersection.P.copy(), testP: testI, objects: [lines[i].object] };

								if (intersection.tLine <= 0)		extendPoint.p0 = A;
						else	if (intersection.tLine >= 1)		extendPoint.p0 = B;
						else										extendPoint.p0 = testI;

						points.push(extendPoint);
					}
				}
			}

			for (var j = i + 1; j < lines.length; ++j)
			{
				if (lines[i].object == lines[j].object)
					continue;

				var C = lines[j].a;
				var D = lines[j].b;

				var AC = sub(C, A);
				var CD = sub(D, C);
				var d = -dot(AC, tan);
				var CD_along_tan = dot(CD.unit(), tan);
				var CD_factor = d / CD_along_tan;

				var I = add(C, mul(CD.unit(), CD_factor));

				var dAB = dot(sub(I, A), AB.unit());
				var dCD = dot(sub(I, C), CD.unit());

				if (dAB < 0 || dCD < 0 || dAB > distance(A, B) || dCD > distance(C, D))
					continue;

				points.push({type:"intersection", p:I, objects:[lines[i].object, lines[j].object] });
			}
		}

		return points;
	}

	function measureText(text, font)
	{
		var p = activeGraphics.measureText(text, font);

		return div(p, view_unitScale);
	}

	this.getUnitScale	 	= function() { return view_unitScale; 	}
	this.getViewBias		= function() { return view_bias; 	 	}
	this.getViewPosition	= function() { return view_position; 	}
	this.getViewSize		= function() { return new Vector(canvasW/view_unitScale, canvasH/view_unitScale); }
	this.getCanvas			= function() { return activeCanvas;		}
	this.getGraphics		= function() { return activeGraphics;	}

	this.setViewPosition	= setViewPosition;
	this.setUnitScale		= setUnitScale;
	this.setLayer			= setLayer;

	this.saveAsJavascript	= saveAsJavascript;
	this.transformP			= transformP;
	this.transformN			= transformN;
	this.scale				= scale;
	this.invScale			= invScale;
	this.invTransformP		= invTransformP;
	this.drawText 			= drawText;
	this.drawArrow 			= drawArrow;
	this.drawLine			= drawLine;
	this.drawBezierCurve	= drawBezierCurve;
	this.drawLines			= drawLines;
	this.drawLineStrip		= drawLineStrip;
	this.drawCross 			= drawCross;
	this.drawRectangle		= drawRectangle;
	this.drawHemisphere 	= drawHemisphere;
	this.drawArc			= drawArc;
	this.drawBRDFGraph		= drawBRDFGraph;
	this.drawLight			= drawLight;
	this.drawStar			= drawStar;
	this.getMousePos		= getMousePos;
	this.getSnapPoints		= getSnapPoints;
	this.measureText		= measureText;

	init();
}
<|endoftext|>


<commit_before>'use strict'

/**
 * Simple wrapper to selected evernote api functions
 * @param {*} event -
 * @param {*} context -
 * @param {*} callback -
 */
exports.handler = (event, context, callback) => {
  /**
   * Get tags of notebook
   * @param {*} notebookguid
   */
  function tagsNotebook(notebookguid) {
    return noteStore.listTagsByNotebook(notebookguid)
  }

  /**
   * Get count of notes of notebook
   * @param {*} noteFilter
   */
  function count(noteFilter) {
    return noteStore.findNoteCounts(noteFilter, false)
  }

  /**
   * Get the guids of notes
   * @param {*} offset
   * @param {*} count
   * @param {*} noteFilter
   * @param {*} notesMetadataResultSpec
   */
  function listNote(offset, count, noteFilter, notesMetadataResultSpec) {
    return noteStore.findNotesMetadata(
      noteFilter,
      offset,
      count,
      notesMetadataResultSpec
    )
  }

  /**
   * Get as many guids of notes as evernote will return and continue until the end
   * @param {*} offset
   * @param {*} count
   * @param {*} noteFilter
   * @param {*} notesMetadataResultSpec
   * @param {*} noteResults
   */
  function listMoreNotes(
    offset,
    count,
    noteFilter,
    notesMetadataResultSpec,
    noteResults
  ) {
    return listNote(offset, count, noteFilter, notesMetadataResultSpec).then(
      response => {
        // add new results to result set
        for (let index = 0; index < response.notes.length; index++) {
          noteResults.push({
            title: response.notes[index].title,
            message: '',
            guid: response.notes[index].guid,
            created: response.notes[index].created,
            updated: response.notes[index].updated,
            doc: ''
          })
        }
        if (offset < count) {
          // there are still more notes, continue
          offset += response.notes.length
          return listMoreNotes(
            offset,
            count,
            noteFilter,
            notesMetadataResultSpec,
            noteResults
          )
        } else {
          return noteResults
        }
      }
    )
  }

  /**
   * Get note guids of notebook
   * @param {*} notebookguid
   */
  function notes(notebookguid) {
    let notesMetadataResultSpec = {
      includeTitle: true,
      includeContentLength: true,
      includeCreated: true,
      includeUpdated: true,
      includeTagGuids: true
    }
    let noteFilter = {
      notebookGuid: notebookguid,
      ascending: false,
      order: 1
    }
    let noteResults = []

    return count(noteFilter).then(response =>
      listMoreNotes(
        0,
        response.notebookCounts[notebookguid],
        noteFilter,
        notesMetadataResultSpec,
        noteResults
      )
    )
  }

  /**
   * Get tags and note guids of notebook
   * @param {*} notebookguid
   */
  function notebook(notebookguid) {
    return Promise.all([tagsNotebook(notebookguid), notes(notebookguid)])
  }

  /**
   * Get tags of note
   * @param {*} noteguid
   */
  function tagsNote(noteguid) {
    return noteStore.getNoteTagNames(noteguid)
  }

  /**
   * Select a usable pic resource
   * @param {*} resources
   */
  function selectPic(resources) {
    let result = ''
    let candidates = []

    if (resources && resources.length) {
      resources.forEach(item => {
        if (item.attributes.sourceURL) candidates.push(item)
      })
      if (candidates.length) {
        if (candidates.length > 1) {
          candidates.sort((a, b) => {
            return b.width * b.height - a.width * a.height
          })
        }
        result = candidates[0].attributes.sourceURL
      }
    }
    return result
  }

  /**
   * Get content of note (html, text and pic)
   * @param {*} noteguid
   */
  function noteContent(noteguid) {
    let resHtml
    return noteStore
      .getNoteWithResultSpec(noteguid, {
        includeContent: true
        // includeResourcesData: true
      })
      .then(response => {
        if (response.message) {
          return Promise.reject(new Error(response.message))
        } else {
          let temp = enml.HTMLOfENML(response.content) // enml.HTMLOfENML(response.content, response.resources)
          return Promise.resolve({
            html: temp,
            text: sanitizeHtml(temp, {
              allowedTags: [],
              allowedAttributes: []
            }),
            pic: '' // selectPic(response.resources)
          })
        }
      })
  }

  /**
   * Get tags, content (raw and sanitized) and pic of note of notebook
   * @param {*} noteguid
   */
  function note(noteguid) {
    return Promise.all([tagsNote(noteguid), noteContent(noteguid)])
  }

  let creds = require('./creds.js').creds
  let Promise = require('bluebird')
  let Evernote = require('evernote')
  var sanitizeHtml = require('sanitize-html')
  let enml = require('enml-js')
  let client
  let noteStore
  let result

  // If no command was provided, then exit immediately.
  if (event === undefined || event.cmd === undefined || event.cmd === '') {
    callback(new Error('Missing cmd parameter'))
  } else if (
    event.cmd !== 'sources' &&
    event.cmd !== 'list' &&
    event.cmd !== 'single'
  ) {
    callback(new Error('Unknown command'))
  } else {
    // Init evernote api.
    client = new Evernote.Client({
      token: creds.token,
      sandbox: false
    })
    client.getUserStore().getPublicUserInfo(creds.userName)
    noteStore = client.getNoteStore()

    // Main switch.
    switch (event.cmd) {
      /**
       * Get sources (notebooks) of authenticated user.
       */
      case 'sources':
        noteStore
          .listNotebooks()
          .then(response => {
            result = []
            response.forEach(element => {
              result.push({
                name: element.name,
                guid: element.guid
              })
            })
            callback(null, result)
          })
          .catch(error =>
            callback(
              new Error('error: ' + error.errorCode + ' ' + error.parameter)
            )
          )
        break

      /**
       * Get cards (notes) of source (notebook).
       * @param {String} notebookguid - the notebook's guid
       */
      case 'list':
        // if required params are missing, then exit immediately.
        if (event.notebookguid === undefined) {
          callback(new Error('Missing notebook guid parameter'))
        } else {
          notebook(event.notebookguid)
            .then(response => {
              result = {}
              result.tags = response[0]
              result.notes = response[1]
              callback(null, result)
            })
            .catch(error => {
              callback(
                new Error('error: ' + error.errorCode + ' ' + error.parameter)
              )
            })
        }
        break

      /**
       * Get contents of card (note).
       * @param {String} noteguid - the note's guid
       */
      case 'single':
        // if required params are missing, then exit immediately.
        if (event.noteguid === undefined) {
          callback(new Error('Missing note guid parameter'))
        } else {
          note(event.noteguid)
            .then(response => {
              debugger
              result = {}
              result.noteTags = response[0]
              result.noteHtml = response[1].html
              result.noteText = response[1].text.trim()
              result.notePic = response[1].pic
              callback(null, result)
            })
            .catch(error => {
              callback(
                new Error('error: ' + error.errorCode + ' ' + error.parameter)
              )
            })
        }
      default:
    }
  }
}

/*
exports.handler({
  // cmd: 'sources'

  // cmd: 'list',
  // notebookguid: 'bf0ff626-e6e1-4bcb-bdfd-07f9c318cb76',

  // cmd: 'single',
  // noteguid: '006b4095-40d5-4557-810e-af0ce1d20852'
  // noteguid: '32f1bff2-523a-428b-a555-fde73f7f1b2d',
  // noteguid: '006b4095-40d5-4557-810e-af0ce1d20852',
  // noteguid: 'a879c0ab-26e7-4f37-89a7-68da9db7a7b9',
  // noteguid: '1623e69b-f462-4d55-8340-a96bd529c328',
  // noteguid: 'b5fd05ca-089e-418d-acec-254dee2326ab',
})
*/
<commit_msg>included attribute source url<commit_after>'use strict'

/**
 * Simple wrapper to selected evernote api functions
 * @param {*} event -
 * @param {*} context -
 * @param {*} callback -
 */
exports.handler = (event, context, callback) => {
  /**
   * Get tags of notebook
   * @param {*} notebookguid
   */
  function tagsNotebook(notebookguid) {
    return noteStore.listTagsByNotebook(notebookguid)
  }

  /**
   * Get count of notes of notebook
   * @param {*} noteFilter
   */
  function count(noteFilter) {
    return noteStore.findNoteCounts(noteFilter, false)
  }

  /**
   * Get the guids of notes
   * @param {*} offset
   * @param {*} count
   * @param {*} noteFilter
   * @param {*} notesMetadataResultSpec
   */
  function listNote(offset, count, noteFilter, notesMetadataResultSpec) {
    return noteStore.findNotesMetadata(
      noteFilter,
      offset,
      count,
      notesMetadataResultSpec
    )
  }

  /**
   * Get as many guids of notes as evernote will return and continue until the end
   * @param {*} offset
   * @param {*} count
   * @param {*} noteFilter
   * @param {*} notesMetadataResultSpec
   * @param {*} noteResults
   */
  function listMoreNotes(
    offset,
    count,
    noteFilter,
    notesMetadataResultSpec,
    noteResults
  ) {
    return listNote(offset, count, noteFilter, notesMetadataResultSpec).then(
      response => {
        // add new results to result set
        for (let index = 0; index < response.notes.length; index++) {
          noteResults.push({
            title: response.notes[index].title,
            message: '',
            guid: response.notes[index].guid,
            created: response.notes[index].created,
            updated: response.notes[index].updated,
            doc: '',
            sourceUrl: response.notes[index].attributes.sourceURL
          })
        }
        if (offset < count) {
          // there are still more notes, continue
          offset += response.notes.length
          return listMoreNotes(
            offset,
            count,
            noteFilter,
            notesMetadataResultSpec,
            noteResults
          )
        } else {
          return noteResults
        }
      }
    )
  }

  /**
   * Get note guids of notebook
   * @param {*} notebookguid
   */
  function notes(notebookguid) {
    let notesMetadataResultSpec = {
      includeTitle: true,
      includeCreated: true,
      includeUpdated: true,
      includeTagGuids: true,
      includeAttributes: true
    }
    let noteFilter = {
      notebookGuid: notebookguid,
      ascending: false,
      order: 1
    }
    let noteResults = []

    return count(noteFilter).then(response =>
      listMoreNotes(
        0,
        response.notebookCounts[notebookguid],
        noteFilter,
        notesMetadataResultSpec,
        noteResults
      )
    )
  }

  /**
   * Get tags and note guids of notebook
   * @param {*} notebookguid
   */
  function notebook(notebookguid) {
    return Promise.all([tagsNotebook(notebookguid), notes(notebookguid)])
  }

  /**
   * Get tags of note
   * @param {*} noteguid
   */
  function tagsNote(noteguid) {
    return noteStore.getNoteTagNames(noteguid)
  }

  /**
   * Select a usable pic resource
   * @param {*} resources
   */
  function selectPic(resources) {
    let result = ''
    let candidates = []

    if (resources && resources.length) {
      resources.forEach(item => {
        if (item.attributes.sourceURL) candidates.push(item)
      })
      if (candidates.length) {
        if (candidates.length > 1) {
          candidates.sort((a, b) => {
            return b.width * b.height - a.width * a.height
          })
        }
        result = candidates[0].attributes.sourceURL
      }
    }
    return result
  }

  /**
   * Get content of note (html, text and pic)
   * @param {*} noteguid
   */
  function noteContent(noteguid) {
    let resHtml
    return noteStore
      .getNoteWithResultSpec(noteguid, {
        includeContent: true
        // includeResourcesData: true
      })
      .then(response => {
        if (response.message) {
          return Promise.reject(new Error(response.message))
        } else {
          let temp = enml.HTMLOfENML(response.content) // enml.HTMLOfENML(response.content, response.resources)
          return Promise.resolve({
            html: temp,
            text: sanitizeHtml(temp, {
              allowedTags: [],
              allowedAttributes: []
            }),
            pic: '' // selectPic(response.resources)
          })
        }
      })
  }

  /**
   * Get tags, content (raw and sanitized) and pic of note of notebook
   * @param {*} noteguid
   */
  function note(noteguid) {
    return Promise.all([tagsNote(noteguid), noteContent(noteguid)])
  }

  let creds = require('./creds.js').creds
  let Promise = require('bluebird')
  let Evernote = require('evernote')
  var sanitizeHtml = require('sanitize-html')
  let enml = require('enml-js')
  let client
  let noteStore
  let result

  // If no command was provided, then exit immediately.
  if (event === undefined || event.cmd === undefined || event.cmd === '') {
    callback(new Error('Missing cmd parameter'))
  } else if (
    event.cmd !== 'sources' &&
    event.cmd !== 'list' &&
    event.cmd !== 'single'
  ) {
    callback(new Error('Unknown command'))
  } else {
    // Init evernote api.
    client = new Evernote.Client({
      token: creds.token,
      sandbox: false
    })
    client.getUserStore().getPublicUserInfo(creds.userName)
    noteStore = client.getNoteStore()

    // Main switch.
    switch (event.cmd) {
      /**
       * Get sources (notebooks) of authenticated user.
       */
      case 'sources':
        noteStore
          .listNotebooks()
          .then(response => {
            result = []
            response.forEach(element => {
              result.push({
                name: element.name,
                guid: element.guid
              })
            })
            callback(null, result)
          })
          .catch(error =>
            callback(
              new Error('error: ' + error.errorCode + ' ' + error.parameter)
            )
          )
        break

      /**
       * Get cards (notes) of source (notebook).
       * @param {String} notebookguid - the notebook's guid
       */
      case 'list':
        // if required params are missing, then exit immediately.
        if (event.notebookguid === undefined) {
          callback(new Error('Missing notebook guid parameter'))
        } else {
          notebook(event.notebookguid)
            .then(response => {
              result = {}
              result.tags = response[0]
              result.notes = response[1]
              callback(null, result)
            })
            .catch(error => {
              callback(
                new Error('error: ' + error.errorCode + ' ' + error.parameter)
              )
            })
        }
        break

      /**
       * Get contents of card (note).
       * @param {String} noteguid - the note's guid
       */
      case 'single':
        // if required params are missing, then exit immediately.
        if (event.noteguid === undefined) {
          callback(new Error('Missing note guid parameter'))
        } else {
          note(event.noteguid)
            .then(response => {
              result = {}
              result.noteTags = response[0]
              result.noteHtml = response[1].html
              result.noteText = response[1].text.trim()
              result.notePic = response[1].pic
              callback(null, result)
            })
            .catch(error => {
              callback(
                new Error('error: ' + error.errorCode + ' ' + error.parameter)
              )
            })
        }
      default:
    }
  }
}

/*
exports.handler({
  // cmd: 'sources'

  // cmd: 'list',
  // notebookguid: 'bf0ff626-e6e1-4bcb-bdfd-07f9c318cb76'

  // cmd: 'single',
  // noteguid: 'b354b021-7d39-4757-a060-ce196aa55c13'
  // noteguid: '006b4095-40d5-4557-810e-af0ce1d20852'
  // noteguid: '32f1bff2-523a-428b-a555-fde73f7f1b2d',
  // noteguid: '006b4095-40d5-4557-810e-af0ce1d20852',
  // noteguid: 'a879c0ab-26e7-4f37-89a7-68da9db7a7b9',
  // noteguid: '1623e69b-f462-4d55-8340-a96bd529c328',
  // noteguid: 'b5fd05ca-089e-418d-acec-254dee2326ab',
})
*/
<|endoftext|>


<commit_before>var express  = require('express');
var mongoose = require('mongoose');
var lingo    = require('lingo');

var BASE_URI = '/api/'; // TODO config

var model = function(schema) {
  var singular = schema.metadata('singular');
  if (!mongoose.models[singular]) return null;
  return mongoose.model(schema.metadata('singular'));
};

var get = function(schema) {
  // retrieve the addressed document
  var r = function(request, response, next) {
    var id        = request.params.id;
    var query     = model(schema).findById(id);
    var populated = schema.metadata('populate') || [];

    populated.forEach( function (field) {
      query.populate(field);
    });

    query.exec(function (err, doc) {
      if (err) return response.send(500); // TODO ?
      if (doc === null) return response.send(404);
      return response.json(doc);
    });
  };

  return r;
};

var post = function(schema) {
  // treat the addressed document as a collection, and push the addressed object to it (?)
  var r = function (request, response, next) {
    response.send(405); // method not allowed (as of yet unimplemented)	 ??? //TODO or 501?
  };

  return r;
};

var put = function (schema) {
  // replace the addressed document, or create it if nonexistant
  var r = function(request, response, next) {
    var id = request.params.id || null;
    var create = (id === null);

    // TODO hsould strip body of non-mongo fields (and in other methods)

    delete request.body._id; // can't send id for update, even if unchanged

    model(schema).update({_id: id}, request.body, {upsert: true}, function (err, doc) {
      if (err) return next(err);

      if (create) response.status(201);
      else response.status(200);

      response.json(doc); // TODO backbone might expect doc for 201, but spec says return url
    });
  };

  return r;
};

var del = function (schema) {
  // delete the addressed object
  var r = function (request, response, next) {
    var id = request.params.id;
    model(schema).remove({ _id: id }).exec( function (err, count) {
      if (err) return next(err);
      response.json(count);
    });
  };

  return r;
};

var pluralGet = function (schema) {
  // retrieve documents matching conditions
  var r = function (request, response, next) {
    var conditions = request.body || {};
    var query      = model(schema).find(conditions);
    var populated  = schema.metadata('populate') || [];

    populated.forEach( function (field) {
      query.populate(field);
    });

    query.exec(function(err, docs) {
      if (err) return next(err);
      response.json(docs);
    });
  };

  return r;
};

var pluralPost = function (schema) {
  // create a new document and return its ID
  var r = function(request, response, next) {
    if (!request.body || request.body.length === 0) {
      return next(new Error('Must supply a document or array to POST'));
    }

    // TODO ids or objects in put/post?
//    var ids   = [];
    var newDocs = [];
    var given = request.body;
    if (!Array.isArray(given)) given = [given];

    var docs = given.map( function(e) {
      return new (model(schema))(e);
    });

    response.status(201);

    docs.forEach( function (doc) {
      doc.save(function (err, doc) {
	if (err) return next(err);
	newDocs.push(doc);
	if (newDocs.length === docs.length) return response.json(docs); // TODO not sure this is http 1.1
      });
    });
  };

  return r;
};

var pluralPut = function (schema) {
  // repalce all docs with given docs ...
  var r = function (request, response, next) {
    response.send(405); // method not allowed (as of yet unimplemented)	 ??? // TODO 501?
  };

  return r;
};

var pluralDel = function (schema) {
  // delete all documents matching conditions
  var r = function(request, response, next) {
    var conditions = request.body || {};
    var query = model(schema).remove(conditions);
    query.exec(function (err, count) {
      if (err) return next(err);
      response.json(count);
    });
  };

  return r;
};

module.exports = {};

// TODO maybe check if express.HTTPServer exists and hook that up for backward compat.

module.exports.rest = function (app, schemata) {
  if (!Array.isArray(schemata)) {
    // if array leave alone, otherwise
    if (schemata.paths) {
      // single schema -> array
      schemata = [schemata];
    }
    else {
      // hash -> array
      schemata = Object.keys(schemata).map( function (key) {
	return schemata[key];
      });
    }
  }

  schemata.forEach( function (schema) {
    if (schema.metadata('private')) return;

    var singular    = schema.metadata('singular');
    var plural      = schema.metadata('plural') || lingo.pluralize(singular);
    var singularUrl = BASE_URI + singular + '/:id';
    var pluralUrl   = BASE_URI + plural + '/';
    var middleware  = schema.metadata('middleware') || [];

    // add if not already present
    if (!model(schema)) mongoose.model(singular, schema, plural);

//    app.head(singularUrl, middleware, head(schema)); // TODO
    app.get(singularUrl,  middleware, get(schema));
    app.post(singularUrl, middleware, post(schema));
    app.put(singularUrl,  middleware, put(schema));
    app.del(singularUrl,  middleware, del(schema));

//    app.head(pluralUrl, middleware, pluralHead(schema)); // TODO
    app.get(pluralUrl,  middleware, pluralGet(schema));
    app.post(pluralUrl, middleware, pluralPost(schema));
    app.put(pluralUrl,  middleware, pluralPut(schema));
    app.del(pluralUrl,  middleware, pluralDel(schema));
  });
};

mongoose.Schema.prototype.metadata = function (data) {
  if(!data)                             return this._metadata;
  if(data && typeof(data) === 'string') return this._metadata[data];

  if(data && typeof(data) === 'object') {
    if (this._metadata) throw new Error('Metadata was already set');
    return this._metadata = data;
  }

  throw new Error('Unrecognized use of metadata method');
};
<commit_msg>More populating<commit_after>var express  = require('express');
var mongoose = require('mongoose');
var lingo    = require('lingo');

var BASE_URI = '/api/'; // TODO config

var model = function(schema) {
  var singular = schema.metadata('singular');
  if (!mongoose.models[singular]) return null;
  return mongoose.model(schema.metadata('singular'));
};

var get = function(schema) {
  // retrieve the addressed document
  var r = function(request, response, next) {
    var id        = request.params.id;
    var query     = model(schema).findById(id);
    var populated = schema.metadata('populate') || [];

    populated.forEach( function (field) {
      query.populate(field);
    });

    query.exec(function (err, doc) {
      if (err) return response.send(500); // TODO ?
      if (doc === null) return response.send(404);
      return response.json(doc);
    });
  };

  return r;
};

var post = function(schema) {
  // treat the addressed document as a collection, and push the addressed object to it (?)
  var r = function (request, response, next) {
    response.send(405); // method not allowed (as of yet unimplemented)	 ??? //TODO or 501?
  };

  return r;
};

var put = function (schema) {
  // replace the addressed document, or create it if nonexistant
  var r = function(request, response, next) {
    var id        = request.params.id || null;
    var create    = (id === null);
    var query     = model(schema).update({_id: id}, request.body, {upsert: true});
    var populated = schema.metadata('populate') || [];

    populated.forEach( function (field) {
      query.populate(field);
    });

    // TODO hsould strip body of non-mongo fields (and in other methods)

    delete request.body._id; // can't send id for update, even if unchanged

    query.exec( function (err, doc) {
      if (err) return next(err);

      if (create) response.status(201);
      else response.status(200);

      response.json(doc); // TODO backbone might expect doc for 201, but spec says return url
    });
  };

  return r;
};

var del = function (schema) {
  // delete the addressed object
  var r = function (request, response, next) {
    var id = request.params.id;
    model(schema).remove({ _id: id }).exec( function (err, count) {
      if (err) return next(err);
      response.json(count);
    });
  };

  return r;
};

var pluralGet = function (schema) {
  // retrieve documents matching conditions
  var r = function (request, response, next) {
    var conditions = request.body || {};
    var query      = model(schema).find(conditions);
    var populated  = schema.metadata('populate') || [];

    populated.forEach( function (field) {
      query.populate(field);
    });

    query.exec(function(err, docs) {
      if (err) return next(err);
      response.json(docs);
    });
  };

  return r;
};

var pluralPost = function (schema) {
  // create a new document and return its ID
  var r = function(request, response, next) {
    if (!request.body || request.body.length === 0) {
      return next(new Error('Must supply a document or array to POST'));
    }

    var Model = model(schema);
    var newDocs = [];
    var populated = schema.metadata('populate') || [];
    var given = request.body;

    if (!Array.isArray(given)) given = [given];

    var docs = given.map( function(e) {
      return new Model(e);
    });

    docs.forEach( function (doc) {
      doc.save(function (err, doc) {
	if (err) return next(err);

	var query = Model.findById(doc._id);

	populated.forEach( function (field) {
	  query.populate(field);
	});

	query.exec( function (err, doc) {
	  if (err) return next(err);
	  newDocs.push(doc);

	  if (newDocs.length === docs.length) {
	    response.status(201);
	    return response.json(docs); // TODO not sure this is http 1.1
	  }
	});
      });
    });
  };

  return r;
};

var pluralPut = function (schema) {
  // repalce all docs with given docs ...
  var r = function (request, response, next) {
    response.send(405); // method not allowed (as of yet unimplemented)	 ??? // TODO 501?
  };

  return r;
};

var pluralDel = function (schema) {
  // delete all documents matching conditions
  var r = function(request, response, next) {
    var conditions = request.body || {};
    var query = model(schema).remove(conditions);
    query.exec(function (err, count) {
      if (err) return next(err);
      response.json(count);
    });
  };

  return r;
};

module.exports = {};

// TODO maybe check if express.HTTPServer exists and hook that up for backward compat.

module.exports.rest = function (app, schemata) {
  if (!Array.isArray(schemata)) {
    // if array leave alone, otherwise
    if (schemata.paths) {
      // single schema -> array
      schemata = [schemata];
    }
    else {
      // hash -> array
      schemata = Object.keys(schemata).map( function (key) {
	return schemata[key];
      });
    }
  }

  schemata.forEach( function (schema) {
    if (schema.metadata('private')) return;

    var singular    = schema.metadata('singular');
    var plural      = schema.metadata('plural') || lingo.pluralize(singular);
    var singularUrl = BASE_URI + singular + '/:id';
    var pluralUrl   = BASE_URI + plural + '/';
    var middleware  = schema.metadata('middleware') || [];

    // add if not already present
    if (!model(schema)) mongoose.model(singular, schema, plural);

//    app.head(singularUrl, middleware, head(schema)); // TODO
    app.get(singularUrl,  middleware, get(schema));
    app.post(singularUrl, middleware, post(schema));
    app.put(singularUrl,  middleware, put(schema));
    app.del(singularUrl,  middleware, del(schema));

//    app.head(pluralUrl, middleware, pluralHead(schema)); // TODO
    app.get(pluralUrl,  middleware, pluralGet(schema));
    app.post(pluralUrl, middleware, pluralPost(schema));
    app.put(pluralUrl,  middleware, pluralPut(schema));
    app.del(pluralUrl,  middleware, pluralDel(schema));
  });
};

mongoose.Schema.prototype.metadata = function (data) {
  if(!data)                             return this._metadata;
  if(data && typeof(data) === 'string') return this._metadata[data];

  if(data && typeof(data) === 'object') {
    if (this._metadata) throw new Error('Metadata was already set');
    return this._metadata = data;
  }

  throw new Error('Unrecognized use of metadata method');
};
<|endoftext|>