<commit_before>'use strict';

var P = require('bluebird');
var hrtime = require('../swissarmyknife/hrtime');

/**
 *
 * @param {module:primus/Spark} spark primus (client) or spark (server) object
 * @param {String} namespace
 * @param {int} [channels=10] Amount of multiplex channels, requires primus-multiplex.
 *         Use 0 to disable in which case primus-multiplex is not needed.
 *         If primus-multiplex is not available the default for this param is 0 instead of 10.
 *         Make sure both client and server pass exactly the same value for this parameter.
 * @constructor
 */
function Resource(spark, namespace, channels)
{
        this.primus = spark && (spark.primus || spark);
        this.spark = spark;
        this.namespace = namespace;
        this.channelSparks = [];
        this.listenNames = [];
        this._acquireQueue = []; // promises that want a channel. {promise: Promise, exclusive: boolean}
        /** While handling an rpc request, this parameter
         * contains channel data for the duration of the RPC method.
         * @type {Object} contains a key "channelSpark", other keys are for your own use
         */
        this.currentChannelData = null;

        if (spark)
        {
                this._setupMultiplexing(channels);
        }

        this._acquireDisposer = this._acquireDisposer.bind(this);
}

module.exports = Resource;

Resource.debugLogging = false;

Resource.prototype._setupMultiplexing = function(channels)
{
        var primus = this.primus;
        var spark = this.spark;
        var i;

        if (channels === undefined)
        {
                if  (!primus.channel)
                {
                        // primus-multiplex is not available and "channels" is not given
                        return;
                }

                channels = 2; // todo 10
        }

        if (channels &&
            !primus.channel)
        {
                throw Error('channels argument is set, however primus-multiplex is not plugged in');
        }

        if (!channels)
        {
                return;
        }

        this.channelSparks = new Array(channels);

        if (spark.primus) // server-side spark
        {
                // channel must be created on the primus, not on the spark
                if (!primus._ResourceChannels)
                {
                        primus._ResourceChannels = Object.create(null);
                }

                for (i = 0; i < this.channelSparks.length; ++i)
                {
                        var channelName = this.namespace + '$' + i;
                        var channel = primus._ResourceChannels[channelName];

                        if (!channel)
                        {
                                channel = primus.channel();
                                primus._ResourceChannels[channelName] = channel;
                        }
                }

                // ugh...
                spark.on('subscribe', function(channel, channelSpark)
                {
                        var nameSplit = channel.name.split('$');

                        if (nameSplit.length !== 2 ||
                            nameSplit[0] !== this.namespace)
                        {
                                return;
                        }

                        var index = nameSplit[1];

                        this.channelSparks[index] = channelSpark;
                        this.getChannelSparkData(channelSpark).index = index;

                        this.listenNames.forEach(function(name)
                        {
                                var fullname = this.namespace + '/' + name;
                                channelSpark.on(fullname, this._rpcHandler(channelSpark, name));
                        }, this);

                }.bind(this));

        }
        else // client side
        {
                for (i = 0; i < this.channelSparks.length; ++i)
                {
                        var channelSpark = primus.channel(this.namespace + '$' + i);
                        this.channelSparks[i] = channelSpark;
                        this.getChannelSparkData(channelSpark).index = i;
                }
        }
};

/**
 * Get the data for a channel spark (if multiplexing is enabled) or for a regular spark.
 * @param {module:primus/Spark|module:primus-multiplex/Spark} channelSpark
 * @returns {{index: int, acquired: int}}
 */
Resource.prototype.getChannelSparkData = function(channelSpark)
{
        if (!channelSpark._ResourceChannelData)
        {
                var data = {
                        // index is -1 if this is a regular spark instead of a channel spark
                        index: -1,
                        acquired: 0,
                        exclusive: false,
                        subClassData: Object.create(null) // passed as the last argument to rpc handlers
                };
                data.subClassData.channelSpark = channelSpark;

                channelSpark._ResourceChannelData = data;
                return data;
        }

        return channelSpark._ResourceChannelData;
};

Resource.prototype._acquireDisposer = function(channelSpark)
{
        var channelSparkData = this.getChannelSparkData(channelSpark);
        channelSparkData.acquired--;
        channelSparkData.exclusive = false;

        if (this._acquireQueue.length)
        {
                if (this._acquireQueue[0].exclusive &&
                    channelSparkData.acquired)
                {
                        // next in queue wants exclusive access, however this channel still has stuff pending
                        return;
                }

                var queueItem = this._acquireQueue.shift();
                channelSparkData.acquired++;
                channelSparkData.exclusive = queueItem.exclusive;
                queueItem.resolve(channelSpark);
        }
};

/**
 * Acquire a channel spark for sending data. If there are multiple channels, the least busy channel will be chosen.
 * @param {boolean} [exclusive=false] If true, acquire the channel spark in exclusive mode, no other channel sparks will be able to acquire it until you are done.
 * @returns {module:bluebird/Disposer<module:primus/Spark|module:primus-multiplex/Spark>} A disposer promise. see bluebird#using
 */
Resource.prototype.acquireChannelSparkForSending = function(exclusive)
{
        exclusive = !!exclusive;
        if (!this.spark)
        {
                return P.reject(Error('This resource has been instantiated without a primus spark'));
        }

        if (!this.channelSparks.length)
        {
                // not using primus-multiplex
                var data = this.getChannelSparkData(this.spark);

                if (data.exclusive ||  // exclusive channel that is busy
                    (exclusive && data.acquired)) // exclusive access is requested and this channel is already in use
                {
                        return new P(function(resolve, reject)
                        {
                                this._acquireQueue.push({
                                        resolve: resolve,
                                        reject: reject,
                                        exclusive: exclusive
                                });
                        }.bind(this)).disposer(this._acquireDisposer);
                }

                data.exclusive = exclusive;
                data.acquired++;
                return P.resolve(this.spark).disposer(this._acquireDisposer);
        }

        var bestSparkChannel = null;
        var lowestAcquired = 0;

        for (var i = 0; i < this.channelSparks.length; ++i)
        {
                var channel = this.channelSparks[i];
                if (!channel)
                {
                        // subscribe event has not fired yet
                        continue;
                }

                var channelSparkData = this.getChannelSparkData(channel);

                if (channelSparkData.exclusive)
                {
                        // exclusive channel that is busy
                        continue;
                }

                if (exclusive &&
                    channelSparkData.acquired)
                {
                        // exclusive access is requested and this channel is already in use
                        continue;
                }

                if (!bestSparkChannel ||
                    channelSparkData.acquired < lowestAcquired)
                {
                        lowestAcquired = channelSparkData.acquired;
                        bestSparkChannel = channel;
                }
        }

        if (bestSparkChannel)
        {
                var bestData = this.getChannelSparkData(bestSparkChannel);
                bestData.exclusive = exclusive;
                bestData.acquired++;
                return P.resolve(bestSparkChannel).disposer(this._acquireDisposer);
        }

        return new P(function(resolve, reject)
        {
                this._acquireQueue.push({
                        resolve: resolve,
                        reject: reject,
                        exclusive: exclusive
                });
        }.bind(this)).disposer(this._acquireDisposer);
};

/**
 * Call a method on the other end (client/server). The other end must explicitly listen
 * for this method call by calling .listen() in its constructor.
 * @param {String} name Name of the method to call on the other end.
 *        The other end must listen for this method name explicitly using .listen(name)
 * @param {...*} arg Any (e)json encodable value
 * @returns {Promise} Containing the rejected error or resolved return value
 */
Resource.prototype.rpc = function(name, arg)
{
        var args = new Array(arguments.length+1);
        for (var ai = 0; ai < arguments.length; ++ai) { args[ai+1] = arguments[ai]; }

        return P.using(this.acquireChannelSparkForSending(), function(channelSpark)
        {
                args[0] = channelSpark;
                return this.channelRpc.apply(this, args);

        }.bind(this)).bind(this); // (the last bind is for the handlers of the promise)
};

/**
 * Call a method on the other end (client/server). The other end must explicitly listen
 * for this method call by calling .listen() in its constructor.
 * @param {module:primus/Spark|module:primus-multiplex/Spark} channelSpark
 * @param {String} name Name of the method to call on the other end.
 *        The other end must listen for this method name explicitly using .listen(name)
 * @param {...*} arg Any (e)json encodable value
 * @returns {Promise} Containing the rejected error or resolved return value
 */
