<commit_before>describe("VglFont component", function() {
    const {VglFont, VglNamespace} = VueGL;
    const assert = chai.assert;
    describe("The instance should be registered to the injected namespace.", function() {
        it("Null should be registered when created.", function() {
            const vm = new Vue({
                template: `<vgl-namespace><vgl-font name="dm'&^>" src="base/test/helvetiker_regular.typeface.json" /><other-component ref="other" /></vgl-namespace>`,
                components: {
                    VglFont,
                    VglNamespace,
                    OtherComponent: {
                        inject: ["vglFonts"],
                        render() {}
                    }
                }
            }).$mount();
            assert.isNull(vm.$refs.other.vglFonts.forGet["dm'&^>"]);
        });
        it("Should be replaced when the typeface is loaded.", function(done) {
            const vm = new Vue({
                template: `<vgl-namespace><vgl-font name="'<!--" src="base/test/helvetiker_regular.typeface.json" ref="f" /><other-component ref="other" /></vgl-namespace>`,
                components: {
                    VglNamespace,
                    VglFont,
                    OtherComponent: {
                        inject: ["vglFonts"],
                        render() {}
                    }
                }
            }).$mount();
            vm.$refs.f.$watch("inst", (inst) => {
                vm.$nextTick(() => {
                    try {
                        assert.strictEqual(vm.$refs.other.vglFonts.forGet["'<!--"], inst);
                        done();
                    } catch(e) {
                        done(e);
                    }
                });
            });
        });
        it("Should be unregistered when destroyed.", function(done) {
            const vm = new Vue({
                template: `<vgl-namespace><vgl-font name="n<!--" src="base/test/helvetiker_regular.typeface.json" v-if="!destroyed" ref="f" /><other-component ref="other" /></vgl-namespace>`,
                components: {
                    VglFont,
                    VglNamespace,
                    OtherComponent: {
                        inject: ["vglFonts"],
                        render() {}
                    }
                },
                data: {destroyed: false}
            }).$mount();
            vm.$refs.f.$watch("inst", () => {
                vm.$nextTick(() => {
                    vm.destroyed = true;
                    vm.$nextTick(() => {
                        try {
                            assert.isEmpty(vm.$refs.other.vglFonts.forGet);
                            done();
                        } catch(e) {
                            done(e);
                        }
                    });
                });
            });
        });
        it("Should be replaced when the instance is replaced.", function(done) {
            const vm = new Vue({
                template: `<vgl-namespace><vgl-font name="'<!--" :src="src" ref="f" /><other-component ref="other" /></vgl-namespace>`,
                components: {
                    VglNamespace,
                    VglFont,
                    OtherComponent: {
                        inject: ["vglFonts"],
                        render() {}
                    }
                },
                data: {src: "base/test/helvetiker_regular.typeface.json"}
            }).$mount();
            const unwatch = vm.$refs.f.$watch("inst", (before) => {
                unwatch();
                vm.src = "base/test/helvetiker_bold.typeface.json";
                vm.$refs.f.$watch("inst", (after) => {
                    try {
                        assert.notEqual(before, after);
                    } catch(e) {
                        done(e);
                    }
                    vm.$nextTick(() => {
                        try {
                            assert.strictEqual(vm.$refs.other.vglFonts.forGet["'<!--"], after);
                            done();
                        } catch(e) {
                            done(e);
                        }
                    });
                });
            });
        });
    });
    describe("Creating the instance", function() {
        it("The initial instance should be null.", function() {
            const vm = new Vue({
                template: `<vgl-namespace><vgl-font src="base/test/helvetiker_regular.typeface.json" ref="f" /></vgl-namespace>`,
                components: {VglNamespace, VglFont}
            }).$mount();
            assert.isNull(vm.$refs.f.inst);
        });
        it("The instance should be loaded from the src property when the src is a normal url.", function(done) {
            const vm = new Vue({
                template: `<vgl-namespace><vgl-font src="base/test/helvetiker_regular.typeface.json" ref="f" /></vgl-namespace>`,
                components: {VglNamespace, VglFont}
            }).$mount();
            vm.$refs.f.$watch("inst", (inst) => {
                const req = new XMLHttpRequest();
                req.addEventListener("load", () => {
                    try {
                        assert.deepEqual(inst.data, JSON.parse(req.responseText));
                        done();
                    } catch(e) {
                        done(e);
                    }
                }, false);
                req.open("GET", "base/test/helvetiker_regular.typeface.json");
                req.send();
            });
        });
        it("The instance should be loaded from the src property when the src is a data uri.", function(done) {
            const req = new XMLHttpRequest();
            req.addEventListener("load", () => {
                const vm = new Vue({
                    template: `<vgl-namespace><vgl-font src="data:,${encodeURIComponent(req.responseText)}" ref="f" /></vgl-namespace>`,
                    components: {VglNamespace, VglFont}
                }).$mount();
                vm.$refs.f.$watch("inst", (inst) => {
                    try {
                        assert.deepEqual(inst.data, JSON.parse(req.responseText));
                        done();
                    } catch(e) {
                        done(e);
                    }
                });
            }, false);
            req.open("GET", "base/test/helvetiker_regular.typeface.json");
            req.send();
        });
    });
    describe("Watching properties", function() {
        it("The instance should be replaced when the src property changes.", function(done) {
            const vm = new Vue({
                components: {VglFont, VglNamespace},
                template: `<vgl-namespace><vgl-font :src="src" ref="f" /></vgl-namespace>`,
                data: {src: "base/test/helvetiker_regular.typeface.json"}
            }).$mount();
            const unwatch = vm.$refs.f.$watch("inst", (before) => {
                unwatch();
                vm.src = "base/test/helvetiker_bold.typeface.json";
                vm.$refs.f.$watch("inst", (after) => {
                    try {
                        assert.notEqual(before, after);
                        done();
                    } catch(e) {
                        done(e);
                    }
                });
            });
        });
    });
});
<commit_msg>Pre-load typefaces to reduce xhr overheads.<commit_after>describe("VglFont component", function() {
    const {VglFont, VglNamespace} = VueGL;
    const assert = chai.assert;
    before(function(done) {
        const xhr = new XMLHttpRequest();
        xhr.addEventListener("load", () => {
            this.typeface_regular_json = JSON.parse(xhr.responseText);
            this.typeface_regular = `data:,${encodeURIComponent(xhr.responseText)}`;
            done();
        }, false);
        xhr.open("GET", "base/test/helvetiker_regular.typeface.json");
        xhr.send();
    });
    before(function(done) {
        const xhr = new XMLHttpRequest();
        xhr.addEventListener("load", () => {
            this.typeface_bold = `data:,${encodeURIComponent(xhr.responseText)}`;
            done();
        }, false);
        xhr.open("GET", "base/test/helvetiker_bold.typeface.json");
        xhr.send();
    });
    describe("The instance should be registered to the injected namespace.", function() {
        it("Null should be registered when created.", function() {
            const vm = new Vue({
                template: `<vgl-namespace><vgl-font name="dm'&^>" src="${this.typeface_regular}" /><other-component ref="other" /></vgl-namespace>`,
                components: {
                    VglFont,
                    VglNamespace,
                    OtherComponent: {
                        inject: ["vglFonts"],
                        render() {}
                    }
                }
            }).$mount();
            assert.isNull(vm.$refs.other.vglFonts.forGet["dm'&^>"]);
        });
        it("Should be replaced when the typeface is loaded.", function(done) {
            const vm = new Vue({
                template: `<vgl-namespace><vgl-font name="'<!--" src="${this.typeface_regular}" ref="f" /><other-component ref="other" /></vgl-namespace>`,
                components: {
                    VglNamespace,
                    VglFont,
                    OtherComponent: {
                        inject: ["vglFonts"],
                        render() {}
                    }
                }
            }).$mount();
            vm.$refs.f.$watch("inst", (inst) => {
                vm.$nextTick(() => {
                    try {
                        assert.strictEqual(vm.$refs.other.vglFonts.forGet["'<!--"], inst);
                        done();
                    } catch(e) {
                        done(e);
                    }
                });
            });
        });
        it("Should be unregistered when destroyed.", function(done) {
            const vm = new Vue({
                template: `<vgl-namespace><vgl-font name="n<!--" src="${this.typeface_regular}" v-if="!destroyed" ref="f" /><other-component ref="other" /></vgl-namespace>`,
                components: {
                    VglFont,
                    VglNamespace,
                    OtherComponent: {
                        inject: ["vglFonts"],
                        render() {}
                    }
                },
                data: {destroyed: false}
            }).$mount();
            vm.$refs.f.$watch("inst", () => {
                vm.$nextTick(() => {
                    vm.destroyed = true;
                    vm.$nextTick(() => {
                        try {
                            assert.isEmpty(vm.$refs.other.vglFonts.forGet);
                            done();
                        } catch(e) {
                            done(e);
                        }
                    });
                });
            });
        });
        it("Should be replaced when the instance is replaced.", function(done) {
            const vm = new Vue({
                template: `<vgl-namespace><vgl-font name="'<!--" :src="src" ref="f" /><other-component ref="other" /></vgl-namespace>`,
                components: {
                    VglNamespace,
                    VglFont,
                    OtherComponent: {
                        inject: ["vglFonts"],
                        render() {}
                    }
                },
                data: {src: this.typeface_regular}
            }).$mount();
            const unwatch = vm.$refs.f.$watch("inst", (before) => {
                unwatch();
                vm.src = this.typeface_bold;
                vm.$refs.f.$watch("inst", (after) => {
                    try {
                        assert.notEqual(before, after);
                    } catch(e) {
                        done(e);
                    }
                    vm.$nextTick(() => {
                        try {
                            assert.strictEqual(vm.$refs.other.vglFonts.forGet["'<!--"], after);
                            done();
                        } catch(e) {
                            done(e);
                        }
                    });
                });
            });
        });
    });
    describe("Creating the instance", function() {
        it("The initial instance should be null.", function() {
            const vm = new Vue({
                template: `<vgl-namespace><vgl-font src="${this.typeface_regular}" ref="f" /></vgl-namespace>`,
                components: {VglNamespace, VglFont}
            }).$mount();
            assert.isNull(vm.$refs.f.inst);
        });
        it("The instance should be loaded from the src property when the src is a normal url.", function(done) {
            const vm = new Vue({
                template: `<vgl-namespace><vgl-font src="base/test/helvetiker_regular.typeface.json" ref="f" /></vgl-namespace>`,
                components: {VglNamespace, VglFont}
            }).$mount();
            vm.$refs.f.$watch("inst", (inst) => {
                const req = new XMLHttpRequest();
                req.addEventListener("load", () => {
                    try {
                        assert.deepEqual(inst.data, JSON.parse(req.responseText));
                        done();
                    } catch(e) {
                        done(e);
                    }
                }, false);
                req.open("GET", "base/test/helvetiker_regular.typeface.json");
                req.send();
            });
        });
        it("The instance should be loaded from the src property when the src is a data uri.", function(done) {
            const req = new XMLHttpRequest();
            const vm = new Vue({
                template: `<vgl-namespace><vgl-font src="${this.typeface_regular}" ref="f" /></vgl-namespace>`,
                components: {VglNamespace, VglFont}
            }).$mount();
            vm.$refs.f.$watch("inst", (inst) => {
                try {
                    assert.deepEqual(inst.data, this.typeface_regular_json);
                    done();
                } catch(e) {
                    done(e);
                }
            });
        });
    });
    describe("Watching properties", function() {
        it("The instance should be replaced when the src property changes.", function(done) {
            const vm = new Vue({
                components: {VglFont, VglNamespace},
                template: `<vgl-namespace><vgl-font :src="src" ref="f" /></vgl-namespace>`,
                data: {src: this.typeface_regular}
            }).$mount();
            const unwatch = vm.$refs.f.$watch("inst", (before) => {
                unwatch();
                vm.src = this.typeface_bold;
                vm.$refs.f.$watch("inst", (after) => {
                    try {
                        assert.notEqual(before, after);
                        done();
                    } catch(e) {
                        done(e);
                    }
                });
            });
        });
    });
});
<|endoftext|>


