Roll src/third_party/WebKit 3529d49:06e8485 (svn 202554:202555)
[chromium-blink-merge.git] / remoting / webapp / base / js / format_iq.js
blob89a5bbe7db441467774a22556652982347dcb9b3
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  * @param {string} clientJid
18  * @param {string} hostJid
19  */
20 remoting.FormatIq = function(clientJid, hostJid) {
21   /** @private */
22   this.clientJid_ = clientJid;
23   /** @private */
24   this.hostJid_ = hostJid;
27 /**
28  * Verify that the only attributes on the given |node| are those specified
29  * in the |attrs| string.
30  *
31  * @param {Node} node The node to verify.
32  * @param {string} validAttrs Comma-separated list of valid attributes.
33  *
34  * @return {boolean} True if the node contains only valid attributes.
35  */
36 remoting.FormatIq.prototype.verifyAttributes = function(node, validAttrs) {
37   var attrs = ',' + validAttrs + ',';
38   var len = node.attributes.length;
39   for (var i = 0; i < len; i++) {
40     /** @type {Node} */
41     var attrNode = node.attributes[i];
42     var attr = attrNode.nodeName;
43     if (attrs.indexOf(',' + attr + ',') == -1) {
44       return false;
45     }
46   }
47   return true;
50 /**
51  * Calculate the 'pretty' version of data from the |server| node.
52  *
53  * @param {Node} server Xml node with server info.
54  *
55  * @return {?string} Formatted server string. Null if error.
56  */
57 remoting.FormatIq.prototype.calcServerString = function(server) {
58   if (!this.verifyAttributes(server, 'host,udp,tcp,tcpssl')) {
59     return null;
60   }
61   var host = server.getAttribute('host');
62   var udp = server.getAttribute('udp');
63   var tcp = server.getAttribute('tcp');
64   var tcpssl = server.getAttribute('tcpssl');
66   var str = "'" + host + "'";
67   if (udp)
68     str += ' udp:' + udp;
69   if (tcp)
70     str += ' tcp:' + tcp;
71   if (tcpssl)
72     str += ' tcpssl:' + tcpssl;
74   str += '; ';
75   return str;
78 /**
79  * Calc the 'pretty' version of channel data.
80  *
81  * @param {Node} channel Xml node with channel info.
82  *
83  * @return {?string} Formatted channel string. Null if error.
84  */
85 remoting.FormatIq.prototype.calcChannelString = function(channel) {
86   var name = channel.nodeName;
87   if (!this.verifyAttributes(channel, 'transport,version,codec')) {
88     return null;
89   }
90   var transport = channel.getAttribute('transport');
91   var version = channel.getAttribute('version');
93   var str = name + ' ' + transport + ' v' + version;
94   if (name == 'video') {
95     str += ' codec=' + channel.getAttribute('codec');
96   }
97   str += '; ';
98   return str;
102  * Pretty print the jingleinfo from the given Xml node.
104  * @param {Node} query Xml query node with jingleinfo in the child nodes.
106  * @return {?string} Pretty version of jingleinfo. Null if error.
107  */
108 remoting.FormatIq.prototype.prettyJingleinfo = function(query) {
109   var nodes = query.childNodes;
110   var stun_servers = '';
111   var result = '';
112   for (var i = 0; i < nodes.length; i++) {
113     /** @type {Node} */
114     var node = nodes[i];
115     var name = node.nodeName;
116     if (name == 'stun') {
117       var sserver = '';
118       var stun_nodes = node.childNodes;
119       for(var s = 0; s < stun_nodes.length; s++) {
120         /** @type {Node} */
121         var stun_node = stun_nodes[s];
122         var sname = stun_node.nodeName;
123         if (sname == 'server') {
124           var stun_str = this.calcServerString(stun_node);
125           if (!stun_str) {
126             return null;
127           }
128           sserver += stun_str;
129         }
130       }
131       result += '\n  stun ' + sserver;
132     } else if (name == 'relay') {
133       var token = '';
134       var rserver = '';
135       var relay_nodes = node.childNodes;
136       for(var r = 0; r < relay_nodes.length; r++) {
137         /** @type {Node} */
138         var relay_node = relay_nodes[r];
139         var rname = relay_node.nodeName;
140         if (rname == 'token') {
141           token = token + relay_node.textContent;
142         }
143         if (rname == 'server') {
144           var relay_str = this.calcServerString(relay_node);
145           if (!relay_str) {
146             return null;
147           }
148           rserver += relay_str;
149         }
150       }
151       result += '\n  relay ' + rserver + ' token: ' + token;
152     } else {
153       return null;
154     }
155   }
157   return result;
161  * Pretty print the session-initiate or session-accept info from the given
162  * Xml node.
164  * @param {Node} jingle Xml node with jingle session-initiate or session-accept
165  *                      info contained in child nodes.
167  * @return {?string} Pretty version of jingle stanza. Null if error.
168  */
169 remoting.FormatIq.prototype.prettySessionInitiateAccept = function(jingle) {
170   if (jingle.childNodes.length != 1) {
171     return null;
172   }
173   var content = jingle.firstChild;
174   if (content.nodeName != 'content') {
175     return null;
176   }
177   var content_children = content.childNodes;
178   var result = '';
179   for (var c = 0; c < content_children.length; c++) {
180     /** @type {Node} */
181     var content_child = content_children[c];
182     var cname = content_child.nodeName;
183     if (cname == 'description') {
184       var channels = '';
185       var resolution = '';
186       var auth = '';
187       var desc_children = content_child.childNodes;
188       for (var d = 0; d < desc_children.length; d++) {
189         /** @type {Node} */
190         var desc = desc_children[d];
191         var dname = desc.nodeName;
192         if (dname == 'control' || dname == 'event' || dname == 'video') {
193           var channel_str = this.calcChannelString(desc);
194           if (!channel_str) {
195             return null;
196           }
197           channels += channel_str;
198         } else if (dname == 'initial-resolution') {
199           resolution = desc.getAttribute('width') + 'x' +
200               desc.getAttribute('height');
201         } else if (dname == 'authentication') {
202           var auth_children = desc.childNodes;
203           for (var a = 0; a < auth_children.length; a++) {
204             /** @type {Node} */
205             var auth_info = auth_children[a];
206             if (auth_info.nodeName == 'auth-token') {
207               auth = auth + ' (auth-token) ' + auth_info.textContent;
208             } else if (auth_info.nodeName == 'certificate') {
209               auth = auth + ' (certificate) ' + auth_info.textContent;
210             } else if (auth_info.nodeName == 'master-key') {
211               auth = auth + ' (master-key) ' + auth_info.textContent;
212             } else {
213               return null;
214             }
215           }
216         } else {
217           return null;
218         }
219       }
220       result += '\n  channels: ' + channels;
221       result += '\n  auth: ' + auth;
222       result += '\n  initial resolution: ' + resolution;
223     } else if (cname == 'transport') {
224       // The 'transport' node is currently empty.
225       var transport_children = content_child.childNodes;
226       if (transport_children.length != 0) {
227         return null;
228       }
229     } else {
230       return null;
231     }
232   }
233   return result;
237  * Pretty print the session-terminate info from the given Xml node.
239  * @param {Node} jingle Xml node with jingle session-terminate info contained in
240  *                      child nodes.
242  * @return {?string} Pretty version of jingle session-terminate stanza. Null if
243  *                  error.
244  */
245 remoting.FormatIq.prototype.prettySessionTerminate = function(jingle) {
246   if (jingle.childNodes.length != 1) {
247     return null;
248   }
249   var reason = jingle.firstChild;
250   if (reason.nodeName != 'reason' || reason.childNodes.length != 1) {
251     return null;
252   }
253   var info = reason.firstChild;
254   if (info.nodeName == 'success' || info.nodeName == 'general-error') {
255     return '\n  reason=' + info.nodeName;
256   }
257   return null;
261  * Pretty print the transport-info info from the given Xml node.
263  * @param {Node} jingle Xml node with jingle transport info contained in child
264  *                      nodes.
266  * @return {?string} Pretty version of jingle transport-info stanza. Null if
267  *                  error.
268  */
269 remoting.FormatIq.prototype.prettyTransportInfo = function(jingle) {
270   if (jingle.childNodes.length != 1) {
271     return null;
272   }
273   var content = jingle.firstChild;
274   if (content.nodeName != 'content') {
275     return null;
276   }
277   var transport = content.firstChild;
278   if (transport.nodeName != 'transport') {
279     return null;
280   }
281   var transport_children = transport.childNodes;
282   var result = '';
283   for (var t = 0; t < transport_children.length; t++) {
284     /** @type {Node} */
285     var candidate = transport_children[t];
286     if (candidate.nodeName != 'candidate') {
287       return null;
288     }
289     if (!this.verifyAttributes(candidate, 'name,address,port,preference,' +
290                                'username,protocol,generation,password,type,' +
291                                'network')) {
292       return null;
293     }
294     var name = candidate.getAttribute('name');
295     var address = candidate.getAttribute('address');
296     var port = candidate.getAttribute('port');
297     var pref = candidate.getAttribute('preference');
298     var username = candidate.getAttribute('username');
299     var protocol = candidate.getAttribute('protocol');
300     var generation = candidate.getAttribute('generation');
301     var password = candidate.getAttribute('password');
302     var type = candidate.getAttribute('type');
303     var network = candidate.getAttribute('network');
305     var info = name + ': ' + address + ':' + port + ' ' + protocol +
306         ' name:' + username + ' pwd:' + password +
307         ' pref:' + pref +
308         ' ' + type;
309     if (network) {
310       info = info + " network:'" + network + "'";
311     }
312     result += '\n  ' + info;
313   }
314   return result;
318  * Pretty print the jingle action contained in the given Xml node.
320  * @param {Node} jingle Xml node with jingle action contained in child nodes.
321  * @param {string} action String containing the jingle action.
323  * @return {?string} Pretty version of jingle action stanze. Null if error.
324  */
325 remoting.FormatIq.prototype.prettyJingleAction = function(jingle, action) {
326   if (action == 'session-initiate' || action == 'session-accept') {
327     return this.prettySessionInitiateAccept(jingle);
328   }
329   if (action == 'session-terminate') {
330     return this.prettySessionTerminate(jingle);
331   }
332   if (action == 'transport-info') {
333     return this.prettyTransportInfo(jingle);
334   }
335   return null;
339  * Pretty print the jingle error information contained in the given Xml node.
341  * @param {Node} error Xml node containing error information in child nodes.
343  * @return {?string} Pretty version of error stanze. Null if error.
344  */
345 remoting.FormatIq.prototype.prettyError = function(error) {
346   if (!this.verifyAttributes(error, 'xmlns:err,code,type,err:hostname,' +
347                              'err:bnsname,err:stacktrace')) {
348     return null;
349   }
350   var code = error.getAttribute('code');
351   var type = error.getAttribute('type');
352   var hostname = error.getAttribute('err:hostname');
353   var bnsname = error.getAttribute('err:bnsname');
354   var stacktrace = error.getAttribute('err:stacktrace');
356   var result = '\n  error ' + code + ' ' + type + " hostname:'" +
357              hostname + "' bnsname:'" + bnsname + "'";
358   var children = error.childNodes;
359   for (var i = 0; i < children.length; i++) {
360     /** @type {Node} */
361     var child = children[i];
362     result += '\n  ' + child.nodeName;
363   }
364   if (stacktrace) {
365     var stack = stacktrace.split(' | ');
366     result += '\n  stacktrace:';
367     // We use 'length-1' because the stack trace ends with " | " which results
368     // in an empty string at the end after the split.
369     for (var s = 0; s < stack.length - 1; s++) {
370       result += '\n    ' + stack[s];
371     }
372   }
373   return result;
377  * Print out the heading line for an iq node.
379  * @param {string} action String describing action (send/receive).
380  * @param {string} id Packet id.
381  * @param {string} desc Description of iq action for this node.
382  * @param {string|null} sid Session id.
384  * @return {string} Pretty version of stanza heading info.
385  */
386 remoting.FormatIq.prototype.prettyIqHeading = function(action, id, desc,
387                                                        sid) {
388   var message = 'iq ' + action + ' id=' + id;
389   if (desc) {
390     message = message + ' ' + desc;
391   }
392   if (sid) {
393     message = message + ' sid=' + sid;
394   }
395   return message;
399  * Print out an iq 'result'-type node.
401  * @param {string} action String describing action (send/receive).
402  * @param {NodeList} iq_list Node list containing the 'result' xml.
404  * @return {?string} Pretty version of Iq result stanza. Null if error.
405  */
406 remoting.FormatIq.prototype.prettyIqResult = function(action, iq_list) {
407   /** @type {Node} */
408   var iq = iq_list[0];
409   var id = iq.getAttribute('id');
410   var iq_children = iq.childNodes;
412   if (iq_children.length == 0) {
413     return this.prettyIqHeading(action, id, 'result (empty)', null);
414   } else if (iq_children.length == 1) {
415     /** @type {Node} */
416     var child = iq_children[0];
417     if (child.nodeName == 'query') {
418       if (!this.verifyAttributes(child, 'xmlns')) {
419         return null;
420       }
421       var xmlns = child.getAttribute('xmlns');
422       if (xmlns == 'google:jingleinfo') {
423         var result = this.prettyIqHeading(action, id, 'result ' + xmlns, null);
424         result += this.prettyJingleinfo(child);
425         return result;
426       }
427       return '';
428     } else if (child.nodeName == 'rem:log-result') {
429       if (!this.verifyAttributes(child, 'xmlns:rem')) {
430         return null;
431       }
432       return this.prettyIqHeading(action, id, 'result (log-result)', null);
433     }
434   }
435   return null;
439  * Print out an Iq 'get'-type node.
441  * @param {string} action String describing action (send/receive).
442  * @param {NodeList} iq_list Node containing the 'get' xml.
444  * @return {?string} Pretty version of Iq get stanza. Null if error.
445  */
446 remoting.FormatIq.prototype.prettyIqGet = function(action, iq_list) {
447   /** @type {Node} */
448   var iq = iq_list[0];
449   var id = iq.getAttribute('id');
450   var iq_children = iq.childNodes;
452   if (iq_children.length != 1) {
453     return null;
454   }
456   /** @type {Node} */
457   var query = iq_children[0];
458   if (query.nodeName != 'query') {
459     return null;
460   }
461   if (!this.verifyAttributes(query, 'xmlns')) {
462     return null;
463   }
464   var xmlns = query.getAttribute('xmlns');
465   return this.prettyIqHeading(action, id, 'get ' + xmlns, null);
469  * Print out an iq 'set'-type node.
471  * @param {string} action String describing action (send/receive).
472  * @param {NodeList} iq_list Node containing the 'set' xml.
474  * @return {?string} Pretty version of Iq set stanza. Null if error.
475  */
476 remoting.FormatIq.prototype.prettyIqSet = function(action, iq_list) {
477   /** @type {Node} */
478   var iq = iq_list[0];
479   var id = iq.getAttribute('id');
480   var iq_children = iq.childNodes;
482   var children = iq_children.length;
483   if (children == 1) {
484     /** @type {Node} */
485     var child = iq_children[0];
486     if (child.nodeName == 'gr:log') {
487       var grlog = child;
488       if (!this.verifyAttributes(grlog, 'xmlns:gr')) {
489         return null;
490       }
492       if (grlog.childNodes.length != 1) {
493         return null;
494       }
495       var grentry = grlog.firstChild;
496       if (grentry.nodeName != 'gr:entry') {
497         return null;
498       }
499       if (!this.verifyAttributes(grentry, 'role,event-name,session-state,' +
500                                  'os-name,cpu,browser-version,' +
501                                  'webapp-version')) {
502         return null;
503       }
504       var role = grentry.getAttribute('role');
505       var event_name = grentry.getAttribute('event-name');
506       var session_state = grentry.getAttribute('session-state');
507       var os_name = grentry.getAttribute('os-name');
508       var cpu = grentry.getAttribute('cpu');
509       var browser_version = grentry.getAttribute('browser-version');
510       var webapp_version = grentry.getAttribute('webapp-version');
512       var result = this.prettyIqHeading(action, id, role + ' ' + event_name +
513                                         ' ' + session_state, null);
514       result += '\n  ' + os_name + ' ' + cpu + " browser:" + browser_version +
515                      " webapp:" + webapp_version;
516       return result;
517     }
518     if (child.nodeName == 'jingle') {
519       var jingle = child;
520       if (!this.verifyAttributes(jingle, 'xmlns,action,sid,initiator')) {
521         return null;
522       }
524       var jingle_action = jingle.getAttribute('action');
525       var sid = jingle.getAttribute('sid');
527       var result = this.prettyIqHeading(action, id, 'set ' + jingle_action,
528                                         sid);
529       var action_str = this.prettyJingleAction(jingle, jingle_action);
530       if (!action_str) {
531         return null;
532       }
533       return result + action_str;
534     }
535   }
536   return null;
540  * Print out an iq 'error'-type node.
542  * @param {string} action String describing action (send/receive).
543  * @param {NodeList} iq_list Node containing the 'error' xml.
545  * @return {?string} Pretty version of iq error stanza. Null if error parsing
546  *                  this stanza.
547  */
548 remoting.FormatIq.prototype.prettyIqError = function(action, iq_list) {
549   /** @type {Node} */
550   var iq = iq_list[0];
551   var id = iq.getAttribute('id');
552   var iq_children = iq.childNodes;
554   var children = iq_children.length;
555   if (children != 2) {
556     return null;
557   }
559   /** @type {Node} */
560   var jingle = iq_children[0];
561   if (jingle.nodeName != 'jingle') {
562     return null;
563   }
564   if (!this.verifyAttributes(jingle, 'xmlns,action,sid,initiator')) {
565     return null;
566   }
567   var jingle_action = jingle.getAttribute('action');
568   var sid = jingle.getAttribute('sid');
569   var result = this.prettyIqHeading(action, id, 'error from ' + jingle_action,
570                                     sid);
571   var action_str = this.prettyJingleAction(jingle, jingle_action);
572   if (!action_str) {
573     return null;
574   }
575   result += action_str;
577   /** @type {Node} */
578   var error = iq_children[1];
579   if (error.nodeName != 'cli:error') {
580     return null;
581   }
583   var error_str = this.prettyError(error);
584   if (!error_str) {
585     return null;
586   }
587   result += error_str;
588   return result;
592  * Try to log a pretty-print the given IQ stanza (XML).
593  * Return true if the stanza was successfully printed.
595  * @param {boolean} send True if we're sending this stanza; false for receiving.
596  * @param {string} message The XML stanza to add to the log.
598  * @return {?string} Pretty version of the Iq stanza. Null if error.
599  */
600 remoting.FormatIq.prototype.prettyIq = function(send, message) {
601   var parser = new DOMParser();
602   var xml = parser.parseFromString(message, 'text/xml');
604   var iq_list = xml.getElementsByTagName('iq');
606   if (iq_list && iq_list.length > 0) {
607     /** @type {Node} */
608     var iq = iq_list[0];
609     if (!this.verifyAttributes(iq, 'xmlns,xmlns:cli,id,to,from,type'))
610       return null;
612     // Verify that the to/from fields match the expected sender/receiver.
613     var to = iq.getAttribute('to');
614     var from = iq.getAttribute('from');
615     var action = '';
616     var bot = remoting.settings.DIRECTORY_BOT_JID;
617     if (send) {
618       if (to && to != this.hostJid_ && to != bot) {
619         console.warn('FormatIq: bad to: ' + to);
620         return null;
621       }
622       if (from && from != this.clientJid_) {
623         console.warn('FormatIq: bad from: ' + from);
624         return null;
625       }
627       action = "send";
628       if (to == bot) {
629         action = action + " (to bot)";
630       }
631     } else {
632       if (to && to != this.clientJid_) {
633         console.warn('FormatIq: bad to: ' + to);
634         return null;
635       }
636       if (from && from != this.hostJid_ && from != bot) {
637         console.warn('FormatIq: bad from: ' + from);
638         return null;
639       }
641       action = "receive";
642       if (from == bot) {
643         action = action + " (from bot)";
644       }
645     }
647     var type = iq.getAttribute('type');
648     if (type == 'result') {
649       return this.prettyIqResult(action, iq_list);
650     } else if (type == 'get') {
651       return this.prettyIqGet(action, iq_list);
652     } else if (type == 'set') {
653       return this.prettyIqSet(action, iq_list);
654     } else  if (type == 'error') {
655       return this.prettyIqError(action, iq_list);
656     }
657   }
659   return null;
663  * Return a pretty-formatted string for the IQ stanza being sent.
664  * If the stanza cannot be made pretty, then a string with a raw dump of the
665  * stanza will be returned.
667  * @param {string} message The XML stanza to make pretty.
669  * @return {string} Pretty version of XML stanza being sent. A raw dump of the
670  *                  stanza is returned if there was a parsing error.
671  */
672 remoting.FormatIq.prototype.prettifySendIq = function(message) {
673   var result = this.prettyIq(true, message);
674   if (!result) {
675     // Fall back to showing the raw stanza.
676     return 'Sending Iq: ' + message;
677   }
678   return result;
682  * Return a pretty-formatted string for the IQ stanza that was received.
683  * If the stanza cannot be made pretty, then a string with a raw dump of the
684  * stanza will be returned.
686  * @param {string} message The XML stanza to make pretty.
688  * @return {string} Pretty version of XML stanza that was received. A raw dump
689  *                  of the stanza is returned if there was a parsing error.
690  */
691 remoting.FormatIq.prototype.prettifyReceiveIq = function(message) {
692   var result = this.prettyIq(false, message);
693   if (!result) {
694     // Fall back to showing the raw stanza.
695     return 'Receiving Iq: ' + message;
696   }
697   return result;