Resource.prototype.channelRpc = function(channelSpark, name, arg)
{
        if (!this.spark)
        {
                throw Error('This resource has been instantiated without a primus spark');
        }

        var start = Resource.debugLogging && hrtime();
        var fullname = this.namespace + '/' + name;

        var args = new Array(arguments.length-2);
        for (var ai = 2; ai < arguments.length; ++ai) { args[ai-2] = arguments[ai]; }

        var channelSparkData = this.getChannelSparkData(channelSpark);

        return new P(function(resolve, reject)
        {
                if (Resource.debugLogging)
                {
                        console.log('RPC >', channelSparkData.index, channelSparkData.acquired, fullname, args);
                }

                channelSpark.send(fullname, args, function (data)
                {
                        var end = Resource.debugLogging && hrtime();
                        if (Resource.debugLogging)
                        {
                                console.log('RPC <', fullname, ':', end - start, 'ms');
                        }

                        if (data[0])
                        {
                                reject(Error('Error from server: ' + data[0]));
                        }
                        else
                        {
                                resolve(data[1]);
                        }
                });

                // todo: reject after timeout?
        }).bind(this); // (this bind is for the handlers of the promise);
};

Resource.prototype._rpcHandler = function(channelSpark, name)
{
        var channelSparkData = this.getChannelSparkData(channelSpark);

        return function(args, result)
        {
                var resultPromise;

                if (Resource.debugLogging)
                {
                        console.log('< RPC', channelSparkData.index, this.namespace, name, args);
                }
                // use P.resolve to make sure this is a bluebird promise
                // and not one from a database or orm library.

                try
                {
                        this.currentChannelData = channelSparkData.subClassData;
                        resultPromise = P.resolve(this[name].apply(this, args));
                        this.currentChannelData = null;
                }
                catch(err)
                {
                        resultPromise = P.reject(err);
                }

                resultPromise
                .catch(function(err)
                {
                        console.error('Error during RPC', err, err.stack);

                        if (result)
                        {
                                result([err.toString(), null]);
                                result = null;
                        }
                })
                .then(function(ret)
                {
                        if (result)
                        {
                                result([null, ret]);
                                result = null;
                        }
                })
                .done();
        }.bind(this);
};

/**
 * Start listening for one or more rpc calls
 * @param names The name to listen for, the method with
 *        the same name on "this" is called whenever .rpc() is invoked on the other end.
 */
Resource.prototype.listen = function(names)
{
        var spark = this.spark;
        var ns = this.namespace;

        if (!Array.isArray(names))
        {
                names = [names];
        }

        names.forEach(function(name)
        {
                var fullname = ns + '/' + name;

                if (typeof this[name] !== 'function')
                {
                        throw Error(name + ' is not a Resource method');
                }

                if (!spark) // null spark = just check if the listeners are valid
                {
                        return;
                }

                this.listenNames.push(name);

                this.channelSparks.forEach(function(channel)
                {
                        channel.on(fullname, this._rpcHandler(channel, name));
                }, this);

        }, this);
};
<commit_msg>remove debug line<commit_after>'use strict';

var P = require('bluebird');
var hrtime = require('../swissarmyknife/hrtime');

/**
 *
 * @param {module:primus/Spark} spark primus (client) or spark (server) object
 * @param {String} namespace
 * @param {int} [channels=10] Amount of multiplex channels, requires primus-multiplex.
 *         Use 0 to disable in which case primus-multiplex is not needed.
 *         If primus-multiplex is not available the default for this param is 0 instead of 10.
 *         Make sure both client and server pass exactly the same value for this parameter.
 * @constructor
 */
function Resource(spark, namespace, channels)
{
        this.primus = spark && (spark.primus || spark);
        this.spark = spark;
        this.namespace = namespace;
        this.channelSparks = [];
        this.listenNames = [];
        this._acquireQueue = []; // promises that want a channel. {promise: Promise, exclusive: boolean}
        /** While handling an rpc request, this parameter
         * contains channel data for the duration of the RPC method.
         * @type {Object} contains a key "channelSpark", other keys are for your own use
         */
        this.currentChannelData = null;

        if (spark)
        {
                this._setupMultiplexing(channels);
        }

        this._acquireDisposer = this._acquireDisposer.bind(this);
}

module.exports = Resource;

Resource.debugLogging = false;

Resource.prototype._setupMultiplexing = function(channels)
{
        var primus = this.primus;
        var spark = this.spark;
        var i;

        if (channels === undefined)
        {
                if  (!primus.channel)
                {
                        // primus-multiplex is not available and "channels" is not given
                        return;
                }

                channels = 10;
        }

        if (channels &&
            !primus.channel)
        {
                throw Error('channels argument is set, however primus-multiplex is not plugged in');
        }

        if (!channels)
        {
                return;
        }

        this.channelSparks = new Array(channels);

        if (spark.primus) // server-side spark
        {
                // channel must be created on the primus, not on the spark
                if (!primus._ResourceChannels)
                {
                        primus._ResourceChannels = Object.create(null);
                }

                for (i = 0; i < this.channelSparks.length; ++i)
                {
                        var channelName = this.namespace + '$' + i;
                        var channel = primus._ResourceChannels[channelName];

                        if (!channel)
                        {
                                channel = primus.channel();
                                primus._ResourceChannels[channelName] = channel;
                        }
                }

                // ugh...
                spark.on('subscribe', function(channel, channelSpark)
                {
                        var nameSplit = channel.name.split('$');

                        if (nameSplit.length !== 2 ||
                            nameSplit[0] !== this.namespace)
                        {
                                return;
                        }

                        var index = nameSplit[1];

                        this.channelSparks[index] = channelSpark;
                        this.getChannelSparkData(channelSpark).index = index;

                        this.listenNames.forEach(function(name)
                        {
                                var fullname = this.namespace + '/' + name;
                                channelSpark.on(fullname, this._rpcHandler(channelSpark, name));
                        }, this);

                }.bind(this));

        }
        else // client side
        {
                for (i = 0; i < this.channelSparks.length; ++i)
                {
                        var channelSpark = primus.channel(this.namespace + '$' + i);
                        this.channelSparks[i] = channelSpark;
                        this.getChannelSparkData(channelSpark).index = i;
                }
        }
};

/**
 * Get the data for a channel spark (if multiplexing is enabled) or for a regular spark.
 * @param {module:primus/Spark|module:primus-multiplex/Spark} channelSpark
 * @returns {{index: int, acquired: int}}
 */
Resource.prototype.getChannelSparkData = function(channelSpark)
{
        if (!channelSpark._ResourceChannelData)
        {
                var data = {
                        // index is -1 if this is a regular spark instead of a channel spark
                        index: -1,
                        acquired: 0,
                        exclusive: false,
                        subClassData: Object.create(null) // passed as the last argument to rpc handlers
                };
                data.subClassData.channelSpark = channelSpark;

                channelSpark._ResourceChannelData = data;
                return data;
        }

        return channelSpark._ResourceChannelData;
};

Resource.prototype._acquireDisposer = function(channelSpark)
{
        var channelSparkData = this.getChannelSparkData(channelSpark);
        channelSparkData.acquired--;
        channelSparkData.exclusive = false;

        if (this._acquireQueue.length)
        {
                if (this._acquireQueue[0].exclusive &&
                    channelSparkData.acquired)
                {
                        // next in queue wants exclusive access, however this channel still has stuff pending
                        return;
                }

                var queueItem = this._acquireQueue.shift();
                channelSparkData.acquired++;
                channelSparkData.exclusive = queueItem.exclusive;
                queueItem.resolve(channelSpark);
        }
};

/**
 * Acquire a channel spark for sending data. If there are multiple channels, the least busy channel will be chosen.
 * @param {boolean} [exclusive=false] If true, acquire the channel spark in exclusive mode, no other channel sparks will be able to acquire it until you are done.
 * @returns {module:bluebird/Disposer<module:primus/Spark|module:primus-multiplex/Spark>} A disposer promise. see bluebird#using
 */
