home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 35 Internet / 35-Internet.zip / mozil06.zip / bin / components / nsXmlRpcClient.js < prev    next >
Text File  |  2001-02-14  |  51KB  |  1,461 lines

  1. /*
  2.  * The contents of this file are subject to the Mozilla Public License Version
  3.  * 1.1 (the "License"); you may not use this file except in compliance with the
  4.  * License. You may obtain a copy of the License at http://www.mozilla.org/MPL/
  5.  *
  6.  * Software distributed under the License is distributed on an "AS IS" basis,
  7.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
  8.  * the specific language governing rights and limitations under the License.
  9.  *
  10.  * The Original Code is Mozilla XML-RPC Client component.
  11.  *
  12.  * The Initial Developer of the Original Code is Digital Creations 2, Inc.
  13.  * Portions created by Digital Creations 2, Inc. are Copyright (C) 2000 Digital
  14.  * Creations 2, Inc.  All Rights Reserved.
  15.  *
  16.  * Contributor(s): Martijn Pieters <mj@digicool.com> (original author)
  17.  */
  18.  
  19. /*
  20.  *  nsXmlRpcClient XPCOM component
  21.  *  Version: $Revision: 1.12 $
  22.  *
  23.  *  $Id: nsXmlRpcClient.js,v 1.12 2000/09/15 09:15:30 mj%digicool.com Exp $
  24.  */
  25.  
  26. /*
  27.  * Constants
  28.  */
  29. const XMLRPCCLIENT_CONTRACTID = '@mozilla.org/xml-rpc/client;1';
  30. const XMLRPCCLIENT_CID =
  31.     Components.ID('{37127241-1e6e-46aa-ba87-601d41bb47df}');
  32. const XMLRPCCLIENT_IID = Components.interfaces.nsIXmlRpcClient;
  33.  
  34. const XMLRPCFAULT_CONTRACTID = '@mozilla.org/xml-rpc/fault;1';
  35. const XMLRPCFAULT_CID =
  36.     Components.ID('{691cb864-0a7e-448c-98ee-4a7f359cf145}');
  37. const XMLRPCFAULT_IID = Components.interfaces.nsIXmlRpcFault;
  38.  
  39. const DEBUG = false;
  40. const DEBUGPARSE = false;
  41.  
  42. /*
  43.  * Class definitions
  44.  */
  45.  
  46. /* The nsXmlRpcFault class constructor. */
  47. function nsXmlRpcFault() {}
  48.  
  49. /* the nsXmlRpcFault class def */
  50. nsXmlRpcFault.prototype = {
  51.     faultCode: 0,
  52.     faultString: '',
  53.  
  54.     init: function(faultCode, faultString) {
  55.         this.faultCode = faultCode;
  56.         this.faultString = faultString;
  57.     },
  58.  
  59.     toString: function() {
  60.         return '<XML-RPC Fault: (' + this.faultCode + ') ' +
  61.             this.faultString + '>';
  62.     },
  63.  
  64.     // nsISupports interface
  65.     QueryInterface: function(iid) {
  66.         if (!iid.equals(Components.interfaces.nsISupports) &&
  67.             !iid.equals(XMLRPCFAULT_IID))
  68.             throw Components.results.NS_ERROR_NO_INTERFACE;
  69.         return this;
  70.     }
  71. };
  72.  
  73. /* The nsXmlRpcClient class constructor. */
  74. function nsXmlRpcClient() {}
  75.  
  76. /* the nsXmlRpcClient class def */
  77. nsXmlRpcClient.prototype = {
  78.     _serverUrl: null,
  79.  
  80.     init: function(serverURL) {
  81.         var oURL = createInstance('@mozilla.org/network/standard-url;1',
  82.             'nsIURL');
  83.         oURL.spec = serverURL;
  84.  
  85.         // Make sure it is a complete spec
  86.         // Note that we don't care what the scheme is otherwise.
  87.         // Should we care? POST works only on http and https..
  88.         if (!oURL.scheme) oURL.scheme = 'http';
  89.         if (oURL.scheme != 'http')
  90.             throw Components.Exceptions('Only HTTP is supported');
  91.  
  92.         this._serverUrl = oURL;
  93.     },
  94.  
  95.     get serverUrl() { return this._serverUrl; },
  96.  
  97.     // BROKEN. Bug 37913
  98.     call: function(methodName, methodArgs, count) {
  99.         debug('call');
  100.         // Check for call in progress.
  101.         if (this._inProgress)
  102.             throw Components.Exception('Call in progress!');
  103.         
  104.         // Check for the server URL;
  105.         if (!this._serverUrl)
  106.             throw Components.Exception('Not initilized');
  107.  
  108.         this._inProgress = true;
  109.  
  110.         // Clear state.
  111.         this._status = null;
  112.         this._errorMsg = null;
  113.  
  114.         debug('Arguments: ' + methodArgs);
  115.  
  116.         // Generate request body
  117.         var xmlWriter = new XMLWriter();
  118.         this._generateRequestBody(xmlWriter, methodName, methodArgs);
  119.  
  120.         var requestBody = xmlWriter.data;
  121.  
  122.         debug('Request: ' + requestBody);
  123.  
  124.         var channel = this._getChannel(requestBody);
  125.  
  126.         debug('Do the deed.');
  127.  
  128.         var input = channel.openInputStream();
  129.         input = toScriptableStream(input);
  130.         
  131.         var now = new Date()
  132.         // This is broken! See bug 11859
  133.         // yes, if we use asyncCall on ourselves, we still don't
  134.         // work. Valeski calls this an event pump blocking problem.
  135.         while (!input.available()) { // Wait for data
  136.             if (new Date() - now > 1 * 60 * 1000) {
  137.                 this._inProgress = false;
  138.                 throw Components.Exception('Connection timed out');
  139.             }
  140.         }
  141.         
  142.         this._reponseStatus = channel.responseStatus;
  143.         this._responseString = channel.responseString;
  144.  
  145.         // Check for a 200 response.
  146.         if (channel.responseStatus != 200) {
  147.             this._status = Components.results.NS_ERROR_FAILURE;
  148.             this._errorMsg = 'Server returned unexpected status ' +
  149.                 channel.responseStatus;
  150.             this._inProgress = false;
  151.             throw Components.Exception('Server returned unexpected status ' +
  152.                         channel.responseStatus);
  153.         }
  154.  
  155.         // check content type
  156.         if (channel.contentType != 'text/xml') {
  157.             this._status = Components.results.NS_ERROR_FAILURE;
  158.             this._errorMsg = 'Server returned unexpected content-type ' +
  159.                 channel.contentType;
  160.             this._inProgress = false;
  161.             throw Components.Exception('Server returned unexpected ' +
  162.                 'content-type ' + channel.contentType);
  163.         }
  164.  
  165.         debug('Viable response. Let\'s parse!');
  166.         debug('Content length = ' + channel.contentLength);
  167.         
  168.         try {
  169.             this._parseResponse(toScriptableStream(inStr),
  170.                 channel.contentLength);
  171.             debug('Parse finished');
  172.             debug('Fault? ' + this._fault);
  173.             debug('Result? ' + this._result);
  174.         } catch(ex) {
  175.             this._status = ex.result;
  176.             this._errorMsg = ex.message;
  177.             throw ex;
  178.         } finally {
  179.             this._inProgress = false;
  180.         }
  181.  
  182.         return this._result;
  183.     },
  184.  
  185.     // Internal copy of the status, so's we can throw it to the syncnronous
  186.     // caller.
  187.     _status: null,
  188.     _errorMsg: null,
  189.     _listener: null,
  190.     _seenStart: false,
  191.  
  192.     asyncCall: function(listener, context, methodName, methodArgs, count) {
  193.         debug('asyncCall');
  194.         // Check for call in progress.
  195.         if (this._inProgress)
  196.             throw Components.Exception('Call in progress!');
  197.  
  198.         // Check for the server URL;
  199.         if (!this._serverUrl)
  200.             throw Components.Exception('Not initilized');
  201.  
  202.         this._inProgress = true;
  203.  
  204.         // Clear state.
  205.         this._status = null;
  206.         this._errorMsg = null;
  207.         this._listener = listener;
  208.         this._seenStart = false;
  209.         
  210.         debug('Arguments: ' + methodArgs);
  211.  
  212.         // Generate request body
  213.         var xmlWriter = new XMLWriter();
  214.         this._generateRequestBody(xmlWriter, methodName, methodArgs);
  215.  
  216.         var requestBody = xmlWriter.data;
  217.  
  218.         debug('Request: ' + requestBody);
  219.  
  220.         var chann = this._getChannel(requestBody);
  221.  
  222.         // And...... call!
  223.         chann.asyncRead(this, context);
  224.     },
  225.  
  226.     // Return a HTTP channel ready for POSTing.
  227.     _getChannel: function(request) {
  228.         // Set up channel.
  229.         var ioService = getService('@mozilla.org/network/io-service;1',
  230.             'nsIIOService');
  231.         var atomService = getService('@mozilla.org/atom-service;1',
  232.             'nsIAtomService');
  233.  
  234.         var chann = ioService.newChannelFromURI(this._serverUrl)
  235.             .QueryInterface(Components.interfaces.nsIHTTPChannel);
  236.  
  237.         // Set the request method.
  238.         chann.SetRequestMethod(atomService.getAtom('POST'));
  239.  
  240.         // Create a stream out of the request and attach it to the channel
  241.         // Note: pending bug #37773, an extra \r\n needs to be added.
  242.         var handler = ioService.getProtocolHandler(this._serverUrl.scheme)
  243.             .QueryInterface(Components.interfaces.nsIHTTPProtocolHandler);
  244.         var postStream = handler.NewPostDataStream(false, '\r\n' + request,
  245.             handler.ENCODE_NORMAL);
  246.         chann.UploadStream = postStream;
  247.  
  248.         // Set the request headers
  249.         chann.SetRequestHeader(atomService.getAtom('content-type'), 'text/xml');
  250.         chann.SetRequestHeader(atomService.getAtom('content-length'),
  251.             request.length);
  252.  
  253.         return chann;
  254.     },
  255.  
  256.     // Flag indicating wether or not we are calling the server.
  257.     _inProgress: false,
  258.     get inProgress() { return this._inProgress; },
  259.  
  260.     // nsIStreamListener interface, so's we know about the pending request.
  261.     onStartRequest: function(channel, ctxt) { 
  262.         debug('Start Request') 
  263.     }, // Do exactly nada.
  264.  
  265.     // End of the request
  266.     onStopRequest: function(channel, ctxt, status, errorMsg) {
  267.         debug('Stop Request');
  268.         if (!this._inProgress) return; // No longer interested.
  269.  
  270.         this._inProgress = false;
  271.         this._parser = null;
  272.         
  273.         if (status) {
  274.             debug('Non-zero status: (' + status.toString(16) + ') ' + errorMsg);
  275.             this._status = status;
  276.             this._errorMsg = errorMsg;
  277.             try { this._listener.onError(this, ctxt, status, errorMsg); }
  278.             catch (ex) {
  279.                 debug('Exception in listener.onError: ' + ex);
  280.             }
  281.             return;
  282.         }
  283.  
  284.         // All done.
  285.         debug('Parse finished');
  286.         if (this._foundFault) {
  287.             try {
  288.                 this._fault = createInstance(XMLRPCFAULT_CONTRACTID,
  289.                     'nsIXmlRpcFault');
  290.                 this._fault.init(this._result.getValue('faultCode').data,
  291.                     this._result.getValue('faultString').data);
  292.                 this._result = null;
  293.             } catch(e) {
  294.                 this._fault = null;
  295.                 this._result = null;
  296.                 throw Components.Exception('Could not parse response');
  297.                 try { 
  298.                     this._listener.onError(this, ctxt, 
  299.                         Components.results.NS_ERROR_FAIL, 
  300.                         'Server returned invalid Fault');
  301.                 }
  302.                 catch(ex) {
  303.                     debug('Exception in listener.onError: ' + ex);
  304.                 }
  305.             }
  306.             debug('Fault: ' + this._fault);
  307.             try { this._listener.onFault(this, ctxt, this._fault); }
  308.             catch(ex) {
  309.                 debug('Exception in listener.onFault: ' + ex);
  310.             }
  311.         } else {
  312.             debug('Result: ' + this._result);
  313.             try { 
  314.                 this._listener.onResult(this, ctxt, this._result);
  315.             } catch (ex) {
  316.                 debug('Exception in listener.onResult: ' + ex);
  317.             }
  318.         }
  319.     },
  320.  
  321.     _parser: null,
  322.     _foundFault: false,
  323.     
  324.     // Houston, we have data.
  325.     onDataAvailable: function(channel, ctxt, inStr, sourceOffset, count) {
  326.         debug('Data available (' + sourceOffset + ', ' + count + ')');
  327.         if (!this._inProgress) return; // No longer interested.
  328.  
  329.         if (!this._seenStart) {
  330.             // First time round
  331.             this._seenStart = true;
  332.  
  333.             // Store request status and message.
  334.             channel = channel
  335.                 .QueryInterface(Components.interfaces.nsIHTTPChannel);
  336.             this._reponseStatus = channel.responseStatus;
  337.             this._responseString = channel.responseString;
  338.  
  339.             // Check for a 200 response.
  340.             if (channel.responseStatus != 200) {
  341.                 this._status = Components.results.NS_ERROR_FAILURE;
  342.                 this._errorMsg = 'Server returned unexpected status ' +
  343.                     channel.responseStatus;
  344.                 this._inProgress = false;
  345.                 try {
  346.                     this._listener.onError(this, ctxt,
  347.                         Components.results.NS_ERROR_FAILURE,
  348.                         'Server returned unexpected status ' +
  349.                             channel.responseStatus);
  350.                 } catch (ex) {
  351.                     debug('Exception in listener.onError: ' + ex);
  352.                 }
  353.                 return;
  354.             }
  355.  
  356.             // check content type
  357.             if (channel.contentType != 'text/xml') {
  358.                 this._status = Components.results.NS_ERROR_FAILURE;
  359.                 this._errorMsg = 'Server returned unexpected content-type ' +
  360.                     channel.contentType;
  361.                 this._inProgress = false;
  362.                 try {
  363.                     this._listener.onError(this, ctxt,
  364.                         Components.results.NS_ERROR_FAILURE,
  365.                         'Server returned unexpected content-type ' +
  366.                             channel.contentType);
  367.                 } catch (ex) {
  368.                     debug('Exception in listener.onError: ' + ex);
  369.                 }
  370.                 return;
  371.             }
  372.  
  373.             debug('Viable response. Let\'s parse!');
  374.             debug('Content length = ' + channel.contentLength);
  375.             
  376.             this._parser = new SimpleXMLParser(toScriptableStream(inStr),
  377.                 channel.contentLength);
  378.             this._parser.setDocumentHandler(this);
  379.  
  380.             // Make sure state is clean
  381.             this._valueStack = [];
  382.             this._currValue = null;
  383.             this._cdata = null;
  384.             this._foundFault = false;
  385.         }
  386.         
  387.         debug('Cranking up the parser, window = ' + count);
  388.         try {
  389.             this._parser.parse(count);
  390.         } catch(ex) {
  391.             debug('Parser exception: ' + ex);
  392.             this._status = ex.result;
  393.             this._errorMsg = ex.message;
  394.             try {
  395.                 this._listener.onError(this, ctxt, ex.result, ex.message);
  396.             } catch(ex) {
  397.                 debug('Exception in listener.onError: ' + ex);
  398.             }
  399.             this._inProgress = false;
  400.             this._parser = null;
  401.         }
  402.  
  403.     },
  404.  
  405.     _fault: null,
  406.     _result: null,
  407.     _responseStatus: null,
  408.     _responseString: null,
  409.  
  410.     get fault() { return this._fault; },
  411.     get result() { return this._result; },
  412.     get responseStatus() { return this._responseStatus; },
  413.     get responseString() { return this._responseString; },
  414.  
  415.     /* Convenience. Create an appropriate XPCOM object for a given type */
  416.     INT:      1,
  417.     BOOLEAN:  2,
  418.     STRING:   3,
  419.     DOUBLE:   4,
  420.     DATETIME: 5,
  421.     ARRAY:    6,
  422.     STRUCT:   7,
  423.     BASE64:   8, // Not part of nsIXmlRpcClient interface, internal use.
  424.     createType: function(type, uuid) {
  425.         const SUPPORTSID = '@mozilla.org/supports-';
  426.         switch(type) {
  427.             case this.INT:
  428.                 uuid.value = Components.interfaces.nsISupportsPRInt32
  429.                 return createInstance(SUPPORTSID + 'PRInt32;1',
  430.                     'nsISupportsPRInt32');
  431.  
  432.             case this.BOOLEAN:
  433.                 uuid.value = Components.interfaces.nsISupportsPRBool
  434.                 return createInstance(SUPPORTSID + 'PRBool;1',
  435.                     'nsISupportsPRBool');
  436.  
  437.             case this.STRING:
  438.                 uuid.value = Components.interfaces.nsISupportsString
  439.                 return createInstance(SUPPORTSID + 'string;1',
  440.                     'nsISupportsString');
  441.  
  442.             case this.DOUBLE:
  443.                 uuid.value = Components.interfaces.nsISupportsDouble
  444.                 return createInstance(SUPPORTSID + 'double;1',
  445.                     'nsISupportsDouble');
  446.  
  447.             case this.DATETIME:
  448.                 uuid.value = Components.interfaces.nsISupportsPRTime
  449.                 return createInstance(SUPPORTSID + 'PRTime;1',
  450.                     'nsISupportsPRTime');
  451.  
  452.             case this.ARRAY:
  453.                 uuid.value = Components.interfaces.nsISupportsArray
  454.                 return createInstance(SUPPORTSID + 'array;1',
  455.                     'nsISupportsArray');
  456.  
  457.             case this.STRUCT:
  458.                 return createInstance('@mozilla.org/dictionary;1', 
  459.                     'nsIDictionary');
  460.  
  461.             default: throw Components.Exception('Unsupported type');
  462.         }
  463.     },
  464.  
  465.     // nsISupports interface
  466.     QueryInterface: function(iid) {
  467.         if (!iid.equals(Components.interfaces.nsISupports) &&
  468.             !iid.equals(XMLRPCCLIENT_IID) &&
  469.             !iid.equals(Components.interfaces.nsIXmlRpcClientListener) &&
  470.             !iid.equals(Components.interfaces.nsIStreamObserver) &&
  471.             !iid.equals(Components.interfaces.nsIStreamListener))
  472.             throw Components.results.NS_ERROR_NO_INTERFACE;
  473.         return this;
  474.     },
  475.  
  476.     /* Generate the XML-RPC request body */
  477.     _generateRequestBody: function(writer, methodName, methodArgs) {
  478.         writer.startElement('methodCall');
  479.  
  480.         writer.startElement('methodName');
  481.         writer.write(methodName);
  482.         writer.endElement('methodName');
  483.  
  484.         writer.startElement('params');
  485.         for (var i in methodArgs) {
  486.             writer.startElement('param');
  487.             this._generateArgumentBody(writer, methodArgs[i]);
  488.             writer.endElement('param');
  489.         }
  490.         writer.endElement('params');
  491.  
  492.         writer.endElement('methodCall');
  493.     },
  494.  
  495.     /* Write out a XML-RPC parameter value */
  496.     _generateArgumentBody: function(writer, obj) {
  497.         writer.startElement('value');
  498.         var sType = this._typeOf(obj);
  499.         switch (sType) {
  500.             case 'PRUint8':
  501.             case 'PRUint16':
  502.             case 'PRInt16':
  503.             case 'PRInt32':
  504.                 obj=obj.QueryInterface(Components.interfaces['nsISupports' +
  505.                     sType]);
  506.                 writer.startElement('i4');
  507.                 writer.write(obj.toString());
  508.                 writer.endElement('i4');
  509.                 break;
  510.  
  511.             case 'PRBool':
  512.                 obj=obj.QueryInterface(Components.interfaces.nsISupportsPRBool);
  513.                 writer.startElement('boolean');
  514.                 writer.write(obj.data ? '1' : '0');
  515.                 writer.endElement('boolean');
  516.                 break;
  517.  
  518.             case 'Char':
  519.             case 'String':
  520.                 obj=obj.QueryInterface(Components.interfaces['nsISupports' +
  521.                     sType]);
  522.                 writer.startElement('string');
  523.                 writer.write(obj.toString());
  524.                 writer.endElement('string');
  525.                 break;
  526.  
  527.             case 'Float':
  528.             case 'Double':
  529.                 obj=obj.QueryInterface(Components.interfaces['nsISupports' +
  530.                     sType]);
  531.                 writer.startElement('double');
  532.                 writer.write(obj.toString());
  533.                 writer.endElement('double');
  534.                 break;
  535.  
  536.             case 'PRTime':
  537.                 obj = obj.QueryInterface(
  538.                     Components.interfaces.nsISupportsPRTime);
  539.                 var date = new Date(obj.data)
  540.                 writer.startElement('dateTime.iso8601');
  541.                 writer.write(iso8601Format(date));
  542.                 writer.endElement('dateTime.iso8601');
  543.                 break;
  544.                 
  545.             case 'InputStream':
  546.                 obj = obj.QueryInterface(Components.interfaces.nsIInputStream);
  547.                 obj = toScriptableStream(obj);
  548.                 writer.startElement('base64');
  549.                 streamToBase64(obj, writer);
  550.                 writer.endElement('base64');
  551.                 break;
  552.             
  553.             case 'Array':
  554.                 obj = obj.QueryInterface(
  555.                     Components.interfaces.nsISupportsArray);
  556.                 writer.startElement('array');
  557.                 writer.startElement('data');
  558.                 for (var i = 0; i < obj.Count(); i++)
  559.                     this._generateArgumentBody(writer, obj.GetElementAt(i));
  560.                 writer.endElement('data');
  561.                 writer.endElement('array');
  562.                 break;
  563.  
  564.             case 'Dictionary':
  565.                 obj = obj.QueryInterface(Components.interfaces.nsIDictionary);
  566.                 writer.startElement('struct');
  567.                 var keys = obj.getKeys({});
  568.                 for (var i in keys) {
  569.                     writer.startElement('member');
  570.                     writer.startElement('name');
  571.                     writer.write(keys[i]);
  572.                     writer.endElement('name');
  573.                     this._generateArgumentBody(writer, obj.getValue(keys[i]));
  574.                     writer.endElement('member');
  575.                 }
  576.                 writer.endElement('struct');
  577.                 break;
  578.  
  579.             default:
  580.                 throw Components.Exception('Unsupported argument', null, null,
  581.                     obj);
  582.         }
  583.  
  584.         writer.endElement('value');
  585.     },
  586.  
  587.     /* Determine type of a nsISupports primitive, array or dictionary. */
  588.     _typeOf: function(obj) {
  589.         // XPConnect alows JS to pass in anything, because we are a regular
  590.         // JS object to it. So we have to test rigorously.
  591.         if (typeof obj != 'object') return 'Unknown';
  592.  
  593.         // Anything else not nsISupports is not allowed.
  594.         if (typeof obj.QueryInterface != 'function') return 'Unknown';
  595.  
  596.         // Now we will have to eliminate by trying all possebilities.
  597.         try {
  598.             obj.QueryInterface(Components.interfaces.nsISupportsPRUint8);
  599.             return 'PRUint8';
  600.         } catch(e) {}
  601.         
  602.         try {
  603.             obj.QueryInterface(Components.interfaces.nsISupportsPRUint16);
  604.             return 'PRUint16';
  605.         } catch(e) {}
  606.         
  607.         try {
  608.             obj.QueryInterface(Components.interfaces.nsISupportsPRInt16);
  609.             return 'PRInt16';
  610.         } catch(e) {}
  611.         
  612.         try {
  613.             obj.QueryInterface(Components.interfaces.nsISupportsPRInt32);
  614.             return 'PRInt32';
  615.         } catch(e) {}
  616.         
  617.         try {
  618.             obj.QueryInterface(Components.interfaces.nsISupportsPRBool);
  619.             return 'PRBool';
  620.         } catch(e) {}
  621.         
  622.         try {
  623.             obj.QueryInterface(Components.interfaces.nsISupportsChar);
  624.             return 'Char';
  625.         } catch(e) {}
  626.         
  627.         try {
  628.             obj.QueryInterface(Components.interfaces.nsISupportsString);
  629.             return 'String';
  630.         } catch(e) {}
  631.         
  632.         try {
  633.             obj.QueryInterface(Components.interfaces.nsISupportsFloat);
  634.             return 'Float';
  635.         } catch(e) {}
  636.         
  637.         try {
  638.             obj.QueryInterface(Components.interfaces.nsISupportsDouble);
  639.             return 'Double';
  640.         } catch(e) {}
  641.         
  642.         try {
  643.             obj.QueryInterface(Components.interfaces.nsISupportsPRTime);
  644.             return 'PRTime';
  645.         } catch(e) {}
  646.         
  647.         try {
  648.             obj.QueryInterface(Components.interfaces.nsIInputStream);
  649.             return 'InputStream';
  650.         } catch(e) {}
  651.         
  652.         try {
  653.             obj.QueryInterface(Components.interfaces.nsISupportsArray);
  654.             return 'Array';
  655.         } catch(e) {}
  656.         
  657.         try {
  658.             obj.QueryInterface(Components.interfaces.nsIDictionary);
  659.             return 'Dictionary';
  660.         } catch(e) {}
  661.         
  662.         // Not a supported type
  663.         return 'Unknown';
  664.     },
  665.  
  666.     /* Parse the XML-RPC response */
  667.     // Only on Sync calls.
  668.     _parseResponse: function(stream, length) {
  669.         debug('Creating parser ... ');
  670.         var parser = new SimpleXMLParser(stream, length);
  671.         parser.setDocumentHandler(this);
  672.  
  673.         // Make sure state is clean
  674.         this._valueStack = [];
  675.         this._currValue = null;
  676.         this._cdata = null;
  677.         this._foundFault = false;
  678.         
  679.         debug('Cranking up the parser');
  680.         parser.parse(length);
  681.  
  682.         if (this._foundFault) {
  683.             try {
  684.                 this._fault = createInstance(XMLRPCFAULT_CONTRACTID,
  685.                     'nsIXmlRpcFault');
  686.                 this._fault.init(this._result.getValue('faultCode').data,
  687.                     this._result.getValue('faultString').data);
  688.                 this._result = null;
  689.             } catch(e) {
  690.                 this._fault = null;
  691.                 this._result = null;
  692.                 throw Components.Exception('Could not parse response');
  693.             }
  694.         }
  695.     },
  696.  
  697.     // Response parsing state
  698.     _valueStack: [],
  699.     _currValue: null,
  700.     _cdata: null,
  701.  
  702.     /* SAX documentHandler interface (well, sorta) */
  703.     characters: function(chars) {
  704.         if (DEBUGPARSE) debug('character data: ' + chars);
  705.         if (this._cdata == null) return;
  706.         this._cdata += chars;
  707.     },
  708.  
  709.     startElement: function(name) {
  710.         if (DEBUGPARSE) debug('Start element ' + name);
  711.         switch (name) {
  712.             case 'fault':
  713.                 this._foundFault = true;
  714.                 break;
  715.  
  716.             case 'value':
  717.                 var val = new Value();
  718.                 this._valueStack.push(val);
  719.                 this._currValue = val;
  720.                 this._cdata = '';
  721.                 break;
  722.  
  723.             case 'name':
  724.                 this._cdata = '';
  725.                 break;
  726.  
  727.             case 'i4':
  728.             case 'int':
  729.                 this._currValue.type = this.INT;
  730.                 break;
  731.  
  732.             case 'boolean':
  733.                 this._currValue.type = this.BOOLEAN;
  734.                 break;
  735.  
  736.             case 'double':
  737.                 this._currValue.type = this.DOUBLE;
  738.                 break;
  739.  
  740.             case 'dateTime.iso8601':
  741.                 this._currValue.type = this.DATETIME;
  742.                 break;
  743.  
  744.             case 'base64':
  745.                 this._currValue.type = this.BASE64;
  746.                 break;
  747.  
  748.             case 'struct':
  749.                 this._currValue.type = this.STRUCT;
  750.                 break;
  751.  
  752.             case 'array':
  753.                 this._currValue.type = this.ARRAY;
  754.                 break;
  755.         }
  756.     },
  757.  
  758.     endElement: function(name) {
  759.         if (DEBUGPARSE) debug('End element ' + name);
  760.         switch (name) {
  761.             case 'value':
  762.                 // take cdata and put it in this value;
  763.                 if (this._currValue.type != this.ARRAY &&
  764.                     this._currValue.type != this.STRUCT) {
  765.                     this._currValue.value = this._cdata;
  766.                     this._cdata = null;
  767.                 }
  768.  
  769.                 // Find out if this is the end value
  770.                 // Note that we treat a struct differently, see 'member'
  771.                 var depth = this._valueStack.length;
  772.                 if (depth < 2 || 
  773.                     this._valueStack[depth - 2].type != this.STRUCT) {
  774.                     var val = this._currValue;
  775.                     this._valueStack.pop();
  776.  
  777.                     if (depth < 2) {
  778.                         if (DEBUG) debug('Found result');
  779.                         // This is the top level object
  780.                         this._result = val.value;
  781.                         this._currValue = null;
  782.                     } else {
  783.                         // This is an array element. Add it.
  784.                         this._currValue = 
  785.                             this._valueStack[this._valueStack.length - 1];
  786.                         this._currValue.appendValue(val.value);
  787.                     }
  788.                 }
  789.                 break;
  790.                 
  791.             case 'member':
  792.                 var val = this._currValue;
  793.                 this._valueStack.pop();
  794.                 this._currValue = this._valueStack[this._valueStack.length - 1];
  795.                 this._currValue.appendValue(val.value);
  796.                 break;
  797.             
  798.             case 'name':
  799.                 this._currValue.name = this._cdata;
  800.                 this._cdata = null;
  801.                 break;
  802.         }
  803.     }
  804. };
  805.  
  806. /* The XMLWriter class constructor */
  807. function XMLWriter() {
  808.     // We assume for now that all data is already in ISO-8859-1.
  809.     this.data = '<?xml version="1.0" encoding="ISO-8859-1"?>\n';
  810. }
  811.  
  812. /* The XMLWriter class def */
  813. XMLWriter.prototype = {
  814.     data: '',
  815.     
  816.     startElement: function(element) {
  817.         this.data += '<' + element + '>';
  818.     },
  819.  
  820.     endElement: function(element) {
  821.         this.data += '</' + element + '>';
  822.     },
  823.     
  824.     write: function(text) {
  825.         for (var i in text) {
  826.             var c = text.charAt(i);
  827.             switch (c) {
  828.                 case '<':
  829.                     this.data += '<';
  830.                     break;
  831.                 case '&':
  832.                     this.data += '&';
  833.                     break;
  834.                 default:
  835.                     this.data += c;
  836.             }
  837.         }
  838.     },
  839.  
  840.     markup: function(text) { this.data += text }
  841. };
  842.  
  843. /* The Value class contructor */
  844. function Value() { this.type = this.STRING; };
  845.  
  846. /* The Value class def */
  847. Value.prototype = {
  848.     INT:      nsXmlRpcClient.prototype.INT,
  849.     BOOLEAN:  nsXmlRpcClient.prototype.BOOLEAN,
  850.     STRING:   nsXmlRpcClient.prototype.STRING,
  851.     DOUBLE:   nsXmlRpcClient.prototype.DOUBLE,
  852.     DATETIME: nsXmlRpcClient.prototype.DATETIME,
  853.     ARRAY:    nsXmlRpcClient.prototype.ARRAY,
  854.     STRUCT:   nsXmlRpcClient.prototype.STRUCT,
  855.     BASE64:   nsXmlRpcClient.prototype.BASE64,
  856.     
  857.     _createType: nsXmlRpcClient.prototype.createType,
  858.  
  859.     name: null,
  860.     
  861.     _value: null,
  862.     get value() { return this._value; },
  863.     set value(val) {
  864.         // accepts [0-9]+ or x[0-9a-fA-F]+ and returns the character.
  865.         function entityTrans(substr, code) {
  866.             return String.fromCharCode("0" + code);
  867.         }
  868.         
  869.         switch (this.type) {
  870.             case this.STRING:
  871.                 val = val.replace(/&#([0-9]+);/g, entityTrans);
  872.                 val = val.replace(/&#(x[0-9a-fA-F]+);/g, entityTrans);
  873.                 val = val.replace(/</g, '<');
  874.                 val = val.replace(/>/g, '>');
  875.                 val = val.replace(/&/g, '&');
  876.                 this._value.data = val;
  877.                 break;
  878.         
  879.             case this.BOOLEAN:
  880.                 this._value.data = (val == 1);
  881.                 break;
  882.  
  883.             case this.DATETIME:
  884.                 this._value.data = Date.UTC(val.slice(0, 4), 
  885.                     val.slice(4, 6) - 1, val.slice(6, 8), val.slice(9, 11),
  886.                     val.slice(12, 14), val.slice(15));
  887.                 break;
  888.  
  889.             case this.BASE64:
  890.                 this._value.data = base64ToString(val);
  891.                 break;
  892.  
  893.             default:
  894.                 this._value.data = val;
  895.         }
  896.     },
  897.  
  898.     _type: null,
  899.     get type() { return this._type; },
  900.     set type(type) { 
  901.         this._type = type;
  902.         if (type == this.BASE64) 
  903.             this._value = this._createType(this.STRING, {});
  904.         else this._value = this._createType(type, {});
  905.     },
  906.  
  907.     appendValue: function(val) {
  908.         switch (this.type) {
  909.             case this.ARRAY:
  910.                 this.value.AppendElement(val);
  911.                 break;
  912.  
  913.             case this.STRUCT:
  914.                 this.value.setValue(this.name, val);
  915.                 break;
  916.         }
  917.     }
  918. };
  919.  
  920. /* The SimpleXMLParser class constructor 
  921.  * This parser is specific to the XML-RPC format!
  922.  * It assumes tags without arguments, in lowercase.
  923.  */
  924. function SimpleXMLParser(instream, contentLength) {
  925.     this._stream = new PushbackInputStream(instream);
  926.     this._maxlength = contentLength;
  927. }
  928.  
  929. /* The SimpleXMLParser class def */
  930. SimpleXMLParser.prototype = {
  931.     _stream: null,
  932.     _docHandler: null,
  933.     _bufferSize: 256,
  934.     _parsed: 0,
  935.     _maxlength: 0,
  936.     _window: 0, // When async on big documents, release after windowsize.
  937.  
  938.     setDocumentHandler: function(handler) { this._docHandler = handler; },
  939.  
  940.     parse: function(windowsize) {
  941.         this._window += windowsize;
  942.         
  943.         this._start();
  944.     },
  945.  
  946.     // Guard maximum length
  947.     _read: function(length) {
  948.         length = Math.min(this._available(), length);
  949.         if (!length) return '';
  950.         var read = this._stream.read(length);
  951.         this._parsed += read.length;
  952.         return read;
  953.     },
  954.     _unread: function(data) {
  955.         this._stream.unread(data);
  956.         this._parsed -= data.length;
  957.     },
  958.     _available: function() {
  959.         return Math.min(this._stream.available(), this._maxAvailable());
  960.     },
  961.     _maxAvailable: function() { return this._maxlength - this._parsed; },
  962.     
  963.     // read length characters from stream, block until we get them.
  964.     _blockingRead: function(length) {
  965.         length = Math.min(length, this._maxAvailable());
  966.         if (!length) return '';
  967.         var read = '';
  968.         while (read.length < length) read += this._read(length - read.length);
  969.         return read;
  970.     },
  971.  
  972.     // read until the the 'findChar' character appears in the stream.
  973.     // We read no more than _bufferSize characters, and return what we have
  974.     // found so far, but no more than up to 'findChar' if found.
  975.     _readUntil: function(findChar) {
  976.         var read = this._blockingRead(this._bufferSize);
  977.         var pos = read.indexOf(findChar.charAt(0));
  978.         if (pos > -1) {
  979.             this._unread(read.slice(pos + 1));
  980.             return read.slice(0, pos + 1);
  981.         }
  982.         return read;
  983.     },
  984.  
  985.     // Skip stream until string end is found.
  986.     _skipUntil: function(end) {
  987.         var read = '';
  988.         while (this._maxAvailable()) {
  989.             read += this._readUntil(end.charAt(0)) + 
  990.                 this._blockingRead(end.length - 1);
  991.             var pos = read.indexOf(end);
  992.             if (pos > -1) {
  993.                 this._unread(read.slice(pos + end.length));
  994.                 return;
  995.             }
  996.             read = read.slice(-(end.length)); // make sure don't miss our man.
  997.         }
  998.         return;
  999.     },
  1000.  
  1001.     _buff: '',
  1002.     // keep track of whitespce, so's we can discard it.
  1003.     _killLeadingWS: false,
  1004.     _trailingWS: '',
  1005.     
  1006.     _start: function() {
  1007.         // parse until exhausted. Note that we only look at a window
  1008.         // of max. this._bufferSize. Also, parsing of comments, PI's and
  1009.         // CDATA isn't as solid as it could be. *shrug*, XML-RPC responses
  1010.         // are 99.99% of the time generated anyway.
  1011.         // We don't check well-formedness either. Errors in tags will
  1012.         // be caught at the doc handler.
  1013.         ParseLoop: while (this._maxAvailable() || this._buff) {
  1014.             // Check for window size. We stop parsing until more comes
  1015.             // available (only in async parsing).
  1016.             if (this._window < this._maxlength && 
  1017.                 this._parsed >= this._window) 
  1018.                 return;
  1019.         
  1020.             this._buff += this._read(this._bufferSize - this._buff.length);
  1021.             this._buff = this._buff.replace('\r\n', '\n');
  1022.             this._buff = this._buff.replace('\r', '\n');
  1023.             
  1024.             var startTag = this._buff.indexOf('<');
  1025.             if (startTag > -1) {
  1026.                 if (startTag > 0) { // We have character data.
  1027.                     var chars = this._buff.slice(0, startTag);
  1028.                     chars = chars.replace(/[ \t\n]*$/, '');
  1029.                     if (chars && this._killLeadingWS)
  1030.                         chars = chars.replace(/^[ \t\n]*/, '');
  1031.                     if (chars) {
  1032.                         // Any whitespace previously marked as trailing is in
  1033.                         // fact in the middle. Prepend.
  1034.                         chars = this._trailingWS + chars;
  1035.                         this._docHandler.characters(chars);
  1036.                     }
  1037.                     this._buff = this._buff.slice(startTag);
  1038.                     this._trailingWS = '';
  1039.                     this._killLeadingWS = false;
  1040.                 }
  1041.  
  1042.                 // Check for a PI
  1043.                 if (this._buff.charAt(1) == '?') {
  1044.                     var endTag = this._buff.indexOf('?>');
  1045.                     if (endTag > -1) this._buff = this._buff.slice(endTag + 2);
  1046.                     else {
  1047.                         // Make sure we don't miss '?' at the end of the buffer
  1048.                         this._unread(this._buff.slice(-1));
  1049.                         this._buff = '';
  1050.                         this._skipUntil('?>');
  1051.                     }
  1052.                     this._killLeadingWS = true;
  1053.                     continue;
  1054.                 }
  1055.  
  1056.                 // Check for a comment
  1057.                 if (this._buff.slice(0, 4) == '<!--') {
  1058.                     var endTag = this._buff.indexOf('-->');
  1059.                     if (endTag > -1) this._buff = this._buff.slice(endTag + 3);
  1060.                     else {
  1061.                         // Make sure we don't miss '--' at the end of the buffer
  1062.                         this._unread(this._buff.slice(-2));
  1063.                         this._buff = '';
  1064.                         this._skipUntil('-->');
  1065.                     }
  1066.                     this._killLeadingWS = true;
  1067.                     continue;
  1068.                 }
  1069.  
  1070.                 // Check for CDATA
  1071.                 // We only check the first four characters. Anything longer and
  1072.                 // we'd miss it and it would be recognized as a corrupt element
  1073.                 // Anything shorter will be missed by the element scanner as
  1074.                 // well. Next loop we'll have more characters to do a better
  1075.                 // match.
  1076.                 if (this._buff.slice(0, 4) == '<![C') {
  1077.                     // We need to be sure. If we have less than
  1078.                     // 9 characters in the buffer, we can't _be_ sure.
  1079.                     if (this._buff.length < 9 && this._maxAvailable()) continue;
  1080.  
  1081.                     if (this._buff.slice(0, 9) != '<![CDATA[')
  1082.                         throw Components.Exception('Error parsing response');
  1083.                     
  1084.                     var endTag = this._buff.indexOf(']]>');
  1085.                     if (endTag > -1) {
  1086.                         this._buff = this._buff.slice(endTag + 3);
  1087.                         this._docHandler.characters(this._buff.slice(9, 
  1088.                             endTag));
  1089.                         this._killLeadingWS = true;
  1090.                         continue;
  1091.                     }  
  1092.                     
  1093.                     // end not in stream. Hrmph
  1094.                     this._docHandler.characters(this._buff.slice(9));
  1095.                     this._buff = '';
  1096.                     while(this._maxAvailable()) {
  1097.                         this._buff += this._readUntil(']') +
  1098.                             this._blockingRead(2);
  1099.                         // Find end.
  1100.                         var pos = this._buff.indexOf(']]>');
  1101.                         // Found.
  1102.                         if (pos > -1) {
  1103.                             this._docHandler.characters(this._buff.slice(0, 
  1104.                                 pos));
  1105.                             this._buff = this._buff.slice(pos + 3);
  1106.                             this._killLeadingWS = true;
  1107.                             continue ParseLoop;
  1108.                         }
  1109.                         // Not yet found. Last 2 chars could be part of end.
  1110.                         this._docHandler.characters(this._buff.slice(0, -2));
  1111.                         this._buff = this._buff.slice(-2); 
  1112.                     }
  1113.  
  1114.                     if (this._buff) // Uhoh. No ]]> found before EOF.
  1115.                         throw Components.Exception('Error parsing response');
  1116.  
  1117.                     continue;
  1118.                 }
  1119.  
  1120.                 // Check for a DOCTYPE decl.
  1121.                 if (this._buff.slice(0, 4) == '<!DO') {
  1122.                     if (this._buff.length < 9 && this.maxAvailable()) continue;
  1123.  
  1124.                     if (this._buff.slice(0, 9) != '<!DOCTYPE')
  1125.                         throw Components.Exception('Error parsing response');
  1126.                     
  1127.                     // Look for markup decl.
  1128.                     var startBrace = this._buff.indexOf('[');
  1129.                     if (startBrace > -1) {
  1130.                         this._unread(this._buff.slice(startBrace + 1));
  1131.                         this._buff = '';
  1132.                         this._skipUntil(']');
  1133.                         this._skipUntil('>');
  1134.                         this._killLeadingWS = true;
  1135.                         continue;
  1136.                     }
  1137.  
  1138.                     var endTag = this._buff.indexOf('>');
  1139.                     if (endTag > -1) {
  1140.                         this._buff = this._buff.slice(endTag + 1);
  1141.                         this._killLeadingWS = true;
  1142.                         continue;
  1143.                     }
  1144.  
  1145.                     this._buff = '';
  1146.                     while(this._available()) {
  1147.                         this._buff = this._readUntil('>');
  1148.  
  1149.                         var startBrace = this._buff.indexOf('[');
  1150.                         if (startBrace > -1) {
  1151.                             this._unread(this._buff.slice(startBrace + 1));
  1152.                             this._buff = '';
  1153.                             this._skipUntil(']');
  1154.                             this._skipUntil('>');
  1155.                             this._killLeadingWS = true;
  1156.                             continue ParseLoop;
  1157.                         }
  1158.  
  1159.                         var endTag = this._buff.indexOf('>');
  1160.                         if (endTag > -1) {
  1161.                             this._buff = this._buff.slice(pos + 1);
  1162.                             this._killLeadingWS = true;
  1163.                             continue;
  1164.                         }
  1165.                     }
  1166.  
  1167.                     if (this._buff)
  1168.                         throw Components.Exception('Error parsing response');
  1169.  
  1170.                     continue;
  1171.                 }
  1172.             
  1173.                 var endTag = this._buff.indexOf('>');
  1174.                 if (endTag > -1) {
  1175.                     var tag = this._buff.slice(1, endTag);
  1176.                     this._buff = this._buff.slice(endTag + 1);
  1177.                     tag = tag.replace(/[ \t\n]+.*?(\/?)$/, '$1');
  1178.  
  1179.                     // XML-RPC tags are pretty simple.
  1180.                     if (/[^a-zA-Z0-9.\/]/.test(tag))
  1181.                         throw Components.Exception('Error parsing response');
  1182.  
  1183.                     // Determine start and/or end tag.
  1184.                     if (tag.charAt(tag.length - 1) == '/') {
  1185.                         this._docHandler.startElement(tag.slice(0, -1));
  1186.                         this._docHandler.endElement(tag.slice(0, -1));
  1187.                     } else if (tag.charAt(0) == '/') {
  1188.                         this._docHandler.endElement(tag.slice(1));
  1189.                     } else {
  1190.                         this._docHandler.startElement(tag);
  1191.                     }
  1192.                     this._killLeadingWS = true; 
  1193.                 } else {
  1194.                     // No end tag. Check for window size to avoid an endless
  1195.                     // loop here.. hackish, I know, but if we get here this is
  1196.                     // not a XML-RPC request..
  1197.                     if (this._buff.length >= this._bufferSize)
  1198.                         throw Components.Exception('Error parsing response');
  1199.                     // If we get here and all what is to be read has
  1200.                     // been readinto the buffer, we have an incomplete stream.
  1201.                     if (!this._maxAvailable())
  1202.                         throw Components.Exception('Error parsing response');
  1203.                 }
  1204.             } else {
  1205.                 if (this._killLeadingWS) {
  1206.                     this._buff = this._buff.replace(/^[ \t\n]*/, '');
  1207.                     if (this._buff) this._killLeadingWS = false;
  1208.                 } else {
  1209.                     // prepend supposed trailing whitespace to the front.
  1210.                     this._buff = this._trailingWS + this._buff;
  1211.                     this._trailingWS = '';
  1212.                 }
  1213.  
  1214.                 // store trailing whitespace, and only hand it over
  1215.                 // the next time round. Unless we hit a tag, then we kill it
  1216.                 if (this._buff) {
  1217.                     this._trailingWS = this._buff.match(/[ \t\n]*$/);
  1218.                     this._buff = this._buff.replace(/[ \t\n]*$/, '');
  1219.                 }
  1220.  
  1221.                 if (this._buff) this._docHandler.characters(this._buff);
  1222.  
  1223.                 this._buff = '';
  1224.             }
  1225.         }
  1226.     }
  1227. };
  1228.                 
  1229.  
  1230. /* The PushbackInputStream class constructor */
  1231. function PushbackInputStream(stream) {
  1232.     this._stream = stream;
  1233. }
  1234.  
  1235. /* The PushbackInputStream class def */
  1236. PushbackInputStream.prototype = {
  1237.     _stream: null,
  1238.     _read_characters: '',
  1239.  
  1240.     available: function() {
  1241.         return this._read_characters.length + this._stream.available();
  1242.     },
  1243.  
  1244.     read: function(length) {
  1245.         if (this._read_characters.length >= length) {
  1246.             var read = this._read_characters.slice(0, length);
  1247.             this._read_characters = this._read_characters.slice(length);
  1248.             return read;
  1249.         } else {
  1250.             var read = this._read_characters;
  1251.             this._read_characters = '';
  1252.             return read + this._stream.read(length - read.length);
  1253.         }
  1254.     },
  1255.  
  1256.     unread: function(chars) { 
  1257.         this._read_characters = chars + this._read_characters;
  1258.     }
  1259. };
  1260.             
  1261. /*
  1262.  * Objects
  1263.  */
  1264.  
  1265. /* nsXmlRpcClient Module (for XPCOM registration) */
  1266. var nsXmlRpcClientModule = {
  1267.     registerSelf: function(compMgr, fileSpec, location, type) {
  1268.         compMgr.registerComponentWithType(XMLRPCCLIENT_CID, 
  1269.             'XML-RPC Client JS component', XMLRPCCLIENT_CONTRACTID, fileSpec,
  1270.             location, true, true, type);
  1271.         compMgr.registerComponentWithType(XMLRPCFAULT_CID, 
  1272.             'XML-RPC Fault JS component', XMLRPCFAULT_CONTRACTID, fileSpec,
  1273.             location, true, true, type);
  1274.     },
  1275.  
  1276.     getClassObject: function(compMgr, cid, iid) {
  1277.         if (!cid.equals(XMLRPCCLIENT_CID) && !cid.equals(XMLRPCFAULT_CID))
  1278.             throw Components.results.NS_ERROR_NO_INTERFACE;
  1279.  
  1280.         if (!iid.equals(Components.interfaces.nsIFactory))
  1281.             throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  1282.  
  1283.         if (cid.equals(XMLRPCCLIENT_CID))
  1284.             return nsXmlRpcClientFactory
  1285.         else return nsXmlRpcFaultFactory;
  1286.     },
  1287.  
  1288.     canUnload: function(compMgr) { return true; }
  1289. };
  1290.  
  1291. /* nsXmlRpcClient Class Factory */
  1292. var nsXmlRpcClientFactory = {
  1293.     createInstance: function(outer, iid) {
  1294.         if (outer != null)
  1295.             throw Components.results.NS_ERROR_NO_AGGREGATION;
  1296.     
  1297.         if (!iid.equals(XMLRPCCLIENT_IID) &&
  1298.             !iid.equals(Components.interfaces.nsISupports))
  1299.             throw Components.results.NS_ERROR_INVALID_ARG;
  1300.  
  1301.         return new nsXmlRpcClient();
  1302.     }
  1303. }
  1304.  
  1305. /* nsXmlRpcFault Class Factory */
  1306. var nsXmlRpcFaultFactory = {
  1307.     createInstance: function(outer, iid) {
  1308.         if (outer != null)
  1309.             throw Components.results.NS_ERROR_NO_AGGREGATION;
  1310.  
  1311.         if (!iid.equals(XMLRPCFAULT_IID) &&
  1312.             !iid.equals(Components.interfaces.nsISupports))
  1313.             throw Components.results.NS_ERROR_INVALID_ARG;
  1314.  
  1315.         return new nsXmlRpcFault();
  1316.     }
  1317. }
  1318.  
  1319. /*
  1320.  * Functions
  1321.  */
  1322.  
  1323. /* module initialisation */
  1324. function NSGetModule(comMgr, fileSpec) { return nsXmlRpcClientModule; }
  1325.  
  1326. /* Create an instance of the given ContractID, with given interface */
  1327. function createInstance(contractId, intf) {
  1328.     return Components.classes[contractId]
  1329.         .createInstance(Components.interfaces[intf]);
  1330. }
  1331.  
  1332. /* Get a pointer to a service indicated by the ContractID, with given interface */
  1333. function getService(contractId, intf) {
  1334.     return Components.classes[contractId].getService(Components.interfaces[intf]);
  1335. }
  1336.  
  1337. /* Convert an inputstream to a scriptable inputstream */
  1338. function toScriptableStream(input) {
  1339.     var SIStream = Components.Constructor(
  1340.         '@mozilla.org/scriptableinputstream;1',
  1341.         'nsIScriptableInputStream', 'init');
  1342.     return new SIStream(input);
  1343. }
  1344.  
  1345. /* format a Date object into a iso8601 datetime string, UTC time */
  1346. function iso8601Format(date) {
  1347.     var datetime = date.getUTCFullYear();
  1348.     var month = date.getUTCMonth() + 1;
  1349.     datetime += (month < 10 ?  '0' + month : month);
  1350.     var day = date.getUTCDate();
  1351.     datetime += (day < 10 ? '0' + day : day);
  1352.  
  1353.     datetime += 'T';
  1354.  
  1355.     var hour = date.getUTCHours();
  1356.     datetime += (hour < 10 ? '0' + hour : hour) + ':';
  1357.     var minutes = date.getUTCMinutes();
  1358.     datetime += (minutes < 10 ? '0' + minutes : minutes) + ':';
  1359.     var seconds = date.getUTCSeconds();
  1360.     datetime += (seconds < 10 ? '0' + seconds : seconds);
  1361.  
  1362.     return datetime;
  1363. }
  1364.  
  1365. /* Convert a stream to Base64, writing it away to a string writer */
  1366. const BASE64CHUNK = 255; // Has to be devidable by 3!!
  1367. function streamToBase64(stream, writer) {
  1368.     while (stream.available()) {
  1369.         var data = [];
  1370.         while (data.length < BASE64CHUNK && stream.available()) {
  1371.             var d = stream.read(1).charCodeAt(0);
  1372.             // reading a 0 results in NaN, compensate.
  1373.             data = data.concat(isNaN(d) ? 0 : d);
  1374.         }
  1375.         writer.write(toBase64(data));
  1376.     }
  1377. }
  1378.  
  1379. /* Convert data (an array of integers) to a Base64 string. */
  1380. const toBase64Table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' +
  1381.     '0123456789+/';
  1382. const base64Pad = '=';
  1383. function toBase64(data) {
  1384.     var result = '';
  1385.     var length = data.length;
  1386.     // Convert every three bytes to 4 ascii characters.
  1387.     for (var i = 0; i < (length - 2); i += 3) {
  1388.         result += toBase64Table[data[i] >> 2];
  1389.         result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
  1390.         result += toBase64Table[((data[i+1] & 0x0f) << 2) + (data[i+2] >> 6)];
  1391.         result += toBase64Table[data[i+2] & 0x3f];
  1392.     }
  1393.  
  1394.     // Convert the remaining 1 or 2 bytes, pad out to 4 characters.
  1395.     if (length%3) {
  1396.         var i = length - (length%3);
  1397.         result += toBase64Table[data[i] >> 2];
  1398.         if ((length%3) == 2) {
  1399.             result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
  1400.             result += toBase64Table[(data[i+1] & 0x0f) << 2];
  1401.             result += base64Pad;
  1402.         } else {
  1403.             result += toBase64Table[(data[i] & 0x03) << 4];
  1404.             result += base64Pad + base64Pad;
  1405.         }
  1406.     }
  1407.  
  1408.     return result;
  1409. }
  1410.  
  1411. /* Convert Base64 data to a string */
  1412. const toBinaryTable = [
  1413.     -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
  1414.     -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
  1415.     -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
  1416.     52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1, 0,-1,-1,
  1417.     -1, 0, 1, 2,  3, 4, 5, 6,  7, 8, 9,10, 11,12,13,14,
  1418.     15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,
  1419.     -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
  1420.     41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
  1421. ];
  1422. function base64ToString(data) {
  1423.     var result = '';
  1424.     var leftbits = 0; // number of bits decoded, but yet to be appended
  1425.     var leftdata = 0; // bits decoded, bt yet to be appended
  1426.  
  1427.     // Convert one by one.
  1428.     for (var i in data) {
  1429.         var c = toBinaryTable[data.charCodeAt(i) & 0x7f];
  1430.         var padding = (data[i] == base64Pad);
  1431.         // Skip illegal characters and whitespace
  1432.         if (c == -1) continue;
  1433.         
  1434.         // Collect data into leftdata, update bitcount
  1435.         leftdata = (leftdata << 6) | c;
  1436.         leftbits += 6;
  1437.  
  1438.         // If we have 8 or more bits, append 8 bits to the result
  1439.         if (leftbits >= 8) {
  1440.             leftbits -= 8;
  1441.             // Append if not padding.
  1442.             if (!padding)
  1443.                 result += String.fromCharCode((leftdata >> leftbits) & 0xff);
  1444.             leftdata &= (1 << leftbits) - 1;
  1445.         }
  1446.     }
  1447.  
  1448.     // If there are any bits left, the base64 string was corrupted
  1449.     if (leftbits)
  1450.         throw Components.Exception('Corrupted base64 string');
  1451.  
  1452.     return result;
  1453. }
  1454.  
  1455. if (DEBUG) debug = function(msg) { 
  1456.     dump(' -- XML-RPC client -- : ' + msg + '\n'); 
  1457. };
  1458. else debug = function() {}
  1459.  
  1460. // vim:sw=4:sr:sta:et:sts:
  1461.