<commit_before>'use strict'

const assert = require('assert')
const validate = require('validate.js')
const sizeChanger = require('../components/size-changer')
const locationChanger = require('../components/location-changer')
const getters = require('../components/getters')
const destructor = require('../components/destructor')

module.exports = PaneWrapper

function findImplementation (constructor) {
  try {
    return require(`../implementations/${constructor.name}`)
  } catch (e) {
    return false
  }
}

function PaneWrapper (Wrappee, opts) {
  assert(validate.isInteger(opts.x), `${opts.x} is not an integer`)
  assert(validate.isInteger(opts.y), `${opts.y} is not an integer`)
  assert(validate.isInteger(opts.width), `${opts.width} is not an integer`)
  assert(validate.isInteger(opts.height), `${opts.height} is not an integer`)
  const implementation = findImplementation(Wrappee)
  const offset = opts.grid && opts.grid.offset || { x: 0, y: 0 }
  const wrapped = typeof Wrappee === 'function'
    ? new Wrappee(Object.assign({}, opts, {
      x: opts.x + offset.x,
      y: opts.y + offset.y
    }))
    : {}
  if (!validate.isDefined(opts.id) && !validate.isDefined(wrapped.id)) {
    if (validate.isFunction(wrapped.close)) wrapped.close()
    throw new Error('id is not defined')
  }
  let state = {
    wrapped,
    grid: opts.grid,
    id: opts.id || wrapped.id,
    width: opts.width,
    height: opts.height,
    x: opts.x,
    y: opts.y
  }
  return Object.defineProperties(Object.assign(
    {},
    sizeChanger(state, implementation),
    locationChanger(state, implementation),
    destructor(state, implementation)
  ), getters(state))
}
<commit_msg>refactor: validation consistency<commit_after>'use strict'

const assert = require('assert')
const validate = require('validate.js')
const sizeChanger = require('../components/size-changer')
const locationChanger = require('../components/location-changer')
const getters = require('../components/getters')
const destructor = require('../components/destructor')

module.exports = PaneWrapper

function findImplementation (constructor) {
  try {
    return require(`../implementations/${constructor.name}`)
  } catch (e) {
    return false
  }
}

function PaneWrapper (Wrappee, opts) {
  assert(validate.isInteger(opts.x), `${opts.x} is not an integer`)
  assert(validate.isInteger(opts.y), `${opts.y} is not an integer`)
  assert(validate.isInteger(opts.width), `${opts.width} is not an integer`)
  assert(validate.isInteger(opts.height), `${opts.height} is not an integer`)
  const implementation = findImplementation(Wrappee)
  const offset = opts.grid && opts.grid.offset || { x: 0, y: 0 }
  const wrapped = typeof Wrappee === 'function'
    ? new Wrappee(Object.assign({}, opts, {
      x: opts.x + offset.x,
      y: opts.y + offset.y
    }))
    : {}
  if (!validate.isDefined(opts.id) && !validate.isDefined(wrapped.id)) {
    if (typeof wrapped.close === 'function') wrapped.close()
    throw new Error('id is not defined')
  }
  let state = {
    wrapped,
    grid: opts.grid,
    id: opts.id || wrapped.id,
    width: opts.width,
    height: opts.height,
    x: opts.x,
    y: opts.y
  }
  return Object.defineProperties(Object.assign(
    {},
    sizeChanger(state, implementation),
    locationChanger(state, implementation),
    destructor(state, implementation)
  ), getters(state))
}
<|endoftext|>


<commit_before>var merge = require('deepmerge'),
    _ = require('underscore');

var defaults = {
		defaultCDNBase: '',
		defaultCDN: '<%= defaultCDNBase %>/<%= filepathRel %>',
		relativeRoot: '',
		allowRev: true,
		allowMin: true,
		bowerComponents: null,
		// escaped to prevent IntelliJ from being too smart
		fallbackScript: '<scr'+'ipt>function cdnizerLoad(u) {document.write(\'<scr\'+\'ipt src="\'+encodeURIComponent(u)+\'"></scr\'+\'ipt>\');}</script>',
		fallbackTest: '<scr'+'ipt>if(!(${ test })) cdnizerLoad("${ filepath }");</script>',
		shouldAddFallback: false,
 		matchers: []
	};

function parseOptions(opts) {
	if(!opts || (typeof opts !== 'object' && !Array.isArray(opts))) {
		throw new Error("No options or invalid options supplied");
	}
	if(Array.isArray(opts)) {
		opts = {files: opts};
	}
	if(!Array.isArray(opts.files) || opts.files.length === 0) {
		throw new Error("Invalid or empty files list supplied");
	}
	opts = merge(defaults, opts);

	opts.files = opts.files.map(function(fileInfo) {
		if(typeof fileInfo === 'string' && fileInfo.length > 0) {
			fileInfo = { file: fileInfo };
		}
		if(!fileInfo.file || typeof fileInfo.file !== 'string') {
			throw new Error('File declaration is invalid');
		}
		if(fileInfo.test) {
			opts.shouldAddFallback = true;
		}
		if(opts.allowMin && fileInfo.file.indexOf('.min') === -1) {
			fileInfo.file = fileInfo.file.replace(/\.(.*)$/, '.?(min.)$1');
		}
		if(opts.allowRev) {
			fileInfo.file = fileInfo.file.replace(/(\..*)$/, '?(-????????)$1');
		}
		return fileInfo;
	});

	opts.matchers = opts.matchers.map(function(matcher) {
		if(_.isRegExp(matcher)) {
			return { pattern: matcher, fallback: false };
		} else {
			return matcher;
		}
	});

	opts.defaultCDNBase = opts.defaultCDNBase.replace(/\/$/, '');
	return opts;
}

module.exports = parseOptions;
<commit_msg>Use lodash, no need for an extra dependency<commit_after>var merge = require('deepmerge'),
    _ = require('lodash');

var defaults = {
		defaultCDNBase: '',
		defaultCDN: '<%= defaultCDNBase %>/<%= filepathRel %>',
		relativeRoot: '',
		allowRev: true,
		allowMin: true,
		bowerComponents: null,
		// escaped to prevent IntelliJ from being too smart
		fallbackScript: '<scr'+'ipt>function cdnizerLoad(u) {document.write(\'<scr\'+\'ipt src="\'+encodeURIComponent(u)+\'"></scr\'+\'ipt>\');}</script>',
		fallbackTest: '<scr'+'ipt>if(!(${ test })) cdnizerLoad("${ filepath }");</script>',
		shouldAddFallback: false,
 		matchers: []
	};

function parseOptions(opts) {
	if(!opts || (typeof opts !== 'object' && !Array.isArray(opts))) {
		throw new Error("No options or invalid options supplied");
	}
	if(Array.isArray(opts)) {
		opts = {files: opts};
	}
	if(!Array.isArray(opts.files) || opts.files.length === 0) {
		throw new Error("Invalid or empty files list supplied");
	}
	opts = merge(defaults, opts);

	opts.files = opts.files.map(function(fileInfo) {
		if(typeof fileInfo === 'string' && fileInfo.length > 0) {
			fileInfo = { file: fileInfo };
		}
		if(!fileInfo.file || typeof fileInfo.file !== 'string') {
			throw new Error('File declaration is invalid');
		}
		if(fileInfo.test) {
			opts.shouldAddFallback = true;
		}
		if(opts.allowMin && fileInfo.file.indexOf('.min') === -1) {
			fileInfo.file = fileInfo.file.replace(/\.(.*)$/, '.?(min.)$1');
		}
		if(opts.allowRev) {
			fileInfo.file = fileInfo.file.replace(/(\..*)$/, '?(-????????)$1');
		}
		return fileInfo;
	});

	opts.matchers = opts.matchers.map(function(matcher) {
		if(_.isRegExp(matcher)) {
			return { pattern: matcher, fallback: false };
		} else {
			return matcher;
		}
	});

	opts.defaultCDNBase = opts.defaultCDNBase.replace(/\/$/, '');
	return opts;
}

module.exports = parseOptions;
<|endoftext|>


<commit_before>var util      = require("../util");
var operators = require("../operators");

var runner = require("./runner");
var stack  = require("./stack");

