Source: main/webapp/modules/Parser.js

  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing,
  13. * software distributed under the License is distributed on an
  14. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. * KIND, either express or implied. See the License for the
  16. * specific language governing permissions and limitations
  17. * under the License.
  18. */
  19. var Guacamole = Guacamole || {};
  20. /**
  21. * Simple Guacamole protocol parser that invokes an oninstruction event when
  22. * full instructions are available from data received via receive().
  23. *
  24. * @constructor
  25. */
  26. Guacamole.Parser = function Parser() {
  27. /**
  28. * Reference to this parser.
  29. *
  30. * @private
  31. * @type {!Guacamole.Parser}
  32. */
  33. var parser = this;
  34. /**
  35. * Current buffer of received data. This buffer grows until a full
  36. * element is available. After a full element is available, that element
  37. * is flushed into the element buffer.
  38. *
  39. * @private
  40. * @type {!string}
  41. */
  42. var buffer = '';
  43. /**
  44. * Buffer of all received, complete elements. After an entire instruction
  45. * is read, this buffer is flushed, and a new instruction begins.
  46. *
  47. * @private
  48. * @type {!string[]}
  49. */
  50. var elementBuffer = [];
  51. /**
  52. * The character offset within the buffer of the current or most recently
  53. * parsed element's terminator. If sufficient characters have not yet been
  54. * read via calls to receive(), this may point to an offset well beyond the
  55. * end of the buffer. If no characters for an element have yet been read,
  56. * this will be -1.
  57. *
  58. * @private
  59. * @type {!number}
  60. */
  61. var elementEnd = -1;
  62. /**
  63. * The character offset within the buffer of the location that the parser
  64. * should start looking for the next element length search or next element
  65. * value.
  66. *
  67. * @private
  68. * @type {!number}
  69. */
  70. var startIndex = 0;
  71. /**
  72. * The declared length of the current element being parsed, in Unicode
  73. * codepoints.
  74. *
  75. * @private
  76. * @type {!number}
  77. */
  78. var elementCodepoints = 0;
  79. /**
  80. * The number of parsed characters that must accumulate in the begining of
  81. * the parse buffer before processing time is expended to truncate that
  82. * buffer and conserve memory.
  83. *
  84. * @private
  85. * @constant
  86. * @type {!number}
  87. */
  88. var BUFFER_TRUNCATION_THRESHOLD = 4096;
  89. /**
  90. * The lowest Unicode codepoint to require a surrogate pair when encoded
  91. * with UTF-16. In UTF-16, characters with codepoints at or above this
  92. * value are represented with a surrogate pair, while characters with
  93. * codepoints below this value are represented with a single character.
  94. *
  95. * @private
  96. * @constant
  97. * @type {!number}
  98. */
  99. var MIN_CODEPOINT_REQUIRES_SURROGATE = 0x10000;
  100. /**
  101. * Appends the given instruction data packet to the internal buffer of
  102. * this Guacamole.Parser, executing all completed instructions at
  103. * the beginning of this buffer, if any.
  104. *
  105. * @param {!string} packet
  106. * The instruction data to receive.
  107. *
  108. * @param {!boolean} [isBuffer=false]
  109. * Whether the provided data should be treated as an instruction buffer
  110. * that grows continuously. If true, the data provided to receive()
  111. * MUST always start with the data provided to the previous call. If
  112. * false (the default), only the new data should be provided to
  113. * receive(), and previously-received data will automatically be
  114. * buffered by the parser as needed.
  115. */
  116. this.receive = function receive(packet, isBuffer) {
  117. if (isBuffer)
  118. buffer = packet;
  119. else {
  120. // Truncate buffer as necessary
  121. if (startIndex > BUFFER_TRUNCATION_THRESHOLD && elementEnd >= startIndex) {
  122. buffer = buffer.substring(startIndex);
  123. // Reset parse relative to truncation
  124. elementEnd -= startIndex;
  125. startIndex = 0;
  126. }
  127. // Append data to buffer ONLY if there is outstanding data present. It
  128. // is otherwise much faster to simply parse the received buffer as-is,
  129. // and tunnel implementations can take advantage of this by preferring
  130. // to send only complete instructions. Both the HTTP and WebSocket
  131. // tunnel implementations included with Guacamole already do this.
  132. if (buffer.length)
  133. buffer += packet;
  134. else
  135. buffer = packet;
  136. }
  137. // While search is within currently received data
  138. while (elementEnd < buffer.length) {
  139. // If we are waiting for element data
  140. if (elementEnd >= startIndex) {
  141. // If we have enough data in the buffer to fill the element
  142. // value, but the number of codepoints in the expected substring
  143. // containing the element value value is less that its declared
  144. // length, that can only be because the element contains
  145. // characters split between high and low surrogates, and the
  146. // actual end of the element value is further out. The minimum
  147. // number of additional characters that must be read to satisfy
  148. // the declared length is simply the difference between the
  149. // number of codepoints actually present vs. the expected
  150. // length.
  151. var codepoints = Guacamole.Parser.codePointCount(buffer, startIndex, elementEnd);
  152. if (codepoints < elementCodepoints) {
  153. elementEnd += elementCodepoints - codepoints;
  154. continue;
  155. }
  156. // If the current element ends with a character involving both
  157. // a high and low surrogate, elementEnd points to the low
  158. // surrogate and NOT the element terminator. We must shift the
  159. // end and reevaluate.
  160. else if (elementCodepoints && buffer.codePointAt(elementEnd - 1) >= MIN_CODEPOINT_REQUIRES_SURROGATE) {
  161. elementEnd++;
  162. continue;
  163. }
  164. // We now have enough data for the element. Parse.
  165. var element = buffer.substring(startIndex, elementEnd);
  166. var terminator = buffer.substring(elementEnd, elementEnd + 1);
  167. // Add element to array
  168. elementBuffer.push(element);
  169. // If last element, handle instruction
  170. if (terminator === ';') {
  171. // Get opcode
  172. var opcode = elementBuffer.shift();
  173. // Call instruction handler.
  174. if (parser.oninstruction !== null)
  175. parser.oninstruction(opcode, elementBuffer);
  176. // Clear elements
  177. elementBuffer = [];
  178. // Immediately truncate buffer if its contents have been
  179. // completely parsed, so that the next call to receive()
  180. // need not append to the buffer unnecessarily
  181. if (!isBuffer && elementEnd + 1 === buffer.length) {
  182. elementEnd = -1;
  183. buffer = '';
  184. }
  185. }
  186. else if (terminator !== ',')
  187. throw new Error('Element terminator of instruction was not ";" nor ",".');
  188. // Start searching for length at character after
  189. // element terminator
  190. startIndex = elementEnd + 1;
  191. }
  192. // Search for end of length
  193. var lengthEnd = buffer.indexOf('.', startIndex);
  194. if (lengthEnd !== -1) {
  195. // Parse length
  196. elementCodepoints = parseInt(buffer.substring(elementEnd + 1, lengthEnd));
  197. if (isNaN(elementCodepoints))
  198. throw new Error('Non-numeric character in element length.');
  199. // Calculate start of element
  200. startIndex = lengthEnd + 1;
  201. // Calculate location of element terminator
  202. elementEnd = startIndex + elementCodepoints;
  203. }
  204. // If no period yet, continue search when more data
  205. // is received
  206. else {
  207. startIndex = buffer.length;
  208. break;
  209. }
  210. } // end parse loop
  211. };
  212. /**
  213. * Fired once for every complete Guacamole instruction received, in order.
  214. *
  215. * @event
  216. * @param {!string} opcode
  217. * The Guacamole instruction opcode.
  218. *
  219. * @param {!string[]} parameters
  220. * The parameters provided for the instruction, if any.
  221. */
  222. this.oninstruction = null;
  223. };
  224. /**
  225. * Returns the number of Unicode codepoints (not code units) within the given
  226. * string. If character offsets are provided, only codepoints between those
  227. * offsets are counted. Unlike the length property of a string, this function
  228. * counts proper surrogate pairs as a single codepoint. High and low surrogate
  229. * characters that are not part of a proper surrogate pair are counted
  230. * separately as individual codepoints.
  231. *
  232. * @param {!string} str
  233. * The string whose contents should be inspected.
  234. *
  235. * @param {number} [start=0]
  236. * The index of the location in the given string where codepoint counting
  237. * should start. If omitted, counting will begin at the start of the
  238. * string.
  239. *
  240. * @param {number} [end]
  241. * The index of the first location in the given string after where counting
  242. * should stop (the character after the last character being counted). If
  243. * omitted, all characters after the start location will be counted.
  244. *
  245. * @returns {!number}
  246. * The number of Unicode codepoints within the requested portion of the
  247. * given string.
  248. */
  249. Guacamole.Parser.codePointCount = function codePointCount(str, start, end) {
  250. // Count only characters within the specified region
  251. str = str.substring(start || 0, end);
  252. // Locate each proper Unicode surrogate pair (one high surrogate followed
  253. // by one low surrogate)
  254. var surrogatePairs = str.match(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g);
  255. // Each surrogate pair represents a single codepoint but is represented by
  256. // two characters in a JavaScript string, and thus is counted twice toward
  257. // string length. Subtracting the number of surrogate pairs adjusts that
  258. // length value such that it gives us the number of codepoints.
  259. return str.length - (surrogatePairs ? surrogatePairs.length : 0);
  260. };
  261. /**
  262. * Converts each of the values within the given array to strings, formatting
  263. * those strings as length-prefixed elements of a complete Guacamole
  264. * instruction.
  265. *
  266. * @param {!Array.<*>} elements
  267. * The values that should be encoded as the elements of a Guacamole
  268. * instruction. Order of these elements is preserved. This array MUST have
  269. * at least one element.
  270. *
  271. * @returns {!string}
  272. * A complete Guacamole instruction consisting of each of the provided
  273. * element values, in order.
  274. */
  275. Guacamole.Parser.toInstruction = function toInstruction(elements) {
  276. /**
  277. * Converts the given value to a length/string pair for use as an
  278. * element in a Guacamole instruction.
  279. *
  280. * @private
  281. * @param {*} value
  282. * The value to convert.
  283. *
  284. * @return {!string}
  285. * The converted value.
  286. */
  287. var toElement = function toElement(value) {
  288. var str = '' + value;
  289. return Guacamole.Parser.codePointCount(str) + "." + str;
  290. };
  291. var instr = toElement(elements[0]);
  292. for (var i = 1; i < elements.length; i++)
  293. instr += ',' + toElement(elements[i]);
  294. return instr + ';';
  295. };