//
//  msrsClient.js
//  A client for Remote Scripting supporting the MSRS (Microsoft) Remote Scripting
//  protocol.  This code is based on JSRS (by Brent Ashley - jsrs@megahuge.com)
//  and has been adapted to the MSRS protocol with his permission.
//
//  SYNOPSIS:  Make MSRS protocol asynchronous and synchronous Remote Scripting calls 
//             to the server side without an applet.  Can be used in .NET with 
//             Thycotic.Web.RemoteScripting
//             http://www.thycotic.com/dotnet_remotescripting.html
//
//  You can get the latest version of this library here:
//  http://www.thycotic.com/dotnet_remotescripting_client.html
//  Please report bugs and enhancement requests here too.
//
//  Author:  
//    Jonathan Cogley
//    Thycotic Software Ltd
//    http://www.thycotic.com
//
//  See msrsclient_license.txt for copyright and license information - this is carried 
//  forward from Brent Ashley's original license of JSRS.
// 
//  Changes:
//  0.43    04/27/2004 (Patch submitted by Alexander Garcia Sanabria) (14 test cases total_
//          Added support for RSGetASOject so that it works also for Asynchronous
//			calls. RSExecuteFromObject was added.
//  0.42    03/31/2004 (Patch submitted by Alexander Garcia Sanabria) (13 test cases total)
//          Added fix context full pool errror. Removed the use of contexts for 
//			synchronous calls since they are not needed
//  0.41    02/01/2004  (12 test cases total)
//          Added fix for container displaying in Mozilla.
//  0.40    12/30/2003  (12 test cases total)
//          Added support for RSGetASPObject to allow the use of a proxy
//          object when calling methods.  
//			See tests/testfixture_rsgetaspobject.html for examples.
//          Tested in Internet Explorer 6, Mozilla 1.5 and Netscape 7.1.
//  0.30    12/29/2003
//          Added support for synchronous calls for browsers that support the
//          use of an XML HTTP type object.  Synchronous calls only currently
//          support HTTP GET.
//          Added comprehensive set of tests using jsUnit (www.jsunit.net)
//          which is a spectacular product!
//          Tested in Internet Explorer 6, Mozilla 1.5 and Netscape 7.1.
//  0.23    12/23/2003
//          Fixed typo bug that prevented errorcallback function from being called.
//  0.22    12/18/2003
//          Fixed "_mtype" bug causing HTTP GET to fail in classic ASP.
//  0.21    02/25/2003
//          Fixed bug with window opening when using POST.
//  0.20    02/24/2003
//          Added support for HTTP POST.
//  0.10    02/10/2003
//          Initial release - only supports GET and asynchronous calls.
//

var msrsVersion = 0.43;

// callback pool needs global scope
var msrsContextPoolSize = 0;
var msrsContextMaxPool = 10;
var msrsContextPool = new Array();
var msrsBrowser = msrsBrowserSniff();
var msrsPOST = false;
var msrsVisibility = false;

// constructor for context object
function msrsContextObj( contextID ){
  // properties
  this.id = contextID;
  this.busy = true;
  this.callback = null;
  this.errorcallback = null;
  this.context = null;
  this.container = contextCreateContainer( contextID );
  this.request = null;  
  // methods
  this.GET = contextGET;
  this.POST = contextPOST;
  this.setVisibility = contextSetVisibility;
}

//  method functions are not privately scoped 
//  because Netscape's debugger chokes on private functions
function contextCreateContainer( containerName ){
  // creates hidden container to receive server data 
  var container;
  switch( msrsBrowser ) {
    case 'NS':
      container = new Layer(100);
      container.contextID = containerName;
      container.name = containerName;
      container.visibility = 'hidden';
      container.clip.width = 100;
      container.clip.height = 100;
      break;
    
    case 'IE':
      document.body.insertAdjacentHTML( "afterBegin", '<span id="SPAN' + containerName + '"></span>' );
      var span = document.all( "SPAN" + containerName );
      var html = '<iframe contextID="' + containerName + '" onload="contextLoaded(this,this.contentWindow.document.documentElement.innerHTML)" name="' + containerName + '" src=""></iframe>';
      span.innerHTML = html;
      span.style.display = 'none';
      container = window.frames[ containerName ];
      break;
      
    case 'MOZ':  
      var span = document.createElement('SPAN');
      span.id = "SPAN" + containerName;
      document.body.appendChild( span );
      var iframe = document.createElement('IFRAME');
      iframe.contextID = containerName;
      iframe.onload = function() { contextLoaded(this,this.contentWindow.document.documentElement.innerHTML); };
      iframe.name = containerName;
      span.appendChild( iframe );
      container = iframe;
      // fix for showing container in Mozilla     
      document.getElementById("SPAN" + containerName).style.visibility = 'hidden';
      container.width = 0;
      container.height = 0;
      // end fix
      break;
  }
  return container;
}

