<commit_before>(function () {
  "use strict";
  var PaymentValidator = (function() {
    var PaymentValidator = function(params) {
      var validator = require("validator");
      var validationErrors = [];
      var payment = params.payment;
      var optionalFields = params.optionalFields || [];
      var moment = require("moment");
      var SAFE_CHARACTERS = /^[a-z_]*$/;
      var log = require("./log")("validation:payment");

      if(params.payment.locale && process.env.NODE_ENV !== "test") {
        var err = new Error("payment.locale will be deprecated soon. This is NOT an error, just a warning! " +
            "And the stacktrace below is just so you can easily find where payment.locale has been used :)");
        if(params.debug) {
          log.warn("Error %O", err.stack);
        } else {
          log.warn(err.message);
        }
      }

      function isOptional(field) {
        return optionalFields.indexOf(field) !== -1;
      }

      function sanitizeForValidation(value) {
        if(value === "undefined" || value === "null") {
          value = null;
        }
        value = value ? validator.stripLow(value + "").replace(/\s\s+/g, " ") : "";
        if(value === " ") {
          value = "";
        }
        return value;
      }

      function validateIpAddress() {
        var ip = sanitizeForValidation(payment.ip);
        if(validator.isEmpty(ip) && isOptional("ip")) {
          return;
        } else {
          if(validator.isEmpty(ip)) {
            validationErrors.push({
              message: "Invalid payment ip",
              translationKey: "invalid.payment.ip",
              elementName: "payment[ip]",
              value: "Payment ip is mandatory"
            });
          } else {
            if(!validator.isIP(ip)) {
              validationErrors.push({
                message: "Invalid payment ip",
                translationKey: "invalid.payment.ip",
                elementName: "payment[ip]",
                value: payment.ip
              });
            }
          }
        }
      }

      function validateCardHolderEmail() {
        var cardHolderEmail = sanitizeForValidation(payment.cardHolderEmail);
        if(validator.isEmpty(cardHolderEmail) && isOptional("cardHolderEmail")) {
          return;
        } else {
          if(!validator.isEmpty(cardHolderEmail)) {
            if(!validator.isEmail(cardHolderEmail)) {
              validationErrors.push({
                message: "Invalid payment cardHolderEmail",
                translationKey: "invalid.payment.cardHolderEmail",
                elementName: "payment[cardHolderEmail]",
                value: cardHolderEmail
              });
            }
          } else {
            validationErrors.push({
              message: "Invalid payment cardHolderEmail",
              translationKey: "invalid.payment.cardHolderEmail",
              elementName: "payment[cardHolderEmail]",
              value: "Payment cardHolderEmail is mandatory"
            });
          }
        }
      }

      function validateCardHolderName() {
        var cardHolderName = sanitizeForValidation(payment.cardHolderName);
        if(validator.isEmpty(cardHolderName) && isOptional("cardHolderName")) {
          return;
        } else {
          if(!validator.isEmpty(cardHolderName)) {
            if(cardHolderName.length < 2 ||
              cardHolderName.length > 52) {
              validationErrors.push({
                message: "Invalid payment cardHolderName",
                translationKey: "invalid.payment.cardHolderName",
                elementName: "payment[cardHolderName]",
                value: payment.cardHolderName
              });
            }
          } else {
            validationErrors.push({
              message: "Invalid payment cardHolderName",
              translationKey: "invalid.payment.cardHolderName",
              elementName: "payment[cardHolderName]",
              value: "Payment cardHolderName is mandatory"
            });
          }
        }
      }

      function validatePaymentMethod() {
        var paymentMethod = sanitizeForValidation(payment.paymentMethod);
        if(validator.isEmpty(paymentMethod) && isOptional("paymentMethod")) {
          return;
        } else {
          if(validator.isEmpty(paymentMethod)) {
            validationErrors.push({
              message: "Invalid payment paymentMethod",
              translationKey: "invalid.payment.paymentMethod",
              elementName: "payment[paymentMethod]",
              value: "Payment paymentMethod is mandatory"
            });
          } else if(!SAFE_CHARACTERS.test(paymentMethod)) {
            validationErrors.push({
              message: "Invalid payment paymentMethod",
              translationKey: "invalid.payment.paymentMethod",
              elementName: "payment[paymentMethod]",
              value: paymentMethod
            });
          }
        }
      }

      function validateCreditCardNumber() {
        var creditCardNumber = sanitizeForValidation(payment.creditCardNumber);
        if(validator.isEmpty(creditCardNumber) && isOptional("creditCardNumber")) {
          return;
        } else {
          if(validator.isEmpty(creditCardNumber)) {
            validationErrors.push({
              message: "Invalid payment creditCardNumber",
              translationKey: "invalid.payment.creditCardNumber",
              elementName: "payment[creditCardNumber]",
              value: "Payment creditCardNumber is mandatory"
            });
          } else {
            if(!validator.isCreditCard(creditCardNumber)) {
              validationErrors.push({
                message: "Invalid payment creditCardNumber",
                translationKey: "invalid.payment.creditCardNumber",
                elementName: "payment[creditCardNumber]",
                value: payment.creditCardNumber
              });
            }
          }
        }
      }

      function validateCvv() {
        var ccv = sanitizeForValidation(payment.ccv || payment.cvv);
        if(validator.isEmpty(ccv) && isOptional("ccv")) {
          return;
        } else {
          if(validator.isEmpty(ccv)) {
            validationErrors.push({
              message: "Invalid payment ccv",
              translationKey: "invalid.payment.ccv",
              elementName: "payment[ccv]",
              value: "Payment ccv is mandatory"
            });
          } else {
            if((ccv.length < 3 || ccv.length > 4) ||
                !validator.isNumeric(ccv)) {
              validationErrors.push({
                message: "Invalid payment ccv",
                translationKey: "invalid.payment.ccv",
                elementName: "payment[ccv]",
                value: payment.ccv
              });
            }
          }
        }
      }

      function validateLocale() {
        var locale = sanitizeForValidation(payment.locale);
        if(validator.isEmpty(locale) && isOptional("locale")) {
          return;
        } else {
          if(validator.isEmpty(locale)) {
            validationErrors.push({
              message: "Invalid payment locale",
              translationKey: "invalid.payment.locale",
              elementName: "payment[locale]",
              value: "Payment locale is mandatory"
            });
          } else {
            if (!(/^([a-z]){2}-[A-Z]{2}$/).test(locale)) { // en-US, es-ES
              validationErrors.push({
                value: "Payment locale must be 5 characters",
                message: "Invalid payment locale",
                elementName: "payment[locale]",
                translationKey: "invalid.payment.locale",
              });
            }
          }
        }
      }

      function validateExpiresMonth() {
        var expiresMonth = sanitizeForValidation(payment.expiresMonth + "");
        if(validator.isEmpty(expiresMonth) && isOptional("expiresMonth")) {
          return;
        } else {
          if(validator.isEmpty(expiresMonth)) {
            validationErrors.push({
              message: "Invalid payment expiresMonth",
              translationKey: "invalid.payment.expiresMonth",
              elementName: "payment[expiresMonth]",
              value: "Payment expiresMonth is mandatory"
            });
          } else {
            expiresMonth = Number(expiresMonth);
            if(!validator.isInt("" + expiresMonth) ||
                expiresMonth < 1 ||
                expiresMonth > 12) {
              validationErrors.push({
                message: "Invalid payment expiresMonth",
                translationKey: "invalid.payment.expiresMonth",
                elementName: "payment[expiresMonth]",
                value: expiresMonth + ""
              });
            }
          }
        }
      }

      function validateExpiresYear() {
        var expiresYear = sanitizeForValidation(payment.expiresYear + "");
        if(validator.isEmpty(expiresYear) && isOptional("expiresYear")) {
          return;
        } else {
          if(validator.isEmpty(expiresYear)) {
            validationErrors.push({
              message: "Invalid payment expiresYear",
              translationKey: "invalid.payment.expiresYear",
              elementName: "payment[expiresYear]",
              value: "Payment expiresYear is mandatory"
            });
          } else {
            expiresYear = Number(expiresYear);
            if(!validator.isInt("" + expiresYear) ||
                expiresYear < moment().year()) {
              validationErrors.push({
                message: "Invalid payment expiresYear",
                translationKey: "invalid.payment.expiresYear",
                elementName: "payment[expiresYear]",
                value: expiresYear + ""
              });
            } else if(moment(payment.expiresMonth + "-" + payment.expiresYear, "MM-YYYY")
                .isBefore(moment().startOf("month"))) {
              validationErrors.push({
                message: "Card has expired",
                translationKey: "invalid.payment.cardHasExpired",
                value: payment.expiresMonth + "/" + payment.expiresYear
              });
            }
          }
        }
      }

      return {
        validate: function() {
          validateIpAddress();
          validateCardHolderEmail();
          validateCardHolderName();
          validatePaymentMethod();
          validateCreditCardNumber();
          validateCvv();
          validateLocale();
          validateExpiresMonth();
          validateExpiresYear();
          return validationErrors;
        }
      };
    };

    module.exports = PaymentValidator;
  })();
}());
<commit_msg>Added validation for payment.numberOfInstallments<commit_after>(function () {
  "use strict";
  var PaymentValidator = (function() {
    var PaymentValidator = function(params) {
      var validator = require("validator");
      var validationErrors = [];
      var payment = params.payment;
      var optionalFields = params.optionalFields || [];
      var moment = require("moment");
      var SAFE_CHARACTERS = /^[a-z_]*$/;
      var log = require("./log")("validation:payment");

      if(params.payment.locale && process.env.NODE_ENV !== "test") {
        var err = new Error("payment.locale will be deprecated soon. This is NOT an error, just a warning! " +
            "And the stacktrace below is just so you can easily find where payment.locale has been used :)");
        if(params.debug) {
          log.warn("Error %O", err.stack);
        } else {
          log.warn(err.message);
        }
      }

      function isOptional(field) {
        return optionalFields.indexOf(field) !== -1;
      }

      function sanitizeForValidation(value) {
        if(value === "undefined" || value === "null") {
          value = null;
        }
        value = value ? validator.stripLow(value + "").replace(/\s\s+/g, " ") : "";
        if(value === " ") {
          value = "";
        }
        return value;
      }

      function validateIpAddress() {
        var ip = sanitizeForValidation(payment.ip);
        if(validator.isEmpty(ip) && isOptional("ip")) {
          return;
        } else {
          if(validator.isEmpty(ip)) {
            validationErrors.push({
              message: "Invalid payment ip",
              translationKey: "invalid.payment.ip",
              elementName: "payment[ip]",
              value: "Payment ip is mandatory"
            });
          } else {
            if(!validator.isIP(ip)) {
              validationErrors.push({
                message: "Invalid payment ip",
                translationKey: "invalid.payment.ip",
                elementName: "payment[ip]",
                value: payment.ip
              });
            }
          }
        }
      }

      function validateCardHolderEmail() {
        var cardHolderEmail = sanitizeForValidation(payment.cardHolderEmail);
        if(validator.isEmpty(cardHolderEmail) && isOptional("cardHolderEmail")) {
          return;
        } else {
          if(!validator.isEmpty(cardHolderEmail)) {
            if(!validator.isEmail(cardHolderEmail)) {
              validationErrors.push({
                message: "Invalid payment cardHolderEmail",
                translationKey: "invalid.payment.cardHolderEmail",
                elementName: "payment[cardHolderEmail]",
                value: cardHolderEmail
              });
            }
          } else {
            validationErrors.push({
              message: "Invalid payment cardHolderEmail",
              translationKey: "invalid.payment.cardHolderEmail",
              elementName: "payment[cardHolderEmail]",
              value: "Payment cardHolderEmail is mandatory"
            });
          }
        }
      }

      function validateCardHolderName() {
        var cardHolderName = sanitizeForValidation(payment.cardHolderName);
        if(validator.isEmpty(cardHolderName) && isOptional("cardHolderName")) {
          return;
        } else {
          if(!validator.isEmpty(cardHolderName)) {
            if(cardHolderName.length < 2 ||
              cardHolderName.length > 52) {
              validationErrors.push({
                message: "Invalid payment cardHolderName",
                translationKey: "invalid.payment.cardHolderName",
                elementName: "payment[cardHolderName]",
                value: payment.cardHolderName
              });
            }
          } else {
            validationErrors.push({
              message: "Invalid payment cardHolderName",
              translationKey: "invalid.payment.cardHolderName",
              elementName: "payment[cardHolderName]",
              value: "Payment cardHolderName is mandatory"
            });
          }
        }
      }

      function validatePaymentMethod() {
        var paymentMethod = sanitizeForValidation(payment.paymentMethod);
        if(validator.isEmpty(paymentMethod) && isOptional("paymentMethod")) {
          return;
        } else {
          if(validator.isEmpty(paymentMethod)) {
            validationErrors.push({
              message: "Invalid payment paymentMethod",
              translationKey: "invalid.payment.paymentMethod",
              elementName: "payment[paymentMethod]",
              value: "Payment paymentMethod is mandatory"
            });
          } else if(!SAFE_CHARACTERS.test(paymentMethod)) {
            validationErrors.push({
              message: "Invalid payment paymentMethod",
              translationKey: "invalid.payment.paymentMethod",
              elementName: "payment[paymentMethod]",
              value: paymentMethod
            });
          }
        }
      }

      function validateCreditCardNumber() {
        var creditCardNumber = sanitizeForValidation(payment.creditCardNumber);
        if(validator.isEmpty(creditCardNumber) && isOptional("creditCardNumber")) {
          return;
        } else {
          if(validator.isEmpty(creditCardNumber)) {
            validationErrors.push({
              message: "Invalid payment creditCardNumber",
              translationKey: "invalid.payment.creditCardNumber",
              elementName: "payment[creditCardNumber]",
              value: "Payment creditCardNumber is mandatory"
            });
          } else {
            if(!validator.isCreditCard(creditCardNumber)) {
              validationErrors.push({
                message: "Invalid payment creditCardNumber",
                translationKey: "invalid.payment.creditCardNumber",
                elementName: "payment[creditCardNumber]",
                value: payment.creditCardNumber
              });
            }
          }
        }
      }

      function validateCvv() {
        var ccv = sanitizeForValidation(payment.ccv || payment.cvv);
        if(validator.isEmpty(ccv) && isOptional("ccv")) {
          return;
        } else {
          if(validator.isEmpty(ccv)) {
            validationErrors.push({
              message: "Invalid payment ccv",
              translationKey: "invalid.payment.ccv",
              elementName: "payment[ccv]",
              value: "Payment ccv is mandatory"
            });
          } else {
            if((ccv.length < 3 || ccv.length > 4) ||
                !validator.isNumeric(ccv)) {
              validationErrors.push({
                message: "Invalid payment ccv",
                translationKey: "invalid.payment.ccv",
                elementName: "payment[ccv]",
                value: payment.ccv
              });
            }
          }
        }
      }

      function validateLocale() {
        var locale = sanitizeForValidation(payment.locale);
        if(validator.isEmpty(locale) && isOptional("locale")) {
          return;
        } else {
          if(validator.isEmpty(locale)) {
            validationErrors.push({
              message: "Invalid payment locale",
              translationKey: "invalid.payment.locale",
              elementName: "payment[locale]",
              value: "Payment locale is mandatory"
            });
          } else {
            if (!(/^([a-z]){2}-[A-Z]{2}$/).test(locale)) { // en-US, es-ES
              validationErrors.push({
                value: "Payment locale must be 5 characters",
                message: "Invalid payment locale",
                elementName: "payment[locale]",
                translationKey: "invalid.payment.locale",
              });
            }
          }
        }
      }

      function validateExpiresMonth() {
        var expiresMonth = sanitizeForValidation(payment.expiresMonth + "");
        if(validator.isEmpty(expiresMonth) && isOptional("expiresMonth")) {
          return;
        } else {
          if(validator.isEmpty(expiresMonth)) {
            validationErrors.push({
              message: "Invalid payment expiresMonth",
              translationKey: "invalid.payment.expiresMonth",
              elementName: "payment[expiresMonth]",
              value: "Payment expiresMonth is mandatory"
            });
          } else {
            expiresMonth = Number(expiresMonth);
            if(!validator.isInt("" + expiresMonth) ||
                expiresMonth < 1 ||
                expiresMonth > 12) {
              validationErrors.push({
                message: "Invalid payment expiresMonth",
                translationKey: "invalid.payment.expiresMonth",
                elementName: "payment[expiresMonth]",
                value: expiresMonth + ""
              });
            }
          }
        }
      }

      function validateExpiresYear() {
        var expiresYear = sanitizeForValidation(payment.expiresYear + "");
        if(validator.isEmpty(expiresYear) && isOptional("expiresYear")) {
          return;
        } else {
          if(validator.isEmpty(expiresYear)) {
            validationErrors.push({
              message: "Invalid payment expiresYear",
              translationKey: "invalid.payment.expiresYear",
              elementName: "payment[expiresYear]",
              value: "Payment expiresYear is mandatory"
            });
          } else {
            expiresYear = Number(expiresYear);
            if(!validator.isInt("" + expiresYear) ||
                expiresYear < moment().year()) {
              validationErrors.push({
                message: "Invalid payment expiresYear",
                translationKey: "invalid.payment.expiresYear",
                elementName: "payment[expiresYear]",
                value: expiresYear + ""
              });
            } else if(moment(payment.expiresMonth + "-" + payment.expiresYear, "MM-YYYY")
                .isBefore(moment().startOf("month"))) {
              validationErrors.push({
                message: "Card has expired",
                translationKey: "invalid.payment.cardHasExpired",
                value: payment.expiresMonth + "/" + payment.expiresYear
              });
            }
          }
        }
      }

      function validateNumberOfInstallments() {
        var numberOfInstallments= sanitizeForValidation(payment.numberOfInstallments + "");
        if (validator.isEmpty(numberOfInstallments) && isOptional("numberOfInstallments")) {
          return;
        } else {
          if (validator.isEmpty(numberOfInstallments)) {
            validationErrors.push({
              value: "Payment numberOfInstallments is mandatory",
              message: "Payment numberOfInstallments is mandatory",
              translationKey: "invalid.payment.numberOfInstallments",
              elementName: "payment[numberOfInstallments]"
            });
          } else {
            if (!validator.isInt("" + numberOfInstallments) ||
              numberOfInstallments < 3 || numberOfInstallments > 60) {
              validationErrors.push({
                message: "Invalid payment numberOfInstallments",
                translationKey: "invalid.payment.numberOfInstallments",
                value: "" + numberOfInstallments ,
                elementName: "payment[numberOfInstallments]"
              });
            }
          }
        }
      }

      return {
        validate: function() {
          validateIpAddress();
          validateCardHolderEmail();
          validateCardHolderName();
          validatePaymentMethod();
          validateCreditCardNumber();
          validateCvv();
          validateLocale();
          validateExpiresMonth();
          validateExpiresYear();
          validateNumberOfInstallments();
          return validationErrors;
        }
      };
    };

    module.exports = PaymentValidator;
  })();
}());
<|endoftext|>