<commit_before>/*
 * Conditions Extension
 *
 *   Allows a condition module to alter the resolution of an import via syntax:
 *
 *     import $ from 'jquery/#{browser}';
 *
 *   Will first load the module 'browser' via `SystemJS.import('browser')` and
 *   take the default export of that module.
 *   If the default export is not a string, an error is thrown.
 *
 *   We then substitute the string into the require to get the conditional resolution
 *   enabling environment-specific variations like:
 *
 *     import $ from 'jquery/ie'
 *     import $ from 'jquery/firefox'
 *     import $ from 'jquery/chrome'
 *     import $ from 'jquery/safari'
 *
 *   It can be useful for a condition module to define multiple conditions.
 *   This can be done via the `|` modifier to specify an export member expression:
 *
 *     import 'jquery/#{./browser.js|grade.version}'
 *
 *   Where the `grade` export `version` member in the `browser.js` module  is substituted.
 *
 *
 * Boolean Conditionals
 *
 *   For polyfill modules, that are used as imports but have no module value,
 *   a binary conditional allows a module not to be loaded at all if not needed:
 *
 *     import 'es5-shim#?./conditions.js|needs-es5shim'
 *
 *   These conditions can also be negated via:
 *
 *     import 'es5-shim#?./conditions.js|~es6'
 *
 */

  var sysConditions = ['browser', 'node', 'dev', 'production', 'default'];

  function parseCondition(condition) {
    var conditionExport, conditionModule, negation;

    var negation = condition[0] == '~';
    var conditionExportIndex = condition.lastIndexOf('|');
    if (conditionExportIndex != -1) {
      conditionExport = condition.substr(conditionExportIndex + 1);
      conditionModule = condition.substr(negation, conditionExportIndex - negation);

      if (negation)
        warn.call(this, 'Condition negation form "' + condition + '" is deprecated for "' + conditionModule + '|~' + conditionExport + '"');

      if (conditionExport[0] == '~') {
        negation = true;
        conditionExport = conditionExport.substr(1);
      }
    }
    else {
      conditionExport = 'default';
      conditionModule = condition.substr(negation);
      if (sysConditions.indexOf(conditionModule) != -1) {
        conditionExport = conditionModule;
        conditionModule = null;
      }
    }

    return {
      module: conditionModule || '@system-env',
      prop: conditionExport,
      negate: negation
    };
  }

  function serializeCondition(conditionObj) {
    return conditionObj.module + '|' + (conditionObj.negate ? '~' : '') + conditionObj.prop;
  }

  function resolveCondition(conditionObj, parentName, bool) {
    return this.load(conditionObj.module, parentName)
    .then(function(m) {
      m = readMemberExpression(conditionObj.prop, m);

      if (bool && typeof m != 'boolean')
        throw new TypeError('Condition ' + serializeCondition(conditionObj) + ' did not resolve to a boolean.');

      return conditionObj.negate ? !m : m;
    });
  }

  var interpolationRegEx = /#\{[^\}]+\}/;
  function interpolateConditional(name, parentName) {
    // first we normalize the conditional
    var conditionalMatch = name.match(interpolationRegEx);

    if (!conditionalMatch)
      return Promise.resolve(name);

    var conditionObj = parseCondition.call(this, conditionalMatch[0].substr(2, conditionalMatch[0].length - 3));

    // in builds, return normalized conditional
    if (this.builder)
      return this['normalize'](conditionObj.module, parentName)
      .then(function(conditionModule) {
        conditionObj.module = conditionModule;
        return name.replace(interpolationRegEx, '#{' + serializeCondition(conditionObj) + '}');
      });

    return resolveCondition.call(this, conditionObj, parentName, false)
    .then(function(conditionValue) {
      if (typeof conditionValue !== 'string')
        throw new TypeError('The condition value for ' + name + ' doesn\'t resolve to a string.');

      if (conditionValue.indexOf('/') != -1)
        throw new TypeError('Unabled to interpolate conditional ' + name + (parentName ? ' in ' + parentName : '') + '\n\tThe condition value ' + conditionValue + ' cannot contain a "/" separator.');

      return name.replace(interpolationRegEx, conditionValue);
    });
  }

  function booleanConditional(name, parentName) {
    // first we normalize the conditional
    var booleanIndex = name.lastIndexOf('#?');

    if (booleanIndex == -1)
      return Promise.resolve(name);

    var conditionObj = parseCondition.call(this, name.substr(booleanIndex + 2));

    // in builds, return normalized conditional
    if (this.builder)
      return this['normalize'](conditionObj.module, parentName)
      .then(function(conditionModule) {
        conditionObj.module = conditionModule;
        return name.substr(0, booleanIndex) + '#?' + serializeCondition(conditionObj);
      });

    return resolveCondition.call(this, conditionObj, parentName, true)
    .then(function(conditionValue) {
      return conditionValue ? name.substr(0, booleanIndex) : '@empty';
    });
  }

  // normalizeSync does not parse conditionals at all although it could
  hook('normalize', function(normalize) {
    return function(name, parentName, parentAddress) {
      var loader = this;
      return booleanConditional.call(loader, name, parentName)
      .then(function(name) {
        return normalize.call(loader, name, parentName, parentAddress);
      })
      .then(function(normalized) {
        return interpolateConditional.call(loader, normalized, parentName);
      });
    };
  });
<commit_msg>fix use of System.load to get condition<commit_after>/*
 * Conditions Extension
 *
 *   Allows a condition module to alter the resolution of an import via syntax:
 *
 *     import $ from 'jquery/#{browser}';
 *
 *   Will first load the module 'browser' via `SystemJS.import('browser')` and
 *   take the default export of that module.
 *   If the default export is not a string, an error is thrown.
 *
 *   We then substitute the string into the require to get the conditional resolution
 *   enabling environment-specific variations like:
 *
 *     import $ from 'jquery/ie'
 *     import $ from 'jquery/firefox'
 *     import $ from 'jquery/chrome'
 *     import $ from 'jquery/safari'
 *
 *   It can be useful for a condition module to define multiple conditions.
 *   This can be done via the `|` modifier to specify an export member expression:
 *
 *     import 'jquery/#{./browser.js|grade.version}'
 *
 *   Where the `grade` export `version` member in the `browser.js` module  is substituted.
 *
 *
 * Boolean Conditionals
 *
 *   For polyfill modules, that are used as imports but have no module value,
 *   a binary conditional allows a module not to be loaded at all if not needed:
 *
 *     import 'es5-shim#?./conditions.js|needs-es5shim'
 *
 *   These conditions can also be negated via:
 *
 *     import 'es5-shim#?./conditions.js|~es6'
 *
 */

  var sysConditions = ['browser', 'node', 'dev', 'production', 'default'];

  function parseCondition(condition) {
    var conditionExport, conditionModule, negation;

    var negation = condition[0] == '~';
    var conditionExportIndex = condition.lastIndexOf('|');
    if (conditionExportIndex != -1) {
      conditionExport = condition.substr(conditionExportIndex + 1);
      conditionModule = condition.substr(negation, conditionExportIndex - negation);

      if (negation)
        warn.call(this, 'Condition negation form "' + condition + '" is deprecated for "' + conditionModule + '|~' + conditionExport + '"');

      if (conditionExport[0] == '~') {
        negation = true;
        conditionExport = conditionExport.substr(1);
      }
    }
    else {
      conditionExport = 'default';
      conditionModule = condition.substr(negation);
      if (sysConditions.indexOf(conditionModule) != -1) {
        conditionExport = conditionModule;
        conditionModule = null;
      }
    }

    return {
      module: conditionModule || '@system-env',
      prop: conditionExport,
      negate: negation
    };
  }

  function serializeCondition(conditionObj) {
    return conditionObj.module + '|' + (conditionObj.negate ? '~' : '') + conditionObj.prop;
  }

  function resolveCondition(conditionObj, parentName, bool) {
    var self = this;
    return this.normalize(conditionObj.module, parentName)
    .then(function(normalizedCondition) {
      return self.load(normalizedCondition)
      .then(function(q) {
        var m = readMemberExpression(conditionObj.prop, self.get(normalizedCondition));

        if (bool && typeof m != 'boolean')
          throw new TypeError('Condition ' + serializeCondition(conditionObj) + ' did not resolve to a boolean.');

        return conditionObj.negate ? !m : m;
      });
    });
  }

  var interpolationRegEx = /#\{[^\}]+\}/;
  function interpolateConditional(name, parentName) {
    // first we normalize the conditional
    var conditionalMatch = name.match(interpolationRegEx);

    if (!conditionalMatch)
      return Promise.resolve(name);

    var conditionObj = parseCondition.call(this, conditionalMatch[0].substr(2, conditionalMatch[0].length - 3));

    // in builds, return normalized conditional
    if (this.builder)
      return this['normalize'](conditionObj.module, parentName)
      .then(function(conditionModule) {
        conditionObj.module = conditionModule;
        return name.replace(interpolationRegEx, '#{' + serializeCondition(conditionObj) + '}');
      });

    return resolveCondition.call(this, conditionObj, parentName, false)
    .then(function(conditionValue) {
      if (typeof conditionValue !== 'string')
        throw new TypeError('The condition value for ' + name + ' doesn\'t resolve to a string.');

      if (conditionValue.indexOf('/') != -1)
        throw new TypeError('Unabled to interpolate conditional ' + name + (parentName ? ' in ' + parentName : '') + '\n\tThe condition value ' + conditionValue + ' cannot contain a "/" separator.');

      return name.replace(interpolationRegEx, conditionValue);
    });
  }

  function booleanConditional(name, parentName) {
    // first we normalize the conditional
    var booleanIndex = name.lastIndexOf('#?');

    if (booleanIndex == -1)
      return Promise.resolve(name);

    var conditionObj = parseCondition.call(this, name.substr(booleanIndex + 2));

    // in builds, return normalized conditional
    if (this.builder)
      return this['normalize'](conditionObj.module, parentName)
      .then(function(conditionModule) {
        conditionObj.module = conditionModule;
        return name.substr(0, booleanIndex) + '#?' + serializeCondition(conditionObj);
      });

    return resolveCondition.call(this, conditionObj, parentName, true)
    .then(function(conditionValue) {
      return conditionValue ? name.substr(0, booleanIndex) : '@empty';
    });
  }

  // normalizeSync does not parse conditionals at all although it could
  hook('normalize', function(normalize) {
    return function(name, parentName, parentAddress) {
      var loader = this;
      return booleanConditional.call(loader, name, parentName)
      .then(function(name) {
        return normalize.call(loader, name, parentName, parentAddress);
      })
      .then(function(normalized) {
        return interpolateConditional.call(loader, normalized, parentName);
      });
    };
  });
