Source: org/terraswarm/accessor/accessors/web/hosts/cordova/modules/http-client/http-client-old.js

// Below is the copyright agreement for the Ptolemy II system.
//
// Copyright (c) 2015-2016 The Regents of the University of California.
// All rights reserved.
//
// Permission is hereby granted, without written agreement and without
// license or royalty fees, to use, copy, modify, and distribute this
// software and its documentation for any purpose, provided that the above
// copyright notice and the following two paragraphs appear in all copies
// of this software.
//
// IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
// FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
// ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
// THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//
// THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
// PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
// CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
// ENHANCEMENTS, OR MODIFICATIONS.
//

/** Module for HTTP clients. Supports get, put and post methods.
 *  
 *  Depends on cordova-plugin-advanced-http. To install run 
 *  <pre>
 *  cordova plugin add cordova-plugin-advanced-http
 *  </pre>
 *
 *  A nominal usage example is:
 *  <pre>
 *  httpClient.get({
 *  		trustAll: true,
 *      	url: "https://www.google.com"
 *   	},
 *   	function(response) {
 *      	console.log(response.body);
 *   	}
 *  );
 *  </pre>
 *  
 *  Many of the features of http-client modules for Cape Code and Node hosts are not
 *  supported in this module due to limitiations in the plugin. Places in the documentation where
 *  this implementation cannot meet the API of the other versions are marked with FIXMEs 
 *
 *  FIXME: PUT and POST commands only accept stringified JSON objects as the body
 *  They are sent as json or url encoding depending on this implementation of http-client's
 *  special option: "encoding". Blame cordova-plugin-advanced-http.
 * 
 *  Both http and https are supported.
 *
 *  @module http-client
 *  @author  Matt Weber, Chadlia Jerad, and Hokeun Kim
 *  @version $$Id: http-client.js 75980 2017-04-23 00:19:25Z beth@berkeley.edu $$
 */

// Needed plugins
var cordovaHTTP = cordova.plugin.http;
exports.requiredPlugins = ['cordova-plugin-advanced-http'];
var EventEmitter = require('events').EventEmitter;
var util = require('util');
//FIXME: this full path require method is brittle. Ideally we would require these
//helper node modules with require('util') style.
var URL = require('./modules/url-parse/url-parse');
var required = require('./modules/requires-port/requires-port');



// Store if 'trust all certificates' and 'validate domain name' were already set
// to the desired value. This will avoid non useful repeated calls.
var trustAllStatus = false;
var validateDomainNameStatus = true;