Resource.prototype.acquireChannelSparkForSending = function(exclusive)
{
        exclusive = !!exclusive;
        if (!this.spark)
        {
                return P.reject(Error('This resource has been instantiated without a primus spark'));
        }

        if (!this.channelSparks.length)
        {
                // not using primus-multiplex
                var data = this.getChannelSparkData(this.spark);

                if (data.exclusive ||  // exclusive channel that is busy
                    (exclusive && data.acquired)) // exclusive access is requested and this channel is already in use
                {
                        return new P(function(resolve, reject)
                        {
                                this._acquireQueue.push({
                                        resolve: resolve,
                                        reject: reject,
                                        exclusive: exclusive
                                });
                        }.bind(this)).disposer(this._acquireDisposer);
                }

                data.exclusive = exclusive;
                data.acquired++;
                return P.resolve(this.spark).disposer(this._acquireDisposer);
        }

        var bestSparkChannel = null;
        var lowestAcquired = 0;

        for (var i = 0; i < this.channelSparks.length; ++i)
        {
                var channel = this.channelSparks[i];
                if (!channel)
                {
                        // subscribe event has not fired yet
                        continue;
                }

                var channelSparkData = this.getChannelSparkData(channel);

                if (channelSparkData.exclusive)
                {
                        // exclusive channel that is busy
                        continue;
                }

                if (exclusive &&
                    channelSparkData.acquired)
                {
                        // exclusive access is requested and this channel is already in use
                        continue;
                }

                if (!bestSparkChannel ||
                    channelSparkData.acquired < lowestAcquired)
                {
                        lowestAcquired = channelSparkData.acquired;
                        bestSparkChannel = channel;
                }
        }

        if (bestSparkChannel)
        {
                var bestData = this.getChannelSparkData(bestSparkChannel);
                bestData.exclusive = exclusive;
                bestData.acquired++;
                return P.resolve(bestSparkChannel).disposer(this._acquireDisposer);
        }

        return new P(function(resolve, reject)
        {
                this._acquireQueue.push({
                        resolve: resolve,
                        reject: reject,
                        exclusive: exclusive
                });
        }.bind(this)).disposer(this._acquireDisposer);
};

/**
 * Call a method on the other end (client/server). The other end must explicitly listen
 * for this method call by calling .listen() in its constructor.
 * @param {String} name Name of the method to call on the other end.
 *        The other end must listen for this method name explicitly using .listen(name)
 * @param {...*} arg Any (e)json encodable value
 * @returns {Promise} Containing the rejected error or resolved return value
 */
Resource.prototype.rpc = function(name, arg)
{
        var args = new Array(arguments.length+1);
        for (var ai = 0; ai < arguments.length; ++ai) { args[ai+1] = arguments[ai]; }

        return P.using(this.acquireChannelSparkForSending(), function(channelSpark)
        {
                args[0] = channelSpark;
                return this.channelRpc.apply(this, args);

        }.bind(this)).bind(this); // (the last bind is for the handlers of the promise)
};

/**
 * Call a method on the other end (client/server). The other end must explicitly listen
 * for this method call by calling .listen() in its constructor.
 * @param {module:primus/Spark|module:primus-multiplex/Spark} channelSpark
 * @param {String} name Name of the method to call on the other end.
 *        The other end must listen for this method name explicitly using .listen(name)
 * @param {...*} arg Any (e)json encodable value
 * @returns {Promise} Containing the rejected error or resolved return value
 */
Resource.prototype.channelRpc = function(channelSpark, name, arg)
{
        if (!this.spark)
        {
                throw Error('This resource has been instantiated without a primus spark');
        }

        var start = Resource.debugLogging && hrtime();
        var fullname = this.namespace + '/' + name;

        var args = new Array(arguments.length-2);
        for (var ai = 2; ai < arguments.length; ++ai) { args[ai-2] = arguments[ai]; }

        var channelSparkData = this.getChannelSparkData(channelSpark);

        return new P(function(resolve, reject)
        {
                if (Resource.debugLogging)
                {
                        console.log('RPC >', channelSparkData.index, channelSparkData.acquired, fullname, args);
                }

                channelSpark.send(fullname, args, function (data)
                {
                        var end = Resource.debugLogging && hrtime();
                        if (Resource.debugLogging)
                        {
                                console.log('RPC <', fullname, ':', end - start, 'ms');
                        }

                        if (data[0])
                        {
                                reject(Error('Error from server: ' + data[0]));
                        }
                        else
                        {
                                resolve(data[1]);
                        }
                });

                // todo: reject after timeout?
        }).bind(this); // (this bind is for the handlers of the promise);
};

Resource.prototype._rpcHandler = function(channelSpark, name)
{
        var channelSparkData = this.getChannelSparkData(channelSpark);

        return function(args, result)
        {
                var resultPromise;

                if (Resource.debugLogging)
                {
                        console.log('< RPC', channelSparkData.index, this.namespace, name, args);
                }
                // use P.resolve to make sure this is a bluebird promise
                // and not one from a database or orm library.

                try
                {
                        this.currentChannelData = channelSparkData.subClassData;
                        resultPromise = P.resolve(this[name].apply(this, args));
                        this.currentChannelData = null;
                }
                catch(err)
                {
                        resultPromise = P.reject(err);
                }

                resultPromise
                .catch(function(err)
                {
                        console.error('Error during RPC', err, err.stack);

                        if (result)
                        {
                                result([err.toString(), null]);
                                result = null;
                        }
                })
                .then(function(ret)
                {
                        if (result)
                        {
                                result([null, ret]);
                                result = null;
                        }
                })
                .done();
        }.bind(this);
};

/**
 * Start listening for one or more rpc calls
 * @param names The name to listen for, the method with
 *        the same name on "this" is called whenever .rpc() is invoked on the other end.
 */
Resource.prototype.listen = function(names)
{
        var spark = this.spark;
        var ns = this.namespace;

        if (!Array.isArray(names))
        {
                names = [names];
        }

        names.forEach(function(name)
        {
                var fullname = ns + '/' + name;

                if (typeof this[name] !== 'function')
                {
                        throw Error(name + ' is not a Resource method');
                }

                if (!spark) // null spark = just check if the listeners are valid
                {
                        return;
                }

                this.listenNames.push(name);

                this.channelSparks.forEach(function(channel)
                {
                        channel.on(fullname, this._rpcHandler(channel, name));
                }, this);

        }, this);
};
<|endoftext|>


<commit_before>/*
 * Bundle: Resources / Buckets
 * Project: Crisp - Node API
 * Author: Mitchell R <https://github.com/mrinc>
 * Copyright: 2016, Crisp IM
 */

"use strict";

/**
 * Crisp Buckets Resource
 * @class
 * @classdesc This is the Crisp Buckets Resource
 */
function Buckets(crisp) {
  /**
   * Generates a bucket URL.
   * @memberof Buckets
   * @method post
   * @return Promise
   */
  this.generate = function(content) {
    return crisp.post(crisp._prepareRestUrl(["bucket", "url", "generate"]), {}, content);
  };
}

module.exports = Buckets;<commit_msg>Cleanup<commit_after>/*
 * Bundle: Resources / Buckets
 * Project: Crisp - Node API
 * Author: Mitchell R https://github.com/mrinc
 * Copyright: 2021, Crisp IM
 */

"use strict";

/**
 * Crisp Buckets Resource
 * @class
 * @classdesc This is the Crisp Buckets Resource
 */
function Buckets(crisp) {
  /**
   * Generates a bucket URL.
   * @memberof Buckets
   * @method post
   * @return Promise
   */
  this.generate = function(content) {
    return crisp.post(crisp._prepareRestUrl(["bucket", "url", "generate"]), {}, content);
  };
}

module.exports = Buckets;
<|endoftext|>


