Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(88)

Side by Side Diff: remoting/webapp/xmpp_stream_parser.js

Issue 514343002: XMPP implementation in JavaScript. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 6 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2014 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.
4
5 'use strict';
6
7 /** @suppress {duplicate} */
8 var remoting = remoting || {};
9
10 /**
11 * XmppStreamParser is used to split data received over XMPP connection into
12 * individual stanzas. Data is fed to the parser using appendData() method.
13 * onStartCallback is called once for the stream header and after that
Jamie 2014/08/29 02:14:09 There is no onStartCallback.
Sergey Ulanov 2014/08/29 23:40:31 Done.
14 * onStanzaCallback is called for each complete stanza.
15 *
16 * @param {function(Element):void} onStanzaCallback
17 * @param {function(string):void} onErrorCallback
18 * @constructor
19 */
20 remoting.XmppStreamParser = function(onStanzaCallback, onErrorCallback) {
21 this.onStanzaCallback_ = onStanzaCallback;
22 this.onErrorCallback_ = onErrorCallback;
23
24
25 /** @type {Array.<ArrayBuffer>} */
Jamie 2014/08/29 02:14:09 Please add descriptions of these members.
Sergey Ulanov 2014/08/29 23:40:31 Done.
26 this.data_ = [];
27 this.depth_ = 0;
28 this.error_ = false;
29 this.startTag_ = '';
30 this.startTagEnd_ = '';
31 this.currentStanza_ = '';
32 }
33
34 /** @param {ArrayBuffer} data */
35 remoting.XmppStreamParser.prototype.appendData = function(data) {
36 base.debug.assert(!this.error_);
37
38 this.data_.push(data);
39
40 // Check if the newly appended data completes XML tag or a piece of text by
41 // looking for '<' and '>' char codes. This has to be done before converting
42 // data to string because the input may not contain complete UTF-8 sequence.
43 var tagStartCode = '<'.charCodeAt(0);
44 var tagEndCode = '>'.charCodeAt(0);
45 var spaceCode = ' '.charCodeAt(0);
46 var tryAgain = true;
47 while (this.data_.length > 0 && tryAgain && !this.error_) {
48 tryAgain = false;
49
50 // If we are not currently in a middle of a stanza then skip spaces (server
51 // may send spaces periodically as heartbeats) and make sure that the first
52 // character starts XML tag.
53 if (this.depth_ <= 1) {
kelvinp 2014/08/29 01:37:10 base.debug.assert(this.depth_ > 0);
Sergey Ulanov 2014/08/29 23:40:30 depth_ can be 0 here.
54 var view = new DataView(this.data_[0]);
55 var firstChar = view.getUint8(0);
56 if (firstChar == spaceCode) {
57 tryAgain = true;
58 this.extractToken_(1);
Jamie 2014/08/29 02:14:09 This doesn't look right. It seems to be testing wh
Sergey Ulanov 2014/08/29 23:40:31 That's for catching this. I've simplified this cod
59 continue;
60 } else if (firstChar != tagStartCode) {
61 this.processError_('Received unexpected text data: ' +
62 base.decodeUtf8(this.data_[0]));
Jamie 2014/08/29 02:14:10 Your earlier comment stated that this might be inc
Sergey Ulanov 2014/08/29 23:40:31 Good point. decodeUtf8() throws in that case. I ch
63 return;
64 }
65 }
66
67 var view = new DataView(this.data_[this.data_.length - 1]);
Jamie 2014/08/29 02:14:10 It's not obvious what this second loop is doing. I
Sergey Ulanov 2014/08/29 23:40:31 Done.
68 for (var i = 0; i < view.byteLength; ++i) {
69 var currentChar = view.getUint8(i);
70 if (currentChar == tagStartCode) {
71 if (this.data_.length > 1 || i > 0) {
72 this.processText_(this.extractToken_(i));
73 tryAgain = true;
74 break;
75 }
76 } else if (currentChar == tagEndCode) {
77 var tag = this.extractToken_(i + 1);
78 if (tag.charAt(0) != '<') {
79 this.processError_('Received \'>\' without \'<\': ' + tag);
80 return;
81 }
82 this.processTag_(tag);
83 tryAgain = true;
84 break;
85 }
86 }
87 }
88 }
89
90 /** @param {string} text */
91 remoting.XmppStreamParser.prototype.processText_ = function(text) {
92 // Tokenization code in appendData() shouldn't allow text tokens in between
93 // stanzas.
94 base.debug.assert(this.depth_ > 1);
95 this.currentStanza_ += text;
96 }
97
98 /** @param {string} tag */
99 remoting.XmppStreamParser.prototype.processTag_ = function(tag) {
100 base.debug.assert(tag.charAt(0) == '<');
101 base.debug.assert(tag.charAt(tag.length - 1) == '>');
102
103 this.currentStanza_ += tag;
104
105 var openTag = tag.charAt(1) != '/';
106 if (openTag) {
107 ++this.depth_;
108 if (this.depth_ == 1) {
109 this.startTag_ = this.currentStanza_;
110 this.currentStanza_ = '';
111
112 // Create end tag matching the start.
113 var tagName =
114 this.startTag_.substr(1, this.startTag_.length - 2).split(' ', 1)[0];
115 this.startTagEnd_ = '</' + tagName + '>';
116
117 // Try parsing start together with the end
118 var parsed = this.parseTag_(this.startTag_ + this.startTagEnd_);
119 if (!parsed) {
120 this.processError_('Failed to parse start tag: ' + this.startTag_);
121 return;
122 }
123 }
124 }
125
126 var closingTag =
127 (tag.charAt(1) == '/') || (tag.charAt(tag.length - 2) == '/');
128 if (closingTag) {
129 // The first start tag is not expected to be closed.
130 if (this.depth_ <= 1) {
131 this.processError_('Unexpected closing tag: ' + tag)
132 return;
133 }
134 --this.depth_;
135 if (this.depth_ == 1) {
136 this.processCompleteStanza_();
137 this.currentStanza_ = '';
138 }
139 }
140 }
141
142 remoting.XmppStreamParser.prototype.processCompleteStanza_ = function() {
143 var stanza = this.startTag_ + this.currentStanza_ + this.startTagEnd_;
144 var parsed = this.parseTag_(stanza);
145 if (!parsed) {
146 this.processError_('Failed to parse stanza: ' + this.currentStanza_);
147 return;
148 }
149 this.onStanzaCallback_(parsed.childNodes[0]);
kelvinp 2014/08/29 01:37:10 childNodes also contains all nodes, including text
Sergey Ulanov 2014/08/29 23:40:31 Done.
150 }
151
152 /** @param {string} text */
153 remoting.XmppStreamParser.prototype.processError_ = function(text) {
154 this.onErrorCallback_(text);
155 this.error_ = true;
156 }
157
158 /**
159 * Helper that assembles block of data from pieces in this.data_ up to specified
160 * position in the last buffer and then removes that data from this.data_ .
161 *
162 * @param {number} endPos Specifies how many bytes should be taken from the last
163 * buffer in this.data_ .
164 * @returns {string}
165 */
166 remoting.XmppStreamParser.prototype.extractToken_ = function(endPos) {
Jamie 2014/08/29 02:14:09 What is a "token" in this context? This seems to b
Sergey Ulanov 2014/08/29 23:40:31 Renamed extractStringFromBuffer_.
167 var size = endPos;
168 for (var i = 0; i < this.data_.length - 1; ++i) {
169 size += this.data_[i].byteLength;
170 }
171
172 var buffer = new Uint8Array(size);
173 var pos = 0;
174 for (var i = 0; i < this.data_.length - 1; ++i) {
175 buffer.set(new Uint8Array(this.data_[i]), pos);
176 pos += this.data_[i].byteLength;
177 }
178 if (endPos > 0) {
179 buffer.set(new Uint8Array(this.data_[this.data_.length - 1], 0, endPos),
180 pos);
181 pos += endPos;
182 }
183 base.debug.assert(pos == size);
184
185 // Remove copied data from |data_|.
186 var dataLeft = this.data_[this.data_.length - 1].slice(endPos);
Jamie 2014/08/29 02:14:10 I suggest calling this dataRemaining, since it's a
Sergey Ulanov 2014/08/29 23:40:31 Done, though some people would say that arrays sta
187 if (dataLeft.byteLength == 0) {
188 this.data_ = []
189 } else {
190 this.data_ = [dataLeft];
191 }
192
193 return base.decodeUtf8(buffer.buffer);
194 }
195
196 /**
197 * @param {string} text
198 * @return {Element}
199 */
200 remoting.XmppStreamParser.prototype.parseTag_ = function(text) {
201 /** @type {Document} */
202 var result = new DOMParser().parseFromString(text, 'text/xml');
203 if (result.querySelector('parsererror') != null)
204 return null;
205 return result.firstChild;
206 }
OLDNEW
« remoting/webapp/xmpp_login_handler.js ('K') | « remoting/webapp/xmpp_login_handler.js ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698