├── 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 | * || 61 | * || 62 | * || 63 | * || 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 | * || 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 | * || 97 | * |<======= TYPE ========>|<======= CLASS =======>| 98 | * |<==================== TTL ====================>| 99 | * |<====== DATALEN ======>|| 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 | --------------------------------------------------------------------------------