<commit_before>module.exports = function(crowi, app) {
  'use strict';

  var debug = require('debug')('crowi:routs:attachment')
    , Attachment = crowi.model('Attachment')
    , User = crowi.model('User')
    , Page = crowi.model('Page')
    , Promise = require('bluebird')
    , config = crowi.getConfig()
    , fs = require('fs')
    , fileUploader = require('../util/fileUploader')(crowi, app)
    , ApiResponse = require('../util/apiResponse')
    , actions = {}
    , api = {};

  actions.api = api;

  api.redirector = function(req, res){
    var id = req.params.id;

    Attachment.findById(id)
    .then(function(data) {

      // TODO: file delivery plugin for cdn
      var deliveryFile = Attachment.findDeliveryFile(data);
      return res.sendFile(deliveryFile.filename, deliveryFile.options);
    }).catch(function(err) {

      // not found
      return res.sendFile(crowi.publicDir + '/images/file-not-found.png');
    });
  };

  /**
   * @api {get} /attachments.list Get attachments of the page
   * @apiName ListAttachments
   * @apiGroup Attachment
   *
   * @apiParam {String} page_id
   */
  api.list = function(req, res){
    var id = req.query.page_id || null;
    if (!id) {
      return res.json(ApiResponse.error('Parameters page_id is required.'));
    }

    Attachment.getListByPageId(id)
    .then(function(attachments) {
      return res.json(ApiResponse.success({
        attachments: attachments
      }));
    });
  };

  /**
   * @api {post} /attachments.add Add attachment to the page
   * @apiName AddAttachments
   * @apiGroup Attachment
   *
   * @apiParam {String} page_id
   * @apiParam {File} file
   */
  api.add = function(req, res){
    var id = req.body.page_id || 0,
      path = decodeURIComponent(req.body.path) || null,
      pageCreated = false,
      page = {};

    debug('id and path are: ', id, path);

    var tmpFile = req.files.file || null;
    debug('Uploaded tmpFile: ', tmpFile);
    if (!tmpFile) {
      return res.json(ApiResponse.error('File error.'));
    }

    new Promise(function(resolve, reject) {
      if (id == 0) {
        if (path === null) {
          throw new Error('path required if page_id is not specified.');
        }
        debug('Create page before file upload');
        Page.create(path, '# '  + path, req.user, {grant: Page.GRANT_OWNER})
          .then(function(page) {
            pageCreated = true;
            resolve(page);
          })
          .catch(reject);
      } else {
        Page.findPageById(id).then(resolve).catch(reject);
      }
    }).then(function(pageData) {
      page = pageData;
      id = pageData._id;

      var tmpPath = tmpFile.path,
        originalName = tmpFile.originalname,
        fileName = tmpFile.name,
        fileType = tmpFile.mimetype,
        fileSize = tmpFile.size,
        filePath = Attachment.createAttachmentFilePath(id, fileName, fileType),
        tmpFileStream = fs.createReadStream(tmpPath, {flags: 'r', encoding: null, fd: null, mode: '0666', autoClose: true });

      return fileUploader.uploadFile(filePath, fileType, tmpFileStream, {})
        .then(function(data) {
          debug('Uploaded data is: ', data);

          // TODO size
          return Attachment.create(id, req.user, filePath, originalName, fileName, fileType, fileSize);
        }).then(function(data) {
          var imageUrl = fileUploader.generateUrl(data.filePath);
          var result = {
            page: page.toObject(),
            attachment: data.toObject(),
            filename: imageUrl,
            pageCreatet: pageCreated,
          };

          debug('atta creator', data.creator);
          debug('atta creator', result.attachment.creator);
          result.page.creator = User.filterToPublicFields(result.page.creator);
          result.attachment.creator = User.filterToPublicFields(result.attachment.creator);
          debug('atta creator', result.attachment.creator);
          return res.json(ApiResponse.success(result));
        }).catch(function (err) {
          debug('Error on saving attachment data', err);
          // @TODO
          // Remove from S3
          return res.json(ApiResponse.error('Error while uploading.'));
        }).finally(function() {
          fs.unlink(tmpPath, function (err) {
            if (err) {
              debug('Error while deleting tmp file.');
            }
          });
        })
      ;
    }).catch(function(err) {
      debug('Attachement upload error', err);
      return res.json(ApiResponse.error('Error.'));
    });
  };

  api.remove = function(req, res){
    var id = req.params.id;
  };

  return actions;
};
<commit_msg>Fixes<commit_after>module.exports = function(crowi, app) {
  'use strict';

  var debug = require('debug')('crowi:routs:attachment')
    , Attachment = crowi.model('Attachment')
    , User = crowi.model('User')
    , Page = crowi.model('Page')
    , Promise = require('bluebird')
    , config = crowi.getConfig()
    , fs = require('fs')
    , fileUploader = require('../util/fileUploader')(crowi, app)
    , ApiResponse = require('../util/apiResponse')
    , actions = {}
    , api = {};

  actions.api = api;

  api.redirector = function(req, res){
    var id = req.params.id;

    Attachment.findById(id)
    .then(function(data) {

      // TODO: file delivery plugin for cdn
      var deliveryFile = Attachment.findDeliveryFile(data);
      return res.sendFile(deliveryFile.filename, deliveryFile.options);
    }).catch(function(err) {

      // not found
      return res.sendFile(crowi.publicDir + '/images/file-not-found.png');
    });
  };

  /**
   * @api {get} /attachments.list Get attachments of the page
   * @apiName ListAttachments
   * @apiGroup Attachment
   *
   * @apiParam {String} page_id
   */
  api.list = function(req, res){
    var id = req.query.page_id || null;
    if (!id) {
      return res.json(ApiResponse.error('Parameters page_id is required.'));
    }

    Attachment.getListByPageId(id)
    .then(function(attachments) {
      return res.json(ApiResponse.success({
        attachments: attachments
      }));
    });
  };

  /**
   * @api {post} /attachments.add Add attachment to the page
   * @apiName AddAttachments
   * @apiGroup Attachment
   *
   * @apiParam {String} page_id
   * @apiParam {File} file
   */
  api.add = function(req, res){
    var id = req.body.page_id || 0,
      path = decodeURIComponent(req.body.path) || null,
      pageCreated = false,
      page = {};

    debug('id and path are: ', id, path);

    var tmpFile = req.files.file || null;
    debug('Uploaded tmpFile: ', tmpFile);
    if (!tmpFile) {
      return res.json(ApiResponse.error('File error.'));
    }

    new Promise(function(resolve, reject) {
      if (id == 0) {
        if (path === null) {
          throw new Error('path required if page_id is not specified.');
        }
        debug('Create page before file upload');
        Page.create(path, '# '  + path, req.user, {grant: Page.GRANT_OWNER})
          .then(function(page) {
            pageCreated = true;
            resolve(page);
          })
          .catch(reject);
      } else {
        Page.findPageById(id).then(resolve).catch(reject);
      }
    }).then(function(pageData) {
      page = pageData;
      id = pageData._id;

      var tmpPath = tmpFile.path,
        originalName = tmpFile.originalname,
        fileName = tmpFile.name,
        fileType = tmpFile.mimetype,
        fileSize = tmpFile.size,
        filePath = Attachment.createAttachmentFilePath(id, fileName, fileType),
        tmpFileStream = fs.createReadStream(tmpPath, {flags: 'r', encoding: null, fd: null, mode: '0666', autoClose: true });

      return fileUploader.uploadFile(filePath, fileType, tmpFileStream, {})
        .then(function(data) {
          debug('Uploaded data is: ', data);

          // TODO size
          return Attachment.create(id, req.user, filePath, originalName, fileName, fileType, fileSize);
        }).then(function(data) {
          var imageUrl = fileUploader.generateUrl(data.filePath);
          var result = {
            page: page.toObject(),
            attachment: data.toObject(),
            filename: imageUrl,
            pageCreated: pageCreated,
          };

          result.page.creator = User.filterToPublicFields(result.page.creator);
          result.attachment.creator = User.filterToPublicFields(result.attachment.creator);
          return res.json(ApiResponse.success(result));
        }).catch(function (err) {
          debug('Error on saving attachment data', err);
          // @TODO
          // Remove from S3
          return res.json(ApiResponse.error('Error while uploading.'));
        }).finally(function() {
          fs.unlink(tmpPath, function (err) {
            if (err) {
              debug('Error while deleting tmp file.');
            }
          });
        })
      ;
    }).catch(function(err) {
      debug('Attachement upload error', err);
      return res.json(ApiResponse.error('Error.'));
    });
  };

  api.remove = function(req, res){
    var id = req.params.id;
  };

  return actions;
};
<|endoftext|>