<commit_before>/** @license MIT License (c) copyright B Cavalier & J Hann */

/**
 * Plugin that allows wire to be used as a plugin within a wire spec
 *
 * wire is part of the cujo.js family of libraries (http://cujojs.com/)
 *
 * Licensed under the MIT License at:
 * http://www.opensource.org/licenses/mit-license.php
 */

(function(define) {
define(function(require) {

	var when, async, object;

	when = require('when');
	async = require('../async');
	object = require('../object');

	return {
		wire$plugin: function(ready) {

			return {
				resolvers: {
					wire: wireResolver
				},
				factories: {
					wire: wireFactory
				}
			};

			/**
			 * Factory that creates either a child context, or a *function* that will create
			 * that child context.  In the case that a child is created, this factory returns
			 * a promise that will resolve when the child has completed wiring.
			 *
			 * @param {Object} resolver used to resolve with the created component
			 * @param {Object} spec component spec for the component to be created
			 * @param {function} wire scoped wire function
			 */
			function wireFactory(resolver, spec, wire) {
				//
				// TODO: Move wireFactory to its own module
				//
				var options, module, provide, defer, waitParent;

				options = spec.wire;

				// Get child spec and options
				if(options && 'spec' in options) {
					module = options.spec;
					waitParent = options.waitParent;
					defer = options.defer;
					provide = options.provide;
				} else {
					module = options;
				}

				function init(context) {
					var initialized;

					if(provide) {
						initialized = when(wire(provide), function(provides) {
							object.safeMixin(context.components, provides);
						});
					}

					return initialized;
				}

				function createChild(/** {Object|String}? */ mixin) {
					var spec, config;

					spec = mixin ? [].concat(module, mixin) : module;
					config = { contextHandlers: { init: init } };

					var child = wire.createChild(spec, config);
					return defer ? child
						: when(child, function(child) {
						return object.hasOwn(child, '$exports') ? child.$exports : child;
					});
				}

				if (defer) {
					// Resolve with the createChild *function* itself
					// which can be used later to wire the spec
					resolver.resolve(createChild);

				} else if(waitParent) {

					var childPromise = when(ready, function() {
						// ensure nothing is passed to createChild here
						return createChild();
					});

					resolver.resolve(async.wrapValue(childPromise));

				} else {
					when.chain(createChild(spec), resolver);

				}
			}
		}
	};

	/**
	 * Builtin reference resolver that resolves to the context-specific
	 * wire function.
	 */
	function wireResolver(resolver, _, __, wire) {
		resolver.resolve(wire.createChild);
	}

});
}(typeof define === 'function' ? define : function(factory) { module.exports = factory(require); }));
<commit_msg>Ditch more when.chain uses<commit_after>/** @license MIT License (c) copyright B Cavalier & J Hann */

