b46166df44530519f9401530eb3e98d9714ad6a6
[Epoz.git] / epoz / epoz_core / vcXMLRPC.js.dtml
1 //
2 // Copyright (C) 2000, 2001, 2002 Virtual Cowboys info@virtualcowboys.nl
3 //
4 // Author: Ruben Daniels <ruben@virtualcowboys.nl>
5 // Version: 0.91
6 // Date: 29-08-2001
7 // Site: www.vcdn.org/Public/XMLRPC/
8 //
9 // This program is free software; you can redistribute it and/or modify
10 // it under the terms of the GNU General Public License as published by
11 // the Free Software Foundation; either version 2 of the License, or
12 // (at your option) any later version.
13 //
14 // This program is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 // GNU General Public License for more details.
18 //
19 // You should have received a copy of the GNU General Public License
20 // along with this program; if not, write to the Free Software
21 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22
23
24 Object.prototype.toXMLRPC = function(){
25 var wo = this.valueOf();
26
27 if(wo.toXMLRPC == this.toXMLRPC){
28 retstr = "<struct>";
29
30 for(prop in this){
31 if(typeof wo[prop] != "function"){
32 retstr += "<member><name>" + prop + "</name><value>" + XMLRPC.getXML(wo[prop]) + "</value></member>";
33 }
34 }
35 retstr += "</struct>";
36
37 return retstr;
38 }
39 else{
40 return wo.toXMLRPC();
41 }
42 }
43
44 String.prototype.toXMLRPC = function(){
45 //<![CDATA[***your text here***]]>
46 return "<string><![CDATA[" + this.replace(/\]\]/g, "] ]") + "]]></string>";//.replace(/</g, "&lt;").replace(/&/g, "&amp;")
47 }
48
49 Number.prototype.toXMLRPC = function(){
50 if(this == parseInt(this)){
51 return "<int>" + this + "</int>";
52 }
53 else if(this == parseFloat(this)){
54 return "<double>" + this + "</double>";
55 }
56 else{
57 return false.toXMLRPC();
58 }
59 }
60
61 Boolean.prototype.toXMLRPC = function(){
62 if(this) return "<boolean>1</boolean>";
63 else return "<boolean>0</boolean>";
64 }
65
66 Date.prototype.toXMLRPC = function(){
67 //Could build in possibilities to express dates
68 //in weeks or other iso8601 possibillities
69 //hmmmm ????
70 //19980717T14:08:55
71 return "<dateTime.iso8601>" + doYear(this.getUTCYear()) + doZero(this.getMonth()) + doZero(this.getUTCDate()) + "T" + doZero(this.getHours()) + ":" + doZero(this.getMinutes()) + ":" + doZero(this.getSeconds()) + "</dateTime.iso8601>";
72
73 function doZero(nr) {
74 nr = String("0" + nr);
75 return nr.substr(nr.length-2, 2);
76 }
77
78 function doYear(year) {
79 if(year > 9999 || year < 0)
80 XMLRPC.handleError(new Error("Unsupported year: " + year));
81
82 year = String("0000" + year)
83 return year.substr(year.length-4, 4);
84 }
85 }
86
87 Array.prototype.toXMLRPC = function(){
88 var retstr = "<array><data>";
89 for(var i=0;i<this.length;i++){
90 retstr += "<value>" + XMLRPC.getXML(this[i]) + "</value>";
91 }
92 return retstr + "</data></array>";
93 }
94
95 function VirtualService(servername, oRPC){
96 this.version = '0.91';
97 this.URL = servername;
98 this.multicall = false;
99 this.autoroute = true;
100 this.onerror = null;
101
102 this.rpc = oRPC;
103 this.receive = {};
104
105 this.purge = function(receive){
106 return this.rpc.purge(this, receive);
107 }
108
109 this.revert = function(){
110 this.rpc.revert(this);
111 }
112
113 this.add = function(name, alias, receive){
114 this.rpc.validateMethodName();if(this.rpc.stop){this.rpc.stop = false;return false}
115 if(receive) this.receive[name] = receive;
116 this[(alias || name)] = new Function('var args = new Array(), i;for(i=0;i<arguments.length;i++){args.push(arguments[i]);};return this.call("' + name + '", args);');
117 return true;
118 }
119
120 //internal function for sending data
121 this.call = function(name, args){
122 var info = this.rpc.send(this.URL, name, args, this.receive[name], this.multicall, this.autoroute);
123
124 if(info){
125 if(!this.multicall) this.autoroute = info[0];
126 return info[1];
127 }
128 else{
129 if(this.onerror) this.onerror(XMLRPC.lastError);
130 return false;
131 }
132 }
133 }
134
135
136 XMLRPC = {
137 routeServer : "http://www.vcdn.org/cgi-bin/rpcproxy.cgi",
138 autoroute : true,
139 multicall : false,
140
141 services : {},
142 stack : {},
143 queue : new Array(),
144 timers : new Array(),
145 timeout : 30000,
146
147 ontimeout : null,
148
149 getService : function(serviceName){
150 //serviceNames cannot contain / or .
151 if(/[\/\.]/.test(serviceName)){
152 return new VirtualService(serviceName, this);
153 }
154 else if(this.services[serviceName]){
155 return this.services[serviceName];
156 }
157 else{
158 try{
159 var ct = eval(serviceName);
160 this.services[serviceName] = new ct(this);
161 }
162 catch(e){
163 return false;
164 }
165 }
166 },
167
168 purge : function(modConst, receive){
169 if(this.stack[modConst.URL].length){
170 var info = this.send(modConst.URL, "system.multicall", [this.stack[modConst.URL]], receive, false, modConst.autoroute);
171 modConst.autoroute = info[0];
172 this.revert(modConst);
173
174 if(info){
175 modConst.autoroute = info[0];
176 return info[1];
177 }
178 else{
179 if(modConst.onerror) modConst.onerror(this.lastError);
180 return false;
181 }
182 }
183 },
184
185 revert : function(modConst){
186 this.stack[modConst.URL] = new Array();
187 },
188
189 call : function(){
190 //[optional info || receive, servername,] functionname, args......
191 var args = new Array(), i, a = arguments;
192 var servername, methodname, receive, service, info, autoroute, multicall;
193
194 if(typeof a[0] == "object"){
195 receive = a[0][0];
196 servername = a[0][1].URL;
197 methodname = a[1];
198 multicall = (a[0][1].supportsMulticall && a[0][1].multicall);
199 autoroute = a[0][1].autoroute;
200 service = a[0][1];
201 }
202 else if(typeof a[0] == "function"){
203 i = 3;
204 receive = a[0];
205 servername = a[1];
206 methodname = a[2];
207 }
208 else{
209 i = 2;
210 servername = a[0];
211 methodname = a[1];
212 }
213
214 for(i=i;i<a.length;i++){
215 args.push(a[i]);
216 }
217
218 info = this.send(servername, methodname, args, receive, multicall, autoroute);
219 if(info){
220 (service || this).autoroute = info[0];
221 return info[1];
222 }
223 else{
224 if(service && service.onerror) service.onerror(this.lastError);
225 return false;
226 }
227
228 },
229
230 /***
231 * Perform typematching on 'vDunno' and return a boolean value corresponding
232 * to the result of the evaluation-match of the mask-value stated in the 2nd argument.
233 * The 2nd argument is optional (none will be treated as a 0-mask) or a sum of
234 * several masks as follows:
235 * type/s -> mask/s
236 * --------------------
237 * undefined -> 0/1 [default]
238 * number -> 2
239 * boolean -> 4
240 * string -> 8
241 * function -> 16
242 * object -> 32
243 * --------------------
244 * Examples:
245 * Want [String] only: (eqv. (typeof(vDunno) == 'string') )
246 * Soya.Common.typematch(unknown, 8)
247 * Anything else than 'undefined' acceptable:
248 * Soya.Common.typematch(unknown)
249 * Want [Number], [Boolean] or [Function]:
250 * Soya.Common.typematch(unknown, 2 + 4 + 16)
251 * Want [Number] only:
252 * Soya.Common.typematch(unknown, 2)
253 **/
254 typematch : function (vDunno, nCase){
255 var nMask;
256 switch(typeof(vDunno)){
257 case 'number' : nMask = 2; break;
258 case 'boolean' : nMask = 4; break;
259 case 'string' : nMask = 8; break;
260 case 'function': nMask = 16; break;
261 case 'object' : nMask = 32; break;
262 default : nMask = 1; break;
263 }
264 return Boolean(nMask & (nCase || 62));
265 },
266
267 getNode : function(data, tree){
268 var nc = 0;//nodeCount
269 //node = 1
270 if(data != null){
271 for(i=0;i<data.childNodes.length;i++){
272 if(data.childNodes[i].nodeType == 1){
273 if(nc == tree[0]){
274 data = data.childNodes[i];
275 if(tree.length > 1){
276 tree.shift();
277 data = this.getNode(data, tree);
278 }
279 return data;
280 }
281 nc++
282 }
283 }
284 }
285
286 return false;
287 },
288
289 toObject : function(data){
290 var ret, i;
291 switch(data.tagName){
292 case "string":
293 var s=""
294 //Mozilla has many textnodes with a size of 4096 chars each instead of one large one.
295 //They all need to be concatenated.
296 for(var j=0;j<data.childNodes.length;j++){
297 s+=new String(data.childNodes.item(j).nodeValue);
298 }
299 return s;
300 break;
301 case "int":
302 case "i4":
303 case "double":
304 return (data.firstChild) ? new Number(data.firstChild.nodeValue) : 0;
305 break;
306 case "dateTime.iso8601":
307 /*
308 Have to read the spec to be able to completely
309 parse all the possibilities in iso8601
310 07-17-1998 14:08:55
311 19980717T14:08:55
312 */
313
314 var sn = (isIE) ? "-" : "/";
315
316 if(/^(\d{4})(\d{2})(\d{2})T(\d{2}):(\d{2}):(\d{2})/.test(data.firstChild.nodeValue)){;//data.text)){
317 return new Date(RegExp.$2 + sn + RegExp.$3 + sn +
318 RegExp.$1 + " " + RegExp.$4 + ":" +
319 RegExp.$5 + ":" + RegExp.$6);
320 }
321 else{
322 return new Date();
323 }
324
325 break;
326 case "array":
327 data = this.getNode(data, [0]);
328
329 if(data && data.tagName == "data"){
330 ret = new Array();
331
332 var i = 0;
333 while(child = this.getNode(data, [i++])){
334 ret.push(this.toObject(child));
335 }
336
337 return ret;
338 }
339 else{
340 this.handleError(new Error("Malformed XMLRPC Message1"));
341 return false;
342 }
343 break;
344 case "struct":
345 ret = {};
346
347 var i = 0;
348 while(child = this.getNode(data, [i++])){
349 if(child.tagName == "member"){
350 ret[this.getNode(child, [0]).firstChild.nodeValue] = this.toObject(this.getNode(child, [1]));
351 }
352 else{
353 this.handleError(new Error("Malformed XMLRPC Message2"));
354 return false;
355 }
356 }
357
358 return ret;
359 break;
360 case "boolean":
361 return Boolean(isNaN(parseInt(data.firstChild.nodeValue)) ? (data.firstChild.nodeValue == "true") : parseInt(data.firstChild.nodeValue))
362
363 break;
364 case "base64":
365 return this.decodeBase64(data.firstChild.nodeValue);
366 break;
367 case "value":
368 child = this.getNode(data, [0]);
369 return (!child) ? ((data.firstChild) ? new String(data.firstChild.nodeValue) : "") : this.toObject(child);
370
371 break;
372 default:
373 this.handleError(new Error("Malformed XMLRPC Message: " + data.tagName));
374 return false;
375 break;
376 }
377 },
378
379 /*** Decode Base64 ******
380 * Original Idea & Code by thomas@saltstorm.net
381 * from Soya.Encode.Base64 [http://soya.saltstorm.net]
382 **/
383 decodeBase64 : function(sEncoded){
384 // Input must be dividable with 4.
385 if(!sEncoded || (sEncoded.length % 4) > 0)
386 return sEncoded;
387
388 /* Use NN's built-in base64 decoder if available.
389 This procedure is horribly slow running under NN4,
390 so the NN built-in equivalent comes in very handy. :) */
391
392 else if(typeof(atob) != 'undefined')
393 return atob(sEncoded);
394
395 var nBits, i, sDecoded = '';
396 var base64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
397 sEncoded = sEncoded.replace(/\W|=/g, '');
398
399 for(i=0; i < sEncoded.length; i += 4){
400 nBits =
401 (base64.indexOf(sEncoded.charAt(i)) & 0xff) << 18 |
402 (base64.indexOf(sEncoded.charAt(i+1)) & 0xff) << 12 |
403 (base64.indexOf(sEncoded.charAt(i+2)) & 0xff) << 6 |
404 base64.indexOf(sEncoded.charAt(i+3)) & 0xff;
405 sDecoded += String.fromCharCode(
406 (nBits & 0xff0000) >> 16, (nBits & 0xff00) >> 8, nBits & 0xff);
407 }
408
409 // not sure if the following statement behaves as supposed under
410 // all circumstances, but tests up til now says it does.
411
412 return sDecoded.substring(0, sDecoded.length -
413 ((sEncoded.charCodeAt(i - 2) == 61) ? 2 :
414 (sEncoded.charCodeAt(i - 1) == 61 ? 1 : 0)));
415 },
416
417 getObject : function(type, message){
418 if(type == "HTTP"){
419 if(isIE)
420 obj = new ActiveXObject("microsoft.XMLHTTP");
421 else if(isNS)
422 obj = new XMLHttpRequest();
423 }
424 else if(type == "XMLDOM"){
425 if(isIE){
426 obj = new ActiveXObject("microsoft.XMLDOM");
427 obj.loadXML(message)
428 }else if(isNS){
429 obj = new DOMParser();
430 obj = obj.parseFromString(message, "text/xml");
431 }
432
433 }
434 else{
435 this.handleError(new Error("Unknown Object"));
436 }
437
438 return obj;
439 },
440
441 validateMethodName : function(name){
442 /*do Checking:
443
444 The string may only contain identifier characters,
445 upper and lower-case A-Z, the numeric characters, 0-9,
446 underscore, dot, colon and slash.
447
448 */
449 if(/^[A-Za-z0-9\._\/:]+$/.test(name))
450 return true
451 else
452 this.handleError(new Error("Incorrect method name"));
453 },
454
455 getXML : function(obj){
456 if(typeof obj == "function"){
457 this.handleError(new Error("Cannot Parse functions"));
458 }else if(obj == null || obj == undefined || (typeof obj == "number" && !isFinite(obj)))
459 return false.toXMLRPC();
460 else
461 return obj.toXMLRPC();
462 },
463
464 handleError : function(e){
465 if(!this.onerror || !this.onerror(e)){
466 //alert("An error has occured: " + e.message);
467 throw e;
468 }
469 this.stop = true;
470 this.lastError = e;
471 },
472
473 cancel : function(id){
474 //You can only cancel a request when it was executed async (I think)
475 if(!this.queue[id]) return false;
476
477 this.queue[id][0].abort();
478 return true;
479 },
480
481 send : function(serverAddress, functionName, args, receive, multicall, autoroute){
482 var id, http;
483 //default is sync
484 this.validateMethodName();
485 if(this.stop){this.stop = false; return false;}
486
487 //setting up multicall
488 multicall = (multicall != null) ? multicall : this.multicall;
489
490 if(multicall){
491 if(!this.stack[serverAddress]) this.stack[serverAddress] = new Array();
492 this.stack[serverAddress].push({methodName : functionName, params : args});
493 return true;
494 }
495
496 //creating http object
497 var http = this.getObject("HTTP");
498
499 //setting some things for async/sync transfers
500 if(!receive || isNS){;
501 async = false;
502 }
503 else{
504 async = true;
505 /* The timer functionality is implemented instead of
506 the onreadystatechange event because somehow
507 the calling of this event crashed IE5.x
508 */
509 id = this.queue.push([http, receive, null, new Date()])-1;
510
511 this.queue[id][2] = new Function("var id='" + id + "';var dt = new Date(new Date().getTime() - XMLRPC.queue[id][3].getTime());diff = parseInt(dt.getSeconds()*1000 + dt.getMilliseconds());if(diff > XMLRPC.timeout){if(XMLRPC.ontimeout) XMLRPC.ontimeout(); clearInterval(XMLRPC.timers[id]);XMLRPC.cancel(id);return};if(XMLRPC.queue[id][0].readyState == 4){XMLRPC.queue[id][0].onreadystatechange = function(){};XMLRPC.receive(id);clearInterval(XMLRPC.timers[id])}");
512 this.timers[id] = setInterval("XMLRPC.queue[" + id + "][2]()", 20);
513 }
514
515 //setting up the routing
516 autoroute = (autoroute || this.autoroute);
517
518 //'active' is only set when direct sending the message has failed
519 var srv = (autoroute == "active") ? this.routeServer : serverAddress;
520
521 try{
522 http.open('POST', srv, async);
523 http.setRequestHeader("User-Agent", "vcXMLRPC v0.91 (" + navigator.userAgent + ")");
524 http.setRequestHeader("Host", srv.replace(/^https?:\/{2}([:\[\]\-\w\.]+)\/?.*/, '$1'));
525 http.setRequestHeader("Content-type", "text/xml");
526 if(autoroute == "active"){
527 http.setRequestHeader("X-Proxy-Request", serverAddress);
528 http.setRequestHeader("X-Compress-Response", "gzip");
529 }
530 }
531 catch(e){
532 if(autoroute == true){
533 //Access has been denied, Routing call.
534 autoroute = "active";
535 if(id){
536 delete this.queue[id];
537 clearInterval(this.timers[id]);
538 }
539 return this.send(serverAddress, functionName, args, receive, multicall, autoroute);
540 }
541
542 //Routing didn't work either..Throwing error
543 this.handleError(new Error("Could not sent XMLRPC Message (Reason: Access Denied on client)"));
544 if(this.stop){this.stop = false;return false}
545 }
546
547 //Construct the message
548 var message = '<?xml version="1.0"?><methodCall><methodName>' + functionName + '</methodName><params>';
549 for(i=0;i<args.length;i++){
550 message += '<param><value>' + this.getXML(args[i]) + '</value></param>';
551 }
552 message += '</params></methodCall>';
553
554 var xmldom = this.getObject('XMLDOM', message);
555 if(self.DEBUG) alert(message);
556
557 try{
558 //send message
559 http.send(xmldom);
560 }
561 catch(e){
562 //Most likely the message timed out(what happend to your internet connection?)
563 this.handleError(new Error("XMLRPC Message not Sent(Reason: " + e.message + ")"));
564 if(this.stop){this.stop = false;return false}
565 }
566
567 if(!async && receive)
568 return [autoroute, receive(this.processResult(http))];
569 else if(receive)
570 return [autoroute, id];
571 else
572 return [autoroute, this.processResult(http)];
573 },
574
575 receive : function(id){
576 //Function for handling async transfers..
577 if(this.queue[id]){
578 var data = this.processResult(this.queue[id][0]);
579 this.queue[id][1](data);
580 delete this.queue[id];
581 }
582 else{
583 this.handleError(new Error("Error while processing queue"));
584 }
585 },
586
587 processResult : function(http){
588 if(self.DEBUG) alert(http.responseText);
589 if(http.status == 200){
590 //getIncoming message
591 dom = http.responseXML;
592
593 if(dom){
594 var rpcErr, main;
595
596 //Check for XMLRPC Errors
597 rpcErr = dom.getElementsByTagName("fault");
598 if(rpcErr.length > 0){
599 rpcErr = this.toObject(rpcErr[0].firstChild);
600 this.handleError(new Error(rpcErr.faultCode, rpcErr.faultString));
601 return false
602 }
603
604 //handle method result
605 main = dom.getElementsByTagName("param");
606 if(main.length == 0) this.handleError(new Error("Malformed XMLRPC Message"));
607 data = this.toObject(this.getNode(main[0], [0]));
608
609 //handle receiving
610 if(this.onreceive) this.onreceive(data);
611 return data;
612 }
613 else{
614 this.handleError(new Error("Malformed XMLRPC Message"));
615 }
616 }
617 else{
618 this.handleError(new Error("HTTP Exception: (" + http.status + ") " + http.statusText + "\n\n" + http.responseText));
619 }
620 }
621 }
622
623 //Smell something
624 ver = navigator.appVersion;
625 app = navigator.appName;
626 isNS = Boolean(navigator.productSub)
627 //moz_can_do_http = (parseInt(navigator.productSub) >= 20010308)
628
629 isIE = (ver.indexOf("MSIE 5") != -1 || ver.indexOf("MSIE 6") != -1) ? 1 : 0;
630 isIE55 = (ver.indexOf("MSIE 5.5") != -1) ? 1 : 0;
631
632 isOTHER = (!isNS && !isIE) ? 1 : 0;
633