function parseRule(parts, m, ln, opts) {
  var pre  = m[1];
  var raw  = m[2];
  var type = m[3];
  var args = m[4];
  var post = m[5];

  var operator = operators[type];
  var operatorArgs = args.split(",")
    .map(util.chomp)
    .map(util.removeQuotes);

  // Remove nulls
  operatorArgs = operatorArgs.filter(function(item) {
    return item !== "";
  });

  parts.push({sql: pre, ln: ln});

  parts.push({
    sql: raw,
    ln: ln,
    fn: function(ctx) {
      return operator.run.apply(ctx, operatorArgs);
    }
  });

  // Check the argument
  try {
    operator.parse.apply(null, operatorArgs);
  } catch(err) {
    if(opts.prettyErrors) {
      err.message += stack.trace(parts, parts.length-1, 100);
    }
    throw err;
  }

  parts.push({sql: post, ln: ln});
}

function parse(sqlRaw, opts) {
	sqlRaw = util.chomp(sqlRaw);

  var m, parts = [];
  var re = /([^}]*)(\{([>?!]?)([^}]*)\})([^{]*)/g;

  var sqlLines = sqlRaw.split("\n");
  sqlLines.forEach(function(sqlLine, idx) {
    if(idx < sqlLines.length-1) {
      sqlLine += "\n";
    }
    var match;
    while(m = re.exec(sqlLine)) {
      match = true;
      parseRule(parts, m, idx, opts);
    }
    if(!match) {
      parts.push({
        sql: sqlLine,
        ln: idx
      });
    }
  });

  return runner.bind(null, parts, opts);
}

module.exports = parse;
<commit_msg>Added some comments<commit_after>var util      = require("../util");
var operators = require("../operators");

var runner = require("./runner");
var stack  = require("./stack");

function parseRule(parts, m, ln, opts) {
  var pre  = m[1];
  var raw  = m[2];
  var type = m[3];
  var args = m[4];
  var post = m[5];

  var operator = operators[type];
  var operatorArgs = args.split(",")
    .map(util.chomp)
    .map(util.removeQuotes);

  // Remove nulls
  operatorArgs = operatorArgs.filter(function(item) {
    return item !== "";
  });

  parts.push({sql: pre, ln: ln});

  parts.push({
    sql: raw,
    ln: ln,
    fn: function(ctx) {
      return operator.run.apply(ctx, operatorArgs);
    }
  });

  // Check the argument
  try {
    operator.parse.apply(null, operatorArgs);
  } catch(err) {
    if(opts.prettyErrors) {
      err.message += stack.trace(parts, parts.length-1, 100);
    }
    throw err;
  }

  parts.push({sql: post, ln: ln});
}

function parse(sqlRaw, opts) {
	sqlRaw = util.chomp(sqlRaw);

  var m, parts = [];
  var re = /([^}]*)(\{([>?!]?)([^}]*)\})([^{]*)/g;

  // Split into lines so we can report line numbers in errors
  var sqlLines = sqlRaw.split("\n");

  // Parse each line, saving the line number
  sqlLines.forEach(function(sqlLine, ln) {
    // Add back in the new lines...bit lame
    if(ln < sqlLines.length-1) {
      sqlLine += "\n";
    }

    var match;
    while(m = re.exec(sqlLine)) {
      match = true;
      parseRule(parts, m, ln, opts);
    }

    // If we didn't match just append the line
    if(!match) {
      parts.push({
        sql: sqlLine,
        ln: ln
      });
    }
  });

  return runner.bind(null, parts, opts);
}

module.exports = parse;
<|endoftext|>


<commit_before>/**
 *
 */
module.exports = {
	trace: function(parts, idx, linesAround) {
		var tmpIdx, p, lnStr = "";
		var ln = parts[idx].ln;
		var debugPos = 0;

		tmpIdx = idx;
		p = parts[--tmpIdx];
		while(p && p.ln >= ln-linesAround) {
			if(p.ln === ln) {
				debugPos += p.sql.length;
			}
			lnStr = p.sql + lnStr;
			p = parts[--tmpIdx];
		}

		var debugStr = "";
		for(var i=0; i<debugPos; i++) {
			debugStr += "-";
		}
		debugStr += "^"

		lnStr += parts[idx].sql;
		lnStr+="\n"+debugStr

		tmpIdx = idx;
		p = parts[++tmpIdx];
		while(p && p.ln <= ln+linesAround) {
			lnStr += p.sql;
			p = parts[++tmpIdx];
		}

		return "\n\n"+lnStr+"\n";
	}
};
<commit_msg>Added some missing semicolons<commit_after>/**
 *
 */
module.exports = {
	trace: function(parts, idx, linesAround) {
		var tmpIdx, p, lnStr = "";
		var ln = parts[idx].ln;
		var debugPos = 0;

		tmpIdx = idx;
		p = parts[--tmpIdx];
		while(p && p.ln >= ln-linesAround) {
			if(p.ln === ln) {
				debugPos += p.sql.length;
			}

			lnStr = p.sql + lnStr;
			p = parts[--tmpIdx];
		}

		var debugStr = "";
		for(var i=0; i<debugPos; i++) {
			debugStr += "-";
		}
		debugStr += "^";

		lnStr += parts[idx].sql;
		lnStr+="\n"+debugStr;

		tmpIdx = idx;
		p = parts[++tmpIdx];
		while(p && p.ln <= ln+linesAround) {
			lnStr += p.sql;
			p = parts[++tmpIdx];
		}

		return "\n\n"+lnStr+"\n";
	}
};
<|endoftext|>


<commit_before>var semver  = require('semver')
var resolve = require('path').resolve

// Basic parser for sass/scss sources

module.exports = function(ctx, done) {
  if(!/\.(sass|scss)$/i.test(ctx.filename)) return done();

  var sass, pkg, stats = {};

  // Let user manage sass version
  try {
    sass = require('node-sass')
    pkg  = require('node-sass/package.json')
  } catch(e) {
    throw new Error('You must install node-sass package to use sass/scss sources')
  }

  // Use semver to check version (so many breaking changes !!!)
  if(semver.lte(pkg.version, '1.1.0')) {
    sass.render({
      file           : ctx.filename,
      sourceComments : 'map',
      stats          : stats,
      success: function(result) {
        ctx.src = result.split('\n').slice(0,-1).join('\n') // Remove sourceUrl comment ...
        ctx.map = JSON.parse(stats.sourceMap)
        done(null, ctx)
      },
      error: done
    })
  } else if(semver.lt(pkg.version, '2.0.0-beta')) {
    sass.render({
      file      : ctx.filename,
      sourceMap : true,
      stats     : stats,
      outFile   : resolve(ctx.filename + '.css'),
      omitSourceMapUrl: true,
      success: function(result, sourceMap) {
        ctx.src = result;
        ctx.map = JSON.parse(stats.sourceMap);
        delete ctx.map.file;
        done(null, ctx)
      },
      error: done
    })
  } else if(semver.gte('2.0.0-beta', pkg.version)) {
    sass.render({
      file      : ctx.filename,
      sourceMap : true,
      stats     : stats,
      outFile   : resolve(ctx.filename + '.css'),
      omitSourceMapUrl: true,
      success: function(result, sourceMap) {
        ctx.src = result.css;
        ctx.map = result.map;
        delete ctx.map.file;
        done(null, ctx);
      },
      error: done
    })
  }

}
<commit_msg>fix: node-sass api breaking changes<commit_after>var semver  = require('semver')
var resolve = require('path').resolve

// Basic parser for sass/scss sources

module.exports = function(ctx, done) {
  if(!/\.(sass|scss)$/i.test(ctx.filename)) return done();

  var sass, pkg, stats = {};

  // Let user manage sass version
  try {
    sass = require('node-sass')
    pkg  = require('node-sass/package.json')
  } catch(e) {
    throw new Error('You must install node-sass package to use sass/scss sources')
  }

  // Use semver to check version (so many breaking changes !!!)
  if(semver.lte(pkg.version, '1.1.0')) {
    sass.render({
      file           : ctx.filename,
      sourceComments : 'map',
      stats          : stats,
      success: function(result) {
        ctx.src = result.split('\n').slice(0,-1).join('\n') // Remove sourceUrl comment ...
        ctx.map = JSON.parse(stats.sourceMap)
        done(null, ctx)
      },
      error: done
    })
  } else if(semver.lt(pkg.version, '2.0.0-beta')) {
    sass.render({
      file      : ctx.filename,
      sourceMap : true,
      stats     : stats,
      outFile   : resolve(ctx.filename + '.css'),
      omitSourceMapUrl: true,
      success: function(result, sourceMap) {
        ctx.src = result;
        ctx.map = JSON.parse(stats.sourceMap);
        delete ctx.map.file;
        done(null, ctx)
      },
      error: done
    })
  } else if(semver.lt(pkg.version, '3.0.0')) {
    sass.render({
      file      : ctx.filename,
      sourceMap : true,
      stats     : stats,
      outFile   : resolve(ctx.filename + '.css'),
      omitSourceMapUrl: true,
      success: function(result, sourceMap) {
        ctx.src = result.css;
        ctx.map = result.map;
        delete ctx.map.file;
        done(null, ctx);
      },
      error: done
    })
  } else {
    sass.render({
      file      : ctx.filename,
      sourceMap : true,
      stats     : stats,
      outFile   : resolve(ctx.filename + '.css'),
      omitSourceMapUrl: true
    }, function(err, result) {
      if(err) {
        return done(err)
      }
      ctx.src = result.css;
      ctx.map = result.map.toString(); // Map is now a Buffer..
      delete ctx.map.file;
      done(null, ctx);
    })
  }

}
<|endoftext|>


