<commit_before>'use strict';

module.exports = function multimd_table_plugin(md, pluginOptions) {
  pluginOptions = pluginOptions || {};

  function getLine(state, line) {
    var pos = state.bMarks[line] + state.blkIndent,
        max = state.eMarks[line];
    return state.src.slice(pos, max);
  }

  function escapedSplit(str) {
    var result = [],
      pos = 0,
      max = str.length,
      lastPos = 0,
      escaped = false,
      backTicked = false;

    for (pos = 0; pos < max; pos++) {
      switch (str.charCodeAt(pos)) {
        case 0x5c/* \ */:
          escaped = true;
          break;
        case 0x60/* ` */:
          if (backTicked || !escaped) {
            // make \` close code sequence, but not open it;
            // the reason is: `\` is correct code block
            backTicked = !backTicked;
          }
          escaped = false;
          break;
        case 0x7c/* | */:
          if (!backTicked && !escaped) {
            result.push(str.slice(lastPos, pos));
            lastPos = pos + 1;
          }
          escaped = false;
          break;
        default:
          escaped = false;
          break;
      }
    }

    result.push(str.slice(lastPos));

    return result;
  }

  function countColspan(columns) {
    var i, emptyCount, colspans;

    emptyCount = 0;
    colspans = [];
    for (i = columns.length - 1; i >= 0; i--) {
      if (columns[i]) {
        colspans.unshift(emptyCount + 1);
        emptyCount = 0;
      } else {
        emptyCount++;
      }
    }
    if (emptyCount > 0) {
      colspans.unshift(emptyCount + 1);
    }

    return colspans;
  }

  function caption(state, lineText, lineNum, silent) {
    var captionInfo, result, token;

    result = lineText.match(/^\[([^[\]]+)\](\[([^[\]]+)\])?\s*$/);
    if (!result) { return false; }
    if (silent)  { return true; }

    captionInfo = { caption: null, label: null };
    captionInfo.content = result[1];
    captionInfo.label = result[2] || result[1];

    token          = state.push('caption_open', 'caption', 1);
    token.map      = [ lineNum, lineNum + 1 ];
    token.attrs    = [ [ 'id', captionInfo.label.toLowerCase().replace(/\W+/g, '') ] ];

    token          = state.push('inline', '', 0);
    token.content  = captionInfo.content;
    token.map      = [ lineNum, lineNum + 1 ];
    token.children = [];

    token         = state.push('caption_close', 'caption', -1);

    return captionInfo;
  }

  function appendRowToken(state, content, startLine, endLine) {
    var linesCount, blockParser, tmpState, token;
    linesCount = content.split(/\n/).length;

    if (linesCount > 1) {
      // Multiline content => subparsing as a block to support lists
      blockParser = state.md.block;
      tmpState = new blockParser.State(content, state.md, state.env, state.tokens);
      blockParser.tokenize(tmpState, 0, linesCount); // appends to state.tokens
    } else {
      token          = state.push('inline', '', 0);
      token.content  = content;
      token.map      = [ startLine, endLine ];
      token.children = [];
    }
  }

  function tableRow(state, lineText, lineNum, silent, separatorInfo, rowType) {
    var rowInfo, columns, nextLineText, nextColumn, token, i, col;
    rowInfo = { colspans: null, columns: null, extractedTextLinesCount: 1 };

    columns = escapedSplit(lineText.replace(/^\||([^\\])\|$/g, '$1'));
    // lineText does not contain valid pipe character
    if (columns.length === 1 && !/^\||[^\\]\|$/.test(lineText)) { return false; }
    if (silent) { return true; }

    while (pluginOptions.enableMultilineRows && columns[columns.length - 1].slice(-1) === '\\') {
      columns[columns.length - 1] = columns[columns.length - 1].slice(0, -1);
      nextLineText = getLine(state, lineNum + rowInfo.extractedTextLinesCount);
      nextColumn = escapedSplit(nextLineText.replace(/^\||([^\\])\|$/g, '$1'));
      if (nextColumn.length === 1 && !/^\||[^\\]\|$/.test(nextLineText)) { return false; }
      if (nextColumn.length !== columns.length && nextColumn.length !== columns.length - 1) { return false; }
      for (i = 0; i < nextColumn.length; i++) {
        columns[i] = columns[i].trim() + '\n' + nextColumn[i].trim();
      }
      rowInfo.extractedTextLinesCount += 1;
    }

    isValidColumn = RegExp.prototype.test.bind(/[^\n]/); // = (s => /[^\n]/.test(s))
    rowInfo.columns = columns.filter(isValidColumn);
    rowInfo.colspans = countColspan(columns.map(isValidColumn));

    token     = state.push('tr_open', 'tr', 1);
    token.map = [ lineNum, lineNum + rowInfo.extractedTextLinesCount ];

    for (i = 0, col = 0; i < rowInfo.columns.length && col < separatorInfo.aligns.length;
                         col += rowInfo.colspans[i], i++) {
      token          = state.push(rowType + '_open', rowType, 1);
      token.map      = [ lineNum, lineNum + rowInfo.extractedTextLinesCount ];
      token.attrs    = [];
      if (separatorInfo.aligns[col]) {
        token.attrs.push([ 'style', 'text-align:' + separatorInfo.aligns[col] ]);
      }
      if (separatorInfo.wraps[col]) {
        token.attrs.push([ 'class', 'extend' ]);
      }
      if (rowInfo.colspans[i] > 1) {
        token.attrs.push([ 'colspan', rowInfo.colspans[i] ]);
      }

      appendRowToken(state, rowInfo.columns[i].trim(), lineNum, lineNum + rowInfo.extractedTextLinesCount);

      token          = state.push(rowType + '_close', rowType, -1);
    }

    state.push('tr_close', 'tr', -1);

    return rowInfo;
  }

  function separator(state, lineText, lineNum, silent) {
    var columns, separatorInfo, i, t;

    // lineText have code indentation
    if (state.sCount[lineNum] - state.blkIndent >= 4) { return false; }

    // lineText does not contain valid pipe character
    columns = escapedSplit(lineText.replace(/^\||([^\\])\|$/g, '$1'));
    if (columns.length === 1 && !/^\||[^\\]\|$/.test(lineText)) { return false; }

    separatorInfo = { aligns: [], wraps: [] };

    for (i = 0; i < columns.length; i++) {
      t = columns[i].trim();
      if (!/^:?(-+|=+):?\+?$/.test(t)) { return false; }

      separatorInfo.wraps.push(t.charCodeAt(t.length - 1) === 0x2B/* + */);
      if (separatorInfo.wraps[i]) {
        t = t.slice(0, -1);
      }

      switch (((t.charCodeAt(0)            === 0x3A/* : */) << 4) +
               (t.charCodeAt(t.length - 1) === 0x3A/* : */)) {
        case 0x00: separatorInfo.aligns.push('');       break;
        case 0x01: separatorInfo.aligns.push('right');  break;
        case 0x10: separatorInfo.aligns.push('left');   break;
        case 0x11: separatorInfo.aligns.push('center'); break;
      }
    }

    return silent || separatorInfo;
  }

  function table(state, startLine, endLine, silent) {
    /* Regex pseudo code for table:
     * caption? tableRow+ separator (tableRow+ | empty)* caption?
     */
    var separatorLine, captionAtFirst, captionAtLast, lineText, nextLine,
        rowInfo, separatorInfo, token, tableLines, tbodyLines, emptyTBody;

    if (startLine + 2 > endLine) { return false; }

    captionAtFirst = captionAtLast = false;

    // first line
    lineText = getLine(state, startLine).trim();
    if (caption(state, lineText, startLine, true)) {
      captionAtFirst = true;
    } else if (!tableRow(state, lineText, startLine, true, null, 'tr')) {
      return false;
    }

    // second line ~ separator line
    for (nextLine = startLine + 1; nextLine < endLine; nextLine++) {
      lineText = getLine(state, nextLine).trim();

      if (separator(state, lineText, nextLine, true)) {
        separatorLine = nextLine;
        break;
      } else if (!tableRow(state, lineText, nextLine, true, null, 'th')) {
        return false;
      }
    }
    if (!separatorLine) { return false; }
    if (silent) { return true; }

    token = state.push('table_open', 'table', 1);
    token.map = tableLines = [ startLine, 0 ];

    separatorInfo = separator(state, lineText, separatorLine, false);

    if (captionAtFirst) {
      lineText = getLine(state, startLine).trim();
      caption(state, lineText, startLine, false);
    }

    token     = state.push('thead_open', 'thead', 1);
    token.map = [ startLine + captionAtFirst, separatorLine ];

    nextLine = startLine + captionAtFirst;
    while (nextLine < separatorLine) {
      lineText = getLine(state, nextLine).trim();
      rowInfo = tableRow(state, lineText, nextLine, false, separatorInfo, 'th');
      nextLine += rowInfo.extractedTextLinesCount;
    }

    token     = state.push('thead_close', 'thead', -1);

    token     = state.push('tbody_open', 'tbody', 1);
    token.map = tbodyLines = [ separatorLine + 1, 0 ];
    emptyTBody = true;

    nextLine = separatorLine + 1;
    while (nextLine < endLine) {
      lineText = getLine(state, nextLine).trim();

      if (!captionAtFirst && caption(state, lineText, nextLine, true)) {
        captionAtLast = true;
        break;
      } else {
        rowInfo = tableRow(state, lineText, nextLine, false, separatorInfo, 'td');
        if (rowInfo) {
          emptyTBody = false;
          nextLine += rowInfo.extractedTextLinesCount;
        } else if (!emptyTBody && !lineText) {
          tbodyLines[1] = nextLine - 1;
          token     = state.push('tbody_close', 'tbody', -1);
          token     = state.push('tbody_open', 'tbody', 1);
          token.map = tbodyLines = [ nextLine + 1, 0 ];
          emptyTBody = true;
          nextLine += 1;
        } else {
          break;
        }
      }
    }
    token = state.push('tbody_close', 'tbody', -1);

    if (captionAtLast) {
      caption(state, lineText, nextLine, false);
      nextLine++;
    }

    token = state.push('table_close', 'table', -1);

    tableLines[1] = tbodyLines[1] = nextLine;
    state.line = nextLine;
    return true;
  }

  md.block.ruler.at('table', table, { alt: [ 'paragraph', 'reference' ] });

};

/* vim: set ts=2 sw=2 et: */
<commit_msg>Declare isValidColumn() beforehand<commit_after>'use strict';