function contextPOST( rsPage, func, parms ){
  var d = new Date();
  var unique = d.getTime() + '' + Math.floor(1000 * Math.random());
  
  var doc = (msrsBrowser == "IE" ) ? this.container.document : this.container.contentDocument;
  this.container.inrequest = true;
  doc.open();
  doc.write('<html><body>');
  doc.write('<form name="msrsForm" method="post" target="" ');
  doc.write(' action="' + rsPage + '?U=' + unique + '">');
  doc.write('<input type="hidden" name="C" value="' + this.id + '">');

  // write the method to call and parameters as hidden form inputs
  if (func != null){
    doc.write('<input type="hidden" name="_method" value="' + func + '">');
    if (parms != null || parms.length == 0){
		// assume parms is array of strings
		for( var i=0; i < parms.length; i++ ){
			doc.write( '<input type="hidden" name="p' + i + '" '
                   + 'value="' + msrsEscapeQQ(parms[i]) + '">');
		}
		doc.write( '<input type="hidden" name="pcount" '
                   + 'value="' + parms.length + '">');
    } else {
		doc.write( '<input type="hidden" name="pcount" '
                   + 'value="0">');
    } // parms
  } // func

  doc.write('</form></body></html>');
  doc.close();
  doc.forms['msrsForm'].submit();
}

function getUrlforGET( rsPage, func, parms ){
  // build URL to call
  var URL = rsPage;
  // always send context
  URL += "?C=" + this.id;
  URL += "&_mtype=execute"; // for ASP compatibility only
  if (func != null){
	URL += "&_method=" + escape(func);
	if (parms != null || parms.length == 0){
		// assume parms is array of strings
		for( var i=0; i < parms.length; i++ ){
			URL += "&p" + i + "=" + escape(parms[i]+'') + "";
		}
		URL += "&pcount=" + parms.length;
    } else {
		URL += "&pcount=0";
    } // parms
  } // func  
  // unique string to defeat cache
  var d = new Date();
  URL += "&U=" + d.getTime();
  return URL;
}

function contextGET( rsPage, func, parms ){
  var URL = getUrlforGET(rsPage,func,parms);
  // make the call
  switch( msrsBrowser ) {
    case 'NS':
      this.container.src = URL;
      break;
    case 'IE':
      this.container.document.location.replace(URL);
      break;
    case 'MOZ':
      this.container.src = '';
      this.container.src = URL; 
      break;
  }  
}

function contextSetVisibility( vis ){
  switch( msrsBrowser ) {
    case 'NS':
      this.container.visibility = (vis)? 'show' : 'hidden';
      break;
    case 'IE':
      document.all("SPAN" + this.id ).style.display = (vis)? '' : 'none';
      break;
    case 'MOZ':
      document.getElementById("SPAN" + this.id).style.visibility = (vis)? '' : 'hidden';
      this.container.width = (vis)? 250 : 0;
      this.container.height = (vis)? 100 : 0;
      break;
  }  
}

// end of context constructor

function msrsGetContextID(){
  var contextObj;
  for (var i = 1; i <= msrsContextPoolSize; i++){
    contextObj = msrsContextPool[ 'msrs' + i ];
    if ( !contextObj.busy ){
      contextObj.busy = true;      
      return contextObj.id;
    }
  }
  // if we got here, there are no existing free contexts
  if ( msrsContextPoolSize <= msrsContextMaxPool ){
    // create new context
    var contextID = "msrs" + (msrsContextPoolSize + 1);
    msrsContextPool[ contextID ] = new msrsContextObj( contextID );
    msrsContextPoolSize++;
    return contextID;
  } else {
    alert( "msrs Error:  context pool full" );
    return null;
  }
}

function _RSExecuteSynchronously(rspage,method,parameters,context) {
	var url = getUrlforGET(rspage,method,parameters);
	var data = msrsDoSynchronousGet(url);
	var request = new RSCallObject();
	request.data = data;
	request.context = context;
	evalRequest(request);
	return request;
}