<commit_before>// Copyright 2012 Mark Cavage, Inc.  All rights reserved.

'use strict';

var zlib = require('zlib');

var assert = require('assert-plus');

function _writeHead(originalFunction) {
    this.removeHeader('Content-Length');
    originalFunction.apply(this, Array.prototype.slice.call(arguments, 1));
}

///--- API

/**
 * gzips the response.
 * https://github.com/restify/node-restify/issues/284
 * @public
 * @function gzipResponse
 * @param   {Object}   opts an options object
 * @returns {Function}
 */
function gzipResponse(opts) {
    assert.optionalObject(opts, 'options');

    function gzip(req, res, next) {
        if (!req.acceptsEncoding('gzip')) {
            next();
            return;
        }

        var gz = zlib.createGzip(opts);

        gz.on('data', res.write.bind(res));
        gz.once('end', res.end.bind(res));
        gz.on('drain', res.emit.bind(res, 'drain'));

        var origWrite = res.write;
        var origEnd = res.end;
        var origWriteHead = res.writeHead;
        res.handledGzip = function _handledGzip() {
            res.write = origWrite;
            res.end = origEnd;
            res.writeHead = origWriteHead;
        };

        res.write = gz.write.bind(gz);
        res.end = gz.end.bind(gz);

        res.writeHead = _writeHead.bind(res, res.writeHead);
        res.setHeader('Content-Encoding', 'gzip');
        next();
    }

    return (gzip);
}

///--- Exports

