home *** CD-ROM | disk | FTP | other *** search
/ PC World 2003 July & August / PCWorld_2003-07-08_cd.bin / Komunik / firebird / MozillaFirebird-0.6-win32.exe / MozillaFirebird / components / nsXmlRpcClient.js < prev    next >
Text File  |  2003-03-25  |  49KB  |  1,405 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.33 $
  24.  *
  25.  *  $Id: nsXmlRpcClient.js,v 1.33 2003/03/26 01:37:02 suresh%netscape.com 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.         Components.returnCode = Components.results.NS_ERROR_NO_INTERFACE;
  411.         return null;
  412.     },
  413.  
  414.     // nsIAuthPrompt interface
  415.     _passwordTried: false,
  416.     promptUsernameAndPassword: function(dialogTitle, text, passwordRealm,
  417.                                         savePassword, user, pwd){
  418.  
  419.         if (this._useAuth){
  420.             if (this._passwordTried){
  421.                 try { 
  422.                     this._listener.onError(this, ctxt, 
  423.                         Components.results.NS_ERROR_FAIL, 
  424.                         'Server returned invalid Fault');
  425.                 }
  426.                 catch(ex) {
  427.                     debug('Exception in listener.onError: ' + ex);
  428.                 }
  429.                 return false;
  430.             }
  431.             user.value = this._username;
  432.             pwd.value = this._password;
  433.             this._passwordTried = true;
  434.             return true;
  435.         }
  436.         return false;
  437.     },
  438.  
  439.     /* Generate the XML-RPC request body */
  440.     _generateRequestBody: function(writer, methodName, methodArgs) {
  441.         writer.startElement('methodCall');
  442.  
  443.         writer.startElement('methodName');
  444.         writer.write(methodName);
  445.         writer.endElement('methodName');
  446.  
  447.         writer.startElement('params');
  448.         for (var i in methodArgs) {
  449.             writer.startElement('param');
  450.             this._generateArgumentBody(writer, methodArgs[i]);
  451.             writer.endElement('param');
  452.         }
  453.         writer.endElement('params');
  454.  
  455.         writer.endElement('methodCall');
  456.     },
  457.  
  458.     /* Write out a XML-RPC parameter value */
  459.     _generateArgumentBody: function(writer, obj) {
  460.         writer.startElement('value');
  461.         var sType = this._typeOf(obj);
  462.         switch (sType) {
  463.             case 'PRUint8':
  464.             case 'PRUint16':
  465.             case 'PRInt16':
  466.             case 'PRInt32':
  467.                 obj=obj.QueryInterface(Components.interfaces['nsISupports' +
  468.                     sType]);
  469.                 writer.startElement('i4');
  470.                 writer.write(obj.toString());
  471.                 writer.endElement('i4');
  472.                 break;
  473.  
  474.             case 'PRBool':
  475.                 obj=obj.QueryInterface(Components.interfaces.nsISupportsPRBool);
  476.                 writer.startElement('boolean');
  477.                 writer.write(obj.data ? '1' : '0');
  478.                 writer.endElement('boolean');
  479.                 break;
  480.  
  481.             case 'Char':
  482.             case 'CString':
  483.                 obj=obj.QueryInterface(Components.interfaces['nsISupports' +
  484.                     sType]);
  485.                 writer.startElement('string');
  486.                 writer.write(obj.toString());
  487.                 writer.endElement('string');
  488.                 break;
  489.  
  490.             case 'Float':
  491.             case 'Double':
  492.                 obj=obj.QueryInterface(Components.interfaces['nsISupports' +
  493.                     sType]);
  494.                 writer.startElement('double');
  495.                 writer.write(obj.toString());
  496.                 writer.endElement('double');
  497.                 break;
  498.  
  499.             case 'PRTime':
  500.                 obj = obj.QueryInterface(
  501.                     Components.interfaces.nsISupportsPRTime);
  502.                 var date = new Date(obj.data)
  503.                 writer.startElement('dateTime.iso8601');
  504.                 writer.write(iso8601Format(date));
  505.                 writer.endElement('dateTime.iso8601');
  506.                 break;
  507.                 
  508.             case 'InputStream':
  509.                 obj = obj.QueryInterface(Components.interfaces.nsIInputStream);
  510.                 obj = toScriptableStream(obj);
  511.                 writer.startElement('base64');
  512.                 streamToBase64(obj, writer);
  513.                 writer.endElement('base64');
  514.                 break;
  515.             
  516.             case 'Array':
  517.                 obj = obj.QueryInterface(
  518.                     Components.interfaces.nsISupportsArray);
  519.                 writer.startElement('array');
  520.                 writer.startElement('data');
  521.                 for (var i = 0; i < obj.Count(); i++)
  522.                     this._generateArgumentBody(writer, obj.GetElementAt(i));
  523.                 writer.endElement('data');
  524.                 writer.endElement('array');
  525.                 break;
  526.  
  527.             case 'Dictionary':
  528.                 obj = obj.QueryInterface(Components.interfaces.nsIDictionary);
  529.                 writer.startElement('struct');
  530.                 var keys = obj.getKeys({});
  531.                 for (var k in keys) {
  532.                     writer.startElement('member');
  533.                     writer.startElement('name');
  534.                     writer.write(keys[k]);
  535.                     writer.endElement('name');
  536.                     this._generateArgumentBody(writer, obj.getValue(keys[k]));
  537.                     writer.endElement('member');
  538.                 }
  539.                 writer.endElement('struct');
  540.                 break;
  541.  
  542.             default:
  543.                 throw Components.Exception('Unsupported argument', null, null,
  544.                     obj);
  545.         }
  546.  
  547.         writer.endElement('value');
  548.     },
  549.  
  550.     /* Determine type of a nsISupports primitive, array or dictionary. */
  551.     _typeOf: function(obj) {
  552.         // XPConnect alows JS to pass in anything, because we are a regular
  553.         // JS object to it. So we have to test rigorously.
  554.         if (typeof obj != 'object') return 'Unknown';
  555.  
  556.         // Anything else not nsISupports is not allowed.
  557.         if (typeof obj.QueryInterface != 'function') return 'Unknown';
  558.  
  559.         // Now we will have to eliminate by trying all possebilities.
  560.         try {
  561.             obj.QueryInterface(Components.interfaces.nsISupportsPRUint8);
  562.             return 'PRUint8';
  563.         } catch(e) {}
  564.         
  565.         try {
  566.             obj.QueryInterface(Components.interfaces.nsISupportsPRUint16);
  567.             return 'PRUint16';
  568.         } catch(e) {}
  569.         
  570.         try {
  571.             obj.QueryInterface(Components.interfaces.nsISupportsPRInt16);
  572.             return 'PRInt16';
  573.         } catch(e) {}
  574.         
  575.         try {
  576.             obj.QueryInterface(Components.interfaces.nsISupportsPRInt32);
  577.             return 'PRInt32';
  578.         } catch(e) {}
  579.         
  580.         try {
  581.             obj.QueryInterface(Components.interfaces.nsISupportsPRBool);
  582.             return 'PRBool';
  583.         } catch(e) {}
  584.         
  585.         try {
  586.             obj.QueryInterface(Components.interfaces.nsISupportsChar);
  587.             return 'Char';
  588.         } catch(e) {}
  589.         
  590.         try {
  591.             obj.QueryInterface(Components.interfaces.nsISupportsCString);
  592.             return 'CString';
  593.         } catch(e) {}
  594.         
  595.         try {
  596.             obj.QueryInterface(Components.interfaces.nsISupportsFloat);
  597.             return 'Float';
  598.         } catch(e) {}
  599.         
  600.         try {
  601.             obj.QueryInterface(Components.interfaces.nsISupportsDouble);
  602.             return 'Double';
  603.         } catch(e) {}
  604.         
  605.         try {
  606.             obj.QueryInterface(Components.interfaces.nsISupportsPRTime);
  607.             return 'PRTime';
  608.         } catch(e) {}
  609.         
  610.         try {
  611.             obj.QueryInterface(Components.interfaces.nsIInputStream);
  612.             return 'InputStream';
  613.         } catch(e) {}
  614.         
  615.         try {
  616.             obj.QueryInterface(Components.interfaces.nsISupportsArray);
  617.             return 'Array';
  618.         } catch(e) {}
  619.         
  620.         try {
  621.             obj.QueryInterface(Components.interfaces.nsIDictionary);
  622.             return 'Dictionary';
  623.         } catch(e) {}
  624.         
  625.         // Not a supported type
  626.         return 'Unknown';
  627.     },
  628.  
  629.     // Response parsing state
  630.     _valueStack: [],
  631.     _currValue: null,
  632.     _cdata: null,
  633.  
  634.     /* SAX documentHandler interface (well, sorta) */
  635.     characters: function(chars) {
  636.         if (DEBUGPARSE) debug('character data: ' + chars);
  637.         if (this._cdata == null) return;
  638.         this._cdata += chars;
  639.     },
  640.  
  641.     startElement: function(name) {
  642.         if (DEBUGPARSE) debug('Start element ' + name);
  643.         switch (name) {
  644.             case 'fault':
  645.                 this._foundFault = true;
  646.                 break;
  647.  
  648.             case 'value':
  649.                 var val = new Value();
  650.                 this._valueStack.push(val);
  651.                 this._currValue = val;
  652.                 this._cdata = '';
  653.                 break;
  654.  
  655.             case 'name':
  656.                 this._cdata = '';
  657.                 break;
  658.  
  659.             case 'i4':
  660.             case 'int':
  661.                 this._currValue.type = this.INT;
  662.                 break;
  663.  
  664.             case 'boolean':
  665.                 this._currValue.type = this.BOOLEAN;
  666.                 break;
  667.  
  668.             case 'double':
  669.                 this._currValue.type = this.DOUBLE;
  670.                 break;
  671.  
  672.             case 'dateTime.iso8601':
  673.                 this._currValue.type = this.DATETIME;
  674.                 break;
  675.  
  676.             case 'base64':
  677.                 this._currValue.type = this.BASE64;
  678.                 break;
  679.  
  680.             case 'struct':
  681.                 this._currValue.type = this.STRUCT;
  682.                 break;
  683.  
  684.             case 'array':
  685.                 this._currValue.type = this.ARRAY;
  686.                 break;
  687.         }
  688.     },
  689.  
  690.     endElement: function(name) {
  691.         var val;
  692.         if (DEBUGPARSE) debug('End element ' + name);
  693.         switch (name) {
  694.             case 'value':
  695.                 // take cdata and put it in this value;
  696.                 if (this._currValue.type != this.ARRAY &&
  697.                     this._currValue.type != this.STRUCT) {
  698.                     this._currValue.value = this._cdata;
  699.                     this._cdata = null;
  700.                 }
  701.  
  702.                 // Find out if this is the end value
  703.                 // Note that we treat a struct differently, see 'member'
  704.                 var depth = this._valueStack.length;
  705.                 if (depth < 2 || 
  706.                     this._valueStack[depth - 2].type != this.STRUCT) {
  707.                     val = this._currValue;
  708.                     this._valueStack.pop();
  709.  
  710.                     if (depth < 2) {
  711.                         if (DEBUG) debug('Found result');
  712.                         // This is the top level object
  713.                         this._result = val.value;
  714.                         this._currValue = null;
  715.                     } else {
  716.                         // This is an array element. Add it.
  717.                         this._currValue = 
  718.                             this._valueStack[this._valueStack.length - 1];
  719.                         this._currValue.appendValue(val.value);
  720.                     }
  721.                 }
  722.                 break;
  723.                 
  724.             case 'member':
  725.                 val = this._currValue;
  726.                 this._valueStack.pop();
  727.                 this._currValue = this._valueStack[this._valueStack.length - 1];
  728.                 this._currValue.appendValue(val.value);
  729.                 break;
  730.             
  731.             case 'name':
  732.                 this._currValue.name = this._cdata;
  733.                 this._cdata = null;
  734.                 break;
  735.         }
  736.     }
  737. };
  738.  
  739. /* The XMLWriter class constructor */
  740. function XMLWriter() {
  741.     // We assume for now that all data is already in ISO-8859-1.
  742.     this.data = '<?xml version="1.0" encoding="ISO-8859-1"?>';
  743. }
  744.  
  745. /* The XMLWriter class def */
  746. XMLWriter.prototype = {
  747.     data: '',
  748.     
  749.     startElement: function(element) {
  750.         this.data += '<' + element + '>';
  751.     },
  752.  
  753.     endElement: function(element) {
  754.         this.data += '</' + element + '>';
  755.     },
  756.     
  757.     write: function(text) {
  758.         for (var i = 0; i < text.length; i++) {
  759.             var c = text[i];
  760.             switch (c) {
  761.                 case '<':
  762.                     this.data += '<';
  763.                     break;
  764.                 case '&':
  765.                     this.data += '&';
  766.                     break;
  767.                 default:
  768.                     this.data += c;
  769.             }
  770.         }
  771.     },
  772.  
  773.     markup: function(text) { this.data += text }
  774. };
  775.  
  776. /* The Value class contructor */
  777. function Value() { this.type = this.STRING; };
  778.  
  779. /* The Value class def */
  780. Value.prototype = {
  781.     INT:      nsXmlRpcClient.prototype.INT,
  782.     BOOLEAN:  nsXmlRpcClient.prototype.BOOLEAN,
  783.     STRING:   nsXmlRpcClient.prototype.STRING,
  784.     DOUBLE:   nsXmlRpcClient.prototype.DOUBLE,
  785.     DATETIME: nsXmlRpcClient.prototype.DATETIME,
  786.     ARRAY:    nsXmlRpcClient.prototype.ARRAY,
  787.     STRUCT:   nsXmlRpcClient.prototype.STRUCT,
  788.     BASE64:   nsXmlRpcClient.prototype.BASE64,
  789.     
  790.     _createType: nsXmlRpcClient.prototype.createType,
  791.  
  792.     name: null,
  793.     
  794.     _value: null,
  795.     get value() { return this._value; },
  796.     set value(val) {
  797.         // accepts [0-9]+ or x[0-9a-fA-F]+ and returns the character.
  798.         function entityTrans(substr, code) {
  799.             return String.fromCharCode("0" + code);
  800.         }
  801.         
  802.         switch (this.type) {
  803.             case this.STRING:
  804.                 val = val.replace(/&#([0-9]+);/g, entityTrans);
  805.                 val = val.replace(/&#(x[0-9a-fA-F]+);/g, entityTrans);
  806.                 val = val.replace(/</g, '<');
  807.                 val = val.replace(/>/g, '>');
  808.                 val = val.replace(/&/g, '&');
  809.                 this._value.data = val;
  810.                 break;
  811.         
  812.             case this.BOOLEAN:
  813.                 this._value.data = (val == 1);
  814.                 break;
  815.  
  816.             case this.DATETIME:
  817.                 this._value.data = Date.UTC(val.slice(0, 4), 
  818.                     val.slice(4, 6) - 1, val.slice(6, 8), val.slice(9, 11),
  819.                     val.slice(12, 14), val.slice(15));
  820.                 break;
  821.  
  822.             case this.BASE64:
  823.                 this._value.data = base64ToString(val);
  824.                 break;
  825.  
  826.             default:
  827.                 this._value.data = val;
  828.         }
  829.     },
  830.  
  831.     _type: null,
  832.     get type() { return this._type; },
  833.     set type(type) { 
  834.         this._type = type;
  835.         if (type == this.BASE64) 
  836.             this._value = this._createType(this.STRING, {});
  837.         else this._value = this._createType(type, {});
  838.     },
  839.  
  840.     appendValue: function(val) {
  841.         switch (this.type) {
  842.             case this.ARRAY:
  843.                 this.value.AppendElement(val);
  844.                 break;
  845.  
  846.             case this.STRUCT:
  847.                 this.value.setValue(this.name, val);
  848.                 break;
  849.         }
  850.     }
  851. };
  852.  
  853. /* The SimpleXMLParser class constructor 
  854.  * This parser is specific to the XML-RPC format!
  855.  * It assumes tags without arguments, in lowercase.
  856.  */
  857. function SimpleXMLParser(instream, contentLength) {
  858.     this._stream = new PushbackInputStream(instream);
  859.     this._maxlength = contentLength;
  860. }
  861.  
  862. /* The SimpleXMLParser class def */
  863. SimpleXMLParser.prototype = {
  864.     _stream: null,
  865.     _docHandler: null,
  866.     _bufferSize: 256,
  867.     _parsed: 0,
  868.     _maxlength: 0,
  869.     _window: 0, // When async on big documents, release after windowsize.
  870.  
  871.     setDocumentHandler: function(handler) { this._docHandler = handler; },
  872.  
  873.     parse: function(windowsize) {
  874.         this._window += windowsize;
  875.         
  876.         this._start();
  877.     },
  878.  
  879.     // Guard maximum length
  880.     _read: function(length) {
  881.         length = Math.min(this._available(), length);
  882.         if (!length) return '';
  883.         var read = this._stream.read(length);
  884.         this._parsed += read.length;
  885.         return read;
  886.     },
  887.     _unread: function(data) {
  888.         this._stream.unread(data);
  889.         this._parsed -= data.length;
  890.     },
  891.     _available: function() {
  892.         return Math.min(this._stream.available(), this._maxAvailable());
  893.     },
  894.     _maxAvailable: function() { return this._maxlength - this._parsed; },
  895.     
  896.     // read length characters from stream, block until we get them.
  897.     _blockingRead: function(length) {
  898.         length = Math.min(length, this._maxAvailable());
  899.         if (!length) return '';
  900.         var read = '';
  901.         while (read.length < length) read += this._read(length - read.length);
  902.         return read;
  903.     },
  904.  
  905.     // read until the the 'findChar' character appears in the stream.
  906.     // We read no more than _bufferSize characters, and return what we have
  907.     // found so far, but no more than up to 'findChar' if found.
  908.     _readUntil: function(findChar) {
  909.         var read = this._blockingRead(this._bufferSize);
  910.         var pos = read.indexOf(findChar.charAt(0));
  911.         if (pos > -1) {
  912.             this._unread(read.slice(pos + 1));
  913.             return read.slice(0, pos + 1);
  914.         }
  915.         return read;
  916.     },
  917.  
  918.     // Skip stream until string end is found.
  919.     _skipUntil: function(end) {
  920.         var read = '';
  921.         while (this._maxAvailable()) {
  922.             read += this._readUntil(end.charAt(0)) + 
  923.                 this._blockingRead(end.length - 1);
  924.             var pos = read.indexOf(end);
  925.             if (pos > -1) {
  926.                 this._unread(read.slice(pos + end.length));
  927.                 return;
  928.             }
  929.             read = read.slice(-(end.length)); // make sure don't miss our man.
  930.         }
  931.         return;
  932.     },
  933.  
  934.     _buff: '',
  935.     // keep track of whitespce, so's we can discard it.
  936.     _killLeadingWS: false,
  937.     _trailingWS: '',
  938.     
  939.     _start: function() {
  940.         // parse until exhausted. Note that we only look at a window
  941.         // of max. this._bufferSize. Also, parsing of comments, PI's and
  942.         // CDATA isn't as solid as it could be. *shrug*, XML-RPC responses
  943.         // are 99.99% of the time generated anyway.
  944.         // We don't check well-formedness either. Errors in tags will
  945.         // be caught at the doc handler.
  946.         ParseLoop: while (this._maxAvailable() || this._buff) {
  947.             // Check for window size. We stop parsing until more comes
  948.             // available (only in async parsing).
  949.             if (this._window < this._maxlength && 
  950.                 this._parsed >= this._window) 
  951.                 return;
  952.         
  953.             this._buff += this._read(this._bufferSize - this._buff.length);
  954.             this._buff = this._buff.replace('\r\n', '\n');
  955.             this._buff = this._buff.replace('\r', '\n');
  956.             
  957.             var startTag = this._buff.indexOf('<');
  958.             var endTag;
  959.             if (startTag > -1) {
  960.                 if (startTag > 0) { // We have character data.
  961.                     var chars = this._buff.slice(0, startTag);
  962.                     chars = chars.replace(/[ \t\n]*$/, '');
  963.                     if (chars && this._killLeadingWS)
  964.                         chars = chars.replace(/^[ \t\n]*/, '');
  965.                     if (chars) {
  966.                         // Any whitespace previously marked as trailing is in
  967.                         // fact in the middle. Prepend.
  968.                         chars = this._trailingWS + chars;
  969.                         this._docHandler.characters(chars);
  970.                     }
  971.                     this._buff = this._buff.slice(startTag);
  972.                     this._trailingWS = '';
  973.                     this._killLeadingWS = false;
  974.                 }
  975.  
  976.                 // Check for a PI
  977.                 if (this._buff.charAt(1) == '?') {
  978.                     endTag = this._buff.indexOf('?>');
  979.                     if (endTag > -1) this._buff = this._buff.slice(endTag + 2);
  980.                     else {
  981.                         // Make sure we don't miss '?' at the end of the buffer
  982.                         this._unread(this._buff.slice(-1));
  983.                         this._buff = '';
  984.                         this._skipUntil('?>');
  985.                     }
  986.                     this._killLeadingWS = true;
  987.                     continue;
  988.                 }
  989.  
  990.                 // Check for a comment
  991.                 if (this._buff.slice(0, 4) == '<!--') {
  992.                     endTag = this._buff.indexOf('-->');
  993.                     if (endTag > -1) this._buff = this._buff.slice(endTag + 3);
  994.                     else {
  995.                         // Make sure we don't miss '--' at the end of the buffer
  996.                         this._unread(this._buff.slice(-2));
  997.                         this._buff = '';
  998.                         this._skipUntil('-->');
  999.                     }
  1000.                     this._killLeadingWS = true;
  1001.                     continue;
  1002.                 }
  1003.  
  1004.                 // Check for CDATA
  1005.                 // We only check the first four characters. Anything longer and
  1006.                 // we'd miss it and it would be recognized as a corrupt element
  1007.                 // Anything shorter will be missed by the element scanner as
  1008.                 // well. Next loop we'll have more characters to do a better
  1009.                 // match.
  1010.                 if (this._buff.slice(0, 4) == '<![C') {
  1011.                     // We need to be sure. If we have less than
  1012.                     // 9 characters in the buffer, we can't _be_ sure.
  1013.                     if (this._buff.length < 9 && this._maxAvailable()) continue;
  1014.  
  1015.                     if (this._buff.slice(0, 9) != '<![CDATA[')
  1016.                         throw Components.Exception('Error parsing response');
  1017.                     
  1018.                     endTag = this._buff.indexOf(']]>');
  1019.                     if (endTag > -1) {
  1020.                         this._buff = this._buff.slice(endTag + 3);
  1021.                         this._docHandler.characters(this._buff.slice(9, 
  1022.                             endTag));
  1023.                         this._killLeadingWS = true;
  1024.                         continue;
  1025.                     }  
  1026.                     
  1027.                     // end not in stream. Hrmph
  1028.                     this._docHandler.characters(this._buff.slice(9));
  1029.                     this._buff = '';
  1030.                     while(this._maxAvailable()) {
  1031.                         this._buff += this._readUntil(']') +
  1032.                             this._blockingRead(2);
  1033.                         // Find end.
  1034.                         var pos = this._buff.indexOf(']]>');
  1035.                         // Found.
  1036.                         if (pos > -1) {
  1037.                             this._docHandler.characters(this._buff.slice(0, 
  1038.                                 pos));
  1039.                             this._buff = this._buff.slice(pos + 3);
  1040.                             this._killLeadingWS = true;
  1041.                             continue ParseLoop;
  1042.                         }
  1043.                         // Not yet found. Last 2 chars could be part of end.
  1044.                         this._docHandler.characters(this._buff.slice(0, -2));
  1045.                         this._buff = this._buff.slice(-2); 
  1046.                     }
  1047.  
  1048.                     if (this._buff) // Uhoh. No ]]> found before EOF.
  1049.                         throw Components.Exception('Error parsing response');
  1050.  
  1051.                     continue;
  1052.                 }
  1053.  
  1054.                 // Check for a DOCTYPE decl.
  1055.                 if (this._buff.slice(0, 4) == '<!DO') {
  1056.                     if (this._buff.length < 9 && this.maxAvailable()) continue;
  1057.  
  1058.                     if (this._buff.slice(0, 9) != '<!DOCTYPE')
  1059.                         throw Components.Exception('Error parsing response');
  1060.                     
  1061.                     // Look for markup decl.
  1062.                     var startBrace = this._buff.indexOf('[');
  1063.                     if (startBrace > -1) {
  1064.                         this._unread(this._buff.slice(startBrace + 1));
  1065.                         this._buff = '';
  1066.                         this._skipUntil(']');
  1067.                         this._skipUntil('>');
  1068.                         this._killLeadingWS = true;
  1069.                         continue;
  1070.                     }
  1071.  
  1072.                     endTag = this._buff.indexOf('>');
  1073.                     if (endTag > -1) {
  1074.                         this._buff = this._buff.slice(endTag + 1);
  1075.                         this._killLeadingWS = true;
  1076.                         continue;
  1077.                     }
  1078.  
  1079.                     this._buff = '';
  1080.                     while(this._available()) {
  1081.                         this._buff = this._readUntil('>');
  1082.  
  1083.                         startBrace = this._buff.indexOf('[');
  1084.                         if (startBrace > -1) {
  1085.                             this._unread(this._buff.slice(startBrace + 1));
  1086.                             this._buff = '';
  1087.                             this._skipUntil(']');
  1088.                             this._skipUntil('>');
  1089.                             this._killLeadingWS = true;
  1090.                             continue ParseLoop;
  1091.                         }
  1092.  
  1093.                         endTag = this._buff.indexOf('>');
  1094.                         if (endTag > -1) {
  1095.                             this._buff = this._buff.slice(pos + 1);
  1096.                             this._killLeadingWS = true;
  1097.                             continue;
  1098.                         }
  1099.                     }
  1100.  
  1101.                     if (this._buff)
  1102.                         throw Components.Exception('Error parsing response');
  1103.  
  1104.                     continue;
  1105.                 }
  1106.             
  1107.                 endTag = this._buff.indexOf('>');
  1108.                 if (endTag > -1) {
  1109.                     var tag = this._buff.slice(1, endTag);
  1110.                     this._buff = this._buff.slice(endTag + 1);
  1111.                     tag = tag.replace(/[ \t\n]+.*?(\/?)$/, '$1');
  1112.  
  1113.                     // XML-RPC tags are pretty simple.
  1114.                     if (/[^a-zA-Z0-9.\/]/.test(tag))
  1115.                         throw Components.Exception('Error parsing response');
  1116.  
  1117.                     // Determine start and/or end tag.
  1118.                     if (tag.charAt(tag.length - 1) == '/') {
  1119.                         this._docHandler.startElement(tag.slice(0, -1));
  1120.                         this._docHandler.endElement(tag.slice(0, -1));
  1121.                     } else if (tag.charAt(0) == '/') {
  1122.                         this._docHandler.endElement(tag.slice(1));
  1123.                     } else {
  1124.                         this._docHandler.startElement(tag);
  1125.                     }
  1126.                     this._killLeadingWS = true; 
  1127.                 } else {
  1128.                     // No end tag. Check for window size to avoid an endless
  1129.                     // loop here.. hackish, I know, but if we get here this is
  1130.                     // not a XML-RPC request..
  1131.                     if (this._buff.length >= this._bufferSize)
  1132.                         throw Components.Exception('Error parsing response');
  1133.                     // If we get here and all what is to be read has
  1134.                     // been readinto the buffer, we have an incomplete stream.
  1135.                     if (!this._maxAvailable())
  1136.                         throw Components.Exception('Error parsing response');
  1137.                 }
  1138.             } else {
  1139.                 if (this._killLeadingWS) {
  1140.                     this._buff = this._buff.replace(/^[ \t\n]*/, '');
  1141.                     if (this._buff) this._killLeadingWS = false;
  1142.                 } else {
  1143.                     // prepend supposed trailing whitespace to the front.
  1144.                     this._buff = this._trailingWS + this._buff;
  1145.                     this._trailingWS = '';
  1146.                 }
  1147.  
  1148.                 // store trailing whitespace, and only hand it over
  1149.                 // the next time round. Unless we hit a tag, then we kill it
  1150.                 if (this._buff) {
  1151.                     this._trailingWS = this._buff.match(/[ \t\n]*$/);
  1152.                     this._buff = this._buff.replace(/[ \t\n]*$/, '');
  1153.                 }
  1154.  
  1155.                 if (this._buff) this._docHandler.characters(this._buff);
  1156.  
  1157.                 this._buff = '';
  1158.             }
  1159.         }
  1160.     }
  1161. };
  1162.                 
  1163.  
  1164. /* The PushbackInputStream class constructor */
  1165. function PushbackInputStream(stream) {
  1166.     this._stream = stream;
  1167. }
  1168.  
  1169. /* The PushbackInputStream class def */
  1170. PushbackInputStream.prototype = {
  1171.     _stream: null,
  1172.     _read_characters: '',
  1173.  
  1174.     available: function() {
  1175.         return this._read_characters.length + this._stream.available();
  1176.     },
  1177.  
  1178.     read: function(length) {
  1179.         var read;
  1180.         if (this._read_characters.length >= length) {
  1181.             read = this._read_characters.slice(0, length);
  1182.             this._read_characters = this._read_characters.slice(length);
  1183.             return read;
  1184.         } else {
  1185.             read = this._read_characters;
  1186.             this._read_characters = '';
  1187.             return read + this._stream.read(length - read.length);
  1188.         }
  1189.     },
  1190.  
  1191.     unread: function(chars) { 
  1192.         this._read_characters = chars + this._read_characters;
  1193.     }
  1194. };
  1195.             
  1196. /*
  1197.  * Objects
  1198.  */
  1199.  
  1200. /* nsXmlRpcClient Module (for XPCOM registration) */
  1201. var nsXmlRpcClientModule = {
  1202.     registerSelf: function(compMgr, fileSpec, location, type) {
  1203.         compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
  1204.  
  1205.         compMgr.registerFactoryLocation(XMLRPCCLIENT_CID, 
  1206.                                         'XML-RPC Client JS component', 
  1207.                                         XMLRPCCLIENT_CONTRACTID, 
  1208.                                         fileSpec,
  1209.                                         location, 
  1210.                                         type);
  1211.         compMgr.registerFactoryLocation(XMLRPCFAULT_CID, 
  1212.                                         'XML-RPC Fault JS component', 
  1213.                                         XMLRPCFAULT_CONTRACTID, 
  1214.                                         fileSpec,
  1215.                                         location, 
  1216.                                         type);
  1217.     },
  1218.  
  1219.     getClassObject: function(compMgr, cid, iid) {
  1220.         if (!cid.equals(XMLRPCCLIENT_CID) && !cid.equals(XMLRPCFAULT_CID))
  1221.             throw Components.results.NS_ERROR_NO_INTERFACE;
  1222.  
  1223.         if (!iid.equals(Components.interfaces.nsIFactory))
  1224.             throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  1225.  
  1226.         if (cid.equals(XMLRPCCLIENT_CID))
  1227.             return nsXmlRpcClientFactory
  1228.         else return nsXmlRpcFaultFactory;
  1229.     },
  1230.  
  1231.     canUnload: function(compMgr) { return true; }
  1232. };
  1233.  
  1234. /* nsXmlRpcClient Class Factory */
  1235. var nsXmlRpcClientFactory = {
  1236.     createInstance: function(outer, iid) {
  1237.         if (outer != null)
  1238.             throw Components.results.NS_ERROR_NO_AGGREGATION;
  1239.     
  1240.         if (!iid.equals(XMLRPCCLIENT_IID) &&
  1241.             !iid.equals(Components.interfaces.nsISupports))
  1242.             throw Components.results.NS_ERROR_INVALID_ARG;
  1243.  
  1244.         return new nsXmlRpcClient();
  1245.     }
  1246. }
  1247.  
  1248. /* nsXmlRpcFault Class Factory */
  1249. var nsXmlRpcFaultFactory = {
  1250.     createInstance: function(outer, iid) {
  1251.         if (outer != null)
  1252.             throw Components.results.NS_ERROR_NO_AGGREGATION;
  1253.  
  1254.         if (!iid.equals(XMLRPCFAULT_IID) &&
  1255.             !iid.equals(Components.interfaces.nsISupports))
  1256.             throw Components.results.NS_ERROR_INVALID_ARG;
  1257.  
  1258.         return new nsXmlRpcFault();
  1259.     }
  1260. }
  1261.  
  1262. /*
  1263.  * Functions
  1264.  */
  1265.  
  1266. /* module initialisation */
  1267. function NSGetModule(comMgr, fileSpec) { return nsXmlRpcClientModule; }
  1268.  
  1269. /* Create an instance of the given ContractID, with given interface */
  1270. function createInstance(contractId, intf) {
  1271.     return Components.classes[contractId]
  1272.         .createInstance(Components.interfaces[intf]);
  1273. }
  1274.  
  1275. /* Get a pointer to a service indicated by the ContractID, with given interface */
  1276. function getService(contractId, intf) {
  1277.     return Components.classes[contractId].getService(Components.interfaces[intf]);
  1278. }
  1279.  
  1280. /* Convert an inputstream to a scriptable inputstream */
  1281. function toScriptableStream(input) {
  1282.     var SIStream = Components.Constructor(
  1283.         '@mozilla.org/scriptableinputstream;1',
  1284.         'nsIScriptableInputStream', 'init');
  1285.     return new SIStream(input);
  1286. }
  1287.  
  1288. /* format a Date object into a iso8601 datetime string, UTC time */
  1289. function iso8601Format(date) {
  1290.     var datetime = date.getUTCFullYear();
  1291.     var month = String(date.getUTCMonth() + 1);
  1292.     datetime += (month.length == 1 ?  '0' + month : month);
  1293.     var day = date.getUTCDate();
  1294.     datetime += (day < 10 ? '0' + day : day);
  1295.  
  1296.     datetime += 'T';
  1297.  
  1298.     var hour = date.getUTCHours();
  1299.     datetime += (hour < 10 ? '0' + hour : hour) + ':';
  1300.     var minutes = date.getUTCMinutes();
  1301.     datetime += (minutes < 10 ? '0' + minutes : minutes) + ':';
  1302.     var seconds = date.getUTCSeconds();
  1303.     datetime += (seconds < 10 ? '0' + seconds : seconds);
  1304.  
  1305.     return datetime;
  1306. }
  1307.  
  1308. /* Convert a stream to Base64, writing it away to a string writer */
  1309. const BASE64CHUNK = 255; // Has to be devidable by 3!!
  1310. function streamToBase64(stream, writer) {
  1311.     while (stream.available()) {
  1312.         var data = [];
  1313.         while (data.length < BASE64CHUNK && stream.available()) {
  1314.             var d = stream.read(1).charCodeAt(0);
  1315.             // reading a 0 results in NaN, compensate.
  1316.             data = data.concat(isNaN(d) ? 0 : d);
  1317.         }
  1318.         writer.write(toBase64(data));
  1319.     }
  1320. }
  1321.  
  1322. /* Convert data (an array of integers) to a Base64 string. */
  1323. const toBase64Table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' +
  1324.     '0123456789+/';
  1325. const base64Pad = '=';
  1326. function toBase64(data) {
  1327.     var result = '';
  1328.     var length = data.length;
  1329.     var i;
  1330.     // Convert every three bytes to 4 ascii characters.
  1331.     for (i = 0; i < (length - 2); i += 3) {
  1332.         result += toBase64Table[data[i] >> 2];
  1333.         result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
  1334.         result += toBase64Table[((data[i+1] & 0x0f) << 2) + (data[i+2] >> 6)];
  1335.         result += toBase64Table[data[i+2] & 0x3f];
  1336.     }
  1337.  
  1338.     // Convert the remaining 1 or 2 bytes, pad out to 4 characters.
  1339.     if (length%3) {
  1340.         i = length - (length%3);
  1341.         result += toBase64Table[data[i] >> 2];
  1342.         if ((length%3) == 2) {
  1343.             result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
  1344.             result += toBase64Table[(data[i+1] & 0x0f) << 2];
  1345.             result += base64Pad;
  1346.         } else {
  1347.             result += toBase64Table[(data[i] & 0x03) << 4];
  1348.             result += base64Pad + base64Pad;
  1349.         }
  1350.     }
  1351.  
  1352.     return result;
  1353. }
  1354.  
  1355. /* Convert Base64 data to a string */
  1356. const toBinaryTable = [
  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,-1, -1,-1,-1,-1,
  1359.     -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
  1360.     52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1, 0,-1,-1,
  1361.     -1, 0, 1, 2,  3, 4, 5, 6,  7, 8, 9,10, 11,12,13,14,
  1362.     15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,
  1363.     -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
  1364.     41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
  1365. ];
  1366. function base64ToString(data) {
  1367.     var result = '';
  1368.     var leftbits = 0; // number of bits decoded, but yet to be appended
  1369.     var leftdata = 0; // bits decoded, bt yet to be appended
  1370.  
  1371.     // Convert one by one.
  1372.     for (var i in data) {
  1373.         var c = toBinaryTable[data.charCodeAt(i) & 0x7f];
  1374.         var padding = (data[i] == base64Pad);
  1375.         // Skip illegal characters and whitespace
  1376.         if (c == -1) continue;
  1377.         
  1378.         // Collect data into leftdata, update bitcount
  1379.         leftdata = (leftdata << 6) | c;
  1380.         leftbits += 6;
  1381.  
  1382.         // If we have 8 or more bits, append 8 bits to the result
  1383.         if (leftbits >= 8) {
  1384.             leftbits -= 8;
  1385.             // Append if not padding.
  1386.             if (!padding)
  1387.                 result += String.fromCharCode((leftdata >> leftbits) & 0xff);
  1388.             leftdata &= (1 << leftbits) - 1;
  1389.         }
  1390.     }
  1391.  
  1392.     // If there are any bits left, the base64 string was corrupted
  1393.     if (leftbits)
  1394.         throw Components.Exception('Corrupted base64 string');
  1395.  
  1396.     return result;
  1397. }
  1398.  
  1399. if (DEBUG) debug = function(msg) { 
  1400.     dump(' -- XML-RPC client -- : ' + msg + '\n'); 
  1401. };
  1402. else debug = function() {}
  1403.  
  1404. // vim:sw=4:sr:sta:et:sts:
  1405.