function RSExecute( rspage, method ) {
  // get parameters
  var parameters = new Array();	
  var callback = null;
  var errorcallback = null;
  var context = null;
  var synchronous = true;
  var finishedParameters = false;
  var length = RSExecute.arguments.length;
  var arg;
  for (var n=2; n < length; n++) {
    arg = RSExecute.arguments[n];
	if (typeof(arg) == 'function') {
	  synchronous = false;
	  finishedParameters = true;
	  if (callback == null) {
	    callback = arg;
	  } else {
	    errorcallback = arg; 
	    break;
	  }
	} else if (!finishedParameters) {
		parameters[parameters.length] = arg;
	} else {
		context = arg;
	}
  }
    
  if (synchronous) {
	if (!msrsSupportsSynchronousGets()) {
		alert("Your browser does not support synchronous calls.\nEither use a different browser such as Internet Explorer, Mozilla or Netscape OR use asynchronous calls.");
		return;
	}
	return _RSExecuteSynchronously(rspage,method,parameters,context);
  }
  // get pooling context only if this is an async call.
  var contextObj = msrsContextPool[ msrsGetContextID() ];
  // assign callbacks and context
  contextObj.callback = callback;
  contextObj.errorcallback = errorcallback;
  contextObj.context = context;

  // set visible if set
  contextObj.setVisibility( msrsVisibility );

  if (msrsPOST && ((msrsBrowser == 'IE') || (msrsBrowser == 'MOZ'))){
    contextObj.POST( rspage, method, parameters );
  } else {
    contextObj.GET( rspage, method, parameters );
  }  
}

function RSExecuteTest( rspage, method, args ) {
  // get parameters
  var parameters = new Array();	
  var callback = null;
  var errorcallback = null;
  var context = null;
  var finishedParameters = false;
  var synchronous = true;
	for (var i=0; i < args.length; i++)
	{
		if (typeof(args[i]) == 'function')
		{
			synchronous = false;
			finishedParameters = true;	// no more params
			if (callback == null)
				callback = args[i];
			else
				errorcallback = args[i];
		}
		else if (!finishedParameters)
		{
			parameters[parameters.length] = args[i];
		}
		else
			context = args[i];
	}
	      
  if (synchronous) {
	if (!msrsSupportsSynchronousGets()) {
		alert("Your browser does not support synchronous calls.\nEither use a different browser such as Internet Explorer, Mozilla or Netscape OR use asynchronous calls.");
		return;
	}
	return _RSExecuteSynchronously(rspage,method,parameters,context);
  }
  // get pooling context only if this is an async call.
  var contextObj = msrsContextPool[ msrsGetContextID() ];
  // assign callbacks and context
  contextObj.callback = callback;
  contextObj.errorcallback = errorcallback;
  contextObj.context = context;

  // set visible if set
  contextObj.setVisibility( msrsVisibility );

  if (msrsPOST && ((msrsBrowser == 'IE') || (msrsBrowser == 'MOZ'))){
    contextObj.POST( rspage, method, parameters );
  } else {
    contextObj.GET( rspage, method, parameters );
  }  
}

function msrsGetSynchronousHttpObject() {
  var remote = null;
  try {
    remote = new XMLHttpRequest();
  } catch (e) {
	try {
      remote = new ActiveXObject("Msxml2.XMLHTTP");
    } catch (e) {
      remote = new ActiveXObject("Microsoft.XMLHTTP");
    }
  }
  return remote;
}

function msrsSupportsSynchronousGets() {
  var remote = msrsGetSynchronousHttpObject();
  return remote != null;
}

function msrsDoSynchronousGet(url) {
  var remote = msrsGetSynchronousHttpObject();
  remote.open("GET",url,false);
  remote.send(null);
  return remote.responseText;
}

function msrsEscapeQQ( thing ){
  return thing.replace(/'"'/g, '\\"');
}

function msrsBrowserSniff(){
  if (document.layers) return "NS";
  if (document.all) return "IE";
  if (document.getElementById) return "MOZ";
  return "OTHER";
}

/////////////////////////////////////////////////
//
// user functions

function msrsDebugInfo(){
  // use for debugging by attaching to f1 (works with IE)
  // with onHelp = "return msrsDebugInfo();" in the body tag
  var doc = window.open().document;
  doc.open;
  doc.write( 'Pool Size: ' + msrsContextPoolSize + '<br><font face="arial" size="2"><b>' );
  for( var i in msrsContextPool ){
    var contextObj = msrsContextPool[i];
    doc.write( '<hr>' + contextObj.id + ' : ' + (contextObj.busy ? 'busy' : 'available') + '<br>');
    doc.write( contextObj.container.document.location.pathname + '<br>');
    doc.write( contextObj.container.document.location.search + '<br>');
    doc.write( '<table border="1"><tr><td>' + contextObj.container.document.body.innerHTML + '</td></tr></table>' );
  }
  doc.write('</table>');
  doc.close();
  return false;
}