/**
 * Plugin that allows wire to be used as a plugin within a wire spec
 *
 * wire is part of the cujo.js family of libraries (http://cujojs.com/)
 *
 * Licensed under the MIT License at:
 * http://www.opensource.org/licenses/mit-license.php
 */

(function(define) {
define(function(require) {

	var when, async, object;

	when = require('when');
	async = require('../async');
	object = require('../object');

	return {
		wire$plugin: function(ready) {

			return {
				resolvers: {
					wire: wireResolver
				},
				factories: {
					wire: wireFactory
				}
			};

			/**
			 * Factory that creates either a child context, or a *function* that will create
			 * that child context.  In the case that a child is created, this factory returns
			 * a promise that will resolve when the child has completed wiring.
			 *
			 * @param {Object} resolver used to resolve with the created component
			 * @param {Object} spec component spec for the component to be created
			 * @param {function} wire scoped wire function
			 */
			function wireFactory(resolver, spec, wire) {
				//
				// TODO: Move wireFactory to its own module
				//
				var options, module, provide, defer, waitParent, result;

				options = spec.wire;

				// Get child spec and options
				if(options && 'spec' in options) {
					module = options.spec;
					waitParent = options.waitParent;
					defer = options.defer;
					provide = options.provide;
				} else {
					module = options;
				}

				function init(context) {
					var initialized;

					if(provide) {
						initialized = when(wire(provide), function(provides) {
							object.safeMixin(context.components, provides);
						});
					}

					return initialized;
				}

				function createChild(/** {Object|String}? */ mixin) {
					var spec, config;

					spec = mixin ? [].concat(module, mixin) : module;
					config = { contextHandlers: { init: init } };

					var child = wire.createChild(spec, config);
					return defer ? child
						: when(child, function(child) {
						return object.hasOwn(child, '$exports') ? child.$exports : child;
					});
				}

				if (defer) {
					// Resolve with the createChild *function* itself
					// which can be used later to wire the spec
					result = createChild;

				} else if(waitParent) {

					var childPromise = when(ready, function() {
						// ensure nothing is passed to createChild here
						return createChild();
					});

					result = async.wrapValue(childPromise);

				} else {
					result = createChild(spec);
				}

				resolver.resolve(result);
			}
		}
	};

	/**
	 * Builtin reference resolver that resolves to the context-specific
	 * wire function.
	 */
	function wireResolver(resolver, _, __, wire) {
		resolver.resolve(wire.createChild);
	}

});
}(typeof define === 'function' ? define : function(factory) { module.exports = factory(require); }));
<|endoftext|>


<commit_before>var B = require("buster-core");
var option = require("./option");
var operand = require("./operand");
var shorthand = require("./shorthand");
var parser = require("./parser");

function detectDuplicateOption(suspect, option) {
    if (!operand.isOperand(option) && suspect.intersects(option)) {
        throw new Error("Duplicate option (" + suspect.signature + ")");
    }
}

function detectDuplicateShorthand(shorthand, option) {
    if (option.recognizes && option.recognizes(shorthand)) {
        throw new Error("Can not add shorthand '" + shorthand +
                        "', option already exists.");
    }
}

module.exports = {
    validators: require("./validators"),

    create: function () {
        return B.create(this);
    },

    add: function (opt) {
        this.options.push(opt);
        return opt;
    },

    createOption: function (options, properties) {
        var opt = option.create(options, properties);
        this.options.forEach(B.partial(detectDuplicateOption, opt));
        return this.add(opt);
    },

    createOperand: function (name, properties) {
        return this.add(operand.create(name, properties));
    },

    addShorthand: function (opt, args) {
        if (!option.isOption(opt)) {
            throw new Error("Invalid option '" + opt + "'");
        }
        this.options.forEach(B.partial(detectDuplicateShorthand, opt));
        return this.add(shorthand.create(opt, args));
    },

    parse: function (argv, onFinished) {
        try {
            parser.parse(this.options, argv, function (err, options) {
                if (err) {
                    err = [err];
                }
                onFinished(err, options);
            }.bind(this));
        } catch (e) {
            onFinished([e.message]);
        }
    },

    get options() {
        if (!this._options) { this._options = []; }
        return this._options;
    },

    set options(value) { throw new Error("options is not writable"); }
};

var flag;
for (flag in operand.flags) {
    module.exports[flag] = operand.flags[flag];
}
<commit_msg>No more getters or setters<commit_after>var B = require("buster-core");
var option = require("./option");
var operand = require("./operand");
var shorthand = require("./shorthand");
var parser = require("./parser");

function detectDuplicateOption(suspect, option) {
    if (!operand.isOperand(option) && suspect.intersects(option)) {
        throw new Error("Duplicate option (" + suspect.signature + ")");
    }
}

function detectDuplicateShorthand(shorthand, option) {
    if (option.recognizes && option.recognizes(shorthand)) {
        throw new Error("Can not add shorthand '" + shorthand +
                        "', option already exists.");
    }
}

function optList(collection) {
    if (!collection.options) { collection.options = []; }
    return collection.options;
}

module.exports = {
    validators: require("./validators"),

    create: function () {
        return B.extend(B.create(this), {
            options: []
        });
    },

    add: function (opt) {
        optList(this).push(opt);
        return opt;
    },

    createOption: function (options, properties) {
        var opt = option.create(options, properties);
        optList(this).forEach(B.partial(detectDuplicateOption, opt));
        return this.add(opt);
    },

    createOperand: function (name, properties) {
        return this.add(operand.create(name, properties));
    },

    addShorthand: function (opt, args) {
        if (!option.isOption(opt)) {
            throw new Error("Invalid option '" + opt + "'");
        }
        optList(this).forEach(B.partial(detectDuplicateShorthand, opt));
        return this.add(shorthand.create(opt, args));
    },

    parse: function (argv, onFinished) {
        try {
            parser.parse(optList(this), argv, function (err, options) {
                if (err) {
                    err = [err];
                }
                onFinished(err, options);
            }.bind(this));
        } catch (e) {
            onFinished([e.message]);
        }
    }
};

var flag;
for (flag in operand.flags) {
    module.exports[flag] = operand.flags[flag];
}
<|endoftext|>


<commit_before>'use strict';

module.exports = requestValidator;

var util = require('./helpers/util'),
    ono  = require('ono'),
    _    = require('lodash');

/**
 * Validates the HTTP request against the Swagger API.
 * An error is sent downstream if the request is invalid for any reason.
 *
 * @param   {MiddlewareContext}    context
 * @returns {function[]}
 */
function requestValidator(context) {
  return [http500, http401, http404, http405, http406, http413, http415];

  /**
   * Throws an HTTP 500 error if the Swagger API is invalid.
   * Calling {@link Middleware#init} again with a valid Swagger API will clear the error.
   */
  function http500(req, res, next) {
    if (context.error) {
      context.error.status = 500;
      throw context.error;
    }

    next();
  }
}

/**
 * If the Swagger API requires security for the request, and the request doesn't contain the necessary security info,
 * then an HTTP 401 (Unauthorized) is thrown, and the WWW-Authenticate header is set.
 * NOTE: This does not perform any authentication or authorization. It simply verifies that authentication info is present.
 */
function http401(req, res, next) {
  if (util.isSwaggerRequest(req) && req.swagger.security.length > 0) {
    var securityTypes = [];

    util.debug('Validating security requirements');

    // Loop through each Security Requirement (if ANY of them are met, then the request is valid)
    var isValid = req.swagger.security.some(function(requirement) {
      var securityDefinitions = _.map(requirement, function(scopes, name) {
        return req.swagger.api.securityDefinitions[name];
      });

      // Loop through each Security Definition (if ALL of them are met, then the request is valid)
      return securityDefinitions.every(function(securityDef) {
        if (securityTypes.indexOf(securityDef.type) === -1) {
          securityTypes.push(securityDef.type);
        }

        if (securityDef.type === 'basic') {
          return _.startsWith(req.header('Authorization'), 'Basic ');
        }
        else if (securityDef.type === 'apiKey' && securityDef.in === 'header') {
          return req.header(securityDef.name) !== undefined;
        }
        else if (securityDef.type === 'apiKey' && securityDef.in === 'query') {
          return req.query[securityDef.name] !== undefined;
        }
        else {
          // For any other type of security, just assume it's valid.
          // TODO: Is there a way to validate OAuth2 here?
          return true;
        }
      });
    });

    if (!isValid) {
      securityTypes = securityTypes.join(', ');
      util.debug(
        'The client didn\'t provide authentication information for any of the required authentication types (%s). ' +
        'Returning HTTP 401 (Unauthorized)', securityTypes
      );
      res.set('WWW-Authenticate', 'Basic realm="' + (req.hostname || 'server') + '"');
      throw ono({status: 401}, '%s %s requires authentication (%s)', req.method, req.path, securityTypes);
    }
  }

  next();
}

/**
 * If the request is under the Swagger API's basePath, but no matching Path was found,
 * then an HTTP 404 (Not Found) error is thrown
 */
function http404(req, res, next) {
  if (req.swagger && req.swagger.api && !req.swagger.path) {
    util.debug(
      'Client requested path "%s", which is not defined in the Swagger API. Returning HTTP 404 (Not Found)',
      req.path
    );
    throw ono({status: 404}, 'Resource not found: %s', req.path);
  }

  next();
}

/**
 * If the Swagger Path was matched, but the HTTP method doesn't match any of the Swagger Operations,
 * then an HTTP 405 (Method Not Allowed) error is thrown, and the "Allow" header is set to the list of valid methods
 */