<|endoftext|>


<commit_before>var rc = require('rc');

/**
 * RC params can be overriden using some environment variables,
 * in which case all values are passed as strings.
 *
 * When dealing with configuration, we can safely assume the expected
 * correct type
 *
 * @param cfg
 */
function unstringify (cfg) {
  Object.keys(cfg).forEach(k => {
    // exception - *version entries need to be kept as string
    if (/version$/i.test(k)) {
      return;
    }

    if (typeof cfg[k] === 'string') {
      if (cfg[k] === 'true') {
        cfg[k] = true;
      }
      else if (cfg[k] === 'false') {
        cfg[k] = false;
      }
      else if (/^[0-9]+$/.test(cfg[k])) {
        cfg[k] = parseInt(cfg[k]);
      }
      else if (/^[0-9]+\.[0-9]+$/.test(cfg[k])) {
        cfg[k] = parseFloat(cfg[k]);
      }
    }
    else if (cfg[k] instanceof Object) {
      cfg[k] = unstringify(cfg[k]);
    }
  });

  return cfg;
}

module.exports = unstringify(rc('kuzzle', require('../../default.config')));
<commit_msg>add config check and auto-correction<commit_after>'use strict';

const
  rc = require('rc'),
  _ = require('lodash'),
  /*
   rc mutates the provided object, we need to duplicate it to keep
   default configuration available for value checks
   */
  defaultConfig = _.cloneDeep(require('../../default.config'));

/**
 * Loads, interprets and checks configuration files
 * @return {object}
 */
function loadConfig () {
  let config = unstringify(rc('kuzzle', require('../../default.config')));

  checkServerConfig(config);

  return config;
}

/**
 * RC params can be overriden using environment variables,
 * in which case all values are passed as strings.
 *
 * When dealing with configuration, we can safely assume the expected
 * correct type
 *
 * @param {object} cfg - configuration loaded using RC
 * @return {object} correctly typed configuration
 */
function unstringify (cfg) {
  Object.keys(cfg).forEach(k => {
    // exception - *version entries need to be kept as string
    if (/version$/i.test(k)) {
      return;
    }

    if (typeof cfg[k] === 'string') {
      if (cfg[k] === 'true') {
        cfg[k] = true;
      }
      else if (cfg[k] === 'false') {
        cfg[k] = false;
      }
      else if (/^[0-9]+$/.test(cfg[k])) {
        cfg[k] = parseInt(cfg[k]);
      }
      else if (/^[0-9]+\.[0-9]+$/.test(cfg[k])) {
        cfg[k] = parseFloat(cfg[k]);
      }
    }
    else if (cfg[k] instanceof Object) {
      cfg[k] = unstringify(cfg[k]);
    }
  });

  return cfg;
}

/**
 * Checks the "server" section of the provided configuration object.
 * Throw warnings if invalid configuration are detected and auto-correct
 * them if necessary
 *
 * @param {object} cfg
 */
function checkServerConfig (cfg) {
  if (cfg.server.maxConcurrentRequests >= cfg.server.maxRetainedRequests) {
    console.warn(`[WARNING] Invalid server.maxConcurrentRequests and server.maxRetainedRequests configuration:
  The former must be strictly inferior to the latter, and preferably by a large margin.
  => Restoring the following default settings: maxConcurrentRequests = ${defaultConfig.server.maxConcurrentRequests}, maxRetainedRequests = ${defaultConfig.server.maxRetainedRequests}`);

    cfg.server.maxConcurrentRequests = defaultConfig.server.maxConcurrentRequests;
    cfg.server.maxRetainedRequests = defaultConfig.server.maxRetainedRequests;
  }

  if (!(cfg.server.warningRetainedRequestsLimit >= cfg.server.maxConcurrentRequests && cfg.server.warningRetainedRequestsLimit <= cfg.server.maxRetainedRequests)) {
    let autoCorrected = Math.max(cfg.server.maxConcurrentRequests, Math.round(cfg.server.maxRetainedRequests * .1));

    console.warn(`[WARNING] Invalid server.warningRetainedRequestsLimit configuration:
  Its value should be between server.maxConcurrentRequests and server.maxRetainedRequests.
  => Defaulting warningRetainedRequestsLimit to ${autoCorrected}`);

    cfg.server.warningRetainedRequestsLimit = autoCorrected;
  }

  /*
   Multi-actions count shouldn't be more than 80% of the maximum retained requests count,
   to keep reasonable chances to pass the overload check
   */
  if (cfg.server.maxMultiActionsCount > cfg.server.maxRetainedRequests * .8) {
    let autoCorrected = Math.round(cfg.server.maxRetainedRequests * .8);

    console.warn(`[WARNING] Invalid server.maxMultiActionsCount configuration:
  Its value should not be more than 80% of server.maxRetainedRequests, to make
  sure multi-actions request can pass the overload-protection check.
  => Defaulting maxMultiActionsCount to ${autoCorrected}`);

    cfg.server.maxMultiActionsCount = autoCorrected;
  }
}

module.exports = loadConfig();
<|endoftext|>


<commit_before>/**
 * The config module is responsible for setting any constant variable that is needed in the application. There are
 * 3 ways to set the configuration values for grasshopper.
 *
 * 1) Include a configuration.json file in lib/config folder
 * 2) Include a ghapi.json file in the root of your application
 * 3) Set an environment variable with GHCONFIG (soon to be deprecated) or GRASSHOPPER_CONFIG
 */
module.exports = (function(){
    'use strict';

    var findup = require('findup-sync'),
        internal = {},
        configString = process.argv.length == 3 && process.argv[2] == 'test' ?
            './configuration.test.json' : './configuration.json',
        config;

    if(process.env.GHCONFIG){
        console.log('Configuration found in the environment. Using config set in process.env');
        config = JSON.parse(process.env.GHCONFIG);
    }
    else if(process.env.GRASSHOPPER_CONFIG){
        console.log('Configuration found in the environment. Using config set in process.env');
        config = JSON.parse(process.env.GRASSHOPPER_CONFIG);
    }
    else {
        try {
            config = require.resolve(configString);
        } catch(e) {
            console.log('Configuration file not found. Loading Default config...');
            config = findup('*/ghapi.json');
        }

        config = require(config);
    }

    internal.config = config;

    config.get = function(moduleName){
        return internal.config[moduleName];
    };

    return config;
})();

<commit_msg>Changed log to be more friendly.<commit_after>/**
 * The config module is responsible for setting any constant variable that is needed in the application. There are
 * 3 ways to set the configuration values for grasshopper.
 *
 * 1) Include a configuration.json file in lib/config folder
 * 2) Include a ghapi.json file in the root of your application
 * 3) Set an environment variable with GHCONFIG (soon to be deprecated) or GRASSHOPPER_CONFIG
 */
module.exports = (function(){
    'use strict';

    var findup = require('findup-sync'),
        internal = {},
        configString = process.argv.length == 3 && process.argv[2] == 'test' ?
            './configuration.test.json' : './configuration.json',
        config;

    if(process.env.GHCONFIG){
        console.log('Configuration found in the environment. Using config set in process.env');
        config = JSON.parse(process.env.GHCONFIG);
    }
    else if(process.env.GRASSHOPPER_CONFIG){
        console.log('Configuration found in the environment. Using config set in process.env');
        config = JSON.parse(process.env.GRASSHOPPER_CONFIG);
    }
    else {
        try {
            config = require.resolve(configString);
        } catch(e) {
            console.log('Looking for ghapi.json file for configuration...');
            config = findup('*/ghapi.json');
        }

        config = require(config);
    }

    internal.config = config;

    config.get = function(moduleName){
        return internal.config[moduleName];
    };

    return config;
})();

<|endoftext|>


<commit_before>/*
 * Copyright (c) 2013 Imre Fazekas. All rights reserved.
 *
 * A restful web service middleware for Connect.
 */
var VERSION = '0.0.21';

var connect = require('connect');
var url = require('url');
var async = require('async');
var semver = require('semver');
var bunyan = require('bunyan');
var http = require('http');
var _ = require('underscore');
_.str = require('underscore.string');
_.mixin(_.str.exports());
_.str.include('Underscore.string', 'string');

