Updating trunk VERSION from 2139.0 to 2140.0
[chromium-blink-merge.git] / remoting / webapp / format_iq.js
blob6fb8e55c2427e39a9712cbc2b64689495567db51
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 /**
6  * @fileoverview
7  * Module to format IQ messages so they can be displayed in the debug log.
8  */
10 'use strict';
12 /** @suppress {duplicate} */
13 var remoting = remoting || {};
15 /**
16  * @constructor
17  */
18 remoting.FormatIq = function() {
19   this.clientJid = '';
20   this.hostJid = '';
23 /**
24  * Verify that the only attributes on the given |node| are those specified
25  * in the |attrs| string.
26  *
27  * @param {Node} node The node to verify.
28  * @param {string} validAttrs Comma-separated list of valid attributes.
29  *
30  * @return {boolean} True if the node contains only valid attributes.
31  */
32 remoting.FormatIq.prototype.verifyAttributes = function(node, validAttrs) {
33   var attrs = ',' + validAttrs + ',';
34   var len = node.attributes.length;
35   for (var i = 0; i < len; i++) {
36     /** @type {Node} */
37     var attrNode = node.attributes[i];
38     var attr = attrNode.nodeName;
39     if (attrs.indexOf(',' + attr + ',') == -1) {
40       return false;
41     }
42   }
43   return true;
46 /**
47  * Record the client and host JIDs so that we can check them against the
48  * params in the IQ packets.
49  *
50  * @param {string} clientJid The client JID string.
51  * @param {string} hostJid The host JID string.
52  */
53 remoting.FormatIq.prototype.setJids = function(clientJid, hostJid) {
54   this.clientJid = clientJid;
55   this.hostJid = hostJid;
58 /**
59  * Calculate the 'pretty' version of data from the |server| node.
60  *
61  * @param {Node} server Xml node with server info.
62  *
63  * @return {?string} Formatted server string. Null if error.
64  */
65 remoting.FormatIq.prototype.calcServerString = function(server) {
66   if (!this.verifyAttributes(server, 'host,udp,tcp,tcpssl')) {
67     return null;
68   }
69   var host = server.getAttribute('host');
70   var udp = server.getAttribute('udp');
71   var tcp = server.getAttribute('tcp');
72   var tcpssl = server.getAttribute('tcpssl');
74   var str = "'" + host + "'";
75   if (udp)
76     str += ' udp:' + udp;
77   if (tcp)
78     str += ' tcp:' + tcp;
79   if (tcpssl)
80     str += ' tcpssl:' + tcpssl;
82   str += '; ';
83   return str;
86 /**
87  * Calc the 'pretty' version of channel data.
88  *
89  * @param {Node} channel Xml node with channel info.
90  *
91  * @return {?string} Formatted channel string. Null if error.
92  */
93 remoting.FormatIq.prototype.calcChannelString = function(channel) {
94   var name = channel.nodeName;
95   if (!this.verifyAttributes(channel, 'transport,version,codec')) {
96     return null;
97   }
98   var transport = channel.getAttribute('transport');
99   var version = channel.getAttribute('version');
101   var str = name + ' ' + transport + ' v' + version;
102   if (name == 'video') {
103     str += ' codec=' + channel.getAttribute('codec');
104   }
105   str += '; ';
106   return str;
110  * Pretty print the jingleinfo from the given Xml node.
112  * @param {Node} query Xml query node with jingleinfo in the child nodes.
114  * @return {?string} Pretty version of jingleinfo. Null if error.
115  */
116 remoting.FormatIq.prototype.prettyJingleinfo = function(query) {
117   var nodes = query.childNodes;
118   var stun_servers = '';
119   var result = '';
120   for (var i = 0; i < nodes.length; i++) {
121     /** @type {Node} */
122     var node = nodes[i];
123     var name = node.nodeName;
124     if (name == 'stun') {
125       var sserver = '';
126       var stun_nodes = node.childNodes;
127       for(var s = 0; s < stun_nodes.length; s++) {
128         /** @type {Node} */
129         var stun_node = stun_nodes[s];
130         var sname = stun_node.nodeName;
131         if (sname == 'server') {
132           var stun_str = this.calcServerString(stun_node);
133           if (!stun_str) {
134             return null;
135           }
136           sserver += stun_str;
137         }
138       }
139       result += '\n  stun ' + sserver;
140     } else if (name == 'relay') {
141       var token = '';
142       var rserver = '';
143       var relay_nodes = node.childNodes;
144       for(var r = 0; r < relay_nodes.length; r++) {
145         /** @type {Node} */
146         var relay_node = relay_nodes[r];
147         var rname = relay_node.nodeName;
148         if (rname == 'token') {
149           token = token + relay_node.textContent;
150         }
151         if (rname == 'server') {
152           var relay_str = this.calcServerString(relay_node);
153           if (!relay_str) {
154             return null;
155           }
156           rserver += relay_str;
157         }
158       }
159       result += '\n  relay ' + rserver + ' token: ' + token;
160     } else {
161       return null;
162     }
163   }
165   return result;
169  * Pretty print the session-initiate or session-accept info from the given
170  * Xml node.
172  * @param {Node} jingle Xml node with jingle session-initiate or session-accept
173  *                      info contained in child nodes.
175  * @return {?string} Pretty version of jingle stanza. Null if error.
176  */
177 remoting.FormatIq.prototype.prettySessionInitiateAccept = function(jingle) {
178   if (jingle.childNodes.length != 1) {
179     return null;
180   }
181   var content = jingle.firstChild;
182   if (content.nodeName != 'content') {
183     return null;
184   }
185   var content_children = content.childNodes;
186   var result = '';
187   for (var c = 0; c < content_children.length; c++) {
188     /** @type {Node} */
189     var content_child = content_children[c];
190     var cname = content_child.nodeName;
191     if (cname == 'description') {
192       var channels = '';
193       var resolution = '';
194       var auth = '';
195       var desc_children = content_child.childNodes;
196       for (var d = 0; d < desc_children.length; d++) {
197         /** @type {Node} */
198         var desc = desc_children[d];
199         var dname = desc.nodeName;
200         if (dname == 'control' || dname == 'event' || dname == 'video') {
201           var channel_str = this.calcChannelString(desc);
202           if (!channel_str) {
203             return null;
204           }
205           channels += channel_str;
206         } else if (dname == 'initial-resolution') {
207           resolution = desc.getAttribute('width') + 'x' +
208               desc.getAttribute('height');
209         } else if (dname == 'authentication') {
210           var auth_children = desc.childNodes;
211           for (var a = 0; a < auth_children.length; a++) {
212             /** @type {Node} */
213             var auth_info = auth_children[a];
214             if (auth_info.nodeName == 'auth-token') {
215               auth = auth + ' (auth-token) ' + auth_info.textContent;
216             } else if (auth_info.nodeName == 'certificate') {
217               auth = auth + ' (certificate) ' + auth_info.textContent;
218             } else if (auth_info.nodeName == 'master-key') {
219               auth = auth + ' (master-key) ' + auth_info.textContent;
220             } else {
221               return null;
222             }
223           }
224         } else {
225           return null;
226         }
227       }
228       result += '\n  channels: ' + channels;
229       result += '\n  auth: ' + auth;
230       result += '\n  initial resolution: ' + resolution;
231     } else if (cname == 'transport') {
232       // The 'transport' node is currently empty.
233       var transport_children = content_child.childNodes;
234       if (transport_children.length != 0) {
235         return null;
236       }
237     } else {
238       return null;
239     }
240   }
241   return result;
245  * Pretty print the session-terminate info from the given Xml node.
247  * @param {Node} jingle Xml node with jingle session-terminate info contained in
248  *                      child nodes.
250  * @return {?string} Pretty version of jingle session-terminate stanza. Null if
251  *                  error.
252  */
253 remoting.FormatIq.prototype.prettySessionTerminate = function(jingle) {
254   if (jingle.childNodes.length != 1) {
255     return null;
256   }
257   var reason = jingle.firstChild;
258   if (reason.nodeName != 'reason' || reason.childNodes.length != 1) {
259     return null;
260   }
261   var info = reason.firstChild;
262   if (info.nodeName == 'success' || info.nodeName == 'general-error') {
263     return '\n  reason=' + info.nodeName;
264   }
265   return null;
269  * Pretty print the transport-info info from the given Xml node.
271  * @param {Node} jingle Xml node with jingle transport info contained in child
272  *                      nodes.
274  * @return {?string} Pretty version of jingle transport-info stanza. Null if
275  *                  error.
276  */
277 remoting.FormatIq.prototype.prettyTransportInfo = function(jingle) {
278   if (jingle.childNodes.length != 1) {
279     return null;
280   }
281   var content = jingle.firstChild;
282   if (content.nodeName != 'content') {
283     return null;
284   }
285   var transport = content.firstChild;
286   if (transport.nodeName != 'transport') {
287     return null;
288   }
289   var transport_children = transport.childNodes;
290   var result = '';
291   for (var t = 0; t < transport_children.length; t++) {
292     /** @type {Node} */
293     var candidate = transport_children[t];
294     if (candidate.nodeName != 'candidate') {
295       return null;
296     }
297     if (!this.verifyAttributes(candidate, 'name,address,port,preference,' +
298                                'username,protocol,generation,password,type,' +
299                                'network')) {
300       return null;
301     }
302     var name = candidate.getAttribute('name');
303     var address = candidate.getAttribute('address');
304     var port = candidate.getAttribute('port');
305     var pref = candidate.getAttribute('preference');
306     var username = candidate.getAttribute('username');
307     var protocol = candidate.getAttribute('protocol');
308     var generation = candidate.getAttribute('generation');
309     var password = candidate.getAttribute('password');
310     var type = candidate.getAttribute('type');
311     var network = candidate.getAttribute('network');
313     var info = name + ': ' + address + ':' + port + ' ' + protocol +
314         ' name:' + username + ' pwd:' + password +
315         ' pref:' + pref +
316         ' ' + type;
317     if (network) {
318       info = info + " network:'" + network + "'";
319     }
320     result += '\n  ' + info;
321   }
322   return result;
326  * Pretty print the jingle action contained in the given Xml node.
328  * @param {Node} jingle Xml node with jingle action contained in child nodes.
329  * @param {string} action String containing the jingle action.
331  * @return {?string} Pretty version of jingle action stanze. Null if error.
332  */
333 remoting.FormatIq.prototype.prettyJingleAction = function(jingle, action) {
334   if (action == 'session-initiate' || action == 'session-accept') {
335     return this.prettySessionInitiateAccept(jingle);
336   }
337   if (action == 'session-terminate') {
338     return this.prettySessionTerminate(jingle);
339   }
340   if (action == 'transport-info') {
341     return this.prettyTransportInfo(jingle);
342   }
343   return null;
347  * Pretty print the jingle error information contained in the given Xml node.
349  * @param {Node} error Xml node containing error information in child nodes.
351  * @return {?string} Pretty version of error stanze. Null if error.
352  */
353 remoting.FormatIq.prototype.prettyError = function(error) {
354   if (!this.verifyAttributes(error, 'xmlns:err,code,type,err:hostname,' +
355                              'err:bnsname,err:stacktrace')) {
356     return null;
357   }
358   var code = error.getAttribute('code');
359   var type = error.getAttribute('type');
360   var hostname = error.getAttribute('err:hostname');
361   var bnsname = error.getAttribute('err:bnsname');
362   var stacktrace = error.getAttribute('err:stacktrace');
364   var result = '\n  error ' + code + ' ' + type + " hostname:'" +
365              hostname + "' bnsname:'" + bnsname + "'";
366   var children = error.childNodes;
367   for (var i = 0; i < children.length; i++) {
368     /** @type {Node} */
369     var child = children[i];
370     result += '\n  ' + child.nodeName;
371   }
372   if (stacktrace) {
373     var stack = stacktrace.split(' | ');
374     result += '\n  stacktrace:';
375     // We use 'length-1' because the stack trace ends with " | " which results
376     // in an empty string at the end after the split.
377     for (var s = 0; s < stack.length - 1; s++) {
378       result += '\n    ' + stack[s];
379     }
380   }
381   return result;
385  * Print out the heading line for an iq node.
387  * @param {string} action String describing action (send/receive).
388  * @param {string} id Packet id.
389  * @param {string} desc Description of iq action for this node.
390  * @param {string|null} sid Session id.
392  * @return {string} Pretty version of stanza heading info.
393  */
394 remoting.FormatIq.prototype.prettyIqHeading = function(action, id, desc,
395                                                        sid) {
396   var message = 'iq ' + action + ' id=' + id;
397   if (desc) {
398     message = message + ' ' + desc;
399   }
400   if (sid) {
401     message = message + ' sid=' + sid;
402   }
403   return message;
407  * Print out an iq 'result'-type node.
409  * @param {string} action String describing action (send/receive).
410  * @param {NodeList} iq_list Node list containing the 'result' xml.
412  * @return {?string} Pretty version of Iq result stanza. Null if error.
413  */
414 remoting.FormatIq.prototype.prettyIqResult = function(action, iq_list) {
415   /** @type {Node} */
416   var iq = iq_list[0];
417   var id = iq.getAttribute('id');
418   var iq_children = iq.childNodes;
420   if (iq_children.length == 0) {
421     return this.prettyIqHeading(action, id, 'result (empty)', null);
422   } else if (iq_children.length == 1) {
423     /** @type {Node} */
424     var child = iq_children[0];
425     if (child.nodeName == 'query') {
426       if (!this.verifyAttributes(child, 'xmlns')) {
427         return null;
428       }
429       var xmlns = child.getAttribute('xmlns');
430       if (xmlns == 'google:jingleinfo') {
431         var result = this.prettyIqHeading(action, id, 'result ' + xmlns, null);
432         result += this.prettyJingleinfo(child);
433         return result;
434       }
435       return '';
436     } else if (child.nodeName == 'rem:log-result') {
437       if (!this.verifyAttributes(child, 'xmlns:rem')) {
438         return null;
439       }
440       return this.prettyIqHeading(action, id, 'result (log-result)', null);
441     }
442   }
443   return null;
447  * Print out an Iq 'get'-type node.
449  * @param {string} action String describing action (send/receive).
450  * @param {NodeList} iq_list Node containing the 'get' xml.
452  * @return {?string} Pretty version of Iq get stanza. Null if error.
453  */
454 remoting.FormatIq.prototype.prettyIqGet = function(action, iq_list) {
455   /** @type {Node} */
456   var iq = iq_list[0];
457   var id = iq.getAttribute('id');
458   var iq_children = iq.childNodes;
460   if (iq_children.length != 1) {
461     return null;
462   }
464   /** @type {Node} */
465   var query = iq_children[0];
466   if (query.nodeName != 'query') {
467     return null;
468   }
469   if (!this.verifyAttributes(query, 'xmlns')) {
470     return null;
471   }
472   var xmlns = query.getAttribute('xmlns');
473   return this.prettyIqHeading(action, id, 'get ' + xmlns, null);
477  * Print out an iq 'set'-type node.
479  * @param {string} action String describing action (send/receive).
480  * @param {NodeList} iq_list Node containing the 'set' xml.
482  * @return {?string} Pretty version of Iq set stanza. Null if error.
483  */
484 remoting.FormatIq.prototype.prettyIqSet = function(action, iq_list) {
485   /** @type {Node} */
486   var iq = iq_list[0];
487   var id = iq.getAttribute('id');
488   var iq_children = iq.childNodes;
490   var children = iq_children.length;
491   if (children == 1) {
492     /** @type {Node} */
493     var child = iq_children[0];
494     if (child.nodeName == 'gr:log') {
495       var grlog = child;
496       if (!this.verifyAttributes(grlog, 'xmlns:gr')) {
497         return null;
498       }
500       if (grlog.childNodes.length != 1) {
501         return null;
502       }
503       var grentry = grlog.firstChild;
504       if (grentry.nodeName != 'gr:entry') {
505         return null;
506       }
507       if (!this.verifyAttributes(grentry, 'role,event-name,session-state,' +
508                                  'os-name,cpu,browser-version,' +
509                                  'webapp-version')) {
510         return null;
511       }
512       var role = grentry.getAttribute('role');
513       var event_name = grentry.getAttribute('event-name');
514       var session_state = grentry.getAttribute('session-state');
515       var os_name = grentry.getAttribute('os-name');
516       var cpu = grentry.getAttribute('cpu');
517       var browser_version = grentry.getAttribute('browser-version');
518       var webapp_version = grentry.getAttribute('webapp-version');
520       var result = this.prettyIqHeading(action, id, role + ' ' + event_name +
521                                         ' ' + session_state, null);
522       result += '\n  ' + os_name + ' ' + cpu + " browser:" + browser_version +
523                      " webapp:" + webapp_version;
524       return result;
525     }
526     if (child.nodeName == 'jingle') {
527       var jingle = child;
528       if (!this.verifyAttributes(jingle, 'xmlns,action,sid,initiator')) {
529         return null;
530       }
532       var jingle_action = jingle.getAttribute('action');
533       var sid = jingle.getAttribute('sid');
535       var result = this.prettyIqHeading(action, id, 'set ' + jingle_action,
536                                         sid);
537       var action_str = this.prettyJingleAction(jingle, jingle_action);
538       if (!action_str) {
539         return null;
540       }
541       return result + action_str;
542     }
543   }
544   return null;
548  * Print out an iq 'error'-type node.
550  * @param {string} action String describing action (send/receive).
551  * @param {NodeList} iq_list Node containing the 'error' xml.
553  * @return {?string} Pretty version of iq error stanza. Null if error parsing
554  *                  this stanza.
555  */
556 remoting.FormatIq.prototype.prettyIqError = function(action, iq_list) {
557   /** @type {Node} */
558   var iq = iq_list[0];
559   var id = iq.getAttribute('id');
560   var iq_children = iq.childNodes;
562   var children = iq_children.length;
563   if (children != 2) {
564     return null;
565   }
567   /** @type {Node} */
568   var jingle = iq_children[0];
569   if (jingle.nodeName != 'jingle') {
570     return null;
571   }
572   if (!this.verifyAttributes(jingle, 'xmlns,action,sid,initiator')) {
573     return null;
574   }
575   var jingle_action = jingle.getAttribute('action');
576   var sid = jingle.getAttribute('sid');
577   var result = this.prettyIqHeading(action, id, 'error from ' + jingle_action,
578                                     sid);
579   var action_str = this.prettyJingleAction(jingle, jingle_action);
580   if (!action_str) {
581     return null;
582   }
583   result += action_str;
585   /** @type {Node} */
586   var error = iq_children[1];
587   if (error.nodeName != 'cli:error') {
588     return null;
589   }
591   var error_str = this.prettyError(error);
592   if (!error_str) {
593     return null;
594   }
595   result += error_str;
596   return result;
600  * Try to log a pretty-print the given IQ stanza (XML).
601  * Return true if the stanza was successfully printed.
603  * @param {boolean} send True if we're sending this stanza; false for receiving.
604  * @param {string} message The XML stanza to add to the log.
606  * @return {?string} Pretty version of the Iq stanza. Null if error.
607  */
608 remoting.FormatIq.prototype.prettyIq = function(send, message) {
609   var parser = new DOMParser();
610   var xml = parser.parseFromString(message, 'text/xml');
612   var iq_list = xml.getElementsByTagName('iq');
614   if (iq_list && iq_list.length > 0) {
615     /** @type {Node} */
616     var iq = iq_list[0];
617     if (!this.verifyAttributes(iq, 'xmlns,xmlns:cli,id,to,from,type'))
618       return null;
620     // Verify that the to/from fields match the expected sender/receiver.
621     var to = iq.getAttribute('to');
622     var from = iq.getAttribute('from');
623     var action = '';
624     var bot = remoting.settings.DIRECTORY_BOT_JID;
625     if (send) {
626       if (to && to != this.hostJid && to != bot) {
627         console.warn('FormatIq: bad to: ' + to);
628         return null;
629       }
630       if (from && from != this.clientJid) {
631         console.warn('FormatIq: bad from: ' + from);
632         return null;
633       }
635       action = "send";
636       if (to == bot) {
637         action = action + " (to bot)";
638       }
639     } else {
640       if (to && to != this.clientJid) {
641         console.warn('FormatIq: bad to: ' + to);
642         return null;
643       }
644       if (from && from != this.hostJid && from != bot) {
645         console.warn('FormatIq: bad from: ' + from);
646         return null;
647       }
649       action = "receive";
650       if (from == bot) {
651         action = action + " (from bot)";
652       }
653     }
655     var type = iq.getAttribute('type');
656     if (type == 'result') {
657       return this.prettyIqResult(action, iq_list);
658     } else if (type == 'get') {
659       return this.prettyIqGet(action, iq_list);
660     } else if (type == 'set') {
661       return this.prettyIqSet(action, iq_list);
662     } else  if (type == 'error') {
663       return this.prettyIqError(action, iq_list);
664     }
665   }
667   return null;
671  * Return a pretty-formatted string for the IQ stanza being sent.
672  * If the stanza cannot be made pretty, then a string with a raw dump of the
673  * stanza will be returned.
675  * @param {string} message The XML stanza to make pretty.
677  * @return {string} Pretty version of XML stanza being sent. A raw dump of the
678  *                  stanza is returned if there was a parsing error.
679  */
680 remoting.FormatIq.prototype.prettifySendIq = function(message) {
681   var result = this.prettyIq(true, message);
682   if (!result) {
683     // Fall back to showing the raw stanza.
684     return 'Sending Iq: ' + message;
685   }
686   return result;
690  * Return a pretty-formatted string for the IQ stanza that was received.
691  * If the stanza cannot be made pretty, then a string with a raw dump of the
692  * stanza will be returned.
694  * @param {string} message The XML stanza to make pretty.
696  * @return {string} Pretty version of XML stanza that was received. A raw dump
697  *                  of the stanza is returned if there was a parsing error.
698  */
699 remoting.FormatIq.prototype.prettifyReceiveIq = function(message) {
700   var result = this.prettyIq(false, message);
701   if (!result) {
702     // Fall back to showing the raw stanza.
703     return 'Receiving Iq: ' + message;
704   }
705   return result;
708 /** @type {remoting.FormatIq} */
709 remoting.formatIq = null;