function http405(req, res, next) {
  if (req.swagger && req.swagger.path && !req.swagger.operation) {
    util.debug(
      'Client attempted a %s operation on "%s", which is not allowed by the Swagger API. ' +
      'Returning HTTP 405 (Method Not Allowed)',
      req.method, req.path
    );

    // Let the client know which methods are allowed
    var allowedList = util.getAllowedMethods(req.swagger.path);
    res.set('Allow', allowedList);

    throw ono({status: 405}, '%s does not allow %s. \nAllowed methods: %s',
      req.path, req.method, allowedList);
  }

  next();
}

/**
 * If the Swagger API specifies the MIME types that this operation produces,
 * and the HTTP Accept header does not match any of those MIME types, then an HTTP 406 (Not Acceptable) is thrown.
 */
function http406(req, res, next) {
  if (util.isSwaggerRequest(req)) {
    // Get the MIME types that this operation produces
    var produces = req.swagger.operation.produces || req.swagger.api.produces || [];

    if (produces.length > 0) {
      util.debug('Validating Accept header (%s)', req.get('Accept'));

      if (!req.accepts(produces)) {
        var accepts = req.accepts();
        util.debug(
          'The %s operation on "%s" only produces %j content, but the client requested %j. ' +
          'Returning HTTP 406 (Not Acceptable)',
          req.method, req.path, produces, accepts
        );
        throw ono({status: 406}, '%s %s cannot produce any of the requested formats (%s). \nSupported formats: %s',
          req.method, req.path, accepts.join(', '), produces.join(', '));
      }
    }
  }

  next();
}

/**
 * Throws an HTTP 413 (Request Entity Too Large) if the HTTP request includes
 * body content that is not allowed by the Swagger API.
 */
function http413(req, res, next) {
  if (util.isSwaggerRequest(req)) {
    // Determine if the request allows body content
    var bodyAllowed = req.swagger.params.some(function(param) {
      return param.in === 'body' || param.in === 'formData';
    });

    if (!bodyAllowed) {
      // NOTE: We used to also check the Transfer-Encoding header, but that fails in Node 0.10.x
      // TODO: Once we drop support for Node 0.10.x, add a Transfer-Encoding check (via typeIs.hasBody())
      var length = req.get('Content-Length');
      util.debug('Validating Content-Length header (%d)', length);

      // NOTE: Even a zero-byte file attachment will have a Content-Length > 0
      if (length > 0) {
        util.debug(
          'The HTTP request contains body content, but the %s operation on "%s" does not allow a request body. ' +
          'Returning HTTP 413 (Request Entity Too Large)',
          req.method, req.path
        );
        throw ono({status: 413}, '%s %s does not allow body content', req.method, req.path);
      }
    }
  }

  next();
}

/**
 * Validates the HTTP Content-Type header against the Swagger API's "consumes" MIME types,
 * and throws an HTTP 415 (Unsupported Media Type) if there's a conflict.
 */
function http415(req, res, next) {
  if (util.isSwaggerRequest(req)) {
    // Only validate the Content-Type if there's body content
    if (!_.isEmpty(req.body)) {
      // Get the MIME types that this operation consumes
      var consumes = req.swagger.operation.consumes || req.swagger.api.consumes || [];

      if (consumes.length > 0) {
        util.debug('Validating Content-Type header (%s)', req.get('Content-Type'));

        if (!req.is(consumes)) {
          var contentType = req.header('Content-Type');
          util.debug(
            'Client attempted to send %s data to the %s operation on "%s", which is not allowed by the Swagger API. ' +
            'Returning HTTP 415 (Unsupported Media Type)',
            contentType, req.method, req.path
          );
          throw ono({status: 415}, '%s %s does not allow Content-Type "%s". \nAllowed Content-Types: %s',
            req.method, req.path, contentType, consumes.join(', '));
        }
      }
    }
  }

  next();
}

<commit_msg>OpenAPI 3 - securityScheme<commit_after>'use strict';

module.exports = requestValidator;

var util = require('./helpers/util'),
    ono  = require('ono'),
    _    = require('lodash');

/**
 * Validates the HTTP request against the Swagger API.
 * An error is sent downstream if the request is invalid for any reason.
 *
 * @param   {MiddlewareContext}    context
 * @returns {function[]}
 */
function requestValidator(context) {
  return [http500, http401, http404, http405, http406, http413, http415];

  /**
   * Throws an HTTP 500 error if the Swagger API is invalid.
   * Calling {@link Middleware#init} again with a valid Swagger API will clear the error.
   */
  function http500(req, res, next) {
    if (context.error) {
      context.error.status = 500;
      throw context.error;
    }

    next();
  }
}

/**
 * If the Swagger API requires security for the request, and the request doesn't contain the necessary security info,
 * then an HTTP 401 (Unauthorized) is thrown, and the WWW-Authenticate header is set.
 * NOTE: This does not perform any authentication or authorization. It simply verifies that authentication info is present.
 */
function http401(req, res, next) {
  if (util.isSwaggerRequest(req) && req.swagger.security.length > 0) {
    var securityTypes = [];

    util.debug('Validating security requirements');

    // Loop through each Security Requirement (if ANY of them are met, then the request is valid)
    var isValid = req.swagger.security.some(function(requirement) {
      var securityDefinitions = _.map(requirement, function(scopes, name) {

        // support OpenAPI 3.0
        if (req.swagger.api.securityDefinitions === undefined)
          return(req.swagger.api.components.securitySchemes[name]);
        else
          return req.swagger.api.securityDefinitions[name];
      });

      // Loop through each Security Definition (if ALL of them are met, then the request is valid)
      return securityDefinitions.every(function(securityDef) {
        if (securityTypes.indexOf(securityDef.type) === -1) {
          securityTypes.push(securityDef.type);
        }

        if (securityDef.type === 'basic') {
          return _.startsWith(req.header('Authorization'), 'Basic ');
        }
        else if (securityDef.type === 'apiKey' && securityDef.in === 'header') {
          return req.header(securityDef.name) !== undefined;
        }
        else if (securityDef.type === 'apiKey' && securityDef.in === 'query') {
          return req.query[securityDef.name] !== undefined;
        }
        else {
          // For any other type of security, just assume it's valid.
          // TODO: Is there a way to validate OAuth2 here?
          return true;
        }
      });
    });

    if (!isValid) {
      securityTypes = securityTypes.join(', ');
      util.debug(
        'The client didn\'t provide authentication information for any of the required authentication types (%s). ' +
        'Returning HTTP 401 (Unauthorized)', securityTypes
      );
      res.set('WWW-Authenticate', 'Basic realm="' + (req.hostname || 'server') + '"');
      throw ono({status: 401}, '%s %s requires authentication (%s)', req.method, req.path, securityTypes);
    }
  }

  next();
}

/**
 * If the request is under the Swagger API's basePath, but no matching Path was found,
 * then an HTTP 404 (Not Found) error is thrown
 */
function http404(req, res, next) {
  if (req.swagger && req.swagger.api && !req.swagger.path) {
    util.debug(
      'Client requested path "%s", which is not defined in the Swagger API. Returning HTTP 404 (Not Found)',
      req.path
    );
    throw ono({status: 404}, 'Resource not found: %s', req.path);
  }

  next();
}

/**
 * If the Swagger Path was matched, but the HTTP method doesn't match any of the Swagger Operations,
 * then an HTTP 405 (Method Not Allowed) error is thrown, and the "Allow" header is set to the list of valid methods
 */
function http405(req, res, next) {
  if (req.swagger && req.swagger.path && !req.swagger.operation) {
    util.debug(
      'Client attempted a %s operation on "%s", which is not allowed by the Swagger API. ' +
      'Returning HTTP 405 (Method Not Allowed)',
      req.method, req.path
    );

    // Let the client know which methods are allowed
    var allowedList = util.getAllowedMethods(req.swagger.path);
    res.set('Allow', allowedList);

    throw ono({status: 405}, '%s does not allow %s. \nAllowed methods: %s',
      req.path, req.method, allowedList);
  }

  next();
}

/**
 * If the Swagger API specifies the MIME types that this operation produces,
 * and the HTTP Accept header does not match any of those MIME types, then an HTTP 406 (Not Acceptable) is thrown.
 */
function http406(req, res, next) {
  if (util.isSwaggerRequest(req)) {
    // Get the MIME types that this operation produces
    var produces = req.swagger.operation.produces || req.swagger.api.produces || [];

    if (produces.length > 0) {
      util.debug('Validating Accept header (%s)', req.get('Accept'));

      if (!req.accepts(produces)) {
        var accepts = req.accepts();
        util.debug(
          'The %s operation on "%s" only produces %j content, but the client requested %j. ' +
          'Returning HTTP 406 (Not Acceptable)',
          req.method, req.path, produces, accepts
        );
        throw ono({status: 406}, '%s %s cannot produce any of the requested formats (%s). \nSupported formats: %s',
          req.method, req.path, accepts.join(', '), produces.join(', '));
      }
    }
  }

  next();
}

/**
 * Throws an HTTP 413 (Request Entity Too Large) if the HTTP request includes
 * body content that is not allowed by the Swagger API.
 */
function http413(req, res, next) {
  if (util.isSwaggerRequest(req)) {
    // Determine if the request allows body content
    var bodyAllowed = req.swagger.params.some(function(param) {
      return param.in === 'body' || param.in === 'formData';
    });

    if (!bodyAllowed) {
      // NOTE: We used to also check the Transfer-Encoding header, but that fails in Node 0.10.x
      // TODO: Once we drop support for Node 0.10.x, add a Transfer-Encoding check (via typeIs.hasBody())
      var length = req.get('Content-Length');
      util.debug('Validating Content-Length header (%d)', length);

      // NOTE: Even a zero-byte file attachment will have a Content-Length > 0
      if (length > 0) {
        util.debug(
          'The HTTP request contains body content, but the %s operation on "%s" does not allow a request body. ' +
          'Returning HTTP 413 (Request Entity Too Large)',
          req.method, req.path
        );
        throw ono({status: 413}, '%s %s does not allow body content', req.method, req.path);
      }
    }
  }

  next();
}

/**
 * Validates the HTTP Content-Type header against the Swagger API's "consumes" MIME types,
 * and throws an HTTP 415 (Unsupported Media Type) if there's a conflict.
 */
function http415(req, res, next) {
  if (util.isSwaggerRequest(req)) {
    // Only validate the Content-Type if there's body content
    if (!_.isEmpty(req.body)) {
      // Get the MIME types that this operation consumes
      var consumes = req.swagger.operation.consumes || req.swagger.api.consumes || [];

      if (consumes.length > 0) {
        util.debug('Validating Content-Type header (%s)', req.get('Content-Type'));

        if (!req.is(consumes)) {
          var contentType = req.header('Content-Type');
          util.debug(
            'Client attempted to send %s data to the %s operation on "%s", which is not allowed by the Swagger API. ' +
            'Returning HTTP 415 (Unsupported Media Type)',
            contentType, req.method, req.path
          );
          throw ono({status: 415}, '%s %s does not allow Content-Type "%s". \nAllowed Content-Types: %s',
            req.method, req.path, contentType, consumes.join(', '));
        }
      }
    }
  }

  next();
}

