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