module.exports = function multimd_table_plugin(md, pluginOptions) {
  pluginOptions = pluginOptions || {};

  function getLine(state, line) {
    var pos = state.bMarks[line] + state.blkIndent,
        max = state.eMarks[line];
    return state.src.slice(pos, max);
  }

  function escapedSplit(str) {
    var result = [],
      pos = 0,
      max = str.length,
      lastPos = 0,
      escaped = false,
      backTicked = false;

    for (pos = 0; pos < max; pos++) {
      switch (str.charCodeAt(pos)) {
        case 0x5c/* \ */:
          escaped = true;
          break;
        case 0x60/* ` */:
          if (backTicked || !escaped) {
            // make \` close code sequence, but not open it;
            // the reason is: `\` is correct code block
            backTicked = !backTicked;
          }
          escaped = false;
          break;
        case 0x7c/* | */:
          if (!backTicked && !escaped) {
            result.push(str.slice(lastPos, pos));
            lastPos = pos + 1;
          }
          escaped = false;
          break;
        default:
          escaped = false;
          break;
      }
    }

    result.push(str.slice(lastPos));

    return result;
  }

  function countColspan(columns) {
    var i, emptyCount, colspans;

    emptyCount = 0;
    colspans = [];
    for (i = columns.length - 1; i >= 0; i--) {
      if (columns[i]) {
        colspans.unshift(emptyCount + 1);
        emptyCount = 0;
      } else {
        emptyCount++;
      }
    }
    if (emptyCount > 0) {
      colspans.unshift(emptyCount + 1);
    }

    return colspans;
  }

  function caption(state, lineText, lineNum, silent) {
    var captionInfo, result, token;

    result = lineText.match(/^\[([^[\]]+)\](\[([^[\]]+)\])?\s*$/);
    if (!result) { return false; }
    if (silent)  { return true; }

    captionInfo = { caption: null, label: null };
    captionInfo.content = result[1];
    captionInfo.label = result[2] || result[1];

    token          = state.push('caption_open', 'caption', 1);
    token.map      = [ lineNum, lineNum + 1 ];
    token.attrs    = [ [ 'id', captionInfo.label.toLowerCase().replace(/\W+/g, '') ] ];

    token          = state.push('inline', '', 0);
    token.content  = captionInfo.content;
    token.map      = [ lineNum, lineNum + 1 ];
    token.children = [];

    token         = state.push('caption_close', 'caption', -1);

    return captionInfo;
  }

  function appendRowToken(state, content, startLine, endLine) {
    var linesCount, blockParser, tmpState, token;
    linesCount = content.split(/\n/).length;

    if (linesCount > 1) {
      // Multiline content => subparsing as a block to support lists
      blockParser = state.md.block;
      tmpState = new blockParser.State(content, state.md, state.env, state.tokens);
      blockParser.tokenize(tmpState, 0, linesCount); // appends to state.tokens
    } else {
      token          = state.push('inline', '', 0);
      token.content  = content;
      token.map      = [ startLine, endLine ];
      token.children = [];
    }
  }

  function tableRow(state, lineText, lineNum, silent, separatorInfo, rowType) {
    var rowInfo, columns, nextLineText, nextColumn, token, i, col, isValidColumn;
    rowInfo = { colspans: null, columns: null, extractedTextLinesCount: 1 };

    columns = escapedSplit(lineText.replace(/^\||([^\\])\|$/g, '$1'));
    // lineText does not contain valid pipe character
    if (columns.length === 1 && !/^\||[^\\]\|$/.test(lineText)) { return false; }
    if (silent) { return true; }

    while (pluginOptions.enableMultilineRows && columns[columns.length - 1].slice(-1) === '\\') {
      columns[columns.length - 1] = columns[columns.length - 1].slice(0, -1);
      nextLineText = getLine(state, lineNum + rowInfo.extractedTextLinesCount);
      nextColumn = escapedSplit(nextLineText.replace(/^\||([^\\])\|$/g, '$1'));
      if (nextColumn.length === 1 && !/^\||[^\\]\|$/.test(nextLineText)) { return false; }
      if (nextColumn.length !== columns.length && nextColumn.length !== columns.length - 1) { return false; }
      for (i = 0; i < nextColumn.length; i++) {
        columns[i] = columns[i].trim() + '\n' + nextColumn[i].trim();
      }
      rowInfo.extractedTextLinesCount += 1;
    }

    isValidColumn = RegExp.prototype.test.bind(/[^\n]/); // = (s => /[^\n]/.test(s))
    rowInfo.columns = columns.filter(isValidColumn);
    rowInfo.colspans = countColspan(columns.map(isValidColumn));

    token     = state.push('tr_open', 'tr', 1);
    token.map = [ lineNum, lineNum + rowInfo.extractedTextLinesCount ];

    for (i = 0, col = 0; i < rowInfo.columns.length && col < separatorInfo.aligns.length;
                         col += rowInfo.colspans[i], i++) {
      token          = state.push(rowType + '_open', rowType, 1);
      token.map      = [ lineNum, lineNum + rowInfo.extractedTextLinesCount ];
      token.attrs    = [];
      if (separatorInfo.aligns[col]) {
        token.attrs.push([ 'style', 'text-align:' + separatorInfo.aligns[col] ]);
      }
      if (separatorInfo.wraps[col]) {
        token.attrs.push([ 'class', 'extend' ]);
      }
      if (rowInfo.colspans[i] > 1) {
        token.attrs.push([ 'colspan', rowInfo.colspans[i] ]);
      }

      appendRowToken(state, rowInfo.columns[i].trim(), lineNum, lineNum + rowInfo.extractedTextLinesCount);

      token          = state.push(rowType + '_close', rowType, -1);
    }

    state.push('tr_close', 'tr', -1);

    return rowInfo;
  }

  function separator(state, lineText, lineNum, silent) {
    var columns, separatorInfo, i, t;

    // lineText have code indentation
    if (state.sCount[lineNum] - state.blkIndent >= 4) { return false; }

    // lineText does not contain valid pipe character
    columns = escapedSplit(lineText.replace(/^\||([^\\])\|$/g, '$1'));
    if (columns.length === 1 && !/^\||[^\\]\|$/.test(lineText)) { return false; }

    separatorInfo = { aligns: [], wraps: [] };

    for (i = 0; i < columns.length; i++) {
      t = columns[i].trim();
      if (!/^:?(-+|=+):?\+?$/.test(t)) { return false; }

      separatorInfo.wraps.push(t.charCodeAt(t.length - 1) === 0x2B/* + */);
      if (separatorInfo.wraps[i]) {
        t = t.slice(0, -1);
      }

      switch (((t.charCodeAt(0)            === 0x3A/* : */) << 4) +
               (t.charCodeAt(t.length - 1) === 0x3A/* : */)) {
        case 0x00: separatorInfo.aligns.push('');       break;
        case 0x01: separatorInfo.aligns.push('right');  break;
        case 0x10: separatorInfo.aligns.push('left');   break;
        case 0x11: separatorInfo.aligns.push('center'); break;
      }
    }

    return silent || separatorInfo;
  }

  function table(state, startLine, endLine, silent) {
    /* Regex pseudo code for table:
     * caption? tableRow+ separator (tableRow+ | empty)* caption?
     */
    var separatorLine, captionAtFirst, captionAtLast, lineText, nextLine,
        rowInfo, separatorInfo, token, tableLines, tbodyLines, emptyTBody;

    if (startLine + 2 > endLine) { return false; }

    captionAtFirst = captionAtLast = false;

    // first line
    lineText = getLine(state, startLine).trim();
    if (caption(state, lineText, startLine, true)) {
      captionAtFirst = true;
    } else if (!tableRow(state, lineText, startLine, true, null, 'tr')) {
      return false;
    }

    // second line ~ separator line
    for (nextLine = startLine + 1; nextLine < endLine; nextLine++) {
      lineText = getLine(state, nextLine).trim();

      if (separator(state, lineText, nextLine, true)) {
        separatorLine = nextLine;
        break;
      } else if (!tableRow(state, lineText, nextLine, true, null, 'th')) {
        return false;
      }
    }
    if (!separatorLine) { return false; }
    if (silent) { return true; }

    token = state.push('table_open', 'table', 1);
    token.map = tableLines = [ startLine, 0 ];

    separatorInfo = separator(state, lineText, separatorLine, false);

    if (captionAtFirst) {
      lineText = getLine(state, startLine).trim();
      caption(state, lineText, startLine, false);
    }

    token     = state.push('thead_open', 'thead', 1);
    token.map = [ startLine + captionAtFirst, separatorLine ];

    nextLine = startLine + captionAtFirst;
    while (nextLine < separatorLine) {
      lineText = getLine(state, nextLine).trim();
      rowInfo = tableRow(state, lineText, nextLine, false, separatorInfo, 'th');
      nextLine += rowInfo.extractedTextLinesCount;
    }

    token     = state.push('thead_close', 'thead', -1);

    token     = state.push('tbody_open', 'tbody', 1);
    token.map = tbodyLines = [ separatorLine + 1, 0 ];
    emptyTBody = true;

    nextLine = separatorLine + 1;
    while (nextLine < endLine) {
      lineText = getLine(state, nextLine).trim();

      if (!captionAtFirst && caption(state, lineText, nextLine, true)) {
        captionAtLast = true;
        break;
      } else {
        rowInfo = tableRow(state, lineText, nextLine, false, separatorInfo, 'td');
        if (rowInfo) {
          emptyTBody = false;
          nextLine += rowInfo.extractedTextLinesCount;
        } else if (!emptyTBody && !lineText) {
          tbodyLines[1] = nextLine - 1;
          token     = state.push('tbody_close', 'tbody', -1);
          token     = state.push('tbody_open', 'tbody', 1);
          token.map = tbodyLines = [ nextLine + 1, 0 ];
          emptyTBody = true;
          nextLine += 1;
        } else {
          break;
        }
      }
    }
    token = state.push('tbody_close', 'tbody', -1);

    if (captionAtLast) {
      caption(state, lineText, nextLine, false);
      nextLine++;
    }

    token = state.push('table_close', 'table', -1);

    tableLines[1] = tbodyLines[1] = nextLine;
    state.line = nextLine;
    return true;
  }

  md.block.ruler.at('table', table, { alt: [ 'paragraph', 'reference' ] });

};

/* vim: set ts=2 sw=2 et: */
<|endoftext|>


<commit_before>// -*- indent-tabs-mode: t; -*-

'use strict';
var async        = require('async');
var asyncReplace = require('async-replace');
var gutil        = require('gulp-util');
var through      = require('through2');
var cheerio      = require('cheerio');
var replace      = require('gulp-replace');
var fs           = require('fs');
var path         = require('path');
var url          = require('url');

function isLocal(link) {
	return link && ! url.parse(link).hostname;
}