<|endoftext|>


<commit_before>'use strict';

var querystring = require('querystring');
var DataHolder = require('raptor-async/DataHolder');

// intercepter for res.end
function end() {
    // this function is always called in scope of response object
    /* jshint validthis:true */
    this._closed = true;
    this._intercepted_end.apply(this, arguments);
}

function Accept(accept) {
    var pos = accept.indexOf(';');
    if (pos !== -1) {
        this.params = accept.substring(pos+1);
        accept = accept.substring(0, pos);
    }

    pos = accept.indexOf('/');
    if (pos === -1) {
        this.type = accept;
    } else {
        this.type = accept.substring(0, pos);
        this.subtype = accept.substring(pos+1);
    }

    this.mimeType = (this.subtype ? this.type + '/' + this.subtype : this.type).toLowerCase();
}

Accept.prototype.getType = function() {
    return this.type;
};

Accept.prototype.getSubtype = function() {
    return this.subtype;
};

Accept.prototype.getMimeType = function() {
    return this.mimeType;
};

function REST(handler, domain) {
    this.handler = handler;
    this.upgrade = false;
    this.domain = domain;
}

function _jsonParser(str, callback) {
    str = str.trim();

    var body;
    if (str.length > 0) {
        try {
            body = JSON.parse(str);
        } catch(e) {
            return callback('Invalid JSON: ' + e);
        }
    } else {
        body = undefined;
    }

    callback(null, body);
}

var PARSERS = {
    'application/x-www-form-urlencoded': function(str, callback) {
        callback(null, querystring.parse(str));
    },

    'application/json': _jsonParser,

    'text/plain': _jsonParser
};

REST.prototype = {

    forwardTo: function(route) {
        this.handler.forwardTo(this, route);
    },

    handle: function() {

        var self = this;

        // inherit the before functions from the RESTHandler
        var allBefore = this.handler.getBefore();
        var routeBefore = this.route._before;

        var res = this.res;

        if (res && !res._intercepted_end) {
            res._intercepted_end = res.end;
            res.end = end;
        }

        var i = -1;

        // calculate the index where the general-purpose middleware ends
        var allBeforeEnd = allBefore.length;

        // calculate the index where the route-specific middleware ends
        var routeBeforeEnd = 0;
        if (routeBefore) {
            routeBeforeEnd = allBefore.length + routeBefore.length;
        }

        var routeInvoked = false;

        var next = function() {
            var beforeHandler;

            if (!self.isClosed()) {
                i++;
                if (i < allBeforeEnd) {
                    // We are in the range of overall middleware...

                    beforeHandler = allBefore[i];
                    // call the all middleware in the scope of the route
                    beforeHandler.call(self.route, self);
                } else if (routeBeforeEnd && (i < routeBeforeEnd)) {
                    // We are in the range of route-specific middleware...
                    beforeHandler = routeBefore[i - allBefore.length];
                    // call the route-specific middleware in the scope of the route
                    beforeHandler.call(self.route, self);
                } else if (routeInvoked === false) {
                    // set flag to indicate that we invoked the main route
                    routeInvoked = true;

                    // always delegate invocation to RESTHandler because this allows
                    // centralization of all route invocation
                    self.handler.invokeRoute(self);
                } else {
                    // we already invoked the route but response is still not closed
                    // so interpret this as not found
                    self.notFound();
                }
            }
        };

        this._next = (this.domain) ? this.domain.bind(next) : next;
        this._next();
    },

    isUpgrade: function() {
        return this.upgrade === true;
    },

    next: function() {
        if (this._next) {
            this._next();
        }
    },

    isClosed: function() {
        return (this.upgrade !== true) && this.res._closed;
    },

    send: function(statusCode, obj) {

        if (arguments.length === 1) {
            obj = statusCode;
        } else {
            this.res.statusCode = statusCode;
        }

        this.handler.send(this, obj);
    },

    error: function(statusCode, error) {

        if (arguments.length === 1) {
            error = statusCode;
        } else {
            this.res.statusCode = statusCode;
        }

        this.handler.error(this, error);
    },

    getCookies: function() {
        if (this._cookies === undefined) {
            var req = this.req;
            var cookies;
            this._cookies = cookies = {};
            if (req.headers.cookie) {
				req.headers.cookie.split(';').forEach(function(cookie) {
	                var parts = cookie.split('=');
	                cookies[parts[0].trim()] = decodeURIComponent((parts[1] || '').trim());
	            });
            }
        }

        return this._cookies;
    },

    getCookie: function(name) {
        return this.getCookies()[name];
    },

    getBasicAuth: function() {

        if (this._basicAuth === undefined) {
            var req = this.req;
            if (req.headers.authorization) {
                var basicAuthParser = require('basic-auth-parser');
                this._basicAuth = basicAuthParser(req.headers.authorization);
            } else {
                this._basicAuth = null;
            }
        }

        return this._basicAuth;
    },

    getBodyBuffer: function(callback, limit) {
        var self = this;

        if (!self._bodyBuffer) {
            self._bodyBuffer = new DataHolder({
                loader: function(callback) {
                    var len = 0;
                    var req = self.req;
                    var chunks = [];

                    req.on('data', function (data) {
                        var limit = self._bodyBuffer.limit;
                        len += data.length;
                        if (!limit || (len <= limit)) {
                            chunks.push(data);
                        } else {
                            callback(new Error('Limit exceeded. Reached ' + len + ' characters. Limit = ' + limit));
                        }
                    });

                    req.on('end', function () {
                        if (callback) {
                            if (chunks.length === 0) {
                                return callback();
                            }
                            callback(null, Buffer.concat(chunks, len));
                        }
                    });
                }
            });
        }

        self._bodyBuffer.done(callback);
    },

    getBody: function(callback, limit) {
        var self = this;

        if (!self._body) {
            self._body = new DataHolder({
                loader: function(callback) {
                    self.getBodyBuffer(function(err, buffer) {
                        if (err) {
                            return callback(err);
                        }

                        if (!buffer) {
                            return callback();
                        }

                        return callback(null, buffer.toString('utf8'));
                    });
                }
            });
        }

        self._body.done(callback);
    },

    getParsedBody: function(callback, limit) {
        var self = this;
        if (!self._parsedBody) {
            self._parsedBody = new DataHolder({
                loader: function(callback) {
                    self.getBody(function(err, body) {
                        if (err) {
                            return callback(err);
                        }

                        if (!body) {
                            return callback();
                        }

                        var contentType = self.req.headers['content-type'];
                        if (!contentType) {
                            contentType = 'application/json';
                        } else {
                            var pos = contentType.indexOf(';');
                            if (pos !== -1) {
                                contentType = contentType.substring(0, pos);
                            }
                        }

                        var parser = PARSERS[contentType];
                        if (!parser) {
                            return callback(new Error('Unrecognized content type for parsing: ' + contentType));
                        }

                        parser(body, callback);
                    });
                }
            });
        }

        self._parsedBody.done(callback);

        if (limit && self._body) {
            self._body.limit = Math.max(limit, self._body.limit);
        }
    },

    setResponseHeader: function() {
        this.res.setHeader.apply(this.res, arguments);
    },

    getResponseHeader: function() {
        this.res.getHeader.apply(this.res, arguments);
    },

    getRequestHeaders: function() {
        return this.req.headers;
    },

    getRequestHeader: function(name) {
        return this.req.headers[name.toLowerCase()];
    },

    getAccepts: function() {
        if (!this._accepts) {
            var accepts = this.getRequestHeader('accept').split(',');

            this._accepts = new Array(accepts.length);
            this._acceptsMap = {};

            for (var i = 0; i < accepts.length; i++) {
                var accept = new Accept(accepts[i]);
                this._accepts[i] = accept;
                this._acceptsMap[accept.getMimeType()] = accept;
            }
        }

        return this._accepts;
    },

    getPreferredContentType: function(candidates) {
        var accepts = this.getAccepts();
        if (accepts.length === 0) {
            return null;
        }

        if (candidates) {
            var supported = {};
            var i;

            if (Array.isArray(candidates)) {
                for (i = 0; i < candidates.length; i++) {
                    var candidate = candidates[i];
                    supported[candidate.toLowerCase()] = candidate;
                }
            } else {
                supported[candidates.toLowerCase()] = candidates;
            }

            for (i = 0; i < accepts.length; i++) {
                var accept = accepts[i];
                var match = supported[accept.getMimeType()];
                if (match) {
                    return match;
                }
            }

            return null;
        } else {
            return accepts[0];
        }
    },

    notFound: function(message) {
        if (arguments.length === 0) {
            this.handler.notFound(this.req, this.res);
        } else {
            this.handler.notFound(message, this.req, this.res);
        }
    }
};

module.exports = REST;
<commit_msg>Abort request if request body size limit reached<commit_after>'use strict';

var querystring = require('querystring');
var DataHolder = require('raptor-async/DataHolder');

// intercepter for res.end
function end() {
    // this function is always called in scope of response object
    /* jshint validthis:true */
    this._closed = true;
    this._intercepted_end.apply(this, arguments);
}

function Accept(accept) {
    var pos = accept.indexOf(';');
    if (pos !== -1) {
        this.params = accept.substring(pos+1);
        accept = accept.substring(0, pos);
    }

    pos = accept.indexOf('/');
    if (pos === -1) {
        this.type = accept;
    } else {
        this.type = accept.substring(0, pos);
        this.subtype = accept.substring(pos+1);
    }

    this.mimeType = (this.subtype ? this.type + '/' + this.subtype : this.type).toLowerCase();
}

Accept.prototype.getType = function() {
    return this.type;
};

Accept.prototype.getSubtype = function() {
    return this.subtype;
};

Accept.prototype.getMimeType = function() {
    return this.mimeType;
};

function REST(handler, domain) {
    this.handler = handler;
    this.upgrade = false;
    this.domain = domain;
}

function _jsonParser(str, callback) {
    str = str.trim();

    var body;
    if (str.length > 0) {
        try {
            body = JSON.parse(str);
        } catch(e) {
            return callback('Invalid JSON: ' + e);
        }
    } else {
        body = undefined;
    }

    callback(null, body);
}

var PARSERS = {
    'application/x-www-form-urlencoded': function(str, callback) {
        callback(null, querystring.parse(str));
    },

    'application/json': _jsonParser,

    'text/plain': _jsonParser
};

