Initial
This commit is contained in:
6
resources/app/node_modules/nat-upnp/lib/nat-upnp.js
generated
vendored
Normal file
6
resources/app/node_modules/nat-upnp/lib/nat-upnp.js
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
var nat = exports;
|
||||
|
||||
nat.utils = require('./nat-upnp/utils');
|
||||
nat.ssdp = require('./nat-upnp/ssdp');
|
||||
nat.device = require('./nat-upnp/device');
|
||||
nat.createClient = require('./nat-upnp/client').create;
|
||||
202
resources/app/node_modules/nat-upnp/lib/nat-upnp/client.js
generated
vendored
Normal file
202
resources/app/node_modules/nat-upnp/lib/nat-upnp/client.js
generated
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
var nat = require('../nat-upnp');
|
||||
var async = require('async');
|
||||
|
||||
var client = exports;
|
||||
|
||||
function Client() {
|
||||
this.ssdp = nat.ssdp.create();
|
||||
this.timeout = 1800;
|
||||
}
|
||||
|
||||
client.create = function create() {
|
||||
return new Client();
|
||||
};
|
||||
|
||||
function normalizeOptions(options) {
|
||||
function toObject(addr) {
|
||||
if (typeof addr === 'number') return { port: addr };
|
||||
if (typeof addr === 'string' && !isNaN(addr)) return { port: Number(addr) };
|
||||
if (typeof addr === 'object') return addr;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
return {
|
||||
remote: toObject(options.public),
|
||||
internal: toObject(options.private)
|
||||
};
|
||||
}
|
||||
|
||||
Client.prototype.portMapping = function portMapping(options, callback) {
|
||||
if (!callback) callback = function() {};
|
||||
|
||||
this.findGateway(function(err, gateway, address) {
|
||||
if (err) return callback(err);
|
||||
|
||||
var ports = normalizeOptions(options);
|
||||
var ttl = 60 * 30;
|
||||
if (typeof options.ttl === 'number') { ttl = options.ttl; }
|
||||
if (typeof options.ttl === 'string' && !isNaN(options.ttl)) { ttl = Number(options.ttl); }
|
||||
|
||||
gateway.run('AddPortMapping', [
|
||||
[ 'NewRemoteHost', ports.remote.host ],
|
||||
[ 'NewExternalPort', ports.remote.port ],
|
||||
[ 'NewProtocol', options.protocol ?
|
||||
options.protocol.toUpperCase() : 'TCP' ],
|
||||
[ 'NewInternalPort', ports.internal.port ],
|
||||
[ 'NewInternalClient', ports.internal.host || address ],
|
||||
[ 'NewEnabled', 1 ],
|
||||
[ 'NewPortMappingDescription', options.description || 'node:nat:upnp' ],
|
||||
[ 'NewLeaseDuration', ttl ]
|
||||
], callback);
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.portUnmapping = function portMapping(options, callback) {
|
||||
if (!callback) callback = function() {};
|
||||
|
||||
this.findGateway(function(err, gateway/*, address*/) {
|
||||
if (err) return callback(err);
|
||||
|
||||
var ports = normalizeOptions(options);
|
||||
|
||||
gateway.run('DeletePortMapping', [
|
||||
[ 'NewRemoteHost', ports.remote.host ],
|
||||
[ 'NewExternalPort', ports.remote.port ],
|
||||
[ 'NewProtocol', options.protocol ?
|
||||
options.protocol.toUpperCase() : 'TCP' ]
|
||||
], callback);
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.getMappings = function getMappings(options, callback) {
|
||||
if (typeof options === 'function') {
|
||||
callback = options;
|
||||
options = null;
|
||||
}
|
||||
|
||||
if (!options) options = {};
|
||||
|
||||
this.findGateway(function(err, gateway, address) {
|
||||
if (err) return callback(err);
|
||||
var i = 0;
|
||||
var end = false;
|
||||
var results = [];
|
||||
|
||||
async.whilst(function() {
|
||||
return !end;
|
||||
}, function(callback) {
|
||||
gateway.run('GetGenericPortMappingEntry', [
|
||||
[ 'NewPortMappingIndex', i++ ]
|
||||
], function(err, data) {
|
||||
if (err) {
|
||||
// If we got an error on index 0, ignore it in case this router starts indicies on 1
|
||||
if (i !== 1) {
|
||||
end = true;
|
||||
}
|
||||
return callback(null);
|
||||
}
|
||||
|
||||
var key;
|
||||
Object.keys(data).some(function(k) {
|
||||
if (!/:GetGenericPortMappingEntryResponse/.test(k)) return false;
|
||||
|
||||
key = k;
|
||||
return true;
|
||||
});
|
||||
data = data[key];
|
||||
|
||||
var result = {
|
||||
public: {
|
||||
host: typeof data.NewRemoteHost === 'string' &&
|
||||
data.NewRemoteHost || '',
|
||||
port: parseInt(data.NewExternalPort, 10)
|
||||
},
|
||||
private: {
|
||||
host: data.NewInternalClient,
|
||||
port: parseInt(data.NewInternalPort, 10)
|
||||
},
|
||||
protocol: data.NewProtocol.toLowerCase(),
|
||||
enabled: data.NewEnabled === '1',
|
||||
description: data.NewPortMappingDescription,
|
||||
ttl: parseInt(data.NewLeaseDuration, 10)
|
||||
};
|
||||
result.local = result.private.host === address;
|
||||
|
||||
results.push(result);
|
||||
|
||||
callback(null);
|
||||
});
|
||||
}, function(err) {
|
||||
if (err) return callback(err);
|
||||
|
||||
if (options.local) {
|
||||
results = results.filter(function(item) {
|
||||
return item.local;
|
||||
});
|
||||
}
|
||||
|
||||
if (options.description) {
|
||||
results = results.filter(function(item) {
|
||||
if (typeof item.description !== 'string')
|
||||
return;
|
||||
|
||||
if (options.description instanceof RegExp) {
|
||||
return item.description.match(options.description) !== null;
|
||||
} else {
|
||||
return item.description.indexOf(options.description) !== -1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
callback(null, results);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.externalIp = function externalIp(callback) {
|
||||
this.findGateway(function(err, gateway/*, address*/) {
|
||||
if (err) return callback(err);
|
||||
gateway.run('GetExternalIPAddress', [], function(err, data) {
|
||||
if (err) return callback(err);
|
||||
var key;
|
||||
|
||||
Object.keys(data).some(function(k) {
|
||||
if (!/:GetExternalIPAddressResponse$/.test(k)) return false;
|
||||
|
||||
key = k;
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!key) return callback(Error('Incorrect response'));
|
||||
callback(null, data[key].NewExternalIPAddress);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.findGateway = function findGateway(callback) {
|
||||
var timeout;
|
||||
var timeouted = false;
|
||||
var p = this.ssdp.search(
|
||||
'urn:schemas-upnp-org:device:InternetGatewayDevice:1'
|
||||
);
|
||||
|
||||
timeout = setTimeout(function() {
|
||||
timeouted = true;
|
||||
p.emit('end');
|
||||
callback(new Error('timeout'));
|
||||
}, this.timeout);
|
||||
|
||||
p.on('device', function (info, address) {
|
||||
if (timeouted) return;
|
||||
p.emit('end');
|
||||
clearTimeout(timeout);
|
||||
|
||||
// Create gateway
|
||||
callback(null, nat.device.create(info.location), address);
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.close = function close() {
|
||||
this.ssdp.close();
|
||||
};
|
||||
162
resources/app/node_modules/nat-upnp/lib/nat-upnp/device.js
generated
vendored
Normal file
162
resources/app/node_modules/nat-upnp/lib/nat-upnp/device.js
generated
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
var nat = require('../nat-upnp'),
|
||||
request = require('request'),
|
||||
url = require('url'),
|
||||
xml2js = require('xml2js'),
|
||||
Buffer = require('buffer').Buffer;
|
||||
|
||||
var device = exports;
|
||||
|
||||
function Device(url) {
|
||||
this.description = url;
|
||||
this.services = [
|
||||
'urn:schemas-upnp-org:service:WANIPConnection:1',
|
||||
'urn:schemas-upnp-org:service:WANPPPConnection:1'
|
||||
];
|
||||
};
|
||||
|
||||
device.create = function create(url) {
|
||||
return new Device(url);
|
||||
};
|
||||
|
||||
Device.prototype._getXml = function _getXml(url, callback) {
|
||||
var once = false;
|
||||
function respond(err, body) {
|
||||
if (once) return;
|
||||
once = true;
|
||||
|
||||
callback(err, body);
|
||||
}
|
||||
|
||||
request(url, function(err, res, body) {
|
||||
if (err) return callback(err);
|
||||
|
||||
if (res.statusCode !== 200) {
|
||||
respond(Error('Failed to lookup device description'));
|
||||
return;
|
||||
}
|
||||
|
||||
var parser = new xml2js.Parser();
|
||||
parser.parseString(body, function(err, body) {
|
||||
if (err) return respond(err);
|
||||
|
||||
respond(null, body);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Device.prototype.getService= function getService(types, callback) {
|
||||
var self = this;
|
||||
|
||||
this._getXml(this.description, function(err, info) {
|
||||
if (err) return callback(err);
|
||||
|
||||
var s = self.parseDescription(info).services.filter(function(service) {
|
||||
return types.indexOf(service.serviceType) !== -1;
|
||||
});
|
||||
|
||||
if (s.length === 0 || !s[0].controlURL || !s[0].SCPDURL) {
|
||||
return callback(Error('Service not found'));
|
||||
}
|
||||
|
||||
var base = url.parse(info.baseURL || self.description);
|
||||
function prefix(u) {
|
||||
var uri = url.parse(u);
|
||||
|
||||
uri.host = uri.host || base.host;
|
||||
uri.protocol = uri.protocol || base.protocol;
|
||||
|
||||
return url.format(uri);
|
||||
}
|
||||
|
||||
callback(null,{
|
||||
service: s[0].serviceType,
|
||||
SCPDURL: prefix(s[0].SCPDURL),
|
||||
controlURL: prefix(s[0].controlURL)
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Device.prototype.parseDescription = function parseDescription(info) {
|
||||
var services = [],
|
||||
devices = [];
|
||||
|
||||
function toArray(item) {
|
||||
return Array.isArray(item) ? item : [ item ];
|
||||
};
|
||||
|
||||
function traverseServices(service) {
|
||||
if (!service) return;
|
||||
services.push(service);
|
||||
}
|
||||
|
||||
function traverseDevices(device) {
|
||||
if (!device) return;
|
||||
devices.push(device);
|
||||
|
||||
if (device.deviceList && device.deviceList.device) {
|
||||
toArray(device.deviceList.device).forEach(traverseDevices);
|
||||
}
|
||||
|
||||
if (device.serviceList && device.serviceList.service) {
|
||||
toArray(device.serviceList.service).forEach(traverseServices);
|
||||
}
|
||||
}
|
||||
|
||||
traverseDevices(info.device);
|
||||
|
||||
return {
|
||||
services: services,
|
||||
devices: devices
|
||||
};
|
||||
};
|
||||
|
||||
Device.prototype.run = function run(action, args, callback) {
|
||||
var self = this;
|
||||
|
||||
this.getService(this.services, function(err, info) {
|
||||
if (err) return callback(err);
|
||||
|
||||
var body = '<?xml version="1.0"?>' +
|
||||
'<s:Envelope ' +
|
||||
'xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" ' +
|
||||
's:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">' +
|
||||
'<s:Body>' +
|
||||
'<u:' + action + ' xmlns:u=' +
|
||||
JSON.stringify(info.service) + '>' +
|
||||
args.map(function(args) {
|
||||
return '<' + args[0]+ '>' +
|
||||
(args[1] === undefined ? '' : args[1]) +
|
||||
'</' + args[0] + '>';
|
||||
}).join('') +
|
||||
'</u:' + action + '>' +
|
||||
'</s:Body>' +
|
||||
'</s:Envelope>';
|
||||
|
||||
request({
|
||||
method: 'POST',
|
||||
url: info.controlURL,
|
||||
headers: {
|
||||
'Content-Type': 'text/xml; charset="utf-8"',
|
||||
'Content-Length': Buffer.byteLength(body),
|
||||
'Connection': 'close',
|
||||
'SOAPAction': JSON.stringify(info.service + '#' + action)
|
||||
},
|
||||
body: body
|
||||
}, function(err, res, body) {
|
||||
if (err) return callback(err);
|
||||
|
||||
var parser = new xml2js.Parser();
|
||||
parser.parseString(body, function(err, body) {
|
||||
if (res.statusCode !== 200) {
|
||||
return callback(Error('Request failed: ' + res.statusCode));
|
||||
}
|
||||
|
||||
var soapns = nat.utils.getNamespace(
|
||||
body,
|
||||
'http://schemas.xmlsoap.org/soap/envelope/');
|
||||
|
||||
callback(null, body[soapns + 'Body']);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
162
resources/app/node_modules/nat-upnp/lib/nat-upnp/ssdp.js
generated
vendored
Normal file
162
resources/app/node_modules/nat-upnp/lib/nat-upnp/ssdp.js
generated
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
var dgram = require('dgram');
|
||||
var util = require('util');
|
||||
var os = require('os');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var ssdp = exports;
|
||||
|
||||
function Ssdp(opts) {
|
||||
EventEmitter.call(this);
|
||||
|
||||
this._opts = opts || {};
|
||||
this._sourcePort = this._opts.sourcePort || 0;
|
||||
this.multicast = '239.255.255.250';
|
||||
this.port = 1900;
|
||||
this._bound = false;
|
||||
this._boundCount = 0;
|
||||
this._closed = false;
|
||||
this._queue = [];
|
||||
|
||||
// Create sockets on all external interfaces
|
||||
this.createSockets();
|
||||
}
|
||||
util.inherits(Ssdp, EventEmitter);
|
||||
|
||||
ssdp.create = function create() {
|
||||
return new Ssdp();
|
||||
};
|
||||
|
||||
Ssdp.parseMimeHeader = function (headerStr) {
|
||||
var lines = headerStr.split(/\r\n/g);
|
||||
|
||||
// Parse headers from lines to hashmap
|
||||
return lines.reduce(function(headers, line) {
|
||||
line.replace(/^([^:]*)\s*:\s*(.*)$/, function (a, key, value) {
|
||||
headers[key.toLowerCase()] = value;
|
||||
});
|
||||
return headers;
|
||||
}, {});
|
||||
};
|
||||
|
||||
Ssdp.prototype.createSockets = function createSockets() {
|
||||
var self = this;
|
||||
var interfaces = os.networkInterfaces();
|
||||
|
||||
this.sockets = Object.keys(interfaces).reduce(function(a, key) {
|
||||
return a.concat(interfaces[key].filter(function(item) {
|
||||
return !item.internal;
|
||||
}).map(function(item) {
|
||||
return self.createSocket(item);
|
||||
}));
|
||||
}, []);
|
||||
};
|
||||
|
||||
Ssdp.prototype.search = function search(device, promise) {
|
||||
if (!promise) {
|
||||
promise = new EventEmitter();
|
||||
promise._ended = false;
|
||||
promise.once('end', function() {
|
||||
promise._ended = true;
|
||||
});
|
||||
}
|
||||
|
||||
if (!this._bound) {
|
||||
this._queue.push({ action: 'search', device: device, promise: promise });
|
||||
return promise;
|
||||
}
|
||||
|
||||
// If promise was ended before binding - do not send queries
|
||||
if (promise._ended) return;
|
||||
|
||||
var self = this;
|
||||
var query = new Buffer('M-SEARCH * HTTP/1.1\r\n' +
|
||||
'HOST: ' + this.multicast + ':' + this.port + '\r\n' +
|
||||
'MAN: "ssdp:discover"\r\n' +
|
||||
'MX: 1\r\n' +
|
||||
'ST: ' + device + '\r\n' +
|
||||
'\r\n');
|
||||
|
||||
// Send query on each socket
|
||||
this.sockets.forEach(function(socket) {
|
||||
socket.send(query, 0, query.length, this.port, this.multicast);
|
||||
}, this);
|
||||
|
||||
function ondevice(info, address) {
|
||||
if (promise._ended) return;
|
||||
if (info.st !== device) return;
|
||||
|
||||
promise.emit('device', info, address);
|
||||
}
|
||||
this.on('_device', ondevice);
|
||||
|
||||
// Detach listener after receiving 'end' event
|
||||
promise.once('end', function() {
|
||||
self.removeListener('_device', ondevice);
|
||||
});
|
||||
|
||||
return promise;
|
||||
};
|
||||
|
||||
Ssdp.prototype.createSocket = function createSocket(interface) {
|
||||
var self = this;
|
||||
var socket = dgram.createSocket(interface.family === 'IPv4' ?
|
||||
'udp4' : 'udp6');
|
||||
|
||||
socket.on('message', function (message, info) {
|
||||
// Ignore messages after closing sockets
|
||||
if (self._closed) return;
|
||||
|
||||
// Parse response
|
||||
self.parseResponse(message.toString(), socket.address, info);
|
||||
});
|
||||
|
||||
// Bind in next tick (sockets should be me in this.sockets array)
|
||||
process.nextTick(function() {
|
||||
// Unqueue this._queue once all sockets are ready
|
||||
function onready() {
|
||||
if (self._boundCount < self.sockets.length) return;
|
||||
|
||||
self._bound = true;
|
||||
self._queue.forEach(function(item) {
|
||||
return self[item.action](item.device, item.promise);
|
||||
});
|
||||
}
|
||||
|
||||
socket.on('listening', function() {
|
||||
self._boundCount += 1;
|
||||
onready();
|
||||
});
|
||||
|
||||
// On error - remove socket from list and execute items from queue
|
||||
socket.once('error', function() {
|
||||
self.sockets.splice(self.sockets.indexOf(socket), 1);
|
||||
onready();
|
||||
});
|
||||
|
||||
socket.address = interface.address;
|
||||
socket.bind(self._sourcePort, interface.address);
|
||||
});
|
||||
|
||||
return socket;
|
||||
};
|
||||
|
||||
// TODO create separate logic for parsing unsolicited upnp broadcasts,
|
||||
// if and when that need arises
|
||||
Ssdp.prototype.parseResponse = function parseResponse(response, addr, remote) {
|
||||
// Ignore incorrect packets
|
||||
if (!/^(HTTP|NOTIFY)/m.test(response)) return;
|
||||
|
||||
var headers = Ssdp.parseMimeHeader(response);
|
||||
|
||||
// We are only interested in messages that can be matched against the original
|
||||
// search target
|
||||
if (!headers.st) return;
|
||||
|
||||
this.emit('_device', headers, addr);
|
||||
};
|
||||
|
||||
Ssdp.prototype.close = function close() {
|
||||
this.sockets.forEach(function(socket) {
|
||||
socket.close();
|
||||
});
|
||||
this._closed = true;
|
||||
};
|
||||
19
resources/app/node_modules/nat-upnp/lib/nat-upnp/utils.js
generated
vendored
Normal file
19
resources/app/node_modules/nat-upnp/lib/nat-upnp/utils.js
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
var utils = exports;
|
||||
|
||||
utils.getNamespace = function getNamespace(data, uri) {
|
||||
var ns;
|
||||
|
||||
if (data['@']) {
|
||||
Object.keys(data['@']).some(function(key) {
|
||||
if (!/^xmlns:/.test(key)) return;
|
||||
if (data['@'][key] !== uri) {
|
||||
return;
|
||||
}
|
||||
|
||||
ns = key.replace(/^xmlns:/, '');
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
return ns ? ns + ':' : '';
|
||||
};
|
||||
Reference in New Issue
Block a user