module.exports = function(options) {

	var cssTags = options && options.cssTags ? options.cssTags : {begin: '<style>', end: '</style>'};
	var jsTags = options && options.jsTags ? options.jsTags : {begin: '<script>', end: '</script>'};
	var base = options && options.base;

	// create a stream through which each file will pass
	return through.obj(function(file, enc, callback) {

		if (file.isNull()) {
			this.push(file); // do nothing if no contents
			return callback();
		}

		if (file.isStream()) {
			this.emit('error', new gutil.PluginError('gulp-smoosher', 'Streaming not supported'));
			return callback();
		}

		if (file.isBuffer()) {

			var rePattern = /<!--(| )smoosh(| )-->([\s\S]*?)<!--(| )endsmoosh(| )-->/g;

			var input = String(file.contents);

			var readFileBase = base !== undefined ? base : file.base;

			asyncReplace(input, rePattern, function(match) {

				// Last argument to asyncReplace is always the callback.
				var callback = Array.prototype.slice.call(arguments).pop();

				var $ = cheerio.load(match);

				// Reduce replaceable elements to an array so we can iterate over them.
				var elements = [$('link'), $('script')].map(function toArray(elements) {
					return Array.prototype.slice.call(elements);
				}).reduce(function flatten(a, b) {
					return a.concat(b);
				});

				// Asynchronously replace the contents of each element in this
				// comment block.
				async.each(elements, function iterator(element, callback) {

					var $element = $(element);
					var url, tags;
					if ($element.is('link')) {
						url = $element.attr('href');
						tags = cssTags;
					} else if ($element.is('script')) {
						url = $element.attr('src');
						tags = jsTags;
					}
					if (isLocal(url)) {
						fs.readFile(path.join(readFileBase, url), function onRead(error, data) {
							if (error) {
								return callback(error);
							}
							$(element).replaceWith(tags.begin + data + tags.end);
							callback(null);
						});
					} else {
						callback(null);
					}

				}, function done(error) {

					if (error) {
						return callback(error);
					}
					// "Return" the replacement for asyncReplace.
					callback(null, $.html());
				});

			}, function done(error, output) {

				if (error) {
					return callback(error);
				}

				output = output.replace(/([ ]*)<!--(| )smoosh(| )-->([ ]*)(\n?)|([ ]*)<!--(| )endsmoosh(| )-->([ ]*)(\n?)/g, '');

				file.contents = new Buffer(output);

				callback(null, file);
			});
		}

	});
};
<commit_msg>Refactor regular expressions.<commit_after>// -*- indent-tabs-mode: t; -*-

'use strict';
var async        = require('async');
var asyncReplace = require('async-replace');
var gutil        = require('gulp-util');
var through      = require('through2');
var cheerio      = require('cheerio');
var replace      = require('gulp-replace');
var fs           = require('fs');
var path         = require('path');
var url          = require('url');

// Reusable regular expression snippets.
var anythingLazy = '([\\s\\S]*?)';
var spaces = '( *)';
var optionalNewline = '(\n?)';
var startSmoosh = ['<!--', anythingLazy, 'smoosh', anythingLazy, '-->'].join('');
var endSmoosh = ['<!--', anythingLazy, 'endsmoosh', anythingLazy, '-->'].join('');

// Used to replace anything within startSmoosh and endSmoosh.
var replaceRegExp = new RegExp([startSmoosh, anythingLazy, endSmoosh].join(''), 'g');

// Used to remove startSmooshs and endSmooshs.
var removeRegExp = new RegExp([
	[spaces, startSmoosh, spaces, optionalNewline].join(''),
	[spaces, endSmoosh, spaces, optionalNewline].join('')
].join('|'), 'g');

function isLocal(link) {
	return link && ! url.parse(link).hostname;
}

module.exports = function(options) {

	var cssTags = options && options.cssTags ? options.cssTags : {begin: '<style>', end: '</style>'};
	var jsTags = options && options.jsTags ? options.jsTags : {begin: '<script>', end: '</script>'};
	var base = options && options.base;

	// create a stream through which each file will pass
	return through.obj(function(file, enc, callback) {

		if (file.isNull()) {
			this.push(file); // do nothing if no contents
			return callback();
		}

		if (file.isStream()) {
			this.emit('error', new gutil.PluginError('gulp-smoosher', 'Streaming not supported'));
			return callback();
		}

		if (file.isBuffer()) {

			var input = String(file.contents);

			var readFileBase = base !== undefined ? base : file.base;

			asyncReplace(input, replaceRegExp, function(match) {

				// Last argument to asyncReplace is always the callback.
				var callback = Array.prototype.slice.call(arguments).pop();

				var $ = cheerio.load(match);

				// Reduce replaceable elements to an array so we can iterate over them.
				var elements = [$('link'), $('script')].map(function toArray(elements) {
					return Array.prototype.slice.call(elements);
				}).reduce(function flatten(a, b) {
					return a.concat(b);
				});

				// Asynchronously replace the contents of each element in this
				// comment block.
				async.each(elements, function iterator(element, callback) {

					var $element = $(element);
					var url, tags;
					if ($element.is('link')) {
						url = $element.attr('href');
						tags = cssTags;
					} else if ($element.is('script')) {
						url = $element.attr('src');
						tags = jsTags;
					}
					if (isLocal(url)) {
						fs.readFile(path.join(readFileBase, url), function onRead(error, data) {
							if (error) {
								return callback(error);
							}
							$(element).replaceWith(tags.begin + data + tags.end);
							callback(null);
						});
					} else {
						callback(null);
					}

				}, function done(error) {

					if (error) {
						return callback(error);
					}
					// "Return" the replacement for asyncReplace.
					callback(null, $.html());
				});

			}, function done(error, output) {

				if (error) {
					return callback(error);
				}

				output = output.replace(removeRegExp, '');

				file.contents = new Buffer(output);

				callback(null, file);
			});
		}

	});
};
<|endoftext|>