module.exports = gzipResponse;
<commit_msg>fix: arguments leak during _writeHead (#14)<commit_after>// Copyright 2012 Mark Cavage, Inc.  All rights reserved.

'use strict';

var zlib = require('zlib');

var assert = require('assert-plus');

function _writeHead(originalFunction) {
    this.removeHeader('Content-Length');
    var argsLength = arguments.length;
    var args = new Array(argsLength - 1);
    for (var i = 1; i < argsLength; i++) {
        args[i - 1] = arguments[i];
    }
    originalFunction.apply(this, args);
}

///--- API

/**
 * gzips the response.
 * https://github.com/restify/node-restify/issues/284
 * @public
 * @function gzipResponse
 * @param   {Object}   opts an options object
 * @returns {Function}
 */
function gzipResponse(opts) {
    assert.optionalObject(opts, 'options');

    function gzip(req, res, next) {
        if (!req.acceptsEncoding('gzip')) {
            next();
            return;
        }

        var gz = zlib.createGzip(opts);

        gz.on('data', res.write.bind(res));
        gz.once('end', res.end.bind(res));
        gz.on('drain', res.emit.bind(res, 'drain'));

        var origWrite = res.write;
        var origEnd = res.end;
        var origWriteHead = res.writeHead;
        res.handledGzip = function _handledGzip() {
            res.write = origWrite;
            res.end = origEnd;
            res.writeHead = origWriteHead;
        };

        res.write = gz.write.bind(gz);
        res.end = gz.end.bind(gz);

        res.writeHead = _writeHead.bind(res, res.writeHead);
        res.setHeader('Content-Encoding', 'gzip');
        next();
    }

    return (gzip);
}

///--- Exports

module.exports = gzipResponse;
<|endoftext|>


<commit_before>/*jslint node: true*/
"use strict";

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

/**
 * JOIN plugin to emit "join" events
 *
 * @return {Function}
 * @api public
 */
module.exports = function () {
    return function (irc) {
        irc.join = function ircJoin(channels, keys, network, fn) {
            if (typeof keys === 'function') { // join(channels, fn)
                fn = keys;
                network = undefined;
                keys = '';
            } else if (typeof network === 'function') { // join(channels, network, fn)
                fn = network;
                network = keys;
            }

            for(var i=0; i < channels.length; i++) {
                var parse = utils.parseTarget(channels[i]);
                this.write('JOIN ' + parse.target + ((keys && keys[i]) ? (' ' + keys[i]) : ''), parse.network, fn);
            }
            //this.write('JOIN ' + utils.toArray(channels).join(',') + ' ' + utils.toArray(keys).join(','), network, fn);
        };

        irc.on('data', function (msg, network) {
            if (msg.command === 'JOIN') {
                utils.emit(irc, network, 'join', {
                    "channel": irc.getChannel(msg.trailing, network),
                    "user": irc.getUser(msg.params, network)
                });
            }
        });
    };
};
<commit_msg>join.js: fix #35<commit_after>/*jslint node: true*/
"use strict";

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

/**
 * JOIN plugin to emit "join" events
 *
 * @return {Function}
 * @api public
 */
module.exports = function () {
    return function (irc) {
        irc.join = function ircJoin(channels, keys, network, fn) {
            if (typeof keys === 'function') { // join(channels, fn)
                fn = keys;
                network = undefined;
                keys = '';
            } else if (typeof network === 'function') { // join(channels, network, fn)
                fn = network;
                network = keys;
            }

            channels = utils.toArray(channels);
            keys = utils.toArray(keys);
            for(var i=0; i < channels.length; i++) {
                var parse = utils.parseTarget(channels[i]);
                this.write('JOIN ' + parse.target + ((keys && keys[i]) ? (' ' + keys[i]) : ''), parse.network, fn);
            }
            //this.write('JOIN ' + utils.toArray(channels).join(',') + ' ' + utils.toArray(keys).join(','), network, fn);
        };

        irc.on('data', function (msg, network) {
            if (msg.command === 'JOIN') {
                utils.emit(irc, network, 'join', {
                    "channel": irc.getChannel(msg.trailing, network),
                    "user": irc.getUser(msg.params, network)
                });
            }
        });
    };
};
<|endoftext|>


<commit_before>const shopifyAPI = require('shopify-node-api');
const shopPromise = require('./shopPromises')(shopifyAPI);
const debug = require('debug')('kotn-utils');

module.exports = function processHooks(shopConfig, makeActive, appHookTopics, opts) {
	var hookURL = 'https://' + opts.hostName + opts.path + '/hooks';
	return shopPromise.get(shopConfig, '/admin/webhooks.json').
		then((data)=> {
			const currentHooks = data.webhooks;
			debug(JSON.stringify(currentHooks, null, ' '));
			const processed = [];
			if (!makeActive) {
				const removeHooks = currentHooks.filter((hk)=>(~appHookTopics.indexOf(hk.topic))); // leave the uninstall hook alone.
				console.log('removing hooks ' + removeHooks.map((hk)=>(hk.topic)).join(', '));
				const removeHook = ()=> {
					if (!removeHooks.length) return processed;
					const hook = removeHooks.shift();
					debug('remove: ' + hook.topic);
					return shopPromise.delete(shopConfig, '/admin/webhooks/' + hook.id + '.json').then(()=> {
						processed.push(hook);
						return removeHook();
					});
				};
				return removeHook();
			}

			// if makeActive
			const needHooks = appHookTopics.filter((ht)=> (currentHooks.find(ct=>(ct.topic == ht))));

			if (!needHooks.length) return [];
			console.log('creating webhooks:\n\t' + needHooks.join('\n\t'));
			const makeHooks = ()=> {
				if (!needHooks.length) return processed;
				const topic = needHooks.shift();
				const hook = {
					topic: topic,
					format: 'json',
					address: hookURL + '/' + topic
				};
				return shopPromise.post(shopConfig, '/admin/webhooks.json', {
					webhook: hook
				}).then((data)=> {
					processed.push(data.webhook);
					return makeHooks();
				});
			};
			return makeHooks();
		}).then((hooks)=> {
			const action = makeActive ? 'created new' : 'deleted';
			debug(action + ' webhooks:\n\t' + hooks.map((h)=>(h.id + ':' + h.topic)).join('\n\t'));
			return hooks;
		});
};

<commit_msg>fixed reversed hook topic filter<commit_after>const shopifyAPI = require('shopify-node-api');
const shopPromise = require('./shopPromises')(shopifyAPI);
const debug = require('debug')('kotn-utils');

module.exports = function processHooks(shopConfig, makeActive, appHookTopics, opts) {
	var hookURL = 'https://' + opts.hostName + opts.path + '/hooks';
	return shopPromise.get(shopConfig, '/admin/webhooks.json').
		then((data)=> {
			const currentHooks = data.webhooks;
			debug('current hooks: '+ JSON.stringify(currentHooks, null, ' '));
			const processed = [];
			if (!makeActive) {
				const removeHooks = currentHooks.filter((hk)=>(~appHookTopics.indexOf(hk.topic))); // leave the uninstall hook alone.
				console.log('removing hooks ' + removeHooks.map((hk)=>(hk.topic)).join(', '));
				const removeHook = ()=> {
					if (!removeHooks.length) return processed;
					const hook = removeHooks.shift();
					debug('remove: ' + hook.topic);
					return shopPromise.delete(shopConfig, '/admin/webhooks/' + hook.id + '.json').then(()=> {
						processed.push(hook);
						return removeHook();
					});
				};
				return removeHook();
			}

			// if makeActive
			const needHooks = appHookTopics.filter((ht)=> (!currentHooks.find(ct=>(ct.topic == ht))));

			if (!needHooks.length) return [];
			console.log('creating webhooks:\n\t' + needHooks.join('\n\t'));
			const makeHooks = ()=> {
				if (!needHooks.length) return processed;
				const topic = needHooks.shift();
				const hook = {
					topic: topic,
					format: 'json',
					address: hookURL + '/' + topic
				};
				return shopPromise.post(shopConfig, '/admin/webhooks.json', {
					webhook: hook
				}).then((data)=> {
					processed.push(data.webhook);
					return makeHooks();
				});
			};
			return makeHooks();
		}).then((hooks)=> {
			const action = makeActive ? 'created new' : 'deleted';
			debug(action + ' webhooks:\n\t' + hooks.map((h)=>(h.id + ':' + h.topic)).join('\n\t'));
			return hooks;
		});
};

<|endoftext|>


<commit_before>const Path = require('path');
const _ = require('underscore');
const httpErrors = require('httperrors');
const getFilterInfosAndTargetContentTypeFromQueryString = require('./getFilterInfosAndTargetContentTypeFromQueryString');
const mime = require('mime');
const stream = require('stream');
const accepts = require('accepts');
const hijackResponse = require('hijackresponse');
const isImageByExtension = {};

Object.keys(mime._extensions).forEach(contentType => {
  if (/^image\//.test(contentType)) {
    const extension = mime._extensions[contentType];
    isImageByExtension[extension] = true;
  }
});

isImageByExtension.jpg = true;

function isImageExtension(extension) {
  return isImageByExtension[extension.toLowerCase()];
}

module.exports = options => {
  options = options || {};

  if (
    typeof options.sharpCache !== 'undefined' &&
    getFilterInfosAndTargetContentTypeFromQueryString.sharp
  ) {
    getFilterInfosAndTargetContentTypeFromQueryString.sharp.cache(
      options.sharpCache
    );
  }
  return (req, res, next) => {
    // Polyfill req.accepts for browser-sync compatibility
    if (typeof req.accepts !== 'function') {
      req.accepts = function requestAccepts() {
        const accept = accepts(req);
        return accept.types.apply(accept, arguments);
      };
    }

    const matchExtensionAndQueryString = req.url.match(/\.(\w+)\?(.*)$/);
    const isMetadataRequest =
      matchExtensionAndQueryString &&
      /^(?:.*&)?metadata(?:$|&|=true)/.test(matchExtensionAndQueryString[2]);
    if (
      matchExtensionAndQueryString &&
      (req.method === 'GET' || req.method === 'HEAD') &&
      ((isImageExtension(matchExtensionAndQueryString[1]) &&
        req.accepts('image/*')) ||
        isMetadataRequest)
    ) {
      // Prevent If-None-Match revalidation with the downstream middleware with ETags that aren't suffixed with "-processimage":
      const queryString = matchExtensionAndQueryString[2];

      const ifNoneMatch = req.headers['if-none-match'];
      if (ifNoneMatch) {
        const validIfNoneMatchTokens = ifNoneMatch
          .split(' ')
          .filter(etag => /-processimage["-]/.test(etag));
        if (validIfNoneMatchTokens.length > 0) {
          req.headers['if-none-match'] = validIfNoneMatchTokens
            .map(token => token.replace(/-processimage(["-])/, '$1'))
            .join(' ');
        } else {
          delete req.headers['if-none-match'];
        }
      }
      delete req.headers['if-modified-since']; // Prevent false positive conditional GETs after enabling processimage
      // hijackResponse will never pass an error here
      // eslint-disable-next-line handle-callback-err
      hijackResponse(
        res,
        // hijackresponse never passes the error
        // eslint-disable-next-line handle-callback-err
        (err, res) => {
          // Polyfill res.status for browser-sync compatibility
          if (typeof res.status !== 'function') {
            res.status = function status(statusCode) {
              res.statusCode = statusCode;
              return res;
            };
          }

          let sourceMetadata;
          function makeFilterInfosAndTargetFormat() {
            return getFilterInfosAndTargetContentTypeFromQueryString(
              queryString,
              _.defaults(
                {
                  allowOperation: options.allowOperation,
                  sourceFilePath:
                    options.root &&
                    Path.resolve(options.root, req.url.substr(1)),
                  sourceMetadata
                },
                options
              )
            );
          }

          const contentLengthHeaderValue = res.getHeader('Content-Length');
          res.removeHeader('Content-Length');
          const oldETag = res.getHeader('ETag');

          let newETag;
          if (oldETag) {
            newETag = oldETag.replace(/"$/g, '-processimage"');
            res.setHeader('ETag', newETag);
            if (ifNoneMatch && ifNoneMatch.indexOf(newETag) !== -1) {
              res.destroyHijacked();
              return res.status(304).end();
            }
          }

          function startProcessing(optionalFirstChunk) {
            let hasEnded = false;

            let cleanedUp = false;

            let filters;
            function cleanUp(doNotDestroyHijacked) {
              if (!doNotDestroyHijacked) {
                res.destroyHijacked();
              }
              if (!cleanedUp) {
                cleanedUp = true;
                // the filters are unpiped after the error is passed to
                // next. doing the unpiping before calling next caused
                // the tests to fail on node 0.12 (not on 4.0 and 0.10).
                if (
                  res._readableState &&
                  res._readableState.buffer &&
                  res._readableState.buffer.length > 0
                ) {
                  res._readableState.buffer = [];
                }
                if (filters) {
                  filters.forEach(filter => {
                    if (filter.unpipe) {
                      filter.unpipe();
                    }
                    if (filter.kill) {
                      filter.kill();
                    } else if (filter.destroy) {
                      filter.destroy();
                    } else if (filter.resume) {
                      filter.resume();
                    }
                    if (filter.end) {
                      filter.end();
                    }
                    if (
                      filter._readableState &&
                      filter._readableState.buffer &&
                      filter._readableState.buffer.length > 0
                    ) {
                      filter._readableState.buffer = [];
                    }
                    filter.removeAllListeners();
                    // Some of the filters seem to emit error more than once sometimes:
                    filter.on('error', () => {});
                  });
                  filters = null;
                }
                res.removeAllListeners();
              }
            }

            function handleError(err) {
              if (!hasEnded) {
                hasEnded = true;
                if (err) {
                  if ('commandLine' in this) {
                    err.message = `${this.commandLine}: ${err.message}`;
                  }
                  if (
                    err.message ===
                    'Input buffer contains unsupported image format'
                  ) {
                    err = new httpErrors.UnsupportedMediaType(err.message);
                  }
                  if (err.message === 'Input image exceeds pixel limit') {
                    // ?metadata with an unrecognized image format
                    err = new httpErrors.RequestEntityTooLarge(err.message);
                  }

                  next(err);
                }
                res.unhijack();
                cleanUp(true);
              }
            }

            res.once('error', () => {
              res.unhijack();
              next(500);
            });
            res.once('close', cleanUp);
            const targetContentType =
              filterInfosAndTargetFormat.targetContentType;
            if (targetContentType) {
              res.setHeader('Content-Type', targetContentType);
            }
            filters = [];
            try {
              filterInfosAndTargetFormat.filterInfos.forEach(filterInfo => {
                const filter = filterInfo.create();
                if (Array.isArray(filter)) {
                  Array.prototype.push.apply(filters, filter);
                } else {
                  filters.push(filter);
                }
              });
            } catch (e) {
              return handleError(new httpErrors.BadRequest(e));
            }
            if (filters.length === 0) {
              filters = [new stream.PassThrough()];
            }
            if (options.debug) {
              // Only used by the test suite to assert that the right engine is used to process gifs:
              res.setHeader(
                'X-Express-Processimage',
                filterInfosAndTargetFormat.filterInfos
                  .map(filterInfo => filterInfo.operationName)
                  .join(',')
              );
            }
            if (optionalFirstChunk) {
              filters[0].write(optionalFirstChunk);
            }
            for (let i = 0; i < filters.length; i += 1) {
              if (i < filters.length - 1) {
                filters[i].pipe(filters[i + 1]);
              }
              // Some of the filters appear to emit error more than once:
              filters[i].once('error', handleError);
            }

            res.pipe(filters[0]);
            filters[filters.length - 1]
              .on('end', () => {
                hasEnded = true;
                cleanUp();
              })
              .pipe(res);
          }

          const contentType = res.getHeader('Content-Type');
          var filterInfosAndTargetFormat;
          if (res.statusCode === 304) {
            res.unhijack();
          } else if (
            isMetadataRequest ||
            (contentType &&
              (options.allowedImageSourceContentTypes
                ? options.allowedImageSourceContentTypes.indexOf(
                    contentType
                  ) !== -1
                : contentType.indexOf('image/') === 0))
          ) {
            sourceMetadata = {
              contentType,
              filesize:
                contentLengthHeaderValue &&
                parseInt(contentLengthHeaderValue, 10),
              etag: oldETag
            };

            filterInfosAndTargetFormat = makeFilterInfosAndTargetFormat();

            if (filterInfosAndTargetFormat.filterInfos.length === 0) {
              return res.unhijack(true);
            }
            if (options.secondGuessSourceContentType) {
              const endOrCloseOrErrorBeforeFirstDataChunkListener = err => {
                if (err) {
                  next(500);
                } else {
                  res.end();
                }
              };
              res.once('error', endOrCloseOrErrorBeforeFirstDataChunkListener);
              res.once('end', endOrCloseOrErrorBeforeFirstDataChunkListener);
              res.once('data', firstChunk => {
                res.removeListener(
                  'end',
                  endOrCloseOrErrorBeforeFirstDataChunkListener
                );
                res.removeListener(
                  'close',
                  endOrCloseOrErrorBeforeFirstDataChunkListener
                );
                let detectedContentType;
                if (
                  firstChunk[0] === 0x47 &&
                  firstChunk[1] === 0x49 &&
                  firstChunk[2] === 0x46
                ) {
                  detectedContentType = 'image/gif';
                } else if (firstChunk[0] === 0xff && firstChunk[1] === 0xd8) {
                  detectedContentType = 'image/jpeg';
                } else if (
                  firstChunk[0] === 0x89 &&
                  firstChunk[1] === 0x50 &&
                  firstChunk[2] === 0x4e &&
                  firstChunk[3] === 0x47
                ) {
                  detectedContentType = 'image/png';
                } else if (firstChunk[0] === 0x42 && firstChunk[1] === 0x4d) {
                  detectedContentType = 'image/bmp';
                }
                if (
                  detectedContentType &&
                  detectedContentType !== sourceMetadata.contentType
                ) {
                  sourceMetadata.contentType = detectedContentType;
                  filterInfosAndTargetFormat = makeFilterInfosAndTargetFormat();
                }
                startProcessing(firstChunk);
              });
            } else {
              startProcessing();
            }
          } else {
            res.unhijack();
          }
        },
        { disableBackpressure: true }
      );
      next();
    } else {
      next();
    }
  };
};
<commit_msg>Begin porting the module to use impro as the image processing core.<commit_after>const Path = require('path');
const _ = require('underscore');
const httpErrors = require('httperrors');
const impro = require('impro');
const mime = require('mime');
const accepts = require('accepts');
const hijackResponse = require('hijackresponse');
const isImageByExtension = {};

Object.keys(mime._extensions).forEach(contentType => {
  if (/^image\//.test(contentType)) {
    const extension = mime._extensions[contentType];
    isImageByExtension[extension] = true;
  }
});

isImageByExtension.jpg = true;

function isImageExtension(extension) {
  return isImageByExtension[extension.toLowerCase()];
}

function prepareQueryString(queryString) {
  if (queryString.startsWith('jpegtran=')) {
    const result = ['jpegtran'];
    const remaining = queryString.slice(9).split(',');
    remaining.forEach(bit => {
      if (bit[0] === '-') {
        result.push(bit.slice(1));
      } else if (bit === 'horizontal' || bit === 'vertical') {
        const indexOfArg = result.indexOf('flip');
        if (indexOfArg > -1) {
          result[indexOfArg] += `=${bit}`;
        } else {
          // XXX
        }
      }
    });
    return result.join('&');
  }

  return queryString;
}

module.exports = options => {
  options = options || {};

  /*
  if (
    typeof options.sharpCache !== 'undefined' &&
    getFilterInfosAndTargetContentTypeFromQueryString.sharp
  ) {
    getFilterInfosAndTargetContentTypeFromQueryString.sharp.cache(
      options.sharpCache
    );
  }
  */

  return (req, res, next) => {
    // Polyfill req.accepts for browser-sync compatibility
    if (typeof req.accepts !== 'function') {
      req.accepts = function requestAccepts() {
        const accept = accepts(req);
        return accept.types.apply(accept, arguments);
      };
    }

    const matchExtensionAndQueryString = req.url.match(/\.(\w+)\?(.*)$/);
    const isMetadataRequest =
      matchExtensionAndQueryString &&
      /^(?:.*&)?metadata(?:$|&|=true)/.test(matchExtensionAndQueryString[2]);
    if (
      matchExtensionAndQueryString &&
      (req.method === 'GET' || req.method === 'HEAD') &&
      ((isImageExtension(matchExtensionAndQueryString[1]) &&
        req.accepts('image/*')) ||
        isMetadataRequest)
    ) {
      // Prevent If-None-Match revalidation with the downstream middleware with ETags that aren't suffixed with "-processimage":
      const queryString = matchExtensionAndQueryString[2];

      const ifNoneMatch = req.headers['if-none-match'];
      if (ifNoneMatch) {
        const validIfNoneMatchTokens = ifNoneMatch
          .split(' ')
          .filter(etag => /-processimage["-]/.test(etag));
        if (validIfNoneMatchTokens.length > 0) {
          req.headers['if-none-match'] = validIfNoneMatchTokens
            .map(token => token.replace(/-processimage(["-])/, '$1'))
            .join(' ');
        } else {
          delete req.headers['if-none-match'];
        }
      }
      delete req.headers['if-modified-since']; // Prevent false positive conditional GETs after enabling processimage
      // hijackResponse will never pass an error here
      // eslint-disable-next-line handle-callback-err
      hijackResponse(
        res,
        // hijackresponse never passes the error
        // eslint-disable-next-line handle-callback-err
        (err, res) => {
          // Polyfill res.status for browser-sync compatibility
          if (typeof res.status !== 'function') {
            res.status = function status(statusCode) {
              res.statusCode = statusCode;
              return res;
            };
          }

          let sourceMetadata;

          function makeFilterInfosAndTargetFormat() {
            const preparedQueryString = prepareQueryString(queryString);
            return impro.parse(
              preparedQueryString,
              _.defaults(
                {
                  allowOperation: options.allowOperation,
                  sourceFilePath:
                    options.root &&
                    Path.resolve(options.root, req.url.substr(1)),
                  sourceMetadata
                },
                options
              )
            );
          }

          const contentLengthHeaderValue = res.getHeader('Content-Length');
          res.removeHeader('Content-Length');
          const oldETag = res.getHeader('ETag');

          let newETag;
          if (oldETag) {
            newETag = oldETag.replace(/"$/g, '-processimage"');
            res.setHeader('ETag', newETag);
            if (ifNoneMatch && ifNoneMatch.indexOf(newETag) !== -1) {
              res.destroyHijacked();
              return res.status(304).end();
            }
          }

          function startProcessing(optionalFirstChunk) {
            let hasEnded = false;

            let cleanedUp = false;

            function cleanUp(doNotDestroyHijacked) {
              if (!doNotDestroyHijacked) {
                res.destroyHijacked();
              }

              if (!cleanedUp) {
                cleanedUp = true;
                /*
                // the filters are unpiped after the error is passed to
                // next. doing the unpiping before calling next caused
                // the tests to fail on node 0.12 (not on 4.0 and 0.10).
                if (
                  res._readableState &&
                  res._readableState.buffer &&
                  res._readableState.buffer.length > 0
                ) {
                  res._readableState.buffer = [];
                }
                */

                res.removeAllListeners();
              }
            }

            function handleError(err) {
              if (!hasEnded) {
                hasEnded = true;
                if (err) {
                  if ('commandLine' in this) {
                    err.message = `${this.commandLine}: ${err.message}`;
                  }
                  if (
                    err.message ===
                    'Input buffer contains unsupported image format'
                  ) {
                    err = new httpErrors.UnsupportedMediaType(err.message);
                  }
                  if (err.message === 'Input image exceeds pixel limit') {
                    // ?metadata with an unrecognized image format
                    err = new httpErrors.RequestEntityTooLarge(err.message);
                  }

                  next(err);
                }
                res.unhijack();
                cleanUp(true);
              }
            }

            res.once('error', () => {
              res.unhijack();
              next(500);
            });
            res.once('close', cleanUp);
            const targetContentType =
              filterInfosAndTargetFormat.targetContentType;
            if (targetContentType) {
              res.setHeader('Content-Type', targetContentType);
            }

            const pipeline = impro.createPipeline(
              { sourceMetadata, maxInputPixels: options.maxInputPixels },
              filterInfosAndTargetFormat.operations
            );

            if (options.debug) {
              // Only used by the test suite to assert that the right engine is used to process gifs:
              res.setHeader(
                'X-Express-Processimage',
                filterInfosAndTargetFormat.operations
                  .map(operation => operation.name)
                  .join(',')
              );
            }

            if (optionalFirstChunk) {
              pipeline.write(optionalFirstChunk);
            }

            // send along processed data
            pipeline
              .on('error', handleError)
              .on('end', () => {
                hasEnded = true;
                cleanUp();
              })
              .pipe(res);

            // initiate processing
            res.pipe(pipeline);
          }

          const contentType = res.getHeader('Content-Type');
          var filterInfosAndTargetFormat;
          if (res.statusCode === 304) {
            res.unhijack();
          } else if (
            isMetadataRequest ||
            (contentType &&
              (options.allowedImageSourceContentTypes
                ? options.allowedImageSourceContentTypes.indexOf(
                    contentType
                  ) !== -1
                : contentType.indexOf('image/') === 0))
          ) {
            sourceMetadata = {
              contentType,
              filesize:
                contentLengthHeaderValue &&
                parseInt(contentLengthHeaderValue, 10),
              etag: oldETag
            };

            filterInfosAndTargetFormat = makeFilterInfosAndTargetFormat();

            if (filterInfosAndTargetFormat.operations.length === 0) {
              return res.unhijack(true);
            }

            if (options.secondGuessSourceContentType) {
              const endOrCloseOrErrorBeforeFirstDataChunkListener = err => {
                if (err) {
                  next(500);
                } else {
                  res.end();
                }
              };
              res.once('error', endOrCloseOrErrorBeforeFirstDataChunkListener);
              res.once('end', endOrCloseOrErrorBeforeFirstDataChunkListener);
              res.once('data', firstChunk => {
                res.removeListener(
                  'end',
                  endOrCloseOrErrorBeforeFirstDataChunkListener
                );
                res.removeListener(
                  'close',
                  endOrCloseOrErrorBeforeFirstDataChunkListener
                );
                let detectedContentType;
                if (
                  firstChunk[0] === 0x47 &&
                  firstChunk[1] === 0x49 &&
                  firstChunk[2] === 0x46
                ) {
                  detectedContentType = 'image/gif';
                } else if (firstChunk[0] === 0xff && firstChunk[1] === 0xd8) {
                  detectedContentType = 'image/jpeg';
                } else if (
                  firstChunk[0] === 0x89 &&
                  firstChunk[1] === 0x50 &&
                  firstChunk[2] === 0x4e &&
                  firstChunk[3] === 0x47
                ) {
                  detectedContentType = 'image/png';
                } else if (firstChunk[0] === 0x42 && firstChunk[1] === 0x4d) {
                  detectedContentType = 'image/bmp';
                }
                if (
                  detectedContentType &&
                  detectedContentType !== sourceMetadata.contentType
                ) {
                  sourceMetadata.contentType = detectedContentType;
                  // filterInfosAndTargetFormat = makeFilterInfosAndTargetFormat();
                }
                startProcessing(firstChunk);
              });
            } else {
              startProcessing();
            }
          } else {
            res.unhijack();
          }
        },
        { disableBackpressure: true }
      );
      next();
    } else {
      next();
    }
  };
};
<|endoftext|>


<commit_before>const Path = require('path');
const httpErrors = require('httperrors');
const impro = require('impro');
const mime = require('mime');
const accepts = require('accepts');
const hijackResponse = require('hijackresponse');

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

const isImageByExtension = {};
Object.keys(mime._extensions).forEach(contentType => {
  if (/^image\//.test(contentType)) {
    const extension = mime._extensions[contentType];
    isImageByExtension[extension] = true;
  }
});

isImageByExtension.jpg = true;

function isImageExtension(extension) {
  return isImageByExtension[extension.toLowerCase()];
}

function pickProperties(obj, properties) {
  if (!obj) return {};
  const ret = {};
  for (const property of properties) {
    ret[property] = obj[property];
  }
  return ret;
}

function reverseIteratorFor(arr) {
  let index = arr.length;

  return {
    next: function() {
      index -= 1;

      const isEnded = index < 0;

      return {
        done: isEnded,
        value: !isEnded ? arr[index] : undefined
      };
    },
    [Symbol.iterator]: function() {
      return this;
    }
  };
}

function toUsedNames(usedEngines) {
  if (usedEngines.length === 0) {
    return [];
  } else if (usedEngines.length === 1) {
    return [usedEngines[0].name];
  }

  let lastUsedName = usedEngines[0].name;
  const orderedUsedNames = [lastUsedName];
  // keep the first occurrence of every engine listed as used
  usedEngines.forEach(({ name }) => {
    if (name !== lastUsedName) {
      orderedUsedNames.push(name);
      lastUsedName = name;
    }
  });

  return orderedUsedNames;
}

module.exports = options => {
  options = options || {};

  const engines = pickProperties(
    options.filters,
    Object.keys(impro.engineByName)
  );

  /*
  if (
    typeof options.sharpCache !== 'undefined' &&
    getFilterInfosAndTargetContentTypeFromQueryString.sharp
  ) {
    getFilterInfosAndTargetContentTypeFromQueryString.sharp.cache(
      options.sharpCache
    );
  }
  */

  const middleware = (req, res, next) => {
    // Polyfill req.accepts for browser-sync compatibility
    if (typeof req.accepts !== 'function') {
      req.accepts = function requestAccepts() {
        const accept = accepts(req);
        return accept.types.apply(accept, arguments);
      };
    }

    const matchExtensionAndQueryString = req.url.match(/\.(\w+)\?(.*)$/);
    const isMetadataRequest =
      matchExtensionAndQueryString &&
      /^(?:.*&)?metadata(?:$|&|=true)/.test(matchExtensionAndQueryString[2]);
    if (
      matchExtensionAndQueryString &&
      (req.method === 'GET' || req.method === 'HEAD') &&
      ((isImageExtension(matchExtensionAndQueryString[1]) &&
        req.accepts('image/*')) ||
        isMetadataRequest)
    ) {
      // Prevent If-None-Match revalidation with the downstream middleware with ETags that aren't suffixed with "-processimage":
      const queryString = matchExtensionAndQueryString[2];

      const ifNoneMatch = req.headers['if-none-match'];
      if (ifNoneMatch) {
        const validIfNoneMatchTokens = ifNoneMatch
          .split(' ')
          .filter(etag => /-processimage["-]/.test(etag));
        if (validIfNoneMatchTokens.length > 0) {
          req.headers['if-none-match'] = validIfNoneMatchTokens
            .map(token => token.replace(/-processimage(["-])/, '$1'))
            .join(' ');
        } else {
          delete req.headers['if-none-match'];
        }
      }
      delete req.headers['if-modified-since']; // Prevent false positive conditional GETs after enabling processimage
      // hijackResponse will never pass an error here
      // eslint-disable-next-line handle-callback-err
      hijackResponse(
        res,
        // hijackresponse never passes the error
        // eslint-disable-next-line handle-callback-err
        (err, res) => {
          // Polyfill res.status for browser-sync compatibility
          if (typeof res.status !== 'function') {
            res.status = function status(statusCode) {
              res.statusCode = statusCode;
              return res;
            };
          }

          let sourceMetadata;

          function makeFilterInfosAndTargetFormat() {
            const preparedQueryString = prepareImproQueryString(queryString);
            const parseResult = impro.parse(
              preparedQueryString,
              options.allowOperation
            );

            // determine the final content type based on the last
            // requested type conversion operation (if present)
            let outputContentType;
            for (const operation of reverseIteratorFor(
              parseResult.operations
            )) {
              if (operation.name === 'metadata') {
                outputContentType = 'application/json; charset=utf-8';
                break;
              } else if (impro.isTypeByName[operation.name]) {
                outputContentType = `image/${operation.name}`;
                break;
              }
            }
            parseResult.outputContentType =
              outputContentType || sourceMetadata.contentType;

            return parseResult;
          }

          const contentLengthHeaderValue = res.getHeader('Content-Length');
          res.removeHeader('Content-Length');
          const oldETag = res.getHeader('ETag');

          let newETag;
          if (oldETag) {
            newETag = oldETag.replace(/"$/g, '-processimage"');
            res.setHeader('ETag', newETag);
            if (ifNoneMatch && ifNoneMatch.indexOf(newETag) !== -1) {
              res.destroyHijacked();
              return res.status(304).end();
            }
          }

          function startProcessing(optionalFirstChunk) {
            let hasEnded = false;

            let cleanedUp = false;

            function cleanUp(doNotDestroyHijacked) {
              if (!doNotDestroyHijacked) {
                res.destroyHijacked();
              }

              if (!cleanedUp) {
                cleanedUp = true;
                /*
                // the filters are unpiped after the error is passed to
                // next. doing the unpiping before calling next caused
                // the tests to fail on node 0.12 (not on 4.0 and 0.10).
                if (
                  res._readableState &&
                  res._readableState.buffer &&
                  res._readableState.buffer.length > 0
                ) {
                  res._readableState.buffer = [];
                }
                */

                res.removeAllListeners();
              }
            }

            function handleError(err) {
              if (!hasEnded) {
                hasEnded = true;
                if (err) {
                  if ('commandLine' in err) {
                    err.message = `${err.commandLine}: ${err.message}`;
                  }
                  if (
                    err.message ===
                    'Input buffer contains unsupported image format'
                  ) {
                    err = new httpErrors.UnsupportedMediaType(err.message);
                  }
                  if (err.message === 'Input image exceeds pixel limit') {
                    // ?metadata with an unrecognized image format
                    err = new httpErrors.RequestEntityTooLarge(err.message);
                  }

                  next(err);
                }
                res.unhijack();
                cleanUp(true);
              }
            }

            res.once('error', err => {
              // trigger teardown of all pipeline streams
              pipeline.emit(err);
              // respond with an error
              handleError(err);
            });
            res.once('close', cleanUp);

            const outputContentType =
              filterInfosAndTargetFormat.outputContentType;
            if (outputContentType) {
              res.setHeader('Content-Type', outputContentType);
            }

            const type = sourceMetadata && sourceMetadata.contentType;

            const pipeline = impro.createPipeline(
              {
                type,
                sourceMetadata,
                ...engines,
                maxInputPixels: options.maxInputPixels,
                sharpCache: options.sharpCache,
                svgAssetPath: options.root
                  ? Path.resolve(options.root, req.url.substr(1))
                  : null
              },
              filterInfosAndTargetFormat.operations
            );

            if (options.debug) {
              let usedEngines;
              try {
                usedEngines = pipeline.flush().usedEngines;
              } catch (e) {
                res.unhijack();
                return next(e);
              }

              // Only used by the test suite to assert that the right engine is used to process gifs:
              res.setHeader(
                'X-Express-Processimage',
                toUsedNames(usedEngines).join(',')
              );
            }

            if (typeof options.onPipeline === 'function') {
              options.onPipeline(pipeline);
            }

            if (optionalFirstChunk) {
              pipeline.write(optionalFirstChunk);
            }

            // send along processed data
            pipeline
              .on('error', handleError)
              .on('end', () => {
                hasEnded = true;
                cleanUp();
              })
              .pipe(res);

            // initiate processing
            res.pipe(pipeline);
          }

          const contentType = res.getHeader('Content-Type');
          var filterInfosAndTargetFormat;
          if (res.statusCode === 304) {
            res.unhijack();
          } else if (
            isMetadataRequest ||
            (contentType &&
              (options.allowedImageSourceContentTypes
                ? options.allowedImageSourceContentTypes.indexOf(
                    contentType
                  ) !== -1
                : contentType.indexOf('image/') === 0))
          ) {
            sourceMetadata = {
              contentType,
              filesize:
                contentLengthHeaderValue &&
                parseInt(contentLengthHeaderValue, 10),
              etag: oldETag
            };

            filterInfosAndTargetFormat = makeFilterInfosAndTargetFormat();

            if (filterInfosAndTargetFormat.operations.length === 0) {
              return res.unhijack(true);
            }

            if (options.secondGuessSourceContentType) {
              const endOrCloseOrErrorBeforeFirstDataChunkListener = err => {
                if (err) {
                  next(500);
                } else {
                  res.end();
                }
              };
              res.once('error', endOrCloseOrErrorBeforeFirstDataChunkListener);
              res.once('end', endOrCloseOrErrorBeforeFirstDataChunkListener);
              res.once('data', firstChunk => {
                res.removeListener(
                  'end',
                  endOrCloseOrErrorBeforeFirstDataChunkListener
                );
                res.removeListener(
                  'close',
                  endOrCloseOrErrorBeforeFirstDataChunkListener
                );
                let detectedContentType;
                if (
                  firstChunk[0] === 0x47 &&
                  firstChunk[1] === 0x49 &&
                  firstChunk[2] === 0x46
                ) {
                  detectedContentType = 'image/gif';
                } else if (firstChunk[0] === 0xff && firstChunk[1] === 0xd8) {
                  detectedContentType = 'image/jpeg';
                } else if (
                  firstChunk[0] === 0x89 &&
                  firstChunk[1] === 0x50 &&
                  firstChunk[2] === 0x4e &&
                  firstChunk[3] === 0x47
                ) {
                  detectedContentType = 'image/png';
                } else if (firstChunk[0] === 0x42 && firstChunk[1] === 0x4d) {
                  detectedContentType = 'image/bmp';
                }
                if (
                  detectedContentType &&
                  detectedContentType !== sourceMetadata.contentType
                ) {
                  sourceMetadata.contentType = detectedContentType;
                  // filterInfosAndTargetFormat = makeFilterInfosAndTargetFormat();
                }
                startProcessing(firstChunk);
              });
            } else {
              startProcessing();
            }
          } else {
            res.unhijack();
          }
        },
        { disableBackpressure: true }
      );
      next();
    } else {
      next();
    }
  };

  // exposed for some testing scenarios
  middleware._impro = impro;

  return middleware;
};
<commit_msg>Remove some lines left commented from early in the port.<commit_after>const Path = require('path');
const httpErrors = require('httperrors');
const impro = require('impro');
const mime = require('mime');
const accepts = require('accepts');
const hijackResponse = require('hijackresponse');

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

const isImageByExtension = {};
Object.keys(mime._extensions).forEach(contentType => {
  if (/^image\//.test(contentType)) {
    const extension = mime._extensions[contentType];
    isImageByExtension[extension] = true;
  }
});

isImageByExtension.jpg = true;

function isImageExtension(extension) {
  return isImageByExtension[extension.toLowerCase()];
}

function pickProperties(obj, properties) {
  if (!obj) return {};
  const ret = {};
  for (const property of properties) {
    ret[property] = obj[property];
  }
  return ret;
}

function reverseIteratorFor(arr) {
  let index = arr.length;

  return {
    next: function() {
      index -= 1;

      const isEnded = index < 0;

      return {
        done: isEnded,
        value: !isEnded ? arr[index] : undefined
      };
    },
    [Symbol.iterator]: function() {
      return this;
    }
  };
}

function toUsedNames(usedEngines) {
  if (usedEngines.length === 0) {
    return [];
  } else if (usedEngines.length === 1) {
    return [usedEngines[0].name];
  }

  let lastUsedName = usedEngines[0].name;
  const orderedUsedNames = [lastUsedName];
  // keep the first occurrence of every engine listed as used
  usedEngines.forEach(({ name }) => {
    if (name !== lastUsedName) {
      orderedUsedNames.push(name);
      lastUsedName = name;
    }
  });

  return orderedUsedNames;
}

module.exports = options => {
  options = options || {};

  const engines = pickProperties(
    options.filters,
    Object.keys(impro.engineByName)
  );

  const middleware = (req, res, next) => {
    // Polyfill req.accepts for browser-sync compatibility
    if (typeof req.accepts !== 'function') {
      req.accepts = function requestAccepts() {
        const accept = accepts(req);
        return accept.types.apply(accept, arguments);
      };
    }

    const matchExtensionAndQueryString = req.url.match(/\.(\w+)\?(.*)$/);
    const isMetadataRequest =
      matchExtensionAndQueryString &&
      /^(?:.*&)?metadata(?:$|&|=true)/.test(matchExtensionAndQueryString[2]);
    if (
      matchExtensionAndQueryString &&
      (req.method === 'GET' || req.method === 'HEAD') &&
      ((isImageExtension(matchExtensionAndQueryString[1]) &&
        req.accepts('image/*')) ||
        isMetadataRequest)
    ) {
      // Prevent If-None-Match revalidation with the downstream middleware with ETags that aren't suffixed with "-processimage":
      const queryString = matchExtensionAndQueryString[2];

      const ifNoneMatch = req.headers['if-none-match'];
      if (ifNoneMatch) {
        const validIfNoneMatchTokens = ifNoneMatch
          .split(' ')
          .filter(etag => /-processimage["-]/.test(etag));
        if (validIfNoneMatchTokens.length > 0) {
          req.headers['if-none-match'] = validIfNoneMatchTokens
            .map(token => token.replace(/-processimage(["-])/, '$1'))
            .join(' ');
        } else {
          delete req.headers['if-none-match'];
        }
      }
      delete req.headers['if-modified-since']; // Prevent false positive conditional GETs after enabling processimage
      // hijackResponse will never pass an error here
      // eslint-disable-next-line handle-callback-err
      hijackResponse(
        res,
        // hijackresponse never passes the error
        // eslint-disable-next-line handle-callback-err
        (err, res) => {
          // Polyfill res.status for browser-sync compatibility
          if (typeof res.status !== 'function') {
            res.status = function status(statusCode) {
              res.statusCode = statusCode;
              return res;
            };
          }

          let sourceMetadata;

          function makeFilterInfosAndTargetFormat() {
            const preparedQueryString = prepareImproQueryString(queryString);
            const parseResult = impro.parse(
              preparedQueryString,
              options.allowOperation
            );

            // determine the final content type based on the last
            // requested type conversion operation (if present)
            let outputContentType;
            for (const operation of reverseIteratorFor(
              parseResult.operations
            )) {
              if (operation.name === 'metadata') {
                outputContentType = 'application/json; charset=utf-8';
                break;
              } else if (impro.isTypeByName[operation.name]) {
                outputContentType = `image/${operation.name}`;
                break;
              }
            }
            parseResult.outputContentType =
              outputContentType || sourceMetadata.contentType;

            return parseResult;
          }

          const contentLengthHeaderValue = res.getHeader('Content-Length');
          res.removeHeader('Content-Length');
          const oldETag = res.getHeader('ETag');

          let newETag;
          if (oldETag) {
            newETag = oldETag.replace(/"$/g, '-processimage"');
            res.setHeader('ETag', newETag);
            if (ifNoneMatch && ifNoneMatch.indexOf(newETag) !== -1) {
              res.destroyHijacked();
              return res.status(304).end();
            }
          }

          function startProcessing(optionalFirstChunk) {
            let hasEnded = false;

            let cleanedUp = false;

            function cleanUp(doNotDestroyHijacked) {
              if (!doNotDestroyHijacked) {
                res.destroyHijacked();
              }

              if (!cleanedUp) {
                cleanedUp = true;

                res.removeAllListeners();
              }
            }

            function handleError(err) {
              if (!hasEnded) {
                hasEnded = true;
                if (err) {
                  if ('commandLine' in err) {
                    err.message = `${err.commandLine}: ${err.message}`;
                  }
                  if (
                    err.message ===
                    'Input buffer contains unsupported image format'
                  ) {
                    err = new httpErrors.UnsupportedMediaType(err.message);
                  }
                  if (err.message === 'Input image exceeds pixel limit') {
                    // ?metadata with an unrecognized image format
                    err = new httpErrors.RequestEntityTooLarge(err.message);
                  }

                  next(err);
                }
                res.unhijack();
                cleanUp(true);
              }
            }

            res.once('error', err => {
              // trigger teardown of all pipeline streams
              pipeline.emit(err);
              // respond with an error
              handleError(err);
            });
            res.once('close', cleanUp);

            const outputContentType =
              filterInfosAndTargetFormat.outputContentType;
            if (outputContentType) {
              res.setHeader('Content-Type', outputContentType);
            }

            const type = sourceMetadata && sourceMetadata.contentType;

            const pipeline = impro.createPipeline(
              {
                type,
                sourceMetadata,
                ...engines,
                maxInputPixels: options.maxInputPixels,
                sharpCache: options.sharpCache,
                svgAssetPath: options.root
                  ? Path.resolve(options.root, req.url.substr(1))
                  : null
              },
              filterInfosAndTargetFormat.operations
            );

            if (options.debug) {
              let usedEngines;
              try {
                usedEngines = pipeline.flush().usedEngines;
              } catch (e) {
                res.unhijack();
                return next(e);
              }

              // Only used by the test suite to assert that the right engine is used to process gifs:
              res.setHeader(
                'X-Express-Processimage',
                toUsedNames(usedEngines).join(',')
              );
            }

            if (typeof options.onPipeline === 'function') {
              options.onPipeline(pipeline);
            }

            if (optionalFirstChunk) {
              pipeline.write(optionalFirstChunk);
            }

            // send along processed data
            pipeline
              .on('error', handleError)
              .on('end', () => {
                hasEnded = true;
                cleanUp();
              })
              .pipe(res);

            // initiate processing
            res.pipe(pipeline);
          }

          const contentType = res.getHeader('Content-Type');
          var filterInfosAndTargetFormat;
          if (res.statusCode === 304) {
            res.unhijack();
          } else if (
            isMetadataRequest ||
            (contentType &&
              (options.allowedImageSourceContentTypes
                ? options.allowedImageSourceContentTypes.indexOf(
                    contentType
                  ) !== -1
                : contentType.indexOf('image/') === 0))
          ) {
            sourceMetadata = {
              contentType,
              filesize:
                contentLengthHeaderValue &&
                parseInt(contentLengthHeaderValue, 10),
              etag: oldETag
            };

            filterInfosAndTargetFormat = makeFilterInfosAndTargetFormat();

            if (filterInfosAndTargetFormat.operations.length === 0) {
              return res.unhijack(true);
            }

            if (options.secondGuessSourceContentType) {
              const endOrCloseOrErrorBeforeFirstDataChunkListener = err => {
                if (err) {
                  next(500);
                } else {
                  res.end();
                }
              };
              res.once('error', endOrCloseOrErrorBeforeFirstDataChunkListener);
              res.once('end', endOrCloseOrErrorBeforeFirstDataChunkListener);
              res.once('data', firstChunk => {
                res.removeListener(
                  'end',
                  endOrCloseOrErrorBeforeFirstDataChunkListener
                );
                res.removeListener(
                  'close',
                  endOrCloseOrErrorBeforeFirstDataChunkListener
                );
                let detectedContentType;
                if (
                  firstChunk[0] === 0x47 &&
                  firstChunk[1] === 0x49 &&
                  firstChunk[2] === 0x46
                ) {
                  detectedContentType = 'image/gif';
                } else if (firstChunk[0] === 0xff && firstChunk[1] === 0xd8) {
                  detectedContentType = 'image/jpeg';
                } else if (
                  firstChunk[0] === 0x89 &&
                  firstChunk[1] === 0x50 &&
                  firstChunk[2] === 0x4e &&
                  firstChunk[3] === 0x47
                ) {
                  detectedContentType = 'image/png';
                } else if (firstChunk[0] === 0x42 && firstChunk[1] === 0x4d) {
                  detectedContentType = 'image/bmp';
                }
                if (
                  detectedContentType &&
                  detectedContentType !== sourceMetadata.contentType
                ) {
                  sourceMetadata.contentType = detectedContentType;
                }
                startProcessing(firstChunk);
              });
            } else {
              startProcessing();
            }
          } else {
            res.unhijack();
          }
        },
        { disableBackpressure: true }
      );
      next();
    } else {
      next();
    }
  };

  // exposed for some testing scenarios
  middleware._impro = impro;

  return middleware;
};
<|endoftext|>