var Route = require('./route');
var httphelper = require('./http-helper');

var LOAD_SIZE_LIMIT = 1e6;
var CONTEXT = '';
var ERROR_MESSAGE = '';

var SERVICE_METHOD_PATTERN = /^[a-zA-Z]([a-zA-Z]|\d|_)*$/g;

var mapping = {
	"HEAD": [],
	"GET": [],
	"POST": [],
	"PUT": [],
	"DELETE": []
};

var API_KEYS;
var logger;

function DummyLogger(){ }
DummyLogger.prototype.info = function() { console.log( parameters ); };
DummyLogger.prototype.debug = function() { console.debug( parameters ); };
DummyLogger.prototype.error = function() { console.error( parameters ); };

function addPath(key, path, action, prototypeObject, options){
	mapping[ key ].push( new Route( path, prototypeObject, options,
		function(request, content, callback){
			if(action.length == 3) {
				action(request, content, function(err, result, statusCode){
					callback(err, { contentType: options.contentType || 'application/json', result : result, statusCode: statusCode } );
				});
			} else {
				callback(null, { contentType: options.contentType || 'application/json', result : action(request, content) } );
			}
		}, _
	) );
}

function protoPather( request, content ){
	var utokens = _.words( request.parameters.path, '/');
	var method = _.first( utokens );
	var version = utokens[1];
	var pathname =	'/' + _.toSentence( _.rest(utokens, 2 ), '/', '/' );

	var routes = _.filter(
		mapping[ method ], function(route){ return route.matches(
			null, pathname, CONTEXT, version, _, semver, false
		); }
	);

	return routes.length > 0 ? _.first( _.map( routes, function(route){ return route.prototypeObject; } ) ) : 'No matching service';
}

function discover( request, content ){
	var version = request.parameters.version;

	var matchingMaps = {};
	_.each(mapping, function(value, key, list){
		matchingMaps[ key ] = [];

		_.each(value, function(element, index, list){
			_.each( element.matchings( version, _, semver ), function(match, matchIndex, matchList){
				matchingMaps[ key ].push( match );
			});
		});
	});

	return matchingMaps;
}

/*
var getClientAddress = function (req) {
    return (req.headers['x-forwarded-for'] || '').split(',')[0]
        || req.connection.remoteAddress;
};
*/

function process(req, res, matching, bodyObj){
	logger.debug('Payload object: ', bodyObj );

	var callChain =  _.map( matching, function(func){
		var headers = req.headers; headers.httpVersion=req.httpVersion; headers.method=req.method; headers.originalUrl=req.originalUrl;
		return async.apply( func, {headers: headers, parameters: req.query}, bodyObj );
	} );

	logger.debug('Calling service functions.' );

	req.headers.clientAddress = (req.headers['x-forwarded-for'] || '').split(',')[0] || req.connection.remoteAddress;

	var asyncCall = req.query.callbackURL;

	async.series( callChain,
		function(err, results){
			logger.info('Service(s) calling finished.', err, results );

			if( asyncCall ){
				var voptions = global._.clone( httphelper.opt );
				voptions.path = async;
				httphelper.generalCall( http, voptions, function(err, response){
					if(err)
						logger.error( err );
					else
						logger.info('Response:', response);
				} );
			} else{
				if( err ){
					logger.error( err );
					res.statusCode = err.statusCode || 500;
					res.end( 'Error occurred: ' + err );
				}
				else{
					var result = _.find(results, function(returnValue){ return returnValue && returnValue.result; }) || {};
					res.writeHead( result.statusCode || 200, {'Content-Type': result.contentType || 'application/json'});

					res.end( result && result.result ? ((result.contentType == 'application/json') ? JSON.stringify( result.result ) : result.result) : '' );
				}
			}
		}
	);

	if( asyncCall ){
		res.statusCode = 200;
		res.end( { answer: 'OK.'} );
	}
}

exports.head = function headRest(path, functionRef, options){
	addPath("HEAD", path, functionRef, null, options || {} );
};
exports.get = function getRest(path, functionRef, options){
	addPath("GET", path, functionRef, null, options || {} );
};
exports.post = function postRest(path, functionRef, prototypeObject, options){
	addPath("POST", path, functionRef, prototypeObject, options || {} );
};
exports.put = function putRest(path, functionRef, prototypeObject, options){
	addPath("PUT", path, functionRef, prototypeObject, options || {} );
};
exports.delete = function deleteRest(path, functionRef, prototypeObject, options){
	addPath("DELETE", path, functionRef, prototypeObject, options || {} );
};
exports.assign = function headRest(methods, path, functionRef, prototypeObject, options){
	if( _.isString(methods) && methods == '*' ){
		exports.rest( ['head','get','post','put','delete'], path, functionRef, prototypeObject, options );
	} else if( _.isArray(methods) ){
		_.each( methods, function(element, index, list){
			if( element == 'head' )
				addPath("HEAD", path, functionRef, null, options || {} );
			else if( element == 'get' )
				addPath("GET", path, functionRef, null, options || {} );
			else if( element == 'post' )
				addPath("POST", path, functionRef, prototypeObject, options || {} );
			else if( element == 'put' )
				addPath("PUT", path, functionRef, prototypeObject, options || {} );
			else if( element == 'delete' )
				addPath("DELETE", path, functionRef, prototypeObject, options || {} );
			else logger.error('Not known rest type:', element);
		} );
	}
	else
		logger.error('Not correct given methods', methods);
};

exports.publish = function (services){
	_.each( _.values( services ), function(element, index, list){
		if( _.isFunction( element ) && SERVICE_METHOD_PATTERN.test( element.name ) ){
			if( element.length == 1 )
				getRest( '/' + element.name, element );
			if( element.length == 2 )
				postRest( '/' + element.name, element );
		}
	} );
};

exports.context = function (context){
	CONTEXT = context;
};

exports.rester = function( options ) {
	var domain;
	if( options ){
		if( options.discoverPath )
			addPath('GET', options.discoverPath + '/:version', discover );
		if( options.protoPath )
			addPath('GET', options.protoPath + '/*path', protoPather );
		API_KEYS = options.apiKeys;

		logger = new DummyLogger();
		if( options.logger )
			logger = _.isString( options.logger ) ? bunyan.createLogger({name: options.logger, src: true, level: options.logLevel || 'info'}) : options.logger;

		domain = options.domain;

		logger.info('connect-rest has been configured. ', options);
	}

	return function(req, res, next) {
		if( domain ){
			domain.add(req);
			domain.add(res);
			domain.on('error', function(err) {
				try {
					res.statusCode = 500;
					res.end(err.message + '\n');
					res.on('close', function() {
						domain.dispose();
					});
				} catch (er) {
					logger.error('Error sending 500', er, req.url);
					domain.dispose();
				}
			});
		}

		if(!req.query) req.query = {};

		logger.info('Incoming request.', req.headers, req.query, req.httpVersion, req.method, req.originalUrl );

		if( API_KEYS && API_KEYS.indexOf(req.query.api_key)==-1 ){
			logger.info('Request without api key.', req.query );

			res.statusCode = 401;
			res.end( 'API_KEY is required.' );
			return;
		}

		var pathname = url.parse( req.url ).pathname;

		logger.debug('Request received from: ', pathname );

		var routes = _.filter(
			mapping[ req.method ], function(route){ return route.matches(
				req, pathname, CONTEXT, req.headers['accept-version'] || req.headers['x-api-version'] || '*', _, semver, true
			) && (!route.options.validator || route.options.validator(req, res) ); }
		);
		var matching = _.map( routes, function(route){ return route.action; } );

		if( matching.length === 0 ){
			logger.info('Request won\'t be handled by connect-rest.', req.url );

			return next();
		}

		logger.debug('Routes matching: ', routes );

		if( req.body ){
			process(req, res, matching, req.body);
		} else{
			var body = '';
			req.on('data', function(chunk) {
				body += chunk;
				if (body.length > LOAD_SIZE_LIMIT) {
					req.connection.destroy();
				}
			});

			req.on('end', function() {
				logger.debug('Body payload: ', body );

				var bodyObj = body.length ? (('application/json' == connect.utils.mime(req)) ? JSON.parse( body ) : body) : '';

				process(req, res, matching, bodyObj);
			} );
		}
	};
};

exports.VERSION = VERSION;
exports.LOAD_SIZE_LIMIT = LOAD_SIZE_LIMIT;
exports.CONTEXT = CONTEXT;
exports.ERROR_MESSAGE = ERROR_MESSAGE;
exports.SERVICE_METHOD_PATTERN = SERVICE_METHOD_PATTERN;
<commit_msg>async rest calls added. logging fixes.<commit_after>/*
 * Copyright (c) 2013 Imre Fazekas. All rights reserved.
 *
 * A restful web service middleware for Connect.
 */
var VERSION = '0.0.21';

var connect = require('connect');
var url = require('url');
var async = require('async');
var semver = require('semver');
var bunyan = require('bunyan');
var http = require('http');
var _ = require('underscore');
_.str = require('underscore.string');
_.mixin(_.str.exports());
_.str.include('Underscore.string', 'string');

var Route = require('./route');
var httphelper = require('./http-helper');

var LOAD_SIZE_LIMIT = 1e6;
var CONTEXT = '';
var ERROR_MESSAGE = '';

var SERVICE_METHOD_PATTERN = /^[a-zA-Z]([a-zA-Z]|\d|_)*$/g;

var mapping = {
	"HEAD": [],
	"GET": [],
	"POST": [],
	"PUT": [],
	"DELETE": []
};

var API_KEYS;
var logger;

function DummyLogger(){ }
DummyLogger.prototype.info = function() { console.log( parameters ); };
DummyLogger.prototype.debug = function() { console.debug( parameters ); };
DummyLogger.prototype.error = function() { console.error( parameters ); };

function addPath(key, path, action, prototypeObject, options){
	mapping[ key ].push( new Route( path, prototypeObject, options,
		function(request, content, callback){
			if(action.length == 3) {
				action(request, content, function(err, result, statusCode){
					callback(err, { contentType: options.contentType || 'application/json', result : result, statusCode: statusCode } );
				});
			} else {
				callback(null, { contentType: options.contentType || 'application/json', result : action(request, content) } );
			}
		}, _
	) );
}