<commit_before>
module.exports = function(app){

    app.use(function(req, res, next){
        //Look for the cookie
        //console.log("ACCESS-TOKEN:" + req.cookies['access_token']);
        if(!req.cookies['access_token']){
            if(req.session.access_token){
                req.session.access_token = null;
            }
            return next();
        }
        //console.log("Cached User:", req.session.user);
        if(req.session.access_token == req.cookies['access_token']){
            //No change of user => No need to load... hopefully
            res.bootstrap('user', new app.sdk.Account(req.session.user));

            return next();
        }
        app.sdk.Account.findByAccessToken(
            req.cookies['access_token'],
            { client_id: app.njax.config.core_app, client_secret:null },
            function(err, account){
                if(err) return next(err);
                req.session.access_token = req.cookies['access_token'];

                req.session.user = account.toObject();
                res.bootstrap('user',  account);
                return next();
            }
        );

    });
    app.use(function(req, res, next){
        var api_url = req.api_url;
        var www_url = req.www_url;

        var allow_origin = null;
        var raw_origin = req.get('Origin');
        var origin = raw_origin && raw_origin.replace('http://', '').replace('https://', '');
        if(!origin){
            allow_origin = api_url;
        }else if(origin == api_url){
            allow_origin = api_url;
        }else if(origin == www_url){
            allow_origin = www_url;
        }/*else if{
         //TODO: Allow core app to get in this bad boy
         }*/
        if(!allow_origin){
            return res.send("Your Origin is NOT allowed by our 'Access-Control-Allow-Credentials'");
        }
        res.setHeader('Access-Control-Allow-Origin', req.protocol + '://' + allow_origin);

        res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
        res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type,access_token,client_id,client_secret');
        res.setHeader('Access-Control-Allow-Credentials', true);

        if(req.method == 'OPTIONS'){
            return res.send("Your Origin is allowed by our 'Access-Control-Allow-Credentials'");
        }
        return next();
    });
}<commit_msg>Code flying from the fingertips<commit_after>
module.exports = function(app){

    app.use(function(req, res, next){
        //Look for the cookie
        //console.log("ACCESS-TOKEN:" + req.cookies['access_token']);
        if(!req.cookies['access_token']){
            if(req.session.access_token){
                req.session.access_token = null;
            }
            return next();
        }
        //console.log("Cached User:", req.session.user);
        if(req.session.access_token == req.cookies['access_token']){
            //No change of user => No need to load... hopefully
            res.bootstrap('user', new app.sdk.Account(req.session.user));

            return next();
        }
        app.sdk.Account.findByAccessToken(
            req.cookies['access_token'],
            { client_id: app.njax.config.core_app, client_secret:null },
            function(err, account){
                if(err) return next(err);
                req.session.access_token = req.cookies['access_token'];

                req.session.user = account.toObject();
                res.bootstrap('user',  account);
                return next();
            }
        );

    });
    app.use(function(req, res, next){
        var api_url = req.api_url;
        var www_url = req.www_url;

        var allow_origin = null;
        var raw_origin = req.get('Origin');
        var origin = raw_origin && raw_origin.replace('http://', '').replace('https://', '');
        if(!origin){
            allow_origin = api_url;
        }else if(origin == api_url){
            allow_origin = api_url;
        }else if(origin == www_url){
            allow_origin = www_url;
        }else if(origin == app.njax.config.core_api.host){
            allow_origin = app.njax.config.core_api.host;
        }else if (origin == app.njax.config.core_api.host.substr('api.'.length)){
            //TODO: Allow core app to get in this bad boy
            allow_origin =  app.njax.config.core_api.host.substr('api.'.length);
        }

        if(!allow_origin){
            return res.send("Your Origin is NOT allowed by our 'Access-Control-Allow-Credentials'");
        }
        res.setHeader('Access-Control-Allow-Origin', req.protocol + '://' + allow_origin);

        res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
        res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type,access_token,client_id,client_secret');
        res.setHeader('Access-Control-Allow-Credentials', true);
        res.bootstrap('allow_origin', allow_origin);

        if(req.method == 'OPTIONS'){
            return res.send("Your Origin is allowed by our 'Access-Control-Allow-Credentials'");
        }
        return next();
    });
}<|endoftext|>


<commit_before>$('form').submit(function () {

  var origin = decodeURIComponent(location.search.slice(1)),
      $submitIcon = $('.canvas-submit-icon'), // submit button in Promotions form
      checkSubmitIcon = function () {
        if ($submitIcon.css('display') === 'block') { // if submit loader is showing
          clearInterval(interval); // stop checking
          window.parent.postMessage('submitted', origin); // send success message to parent page
          console.log('message sent');
          console.log(origin);
        }
      },
      interval = setInterval(checkSubmitIcon, 100); // check every 1/10th second

  checkSubmitIcon(); // check initially
  setTimeout(function () { // check for up to 2 seconds
    clearInterval(interval);
  }, 2000);

});
<commit_msg>add debugging<commit_after>$('form').submit(function () {

  var origin = decodeURIComponent(location.search.slice(1)),
      $submitIcon = $('.canvas-submit-icon'), // submit button in Promotions form
      checkSubmitIcon = function () {
        if ($submitIcon.css('display') === 'block') { // if submit loader is showing
          clearInterval(interval); // stop checking
          window.parent.postMessage('submitted', origin); // send success message to parent page
          console.log('message sent');
          console.log(origin);
        }
      },
      interval = setInterval(checkSubmitIcon, 100); // check every 1/10th second

  console.log(1);
  console.log(location);

  checkSubmitIcon(); // check initially
  setTimeout(function () { // check for up to 2 seconds
    clearInterval(interval);
  }, 2000);

});
<|endoftext|>


<commit_before>describe('suite1', function(){
  beforeAll(function (){
    // Let's do 'cleanup' beforeEach too, in case another suite didn't clean up properly
    spies.restoreAll();
    stubs.restoreAll();
    console.log("I'm beforeAll");
  });
  beforeEach(function (){
    console.log("I'm beforeEach");
    spies.create('log', console, 'log');
  });
  afterEach(function (){
    spies.restoreAll();
    console.log("I'm afterEach");
  });
  afterAll(function (){
    console.log("I'm afterAll");
    spies.restoreAll();
    stubs.restoreAll();
  });
  it('test1', function(){
    console.log('Hello world');
    expect(spies.log).to.have.been.calledWith('Hello world');
  })
});

describe('suite2', function(){
  it('async test', function(done){
    var onTimeout = function () {
      done();
    };
    Meteor.setTimeout(onTimeout, 50);
  });
});

describe.skip('skipped suite', function(){
  it('work in progress', function(){
    expect(false).to.be.true;
  });
});

describe('suite with skipped test', function(){
  it.skip('skipped test', function(){
    expect(false).to.be.true;
  });
});

describe('client only and server only tests', function(){
  it.client('runs only in client', function(){
    expect(Meteor.isClient).to.be.true;
  });
  it.server('runs only in server', function(){
    expect(Meteor.isServer).to.be.true;
  });
});

describe.client('client only test suite', function(){
  it('runs only in client', function(){
    expect(Meteor.isClient).to.be.true;
  });
  it.server('overrides describe.client and runs only in server', function(){
    expect(Meteor.isServer).to.be.true;
  });
});

describe.server('server only test suite', function(){
  it('runs only in server', function(){
    expect(Meteor.isServer).to.be.true;
  });
  it.client('overrides describe.server and runs only in client', function(){
    expect(Meteor.isClient).to.be.true;
  });
});
<commit_msg>Add nested describe test in js<commit_after>describe('suite1', function(){
  beforeAll(function (){
    // Let's do 'cleanup' beforeEach too, in case another suite didn't clean up properly
    spies.restoreAll();
    stubs.restoreAll();
    console.log("I'm beforeAll");
  });
  beforeEach(function (){
    console.log("I'm beforeEach");
    spies.create('log', console, 'log');
  });
  afterEach(function (){
    spies.restoreAll();
    console.log("I'm afterEach");
  });
  afterAll(function (){
    console.log("I'm afterAll");
    spies.restoreAll();
    stubs.restoreAll();
  });
  it('test1', function(){
    console.log('Hello world');
    expect(spies.log).to.have.been.calledWith('Hello world');
  })
});

describe('suite2', function(){
  it('async test', function(waitFor){
    var onTimeout = function () {
      expect(true).to.be.true
    };
    Meteor.setTimeout(waitFor(onTimeout), 50);
  });
});

describe.skip('skipped suite', function(){
  it('work in progress', function(){
    expect(false).to.be.true;
  });
});

describe('suite with skipped test', function(){
  it.skip('skipped test', function(){
    expect(false).to.be.true;
  });
});

describe('client only and server only tests', function(){
  it.client('runs only in client', function(){
    expect(Meteor.isClient).to.be.true;
  });
  it.server('runs only in server', function(){
    expect(Meteor.isServer).to.be.true;
  });
});

describe.client('client only test suite', function(){
  it('runs only in client', function(){
    expect(Meteor.isClient).to.be.true;
  });
  it.server('overrides describe.client and runs only in server', function(){
    expect(Meteor.isServer).to.be.true;
  });
});

describe.server('server only test suite', function(){
  it('runs only in server', function(){
    expect(Meteor.isServer).to.be.true;
  });
  it.client('overrides describe.server and runs only in client', function(){
    expect(Meteor.isClient).to.be.true;
  });
});

describe('top-level describe', function(){
  describe('nested describe', function() {
    describe('deep nested describe', function() {
      it('a test', function () {
        expect(true).to.be.true;
      })
    })
  })
});
<|endoftext|>


<commit_before>var Actions = {};
Actions.element = function(using, value, callback) {
  var check = /class name|css selector|id|name|link text|partial link text|tag name|xpath/gi;
  if (!check.test(using)) {
    throw new Error("Please provide any of the following using strings as the first parameter: " +
        "class name, css selector, id, name, link text, partial link text, tag name or xpath");
  }

  return postRequest.call(this, "/element", {
    "using": using,
    "value": value
  }, callback);
};

Actions.elementActive = function(callback) {
  return postRequest.call(this, "/element/active", {}, callback);
};

Actions.elementIdAttribute = function(id, attributeName, callback) {
  return getRequest.call(this,
    "/element/" + id + "/attribute/" + attributeName, callback);
};

Actions.elementIdClick = function(id, callback) {
  return postRequest.call(this, "/element/" + id + "/click", '', callback);
};

