1 /* 2 * Copyright (C) 2013 Glyptodon LLC 3 * 4 * Permission is hereby granted, free of charge, to any person obtaining a copy 5 * of this software and associated documentation files (the "Software"), to deal 6 * in the Software without restriction, including without limitation the rights 7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 * copies of the Software, and to permit persons to whom the Software is 9 * furnished to do so, subject to the following conditions: 10 * 11 * The above copyright notice and this permission notice shall be included in 12 * all copies or substantial portions of the Software. 13 * 14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 * THE SOFTWARE. 21 */ 22 23 var Guacamole = Guacamole || {}; 24 25 /** 26 * A reader which automatically handles the given input stream, returning 27 * strictly text data. Note that this object will overwrite any installed event 28 * handlers on the given Guacamole.InputStream. 29 * 30 * @constructor 31 * @param {Guacamole.InputStream} stream The stream that data will be read 32 * from. 33 */ 34 Guacamole.StringReader = function(stream) { 35 36 /** 37 * Reference to this Guacamole.InputStream. 38 * @private 39 */ 40 var guac_reader = this; 41 42 /** 43 * Wrapped Guacamole.ArrayBufferReader. 44 * @private 45 * @type Guacamole.ArrayBufferReader 46 */ 47 var array_reader = new Guacamole.ArrayBufferReader(stream); 48 49 /** 50 * The number of bytes remaining for the current codepoint. 51 * 52 * @type Number 53 */ 54 var bytes_remaining = 0; 55 56 /** 57 * The current codepoint value, as calculated from bytes read so far. 58 * @type Number 59 */ 60 var codepoint = 0; 61 62 /** 63 * Decodes the given UTF-8 data into a Unicode string. The data may end in 64 * the middle of a multibyte character. 65 * 66 * @private 67 * @param {ArrayBuffer} buffer Arbitrary UTF-8 data. 68 * @return {String} A decoded Unicode string. 69 */ 70 function __decode_utf8(buffer) { 71 72 var text = ""; 73 74 var bytes = new Uint8Array(buffer); 75 for (var i=0; i<bytes.length; i++) { 76 77 // Get current byte 78 var value = bytes[i]; 79 80 // Start new codepoint if nothing yet read 81 if (bytes_remaining === 0) { 82 83 // 1 byte (0xxxxxxx) 84 if ((value | 0x7F) === 0x7F) 85 text += String.fromCharCode(value); 86 87 // 2 byte (110xxxxx) 88 else if ((value | 0x1F) === 0xDF) { 89 codepoint = value & 0x1F; 90 bytes_remaining = 1; 91 } 92 93 // 3 byte (1110xxxx) 94 else if ((value | 0x0F )=== 0xEF) { 95 codepoint = value & 0x0F; 96 bytes_remaining = 2; 97 } 98 99 // 4 byte (11110xxx) 100 else if ((value | 0x07) === 0xF7) { 101 codepoint = value & 0x07; 102 bytes_remaining = 3; 103 } 104 105 // Invalid byte 106 else 107 text += "\uFFFD"; 108 109 } 110 111 // Continue existing codepoint (10xxxxxx) 112 else if ((value | 0x3F) === 0xBF) { 113 114 codepoint = (codepoint << 6) | (value & 0x3F); 115 bytes_remaining--; 116 117 // Write codepoint if finished 118 if (bytes_remaining === 0) 119 text += String.fromCharCode(codepoint); 120 121 } 122 123 // Invalid byte 124 else { 125 bytes_remaining = 0; 126 text += "\uFFFD"; 127 } 128 129 } 130 131 return text; 132 133 } 134 135 // Receive blobs as strings 136 array_reader.ondata = function(buffer) { 137 138 // Decode UTF-8 139 var text = __decode_utf8(buffer); 140 141 // Call handler, if present 142 if (guac_reader.ontext) 143 guac_reader.ontext(text); 144 145 }; 146 147 // Simply call onend when end received 148 array_reader.onend = function() { 149 if (guac_reader.onend) 150 guac_reader.onend(); 151 }; 152 153 /** 154 * Fired once for every blob of text data received. 155 * 156 * @event 157 * @param {String} text The data packet received. 158 */ 159 this.ontext = null; 160 161 /** 162 * Fired once this stream is finished and no further data will be written. 163 * @event 164 */ 165 this.onend = null; 166 167 };