├── flyweb
├── README.md
├── index.js
├── package.json
├── test
│ └── test-index.js
├── lib
│ ├── advertise-registry.js
│ ├── event-target.js
│ ├── binary-utils.js
│ ├── discover-listener.js
│ ├── http-server.js
│ ├── flyweb.js
│ ├── http-status.js
│ ├── dns-utils.js
│ ├── discover-registry.js
│ ├── byte-array.js
│ ├── utils.js
│ ├── dns-records.js
│ ├── http-response.js
│ ├── dns-codes.js
│ ├── dns-packet.js
│ ├── http-request.js
│ ├── api.js
│ └── dns-sd.js
└── data
│ └── page-script.js
├── examples
└── hello-world
│ ├── hw-client.html
│ ├── hello-world.html
│ ├── hw-server.html
│ ├── hello-world.css
│ ├── stream-reader.js
│ ├── sha-1.js
│ ├── web-socket.js
│ └── hello-world.js
└── README.md
/flyweb/README.md:
--------------------------------------------------------------------------------
1 | #Fly Web
2 | Web architecture for local-area applications
--------------------------------------------------------------------------------
/flyweb/index.js:
--------------------------------------------------------------------------------
1 | var self = require('sdk/self');
2 | var dns_sd = require('./lib/flyweb');
3 |
--------------------------------------------------------------------------------
/flyweb/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Fly Web",
3 | "name": "flyweb",
4 | "version": "0.0.1",
5 | "description": "Web architecture for local-area applications",
6 | "main": "index.js",
7 | "author": "Kannan Vijayan",
8 | "engines": {
9 | "firefox": ">=38.0a1",
10 | "fennec": ">=38.0a1"
11 | },
12 | "license": "MIT"
13 | }
14 |
--------------------------------------------------------------------------------
/flyweb/test/test-index.js:
--------------------------------------------------------------------------------
1 | var main = require("../");
2 |
3 | exports["test main"] = function(assert) {
4 | assert.pass("Unit test running!");
5 | };
6 |
7 | exports["test main async"] = function(assert, done) {
8 | assert.pass("async Unit test running!");
9 | done();
10 | };
11 |
12 | exports["test dummy"] = function(assert, done) {
13 | main.dummy("foo", function(text) {
14 | assert.ok((text === "foo"), "Is the text actually 'foo'");
15 | done();
16 | });
17 | };
18 |
19 | require("sdk/test").run(exports);
20 |
--------------------------------------------------------------------------------
/examples/hello-world/hw-client.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | FlyWeb Hello World - Client
5 |
6 |
7 |
8 |
9 |
10 | [Fly Web] Hello World Client
11 |
12 | Discover Clients
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/examples/hello-world/hello-world.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | FlyWeb Hello World
5 |
6 |
7 |
8 |
9 |
10 | [Fly Web] Hello World
11 |
12 | Run Server
13 |
14 |
15 |
16 | Run Client
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # **** THIS REPO IS OBSOLETE!!! ****
2 |
3 | ### FlyWeb has now landed in Firefox Nightly. See http://flyweb.github.io/ for more information
4 |
5 | -
6 |
7 | #FlyWeb
8 |
9 | This is a Firefox add-on to prototype the fly web concept. Please note that this add-on is very experimental, and likely contains security vulnerabilities, and is not intended for any production usage. Use at your own risk.
10 |
11 | #Building the addon
12 |
13 | The addon can be built using the firefox jetpack tool 'jpm'.
14 |
15 | See https://developer.mozilla.org/en-US/Add-ons/SDK/Tools/jpm#Installation for
16 | info on how to install jpm (short version: `npm install jpm --global`).
17 |
18 | To build the addon, do `jpm xpi` from inside the flyweb directory.
19 |
20 | #Running examples
21 |
22 | Examples are meant to be run using python's SimpleHTTPServer.
23 |
24 | Do `python -m SimpleHTTPServer -p SOMEPORT` within the `examples` directory,
25 | and navigate to your machine on that port to test the examples.
26 |
--------------------------------------------------------------------------------
/examples/hello-world/hw-server.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | FlyWeb Hello World - Server
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | [Fly Web] Hello World Server
14 |
15 | Server name:
16 |
17 |
18 |
19 |
20 | Start Server
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/examples/hello-world/hello-world.css:
--------------------------------------------------------------------------------
1 |
2 | .button {
3 | margin: 0.5em;
4 | border: 2px solid black;
5 | background-color: #a090ff;
6 | font-size: 14pt;
7 | padding: 0.5em;
8 | display: inline-block;
9 | cursor: pointer;
10 | }
11 |
12 | .flyweb-emblem {
13 | color: red;
14 | }
15 |
16 | .client-services-list {
17 | margin: 0.5em;
18 | border: 2px solid black;
19 | background-color: #ffa090;
20 | display: inline-block;
21 | padding: 0.5em;
22 | }
23 |
24 | .client-service {
25 | margin: 0.3em;
26 | border-bottom: 1px solid #808080;
27 | background-color: #ffa090;
28 | display: inline-block;
29 | }
30 | .client-service-type {
31 | margin: 0.3em;
32 | display: inline-block;
33 | font-weight: bold;
34 | }
35 | .client-service-name {
36 | margin: 0.3em;
37 | display: inline-block;
38 | }
39 | .client-service-establish {
40 | margin: 0.3em;
41 | background-color: #a08060;
42 | display: inline-block;
43 | cursor: pointer;
44 | }
45 |
--------------------------------------------------------------------------------
/flyweb/lib/advertise-registry.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | class AdvertisedService {
4 | constructor({serviceName, name, port, options}) {
5 | this.serviceName = serviceName;
6 | this.name = name;
7 | this.port = port || 0;
8 | this.options = options || {};
9 | }
10 |
11 | get fullname() {
12 | return this.name + "." + this.serviceName;
13 | }
14 |
15 | get location() {
16 | return this.name + "." + this.serviceName;
17 | }
18 |
19 | get target() {
20 | return this.name + ".local";
21 | }
22 | }
23 |
24 | class AdvertiseRegistry {
25 | constructor() {
26 | this.services = {};
27 | }
28 |
29 | addService(svc) {
30 | this.services[svc.fullname] = svc;
31 | }
32 |
33 | getService(fullname) {
34 | return this.services[fullname];
35 | }
36 |
37 | delService(fullname) {
38 | delete this.services[fullname];
39 | }
40 |
41 | names() {
42 | return Object.keys(this.services);
43 | }
44 |
45 | numServices() {
46 | return this.names().length;
47 | }
48 |
49 | hasServices() {
50 | return this.numServices() == 0;
51 | }
52 | }
53 |
54 | exports.AdvertiseRegistry = AdvertiseRegistry;
55 | exports.AdvertisedService = AdvertisedService;
56 |
--------------------------------------------------------------------------------
/flyweb/lib/event-target.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | function EventTarget(object) {
4 | if (typeof object !== 'object') {
5 | return;
6 | }
7 |
8 | for (let property in object) {
9 | this[property] = object[property];
10 | }
11 | }
12 |
13 | EventTarget.prototype.constructor = EventTarget;
14 |
15 | EventTarget.prototype.dispatchEvent = function(name, data) {
16 | const events = this._events || {};
17 | const listeners = events[name] || [];
18 | listeners.forEach((listener) => {
19 | listener.call(this, data);
20 | });
21 | };
22 |
23 | EventTarget.prototype.addEventListener = function(name, listener) {
24 | const events = this._events = this._events || {};
25 | const listeners = events[name] = events[name] || [];
26 | if (listeners.find(fn => fn === listener)) {
27 | return;
28 | }
29 |
30 | listeners.push(listener);
31 | };
32 |
33 | EventTarget.prototype.removeEventListener = function(name, listener) {
34 | const events = this._events || {};
35 | const listeners = events[name] || [];
36 | for (let i = listeners.length - 1; i >= 0; i--) {
37 | if (listeners[i] === listener) {
38 | listeners.splice(i, 1);
39 | return;
40 | }
41 | }
42 | };
43 |
44 | exports.EventTarget = EventTarget;
45 |
--------------------------------------------------------------------------------
/flyweb/lib/binary-utils.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | function stringToArrayBuffer(string) {
4 | let length = (string || '').length;
5 | let arrayBuffer = new ArrayBuffer(length);
6 | let uint8Array = new Uint8Array(arrayBuffer);
7 | for (let i = 0; i < length; i++) {
8 | uint8Array[i] = string.charCodeAt(i);
9 | }
10 |
11 | return arrayBuffer;
12 | }
13 |
14 | function arrayBufferToString(arrayBuffer) {
15 | let results = [];
16 | let uint8Array = new Uint8Array(arrayBuffer);
17 |
18 | for (let i = 0, length = uint8Array.length; i < length; i += 200000) {
19 | let subArray = uint8Array.subarray(i, i + 200000);
20 | results.push(String.fromCharCode.apply(null, subArray));
21 | }
22 |
23 | return results.join('');
24 | }
25 |
26 | function blobToArrayBuffer(blob, callback) {
27 | let fileReader = new FileReader();
28 | fileReader.onload = function() {
29 | if (typeof callback === 'function') {
30 | callback(fileReader.result);
31 | }
32 | };
33 | fileReader.readAsArrayBuffer(blob);
34 |
35 | return fileReader.result;
36 | }
37 |
38 | function mergeArrayBuffers(arrayBuffers, callback) {
39 | return blobToArrayBuffer(new Blob(arrayBuffers), callback);
40 | }
41 |
42 | exports.BinaryUtils = {
43 | stringToArrayBuffer,
44 | arrayBufferToString,
45 | blobToArrayBuffer,
46 | mergeArrayBuffers
47 | };
48 |
--------------------------------------------------------------------------------
/flyweb/lib/discover-listener.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | class DiscoverListenerList
4 | {
5 | constructor() {
6 | this.listeners_ = [];
7 | this.idno_ = 0;
8 | }
9 |
10 | nextId() {
11 | let idno = ++this.idno_;
12 | return 'discover_listener_' + idno;
13 | }
14 |
15 | addListener(spec, callback) {
16 | let id = this.nextId();
17 | let listener = new DiscoverListener({id, spec, callback});
18 | this.listeners_.push(listener);
19 | return id;
20 | }
21 |
22 | removeListener(id) {
23 | this.listeners_ = this.listeners_.filter((listener) => {
24 | return listener.id != id;
25 | });
26 | }
27 |
28 | found(service) {
29 | for (let listener of this.listeners_)
30 | listener.found(service);
31 | }
32 |
33 | lost(service) {
34 | for (let listener of this.listeners_)
35 | listener.lost(service);
36 | }
37 | }
38 |
39 | class DiscoverListener
40 | {
41 | constructor({id, spec, callback}) {
42 | this.id_ = id;
43 | this.spec_ = spec;
44 | this.callback_ = callback;
45 | }
46 |
47 | get id() { return this.id_; }
48 | get spec() { return this.spec_; }
49 | get callback() { return this.callback_; }
50 |
51 | match_(service) {
52 | return true;
53 | }
54 |
55 | found(service) {
56 | if (this.callback_ && this.match_(service))
57 | this.callback_(service, true)
58 | }
59 |
60 | lost(service) {
61 | if (this.callback_ && this.match_(service))
62 | this.callback_(service, false)
63 | }
64 | }
65 |
66 | exports.DiscoverListenerList = DiscoverListenerList;
67 | exports.DiscoverListener = DiscoverListener;
68 |
--------------------------------------------------------------------------------
/flyweb/lib/http-server.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var {Ci,Cr} = require('chrome');
4 | var {EventTarget} = require('./event-target');
5 | var {HTTPRequest} = require('./http-request');
6 | var {HTTPResponse} = require('./http-response');
7 | var utils = require('./utils');
8 |
9 | var DEFAULT_PORT = 8080;
10 | var DEFAULT_TIMEOUT = 20000;
11 |
12 | var CRLF = '\r\n';
13 |
14 | function HTTPServer(port, options) {
15 | this.port = port;
16 | options = options || {};
17 |
18 | if (options.rawRequest)
19 | this.rawRequest = true;
20 | else
21 | this.rawRequest = false;
22 |
23 | this.running = false;
24 | }
25 |
26 | HTTPServer.HTTP_VERSION = 'HTTP/1.1';
27 |
28 | HTTPServer.prototype = new EventTarget();
29 |
30 | HTTPServer.prototype.constructor = HTTPServer;
31 |
32 | HTTPServer.prototype.timeout = DEFAULT_TIMEOUT;
33 |
34 | HTTPServer.prototype.start = function() {
35 | if (this.running) {
36 | return;
37 | }
38 |
39 | console.log('Starting HTTP server on port ' + this.port);
40 | var socket = utils.newTCPServerSocket({port:this.port});
41 | this.port = socket.port;
42 | socket.asyncListen({
43 | onSocketAccepted: (sock, transport) => {
44 | // Don't accept requests until 'onrequest' is installed.
45 | if (!this.onrequest) {
46 | this.transport_.close(Cr.NS_OK);
47 | return;
48 | }
49 | var request = new HTTPRequest(transport);
50 | var response = new HTTPResponse(transport);
51 | this.onrequest(request, response);
52 | },
53 |
54 | onStopListening: (sock, status) => {
55 | }
56 | });
57 |
58 | this.socket = socket;
59 | this.running = true;
60 | };
61 |
62 | HTTPServer.prototype.stop = function() {
63 | if (!this.running) {
64 | return;
65 | }
66 |
67 | console.log('Shutting down HTTP server on port ' + this.port);
68 |
69 | this.socket.close();
70 |
71 | this.running = false;
72 | };
73 |
74 | exports.HTTPServer = HTTPServer;
75 |
--------------------------------------------------------------------------------
/flyweb/lib/flyweb.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var { Class } = require('sdk/core/heritage');
4 | var { Cc, Ci, Cu, Cm } = require('chrome');
5 | var xpcom = require('sdk/platform/xpcom');
6 | var categoryManager = Cc["@mozilla.org/categorymanager;1"]
7 | .getService(Ci.nsICategoryManager);
8 |
9 | var contractId = '@mozilla.org/flyweb;1';
10 |
11 | var DNSSD = require('./dns-sd');
12 | var API = require('./api');
13 | var PageMod = require('sdk/page-mod');
14 | var Self = require('sdk/self');
15 | var {HTTPServer} = require('./http-server');
16 |
17 | PageMod.PageMod({
18 | include: "*",
19 | contentScriptFile: Self.data.url('page-script.js'),
20 | onAttach: function (worker) {
21 | dump("[FlyWeb-Addon] Attached to page!\n");
22 | API.registerWorker(worker);
23 | worker.port.on("request", function (message) {
24 | let obj = JSON.parse(message);
25 | // Only dump message contents if there is a message error.
26 | if (!obj.messageName || !obj.messageId) {
27 | dump("Addon got message: " + message + "\n");
28 | if (!obj.messageName) {
29 | dump(" No name for message!?\n");
30 | }
31 | if (!obj.messageId) {
32 | dump(" No id for message!? (" + obj.messageName + ")\n");
33 | }
34 | return;
35 | }
36 | let {messageName, messageId} = obj;
37 | delete obj.messageName;
38 | delete obj.messageId;
39 | API.dispatchRequest(worker, messageName, obj, resultObj => {
40 | resultObj.messageName = messageName;
41 | resultObj.messageId = messageId;
42 | worker.port.emit("response", JSON.stringify(resultObj));
43 | });
44 | });
45 | worker.on('detach', function () {
46 | API.unregisterWorker(this);
47 | });
48 | }
49 | });
50 |
--------------------------------------------------------------------------------
/flyweb/lib/http-status.js:
--------------------------------------------------------------------------------
1 | /*jshint esnext:true*/
2 | /*exported HTTPStatus*/
3 | 'use strict';
4 |
5 | var CRLF = '\r\n';
6 |
7 | var HTTPServer;
8 |
9 | var HTTPStatus = {};
10 |
11 | HTTPStatus.STATUS_CODES = {
12 | 100: 'Continue',
13 | 101: 'Switching Protocols',
14 | 102: 'Processing',
15 | 200: 'OK',
16 | 201: 'Created',
17 | 202: 'Accepted',
18 | 203: 'Non Authoritative Information',
19 | 204: 'No Content',
20 | 205: 'Reset Content',
21 | 206: 'Partial Content',
22 | 207: 'Multi-Status',
23 | 300: 'Mutliple Choices',
24 | 301: 'Moved Permanently',
25 | 302: 'Moved Temporarily',
26 | 303: 'See Other',
27 | 304: 'Not Modified',
28 | 305: 'Use Proxy',
29 | 307: 'Temporary Redirect',
30 | 400: 'Bad Request',
31 | 401: 'Unauthorized',
32 | 402: 'Payment Required',
33 | 403: 'Forbidden',
34 | 404: 'Not Found',
35 | 405: 'Method Not Allowed',
36 | 406: 'Not Acceptable',
37 | 407: 'Proxy Authentication Required',
38 | 408: 'Request Timeout',
39 | 409: 'Conflict',
40 | 410: 'Gone',
41 | 411: 'Length Required',
42 | 412: 'Precondition Failed',
43 | 413: 'Request Entity Too Large',
44 | 414: 'Request-URI Too Long',
45 | 415: 'Unsupported Media Type',
46 | 416: 'Requested Range Not Satisfiable',
47 | 417: 'Expectation Failed',
48 | 419: 'Insufficient Space on Resource',
49 | 420: 'Method Failure',
50 | 422: 'Unprocessable Entity',
51 | 423: 'Locked',
52 | 424: 'Failed Dependency',
53 | 500: 'Server Error',
54 | 501: 'Not Implemented',
55 | 502: 'Bad Gateway',
56 | 503: 'Service Unavailable',
57 | 504: 'Gateway Timeout',
58 | 505: 'HTTP Version Not Supported',
59 | 507: 'Insufficient Storage'
60 | };
61 |
62 | HTTPStatus.getStatusLine = function(status) {
63 | if (!HTTPServer)
64 | HTTPServer = require('./http-server').HTTPServer;
65 |
66 | var reason = HTTPStatus.STATUS_CODES[status] || 'Unknown';
67 |
68 | return [HTTPServer.HTTP_VERSION, status, reason].join(' ') + CRLF;
69 | };
70 |
71 | exports.HTTPStatus = HTTPStatus;
72 |
--------------------------------------------------------------------------------
/flyweb/lib/dns-utils.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var {BinaryUtils} = require('./binary-utils');
4 | var {ByteArray} = require('./byte-array');
5 |
6 | function parseNameIntoArray(reader, outArray) {
7 | const byteArray = reader.byteArray;
8 |
9 | let partLength;
10 | while (partLength = reader.getValue()) {
11 | if ((partLength & 0xc0) == 0xc0) {
12 | // name pointer to elsewhere in the response.
13 | let offset = ((partLength & 0x3f) << 8) | reader.getValue();
14 | parseNameIntoArray(byteArray.getReader(offset), outArray);
15 | break;
16 | }
17 |
18 | var nameBytes = reader.getBytes(partLength);
19 | outArray.push(BinaryUtils.arrayBufferToString(nameBytes));
20 | }
21 | }
22 |
23 | function byteArrayToName(reader) {
24 | const parts = [];
25 | parseNameIntoArray(reader, parts)
26 | return parts.join('.');
27 | }
28 |
29 | function nameToByteArray(name) {
30 | const byteArray = new ByteArray();
31 | const parts = name.split('.');
32 | parts.forEach((part) => {
33 | var length = part.length;
34 | byteArray.push(length);
35 |
36 | for (let i = 0; i < length; i++) {
37 | byteArray.push(part.charCodeAt(i));
38 | }
39 | });
40 |
41 | byteArray.push(0x00);
42 | return byteArray.data;
43 | }
44 |
45 | function valueToFlags(value) {
46 | return {
47 | QR: (value & 0x8000) >> 15,
48 | OP: (value & 0x7800) >> 11,
49 | AA: (value & 0x0400) >> 10,
50 | TC: (value & 0x0200) >> 9,
51 | RD: (value & 0x0100) >> 8,
52 | RA: (value & 0x0080) >> 7,
53 | UN: (value & 0x0040) >> 6,
54 | AD: (value & 0x0020) >> 5,
55 | CD: (value & 0x0010) >> 4,
56 | RC: (value & 0x000f) >> 0
57 | };
58 | }
59 |
60 | function flagsToValue(flags) {
61 | var value = 0x0000;
62 |
63 | value = value << 1;
64 | value += flags.QR & 0x01;
65 |
66 | value = value << 4;
67 | value += flags.OP & 0x0f;
68 |
69 | value = value << 1;
70 | value += flags.AA & 0x01;
71 |
72 | value = value << 1;
73 | value += flags.TC & 0x01;
74 |
75 | value = value << 1;
76 | value += flags.RD & 0x01;
77 |
78 | value = value << 1;
79 | value += flags.RA & 0x01;
80 |
81 | value = value << 1;
82 | value += flags.UN & 0x01;
83 |
84 | value = value << 1;
85 | value += flags.AD & 0x01;
86 |
87 | value = value << 1;
88 | value += flags.CD & 0x01;
89 |
90 | value = value << 4;
91 | value += flags.RC & 0x0f;
92 |
93 | return value;
94 | }
95 |
96 | exports.DNSUtils = {
97 | parseNameIntoArray,
98 | byteArrayToName,
99 | nameToByteArray,
100 | valueToFlags,
101 | flagsToValue
102 | };
103 |
--------------------------------------------------------------------------------
/flyweb/lib/discover-registry.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var {DNSCodes} = require('./dns-codes');
4 | var {EventTarget} = require('./event-target');
5 |
6 | function DiscoverRegistry() {
7 | // ptrRecords holds a list of all fly web PTR records, keyed by location.
8 | this._ptrRecords = {};
9 |
10 | // srvRecords map specific locations to an SRV record
11 | // identifying the target and port.
12 | this._srvRecords = {};
13 |
14 | // txtRecords map specific locations to a TXT record.
15 | this._txtRecords = {};
16 |
17 | // addrRecords map target names to IP addresses.
18 | this._addrRecords = {};
19 | }
20 |
21 | DiscoverRegistry.prototype = Object.create(EventTarget.prototype);
22 | DiscoverRegistry.prototype.constructor = DiscoverRegistry;
23 |
24 | DiscoverRegistry.prototype._clearLocation = function(loc) {
25 | // Clear any PTR records for the location.
26 | delete this._ptrRecords[loc];
27 |
28 | // Clear any SRV records for the location.
29 | let srvRecord = this._srvRecords[loc];
30 | delete this._srvRecords[loc];
31 |
32 | // Clear any TXT records for the location.
33 | delete this._txtRecords[loc];
34 |
35 | // Clear any A records associated with the target of the SRV record.
36 | if (srvRecord)
37 | delete this._addrRecords[srvRecord.parsedData.target];
38 | };
39 |
40 | DiscoverRegistry.prototype.addRecord = function(record, serviceSet) {
41 | if (record.recordType == DNSCodes.RECORD_TYPES.PTR) {
42 | let loc = record.parsedData.location;
43 | this._clearLocation(loc);
44 | this._ptrRecords[loc] = record;
45 | serviceSet.add(loc);
46 |
47 | } else if (record.recordType == DNSCodes.RECORD_TYPES.SRV) {
48 | this._srvRecords[record.name] = record;
49 |
50 | } else if (record.recordType == DNSCodes.RECORD_TYPES.TXT) {
51 | this._txtRecords[record.name] = record;
52 |
53 | } else if (record.recordType == DNSCodes.RECORD_TYPES.A) {
54 | this._addrRecords[record.name] = record;
55 | }
56 | };
57 |
58 | DiscoverRegistry.prototype.serviceInfo = function(location) {
59 | // Get the PTR record for this location.
60 | let ptrRecord = this._ptrRecords[location];
61 | if (!ptrRecord)
62 | return null;
63 |
64 | // Get the SRV record for this location.
65 | let srvRecord = this._srvRecords[location];
66 | if (!srvRecord)
67 | return null;
68 |
69 | // Get the TXT record for this location.
70 | let txt = {};
71 | let txtRecord = this._txtRecords[location];
72 | if (txtRecord) {
73 | for (let part of txtRecord.parsedData.parts) {
74 | let idx = part.indexOf('=');
75 | if (idx == -1)
76 | continue;
77 | txt[part.slice(0, idx)] = part.slice(idx+1);
78 | }
79 | }
80 |
81 | // Get the A record for the SRV target.
82 | let port = srvRecord.parsedData.port;
83 | let target = srvRecord.parsedData.target;
84 | let addrRecord = this._addrRecords[target];
85 | if (!addrRecord)
86 | return null;
87 |
88 | let ip = addrRecord.parsedData.ip;
89 |
90 | // Compose the info into a ServiceInfo and return it.
91 | return new ServiceInfo({location, target, ip, port, txt});
92 | };
93 |
94 |
95 | function ServiceInfo({location, target, ip, port, txt}) {
96 | this.location = location;
97 | this.target = target;
98 | this.ip = ip;
99 | this.port = port;
100 | this.txt = txt;
101 | }
102 |
103 | ServiceInfo.prototype.equals = function (other) {
104 | return (this.location == other.location) &&
105 | (this.target == other.target) &&
106 | (this.ip == other.ip) &&
107 | (this.port == other.port);
108 | }
109 |
110 | exports.DiscoverRegistry = DiscoverRegistry;
111 | exports.ServiceInfo = ServiceInfo;
112 |
--------------------------------------------------------------------------------
/flyweb/lib/byte-array.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var utils = require("./utils");
4 |
5 | class ByteArray {
6 | constructor(...args) {
7 | if (args.length == 0)
8 | this.init(new ArrayBuffer(256), 0);
9 | else if (typeof(args[0]) === 'number')
10 | this.init(new ArrayBuffer(args[0]), 0);
11 | else if (typeof(args[0]) === 'object')
12 | this.init(args[0], args[0].length);
13 | else
14 | utils.raiseError(new Error("Unrecognized arguments: " + args.toString()));
15 | }
16 |
17 | init(buf, cursor) {
18 | this._buffer = buf;
19 | this._data = new Uint8Array(buf);
20 | this._cursor = cursor;
21 | }
22 |
23 | get byteLength() {
24 | return this._cursor;
25 | }
26 |
27 | get buffer() {
28 | return this._buffer.slice(0, this._cursor);
29 | }
30 | get data() {
31 | return this._data.slice(0, this._cursor);
32 | }
33 |
34 | push(value, byteLength) {
35 | byteLength = byteLength || 1;
36 | this.append(valueToUint8Array(value, byteLength));
37 | }
38 |
39 | append(bytes) {
40 | for (var i = 0, byteLength = bytes.length; i < byteLength; i++) {
41 | this._data[this._cursor] = bytes[i];
42 | this._cursor++;
43 | }
44 | }
45 |
46 | getReader(startByte) {
47 | var cursor = startByte || 0;
48 |
49 | var getBytes = (byteLength) => {
50 | if (byteLength === null) {
51 | return new Uint8Array();
52 | }
53 |
54 | byteLength = byteLength || 1;
55 |
56 | var endPointer = cursor + byteLength;
57 | if (endPointer > this.byteLength) {
58 | return new Uint8Array();
59 | }
60 |
61 | var uint8Array = new Uint8Array(this._buffer.slice(cursor, endPointer));
62 | cursor += byteLength;
63 |
64 | return uint8Array;
65 | };
66 |
67 | var getValue = (byteLength) => {
68 | var bytes = getBytes(byteLength);
69 | if (bytes.length === 0) {
70 | return null;
71 | }
72 |
73 | return uint8ArrayToValue(bytes);
74 | };
75 |
76 | var isEOF = () => {
77 | return cursor >= this.byteLength;
78 | };
79 |
80 | return {
81 | getBytes: getBytes,
82 | getValue: getValue,
83 | isEOF: isEOF,
84 |
85 | get offset() { return cursor; },
86 |
87 | byteArray: this
88 | };
89 | }
90 | }
91 |
92 |
93 | /**
94 | * Bit 1-Byte 2-Bytes 3-Bytes 4-Bytes
95 | * -----------------------------------------------
96 | * 0 1 256 65536 16777216
97 | * 1 2 512 131072 33554432
98 | * 2 4 1024 262144 67108864
99 | * 3 8 2048 524288 134217728
100 | * 4 16 4096 1048576 268435456
101 | * 5 32 8192 2097152 536870912
102 | * 6 64 16384 4194304 1073741824
103 | * 7 128 32768 8388608 2147483648
104 | * -----------------------------------------------
105 | * Offset 0 255 65535 16777215
106 | * Total 255 65535 16777215 4294967295
107 | */
108 | function valueToUint8Array(value, byteLength) {
109 | var arrayBuffer = new ArrayBuffer(byteLength);
110 | var uint8Array = new Uint8Array(arrayBuffer);
111 | for (var i = byteLength - 1; i >= 0; i--) {
112 | uint8Array[i] = value & 0xff;
113 | value = value >> 8;
114 | }
115 |
116 | return uint8Array;
117 | }
118 |
119 | function uint8ArrayToValue(uint8Array) {
120 | var byteLength = uint8Array.length;
121 | if (byteLength === 0) {
122 | return null;
123 | }
124 |
125 | var value = 0;
126 | for (var i = 0; i < byteLength; i++) {
127 | value = value << 8;
128 | value += uint8Array[i];
129 | }
130 |
131 | return value;
132 | }
133 |
134 | exports.ByteArray = ByteArray;
135 |
--------------------------------------------------------------------------------
/flyweb/lib/utils.js:
--------------------------------------------------------------------------------
1 | var {Cc, Ci, Cu} = require("chrome");
2 |
3 | const systemPrincipal = Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal);
4 |
5 | function newUDPSocket(config) {
6 | let loopback = config.loopback || false;
7 | let port = config.localPort || 0;
8 | let principal = config.principal || systemPrincipal;
9 | let sock = Cc["@mozilla.org/network/udp-socket;1"].createInstance(Ci.nsIUDPSocket);
10 | sock.init(port, loopback, principal);
11 | return sock;
12 | }
13 |
14 | function newTCPServerSocket(config) {
15 | let loopback = config.loopback || false;
16 | let port = config.port || 0;
17 | let backlog = config.backlog || -1;
18 | let sock = Cc["@mozilla.org/network/server-socket;1"].createInstance(Ci.nsIServerSocket);
19 | sock.init(port, loopback, backlog);
20 | return sock;
21 | }
22 |
23 | function newBinaryInputStream(inputStream) {
24 | let bis = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream);
25 | bis.setInputStream(inputStream);
26 | return bis;
27 | }
28 |
29 | function newBinaryOutputStream(outputStream) {
30 | let bos = Cc["@mozilla.org/binaryoutputstream;1"].createInstance(Ci.nsIBinaryOutputStream);
31 | bos.setOutputStream(outputStream);
32 | return bos;
33 | }
34 |
35 | function newThreadManager() {
36 | return Cc["@mozilla.org/thread-manager;1"].createInstance(Ci.nsIThreadManager);
37 | }
38 |
39 | var ThreadManager;
40 | function currentThread() {
41 | if (!ThreadManager)
42 | ThreadManager = newThreadManager();
43 | return ThreadManager.currentThread;
44 | }
45 |
46 | function dumpError(err) {
47 | dump("!!! Exception raised: " + err.toString() + "\n");
48 | dump(err.stack + "\n");
49 | }
50 |
51 | function raiseError(err) {
52 | dumpError(err);
53 | throw err;
54 | }
55 |
56 | function tryWrap(fn) {
57 | try {
58 | return fn();
59 | } catch(err) {
60 | dump("tryWrap ERROR: " + err.toString() + "\n");
61 | dump(err.stack + "\n");
62 | throw err;
63 | }
64 | }
65 |
66 | function tryWrapF(fn) {
67 | return function (...args) {
68 | return tryWrap(() => { fn.apply(null, args); });
69 | };
70 | }
71 |
72 | function suppressError(fn) {
73 | try {
74 | return fn();
75 | } catch(err) {
76 | dump("suppressError ERROR: " + err.toString() + "\n");
77 | dump(err.stack + "\n");
78 | }
79 | }
80 |
81 | function getIp() {
82 | return new Promise((resolve, reject) => {
83 | let receiver = newUDPSocket({localPort: 0, loopback: false});
84 | let sender = newUDPSocket({localPort: 0, loopback: false});
85 | const MULTICAST_GROUP = '224.0.2.222';
86 | const PORT = receiver.port;
87 |
88 | receiver.asyncListen({
89 | onPacketReceived: function(aSocket, aMessage) {
90 | let packet = aMessage.rawData;
91 | let addr = aMessage.fromAddr.address;
92 | receiver.close();
93 | sender.close();
94 | resolve(addr);
95 | },
96 |
97 | onStopListening: function(aSocket, aStatus) {
98 | },
99 | });
100 | receiver.joinMulticast(MULTICAST_GROUP);
101 |
102 | let msg = "FLYWEB_IP_HACK";
103 | let msgarray = [];
104 | for (let i = 0; i < msg.length; i++)
105 | msgarray.push(msg.charCodeAt(i));
106 | sender.asyncListen({
107 | onPacketReceived: function(aSocket, aMessage) {},
108 | onStopListening: function(aSocket, aStatus) {},
109 | });
110 | sender.send(MULTICAST_GROUP, PORT, msgarray, msgarray.length);
111 | });
112 | }
113 |
114 | exports.systemPrincipal = systemPrincipal;
115 | exports.newUDPSocket = newUDPSocket;
116 | exports.newTCPServerSocket = newTCPServerSocket;
117 | exports.newBinaryInputStream = newBinaryInputStream;
118 | exports.newBinaryOutputStream = newBinaryOutputStream;
119 | exports.newThreadManager = newThreadManager;
120 | exports.currentThread = currentThread;
121 | exports.dumpError = dumpError;
122 | exports.raiseError = raiseError;
123 | exports.tryWrap = tryWrap;
124 | exports.tryWrapF = tryWrapF;
125 | exports.suppressError = suppressError;
126 | exports.getIp = getIp;
127 |
--------------------------------------------------------------------------------
/flyweb/lib/dns-records.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var {BinaryUtils} = require('./binary-utils');
4 | var {ByteArray} = require('./byte-array');
5 | var {DNSCodes} = require('./dns-codes');
6 | var {DNSUtils} = require('./dns-utils');
7 |
8 | /**
9 | * DNSRecord
10 | */
11 |
12 | function DNSRecord(name, recordType, classCode) {
13 | this.name = name;
14 | this.timestamp = Date.now();
15 | this.recordType = recordType;
16 | this.recordTypeName = DNSCodes.RECORD_TYPES(recordType) || "!!UNKNOWN!!";
17 | this.classCode = classCode || DNSCodes.CLASS_CODES.IN;
18 | }
19 |
20 | DNSRecord.prototype.constructor = DNSRecord;
21 |
22 | /**
23 | * DNSQuestionRecord
24 | */
25 |
26 | function DNSQuestionRecord(name, recordType, classCode) {
27 | DNSRecord.call(this, name, recordType, classCode);
28 | }
29 |
30 | DNSQuestionRecord.prototype = Object.create(DNSRecord.prototype);
31 | DNSQuestionRecord.prototype.constructor = DNSQuestionRecord;
32 |
33 | /**
34 | * DNSResourceRecord
35 | */
36 |
37 | const DNS_RESOURCE_RECORD_DEFAULT_TTL = 10; // 10 seconds
38 | // const DNS_RESOURCE_RECORD_DEFAULT_TTL = 3600; // 1 hour
39 |
40 | function DNSResourceRecord(name, recordType, classCode, ttl, data) {
41 | DNSRecord.call(this, name, recordType, classCode);
42 | this.ttl = ttl || DNS_RESOURCE_RECORD_DEFAULT_TTL;
43 | this.data = data;
44 | }
45 |
46 | DNSResourceRecord.prototype = Object.create(DNSRecord.prototype);
47 | DNSResourceRecord.prototype.constructor = DNSResourceRecord;
48 |
49 | DNSResourceRecord.prototype.parseData = function (packetData, offset) {
50 | if (this.recordType === DNSCodes.RECORD_TYPES.PTR) {
51 | let reader = packetData.getReader(offset);
52 | let location = DNSUtils.byteArrayToName(reader);
53 | this.parsedData = {location};
54 | } else if (this.recordType == DNSCodes.RECORD_TYPES.SRV) {
55 | let reader = packetData.getReader(offset);
56 | let priority = reader.getValue(2);
57 | let weight = reader.getValue(2);
58 | let port = reader.getValue(2);
59 | let target = DNSUtils.byteArrayToName(reader);
60 | this.parsedData = {priority,weight,port,target};
61 | } else if (this.recordType == DNSCodes.RECORD_TYPES.TXT) {
62 | let byteArray = new ByteArray(this.data.buffer);
63 | let reader = byteArray.getReader(0);
64 | let parts = [];
65 | let partLength;
66 | while (partLength = reader.getValue()) {
67 | let bytes = reader.getBytes(partLength);
68 | let str = BinaryUtils.arrayBufferToString(bytes);
69 | parts.push(str);
70 | }
71 | this.parsedData = {parts};
72 | } else if (this.recordType == DNSCodes.RECORD_TYPES.A) {
73 | let byteArray = new ByteArray(this.data.buffer);
74 | let reader = byteArray.getReader(0);
75 | let parts = [];
76 | for (let i = 0; i < 4; i++) {
77 | parts.push('' + reader.getValue());
78 | }
79 | let ip = parts.join('.');
80 | this.parsedData = {ip};
81 | }
82 | delete this.data;
83 | };
84 |
85 | DNSResourceRecord.prototype.setParsedData = function (obj) {
86 | this.parsedData = obj;
87 | if (!this.parsedData) {
88 | this.data = new Uint8Array(new ArrayBuffer(0));
89 | return;
90 | }
91 | this.serializeRData();
92 | };
93 |
94 | DNSResourceRecord.prototype.serializeRData = function () {
95 | if (this.recordType === DNSCodes.RECORD_TYPES.PTR) {
96 | this.data = DNSUtils.nameToByteArray(this.parsedData.location);
97 |
98 | } else if (this.recordType === DNSCodes.RECORD_TYPES.SRV) {
99 | let byteArray = new ByteArray();
100 | byteArray.push(this.parsedData.priority, 2);
101 | byteArray.push(this.parsedData.weight, 2);
102 | byteArray.push(this.parsedData.port, 2);
103 |
104 | let buf = DNSUtils.nameToByteArray(this.parsedData.target);
105 | byteArray.append(buf);
106 |
107 | this.data = new Uint8Array(byteArray.buffer);
108 |
109 | } else if (this.recordType === DNSCodes.RECORD_TYPES.TXT) {
110 | let byteArray = new ByteArray();
111 | for (let part of this.parsedData.parts) {
112 | byteArray.push(part.length, 1);
113 | for (let i = 0; i < part.length; i++)
114 | byteArray.push(part.charCodeAt(i) & 0xff, 1);
115 | }
116 |
117 | this.data = new Uint8Array(byteArray.buffer);
118 |
119 | } else if (this.recordType === DNSCodes.RECORD_TYPES.A) {
120 | let byteArray = new ByteArray();
121 | let ip = this.parsedData.ip.split('.').map(x => parseInt(x));
122 | byteArray.push(ip[0] & 0xff);
123 | byteArray.push(ip[1] & 0xff);
124 | byteArray.push(ip[2] & 0xff);
125 | byteArray.push(ip[3] & 0xff);
126 | this.data = new Uint8Array(byteArray.buffer);
127 |
128 | }
129 | };
130 |
131 | DNSResourceRecord.prototype.getData = function() {
132 | return this.parsedData;
133 | };
134 |
135 | DNSResourceRecord.prototype.getName = function() {
136 | return this.name;
137 | };
138 |
139 | exports.DNSRecord = DNSRecord;
140 | exports.DNSQuestionRecord = DNSQuestionRecord;
141 | exports.DNSResourceRecord = DNSResourceRecord;
142 |
--------------------------------------------------------------------------------
/flyweb/lib/http-response.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var {Ci,Cr} = require('chrome');
4 | var {EventTarget} = require('./event-target');
5 | var {BinaryUtils} = require('./binary-utils');
6 | var {HTTPStatus} = require('./http-status');
7 | var utils = require('./utils');
8 |
9 | var CRLF = '\r\n';
10 | var BUFFER_SIZE = 64 * 1024;
11 |
12 | function HTTPResponse(transport) {
13 | this.transport = transport;
14 | var outputStream = transport.openOutputStream(
15 | Ci.nsITransport.OPEN_UNBUFFERED, 0, 0);
16 | var asyncOutputStream = outputStream.QueryInterface(Ci.nsIAsyncOutputStream);
17 | this.outputStream = asyncOutputStream;
18 | }
19 |
20 | HTTPResponse.prototype = new EventTarget();
21 |
22 | HTTPResponse.prototype.constructor = HTTPResponse;
23 |
24 | HTTPResponse.prototype.send = function(status, headers, body) {
25 | return new Promise((resolve, reject) => {
26 | let response = createResponse(status, headers, body);
27 | let idx = 0;
28 |
29 | let sendData = (stream) => {
30 | if (idx < response.length) {
31 | let written;
32 | try {
33 | written = this.outputStream.write(response.substr(idx), response.length - idx);
34 | } catch(err) {
35 | utils.dumpError(err);
36 | reject(err);
37 | return;
38 | }
39 | idx += written;
40 | this.outputStream.asyncWait({
41 | onOutputStreamReady: sendData
42 | }, 0, 128, utils.currentThread());
43 |
44 | } else {
45 | try {
46 | this.outputStream.close();
47 | this.transport.close(Cr.NS_OK);
48 | resolve();
49 | } catch(err) {
50 | utils.dumpError(err);
51 | reject(err);
52 | return;
53 | }
54 | }
55 | };
56 |
57 | this.outputStream.asyncWait({
58 | onOutputStreamReady: sendData
59 | }, 0, 128, utils.currentThread());
60 | });
61 | };
62 |
63 | function HTTPResponseStream(response, outputStream, transport) {
64 | this.response_ = response;
65 | this.outputStream_ = outputStream;
66 | this.transport_ = transport;
67 | this.buffer_ = [];
68 | this.done_ = false;
69 | this.error_ = false;
70 | this.transmitting_ = false;
71 | }
72 | HTTPResponseStream.prototype = Object.create(EventTarget.prototype);
73 | HTTPResponseStream.prototype.constructor = HTTPResponseStream;
74 |
75 | HTTPResponseStream.prototype.addData = function (data) {
76 | if (this.done_ || this.error_)
77 | return;
78 | if (data.length == 0)
79 | return;
80 | this.buffer_.push(data);
81 | this.ensureTransmitting();
82 | };
83 | HTTPResponseStream.prototype.endData = function () {
84 | if (this.done_ || this.error_)
85 | return;
86 | this.done_ = true;
87 | if (this.transmitting_)
88 | return;
89 | // If already idling, close the connection.
90 | this.close();
91 | };
92 | HTTPResponseStream.prototype.ensureTransmitting = function () {
93 | if (this.transmitting_)
94 | return;
95 | this.outputStream_.asyncWait({
96 | onOutputStreamReady: stream => { this.transmit(); }
97 | }, 0, 0, utils.currentThread());
98 | this.transmitting_ = true;
99 | };
100 | HTTPResponseStream.prototype.transmit = function () {
101 | this.transmitting_ = false;
102 |
103 | if (this.buffer_.length > 0) {
104 | let data = this.buffer_.join('');
105 | this.buffer_.splice(0);
106 |
107 | let written;
108 | try {
109 | written = this.outputStream_.write(data, data.length);
110 | } catch(err) {
111 | utils.dumpError(err);
112 | this.error_ = true;
113 | this.done_ = true;
114 | this.dispatchEvent('error', err);
115 | return;
116 | }
117 |
118 | if (written < data.length) {
119 | this.buffer_.push(data.substr(written));
120 | }
121 | this.ensureTransmitting();
122 | return;
123 | }
124 |
125 | if (this.done_) {
126 | this.close();
127 | }
128 | };
129 | HTTPResponseStream.prototype.close = function () {
130 | this.outputStream_.close();
131 | this.transport_.close(Cr.NS_OK);
132 | this.dispatchEvent('complete');
133 | }
134 | HTTPResponse.prototype.stream = function() {
135 | return new HTTPResponseStream(this, this.outputStream, this.transport);
136 | };
137 |
138 | function createResponseHeader(status, headers) {
139 | var header = HTTPStatus.getStatusLine(status);
140 |
141 | for (var name in headers) {
142 | header += name + ': ' + headers[name] + CRLF;
143 | }
144 |
145 | return header;
146 | }
147 |
148 | function createResponse(status, headers, body) {
149 | body = body || '';
150 | status = status || 200;
151 | headers = headers || {};
152 |
153 | headers['Content-Length'] = body.length;
154 | if (!headers['Content-Type'])
155 | headers['Content-Type'] = "text/html";
156 |
157 | let header_string = createResponseHeader(status, headers);
158 | let response_array = [header_string, CRLF];
159 |
160 | if (typeof(body) === 'string') {
161 | response_array.push(body);
162 | } else {
163 | for (let i = 0; i < body.length; i++)
164 | response_array.push(String.fromCharCode(body[i]));
165 | }
166 |
167 | return response_array.join('');
168 | }
169 |
170 | exports.HTTPResponse = HTTPResponse;
171 |
--------------------------------------------------------------------------------
/examples/hello-world/stream-reader.js:
--------------------------------------------------------------------------------
1 |
2 | function StreamReader(req) {
3 | this.req = req;
4 | this.buffer = [];
5 | this.checkOnInput = null;
6 |
7 | this.req.ondata(data => {
8 | for (var c of data)
9 | this.buffer.push(c);
10 | if (this.checkOnInput)
11 | this.checkOnInput();
12 | });
13 | }
14 |
15 | StreamReader.prototype.readRest = function(callback) {
16 | this.checkOnInput = function () {
17 | if (this.buffer.length > 0)
18 | callback(this.buffer.splice(0));
19 | };
20 | if (this.buffer.length > 0)
21 | callback(this.buffer.splice(0));
22 | }
23 |
24 | StreamReader.prototype.readLine = function() {
25 | return new Promise((resolve, reject) => {
26 | var s = this.tryGetLine();
27 | if (typeof(s) == 'string') {
28 | resolve(s);
29 | return;
30 | }
31 |
32 | // Check for line.
33 | function checkOnInput() {
34 | var s = this.tryGetLine();
35 | if (typeof(s) == 'string') {
36 | this.checkOnInput = null;
37 | resolve(s);
38 | return;
39 | }
40 | }
41 | this.checkOnInput = checkOnInput;
42 | });
43 | }
44 |
45 | StreamReader.prototype.read = function(bytes) {
46 | return new Promise((resolve, reject) => {
47 | var data = this.tryRead(bytes);
48 | if (data) {
49 | resolve(data);
50 | return;
51 | }
52 |
53 | // Check for line.
54 | function checkOnInput() {
55 | var data = this.tryRead(bytes);
56 | if (data) {
57 | this.checkOnInput = null;
58 | resolve(data);
59 | return;
60 | }
61 | }
62 | this.checkOnInput = checkOnInput;
63 | });
64 | };
65 |
66 |
67 | StreamReader.prototype.readHeader = function() {
68 | // Get header line.
69 | return new Promise((resolve, reject) => {
70 | var headerString = this.tryGetHeader();
71 | if (typeof(headerString) == 'string') {
72 | this.parseHeader(headerString, resolve, reject);
73 | return;
74 | }
75 |
76 | function checkOnInput() {
77 | var headerString = this.tryGetHeader();
78 | if (typeof(headerString) == 'string') {
79 | this.checkOnInput = null;
80 | parseHeader(headerString, resolve, reject);
81 | return;
82 | }
83 | }
84 | this.checkOnInput = checkOnInput;
85 | });
86 | };
87 |
88 | StreamReader.prototype.tryGetHeader = function() {
89 | var a = [];
90 | for (var b of this.buffer)
91 | a.push(String.fromCharCode(b));
92 | var s = a.join('');
93 | var idx = s.indexOf('\r\n\r\n');
94 | if (idx >= 0) {
95 | var header = s.substr(0, idx);
96 | this.buffer.splice(0, idx+4);
97 | return header;
98 | }
99 | };
100 |
101 | StreamReader.prototype.tryGetLine = function() {
102 | var a = [];
103 | for (var b of this.buffer)
104 | a.push(String.fromCharCode(b));
105 | var s = a.join('');
106 | var idx = s.indexOf('\r\n');
107 | if (idx >= 0) {
108 | var line = s.substr(0, idx+2);
109 | this.buffer.splice(0, idx+2);
110 | return line;
111 | }
112 | };
113 |
114 | StreamReader.prototype.tryRead = function(bytes) {
115 | if (this.buffer.length >= bytes)
116 | return this.buffer.splice(0, bytes);
117 | };
118 |
119 | function parseHeader(header, resolve, reject) {
120 | var headerLines = header.split('\r\n');
121 | var requestLine = headerLines.shift().split(' ');
122 |
123 | var method = requestLine[0];
124 | var uri = requestLine[1];
125 | var version = requestLine[2];
126 |
127 | if (version !== "HTTP/1.1") {
128 | reject("Invalid http version: " + version);
129 | return;
130 | }
131 |
132 | var uriParts = uri.split('?');
133 |
134 | var path = uriParts.shift();
135 | var params = parseURLEncodedString(uriParts.join('?'));
136 |
137 | var headers = {};
138 | headerLines.forEach((headerLine) => {
139 | var parts = headerLine.split(': ');
140 | if (parts.length !== 2) {
141 | return;
142 | }
143 |
144 | var name = parts[0];
145 | var value = parts[1];
146 |
147 | headers[name] = value;
148 | });
149 |
150 | resolve({method, path, params, headers});
151 | return;
152 | }
153 |
154 | function parseURLEncodedString(string) {
155 | var values = {};
156 |
157 | string.split('&').forEach((pair) => {
158 | if (!pair) {
159 | return;
160 | }
161 |
162 | var parts = decodeURIComponent(pair).split('=');
163 |
164 | var name = parts.shift();
165 | var value = parts.join('=');
166 |
167 | setOrAppendValue(values, name, value);
168 | });
169 |
170 | return values;
171 | }
172 |
173 | function setOrAppendValue(object, name, value) {
174 | var existingValue = object[name];
175 | if (existingValue === undefined) {
176 | object[name] = value;
177 | } else {
178 | if (Array.isArray(existingValue)) {
179 | existingValue.push(value);
180 | } else {
181 | object[name] = [existingValue, value];
182 | }
183 | }
184 | }
185 |
--------------------------------------------------------------------------------
/examples/hello-world/sha-1.js:
--------------------------------------------------------------------------------
1 | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2 | /* SHA-1 implementation in JavaScript (c) Chris Veness 2002-2014 / MIT Licence */
3 | /* */
4 | /* - see http://csrc.nist.gov/groups/ST/toolkit/secure_hashing.html */
5 | /* http://csrc.nist.gov/groups/ST/toolkit/examples.html */
6 | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
7 |
8 | /* jshint node:true *//* global define, escape, unescape */
9 | 'use strict';
10 |
11 |
12 | /**
13 | * SHA-1 hash function reference implementation.
14 | *
15 | * @namespace
16 | */
17 | var Sha1 = {};
18 |
19 |
20 | /**
21 | * Generates SHA-1 hash of string.
22 | *
23 | * @param {string} msg - (Unicode) string to be hashed.
24 | * @returns {string} Hash of msg as hex character string.
25 | */
26 | Sha1.hash = function(msg) {
27 | // convert string to UTF-8, as SHA only deals with byte-streams
28 | msg = msg.utf8Encode();
29 |
30 | // constants [§4.2.1]
31 | var K = [ 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6 ];
32 |
33 | // PREPROCESSING
34 |
35 | msg += String.fromCharCode(0x80); // add trailing '1' bit (+ 0's padding) to string [§5.1.1]
36 |
37 | // convert string msg into 512-bit/16-integer blocks arrays of ints [§5.2.1]
38 | var l = msg.length/4 + 2; // length (in 32-bit integers) of msg + ‘1’ + appended length
39 | var N = Math.ceil(l/16); // number of 16-integer-blocks required to hold 'l' ints
40 | var M = new Array(N);
41 |
42 | for (var i=0; i>> 32, but since JS converts
51 | // bitwise-op args to 32 bits, we need to simulate this by arithmetic operators
52 | M[N-1][14] = ((msg.length-1)*8) / Math.pow(2, 32); M[N-1][14] = Math.floor(M[N-1][14]);
53 | M[N-1][15] = ((msg.length-1)*8) & 0xffffffff;
54 |
55 | // set initial hash value [§5.3.1]
56 | var H0 = 0x67452301;
57 | var H1 = 0xefcdab89;
58 | var H2 = 0x98badcfe;
59 | var H3 = 0x10325476;
60 | var H4 = 0xc3d2e1f0;
61 |
62 | // HASH COMPUTATION [§6.1.2]
63 |
64 | var W = new Array(80); var a, b, c, d, e;
65 | for (var i=0; i>>(32-n));
130 | };
131 |
132 |
133 | /**
134 | * Hexadecimal representation of a number.
135 | * @private
136 | */
137 | Sha1.toHexStr = function(n) {
138 | // note can't use toString(16) as it is implementation-dependant,
139 | // and in IE returns signed numbers when used on full words
140 | var s="", v;
141 | for (var i=7; i>=0; i--) { v = (n>>>(i*4)) & 0xf; s += v.toString(16); }
142 | return s;
143 | };
144 |
145 |
146 | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
147 |
148 |
149 | /** Extend String object with method to encode multi-byte string to utf8
150 | * - monsur.hossa.in/2012/07/20/utf-8-in-javascript.html */
151 | if (typeof String.prototype.utf8Encode == 'undefined') {
152 | String.prototype.utf8Encode = function() {
153 | return unescape( encodeURIComponent( this ) );
154 | };
155 | }
156 |
157 | /** Extend String object with method to decode utf8 string to multi-byte */
158 | if (typeof String.prototype.utf8Decode == 'undefined') {
159 | String.prototype.utf8Decode = function() {
160 | try {
161 | return decodeURIComponent( escape( this ) );
162 | } catch (e) {
163 | return this; // invalid UTF-8? return as-is
164 | }
165 | };
166 | }
167 |
168 |
169 | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
170 | if (typeof module != 'undefined' && module.exports) module.exports = Sha1; // CommonJs export
171 | if (typeof define == 'function' && define.amd) define([], function() { return Sha1; }); // AMD
172 |
--------------------------------------------------------------------------------
/flyweb/lib/dns-codes.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | function defineType(values) {
4 | function T(value) {
5 | for (var name in T) {
6 | if (T[name] === value) {
7 | return name;
8 | }
9 | }
10 |
11 | return null;
12 | }
13 |
14 | for (var name in values) {
15 | T[name] = values[name];
16 | }
17 |
18 | return T;
19 | }
20 |
21 | const QUERY_RESPONSE_CODES = defineType({
22 | QUERY : 0, // RFC 1035 - Query
23 | RESPONSE : 1 // RFC 1035 - Reponse
24 | });
25 |
26 | const OPERATION_CODES = defineType({
27 | QUERY : 0, // RFC 1035 - Query
28 | IQUERY : 1, // RFC 1035 - Inverse Query
29 | STATUS : 2, // RFC 1035 - Status
30 | NOTIFY : 4, // RFC 1996 - Notify
31 | UPDATE : 5 // RFC 2136 - Update
32 | });
33 |
34 | const AUTHORITATIVE_ANSWER_CODES = defineType({
35 | NO : 0, // RFC 1035 - Not Authoritative
36 | YES : 1 // RFC 1035 - Is Authoritative
37 | });
38 |
39 | const TRUNCATED_RESPONSE_CODES = defineType({
40 | NO : 0, // RFC 1035 - Not Truncated
41 | YES : 1 // RFC 1035 - Is Truncated
42 | });
43 |
44 | const RECURSION_DESIRED_CODES = defineType({
45 | NO : 0, // RFC 1035 - Recursion Not Desired
46 | YES : 1 // RFC 1035 - Recursion Is Desired
47 | });
48 |
49 | const RECURSION_AVAILABLE_CODES = defineType({
50 | NO : 0, // RFC 1035 - Recursive Query Support Not Available
51 | YES : 1 // RFC 1035 - Recursive Query Support Is Available
52 | });
53 |
54 | const AUTHENTIC_DATA_CODES = defineType({
55 | NO : 0, // RFC 4035 - Response Has Not Been Authenticated/Verified
56 | YES : 1 // RFC 4035 - Response Has Been Authenticated/Verified
57 | });
58 |
59 | const CHECKING_DISABLED_CODES = defineType({
60 | NO : 0, // RFC 4035 - Authentication/Verification Checking Not Disabled
61 | YES : 1 // RFC 4035 - Authentication/Verification Checking Is Disabled
62 | });
63 |
64 | const RETURN_CODES = defineType({
65 | NOERROR : 0, // RFC 1035 - No Error
66 | FORMERR : 1, // RFC 1035 - Format Error
67 | SERVFAIL : 2, // RFC 1035 - Server Failure
68 | NXDOMAIN : 3, // RFC 1035 - Non-Existent Domain
69 | NOTIMP : 4, // RFC 1035 - Not Implemented
70 | REFUSED : 5, // RFC 1035 - Query Refused
71 | YXDOMAIN : 6, // RFC 2136 - Name Exists when it should not
72 | YXRRSET : 7, // RFC 2136 - RR Set Exists when it should not
73 | NXRRSET : 8, // RFC 2136 - RR Set that should exist does not
74 | NOTAUTH : 9, // RFC 2136 - Server Not Authoritative for zone
75 | NOTZONE : 10 // RFC 2136 - NotZone Name not contained in zone
76 | });
77 |
78 | const CLASS_CODES = defineType({
79 | IN : 1, // RFC 1035 - Internet
80 | CS : 2, // RFC 1035 - CSNET
81 | CH : 3, // RFC 1035 - CHAOS
82 | HS : 4, // RFC 1035 - Hesiod
83 | NONE : 254, // RFC 2136 - None
84 | ANY : 255 // RFC 1035 - Any
85 | });
86 |
87 | const OPTION_CODES = defineType({
88 | LLQ : 1, // RFC ???? - Long-Lived Queries
89 | UL : 2, // RFC ???? - Update Leases
90 | NSID : 3, // RFC ???? - Name Server Identifier
91 | OWNER : 4, // RFC ???? - Owner
92 | UNKNOWN : 65535 // RFC ???? - Token
93 | });
94 |
95 | const RECORD_TYPES = defineType({
96 | SIGZERO : 0, // RFC 2931
97 | A : 1, // RFC 1035
98 | NS : 2, // RFC 1035
99 | MD : 3, // RFC 1035
100 | MF : 4, // RFC 1035
101 | CNAME : 5, // RFC 1035
102 | SOA : 6, // RFC 1035
103 | MB : 7, // RFC 1035
104 | MG : 8, // RFC 1035
105 | MR : 9, // RFC 1035
106 | NULL : 10, // RFC 1035
107 | WKS : 11, // RFC 1035
108 | PTR : 12, // RFC 1035
109 | HINFO : 13, // RFC 1035
110 | MINFO : 14, // RFC 1035
111 | MX : 15, // RFC 1035
112 | TXT : 16, // RFC 1035
113 | RP : 17, // RFC 1183
114 | AFSDB : 18, // RFC 1183
115 | X25 : 19, // RFC 1183
116 | ISDN : 20, // RFC 1183
117 | RT : 21, // RFC 1183
118 | NSAP : 22, // RFC 1706
119 | NSAP_PTR : 23, // RFC 1348
120 | SIG : 24, // RFC 2535
121 | KEY : 25, // RFC 2535
122 | PX : 26, // RFC 2163
123 | GPOS : 27, // RFC 1712
124 | AAAA : 28, // RFC 1886
125 | LOC : 29, // RFC 1876
126 | NXT : 30, // RFC 2535
127 | EID : 31, // RFC ????
128 | NIMLOC : 32, // RFC ????
129 | SRV : 33, // RFC 2052
130 | ATMA : 34, // RFC ????
131 | NAPTR : 35, // RFC 2168
132 | KX : 36, // RFC 2230
133 | CERT : 37, // RFC 2538
134 | DNAME : 39, // RFC 2672
135 | OPT : 41, // RFC 2671
136 | APL : 42, // RFC 3123
137 | DS : 43, // RFC 4034
138 | SSHFP : 44, // RFC 4255
139 | IPSECKEY : 45, // RFC 4025
140 | RRSIG : 46, // RFC 4034
141 | NSEC : 47, // RFC 4034
142 | DNSKEY : 48, // RFC 4034
143 | DHCID : 49, // RFC 4701
144 | NSEC3 : 50, // RFC ????
145 | NSEC3PARAM : 51, // RFC ????
146 | HIP : 55, // RFC 5205
147 | SPF : 99, // RFC 4408
148 | UINFO : 100, // RFC ????
149 | UID : 101, // RFC ????
150 | GID : 102, // RFC ????
151 | UNSPEC : 103, // RFC ????
152 | TKEY : 249, // RFC 2930
153 | TSIG : 250, // RFC 2931
154 | IXFR : 251, // RFC 1995
155 | AXFR : 252, // RFC 1035
156 | MAILB : 253, // RFC 1035
157 | MAILA : 254, // RFC 1035
158 | ANY : 255, // RFC 1035
159 | DLV : 32769 // RFC 4431
160 | });
161 |
162 | var DNSCodes = {
163 | QUERY_RESPONSE_CODES : QUERY_RESPONSE_CODES,
164 | OPERATION_CODES : OPERATION_CODES,
165 | AUTHORITATIVE_ANSWER_CODES : AUTHORITATIVE_ANSWER_CODES,
166 | TRUNCATED_RESPONSE_CODES : TRUNCATED_RESPONSE_CODES,
167 | RECURSION_DESIRED_CODES : RECURSION_DESIRED_CODES,
168 | RECURSION_AVAILABLE_CODES : RECURSION_AVAILABLE_CODES,
169 | AUTHENTIC_DATA_CODES : AUTHENTIC_DATA_CODES,
170 | CHECKING_DISABLED_CODES : CHECKING_DISABLED_CODES,
171 | RETURN_CODES : RETURN_CODES,
172 | CLASS_CODES : CLASS_CODES,
173 | OPTION_CODES : OPTION_CODES,
174 | RECORD_TYPES : RECORD_TYPES
175 | };
176 |
177 | exports.DNSCodes = DNSCodes;
178 |
--------------------------------------------------------------------------------
/flyweb/lib/dns-packet.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var {ByteArray} = require('./byte-array');
4 | var {DNSUtils} = require('./dns-utils');
5 | var {DNSRecord,
6 | DNSQuestionRecord,
7 | DNSResourceRecord} = require('./dns-records');
8 |
9 | const DNS_PACKET_RECORD_SECTION_TYPES = [
10 | 'QD', // Question
11 | 'AN', // Answer
12 | 'NS', // Authority
13 | 'AR' // Additional
14 | ];
15 |
16 | /**
17 | * DNS Packet Structure
18 | * *************************************************
19 | *
20 | * Header
21 | * ======
22 | *
23 | * 00 2-Bytes 15
24 | * -------------------------------------------------
25 | * |00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|
26 | * -------------------------------------------------
27 | * |<==================== ID =====================>|
28 | * |QR|<== OP ===>|AA|TC|RD|RA|UN|AD|CD|<== RC ===>|
29 | * |<================== QDCOUNT ==================>|
30 | * |<================== ANCOUNT ==================>|
31 | * |<================== NSCOUNT ==================>|
32 | * |<================== ARCOUNT ==================>|
33 | * -------------------------------------------------
34 | *
35 | * ID: 2-Bytes
36 | * FLAGS: 2-Bytes
37 | * - QR: 1-Bit
38 | * - OP: 4-Bits
39 | * - AA: 1-Bit
40 | * - TC: 1-Bit
41 | * - RD: 1-Bit
42 | * - RA: 1-Bit
43 | * - UN: 1-Bit
44 | * - AD: 1-Bit
45 | * - CD: 1-Bit
46 | * - RC: 4-Bits
47 | * QDCOUNT: 2-Bytes
48 | * ANCOUNT: 2-Bytes
49 | * NSCOUNT: 2-Bytes
50 | * ARCOUNT: 2-Bytes
51 | *
52 | *
53 | * Data
54 | * ====
55 | *
56 | * 00 2-Bytes 15
57 | * -------------------------------------------------
58 | * |00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|
59 | * -------------------------------------------------
60 | * |??=============== QD[...] ===============???>|
61 | * |??=============== AN[...] ===============???>|
62 | * |??=============== NS[...] ===============???>|
63 | * |??=============== AR[...] ===============???>|
64 | * -------------------------------------------------
65 | *
66 | * QD: ??-Bytes
67 | * AN: ??-Bytes
68 | * NS: ??-Bytes
69 | * AR: ??-Bytes
70 | *
71 | *
72 | * Question Record
73 | * ===============
74 | *
75 | * 00 2-Bytes 15
76 | * -------------------------------------------------
77 | * |00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|
78 | * -------------------------------------------------
79 | * |??================ NAME =================???>|
80 | * |<=================== TYPE ====================>|
81 | * |<=================== CLASS ===================>|
82 | * -------------------------------------------------
83 | *
84 | * NAME: ??-Bytes
85 | * TYPE: 2-Bytes
86 | * CLASS: 2-Bytes
87 | *
88 | *
89 | * Resource Record
90 | * ===============
91 | *
92 | * 00 4-Bytes 31
93 | * -------------------------------------------------
94 | * |00|02|04|06|08|10|12|14|16|18|20|22|24|26|28|30|
95 | * -------------------------------------------------
96 | * |??================ NAME =================???>|
97 | * |<======= TYPE ========>|<======= CLASS =======>|
98 | * |<==================== TTL ====================>|
99 | * |<====== DATALEN ======>|??==== DATA =====???>|
100 | * -------------------------------------------------
101 | *
102 | * NAME: ??-Bytes
103 | * TYPE: 2-Bytes
104 | * CLASS: 2-Bytes
105 | * DATALEN: 2-Bytes
106 | * DATA: ??-Bytes (Specified By DATALEN)
107 | */
108 | function DNSPacket(arrayBuffer) {
109 | this.flags = DNSUtils.valueToFlags(0x0000);
110 | this.records = {};
111 |
112 | DNSPacket.RECORD_SECTION_TYPES.forEach((recordSectionType) => {
113 | this.records[recordSectionType] = [];
114 | });
115 |
116 | if (!arrayBuffer) {
117 | return this;
118 | }
119 |
120 | exports.PACKETS.push({buffer:arrayBuffer, packet:this});
121 |
122 | const byteArray = new ByteArray(arrayBuffer);
123 | const reader = byteArray.getReader();
124 |
125 | if (reader.getValue(2) !== 0x0000) {
126 | throw new Error('Packet must start with 0x0000');
127 | }
128 |
129 | this.flags = DNSUtils.valueToFlags(reader.getValue(2));
130 |
131 | const recordCounts = {};
132 |
133 | // Parse the record counts.
134 | DNSPacket.RECORD_SECTION_TYPES.forEach((recordSectionType) => {
135 | recordCounts[recordSectionType] = reader.getValue(2);
136 | });
137 |
138 | // Parse the actual records.
139 | DNSPacket.RECORD_SECTION_TYPES.forEach((recordSectionType) => {
140 | const count = recordCounts[recordSectionType];
141 |
142 | for (let i = 0; i < count; i++) {
143 | let offset = reader.offset;
144 | let name = DNSUtils.byteArrayToName(reader);// || name;
145 |
146 | if (recordSectionType === 'QD') {
147 | let rec = new DNSQuestionRecord(
148 | name, // Name
149 | reader.getValue(2), // Type
150 | reader.getValue(2) // Class
151 | );
152 | rec.offset = offset;
153 | this.addRecord(recordSectionType, rec);
154 | }
155 |
156 | else {
157 | let type = reader.getValue(2);
158 | let cls = reader.getValue(2);
159 | let ttl = reader.getValue(4);
160 | let dataLen = reader.getValue(2);
161 | let dataOffset = reader.offset;
162 | let data = reader.getBytes(dataLen);
163 | let rec = new DNSResourceRecord(name, type, cls, ttl, data);
164 | rec.parseData(byteArray, dataOffset);
165 | rec.offset = offset;
166 | this.addRecord(recordSectionType, rec);
167 | }
168 | }
169 | });
170 |
171 | if (!reader.isEOF()) {
172 | console.warn('Did not complete parsing packet data');
173 | }
174 | }
175 |
176 | DNSPacket.RECORD_SECTION_TYPES = DNS_PACKET_RECORD_SECTION_TYPES;
177 |
178 | DNSPacket.prototype.constructor = DNSPacket;
179 |
180 | DNSPacket.prototype.addRecord = function(recordSectionType, record) {
181 | this.records[recordSectionType].push(record);
182 | };
183 |
184 | DNSPacket.prototype.getRecords = function(recordSectionType) {
185 | return this.records[recordSectionType];
186 | };
187 |
188 | DNSPacket.prototype.serialize = function() {
189 | const byteArray = new ByteArray();
190 |
191 | byteArray.push(0x0000, 2);
192 | byteArray.push(DNSUtils.flagsToValue(this.flags), 2);
193 |
194 | DNSPacket.RECORD_SECTION_TYPES.forEach((recordSectionType) => {
195 | byteArray.push(this.records[recordSectionType].length, 2);
196 | });
197 |
198 | DNSPacket.RECORD_SECTION_TYPES.forEach((recordSectionType) => {
199 | this.records[recordSectionType].forEach((record) => {
200 | let ba = DNSUtils.nameToByteArray(record.name);
201 | byteArray.append(DNSUtils.nameToByteArray(record.name));
202 | byteArray.push(record.recordType, 2);
203 | byteArray.push(record.classCode, 2);
204 |
205 | // No more data to serialize if this is a question record.
206 | if (recordSectionType === 'QD') {
207 | return;
208 | }
209 |
210 | byteArray.push(record.ttl, 4);
211 |
212 | byteArray.push(record.data.length, 2);
213 | byteArray.append(record.data);
214 | });
215 | });
216 |
217 | return byteArray.buffer;
218 | };
219 |
220 | exports.DNSPacket = DNSPacket;
221 | exports.PACKETS = [];
222 |
--------------------------------------------------------------------------------
/examples/hello-world/web-socket.js:
--------------------------------------------------------------------------------
1 |
2 | var WS_ACCEPT_SUFFIX = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
3 |
4 | var WsOpcodes = {
5 | Continuation: 0,
6 | Text: 1,
7 | Binary: 2,
8 | Close: 8,
9 | Ping: 9,
10 | Pong: 10
11 | };
12 |
13 | function Cursor(array) {
14 | this.array_ = array;
15 | this.idx_ = 0;
16 | }
17 |
18 | Cursor.prototype.hasByte = function () {
19 | return this.idx_ < this.array_.length;
20 | }
21 | Cursor.prototype.hasBytes = function (bytes) {
22 | return (this.idx_ + bytes) <= this.array_.length;
23 | }
24 | Cursor.prototype.readByte = function () {
25 | if (this.idx_ >= this.array_.length)
26 | return undefined;
27 | var result = this.array_[this.idx_];
28 | this.idx_ += 1;
29 | return result;
30 | }
31 | Cursor.prototype.readBytes = function (bytes) {
32 | var result = [];
33 | var i;
34 | for (i = 0; i < bytes; i++) {
35 | if (!this.hasByte())
36 | break;
37 | result.push(this.readByte());
38 | }
39 | return result;
40 | }
41 | Cursor.prototype.splice = function () {
42 | this.array_.splice(0, this.idx_);
43 | }
44 |
45 | function ServerWebSocket({onerror, onmessage, instream, outstream, headers, stringMessage}) {
46 | this.onerror = onerror;
47 | this.onmessage = onmessage;
48 |
49 | this.stringMessage = stringMessage;
50 |
51 | this.instream = instream;
52 | this.outstream = outstream;
53 |
54 | this.inputBuffer = [];
55 |
56 | // Ensure Upgrade: websocket header exists.
57 | if (headers['Upgrade'] != 'websocket') {
58 | this.badHeader("Upgrade header not given.");
59 | return;
60 | }
61 |
62 | // Get the websocket key.
63 | var key = headers['Sec-WebSocket-Key'];
64 | if (!key) {
65 | this.badHeader("Sec-WebSocket-Key not given.");
66 | return;
67 | }
68 |
69 | // Ensure version is 13.
70 | var version = headers['Sec-WebSocket-Version'];
71 | if (version != "13") {
72 | this.badHeader("Unrecognized Sec-WebSocket-Version: " + version);
73 | return;
74 | }
75 |
76 | instream.readRest(data => {
77 | for (var i = 0; i < data.length; i++)
78 | this.inputBuffer.push(data[i]);
79 | this.checkInputBuffer();
80 | });
81 |
82 | // Hash it.
83 | var hashString = Sha1.hashToString(key + WS_ACCEPT_SUFFIX);
84 | var uuenc = btoa(hashString);
85 |
86 | console.log("Sending uuenc: " + uuenc);
87 |
88 | outstream.send("HTTP/1.1 101 Switching Protocols\r\n");
89 | outstream.send("Upgrade: websocket\r\n");
90 | outstream.send("Connection: Upgrade\r\n");
91 | outstream.send("Sec-WebSocket-Accept: " + uuenc + "\r\n");
92 | outstream.send("\r\n");
93 | }
94 |
95 | ServerWebSocket.prototype.badHeader = function (message) {
96 | console.log("Bad header: " + message);
97 | if (this.onerror) {
98 | this.onerror("Bad header: " + message);
99 | return;
100 | }
101 | }
102 |
103 | ServerWebSocket.prototype.emitError = function (message) {
104 | console.log("Web Socket Error: " + message);
105 | if (this.onerror) {
106 | this.onerror(message);
107 | return;
108 | }
109 | }
110 |
111 | /**
112 | * Format:
113 | *
114 | * Byte 0 - FRRROOOO
115 | * F - Fin bit.
116 | * R - Reserved (0)
117 | * O - Opcode
118 | *
119 | * Byte 1 - MPPPPPPP
120 | * M - Mask bit.
121 | * P - Payload size.
122 | *
123 | * If P = 126, then next 2 bytes are extended payload length.
124 | * If P = 127, then next 4 bytes are extended payload length.
125 | *
126 | * Next 4 bytes - Mask.
127 | */
128 | ServerWebSocket.prototype.checkInputBuffer = function () {
129 | console.log("CheckInputBuffer", this.inputBuffer);
130 | var curs = new Cursor(this.inputBuffer);
131 | if (!curs.hasBytes(2))
132 | return;
133 |
134 | var b1 = curs.readByte();
135 | var finBit = (b1 >> 7) & 0x1;
136 | var opcode = (b1 & 0xf);
137 |
138 | var b2 = curs.readByte();
139 | var maskBit = (b2 >> 7) & 0x1;
140 | var payloadSize = b2 & 0x7f;
141 |
142 | if (!maskBit) {
143 | this.emitError("Expected mask bit!");
144 | return;
145 | }
146 |
147 | console.log("Got bytes", {finBit, opcode, maskBit, payloadSize});
148 |
149 | if (payloadSize == 126) {
150 | if (!curs.hasBytes(2))
151 | return;
152 | payloadSize = (curs.readByte() << 8) | curs.readByte();
153 | }
154 |
155 | if (payloadSize == 127) {
156 | // Error, payload too large.
157 | this.emitError("Client frame payload too large.");
158 | return;
159 | }
160 |
161 | // Read mask.
162 | if (!curs.hasBytes(4))
163 | return;
164 | var maskBytes = curs.readBytes(4);
165 | console.log("Got maskBytes", maskBytes);
166 | console.log("Cursor", curs);
167 |
168 | // Read payload.
169 | if (!curs.hasBytes(payloadSize))
170 | return;
171 |
172 | var payload = curs.readBytes(payloadSize);
173 | console.log("Got payload", payload);
174 | // Unmask the payload.
175 | for (var i = 0; i < payload.length; i++) {
176 | payload[i] ^= maskBytes[i % 4];
177 | }
178 |
179 | // Splice the frame out from the buffer.
180 | curs.splice();
181 |
182 | this.handleFrame({opcode, finBit, payload});
183 | };
184 |
185 | ServerWebSocket.prototype.send = function (data) {
186 | if (typeof(data) == 'string') {
187 | this.sendFrame({opcode: WsOpcodes.Text, payload: data});
188 | } else {
189 | this.sendFrame({opcode: WsOpcodes.Binary, payload: data});
190 | }
191 | };
192 |
193 | ServerWebSocket.prototype.handleFrame = function ({opcode, finBit, payload}) {
194 | if (!finBit) {
195 | // Cannot handle multi-part messages.
196 | this.emitError("Cannot handle multi-part frames.");
197 | }
198 | if (opcode == WsOpcodes.Continuation) {
199 | this.emitError("Cannot handle continuation frames.");
200 | }
201 |
202 | if (opcode == WsOpcodes.Text) {
203 | // Convert message to text.
204 | var message = payload.map(function (i) { return String.fromCharCode(i); }).join('');
205 | message = decodeURIComponent(escape(message));
206 | if (this.onmessage)
207 | this.onmessage(message);
208 | return;
209 | }
210 |
211 | if (opcode == WsOpcodes.Binary) {
212 | // Convert message to text.
213 | if (this.onmessage)
214 | this.onmessage(payload);
215 | return;
216 | }
217 |
218 | if (opcode == WsOpcodes.Ping) {
219 | this.sendFrame({opcode: WsOpcodes.Pont, payload:''});
220 | return;
221 | }
222 |
223 | if (opcode == WsOpcodes.Pong) {
224 | // Ignore.
225 | }
226 |
227 | // Otherwise, ignore.
228 | this.emitError("Unknown frame type received: " + opcode);
229 | };
230 |
231 | ServerWebSocket.prototype.sendFrame = function ({opcode, payload}) {
232 | var frame = [];
233 | // Write opcode. We don't send fragmented frames at all.
234 | frame.push(0x80 | opcode);
235 |
236 | if (typeof(payload) == 'string') {
237 | var message = unescape(encodeURIComponent(payload));
238 | var bytes = [];
239 | for (var i = 0; i < message.length; i++) {
240 | bytes.push(message.charCodeAt(i));
241 | }
242 | payload = bytes;
243 | }
244 |
245 | // Write payload size. No masking.
246 | var payloadSize = payload.length;
247 | if (payloadSize > 0xffff) {
248 | this.emitError("Payload too large for outgoing frame.");
249 | return;
250 | }
251 |
252 | if (payloadSize < 126) {
253 | frame.push(payloadSize);
254 | } else {
255 | frame.push(126);
256 | frame.push((payloadSize >> 8)|0xff);
257 | frame.push(payloadSize & 0xff);
258 | }
259 |
260 | for (var i = 0; i < payload.length; i++) {
261 | frame.push(payload[i]);
262 | }
263 |
264 | // Send frame.
265 | var frameStr = frame.map(function (i) { return String.fromCharCode(i); }).join('');
266 | this.outstream.send(frameStr);
267 | };
268 |
--------------------------------------------------------------------------------
/examples/hello-world/hello-world.js:
--------------------------------------------------------------------------------
1 |
2 | function subPage(subpath) {
3 | var parts = document.location.toString().split("/");
4 | parts.pop();
5 | parts.push(subpath);
6 | return parts.join("/");
7 | }
8 | function runServer() {
9 | document.location = subPage("hw-server.html");
10 | }
11 | function runClient() {
12 | document.location = subPage("hw-client.html");
13 | }
14 |
15 |
16 | var CurrentDiscoverList = null;
17 |
18 | var FlyWebServices = [];
19 | var FlyWebConnection = null;
20 |
21 | function clientDiscover() {
22 | // Clear out FlyWebServices list.
23 | FlyWebServices = [];
24 |
25 | var stopOldDiscovery;
26 | if (CurrentDiscoverList) {
27 | stopOldDiscovery = CurrentDiscoverList.stopDiscovery();
28 | CurrenetDiscoverList = null;
29 | } else {
30 | stopOldDiscovery = Promise.resolve();
31 | }
32 | stopOldDiscovery.then(() => {
33 | return navigator.discoverNearbyServices()
34 | }).then(svcs => {
35 | CurrentDiscoverList = svcs;
36 | console.log("clientDiscover got services!");
37 |
38 | svcs.onservicefound(function (event) {
39 | console.log("clientDiscover: onservicefound: ", event.serviceId);
40 | var svc = svcs.get(event.serviceId);
41 | console.log("clientDiscover: service: ", svc);
42 | addClientService(svc);
43 | renderClientServiceList();
44 | });
45 |
46 | for (var i = 0; i < svcs.length; i++) {
47 | var svc = svcs.get(i);
48 | console.log("clientDiscover: saw service: ", svc);
49 | addClientService(svc);
50 | }
51 | renderClientServiceList();
52 | });
53 | }
54 |
55 | function establishConnection(serviceId) {
56 | console.log('establishConnection HERE!: ' + serviceId);
57 | for (var i = 0; i < FlyWebServices.length; i++) {
58 | var svc = FlyWebServices[i];
59 | console.log('establishConnection checking svc: ' + svc.id);
60 | if (svc.id != serviceId)
61 | continue;
62 | // Found service.
63 | console.log('establishConnection proceeding');
64 | navigator.connectToService(serviceId).then(conn => {
65 | console.log('establishConnection got connection');
66 | FlyWebConnection = conn;
67 | window.open(conn.url, '_blank');
68 | });
69 | }
70 | }
71 |
72 | function addClientService(svc) {
73 | FlyWebServices.push(svc);
74 | }
75 |
76 | function renderClientServiceList() {
77 | var innerHTML = [];
78 | for (var i = 0; i < FlyWebServices.length; i++) {
79 | var svc = FlyWebServices[i];
80 | var svcText = renderClientService(svc);
81 | innerHTML.push(svcText);
82 | }
83 |
84 | var listElem = document.getElementById("client-services-list");
85 | listElem.innerHTML = innerHTML.join("\n");
86 | }
87 |
88 | function renderClientService(svc, outArray) {
89 | return [
90 | '',
91 | '
', svc.type, '
',
92 | '
', svc.name, '
',
93 | '
',
96 | 'Establish Connection',
97 | '
',
98 | '
',
99 | ' '
100 | ].join('\n');
101 | }
102 |
103 | var WORDS = ["candy", "james", "pool", "singalong",
104 | "able", "pine", "tree", "clarity", "star",
105 | "ice", "sky", "pluto", "kind", "stock",
106 | "lift", "poppy"];
107 | function generateServerName() {
108 | // Generate a random server name.
109 | var arr = [];
110 | for (var i = 0; i < 2; i++) {
111 | arr.push(WORDS[(Math.random() * WORDS.length)|0]);
112 | }
113 | return arr.join("_");
114 | }
115 |
116 | var ServerName = null;
117 | function initServer() {
118 | ServerName = generateServerName();
119 | var serverNameElem = document.getElementById("server-name");
120 | serverNameElem.innerHTML = ServerName;
121 | }
122 |
123 | function startServer() {
124 | if (!ServerName)
125 | initServer();
126 |
127 | navigator.publishServer(ServerName).then(server => {
128 | console.log("Published server: " + JSON.stringify(server));
129 | server.onrequest(requestEvent => {
130 | var rawReq = requestEvent.requestRaw();
131 | var streamReader = new StreamReader(rawReq);
132 | GLOBAL_STREAM_READER = streamReader;
133 | streamReader.readHeader().then(reqinfo => {
134 | console.log("HEADER: ", reqinfo);
135 | var method = reqinfo.method;
136 | var path = reqinfo.path;
137 | //console.log("Got " + method + " request for " + path);
138 | if (path == "/get-text") {
139 | serveGetText(requestEvent);
140 | } else if (path == "/web/socket") {
141 | serveWebSocket(requestEvent, streamReader, reqinfo.headers);
142 | } else if (path == "/") {
143 | serveMainPage(requestEvent);
144 | } else {
145 | serveErrorPage(requestEvent);
146 | }
147 | });
148 | });
149 | });
150 | }
151 |
152 | function serveGetText(requestEvent) {
153 | requestEvent.stream().then(stream => {
154 | stream.oncomplete(() => {
155 | //console.log("Sent data!\n");
156 | });
157 | var inputElement = document.getElementById('sendText');
158 | var text = '' + inputElement.value;
159 | var content = JSON.stringify({text: text});
160 | stream.send("HTTP/1.1 200 OK\r\n");
161 | stream.send("Content-Type: application/json\r\n");
162 | stream.send("Content-Length: " + content.length + "\r\n");
163 | stream.send("Access-Control-Allow-Origin: *\r\n");
164 | stream.send("\r\n");
165 | stream.send(content);
166 | stream.end();
167 | });
168 | }
169 |
170 | function serveWebSocket(requestEvent, instream, headers) {
171 | requestEvent.stream().then(outstream => {
172 | function onmessage(msg) {
173 | console.log("WebSocket got message: " + msg);
174 | }
175 | function onerror(msg) {
176 | console.log("WebSocket got error: " + msg);
177 | }
178 | var ws = new ServerWebSocket({
179 | instream, outstream, headers, onmessage, onerror,
180 | stringMessage: true});
181 | window.SERVER_WS = ws;
182 |
183 | var inputElement = document.getElementById('sendText');
184 | inputElement.onkeyup = function () {
185 | var text = '' + inputElement.value;
186 | var content = JSON.stringify({text: text});
187 | ws.send(content);
188 | };
189 | });
190 | }
191 |
192 | function serveMainPage(requestEvent) {
193 | requestEvent.stream().then(stream => {
194 | stream.oncomplete(() => {
195 | //console.log("Sent data!\n");
196 | });
197 | function updateThing() {
198 | if (!window.UPDATE_TIME)
199 | window.UPDATE_TIME = 60000;
200 |
201 | var wsurl = "ws://" + window.location.host + "/web/socket";
202 | var ws = new WebSocket(wsurl);
203 |
204 | ws.onmessage = function (msg) {
205 | var data = JSON.parse(msg.data);
206 | var elem = document.getElementById('h');
207 | var text = (data.text.length > 0) ? "'" + data.text + "'" : "NOTHING!";
208 | elem.innerHTML = "WebSocket SAYS " + text;
209 | window.OLD_TEXT = data.text;
210 | }
211 |
212 | /*
213 | var xmlhttp = new XMLHttpRequest();
214 | var oldText = window.OLD_TEXT || '';
215 |
216 | xmlhttp.onreadystatechange = function() {
217 | if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
218 | var data = JSON.parse(xmlhttp.responseText);
219 | if (data.text == oldText)
220 | return;
221 | var elem = document.getElementById('h');
222 | var text = (data.text.length > 0) ? "'" + data.text + "'" : "NOTHING!";
223 | elem.innerHTML = "OTHER PAGE SAYS " + text;
224 | window.OLD_TEXT = data.text;
225 | }
226 | }
227 | xmlhttp.open("GET", "/get-text", true);
228 | xmlhttp.send();
229 | */
230 |
231 | setTimeout(updateThing, window.UPDATE_TIME);
232 | }
233 | var content = ["AAAAAAAAAHAHAHAHAHAHAH!!!!!! ",
234 | '',
237 | "",
238 | 'OTHER PAGE SAYS WHAT? ',
239 | ""].join('\n');
240 |
241 | stream.send("HTTP/1.1 200 OK\r\n");
242 | stream.send("Content-Type: text/html\r\n");
243 | stream.send("Content-Length: " + content.length + "\r\n");
244 | stream.send("\r\n");
245 | stream.send(content);
246 | stream.end();
247 | });
248 | }
249 |
250 | function serveErrorPage(requestEvent) {
251 | requestEvent.stream().then(stream => {
252 | stream.oncomplete(() => {
253 | //console.log("Sent data!\n");
254 | });
255 | var content = ["Not Found ",
256 | 'PAGE NOT FOUND '].join('\n');
257 | stream.send("HTTP/1.1 404 Not Found\r\n");
258 | stream.send("Content-Type: text/html\r\n");
259 | stream.send("Content-Length: " + content.length + "\r\n");
260 | stream.send("\r\n");
261 | stream.send(content);
262 | stream.end();
263 | });
264 | }
265 |
--------------------------------------------------------------------------------
/flyweb/lib/http-request.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var {Ci, Cr} = require("chrome");
4 | var utils = require('./utils');
5 | var {EventTarget} = require('./event-target');
6 | var {BinaryUtils} = require('./binary-utils');
7 |
8 | var HTTPServer;
9 |
10 | var CRLF = '\r\n';
11 | var CRLFx2 = CRLF + CRLF;
12 |
13 | function HTTPRequest(transport, options) {
14 | options = options || {};
15 | if (!HTTPServer)
16 | HTTPServer = require('./http-server').HTTPServer;
17 |
18 | var inputStream = transport.openInputStream(
19 | Ci.nsITransport.OPEN_UNBUFFERED, 0, 0);
20 | var asyncInputStream = inputStream.QueryInterface(Ci.nsIAsyncInputStream);
21 | var binaryInputStream = utils.newBinaryInputStream(inputStream);
22 |
23 | this.transport = transport;
24 | this.inputStream = inputStream;
25 | this.asyncInputStream = asyncInputStream;
26 | this.binaryInputStream = binaryInputStream;
27 | }
28 | HTTPRequest.prototype = new EventTarget();
29 | HTTPRequest.prototype.constructor = HTTPRequest;
30 |
31 | HTTPRequest.prototype.waitForData = function (handler) {
32 | this.asyncInputStream.asyncWait({
33 | onInputStreamReady: utils.tryWrapF(handler)
34 | }, 0, 0, utils.currentThread());
35 | };
36 |
37 | HTTPRequest.prototype.receiveRaw = function () {
38 | var buffer = [];
39 |
40 | var handler = (stream) => {
41 | let avail;
42 | try { avail = this.asyncInputStream.available(); }
43 | catch (err) { /* closed stream. */ return; }
44 |
45 | // Check if stream is finished.
46 | if (avail == 0) {
47 | this.dispatchEvent('complete');
48 | return;
49 | }
50 |
51 | let data = this.binaryInputStream.readByteArray(avail);
52 | let array = [];
53 | for (let byte of data)
54 | array.push(byte);
55 | this.dispatchEvent('data', array);
56 | this.waitForData(handler);
57 | };
58 |
59 | this.waitForData(handler);
60 | };
61 |
62 | HTTPRequest.prototype.receiveParsed = function () {
63 | var parts = [];
64 | var readingHeader = true;
65 | var readingBody = false;
66 |
67 | var handler = (stream) => {
68 | let avail;
69 | try { avail = this.asyncInputStream.available(); }
70 | catch (err) {
71 | // Closed stream.
72 | return;
73 | }
74 |
75 | if (avail == 0) {
76 | // Stream is finished.
77 | return;
78 | }
79 |
80 | // If we're neither reading the header nor the body,
81 | // not sure what's going on.
82 | if (!readingHeader && !readingBody) {
83 | dump("[FlyWeb-HTTPRequest] Incoming when reading " +
84 | "neither header nor body.");
85 | return;
86 | }
87 |
88 | let data = this.binaryInputStream.readByteArray(avail);
89 | if (readingHeader) {
90 | if (data) {
91 | for (let byte of data)
92 | parts.push(byte);
93 | }
94 |
95 | if (tryParseHeader()) {
96 | if (readingBody)
97 | this.waitForData(handler);
98 | return;
99 | }
100 | this.waitForData(handler);
101 | return;
102 | }
103 |
104 | if (readingBody) {
105 | if (data) {
106 | let dataArray = [];
107 | for (let byte of data) {
108 | dataArray.push(byte);
109 | this.content.push(byte);
110 | }
111 | this.dispatchEvent('requestData', dataArray);
112 | }
113 |
114 | if (this.content.length >= this.contentLength) {
115 | // Trim content down to specified length (if necessary)
116 | while (this.content.length > this.contentLength)
117 | this.content.pop();
118 |
119 | // emit complete event.
120 | this.complete = true;
121 | this.dispatchEvent('complete', this);
122 | return;
123 | }
124 | }
125 | };
126 |
127 | this.waitForData(handler);
128 |
129 | var tryParseHeader = () => {
130 | let arr = new Uint8Array(parts);
131 | let resp = this.parseHeader(arr.buffer);
132 | if (this.invalid) {
133 | transport.close(Cr.NS_OK);
134 | this.dispatchEvent('error', this);
135 | readingHeader = false;
136 | return true;
137 | }
138 |
139 | if (!resp)
140 | return false;
141 |
142 | // Check content-length for body.
143 | var contentLength = parseInt(this.headers['Content-Length'], 10);
144 | if (isNaN(contentLength)) {
145 | this.complete = true;
146 | readingHeader = false;
147 | this.dispatchEvent('headerComplete', this);
148 | this.dispatchEvent('complete', this);
149 | return true;
150 | }
151 |
152 | this.contentLength = contentLength;
153 | readingHeader = false;
154 | readingBody = true;
155 | this.dispatchEvent('headerComplete', this);
156 | return true;
157 | };
158 | };
159 |
160 | HTTPRequest.prototype.parseHeader = function (data) {
161 | if (!data) {
162 | this.invalid = true;
163 | return false;
164 | }
165 |
166 | data = BinaryUtils.arrayBufferToString(data);
167 | // Check for presence of CRLF-CRLF
168 | var index = data.indexOf(CRLFx2);
169 | if (index == -1) {
170 | return false;
171 | }
172 |
173 | var header = data.substr(0, index);
174 | var body = data.substr(index + CRLFx2.length)
175 |
176 | var headerLines = header.split(CRLF);
177 | var requestLine = headerLines.shift().split(' ');
178 |
179 | var method = requestLine[0];
180 | var uri = requestLine[1];
181 | var version = requestLine[2];
182 |
183 | if (version !== HTTPServer.HTTP_VERSION) {
184 | this.invalid = true;
185 | return false;
186 | }
187 |
188 | var uriParts = uri.split('?');
189 |
190 | var path = uriParts.shift();
191 | var params = parseURLEncodedString(uriParts.join('?'));
192 |
193 | var headers = {};
194 | headerLines.forEach((headerLine) => {
195 | var parts = headerLine.split(': ');
196 | if (parts.length !== 2) {
197 | return;
198 | }
199 |
200 | var name = parts[0];
201 | var value = parts[1];
202 |
203 | headers[name] = value;
204 | });
205 |
206 | this.method = method;
207 | this.path = path;
208 | this.params = params;
209 | this.headers = headers;
210 |
211 | if (headers['Content-Length']) {
212 | this.content = [];
213 | for (var i = 0; i < body.length; i++)
214 | this.content.push(body[i]);
215 | return true;
216 | }
217 |
218 | return true;
219 | }
220 |
221 | function setOrAppendValue(object, name, value) {
222 | var existingValue = object[name];
223 | if (existingValue === undefined) {
224 | object[name] = value;
225 | } else {
226 | if (Array.isArray(existingValue)) {
227 | existingValue.push(value);
228 | } else {
229 | object[name] = [existingValue, value];
230 | }
231 | }
232 | }
233 |
234 | function parseURLEncodedString(string) {
235 | var values = {};
236 |
237 | string.split('&').forEach((pair) => {
238 | if (!pair) {
239 | return;
240 | }
241 |
242 | var parts = decodeURIComponent(pair).split('=');
243 |
244 | var name = parts.shift();
245 | var value = parts.join('=');
246 |
247 | setOrAppendValue(values, name, value);
248 | });
249 |
250 | return values;
251 | }
252 |
253 | function parseMultipartFormDataString(string, boundary) {
254 | var values = {};
255 |
256 | string.split('--' + boundary).forEach((data) => {
257 | data = data.replace(/^\r\n/, '').replace(/\r\n$/, '');
258 |
259 | if (!data || data === '--') {
260 | return;
261 | }
262 |
263 | var parts = data.split(CRLF + CRLF);
264 |
265 | var header = parts.shift();
266 | var value = {
267 | headers: {},
268 | metadata: {},
269 | value: parts.join(CRLF + CRLF)
270 | };
271 |
272 | var name;
273 |
274 | var headers = header.split(CRLF);
275 | headers.forEach((header) => {
276 | var headerParams = header.split(';');
277 | var headerParts = headerParams.shift().split(': ');
278 |
279 | var headerName = headerParts[0];
280 | var headerValue = headerParts[1];
281 |
282 | if (headerName !== 'Content-Disposition' ||
283 | headerValue !== 'form-data') {
284 | value.headers[headerName] = headerValue;
285 | return;
286 | }
287 |
288 | headerParams.forEach((param) => {
289 | var paramParts = param.trim().split('=');
290 |
291 | var paramName = paramParts[0];
292 | var paramValue = paramParts[1];
293 |
294 | paramValue = paramValue.replace(/\"(.*?)\"/, '$1') || paramValue;
295 |
296 | if (paramName === 'name') {
297 | name = paramValue;
298 | }
299 |
300 | else {
301 | value.metadata[paramName] = paramValue;
302 | }
303 | });
304 | });
305 |
306 | if (name) {
307 | setOrAppendValue(values, name, value);
308 | }
309 | });
310 |
311 | return values;
312 | }
313 |
314 | function parseBody(contentType, data) {
315 | contentType = contentType || 'text/plain';
316 |
317 | var contentTypeParams = contentType.replace(/\s/g, '').split(';');
318 | var mimeType = contentTypeParams.shift();
319 |
320 | var body = BinaryUtils.arrayBufferToString(data);
321 |
322 | var result;
323 |
324 | try {
325 | switch (mimeType) {
326 | case 'application/x-www-form-urlencoded':
327 | result = parseURLEncodedString(body);
328 | break;
329 | case 'multipart/form-data':
330 | contentTypeParams.forEach((contentTypeParam) => {
331 | var parts = contentTypeParam.split('=');
332 |
333 | var name = parts[0];
334 | var value = parts[1];
335 |
336 | if (name === 'boundary') {
337 | result = parseMultipartFormDataString(body, value);
338 | }
339 | });
340 | break;
341 | case 'application/json':
342 | result = JSON.parse(body);
343 | break;
344 | case 'application/xml':
345 | result = new DOMParser().parseFromString(body, 'text/xml');
346 | break;
347 | default:
348 | break;
349 | }
350 | } catch (exception) {
351 | console.log('Unable to parse HTTP request body with Content-Type: ' + contentType);
352 | }
353 |
354 | return result || body;
355 | }
356 |
357 | exports.HTTPRequest = HTTPRequest;
358 |
--------------------------------------------------------------------------------
/flyweb/lib/api.js:
--------------------------------------------------------------------------------
1 |
2 | var DNSSD = require('./dns-sd');
3 | var {HTTPServer} = require('./http-server');
4 | var {HTTPStatus} = require('./http-status');
5 | var utils = require('./utils');
6 | var uuid = require('sdk/util/uuid');
7 |
8 | function dispatchRequest(worker, name, obj, responseCallback) {
9 | let func;
10 | if (name == "discoverNearbyServices") {
11 | func = discoverNearbyServices;
12 | } else if (name == "stopDiscovery") {
13 | func = stopDiscovery;
14 | } else if (name == "publishServer") {
15 | func = publishServer;
16 | } else if (name == "stopServer") {
17 | func = stopServer;
18 | } else if (name == "httpRequestRaw") {
19 | func = httpRequestRaw;
20 | } else if (name == "httpRequestParsed") {
21 | func = httpRequestParsed;
22 | } else if (name == "httpResponse") {
23 | func = httpResponse;
24 | } else if (name == "httpResponseStream") {
25 | func = httpResponseStream;
26 | } else if (name == "httpResponseStreamData") {
27 | func = httpResponseStreamData;
28 | } else if (name == "httpResponseStreamEnd") {
29 | func = httpResponseStreamEnd;
30 | } else {
31 | dump("[FlyWeb-Addon] dispatchMessage: unhandled name " + name +
32 | " (" + JSON.stringify(obj) + ")\n");
33 | return;
34 | }
35 |
36 | try {
37 | func(worker, obj, responseCallback);
38 | } catch(err) {
39 | dump("[FlyWeb-Addon] dispatchRequest '" + name + "' failed: " +
40 | err.message + "\n" + err.stack + "\n");
41 | responseCallback({error:err.stack});
42 | }
43 | }
44 |
45 | var nextServiceListId = 0;
46 | function newServiceListId() {
47 | return 'flyweb_service_list_' + (++nextServiceListId);
48 | }
49 |
50 | var nextServiceId = 0;
51 | function newServiceId() {
52 | return 'flyweb_service_' + (++nextServiceId);
53 | }
54 |
55 | var nextSessionId = 0;
56 | function newSessionId() {
57 | return 'flyweb_session_' + (++nextSessionId);
58 | }
59 |
60 | var ServiceLists = {};
61 |
62 | function discoverNearbyServices(worker, obj, responseCallback) {
63 | let spec = obj.spec;
64 | let serviceListId = newServiceListId();
65 | let debugPrefix = "[FlyWeb-Addon] discoverNearbyServices" +
66 | "[" + serviceListId + "]";
67 | dump(debugPrefix + " Create\n")
68 | let listenerId = DNSSD.discoverListeners.addListener(spec,
69 | (service, found) => {
70 | if (found) {
71 | dump(debugPrefix + " Found service: " +
72 | JSON.stringify(service) + "\n")
73 | try {
74 | worker.port.emit('message', JSON.stringify({
75 | messageName: 'serviceFound',
76 | messageId: serviceListId,
77 | service: service
78 | }));
79 | } catch(err) {
80 | dump(debugPrefix + " Error emitting serviceFounc message: "
81 | + err.message + "\n" + err.stack + "\n");
82 | }
83 | } else {
84 | worker.port.emit('message', JSON.stringify({
85 | messageName: 'serviceLost',
86 | messageId: serviceListId,
87 | service: service
88 | }));
89 | }
90 | }
91 | );
92 | DNSSD.startDiscovery('_flyweb._tcp.local');
93 | ServiceLists[serviceListId] = {serviceListId, listenerId};
94 | responseCallback({serviceListId});
95 | }
96 |
97 | function stopDiscovery(worker, obj, responseCallback) {
98 | let {serviceListId} = obj;
99 | let debugPrefix = "[FlyWeb-Addon] stopDiscovery[" + serviceListId + "]";
100 | dump(debugPrefix + " Checking\n");
101 | if (ServiceLists[serviceListId]) {
102 | let svcList = ServiceLists[serviceListId];
103 | dump(debugPrefix + " Found " + JSON.stringify(svcList) + "\n");
104 | let {listenerId} = svcList;
105 | DNSSD.discoverListeners.removeListener(listenerId);
106 | delete ServiceLists[serviceListId];
107 | }
108 | responseCallback({});
109 | }
110 |
111 |
112 | var nextHTTPServerId = 0;
113 | function newHTTPServerId() {
114 | return 'flyweb_httpserver_' + (++nextHTTPServerId);
115 | }
116 | var nextHTTPRequestId = 0;
117 | function newHTTPRequestId() {
118 | return 'flyweb_httprequest_' + (++nextHTTPRequestId);
119 | }
120 | var HTTPServers = {};
121 | var HTTPRequests = {};
122 | function publishServer(worker, obj, responseCallback) {
123 | let httpServerId = newHTTPServerId();
124 | let {name,options} = obj;
125 | let {rawRequest} = options;
126 | let serverOpts = {rawRequest};
127 |
128 | // Create and start a new HTTP server, get port.
129 | let httpServer = new HTTPServer(undefined, serverOpts);
130 | httpServer.start();
131 | let {port} = httpServer;
132 |
133 | // Request handler - register request and response,
134 | // send message to content script about request.
135 | httpServer.onrequest = (request, response) => {
136 | let httpRequestId = newHTTPRequestId();
137 | HTTPRequests[httpRequestId] = {httpRequestId, request, response};
138 | worker.port.emit("message", JSON.stringify({
139 | messageName: "httpRequest",
140 | messageId: httpServerId,
141 | httpRequestId
142 | }));
143 | };
144 |
145 | let service = DNSSD.registerService("_flyweb._tcp.local", name, port,
146 | options);
147 | HTTPServers[httpServerId] = {httpServerId, httpServer, service};
148 | getWorkerInfo(worker).servers.push({httpServerId});
149 | responseCallback({httpServerId});
150 | }
151 |
152 | function httpRequestRaw(worker, obj, responseCallback) {
153 | let httpRequestId = obj.httpRequestId;
154 | if (!HTTPRequests[httpRequestId]) {
155 | responseCallback({error: "Invalid http request id: " + httpRequestId});
156 | return;
157 | }
158 | let {request} = HTTPRequests[httpRequestId];
159 | request.addEventListener('data', (data) => {
160 | worker.port.emit("message", JSON.stringify({
161 | messageName: "httpRequestData",
162 | messageId: httpRequestId,
163 | data: data
164 | }));
165 | });
166 | request.addEventListener('complete', () => {
167 | worker.port.emit("message", JSON.stringify({
168 | messageName: "httpRequestComplete",
169 | messageId: httpRequestId
170 | }));
171 | });
172 | request.receiveRaw();
173 | }
174 |
175 | function httpRequestParsed(worker, obj, responseCallback) {
176 | let httpRequestId = obj.httpRequestId;
177 | if (!HTTPRequests[httpRequestId]) {
178 | responseCallback({error: "Invalid http request id: " + httpRequestId});
179 | return;
180 | }
181 | let {request} = HTTPRequests[httpRequestId];
182 | request.addEventListener('complete', () => {
183 | let {method, path, params, headers, content} = request;
184 | worker.port.emit("message", JSON.stringify({
185 | messageName: "httpRequestComplete",
186 | messageId: httpRequestId,
187 | method, path, params, headers, content
188 | }));
189 | });
190 | request.receiveParsed();
191 | responseCallback({});
192 | }
193 |
194 | function stopServerImpl(httpServerId) {
195 | dump("Stopping server with id " + httpServerId + "\n");
196 | if (!(httpServerId in HTTPServers))
197 | return;
198 | let {service, httpServer} = HTTPServers[httpServerId];
199 | DNSSD.unregisterService(service.fullname);
200 | httpServer.stop();
201 | delete HTTPServers[httpServerId];
202 | }
203 | function stopServer(worker, obj, responseCallback) {
204 | let {httpServerId} = obj;
205 | if (!(httpServerId in HTTPServers)) {
206 | responseCallback({error:"Invalid http server id: " + httpServerId});
207 | return;
208 | }
209 | stopServerImpl(httpServerId);
210 | responseCallback({});
211 | }
212 |
213 | function httpResponse(worker, obj, responseCallback) {
214 | let {httpRequestId, status, headers, body} = obj;
215 | if (!(httpRequestId in HTTPRequests)) {
216 | responseCallback({error:"Invalid http request id: " + httpRequestId});
217 | return;
218 | }
219 | let {response} = HTTPRequests[httpRequestId];
220 | response.send(status, headers, body).then(ok => {
221 | responseCallback({});
222 | }).catch(err => {
223 | responseCallback({error: err.message + "\n" + err.stack});
224 | });
225 | delete HTTPRequests[httpRequestId];
226 | }
227 |
228 | function httpResponseStream(worker, obj, responseCallback) {
229 | let {httpRequestId, status} = obj;
230 | if (!(httpRequestId in HTTPRequests)) {
231 | responseCallback({error:"Invalid http request id: " + httpRequestId});
232 | return;
233 | }
234 | let {response} = HTTPRequests[httpRequestId];
235 | let responseStream = response.stream();
236 | responseStream.addEventListener('complete', () => {
237 | worker.port.emit('message', JSON.stringify({
238 | messageName: 'httpResponseStreamComplete',
239 | messageId: httpRequestId
240 | }));
241 | });
242 | responseStream.addEventListener('error', () => {
243 | worker.port.emit('message', JSON.stringify({
244 | messageName: 'httpResponseStreamError',
245 | messageId: httpRequestId
246 | }));
247 | });
248 | HTTPRequests[httpRequestId].responseStream = responseStream;
249 | responseCallback({});
250 | }
251 |
252 | function httpResponseStreamData(worker, obj, responseCallback) {
253 | let {httpRequestId, data} = obj;
254 | if (!(httpRequestId in HTTPRequests)) {
255 | responseCallback({error:"Invalid http request id: " + httpRequestId});
256 | return;
257 | }
258 | let {responseStream} = HTTPRequests[httpRequestId];
259 | if (!responseStream) {
260 | responseCallback({error:"Http request id: " + httpRequestId + " has no stream"});
261 | return;
262 | }
263 | responseStream.addData(data);
264 | responseCallback({});
265 | }
266 | function httpResponseStreamEnd(worker, obj, responseCallback) {
267 | let {httpRequestId, data} = obj;
268 | if (!(httpRequestId in HTTPRequests)) {
269 | responseCallback({error:"Invalid http request id: " + httpRequestId});
270 | return;
271 | }
272 | let {responseStream} = HTTPRequests[httpRequestId];
273 | if (!responseStream) {
274 | responseCallback({error:"Http request id: " + httpRequestId + " has no stream"});
275 | return;
276 | }
277 | responseStream.endData();
278 | delete HTTPRequests[httpRequestId];
279 | responseCallback({});
280 | }
281 |
282 | var nextWorkerId = 0;
283 | function newWorkerId() {
284 | return 'worker_' + (++nextWorkerId);
285 | }
286 | var WORKERS = [];
287 | function registerWorker(worker) {
288 | var workerId = newWorkerId();
289 | dump("Registering worker: " + workerId + "\n");
290 | WORKERS.push({worker, workerId, servers: []});
291 | }
292 | function getWorkerIndex(worker) {
293 | for (var i = 0; i < WORKERS.length; i++) {
294 | if (WORKERS[i].worker == worker)
295 | return i;
296 | }
297 | }
298 | function getWorkerInfo(worker) {
299 | var index = getWorkerIndex(worker);
300 | if (index != -1)
301 | return WORKERS[index];
302 | }
303 | function delWorkerInfo(worker) {
304 | var index = getWorkerIndex(worker);
305 | if (index != -1)
306 | WORKERS.splice(index,1);
307 | }
308 | function unregisterWorker(worker) {
309 | var info = getWorkerInfo(worker);
310 | if (!info)
311 | return;
312 | dump("Unregistering worker: " + info.workerId + "\n");
313 |
314 | // Stop all servers.
315 | if (info.servers) {
316 | for (var server of info.servers) {
317 | stopServerImpl(server.httpServerId);
318 | }
319 | }
320 |
321 | delWorkerInfo(worker);
322 | }
323 |
324 | exports.dispatchRequest = dispatchRequest;
325 | exports.registerWorker = registerWorker;
326 | exports.unregisterWorker = unregisterWorker;
327 |
--------------------------------------------------------------------------------
/flyweb/lib/dns-sd.js:
--------------------------------------------------------------------------------
1 | var {Cc, Ci, Cu} = require("chrome");
2 |
3 | var utils = require("./utils");
4 | var {BinaryUtils} = require('./binary-utils');
5 | var {ByteArray} = require('./byte-array');
6 | var {DNSCodes} = require('./dns-codes');
7 | var {DNSUtils} = require('./dns-utils');
8 | var {DNSRecord,
9 | DNSQuestionRecord,
10 | DNSResourceRecord} = require('./dns-records');
11 | var {DNSPacket, PACKETS} = require('./dns-packet');
12 |
13 | var {EventTarget} = require('./event-target');
14 | var {DiscoverRegistry} = require('./discover-registry');
15 | var {AdvertisedService,
16 | AdvertiseRegistry} = require('./advertise-registry');
17 | var {DiscoverListenerList,
18 | DiscoverListener} = require('./discover-listener');
19 |
20 | /* The following was modified from https://github.com/justindarc/dns-sd.js */
21 |
22 | /**
23 | * DNSSD
24 | */
25 |
26 | const DNSSD_SERVICE_NAME = '_services._dns-sd._udp.local';
27 |
28 | // Actual mDNS port and group
29 | //const DNSSD_MULTICAST_GROUP = '224.0.0.251';
30 | //const DNSSD_PORT = 5353;
31 |
32 | // Fake prototype mDNS port and group
33 | const DNSSD_MULTICAST_GROUP = '224.0.1.253';
34 | const DNSSD_PORT = 6363;
35 |
36 | var DNSSD = new EventTarget();
37 |
38 | var discovering = false;
39 | var discoverRegistry = new DiscoverRegistry();
40 | var advertiseRegistry = new AdvertiseRegistry();
41 |
42 | var discoverListeners = new DiscoverListenerList();
43 |
44 | DNSSD.getAdvertisingSocket = function() {
45 | return new Promise((resolve) => {
46 | if (!this.advertisingSocket) {
47 | this.advertisingSocket = utils.newUDPSocket({localPort: DNSSD_PORT,
48 | loopback: false});
49 | this.advertisingSocket.asyncListen({
50 | onPacketReceived: function(aSocket, aMessage) {
51 | let packet = new DNSPacket(aMessage.rawData);
52 | switch (packet.flags.QR) {
53 | case DNSCodes.QUERY_RESPONSE_CODES.QUERY:
54 | handleQueryPacket.call(this, packet, aMessage);
55 | break;
56 | default:
57 | break;
58 | }
59 | },
60 |
61 | onStopListening: function(aSocket, aStatus) {
62 | },
63 | });
64 | this.advertisingSocket.joinMulticast(DNSSD_MULTICAST_GROUP);
65 | }
66 |
67 | resolve(this.advertisingSocket);
68 | });
69 | };
70 |
71 | DNSSD.getDiscoverySocket = function() {
72 | return new Promise((resolve) => {
73 | if (!this.discoverySocket) {
74 | this.discoverySocket = utils.newUDPSocket({localPort: 0,
75 | loopback: false});
76 | this.discoverySocket.asyncListen({
77 | onPacketReceived(aSocket, aMessage) {
78 | let packet = new DNSPacket(aMessage.rawData);
79 | switch (packet.flags.QR) {
80 | case DNSCodes.QUERY_RESPONSE_CODES.RESPONSE:
81 | handleResponsePacket(packet);
82 | break;
83 | default:
84 | break;
85 | }
86 | },
87 | onStopListening(aSocket, aStatus) {
88 | if (handler.onStopListening)
89 | handler.onStopListening(aStatus);
90 | },
91 | });
92 | }
93 | resolve(this.discoverySocket);
94 | });
95 | };
96 |
97 | DNSSD.startDiscovery = function(target) {
98 | discovering = true;
99 |
100 | // Broadcast query for advertised services.
101 | discover.call(this, target);
102 | };
103 |
104 | DNSSD.stopDiscovery = function() {
105 | discovering = false;
106 | };
107 |
108 | DNSSD.registerService = function(serviceName, name, port, options) {
109 | let svc = new AdvertisedService({
110 | serviceName: serviceName,
111 | name: name,
112 | port: port || 0,
113 | options: options || {}
114 | });
115 | advertiseRegistry.addService(svc);
116 |
117 | // Broadcast advertisement of registered services.
118 | advertise();
119 |
120 | return svc;
121 | };
122 |
123 | DNSSD.unregisterService = function(fullname) {
124 | advertiseRegistry.delService(fullname);
125 |
126 | // Broadcast advertisement of registered services.
127 | advertise();
128 | };
129 |
130 | function handleQueryPacket(packet, message) {
131 | packet.getRecords('QD').forEach((record) => {
132 | // Don't respond if the query's class code is not IN or ANY.
133 | if (record.classCode !== DNSCodes.CLASS_CODES.IN &&
134 | record.classCode !== DNSCodes.CLASS_CODES.ANY) {
135 | return;
136 | }
137 |
138 | if (record.recordType === DNSCodes.RECORD_TYPES.PTR) {
139 | respondToPtrQuery(record, message);
140 | return;
141 | }
142 |
143 | if (record.recordType === DNSCodes.RECORD_TYPES.SRV) {
144 | respondToSrvQuery(record, message);
145 | return;
146 | }
147 |
148 | if (record.recordType === DNSCodes.RECORD_TYPES.A) {
149 | respondToAddrQuery(record, message);
150 | return;
151 | }
152 |
153 | if (record.recordType === DNSCodes.RECORD_TYPES.TXT) {
154 | respondToTxtQuery(record, message);
155 | return;
156 | }
157 |
158 | if (record.recordType === DNSCodes.RECORD_TYPES.ANY) {
159 | respondToAnyQuery(record, message);
160 | return;
161 | }
162 | });
163 | }
164 |
165 | function respondToPtrQuery(query, message) {
166 | dump("KVKV: Respond to PTR Query: " + JSON.stringify(query) + "\n");
167 | advertiseRegistry.names().forEach(fullname => {
168 | let svc = advertiseRegistry.getService(fullname);
169 | if (svc.serviceName == query.name) {
170 | // Respond to query.
171 | utils.getIp().then((ip) => {
172 | DNSSD.getAdvertisingSocket().then((socket) => {
173 | let packet = new DNSPacket();
174 | packet.flags.QR = DNSCodes.QUERY_RESPONSE_CODES.RESPONSE;
175 | packet.flags.AA = DNSCodes.AUTHORITATIVE_ANSWER_CODES.YES;
176 | let target = message.fromAddr;
177 | dump("KVKV: Sending service: " + JSON.stringify(svc) + "\n");
178 | packet.addRecord('QD', query);
179 | addPtrRecord(svc, packet, 'AN');
180 | addSrvRecord(svc, packet, 'AR');
181 | addAddrRecord(svc, ip, packet, 'AR');
182 | addTxtRecord(svc, packet, 'AR');
183 | sendPacket(packet, socket, target.address, target.port);
184 | }).catch((err) => {
185 | dump("Caught error: " + err.toString() + "\n");
186 | dump(err.stack + "\n");
187 | });
188 | }).catch((err) => {
189 | dump("Caught error: " + err.toString() + "\n");
190 | dump(err.stack + "\n");
191 | });
192 | }
193 | });
194 | }
195 |
196 | function respondToSrvQuery(query, message) {
197 | dump("KVKV: Respond to SRV Query: " + JSON.stringify(query) + "\n");
198 | for (let fullname of advertiseRegistry.names()) {
199 | let svc = advertiseRegistry.getService(fullname);
200 | if (svc.location == query.name) {
201 | // Respond to query.
202 | var packet = new DNSPacket();
203 | packet.flags.QR = DNSCodes.QUERY_RESPONSE_CODES.RESPONSE;
204 | packet.flags.AA = DNSCodes.AUTHORITATIVE_ANSWER_CODES.YES;
205 | utils.getIp().then((ip) => {
206 | DNSSD.getAdvertisingSocket().then((socket) => {
207 | let target = message.fromAddr;
208 | packet.addRecord('QD', query);
209 | addSrvRecord(svc, packet, 'AN');
210 | addAddrRecord(svc, ip, packet, 'AR');
211 | addTxtRecord(svc, packet, 'AR');
212 | sendPacket(packet, socket, target.address, target.port);
213 | }).catch((err) => {
214 | dump("Caught error: " + err.toString() + "\n");
215 | dump(err.stack + "\n");
216 | });
217 | }).catch((err) => {
218 | dump("Caught error: " + err.toString() + "\n");
219 | dump(err.stack + "\n");
220 | });
221 | }
222 | }
223 | }
224 |
225 | function respondToAddrQuery(query, message) {
226 | dump("KVKV: TODO: Respond to A Query: " + JSON.stringify(query) + "\n");
227 | for (let fullname of advertiseRegistry.names()) {
228 | let svc = advertiseRegistry.getService(fullname);
229 | if (svc.target == query.name) {
230 | // Respond to query.
231 | var packet = new DNSPacket();
232 | packet.flags.QR = DNSCodes.QUERY_RESPONSE_CODES.RESPONSE;
233 | packet.flags.AA = DNSCodes.AUTHORITATIVE_ANSWER_CODES.YES;
234 | utils.getIp().then((ip) => {
235 | DNSSD.getAdvertisingSocket().then((socket) => {
236 | let target = message.fromAddr;
237 | packet.addRecord('QD', query);
238 | addAddrRecord(svc, ip, packet, 'AN');
239 | sendPacket(packet, socket, target.address, target.port);
240 | }).catch((err) => {
241 | dump("Caught error: " + err.toString() + "\n");
242 | dump(err.stack + "\n");
243 | });
244 | }).catch((err) => {
245 | dump("Caught error: " + err.toString() + "\n");
246 | dump(err.stack + "\n");
247 | });
248 | }
249 | }
250 | }
251 |
252 | function respondToTxtQuery(query, message) {
253 | dump("KVKV: TODO: Respond to TXT Query: " + JSON.stringify(query) + "\n");
254 | for (let fullname of advertiseRegistry.names()) {
255 | let svc = advertiseRegistry.getService(fullname);
256 | if (svc.location == query.name) {
257 | // Respond to query.
258 | var packet = new DNSPacket();
259 | packet.flags.QR = DNSCodes.QUERY_RESPONSE_CODES.RESPONSE;
260 | packet.flags.AA = DNSCodes.AUTHORITATIVE_ANSWER_CODES.YES;
261 | utils.getIp().then((ip) => {
262 | DNSSD.getAdvertisingSocket().then((socket) => {
263 | let target = message.fromAddr;
264 | packet.addRecord('QD', query);
265 | addTxtRecord(svc, packet, 'An');
266 | sendPacket(packet, socket, target.address, target.port);
267 | }).catch((err) => {
268 | dump("Caught error: " + err.toString() + "\n");
269 | dump(err.stack + "\n");
270 | });
271 | }).catch((err) => {
272 | dump("Caught error: " + err.toString() + "\n");
273 | dump(err.stack + "\n");
274 | });
275 | }
276 | }
277 | }
278 |
279 | function respondToAnyQuery(query, message) {
280 | dump("KVKV: Respond to ANY Query: " + JSON.stringify(query) + "\n");
281 | }
282 |
283 | function handleResponsePacket(packet) {
284 | if (!discovering) {
285 | return;
286 | }
287 |
288 | let seenServices = new Set();
289 | packet.getRecords('AN').forEach((record) => {
290 | handleResponseRecord(record, seenServices);
291 | });
292 | packet.getRecords('AR').forEach((record) => {
293 | handleResponseRecord(record, seenServices);
294 | });
295 |
296 | for (let svc of seenServices) {
297 | let svcInfo = discoverRegistry.serviceInfo(svc);
298 | dump("KVKV: Seen service " + svc + ": " + JSON.stringify(svcInfo) + "\n");
299 | discoverListeners.found(svcInfo);
300 | }
301 | }
302 |
303 | function handleResponseRecord(record, seenServices) {
304 | discoverRegistry.addRecord(record, seenServices);
305 | }
306 |
307 | function discover(target) {
308 | var packet = new DNSPacket();
309 | packet.flags.QR = DNSCodes.QUERY_RESPONSE_CODES.QUERY;
310 | var question = new DNSQuestionRecord(target ? target : DNSSD_SERVICE_NAME,
311 | DNSCodes.RECORD_TYPES.PTR);
312 | packet.addRecord('QD', question);
313 |
314 | DNSSD.getDiscoverySocket().then((socket) => {
315 | var data = packet.serialize();
316 | // socket.send(data, DNSSD_MULTICAST_GROUP, DNSSD_PORT);
317 |
318 | let raw = new DataView(data);
319 | let length = raw.byteLength;
320 | let buf = [];
321 | for (let x = 0; x < length; x++) {
322 | let charcode = raw.getUint8(x);
323 | buf[x] = charcode;
324 | }
325 | socket.send( DNSSD_MULTICAST_GROUP, DNSSD_PORT, buf, buf.length);
326 | }).catch((err) => {
327 | dump("Caught error: " + err.toString() + "\n");
328 | dump(err.stack + "\n");
329 | });
330 | }
331 |
332 | function advertise() {
333 | if (advertiseRegistry.hasServices()) {
334 | return;
335 | }
336 |
337 | utils.getIp().then((ip) => {
338 | DNSSD.getAdvertisingSocket().then((socket) => {
339 | for (var fullname of advertiseRegistry.names()) {
340 | advertiseService(fullname, socket, ip);
341 | }
342 | }).catch((err) => {
343 | dump("Caught error: " + err.toString() + "\n");
344 | dump(err.stack + "\n");
345 | });
346 | }).catch((err) => {
347 | dump("Caught error: " + err.toString() + "\n");
348 | dump(err.stack + "\n");
349 | });
350 | }
351 |
352 | function advertiseService(fullname, socket, ip) {
353 | let svc = advertiseRegistry.getService(fullname);
354 | if (!svc)
355 | return;
356 | dump("KVKV: advertiseService " + JSON.stringify(svc) + "\n");
357 |
358 | var packet = new DNSPacket();
359 |
360 | packet.flags.QR = DNSCodes.QUERY_RESPONSE_CODES.RESPONSE;
361 | packet.flags.AA = DNSCodes.AUTHORITATIVE_ANSWER_CODES.YES;
362 |
363 | addPtrRecord(svc, packet, 'AN');
364 | addSrvRecord(svc, packet, 'AR');
365 | addAddrRecord(svc, ip, packet, 'AR');
366 | addTxtRecord(svc, packet, 'AR');
367 | sendPacket(packet, socket, DNSSD_MULTICAST_GROUP, DNSSD_PORT);
368 | }
369 |
370 | function sendPacket(packet, socket, targetip, port) {
371 | var data = packet.serialize();
372 | // socket.send(data, DNSSD_MULTICAST_GROUP, DNSSD_PORT);
373 |
374 | let raw = new DataView(data);
375 | let length = raw.byteLength;
376 | let buf = [];
377 | for (let x = 0; x < length; x++) {
378 | let charcode = raw.getUint8(x);
379 | buf[x] = charcode;
380 | }
381 | socket.send(targetip, port, buf, buf.length);
382 |
383 | // Parse raw data from packet.
384 | let parsed_packet = new DNSPacket(data);
385 | dump("KVKV: Sent packet: " + JSON.stringify(parsed_packet) + "\n");
386 | }
387 |
388 | function addServiceToPacket(svc, packet, ip) {
389 | addPtrRecord(svc, packet, 'AN');
390 | addSrvRecord(svc, packet, 'AR');
391 | addAddrRecord(svc, ip, packet, 'AR');
392 | addTxtRecord(svc, packet, 'AR');
393 | }
394 |
395 | function addPtrRecord(svc, packet, section) {
396 | let location = svc.location;
397 | let rec = new DNSResourceRecord(svc.serviceName, DNSCodes.RECORD_TYPES.PTR);
398 | rec.setParsedData({location});
399 | packet.addRecord(section, rec);
400 | }
401 |
402 | function addSrvRecord(svc, packet, section) {
403 | let priority = 0;
404 | let weight = 0;
405 | let port = svc.port;
406 | let target = svc.target;
407 | let rec = new DNSResourceRecord(svc.location, DNSCodes.RECORD_TYPES.SRV);
408 | rec.setParsedData({priority,weight,port,target});
409 | packet.addRecord(section, rec);
410 | }
411 |
412 | function addAddrRecord(svc, ip, packet, section) {
413 | let rec = new DNSResourceRecord(svc.target, DNSCodes.RECORD_TYPES.A);
414 | rec.setParsedData({ip});
415 | packet.addRecord(section, rec);
416 | }
417 |
418 | function addTxtRecord(svc, packet, section) {
419 | let parts = [];
420 | for (let name in svc.options) {
421 | parts.push(name + "=" + svc.options[name]);
422 | }
423 | var rec = new DNSResourceRecord(svc.location, DNSCodes.RECORD_TYPES.TXT);
424 | rec.setParsedData({parts});
425 | packet.addRecord(section, rec);
426 | }
427 |
428 | /**
429 | * exports
430 | */
431 |
432 | exports.startDiscovery = DNSSD.startDiscovery;
433 | exports.stopDiscovery = DNSSD.stopDiscovery;
434 | exports.registerService = DNSSD.registerService;
435 | exports.unregisterService = DNSSD.unregisterService;
436 | exports.discoverRegistry = discoverRegistry;
437 | exports.discoverListeners = discoverListeners;
438 | exports.getIp = utils.getIp;
439 | exports.utils = utils;
440 |
441 | exports.PACKETS = PACKETS;
442 |
--------------------------------------------------------------------------------
/flyweb/data/page-script.js:
--------------------------------------------------------------------------------
1 | //var { Cc, Ci, Cu, Cm } = require('chrome');
2 |
3 | delete window.Promise;
4 |
5 | var MESSAGE_ID = 0;
6 | function NextMessageId() {
7 | return ++MESSAGE_ID;
8 | }
9 |
10 | // Map of responses to watch for.
11 | var Handlers = {};
12 | function AddHandler(name, messageId, handler) {
13 | if (!(name in Handlers))
14 | Handlers[name] = {}
15 |
16 | let subHandlers = Handlers[name];
17 | if (messageId in subHandlers) {
18 | dump("[ContentScript] HANDLER CONFLICT FOR ID: " + messageId + "!\n");
19 | dump(new Error().stack + "\n");
20 | }
21 | let descriptor = {handler};
22 | subHandlers[messageId] = descriptor;
23 | }
24 | function SendRequest(name, obj, handler) {
25 | let messageId = NextMessageId();
26 | obj.messageId = messageId;
27 | obj.messageName = name;
28 | AddHandler(name, messageId, handler);
29 | self.port.emit("request", JSON.stringify(obj));
30 | }
31 | function HandleMessage(kind, message) {
32 | // dump("[ContentScript] Got " + kind + ": " + message + "\n");
33 | let obj = JSON.parse(message);
34 | let {messageName, messageId} = obj;
35 | if (!obj.messageName) {
36 | dump(" No name for " + kind + "!?\n");
37 | return;
38 | }
39 | if (!obj.messageId) {
40 | dump(" No id for " + kind + "!? (" + obj.messageName + ")\n");
41 | return;
42 | }
43 | delete obj.messageName;
44 | delete obj.messageId;
45 | if (!(messageName in Handlers)) {
46 | dump(" No handler for " + kind + "!? (" + messageName + ")\n");
47 | return;
48 | }
49 | let subHandlers = Handlers[messageName];
50 | if (!(messageId in subHandlers)) {
51 | dump(" No handler for " + kind + " id!? (" + messageName + "." +
52 | messageId + ")\n");
53 | return;
54 | }
55 | let {handler} = subHandlers[messageId];
56 | if (kind == "response")
57 | delete subHandlers[messageId];
58 | handler(obj);
59 | }
60 | self.port.on("response", message => { HandleMessage("response", message); });
61 | self.port.on("message", message => { HandleMessage("message", message); });
62 |
63 | var NEXT_SVC_ID = 0;
64 | function nextServiceId() {
65 | return "flyweb_service_" + (++NEXT_SVC_ID);
66 | }
67 | var NEXT_SESS_ID = 0;
68 | function nextSessionId() {
69 | return "flyweb_session_" + (++NEXT_SESS_ID);
70 | }
71 |
72 | var GLOBAL_SERVICES = [];
73 | var GLOBAL_CONNECTIONS = [];
74 | function addGlobalService(service) {
75 | let serviceId = nextServiceId();
76 | let {location, target, ip, port, txt} = service;
77 | let [name] = location.split('.');
78 | let publicService = CI({
79 | id: serviceId,
80 | name: name,
81 | http: true
82 | });
83 | let descriptor = {serviceId, service, publicService};
84 | GLOBAL_SERVICES.push(descriptor);
85 | return descriptor;
86 | }
87 | function addGlobalConnection(svc) {
88 | let {serviceId} = svc;
89 | let sessionId = nextSessionId();
90 | let url = "http://" + svc.service.ip + ":" + svc.service.port + "/";
91 | let config = CI(svc.service.txt);
92 | let publicConnection = CI({
93 | serviceId, sessionId, url,
94 | close: function () {
95 | for (let i = 0; i < GLOBAL_CONNECTIONS.length; i++) {
96 | let conn = GLOBAL_CONNECTIONS[i];
97 | if (conn.sessionId == sessionId) {
98 | GLOBAL_CONNECTIONS.splice(i, 1);
99 | return;
100 | }
101 | }
102 | }
103 | });
104 | let descriptor = {serviceId, sessionId, publicConnection};
105 | GLOBAL_CONNECTIONS.push(descriptor);
106 | return descriptor;
107 | }
108 |
109 | function discoverNearbyServices(spec) {
110 | return new window.Promise(XF((resolve, reject) => {
111 | try {
112 | SendRequest("discoverNearbyServices", {spec}, resp => {
113 | // Handle error.
114 | if (resp.error) {
115 | reject(resp.error);
116 | return;
117 | }
118 |
119 | let onservicefound = null;
120 | let onservicelost = null;
121 |
122 | // Handle response.
123 | let {serviceListId} = resp;
124 | let services = [];
125 | let result = CI({
126 | length: function () { return services.length; },
127 | get: function (id_or_idx) {
128 | if (typeof(id_or_idx) == 'number') {
129 | let svc = services[id_or_idx|0];
130 | if (!svc)
131 | return undefined;
132 | return svc.publicService;
133 | }
134 |
135 | for (let svc of services) {
136 | if (svc.serviceId == id_or_idx)
137 | return svc.publicService;
138 | }
139 | },
140 | stopDiscovery: function () {
141 | return new window.Promise(XF((resolve, reject) => {
142 | SendRequest("stopDiscovery", {serviceListId},
143 | resp => {
144 | if (resp.error)
145 | reject(resp.error);
146 | else
147 | resolve();
148 | }
149 | );
150 | }));
151 | },
152 | onservicefound: function (callback) {
153 | onservicefound = callback;
154 | },
155 | onservicelost: function (callback) {
156 | onservicelost = callback;
157 | },
158 | });
159 |
160 | AddHandler("serviceFound", serviceListId, message => {
161 | let {service} = message;
162 | let descriptor = addGlobalService(service);
163 | let {serviceId} = descriptor;
164 | services.push(descriptor);
165 | if (onservicefound) {
166 | try {
167 | onservicefound(CI({serviceId}));
168 | } catch(err) {
169 | dump("Error calling page onservicefound: "
170 | + err.message + "\n" + err.stack + "\n");
171 | }
172 | }
173 | });
174 | AddHandler("serviceLost", serviceListId, message => {
175 | let {service} = message;
176 | });
177 |
178 | try { resolve(result); }
179 | catch(err) { reject(err.message + "\n" + err.stack); }
180 | });
181 | } catch (err) {
182 | reject(err.message + "\n" + err.stack);
183 | }
184 | }));
185 | }
186 |
187 | function connectToService(spec) {
188 | return new window.Promise(XF((resolve, reject) => {
189 | try {
190 | if (typeof(spec) == 'string')
191 | spec = {id:spec};
192 |
193 | if (!spec.id) {
194 | reject("Only service id specs accepted for now.");
195 | return;
196 | }
197 |
198 | // Look up the service.
199 | let svc;
200 | for (let checkSvc of GLOBAL_SERVICES) {
201 | if (checkSvc.serviceId == spec.id) {
202 | svc = checkSvc;
203 | break;
204 | }
205 | }
206 | if (!svc) {
207 | reject("Invalid service id: " + spec.id);
208 | return;
209 | }
210 |
211 | // Return a connection object.
212 | let connection = addGlobalConnection(svc);
213 | let {serviceId, sessionId} = connection;
214 | resolve(connection.publicConnection);
215 | } catch (err) {
216 | reject(err.message + "\n" + err.stack);
217 | }
218 | }));
219 | }
220 |
221 | function publishServer(name, options) {
222 | return new window.Promise(XF((resolve, reject) => {
223 | try {
224 | if (typeof(name) != 'string') {
225 | reject("Server name must be a string.");
226 | return;
227 | }
228 | if (!options)
229 | options = {};
230 | if (typeof(options) != 'object') {
231 | reject("Server options must be an object.");
232 | return;
233 | }
234 |
235 | let rawRequest = false;
236 | if (options.rawRequest) {
237 | let {rawRequest} = options;
238 | delete options.rawRequest;
239 | }
240 |
241 | let localOptions = {};
242 | for (let opt in options) {
243 | localOptions[opt] = options[opt];
244 | }
245 | options = localOptions;
246 |
247 | SendRequest("publishServer", {name, options}, resp => {
248 | let {httpServerId} = resp;
249 |
250 | let onrequest = null;
251 |
252 | let result = CI({
253 | name, options,
254 | stop: function () {
255 | SendRequest("stopServer", {httpServerId}, resp => {});
256 | },
257 | onrequest: function (callback) {
258 | onrequest = callback;
259 | }
260 | });
261 |
262 | AddHandler("httpRequest", httpServerId, message => {
263 | let {httpRequestId} = message;
264 |
265 | function requestRaw() {
266 | var bufferedData = null;
267 | var ondata = null;
268 | function setondata(callback) {
269 | ondata = callback;
270 | if (bufferedData) {
271 | try { ondata(CI(bufferedData)); }
272 | finally { bufferedData = []; }
273 | }
274 | }
275 |
276 | var complete = false;
277 | var oncomplete = null;
278 | function setoncomplete(callback) {
279 | oncomplete = callback;
280 | if (complete) {
281 | oncomplete();
282 | }
283 | }
284 |
285 | AddHandler("httpRequestData", httpRequestId, message => {
286 | if (ondata)
287 | ondata(CI(message.data));
288 | else
289 | bufferedData = message.data;
290 | });
291 | AddHandler("httpRequestComplete", httpRequestId, message => {
292 | if (oncomplete)
293 | oncomplete();
294 | else
295 | complete = true;
296 | });
297 |
298 | SendRequest("httpRequestRaw", {httpRequestId}, resp => {});
299 |
300 | return CI({
301 | ondata: setondata,
302 | oncomplete: setoncomplete
303 | });
304 | }
305 |
306 | function requestParsed() {
307 | return new window.Promise(XF((resolve, reject) => {
308 | SendRequest("httpRequestParsed", {httpRequestId}, resp => {
309 | AddHandler("httpRequestComplete", httpRequestId, message => {
310 | let {method, path, params, headers, content} = message;
311 | resolve(CI({method, path, params, headers, content}));
312 | });
313 | });
314 | }));
315 | }
316 |
317 | function sendResponse(status, headers, body) {
318 | SendRequest("httpResponse", {httpRequestId, status,
319 | headers, body},
320 | resp => {}
321 | );
322 | }
323 | function sendResponseStream(status) {
324 | return new window.Promise(XF((resolve, reject) => {
325 | SendRequest("httpResponseStream",
326 | {httpRequestId, status},
327 | resp => {
328 | let error = null;
329 | let complete = false;
330 |
331 | let onerror = null;
332 | let oncomplete = null;
333 |
334 | let result = CI({
335 | send: function (data) {
336 | SendRequest("httpResponseStreamData",
337 | {httpRequestId, data}, resp => {});
338 | },
339 | end: function () {
340 | SendRequest("httpResponseStreamEnd",
341 | {httpRequestId}, resp => {});
342 | },
343 | onerror: function (callback) {
344 | onerror = callback;
345 | if (error)
346 | onerror();
347 | },
348 | oncomplete: function (callback) {
349 | oncomplete = callback;
350 | if (complete)
351 | oncomplete();
352 | }
353 | });
354 |
355 | AddHandler("httpResponseStreamError", httpRequestId, message => {
356 | if (onerror)
357 | onerror();
358 | else
359 | error = true;
360 | });
361 | AddHandler("httpResponseStreamComplete", httpRequestId, message => {
362 | if (oncomplete)
363 | oncomplete();
364 | else
365 | complete = true;
366 | });
367 |
368 | resolve(result);
369 | }
370 | );
371 | }));
372 | }
373 |
374 | if (onrequest) {
375 | try {
376 | onrequest(CI({
377 | requestRaw, requestParsed,
378 | sendResponse, stream: sendResponseStream,
379 | }));
380 | } catch(err) {
381 | dump("Error calling page onrequest: " + err.message + "\n"
382 | + err.stack + "\n");
383 | }
384 | } else {
385 | sendResponse(404, {"Content-type":"text/plain"},
386 | "Request handler not installed");
387 | }
388 | });
389 |
390 | resolve(result);
391 | });
392 | } catch (err) {
393 | reject(err.message + "\n" + err.stack);
394 | }
395 | }));
396 | }
397 |
398 | exportFunction(discoverNearbyServices, window.navigator, {
399 | defineAs: 'discoverNearbyServices'
400 | });
401 | exportFunction(connectToService, window.navigator, {
402 | defineAs: 'connectToService'
403 | });
404 | exportFunction(publishServer, window.navigator, {
405 | defineAs: 'publishServer'
406 | });
407 |
408 | function XF(fn, obj, defName) {
409 | if (!obj)
410 | obj = window.navigator;
411 | if (defName)
412 | return exportFunction(fn, obj, {defineAs:defName});
413 | return exportFunction(fn, obj);
414 | }
415 | function CI(obj) {
416 | return cloneInto(obj, window.navigator, {cloneFunctions:true});
417 | }
418 |
419 | function DEF_EVENT_PROP(obj, name, get, set) {
420 | window.Object.defineProperty(obj, name, CI({get, set}));
421 | }
422 |
--------------------------------------------------------------------------------