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

Unified 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, 4 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 side-by-side diff with in-line comments
Download patch
Index: remoting/webapp/xmpp_stream_parser.js
diff --git a/remoting/webapp/xmpp_stream_parser.js b/remoting/webapp/xmpp_stream_parser.js
new file mode 100644
index 0000000000000000000000000000000000000000..9a9881f2fcb863ce14993dacbf519a11c67a0b6b
--- /dev/null
+++ b/remoting/webapp/xmpp_stream_parser.js
@@ -0,0 +1,206 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+'use strict';
+
+/** @suppress {duplicate} */
+var remoting = remoting || {};
+
+/**
+ * XmppStreamParser is used to split data received over XMPP connection into
+ * individual stanzas. Data is fed to the parser using appendData() method.
+ * 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.
+ * onStanzaCallback is called for each complete stanza.
+ *
+ * @param {function(Element):void} onStanzaCallback
+ * @param {function(string):void} onErrorCallback
+ * @constructor
+ */
+remoting.XmppStreamParser = function(onStanzaCallback, onErrorCallback) {
+ this.onStanzaCallback_ = onStanzaCallback;
+ this.onErrorCallback_ = onErrorCallback;
+
+
+ /** @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.
+ this.data_ = [];
+ this.depth_ = 0;
+ this.error_ = false;
+ this.startTag_ = '';
+ this.startTagEnd_ = '';
+ this.currentStanza_ = '';
+}
+
+/** @param {ArrayBuffer} data */
+remoting.XmppStreamParser.prototype.appendData = function(data) {
+ base.debug.assert(!this.error_);
+
+ this.data_.push(data);
+
+ // Check if the newly appended data completes XML tag or a piece of text by
+ // looking for '<' and '>' char codes. This has to be done before converting
+ // data to string because the input may not contain complete UTF-8 sequence.
+ var tagStartCode = '<'.charCodeAt(0);
+ var tagEndCode = '>'.charCodeAt(0);
+ var spaceCode = ' '.charCodeAt(0);
+ var tryAgain = true;
+ while (this.data_.length > 0 && tryAgain && !this.error_) {
+ tryAgain = false;
+
+ // If we are not currently in a middle of a stanza then skip spaces (server
+ // may send spaces periodically as heartbeats) and make sure that the first
+ // character starts XML tag.
+ 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.
+ var view = new DataView(this.data_[0]);
+ var firstChar = view.getUint8(0);
+ if (firstChar == spaceCode) {
+ tryAgain = true;
+ 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
+ continue;
+ } else if (firstChar != tagStartCode) {
+ this.processError_('Received unexpected text data: ' +
+ 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
+ return;
+ }
+ }
+
+ 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.
+ for (var i = 0; i < view.byteLength; ++i) {
+ var currentChar = view.getUint8(i);
+ if (currentChar == tagStartCode) {
+ if (this.data_.length > 1 || i > 0) {
+ this.processText_(this.extractToken_(i));
+ tryAgain = true;
+ break;
+ }
+ } else if (currentChar == tagEndCode) {
+ var tag = this.extractToken_(i + 1);
+ if (tag.charAt(0) != '<') {
+ this.processError_('Received \'>\' without \'<\': ' + tag);
+ return;
+ }
+ this.processTag_(tag);
+ tryAgain = true;
+ break;
+ }
+ }
+ }
+}
+
+/** @param {string} text */
+remoting.XmppStreamParser.prototype.processText_ = function(text) {
+ // Tokenization code in appendData() shouldn't allow text tokens in between
+ // stanzas.
+ base.debug.assert(this.depth_ > 1);
+ this.currentStanza_ += text;
+}
+
+/** @param {string} tag */
+remoting.XmppStreamParser.prototype.processTag_ = function(tag) {
+ base.debug.assert(tag.charAt(0) == '<');
+ base.debug.assert(tag.charAt(tag.length - 1) == '>');
+
+ this.currentStanza_ += tag;
+
+ var openTag = tag.charAt(1) != '/';
+ if (openTag) {
+ ++this.depth_;
+ if (this.depth_ == 1) {
+ this.startTag_ = this.currentStanza_;
+ this.currentStanza_ = '';
+
+ // Create end tag matching the start.
+ var tagName =
+ this.startTag_.substr(1, this.startTag_.length - 2).split(' ', 1)[0];
+ this.startTagEnd_ = '</' + tagName + '>';
+
+ // Try parsing start together with the end
+ var parsed = this.parseTag_(this.startTag_ + this.startTagEnd_);
+ if (!parsed) {
+ this.processError_('Failed to parse start tag: ' + this.startTag_);
+ return;
+ }
+ }
+ }
+
+ var closingTag =
+ (tag.charAt(1) == '/') || (tag.charAt(tag.length - 2) == '/');
+ if (closingTag) {
+ // The first start tag is not expected to be closed.
+ if (this.depth_ <= 1) {
+ this.processError_('Unexpected closing tag: ' + tag)
+ return;
+ }
+ --this.depth_;
+ if (this.depth_ == 1) {
+ this.processCompleteStanza_();
+ this.currentStanza_ = '';
+ }
+ }
+}
+
+remoting.XmppStreamParser.prototype.processCompleteStanza_ = function() {
+ var stanza = this.startTag_ + this.currentStanza_ + this.startTagEnd_;
+ var parsed = this.parseTag_(stanza);
+ if (!parsed) {
+ this.processError_('Failed to parse stanza: ' + this.currentStanza_);
+ return;
+ }
+ 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.
+}
+
+/** @param {string} text */
+remoting.XmppStreamParser.prototype.processError_ = function(text) {
+ this.onErrorCallback_(text);
+ this.error_ = true;
+}
+
+/**
+ * Helper that assembles block of data from pieces in this.data_ up to specified
+ * position in the last buffer and then removes that data from this.data_ .
+ *
+ * @param {number} endPos Specifies how many bytes should be taken from the last
+ * buffer in this.data_ .
+ * @returns {string}
+ */
+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_.
+ var size = endPos;
+ for (var i = 0; i < this.data_.length - 1; ++i) {
+ size += this.data_[i].byteLength;
+ }
+
+ var buffer = new Uint8Array(size);
+ var pos = 0;
+ for (var i = 0; i < this.data_.length - 1; ++i) {
+ buffer.set(new Uint8Array(this.data_[i]), pos);
+ pos += this.data_[i].byteLength;
+ }
+ if (endPos > 0) {
+ buffer.set(new Uint8Array(this.data_[this.data_.length - 1], 0, endPos),
+ pos);
+ pos += endPos;
+ }
+ base.debug.assert(pos == size);
+
+ // Remove copied data from |data_|.
+ 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
+ if (dataLeft.byteLength == 0) {
+ this.data_ = []
+ } else {
+ this.data_ = [dataLeft];
+ }
+
+ return base.decodeUtf8(buffer.buffer);
+}
+
+/**
+ * @param {string} text
+ * @return {Element}
+ */
+remoting.XmppStreamParser.prototype.parseTag_ = function(text) {
+ /** @type {Document} */
+ var result = new DOMParser().parseFromString(text, 'text/xml');
+ if (result.querySelector('parsererror') != null)
+ return null;
+ return result.firstChild;
+}
« 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