/** Issue an HTTP request and provide a callback function for responses.
 *  The callback is a function that is passed an instance of IncomingMessage,
 *  defined here. This function returns an instance of ClientRequest, also defined here.
 *  The HTTP request will not actually be issued until you call the end() function on
 *  the returned ClientRequest.
 *
 *  This implementation ensures that for any accessor that calls this function,
 *  the callback functions are called in the same order as
 *  invocations of this request() function that triggered the request.
 *  If you call this function from the same accessor before the previous
 *  request has been completed (the callback function has been called or it has
 *  timed out), then the request will be queued to be issued only after the previous
 *  request has been satisfied.
 *
 *  The options argument can be a string URL
 *  or a map with the following fields (this helper class assumes
 *  all fields are present, so please be sure they are):
 *  <ul>
 *  <li> trustAll: FIXME THIS IS A CORDOVA EXCLUSIVE OPTION.
 *       Accept all SSL certificates. Or disable accepting all certificates. Defaults to false.
 *  <li> validateDomainName: FIXME THIS IS A CORDOVA EXCLUSIVE OPTION.
 *       Whether or not to validate the domain name in the certificate. Defaults to true.
 *  <li> encoding: FIXME: THIS IS ONLY A CORDOVA ISSUE
 *       For put and post requests, cordova-plugin-advanced-http only supports
 *       JSON and url encodings. Possible values are "json" and "urlencoded". "json" is default. See. 
 *       https://www.npmjs.com/package/cordova-plugin-advanced-http-custom-error#setdataserializer
 *  <li> body: The request body, if any. FIXME: This should support all strings and image data
 *       but doesn't. It only supports stringified JSON objects (not stringified strings!).
 *       These are either url or json encoded in the body depending on the value of the Cordova-only
 *       special "encoding" option. Default is json encoding.
 *  <li> headers: An object containing request headers. By default this
 *       is an empty object. Items may have a value that is an array of values,
 *       for headers with more than one value.
 *  <li> keepAlive: FIXME: The value true is not supported by the cordova http plugin!
 *       In other hosts, this is a boolean
 *       that specifies whether to keep sockets around
 *       in a pool to be used by other requests in the future. This defaults to false.
 *  <li> method: FIXME: The only supported HTTP request methods in this module are 'GET', 'PUT', and 'POST'.
 *       Defaults to 'GET'. In other hosts more methods may be supported.
 *  <li> outputCompleteResponseOnly: FIXME: The value false is not supported by the cordova http plugin! 
 *       In other hosts, if false, then the multiple invocations of the
 *       callback may be invoked for each request. This defaults to true, in which case
 *       there will be only one invocation of the callback.
 *  <li> timeout: FIXME: This parameter is ignored by the cordova http plugin! 
 *       In other hosts, the amount of time (in milliseconds) to wait for a response
 *       before triggering a null response and an error. This defaults to 5000.
 *  <li> url: A string that can be parsed as a URL, or an object containing
 *       the following fields:
 *       <ul>
 *       <li> host: A string giving the domain name or IP address of
 *            the server to issue the request to. This defaults to 'localhost'.
 *       <li> path: Request path as a string. This defaults to '/'. This can
 *            include a query string, e.g. '/index.html?page=12', or the query
 *            string can be specified as a separate field (see below).
 *            An exception is thrown if the request path contains illegal characters.
 *       <li> protocol: The protocol. This is a string that defaults to 'http'.
 *       <li> port: Port of remote server. This defaults to 80. Using the defaultwill probably yield
 *            undesired behavior if the specified protocol isn't http.
 *       <li> query: A query string to be appended to the path, such as '?page=12'.
 *       </ul>
 *  </ul>
 *  @param options The options or URL.
 *  @param responseCallback The callback function to call with an instance of IncomingMessage,
 *   or with a null argument to signal an error.
 *  @return An instance of ClientRequest.
 */

exports.request = function (options, responseCallback) {
    return new ClientRequest(options, responseCallback);
};

/** Constructor for the object type returned by the request() function.
 *  This object type provides the following functions:
 *  <ul>
 *  <li> end(): Call this to end the request. </li>
 *  <li> write(''data'', ''encoding''): FIXME: Not supported by this module!
 *  In other hosts, writes data (e.g. for a POST request). </li>
 *  </ul>
 *  The request will not be issued until you call end().
 *  See the documentation of the request function for an explanation of the arguments.
 *  This is an event emitter that emits the following events:
 *  <ul>
 *  <li> 'error': If an error occurs. The message is passed as an argument. </li>
 *  <li> 'response': A response is received from the server. This event is automatically
 *       handled by calling responseCallback, if responseCallback is not null.</li>
 *  </ul>
 *  @constructor
 *  @param options The options.
 *  @param responseCallback The callback function to call with an instance of IncomingMessage,
 *   or with a null argument to signal an error.
 */

function ClientRequest(options, responseCallback) {
    var self = this;

    var defaultOptions = {
        'headers': {},
        'keepAlive': false,
        'method': 'GET',
        'outputCompleteResponseOnly': true,
        'timeout': 5000,
        'trustAll': false,
        'validateDomainName': true,
        'encoding': 'json'
    };
    var defaultURL = {
        'host': 'localhost',
        'path': '/',
        'port': 80,
        'protocol': 'http',
        'query': ''
    };

    var urlSpec;
    if (util.isString(options)) {
        //The entire options input is given as just a string
        urlSpec = options;
        options = {}; // If only URL is passed in, create new options object
    } else if (util.isString(options.url)) {
        //Options is an object with a url field that is just a string
        urlSpec = options.url;
    }
    if (urlSpec) {
        //Either options is a string or an object with a string url field.
        var url = new URL(urlSpec);
        //var url = new URL('https://github.com/foo/bar');
        var port = url.port;
        if (!port || port < 0) {
            port = false; //indicates port should be omited from url
        }
        options.url = {
            'host': url.hostname,
            'path': url.pathname,
            'port': port,
            'protocol': url.protocol.split(':')[0],
            'query': url.query
        };
    } else {
        //Options is an object with its URL field set to an object
        options.url = util._extend(defaultURL, options.url);
    }
    options = util._extend(defaultOptions, options);

    // Attach the callback to be invoked when this object issues
    // a 'response' event.
    if (responseCallback) {
        if (options.outputCompleteResponseOnly) {
            self.once('response', responseCallback);
        } else {
            self.on('response', responseCallback);
        }
    }

    // Set the Content-Length header
    if (options.body !== null && options.body !== undefined) {
        var headers;
        if (typeof options.headers == "undefined") {
            headers = {};
        } else {
            headers = options.headers;
        }

        headers['Content-Length'] = options.body.length;
        options.headers = headers;
    }

    this.options = options;
}
util.inherits(ClientRequest, EventEmitter);
exports.ClientRequest = ClientRequest;

