UNPKG

soap-rawmessage

Version:

A minimal node SOAP client, fork which adds ability to send raw xml

1,706 lines (1,489 loc) 65.3 kB
/* * Copyright (c) 2011 Vinay Pulim <vinay@milewise.com> * 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 selectn = require('selectn'); 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) { var i = typeof nsName === 'string' ? nsName.indexOf(':') : -1; return i < 0 ? {prefix: TNS_PREFIX, name: nsName} : {prefix: nsName.substring(0, i), name: nsName.substring(i + 1)}; } function xmlEscape(obj) { if (typeof (obj) === 'string') { if (obj.substr(0,9) === '<![CDATA[' && obj.substr(-3) === "]]>") { return obj; } return obj .replace(/&/g, '&amp;') .replace(/</g, '&lt;') .replace(/>/g, '&gt;') .replace(/"/g, '&quot;') .replace(/'/g, '&apos;'); } return obj; } var trimLeft = /^[\s\xA0]+/; var trimRight = /[\s\xA0]+$/; function trim(text) { return text.replace(trimLeft, '').replace(trimRight, ''); } /** * What we want is to copy properties from one object to another one and avoid * properties overriding. This way we ensure the 'inheritance' of * <xsd:extension base=...> usage. * * NB: 'Element' (and subtypes) don't have any prototyped properties: there's * no need to process a 'hasOwnProperties' call, we should just iterate over the * keys. */ function extend(base, obj) { if(base !== null && typeof base === "object" && obj !== null && typeof obj === "object"){ Object.keys(obj).forEach(function(key) { if(!base.hasOwnProperty(key)) base[key] = obj[key]; }); } return base; } function deepMerge(destination, source) { return _.merge(destination || {}, source, function(a, b) { return _.isArray(a) ? a.concat(b) : undefined; }); } var Element = function(nsName, attrs, options) { var parts = splitQName(nsName); this.nsName = nsName; this.prefix = parts.prefix; this.name = parts.name; this.children = []; this.xmlns = {}; 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]; } } } 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) { if (!this.allowedChildren) { return; } var ChildClass = this.allowedChildren[splitQName(nsName).name], element = null; if (ChildClass) { stack.push(new ChildClass(nsName, attrs, options)); } 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]) { extend(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'], 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); if (this.$targetNamespace === source.$targetNamespace) { _.merge(this.complexTypes, source.complexTypes); _.merge(this.types, source.types); _.merge(this.elements, source.elements); _.merge(this.xmlns, source.xmlns); } 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') { // <wsdl:documentation can be present under <wsdl:message> 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(); } 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 + "|" : ""; return base + this.children.map(function(child) { return child.description(); }).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); extend(desc, base); } } } 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], 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) { 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.message); } 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; } // 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; } 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; if (options.overrideRootElement !== undefined) { this.options.overrideRootElement = options.overrideRootElement; } }; 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) { _.merge(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) { var self = this; var p = 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) { // 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]; } 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') { 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) { 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) { 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(text); } }; p.onerror = function(e) { p.resume(); throw { Fault: { faultcode: 500, faultstring: 'Invalid XML', detail: new Error(e).message, statusCode: 500 } }; }; p.ontext = function(text) { text = trim(text); if (!text.length) { return; } var top = stack[stack.length - 1]; var name = splitQName(top.schema).name, value; 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 { // 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; } }; p.write(xml).close(); // merge obj with href var merge = function(href, obj) { for (var j in obj) { if (obj.hasOwnProperty(j)) { href.obj[j] = obj[j]; } } }; // 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++) { merge(ref.hrefs[i], ref.obj); } } if (root.Envelope) { var body = root.Envelope.Body; if (body.Fault) { var code = selectn('faultcode.$value', body.Fault) || selectn('faultcode', body.Fault); var string = selectn('faultstring.$value', body.Fault) || selectn('faultstring', body.Fault); var detail = selectn('detail.$value', body.Fault) || selectn('detail.message', body.Fault); var error = new Error(code + ': ' + string + (detail ? ': ' + 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) { 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; parts.push(['<', prefixedKey, '>'].join('')); parts.push((typeof value === 'object') ? this.objectToXML(value, key, nsPrefix, nsURI) : xmlEscape(value)); parts.push(['</', prefixedKey, '>'].join('')); } } parts.push(['</', nsPrefix, name, '>'].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)) { for (i = 0, n = obj.length; i < n; i++) { var item = obj[i]; var arrayAttr = self.processAttributes(item, nsContext), correctOuterNsPrefix = parentNsPrefix || ns; //using the parent namespace prefix if given parts.push(['<', appendColon(correctOuterNsPrefix), name, arrayAttr, xmlnsAttrib, '>'].join('')); parts.push(self.objectToXML(item, name, nsPrefix, nsURI, false, null, schemaObject, nsContext)); parts.push(['</', appendColon(correctOuterNsPrefix), name, '>'].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)