REST.prototype = {

    forwardTo: function(route) {
        this.handler.forwardTo(this, route);
    },

    handle: function() {

        var self = this;

        // inherit the before functions from the RESTHandler
        var allBefore = this.handler.getBefore();
        var routeBefore = this.route._before;

        var res = this.res;

        if (res && !res._intercepted_end) {
            res._intercepted_end = res.end;
            res.end = end;
        }

        var i = -1;

        // calculate the index where the general-purpose middleware ends
        var allBeforeEnd = allBefore.length;

        // calculate the index where the route-specific middleware ends
        var routeBeforeEnd = 0;
        if (routeBefore) {
            routeBeforeEnd = allBefore.length + routeBefore.length;
        }

        var routeInvoked = false;

        var next = function() {
            var beforeHandler;

            if (!self.isClosed()) {
                i++;
                if (i < allBeforeEnd) {
                    // We are in the range of overall middleware...

                    beforeHandler = allBefore[i];
                    // call the all middleware in the scope of the route
                    beforeHandler.call(self.route, self);
                } else if (routeBeforeEnd && (i < routeBeforeEnd)) {
                    // We are in the range of route-specific middleware...
                    beforeHandler = routeBefore[i - allBefore.length];
                    // call the route-specific middleware in the scope of the route
                    beforeHandler.call(self.route, self);
                } else if (routeInvoked === false) {
                    // set flag to indicate that we invoked the main route
                    routeInvoked = true;

                    // always delegate invocation to RESTHandler because this allows
                    // centralization of all route invocation
                    self.handler.invokeRoute(self);
                } else {
                    // we already invoked the route but response is still not closed
                    // so interpret this as not found
                    self.notFound();
                }
            }
        };

        this._next = (this.domain) ? this.domain.bind(next) : next;
        this._next();
    },

    isUpgrade: function() {
        return this.upgrade === true;
    },

    next: function() {
        if (this._next) {
            this._next();
        }
    },

    isClosed: function() {
        return (this.upgrade !== true) && this.res._closed;
    },

    send: function(statusCode, obj) {

        if (arguments.length === 1) {
            obj = statusCode;
        } else {
            this.res.statusCode = statusCode;
        }

        this.handler.send(this, obj);
    },

    error: function(statusCode, error) {

        if (arguments.length === 1) {
            error = statusCode;
        } else {
            this.res.statusCode = statusCode;
        }

        this.handler.error(this, error);
    },

    getCookies: function() {
        if (this._cookies === undefined) {
            var req = this.req;
            var cookies;
            this._cookies = cookies = {};
            if (req.headers.cookie) {
                req.headers.cookie.split(';').forEach(function(cookie) {
                    var parts = cookie.split('=');
                    cookies[parts[0].trim()] = decodeURIComponent((parts[1] || '').trim());
                });
            }
        }

        return this._cookies;
    },

    getCookie: function(name) {
        return this.getCookies()[name];
    },

    getBasicAuth: function() {

        if (this._basicAuth === undefined) {
            var req = this.req;
            if (req.headers.authorization) {
                var basicAuthParser = require('basic-auth-parser');
                this._basicAuth = basicAuthParser(req.headers.authorization);
            } else {
                this._basicAuth = null;
            }
        }

        return this._basicAuth;
    },

    getBodyBuffer: function(callback, limit) {
        var self = this;

        if (!self._bodyBuffer) {
            self._bodyBuffer = new DataHolder({
                loader: function(callback) {
                    var len = 0;
                    var req = self.req;
                    var chunks = [];
                    var err;
                    req.on('data', function (data) {
                        var limit = self._bodyBuffer.limit;
                        len += data.length;
                        if (!limit || (len <= limit)) {
                            chunks.push(data);
                        } else {
                            err = new Error('Limit exceeded. Reached ' + len + ' characters. Limit = ' + limit);

                            // end the request
                            var res = self.res;
                            res.writeHead(413, {'Content-Type': 'text/plain'});
                            res.end('Limit of ' + limit +' bytes exceeded');
                        }
                    });

                    req.on('end', function () {
                        if (callback) {
                            if (err || (chunks.length === 0)) {
                                return callback(err);
                            }
                            callback(null, Buffer.concat(chunks, len));
                        }
                    });
                }
            });
        }

        self._bodyBuffer.done(callback);
    },

    getBody: function(callback, limit) {
        var self = this;

        if (!self._body) {
            self._body = new DataHolder({
                loader: function(callback) {
                    self.getBodyBuffer(function(err, buffer) {
                        if (err) {
                            return callback(err);
                        }

                        if (!buffer) {
                            return callback();
                        }

                        return callback(null, buffer.toString('utf8'));
                    }, limit);
                }
            });
        }

        self._body.done(callback);
    },

    getParsedBody: function(callback, limit) {
        var self = this;
        if (!self._parsedBody) {
            self._parsedBody = new DataHolder({
                loader: function(callback) {
                    self.getBody(function(err, body) {
                        if (err) {
                            return callback(err);
                        }

                        if (!body) {
                            return callback();
                        }

                        var contentType = self.req.headers['content-type'];
                        if (!contentType) {
                            contentType = 'application/json';
                        } else {
                            var pos = contentType.indexOf(';');
                            if (pos !== -1) {
                                contentType = contentType.substring(0, pos);
                            }
                        }

                        var parser = PARSERS[contentType];
                        if (!parser) {
                            return callback(new Error('Unrecognized content type for parsing: ' + contentType));
                        }

                        parser(body, callback);
                    }, limit);
                }
            });
        }

        self._parsedBody.done(callback);

        if (limit && self._body) {
            self._body.limit = Math.max(limit, self._body.limit);
        }
    },

    setResponseHeader: function() {
        this.res.setHeader.apply(this.res, arguments);
    },

    getResponseHeader: function() {
        this.res.getHeader.apply(this.res, arguments);
    },

    getRequestHeaders: function() {
        return this.req.headers;
    },

    getRequestHeader: function(name) {
        return this.req.headers[name.toLowerCase()];
    },

    getAccepts: function() {
        if (!this._accepts) {
            var accepts = this.getRequestHeader('accept').split(',');

            this._accepts = new Array(accepts.length);
            this._acceptsMap = {};

            for (var i = 0; i < accepts.length; i++) {
                var accept = new Accept(accepts[i]);
                this._accepts[i] = accept;
                this._acceptsMap[accept.getMimeType()] = accept;
            }
        }

        return this._accepts;
    },

    getPreferredContentType: function(candidates) {
        var accepts = this.getAccepts();
        if (accepts.length === 0) {
            return null;
        }

        if (candidates) {
            var supported = {};
            var i;

            if (Array.isArray(candidates)) {
                for (i = 0; i < candidates.length; i++) {
                    var candidate = candidates[i];
                    supported[candidate.toLowerCase()] = candidate;
                }
            } else {
                supported[candidates.toLowerCase()] = candidates;
            }

            for (i = 0; i < accepts.length; i++) {
                var accept = accepts[i];
                var match = supported[accept.getMimeType()];
                if (match) {
                    return match;
                }
            }

            return null;
        } else {
            return accepts[0];
        }
    },

    notFound: function(message) {
        if (arguments.length === 0) {
            this.handler.notFound(this.req, this.res);
        } else {
            this.handler.notFound(message, this.req, this.res);
        }
    }
};

module.exports = REST;
<|endoftext|>


<commit_before>'use babel';
'use strict';

import {View} from 'atom-space-pen-views';
import cscompatability from './cscompatability';

module.exports = (function() {
  function SaveConfirmView() {
    View.apply(this, arguments);
  }

  cscompatability.extends(SaveConfirmView, View);

  SaveConfirmView.content = function() {
    SaveConfirmView.div({ class: 'build-confirm overlay from-top' }, function() {
      SaveConfirmView.h3('You have unsaved changes');
      SaveConfirmView.div({ class: 'btn-container pull-right' }, function() {
        SaveConfirmView.button({ class: 'btn btn-success', outlet: 'saveBuildButton', title: 'Save and Build', click: 'saveAndConfirm' }, 'Save and build');
        SaveConfirmView.button({ class: 'btn btn-info', title: 'Build Without Saving', click: 'confirmWithoutSave' }, 'Build Without Saving');
      });
      SaveConfirmView.div({ class: 'btn-container pull-left' }, function() {
        SaveConfirmView.button({ class: 'btn btn-info', title: 'Cancel', click: 'cancel' }, 'Cancel');
      });
    });
  };

  SaveConfirmView.prototype.destroy = function() {
    this.confirmcb = undefined;
    this.cancelcb = undefined;
    if (this.panel) {
      this.panel.destroy();
      this.panel = null;
    }
  };

  SaveConfirmView.prototype.show = function(confirmcb, cancelcb) {
    this.confirmcb = confirmcb;
    this.cancelcb = cancelcb;

    this.panel = atom.workspace.addTopPanel({
      item: this
    });
    this.saveBuildButton.focus();
  };

  SaveConfirmView.prototype.cancel = function() {
    this.destroy();
    if (this.cancelcb) {
      this.cancelcb();
    }
  };

  SaveConfirmView.prototype.saveAndConfirm = function() {
    if (this.confirmcb) {
      this.confirmcb(true);
    }
    this.destroy();
  };

  SaveConfirmView.prototype.confirmWithoutSave =  function() {
    if (this.confirmcb) {
      this.confirmcb(false);
    }
    this.destroy();
  };

  return SaveConfirmView;
})();
<commit_msg>Convert SaveConfirmView to ES6 Class.<commit_after>'use babel';
'use strict';

import {View} from 'atom-space-pen-views';

export default class SaveConfirmView extends View {
  static content() {
    this.div({ class: 'build-confirm overlay from-top' }, () => {
      this.h3('You have unsaved changes');
      this.div({ class: 'btn-container pull-right' }, () => {
        this.button({ class: 'btn btn-success', outlet: 'saveBuildButton', title: 'Save and Build', click: 'saveAndConfirm' }, 'Save and build');
        this.button({ class: 'btn btn-info', title: 'Build Without Saving', click: 'confirmWithoutSave' }, 'Build Without Saving');
      });
      this.div({ class: 'btn-container pull-left' }, () => {
        this.button({ class: 'btn btn-info', title: 'Cancel', click: 'cancel' }, 'Cancel');
      });
    });
  }

  destroy() {
    this.confirmcb = undefined;
    this.cancelcb = undefined;
    if (this.panel) {
      this.panel.destroy();
      this.panel = null;
    }
  }

