home *** CD-ROM | disk | FTP | other *** search
/ PC World 2008 February / PCWorld_2008-02_cd.bin / temacd / songbird / Songbird_0.4_windows-i686.exe / xulrunner / components / nsXmlRpcClient.js < prev    next >
Text File  |  2006-10-24  |  35KB  |  1,039 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is Mozilla XML-RPC Client component.
  15.  *
  16.  * The Initial Developer of the Original Code is
  17.  * Digital Creations 2, Inc.
  18.  * Portions created by the Initial Developer are Copyright (C) 2000
  19.  * the Initial Developer. All Rights Reserved.
  20.  *
  21.  * Contributor(s):
  22.  *   Martijn Pieters <mj@digicool.com> (original author)
  23.  *   Samuel Sieb <samuel@sieb.net>
  24.  *
  25.  * Alternatively, the contents of this file may be used under the terms of
  26.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  27.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  28.  * in which case the provisions of the GPL or the LGPL are applicable instead
  29.  * of those above. If you wish to allow use of your version of this file only
  30.  * under the terms of either the GPL or the LGPL, and not to allow others to
  31.  * use your version of this file under the terms of the MPL, indicate your
  32.  * decision by deleting the provisions above and replace them with the notice
  33.  * and other provisions required by the GPL or the LGPL. If you do not delete
  34.  * the provisions above, a recipient may use your version of this file under
  35.  * the terms of any one of the MPL, the GPL or the LGPL.
  36.  *
  37.  * ***** END LICENSE BLOCK ***** */
  38.  
  39. /*
  40.  *  nsXmlRpcClient XPCOM component
  41.  *  Version: $Revision: 1.39 $
  42.  *
  43.  *  $Id: nsXmlRpcClient.js,v 1.39 2006/10/24 16:02:01 silver%warwickcompsoc.co.uk Exp $
  44.  */
  45.  
  46. /*
  47.  * Constants
  48.  */
  49. const XMLRPCCLIENT_CONTRACTID = '@mozilla.org/xml-rpc/client;1';
  50. const XMLRPCCLIENT_CID =
  51.     Components.ID('{4d7d15c0-3747-4f7f-b6b3-792a5ea1a9aa}');
  52. const XMLRPCCLIENT_IID = Components.interfaces.nsIXmlRpcClient;
  53.  
  54. const XMLRPCFAULT_CONTRACTID = '@mozilla.org/xml-rpc/fault;1';
  55. const XMLRPCFAULT_CID =
  56.     Components.ID('{691cb864-0a7e-448c-98ee-4a7f359cf145}');
  57. const XMLRPCFAULT_IID = Components.interfaces.nsIXmlRpcFault;
  58.  
  59. const XMLHTTPREQUEST_CONTRACTID = '@mozilla.org/xmlextras/xmlhttprequest;1';
  60.  
  61. const NSICHANNEL = Components.interfaces.nsIChannel;
  62.  
  63. const DEBUG = false;
  64. const DEBUGPARSE = false;
  65.  
  66. const DOMNode = Components.interfaces.nsIDOMNode;
  67. /*
  68.  * Class definitions
  69.  */
  70.  
  71. /* The nsXmlRpcFault class constructor. */
  72. function nsXmlRpcFault() {}
  73.  
  74. /* the nsXmlRpcFault class def */
  75. nsXmlRpcFault.prototype = {
  76.     faultCode: 0,
  77.     faultString: '',
  78.  
  79.     init: function(faultCode, faultString) {
  80.         this.faultCode = faultCode;
  81.         this.faultString = faultString;
  82.     },
  83.  
  84.     toString: function() {
  85.         return '<XML-RPC Fault: (' + this.faultCode + ') ' +
  86.             this.faultString + '>';
  87.     },
  88.  
  89.     // nsISupports interface
  90.     QueryInterface: function(iid) {
  91.         if (!iid.equals(Components.interfaces.nsISupports) &&
  92.             !iid.equals(XMLRPCFAULT_IID))
  93.             throw Components.results.NS_ERROR_NO_INTERFACE;
  94.         return this;
  95.     }
  96. };
  97.  
  98. /* The nsXmlRpcClient class constructor. */
  99. function nsXmlRpcClient() {}
  100.  
  101. /* the nsXmlRpcClient class def */
  102. nsXmlRpcClient.prototype = {
  103.     _serverUrl: null,
  104.     _useAuth: false,
  105.  
  106.     init: function(serverURL) {
  107.         this._serverUrl = serverURL;
  108.         this._encoding = "UTF-8";
  109.     },
  110.  
  111.     setAuthentication: function(username, password){
  112.         if ((typeof username == "string") &&
  113.             (typeof password == "string")){
  114.           this._useAuth = true;
  115.           this._username = username;
  116.           this._password = password;
  117.         }
  118.     },
  119.  
  120.     clearAuthentication: function(){
  121.         this._useAuth = false;
  122.     },
  123.  
  124.     setEncoding: function(encoding){
  125.         this._encoding = encoding;
  126.     },
  127.  
  128.     get serverUrl() { return this._serverUrl; },
  129.  
  130.     // Internal copy of the status
  131.     _status: null,
  132.     _listener: null,
  133.  
  134.     asyncCall: function(listener, context, methodName, methodArgs, count) {
  135.         debug('asyncCall');
  136.         // Check for call in progress.
  137.         if (this._inProgress)
  138.             throw Components.Exception('Call in progress!');
  139.  
  140.         // Check for the server URL;
  141.         if (!this._serverUrl)
  142.             throw Components.Exception('Not initialized');
  143.  
  144.         this._inProgress = true;
  145.  
  146.         // Clear state.
  147.         this._foundFault = false;
  148.         this._passwordTried = false;
  149.         this._result = null;
  150.         this._fault = null;
  151.         this._status = null;
  152.         this._responseStatus = null;
  153.         this._responseString = null;
  154.         this._listener = listener;
  155.         this._seenStart = false;
  156.         this._context = context;
  157.         
  158.         debug('Arguments: ' + methodArgs);
  159.  
  160.         // Generate request body
  161.         var xmlWriter = new XMLWriter(this._encoding);
  162.         this._generateRequestBody(xmlWriter, methodName, methodArgs);
  163.  
  164.         var requestBody = xmlWriter.data;
  165.  
  166.         debug('Request: ' + requestBody);
  167.  
  168.         this.xmlhttp = Components.classes[XMLHTTPREQUEST_CONTRACTID]
  169.             .createInstance(Components.interfaces.nsIXMLHttpRequest);
  170.         if (this._useAuth) {
  171.             this.xmlhttp.open('POST', this._serverUrl, true,
  172.                               this._username, this._password);
  173.         } else {
  174.             this.xmlhttp.open('POST', this._serverUrl);
  175.         }
  176.         this.xmlhttp.onload = this._onload;
  177.         this.xmlhttp.onerror = this._onerror;
  178.         this.xmlhttp.parent = this;
  179.         this.xmlhttp.setRequestHeader('Content-Type','text/xml');
  180.         this.xmlhttp.send(requestBody);
  181.         var chan = this.xmlhttp.channel.QueryInterface(NSICHANNEL);
  182.         chan.notificationCallbacks = this;
  183.     },
  184.  
  185.     _onload: function(e) {
  186.         var result;
  187.         var parent = e.target.parent;
  188.         parent._inProgress = false;
  189.         parent._responseStatus = e.target.status;
  190.         parent._responseString = e.target.statusText;
  191.         if (!e.target.responseXML) {
  192.             if (e.target.status) {
  193.                 try {
  194.                     parent._listener.onError(parent, parent._context,
  195.                         Components.results.NS_ERROR_FAILURE,
  196.                         'Server returned status ' + e.target.status);
  197.                 } catch (ex) {
  198.                     debug('Exception in listener.onError: ' + ex);
  199.                 }
  200.             } else {
  201.                 try {
  202.                     parent._listener.onError(parent, parent._context,
  203.                                       Components.results.NS_ERROR_FAILURE,
  204.                                       'Unknown network error');
  205.                 } catch (ex) {
  206.                     debug('Exception in listener.onError: ' + ex);
  207.                 }
  208.             }
  209.             return;
  210.         }
  211.         try {
  212.             e.target.responseXML.normalize();
  213.             result = parent.parse(e.target.responseXML);
  214.         } catch (ex) {
  215.             try {
  216.                 parent._listener.onError(parent, parent._context,
  217.                                          ex.result, ex.message);
  218.             } catch (ex) {
  219.                 debug('Exception in listener.onError: ' + ex);
  220.             }
  221.             return;
  222.         }
  223.         if (parent._foundFault) {
  224.             parent._fault = result;
  225.             try {
  226.                 parent._listener.onFault(parent, parent._context, result);
  227.             } catch(ex) {
  228.                 debug('Exception in listener.onFault: ' + ex);
  229.             }
  230.         } else {
  231.             parent._result = result.value;
  232.             try { 
  233.                 parent._listener.onResult(parent, parent._context,
  234.                                           result.value);
  235.             } catch (ex) {
  236.                 debug('Exception in listener.onResult: ' + ex);
  237.             }
  238.         }
  239.     },
  240.  
  241.     _onerror: function(e) {
  242.         var parent = e.target.parent;
  243.         parent._inProgress = false;
  244.         try {
  245.             parent._listener.onError(parent, parent._context,
  246.                                 Components.results.NS_ERROR_FAILURE,
  247.                                 'Unknown network error');
  248.         } catch (ex) {
  249.             debug('Exception in listener.onError: ' + ex);
  250.         }
  251.     },
  252.     
  253.     _foundFault: false,
  254.  
  255.     _fault: null,
  256.     _result: null,
  257.     _responseStatus: null,
  258.     _responseString: null,
  259.  
  260.     get fault() { return this._fault; },
  261.     get result() { return this._result; },
  262.     get responseStatus() { return this._responseStatus; },
  263.     get responseString() { return this._responseString; },
  264.  
  265.     /* Convenience. Create an appropriate XPCOM object for a given type */
  266.     INT:      1,
  267.     BOOLEAN:  2,
  268.     STRING:   3,
  269.     DOUBLE:   4,
  270.     DATETIME: 5,
  271.     ARRAY:    6,
  272.     STRUCT:   7,
  273.     BASE64:   8, // Not part of nsIXmlRpcClient interface, internal use.
  274.     createType: function(type, uuid) {
  275.         const SUPPORTSID = '@mozilla.org/supports-';
  276.         switch(type) {
  277.             case this.INT:
  278.                 uuid.value = Components.interfaces.nsISupportsPRInt32
  279.                 return createInstance(SUPPORTSID + 'PRInt32;1',
  280.                     'nsISupportsPRInt32');
  281.  
  282.             case this.BOOLEAN:
  283.                 uuid.value = Components.interfaces.nsISupportsPRBool
  284.                 return createInstance(SUPPORTSID + 'PRBool;1',
  285.                     'nsISupportsPRBool');
  286.  
  287.             case this.STRING:
  288.                 uuid.value = Components.interfaces.nsISupportsCString
  289.                 return createInstance(SUPPORTSID + 'cstring;1',
  290.                     'nsISupportsCString');
  291.  
  292.             case this.DOUBLE:
  293.                 uuid.value = Components.interfaces.nsISupportsDouble
  294.                 return createInstance(SUPPORTSID + 'double;1',
  295.                     'nsISupportsDouble');
  296.  
  297.             case this.DATETIME:
  298.                 uuid.value = Components.interfaces.nsISupportsPRTime
  299.                 return createInstance(SUPPORTSID + 'PRTime;1',
  300.                     'nsISupportsPRTime');
  301.  
  302.             case this.ARRAY:
  303.                 uuid.value = Components.interfaces.nsISupportsArray
  304.                 return createInstance(SUPPORTSID + 'array;1',
  305.                     'nsISupportsArray');
  306.  
  307.             case this.STRUCT:
  308.                 uuid.value = Components.interfaces.nsIDictionary
  309.                 return createInstance('@mozilla.org/dictionary;1', 
  310.                     'nsIDictionary');
  311.  
  312.             default: throw Components.Exception('Unsupported type');
  313.         }
  314.     },
  315.  
  316.     // nsISupports interface
  317.     QueryInterface: function(iid) {
  318.         if (!iid.equals(Components.interfaces.nsISupports) &&
  319.             !iid.equals(XMLRPCCLIENT_IID) &&
  320.             !iid.equals(Components.interfaces.nsIInterfaceRequestor))
  321.             throw Components.results.NS_ERROR_NO_INTERFACE;
  322.         return this;
  323.     },
  324.  
  325.     // nsIInterfaceRequester interface
  326.     getInterface: function(iid, result){
  327.         if (iid.equals(Components.interfaces.nsIAuthPrompt)){
  328.             return this;
  329.         }
  330.         Components.returnCode = Components.results.NS_ERROR_NO_INTERFACE;
  331.         return null;
  332.     },
  333.  
  334.     // nsIAuthPrompt interface
  335.     _passwordTried: false,
  336.     promptUsernameAndPassword: function(dialogTitle, text, passwordRealm,
  337.                                         savePassword, user, pwd){
  338.  
  339.         if (this._useAuth){
  340.             if (this._passwordTried){
  341.                 return false;
  342.             }
  343.             user.value = this._username;
  344.             pwd.value = this._password;
  345.             this._passwordTried = true;
  346.             return true;
  347.         }
  348.         return false;
  349.     },
  350.  
  351.     /* Generate the XML-RPC request body */
  352.     _generateRequestBody: function(writer, methodName, methodArgs) {
  353.         writer.startElement('methodCall');
  354.  
  355.         writer.startElement('methodName');
  356.         writer.write(methodName);
  357.         writer.endElement('methodName');
  358.  
  359.         writer.startElement('params');
  360.         for (var i = 0; i < methodArgs.length; i++) {
  361.             writer.startElement('param');
  362.             this._generateArgumentBody(writer, methodArgs[i]);
  363.             writer.endElement('param');
  364.         }
  365.         writer.endElement('params');
  366.  
  367.         writer.endElement('methodCall');
  368.     },
  369.  
  370.     /* Write out a XML-RPC parameter value */
  371.     _generateArgumentBody: function(writer, obj) {
  372.         writer.startElement('value');
  373.         var sType = this._typeOf(obj);
  374.         switch (sType) {
  375.             case 'PRUint8':
  376.             case 'PRUint16':
  377.             case 'PRInt16':
  378.             case 'PRInt32':
  379.                 obj=obj.QueryInterface(Components.interfaces['nsISupports' +
  380.                     sType]);
  381.                 writer.startElement('i4');
  382.                 writer.write(obj.toString());
  383.                 writer.endElement('i4');
  384.                 break;
  385.  
  386.             case 'PRBool':
  387.                 obj=obj.QueryInterface(Components.interfaces.nsISupportsPRBool);
  388.                 writer.startElement('boolean');
  389.                 writer.write(obj.data ? '1' : '0');
  390.                 writer.endElement('boolean');
  391.                 break;
  392.  
  393.             case 'Char':
  394.             case 'CString':
  395.                 obj=obj.QueryInterface(Components.interfaces['nsISupports' +
  396.                     sType]);
  397.                 writer.startElement('string');
  398.                 writer.write(obj.toString());
  399.                 writer.endElement('string');
  400.                 break;
  401.  
  402.             case 'Float':
  403.             case 'Double':
  404.                 obj=obj.QueryInterface(Components.interfaces['nsISupports' +
  405.                     sType]);
  406.                 writer.startElement('double');
  407.                 writer.write(obj.toString());
  408.                 writer.endElement('double');
  409.                 break;
  410.  
  411.             case 'PRTime':
  412.                 obj = obj.QueryInterface(
  413.                     Components.interfaces.nsISupportsPRTime);
  414.                 var date = new Date(obj.data)
  415.                 writer.startElement('dateTime.iso8601');
  416.                 writer.write(iso8601Format(date));
  417.                 writer.endElement('dateTime.iso8601');
  418.                 break;
  419.                 
  420.             case 'InputStream':
  421.                 obj = obj.QueryInterface(Components.interfaces.nsIInputStream);
  422.                 obj = toScriptableStream(obj);
  423.                 writer.startElement('base64');
  424.                 streamToBase64(obj, writer);
  425.                 writer.endElement('base64');
  426.                 break;
  427.             
  428.             case 'Array':
  429.                 obj = obj.QueryInterface(
  430.                     Components.interfaces.nsISupportsArray);
  431.                 writer.startElement('array');
  432.                 writer.startElement('data');
  433.                 for (var i = 0; i < obj.Count(); i++)
  434.                     this._generateArgumentBody(writer, obj.GetElementAt(i));
  435.                 writer.endElement('data');
  436.                 writer.endElement('array');
  437.                 break;
  438.  
  439.             case 'Dictionary':
  440.                 obj = obj.QueryInterface(Components.interfaces.nsIDictionary);
  441.                 writer.startElement('struct');
  442.                 var keys = obj.getKeys({});
  443.                 for (var k = 0; k < keys.length; k++) {
  444.                     writer.startElement('member');
  445.                     writer.startElement('name');
  446.                     writer.write(keys[k]);
  447.                     writer.endElement('name');
  448.                     this._generateArgumentBody(writer, obj.getValue(keys[k]));
  449.                     writer.endElement('member');
  450.                 }
  451.                 writer.endElement('struct');
  452.                 break;
  453.  
  454.             default:
  455.                 throw Components.Exception('Unsupported argument', null, null,
  456.                     obj);
  457.         }
  458.  
  459.         writer.endElement('value');
  460.     },
  461.  
  462.     /* Determine type of a nsISupports primitive, array or dictionary. */
  463.     _typeOf: function(obj) {
  464.         // XPConnect alows JS to pass in anything, because we are a regular
  465.         // JS object to it. So we have to test rigorously.
  466.         if (typeof obj != 'object') return 'Unknown';
  467.  
  468.         // Anything else not nsISupports is not allowed.
  469.         if (typeof obj.QueryInterface != 'function') return 'Unknown';
  470.  
  471.         // Now we will have to eliminate by trying all possebilities.
  472.         try {
  473.             obj.QueryInterface(Components.interfaces.nsISupportsPRUint8);
  474.             return 'PRUint8';
  475.         } catch(e) {}
  476.         
  477.         try {
  478.             obj.QueryInterface(Components.interfaces.nsISupportsPRUint16);
  479.             return 'PRUint16';
  480.         } catch(e) {}
  481.         
  482.         try {
  483.             obj.QueryInterface(Components.interfaces.nsISupportsPRInt16);
  484.             return 'PRInt16';
  485.         } catch(e) {}
  486.         
  487.         try {
  488.             obj.QueryInterface(Components.interfaces.nsISupportsPRInt32);
  489.             return 'PRInt32';
  490.         } catch(e) {}
  491.         
  492.         try {
  493.             obj.QueryInterface(Components.interfaces.nsISupportsPRBool);
  494.             return 'PRBool';
  495.         } catch(e) {}
  496.         
  497.         try {
  498.             obj.QueryInterface(Components.interfaces.nsISupportsChar);
  499.             return 'Char';
  500.         } catch(e) {}
  501.         
  502.         try {
  503.             obj.QueryInterface(Components.interfaces.nsISupportsCString);
  504.             return 'CString';
  505.         } catch(e) {}
  506.         
  507.         try {
  508.             obj.QueryInterface(Components.interfaces.nsISupportsFloat);
  509.             return 'Float';
  510.         } catch(e) {}
  511.         
  512.         try {
  513.             obj.QueryInterface(Components.interfaces.nsISupportsDouble);
  514.             return 'Double';
  515.         } catch(e) {}
  516.         
  517.         try {
  518.             obj.QueryInterface(Components.interfaces.nsISupportsPRTime);
  519.             return 'PRTime';
  520.         } catch(e) {}
  521.         
  522.         try {
  523.             obj.QueryInterface(Components.interfaces.nsIInputStream);
  524.             return 'InputStream';
  525.         } catch(e) {}
  526.         
  527.         try {
  528.             obj.QueryInterface(Components.interfaces.nsISupportsArray);
  529.             return 'Array';
  530.         } catch(e) {}
  531.         
  532.         try {
  533.             obj.QueryInterface(Components.interfaces.nsIDictionary);
  534.             return 'Dictionary';
  535.         } catch(e) {}
  536.         
  537.         // Not a supported type
  538.         return 'Unknown';
  539.     },
  540.  
  541.     // Response parsing state
  542.     _valueStack: [],
  543.     _currValue: null,
  544.     _cdata: null,
  545.  
  546.     parse: function(doc) {
  547.         var node = doc.firstChild;
  548.         var result;
  549.         if (node.nodeType == DOMNode.TEXT_NODE)
  550.             node = node.nextSibling;
  551.         if ((node.nodeType != DOMNode.ELEMENT_NODE) ||
  552.             (node.nodeName != 'methodResponse')) {
  553.             throw Components.Exception('Expecting a methodResponse', null, null,
  554.                                        doc);
  555.         }
  556.         node = node.firstChild;
  557.         if (node.nodeType == DOMNode.TEXT_NODE)
  558.             node = node.nextSibling;
  559.         if (node.nodeType != DOMNode.ELEMENT_NODE)
  560.             throw Components.Exception('Expecting a params or fault', null,
  561.                                        null, doc);
  562.         if (node.nodeName == 'params') {
  563.             node = node.firstChild;
  564.             if (node.nodeType == DOMNode.TEXT_NODE)
  565.                 node = node.nextSibling;
  566.             if ((node.nodeType != DOMNode.ELEMENT_NODE) ||
  567.                 (node.nodeName != 'param')) {
  568.                 throw Components.Exception('Expecting a param', null, null,
  569.                                            doc);
  570.             }
  571.             result = this.parseValue(node.firstChild);
  572.         } else if (node.nodeName == 'fault') {
  573.             this._foundFault = true;
  574.             result = this.parseFault(node.firstChild);
  575.         } else {
  576.             throw Components.Exception('Expecting a params or fault', null,
  577.                                        null, doc);
  578.         }
  579.         debug('Parse finished');
  580.         return result;
  581.     },
  582.  
  583.     parseValue: function(node) {
  584.         var cValue = new Value();
  585.         if (node && (node.nodeType == DOMNode.TEXT_NODE))
  586.             node = node.nextSibling;
  587.         if (!node || (node.nodeType != DOMNode.ELEMENT_NODE) ||
  588.             (node.nodeName != 'value')) {
  589.             throw Components.Exception('Expecting a value', null, null, node);
  590.         }
  591.         node = node.firstChild;
  592.         if (!node)
  593.             return cValue;
  594.         if (node.nodeType == DOMNode.TEXT_NODE){
  595.             if (!node.nextSibling) {
  596.                 cValue.value = node.nodeValue;
  597.                 return cValue;
  598.             } else {
  599.                 node = node.nextSibling;
  600.             }
  601.         }
  602.         if (node.nodeType != DOMNode.ELEMENT_NODE)
  603.             throw Components.Exception('Expecting a value type', null, null,
  604.                                        node);
  605.         switch (node.nodeName) {
  606.             case 'string':
  607.                 cValue.value = this.parseString(node.firstChild);
  608.                 break;
  609.             case 'i4':
  610.             case 'int':
  611.                 cValue.type = this.INT;
  612.                 cValue.value = this.parseString(node.firstChild);
  613.                 break;
  614.             case 'boolean':
  615.                 cValue.type = this.BOOLEAN;
  616.                 cValue.value = this.parseString(node.firstChild);
  617.                 break;
  618.             case 'double':
  619.                 cValue.type = this.DOUBLE;
  620.                 cValue.value = this.parseString(node.firstChild);
  621.                 break;
  622.             case 'dateTime.iso8601':
  623.                 cValue.type = this.DATETIME;
  624.                 cValue.value = this.parseString(node.firstChild);
  625.                 break;
  626.             case 'base64':
  627.                 cValue.type = this.BASE64;
  628.                 cValue.value = this.parseString(node.firstChild);
  629.                 break;
  630.             case 'struct':
  631.                 cValue.type = this.STRUCT;
  632.                 this.parseStruct(cValue, node.firstChild);
  633.                 break;
  634.             case 'array':
  635.                 cValue.type = this.ARRAY;
  636.                 this.parseArray(cValue, node.firstChild);
  637.                 break;
  638.             default:
  639.                 throw Components.Exception('Expecting a value type', null, null,
  640.                                            node);
  641.         }
  642.         return cValue;
  643.     },
  644.  
  645.     parseString: function(node) {
  646.         value = '';
  647.         while (node) {
  648.             if (node.nodeType != DOMNode.TEXT_NODE)
  649.                 throw Components.Exception('Expecting a text node', null, null,
  650.                                            node);
  651.             value += node.nodeValue; 
  652.             node = node.nextSibling;
  653.         }
  654.         return value;
  655.     },
  656.  
  657.     parseStruct: function(struct, node) {
  658.         while (node) {
  659.             if (node.nodeType == DOMNode.TEXT_NODE)
  660.                 node = node.nextSibling;
  661.             if (!node)
  662.                 return;
  663.             if ((node.nodeType != DOMNode.ELEMENT_NODE) ||
  664.                 (node.nodeName != 'member')) {
  665.                 throw Components.Exception('Expecting a member', null, null,
  666.                                            node);
  667.             }
  668.             this.parseMember(struct, node.firstChild);
  669.             node = node.nextSibling;
  670.         }
  671.     },
  672.  
  673.     parseMember: function(struct, node) {
  674.         var cValue;
  675.         if (node.nodeType == DOMNode.TEXT_NODE)
  676.             node = node.nextSibling;
  677.         if (!node || (node.nodeType != DOMNode.ELEMENT_NODE) ||
  678.             (node.nodeName != 'name')) {
  679.             throw Components.Exception('Expecting a name', null, null, node);
  680.         }
  681.         struct.name = this.parseString(node.firstChild);
  682.         cValue = this.parseValue(node.nextSibling);
  683.         struct.appendValue(cValue.value);
  684.     },
  685.  
  686.     parseArray: function(array, node) {
  687.         if (node.nodeType == DOMNode.TEXT_NODE)
  688.             node = node.nextSibling;
  689.         if (!node || (node.nodeType != DOMNode.ELEMENT_NODE) ||
  690.             (node.nodeName != 'data')) {
  691.             throw Components.Exception('Expecting array data', null, null, node);
  692.         }
  693.         for (node = node.firstChild; node; node = node.nextSibling) {
  694.             if (node.nodeType == DOMNode.TEXT_NODE)
  695.                 continue;
  696.             array.appendValue(this.parseValue(node).value);
  697.         }
  698.     },
  699.  
  700.     parseFault: function(node) {
  701.         var fault = createInstance(XMLRPCFAULT_CONTRACTID, 'nsIXmlRpcFault');
  702.         var cValue = this.parseValue(node);
  703.         if ((cValue.type != this.STRUCT) ||
  704.             (!cValue.value.hasKey('faultCode')) ||
  705.             (!cValue.value.hasKey('faultString'))) {
  706.             throw Components.Exception('Invalid fault', null, null, node);
  707.         }
  708.         fault.init(cValue.value.getValue('faultCode').data,
  709.                    cValue.value.getValue('faultString').data);
  710.         return fault;
  711.     }
  712. };
  713.  
  714. /* The XMLWriter class constructor */
  715. function XMLWriter(encoding) {
  716.     if (!encoding)
  717.         encoding = "UTF-8";
  718.     this.data = '<?xml version="1.0" encoding="' + encoding + '"?>';
  719. }
  720.  
  721. /* The XMLWriter class def */
  722. XMLWriter.prototype = {
  723.     data: '',
  724.     
  725.     startElement: function(element) {
  726.         this.data += '<' + element + '>';
  727.     },
  728.  
  729.     endElement: function(element) {
  730.         this.data += '</' + element + '>';
  731.     },
  732.     
  733.     write: function(text) {
  734.         for (var i = 0; i < text.length; i++) {
  735.             var c = text[i];
  736.             switch (c) {
  737.                 case '<':
  738.                     this.data += '<';
  739.                     break;
  740.                 case '&':
  741.                     this.data += '&';
  742.                     break;
  743.                 default:
  744.                     this.data += c;
  745.             }
  746.         }
  747.     },
  748.  
  749.     markup: function(text) { this.data += text }
  750. };
  751.  
  752. /* The Value class contructor */
  753. function Value() { this.type = this.STRING; };
  754.  
  755. /* The Value class def */
  756. Value.prototype = {
  757.     INT:      nsXmlRpcClient.prototype.INT,
  758.     BOOLEAN:  nsXmlRpcClient.prototype.BOOLEAN,
  759.     STRING:   nsXmlRpcClient.prototype.STRING,
  760.     DOUBLE:   nsXmlRpcClient.prototype.DOUBLE,
  761.     DATETIME: nsXmlRpcClient.prototype.DATETIME,
  762.     ARRAY:    nsXmlRpcClient.prototype.ARRAY,
  763.     STRUCT:   nsXmlRpcClient.prototype.STRUCT,
  764.     BASE64:   nsXmlRpcClient.prototype.BASE64,
  765.     
  766.     _createType: nsXmlRpcClient.prototype.createType,
  767.  
  768.     name: null,
  769.     
  770.     _value: null,
  771.     get value() { return this._value; },
  772.     set value(val) {
  773.         // accepts [0-9]+ or x[0-9a-fA-F]+ and returns the character.
  774.         function entityTrans(substr, code) {
  775.             return String.fromCharCode("0" + code);
  776.         }
  777.         
  778.         switch (this.type) {
  779.             case this.STRING:
  780.                 val = val.replace(/&#([0-9]+);/g, entityTrans);
  781.                 val = val.replace(/&#(x[0-9a-fA-F]+);/g, entityTrans);
  782.                 val = val.replace(/</g, '<');
  783.                 val = val.replace(/>/g, '>');
  784.                 val = val.replace(/&/g, '&');
  785.                 this._value.data = val;
  786.                 break;
  787.         
  788.             case this.BOOLEAN:
  789.                 this._value.data = (val == 1);
  790.                 break;
  791.  
  792.             case this.DATETIME:
  793.                 this._value.data = Date.UTC(val.slice(0, 4), 
  794.                     val.slice(4, 6) - 1, val.slice(6, 8), val.slice(9, 11),
  795.                     val.slice(12, 14), val.slice(15));
  796.                 break;
  797.  
  798.             case this.BASE64:
  799.                 this._value.data = base64ToString(val);
  800.                 break;
  801.  
  802.             default:
  803.                 this._value.data = val;
  804.         }
  805.     },
  806.  
  807.     _type: null,
  808.     get type() { return this._type; },
  809.     set type(type) { 
  810.         this._type = type;
  811.         if (type == this.BASE64) 
  812.             this._value = this._createType(this.STRING, {});
  813.         else this._value = this._createType(type, {});
  814.     },
  815.  
  816.     appendValue: function(val) {
  817.         switch (this.type) {
  818.             case this.ARRAY:
  819.                 this.value.AppendElement(val);
  820.                 break;
  821.  
  822.             case this.STRUCT:
  823.                 this.value.setValue(this.name, val);
  824.                 break;
  825.         }
  826.     }
  827. };
  828.  
  829. /*
  830.  * Objects
  831.  */
  832.  
  833. /* nsXmlRpcClient Module (for XPCOM registration) */
  834. var nsXmlRpcClientModule = {
  835.     registerSelf: function(compMgr, fileSpec, location, type) {
  836.         compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
  837.  
  838.         compMgr.registerFactoryLocation(XMLRPCCLIENT_CID, 
  839.                                         'XML-RPC Client JS component', 
  840.                                         XMLRPCCLIENT_CONTRACTID, 
  841.                                         fileSpec,
  842.                                         location, 
  843.                                         type);
  844.         compMgr.registerFactoryLocation(XMLRPCFAULT_CID, 
  845.                                         'XML-RPC Fault JS component', 
  846.                                         XMLRPCFAULT_CONTRACTID, 
  847.                                         fileSpec,
  848.                                         location, 
  849.                                         type);
  850.     },
  851.  
  852.     getClassObject: function(compMgr, cid, iid) {
  853.         if (!cid.equals(XMLRPCCLIENT_CID) && !cid.equals(XMLRPCFAULT_CID))
  854.             throw Components.results.NS_ERROR_NO_INTERFACE;
  855.  
  856.         if (!iid.equals(Components.interfaces.nsIFactory))
  857.             throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  858.  
  859.         if (cid.equals(XMLRPCCLIENT_CID))
  860.             return nsXmlRpcClientFactory
  861.         else return nsXmlRpcFaultFactory;
  862.     },
  863.  
  864.     canUnload: function(compMgr) { return true; }
  865. };
  866.  
  867. /* nsXmlRpcClient Class Factory */
  868. var nsXmlRpcClientFactory = {
  869.     createInstance: function(outer, iid) {
  870.         if (outer != null)
  871.             throw Components.results.NS_ERROR_NO_AGGREGATION;
  872.     
  873.         if (!iid.equals(XMLRPCCLIENT_IID) &&
  874.             !iid.equals(Components.interfaces.nsISupports))
  875.             throw Components.results.NS_ERROR_INVALID_ARG;
  876.  
  877.         return new nsXmlRpcClient();
  878.     }
  879. }
  880.  
  881. /* nsXmlRpcFault Class Factory */
  882. var nsXmlRpcFaultFactory = {
  883.     createInstance: function(outer, iid) {
  884.         if (outer != null)
  885.             throw Components.results.NS_ERROR_NO_AGGREGATION;
  886.  
  887.         if (!iid.equals(XMLRPCFAULT_IID) &&
  888.             !iid.equals(Components.interfaces.nsISupports))
  889.             throw Components.results.NS_ERROR_INVALID_ARG;
  890.  
  891.         return new nsXmlRpcFault();
  892.     }
  893. }
  894.  
  895. /*
  896.  * Functions
  897.  */
  898.  
  899. /* module initialisation */
  900. function NSGetModule(comMgr, fileSpec) { return nsXmlRpcClientModule; }
  901.  
  902. /* Create an instance of the given ContractID, with given interface */
  903. function createInstance(contractId, intf) {
  904.     return Components.classes[contractId]
  905.         .createInstance(Components.interfaces[intf]);
  906. }
  907.  
  908. /* Get a pointer to a service indicated by the ContractID, with given interface */
  909. function getService(contractId, intf) {
  910.     return Components.classes[contractId]
  911.         .getService(Components.interfaces[intf]);
  912. }
  913.  
  914. /* Convert an inputstream to a scriptable inputstream */
  915. function toScriptableStream(input) {
  916.     var SIStream = Components.Constructor(
  917.         '@mozilla.org/scriptableinputstream;1',
  918.         'nsIScriptableInputStream', 'init');
  919.     return new SIStream(input);
  920. }
  921.  
  922. /* format a Date object into a iso8601 datetime string, UTC time */
  923. function iso8601Format(date) {
  924.     var datetime = date.getUTCFullYear();
  925.     var month = String(date.getUTCMonth() + 1);
  926.     datetime += (month.length == 1 ?  '0' + month : month);
  927.     var day = date.getUTCDate();
  928.     datetime += (day < 10 ? '0' + day : day);
  929.  
  930.     datetime += 'T';
  931.  
  932.     var hour = date.getUTCHours();
  933.     datetime += (hour < 10 ? '0' + hour : hour) + ':';
  934.     var minutes = date.getUTCMinutes();
  935.     datetime += (minutes < 10 ? '0' + minutes : minutes) + ':';
  936.     var seconds = date.getUTCSeconds();
  937.     datetime += (seconds < 10 ? '0' + seconds : seconds);
  938.  
  939.     return datetime;
  940. }
  941.  
  942. /* Convert a stream to Base64, writing it away to a string writer */
  943. const BASE64CHUNK = 255; // Has to be dividable by 3!!
  944. function streamToBase64(stream, writer) {
  945.     while (stream.available()) {
  946.         var data = [];
  947.         while (data.length < BASE64CHUNK && stream.available()) {
  948.             var d = stream.read(1).charCodeAt(0);
  949.             // reading a 0 results in NaN, compensate.
  950.             data = data.concat(isNaN(d) ? 0 : d);
  951.         }
  952.         writer.write(toBase64(data));
  953.     }
  954. }
  955.  
  956. /* Convert data (an array of integers) to a Base64 string. */
  957. const toBase64Table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' +
  958.     '0123456789+/';
  959. const base64Pad = '=';
  960. function toBase64(data) {
  961.     var result = '';
  962.     var length = data.length;
  963.     var i;
  964.     // Convert every three bytes to 4 ascii characters.
  965.     for (i = 0; i < (length - 2); i += 3) {
  966.         result += toBase64Table[data[i] >> 2];
  967.         result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
  968.         result += toBase64Table[((data[i+1] & 0x0f) << 2) + (data[i+2] >> 6)];
  969.         result += toBase64Table[data[i+2] & 0x3f];
  970.     }
  971.  
  972.     // Convert the remaining 1 or 2 bytes, pad out to 4 characters.
  973.     if (length%3) {
  974.         i = length - (length%3);
  975.         result += toBase64Table[data[i] >> 2];
  976.         if ((length%3) == 2) {
  977.             result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
  978.             result += toBase64Table[(data[i+1] & 0x0f) << 2];
  979.             result += base64Pad;
  980.         } else {
  981.             result += toBase64Table[(data[i] & 0x03) << 4];
  982.             result += base64Pad + base64Pad;
  983.         }
  984.     }
  985.  
  986.     return result;
  987. }
  988.  
  989. /* Convert Base64 data to a string */
  990. const toBinaryTable = [
  991.     -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
  992.     -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
  993.     -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
  994.     52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1, 0,-1,-1,
  995.     -1, 0, 1, 2,  3, 4, 5, 6,  7, 8, 9,10, 11,12,13,14,
  996.     15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,
  997.     -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
  998.     41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
  999. ];
  1000. function base64ToString(data) {
  1001.     var result = '';
  1002.     var leftbits = 0; // number of bits decoded, but yet to be appended
  1003.     var leftdata = 0; // bits decoded, but yet to be appended
  1004.  
  1005.     // Convert one by one.
  1006.     for (var i = 0; i < data.length; i++) {
  1007.         var c = toBinaryTable[data.charCodeAt(i) & 0x7f];
  1008.         var padding = (data[i] == base64Pad);
  1009.         // Skip illegal characters and whitespace
  1010.         if (c == -1) continue;
  1011.         
  1012.         // Collect data into leftdata, update bitcount
  1013.         leftdata = (leftdata << 6) | c;
  1014.         leftbits += 6;
  1015.  
  1016.         // If we have 8 or more bits, append 8 bits to the result
  1017.         if (leftbits >= 8) {
  1018.             leftbits -= 8;
  1019.             // Append if not padding.
  1020.             if (!padding)
  1021.                 result += String.fromCharCode((leftdata >> leftbits) & 0xff);
  1022.             leftdata &= (1 << leftbits) - 1;
  1023.         }
  1024.     }
  1025.  
  1026.     // If there are any bits left, the base64 string was corrupted
  1027.     if (leftbits)
  1028.         throw Components.Exception('Corrupted base64 string');
  1029.  
  1030.     return result;
  1031. }
  1032.  
  1033. if (DEBUG) debug = function(msg) { 
  1034.     dump(' -- XML-RPC client -- : ' + msg + '\n'); 
  1035. };
  1036. else debug = function() {}
  1037.  
  1038. // vim:sw=4:sr:sta:et:sts:
  1039.