/** Issue the request. */
ClientRequest.prototype.end = function () {
    var that = this;
    //trustall and validate domain name options can only be set asynchronously
    //by the http plugin. To ensure the plugin is in the correct state before
    //issuing the request we have to chain callbacks.

    //setTrustAll is a helper function which upon success calls
    //setValidateDomainName which upon success calls the main body of this function (below)
    setTrustAll(this, setValidateDomainName, function() {
        //Call the apropriate wrapper for the cordova http plugin method.
        if(that.options.method){
            switch(that.options.method) {
                case "GET":
                    cordovaGet(that);
                    break;
                case "PUT":
                    cordovaPut(that);
                    break;
                case "POST":
                    cordovaPost(that);
                    break;
                default:
                    that._handleError("http-client: invalid HTTP command specified");
                    break;
            }
        } else{
            that._handleError("http-client: No HTTP command specified");
        }
    });
};

//Helper function for ClientRequest.prototype.end. See the comment in that function
var setTrustAll = function(request, setValidateCallback, endCallback){
    if(request.options.trustAll == trustAllStatus){
        //no need to set anything
        setValidateCallback(request, endCallback);
    } else {
        cordovaHTTP.acceptAllCerts(request.options.trustAll, function() {
            //If successfully set
            trustAllStatus = request.options.trustAll;
            console.log('trustAllStatus changed to ' + request.options.trustAll);
            setValidateCallback(request, endCallback);
        }, function() {
            //If not successfully set
            request._handleError('Unable to set trustAll status for cordova plugin!');
        });
    }
};

//Helper function for ClientRequest.prototype.end. See the comment in that function.
var setValidateDomainName = function(request, endCallback){
    if(request.options.validateDomainName == validateDomainNameStatus ){
        //no need to set anything
        endCallback();
    } else {
        cordovaHTTP.validateDomainName(request.options.validateDomainName, function() {
            //If successfully set
            validateDomainNameStatus = request.options.validateDomainName;
            console.log('validateDomainNameStatus changed to ' + request.options.validateDomainName);
            endCallback();
        }, function() {
            //If not successfully set
            request._handleError('Unable to set validateDomainName status for cordova plugin!');
        });
    }
};


/** Internal function used to handle an error.
 *  @param message The error message.
 */
ClientRequest.prototype._handleError = function (message) {
    // There may be no registered error event handler.
    try {
        this.emit('error', message);
    } catch (err) {
        error(message);
    }
};

/** Convenience method to issue an HTTP GET.  This just calls request() and then
 *  calls end() on the object returned by request(). It returns the object returned
 *  by request() (an instance of ClientRequest). See request() for documentation of
 *  the arguments.
 *
 *  This implementation ensures that for any accessor that calls this function,
 *  the callback functions are called in the same order as
 *  invocations of this request() function that triggered the request.
 *  If you call this function from the same accessor before the previous
 *  request has been completed (the callback function has been called or it has
 *  timed out), then the request will be queued to be issued only after the previous
 *  request has been satisfied.
 *
 *  @param options The options.
 *  @param responseCallback The callback function to call with an instance of IncomingMessage,
 *   or with a null argument to signal an error.
 */
exports.get = function (options, responseCallback) {
    var request = exports.request(options, responseCallback);
    request.end();
    return request;
};