/**
 * http://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/element/:id/css/:propertyName
 *
 * Query the value of an element's computed CSS property. The CSS property to query should be specified using
 * the CSS property name, not the JavaScript property name (e.g. background-color instead of backgroundColor).
 *
 * @param {String} id - ID of the element to route the command to (ID is a WebElement JSON object)
 * @param {String} cssProperyName
 * @param {Function} callback
 */
Actions.elementIdCssProperty = function(id, cssProperyName, callback) {
  return getRequest.call(this,
    "/element/" + id + "/css/" + cssProperyName,
    callback);
};

/**
 * @param {String} id - ID of the element to route the command to (ID is a WebElement JSON object)
 * @param {Function} callback
 */
Actions.elementIdDisplayed = function(id, callback) {
  return getRequest.call(this,
    "/element/" + id + "/displayed",
    callback);
};

/**
 * @param {String} id - ID of the element to route the command to (ID is a WebElement JSON object)
 * @param {Function} callback
 */
Actions.elementIdLocationInView = function(id, callback) {
  return getRequest.call(this,
    "/element/" + id + "/location_in_view",
    callback);
};

/**
 * Determine an element's location on the page. The point (0, 0) refers to the upper-left corner of the page.
 * The element's coordinates are returned as a JSON object with x and y properties.
 *
 * Returns {x:number, y:number} The X and Y coordinates for the element on the page.
 *
 * @param {String} id - ID of the element to route the command to (ID is a WebElement JSON object)
 * @param {Function} callback
 */
Actions.elementIdLocation = function(id, callback) {
  return getRequest.call(this, "/element/" + id + "/location", callback);
};

/**
 * @param {String} id - ID of the element to route the command to (ID is a WebElement JSON object)
 * @param {Function} callback
 */
Actions.elementIdName = function(id, callback) {
  return getRequest.call(this,
    "/element/" + id + "/name", callback);
};

/**
 * @param {String} id - ID of the element to route the command to (ID is a WebElement JSON object)
 * @param {Function} callback
 */
Actions.elementIdClear = function(id, callback) {
  return postRequest.call(this, "/element/" + id + "/clear", callback);
};

Actions.elementIdSelected = function(id, callback) {
  return getRequest.call(this, "/element/" + id + "/selected", callback);
};

Actions.elementIdEnabled = function(id, callback) {
  return getRequest.call(this, "/element/" + id + "/enabled", callback);
};

Actions.elementIdEquals = function(id1, id2, callback) {
  return getRequest.call(this, "/element/" + id1 + "/equals/" + id2, callback);
};

Actions.elementIdSize = function(id, callback) {
  return getRequest.call(this,
    "/element/" + id + "/size",
    callback);
};

Actions.elementIdText = function(id, callback) {
  return getRequest.call(this,
    "/element/" + id + "/text",
    callback);
};

Actions.elementIdValue = function(id, value, callback) {
  if (arguments.length === 2) {
    callback = value;
    return getRequest.call(this,
      "/element/" + id + "/value",
      callback);
  }

  value = String(value);
  return postRequest.call(this, "/element/" + id + "/value", {
    "value": value.split("")
  }, callback);
};

/**
 * Search for an element on the page, starting from the document root. The located element will be
 * returned as a WebElement JSON object.
 * The table below lists the locator strategies that each server should support. Each locator must
 * return the first matching element located in the DOM.
 *
 * class name         Returns an element whose class name contains the search value; compound class names are not permitted.
 * css selector       Returns an element matching a CSS selector.
 * id                 Returns an element whose ID attribute matches the search value.
 * name               Returns an element whose NAME attribute matches the search value.
 * link text          Returns an anchor element whose visible text matches the search value.
 * partial link text  Returns an anchor element whose visible text partially matches the search value.
 * tag name           Returns an element whose tag name matches the search value.
 * xpath              Returns an element matching an XPath expression.
 *
 * @param {Object} using
 * @param {Object} value
 * @param {Object} callback
 */
Actions.elements = function(using, value, callback) {
  var check = /class name|css selector|id|name|link text|partial link text|tag name|xpath/gi;
  if (!check.test(using)) {
    throw new Error("Please provide any of the following using strings as the first parameter: " +
        "class name, css selector, id, name, link text, partial link text, tag name or xpath");
  }

  return postRequest.call(this, "/elements", {
    "using": using,
    "value": value
  }, callback);
};

/**
 * Inject a snippet of JavaScript into the page for execution in the context of the currently selected
 * frame. The executed script is assumed to be synchronous and the result of evaluating the script is
 * returned to the client.
 *
 * The script argument defines the script to execute in the form of a function body. The value
 * returned by that function will be returned to the client. The function will be invoked with the
 * provided args array and the values may be accessed via the arguments object in the order specified.
 *
 * Arguments may be any JSON-primitive, array, or JSON object. JSON objects that define a WebElement
 * reference will be converted to the corresponding DOM element. Likewise, any WebElements in the
 * script result will be returned to the client as WebElement JSON objects.
 *
 * Potential Errors:
 *  - NoSuchWindow - If the currently selected window has been closed.
 *  - StaleElementReference - If one of the script arguments is a WebElement that is not attached to the page's DOM.
 *  - JavaScriptError - If the script throws an Error.
 *
 * @param {String} string
 * @param {Array} args
 * @param {Function} callback
 */
Actions.execute = function(script, args, callback) {
  if (typeof script == 'function') {
    var fn = 'var passedArgs = Array.prototype.slice.call(arguments,0); return ' + script.toString() + '.apply(window, passedArgs);';
  } else {
    fn = script;
  }

  if (arguments.length == 2 && typeof arguments[1] == 'function') {
    callback = arguments[1];
    args = [];
  }

  return postRequest.call(this, "/execute", {
    "script": fn,
    "args": args
  }, callback);
}

/**
 * Change focus to another frame on the page. If the frame id is null, the server
 * should switch to the page's default content.
 *
 * @param {String} frameId
 * @param {Function} callback
 */
Actions.frame = function(frameId, callback) {
  if (arguments.length == 1 && typeof frameId == "function") {
    callback = frameId;
    return postRequest.call(this, "/frame", callback);
  }

  return postRequest.call(this, "/frame", {
    "frameId": frameId
  }, callback);
};

/**
 * Move the mouse by an offset of the specificed element. If no element is specified, the move is relative
 * to the current mouse cursor. If an element is provided but no offset, the mouse will be moved to the
 * center of the element. If the element is not visible, it will be scrolled into view.
 *
 * @param {String} element - Opaque ID assigned to the element to move to. If not specified or is null,
 *  the offset is relative to current position of the mouse.
 * @param {Number} xoffset - X offset to move to, relative to the top-left corner of the element.
 *  If not specified, the mouse will move to the middle of the element.
 * @param {String} yoffset - Y offset to move to, relative to the top-left corner of the element.
 * If not specified, the mouse will move to the middle of the element.
 * @param {Function} callback
 */
Actions.moveTo = function(element, xoffset, yoffset, callback) {
  var data = {}
  if (typeof element == 'string') {
    data.element = element;
  }
  if (typeof xoffset == 'number') {
    data.xoffset = xoffset;
  }
  if (typeof yoffset == 'number') {
    data.yoffset = yoffset;
  }
  return postRequest.call(this,
      "/moveto", data, callback);
};

Actions.submit = function(id, callback) {
  return postRequest.call(this,
      "/element/" + id + "/submit", "", callback);
};

Actions.screenshot = function(callback) {
  return getRequest.call(this, "/screenshot", callback);
};

/**
 * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol#/status
 */
Actions.status = function(callback) {
  return getRequest.call(this, "/status", callback);
};

Actions.title = function(callback) {
  return getRequest.call(this, "/title", callback);
};

Actions.window_handle = function(callback) {
  return getRequest.call(this, "/window_handle", callback);
};

Actions.window_handles = function(callback) {
  return getRequest.call(this, "/window_handles", callback);
};

/**
 * GET /session/:sessionId/url - Retrieve the URL of the current page.
 * POST /session/:sessionId/url - Navigate to a new URL.
 *
 * Potential Errors:
 *  NoSuchWindow - If the currently selected window has been closed.
 *
 * @param {String} url
 * @param {Function} callback
 */
Actions.url = function(url, callback) {
  if (typeof url == "string") {
    return postRequest.call(this, "/url", {
      url : url
    }, callback);
  }

  if (typeof url == "function") {
    callback = url;
  }

  return getRequest.call(this, "/url", callback);
};

Actions.session  = function(action, callback) {
  var options = {
    path : "/session"
  };

  if (typeof action == "function") {
    callback = action;
    action = "get";
  }

  switch (action) {
    case "delete":
      options.path += '/' + this.sessionId;
      options.method = "DELETE";
      break;
    default:
      options.method = "GET";
  }

  return this.runProtocolCommand(options, callback).send();
};