<commit_before>'use strict';
(function () {

  var crypto = require('crypto');
  var _ = require('underscore');
  var mongoose = require('mongoose');
  var ObjectId = mongoose.Types.ObjectId;
  var stableStringify = require('json-stable-stringify');
  var async = require('async');
  var dotty = require('dotty');
  var bufferEqual = require('buffer-equal-constant-time');

  // Constants //

  var ENCRYPTION_ALGORITHM = 'aes-256-cbc';
  var IV_LENGTH = 16;
  var AAC_LENGTH = 32;

  var VERSION_LENGTH = 1;

  var VERSION = 'a';
  var VERSION_BUF = new Buffer(VERSION);

  // Utility Functions //

  var isEmbeddedDocument = function (doc) {
    return doc.constructor.name === 'EmbeddedDocument';
  };

  var deriveKey = function (master, type) {
    var hmac = crypto.createHmac('sha512', master);
    hmac.update(type);
    return new Buffer(hmac.digest());
  };

  var clearBuffer = function (buf) {
    for (var i = 0; i < buf.length; i++) {
      buf[i] = 0;
    }
  };

  var drop256 = function (buf) {
    var buf256 = new Buffer(32);
    buf.copy(buf256, 0, 0, 32);
    clearBuffer(buf);
    return buf256;
  };

  var decryptEmbeddedDocs = function(doc) {
    _.keys(doc.schema.paths).forEach(function(path) {
      if (path === '_id' || path === '__v') {
        return;
      }

      var nestedDoc = dotty.get(doc, path);

      if (nestedDoc && nestedDoc[0] && isEmbeddedDocument(nestedDoc[0])) {
        nestedDoc.forEach(function(subDoc) {
          if (_.isFunction(subDoc.decryptSync)){
            subDoc.decryptSync();
          }
        });
      }
    });
  };

  // Exported Plugin //

  var mongoosePlugin = module.exports = function(schema, options) {
    var details, encryptedFields, excludedFields, authenticatedFields, encryptionKey, signingKey, path;

    _.defaults(options, {
      middleware: true, // allow for skipping middleware with false
      requireAuthenticationCode: true, // allow for no authentication code on docs (not recommended),
      decryptPostSave: true // allow for skipping the decryption after save for improved performance
    });

    // Encryption Keys //

    if (options.secret) {
      if (options.encryptionKey || options.signingKey) {
        throw new Error('if options.secret is used, then options.encryptionKey and options.signingKey must not be included');
      } else {
        encryptionKey = drop256(deriveKey(options.secret, 'enc'));
        signingKey = deriveKey(options.secret, 'sig');
      }
    } else {
      if (!options.encryptionKey || !options.signingKey) {
        throw new Error('must provide either options.secret or both options.encryptionKey and options.signingKey');
      } else {
        encryptionKey = new Buffer(options.encryptionKey, 'base64');
        if (encryptionKey.length !== 32) {
          throw new Error('options.encryptionKey must be a a 32 byte base64 string');
        }
        signingKey = new Buffer(options.signingKey, 'base64');
        if (signingKey.length !== 64) {
          throw new Error('options.signingKey must be a a 64 byte base64 string');
        }
      }
    }

    // Deprecated options

    if (options.fields) {
      options.encryptedFields = options.fields;
      console.warn('options.fields has been deprecated. please use options.encryptedFields');
    }
    if (options.exclude) {
      options.excludeFromEncryption = options.exclude;
      console.warn('options.fields has been deprecated. please use options.excludeFromEncryption');
    }

    // Check no disallowed characters used in options

    var fieldsUsedInOptions = _.compact(_.union(
        options.encryptedFields,
        options.excludeFromEncryption,
        options.additionalAuthenticatedFields
      ));

    if (_.any(fieldsUsedInOptions, function(field){ return field.indexOf('.') !== -1 })) {
      throw new Error("Field names containing '.' are not currently supported")
    }

    // Encryption Options //

    if (options.encryptedFields) {
      encryptedFields = _.difference(options.encryptedFields, ['_ct']);
    } else {
      excludedFields = _.union(['_id', '_ct'], options.excludeFromEncryption);
      encryptedFields = _.chain(schema.paths)
        .filter(function(pathDetails) { // exclude indexed fields
          return !pathDetails._index })
        .pluck('path') // get path name
        .difference(excludedFields) // exclude excluded fields
        .map(function(path) { // get the top level field
          return path.split('.')[0]
        })
        .uniq()
        .value()
    }

    // Authentication Options //

    var baselineAuthenticateFields = ['_id', '_ct'];

    if (options.additionalAuthenticatedFields) {
      authenticatedFields = _.union(options.additionalAuthenticatedFields, baselineAuthenticateFields);
    } else {
      authenticatedFields = baselineAuthenticateFields;
    }

    // Augment Schema //

    if (!schema.paths._ct) { // ciphertext
      schema.add({
        _ct: {
          type: Buffer
        }
      });
    }
    if (!schema.paths._ac) { // authentication code
      schema.add({
        _ac: {
          type: Buffer
        }
      });
    }

    // Authentication Helper Functions //

    var computeAC = function(doc, fields, version, modelName) {
      // HMAC-SHA512-drop-256
      var hmac = crypto.createHmac('sha512', signingKey);

      if (!(fields instanceof Array)){
        throw new Error('fields must be an array');
      }
      if (fields.indexOf('_id') === -1) {
        throw new Error('_id must be in array of fields to authenticate');
      }
      if (fields.indexOf('_ac') !== -1) {
        throw new Error('_ac cannot be in array of fields to authenticate');
      }

      var collectionId = options.collectionId || modelName || doc.constructor.modelName;

      if (!collectionId) {
        throw new Error('For authentication, each collection must have a unique id. This is normally the model name when there is one, but can be overridden or added by options.collectionId');
      }

      // convert to regular object if possible in order to convert to the eventual mongo form which may be different than mongoose form
      // and only pick fields that will be authenticated
      var objectToAuthenticate = _.pick((doc.toObject ? doc.toObject() : doc), fields);
      var stringToAuthenticate = stableStringify(objectToAuthenticate);
      hmac.update(collectionId);
      hmac.update(version);
      hmac.update(stringToAuthenticate);
      hmac.update(JSON.stringify(fields));
      var fullAuthenticationBuffer = new Buffer(hmac.digest());
      return drop256(fullAuthenticationBuffer);
    };

    // Functions To Check If Authenticated Fields Were Selected By Query //

    var authenticationFieldsToCheck = _.chain(authenticatedFields).union(['_ac']).without('_id').value(); // _id is implicitly selected

    var authenticatedFieldsIsSelected = function(doc){
      return _.map(authenticationFieldsToCheck, function(field) {return doc.isSelected(field);});
    };

    var allAuthenticationFieldsSelected = function(doc){
      var isSelected = authenticatedFieldsIsSelected(doc);
      if (_.uniq(isSelected).length === 1){
        return isSelected[0];
      } else {
        return false;
      }
    };

    var noAuthenticationFieldsSelected = function(doc){
      var isSelected = authenticatedFieldsIsSelected(doc);
      if (_.uniq(isSelected).length === 1){
        return isSelected[0] === false;
      } else {
        return false;
      }
    };

    // Middleware //

    if (options.middleware) { // defaults to true
      schema.pre('init', function(next, data) {
          var err = null;
          try { // this hook must be synchronous for embedded docs, so everything is synchronous for code simplicity
            if (!isEmbeddedDocument(this)){ // don't authenticate embedded docs because there's no way to handle the error appropriately
              if (allAuthenticationFieldsSelected(this)) {
                this.authenticateSync.call(data, this.constructor.modelName);
              } else {
                if (!noAuthenticationFieldsSelected(this)){
                  throw new Error("Authentication failed: Only some authenticated fields were selected by the query. Either all or none of the authenticated fields (" + authenticationFieldsToCheck + ") should be selected for proper authentication.");
                }
              }
            }
            if (this.isSelected('_ct')){
              this.decryptSync.call(data);
            }
          } catch (e) {
            err = e;
          }

          if (isEmbeddedDocument(this)) {
            if (err) {
              console.error(err);
              throw err; // note: this won't actually get thrown until save, because errors in subdoc init fns are CastErrors and aren't thrown by validate()
            }
            this._doc = data;
            return this;
          } else {
            return next(err);
          }
      });

      schema.pre('save', function(next) {
        if (this.isNew || this.isSelected('_ct') ){
          var that = this;
          that.encrypt(function(err){
            if (err) {
              next(err);
            } else {
              if ((that.isNew || allAuthenticationFieldsSelected(that)) && !isEmbeddedDocument(that)) {
                that.sign(next);
              } else {
                next();
              }
            }
          });
        } else if (allAuthenticationFieldsSelected(this) && !isEmbeddedDocument(this)) { // _ct is not selected but all authenticated fields are. cannot get hit in current version.
          this.sign(next);
        } else {
          next();
        }
      });

      if (options.decryptPostSave) { // true by default
        schema.post('save', function(doc) {
          if (_.isFunction(doc.decryptSync)) {
            doc.decryptSync();
          }

          // Until 3.8.6, Mongoose didn't trigger post save hook on EmbeddedDocuments,
          // instead had to call decrypt on all subDocs.
          // ref https://github.com/LearnBoost/mongoose/issues/915

          decryptEmbeddedDocs(doc);

          return doc;
        });
      }
    }

    // Encryption Instance Methods //

    schema.methods.encrypt = function(cb) {
      var that = this;
      // generate random iv
      crypto.randomBytes(IV_LENGTH, function(err, iv) {
        var cipher, field, jsonToEncrypt, objectToEncrypt, val;
        if (err) {
          return cb(err);
        }
        cipher = crypto.createCipheriv(ENCRYPTION_ALGORITHM, encryptionKey, iv);
        objectToEncrypt = _.pick(that, encryptedFields);

        // only encrypt fields that are defined
        for (field in objectToEncrypt) {
          val = objectToEncrypt[field];
          if (val === undefined) {
            delete objectToEncrypt[field];
          }
        }
        jsonToEncrypt = JSON.stringify(objectToEncrypt);

        cipher.end(jsonToEncrypt, 'utf-8', function() {
          // add ciphertext to document
          that._ct = Buffer.concat([VERSION_BUF, iv, cipher.read()]);

          // remove encrypted fields from cleartext
          encryptedFields.forEach(function(field){
            that[field] = undefined;
          });

          cb(null);
        });
      });
    };

    schema.methods.decrypt = function(cb) { // callback style but actually synchronous to allow for decryptSync without copypasta or complication
      try {
        schema.methods.decryptSync.call(this);
      } catch(e){
        return cb(e);
      }
      cb();
    };

    schema.methods.decryptSync = function() {
      var ct, ctWithIV, decipher, iv, idString, decryptedObject, decryptedObjectJSON, decipheredVal;
      if (this._ct) {
        ctWithIV = this._ct.buffer || this._ct;
        iv = ctWithIV.slice(VERSION_LENGTH, VERSION_LENGTH + IV_LENGTH);
        ct = ctWithIV.slice(VERSION_LENGTH + IV_LENGTH, ctWithIV.length);
        decipher = crypto.createDecipheriv(ENCRYPTION_ALGORITHM, encryptionKey, iv);
        try {
          decryptedObjectJSON = decipher.update(ct, undefined, 'utf8') + decipher.final('utf8');
          decryptedObject = JSON.parse(decryptedObjectJSON);
        } catch (err) {
          if (this._id) {
            idString = this._id.toString();
          } else {
            idString = 'unknown';
          }
          throw new Error('Error parsing JSON during decrypt of ' + idString + ': ' + err);
        }
        for (var field in decryptedObject) {
          decipheredVal = decryptedObject[field];
          this[field] = decipheredVal;
        }
        this._ct = undefined;
      }
    };

    // Authentication Instance Methods //

    schema.methods.sign = function(cb) {
      var basicAC = computeAC(this, authenticatedFields, VERSION);
      var authenticatedFieldsBuf = new Buffer(JSON.stringify(authenticatedFields));
      this._ac = Buffer.concat([VERSION_BUF, basicAC, authenticatedFieldsBuf]);
      cb();
    };

    schema.methods.authenticate = function(cb) { // callback style but actually synchronous to allow for decryptSync without copypasta or complication
      try {
        schema.methods.authenticateSync.call(this);
      } catch(e){
        return cb(e);
      }
      cb();
    };

    schema.methods.authenticateSync = function() {
      if (!this._ac) {
        if (options.requireAuthenticationCode) {
          throw new Error('Authentication code missing');
        } else {
          return null;
        }
      }
      var acBuf = this._ac.buffer || this._ac;
      if (acBuf.length < VERSION_LENGTH + AAC_LENGTH + 2) {
        throw new Error('_ac is too short and has likely been cut off or modified');
      }
      var versionUsed = acBuf.slice(0, VERSION_LENGTH).toString();
      var basicAC = acBuf.slice(VERSION_LENGTH, VERSION_LENGTH + AAC_LENGTH);
      var authenticatedFieldsUsed = JSON.parse(acBuf.slice(VERSION_LENGTH + AAC_LENGTH, acBuf.length).toString());

      var expectedHMAC = computeAC(this, authenticatedFieldsUsed, versionUsed, arguments[0]); // pass in modelName as argument in init hook

      var authentic = bufferEqual(basicAC, expectedHMAC);
      if (authentic){
        this._ac = undefined;
        return null;
      } else {
        throw new Error('Authentication failed');
      }
    };
  };

  // Exports For Schemas That Contain Encrypted Embedded Documents

  // this ensures that if parent has a validation error, children don't come out encrypted,
  // which could otherwise cause data loss if validation error fixed and a resave was attempted
  // For use in conjunction with the main encryption plugin
  module.exports.encryptedChildren = function(schema, options) {
    schema.post('validate', function(doc) {
      if (doc.errors) {
        decryptEmbeddedDocs(doc);
      }
    });
  };

  module.exports.migrations = function(schema, options) {
    options.middleware = false; // don't run middleware during the migration
    mongoosePlugin(schema, options); // get all instance methods

    schema.statics.migrateToA = function(cb) {
      this.find({}, function(err, docs){ // find all docs in collection
        if (err) {
          return cb(err);
        }
        async.each(docs, function(doc, errCb){ // for each doc
          if (doc._ac) { // don't migrate if already migrated
            return errCb();
          }
          if (doc._ct) { // if previously encrypted
            doc._ct = Buffer.concat([VERSION_BUF, doc._ct]); // append version to ciphertext
            doc.sign(function(err){ // sign
              if (err) {
                return errCb(err);
              }
              return doc.save(errCb); // save
            });
          } else { // if not previously encrypted
            doc.encrypt(function(err){ // encrypt
              if (err) {
                return errCb(err);
              }
              doc.sign(function(err){ // sign
                if (err) {
                  return errCb(err);
                }
                return doc.save(errCb); // save
              });
            });
          }
        }, cb);
      });
    };

    schema.statics.migrateSubDocsToA = function(subDocField, cb) {
      if (typeof subDocField !== 'string'){
        cb(new Error('First argument must be the name of a field in which subdocuments are stored'));
      }
      this.find({}, function(err, docs){ // find all docs in collection
        if (err) {
          return cb(err);
        }
        async.each(docs, function(doc, errCb){ // for each doc
          if (doc[subDocField]) {
            _.each(doc[subDocField], function(subDoc){ // for each subdoc
              if (subDoc._ct) { // if previously encrypted
                subDoc._ct = Buffer.concat([VERSION_BUF, subDoc._ct]); // append version to ciphertext
              }
            });
            return doc.save(errCb); // save
          } else {
            errCb()
          }
        }, cb);
      });
    };

    // sign all the documents in a collection
    schema.statics.signAll = function(cb) {
      this.find({}, function(err, docs){ // find all docs in collection
        if (err) {
          return cb(err);
        }
        async.each(docs, function(doc, errCb){ // for each doc
          doc.sign(function(err){ // sign
            if (err) {
              return errCb(err);
            }
            doc.save(errCb); // save
          });
        }, cb);
      });
    };
  };

  // Exports For Tests //
  module.exports.AAC_LENGTH = AAC_LENGTH;
  module.exports.VERSION_LENGTH = VERSION_LENGTH;

}).call(this);
<commit_msg>marks all authenticated fields as modified when saving<commit_after>'use strict';
(function () {

  var crypto = require('crypto');
  var _ = require('underscore');
  var mongoose = require('mongoose');
  var ObjectId = mongoose.Types.ObjectId;
  var stableStringify = require('json-stable-stringify');
  var async = require('async');
  var dotty = require('dotty');
  var bufferEqual = require('buffer-equal-constant-time');

  // Constants //

  var ENCRYPTION_ALGORITHM = 'aes-256-cbc';
  var IV_LENGTH = 16;
  var AAC_LENGTH = 32;

  var VERSION_LENGTH = 1;

  var VERSION = 'a';
  var VERSION_BUF = new Buffer(VERSION);

  // Utility Functions //

  var isEmbeddedDocument = function (doc) {
    return doc.constructor.name === 'EmbeddedDocument';
  };

  var deriveKey = function (master, type) {
    var hmac = crypto.createHmac('sha512', master);
    hmac.update(type);
    return new Buffer(hmac.digest());
  };

  var clearBuffer = function (buf) {
    for (var i = 0; i < buf.length; i++) {
      buf[i] = 0;
    }
  };

  var drop256 = function (buf) {
    var buf256 = new Buffer(32);
    buf.copy(buf256, 0, 0, 32);
    clearBuffer(buf);
    return buf256;
  };

  var decryptEmbeddedDocs = function(doc) {
    _.keys(doc.schema.paths).forEach(function(path) {
      if (path === '_id' || path === '__v') {
        return;
      }

      var nestedDoc = dotty.get(doc, path);

      if (nestedDoc && nestedDoc[0] && isEmbeddedDocument(nestedDoc[0])) {
        nestedDoc.forEach(function(subDoc) {
          if (_.isFunction(subDoc.decryptSync)){
            subDoc.decryptSync();
          }
        });
      }
    });
  };

  // Exported Plugin //

  var mongoosePlugin = module.exports = function(schema, options) {
    var details, encryptedFields, excludedFields, authenticatedFields, encryptionKey, signingKey, path;

    _.defaults(options, {
      middleware: true, // allow for skipping middleware with false
      requireAuthenticationCode: true, // allow for no authentication code on docs (not recommended),
      decryptPostSave: true // allow for skipping the decryption after save for improved performance
    });

    // Encryption Keys //

    if (options.secret) {
      if (options.encryptionKey || options.signingKey) {
        throw new Error('if options.secret is used, then options.encryptionKey and options.signingKey must not be included');
      } else {
        encryptionKey = drop256(deriveKey(options.secret, 'enc'));
        signingKey = deriveKey(options.secret, 'sig');
      }
    } else {
      if (!options.encryptionKey || !options.signingKey) {
        throw new Error('must provide either options.secret or both options.encryptionKey and options.signingKey');
      } else {
        encryptionKey = new Buffer(options.encryptionKey, 'base64');
        if (encryptionKey.length !== 32) {
          throw new Error('options.encryptionKey must be a a 32 byte base64 string');
        }
        signingKey = new Buffer(options.signingKey, 'base64');
        if (signingKey.length !== 64) {
          throw new Error('options.signingKey must be a a 64 byte base64 string');
        }
      }
    }

    // Deprecated options

    if (options.fields) {
      options.encryptedFields = options.fields;
      console.warn('options.fields has been deprecated. please use options.encryptedFields');
    }
    if (options.exclude) {
      options.excludeFromEncryption = options.exclude;
      console.warn('options.fields has been deprecated. please use options.excludeFromEncryption');
    }

    // Check no disallowed characters used in options

    var fieldsUsedInOptions = _.compact(_.union(
        options.encryptedFields,
        options.excludeFromEncryption,
        options.additionalAuthenticatedFields
      ));

    if (_.any(fieldsUsedInOptions, function(field){ return field.indexOf('.') !== -1 })) {
      throw new Error("Field names containing '.' are not currently supported")
    }

    // Encryption Options //

    if (options.encryptedFields) {
      encryptedFields = _.difference(options.encryptedFields, ['_ct']);
    } else {
      excludedFields = _.union(['_id', '_ct'], options.excludeFromEncryption);
      encryptedFields = _.chain(schema.paths)
        .filter(function(pathDetails) { // exclude indexed fields
          return !pathDetails._index })
        .pluck('path') // get path name
        .difference(excludedFields) // exclude excluded fields
        .map(function(path) { // get the top level field
          return path.split('.')[0]
        })
        .uniq()
        .value()
    }

    // Authentication Options //

    var baselineAuthenticateFields = ['_id', '_ct'];

    if (options.additionalAuthenticatedFields) {
      authenticatedFields = _.union(options.additionalAuthenticatedFields, baselineAuthenticateFields);
    } else {
      authenticatedFields = baselineAuthenticateFields;
    }

    // Augment Schema //

    if (!schema.paths._ct) { // ciphertext
      schema.add({
        _ct: {
          type: Buffer
        }
      });
    }
    if (!schema.paths._ac) { // authentication code
      schema.add({
        _ac: {
          type: Buffer
        }
      });
    }

    // Authentication Helper Functions //

    var computeAC = function(doc, fields, version, modelName) {
      // HMAC-SHA512-drop-256
      var hmac = crypto.createHmac('sha512', signingKey);

      if (!(fields instanceof Array)){
        throw new Error('fields must be an array');
      }
      if (fields.indexOf('_id') === -1) {
        throw new Error('_id must be in array of fields to authenticate');
      }
      if (fields.indexOf('_ac') !== -1) {
        throw new Error('_ac cannot be in array of fields to authenticate');
      }

      var collectionId = options.collectionId || modelName || doc.constructor.modelName;

      if (!collectionId) {
        throw new Error('For authentication, each collection must have a unique id. This is normally the model name when there is one, but can be overridden or added by options.collectionId');
      }

      // convert to regular object if possible in order to convert to the eventual mongo form which may be different than mongoose form
      // and only pick fields that will be authenticated
      var objectToAuthenticate = _.pick((doc.toObject ? doc.toObject() : doc), fields);
      var stringToAuthenticate = stableStringify(objectToAuthenticate);
      hmac.update(collectionId);
      hmac.update(version);
      hmac.update(stringToAuthenticate);
      hmac.update(JSON.stringify(fields));
      var fullAuthenticationBuffer = new Buffer(hmac.digest());
      return drop256(fullAuthenticationBuffer);
    };

    // Functions To Check If Authenticated Fields Were Selected By Query //

    var authenticationFieldsToCheck = _.chain(authenticatedFields).union(['_ac']).without('_id').value(); // _id is implicitly selected

    var authenticatedFieldsIsSelected = function(doc){
      return _.map(authenticationFieldsToCheck, function(field) {return doc.isSelected(field);});
    };

    var allAuthenticationFieldsSelected = function(doc){
      var isSelected = authenticatedFieldsIsSelected(doc);
      if (_.uniq(isSelected).length === 1){
        return isSelected[0];
      } else {
        return false;
      }
    };

    var noAuthenticationFieldsSelected = function(doc){
      var isSelected = authenticatedFieldsIsSelected(doc);
      if (_.uniq(isSelected).length === 1){
        return isSelected[0] === false;
      } else {
        return false;
      }
    };

    // Middleware //

    if (options.middleware) { // defaults to true
      schema.pre('init', function(next, data) {
          var err = null;
          try { // this hook must be synchronous for embedded docs, so everything is synchronous for code simplicity
            if (!isEmbeddedDocument(this)){ // don't authenticate embedded docs because there's no way to handle the error appropriately
              if (allAuthenticationFieldsSelected(this)) {
                this.authenticateSync.call(data, this.constructor.modelName);
              } else {
                if (!noAuthenticationFieldsSelected(this)){
                  throw new Error("Authentication failed: Only some authenticated fields were selected by the query. Either all or none of the authenticated fields (" + authenticationFieldsToCheck + ") should be selected for proper authentication.");
                }
              }
            }
            if (this.isSelected('_ct')){
              this.decryptSync.call(data);
            }
          } catch (e) {
            err = e;
          }

          if (isEmbeddedDocument(this)) {
            if (err) {
              console.error(err);
              throw err; // note: this won't actually get thrown until save, because errors in subdoc init fns are CastErrors and aren't thrown by validate()
            }
            this._doc = data;
            return this;
          } else {
            return next(err);
          }
      });

      schema.pre('save', function(next) {
        var that = this;
        _.forEach(authenticatedFields, function(authenticatedField){
          that.markModified(authenticatedField)
        });

        if (this.isNew || this.isSelected('_ct') ){
          that.encrypt(function(err){
            if (err) {
              next(err);
            } else {
              if ((that.isNew || allAuthenticationFieldsSelected(that)) && !isEmbeddedDocument(that)) {
                that.sign(next);
              } else {
                next();
              }
            }
          });
        } else if (allAuthenticationFieldsSelected(this) && !isEmbeddedDocument(this)) { // _ct is not selected but all authenticated fields are. cannot get hit in current version.
          this.sign(next);
        } else {
          next();
        }
      });

      if (options.decryptPostSave) { // true by default
        schema.post('save', function(doc) {
          if (_.isFunction(doc.decryptSync)) {
            doc.decryptSync();
          }

          // Until 3.8.6, Mongoose didn't trigger post save hook on EmbeddedDocuments,
          // instead had to call decrypt on all subDocs.
          // ref https://github.com/LearnBoost/mongoose/issues/915

          decryptEmbeddedDocs(doc);

          return doc;
        });
      }
    }

    // Encryption Instance Methods //

    schema.methods.encrypt = function(cb) {
      var that = this;
      // generate random iv
      crypto.randomBytes(IV_LENGTH, function(err, iv) {
        var cipher, field, jsonToEncrypt, objectToEncrypt, val;
        if (err) {
          return cb(err);
        }
        cipher = crypto.createCipheriv(ENCRYPTION_ALGORITHM, encryptionKey, iv);
        objectToEncrypt = _.pick(that, encryptedFields);

        // only encrypt fields that are defined
        for (field in objectToEncrypt) {
          val = objectToEncrypt[field];
          if (val === undefined) {
            delete objectToEncrypt[field];
          }
        }
        jsonToEncrypt = JSON.stringify(objectToEncrypt);

        cipher.end(jsonToEncrypt, 'utf-8', function() {
          // add ciphertext to document
          that._ct = Buffer.concat([VERSION_BUF, iv, cipher.read()]);

          // remove encrypted fields from cleartext
          encryptedFields.forEach(function(field){
            that[field] = undefined;
          });

          cb(null);
        });
      });
    };

    schema.methods.decrypt = function(cb) { // callback style but actually synchronous to allow for decryptSync without copypasta or complication
      try {
        schema.methods.decryptSync.call(this);
      } catch(e){
        return cb(e);
      }
      cb();
    };

    schema.methods.decryptSync = function() {
      var ct, ctWithIV, decipher, iv, idString, decryptedObject, decryptedObjectJSON, decipheredVal;
      if (this._ct) {
        ctWithIV = this._ct.buffer || this._ct;
        iv = ctWithIV.slice(VERSION_LENGTH, VERSION_LENGTH + IV_LENGTH);
        ct = ctWithIV.slice(VERSION_LENGTH + IV_LENGTH, ctWithIV.length);
        decipher = crypto.createDecipheriv(ENCRYPTION_ALGORITHM, encryptionKey, iv);
        try {
          decryptedObjectJSON = decipher.update(ct, undefined, 'utf8') + decipher.final('utf8');
          decryptedObject = JSON.parse(decryptedObjectJSON);
        } catch (err) {
          if (this._id) {
            idString = this._id.toString();
          } else {
            idString = 'unknown';
          }
          throw new Error('Error parsing JSON during decrypt of ' + idString + ': ' + err);
        }
        for (var field in decryptedObject) {
          decipheredVal = decryptedObject[field];
          this[field] = decipheredVal;
        }
        this._ct = undefined;
      }
    };

    // Authentication Instance Methods //

    schema.methods.sign = function(cb) {
      var basicAC = computeAC(this, authenticatedFields, VERSION);
      var authenticatedFieldsBuf = new Buffer(JSON.stringify(authenticatedFields));
      this._ac = Buffer.concat([VERSION_BUF, basicAC, authenticatedFieldsBuf]);
      cb();
    };

    schema.methods.authenticate = function(cb) { // callback style but actually synchronous to allow for decryptSync without copypasta or complication
      try {
        schema.methods.authenticateSync.call(this);
      } catch(e){
        return cb(e);
      }
      cb();
    };

    schema.methods.authenticateSync = function() {
      if (!this._ac) {
        if (options.requireAuthenticationCode) {
          throw new Error('Authentication code missing');
        } else {
          return null;
        }
      }
      var acBuf = this._ac.buffer || this._ac;
      if (acBuf.length < VERSION_LENGTH + AAC_LENGTH + 2) {
        throw new Error('_ac is too short and has likely been cut off or modified');
      }
      var versionUsed = acBuf.slice(0, VERSION_LENGTH).toString();
      var basicAC = acBuf.slice(VERSION_LENGTH, VERSION_LENGTH + AAC_LENGTH);
      var authenticatedFieldsUsed = JSON.parse(acBuf.slice(VERSION_LENGTH + AAC_LENGTH, acBuf.length).toString());

      var expectedHMAC = computeAC(this, authenticatedFieldsUsed, versionUsed, arguments[0]); // pass in modelName as argument in init hook

      var authentic = bufferEqual(basicAC, expectedHMAC);
      if (authentic){
        this._ac = undefined;
        return null;
      } else {
        throw new Error('Authentication failed');
      }
    };
  };

  // Exports For Schemas That Contain Encrypted Embedded Documents

  // this ensures that if parent has a validation error, children don't come out encrypted,
  // which could otherwise cause data loss if validation error fixed and a resave was attempted
  // For use in conjunction with the main encryption plugin
  module.exports.encryptedChildren = function(schema, options) {
    schema.post('validate', function(doc) {
      if (doc.errors) {
        decryptEmbeddedDocs(doc);
      }
    });
  };

  module.exports.migrations = function(schema, options) {
    options.middleware = false; // don't run middleware during the migration
    mongoosePlugin(schema, options); // get all instance methods

    schema.statics.migrateToA = function(cb) {
      this.find({}, function(err, docs){ // find all docs in collection
        if (err) {
          return cb(err);
        }
        async.each(docs, function(doc, errCb){ // for each doc
          if (doc._ac) { // don't migrate if already migrated
            return errCb();
          }
          if (doc._ct) { // if previously encrypted
            doc._ct = Buffer.concat([VERSION_BUF, doc._ct]); // append version to ciphertext
            doc.sign(function(err){ // sign
              if (err) {
                return errCb(err);
              }
              return doc.save(errCb); // save
            });
          } else { // if not previously encrypted
            doc.encrypt(function(err){ // encrypt
              if (err) {
                return errCb(err);
              }
              doc.sign(function(err){ // sign
                if (err) {
                  return errCb(err);
                }
                return doc.save(errCb); // save
              });
            });
          }
        }, cb);
      });
    };

    schema.statics.migrateSubDocsToA = function(subDocField, cb) {
      if (typeof subDocField !== 'string'){
        cb(new Error('First argument must be the name of a field in which subdocuments are stored'));
      }
      this.find({}, function(err, docs){ // find all docs in collection
        if (err) {
          return cb(err);
        }
        async.each(docs, function(doc, errCb){ // for each doc
          if (doc[subDocField]) {
            _.each(doc[subDocField], function(subDoc){ // for each subdoc
              if (subDoc._ct) { // if previously encrypted
                subDoc._ct = Buffer.concat([VERSION_BUF, subDoc._ct]); // append version to ciphertext
              }
            });
            return doc.save(errCb); // save
          } else {
            errCb()
          }
        }, cb);
      });
    };

    // sign all the documents in a collection
    schema.statics.signAll = function(cb) {
      this.find({}, function(err, docs){ // find all docs in collection
        if (err) {
          return cb(err);
        }
        async.each(docs, function(doc, errCb){ // for each doc
          doc.sign(function(err){ // sign
            if (err) {
              return errCb(err);
            }
            doc.save(errCb); // save
          });
        }, cb);
      });
    };
  };

  // Exports For Tests //
  module.exports.AAC_LENGTH = AAC_LENGTH;
  module.exports.VERSION_LENGTH = VERSION_LENGTH;

}).call(this);
<|endoftext|>