/** Convenience method to issue an HTTP POST.  This just calls request() and then
 *  calls end() on the object returned by request(). It returns the object returned
 *  by request() (an instance of ClientRequest). See request() for documentation of
 *  the arguments.
 *
 *  This implementation ensures that for any accessor that calls this function,
 *  the callback functions are called in the same order as
 *  invocations of this request() function that triggered the request.
 *  If you call this function from the same accessor before the previous
 *  request has been completed (the callback function has been called or it has
 *  timed out), then the request will be queued to be issued only after the previous
 *  request has been satisfied.
 *
 *  @param options The options.
 *  @param responseCallback The callback function to call with an instance of IncomingMessage,
 *   or with a null argument to signal an error.
 */
exports.post = function (options, responseCallback) {
    options.method = "POST";
    var request = exports.request(options, responseCallback);
    request.end();
    return request;
};


/** Convenience method to issue an HTTP PUT.  This just calls request() and then
 *  calls end() on the object returned by request(). It returns the object returned
 *  by request() (an instance of ClientRequest). See request() for documentation of
 *  the arguments.
 *
 *  This implementation ensures that for any accessor that calls this function,
 *  the callback functions are called in the same order as
 *  invocations of this request() function that triggered the request.
 *  If you call this function from the same accessor before the previous
 *  request has been completed (the callback function has been called or it has
 *  timed out), then the request will be queued to be issued only after the previous
 *  request has been satisfied.
 *
 *  @param options The options.
 *  @param responseCallback The callback function to call with an instance of IncomingMessage,
 *   or with a null argument to signal an error.
 */
exports.put = function (options, responseCallback) {
    options.method = "PUT";
    var request = exports.request(options, responseCallback);
    request.end();
    return request;
};

/** Issue an HTTP get request. Initiated by calling end() on a ClientRequest with its method
 *  parameter set to GET
 *
 *  In addition to the description of options parameter given in parseOptions(),
 *  this parameter can call accepting all, if trustAll attribute in options is set
 *  to true.
 *  
 *  @param request The clientRequest object initiating the command.
 */
var cordovaGet = function (request) {
	var parsedOptions = parseOptions(request);
    if(parsedOptions){
         console.log('Get Request to URL...');
         console.log(JSON.stringify(parsedOptions));
    
        // Call cordovaHTTP get
        cordovaHTTP.get(parsedOptions.url, 
                parsedOptions.parameters, 
                parsedOptions.headers, 
                function(response) {
                    console.log('Get request successful!');
                    var receivedMessage = new IncomingMessage(response);
                    request.emit('response', receivedMessage);
                },
                function(response) {
                    console.log('Get request failed');
                    request.emit('response', null);
                    request._handleError('cordovaGet error in http-client: ' + response.error);
                }
        );
    } else {
        request.emit('response', null);
        request._handleError('cordovaPut error in http-client: unable to parse options');
    }


   
};

/** Issue an HTTP post request.
 *  
 *  @param options The options.
 *  @param responseCallback The callback function to call with an instance of IncomingMessage,
 *   or with a null argument to signal an error.
 */
 
var cordovaPost = function (request) {
	var parsedOptions = parseOptions(request);
    if(parsedOptions){
         console.log('Post Request to URL...');
         console.log(JSON.stringify(parsedOptions));        
        // Call cordovaHTTP post. It takes the url, data, headers and two callback functions
        cordovaHTTP.post(parsedOptions.url, 
            parsedOptions.data, 
                 parsedOptions.headers,
                function(response) {
                    console.log('Post request successful!');
                    var receivedMessage = new IncomingMessage(response);
                    request.emit('response', receivedMessage);
                },
                function(response) {
                    console.log("Post request failed");
                    request.emit('response', null);
                    request._handleError('cordovaPost error in http-client: ' + response.error);
                }
        );
    } else {
        request.emit('response', null);
        request._handleError('cordovaPost error in http-client: unable to parse options');
    }

};

/** Issue an HTTP put request.
 *
 *  @param options The options, see parseOptions for more details.
 *  @param responseCallback The callback function to call with an instance of IncomingMessage,
 *   or with a null argument to signal an error.
 */
