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