From 59d8cab0138c4561ffc1e50c8c48509ec27dd67b Mon Sep 17 00:00:00 2001 From: Andy Greene Date: Fri, 15 Feb 2019 11:46:02 -0500 Subject: [PATCH] converting the project to TypeScript --- .gitignore | 2 + .npmignore | 1 + .travis.yml | 1 + lib/client.js | 434 --- lib/http.js | 150 -- lib/nscontext.js | 223 -- lib/security/BasicAuthSecurity.js | 24 - lib/security/BearerSecurity.js | 23 - lib/security/ClientSSLSecurity.js | 82 - lib/security/ClientSSLSecurityPFX.js | 51 - lib/security/NTLMSecurity.js | 32 - lib/security/WSSecurity.js | 92 - lib/security/WSSecurityCert.js | 97 - lib/security/index.js | 11 - lib/server.js | 535 ---- lib/soap.d.ts | 332 --- lib/soap.js | 111 - lib/utils.js | 35 - lib/wsdl.js | 2329 ----------------- package-lock.json | 653 ++++- package.json | 18 +- .../templates/wsse-security-header.ejs | 0 .../templates/wsse-security-token.ejs | 0 src/client.ts | 485 ++++ src/http.ts | 162 ++ src/nscontext.ts | 231 ++ src/security/BasicAuthSecurity.ts | 28 + src/security/BearerSecurity.ts | 26 + src/security/ClientSSLSecurity.ts | 89 + src/security/ClientSSLSecurityPFX.ts | 56 + src/security/NTLMSecurity.ts | 35 + src/security/WSSecurity.ts | 111 + src/security/WSSecurityCert.ts | 107 + src/security/index.ts | 8 + src/server.ts | 594 +++++ src/soap.ts | 128 + src/types.ts | 136 + src/utils.ts | 50 + src/wsdl/elements.ts | 1173 +++++++++ src/wsdl/index.ts | 1379 ++++++++++ test/client-customHttp-test.js | 2 +- test/client-customHttp-xsdinclude-test.js | 2 +- test/request-response-samples-test.js | 2 +- test/wsdl-test.js | 3 +- tsconfig.json | 17 + tslint.json | 32 + 46 files changed, 5522 insertions(+), 4570 deletions(-) delete mode 100644 lib/client.js delete mode 100644 lib/http.js delete mode 100644 lib/nscontext.js delete mode 100644 lib/security/BasicAuthSecurity.js delete mode 100644 lib/security/BearerSecurity.js delete mode 100644 lib/security/ClientSSLSecurity.js delete mode 100644 lib/security/ClientSSLSecurityPFX.js delete mode 100644 lib/security/NTLMSecurity.js delete mode 100644 lib/security/WSSecurity.js delete mode 100644 lib/security/WSSecurityCert.js delete mode 100644 lib/security/index.js delete mode 100644 lib/server.js delete mode 100644 lib/soap.d.ts delete mode 100644 lib/soap.js delete mode 100644 lib/utils.js delete mode 100644 lib/wsdl.js rename {lib/security => resources}/templates/wsse-security-header.ejs (100%) rename {lib/security => resources}/templates/wsse-security-token.ejs (100%) create mode 100644 src/client.ts create mode 100644 src/http.ts create mode 100644 src/nscontext.ts create mode 100644 src/security/BasicAuthSecurity.ts create mode 100644 src/security/BearerSecurity.ts create mode 100644 src/security/ClientSSLSecurity.ts create mode 100644 src/security/ClientSSLSecurityPFX.ts create mode 100644 src/security/NTLMSecurity.ts create mode 100644 src/security/WSSecurity.ts create mode 100644 src/security/WSSecurityCert.ts create mode 100644 src/security/index.ts create mode 100644 src/server.ts create mode 100644 src/soap.ts create mode 100644 src/types.ts create mode 100644 src/utils.ts create mode 100644 src/wsdl/elements.ts create mode 100644 src/wsdl/index.ts create mode 100644 tsconfig.json create mode 100644 tslint.json diff --git a/.gitignore b/.gitignore index 0c7a616f7..d6f9e40d5 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ yarn.lock .vscode .idea *.iml +/lib +/docs diff --git a/.npmignore b/.npmignore index 686101273..68c68acaa 100644 --- a/.npmignore +++ b/.npmignore @@ -5,3 +5,4 @@ CONTRIBUTING.md History.md PUBLISHING.md .* +/src diff --git a/.travis.yml b/.travis.yml index affd9dd26..c50d82f44 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,5 +19,6 @@ addons: before_install: - npm -g install npm@latest script: + - npm run build - npm run cover - npm run coveralls diff --git a/lib/client.js b/lib/client.js deleted file mode 100644 index e610f4344..000000000 --- a/lib/client.js +++ /dev/null @@ -1,434 +0,0 @@ -/* - * Copyright (c) 2011 Vinay Pulim - * MIT Licensed - */ - -"use strict"; - - -var HttpClient = require('./http'), - assert = require('assert'), - events = require('events'), - util = require('util'), - debug = require('debug')('node-soap'), - findPrefix = require('./utils').findPrefix, - _ = require('lodash'), - concatStream = require('concat-stream'), - BluebirdPromise = require("bluebird"), - uuid4 = require('uuid/v4'); - -var nonIdentifierChars = /[^a-z$_0-9]/i; - -var Client = function(wsdl, endpoint, options) { - events.EventEmitter.call(this); - options = options || {}; - this.wsdl = wsdl; - this._initializeOptions(options); - this._initializeServices(endpoint); - this.httpClient = options.httpClient || new HttpClient(options); - var promiseOptions = { multiArgs: true }; - if (options.overridePromiseSuffix) { - promiseOptions.suffix = options.overridePromiseSuffix; - } - BluebirdPromise.promisifyAll(this, promiseOptions); -}; -util.inherits(Client, events.EventEmitter); - -Client.prototype.addSoapHeader = function(soapHeader, name, namespace, xmlns) { - if (!this.soapHeaders) { - this.soapHeaders = []; - } - if (typeof soapHeader === 'object') { - soapHeader = this.wsdl.objectToXML(soapHeader, name, namespace, xmlns, true); - } - return this.soapHeaders.push(soapHeader) - 1; -}; - -Client.prototype.changeSoapHeader = function(index, soapHeader, name, namespace, xmlns) { - if (!this.soapHeaders) { - this.soapHeaders = []; - } - if (typeof soapHeader === 'object') { - soapHeader = this.wsdl.objectToXML(soapHeader, name, namespace, xmlns, true); - } - this.soapHeaders[index] = soapHeader; -}; - -Client.prototype.getSoapHeaders = function() { - return this.soapHeaders; -}; - -Client.prototype.clearSoapHeaders = function() { - this.soapHeaders = null; -}; - -Client.prototype.addHttpHeader = function(name, value) { - if (!this.httpHeaders) { - this.httpHeaders = {}; - } - this.httpHeaders[name] = value; -}; - -Client.prototype.getHttpHeaders = function() { - return this.httpHeaders; -}; - -Client.prototype.clearHttpHeaders = function() { - this.httpHeaders = {}; -}; - - -Client.prototype.addBodyAttribute = function(bodyAttribute, name, namespace, xmlns) { - if (!this.bodyAttributes) { - this.bodyAttributes = []; - } - if (typeof bodyAttribute === 'object') { - var composition = ''; - Object.getOwnPropertyNames(bodyAttribute).forEach(function(prop, idx, array) { - composition += ' ' + prop + '="' + bodyAttribute[prop] + '"'; - }); - bodyAttribute = composition; - } - if (bodyAttribute.substr(0, 1) !== ' ') bodyAttribute = ' ' + bodyAttribute; - this.bodyAttributes.push(bodyAttribute); -}; - -Client.prototype.getBodyAttributes = function() { - return this.bodyAttributes; -}; - -Client.prototype.clearBodyAttributes = function() { - this.bodyAttributes = null; -}; - -Client.prototype.setEndpoint = function(endpoint) { - this.endpoint = endpoint; - this._initializeServices(endpoint); -}; - -Client.prototype.describe = function() { - var types = this.wsdl.definitions.types; - return this.wsdl.describeServices(); -}; - -Client.prototype.setSecurity = function(security) { - this.security = security; -}; - -Client.prototype.setSOAPAction = function(SOAPAction) { - this.SOAPAction = SOAPAction; -}; - -Client.prototype._initializeServices = function(endpoint) { - var definitions = this.wsdl.definitions, - services = definitions.services; - for (var name in services) { - this[name] = this._defineService(services[name], endpoint); - } -}; - -Client.prototype._initializeOptions = function(options) { - this.streamAllowed = options.stream; - this.normalizeNames = options.normalizeNames; - this.wsdl.options.attributesKey = options.attributesKey || 'attributes'; - this.wsdl.options.envelopeKey = options.envelopeKey || 'soap'; - this.wsdl.options.preserveWhitespace = !!options.preserveWhitespace; - if(options.ignoredNamespaces !== undefined) { - if(options.ignoredNamespaces.override !== undefined) { - if(options.ignoredNamespaces.override === true) { - if(options.ignoredNamespaces.namespaces !== undefined) { - this.wsdl.options.ignoredNamespaces = options.ignoredNamespaces.namespaces; - } - } - } - } - if(options.overrideRootElement !== undefined) { - this.wsdl.options.overrideRootElement = options.overrideRootElement; - } - this.wsdl.options.forceSoap12Headers = !!options.forceSoap12Headers; -}; - -Client.prototype._defineService = function(service, endpoint) { - var ports = service.ports, - def = {}; - for (var name in ports) { - def[name] = this._definePort(ports[name], endpoint ? endpoint : ports[name].location); - } - return def; -}; - -Client.prototype._definePort = function(port, endpoint) { - var location = endpoint, - binding = port.binding, - methods = binding.methods, - def = {}; - for (var name in methods) { - def[name] = this._defineMethod(methods[name], location); - var methodName = this.normalizeNames ? name.replace(nonIdentifierChars, '_') : name; - this[methodName] = def[name]; - } - return def; -}; - -Client.prototype._defineMethod = function(method, location) { - var self = this; - var temp; - return function(args, callback, options, extraHeaders) { - if (typeof args === 'function') { - callback = args; - args = {}; - } else if (typeof options === 'function') { - temp = callback; - callback = options; - options = temp; - } else if (typeof extraHeaders === 'function') { - temp = callback; - callback = extraHeaders; - extraHeaders = options; - options = temp; - } - self._invoke(method, args, location, function(error, result, rawResponse, soapHeader, rawRequest) { - callback(error, result, rawResponse, soapHeader, rawRequest); - }, options, extraHeaders); - }; -}; - -Client.prototype._invoke = function(method, args, location, callback, options, extraHeaders) { - var self = this, - name = method.$name, - input = method.input, - output = method.output, - style = method.style, - defs = this.wsdl.definitions, - envelopeKey = this.wsdl.options.envelopeKey, - ns = defs.$targetNamespace, - encoding = '', - message = '', - xml = null, - req = null, - soapAction, - alias = findPrefix(defs.xmlns, ns), - headers = { - "Content-Type": "text/xml; charset=utf-8" - }, - xmlnsSoap = "xmlns:" + envelopeKey + "=\"http://schemas.xmlsoap.org/soap/envelope/\""; - - if (this.wsdl.options.forceSoap12Headers) { - headers["Content-Type"] = "application/soap+xml; charset=utf-8"; - xmlnsSoap = "xmlns:" + envelopeKey + "=\"http://www.w3.org/2003/05/soap-envelope\""; - } - - if (this.SOAPAction) { - soapAction = this.SOAPAction; - } else if (method.soapAction !== undefined && method.soapAction !== null) { - soapAction = method.soapAction; - } else { - soapAction = ((ns.lastIndexOf("/") !== ns.length - 1) ? ns + "/" : ns) + name; - } - - if (!this.wsdl.options.forceSoap12Headers) { - headers.SOAPAction = '"' + soapAction + '"'; - } - - options = options || {}; - - //Add extra headers - for (var header in this.httpHeaders ) { headers[header] = this.httpHeaders[header]; } - for (var attr in extraHeaders) { headers[attr] = extraHeaders[attr]; } - - // Allow the security object to add headers - if (self.security && self.security.addHeaders) - self.security.addHeaders(headers); - if (self.security && self.security.addOptions) - self.security.addOptions(options); - - if ((style === 'rpc')&& ( ( input.parts || input.name==="element" ) || args === null) ) { - assert.ok(!style || style === 'rpc', 'invalid message definition for document style binding'); - message = self.wsdl.objectToRpcXML(name, args, alias, ns,(input.name!=="element" )); - (method.inputSoap === 'encoded') && (encoding = 'soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" '); - } else { - assert.ok(!style || style === 'document', 'invalid message definition for rpc style binding'); - // pass `input.$lookupType` if `input.$type` could not be found - message = self.wsdl.objectToDocumentXML(input.$name, args, input.targetNSAlias, input.targetNamespace, (input.$type || input.$lookupType)); - } - xml = "" + - "<" + envelopeKey + ":Envelope " + - xmlnsSoap + " " + - "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " + - encoding + - this.wsdl.xmlnsInEnvelope + '>' + - ((self.soapHeaders || self.security) ? - ( - "<" + envelopeKey + ":Header>" + - (self.soapHeaders ? self.soapHeaders.join("\n") : "") + - (self.security && !self.security.postProcess ? self.security.toXML() : "") + - "" - ) - : - '' - ) + - "<" + envelopeKey + ":Body" + - (self.bodyAttributes ? self.bodyAttributes.join(' ') : '') + - (self.security && self.security.postProcess ? ' Id="_0"' : '') + - ">" + - message + - "" + - ""; - - if(self.security && self.security.postProcess){ - xml = self.security.postProcess(xml, envelopeKey); - } - - if(options && options.postProcess){ - xml = options.postProcess(xml); - } - - self.lastMessage = message; - self.lastRequest = xml; - self.lastEndpoint = location; - - var eid = options.exchangeId || uuid4(); - - self.emit('message', message, eid); - self.emit('request', xml, eid); - - var tryJSONparse = function(body) { - try { - return JSON.parse(body); - } - catch(err) { - return undefined; - } - }; - - if (this.streamAllowed && typeof self.httpClient.requestStream === 'function') { - callback = _.once(callback); - var startTime = Date.now(); - req = self.httpClient.requestStream(location, xml, headers, options, self); - self.lastRequestHeaders = req.headers; - var onError = function onError(err) { - self.lastResponse = null; - self.lastResponseHeaders = null; - self.lastElapsedTime = null; - self.emit('response', null, null, eid); - - callback(err, undefined, undefined, undefined, xml); - }; - req.on('error', onError); - req.on('response', function (response) { - response.on('error', onError); - - // When the output element cannot be looked up in the wsdl, play it safe and - // don't stream - if(response.statusCode !== 200 || !output || !output.$lookupTypes) { - response.pipe(concatStream({encoding: 'string'}, function (body) { - self.lastResponse = body; - self.lastResponseHeaders = response && response.headers; - self.lastElapsedTime = Date.now() - startTime; - self.emit('response', body, response, eid); - - return parseSync(body, response); - - })); - return; - } - - self.wsdl.xmlToObject(response, function (error, obj) { - self.lastResponse = response; - self.lastResponseHeaders = response && response.headers; - self.lastElapsedTime = Date.now() - startTime; - self.emit('response', '', response, eid); - - if (error) { - error.response = response; - error.body = ''; - self.emit('soapError', error, eid); - return callback(error, response, undefined, undefined, xml); - } - - return finish(obj, '', response); - }); - }); - return; - } - - req = self.httpClient.request(location, xml, function(err, response, body) { - self.lastResponse = body; - self.lastResponseHeaders = response && response.headers; - self.lastElapsedTime = response && response.elapsedTime; - self.emit('response', body, response, eid); - - if (err) { - callback(err, undefined, undefined, undefined, xml); - } else { - return parseSync(body, response); - } - }, headers, options, self); - - function parseSync(body, response) { - var obj; - try { - obj = self.wsdl.xmlToObject(body); - } catch (error) { - // When the output element cannot be looked up in the wsdl and the body is JSON - // instead of sending the error, we pass the body in the response. - if(!output || !output.$lookupTypes) { - debug('Response element is not present. Unable to convert response xml to json.'); - // If the response is JSON then return it as-is. - var json = _.isObject(body) ? body : tryJSONparse(body); - if (json) { - return callback(null, response, json, undefined, xml); - } - } - error.response = response; - error.body = body; - self.emit('soapError', error, eid); - return callback(error, response, body, undefined, xml); - } - return finish(obj, body, response); - } - - function finish(obj, body, response) { - var result; - - if (!output){ - // one-way, no output expected - return callback(null, null, body, obj.Header, xml); - } - - // If it's not HTML and Soap Body is empty - if (!obj.html && !obj.Body) { - return callback(null, obj, body, obj.Header); - } - - if( typeof obj.Body !== 'object' ) { - var error = new Error('Cannot parse response'); - error.response = response; - error.body = body; - return callback(error, obj, body, undefined, xml); - } - - result = obj.Body[output.$name]; - // RPC/literal response body may contain elements with added suffixes I.E. - // 'Response', or 'Output', or 'Out' - // This doesn't necessarily equal the ouput message name. See WSDL 1.1 Section 2.4.5 - if(!result){ - result = obj.Body[output.$name.replace(/(?:Out(?:put)?|Response)$/, '')]; - } - if (!result) { - ['Response', 'Out', 'Output'].forEach(function (term) { - if (obj.Body.hasOwnProperty(name + term)) { - return result = obj.Body[name + term]; - } - }); - } - - callback(null, result, body, obj.Header, xml); - } - - // Added mostly for testability, but possibly useful for debugging - if(req && req.headers && !options.ntlm) //fixes an issue when req or req.headers is undefined, doesn't apply to ntlm requests - self.lastRequestHeaders = req.headers; -}; - -exports.Client = Client; diff --git a/lib/http.js b/lib/http.js deleted file mode 100644 index 49fa0c136..000000000 --- a/lib/http.js +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (c) 2011 Vinay Pulim - * MIT Licensed - */ - -'use strict'; - -var url = require('url'); -var req = require('request'); -var debug = require('debug')('node-soap'); -var httpNtlm = require('httpntlm'); - -var VERSION = require('../package.json').version; - -/** - * A class representing the http client - * @param {Object} [options] Options object. It allows the customization of - * `request` module - * - * @constructor - */ -function HttpClient(options) { - options = options || {}; - this._request = options.request || req; -} - -/** - * Build the HTTP request (method, uri, headers, ...) - * @param {String} rurl The resource url - * @param {Object|String} data The payload - * @param {Object} exheaders Extra http headers - * @param {Object} exoptions Extra options - * @returns {Object} The http request object for the `request` module - */ -HttpClient.prototype.buildRequest = function(rurl, data, exheaders, exoptions) { - var curl = url.parse(rurl); - var secure = curl.protocol === 'https:'; - var host = curl.hostname; - var port = parseInt(curl.port, 10); - var path = [curl.pathname || '/', curl.search || '', curl.hash || ''].join(''); - var method = data ? 'POST' : 'GET'; - var headers = { - 'User-Agent': 'node-soap/' + VERSION, - 'Accept': 'text/html,application/xhtml+xml,application/xml,text/xml;q=0.9,*/*;q=0.8', - 'Accept-Encoding': 'none', - 'Accept-Charset': 'utf-8', - 'Connection': exoptions && exoptions.forever ? 'keep-alive' : 'close', - 'Host': host + (isNaN(port) ? '' : ':' + port) - }; - var attr; - var header; - var mergeOptions = ['headers']; - - if (typeof data === 'string') { - headers['Content-Length'] = Buffer.byteLength(data, 'utf8'); - headers['Content-Type'] = 'application/x-www-form-urlencoded'; - } - - exheaders = exheaders || {}; - for (attr in exheaders) { - headers[attr] = exheaders[attr]; - } - - var options = { - uri: curl, - method: method, - headers: headers, - followAllRedirects: true - }; - - - options.body = data; - - - exoptions = exoptions || {}; - for (attr in exoptions) { - if (mergeOptions.indexOf(attr) !== -1) { - for (header in exoptions[attr]) { - options[attr][header] = exoptions[attr][header]; - } - } else { - options[attr] = exoptions[attr]; - } - } - debug('Http request: %j', options); - return options; -}; - -/** - * Handle the http response - * @param {Object} The req object - * @param {Object} res The res object - * @param {Object} body The http body - * @param {Object} The parsed body - */ -HttpClient.prototype.handleResponse = function(req, res, body) { - debug('Http response body: %j', body); - if (typeof body === 'string') { - // Remove any extra characters that appear before or after the SOAP - // envelope. - var match = - body.replace(//, "").match(/(?:<\?[^?]*\?>[\s]*)?<([^:]*):Envelope([\S\s]*)<\/\1:Envelope>/i); - if (match) { - body = match[0]; - } - } - return body; -}; - -HttpClient.prototype.request = function(rurl, data, callback, exheaders, exoptions) { - var self = this; - var options = self.buildRequest(rurl, data, exheaders, exoptions); - var req; - - if (exoptions !== undefined && exoptions.hasOwnProperty('ntlm')) { - // sadly when using ntlm nothing to return - // Not sure if this can be handled in a cleaner way rather than an if/else, - // will to tidy up if I get chance later, patches welcome - insanityinside - options.url = rurl; - httpNtlm[options.method.toLowerCase()](options, function (err, res) { - if (err) { - return callback(err); - } - // if result is stream - if( typeof res.body !== 'string') { - res.body = res.body.toString(); - } - res.body = self.handleResponse(req, res, res.body); - callback(null, res, res.body); - }); - } else { - req = self._request(options, function (err, res, body) { - if (err) { - return callback(err); - } - body = self.handleResponse(req, res, body); - callback(null, res, body); - }); - } - - return req; -}; - -HttpClient.prototype.requestStream = function(rurl, data, exheaders, exoptions) { - var self = this; - var options = self.buildRequest(rurl, data, exheaders, exoptions); - return self._request(options); -}; - -module.exports = HttpClient; diff --git a/lib/nscontext.js b/lib/nscontext.js deleted file mode 100644 index c58171f12..000000000 --- a/lib/nscontext.js +++ /dev/null @@ -1,223 +0,0 @@ -'use strict'; - -module.exports = NamespaceContext; - -/** - * Scope for XML namespaces - * @param {NamespaceScope} [parent] Parent scope - * @returns {NamespaceScope} - * @constructor - */ -function NamespaceScope(parent) { - if (!(this instanceof NamespaceScope)) { - return new NamespaceScope(parent); - } - this.parent = parent; - this.namespaces = {}; -} - -/** - * Namespace context that manages hierarchical scopes - * @returns {NamespaceContext} - * @constructor - */ -function NamespaceContext() { - if (!(this instanceof NamespaceContext)) { - return new NamespaceContext(); - } - this.scopes = []; - this.pushContext(); - this.prefixCount = 0; -} - -/** - * Look up the namespace URI by prefix - * @param {String} prefix Namespace prefix - * @param {Boolean} [localOnly] Search current scope only - * @returns {String} Namespace URI - */ -NamespaceScope.prototype.getNamespaceURI = function(prefix, localOnly) { - switch (prefix) { - case 'xml': - return 'http://www.w3.org/XML/1998/namespace'; - case 'xmlns': - return 'http://www.w3.org/2000/xmlns/'; - default: - var nsUri = this.namespaces[prefix]; - /*jshint -W116 */ - if (nsUri != null) { - return nsUri.uri; - } else if (!localOnly && this.parent) { - return this.parent.getNamespaceURI(prefix); - } else { - return null; - } - } -}; - -NamespaceScope.prototype.getNamespaceMapping = function(prefix) { - switch (prefix) { - case 'xml': - return { - uri: 'http://www.w3.org/XML/1998/namespace', - prefix: 'xml', - declared: true - }; - case 'xmlns': - return { - uri: 'http://www.w3.org/2000/xmlns/', - prefix: 'xmlns', - declared: true - }; - default: - var mapping = this.namespaces[prefix]; - /*jshint -W116 */ - if (mapping != null) { - return mapping; - } else if (this.parent) { - return this.parent.getNamespaceMapping(prefix); - } else { - return null; - } - } -}; - -/** - * Look up the namespace prefix by URI - * @param {String} nsUri Namespace URI - * @param {Boolean} [localOnly] Search current scope only - * @returns {String} Namespace prefix - */ -NamespaceScope.prototype.getPrefix = function(nsUri, localOnly) { - switch (nsUri) { - case 'http://www.w3.org/XML/1998/namespace': - return 'xml'; - case 'http://www.w3.org/2000/xmlns/': - return 'xmlns'; - default: - for (var p in this.namespaces) { - if (this.namespaces[p].uri === nsUri) { - return p; - } - } - if (!localOnly && this.parent) { - return this.parent.getPrefix(nsUri); - } else { - return null; - } - } -}; - -/** - * Add a prefix/URI namespace mapping - * @param {String} prefix Namespace prefix - * @param {String} nsUri Namespace URI - * @param {Boolean} [localOnly] Search current scope only - * @returns {boolean} true if the mapping is added or false if the mapping - * already exists - */ -NamespaceContext.prototype.addNamespace = function(prefix, nsUri, localOnly) { - if (this.getNamespaceURI(prefix, localOnly) === nsUri) { - return false; - } - if (this.currentScope) { - this.currentScope.namespaces[prefix] = { - uri: nsUri, - prefix: prefix, - declared: false - }; - return true; - } - return false; -}; - -/** - * Push a scope into the context - * @returns {NamespaceScope} The current scope - */ -NamespaceContext.prototype.pushContext = function() { - var scope = new NamespaceScope(this.currentScope); - this.scopes.push(scope); - this.currentScope = scope; - return scope; -}; - -/** - * Pop a scope out of the context - * @returns {NamespaceScope} The removed scope - */ -NamespaceContext.prototype.popContext = function() { - var scope = this.scopes.pop(); - if (scope) { - this.currentScope = scope.parent; - } else { - this.currentScope = null; - } - return scope; -}; - -/** - * Look up the namespace URI by prefix - * @param {String} prefix Namespace prefix - * @param {Boolean} [localOnly] Search current scope only - * @returns {String} Namespace URI - */ -NamespaceContext.prototype.getNamespaceURI = function(prefix, localOnly) { - return this.currentScope && this.currentScope.getNamespaceURI(prefix, localOnly); -}; - -/** - * Look up the namespace prefix by URI - * @param {String} nsURI Namespace URI - * @param {Boolean} [localOnly] Search current scope only - * @returns {String} Namespace prefix - */ -NamespaceContext.prototype.getPrefix = function(nsUri, localOnly) { - return this.currentScope && this.currentScope.getPrefix(nsUri, localOnly); -}; - -/** - * Register a namespace - * @param {String} nsUri Namespace URI - * @returns {String} The matching or generated namespace prefix - */ -NamespaceContext.prototype.registerNamespace = function(nsUri) { - var prefix = this.getPrefix(nsUri); - if (prefix) { - // If the namespace has already mapped to a prefix - return prefix; - } else { - // Try to generate a unique namespace - while (true) { - prefix = 'ns' + (++this.prefixCount); - if (!this.getNamespaceURI(prefix)) { - // The prefix is not used - break; - } - } - } - this.addNamespace(prefix, nsUri, true); - return prefix; -}; - -/** - * Declare a namespace prefix/uri mapping - * @param {String} prefix Namespace prefix - * @param {String} nsUri Namespace URI - * @returns {Boolean} true if the declaration is created - */ -NamespaceContext.prototype.declareNamespace = function(prefix, nsUri) { - if (this.currentScope) { - var mapping = this.currentScope.getNamespaceMapping(prefix); - if (mapping && mapping.uri === nsUri && mapping.declared) { - return false; - } - this.currentScope.namespaces[prefix] = { - uri: nsUri, - prefix: prefix, - declared: true - }; - return true; - } - return false; -}; diff --git a/lib/security/BasicAuthSecurity.js b/lib/security/BasicAuthSecurity.js deleted file mode 100644 index 5c954be72..000000000 --- a/lib/security/BasicAuthSecurity.js +++ /dev/null @@ -1,24 +0,0 @@ -"use strict"; - -var _ = require('lodash'); - -function BasicAuthSecurity(username, password, defaults) { - this._username = username; - this._password = password; - this.defaults = {}; - _.merge(this.defaults, defaults); -} - -BasicAuthSecurity.prototype.addHeaders = function(headers) { - headers.Authorization = 'Basic ' + new Buffer((this._username + ':' + this._password) || '').toString('base64'); -}; - -BasicAuthSecurity.prototype.toXML = function() { - return ''; -}; - -BasicAuthSecurity.prototype.addOptions = function(options) { - _.merge(options, this.defaults); -}; - -module.exports = BasicAuthSecurity; diff --git a/lib/security/BearerSecurity.js b/lib/security/BearerSecurity.js deleted file mode 100644 index 66e6f0fd5..000000000 --- a/lib/security/BearerSecurity.js +++ /dev/null @@ -1,23 +0,0 @@ -"use strict"; - -var _ = require('lodash'); - -function BearerSecurity(token, defaults) { - this._token = token; - this.defaults = {}; - _.merge(this.defaults, defaults); -} - -BearerSecurity.prototype.addHeaders = function(headers) { - headers.Authorization = "Bearer " + this._token; -}; - -BearerSecurity.prototype.toXML = function() { - return ''; -}; - -BearerSecurity.prototype.addOptions = function(options) { - _.merge(options, this.defaults); -}; - -module.exports = BearerSecurity; diff --git a/lib/security/ClientSSLSecurity.js b/lib/security/ClientSSLSecurity.js deleted file mode 100644 index 3a3800125..000000000 --- a/lib/security/ClientSSLSecurity.js +++ /dev/null @@ -1,82 +0,0 @@ -'use strict'; - -var fs = require('fs') - , https = require('https') - , _ = require('lodash'); - -/** - * activates SSL for an already existing client - * - * @module ClientSSLSecurity - * @param {Buffer|String} key - * @param {Buffer|String} cert - * @param {Buffer|String|Array} [ca] - * @param {Object} [defaults] - * @constructor - */ -function ClientSSLSecurity(key, cert, ca, defaults) { - if (key) { - if(Buffer.isBuffer(key)) { - this.key = key; - } else if (typeof key === 'string') { - this.key = fs.readFileSync(key); - } else { - throw new Error('key should be a buffer or a string!'); - } - } - - if (cert) { - if(Buffer.isBuffer(cert)) { - this.cert = cert; - } else if (typeof cert === 'string') { - this.cert = fs.readFileSync(cert); - } else { - throw new Error('cert should be a buffer or a string!'); - } - } - - if (ca) { - if(Buffer.isBuffer(ca) || Array.isArray(ca)) { - this.ca = ca; - } else if (typeof ca === 'string') { - this.ca = fs.readFileSync(ca); - } else { - defaults = ca; - this.ca = null; - } - } - - this.defaults = {}; - _.merge(this.defaults, defaults); - - this.agent = null; -} - -ClientSSLSecurity.prototype.toXML = function(headers) { - return ''; -}; - -ClientSSLSecurity.prototype.addOptions = function(options) { - var httpsAgent = null; - - options.key = this.key; - options.cert = this.cert; - options.ca = this.ca; - _.merge(options, this.defaults); - - if (!!options.forever) { - if (!this.agent) { - options.keepAlive = true; - - this.agent = new https.Agent(options); - } - - httpsAgent = this.agent; - } else { - httpsAgent = new https.Agent(options); - } - - options.agent = httpsAgent; -}; - -module.exports = ClientSSLSecurity; diff --git a/lib/security/ClientSSLSecurityPFX.js b/lib/security/ClientSSLSecurityPFX.js deleted file mode 100644 index 3ce7af535..000000000 --- a/lib/security/ClientSSLSecurityPFX.js +++ /dev/null @@ -1,51 +0,0 @@ -'use strict'; - -var fs = require('fs') - , https = require('https') - , _ = require('lodash'); - -/** - * activates SSL for an already existing client using a PFX cert - * - * @module ClientSSLSecurityPFX - * @param {Buffer|String} pfx - * @param {String} passphrase - * @constructor - */ -function ClientSSLSecurityPFX(pfx, passphrase, defaults) { - if (typeof passphrase === 'object') { - defaults = passphrase; - } - if (pfx) { - if (Buffer.isBuffer(pfx)) { - this.pfx = pfx; - } else if (typeof pfx === 'string') { - this.pfx = fs.readFileSync(pfx); - } else { - throw new Error('supplied pfx file should be a buffer or a file location'); - } - } - - if (passphrase) { - if (typeof passphrase === 'string') { - this.passphrase = passphrase; - } - } - this.defaults = {}; - _.merge(this.defaults, defaults); -} - -ClientSSLSecurityPFX.prototype.toXML = function(headers) { - return ''; -}; - -ClientSSLSecurityPFX.prototype.addOptions = function(options) { - options.pfx = this.pfx; - if (this.passphrase) { - options.passphrase = this.passphrase; - } - _.merge(options, this.defaults); - options.agent = new https.Agent(options); -}; - -module.exports = ClientSSLSecurityPFX; diff --git a/lib/security/NTLMSecurity.js b/lib/security/NTLMSecurity.js deleted file mode 100644 index ccec3be26..000000000 --- a/lib/security/NTLMSecurity.js +++ /dev/null @@ -1,32 +0,0 @@ -"use strict"; - -var _ = require('lodash'); - -function NTLMSecurity(username, password, domain, workstation) { - if (typeof username === "object") { - this.defaults = username; - this.defaults.ntlm = true; - } else { - this.defaults = { - ntlm: true, - username: username, - password: password, - domain: domain, - workstation: workstation - }; - } -} - -NTLMSecurity.prototype.addHeaders = function (headers) { - headers.Connection = 'keep-alive'; -}; - -NTLMSecurity.prototype.toXML = function () { - return ''; -}; - -NTLMSecurity.prototype.addOptions = function (options) { - _.merge(options, this.defaults); -}; - -module.exports = NTLMSecurity; diff --git a/lib/security/WSSecurity.js b/lib/security/WSSecurity.js deleted file mode 100644 index 317c8b883..000000000 --- a/lib/security/WSSecurity.js +++ /dev/null @@ -1,92 +0,0 @@ -"use strict"; - -var crypto = require('crypto'); -var passwordDigest = require('../utils').passwordDigest; -var validPasswordTypes = ['PasswordDigest', 'PasswordText']; - -function WSSecurity(username, password, options) { - options = options || {}; - this._username = username; - this._password = password; - //must account for backward compatibility for passwordType String param as well as object options defaults: passwordType = 'PasswordText', hasTimeStamp = true - if (typeof options === 'string') { - this._passwordType = options ? options : 'PasswordText'; - options = {}; - } else { - this._passwordType = options.passwordType ? options.passwordType : 'PasswordText'; - } - - if (validPasswordTypes.indexOf(this._passwordType) === -1) { - this._passwordType = 'PasswordText'; - } - - this._hasTimeStamp = options.hasTimeStamp || typeof options.hasTimeStamp === 'boolean' ? !!options.hasTimeStamp : true; - /*jshint eqnull:true */ - if (options.hasNonce != null) { - this._hasNonce = !!options.hasNonce; - } - this._hasTokenCreated = options.hasTokenCreated || typeof options.hasTokenCreated === 'boolean' ? !!options.hasTokenCreated : true; - if (options.actor != null) { - this._actor = options.actor; - } - if (options.mustUnderstand != null) { - this._mustUnderstand = !!options.mustUnderstand; - } -} - -WSSecurity.prototype.toXML = function() { - // avoid dependency on date formatting libraries - function getDate(d) { - function pad(n) { - return n < 10 ? '0' + n : n; - } - return d.getUTCFullYear() + '-' - + pad(d.getUTCMonth() + 1) + '-' - + pad(d.getUTCDate()) + 'T' - + pad(d.getUTCHours()) + ':' - + pad(d.getUTCMinutes()) + ':' - + pad(d.getUTCSeconds()) + 'Z'; - } - var now = new Date(); - var created = getDate(now); - var timeStampXml = ''; - if (this._hasTimeStamp) { - var expires = getDate( new Date(now.getTime() + (1000 * 600)) ); - timeStampXml = "" + - ""+created+"" + - ""+expires+"" + - ""; - } - - var password, nonce; - if (this._hasNonce || this._passwordType !== 'PasswordText') { - // nonce = base64 ( sha1 ( created + random ) ) - var nHash = crypto.createHash('sha1'); - nHash.update(created + Math.random()); - nonce = nHash.digest('base64'); - } - if (this._passwordType === 'PasswordText') { - password = "" + this._password + ""; - if (nonce) { - password += "" + nonce + ""; - } - } else { - /* Specific Testcase for passwordDigest calculation cover this code - /* istanbul ignore next */ - password = "" + passwordDigest(nonce, created, this._password) + "" + - "" + nonce + ""; - } - - return "" + - timeStampXml + - "" + - "" + this._username + "" + - password + - (this._hasTokenCreated ? "" + created + "" : "") + - "" + - ""; -}; - -module.exports = WSSecurity; diff --git a/lib/security/WSSecurityCert.js b/lib/security/WSSecurityCert.js deleted file mode 100644 index 027d55b28..000000000 --- a/lib/security/WSSecurityCert.js +++ /dev/null @@ -1,97 +0,0 @@ -"use strict"; - -var fs = require('fs'); -var path = require('path'); -var ejs = require('ejs'); -var SignedXml = require('xml-crypto').SignedXml; -var uuid4 = require('uuid/v4'); -var wsseSecurityHeaderTemplate; -var wsseSecurityTokenTemplate; - -function addMinutes(date, minutes) { - return new Date(date.getTime() + minutes * 60000); -} - -function dateStringForSOAP(date) { - return date.getUTCFullYear() + '-' + ('0' + (date.getUTCMonth() + 1)).slice(-2) + '-' + - ('0' + date.getUTCDate()).slice(-2) + 'T' + ('0' + date.getUTCHours()).slice(-2) + ":" + - ('0' + date.getUTCMinutes()).slice(-2) + ":" + ('0' + date.getUTCSeconds()).slice(-2) + "Z"; -} - -function generateCreated() { - return dateStringForSOAP(new Date()); -} - -function generateExpires() { - return dateStringForSOAP(addMinutes(new Date(), 10)); -} - -function insertStr(src, dst, pos) { - return [dst.slice(0, pos), src, dst.slice(pos)].join(''); -} - -function generateId() { - return uuid4().replace(/-/gm, ''); -} - -function WSSecurityCert(privatePEM, publicP12PEM, password, options) { - options = options || {}; - this.publicP12PEM = publicP12PEM.toString().replace('-----BEGIN CERTIFICATE-----', '').replace('-----END CERTIFICATE-----', '').replace(/(\r\n|\n|\r)/gm, ''); - - this.signer = new SignedXml(); - this.signer.signingKey = { - key: privatePEM, - passphrase: password - }; - this.x509Id = "x509-" + generateId(); - this.hasTimeStamp = typeof options.hasTimeStamp === 'undefined' ? true : !!options.hasTimeStamp; - this.signatureTransformations = Array.isArray(options.signatureTransformations) ? options.signatureTransformations - : ["http://www.w3.org/2000/09/xmldsig#enveloped-signature", "http://www.w3.org/2001/10/xml-exc-c14n#"]; - - var _this = this; - this.signer.keyInfoProvider = {}; - this.signer.keyInfoProvider.getKeyInfo = function (key) { - if (!wsseSecurityTokenTemplate) { - wsseSecurityTokenTemplate = ejs.compile(fs.readFileSync(path.join(__dirname, 'templates', 'wsse-security-token.ejs')).toString()); - } - - return wsseSecurityTokenTemplate({ x509Id: _this.x509Id }); - }; -} - -WSSecurityCert.prototype.postProcess = function (xml, envelopeKey) { - this.created = generateCreated(); - this.expires = generateExpires(); - - if (!wsseSecurityHeaderTemplate) { - wsseSecurityHeaderTemplate = ejs.compile(fs.readFileSync(path.join(__dirname, 'templates', 'wsse-security-header.ejs')).toString()); - } - - var secHeader = wsseSecurityHeaderTemplate({ - binaryToken: this.publicP12PEM, - created: this.created, - expires: this.expires, - hasTimeStamp: this.hasTimeStamp, - id: this.x509Id - }); - - var xmlWithSec = insertStr(secHeader, xml, xml.indexOf('')); - - var references = this.signatureTransformations; - - var bodyXpath = "//*[name(.)='" + envelopeKey + ":Body']"; - if (!this.signer.references.filter(function(ref){ return ref.xpath === bodyXpath; }).length > 0) { - this.signer.addReference(bodyXpath, references); - } - - var timestampXpath = "//*[name(.)='wsse:Security']/*[local-name(.)='Timestamp']" - if (this.hasTimeStamp && !this.signer.references.filter(function(ref){ return ref.xpath === timestampXpath; }).length > 0) { - this.signer.addReference(timestampXpath, references); - } - - this.signer.computeSignature(xmlWithSec); - - return insertStr(this.signer.getSignatureXml(), xmlWithSec, xmlWithSec.indexOf('')); -}; - -module.exports = WSSecurityCert; diff --git a/lib/security/index.js b/lib/security/index.js deleted file mode 100644 index 2e364a11d..000000000 --- a/lib/security/index.js +++ /dev/null @@ -1,11 +0,0 @@ -"use strict"; - -module.exports = { - BasicAuthSecurity: require('./BasicAuthSecurity') -, NTLMSecurity: require('./NTLMSecurity') -, ClientSSLSecurity: require('./ClientSSLSecurity') -, ClientSSLSecurityPFX: require('./ClientSSLSecurityPFX') -, WSSecurity: require('./WSSecurity') -, BearerSecurity: require('./BearerSecurity') -, WSSecurityCert: require('./WSSecurityCert') -}; diff --git a/lib/server.js b/lib/server.js deleted file mode 100644 index cda278315..000000000 --- a/lib/server.js +++ /dev/null @@ -1,535 +0,0 @@ -/* - * Copyright (c) 2011 Vinay Pulim - * MIT Licensed - */ - -"use strict"; - -function getDateString(d) { - function pad(n) { - return n < 10 ? '0' + n : n; - } - return d.getUTCFullYear() + '-' - + pad(d.getUTCMonth() + 1) + '-' - + pad(d.getUTCDate()) + 'T' - + pad(d.getUTCHours()) + ':' - + pad(d.getUTCMinutes()) + ':' - + pad(d.getUTCSeconds()) + 'Z'; -} - -var url = require('url'), - zlib = null, - events = require('events'), - util = require('util'), - findPrefix = require('./utils').findPrefix; - -try { - zlib = require("zlib"); -} catch (error) { -} - -var Server = function (server, path, services, wsdl, options) { - var self = this; - - events.EventEmitter.call(this); - - options = options || {}; - this.path = path; - this.services = services; - this.wsdl = wsdl; - this.suppressStack = options && options.suppressStack; - this.returnFault = options && options.returnFault; - this.onewayOptions = options && options.oneWay || {}; - this.enableChunkedEncoding = - options.enableChunkedEncoding === undefined ? true : !!options.enableChunkedEncoding; - - if (path[path.length - 1] !== '/') - path += '/'; - wsdl.onReady(function (err) { - if (typeof server.route === 'function' && typeof server.use === 'function') { - //handle only the required URL path for express server - server.route(path).all(function (req, res, next) { - if (typeof self.authorizeConnection === 'function') { - if (!self.authorizeConnection(req, res)) { - res.end(); - return; - } - } - self._requestListener(req, res); - }); - } else { - var listeners = server.listeners('request').slice(); - server.removeAllListeners('request'); - server.addListener('request', function (req, res) { - if (typeof self.authorizeConnection === 'function') { - if (!self.authorizeConnection(req, res)) { - res.end(); - return; - } - } - var reqPath = url.parse(req.url).pathname; - if (reqPath[reqPath.length - 1] !== '/') { - reqPath += '/'; - } - if (path === reqPath) { - self._requestListener(req, res); - } else { - for (var i = 0, len = listeners.length; i < len; i++) { - listeners[i].call(this, req, res); - } - } - }); - } - }); - - this._initializeOptions(options); -}; -util.inherits(Server, events.EventEmitter); - -Server.prototype._processSoapHeader = function (soapHeader, name, namespace, xmlns) { - var self = this; - - switch (typeof soapHeader) { - case 'object': - return this.wsdl.objectToXML(soapHeader, name, namespace, xmlns, true); - case 'function': - return function() { - var result = soapHeader.apply(null, arguments); - - if (typeof result === 'object') { - return self.wsdl.objectToXML(result, name, namespace, xmlns, true); - } else { - return result; - } - }; - default: - return soapHeader; - } -}; - -Server.prototype.addSoapHeader = function (soapHeader, name, namespace, xmlns) { - if (!this.soapHeaders) { - this.soapHeaders = []; - } - soapHeader = this._processSoapHeader(soapHeader, name, namespace, xmlns); - return this.soapHeaders.push(soapHeader) - 1; -}; - -Server.prototype.changeSoapHeader = function (index, soapHeader, name, namespace, xmlns) { - if (!this.soapHeaders) { - this.soapHeaders = []; - } - soapHeader = this._processSoapHeader(soapHeader, name, namespace, xmlns); - this.soapHeaders[index] = soapHeader; -}; - -Server.prototype.getSoapHeaders = function () { - return this.soapHeaders; -}; - -Server.prototype.clearSoapHeaders = function () { - this.soapHeaders = null; -}; - -Server.prototype._initializeOptions = function (options) { - this.wsdl.options.attributesKey = options.attributesKey || 'attributes'; - this.onewayOptions.statusCode = this.onewayOptions.responseCode || 200; - this.onewayOptions.emptyBody = !!this.onewayOptions.emptyBody; -}; - -Server.prototype._processRequestXml = function (req, res, xml) { - var self = this; - var result; - var error; - try { - if (typeof self.log === 'function') { - self.log("received", xml); - } - self._process(xml, req, function (result, statusCode) { - self._sendHttpResponse(res, statusCode, result); - if (typeof self.log === 'function') { - self.log("replied", result); - } - }); - } catch (err) { - if (err.Fault !== undefined) { - return self._sendError(err.Fault, function (result, statusCode) { - self._sendHttpResponse(res, statusCode || 500, result); - if (typeof self.log === 'function') { - self.log("error", err); - } - }, new Date().toISOString()); - } else { - error = err.stack ? (self.suppressStack === true ? err.message : err.stack) : err; - self._sendHttpResponse(res, /* statusCode */ 500, error); - if (typeof self.log === 'function') { - self.log("error", error); - } - } - } -}; - -Server.prototype._requestListener = function (req, res) { - var self = this; - var reqParse = url.parse(req.url); - var reqPath = reqParse.pathname; - var reqQuery = reqParse.search; - - if (typeof self.log === 'function') { - self.log("info", "Handling " + req.method + " on " + req.url); - } - - if (req.method === 'GET') { - if (reqQuery && reqQuery.toLowerCase() === '?wsdl') { - if (typeof self.log === 'function') { - self.log("info", "Wants the WSDL"); - } - res.setHeader("Content-Type", "application/xml"); - res.write(self.wsdl.toXML()); - } - res.end(); - } else if (req.method === 'POST') { - if (typeof req.headers['content-type'] !== "undefined") { - res.setHeader('Content-Type', req.headers['content-type']); - } else { - res.setHeader('Content-Type', "application/xml"); - } - - //request body is already provided by an express middleware - //in this case unzipping should also be done by the express middleware itself - if (req.body && Object.keys(req.body).length > 0) { - return self._processRequestXml(req, res, req.body.toString()); - } - - var chunks = [], gunzip, source = req; - if (req.headers["content-encoding"] === "gzip") { - gunzip = zlib.createGunzip(); - req.pipe(gunzip); - source = gunzip; - } - source.on('data', function (chunk) { - chunks.push(chunk); - }); - source.on('end', function () { - var xml = Buffer.concat(chunks).toString(); - var result; - var error; - self._processRequestXml(req, res, xml); - }); - } - else { - res.end(); - } -}; - -Server.prototype._process = function (input, req, callback) { - var self = this, - pathname = url.parse(req.url).pathname.replace(/\/$/, ''), - obj = this.wsdl.xmlToObject(input), - body = obj.Body, - headers = obj.Header, - bindings = this.wsdl.definitions.bindings, binding, - method, methodName, - serviceName, portName, - includeTimestamp = obj.Header && obj.Header.Security && obj.Header.Security.Timestamp, - authenticate = self.authenticate || function defaultAuthenticate() { return true; }; - - function process() { - - if (typeof self.log === 'function') { - self.log("info", "Attempting to bind to " + pathname); - } - - //Avoid Cannot convert undefined or null to object due to Object.keys(body) - //and throw more meaningful error - if (!body) { - throw new Error('Failed to parse the SOAP Message body'); - } - - // use port.location and current url to find the right binding - binding = (function () { - var services = self.wsdl.definitions.services; - var firstPort; - var name; - for (name in services) { - serviceName = name; - var service = services[serviceName]; - var ports = service.ports; - for (name in ports) { - portName = name; - var port = ports[portName]; - var portPathname = url.parse(port.location).pathname.replace(/\/$/, ''); - - if (typeof self.log === 'function') { - self.log("info", "Trying " + portName + " from path " + portPathname); - } - - if (portPathname === pathname) - return port.binding; - - // The port path is almost always wrong for generated WSDLs - if (!firstPort) { - firstPort = port; - } - } - } - return !firstPort ? void 0 : firstPort.binding; - })(); - - if (!binding) { - throw new Error('Failed to bind to WSDL'); - } - - try { - if (binding.style === 'rpc') { - methodName = Object.keys(body)[0]; - - self.emit('request', obj, methodName); - if (headers) - self.emit('headers', headers, methodName); - - self._executeMethod({ - serviceName: serviceName, - portName: portName, - methodName: methodName, - outputName: methodName + 'Response', - args: body[methodName], - headers: headers, - style: 'rpc' - }, req, callback); - } else { - 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); - if (headers) - self.emit('headers', headers, pair.methodName); - - self._executeMethod({ - serviceName: serviceName, - portName: portName, - methodName: pair.methodName, - outputName: pair.outputName, - args: body[messageElemName], - headers: headers, - style: 'document' - }, req, callback, includeTimestamp); - } - } - catch (error) { - if (error.Fault !== undefined) { - return self._sendError(error.Fault, callback, includeTimestamp); - } - - throw error; - } - } - - // Authentication - - if (typeof authenticate === 'function') { - - var authResultProcessed = false, - processAuthResult = function (authResult) { - - if (!authResultProcessed && (authResult || authResult === false)) { - - authResultProcessed = true; - - if (authResult) { - - try { - process(); - } catch (error) { - - if (error.Fault !== undefined) { - return self._sendError(error.Fault, callback, includeTimestamp); - } - - return self._sendError({ - Code: { - Value: 'SOAP-ENV:Server', - Subcode: { value: 'InternalServerError' } - }, - Reason: { Text: error.toString() }, - statusCode: 500 - }, callback, includeTimestamp); - } - - } else { - - return self._sendError({ - Code: { - Value: 'SOAP-ENV:Client', - Subcode: { value: 'AuthenticationFailure' } - }, - Reason: { Text: 'Invalid username or password' }, - statusCode: 401 - }, callback, includeTimestamp); - } - } - }; - - processAuthResult(authenticate(obj.Header && obj.Header.Security, processAuthResult)); - - } else { - throw new Error('Invalid authenticate function (not a function)'); - } -}; - -Server.prototype._executeMethod = function (options, req, callback, includeTimestamp) { - options = options || {}; - var self = this, - method, body, headers, - serviceName = options.serviceName, - portName = options.portName, - methodName = options.methodName, - outputName = options.outputName, - args = options.args, - style = options.style, - handled = false; - - if (this.soapHeaders) { - headers = this.soapHeaders.map(function(header) { - if (typeof header === 'function') { - return header(methodName, args, options.headers, req); - } else { - return header; - } - }).join("\n"); - } - - try { - method = this.services[serviceName][portName][methodName]; - } catch (error) { - return callback(this._envelope('', headers, includeTimestamp)); - } - - function handleResult(error, result) { - if (handled) - return; - handled = true; - - if (error && error.Fault !== undefined) { - return self._sendError(error.Fault, callback, includeTimestamp); - } - else if (result === undefined) { - // Backward compatibility to support one argument callback style - result = error; - } - - if (style === 'rpc') { - 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); - } - callback(self._envelope(body, headers, includeTimestamp)); - } - - if (!self.wsdl.definitions.services[serviceName].ports[portName].binding.methods[methodName].output) { - // no output defined = one-way operation so return empty response - handled = true; - body = ''; - if (this.onewayOptions.emptyBody) { - body = self._envelope('', headers, includeTimestamp); - } - callback(body, this.onewayOptions.responseCode); - } - - var result = method(args, handleResult, options.headers, req); - if (typeof result !== 'undefined') { - handleResult(result); - } -}; - -Server.prototype._envelope = function (body, headers, includeTimestamp) { - var defs = this.wsdl.definitions, - ns = defs.$targetNamespace, - encoding = '', - alias = findPrefix(defs.xmlns, ns); - - var envelopeDefinition = this.wsdl.options.forceSoap12Headers - ? "http://www.w3.org/2003/05/soap-envelope" - : "http://schemas.xmlsoap.org/soap/envelope/"; - - var xml = "" + - "'; - - headers = headers || ''; - - if (includeTimestamp) { - var now = new Date(); - var created = getDateString(now); - var expires = getDateString(new Date(now.getTime() + (1000 * 600))); - - headers += "" + - " " + - " " + created + "" + - " " + expires + "" + - " " + - " \n"; - } - - if (headers !== '') { - xml += "" + headers + ""; - } - - xml += body ? "" + body + "" : ""; - - xml += ""; - return xml; -}; - -Server.prototype._sendError = function (soapFault, callback, includeTimestamp) { - var self = this, - fault; - - var statusCode; - if (soapFault.statusCode) { - statusCode = soapFault.statusCode; - soapFault.statusCode = undefined; - } - - if (soapFault.faultcode) { - // Soap 1.1 error style - // Root element will be prependend with the soap NS - // It must match the NS defined in the Envelope (set by the _envelope method) - fault = self.wsdl.objectToDocumentXML("soap:Fault", soapFault, undefined); - } - else { - // Soap 1.2 error style. - // 3rd param is the NS prepended to all elements - // It must match the NS defined in the Envelope (set by the _envelope method) - fault = self.wsdl.objectToDocumentXML("Fault", soapFault, "soap"); - } - - return callback(self._envelope(fault, '', includeTimestamp), statusCode); -}; - -Server.prototype._sendHttpResponse = function (res, statusCode, result) { - if (statusCode) { - res.statusCode = statusCode; - } - - /* - * Calling res.write(result) follow by res.end() will cause Node.js to use - * chunked encoding, while calling res.end(result) directly will cause - * Node.js to calculate and send Content-Length header. See - * nodejs/node#26005. - */ - - if (this.enableChunkedEncoding) { - res.write(result); - res.end(); - } - else { - res.end(result); - } -} - -exports.Server = Server; diff --git a/lib/soap.d.ts b/lib/soap.d.ts deleted file mode 100644 index 09e75223c..000000000 --- a/lib/soap.d.ts +++ /dev/null @@ -1,332 +0,0 @@ -/// - -import { EventEmitter } from 'events'; -import * as BluebirdPromise from 'bluebird'; - -export interface ISoapMethod { - (args: any, callback: (err: any, result: any, raw: any, soapHeader: any) => void, options?: any, extraHeaders?: any): void; -} - -export interface ISoapServiceMethod { - (args:any, callback?: (data: any) => void, headers?: any, req?: any): any; -} - -// SOAP Fault 1.1 & 1.2 -export type ISoapFault = ISoapFault12 | ISoapFault11; - -// SOAP Fault 1.1 -export interface ISoapFault11 { - Fault: { - faultcode: number | string; - faultstring: string; - detail?: string; - statusCode?: number; - }; -} - -// SOAP Fault 1.2 -// 1.2 also supports additional, optional elements: -// Role, Node, Detail. Should be added when soap module implements them -// https://www.w3.org/TR/soap12/#soapfault -export interface ISoapFault12 { - Fault: { - Code: { Value: string; Subcode?: { Value: string; }; }; - Reason: { Text: string; }; - statusCode?: number; - }; -} - -export interface ISecurity { - addOptions(options: any): void; - toXML(): string; -} - -export interface IServicePort { - [methodName: string]: ISoapServiceMethod; -} - -export interface IService { - [portName: string]: IServicePort; -} - -export interface IServices { - [serviceName: string]: IService; -} - -export interface IXmlAttribute { - name: string; - value: string; -} - -export interface IWsdlBaseOptions { - attributesKey?: string; - valueKey?: string; - xmlKey?: string; - overrideRootElement?: { namespace: string; xmlnsAttributes?: IXmlAttribute[]; }; - ignoredNamespaces?: boolean | string[] | { namespaces?: string[]; override?: boolean; }; - ignoreBaseNameSpaces?: boolean; - escapeXML?: boolean; - returnFault?: boolean; - handleNilAsNull?: boolean; - wsdl_headers?: { [key: string]: any }; - wsdl_options?: { [key: string]: any }; -} - -export interface IOptions extends IWsdlBaseOptions { - disableCache?: boolean; - endpoint?: string; - envelopeKey?: string; - httpClient?: HttpClient; - request?: (options: any, callback?: (error: any, res: any, body: any) => void) => void; - stream?: boolean; - // wsdl options that only work for client - forceSoap12Headers?: boolean; - customDeserializer?: any; - [key: string]: any; -} - -export interface IOneWayOptions { - responseCode?: number; - emptyBody?: boolean; -} - -export interface IServerOptions extends IWsdlBaseOptions { - path: string; - services: IServices; - xml?: string; - uri?: string; - suppressStack?: boolean; - oneWay?: IOneWayOptions; - enableChunkedEncoding?: boolean; - [key: string]: any; -} - -export interface Definitions { - descriptions: object; - ignoredNamespaces: string[]; - messages: WsdlMessages; - portTypes: WsdlPortTypes; - bindings: WsdlBindings; - services: WsdlServices; - schemas: WsdlSchemas; - valueKey: string; - xmlKey: string; - xmlns: WsdlXmlns; - '$targetNamespace': string; - '$name': string; -} - -export interface XsdTypeBase { - ignoredNamespaces: string[]; - valueKey: string; - xmlKey: string; - xmlns?: WsdlXmlns, -} - - -export interface WsdlSchemas { - [prop: string]: WsdlSchema; -} -export interface WsdlSchema extends XsdTypeBase { - children: any[]; - complexTypes?: WsdlElements; - elements?: WsdlElements; - includes: any[]; - name: string; - nsName: string; - prefix: string; - types?: WsdlElements; - xmlns: WsdlXmlns; -} -export interface WsdlElements { - [prop: string]: XsdElement; -} -export type XsdElement = XsdElementType | XsdComplexType; - -export interface WsdlXmlns { - wsu?: string; - wsp?: string; - wsam?: string; - soap?: string; - tns?: string; - xsd?: string; - __tns__?: string; - [prop: string]: string | void; -} - -export interface XsdComplexType extends XsdTypeBase { - children: XsdElement[] | void; - name: string; - nsName: string; - prefix: string; - '$name': string; - [prop: string]: any; -} - -export interface XsdElementType extends XsdTypeBase { - children: XsdElement[] | void; - name: string; - nsName: string; - prefix: string; - targetNSAlias: string; - targetNamespace: string; - '$lookupType': string; - '$lookupTypes': any[]; - '$name': string; - '$type': string; - [prop: string]: any; -} - -export interface WsdlMessages { - [prop: string]: WsdlMessage; -} -export interface WsdlMessage extends XsdTypeBase { - element: XsdElement; - parts: { [prop: string]: any }; - '$name': string; -} - -export interface WsdlPortTypes { - [prop: string]: WsdlPortType; -} -export interface WsdlPortType extends XsdTypeBase { - methods: { [prop: string]: XsdElement } -} - -export interface WsdlBindings { - [prop: string]: WsdlBinding; -} -export interface WsdlBinding extends XsdTypeBase { - methods: WsdlElements; - style: string; - transport: string; - topElements: {[prop: string]: any}; -} - -export interface WsdlServices { - [prop: string]: WsdlService; -} -export interface WsdlService extends XsdTypeBase { - ports: {[prop: string]: any}; -} - -export class WSDL { - constructor(definition: any, uri: string, options?: IOptions); - ignoredNamespaces: string[]; - ignoreBaseNameSpaces: boolean; - valueKey: string; - xmlKey: string; - xmlnsInEnvelope: string; - onReady(callback: (err:Error) => void): void; - processIncludes(callback: (err:Error) => void): void; - describeServices(): { [k: string]: any }; - toXML(): string; - xmlToObject(xml: any, callback?: (err:Error, result:any) => void): any; - findSchemaObject(nsURI: string, qname: string): XsdElement | null | undefined; - objectToDocumentXML(name: string, params: any, nsPrefix?: string, nsURI?: string, type?: string): any; - objectToRpcXML(name: string, params: any, nsPrefix?: string, nsURI?: string, isParts?: any): string; - isIgnoredNameSpace(ns: string): boolean; - filterOutIgnoredNameSpace(ns: string): string; - objectToXML(obj: any, name: string, nsPrefix?: any, nsURI?: string, isFirst?: boolean, xmlnsAttr?: any, schemaObject?: any, nsContext?: any): string; - processAttributes(child: any, nsContext: any): string; - findSchemaType(name: any, nsURI: any): any; - findChildSchemaObject(parameterTypeObj: any, childName: any, backtrace?: any): any; - uri: string; - definitions: Definitions; -} - -export class Client extends EventEmitter { - constructor(wsdl: WSDL, endpoint?: string, options?: IOptions); - addBodyAttribute(bodyAttribute: any, name?: string, namespace?: string, xmlns?: string): void; - addHttpHeader(name: string, value: any): void; - addSoapHeader(soapHeader: any, name?: string, namespace?: any, xmlns?: string): number; - changeSoapHeader(index: number, soapHeader: any, name?: string, namespace?: string, xmlns?: string): void; - clearBodyAttributes(): void; - clearHttpHeaders(): void; - clearSoapHeaders(): void; - describe(): any; - getBodyAttributes(): any[]; - getHttpHeaders(): { [k:string]: string }; - getSoapHeaders(): string[]; - setEndpoint(endpoint: string): void; - setSOAPAction(action: string): void; - setSecurity(security: ISecurity): void; - wsdl: WSDL; - [method: string]: ISoapMethod | WSDL | Function; -} - -export function createClient(url: string, callback: (err: any, client: Client) => void): void; -export function createClient(url: string, options: IOptions, callback: (err: any, client: Client) => void): void; -export function createClientAsync(url: string, options?: IOptions, endpoint?: string): BluebirdPromise; - -export class Server extends EventEmitter { - constructor(server: any, path: string, services: IServices, wsdl: WSDL, options: IServerOptions); - path: string; - services: IServices; - wsdl: WSDL; - addSoapHeader(soapHeader: any, name?: string, namespace?: any, xmlns?: string): number; - changeSoapHeader(index: any, soapHeader: any, name?: any, namespace?: any, xmlns?: any): void; - getSoapHeaders(): string[]; - clearSoapHeaders(): void; - log(type: string, data: any): any; - authorizeConnection(req: any): boolean; - authenticate(security: ISecurity): boolean; -} - -export function listen(server: any, path: string, service: any, wsdl: string): Server; -export function listen(server: any, options: IServerOptions): Server; - -export class HttpClient { - constructor(options?: IOptions); - buildRequest(rurl: string, data: any | string, exheaders?: { [key: string]: any }, exoptions?: { [key: string]: any }): any; - handleResponse(req: any, res: any, body: any | string): any | string; - request(rurl: string, data: any | string, callback: (err: any, res: any, body: any | string) => void, exheaders?: { [key: string]: any }, exoptions?: { [key: string]: any }): any; - requestStream(rurl: string, data: any | string, exheaders?: { [key: string]: any }, exoptions?: { [key: string]: any }): any; -} - -export class BasicAuthSecurity implements ISecurity { - constructor(username: string, password: string, defaults?: any); - addHeaders(headers: any): void; - addOptions(options: any): void; - toXML(): string; -} - -export class BearerSecurity implements ISecurity { - constructor(token: string, defaults?: any); - addHeaders(headers: any): void; - addOptions(options: any): void; - toXML(): string; -} - -export class WSSecurity implements ISecurity { - constructor(username: string, password: string, options?: any); - addOptions(options: any): void; - toXML(): string; -} - -export class WSSecurityCert implements ISecurity { - constructor(privatePEM: any, publicP12PEM: any, password: any, options?: any); - addOptions(options: any): void; - toXML(): string; -} - -export class ClientSSLSecurity implements ISecurity { - constructor(key: string | Buffer, cert: string | Buffer, ca?: string | any[] | Buffer, defaults?: any); - constructor(key: string | Buffer, cert: string | Buffer, defaults?: any); - addOptions(options: any): void; - toXML(): string; -} - -export class ClientSSLSecurityPFX implements ISecurity { - constructor(pfx: string | Buffer, passphrase: string, defaults?: any); - constructor(pfx: string | Buffer, defaults?: any); - addOptions(options: any): void; - toXML(): string; -} - -export function passwordDigest(nonce: string, created: string, password: string): string; - -// Below are added for backwards compatibility for previous @types/soap users. -export interface Security extends ISecurity {} -export interface SoapMethod extends ISoapMethod {} -export interface Option extends IOptions {} diff --git a/lib/soap.js b/lib/soap.js deleted file mode 100644 index 4a2d36380..000000000 --- a/lib/soap.js +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (c) 2011 Vinay Pulim - * MIT Licensed - */ - -"use strict"; - -var Client = require('./client').Client, - Server = require('./server').Server, - HttpClient = require('./http'), - security = require('./security'), - passwordDigest = require('./utils').passwordDigest, - BluebirdPromise = require('bluebird'), - wsdl = require('./wsdl'), - WSDL = require('./wsdl').WSDL; - -function createCache() { - var cache = {}; - return function (key, load, callback) { - if (!cache[key]) { - load(function (err, result) { - if (err) { - return callback(err); - } - cache[key] = result; - callback(null, result); - }); - } else { - process.nextTick(function () { - callback(null, cache[key]); - }); - } - }; -} -var getFromCache = createCache(); - -function _requestWSDL(url, options, callback) { - if (typeof options === 'function') { - callback = options; - options = {}; - } - var openWsdl = wsdl.open_wsdl.bind(null, url, options); - - if (options.disableCache === true) { - openWsdl(callback); - } else { - getFromCache(url, openWsdl, callback); - } -} - -function createClient(url, options, callback, endpoint) { - if (typeof options === 'function') { - endpoint = callback; - callback = options; - options = {}; - } - endpoint = options.endpoint || endpoint; - _requestWSDL(url, options, function(err, wsdl) { - callback(err, wsdl && new Client(wsdl, endpoint, options)); - }); -} - -function createClientAsync(url, options, endpoint) { - if (typeof options === 'undefined') { - options = {}; - } - return new BluebirdPromise(function(resolve, reject) { - createClient(url, options, function(err, client) { - if (err) { - reject(err); - } - resolve(client); - }, endpoint); - }); -} - -function listen(server, pathOrOptions, services, xml) { - var options = {}, - path = pathOrOptions, - uri = ""; - - if (typeof pathOrOptions === 'object') { - options = pathOrOptions; - path = options.path; - services = options.services; - xml = options.xml; - uri = options.uri; - } - - var wsdl = new WSDL(xml || services, uri, options); - return new Server(server, path, services, wsdl, options); -} - -exports.security = security; -exports.BasicAuthSecurity = security.BasicAuthSecurity; -exports.NTLMSecurity = security.NTLMSecurity; -exports.WSSecurity = security.WSSecurity; -exports.WSSecurityCert = security.WSSecurityCert; -exports.ClientSSLSecurity = security.ClientSSLSecurity; -exports.ClientSSLSecurityPFX = security.ClientSSLSecurityPFX; -exports.BearerSecurity = security.BearerSecurity; -exports.createClient = createClient; -exports.createClientAsync = createClientAsync; -exports.passwordDigest = passwordDigest; -exports.listen = listen; -exports.WSDL = WSDL; - -// Export Client and Server to allow customization -exports.Server = Server; -exports.Client = Client; -exports.HttpClient = HttpClient; diff --git a/lib/utils.js b/lib/utils.js deleted file mode 100644 index ea2057ad0..000000000 --- a/lib/utils.js +++ /dev/null @@ -1,35 +0,0 @@ - -"use strict"; -var crypto = require('crypto'); -exports.passwordDigest = function passwordDigest(nonce, created, password) { - // digest = base64 ( sha1 ( nonce + created + password ) ) - var pwHash = crypto.createHash('sha1'); - - var NonceBytes = Buffer.from(nonce || '', 'base64'); - var CreatedBytes = Buffer.from(created || '','utf8'); - var PasswordBytes = Buffer.from(password || '', 'utf8'); - var FullBytes = Buffer.concat([NonceBytes, CreatedBytes, PasswordBytes ]); - - pwHash.update(FullBytes); - return pwHash.digest('base64'); -}; - - -var TNS_PREFIX = '__tns__'; // Prefix for targetNamespace - -exports.TNS_PREFIX = TNS_PREFIX; - -/** - * Find a key from an object based on the value - * @param {Object} Namespace prefix/uri mapping - * @param {*} nsURI value - * @returns {String} The matching key - */ -exports.findPrefix = function(xmlnsMapping, nsURI) { - for (var n in xmlnsMapping) { - if (n === TNS_PREFIX) continue; - if (xmlnsMapping[n] === nsURI) { - return n; - } - } -}; diff --git a/lib/wsdl.js b/lib/wsdl.js deleted file mode 100644 index 5119580fb..000000000 --- a/lib/wsdl.js +++ /dev/null @@ -1,2329 +0,0 @@ -/* - * Copyright (c) 2011 Vinay Pulim - * MIT Licensed - * - */ -/*jshint proto:true*/ - -"use strict"; - -var sax = require('sax'); -var inherits = require('util').inherits; -var HttpClient = require('./http'); -var NamespaceContext = require('./nscontext'); -var fs = require('fs'); -var url = require('url'); -var path = require('path'); -var assert = require('assert').ok; -var stripBom = require('strip-bom'); -var debug = require('debug')('node-soap'); -var _ = require('lodash'); -var utils = require('./utils'); -var TNS_PREFIX = utils.TNS_PREFIX; -var findPrefix = utils.findPrefix; - -var Primitives = { - string: 1, - boolean: 1, - decimal: 1, - float: 1, - double: 1, - anyType: 1, - byte: 1, - int: 1, - long: 1, - short: 1, - negativeInteger: 1, - nonNegativeInteger: 1, - positiveInteger: 1, - nonPositiveInteger:1, - unsignedByte: 1, - unsignedInt: 1, - unsignedLong: 1, - unsignedShort: 1, - duration: 0, - dateTime: 0, - time: 0, - date: 0, - gYearMonth: 0, - gYear: 0, - gMonthDay: 0, - gDay: 0, - gMonth: 0, - hexBinary: 0, - base64Binary: 0, - anyURI: 0, - QName: 0, - NOTATION: 0 -}; - -function splitQName(nsName) { - if(typeof nsName !== 'string') { - return { - prefix: TNS_PREFIX, - name: nsName, - }; - } - - const [topLevelName] = nsName.split('|'); - - const prefixOffset = topLevelName.indexOf(':'); - - return { - prefix: topLevelName.substring(0, prefixOffset) || TNS_PREFIX, - name: topLevelName.substring(prefixOffset + 1) - }; -} - -function xmlEscape(obj) { - if (typeof (obj) === 'string') { - if (obj.substr(0,9) === '") { - return obj; - } - return obj - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"') - .replace(/'/g, '''); - } - - return obj; -} - -var trimLeft = /^[\s\xA0]+/; -var trimRight = /[\s\xA0]+$/; - -function trim(text) { - return text.replace(trimLeft, '').replace(trimRight, ''); -} - -function deepMerge(destination, source) { - return _.mergeWith(destination || {}, source, function(a, b) { - return _.isArray(a) ? a.concat(b) : undefined; - }); -} - -var Element = function(nsName, attrs, options, schemaAttrs) { - var parts = splitQName(nsName); - - this.nsName = nsName; - this.prefix = parts.prefix; - this.name = parts.name; - this.children = []; - this.xmlns = {}; - this.schemaXmlns = {}; - - this._initializeOptions(options); - - for (var key in attrs) { - var match = /^xmlns:?(.*)$/.exec(key); - if (match) { - this.xmlns[match[1] ? match[1] : TNS_PREFIX] = attrs[key]; - } - else { - if(key === 'value') { - this[this.valueKey] = attrs[key]; - } else { - this['$' + key] = attrs[key]; - } - } - } - for (var schemaKey in schemaAttrs) { - var schemaMatch = /^xmlns:?(.*)$/.exec(schemaKey); - if (schemaMatch && schemaMatch[1]) { - this.schemaXmlns[schemaMatch[1]] = schemaAttrs[schemaKey]; - } - } - if (this.$targetNamespace !== undefined) { - // Add targetNamespace to the mapping - this.xmlns[TNS_PREFIX] = this.$targetNamespace; - } -}; - -Element.prototype._initializeOptions = function (options) { - if(options) { - this.valueKey = options.valueKey || '$value'; - this.xmlKey = options.xmlKey || '$xml'; - this.ignoredNamespaces = options.ignoredNamespaces || []; - } else { - this.valueKey = '$value'; - this.xmlKey = '$xml'; - this.ignoredNamespaces = []; - } -}; - -Element.prototype.deleteFixedAttrs = function() { - this.children && this.children.length === 0 && delete this.children; - this.xmlns && Object.keys(this.xmlns).length === 0 && delete this.xmlns; - delete this.nsName; - delete this.prefix; - delete this.name; -}; - -Element.prototype.allowedChildren = []; - -Element.prototype.startElement = function(stack, nsName, attrs, options, schemaXmlns) { - if (!this.allowedChildren) { - return; - } - - var ChildClass = this.allowedChildren[splitQName(nsName).name], - element = null; - - if (ChildClass) { - stack.push(new ChildClass(nsName, attrs, options, schemaXmlns)); - } - else { - this.unexpected(nsName); - } - -}; - -Element.prototype.endElement = function(stack, nsName) { - if (this.nsName === nsName) { - if (stack.length < 2) - return; - var parent = stack[stack.length - 2]; - if (this !== stack[0]) { - _.defaultsDeep(stack[0].xmlns, this.xmlns); - // delete this.xmlns; - parent.children.push(this); - parent.addChild(this); - } - stack.pop(); - } -}; - -Element.prototype.addChild = function(child) { - return; -}; - -Element.prototype.unexpected = function(name) { - throw new Error('Found unexpected element (' + name + ') inside ' + this.nsName); -}; - -Element.prototype.description = function(definitions) { - return this.$name || this.name; -}; - -Element.prototype.init = function() { -}; - -Element.createSubClass = function() { - var root = this; - var subElement = function() { - root.apply(this, arguments); - this.init(); - }; - // inherits(subElement, root); - subElement.prototype.__proto__ = root.prototype; - return subElement; -}; - - -var ElementElement = Element.createSubClass(); -var AnyElement = Element.createSubClass(); -var InputElement = Element.createSubClass(); -var OutputElement = Element.createSubClass(); -var SimpleTypeElement = Element.createSubClass(); -var RestrictionElement = Element.createSubClass(); -var ExtensionElement = Element.createSubClass(); -var ChoiceElement = Element.createSubClass(); -var EnumerationElement = Element.createSubClass(); -var ComplexTypeElement = Element.createSubClass(); -var ComplexContentElement = Element.createSubClass(); -var SimpleContentElement = Element.createSubClass(); -var SequenceElement = Element.createSubClass(); -var AllElement = Element.createSubClass(); -var MessageElement = Element.createSubClass(); -var DocumentationElement = Element.createSubClass(); - -var SchemaElement = Element.createSubClass(); -var TypesElement = Element.createSubClass(); -var OperationElement = Element.createSubClass(); -var PortTypeElement = Element.createSubClass(); -var BindingElement = Element.createSubClass(); -var PortElement = Element.createSubClass(); -var ServiceElement = Element.createSubClass(); -var DefinitionsElement = Element.createSubClass(); - -var ElementTypeMap = { - types: [TypesElement, 'schema documentation'], - schema: [SchemaElement, 'element complexType simpleType include import'], - element: [ElementElement, 'annotation complexType simpleType'], - any: [AnyElement, ''], - simpleType: [SimpleTypeElement, 'restriction'], - restriction: [RestrictionElement, 'enumeration all choice sequence'], - extension: [ExtensionElement, 'all sequence choice'], - choice: [ChoiceElement, 'element sequence choice any'], - // group: [GroupElement, 'element group'], - enumeration: [EnumerationElement, ''], - complexType: [ComplexTypeElement, 'annotation sequence all complexContent simpleContent choice'], - complexContent: [ComplexContentElement, 'extension'], - simpleContent: [SimpleContentElement, 'extension'], - sequence: [SequenceElement, 'element sequence choice any'], - all: [AllElement, 'element choice'], - - service: [ServiceElement, 'port documentation'], - port: [PortElement, 'address documentation'], - binding: [BindingElement, '_binding SecuritySpec operation documentation'], - portType: [PortTypeElement, 'operation documentation'], - message: [MessageElement, 'part documentation'], - operation: [OperationElement, 'documentation input output fault _operation'], - input: [InputElement, 'body SecuritySpecRef documentation header'], - output: [OutputElement, 'body SecuritySpecRef documentation header'], - fault: [Element, '_fault documentation'], - definitions: [DefinitionsElement, 'types message portType binding service import documentation'], - documentation: [DocumentationElement, ''] -}; - -function mapElementTypes(types) { - var rtn = {}; - types = types.split(' '); - types.forEach(function(type) { - rtn[type.replace(/^_/, '')] = (ElementTypeMap[type] || [Element]) [0]; - }); - return rtn; -} - -for (var n in ElementTypeMap) { - var v = ElementTypeMap[n]; - v[0].prototype.allowedChildren = mapElementTypes(v[1]); -} - -MessageElement.prototype.init = function() { - this.element = null; - this.parts = null; -}; - -SchemaElement.prototype.init = function() { - this.complexTypes = {}; - this.types = {}; - this.elements = {}; - this.includes = []; -}; - -TypesElement.prototype.init = function() { - this.schemas = {}; -}; - -OperationElement.prototype.init = function() { - this.input = null; - this.output = null; - this.inputSoap = null; - this.outputSoap = null; - this.style = ''; - this.soapAction = ''; -}; - -PortTypeElement.prototype.init = function() { - this.methods = {}; -}; - -BindingElement.prototype.init = function() { - this.transport = ''; - this.style = ''; - this.methods = {}; -}; - -PortElement.prototype.init = function() { - this.location = null; -}; - -ServiceElement.prototype.init = function() { - this.ports = {}; -}; - -DefinitionsElement.prototype.init = function() { - if (this.name !== 'definitions')this.unexpected(this.nsName); - this.messages = {}; - this.portTypes = {}; - this.bindings = {}; - this.services = {}; - this.schemas = {}; -}; - -DocumentationElement.prototype.init = function() { -}; - -SchemaElement.prototype.merge = function(source) { - assert(source instanceof SchemaElement); - - var self = this; - - _.merge(this.complexTypes, source.complexTypes); - _.merge(this.types, source.types); - _.merge(this.elements, source.elements); - _.merge(this.xmlns, source.xmlns); - - // Merge attributes from source without overwriting our's - _.merge(this, _.pickBy(source, function(value, key) { - return key.startsWith('$') && !self.hasOwnProperty(key); - })); - - return this; -}; - - -SchemaElement.prototype.addChild = function(child) { - if (child.$name in Primitives) - return; - if (child.name === 'include' || child.name === 'import') { - var location = child.$schemaLocation || child.$location; - if (location) { - this.includes.push({ - namespace: child.$namespace || child.$targetNamespace || this.$targetNamespace, - location: location - }); - } - } - else if (child.name === 'complexType') { - this.complexTypes[child.$name] = child; - } - else if (child.name === 'element') { - this.elements[child.$name] = child; - } - else if (child.$name) { - this.types[child.$name] = child; - } - this.children.pop(); - // child.deleteFixedAttrs(); -}; -//fix#325 -TypesElement.prototype.addChild = function (child) { - assert(child instanceof SchemaElement); - - var targetNamespace = child.$targetNamespace; - - if(!this.schemas.hasOwnProperty(targetNamespace)) { - this.schemas[targetNamespace] = child; - } else { - console.error('Target-Namespace "'+ targetNamespace +'" already in use by another Schema!'); - } -}; - -InputElement.prototype.addChild = function(child) { - if (child.name === 'body') { - this.use = child.$use; - if (this.use === 'encoded') { - this.encodingStyle = child.$encodingStyle; - } - this.children.pop(); - } -}; - -OutputElement.prototype.addChild = function(child) { - if (child.name === 'body') { - this.use = child.$use; - if (this.use === 'encoded') { - this.encodingStyle = child.$encodingStyle; - } - this.children.pop(); - } -}; - -OperationElement.prototype.addChild = function(child) { - if (child.name === 'operation') { - this.soapAction = child.$soapAction || ''; - this.style = child.$style || ''; - this.children.pop(); - } -}; - -BindingElement.prototype.addChild = function(child) { - if (child.name === 'binding') { - this.transport = child.$transport; - this.style = child.$style; - this.children.pop(); - } -}; - -PortElement.prototype.addChild = function(child) { - if (child.name === 'address' && typeof (child.$location) !== 'undefined') { - this.location = child.$location; - } -}; - -DefinitionsElement.prototype.addChild = function(child) { - var self = this; - if (child instanceof TypesElement) { - // Merge types.schemas into definitions.schemas - _.merge(self.schemas, child.schemas); - } - else if (child instanceof MessageElement) { - self.messages[child.$name] = child; - } - else if (child.name === 'import') { - self.schemas[child.$namespace] = new SchemaElement(child.$namespace, {}); - self.schemas[child.$namespace].addChild(child); - } - else if (child instanceof PortTypeElement) { - self.portTypes[child.$name] = child; - } - else if (child instanceof BindingElement) { - if (child.transport === 'http://schemas.xmlsoap.org/soap/http' || - child.transport === 'http://www.w3.org/2003/05/soap/bindings/HTTP/') - self.bindings[child.$name] = child; - } - else if (child instanceof ServiceElement) { - self.services[child.$name] = child; - } - else if (child instanceof DocumentationElement) { - } - this.children.pop(); -}; - -MessageElement.prototype.postProcess = function(definitions) { - var part = null; - var child; - var children = this.children || []; - var ns; - var nsName; - var i; - var type; - - for (i in children) { - if ((child = children[i]).name === 'part') { - part = child; - break; - } - } - - if (!part) { - return; - } - - if (part.$element) { - var lookupTypes = [], - elementChildren ; - - delete this.parts; - - nsName = splitQName(part.$element); - ns = nsName.prefix; - var schema = definitions.schemas[definitions.xmlns[ns]]; - this.element = schema.elements[nsName.name]; - if(!this.element) { - debug(nsName.name + " is not present in wsdl and cannot be processed correctly."); - return; - } - this.element.targetNSAlias = ns; - this.element.targetNamespace = definitions.xmlns[ns]; - - // set the optional $lookupType to be used within `client#_invoke()` when - // calling `wsdl#objectToDocumentXML() - this.element.$lookupType = part.$element; - - elementChildren = this.element.children; - - // get all nested lookup types (only complex types are followed) - if (elementChildren.length > 0) { - for (i = 0; i < elementChildren.length; i++) { - lookupTypes.push(this._getNestedLookupTypeString(elementChildren[i])); - } - } - - // if nested lookup types where found, prepare them for furter usage - if (lookupTypes.length > 0) { - lookupTypes = lookupTypes. - join('_'). - split('_'). - filter(function removeEmptyLookupTypes (type) { - return type !== '^'; - }); - - var schemaXmlns = definitions.schemas[this.element.targetNamespace].xmlns; - - for (i = 0; i < lookupTypes.length; i++) { - lookupTypes[i] = this._createLookupTypeObject(lookupTypes[i], schemaXmlns); - } - } - - this.element.$lookupTypes = lookupTypes; - - if (this.element.$type) { - type = splitQName(this.element.$type); - var typeNs = schema.xmlns && schema.xmlns[type.prefix] || definitions.xmlns[type.prefix]; - - if (typeNs) { - if (type.name in Primitives) { - // this.element = this.element.$type; - } - else { - // first check local mapping of ns alias to namespace - schema = definitions.schemas[typeNs]; - var ctype = schema.complexTypes[type.name] || schema.types[type.name] || schema.elements[type.name]; - - - if (ctype) { - this.parts = ctype.description(definitions, schema.xmlns); - } - } - } - } - else { - var method = this.element.description(definitions, schema.xmlns); - this.parts = method[nsName.name]; - } - - - this.children.splice(0, 1); - } else { - // rpc encoding - this.parts = {}; - delete this.element; - for (i = 0; part = this.children[i]; i++) { - if (part.name === 'documentation') { - // - continue; - } - assert(part.name === 'part', 'Expected part element'); - nsName = splitQName(part.$type); - ns = definitions.xmlns[nsName.prefix]; - type = nsName.name; - var schemaDefinition = definitions.schemas[ns]; - if (typeof schemaDefinition !== 'undefined') { - this.parts[part.$name] = definitions.schemas[ns].types[type] || definitions.schemas[ns].complexTypes[type]; - } else { - this.parts[part.$name] = part.$type; - } - - if (typeof this.parts[part.$name] === 'object') { - this.parts[part.$name].prefix = nsName.prefix; - this.parts[part.$name].xmlns = ns; - } - - this.children.splice(i--, 1); - } - } - this.deleteFixedAttrs(); -}; - -/** - * Takes a given namespaced String(for example: 'alias:property') and creates a lookupType - * object for further use in as first (lookup) `parameterTypeObj` within the `objectToXML` - * method and provides an entry point for the already existing code in `findChildSchemaObject`. - * - * @method _createLookupTypeObject - * @param {String} nsString The NS String (for example "alias:type"). - * @param {Object} xmlns The fully parsed `wsdl` definitions object (including all schemas). - * @returns {Object} - * @private - */ -MessageElement.prototype._createLookupTypeObject = function (nsString, xmlns) { - var splittedNSString = splitQName(nsString), - nsAlias = splittedNSString.prefix, - splittedName = splittedNSString.name.split('#'), - type = splittedName[0], - name = splittedName[1], - lookupTypeObj = {}; - - lookupTypeObj.$namespace = xmlns[nsAlias]; - lookupTypeObj.$type = nsAlias + ':' + type; - lookupTypeObj.$name = name; - - return lookupTypeObj; -}; - -/** - * Iterates through the element and every nested child to find any defined `$type` - * property and returns it in a underscore ('_') separated String (using '^' as default - * value if no `$type` property was found). - * - * @method _getNestedLookupTypeString - * @param {Object} element The element which (probably) contains nested `$type` values. - * @returns {String} - * @private - */ -MessageElement.prototype._getNestedLookupTypeString = function (element) { - var resolvedType = '^', - excluded = this.ignoredNamespaces.concat('xs'); // do not process $type values wich start with - - if (element.hasOwnProperty('$type') && typeof element.$type === 'string') { - if (excluded.indexOf(element.$type.split(':')[0]) === -1) { - resolvedType += ('_' + element.$type + '#' + element.$name); - } - } - - if (element.children.length > 0) { - var self = this; - - element.children.forEach(function (child) { - var resolvedChildType = self._getNestedLookupTypeString(child).replace(/\^_/, ''); - - if (resolvedChildType && typeof resolvedChildType === 'string') { - resolvedType += ('_' + resolvedChildType); - } - }); - } - - return resolvedType; -}; - -OperationElement.prototype.postProcess = function(definitions, tag) { - var children = this.children; - for (var i = 0, child; child = children[i]; i++) { - if (child.name !== 'input' && child.name !== 'output') - continue; - if (tag === 'binding') { - this[child.name] = child; - children.splice(i--, 1); - continue; - } - var messageName = splitQName(child.$message).name; - var message = definitions.messages[messageName]; - message.postProcess(definitions); - if (message.element) { - definitions.messages[message.element.$name] = message; - this[child.name] = message.element; - } - else { - this[child.name] = message; - } - children.splice(i--, 1); - } - this.deleteFixedAttrs(); -}; - -PortTypeElement.prototype.postProcess = function(definitions) { - var children = this.children; - if (typeof children === 'undefined') - return; - for (var i = 0, child; child = children[i]; i++) { - if (child.name !== 'operation') - continue; - child.postProcess(definitions, 'portType'); - this.methods[child.$name] = child; - children.splice(i--, 1); - } - delete this.$name; - this.deleteFixedAttrs(); -}; - -BindingElement.prototype.postProcess = function(definitions) { - var type = splitQName(this.$type).name, - portType = definitions.portTypes[type], - style = this.style, - children = this.children; - if (portType){ - portType.postProcess(definitions); - this.methods = portType.methods; - - for (var i = 0, child; child = children[i]; i++) { - if (child.name !== 'operation') - continue; - child.postProcess(definitions, 'binding'); - children.splice(i--, 1); - child.style || (child.style = style); - var method = this.methods[child.$name]; - - if (method) { - method.style = child.style; - method.soapAction = child.soapAction; - method.inputSoap = child.input || null; - method.outputSoap = child.output || null; - method.inputSoap && method.inputSoap.deleteFixedAttrs(); - method.outputSoap && method.outputSoap.deleteFixedAttrs(); - } - } - } - delete this.$name; - delete this.$type; - this.deleteFixedAttrs(); -}; - -ServiceElement.prototype.postProcess = function(definitions) { - var children = this.children, - bindings = definitions.bindings; - if (children && children.length > 0) { - for (var i = 0, child; child = children[i]; i++) { - if (child.name !== 'port') - continue; - var bindingName = splitQName(child.$binding).name; - var binding = bindings[bindingName]; - if (binding) { - binding.postProcess(definitions); - this.ports[child.$name] = { - location: child.location, - binding: binding - }; - children.splice(i--, 1); - } - } - } - delete this.$name; - this.deleteFixedAttrs(); -}; - - -SimpleTypeElement.prototype.description = function(definitions) { - var children = this.children; - for (var i = 0, child; child = children[i]; i++) { - if (child instanceof RestrictionElement) - return [this.$name, child.description()].filter(Boolean).join('|'); - } - return {}; -}; - -RestrictionElement.prototype.description = function(definitions, xmlns) { - var children = this.children; - var desc; - for (var i=0, child; child=children[i]; i++) { - if (child instanceof SequenceElement || - child instanceof ChoiceElement) { - desc = child.description(definitions, xmlns); - break; - } - } - if (desc && this.$base) { - var type = splitQName(this.$base), - typeName = type.name, - ns = xmlns && xmlns[type.prefix] || definitions.xmlns[type.prefix], - schema = definitions.schemas[ns], - typeElement = schema && ( schema.complexTypes[typeName] || schema.types[typeName] || schema.elements[typeName] ); - - desc.getBase = function() { - return typeElement.description(definitions, schema.xmlns); - }; - return desc; - } - - // then simple element - var base = this.$base ? this.$base + "|" : ""; - var restrictions = this.children.map(function(child) { - return child.description(); - }).join(","); - - return [this.$base, restrictions].filter(Boolean).join('|'); -}; - -ExtensionElement.prototype.description = function(definitions, xmlns) { - var children = this.children; - var desc = {}; - for (var i=0, child; child=children[i]; i++) { - if (child instanceof SequenceElement || - child instanceof ChoiceElement) { - desc = child.description(definitions, xmlns); - } - } - if (this.$base) { - var type = splitQName(this.$base), - typeName = type.name, - ns = xmlns && xmlns[type.prefix] || definitions.xmlns[type.prefix], - schema = definitions.schemas[ns]; - - if (typeName in Primitives) { - return this.$base; - } - else { - var typeElement = schema && ( schema.complexTypes[typeName] || - schema.types[typeName] || schema.elements[typeName] ); - - if (typeElement) { - var base = typeElement.description(definitions, schema.xmlns); - desc = _.defaultsDeep(base, desc); - } - } - } - return desc; -}; - -EnumerationElement.prototype.description = function() { - return this[this.valueKey]; -}; - -ComplexTypeElement.prototype.description = function(definitions, xmlns) { - var children = this.children || []; - for (var i=0, child; child=children[i]; i++) { - if (child instanceof ChoiceElement || - child instanceof SequenceElement || - child instanceof AllElement || - child instanceof SimpleContentElement || - child instanceof ComplexContentElement) { - - return child.description(definitions, xmlns); - } - } - return {}; -}; - -ComplexContentElement.prototype.description = function(definitions, xmlns) { - var children = this.children; - for (var i = 0, child; child = children[i]; i++) { - if (child instanceof ExtensionElement) { - return child.description(definitions, xmlns); - } - } - return {}; -}; - -SimpleContentElement.prototype.description = function(definitions, xmlns) { - var children = this.children; - for (var i = 0, child; child = children[i]; i++) { - if (child instanceof ExtensionElement) { - return child.description(definitions, xmlns); - } - } - return {}; -}; - -ElementElement.prototype.description = function(definitions, xmlns) { - var element = {}, - name = this.$name; - var isMany = !this.$maxOccurs ? false : (isNaN(this.$maxOccurs) ? (this.$maxOccurs === 'unbounded') : (this.$maxOccurs > 1)); - if (this.$minOccurs !== this.$maxOccurs && isMany) { - name += '[]'; - } - - if (xmlns && xmlns[TNS_PREFIX]) { - this.$targetNamespace = xmlns[TNS_PREFIX]; - } - var type = this.$type || this.$ref; - if (type) { - type = splitQName(type); - var typeName = type.name, - ns = xmlns && xmlns[type.prefix] || - (definitions.xmlns[type.prefix] !== undefined && this.schemaXmlns[type.prefix]) || - definitions.xmlns[type.prefix], - schema = definitions.schemas[ns], - typeElement = schema && ( this.$type? schema.complexTypes[typeName] || schema.types[typeName] : schema.elements[typeName] ); - - if (ns && definitions.schemas[ns]) { - xmlns = definitions.schemas[ns].xmlns; - } - - if (typeElement && !(typeName in Primitives)) { - - if (!(typeName in definitions.descriptions.types)) { - - var elem = {}; - definitions.descriptions.types[typeName] = elem; - var description = typeElement.description(definitions, xmlns); - if (typeof description === 'string') { - elem = description; - } - else { - Object.keys(description).forEach(function (key) { - elem[key] = description[key]; - }); - } - - if (this.$ref) { - element = elem; - } - else { - element[name] = elem; - } - - if (typeof elem === 'object') { - elem.targetNSAlias = type.prefix; - elem.targetNamespace = ns; - } - - definitions.descriptions.types[typeName] = elem; - } - else { - if (this.$ref) { - element = definitions.descriptions.types[typeName]; - } - else { - element[name] = definitions.descriptions.types[typeName]; - } - } - - } - else { - element[name] = this.$type; - } - } - else { - var children = this.children; - element[name] = {}; - for (var i = 0, child; child = children[i]; i++) { - if ( - child instanceof ComplexTypeElement - || child instanceof SimpleTypeElement - ) { - element[name] = child.description(definitions, xmlns); - } - } - } - return element; -}; - -AllElement.prototype.description = -SequenceElement.prototype.description = function(definitions, xmlns) { - var children = this.children; - var sequence = {}; - for (var i = 0, child; child = children[i]; i++) { - if (child instanceof AnyElement) { - continue; - } - var description = child.description(definitions, xmlns); - for (var key in description) { - sequence[key] = description[key]; - } - } - return sequence; -}; - -ChoiceElement.prototype.description = function(definitions, xmlns) { - var children = this.children; - var choice = {}; - for (var i=0, child; child=children[i]; i++) { - var description = child.description(definitions, xmlns); - for (var key in description) { - choice[key] = description[key]; - } - } - return choice; -}; - -MessageElement.prototype.description = function(definitions) { - if (this.element) { - return this.element && this.element.description(definitions); - } - var desc = {}; - desc[this.$name] = this.parts; - return desc; -}; - -PortTypeElement.prototype.description = function(definitions) { - var methods = {}; - for (var name in this.methods) { - var method = this.methods[name]; - methods[name] = method.description(definitions); - } - return methods; -}; - -OperationElement.prototype.description = function(definitions) { - var inputDesc = this.input ? this.input.description(definitions) : null; - var outputDesc = this.output ? this.output.description(definitions) : null; - return { - input: inputDesc && inputDesc[Object.keys(inputDesc)[0]], - output: outputDesc && outputDesc[Object.keys(outputDesc)[0]] - }; -}; - -BindingElement.prototype.description = function(definitions) { - var methods = {}; - for (var name in this.methods) { - var method = this.methods[name]; - methods[name] = method.description(definitions); - } - return methods; -}; - -ServiceElement.prototype.description = function(definitions) { - var ports = {}; - for (var name in this.ports) { - var port = this.ports[name]; - ports[name] = port.binding.description(definitions); - } - return ports; -}; - -var WSDL = function(definition, uri, options) { - var self = this, - fromFunc; - - this.uri = uri; - this.callback = function() { - }; - this._includesWsdl = []; - - // initialize WSDL cache - this.WSDL_CACHE = (options || {}).WSDL_CACHE || {}; - - this._initializeOptions(options); - - if (typeof definition === 'string') { - definition = stripBom(definition); - fromFunc = this._fromXML; - } - else if (typeof definition === 'object') { - fromFunc = this._fromServices; - } - else { - throw new Error('WSDL constructor takes either an XML string or service definition'); - } - - process.nextTick(function() { - try { - fromFunc.call(self, definition); - } catch (e) { - return self.callback(e); - } - - self.processIncludes(function(err) { - var name; - if (err) { - return self.callback(err); - } - - self.definitions.deleteFixedAttrs(); - var services = self.services = self.definitions.services; - if (services) { - for (name in services) { - services[name].postProcess(self.definitions); - } - } - var complexTypes = self.definitions.complexTypes; - if (complexTypes) { - for (name in complexTypes) { - complexTypes[name].deleteFixedAttrs(); - } - } - - // for document style, for every binding, prepare input message element name to (methodName, output message element name) mapping - var bindings = self.definitions.bindings; - for (var bindingName in bindings) { - var binding = bindings[bindingName]; - if (typeof binding.style === 'undefined') { - binding.style = 'document'; - } - if (binding.style !== 'document') - continue; - var methods = binding.methods; - var topEls = binding.topElements = {}; - for (var methodName in methods) { - if (methods[methodName].input) { - var inputName = methods[methodName].input.$name; - var outputName=""; - if(methods[methodName].output ) - outputName = methods[methodName].output.$name; - topEls[inputName] = {"methodName": methodName, "outputName": outputName}; - } - } - } - - // prepare soap envelope xmlns definition string - self.xmlnsInEnvelope = self._xmlnsMap(); - - self.callback(err, self); - }); - - }); -}; - -WSDL.prototype.ignoredNamespaces = ['tns', 'targetNamespace', 'typedNamespace']; - -WSDL.prototype.ignoreBaseNameSpaces = false; - -WSDL.prototype.valueKey = '$value'; -WSDL.prototype.xmlKey = '$xml'; - -WSDL.prototype._initializeOptions = function (options) { - this._originalIgnoredNamespaces = (options || {}).ignoredNamespaces; - this.options = {}; - - var ignoredNamespaces = options ? options.ignoredNamespaces : null; - - if (ignoredNamespaces && - (Array.isArray(ignoredNamespaces.namespaces) || typeof ignoredNamespaces.namespaces === 'string')) { - if (ignoredNamespaces.override) { - this.options.ignoredNamespaces = ignoredNamespaces.namespaces; - } else { - this.options.ignoredNamespaces = this.ignoredNamespaces.concat(ignoredNamespaces.namespaces); - } - } else { - this.options.ignoredNamespaces = this.ignoredNamespaces; - } - - this.options.valueKey = options.valueKey || this.valueKey; - this.options.xmlKey = options.xmlKey || this.xmlKey; - if (options.escapeXML !== undefined) { - this.options.escapeXML = options.escapeXML; - } else { - this.options.escapeXML = true; - } - if (options.returnFault !== undefined) { - this.options.returnFault = options.returnFault; - } else { - this.options.returnFault = false; - } - this.options.handleNilAsNull = !!options.handleNilAsNull; - - if (options.namespaceArrayElements !== undefined) { - this.options.namespaceArrayElements = options.namespaceArrayElements; - } else { - this.options.namespaceArrayElements = true; - } - - // Allow any request headers to keep passing through - this.options.wsdl_headers = options.wsdl_headers; - this.options.wsdl_options = options.wsdl_options; - if (options.httpClient) { - this.options.httpClient = options.httpClient; - } - - // The supplied request-object should be passed through - if (options.request) { - this.options.request = options.request; - } - - var ignoreBaseNameSpaces = options ? options.ignoreBaseNameSpaces : null; - if (ignoreBaseNameSpaces !== null && typeof ignoreBaseNameSpaces !== 'undefined') { - this.options.ignoreBaseNameSpaces = ignoreBaseNameSpaces; - } else { - this.options.ignoreBaseNameSpaces = this.ignoreBaseNameSpaces; - } - - // Works only in client - this.options.forceSoap12Headers = options.forceSoap12Headers; - this.options.customDeserializer = options.customDeserializer; - - if (options.overrideRootElement !== undefined) { - this.options.overrideRootElement = options.overrideRootElement; - } - - this.options.useEmptyTag = !!options.useEmptyTag; -}; - -WSDL.prototype.onReady = function(callback) { - if (callback) - this.callback = callback; -}; - -WSDL.prototype._processNextInclude = function(includes, callback) { - var self = this, - include = includes.shift(), - options; - - if (!include) - return callback(); - - var includePath; - if (!/^https?:/.test(self.uri) && !/^https?:/.test(include.location)) { - includePath = path.resolve(path.dirname(self.uri), include.location); - } else { - includePath = url.resolve(self.uri||'', include.location); - } - - options = _.assign({}, this.options); - // follow supplied ignoredNamespaces option - options.ignoredNamespaces = this._originalIgnoredNamespaces || this.options.ignoredNamespaces; - options.WSDL_CACHE = this.WSDL_CACHE; - - open_wsdl_recursive(includePath, options, function(err, wsdl) { - if (err) { - return callback(err); - } - - self._includesWsdl.push(wsdl); - - if (wsdl.definitions instanceof DefinitionsElement) { - _.mergeWith(self.definitions, wsdl.definitions, function(a,b) { - return (a instanceof SchemaElement) ? a.merge(b) : undefined; - }); - } else { - self.definitions.schemas[include.namespace || wsdl.definitions.$targetNamespace] = deepMerge(self.definitions.schemas[include.namespace || wsdl.definitions.$targetNamespace], wsdl.definitions); - } - self._processNextInclude(includes, function(err) { - callback(err); - }); - }); -}; - -WSDL.prototype.processIncludes = function(callback) { - var schemas = this.definitions.schemas, - includes = []; - - for (var ns in schemas) { - var schema = schemas[ns]; - includes = includes.concat(schema.includes || []); - } - - this._processNextInclude(includes, callback); -}; - -WSDL.prototype.describeServices = function() { - var services = {}; - for (var name in this.services) { - var service = this.services[name]; - services[name] = service.description(this.definitions); - } - return services; -}; - -WSDL.prototype.toXML = function() { - return this.xml || ''; -}; - -WSDL.prototype.xmlToObject = function(xml, callback) { - var self = this; - var p = typeof callback === 'function' ? {} : sax.parser(true); - var objectName = null; - var root = {}; - var schema = { - Envelope: { - Header: { - Security: { - UsernameToken: { - Username: 'string', - Password: 'string' - } - } - }, - Body: { - Fault: { - faultcode: 'string', - faultstring: 'string', - detail: 'string' - } - } - } - }; - var stack = [{name: null, object: root, schema: schema}]; - var xmlns = {}; - - var refs = {}, id; // {id:{hrefs:[],obj:}, ...} - - p.onopentag = function(node) { - var nsName = node.name; - var attrs = node.attributes; - - var name = splitQName(nsName).name, - attributeName, - top = stack[stack.length - 1], - topSchema = top.schema, - elementAttributes = {}, - hasNonXmlnsAttribute = false, - hasNilAttribute = false, - obj = {}; - var originalName = name; - - if (!objectName && top.name === 'Body' && name !== 'Fault') { - var message = self.definitions.messages[name]; - // Support RPC/literal messages where response body contains one element named - // after the operation + 'Response'. See http://www.w3.org/TR/wsdl#_names - if (!message) { - try { - // Determine if this is request or response - var isInput = false; - var isOutput = false; - if ((/Response$/).test(name)) { - isOutput = true; - name = name.replace(/Response$/, ''); - } else if ((/Request$/).test(name)) { - isInput = true; - name = name.replace(/Request$/, ''); - } else if ((/Solicit$/).test(name)) { - isInput = true; - name = name.replace(/Solicit$/, ''); - } - // Look up the appropriate message as given in the portType's operations - var portTypes = self.definitions.portTypes; - var portTypeNames = Object.keys(portTypes); - // Currently this supports only one portType definition. - var portType = portTypes[portTypeNames[0]]; - if (isInput) { - name = portType.methods[name].input.$name; - } else { - name = portType.methods[name].output.$name; - } - message = self.definitions.messages[name]; - // 'cache' this alias to speed future lookups - self.definitions.messages[originalName] = self.definitions.messages[name]; - } catch (e) { - if (self.options.returnFault) { - p.onerror(e); - } - } - } - - topSchema = message.description(self.definitions); - objectName = originalName; - } - - if (attrs.href) { - id = attrs.href.substr(1); - if (!refs[id]) { - refs[id] = {hrefs: [], obj: null}; - } - refs[id].hrefs.push({par: top.object, key: name, obj: obj}); - } - if (id = attrs.id) { - if (!refs[id]) { - refs[id] = {hrefs: [], obj: null}; - } - } - - //Handle element attributes - for (attributeName in attrs) { - if (/^xmlns:|^xmlns$/.test(attributeName)) { - xmlns[splitQName(attributeName).name] = attrs[attributeName]; - continue; - } - hasNonXmlnsAttribute = true; - elementAttributes[attributeName] = attrs[attributeName]; - } - - for(attributeName in elementAttributes){ - var res = splitQName(attributeName); - if (res.name === 'nil' && xmlns[res.prefix] === 'http://www.w3.org/2001/XMLSchema-instance' && elementAttributes[attributeName] && - (elementAttributes[attributeName].toLowerCase() === 'true' || elementAttributes[attributeName] === '1') - ) { - hasNilAttribute = true; - break; - } - } - - if (hasNonXmlnsAttribute) { - obj[self.options.attributesKey] = elementAttributes; - } - - // Pick up the schema for the type specified in element's xsi:type attribute. - var xsiTypeSchema; - var xsiType = elementAttributes['xsi:type']; - if (xsiType) { - var type = splitQName(xsiType); - var typeURI; - if (type.prefix === TNS_PREFIX) { - // In case of xsi:type = "MyType" - typeURI = xmlns[type.prefix] || xmlns.xmlns; - } else { - typeURI = xmlns[type.prefix]; - } - var typeDef = self.findSchemaObject(typeURI, type.name); - if (typeDef) { - xsiTypeSchema = typeDef.description(self.definitions); - } - } - - if (topSchema && topSchema[name + '[]']) { - name = name + '[]'; - } - stack.push({name: originalName, object: obj, schema: (xsiTypeSchema || (topSchema && topSchema[name])), id: attrs.id, nil: hasNilAttribute}); - }; - - p.onclosetag = function(nsName) { - var cur = stack.pop(), - obj = cur.object, - top = stack[stack.length - 1], - topObject = top.object, - topSchema = top.schema, - name = splitQName(nsName).name; - - if (typeof cur.schema === 'string' && (cur.schema === 'string' || cur.schema.split(':')[1] === 'string')) { - if (typeof obj === 'object' && Object.keys(obj).length === 0) obj = cur.object = ''; - } - - if (cur.nil === true) { - if (self.options.handleNilAsNull) { - obj = null; - } else { - return; - } - } - - if (_.isPlainObject(obj) && !Object.keys(obj).length) { - obj = null; - } - - if (topSchema && topSchema[name + '[]']) { - if (!topObject[name]) { - topObject[name] = []; - } - topObject[name].push(obj); - } else if (name in topObject) { - if (!Array.isArray(topObject[name])) { - topObject[name] = [topObject[name]]; - } - topObject[name].push(obj); - } else { - topObject[name] = obj; - } - - if (cur.id) { - refs[cur.id].obj = obj; - } - }; - - p.oncdata = function (text) { - var originalText = text; - text = trim(text); - if (!text.length) { - return; - } - - if (/<\?xml[\s\S]+\?>/.test(text)) { - var top = stack[stack.length - 1]; - var value = self.xmlToObject(text); - if (top.object[self.options.attributesKey]) { - top.object[self.options.valueKey] = value; - } else { - top.object = value; - } - } else { - p.ontext(originalText); - } - }; - - p.onerror = function(e) { - p.resume(); - throw { - Fault: { - faultcode: 500, - faultstring: 'Invalid XML', - detail: new Error(e).message, - statusCode: 500 - } - }; - }; - - p.ontext = function(text) { - var originalText = text; - text = trim(text); - if (!text.length) { - return; - } - - var top = stack[stack.length - 1]; - var name = splitQName(top.schema).name, - value; - - if (self.options && self.options.customDeserializer && self.options.customDeserializer[name]) { - value = self.options.customDeserializer[name](text, top); - } - else { - if (name === 'int' || name === 'integer') { - value = parseInt(text, 10); - } else if (name === 'bool' || name === 'boolean') { - value = text.toLowerCase() === 'true' || text === '1'; - } else if (name === 'dateTime' || name === 'date') { - value = new Date(text); - } else { - if (self.options.preserveWhitespace) { - text = originalText; - } - // handle string or other types - if (typeof top.object !== 'string') { - value = text; - } else { - value = top.object + text; - } - } - } - - if (top.object[self.options.attributesKey]) { - top.object[self.options.valueKey] = value; - } else { - top.object = value; - } - }; - - if (typeof callback === 'function') { - // we be streaming - var saxStream = sax.createStream(true); - saxStream.on('opentag', p.onopentag); - saxStream.on('closetag', p.onclosetag); - saxStream.on('cdata', p.oncdata); - saxStream.on('text', p.ontext); - xml.pipe(saxStream) - .on('error', function (err) { - callback(err); - }) - .on('end', function () { - var r; - try { - r = finish(); - } catch (e) { - return callback(e); - } - callback(null, r); - }); - return; - } - p.write(xml).close(); - - return finish(); - - function finish() { - // MultiRef support: merge objects instead of replacing - for (var n in refs) { - var ref = refs[n]; - for (var i = 0; i < ref.hrefs.length; i++) { - _.assign(ref.hrefs[i].obj, ref.obj); - } - } - - if (root.Envelope) { - var body = root.Envelope.Body; - if (body && body.Fault) { - var code = body.Fault.faultcode && body.Fault.faultcode.$value; - var string = body.Fault.faultstring && body.Fault.faultstring.$value; - var detail = body.Fault.detail && body.Fault.detail.$value; - - code = code || body.Fault.faultcode; - string = string || body.Fault.faultstring; - detail = detail || body.Fault.detail; - - var error = new Error(code + ': ' + string + (detail ? ': ' + JSON.stringify(detail) : '')); - - error.root = root; - throw error; - } - return root.Envelope; - } - return root; - } -}; - -/** - * Look up a XSD type or element by namespace URI and name - * @param {String} nsURI Namespace URI - * @param {String} qname Local or qualified name - * @returns {*} The XSD type/element definition - */ -WSDL.prototype.findSchemaObject = function(nsURI, qname) { - if (!nsURI || !qname) { - return null; - } - - var def = null; - - if (this.definitions.schemas) { - var schema = this.definitions.schemas[nsURI]; - if (schema) { - if (qname.indexOf(':') !== -1) { - qname = qname.substring(qname.indexOf(':') + 1, qname.length); - } - - // if the client passed an input element which has a `$lookupType` property instead of `$type` - // the `def` is found in `schema.elements`. - def = schema.complexTypes[qname] || schema.types[qname] || schema.elements[qname]; - } - } - - return def; -}; - -/** - * Create document style xml string from the parameters - * @param {String} name - * @param {*} params - * @param {String} nsPrefix - * @param {String} nsURI - * @param {String} type - */ -WSDL.prototype.objectToDocumentXML = function(name, params, nsPrefix, nsURI, type) { - //If user supplies XML already, just use that. XML Declaration should not be present. - if (params && params._xml) { - return params._xml; - } - var args = {}; - args[name] = params; - var parameterTypeObj = type ? this.findSchemaObject(nsURI, type) : null; - return this.objectToXML(args, null, nsPrefix, nsURI, true, null, parameterTypeObj); -}; - -/** - * Create RPC style xml string from the parameters - * @param {String} name - * @param {*} params - * @param {String} nsPrefix - * @param {String} nsURI - * @returns {string} - */ -WSDL.prototype.objectToRpcXML = function(name, params, nsPrefix, nsURI,isParts) { - var parts = []; - var defs = this.definitions; - var nsAttrName = '_xmlns'; - - nsPrefix = nsPrefix || findPrefix(defs.xmlns, nsURI); - - nsURI = nsURI || defs.xmlns[nsPrefix]; - nsPrefix = nsPrefix === TNS_PREFIX ? '' : (nsPrefix + ':'); - - parts.push(['<', nsPrefix, name, '>'].join('')); - - for (var key in params) { - if (!params.hasOwnProperty(key)) { - continue; - } - if (key !== nsAttrName) { - var value = params[key]; - var prefixedKey = (isParts ? '' : nsPrefix) + key; - var attributes = []; - if (typeof value === 'object' && value.hasOwnProperty(this.options.attributesKey)) { - var attrs = value[this.options.attributesKey]; - for (var n in attrs) { - attributes.push(' ' + n + '=' + '"' + attrs[n] + '"'); - } - } - parts.push(['<', prefixedKey ].concat(attributes).concat('>').join('')); - parts.push((typeof value === 'object') ? this.objectToXML(value, key, nsPrefix, nsURI) : xmlEscape(value)); - parts.push([''].join('')); - } - } - parts.push([''].join('')); - return parts.join(''); -}; - - -function appendColon(ns) { - return (ns && ns.charAt(ns.length - 1) !== ':') ? ns + ':' : ns; -} - -function noColonNameSpace(ns) { - return (ns && ns.charAt(ns.length - 1) === ':') ? ns.substring(0, ns.length - 1) : ns; -} - -WSDL.prototype.isIgnoredNameSpace = function(ns) { - return this.options.ignoredNamespaces.indexOf(ns) > -1; -}; - -WSDL.prototype.filterOutIgnoredNameSpace = function(ns) { - var namespace = noColonNameSpace(ns); - return this.isIgnoredNameSpace(namespace) ? '' : namespace; -}; - - - -/** - * Convert an object to XML. This is a recursive method as it calls itself. - * - * @param {Object} obj the object to convert. - * @param {String} name the name of the element (if the object being traversed is - * an element). - * @param {String} nsPrefix the namespace prefix of the object I.E. xsd. - * @param {String} nsURI the full namespace of the object I.E. http://w3.org/schema. - * @param {Boolean} isFirst whether or not this is the first item being traversed. - * @param {?} xmlnsAttr - * @param {?} parameterTypeObject - * @param {NamespaceContext} nsContext Namespace context - */ -WSDL.prototype.objectToXML = function(obj, name, nsPrefix, nsURI, isFirst, xmlnsAttr, schemaObject, nsContext) { - var self = this; - var schema = this.definitions.schemas[nsURI]; - - var parentNsPrefix = nsPrefix ? nsPrefix.parent : undefined; - if (typeof parentNsPrefix !== 'undefined') { - //we got the parentNsPrefix for our array. setting the namespace-variable back to the current namespace string - nsPrefix = nsPrefix.current; - } - - parentNsPrefix = noColonNameSpace(parentNsPrefix); - if (this.isIgnoredNameSpace(parentNsPrefix)) { - parentNsPrefix = ''; - } - - var soapHeader = !schema; - var qualified = schema && schema.$elementFormDefault === 'qualified'; - var parts = []; - var prefixNamespace = (nsPrefix || qualified) && nsPrefix !== TNS_PREFIX; - - var xmlnsAttrib = ''; - if (nsURI && isFirst) { - if(self.options.overrideRootElement && self.options.overrideRootElement.xmlnsAttributes) { - self.options.overrideRootElement.xmlnsAttributes.forEach(function(attribute) { - xmlnsAttrib += ' ' + attribute.name + '="' + attribute.value + '"'; - }); - } else { - if (prefixNamespace && !this.isIgnoredNameSpace(nsPrefix)) { - // resolve the prefix namespace - xmlnsAttrib += ' xmlns:' + nsPrefix + '="' + nsURI + '"'; - } - // only add default namespace if the schema elementFormDefault is qualified - if (qualified || soapHeader) xmlnsAttrib += ' xmlns="' + nsURI + '"'; - } - } - - if (!nsContext) { - nsContext = new NamespaceContext(); - nsContext.declareNamespace(nsPrefix, nsURI); - } else { - nsContext.pushContext(); - } - - // explicitly use xmlns attribute if available - if (xmlnsAttr && !(self.options.overrideRootElement && self.options.overrideRootElement.xmlnsAttributes)) { - xmlnsAttrib = xmlnsAttr; - } - - var ns = ''; - - if (self.options.overrideRootElement && isFirst) { - ns = self.options.overrideRootElement.namespace; - } else if (prefixNamespace && (qualified || isFirst || soapHeader) && !this.isIgnoredNameSpace(nsPrefix)) { - ns = nsPrefix; - } - - var i, n; - // start building out XML string. - if (Array.isArray(obj)) { - var nonSubNameSpace = ''; - var nameWithNsRegex = /^([^:]+):([^:]+)$/.exec(name); - if (nameWithNsRegex) { - nonSubNameSpace = nameWithNsRegex[1]; - name = nameWithNsRegex[2]; - } - - for (i = 0, n = obj.length; i < n; i++) { - var item = obj[i]; - var arrayAttr = self.processAttributes(item, nsContext), - correctOuterNsPrefix = nonSubNameSpace || parentNsPrefix || ns; //using the parent namespace prefix if given - - var body = self.objectToXML(item, name, nsPrefix, nsURI, false, null, schemaObject, nsContext); - - var openingTagParts = ['<', appendColon(correctOuterNsPrefix), name, arrayAttr, xmlnsAttrib]; - - if (body === '' && self.options.useEmptyTag) { - // Use empty (self-closing) tags if no contents - openingTagParts.push(' />'); - parts.push(openingTagParts.join('')); - } else { - openingTagParts.push('>'); - if(self.options.namespaceArrayElements || i === 0) { - parts.push(openingTagParts.join('')); - } - parts.push(body); - if(self.options.namespaceArrayElements || i === n-1) { - parts.push([''].join('')); - } - } - } - } else if (typeof obj === 'object') { - for (name in obj) { - if (!obj.hasOwnProperty(name)) continue; - //don't process attributes as element - if (name === self.options.attributesKey) { - continue; - } - //Its the value of a xml object. Return it directly. - if (name === self.options.xmlKey){ - nsContext.popContext(); - return obj[name]; - } - //Its the value of an item. Return it directly. - if (name === self.options.valueKey) { - nsContext.popContext(); - return xmlEscape(obj[name]); - } - - var child = obj[name]; - if (typeof child === 'undefined') { - continue; - } - - var attr = self.processAttributes(child, nsContext); - - var value = ''; - var nonSubNameSpace = ''; - var emptyNonSubNameSpace = false; - - var nameWithNsRegex = /^([^:]+):([^:]+)$/.exec(name); - if (nameWithNsRegex) { - nonSubNameSpace = nameWithNsRegex[1] + ':'; - name = nameWithNsRegex[2]; - } else if(name[0] === ':'){ - emptyNonSubNameSpace = true; - name = name.substr(1); - } - - if (isFirst) { - value = self.objectToXML(child, name, nsPrefix, nsURI, false, null, schemaObject, nsContext); - } else { - - if (self.definitions.schemas) { - if (schema) { - var childSchemaObject = self.findChildSchemaObject(schemaObject, name); - //find sub namespace if not a primitive - if (childSchemaObject && - ((childSchemaObject.$type && (childSchemaObject.$type.indexOf('xsd:') === -1)) || - childSchemaObject.$ref || childSchemaObject.$name)) { - /*if the base name space of the children is not in the ingoredSchemaNamspaces we use it. - This is because in some services the child nodes do not need the baseNameSpace. - */ - - var childNsPrefix = ''; - var childName = ''; - var childNsURI; - var childXmlnsAttrib = ''; - - var elementQName = childSchemaObject.$ref || childSchemaObject.$name; - if (elementQName) { - elementQName = splitQName(elementQName); - childName = elementQName.name; - if (elementQName.prefix === TNS_PREFIX) { - // Local element - childNsURI = childSchemaObject.$targetNamespace; - childNsPrefix = nsContext.registerNamespace(childNsURI); - if (this.isIgnoredNameSpace(childNsPrefix)) { - childNsPrefix = nsPrefix; - } - } else { - childNsPrefix = elementQName.prefix; - if (this.isIgnoredNameSpace(childNsPrefix)) { - childNsPrefix = nsPrefix; - } - childNsURI = schema.xmlns[childNsPrefix] || self.definitions.xmlns[childNsPrefix]; - } - - var unqualified = false; - // Check qualification form for local elements - if (childSchemaObject.$name && childSchemaObject.targetNamespace === undefined) { - if (childSchemaObject.$form === 'unqualified') { - unqualified = true; - } else if (childSchemaObject.$form === 'qualified') { - unqualified = false; - } else { - unqualified = schema.$elementFormDefault !== 'qualified'; - } - } - if (unqualified) { - childNsPrefix = ''; - } - - if (childNsURI && childNsPrefix) { - if (nsContext.declareNamespace(childNsPrefix, childNsURI)) { - childXmlnsAttrib = ' xmlns:' + childNsPrefix + '="' + childNsURI + '"'; - xmlnsAttrib += childXmlnsAttrib; - } - } - } - - var resolvedChildSchemaObject; - if (childSchemaObject.$type) { - var typeQName = splitQName(childSchemaObject.$type); - var typePrefix = typeQName.prefix; - var typeURI = schema.xmlns[typePrefix] || self.definitions.xmlns[typePrefix]; - childNsURI = typeURI; - if (typeURI !== 'http://www.w3.org/2001/XMLSchema' && typePrefix !== TNS_PREFIX) { - // Add the prefix/namespace mapping, but not declare it - nsContext.addNamespace(typePrefix, typeURI); - } - resolvedChildSchemaObject = - self.findSchemaType(typeQName.name, typeURI) || childSchemaObject; - } else { - resolvedChildSchemaObject = - self.findSchemaObject(childNsURI, childName) || childSchemaObject; - } - - if (childSchemaObject.$baseNameSpace && this.options.ignoreBaseNameSpaces) { - childNsPrefix = nsPrefix; - childNsURI = nsURI; - } - - if (this.options.ignoreBaseNameSpaces) { - childNsPrefix = ''; - childNsURI = ''; - } - - ns = childNsPrefix; - - if (Array.isArray(child)) { - //for arrays, we need to remember the current namespace - childNsPrefix = { - current: childNsPrefix, - parent: ns - }; - } else { - //parent (array) already got the namespace - childXmlnsAttrib = null; - } - - value = self.objectToXML(child, name, childNsPrefix, childNsURI, - false, childXmlnsAttrib, resolvedChildSchemaObject, nsContext); - } else if (obj[self.options.attributesKey] && obj[self.options.attributesKey].xsi_type) { - //if parent object has complex type defined and child not found in parent - var completeChildParamTypeObject = self.findChildSchemaObject( - obj[self.options.attributesKey].xsi_type.type, - obj[self.options.attributesKey].xsi_type.xmlns); - - nonSubNameSpace = obj[self.options.attributesKey].xsi_type.prefix; - nsContext.addNamespace(obj[self.options.attributesKey].xsi_type.prefix, - obj[self.options.attributesKey].xsi_type.xmlns); - value = self.objectToXML(child, name, obj[self.options.attributesKey].xsi_type.prefix, - obj[self.options.attributesKey].xsi_type.xmlns, false, null, null, nsContext); - } else { - if(Array.isArray(child)) { - name = nonSubNameSpace + name; - } - - value = self.objectToXML(child, name, nsPrefix, nsURI, false, null, null, nsContext); - } - } else { - value = self.objectToXML(child, name, nsPrefix, nsURI, false, null, null, nsContext); - } - } - } - - ns = noColonNameSpace(ns); - if (prefixNamespace && !qualified && isFirst && !self.options.overrideRootElement) { - ns = nsPrefix; - } else if (this.isIgnoredNameSpace(ns)) { - ns = ''; - } - - var useEmptyTag = !value && self.options.useEmptyTag; - if (!Array.isArray(child)) { - // start tag - parts.push(['<', emptyNonSubNameSpace ? '' : appendColon(nonSubNameSpace || ns), name, attr, xmlnsAttrib, - (child === null ? ' xsi:nil="true"' : ''), - useEmptyTag ? ' />' : '>' - ].join('')); - } - - if (!useEmptyTag) { - parts.push(value); - if (!Array.isArray(child)) { - // end tag - parts.push([''].join('')); - } - } - } - } else if (obj !== undefined) { - parts.push((self.options.escapeXML) ? xmlEscape(obj) : obj); - } - nsContext.popContext(); - return parts.join(''); -}; - -WSDL.prototype.processAttributes = function(child, nsContext) { - var attr = ''; - - if(child === null) { - child = []; - } - - var attrObj = child[this.options.attributesKey]; - if (attrObj && attrObj.xsi_type) { - var xsiType = attrObj.xsi_type; - - var prefix = xsiType.prefix || xsiType.namespace; - // Generate a new namespace for complex extension if one not provided - if (!prefix) { - prefix = nsContext.registerNamespace(xsiType.xmlns); - } else { - nsContext.declareNamespace(prefix, xsiType.xmlns); - } - xsiType.prefix = prefix; - } - - - if (attrObj) { - for (var attrKey in attrObj) { - //handle complex extension separately - if (attrKey === 'xsi_type') { - var attrValue = attrObj[attrKey]; - attr += ' xsi:type="' + attrValue.prefix + ':' + attrValue.type + '"'; - attr += ' xmlns:' + attrValue.prefix + '="' + attrValue.xmlns + '"'; - - continue; - } else { - attr += ' ' + attrKey + '="' + xmlEscape(attrObj[attrKey]) + '"'; - } - } - } - - return attr; -}; - -/** - * Look up a schema type definition - * @param name - * @param nsURI - * @returns {*} - */ -WSDL.prototype.findSchemaType = function(name, nsURI) { - if (!this.definitions.schemas || !name || !nsURI) { - return null; - } - - var schema = this.definitions.schemas[nsURI]; - if (!schema || !schema.complexTypes) { - return null; - } - - return schema.complexTypes[name]; -}; - -WSDL.prototype.findChildSchemaObject = function(parameterTypeObj, childName, backtrace) { - if (!parameterTypeObj || !childName) { - return null; - } - - if (!backtrace) { - backtrace = []; - } - - if (backtrace.indexOf(parameterTypeObj) >= 0) { - // We've recursed back to ourselves; break. - return null; - } else { - backtrace = backtrace.concat([parameterTypeObj]); - } - - var found = null, - i = 0, - child, - ref; - - if (Array.isArray(parameterTypeObj.$lookupTypes) && parameterTypeObj.$lookupTypes.length) { - var types = parameterTypeObj.$lookupTypes; - - for(i = 0; i < types.length; i++) { - var typeObj = types[i]; - - if(typeObj.$name === childName) { - found = typeObj; - break; - } - } - } - - var object = parameterTypeObj; - if (object.$name === childName && object.name === 'element') { - return object; - } - if (object.$ref) { - ref = splitQName(object.$ref); - if (ref.name === childName) { - return object; - } - } - - var childNsURI; - - // want to avoid unecessary recursion to improve performance - if (object.$type && backtrace.length === 1) { - var typeInfo = splitQName(object.$type); - if (typeInfo.prefix === TNS_PREFIX) { - childNsURI = parameterTypeObj.$targetNamespace; - } else { - childNsURI = this.definitions.xmlns[typeInfo.prefix]; - } - var typeDef = this.findSchemaType(typeInfo.name, childNsURI); - if (typeDef) { - return this.findChildSchemaObject(typeDef, childName, backtrace); - } - } - - if (object.children) { - for (i = 0, child; child = object.children[i]; i++) { - found = this.findChildSchemaObject(child, childName, backtrace); - if (found) { - break; - } - - if (child.$base) { - var baseQName = splitQName(child.$base); - var childNameSpace = baseQName.prefix === TNS_PREFIX ? '' : baseQName.prefix; - childNsURI = child.xmlns[baseQName.prefix] || this.definitions.xmlns[baseQName.prefix]; - - var foundBase = this.findSchemaType(baseQName.name, childNsURI); - - if (foundBase) { - found = this.findChildSchemaObject(foundBase, childName, backtrace); - - if (found) { - found.$baseNameSpace = childNameSpace; - found.$type = childNameSpace + ':' + childName; - break; - } - } - } - } - - } - - if (!found && object.$name === childName) { - return object; - } - - return found; -}; - -WSDL.prototype._parse = function(xml) { - var self = this, - p = sax.parser(true), - stack = [], - root = null, - types = null, - schema = null, - schemaAttrs = null, - options = self.options; - - p.onopentag = function(node) { - var nsName = node.name; - var attrs = node.attributes; - - var top = stack[stack.length - 1]; - var name = splitQName(nsName).name; - - if (name === 'schema') { - schemaAttrs = attrs; - } - if (top) { - try { - top.startElement(stack, nsName, attrs, options, schemaAttrs); - } catch (e) { - if (self.options.strict) { - throw e; - } else { - stack.push(new Element(nsName, attrs, options, schemaAttrs)); - } - } - } else { - if (name === 'definitions') { - root = new DefinitionsElement(nsName, attrs, options); - stack.push(root); - } else if (name === 'schema') { - // Shim a structure in here to allow the proper objects to be created when merging back. - root = new DefinitionsElement('definitions', {}, {}); - types = new TypesElement('types', {}, {}); - schema = new SchemaElement(nsName, attrs, options); - types.addChild(schema); - root.addChild(types); - stack.push(schema); - } else { - throw new Error('Unexpected root element of WSDL or include'); - } - } - }; - - p.onclosetag = function(name) { - var top = stack[stack.length - 1]; - assert(top, 'Unmatched close tag: ' + name); - - top.endElement(stack, name); - }; - - p.write(xml).close(); - - return root; -}; - -WSDL.prototype._fromXML = function(xml) { - this.definitions = this._parse(xml); - this.definitions.descriptions = { - types:{} - }; - this.xml = xml; -}; - -WSDL.prototype._fromServices = function(services) { - -}; - - - -WSDL.prototype._xmlnsMap = function() { - var xmlns = this.definitions.xmlns; - var str = ''; - for (var alias in xmlns) { - if (alias === '' || alias === TNS_PREFIX) { - continue; - } - var ns = xmlns[alias]; - switch (ns) { - case "http://xml.apache.org/xml-soap" : // apachesoap - case "http://schemas.xmlsoap.org/wsdl/" : // wsdl - case "http://schemas.xmlsoap.org/wsdl/soap/" : // wsdlsoap - case "http://schemas.xmlsoap.org/wsdl/soap12/": // wsdlsoap12 - case "http://schemas.xmlsoap.org/soap/encoding/" : // soapenc - case "http://www.w3.org/2001/XMLSchema" : // xsd - continue; - } - if (~ns.indexOf('http://schemas.xmlsoap.org/')) { - continue; - } - if (~ns.indexOf('http://www.w3.org/')) { - continue; - } - if (~ns.indexOf('http://xml.apache.org/')) { - continue; - } - str += ' xmlns:' + alias + '="' + ns + '"'; - } - return str; -}; - -/* - * Have another function to load previous WSDLs as we - * don't want this to be invoked externally (expect for tests) - * This will attempt to fix circular dependencies with XSD files, - * Given - * - file.wsdl - * - xs:import namespace="A" schemaLocation: A.xsd - * - A.xsd - * - xs:import namespace="B" schemaLocation: B.xsd - * - B.xsd - * - xs:import namespace="A" schemaLocation: A.xsd - * file.wsdl will start loading, import A, then A will import B, which will then import A - * Because A has already started to load previously it will be returned right away and - * have an internal circular reference - * B would then complete loading, then A, then file.wsdl - * By the time file A starts processing its includes its definitions will be already loaded, - * this is the only thing that B will depend on when "opening" A - */ -function open_wsdl_recursive(uri, options, callback) { - var fromCache, - WSDL_CACHE; - - if (typeof options === 'function') { - callback = options; - options = {}; - } - - WSDL_CACHE = options.WSDL_CACHE; - - if (fromCache = WSDL_CACHE[ uri ]) { - return callback.call(fromCache, null, fromCache); - } - - return open_wsdl(uri, options, callback); -} - -function open_wsdl(uri, options, callback) { - if (typeof options === 'function') { - callback = options; - options = {}; - } - - // initialize cache when calling open_wsdl directly - var WSDL_CACHE = options.WSDL_CACHE || {}; - var request_headers = options.wsdl_headers; - var request_options = options.wsdl_options; - - var wsdl; - if (!/^https?:/.test(uri)) { - debug('Reading file: %s', uri); - fs.readFile(uri, 'utf8', function(err, definition) { - if (err) { - callback(err); - } - else { - wsdl = new WSDL(definition, uri, options); - WSDL_CACHE[ uri ] = wsdl; - wsdl.WSDL_CACHE = WSDL_CACHE; - wsdl.onReady(callback); - } - }); - } - else { - debug('Reading url: %s', uri); - var httpClient = options.httpClient || new HttpClient(options); - httpClient.request(uri, null /* options */, function(err, response, definition) { - if (err) { - callback(err); - } else if (response && response.statusCode === 200) { - wsdl = new WSDL(definition, uri, options); - WSDL_CACHE[ uri ] = wsdl; - wsdl.WSDL_CACHE = WSDL_CACHE; - wsdl.onReady(callback); - } else { - callback(new Error('Invalid WSDL URL: ' + uri + "\n\n\r Code: " + response.statusCode + "\n\n\r Response Body: " + response.body)); - } - }, request_headers, request_options); - } - - return wsdl; -} - -exports.open_wsdl = open_wsdl; -exports.WSDL = WSDL; diff --git a/package-lock.json b/package-lock.json index 3308de4a8..8dabb2619 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "soap", - "version": "0.25.0", + "version": "0.26.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -10,6 +10,197 @@ "integrity": "sha512-Wk41MVdF+cHBfVXj/ufUHJeO3BlIQr1McbHZANErMykaCWeDSZbH5erGjNBw2/3UlRdSxZbLfSuQTzFmPOYFsA==", "dev": true }, + "@types/body-parser": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.0.tgz", + "integrity": "sha512-a2+YeUjPkztKJu5aIF2yArYFQQp8d51wZ7DavSHjFuY1mqVgidGyzEQ41JIVNy82fXj8yPgy2vJmfIywgESW6w==", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/caseless": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.1.tgz", + "integrity": "sha512-FhlMa34NHp9K5MY1Uz8yb+ZvuX0pnvn3jScRSNAb75KHGB8d3rEU6hqMs3Z2vjuytcMfRg6c5CHMc3wtYyD2/A==", + "dev": true + }, + "@types/connect": { + "version": "3.4.32", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.32.tgz", + "integrity": "sha512-4r8qa0quOvh7lGD0pre62CAb1oni1OO6ecJLGCezTmhQ8Fz50Arx9RUszryR8KlgK6avuSXvviL6yWyViQABOg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-OKKcc+Tt1rLMg1DTnmgvWHRna3SN51GWs/ERxpgjFNRjCUUVhGG8FEKeFqVTzBv1jEGFqbkJRWfsHV6KAcLT+A==", + "dev": true + }, + "@types/events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", + "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==", + "dev": true + }, + "@types/express": { + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.16.1.tgz", + "integrity": "sha512-V0clmJow23WeyblmACoxbHBu2JKlE5TiIme6Lem14FnPW9gsttyHtk6wq7njcdIWH1njAaFgR8gW09lgY98gQg==", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.16.1.tgz", + "integrity": "sha512-QgbIMRU1EVRry5cIu1ORCQP4flSYqLM1lS5LYyGWfKnFT3E58f0gKto7BR13clBFVrVZ0G0rbLZ1hUpSkgQQOA==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/range-parser": "*" + } + }, + "@types/form-data": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-2.2.1.tgz", + "integrity": "sha512-JAMFhOaHIciYVh8fb5/83nmuO/AHwmto+Hq7a9y8FzLDcC1KCU344XDOMEmahnrTFlHjgh4L0WJFczNIX2GxnQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/fs-extra": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-5.0.5.tgz", + "integrity": "sha512-w7iqhDH9mN8eLClQOYTkhdYUOSpp25eXxfc6VbFOGtzxW34JcvctH2bKjj4jD4++z4R5iO5D+pg48W2e03I65A==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/glob": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", + "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", + "dev": true, + "requires": { + "@types/events": "*", + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/handlebars": { + "version": "4.0.40", + "resolved": "https://registry.npmjs.org/@types/handlebars/-/handlebars-4.0.40.tgz", + "integrity": "sha512-sGWNtsjNrLOdKha2RV1UeF8+UbQnPSG7qbe5wwbni0mw4h2gHXyPFUMOC+xwGirIiiydM/HSqjDO4rk6NFB18w==", + "dev": true + }, + "@types/highlight.js": { + "version": "9.12.3", + "resolved": "https://registry.npmjs.org/@types/highlight.js/-/highlight.js-9.12.3.tgz", + "integrity": "sha512-pGF/zvYOACZ/gLGWdQH8zSwteQS1epp68yRcVLJMgUck/MjEn/FBYmPub9pXT8C1e4a8YZfHo1CKyV8q1vKUnQ==", + "dev": true + }, + "@types/lodash": { + "version": "4.14.121", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.121.tgz", + "integrity": "sha512-ORj7IBWj13iYufXt/VXrCNMbUuCTJfhzme5kx9U/UtcIPdJYuvPDUAlHlbNhz/8lKCLy9XGIZnGrqXOtQbPGoQ==", + "dev": true + }, + "@types/marked": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@types/marked/-/marked-0.4.2.tgz", + "integrity": "sha512-cDB930/7MbzaGF6U3IwSQp6XBru8xWajF5PV2YZZeV8DyiliTuld11afVztGI9+yJZ29il5E+NpGA6ooV/Cjkg==", + "dev": true + }, + "@types/mime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz", + "integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==", + "dev": true + }, + "@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "dev": true + }, + "@types/node": { + "version": "11.9.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.9.4.tgz", + "integrity": "sha512-Zl8dGvAcEmadgs1tmSPcvwzO1YRsz38bVJQvH1RvRqSR9/5n61Q1ktcDL0ht3FXWR+ZpVmXVwN1LuH4Ax23NsA==", + "dev": true + }, + "@types/range-parser": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", + "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==", + "dev": true + }, + "@types/request": { + "version": "2.48.1", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.1.tgz", + "integrity": "sha512-ZgEZ1TiD+KGA9LiAAPPJL68Id2UWfeSO62ijSXZjFJArVV+2pKcsVHmrcu+1oiE3q6eDGiFiSolRc4JHoerBBg==", + "dev": true, + "requires": { + "@types/caseless": "*", + "@types/form-data": "*", + "@types/node": "*", + "@types/tough-cookie": "*" + } + }, + "@types/sax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.0.1.tgz", + "integrity": "sha512-5O70hTAMd9zEOoHiDJ6lk/WvqQgH+aIqU6zkvPLKIl6WJkQeHecHRUWomkjQzAAYPG346nDNus7y724FzTwTKQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/serve-static": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.2.tgz", + "integrity": "sha512-/BZ4QRLpH/bNYgZgwhKEh+5AsboDBcUdlBYgzoLX0fpj3Y2gp6EApyOlM3bK53wQS/OE1SrdSYBAbux2D1528Q==", + "dev": true, + "requires": { + "@types/express-serve-static-core": "*", + "@types/mime": "*" + } + }, + "@types/shelljs": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/@types/shelljs/-/shelljs-0.8.3.tgz", + "integrity": "sha512-miY41hqc5SkRlsZDod3heDa4OS9xv8G77EMBQuSpqq86HBn66l7F+f8y9YKm+1PIuwC8QEZVwN8YxOOG7Y67fA==", + "dev": true, + "requires": { + "@types/glob": "*", + "@types/node": "*" + } + }, + "@types/tough-cookie": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.5.tgz", + "integrity": "sha512-SCcK7mvGi3+ZNz833RRjFIxrn4gI1PPR3NtuIS+6vMkvmsGjosqTJwRt5bAEFLRz+wtJMWv8+uOnZf2hi2QXTg==", + "dev": true + }, + "@types/uuid": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.4.tgz", + "integrity": "sha512-tPIgT0GUmdJQNSHxp0X2jnpQfBSTfGxUMc/2CXBU2mnyTFVYVa2ojpoQ74w0U2yn2vw3jnC640+77lkFFpdVDw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "accepts": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", @@ -40,6 +231,18 @@ "emoji-regex": "~6.1.0" } }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -65,6 +268,23 @@ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" }, + "async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", + "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", + "dev": true, + "requires": { + "lodash": "^4.17.11" + }, + "dependencies": { + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + } + } + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -80,6 +300,32 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==" }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + }, + "dependencies": { + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + } + } + }, "bail": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.3.tgz", @@ -151,6 +397,12 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.0.tgz", "integrity": "sha512-c5mRlguI/Pe2dSZmpER62rSCu0ryKmWddzRYsuXc50U2/g8jMOulc31VZMa4mYx31U5xsmSOpDCgH88Vl9cDGQ==" }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, "bytes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", @@ -168,6 +420,37 @@ "integrity": "sha512-Jt9tIBkRc9POUof7QA/VwWd+58fKkEEfI+/t1/eOlxKM7ZhrczNzMFefge7Ai+39y1pR/pP6cI19guHy3FSLmw==", "dev": true }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "character-entities": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.2.tgz", @@ -212,6 +495,21 @@ "integrity": "sha512-YfQ1tAUZm561vpYD+5eyWN8+UsceQbSrqqlc/6zDY2gtAE+uZLSdkkovhnGpmCThsvKBFakq4EdY/FF93E8XIw==", "dev": true }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, "colors": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.0.tgz", @@ -474,6 +772,12 @@ "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", "dev": true }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -617,6 +921,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", + "dev": true, "requires": { "debug": "2.6.9", "encodeurl": "~1.0.2", @@ -667,6 +972,17 @@ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -697,6 +1013,24 @@ "minimatch": "0.3" } }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true + }, + "handlebars": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.0.tgz", + "integrity": "sha512-l2jRuU1NAWK6AW5qqcTATWQJvNPEwkM7NEKSiv/gqOsoSQbVoWyqVEY5GS+XPQ88zLNmqASRpzfdm8d79hJS+w==", + "dev": true, + "requires": { + "async": "^2.5.0", + "optimist": "^0.6.1", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4" + } + }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -720,6 +1054,15 @@ "function-bind": "^1.1.1" } }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -732,6 +1075,12 @@ "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", "dev": true }, + "highlight.js": { + "version": "9.14.2", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.14.2.tgz", + "integrity": "sha512-Nc6YNECYpxyJABGYJAyw7dBAYbXEuIzwzkqoJnwbc1nIpCiN+3ioYf0XrBnLiyyG0JLuJhpPtt2iTSbXiKLoyA==", + "dev": true + }, "htmlparser2": { "version": "3.9.2", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz", @@ -805,6 +1154,12 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, + "interpret": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", + "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==", + "dev": true + }, "ipaddr.js": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.6.0.tgz", @@ -859,6 +1214,12 @@ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, "js-yaml": { "version": "3.6.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.6.1.tgz", @@ -911,6 +1272,15 @@ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -975,6 +1345,12 @@ "traverse": "^0.6.6" } }, + "marked": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.4.0.tgz", + "integrity": "sha512-tMsdNBgOsrUophCAFQl0XPe6Zqk/uy9gnue+jIIKhykO51hxyu6uNx7zBPy0+y/WKYVZZMspV9YeXLNdKk+iYw==", + "dev": true + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -1166,6 +1542,7 @@ "version": "0.1.4", "bundled": true, "dev": true, + "optional": true, "requires": { "kind-of": "^3.0.2", "longest": "^1.0.1", @@ -2348,7 +2725,8 @@ "longest": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "loose-envify": { "version": "1.3.1", @@ -3779,6 +4157,24 @@ "wrappy": "1" } }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + }, + "dependencies": { + "minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", + "dev": true + } + } + }, "parse-entities": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-1.1.2.tgz", @@ -3804,6 +4200,12 @@ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, "path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", @@ -3820,6 +4222,12 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, "proxy-addr": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.3.tgz", @@ -3885,6 +4293,15 @@ } } }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "requires": { + "resolve": "^1.1.6" + } + }, "remark": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/remark/-/remark-5.1.0.tgz", @@ -3962,6 +4379,15 @@ "uuid": "^3.1.0" } }, + "resolve": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", + "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -4055,6 +4481,12 @@ "util": ">=0.10.3 <1" } }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -4102,6 +4534,15 @@ "is-hexadecimal": "^1.0.0" } }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, "strip-bom": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-0.3.1.tgz", @@ -4120,6 +4561,12 @@ "boundary": "^1.0.1" } }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, "timekeeper": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/timekeeper/-/timekeeper-0.0.5.tgz", @@ -4158,6 +4605,94 @@ "integrity": "sha512-FHkoUZvG6Egrv9XZAyYGKEyb1JMsFphgPjoczkZC2y6W93U1jswcVURB8MUvtsahEPEVACyxD47JAL63vF4JsQ==", "dev": true }, + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", + "dev": true + }, + "tslint": { + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.12.1.tgz", + "integrity": "sha512-sfodBHOucFg6egff8d1BvuofoOQ/nOeYNfbp7LDlKBcLNrL3lmS5zoiDGyOMdT7YsEXAwWpTdAHwOGOc8eRZAw==", + "dev": true, + "requires": { + "babel-code-frame": "^6.22.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^3.2.0", + "glob": "^7.1.1", + "js-yaml": "^3.7.0", + "minimatch": "^3.0.4", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.8.0", + "tsutils": "^2.27.2" + }, + "dependencies": { + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "js-yaml": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.1.tgz", + "integrity": "sha512-um46hB9wNOKlwkHgiuyEVAybXBjwFUV0Z/RaHJblRd9DXltue9FTYvzCr9ErQrK9Adz5MU4gHWVaNUfdmrC8qA==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "dev": true + } + } + }, + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -4187,6 +4722,105 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, + "typedoc": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.14.2.tgz", + "integrity": "sha512-aEbgJXV8/KqaVhcedT7xG6d2r+mOvB5ep3eIz1KuB5sc4fDYXcepEEMdU7XSqLFO5hVPu0nllHi1QxX2h/QlpQ==", + "dev": true, + "requires": { + "@types/fs-extra": "^5.0.3", + "@types/handlebars": "^4.0.38", + "@types/highlight.js": "^9.12.3", + "@types/lodash": "^4.14.110", + "@types/marked": "^0.4.0", + "@types/minimatch": "3.0.3", + "@types/shelljs": "^0.8.0", + "fs-extra": "^7.0.0", + "handlebars": "^4.0.6", + "highlight.js": "^9.13.1", + "lodash": "^4.17.10", + "marked": "^0.4.0", + "minimatch": "^3.0.0", + "progress": "^2.0.0", + "shelljs": "^0.8.2", + "typedoc-default-themes": "^0.5.0", + "typescript": "3.2.x" + }, + "dependencies": { + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "shelljs": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.3.tgz", + "integrity": "sha512-fc0BKlAWiLpwZljmOvAOTE/gXawtCoNrP5oaY7KIaQbbyHeQVg01pSEuEGvGh3HEdBU4baCD7wQBwADmM/7f7A==", + "dev": true, + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + } + }, + "typescript": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.4.tgz", + "integrity": "sha512-0RNDbSdEokBeEAkgNbxJ+BLwSManFy9TeXz8uW+48j/xhEXv1ePME60olyzw2XzUqUBNAYFeJadIqAgNqIACwg==", + "dev": true + } + } + }, + "typedoc-default-themes": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/typedoc-default-themes/-/typedoc-default-themes-0.5.0.tgz", + "integrity": "sha1-bcJDPnjti+qOiHo6zeLzF4W9Yic=", + "dev": true + }, + "typescript": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.3.3.tgz", + "integrity": "sha512-Y21Xqe54TBVp+VDSNbuDYdGw0BpoR/Q6wo/+35M8PAU0vipahnyduJWirxxdxjsAkS7hue53x2zp8gz7F05u0A==", + "dev": true + }, + "uglify-js": { + "version": "3.4.9", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", + "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", + "dev": true, + "optional": true, + "requires": { + "commander": "~2.17.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "dev": true, + "optional": true + } + } + }, "underscore": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", @@ -4240,10 +4874,17 @@ "unist-util-is": "^2.1.1" } }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true }, "update-section": { "version": "0.3.3", @@ -4304,6 +4945,12 @@ "integrity": "sha512-zM5/l4lfw1CBoPx3Jimxoc5RNDAHHpk6AM6LM0pTIkm5SUSsx8ZekZ0PVdf0WEZ7kjlhSt7ZlqbRL6Cd6dBs6A==", "dev": true }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index c5f4951a2..c73356944 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,6 @@ "concat-stream": "^1.5.1", "debug": "^2.6.9", "ejs": "~2.5.5", - "finalhandler": "^1.0.3", "lodash": "^4.17.5", "httpntlm": "^1.5.2", "request": ">=2.9.0", @@ -31,9 +30,13 @@ "lib": "./lib" }, "scripts": { + "build": "tsc -p .", + "clean": "rm -rf lib", + "watch": "tsc -w -p .", "toc": "./node_modules/.bin/doctoc Readme.md --github --maxlevel 3", "cover": "nyc --reporter=lcov --reporter=html --reporter=text mocha --exit test/*-test.js test/security/*.js", "coveralls": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js -v", + "docs": "typedoc --out docs", "test": "mocha --timeout 10000 --bail --exit test/*-test.js test/security/*.js" }, "keywords": [ @@ -42,6 +45,13 @@ "license": "MIT", "devDependencies": { "@types/bluebird": "^3.5.1", + "@types/debug": "^4.1.1", + "@types/express": "^4.16.1", + "@types/lodash": "^4.14.121", + "@types/node": "^11.9.4", + "@types/request": "^2.48.1", + "@types/sax": "^1.0.1", + "@types/uuid": "^3.4.4", "body-parser": "^1.15.2", "colors": "^1.1.2", "coveralls": "^3.0.1", @@ -49,6 +59,7 @@ "doctoc": "^1.0.0", "duplexer": "~0.1.1", "express": "^4.14.0", + "finalhandler": "^1.1.1", "glob": "~3.2.8", "jshint": "2.3.0", "mocha": "~5.2.0", @@ -57,6 +68,9 @@ "semver": "~5.0.3", "should": "~3.3.0", "sinon": "^1.17.5", - "timekeeper": "~0.0.4" + "timekeeper": "~0.0.4", + "tslint": "^5.12.1", + "typedoc": "^0.14.2", + "typescript": "^3.3.3" } } diff --git a/lib/security/templates/wsse-security-header.ejs b/resources/templates/wsse-security-header.ejs similarity index 100% rename from lib/security/templates/wsse-security-header.ejs rename to resources/templates/wsse-security-header.ejs diff --git a/lib/security/templates/wsse-security-token.ejs b/resources/templates/wsse-security-token.ejs similarity index 100% rename from lib/security/templates/wsse-security-token.ejs rename to resources/templates/wsse-security-token.ejs diff --git a/src/client.ts b/src/client.ts new file mode 100644 index 000000000..76af2c80f --- /dev/null +++ b/src/client.ts @@ -0,0 +1,485 @@ +/* + * Copyright (c) 2011 Vinay Pulim + * MIT Licensed + */ + +import * as assert from 'assert'; +import * as BluebirdPromise from 'bluebird'; +import * as concatStream from 'concat-stream'; +import * as debugBuilder from 'debug'; +import { EventEmitter } from 'events'; +import * as _ from 'lodash'; +import { v4 as uuid4 } from 'uuid'; +import { HttpClient, Request } from './http'; +import { IHeaders, IOptions, ISecurity, SoapMethod } from './types'; +import { findPrefix } from './utils'; +import { WSDL } from './wsdl'; +import { IPort, OperationElement, ServiceElement } from './wsdl/elements'; + +const debug = debugBuilder('node-soap'); + +var nonIdentifierChars = /[^a-z$_0-9]/i; + +export interface ISoapError extends Error { + response?; + body?; +} + +// tslint:disable unified-signatures +// tslint:disable-next-line:interface-name +export interface Client { + emit(event: 'request', xml: string, eid: string): boolean; + emit(event: 'message', message: string, eid: string): boolean; + emit(event: 'soapError', error: any, eid: string): boolean; + emit(event: 'response', body: any, response: any, eid: string): boolean; + + /** Emitted before a request is sent. */ + on(event: 'request', listener: (xml: string, eid: string) => void): this; + /** Emitted before a request is sent, but only the body is passed to the event handler. Useful if you don't want to log /store Soap headers. */ + on(event: 'message', listener: (message: string, eid: string) => void): this; + /** Emitted when an erroneous response is received. */ + on(event: 'soapError', listener: (error, eid: string) => void): this; + /** Emitted after a response is received. This is emitted for all responses (both success and errors). */ + on(event: 'response', listener: (body: any, response: any, eid: string) => void): this; +} + +export class Client extends EventEmitter { + [method: string]: any; + /** contains last full soap request for client logging */ + public lastRequest: string; + + private wsdl: WSDL; + private httpClient: HttpClient; + private soapHeaders: string[]; + private httpHeaders: IHeaders; + private bodyAttributes: string[]; + private endpoint: string; + private security: ISecurity; + private SOAPAction: string; + private streamAllowed: boolean; + private normalizeNames: boolean; + private lastMessage: string; + private lastEndpoint: string; + private lastRequestHeaders; + private lastResponse; + private lastResponseHeaders; + private lastElapsedTime: number; + + constructor(wsdl: WSDL, endpoint?: string, options?: IOptions) { + super(); + options = options || {}; + this.wsdl = wsdl; + this._initializeOptions(options); + this._initializeServices(endpoint); + this.httpClient = options.httpClient || new HttpClient(options); + var promiseOptions: BluebirdPromise.PromisifyAllOptions = { multiArgs: true }; + if (options.overridePromiseSuffix) { + promiseOptions.suffix = options.overridePromiseSuffix; + } + BluebirdPromise.promisifyAll(this, promiseOptions); + } + + /** add soapHeader to soap:Header node */ + public addSoapHeader(soapHeader: any, name?: string, namespace?: string, xmlns?: string): number { + if (!this.soapHeaders) { + this.soapHeaders = []; + } + if (typeof soapHeader === 'object') { + soapHeader = this.wsdl.objectToXML(soapHeader, name, namespace, xmlns, true); + } + return this.soapHeaders.push(soapHeader) - 1; + } + + public changeSoapHeader(index: number, soapHeader: any, name?: string, namespace?: string, xmlns?: string): void { + if (!this.soapHeaders) { + this.soapHeaders = []; + } + if (typeof soapHeader === 'object') { + soapHeader = this.wsdl.objectToXML(soapHeader, name, namespace, xmlns, true); + } + this.soapHeaders[index] = soapHeader; + } + + /** return all defined headers */ + public getSoapHeaders(): string[] { + return this.soapHeaders; + } + + /** remove all defined headers */ + public clearSoapHeaders(): void { + this.soapHeaders = null; + } + + public addHttpHeader(name: string, value: any): void { + if (!this.httpHeaders) { + this.httpHeaders = {}; + } + this.httpHeaders[name] = value; + } + + public getHttpHeaders(): IHeaders { + return this.httpHeaders; + } + + public clearHttpHeaders(): void { + this.httpHeaders = {}; + } + + public addBodyAttribute(bodyAttribute: any, name?: string, namespace?: string, xmlns?: string): void { + if (!this.bodyAttributes) { + this.bodyAttributes = []; + } + if (typeof bodyAttribute === 'object') { + var composition = ''; + Object.getOwnPropertyNames(bodyAttribute).forEach(function(prop, idx, array) { + composition += ' ' + prop + '="' + bodyAttribute[prop] + '"'; + }); + bodyAttribute = composition; + } + if (bodyAttribute.substr(0, 1) !== ' ') bodyAttribute = ' ' + bodyAttribute; + this.bodyAttributes.push(bodyAttribute); + } + + public getBodyAttributes(): any[] { + return this.bodyAttributes; + } + + public clearBodyAttributes(): void { + this.bodyAttributes = null; + } + + /** overwrite the SOAP service endpoint address */ + public setEndpoint(endpoint: string): void { + this.endpoint = endpoint; + this._initializeServices(endpoint); + } + + /** description of services, ports and methods as a JavaScript object */ + public describe(): any { + return this.wsdl.describeServices(); + } + + /** use the specified security protocol */ + public setSecurity(security: ISecurity): void { + this.security = security; + } + + public setSOAPAction(SOAPAction: string): void { + this.SOAPAction = SOAPAction; + } + + private _initializeServices(endpoint: string) { + var definitions = this.wsdl.definitions, + services = definitions.services; + for (var name in services) { + this[name] = this._defineService(services[name], endpoint); + } + } + + private _initializeOptions(options: IOptions) { + this.streamAllowed = options.stream; + this.normalizeNames = options.normalizeNames; + this.wsdl.options.attributesKey = options.attributesKey || 'attributes'; + this.wsdl.options.envelopeKey = options.envelopeKey || 'soap'; + this.wsdl.options.preserveWhitespace = !!options.preserveWhitespace; + const igNs = options.ignoredNamespaces; + if (igNs !== undefined && typeof igNs === 'object') { + if ('override' in igNs) { + if (igNs.override === true) { + if (igNs.namespaces !== undefined) { + this.wsdl.options.ignoredNamespaces = igNs.namespaces; + } + } + } + } + if (options.overrideRootElement !== undefined) { + this.wsdl.options.overrideRootElement = options.overrideRootElement; + } + this.wsdl.options.forceSoap12Headers = !!options.forceSoap12Headers; + } + + private _defineService(service: ServiceElement, endpoint?: string) { + var ports = service.ports, + def = {}; + for (var name in ports) { + def[name] = this._definePort(ports[name], endpoint ? endpoint : ports[name].location); + } + return def; + } + + private _definePort(port: IPort, endpoint: string) { + var location = endpoint, + binding = port.binding, + methods = binding.methods, + def: { + [methodName: string]: SoapMethod; + } = {}; + for (var name in methods) { + def[name] = this._defineMethod(methods[name], location); + var methodName = this.normalizeNames ? name.replace(nonIdentifierChars, '_') : name; + this[methodName] = def[name]; + } + return def; + } + + private _defineMethod(method: OperationElement, location: string): SoapMethod { + var self = this; + var temp; + return function(args, callback, options, extraHeaders) { + if (typeof args === 'function') { + callback = args; + args = {}; + } else if (typeof options === 'function') { + temp = callback; + callback = options; + options = temp; + } else if (typeof extraHeaders === 'function') { + temp = callback; + callback = extraHeaders; + extraHeaders = options; + options = temp; + } + self._invoke(method, args, location, function(error, result, rawResponse, soapHeader, rawRequest) { + callback(error, result, rawResponse, soapHeader, rawRequest); + }, options, extraHeaders); + }; + } + + private _invoke(method: OperationElement, args, location: string, callback, options, extraHeaders) { + var self = this, + name = method.$name, + input = method.input, + output = method.output, + style = method.style, + defs = this.wsdl.definitions, + envelopeKey = this.wsdl.options.envelopeKey, + ns: string = defs.$targetNamespace, + encoding = '', + message = '', + xml: string = null, + req: Request, + soapAction: string, + alias = findPrefix(defs.xmlns, ns), + headers: any = { + "Content-Type": "text/xml; charset=utf-8", + }, + xmlnsSoap = "xmlns:" + envelopeKey + "=\"http://schemas.xmlsoap.org/soap/envelope/\""; + + if (this.wsdl.options.forceSoap12Headers) { + headers["Content-Type"] = "application/soap+xml; charset=utf-8"; + xmlnsSoap = "xmlns:" + envelopeKey + "=\"http://www.w3.org/2003/05/soap-envelope\""; + } + + if (this.SOAPAction) { + soapAction = this.SOAPAction; + } else if (method.soapAction !== undefined && method.soapAction !== null) { + soapAction = method.soapAction; + } else { + soapAction = ((ns.lastIndexOf("/") !== ns.length - 1) ? ns + "/" : ns) + name; + } + + if (!this.wsdl.options.forceSoap12Headers) { + headers.SOAPAction = '"' + soapAction + '"'; + } + + options = options || {}; + + // Add extra headers + for (var header in this.httpHeaders ) { headers[header] = this.httpHeaders[header]; } + for (var attr in extraHeaders) { headers[attr] = extraHeaders[attr]; } + + // Allow the security object to add headers + if (self.security && self.security.addHeaders) + self.security.addHeaders(headers); + if (self.security && self.security.addOptions) + self.security.addOptions(options); + + if ((style === 'rpc') && ( ( input.parts || input.name === "element" ) || args === null) ) { + assert.ok(!style || style === 'rpc', 'invalid message definition for document style binding'); + message = self.wsdl.objectToRpcXML(name, args, alias, ns, (input.name !== "element" )); + (method.inputSoap === 'encoded') && (encoding = 'soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" '); + } else { + assert.ok(!style || style === 'document', 'invalid message definition for rpc style binding'); + // pass `input.$lookupType` if `input.$type` could not be found + message = self.wsdl.objectToDocumentXML(input.$name, args, input.targetNSAlias, input.targetNamespace, (input.$type || input.$lookupType)); + } + xml = "" + + "<" + envelopeKey + ":Envelope " + + xmlnsSoap + " " + + "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " + + encoding + + this.wsdl.xmlnsInEnvelope + '>' + + ((self.soapHeaders || self.security) ? + ( + "<" + envelopeKey + ":Header>" + + (self.soapHeaders ? self.soapHeaders.join("\n") : "") + + (self.security && !self.security.postProcess ? self.security.toXML() : "") + + "" + ) + : + '' + ) + + "<" + envelopeKey + ":Body" + + (self.bodyAttributes ? self.bodyAttributes.join(' ') : '') + + (self.security && self.security.postProcess ? ' Id="_0"' : '') + + ">" + + message + + "" + + ""; + + if (self.security && self.security.postProcess){ + xml = self.security.postProcess(xml, envelopeKey); + } + + if (options && options.postProcess){ + xml = options.postProcess(xml); + } + + self.lastMessage = message; + self.lastRequest = xml; + self.lastEndpoint = location; + + var eid: string = options.exchangeId || uuid4(); + + self.emit('message', message, eid); + self.emit('request', xml, eid); + + var tryJSONparse = function(body) { + try { + return JSON.parse(body); + } + catch (err) { + return undefined; + } + }; + + if (this.streamAllowed && typeof self.httpClient.requestStream === 'function') { + callback = _.once(callback); + var startTime = Date.now(); + req = self.httpClient.requestStream(location, xml, headers, options, self); + self.lastRequestHeaders = req.headers; + var onError = function onError(err) { + self.lastResponse = null; + self.lastResponseHeaders = null; + self.lastElapsedTime = null; + self.emit('response', null, null, eid); + + callback(err, undefined, undefined, undefined, xml); + }; + req.on('error', onError); + req.on('response', function(response) { + response.on('error', onError); + + // When the output element cannot be looked up in the wsdl, play it safe and + // don't stream + if (response.statusCode !== 200 || !output || !output.$lookupTypes) { + response.pipe(concatStream({encoding: 'string'}, function(body) { + self.lastResponse = body; + self.lastResponseHeaders = response && response.headers; + self.lastElapsedTime = Date.now() - startTime; + self.emit('response', body, response, eid); + + return parseSync(body, response); + + })); + return; + } + + self.wsdl.xmlToObject(response, function(error, obj) { + self.lastResponse = response; + self.lastResponseHeaders = response && response.headers; + self.lastElapsedTime = Date.now() - startTime; + self.emit('response', '', response, eid); + + if (error) { + error.response = response; + error.body = ''; + self.emit('soapError', error, eid); + return callback(error, response, undefined, undefined, xml); + } + + return finish(obj, '', response); + }); + }); + return; + } + + req = self.httpClient.request(location, xml, function(err, response, body) { + self.lastResponse = body; + self.lastResponseHeaders = response && response.headers; + self.lastElapsedTime = response && response.elapsedTime; + self.emit('response', body, response, eid); + + if (err) { + callback(err, undefined, undefined, undefined, xml); + } else { + return parseSync(body, response); + } + }, headers, options, self); + + function parseSync(body, response) { + var obj; + try { + obj = self.wsdl.xmlToObject(body); + } catch (error) { + // When the output element cannot be looked up in the wsdl and the body is JSON + // instead of sending the error, we pass the body in the response. + if (!output || !output.$lookupTypes) { + debug('Response element is not present. Unable to convert response xml to json.'); + // If the response is JSON then return it as-is. + var json = _.isObject(body) ? body : tryJSONparse(body); + if (json) { + return callback(null, response, json, undefined, xml); + } + } + error.response = response; + error.body = body; + self.emit('soapError', error, eid); + return callback(error, response, body, undefined, xml); + } + return finish(obj, body, response); + } + + function finish(obj, body, response) { + var result; + + if (!output){ + // one-way, no output expected + return callback(null, null, body, obj.Header, xml); + } + + // If it's not HTML and Soap Body is empty + if (!obj.html && !obj.Body) { + return callback(null, obj, body, obj.Header); + } + + if ( typeof obj.Body !== 'object' ) { + var error: ISoapError = new Error('Cannot parse response'); + error.response = response; + error.body = body; + return callback(error, obj, body, undefined, xml); + } + + result = obj.Body[output.$name]; + // RPC/literal response body may contain elements with added suffixes I.E. + // 'Response', or 'Output', or 'Out' + // This doesn't necessarily equal the ouput message name. See WSDL 1.1 Section 2.4.5 + if (!result){ + result = obj.Body[output.$name.replace(/(?:Out(?:put)?|Response)$/, '')]; + } + if (!result) { + ['Response', 'Out', 'Output'].forEach(function(term) { + if (obj.Body.hasOwnProperty(name + term)) { + return result = obj.Body[name + term]; + } + }); + } + + callback(null, result, body, obj.Header, xml); + } + + // Added mostly for testability, but possibly useful for debugging + if (req && req.headers && !options.ntlm) // fixes an issue when req or req.headers is undefined, doesn't apply to ntlm requests + self.lastRequestHeaders = req.headers; + } +} diff --git a/src/http.ts b/src/http.ts new file mode 100644 index 000000000..d9411d79d --- /dev/null +++ b/src/http.ts @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2011 Vinay Pulim + * MIT Licensed + */ + +import * as debugBuilder from 'debug'; +import * as httpNtlm from 'httpntlm'; +import * as req from 'request'; +import * as url from 'url'; +import { IHeaders, IOptions } from './types'; + +const debug = debugBuilder('node-soap'); +var VERSION = require('../package.json').version; + +export interface IExOptions { + [key: string]: any; +} + +export type Request = req.Request; + +/** + * A class representing the http client + * @param {Object} [options] Options object. It allows the customization of + * `request` module + * + * @constructor + */ +export class HttpClient { + private _request: req.RequestAPI; + + constructor(options?: IOptions) { + options = options || {}; + this._request = options.request || req; + } + + /** + * Build the HTTP request (method, uri, headers, ...) + * @param {String} rurl The resource url + * @param {Object|String} data The payload + * @param {Object} exheaders Extra http headers + * @param {Object} exoptions Extra options + * @returns {Object} The http request object for the `request` module + */ + public buildRequest(rurl: string, data: any, exheaders?: IHeaders, exoptions?: IExOptions): any { + var curl = url.parse(rurl); + var secure = curl.protocol === 'https:'; + var host = curl.hostname; + var port = parseInt(curl.port, 10); + var path = [curl.pathname || '/', curl.search || '', curl.hash || ''].join(''); + var method = data ? 'POST' : 'GET'; + var headers: IHeaders = { + 'User-Agent': 'node-soap/' + VERSION, + 'Accept': 'text/html,application/xhtml+xml,application/xml,text/xml;q=0.9,*/*;q=0.8', + 'Accept-Encoding': 'none', + 'Accept-Charset': 'utf-8', + 'Connection': exoptions && exoptions.forever ? 'keep-alive' : 'close', + 'Host': host + (isNaN(port) ? '' : ':' + port), + }; + var mergeOptions = ['headers']; + + if (typeof data === 'string') { + headers['Content-Length'] = Buffer.byteLength(data, 'utf8'); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } + + exheaders = exheaders || {}; + for (const attr in exheaders) { + headers[attr] = exheaders[attr]; + } + + var options: req.Options = { + uri: curl, + method: method, + headers: headers, + followAllRedirects: true, + }; + + options.body = data; + + exoptions = exoptions || {}; + for (const attr in exoptions) { + if (mergeOptions.indexOf(attr) !== -1) { + for (const header in exoptions[attr]) { + options[attr][header] = exoptions[attr][header]; + } + } else { + options[attr] = exoptions[attr]; + } + } + debug('Http request: %j', options); + return options; + } + + /** + * Handle the http response + * @param {Object} The req object + * @param {Object} res The res object + * @param {Object} body The http body + * @param {Object} The parsed body + */ + public handleResponse(req: req.Request, res: req.Response, body: any) { + debug('Http response body: %j', body); + if (typeof body === 'string') { + // Remove any extra characters that appear before or after the SOAP + // envelope. + var match = + body.replace(//, "").match(/(?:<\?[^?]*\?>[\s]*)?<([^:]*):Envelope([\S\s]*)<\/\1:Envelope>/i); + if (match) { + body = match[0]; + } + } + return body; + } + + public request( + rurl: string, + data: any, + callback: (error: any, res?: any, body?: any) => any, + exheaders?: IHeaders, + exoptions?: IExOptions, + caller?, + ) { + var self = this; + var options = self.buildRequest(rurl, data, exheaders, exoptions); + var req: req.Request; + + if (exoptions !== undefined && exoptions.hasOwnProperty('ntlm')) { + // sadly when using ntlm nothing to return + // Not sure if this can be handled in a cleaner way rather than an if/else, + // will to tidy up if I get chance later, patches welcome - insanityinside + // TODO - should the following be uri? + options.url = rurl; + httpNtlm[options.method.toLowerCase()](options, function(err, res) { + if (err) { + return callback(err); + } + // if result is stream + if ( typeof res.body !== 'string') { + res.body = res.body.toString(); + } + res.body = self.handleResponse(req, res, res.body); + callback(null, res, res.body); + }); + } else { + req = self._request(options, function(err, res, body) { + if (err) { + return callback(err); + } + body = self.handleResponse(req, res, body); + callback(null, res, body); + }); + } + + return req; + } + + public requestStream(rurl: string, data: any, exheaders?: IHeaders, exoptions?: IExOptions, caller?): req.Request { + var self = this; + var options = self.buildRequest(rurl, data, exheaders, exoptions); + return self._request(options); + } +} diff --git a/src/nscontext.ts b/src/nscontext.ts new file mode 100644 index 000000000..e22ebd6b1 --- /dev/null +++ b/src/nscontext.ts @@ -0,0 +1,231 @@ + +interface INamespace { + declared: boolean; + prefix: string; + uri: string; +} + +/** + * Scope for XML namespaces + * @param {NamespaceScope} [parent] Parent scope + * @returns {NamespaceScope} + * @constructor + */ +class NamespaceScope { + public parent: NamespaceScope; + public namespaces: {[key: string]: INamespace}; + + constructor(parent: NamespaceScope) { + this.parent = parent; + this.namespaces = {}; + } + + /** + * Look up the namespace URI by prefix + * @param {String} prefix Namespace prefix + * @param {Boolean} [localOnly] Search current scope only + * @returns {String} Namespace URI + */ + public getNamespaceURI(prefix: string, localOnly?: boolean): string { + switch (prefix) { + case 'xml': + return 'http://www.w3.org/XML/1998/namespace'; + case 'xmlns': + return 'http://www.w3.org/2000/xmlns/'; + default: + var nsUri = this.namespaces[prefix]; + /*jshint -W116 */ + if (nsUri != null) { + return nsUri.uri; + } else if (!localOnly && this.parent) { + return this.parent.getNamespaceURI(prefix); + } else { + return null; + } + } + } + + public getNamespaceMapping(prefix: string): INamespace { + switch (prefix) { + case 'xml': + return { + uri: 'http://www.w3.org/XML/1998/namespace', + prefix: 'xml', + declared: true, + }; + case 'xmlns': + return { + uri: 'http://www.w3.org/2000/xmlns/', + prefix: 'xmlns', + declared: true, + }; + default: + var mapping = this.namespaces[prefix]; + /*jshint -W116 */ + if (mapping != null) { + return mapping; + } else if (this.parent) { + return this.parent.getNamespaceMapping(prefix); + } else { + return null; + } + } + } + + /** + * Look up the namespace prefix by URI + * @param {String} nsUri Namespace URI + * @param {Boolean} [localOnly] Search current scope only + * @returns {String} Namespace prefix + */ + public getPrefix(nsUri: string, localOnly?: boolean): string { + switch (nsUri) { + case 'http://www.w3.org/XML/1998/namespace': + return 'xml'; + case 'http://www.w3.org/2000/xmlns/': + return 'xmlns'; + default: + for (var p in this.namespaces) { + if (this.namespaces[p].uri === nsUri) { + return p; + } + } + if (!localOnly && this.parent) { + return this.parent.getPrefix(nsUri); + } else { + return null; + } + } + } +} + +/** + * Namespace context that manages hierarchical scopes + * @returns {NamespaceContext} + * @constructor + */ +export class NamespaceContext { + public scopes: NamespaceScope[]; + public prefixCount: number; + public currentScope?: NamespaceScope; + + constructor() { + this.scopes = []; + this.pushContext(); + this.prefixCount = 0; + } + + /** + * Add a prefix/URI namespace mapping + * @param {String} prefix Namespace prefix + * @param {String} nsUri Namespace URI + * @param {Boolean} [localOnly] Search current scope only + * @returns {boolean} true if the mapping is added or false if the mapping + * already exists + */ + public addNamespace(prefix: string, nsUri: string, localOnly?: boolean): boolean { + if (this.getNamespaceURI(prefix, localOnly) === nsUri) { + return false; + } + if (this.currentScope) { + this.currentScope.namespaces[prefix] = { + uri: nsUri, + prefix: prefix, + declared: false, + }; + return true; + } + return false; + } + + /** + * Push a scope into the context + * @returns {NamespaceScope} The current scope + */ + public pushContext(): NamespaceScope { + var scope = new NamespaceScope(this.currentScope); + this.scopes.push(scope); + this.currentScope = scope; + return scope; + } + + /** + * Pop a scope out of the context + * @returns {NamespaceScope} The removed scope + */ + public popContext(): NamespaceScope { + var scope = this.scopes.pop(); + if (scope) { + this.currentScope = scope.parent; + } else { + this.currentScope = null; + } + return scope; + } + + /** + * Look up the namespace URI by prefix + * @param {String} prefix Namespace prefix + * @param {Boolean} [localOnly] Search current scope only + * @returns {String} Namespace URI + */ + public getNamespaceURI(prefix: string, localOnly?: boolean): string { + return this.currentScope && this.currentScope.getNamespaceURI(prefix, localOnly); + } + + /** + * Look up the namespace prefix by URI + * @param {String} nsURI Namespace URI + * @param {Boolean} [localOnly] Search current scope only + * @returns {String} Namespace prefix + */ + public getPrefix(nsUri: string, localOnly?: boolean): string { + return this.currentScope && this.currentScope.getPrefix(nsUri, localOnly); + } + + /** + * Register a namespace + * @param {String} nsUri Namespace URI + * @returns {String} The matching or generated namespace prefix + */ + public registerNamespace(nsUri: string): string { + var prefix = this.getPrefix(nsUri); + if (prefix) { + // If the namespace has already mapped to a prefix + return prefix; + } else { + // Try to generate a unique namespace + while (true) { + prefix = 'ns' + (++this.prefixCount); + if (!this.getNamespaceURI(prefix)) { + // The prefix is not used + break; + } + } + } + this.addNamespace(prefix, nsUri, true); + return prefix; + } + + /** + * Declare a namespace prefix/uri mapping + * @param {String} prefix Namespace prefix + * @param {String} nsUri Namespace URI + * @returns {Boolean} true if the declaration is created + */ + public declareNamespace(prefix: string, nsUri: string): boolean { + if (this.currentScope) { + var mapping = this.currentScope.getNamespaceMapping(prefix); + if (mapping && mapping.uri === nsUri && mapping.declared) { + return false; + } + this.currentScope.namespaces[prefix] = { + uri: nsUri, + prefix: prefix, + declared: true, + }; + return true; + } + return false; + } +} diff --git a/src/security/BasicAuthSecurity.ts b/src/security/BasicAuthSecurity.ts new file mode 100644 index 000000000..9d283fbcd --- /dev/null +++ b/src/security/BasicAuthSecurity.ts @@ -0,0 +1,28 @@ + +import * as _ from 'lodash'; +import { IHeaders, ISecurity } from '../types'; + +export class BasicAuthSecurity implements ISecurity { + private _username: string; + private _password: string; + private defaults; + + constructor(username: string, password: string, defaults?: any) { + this._username = username; + this._password = password; + this.defaults = {}; + _.merge(this.defaults, defaults); + } + + public addHeaders(headers: IHeaders): void { + headers.Authorization = 'Basic ' + new Buffer((this._username + ':' + this._password) || '').toString('base64'); + } + + public toXML(): string { + return ''; + } + + public addOptions(options: any): void { + _.merge(options, this.defaults); + } +} diff --git a/src/security/BearerSecurity.ts b/src/security/BearerSecurity.ts new file mode 100644 index 000000000..13c34ade7 --- /dev/null +++ b/src/security/BearerSecurity.ts @@ -0,0 +1,26 @@ + +import * as _ from 'lodash'; +import { IHeaders, ISecurity } from '../types'; + +export class BearerSecurity implements ISecurity { + private defaults; + private _token: string; + + constructor(token: string, defaults?: any) { + this._token = token; + this.defaults = {}; + _.merge(this.defaults, defaults); + } + + public addHeaders(headers: IHeaders): void { + headers.Authorization = "Bearer " + this._token; + } + + public toXML(): string { + return ''; + } + + public addOptions(options: any): void { + _.merge(options, this.defaults); + } +} diff --git a/src/security/ClientSSLSecurity.ts b/src/security/ClientSSLSecurity.ts new file mode 100644 index 000000000..d180564b9 --- /dev/null +++ b/src/security/ClientSSLSecurity.ts @@ -0,0 +1,89 @@ + +import * as fs from 'fs'; +import * as https from 'https'; +import * as _ from 'lodash'; +import { ISecurity } from '../types'; + +/** + * activates SSL for an already existing client + * + * @module ClientSSLSecurity + * @param {Buffer|String} key + * @param {Buffer|String} cert + * @param {Buffer|String|Array} [ca] + * @param {Object} [defaults] + * @constructor + */ +export class ClientSSLSecurity implements ISecurity { + private key: Buffer; + private cert: Buffer; + private ca; + private defaults; + private agent: https.Agent; + + constructor(key: string | Buffer, cert: string | Buffer, defaults?: any); + constructor(key: string | Buffer, cert: string | Buffer, ca?: Buffer | string | any[], defaults?: any) { + if (key) { + if (Buffer.isBuffer(key)) { + this.key = key; + } else if (typeof key === 'string') { + this.key = fs.readFileSync(key); + } else { + throw new Error('key should be a buffer or a string!'); + } + } + + if (cert) { + if (Buffer.isBuffer(cert)) { + this.cert = cert; + } else if (typeof cert === 'string') { + this.cert = fs.readFileSync(cert); + } else { + throw new Error('cert should be a buffer or a string!'); + } + } + + if (ca) { + if (Buffer.isBuffer(ca) || Array.isArray(ca)) { + this.ca = ca; + } else if (typeof ca === 'string') { + this.ca = fs.readFileSync(ca); + } else { + defaults = ca; + this.ca = null; + } + } + + this.defaults = {}; + _.merge(this.defaults, defaults); + + this.agent = null; + } + + public toXML(): string { + return ''; + } + + public addOptions(options: any): void { + var httpsAgent = null; + + options.key = this.key; + options.cert = this.cert; + options.ca = this.ca; + _.merge(options, this.defaults); + + if (!!options.forever) { + if (!this.agent) { + options.keepAlive = true; + + this.agent = new https.Agent(options); + } + + httpsAgent = this.agent; + } else { + httpsAgent = new https.Agent(options); + } + + options.agent = httpsAgent; + } +} diff --git a/src/security/ClientSSLSecurityPFX.ts b/src/security/ClientSSLSecurityPFX.ts new file mode 100644 index 000000000..71418084d --- /dev/null +++ b/src/security/ClientSSLSecurityPFX.ts @@ -0,0 +1,56 @@ + +import * as fs from 'fs'; +import * as https from 'https'; +import * as _ from 'lodash'; +import { ISecurity } from '../types'; + +/** + * activates SSL for an already existing client using a PFX cert + * + * @module ClientSSLSecurityPFX + * @param {Buffer|String} pfx + * @param {String} passphrase + * @constructor + */ +export class ClientSSLSecurityPFX implements ISecurity { + private pfx: Buffer; + private defaults; + private passphrase: string; + + constructor(pfx: string | Buffer, defaults?: any); + constructor(pfx: string | Buffer, passphrase: string, defaults?: any) { + if (typeof passphrase === 'object') { + defaults = passphrase; + } + if (pfx) { + if (Buffer.isBuffer(pfx)) { + this.pfx = pfx; + } else if (typeof pfx === 'string') { + this.pfx = fs.readFileSync(pfx); + } else { + throw new Error('supplied pfx file should be a buffer or a file location'); + } + } + + if (passphrase) { + if (typeof passphrase === 'string') { + this.passphrase = passphrase; + } + } + this.defaults = {}; + _.merge(this.defaults, defaults); + } + + public toXML(): string { + return ''; + } + + public addOptions(options: any): void { + options.pfx = this.pfx; + if (this.passphrase) { + options.passphrase = this.passphrase; + } + _.merge(options, this.defaults); + options.agent = new https.Agent(options); + } +} diff --git a/src/security/NTLMSecurity.ts b/src/security/NTLMSecurity.ts new file mode 100644 index 000000000..63d91bac6 --- /dev/null +++ b/src/security/NTLMSecurity.ts @@ -0,0 +1,35 @@ + +import * as _ from 'lodash'; +import { IHeaders, ISecurity } from '../types'; + +export class NTLMSecurity implements ISecurity { + private defaults; + + constructor(defaults: any); + constructor(username: any, password?: string, domain?: string, workstation?: string) { + if (typeof username === "object") { + this.defaults = username; + this.defaults.ntlm = true; + } else { + this.defaults = { + ntlm: true, + username: username, + password: password, + domain: domain, + workstation: workstation, + }; + } + } + + public addHeaders(headers: IHeaders): void { + headers.Connection = 'keep-alive'; + } + + public toXML(): string { + return ''; + } + + public addOptions(options: any): void { + _.merge(options, this.defaults); + } +} diff --git a/src/security/WSSecurity.ts b/src/security/WSSecurity.ts new file mode 100644 index 000000000..1082f748f --- /dev/null +++ b/src/security/WSSecurity.ts @@ -0,0 +1,111 @@ + +import * as crypto from 'crypto'; +import { ISecurity } from '../types'; +import { passwordDigest } from '../utils'; + +var validPasswordTypes = ['PasswordDigest', 'PasswordText']; + +export interface IWSSecurityOptions { + passwordType?: string; + hasTimeStamp?: boolean; + hasNonce?: boolean; + hasTokenCreated?: boolean; + actor?: string; + mustUnderstand?; +} + +export class WSSecurity implements ISecurity { + private _username: string; + private _password: string; + private _passwordType: string; + private _hasTimeStamp: boolean; + private _hasNonce: boolean; + private _hasTokenCreated: boolean; + private _actor: string; + private _mustUnderstand: boolean; + + constructor(username: string, password: string, options?: string | IWSSecurityOptions) { + options = options || {}; + this._username = username; + this._password = password; + // must account for backward compatibility for passwordType String param as well as object options defaults: passwordType = 'PasswordText', hasTimeStamp = true + if (typeof options === 'string') { + this._passwordType = options ? options : 'PasswordText'; + options = {}; + } else { + this._passwordType = options.passwordType ? options.passwordType : 'PasswordText'; + } + + if (validPasswordTypes.indexOf(this._passwordType) === -1) { + this._passwordType = 'PasswordText'; + } + + this._hasTimeStamp = options.hasTimeStamp || typeof options.hasTimeStamp === 'boolean' ? !!options.hasTimeStamp : true; + /*jshint eqnull:true */ + if (options.hasNonce != null) { + this._hasNonce = !!options.hasNonce; + } + this._hasTokenCreated = options.hasTokenCreated || typeof options.hasTokenCreated === 'boolean' ? !!options.hasTokenCreated : true; + if (options.actor != null) { + this._actor = options.actor; + } + if (options.mustUnderstand != null) { + this._mustUnderstand = !!options.mustUnderstand; + } + } + + public toXML(): string { + // avoid dependency on date formatting libraries + function getDate(d) { + function pad(n) { + return n < 10 ? '0' + n : n; + } + return d.getUTCFullYear() + '-' + + pad(d.getUTCMonth() + 1) + '-' + + pad(d.getUTCDate()) + 'T' + + pad(d.getUTCHours()) + ':' + + pad(d.getUTCMinutes()) + ':' + + pad(d.getUTCSeconds()) + 'Z'; + } + var now = new Date(); + var created = getDate(now); + var timeStampXml = ''; + if (this._hasTimeStamp) { + var expires = getDate( new Date(now.getTime() + (1000 * 600)) ); + timeStampXml = "" + + "" + created + "" + + "" + expires + "" + + ""; + } + + var password, nonce; + if (this._hasNonce || this._passwordType !== 'PasswordText') { + // nonce = base64 ( sha1 ( created + random ) ) + var nHash = crypto.createHash('sha1'); + nHash.update(created + Math.random()); + nonce = nHash.digest('base64'); + } + if (this._passwordType === 'PasswordText') { + password = "" + this._password + ""; + if (nonce) { + password += "" + nonce + ""; + } + } else { + /* Specific Testcase for passwordDigest calculation cover this code + /* istanbul ignore next */ + password = "" + passwordDigest(nonce, created, this._password) + "" + + "" + nonce + ""; + } + + return "" + + timeStampXml + + "" + + "" + this._username + "" + + password + + (this._hasTokenCreated ? "" + created + "" : "") + + "" + + ""; + } +} diff --git a/src/security/WSSecurityCert.ts b/src/security/WSSecurityCert.ts new file mode 100644 index 000000000..aabf019ae --- /dev/null +++ b/src/security/WSSecurityCert.ts @@ -0,0 +1,107 @@ + +import * as ejs from 'ejs'; +import * as fs from 'fs'; +import * as path from 'path'; +import { v4 as uuid4 } from 'uuid'; +import { SignedXml } from 'xml-crypto'; +import { ISecurity } from '../types'; + +const templatesDir = path.resolve(__dirname, '..', '..', 'resources', 'templates'); +var wsseSecurityHeaderTemplate; +var wsseSecurityTokenTemplate; + +function addMinutes(date: Date, minutes: number) { + return new Date(date.getTime() + minutes * 60000); +} + +function dateStringForSOAP(date: Date): string { + return date.getUTCFullYear() + '-' + ('0' + (date.getUTCMonth() + 1)).slice(-2) + '-' + + ('0' + date.getUTCDate()).slice(-2) + 'T' + ('0' + date.getUTCHours()).slice(-2) + ":" + + ('0' + date.getUTCMinutes()).slice(-2) + ":" + ('0' + date.getUTCSeconds()).slice(-2) + "Z"; +} + +function generateCreated(): string { + return dateStringForSOAP(new Date()); +} + +function generateExpires(): string { + return dateStringForSOAP(addMinutes(new Date(), 10)); +} + +function insertStr(src: string, dst: string, pos: number): string { + return [dst.slice(0, pos), src, dst.slice(pos)].join(''); +} + +function generateId(): string { + return uuid4().replace(/-/gm, ''); +} + +export class WSSecurityCert implements ISecurity { + private publicP12PEM: string; + private signer: any; + private x509Id: string; + private hasTimeStamp: boolean; + private signatureTransformations: []; + private created: string; + private expires: string; + + constructor(privatePEM: any, publicP12PEM: any, password: any, options?: any) { + options = options || {}; + this.publicP12PEM = publicP12PEM.toString().replace('-----BEGIN CERTIFICATE-----', '').replace('-----END CERTIFICATE-----', '').replace(/(\r\n|\n|\r)/gm, ''); + + this.signer = new SignedXml(); + this.signer.signingKey = { + key: privatePEM, + passphrase: password, + }; + this.x509Id = "x509-" + generateId(); + this.hasTimeStamp = typeof options.hasTimeStamp === 'undefined' ? true : !!options.hasTimeStamp; + this.signatureTransformations = Array.isArray(options.signatureTransformations) ? options.signatureTransformations + : ["http://www.w3.org/2000/09/xmldsig#enveloped-signature", "http://www.w3.org/2001/10/xml-exc-c14n#"]; + + var _this = this; + this.signer.keyInfoProvider = {}; + this.signer.keyInfoProvider.getKeyInfo = function (key) { + if (!wsseSecurityTokenTemplate) { + wsseSecurityTokenTemplate = ejs.compile(fs.readFileSync(path.join(templatesDir, 'wsse-security-token.ejs')).toString()); + } + + return wsseSecurityTokenTemplate({ x509Id: _this.x509Id }); + }; + } + + public postProcess(xml, envelopeKey) { + this.created = generateCreated(); + this.expires = generateExpires(); + + if (!wsseSecurityHeaderTemplate) { + wsseSecurityHeaderTemplate = ejs.compile(fs.readFileSync(path.join(templatesDir, 'wsse-security-header.ejs')).toString()); + } + + var secHeader: string = wsseSecurityHeaderTemplate({ + binaryToken: this.publicP12PEM, + created: this.created, + expires: this.expires, + hasTimeStamp: this.hasTimeStamp, + id: this.x509Id, + }); + + var xmlWithSec = insertStr(secHeader, xml, xml.indexOf('')); + + var references = this.signatureTransformations; + + var bodyXpath = "//*[name(.)='" + envelopeKey + ":Body']"; + if (!(this.signer.references.filter(function(ref){ return ref.xpath === bodyXpath; }).length > 0)) { + this.signer.addReference(bodyXpath, references); + } + + var timestampXpath = "//*[name(.)='wsse:Security']/*[local-name(.)='Timestamp']"; + if (this.hasTimeStamp && !(this.signer.references.filter(function(ref){ return ref.xpath === timestampXpath; }).length > 0)) { + this.signer.addReference(timestampXpath, references); + } + + this.signer.computeSignature(xmlWithSec); + + return insertStr(this.signer.getSignatureXml(), xmlWithSec, xmlWithSec.indexOf('')); + } +} diff --git a/src/security/index.ts b/src/security/index.ts new file mode 100644 index 000000000..1fead54cd --- /dev/null +++ b/src/security/index.ts @@ -0,0 +1,8 @@ + +export * from './BasicAuthSecurity'; +export * from './BearerSecurity'; +export * from './ClientSSLSecurity'; +export * from './ClientSSLSecurityPFX'; +export * from './NTLMSecurity'; +export * from './WSSecurity'; +export * from './WSSecurityCert'; diff --git a/src/server.ts b/src/server.ts new file mode 100644 index 000000000..ddbc2c12d --- /dev/null +++ b/src/server.ts @@ -0,0 +1,594 @@ +/* + * Copyright (c) 2011 Vinay Pulim + * MIT Licensed + */ + +import { EventEmitter } from 'events'; +import * as http from 'http'; +import * as url from 'url'; +import { IOneWayOptions, ISecurity, IServerOptions, IServices, ISoapFault, ISoapServiceMethod } from "./types"; +import { findPrefix } from './utils'; +import { WSDL } from './wsdl'; +import { BindingElement, IPort } from './wsdl/elements'; + +let zlib; +try { + zlib = require("zlib"); +} catch (error) { +} + +interface IExpressApp { + route; + use; +} + +export type ServerType = http.Server | IExpressApp; +type Request = http.IncomingMessage & { body?: any }; +type Response = http.ServerResponse; + +function isExpress(server): server is IExpressApp { + return (typeof server.route === 'function' && typeof server.use === 'function'); +} + +function getDateString(d) { + function pad(n) { + return n < 10 ? '0' + n : n; + } + return d.getUTCFullYear() + '-' + + pad(d.getUTCMonth() + 1) + '-' + + pad(d.getUTCDate()) + 'T' + + pad(d.getUTCHours()) + ':' + + pad(d.getUTCMinutes()) + ':' + + pad(d.getUTCSeconds()) + 'Z'; +} + +// tslint:disable unified-signatures +// tslint:disable-next-line:interface-name +export interface Server { + emit(event: 'request', request: any, methodName: string): boolean; + emit(event: 'headers', headers: any, methodName: string): boolean; + + /** Emitted for every received messages. */ + on(event: 'request', listener: (request: any, methodName: string) => void): this; + /** Emitted when the SOAP Headers are not empty. */ + on(event: 'headers', listener: (headers: any, methodName: string) => void): this; +} + +interface IExecuteMethodOptions { + serviceName?: string; + portName?: string; + methodName?: string; + outputName?: string; + args?: any; + headers?: any; + style?: 'document' | 'rpc'; +} + +export class Server extends EventEmitter { + public path: string; + public services: IServices; + public log: (type: string, data: any) => any; + public authorizeConnection: (req: Request, res?: Response) => boolean; + public authenticate: (security: ISecurity, processAuthResult?) => boolean; + + private wsdl: WSDL; + private suppressStack: boolean; + private returnFault: boolean; + private onewayOptions: IOneWayOptions & { statusCode?: number; }; + private enableChunkedEncoding: boolean; + private soapHeaders: any[]; + + constructor(server: ServerType, path: string, services: IServices, wsdl: WSDL, options?: IServerOptions) { + super(); + var self = this; + + options = options || { + path: path, + services: services, + }; + this.path = path; + this.services = services; + this.wsdl = wsdl; + this.suppressStack = options && options.suppressStack; + this.returnFault = options && options.returnFault; + this.onewayOptions = options && options.oneWay || {}; + this.enableChunkedEncoding = + options.enableChunkedEncoding === undefined ? true : !!options.enableChunkedEncoding; + + if (path[path.length - 1] !== '/') + path += '/'; + wsdl.onReady(function (err) { + if (isExpress(server)) { + // handle only the required URL path for express server + server.route(path).all(function (req, res) { + if (typeof self.authorizeConnection === 'function') { + if (!self.authorizeConnection(req, res)) { + res.end(); + return; + } + } + self._requestListener(req, res); + }); + } else { + var listeners = server.listeners('request').slice(); + server.removeAllListeners('request'); + server.addListener('request', function(req, res) { + if (typeof self.authorizeConnection === 'function') { + if (!self.authorizeConnection(req, res)) { + res.end(); + return; + } + } + var reqPath = url.parse(req.url).pathname; + if (reqPath[reqPath.length - 1] !== '/') { + reqPath += '/'; + } + if (path === reqPath) { + self._requestListener(req, res); + } else { + for (var i = 0, len = listeners.length; i < len; i++) { + listeners[i].call(this, req, res); + } + } + }); + } + }); + + this._initializeOptions(options); + } + + public addSoapHeader(soapHeader: any, name?: string, namespace?: any, xmlns?: string): number { + if (!this.soapHeaders) { + this.soapHeaders = []; + } + soapHeader = this._processSoapHeader(soapHeader, name, namespace, xmlns); + return this.soapHeaders.push(soapHeader) - 1; + } + + public changeSoapHeader(index: any, soapHeader: any, name?: any, namespace?: any, xmlns?: any): void { + if (!this.soapHeaders) { + this.soapHeaders = []; + } + soapHeader = this._processSoapHeader(soapHeader, name, namespace, xmlns); + this.soapHeaders[index] = soapHeader; + } + + public getSoapHeaders(): string[] { + return this.soapHeaders; + } + + public clearSoapHeaders(): void { + this.soapHeaders = null; + } + + private _processSoapHeader(soapHeader, name, namespace, xmlns) { + var self = this; + + switch (typeof soapHeader) { + case 'object': + return this.wsdl.objectToXML(soapHeader, name, namespace, xmlns, true); + case 'function': + return function() { + var result = soapHeader.apply(null, arguments); + + if (typeof result === 'object') { + return self.wsdl.objectToXML(result, name, namespace, xmlns, true); + } else { + return result; + } + }; + default: + return soapHeader; + } + } + + private _initializeOptions(options: IServerOptions) { + this.wsdl.options.attributesKey = options.attributesKey || 'attributes'; + this.onewayOptions.statusCode = this.onewayOptions.responseCode || 200; + this.onewayOptions.emptyBody = !!this.onewayOptions.emptyBody; + } + + private _processRequestXml(req: Request, res: Response, xml) { + var self = this; + var result; + var error; + try { + if (typeof self.log === 'function') { + self.log("received", xml); + } + self._process(xml, req, function(result, statusCode) { + self._sendHttpResponse(res, statusCode, result); + if (typeof self.log === 'function') { + self.log("replied", result); + } + }); + } catch (err) { + if (err.Fault !== undefined) { + return self._sendError(err.Fault, function(result, statusCode) { + self._sendHttpResponse(res, statusCode || 500, result); + if (typeof self.log === 'function') { + self.log("error", err); + } + }, new Date().toISOString()); + } else { + error = err.stack ? (self.suppressStack === true ? err.message : err.stack) : err; + self._sendHttpResponse(res, /* statusCode */ 500, error); + if (typeof self.log === 'function') { + self.log("error", error); + } + } + } + } + + private _requestListener(req: Request, res: Response) { + var self = this; + var reqParse = url.parse(req.url); + var reqPath = reqParse.pathname; + var reqQuery = reqParse.search; + + if (typeof self.log === 'function') { + self.log("info", "Handling " + req.method + " on " + req.url); + } + + if (req.method === 'GET') { + if (reqQuery && reqQuery.toLowerCase() === '?wsdl') { + if (typeof self.log === 'function') { + self.log("info", "Wants the WSDL"); + } + res.setHeader("Content-Type", "application/xml"); + res.write(self.wsdl.toXML()); + } + res.end(); + } else if (req.method === 'POST') { + if (typeof req.headers['content-type'] !== "undefined") { + res.setHeader('Content-Type', req.headers['content-type']); + } else { + res.setHeader('Content-Type', "application/xml"); + } + + // request body is already provided by an express middleware + // in this case unzipping should also be done by the express middleware itself + if (req.body && Object.keys(req.body).length > 0) { + return self._processRequestXml(req, res, req.body.toString()); + } + + var chunks = [], gunzip, source = req; + if (req.headers["content-encoding"] === "gzip") { + gunzip = zlib.createGunzip(); + req.pipe(gunzip); + source = gunzip; + } + source.on('data', function (chunk) { + chunks.push(chunk); + }); + source.on('end', function () { + var xml = Buffer.concat(chunks).toString(); + var result; + var error; + self._processRequestXml(req, res, xml); + }); + } + else { + res.end(); + } + } + + private _process(input, req: Request, callback: (result: any, statusCode?: number) => any) { + var self = this, + pathname = url.parse(req.url).pathname.replace(/\/$/, ''), + obj = this.wsdl.xmlToObject(input), + body = obj.Body, + headers = obj.Header, + binding: BindingElement, + methodName: string, + serviceName: string, + portName: string, + includeTimestamp = obj.Header && obj.Header.Security && obj.Header.Security.Timestamp, + authenticate = self.authenticate || function defaultAuthenticate() { return true; }; + + function process() { + + if (typeof self.log === 'function') { + self.log("info", "Attempting to bind to " + pathname); + } + + // Avoid Cannot convert undefined or null to object due to Object.keys(body) + // and throw more meaningful error + if (!body) { + throw new Error('Failed to parse the SOAP Message body'); + } + + // use port.location and current url to find the right binding + binding = (function () { + var services = self.wsdl.definitions.services; + var firstPort: IPort; + var name; + for (name in services) { + serviceName = name; + var service = services[serviceName]; + var ports = service.ports; + for (name in ports) { + portName = name; + var port = ports[portName]; + var portPathname = url.parse(port.location).pathname.replace(/\/$/, ''); + + if (typeof self.log === 'function') { + self.log("info", "Trying " + portName + " from path " + portPathname); + } + + if (portPathname === pathname) + return port.binding; + + // The port path is almost always wrong for generated WSDLs + if (!firstPort) { + firstPort = port; + } + } + } + return !firstPort ? void 0 : firstPort.binding; + })(); + + if (!binding) { + throw new Error('Failed to bind to WSDL'); + } + + try { + if (binding.style === 'rpc') { + methodName = Object.keys(body)[0]; + + self.emit('request', obj, methodName); + if (headers) + self.emit('headers', headers, methodName); + + self._executeMethod({ + serviceName: serviceName, + portName: portName, + methodName: methodName, + outputName: methodName + 'Response', + args: body[methodName], + headers: headers, + style: 'rpc', + }, req, callback); + } else { + 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); + if (headers) + self.emit('headers', headers, pair.methodName); + + self._executeMethod({ + serviceName: serviceName, + portName: portName, + methodName: pair.methodName, + outputName: pair.outputName, + args: body[messageElemName], + headers: headers, + style: 'document', + }, req, callback, includeTimestamp); + } + } + catch (error) { + if (error.Fault !== undefined) { + return self._sendError(error.Fault, callback, includeTimestamp); + } + + throw error; + } + } + + // Authentication + + if (typeof authenticate === 'function') { + + var authResultProcessed = false, + processAuthResult = function(authResult) { + + if (!authResultProcessed && (authResult || authResult === false)) { + + authResultProcessed = true; + + if (authResult) { + + try { + process(); + } catch (error) { + + if (error.Fault !== undefined) { + return self._sendError(error.Fault, callback, includeTimestamp); + } + + return self._sendError({ + Code: { + Value: 'SOAP-ENV:Server', + Subcode: { value: 'InternalServerError' }, + }, + Reason: { Text: error.toString() }, + statusCode: 500, + }, callback, includeTimestamp); + } + + } else { + + return self._sendError({ + Code: { + Value: 'SOAP-ENV:Client', + Subcode: { value: 'AuthenticationFailure' }, + }, + Reason: { Text: 'Invalid username or password' }, + statusCode: 401, + }, callback, includeTimestamp); + } + } + }; + + processAuthResult(authenticate(obj.Header && obj.Header.Security, processAuthResult)); + + } else { + throw new Error('Invalid authenticate function (not a function)'); + } + } + + private _executeMethod( + options: IExecuteMethodOptions, + req: Request, + callback: (result: any, statusCode?: number) => any, + includeTimestamp?, + ) { + options = options || {}; + var self = this, + method: ISoapServiceMethod, + body, headers, + serviceName = options.serviceName, + portName = options.portName, + methodName = options.methodName, + outputName = options.outputName, + args = options.args, + style = options.style, + handled = false; + + if (this.soapHeaders) { + headers = this.soapHeaders.map(function(header) { + if (typeof header === 'function') { + return header(methodName, args, options.headers, req); + } else { + return header; + } + }).join("\n"); + } + + try { + method = this.services[serviceName][portName][methodName]; + } catch (error) { + return callback(this._envelope('', headers, includeTimestamp)); + } + + function handleResult(error, result?); + function handleResult(result); + function handleResult(error, result?) { + if (handled) + return; + handled = true; + + if (error && error.Fault !== undefined) { + return self._sendError(error.Fault, callback, includeTimestamp); + } + else if (result === undefined) { + // Backward compatibility to support one argument callback style + result = error; + } + + if (style === 'rpc') { + 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); + } + callback(self._envelope(body, headers, includeTimestamp)); + } + + if (!self.wsdl.definitions.services[serviceName].ports[portName].binding.methods[methodName].output) { + // no output defined = one-way operation so return empty response + handled = true; + body = ''; + if (this.onewayOptions.emptyBody) { + body = self._envelope('', headers, includeTimestamp); + } + callback(body, this.onewayOptions.responseCode); + } + + var result = method(args, handleResult, options.headers, req); + if (typeof result !== 'undefined') { + handleResult(result); + } + } + + private _envelope(body, headers, includeTimestamp) { + var defs = this.wsdl.definitions, + ns = defs.$targetNamespace, + encoding = '', + alias = findPrefix(defs.xmlns, ns); + + var envelopeDefinition = this.wsdl.options.forceSoap12Headers + ? "http://www.w3.org/2003/05/soap-envelope" + : "http://schemas.xmlsoap.org/soap/envelope/"; + + var xml = "" + + "'; + + headers = headers || ''; + + if (includeTimestamp) { + var now = new Date(); + var created = getDateString(now); + var expires = getDateString(new Date(now.getTime() + (1000 * 600))); + + headers += "" + + " " + + " " + created + "" + + " " + expires + "" + + " " + + " \n"; + } + + if (headers !== '') { + xml += "" + headers + ""; + } + + xml += body ? "" + body + "" : ""; + + xml += ""; + return xml; + } + + private _sendError(soapFault: ISoapFault, callback: (result: any, statusCode?: number) => any, includeTimestamp) { + var self = this, + fault; + + var statusCode: number; + if (soapFault.statusCode) { + statusCode = soapFault.statusCode; + soapFault.statusCode = undefined; + } + + if ('faultcode' in soapFault) { + // Soap 1.1 error style + // Root element will be prependend with the soap NS + // It must match the NS defined in the Envelope (set by the _envelope method) + fault = self.wsdl.objectToDocumentXML("soap:Fault", soapFault, undefined); + } + else { + // Soap 1.2 error style. + // 3rd param is the NS prepended to all elements + // It must match the NS defined in the Envelope (set by the _envelope method) + fault = self.wsdl.objectToDocumentXML("Fault", soapFault, "soap"); + } + + return callback(self._envelope(fault, '', includeTimestamp), statusCode); + } + + private _sendHttpResponse(res: Response, statusCode: number, result) { + if (statusCode) { + res.statusCode = statusCode; + } + + /* + * Calling res.write(result) follow by res.end() will cause Node.js to use + * chunked encoding, while calling res.end(result) directly will cause + * Node.js to calculate and send Content-Length header. See + * nodejs/node#26005. + */ + + if (this.enableChunkedEncoding) { + res.write(result); + res.end(); + } + else { + res.end(result); + } + } +} diff --git a/src/soap.ts b/src/soap.ts new file mode 100644 index 000000000..0d31aded6 --- /dev/null +++ b/src/soap.ts @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2011 Vinay Pulim + * MIT Licensed + */ + +import * as BluebirdPromise from 'bluebird'; +import * as debugBuilder from 'debug'; +import { Client } from './client'; +import * as _security from './security'; +import { Server, ServerType } from './server'; +import { IOptions, IServerOptions } from './types'; +import { open_wsdl, WSDL } from './wsdl'; + +const debug = debugBuilder('node-soap:soap'); + +export const security = _security; +export { Client } from './client'; +export { HttpClient } from './http'; +export { BasicAuthSecurity, BearerSecurity, ClientSSLSecurity, ClientSSLSecurityPFX, NTLMSecurity, WSSecurity, WSSecurityCert } from './security'; +export { Server } from './server'; +export { passwordDigest } from './utils'; +export * from './types'; + +type WSDLCallback = (error: any, result?: WSDL) => any; + +function createCache() { + var cache: { + [key: string]: WSDL, + } = {}; + return function (key: string, load: (cb: WSDLCallback) => any, callback: WSDLCallback) { + if (!cache[key]) { + load(function (err, result) { + if (err) { + return callback(err); + } + cache[key] = result; + callback(null, result); + }); + } else { + process.nextTick(function () { + callback(null, cache[key]); + }); + } + }; +} +var getFromCache = createCache(); + +function _requestWSDL(url: string, options: IOptions, callback: WSDLCallback) { + if (typeof options === 'function') { + callback = options; + options = {}; + } + var openWsdl = function(callback: WSDLCallback) { + open_wsdl(url, options, callback); + }; + + if (options.disableCache === true) { + openWsdl(callback); + } else { + getFromCache(url, openWsdl, callback); + } +} + +export type CreateClientCallback = (err: any, client: Client) => void; + +export function createClient(url: string, callback: CreateClientCallback, endpoint?: string): void; +export function createClient(url: string, options: IOptions, callback: CreateClientCallback, endpoint?: string): void; +export function createClient(url: string, p2: CreateClientCallback | IOptions, p3?: CreateClientCallback | string, p4?: string): void { + let endpoint: string = p4; + let callback: CreateClientCallback; + let options: IOptions; + if (typeof p2 === 'function') { + callback = p2; + endpoint = p3 as string; + options = {}; + } else if (typeof p3 === 'function') { + options = p2; + callback = p3; + endpoint = p4; + } + endpoint = options.endpoint || endpoint; + _requestWSDL(url, options, function(err, wsdl) { + callback(err, wsdl && new Client(wsdl, endpoint, options)); + }); +} + +export function createClientAsync(url: string, options: IOptions, endpoint?: string): BluebirdPromise { + if (typeof options === 'undefined') { + options = {}; + } + return new BluebirdPromise(function(resolve, reject) { + createClient(url, options, function(err, client) { + if (err) { + reject(err); + } + resolve(client); + }, endpoint); + }); +} + +export function listen(server: ServerType, path: string, services: any, wsdl: string): Server; +export function listen(server: ServerType, options: IServerOptions): Server; +export function listen(server: ServerType, p2: string | IServerOptions, services?: any, xml?: string): Server { + let options: IServerOptions; + let path: string; + let uri = ''; + + if (typeof p2 === 'object') { + // p2 is options + // server, options + options = p2; + path = options.path; + services = options.services; + xml = options.xml; + uri = options.uri; + } else { + // p2 is path + // server, path, services, wsdl + path = p2; + options = { + path: p2, + services: services, + }; + } + + var wsdl = new WSDL(xml || services, uri, options); + return new Server(server, path, services, wsdl, options); +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 000000000..ba78f9123 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,136 @@ + +import * as req from 'request'; +import { HttpClient } from './http'; + +export interface IHeaders { + [k: string]: any; +} + +/** @deprecated use SoapMethod */ +export type ISoapMethod = SoapMethod; +export type SoapMethod = ( + args: any, + callback: (err: any, result: any, rawResponse: any, soapHeader: any, rawRequest: any) => void, + options?: any, + extraHeaders?: any, +) => void; + +export type ISoapServiceMethod = (args: any, callback?: (data: any) => void, headers?: any, req?: any) => any; + +// SOAP Fault 1.1 & 1.2 +export interface ISoapFaultError { + Fault: ISoapFault; +} + +export type ISoapFault = ISoapFault11 | ISoapFault12; + +// SOAP Fault 1.1 +export interface ISoapFault11 { + faultcode: number | string; + faultstring: string; + detail?: string; + statusCode?: number; +} + +// SOAP Fault 1.2 +// 1.2 also supports additional, optional elements: +// Role, Node, Detail. Should be added when soap module implements them +// https://www.w3.org/TR/soap12/#soapfault +export interface ISoapFault12 { + Code: { Value: string; Subcode?: { value: string; }; }; + Reason: { Text: string; }; + statusCode?: number; +} + +/** @deprecated use ISecurity */ +export type Security = ISecurity; +export interface ISecurity { + addOptions?(options: any): void; + toXML?(): string; + addHeaders?(headers: IHeaders): void; + postProcess?(xml, envelopeKey): string; +} + +export interface IServicePort { + [methodName: string]: ISoapServiceMethod; +} + +export interface IService { + [portName: string]: IServicePort; +} + +export interface IServices { + [serviceName: string]: IService; +} + +export interface IXmlAttribute { + name: string; + value: string; +} + +export interface IWsdlBaseOptions { + attributesKey?: string; + valueKey?: string; + xmlKey?: string; + overrideRootElement?: { namespace: string; xmlnsAttributes?: IXmlAttribute[]; }; + ignoredNamespaces?: boolean | string[] | { namespaces?: string[]; override?: boolean; }; + ignoreBaseNameSpaces?: boolean; + /** escape special XML characters in SOAP message (e.g. &, >, < etc), default: true. */ + escapeXML?: boolean; + /** return an Invalid XML SOAP fault on a bad request, default: false. */ + returnFault?: boolean; + handleNilAsNull?: boolean; + /** if your wsdl operations contains names with non identifier characters ([^a-z$_0-9]), replace them with _. Note: if using this option, clients using wsdls with two operations like soap:method and soap-method will be overwritten. Then, use bracket notation instead (client['soap:method']()). */ + normalizeNames?: boolean; + /** to preserve leading and trailing whitespace characters in text and cdata. */ + preserveWhitespace?: boolean; + /** provides support for nonstandard array semantics. If true, JSON arrays of the form {list: [{elem: 1}, {elem: 2}]} are marshalled into xml as 1 2. If false, marshalls into 1 2 . Default: true. */ + namespaceArrayElements?: boolean; + useEmptyTag?: boolean; + strict?: boolean; + /** custom HTTP headers to be sent on WSDL requests. */ + wsdl_headers?: { [key: string]: any }; + /** custom options for the request module on WSDL requests. */ + wsdl_options?: { [key: string]: any }; +} + +/** @deprecated use IOptions */ +export type Option = IOptions; +export interface IOptions extends IWsdlBaseOptions { + /** don't cache WSDL files, request them every time. */ + disableCache?: boolean; + /** override the SOAP service's host specified in the .wsdl file. */ + endpoint?: string; + /** set specific key instead of
. */ + envelopeKey?: string; + /** provide your own http client that implements request(rurl, data, callback, exheaders, exoptions) */ + httpClient?: HttpClient; + /** override the request module. */ + request?: req.RequestAPI; + stream?: boolean; + // wsdl options that only work for client + /** set proper headers for SOAP v1.2. */ + forceSoap12Headers?: boolean; + customDeserializer?: any; + /** if your wsdl operations contains names with Async suffix, you will need to override the default promise suffix to a custom one, default: Async. */ + overridePromiseSuffix?: string; + /** @internal */ + WSDL_CACHE?; +} + +export interface IOneWayOptions { + responseCode?: number; + emptyBody?: boolean; +} + +export interface IServerOptions extends IWsdlBaseOptions { + path: string; + services: IServices; + xml?: string; + uri?: string; + /** suppress the full stack trace for error messages. */ + suppressStack?: boolean; + oneWay?: IOneWayOptions; + /** A boolean for controlling chunked transfer encoding in response. Some client (such as Windows 10's MDM enrollment SOAP client) is sensitive to transfer-encoding mode and can't accept chunked response. This option let user disable chunked transfer encoding for such a client. Default to true for backward compatibility. */ + enableChunkedEncoding?: boolean; +} diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 000000000..e8a71b292 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,50 @@ + +import * as crypto from 'crypto'; + +export function passwordDigest(nonce: string, created: string, password: string): string { + // digest = base64 ( sha1 ( nonce + created + password ) ) + var pwHash = crypto.createHash('sha1'); + + var NonceBytes = Buffer.from(nonce || '', 'base64'); + var CreatedBytes = Buffer.from(created || '', 'utf8'); + var PasswordBytes = Buffer.from(password || '', 'utf8'); + var FullBytes = Buffer.concat([NonceBytes, CreatedBytes, PasswordBytes ]); + + pwHash.update(FullBytes); + return pwHash.digest('base64'); +} + +export const TNS_PREFIX = '__tns__'; // Prefix for targetNamespace + +/** + * Find a key from an object based on the value + * @param {Object} Namespace prefix/uri mapping + * @param {*} nsURI value + * @returns {String} The matching key + */ +export function findPrefix(xmlnsMapping, nsURI) { + for (var n in xmlnsMapping) { + if (n === TNS_PREFIX) continue; + if (xmlnsMapping[n] === nsURI) { + return n; + } + } +} + +export function splitQName(nsName: T) { + if (typeof nsName !== 'string') { + return { + prefix: TNS_PREFIX, + name: nsName, + }; + } + + const [topLevelName] = nsName.split('|'); + + const prefixOffset = topLevelName.indexOf(':'); + + return { + prefix: topLevelName.substring(0, prefixOffset) || TNS_PREFIX, + name: topLevelName.substring(prefixOffset + 1), + }; +} diff --git a/src/wsdl/elements.ts b/src/wsdl/elements.ts new file mode 100644 index 000000000..007e2b6e6 --- /dev/null +++ b/src/wsdl/elements.ts @@ -0,0 +1,1173 @@ + +import { ok as assert } from 'assert'; +import * as debugBuilder from 'debug'; +import * as _ from 'lodash'; +import { IWsdlBaseOptions } from '../types'; +import { splitQName, TNS_PREFIX } from '../utils'; + +const debug = debugBuilder('node-soap'); + +var Primitives: { + [type: string]: number; +} = { + string: 1, + boolean: 1, + decimal: 1, + float: 1, + double: 1, + anyType: 1, + byte: 1, + int: 1, + long: 1, + short: 1, + negativeInteger: 1, + nonNegativeInteger: 1, + positiveInteger: 1, + nonPositiveInteger: 1, + unsignedByte: 1, + unsignedInt: 1, + unsignedLong: 1, + unsignedShort: 1, + duration: 0, + dateTime: 0, + time: 0, + date: 0, + gYearMonth: 0, + gYear: 0, + gMonthDay: 0, + gDay: 0, + gMonth: 0, + hexBinary: 0, + base64Binary: 0, + anyURI: 0, + QName: 0, + NOTATION: 0, +}; + +export interface IWsdlXmlns { + wsu?: string; + wsp?: string; + wsam?: string; + soap?: string; + tns?: string; + xsd?: string; + __tns__?: string; + [prop: string]: string | void; +} + +export interface IXmlNs { + [key: string]: string; +} + +export class Element { + public readonly allowedChildren?: {[k: string]: typeof Element} = {}; + public $name?: string; + public $targetNamespace?; + public children: Element[] = []; + public ignoredNamespaces; + public name?: string; + public nsName?; + public prefix?: string; + public schemaXmlns?; + public valueKey: string; + public xmlKey; + public xmlns?: IXmlNs; + + constructor(nsName: string, attrs, options?: IWsdlBaseOptions, schemaAttrs?) { + var parts = splitQName(nsName); + + this.nsName = nsName; + this.prefix = parts.prefix; + this.name = parts.name; + this.children = []; + this.xmlns = {}; + this.schemaXmlns = {}; + + this._initializeOptions(options); + + for (var key in attrs) { + var match = /^xmlns:?(.*)$/.exec(key); + if (match) { + this.xmlns[match[1] ? match[1] : TNS_PREFIX] = attrs[key]; + } + else { + if (key === 'value') { + this[this.valueKey] = attrs[key]; + } else { + this['$' + key] = attrs[key]; + } + } + } + for (var schemaKey in schemaAttrs) { + var schemaMatch = /^xmlns:?(.*)$/.exec(schemaKey); + if (schemaMatch && schemaMatch[1]) { + this.schemaXmlns[schemaMatch[1]] = schemaAttrs[schemaKey]; + } + } + if (this.$targetNamespace !== undefined) { + // Add targetNamespace to the mapping + this.xmlns[TNS_PREFIX] = this.$targetNamespace; + } + + this.init(); + } + + public deleteFixedAttrs() { + this.children && this.children.length === 0 && delete this.children; + this.xmlns && Object.keys(this.xmlns).length === 0 && delete this.xmlns; + delete this.nsName; + delete this.prefix; + delete this.name; + } + + public startElement(stack: Element[], nsName: string, attrs, options: IWsdlBaseOptions, schemaXmlns) { + if (!this.allowedChildren) { + return; + } + + var ChildClass = this.allowedChildren[splitQName(nsName).name]; + if (ChildClass) { + const child = new ChildClass(nsName, attrs, options, schemaXmlns); + child.init(); + stack.push(child); + } + else { + this.unexpected(nsName); + } + + } + + public endElement(stack: Element[], nsName: string) { + if (this.nsName === nsName) { + if (stack.length < 2) + return; + var parent = stack[stack.length - 2]; + if (this !== stack[0]) { + _.defaultsDeep(stack[0].xmlns, this.xmlns); + // delete this.xmlns; + parent.children.push(this); + parent.addChild(this); + } + stack.pop(); + } + } + + public addChild(child: Element) { + return; + } + + public unexpected(name: string) { + throw new Error('Found unexpected element (' + name + ') inside ' + this.nsName); + } + + public description(definitions?: DefinitionsElement, xmlns?: IXmlNs): any { + return this.$name || this.name; + } + + public init(): void { + } + + private _initializeOptions(options: IWsdlBaseOptions) { + if (options) { + this.valueKey = options.valueKey || '$value'; + this.xmlKey = options.xmlKey || '$xml'; + this.ignoredNamespaces = options.ignoredNamespaces || []; + } else { + this.valueKey = '$value'; + this.xmlKey = '$xml'; + this.ignoredNamespaces = []; + } + } +} + +export class ElementElement extends Element { + public readonly allowedChildren = buildAllowedChildren([ + 'annotation', + 'complexType', + 'simpleType', + ]); + public $minOccurs?: number | string; + public $maxOccurs?: number | string; + public $type?: string; + public $ref?: string; + public targetNSAlias?: string; + public targetNamespace?: string; + public $lookupType?: string; + public $lookupTypes?: any[]; + + public description(definitions: DefinitionsElement, xmlns?: IXmlNs) { + var element = {}, + name = this.$name; + var isMany = !this.$maxOccurs ? false : (typeof this.$maxOccurs === 'string' ? (this.$maxOccurs === 'unbounded') : (this.$maxOccurs > 1)); + if (this.$minOccurs !== this.$maxOccurs && isMany) { + name += '[]'; + } + + if (xmlns && xmlns[TNS_PREFIX]) { + this.$targetNamespace = xmlns[TNS_PREFIX]; + } + var type: any = this.$type || this.$ref; + if (type) { + type = splitQName(type); + var typeName: string = type.name, + ns: string = xmlns && xmlns[type.prefix] || + (definitions.xmlns[type.prefix] !== undefined && this.schemaXmlns[type.prefix]) || + definitions.xmlns[type.prefix], + schema = definitions.schemas[ns], + typeElement = schema && ( this.$type ? schema.complexTypes[typeName] || schema.types[typeName] : schema.elements[typeName] ); + + if (ns && definitions.schemas[ns]) { + xmlns = definitions.schemas[ns].xmlns; + } + + if (typeElement && !(typeName in Primitives)) { + + if (!(typeName in definitions.descriptions.types)) { + + var elem: any = {}; + definitions.descriptions.types[typeName] = elem; + var description = typeElement.description(definitions, xmlns); + if (typeof description === 'string') { + elem = description; + } + else { + Object.keys(description).forEach(function (key) { + elem[key] = description[key]; + }); + } + + if (this.$ref) { + element = elem; + } + else { + element[name] = elem; + } + + if (typeof elem === 'object') { + elem.targetNSAlias = type.prefix; + elem.targetNamespace = ns; + } + + definitions.descriptions.types[typeName] = elem; + } + else { + if (this.$ref) { + element = definitions.descriptions.types[typeName]; + } + else { + element[name] = definitions.descriptions.types[typeName]; + } + } + + } + else { + element[name] = this.$type; + } + } + else { + var children = this.children; + element[name] = {}; + for (const child of children) { + if (child instanceof ComplexTypeElement || child instanceof SimpleTypeElement) { + element[name] = child.description(definitions, xmlns); + } + } + } + return element; + } +} + +export class AnyElement extends Element { +} + +export class InputElement extends Element { + public readonly allowedChildren = buildAllowedChildren([ + 'body', + 'documentation', + 'header', + 'SecuritySpecRef', + ]); + public use: string; + public encodingStyle: string; + public $type: string; + public $lookupType: string; + public targetNSAlias?: string; + public targetNamespace?: string; + public parts?; + + public addChild(child: Element) { + if (child instanceof BodyElement) { + this.use = child.$use; + if (this.use === 'encoded') { + this.encodingStyle = child.$encodingStyle; + } + this.children.pop(); + } + } +} + +export class OutputElement extends Element { + public readonly allowedChildren = buildAllowedChildren([ + 'body', + 'documentation', + 'header', + 'SecuritySpecRef', + ]); + public targetNSAlias?: string; + public targetNamespace?: string; + public use?: string; + public encodingStyle?: string; + public $lookupTypes; + + public addChild(child: Element) { + if (child instanceof BodyElement) { + this.use = child.$use; + if (this.use === 'encoded') { + this.encodingStyle = child.$encodingStyle; + } + this.children.pop(); + } + } +} + +export class SimpleTypeElement extends Element { + public readonly allowedChildren = buildAllowedChildren([ + 'restriction', + ]); + + public description(definitions: DefinitionsElement) { + for (const child of this.children) { + if (child instanceof RestrictionElement) + return [this.$name, child.description()].filter(Boolean).join('|'); + } + return {}; + } +} + +export class RestrictionElement extends Element { + public readonly allowedChildren = buildAllowedChildren([ + 'all', + 'choice', + 'enumeration', + 'sequence', + ]); + public $base: string; + + public description(definitions?: DefinitionsElement, xmlns?: IXmlNs) { + var children = this.children; + var desc; + for (var i = 0, child; child = children[i]; i++) { + if (child instanceof SequenceElement || child instanceof ChoiceElement) { + desc = child.description(definitions, xmlns); + break; + } + } + if (desc && this.$base) { + var type = splitQName(this.$base), + typeName = type.name, + ns = xmlns && xmlns[type.prefix] || definitions.xmlns[type.prefix], + schema = definitions.schemas[ns], + typeElement = schema && ( schema.complexTypes[typeName] || schema.types[typeName] || schema.elements[typeName] ); + + desc.getBase = function() { + return typeElement.description(definitions, schema.xmlns); + }; + return desc; + } + + // then simple element + var base = this.$base ? this.$base + "|" : ""; + var restrictions = this.children.map(function(child) { + return child.description(); + }).join(","); + + return [this.$base, restrictions].filter(Boolean).join('|'); + } +} + +export class ExtensionElement extends Element { + public readonly allowedChildren = buildAllowedChildren([ + 'all', + 'choice', + 'sequence', + ]); + public $base: string; + + public description(definitions: DefinitionsElement, xmlns?: IXmlNs) { + var desc = {}; + for (const child of this.children) { + if (child instanceof SequenceElement || child instanceof ChoiceElement) { + desc = child.description(definitions, xmlns); + } + } + if (this.$base) { + var type = splitQName(this.$base), + typeName = type.name, + ns = xmlns && xmlns[type.prefix] || definitions.xmlns[type.prefix], + schema = definitions.schemas[ns]; + + if (typeName in Primitives) { + return this.$base; + } + else { + var typeElement = schema && ( schema.complexTypes[typeName] || + schema.types[typeName] || schema.elements[typeName] ); + + if (typeElement) { + var base = typeElement.description(definitions, schema.xmlns); + desc = _.defaultsDeep(base, desc); + } + } + } + return desc; + } +} + +export class ChoiceElement extends Element { + public readonly allowedChildren = buildAllowedChildren([ + 'any', + 'choice', + 'element', + 'sequence', + ]); + public description(definitions: DefinitionsElement, xmlns: IXmlNs) { + var choice = {}; + for (const child of this.children) { + var description = child.description(definitions, xmlns); + for (var key in description) { + choice[key] = description[key]; + } + } + return choice; + } +} + +export class EnumerationElement extends Element { + // no children + public description(): string { + return this[this.valueKey]; + } +} + +export class ComplexTypeElement extends Element { + public readonly allowedChildren = buildAllowedChildren([ + 'all', + 'annotation', + 'choice', + 'complexContent', + 'sequence', + 'simpleContent', + ]); + public description(definitions: DefinitionsElement, xmlns: IXmlNs) { + var children = this.children || []; + for (const child of children) { + if (child instanceof ChoiceElement || + child instanceof SequenceElement || + child instanceof AllElement || + child instanceof SimpleContentElement || + child instanceof ComplexContentElement) { + + return child.description(definitions, xmlns); + } + } + return {}; + } +} + +export class ComplexContentElement extends Element { + public readonly allowedChildren = buildAllowedChildren([ + 'extension', + ]); + public description(definitions: DefinitionsElement, xmlns: IXmlNs) { + for (const child of this.children) { + if (child instanceof ExtensionElement) { + return child.description(definitions, xmlns); + } + } + return {}; + } +} + +export class SimpleContentElement extends Element { + public readonly allowedChildren = buildAllowedChildren([ + 'extension', + ]); + public description(definitions: DefinitionsElement, xmlns: IXmlNs) { + for (const child of this.children) { + if (child instanceof ExtensionElement) { + return child.description(definitions, xmlns); + } + } + return {}; + } +} + +export class SequenceElement extends Element { + public readonly allowedChildren = buildAllowedChildren([ + 'any', + 'choice', + 'element', + 'sequence', + ]); + public description(definitions: DefinitionsElement, xmlns: IXmlNs) { + var sequence = {}; + for (const child of this.children) { + if (child instanceof AnyElement) { + continue; + } + var description = child.description(definitions, xmlns); + for (var key in description) { + sequence[key] = description[key]; + } + } + return sequence; + } +} + +export class AllElement extends Element { + public readonly allowedChildren = buildAllowedChildren([ + 'choice', + 'element', + ]); + public description(definitions: DefinitionsElement, xmlns: IXmlNs) { + var sequence = {}; + for (const child of this.children) { + if (child instanceof AnyElement) { + continue; + } + var description = child.description(definitions, xmlns); + for (var key in description) { + sequence[key] = description[key]; + } + } + return sequence; + } +} + +export class MessageElement extends Element { + public readonly allowedChildren = buildAllowedChildren([ + 'part', + 'documentation', + ]); + public element: ElementElement = null; + public parts = null; + + public postProcess(definitions: DefinitionsElement) { + var part: any = null; + var children = this.children || []; + + for (const child of children) { + if (child.name === 'part') { + part = child; + break; + } + } + + if (!part) { + return; + } + + if (part.$element) { + var lookupTypes: any[] = []; + + delete this.parts; + + const nsName = splitQName(part.$element); + const ns = nsName.prefix; + var schema = definitions.schemas[definitions.xmlns[ns]]; + this.element = schema.elements[nsName.name]; + if (!this.element) { + debug(nsName.name + " is not present in wsdl and cannot be processed correctly."); + return; + } + this.element.targetNSAlias = ns; + this.element.targetNamespace = definitions.xmlns[ns]; + + // set the optional $lookupType to be used within `client#_invoke()` when + // calling `wsdl#objectToDocumentXML() + this.element.$lookupType = part.$element; + + const elementChildren = this.element.children; + + // get all nested lookup types (only complex types are followed) + if (elementChildren.length > 0) { + for (let i = 0; i < elementChildren.length; i++) { + lookupTypes.push(this._getNestedLookupTypeString(elementChildren[i])); + } + } + + // if nested lookup types where found, prepare them for furter usage + if (lookupTypes.length > 0) { + lookupTypes = lookupTypes. + join('_'). + split('_'). + filter(function removeEmptyLookupTypes (type) { + return type !== '^'; + }); + + var schemaXmlns = definitions.schemas[this.element.targetNamespace].xmlns; + + for (let i = 0; i < lookupTypes.length; i++) { + lookupTypes[i] = this._createLookupTypeObject(lookupTypes[i], schemaXmlns); + } + } + + this.element.$lookupTypes = lookupTypes; + + if (this.element.$type) { + const type = splitQName(this.element.$type); + var typeNs = schema.xmlns && schema.xmlns[type.prefix] || definitions.xmlns[type.prefix]; + + if (typeNs) { + if (type.name in Primitives) { + // this.element = this.element.$type; + } + else { + // first check local mapping of ns alias to namespace + schema = definitions.schemas[typeNs]; + var ctype = schema.complexTypes[type.name] || schema.types[type.name] || schema.elements[type.name]; + + if (ctype) { + this.parts = ctype.description(definitions, schema.xmlns); + } + } + } + } + else { + var method = this.element.description(definitions, schema.xmlns); + this.parts = method[nsName.name]; + } + + this.children.splice(0, 1); + } else { + // rpc encoding + this.parts = {}; + delete this.element; + for (let i = 0; part = this.children[i]; i++) { + if (part.name === 'documentation') { + // + continue; + } + assert(part.name === 'part', 'Expected part element'); + const nsName = splitQName(part.$type); + const ns = definitions.xmlns[nsName.prefix]; + const type = nsName.name; + var schemaDefinition = definitions.schemas[ns]; + if (typeof schemaDefinition !== 'undefined') { + this.parts[part.$name] = definitions.schemas[ns].types[type] || definitions.schemas[ns].complexTypes[type]; + } else { + this.parts[part.$name] = part.$type; + } + + if (typeof this.parts[part.$name] === 'object') { + this.parts[part.$name].prefix = nsName.prefix; + this.parts[part.$name].xmlns = ns; + } + + this.children.splice(i--, 1); + } + } + this.deleteFixedAttrs(); + } + + public description(definitions: DefinitionsElement) { + if (this.element) { + return this.element && this.element.description(definitions); + } + var desc = {}; + desc[this.$name] = this.parts; + return desc; + } + + /** + * Takes a given namespaced String(for example: 'alias:property') and creates a lookupType + * object for further use in as first (lookup) `parameterTypeObj` within the `objectToXML` + * method and provides an entry point for the already existing code in `findChildSchemaObject`. + * + * @method _createLookupTypeObject + * @param {String} nsString The NS String (for example "alias:type"). + * @param {Object} xmlns The fully parsed `wsdl` definitions object (including all schemas). + * @returns {Object} + * @private + */ + private _createLookupTypeObject(nsString: string, xmlns: IXmlNs) { + var splittedNSString = splitQName(nsString), + nsAlias = splittedNSString.prefix, + splittedName = splittedNSString.name.split('#'), + type = splittedName[0], + name = splittedName[1]; + + return { + $namespace: xmlns[nsAlias], + $type: nsAlias + ':' + type, + $name: name, + }; + } + + /** + * Iterates through the element and every nested child to find any defined `$type` + * property and returns it in a underscore ('_') separated String (using '^' as default + * value if no `$type` property was found). + * + * @method _getNestedLookupTypeString + * @param {Object} element The element which (probably) contains nested `$type` values. + * @returns {String} + * @private + */ + private _getNestedLookupTypeString(element): string { + var resolvedType = '^', + excluded = this.ignoredNamespaces.concat('xs'); // do not process $type values wich start with + + if (element.hasOwnProperty('$type') && typeof element.$type === 'string') { + if (excluded.indexOf(element.$type.split(':')[0]) === -1) { + resolvedType += ('_' + element.$type + '#' + element.$name); + } + } + + if (element.children.length > 0) { + var self = this; + + element.children.forEach(function (child) { + var resolvedChildType = self._getNestedLookupTypeString(child).replace(/\^_/, ''); + + if (resolvedChildType && typeof resolvedChildType === 'string') { + resolvedType += ('_' + resolvedChildType); + } + }); + } + + return resolvedType; + } +} + +export class DocumentationElement extends Element { + // no children +} + +export interface IInclude { + namespace: string; + location: string; +} + +export class SchemaElement extends Element { + public readonly allowedChildren = buildAllowedChildren([ + 'complexType', + 'element', + 'import', + 'include', + 'simpleType', + ]); + public complexTypes: {[name: string]: ComplexTypeElement} = {}; + public types: {[name: string]: SimpleTypeElement} = {}; + public elements: {[name: string]: ElementElement} = {}; + public includes: IInclude[] = []; + public $elementFormDefault; + + public merge(source: SchemaElement) { + assert(source instanceof SchemaElement); + + var self = this; + + _.merge(this.complexTypes, source.complexTypes); + _.merge(this.types, source.types); + _.merge(this.elements, source.elements); + _.merge(this.xmlns, source.xmlns); + + // Merge attributes from source without overwriting our's + _.merge(this, _.pickBy(source, function(value, key) { + return key.startsWith('$') && !self.hasOwnProperty(key); + })); + + return this; + } + + public addChild(child: Element) { + if (child.$name in Primitives) + return; + if (child instanceof IncludeElement || child instanceof ImportElement) { + var location = child.$schemaLocation || child.$location; + if (location) { + this.includes.push({ + namespace: child.$namespace || child.$targetNamespace || this.$targetNamespace, + location: location, + }); + } + } + else if (child instanceof ComplexTypeElement) { + this.complexTypes[child.$name] = child; + } + else if (child instanceof ElementElement) { + this.elements[child.$name] = child; + } + else if (child instanceof SimpleTypeElement) { + this.types[child.$name] = child; + } + this.children.pop(); + // child.deleteFixedAttrs(); + } +} + +export class TypesElement extends Element { + public readonly allowedChildren = buildAllowedChildren([ + 'documentation', + 'schema', + ]); + public schemas: {[name: string]: SchemaElement} = {}; + + // fix#325 + public addChild(child) { + assert(child instanceof SchemaElement); + + var targetNamespace = child.$targetNamespace; + + if (!this.schemas.hasOwnProperty(targetNamespace)) { + this.schemas[targetNamespace] = child; + } else { + console.error('Target-Namespace "' + targetNamespace + '" already in use by another Schema!'); + } + } +} + +export class OperationElement extends Element { + public readonly allowedChildren = buildAllowedChildren([ + 'documentation', + 'fault', + 'input', + 'operation', + 'output', + ]); + public input: InputElement = null; + public output: OutputElement = null; + public inputSoap = null; + public outputSoap = null; + public style = ''; + public soapAction = ''; + public $soapAction?: string; + public $style?: string; + + public addChild(child) { + if (child instanceof OperationElement) { + this.soapAction = child.$soapAction || ''; + this.style = child.$style || ''; + this.children.pop(); + } + } + + public postProcess(definitions: DefinitionsElement, tag: string) { + var children = this.children; + for (var i = 0, child; child = children[i]; i++) { + if (child.name !== 'input' && child.name !== 'output') + continue; + if (tag === 'binding') { + this[child.name] = child; + children.splice(i--, 1); + continue; + } + var messageName = splitQName(child.$message).name; + var message = definitions.messages[messageName]; + message.postProcess(definitions); + if (message.element) { + definitions.messages[message.element.$name] = message; + this[child.name] = message.element; + } + else { + this[child.name] = message; + } + children.splice(i--, 1); + } + this.deleteFixedAttrs(); + } + + public description(definitions: DefinitionsElement) { + var inputDesc = this.input ? this.input.description(definitions) : null; + var outputDesc = this.output ? this.output.description(definitions) : null; + return { + input: inputDesc && inputDesc[Object.keys(inputDesc)[0]], + output: outputDesc && outputDesc[Object.keys(outputDesc)[0]], + }; + } +} + +export class PortTypeElement extends Element { + public readonly allowedChildren = buildAllowedChildren([ + 'documentation', + 'operation', + ]); + public methods: { + [name: string]: OperationElement; + } = {}; + + public postProcess(definitions: DefinitionsElement) { + var children = this.children; + if (typeof children === 'undefined') + return; + for (var i = 0, child; child = children[i]; i++) { + if (child.name !== 'operation') + continue; + child.postProcess(definitions, 'portType'); + this.methods[child.$name] = child; + children.splice(i--, 1); + } + delete this.$name; + this.deleteFixedAttrs(); + } + + public description(definitions: DefinitionsElement) { + var methods = {}; + for (var name in this.methods) { + var method = this.methods[name]; + methods[name] = method.description(definitions); + } + return methods; + } +} + +export interface ITopElement { + methodName: string; + outputName: string; +} + +export interface ITopElements { + [name: string]: ITopElement; +} + +export class BindingElement extends Element { + public readonly allowedChildren = buildAllowedChildren([ + 'binding', + 'documentation', + 'operation', + 'SecuritySpec', + ]); + public topElements?: ITopElements; + public transport = ''; + public style = ''; + public methods: {[name: string]: OperationElement} = {}; + public $type?: string; + + public addChild(child) { + if (child.name === 'binding') { + this.transport = child.$transport; + this.style = child.$style; + this.children.pop(); + } + } + + public postProcess(definitions: DefinitionsElement) { + var type = splitQName(this.$type).name, + portType = definitions.portTypes[type], + style = this.style, + children = this.children; + if (portType) { + portType.postProcess(definitions); + this.methods = portType.methods; + + for (var i = 0, child; child = children[i]; i++) { + if (child.name !== 'operation') + continue; + child.postProcess(definitions, 'binding'); + children.splice(i--, 1); + child.style || (child.style = style); + var method = this.methods[child.$name]; + + if (method) { + method.style = child.style; + method.soapAction = child.soapAction; + method.inputSoap = child.input || null; + method.outputSoap = child.output || null; + method.inputSoap && method.inputSoap.deleteFixedAttrs(); + method.outputSoap && method.outputSoap.deleteFixedAttrs(); + } + } + } + delete this.$name; + delete this.$type; + this.deleteFixedAttrs(); + } + + public description(definitions: DefinitionsElement) { + var methods = {}; + for (var name in this.methods) { + var method = this.methods[name]; + methods[name] = method.description(definitions); + } + return methods; + } +} + +export class PortElement extends Element { + public readonly allowedChildren = buildAllowedChildren([ + 'address', + 'documentation', + ]); + public location = null; + + public addChild(child) { + if (child.name === 'address' && typeof (child.$location) !== 'undefined') { + this.location = child.$location; + } + } +} + +export interface IPort { + location: string; + binding: BindingElement; +} + +export class ServiceElement extends Element { + public readonly allowedChildren = buildAllowedChildren([ + 'documentation', + 'port', + ]); + public ports: {[name: string]: IPort} = {}; + + public postProcess(definitions: DefinitionsElement) { + var children = this.children, + bindings = definitions.bindings; + if (children && children.length > 0) { + for (var i = 0, child; child = children[i]; i++) { + if (child.name !== 'port') + continue; + var bindingName = splitQName(child.$binding).name; + var binding = bindings[bindingName]; + if (binding) { + binding.postProcess(definitions); + this.ports[child.$name] = { + location: child.location, + binding: binding, + }; + children.splice(i--, 1); + } + } + } + delete this.$name; + this.deleteFixedAttrs(); + } + + public description(definitions: DefinitionsElement) { + var ports = {}; + for (var name in this.ports) { + var port = this.ports[name]; + ports[name] = port.binding.description(definitions); + } + return ports; + } +} + +export class DefinitionsElement extends Element { + public readonly allowedChildren = buildAllowedChildren([ + 'binding', + 'documentation', + 'import', + 'message', + 'portType', + 'service', + 'types', + ]); + public complexTypes; + public messages: {[name: string]: MessageElement} = {}; + public portTypes: {[name: string]: PortTypeElement} = {}; + public bindings: {[name: string]: BindingElement} = {}; + public services: {[name: string]: ServiceElement} = {}; + public schemas: {[name: string]: SchemaElement} = {}; + public descriptions: { + types: { + [key: string]: Element; + }, + } = { + types: {}, + }; + + public init() { + if (this.name !== 'definitions') this.unexpected(this.nsName); + } + + public addChild(child) { + var self = this; + if (child instanceof TypesElement) { + // Merge types.schemas into definitions.schemas + _.merge(self.schemas, child.schemas); + } + else if (child instanceof MessageElement) { + self.messages[child.$name] = child; + } + else if (child.name === 'import') { + const schemaElement = new SchemaElement(child.$namespace, {}); + schemaElement.init(); + self.schemas[child.$namespace] = schemaElement; + self.schemas[child.$namespace].addChild(child); + } + else if (child instanceof PortTypeElement) { + self.portTypes[child.$name] = child; + } + else if (child instanceof BindingElement) { + if (child.transport === 'http://schemas.xmlsoap.org/soap/http' || + child.transport === 'http://www.w3.org/2003/05/soap/bindings/HTTP/') + self.bindings[child.$name] = child; + } + else if (child instanceof ServiceElement) { + self.services[child.$name] = child; + } + else if (child instanceof DocumentationElement) { + } + this.children.pop(); + } +} + +export class BodyElement extends Element { + public $use?: string; + public $encodingStyle?: string; +} + +export class IncludeElement extends Element { + public $schemaLocation?; + public $location?; + public $namespace?; +} + +export class ImportElement extends Element { + public $schemaLocation?; + public $location?; + public $namespace?; +} + +var ElementTypeMap: { + [k: string]: typeof Element; +} = { + // group: [GroupElement, 'element group'], + all: AllElement, + any: AnyElement, + binding: BindingElement, + body: BodyElement, + choice: ChoiceElement, + complexContent: ComplexContentElement, + complexType: ComplexTypeElement, + definitions: DefinitionsElement, + documentation: DocumentationElement, + element: ElementElement, + enumeration: EnumerationElement, + extension: ExtensionElement, + fault: Element, + import: ImportElement, + include: IncludeElement, + input: InputElement, + message: MessageElement, + operation: OperationElement, + output: OutputElement, + port: PortElement, + portType: PortTypeElement, + restriction: RestrictionElement, + schema: SchemaElement, + sequence: SequenceElement, + service: ServiceElement, + simpleContent: SimpleContentElement, + simpleType: SimpleTypeElement, + types: TypesElement, +}; + +function buildAllowedChildren(elementList: string[]): {[k: string]: typeof Element} { + var rtn = {}; + for (const element of elementList) { + rtn[element.replace(/^_/, '')] = ElementTypeMap[element] || Element; + } + return rtn; +} diff --git a/src/wsdl/index.ts b/src/wsdl/index.ts new file mode 100644 index 000000000..8faf34389 --- /dev/null +++ b/src/wsdl/index.ts @@ -0,0 +1,1379 @@ +/* + * Copyright (c) 2011 Vinay Pulim + * MIT Licensed + * + */ +/*jshint proto:true*/ + +import { ok as assert } from 'assert'; +import * as debugBuilder from 'debug'; +import * as fs from 'fs'; +import * as _ from 'lodash'; +import * as path from 'path'; +import * as sax from 'sax'; +import * as stripBom from 'strip-bom'; +import * as url from 'url'; +import { HttpClient } from '../http'; +import { NamespaceContext } from '../nscontext'; +import { IOptions } from '../types'; +import { findPrefix, splitQName, TNS_PREFIX } from '../utils'; +import * as elements from './elements'; + +const debug = debugBuilder('node-soap'); + +function xmlEscape(obj) { + if (typeof (obj) === 'string') { + if (obj.substr(0, 9) === '") { + return obj; + } + return obj + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + } + + return obj; +} + +const trimLeft = /^[\s\xA0]+/; +const trimRight = /[\s\xA0]+$/; + +function trim(text) { + return text.replace(trimLeft, '').replace(trimRight, ''); +} + +function deepMerge(destination: A, source: B): A & B { + return _.mergeWith(destination, source, function(a, b) { + return _.isArray(a) ? a.concat(b) : undefined; + }); +} + +function appendColon(ns: string): string { + return (ns && ns.charAt(ns.length - 1) !== ':') ? ns + ':' : ns; +} + +function noColonNameSpace(ns: string): string { + return (ns && ns.charAt(ns.length - 1) === ':') ? ns.substring(0, ns.length - 1) : ns; +} + +interface IInitializedOptions extends IOptions { + ignoredNamespaces?: string[]; +} + +export class WSDL { + public ignoredNamespaces = ['tns', 'targetNamespace', 'typedNamespace']; + public ignoreBaseNameSpaces = false; + public valueKey = '$value'; + public xmlKey = '$xml'; + public xmlnsInEnvelope: string; + public uri: string; + public definitions: elements.DefinitionsElement; + public options: IInitializedOptions; + /** @internal */ + public WSDL_CACHE; + + private callback: (err: Error, caller?) => void; + private services: {[name: string]: elements.ServiceElement}; + private xml: string; + private _includesWsdl; + private _originalIgnoredNamespaces; + + constructor(definition: any, uri: string, options: IOptions) { + var self = this, + fromFunc; + + this.uri = uri; + this.callback = function() { + }; + this._includesWsdl = []; + + // initialize WSDL cache + this.WSDL_CACHE = {}; + if (options && options.WSDL_CACHE) { + this.WSDL_CACHE = options.WSDL_CACHE; + } + + this._initializeOptions(options); + + if (typeof definition === 'string') { + definition = stripBom(definition); + fromFunc = this._fromXML; + } + else if (typeof definition === 'object') { + fromFunc = this._fromServices; + } + else { + throw new Error('WSDL constructor takes either an XML string or service definition'); + } + + process.nextTick(function() { + try { + fromFunc.call(self, definition); + } catch (e) { + return self.callback(e); + } + + self.processIncludes(function(err) { + var name; + if (err) { + return self.callback(err); + } + + self.definitions.deleteFixedAttrs(); + var services = self.services = self.definitions.services; + if (services) { + for (name in services) { + services[name].postProcess(self.definitions); + } + } + var complexTypes = self.definitions.complexTypes; + if (complexTypes) { + for (name in complexTypes) { + complexTypes[name].deleteFixedAttrs(); + } + } + + // for document style, for every binding, prepare input message element name to (methodName, output message element name) mapping + var bindings = self.definitions.bindings; + for (var bindingName in bindings) { + var binding = bindings[bindingName]; + if (typeof binding.style === 'undefined') { + binding.style = 'document'; + } + if (binding.style !== 'document') + continue; + var methods = binding.methods; + var topEls: elements.ITopElements = binding.topElements = {}; + for (var methodName in methods) { + if (methods[methodName].input) { + var inputName = methods[methodName].input.$name; + var outputName = ""; + if (methods[methodName].output ) + outputName = methods[methodName].output.$name; + topEls[inputName] = {methodName: methodName, outputName: outputName}; + } + } + } + + // prepare soap envelope xmlns definition string + self.xmlnsInEnvelope = self._xmlnsMap(); + + self.callback(err, self); + }); + + }); + } + + public onReady(callback: (err: Error) => void): void { + if (callback) + this.callback = callback; + } + + public processIncludes(callback) { + var schemas = this.definitions.schemas, + includes: elements.IInclude[] = []; + + for (var ns in schemas) { + var schema = schemas[ns]; + includes = includes.concat(schema.includes || []); + } + + this._processNextInclude(includes, callback); + } + + public describeServices() { + var services = {}; + for (var name in this.services) { + var service = this.services[name]; + services[name] = service.description(this.definitions); + } + return services; + } + + public toXML() { + return this.xml || ''; + } + + public xmlToObject(xml, callback?) { + var self = this; + var p: any = typeof callback === 'function' ? {} : sax.parser(true, null); + var objectName = null; + var root: any = {}; + var schema = { + Envelope: { + Header: { + Security: { + UsernameToken: { + Username: 'string', + Password: 'string', + }, + }, + }, + Body: { + Fault: { + faultcode: 'string', + faultstring: 'string', + detail: 'string', + }, + }, + }, + }; + var stack: any[] = [{name: null, object: root, schema: schema}]; + var xmlns: any = {}; + + var refs = {}, id; // {id:{hrefs:[],obj:}, ...} + + p.onopentag = function(node) { + var nsName = node.name; + var attrs: any = node.attributes; + + var name = splitQName(nsName).name, + attributeName, + top = stack[stack.length - 1], + topSchema = top.schema, + elementAttributes = {}, + hasNonXmlnsAttribute = false, + hasNilAttribute = false, + obj = {}; + var originalName = name; + + if (!objectName && top.name === 'Body' && name !== 'Fault') { + var message = self.definitions.messages[name]; + // Support RPC/literal messages where response body contains one element named + // after the operation + 'Response'. See http://www.w3.org/TR/wsdl#_names + if (!message) { + try { + // Determine if this is request or response + var isInput = false; + var isOutput = false; + if ((/Response$/).test(name)) { + isOutput = true; + name = name.replace(/Response$/, ''); + } else if ((/Request$/).test(name)) { + isInput = true; + name = name.replace(/Request$/, ''); + } else if ((/Solicit$/).test(name)) { + isInput = true; + name = name.replace(/Solicit$/, ''); + } + // Look up the appropriate message as given in the portType's operations + var portTypes = self.definitions.portTypes; + var portTypeNames = Object.keys(portTypes); + // Currently this supports only one portType definition. + var portType = portTypes[portTypeNames[0]]; + if (isInput) { + name = portType.methods[name].input.$name; + } else { + name = portType.methods[name].output.$name; + } + message = self.definitions.messages[name]; + // 'cache' this alias to speed future lookups + self.definitions.messages[originalName] = self.definitions.messages[name]; + } catch (e) { + if (self.options.returnFault) { + p.onerror(e); + } + } + } + + topSchema = message.description(self.definitions); + objectName = originalName; + } + + if (attrs.href) { + id = attrs.href.substr(1); + if (!refs[id]) { + refs[id] = {hrefs: [], obj: null}; + } + refs[id].hrefs.push({par: top.object, key: name, obj: obj}); + } + if (id = attrs.id) { + if (!refs[id]) { + refs[id] = {hrefs: [], obj: null}; + } + } + + // Handle element attributes + for (attributeName in attrs) { + if (/^xmlns:|^xmlns$/.test(attributeName)) { + xmlns[splitQName(attributeName).name] = attrs[attributeName]; + continue; + } + hasNonXmlnsAttribute = true; + elementAttributes[attributeName] = attrs[attributeName]; + } + + for (attributeName in elementAttributes){ + var res = splitQName(attributeName); + if (res.name === 'nil' && xmlns[res.prefix] === 'http://www.w3.org/2001/XMLSchema-instance' && elementAttributes[attributeName] && + (elementAttributes[attributeName].toLowerCase() === 'true' || elementAttributes[attributeName] === '1') + ) { + hasNilAttribute = true; + break; + } + } + + if (hasNonXmlnsAttribute) { + obj[self.options.attributesKey] = elementAttributes; + } + + // Pick up the schema for the type specified in element's xsi:type attribute. + var xsiTypeSchema; + var xsiType = elementAttributes['xsi:type']; + if (xsiType) { + var type = splitQName(xsiType); + var typeURI; + if (type.prefix === TNS_PREFIX) { + // In case of xsi:type = "MyType" + typeURI = xmlns[type.prefix] || xmlns.xmlns; + } else { + typeURI = xmlns[type.prefix]; + } + var typeDef = self.findSchemaObject(typeURI, type.name); + if (typeDef) { + xsiTypeSchema = typeDef.description(self.definitions); + } + } + + if (topSchema && topSchema[name + '[]']) { + name = name + '[]'; + } + stack.push({name: originalName, object: obj, schema: (xsiTypeSchema || (topSchema && topSchema[name])), id: attrs.id, nil: hasNilAttribute}); + }; + + p.onclosetag = function(nsName) { + var cur: any = stack.pop(), + obj = cur.object, + top = stack[stack.length - 1], + topObject = top.object, + topSchema = top.schema, + name = splitQName(nsName).name; + + if (typeof cur.schema === 'string' && (cur.schema === 'string' || cur.schema.split(':')[1] === 'string')) { + if (typeof obj === 'object' && Object.keys(obj).length === 0) obj = cur.object = ''; + } + + if (cur.nil === true) { + if (self.options.handleNilAsNull) { + obj = null; + } else { + return; + } + } + + if (_.isPlainObject(obj) && !Object.keys(obj).length) { + obj = null; + } + + if (topSchema && topSchema[name + '[]']) { + if (!topObject[name]) { + topObject[name] = []; + } + topObject[name].push(obj); + } else if (name in topObject) { + if (!Array.isArray(topObject[name])) { + topObject[name] = [topObject[name]]; + } + topObject[name].push(obj); + } else { + topObject[name] = obj; + } + + if (cur.id) { + refs[cur.id].obj = obj; + } + }; + + p.oncdata = function (text) { + var originalText = text; + text = trim(text); + if (!text.length) { + return; + } + + if (/<\?xml[\s\S]+\?>/.test(text)) { + var top = stack[stack.length - 1]; + var value = self.xmlToObject(text); + if (top.object[self.options.attributesKey]) { + top.object[self.options.valueKey] = value; + } else { + top.object = value; + } + } else { + p.ontext(originalText); + } + }; + + p.onerror = function(e) { + p.resume(); + throw { + Fault: { + faultcode: 500, + faultstring: 'Invalid XML', + detail: new Error(e).message, + statusCode: 500, + }, + }; + }; + + p.ontext = function(text) { + var originalText = text; + text = trim(text); + if (!text.length) { + return; + } + + var top = stack[stack.length - 1]; + var name = splitQName(top.schema).name, + value; + + if (self.options && self.options.customDeserializer && self.options.customDeserializer[name]) { + value = self.options.customDeserializer[name](text, top); + } + else { + if (name === 'int' || name === 'integer') { + value = parseInt(text, 10); + } else if (name === 'bool' || name === 'boolean') { + value = text.toLowerCase() === 'true' || text === '1'; + } else if (name === 'dateTime' || name === 'date') { + value = new Date(text); + } else { + if (self.options.preserveWhitespace) { + text = originalText; + } + // handle string or other types + if (typeof top.object !== 'string') { + value = text; + } else { + value = top.object + text; + } + } + } + + if (top.object[self.options.attributesKey]) { + top.object[self.options.valueKey] = value; + } else { + top.object = value; + } + }; + + if (typeof callback === 'function') { + // we be streaming + var saxStream = sax.createStream(true, null); + saxStream.on('opentag', p.onopentag); + saxStream.on('closetag', p.onclosetag); + saxStream.on('cdata', p.oncdata); + saxStream.on('text', p.ontext); + xml.pipe(saxStream) + .on('error', function (err) { + callback(err); + }) + .on('end', function () { + var r; + try { + r = finish(); + } catch (e) { + return callback(e); + } + callback(null, r); + }); + return; + } + p.write(xml).close(); + + return finish(); + + function finish() { + // MultiRef support: merge objects instead of replacing + for (var n in refs) { + var ref = refs[n]; + for (var i = 0; i < ref.hrefs.length; i++) { + _.assign(ref.hrefs[i].obj, ref.obj); + } + } + + if (root.Envelope) { + var body = root.Envelope.Body; + if (body && body.Fault) { + var code = body.Fault.faultcode && body.Fault.faultcode.$value; + var string = body.Fault.faultstring && body.Fault.faultstring.$value; + var detail = body.Fault.detail && body.Fault.detail.$value; + + code = code || body.Fault.faultcode; + string = string || body.Fault.faultstring; + detail = detail || body.Fault.detail; + + var error: any = new Error(code + ': ' + string + (detail ? ': ' + JSON.stringify(detail) : '')); + + error.root = root; + throw error; + } + return root.Envelope; + } + return root; + } + } + + /** + * Look up a XSD type or element by namespace URI and name + * @param {String} nsURI Namespace URI + * @param {String} qname Local or qualified name + * @returns {*} The XSD type/element definition + */ + public findSchemaObject(nsURI: string, qname: string) { + if (!nsURI || !qname) { + return null; + } + + var def = null; + + if (this.definitions.schemas) { + var schema = this.definitions.schemas[nsURI]; + if (schema) { + if (qname.indexOf(':') !== -1) { + qname = qname.substring(qname.indexOf(':') + 1, qname.length); + } + + // if the client passed an input element which has a `$lookupType` property instead of `$type` + // the `def` is found in `schema.elements`. + def = schema.complexTypes[qname] || schema.types[qname] || schema.elements[qname]; + } + } + + return def; + } + + /** + * Create document style xml string from the parameters + * @param {String} name + * @param {*} params + * @param {String} nsPrefix + * @param {String} nsURI + * @param {String} type + */ + public objectToDocumentXML(name: string, params, nsPrefix: string, nsURI?: string, type?: string) { + // If user supplies XML already, just use that. XML Declaration should not be present. + if (params && params._xml) { + return params._xml; + } + var args = {}; + args[name] = params; + var parameterTypeObj = type ? this.findSchemaObject(nsURI, type) : null; + return this.objectToXML(args, null, nsPrefix, nsURI, true, null, parameterTypeObj); + } + + /** + * Create RPC style xml string from the parameters + * @param {String} name + * @param {*} params + * @param {String} nsPrefix + * @param {String} nsURI + * @returns {string} + */ + public objectToRpcXML(name: string, params, nsPrefix: string, nsURI: string, isParts?: boolean) { + var parts = []; + var defs = this.definitions; + var nsAttrName = '_xmlns'; + + nsPrefix = nsPrefix || findPrefix(defs.xmlns, nsURI); + + nsURI = nsURI || defs.xmlns[nsPrefix]; + nsPrefix = nsPrefix === TNS_PREFIX ? '' : (nsPrefix + ':'); + + parts.push(['<', nsPrefix, name, '>'].join('')); + + for (var key in params) { + if (!params.hasOwnProperty(key)) { + continue; + } + if (key !== nsAttrName) { + var value = params[key]; + var prefixedKey = (isParts ? '' : nsPrefix) + key; + var attributes = []; + if (typeof value === 'object' && value.hasOwnProperty(this.options.attributesKey)) { + var attrs = value[this.options.attributesKey]; + for (var n in attrs) { + attributes.push(' ' + n + '=' + '"' + attrs[n] + '"'); + } + } + parts.push(['<', prefixedKey ].concat(attributes).concat('>').join('')); + parts.push((typeof value === 'object') ? this.objectToXML(value, key, nsPrefix, nsURI) : xmlEscape(value)); + parts.push([''].join('')); + } + } + parts.push([''].join('')); + return parts.join(''); + } + + public isIgnoredNameSpace(ns: string): boolean { + return this.options.ignoredNamespaces.indexOf(ns) > -1; + } + + public filterOutIgnoredNameSpace(ns: string): string { + var namespace = noColonNameSpace(ns); + return this.isIgnoredNameSpace(namespace) ? '' : namespace; + } + + /** + * Convert an object to XML. This is a recursive method as it calls itself. + * + * @param {Object} obj the object to convert. + * @param {String} name the name of the element (if the object being traversed is + * an element). + * @param {String} nsPrefix the namespace prefix of the object I.E. xsd. + * @param {String} nsURI the full namespace of the object I.E. http://w3.org/schema. + * @param {Boolean} isFirst whether or not this is the first item being traversed. + * @param {?} xmlnsAttr + * @param {?} parameterTypeObject + * @param {NamespaceContext} nsContext Namespace context + */ + public objectToXML(obj, name: string, nsPrefix: any, nsURI: string, isFirst?: boolean, xmlnsAttr?, schemaObject?, nsContext?: NamespaceContext) { + var self = this; + var schema = this.definitions.schemas[nsURI]; + + var parentNsPrefix = nsPrefix ? nsPrefix.parent : undefined; + if (typeof parentNsPrefix !== 'undefined') { + // we got the parentNsPrefix for our array. setting the namespace-variable back to the current namespace string + nsPrefix = nsPrefix.current; + } + + parentNsPrefix = noColonNameSpace(parentNsPrefix); + if (this.isIgnoredNameSpace(parentNsPrefix)) { + parentNsPrefix = ''; + } + + var soapHeader = !schema; + var qualified = schema && schema.$elementFormDefault === 'qualified'; + var parts = []; + var prefixNamespace = (nsPrefix || qualified) && nsPrefix !== TNS_PREFIX; + + var xmlnsAttrib = ''; + if (nsURI && isFirst) { + if (self.options.overrideRootElement && self.options.overrideRootElement.xmlnsAttributes) { + self.options.overrideRootElement.xmlnsAttributes.forEach(function(attribute) { + xmlnsAttrib += ' ' + attribute.name + '="' + attribute.value + '"'; + }); + } else { + if (prefixNamespace && !this.isIgnoredNameSpace(nsPrefix)) { + // resolve the prefix namespace + xmlnsAttrib += ' xmlns:' + nsPrefix + '="' + nsURI + '"'; + } + // only add default namespace if the schema elementFormDefault is qualified + if (qualified || soapHeader) xmlnsAttrib += ' xmlns="' + nsURI + '"'; + } + } + + if (!nsContext) { + nsContext = new NamespaceContext(); + nsContext.declareNamespace(nsPrefix, nsURI); + } else { + nsContext.pushContext(); + } + + // explicitly use xmlns attribute if available + if (xmlnsAttr && !(self.options.overrideRootElement && self.options.overrideRootElement.xmlnsAttributes)) { + xmlnsAttrib = xmlnsAttr; + } + + var ns = ''; + + if (self.options.overrideRootElement && isFirst) { + ns = self.options.overrideRootElement.namespace; + } else if (prefixNamespace && (qualified || isFirst || soapHeader) && !this.isIgnoredNameSpace(nsPrefix)) { + ns = nsPrefix; + } + + var i, n; + // start building out XML string. + if (Array.isArray(obj)) { + var nonSubNameSpace = ''; + var nameWithNsRegex = /^([^:]+):([^:]+)$/.exec(name); + if (nameWithNsRegex) { + nonSubNameSpace = nameWithNsRegex[1]; + name = nameWithNsRegex[2]; + } + + for (i = 0, n = obj.length; i < n; i++) { + var item = obj[i]; + var arrayAttr = self.processAttributes(item, nsContext), + correctOuterNsPrefix = nonSubNameSpace || parentNsPrefix || ns; // using the parent namespace prefix if given + + var body = self.objectToXML(item, name, nsPrefix, nsURI, false, null, schemaObject, nsContext); + + var openingTagParts = ['<', appendColon(correctOuterNsPrefix), name, arrayAttr, xmlnsAttrib]; + + if (body === '' && self.options.useEmptyTag) { + // Use empty (self-closing) tags if no contents + openingTagParts.push(' />'); + parts.push(openingTagParts.join('')); + } else { + openingTagParts.push('>'); + if (self.options.namespaceArrayElements || i === 0) { + parts.push(openingTagParts.join('')); + } + parts.push(body); + if (self.options.namespaceArrayElements || i === n - 1) { + parts.push([''].join('')); + } + } + } + } else if (typeof obj === 'object') { + for (name in obj) { + if (!obj.hasOwnProperty(name)) continue; + // don't process attributes as element + if (name === self.options.attributesKey) { + continue; + } + // Its the value of a xml object. Return it directly. + if (name === self.options.xmlKey){ + nsContext.popContext(); + return obj[name]; + } + // Its the value of an item. Return it directly. + if (name === self.options.valueKey) { + nsContext.popContext(); + return xmlEscape(obj[name]); + } + + var child = obj[name]; + if (typeof child === 'undefined') { + continue; + } + + var attr = self.processAttributes(child, nsContext); + + var value = ''; + var nonSubNameSpace = ''; + var emptyNonSubNameSpace = false; + + var nameWithNsRegex = /^([^:]+):([^:]+)$/.exec(name); + if (nameWithNsRegex) { + nonSubNameSpace = nameWithNsRegex[1] + ':'; + name = nameWithNsRegex[2]; + } else if (name[0] === ':') { + emptyNonSubNameSpace = true; + name = name.substr(1); + } + + if (isFirst) { + value = self.objectToXML(child, name, nsPrefix, nsURI, false, null, schemaObject, nsContext); + } else { + + if (self.definitions.schemas) { + if (schema) { + var childSchemaObject = self.findChildSchemaObject(schemaObject, name); + // find sub namespace if not a primitive + if (childSchemaObject && + ((childSchemaObject.$type && (childSchemaObject.$type.indexOf('xsd:') === -1)) || + childSchemaObject.$ref || childSchemaObject.$name)) { + /*if the base name space of the children is not in the ingoredSchemaNamspaces we use it. + This is because in some services the child nodes do not need the baseNameSpace. + */ + + var childNsPrefix: any = ''; + var childName = ''; + var childNsURI; + var childXmlnsAttrib = ''; + + var elementQName = childSchemaObject.$ref || childSchemaObject.$name; + if (elementQName) { + elementQName = splitQName(elementQName); + childName = elementQName.name; + if (elementQName.prefix === TNS_PREFIX) { + // Local element + childNsURI = childSchemaObject.$targetNamespace; + childNsPrefix = nsContext.registerNamespace(childNsURI); + if (this.isIgnoredNameSpace(childNsPrefix)) { + childNsPrefix = nsPrefix; + } + } else { + childNsPrefix = elementQName.prefix; + if (this.isIgnoredNameSpace(childNsPrefix)) { + childNsPrefix = nsPrefix; + } + childNsURI = schema.xmlns[childNsPrefix] || self.definitions.xmlns[childNsPrefix]; + } + + var unqualified = false; + // Check qualification form for local elements + if (childSchemaObject.$name && childSchemaObject.targetNamespace === undefined) { + if (childSchemaObject.$form === 'unqualified') { + unqualified = true; + } else if (childSchemaObject.$form === 'qualified') { + unqualified = false; + } else { + unqualified = schema.$elementFormDefault !== 'qualified'; + } + } + if (unqualified) { + childNsPrefix = ''; + } + + if (childNsURI && childNsPrefix) { + if (nsContext.declareNamespace(childNsPrefix, childNsURI)) { + childXmlnsAttrib = ' xmlns:' + childNsPrefix + '="' + childNsURI + '"'; + xmlnsAttrib += childXmlnsAttrib; + } + } + } + + var resolvedChildSchemaObject; + if (childSchemaObject.$type) { + var typeQName = splitQName(childSchemaObject.$type); + var typePrefix = typeQName.prefix; + var typeURI = schema.xmlns[typePrefix] || self.definitions.xmlns[typePrefix]; + childNsURI = typeURI; + if (typeURI !== 'http://www.w3.org/2001/XMLSchema' && typePrefix !== TNS_PREFIX) { + // Add the prefix/namespace mapping, but not declare it + nsContext.addNamespace(typePrefix, typeURI); + } + resolvedChildSchemaObject = + self.findSchemaType(typeQName.name, typeURI) || childSchemaObject; + } else { + resolvedChildSchemaObject = + self.findSchemaObject(childNsURI, childName) || childSchemaObject; + } + + if (childSchemaObject.$baseNameSpace && this.options.ignoreBaseNameSpaces) { + childNsPrefix = nsPrefix; + childNsURI = nsURI; + } + + if (this.options.ignoreBaseNameSpaces) { + childNsPrefix = ''; + childNsURI = ''; + } + + ns = childNsPrefix; + + if (Array.isArray(child)) { + // for arrays, we need to remember the current namespace + childNsPrefix = { + current: childNsPrefix, + parent: ns, + }; + } else { + // parent (array) already got the namespace + childXmlnsAttrib = null; + } + + value = self.objectToXML(child, name, childNsPrefix, childNsURI, + false, childXmlnsAttrib, resolvedChildSchemaObject, nsContext); + } else if (obj[self.options.attributesKey] && obj[self.options.attributesKey].xsi_type) { + // if parent object has complex type defined and child not found in parent + var completeChildParamTypeObject = self.findChildSchemaObject( + obj[self.options.attributesKey].xsi_type.type, + obj[self.options.attributesKey].xsi_type.xmlns); + + nonSubNameSpace = obj[self.options.attributesKey].xsi_type.prefix; + nsContext.addNamespace(obj[self.options.attributesKey].xsi_type.prefix, + obj[self.options.attributesKey].xsi_type.xmlns); + value = self.objectToXML(child, name, obj[self.options.attributesKey].xsi_type.prefix, + obj[self.options.attributesKey].xsi_type.xmlns, false, null, null, nsContext); + } else { + if (Array.isArray(child)) { + name = nonSubNameSpace + name; + } + + value = self.objectToXML(child, name, nsPrefix, nsURI, false, null, null, nsContext); + } + } else { + value = self.objectToXML(child, name, nsPrefix, nsURI, false, null, null, nsContext); + } + } + } + + ns = noColonNameSpace(ns); + if (prefixNamespace && !qualified && isFirst && !self.options.overrideRootElement) { + ns = nsPrefix; + } else if (this.isIgnoredNameSpace(ns)) { + ns = ''; + } + + var useEmptyTag = !value && self.options.useEmptyTag; + if (!Array.isArray(child)) { + // start tag + parts.push(['<', emptyNonSubNameSpace ? '' : appendColon(nonSubNameSpace || ns), name, attr, xmlnsAttrib, + (child === null ? ' xsi:nil="true"' : ''), + useEmptyTag ? ' />' : '>', + ].join('')); + } + + if (!useEmptyTag) { + parts.push(value); + if (!Array.isArray(child)) { + // end tag + parts.push([''].join('')); + } + } + } + } else if (obj !== undefined) { + parts.push((self.options.escapeXML) ? xmlEscape(obj) : obj); + } + nsContext.popContext(); + return parts.join(''); + } + + public processAttributes(child: any, nsContext: NamespaceContext): string { + var attr = ''; + + if (child === null) { + child = []; + } + + var attrObj = child[this.options.attributesKey]; + if (attrObj && attrObj.xsi_type) { + var xsiType = attrObj.xsi_type; + + var prefix = xsiType.prefix || xsiType.namespace; + // Generate a new namespace for complex extension if one not provided + if (!prefix) { + prefix = nsContext.registerNamespace(xsiType.xmlns); + } else { + nsContext.declareNamespace(prefix, xsiType.xmlns); + } + xsiType.prefix = prefix; + } + + if (attrObj) { + for (var attrKey in attrObj) { + // handle complex extension separately + if (attrKey === 'xsi_type') { + var attrValue = attrObj[attrKey]; + attr += ' xsi:type="' + attrValue.prefix + ':' + attrValue.type + '"'; + attr += ' xmlns:' + attrValue.prefix + '="' + attrValue.xmlns + '"'; + + continue; + } else { + attr += ' ' + attrKey + '="' + xmlEscape(attrObj[attrKey]) + '"'; + } + } + } + + return attr; + } + + /** + * Look up a schema type definition + * @param name + * @param nsURI + * @returns {*} + */ + public findSchemaType(name: any, nsURI: any): any { + if (!this.definitions.schemas || !name || !nsURI) { + return null; + } + + var schema = this.definitions.schemas[nsURI]; + if (!schema || !schema.complexTypes) { + return null; + } + + return schema.complexTypes[name]; + } + + public findChildSchemaObject(parameterTypeObj: any, childName: any, backtrace?: any): any { + if (!parameterTypeObj || !childName) { + return null; + } + + if (!backtrace) { + backtrace = []; + } + + if (backtrace.indexOf(parameterTypeObj) >= 0) { + // We've recursed back to ourselves; break. + return null; + } else { + backtrace = backtrace.concat([parameterTypeObj]); + } + + var found = null, + i = 0, + child, + ref; + + if (Array.isArray(parameterTypeObj.$lookupTypes) && parameterTypeObj.$lookupTypes.length) { + var types = parameterTypeObj.$lookupTypes; + + for (i = 0; i < types.length; i++) { + var typeObj = types[i]; + + if (typeObj.$name === childName) { + found = typeObj; + break; + } + } + } + + var object = parameterTypeObj; + if (object.$name === childName && object.name === 'element') { + return object; + } + if (object.$ref) { + ref = splitQName(object.$ref); + if (ref.name === childName) { + return object; + } + } + + var childNsURI; + + // want to avoid unecessary recursion to improve performance + if (object.$type && backtrace.length === 1) { + var typeInfo = splitQName(object.$type); + if (typeInfo.prefix === TNS_PREFIX) { + childNsURI = parameterTypeObj.$targetNamespace; + } else { + childNsURI = this.definitions.xmlns[typeInfo.prefix]; + } + var typeDef = this.findSchemaType(typeInfo.name, childNsURI); + if (typeDef) { + return this.findChildSchemaObject(typeDef, childName, backtrace); + } + } + + if (object.children) { + for (i = 0, child; child = object.children[i]; i++) { + found = this.findChildSchemaObject(child, childName, backtrace); + if (found) { + break; + } + + if (child.$base) { + var baseQName = splitQName(child.$base); + var childNameSpace = baseQName.prefix === TNS_PREFIX ? '' : baseQName.prefix; + childNsURI = child.xmlns[baseQName.prefix] || this.definitions.xmlns[baseQName.prefix]; + + var foundBase = this.findSchemaType(baseQName.name, childNsURI); + + if (foundBase) { + found = this.findChildSchemaObject(foundBase, childName, backtrace); + + if (found) { + found.$baseNameSpace = childNameSpace; + found.$type = childNameSpace + ':' + childName; + break; + } + } + } + } + + } + + if (!found && object.$name === childName) { + return object; + } + + return found; + } + + private _initializeOptions(options: any) { + this._originalIgnoredNamespaces = (options || {}).ignoredNamespaces; + this.options = {}; + + var ignoredNamespaces = options ? options.ignoredNamespaces : null; + + if (ignoredNamespaces && + (Array.isArray(ignoredNamespaces.namespaces) || typeof ignoredNamespaces.namespaces === 'string')) { + if (ignoredNamespaces.override) { + this.options.ignoredNamespaces = ignoredNamespaces.namespaces; + } else { + this.options.ignoredNamespaces = this.ignoredNamespaces.concat(ignoredNamespaces.namespaces); + } + } else { + this.options.ignoredNamespaces = this.ignoredNamespaces; + } + + this.options.valueKey = options.valueKey || this.valueKey; + this.options.xmlKey = options.xmlKey || this.xmlKey; + if (options.escapeXML !== undefined) { + this.options.escapeXML = options.escapeXML; + } else { + this.options.escapeXML = true; + } + if (options.returnFault !== undefined) { + this.options.returnFault = options.returnFault; + } else { + this.options.returnFault = false; + } + this.options.handleNilAsNull = !!options.handleNilAsNull; + + if (options.namespaceArrayElements !== undefined) { + this.options.namespaceArrayElements = options.namespaceArrayElements; + } else { + this.options.namespaceArrayElements = true; + } + + // Allow any request headers to keep passing through + this.options.wsdl_headers = options.wsdl_headers; + this.options.wsdl_options = options.wsdl_options; + if (options.httpClient) { + this.options.httpClient = options.httpClient; + } + + // The supplied request-object should be passed through + if (options.request) { + this.options.request = options.request; + } + + var ignoreBaseNameSpaces = options ? options.ignoreBaseNameSpaces : null; + if (ignoreBaseNameSpaces !== null && typeof ignoreBaseNameSpaces !== 'undefined') { + this.options.ignoreBaseNameSpaces = ignoreBaseNameSpaces; + } else { + this.options.ignoreBaseNameSpaces = this.ignoreBaseNameSpaces; + } + + // Works only in client + this.options.forceSoap12Headers = options.forceSoap12Headers; + this.options.customDeserializer = options.customDeserializer; + + if (options.overrideRootElement !== undefined) { + this.options.overrideRootElement = options.overrideRootElement; + } + + this.options.useEmptyTag = !!options.useEmptyTag; + } + + private _processNextInclude(includes: elements.IInclude[], callback) { + var self = this, + include = includes.shift(); + + if (!include) + return callback(); + + var includePath: string; + if (!/^https?:/.test(self.uri) && !/^https?:/.test(include.location)) { + includePath = path.resolve(path.dirname(self.uri), include.location); + } else { + includePath = url.resolve(self.uri || '', include.location); + } + + const options = _.assign({}, this.options); + // follow supplied ignoredNamespaces option + options.ignoredNamespaces = this._originalIgnoredNamespaces || this.options.ignoredNamespaces; + options.WSDL_CACHE = this.WSDL_CACHE; + + open_wsdl_recursive(includePath, options, function(err, wsdl) { + if (err) { + return callback(err); + } + + self._includesWsdl.push(wsdl); + + if (wsdl.definitions instanceof elements.DefinitionsElement) { + _.mergeWith(self.definitions, wsdl.definitions, function(a, b) { + return (a instanceof elements.SchemaElement) ? a.merge(b) : undefined; + }); + } else { + return callback(new Error('wsdl.defintions is not an instance of elements.DefinitionsElement')); + } + + self._processNextInclude(includes, function(err) { + callback(err); + }); + }); + } + + private _parse(xml): elements.DefinitionsElement { + var self = this, + p = sax.parser(true, null), + stack: elements.Element[] = [], + root: elements.DefinitionsElement = null, + types: elements.TypesElement = null, + schema: elements.SchemaElement = null, + schemaAttrs = null, + options = self.options; + + p.onopentag = function(node) { + var nsName = node.name; + var attrs = node.attributes; + + var top = stack[stack.length - 1]; + var name = splitQName(nsName).name; + + if (name === 'schema') { + schemaAttrs = attrs; + } + if (top) { + try { + top.startElement(stack, nsName, attrs, options, schemaAttrs); + } catch (e) { + if (self.options.strict) { + throw e; + } else { + stack.push(new elements.Element(nsName, attrs, options, schemaAttrs)); + } + } + } else { + if (name === 'definitions') { + root = new elements.DefinitionsElement(nsName, attrs, options); + stack.push(root); + } else if (name === 'schema') { + // Shim a structure in here to allow the proper objects to be created when merging back. + root = new elements.DefinitionsElement('definitions', {}, {}); + types = new elements.TypesElement('types', {}, {}); + schema = new elements.SchemaElement(nsName, attrs, options); + types.addChild(schema); + root.addChild(types); + stack.push(schema); + } else { + throw new Error('Unexpected root element of WSDL or include'); + } + } + }; + + p.onclosetag = function(name) { + var top = stack[stack.length - 1]; + assert(top, 'Unmatched close tag: ' + name); + + top.endElement(stack, name); + }; + + p.write(xml).close(); + + return root; + } + + private _fromXML(xml: string): void { + this.definitions = this._parse(xml); + this.definitions.descriptions = { + types: {}, + }; + this.xml = xml; + } + + private _fromServices(services): void { + + } + + private _xmlnsMap(): string { + var xmlns = this.definitions.xmlns; + var str = ''; + for (var alias in xmlns) { + if (alias === '' || alias === TNS_PREFIX) { + continue; + } + var ns = xmlns[alias]; + switch (ns) { + case "http://xml.apache.org/xml-soap" : // apachesoap + case "http://schemas.xmlsoap.org/wsdl/" : // wsdl + case "http://schemas.xmlsoap.org/wsdl/soap/" : // wsdlsoap + case "http://schemas.xmlsoap.org/wsdl/soap12/": // wsdlsoap12 + case "http://schemas.xmlsoap.org/soap/encoding/" : // soapenc + case "http://www.w3.org/2001/XMLSchema" : // xsd + continue; + } + if (~ns.indexOf('http://schemas.xmlsoap.org/')) { + continue; + } + if (~ns.indexOf('http://www.w3.org/')) { + continue; + } + if (~ns.indexOf('http://xml.apache.org/')) { + continue; + } + str += ' xmlns:' + alias + '="' + ns + '"'; + } + return str; + } +} + +type WSDLCallback = (error: any, result?: WSDL) => any; + +/* + * Have another function to load previous WSDLs as we + * don't want this to be invoked externally (expect for tests) + * This will attempt to fix circular dependencies with XSD files, + * Given + * - file.wsdl + * - xs:import namespace="A" schemaLocation: A.xsd + * - A.xsd + * - xs:import namespace="B" schemaLocation: B.xsd + * - B.xsd + * - xs:import namespace="A" schemaLocation: A.xsd + * file.wsdl will start loading, import A, then A will import B, which will then import A + * Because A has already started to load previously it will be returned right away and + * have an internal circular reference + * B would then complete loading, then A, then file.wsdl + * By the time file A starts processing its includes its definitions will be already loaded, + * this is the only thing that B will depend on when "opening" A + */ +function open_wsdl_recursive(uri: any, callback: WSDLCallback); +function open_wsdl_recursive(uri: any, options: IOptions, callback: WSDLCallback); +function open_wsdl_recursive(uri: any, p2: WSDLCallback | IOptions, p3?: WSDLCallback) { + var fromCache, WSDL_CACHE; + let options: IOptions; + let callback: WSDLCallback; + + if (typeof p2 === 'function') { + options = {}; + callback = p2; + } else { + options = p2; + callback = p3; + } + + WSDL_CACHE = options.WSDL_CACHE; + + if (fromCache = WSDL_CACHE[ uri ]) { + return callback.call(fromCache, null, fromCache); + } + + return open_wsdl(uri, options, callback); +} + +export function open_wsdl(uri: any, callback: WSDLCallback); +export function open_wsdl(uri: any, options: IOptions, callback: WSDLCallback); +export function open_wsdl(uri: any, p2: WSDLCallback | IOptions, p3?: WSDLCallback) { + let options: IOptions; + let callback: WSDLCallback; + if (typeof p2 === 'function') { + options = {}; + callback = p2; + } else if (typeof p3 === 'function') { + options = p2; + callback = p3; + } + + // initialize cache when calling open_wsdl directly + var WSDL_CACHE = options.WSDL_CACHE || {}; + var request_headers = options.wsdl_headers; + var request_options = options.wsdl_options; + + var wsdl: WSDL; + if (!/^https?:/.test(uri)) { + debug('Reading file: %s', uri); + fs.readFile(uri, 'utf8', function(err, definition) { + if (err) { + callback(err); + } + else { + wsdl = new WSDL(definition, uri, options); + WSDL_CACHE[ uri ] = wsdl; + wsdl.WSDL_CACHE = WSDL_CACHE; + wsdl.onReady(callback); + } + }); + } + else { + debug('Reading url: %s', uri); + var httpClient = options.httpClient || new HttpClient(options); + httpClient.request(uri, null /* options */, function(err, response, definition) { + if (err) { + callback(err); + } else if (response && response.statusCode === 200) { + wsdl = new WSDL(definition, uri, options); + WSDL_CACHE[ uri ] = wsdl; + wsdl.WSDL_CACHE = WSDL_CACHE; + wsdl.onReady(callback); + } else { + callback(new Error('Invalid WSDL URL: ' + uri + "\n\n\r Code: " + response.statusCode + "\n\n\r Response Body: " + response.body)); + } + }, request_headers, request_options); + } + + return wsdl; +} diff --git a/test/client-customHttp-test.js b/test/client-customHttp-test.js index 7677cc7f2..b16efa26a 100644 --- a/test/client-customHttp-test.js +++ b/test/client-customHttp-test.js @@ -6,7 +6,7 @@ var fs = require('fs'), assert = require('assert'), duplexer = require('duplexer'), req = require('request'), - httpClient = require('../lib/http.js'), + httpClient = require('../lib/http.js').HttpClient, // stream = require('stream'), stream = require('readable-stream'), util = require('util'), diff --git a/test/client-customHttp-xsdinclude-test.js b/test/client-customHttp-xsdinclude-test.js index f8f8b28a1..b2314ff8b 100644 --- a/test/client-customHttp-xsdinclude-test.js +++ b/test/client-customHttp-xsdinclude-test.js @@ -4,7 +4,7 @@ var soap = require('..'), http = require('http'), assert = require('assert'), req = require('request'), - httpClient = require('../lib/http.js'), + httpClient = require('../lib/http.js').HttpClient, util = require('util'), events = require('events'), createSocketStream = require('./_socketStream'); diff --git a/test/request-response-samples-test.js b/test/request-response-samples-test.js index 165172720..d24e290ad 100644 --- a/test/request-response-samples-test.js +++ b/test/request-response-samples-test.js @@ -9,7 +9,7 @@ var timekeeper = require('timekeeper'); var jsdiff = require('diff'); require('colors'); var soap = require('../'); -var WSSecurity = require('../lib/security/WSSecurity'); +var WSSecurity = require('../lib/security').WSSecurity; var server; var port; var tests = glob.sync('./request-response-samples/*', {cwd:__dirname}) diff --git a/test/wsdl-test.js b/test/wsdl-test.js index 821b8a8eb..0f304b554 100644 --- a/test/wsdl-test.js +++ b/test/wsdl-test.js @@ -2,6 +2,7 @@ var fs = require('fs'), soap = require('..'), + WSDL = require('../lib/wsdl').WSDL, assert = require('assert'), sinon = require('sinon'); @@ -128,7 +129,7 @@ wsdlStrictTests['should handle type ref'] = function(done) { wsdlStrictTests['should parse POJO into xml without making unnecessary recursion'] = function(done) { var expectedMsg = require('./wsdl/perf/request.xml.js'); var reqJson = require('./wsdl/perf/request.json'); - var spy = sinon.spy(soap.WSDL.prototype, "findChildSchemaObject"); + var spy = sinon.spy(WSDL.prototype, "findChildSchemaObject"); soap.createClient(__dirname + '/wsdl/perf/order.wsdl', {strict: true}, function(err, client) { var i, spyCall; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..773e6143c --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compileOnSave": true, + "compilerOptions": { + "target": "es3", + "module": "commonjs", + "outDir": "lib", + "sourceMap": true, + "declaration": true, + "stripInternal": true + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules" + ] +} diff --git a/tslint.json b/tslint.json new file mode 100644 index 000000000..7135e5193 --- /dev/null +++ b/tslint.json @@ -0,0 +1,32 @@ +{ + "defaultSeverity": "error", + "extends": [ + "tslint:recommended" + ], + "jsRules": {}, + "rules": { + "quotemark": [false, "single"], + "variable-name": false, + "max-line-length": false, + "forin": false, + "one-variable-per-declaration": false, + "no-var-keyword": false, + "prefer-const": false, + "only-arrow-functions": false, + "one-line": false, + "prefer-for-of": false, + "curly": false, + "object-literal-sort-keys": false, + "no-var-requires": false, + "no-unused-expression": false, + "no-shadowed-variable": false, + "object-literal-shorthand": false, + "max-classes-per-file": false, + "no-empty": false, + "space-before-function-paren": false, + "no-conditional-assignment": false, + "no-bitwise": false, + "no-console": false + }, + "rulesDirectory": [] +}