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.
7 * Module to format IQ messages so they can be displayed in the debug log.
12 /** @suppress {duplicate} */
13 var remoting
= remoting
|| {};
17 * @param {string} clientJid
18 * @param {string} hostJid
20 remoting
.FormatIq = function(clientJid
, hostJid
) {
22 this.clientJid_
= clientJid
;
24 this.hostJid_
= hostJid
;
28 * Verify that the only attributes on the given |node| are those specified
29 * in the |attrs| string.
31 * @param {Node} node The node to verify.
32 * @param {string} validAttrs Comma-separated list of valid attributes.
34 * @return {boolean} True if the node contains only valid attributes.
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
++) {
41 var attrNode
= node
.attributes
[i
];
42 var attr
= attrNode
.nodeName
;
43 if (attrs
.indexOf(',' + attr
+ ',') == -1) {
51 * Calculate the 'pretty' version of data from the |server| node.
53 * @param {Node} server Xml node with server info.
55 * @return {?string} Formatted server string. Null if error.
57 remoting
.FormatIq
.prototype.calcServerString = function(server
) {
58 if (!this.verifyAttributes(server
, 'host,udp,tcp,tcpssl')) {
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
+ "'";
72 str
+= ' tcpssl:' + tcpssl
;
79 * Calc the 'pretty' version of channel data.
81 * @param {Node} channel Xml node with channel info.
83 * @return {?string} Formatted channel string. Null if error.
85 remoting
.FormatIq
.prototype.calcChannelString = function(channel
) {
86 var name
= channel
.nodeName
;
87 if (!this.verifyAttributes(channel
, 'transport,version,codec')) {
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');
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.
108 remoting
.FormatIq
.prototype.prettyJingleinfo = function(query
) {
109 var nodes
= query
.childNodes
;
110 var stun_servers
= '';
112 for (var i
= 0; i
< nodes
.length
; i
++) {
115 var name
= node
.nodeName
;
116 if (name
== 'stun') {
118 var stun_nodes
= node
.childNodes
;
119 for(var s
= 0; s
< stun_nodes
.length
; s
++) {
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
);
131 result
+= '\n stun ' + sserver
;
132 } else if (name
== 'relay') {
135 var relay_nodes
= node
.childNodes
;
136 for(var r
= 0; r
< relay_nodes
.length
; r
++) {
138 var relay_node
= relay_nodes
[r
];
139 var rname
= relay_node
.nodeName
;
140 if (rname
== 'token') {
141 token
= token
+ relay_node
.textContent
;
143 if (rname
== 'server') {
144 var relay_str
= this.calcServerString(relay_node
);
148 rserver
+= relay_str
;
151 result
+= '\n relay ' + rserver
+ ' token: ' + token
;
161 * Pretty print the session-initiate or session-accept info from the given
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.
169 remoting
.FormatIq
.prototype.prettySessionInitiateAccept = function(jingle
) {
170 if (jingle
.childNodes
.length
!= 1) {
173 var content
= jingle
.firstChild
;
174 if (content
.nodeName
!= 'content') {
177 var content_children
= content
.childNodes
;
179 for (var c
= 0; c
< content_children
.length
; c
++) {
181 var content_child
= content_children
[c
];
182 var cname
= content_child
.nodeName
;
183 if (cname
== 'description') {
187 var desc_children
= content_child
.childNodes
;
188 for (var d
= 0; d
< desc_children
.length
; d
++) {
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
);
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
++) {
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
;
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) {
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
242 * @return {?string} Pretty version of jingle session-terminate stanza. Null if
245 remoting
.FormatIq
.prototype.prettySessionTerminate = function(jingle
) {
246 if (jingle
.childNodes
.length
!= 1) {
249 var reason
= jingle
.firstChild
;
250 if (reason
.nodeName
!= 'reason' || reason
.childNodes
.length
!= 1) {
253 var info
= reason
.firstChild
;
254 if (info
.nodeName
== 'success' || info
.nodeName
== 'general-error') {
255 return '\n reason=' + info
.nodeName
;
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
266 * @return {?string} Pretty version of jingle transport-info stanza. Null if
269 remoting
.FormatIq
.prototype.prettyTransportInfo = function(jingle
) {
270 if (jingle
.childNodes
.length
!= 1) {
273 var content
= jingle
.firstChild
;
274 if (content
.nodeName
!= 'content') {
277 var transport
= content
.firstChild
;
278 if (transport
.nodeName
!= 'transport') {
281 var transport_children
= transport
.childNodes
;
283 for (var t
= 0; t
< transport_children
.length
; t
++) {
285 var candidate
= transport_children
[t
];
286 if (candidate
.nodeName
!= 'candidate') {
289 if (!this.verifyAttributes(candidate
, 'name,address,port,preference,' +
290 'username,protocol,generation,password,type,' +
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
+
310 info
= info
+ " network:'" + network
+ "'";
312 result
+= '\n ' + info
;
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.
325 remoting
.FormatIq
.prototype.prettyJingleAction = function(jingle
, action
) {
326 if (action
== 'session-initiate' || action
== 'session-accept') {
327 return this.prettySessionInitiateAccept(jingle
);
329 if (action
== 'session-terminate') {
330 return this.prettySessionTerminate(jingle
);
332 if (action
== 'transport-info') {
333 return this.prettyTransportInfo(jingle
);
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.
345 remoting
.FormatIq
.prototype.prettyError = function(error
) {
346 if (!this.verifyAttributes(error
, 'xmlns:err,code,type,err:hostname,' +
347 'err:bnsname,err:stacktrace')) {
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
++) {
361 var child
= children
[i
];
362 result
+= '\n ' + child
.nodeName
;
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
];
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.
386 remoting
.FormatIq
.prototype.prettyIqHeading = function(action
, id
, desc
,
388 var message
= 'iq ' + action
+ ' id=' + id
;
390 message
= message
+ ' ' + desc
;
393 message
= message
+ ' sid=' + sid
;
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.
406 remoting
.FormatIq
.prototype.prettyIqResult = function(action
, iq_list
) {
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) {
416 var child
= iq_children
[0];
417 if (child
.nodeName
== 'query') {
418 if (!this.verifyAttributes(child
, 'xmlns')) {
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
);
428 } else if (child
.nodeName
== 'rem:log-result') {
429 if (!this.verifyAttributes(child
, 'xmlns:rem')) {
432 return this.prettyIqHeading(action
, id
, 'result (log-result)', 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.
446 remoting
.FormatIq
.prototype.prettyIqGet = function(action
, iq_list
) {
449 var id
= iq
.getAttribute('id');
450 var iq_children
= iq
.childNodes
;
452 if (iq_children
.length
!= 1) {
457 var query
= iq_children
[0];
458 if (query
.nodeName
!= 'query') {
461 if (!this.verifyAttributes(query
, 'xmlns')) {
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.
476 remoting
.FormatIq
.prototype.prettyIqSet = function(action
, iq_list
) {
479 var id
= iq
.getAttribute('id');
480 var iq_children
= iq
.childNodes
;
482 var children
= iq_children
.length
;
485 var child
= iq_children
[0];
486 if (child
.nodeName
== 'gr:log') {
488 if (!this.verifyAttributes(grlog
, 'xmlns:gr')) {
492 if (grlog
.childNodes
.length
!= 1) {
495 var grentry
= grlog
.firstChild
;
496 if (grentry
.nodeName
!= 'gr:entry') {
499 if (!this.verifyAttributes(grentry
, 'role,event-name,session-state,' +
500 'os-name,cpu,browser-version,' +
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
;
518 if (child
.nodeName
== 'jingle') {
520 if (!this.verifyAttributes(jingle
, 'xmlns,action,sid,initiator')) {
524 var jingle_action
= jingle
.getAttribute('action');
525 var sid
= jingle
.getAttribute('sid');
527 var result
= this.prettyIqHeading(action
, id
, 'set ' + jingle_action
,
529 var action_str
= this.prettyJingleAction(jingle
, jingle_action
);
533 return result
+ action_str
;
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
548 remoting
.FormatIq
.prototype.prettyIqError = function(action
, iq_list
) {
551 var id
= iq
.getAttribute('id');
552 var iq_children
= iq
.childNodes
;
554 var children
= iq_children
.length
;
560 var jingle
= iq_children
[0];
561 if (jingle
.nodeName
!= 'jingle') {
564 if (!this.verifyAttributes(jingle
, 'xmlns,action,sid,initiator')) {
567 var jingle_action
= jingle
.getAttribute('action');
568 var sid
= jingle
.getAttribute('sid');
569 var result
= this.prettyIqHeading(action
, id
, 'error from ' + jingle_action
,
571 var action_str
= this.prettyJingleAction(jingle
, jingle_action
);
575 result
+= action_str
;
578 var error
= iq_children
[1];
579 if (error
.nodeName
!= 'cli:error') {
583 var error_str
= this.prettyError(error
);
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.
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) {
609 if (!this.verifyAttributes(iq
, 'xmlns,xmlns:cli,id,to,from,type'))
612 // Verify that the to/from fields match the expected sender/receiver.
613 var to
= iq
.getAttribute('to');
614 var from = iq
.getAttribute('from');
616 var bot
= remoting
.settings
.DIRECTORY_BOT_JID
;
618 if (to
&& to
!= this.hostJid_
&& to
!= bot
) {
619 console
.warn('FormatIq: bad to: ' + to
);
622 if (from && from != this.clientJid_
) {
623 console
.warn('FormatIq: bad from: ' + from);
629 action
= action
+ " (to bot)";
632 if (to
&& to
!= this.clientJid_
) {
633 console
.warn('FormatIq: bad to: ' + to
);
636 if (from && from != this.hostJid_
&& from != bot
) {
637 console
.warn('FormatIq: bad from: ' + from);
643 action
= action
+ " (from bot)";
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
);
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.
672 remoting
.FormatIq
.prototype.prettifySendIq = function(message
) {
673 var result
= this.prettyIq(true, message
);
675 // Fall back to showing the raw stanza.
676 return 'Sending Iq: ' + message
;
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.
691 remoting
.FormatIq
.prototype.prettifyReceiveIq = function(message
) {
692 var result
= this.prettyIq(false, message
);
694 // Fall back to showing the raw stanza.
695 return 'Receiving Iq: ' + message
;