spice-html5-rails 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. data/COPYING +674 -0
  2. data/COPYING.LESSER +165 -0
  3. data/LICENSE.txt +15 -0
  4. data/README.md +35 -0
  5. data/lib/spice-html5-rails/version.rb +7 -0
  6. data/lib/spice-html5-rails.rb +10 -0
  7. data/vendor/assets/javascripts/spice-html5.js +25 -0
  8. data/vendor/assets/javascripts/spiceHTML5/atKeynames.js +183 -0
  9. data/vendor/assets/javascripts/spiceHTML5/bitmap.js +51 -0
  10. data/vendor/assets/javascripts/spiceHTML5/cursor.js +92 -0
  11. data/vendor/assets/javascripts/spiceHTML5/display.js +806 -0
  12. data/vendor/assets/javascripts/spiceHTML5/enums.js +282 -0
  13. data/vendor/assets/javascripts/spiceHTML5/inputs.js +271 -0
  14. data/vendor/assets/javascripts/spiceHTML5/lz.js +166 -0
  15. data/vendor/assets/javascripts/spiceHTML5/main.js +177 -0
  16. data/vendor/assets/javascripts/spiceHTML5/png.js +256 -0
  17. data/vendor/assets/javascripts/spiceHTML5/quic.js +1335 -0
  18. data/vendor/assets/javascripts/spiceHTML5/spiceconn.js +455 -0
  19. data/vendor/assets/javascripts/spiceHTML5/spicedataview.js +96 -0
  20. data/vendor/assets/javascripts/spiceHTML5/spicemsg.js +883 -0
  21. data/vendor/assets/javascripts/spiceHTML5/spicetype.js +480 -0
  22. data/vendor/assets/javascripts/spiceHTML5/thirdparty/jsbn.js +589 -0
  23. data/vendor/assets/javascripts/spiceHTML5/thirdparty/prng4.js +79 -0
  24. data/vendor/assets/javascripts/spiceHTML5/thirdparty/rng.js +102 -0
  25. data/vendor/assets/javascripts/spiceHTML5/thirdparty/rsa.js +146 -0
  26. data/vendor/assets/javascripts/spiceHTML5/thirdparty/sha1.js +346 -0
  27. data/vendor/assets/javascripts/spiceHTML5/ticket.js +250 -0
  28. data/vendor/assets/javascripts/spiceHTML5/utils.js +261 -0
  29. data/vendor/assets/javascripts/spiceHTML5/wire.js +123 -0
  30. metadata +108 -0