function protoPather( request, content ){
	var utokens = _.words( request.parameters.path, '/');
	var method = _.first( utokens );
	var version = utokens[1];
	var pathname =	'/' + _.toSentence( _.rest(utokens, 2 ), '/', '/' );

	var routes = _.filter(
		mapping[ method ], function(route){ return route.matches(
			null, pathname, CONTEXT, version, _, semver, false
		); }
	);

	return routes.length > 0 ? _.first( _.map( routes, function(route){ return route.prototypeObject; } ) ) : 'No matching service';
}

function discover( request, content ){
	var version = request.parameters.version;

	var matchingMaps = {};
	_.each(mapping, function(value, key, list){
		matchingMaps[ key ] = [];

		_.each(value, function(element, index, list){
			_.each( element.matchings( version, _, semver ), function(match, matchIndex, matchList){
				matchingMaps[ key ].push( match );
			});
		});
	});

	return matchingMaps;
}

/*
var getClientAddress = function (req) {
    return (req.headers['x-forwarded-for'] || '').split(',')[0]
        || req.connection.remoteAddress;
};
*/

function process(req, res, matching, bodyObj){
	logger.debug('Payload object: ', bodyObj );

	var callChain =  _.map( matching, function(func){
		var headers = req.headers; headers.httpVersion=req.httpVersion; headers.method=req.method; headers.originalUrl=req.originalUrl;
		return async.apply( func, {headers: headers, parameters: req.query}, bodyObj );
	} );

	logger.debug('Calling service functions.' );

	req.headers.clientAddress = (req.headers['x-forwarded-for'] || '').split(',')[0] || req.connection.remoteAddress;

	var asyncCall = req.query.callbackURL;

	async.series( callChain,
		function(err, results){
			logger.info('Service(s) calling finished.', err, results );

			if( asyncCall ){
				var voptions = global._.clone( httphelper.opt );
				voptions.path = async;
				httphelper.generalCall( http, voptions, function(err, response){
					if(err)
						logger.error( err );
					else
						logger.info('Response:', response);
				} );
			} else{
				if( err ){
					logger.error( err );
					res.statusCode = err.statusCode || 500;
					res.end( 'Error occurred: ' + err );
				}
				else{
					logger.debug( 'Results:', results );
					var result = _.find(results, function(returnValue){ return returnValue && returnValue.result; }) || {};
					res.writeHead( result.statusCode || 200, {'Content-Type': result.contentType || 'application/json'});

					res.end( result && result.result ? ((result.contentType == 'application/json') ? JSON.stringify( result.result ) : result.result) : '' );
				}
			}
		}
	);

	if( asyncCall ){
		res.statusCode = 200;
		res.end( { answer: 'OK.'} );
	}
}

exports.head = function headRest(path, functionRef, options){
	addPath("HEAD", path, functionRef, null, options || {} );
};
exports.get = function getRest(path, functionRef, options){
	addPath("GET", path, functionRef, null, options || {} );
};
exports.post = function postRest(path, functionRef, prototypeObject, options){
	addPath("POST", path, functionRef, prototypeObject, options || {} );
};
exports.put = function putRest(path, functionRef, prototypeObject, options){
	addPath("PUT", path, functionRef, prototypeObject, options || {} );
};
exports.delete = function deleteRest(path, functionRef, prototypeObject, options){
	addPath("DELETE", path, functionRef, prototypeObject, options || {} );
};
exports.assign = function headRest(methods, path, functionRef, prototypeObject, options){
	if( _.isString(methods) && methods == '*' ){
		exports.rest( ['head','get','post','put','delete'], path, functionRef, prototypeObject, options );
	} else if( _.isArray(methods) ){
		_.each( methods, function(element, index, list){
			if( element == 'head' )
				addPath("HEAD", path, functionRef, null, options || {} );
			else if( element == 'get' )
				addPath("GET", path, functionRef, null, options || {} );
			else if( element == 'post' )
				addPath("POST", path, functionRef, prototypeObject, options || {} );
			else if( element == 'put' )
				addPath("PUT", path, functionRef, prototypeObject, options || {} );
			else if( element == 'delete' )
				addPath("DELETE", path, functionRef, prototypeObject, options || {} );
			else logger.error('Not known rest type:', element);
		} );
	}
	else
		logger.error('Not correct given methods', methods);
};

exports.publish = function (services){
	_.each( _.values( services ), function(element, index, list){
		if( _.isFunction( element ) && SERVICE_METHOD_PATTERN.test( element.name ) ){
			if( element.length == 1 )
				getRest( '/' + element.name, element );
			if( element.length == 2 )
				postRest( '/' + element.name, element );
		}
	} );
};

exports.context = function (context){
	CONTEXT = context;
};

exports.rester = function( options ) {
	var domain;
	if( options ){
		if( options.discoverPath )
			addPath('GET', options.discoverPath + '/:version', discover );
		if( options.protoPath )
			addPath('GET', options.protoPath + '/*path', protoPather );
		API_KEYS = options.apiKeys;

		logger = new DummyLogger();
		if( options.logger )
			logger = _.isString( options.logger ) ? bunyan.createLogger({name: options.logger, src: true, level: options.logLevel || 'info'}) : options.logger;

		domain = options.domain;

		logger.info('connect-rest has been configured. ', options);
	}

	return function(req, res, next) {
		if( domain ){
			domain.add(req);
			domain.add(res);
			domain.on('error', function(err) {
				try {
					res.statusCode = 500;
					res.end(err.message + '\n');
					res.on('close', function() {
						domain.dispose();
					});
				} catch (er) {
					logger.error('Error sending 500', er, req.url);
					domain.dispose();
				}
			});
		}

		if(!req.query) req.query = {};

		logger.info('Incoming request.', req.headers, req.query, req.httpVersion, req.method, req.originalUrl );

		if( API_KEYS && API_KEYS.indexOf(req.query.api_key)==-1 ){
			logger.info('Request without api key.', req.query );

			res.statusCode = 401;
			res.end( 'API_KEY is required.' );
			return;
		}

		var pathname = url.parse( req.url ).pathname;

		logger.debug('Request received from: ', pathname );

		var routes = _.filter(
			mapping[ req.method ], function(route){ return route.matches(
				req, pathname, CONTEXT, req.headers['accept-version'] || req.headers['x-api-version'] || '*', _, semver, true
			) && (!route.options.validator || route.options.validator(req, res) ); }
		);
		var matching = _.map( routes, function(route){ return route.action; } );

		if( matching.length === 0 ){
			logger.info('Request won\'t be handled by connect-rest.', req.url );

			return next();
		}

		logger.debug('Routes matching: ', routes );

		if( req.body ){
			process(req, res, matching, req.body);
		} else{
			var body = '';
			req.on('data', function(chunk) {
				body += chunk;
				if (body.length > LOAD_SIZE_LIMIT) {
					req.connection.destroy();
				}
			});

			req.on('end', function() {
				logger.debug('Body payload: ', body );

				var bodyObj = body.length ? (('application/json' == connect.utils.mime(req)) ? JSON.parse( body ) : body) : '';

				process(req, res, matching, bodyObj);
			} );
		}
	};
};

exports.VERSION = VERSION;
exports.LOAD_SIZE_LIMIT = LOAD_SIZE_LIMIT;
exports.CONTEXT = CONTEXT;
exports.ERROR_MESSAGE = ERROR_MESSAGE;
exports.SERVICE_METHOD_PATTERN = SERVICE_METHOD_PATTERN;
<|endoftext|>


<commit_before>'use strict';

var pathFn = require('path');
var chalk = require('chalk');
var fs = require('hexo-fs');
var tildify = require('tildify');
var spawn = require('hexo-util/lib/spawn');
var assign = require('object-assign');

var ASSET_DIR = pathFn.join(__dirname, '../../assets');
var GIT_REPO_URL = 'https://github.com/hexojs/hexo-starter.git';

function initConsole(args) {
  args = assign({
    install: true,
    clone: true
  }, args);

  var baseDir = this.base_dir;
  var target = args._[0] ? pathFn.resolve(baseDir, args._[0]) : baseDir;
  var log = this.log;
  var promise;

  log.info('Cloning hexo-starter to', chalk.magenta(tildify(target)));

  if (args.clone) {
    promise = spawn('git', ['clone', '--recursive', GIT_REPO_URL, target], {
      stdio: 'inherit'
    });
  } else {
    promise = copyAsset(target);
  }

  return promise.catch(function() {
    log.warn('git clone failed. Copying data instead');

    return copyAsset(target);
  }).then(function() {
    return Promise.all([
      removeGitDir(target),
      removeGitModules(target)
    ]);
  }).then(function() {
    if (!args.install) return;

    log.info('Install dependencies');

    return spawn('npm', ['install', '--production'], {
      cwd: target,
      stdio: 'inherit'
    });
  }).then(function() {
    log.info('Start blogging with Hexo!');
  }).catch(function() {
    log.warn('Failed to install dependencies. Please run \'npm install\' manually!');
  });
}

function copyAsset(target) {
  return fs.copyDir(ASSET_DIR, target, {
    ignoreHidden: false
  });
}

function removeGitDir(target) {
  var gitDir = pathFn.join(target, '.git');

  return fs.stat(gitDir).catch(function(err) {
    if (err.cause && err.cause.code === 'ENOENT') return;
    throw err;
  }).then(function(stats) {
    if (stats) {
      if (stats.isDirectory()) return fs.rmdir(gitDir);
      return fs.unlink(gitDir);
    }
  }).then(function() {
    return fs.readdir(target);
  }).map(function(path) {
    return pathFn.join(target, path);
  }).filter(function(path) {
    return fs.stat(path).then(function(stats) {
      return stats.isDirectory();
    });
  }).each(removeGitDir);
}

function removeGitModules(target) {
  return fs.unlink(pathFn.join(target, '.gitmodules')).catch(function(err) {
    if (err.cause && err.cause.code === 'ENOENT') return;
    throw err;
  });
}

module.exports = initConsole;
<commit_msg>fix: #12 init destroys `.git`<commit_after>'use strict';

var Promise = require('bluebird');
var pathFn = require('path');
var chalk = require('chalk');
var fs = require('hexo-fs');
var tildify = require('tildify');
var spawn = require('hexo-util/lib/spawn');
var assign = require('object-assign');

