diff --git a/Readme.md b/Readme.md index bc59b6b23..e9f511b98 100644 --- a/Readme.md +++ b/Readme.md @@ -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. diff --git a/lib/server.js b/lib/server.js index a3920afec..6e62f80f3 100644 --- a/lib/server.js +++ b/lib/server.js @@ -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') { @@ -67,6 +66,8 @@ var Server = function(server, path, services, wsdl, options) { } } }); + if (options.onReady) + options.onReady(); }); this._initializeOptions(options); @@ -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') { @@ -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); @@ -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, @@ -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); @@ -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); @@ -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)); } @@ -299,7 +312,8 @@ Server.prototype._envelope = function(body, includeTimestamp) { encoding = '', alias = findKey(defs.xmlns, ns); var xml = "" + - "'; if (includeTimestamp) { diff --git a/lib/soap.js b/lib/soap.js index 705922127..21c115b1d 100644 --- a/lib/soap.js +++ b/lib/soap.js @@ -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; diff --git a/lib/wsdl.js b/lib/wsdl.js index 4b075cc82..aaf2f60ba 100644 --- a/lib/wsdl.js +++ b/lib/wsdl.js @@ -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) { @@ -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 {