@@ -0,0 +1,455 @@
1
+ "use strict";
2
+ /*
3
+ Copyright (C) 2012 by Jeremy P. White <jwhite@codeweavers.com>
4
+
5
+ This file is part of spice-html5.
6
+
7
+ spice-html5 is free software: you can redistribute it and/or modify
8
+ it under the terms of the GNU Lesser General Public License as published by
9
+ the Free Software Foundation, either version 3 of the License, or
10
+ (at your option) any later version.
11
+
12
+ spice-html5 is distributed in the hope that it will be useful,
13
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ GNU Lesser General Public License for more details.
16
+
17
+ You should have received a copy of the GNU Lesser General Public License
18
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
19
+ */
20
+
21
+ /*----------------------------------------------------------------------------
22
+ ** SpiceConn
23
+ ** This is the base Javascript class for establishing and
24
+ ** managing a connection to a Spice Server.
25
+ ** It is used to provide core functionality to the Spice main,
26
+ ** display, inputs, and cursor channels. See main.js for
27
+ ** usage.
28
+ **--------------------------------------------------------------------------*/
29
+ function SpiceConn(o)
30
+ {
31
+ if (o === undefined || o.uri === undefined || ! o.uri)
32
+ throw new Error("You must specify a uri");
33
+
34
+ this.ws = new WebSocket(o.uri, 'binary');
35
+
36
+ if (! this.ws.binaryType)
37
+ throw new Error("WebSocket doesn't support binaryType. Try a different browser.");
38
+
39
+ this.connection_id = o.connection_id !== undefined ? o.connection_id : 0;
40
+ this.type = o.type !== undefined ? o.type : SPICE_CHANNEL_MAIN;
41
+ this.chan_id = o.chan_id !== undefined ? o.chan_id : 0;
42
+ if (o.parent !== undefined)
43
+ {
44
+ this.parent = o.parent;
45
+ this.message_id = o.parent.message_id;
46
+ this.password = o.parent.password;
47
+ }
48
+ if (o.screen_id !== undefined)
49
+ this.screen_id = o.screen_id;
50
+ if (o.dump_id !== undefined)
51
+ this.dump_id = o.dump_id;
52
+ if (o.message_id !== undefined)
53
+ this.message_id = o.message_id;
54
+ if (o.password !== undefined)
55
+ this.password = o.password;
56
+ if (o.onerror !== undefined)
57
+ this.onerror = o.onerror;
58
+ if (o.onsuccess !== undefined)
59
+ this.onsuccess = o.onsuccess;
60
+
61
+ this.state = "connecting";
62
+ this.ws.parent = this;
63
+ this.wire_reader = new SpiceWireReader(this, this.process_inbound);
64
+ this.messages_sent = 0;
65
+ this.warnings = [];
66
+
67
+ this.ws.addEventListener('open', function(e) {
68
+ DEBUG > 0 && console.log(">> WebSockets.onopen");
69
+ DEBUG > 0 && console.log("id " + this.parent.connection_id +"; type " + this.parent.type);
70
+
71
+ /***********************************************************************
72
+ ** WHERE IT ALL REALLY BEGINS
73
+ ***********************************************************************/
74
+ this.parent.send_hdr();
75
+ this.parent.wire_reader.request(SpiceLinkHeader.prototype.buffer_size());
76
+ this.parent.state = "start";
77
+ });
78
+ this.ws.addEventListener('error', function(e) {
79
+ this.parent.log_err(">> WebSockets.onerror" + e.toString());
80
+ this.parent.report_error(e);
81
+ });
82
+ this.ws.addEventListener('close', function(e) {
83
+ DEBUG > 0 && console.log(">> WebSockets.onclose");
84
+ DEBUG > 0 && console.log("id " + this.parent.connection_id +"; type " + this.parent.type);
85
+ DEBUG > 0 && console.log(e);
86
+ if (this.parent.state != "closing" && this.parent.state != "error" && this.parent.onerror !== undefined)
87
+ {
88
+ var e;
89
+ if (this.parent.state == "connecting")
90
+ e = new Error("Connection refused.");
91
+ else if (this.parent.state == "start" || this.parent.state == "link")
92
+ e = new Error("Unexpected protocol mismatch.");
93
+ else if (this.parent.state == "ticket")
94
+ e = new Error("Bad password.");
95
+ else
96
+ e = new Error("Unexpected close while " + this.parent.state);
97
+
98
+ this.parent.onerror(e);
99
+ this.parent.log_err(e.toString());
100
+ }
101
+ });
102
+
103
+ if (this.ws.readyState == 2 || this.ws.readyState == 3)
104
+ throw new Error("Unable to connect to " + o.uri);
105
+
106
+ this.timeout = window.setTimeout(spiceconn_timeout, SPICE_CONNECT_TIMEOUT, this);
107
+ }
108
+
109
+ SpiceConn.prototype =
110
+ {
111
+ send_hdr : function ()
112
+ {
113
+ var hdr = new SpiceLinkHeader;
114
+ var msg = new SpiceLinkMess;
115
+
116
+ msg.connection_id = this.connection_id;
117
+ msg.channel_type = this.type;
118
+ // FIXME - we're not setting a channel_id...
119
+ msg.common_caps.push(
120
+ (1 << SPICE_COMMON_CAP_PROTOCOL_AUTH_SELECTION) |
121
+ (1 << SPICE_COMMON_CAP_MINI_HEADER)
122
+ );
123
+
124
+ hdr.size = msg.buffer_size();
125
+
126
+ var mb = new ArrayBuffer(hdr.buffer_size() + msg.buffer_size());
127
+ hdr.to_buffer(mb);
128
+ msg.to_buffer(mb, hdr.buffer_size());
129
+
130
+ DEBUG > 1 && console.log("Sending header:");
131
+ DEBUG > 2 && hexdump_buffer(mb);
132
+ this.ws.send(mb);
133
+ },
134
+
135
+ send_ticket: function(ticket)
136
+ {
137
+ var hdr = new SpiceLinkAuthTicket();
138
+ hdr.auth_mechanism = SPICE_COMMON_CAP_AUTH_SPICE;
139
+ // FIXME - we need to implement RSA to make this work right
140
+ hdr.encrypted_data = ticket;
141
+ var mb = new ArrayBuffer(hdr.buffer_size());
142
+
143
+ hdr.to_buffer(mb);
144
+ DEBUG > 1 && console.log("Sending ticket:");
145
+ DEBUG > 2 && hexdump_buffer(mb);
146
+ this.ws.send(mb);
147
+ },
148
+
149
+ send_msg: function(msg)
150
+ {
151
+ var mb = new ArrayBuffer(msg.buffer_size());
152
+ msg.to_buffer(mb);
153
+ this.messages_sent++;
154
+ DEBUG > 0 && console.log(">> hdr " + this.channel_type() + " type " + msg.type + " size " + mb.byteLength);
155
+ DEBUG > 2 && hexdump_buffer(mb);
156
+ this.ws.send(mb);
157
+ },
158
+
159
+ process_inbound: function(mb, saved_header)
160
+ {
161
+ DEBUG > 2 && console.log(this.type + ": processing message of size " + mb.byteLength + "; state is " + this.state);
162
+ if (this.state == "ready")
163
+ {
164
+ if (saved_header == undefined)
165
+ {
166
+ var msg = new SpiceMiniData(mb);
167
+
168
+ if (msg.type > 500)
169
+ {
170
+ alert("Something has gone very wrong; we think we have message of type " + msg.type);
171
+ debugger;
172
+ }
173
+
174
+ if (msg.size == 0)
175
+ {
176
+ this.process_message(msg);
177
+ this.wire_reader.request(SpiceMiniData.prototype.buffer_size());
178
+ }
179
+ else
180
+ {
181
+ this.wire_reader.request(msg.size);
182
+ this.wire_reader.save_header(msg);
183
+ }
184
+ }
185
+ else
186
+ {
187
+ saved_header.data = mb;
188
+ this.process_message(saved_header);
189
+ this.wire_reader.request(SpiceMiniData.prototype.buffer_size());
190
+ this.wire_reader.save_header(undefined);
191
+ }
192
+ }
193
+
194
+ else if (this.state == "start")
195
+ {
196
+ this.reply_hdr = new SpiceLinkHeader(mb);
197
+ if (this.reply_hdr.magic != SPICE_MAGIC)
198
+ {
199
+ this.state = "error";
200
+ var e = new Error('Error: magic mismatch: ' + this.reply_hdr.magic);
201
+ this.report_error(e);
202
+ }
203
+ else
204
+ {
205
+ // FIXME - Determine major/minor version requirements
206
+ this.wire_reader.request(this.reply_hdr.size);
207
+ this.state = "link";
208
+ }
209
+ }
210
+
211
+ else if (this.state == "link")
212
+ {
213
+ this.reply_link = new SpiceLinkReply(mb);
214
+ // FIXME - Screen the caps - require minihdr at least, right?
215
+ if (this.reply_link.error)
216
+ {
217
+ this.state = "error";
218
+ var e = new Error('Error: reply link error ' + this.reply_link.error);
219
+ this.report_error(e);
220
+ }
221
+ else
222
+ {
223
+ this.send_ticket(rsa_encrypt(this.reply_link.pub_key, this.password + String.fromCharCode(0)));
224
+ this.state = "ticket";
225
+ this.wire_reader.request(SpiceLinkAuthReply.prototype.buffer_size());
226
+ }
227
+ }
228
+
229
+ else if (this.state == "ticket")
230
+ {
231
+ this.auth_reply = new SpiceLinkAuthReply(mb);
232
+ if (this.auth_reply.auth_code == SPICE_LINK_ERR_OK)
233
+ {
234
+ DEBUG > 0 && console.log(this.type + ': Connected');
235
+
236
+ if (this.type == SPICE_CHANNEL_DISPLAY)
237
+ {
238
+ // FIXME - pixmap and glz dictionary config info?
239
+ var dinit = new SpiceMsgcDisplayInit();
240
+ var reply = new SpiceMiniData();
241
+ reply.build_msg(SPICE_MSGC_DISPLAY_INIT, dinit);
242
+ DEBUG > 0 && console.log("Request display init");
243
+ this.send_msg(reply);
244
+ }
245
+ this.state = "ready";
246
+ this.wire_reader.request(SpiceMiniData.prototype.buffer_size());
247
+ if (this.timeout)
248
+ {
249
+ window.clearTimeout(this.timeout);
250
+ delete this.timeout;
251
+ }
252
+ }
253
+ else
254
+ {
255
+ this.state = "error";
256
+ if (this.auth_reply.auth_code == SPICE_LINK_ERR_PERMISSION_DENIED)
257
+ {
258
+ var e = new Error("Permission denied.");
259
+ }
260
+ else
261
+ {
262
+ var e = new Error("Unexpected link error " + this.auth_reply.auth_code);
263
+ }
264
+ this.report_error(e);
265
+ }
266
+ }
267
+ },
268
+
269
+ process_common_messages : function(msg)
270
+ {
271
+ if (msg.type == SPICE_MSG_SET_ACK)
272
+ {
273
+ var ack = new SpiceMsgSetAck(msg.data);
274
+ // FIXME - what to do with generation?
275
+ this.ack_window = ack.window;
276
+ DEBUG > 1 && console.log(this.type + ": set ack to " + ack.window);
277
+ this.msgs_until_ack = this.ack_window;
278
+ var ackack = new SpiceMsgcAckSync(ack);
279
+ var reply = new SpiceMiniData();
280
+ reply.build_msg(SPICE_MSGC_ACK_SYNC, ackack);
281
+ this.send_msg(reply);
282
+ return true;
283
+ }
284
+
285
+ if (msg.type == SPICE_MSG_PING)
286
+ {
287
+ DEBUG > 1 && console.log("ping!");
288
+ var pong = new SpiceMiniData;
289
+ pong.type = SPICE_MSGC_PONG;
290
+ if (msg.data)
291
+ {
292
+ pong.data = msg.data.slice(0, 12);
293
+ }
294
+ pong.size = pong.buffer_size();
295
+ this.send_msg(pong);
296
+ return true;
297
+ }
298
+
299
+ if (msg.type == SPICE_MSG_NOTIFY)
300
+ {
301
+ // FIXME - Visibility + what
302
+ var notify = new SpiceMsgNotify(msg.data);
303
+ if (notify.severity == SPICE_NOTIFY_SEVERITY_ERROR)
304
+ this.log_err(notify.message);
305
+ else if (notify.severity == SPICE_NOTIFY_SEVERITY_WARN )
306
+ this.log_warn(notify.message);
307
+ else
308
+ this.log_info(notify.message);
309
+ return true;
310
+ }
311
+
312
+ return false;
313
+
314
+ },
315
+
316
+ process_message: function(msg)
317
+ {
318
+ var rc;
319
+ DEBUG > 0 && console.log("<< hdr " + this.channel_type() + " type " + msg.type + " size " + (msg.data && msg.data.byteLength));
320
+ rc = this.process_common_messages(msg);
321
+ if (rc)
322
+ return rc;
323
+
324
+ if (this.process_channel_message)
325
+ rc = this.process_channel_message(msg);
326
+ else
327
+ {
328
+ this.log_err(this.type + ": No message handlers for this channel; message " + msg.type);
329
+ return false;
330
+ }
331
+
332
+ if (! rc)
333
+ this.log_warn(this.type + ": Unknown message type " + msg.type + "!");
334
+
335
+ if (this.msgs_until_ack !== undefined && this.ack_window)
336
+ {
337
+ this.msgs_until_ack--;
338
+ if (this.msgs_until_ack <= 0)
339
+ {
340
+ this.msgs_until_ack = this.ack_window;
341
+ var ack = new SpiceMiniData();
342
+ ack.type = SPICE_MSGC_ACK;
343
+ this.send_msg(ack);
344
+ DEBUG > 1 && console.log(this.type + ": sent ack");
345
+ }
346
+ }
347
+
348
+ return rc;
349
+ },
350
+
351
+ channel_type: function()
352
+ {
353
+ if (this.type == SPICE_CHANNEL_MAIN)
354
+ return "main";
355
+ else if (this.type == SPICE_CHANNEL_DISPLAY)
356
+ return "display";
357
+ else if (this.type == SPICE_CHANNEL_INPUTS)
358
+ return "inputs";
359
+ else if (this.type == SPICE_CHANNEL_CURSOR)
360
+ return "cursor";
361
+ return "unknown-" + this.type;
362
+
363
+ },
364
+
365
+ log_info: function()
366
+ {
367
+ var msg = Array.prototype.join.call(arguments, " ");
368
+ console.log(msg);
369
+ if (this.message_id)
370
+ {
371
+ var p = document.createElement("p");
372
+ p.appendChild(document.createTextNode(msg));
373
+ p.className += "spice-message-info";
374
+ document.getElementById(this.message_id).appendChild(p);
375
+ }
376
+ },
377
+
378
+ log_warn: function()
379
+ {
380
+ var msg = Array.prototype.join.call(arguments, " ");
381
+ console.log("WARNING: " + msg);
382
+ if (this.message_id)
383
+ {
384
+ var p = document.createElement("p");
385
+ p.appendChild(document.createTextNode(msg));
386
+ p.className += "spice-message-warning";
387
+ document.getElementById(this.message_id).appendChild(p);
388
+ }
389
+ },
390
+
391
+ log_err: function()
392
+ {
393
+ var msg = Array.prototype.join.call(arguments, " ");
394
+ console.log("ERROR: " + msg);
395
+ if (this.message_id)
396
+ {
397
+ var p = document.createElement("p");
398
+ p.appendChild(document.createTextNode(msg));
399
+ p.className += "spice-message-error";
400
+ document.getElementById(this.message_id).appendChild(p);
401
+ }
402
+ },
403
+
404
+ known_unimplemented: function(type, msg)
405
+ {
406
+ if ( (!this.warnings[type]) || DEBUG > 1)
407
+ {
408
+ var str = "";
409
+ if (DEBUG <= 1)
410
+ str = " [ further notices suppressed ]";
411
+ this.log_warn("Unimplemented function " + type + "(" + msg + ")" + str);
412
+ this.warnings[type] = true;
413
+ }
414
+ },
415
+
416
+ report_error: function(e)
417
+ {
418
+ this.log_err(e.toString());
419
+ if (this.onerror != undefined)
420
+ this.onerror(e);
421
+ else
422
+ throw(e);
423
+ },
424
+
425
+ report_success: function(m)
426
+ {
427
+ if (this.onsuccess != undefined)
428
+ this.onsuccess(m);
429
+ },
430
+
431
+ cleanup: function()
432
+ {
433
+ if (this.timeout)
434
+ {
435
+ window.clearTimeout(this.timeout);
436
+ delete this.timeout;
437
+ }
438
+ if (this.ws)
439
+ {
440
+ this.ws.close();
441
+ this.ws = undefined;
442
+ }
443
+ },
444
+
445
+ handle_timeout: function()
446
+ {
447
+ var e = new Error("Connection timed out.");
448
+ this.report_error(e);
449
+ },
450
+ }
451
+
452
+ function spiceconn_timeout(sc)
453
+ {
454
+ SpiceConn.prototype.handle_timeout.call(sc);
455
+ }
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ /*
3
+ Copyright (C) 2012 by Jeremy P. White <jwhite@codeweavers.com>
4
+
5
+ This file is part of spice-html5.
6
+
7
+ spice-html5 is free software: you can redistribute it and/or modify
8
+ it under the terms of the GNU Lesser General Public License as published by
9
+ the Free Software Foundation, either version 3 of the License, or
10
+ (at your option) any later version.
11
+
12
+ spice-html5 is distributed in the hope that it will be useful,
13
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ GNU Lesser General Public License for more details.
16
+
17
+ You should have received a copy of the GNU Lesser General Public License
18
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
19
+ */
20
+
21
+ /*----------------------------------------------------------------------------
22
+ ** SpiceDataView
23
+ ** FIXME FIXME
24
+ ** This is used because Firefox does not have DataView yet.
25
+ ** We should use DataView if we have it, because it *has* to
26
+ ** be faster than this code
27
+ **--------------------------------------------------------------------------*/
28
+ function SpiceDataView(buffer, byteOffset, byteLength)
29
+ {
30
+ if (byteOffset !== undefined)
31
+ {
32
+ if (byteLength !== undefined)
33
+ this.u8 = new Uint8Array(buffer, byteOffset, byteLength);
34
+ else
35
+ this.u8 = new Uint8Array(buffer, byteOffset);
36
+ }
37
+ else
38
+ this.u8 = new Uint8Array(buffer);
39
+ };
40
+
41
+ SpiceDataView.prototype = {
42
+ getUint8: function(byteOffset)
43
+ {
44
+ return this.u8[byteOffset];
45
+ },
46
+ getUint16: function(byteOffset, littleEndian)
47
+ {
48
+ var low = 1, high = 0;
49
+ if (littleEndian)
50
+ {
51
+ low = 0;
52
+ high = 1;
53
+ }
54
+
55
+ return (this.u8[byteOffset + high] << 8) | this.u8[byteOffset + low];
56
+ },
57
+ getUint32: function(byteOffset, littleEndian)
58
+ {
59
+ var low = 2, high = 0;
60
+ if (littleEndian)
61
+ {
62
+ low = 0;
63
+ high = 2;
64
+ }
65
+
66
+ return (this.getUint16(byteOffset + high, littleEndian) << 16) |
67
+ this.getUint16(byteOffset + low, littleEndian);
68
+ },
69
+ setUint8: function(byteOffset, b)
70
+ {
71
+ this.u8[byteOffset] = (b & 0xff);
72
+ },
73
+ setUint16: function(byteOffset, i, littleEndian)
74
+ {
75
+ var low = 1, high = 0;
76
+ if (littleEndian)
77
+ {
78
+ low = 0;
79
+ high = 1;
80
+ }
81
+ this.u8[byteOffset + high] = (i & 0xffff) >> 8;
82
+ this.u8[byteOffset + low] = (i & 0x00ff);
83
+ },
84
+ setUint32: function(byteOffset, w, littleEndian)
85
+ {
86
+ var low = 2, high = 0;
87
+ if (littleEndian)
88
+ {
89
+ low = 0;
90
+ high = 2;
91
+ }
92
+
93
+ this.setUint16(byteOffset + high, (w & 0xffffffff) >> 16, littleEndian);
94
+ this.setUint16(byteOffset + low, (w & 0x0000ffff), littleEndian);
95
+ },
96
+ }