var ASSET_DIR = pathFn.join(__dirname, '../../assets');
var GIT_REPO_URL = 'https://github.com/hexojs/hexo-starter.git';

function initConsole(args) {
  args = assign({
    install: true,
    clone: true
  }, args);

  var baseDir = this.base_dir;
  var target = args._[0] ? pathFn.resolve(baseDir, args._[0]) : baseDir;
  var log = this.log;
  var promise;

  if (fs.existsSync(target) && fs.readdirSync(target).length !== 0) {
    log.fatal(chalk.magenta(tildify(target)) +
      ' not empty, please run `hexo init` on an empty folder and then copy your files into it');
    return Promise.reject(new Error('target not empty'));
  }

  log.info('Cloning hexo-starter to', chalk.magenta(tildify(target)));

  if (args.clone) {
    promise = spawn('git', ['clone', '--recursive', GIT_REPO_URL, target], {
      stdio: 'inherit'
    });
  } else {
    promise = copyAsset(target);
  }

  return promise.catch(function() {
    log.warn('git clone failed. Copying data instead');

    return copyAsset(target);
  }).then(function() {
    return Promise.all([
      removeGitDir(target),
      removeGitModules(target)
    ]);
  }).then(function() {
    if (!args.install) return;

    log.info('Install dependencies');

    return spawn('npm', ['install', '--production'], {
      cwd: target,
      stdio: 'inherit'
    });
  }).then(function() {
    log.info('Start blogging with Hexo!');
  }).catch(function() {
    log.warn('Failed to install dependencies. Please run \'npm install\' manually!');
  });
}

function copyAsset(target) {
  return fs.copyDir(ASSET_DIR, target, {
    ignoreHidden: false
  });
}

function removeGitDir(target) {
  var gitDir = pathFn.join(target, '.git');

  return fs.stat(gitDir).catch(function(err) {
    if (err.cause && err.cause.code === 'ENOENT') return;
    throw err;
  }).then(function(stats) {
    if (stats) {
      if (stats.isDirectory()) return fs.rmdir(gitDir);
      return fs.unlink(gitDir);
    }
  }).then(function() {
    return fs.readdir(target);
  }).map(function(path) {
    return pathFn.join(target, path);
  }).filter(function(path) {
    return fs.stat(path).then(function(stats) {
      return stats.isDirectory();
    });
  }).each(removeGitDir);
}

function removeGitModules(target) {
  return fs.unlink(pathFn.join(target, '.gitmodules')).catch(function(err) {
    if (err.cause && err.cause.code === 'ENOENT') return;
    throw err;
  });
}

module.exports = initConsole;
<|endoftext|>


<commit_before>var Q = require("q");
var _ = require("lodash");

var utils = require("../utils");
var BaseError = utils.BaseError;

var events = require("../events");
var Eventable = events.Eventable;

var structs = require("../structs");
var Model = structs.Model;

var ContactError = BaseError.extend(function(self, contact, message) {
    /**class:ContactError(contact, message)

    Thrown when an error occurs while interacting with a :class:`Contact`.

    :param Contact contact: the contact that associated to the error.
    :param string message: the reason for the error.
    */
    self.name = "ContactError";
    self.contact = contact;
    self.message = message;
});

var Contact = Model.extend(function(self, attrs) {
    /**class:Contact(attrs)

    Holds long-term information about a user interacting with the application.

    :param string attrs.key:
        A unique identifier for looking up the contact.
    :param string attrs.user_account:
        The name of the vumi go account that the contact is stored under.
    :param string attrs.msisdn:
        The contact's msisdn.
    :param string attrs.gtalk_id:
        The contact's gtalk address. Optional.
    :param string attrs.facebook_id:
        The contact's facebook address. Optional.
    :param string attrs.twitter_handle:
        The contact's twitter handle. Optional.
    :param string attrs.name:
        The contact's name. Optional.
    :param string attrs.surname:
        The contact's surname. Optional.
    :param object attrs.extra:
        A data object for additional, app-specific information about a contact.
        Both the keys and values need to be strings. Optional.
    :param array attrs.groups:
        A unique identifier for looking up the contact. Optional.
    */
    Model.call(self);

    self.cls.defaults = {
        extra: {},
        groups: [],
        subscriptions: {}
    };

    self.do.validate = function() {
        /**:Contact.do.validate()

        Validates a contact, throwing a :class:`ContactError` if one of its
        attributes are invalid.
        */
        if (!_.isString(self.msisdn)) {
            throw new ContactError(self, [
                "Contact has an msisdn of type",
                "'" + typeof self.msisdn + "'",
                "instead of 'string': " + self.msisdn
            ].join(' '));
        }

        _.each(self.extra, self.do.validate.extra);
        _.each(self.groups, self.do.validate.group);
        _.each(self.subscriptions, self.do.validate.subscriptions);
    };

    self.do.validate.extra = function(value, name) {
        if (!_.isString(value)) {
            throw new ContactError(self, [
                "Contact extra '" + name + "' has a value of type",
                "'" + typeof value + "' instead of 'string': " + value
            ].join(' '));
        }
    };

    self.do.validate.subscriptions = function(value, name) {
        if (!_.isString(value)) {
            throw new ContactError(self, [
                "Contact subscription '" + name + "' has a value of type",
                "'" + typeof value + "' instead of 'string': " + value
            ].join(' '));
        }
    };

    self.do.validate.group = function(group) {
        if (!_.isString(group)) {
            throw new ContactError(self, [
                "Contact has a group of type '" + typeof group + "'",
                "instead of 'string': " + group
            ].join(' '));
        }
    };

    /**:Contact.do.reset(attrs)

    Resets a contact's attributes to `attrs`. All the contact's current
    attributes will be lost.

    :param object attrs:
        the attributes to reset the contact with.
    */
    self.do.reset;

    /**:Contact.serialize()

    Returns a deep copy of the contact's attributes.
    */
    self.do.serialize;

    self.do.init(attrs);
});

var ContactStore = Eventable.extend(function(self, im) {
    /**:ContactStore(im)

    Provides 'ORM-like' access to the sandbox's contacts resource, handling the
    raw contact resource api requests and allowing people to interact with
    their contacts as :class:`Contact` instances.

    :param InteractionMachine im:
        The interaction machine
    */
    Eventable.call(self);
    self.im = im;

    self.setup = function(opts) {
        /**ContactStore.setup(opts)

        Sets up the store.

        :param opts.delivery class:
            The fallback delivery class to use when retrieving contacts by
            their address. :class:`InteractionMachine` sets up its
            :class:`ContactStore` with the delivery class given its config,
            or ``'ussd'`` if not specified.
        */
        opts = _.defaults(opts || {}, {delivery_class: 'ussd'});
        self.delivery_class = opts.delivery_class;
        return self.emit.setup();
    };

    self.request = function(name, cmd) {
        /**:ContactStore.request(name, cmd)

        Makes raw requests to the api's contact resource.

        :param string name:
            The name of the contact api method (for eg, ``'get'``)
        :param object cmd:
            The request's command data
        */
        return self.im.api_request('contacts.' + name, cmd);
    };

    self.create = function(attrs) {
        /**:ContactStore.create(attrs)

        Creates and adds a new contact, returning a corresponding
        :class:`Contact` via a promise.

        :param object attrs:
            The attributes to initialise the new contact with.

        .. code-block:: javascript

            self.im.contacts.create({
                surname: 'Jones',
                extra: {location: 'CPT'}
            }).then(function(contact) {
                console.log(contact instanceof Contact);
            });
        */
        return self
            .request('new', {contact: attrs})
            .then(function(reply) {
                return new Contact(reply.contact);
            });
    };

    self.get = function(addr, opts) {
        /**:ContactStore.get(addr[, opts])

        Retrieves a contact by its address for a particular delivery class,
        returning a corresponding :class:`Contact` via a promise.

        :param boolean opts.create:
            Create the contact if it does not yet exist. Defaults to ``false``.
        :param string delivery_class:
            The delivery class corresponding to the given address. If not
            specified, :class:`ContactStore` tries uses its fallback,
            :attr:`ContactStore.delivery_class`.

        .. code-block:: javascript

            self.im.contacts.get('+27731234567').then(function(contact) {
                console.log(contact instanceof Contact);
            });
        */
        opts = _.defaults(opts || {}, {
            create: false,
            delivery_class: self.delivery_class
        });

        var method = opts.create
            ? 'get_or_create'
            : 'get';

        return self
            .request(method, {
                addr: addr,
                delivery_class: opts.delivery_class
            }).then(function(reply) {
                return new Contact(reply.contact);
            });
    };

    self.get_by_key = function(key) {
        /**:ContactStore.get(key)

        Retrieves a contact by its key, returning a corresponding
        :class:`Contact` via a promise.

        :param string key:
            The contact's key.

        .. code-block:: javascript

            self.im.contacts.get('1234').then(function(contact) {
                console.log(contact instanceof Contact);
            });
        */
        return self
            .request('get_by_key', {key: key})
            .then(function(reply) {
                return new Contact(reply.contact);
            });
    };

    self.for_user = function(opts) {
        /**:ContactStore.for_user(opts)

        Retrieves a contact for the the current user in the
        :class:`InteractionMachine`, returning a corresponding
        :class:`Contact` via a promise. If no contact exists for the
        user, a contact is created.

        :param boolean opts.create:
            Whether to create a contact for the user if it does not yet exist.
            Defaults to ``true``.
        :param string opts.delivery_class:
            The delivery class corresponding to the current user's address. If
            not specified, :class:`ContactStore` tries uses its fallback,
            :attr:`ContactStore.delivery_class`.

        .. code-block:: javascript

            self.im.contacts.for_user().then(function(contact) {
                console.log(contact instanceof Contact);
            });
        */
        opts = _.defaults(opts || {}, {create: true});
        return self.get(im.user.addr, opts);
    };

    self.search = function(query) {
        /**:ContactStore.search(query)

        Searches for contacts matching the given Lucene search query, returning
        an array of the matching :class:`Contact` instances via a promise. Note
        that this can be a fairly heavy operation. If only the contact keys are
        needed, please use :meth:`ContactStore.search_keys` instead.

        :param string query:
            The Lucene query to perform

        .. code-block:: javascript

            self.im.contacts.search('name:"Moog"').then(function(contacts) {
                contacts.forEach(function(contact) {
                    console.log(contact instanceof Contact);
                });
            });
        */
        return self
            .search_keys(query)
            .then(function(keys) {
                return Q.all(keys.map(self.get_by_key));
            });
    };

    self.search_keys = function(query) {
        /**:ContactStore.search_keys(query)

        Searches for contacts matching the given Lucene search query, returning
        an array of the contacts' keys via a promise.

        :param string query:
            The Lucene query to perform

        .. code-block:: javascript

            self.im.contacts.search_keys('name:"Moog"').then(function(keys) {
                keys.forEach(function(key) {
                    console.log(typeof key == 'string');
                });
            });
        */
        return self.request('search', {query: query}).get('keys');
    };

    self.save = function(contact) {
        /**:ContactStore.save(contact)

        Saves the given contact to the store, returning a promise that is
        fulfilled once the operation completes.

        :param Contact contact:
            The contact to be saved
        */
        return self
            .request('save', {contact: contact})
            .then(_.noop);
    };
});