<commit_before>const crypto = require('crypto');

var sha256 = exports.sha256 = function(data) {
  return new Buffer(crypto.createHash('sha256').update(data).digest('binary'), 'binary');
};

var twoSha256 = exports.twoSha256 = function(data) {
  return sha256(sha256(data));
};

var bufReverse = exports.bufReverse = function(buf) {
  return new Buffer(Array.prototype.slice.call(buf).reverse());
}

exports.getProof = function(txs, index) {
  // if (txs.length == 0) {
  //   return [util.NULL_HASH.slice(0)];
  // }

  // adapted from BitcoinJ and bitcore
  var tree = txs.map(function(txStr) {
    return new Buffer(txStr, 'hex');
  });

  var j = 0;

  if (index >= 0) {
    var lookFor = txs[index].toString('hex');
    var proof = [];
    var foundPath = 0;
  }

  // Now step through each level ...
  for (var size = txs.length; size > 1; size = Math.floor((size + 1) / 2)) {
    // and for each leaf on that level ..
    for (var i = 0; i < size; i += 2) {
      var i2 = Math.min(i + 1, size - 1);
      var a = tree[j + i];
      var b = tree[j + i2];

      if (index >= 0) {
        var aHex = a.toString('hex'),
          bHex = b.toString('hex');
console.log('lf: ', lookFor, aHex, bHex)
        if (lookFor === aHex) {
          foundPath = 2;
          proof.push({hash: bHex, path: 2});
console.log('pA: ', proof)
        } else if (lookFor === bHex) {
          foundPath = 1;
          proof.push({hash: aHex, path: 1});
console.log('pB: ', proof)
        }
      }

      var dblSha = twoSha256(Buffer.concat([bufReverse(a), bufReverse(b)]));
      dblSha = bufReverse(dblSha);

      if (foundPath > 0) {
        lookFor = dblSha.toString('hex'); //foundPath === 1 ? aHex : bHex;
        console.log('@@@@@@@@ ', bufReverse(a), bufReverse(b), lookFor)

        foundPath = 0;
      }

      tree.push(dblSha);
    }
    j += size;
  }

  if (index >= 0) {
    console.log('@@@@@@@@@proof: ', proof)
    return proof;
  }

  return tree[tree.length - 1].toString('hex');
};