//*****************************************************************
// Handle parsing the data when loading and creating the msrs return object
//*****************************************************************
function contextLoaded(container,data) {
	// get context object and invoke callback
	var contextObj = msrsContextPool[ container.contextID ];
	var request = new RSCallObject();
	request.data = data;
	request.context = contextObj.context;
	evalRequest(request);
	contextObj.request = request;
	if (request.status != MSRS_INVALID) {
		if (request.status == MSRS_FAIL)
		{	
			if (typeof(contextObj.errorcallback) == 'function')
			{
				contextObj.errorcallback(request);
			}
			else 
			{
				alert('Remote Scripting Error\n' + request.message);
			}
		}
		else
		{
			if (typeof(contextObj.callback) == 'function')
			{
				contextObj.callback(request);
			}
		}	
		// clean up and return context to pool
		contextObj.callback = null;
		contextObj.busy = false;
	}
}


//*****************************************************************
// Constants from rs.htm for MSRS
//*****************************************************************
var MSRS_FAIL = -1;
var MSRS_COMPLETED = 0;
var MSRS_PENDING = 1;
var MSRS_PARTIAL = 2;
var MSRS_INVALID = 3;
	
//*****************************************************************
// function RSGetASPObject(url)
//	This function returns a server object for an ASP file
//	described by its public_description.
//*****************************************************************
function RSGetASPObject(url)
{
	var cb, ecb, context;
	var params = new Array;
	var request = RSExecute(url,'GetServerProxy');
	if (request.status == MSRS_COMPLETED)
	{
		var server = request.return_value;
		if (typeof(Function) == 'function')
		{
			for (var name in server)
				server[name] = Function('return RSExecuteTest(this.location,"' +  name + '",this.' + name + '.arguments);');
		}
		else
		{	// JavaScript 1.0 does not support Function  ( IE3.0 )
			for (var name in server)
				server[name] = eval('function t(p0,p1,p2,p3,p4,p5,p6,p7,p8,p9,pA,pB,pC,pD,pE,pF) { return _RSExecuteSynchronously(this.location,"' + name + '",this.' + name + '.arguments);} t');
		}
		server.location = url;
		return server;
	}
	alert('Failed to create ASP object for : ' + url);
	return null;
}

//*****************************************************************
// function evalRequest(request)
//
//	This function evaluates the data returned to the request. 
//	Marshalled jscript objects are re-evaluated on the client.
//*****************************************************************
function evalRequest(request)
{
	request.status = MSRS_COMPLETED;
	var data = request.data;
	var start_index = 0;
	var end_index = 0;
	var start_key = '<' + 'return_value';
	var end_key = '<' + '/return_value>';
	// check if there otherwise switch case 
	if (data.indexOf(start_key) == -1) {
		start_key = start_key.toUpperCase();
		end_key = end_key.toUpperCase();
	}
	if ((start_index = data.indexOf(start_key)) != -1)
	{
		var data_start_index = data.indexOf('>',start_index) + 1;
		end_index = data.indexOf(end_key,data_start_index);
		if (end_index == -1) 
			end_index = data.length;
		var metatag = data.substring(start_index,data_start_index);
		if (metatag.indexOf('SIMPLE') != -1)
		{
			request.return_value = unescape(data.substring(data_start_index,end_index));
		}
		else if (metatag.indexOf('EVAL_OBJECT') != -1)
		{
			request.return_value = data.substring(data_start_index,end_index);
			request.return_value = eval(unescape(request.return_value));
		}
		else if (metatag.indexOf('ERROR') != -1)
		{
			request.status = MSRS_FAIL;
			request.message = unescape(data.substring(data_start_index,end_index));		
		}
	}
	else
	{
		request.status = MSRS_INVALID;
		request.message = 'REMOTE SCRIPTING ERROR: Page invoked does not support remote scripting.';			
		// extra debug for errors
		var win = window.open('','_blank','height=600,width=450,scrollbars=yes');
		win.document.write( request.data );
	}
}

function RSCallObject()
{
	this.status = MSRS_PENDING;
	this.message = '';
	this.data = '';
	this.return_value = '';
	this.context = null;
}