var cordovaPut = function (request) {
	var parsedOptions = parseOptions(request);

    if(parsedOptions){
        console.log('Put Request to URL...');
        console.log(JSON.stringify(parsedOptions));     
    // Call cordovaHTTP post. It takes the url, data, headers and two callback functions
        cordovaHTTP.put(parsedOptions.url, 
            parsedOptions.data, 
            parsedOptions.headers, 
            function(response) {
                console.log('Put request successful!');
                var receivedMessage = new IncomingMessage(response);
                request.emit('response', receivedMessage);
            },
            function(response) {
                console.log('Put request failed');
                request.emit('response', null);
                request._handleError('cordovaPut error in http-client: ' + response.error);
            }
        );
    } else {
        request.emit('response', null);
        request._handleError('cordovaPut error in http-client: unable to parse options');
    }

    
};

/** Parses the request options and returns cordova plugin compatible attributes.
 *  
 *  @param request the clientRequest object containing options at request.options.
 *  @returns an object with cordova-plugin-advanced-http compatible attributes: url, parameters and headers.
 *  returns null if unable to parse the options.
 */
function parseOptions (request) {
    var options = request.options;
	var url = '', parameters = {}, data = {}, headers = {};
    
    // Extract URL from options, if any
	if (typeof options === 'string') {
		url = options;
	} else if (options.url) {
		if (typeof options.url === 'string') {
			url = options.url;
		} else {
			var host = 'localhost';
			var path = '/';
			var protocol = 'http';
			var port = '80';

			if (typeof options.url.host !== 'undefined' && options.url) {
				host = options.url.host; 
			}
			if (typeof options.url.path !== 'undefined' && options.url.path) {
				path = options.url.path; 
			}
			if (typeof options.url.protocol !== 'undefined' && options.url.protocol) {
				protocol = options.url.protocol; 
			}
			if (typeof options.url.port !== 'undefined' && options.url.port) {
				port = options.url.port; 
			}

            //If port is set to false it should be omited from the url
            if(options.url.port){
                host = host + ':' + port;
            }
            url = protocol + '://' + host + path;
		}
	}
		
	// Construct headers from options, if any
	if (options.headers) {
		headers = options.headers;
	}
	
	// Construct parameters from options, if any
	if (options.url.query) {
		parameters = options.url.query;
	}
	
	// Construct data from options, if any
	// In a GET request, arguments should already be encoded in the url.
	if (options.body) {
        //FIXME: cordova-plugin-advanced-http accepts only JSON objects as the body
		try{
            data = JSON.parse(options.body);
        } catch (error) {
            if( options.method == "POST" || options.method == "PUT"){
                return null;
            }
        }
	}

    // FIXME encodings are not a problem on the other hosts!
    if (options.encoding){
        if(options.encoding == "urlencoded"){
            cordovaHTTP.setDataSerializer("urlencoded");
        } else {
            cordovaHTTP.setDataSerializer("json");
        }
    }
	
	return {'url': url, 'headers': headers, 'parameters': parameters, 'data': data};
}


/** Incoming message object type.  Used by cordovaGet, cordovaPut, and cordovaPost
 *
 *  An instance of this object type will be passed to the callback passed to the
 *  request() or this.get() functions. The instance contains:
 *  <ul>
 *  <li> body: a string with the body of the response. </li>
 *  <li> headers: message header names and values. Names are lower case. </li>
 *  <li> statusCode: an integer indicating the status of the response. </li>
 *  <li> cookies: FIXME: not returned in this module!
 *  On other hosts, an array of strings with cookies returned. </li>
 *  <li> statusMessage: FIXME: not returned in this module!
 *  On other hosts a string with the status message of the response. </li>
 *  </ul>
 *
 *  FIXME: the fields cookies and statusMessage are not returned by the cordova-plugin-advanced-http.
 *  Therefore they are written to be an empty array and empty string respectively.
 *
 *  This object should match the interface of the Node 
 *  [http.IncomingMessage](https://nodejs.org/api/http.html#http_class_http_incomingmessage).
 *  Do not add fields here, otherwise the node host will fail.
 *
 *  @constructor
 *  @param response A response from the cordova-plugin-advanced-http, as given to a callback for an http
 *  command such as cordovaHTTP.get
 */
// IncomingMessage = function(response, body) {
function IncomingMessage(response) {
    this.body = response.data;
    this.statusCode = status;
    this.cookies = [];
    this.statusMessage = "";
    this.headers = {};

    for (var name in response.headers) {

            // Vert.x header keys are in lowercase.
        if(response.headers.hasOwnProperty(name)){
            this.headers[name.toLowerCase()] = response.headers[name];
        }
    }
}