  show(confirmcb, cancelcb) {
    this.confirmcb = confirmcb;
    this.cancelcb = cancelcb;

    this.panel = atom.workspace.addTopPanel({
      item: this
    });
    this.saveBuildButton.focus();
  }

  cancel() {
    this.destroy();
    if (this.cancelcb) {
      this.cancelcb();
    }
  }

  saveAndConfirm() {
    if (this.confirmcb) {
      this.confirmcb(true);
    }
    this.destroy();
  }

  confirmWithoutSave() {
    if (this.confirmcb) {
      this.confirmcb(false);
    }
    this.destroy();
  }
}
<|endoftext|>


<commit_before>const joi = require('joi')

module.exports = joi.object().keys({
  port: joi.number().integer().positive().required(),
  host: joi.string(),
  plugins: joi.array().optional(),
  views: joi.object().optional(),
  server: joi.string(),
  assets: joi.string().optional()
})

<commit_msg>added options validation<commit_after>const joi = require('joi')

module.exports = joi.object().keys({
  port: joi.number().integer().positive().required(),
  host: joi.string(),
  plugins: joi.array().optional(),
  views: joi.object().optional(),
  server: joi.string(),
  assets: joi.string().optional(),
  options: joi.object().optional()
})
<|endoftext|>


<commit_before>'use strict';

let
  http = require('http'),
  bytes = require('bytes'),
  uuid = require('uuid'),
  url = require('url'),
  ClientConnection = require('../core/clientConnection'),
  BadRequestError = require('kuzzle-common-objects').errors.BadRequestError,
  SizeLimitError = require('kuzzle-common-objects').errors.SizeLimitError,
  Writable = require('stream').Writable,
  HttpFormDataStream = require('./HttpFormDataStream'),
  KuzzleRoom = 'httpRequest';

let _proxy = {};

/**
 * @returns {HttpProxy}
 * @constructor
 */
function HttpProxy () {
  this.maxRequestSize = null;
  this.maxFileSize = null;
  this.server = null;
  return this;
}

/**
 * Initializes the HTTP server
 *
 * @param {KuzzleProxy} proxy
 */
HttpProxy.prototype.init = function (proxy) {
  this.maxRequestSize = bytes.parse(proxy.config.http.maxRequestSize);
  this.maxFileSize = bytes.parse(proxy.config.http.maxFileSize);

  _proxy = proxy;

  if (this.maxRequestSize === null || isNaN(this.maxRequestSize)) {
    throw new Error('Invalid HTTP "maxRequestSize" parameter');
  }

  if (this.maxFileSize === null || isNaN(this.maxFileSize)) {
    throw new Error('Invalid HTTP "maxFileSize" parameter');
  }

  if (!proxy.config.http.port) {
    throw new Error('No HTTP port configured.');
  }

  this.server = http.createServer((request, response) => {
    let
      stream,
      streamLength = 0,
      payload = {
        requestId: uuid.v4(),
        url: request.url,
        method: request.method,
        headers: request.headers,
        content: ''
      },
      ips = [request.socket.remoteAddress];

    if (request.headers['x-forwarded-for']) {
      ips = request.headers['x-forwarded-for']
        .split(',')
        .map(s => s.trim())
        .concat(ips);
    }

    const connection = new ClientConnection('HTTP/' + request.httpVersion, ips, request.headers);
    _proxy.clientConnectionStore.add(connection);

    if (request.headers['content-length'] > this.maxRequestSize) {
      request
        .removeAllListeners()
        .resume();
      return replyWithError(connection.id, payload, response, new SizeLimitError('Error: maximum HTTP request size exceeded'));
    }

    if (!request.headers['content-type'] || request.headers['content-type'].startsWith('application/json')) {
      stream = new Writable({
        write(chunk, encoding, callback) {
          payload.content += chunk.toString();
          callback();
        }
      });

      stream.on('error', err => {
        stream.end();
        request
          .removeAllListeners('data')
          .removeAllListeners('end')
          .resume();
        return replyWithError(connection.id, payload, response, err);
      });
    } else {
      try {
        stream = new HttpFormDataStream({headers: request.headers, limits: {fileSize: this.maxFileSize}}, payload);
      } catch (error) {
        request.resume();
        return replyWithError(connection.id, payload, response, new BadRequestError(error.message));
      }

      stream.on('error', err => {
        /*
        * Force Dicer parser to finish prematurely
        * without throwing an 'Unexpected end of multipart data' error :
        */
        stream._parser.parser._finished = true;
        stream.end();

        request
          .removeAllListeners('data')
          .removeAllListeners('end')
          .resume();
        return replyWithError(connection.id, payload, response, err);
      });
    }

    request.on('data', chunk => {
      streamLength += chunk.length;

      /*
       * The content-length header can be bypassed and
       * is not reliable enough. We have to enforce the HTTP
       * max size limit while reading the stream too
       */
      if (streamLength > this.maxRequestSize) {
        return stream.emit('error', new SizeLimitError('Error: maximum HTTP request size exceeded'));
      }
      stream.write(chunk);
    });

    request.on('end', () => {
      stream.end(() => {
        payload.headers['content-type'] = 'application/json';
        sendRequest(connection.id, response, payload);
      });
    });
  });

  this.server.listen(proxy.config.http.port, proxy.config.http.host);
};

/**
 * Sends a request to Kuzzle and forwards the response back to the client
 *
 * @param {connectionId} connectionId - connection Id
 * @param {ServerResponse} response
 * @param {Object} payload
 */
function sendRequest (connectionId, response, payload) {
  if (payload.json) {
    payload.content = JSON.stringify(payload.json);
    delete payload.json;
  }

  _proxy.broker.brokerCallback(KuzzleRoom, payload.requestId, connectionId, payload, (error, result) => {
    if (result) {
      _proxy.clientConnectionStore.remove(connectionId);

      if (result.headers) {
        Object.keys(result.headers)
          .forEach(header => {
            response.setHeader(header, result.headers[header]);
          });
      }

      response.writeHead(result.status);
      response.end(contentToPayload(result, payload.url));
    }
    else {
      replyWithError(connectionId, payload, response, error);
    }
  });
}

/**
 * Forward an error response to the client
 *
 * @param {string} connectionId
 * @param {Object} payload
 * @param {Object} response
 * @param {Object} error
 */
function replyWithError(connectionId, payload, response, error) {
  const result = {
    raw: true,
    content: JSON.stringify(error)
  };

  _proxy.logAccess(connectionId, payload, error, result);

  response.writeHead(error.status, {
    'Content-Type': 'application/json',
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods' : 'GET,POST,PUT,DELETE,OPTIONS',
    'Access-Control-Allow-Headers': 'Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With'
  });

  response.end(result.content);
}

/**
 * Converts a Kuzzle query result into an appropriate payload format
 * to send back to the client
 *
 * @param {Object} result
 * @param {String} invokedUrl - invoked URL. Used to check if the ?pretty
 *                              argument was passed by the client, changing
 *                              the payload format
 * @return {String|Buffer}
 */
function contentToPayload(result, invokedUrl) {
  let data;

  if (result.raw) {
    if (typeof result.content === 'object') {
      /*
       Either the content can be stored in a Buffer object, or
       the conversion throws and we stringify the content
       instead
       */
      if (result.content instanceof Buffer || (result.content.type === 'Buffer' && Array.isArray(result.content.data))) {
        data = Buffer.from(result.content);
      }
      else {
        data = JSON.stringify(result.content);
      }
    }
    else {
      // scalars are sent as-is
      data = result.content;
    }
  }
  else {
    let indent = 0;
    const parsedUrl = url.parse(invokedUrl, true);

    if (parsedUrl.query && parsedUrl.query.pretty !== undefined) {
      indent = 2;
    }

    data = JSON.stringify(result.content, undefined, indent);
  }

  return data
}

module.exports = HttpProxy;
<commit_msg>fix explanation comment<commit_after>'use strict';

let
  http = require('http'),
  bytes = require('bytes'),
  uuid = require('uuid'),
  url = require('url'),
  ClientConnection = require('../core/clientConnection'),
  BadRequestError = require('kuzzle-common-objects').errors.BadRequestError,
  SizeLimitError = require('kuzzle-common-objects').errors.SizeLimitError,
  Writable = require('stream').Writable,
  HttpFormDataStream = require('./HttpFormDataStream'),
  KuzzleRoom = 'httpRequest';

let _proxy = {};

/**
 * @returns {HttpProxy}
 * @constructor
 */
function HttpProxy () {
  this.maxRequestSize = null;
  this.maxFileSize = null;
  this.server = null;
  return this;
}

/**
 * Initializes the HTTP server
 *
 * @param {KuzzleProxy} proxy
 */
HttpProxy.prototype.init = function (proxy) {
  this.maxRequestSize = bytes.parse(proxy.config.http.maxRequestSize);
  this.maxFileSize = bytes.parse(proxy.config.http.maxFileSize);

  _proxy = proxy;

  if (this.maxRequestSize === null || isNaN(this.maxRequestSize)) {
    throw new Error('Invalid HTTP "maxRequestSize" parameter');
  }

  if (this.maxFileSize === null || isNaN(this.maxFileSize)) {
    throw new Error('Invalid HTTP "maxFileSize" parameter');
  }

  if (!proxy.config.http.port) {
    throw new Error('No HTTP port configured.');
  }

  this.server = http.createServer((request, response) => {
    let
      stream,
      streamLength = 0,
      payload = {
        requestId: uuid.v4(),
        url: request.url,
        method: request.method,
        headers: request.headers,
        content: ''
      },
      ips = [request.socket.remoteAddress];

    if (request.headers['x-forwarded-for']) {
      ips = request.headers['x-forwarded-for']
        .split(',')
        .map(s => s.trim())
        .concat(ips);
    }

    const connection = new ClientConnection('HTTP/' + request.httpVersion, ips, request.headers);
    _proxy.clientConnectionStore.add(connection);

    if (request.headers['content-length'] > this.maxRequestSize) {
      request
        .removeAllListeners()
        .resume();
      return replyWithError(connection.id, payload, response, new SizeLimitError('Error: maximum HTTP request size exceeded'));
    }

    if (!request.headers['content-type'] || request.headers['content-type'].startsWith('application/json')) {
      stream = new Writable({
        write(chunk, encoding, callback) {
          payload.content += chunk.toString();
          callback();
        }
      });

      stream.on('error', err => {
        stream.end();
        request
          .removeAllListeners('data')
          .removeAllListeners('end')
          .resume();
        return replyWithError(connection.id, payload, response, err);
      });
    } else {
      try {
        stream = new HttpFormDataStream({headers: request.headers, limits: {fileSize: this.maxFileSize}}, payload);
      } catch (error) {
        request.resume();
        return replyWithError(connection.id, payload, response, new BadRequestError(error.message));
      }

      stream.on('error', err => {
        /*
        * Force Dicer parser to finish prematurely
        * without throwing an 'Unexpected end of multipart data' error :
        */
        stream._parser.parser._finished = true;
        stream.end();

        request
          .removeAllListeners('data')
          .removeAllListeners('end')
          .resume();
        return replyWithError(connection.id, payload, response, err);
      });
    }

    request.on('data', chunk => {
      streamLength += chunk.length;

      /*
       * The content-length header can be bypassed and
       * is not reliable enough. We have to enforce the HTTP
       * max size limit while reading the stream too
       */
      if (streamLength > this.maxRequestSize) {
        return stream.emit('error', new SizeLimitError('Error: maximum HTTP request size exceeded'));
      }
      stream.write(chunk);
    });

    request.on('end', () => {
      stream.end(() => {
        payload.headers['content-type'] = 'application/json';
        sendRequest(connection.id, response, payload);
      });
    });
  });

  this.server.listen(proxy.config.http.port, proxy.config.http.host);
};