function getRequest(path, callback) {
  var options = {
    path : "/session/" + this.sessionId + path,
    method : "GET"
  }
  callback = callback || function() {};

  return this.runProtocolCommand(options, callback).send();
}

function postRequest(path, data, callback) {
  if (arguments.length == 2 && typeof data == "function") {
    callback = data;
    data = "";
  }

  var options = {
    path : "/session/" + this.sessionId + path,
    method : "POST",
    data : data || ""
  }
  callback = callback || function() {};
  return this.runProtocolCommand(options, callback).send();
}

module.exports.actions = Actions;
<commit_msg>Hacking in the buttonDown/buttonUp protocols, thus enabling Dragand-Drop.<commit_after>var Actions = {};
Actions.element = function(using, value, callback) {
  var check = /class name|css selector|id|name|link text|partial link text|tag name|xpath/gi;
  if (!check.test(using)) {
    throw new Error("Please provide any of the following using strings as the first parameter: " +
        "class name, css selector, id, name, link text, partial link text, tag name or xpath");
  }

  return postRequest.call(this, "/element", {
    "using": using,
    "value": value
  }, callback);
};

Actions.elementActive = function(callback) {
  return postRequest.call(this, "/element/active", {}, callback);
};

Actions.elementIdAttribute = function(id, attributeName, callback) {
  return getRequest.call(this,
    "/element/" + id + "/attribute/" + attributeName, callback);
};

Actions.elementIdClick = function(id, callback) {
  return postRequest.call(this, "/element/" + id + "/click", '', callback);
};

function buttonHandler(that, direction, id, button, callback) {
    var bi;
    if (typeof button == 'function') {
        callback = button;
        button = 0;
    }
    if (typeof button == 'string') {
        bi = ['left', 'middle', 'right'].indexOf(button.toLowerCase());
        if (bi !== -1) {
            button = bi;
        }
    }
    return postRequest.call(that, '/button' + direction, {button: button}, callback);
};

Actions.buttonDown = function(id, button, callback) {
    return buttonHandler(this, 'down', id, button, callback);
};

Actions.buttonUp = function(id, button, callback) {
    return buttonHandler(this, 'up', id, button, callback);
};

/**
 * http://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/element/:id/css/:propertyName
 *
 * Query the value of an element's computed CSS property. The CSS property to query should be specified using
 * the CSS property name, not the JavaScript property name (e.g. background-color instead of backgroundColor).
 *
 * @param {String} id - ID of the element to route the command to (ID is a WebElement JSON object)
 * @param {String} cssProperyName
 * @param {Function} callback
 */
Actions.elementIdCssProperty = function(id, cssProperyName, callback) {
  return getRequest.call(this,
    "/element/" + id + "/css/" + cssProperyName,
    callback);
};

/**
 * @param {String} id - ID of the element to route the command to (ID is a WebElement JSON object)
 * @param {Function} callback
 */
Actions.elementIdDisplayed = function(id, callback) {
  return getRequest.call(this,
    "/element/" + id + "/displayed",
    callback);
};

/**
 * @param {String} id - ID of the element to route the command to (ID is a WebElement JSON object)
 * @param {Function} callback
 */
Actions.elementIdLocationInView = function(id, callback) {
  return getRequest.call(this,
    "/element/" + id + "/location_in_view",
    callback);
};

/**
 * Determine an element's location on the page. The point (0, 0) refers to the upper-left corner of the page.
 * The element's coordinates are returned as a JSON object with x and y properties.
 *
 * Returns {x:number, y:number} The X and Y coordinates for the element on the page.
 *
 * @param {String} id - ID of the element to route the command to (ID is a WebElement JSON object)
 * @param {Function} callback
 */
Actions.elementIdLocation = function(id, callback) {
  return getRequest.call(this, "/element/" + id + "/location", callback);
};

/**
 * @param {String} id - ID of the element to route the command to (ID is a WebElement JSON object)
 * @param {Function} callback
 */
Actions.elementIdName = function(id, callback) {
  return getRequest.call(this,
    "/element/" + id + "/name", callback);
};

/**
 * @param {String} id - ID of the element to route the command to (ID is a WebElement JSON object)
 * @param {Function} callback
 */
Actions.elementIdClear = function(id, callback) {
  return postRequest.call(this, "/element/" + id + "/clear", callback);
};

Actions.elementIdSelected = function(id, callback) {
  return getRequest.call(this, "/element/" + id + "/selected", callback);
};

Actions.elementIdEnabled = function(id, callback) {
  return getRequest.call(this, "/element/" + id + "/enabled", callback);
};

Actions.elementIdEquals = function(id1, id2, callback) {
  return getRequest.call(this, "/element/" + id1 + "/equals/" + id2, callback);
};

Actions.elementIdSize = function(id, callback) {
  return getRequest.call(this,
    "/element/" + id + "/size",
    callback);
};

Actions.elementIdText = function(id, callback) {
  return getRequest.call(this,
    "/element/" + id + "/text",
    callback);
};

Actions.elementIdValue = function(id, value, callback) {
  if (arguments.length === 2) {
    callback = value;
    return getRequest.call(this,
      "/element/" + id + "/value",
      callback);
  }

  value = String(value);
  return postRequest.call(this, "/element/" + id + "/value", {
    "value": value.split("")
  }, callback);
};

/**
 * Search for an element on the page, starting from the document root. The located element will be
 * returned as a WebElement JSON object.
 * The table below lists the locator strategies that each server should support. Each locator must
 * return the first matching element located in the DOM.
 *
 * class name         Returns an element whose class name contains the search value; compound class names are not permitted.
 * css selector       Returns an element matching a CSS selector.
 * id                 Returns an element whose ID attribute matches the search value.
 * name               Returns an element whose NAME attribute matches the search value.
 * link text          Returns an anchor element whose visible text matches the search value.
 * partial link text  Returns an anchor element whose visible text partially matches the search value.
 * tag name           Returns an element whose tag name matches the search value.
 * xpath              Returns an element matching an XPath expression.
 *
 * @param {Object} using
 * @param {Object} value
 * @param {Object} callback
 */
Actions.elements = function(using, value, callback) {
  var check = /class name|css selector|id|name|link text|partial link text|tag name|xpath/gi;
  if (!check.test(using)) {
    throw new Error("Please provide any of the following using strings as the first parameter: " +
        "class name, css selector, id, name, link text, partial link text, tag name or xpath");
  }

  return postRequest.call(this, "/elements", {
    "using": using,
    "value": value
  }, callback);
};

/**
 * Inject a snippet of JavaScript into the page for execution in the context of the currently selected
 * frame. The executed script is assumed to be synchronous and the result of evaluating the script is
 * returned to the client.
 *
 * The script argument defines the script to execute in the form of a function body. The value
 * returned by that function will be returned to the client. The function will be invoked with the
 * provided args array and the values may be accessed via the arguments object in the order specified.
 *
 * Arguments may be any JSON-primitive, array, or JSON object. JSON objects that define a WebElement
 * reference will be converted to the corresponding DOM element. Likewise, any WebElements in the
 * script result will be returned to the client as WebElement JSON objects.
 *
 * Potential Errors:
 *  - NoSuchWindow - If the currently selected window has been closed.
 *  - StaleElementReference - If one of the script arguments is a WebElement that is not attached to the page's DOM.
 *  - JavaScriptError - If the script throws an Error.
 *
 * @param {String} string
 * @param {Array} args
 * @param {Function} callback
 */
Actions.execute = function(script, args, callback) {
  if (typeof script == 'function') {
    var fn = 'var passedArgs = Array.prototype.slice.call(arguments,0); return ' + script.toString() + '.apply(window, passedArgs);';
  } else {
    fn = script;
  }

  if (arguments.length == 2 && typeof arguments[1] == 'function') {
    callback = arguments[1];
    args = [];
  }

  return postRequest.call(this, "/execute", {
    "script": fn,
    "args": args
  }, callback);
}

/**
 * Change focus to another frame on the page. If the frame id is null, the server
 * should switch to the page's default content.
 *
 * @param {String} frameId
 * @param {Function} callback
 */
Actions.frame = function(frameId, callback) {
  if (arguments.length == 1 && typeof frameId == "function") {
    callback = frameId;
    return postRequest.call(this, "/frame", callback);
  }

  return postRequest.call(this, "/frame", {
    "frameId": frameId
  }, callback);
};

