Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions Readme.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,39 @@
# This is a fork with changes required by the RPOS ONVIF Server (ONVIF NVT)
## Initial changes (c) Jeroen Versteege 2015
## More recent changes (c) Roger Hardiman 2016-2024
Main changes are
* Telling the user when the node-soap library is ready and is listening via an onReady() callback
* Passing in the path to the WSDL files
* Using the SOAP 2003/05 headers that the ONVIF Standard requires
* Including a Content-Length in the SOAP reply
* Handling SOAP requests with extra NULL characters after the end of the XML
* Passing the full HTTP request object back to the user in the 'request' emit. (Used by RPOS with HTTP Digest Authentication)
* Allowing the user to send a HTTP response with a custom HTTP Status Code and HTTP Body. (Used by RPOS with HTTP Digest Authentication) with
```
throw { "statusCode": 401,
"httpHeader": {
"key": "WWW-Authenticate",
"value": `Digest realm="rpos-realm", qop="auth", algorithm="MD5", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093"`
},
"httpContents": "401 Digest Authentication Required"
};
};
```
* Allowing the override of namespaces (used in Device Service GetServices() and taken from the upstream Node-Soap project and a commit in 2015

When using this you can also define the namespace as a attribute to the XML tag
```
const reply = {
Capabilities : {
"tptz:Capabilities": { // note the namespace override with "Namespace:Name" format
attributes: { // Add namespace here. Would be nicer to have the namespace at the top level in the envelope but this method is valid
'xmlns:tptz' : 'http://www.onvif.org/ver20/ptz/wsdl',
},
}
}
};
```

# Soap [![NPM version][npm-image]][npm-url] [![Downloads][downloads-image]][npm-url] [![Build Status][travis-image]][travis-url]
> A SOAP client and server for node.js.

Expand Down
36 changes: 25 additions & 11 deletions lib/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,8 @@ var Server = function(server, path, services, wsdl, options) {

if (path[path.length - 1] !== '/')
path += '/';
wsdl.onReady(function(err) {
wsdl.onReady(function (err) {
var listeners = server.listeners('request').slice();

server.removeAllListeners('request');
server.addListener('request', function(req, res) {
if (typeof self.authorizeConnection === 'function') {
Expand All @@ -67,6 +66,8 @@ var Server = function(server, path, services, wsdl, options) {
}
}
});
if (options.onReady)
options.onReady();
});

this._initializeOptions(options);
Expand Down Expand Up @@ -120,7 +121,8 @@ Server.prototype._requestListener = function(req, res) {
if (typeof self.log === 'function') {
self.log("received", xml);
}
self._process(xml, req.url, function(result) {
self._process(req, xml, req.url, function (result) {
res.setHeader('Content-Length', result.length);
res.write(result);
res.end();
if (typeof self.log === 'function') {
Expand All @@ -129,8 +131,18 @@ Server.prototype._requestListener = function(req, res) {
});
}
catch (err) {
error = err.stack || err;
res.write(error);
error = err.stack || err
if (err.httpHeader) {
res.setHeader(err.httpHeader.key, err.httpHeader.value);
}
if (err.statusCode) {
res.statusCode = Number(err.statusCode);
}
if (err.httpContents) {
res.write(err.httpContents);
} else {
res.write(error.toString());
}
res.end();
if (typeof self.log === 'function') {
self.log("error", error);
Expand All @@ -143,10 +155,11 @@ Server.prototype._requestListener = function(req, res) {
}
};

Server.prototype._process = function(input, URL, callback) {
Server.prototype._process = function(httpRequest, input, URL, callback) {
var trimmedInput = input.replace(/\0.*$/g,'').trim(); // includes removing any NULL characters as ODTT 22.12 sends some for GetStreamUri in test Media-7-1-2-v18.06
var self = this,
pathname = url.parse(URL).pathname.replace(/\/$/, ''),
obj = this.wsdl.xmlToObject(input),
obj = this.wsdl.xmlToObject(trimmedInput),
body = obj.Body,
headers = obj.Header,
bindings = this.wsdl.definitions.bindings, binding,
Expand Down Expand Up @@ -205,7 +218,7 @@ Server.prototype._process = function(input, URL, callback) {
if (binding.style === 'rpc') {
methodName = Object.keys(body)[0];

self.emit('request', obj, methodName);
self.emit('request', obj, methodName, httpRequest);
if (headers)
self.emit('headers', headers, methodName);

Expand All @@ -222,7 +235,7 @@ Server.prototype._process = function(input, URL, callback) {
var messageElemName = (Object.keys(body)[0] === 'attributes' ? Object.keys(body)[1] : Object.keys(body)[0]);
var pair = binding.topElements[messageElemName];

self.emit('request', obj, pair.methodName);
self.emit('request', obj, pair.methodName, httpRequest);
if (headers)
self.emit('headers', headers, pair.methodName);

Expand Down Expand Up @@ -276,7 +289,7 @@ Server.prototype._executeMethod = function(options, callback, includeTimestamp)
body = self.wsdl.objectToRpcXML(outputName, result, '', self.wsdl.definitions.$targetNamespace);
} else {
var element = self.wsdl.definitions.services[serviceName].ports[portName].binding.methods[methodName].output;
body = self.wsdl.objectToDocumentXML(outputName, result, element.targetNSAlias, element.targetNamespace);
body = self.wsdl.objectToDocumentXML(outputName, result, element.targetNSAlias, element.targetNamespace, outputName);
}
callback(self._envelope(body, includeTimestamp));
}
Expand All @@ -299,7 +312,8 @@ Server.prototype._envelope = function(body, includeTimestamp) {
encoding = '',
alias = findKey(defs.xmlns, ns);
var xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
"<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" " +
// "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" " +
"<soap:Envelope xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\" " +
encoding +
this.wsdl.xmlnsInEnvelope + '>';
if (includeTimestamp) {
Expand Down
8 changes: 5 additions & 3 deletions lib/soap.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,19 @@ function createClient(url, options, callback, endpoint) {

function listen(server, pathOrOptions, services, xml) {
var options = {},
path = pathOrOptions;
path = pathOrOptions,
wsdlPath = null;

if (typeof pathOrOptions === 'object') {
options = pathOrOptions;
path = options.path;
services = options.services;
xml = options.xml;
wsdlPath = options.wsdlPath;
}

var wsdl = new WSDL(xml || services, null, options);
return new Server(server, path, services, wsdl);
var wsdl = new WSDL(xml || services, wsdlPath, options);
return new Server(server, path, services, wsdl, options);
}

exports.security = security;
Expand Down
11 changes: 10 additions & 1 deletion lib/wsdl.js
Original file line number Diff line number Diff line change
Expand Up @@ -1106,8 +1106,10 @@ WSDL.prototype._processNextInclude = function(includes, callback) {
var includePath;
if (!/^http/.test(self.uri) && !/^http/.test(include.location)) {
includePath = path.resolve(path.dirname(self.uri), include.location);
} else {
} else if(self.uri) {
includePath = url.resolve(self.uri, include.location);
} else {
includePath = include.location;
}

open_wsdl(includePath, this.options, function(err, wsdl) {
Expand Down Expand Up @@ -1536,6 +1538,13 @@ WSDL.prototype.objectToXML = function(obj, name, namespace, xmlns, first, xmlnsA

var value = '';
var nonSubNameSpace = '';

var nameWithNsRegex = /^([^:]+):([^:]+)$/.exec(name);
if (nameWithNsRegex) {
nonSubNameSpace = nameWithNsRegex[1] + ':';
name = nameWithNsRegex[2];
}

if (first) {
value = self.objectToXML(child, name, namespace, xmlns, false, null, parameterTypeObject, ancXmlns);
} else {
Expand Down