exports.getTxMerkle = function(tx, proofArr) {
  var resultHash = new Buffer(tx, 'hex');

  proofArr.forEach(function(proof) {
    var proofHex = new Buffer(proof.hash, 'hex');
    if (proof.path === 1) {
      left = proofHex;
      right = resultHash;
    } else if (proof.path === 2) {
      left = resultHash;
      right = proofHex;
    }

    resultHash = twoSha256(Buffer.concat([bufReverse(left), bufReverse(right)]));
    resultHash = bufReverse(resultHash);
  })

  return resultHash.toString('hex');
}
<commit_msg>constants for the hash positions<commit_after>const crypto = require('crypto');

const LEFT_HASH = 1,
  RIGHT_HASH = 2;

var sha256 = exports.sha256 = function(data) {
  return new Buffer(crypto.createHash('sha256').update(data).digest('binary'), 'binary');
};

var twoSha256 = exports.twoSha256 = function(data) {
  return sha256(sha256(data));
};

var bufReverse = exports.bufReverse = function(buf) {
  return new Buffer(Array.prototype.slice.call(buf).reverse());
}

exports.getProof = function(txs, index) {
  // if (txs.length == 0) {
  //   return [util.NULL_HASH.slice(0)];
  // }

  // adapted from BitcoinJ and bitcore
  var tree = txs.map(function(txStr) {
    return new Buffer(txStr, 'hex');
  });

  var j = 0;

  if (index >= 0) {
    var lookFor = txs[index].toString('hex');
    var proof = [];
    var foundPath = 0;
  }

  // Now step through each level ...
  for (var size = txs.length; size > 1; size = Math.floor((size + 1) / 2)) {
    // and for each leaf on that level ..
    for (var i = 0; i < size; i += 2) {
      var i2 = Math.min(i + 1, size - 1);
      var a = tree[j + i];
      var b = tree[j + i2];

      if (index >= 0) {
        var aHex = a.toString('hex'),
          bHex = b.toString('hex');
console.log('lf: ', lookFor, aHex, bHex)
        if (lookFor === aHex) {
          foundPath = RIGHT_HASH;
          proof.push({hash: bHex, path: RIGHT_HASH});
console.log('pA: ', proof)
        } else if (lookFor === bHex) {
          foundPath = LEFT_HASH;
          proof.push({hash: aHex, path: LEFT_HASH});
console.log('pB: ', proof)
        }
      }

      var dblSha = twoSha256(Buffer.concat([bufReverse(a), bufReverse(b)]));
      dblSha = bufReverse(dblSha);

      if (foundPath > 0) {
        lookFor = dblSha.toString('hex'); //foundPath === 1 ? aHex : bHex;
        console.log('@@@@@@@@ ', bufReverse(a), bufReverse(b), lookFor)

        foundPath = 0;
      }

      tree.push(dblSha);
    }
    j += size;
  }

  if (index >= 0) {
    console.log('@@@@@@@@@proof: ', proof)
    return proof;
  }

  return tree[tree.length - 1].toString('hex');
};

exports.getTxMerkle = function(tx, proofArr) {
  var resultHash = new Buffer(tx, 'hex');

  proofArr.forEach(function(proof) {
    var proofHex = new Buffer(proof.hash, 'hex');
    if (proof.path === LEFT_HASH) {
      left = proofHex;
      right = resultHash;
    } else if (proof.path === RIGHT_HASH) {
      left = resultHash;
      right = proofHex;
    }

    resultHash = twoSha256(Buffer.concat([bufReverse(left), bufReverse(right)]));
    resultHash = bufReverse(resultHash);
  })

  return resultHash.toString('hex');
}
<|endoftext|>


<commit_before>'use strict';

var util = require('./lib/util');
util.boil = require('./lib/boil');

exports = module.exports = Parth;

function Parth(){
  if(!(this instanceof Parth)){ return new Parth(); }
  this.store = Object.create(null);
  this.regex = Object.create(null);
  this.master = Object.create(null);
  this.master.length = 0;
}

// ## Parth.set(path[, opt])
// > convert a string or array path to a regexp
// > TODO: implement regexp input
//
// arguments
//  - `path` type `string` or `array`
//  - `opt` type `object` optional holding all extra information
//
// return `this`
// --
// api.public
// --
//