/**
 * Sends a request to Kuzzle and forwards the response back to the client
 *
 * @param {connectionId} connectionId - connection Id
 * @param {ServerResponse} response
 * @param {Object} payload
 */
function sendRequest (connectionId, response, payload) {
  if (payload.json) {
    payload.content = JSON.stringify(payload.json);
    delete payload.json;
  }

  _proxy.broker.brokerCallback(KuzzleRoom, payload.requestId, connectionId, payload, (error, result) => {
    if (result) {
      _proxy.clientConnectionStore.remove(connectionId);

      if (result.headers) {
        Object.keys(result.headers)
          .forEach(header => {
            response.setHeader(header, result.headers[header]);
          });
      }

      response.writeHead(result.status);
      response.end(contentToPayload(result, payload.url));
    }
    else {
      replyWithError(connectionId, payload, response, error);
    }
  });
}

/**
 * Forward an error response to the client
 *
 * @param {string} connectionId
 * @param {Object} payload
 * @param {Object} response
 * @param {Object} error
 */
function replyWithError(connectionId, payload, response, error) {
  const result = {
    raw: true,
    content: JSON.stringify(error)
  };

  _proxy.logAccess(connectionId, payload, error, result);

  response.writeHead(error.status, {
    'Content-Type': 'application/json',
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods' : 'GET,POST,PUT,DELETE,OPTIONS',
    'Access-Control-Allow-Headers': 'Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With'
  });

  response.end(result.content);
}

/**
 * Converts a Kuzzle query result into an appropriate payload format
 * to send back to the client
 *
 * @param {Object} result
 * @param {String} invokedUrl - invoked URL. Used to check if the ?pretty
 *                              argument was passed by the client, changing
 *                              the payload format
 * @return {String|Buffer}
 */
function contentToPayload(result, invokedUrl) {
  let data;

  if (result.raw) {
    if (typeof result.content === 'object') {
      /*
       This object can be either a Buffer object, a stringified Buffer object,
       or anything else.
       In the former two cases, we create a new Buffer object, and in the latter,
       we stringify the content.
       */
      if (result.content instanceof Buffer || (result.content.type === 'Buffer' && Array.isArray(result.content.data))) {
        data = Buffer.from(result.content);
      }
      else {
        data = JSON.stringify(result.content);
      }
    }
    else {
      // scalars are sent as-is
      data = result.content;
    }
  }
  else {
    let indent = 0;
    const parsedUrl = url.parse(invokedUrl, true);

    if (parsedUrl.query && parsedUrl.query.pretty !== undefined) {
      indent = 2;
    }

    data = JSON.stringify(result.content, undefined, indent);
  }

  return data
}

module.exports = HttpProxy;
<|endoftext|>


<commit_before>var _ = require('lodash'),
    util = require('util'),
    JSONStream = require('JSONStream'),
    EventEmitter = require('events').EventEmitter;

module.exports = SocketConnection;

/**
 * Creates a new SocketConnection using specified socket.
 * @param {object} socket   Socket handle.
 * @extends EventEmitter
 * @constructor
 */
function SocketConnection(socket) {
    var self = this;
    this._socket = socket;

    var jsonStream = this._socket.pipe(JSONStream.parse());
    jsonStream.on('data', function(data) {self._onData(data);});
    this._socket.on('close', function() {self._onClose();});
    this._socket.on('error', function(err) {self._onError(err);});

    this.remoteAddress = socket.remoteAddress;
    this.remotePort = socket.remotePort;

    EventEmitter.call(this);
}

util.inherits(SocketConnection, EventEmitter);

/**
 * Sends the specified message and data to the client.
 * @param {String} message      Message type. See `ServerMessages` of `SocketPlayer`.
 * @param {Object} data         Data to be sent to the client.
 */
SocketConnection.prototype.sendMessage = function(message, data) {
    this._send({msg: message, data: data});
};

/**
 * Sends the specified message to the client and destroys the socket.
 * @param {String} message      Message type. See `ServerMessages` of `SocketPlayer`.
 * @param {Object} data         Data to be sent to the client.
 */
SocketConnection.prototype.sendAndClose = function(message, data) {
    this.sendMessage(message, data);
    this._socket.destroy();
};

/**
 * Handles 'socket closed' event.
 * @private
 */
SocketConnection.prototype._onClose = function() {
    this._terminate();
};

/**
 * Handles 'error' event.
 * @private
 */
SocketConnection.prototype._onError = function(err) {
    this._log("ERROR: " + err);
    this._terminate();
};

/**
 * Handles raw data sent from the client
 * @param {Object} data     Object sent to the socket
 * @private
 */
SocketConnection.prototype._onData = function(data) {
    this._log("RECEIVED: " + JSON.stringify(data));

    if(_.isUndefined(data.msg)) {
        return this._syntaxError("msg is not defined");
    }
    if(_.isUndefined(data.data)) {
        return this._syntaxError("data is not defined");
    }

    this.emit("message", data.msg, data.data);
};

/**
 * Sends specified data to the client as JSON string.
 * @param {Object} data     Data to be sent
 * @private
 */
SocketConnection.prototype._send = function(data) {
    var jsonData = JSON.stringify(data);
    this._socket.write(jsonData);
};

/**
 * Ends current game. Call this on unrecoverable error situation (e.g. connection lost).
 * @private
 */
SocketConnection.prototype._terminate = function() {

    // TODO: Inject game object or send terminate event.
    // This does currently nothing.
    if(this._game) {
        this._game.end();
        delete this._games[this._game.id];
        this._game = null;
    }
};

/**
 * Sends specified error to the client with 'SYNTAX ERROR' and destroys the socket.
 * @param {String} err      Error text
 * @private
 */
SocketConnection.prototype._syntaxError = function(err) {
    this.sendAndClose("SYNTAX ERROR",  err);
};

/**
 * Logs text
 * @param {String} text     Text to be logged.
 * @private
 */
SocketConnection.prototype._log = function(text) {
    var msg = this.remoteAddress +':'+ this.remotePort;
    msg +=  " | " + text;
    console.log(msg);
};<commit_msg>Improve socket termination.<commit_after>var _ = require('lodash'),
    util = require('util'),
    JSONStream = require('JSONStream'),
    EventEmitter = require('events').EventEmitter;

module.exports = SocketConnection;

/**
 * Creates a new SocketConnection using specified socket.
 * @param {object} socket   Socket handle.
 * @extends EventEmitter
 * @constructor
 */
function SocketConnection(socket) {
    var self = this;
    this._socket = socket;

    var jsonStream = this._socket.pipe(JSONStream.parse());
    jsonStream.on('data', function(data) {self._onData(data);});
    this._socket.on('close', function() {self._onClose();});
    this._socket.on('error', function(err) {self._onError(err);});

    this.remoteAddress = socket.remoteAddress;
    this.remotePort = socket.remotePort;

    EventEmitter.call(this);
}

util.inherits(SocketConnection, EventEmitter);

/**
 * Sends the specified message and data to the client.
 * @param {String} message      Message type. See `ServerMessages` of `SocketPlayer`.
 * @param {Object} data         Data to be sent to the client.
 */
SocketConnection.prototype.sendMessage = function(message, data) {
    this._send({msg: message, data: data});
};

/**
 * Sends the specified message to the client and destroys the socket.
 * @param {String} message      Message type. See `ServerMessages` of `SocketPlayer`.
 * @param {Object} data         Data to be sent to the client.
 */
SocketConnection.prototype.sendAndClose = function(message, data) {
    this.sendMessage(message, data);
    var socket = this._socket;
    this._socket = null;
    socket.destroy();
};

/**
 * Handles 'socket closed' event.
 * @private
 */
SocketConnection.prototype._onClose = function() {
    this._terminate();
};

/**
 * Handles 'error' event.
 * @private
 */
SocketConnection.prototype._onError = function(err) {
    this._log("ERROR: " + err);
    this._terminate();
};

/**
 * Handles raw data sent from the client
 * @param {Object} data     Object sent to the socket
 * @private
 */
SocketConnection.prototype._onData = function(data) {
    this._log("RECEIVED: " + JSON.stringify(data));

    if(_.isUndefined(data.msg)) {
        return this._syntaxError("msg is not defined");
    }
    if(_.isUndefined(data.data)) {
        return this._syntaxError("data is not defined");
    }

    this.emit("message", data.msg, data.data);
};

/**
 * Sends specified data to the client as JSON string.
 * @param {Object} data     Data to be sent
 * @private
 */
SocketConnection.prototype._send = function(data) {
    var jsonData = JSON.stringify(data);
    this._socket.write(jsonData);
};

/**
 * Ends current game. Call this on unrecoverable error situation (e.g. connection lost).
 * @private
 */
SocketConnection.prototype._terminate = function() {
    if(this._socket !== null) {
        var socket = this._socket;
        this._socket = null;
        socket.destroy();
    }

    this.emit('close');
};

/**
 * Sends specified error to the client with 'SYNTAX ERROR' and destroys the socket.
 * @param {String} err      Error text
 * @private
 */
SocketConnection.prototype._syntaxError = function(err) {
    this.sendAndClose("SYNTAX ERROR",  err);
};

/**
 * Logs text
 * @param {String} text     Text to be logged.
 * @private
 */
SocketConnection.prototype._log = function(text) {
    var msg = this.remoteAddress +':'+ this.remotePort;
    msg +=  " | " + text;
    console.log(msg);
};<|endoftext|>