this.Contact = Contact;
this.ContactStore = ContactStore;
this.ContactError = ContactError;
<commit_msg>Fix ContactStore's docstrings<commit_after>var Q = require("q");
var _ = require("lodash");

var utils = require("../utils");
var BaseError = utils.BaseError;

var events = require("../events");
var Eventable = events.Eventable;

var structs = require("../structs");
var Model = structs.Model;

var ContactError = BaseError.extend(function(self, contact, message) {
    /**class:ContactError(contact, message)

    Thrown when an error occurs while interacting with a :class:`Contact`.

    :param Contact contact: the contact that associated to the error.
    :param string message: the reason for the error.
    */
    self.name = "ContactError";
    self.contact = contact;
    self.message = message;
});

var Contact = Model.extend(function(self, attrs) {
    /**class:Contact(attrs)

    Holds long-term information about a user interacting with the application.

    :param string attrs.key:
        A unique identifier for looking up the contact.
    :param string attrs.user_account:
        The name of the vumi go account that the contact is stored under.
    :param string attrs.msisdn:
        The contact's msisdn.
    :param string attrs.gtalk_id:
        The contact's gtalk address. Optional.
    :param string attrs.facebook_id:
        The contact's facebook address. Optional.
    :param string attrs.twitter_handle:
        The contact's twitter handle. Optional.
    :param string attrs.name:
        The contact's name. Optional.
    :param string attrs.surname:
        The contact's surname. Optional.
    :param object attrs.extra:
        A data object for additional, app-specific information about a contact.
        Both the keys and values need to be strings. Optional.
    :param array attrs.groups:
        A unique identifier for looking up the contact. Optional.
    */
    Model.call(self);

    self.cls.defaults = {
        extra: {},
        groups: [],
        subscriptions: {}
    };

    self.do.validate = function() {
        /**:Contact.do.validate()

        Validates a contact, throwing a :class:`ContactError` if one of its
        attributes are invalid.
        */
        if (!_.isString(self.msisdn)) {
            throw new ContactError(self, [
                "Contact has an msisdn of type",
                "'" + typeof self.msisdn + "'",
                "instead of 'string': " + self.msisdn
            ].join(' '));
        }

        _.each(self.extra, self.do.validate.extra);
        _.each(self.groups, self.do.validate.group);
        _.each(self.subscriptions, self.do.validate.subscriptions);
    };

    self.do.validate.extra = function(value, name) {
        if (!_.isString(value)) {
            throw new ContactError(self, [
                "Contact extra '" + name + "' has a value of type",
                "'" + typeof value + "' instead of 'string': " + value
            ].join(' '));
        }
    };

    self.do.validate.subscriptions = function(value, name) {
        if (!_.isString(value)) {
            throw new ContactError(self, [
                "Contact subscription '" + name + "' has a value of type",
                "'" + typeof value + "' instead of 'string': " + value
            ].join(' '));
        }
    };

    self.do.validate.group = function(group) {
        if (!_.isString(group)) {
            throw new ContactError(self, [
                "Contact has a group of type '" + typeof group + "'",
                "instead of 'string': " + group
            ].join(' '));
        }
    };

    /**:Contact.do.reset(attrs)

    Resets a contact's attributes to `attrs`. All the contact's current
    attributes will be lost.

    :param object attrs:
        the attributes to reset the contact with.
    */
    self.do.reset;

    /**:Contact.serialize()

    Returns a deep copy of the contact's attributes.
    */
    self.do.serialize;

    self.do.init(attrs);
});

var ContactStore = Eventable.extend(function(self, im) {
    /**:ContactStore(im)

    Provides 'ORM-like' access to the sandbox's contacts resource, handling the
    raw contact resource api requests and allowing people to interact with
    their contacts as :class:`Contact` instances.

    :param InteractionMachine im:
        The interaction machine
    */
    Eventable.call(self);
    self.im = im;

    self.setup = function(opts) {
        /**ContactStore.setup(opts)

        Sets up the store.

        :param opts.delivery class:
            The fallback delivery class to use when retrieving contacts by
            their address. :class:`InteractionMachine` sets up its
            :class:`ContactStore` with the delivery class given its config,
            or ``'ussd'`` if not specified.
        */
        opts = _.defaults(opts || {}, {delivery_class: 'ussd'});
        self.delivery_class = opts.delivery_class;
        return self.emit.setup();
    };

    self.request = function(name, cmd) {
        /**:ContactStore.request(name, cmd)

        Makes raw requests to the api's contact resource.

        :param string name:
            The name of the contact api method (for eg, ``'get'``)
        :param object cmd:
            The request's command data
        */
        return self.im.api_request('contacts.' + name, cmd);
    };

    self.create = function(attrs) {
        /**:ContactStore.create(attrs)

        Creates and adds a new contact, returning a corresponding
        :class:`Contact` via a promise.

        :param object attrs:
            The attributes to initialise the new contact with.

        .. code-block:: javascript

            self.im.contacts.create({
                surname: 'Jones',
                extra: {location: 'CPT'}
            }).then(function(contact) {
                console.log(contact instanceof Contact);
            });
        */
        return self
            .request('new', {contact: attrs})
            .then(function(reply) {
                return new Contact(reply.contact);
            });
    };

    self.get = function(addr, opts) {
        /**:ContactStore.get(addr[, opts])

        Retrieves a contact by its address for a particular delivery class,
        returning a corresponding :class:`Contact` via a promise.

        :param boolean opts.create:
            Create the contact if it does not yet exist. Defaults to ``false``.
        :param string delivery_class:
            The delivery class corresponding to the given address. If not
            specified, :class:`ContactStore` uses its fallback,
            :attr:`ContactStore.delivery_class`.

        .. code-block:: javascript

            self.im.contacts.get('+27731234567').then(function(contact) {
                console.log(contact instanceof Contact);
            });
        */
        opts = _.defaults(opts || {}, {
            create: false,
            delivery_class: self.delivery_class
        });

        var method = opts.create
            ? 'get_or_create'
            : 'get';

        return self
            .request(method, {
                addr: addr,
                delivery_class: opts.delivery_class
            }).then(function(reply) {
                return new Contact(reply.contact);
            });
    };

    self.get_by_key = function(key) {
        /**:ContactStore.get(key)

        Retrieves a contact by its key, returning a corresponding
        :class:`Contact` via a promise.

        :param string key:
            The contact's key.

        .. code-block:: javascript

            self.im.contacts.get('1234').then(function(contact) {
                console.log(contact instanceof Contact);
            });
        */
        return self
            .request('get_by_key', {key: key})
            .then(function(reply) {
                return new Contact(reply.contact);
            });
    };

    self.for_user = function(opts) {
        /**:ContactStore.for_user(opts)

        Retrieves a contact for the the current user in the
        :class:`InteractionMachine`, returning a corresponding
        :class:`Contact` via a promise. If no contact exists for the
        user, a contact is created.

        :param boolean opts.create:
            Whether to create a contact for the user if it does not yet exist.
            Defaults to ``true``.
        :param string opts.delivery_class:
            The delivery class corresponding to the current user's address. If
            not specified, :class:`ContactStore` uses its fallback,
            :attr:`ContactStore.delivery_class`.

        .. code-block:: javascript

            self.im.contacts.for_user().then(function(contact) {
                console.log(contact instanceof Contact);
            });
        */
        opts = _.defaults(opts || {}, {create: true});
        return self.get(im.user.addr, opts);
    };

    self.search = function(query) {
        /**:ContactStore.search(query)

        Searches for contacts matching the given Lucene search query, returning
        an array of the matching :class:`Contact` instances via a promise. Note
        that this can be a fairly heavy operation. If only the contact keys are
        needed, please use :meth:`ContactStore.search_keys` instead.

        :param string query:
            The Lucene query to perform

        .. code-block:: javascript

            self.im.contacts.search('name:"Moog"').then(function(contacts) {
                contacts.forEach(function(contact) {
                    console.log(contact instanceof Contact);
                });
            });
        */
        return self
            .search_keys(query)
            .then(function(keys) {
                return Q.all(keys.map(self.get_by_key));
            });
    };

    self.search_keys = function(query) {
        /**:ContactStore.search_keys(query)

        Searches for contacts matching the given Lucene search query, returning
        an array of the contacts' keys via a promise.

        :param string query:
            The Lucene query to perform

        .. code-block:: javascript

            self.im.contacts.search_keys('name:"Moog"').then(function(keys) {
                keys.forEach(function(key) {
                    console.log(typeof key == 'string');
                });
            });
        */
        return self.request('search', {query: query}).get('keys');
    };

    self.save = function(contact) {
        /**:ContactStore.save(contact)

        Saves the given contact to the store, returning a promise that is
        fulfilled once the operation completes.

        :param Contact contact:
            The contact to be saved
        */
        return self
            .request('save', {contact: contact})
            .then(_.noop);
    };
});

this.Contact = Contact;
this.ContactStore = ContactStore;
this.ContactError = ContactError;
<|endoftext|>


<commit_before>// Copyright 2015 ISRG.  All rights reserved
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

/**
 * Crypto Utilities
 * @module crypto-utils
 * @requires crypto
 * @requires node-forge
 * @requires ./utils
 */
'use strict';

var crypto = require('crypto');
var forge  = require('node-forge');
var jose   = require('node-jose');
var util   = require('./utils');