util.paramRE = /(^|\W)\:([^()?#\.\/ ]+)(\(+[^ ]*\)+)?/g;

Parth.prototype.set = function(p){
  var o = Object.create(null);
  if(!util.boil(p, o)){ return null; }
  if(this.store[o.path]){
    o = this.store[o.path];
    return o.regex;
  } // ^ already defined

  // # of default and custom regex
  var sep; o.custom = o.default = 0;
  o.regex = '^' + o.path.replace(/\S+/g, function(stem){
    if((/\//).test(stem)){ sep = '/#?'; } else
    if((/\./).test(stem)){ sep = '.';   } else { sep = ''; }
    return stem.replace(util.paramRE, function($0, $1, $2, $3){
      if($3){ o.custom++; } else { o.default++; }
      return $1 + ($3 || '([^' + sep + '^]+)');
    });
  }).replace(/[^\/ ]*\/\S+/g, '$&\\/?(?:[^ ])?') // includes /
    .replace(/\^\]\+/g, ' ]+'); // default params;

  // update depths
  o.depth = o.argv.length;
  if(!this.regex[o.depth]){
    this.regex[o.depth] = [ ];
    while(this.master.length < o.depth){
      this.master[++this.master.length] = null;
    }
  }

  // attach relevant info.
  o.regex = new RegExp(o.regex, 'i');
  o.regex.path = o.path; o.regex.argv = o.argv;
  o.regex.def = o.default; o.regex.cust = o.custom;

  // reorder them
  this.regex[o.depth].push(o.regex);
  this.regex[o.depth] = this.regex[o.depth].sort(function(a, b){
    return ((a.def - a.cust) || -Infinity) - (b.def - b.cust);
  });

  // sum up all learned: void groups and make it one
  this.master[o.depth] =
    new RegExp(this.regex[o.depth].map(function(re){
      return '(' + re.source.replace(/\((?=[^?])/g, '(?:') + ')';
    }).join('|'), 'i');

  // clean, save & return
  delete o.default; delete o.custom;
  this.store[o.path] = o;
  return o.regex;
};

// ## Parth.get
// > take a string or array, return the matching path
//
// arguments
//  - `path` type `string` or `array`
//  - `o` type `object` optional holding all extra information
//
// return `o`
// --
// api.public
// --
//
Parth.prototype.get = function(p, o){
  o = o || { }; o.notFound = true;
  if(!util.boil(p, o)){ return null; }
  if(this.store[o.path]){
    util.merge(o, this.store[o.path]);
    o.match = o.path;
    return o.regex;
  }

  var found = this.master;
  var index = this.master.length;
  while(index > -1){
    if(found[index] && found[index].test(o.path)){
      found = o.path.match(found[index]).slice(1);
      o.depth = index; index = -1;
    } else if(--index < 1){ return null; }
    // ^ depth starts at 1 :), notFound
  }

  o.match = found.join('');
  index = found.indexOf(o.match);
  var regex = this.regex[o.depth][index];
  o.params = {_: o.path.match(regex).slice(1)};

  index = 0;
  regex.path.replace(util.paramRE, function($0, $1, $2){
    var p = o.params._[index];
    o.params[$2] = o.params._[index++] = Number(p) || p;
    o.params._[index-1] = $2;
  });

  o.notFound = !(/[ ]/).test(o.path.replace(o.match, '')[0] || ' ');
  return regex;
};
<commit_msg>fix 0 for number parsing<commit_after>'use strict';

var util = require('./lib/util');
util.boil = require('./lib/boil');

exports = module.exports = Parth;

function Parth(){
  if(!(this instanceof Parth)){ return new Parth(); }
  this.store = Object.create(null);
  this.regex = Object.create(null);
  this.master = Object.create(null);
  this.master.length = 0;
}

// ## Parth.set(path[, opt])
// > convert a string or array path to a regexp
// > TODO: implement regexp input
//
// arguments
//  - `path` type `string` or `array`
//  - `opt` type `object` optional holding all extra information
//
// return `this`
// --
// api.public
// --
//

util.paramRE = /(^|\W)\:([^()?#\.\/ ]+)(\(+[^ ]*\)+)?/g;

Parth.prototype.set = function(p){
  var o = Object.create(null);
  if(!util.boil(p, o)){ return null; }
  if(this.store[o.path]){
    o = this.store[o.path];
    return o.regex;
  } // ^ already defined

  // # of default and custom regex
  var sep; o.custom = o.default = 0;
  o.regex = '^' + o.path.replace(/\S+/g, function(stem){
    if((/\//).test(stem)){ sep = '/#?'; } else
    if((/\./).test(stem)){ sep = '.';   } else { sep = ''; }
    return stem.replace(util.paramRE, function($0, $1, $2, $3){
      if($3){ o.custom++; } else { o.default++; }
      return $1 + ($3 || '([^' + sep + '^]+)');
    });
  }).replace(/[^\/ ]*\/\S+/g, '$&\\/?(?:[^ ])?') // includes /
    .replace(/\^\]\+/g, ' ]+'); // default params;

  // update depths
  o.depth = o.argv.length;
  if(!this.regex[o.depth]){
    this.regex[o.depth] = [ ];
    while(this.master.length < o.depth){
      this.master[++this.master.length] = null;
    }
  }

  // attach relevant info.
  o.regex = new RegExp(o.regex, 'i');
  o.regex.path = o.path; o.regex.argv = o.argv;
  o.regex.def = o.default; o.regex.cust = o.custom;

  // reorder them
  this.regex[o.depth].push(o.regex);
  this.regex[o.depth] = this.regex[o.depth].sort(function(a, b){
    return ((a.def - a.cust) || -Infinity) - (b.def - b.cust);
  });

  // sum up all learned: void groups and make it one
  this.master[o.depth] =
    new RegExp(this.regex[o.depth].map(function(re){
      return '(' + re.source.replace(/\((?=[^?])/g, '(?:') + ')';
    }).join('|'), 'i');

  // clean, save & return
  delete o.default; delete o.custom;
  this.store[o.path] = o;
  return o.regex;
};

// ## Parth.get
// > take a string or array, return the matching path
//
// arguments
//  - `path` type `string` or `array`
//  - `o` type `object` optional holding all extra information
//
// return `o`
// --
// api.public
// --
//
Parth.prototype.get = function(p, o){
  o = o || { }; o.notFound = true;
  if(!util.boil(p, o)){ return null; }
  if(this.store[o.path]){
    util.merge(o, this.store[o.path]);
    o.match = o.path; return o.regex;
  }

  var found = this.master;
  var index = this.master.length;
  while(index > -1){
    if(found[index] && found[index].test(o.path)){
      found = o.path.match(found[index]).slice(1);
      o.depth = index; index = -1;
    } else if(--index < 1){ return null; }
    // ^ depth starts at 1 :), notFound
  }

  o.match = found.join('');
  index = found.indexOf(o.match);
  var regex = this.regex[o.depth][index];
  o.params = {_: o.path.match(regex).slice(1)};

  index = 0;
  regex.path.replace(util.paramRE, function($0, $1, $2){
    var p = o.params._[index];
    var num = Number(p) + 1;
    o.params[$2] = o.params._[index] = num ? num-1 : p;
    o.params._[index++] = $2;
  });

  o.notFound = !(/[ ]/).test(o.path.replace(o.match, '')[0] || ' ');
  return regex;
};
<|endoftext|>


<commit_before>/**
 * Copyright: (c) 2016 Max Klein
 * License: MIT
 */

const http = require('http');
const crypto = require('crypto');

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

function verifySignature(payload, signature, secret) {
	const equalsIndex = signature.indexOf('=');
	if(equalsIndex === -1) {
		return false;
	}
	const algorithm = signature.slice(0, equalsIndex);
	const receivedDigest = Buffer.from(signature.slice(equalsIndex + 1));
	const calculatedDigest = crypto.createHmac(algorithm, secret).update(payload).digest();
	return crypto.timingSafeEquals(calculatedDigest, digest);
}

function readBody(req) {
	return new Promise(function (resolve, reject) {
		const chunks = [];
		req.on('data', function (chunk) {
			chunks.push(chunk);
		});
		req.on('end', function () {
			const body = Buffer.concat(chunks).toString();
			resolve(body);
		});
		req.on('error', function (err) {
			reject(err);
		});
	});
}

class HttpError extends Error {
	constructor(statusCode, message) {
		super(http.STATUS_CODES[statusCode] + message ? ': ' + message : '');
		Object.defineProperties(this, {
			name: {
				value: this.constructor.name,
				enumerable: false
			},
			httpStatus: {
				value: statusCode,
				enumerable: false
			}
		});
	}
}

async function handleRequest(req, res) {
	if(req.method !== 'POST') {
		throw new HttpError(404);
	}

	const headers = req.headers;
	const eventName = headers['x-github-event'];
	const signature = headers['x-hub-signature'];

	const body = await readBody(req);
	if(!verifySignature(body, signature, config.secret)) {
		throw new HttpError(400, 'Signature does not match');
	}

	const payload = JSON.parse(body);
	if(eventName === 'push') {
		console.log('Push to %s', payload.repository.name);
	} else if(eventName === 'ping') {
		console.log('Ping from %s', payload.repository.name);
	} else {
		throw new HttpError(400, 'Invalid event');
	}
}

const server = http.createServer(function (req, res) {
	let ret;
	try {
		ret = handleRequest(req, res);
	} catch(err) {
		ret = Promise.reject(err);
	}
	Promise.resolve(ret)
		.then(function () {
			res.end();
		}, function (err) {
			console.error(err);

			res.statusCode = err.httpStatus || 500;
			res.end();
		});
}).listen(config.port);
<commit_msg>Use crypto API correctly<commit_after>/**
 * Copyright: (c) 2016 Max Klein
 * License: MIT
 */

const http = require('http');
const crypto = require('crypto');

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

function verifySignature(payload, signature, secret) {
	const equalsIndex = signature.indexOf('=');
	if(equalsIndex === -1) {
		return false;
	}
	const algorithm = signature.slice(0, equalsIndex);
	const receivedDigest = Buffer.from(signature.slice(equalsIndex + 1));
	const hmac = crypto.createHmac(algorithm, secret)
	hmac.update(payload);
	const calculatedDigest = hmac.digest();
	return crypto.timingSafeEquals(calculatedDigest, digest);
}

function readBody(req) {
	return new Promise(function (resolve, reject) {
		const chunks = [];
		req.on('data', function (chunk) {
			chunks.push(chunk);
		});
		req.on('end', function () {
			const body = Buffer.concat(chunks).toString();
			resolve(body);
		});
		req.on('error', function (err) {
			reject(err);
		});
	});
}

class HttpError extends Error {
	constructor(statusCode, message) {
		super(http.STATUS_CODES[statusCode] + message ? ': ' + message : '');
		Object.defineProperties(this, {
			name: {
				value: this.constructor.name,
				enumerable: false
			},
			httpStatus: {
				value: statusCode,
				enumerable: false
			}
		});
	}
}

async function handleRequest(req, res) {
	if(req.method !== 'POST') {
		throw new HttpError(404);
	}

	const headers = req.headers;
	const eventName = headers['x-github-event'];
	const signature = headers['x-hub-signature'];

	const body = await readBody(req);
	if(!verifySignature(body, signature, config.secret)) {
		throw new HttpError(400, 'Signature does not match');
	}

	const payload = JSON.parse(body);
	if(eventName === 'push') {
		console.log('Push to %s', payload.repository.name);
	} else if(eventName === 'ping') {
		console.log('Ping from %s', payload.repository.name);
	} else {
		throw new HttpError(400, 'Invalid event');
	}
}

const server = http.createServer(function (req, res) {
	let ret;
	try {
		ret = handleRequest(req, res);
	} catch(err) {
		ret = Promise.reject(err);
	}
	Promise.resolve(ret)
		.then(function () {
			res.end();
		}, function (err) {
			console.error(err);

			res.statusCode = err.httpStatus || 500;
			res.end();
		});
}).listen(config.port);
<|endoftext|>


<commit_before>var parse = require('chr/parse')
var compile = require('chr/compile')

module.exports = function (babel) {
  var t = babel.types

  var constructors = []
  var instances = {}
  var runtimeName = 'Runtime'

  return new babel.Transformer('chr', {
    'CallExpression|TaggedTemplateExpression': function (node, parent, scope, file) {
      var self = this

      if (t.isIdentifier(node.callee, { name: 'require' }) && node.arguments[0].value === 'chr') {
        // Found: "require('chr')"

        assign(parent, function (name) {
          constructors.push(name)
        })

        runtimeName = scope.generateUidIdentifier('Runtime').name

        this.parentPath.replaceWithSourceString(
          runtimeName + ' = require("chr/runtime")'
        )
      } else if ((node.type === 'CallExpression' && instances.hasOwnProperty(node.callee.name)) ||
        (node.type === 'TaggedTemplateExpression') && instances.hasOwnProperty(node.tag.name)) {
        // Found: "chr(...)"
        //    or: chr`...`

        var prefix = (node.type === 'CallExpression' ? node.callee.name : node.tag.name)
        var instance = instances[prefix]

        var chrSource
        if (node.type === 'CallExpression') {
          chrSource = node.arguments[0].value
        } else {
          chrSource = node.quasi.quasis.map(function (quasi) {
            return quasi.value.raw
          })
          var taggedChrSource = chrSource[0]

          var replacements = node.quasi.expressions
          replacements = replacements.map(function (expr, ix) {
            // new name for expr
            var exprId = scope.generateUidIdentifier('replacement' + ix).name
            var exprF
            if (t.isFunction(expr)) {
              exprF = t.functionExpression(t.identifier(exprId), [], t.blockStatement([
                t.returnStatement(t.callExpression(t.parenthesizedExpression(expr), []))
              ]))
            } else if (t.isIdentifier(expr) || t.isMemberExpression(expr)) {
              exprF = t.functionExpression(t.identifier(exprId), [], t.blockStatement([
                t.returnStatement(t.callExpression(expr, []))
              ]))
            } else {
              // BinaryExpression etc
              exprF = t.functionExpression(t.identifier(exprId), [], t.blockStatement([
                t.returnStatement(expr)
              ]))
            }
            self.insertAfter(exprF)

            var replacementId = instance.replacements
            taggedChrSource += '${' + replacementId + '}' + chrSource[ix + 1]
            instance.replacements += 1

            return 'chr.Replacements[' + replacementId + '] = ' + exprId + '.bind(chr)'
          })

          chrSource = taggedChrSource
        }

        var parts = ['']
        parts.push(
          prefix + ' = (function (chr) {'
        )

        var parsed = parse(chrSource)
        parsed.body.forEach(function (rule) {
          var head
          var functor
          var compiled

          parts.push(
            '/**',
            ' * [CHR Rule] ' + rule.original,
            ' */'
          )

          // add callers, e.g. 'chr.a = function() { ... }'
          rule.constraints.forEach(function (functor) {
            var name = functor.split('/')[0]
            if (!instance.constraintNames.hasOwnProperty(name)) {
              // not already defined
              parts.push('chr.' + name + ' = ' + runtimeName + '.Helper.dynamicCaller("' + name + '")')
              instance.constraintNames[name] = true
            }

            if (!instance.constraints.hasOwnProperty(functor)) {
              parts.push('chr.Constraints["' + functor + '"] = []')
              instance.constraints[functor] = 0
            }
          })

          // add replacements, e.g. 'chr.Replacements[0] = function() { ... }'
          ;['guard', 'body'].forEach(function (location) {
            rule[location] = rule[location].map(function (element) {
              if (element.type !== 'Replacement' || !element.hasOwnProperty('original')) {
                return element
              }

              var src = element.original
              if (location === 'guard') {
                src = 'return ' + src
              }

              var replacementId = instance.replacements
              parts.push('chr.Replacements[' + replacementId + '] = function() {' + src + '}')

              var newElement = {
                type: 'Replacement',
                num: replacementId
              }
              return newElement
            })
          })

          // add occurences
          for (var headNo = rule.head.length - 1; headNo >= 0; headNo--) {
            head = rule.head[headNo]
            functor = head.name + '/' + head.arity

            compiled = compile.head(rule, headNo, {
              helper: runtimeName + '.Helper'
            })

            var occurenceNumber = instance.constraints[functor]
            parts.push('chr.Constraints["' + functor + '"][' + occurenceNumber + '] = function (constraint) {')
            parts = parts.concat(compiled)
            parts.push('}')
            instance.constraints[functor] += 1
          }

          parts.push('')
        })

        parts = parts.concat(replacements)

        parts.push(
          'return chr',
          '})(' + prefix + ')'
        )

        this.replaceWithSourceString(parts.join('\n'))
      }
    },
    NewExpression: function (node, parent, scope, file) {
      if (constructors.indexOf(node.callee.name) === -1) {
        return
      }

      assign(parent, function (name) {
        instances[name] = newInstance(name)
      })

      this.replaceWithSourceString([
        '{',
        'Store: new ' + runtimeName + '.Store(),',
        'History: new ' + runtimeName + '.History(),',
        'Constraints: {},',
        'Replacements: []',
        '}'
      ].join('\n'))
    }
  })
}

function assign (node, cb) {
  if (node.type === 'VariableDeclarator') {
    cb(node.id.name)
    return
  }

  if (node.type === 'AssignmentExpression') {
    cb(node.left.name)
    return
  }
}

function newInstance (name) {
  return {
    constraints: {},
    constraintNames: {},
    replacements: 0
  }
}
<commit_msg>Support "chr = CHR()" without "new" keyword<commit_after>var parse = require('chr/parse')
var compile = require('chr/compile')

module.exports = function (babel) {
  var t = babel.types

  var constructors = []
  var instances = {}
  var runtimeName = 'Runtime'

  return new babel.Transformer('chr', {
    'CallExpression|TaggedTemplateExpression': function (node, parent, scope, file) {
      var self = this

      if (t.isIdentifier(node.callee, { name: 'require' }) && node.arguments[0].value === 'chr') {
        // Found: "require('chr')"

        assign(parent, function (name) {
          constructors.push(name)
        })

        runtimeName = scope.generateUidIdentifier('Runtime').name

        this.parentPath.replaceWithSourceString(
          runtimeName + ' = require("chr/runtime")'
        )
      } else if (node.type === 'CallExpression' && constructors.indexOf(node.callee.name) >= 0) {
        assign(parent, function (name) {
          instances[name] = newInstance(name)
        })

        this.replaceWithSourceString([
          '{',
          'Store: new ' + runtimeName + '.Store(),',
          'History: new ' + runtimeName + '.History(),',
          'Constraints: {},',
          'Replacements: []',
          '}'
        ].join('\n'))

        return
      } else if ((node.type === 'CallExpression' && instances.hasOwnProperty(node.callee.name)) ||
        (node.type === 'TaggedTemplateExpression') && instances.hasOwnProperty(node.tag.name)) {
        // Found: "chr(...)"
        //    or: chr`...`

        var prefix = (node.type === 'CallExpression' ? node.callee.name : node.tag.name)
        var instance = instances[prefix]

        var chrSource
        if (node.type === 'CallExpression') {
          chrSource = node.arguments[0].value
        } else {
          chrSource = node.quasi.quasis.map(function (quasi) {
            return quasi.value.raw
          })
          var taggedChrSource = chrSource[0]

          var replacements = node.quasi.expressions
          replacements = replacements.map(function (expr, ix) {
            // new name for expr
            var exprId = scope.generateUidIdentifier('replacement' + ix).name
            var exprF
            if (t.isFunction(expr)) {
              exprF = t.functionExpression(t.identifier(exprId), [], t.blockStatement([
                t.returnStatement(t.callExpression(t.parenthesizedExpression(expr), []))
              ]))
            } else if (t.isIdentifier(expr) || t.isMemberExpression(expr)) {
              exprF = t.functionExpression(t.identifier(exprId), [], t.blockStatement([
                t.returnStatement(t.callExpression(expr, []))
              ]))
            } else {
              // BinaryExpression etc
              exprF = t.functionExpression(t.identifier(exprId), [], t.blockStatement([
                t.returnStatement(expr)
              ]))
            }
            self.insertAfter(exprF)

            var replacementId = instance.replacements
            taggedChrSource += '${' + replacementId + '}' + chrSource[ix + 1]
            instance.replacements += 1

            return 'chr.Replacements[' + replacementId + '] = ' + exprId + '.bind(chr)'
          })

          chrSource = taggedChrSource
        }

        var parts = ['']
        parts.push(
          prefix + ' = (function (chr) {'
        )

        var parsed = parse(chrSource)
        parsed.body.forEach(function (rule) {
          var head
          var functor
          var compiled

          parts.push(
            '/**',
            ' * [CHR Rule] ' + rule.original,
            ' */'
          )

          // add callers, e.g. 'chr.a = function() { ... }'
          rule.constraints.forEach(function (functor) {
            var name = functor.split('/')[0]
            if (!instance.constraintNames.hasOwnProperty(name)) {
              // not already defined
              parts.push('chr.' + name + ' = ' + runtimeName + '.Helper.dynamicCaller("' + name + '")')
              instance.constraintNames[name] = true
            }

            if (!instance.constraints.hasOwnProperty(functor)) {
              parts.push('chr.Constraints["' + functor + '"] = []')
              instance.constraints[functor] = 0
            }
          })

          // add replacements, e.g. 'chr.Replacements[0] = function() { ... }'
          ;['guard', 'body'].forEach(function (location) {
            rule[location] = rule[location].map(function (element) {
              if (element.type !== 'Replacement' || !element.hasOwnProperty('original')) {
                return element
              }

              var src = element.original
              if (location === 'guard') {
                src = 'return ' + src
              }

              var replacementId = instance.replacements
              parts.push('chr.Replacements[' + replacementId + '] = function() {' + src + '}')

              var newElement = {
                type: 'Replacement',
                num: replacementId
              }
              return newElement
            })
          })

          // add occurences
          for (var headNo = rule.head.length - 1; headNo >= 0; headNo--) {
            head = rule.head[headNo]
            functor = head.name + '/' + head.arity

            compiled = compile.head(rule, headNo, {
              helper: runtimeName + '.Helper'
            })

            var occurenceNumber = instance.constraints[functor]
            parts.push('chr.Constraints["' + functor + '"][' + occurenceNumber + '] = function (constraint) {')
            parts = parts.concat(compiled)
            parts.push('}')
            instance.constraints[functor] += 1
          }

          parts.push('')
        })

        parts = parts.concat(replacements)

        parts.push(
          'return chr',
          '})(' + prefix + ')'
        )

        this.replaceWithSourceString(parts.join('\n'))
      }
    },
    NewExpression: function (node, parent, scope, file) {
      if (constructors.indexOf(node.callee.name) === -1) {
        return
      }

      assign(parent, function (name) {
        instances[name] = newInstance(name)
      })

      this.replaceWithSourceString([
        '{',
        'Store: new ' + runtimeName + '.Store(),',
        'History: new ' + runtimeName + '.History(),',
        'Constraints: {},',
        'Replacements: []',
        '}'
      ].join('\n'))
    }
  })
}

function assign (node, cb) {
  if (node.type === 'VariableDeclarator') {
    cb(node.id.name)
    return
  }

  if (node.type === 'AssignmentExpression') {
    cb(node.left.name)
    return
  }
}

function newInstance (name) {
  return {
    constraints: {},
    constraintNames: {},
    replacements: 0
  }
}
<|endoftext|>