/**
 * Move the mouse by an offset of the specificed element. If no element is specified, the move is relative
 * to the current mouse cursor. If an element is provided but no offset, the mouse will be moved to the
 * center of the element. If the element is not visible, it will be scrolled into view.
 *
 * @param {String} element - Opaque ID assigned to the element to move to. If not specified or is null,
 *  the offset is relative to current position of the mouse.
 * @param {Number} xoffset - X offset to move to, relative to the top-left corner of the element.
 *  If not specified, the mouse will move to the middle of the element.
 * @param {String} yoffset - Y offset to move to, relative to the top-left corner of the element.
 * If not specified, the mouse will move to the middle of the element.
 * @param {Function} callback
 */
Actions.moveTo = function(element, xoffset, yoffset, callback) {
  var data = {}
  if (typeof element == 'string') {
    data.element = element;
  }
  if (typeof xoffset == 'number') {
    data.xoffset = xoffset;
  }
  if (typeof yoffset == 'number') {
    data.yoffset = yoffset;
  }
  return postRequest.call(this,
      "/moveto", data, callback);
};

Actions.submit = function(id, callback) {
  return postRequest.call(this,
      "/element/" + id + "/submit", "", callback);
};

Actions.screenshot = function(callback) {
  return getRequest.call(this, "/screenshot", callback);
};

/**
 * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol#/status
 */
Actions.status = function(callback) {
  return getRequest.call(this, "/status", callback);
};

Actions.title = function(callback) {
  return getRequest.call(this, "/title", callback);
};

Actions.window_handle = function(callback) {
  return getRequest.call(this, "/window_handle", callback);
};

Actions.window_handles = function(callback) {
  return getRequest.call(this, "/window_handles", callback);
};

/**
 * GET /session/:sessionId/url - Retrieve the URL of the current page.
 * POST /session/:sessionId/url - Navigate to a new URL.
 *
 * Potential Errors:
 *  NoSuchWindow - If the currently selected window has been closed.
 *
 * @param {String} url
 * @param {Function} callback
 */
Actions.url = function(url, callback) {
  if (typeof url == "string") {
    return postRequest.call(this, "/url", {
      url : url
    }, callback);
  }

  if (typeof url == "function") {
    callback = url;
  }

  return getRequest.call(this, "/url", callback);
};

Actions.session  = function(action, callback) {
  var options = {
    path : "/session"
  };

  if (typeof action == "function") {
    callback = action;
    action = "get";
  }

  switch (action) {
    case "delete":
      options.path += '/' + this.sessionId;
      options.method = "DELETE";
      break;
    default:
      options.method = "GET";
  }

  return this.runProtocolCommand(options, callback).send();
};

function getRequest(path, callback) {
  var options = {
    path : "/session/" + this.sessionId + path,
    method : "GET"
  }
  callback = callback || function() {};

  return this.runProtocolCommand(options, callback).send();
}

function postRequest(path, data, callback) {
  if (arguments.length == 2 && typeof data == "function") {
    callback = data;
    data = "";
  }

  var options = {
    path : "/session/" + this.sessionId + path,
    method : "POST",
    data : data || ""
  }
  callback = callback || function() {};
  return this.runProtocolCommand(options, callback).send();
}

module.exports.actions = Actions;
<|endoftext|>


<commit_before>module.exports = parseHttpRequest

var common = require('../common')

function parseHttpRequest (req, opts) {
  if (!opts) opts = {}
  var s = req.url.split('?')
  var params = common.querystringParse(s[1])
  params.type = 'http'

  if (opts.action === 'announce' || s[0] === '/announce') {
    params.action = common.ACTIONS.ANNOUNCE

    if (typeof params.info_hash !== 'string' || params.info_hash.length !== 20) {
      throw new Error('invalid info_hash')
    }
    params.info_hash = common.binaryToHex(params.info_hash)

    if (typeof params.peer_id !== 'string' || params.peer_id.length !== 20) {
      throw new Error('invalid peer_id')
    }
    params.peer_id = common.binaryToHex(params.peer_id)

    params.port = Number(params.port)
    if (!params.port) throw new Error('invalid port')

    params.left = Number(params.left)
    if (isNaN(params.left)) params.left = Infinity

    params.compact = Number(params.compact) || 0
    params.numwant = Math.min(
      Number(params.numwant) || common.DEFAULT_ANNOUNCE_PEERS,
      common.MAX_ANNOUNCE_PEERS
    )

    params.ip = opts.trustProxy
      ? req.headers['x-forwarded-for'] || req.connection.remoteAddress
      : req.connection.remoteAddress.replace(common.REMOVE_IPV4_MAPPED_IPV6_RE, '') // force ipv4
    params.addr = (common.IPV6_RE.test(params.ip) ? '[' + params.ip + ']' : params.ip) + ':' + params.port

    params.headers = req.headers
  } else if (opts.action === 'scrape' || s[0] === '/scrape') {
    params.action = common.ACTIONS.SCRAPE

    if (typeof params.info_hash === 'string') params.info_hash = [ params.info_hash ]
    if (Array.isArray(params.info_hash)) {
      params.info_hash = params.info_hash.map(function (binaryInfoHash) {
        if (typeof binaryInfoHash !== 'string' || binaryInfoHash.length !== 20) {
          throw new Error('invalid info_hash')
        }
        return common.binaryToHex(binaryInfoHash)
      })
    }
  } else {
    throw new Error('invalid action in HTTP request: ' + req.url)
  }

  return params
}
<commit_msg>Fixes for PR #185<commit_after>module.exports = parseHttpRequest

var common = require('../common')

function parseHttpRequest (req, opts) {
  if (!opts) opts = {}
  var s = req.url.split('?')
  var params = common.querystringParse(s[1])
  params.type = 'http'

  if (opts.action === 'announce' || s[0] === '/announce') {
    params.action = common.ACTIONS.ANNOUNCE

    if (typeof params.info_hash !== 'string' || params.info_hash.length !== 20) {
      throw new Error('invalid info_hash')
    }
    params.info_hash = common.binaryToHex(params.info_hash)

    if (typeof params.peer_id !== 'string' || params.peer_id.length !== 20) {
      throw new Error('invalid peer_id')
    }
    params.peer_id = common.binaryToHex(params.peer_id)

    params.port = Number(params.port)
    if (!params.port) throw new Error('invalid port')

    params.left = Number(params.left)
    if (Number.isNaN(params.left)) params.left = Infinity

    params.compact = Number(params.compact) || 0
    params.numwant = Math.min(
      Number(params.numwant) || common.DEFAULT_ANNOUNCE_PEERS,
      common.MAX_ANNOUNCE_PEERS
    )

    params.ip = opts.trustProxy
      ? req.headers['x-forwarded-for'] || req.connection.remoteAddress
      : req.connection.remoteAddress.replace(common.REMOVE_IPV4_MAPPED_IPV6_RE, '') // force ipv4
    params.addr = (common.IPV6_RE.test(params.ip) ? '[' + params.ip + ']' : params.ip) + ':' + params.port

    params.headers = req.headers
  } else if (opts.action === 'scrape' || s[0] === '/scrape') {
    params.action = common.ACTIONS.SCRAPE

    if (typeof params.info_hash === 'string') params.info_hash = [ params.info_hash ]
    if (Array.isArray(params.info_hash)) {
      params.info_hash = params.info_hash.map(function (binaryInfoHash) {
        if (typeof binaryInfoHash !== 'string' || binaryInfoHash.length !== 20) {
          throw new Error('invalid info_hash')
        }
        return common.binaryToHex(binaryInfoHash)
      })
    }
  } else {
    throw new Error('invalid action in HTTP request: ' + req.url)
  }

  return params
}
<|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,
      stream_length = 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 => {
      stream_length += 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 (stream_length > 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);

      let indent = 0;
      const parsedUrl = url.parse(payload.url, true);

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

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

      response.writeHead(result.status);

      if (result.raw) {
        // binary data are sent as-is
        if (result.content instanceof Buffer) {
          return response.end(result.content);
        }

        // all JS objects are serialized, including arrays
        if (typeof result.content === 'object') {
          return response.end(JSON.stringify(result.content));
        }

        // scalars are sent as-is
        return response.end(result.content);
      }

      response.end(JSON.stringify(result.content, undefined, indent));
    }
    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);
}

module.exports = HttpProxy;
<commit_msg>use camelCase variable<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);

      let indent = 0;
      const parsedUrl = url.parse(payload.url, true);

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

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

      response.writeHead(result.status);

      if (result.raw) {
        // binary data are sent as-is
        if (result.content instanceof Buffer) {
          return response.end(result.content);
        }

        // all JS objects are serialized, including arrays
        if (typeof result.content === 'object') {
          return response.end(JSON.stringify(result.content));
        }

        // scalars are sent as-is
        return response.end(result.content);
      }

      response.end(JSON.stringify(result.content, undefined, indent));
    }
    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);
}

module.exports = HttpProxy;
<|endoftext|>