const ONE_DAY_IN_MS = 24 * 3600 * 1000;

// TODO: Have node-jose expose this directly
function convertToForge(key, isPublic) {
  var obj = key.toObject(true);
  var parts = isPublic ?
              ['n', 'e'] :
              ['n', 'e', 'd', 'p', 'q', 'dp', 'dq', 'qi'];
  parts = parts.map(function(f) {
    return new forge.jsbn.BigInteger(obj[f].toString('hex'), 16);
  });

  var fn = isPublic ?
           forge.pki.rsa.setPublicKey :
           forge.pki.rsa.setPrivateKey;
  return fn.apply(forge.pki.rsa, parts);
}

// A note on formats:
// * Keys are always represented as JWKs
// * Signature objects are in ACME format
// * Certs and CSRs are base64-encoded
module.exports = {
  /**
   * Compute the SHA-256 digest of a buffer.
   *
   * @param  {Buffer} input Buffer to be digested
   * @return {Buffer}       The digest value
   */
  sha256: function(input) {
    return crypto.createHash('sha256').update(input).digest();
  },

  /**
   * Generate a public/private key pair.
   *
   * @param  {Integer} bits Number of bits in the key
   * @return {Promise}      Promise(privateKey)
   */
  generateKey: function(bits) {
    var ks = jose.JWK.createKeyStore();
    return ks.generate('RSA', bits);
  },

  /**
   * Parse PEM-encoded private key.
   *
   * @param  {String} pem decode this
   * @return {Promise}     Promise(privateKey)
   */
  importPemPrivateKey: function(pem) {
    return jose.JWK.asKey(pem, 'pem');
  },

  generateSignature: function(priv, nonce, payload) {
    return jose.JWS.createSign({format: 'flattened'}, {
      reference: 'jwk',
      key:       priv,
      header:    {
        nonce: nonce
      }
    })
    .update(JSON.stringify(payload))
    .final();
  },

  verifySignature: function(jws) {
    var obj = jose.parse(jws);
    return jose.JWK.asKeyStore(obj.all.map(o => o.jwk))
    .then(ks => obj.perform(ks))
    .then(v => {
      v.payload = JSON.parse(v.payload);
      return v;
    });
  },

  /**
   * Generate a Certificate Signing Request
   *
   * @param  {Ojbect} keyPair    {privateKey, publicKey}
   * @param  {String} identifier Common Name (CN) for the certificate
   * @return {String}            Base64-encoded CSR
   */
  generateCSR: function(keyPair, identifier) {
    var privateKey = this.importPrivateKey(keyPair.privateKey);
    var publicKey  = this.importPublicKey(keyPair.publicKey);

    // Create and sign the CSR
    var csr = forge.pki.createCertificationRequest();
    csr.publicKey = publicKey;
    csr.setSubject([{ name: 'commonName', value: identifier }]);
    csr.sign(privateKey);

    // Convert CSR -> DER -> Base64
    var der = forge.asn1.toDer(forge.pki.certificationRequestToAsn1(csr));
    return util.b64enc(this.bytesToBuffer(der));
  },

  /**
   * Verify a CSR, and extract the common name from it.
   *
   * @param  {String} csr_b64 Base64-encoded CSR
   * @return {String}         Common Name (CN) or false on error
   */
  verifiedCommonName: function(csr_b64) {
    var der = this.bufferToBytes(util.b64dec(csr_b64));
    var csr = forge.pki.certificationRequestFromAsn1(forge.asn1.fromDer(der));

    if (!csr.verify()) {
      return false;
    }

    for (var i=0; i<csr.subject.attributes.length; ++i) {
      if (csr.subject.attributes[i].name === 'commonName') {
        return csr.subject.attributes[i].value;
      }
    }
    return false;
  },

  /**
   * Create a self-signed certificate with a given name and public key
   *
   * @param  {String} name       Domain name to included in the cert
   * @param  {Object} privateKey JWK private key.  Public key will be put in the
   *                             cert; the private key will be used to sign it.
   * @return {String}            PEM-encoded certificate
   */
  selfSigned: function(name, privateKey) {
    var privKey = convertToForge(privateKey, false);
    var pubKey = convertToForge(privateKey, true);

    var cert = forge.pki.createCertificate();

    // Constants that shouldn't matter
    cert.serialNumber = '01';
    var now = (new Date()).getTime();
    cert.validity.notBefore = new Date(now - ONE_DAY_IN_MS);
    cert.validity.notAfter = new Date(now + 365 * ONE_DAY_IN_MS);

    // Specifics of this certificate
    cert.publicKey = pubKey;
    var attrs = [{ name: 'commonName', value: name }];
    cert.setSubject(attrs);
    cert.setIssuer(attrs);
    cert.setExtensions([{
      name:     'subjectAltName',
      altNames: [{type: 2, value: name}]
    }]);

    cert.sign(privKey, forge.md.sha256.create());
    return forge.pki.certificateToPem(cert);
  }
};
<commit_msg>Better docs for crypto-utils by wrapping in a class<commit_after>// Copyright 2015 ISRG.  All rights reserved
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

/**
 * Crypto Utilities
 * @module crypto-utils
 * @requires crypto
 * @requires node-forge
 * @requires ./utils
 */
'use strict';

var crypto = require('crypto');
var forge  = require('node-forge');
var jose   = require('node-jose');
var util   = require('./utils');

const ONE_DAY_IN_MS = 24 * 3600 * 1000;

// TODO: Have node-jose expose this directly
function convertToForge(key, isPublic) {
  var obj = key.toObject(true);
  var parts = isPublic ?
              ['n', 'e'] :
              ['n', 'e', 'd', 'p', 'q', 'dp', 'dq', 'qi'];
  parts = parts.map(function(f) {
    return new forge.jsbn.BigInteger(obj[f].toString('hex'), 16);
  });

  var fn = isPublic ?
           forge.pki.rsa.setPublicKey :
           forge.pki.rsa.setPrivateKey;
  return fn.apply(forge.pki.rsa, parts);
}

/**
 * Crypto utilities.
 *
 * A note on formats:
 * * Keys are always represented as JWKs
 * * Signature objects are in ACME format
 * * Certs and CSRs are base64-encoded
 */
class AcmeCrypto {
  /**
   * Compute the SHA-256 digest of a buffer.
   *
   * @param  {Buffer} input Buffer to be digested
   * @return {Buffer}       The digest value
   */
  static sha256(input) {
    return crypto.createHash('sha256').update(input).digest();
  }

  /**
   * Generate a public/private key pair.
   *
   * @param  {Integer} bits Number of bits in the key
   * @return {Promise}      Promise(privateKey)
   */
  static generateKey(bits) {
    var ks = jose.JWK.createKeyStore();
    return ks.generate('RSA', bits);
  }

  /**
   * Parse PEM-encoded private key.
   *
   * @param  {String} pem decode this
   * @return {Promise}     Promise(privateKey)
   */
  static importPemPrivateKey(pem) {
    return jose.JWK.asKey(pem, 'pem');
  }

  static generateSignature(priv, nonce, payload) {
    return jose.JWS.createSign({format: 'flattened'}, {
      reference: 'jwk',
      key:       priv,
      header:    {
        nonce: nonce
      }
    })
    .update(JSON.stringify(payload))
    .final();
  }

  static verifySignature(jws) {
    var obj = jose.parse(jws);
    return jose.JWK.asKeyStore(obj.all.map(o => o.jwk))
    .then(ks => obj.perform(ks))
    .then(v => {
      v.payload = JSON.parse(v.payload);
      return v;
    });
  }

  /**
   * Generate a Certificate Signing Request
   *
   * @param  {Ojbect} keyPair    {privateKey, publicKey}
   * @param  {String} identifier Common Name (CN) for the certificate
   * @return {String}            Base64-encoded CSR
   */
  static generateCSR(keyPair, identifier) {
    var privateKey = this.importPrivateKey(keyPair.privateKey);
    var publicKey  = this.importPublicKey(keyPair.publicKey);

    // Create and sign the CSR
    var csr = forge.pki.createCertificationRequest();
    csr.publicKey = publicKey;
    csr.setSubject([{ name: 'commonName', value: identifier }]);
    csr.sign(privateKey);

    // Convert CSR -> DER -> Base64
    var der = forge.asn1.toDer(forge.pki.certificationRequestToAsn1(csr));
    return util.b64enc(this.bytesToBuffer(der));
  }

  /**
   * Verify a CSR, and extract the common name from it.
   *
   * @param  {String} csr_b64 Base64-encoded CSR
   * @return {String}         Common Name (CN) or false on error
   */
  static verifiedCommonName(csr_b64) {
    var der = this.bufferToBytes(util.b64dec(csr_b64));
    var csr = forge.pki.certificationRequestFromAsn1(forge.asn1.fromDer(der));

    if (!csr.verify()) {
      return false;
    }

    for (var i=0; i<csr.subject.attributes.length; ++i) {
      if (csr.subject.attributes[i].name === 'commonName') {
        return csr.subject.attributes[i].value;
      }
    }
    return false;
  }

  /**
   * Create a self-signed certificate with a given name and public key
   *
   * @param  {String} name       Domain name to included in the cert
   * @param  {Object} privateKey JWK private key.  Public key will be put in the
   *                             cert; the private key will be used to sign it.
   * @return {String}            PEM-encoded certificate
   */
  static selfSigned(name, privateKey) {
    var privKey = convertToForge(privateKey, false);
    var pubKey = convertToForge(privateKey, true);

    var cert = forge.pki.createCertificate();

    // Constants that shouldn't matter
    cert.serialNumber = '01';
    var now = (new Date()).getTime();
    cert.validity.notBefore = new Date(now - ONE_DAY_IN_MS);
    cert.validity.notAfter = new Date(now + 365 * ONE_DAY_IN_MS);

    // Specifics of this certificate
    cert.publicKey = pubKey;
    var attrs = [{ name: 'commonName', value: name }];
    cert.setSubject(attrs);
    cert.setIssuer(attrs);
    cert.setExtensions([{
      name:     'subjectAltName',
      altNames: [{type: 2, value: name}]
    }]);

    cert.sign(privKey, forge.md.sha256.create());
    return forge.pki.certificateToPem(cert);
  }
}

module.exports = AcmeCrypto;
<|endoftext|>